001/*
002 * Java Genetic Algorithm Library (jenetics-8.0.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 java.util.Objects.requireNonNull;
024
025import java.util.stream.IntStream;
026
027import io.jenetics.internal.util.Requires;
028import io.jenetics.util.ISeq;
029import io.jenetics.util.IntRange;
030import io.jenetics.util.MSeq;
031import io.jenetics.util.Seq;
032
033/**
034 * This alterer wraps a given alterer which works on a given section of the
035 * genotype's chromosomes.
036 * {@snippet lang="java":
037 * // The genotype prototype, consisting of 4 chromosomes
038 * final Genotype<DoubleGene> gtf = Genotype.of(
039 *     DoubleChromosome.of(0, 1),
040 *     DoubleChromosome.of(1, 2),
041 *     DoubleChromosome.of(2, 3),
042 *     DoubleChromosome.of(3, 4)
043 * );
044 *
045 * // Define the GA engine.
046 * final Engine<DoubleGene, Double> engine = Engine
047 *     .builder(gt -> gt.getGene().doubleValue(), gtf)
048 *     .selector(new RouletteWheelSelector<>())
049 *     .alterers(
050 *         // The `Mutator` is used on chromosome with index 0 and 2.
051 *         PartialAlterer.of(new Mutator<DoubleGene, Double>(), 0, 2),
052 *         // The `MeanAlterer` is used on chromosome 3.
053 *         PartialAlterer.of(new MeanAlterer<DoubleGene, Double>(), 3),
054 *         // The `GaussianMutator` is used on all chromosomes.
055 *         new GaussianMutator<>()
056 *     )
057 *     .build();
058 * }
059 *
060 * If you are using chromosome indices which are greater or equal than the
061 * number of chromosomes defined in the genotype, a
062 * {@link java.util.concurrent.CompletionException} is thrown when the evolution
063 * stream is evaluated.
064 *
065 * @implNote
066 * This alterer is slower than the performance of the wrapped alterer, because
067 * of the needed <em>sectioning</em> of the genotype.
068 *
069 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
070 * @version 5.0
071 * @since 5.0
072 */
073public final class PartialAlterer<
074        G extends Gene<?, G>,
075        C extends Comparable<? super C>
076>
077        implements Alterer<G, C>
078{
079
080        private final Alterer<G, C> _alterer;
081        private final Projection _projection;
082
083        private PartialAlterer(final Alterer<G, C> alterer, final Projection projection) {
084                _alterer = requireNonNull(alterer);
085                _projection = requireNonNull(projection);
086        }
087
088        @Override
089        public AltererResult<G, C>
090        alter(final Seq<Phenotype<G, C>> population, final long generation) {
091                if (!population.isEmpty()) {
092                        _projection.checkIndices(population.get(0).genotype().length());
093
094                        final var projectedPopulation  = _projection.project(population);
095                        final var result = _alterer.alter(projectedPopulation, generation);
096
097                        return new AltererResult<>(
098                                _projection.merge(result.population(), population),
099                                result.alterations()
100                        );
101                } else {
102                        return new AltererResult<>(population.asISeq());
103                }
104        }
105
106        /**
107         * Wraps the given {@code alterer}, so that it will only work on chromosomes
108         * with the given chromosome indices.
109         *
110         * @see #of(Alterer, IntRange)
111         *
112         * @param alterer the alterer to user for altering the chromosomes with the
113         *        given {@code indices}
114         * @param indices the chromosomes indices (section)
115         * @param <G> the gene type
116         * @param <C> the fitness value type
117         * @return a wrapped alterer which only works for the given chromosome
118         *         section
119         * @throws NullPointerException if the given {@code indices} array is
120         *         {@code null}
121         * @throws IllegalArgumentException if the given {@code indices} array is
122         *         empty
123         * @throws NegativeArraySizeException if one of the given {@code indices} is
124         *         negative
125         */
126        public static <G extends Gene<?, G>, C extends Comparable<? super C>>
127        Alterer<G, C> of(final Alterer<G, C> alterer, final int... indices) {
128                return new PartialAlterer<>(alterer, new Projection(indices));
129        }
130
131        /**
132         * Wraps the given {@code alterer}, so that it will only work on chromosomes
133         * with the given chromosome indices.
134         *
135         * @see #of(Alterer, int...)
136         *
137         * @param alterer the alterer to user for altering the chromosomes with the
138         *        given {@code indices}
139         * @param section the half-open chromosome index range {@code [min, max)}
140         * @param <G> the gene type
141         * @param <C> the fitness value type
142         * @return a wrapped alterer which only works for the given chromosome
143         *         section
144         * @throws NullPointerException if the given {@code indices} array is
145         *         {@code null}
146         * @throws IllegalArgumentException if the given {@code indices} array is
147         *         empty
148         * @throws NegativeArraySizeException if one of the given {@code indices} is
149         *         negative
150         */
151        public static <G extends Gene<?, G>, C extends Comparable<? super C>>
152        Alterer<G, C> of(final Alterer<G, C> alterer, final IntRange section) {
153                return new PartialAlterer<>(
154                        alterer,
155                        new Projection(section.stream().toArray())
156                );
157        }
158
159
160        /**
161         * The section class, which defines the chromosomes used by the alterer.
162         */
163        record Projection(int[] indices) {
164                Projection {
165                        if (indices.length == 0) {
166                                throw new IllegalArgumentException(
167                                        "Chromosome indices must not be empty."
168                                );
169                        }
170                        for (int index : indices) {
171                                Requires.nonNegative(index);
172                        }
173                }
174
175                void checkIndices(final int length) {
176                        for (int index : indices) {
177                                if (index >= length) {
178                                        throw new IndexOutOfBoundsException(format(
179                                                "Genotype contains %d Chromosome, but found " +
180                                                        "SectionAlterer for Chromosome index %d.",
181                                                length, index
182                                        ));
183                                }
184                        }
185                }
186
187                <G extends Gene<?, G>, C extends Comparable<? super C>>
188                Seq<Phenotype<G, C>> project(final Seq<Phenotype<G, C>> population) {
189                        return population.map(this::project);
190                }
191
192                <G extends Gene<?, G>, C extends Comparable<? super C>>
193                Phenotype<G, C> project(final Phenotype<G, C> pt) {
194                        final MSeq<Chromosome<G>> chromosomes = MSeq.ofLength(indices.length);
195                        for (int i = 0; i < indices.length; ++i) {
196                                chromosomes.set(i, pt.genotype().get(indices[i]));
197                        }
198                        final var gt = Genotype.of(chromosomes);
199
200                        return pt.isEvaluated()
201                                ? Phenotype.of(gt, pt.generation(), pt.fitness())
202                                : Phenotype.of(gt, pt.generation());
203                }
204
205                <G extends Gene<?, G>, C extends Comparable<? super C>>
206                ISeq<Phenotype<G, C>> merge(
207                        final Seq<Phenotype<G, C>> projection,
208                        final Seq<Phenotype<G, C>> population
209                ) {
210                        assert projection.length() == population.length();
211
212                        return IntStream.range(0, projection.length())
213                                .mapToObj(i -> merge(projection.get(i), population.get(i)))
214                                .collect(ISeq.toISeq());
215                }
216
217                <G extends Gene<?, G>, C extends Comparable<? super C>>
218                Phenotype<G, C> merge(
219                        final Phenotype<G, C> projection,
220                        final Phenotype<G, C> pt
221                ) {
222                        final var ch = MSeq.of(pt.genotype());
223                        for (int i = 0; i < indices.length; ++i) {
224                                ch.set(indices[i], projection.genotype().get(i));
225                        }
226                        final var gt = Genotype.of(ch);
227
228                        return gt.equals(pt.genotype())
229                                ? pt
230                                : Phenotype.of(gt, pt.generation());
231                }
232
233        }
234
235
236}