Commit 65dcf0c3 authored by Paul Schütze's avatar Paul Schütze
Browse files

Merge branch 'b-capa-fix' into 'v3.1-stable'

[v3.1-stable] Capacitative transfer bugfix and update

See merge request allpix-squared/allpix-squared!1141
parents cd669551 c5df76d1
Loading
Loading
Loading
Loading
+47 −7
Original line number Diff line number Diff line
@@ -41,10 +41,13 @@ CapacitiveTransferModule::CapacitiveTransferModule(Configuration& config,
    config_.setDefault("cross_coupling", true);
    config_.setDefault("nominal_gap", 0.0);
    config_.setDefault("max_depth_distance", Units::get<double>(5, "um"));
    // By default, collect from the full sensor surface, not the implant region
    config_.setDefault("collect_from_implant", false);
    config_.setDefault("minimum_gap", config_.get<double>("nominal_gap"));

    cross_coupling_ = config_.get<bool>("cross_coupling");
    max_depth_distance_ = config_.get<double>("max_depth_distance");
    collect_from_implant_ = config_.get<bool>("collect_from_implant");

    // Require propagated deposits for single detector
    messenger_->bindSingle<PropagatedChargeMessage>(this, MsgFlags::REQUIRED);
@@ -52,6 +55,26 @@ CapacitiveTransferModule::CapacitiveTransferModule(Configuration& config,

void CapacitiveTransferModule::initialize() {

    auto model = detector_->getModel();
    if(collect_from_implant_) {
        if(model->getImplants().empty()) {
            throw InvalidValueError(config_,
                                    "collect_from_implant",
                                    "Detector model does not have implants defined, but collection requested from implants");
        }
        if(detector_->getElectricFieldType() == FieldType::LINEAR) {
            throw ModuleError("Charge collection from implant region should not be used with linear electric fields.");
        }
        LOG(INFO) << "Collecting charges from implants";
    } else if(!model->getImplants().empty()) {
        LOG(WARNING)
            << "Detector " << detector_->getName() << " of type " << model->getType()
            << " has implants defined but collecting charge carriers from full sensor surface, with a max depth distance of "
            << max_depth_distance_;
    } else {
        LOG(DEBUG) << "Collecting from sensor surface with a max depth distance of " << max_depth_distance_;
    }

    if(config_.count({"coupling_matrix", "coupling_file", "coupling_scan_file"}) > 1) {
        throw InvalidCombinationError(
            config_, {"coupling_matrix", "coupling_file", "coupling_scan_file"}, "More than one coupling input defined");
@@ -84,7 +107,7 @@ void CapacitiveTransferModule::initialize() {
            }
        }

        LOG(STATUS) << max_col_ << "x" << max_row_ << " coupling matrix imported from config file";
        LOG(STATUS) << max_col_ << "x" << max_row_ << " coupling matrix read from config file";

    } else if(config_.has("coupling_file")) {
        LOG(TRACE) << "Reading cross-coupling matrix file " << config_.get<std::string>("coupling_file");
@@ -269,11 +292,28 @@ void CapacitiveTransferModule::run(Event* event) {
    std::map<Pixel::Index, std::pair<double, std::vector<const PropagatedCharge*>>> pixel_map;
    for(const auto& propagated_charge : propagated_message->getData()) {
        auto position = propagated_charge.getLocalPosition();
        // Ignore if outside depth range of implant
        if(std::fabs(position.z() - (model_->getSensorCenter().z() + model_->getSensorSize().z() / 2.0)) >

        if(collect_from_implant_) {
            // Ignore if outside the implant region:
            auto implant = model_->isWithinImplant(position);
            if(!implant.has_value()) {
                LOG(TRACE) << "Skipping set of " << propagated_charge.getCharge() << " propagated charges at "
                           << Units::display(propagated_charge.getLocalPosition(), {"mm", "um"})
                           << " because their local position is outside the pixel implant";
                continue;
            }
            if(implant->getType() != DetectorModel::Implant::Type::FRONTSIDE) {
                LOG(TRACE) << "Skipping set of " << propagated_charge.getCharge() << " propagated charges at "
                           << Units::display(propagated_charge.getLocalPosition(), {"mm", "um"})
                           << " because the pixel implant is located at " << allpix::to_string(implant->getType());
                continue;
            }
        } else if(std::fabs(position.z() - (model_->getSensorCenter().z() + model_->getSensorSize().z() / 2.0)) >
                  max_depth_distance_) {
            LOG(DEBUG) << "Skipping set of " << propagated_charge.getCharge() << " propagated charges at "
                       << propagated_charge.getLocalPosition() << " because their local position is not in implant range";
            // Ignore if not close to the sensor surface:
            LOG(TRACE) << "Skipping set of " << propagated_charge.getCharge() << " propagated charges at "
                       << Units::display(propagated_charge.getLocalPosition(), {"mm", "um"})
                       << " because their local position is not near sensor surface";
            continue;
        }

@@ -336,7 +376,7 @@ void CapacitiveTransferModule::run(Event* event) {
                           << col << "," << row << " pixel " << pixel_index << "with cross-coupling of " << ccpd_factor * 100
                           << "%";

                // Add the pixel the list of hit pixels
                // Add the pixel to the list of hit pixels
                pixel_map[pixel_index].first += neighbour_charge;
                pixel_map[pixel_index].second.emplace_back(&propagated_charge);
            }
+1 −0
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ namespace allpix {

        double normalization_{};
        double max_depth_distance_{};
        bool collect_from_implant_{};
        bool cross_coupling_{};

        void getCapacitanceScan(TFile* root_file);
+2 −1
Original line number Diff line number Diff line
@@ -36,7 +36,8 @@ This module requires an installation of Eigen3.
* `cross_coupling`: Enables cross-coupling between pixels. Defaults to `true` (enabled).
* `coupling_file`: Path to the file containing the cross-coupling matrix. The file must contain the relative capacitance to the central pixel.
* `coupling_matrix`: Cross-coupling matrix with relative capacitances.
* `max_depth_distance`: Maximum distance in depth, i.e. normal to the sensor surface at the implant side, for a propagated charge to be taken into account. Defaults to 5um.
* `max_depth_distance`: Maximum distance in depth, i.e. normal to the sensor surface at the implant side, for a propagated charge to be taken into account in case the detector has no implants defined or `collect_from_implant` is set to `false`. Defaults to `5um`.
* `collect_from_implant`: Only consider charge carriers within the implant region of the respective detector instead of the full surface of the sensor. Should only be used with non-linear electric fields and defaults to `false`.
* `output_plots`: Saves the output plots for this module. Defaults to 1 (enabled).

The cross-coupling matrix, to be parsed via the matrix file or via the configuration file, must be organized in Row vs Col, such as:
+33 −0
Original line number Diff line number Diff line
# SPDX-FileCopyrightText: 2018-2024 CERN and the Allpix Squared authors
# SPDX-License-Identifier: MIT

#DESC tests the transfer of charges from sensor implants to readout chip in case sensor implants have been defined in the detector model.
[Allpix]
detectors_file = "detector_implant.conf"
number_of_events = 1
random_seed = 0

[DepositionPointCharge]
model = "fixed"
source_type = "point"
position = 445um 220um 0um
number_of_charges = 20

[ElectricFieldReader]
model = "mesh"
file_name = "@PROJECT_SOURCE_DIR@/examples/example_electric_field.init"
field_mapping = PIXEL_FULL

[GenericPropagation]
temperature = 293K
charge_per_step = 1
propagate_electrons = false
propagate_holes = true

[CapacitiveTransfer]
collect_from_implant = true
log_level = TRACE
coupling_matrix = [[0.000   0.023   0.000], [0.004   1.000   0.006], [0.001   0.037   0.001]]

#PASS [R:CapacitiveTransfer:mydetector] Skipping set of 1 propagated charges at (452.42um,213.772um,1.821um) because their local position is outside the pixel implant
#FAIL ERROR;FATAL
+7 −0
Original line number Diff line number Diff line
# SPDX-FileCopyrightText: 2017-2024 CERN and the Allpix Squared authors
# SPDX-License-Identifier: MIT

[mydetector]
type = "test_implants"
position = 0 0 0
orientation = 0 0 0