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

Merge branch 'b-fix-laser' into 'v2.4-stable'

[v2.4-stable]  DepositionLaser: smarter use of the lookup table

See merge request allpix-squared/allpix-squared!970
parents 45f36793 4482a786
Loading
Loading
Loading
Loading
+61 −16
Original line number Diff line number Diff line
@@ -85,41 +85,86 @@ DepositionLaserModule::DepositionLaserModule(Configuration& config, Messenger* m
        throw InvalidValueError(config_, "pulse_duration_", "Pulse should be a positive value");
    }

    // FIXME less hardcoded values
    // Select user optics or silicon absorption lookup:
    is_user_optics_ = (config_.count({"absorption_length", "refractive_index"}) == 2);

    if(config_.count({"absorption_length", "refractive_index", "wavelength"}) == 3) {
        throw InvalidCombinationError(config_,
                                      {"absorption_length", "refractive_index", "wavelength"},
                                      "User definition for optical parameters and wavelength are mutually exclusive!");
    }

    if(is_user_optics_) {
        absorption_length_ = config_.get<double>("absorption_length");
        refractive_index_ = config_.get<double>("refractive_index");
        LOG(DEBUG) << "Setting user-defined optical properties for sensor material";
    } else {
        wavelength_ = config_.get<double>("wavelength");
        if(Units::convert(wavelength_, "nm") < 250 || Units::convert(wavelength_, "nm") > 1450) {
            throw InvalidValueError(config_, "wavelength", "Currently supported wavelengths are 250 -- 1450 nm");
        }

        // Register lookup path for data files:
        if(config_.has("data_path")) {
            auto path = config_.getPath("data_path", true);
            if(!std::filesystem::is_directory(path)) {
                throw InvalidValueError(config_, "data_path", "path does not point to a directory");
            }
            LOG(TRACE) << "Registered absorption data path from configuration: " << path;
        } else {
            if(std::filesystem::is_directory(ALLPIX_LASER_DATA_DIRECTORY)) {
                config_.set<std::string>("data_path", ALLPIX_LASER_DATA_DIRECTORY);
                LOG(TRACE) << "Registered absorption data path from system: " << ALLPIX_LASER_DATA_DIRECTORY;
            } else {
                const char* data_dirs_env = std::getenv("XDG_DATA_DIRS");
                if(data_dirs_env == nullptr || strlen(data_dirs_env) == 0) {
                    data_dirs_env = "/usr/local/share/:/usr/share/:";
                }

                auto data_dirs = split<std::filesystem::path>(data_dirs_env, ":");
                for(auto data_dir : data_dirs) {
                    data_dir /= std::filesystem::path(ALLPIX_PROJECT_NAME) / "data";
                    if(std::filesystem::is_directory(data_dir)) {
                        config_.set<std::string>("data_path", data_dir);
                        LOG(TRACE) << "Registered absorption data path from XDG_DATA_DIRS: " << data_dir;
                    } else {
                        throw ModuleError(
                            "Cannot find absorption data files, provide them in the configuration, via XDG_DATA_DIRS or "
                            "in system directory " +
                            std::string(ALLPIX_LASER_DATA_DIRECTORY));
                    }
                }
            }
        }
    }

    config_.setDefault<bool>("output_plots", false);
    output_plots_ = config.get<bool>("output_plots");
}

