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 noiseGenerator_ = std::make_unique<ldmx::NoiseGenerator>();
25}
26
28 // settings of readout chip
29 // used in actual digitization
30 auto hgcrocParams = ps.getParameter<framework::config::Parameters>("hgcroc");
31 hgcroc_ = std::make_unique<ldmx::HgcrocEmulator>(hgcrocParams);
32 clockCycle_ = hgcrocParams.getParameter<double>("clockCycle");
33 nADCs_ = hgcrocParams.getParameter<int>("nADCs");
34 iSOI_ = hgcrocParams.getParameter<int>("iSOI");
35 noise_ = hgcrocParams.getParameter<bool>("noise");
36
37 // If true, ignore readout threshold
38 // and generate pedestal noise digis in every empty channel
39 zeroSuppression_ = ps.getParameter<bool>("zeroSuppression");
40
41 // collection names
42 inputCollName_ = ps.getParameter<std::string>("inputCollName");
43 inputPassName_ = ps.getParameter<std::string>("inputPassName");
44 digiCollName_ = ps.getParameter<std::string>("digiCollName");
45
46 // physical constants
47 // used to calculate unit conversions
48 MeV_ = ps.getParameter<double>("MeV");
49 attlength_ = ps.getParameter<double>("attenuationLength");
50
51 // Time -> clock counts conversion
52 // time [ns] * ( 2^10 / max time in ns ) = clock counts
53 ns_ = 1024. / clockCycle_;
54
55 // Configure generator that will produce noise hits in empty channels
56 gain_ = ps.getParameter<double>("avgGain");
57 readoutThreshold_ = ps.getParameter<double>("avgReadoutThreshold");
58 pedestal_ = ps.getParameter<double>("avgPedestal");
59 noiseRMS_ = ps.getParameter<double>("avgNoiseRMS");
60}
61
63 // rms noise in mV
64 noiseGenerator_->setNoise(gain_ * noiseRMS_);
65 // mean noise amplitude (if using Gaussian Model for the noise) in mV
66 noiseGenerator_->setPedestal(gain_ * pedestal_);
67 // threshold for readout in mV
68 noiseGenerator_->setNoiseThreshold(gain_ * readoutThreshold_);
69
70 // Set up seeds
73 noiseGenerator_->seedGenerator(
74 rseed.getSeed("HcalDigiProducer::NoiseGenerator"));
75
76 // Random number generator for layer / module / cell
77 rng_.seed(rseed.getSeed("HcalDigiProducer"));
78 // Setting up the read-out chip
79 hgcroc_->seedGenerator(rseed.getSeed("HcalDigiProducer::HgcrocEmulator"));
80}
81
83 // Get the Hgcroc Conditions
84 hgcroc_->condition(
86
87 // Get the Hcal Geometry
88 const auto& hcalGeometry = getCondition<ldmx::HcalGeometry>(
90
91 // Empty collection to be filled
95
96 std::map<unsigned int, std::vector<const ldmx::SimCalorimeterHit*>> hitsByID;
97
98 // get simulated hcal hits from Geant4 and group them by id
99 auto hcalSimHits{event.getCollection<ldmx::SimCalorimeterHit>(
101
102 for (auto const& simHit : hcalSimHits) {
103 // get ID
104 unsigned int hitID = simHit.getID();
105
106 auto idh = hitsByID.find(hitID);
107 if (idh == hitsByID.end()) {
108 hitsByID[hitID] = std::vector<const ldmx::SimCalorimeterHit*>(1, &simHit);
109 } else {
110 idh->second.push_back(&simHit);
111 }
112 }
113
114 /******************************************************************************************
115 * HGCROC Emulation on Simulated Hits (grouped by HcalID)
116 ******************************************************************************************/
117 for (auto const& simBar : hitsByID) {
118 ldmx::HcalID detID(simBar.first);
119 int section = detID.section();
120 int layer = detID.layer();
121 int strip = detID.strip();
122
123 // get position
124 double half_total_width = hcalGeometry.getHalfTotalWidth(section, layer);
125 double ecal_dx = hcalGeometry.getEcalDx();
126 double ecal_dy = hcalGeometry.getEcalDy();
127
128 // contributions
129 std::vector<std::pair<double, double>> pulses_posend;
130 std::vector<std::pair<double, double>> pulses_negend;
131
132 for (auto psimHit : simBar.second) {
133 const ldmx::SimCalorimeterHit& simHit = *psimHit;
134
135 std::vector<float> position = simHit.getPosition();
136
165 float distance_along_bar, distance_ecal;
166 float distance_close, distance_far;
167 int end_close;
168 const auto orientation{hcalGeometry.getScintillatorOrientation(detID)};
169 if (section == ldmx::HcalID::HcalSection::BACK) {
170 distance_along_bar =
171 (orientation ==
172 ldmx::HcalGeometry::ScintillatorOrientation::horizontal)
173 ? position[0]
174 : position[1];
175 end_close = (distance_along_bar > 0) ? 0 : 1;
176 distance_close = half_total_width;
177 distance_far = half_total_width;
178 } else {
179 if ((section == ldmx::HcalID::HcalSection::TOP) ||
180 ((section == ldmx::HcalID::HcalSection::BOTTOM))) {
181 distance_along_bar = position[0];
182 distance_ecal = ecal_dx;
183 } else if ((section == ldmx::HcalID::HcalSection::LEFT) ||
184 (section == ldmx::HcalID::HcalSection::RIGHT)) {
185 distance_along_bar = position[1];
186 distance_ecal = ecal_dy;
187 } else {
188 distance_along_bar = -9999.;
189 EXCEPTION_RAISE(
190 "BadCode",
191 "We should never end up here "
192 "All cases of HCAL considered, end_close is meaningless");
193 }
194 end_close = (distance_along_bar > half_total_width) ? 0 : 1;
195 distance_close = (end_close == 0)
196 ? 2 * half_total_width - distance_ecal / 2
197 : distance_ecal / 2;
198 distance_far = (end_close == 0)
199 ? distance_ecal / 2
200 : 2 * half_total_width - distance_ecal / 2;
201 }
202
203 // Calculate voltage attenuation and time shift for the close and far
204 // pulse.
205 // velocity of light in Polystyrene, n = 1.6 = c/v mm/ns
206 float v = 299.792 / 1.6;
207 double att_close =
208 exp(-1. * ((distance_close - fabs(distance_along_bar)) / 1000.) /
209 attlength_);
210 double att_far =
211 exp(-1. * ((distance_far + fabs(distance_along_bar)) / 1000.) /
212 attlength_);
213 double shift_close =
214 fabs((distance_close - fabs(distance_along_bar)) / v);
215 double shift_far = fabs((distance_far + fabs(distance_along_bar)) / v);
216
217 // Get voltages and times.
218 for (int iContrib = 0; iContrib < simHit.getNumberOfContribs();
219 iContrib++) {
220 double voltage = simHit.getContrib(iContrib).edep * MeV_;
221 // global time (t=0ns at target)
222 double time = simHit.getContrib(iContrib).time;
223 // shift light-speed particle traveling along z
224 time -= position.at(2) / 299.702547;
225
226 if (end_close == 0) {
227 pulses_posend.emplace_back(voltage * att_close, time + shift_close);
228 pulses_negend.emplace_back(voltage * att_far, time + shift_far);
229 } else {
230 pulses_posend.emplace_back(voltage * att_far, time + shift_far);
231 pulses_negend.emplace_back(voltage * att_close, time + shift_close);
232 }
233 }
234 }
235
245 if (section == ldmx::HcalID::HcalSection::BACK) {
246 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAddPosend,
247 digiToAddNegend;
248 ldmx::HcalDigiID posendID(section, layer, strip, 0);
249 ldmx::HcalDigiID negendID(section, layer, strip, 1);
250
251 bool posEndActivity =
252 hgcroc_->digitize(posendID.raw(), pulses_posend, digiToAddPosend);
253 bool negEndActivity =
254 hgcroc_->digitize(negendID.raw(), pulses_negend, digiToAddNegend);
255
256 if (posEndActivity && negEndActivity && zeroSuppression_) {
257 hcalDigis.addDigi(posendID.raw(), digiToAddPosend);
258 hcalDigis.addDigi(negendID.raw(), digiToAddNegend);
259 } // If zeroSuppression == true, Back Hcal needs to digitize both
260 // pulses or none
261
262 if (!zeroSuppression_) {
263 if (posEndActivity) {
264 hcalDigis.addDigi(posendID.raw(), digiToAddPosend);
265 } else {
266 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
267 hgcroc_->noiseDigi(posendID.raw(), 0.0);
268 hcalDigis.addDigi(posendID.raw(), digi);
269 }
270 if (negEndActivity) {
271 hcalDigis.addDigi(negendID.raw(), digiToAddNegend);
272 } else {
273 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
274 hgcroc_->noiseDigi(negendID.raw(), 0.0);
275 hcalDigis.addDigi(negendID.raw(), digi);
276 }
277 }
278
279 } else {
280 bool is_posend = false;
281 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAdd;
282 if ((section == ldmx::HcalID::HcalSection::TOP) ||
283 (section == ldmx::HcalID::HcalSection::LEFT)) {
284 is_posend = true;
285 } else if ((section == ldmx::HcalID::HcalSection::BOTTOM) ||
286 (section == ldmx::HcalID::HcalSection::RIGHT)) {
287 is_posend = false;
288 }
289 if (is_posend) {
290 ldmx::HcalDigiID digiID(section, layer, strip, 0);
291 if (hgcroc_->digitize(digiID.raw(), pulses_posend, digiToAdd)) {
292 hcalDigis.addDigi(digiID.raw(), digiToAdd);
293 } else if (!zeroSuppression_) {
294 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
295 hgcroc_->noiseDigi(digiID.raw(), 0.0);
296 hcalDigis.addDigi(digiID.raw(), digi);
297 }
298 } else {
299 ldmx::HcalDigiID digiID(section, layer, strip, 1);
300 if (hgcroc_->digitize(digiID.raw(), pulses_negend, digiToAdd)) {
301 hcalDigis.addDigi(digiID.raw(), digiToAdd);
302 } else if (!zeroSuppression_) {
303 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
304 hgcroc_->noiseDigi(digiID.raw(), 0.0);
305 hcalDigis.addDigi(digiID.raw(), digi);
306 }
307 }
308 }
309 }
310
311 /******************************************************************************************
312 * Noise Simulation on Empty Channels
313 *****************************************************************************************/
314 if (noise_) {
315 std::vector<ldmx::HcalDigiID> channelMap;
316 int numChannels = 0;
317 for (int section = 0; section < hcalGeometry.getNumSections(); section++) {
318 for (int layer = 1; layer <= hcalGeometry.getNumLayers(section);
319 layer++) {
320 // Note zero-indexed strip numbering...
321 for (int strip = 0; strip < hcalGeometry.getNumStrips(section, layer);
322 strip++) {
323 if (section == ldmx::HcalID::HcalSection::BACK) {
324 auto digiIDend0 = ldmx::HcalDigiID(section, layer, strip, 0);
325 auto digiIDend1 = ldmx::HcalDigiID(section, layer, strip, 1);
326 channelMap.push_back(digiIDend0);
327 channelMap.push_back(digiIDend1);
328 numChannels += 2;
329 } else {
330 auto digiID = ldmx::HcalDigiID(section, layer, strip, 0);
331 channelMap.push_back(digiID);
332 numChannels++;
333 }
334 }
335 }
336 }
337
338 // Uniform distributions for integer generation
339 std::uniform_int_distribution<int> section_dist(
340 0, hcalGeometry.getNumSections() - 1);
341 std::uniform_int_distribution<int> end_dist(0, 1);
342 std::uniform_int_distribution<int> clock_dist(0, clockCycle_);
343
344 // Fast noise sim
345 if (zeroSuppression_) {
346 int numEmptyChannels = numChannels - hcalDigis.getNumDigis();
347 // noise generator gives us a list of noise amplitudes [mV] that randomly
348 // populate the empty channels and are above the readout threshold
349 auto noiseHitAmplitudes{
350 noiseGenerator_->generateNoiseHits(numEmptyChannels)};
351 std::vector<std::pair<double, double>> fake_pulse(1, {0., 0.});
352
353 for (double noiseHit : noiseHitAmplitudes) {
354 // generate detector ID for noise hit
355 // making sure that it is in an empty channel
356 unsigned int noiseID;
357 int sectionID, layerID, stripID, endID;
358 do {
359 // Get a random section value
360 sectionID = section_dist(rng_);
361
362 // Get a random value for the layer
363 std::uniform_int_distribution<int> layer_dist(
364 0, hcalGeometry.getNumLayers(sectionID) - 1);
365 layerID = layer_dist(rng_);
366 // set layer to 1 if the generator says it is 0 (geometry map starts
367 // from 1)
368 if (layerID == 0) layerID = 1;
369
370 // Get a random value for the strip
371 std::uniform_int_distribution<int> strips_dist(
372 0, hcalGeometry.getNumStrips(sectionID, layerID) - 1);
373 stripID = strips_dist(rng_);
374
375 // Get a random value for the end
376 if ((sectionID == ldmx::HcalID::HcalSection::TOP) ||
377 (sectionID == ldmx::HcalID::HcalSection::LEFT)) {
378 endID = 0;
379 } else if ((sectionID == ldmx::HcalID::HcalSection::BOTTOM) ||
380 (sectionID == ldmx::HcalID::HcalSection::RIGHT)) {
381 endID = 1;
382 } else {
383 endID = end_dist(rng_);
384 }
385 auto detID = ldmx::HcalDigiID(sectionID, layerID, stripID, endID);
386 noiseID = detID.raw();
387 } while (hitsByID.find(noiseID) != hitsByID.end());
388 hitsByID[noiseID] =
389 std::vector<const ldmx::SimCalorimeterHit*>(); // mark this as used
390
391 // get a time for this noise hit
392 fake_pulse[0].second = clock_dist(rng_);
393
394 // noise generator gives the amplitude above the readout threshold
395 // we need to convert it to the amplitude above the pedestal
396 double gain = hgcroc_->gain(noiseID);
397 fake_pulse[0].first = noiseHit +
398 gain * hgcroc_->readoutThreshold(noiseID) -
399 gain * hgcroc_->pedestal(noiseID);
400
401 if (sectionID == ldmx::HcalID::HcalSection::BACK) {
402 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAddPosend,
403 digiToAddNegend;
404 ldmx::HcalDigiID posendID(sectionID, layerID, stripID, 0);
405 ldmx::HcalDigiID negendID(sectionID, layerID, stripID, 1);
406 if (hgcroc_->digitize(posendID.raw(), fake_pulse, digiToAddPosend) &&
407 hgcroc_->digitize(negendID.raw(), fake_pulse, digiToAddNegend)) {
408 hcalDigis.addDigi(posendID.raw(), digiToAddPosend);
409 hcalDigis.addDigi(negendID.raw(), digiToAddNegend);
410 }
411 } else {
412 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAdd;
413 if (hgcroc_->digitize(noiseID, fake_pulse, digiToAdd)) {
414 hcalDigis.addDigi(noiseID, digiToAdd);
415 }
416 }
417 } // loop over noise amplitudes
418 } else { // If zeroSuppression_ == false, add noise digis for all bars
419 // without simhits
420 for (auto digiID : channelMap) {
421 // Convert from digi ID to det ID (since simhits don't know about
422 // different ends of the bar)
423 ldmx::HcalID detid(digiID.section(), digiID.layer(), digiID.strip());
424 unsigned int rawdetID = detid.raw();
425 if (hitsByID.find(rawdetID) == hitsByID.end()) {
426 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
427 hgcroc_->noiseDigi(digiID.raw(), 0.0);
428 hcalDigis.addDigi(digiID.raw(), digi);
429 }
430 }
431 }
432 } // if we should add noise
433
434 event.add(digiCollName_, hcalDigis);
435
436 return;
437} // produce
438
439} // namespace hcal
440
441DECLARE_PRODUCER_NS(hcal, HcalDigiProducer);
#define DECLARE_PRODUCER_NS(NS, 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:36
static const std::string CONDITIONS_OBJECT_NAME
Conditions object name.
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:29
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.
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
bool zeroSuppression_
If false, save digis from all channels, even pure noise in empty bars Helpful when comparing with tes...
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::mt19937 rng_
Generates Gaussian noise on top of real hits.
std::unique_ptr< ldmx::HgcrocEmulator > hgcroc_
Hgcroc Emulator to digitize analog voltage signals.
std::string inputCollName_
input hit collection name
double readoutThreshold_
Read out threshold.
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
int iSOI_
Index for the Sample Of Interest in the list of digi samples.
virtual void onNewRun(const ldmx::RunHeader &runHeader) override
Random number generation.
double pedestal_
Read out pedestal.
double noiseRMS_
Noise RMS.
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.
Contrib getContrib(int i) const
Get a hit contribution by index.
std::vector< float > getPosition() const
Get the XYZ position of the hit [mm].
float time
Time this contributor made the hit (global Geant4 time)
float edep
Energy depostied by this contributor.