EliteSelector.java
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  */
020 package io.jenetics;
021 
022 import static java.lang.Math.max;
023 import static java.lang.Math.min;
024 import static java.lang.String.format;
025 import static java.util.Objects.requireNonNull;
026 
027 import io.jenetics.internal.util.Requires;
028 import io.jenetics.util.ISeq;
029 import io.jenetics.util.Seq;
030 
031 /**
032  * The {@code EliteSelector} copies a small proportion of the fittest candidates,
033  * without changes, into the next generation. This may have a dramatic impact on
034  * performance by ensuring that the GA doesn't waste time re-discovering
035  * previously refused partial solutions. Individuals that are preserved through
036  * elitism remain eligible for selection as parents of the next generation.
037  * Elitism is also related to memory: remember the best solution found so far.
038  * A problem with elitism is that it may cause the GA to converge to a local
039  * optimum, so pure elitism is a race to the nearest local optimum.
040  *
041  * {@snippet lang="java":
042  * final Selector<DoubleGene, Double> selector = new EliteSelector<>(
043  *     // Number of best individuals preserved for next generation: elites
044  *     3,
045  *     // Selector used for selecting rest of population.
046  *     new RouletteWheelSelector<>()
047  * );
048  * }
049  *
050  @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
051  @version 5.0
052  @since 4.0
053  */
054 public class EliteSelector<
055     extends Gene<?, G>,
056     extends Comparable<? super C>
057 >
058     implements Selector<G, C>
059 {
060     private final TruncationSelector<G, C>
061     ELITE_SELECTOR = new TruncationSelector<>();
062 
063     private final Selector<G, C> _nonEliteSelector;
064     private final int _eliteCount;
065 
066     /**
067      * Create a new elite selector with the desired number of elites to be
068      * selected, and the selector used for selecting the rest of the population.
069      *
070      @param eliteCount the desired number of elite individuals to be selected
071      @param nonEliteSelector the selector used for selecting the rest of the
072      *        population
073      @throws IllegalArgumentException if {@code eliteCount < 1}
074      @throws NullPointerException if the {@code nonEliteSelector} is
075      *         {@code null}
076      */
077     public EliteSelector(
078         final int eliteCount,
079         final Selector<G, C> nonEliteSelector
080     ) {
081         _eliteCount = Requires.positive(eliteCount);
082         _nonEliteSelector = requireNonNull(nonEliteSelector);
083     }
084 
085     /**
086      * Create a new elite selector with the desired number of elites to be
087      * selected. The selector for selecting the rest of the population is
088      * initialized with {@code TournamentSelector<>(3)}.
089      *
090      @see TournamentSelector
091      *
092      @param eliteCount the desired number of elite individuals to be selected
093      @throws IllegalArgumentException if {@code eliteCount < 1}
094      */
095     public EliteSelector(final int eliteCount) {
096         this(eliteCount, new TournamentSelector<>(3));
097     }
098 
099     /**
100      * Create a new elite selector with selector used for selecting the rest of
101      * the population. The elite count is set to 1.
102      *
103      @see TournamentSelector
104      *
105      @param nonEliteSelector the selector used for selecting the rest of the
106      *        population
107      @throws NullPointerException if the {@code nonEliteSelector} is
108      *         {@code null}
109      */
110     public EliteSelector(final Selector<G, C> nonEliteSelector) {
111         this(1, nonEliteSelector);
112     }
113 
114     /**
115      * Create a new elite selector with elite count 1, and the selector for
116      * selecting the rest of the population is initialized with
117      * {@code TournamentSelector<>(3)}
118      */
119     public EliteSelector() {
120         this(1new TournamentSelector<>(3));
121     }
122 
123     @Override
124     public ISeq<Phenotype<G, C>> select(
125         final Seq<Phenotype<G, C>> population,
126         final int count,
127         final Optimize opt
128     ) {
129         if (count < 0) {
130             throw new IllegalArgumentException(format(
131                 "Selection count must be greater or equal then zero, but was %s.",
132                 count
133             ));
134         }
135 
136         ISeq<Phenotype<G, C>> result;
137         if (population.isEmpty() || count <= 0) {
138             result = ISeq.empty();
139         else {
140             final int ec = min(count, _eliteCount);
141             result = ELITE_SELECTOR.select(population, ec, opt);
142             result = result.append(
143                 _nonEliteSelector.select(population, max(0, count - ec), opt)
144             );
145         }
146 
147         return result;
148     }
149 
150     @Override
151     public String toString() {
152         return format("EliteSelector[%d, %s]", _eliteCount, _nonEliteSelector);
153     }
154 
155 }