Project

General

Profile

SeedService

A random engine seed (just seed from now on) is here defined as an unsigned integer in the 1-900,000,000 (nine hundred million) range, used to initialize the status of a random engine. Although this facility allows for seed value 0, we recommend to avoid it, as it is given special meanings by certain engines (e.g. to initialize from the clock).

The art service SeedService provides a central management of random seeds in a art job.
Although it is designed to be used with art's RandomNumberGenerator service, the two services are independent and SeedService can distribute seeds for any random engine, not only for the ones from RandomNumberGenerator (for example, it can be used with GENIE).

The service is an interface between art framework and the SeedMaster class, that provides almost all the functionality.

For $\mu2e$ collaborators, the information on http://mu2e.fnal.gov/atwork/computing/Random.shtml#background and http://mu2e.fnal.gov/atwork/computing/Random.shtml#user helps in understanding the following instructions.

Using the service in your code

In a art module, the code requests a seed by making one of the following two calls:

art::ServiceHandle<SeedService>->getSeed();
art::ServiceHandle<SeedService>->getSeed("instanceName");

This must happen in the constructor of the module. The limitation is mostly connected to RandomNumberGenerator service creating and initializing its engines in the constructors.
Code instanciating a SeedMaster can request a seed by making one of the following two calls:
SeedMasterInstance.getSeed("moduleLabel");
SeedMasterInstance.getSeed("moduleLabel", "instanceName");

In both cases it is caller's responsibility to use the appropriate form.

When getSeed is called with a particular module label and instance name, it computes a seed value, saves it and returns it. If there is a subsequent call to getSeed with the same module label and instance name, the class will return the saved value of the seed. This document will use the phrase "unique calls to getSeed": the second of two calls with the same module label and instance names is not considered unique.

Run-time configuration

SeedMaster class and SeedService are configured from a FHiCL parameter set.
The configuration of SeedService is exactly the same as SeedMaster's, and art reads it from services.SeedService.

Configuration common to all the policies

The complete configuration depends on the policy chosen; the following parameters are common to all the policies:
SeedService : {
   policy           : "autoIncrement" // Required: legal value are listed below
   verbosity        : 0               // Optional: default=0, no informational printout
   endOfJobSummary  : false           // Optional: print list of all managed seeds at end of job
}

The policy parameter tells the service to which algorithm to use. Supported policies are:

If the value of the policy parameter is not one of the known policies, the code will throw an exception.

Range check

Most of the policies allow for a range check that ensures that no seed falls outside the expected range.
The range check is enabled by default in most of the policies supporting it, and it can be explicitly enabled or disabled by a FHiCL parameter (typically checkRange).
The expected range is also typically defined by a FHiCL parameter (usually maxUniqueEngines).
Each policy defines its allowed range. When a seed is requested with getSeed(), if the range check is enabled the policy will verify that the seed that is going to be offered lies within the allowed range, and will throw an exception otherwise. Such an exception can represent a bug in SeedService code, but more often reflects a configuration either wrong or not compatible with the needs of the job.

autoIncrement policy

The autoIncrement policy assigns the seeds in sequence in the order they are requested, starting from the base seed explicitly specified in the configuration.

The configurable items specific for this policy are:

SeedService : {
   policy           : "autoIncrement" 
   // ... and all the common ones, plus:
   baseSeed         : 101   // Required: An integer >= 0
   checkRange       : true  // Optional: legal values true (default) or false
   maxUniqueEngines : 20    // Required iff checkRange is true
}

