LDMX Software
HcalDigiProducer.cxx
Go to the documentation of this file.
1
9#include "Hcal/HcalDigiProducer.h"
10
12
13namespace hcal {
14
15HcalDigiProducer::HcalDigiProducer(const std::string& name,
16 framework::Process& process)
17 : Producer(name, process) {
18 /*
19 * Noise generator by default uses a Gausian model for noise
20 * i.e. It assumes the noise is distributed around a mean (setPedestal)
21 * with a certain RMS (setNoise) and then calculates
22 * how many hits should be generated for a given number of empty
23 * channels and a minimum readout value (setNoiseThreshold)
24 */
25 noiseGenerator_ = std::make_unique<ldmx::NoiseGenerator>();
26}
27
29 // settings of readout chip
30 // used in actual digitization
31 auto hgcrocParams = ps.getParameter<framework::config::Parameters>("hgcroc");
32 hgcroc_ = std::make_unique<ldmx::HgcrocEmulator>(hgcrocParams);
33 clockCycle_ = hgcrocParams.getParameter<double>("clockCycle");
34 nADCs_ = hgcrocParams.getParameter<int>("nADCs");
35 iSOI_ = hgcrocParams.getParameter<int>("iSOI");
36 noise_ = hgcrocParams.getParameter<bool>("noise");
37
38 // collection names
39 inputCollName_ = ps.getParameter<std::string>("inputCollName");
40 inputPassName_ = ps.getParameter<std::string>("inputPassName");
41 digiCollName_ = ps.getParameter<std::string>("digiCollName");
42
43 // physical constants
44 // used to calculate unit conversions
45 MeV_ = ps.getParameter<double>("MeV");
46 attlength_ = ps.getParameter<double>("attenuationLength");
47
48 // Time -> clock counts conversion
49 // time [ns] * ( 2^10 / max time in ns ) = clock counts
50 ns_ = 1024. / clockCycle_;
51
52 // Configure generator that will produce noise hits in empty channels
53 double readoutThreshold = ps.getParameter<double>("avgReadoutThreshold");
54 double gain = ps.getParameter<double>("avgGain");
55 double pedestal = ps.getParameter<double>("avgPedestal");
56 // rms noise in mV
57 noiseGenerator_->setNoise(gain * ps.getParameter<double>("avgNoiseRMS"));
58 // mean noise amplitude (if using Gaussian Model for the noise) in mV
59 noiseGenerator_->setPedestal(gain * pedestal);
60 // threshold for readout in mV
61 noiseGenerator_->setNoiseThreshold(gain * readoutThreshold);
62}
63
65 // Handle seeding on the first event
66 if (!noiseGenerator_->hasSeed()) {
67 const auto& rseed = getCondition<framework::RandomNumberSeedService>(
69 noiseGenerator_->seedGenerator(
70 rseed.getSeed("HcalDigiProducer::NoiseGenerator"));
71 }
72 if (noiseInjector_.get() == nullptr) {
73 const auto& rseed = getCondition<framework::RandomNumberSeedService>(
75 noiseInjector_ = std::make_unique<TRandom3>(
76 rseed.getSeed("HcalDigiProducer::NoiseInjector"));
77 }
78 if (!hgcroc_->hasSeed()) {
79 const auto& rseed = getCondition<framework::RandomNumberSeedService>(
81 hgcroc_->seedGenerator(rseed.getSeed("HcalDigiProducer::HgcrocEmulator"));
82 }
83
84 // Get the Hgcroc Conditions
85 hgcroc_->condition(
86 getCondition<conditions::DoubleTableCondition>("HcalHgcrocConditions"));
87
88 // Get the Hcal Geometry
89 const auto& hcalGeometry = getCondition<ldmx::HcalGeometry>(
91
92 // Empty collection to be filled
96
97 std::map<unsigned int, std::vector<const ldmx::SimCalorimeterHit*>> hitsByID;
98
99 // get simulated hcal hits from Geant4 and group them by id
100 auto hcalSimHits{event.getCollection<ldmx::SimCalorimeterHit>(
102
103 for (auto const& simHit : hcalSimHits) {
104 // get ID
105 unsigned int hitID = simHit.getID();
106
107 auto idh = hitsByID.find(hitID);
108 if (idh == hitsByID.end()) {
109 hitsByID[hitID] = std::vector<const ldmx::SimCalorimeterHit*>(1, &simHit);
110 } else {
111 idh->second.push_back(&simHit);
112 }
113 }
114
115 /******************************************************************************************
116 * HGCROC Emulation on Simulated Hits (grouped by HcalID)
117 ******************************************************************************************/
118 for (auto const& simBar : hitsByID) {
119 ldmx::HcalID detID(simBar.first);
120 int section = detID.section();
121 int layer = detID.layer();
122 int strip = detID.strip();
123
124 // get position
125 double half_total_width = hcalGeometry.getHalfTotalWidth(section, layer);
126 double ecal_dx = hcalGeometry.getEcalDx();
127 double ecal_dy = hcalGeometry.getEcalDy();
128
129 // contributions
130 std::vector<std::pair<double, double>> pulses_posend;
131 std::vector<std::pair<double, double>> pulses_negend;
132
133 for (auto psimHit : simBar.second) {
134 const ldmx::SimCalorimeterHit& simHit = *psimHit;
135
136 std::vector<float> position = simHit.getPosition();
137
166 float distance_along_bar, distance_ecal;
167 float distance_close, distance_far;
168 int end_close;
169 const auto orientation{hcalGeometry.getScintillatorOrientation(detID)};
170 if (section == ldmx::HcalID::HcalSection::BACK) {
171 distance_along_bar =
172 (orientation ==
173 ldmx::HcalGeometry::ScintillatorOrientation::horizontal)
174 ? position[0]
175 : position[1];
176 end_close = (distance_along_bar > 0) ? 0 : 1;
177 distance_close = half_total_width;
178 distance_far = half_total_width;
179 } else {
180 if ((section == ldmx::HcalID::HcalSection::TOP) ||
181 ((section == ldmx::HcalID::HcalSection::BOTTOM))) {
182 distance_along_bar = position[0];
183 distance_ecal = ecal_dx;
184 } else if ((section == ldmx::HcalID::HcalSection::LEFT) ||
185 (section == ldmx::HcalID::HcalSection::RIGHT)) {
186 distance_along_bar = position[1];
187 distance_ecal = ecal_dy;
188 } else {
189 distance_along_bar = -9999.;
190 EXCEPTION_RAISE(
191 "BadCode",
192 "We should never end up here "
193 "All cases of HCAL considered, end_close is meaningless");
194 }
195 end_close = (distance_along_bar > half_total_width) ? 0 : 1;
196 distance_close = (end_close == 0)
197 ? 2 * half_total_width - distance_ecal / 2
198 : distance_ecal / 2;
199 distance_far = (end_close == 0)
200 ? distance_ecal / 2
201 : 2 * half_total_width - distance_ecal / 2;
202 }
203
204 // Calculate voltage attenuation and time shift for the close and far
205 // pulse.
206 float v = 299.792 /
207 1.6; // velocity of light in Polystyrene, n = 1.6 = c/v mm/ns
208 double att_close =
209 exp(-1. * ((distance_close - fabs(distance_along_bar)) / 1000.) /
210 attlength_);
211 double att_far =
212 exp(-1. * ((distance_far + fabs(distance_along_bar)) / 1000.) /
213 attlength_);
214 double shift_close =
215 fabs((distance_close - fabs(distance_along_bar)) / v);
216 double shift_far = fabs((distance_far + fabs(distance_along_bar)) / v);
217
218 // Get voltages and times.
219 for (int iContrib = 0; iContrib < simHit.getNumberOfContribs();
220 iContrib++) {
221 double voltage = simHit.getContrib(iContrib).edep * MeV_;
222 double time =
223 simHit.getContrib(iContrib).time; // global time (t=0ns at target)
224 time -= position.at(2) /
225 299.702547; // shift light-speed particle traveling along z
226
227 if (end_close == 0) {
228 pulses_posend.emplace_back(voltage * att_close, time + shift_close);
229 pulses_negend.emplace_back(voltage * att_far, time + shift_far);
230 } else {
231 pulses_posend.emplace_back(voltage * att_far, time + shift_far);
232 pulses_negend.emplace_back(voltage * att_close, time + shift_close);
233 }
234 }
235 }
236
246 if (section == ldmx::HcalID::HcalSection::BACK) {
247 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAddPosend,
248 digiToAddNegend;
249 ldmx::HcalDigiID posendID(section, layer, strip, 0);
250 ldmx::HcalDigiID negendID(section, layer, strip, 1);
251 if (hgcroc_->digitize(posendID.raw(), pulses_posend, digiToAddPosend) &&
252 hgcroc_->digitize(negendID.raw(), pulses_negend, digiToAddNegend)) {
253 hcalDigis.addDigi(posendID.raw(), digiToAddPosend);
254 hcalDigis.addDigi(negendID.raw(), digiToAddNegend);
255 } // Back Hcal needs to digitize both pulses or none
256 } else {
257 bool is_posend = false;
258 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAdd;
259 if ((section == ldmx::HcalID::HcalSection::TOP) ||
260 (section == ldmx::HcalID::HcalSection::LEFT)) {
261 is_posend = true;
262 } else if ((section == ldmx::HcalID::HcalSection::BOTTOM) ||
263 (section == ldmx::HcalID::HcalSection::RIGHT)) {
264 is_posend = false;
265 }
266 if (is_posend) {
267 ldmx::HcalDigiID digiID(section, layer, strip, 0);
268 if (hgcroc_->digitize(digiID.raw(), pulses_posend, digiToAdd)) {
269 hcalDigis.addDigi(digiID.raw(), digiToAdd);
270 }
271 } else {
272 ldmx::HcalDigiID digiID(section, layer, strip, 1);
273 if (hgcroc_->digitize(digiID.raw(), pulses_negend, digiToAdd)) {
274 hcalDigis.addDigi(digiID.raw(), digiToAdd);
275 }
276 }
277 }
278 }
279
280 /******************************************************************************************
281 * Noise Simulation on Empty Channels
282 *****************************************************************************************/
283 if (noise_) {
284 int numChannels = 0;
285 for (int section = 0; section < hcalGeometry.getNumSections(); section++) {
286 int numChannelsInSection = 0;
287 for (int layer = 1; layer <= hcalGeometry.getNumLayers(section);
288 layer++) {
289 numChannelsInSection += hcalGeometry.getNumStrips(section, layer);
290 }
291 // for back Hcal we have double readout, therefore we multiply the number
292 // of channels by 2.
293 if (section == ldmx::HcalID::HcalSection::BACK) {
294 numChannelsInSection *= 2;
295 }
296 numChannels += numChannelsInSection;
297 }
298 int numEmptyChannels = numChannels - hcalDigis.getNumDigis();
299 // noise generator gives us a list of noise amplitudes [mV] that randomly
300 // populate the empty channels and are above the readout threshold
301 auto noiseHitAmplitudes{
302 noiseGenerator_->generateNoiseHits(numEmptyChannels)};
303 std::vector<std::pair<double, double>> fake_pulse(1, {0., 0.});
304 for (double noiseHit : noiseHitAmplitudes) {
305 // generate detector ID for noise hit
306 // making sure that it is in an empty channel
307 unsigned int noiseID;
308 int sectionID, layerID, stripID, endID;
309 do {
310 sectionID = noiseInjector_->Integer(hcalGeometry.getNumSections());
311 layerID = noiseInjector_->Integer(hcalGeometry.getNumLayers(sectionID));
312 // set layer to 1 if the generator says it is 0 (geometry map starts
313 // from 1)
314 if (layerID == 0) layerID = 1;
315 stripID = noiseInjector_->Integer(
316 hcalGeometry.getNumStrips(sectionID, layerID));
317 endID = noiseInjector_->Integer(2);
318 if ((sectionID == ldmx::HcalID::HcalSection::TOP) ||
319 (sectionID == ldmx::HcalID::HcalSection::LEFT)) {
320 endID = 0;
321 } else if ((sectionID == ldmx::HcalID::HcalSection::BOTTOM) ||
322 (sectionID == ldmx::HcalID::HcalSection::RIGHT)) {
323 endID = 1;
324 }
325 auto detID = ldmx::HcalDigiID(sectionID, layerID, stripID, endID);
326 noiseID = detID.raw();
327 } while (hitsByID.find(noiseID) != hitsByID.end());
328 hitsByID[noiseID] =
329 std::vector<const ldmx::SimCalorimeterHit*>(); // mark this as used
330
331 // get a time for this noise hit
332 fake_pulse[0].second = noiseInjector_->Uniform(clockCycle_);
333
334 // noise generator gives the amplitude above the readout threshold
335 // we need to convert it to the amplitude above the pedestal
336 double gain = hgcroc_->gain(noiseID);
337 fake_pulse[0].first = noiseHit +
338 gain * hgcroc_->readoutThreshold(noiseID) -
339 gain * hgcroc_->pedestal(noiseID);
340
341 if (sectionID == ldmx::HcalID::HcalSection::BACK) {
342 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAddPosend,
343 digiToAddNegend;
344 ldmx::HcalDigiID posendID(sectionID, layerID, stripID, 0);
345 ldmx::HcalDigiID negendID(sectionID, layerID, stripID, 1);
346 if (hgcroc_->digitize(posendID.raw(), fake_pulse, digiToAddPosend) &&
347 hgcroc_->digitize(negendID.raw(), fake_pulse, digiToAddNegend)) {
348 hcalDigis.addDigi(posendID.raw(), digiToAddPosend);
349 hcalDigis.addDigi(negendID.raw(), digiToAddNegend);
350 }
351 } else {
352 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAdd;
353 if (hgcroc_->digitize(noiseID, fake_pulse, digiToAdd)) {
354 hcalDigis.addDigi(noiseID, digiToAdd);
355 }
356 }
357 } // loop over noise amplitudes
358 } // if we should add noise
359
360 event.add(digiCollName_, hcalDigis);
361
362 return;
363} // produce
364
365} // namespace hcal
366
367DECLARE_PRODUCER_NS(hcal, HcalDigiProducer);
#define DECLARE_PRODUCER_NS(NS, CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Conditions object for random number seeds.
Implements an event buffer system for storing event data.
Definition Event.h:41
Class which represents the process under execution.
Definition Process.h:36
static const std::string CONDITIONS_OBJECT_NAME
Conditions object name.
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:27
T getParameter(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:89
bool noise_
Put noise into empty channels, not configurable, only helpful in development.
HcalDigiProducer(const std::string &name, framework::Process &process)
Constructor Makes unique noise generator and injector for this class.
double attlength_
Strip attenuation length [m].
void produce(framework::Event &event) override
Simulates measurement of pulse and creates digi collection for input event.
std::unique_ptr< ldmx::NoiseGenerator > noiseGenerator_
Generates noise hits based off of number of cells that are not hit.
std::string digiCollName_
output hit collection name
double ns_
Conversion from time in ns to ticks of the internal clock.
int nADCs_
Depth of ADC buffer.
double MeV_
Conversion from energy in MeV to voltage in mV.
std::unique_ptr< ldmx::HgcrocEmulator > hgcroc_
Hgcroc Emulator to digitize analog voltage signals.
std::string inputCollName_
input hit collection name
void configure(framework::config::Parameters &) override
Configure this producer from the python configuration.
double clockCycle_
Time interval for chip clock in ns.
std::string inputPassName_
input pass name
std::unique_ptr< TRandom3 > noiseInjector_
Generates Gaussian noise on top of real hits.
int iSOI_
Index for the Sample Of Interest in the list of digi samples.
RawValue raw() const
Definition DetectorID.h:68
Extension of HcalAbstractID providing access to HCal digi information.
Definition HcalDigiID.h:13
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
unsigned int strip() const
Get the value of the 'strip' field from the ID.
Definition HcalID.h:108
unsigned int layer() const
Get the value of the layer field from the ID.
Definition HcalID.h:90
Represents a collection of the digi hits readout by an HGCROC.
void setNumSamplesPerDigi(unsigned int n)
Set number of samples for each digi.
void setSampleOfInterestIndex(unsigned int n)
Set index of sample of interest.
void addDigi(unsigned int id, const std::vector< Sample > &digi)
Add samples to collection.
unsigned int getNumDigis() const
Get total number of digis.
Stores simulated calorimeter hit information.
unsigned getNumberOfContribs() const
Get the number of hit contributions.
Contrib getContrib(int i) const
Get a hit contribution by index.
std::vector< float > getPosition() const
Get the XYZ position of the hit [mm].
int getID() const
Get the detector ID.
float time
Time this contributor made the hit (global Geant4 time)
float edep
Energy depostied by this contributor.