Commit 2fc2a72a authored by Paul Schütze's avatar Paul Schütze
Browse files

Merge branch 'f-reflection-cs-bb' into 'master'

Charge carrier reflection on surface

See merge request allpix-squared/allpix-squared!1041
parents de842a94 68cce96e
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ It should be noted that generating the animations is time-consuming and should b
* `detrapping_model`: Model for simulating charge carrier detrapping from radiation-induced damage. Defaults to `none`, a list of available models can be found in the documentation.
* `fluence`: 1MeV-neutron equivalent fluence the sensor has been exposed to.
* `charge_per_step`: Maximum number of charge carriers to propagate together. Divides the total number of deposited charge carriers at a specific point into sets of this number of charge carriers and a set with the remaining charge carriers. A value of 10 charges per step is used by default if this value is not specified.
* `max_charge_groups`: Maximum number of charge groups to propagate from a single deposit point. Temporarily increases the value of `charge_per_step` to reduce the number of propagated groups if the deposit is larger than the value `max_charge_groups`*`charge_per_step`, thus reducing the negative performance impact of unexpectedly large deposits. The default value is 1000 charge groups. If it is set to 0, there is no upper limit on the number of charge groups propagated.
* `max_charge_groups`: Maximum number of charge groups to propagate from a single deposit point. Temporarily increases the value of `charge_per_step` to reduce the number of propagated groups if the deposit is larger than the value `max_charge_groups * charge_per_step`, thus reducing the negative performance impact of unexpectedly large deposits. The default value is 1000 charge groups. If it is set to 0, there is no upper limit on the number of charge groups propagated.
* `timestep`: Time step for the Runge-Kutta integration, representing the granularity with which the induced charge is calculated. Default value is 0.01ns.
* `integration_time`: Time within which charge carriers are propagated. After exceeding this time, no further propagation is performed for the respective carriers. Defaults to the LHC bunch crossing time of 25ns.
* `distance`: Maximum distance of pixels to be considered for current induction, calculated from the pixel the charge carrier under investigation is below. A distance of `1` for example means that the induced current for the closest pixel plus all neighbors is calculated. It should be noted that the time required for simulating a single event depends almost linearly on the number of pixels the induced charge is calculated for. Usually, for Cartesian sensors a 3x3 grid (9 pixels, distance 1) should suffice since the weighting potential at a distance of more than one pixel pitch often is small enough to be neglected while the simulation time is almost tripled for `distance = 2` (5x5 grid, 25 pixels). To just calculate the induced current in the one pixel the charge carrier is below, `distance = 0` can be used. Defaults to `1`.
@@ -64,7 +64,7 @@ It should be noted that generating the animations is time-consuming and should b
* `multiplication_model`: Model used to calculate impact ionization parameters and charge multiplication. Defaults to `none` which corresponds to unity gain, a list of available models can be found in the documentation.
* `multiplication_threshold`: Threshold field above which charge multiplication is calculated. Defaults to `100kV/cm`.
* `max_multiplication_level`: Maximum level depth of the generated impact ionization charge multiplication shower after which the generation of further multiplication charge carrier levels is prohibited. This number represents the maximum number of daughter charge carrier groups that can be produced by one initial charge carrier group. This does not concern the size of the charge group itself but solely the level of generation. If a group generates a secondary group through impact ionization, the depth is `1`. If this secondary group again creates charge carriers when propagating, the level is `2` and so on. The default value is `5`.

* `surface_reflectivity`: Reflectivity of the sensor surface for charge carriers. Used to calculate a probability that charge carriers are not absorbed at the interface but reflected back into the sensor volume. Defaults to `0.0`, i.e. no reflectivity, and a value of `1.0` corresponds to total reflection.

