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
37
38
39
40
41 public class FADCEcalReadoutDriver extends EcalReadoutDriver<RawCalorimeterHit> {
42
43
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
51 private Map<Long, RingBuffer> analogPipelines = null;
52
53 private Map<Long, FADCPipeline> digitalPipelines = null;
54
55 private Map<Long, Integer> triggerPathHitSums = null;
56
57 private Map<Long, Integer> triggerPathHitTimes = null;
58
59 private PriorityQueue<RawCalorimeterHit> triggerPathDelayQueue = null;
60
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;
91 hitClass = RawCalorimeterHit.class;
92 setReadoutPeriod(ecalReadoutPeriod);
93
94 }
95
96
97
98
99
100
101 public void setAddNoise(boolean addNoise) {
102 this.addNoise = addNoise;
103 }
104
105
106
107
108
109
110 public void setConstantTriggerWindow(boolean constantTriggerWindow) {
111 this.constantTriggerWindow = constantTriggerWindow;
112 }
113
114
115
116
117
118
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
130
131
132
133 public void setReadoutThreshold(int readoutThreshold) {
134 this.readoutThreshold = readoutThreshold;
135 }
136
137
138
139
140
141
142 public void setScaleFactor(double scaleFactor) {
143 this.scaleFactor = scaleFactor;
144 }
145
146
147
148
149
150
151 public void setTriggerThreshold(int triggerThreshold) {
152 this.triggerThreshold = triggerThreshold;
153 }
154
155
156
157
158
159
160 public void setEcalReadoutCollectionName(String ecalReadoutCollectionName) {
161 this.ecalReadoutCollectionName = ecalReadoutCollectionName;
162 }
163
164
165
166
167
168
169
170 public void setNumSamplesAfter(int numSamplesAfter) {
171 this.numSamplesAfter = numSamplesAfter;
172 }
173
174
175
176
177
178
179
180 public void setNumSamplesBefore(int numSamplesBefore) {
181 this.numSamplesBefore = numSamplesBefore;
182 }
183
184
185
186
187
188
189
190 public void setReadoutLatency(int readoutLatency) {
191 this.readoutLatency = readoutLatency;
192 }
193
194
195
196
197
198
199 public void setReadoutWindow(int readoutWindow) {
200 this.readoutWindow = readoutWindow;
201 }
202
203
204
205
206
207
208
209 public void setCoincidenceWindow(int coincidenceWindow) {
210 this.coincidenceWindow = coincidenceWindow;
211 }
212
213
214
215
216
217
218
219 public void setUse2014Gain(boolean use2014Gain) {
220 this.use2014Gain = use2014Gain;
221 }
222
223
224
225
226
227
228 public void setPulseShape(String pulseShape) {
229 this.pulseShape = PulseShape.valueOf(pulseShape);
230 }
231
232
233
234
235
236
237
238 public void setTp(double tp) {
239 this.tp = tp;
240 }
241
242
243
244
245
246
247
248 public void setPePerMeV(double pePerMeV) {
249 this.pePerMeV = pePerMeV;
250 }
251
252
253
254
255
256
257 public void setDelay0(int delay0) {
258 this.delay0 = delay0;
259 }
260
261
262
263
264
265
266 public void setBufferLength(int bufferLength) {
267 this.bufferLength = bufferLength;
268 resetFADCBuffers();
269 }
270
271
272
273
274
275
276 public void setPipelineLength(int pipelineLength) {
277 this.pipelineLength = pipelineLength;
278 resetFADCBuffers();
279 }
280
281
282
283
284
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
295
296
297
298 public Map<Long, RingBuffer> getSignalMap() {
299 return analogPipelines;
300 }
301
302
303
304
305
306
307 public Map<Long, FADCPipeline> getPipelineMap() {
308 return digitalPipelines;
309 }
310
311
312
313
314
315
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
327 EcalChannelConstants channelData = findChannel(cellID);
328
329 double currentValue = signalBuffer.currentValue() * ((Math.pow(2, nBit) - 1) / maxVolt);
330
331 int pedestal = (int) Math.round(channelData.getCalibration().getPedestal());
332
333
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
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
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
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
456
457
458 }
459 return adcValues;
460 }
461
462 protected List<RawTrackerHit> readWindow() {
463
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
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
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
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
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
559
560
561
562 @Override
563 protected void putHits(List<CalorimeterHit> hits) {
564
565 for (CalorimeterHit hit : hits) {
566 RingBuffer eDepBuffer = analogPipelines.get(hit.getCellID());
567 double energyAmplitude = hit.getRawEnergy();
568
569 EcalChannelConstants channelData = findChannel(hit.getCellID());
570 if (addNoise) {
571
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
589
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
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
613 ecal = detector.getSubdetector(ecalName);
614
615
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
639
640
641
642
643
644 private double pulseAmplitude(double time, long cellID) {
645
646 EcalChannelConstants channelData = findChannel(cellID);
647
648 if (use2014Gain) {
649
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
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
674
675
676
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
685
686 return ((time / (shapingTime * shapingTime)) * Math.exp(-time / shapingTime));
687 case DoubleGaussian:
688
689
690
691
692
693 double norm = ((riseTime + fallTime) / 2) * Math.sqrt(2 * Math.PI);
694
695 return funcGaus(time - 3 * riseTime, (time < 3 * riseTime) ? riseTime : fallTime) / norm;
696 case ThreePole:
697
698
699 return ((time * time / (2 * shapingTime * shapingTime * shapingTime)) * Math.exp(-time / shapingTime));
700 default:
701 return 0.0;
702 }
703 }
704
705
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];
719 ptr = 0;
720 }
721
722
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
734
735 public void writeValue(int val) {
736 array[ptr] = val;
737 }
738
739
740
741
742 public void step() {
743 ptr++;
744 if (ptr == size) {
745 ptr = 0;
746 }
747 }
748
749
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
760
761
762
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 }