All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SampledFunction.h
Go to the documentation of this file.
1 /**
2  * @file icarusalg/Utilities/SampledFunction.h
3  * @brief Class for a function with precomputed values.
4  * @author Gianluca Petrillo (petrillo@slac.stanford.edu)
5  * @date February 14, 2020
6  *
7  * This is a header-only library.
8  */
9 
10 #ifndef ICARUSALG_UTILITIES_SAMPLEDFUNCTION_H
11 #define ICARUSALG_UTILITIES_SAMPLEDFUNCTION_H
12 
13 // LArSoft libraries
16 
17 // C++ core guideline library
18 #include "gsl/span"
19 #include "gsl/gsl_util" // gsl::index
20 
21 // C++ standard library
22 #include <vector>
23 #include <functional> // std::function<>
24 #include <limits> // std::numeric_limits<>
25 #include <cmath> // std::isnormal()
26 #include <cassert>
27 
28 
29 // ---------------------------------------------------------------------------
30 namespace util {
31  template <typename XType, typename YType> class SampledFunction;
32 } // namespace util
33 
34 /**
35  * @brief Precomputed discrete sampling of a given function.
36  * @tparam XType (default: `double`) type of value accepted by the function
37  * @tparam YType (default: as `XType`) type of value returned by the function
38  *
39  * This object contains the sampling of a specified function at regular values
40  * of its variable.
41  *
42  * If the `size()` of the sampling is requested to be _N_, there will be a
43  * sampling of _N_ values covering the specified range in steps of the same
44  * length, last value excluded.
45  * The sampling happens at the beginning of each step.
46  *
47  * In addition, subsampling can be requested. If _M_ subsamples are requested,
48  * the first step is split in _M_ points and from each one a sampling of _N_
49  * steps is started, causing overall _M N_ samples to be computed.
50  *
51  *
52  * Requirements
53  * -------------
54  *
55  * The function must be unary.
56  *
57  *
58  * Technical note
59  * ---------------
60  *
61  * The _M_ subsamples are stored each one contiguously.
62  * Therefore a function with _M_ subsamples of size _N_ is different, at least
63  * in storage, from a function with a single sampling (no subsamples) of size
64  * _M N_.
65  *
66  * @note Due to the implementation of `gsl::span`, its iterators are valid only
67  * while the span object is valid as well.
68  * This means that a construct like:
69  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
70  * for (auto it = sf.subsample(0).begin(); it != sf.subsample(0).end(); ++it)
71  * // ...
72  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
73  * will mysteriously fail at run time because `it` refers to a temporary
74  * span that quickly falls out of scope (and the end iterator refers to
75  * a different span object, too). The correct pattern is to store the
76  * subsample result before iterating through it:
77  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
78  * auto const& subsample = sf.subsample(0);
79  * for (auto it = subsample.begin(); it != subsample.end(); ++it)
80  * // ...
81  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
82  * or, if appliable, let the range-for loop di that for us:
83  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp}
84  * for (auto value: sf.subsample(0))
85  * // ...
86  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
87  * This will not be the case any more with `std::span`, which apparently
88  * is going to satisfy the `borrowed_range` requirement.
89  *
90  */
91 template <typename XType = double, typename YType = XType>
93  public:
94 
95  using X_t = XType; ///< Type of value accepted by the function.
96  using Y_t = YType; ///< Type of value returned by the function.
97  using Function_t = std::function<Y_t(X_t)>; ///< Type of sampled function.
98 
99  /// Invalid index of sample, returned in case of error.
100  static constexpr auto npos = std::numeric_limits<gsl::index>::max();
101 
102  /// Span of subsample data. Can be forward iterated.
103  using SubsampleData_t = gsl::span<Y_t const>;
104 
105  SampledFunction() = default; // FIXME remove this
106 
107  /**
108  * @brief Constructor: samples `function` in the specified range.
109  * @tparam Func type of a functor (see requirements below)
110  * @param function the function to be sampled
111  * @param lower the lower limit of the range to be sampled
112  * @param upper the upper limit of the range to be sampled
113  * @param nSamples the number (_N_) of samples to be computed
114  * @param subsamples (default: `1`) the number (_M_) of subsamples to be
115  * computed
116  *
117  * The sampling of `function` is performed on `nSamples` points from `lower`
118  * to `upper` (excluded).
119  *
120  * The `function` parameter type `Func` need to be a unary functor, i.e. it
121  * must support a call of type `function(X_t) const` returning some value
122  * convertible to `Y_t`. Plain C functions and closures also work.
123  *
124  * The `function` is not copied nor retained in any form, so it can be from
125  * a temporary object.
126  */
127  template <typename Func>
128  SampledFunction(Func const& function,
129  X_t lower, X_t upper,
130  gsl::index nSamples,
131  gsl::index subsamples = 1
132  );
133 
134 
135  // @{
136  /**
137  * @brief Constructor: samples `function` in the specified range.
138  * @tparam UntilFunc type of functor indicating when to stop sampling
139  * @param function the function to be sampled
140  * @param lower the lower limit of the range to be sampled
141  * @param step the sampling interval
142  * @param until functor to indicate when to stop sampling
143  * @param subsamples (default: `1`) the number (_M_) of subsamples to be
144  * computed
145  * @param min_upper (default: none) minimum value covered by the sampling
146  *
147  * The sampling of `function` is performed from `lower`, advancing by `step`
148  * at each following sample, until the `until` functor returns `true`.
149  * If `min_upper` is specified, regardless of the result of `until`, samples
150  * below `min_upper` are always covered.
151  *
152  * The functor `until` should be callable as in `bool until(X_t x, Y_t y)`,
153  * and should return `false` if the sample of value `y`, corresponding to the
154  * evaluation point `x`, needs to be sampled, and `true` if instead that
155  * sample needs to be discarded, and the sampling stopped. For example,
156  * to apply a threshold so that sampling stops when the function is 0.1,
157  * `until` can be defined as `[](X_t, Y_t s){ return s >= Y_t{0.1}; }` (`x`
158  * is ignored).
159  *
160  * Subsampling is performed based on the `subsamples` argument.
161  */
162  template <typename Func, typename UntilFunc>
163  SampledFunction(Func const& function,
164  X_t lower, X_t step, UntilFunc&& until,
165  gsl::index subsamples,
166  X_t min_upper
167  );
168  template <typename Func, typename UntilFunc>
169  SampledFunction(Func const& function,
170  X_t lower, X_t step, UntilFunc&& until,
171  gsl::index subsamples = 1
172  );
173  // @}
174 
175 
176 
177  // --- BEGIN --- Query -------------------------------------------------------
178  /// @name Query
179  /// @{
180 
181  // @{
182  /// Returns the number of samples (in each subsample).
183  gsl::index size() const { return fNSamples; }
184  // @}
185 
186  // @{
187  /// Returns the number of subsamples.
188  gsl::index nSubsamples() const { return fNSubsamples; }
189  // @}
190 
191  // @{
192  /// Returns the lower limit of the covered range.
193  X_t lower() const { return fLower; }
194  // @}
195 
196  // @{
197  /// Returns the upper limit of the covered range.
198  X_t upper() const { return fUpper; }
199  // @}
200 
201  // @{
202  /// Returns the extension of the covered range.
203  X_t rangeSize() const { return upper() - lower(); }
204  // @}
205 
206  // @{
207  /// Returns the extension of a step.
208  X_t stepSize() const { return fStep; }
209  // @}
210 
211  // @{
212  /// Returns the base offset of the subsamples.
213  X_t substepSize() const { return stepSize() / nSubsamples(); }
214  // @}
215 
216  /// @}
217  // --- END --- Query ---------------------------------------------------------
218 
219 
220  // --- BEGIN --- Access ------------------------------------------------------
221  /// @name Access to the sampled data
222  /// @{
223 
224  // @{
225  /// Returns the `iSample` value of the subsample with the specified index `n`.
226  Y_t value(gsl::index iSample, gsl::index n = 0U) const
227  { return subsampleData(n)[iSample]; }
228  // @}
229 
230 
231  // @{
232  /// Returns the data of the subsample with the specified index `n`.
233  SubsampleData_t subsample(gsl::index const n) const
234  { return { subsampleData(n), static_cast<std::size_t>(fNSamples) }; }
235  // @}
236 
237  // @{
238  /**
239  * @brief Returns the index of the step including `x`.
240  * @param x the argument to the function
241  * @param iSubsample the index of the subsample
242  * @return the index of step including `x`, or `npos` if none does
243  *
244  * This function returns the index of the sample whose step includes `x`.
245  * A step includes its lower limit but not its upper limit, which usually
246  * belongs to the next step (or it does not belong to any valid step).
247  * If there is no step including `x`, the index of the would-be step is
248  * returned (it can be checked e.g. with `isValidStepIndex()`).
249  */
250  gsl::index stepIndex(X_t const x, gsl::index const iSubsample) const;
251  // @}
252 
253  // @{
254  /// Returns whether the specified step index is valid.
255  bool isValidStepIndex(gsl::index const index) const
256  { return (index >= 0) && (index < size()); }
257  // @}
258 
259  // @{
260  /**
261  * @brief Returns the subsample closest to the value `x`.
262  * @param x value to be checked
263  * @return the index of the subsample found
264  *
265  * The subsample with the bin including `x` whose lower bound is the closest
266  * to `x` itself is returned.
267  *
268  * For example, assuming bins aligned with 0 and a sampling with steps of
269  * size 1 and 5 subsamples, there will be 5 bins contaning the value `x` 3.65:
270  * [ 3.0, 4.0 ], [ 3.2, 4.2 ], [ 3.4, 4.4 ], [ 3.6, 4.6 ] and [ 2.8, 3.8 ],
271  * one for each subsample: `closestSubsampleIndex(3.65)` will return the
272  * sample with the bin [ 3.6, 4.6 ] (that is the fourth one, i.e. subsample
273  * number 3), because its lower bound 3.6 is the closest to 3.65.
274  *
275  * The value `x` does not need to be in the sampling range. In the example
276  * above, the range could have been between 0 and 2, and the result would be
277  * the same.
278  */
279  gsl::index closestSubsampleIndex(X_t x) const;
280  // @}
281 
282  /// @}
283  // --- END --- Access --------------------------------------------------------
284 
285  /// Dumps the full content of the sampling into `out` stream.
286  template <typename Stream>
287  void dump
288  (Stream&& out, std::string const& indent, std::string const& firstIndent)
289  const;
290 
291  /// Dumps the full content of the sampling into `out` stream.
292  template <typename Stream>
293  void dump(Stream&& out, std::string const& indent = "") const
294  { dump(std::forward<Stream>(out), indent, indent); }
295 
296 
297  private:
298 
299  /// Record used during initialization.
300  struct Range_t {
303  gsl::index nSamples;
304  }; // Range_t
305 
306  X_t fLower; ///< Lower limit of sampled range.
307  X_t fUpper; ///< Upper limit of sampled range.
308  gsl::index fNSamples; ///< Number of samples in the range.
309  gsl::index fNSubsamples; ///< Number of subsamples.
310 
311  X_t fStep; ///< Step size.
312 
313  /// All samples, the entire first subsample first.
314  std::vector<Y_t> fAllSamples;
315 
316  /// Constructor implementation.
318  (Function_t const& function, Range_t const& range, gsl::index subsamples);
319 
320  /// Returns the starting point of the subsample `n`.
321  X_t subsampleOffset(gsl::index n) const
322  { return lower() + substepSize() * n; }
323 
324 
325  // @{
326  /// Start of the block of values for subsample `n` (unchecked).
327  Y_t const* subsampleData(gsl::index n) const
328  { return fAllSamples.data() + fNSamples * n; }
329  Y_t* subsampleData(gsl::index n) { return fAllSamples.data() + fNSamples * n; }
330  // @}
331 
332  /// Computes the total size of the data.
333  std::size_t computeTotalSize() const { return nSubsamples() * size(); }
334 
335 
336  /// Returns a range including at least from `lower` to `min_upper`,
337  /// extended enough that `until(upper, f(upper))` is `true`, and with an
338  /// integral number of steps.
339  template <typename UntilFunc>
340  static Range_t extendRange(
341  Function_t const& function, X_t lower, X_t min_upper, X_t step,
342  UntilFunc&& until
343  );
344 
345  /// Samples the `function` and fills the internal caches.
346  void fillSamples(Function_t const& function);
347 
348 
349  /// Returns `value` made non-negative by adding multiples of `range`.
350  template <typename T>
351  static T wrapUp(T value, T range);
352 
353 }; // class util::SampledFunction<>
354 
355 
356 // template deduction guide:
357 namespace util {
358 
359  // when YType is not deducible (Clang 7.0.0 can't deduce it, GCC 8.2 can)
360  template <typename XType, typename Func, typename UntilFunc>
361  SampledFunction(Func const&, XType, XType, UntilFunc&&, gsl::index, XType)
363 
364  template <typename XType, typename Func>
365  SampledFunction(Func const&, XType, XType, gsl::index, gsl::index = 1)
367 
368  template <typename XType, typename Func, typename UntilFunc>
369  SampledFunction(Func const&, XType, XType, UntilFunc&&, gsl::index = 1)
371 
372 } // namespace util
373 
374 
375 // =============================================================================
376 // === template implementation
377 // =============================================================================
378 template <typename XType, typename YType>
379 template <typename Func>
381  Func const& function,
382  X_t lower, X_t upper,
383  gsl::index nSamples,
384  gsl::index subsamples
385  )
386  : SampledFunction(
387  Function_t(function),
388  Range_t{ lower, upper, (upper - lower) / nSamples, nSamples },
389  subsamples
390  )
391 {}
392 
393 
394 // -----------------------------------------------------------------------------
395 template <typename XType, typename YType>
396 template <typename Func, typename UntilFunc>
398  Func const& function,
399  X_t lower, X_t step, UntilFunc&& until,
400  gsl::index subsamples,
401  X_t min_upper
402  )
403  : SampledFunction(
404  Function_t(function),
406  (function, lower, min_upper, step, std::forward<UntilFunc>(until)),
407  subsamples
408  )
409 {}
410 
411 
412 // -----------------------------------------------------------------------------
413 template <typename XType, typename YType>
415  (X_t const x, gsl::index const iSubsample) const
416 {
417  auto const dx = static_cast<double>(x - subsampleOffset(iSubsample));
418  return static_cast<gsl::index>(std::floor(dx / stepSize()));
419 } // gsl::index util::SampledFunction<XType, YType>::stepIndex()
420 
421 
422 // -----------------------------------------------------------------------------
423 template <typename XType, typename YType>
425  (X_t const x) const
426 {
427  auto const dx = static_cast<double>(x - lower());
428  auto const step = static_cast<double>(stepSize());
429  auto const substep = static_cast<double>(substepSize());
430  return static_cast<gsl::index>(wrapUp(std::fmod(dx, step), step) / substep);
431 } // gsl::index util::SampledFunction<XType, YType>::stepIndex()
432 
433 
434 // -----------------------------------------------------------------------------
435 template <typename XType, typename YType>
436 template <typename Stream>
438  (Stream&& out, std::string const& indent, std::string const& firstIndent) const
439 {
440  out << firstIndent << "Function sampled from " << lower() << " to " << upper()
441  << " (extent: " << rangeSize() << ")"
442  << " with " << size() << " samples (" << stepSize() << " long)";
443  if (nSubsamples() > 1) {
444  out << " and " << nSubsamples() << " subsamples (" << substepSize()
445  << " long):";
446  }
447  for (auto const iSub: util::counter(nSubsamples())) {
448  out << "\n" << indent << "<subsample #" << iSub << ">:";
449  // FIXME with C++20's `std::span` temporary won't be needed any more
450  auto const& sub = subsample(iSub);
451  for (auto const [ i, sample ]: util::enumerate(sub))
452  out << " [" << i << "] " << sample;
453  } // for
454  out << "\n";
455 } // util::SampledFunction<>::dump()
456 
457 
458 // -----------------------------------------------------------------------------
459 template <typename XType, typename YType>
461  Function_t const& function,
462  Range_t const& range,
463  gsl::index subsamples
464  )
465  : fLower(range.lower)
466  , fUpper(range.upper)
467  , fNSamples(range.nSamples)
468  , fNSubsamples(subsamples)
469  , fStep(range.step)
470  , fAllSamples(computeTotalSize())
471 {
472  assert(fNSamples > 0);
473  assert(subsamples > 0);
474  fillSamples(function);
475 } // util::SampledFunction<>::SampledFunction(range)
476 
477 
478 // -----------------------------------------------------------------------------
479 template <typename XType, typename YType>
480 template <typename UntilFunc>
482  Function_t const& function, X_t lower, X_t min_upper, X_t step,
483  UntilFunc&& until
484  ) -> Range_t
485 {
486  assert(min_upper >= lower);
487  auto const startSamples
488  = static_cast<gsl::index>(std::ceil((min_upper - lower) / step));
489 
490  auto const endStep
491  = [lower, step](gsl::index iStep){ return lower + step * iStep; };
492 
493  Range_t r { lower, endStep(startSamples), step, startSamples };
494 
495  while (!until(r.upper + r.step, function(r.upper + r.step))) {
496  // upper + step is not too much: extend to there
497  ++r.nSamples;
498  r.upper = endStep(r.nSamples);
499  } // while
500 
501  return r;
502 } // util::SampledFunction<>::extendRange()
503 
504 
505 // -----------------------------------------------------------------------------
506 template <typename XType, typename YType>
508  (Function_t const& function)
509 {
510 
511  /*
512  * Plan:
513  * 0. rely on the currently stored size specifications (range and samples)
514  * 1. resize the data structure to the required size
515  * 2. fill all the subsamples, in sequence
516  *
517  */
518 
519  //
520  // 0. rely on the currently stored size specifications (range and samples)
521  //
522  std::size_t const dataSize = computeTotalSize();
523  assert(dataSize > 0U);
524  assert(fLower <= fUpper);
525  assert(fStep > X_t{0});
526 
527  //
528  // 1. resize the data structure to the required size
529  //
530  fAllSamples.resize(dataSize);
531 
532  //
533  // 2. fill all the subsamples, in sequence
534  //
535  auto iValue = fAllSamples.begin();
536  for (gsl::index const iSubsample: util::counter(nSubsamples())) {
537  X_t const offset = subsampleOffset(iSubsample);
538  for (gsl::index const iStep: util::counter(size())) {
539  X_t const x = offset + iStep * stepSize();
540  Y_t const y = function(x);
541  *iValue++ = y;
542  } // for steps
543  } // for subsamples
544 
545 } // util::SampledFunction<>::fillSamples()
546 
547 
548 // -----------------------------------------------------------------------------
549 template <typename XType, typename YType>
550 template <typename T>
552  while (value < T{ 0 }) value += range;
553  return value;
554 } // util::SampledFunction<>::wrapUp()
555 
556 
557 // -----------------------------------------------------------------------------
558 
559 
560 #endif // ICARUSALG_UTILITIES_SAMPLEDFUNCTION_H
double std(const std::vector< short > &wf, const double ped_mean, size_t start, size_t nsample)
Definition: UtilFunc.cxx:42
gsl::index size() const
Returns the number of samples (in each subsample).
gsl::index fNSamples
Number of samples in the range.
std::vector< Y_t > fAllSamples
All samples, the entire first subsample first.
BEGIN_PROLOG TPC Trig offset(g4 rise time) ProjectToHeight
Definition: CORSIKAGen.fcl:7
X_t subsampleOffset(gsl::index n) const
Returns the starting point of the subsample n.
X_t upper() const
Returns the upper limit of the covered range.
bool isValidStepIndex(gsl::index const index) const
Returns whether the specified step index is valid.
X_t lower() const
Returns the lower limit of the covered range.
X_t fLower
Lower limit of sampled range.
Y_t const * subsampleData(gsl::index n) const
Start of the block of values for subsample n (unchecked).
process_name opflash particleana ie x
Definition of util::enumerate().
gsl::index nSubsamples() const
Returns the number of subsamples.
static T wrapUp(T value, T range)
Returns value made non-negative by adding multiples of range.
nanoseconds X_t
Type of value accepted by the function.
gsl::index stepIndex(X_t const x, gsl::index const iSubsample) const
Returns the index of the step including x.
X_t substepSize() const
Returns the base offset of the subsamples.
X_t rangeSize() const
Returns the extension of the covered range.
static constexpr auto npos
Invalid index of sample, returned in case of error.
X_t stepSize() const
Returns the extension of a step.
Y_t value(gsl::index iSample, gsl::index n=0U) const
Returns the iSample value of the subsample with the specified index n.
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
Definition: enumerate.h:69
decltype(auto) constexpr size(T &&obj)
ADL-aware version of std::size.
Definition: StdUtils.h:92
gsl::index fNSubsamples
Number of subsamples.
void fillSamples(Function_t const &function)
Samples the function and fills the internal caches.
void dump(Stream &&out, std::string const &indent="") const
Dumps the full content of the sampling into out stream.
auto counter(T begin, T end)
Returns an object to iterate values from begin to end in a range-for loop.
Definition: counter.h:285
process_name opflash particleana ie ie y
gsl::index closestSubsampleIndex(X_t x) const
Returns the subsample closest to the value x.
Test of util::counter and support utilities.
SubsampleData_t subsample(gsl::index const n) const
Returns the data of the subsample with the specified index n.
std::size_t computeTotalSize() const
Computes the total size of the data.
static Range_t extendRange(Function_t const &function, X_t lower, X_t min_upper, X_t step, UntilFunc &&until)
ADCcount Y_t
Type of value returned by the function.
X_t fUpper
Upper limit of sampled range.
std::function< Y_t(X_t)> Function_t
Type of sampled function.
void dump(Stream &&out, std::string const &indent, std::string const &firstIndent) const
Dumps the full content of the sampling into out stream.
temporary value
Record used during initialization.
Precomputed discrete sampling of a given function.
gsl::span< Y_t const > SubsampleData_t
Span of subsample data. Can be forward iterated.
esac echo uname r
Y_t * subsampleData(gsl::index n)
bnb BNB Stream
std::string sub(const std::string &a, const std::string &b)
Definition: TruthText.cxx:100