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