001/* 002 * Java Genetic Algorithm Library (jenetics-9.0.0). 003 * Copyright (c) 2007-2026 Franz Wilhelmstötter 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 * Author: 018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com) 019 */ 020package io.jenetics.util; 021 022import static java.util.Objects.requireNonNull; 023 024import java.util.Comparator; 025import java.util.Optional; 026import java.util.Random; 027import java.util.function.Supplier; 028import java.util.random.RandomGenerator; 029import java.util.random.RandomGeneratorFactory; 030 031/** 032 * This class holds the {@link RandomGenerator} engine used for the GA. The 033 * {@code RandomRegistry} is thread safe and is initialized with the 034 * {@link RandomGeneratorFactory#getDefault()} PRNG. 035 * 036 * <h2>Set up the PRNG used for the evolution process</h2> 037 * There are several ways on how to set the {@link RandomGenerator} used during 038 * the evolution process. 039 * <p> 040 * 041 * <b>Using a {@link RandomGeneratorFactory}</b><br> 042 * The following example registers the <em>L128X1024MixRandom</em> random 043 * generator. By using a factory, each thread gets its own generator instance, 044 * which ensures thread-safety without the necessity of the created random 045 * generator to be thread-safe. 046 * {@snippet lang="java": 047 * // This is the default setup. 048 * RandomRegistry.random(RandomGeneratorFactory.getDefault()); 049 * 050 * // Using the "L128X1024MixRandom" random generator for the evolution. 051 * RandomRegistry.random(RandomGeneratorFactory.of("L128X1024MixRandom")); 052 * } 053 * <br> 054 * 055 * <b>Using a {@link RandomGenerator} {@link Supplier}</b><br> 056 * If you have a random engine, which is not available as 057 * {@link RandomGeneratorFactory}, it is also possible to register a 058 * {@link Supplier} of the desired random generator. This method has the same 059 * thread-safety property as the method above. 060 * {@snippet lang="java": 061 * RandomRegistry.random(() -> new MySpecialRandomGenerator()); 062 * } 063 * 064 * Register a random generator supplier is also more flexible. It allows 065 * using the streaming and splitting capabilities of the random generators 066 * implemented in the Java library. 067 * {@snippet lang="java": 068 * final Iterator<RandomGenerator> randoms = 069 * StreamableGenerator.of("L128X1024MixRandom") 070 * .rngs() 071 * .iterator(); 072 * 073 * RandomRegistry.random(randoms::next); 074 * } 075 * <br> 076 * 077 * <b>Using a {@link RandomGenerator} instance</b><br> 078 * It is also possible to set a single random generator instance for the whole 079 * evolution process. When using this setup, the used random generator must be 080 * thread safe. 081 * {@snippet lang="java": 082 * RandomRegistry.random(new Random(123456)); 083 * } 084 * <p> 085 * 086 * The following code snippet shows an almost complete example of a typical 087 * random generator setup. 088 * {@snippet lang="java": 089 * public class GA { 090 * public static void main(final String[] args) { 091 * // Initialize the registry with the factory of the PRGN. 092 * final var factory = RandomGeneratorFactory.of("L128X1024MixRandom"); 093 * RandomRegistry.random(factory); 094 * 095 * final Engine<DoubleGene, Double> engine = null; // @replace substring='null' replacement="..." 096 * final EvolutionResult<DoubleGene, Double> result = engine.stream() 097 * .limit(100) 098 * .collect(toBestEvolutionResult()); 099 * } 100 * } 101 * } 102 * 103 * <h2>Setup of a <i>local</i> PRNG</h2> 104 * 105 * You can temporarily (and locally) change the implementation of the PRNG. E.g., 106 * for initialize the engine stream with the same initial population. 107 * {@snippet lang="java": 108 * public class GA { 109 * public static void main(final String[] args) { 110 * // Create a reproducible list of genotypes. 111 * final var factory = RandomGeneratorFactory.of("L128X1024MixRandom"); 112 * final List<Genotype<DoubleGene>> genotypes = RandomRegistry 113 * .with(factory.create(123)) 114 * .call(() -> 115 * Genotype.of(DoubleChromosome.of(0, 10)).instances() 116 * .limit(50) 117 * .collect(toList()) 118 * ); 119 * 120 * final Engine<DoubleGene, Double> engine = null; // @replace substring='null' replacement="..." 121 * final EvolutionResult<DoubleGene, Double> result = engine 122 * // Initialize the evolution stream with the given genotypes. 123 * .stream(genotypes) 124 * .limit(100) 125 * .collect(toBestEvolutionResult()); 126 * } 127 * } 128 * } 129 * 130 * <p> 131 * The default random generator used by <em>Jenetics</em> is 132 * {@code L64X256MixRandom}. Via the system property 133 * {@code io.jenetics.util.defaultRandomGenerator}, it is possible to use a 134 * different random generator. 135 * <pre>{@code 136 * java -Dio.jenetics.util.defaultRandomGenerator=L64X1024MixRandom \ 137 * -cp jenetics-@__version__@.jar:app.jar \ 138 * com.foo.bar.MyJeneticsApp 139 * }</pre> 140 * 141 * @see RandomGenerator 142 * @see RandomGeneratorFactory 143 * 144 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 145 * @since 1.0 146 * @version 9.0 147 */ 148public final class RandomRegistry { 149 150 /** 151 * Runs code with specifically bound random generator. 152 * 153 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 154 * @version 9.0 155 * @since 9.0 156 */ 157 public static final class Runner { 158 159 private final ScopedVariable.Runner runner; 160 161 private Runner(final ScopedVariable.Runner runner) { 162 this.runner = requireNonNull(runner); 163 } 164 165 /** 166 * Runs an operation with each scoped value in this mapping bound to 167 * its value in the current thread. 168 * 169 * @param op the operation to run 170 */ 171 public void run(Runnable op) { 172 runner.run(op); 173 } 174 175 /** 176 * Calls a value-returning operation with each scoped random generator. 177 * 178 * @param op the operation to run 179 * @param <R> the type of the result of the operation 180 * @param <X> type of the exception thrown by the operation 181 * @return the result 182 * @throws X if {@code op} completes with an exception 183 */ 184 public <R, X extends Throwable> R call(ScopedValue.CallableOp<? extends R, X> op) 185 throws X 186 { 187 return runner.call(op); 188 } 189 190 } 191 192 private RandomRegistry() { 193 } 194 195 private static final ThreadLocal<RandomGenerator> 196 DEFAULT_RANDOM_FACTORY = 197 toThreadLocal(() -> 198 RandomGeneratorFactory 199 .of(Env.defaultRandomGeneratorName) 200 .create() 201 ); 202 203 private static final ScopedVariable<ThreadLocal<RandomGenerator>> 204 RANDOM = 205 ScopedVariable.of(DEFAULT_RANDOM_FACTORY); 206 207 @SuppressWarnings("unchecked") 208 private static ThreadLocal<RandomGenerator> 209 toThreadLocal(final Supplier<? extends RandomGenerator> factory) { 210 if (factory instanceof ThreadLocal<?> tl) { 211 return (ThreadLocal<RandomGenerator>)tl; 212 } else { 213 return ThreadLocal.withInitial(factory); 214 } 215 } 216 217 /** 218 * Return the {@link RandomGenerator} of the current scope. 219 * 220 * @return the {@link RandomGenerator} of the current scope 221 */ 222 public static RandomGenerator random() { 223 return RANDOM.get().get(); 224 } 225 226 /** 227 * Set a new {@link RandomGenerator} for the <em>global</em> scope. The given 228 * {@link RandomGenerator} <b>must</b> be thread safe, which is the case for 229 * the Java {@link Random} class. Each thread will get the <em>same</em> 230 * random generator, when getting one with the {@link #random()} method. 231 * 232 * @implSpec 233 * The given {@code random} generator <b>must</b> be thread safe. 234 * 235 * @see #random(RandomGeneratorFactory) 236 * 237 * @param random the new {@link RandomGenerator} for the <em>global</em> 238 * scope 239 * @throws NullPointerException if the {@code random} object is {@code null} 240 */ 241 public static void random(final RandomGenerator random) { 242 requireNonNull(random); 243 RANDOM.set(toThreadLocal(() -> random)); 244 } 245 246 /** 247 * Set a new {@link RandomGeneratorFactory} for the <em>global</em> scope. 248 * When setting a random generator <em>factory</em> instead of the 249 * generator directly, every thread gets its own generator. It is not 250 * necessary, that the created random generators must be thread-safe. 251 * 252 * @param factory the random generator factory 253 * @throws NullPointerException if the {@code factory} object is {@code null}. 254 */ 255 public static void random(final RandomGeneratorFactory<?> factory) { 256 requireNonNull(factory); 257 RANDOM.set(toThreadLocal(factory::create)); 258 } 259 260 /** 261 * Set a new {@link Supplier} of {@link RandomGenerator} for the 262 * <em>global</em> scope. 263 * When setting a random generator <em>supplier</em> instead of the 264 * generator directly, every thread gets its own generator, as returned by 265 * the supplier. It is not necessary, that the created random generators must 266 * be thread-safe. 267 * 268 * @see #random(RandomGeneratorFactory) 269 * 270 * @param supplier the random generator supplier 271 * @throws NullPointerException if the {@code supplier} object is {@code null}. 272 */ 273 public static void random(final Supplier<? extends RandomGenerator> supplier) { 274 requireNonNull(supplier); 275 RANDOM.set(toThreadLocal(supplier)); 276 } 277 278 /** 279 * Set the random object to its default value. 280 */ 281 public static void reset() { 282 RANDOM.reset(); 283 } 284 285 286 /** 287 * Return a scoped runner with the given {@code random} generator bound to 288 * the {@link RandomRegistry}. 289 * 290 * @param random the {@code random} generator to bind 291 * @return a new scoped runner object 292 */ 293 public static Runner with(final RandomGenerator random) { 294 requireNonNull(random); 295 return new Runner( 296 ScopedVariable.with(RANDOM.value(toThreadLocal(() -> random))) 297 ); 298 } 299 300 /** 301 * Return a scoped runner with the given random generator {@code factory} 302 * bound to the {@link RandomRegistry}. Every thread spawned in the returned 303 * runner will use a new random generator, created by the factory. 304 * 305 * @param factory the random generator factory used for creating the desired 306 * random generator. Every thread gets its own random generator when 307 * calling {@link RandomRegistry#random()}. 308 * @return a new scoped runner object 309 */ 310 public static Runner with(final RandomGeneratorFactory<?> factory) { 311 requireNonNull(factory); 312 return new Runner( 313 ScopedVariable.with(RANDOM.value(toThreadLocal(factory::create))) 314 ); 315 } 316 317 /** 318 * Return a scoped runner with the given random generator {@code supplier} 319 * bound to the {@link RandomRegistry}. Every thread spawned in the returned 320 * runner will use a new random generator, returned by the supplier. 321 * 322 * @param supplier the random generator supplier used for creating the desired 323 * random generator. Every thread gets its own random generator when 324 * calling {@link RandomRegistry#random()}. 325 * @return a new scoped runner object 326 */ 327 public static Runner 328 with(final Supplier<? extends RandomGenerator> supplier) { 329 requireNonNull(supplier); 330 return new Runner( 331 ScopedVariable.with(RANDOM.value(toThreadLocal(supplier))) 332 ); 333 } 334 335 private record Env() { 336 private static final String defaultRandomGeneratorName = get(); 337 338 private static String get() { 339 return getConfigured() 340 .or(Env::getDefault) 341 .orElseGet(Env::getBest); 342 } 343 344 private static Optional<String> getConfigured() { 345 return Optional.ofNullable(System.getProperty( 346 "io.jenetics.util.defaultRandomGenerator" 347 )); 348 } 349 350 private static Optional<String> getDefault() { 351 return RandomGeneratorFactory.all() 352 .map(RandomGeneratorFactory::name) 353 .filter("L64X256MixRandom"::equals) 354 .findFirst(); 355 } 356 357 private static String getBest() { 358 final var highestStateBits = Comparator 359 .<RandomGeneratorFactory<?>>comparingInt(RandomGeneratorFactory::stateBits) 360 .reversed(); 361 362 return RandomGeneratorFactory.all() 363 .sorted(highestStateBits) 364 .map(RandomGeneratorFactory::name) 365 .findFirst() 366 .orElse("Random"); 367 } 368 369 } 370 371}