LArSoft  v07_13_02
Liquid Argon Software toolkit - http://larsoft.org/
RandomNumberGenerator_service.cc
Go to the documentation of this file.
1 // ======================================================================
2 //
3 // Maintain multiple independent random number engines, including
4 // saving and restoring state.
5 //
6 // ======================================================================
7 //
8 // Notes
9 // -----
10 //
11 // 0) These notes predate this version.
12 // TODO: review/apply/update these notes as/when time permits.
13 //
14 // 1) The CMS code on which is this modelled is available at
15 // http://cmslxr.fnal.gov/lxr/source/IOMC/RandomEngine/src/RandomNumberGenerator.cc
16 //
17 // 2) CLHEP specifies that state will be returned as vector<unsigned long>.
18 // The size of a long is machine dependent. If unsigned long is an
19 // 8 byte variable, only the least significant 4 bytes are filled
20 // and the most significant 4 bytes are zero. We need to store the
21 // state with a machine independent size, which we choose to be
22 // uint32_t. This conversion really belongs in the
23 // RandomEngineState class but we are at the moment constrained by
24 // the framework's HepRandomGenerator interface.
25 //
26 // ======================================================================
27 
29 
30 #include "CLHEP/Random/DRand48Engine.h"
31 #include "CLHEP/Random/DualRand.h"
32 #include "CLHEP/Random/Hurd160Engine.h"
33 #include "CLHEP/Random/Hurd288Engine.h"
34 #include "CLHEP/Random/JamesRandom.h"
35 #include "CLHEP/Random/MTwistEngine.h"
36 #include "CLHEP/Random/NonRandomEngine.h"
37 #include "CLHEP/Random/Random.h"
38 #include "CLHEP/Random/RanecuEngine.h"
39 #include "CLHEP/Random/Ranlux64Engine.h"
40 #include "CLHEP/Random/RanluxEngine.h"
41 #include "CLHEP/Random/RanshiEngine.h"
42 #include "CLHEP/Random/TripleRand.h"
49 #include "cetlib/assert_only_one_thread.h"
50 #include "cetlib/container_algorithms.h"
51 #include "cetlib/no_delete.h"
52 #include "cetlib_except/exception.h"
54 
55 #include <algorithm>
56 #include <atomic>
57 #include <cassert>
58 #include <fstream>
59 #include <string>
60 #include <vector>
61 
62 using art::RNGsnapshot;
64 using art::ScheduleID;
66 using std::ifstream;
67 using std::ofstream;
68 using std::string;
69 
70 // ======================================================================
71 
72 namespace {
73 
74  using RNGservice = RandomNumberGenerator;
75  using eptr_t = RNGservice::eptr_t;
76  using label_t = RNGservice::label_t;
77  using seed_t = RNGservice::seed_t;
78  using base_engine_t = RNGservice::base_engine_t;
79 
80  string const DEFAULT_ENGINE_KIND{"HepJamesRandom"};
81  seed_t constexpr MAXIMUM_CLHEP_SEED{900000000};
82  seed_t constexpr USE_DEFAULT_SEED{-1};
83  RNGsnapshot const EMPTY_SNAPSHOT;
84 
85  struct G4Engine {
86  };
87 
88  void
89  throw_if_invalid_seed(seed_t const seed)
90  {
91  if (seed == USE_DEFAULT_SEED)
92  return;
93  if (seed > MAXIMUM_CLHEP_SEED)
94  throw cet::exception("RANGE")
95  << "RNGservice::throw_if_invalid_seed():\n"
96  "Seed "
97  << seed << " exceeds permitted maximum " << MAXIMUM_CLHEP_SEED << ".\n";
98  if (seed < 0) // too small for CLHEP
99  throw cet::exception("RANGE")
100  << "RNGservice::throw_if_invalid_seed():\n"
101  "Seed "
102  << seed << " is not permitted to be negative.\n";
103  }
104 
105  inline label_t
106  qualify_engine_label(ScheduleID::size_type const schedule_id,
107  label_t const& engine_label)
108  {
109  // ModuleLabel:ScheduleID:EngineLabel
110  std::string label{art::ServiceHandle<art::CurrentModule const>{}->label()};
111  label += ':';
112  label += std::to_string(schedule_id);
113  label += ':';
114  label += engine_label;
115  return label;
116  }
117 
118  template <class DesiredEngineType>
119  inline eptr_t
120  manufacture_an_engine(seed_t const seed)
121  {
122  return eptr_t{seed == USE_DEFAULT_SEED ? new DesiredEngineType :
123  new DesiredEngineType(seed)};
124  }
125 
126  template <>
127  inline eptr_t manufacture_an_engine<CLHEP::NonRandomEngine>(
128  seed_t /* unused */)
129  {
130  // no engine c'tor takes a seed:
131  return std::make_shared<CLHEP::NonRandomEngine>();
132  }
133 
134  template <>
135  inline eptr_t
136  manufacture_an_engine<G4Engine>(seed_t const seed)
137  {
138  if (seed != USE_DEFAULT_SEED)
139  CLHEP::HepRandom::setTheSeed(seed);
140 
141  return eptr_t{CLHEP::HepRandom::getTheEngine(), cet::no_delete{}};
142  }
143 
144  eptr_t
145  engine_factory(string const& kind_of_engine_to_make, seed_t const seed)
146  {
147 #define MANUFACTURE_EXPLICIT(KIND, TYPE) \
148  if (kind_of_engine_to_make == string{KIND}) \
149  return manufacture_an_engine<TYPE>(seed);
150  MANUFACTURE_EXPLICIT("G4Engine", G4Engine)
151 #define MANUFACTURE_IMPLICIT(ENGINE) \
152  MANUFACTURE_EXPLICIT(#ENGINE, CLHEP::ENGINE)
153  MANUFACTURE_IMPLICIT(DRand48Engine)
154  MANUFACTURE_IMPLICIT(DualRand)
155  MANUFACTURE_IMPLICIT(Hurd160Engine)
156  MANUFACTURE_IMPLICIT(Hurd288Engine)
157  MANUFACTURE_IMPLICIT(HepJamesRandom)
158  MANUFACTURE_IMPLICIT(MTwistEngine)
159  MANUFACTURE_IMPLICIT(NonRandomEngine)
160  MANUFACTURE_IMPLICIT(RanecuEngine)
161  MANUFACTURE_IMPLICIT(Ranlux64Engine)
162  MANUFACTURE_IMPLICIT(RanluxEngine)
163  MANUFACTURE_IMPLICIT(RanshiEngine)
164  MANUFACTURE_IMPLICIT(TripleRand)
165 #undef MANUFACTURE_IMPLICIT
166 #undef MANUFACTURE_EXPLICIT
167 
168  throw cet::exception("RANDOM")
169  << "engine_factory():\n"
170  "Attempt to create engine of unknown kind \""
171  << kind_of_engine_to_make << "\".\n";
172  } // engine_factory()
173 
174  void
175  expand_if_abbrev_kind(string& requested_engine_kind)
176  {
177  if (requested_engine_kind.empty() ||
178  requested_engine_kind == "DefaultEngine" ||
179  requested_engine_kind == "JamesRandom") {
180  requested_engine_kind = DEFAULT_ENGINE_KIND;
181  }
182  }
183 
184 } // namespace
185 
186 // ======================================================================
187 
188 RNGservice::RandomNumberGenerator(Parameters const& config,
190  : restoreStateLabel_{config().restoreStateLabel()}
191  , saveToFilename_{config().saveTo()}
192  , restoreFromFilename_{config().restoreFrom()}
193  , nPrint_{config().nPrint()}
194  , debug_{config().debug()}
195 {
196  reg.sPostBeginJob.watch(this, &RNGservice::postBeginJob);
197  reg.sPostEndJob.watch(this, &RNGservice::postEndJob);
198  reg.sPreProcessEvent.watch(this, &RNGservice::preProcessEvent);
199 
200  // MT-TODO: Placeholder until we can query number of schedules.
201  unsigned const nSchedules{1u};
202  data_.resize(nSchedules);
203 }
204 
205 // ----------------------------------------------------------------------
206 
208 RNGservice::getEngine() const
209 {
210  return getEngine(label_t{});
211 }
212 
214 RNGservice::getEngine(label_t const& engine_label) const
215 {
216  // Place holder until we can use a system that provides the right context.
217  auto const schedule_id = ScheduleID::first();
218  return getEngine(schedule_id, engine_label);
219 }
220 
222 RNGservice::getEngine(ScheduleID const schedule_id,
223  label_t const& engine_label) const
224 {
225  // Place holder until we can use a system that provides the right context.
226  auto const sid = schedule_id.id();
227  label_t const& label = qualify_engine_label(sid, engine_label);
228 
229  auto d = data_[sid].dict_.find(label);
230  if (d == data_[sid].dict_.end()) {
231  throw cet::exception("RANDOM") << "RNGservice::getEngine():\n"
232  "The requested engine \""
233  << label << "\" has not been established.\n";
234  }
235  assert(d->second && "RNGservice::getEngine()");
236 
237  return *d->second;
238 }
239 
240 // ======================================================================
241 
242 bool
243 RNGservice::invariant_holds_(ScheduleID::size_type const schedule_id)
244 {
245  auto const& d = data_[schedule_id];
246  return d.dict_.size() == d.tracker_.size() &&
247  d.dict_.size() == d.kind_.size();
248 }
249 
250 // ----------------------------------------------------------------------
251 
253 RNGservice::createEngine(ScheduleID const schedule_id, seed_t const seed)
254 {
255  return createEngine(schedule_id, seed, DEFAULT_ENGINE_KIND);
256 }
257 
259 RNGservice::createEngine(ScheduleID const schedule_id,
260  seed_t const seed,
261  string const& requested_engine_kind)
262 {
263  return createEngine(schedule_id, seed, requested_engine_kind, label_t{});
264 }
265 
267 RNGservice::createEngine(ScheduleID const schedule_id,
268  seed_t const seed,
269  string requested_engine_kind,
270  label_t const& engine_label)
271 {
272  // If concurrrent engine creation is desired...even within a
273  // schedule, then concurrent containers should be used.
274  CET_ASSERT_ONLY_ONE_THREAD();
275 
276  auto const sid = schedule_id.id();
277  assert(sid < data_.size());
278 
279  label_t const& label = qualify_engine_label(sid, engine_label);
280 
282  throw cet::exception("RANDOM") << "RNGservice::createEngine():\n"
283  "Attempt to create engine \""
284  << label << "\" is too late.\n";
285  }
286 
287  auto& d = data_[sid];
288  if (d.tracker_.find(label) != d.tracker_.cend()) {
289  throw cet::exception("RANDOM") << "RNGservice::createEngine():\n"
290  "Engine \""
291  << label << "\" has already been created.\n";
292  }
293 
294  throw_if_invalid_seed(seed);
295  expand_if_abbrev_kind(requested_engine_kind);
296  eptr_t eptr{engine_factory(requested_engine_kind, seed)};
297  assert(eptr && "RNGservice::createEngine()");
298  d.dict_[label] = eptr;
299  d.tracker_[label] = VIA_SEED;
300  d.kind_[label] = requested_engine_kind;
301 
302  mf::LogInfo log{"RANDOM"};
303  log << "Instantiated " << requested_engine_kind << " engine \"" << label
304  << "\" with ";
305  if (seed == USE_DEFAULT_SEED)
306  log << "default seed " << seed;
307  else
308  log << "seed " << seed;
309  log << ".\n";
310 
311  assert(invariant_holds_(sid) && "RNGservice::createEngine()");
312  return *eptr;
313 
314 } // createEngine<>()
315 
316 // ----------------------------------------------------------------------
317 // The 'print_()' does not receive a schedule ID as an argument since
318 // it is only intended for debugging purposes. Because of this, it is
319 // possible that data races can occur in a multi-threaded context.
320 // Since the only user of 'print_' is the RandomNumberSaver module, we
321 // place the burden of synchronizing on that module, and not on this
322 // service, which could be expensive.
323 void
324 RNGservice::print_() const
325 {
326  CET_ASSERT_ONLY_ONE_THREAD();
327 
328  static unsigned ncalls{};
329 
330  if (!debug_ || ++ncalls > nPrint_)
331  return;
332 
333  auto print_per_stream = [](std::size_t const i, auto const& d) {
334  mf::LogInfo log{"RANDOM"};
335  if (d.snapshot_.empty()) {
336  log << "No snapshot has yet been made.\n";
337  return;
338  }
339 
340  log << "Snapshot information:";
341  for (auto const& ss : d.snapshot_) {
342  log << "\nEngine: " << ss.label() << " Kind: " << ss.ekind()
343  << " Schedule ID: " << i << " State size: " << ss.state().size();
344  }
345  log << "\n";
346  };
347 
348  cet::for_all_with_index(data_, print_per_stream);
349 }
350 
351 // ----------------------------------------------------------------------
352 
353 void
354 RNGservice::takeSnapshot_(ScheduleID const schedule_id)
355 {
356  mf::LogDebug log{"RANDOM"};
357  log << "RNGservice::takeSnapshot_() of the following engine labels:\n";
358 
359  auto& d = data_[schedule_id.id()];
360  d.snapshot_.clear();
361  for (auto const& pr : d.dict_) {
362  label_t const& label = pr.first;
363  eptr_t const& eptr = pr.second;
364  assert(eptr && "RNGservice::takeSnapshot_()");
365 
366  d.snapshot_.emplace_back(d.kind_[label], label, eptr->put());
367  log << " | " << label;
368  }
369  log << " |\n";
370 }
371 
372 // ----------------------------------------------------------------------
373 
374 void
375 RNGservice::restoreSnapshot_(ScheduleID const schedule_id,
376  art::Event const& event)
377 {
378  if (restoreStateLabel_.empty())
379  return;
380 
381  // access the saved-states product:
382  using saved_t = std::vector<RNGsnapshot>;
383  auto const& saved = *event.getValidHandle<saved_t>(restoreStateLabel_);
384 
385  auto& d = data_[schedule_id.id()];
386 
387  // restore engines from saved-states product:
388  for (auto const& snapshot : saved) {
389  label_t const& label = snapshot.label();
390  mf::LogInfo log("RANDOM");
391  log << "RNGservice::restoreSnapshot_(): label \"" << label << "\"";
392 
393  auto t = d.tracker_.find(label);
394  if (t == d.tracker_.end()) {
395  log << " could not be restored;\n"
396  "no established engine bears this label.\n";
397  continue;
398  }
399 
400  if (t->second == VIA_FILE) {
401  throw cet::exception("RANDOM")
402  << "RNGservice::restoreSnapshot_():\n"
403  "The state of engine \""
404  << label
405  << "\" has been previously read from a file;\n"
406  "it is therefore not restorable from a snapshot product.\n";
407  }
408 
409  eptr_t ep{d.dict_[label]};
410  assert(ep && "RNGservice::restoreSnapshot_()");
411 
412  d.tracker_[label] = VIA_PRODUCT;
413  auto const& est = snapshot.restoreState();
414  if (ep->get(est)) {
415  log << " successfully restored.\n";
416  } else {
417  throw cet::exception("RANDOM")
418  << "RNGservice::restoreSnapshot_():\n"
419  "Failed during restore of state of engine for \""
420  << label << "\"\n";
421  }
422  } // for
423 
424  assert(invariant_holds_(schedule_id.id()) &&
425  "RNGsnapshot::restoreSnapshot_()");
426 } // restoreSnapshot_()
427 
428 // ----------------------------------------------------------------------
429 void
430 RNGservice::saveToFile_()
431 {
432  if (saveToFilename_.empty())
433  return;
434 
435  CET_ASSERT_ONLY_ONE_THREAD();
436 
437  // access the file:
438  ofstream outfile{saveToFilename_.c_str()};
439  if (!outfile)
440  mf::LogWarning("RANDOM")
441  << "Can't create/access file \"" << saveToFilename_ << "\"\n";
442 
443  // save each engine:
444  for (auto const& d : data_) {
445  for (auto const& pr : d.dict_) {
446  outfile << pr.first << '\n';
447  auto const& eptr = pr.second;
448  assert(eptr && "RNGservice::saveToFile_()");
449 
450  eptr->put(outfile);
451  if (!outfile)
452  mf::LogWarning("RANDOM")
453  << "This module's engine has not been saved;\n"
454  "file \""
455  << saveToFilename_ << "\" is likely now corrupted.\n";
456  }
457  }
458 } // saveToFile_()
459 
460 // ----------------------------------------------------------------------
461 void
462 RNGservice::restoreFromFile_()
463 {
464  if (restoreFromFilename_.empty())
465  return;
466 
467  CET_ASSERT_ONLY_ONE_THREAD();
468 
469  // access the file:
470  ifstream infile{restoreFromFilename_.c_str()};
471  if (!infile)
472  throw cet::exception("RANDOM")
473  << "RNGservice::restoreFromFile_():\n"
474  "Can't open file \""
475  << restoreFromFilename_ << "\" to initialize engines\n";
476 
477  // restore engines:
478  for (label_t label{}; infile >> label;) {
479  // Get schedule ID from engine label
480  assert(std::count(label.cbegin(), label.cend(), ':') == 2u);
481  auto const p1 = label.find_first_of(':');
482  auto const p2 = label.find_last_of(':');
483  ScheduleID const schedule_id(std::stoul(label.substr(p1 + 1, p2)));
484  auto& data = data_[schedule_id.id()];
485 
486  auto d = data.dict_.find(label);
487  if (d == data.dict_.end()) {
488  throw Exception(errors::Configuration, "RANDOM")
489  << "Attempt to restore an engine with label " << label
490  << " not configured in this job.\n";
491  }
492 
493  assert(data.tracker_.find(label) != data.tracker_.cend() &&
494  "RNGservice::restoreFromFile_()");
495  init_t& how{data.tracker_[label]};
496  if (how == VIA_SEED) { // OK
497  auto& eptr = d->second;
498  assert(eptr && "RNGservice::restoreFromFile_()");
499  if (!eptr->get(infile)) {
500  throw cet::exception("RANDOM")
501  << "RNGservice::restoreFromFile_():\n"
502  << "Failed during restore of state of engine for label " << label
503  << "from file \"" << restoreFromFilename_ << "\"\n";
504  }
505  how = VIA_FILE;
506  } else if (how == VIA_FILE) {
507  throw Exception(errors::Configuration, "RANDOM")
508  << "Engine state file contains two engine states with the "
509  << "same label: " << label << "\n.";
510  } else {
511  throw Exception(errors::LogicError, "RANDOM")
512  << "Internal error: attempt to restore an engine state " << label
513  << " from file\nwhich was originally initialized via an "
514  << " unknown or impossible method.\n";
515  }
516  assert(invariant_holds_(schedule_id.id()) &&
517  "RNGservice::restoreFromFile_()");
518  }
519 } // restoreFromFile_()
520 
521 // ----------------------------------------------------------------------
522 
523 void RNGservice::postBeginJob() // Consider changing this to preBeginJob
524 {
526  engine_creation_is_okay_ = false;
527 }
528 
529 void
530 RNGservice::preProcessEvent(art::Event const& e)
531 {
532  auto const schedule_id = ScheduleID::first();
533  takeSnapshot_(schedule_id);
534  restoreSnapshot_(schedule_id, e);
535 }
536 
537 void
538 RNGservice::postEndJob()
539 {
540  // For normal termination, we wish to save the state at the *end* of
541  // processing, not at the beginning of the last event.
542 
543  // MT-TODO: Adjust so that the loop does not require creating
544  // temporary schedule IDs, but instead uses a system-provided
545  // looping mechanism.
546  for (ScheduleID::size_type i{}; i < data_.size(); ++i) {
548  }
549  saveToFile_();
550 }
551 
552 // ======================================================================
#define MANUFACTURE_IMPLICIT(ENGINE)
GlobalSignal< detail::SignalResponseType::FIFO, void()> sPostBeginJob
std::shared_ptr< base_engine_t > eptr_t
#define MANUFACTURE_EXPLICIT(KIND, TYPE)
#define DEFINE_ART_SERVICE(svc)
Definition: ServiceMacros.h:93
GlobalSignal< detail::SignalResponseType::LIFO, void()> sPostEndJob
std::string const restoreFromFilename_
void restoreSnapshot_(ScheduleID scheduleID, art::Event const &)
bool invariant_holds_(ScheduleID::size_type scheduleID)
Float_t ss
Definition: plot.C:23
constexpr id_type id() const
Definition: ScheduleID.h:70
static ScheduleID first()
Definition: ScheduleID.h:82
base_engine_t & getEngine() const
base_engine_t & createEngine(ScheduleID schedule_id, seed_t seed)
std::vector< ScheduleData > data_
long seed
Definition: chem4.cc:68
Float_t d
Definition: plot.C:237
CLHEP::HepRandomEngine base_engine_t
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
std::string to_string(Flag_t< Storage > const flag)
Convert a flag into a stream (shows its index).
Definition: BitMask.h:187
id_type size_type
Definition: ScheduleID.h:27
GlobalSignal< detail::SignalResponseType::FIFO, void(Event const &)> sPreProcessEvent
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
void takeSnapshot_(ScheduleID scheduleID)
Float_t e
Definition: plot.C:34
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
Event finding and building.