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 // If true, ignore readout threshold
39 // and generate pedestal noise digis in every empty channel
40 zeroSuppression_ = ps.getParameter<bool>("zeroSuppression");
41
42 // collection names
43 inputCollName_ = ps.getParameter<std::string>("inputCollName");
44 inputPassName_ = ps.getParameter<std::string>("inputPassName");
45 digiCollName_ = ps.getParameter<std::string>("digiCollName");
46
47 // physical constants
48 // used to calculate unit conversions
49 MeV_ = ps.getParameter<double>("MeV");
50 attlength_ = ps.getParameter<double>("attenuationLength");
51
52 // Time -> clock counts conversion
53 // time [ns] * ( 2^10 / max time in ns ) = clock counts
54 ns_ = 1024. / clockCycle_;
55
56 // Configure generator that will produce noise hits in empty channels
57 double readoutThreshold = ps.getParameter<double>("avgReadoutThreshold");
58 double gain = ps.getParameter<double>("avgGain");
59 double pedestal = ps.getParameter<double>("avgPedestal");
60 // rms noise in mV
61 noiseGenerator_->setNoise(gain * ps.getParameter<double>("avgNoiseRMS"));
62 // mean noise amplitude (if using Gaussian Model for the noise) in mV
63 noiseGenerator_->setPedestal(gain * pedestal);
64 // threshold for readout in mV
65 noiseGenerator_->setNoiseThreshold(gain * readoutThreshold);
66}
67
69 // Handle seeding on the first event
70 if (!noiseGenerator_->hasSeed()) {
71 const auto& rseed = getCondition<framework::RandomNumberSeedService>(
73 noiseGenerator_->seedGenerator(
74 rseed.getSeed("HcalDigiProducer::NoiseGenerator"));
75 }
76 if (noiseInjector_.get() == nullptr) {
77 const auto& rseed = getCondition<framework::RandomNumberSeedService>(
79 noiseInjector_ = std::make_unique<TRandom3>(
80 rseed.getSeed("HcalDigiProducer::NoiseInjector"));
81 }
82 if (!hgcroc_->hasSeed()) {
83 const auto& rseed = getCondition<framework::RandomNumberSeedService>(
85 hgcroc_->seedGenerator(rseed.getSeed("HcalDigiProducer::HgcrocEmulator"));
86 }
87
88 // Get the Hgcroc Conditions
89 hgcroc_->condition(
90 getCondition<conditions::DoubleTableCondition>("HcalHgcrocConditions"));
91
92 // Get the Hcal Geometry
93 const auto& hcalGeometry = getCondition<ldmx::HcalGeometry>(
95
96 // Empty collection to be filled
100
101 std::map<unsigned int, std::vector<const ldmx::SimCalorimeterHit*>> hitsByID;
102
103 // get simulated hcal hits from Geant4 and group them by id
104 auto hcalSimHits{event.getCollection<ldmx::SimCalorimeterHit>(
106
107 for (auto const& simHit : hcalSimHits) {
108 // get ID
109 unsigned int hitID = simHit.getID();
110
111 auto idh = hitsByID.find(hitID);
112 if (idh == hitsByID.end()) {
113 hitsByID[hitID] = std::vector<const ldmx::SimCalorimeterHit*>(1, &simHit);
114 } else {
115 idh->second.push_back(&simHit);
116 }
117 }
118
119 /******************************************************************************************
120 * HGCROC Emulation on Simulated Hits (grouped by HcalID)
121 ******************************************************************************************/
122 for (auto const& simBar : hitsByID) {
123 ldmx::HcalID detID(simBar.first);
124 int section = detID.section();
125 int layer = detID.layer();
126 int strip = detID.strip();
127
128 // get position
129 double half_total_width = hcalGeometry.getHalfTotalWidth(section, layer);
130 double ecal_dx = hcalGeometry.getEcalDx();
131 double ecal_dy = hcalGeometry.getEcalDy();
132
133 // contributions
134 std::vector<std::pair<double, double>> pulses_posend;
135 std::vector<std::pair<double, double>> pulses_negend;
136
137 for (auto psimHit : simBar.second) {
138 const ldmx::SimCalorimeterHit& simHit = *psimHit;
139
140 std::vector<float> position = simHit.getPosition();
141
170 float distance_along_bar, distance_ecal;
171 float distance_close, distance_far;
172 int end_close;
173 const auto orientation{hcalGeometry.getScintillatorOrientation(detID)};
174 if (section == ldmx::HcalID::HcalSection::BACK) {
175 distance_along_bar =
176 (orientation ==
177 ldmx::HcalGeometry::ScintillatorOrientation::horizontal)
178 ? position[0]
179 : position[1];
180 end_close = (distance_along_bar > 0) ? 0 : 1;
181 distance_close = half_total_width;
182 distance_far = half_total_width;
183 } else {
184 if ((section == ldmx::HcalID::HcalSection::TOP) ||
185 ((section == ldmx::HcalID::HcalSection::BOTTOM))) {
186 distance_along_bar = position[0];
187 distance_ecal = ecal_dx;
188 } else if ((section == ldmx::HcalID::HcalSection::LEFT) ||
189 (section == ldmx::HcalID::HcalSection::RIGHT)) {
190 distance_along_bar = position[1];
191 distance_ecal = ecal_dy;
192 } else {
193 distance_along_bar = -9999.;
194 EXCEPTION_RAISE(
195 "BadCode",
196 "We should never end up here "
197 "All cases of HCAL considered, end_close is meaningless");
198 }
199 end_close = (distance_along_bar > half_total_width) ? 0 : 1;
200 distance_close = (end_close == 0)
201 ? 2 * half_total_width - distance_ecal / 2
202 : distance_ecal / 2;
203 distance_far = (end_close == 0)
204 ? distance_ecal / 2
205 : 2 * half_total_width - distance_ecal / 2;
206 }
207
208 // Calculate voltage attenuation and time shift for the close and far
209 // pulse.
210 float v = 299.792 /
211 1.6; // velocity of light in Polystyrene, n = 1.6 = c/v mm/ns
212 double att_close =
213 exp(-1. * ((distance_close - fabs(distance_along_bar)) / 1000.) /
214 attlength_);
215 double att_far =
216 exp(-1. * ((distance_far + fabs(distance_along_bar)) / 1000.) /
217 attlength_);
218 double shift_close =
219 fabs((distance_close - fabs(distance_along_bar)) / v);
220 double shift_far = fabs((distance_far + fabs(distance_along_bar)) / v);
221
222 // Get voltages and times.
223 for (int iContrib = 0; iContrib < simHit.getNumberOfContribs();
224 iContrib++) {
225 double voltage = simHit.getContrib(iContrib).edep * MeV_;
226 double time =
227 simHit.getContrib(iContrib).time; // global time (t=0ns at target)
228 time -= position.at(2) /
229 299.702547; // shift light-speed particle traveling along z
230
231 if (end_close == 0) {
232 pulses_posend.emplace_back(voltage * att_close, time + shift_close);
233 pulses_negend.emplace_back(voltage * att_far, time + shift_far);
234 } else {
235 pulses_posend.emplace_back(voltage * att_far, time + shift_far);
236 pulses_negend.emplace_back(voltage * att_close, time + shift_close);
237 }
238 }
239 }
240
250 if (section == ldmx::HcalID::HcalSection::BACK) {
251 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAddPosend,
252 digiToAddNegend;
253 ldmx::HcalDigiID posendID(section, layer, strip, 0);
254 ldmx::HcalDigiID negendID(section, layer, strip, 1);
255
256 bool posEndActivity =
257 hgcroc_->digitize(posendID.raw(), pulses_posend, digiToAddPosend);
258 bool negEndActivity =
259 hgcroc_->digitize(negendID.raw(), pulses_negend, digiToAddNegend);
260
261 if (posEndActivity && negEndActivity && zeroSuppression_) {
262 hcalDigis.addDigi(posendID.raw(), digiToAddPosend);
263 hcalDigis.addDigi(negendID.raw(), digiToAddNegend);
264 } // If zeroSuppression == true, Back Hcal needs to digitize both
265 // pulses or none
266
267 if (!zeroSuppression_) {
268 if (posEndActivity) {
269 hcalDigis.addDigi(posendID.raw(), digiToAddPosend);
270 } else {
271 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
272 hgcroc_->noiseDigi(posendID.raw(), 0.0);
273 hcalDigis.addDigi(posendID.raw(), digi);
274 }
275 if (negEndActivity) {
276 hcalDigis.addDigi(posendID.raw(), digiToAddPosend);
277 } else {
278 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
279 hgcroc_->noiseDigi(negendID.raw(), 0.0);
280 hcalDigis.addDigi(negendID.raw(), digi);
281 }
282 }
283
284 } else {
285 bool is_posend = false;
286 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAdd;
287 if ((section == ldmx::HcalID::HcalSection::TOP) ||
288 (section == ldmx::HcalID::HcalSection::LEFT)) {
289 is_posend = true;
290 } else if ((section == ldmx::HcalID::HcalSection::BOTTOM) ||
291 (section == ldmx::HcalID::HcalSection::RIGHT)) {
292 is_posend = false;
293 }
294 if (is_posend) {
295 ldmx::HcalDigiID digiID(section, layer, strip, 0);
296 if (hgcroc_->digitize(digiID.raw(), pulses_posend, digiToAdd)) {
297 hcalDigis.addDigi(digiID.raw(), digiToAdd);
298 } else if (!zeroSuppression_) {
299 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
300 hgcroc_->noiseDigi(digiID.raw(), 0.0);
301 hcalDigis.addDigi(digiID.raw(), digi);
302 }
303 } else {
304 ldmx::HcalDigiID digiID(section, layer, strip, 1);
305 if (hgcroc_->digitize(digiID.raw(), pulses_negend, digiToAdd)) {
306 hcalDigis.addDigi(digiID.raw(), digiToAdd);
307 } else if (!zeroSuppression_) {
308 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
309 hgcroc_->noiseDigi(digiID.raw(), 0.0);
310 hcalDigis.addDigi(digiID.raw(), digi);
311 }
312 }
313 }
314 }
315
316 /******************************************************************************************
317 * Noise Simulation on Empty Channels
318 *****************************************************************************************/
319 if (noise_) {
320 std::vector<ldmx::HcalDigiID> channelMap;
321 int numChannels = 0;
322 for (int section = 0; section < hcalGeometry.getNumSections(); section++) {
323 int numChannelsInSection = 0;
324 for (int layer = 1; layer <= hcalGeometry.getNumLayers(section);
325 layer++) {
326 numChannelsInSection += hcalGeometry.getNumStrips(section, layer);
327 // Note zero-indexed strip numbering...
328 for (int strip = 0; strip < hcalGeometry.getNumStrips(section, layer);
329 strip++) {
330 if (section == ldmx::HcalID::HcalSection::BACK) {
331 auto digiIDend0 = ldmx::HcalDigiID(section, layer, strip, 0);
332 auto digiIDend1 = ldmx::HcalDigiID(section, layer, strip, 1);
333 channelMap.push_back(digiIDend0);
334 channelMap.push_back(digiIDend1);
335 numChannels += 2;
336 } else {
337 auto digiID = ldmx::HcalDigiID(section, layer, strip, 0);
338 channelMap.push_back(digiID);
339 numChannels++;
340 }
341 }
342 }
343 }
344
345 if (zeroSuppression_) { // Fast noise sim
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 for (double noiseHit : noiseHitAmplitudes) {
353 // generate detector ID for noise hit
354 // making sure that it is in an empty channel
355 unsigned int noiseID;
356 int sectionID, layerID, stripID, endID;
357 do {
358 sectionID = noiseInjector_->Integer(hcalGeometry.getNumSections());
359 layerID =
360 noiseInjector_->Integer(hcalGeometry.getNumLayers(sectionID));
361 // set layer to 1 if the generator says it is 0 (geometry map starts
362 // from 1)
363 if (layerID == 0) layerID = 1;
364 stripID = noiseInjector_->Integer(
365 hcalGeometry.getNumStrips(sectionID, layerID));
366 endID = noiseInjector_->Integer(2);
367 if ((sectionID == ldmx::HcalID::HcalSection::TOP) ||
368 (sectionID == ldmx::HcalID::HcalSection::LEFT)) {
369 endID = 0;
370 } else if ((sectionID == ldmx::HcalID::HcalSection::BOTTOM) ||
371 (sectionID == ldmx::HcalID::HcalSection::RIGHT)) {
372 endID = 1;
373 }
374 auto detID = ldmx::HcalDigiID(sectionID, layerID, stripID, endID);
375 noiseID = detID.raw();
376 } while (hitsByID.find(noiseID) != hitsByID.end());
377 hitsByID[noiseID] =
378 std::vector<const ldmx::SimCalorimeterHit*>(); // mark this as used
379
380 // get a time for this noise hit
381 fake_pulse[0].second = noiseInjector_->Uniform(clockCycle_);
382
383 // noise generator gives the amplitude above the readout threshold
384 // we need to convert it to the amplitude above the pedestal
385 double gain = hgcroc_->gain(noiseID);
386 fake_pulse[0].first = noiseHit +
387 gain * hgcroc_->readoutThreshold(noiseID) -
388 gain * hgcroc_->pedestal(noiseID);
389
390 if (sectionID == ldmx::HcalID::HcalSection::BACK) {
391 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAddPosend,
392 digiToAddNegend;
393 ldmx::HcalDigiID posendID(sectionID, layerID, stripID, 0);
394 ldmx::HcalDigiID negendID(sectionID, layerID, stripID, 1);
395 if (hgcroc_->digitize(posendID.raw(), fake_pulse, digiToAddPosend) &&
396 hgcroc_->digitize(negendID.raw(), fake_pulse, digiToAddNegend)) {
397 hcalDigis.addDigi(posendID.raw(), digiToAddPosend);
398 hcalDigis.addDigi(negendID.raw(), digiToAddNegend);
399 }
400 } else {
401 std::vector<ldmx::HgcrocDigiCollection::Sample> digiToAdd;
402 if (hgcroc_->digitize(noiseID, fake_pulse, digiToAdd)) {
403 hcalDigis.addDigi(noiseID, digiToAdd);
404 }
405 }
406 } // loop over noise amplitudes
407 } else { // If zeroSuppression_ == false, add noise digis for all bars
408 // without simhits
409 for (auto digiID : channelMap) {
410 // Convert from digi ID to det ID (since simhits don't know about
411 // different ends of the bar)
412 ldmx::HcalID detid(digiID.section(), digiID.layer(), digiID.strip());
413 unsigned int rawdetID = detid.raw();
414 if (hitsByID.find(rawdetID) == hitsByID.end()) {
415 std::vector<ldmx::HgcrocDigiCollection::Sample> digi =
416 hgcroc_->noiseDigi(digiID.raw(), 0.0);
417 hcalDigis.addDigi(digiID.raw(), digi);
418 }
419 }
420 }
421 } // if we should add noise
422
423 event.add(digiCollName_, hcalDigis);
424
425 return;
426} // produce
427
428} // namespace hcal
429
430DECLARE_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
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::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.