001/*
002 * Java Genetic Algorithm Library (jenetics-8.1.0).
003 * Copyright (c) 2007-2024 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;
021
022import static java.lang.String.format;
023import static io.jenetics.internal.util.Arrays.shuffle;
024import static io.jenetics.internal.util.Bits.getAndSet;
025import static io.jenetics.internal.util.SerialIO.readInt;
026import static io.jenetics.internal.util.SerialIO.writeInt;
027
028import java.io.IOException;
029import java.io.InvalidObjectException;
030import java.io.ObjectInput;
031import java.io.ObjectInputStream;
032import java.io.ObjectOutput;
033import java.io.Serial;
034import java.io.Serializable;
035import java.util.stream.Collectors;
036import java.util.stream.IntStream;
037
038import io.jenetics.internal.math.Subsets;
039import io.jenetics.internal.util.Bits;
040import io.jenetics.internal.util.Requires;
041import io.jenetics.util.ISeq;
042import io.jenetics.util.IntRange;
043import io.jenetics.util.MSeq;
044import io.jenetics.util.RandomRegistry;
045
046/**
047 * This chromosome can be used to model permutations of a given (sub) set of
048 * alleles.
049 * {@snippet lang="java":
050 * final ISeq<String> alleles = ISeq.of("one", "two", "three", "four", "five");
051 *
052 * // Create a new randomly permuted chromosome from the given alleles.
053 * final PermutationChromosome<String> ch1 = PermutationChromosome.of(alleles);
054 * System.out.println(ch1);
055 * System.out.println(ch1.newInstance());
056 *
057 * // Create a new randomly permuted chromosome from a subset of the given alleles.
058 * final PermutationChromosome<String> ch2 = PermutationChromosome.of(alleles, 3);
059 * System.out.println(ch2);
060 * System.out.println(ch2.newInstance());
061 *
062 * // Console output:
063 * // > three|two|one|five|four
064 * // > two|one|four|five|three
065 * // > three|one|five
066 * // > five|three|one
067 * }
068 *
069 * Usable {@link Alterer} for this chromosome:
070 * <ul>
071 *     <li>{@link PartiallyMatchedCrossover}</li>
072 *     <li>{@link SwapMutator}</li>
073 * </ul>
074 * <p>
075 * <em><b>Implementation note 1:</b>
076 * The factory methods of the {@link AbstractChromosome} has been overridden so
077 * that no invalid permutation will be created.
078 * </em>
079 *
080 * <p>
081 * <em><b>Implementation note 2:</b>
082 * This class uses an algorithm for choosing subsets which is based on a
083 * FORTRAN77 version, originally implemented by Albert Nijenhuis, Herbert Wilf.
084 * The actual Java implementation is based on the  C++ version by John Burkardt.
085 * </em>
086 * <br>
087 * <em><a href="https://people.sc.fsu.edu/~burkardt/c_src/subset/subset.html">
088 *  Reference:</a></em>
089 *   Albert Nijenhuis, Herbert Wilf,
090 *   Combinatorial Algorithms for Computers and Calculators,
091 *   Second Edition,
092 *   Academic Press, 1978,
093 *   ISBN: 0-12-519260-6,
094 *   LC: QA164.N54.
095 * </p>
096 *
097 *
098 * @see EnumGene
099 * @see PartiallyMatchedCrossover
100 * @see SwapMutator
101 *
102 * @implNote
103 * This class is immutable and thread-safe.
104 *
105 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
106 * @since 1.0
107 * @version 6.0
108 */
109public final class PermutationChromosome<T>
110        extends AbstractChromosome<EnumGene<T>>
111        implements Serializable
112{
113        @Serial
114        private static final long serialVersionUID = 2L;
115
116        private final ISeq<T> _validAlleles;
117
118        // Private primary constructor.
119        private PermutationChromosome(
120                final ISeq<EnumGene<T>> genes,
121                final Boolean valid
122        ) {
123                super(genes);
124
125                assert !genes.isEmpty();
126                _validAlleles = genes.get(0).validAlleles();
127                _valid = valid;
128        }
129
130        /**
131         * Create a new {@code PermutationChromosome} from the given {@code genes}.
132         * If the given {@code genes} sequence contains duplicate entries, the
133         * created {@code PermutationChromosome} will be invalid
134         * ({@code ch.isValid() == false}).
135         *
136         * @param genes the enum genes the new chromosome consists of
137         * @throws NullPointerException if the given {@code genes} are null
138         * @throws IllegalArgumentException if the given {@code genes} sequence is
139         *         empty
140         */
141        public PermutationChromosome(final ISeq<EnumGene<T>> genes) {
142                this(genes, null);
143        }
144
145        /**
146         * Return the sequence of the valid alleles of this chromosome.
147         *
148         * @return the sequence of valid alleles of this chromosome
149         */
150        public ISeq<T> validAlleles() {
151                return _validAlleles;
152        }
153
154        /**
155         * Check if this chromosome represents still a valid permutation (or subset)
156         * of the given valid alleles.
157         */
158        @Override
159        public boolean isValid() {
160                if (_valid == null) {
161                        final byte[] check = Bits.newArray(_validAlleles.length());
162                        _valid = _genes.forAll(g -> !getAndSet(check, g.alleleIndex()));
163                }
164
165                return _valid;
166        }
167
168        /**
169         * Create a new, <em>random</em> chromosome.
170         */
171        @Override
172        public PermutationChromosome<T> newInstance() {
173                return of(_validAlleles, length());
174        }
175
176        @Override
177        public PermutationChromosome<T> newInstance(final ISeq<EnumGene<T>> genes) {
178                return new PermutationChromosome<>(genes);
179        }
180
181        @Override
182        public String toString() {
183                return _genes.stream()
184                        .map(g -> g.allele().toString())
185                        .collect(Collectors.joining("|"));
186        }
187
188        /**
189         * Create a new, random chromosome with the given valid alleles and the
190         * desired length.
191         * <p>
192         * The following example shows how to create a {@code PermutationChromosome}
193         * for encoding a subset problem (of a fixed {@code length}).
194         * {@snippet lang="java":
195         * final ISeq<String> basicSet = ISeq.of("a", "b", "c", "d", "e", "f");
196         *
197         * // The chromosome has a length of 3 and will only contain values from the
198         * // given basic-set, with no duplicates.
199         * final PermutationChromosome<String> ch = PermutationChromosome.of(basicSet, 3);
200         * }
201         *
202         * @since 3.4
203         *
204         * @param <T> the allele type
205         * @param alleles the base-set of the valid alleles
206         * @param length the length of the created chromosomes
207         * @return a new chromosome with the given valid alleles and the desired
208         *         length
209         * @throws IllegalArgumentException if {@code alleles.size() < length},
210         *         {@code length <= 0} or {@code alleles.size()*length} will
211         *         cause an integer overflow.
212         * @throws NullPointerException if one of the arguments is {@code null}
213         */
214        public static <T> PermutationChromosome<T> of(
215                final ISeq<? extends T> alleles,
216                final int length
217        ) {
218                Requires.positive(length);
219                if (length > alleles.size()) {
220                        throw new IllegalArgumentException(format(
221                                "The sub-set size must be be greater then the base-set: %d > %d",
222                                length, alleles.size()
223                        ));
224                }
225
226                final var rnd = RandomRegistry.random();
227                final int[] subset = Subsets.next(rnd, alleles.size(), length);
228                shuffle(subset, rnd);
229
230                final ISeq<EnumGene<T>> genes = IntStream.of(subset)
231                        .mapToObj(i -> EnumGene.<T>of(i, alleles))
232                        .collect(ISeq.toISeq());
233
234                return new PermutationChromosome<>(genes, true);
235        }
236
237        /**
238         * Create a new, random chromosome with the given valid alleles.
239         *
240         * @param <T> the gene type of the chromosome
241         * @param alleles the valid alleles used for this permutation arrays.
242         * @return a new chromosome with the given alleles
243         * @throws IllegalArgumentException if the given allele sequence is empty.
244         */
245        public static <T> PermutationChromosome<T>
246        of(final ISeq<? extends T> alleles) {
247                return of(alleles, alleles.size());
248        }
249
250        /**
251         * Create a new, random chromosome with the given valid alleles.
252         *
253         * @since 2.0
254         *
255         * @param <T> the gene type of the chromosome
256         * @param alleles the valid alleles used for this permutation arrays.
257         * @return a new chromosome with the given alleles
258         * @throws IllegalArgumentException if the given allele array is empty.
259         * @throws NullPointerException if one of the alleles is {@code null}
260         */
261        @SafeVarargs
262        public static <T> PermutationChromosome<T> of(final T... alleles) {
263                return of(ISeq.of(alleles));
264        }
265
266        /**
267         * Create a integer permutation chromosome with the given length.
268         *
269         * @param length the chromosome length.
270         * @return a integer permutation chromosome with the given length.
271         * @throws IllegalArgumentException if {@code length <= 0}.
272         */
273        public static PermutationChromosome<Integer> ofInteger(final int length) {
274                return ofInteger(0, Requires.positive(length));
275        }
276
277        /**
278         * Create an integer permutation chromosome with the given range.
279         *
280         * @since 2.0
281         *
282         * @param start the start of the integer range (inclusively) of the returned
283         *        chromosome.
284         * @param end the end of the integer range (exclusively) of the returned
285         *        chromosome.
286         * @return a integer permutation chromosome with the given integer range
287         *         values.
288         * @throws IllegalArgumentException if {@code start >= end} or
289         *         {@code start <= 0}
290         */
291        public static PermutationChromosome<Integer>
292        ofInteger(final int start, final int end) {
293                if (end <= start) {
294                        throw new IllegalArgumentException(format(
295                                "end <= start: %d <= %d", end, start
296                        ));
297                }
298
299                return ofInteger(IntRange.of(start, end), end - start);
300        }
301
302        /**
303         * Create an integer permutation chromosome with the given range and length
304         *
305         * @since 3.4
306         *
307         * @param range the value range
308         * @param length the chromosome length
309         * @return a new integer permutation chromosome
310         * @throws NullPointerException if the given {@code range} is {@code null}
311         * @throws IllegalArgumentException if
312         *         {@code range.getMax() - range.getMin() < length},
313         *         {@code length <= 0} or
314         *         {@code (range.getMax() - range.getMin())*length} will cause an
315         *         integer overflow.
316         */
317        public static PermutationChromosome<Integer>
318        ofInteger(final IntRange range, final int length) {
319                return of(
320                        range.stream().boxed().collect(ISeq.toISeq()),
321                        length
322                );
323        }
324
325        /* *************************************************************************
326         *  Java object serialization
327         * ************************************************************************/
328
329        @Serial
330        private Object writeReplace() {
331                return new SerialProxy(SerialProxy.PERMUTATION_CHROMOSOME, this);
332        }
333
334        @Serial
335        private void readObject(final ObjectInputStream stream)
336                throws InvalidObjectException
337        {
338                throw new InvalidObjectException("Serialization proxy required.");
339        }
340
341        void write(final ObjectOutput out) throws IOException {
342                out.writeObject(_validAlleles);
343                for (EnumGene<?> gene : _genes) {
344                        writeInt(gene.alleleIndex(), out);
345                }
346        }
347
348        @SuppressWarnings({"unchecked", "rawtypes"})
349        static PermutationChromosome read(final ObjectInput in)
350                throws IOException, ClassNotFoundException
351        {
352                final ISeq validAlleles = (ISeq)in.readObject();
353                final MSeq genes = MSeq.ofLength(validAlleles.length());
354                for (int i = 0, n = validAlleles.length(); i < n; ++i) {
355                        genes.set(i, new EnumGene(readInt(in), validAlleles));
356                }
357
358                return new PermutationChromosome(genes.toISeq());
359        }
360
361}