ATLAS Offline Software
Loading...
Searching...
No Matches
SensorSimPlanarTool.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
11
14
15#include "CLHEP/Random/RandFlat.h"
16#include "CLHEP/Random/RandGaussZiggurat.h"
17
18#include "CLHEP/Units/PhysicalConstants.h"
20
21#include "GaudiKernel/SystemOfUnits.h"
22
23#include "TFile.h"
24
25#include <cmath>
26#include <memory>
27
28using namespace InDetDD;
29
30//===============================================
31// C O N S T R U C T O R
32//===============================================
33SensorSimPlanarTool::SensorSimPlanarTool(const std::string& type, const std::string& name, const IInterface* parent) :
34 SensorSimTool(type, name, parent) {
35}
36
38
39//===============================================
40// I N I T I A L I Z E
41//===============================================
43 ATH_MSG_DEBUG("SensorSimPlanarTool::initialize()");
45
46 ATH_CHECK(m_radDamageUtil.retrieve());
47 ATH_MSG_DEBUG("RadDamageUtil tool retrieved successfully");
48
49 ATH_CHECK(m_lorentzAngleTool.retrieve());
50
52 //If any fluence or voltage initialized negative use benchmark maps and not interpolation
53 std::vector<std::string> mapsPath_list;
54 std::vector<std::string> TCADpath_list;
55
56 // Use all TCAD E field files in this directory for creating E field via interpolation (pruned filed excluded)
57 std::string iblFiles = PathResolverFindCalibDirectory("PixelDigitization/TCAD_IBL_efields/fei4-200um/");
58 std::string sensorFiles = PathResolverFindCalibDirectory("PixelDigitization/TCAD_Blayer_efields/fei4-250um/");
59
60 // For each layer one configuration
61 TCADpath_list = {
62 iblFiles, sensorFiles, sensorFiles, sensorFiles
63 };
64
65 if (m_fluenceMap.size() == 0 || m_fluenceLayer.size() == 0 || m_voltageLayer.size() == 0 ||
66 m_fluenceMap.size() != m_fluenceLayer.size() || m_fluenceMap.size() != m_voltageLayer.size()) {
67 ATH_MSG_INFO("Use interpolation, but the input map/fluence/valtage are not set.");
68 return StatusCode::FAILURE;
69 }
70 ATH_MSG_INFO("No benchmark value set for fluence. Use interpolation.");
71
72 mapsPath_list.clear();
73 for (size_t i = 0; i < m_fluenceMap.size(); i++) {
74 mapsPath_list.push_back(PathResolverFindCalibFile(m_fluenceMap[i]));
75 }
76
77 // *****************************
78 // *** Setup Maps ****
79 // *****************************
80 //TODO This is only temporary until remotely stored maps and locally generated maps can be implemented
81 //E field already implemented: needs fluence and bias voltage given as Property m_fluence, m_fluenceB, ...,
82 // m_fluence1, ...
83 for (unsigned int i = 0; i < mapsPath_list.size(); i++) {
84 ATH_MSG_INFO("Using maps located in: " << mapsPath_list.at(i) << " for layer No." << i);
85 ATH_MSG_INFO("Create E field via interpolation based on files from: " << TCADpath_list.at(i));
86 std::unique_ptr<TFile> mapsFile(TFile::Open((mapsPath_list.at(i)).c_str(), "READ")); //this is the ramo potential.
87 if (!mapsFile) {
88 ATH_MSG_ERROR("Cannot open file: " << mapsPath_list.at(i));
89 return StatusCode::FAILURE;
90 }
91
92 //Setup ramo weighting field map
93 std::unique_ptr<TH3F> ramoPotentialMap_hold(mapsFile->Get<TH3F>("hramomap1"));
94 if (!ramoPotentialMap_hold) {
95 ramoPotentialMap_hold.reset(mapsFile->Get<TH3F>("ramo3d"));
96 ATH_MSG_INFO("Did not find a Ramo potential map. Will use an approximate form.");
97 }
98 if (!ramoPotentialMap_hold) {
99 ATH_MSG_WARNING("Not implemented yet - exit");
100 return StatusCode::FAILURE; //Obviously, remove this when gen. code is set up
101 }
102 ramoPotentialMap_hold->SetDirectory(nullptr);
103 m_ramoPotentialMap.emplace_back();
104 ATH_CHECK(m_ramoPotentialMap.back().setHisto3D(ramoPotentialMap_hold.get()));
105 //Now setup the E-field.
106 TH1F* eFieldMap_hold(nullptr);
107 CHECK(m_radDamageUtil->generateEfieldMap(eFieldMap_hold, nullptr, m_fluenceLayer[i], m_voltageLayer[i], i,
108 TCADpath_list.at(i), true));
109
110 eFieldMap_hold->SetDirectory(nullptr);
111
112 TH2F* lorentzMap_e_hold(nullptr);
113 TH2F* lorentzMap_h_hold(nullptr);
114 TH2F* distanceMap_h_hold(nullptr);
115 TH2F* distanceMap_e_hold(nullptr);
116 TH1F* timeMap_e_hold(nullptr);
117 TH1F* timeMap_h_hold(nullptr);
118
119 ATH_CHECK(m_radDamageUtil->generateDistanceTimeMap(distanceMap_e_hold, distanceMap_h_hold, timeMap_e_hold,
120 timeMap_h_hold, lorentzMap_e_hold, lorentzMap_h_hold,
121 eFieldMap_hold, nullptr));
122
123 // For debugging and documentation: uncomment to save different maps which are based on the interpolated E field
124 if (m_radDamageUtil->saveDebugMaps()) {
125 TString prename = "map_layer_";
126 prename += i;
127 prename += "distance_e.root";
128 distanceMap_e_hold->SaveAs(prename, "");
129 prename.ReplaceAll("_e", "_h");
130 distanceMap_h_hold->SaveAs(prename, "");
131 prename.ReplaceAll("distance", "time");
132 timeMap_h_hold->SaveAs(prename, "");
133 prename.ReplaceAll("_h", "_e");
134 timeMap_e_hold->SaveAs(prename, "");
135 prename.ReplaceAll("time", "lorentz");
136 lorentzMap_e_hold->SaveAs(prename, "");
137 prename.ReplaceAll("_e", "_h");
138 lorentzMap_h_hold->SaveAs(prename, "");
139 }
140 //Safetycheck
141 if (!distanceMap_e_hold || !distanceMap_h_hold || !timeMap_e_hold || !timeMap_h_hold ||
142 !lorentzMap_e_hold || !lorentzMap_h_hold) {
143 ATH_MSG_ERROR("Unable to load at least one of the distance/time/Lorentz angle maps.");
144 return StatusCode::FAILURE;//Obviously, remove this when gen. code is set up
145 }
146
147 lorentzMap_e_hold->SetDirectory(nullptr);
148 lorentzMap_h_hold->SetDirectory(nullptr);
149 distanceMap_e_hold->SetDirectory(nullptr);
150 distanceMap_h_hold->SetDirectory(nullptr);
151 timeMap_e_hold->SetDirectory(nullptr);
152 timeMap_h_hold->SetDirectory(nullptr);
153
154 m_distanceMap_e.emplace_back();
155 m_distanceMap_h.emplace_back();
156 ATH_CHECK(m_distanceMap_e.back().setHisto2D(distanceMap_e_hold));
157 ATH_CHECK(m_distanceMap_h.back().setHisto2D(distanceMap_h_hold));
158 m_lorentzMap_e.emplace_back();
159 m_lorentzMap_h.emplace_back();
160 ATH_CHECK(m_lorentzMap_e.back().setHisto2D(lorentzMap_e_hold));
161 ATH_CHECK(m_lorentzMap_h.back().setHisto2D(lorentzMap_h_hold));
162
163 delete eFieldMap_hold;
164 delete lorentzMap_e_hold;
165 delete lorentzMap_h_hold;
166 delete distanceMap_e_hold;
167 delete distanceMap_h_hold;
168 delete timeMap_e_hold;
169 delete timeMap_h_hold;
170
171 mapsFile->Close();
172 }
173 }
174
175 // read the correction histograms
177
178 constexpr std::size_t numberOfLayers = 4;
179
180 ATH_MSG_INFO("Opening file: " << m_templateCorrectionRootFile << " for radiation damage correction");
181 std::unique_ptr<TFile> file(TFile::Open(PathResolverFindCalibFile(m_templateCorrectionRootFile).c_str(), "READ"));
182 if (!file) {
183 ATH_MSG_ERROR("Unable to read the ROOT file needed for radiation damage correction at: " << m_templateCorrectionRootFile);
184 return StatusCode::FAILURE;
185 }
186
187 if (m_lorentzAngleCorrectionHistos.size() != numberOfLayers) {
188 ATH_MSG_ERROR("The size of the vector of Lorentz angle histogram paths is " << m_lorentzAngleCorrectionHistos.size() << " instead of " << numberOfLayers);
189 return StatusCode::FAILURE;
190 }
191
192 if (m_chargeCorrectionHistos.size() != numberOfLayers) {
193 ATH_MSG_ERROR("The size of the vector of charge correction histogram paths is " << m_chargeCorrectionHistos.size() << " instead of " << numberOfLayers);
194 return StatusCode::FAILURE;
195 }
196
197 if (m_distanceCorrectionHistos.size() != numberOfLayers) {
198 ATH_MSG_ERROR("The size of the vector of distance correction histogram paths is " << m_distanceCorrectionHistos.size() << " instead of " << numberOfLayers);
199 return StatusCode::FAILURE;
200 }
201
202 for (const std::string& i : m_lorentzAngleCorrectionHistos) {
203 ATH_MSG_INFO("Reading histogram: " << i << " for Lorentz angle correction needed for Pixel radiation damage simulation");
204 std::unique_ptr<TH1> h(file->Get<TH1>(i.c_str()));
205 if (!h) {
206 ATH_MSG_ERROR("Cannot read histogram " << i << " needed for Lorentz angle correction");
207 return StatusCode::FAILURE;
208 }
209
210 m_lorentzCorrection.emplace_back();
211 ATH_CHECK(m_lorentzCorrection.back().setHisto1D(h.get()));
212 }
213
214 for (const std::string& i : m_chargeCorrectionHistos) {
215 ATH_MSG_INFO("Reading histogram: " << i << " for charge correction needed for Pixel radiation damage simulation");
216 std::unique_ptr<TH1> h(file->Get<TH1>(i.c_str()));
217 if (!h) {
218 ATH_MSG_ERROR("Cannot read histogram " << i << " needed for charge correction");
219 return StatusCode::FAILURE;
220 }
221
222 m_chargeCorrection.emplace_back();
223 ATH_CHECK(m_chargeCorrection.back().setHisto1D(h.get()));
224 }
225
226 for (const std::string& i : m_distanceCorrectionHistos) {
227 ATH_MSG_INFO("Reading histogram: " << i << " for distance correction needed for Pixel radiation damage simulation");
228 std::unique_ptr<TH1> h(file->Get<TH1>(i.c_str()));
229 if (!h) {
230 ATH_MSG_ERROR("Cannot read histogram " << i << " needed for distance correction");
231 return StatusCode::FAILURE;
232 }
233
234 m_distanceCorrection.emplace_back();
235 ATH_CHECK(m_distanceCorrection.back().setHisto1D(h.get()));
236 }
237
238 file->Close();
239 }
240
241 return StatusCode::SUCCESS;
242}
243
244//===============================================
245// F I N A L I Z E
246//===============================================
248 ATH_MSG_DEBUG("SensorSimPlanarTool::finalize()");
249 return StatusCode::SUCCESS;
250}
251
252//===============================================
253// I N D U C E C H A R G E
254//===============================================
256 SiChargedDiodeCollection& chargedDiodes,
257 const InDetDD::SiDetectorElement& Module,
258 const InDetDD::PixelModuleDesign& p_design,
259 std::vector< std::pair<double, double> >& trfHitRecord,
260 std::vector<double>& initialConditions,
261 CLHEP::HepRandomEngine* rndmEngine,
262 const EventContext &ctx) const {
263
264 bool isITk(false);
266 isITk = true;
267 if (p_design.is3D() && m_digitizeITk3Das3D) {
268 return StatusCode::SUCCESS;
269 }
270 } else {
271 // So far, this is only discriminating variable from 3D sensor.
272 if (p_design.numberOfCircuits() < 2) {
273 if (!Module.isDBM()) { //DBM modules also processed here
274 return StatusCode::SUCCESS;
275 }
276 }
277 }
278
279 const PixelID* p_pixelId = static_cast<const PixelID*>(Module.getIdHelper());
280 int layer = p_pixelId->layer_disk(Module.identify());
281
282 //Load values from energyDeposition
283 double eta_0 = initialConditions[0];
284 double phi_0 = initialConditions[1];
285 double depth_0 = initialConditions[2];
286 double dEta = initialConditions[3];
287 double dPhi = initialConditions[4];
288 double dDepth = initialConditions[5];
289 double ncharges = initialConditions[6];
290 double iTotalLength = initialConditions[7];
291
292 const double oneOverNchargesTimes1e6 = 1./(1.E+6 * ncharges);
293
294 //Set up physical detector properties, switch on detector material
295 ATH_MSG_DEBUG("Applying planar sensor simulation");
296 double sensorThickness = Module.design().thickness();
297 const InDet::SiliconProperties& siProperties = m_siPropertiesTool->getSiProperties(Module.identifyHash(), ctx);
298
299 int etaCells = p_design.columns();
300 int phiCells = p_design.rows();
301
302 double eleholePairEnergy = 0;
303 double smearRand = 0;
304
305 double diffusionConstant;
306 if (Module.isDBM()) {
307 eleholePairEnergy = 1. / (13. * CLHEP::eV); // was 3.62 eV.
308 diffusionConstant = .00265;
309 smearRand = CLHEP::RandGaussZiggurat::shoot(rndmEngine);
310 } else {
311 eleholePairEnergy = siProperties.electronHolePairsPerEnergy();
312 diffusionConstant = .007;
313 }
314
315 double collectionDist = 0.2 * CLHEP::mm;
316 double smearScale = 1. + 0.35 * smearRand;
317 double tanLorentz(0);
318 double coLorentz(0);
320 tanLorentz = m_lorentzAngleTool->getTanLorentzAngle(Module.identifyHash(), ctx);
321 coLorentz = std::sqrt(1.0 + (tanLorentz*tanLorentz));
322 }
323
324 //**************************************//
325 //*** Now diffuse charges to surface *** //
326 //**************************************//
327 // pre-make HepMcParticleLink
328 const HepMcParticleLink particleLink = HepMcParticleLink::getRedirectedLink(phit->particleLink(), phit.eventId(), ctx); // This link should now correctly resolve to the TruthEvent McEventCollection in the main StoreGateSvc.
329 const double pHitTime = hitTime(phit);
330
331 const double halfEtaPitch = 0.5*Module.etaPitch();
332 const double halfPhiPitch = 0.5*Module.phiPitch();
333
336 const PixelRadiationDamageFluenceMapData *fluenceData = *fluenceDataHandle;
337
338 std::pair<double, double> trappingTimes;
340 trappingTimes = m_radDamageUtil->getTrappingTimes(m_fluenceLayer[layer]);
341 }
342 else {
343 trappingTimes = m_radDamageUtil->getTrappingTimes(fluenceData->getFluenceLayer(layer));
344 }
345
346 const PixelHistoConverter& distanceMap_e = m_doInterpolateEfield ? m_distanceMap_e[layer] : fluenceData->getDistanceMap_e(layer);
347 const PixelHistoConverter& distanceMap_h = m_doInterpolateEfield ? m_distanceMap_h[layer] : fluenceData->getDistanceMap_h(layer);
348 const PixelHistoConverter& lorentzMap_e = m_doInterpolateEfield ? m_lorentzMap_e[layer] : fluenceData->getLorentzMap_e(layer);
349 const PixelHistoConverter& lorentzMap_h = m_doInterpolateEfield ? m_lorentzMap_h[layer] : fluenceData->getLorentzMap_h(layer);
350 const PixelHistoConverter& ramoPotentialMap = m_doInterpolateEfield ? m_ramoPotentialMap[layer] : fluenceData->getRamoPotentialMap(layer);
351
352 std::map<unsigned, std::pair<SiLocalPosition, double>> cachedChargeMap;
353 std::map<unsigned, SiCellId> diodeCellMap;
354 for (const auto& iHitRecord : trfHitRecord) {
355
356 double eta_i = eta_0;
357 double phi_i = phi_0;
358 double depth_i = depth_0;
359 if (iTotalLength) {
360 eta_i += 1.0 * iHitRecord.first / iTotalLength * dEta;
361 phi_i += 1.0 * iHitRecord.first / iTotalLength * dPhi;
362 depth_i += 1.0 * iHitRecord.first / iTotalLength * dDepth;
363 }
364
365 //Find the position of the centre of the pixel in which the charge carriers are created, wrt centre of module
366 SiLocalPosition pos_i = Module.hitLocalToLocal(eta_i, phi_i);
367 SiCellId pixel_i = Module.cellIdOfPosition(pos_i);
368 if (!pixel_i.isValid()) continue;
369
370 // Distance between charge and readout side. p_design->readoutSide() is
371 // +1 if readout side is in +ve depth axis direction and visa-versa.
372 double dist_electrode = 0.5 * sensorThickness - Module.design().readoutSide() * depth_i;
373 if (dist_electrode < 0) {
374 dist_electrode = 0;
375 }
376
377 SiLocalPosition centreOfPixel_i = p_design.positionFromColumnRow(pixel_i.etaIndex(), pixel_i.phiIndex());
378
379 //Make limits for NN loop
380 const int nnLoop_pixelEtaMax = std::min(1, pixel_i.etaIndex());
381 const int nnLoop_pixelEtaMin = std::max(-1, pixel_i.etaIndex() + 1 - etaCells);
382 const int nnLoop_pixelPhiMax = std::min(1, pixel_i.phiIndex());
383 const int nnLoop_pixelPhiMin = std::max(-1, pixel_i.phiIndex() + 1 - phiCells);
384
385 std::array<double, 3> sensorScales{};
386
387 const std::size_t distance_f_e_bin_x = distanceMap_e.getBinX(dist_electrode);
388 const std::size_t distance_f_h_bin_x = distanceMap_h.getBinX(dist_electrode);
389 const std::size_t tanLorentz_e_bin_x = lorentzMap_e.getBinX(dist_electrode);
390 const std::size_t tanLorentz_h_bin_x = lorentzMap_h.getBinX(dist_electrode);
391
392 const std::size_t sizePhi = std::abs(nnLoop_pixelPhiMax - nnLoop_pixelPhiMin) + 1;
393
394 const auto pixel_eta = pixel_i.etaIndex();
395 const auto pixel_phi = pixel_i.phiIndex();
396
397 // According to a comment: need at most 3x3 elements
398 std::array<std::pair<double,double>,9 > centrePixelNNEtaPhi;
399 for (int p = nnLoop_pixelEtaMin; p <= nnLoop_pixelEtaMax; p++) {
400 const std::size_t ieta = p - nnLoop_pixelEtaMin;
401 // scale factors accounting for different pixel sizes
402 double scale_f = 1.;
403 double columnWidth = p_design.widthFromColumnRange(pixel_eta - p, pixel_eta - p);
404 if (std::abs(columnWidth - 0.6) < 1e-9) {
405 scale_f = 4. / 6.;
406 } else if (std::abs(columnWidth - 0.45) < 1e-9) {
407 scale_f = 25. / 45.;
408 } else if (std::abs(columnWidth - 0.5) < 1e-9) {
409 scale_f = 25. / 50.;
410 }
411 sensorScales[ieta] = scale_f;
412
413 for (int q = nnLoop_pixelPhiMin; q <= nnLoop_pixelPhiMax; q++) {
414 const SiLocalPosition& centreOfPixel_nn = p_design.positionFromColumnRow(pixel_eta - p,
415 pixel_phi - q);
416 const std::size_t iphi = q - nnLoop_pixelPhiMin;
417 const std::size_t index = iphi + ieta*sizePhi;
418 centrePixelNNEtaPhi.at(index).first = centreOfPixel_nn.xEta();
419 centrePixelNNEtaPhi[index].second = centreOfPixel_nn.xPhi();
420 }
421 }
422
423
424 for (int j = 0; j < ncharges; j++) {
425 // amount of energy to be converted into charges at current step
426 double energy_per_step = oneOverNchargesTimes1e6 * iHitRecord.second;
427 double u = CLHEP::RandFlat::shoot(rndmEngine, 0.0, 1.0);
428 // need to update to std::logf when we update gcc - this is a known bug in gcc libc
429 const double drifttime_e = (-1.) * (trappingTimes.first) * logf(u); //ns
430 u = CLHEP::RandFlat::shoot(rndmEngine, 0.0, 1.0);
431 const double drifttime_h = (-1.) * (trappingTimes.second) * logf(u); //ns
432
433 //Now, need the z-position at the trap.
434 //TODO: the holes map does not currently extend for a drift time long enough that, any hole will reach
435 //the corresponding electrode. This needs to be rectified by either (a) extrapolating the current map or
436 //(b) making a new map with a y-axis (drift time) that extends to at least 18 ns so all charge carriers reach
437 // electrode.
438 //However, if choose (b), will need to reduce granularity of map.
439 const double depth_f_e = distanceMap_e.getContent(distance_f_e_bin_x, distanceMap_e.getBinY(drifttime_e));
440 const double depth_f_h = distanceMap_h.getContent(distance_f_h_bin_x, distanceMap_h.getBinY(drifttime_h));
441 const double tanLorentz_e = lorentzMap_e.getContent(tanLorentz_e_bin_x, lorentzMap_e.getBinY(depth_f_e));
442 const double tanLorentz_h = lorentzMap_h.getContent(tanLorentz_h_bin_x, lorentzMap_h.getBinY(depth_f_h));
443 const double dz_e = std::abs(dist_electrode - depth_f_e);
444 const double dz_h = std::abs(depth_f_h - dist_electrode);
445 const double coLorentz_e = std::sqrt(1.0 + (tanLorentz_e*tanLorentz_e));
446
447 //Apply drift due to Lorentz force and diffusion
448 double phiRand = CLHEP::RandGaussZiggurat::shoot(rndmEngine);
449
450 //Apply diffusion. rdif is teh max. diffusion
451 const double rdif_e = diffusionConstant * std::sqrt(dz_e * coLorentz_e / 0.3);
452 const double phi_f_e = phi_i + dz_e * tanLorentz_e + rdif_e * phiRand;
453 double etaRand = CLHEP::RandGaussZiggurat::shoot(rndmEngine);
454 double eta_f_e = eta_i + rdif_e * etaRand;
455
456 phiRand = CLHEP::RandGaussZiggurat::shoot(rndmEngine);
457 const double coLorentz_h = std::sqrt(1.0 + (tanLorentz_h*tanLorentz_h));
458 const double rdif_h = diffusionConstant * std::sqrt(dz_h * coLorentz_h / 0.3);
459 const double phi_f_h = phi_i + dz_h * tanLorentz_h + rdif_h * phiRand;
460 etaRand = CLHEP::RandGaussZiggurat::shoot(rndmEngine);
461 double eta_f_h = eta_i + rdif_h * etaRand;
462
463
464 // Slim Edge for IBL planar sensors:
466 applyIBLSlimEdges(energy_per_step, eta_f_e);
467 applyIBLSlimEdges(energy_per_step, eta_f_h);
468 }
469
470 const std::size_t ramo_f_e_bin_z = ramoPotentialMap.getBinZ(1e3*depth_f_e);
471 const std::size_t ramo_f_h_bin_z = ramoPotentialMap.getBinZ(1e3*depth_f_h);
472
473 const bool isFirstZ_e = ramoPotentialMap.isFirstZ(1e3*depth_f_e);
474 const bool isOverflowZ_h = ramoPotentialMap.isOverflowZ(1e3*depth_f_h);
475
476 const double pixelEta_f_e = eta_f_e - centreOfPixel_i.xEta();
477 const double pixelPhi_f_e = phi_f_e - centreOfPixel_i.xPhi();
478
479 const double pixelEta_f_h = eta_f_h - centreOfPixel_i.xEta();
480 const double pixelPhi_f_h = phi_f_h - centreOfPixel_i.xPhi();
481
482 //Loop over nearest neighbours in x and y
483 //We assume that the lateral diffusion is minimal
484 for (int p = nnLoop_pixelEtaMin; p <= nnLoop_pixelEtaMax; p++) {
485 const std::size_t ieta = p - nnLoop_pixelEtaMin;
486
487 for (int q = nnLoop_pixelPhiMin; q <= nnLoop_pixelPhiMax; q++) {
488 //Since both e-h charge carriers start in the same place, they have the same initial ramo value
489 //Centre of nearest neighbour (nn) pixel
490
491 const std::size_t iphi = q - nnLoop_pixelPhiMin;
492 const std::size_t index = iphi + ieta*sizePhi;
493 //What is the displacement of the nn pixel from the primary pixel.
494 //This is to index the correct entry in the Ramo weighting potential map
495 const std::pair<double,double>& centrePixelNN = centrePixelNNEtaPhi.at(index);
496 const double dPhi_nn_centre = centrePixelNN.second - centreOfPixel_i.xPhi(); //in mm
497 const double dEta_nn_centre = centrePixelNN.first - centreOfPixel_i.xEta(); //in mm
498
499 //This all has to be done relative to the (0,0) position since the
500 //Ramo weighting potential is only mapped out for 1/8th of a pixel. Much of this logic is reflecting the
501 // charge carrier across the boundaries.
502 //Find the displacment of the charge carriers from the centre of the pixel in +ve quadrant
503
504 //Final position of charge carriers wrt nn centre
505 const double dEta_f_e = std::abs(pixelEta_f_e - dEta_nn_centre)*sensorScales[ieta];
506 const double dPhi_f_e = std::abs(pixelPhi_f_e - dPhi_nn_centre);
507 const double dEta_f_h = 1e3*std::abs(pixelEta_f_h - dEta_nn_centre)*sensorScales[ieta];
508 const double dPhi_f_h = 1e3*std::abs(pixelPhi_f_h - dPhi_nn_centre);
509
510 //Boundary check on maps
511 double ramo_f_e = 0.0;
512 double ramo_f_h = 0.0;
513
514 if (isFirstZ_e) {
515 if (dEta_f_e >= halfEtaPitch || dPhi_f_e >= halfPhiPitch) {
516 ramo_f_e = 0.0;
517 } else {
518 ramo_f_e = 1.0;
519 }
520 } else {
521 ramo_f_e = ramoPotentialMap.getContent(ramoPotentialMap.getBinX(1e3*dPhi_f_e), ramoPotentialMap.getBinY(1e3*dEta_f_e), ramo_f_e_bin_z);
522 }
523
524 //Account for the imperfect binning that would cause charge to be double-counted
525 if (isOverflowZ_h) {
526 ramo_f_h = 0;
527 } else {
528 ramo_f_h = ramoPotentialMap.getContent(ramoPotentialMap.getBinX(dPhi_f_h), ramoPotentialMap.getBinY(dEta_f_h), ramo_f_h_bin_z);
529 }
530
531 //Given final position of charge carrier, find induced charge. The difference in Ramo weighting potential
532 // gives the fraction of charge induced.
533 //The energy_per_step is transformed into charge with the eleholePair per Energy
534 const double potentialDiff = ramo_f_e - ramo_f_h;
535 // this variable ^ can be used to apply some cut to skip the loop
536 const double induced_charge = potentialDiff * energy_per_step * eleholePairEnergy;
537
538 unsigned key = (static_cast<unsigned>(pixel_eta-p) << 16) | static_cast<unsigned>(pixel_phi-q);
539 auto cacheIterator = cachedChargeMap.find(key);
540 if(cacheIterator == cachedChargeMap.end()) {
541 cachedChargeMap.insert(std::make_pair(key, std::make_pair(Module.hitLocalToLocal(centrePixelNN.first, centrePixelNN.second), induced_charge)));
542 } else {
543 cacheIterator->second.second += induced_charge;
544 }
545 } //For q
546 } //for p
547 }//end cycle for charge
548 } //trfHitRecord.size()
549
550 std::for_each(cachedChargeMap.begin(), cachedChargeMap.end(), [&diodeCellMap, &Module, &chargedDiodes, &pHitTime, &particleLink](auto& pos_charge_pair){
551 auto& key = pos_charge_pair.first;
552 auto& chargePos = pos_charge_pair.second.first;
553 auto& charge_value = pos_charge_pair.second.second;
554
555 const SiSurfaceCharge scharge(chargePos, SiCharge(charge_value, pHitTime, SiCharge::track, particleLink));
556 auto diodeIterator = diodeCellMap.find(key);
557 if(diodeIterator == diodeCellMap.end()) diodeIterator = diodeCellMap.insert(std::make_pair(key, Module.cellIdOfPosition(scharge.position()))).first;
558 const SiCellId& thisDiode = diodeIterator->second;
559 if (thisDiode.isValid()) {
560 const SiCharge& charge = scharge.charge();
561 chargedDiodes.add(thisDiode, charge);
562 }
563 });
564
565 }
566 else if (m_radiationDamageSimulationType == RadiationDamageSimulationType::TEMPLATE_CORRECTION && !(Module.isDBM()) && Module.isBarrel()){ // will run radiation damage but with the template method
567
568 // For Pixel, the layers in the barrel are 0, 1, 2 and 3
569 // But for ITk Pixel, these are 1, 2, 3 and 4
570 // So, we need to adjust for the position in the vector for ITk
571
572 int layerToRead = isITk && m_digitizeITk3Das3D ? layer - 1 : layer;
573
574 // temporary solution for treating 3D as planar in the digitisation
575 if (layerToRead > 3) {
576 layerToRead = 3;
577 }
578
579 const PixelHistoConverter& distanceCorrectionHist = m_distanceCorrection[layerToRead];
580 const PixelHistoConverter& lorentzCorrectionHist = m_lorentzCorrection[layerToRead];
581 const PixelHistoConverter& chargeCorrectionHist = m_chargeCorrection[layerToRead];
582
583 for (const auto & iHitRecord : trfHitRecord) {
584 double eta_i = eta_0;
585 double phi_i = phi_0;
586 double depth_i = depth_0;
587 if (iTotalLength) {
588 eta_i += 1.0 * iHitRecord.first / iTotalLength * dEta;
589 phi_i += 1.0 * iHitRecord.first / iTotalLength * dPhi;
590 depth_i += 1.0 * iHitRecord.first / iTotalLength * dDepth;
591 }
592
593 const double depthZ = (depth_i + 0.5*sensorThickness)/Gaudi::Units::micrometer; // to get it in micro meters
594
595 const double dist_electrode = distanceCorrectionHist.getContent(depthZ)*Gaudi::Units::micrometer; // to get it in mm
596
597 // get corrected LA
598 tanLorentz = lorentzCorrectionHist.getContent(depthZ);
599 coLorentz = std::sqrt(1.0 + (tanLorentz*tanLorentz));
600
601 // for charge corrections
602 const double chargeCorrection = chargeCorrectionHist.getContent(depthZ);
603
604 // nonTrapping probability
605 double nontrappingProbability = 1.0;
606 if (Module.isDBM()) {
607 nontrappingProbability = exp(-dist_electrode / collectionDist);
608 }
609
610 for (int j = 0; j < ncharges; j++) {
611 // amount of energy to be converted into charges at current step
612 double energy_per_step = oneOverNchargesTimes1e6 * iHitRecord.second;
613 // diffusion sigma
614 double rdif = diffusionConstant * std::sqrt(dist_electrode * coLorentz / 0.3);
615
616 // position at the surface
617 double phiRand = CLHEP::RandGaussZiggurat::shoot(rndmEngine);
618 double phi_drifted = phi_i + dist_electrode * tanLorentz + rdif * phiRand;
619 double etaRand = CLHEP::RandGaussZiggurat::shoot(rndmEngine);
620 double eta_drifted = eta_i + rdif * etaRand;
621
622 // Slim Edge for IBL planar sensors:
623 if (!(Module.isDBM()) && p_design.getReadoutTechnology() == InDetDD::PixelReadoutTechnology::FEI4) {
624 applyIBLSlimEdges(energy_per_step, eta_drifted);
625 }
626
627 // Get the charge position in Reconstruction local coordinates.
628 const SiLocalPosition& chargePos = Module.hitLocalToLocal(eta_drifted, phi_drifted);
629
630 // The parametrization of the sensor efficiency (if needed)
631 double ed = 0;
632 if (Module.isDBM()) {
633 ed = energy_per_step * eleholePairEnergy * nontrappingProbability * smearScale;
634 } else {
635 ed = energy_per_step * eleholePairEnergy;
636 }
637 // apply the correction from radiation damage
638 ed *= chargeCorrection;
639
640 //The following lines are adapted from SiDigitization's Inserter class
641 const SiSurfaceCharge scharge(chargePos, SiCharge(ed, pHitTime, SiCharge::track, particleLink));
642
643 const SiCellId& diode = Module.cellIdOfPosition(scharge.position());
644
645 if (diode.isValid()) {
646 const SiCharge& charge = scharge.charge();
647 chargedDiodes.add(diode, charge);
648 }
649 }//end cycle for charge
650 }//trfHitRecord.size()
651 } else { // run without radiation damage
652 for (const auto & iHitRecord : trfHitRecord) {
653 double eta_i = eta_0;
654 double phi_i = phi_0;
655 double depth_i = depth_0;
656 if (iTotalLength) {
657 eta_i += 1.0 * iHitRecord.first / iTotalLength * dEta;
658 phi_i += 1.0 * iHitRecord.first / iTotalLength * dPhi;
659 depth_i += 1.0 * iHitRecord.first / iTotalLength * dDepth;
660 }
661
662 // Distance between charge and readout side. p_design->readoutSide() is
663 // +1 if readout side is in +ve depth axis direction and visa-versa.
664 double dist_electrode = 0.5 * sensorThickness - Module.design().readoutSide() * depth_i;
665 if (dist_electrode < 0) {
666 dist_electrode = 0;
667 }
668
669 // nonTrapping probability
670 double nontrappingProbability = 1.0;
671 if (Module.isDBM()) {
672 nontrappingProbability = exp(-dist_electrode / collectionDist);
673 }
674
675 for (int j = 0; j < ncharges; j++) {
676 // amount of energy to be converted into charges at current step
677 double energy_per_step = oneOverNchargesTimes1e6 * iHitRecord.second;
678 // diffusion sigma
679 double rdif = diffusionConstant * std::sqrt(dist_electrode * coLorentz / 0.3);
680
681 // position at the surface
682 double phiRand = CLHEP::RandGaussZiggurat::shoot(rndmEngine);
683 double phi_drifted = phi_i + dist_electrode * tanLorentz + rdif * phiRand;
684 double etaRand = CLHEP::RandGaussZiggurat::shoot(rndmEngine);
685 double eta_drifted = eta_i + rdif * etaRand;
686
687 // Slim Edge for IBL planar sensors:
688 if (!(Module.isDBM()) && p_design.getReadoutTechnology() == InDetDD::PixelReadoutTechnology::FEI4) {
689 applyIBLSlimEdges(energy_per_step, eta_drifted);
690 }
691
692 // Get the charge position in Reconstruction local coordinates.
693 const SiLocalPosition& chargePos = Module.hitLocalToLocal(eta_drifted, phi_drifted);
694
695 // The parametrization of the sensor efficiency (if needed)
696 double ed = 0;
697 if (Module.isDBM()) {
698 ed = energy_per_step * eleholePairEnergy * nontrappingProbability * smearScale;
699 } else {
700 ed = energy_per_step * eleholePairEnergy;
701 }
702
703 //The following lines are adapted from SiDigitization's Inserter class
704 const SiSurfaceCharge scharge(chargePos, SiCharge(ed, pHitTime, SiCharge::track, particleLink));
705
706 const SiCellId& diode = Module.cellIdOfPosition(scharge.position());
707
708 if (diode.isValid()) {
709 const SiCharge& charge = scharge.charge();
710 chargedDiodes.add(diode, charge);
711 }
712 }//end cycle for charge
713 }
714 }
715
716 return StatusCode::SUCCESS;
717}
718
719void SensorSimPlanarTool::applyIBLSlimEdges(double& energy_per_step, double& eta_drifted) {
720 if (std::abs(eta_drifted) > 20.440) {
721 energy_per_step = 0.0;
722 }
723 if (std::abs(eta_drifted) < 20.440 && std::abs(eta_drifted) > 20.200) {
724 if (eta_drifted > 0) {
725 energy_per_step = energy_per_step * (68.13 - eta_drifted * 3.333);
726 eta_drifted = eta_drifted - 0.250;
727 } else {
728 energy_per_step = energy_per_step * (68.13 + eta_drifted * 3.333);
729 eta_drifted = eta_drifted + 0.250;
730 }
731 }
732 if (std::abs(eta_drifted) < 20.200 && std::abs(eta_drifted) > 20.100) {
733 if (eta_drifted > 0) {
734 energy_per_step = energy_per_step * (41.2 - eta_drifted * 2.0);
735 eta_drifted = eta_drifted - 0.250;
736 } else {
737 energy_per_step = energy_per_step * (41.2 + eta_drifted * 2.0);
738 eta_drifted = eta_drifted + 0.250;
739 }
740 }
741}
float hitTime(const AFP_SIDSimHit &hit)
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
double charge(const T &p)
Definition AtlasPID.h:997
#define CHECK(...)
Evaluate an expression and check for errors.
std::string PathResolverFindCalibDirectory(const std::string &logical_file_name)
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
This is an Identifier helper class for the Pixel subdetector.
double thickness() const
Method which returns thickness of the silicon wafer.
int readoutSide() const
ReadoutSide.
Class used to describe the design of a module (diode segmentation and readout scheme)
PixelReadoutTechnology getReadoutTechnology() const
int columns() const
Number of cell columns per module:
int rows() const
Number of cell rows per module:
SiLocalPosition positionFromColumnRow(const int column, const int row) const
Given row and column index of a diode, return position of diode center ALTERNATIVE/PREFERED way is to...
double widthFromColumnRange(const int colMin, const int colMax) const
Method to calculate eta width from a column range.
int numberOfCircuits() const
Total number of circuits:
Identifier for the strip or pixel cell.
Definition SiCellId.h:29
int phiIndex() const
Get phi index. Equivalent to strip().
Definition SiCellId.h:122
bool isValid() const
Test if its in a valid state.
Definition SiCellId.h:136
int etaIndex() const
Get eta index.
Definition SiCellId.h:114
Class to hold geometrical description of a silicon detector element.
virtual const SiDetectorDesign & design() const override final
access to the local description (inline):
double phiPitch() const
Pitch (inline methods)
Class to represent a position in the natural frame of a silicon sensor, for Pixel and SCT For Pixel: ...
double xPhi() const
position along phi direction:
double xEta() const
position along eta direction:
SiCellId cellIdOfPosition(const Amg::Vector2D &localPos) const
As in previous method but returns SiCellId.
virtual IdentifierHash identifyHash() const override final
identifier hash (inline)
virtual Identifier identify() const override final
identifier of this detector element (inline)
const AtlasDetectorID * getIdHelper() const
Returns the id helper (inline)
Amg::Vector2D hitLocalToLocal(double xEta, double xPhi) const
Simulation/Hit local frame to reconstruction local frame.
double etaPitch() const
Pitch (inline methods)
double electronHolePairsPerEnergy() const
float getContent(std::size_t x) const
bool isFirstZ(const float value) const
std::size_t getBinY(Args &&...args) const
std::size_t getBinZ(Args &&...args) const
bool isOverflowZ(const float value) const
std::size_t getBinX(Args &&...args) const
This is an Identifier helper class for the Pixel subdetector.
Definition PixelID.h:67
int layer_disk(const Identifier &id) const
Definition PixelID.h:607
const PixelHistoConverter & getLorentzMap_e(int layer) const
const PixelHistoConverter & getRamoPotentialMap(int layer) const
const PixelHistoConverter & getDistanceMap_e(int layer) const
const PixelHistoConverter & getDistanceMap_h(int layer) const
const PixelHistoConverter & getLorentzMap_h(int layer) const
virtual ~SensorSimPlanarTool()
std::vector< PixelHistoConverter > m_lorentzCorrection
std::vector< PixelHistoConverter > m_ramoPotentialMap
virtual StatusCode induceCharge(const TimedHitPtr< SiHit > &phit, SiChargedDiodeCollection &chargedDiodes, const InDetDD::SiDetectorElement &Module, const InDetDD::PixelModuleDesign &p_design, std::vector< std::pair< double, double > > &trfHitRecord, std::vector< double > &initialConditions, CLHEP::HepRandomEngine *rndmEngine, const EventContext &ctx) const override
virtual StatusCode initialize() override
static void applyIBLSlimEdges(double &energyPerStep, double &eta_drifted)
virtual StatusCode finalize() override
std::vector< PixelHistoConverter > m_chargeCorrection
std::vector< PixelHistoConverter > m_lorentzMap_e
ToolHandle< ISiLorentzAngleTool > m_lorentzAngleTool
Gaudi::Property< bool > m_doInterpolateEfield
Gaudi::Property< std::vector< std::string > > m_fluenceMap
std::vector< PixelHistoConverter > m_lorentzMap_h
std::vector< PixelHistoConverter > m_distanceMap_h
std::vector< PixelHistoConverter > m_distanceMap_e
Gaudi::Property< std::vector< double > > m_fluenceLayer
ToolHandle< RadDamageUtil > m_radDamageUtil
Gaudi::Property< std::vector< float > > m_voltageLayer
std::vector< PixelHistoConverter > m_distanceCorrection
Gaudi::Property< std::string > m_templateCorrectionRootFile
Gaudi::Property< std::vector< std::string > > m_chargeCorrectionHistos
Gaudi::Property< std::vector< std::string > > m_lorentzAngleCorrectionHistos
ToolHandle< ISiPropertiesTool > m_siPropertiesTool
SG::ReadCondHandleKey< PixelRadiationDamageFluenceMapData > m_fluenceDataKey
Gaudi::Property< std::vector< std::string > > m_distanceCorrectionHistos
virtual StatusCode initialize()
SensorSimTool(const std::string &type, const std::string &name, const IInterface *parent)
Gaudi::Property< bool > m_digitizeITk3Das3D
Gaudi::Property< int > m_radiationDamageSimulationType
a smart pointer to a hit that also provides access to the extended timing info of the host event.
Definition TimedHitPtr.h:18
unsigned short eventId() const
the index of the component event in PileUpEventInfo.
Definition TimedHitPtr.h:47
Message Stream Member.
bool dPhi(const xAOD::TauJet &tau, const xAOD::CaloVertexedTopoCluster &cluster, float &out)
bool dEta(const xAOD::TauJet &tau, const xAOD::CaloVertexedTopoCluster &cluster, float &out)
Definition index.py:1
TFile * file