MDetector/Channel.cc
Go to the documentation of this file.
1 #include <mdet/Channel.h>
2 
3 #include <mdet/MHierarchyInfo.h>
4 //
5 #include <utl/AugerUnits.h>
6 //
7 #include <fwk/RandomEngineRegistry.h>
8 //
9 #include <algorithm>
10 #include <vector>
11 #include <sstream>
12 #include <iostream>
13 #include <string>
14 //
15 #include <CLHEP/Random/RandGauss.h>
16 
17 #include <boost/math/tools/roots.hpp>
18 #include <boost/math/tools/tuple.hpp>
19 using boost::math::tools::eps_tolerance; // Binary functor for specified number of bits.
20 using boost::math::tools::toms748_solve;
21 
22 namespace {
23 
25  struct RootComp {
26  /*
27  * In order to keep things strictly standard, the comparisson
28  * functor needs to be homogeneous: so then a dummy key needs to
29  * be built.
30  *
31  * http://www.sgi.com/tech/stl/lower_bound.html
32  *
33  * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2001/n1313.html
34  */
35  template<class C>
36  bool operator()(const C& r1, const C& r2) {
37  // We go to a template, tough we're kinda assuming our Root type specificaly.
38  return r1.fTime < r2.fTime;
39  }
40  };
41 
43  bool IsEqual(double a, double b, double eps)
44  {
45  return std::fabs(a - b) < eps * std::max( std::fabs(a), std::fabs(b));
46  };
47 
49  class EqualPredicate {
50  public:
51  EqualPredicate(double e) : fEps(e) {
52  }
53  bool operator()(double a, double b) {
54  return IsEqual(a, b, fEps);
55  }
56  private:
57  double fEps;
58  };
59 
60 
61  template <class T, class F>
62  class MyFunctionObject {
63  public:
64  MyFunctionObject(T const& thr, mdet::Channel::Discriminator::Proxy<F> const& func) : fThr(thr), fFctor( func ) {}
65 
66  T operator()(T const& t ) {
67  return fFctor(t) - fThr;
68  }
69 
70  private:
71  T fThr;
73  };
74 
75 
77 
90  template<class BackInsertionSequence, class Thr,class Fctor>
91  void RootFinder( MyFunctionObject<Thr,Fctor>& func, BackInsertionSequence& roots, double s, double e, double f_min, double f_max, int nest, double relTol) {
92  //void RootFinder( mdet::Channel::Discriminator::Proxy<Fctor> & func, BackInsertionSequence& roots, double s, double e, double f_min, double f_max, int nest, double relTol) {
93 
94  using namespace std;
95  using namespace boost::math;
96  //double guess = (s+e)/2;
97 
98  int digits = std::numeric_limits<double>::digits; // Maximum possible binary digits accuracy for type double.
99  // digits used to control how accurate to try to make the result.
100  int get_digits = (digits * 3) /4; // (3/4) of maximum accuracy.
101 
102  const boost::uintmax_t maxit = 100;
103  boost::uintmax_t it = maxit; // Initally our chosen max iterations, but updated with actual.
104 
105  eps_tolerance<double> tol(get_digits);
106 
107  //Solve
108  std::pair<double, double> bracket_root = toms748_solve(func, s, e, f_min, f_max, tol, it);
109 
110  double r = bracket_root.first + (bracket_root.second - bracket_root.first)/2; // Midway between brackets.
111 
112  if (it < maxit ) {
113  //cout << "Converged after " << it << " iterations " << endl;
114  //cout << "(" << nest << ") start=" << s << " end=" << e << " guess=" << guess << " root=" << r << endl;
115  /*
116  * Extended check including boundaries; and using IsEqual
117  */
118  if ( (s <= r || IsEqual(r, s, relTol)) && (r <= e || IsEqual(r, e, relTol)) ) {
119  //cout << "ADDING ONE ROOT\n";
120  roots.push_back( r ) ;
121  if ( !IsEqual(r, s, relTol) && !IsEqual(r, e, relTol ) ) {
122  RootFinder( func, roots, s, r, func(s), func(r), nest+1, relTol);
123  RootFinder( func, roots, r, e, func(r), func(r), nest+1, relTol);
124  }//end boundary check
125  }//end if in interval
126 
127  }//end if it < maxit
128 
129  return;
130  }
131 }
132 
133 namespace mdet {
134 
135  const char* const
137 
138  const char* const
140 
142  (int cId, const det::VManager::IndexMap& parentMap, const FrontEnd& parent) :
143  MDetectorComponent<Channel>::Type(cId, parentMap),
144  fFrontEnd(parent)
145  {
146  Register(fInvertingInputResistance);
147  Register(fFeedbackResistance);
148  Register(fDCGain);
149  Register(fLowCutoffFrequency);
150  Register(fHighCutoffFrequency);
151  Register(fThreshold);
152  Register(fResponseTime);
153  Register(fDiscriminatorLowLevel);
154  Register(fDiscriminatorHiLevel);
155  Register(fSlewRate);
156  Register(fAbsoluteError);
157  Register(fInitialIntervalLength);
158  Register(fIterationsNumber);
159  Register(fMaxNumberOfErrors);
160  Register(fSignalShiftMean);
161  Register(fSignalShiftStdDev);
162  }
163 
164  double
166  const
167  {
168  return GetData(fInvertingInputResistance, "invertingInputResistance");
169  }
170 
171  double
173  const
174  {
175  return GetData(fFeedbackResistance, "feedbackResistance");
176  }
177 
178  double
180  const
181  {
182  return GetData(fDCGain, "DCGain");
183 
184  }
185 
186  double
188  const
189  {
190  return GetData(fLowCutoffFrequency, "lowCutoffFrequency");
191  }
192 
193  double
195  const
196  {
197  return GetData(fHighCutoffFrequency, "highCutoffFrequency");
198  }
199 
200  double
202  const
203  {
204  return GetData(fThreshold, "threshold");
205  }
206 
207  double
209  const
210  {
211  return GetData(fDiscriminatorLowLevel, "discriminatorLowLevel");
212  }
213 
214  double
216  const
217  {
218  return GetData(fDiscriminatorHiLevel, "discriminatorHiLevel");
219  }
220 
221  double
223  const
224  {
225  return GetData(fSlewRate, "slewRate");
226  }
227 
228  double
230  const
231  {
232  return GetData(fResponseTime, "responseTime");
233  }
234 
235  double
237  const
238  {
239  return GetData(fAbsoluteError, "absoluteError");
240  }
241 
242  double
244  const
245  {
246  return GetData(fInitialIntervalLength, "initialIntervalLength");
247  }
248 
249  unsigned int
251  const
252  {
253  return GetData(fIterationsNumber, "iterationsNumber");
254  }
255 
256  unsigned int
258  const
259  {
260  return GetData(fMaxNumberOfErrors, "maxNumberOfErrors");
261  }
262 
263 
264  double
266  const
267  {
268  return GetData(fSignalShiftMean, "signalShiftMean");
269  }
270 
271  double
273  const
274  {
275  return GetData(fSignalShiftStdDev, "signalShiftStdDev");
276 
277  }
278 
279  double
281  const
282  {
283  utl::RandomEngine& rnd = fwk::RandomEngineRegistry::GetInstance().Get(fwk::RandomEngineRegistry::eDetector);
284  /*
285  * 1) Initially, as in other cases, the return value was forced to be positive (with a loop until that), but
286  * for this cases (a shift) it doesn't matter if the number (likely or not) is negative.
287  * Granted, a negative shift will mean something like that the output came before the signal
288  * entering the device. Despite that being impossible physically, it's something quite different than
289  * having a negative amplitude in the pulses.
290  *
291  * 2) From CLHEP's RandGauss.icc file:
292  *
293  * inline double RandGauss::shoot(HepRandomEngine* anEngine, double mean, double stdDev) {
294  * return shoot(anEngine)*stdDev + mean;
295  * }
296  *
297  * so we can freely set the standard deviation to zero and generate safely a degenerated
298  * spike distribution, with only the mean as possible outcome.
299  */
300  return CLHEP::RandGauss::shoot(& rnd.GetEngine(), GetSignalShiftMean(), GetSignalShiftStdDev());
301  }
302 
303  /*
304  *********************************
305  ***** Amplification stage. ******
306  *********************************
307  */
308 
309  std::complex<double>
310  Channel::ComputeFrequencyFactor(double freq, double freqLimit)
311  const
312  {
313  const std::complex<double> _1(1, 0); // real unit.
314  const std::complex<double> i(0, 1); // imaginary unit
315  return _1 + i * (freq / freqLimit);
316  }
317 
318  std::complex<double>
320  const
321  {
322  const std::complex<double> fDenominator = ComputeFrequencyFactor(freq, GetLowCutoffFrequency()) *
324  const std::complex<double> openLoopGain = GetDCGain() / fDenominator;
325  const std::complex<double> rDenominator = GetInvertingInputResistance() +
327  openLoopGain * GetInvertingInputResistance();
328  // Note the sign that defines the inverting characteristic.
329  // This is related to the fact that the SPE pulses are negative peaks.
330  return - openLoopGain * GetInvertingInputResistance() * GetFeedbackResistance() / rDenominator;
331  }
332 
333  /*
334  *********************************
335  ***** Discrimination stage. *****
336  *********************************
337  */
338 
340  {
341  // At first this destructor was made protected and non-virtual:
342  //
343  // Being an interface we protect (and non-virtualize)
344  // the destructor.
345  // http://www.gotw.ca/publications/mill18.htm
346  //
347  // One may get
348  // ./mdet/Channel.h:59: warning:
349  // 'class mdet::Channel::Discriminator::Callable' has virtual functions but non-virtual destructor
350  // but that's a phony alarm.
351  // See for instance,
352  // http://stackoverflow.com/questions/127426/gnu-compiler-warning-class-has-virtual-functions-but-non-virtual-destructor
353  //
354  // At last, that's an error, because we'll be deleting through it: so needs to be virtual, so
355  // as to have the subclass' destructor be properly called.
356  // Also, being protected you may get
357  // MDetector/Channel.cc: In destructor 'std::auto_ptr<_Tp>::~auto_ptr() [with _Tp = mdet::Channel::Discriminator::Callable]':
358  // MDetector/Channel.cc:174: instantiated from here
359  // MDetector/Channel.cc:128: error: 'mdet::Channel::Discriminator::Callable::~Callable()' is protected
360  }
361 
362  bool
364  const
365  {
366  return ( (*fInputPulse)(t) - fChannel.GetThreshold() ) > 0;
367  }
368 
369  double
371  const
372  {
375  else if (v < fChannel.GetDiscriminatorLowLevel())
377  else
378  return v;
379  }
380 
381  double
382  Channel::Discriminator::ComputeOutput(double signReferenceTime, double deltaTime, double prevOutput)
383  const
384  {
385  const bool over = OverThreshold(signReferenceTime);
386  if (deltaTime > fTransitionTime) {
387  if (over)
389  else
391  } else {
392  double deltaVoltage = deltaTime * fChannel.GetSlewRate();
393  double o = prevOutput;
394  if (over)
395  o += deltaVoltage;
396  else
397  o -= deltaVoltage;
398  return ApplySaturation(o);
399  }
400  }
401 
404  const
405  {
406  return fRawRoots.begin();
407  }
408 
411  const
412  {
413  return fRawRoots.end();
414  }
415 
416  void
418  {
419  /*
420  * Divide the interval and look up for root in each interval
421  * individually.
422  */
423  const double absTol = fChannel.GetAbsoluteError();
424  const double relTol = absTol / std::max( std::fabs(fBeginTime), std::fabs(fEndTime) );
425  const double gap = fChannel.GetInitialIntervalLength();
426  double currStart = fBeginTime;
427  double currEnd = currStart + gap;
428  double shift = 0.1*gap;//To overlap intervals
429 
431  double thr = fChannel.GetThreshold();
432  MyFunctionObject<double,Proxy<Callable> > functor(thr,f);
433 
434  //Solve in each interval
435  for ( ; currEnd <= fEndTime; currStart += gap, currEnd = currStart+gap ) {
436 
437  double f_min = functor( currStart-shift ) ;
438  double f_max = functor( currEnd ) ;
439 
440  try {
441  RootFinder( functor, fRawRoots, currStart-shift, currEnd, f_min, f_max, 0, relTol);
442  } catch(const std::exception& e) {
443  // Always useful to include try & catch blocks because default policies
444  // are to throw exceptions on arguments that cause errors like underflow, overflow.
445  // Lacking try & catch blocks, the program will abort without a message below,
446  // which may give some helpful clues as to the cause of the exception.
447  //if( verbose > 1 ) std::cout << "\nMessage from thrown exception was:\n " << e.what() << std::endl;
448  }//end try
449  }//end for intervals
450 
451  /*
452  * Sort: needed for the following duplicate removal and also because
453  * the operator() assumes the roots to be sorted.
454  */
455  std::sort(fRawRoots.begin(), fRawRoots.end());
456  // Given that we may continue subdiving the intervals, some root can be found several times.
457  RawRootsContainer::iterator last = std::unique_copy(
458  fRawRoots.begin(),
459  fRawRoots.end(),
460  fRawRoots.begin(),
461  EqualPredicate(relTol) // create a functor for comparison
462  );
463  // Get rid of the remaining non-important elements.
464  fRawRoots.erase(last, fRawRoots.end());
465  // Preallocate:
466  fRoots.reserve(fRawRoots.size());
467  // Now fill the field with Root (time + output).
468  double prevTime = fBeginTime;
469  // Take the low as the starting level (see operator()(double t)).
470  double prevOutput = fChannel.GetDiscriminatorLowLevel();
471  // (These 2 last values could be stored in our structure Root,
472  // but I feel that to be misleading).
473  /*
474  * Computing the roots is not the whole history, since the device has a finite-time
475  * response we have to do some calculations so as to know if the roots are separated
476  * enough (or not) for the device output signal change to the final desired value.
477  */
478  for (RawRootsContainer::const_iterator i = fRawRoots.begin(), e = fRawRoots.end(); i != e; ++i) {
479  Root r;
480  r.fTime = *i;
481  const double delta = r.fTime - prevTime;
482  // See the sign half way to the previous
483  const double halfTime = ( prevTime + r.fTime ) / 2;
484  r.fOutput = ComputeOutput(halfTime, delta, prevOutput);
485  fRoots.push_back(r);
486  // Update for the next loop.
487  prevTime = r.fTime;
488  prevOutput = r.fOutput;
489  }
490  }
491 
493  fChannel(d.fChannel),
494  fInputPulse(d.fInputPulse->Clone()), // This is the important one, the rest are just a copy-construction.
497  fEndTime(d.fEndTime),
498  fRoots(d.fRoots)
499  {
500  }
501 
502  double
504  const
505  {
506  /*
507  * Note that the real device may have a finite response time
508  * (in the sense of detecting the signal over the threshold).
509  * Given this, the real device won't start the transition
510  * as soon as the signal crosses the threshold.
511  * So far this hasn't been modeled. The changes needed may
512  * need in these lines:
513  * - Call this finite time 'response'
514  * - While calculating the roots (in the init method) perform
515  * an analysis in the neighborhood: [root, root + response]
516  * - If the time over / under the threshold (depending of the sign
517  * of the slope in the root) is not enough, the root may be
518  * discarded.
519  * - If the time is enough, the root maybe taken as root + response;
520  * so as to start the transition when the required time is over.
521  * - This shift seems to be enough to take into account the response
522  * time, and the algorithm may stay the same.
523  */
524  /*
525  * In general, for this method we take that
526  * initially (say in "turned-off" state)
527  * the output is at the low level.
528  */
529  // Special case without roots
530  if (fRoots.empty()) {
531  double delta = t - fBeginTime;
532  // 1st arg: Take the reference time for sign as the beginTime (any time will do).
533  // 2nd arg: The delta, since there's no root, against the starting time.
534  // 3rd arg: For the previous output take the low state (see comment at the beginning).
535  return ComputeOutput(fBeginTime, delta, fChannel.GetDiscriminatorLowLevel());
536  }
537  RootComp comp; // Comparator.
538  Root key; // Dummy object to use for searching: only the time is relevant.
539  key.fTime = t;
540  /*
541  * Find the lower bound for the given time
542  * "The second version of lower_bound returns the furthermost iterator i in [first, last)
543  * such that, for every iterator j in [first, i), comp(*j, value) is true."
544  * See http://www.sgi.com/tech/stl/lower_bound.html.
545  *
546  * So the iterator points to the first root that comes after the given time.
547  */
548  RootIterator i = std::lower_bound(fRoots.begin(), fRoots.end(), key, comp);
549  // Difference time against the time of a root (or the start).
550  double deltaTime;
551  // Reference output taken as starting point to compute the value at the desired time.
552  double prevOutput;
553  if (i == fRoots.end()) {
554  // The time comes after the last root.
555  deltaTime = t - fRoots.back().fTime;
556  prevOutput = fRoots.back().fOutput;
557  } else if (i == fRoots.begin()) {
558  // The time comes before the first root: so take difference against the starting time.
559  deltaTime = t - fBeginTime;
560  // Take as starting in the low level (see initial comment).
561  prevOutput = fChannel.GetDiscriminatorLowLevel();
562  } else {
563  // See the previous root, that's the
564  // inmediately to the left of the given time.
565  RootIterator prev = i - 1;
566  deltaTime = t - prev->fTime;
567  prevOutput = prev->fOutput;
568  }
569  // The sign is checked for the given time (first argument).
570  return ComputeOutput(t, deltaTime, prevOutput);
571  }
572 
573 
574  template<class Iterator>
575  double
576  Channel::Discriminator::ComputeBorderTime(Iterator beg, Iterator end, double time)
577  const
578  {
579  /*
580  * Note that strictly speaking the roots may only touch zero, ie they are an extrema.
581  * So maybe none of the conditions hold, and that would mean that we wouldn't initialize
582  * this variable in the following: so give it a value.
583  * Important: note that in case we return this zero, both calls to this functions
584  * (using in-order or reversed iterators) would return that zero. So there's
585  * no problem, that's exactly what we need: if never the conditions holds that
586  * would meant that all the roots are maxima, so actually the function never crosses
587  * (tough it touches) zero. So, when calculating the difference between both calls
588  * to this function we would obtain 0, which is correct.
589  * Note that if all the roots are minima, then at least the first condition over
590  * time (which isn't a root in both calls to this function) would be satisfied, and
591  * so the return value initilized with that time.
592  */
593  double borderTime = 0;
594  if (OverThreshold(time))
595  borderTime = time;
596  else {
597  /*
598  * Note that if there are no roots, then zero is returned: see comment at the beginning.
599  * Also, if there are no roots, and the signal is over, then we would have entered
600  * the 'then' condition of this if-then-else.
601  */
602  double prevTime = time;
603  for (Iterator i = beg; i != end; ++i) {
604  double t = i->fTime;
605  // Check the sign halfway between these two representative times: 't' is always
606  // a root and 'prevTime' is always a root but in the first loop.
607  double test = (prevTime + t) / 2;
608  if (OverThreshold(test)) {
609  // If it's over the threshold, then it started doing so in the previous root.
610  borderTime = prevTime;
611  break;
612  }
613  // Keep the current root for the following loop.
614  prevTime = t;
615  }
616  }
617  return borderTime;
618  }
619 
620  double
622  const
623  {
624  double earliestTime = ComputeBorderTime(fRoots.begin(), fRoots.end(), fBeginTime);
625  // Note that we're using here that we have a ReversibleContainer, which is the case.
626  double latestTime = ComputeBorderTime(fRoots.rbegin(), fRoots.rend(), fEndTime);
627  return latestTime - earliestTime;
628  }
629 
630  double
631  Channel::Discriminator::operator()(double *x, double * /*p*/)
632  const
633  {
634  return (*fInputPulse)(x[0]) - fChannel.GetThreshold();
635  }
636 
637 }
double ComputeOutput(double signReferenceTime, double deltaTime, double prevOutput) const
Compute final output for a give time difference.
AtThresholdConstIterator AtThresholdBegin() const
Begin iterator over times.
static const char *const kComponentName
T * Clone(const T &src)
Clone a given object.
Definition: ShadowPtr.h:69
utl::Validated< double > fSlewRate
utl::Validated< double > fResponseTime
utl::Validated< double > fSignalShiftStdDev
utl::Validated< double > fLowCutoffFrequency
RandomEngineType & GetEngine()
Definition: RandomEngine.h:32
utl::Validated< double > fDiscriminatorLowLevel
int freq
Definition: dump1090.h:244
const Channel & fChannel
The channel where to apply the discrimination.
bool OverThreshold(double t) const
Check input agains threshold.
static const char *const kComponentsNames[13]
det::DetectorComponent< C, MManagerProvider > Type
Type specializing det::DetectorComponent for Muon hierarchy.
double GetInitialIntervalLength() const
Lenght of the intervals (in time) in which divide the whole interval where roots are looked for...
void Init()
Perform initialization.
unsigned int GetMaxNumberOfErrors() const
Maximum number of errors admited without loggin when solving the equation threshold == input...
RawRootsContainer fRawRoots
Plain root value (only times).
double ComputeSignalShift() const
Computes a signal shift value according this Channel&#39;s characteristics (and, particularly, to the distribution of these values).
static const char *const kComponentsIds[13]
double GetFeedbackResistance() const
The resistance within the feedback loop.
Helper class encapsulating the discriminator response logic.
Electronic front-end for the modules.
Definition: FrontEnd.h:33
double ComputeBorderTime(Iterator beg, Iterator end, double time) const
Helper templated function to calculate the times that define the total span.
double fTransitionTime
Maximum time required to switch between states.
virtual ~Callable()
The deletion of the objects will be made through a Callable*.
T & GetData(P< T > &d, const std::string &p) const
Common utility function for configuration.
RootContainer fRoots
Once and for all calculate the roots given the input pulse.
double GetResponseTime() const
Response time of the discriminator associated with the channel.
double GetHighCutoffFrequency() const
Hi end of frequency response.
utl::Validated< double > fThreshold
utl::Validated< double > fSignalShiftMean
#define max(a, b)
double GetDCGain() const
A dimensionless quantity signaling the DC gain.
constexpr double s
Definition: AugerUnits.h:163
double GetSlewRate() const
Voltage change per time unit.
Channel(int cId, const det::VManager::IndexMap &parentMap, const FrontEnd &parent)
Constructs the electronic channel.
double fTime
The x value (that&#39;s the root of the equation input == threshold).
Wraps the random number engine used to generate distributions.
Definition: RandomEngine.h:27
std::complex< double > ComputeTransfer(double freq) const
Computes the circuit transfer function at the given frequency.
utl::Validated< int > fMaxNumberOfErrors
static const char *const kComponentId
utl::Validated< int > fIterationsNumber
double fOutput
The y value (that&#39;s the output voltage while in the associated time).
Templated specialization of the mdet::Channel::Discriminator::Callable interface. ...
double eps
utl::Validated< double > fAbsoluteError
RawRootsContainer::const_iterator AtThresholdConstIterator
Iterator over the times when the input signal reaches the threshold level.
double GetDiscriminatorLowLevel() const
Target voltage when the signal is lower than the threshold.
utl::Validated< double > fDCGain
double GetSignalShiftMean() const
Retrieves the mean shift.
double GetSignalShiftStdDev() const
The standard deviation of the shift.
double fBeginTime
Define the relevant interval start time.
const VManager::Status f
double GetOverThresholdTimeSpan() const
Return the total time span over the discrimination threshold.
double GetInvertingInputResistance() const
The resistance attached to the inverting input.
unsigned int GetIterationsNumber() const
Number of iterations allowed for the root finding procedures.
AtThresholdConstIterator AtThresholdEnd() const
End iterator over times.
std::map< std::string, std::string > IndexMap
Definition: VManager.h:133
const std::unique_ptr< Callable > fInputPulse
The input pulse where to apply the discrimination.
double GetLowCutoffFrequency() const
Low end of frequency response.
double GetAbsoluteError() const
Absolute error for root finding.
utl::Validated< double > fFeedbackResistance
double operator()(double t) const
Evaluate the discriminator output given the input pulse.
std::complex< double > ComputeFrequencyFactor(double freq, double freqLimit) const
Helper method to compute the factor (in the transfer function) that depends on frequency.
RootContainer::const_iterator RootIterator
Iterator over our roots struct.
double ApplySaturation(double v) const
Compute the final output for a given difference agains the threshold.
utl::Validated< double > fHighCutoffFrequency
utl::Validated< double > fInvertingInputResistance
utl::Validated< double > fInitialIntervalLength
double GetThreshold() const
Discrimination threshold.
double fEndTime
Define the relevant interval final time.
utl::Validated< double > fDiscriminatorHiLevel
Discriminator(const Discriminator &d)
Copy.
double GetDiscriminatorHiLevel() const
Target voltage when the signal is higher than the threshold.

, generated on Tue Sep 26 2023.