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.ext.moea; 021 022import static java.lang.String.format; 023import static java.util.Objects.requireNonNull; 024import static io.jenetics.ext.moea.Pareto.front; 025 026import java.util.Comparator; 027import java.util.Objects; 028import java.util.function.ToIntFunction; 029import java.util.stream.Collector; 030 031import io.jenetics.Gene; 032import io.jenetics.Optimize; 033import io.jenetics.Phenotype; 034import io.jenetics.engine.EvolutionResult; 035import io.jenetics.util.ISeq; 036import io.jenetics.util.IntRange; 037 038/** 039 * Collectors for collecting final <em>pareto-set</em> for multi-objective 040 * optimization. 041 * 042 * {@snippet lang="java": 043 * final Problem<double[], DoubleGene, Vec<double[]>> problem = Problem.of( 044 * v -> Vec.of(v[0]*cos(v[1]), v[0]*sin(v[1])), 045 * Codecs.ofVector( 046 * DoubleRange.of(0, 1), 047 * DoubleRange.of(0, 2*PI) 048 * ) 049 * ); 050 * 051 * final Engine<DoubleGene, Vec<double[]>> engine = Engine.builder(problem) 052 * .alterers( 053 * new Mutator<>(0.1), 054 * new MeanAlterer<>()) 055 * .offspringSelector(new TournamentSelector<>(2)) 056 * .survivorsSelector(UFTournamentSelector.ofVec()) 057 * .build(); 058 * 059 * final ISeq<Phenotype<DoubleGene, Vec<double[]>>> result = engine.stream() 060 * .limit(Limits.byFixedGeneration(50)) 061 * .collect(MOEA.toParetoSet()); 062 * } 063 * 064 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 065 * @version 5.1 066 * @since 4.1 067 */ 068public final class MOEA { 069 070 private static final IntRange DEFAULT_SET_RANGE = IntRange.of(75, 100); 071 072 private MOEA() { 073 } 074 075 /** 076 * Collector of {@link Phenotype} objects, who's (multi-objective) fitness 077 * value is part of the <a href="https://en.wikipedia.org/wiki/Pareto_efficiency"> 078 * pareto front</a>. 079 * 080 * @param <G> the gene type 081 * @param <T> the array type, e.g. {@code double[]} 082 * @param <V> the multi object result type vector 083 * @return the pareto set collector 084 * @throws IllegalArgumentException if the minimal pareto set {@code size} 085 * is smaller than one 086 */ 087 public static <G extends Gene<?, G>, T, V extends Vec<T>> 088 Collector<EvolutionResult<G, V>, ?, ISeq<Phenotype<G, V>>> 089 toParetoSet() { 090 return toParetoSet(DEFAULT_SET_RANGE); 091 } 092 093 /** 094 * Collector of {@link Phenotype} objects, who's (multi-objective) fitness 095 * value is part of the <a href="https://en.wikipedia.org/wiki/Pareto_efficiency"> 096 * pareto front</a>. 097 * 098 * @param size the allowed size range of the returned pareto set. If the 099 * size of the pareto set is bigger than {@code size.getMax()}, 100 * during the collection, it is reduced to {@code size.getMin()}. 101 * Pareto set elements which are close to each other are removed first. 102 * @param <G> the gene type 103 * @param <T> the array type, e.g. {@code double[]} 104 * @param <V> the multi object result type vector 105 * @return the pareto set collector 106 * @throws NullPointerException if one the {@code size} is {@code null} 107 * @throws IllegalArgumentException if the minimal pareto set {@code size} 108 * is smaller than one 109 */ 110 public static <G extends Gene<?, G>, T, V extends Vec<T>> 111 Collector<EvolutionResult<G, V>, ?, ISeq<Phenotype<G, V>>> 112 toParetoSet(final IntRange size) { 113 return toParetoSet( 114 size, 115 Vec::dominance, 116 Vec::compare, 117 Vec::distance, 118 Vec::length 119 ); 120 } 121 122 /** 123 * Collector of {@link Phenotype} objects, who's (multi-objective) fitness 124 * value is part of the <a href="https://en.wikipedia.org/wiki/Pareto_efficiency"> 125 * pareto front</a>. 126 * 127 * @see #toParetoSet(IntRange) 128 * 129 * @param size the allowed size range of the returned pareto set. If the 130 * size of the pareto set is bigger than {@code size.getMax()}, 131 * during the collection, it is reduced to {@code size.getMin()}. 132 * Pareto set elements which are close to each other are removed first. 133 * @param dominance the pareto dominance measure of the fitness result type 134 * {@code C} 135 * @param comparator the comparator of the elements of the vector type 136 * {@code C} 137 * @param distance the distance function of two elements of the vector 138 * type {@code C} 139 * @param dimension the dimensionality of the result vector {@code C}. 140 * Usually {@code Vec::length}. 141 * @param <G> the gene type 142 * @param <C> the multi object result vector. E.g. {@code Vec<double[]>} 143 * @return the pareto set collector 144 * @throws NullPointerException if one the arguments is {@code null} 145 * @throws IllegalArgumentException if the minimal pareto set {@code size} 146 * is smaller than one 147 */ 148 public static <G extends Gene<?, G>, C extends Comparable<? super C>> 149 Collector<EvolutionResult<G, C>, ?, ISeq<Phenotype<G, C>>> 150 toParetoSet( 151 final IntRange size, 152 final Comparator<? super C> dominance, 153 final ElementComparator<? super C> comparator, 154 final ElementDistance<? super C> distance, 155 final ToIntFunction<? super C> dimension 156 ) { 157 requireNonNull(size); 158 requireNonNull(dominance); 159 requireNonNull(distance); 160 161 if (size.min() < 1) { 162 throw new IllegalArgumentException(format( 163 "Minimal pareto set size must be greater than zero: %d", 164 size.min() 165 )); 166 } 167 168 return Collector.of( 169 () -> new Front<G, C>( 170 size, dominance, comparator, distance, dimension 171 ), 172 Front::add, 173 Front::merge, 174 Front::toISeq 175 ); 176 } 177 178 private static final class Front< 179 G extends Gene<?, G>, 180 C extends Comparable<? super C> 181 > { 182 183 final IntRange _size; 184 final Comparator<? super C> _dominance; 185 final ElementComparator<? super C> _comparator; 186 final ElementDistance<? super C> _distance; 187 final ToIntFunction<? super C> _dimension; 188 189 private Optimize _optimize; 190 private ParetoFront<Phenotype<G, C>> _front; 191 192 Front( 193 final IntRange size, 194 final Comparator<? super C> dominance, 195 final ElementComparator<? super C> comparator, 196 final ElementDistance<? super C> distance, 197 final ToIntFunction<? super C> dimension 198 ) { 199 _size = size; 200 _dominance = dominance; 201 _comparator = comparator; 202 _distance = distance; 203 _dimension = dimension; 204 } 205 206 void add(final EvolutionResult<G, C> result) { 207 if (_front == null) { 208 _optimize = result.optimize(); 209 _front = new ParetoFront<>(this::dominance, this::equals); 210 } 211 212 final ISeq<Phenotype<G, C>> front = front( 213 result.population(), 214 this::dominance 215 ); 216 _front.addAll(front.asList()); 217 trim(); 218 } 219 220 private int dominance(final Phenotype<G, C> a, final Phenotype<G, C> b) { 221 return _optimize == Optimize.MAXIMUM 222 ? _dominance.compare(a.fitness(), b.fitness()) 223 : _dominance.compare(b.fitness(), a.fitness()); 224 } 225 226 private boolean equals(final Phenotype<?, ?> a, final Phenotype<?, ?> b) { 227 return Objects.equals(a.genotype(), b.genotype()); 228 } 229 230 private void trim() { 231 assert _front != null; 232 assert _optimize != null; 233 234 if (_front.size() > _size.max() - 1) { 235 _front.trim( 236 _size.min(), 237 this::compare, 238 _distance.map(Phenotype::fitness), 239 v -> _dimension.applyAsInt(v.fitness()) 240 ); 241 } 242 } 243 244 private int compare( 245 final Phenotype<G, C> a, 246 final Phenotype<G, C> b, 247 final int i 248 ) { 249 return _optimize == Optimize.MAXIMUM 250 ? _comparator.compare(a.fitness(), b.fitness(), i) 251 : _comparator.compare(b.fitness(), a.fitness(), i); 252 } 253 254 Front<G, C> merge(final Front<G, C> front) { 255 _front.merge(front._front); 256 trim(); 257 return this; 258 } 259 260 ISeq<Phenotype<G, C>> toISeq() { 261 return _front != null ? _front.toISeq() : ISeq.empty(); 262 } 263 264 } 265 266}