NPointCrossover.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.math4.legacy.genetics;

  18. import java.util.ArrayList;
  19. import java.util.List;

  20. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  21. import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
  22. import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
  23. import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
  24. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
  25. import org.apache.commons.rng.UniformRandomProvider;

  26. /**
  27.  * N-point crossover policy. For each iteration a random crossover point is
  28.  * selected and the first part from each parent is copied to the corresponding
  29.  * child, and the second parts are copied crosswise.
  30.  *
  31.  * Example (2-point crossover):
  32.  * <pre>
  33.  * -C- denotes a crossover point
  34.  *           -C-       -C-                         -C-        -C-
  35.  * p1 = (1 0  | 1 0 0 1 | 0 1 1)    X    p2 = (0 1  | 1 0 1 0  | 1 1 1)
  36.  *      \----/ \-------/ \-----/              \----/ \--------/ \-----/
  37.  *        ||      (*)       ||                  ||      (**)       ||
  38.  *        VV      (**)      VV                  VV      (*)        VV
  39.  *      /----\ /--------\ /-----\             /----\ /--------\ /-----\
  40.  * c1 = (1 0  | 1 0 1 0  | 0 1 1)    X   c2 = (0 1  | 1 0 0 1  | 0 1 1)
  41.  * </pre>
  42.  *
  43.  * This policy works only on {@link AbstractListChromosome}, and therefore it
  44.  * is parameterized by T. Moreover, the chromosomes must have same lengths.
  45.  *
  46.  * @param <T> generic type of the {@link AbstractListChromosome}s for crossover
  47.  * @since 3.1
  48.  */
  49. public class NPointCrossover<T> implements CrossoverPolicy {

  50.     /** The number of crossover points. */
  51.     private final int crossoverPoints;

  52.     /**
  53.      * Creates a new {@link NPointCrossover} policy using the given number of points.
  54.      * <p>
  55.      * <b>Note</b>: the number of crossover points must be &lt; <code>chromosome length - 1</code>.
  56.      * This condition can only be checked at runtime, as the chromosome length is not known in advance.
  57.      *
  58.      * @param crossoverPoints the number of crossover points
  59.      * @throws NotStrictlyPositiveException if the number of {@code crossoverPoints} is not strictly positive
  60.      */
  61.     public NPointCrossover(final int crossoverPoints) throws NotStrictlyPositiveException {
  62.         if (crossoverPoints <= 0) {
  63.             throw new NotStrictlyPositiveException(crossoverPoints);
  64.         }
  65.         this.crossoverPoints = crossoverPoints;
  66.     }

  67.     /**
  68.      * Returns the number of crossover points used by this {@link CrossoverPolicy}.
  69.      *
  70.      * @return the number of crossover points
  71.      */
  72.     public int getCrossoverPoints() {
  73.         return crossoverPoints;
  74.     }

  75.     /**
  76.      * Performs a N-point crossover. N random crossover points are selected and are used
  77.      * to divide the parent chromosomes into segments. The segments are copied in alternate
  78.      * order from the two parents to the corresponding child chromosomes.
  79.      *
  80.      * Example (2-point crossover):
  81.      * <pre>
  82.      * -C- denotes a crossover point
  83.      *           -C-       -C-                         -C-        -C-
  84.      * p1 = (1 0  | 1 0 0 1 | 0 1 1)    X    p2 = (0 1  | 1 0 1 0  | 1 1 1)
  85.      *      \----/ \-------/ \-----/              \----/ \--------/ \-----/
  86.      *        ||      (*)       ||                  ||      (**)       ||
  87.      *        VV      (**)      VV                  VV      (*)        VV
  88.      *      /----\ /--------\ /-----\             /----\ /--------\ /-----\
  89.      * c1 = (1 0  | 1 0 1 0  | 0 1 1)    X   c2 = (0 1  | 1 0 0 1  | 0 1 1)
  90.      * </pre>
  91.      *
  92.      * @param first first parent (p1)
  93.      * @param second second parent (p2)
  94.      * @return pair of two children (c1,c2)
  95.      * @throws MathIllegalArgumentException iff one of the chromosomes is
  96.      *   not an instance of {@link AbstractListChromosome}
  97.      * @throws DimensionMismatchException if the length of the two chromosomes is different
  98.      */
  99.     @Override
  100.     @SuppressWarnings("unchecked") // OK because of instanceof checks
  101.     public ChromosomePair crossover(final Chromosome first, final Chromosome second)
  102.         throws DimensionMismatchException, MathIllegalArgumentException {

  103.         if (!(first instanceof AbstractListChromosome<?> && second instanceof AbstractListChromosome<?>)) {
  104.             throw new MathIllegalArgumentException(LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME);
  105.         }
  106.         return mate((AbstractListChromosome<T>) first, (AbstractListChromosome<T>) second);
  107.     }

  108.     /**
  109.      * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover.
  110.      *
  111.      * @param first the first chromosome
  112.      * @param second the second chromosome
  113.      * @return the pair of new chromosomes that resulted from the crossover
  114.      * @throws DimensionMismatchException if the length of the two chromosomes is different
  115.      * @throws NumberIsTooLargeException if the number of crossoverPoints is too large for the actual chromosomes
  116.      */
  117.     private ChromosomePair mate(final AbstractListChromosome<T> first,
  118.                                 final AbstractListChromosome<T> second)
  119.         throws DimensionMismatchException, NumberIsTooLargeException {

  120.         final int length = first.getLength();
  121.         if (length != second.getLength()) {
  122.             throw new DimensionMismatchException(second.getLength(), length);
  123.         }
  124.         if (crossoverPoints >= length) {
  125.             throw new NumberIsTooLargeException(crossoverPoints, length, false);
  126.         }

  127.         // array representations of the parents
  128.         final List<T> parent1Rep = first.getRepresentation();
  129.         final List<T> parent2Rep = second.getRepresentation();
  130.         // and of the children
  131.         final List<T> child1Rep = new ArrayList<>(length);
  132.         final List<T> child2Rep = new ArrayList<>(length);

  133.         final UniformRandomProvider random = GeneticAlgorithm.getRandomGenerator();

  134.         List<T> c1 = child1Rep;
  135.         List<T> c2 = child2Rep;

  136.         int remainingPoints = crossoverPoints;
  137.         int lastIndex = 0;
  138.         for (int i = 0; i < crossoverPoints; i++, remainingPoints--) {
  139.             // select the next crossover point at random
  140.             final int crossoverIndex = 1 + lastIndex + random.nextInt(length - lastIndex - remainingPoints);

  141.             // copy the current segment
  142.             for (int j = lastIndex; j < crossoverIndex; j++) {
  143.                 c1.add(parent1Rep.get(j));
  144.                 c2.add(parent2Rep.get(j));
  145.             }

  146.             // swap the children for the next segment
  147.             List<T> tmp = c1;
  148.             c1 = c2;
  149.             c2 = tmp;

  150.             lastIndex = crossoverIndex;
  151.         }

  152.         // copy the last segment
  153.         for (int j = lastIndex; j < length; j++) {
  154.             c1.add(parent1Rep.get(j));
  155.             c2.add(parent2Rep.get(j));
  156.         }

  157.         return new ChromosomePair(first.newFixedLengthChromosome(child1Rep),
  158.                                   second.newFixedLengthChromosome(child2Rep));
  159.     }
  160. }