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"
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()}
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  log << "\n";
254  };
255  cet::for_all_with_index(data_, print_per_stream);
256  }
257 
258  vector<RNGsnapshot> const&
260  {
261  std::lock_guard sentry{mutex_};
262  return data_[sid].snapshot_;
263  }
264 
265  void
267  {
268  std::lock_guard sentry{mutex_};
269  mf::LogDebug log{"RANDOM"};
270  log << "RNGservice::takeSnapshot_() of the following engine labels:\n";
271  data_[sid].snapshot_.clear();
272  for (auto const& pr : data_[sid].dict_) {
273  string const& label = pr.first;
274  shared_ptr<CLHEP::HepRandomEngine> const& eptr = pr.second;
275  assert(eptr && "RNGservice::takeSnapshot_()");
276  data_[sid].snapshot_.emplace_back(
277  data_[sid].kind_[label], label, eptr->put());
278  log << " | " << label;
279  }
280  log << " |\n";
281  }
282 
283  void
285  Event const& event)
286  {
287  std::lock_guard sentry{mutex_};
288  if (restoreStateLabel_.empty()) {
289  return;
290  }
291  // access the saved-states product:
292  auto const& saved =
293  event.getProduct<vector<RNGsnapshot>>(restoreStateLabel_);
294  // restore engines from saved-states product:
295  for (auto const& snapshot : saved) {
296  string const& label = snapshot.label();
297  mf::LogInfo log("RANDOM");
298  log << "RNGservice::restoreSnapshot_(): label \"" << label << "\"";
299  auto t = data_[sid].tracker_.find(label);
300  if (t == data_[sid].tracker_.end()) {
301  log << " could not be restored;\n"
302  << "no established engine bears this label.\n";
303  continue;
304  }
305  if (t->second == EngineSource::File) {
306  throw cet::exception("RANDOM")
307  << "RNGservice::restoreSnapshot_():\n"
308  << "The state of engine \"" << label
309  << "\" has been previously read from a file;\n"
310  << "it is therefore not restorable from a snapshot product.\n";
311  }
312  shared_ptr<CLHEP::HepRandomEngine> ep{data_[sid].dict_[label]};
313  assert(ep && "RNGservice::restoreSnapshot_()");
314  data_[sid].tracker_[label] = EngineSource::Product;
315  auto const& est = snapshot.restoreState();
316  if (ep->get(est)) {
317  log << " successfully restored.\n";
318  } else {
319  throw cet::exception("RANDOM")
320  << "RNGservice::restoreSnapshot_():\n"
321  << "Failed during restore of state of engine for \"" << label
322  << "\"\n";
323  }
324  }
325  assert(invariant_holds_(sid) && "RNGsnapshot::restoreSnapshot_()");
326  }
327 
328  void
330  {
331  std::lock_guard sentry{mutex_};
332  if (saveToFilename_.empty()) {
333  return;
334  }
335  HEP_CONCURRENCY_ASSERT_ONLY_ONE_THREAD();
336  // access the file:
337  ofstream outfile{saveToFilename_.c_str()};
338  if (!outfile) {
339  mf::LogWarning("RANDOM")
340  << "Can't create/access file \"" << saveToFilename_ << "\"\n";
341  }
342  // save each engine:
343  for (auto const& d : data_) {
344  for (auto const& pr : d.dict_) {
345  outfile << pr.first << '\n';
346  auto const& eptr = pr.second;
347  assert(eptr && "RNGservice::saveToFile_()");
348  eptr->put(outfile);
349  if (!outfile) {
350  mf::LogWarning("RANDOM")
351  << "This module's engine has not been saved;\n"
352  << "file \"" << saveToFilename_ << "\" is likely now corrupted.\n";
353  }
354  }
355  }
356  }
357 
358  void
360  {
361  std::lock_guard sentry{mutex_};
362  if (restoreFromFilename_.empty()) {
363  return;
364  }
365  HEP_CONCURRENCY_ASSERT_ONLY_ONE_THREAD();
366  // access the file:
367  ifstream infile{restoreFromFilename_.c_str()};
368  if (!infile) {
369  throw cet::exception("RANDOM")
370  << "RNGservice::restoreFromFile_():\n"
371  << "Can't open file \"" << restoreFromFilename_
372  << "\" to initialize engines\n";
373  }
374  // restore engines:
375  for (string label{}; infile >> label;) {
376  // Get schedule ID from engine label
377  assert(count(label.cbegin(), label.cend(), ':') == 2u);
378  auto const p1 = label.find_first_of(':');
379  auto const p2 = label.find_last_of(':');
380  ScheduleID const sid{
381  static_cast<ScheduleID::size_type>(stoi(label.substr(p1 + 1, p2)))};
382  auto d = data_[sid].dict_.find(label);
383  if (d == data_[sid].dict_.end()) {
384  throw Exception(errors::Configuration, "RANDOM")
385  << "Attempt to restore an engine with label " << label
386  << " not configured in this job.\n";
387  }
388  assert((data_[sid].tracker_.find(label) != data_[sid].tracker_.cend()) &&
389  "RNGservice::restoreFromFile_()");
390  EngineSource& how{data_[sid].tracker_[label]};
391  if (how == EngineSource::Seed) {
392  auto& eptr = d->second;
393  assert(eptr && "RNGservice::restoreFromFile_()");
394  if (!eptr->get(infile)) {
395  throw cet::exception("RANDOM")
396  << "RNGservice::restoreFromFile_():\n"
397  << "Failed during restore of state of engine for label " << label
398  << "from file \"" << restoreFromFilename_ << "\"\n";
399  }
400  how = EngineSource::File;
401  } else if (how == EngineSource::File) {
402  throw Exception(errors::Configuration, "RANDOM")
403  << "Engine state file contains two engine states with the same "
404  "label: "
405  << label << "\n.";
406  } else {
407  throw Exception(errors::LogicError, "RANDOM")
408  << "Internal error: attempt to restore an engine state " << label
409  << " from file\n"
410  << "which was originally initialized via an unknown or impossible "
411  "method.\n";
412  }
413  assert(invariant_holds_(sid) &&
414  "RNGservice::restoreFromFile_() invariant failure");
415  }
416  }
417 
418  // Consider changing this to preBeginJob
419  void
421  {
422  std::lock_guard sentry{mutex_};
424  engine_creation_is_okay_ = false;
425  }
426 
427  void
429  ScheduleContext const sc)
430  {
431  auto const sid = sc.id();
432  std::lock_guard sentry{mutex_};
433  takeSnapshot_(sid);
434  restoreSnapshot_(sid, e);
435  }
436 
437  void
439  {
440  // For normal termination, we wish to save the state at the *end* of
441  // processing, not at the beginning of the last event.
442  std::lock_guard sentry{mutex_};
443  ScheduleIteration iteration(data_.size());
444  iteration.for_each_schedule(
445  [this](ScheduleID const sid) { takeSnapshot_(sid); });
446  saveToFile_();
447  }
448 
449 } // namespace art
static long constexpr maxCLHEPSeed
std::string string
Definition: nybbler.cc:12
void for_all_with_index(FwdCont &, Func)
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
const double e
string infile
static Config * config
Definition: config.cpp:1054
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
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
void for_each_schedule(F f) const
static Globals * instance()
Definition: Globals.cc:17
std::string to_string(ModuleType const mt)
Definition: ModuleType.h:34
static QCString * s
Definition: config.cpp:1042
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
Event finding and building.