All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SampledWaveformFunctionTool_tool.cc
Go to the documentation of this file.
1 /**
2  * @file icaruscode/PMT/SampledWaveformFunctionTool_tool.cc
3  * @brief Toolization of `icarus::opdet::SampledWaveformFunction<nanosecond>`.
4  * @author Gianluca Petrillo (petrillo@slac.stanford.edu)
5  * @date March 17, 2020
6  * @see `icaruscode/PMT/Algorithms/SampledWaveformFunction.h`
7  *
8  * This is an implementation of tool interface
9  * `icarus::opdet::SinglePhotonPulseFunctionTool`.
10  */
11 
12 
13 // ICARUS libraries
18 
19 // LArSoft libraries
21 #include "lardataalg/Utilities/quantities_fhicl.h" // nanoseconds from FHiCL
22 #include "lardataalg/Utilities/quantities.h" // util::quantities::makeQuantity()
24 
25 // framework libraries
26 #include "art/Utilities/ToolConfigTable.h"
27 #include "art/Utilities/ToolMacros.h"
28 #include "canvas/Utilities/Exception.h"
29 #include "fhiclcpp/types/OptionalAtom.h"
30 #include "fhiclcpp/types/Atom.h"
31 #include "cetlib/search_path.h"
32 #include "cetlib_except/exception.h"
33 
34 // C/C++ standard libraries
35 #include <memory> // std::unique_ptr()
36 #include <cassert>
37 
38 
39 //------------------------------------------------------------------------------
40 namespace icarus::opdet { struct SampledWaveformFunctionTool; }
41 /**
42  * @brief Creates a `SampledWaveformFunction` pulse shape.
43  * @see `icarus::opdet::SinglePhotonPulseFunctionTool`
44  *
45  * This tool creates a `icarus::opdet::SampledWaveformFunction<nanosecond>`
46  * function to describe a R5912 PMT pulse attached to the ICARUS detector.
47  *
48  * See `icarus::opdet::SampledWaveformFunction` for the details of the function.
49  *
50  *
51  * Waveform specification file format
52  * -----------------------------------
53  *
54  * The response must be described in a plain text file following the syntax from
55  * `icarus::details::KeyValueParser`.
56  * The following fields are supported:
57  *
58  * * `"FileFormat"` (integer, implied: `1`) represents the version of the data
59  * format, i.e. the list of the supported fields and their meaning.
60  * Currently it's only a formal parameter, which is ignored.
61  * * `"Name"` (string): short name identifying this response
62  * * `"Description"` (string): the description of this response; may be long and
63  * spanning multiple lines (always adhering `icarus::details::KeyValueParser`
64  * syntax)
65  * * `"Date"` (string): the date of this response; free-form.
66  * * `"Version"` (positive integer): the version of this response; it may
67  * describe updates for the same response `Name`.
68  * * `"Tick"` (time quantity string, mandatory): the duration of one tick in the
69  * response sampling, in time quantity format (e.g. `"2 ns"` or "0.4 us").
70  * * `"Gain"` (real number): the PMT gain associated to this response.
71  * If provided, it will allow rescaling to different gains.
72  * * `"Samples"` (sequence of samples in mV, mandatory): values of the samples
73  * in the response, one per tick. No reference time is needed (the algorithms
74  * will look for the peak sample to be used as reference time).
75  * * `"NSamples"` (positive integer): the number of samples in the `Samples`
76  * array; used to validate the input.
77  *
78  *
79  * Configuration
80  * --------------
81  *
82  * Run `lar --print-description SampledWaveformFunctionTool` (or read `Config`
83  * data structure) for a short explanation of the meaning of the parameters.
84  *
85  * * `TransitTime` (time, mandatory): time from the arrival of a photoelectron
86  * to the surface of the photodetector to the peak of the signal that the
87  * photodetector produces. It must include the unit (e.g. `"51.5 ns"`).
88  * * `WaveformData` (path, mandatory): path to the file with the complete
89  * information about the single photoelectron response. The file is searched
90  * for in `FW_SEARCH_PATH` path
91  * * `Gain` (real, optional): if specified, the input must provide the nominal
92  * gain of the response, and that response will be rescaled from its gain
93  * value to the one specified with this parameter. If not specified,
94  * the response is used as is.
95  *
96  */
99 {
100 
101  /// Configuration parameters.
102  struct Config {
103 
104  using Name = fhicl::Name;
105  using Comment = fhicl::Comment;
106 
107  fhicl::Atom<std::string> WaveformData {
108  Name("WaveformData"),
109  Comment("Path to the data file with the SPR information")
110  // mandatory
111  };
112  fhicl::Atom<nanoseconds> TransitTime {
113  Name("TransitTime"),
114  Comment("peak time from the beginning of the waveform [ns]")
115  // mandatory
116  };
117  fhicl::OptionalAtom<float> Gain {
118  Name("Gain"),
119  Comment("PMT amplification gain factor")
120  };
121 
122  }; // struct Config
123 
124 
125  /// Tool parameter configuration.
126  using Parameters = art::ToolConfigTable<Config>;
127 
128  /// Constructor: sets the configuration.
130  : fPulseFunction(makePulseFunction(config())) {}
131 
132 
133  private:
134 
135  /// The actual function type we offer.
137 
138 
139  // --- BEGIN -- Virtual interface --------------------------------------------
140 
141  /// Returns the function that was created at construction time.
142  virtual std::unique_ptr<PulseFunction_t> doGetPulseFunction() override
143  { return std::move(fPulseFunction); }
144 
145  // --- END -- Virtual interface ----------------------------------------------
146 
147  /// Function stored while waiting to be delivered.
148  std::unique_ptr<PulseFunction_t> fPulseFunction;
149 
150 
151  /// Creates and returns a pulse function with the specified configuration.
152  static std::unique_ptr<PulseFunction_t> makePulseFunction
153  (Config const& config);
154 
155  /// Parses the specified file and returns the information on the SPR waveform.
157  (std::string const& path);
158 
159 }; // icarus::opdet::SampledWaveformFunctionTool
160 
161 
162 //------------------------------------------------------------------------------
163 //--- icarus::opdet::SampledWaveformFunctionTool implementation
164 //------------------------------------------------------------------------------
165 
167  (std::string const& path) -> MyFunction_t::WaveformSpecs_t
168 {
169  //
170  // text file parsing
171  //
172  std::ifstream srcFile { path };
173  if (!srcFile.is_open()) {
174  // quite strange, actually, since the file was found by `cet::search_path`
175  throw art::Exception{ art::errors::FileReadError }
176  << "Can't open single photoelectron response file '" << path << "'\n";
177  }
178 
180  icarus::KeyValuesData const data { parser(srcFile) };
181  srcFile.close();
182 
183  //
184  // interpretation
185  //
186 
187  auto makeException = [path]()
188  {
189  return cet::exception{ "SampledWaveformFunctionTool" }
190  << "in '" << path << "': ";
191  };
192 
194 
195  if (auto const* item = data.findItem("Name")) {
196  if (item->nValues() != 1) {
197  throw makeException() << "'Name' must have exactly 1 entry, not "
198  << item->nValues() << "!\n";
199  }
200  specs.name = item->value();
201  }
202 
203  if (auto const* item = data.findItem("Description")) {
204  if (item->nValues() != 1) {
205  throw makeException()
206  << "'Description' must have exactly 1 entry (possibly quoted), not "
207  << item->nValues() << "!\n";
208  }
209  specs.description = item->value();
210  }
211 
212  if (auto const* item = data.findItem("Date")) {
213  if (item->nValues() != 1) {
214  throw makeException()
215  << "'Date' must have exactly 1 entry (possibly quoted), not "
216  << item->nValues() << "!\n";
217  }
218  specs.date = item->value();
219  }
220 
221  if (auto const* item = data.findItem("Version")) {
222  if (item->nValues() != 1) {
223  throw makeException()
224  << "'Version' must have exactly 1 entry, not " << item->nValues()
225  << "!\n";
226  }
227  try {
228  specs.version = item->getNumber<unsigned int>(0);
229  }
230  catch (icarus::KeyValuesData::Error const& e) {
231  throw makeException() << "value in 'Version' ('" << item->value()
232  << "') can't be interpreted as version number (unsigned int):\n"
233  << e.what() << "\n";
234  }
235  }
236 
237  if (auto const* item = data.findItem("Tick")) {
238  if (item->nValues() != 1) {
239  throw makeException()
240  << "'Date' must have exactly 1 entry (possibly quoted), not "
241  << item->nValues() << "!\n";
242  }
243  try {
244  specs.sampleDuration
245  = util::quantities::makeQuantity<nanoseconds>(item->value());
246  }
247  catch(std::runtime_error const& e) {
248  throw makeException()
249  << "Failed to parse 'Tick' ('" << item->value() << "') as a time:\n"
250  << e.what() << "\n";
251  }
252  }
253  else throw makeException() << "'Tick' entry is mandatory.\n";
254 
255  if (auto const* item = data.findItem("Gain")) {
256  if (item->nValues() != 1) {
257  throw makeException()
258  << "'Gain' must have exactly 1 entry, not " << item->nValues() << "!\n";
259  }
260  try {
261  specs.gain = item->getNumber<float>(0);
262  }
263  catch (icarus::KeyValuesData::Error const& e) {
264  throw makeException() << "value in 'Gain' ('" << item->value()
265  << "') can't be interpreted as a gain factor:\n"
266  << e.what() << "\n";
267  }
268  }
269 
270 
271  if (auto const* item = data.findItem("Samples")) {
272  if (item->nValues() < 2) {
273  throw makeException()
274  << "'Samples' has only " << item->nValues() << " values!!\n";
275  }
276  try {
277  specs.samples = item->getVector<float>();
278  }
279  catch(icarus::KeyValuesData::Error const& e) {
280  throw makeException()
281  << "Error converting values in 'Samples' into voltage:\n"
282  << e.what() << "\n";
283  }
284  }
285  else throw makeException() << "'Samples' entry is mandatory.\n";
286 
287 
288  if (auto const* item = data.findItem("NSamples")) {
289  if (item->nValues() != 1) {
290  throw makeException()
291  << "'NSamples' must have exactly 1 entry, not " << item->nValues()
292  << "!\n";
293  }
294  unsigned int nSamples;
295  try {
296  nSamples = item->getNumber<unsigned int>(0);
297  }
298  catch (icarus::KeyValuesData::Error const& e) {
299  throw makeException() << "value in 'NSamples' ('" << item->value()
300  << "') can't be interpreted as a sample number (unsigned int):\n"
301  << e.what() << "\n";
302  }
303  if (specs.samples.size() != nSamples) {
304  throw makeException() << "'Samples' has " << specs.samples.size()
305  << " values, but according to 'NSamples' there should be " << nSamples
306  << "!\n";
307  }
308  }
309 
310 
311  return specs;
312 } // icarus::opdet::SampledWaveformFunctionTool::extractWaveformSpecification()
313 
314 //------------------------------------------------------------------------------
316  (Config const& config) -> std::unique_ptr<PulseFunction_t>
317 {
318 
319  //
320  // find and process the waveform information file
321  //
322  cet::search_path searchPath{ "FW_SEARCH_PATH" };
323  std::string waveformSpecsPath;
324  try {
325  waveformSpecsPath = searchPath.find_file(config.WaveformData());
326  }
327  catch (cet::exception& e) {
328  throw art::Exception{ art::errors::Configuration, "", e }
329  << "Error looking for the waveform data file '" << config.WaveformData()
330  << "' (configured via: '" << config.WaveformData.name() << "')\n";
331  }
332 
333  MyFunction_t::WaveformSpecs_t waveformSpecs
334  = extractWaveformSpecification(waveformSpecsPath);
335 
336  //
337  // fix the gain request
338  //
339  if ((config.Gain().value_or(0.0) != 0.0) && (waveformSpecs.gain == 0.0)) {
340  throw art::Exception(art::errors::Configuration)
341  << "The single photoelectron response '" << waveformSpecs.name
342  << "' at '" << waveformSpecsPath
343  << "' does not specify a base gain, so it can't be rescaled to "
344  << config.Gain().value()
345  << " ('" << config.Gain.name() << "' parameter)\n";
346  }
347  float const reqGain = (config.Gain().value_or(0.0) != 0.0)
348  ? config.Gain().value(): waveformSpecs.gain;
349 
350  //
351  // create the algorithm
352  //
353  return std::make_unique<MyFunction_t>(
354  std::move(waveformSpecs) // waveformSpecs
355  , config.TransitTime() // peakTime
356  , reqGain // gain
357  );
358 
359 } // icarus::opdet::SampledWaveformFunctionTool::makePulseFunction()
360 
361 
362 //------------------------------------------------------------------------------
363 DEFINE_ART_CLASS_TOOL(icarus::opdet::SampledWaveformFunctionTool)
364 
365 
366 //------------------------------------------------------------------------------
367 
Pulse from one photoelectron as a train of samples.
virtual std::unique_ptr< PulseFunction_t > doGetPulseFunction() override
Returns the function that was created at construction time.
Simple parser for &quot;key: value&quot; text.
Creates a SampledWaveformFunction pulse shape.
art::ToolConfigTable< Config > Parameters
Tool parameter configuration.
Describes the waveform from a single photoelectron.
std::unique_ptr< PulseFunction_t > fPulseFunction
Function stored while waiting to be delivered.
static MyFunction_t::WaveformSpecs_t extractWaveformSpecification(std::string const &path)
Parses the specified file and returns the information on the SPR waveform.
Dimensioned variables representing electromagnetic quantities.
Tool to create a pulse function for PMT single photon response.
std::string date
An indication of the date of the waveform.
BEGIN_PROLOG triggeremu_data_config_icarus settings PMTADCthresholds sequence::icarus_stage0_multiTPC_TPC physics sequence::icarus_stage0_EastHits_TPC physics sequence::icarus_stage0_WestHits_TPC physics producers purityana0 caloskimCalorimetryCryoE physics caloskimCalorimetryCryoW physics path
SampledWaveformFunctionTool(Parameters const &config)
Constructor: sets the configuration.
Simple parsed data format.
Creates a PhotoelectronPulseFunction pulse shape.
Utilities to read and write quantities in FHiCL configuration.
Collection of items with key/values structure.
static std::unique_ptr< PulseFunction_t > makePulseFunction(Config const &config)
Creates and returns a pulse function with the specified configuration.
std::string description
Description of this waveform.
Parser to fill a KeyValuesData structure out of a character buffer.
BEGIN_PROLOG vertical distance to the surface Name
Test of util::counter and support utilities.
Numeric variable proxies with embedded unit of measurement.
do i e