LDMX Software
HcalSD.cxx
1#include "SimCore/SDs/HcalSD.h"
2
3/*~~~~~~~~~~~~~~*/
4/* DetDescr */
5/*~~~~~~~~~~~~~~*/
7#include "DetDescr/HcalID.h"
8
9// STL
10
11// Geant4
13#include "G4Box.hh"
14#include "G4Gamma.hh"
15#include "G4Neutron.hh"
16#include "G4Step.hh"
17#include "G4StepPoint.hh"
18#include "SimCore/G4User/TrackMap.h"
19
20namespace simcore {
21
22const std::string HcalSD::COLLECTION_NAME = "HcalSimHits";
23
24HcalSD::HcalSD(const std::string& name, simcore::ConditionsInterface& ci,
26 : SensitiveDetector(name, ci, p), birksc1_(1.29e-2), birksc2_(9.59e-6) {
27 gdml_identifiers_ = {p.get<std::vector<std::string>>("gdml_identifiers")};
28 enable_hit_contribs_ = p.get<bool>("enable_hit_contribs");
29 compress_hit_contribs_ = p.get<bool>("compress_hit_contribs");
30 max_origin_track_id_ = p.get<int>("max_origin_track_id");
31}
32
33ldmx::HcalID HcalSD::decodeCopyNumber(const std::uint32_t copyNumber,
34 const G4ThreeVector& localPosition,
35 const G4Box* scint) {
36 const unsigned int version{copyNumber / 0x01000000};
37 if (version != 0) {
39 return ldmx::HcalID{Index(copyNumber).field2(), Index(copyNumber).field1(),
40 Index(copyNumber).field0()};
41 }
42 const auto& geometry = getCondition<ldmx::HcalGeometry>(
44 unsigned int strip_id = 0;
45 const unsigned int section = copyNumber / 1000;
46 const unsigned int layer = copyNumber % 1000;
47
48 // 5cm wide bars are HARD-CODED
49 if (section == ldmx::HcalID::BACK) {
50 if (geometry.backLayerIsHorizontal(layer)) {
51 strip_id = int((localPosition.y() + scint->GetYHalfLength()) / 50.0);
52 } else {
53 strip_id = int((localPosition.x() + scint->GetXHalfLength()) / 50.0);
54 }
55 } else {
56 strip_id = int((localPosition.z() + scint->GetZHalfLength()) / 50.0);
57 }
58 return ldmx::HcalID{section, layer, strip_id};
59}
60
61G4bool HcalSD::ProcessHits(G4Step* aStep, G4TouchableHistory* ROhist) {
62 // Get the edep from the step.
63 G4double edep = aStep->GetTotalEnergyDeposit();
64
65 // Skip steps with no energy dep which come from non-Geantino particles.
66 if (edep == 0.0 and not isGeantino(aStep)) {
67 ldmx_log(trace) << "CalorimeterSD skipping step with zero edep.";
68 return false;
69 }
70
71 //---------------------------------------------------------------------------------------------------
72 // Birks' Law
73 // ===========
74 //
75 // In the case of Scintillator as active medium, we can
76 // describe the quenching effects with the Birks' law,
77 // using the expression and the coefficients taken from
78 // the paper NIM 80 (1970) 239-244 for the organic
79 // scintillator NE-102:
80 // S*dE/dr
81 // dL/dr = -----------------------------------
82 // 1 + C1*(dE/dr)
83 // with:
84 // S=1
85 // C1 = 1.29 x 10^-2 g*cm^-2*MeV^-1
86 // C2 = 9.59 x 10^-6 g^2*cm^-4*MeV^-2
87 // These are the same values used by ATLAS TileCal
88 // and CMS HCAL (and also the default in Geant3).
89 // You can try different values for these parameters,
90 // to have an idea on the uncertainties due to them,
91 // by uncommenting one of the lines below.
92 // To get the "dE/dr" that appears in the formula,
93 // which has the dimensions
94 // [ dE/dr ] = MeV * cm^2 / g
95 // we have to divide the energy deposit in MeV by the
96 // product of the step length (in cm) and the density
97 // of the scintillator:
98
99 G4double birks_factor(1.0);
100 G4double step_length = aStep->GetStepLength() / CLHEP::cm;
101 // Do not apply Birks for gamma deposits!
102 // Check, cut if necessary.
103 if (step_length > 1.0e-6) {
104 G4double rho = aStep->GetPreStepPoint()->GetMaterial()->GetDensity() /
105 (CLHEP::g / CLHEP::cm3);
106 G4double dedx = edep / (rho * step_length); //[MeV*cm^2/g]
107 birks_factor = 1.0 / (1.0 + birksc1_ * dedx + birksc2_ * dedx * dedx);
108 if (aStep->GetTrack()->GetDefinition() == G4Gamma::GammaDefinition())
109 birks_factor = 1.0;
110 if (aStep->GetTrack()->GetDefinition() == G4Neutron::NeutronDefinition())
111 birks_factor = 1.0;
112 }
113
114 // update edep to include birksFactor
115 edep *= birks_factor;
116
117 // Get the scintillator solid box
118 G4Box* scint = nullptr;
119
120 if (aStep) {
121 const auto* pre_step_point = aStep->GetPreStepPoint();
122 if (pre_step_point) {
123 const auto& touchable_handle = pre_step_point->GetTouchableHandle();
124 if (touchable_handle) {
125 const auto* volume = touchable_handle->GetVolume();
126
127 if (volume) {
128 const auto* logical_volume = volume->GetLogicalVolume();
129 if (logical_volume) {
130 auto* solid = logical_volume->GetSolid();
131 if (solid) {
132 scint = static_cast<G4Box*>(solid);
133 }
134 }
135 }
136 }
137 }
138 }
139
140 // Set the step mid-point as the hit position.
141 G4StepPoint* pre_point = aStep->GetPreStepPoint();
142 G4StepPoint* post_point = aStep->GetPostStepPoint();
143 // A Geant4 "touchable" is a way to uniquely identify a particular volume,
144 // short for touchable detector element. See the detector definition and
145 // response section of the Geant4 application developers manual for details.
146 //
147 // The TouchableHandle is just a reference counted pointer to a
148 // G4TouchableHistory object, which is a concrete implementation of a
149 // G4Touchable interface.
150 //
151 auto touchable_history{pre_point->GetTouchableHandle()->GetHistory()};
152 // Affine transform for converting between local and global coordinates
153 auto top_transform{touchable_history->GetTopTransform()};
154 G4ThreeVector position =
155 0.5 * (pre_point->GetPosition() + post_point->GetPosition());
156 G4ThreeVector local_position = top_transform.TransformPoint(position);
157
158 // Create the ID for the hit. Note 2 here corresponds to the "depth" of the
159 // geometry tree. If this changes in the GDML, this would have to be updated
160 // here. Currently, 0 corresponds to the world volume, 1 corresponds to the
161 // Hcal, and 2 to the bars/absorbers
162 int copy_num = touchable_history->GetVolume(2)->GetCopyNo();
163 ldmx::HcalID id = decodeCopyNumber(copy_num, local_position, scint);
164
165 if (hits_.find(id) == hits_.end()) {
166 // hit in empty cell/bar
167 auto& new_hit = hits_[id];
168 new_hit.setID(id.raw());
169 new_hit.setPosition(position[0], position[1], position[2]);
170 }
171
172 auto& hit = hits_[id];
173
174 // hit variables
175 const G4Track* track = aStep->GetTrack();
176 auto time = track->GetGlobalTime();
177 auto track_id = track->GetTrackID();
178 auto pdg = track->GetParticleDefinition()->GetPDGEncoding();
179
181 int contrib_i = hit.findContribIndex(track_id, pdg);
182 if (compress_hit_contribs_ and contrib_i != -1) {
183 hit.updateContrib(contrib_i, edep, time);
184 } else {
185 auto map{getTrackMap()};
186 auto incident{map.findIncident(track_id)};
187 // default "origin" is just the same as incident
188 // "origin" checks if a hit "originates" from one of the earliest
189 // track IDs (i.e. probably one of the primaries)
190 int origin{incident};
191 for (int i{1}; i < max_origin_track_id_; ++i) {
192 if (map.isDescendant(track_id, i, 100)) {
193 origin = i;
194 break;
195 }
196 }
197 hit.addContrib(incident, track_id, pdg, edep, time, origin);
198 }
199 } else {
200 // no hit contribs and hit already exists
201 hit.setEdep(hit.getEdep() + edep);
202 if (time < hit.getTime() or hit.getTime() == 0) {
203 hit.setTime(time);
204 }
205 }
206 // Pre/post step details for scintillator response simulation
207 // Note: These are set for each step, so the last step's details will be used
208 // if multiple steps occur in the same bar
209
210 // Convert back to mm
211 hit.setPathLength(step_length * CLHEP::cm / CLHEP::mm);
212 hit.setVelocity(track->GetVelocity());
213 const auto& geometry = getCondition<ldmx::HcalGeometry>(
215 // Convert pre/post step position from global coordinates to coordinates
216 // within the scintillator bar
217 const auto local_pre_step_point{
218 top_transform.TransformPoint(pre_point->GetPosition())};
219 const auto local_post_step_point{
220 top_transform.TransformPoint(post_point->GetPosition())};
221
222 // And rotate them to a local coordinate system for the bar that always has
223 // the same x/y/z definitions (see HcalGeometry for details)
224 auto local_pre_position_rotated{geometry.rotateGlobalToLocalBarPosition(
225 {local_pre_step_point[0], local_pre_step_point[1],
226 local_pre_step_point[2]},
227 id)};
228
229 auto local_post_position_rotated{geometry.rotateGlobalToLocalBarPosition(
230 {local_post_step_point[0], local_post_step_point[1],
231 local_post_step_point[2]},
232 id)};
233 hit.setPreStepPosition(local_pre_position_rotated[0],
234 local_pre_position_rotated[1],
235 local_pre_position_rotated[2]);
236 hit.setPostStepPosition(local_post_position_rotated[0],
237 local_post_position_rotated[1],
238 local_post_position_rotated[2]);
239 hit.setPreStepTime(pre_point->GetGlobalTime());
240 hit.setPostStepTime(post_point->GetGlobalTime());
241
242 ldmx_log(trace) << hit;
243
244 return true;
245}
246
248 // squash hits into list
249 std::vector<ldmx::SimCalorimeterHit> hits;
250 hits.reserve(hits_.size());
251 for (const auto& [id, hit] : hits_) hits.push_back(hit);
252 event.add(COLLECTION_NAME, hits);
253}
254
255} // namespace simcore
256
257DECLARE_SENSITIVEDETECTOR(simcore::HcalSD)
Class that translates HCal ID into positions of strip hits.
Class that defines an HCal sensitive detector.
Class which represents a maximally-packed index of up to four fields.
Implements an event buffer system for storing event data.
Definition Event.h:40
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:26
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:75
static constexpr const char * CONDITIONS_OBJECT_NAME
Conditions object: The name of the python configuration calling this class (Hcal/python/HcalGeometry....
Implements detector ids for HCal subdetector.
Definition HcalID.h:19
A maximally-packed index of up to four different fields.
Definition PackedIndex.h:32
Handle to the conditions system, provided at construction to classes which require it.
Class defining a sensitive detector of type HCal.
Definition HcalSD.h:21
ldmx::HcalID decodeCopyNumber(const std::uint32_t copyNumber, const G4ThreeVector &localPosition, const G4Box *scint)
Decode copy number of scintillator bar.
Definition HcalSD.cxx:33
virtual void saveHits(framework::Event &event) override
Add our hits to the event bus and then reset the container.
Definition HcalSD.cxx:247
bool compress_hit_contribs_
compress hit contribs
Definition HcalSD.h:106
static const std::string COLLECTION_NAME
name of collection to be added to event bus
Definition HcalSD.h:24
std::map< ldmx::HcalID, ldmx::SimCalorimeterHit > hits_
map of hits to add to the event (will be squashed)
Definition HcalSD.h:102
virtual G4bool ProcessHits(G4Step *aStep, G4TouchableHistory *ROhist) override
Create a hit out of the energy deposition deposited during a step.
Definition HcalSD.cxx:61
HcalSD(const std::string &name, simcore::ConditionsInterface &ci, const framework::config::Parameters &params)
Constructor.
Definition HcalSD.cxx:24
bool enable_hit_contribs_
enable hit contribs
Definition HcalSD.h:104
int max_origin_track_id_
maximum track ID to be considered an "origin"
Definition HcalSD.h:108
Dynamically loaded Geant4 SensitiveDetector for saving hits in specific volumes within the simulation...
bool isGeantino(const G4Step *step) const
Check if the passed step is a step of a geantino.
const TrackMap & getTrackMap() const
Get a handle to the current track map.
const T & getCondition(const std::string &condition_name)
Record the configuration of this detector into the run header.
Dynamically loadable photonuclear models either from SimCore or external libraries implementing this ...