In this policy, the seed is set to baseSeed + offset, where on the first unique call to getSeed the offset is set to 101; on the second unique call to getSeed it is set to 102, and so on.
The seeds provided by this policy are unique by construction. Uniqueness is still checked as a sanity check.
If the range check is not disabled, the allowed range is in [ baseSeed, baseSeed + maxUniqueEngines [.

linearMapping policy

The linearMapping policy assigns the seeds in sequence in the order they are requested, starting from a base seed computed from the job number specified in the configuration.
It requires to know how many seeds are allowed to be used by any job (maxUniqueEngines), and the sequence number of this job (nJob).
This policy allocates blocks of seeds (each one containing maxUniqueEngines contiguous seeds) to each job, and uses the seeds in the block corresponding to nJob-th job.
For example, in the configuration in the example below, the blocks are of 20 seeds per job, and the job in that configuration will be assigned the seeds between 1 and 20 (both included). A second job (nJob: 1) would obtain the seeds between 21 and 40.

The configurable items specific for this policy are:

SeedService : {
   policy           : "linearMapping" 
   // ... and all the common ones, plus:
   nJob             : 0     // Required: An integer >= 0
   checkRange       : true  // Optional: legal values true (default) or false
   maxUniqueEngines : 20    // Required iff checkRange is true
}

If the range check is not disabled, the allowed range spans the sequence of maxUniqueEngines seeds.
It is the responsibility of the user to ensure that the parameters (e.g. nJob and maxUniqueEngines for the linearMapping policy) are chosen it a way that ensures the required level of uniqueness of seeds.
The example grid jobs have a single point of maintenance to achieve this: the user must specify the starting job number for each grid submission.

Note: as a legacy option, baseSeed is an accepted, but deprecated, alias of nJob.

preDefinedOffset policy

The seeds assigned by the preDefinedOffset policy are based on a specified base seed (baseSeed), plus an offset specified by the configuration for each engine instance.

The configurable items specific for this policy are:

SeedService : {
   policy           : "preDefinedOffset" 
   // ... and all the common ones, plus:
   baseSeed         : 101   // Required: An integer >= 0.
   checkRange       : true  // Optional: legal values true (default) or false
   maxUniqueEngines : 20    // Required iff checkRange is true

   module_label1: offset1      // for each module with a nameless engine
   module_label2: {            // for each module with nemed engine instances
     instance_name1: offset21  //   ... one entry for each instance name
     instance_name2: offset22
     // ...
   }
   // ...
}

When getSeed is called, the class will look into the parameter set to find a defined offset for the specified module label and instance name. The returned seed will be baseSeed + offset##.
If the offset is not configured for a specific module/instance, getSeed() will throw an exception.
If the range check is not disabled, the allowed range is in [ baseSeed, baseSeed + maxUniqueEngines [.

preDefinedSeed policy

The seeds assigned by the preDefinedSeed@ policy are explicitly specified by the configuration for each engine instance.
The configurable items specific for this policy are:

SeedService : {
   policy           : "preDefinedSeed" 
   // ... and all the common ones, plus:

   module_label1: seed1      // for each module with a nameless engine
   module_label2: {          // for each module with nemed engine instances
     instance_name1: seed21  //   ... one entry for each instance name
     instance_name2: seed22
   }
}

When getSeed is called, the class will look into the parameter set to find a defined seed for the specified module label and instance name and it will directly return it.
No range check is supported, and the seed is not guaranteed to be unique. User has control on whether such collisions occur.

This policy is intended for debugging and special tests: use it with care!

random policy

This policy assigns random seeds to each engine instance.
The configurable items specific for it are:

SeedService : {
   policy           : "random" 
   // ... and all the common ones, plus:
   masterSeed: master_seed // optional: an integer >= 0
}

With this policy, the seed is extracted from a local random number generator. The seed used to initialize this additional random number generator is taken from the clock, unless the masterSeed parameter is set to specify the actual seed. Uniqueness of the seeds is not guaranteed.
This policy is meant as a quick way to disentangle the code from the random seed policy used, and it's meant for special needs only and definitely not for production.
You can enable this policy instead of whatever is already in your configuration by adding at the end of your configuration:
services.SeedService.policy: "random" 

(this assumes that the configuration of the SeedMaster is read from `services.SeedService`, that is the case in the art framework).

perEvent policy

This policy assigns seeds to each engine instance determined by the content of the event and the module the engine belongs to.
This means that each event will see the same sequences of random numbers every time the job is run.

The configurable items specific for it are:
SeedService : {
   policy           : "perEvent" 
   // ... and all the common ones, plus:
   algorithm        : "default" // optional
   offset           : 0         // optional
}

The algorithm determines how the information of the event and the execution context is combined into a random number seed:
  • EventTimestamp_v1 (default) combines run, subrun and event number, event timestamp, process name, module label and engine instance name.
    Note that in simulated events the run, subrun and event number are far from unique: you have to rely on the time stamp.
    The time stamp is generated by art input module, typically EmptyEvent. The input module can be configured to pick a timestamp plugin, but by default nothing is plugged in and the timestamp is always the same invalid one (0).
    So, before using this policy for event generation jobs, make sure you are using a timestamp plugin!
  • default is a valid algorithm name, that acts as an alias
    Not many choices so far.

The offset value (0 by default) is added to every delivered seed. This is meant as a quick'n'dirty way to get out of trouble if the seed that was assigned to the event causes troubles. All the seeds, from all the events and for all the modules will be offset by this value, and the offset can make the seed invalid. Also the use of this parameter defies the purpose of this policy, since to reproduce the random stream the knowledge of the offset value and of when it is applied are necessary. In short: avoid using it.

Notes on the grammar to specify per-instance seeds and offsets

The FHiCL grammar to specify the seeds and offsets takes two forms.
If no instance name is given, the seed or offset is given by:

moduleLabel : value

When a module has multiple instances, the values are given by:

moduleLabel : {
   instanceName1 : value1
   instanceName2 : value2
}

Interface with art's RandomNumberGenerator service

It is possible to use the interface of this service to fully manage the random engines.
This means that the user can ask SeedService a new engine, instead of RandomNumberGenerator.
The internally preferred engine manager is still RandomNumberGenerator, but other engines can be integrated to be managed by SeedService as well.

The normal way to obtain random number engines is to ask RandomNumberGenerator service during module's construction:

// within module construction:
createEngine(seed, "HepJamesRandom", "instanceA");

that will create an engine associated to the current module and seed it. The engine will be of type CLHEP::HepRandomEngine.
The equivalent line with SeedService interface is:
// within module construction:
art::ServiceHandle<artext::SeedService>()->createEngine(*this, "HepJamesRandom", "instanceA");

This will in fact internally end up calling RandomNumberGenerator::createEngine() as above.
The gain is automatic seeding of the engine by SeedService and the fact that from now on SeedService knows about the engine.
That is traded with a more cumbersome syntax and the loss of control on the seed. The syntax is the result of the need to talk to a service and the fact that RandomNumberGenerator has a very restricted interface. About the second point, a bit more control is offered by an extended syntax:
// within module construction:
art::ServiceHandle<artext::SeedService>()->createEngine
  (*this, "HepJamesRandom", "instanceA", pset, { "Seed", "MySeed" });

Here SeedService will provide a seed only if it does not find in the parameter set pset any of the specified configuration parameters (first "Seed", then "MySeed" in this example). In other words, if "Seed" configuration parameter exists, its value is used as seed, otherwise if "MySeed" exists, its value is used instead, and otherwise SeedService is given control of that seed.
The exception is that if the specified seed is a magic value, InvalidSeed (0), it is interpreted as a request to ignore the parameter and use the service to get the seed. This is made as a quick way to remove the seed override from an existing FHiCL file with one line.
Note that if SeedService does not get the control, also the policies that reseed on event-by-event basis will not reseed1.

There are a few cases where RandomNumberGenerator is not controlling the random engine2. In that case, SeedService can still interact with the engine (that has to have been created already):

// within module construction:
art::ServiceHandle<artext::SeedService>()->registerEngine(
  artext::SeedService::TRandomSeeder(pRandom),
  "instanceA", pset, "Seed" 
  );

The first parameter is a function or functor needed to set the seed of the engine (in the example, it's a optional functor SeedService provides to deal with ROOT's TRandom objects, but a custom function can be used if needed). If that function is not provided, SeedService will not be able to set the seed of the engine, neither at the beginning of the job nor on every new event, as some policies require.
Here some simple (untested) functor that can be used:

  • ROOT TRandom:
    #include <TRandom.h>
    
    /// Seeder_t functor setting the seed of a ROOT TRandom engine (untested!)
    class TRandomSeeder {
        public:
      TRandomSeeder(TRandom* engine): pRandom(engine) {}
      void operator() (EngineId const&, seed_t seed)
        { if (pRandom) pRandom->SetSeed(seed); }
        protected:
      TRandom* pRandom = nullptr;
    }; // class TRandomSeeder
    
  • CLHEP HepRandomEngine (that's what RandomNumberGenerator uses)
    #include "CLHEP/Random/RandomEngine.h" 
    
    /// Seeder_t functor setting the seed of a CLHEP::HepRandomEngine engine (untested!)
    class CLHEPengineSeeder {
        public:
      CLHEPengineSeeder(CLHEP::HepRandomEngine* engine): pRandom(engine) {}
      void operator() (EngineId const&, seed_t seed)
        { if (pRandom) pRandom->setSeed(seed, 0); }
        protected:
      CLHEP::HepRandomEngine* pRandom = nullptr;
    }; // class CLHEPengineSeeder
    
  • C++ random engines
    #include <random>
    
    /// Seeder_t functor setting the seed of a standard C++ engine (untested!)
    template <typename STLengine>
    class STLengineSeeder {
        public:
      STLengineSeeder(STLengine* engine): pRandom(engine) {}
      void operator() (EngineId const&, seed_t seed)
        { if (pRandom) pRandom->seed(seed); }
        protected:
      STLengine* pRandom = nullptr;
    }; // class STLengineSeeder
    
  • C++11 closures can be used as well; for example, for a CLHEP engine:
    // within module construction:
    art::ServiceHandle<artext::SeedService>()->registerEngine(
      [engine](artext::SeedService::EngineId const&, seed_t seed)
        { engine->setSeed(seed, 0); },
      "instanceA", pset, "Seed" 
      );
    

    where engine is a variable of type CLHEP::HepRandomEngine* (the capture part may change: capture this if the engine is a data member, capture &engine if it's the engine itself rather than its pointer ).

1 It would be possible to implement the automatic reseeding anyway for all the engines in RandomNumberGenerator, but that would create confusion for the ones that are not, since the user should still reseed them manually.

2 A noticeable example is GENIE generator, that uses TRandom engines.