REST-for-Physics  v2.3
Rare Event Searches ToolKit for Physics
TRestGeant4QuenchingProcess.cxx
1/*************************************************************************
2 * This file is part of the REST software framework. *
3 * *
4 * Copyright (C) 2016 GIFNA/TREX (University of Zaragoza) *
5 * For more information see http://gifna.unizar.es/trex *
6 * *
7 * REST is free software: you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation, either version 3 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * REST is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have a copy of the GNU General Public License along with *
18 * REST in $REST_PATH/LICENSE. *
19 * If not, see http://www.gnu.org/licenses/. *
20 * For the list of contributors see $REST_PATH/CREDITS. *
21 *************************************************************************/
22
41
42#include "TRestGeant4QuenchingProcess.h"
43
44#include <TMath.h>
45
46#include <algorithm>
47#include <cctype>
48#include <cmath>
49#include <limits>
50
51using namespace std;
52
54
55namespace {
56string ToLower(string value) {
57 transform(value.begin(), value.end(), value.begin(),
58 [](unsigned char c) { return static_cast<char>(tolower(c)); });
59 return value;
60}
61
62string NormalizeQuenchingModel(const string& model) {
63 const auto normalized = ToLower(model);
64 if (normalized == "auto" || normalized == "lindhard" || normalized == "birks") {
65 return normalized;
66 }
67
68 RESTWarning << "TRestGeant4QuenchingProcess: Unknown quenching model '" << model
69 << "'. Falling back to auto." << RESTendl;
70 return "auto";
71}
72
73string InferQuenchingModel(const string& requestedModel, const string& userVolumeExpression,
74 const string& volumeName) {
75 if (requestedModel != "auto") {
76 return requestedModel;
77 }
78
79 const auto haystack = ToLower(userVolumeExpression + " " + volumeName);
80 if (haystack.find("scintillator") != string::npos || haystack.find("veto") != string::npos) {
81 return "birks";
82 }
83 return "lindhard";
84}
85
86double Clamp01(double value) {
87 if (value < 0) {
88 return 0;
89 }
90 if (value > 1) {
91 return 1;
92 }
93 return value;
94}
95
96double LindhardQuenchingFactor(double recoilEnergy, int A, int Z) {
97 if (recoilEnergy <= 0 || A <= 0 || Z <= 0) {
98 return 1.0;
99 }
100
101 const double gamma = 11.5 * recoilEnergy * TMath::Power(Z, -7.0 / 3.0);
102 const double g = 3 * TMath::Power(gamma, 0.15) + 0.7 * TMath::Power(gamma, 0.6) + gamma;
103 const double k = 0.133 * TMath::Power(Z, 2.0 / 3.0) * TMath::Power(A, -1.0 / 2.0);
104 return Clamp01(k * g / (1 + k * g));
105}
106
107bool IsNeutralParticleWithoutBirksQuenching(const string& particleName) {
108 const auto name = ToLower(particleName);
109 return name == "gamma" || name == "opticalphoton" || name == "geantino" || name == "chargedgeantino" ||
110 name.find("neutrino") != string::npos;
111}
112
113double EstimateStepLength(const TRestGeant4Hits& hits, int hitIndex, double fallbackStepLength) {
114 const auto nHits = static_cast<int>(hits.GetNumberOfHits());
115 if (nHits <= 1) {
116 return fallbackStepLength;
117 }
118
119 const auto volumeId = hits.GetVolumeId(hitIndex);
120 const auto position = hits.GetPosition(hitIndex);
121 double stepLength = numeric_limits<double>::max();
122
123 if (hitIndex > 0 && hits.GetVolumeId(hitIndex - 1) == volumeId) {
124 stepLength = min(stepLength, (position - hits.GetPosition(hitIndex - 1)).Mag());
125 }
126 if (hitIndex + 1 < nHits && hits.GetVolumeId(hitIndex + 1) == volumeId) {
127 stepLength = min(stepLength, (hits.GetPosition(hitIndex + 1) - position).Mag());
128 }
129
130 if (!std::isfinite(stepLength) || stepLength <= 0 || stepLength == numeric_limits<double>::max()) {
131 return fallbackStepLength;
132 }
133 return stepLength;
134}
135
136double BirksQuenchingFactor(double energy, double stepLength, double birksConstant) {
137 if (energy <= 0 || stepLength <= 0 || birksConstant <= 0) {
138 return 1.0;
139 }
140
141 const double dEdx = energy / stepLength;
142 return Clamp01(1.0 / (1.0 + birksConstant * dEdx));
143}
144} // namespace
145
150
164 Initialize();
165
166 if (LoadConfigFromFile(configFilename)) {
168 }
169}
170
175
179void TRestGeant4QuenchingProcess::LoadDefaultConfig() { SetTitle("Default config"); }
180
186 fGeant4Metadata = nullptr;
187 SetSectionName(this->ClassName());
188 SetLibraryVersion(LIBRARY_VERSION);
189
190 fInputG4Event = nullptr;
192
193 fBirksConstant = 0.000126; // 0.126 mm/MeV in REST units, mm/keV
194 fBirksFallbackStepLength = 0.5; // mm
195 fApplyToHitEnergies = true;
196}
197
210void TRestGeant4QuenchingProcess::LoadConfig(const string& configFilename, const string& name) {
211 if (LoadConfigFromFile(configFilename, name)) {
213 }
214}
215
217 fUserVolumeExpressions.clear();
218 fUserVolumeModels.clear();
219 fUserVolumeBirksConstants.clear();
220 fUserVolumeBirksFallbackStepLengths.clear();
221
222 fApplyToHitEnergies = StringToBool(GetParameter("applyToHitEnergies", fApplyToHitEnergies));
223 fBirksConstant = GetDblParameterWithUnits("birksConstant", fBirksConstant);
224 fBirksFallbackStepLength = GetDblParameterWithUnits("birksFallbackStepLength", fBirksFallbackStepLength);
225
226 TiXmlElement* volumeElement = GetElement("volume");
227 while (volumeElement) {
228 const string volumeName = GetParameter("name", volumeElement, "");
229 if (volumeName.empty()) {
230 cerr << "TRestGeant4QuenchingProcess: No volume expression specified" << endl;
231 exit(1);
232 }
233 fUserVolumeExpressions.insert(volumeName);
234 fUserVolumeModels[volumeName] =
235 NormalizeQuenchingModel(GetParameter("model", volumeElement, string("auto")));
236 fUserVolumeBirksConstants[volumeName] =
237 GetDblParameterWithUnits("birksConstant", volumeElement, fBirksConstant);
238 fUserVolumeBirksFallbackStepLengths[volumeName] =
239 GetDblParameterWithUnits("birksFallbackStepLength", volumeElement, fBirksFallbackStepLength);
240 volumeElement = GetNextElement(volumeElement);
241 }
242}
243
247 fGeant4Metadata = GetMetadata<TRestGeant4Metadata>();
248 if (fGeant4Metadata == nullptr) {
249 cerr << "TRestGeant4QuenchingProcess: No TRestGeant4Metadata found" << endl;
250 exit(1);
251 }
252
253 fVolumes.clear();
254 fVolumeModels.clear();
255 fVolumeBirksConstants.clear();
256 fVolumeBirksFallbackStepLengths.clear();
257
258 const auto geometryInfo = fGeant4Metadata->GetGeant4GeometryInfo();
259 // check all the user volume expressions are valid and correspond to at least a volume
260 for (const auto& userVolume : fUserVolumeExpressions) {
261 set<string> physicalVolumes = {};
262 for (const auto& volume : geometryInfo.GetAllPhysicalVolumesMatchingExpression(userVolume)) {
263 physicalVolumes.insert(volume.Data());
264 }
265 if (physicalVolumes.empty()) {
266 // maybe it refers to a logical volume
267 for (const auto& logicalVolume :
268 geometryInfo.GetAllLogicalVolumesMatchingExpression(userVolume)) {
269 for (const auto& volume :
270 geometryInfo.GetAllPhysicalVolumesFromLogical(logicalVolume.Data())) {
271 physicalVolumes.insert(volume.Data());
272 }
273 }
274 }
275
276 if (physicalVolumes.empty()) {
277 RESTWarning << "TRestGeant4QuenchingProcess: No volume found matching expression: " << userVolume
278 << RESTendl;
279 continue;
280 }
281
282 for (const auto& physicalVolume : physicalVolumes) {
283 const auto volumeName = geometryInfo.GetAlternativeNameFromGeant4PhysicalName(physicalVolume);
284 const string volumeNameString = volumeName.Data();
285 fVolumes.insert(volumeNameString);
286 fVolumeModels[volumeNameString] =
287 InferQuenchingModel(fUserVolumeModels[userVolume], userVolume, volumeNameString);
288 fVolumeBirksConstants[volumeNameString] = fUserVolumeBirksConstants[userVolume];
289 fVolumeBirksFallbackStepLengths[volumeNameString] =
290 fUserVolumeBirksFallbackStepLengths[userVolume];
291 }
292 }
293
294 RESTDebug << "TRestGeant4QuenchingProcess initialized with volumes" << RESTendl;
295 for (const auto& volume : fVolumes) {
296 RESTDebug << " " << volume << " (" << fVolumeModels[volume] << ")" << RESTendl;
297 }
298}
299
304 fInputG4Event = (TRestGeant4Event*)inputEvent;
305 *fOutputG4Event = *((TRestGeant4Event*)inputEvent);
306
307 const string sensitiveVolumeName = fGeant4Metadata->GetSensitiveVolume().Data();
308 const double sensitiveVolumeEnergyBefore = fOutputG4Event->GetEnergyInVolume(sensitiveVolumeName);
309
311 fOutputG4Event->fEnergyInVolumePerParticlePerProcess.clear();
312 fOutputG4Event->fTotalDepositedEnergy = 0;
313 fOutputG4Event->SetSensitiveVolumeEnergy(0);
314 fill(fOutputG4Event->fVolumeDepositedEnergy.begin(), fOutputG4Event->fVolumeDepositedEnergy.end(), 0.0);
315
316 bool missingHadronicInfoWarningPrinted = false;
317
318 // loop over all tracks
319 for (int trackIndex = 0; trackIndex < int(fOutputG4Event->GetNumberOfTracks()); trackIndex++) {
320 // get the track
321 TRestGeant4Track* track = fOutputG4Event->GetTrackPointer(trackIndex);
322 const string particleName = track->GetParticleName().Data();
323
324 auto hits = track->GetHitsPointer();
325 auto& energy = hits->GetEnergyRef();
326 for (int hitIndex = 0; hitIndex < int(hits->GetNumberOfHits()); hitIndex++) {
327 const string volumeName = hits->GetVolumeName(hitIndex).Data();
328 const double depositedEnergy = hits->GetEnergy(hitIndex);
329 double quenchingFactor = 1.0;
330
331 if (fVolumes.count(volumeName) && depositedEnergy > 0) {
332 const auto model =
333 fVolumeModels.count(volumeName) ? fVolumeModels[volumeName] : string("lindhard");
334 if (model == "birks") {
335 if (!IsNeutralParticleWithoutBirksQuenching(particleName)) {
336 const double stepLength =
337 EstimateStepLength(*hits, hitIndex, fVolumeBirksFallbackStepLengths[volumeName]);
338 quenchingFactor = BirksQuenchingFactor(depositedEnergy, stepLength,
339 fVolumeBirksConstants[volumeName]);
340 }
341 } else if (model == "lindhard") {
342 if (hits->GetHadronicOk()) {
343 const string isotopeName = hits->GetHadronicTargetIsotopeName(hitIndex);
344 const int isotopeA = hits->GetHadronicTargetIsotopeA(hitIndex);
345 const int isotopeZ = hits->GetHadronicTargetIsotopeZ(hitIndex);
346 if (!isotopeName.empty()) {
347 quenchingFactor = LindhardQuenchingFactor(depositedEnergy, isotopeA, isotopeZ);
348 }
349 } else if (!missingHadronicInfoWarningPrinted) {
350 RESTWarning
351 << "TRestGeant4QuenchingProcess: Lindhard quenching requested but "
352 "hadronic target information is not available. Unquenched hit energies "
353 "will be used for those hits. Enable storeHadronicTargetInfo in restG4 "
354 "to apply this model."
355 << RESTendl;
356 missingHadronicInfoWarningPrinted = true;
357 }
358 }
359 }
360
361 const auto visibleEnergy = depositedEnergy * quenchingFactor;
362 if (fApplyToHitEnergies) {
363 energy[hitIndex] = visibleEnergy;
364 }
365
366 const auto processName = hits->GetProcessName(hitIndex);
367
368 if (visibleEnergy > 0) {
369 fOutputG4Event->AddEnergyInVolumeForParticleForProcess(visibleEnergy, volumeName,
370 particleName, processName.Data());
371 const auto volumeId = hits->GetVolumeId(hitIndex);
372 if (volumeId >= 0 && volumeId < int(fOutputG4Event->fVolumeDepositedEnergy.size())) {
373 fOutputG4Event->fVolumeDepositedEnergy[volumeId] += visibleEnergy;
374 }
375 }
376 }
377 }
378
379 const double sensitiveVolumeEnergyAfter = fOutputG4Event->GetEnergyInVolume(sensitiveVolumeName);
380 fOutputG4Event->SetSensitiveVolumeEnergy(sensitiveVolumeEnergyAfter);
381
382 bool sensitiveQuenched = TMath::Abs(sensitiveVolumeEnergyAfter - sensitiveVolumeEnergyBefore) > 1e-2;
383 SetObservableValue("sensitiveQuenched", sensitiveQuenched);
384 SetObservableValue("sensitiveVolumeEnergyBefore", sensitiveVolumeEnergyBefore);
385 SetObservableValue("sensitiveVolumeEnergyAfter", sensitiveVolumeEnergyAfter);
386
387 return fOutputG4Event;
388}
389
391
394 RESTMetadata << "Apply to hit energies: " << (fApplyToHitEnergies ? "true" : "false") << RESTendl;
395 RESTMetadata << "Default Birks constant: " << fBirksConstant << " mm/keV" << RESTendl;
396 RESTMetadata << "Default Birks fallback step length: " << fBirksFallbackStepLength << " mm" << RESTendl;
397 for (auto const& volume : fVolumes) {
398 RESTMetadata << "Volume: " << volume << " model=" << fVolumeModels[volume] << RESTendl;
399 }
400 EndPrintProcess();
401}
402
403std::set<std::string> TRestGeant4QuenchingProcess::GetVolumes() const { return fVolumes; }
404
405std::set<std::string> TRestGeant4QuenchingProcess::GetUserVolumeExpressions() const {
406 return fUserVolumeExpressions;
407}
TRestRun * GetRunInfo() const
Return the pointer of the hosting TRestRun object.
void BeginPrintProcess()
[name, cut range]
void SetObservableValue(const std::string &name, const T &value)
Set observable value for AnalysisTree.
A base class for any REST event.
Definition: TRestEvent.h:38
An event class to store geant4 generated event information.
void InitializeReferences(TRestRun *run) override
Initialize dynamical references when loading the event from a root file.
TString GetSensitiveVolume(int n=0) const
Returns a std::string with the name of the sensitive volume.
const TRestGeant4GeometryInfo & GetGeant4GeometryInfo() const
Returns an immutable reference to the geometry info.
Recomputes visible hit energies using simple Lindhard and Birks quenching models.
TRestGeant4Event * fOutputG4Event
A pointer to the specific TRestGeant4Event output.
void InitFromConfigFile() override
To make settings from rml file. This method must be implemented in the derived class.
TRestEvent * ProcessEvent(TRestEvent *inputEvent) override
The main processing event function.
TRestGeant4Metadata * fGeant4Metadata
A pointer to the simulation metadata information accessible to TRestRun.
void PrintMetadata() override
Implemented it in the derived metadata class to print out specific metadata information.
TRestGeant4Event * fInputG4Event
A pointer to the specific TRestGeant4Event input.
void EndProcess() override
To be executed at the end of the run (outside event loop)
void LoadDefaultConfig()
Function to load the default config in absence of RML input.
void LoadConfig(const std::string &configFilename, const std::string &name="")
Function to load the configuration from an external configuration file.
TRestGeant4QuenchingProcess()
Default constructor.
void Initialize() override
Function to initialize input/output event members and define the section name.
~TRestGeant4QuenchingProcess() override
Default destructor.
void InitProcess() override
Process initialization. User volume expressions are matched to physical volumes.
TVector3 GetPosition(int n) const
It returns the position of hit number n.
Definition: TRestHits.cxx:515
endl_t RESTendl
Termination flag object for TRestStringOutput.
TiXmlElement * GetElement(std::string eleDeclare, TiXmlElement *e=nullptr)
Get an xml element from a given parent element, according to its declaration.
Int_t LoadConfigFromFile(const std::string &configFilename, const std::string &sectionName="")
Give the file name, find out the corresponding section. Then call the main starter.
void SetLibraryVersion(TString version)
Set the library version of this metadata class.
void SetSectionName(std::string sName)
set the section name, clear the section content
TiXmlElement * GetNextElement(TiXmlElement *e)
Get the next sibling xml element of this element, with same eleDeclare.
std::string GetParameter(std::string parName, TiXmlElement *e, TString defaultValue=PARAMETER_NOT_FOUND_STR)
Returns the value for the parameter named parName in the given section.
std::string ToLower(std::string in)
Convert string to its lower case. Alternative of TString::ToLower.