001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. 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 */ 017package org.apache.commons.math4.legacy.genetics; 018 019import java.util.ArrayList; 020import java.util.HashSet; 021import java.util.List; 022import java.util.Set; 023 024import org.apache.commons.math4.legacy.exception.DimensionMismatchException; 025import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException; 026import org.apache.commons.math4.legacy.exception.util.LocalizedFormats; 027 028/** 029 * Cycle Crossover [CX] builds offspring from <b>ordered</b> chromosomes by identifying cycles 030 * between two parent chromosomes. To form the children, the cycles are copied from the 031 * respective parents. 032 * <p> 033 * To form a cycle the following procedure is applied: 034 * <ol> 035 * <li>start with the first gene of parent 1</li> 036 * <li>look at the gene at the same position of parent 2</li> 037 * <li>go to the position with the same gene in parent 1</li> 038 * <li>add this gene index to the cycle</li> 039 * <li>repeat the steps 2-5 until we arrive at the starting gene of this cycle</li> 040 * </ol> 041 * The indices that form a cycle are then used to form the children in alternating order, i.e. 042 * in cycle 1, the genes of parent 1 are copied to child 1, while in cycle 2 the genes of parent 1 043 * are copied to child 2, and so forth ... 044 * 045 * Example (zero-start cycle): 046 * <pre> 047 * p1 = (8 4 7 3 6 2 5 1 9 0) X c1 = (8 1 2 3 4 5 6 7 9 0) 048 * p2 = (0 1 2 3 4 5 6 7 8 9) X c2 = (0 4 7 3 6 2 5 1 8 9) 049 * 050 * cycle 1: 8 0 9 051 * cycle 2: 4 1 7 2 5 6 052 * cycle 3: 3 053 * </pre> 054 * 055 * This policy works only on {@link AbstractListChromosome}, and therefore it 056 * is parameterized by T. Moreover, the chromosomes must have same lengths. 057 * 058 * @see <a href="http://www.rubicite.com/Tutorials/GeneticAlgorithms/CrossoverOperators/CycleCrossoverOperator.aspx"> 059 * Cycle Crossover Operator</a> 060 * 061 * @param <T> generic type of the {@link AbstractListChromosome}s for crossover 062 * @since 3.1 063 */ 064public class CycleCrossover<T> implements CrossoverPolicy { 065 066 /** If the start index shall be chosen randomly. */ 067 private final boolean randomStart; 068 069 /** 070 * Creates a new {@link CycleCrossover} policy. 071 */ 072 public CycleCrossover() { 073 this(false); 074 } 075 076 /** 077 * Creates a new {@link CycleCrossover} policy using the given {@code randomStart} behavior. 078 * 079 * @param randomStart whether the start index shall be chosen randomly or be set to 0 080 */ 081 public CycleCrossover(final boolean randomStart) { 082 this.randomStart = randomStart; 083 } 084 085 /** 086 * Returns whether the starting index is chosen randomly or set to zero. 087 * 088 * @return {@code true} if the starting index is chosen randomly, {@code false} otherwise 089 */ 090 public boolean isRandomStart() { 091 return randomStart; 092 } 093 094 /** 095 * {@inheritDoc} 096 * 097 * @throws MathIllegalArgumentException if the chromosomes are not an instance of {@link AbstractListChromosome} 098 * @throws DimensionMismatchException if the length of the two chromosomes is different 099 */ 100 @Override 101 @SuppressWarnings("unchecked") 102 public ChromosomePair crossover(final Chromosome first, final Chromosome second) 103 throws DimensionMismatchException, MathIllegalArgumentException { 104 105 if (!(first instanceof AbstractListChromosome<?> && second instanceof AbstractListChromosome<?>)) { 106 throw new MathIllegalArgumentException(LocalizedFormats.INVALID_FIXED_LENGTH_CHROMOSOME); 107 } 108 return mate((AbstractListChromosome<T>) first, (AbstractListChromosome<T>) second); 109 } 110 111 /** 112 * Helper for {@link #crossover(Chromosome, Chromosome)}. Performs the actual crossover. 113 * 114 * @param first the first chromosome 115 * @param second the second chromosome 116 * @return the pair of new chromosomes that resulted from the crossover 117 * @throws DimensionMismatchException if the length of the two chromosomes is different 118 */ 119 protected ChromosomePair mate(final AbstractListChromosome<T> first, final AbstractListChromosome<T> second) 120 throws DimensionMismatchException { 121 122 final int length = first.getLength(); 123 if (length != second.getLength()) { 124 throw new DimensionMismatchException(second.getLength(), length); 125 } 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: do a crossover copy to simplify the later processing 131 final List<T> child1Rep = new ArrayList<>(second.getRepresentation()); 132 final List<T> child2Rep = new ArrayList<>(first.getRepresentation()); 133 134 // the set of all visited indices so far 135 final Set<Integer> visitedIndices = new HashSet<>(length); 136 // the indices of the current cycle 137 final List<Integer> indices = new ArrayList<>(length); 138 139 // determine the starting index 140 int idx = randomStart ? GeneticAlgorithm.getRandomGenerator().nextInt(length) : 0; 141 int cycle = 1; 142 143 while (visitedIndices.size() < length) { 144 indices.add(idx); 145 146 T item = parent2Rep.get(idx); 147 idx = parent1Rep.indexOf(item); 148 149 while (idx != indices.get(0)) { 150 // add that index to the cycle indices 151 indices.add(idx); 152 // get the item in the second parent at that index 153 item = parent2Rep.get(idx); 154 // get the index of that item in the first parent 155 idx = parent1Rep.indexOf(item); 156 } 157 158 // for odd cycles: swap the child elements on the indices found in this cycle 159 if ((cycle++ & 1) != 0) { 160 for (int i : indices) { 161 T tmp = child1Rep.get(i); 162 child1Rep.set(i, child2Rep.get(i)); 163 child2Rep.set(i, tmp); 164 } 165 } 166 167 visitedIndices.addAll(indices); 168 // find next starting index: last one + 1 until we find an unvisited index 169 idx = (indices.get(0) + 1) % length; 170 while (visitedIndices.contains(idx) && visitedIndices.size() < length) { 171 idx++; 172 if (idx >= length) { 173 idx = 0; 174 } 175 } 176 indices.clear(); 177 } 178 179 return new ChromosomePair(first.newFixedLengthChromosome(child1Rep), 180 second.newFixedLengthChromosome(child2Rep)); 181 } 182}