KAHIBARO
Discord Login Register

21.1. Monte Carlo Random Numbers

Random engines

In Geant4, every Monte Carlo decision is based on calls to a random number generator. This includes choosing free paths between interactions, selecting which physical process happens, scattering angles, and energy shares between secondaries. Understanding how these numbers are produced is essential if you want to control reproducibility or debug your simulations.

Geant4 does not implement its own random algorithms. Instead, it uses the CLHEP random number package behind a common interface. The central access point is the singleton class G4Random. All random numbers used internally by Geant4 are drawn from the engine that is currently attached to G4Random.

The engine is the object that actually implements a particular random number algorithm. Typical CLHEP engines you may encounter are CLHEP::HepJamesRandom, CLHEP::RanecuEngine, or CLHEP::MTwistEngine. Each of them has its own internal state and period. For example, a Mersenne Twister engine such as MTwistEngine has a very long period and is suitable for large simulations, while other engines can be faster but with a shorter period.

You rarely need to choose a specific engine as a beginner, because Geant4 selects a reasonable default. However, it is important to know that the choice of engine and its state together determine the exact sequence of random numbers and therefore the exact microscopic sequence of events in your simulation.

The connection between Geant4 and the CLHEP engine can be summarized in three layers:

  1. The engine object, defined in CLHEP, which stores the internal random state and algorithm.
  2. The CLHEP distribution helpers, such as CLHEP::RandFlat, CLHEP::RandGauss, which build specific distributions on top of the engine.
  3. The Geant4 G4Random interface, which provides static methods like G4UniformRand() that many Geant4 classes use.

Inside your own user code, you can use the same random engine that Geant4 uses. The recommended way is to call

cpp
G4double x = G4UniformRand();

which returns a double in the interval $(0,1)$. This call is equivalent to using a CLHEP flat distribution bound to the current engine. By using G4UniformRand() instead of creating your own separate engine, you keep your code consistent with Geant4 and preserve the reproducibility that comes from having a single well defined random sequence.

Sometimes you may need other distributions, for example Gaussian-distributed errors or angular distributions. In that case, you can either build them from uniform numbers yourself or use CLHEP distribution classes directly, but still pointing them to the engine that Geant4 manages. For example:

cpp
CLHEP::RandGauss gaussEngine(G4Random::getTheEngine());
G4double smearedE = gaussEngine.shoot(meanE, sigmaE);

Here G4Random::getTheEngine() returns a pointer to the current engine, so calls to shoot consume numbers from the same sequence that Geant4 uses for geometry and physics choices.

Multithreading introduces another layer. In a multithreaded Geant4 application, each worker thread has its own random engine instance and its own state. This prevents race conditions and guarantees that individual threads can be seeded independently. The master thread typically initializes the engines or seeds for each worker. As a result, the total random sequence of a multithreaded job depends not only on seeds but also on how events are distributed across threads, which means exact bit-by-bit reproducibility between single-threaded and multithreaded runs is not guaranteed.

To summarize the practical points about random engines in Geant4:

You should rely on G4Random and G4UniformRand() instead of inventing an independent random source.

You should recognize that the engine type and its state determine the entire microscopic history of your simulation.

In multithreaded runs, engines are per thread, so state control and seeding are done per engine and per thread.

Important rule: Always use G4UniformRand() or CLHEP distributions tied to G4Random::getTheEngine() in your own code if you want your results to be reproducible and consistent with Geant4’s internal random usage.

Random seeds

The random seed initializes the internal state of the engine and thereby fixes the future sequence of random numbers. For a given engine and a given seed, the generated sequence is deterministic. This is the key to reproducible Monte Carlo simulations in Geant4.

When you start a Geant4 application without explicitly setting a seed, Geant4 may either use a default fixed seed or derive one from the system time, depending on how the application is written. For controlled studies, this is usually not enough, and you should take explicit control over the seeds.

Geant4 provides methods in G4Random to set and inspect seeds. The most common entry point in user code is the run action, because seeds should be fixed before events are generated. A typical pattern in your RunAction::BeginOfRunAction might look like this:

cpp
#include "Randomize.hh"
// ...
void MyRunAction::BeginOfRunAction(const G4Run*)
{
  G4long seed = 123456;              // choose a fixed value
  CLHEP::HepRandom::setTheSeed(seed);
}

Here CLHEP::HepRandom::setTheSeed affects the engine that Geant4 uses through its interface. From this moment on, all random choices inside the run will follow the same sequence each time you execute the program with the same seed and the same configuration.

You can also use more than one number to initialize an engine state. Many CLHEP engines provide setSeeds(const long* seeds, int nSeeds), which lets you specify a vector of integers. This is especially useful in multithreaded runs, where each worker thread can be given a separate seed sequence:

cpp
G4long seeds[2];
seeds[0] = baseSeed + threadId;
seeds[1] = baseSeed + 1000*threadId;
CLHEP::HepRandom::setTheSeeds(seeds);

In this example each thread gets a distinct seed pair, so its random sequence is independent from other threads. This independence is essential for correct statistics when you run the same simulation multiple times or on multiple cores.

There are also built in Geant4 macro commands that allow you to control seeds without recompiling. For instance, the command

/random/setSeeds s1 s2

sets the seeds of the current engine at runtime, with s1 and s2 as integer values. By including such commands in a macro file, you can run the same simulation many times with different seeds, such as in parameter scans or when estimating statistical uncertainties.

Saving and restoring random states is a useful technique when debugging a specific event. At the beginning of a run, you can ask the engine for its current state in ASCII form, write it to a file, and later reload it to reproduce exactly the same random sequence from that point onward. CLHEP provides functions like saveEngineStatus("fileName") and restoreEngineStatus("fileName"), which Geant4 mirrors with macro commands such as

/random/saveThisRun
/random/readFromFile fileName

Using these, you can track a problematic event, store the random state before it occurs, and reproduce the simulation again and again with identical microscopic details.

From a statistical point of view, good seed management supports both reproducibility and independence. If you want to compare two different physics settings under identical random fluctuations, you can run both with the same seed. If you want statistically independent results for error estimation, use different seeds for each run or each batch job.

A simple pattern for independent batch runs is to take a base seed and add a run index:

cpp
G4long baseSeed = 98765;
G4long seed = baseSeed + runIndex;
CLHEP::HepRandom::setTheSeed(seed);

Here runIndex might be a number given on the command line or derived from a job ID. Each job then covers a different region of the engine sequence.

Important rules:

  1. Same engine, same seed, same application configuration implies an identical sequence of random numbers and therefore reproducible results.
  2. To obtain statistically independent runs, always vary the seeds, for example by using a different seed for each run or each worker thread.

By combining a clear understanding of random engines with explicit seed control, you can make your Geant4 simulations both reproducible when needed and statistically sound when you perform large studies.

Views: 8

Comments

Please login to add a comment.

Don't have an account? Register now!