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}