LDMX Software
HcalDigiProducer.cxx
Go to the documentation of this file.
1
10#include "Hcal/HcalDigiProducer.h"
11
12namespace hcal {
13
14HcalDigiProducer::HcalDigiProducer(const std::string& name,
15 framework::Process& process)
16 : Producer(name, process) {
17 /*
18 * Noise generator by default uses a Gausian model for noise
19 * i.e. it assumes the noise is distributed around a mean (setPedestal)
20 * with a certain RMS (setNoise) and then calculates
21 * how many hits should be generated for a given number of empty
22 * channels and a minimum readout value (setNoiseThreshold)
23 */
24 noise_generator_ = std::make_unique<ldmx::NoiseGenerator>();
25}
26
27double HcalDigiProducer::makeTimeDelta(TimeSpreadType kind,
28 std::vector<double>& parameters) const {
29 switch (kind) {
30 case TimeSpreadType::UNIFORM:
31 return random_time_->Uniform(parameters[0], parameters[1]);
32 case TimeSpreadType::CONSTANT:
33 return parameters[0];
34 case TimeSpreadType::GAUSSIAN:
35 return random_time_->Gaus(parameters[0], parameters[1]);
36 }
37 return 0;
38}
39
41 // settings of readout chip
42 // used in actual digitization
43 auto hgcroc_params = ps.get<framework::config::Parameters>("hgcroc");
44 hgcroc_ = std::make_unique<ldmx::HgcrocEmulator>(hgcroc_params);
45 clock_cycle_ = hgcroc_params.get<double>("clock_cycle");
46 n_adcs_ = hgcroc_params.get<int>("n_adcs");
47 i_soi_ = hgcroc_params.get<int>("i_soi");
48 noise_ = hgcroc_params.get<bool>("noise");
49
50 // If true, ignore readout threshold
51 // and generate pedestal noise digis in every empty channel
52 zero_suppression_ = ps.get<bool>("zero_suppression");
53
54 // Save full analog pulse shapes from the HGCROC emulation
55 save_pulse_truth_info_ = ps.get<bool>("save_pulse_truth_info");
56
57 // collection names
58 input_coll_name_ = ps.get<std::string>("input_coll_name");
59 input_pass_name_ = ps.get<std::string>("input_pass_name");
60 digi_coll_name_ = ps.get<std::string>("digi_coll_name");
61 pulse_truth_coll_name_ = ps.get<std::string>("pulse_truth_coll_name");
62
63 // physical constants
64 // used to calculate unit conversions
65 mev_ = ps.get<double>("mev");
66 attlength_ = ps.get<double>("attenuation_length");
67
68 // Time -> clock counts conversion
69 // time [ns] * ( 2^10 / max time in ns ) = clock counts
70 ns_ = 1024. / clock_cycle_;
71
72 // Configure generator that will produce noise hits in empty channels
73 gain_ = ps.get<double>("avg_gain");
74 readout_threshold_ = ps.get<double>("avg_readout_threshold");
75 pedestal_ = ps.get<double>("avg_pedestal");
76 noise_rms_ = ps.get<double>("avg_noise_rms");
77
78 flat_time_shift_ = ps.get<double>("flat_time_shift");
79 // Time spread parameters
80 // Per hit
81 const auto& time_spread_per_hit{
82 ps.get<framework::config::Parameters>("time_spread_per_hit")};
83 int kind = time_spread_per_hit.get<int>("kind");
84 time_spread_per_hit_parameters_ =
85 time_spread_per_hit.get<std::vector<double>>("parameters");
86 do_time_spread_per_hit_ = kind != -1;
87 time_spread_per_hit_type_ = static_cast<TimeSpreadType>(kind);
88 // Per spill / event
89 const auto& time_spread_per_spill{
90 ps.get<framework::config::Parameters>("time_spread_per_spill")};
91 kind = time_spread_per_spill.get<int>("kind");
92 time_spread_per_spill_parameters_ =
93 time_spread_per_spill.get<std::vector<double>>("parameters");
94 do_time_spread_per_spill_ = kind != -1;
95 time_spread_per_spill_type_ = static_cast<TimeSpreadType>(kind);
96}
97
99 // rms noise in mV
100 noise_generator_->setNoise(gain_ * noise_rms_);
101 // mean noise amplitude (if using Gaussian Model for the noise) in mV
102 noise_generator_->setPedestal(gain_ * pedestal_);
103 // threshold for readout in mV
104 noise_generator_->setNoiseThreshold(gain_ * readout_threshold_);
105
106 // Set up seeds
109 noise_generator_->seedGenerator(
110 rseed.getSeed("HcalDigiProducer::NoiseGenerator"));
111
112 // Random number generator for layer_ / module_ / cell
113 rng_.seed(rseed.getSeed("HcalDigiProducer"));
114 // Setting up the read-out chip
115 hgcroc_->seedGenerator(rseed.getSeed("HcalDigiProducer::HgcrocEmulator"));
116
117 // Random number generator for time shifts
118 random_time_ =
119 std::make_unique<TRandom2>(rseed.getSeed("HcalDigiProducer::randomTime"));
120}
121
123 // Get the Hgcroc Conditions
124 hgcroc_->condition(
125 getCondition<conditions::DoubleTableCondition>("HcalHgcrocConditions"));
126
127 // Get the Hcal Geometry
128 const auto& hcal_geometry = getCondition<ldmx::HcalGeometry>(
130
131 // Empty collection to be filled
133 hcal_digis.setNumSamplesPerDigi(n_adcs_);
135
136 std::map<unsigned int, std::vector<const ldmx::SimCalorimeterHit*>>
137 hits_by_id;
138
139 // get simulated hcal hits from Geant4 and group them by id
140 auto hcal_sim_hits{event.getCollection<ldmx::SimCalorimeterHit>(
142
143 for (auto const& sim_hit : hcal_sim_hits) {
144 // get ID
145 unsigned int hit_id = sim_hit.getID();
146
147 auto idh = hits_by_id.find(hit_id);
148 if (idh == hits_by_id.end()) {
149 hits_by_id[hit_id] =
150 std::vector<const ldmx::SimCalorimeterHit*>(1, &sim_hit);
151 } else {
152 idh->second.push_back(&sim_hit);
153 }
154 }
155
156 ldmx::HgcrocPulseTruthCollection hcal_pulse_truth_coll;
158 hgcroc_->pulse_truth_coll_ = &hcal_pulse_truth_coll;
159 hgcroc_->save_pulse_truth_info_ = true;
160 }
161
162 /******************************************************************************************
163 * HGCROC Emulation on Simulated Hits (grouped by HcalID)
164 ******************************************************************************************/
165 double time_delta{flat_time_shift_};
166 if (do_time_spread_per_spill_) {
167 time_delta += makeTimeDelta(time_spread_per_spill_type_,
168 time_spread_per_spill_parameters_);
169 }
170 for (auto const& sim_bar : hits_by_id) {
171 ldmx::HcalID det_id(sim_bar.first);
172 int section = det_id.section();
173 int layer = det_id.layer();
174 int strip = det_id.strip();
175
176 // get position
177 double half_total_width = hcal_geometry.getHalfTotalWidth(section, layer);
178 double ecal_dx = hcal_geometry.getEcalDx();
179 double ecal_dy = hcal_geometry.getEcalDy();
180
181 // contributions
182 std::vector<std::pair<double, double>> pulses_posend;
183 std::vector<std::pair<double, double>> pulses_negend;
184
185 for (auto psim_hit : sim_bar.second) {
186 const ldmx::SimCalorimeterHit& sim_hit = *psim_hit;
187
188 std::vector<float> position = sim_hit.getPosition();
189
218 float distance_along_bar, distance_ecal;
219 float distance_close, distance_far;
220 int end_close;
221 const auto orientation{hcal_geometry.getScintillatorOrientation(det_id)};
222 if (section == ldmx::HcalID::HcalSection::BACK) {
223 distance_along_bar =
224 (orientation ==
225 ldmx::HcalGeometry::ScintillatorOrientation::horizontal)
226 ? position[0]
227 : position[1];
228 end_close = (distance_along_bar > 0) ? 0 : 1;
229 distance_close = half_total_width;
230 distance_far = half_total_width;
231 } else {
232 if ((section == ldmx::HcalID::HcalSection::TOP) ||
233 ((section == ldmx::HcalID::HcalSection::BOTTOM))) {
234 distance_along_bar = position[0];
235 distance_ecal = ecal_dx;
236 } else if ((section == ldmx::HcalID::HcalSection::LEFT) ||
237 (section == ldmx::HcalID::HcalSection::RIGHT)) {
238 distance_along_bar = position[1];
239 distance_ecal = ecal_dy;
240 } else {
241 distance_along_bar = -9999.;
242 EXCEPTION_RAISE(
243 "BadCode",
244 "We should never end up here "
245 "All cases of HCAL considered, end_close is meaningless");
246 }
247 end_close = (distance_along_bar > half_total_width) ? 0 : 1;
248 distance_close = (end_close == 0)
249 ? 2 * half_total_width - distance_ecal / 2
250 : distance_ecal / 2;
251 distance_far = (end_close == 0)
252 ? distance_ecal / 2
253 : 2 * half_total_width - distance_ecal / 2;
254 }
255
256 // Calculate voltage attenuation and time shift for the close and far
257 // pulse.
258 // velocity of light in Polystyrene, n = 1.6 = c/v mm/ns
259 float v = 299.792 / 1.6;
260 double att_close =
261 exp(-1. * ((distance_close - fabs(distance_along_bar)) / 1000.) /
262 attlength_);
263 double att_far =
264 exp(-1. * ((distance_far + fabs(distance_along_bar)) / 1000.) /
265 attlength_);
266 double shift_close =
267 fabs((distance_close - fabs(distance_along_bar)) / v);
268 double shift_far = fabs((distance_far + fabs(distance_along_bar)) / v);
269
270 // Get voltages and times.
271 for (int i_contrib = 0; i_contrib < sim_hit.getNumberOfContribs();
272 i_contrib++) {
273 double voltage = sim_hit.getContrib(i_contrib).edep_ * mev_;
274 // global time (t=0ns at target)
275 double time = sim_hit.getContrib(i_contrib).time_;
276 // shift light-speed particle traveling along z_
277 time -= position.at(2) / 299.702547;
278 if (do_time_spread_per_hit_) {
279 time += makeTimeDelta(time_spread_per_hit_type_,
280 time_spread_per_hit_parameters_);
281 }
282 time += time_delta;
283
284 if (end_close == 0) {
285 pulses_posend.emplace_back(voltage * att_close, time + shift_close);
286 pulses_negend.emplace_back(voltage * att_far, time + shift_far);
287 } else {
288 pulses_posend.emplace_back(voltage * att_far, time + shift_far);
289 pulses_negend.emplace_back(voltage * att_close, time + shift_close);
290 }
291 }
292 }
293
303 if (section == ldmx::HcalID::HcalSection::BACK) {
304 std::vector<ldmx::HgcrocDigiCollection::Sample> digi_to_add_posend,
305 digi_to_add_negend;
306 ldmx::HcalDigiID posend_id(section, layer, strip, 0);
307 ldmx::HcalDigiID negend_id(section, layer, strip, 1);
308
309 bool pos_end_activity =
310 hgcroc_->digitize(posend_id.raw(), pulses_posend, digi_to_add_posend);
311 bool neg_end_activity =
312 hgcroc_->digitize(negend_id.raw(), pulses_negend, digi_to_add_negend);
313
314 if (pos_end_activity && neg_end_activity && zero_suppression_) {
315 hcal_digis.addDigi(posend_id.raw(), digi_to_add_posend);
316 hcal_digis.addDigi(negend_id.raw(), digi_to_add_negend);
317 } // If zeroSuppression == true, Back Hcal needs to digitize both
318 // pulses or none
319
320 if (!zero_suppression_) {
321 if (pos_end_activity) {
322 hcal_digis.addDigi(posend_id.raw(), digi_to_add_posend);
323 } else {
324 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
325 hgcroc_->noiseDigi(posend_id.raw(), 0.0);
326 hcal_digis.addDigi(posend_id.raw(), digi);
327 }
328 if (neg_end_activity) {
329 hcal_digis.addDigi(negend_id.raw(), digi_to_add_negend);
330 } else {
331 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
332 hgcroc_->noiseDigi(negend_id.raw(), 0.0);
333 hcal_digis.addDigi(negend_id.raw(), digi);
334 }
335 }
336
337 } else {
338 bool is_posend = false;
339 std::vector<ldmx::HgcrocDigiCollection::Sample> digi_to_add;
340 if ((section == ldmx::HcalID::HcalSection::TOP) ||
341 (section == ldmx::HcalID::HcalSection::LEFT)) {
342 is_posend = true;
343 } else if ((section == ldmx::HcalID::HcalSection::BOTTOM) ||
344 (section == ldmx::HcalID::HcalSection::RIGHT)) {
345 is_posend = false;
346 }
347 if (is_posend) {
348 ldmx::HcalDigiID digi_id(section, layer, strip, 0);
349 if (hgcroc_->digitize(digi_id.raw(), pulses_posend, digi_to_add)) {
350 hcal_digis.addDigi(digi_id.raw(), digi_to_add);
351 } else if (!zero_suppression_) {
352 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
353 hgcroc_->noiseDigi(digi_id.raw(), 0.0);
354 hcal_digis.addDigi(digi_id.raw(), digi);
355 }
356 } else {
357 ldmx::HcalDigiID digi_id(section, layer, strip, 1);
358 if (hgcroc_->digitize(digi_id.raw(), pulses_negend, digi_to_add)) {
359 hcal_digis.addDigi(digi_id.raw(), digi_to_add);
360 } else if (!zero_suppression_) {
361 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
362 hgcroc_->noiseDigi(digi_id.raw(), 0.0);
363 hcal_digis.addDigi(digi_id.raw(), digi);
364 }
365 }
366 }
367 }
368
369 /******************************************************************************************
370 * Noise Simulation on Empty Channels
371 *****************************************************************************************/
372 if (noise_) {
373 std::vector<ldmx::HcalDigiID> channel_map;
374 int num_channels = 0;
375 for (int section = 0; section < hcal_geometry.getNumSections(); section++) {
376 for (int layer = 1; layer <= hcal_geometry.getNumLayers(section);
377 layer++) {
378 // Note zero-indexed strip numbering...
379 for (int strip = 0; strip < hcal_geometry.getNumStrips(section, layer);
380 strip++) {
381 if (section == ldmx::HcalID::HcalSection::BACK) {
382 auto digi_i_dend0 = ldmx::HcalDigiID(section, layer, strip, 0);
383 auto digi_i_dend1 = ldmx::HcalDigiID(section, layer, strip, 1);
384 channel_map.push_back(digi_i_dend0);
385 channel_map.push_back(digi_i_dend1);
386 num_channels += 2;
387 } else {
388 auto digi_id = ldmx::HcalDigiID(section, layer, strip, 0);
389 channel_map.push_back(digi_id);
390 num_channels++;
391 }
392 }
393 }
394 }
395
396 // Uniform distributions for integer generation
397 std::uniform_int_distribution<int> section_dist(
398 0, hcal_geometry.getNumSections() - 1);
399 std::uniform_int_distribution<int> end_dist(0, 1);
400 std::uniform_int_distribution<int> clock_dist(0, clock_cycle_);
401
402 // Fast noise sim
403 if (zero_suppression_) {
404 int num_empty_channels = num_channels - hcal_digis.getNumDigis();
405 // noise generator gives us a list of noise amplitudes [mV] that randomly
406 // populate the empty channels and are above the readout threshold
407 auto noise_hit_amplitudes{
408 noise_generator_->generateNoiseHits(num_empty_channels)};
409 std::vector<std::pair<double, double>> fake_pulse(1, {0., 0.});
410
411 for (double noise_hit : noise_hit_amplitudes) {
412 // generate detector ID for noise hit
413 // making sure that it is in an empty channel
414 unsigned int noise_id;
415 int section_id, layer_id, strip_id, end_id;
416 do {
417 // Get a random section value
418 section_id = section_dist(rng_);
419
420 // Get a random value for the layer_
421 std::uniform_int_distribution<int> layer_dist(
422 0, hcal_geometry.getNumLayers(section_id) - 1);
423 layer_id = layer_dist(rng_);
424 // set layer_ to 1 if the generator says it is 0 (geometry map starts
425 // from 1)
426 if (layer_id == 0) layer_id = 1;
427
428 // Get a random value for the strip
429 std::uniform_int_distribution<int> strips_dist(
430 0, hcal_geometry.getNumStrips(section_id, layer_id) - 1);
431 strip_id = strips_dist(rng_);
432
433 // Get a random value for the end
434 if ((section_id == ldmx::HcalID::HcalSection::TOP) ||
435 (section_id == ldmx::HcalID::HcalSection::LEFT)) {
436 end_id = 0;
437 } else if ((section_id == ldmx::HcalID::HcalSection::BOTTOM) ||
438 (section_id == ldmx::HcalID::HcalSection::RIGHT)) {
439 end_id = 1;
440 } else {
441 end_id = end_dist(rng_);
442 }
443 auto det_id =
444 ldmx::HcalDigiID(section_id, layer_id, strip_id, end_id);
445 noise_id = det_id.raw();
446 } while (hits_by_id.find(noise_id) != hits_by_id.end());
447 hits_by_id[noise_id] =
448 std::vector<const ldmx::SimCalorimeterHit*>(); // mark this as used
449
450 // get a time for this noise hit
451 fake_pulse[0].second = clock_dist(rng_);
452
453 // noise generator gives the amplitude above the readout threshold
454 // we need to convert it to the amplitude above the pedestal
455 double gain = hgcroc_->gain(noise_id);
456 fake_pulse[0].first = noise_hit +
457 gain * hgcroc_->readoutThreshold(noise_id) -
458 gain * hgcroc_->pedestal(noise_id);
459
460 if (section_id == ldmx::HcalID::HcalSection::BACK) {
461 std::vector<ldmx::HgcrocDigiCollection::Sample> digi_to_add_posend,
462 digi_to_add_negend;
463 ldmx::HcalDigiID posend_id(section_id, layer_id, strip_id, 0);
464 ldmx::HcalDigiID negend_id(section_id, layer_id, strip_id, 1);
465 if (hgcroc_->digitize(posend_id.raw(), fake_pulse,
466 digi_to_add_posend) &&
467 hgcroc_->digitize(negend_id.raw(), fake_pulse,
468 digi_to_add_negend)) {
469 hcal_digis.addDigi(posend_id.raw(), digi_to_add_posend);
470 hcal_digis.addDigi(negend_id.raw(), digi_to_add_negend);
471 }
472 } else {
473 std::vector<ldmx::HgcrocDigiCollection::Sample> digi_to_add;
474 if (hgcroc_->digitize(noise_id, fake_pulse, digi_to_add)) {
475 hcal_digis.addDigi(noise_id, digi_to_add);
476 }
477 }
478 } // loop over noise amplitudes
479 } else { // If zero_suppression_ == false, add noise digis for all bars
480 // without simhits
481 for (auto digi_id : channel_map) {
482 // Convert from digi ID to det ID (since simhits don't know about
483 // different ends of the bar)
484 ldmx::HcalID detid(digi_id.section(), digi_id.layer(), digi_id.strip());
485 unsigned int rawdet_id = detid.raw();
486 if (hits_by_id.find(rawdet_id) == hits_by_id.end()) {
487 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
488 hgcroc_->noiseDigi(digi_id.raw(), 0.0);
489 hcal_digis.addDigi(digi_id.raw(), digi);
490 }
491 }
492 }
493 } // if we should add noise
494
495 event.add(digi_coll_name_, hcal_digis);
497 event.add(pulse_truth_coll_name_, hcal_pulse_truth_coll);
498
499 return;
500} // produce
501
502} // namespace hcal
503
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
const T & getCondition(const std::string &condition_name)
Access a conditions object for the current event.
Implements an event buffer system for storing event data.
Definition Event.h:42
Class which represents the process under execution.
Definition Process.h:37
static const std::string CONDITIONS_OBJECT_NAME
Conditions object name.
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:29
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78
Performs basic HCal digitization.
bool noise_
Put noise into empty channels, not configurable, only helpful in development.
double gain_
Read out gain.
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.
bool zero_suppression_
If false, save digis from all channels, even pure noise in empty bars Helpful when comparing with tes...
std::string pulse_truth_coll_name_
output pulse truth collection name
std::string digi_coll_name_
output hit collection name
double mev_
Conversion from energy in MeV to voltage in mV.
double ns_
Conversion from time in ns to ticks of the internal clock.
std::mt19937 rng_
Generates Gaussian noise on top of real hits_.
double noise_rms_
Noise RMS.
int n_adcs_
Depth of ADC buffer.
std::unique_ptr< ldmx::HgcrocEmulator > hgcroc_
Hgcroc Emulator to digitize analog voltage signals.
std::string input_coll_name_
input hit collection name
double readout_threshold_
Read out threshold.
void configure(framework::config::Parameters &) override
Configure this producer from the python configuration.
double clock_cycle_
Time interval for chip clock in ns.
std::string input_pass_name_
input pass name
virtual void onNewRun(const ldmx::RunHeader &runHeader) override
Random number generation.
double makeTimeDelta(TimeSpreadType kind, std::vector< double > &parameters) const
Create a randomized time delta based on the configured time spread type.
bool save_pulse_truth_info_
If true, save the "analog" composite pulse shape in the HGCROC emulator before it gets digitized.
int i_soi_
Index for the Sample Of Interest in the list of digi samples.
double pedestal_
Read out pedestal.
std::unique_ptr< ldmx::NoiseGenerator > noise_generator_
Generates noise hits based off of number of cells that are not hit.
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.
Run-specific configuration and data stored in its own output TTree alongside the event TTree in the o...
Definition RunHeader.h:57
Stores simulated calorimeter hit information.
unsigned getNumberOfContribs() const
Get the number of hit contributions.
std::vector< float > getPosition() const
Get the XYZ position of the hit [mm].
Contrib getContrib(int i) const
Get a hit contribution by index_.
float time_
Time this contributor made the hit (global Geant4 time)
float edep_
Energy depostied by this contributor.