## Plotting parameters
* `output_plots` : Determines if simple output plots should be generated for a monitoring of the simulation flow. Disabled by default.
+37 −9
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ TransientPropagationModule::TransientPropagationModule(Configuration& config,
    config_.setDefault<double>("temperature", 293.15);
    config_.setDefault<unsigned int>("distance", 1);
    config_.setDefault<bool>("ignore_magnetic_field", false);
    config_.setDefault<double>("surface_reflectivity", 0.0);

    // Set defaults for charge carrier multiplication
    config_.setDefault<double>("multiplication_threshold", 1e-2);
@@ -80,6 +81,7 @@ TransientPropagationModule::TransientPropagationModule(Configuration& config,
    charge_per_step_ = config_.get<unsigned int>("charge_per_step");
    max_charge_groups_ = config_.get<unsigned int>("max_charge_groups");
    boltzmann_kT_ = Units::get(8.6173333e-5, "eV/K") * temperature_;
    surface_reflectivity_ = config_.get<double>("surface_reflectivity");

    max_multiplication_level_ = config.get<unsigned int>("max_multiplication_level");

@@ -617,12 +619,6 @@ TransientPropagationModule::propagate(Event* event,
        // Apply diffusion step
        auto diffusion = carrier_diffusion(std::sqrt(efield.Mag2()), doping, timestep_);
        position += diffusion;
        runge_kutta.setValue(position);

        // Update step length histogram
        if(output_plots_) {
            step_length_histo_->Fill(static_cast<double>(Units::convert(step.value.norm(), "um")));
        }

        // If charge carrier reaches implant, interpolate surface position for higher accuracy:
        if(auto implant = model_->isWithinImplant(static_cast<ROOT::Math::XYZPoint>(position))) {
@@ -636,15 +632,47 @@ TransientPropagationModule::propagate(Event* event,

        // Check for overshooting outside the sensor and correct for it:
        if(!model_->isWithinSensor(static_cast<ROOT::Math::XYZPoint>(position))) {
            LOG(TRACE) << "Carrier outside sensor: " << Units::display(static_cast<ROOT::Math::XYZPoint>(position), {"nm"});
            // Reflect off the sensor surface with a certain probability, otherwise halt motion:
            if(uniform_distribution(event->getRandomEngine()) > surface_reflectivity_) {
                LOG(TRACE) << "Carrier outside sensor: "
                           << Units::display(static_cast<ROOT::Math::XYZPoint>(position), {"nm"});
                state = CarrierState::HALTED;
            }

            auto intercept = model_->getSensorIntercept(static_cast<ROOT::Math::XYZPoint>(last_position),
                                                        static_cast<ROOT::Math::XYZPoint>(position));

            if(state == CarrierState::HALTED) {
                position = Eigen::Vector3d(intercept.x(), intercept.y(), intercept.z());
            } else {
                // geom. reflection on x-y plane at upper sensor boundary (we have an implant on the lower edge)
                position = Eigen::Vector3d(position.x(), position.y(), 2. * intercept.z() - position.z());
                LOG(TRACE) << "Carrier was reflected on the sensor surface to "
                           << Units::display(static_cast<ROOT::Math::XYZPoint>(position), {"um", "nm"});

                // Re-check if we ended in an implant - corner case.
                if(model_->isWithinImplant(static_cast<ROOT::Math::XYZPoint>(position))) {
                    LOG(TRACE) << "Ended in implant after reflection - halting";
                    state = CarrierState::HALTED;
                }

                // Re-check if we are within the sensor - reflection at sensor side walls:
                if(!model_->isWithinSensor(static_cast<ROOT::Math::XYZPoint>(position))) {
                    position = Eigen::Vector3d(intercept.x(), intercept.y(), intercept.z());
                    state = CarrierState::HALTED;
                }
            }
            LOG(TRACE) << "Moved carrier to: " << Units::display(static_cast<ROOT::Math::XYZPoint>(position), {"nm"});
        }

        // Update final position after applying corrections from surface intercepts
        runge_kutta.setValue(position);

        // Update step length histogram
        if(output_plots_) {
            step_length_histo_->Fill(static_cast<double>(Units::convert(step.value.norm(), "um")));
        }

        // Physics effects:

        // Check if charge carrier is still alive:
+3 −0
Original line number Diff line number Diff line
@@ -128,6 +128,9 @@ namespace allpix {
        double electron_Hall_;
        double hole_Hall_;

        // Reflectivity of sensor surface (outside implants)
        double surface_reflectivity_{0};

        // Magnetic field
        bool has_magnetic_field_{};

+1 −1
Original line number Diff line number Diff line
@@ -30,4 +30,4 @@ timestep = 1ps
multiplication_model = "overstraeten"
multiplication_threshold = 10kV/cm

#PASS Propagated 22 (initial: 10) to (444.653um,219.202um,200um) in 0.07ns time, induced 20e, final state: halted
#PASS Propagated 22 (initial: 10) to (445.716um,219.662um,200um) in 0.08ns time, induced 20e, final state: halted