55#include "TRestGeant4QuenchingProcess.h"
71 transform(value.begin(), value.end(), value.begin(),
72 [](
unsigned char c) { return static_cast<char>(tolower(c)); });
76string NormalizeQuenchingModel(
const string& model) {
77 const auto normalized =
ToLower(model);
78 if (normalized ==
"auto" || normalized ==
"lindhard" || normalized ==
"birks" ||
79 normalized ==
"lindhard-stopped-recoil") {
83 RESTWarning <<
"TRestGeant4QuenchingProcess: Unknown quenching model '" << model
84 <<
"'. Falling back to auto." << RESTendl;
88string InferQuenchingModel(
const string& requestedModel,
const string& userVolumeExpression,
89 const string& volumeName) {
90 if (requestedModel !=
"auto") {
91 return requestedModel;
94 const auto haystack =
ToLower(userVolumeExpression +
" " + volumeName);
95 if (haystack.find(
"scintillator") != string::npos || haystack.find(
"veto") != string::npos) {
101double Clamp01(
double value) {
111double LindhardQuenchingFactor(
double recoilEnergy,
int A,
int Z) {
112 if (recoilEnergy <= 0 || A <= 0 || Z <= 0) {
116 const double gamma = 11.5 * recoilEnergy * TMath::Power(Z, -7.0 / 3.0);
117 const double g = 3 * TMath::Power(gamma, 0.15) + 0.7 * TMath::Power(gamma, 0.6) + gamma;
118 const double k = 0.133 * TMath::Power(Z, 2.0 / 3.0) * TMath::Power(A, -1.0 / 2.0);
119 return Clamp01(k * g / (1 + k * g));
125 const string name = track.GetParticleName().Data();
126 const auto pdg = physicsInfo.GetParticleID(track.GetParticleName());
127 if (pdg == 2212 || (pdg == 0 && name ==
"proton")) {
131 if (pdg >= 1000000000) {
133 Z = (pdg / 10000) % 1000;
134 A = (pdg / 10) % 1000;
135 if (pdg / 100000000 != 10 || (pdg / 10000000) % 10 != 0 || pdg % 10 != 0 || Z <= 0 || A < Z) {
136 throw runtime_error(
"unsupported nuclear PDG code " + to_string(pdg));
144 const auto lowerName =
ToLower(name);
145 if (lowerName ==
"e-" || lowerName ==
"e+" || lowerName ==
"gamma" || lowerName ==
"opticalphoton" ||
146 lowerName ==
"neutron" || lowerName ==
"mu-" || lowerName ==
"mu+" || lowerName ==
"geantino" ||
147 lowerName ==
"chargedgeantino" || lowerName.find(
"neutrino") != string::npos) {
150 throw runtime_error(
"missing recoil identity in the particle-name/PDG map for '" + name +
"'");
154 int volumeId,
int A,
int Z) {
155 const auto& hits = track.GetHits();
156 const double initialEnergy = track.GetInitialKineticEnergy();
157 if (!isfinite(initialEnergy) || initialEnergy <= 0 || hits.GetNumberOfHits() < 2) {
158 throw runtime_error(
"missing positive initial kinetic energy or complete Init/step history");
160 const double tolerance = 8 * numeric_limits<float>::epsilon() * initialEnergy;
161 if (hits.GetProcessId(0) != 0 || hits.GetEnergy(0) != 0 || !isfinite(hits.GetKineticEnergy(0)) ||
162 abs(hits.GetKineticEnergy(0) - initialEnergy) > tolerance) {
163 throw runtime_error(
"first saved hit is not the unmodified track Init state");
165 if (!track.GetSecondaryTrackIDs().empty()) {
166 throw runtime_error(
"explicit secondary track IDs are present");
168 for (
const auto& other : event.GetTracks()) {
169 if (other.GetParentID() == track.GetTrackID()) {
170 throw runtime_error(
"an explicit child track is present in the event");
173 double previousEnergy = initialEnergy;
174 double totalDeposit = 0;
175 for (
size_t hit = 0; hit < hits.GetNumberOfHits(); ++hit) {
176 if (hits.GetVolumeId(hit) != volumeId) {
177 throw runtime_error(
"recoil is born outside, leaves, or spans the selected physical volume");
179 const double deposit = hits.GetEnergy(hit);
180 const double remainingEnergy = hits.GetKineticEnergy(hit);
181 if (!isfinite(deposit) || deposit < 0 || !isfinite(remainingEnergy) || remainingEnergy < 0 ||
182 remainingEnergy > previousEnergy + tolerance ||
183 abs(previousEnergy - remainingEnergy - deposit) > tolerance) {
184 throw runtime_error(
"saved kinetic-energy loss does not close with the local deposited energy");
186 totalDeposit += deposit;
187 previousEnergy = remainingEnergy;
189 if (previousEnergy != 0 || abs(totalDeposit - initialEnergy) > tolerance) {
190 throw runtime_error(
"recoil is not stopped with its initial kinetic energy deposited locally");
192 return LindhardQuenchingFactor(initialEnergy, A, Z);
195bool IsNeutralParticleWithoutBirksQuenching(
const string& particleName) {
196 const auto name =
ToLower(particleName);
197 return name ==
"gamma" || name ==
"opticalphoton" || name ==
"geantino" || name ==
"chargedgeantino" ||
198 name.find(
"neutrino") != string::npos;
201double EstimateStepLength(
const TRestGeant4Hits& hits,
int hitIndex,
double fallbackStepLength) {
202 const auto nHits =
static_cast<int>(hits.GetNumberOfHits());
204 return fallbackStepLength;
207 const auto volumeId = hits.GetVolumeId(hitIndex);
209 double stepLength = numeric_limits<double>::max();
211 if (hitIndex > 0 && hits.GetVolumeId(hitIndex - 1) == volumeId) {
212 stepLength = min(stepLength, (position - hits.
GetPosition(hitIndex - 1)).Mag());
214 if (hitIndex + 1 < nHits && hits.GetVolumeId(hitIndex + 1) == volumeId) {
215 stepLength = min(stepLength, (hits.
GetPosition(hitIndex + 1) - position).Mag());
218 if (!std::isfinite(stepLength) || stepLength <= 0 || stepLength == numeric_limits<double>::max()) {
219 return fallbackStepLength;
224double BirksQuenchingFactor(
double energy,
double stepLength,
double birksConstant) {
225 if (energy <= 0 || stepLength <= 0 || birksConstant <= 0) {
229 const double dEdx = energy / stepLength;
230 return Clamp01(1.0 / (1.0 + birksConstant * dEdx));
281 fBirksConstant = 0.000126;
282 fBirksFallbackStepLength = 0.5;
283 fApplyToHitEnergies =
true;
305 fUserVolumeExpressions.clear();
306 fUserVolumeModels.clear();
307 fUserVolumeBirksConstants.clear();
308 fUserVolumeBirksFallbackStepLengths.clear();
310 fApplyToHitEnergies = StringToBool(
GetParameter(
"applyToHitEnergies", fApplyToHitEnergies));
311 fBirksConstant = GetDblParameterWithUnits(
"birksConstant", fBirksConstant);
312 fBirksFallbackStepLength = GetDblParameterWithUnits(
"birksFallbackStepLength", fBirksFallbackStepLength);
314 TiXmlElement* volumeElement =
GetElement(
"volume");
315 while (volumeElement) {
316 const string volumeName =
GetParameter(
"name", volumeElement,
"");
317 if (volumeName.empty()) {
318 cerr <<
"TRestGeant4QuenchingProcess: No volume expression specified" << endl;
321 fUserVolumeExpressions.insert(volumeName);
322 fUserVolumeModels[volumeName] =
323 NormalizeQuenchingModel(
GetParameter(
"model", volumeElement,
string(
"auto")));
324 fUserVolumeBirksConstants[volumeName] =
325 GetDblParameterWithUnits(
"birksConstant", volumeElement, fBirksConstant);
326 fUserVolumeBirksFallbackStepLengths[volumeName] =
327 GetDblParameterWithUnits(
"birksFallbackStepLength", volumeElement, fBirksFallbackStepLength);
337 cerr <<
"TRestGeant4QuenchingProcess: No TRestGeant4Metadata found" << endl;
342 fVolumeModels.clear();
343 fVolumeBirksConstants.clear();
344 fVolumeBirksFallbackStepLengths.clear();
348 for (
const auto& userVolume : fUserVolumeExpressions) {
349 set<string> physicalVolumes = {};
350 for (
const auto& volume : geometryInfo.GetAllPhysicalVolumesMatchingExpression(userVolume)) {
351 physicalVolumes.insert(volume.Data());
353 if (physicalVolumes.empty()) {
355 for (
const auto& logicalVolume :
356 geometryInfo.GetAllLogicalVolumesMatchingExpression(userVolume)) {
357 for (
const auto& volume :
358 geometryInfo.GetAllPhysicalVolumesFromLogical(logicalVolume.Data())) {
359 physicalVolumes.insert(volume.Data());
364 if (physicalVolumes.empty()) {
365 if (fUserVolumeModels[userVolume] ==
"lindhard-stopped-recoil") {
366 throw runtime_error(
"TRestGeant4QuenchingProcess: No volume matches stopped-recoil " +
369 RESTWarning <<
"TRestGeant4QuenchingProcess: No volume found matching expression: " << userVolume
374 for (
const auto& physicalVolume : physicalVolumes) {
375 const auto volumeName = geometryInfo.GetAlternativeNameFromGeant4PhysicalName(physicalVolume);
376 const string volumeNameString = volumeName.Data();
377 fVolumes.insert(volumeNameString);
378 fVolumeModels[volumeNameString] =
379 InferQuenchingModel(fUserVolumeModels[userVolume], userVolume, volumeNameString);
380 fVolumeBirksConstants[volumeNameString] = fUserVolumeBirksConstants[userVolume];
381 fVolumeBirksFallbackStepLengths[volumeNameString] =
382 fUserVolumeBirksFallbackStepLengths[userVolume];
386 for (
const auto& volume : fVolumes) {
387 if (fVolumeModels[volume] !=
"lindhard-stopped-recoil") {
394 "TRestGeant4QuenchingProcess: lindhard-stopped-recoil requires storeTracks=true, "
395 "registerEmptyTracks=true, removeUnwantedTracks=false, no kill volumes, and "
396 "storage chance=1 for " +
400 for (
const auto& physical : geometryInfo.GetAllPhysicalVolumes()) {
401 auto names = geometryInfo.GetAlternativeNamesFromGeant4PhysicalName(physical);
403 names.insert(physical);
405 for (
const auto& stored : names) {
408 "TRestGeant4QuenchingProcess: lindhard-stopped-recoil requires "
409 "complete geometry-volume storage; incomplete volume " +
410 string(stored.Data()));
416 RESTDebug <<
"TRestGeant4QuenchingProcess initialized with volumes" <<
RESTendl;
417 for (
const auto& volume : fVolumes) {
418 RESTDebug <<
" " << volume <<
" (" << fVolumeModels[volume] <<
")" <<
RESTendl;
430 const double sensitiveVolumeEnergyBefore =
fOutputG4Event->GetEnergyInVolume(sensitiveVolumeName);
436 map<int, double> stoppedRecoilFactors;
437 const bool hasStoppedRecoilModel =
438 any_of(fVolumeModels.begin(), fVolumeModels.end(),
439 [](
const auto& model) { return model.second ==
"lindhard-stopped-recoil"; });
440 for (
size_t trackIndex = 0; trackIndex <
fOutputG4Event->GetNumberOfTracks(); ++trackIndex) {
441 if (!hasStoppedRecoilModel) {
445 const auto& hits = track.GetHits();
448 "TRestGeant4QuenchingProcess: lindhard-stopped-recoil missing step "
450 to_string(
fOutputG4Event->GetID()) +
" track=" + to_string(track.GetTrackID()));
452 for (
size_t hit = 0; hit < hits.GetNumberOfHits(); ++hit) {
453 const string volume = hits.GetVolumeName(hit).Data();
454 if (hits.GetEnergy(hit) <= 0 || fVolumeModels.count(volume) == 0 ||
455 fVolumeModels.at(volume) !=
"lindhard-stopped-recoil") {
461 stoppedRecoilFactors[trackIndex] =
462 StoppedRecoilQuenchingFactor(track, *
fOutputG4Event, hits.GetVolumeId(hit), A, Z);
464 }
catch (
const runtime_error& error) {
466 "TRestGeant4QuenchingProcess: lindhard-stopped-recoil unsupported "
468 to_string(
fOutputG4Event->GetID()) +
" track=" + to_string(track.GetTrackID()) +
469 " volume=" + volume +
": " + error.what());
479 bool missingHadronicInfoWarningPrinted =
false;
482 for (
int trackIndex = 0; trackIndex < int(
fOutputG4Event->GetNumberOfTracks()); trackIndex++) {
485 const string particleName = track->GetParticleName().Data();
487 auto hits = track->GetHitsPointer();
488 auto& energy = hits->GetEnergyRef();
489 for (
int hitIndex = 0; hitIndex < int(hits->GetNumberOfHits()); hitIndex++) {
490 const string volumeName = hits->GetVolumeName(hitIndex).Data();
491 const double depositedEnergy = hits->GetEnergy(hitIndex);
492 double quenchingFactor = 1.0;
494 if (fVolumes.count(volumeName) && depositedEnergy > 0) {
496 fVolumeModels.count(volumeName) ? fVolumeModels[volumeName] : string(
"lindhard");
497 if (model ==
"birks") {
498 if (!IsNeutralParticleWithoutBirksQuenching(particleName)) {
499 const double stepLength =
500 EstimateStepLength(*hits, hitIndex, fVolumeBirksFallbackStepLengths[volumeName]);
501 quenchingFactor = BirksQuenchingFactor(depositedEnergy, stepLength,
502 fVolumeBirksConstants[volumeName]);
504 }
else if (model ==
"lindhard-stopped-recoil") {
505 if (stoppedRecoilFactors.count(trackIndex)) {
506 quenchingFactor = stoppedRecoilFactors.at(trackIndex);
508 }
else if (model ==
"lindhard") {
509 if (hits->GetHadronicOk()) {
510 const string isotopeName = hits->GetHadronicTargetIsotopeName(hitIndex);
511 const int isotopeA = hits->GetHadronicTargetIsotopeA(hitIndex);
512 const int isotopeZ = hits->GetHadronicTargetIsotopeZ(hitIndex);
513 if (!isotopeName.empty()) {
514 quenchingFactor = LindhardQuenchingFactor(depositedEnergy, isotopeA, isotopeZ);
516 }
else if (!missingHadronicInfoWarningPrinted) {
518 <<
"TRestGeant4QuenchingProcess: Lindhard quenching requested but "
519 "hadronic target information is not available. Unquenched hit energies "
520 "will be used for those hits. Enable storeHadronicTargetInfo in restG4 "
521 "to apply this model."
523 missingHadronicInfoWarningPrinted =
true;
528 const auto visibleEnergy = depositedEnergy * quenchingFactor;
529 if (fApplyToHitEnergies) {
530 energy[hitIndex] = visibleEnergy;
533 const auto processName = hits->GetProcessName(hitIndex);
535 if (visibleEnergy > 0) {
536 fOutputG4Event->AddEnergyInVolumeForParticleForProcess(visibleEnergy, volumeName,
537 particleName, processName.Data());
538 const auto volumeId = hits->GetVolumeId(hitIndex);
539 if (volumeId >= 0 && volumeId <
int(
fOutputG4Event->fVolumeDepositedEnergy.size())) {
540 fOutputG4Event->fVolumeDepositedEnergy[volumeId] += visibleEnergy;
546 const double sensitiveVolumeEnergyAfter =
fOutputG4Event->GetEnergyInVolume(sensitiveVolumeName);
547 fOutputG4Event->SetSensitiveVolumeEnergy(sensitiveVolumeEnergyAfter);
549 bool sensitiveQuenched = TMath::Abs(sensitiveVolumeEnergyAfter - sensitiveVolumeEnergyBefore) > 1e-2;
561 RESTMetadata <<
"Apply to hit energies: " << (fApplyToHitEnergies ?
"true" :
"false") <<
RESTendl;
562 RESTMetadata <<
"Default Birks constant: " << fBirksConstant <<
" mm/keV" <<
RESTendl;
563 RESTMetadata <<
"Default Birks fallback step length: " << fBirksFallbackStepLength <<
" mm" <<
RESTendl;
564 for (
auto const& volume : fVolumes) {
565 RESTMetadata <<
"Volume: " << volume <<
" model=" << fVolumeModels[volume] <<
RESTendl;
570std::set<std::string> TRestGeant4QuenchingProcess::GetVolumes()
const {
return fVolumes; }
572std::set<std::string> TRestGeant4QuenchingProcess::GetUserVolumeExpressions()
const {
573 return fUserVolumeExpressions;
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.
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.
bool HasCompleteStepInfo() const
True when the saved process, volume, and post-step kinetic-energy arrays cover every hit.
Recomputes visible hit energies using hit/contained-recoil 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.
std::string ToLower(std::string in)
Convert string to its lower case. Alternative of TString::ToLower.