001/* 002 * Java Genetic Algorithm Library (jenetics-7.2.0). 003 * Copyright (c) 2007-2023 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 * <pre>{@code 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 * }</pre> 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}