001/*
002 * Java Genetic Algorithm Library (jenetics-8.3.0).
003 * Copyright (c) 2007-2025 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.Consumer;
028import java.util.function.Function;
029import java.util.function.Supplier;
030import java.util.random.RandomGenerator;
031import java.util.random.RandomGeneratorFactory;
032
033/**
034 * This class holds the {@link RandomGenerator} engine used for the GA. The
035 * {@code RandomRegistry} is thread safe and is initialized with the
036 * {@link RandomGeneratorFactory#getDefault()} PRNG.
037 *
038 * <h2>Set up the PRNG used for the evolution process</h2>
039 * There are several ways on how to set the {@link RandomGenerator} used during
040 * the evolution process.
041 * <p>
042 *
043 * <b>Using a {@link RandomGeneratorFactory}</b><br>
044 * The following example registers the <em>L128X1024MixRandom</em> random
045 * generator. By using a factory, each thread gets its own generator instance,
046 * which ensures thread-safety without the necessity of the created random
047 * generator to be thread-safe.
048 * {@snippet lang="java":
049 * // This is the default setup.
050 * RandomRegistry.random(RandomGeneratorFactory.getDefault());
051 *
052 * // Using the "L128X1024MixRandom" random generator for the evolution.
053 * RandomRegistry.random(RandomGeneratorFactory.of("L128X1024MixRandom"));
054 * }
055 * <br>
056 *
057 * <b>Using a {@link RandomGenerator} {@link Supplier}</b><br>
058 * If you have a random engine, which is not available as
059 * {@link RandomGeneratorFactory}, it is also possible to register a
060 * {@link Supplier} of the desired random generator. This method has the same
061 * thread-safety property as the method above.
062 * {@snippet lang="java":
063 * RandomRegistry.random(() -> new MySpecialRandomGenerator());
064 * }
065 *
066 * Register a random generator supplier is also more flexible. It allows
067 * using the streaming and splitting capabilities of the random generators
068 * implemented in the Java library.
069 * {@snippet lang="java":
070 * final Iterator<RandomGenerator> randoms =
071 *     StreamableGenerator.of("L128X1024MixRandom")
072 *         .rngs()
073 *         .iterator();
074 *
075 * RandomRegistry.random(randoms::next);
076 * }
077 * <br>
078 *
079 * <b>Using a {@link RandomGenerator} instance</b><br>
080 * It is also possible to set a single random generator instance for the whole
081 * evolution process. When using this setup, the used random generator must be
082 * thread safe.
083 * {@snippet lang="java":
084 * RandomRegistry.random(new Random(123456));
085 * }
086 * <p>
087 *
088 * The following code snippet shows an almost complete example of a typical
089 * random generator setup.
090 * {@snippet lang="java":
091 * public class GA {
092 *     public static void main(final String[] args) {
093 *         // Initialize the registry with the factory of the PRGN.
094 *         final var factory = RandomGeneratorFactory.of("L128X1024MixRandom");
095 *         RandomRegistry.random(factory);
096 *
097 *         final Engine<DoubleGene, Double> engine = null; // @replace substring='null' replacement="..."
098 *         final EvolutionResult<DoubleGene, Double> result = engine.stream()
099 *             .limit(100)
100 *             .collect(toBestEvolutionResult());
101 *     }
102 * }
103 * }
104 *
105 * <h2>Setup of a <i>local</i> PRNG</h2>
106 *
107 * You can temporarily (and locally) change the implementation of the PRNG. E.g.,
108 * for initialize the engine stream with the same initial population.
109 * {@snippet lang="java":
110 * public class GA {
111 *     public static void main(final String[] args) {
112 *         // Create a reproducible list of genotypes.
113 *         final var factory = RandomGeneratorFactory.of("L128X1024MixRandom");
114 *         final List<Genotype<DoubleGene>> genotypes =
115 *             with(factory.create(123), r ->
116 *                 Genotype.of(DoubleChromosome.of(0, 10)).instances()
117 *                     .limit(50)
118 *                     .collect(toList())
119 *             );
120 *
121 *         final Engine<DoubleGene, Double> engine = null; // @replace substring='null' replacement="..."
122 *         final EvolutionResult<DoubleGene, Double> result = engine
123 *              // Initialize the evolution stream with the given genotypes.
124 *             .stream(genotypes)
125 *             .limit(100)
126 *             .collect(toBestEvolutionResult());
127 *     }
128 * }
129 * }
130 *
131 * <p>
132 * The default random generator used by <em>Jenetics</em> is
133 * {@code L64X256MixRandom}. Via the system property
134 * {@code io.jenetics.util.defaultRandomGenerator}, it is possible to use a
135 * different random generator.
136 * <pre>{@code
137 * java -Dio.jenetics.util.defaultRandomGenerator=L64X1024MixRandom \
138 *      -cp jenetics-@__version__@.jar:app.jar \
139 *          com.foo.bar.MyJeneticsApp
140 * }</pre>
141 *
142 * @see RandomGenerator
143 * @see RandomGeneratorFactory
144 *
145 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
146 * @since 1.0
147 * @version 8.0
148 */
149public final class RandomRegistry {
150        private RandomRegistry() {
151        }
152
153        /**
154         * Thread-local wrapper for a random generator supplier (factory).
155         *
156         * @param <R> the type of the random generator
157         */
158        private static final class TLR<R extends RandomGenerator>
159                extends ThreadLocal<R>
160                implements Supplier<R>
161        {
162                private final Supplier<? extends R> _factory;
163
164                TLR(final Supplier<? extends R> factory) {
165                        _factory = requireNonNull(factory);
166                }
167
168                @Override
169                protected synchronized R initialValue() {
170                        return _factory.get();
171                }
172        }
173
174        private static final TLR<RandomGenerator>
175                DEFAULT_RANDOM_FACTORY =
176                new TLR<>(RandomGeneratorFactory.of(Env.defaultRandomGenerator)::create);
177
178        private static final Context<Supplier<? extends RandomGenerator>>
179                CONTEXT =
180                new Context<>(DEFAULT_RANDOM_FACTORY);
181
182        /**
183         * Return the {@link RandomGenerator} of the current scope.
184         *
185         * @return the {@link RandomGenerator} of the current scope
186         */
187        public static RandomGenerator random() {
188                return CONTEXT.get().get();
189        }
190
191        /**
192         * Set a new {@link RandomGenerator} for the <em>global</em> scope. The given
193         * {@link RandomGenerator} <b>must</b> be thread safe, which is the case for
194         * the Java {@link Random} class.
195         *
196         * @see #random(RandomGeneratorFactory)
197         *
198         * @param random the new {@link RandomGenerator} for the <em>global</em>
199         *        scope
200         * @throws NullPointerException if the {@code random} object is {@code null}
201         */
202        public static void random(final RandomGenerator random) {
203                requireNonNull(random);
204                CONTEXT.set(() -> random);
205        }
206
207        /**
208         * Set a new {@link RandomGeneratorFactory} for the <em>global</em> scope.
209         *
210         * @param factory the random generator factory
211         * @throws NullPointerException if the {@code factory} object is {@code null}.
212         */
213        public static <R extends RandomGenerator> void
214        random(final RandomGeneratorFactory<? extends R> factory) {
215                requireNonNull(factory);
216                CONTEXT.set(new TLR<>(factory::create));
217        }
218
219        /**
220         * Set a new {@link Supplier} of {@link RandomGenerator} for the
221         * <em>global</em> scope.
222         *
223         * @param supplier the random generator supplier
224         * @throws NullPointerException if the {@code supplier} object is {@code null}.
225         */
226        public static <R extends RandomGenerator> void
227        random(final Supplier<? extends R> supplier) {
228                requireNonNull(supplier);
229                CONTEXT.set(new TLR<>(supplier));
230        }
231
232        /**
233         * Set the random object to its default value.
234         */
235        public static void reset() {
236                CONTEXT.reset();
237        }
238
239        /**
240         * Executes the consumer code using the given {@code random} generator.
241         * {@snippet lang="java":
242         * final MSeq<Integer> seq = null; // @replace substring='null' replacement="..."
243         * using(new Random(123), r -> seq.shuffle());
244         * }
245         *
246         * The example above shuffles the given integer {@code seq} <i>using</i> the
247         * given {@code Random(123)} engine.
248         *
249         * @since 3.0
250         *
251         * @param random the PRNG used within the consumer
252         * @param consumer the consumer which is executed with the <i>scope</i> of
253         *        the given {@code random} engine.
254         * @param <R> the type of the random engine
255         * @throws NullPointerException if one of the arguments is {@code null}
256         */
257        public static <R extends RandomGenerator> void using(
258                final R random,
259                final Consumer<? super R> consumer
260        ) {
261                CONTEXT.with(
262                        () -> random,
263                        r -> { consumer.accept(random); return null; }
264                );
265        }
266
267        /**
268         * Executes the consumer code using the given {@code random} generator.
269         * {@snippet lang="java":
270         * final MSeq<Integer> seq = null; // @replace substring='null' replacement="..."
271         * using(RandomGeneratorFactory.getDefault(), r -> seq.shuffle());
272         * }
273         *
274         * The example above shuffles the given integer {@code seq} <i>using</i> the
275         * given {@link RandomGeneratorFactory#getDefault()} factory.
276         *
277         * @since 7.0
278         *
279         * @param factory the random generator factory used within the consumer
280         * @param consumer the consumer which is executed within the <i>scope</i> of
281         *        the given random generator.
282         * @param <R> the type of the random engine
283         * @throws NullPointerException if one of the arguments is {@code null}
284         */
285        public static <R extends RandomGenerator> void using(
286                final RandomGeneratorFactory<? extends R> factory,
287                final Consumer<? super R> consumer
288        ) {
289                CONTEXT.with(
290                        new TLR<>(factory::create),
291                        r -> { consumer.accept(r.get()); return null; }
292                );
293        }
294
295        /**
296         * Executes the consumer code using the given {@code random} generator
297         * supplier.
298         * {@snippet lang="java":
299         * final MSeq<Integer> seq = null; // @replace substring='null' replacement="..."
300         * using(() -> new MyRandomGenerator(), r -> seq.shuffle());
301         * }
302         *
303         * @since 7.0
304         *
305         * @param supplier the random generator supplier used within the consumer
306         * @param consumer the consumer which is executed within the <i>scope</i> of
307         *        the given random generator.
308         * @param <R> the type of the random engine
309         * @throws NullPointerException if one of the arguments is {@code null}
310         */
311        public static <R extends RandomGenerator> void using(
312                final Supplier<? extends R> supplier,
313                final Consumer<? super R> consumer
314        ) {
315                CONTEXT.with(
316                        new TLR<>(supplier),
317                        r -> { consumer.accept(r.get()); return null; }
318                );
319        }
320
321        /**
322         * Opens a new <em>scope</em> with the given random generator and executes
323         * the given function within it. The following example shows how to create a
324         * reproducible list of genotypes:
325         * {@snippet lang="java":
326         * final List<Genotype<DoubleGene>> genotypes =
327         *     with(new LCG64ShiftRandom(123), r ->
328         *         Genotype.of(DoubleChromosome.of(0, 10)).instances()
329         *            .limit(50)
330         *            .collect(toList())
331         *     );
332         * }
333         *
334         * @since 3.0
335         *
336         * @param <R> the type of the random engine
337         * @param <T> the function return type
338         * @param random the PRNG used for the opened scope
339         * @param function the function to apply within the random scope
340         * @return the object returned by the given function
341         * @throws NullPointerException if one of the arguments is {@code null}
342         */
343        public static <R extends RandomGenerator, T> T with(
344                final R random,
345                final Function<? super R, ? extends T> function
346        ) {
347                return CONTEXT.with(
348                        () -> random,
349                        s -> function.apply(random)
350                );
351        }
352
353        /**
354         * Opens a new <em>scope</em> with the given random generator factory and
355         * executes the given function within it.
356         * {@snippet lang="java":
357         * final List<Genotype<DoubleGene>> genotypes =
358         *     with(RandomGeneratorFactory.getDefault(), random ->
359         *         Genotype.of(DoubleChromosome.of(0, 10)).instances()
360         *            .limit(50)
361         *            .collect(toList())
362         *     );
363         * }
364         *
365         * @since 3.0
366         *
367         * @param <R> the type of the random engine
368         * @param <T> the function return type
369         * @param factory the PRNG used for the opened scope
370         * @param function the function to apply within the random scope
371         * @return the object returned by the given function
372         * @throws NullPointerException if one of the arguments is {@code null}.
373         */
374        public static <R extends RandomGenerator, T> T with(
375                final RandomGeneratorFactory<? extends R> factory,
376                final Function<? super R, ? extends T> function
377        ) {
378                return CONTEXT.with(
379                        new TLR<>(factory::create),
380                        r -> function.apply(r.get())
381                );
382        }
383
384        /**
385         * Opens a new <em>scope</em> with the given random generator supplier and
386         * executes the given function within it.
387         * {@snippet lang="java":
388         * final List<Genotype<DoubleGene>> genotypes =
389         *     with(() -> new MyRandomGenerator(), random ->
390         *         Genotype.of(DoubleChromosome.of(0, 10)).instances()
391         *            .limit(50)
392         *            .collect(toList())
393         *     );
394         * }
395         *
396         * @since 3.0
397         *
398         * @param <R> the type of the random engine
399         * @param <T> the function return type
400         * @param supplier the PRNG used for the opened scope
401         * @param function the function to apply within the random scope
402         * @return the object returned by the given function
403         * @throws NullPointerException if one of the arguments is {@code null}.
404         */
405        public static <R extends RandomGenerator, T> T with(
406                final Supplier<? extends R> supplier,
407                final Function<? super R, ? extends T> function
408        ) {
409                return CONTEXT.with(
410                        new TLR<>(supplier),
411                        r -> function.apply(r.get())
412                );
413        }
414
415        private static final class Env {
416
417                private static final String defaultRandomGenerator = get();
418
419                private static String get() {
420                        return getConfigured()
421                                .or(Env::getDefault)
422                                .orElseGet(Env::getBest);
423                }
424
425                private static Optional<String> getConfigured() {
426                        return Optional.ofNullable(
427                                System.getProperty("io.jenetics.util.defaultRandomGenerator")
428                        );
429                }
430
431                private static Optional<String> getDefault() {
432                        return RandomGeneratorFactory.all()
433                                .map(RandomGeneratorFactory::name)
434                                .filter("L64X256MixRandom"::equals)
435                                .findFirst();
436                }
437
438                private static String getBest() {
439                        final var highestStateBits = Comparator
440                                .<RandomGeneratorFactory<?>>comparingInt(RandomGeneratorFactory::stateBits)
441                                .reversed();
442
443                        return RandomGeneratorFactory.all()
444                                .sorted(highestStateBits)
445                                .map(RandomGeneratorFactory::name)
446                                .findFirst()
447                                .orElse("Random");
448                }
449
450        }
451
452}