42#include "TRestGeant4QuenchingProcess.h"
57 transform(value.begin(), value.end(), value.begin(),
58 [](
unsigned char c) { return static_cast<char>(tolower(c)); });
62string NormalizeQuenchingModel(
const string& model) {
63 const auto normalized =
ToLower(model);
64 if (normalized ==
"auto" || normalized ==
"lindhard" || normalized ==
"birks") {
68 RESTWarning <<
"TRestGeant4QuenchingProcess: Unknown quenching model '" << model
69 <<
"'. Falling back to auto." << RESTendl;
73string InferQuenchingModel(
const string& requestedModel,
const string& userVolumeExpression,
74 const string& volumeName) {
75 if (requestedModel !=
"auto") {
76 return requestedModel;
79 const auto haystack =
ToLower(userVolumeExpression +
" " + volumeName);
80 if (haystack.find(
"scintillator") != string::npos || haystack.find(
"veto") != string::npos) {
86double Clamp01(
double value) {
96double LindhardQuenchingFactor(
double recoilEnergy,
int A,
int Z) {
97 if (recoilEnergy <= 0 || A <= 0 || Z <= 0) {
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));
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;
113double EstimateStepLength(
const TRestGeant4Hits& hits,
int hitIndex,
double fallbackStepLength) {
114 const auto nHits =
static_cast<int>(hits.GetNumberOfHits());
116 return fallbackStepLength;
119 const auto volumeId = hits.GetVolumeId(hitIndex);
121 double stepLength = numeric_limits<double>::max();
123 if (hitIndex > 0 && hits.GetVolumeId(hitIndex - 1) == volumeId) {
124 stepLength = min(stepLength, (position - hits.
GetPosition(hitIndex - 1)).Mag());
126 if (hitIndex + 1 < nHits && hits.GetVolumeId(hitIndex + 1) == volumeId) {
127 stepLength = min(stepLength, (hits.
GetPosition(hitIndex + 1) - position).Mag());
130 if (!std::isfinite(stepLength) || stepLength <= 0 || stepLength == numeric_limits<double>::max()) {
131 return fallbackStepLength;
136double BirksQuenchingFactor(
double energy,
double stepLength,
double birksConstant) {
137 if (energy <= 0 || stepLength <= 0 || birksConstant <= 0) {
141 const double dEdx = energy / stepLength;
142 return Clamp01(1.0 / (1.0 + birksConstant * dEdx));
193 fBirksConstant = 0.000126;
194 fBirksFallbackStepLength = 0.5;
195 fApplyToHitEnergies =
true;
217 fUserVolumeExpressions.clear();
218 fUserVolumeModels.clear();
219 fUserVolumeBirksConstants.clear();
220 fUserVolumeBirksFallbackStepLengths.clear();
222 fApplyToHitEnergies = StringToBool(
GetParameter(
"applyToHitEnergies", fApplyToHitEnergies));
223 fBirksConstant = GetDblParameterWithUnits(
"birksConstant", fBirksConstant);
224 fBirksFallbackStepLength = GetDblParameterWithUnits(
"birksFallbackStepLength", fBirksFallbackStepLength);
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;
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);
249 cerr <<
"TRestGeant4QuenchingProcess: No TRestGeant4Metadata found" << endl;
254 fVolumeModels.clear();
255 fVolumeBirksConstants.clear();
256 fVolumeBirksFallbackStepLengths.clear();
260 for (
const auto& userVolume : fUserVolumeExpressions) {
261 set<string> physicalVolumes = {};
262 for (
const auto& volume : geometryInfo.GetAllPhysicalVolumesMatchingExpression(userVolume)) {
263 physicalVolumes.insert(volume.Data());
265 if (physicalVolumes.empty()) {
267 for (
const auto& logicalVolume :
268 geometryInfo.GetAllLogicalVolumesMatchingExpression(userVolume)) {
269 for (
const auto& volume :
270 geometryInfo.GetAllPhysicalVolumesFromLogical(logicalVolume.Data())) {
271 physicalVolumes.insert(volume.Data());
276 if (physicalVolumes.empty()) {
277 RESTWarning <<
"TRestGeant4QuenchingProcess: No volume found matching expression: " << userVolume
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];
294 RESTDebug <<
"TRestGeant4QuenchingProcess initialized with volumes" <<
RESTendl;
295 for (
const auto& volume : fVolumes) {
296 RESTDebug <<
" " << volume <<
" (" << fVolumeModels[volume] <<
")" <<
RESTendl;
308 const double sensitiveVolumeEnergyBefore =
fOutputG4Event->GetEnergyInVolume(sensitiveVolumeName);
316 bool missingHadronicInfoWarningPrinted =
false;
319 for (
int trackIndex = 0; trackIndex < int(
fOutputG4Event->GetNumberOfTracks()); trackIndex++) {
322 const string particleName = track->GetParticleName().Data();
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;
331 if (fVolumes.count(volumeName) && depositedEnergy > 0) {
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]);
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);
349 }
else if (!missingHadronicInfoWarningPrinted) {
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."
356 missingHadronicInfoWarningPrinted =
true;
361 const auto visibleEnergy = depositedEnergy * quenchingFactor;
362 if (fApplyToHitEnergies) {
363 energy[hitIndex] = visibleEnergy;
366 const auto processName = hits->GetProcessName(hitIndex);
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;
379 const double sensitiveVolumeEnergyAfter =
fOutputG4Event->GetEnergyInVolume(sensitiveVolumeName);
380 fOutputG4Event->SetSensitiveVolumeEnergy(sensitiveVolumeEnergyAfter);
382 bool sensitiveQuenched = TMath::Abs(sensitiveVolumeEnergyAfter - sensitiveVolumeEnergyBefore) > 1e-2;
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;
403std::set<std::string> TRestGeant4QuenchingProcess::GetVolumes()
const {
return fVolumes; }
405std::set<std::string> TRestGeant4QuenchingProcess::GetUserVolumeExpressions()
const {
406 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.
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.
std::string ToLower(std::string in)
Convert string to its lower case. Alternative of TString::ToLower.