void DepositionLaserModule::initialize() {
    // Check if there are user-specified optical properties for materials
    is_user_optics_ = (config_.count({"absorption_length", "refractive_index"}) == 2);
    if(is_user_optics_) {
        absorption_length_ = config_.get<double>("absorption_length");
        refractive_index_ = config_.get<double>("refractive_index");
        LOG(DEBUG) << "Setting user-defined optical properties for sensor material";
    } else {
        // Load data
        std::string laser_data_path = ALLPIX_LASER_DATA_DIRECTORY;
        std::ifstream f(std::filesystem::path(laser_data_path) / "silicon_photoabsorption.data");
    if(!is_user_optics_) {
        // wavelength: {absorption_length, refractive_index}
        std::map<double, std::pair<double, double>> optics_lut;
        double wl = 0;
        double abs_length = 0;
        double refr_ind = 0;

        // Load data
        auto file_path = config_.getPath("data_path", true) / "silicon_photoabsorption.data";
        std::ifstream f(file_path);
        LOG(DEBUG) << "Loading optical properties for sensor material from LUT: " << std::endl << file_path.string();

        if(!f || !std::filesystem::is_regular_file(file_path)) {
            throw ModuleError("Could not open optical properties reference file at \"" + file_path.string() + "\"");
        }

        while(f >> wl >> abs_length >> refr_ind) {
            optics_lut[Units::get(wl, "nm")] = {abs_length, refr_ind};
        }

        LOG(DEBUG) << "Loading optical properties for sensor material from LUT: " << laser_data_path;

        // Find or interpolate absorption depth for given wavelength

        if(optics_lut.count(wavelength_) != 0) {
            absorption_length_ = optics_lut[wavelength_].first;
            refractive_index_ = optics_lut[wavelength_].second;
+1 −1
Original line number Diff line number Diff line
@@ -129,7 +129,7 @@ namespace allpix {
        double focal_distance_;

        size_t number_of_photons_;
        double wavelength_;
        double wavelength_{0.};
        double absorption_length_{0.};
        double refractive_index_{0.};
        double pulse_duration_;
+22 −13
Original line number Diff line number Diff line
---
# SPDX-FileCopyrightText: 2022 CERN and the Allpix Squared authors
# SPDX-FileCopyrightText: 2022-2023 CERN and the Allpix Squared authors
# SPDX-License-Identifier: CC-BY-4.0 OR MIT
title: "DepositionLaser"
description: "A simplistic deposition generator for charge injection with a laser"
@@ -35,41 +35,50 @@ spatial and temporal distributions of delivered intensity of a real laser pulse.
Two options for beam geometry are currently available: `cylindrical` and `converging`.
For both options, transversal beam profiles will have a gaussian shape.
For a `cylindrical` beam, all tracks are parallel to the set beam direction.
For a `converging` beam, track directions would have isotropic distribution (but with a limit on a max angle between the track and the set beam direction).
For a `converging` beam, track directions would have isotropic distribution (but with a limit on a max angle between the
track and the set beam direction).

**NB**: convention on global time zero for this module contradicts the general convention of the Allpix Squared.
For this module, global t=0 is chosen in such a way that the mean value of temporal distribution is *always* positioned at *4 standard deviations*  w.r.t. the global t=0.
For this module, global t=0 is chosen in such a way that the mean value of temporal distribution is *always* positioned at
*4 standard deviations*  w.r.t. the global t=0.
Thus, there is not necessarily a particle that is created exactly when the global time starts.
Although, the following Allpix Squared conventions still apply:

* No particles have a negative timestamp.
* Local time zero for each detector is a moment when the first particle that creates a hit in this detectors enters its bulk.

As a result, this module yields `DepositedCharge` instances for each detector, with them having physically correct spatial and temporal distribution.

As a result, this module yields `DepositedCharge` instances for each detector, with them having physically correct spatial
and temporal distribution.


## Parameters

* `number_of_photons`: number of incident photons, generated in *one* event. Defaults to 10000. The total deposited charge will also depend on wavelength and geometry.
* `wavelength` of the laser. Supported values are 250 -- 1450 nm.
* `number_of_photons`: number of incident photons, generated in *one* event. Defaults to 10000. The total deposited charge
  will also depend on wavelength and geometry.
* `wavelength` of the laser. If specified, it is used to retrieve sensor optical properties from the lookup table (data is available for the range of 250 -- 1450 nm). The only supported material is silicon.
* `data_path`: Directory to read the tabulated input data for the absorption on silicon. By default, this is the standard installation path of the data files shipped with the framework.
* `absorption_length` and `refractive_index`: if both are specified, given values are used instead of the lookup table. This also allows use of sensor materials other than silicon.
* `pulse_duration`: gaussian width of pulse temporal profile. Defaults to 0.5 ns.
* `source_position`: a 3D position vector.
* `beam_direction`: a 3D direction vector.
* `beam_geometry`: either `cylindrical` or `converging`
* `beam_waist`: standard deviation of transversal beam intensity distribution at focus. Defaults to 20 um.
* `focal_distance`: needs to be specified for `converging` beam. This distance is *as it would be in air*. In silicon, beam shape will effectively stretch along its direction due to refraction and the actual focus will be further away from the source.
* `focal_distance`: needs to be specified for `converging` beam. This distance is *as it would be in air*. In silicon, beam
  shape will effectively stretch along its direction due to refraction and the actual focus will be further away from the
  source.
* `beam_convergence_angle`: max angle between tracks and `beam_direction`. Needs to be specified for a `converging` beam.
* `output_plots`: if set `true`, this module will produce histograms to monitor beam shape and also 3D distributions of charges, deposited in each detector. Histograms would look sensible even for one-event runs. Defaults to `false`.
* `absorption_length` and `refractive_index`: if both are specified, they override corresponding values from the lookup table. This also allows use of sensor materials other than silicon.


## Usage
A simulation pipeline to build an analog detector response would include `DepositionLaser`, `TransientPropagation` and `PulseTransfer`.
A simulation pipeline to build an analog detector response would include `DepositionLaser`, `TransientPropagation` and
`PulseTransfer`.
Usually it is enough to run just a single event (or a few).
Multithreading is supported by this module.
One should note that for the pipeline above each event is very computation-heavy, and runs with just one event do not gain any additional performance from multi-threaded execution.
While multithreading is supported by this module, one should note that the pipeline for each event is very computationally
intensive and runs with only one event do not gain any additional performance from multi-threaded execution.

Such pipeline is expected to produce pulse shapes, comparable with experimentally obtained ones. An example of `DepositionLaser` configuration is shown below.
Such pipeline is expected to produce pulse shapes, comparable with experimentally obtained ones. An example of
`DepositionLaser` configuration is shown below.

```ini
[Allpix]
+1 −2
Original line number Diff line number Diff line
@@ -14,10 +14,9 @@ beam_geometry = "cylindrical"
number_of_photons = 1
source_position = 0 0 0
beam_direction = 0 0 1
wavelength = 1064nm
absorption_length = 1mm
refractive_index = 2.5


#PASS Wavelength = 1064nm, absorption length: 1mm, refractive index: 2.5
#PASS Wavelength = 0nm, absorption length: 1mm, refractive index: 2.5
#FAIL Detector d1 has unsupported material and will be ignored