LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
RandomNumberGenerator.cc
Go to the documentation of this file.
2 // vim: set sw=2 expandtab :
3 
4 // ======================================================================
5 // Maintain multiple independent random number engines, including
6 // saving and restoring state.
7 // ======================================================================
8 
9 #include "CLHEP/Random/DRand48Engine.h"
10 #include "CLHEP/Random/DualRand.h"
11 #include "CLHEP/Random/Hurd160Engine.h"
12 #include "CLHEP/Random/Hurd288Engine.h"
13 #include "CLHEP/Random/JamesRandom.h"
14 #include "CLHEP/Random/MTwistEngine.h"
15 #include "CLHEP/Random/MixMaxRng.h"
16 #include "CLHEP/Random/NonRandomEngine.h"
17 #include "CLHEP/Random/Random.h"
18 #include "CLHEP/Random/RanecuEngine.h"
19 #include "CLHEP/Random/Ranlux64Engine.h"
20 #include "CLHEP/Random/RanluxEngine.h"
21 #include "CLHEP/Random/RanshiEngine.h"
22 #include "CLHEP/Random/TripleRand.h"
26 #include "art/Utilities/Globals.h"
30 #include "cetlib/container_algorithms.h"
31 #include "cetlib_except/exception.h"
32 #include "hep_concurrency/assert_only_one_thread.h"
34 
35 #include <algorithm>
36 #include <cassert>
37 #include <cstddef>
38 #include <fstream>
39 #include <string>
40 #include <vector>
41 
42 using namespace std;
43 using namespace string_literals;
44 using namespace hep::concurrency;
46 
47 namespace art {
48 
49  long constexpr RandomNumberGenerator::maxCLHEPSeed; // if not MixMaxRng
50  long constexpr RandomNumberGenerator::useDefaultSeed;
51 
52  namespace {
53 
54  string
55  qualify_engine_label(ScheduleID const sid,
56  string const& module_label,
57  string const& engine_label)
58  {
59  // Format is ModuleLabel:scheduleID:EngineLabel
60  string label;
61  label += module_label;
62  label += ':';
63  label += std::to_string(sid.id());
64  label += ':';
65  label += engine_label;
66  return label;
67  }
68 
69  template <class DesiredEngineType>
70  shared_ptr<CLHEP::HepRandomEngine>
71  manufacture_an_engine(long const seed)
72  {
73  shared_ptr<CLHEP::HepRandomEngine> ret;
74  if (seed == RandomNumberGenerator::useDefaultSeed) {
75  ret.reset(new DesiredEngineType);
76  } else {
77  ret.reset(new DesiredEngineType(seed));
78  }
79  return ret;
80  }
81 
82  shared_ptr<CLHEP::HepRandomEngine>
83  engine_factory(string const& kind_of_engine_to_make, long const seed)
84  {
85 #define MANUFACTURE(ENGINE) \
86  if (kind_of_engine_to_make == string{#ENGINE}) { \
87  return manufacture_an_engine<CLHEP::ENGINE>(seed); \
88  }
89  MANUFACTURE(DRand48Engine)
90  MANUFACTURE(DualRand)
91  MANUFACTURE(Hurd160Engine)
92  MANUFACTURE(Hurd288Engine)
93  MANUFACTURE(HepJamesRandom)
94  MANUFACTURE(MixMaxRng)
95  MANUFACTURE(MTwistEngine)
96  MANUFACTURE(RanecuEngine)
97  MANUFACTURE(Ranlux64Engine)
98  MANUFACTURE(RanluxEngine)
99  MANUFACTURE(RanshiEngine)
100  MANUFACTURE(TripleRand)
101 #undef MANUFACTURE
102  throw cet::exception("RANDOM")
103  << "engine_factory():\n"
104  << "Attempt to create engine of unknown kind \""
105  << kind_of_engine_to_make << "\".\n";
106  }
107 
108  } // unnamed namespace
109 
110  bool
111  RandomNumberGenerator::invariant_holds_(ScheduleID const sid)
112  {
113  std::lock_guard sentry{mutex_};
114  return (data_[sid].dict_.size() == data_[sid].tracker_.size()) &&
115  (data_[sid].dict_.size() == data_[sid].kind_.size());
116  }
117 
118  RandomNumberGenerator::RandomNumberGenerator(Parameters const& config,
119  ActivityRegistry& actReg)
120  : defaultEngineKind_{config().defaultEngineKind()}
121  , restoreStateLabel_{config().restoreStateLabel()}
122  , saveToFilename_{config().saveTo()}
123  , restoreFromFilename_{config().restoreFrom()}
124  , debug_{config().debug()}
125  , nPrint_{config().nPrint()}
126  {
127  actReg.sPostBeginJob.watch(this, &RandomNumberGenerator::postBeginJob);
128  actReg.sPostEndJob.watch(this, &RandomNumberGenerator::postEndJob);
129  actReg.sPreProcessEvent.watch(this,
131  data_.resize(Globals::instance()->nschedules());
132  }
133 
134  CLHEP::HepRandomEngine&
136  std::string const& module_label,
137  long const seed,
138  string const& requested_engine_kind,
139  string const& engine_label)
140  {
141  std::lock_guard sentry{mutex_};
143  throw cet::exception("RANDOM")
144  << "RNGservice::createEngine():\n"
145  << "Attempt to create engine \"" << engine_label << "\" is too late.\n";
146  }
147  if (sid.id() >= data_.size()) {
148  throw cet::exception("RANDOM")
149  << "RNGservice::createEngine():\n"
150  << "Attempt to create engine with out-of-range ScheduleID: " << sid
151  << '\n';
152  }
153  string const& label = qualify_engine_label(sid, module_label, engine_label);
154  if (data_[sid].tracker_.find(label) != data_[sid].tracker_.cend()) {
155  throw cet::exception("RANDOM")
156  << "RNGservice::createEngine():\n"
157  << "Engine \"" << label << "\" has already been created.\n";
158  }
159  string engineKind{requested_engine_kind};
160  if (requested_engine_kind.empty()) {
161  engineKind = defaultEngineKind_;
162  }
163 
164  validate_(engineKind, seed);
165 
166  shared_ptr<CLHEP::HepRandomEngine> eptr;
167  if (engineKind == "G4Engine"s) {
168  eptr = engine_factory(defaultEngineKind_, seed);
169  // We set CLHEP's random-number engine to be of type
170  // defaultEngineKind_.
171  CLHEP::HepRandom::setTheEngine(eptr.get());
172  if (seed != RandomNumberGenerator::useDefaultSeed) {
173  CLHEP::HepRandom::setTheSeed(seed);
174  }
175  } else if (engineKind == "NonRandomEngine"s) {
176  eptr = std::make_shared<CLHEP::NonRandomEngine>();
177  } else {
178  eptr = engine_factory(engineKind, seed);
179  }
180  if (!eptr) {
181  throw cet::exception("RANDOM")
182  << "RNGservice::createEngine():\n"
183  << "Engine \"" << label << "\" could not be created.\n";
184  }
185  data_[sid].dict_[label] = eptr;
186  data_[sid].tracker_[label] = EngineSource::Seed;
187  data_[sid].kind_[label] = engineKind;
188  mf::LogInfo{"RANDOM"} << "Instantiated " << engineKind << " engine \""
189  << label << "\" with "
190  << ((seed == useDefaultSeed) ? "default seed " :
191  "seed ")
192  << seed << '.';
193  assert(invariant_holds_(sid) &&
194  "RNGservice::createEngine() invariant failed");
195  return *eptr;
196  }
197 
198  void
200  std::string const& user_specified_engine_kind,
201  long const user_specified_seed) noexcept(false)
202  {
203  // The only time a user-specified seed can be negative is for
204  // indicating to this service that the default seed for the
205  // requested engine kind should be used.
206  if (user_specified_seed == useDefaultSeed)
207  return;
208 
209  if (user_specified_seed < 0) {
210  throw cet::exception("RANGE") << "RNGservice::throw_if_invalid_seed():\n"
211  << "Seed " << user_specified_seed
212  << " is not permitted to be negative.\n";
213  }
214 
215  if (user_specified_seed <= maxCLHEPSeed)
216  return;
217 
218  // For now, only MixMaxRng engines can be constructed with a seed
219  // value greater than maxCLHEPSeed.
220  if (user_specified_engine_kind == "MixMaxRng"s)
221  return;
222 
223  if (user_specified_engine_kind == "G4Engine"s &&
224  defaultEngineKind_ == "MixMaxRng"s)
225  return;
226 
227  throw cet::exception("RANGE")
228  << "RNGservice::throw_if_invalid_seed():\n"
229  << "Seed " << user_specified_seed << " exceeds permitted maximum of "
230  << maxCLHEPSeed << " for engine type " << user_specified_engine_kind
231  << ".\n";
232  }
233 
234  void
236  {
237  std::lock_guard sentry{mutex_};
238  static unsigned ncalls = 0;
239  if (!debug_ || (++ncalls > nPrint_)) {
240  return;
241  }
242  auto print_per_stream = [](size_t const i, auto const& d) {
243  mf::LogInfo log{"RANDOM"};
244  if (d.snapshot_.empty()) {
245  log << "No snapshot has yet been made.\n";
246  return;
247  }
248  log << "Snapshot information:";
249  for (auto const& ss : d.snapshot_) {
250  log << "\nEngine: " << ss.label() << " Kind: " << ss.ekind()
251  << " Schedule ID: " << i << " State size: " << ss.state().size();
252  }
253  };
254  cet::for_all_with_index(data_, print_per_stream);
255  }
256 
257  vector<RNGsnapshot> const&
259  {
260  std::lock_guard sentry{mutex_};
261  return data_[sid].snapshot_;
262  }
263 
264  void
266  {
267  std::lock_guard sentry{mutex_};
268  mf::LogDebug log{"RANDOM"};
269  log << "RNGservice::takeSnapshot_() of the following engine labels:\n";
270  data_[sid].snapshot_.clear();
271  for (auto const& [label, eptr] : data_[sid].dict_) {
272  assert(eptr && "RNGservice::takeSnapshot_()");
273  data_[sid].snapshot_.emplace_back(
274  data_[sid].kind_[label], label, eptr->put());
275  log << " | " << label;
276  }
277  log << " |";
278  }
279 
280  void
282  Event const& event)
283  {
284  std::lock_guard sentry{mutex_};
285  if (restoreStateLabel_.empty()) {
286  return;
287  }
288  // access the saved-states product:
289  auto const& saved =
290  event.getProduct<vector<RNGsnapshot>>(restoreStateLabel_);
291  // restore engines from saved-states product:
292  for (auto const& snapshot : saved) {
293  string const& label = snapshot.label();
294  mf::LogInfo log("RANDOM");
295  log << "RNGservice::restoreSnapshot_(): label \"" << label << "\"";
296  auto t = data_[sid].tracker_.find(label);
297  if (t == data_[sid].tracker_.end()) {
298  log << " could not be restored;\n"
299  << "no established engine bears this label.\n";
300  continue;
301  }
302  if (t->second == EngineSource::File) {
303  throw cet::exception("RANDOM")
304  << "RNGservice::restoreSnapshot_():\n"
305  << "The state of engine \"" << label
306  << "\" has been previously read from a file;\n"
307  << "it is therefore not restorable from a snapshot product.\n";
308  }
309  shared_ptr<CLHEP::HepRandomEngine> ep{data_[sid].dict_[label]};
310  assert(ep && "RNGservice::restoreSnapshot_()");
311  data_[sid].tracker_[label] = EngineSource::Product;
312  auto const& est = snapshot.restoreState();
313  if (ep->get(est)) {
314  log << " successfully restored.\n";
315  } else {
316  throw cet::exception("RANDOM")
317  << "RNGservice::restoreSnapshot_():\n"
318  << "Failed during restore of state of engine for \"" << label
319  << "\"\n";
320  }
321  }
322  assert(invariant_holds_(sid) && "RNGsnapshot::restoreSnapshot_()");
323  }
324 
325  void
327  {
328  std::lock_guard sentry{mutex_};
329  if (saveToFilename_.empty()) {
330  return;
331  }
332  HEP_CONCURRENCY_ASSERT_ONLY_ONE_THREAD();
333  // access the file:
334  ofstream outfile{saveToFilename_.c_str()};
335  if (!outfile) {
336  mf::LogWarning("RANDOM")
337  << "Can't create/access file \"" << saveToFilename_ << "\"\n";
338  }
339  // save each engine:
340  for (auto const& d : data_) {
341  for (auto const& [label, eptr] : d.dict_) {
342  outfile << label << '\n';
343  assert(eptr && "RNGservice::saveToFile_()");
344  eptr->put(outfile);
345  if (!outfile) {
346  mf::LogWarning("RANDOM")
347  << "This module's engine has not been saved;\n"
348  << "file \"" << saveToFilename_ << "\" is likely now corrupted.\n";
349  }
350  }
351  }
352  }
353 
354  void
356  {
357  std::lock_guard sentry{mutex_};
358  if (restoreFromFilename_.empty()) {
359  return;
360  }
361  HEP_CONCURRENCY_ASSERT_ONLY_ONE_THREAD();
362  // access the file:
363  ifstream infile{restoreFromFilename_.c_str()};
364  if (!infile) {
365  throw cet::exception("RANDOM")
366  << "RNGservice::restoreFromFile_():\n"
367  << "Can't open file \"" << restoreFromFilename_
368  << "\" to initialize engines\n";
369  }
370  // restore engines:
371  for (string label{}; infile >> label;) {
372  // Get schedule ID from engine label
373  assert(count(label.cbegin(), label.cend(), ':') == 2u);
374  auto const p1 = label.find_first_of(':');
375  auto const p2 = label.find_last_of(':');
376  ScheduleID const sid{
377  static_cast<ScheduleID::size_type>(stoi(label.substr(p1 + 1, p2)))};
378  auto d = data_[sid].dict_.find(label);
379  if (d == data_[sid].dict_.end()) {
380  throw Exception(errors::Configuration, "RANDOM")
381  << "Attempt to restore an engine with label " << label
382  << " not configured in this job.\n";
383  }
384  assert((data_[sid].tracker_.find(label) != data_[sid].tracker_.cend()) &&
385  "RNGservice::restoreFromFile_()");
386  EngineSource& how{data_[sid].tracker_[label]};
387  if (how == EngineSource::Seed) {
388  auto& eptr = d->second;
389  assert(eptr && "RNGservice::restoreFromFile_()");
390  if (!eptr->get(infile)) {
391  throw cet::exception("RANDOM")
392  << "RNGservice::restoreFromFile_():\n"
393  << "Failed during restore of state of engine for label " << label
394  << "from file \"" << restoreFromFilename_ << '"';
395  }
396  how = EngineSource::File;
397  } else if (how == EngineSource::File) {
398  throw Exception(errors::Configuration, "RANDOM")
399  << "Engine state file contains two engine states with the same "
400  "label: "
401  << label << "\n.";
402  } else {
403  throw Exception(errors::LogicError, "RANDOM")
404  << "Internal error: attempt to restore an engine state " << label
405  << " from file\n"
406  << "which was originally initialized via an unknown or impossible "
407  "method.\n";
408  }
409  assert(invariant_holds_(sid) &&
410  "RNGservice::restoreFromFile_() invariant failure");
411  }
412  }
413 
414  // Consider changing this to preBeginJob
415  void
417  {
418  std::lock_guard sentry{mutex_};
420  engine_creation_is_okay_ = false;
421  }
422 
423  void
425  ScheduleContext const sc)
426  {
427  auto const sid = sc.id();
428  std::lock_guard sentry{mutex_};
429  takeSnapshot_(sid);
430  restoreSnapshot_(sid, e);
431  }
432 
433  void
435  {
436  // For normal termination, we wish to save the state at the *end* of
437  // processing, not at the beginning of the last event.
438  std::lock_guard sentry{mutex_};
439  ScheduleIteration iteration(data_.size());
440  iteration.for_each_schedule(
441  [this](ScheduleID const sid) { takeSnapshot_(sid); });
442  saveToFile_();
443  }
444 
445 } // namespace art
static long constexpr maxCLHEPSeed
CLHEP::HepRandomEngine & createEngine(ScheduleID sid, std::string const &module_label, long seed, std::string const &kind_of_engine_to_make, std::string const &engine_label={})
std::string const restoreFromFilename_
std::vector< RNGsnapshot > const & accessSnapshot_(ScheduleID) const
PerScheduleContainer< ScheduleData > data_
STL namespace.
void restoreSnapshot_(ScheduleID, Event const &)
#define MANUFACTURE(ENGINE)
static long constexpr useDefaultSeed
long seed
Definition: chem4.cc:67
decltype(auto) constexpr to_string(T &&obj)
ADL-aware version of std::to_string.
Float_t d
Definition: plot.C:235
void preProcessEvent(Event const &, ScheduleContext)
void validate_(std::string const &user_specified_engine_kind, long user_specified_seed) noexcept(false)
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
constexpr id_type id() const noexcept
Definition: ScheduleID.h:77
id_type size_type
Definition: ScheduleID.h:25
Float_t sc
Definition: plot.C:23
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
Definition: MVAAlg.h:12
void for_each_schedule(F f) const
static Globals * instance()
Definition: Globals.cc:17
Float_t e
Definition: plot.C:35
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
Event finding and building.