Jpp 21.0.0-rc.1
the software that should make you happy
Loading...
Searching...
No Matches
JPMTAnalogueSignalProcessor.hh
Go to the documentation of this file.
1#ifndef __JDETECTOR__JPMTANALOGUESIGNALPROCESSOR__
2#define __JDETECTOR__JPMTANALOGUESIGNALPROCESSOR__
3
4#include <istream>
5#include <cmath>
6#include <limits>
7
8#include "TRandom3.h"
9
10#include "JLang/JException.hh"
16
17/**
18 * \file
19 *
20 * PMT analogue signal processor.
21 * \author mdejong
22 */
23namespace JDETECTOR {
24
26
27
28 /**
29 * PMT analogue signal processor.
30 *
31 * This class provides for an implementation of the JDETECTOR::JPMTSignalProcessorInterface
32 * using a specific model for the analogue pulse of the PMT.\n
33 * In this, the leading edge of the analogue pulse from the PMT is assumed to be a Gaussian and the tail an exponential.\n
34 * The width of the Gaussian is referred to as the rise time and
35 * the inverse slope of the exponential to the decay time.\n
36 * The two functions are matched at a point where the values and first derivatives are identical.\n
37 *
38 * Note that the decay time is related to the rise time via the specification JDETECTOR::TIME_OVER_THRESHOLD_NS.
39 *
40 * The charge distribution is assumed to be a Gaussian which is centered at the specified gain
41 * and truncated by the specified threshold.
42 *
43 * The transit times are generated according the specified spread as follows.
44 * - If the specified transit-time spread (TTS) is negative,
45 * the transit times are generated according to measurements (see method JDETECTOR::getTransitTime).\n
46 * In this, the negated integral value of the TTS corresponds to the option
47 * which in turn corresponds to the detector identifier of the measurements.
48 * - If the specified TTS is positive,
49 * the transit times are generated according a Gaussian with a sigma equals to the given TTS.
50 * - If the TTS is zero, the transit times are generated without any spread.
51 */
54 public JPMTParameters
55 {
56 /**
57 * Threshold domain specifiers
58 */
60 BELOW_THRESHOLD = -1, //!< below threshold
61 THRESHOLDBAND = 0, //!< inside threshold band
62 ABOVE_THRESHOLD = 2 //!< above threshold
63 };
64
65
66 /**
67 * Constructor.
68 *
69 * \param parameters PMT parameters
70 */
73 JPMTParameters(parameters),
74 decayTime_ns(0.0),
75 t1(0.0),
76 y1(0.0),
77 x1(std::numeric_limits<double>::max())
78 {
79 configure();
80 }
81
82
83 /**
84 * Configure internal parameters.
85 *
86 * This method provides the implementations for
87 * - matching of the leading edge of the analogue pulse (Gaussian) and the tail (exponential); and
88 * - determination of number of photo-electrons above which the time-over-threshold
89 * linearly depends on the number of photo-electrons (apart from saturation).
90 *
91 * Note that this method will throw an error if the value of the rise time (i.e.\ width of the Gaussian)
92 * is too large with respect to the specification JDETECTOR::TIME_OVER_THRESHOLD_NS.
93 */
94 void configure()
95 {
96 static const int N = 100;
97 static const double precision = 1.0e-4;
98
99 // check thresholdband
100
101 if (threshold - thresholdBand < getTH0()) {
102 THROW(JValueOutOfRange, "JPMTAnalogueSignalProcessor::configure(): Invalid thresholdband [npe] " << thresholdBand);
103 }
104
105 // check rise time
106
108 THROW(JValueOutOfRange, "JPMTAnalogueSignalProcessor::configure(): Invalid rise time [ns] " << riseTime_ns);
109 }
110
111 // check probability for under-amplified signal
112
113 if (PunderAmplified < 0.0) {
114 THROW(JValueOutOfRange, "JPMTAnalogueSignalProcessor::configure(): Invalid probability of under-amplification " << PunderAmplified);
115 }
116
117 // check invalid combination of zero probability for under-amplified signal and zero gain spread
118
119 if (PunderAmplified == 0.0 && gainSpread <= 0.0) {
120 THROW(JValueOutOfRange, "JPMTAnalogueSignalProcessor::configure(): Invalid gain spread " << gainSpread << " for probability of under-amplification " << PunderAmplified);
121 }
122
123 // decay time
124
125 const double y = -log(threshold);
126
127 const double a = y;
128 const double b = riseTime_ns * sqrt(2.0*y) - TIME_OVER_THRESHOLD_NS;
129 const double c = 0.5*riseTime_ns*riseTime_ns;
130 const double Q = b*b - 4.0*a*c;
131
132 if (Q > 0.0)
133 decayTime_ns = (-b + sqrt(Q)) / (2.0*a);
134 else
135 decayTime_ns = -b / (2.0*a);
136
137 // fix matching of Gaussian and exponential
138
139 const double x = riseTime_ns / decayTime_ns;
140
141 t1 = riseTime_ns*x;
142 y1 = exp(-0.5*x*x);
143
144 // determine transition point to linear dependence of time-over-threshold as a function of number of photo-electrons
145
146 const double xs = saturation; // disable saturation
147
148 saturation = 1.0e50;
149
150 x1 = std::numeric_limits<double>::max(); // disable linearisation
151
152 double xmin = 1.0;
153 double xmax = 1.0 / (getDerivative(1.0) * slope);
154
155 for (int i = 0; i != N; ++i) {
156
157 const double x = 0.5 * (xmin + xmax);
158 const double u = getDerivative(x) * slope;
159
160 if (fabs(1.0 - u) < precision) {
161 break;
162 }
163
164 if (u < 1.0)
165 xmin = x;
166 else
167 xmax = x;
168 }
169
170 x1 = 0.5 * (xmin + xmax);
171
172 saturation = xs; // restore saturation
173 }
174
175
176 /**
177 * Get decay time.
178 *
179 * \return decay time [ns]
180 */
181 double getDecayTime() const
182 {
183 return decayTime_ns;
184 }
185
186
187 /**
188 * Get time at transition point from Gaussian to exponential.
189 *
190 * \return time [ns]
191 */
192 double getT1() const
193 {
194 return t1;
195 }
196
197
198 /**
199 * Get amplitude at transition point from Gaussian to exponential.
200 *
201 * \return amplitude [npe]
202 */
203 double getY1() const
204 {
205 return y1;
206 }
207
208
209 /**
210 * Get transition point from a model-dependent to linear relation between time-over-threshold and number of photo-electrons.
211 *
212 * \return number of photo-electrons [npe]
213 */
215 {
216 return x1;
217 }
218
219
220 /**
221 * Get amplitude at given time for a one photo-electron pulse.
222 *
223 * \param t1_ns time [ns]
224 * \return amplitude [npe]
225 */
226 double getAmplitude(const double t1_ns) const
227 {
228 if (t1_ns < t1) {
229
230 const double x = t1_ns / riseTime_ns;
231
232 return exp(-0.5*x*x); // Gaussian
233
234 } else {
235
236 const double x = t1_ns / decayTime_ns;
237
238 return exp(-x) / y1; // exponential
239 }
240 }
241
242
243 /**
244 * Get time to pass from threshold to top of analogue pulse.\n
245 * In this, the leading edge of the analogue pulse is assumed to be Gaussian.
246 *
247 * \param npe number of photo-electrons
248 * \param th threshold [npe]
249 * \return time [ns]
250 */
251 double getRiseTime(const double npe, const double th) const
252 {
253 return riseTime_ns * sqrt(2.0*log(npe/th)); // Gaussian
254 }
255
256
257 /**
258 * Get time to pass from top of analogue pulse to threshold.\n
259 * In this, the trailing edge of the analogue pulse is assumed to be exponential.
260 *
261 * \param npe number of photo-electrons
262 * \param th threshold [npe]
263 * \return time [ns]
264 */
265 double getDecayTime(const double npe, const double th) const
266 {
267 if (npe*y1 > th)
268 return decayTime_ns * (log(npe/th) - log(y1)); // exponential
269 else
270 return riseTime_ns * sqrt(2.0*log(npe/th)); // Gaussian
271 }
272
273
274 /**
275 * Get maximal rise time for given threshold.
276 *
277 * Note that the rise time is entirely constrained by the specification JDETECTOR::TIME_OVER_THRESHOLD_NS.
278 *
279 * \param th threshold [npe]
280 * \return rise time [ns]
281 */
282 static double getMaximalRiseTime(const double th)
283 {
284 if (th > 0.0 && th < 1.0)
285 return 0.5 * TIME_OVER_THRESHOLD_NS / sqrt(-2.0*log(th));
286 else
287 THROW(JValueOutOfRange, "JPMTAnalogueSignalProcessor::getMaximalRiseTime(): Invalid threshold " << th);
288 }
289
290
291 /**
292 * Get time-over-threshold with saturation.
293 *
294 * \param tot_ns time-over-threshold without saturation
295 * \return time-over-threshold with saturation
296 */
297 double applySaturation(const double tot_ns) const
298 {
299 return saturation / sqrt(tot_ns*tot_ns + saturation*saturation) * tot_ns;
300 }
301
302
303 /**
304 * Get time-over-threshold without saturation.
305 *
306 * \param tot_ns time-over-threshold with saturation
307 * \return time-over-threshold without saturation
308 */
309 double removeSaturation(const double tot_ns) const
310 {
311 if (tot_ns < saturation)
312 return saturation / sqrt(saturation*saturation - tot_ns*tot_ns) * tot_ns;
313 else
314 return std::numeric_limits<double>::max();
315 //THROW(JValueOutOfRange, "Time-over-threshold exceeds saturation " << tot_ns << " >= " << saturation);
316 }
317
318
319 /**
320 * Get derivative of saturation factor.
321 *
322 * \param tot_ns time-over-threshold without saturation
323 * \return derivative of saturation factor
324 */
325 double getDerivativeOfSaturation(const double tot_ns) const
326 {
327 return saturation * saturation * saturation / ((saturation*saturation + tot_ns*tot_ns) * sqrt(saturation*saturation + tot_ns*tot_ns));
328 }
329
330
331 /**
332 * Get gain spread for given number of photo-electrons.
333 *
334 * \param NPE number of photo-electrons
335 * \return gain spread
336 */
337 double getGainSpread(int NPE) const
338 {
339 return sqrt((double) NPE * gain) * gainSpread;
340 }
341
342
343 /**
344 * Get integral of probability.
345 *
346 * \param xmin minimum number of photo-electrons
347 * \param xmax maximum number of photo-electrons
348 * \param NPE true number of photo-electrons
349 * \return probability
350 */
351 double getIntegralOfChargeProbability(const double xmin, const double xmax, const int NPE) const
352 {
353 double zmin = xmin;
354 double zmax = xmax;
355
356 const double th = threshold - thresholdBand;
357
358 if (zmin < th) { zmin = th; }
359 if (zmax < th) { zmax = th; }
360
361 if (PunderAmplified <= 0.0) {
362
363 const double mu = NPE * gain;
364 const double sigma = getGainSpread(NPE);
365
366 const double cumulP = (0.5 * erfc((zmin - mu) / sqrt(2.0) / sigma) -
367 0.5 * erfc((zmax - mu) / sqrt(2.0) / sigma));
368 const double norm = 0.5 * erfc((th - mu) / sqrt(2.0) / sigma);
369
370 return cumulP / norm;
371
372 } else if (PunderAmplified < 1.0) {
373
374 const double fs = gainSpread * gainSpread;
375
376 double cumulP = 0.0;
377 double norm = 0.0;
378 double weight = pow(1.0 - PunderAmplified, NPE);
379
380 for (int k = 0; k <= NPE; ++k) { // k underamplified photo-electrons
381
382 const double mu = (NPE-k) * gain + (k) * fs * gain;
383 const double sigma = sqrt(mu) * getGainSpread(1);
384
385 cumulP += weight * (0.5 * erfc((zmin - mu) / sqrt(2.0) / sigma) -
386 0.5 * erfc((zmax - mu) / sqrt(2.0) / sigma));
387 norm += weight * (0.5 * erfc((th - mu) / sqrt(2.0) / sigma));
388
389 weight *= ((double) (NPE-k) / ((double) (k+1))) * PunderAmplified / (1.0 - PunderAmplified);
390 }
391
392 return cumulP / norm;
393
394 } else {
395
396 THROW(JValueOutOfRange, "JPMTAnalogueSignalProcessor::getIntegralOfChargeProbability(): Invalid probability of under-amplification " << PunderAmplified);
397 }
398 }
399
400
401 /**
402 * Get integral of probability in specific threshold domain
403 *
404 * \param domain threshold domain
405 * \param NPE true number of photo-electrons
406 * \return probability
407 */
408 double getIntegralOfChargeProbability(const JThresholdDomain domain, const int NPE) const
409 {
410 switch (domain) {
411
412 case ABOVE_THRESHOLD:
414
415 case THRESHOLDBAND:
417
418 default:
419 return 0.0;
420 }
421 }
422
423
424 /**
425 * Set PMT parameters.
426 *
427 * \param parameters PMT parameters
428 */
429 void setPMTParameters(const JPMTParameters& parameters)
430 {
431 static_cast<JPMTParameters&>(*this).setPMTParameters(parameters);
432
433 configure();
434 }
435
436
437 /**
438 * Read PMT signal from input.
439 *
440 * \param in input stream
441 * \param object PMT signal
442 * \return input stream
443 */
444 friend std::istream& operator>>(std::istream& in, JPMTAnalogueSignalProcessor& object)
445 {
446 in >> static_cast<JPMTParameters&>(object);
447
448 object.configure();
449
450 return in;
451 }
452
453
454 /**
455 * Get threshold domain.
456 *
457 * \param npe number of photo-electrons
458 * \return threshold domain
459 */
460 JThresholdDomain getThresholdDomain(const double npe) const
461 {
462 if (npe > threshold) {
463
464 return ABOVE_THRESHOLD;
465
466 } else if (npe > threshold - thresholdBand) {
467
468 return THRESHOLDBAND;
469
470 } else {
471
472 return BELOW_THRESHOLD;
473 }
474 }
475
476
477 /**
478 * Apply relative QE.
479 *
480 * \return true if accepted; false if rejected
481 */
482 virtual bool applyQE() const override
483 {
484 if (QE <= 0.0)
485 return false;
486 else if (QE < 1.0)
487 return gRandom->Rndm() < QE;
488 else
489 return true;
490 }
491
492
493 /**
494 * Get randomised time according transit-time distribution.
495 *
496 * \param t_ns time [ns]
497 * \return time [ns]
498 */
499 virtual double getRandomTime(const double t_ns) const override
500 {
501 if (TTS_ns < 0.0)
502 return t_ns + getTransitTime(gRandom->Rndm(), getType());
503 else if (TTS_ns > 0.0)
504 return gRandom->Gaus(t_ns, TTS_ns);
505 else
506 return t_ns;
507 }
508
509
510 /**
511 * Compare arrival times of photo-electrons.
512 * This implementation uses the internal rise time as two photo-electron resolution.
513 *
514 * Two (or more) photo-electrons are merged if they are comparable.
515 *
516 * \param first first photo-electron
517 * \param second second photo-electron
518 * \return true if arrival times of photo-electrons are within two photo-electron resolution; else false
519 */
520 virtual bool compare(const JPhotoElectron& first, const JPhotoElectron& second) const override
521 {
522 return second.t_ns < first.t_ns + riseTime_ns;
523 }
524
525
526 /**
527 * Get randomised charge according to gain and gain spread.
528 *
529 * \param NPE number of photo-electrons
530 * \return number of photo-electrons
531 */
532 virtual double getRandomCharge(const int NPE) const override
533 {
534 double q;
535
536 do {
537
538 if (PunderAmplified <= 0.0) {
539
540 const double mu = NPE * gain;
541 const double sigma = getGainSpread(NPE);
542
543 q = gRandom->Gaus(mu,sigma);
544
545 } else if (PunderAmplified < 1.0) {
546
547 // Determine which contribution to sample from
548 // Method uses inverse transform sampling for a binomial distribution
549
550 const double X = gRandom->Uniform();
551 double sum_p = 0.0;
552 double weight = pow(1-PunderAmplified, NPE);
553 int k = 0; // k underamplified photo-electrons
554
555 for (; k <= NPE; ++k) {
556
557 sum_p += weight;
558 if (sum_p > X) { break; }
559
560 weight *= ((double) (NPE-k) / ((double) (k+1))) * PunderAmplified / (1.0 - PunderAmplified);
561 }
562
563 // Sample from chosen contribution
564 const double fs = gainSpread * gainSpread;
565 const double mu = (NPE-k) * gain + (k) * fs * gain;
566 const double sigma = sqrt(mu) * getGainSpread(1);
567
568 q = gRandom->Gaus(mu,sigma);
569
570 } else {
571
572 THROW(JValueOutOfRange, "JPMTAnalogueSignalProcessor::getRandomCharge(): Invalid probability of under-amplification " << PunderAmplified);
573 }
574
575 } while (q < 0.0);
576
577 return q;
578 }
579
580
581 /**
582 * Get probability density for given charge.
583 *
584 * \param npe observed number of photo-electrons
585 * \param NPE true number of photo-electrons
586 * \return probability [npe^-1]
587 */
588 virtual double getChargeProbability(const double npe, const int NPE) const override
589 {
591
592 if (PunderAmplified <= 0.0) {
593
594 const double mu = NPE * gain;
595 const double sigma = getGainSpread(NPE);
596
597 const double prob = JMATH::Gauss(npe, mu, sigma);
598 const double norm = 0.5 * erfc(((threshold-thresholdBand) - mu) / sqrt(2.0) / sigma);
599
600 return prob / norm;
601
602 } else if (PunderAmplified < 1.0) {
603
604 const double fs = gainSpread * gainSpread;
605
606 double norm = 0.0;
607 double prob = 0.0;
608 double weight = pow(1-PunderAmplified, NPE);
609
610 for (int k = 0; k <= NPE; ++k) { // k underamplified photo-electrons
611
612 const double mu = (NPE-k) * gain + (k) * fs * gain;
613 const double sigma = sqrt(mu) * getGainSpread(1);
614
615 prob += weight * JMATH::Gauss(npe, mu, sigma);
616 norm += weight * (0.5 * erfc(((threshold-thresholdBand) - mu) / sqrt(2.0) / sigma));
617
618 weight *= ((double) (NPE-k) / ((double) (k+1))) * PunderAmplified / (1.0 - PunderAmplified);
619 }
620
621 return prob / norm;
622 }
623 }
624
625 return 0.0;
626 }
627
628
629 /**
630 * Apply threshold.
631 *
632 * \param npe number of photo-electrons
633 * \return true if pass; else false
634 */
635 virtual bool applyThreshold(const double npe) const override
636 {
638 }
639
640
641 /**
642 * Get time to reach threshold.
643 *
644 * Note that the rise time is defined to be zero for a one photo-electron signal.
645 *
646 * \param npe number of photo-electrons
647 * \return time [ns]
648 */
649 virtual double getRiseTime(const double npe) const override
650 {
651 if (slewing) {
652
653 switch (getThresholdDomain(npe)) {
654
655 case THRESHOLDBAND:
656 return ((getRiseTime(npe, getTH0()) - getRiseTime(npe, threshold-thresholdBand)) -
657 (getRiseTime(1.0, getTH0()) - getRiseTime(1.0, threshold-thresholdBand))) + this->mean_ns;
658
659 case ABOVE_THRESHOLD:
660 return ((getRiseTime(npe, getTH0()) - getRiseTime(npe, threshold)) -
661 (getRiseTime(1.0, getTH0()) - getRiseTime(1.0, threshold)));
662
663 default:
664 THROW(JValueOutOfRange, "JPMTAnalogueSignalProcessor::getRiseTime: Invalid charge " << npe);
665 }
666
667 } else {
668
669 return 0.0;
670 }
671 }
672
673
674 /**
675 * Get time-over-threshold (ToT).
676 *
677 * \param npe number of photo-electrons
678 * \return ToT [ns]
679 */
680 virtual double getTimeOverThreshold(const double npe) const override
681 {
682 switch (getThresholdDomain(npe)) {
683
684 case THRESHOLDBAND: {
685
686 return gRandom->Gaus(mean_ns, sigma_ns);
687 }
688
689 case ABOVE_THRESHOLD: {
690
691 double tot = 0.0;
692
693 if (npe*y1 <= threshold) {
694
695 tot += getRiseTime(npe, threshold); // Gaussian
696 tot += getRiseTime(npe, threshold); // Gaussian
697
698 } else if (npe <= getStartOfLinearisation()) {
699
700 tot += getRiseTime (npe, threshold); // Gaussian
701 tot += getDecayTime(npe, threshold); // exponential
702
703 } else {
704
705 tot += getRiseTime (getStartOfLinearisation(), threshold); // Gaussian
706 tot += getDecayTime(getStartOfLinearisation(), threshold); // exponential
707
708 tot += slope * (npe - getStartOfLinearisation()); // linear
709 }
710
711 return applySaturation(tot);
712 }
713
714 default: {
715
716 THROW(JValueOutOfRange, "JPMTAnalogueSignalProcessor::getTimeOverThreshold: Invalid charge " << npe);
717 }}
718 }
719
720
721 /**
722 * Get derivative of number of photo-electrons to time-over-threshold.
723 *
724 * \param npe number of photo-electrons
725 * \return dnpe/dToT [ns^-1]
726 */
727 virtual double getDerivative(const double npe) const override
728 {
729 switch (getThresholdDomain(npe)) {
730
731 case ABOVE_THRESHOLD: {
732
733 const double z = riseTime_ns / sqrt(2.0 * log(npe/threshold));
734
735 double y = 0.0;
736
737 if (npe*y1 > threshold) {
738
739 if (npe <= getStartOfLinearisation())
740 y = npe / (z + decayTime_ns); // Gaussian + exponential
741 else
742 y = 1.0 / slope; // linear
743
744 } else {
745
746 y = npe / (2.0 * z); // Gaussian + Gaussian
747 }
748
749 const double tot_ns = getTimeOverThreshold(npe);
750
752 }
753
754 default:
755 return 0.0;
756 }
757 }
758
759
760 /**
761 * Probability that a hit survives the simulation of the PMT.
762 * The survival probability takes into account the number of photo-electrons, the analogue signal of the PMT and the threshold of the discriminator.
763 *
764 * \param NPE number of photo-electrons
765 * \return probability
766 */
767 virtual double getSurvivalProbability(const int NPE) const override
768 {
769 using namespace JPP;
770
771 if (QE <= 0.0) {
772
773 return 0.0;
774
775 } else if (QE < 1.0) {
776
777 double P = 0.0;
778
779 for (int i = 1; i <= NPE; ++i) { // i corresponds to number of photo-electrons passing relative QE
780
781 const double p = (binomial(NPE, i) * pow(QE, i) * pow(1.0 - QE, NPE - i));
782
783 P += p * getThresholdProbability(NPE);
784 }
785
786 return P;
787
788 } else {
789
790 return getThresholdProbability(NPE);
791 }
792 }
793
794
795 /**
796 * Probability that a hit survives the threshold of the PMT.
797 *
798 * \param NPE number of photo-electrons
799 * \return probability
800 */
801 virtual double getThresholdProbability(const int NPE) const override
802 {
803 if (NPE > 0) {
804
805 if (PunderAmplified <= 0.0) {
806
807 const double mu = NPE * gain;
808 const double sigma = getGainSpread(NPE);
809
810 if (sigma > 0.0) {
811
812 const double Ptotal = 0.5 * erfc((0.0 - mu) / (sqrt(2.0) * sigma));
813 const double Pabove = 0.5 * erfc((threshold - thresholdBand - mu) / (sqrt(2.0) * sigma));
814
815 return Pabove / Ptotal;
816
817 } else if (mu >= threshold - thresholdBand) {
818
819 return 1.0;
820
821 } else {
822
823 return 0.0;
824 }
825
826 } else if (PunderAmplified < 1.0) {
827
828 const double fs = gainSpread * gainSpread; // amplification factor per stage equals square of relative gain spread
829
830 double Ptotal = 0.0;
831 double Pabove = 0.0;
832 double weight = pow(1.0 - PunderAmplified, NPE); // binomial weight - start with all photo-electrons nominal
833
834 for (int k = 0; k <= NPE; ++k) { // NPE-k nominal photo-electrons and k underamplified photo-electrons
835
836 const double mu = (NPE-k) * gain + (k) * fs * gain;
837 const double sigma = sqrt(mu) * getGainSpread(1);
838
839 if (sigma > 0.0) {
840
841 Ptotal += weight * 0.5 * erfc((0.0 - mu) / (sqrt(2.0) * sigma));
842 Pabove += weight * 0.5 * erfc((threshold - thresholdBand - mu) / (sqrt(2.0) * sigma));
843
844 } else if (mu >= threshold - thresholdBand) {
845
846 Ptotal += weight;
847 Pabove += weight;
848 }
849
850 weight *= ((double) (NPE-k) / ((double) (k+1))) * PunderAmplified / (1.0 - PunderAmplified);
851 }
852
853 return Pabove / Ptotal;
854
855 } else {
856
857 THROW(JValueOutOfRange, "JPMTAnalogueSignalProcessor::getThresholdProbability(): Invalid PunderAmplified " << PunderAmplified);
858 }
859 }
860
861 return 0.0;
862 }
863
864
865 /**
866 * Get number of photo-electrons.
867 *
868 * \param tot_ns time over threshold [ns]
869 * \return number of photo-electrons
870 */
871 virtual double getNPE(const double tot_ns) const override
872 {
873 if (tot_ns >= saturation) {
874 return std::numeric_limits<double>::max();
875 }
876
877 const double tot = removeSaturation(tot_ns);
878 const double TOT = (getRiseTime (getStartOfLinearisation(), threshold) +
880
881 if (tot <= 2*getRiseTime(threshold/y1,threshold)) { // Gaussian + Gaussian
882
883 return threshold * exp(tot*tot/riseTime_ns/riseTime_ns/8.0);
884
885 } else if (tot <= TOT) { // Gaussian + Exponential
886
887 const double a = decayTime_ns;
888 const double b = sqrt(2.0) * riseTime_ns;
889 const double c = -(decayTime_ns*log(y1) + tot);
890 const double z = (-b + sqrt(b*b - 4*a*c)) / (2*a);
891
892 return threshold * exp(z*z);
893
894 } else { // linear
895
896 return getStartOfLinearisation() + (tot - TOT) / slope;
897 }
898 }
899
900
901 /**
902 * Get probability of having a pulse with specific time-over-threshold
903 *
904 * \param tot_ns time-over-threshold with saturation [ns]
905 * \param NPE true number of photo-electrons
906 * \return probability [ns^-1]
907 */
908 double getTimeOverThresholdProbability(const double tot_ns, const int NPE) const
909 {
910 const double PthBand = getIntegralOfChargeProbability(THRESHOLDBAND, NPE);
911
912 const double npe = getNPE(tot_ns);
913 const double y = getChargeProbability(npe, NPE);
914 const double v = getDerivative(npe);
915
916 const double RthBand = PthBand * JMATH::Gauss(tot_ns, mean_ns, sigma_ns);
917 const double RaboveTh = y * v;
918
919 return RthBand + RaboveTh;
920 }
921
922
923 /**
924 * Get cumulative probability of time-over-threshold distribution
925 *
926 * \param Tmin minimum time-over-threshold (with saturation) [ns]
927 * \param Tmax maximum time-over-threshold (with saturation) [ns]
928 * \param NPE true number of photo-electrons
929 * \return probability [ns^-1]
930 */
931 double getIntegralOfTimeOverThresholdProbability(const double Tmin, const double Tmax, const int NPE) const
932 {
933
934 const double PthBand = getIntegralOfChargeProbability(THRESHOLDBAND, NPE);
935
936 const double IthBand = PthBand * (0.5 * erfc((Tmin - mean_ns) / sqrt(2.0) / sigma_ns) -
937 0.5 * erfc((Tmax - mean_ns) / sqrt(2.0) / sigma_ns));
938 const double IaboveTh = getIntegralOfChargeProbability(getNPE(Tmin), getNPE(Tmax), NPE);
939
940 return IthBand + IaboveTh;
941 }
942
943
944 /**
945 * Get lower threshold for rise time evaluation.
946 *
947 * \return threshold [npe]
948 */
949 static double getTH0()
950 {
951 return 0.1;
952 }
953
954
955 /**
956 * Get upper threshold for rise time evaluation.
957 *
958 * \return threshold [npe]
959 */
960 static double getTH1()
961 {
962 return 0.9;
963 }
964
965 protected:
966
967 double decayTime_ns; //!< decay time [ns]
968 double t1; //!< time at match point [ns]
969 double y1; //!< amplitude at match point [npe]
970 /**
971 * Transition point from a logarithmic to a linear relation
972 * between time-over-threshold and number of photo-electrons.\n
973 * Measurements by B. Schermer and R. Bruijn at Nikhef.
974 */
975 double x1;
976 };
977
978
979 /**
980 * Get time-over-threshold probability.
981 *
982 * \param pmt PMT signal processor
983 * \param tot_ns time-over-threshold [ns]
984 * \param NPE expected number of photo-electrons
985 * \param precision precision
986 * \return probability
987 */
989 const double tot_ns,
990 const double NPE,
991 const double precision = 1.0e-4)
992 {
993 int i = (int) (NPE - 5.0 * sqrt(NPE) - 0.5);
994 int M = (int) (NPE + 5.0 * sqrt(NPE) + 0.5);
995
996 if (i < 1) { i = 1; }
997 if (M < i) { M = i; }
998
999 double p = NPE * exp(-NPE) / (double) 1;
1000
1001 for (int __i = 1; __i != i; ++__i) {
1002 p *= NPE / __i;
1003 }
1004
1005 double P = 0.0;
1006
1007 for (double p0 = 0.0; (p >= p0 || p > precision) && i != M; ++i, p0 = p, p *= NPE / (double) i) {
1008 P += pmt.getTimeOverThresholdProbability(tot_ns, i) * p;
1009 }
1010
1011 return P;
1012 }
1013}
1014
1015#endif
Time calibration (including definition of sign of time offset).
Exceptions.
#define THROW(JException_t, A)
Marco for throwing exception with std::ostream compatible message.
Auxiliary methods for mathematics.
Data structure for PMT parameters.
double sigma_ns
time-over-threshold standard deviation of threshold-band hits [ns]
double QE
relative quantum efficiency
int getType() const
Get type for for time-slewing correction.
double thresholdBand
threshold-band [npe]
double gainSpread
gain spread [unit]
JPMTParameters()
Default constructor.
double riseTime_ns
rise time of analogue pulse [ns]
double TTS_ns
transition time spread [ns]
double threshold
threshold [npe]
double mean_ns
mean time-over-threshold of threshold-band hits [ns]
void setPMTParameters(const JPMTParameters &parameters)
Set PMT parameters.
double slope
slope [ns/npe]
double PunderAmplified
probability of underamplified hit
bool slewing
time slewing of analogue signal
double saturation
saturation [ns]
Exception for accessing a value in a collection that is outside of its range.
file Auxiliary data structures and methods for detector calibration.
Definition JAnchor.hh:12
JDETECTOR::JTransitTimeGenerator_t getTransitTime
Function object to generate transit time.
const double TIME_OVER_THRESHOLD_NS
Specification for time-over-threshold corresponding to a one photo-electron pulse.
double getTimeOverThresholdProbability(const JPMTAnalogueSignalProcessor &pmt, const double tot_ns, const double NPE, const double precision=1.0e-4)
Get time-over-threshold probability.
double Gauss(const double x, const double sigma)
Normalised Gauss function.
This name space includes all other name spaces (except KM3NETDAQ, KM3NET and ANTARES).
friend std::istream & operator>>(std::istream &in, JPMTAnalogueSignalProcessor &object)
Read PMT signal from input.
virtual bool applyQE() const override
Apply relative QE.
virtual bool compare(const JPhotoElectron &first, const JPhotoElectron &second) const override
Compare arrival times of photo-electrons.
static double getMaximalRiseTime(const double th)
Get maximal rise time for given threshold.
double getT1() const
Get time at transition point from Gaussian to exponential.
double getGainSpread(int NPE) const
Get gain spread for given number of photo-electrons.
double getIntegralOfChargeProbability(const JThresholdDomain domain, const int NPE) const
Get integral of probability in specific threshold domain.
virtual double getRandomTime(const double t_ns) const override
Get randomised time according transit-time distribution.
virtual bool applyThreshold(const double npe) const override
Apply threshold.
virtual double getChargeProbability(const double npe, const int NPE) const override
Get probability density for given charge.
static double getTH1()
Get upper threshold for rise time evaluation.
virtual double getRiseTime(const double npe) const override
Get time to reach threshold.
virtual double getNPE(const double tot_ns) const override
Get number of photo-electrons.
double x1
Transition point from a logarithmic to a linear relation between time-over-threshold and number of ph...
double getTimeOverThresholdProbability(const double tot_ns, const int NPE) const
Get probability of having a pulse with specific time-over-threshold.
double getIntegralOfChargeProbability(const double xmin, const double xmax, const int NPE) const
Get integral of probability.
double getIntegralOfTimeOverThresholdProbability(const double Tmin, const double Tmax, const int NPE) const
Get cumulative probability of time-over-threshold distribution.
double getY1() const
Get amplitude at transition point from Gaussian to exponential.
JThresholdDomain getThresholdDomain(const double npe) const
Get threshold domain.
virtual double getThresholdProbability(const int NPE) const override
Probability that a hit survives the threshold of the PMT.
virtual double getRandomCharge(const int NPE) const override
Get randomised charge according to gain and gain spread.
JPMTAnalogueSignalProcessor(const JPMTParameters &parameters=JPMTParameters())
Constructor.
double getDerivativeOfSaturation(const double tot_ns) const
Get derivative of saturation factor.
double applySaturation(const double tot_ns) const
Get time-over-threshold with saturation.
double getDecayTime(const double npe, const double th) const
Get time to pass from top of analogue pulse to threshold.
double removeSaturation(const double tot_ns) const
Get time-over-threshold without saturation.
double getRiseTime(const double npe, const double th) const
Get time to pass from threshold to top of analogue pulse.
double getAmplitude(const double t1_ns) const
Get amplitude at given time for a one photo-electron pulse.
static double getTH0()
Get lower threshold for rise time evaluation.
double getStartOfLinearisation() const
Get transition point from a model-dependent to linear relation between time-over-threshold and number...
void setPMTParameters(const JPMTParameters &parameters)
Set PMT parameters.
void configure()
Configure internal parameters.
virtual double getSurvivalProbability(const int NPE) const override
Probability that a hit survives the simulation of the PMT.
virtual double getTimeOverThreshold(const double npe) const override
Get time-over-threshold (ToT).
virtual double getDerivative(const double npe) const override
Get derivative of number of photo-electrons to time-over-threshold.
Data structure for single photo-electron.