CycleCrossover.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.HashSet;
  20. import java.util.List;
  21. import java.util.Set;

  22. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  23. import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
  24. import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;

  25. /**
  26.  * Cycle Crossover [CX] builds offspring from <b>ordered</b> chromosomes by identifying cycles
  27.  * between two parent chromosomes. To form the children, the cycles are copied from the
  28.  * respective parents.
  29.  * <p>
  30.  * To form a cycle the following procedure is applied:
  31.  * <ol>
  32.  *   <li>start with the first gene of parent 1</li>
  33.  *   <li>look at the gene at the same position of parent 2</li>
  34.  *   <li>go to the position with the same gene in parent 1</li>
  35.  *   <li>add this gene index to the cycle</li>
  36.  *   <li>repeat the steps 2-5 until we arrive at the starting gene of this cycle</li>
  37.  * </ol>
  38.  * The indices that form a cycle are then used to form the children in alternating order, i.e.
  39.  * in cycle 1, the genes of parent 1 are copied to child 1, while in cycle 2 the genes of parent 1
  40.  * are copied to child 2, and so forth ...
  41.  *
  42.  * Example (zero-start cycle):
  43.  * <pre>
  44.  * p1 = (8 4 7 3 6 2 5 1 9 0)    X   c1 = (8 1 2 3 4 5 6 7 9 0)
  45.  * p2 = (0 1 2 3 4 5 6 7 8 9)    X   c2 = (0 4 7 3 6 2 5 1 8 9)
  46.  *
  47.  * cycle 1: 8 0 9
  48.  * cycle 2: 4 1 7 2 5 6
  49.  * cycle 3: 3
  50.  * </pre>
  51.  *
  52.  * This policy works only on {@link AbstractListChromosome}, and therefore it
  53.  * is parameterized by T. Moreover, the chromosomes must have same lengths.
  54.  *
  55.  * @see <a href="http://www.rubicite.com/Tutorials/GeneticAlgorithms/CrossoverOperators/CycleCrossoverOperator.aspx">
  56.  * Cycle Crossover Operator</a>
  57.  *
  58.  * @param <T> generic type of the {@link AbstractListChromosome}s for crossover
  59.  * @since 3.1
  60.  */
  61. public class CycleCrossover<T> implements CrossoverPolicy {

  62.     /** If the start index shall be chosen randomly. */
  63.     private final boolean randomStart;

  64.     /**
  65.      * Creates a new {@link CycleCrossover} policy.
  66.      */
  67.     public CycleCrossover() {
  68.         this(false);
  69.     }

  70.     /**
  71.      * Creates a new {@link CycleCrossover} policy using the given {@code randomStart} behavior.
  72.      *
  73.      * @param randomStart whether the start index shall be chosen randomly or be set to 0
  74.      */
  75.     public CycleCrossover(final boolean randomStart) {
  76.         this.randomStart = randomStart;
  77.     }

  78.     /**
  79.      * Returns whether the starting index is chosen randomly or set to zero.
  80.      *
  81.      * @return {@code true} if the starting index is chosen randomly, {@code false} otherwise
  82.      */
  83.     public boolean isRandomStart() {
  84.         return randomStart;
  85.     }

  86.     /**
  87.      * {@inheritDoc}
  88.      *
  89.      * @throws MathIllegalArgumentException if the chromosomes are not an instance of {@link AbstractListChromosome}
  90.      * @throws DimensionMismatchException if the length of the two chromosomes is different
  91.      */
  92.     @Override
  93.     @SuppressWarnings("unchecked")
  94.     public ChromosomePair crossover(final Chromosome first, final Chromosome second)
  95.         throws DimensionMismatchException, MathIllegalArgumentException {

  96.         if (!(first instanceof AbstractListChromosome<?> && second instanceof AbstractListChromosome<?>)) {
  97.             throw new MathIllegalArgumentException(LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME);
  98.         }
  99.         return mate((AbstractListChromosome<T>) first, (AbstractListChromosome<T>) second);
  100.     }

  101.     /**
  102.      * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover.
  103.      *
  104.      * @param first the first chromosome
  105.      * @param second the second chromosome
  106.      * @return the pair of new chromosomes that resulted from the crossover
  107.      * @throws DimensionMismatchException if the length of the two chromosomes is different
  108.      */
  109.     protected ChromosomePair mate(final AbstractListChromosome<T> first, final AbstractListChromosome<T> second)
  110.         throws DimensionMismatchException {

  111.         final int length = first.getLength();
  112.         if (length != second.getLength()) {
  113.             throw new DimensionMismatchException(second.getLength(), length);
  114.         }

  115.         // array representations of the parents
  116.         final List<T> parent1Rep = first.getRepresentation();
  117.         final List<T> parent2Rep = second.getRepresentation();
  118.         // and of the children: do a crossover copy to simplify the later processing
  119.         final List<T> child1Rep = new ArrayList<>(second.getRepresentation());
  120.         final List<T> child2Rep = new ArrayList<>(first.getRepresentation());

  121.         // the set of all visited indices so far
  122.         final Set<Integer> visitedIndices = new HashSet<>(length);
  123.         // the indices of the current cycle
  124.         final List<Integer> indices = new ArrayList<>(length);

  125.         // determine the starting index
  126.         int idx = randomStart ? GeneticAlgorithm.getRandomGenerator().nextInt(length) : 0;
  127.         int cycle = 1;

  128.         while (visitedIndices.size() < length) {
  129.             indices.add(idx);

  130.             T item = parent2Rep.get(idx);
  131.             idx = parent1Rep.indexOf(item);

  132.             while (idx != indices.get(0)) {
  133.                 // add that index to the cycle indices
  134.                 indices.add(idx);
  135.                 // get the item in the second parent at that index
  136.                 item = parent2Rep.get(idx);
  137.                 // get the index of that item in the first parent
  138.                 idx = parent1Rep.indexOf(item);
  139.             }

  140.             // for odd cycles: swap the child elements on the indices found in this cycle
  141.             if ((cycle++ & 1) != 0) {
  142.                 for (int i : indices) {
  143.                     T tmp = child1Rep.get(i);
  144.                     child1Rep.set(i, child2Rep.get(i));
  145.                     child2Rep.set(i, tmp);
  146.                 }
  147.             }

  148.             visitedIndices.addAll(indices);
  149.             // find next starting index: last one + 1 until we find an unvisited index
  150.             idx = (indices.get(0) + 1) % length;
  151.             while (visitedIndices.contains(idx) && visitedIndices.size() < length) {
  152.                 idx++;
  153.                 if (idx >= length) {
  154.                     idx = 0;
  155.                 }
  156.             }
  157.             indices.clear();
  158.         }

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