Generally, it is possible to define one random seed per model part (module), initialise one generator per model part and use that generator for every random number stream required within the model part. The other way is to also define one random seed per model part but use numerous generators (e.g. for every specific task within the model part) that are all initialised with that seed.
Advantages | Disadvantages | |
One generator | More efficient and Clearer | Not possible to exclude specific tasks without influencing following tasks that depend on that generator |
Numerous generators | Independent tasks | More effort to configure |
When defining several random seeds and generators as parameters it is possible to define defaults where random seeds and generators relating to specific areas of the model may point to if they are not specified explicitly. Whereas this is recommended for random seeds it is not for random number generators: if one chooses to define a certain generator explicitly in certain cases that would also effect other random processes since random numbers of that specific area are no longer drawn from the default generator!
When a generator defaults to a super-generator (same String) an alternative name is required (set a new name for the generator) in case of customisation to indicate that it is now customised and may no longer point to the super-generator (otherwise, the customized generator would overwrite the super-generator).
Whereas seeds and thus generators define the sequence of underlying pseudo-random numbers the distributions define the characteristic of resulting random numbers.
Therefore, code that uses random numbers and wants to enable the user to influence the random numbers should provide means to adapt both, the random seed used for random engine creation (or the random engine itself) and the distribution. One way is to use parameters for random seed, generators, and for the distribution name that is to be registered at the UranusRandomService.
In order to allow full flexibility for simulations the modeler should define parameters for random seed, generator, and distributions for both, the entire model and specific parts of the model. E.g.:
RANDOM_SEED(Integer.class, 1), RANDOM_SEED_AGENTINIT(Integer.class, RANDOM_SEED), RANDOM_SEED_DECISIONMAKING(Integer.class, RANDOM_SEED), RANDOM_STREAM(String.class, "RANDOM_STREAM"), RANDOM_STREAM_AGENTINIT(String.class, "RANDOM_STREAM_AGENTINIT"), RANDOM_STREAM_DECISIONMAKING(String.class, "RANDOM_STREAM_DECISIONMAKING"), RANDOM_DIST(String.class, "RANDOM_DIST"), RANDOM_DIST_AGENTINIT(String.class, "RANDOM_DIST_AGENTINIT"), RANDOM_DIST_DECISIONMAKING(String.class, "RANDOM_DIST_DECISIONMAKING"),
A quite rich behaviour is possible:
NOTE: To achieve independent random number streams for different parts it is sufficient to set the random seeds accordingly, even if they are set to the same value.
A typical initialisation following above considerations would be (assuming random number generators are initialised for each area of the model anyway - see Seeds, Streams and Generators:
UranusRandomService randomService = URandomService.getURandomService(); // init the general random stream (unless it is changed or registered by the user before): if (!PmParameterManager.isCustomised(RANDOM_STREAM) && !getURandomService().isGeneratorRegistered( (String) PmParameterManager.getParameter(RANDOM_STREAM))) { getURandomService().registerGenerator((String) PmParameterManager.getParameter(RANDOM_STREAM), new MersenneTwister(((Integer) PmParameterManager.getParameter(RANDOM_SEED)).intValue())); } /* * The following specific distributions and generators are only initialised when the according parameter values * have changed. Otherwise, the default distribution and generator are used for all. */ if (PmParameterManager.isCustomised(RANDOM_SEED_AGENTINIT) && !(PmParameterManager.isCustomised(RANDOM_STREAM_AGENTINIT) && randomService.isGeneratorRegistered((String) PmParameterManager .getParameter(RANDOM_STREAM_AGENTINIT)))) { randomService.registerGenerator((String) PmParameterManager .getParameter(RANDOM_STREAM_AGENTINIT), new MersenneTwister( ((Integer) PmParameterManager.getParameter(RANDOM_SEED_AGENTINIT)).intValue())); } String distName = (String) PmParameterManager.getParameter(RANDOM_DIST_AGENTINIT); if (randomService.isDistributionRegistered(distName)) { if (!(randomService.getDistribution(distName) instanceof Uniform)) { logger.warn("Distribution with assigned name in RANDOM_DIST_AGENTINIT (" + distName + ") is not an Uniform distribution"); randomService.registerDistribution(randomService.getNewUniformDistribution(randomService .getGenerator((String) PmParameterManager.getParameter(RANDOM_STREAM_AGENTINIT))), distName); } } else { randomService.registerDistribution(randomService.getNewUniformDistribution(randomService .getGenerator((String) PmParameterManager.getParameter(RANDOM_STREAM_AGENTINIT))), distName); }
Some attention is required when registering random generators and distributions at URaNuS. If one uses the current parameter values ugly effects occur when one decides to assign the same value to all generator and/or distribution names: The last generator/distribution registered overwrites earlier registrations. URaNuS issues a warning in this case. To prevent such situations it is better to use the default parameter value or another constant as name for registration. However, if the default is another parameter anyway one should not register the specific generator/distribution at all.
URandomService.getURandomService().registerDistribution(new Uniform( URandomService.getURandomService().getGenerator((String) PmParameterManager.getParameter( RandomPa.RANDOM_STREAM_DECISIONMAKING))), (String)RandomPa.RANDOM_DIST_DECISIONMAKING.getDefaultValue());
It is certainly a good idea to use logging to control which random distribution/generator/seed is used in which model part. To activate logging configure your Log4J as follows:
log4j.logger.de.cesr.uranus = INFO, A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p - (%C{1}:%L) %m%n
If the logging level is set to DEBUG instead of INFO the framework logs the origin of the random number request using UIdentifyCallerException.
NOTE: In order to enable the logging of origin of random number requests it is required to initialise the distributions by URandomService with methods getNewUniformDistribution() and createNormal(). Of course it is also possible to use own distributions that are adapted according to e.g. UNormalController.