View Javadoc
1   package org.hps.readout.ecal;
2   
3   import static org.hps.recon.ecal.EcalUtils.ecalReadoutPeriod;
4   import static org.hps.recon.ecal.EcalUtils.fallTime;
5   import static org.hps.recon.ecal.EcalUtils.maxVolt;
6   import static org.hps.recon.ecal.EcalUtils.nBit;
7   import static org.hps.recon.ecal.EcalUtils.readoutGain;
8   import static org.hps.recon.ecal.EcalUtils.riseTime;
9   
10  import java.util.ArrayList;
11  import java.util.Comparator;
12  import java.util.HashMap;
13  import java.util.LinkedList;
14  import java.util.List;
15  import java.util.Map;
16  import java.util.PriorityQueue;
17  import java.util.Set;
18  
19  import org.hps.conditions.database.DatabaseConditionsManager;
20  import org.hps.conditions.ecal.EcalChannelConstants;
21  import org.hps.conditions.ecal.EcalConditions;
22  import org.hps.recon.ecal.EcalUtils;
23  import org.hps.util.RandomGaussian;
24  import org.lcsim.event.CalorimeterHit;
25  import org.lcsim.event.EventHeader;
26  import org.lcsim.event.RawCalorimeterHit;
27  import org.lcsim.event.RawTrackerHit;
28  import org.lcsim.event.base.BaseRawCalorimeterHit;
29  import org.lcsim.event.base.BaseRawTrackerHit;
30  import org.lcsim.geometry.Detector;
31  import org.lcsim.geometry.Subdetector;
32  import org.lcsim.geometry.subdetector.HPSEcal3;
33  import org.lcsim.lcio.LCIOConstants;
34  
35  /**
36   * Performs readout of ECal hits. Simulates time evolution of preamp output pulse.
37   *
38   * @author Sho Uemura <meeg@slac.stanford.edu>
39   * @version $Id: FADCEcalReadoutDriver.java,v 1.4 2013/10/31 00:11:02 meeg Exp $
40   */
41  public class FADCEcalReadoutDriver extends EcalReadoutDriver<RawCalorimeterHit> {
42  
43      // Repeated here from EventConstants in evio module to avoid depending on it.
44      private static final int ECAL_RAW_MODE = 1;
45      private static final int ECAL_PULSE_MODE = 2;
46      private static final int ECAL_PULSE_INTEGRAL_MODE = 3;
47      private String ecalName = "Ecal";
48      private Subdetector ecal;
49      private EcalConditions ecalConditions = null;
50      // buffer for preamp signals (units of volts, no pedestal)
51      private Map<Long, RingBuffer> analogPipelines = null;
52      // ADC pipeline for readout (units of ADC counts)
53      private Map<Long, FADCPipeline> digitalPipelines = null;
54      // buffer for window sums
55      private Map<Long, Integer> triggerPathHitSums = null;
56      // buffer for timestamps
57      private Map<Long, Integer> triggerPathHitTimes = null;
58      // queue for hits to be output to clusterer
59      private PriorityQueue<RawCalorimeterHit> triggerPathDelayQueue = null;
60      // output buffer for hits
61      private LinkedList<RawCalorimeterHit> triggerPathCoincidenceQueue = new LinkedList<RawCalorimeterHit>();
62      private int bufferLength = 100;
63      private int pipelineLength = 2000;
64      private double tp = 9.6;
65      private int delay0 = 32;
66      private int readoutLatency = 100;
67      private int readoutWindow = 100;
68      private int numSamplesBefore = 5;
69      private int numSamplesAfter = 25;
70      private int coincidenceWindow = 2;
71      private String ecalReadoutCollectionName = "EcalReadoutHits";
72      private int mode = ECAL_PULSE_INTEGRAL_MODE;
73      private int readoutThreshold = 10;
74      private int triggerThreshold = 10;
75      private double scaleFactor = 1;
76      private double fixedGain = -1;
77      private boolean constantTriggerWindow = true;
78      private boolean addNoise = true;
79      private double pePerMeV = 32.8;
80      private boolean use2014Gain = false;
81      private PulseShape pulseShape = PulseShape.ThreePole;
82  
83      public enum PulseShape {
84  
85          CRRC, DoubleGaussian, ThreePole
86      }
87  
88      public FADCEcalReadoutDriver() {
89          flags = 0;
90          flags += 1 << LCIOConstants.RCHBIT_TIME; // store timestamp
91          hitClass = RawCalorimeterHit.class;
92          setReadoutPeriod(ecalReadoutPeriod);
93          // converter = new HPSEcalConverter(null);
94      }
95  
96      /**
97       * Add noise (photoelectron statistics and readout/preamp noise) to hits before adding them to the analog pipeline.
98       *
99       * @param addNoise True to add noise, default of false.
100      */
101     public void setAddNoise(boolean addNoise) {
102         this.addNoise = addNoise;
103     }
104 
105     /**
106      * Sets the trigger-path hit processing algorithm.
107      *
108      * @param constantTriggerWindow True for 2014+ FADC behavior, false for test run behavior. True by default.
109      */
110     public void setConstantTriggerWindow(boolean constantTriggerWindow) {
111         this.constantTriggerWindow = constantTriggerWindow;
112     }
113 
114     /**
115      * Override the ECal gains set in the conditions system with a single uniform value.
116      *
117      * @param fixedGain Units of MeV/(ADC counts in pulse integral). Negative value causes the conditions system to be
118      *            used for gains. Default is negative.
119      */
120     public void setFixedGain(double fixedGain) {
121         this.fixedGain = fixedGain;
122     }
123 
124     public void setEcalName(String ecalName) {
125         this.ecalName = ecalName;
126     }
127 
128     /**
129      * Threshold for readout-path hits. For 2014+ running this should always equal the trigger threshold.
130      *
131      * @param readoutThreshold Units of ADC counts, default of 10.
132      */
133     public void setReadoutThreshold(int readoutThreshold) {
134         this.readoutThreshold = readoutThreshold;
135     }
136 
137     /**
138      * Scale factor for trigger-path hit amplitudes. Only used for test run.
139      *
140      * @param scaleFactor Default of 1.
141      */
142     public void setScaleFactor(double scaleFactor) {
143         this.scaleFactor = scaleFactor;
144     }
145 
146     /**
147      * Threshold for trigger-path hits. For 2014+ running this should always equal the readout threshold.
148      *
149      * @param triggerThreshold Units of ADC counts, default of 10.
150      */
151     public void setTriggerThreshold(int triggerThreshold) {
152         this.triggerThreshold = triggerThreshold;
153     }
154 
155     /**
156      * Output collection name for readout-path hits.
157      *
158      * @param ecalReadoutCollectionName
159      */
160     public void setEcalReadoutCollectionName(String ecalReadoutCollectionName) {
161         this.ecalReadoutCollectionName = ecalReadoutCollectionName;
162     }
163 
164     /**
165      * Number of ADC samples to process after each rising threshold crossing. In FADC documentation,
166      * "number of samples after" or NSA.
167      *
168      * @param numSamplesAfter Units of 4 ns FADC clock cycles, default of 30.
169      */
170     public void setNumSamplesAfter(int numSamplesAfter) {
171         this.numSamplesAfter = numSamplesAfter;
172     }
173 
174     /**
175      * Number of ADC samples to process before each rising threshold crossing. In FADC documentation,
176      * "number of samples before" or NSB.
177      *
178      * @param numSamplesBefore Units of 4 ns FADC clock cycles, default of 5.
179      */
180     public void setNumSamplesBefore(int numSamplesBefore) {
181         this.numSamplesBefore = numSamplesBefore;
182     }
183 
184     /**
185      * Start of readout window relative to trigger time (in readout cycles). In FADC documentation,
186      * "Programmable Latency" or PL.
187      *
188      * @param readoutLatency Units of 4 ns FADC clock cycles, default of 100.
189      */
190     public void setReadoutLatency(int readoutLatency) {
191         this.readoutLatency = readoutLatency;
192     }
193 
194     /**
195      * Number of ADC samples to read out. In FADC documentation, "Programmable Trigger Window" or PTW.
196      *
197      * @param readoutWindow Units of 4 ns FADC clock cycles, default of 100.
198      */
199     public void setReadoutWindow(int readoutWindow) {
200         this.readoutWindow = readoutWindow;
201     }
202 
203     /**
204      * Number of clock cycles for which the same trigger-path hit is sent to the clusterer. Only used for old clusterer
205      * simulations (CTPClusterDriver). Otherwise this should be set to 1.
206      *
207      * @param coincidenceWindow Units of 4 ns FADC clock cycles, default of 1.
208      */
209     public void setCoincidenceWindow(int coincidenceWindow) {
210         this.coincidenceWindow = coincidenceWindow;
211     }
212 
213     /**
214      * Switch between test run and 2014 definitions of gain constants. True for MC studies and mock data in 2014. For
215      * all real data (test run and 2014+), test run MC, and 2015+ production MC, this should be false.
216      *
217      * @param use2014Gain True ONLY for simulation studies in 2014. Default of false.
218      */
219     public void setUse2014Gain(boolean use2014Gain) {
220         this.use2014Gain = use2014Gain;
221     }
222 
223     /**
224      * Model used for the preamp pulse shape.
225      *
226      * @param pulseShape ThreePole, DoubleGaussian, or CRRC. Default is ThreePole.
227      */
228     public void setPulseShape(String pulseShape) {
229         this.pulseShape = PulseShape.valueOf(pulseShape);
230     }
231 
232     /**
233      * Shaper time constant. Definition depends on the pulse shape. For the three-pole function, this is equal to RC, or
234      * half the peaking time.
235      *
236      * @param tp Units of ns, default of 9.6.
237      */
238     public void setTp(double tp) {
239         this.tp = tp;
240     }
241 
242     /**
243      * Photoelectrons per MeV, used to calculate noise due to photoelectron statistics. Test run detector had a value of
244      * 2 photoelectrons/MeV; new 2014 detector has a value of 32.8 photoelectrons/MeV.
245      *
246      * @param pePerMeV Units of photoelectrons/MeV, default of 32.8.
247      */
248     public void setPePerMeV(double pePerMeV) {
249         this.pePerMeV = pePerMeV;
250     }
251 
252     /**
253      * Latency between threshold crossing and output of trigger-path hit to clusterer.
254      *
255      * @param delay0 Units of 4 ns FADC clock cycles, default of 32.
256      */
257     public void setDelay0(int delay0) {
258         this.delay0 = delay0;
259     }
260 
261     /**
262      * Length of analog pipeline.
263      *
264      * @param bufferLength Units of 4 ns FADC clock cycles, default of 100.
265      */
266     public void setBufferLength(int bufferLength) {
267         this.bufferLength = bufferLength;
268         resetFADCBuffers();
269     }
270 
271     /**
272      * Length of digital pipeline. The digital pipeline in the FADC is 2000 cells long.
273      *
274      * @param pipelineLength Units of 4 ns FADC clock cycles, default of 2000.
275      */
276     public void setPipelineLength(int pipelineLength) {
277         this.pipelineLength = pipelineLength;
278         resetFADCBuffers();
279     }
280 
281     /**
282      * Mode for readout-path hits.
283      *
284      * @param mode 1, 2 or 3. Values correspond to the standard FADC mode numbers (1=raw, 2=pulse, 3=pulse integral).
285      */
286     public void setMode(int mode) {
287         this.mode = mode;
288         if (mode != ECAL_RAW_MODE && mode != ECAL_PULSE_MODE && mode != ECAL_PULSE_INTEGRAL_MODE) {
289             throw new IllegalArgumentException("invalid mode " + mode);
290         }
291     }
292 
293     /**
294      * Return the map of preamp signal buffers. For debug only.
295      *
296      * @return the map of preamp signal buffers
297      */
298     public Map<Long, RingBuffer> getSignalMap() {
299         return analogPipelines;
300     }
301 
302     /**
303      * Return the map of FADC pipelines. For debug only.
304      *
305      * @return the map of FADC pipelines
306      */
307     public Map<Long, FADCPipeline> getPipelineMap() {
308         return digitalPipelines;
309     }
310 
311     /**
312      * Digitize values in the analog pipelines and append them to the digital pipelines. Integrate trigger-path hits and
313      * add them to the trigger path queues. Read out trigger-path hits to the list sent to the clusterer.
314      *
315      * @param hits List to be filled by this method.
316      */
317     @Override
318     protected void readHits(List<RawCalorimeterHit> hits) {
319 
320         for (Long cellID : analogPipelines.keySet()) {
321             RingBuffer signalBuffer = analogPipelines.get(cellID);
322 
323             FADCPipeline pipeline = digitalPipelines.get(cellID);
324             pipeline.step();
325 
326             // Get the channel data.
327             EcalChannelConstants channelData = findChannel(cellID);
328 
329             double currentValue = signalBuffer.currentValue() * ((Math.pow(2, nBit) - 1) / maxVolt); // 12-bit ADC with
330                                                                                                      // maxVolt V range
331             int pedestal = (int) Math.round(channelData.getCalibration().getPedestal());
332             
333             // ADC can't return a value larger than 4095; 4096 (overflow) is returned for any input >2V
334             int digitizedValue = Math.min((int) Math.round(pedestal + currentValue), (int) Math.pow(2, nBit));
335             
336             pipeline.writeValue(digitizedValue);
337             int pedestalSubtractedValue = digitizedValue - pedestal;
338             // System.out.println(signalBuffer.currentValue() + "   " + currentValue + "   " + pipeline.currentValue());
339 
340             Integer sum = triggerPathHitSums.get(cellID);
341             if (sum == null && pedestalSubtractedValue > triggerThreshold) {
342                 triggerPathHitTimes.put(cellID, readoutCounter);
343                 if (constantTriggerWindow) {
344                     int sumBefore = 0;
345                     for (int i = 0; i < numSamplesBefore; i++) {
346                         if (debug) {
347                             System.out.format("trigger %d, %d: %d\n", cellID, i,
348                                     pipeline.getValue(numSamplesBefore - i - 1));
349                         }
350                         sumBefore += pipeline.getValue(numSamplesBefore - i - 1);
351                     }
352                     triggerPathHitSums.put(cellID, sumBefore);
353                 } else {
354                     triggerPathHitSums.put(cellID, pedestalSubtractedValue);
355                 }
356             }
357             if (sum != null) {
358                 if (constantTriggerWindow) {
359                     if (triggerPathHitTimes.get(cellID) + numSamplesAfter >= readoutCounter) {
360                         if (debug) {
361                             System.out.format("trigger %d, %d: %d\n", cellID,
362                                     readoutCounter - triggerPathHitTimes.get(cellID) + numSamplesBefore - 1,
363                                     pipeline.getValue(0));
364                         }
365                         triggerPathHitSums.put(cellID, sum + pipeline.getValue(0));
366                     } else if (triggerPathHitTimes.get(cellID) + delay0 <= readoutCounter) {
367                         // System.out.printf("sum = %f\n", sum);
368                         triggerPathDelayQueue.add(new BaseRawCalorimeterHit(cellID,
369                                 (int) Math.round(sum / scaleFactor), 64 * triggerPathHitTimes.get(cellID)));
370                         triggerPathHitSums.remove(cellID);
371                     }
372                 } else {
373                     if (pedestalSubtractedValue < triggerThreshold
374                             || triggerPathHitTimes.get(cellID) + delay0 == readoutCounter) {
375                         // System.out.printf("sum = %f\n",sum);
376                         triggerPathDelayQueue.add(new BaseRawCalorimeterHit(cellID, (int) Math
377                                 .round((sum + pedestalSubtractedValue) / scaleFactor), 64 * triggerPathHitTimes
378                                 .get(cellID)));
379                         triggerPathHitSums.remove(cellID);
380                     } else {
381                         triggerPathHitSums.put(cellID, sum + pedestalSubtractedValue);
382                     }
383                 }
384             }
385             signalBuffer.step();
386         }
387         while (triggerPathDelayQueue.peek() != null
388                 && triggerPathDelayQueue.peek().getTimeStamp() / 64 <= readoutCounter - delay0) {
389             if (triggerPathDelayQueue.peek().getTimeStamp() / 64 < readoutCounter - delay0) {
390                 System.out.println(this.getName() + ": Stale hit in output queue");
391                 triggerPathDelayQueue.poll();
392             } else {
393                 triggerPathCoincidenceQueue.add(triggerPathDelayQueue.poll());
394             }
395         }
396         while (!triggerPathCoincidenceQueue.isEmpty()
397                 && triggerPathCoincidenceQueue.peek().getTimeStamp() / 64 <= readoutCounter - delay0
398                         - coincidenceWindow) {
399             triggerPathCoincidenceQueue.remove();
400         }
401         if (debug) {
402             for (RawCalorimeterHit hit : triggerPathCoincidenceQueue) {
403                 System.out.format("new hit: energy %d\n", hit.getAmplitude());
404             }
405         }
406 
407         hits.addAll(triggerPathCoincidenceQueue);
408     }
409 
410     @Override
411     public void startOfData() {
412         super.startOfData();
413         if (ecalReadoutCollectionName == null) {
414             throw new RuntimeException("The parameter ecalReadoutCollectionName was not set!");
415         }
416     }
417 
418     @Override
419     protected void processTrigger(EventHeader event) {
420         switch (mode) {
421             case ECAL_RAW_MODE:
422                 if (debug) {
423                     System.out.println("Reading out ECal in raw mode");
424                 }
425                 event.put(ecalReadoutCollectionName, readWindow(), RawTrackerHit.class, 0, ecalReadoutName);
426                 break;
427             case ECAL_PULSE_MODE:
428                 if (debug) {
429                     System.out.println("Reading out ECal in pulse mode");
430                 }
431                 event.put(ecalReadoutCollectionName, readPulses(), RawTrackerHit.class, 0, ecalReadoutName);
432                 break;
433             case ECAL_PULSE_INTEGRAL_MODE:
434                 if (debug) {
435                     System.out.println("Reading out ECal in integral mode");
436                 }
437                 event.put(ecalReadoutCollectionName, readIntegrals(), RawCalorimeterHit.class, flags, ecalReadoutName);
438                 break;
439         }
440     }
441 
442     @Override
443     public double readoutDeltaT() {
444         double triggerTime = ClockSingleton.getTime() + triggerDelay;
445         int cycle = (int) Math.floor((triggerTime - readoutOffset + ClockSingleton.getDt()) / readoutPeriod);
446         double readoutTime = (cycle - readoutLatency) * readoutPeriod + readoutOffset - ClockSingleton.getDt();
447         return readoutTime;
448     }
449 
450     protected short[] getWindow(long cellID) {
451         FADCPipeline pipeline = digitalPipelines.get(cellID);
452         short[] adcValues = new short[readoutWindow];
453         for (int i = 0; i < readoutWindow; i++) {
454             adcValues[i] = (short) pipeline.getValue(readoutLatency - i - 1);
455             // if (adcValues[i] != 0) {
456             // System.out.println("getWindow: " + adcValues[i] + " at i = " + i);
457             // }
458         }
459         return adcValues;
460     }
461 
462     protected List<RawTrackerHit> readWindow() {
463         // System.out.println("Reading FADC data");
464         List<RawTrackerHit> hits = new ArrayList<RawTrackerHit>();
465         for (Long cellID : digitalPipelines.keySet()) {
466             short[] adcValues = getWindow(cellID);
467             EcalChannelConstants channelData = findChannel(cellID);
468             boolean isAboveThreshold = false;
469             for (int i = 0; i < adcValues.length; i++) {
470                 if (adcValues[i] > channelData.getCalibration().getPedestal() + readoutThreshold) {
471                     isAboveThreshold = true;
472                     break;
473                 }
474             }
475             if (isAboveThreshold) {
476                 hits.add(new BaseRawTrackerHit(cellID, 0, adcValues));
477             }
478         }
479         return hits;
480     }
481 
482     protected List<RawTrackerHit> readPulses() {
483         // System.out.println("Reading FADC data");
484         List<RawTrackerHit> hits = new ArrayList<RawTrackerHit>();
485         for (Long cellID : digitalPipelines.keySet()) {
486             short[] window = getWindow(cellID);
487             short[] adcValues = null;
488             int pointerOffset = 0;
489             int numSamplesToRead = 0;
490             int thresholdCrossing = 0;
491 
492             // Get the channel data.
493             EcalChannelConstants channelData = findChannel(cellID);
494 
495             for (int i = 0; i < readoutWindow; i++) {
496                 if (numSamplesToRead != 0) {
497                     if (adcValues == null) {
498                         throw new RuntimeException("expected a pulse buffer, none found (this should never happen)");
499                     }
500                     adcValues[adcValues.length - numSamplesToRead] = window[i - pointerOffset];
501                     numSamplesToRead--;
502                     if (numSamplesToRead == 0) {
503                         hits.add(new BaseRawTrackerHit(cellID, thresholdCrossing, adcValues));
504                     }
505                 } else if ((i == 0 || window[i - 1] <= channelData.getCalibration().getPedestal() + readoutThreshold)
506                         && window[i] > channelData.getCalibration().getPedestal() + readoutThreshold) {
507                     thresholdCrossing = i;
508                     pointerOffset = Math.min(numSamplesBefore, i);
509                     numSamplesToRead = pointerOffset + Math.min(numSamplesAfter, readoutWindow - i - pointerOffset - 1);
510                     adcValues = new short[numSamplesToRead];
511                 }
512             }
513         }
514         return hits;
515     }
516 
517     protected List<RawCalorimeterHit> readIntegrals() {
518         // System.out.println("Reading FADC data");
519         List<RawCalorimeterHit> hits = new ArrayList<RawCalorimeterHit>();
520         for (Long cellID : digitalPipelines.keySet()) {
521             short[] window = getWindow(cellID);
522             int adcSum = 0;
523             int pointerOffset = 0;
524             int numSamplesToRead = 0;
525             int thresholdCrossing = 0;
526 
527             // Get the channel data.
528             EcalChannelConstants channelData = findChannel(cellID);
529 
530             if (window != null) {
531                 for (int i = 0; i < readoutWindow; i++) {
532                     if (numSamplesToRead != 0) {
533                         if (debug) {
534                             System.out.format("readout %d, %d: %d\n", cellID, numSamplesBefore + numSamplesAfter
535                                     - numSamplesToRead, window[i - pointerOffset]);
536                         }
537                         adcSum += window[i - pointerOffset];
538                         numSamplesToRead--;
539                         if (numSamplesToRead == 0) {
540                             hits.add(new BaseRawCalorimeterHit(cellID, adcSum, 64 * thresholdCrossing));
541                         }
542                     } else if ((i == 0 || window[i - 1] <= channelData.getCalibration().getPedestal()
543                             + readoutThreshold)
544                             && window[i] > channelData.getCalibration().getPedestal() + readoutThreshold) {
545                         thresholdCrossing = i;
546                         pointerOffset = Math.min(numSamplesBefore, i);
547                         numSamplesToRead = pointerOffset
548                                 + Math.min(numSamplesAfter, readoutWindow - i - pointerOffset - 1);
549                         adcSum = 0;
550                     }
551                 }
552             }
553         }
554         return hits;
555     }
556 
557     /**
558      * Fill the analog pipelines with the preamp pulses generated by hits in the ECal.
559      *
560      * @param hits ECal hits from SLIC/Geant4.
561      */
562     @Override
563     protected void putHits(List<CalorimeterHit> hits) {
564         // fill the readout buffers
565         for (CalorimeterHit hit : hits) {
566             RingBuffer eDepBuffer = analogPipelines.get(hit.getCellID());
567             double energyAmplitude = hit.getRawEnergy();
568             // Get the channel data.
569             EcalChannelConstants channelData = findChannel(hit.getCellID());
570             if (addNoise) {
571                 // add preamp noise and photoelectron Poisson noise in quadrature
572                 double noise;
573                 if (use2014Gain) {
574                     noise = Math.sqrt(Math.pow(channelData.getCalibration().getNoise()
575                             * channelData.getGain().getGain() * EcalUtils.gainFactor * EcalUtils.ecalReadoutPeriod, 2)
576                             + hit.getRawEnergy() / (EcalUtils.lightYield * EcalUtils.quantumEff * EcalUtils.surfRatio));
577                 } else {
578                     noise = Math.sqrt(Math.pow(channelData.getCalibration().getNoise()
579                             * channelData.getGain().getGain() * EcalUtils.MeV, 2)
580                             + hit.getRawEnergy() * EcalUtils.MeV / pePerMeV);
581                 }
582                 energyAmplitude += RandomGaussian.getGaussian(0, noise);
583             }
584             if ((1) * readoutPeriod + readoutTime() - (ClockSingleton.getTime() + hit.getTime()) >= readoutPeriod) {
585                 throw new RuntimeException("trying to add a hit to the analog pipeline, but time seems incorrect");
586             }
587             for (int i = 0; i < bufferLength; i++) {
588                 // eDepBuffer.addToCell(i, energyAmplitude * pulseAmplitude((i + 1) * readoutPeriod + readoutTime() -
589                 // (ClockSingleton.getTime() + hit.getTime()), hit.getCellID()));
590                 eDepBuffer.addToCell(
591                         i,
592                         energyAmplitude
593                                 * pulseAmplitude((i + 1) * readoutPeriod + readoutTime()
594                                         - (ClockSingleton.getTime() + hit.getTime())
595                                         - findChannel(hit.getCellID()).getTimeShift().getTimeShift(), hit.getCellID()));
596 
597             }
598         }
599     }
600 
601     @Override
602     protected void initReadout() {
603         // initialize buffers
604         triggerPathHitSums = new HashMap<Long, Integer>();
605         triggerPathHitTimes = new HashMap<Long, Integer>();
606         triggerPathDelayQueue = new PriorityQueue(20, new TimeComparator());
607         resetFADCBuffers();
608     }
609 
610     @Override
611     public void detectorChanged(Detector detector) {
612         // Get the Subdetector.
613         ecal = detector.getSubdetector(ecalName);
614 
615         // ECAL combined conditions object.
616         ecalConditions = DatabaseConditionsManager.getInstance().getEcalConditions();
617 
618         resetFADCBuffers();
619     }
620 
621     private boolean resetFADCBuffers() {
622         if (ecal == null) {
623             return false;
624         }
625         analogPipelines = new HashMap<Long, RingBuffer>();
626         digitalPipelines = new HashMap<Long, FADCPipeline>();
627         Set<Long> cells = ((HPSEcal3) ecal).getNeighborMap().keySet();
628         for (Long cellID : cells) {
629             EcalChannelConstants channelData = findChannel(cellID);
630             analogPipelines.put(cellID, new RingBuffer(bufferLength));
631             digitalPipelines.put(cellID,
632                     new FADCPipeline(pipelineLength, (int) Math.round(channelData.getCalibration().getPedestal())));
633         }
634         return true;
635     }
636 
637     /**
638      * Returns pulse amplitude at the given time (relative to hit time). Gain is applied.
639      *
640      * @param time Units of ns. Relative to hit time (negative=before the start of the pulse).
641      * @param cellID Crystal ID as returned by hit.getCellID().
642      * @return Amplitude, units of volts/GeV.
643      */
644     private double pulseAmplitude(double time, long cellID) {
645         // Get the channel data.
646         EcalChannelConstants channelData = findChannel(cellID);
647 
648         if (use2014Gain) {
649             // if fixedGain is set, multiply the default gain by this factor
650             double corrGain;
651             if (fixedGain > 0) {
652                 corrGain = fixedGain;
653             } else {
654                 corrGain = 1.0 / channelData.getGain().getGain();
655             }
656 
657             return corrGain * readoutGain * pulseAmplitude(time, pulseShape, tp);
658         } else {
659             // normalization constant from cal gain (MeV/integral bit) to amplitude gain (amplitude bit/GeV)
660             double gain;
661             if (fixedGain > 0) {
662                 gain = readoutPeriod / (fixedGain * EcalUtils.MeV * ((Math.pow(2, nBit) - 1) / maxVolt));
663             } else {
664                 gain = readoutPeriod
665                         / (channelData.getGain().getGain() * EcalUtils.MeV * ((Math.pow(2, nBit) - 1) / maxVolt));
666             }
667 
668             return gain * pulseAmplitude(time, pulseShape, tp);
669         }
670     }
671 
672     /**
673      * Returns pulse amplitude at the given time (relative to hit time).
674      *
675      * @param time Units of ns. Relative to hit time (negative=before the start of the pulse).
676      * @return Amplitude, units of inverse ns. Normalized so the pulse integral is 1.
677      */
678     public static double pulseAmplitude(double time, PulseShape shape, double shapingTime) {
679         if (time <= 0.0) {
680             return 0.0;
681         }
682         switch (shape) {
683             case CRRC:
684                 // peak at tp
685                 // peak value 1/(tp*e)
686                 return ((time / (shapingTime * shapingTime)) * Math.exp(-time / shapingTime));
687             case DoubleGaussian:
688                 // According to measurements the output signal can be fitted by two gaussians, one for the rise of the
689                 // signal, one for the fall
690                 // peak at 3*riseTime
691                 // peak value 1/norm
692 
693                 double norm = ((riseTime + fallTime) / 2) * Math.sqrt(2 * Math.PI); // to ensure the total integral is
694                                                                                     // equal to 1: = 33.8
695                 return funcGaus(time - 3 * riseTime, (time < 3 * riseTime) ? riseTime : fallTime) / norm;
696             case ThreePole:
697                 // peak at 2*tp
698                 // peak value 2/(tp*e^2)
699                 return ((time * time / (2 * shapingTime * shapingTime * shapingTime)) * Math.exp(-time / shapingTime));
700             default:
701                 return 0.0;
702         }
703     }
704 
705     // Gaussian function needed for the calculation of the pulse shape amplitude
706     public static double funcGaus(double t, double sig) {
707         return Math.exp(-t * t / (2 * sig * sig));
708     }
709 
710     public class FADCPipeline {
711 
712         private final int[] array;
713         private final int size;
714         private int ptr;
715 
716         public FADCPipeline(int size) {
717             this.size = size;
718             array = new int[size]; // initialized to 0
719             ptr = 0;
720         }
721 
722         // construct pipeline with a nonzero initial value
723         public FADCPipeline(int size, int init) {
724             this.size = size;
725             array = new int[size];
726             for (int i = 0; i < size; i++) {
727                 array[i] = init;
728             }
729             ptr = 0;
730         }
731 
732         /**
733          * Write value to current cell
734          */
735         public void writeValue(int val) {
736             array[ptr] = val;
737         }
738 
739         /**
740          * Write value to current cell
741          */
742         public void step() {
743             ptr++;
744             if (ptr == size) {
745                 ptr = 0;
746             }
747         }
748 
749         // return content of specified cell (pos=0 for current cell)
750         public int getValue(int pos) {
751             if (pos >= size || pos < 0) {
752                 throw new ArrayIndexOutOfBoundsException();
753             }
754             return array[((ptr - pos) % size + size) % size];
755         }
756     }
757 
758     /**
759      * Convert physical ID to gain value.
760      *
761      * @param cellID (long)
762      * @return channel constants (EcalChannelConstants)
763      */
764     private EcalChannelConstants findChannel(long cellID) {
765         return ecalConditions.getChannelConstants(ecalConditions.getChannelCollection().findGeometric(cellID));
766     }
767 
768     public static class TimeComparator implements Comparator<RawCalorimeterHit> {
769 
770         @Override
771         public int compare(RawCalorimeterHit o1, RawCalorimeterHit o2) {
772             return o1.getTimeStamp() - o2.getTimeStamp();
773         }
774     }
775 }