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.math3.genetics; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.List; 024 025import org.apache.commons.math3.exception.DimensionMismatchException; 026import org.apache.commons.math3.exception.MathIllegalArgumentException; 027import org.apache.commons.math3.exception.util.LocalizedFormats; 028 029/** 030 * Random Key chromosome is used for permutation representation. It is a vector 031 * of a fixed length of real numbers in [0,1] interval. The index of the i-th 032 * smallest value in the vector represents an i-th member of the permutation. 033 * <p> 034 * For example, the random key [0.2, 0.3, 0.8, 0.1] corresponds to the 035 * permutation of indices (3,0,1,2). If the original (unpermuted) sequence would 036 * be (a,b,c,d), this would mean the sequence (d,a,b,c). 037 * <p> 038 * With this representation, common operators like n-point crossover can be 039 * used, because any such chromosome represents a valid permutation. 040 * <p> 041 * Since the chromosome (and thus its arrayRepresentation) is immutable, the 042 * array representation is sorted only once in the constructor. 043 * <p> 044 * For details, see: 045 * <ul> 046 * <li>Bean, J.C.: Genetic algorithms and random keys for sequencing and 047 * optimization. ORSA Journal on Computing 6 (1994) 154-160</li> 048 * <li>Rothlauf, F.: Representations for Genetic and Evolutionary Algorithms. 049 * Volume 104 of Studies in Fuzziness and Soft Computing. Physica-Verlag, 050 * Heidelberg (2002)</li> 051 * </ul> 052 * 053 * @param <T> type of the permuted objects 054 * @since 2.0 055 */ 056public abstract class RandomKey<T> extends AbstractListChromosome<Double> implements PermutationChromosome<T> { 057 058 /** Cache of sorted representation (unmodifiable). */ 059 private final List<Double> sortedRepresentation; 060 061 /** 062 * Base sequence [0,1,...,n-1], permuted according to the representation (unmodifiable). 063 */ 064 private final List<Integer> baseSeqPermutation; 065 066 /** 067 * Constructor. 068 * 069 * @param representation list of [0,1] values representing the permutation 070 * @throws InvalidRepresentationException iff the <code>representation</code> can not represent a valid chromosome 071 */ 072 public RandomKey(final List<Double> representation) throws InvalidRepresentationException { 073 super(representation); 074 // store the sorted representation 075 List<Double> sortedRepr = new ArrayList<Double> (getRepresentation()); 076 Collections.sort(sortedRepr); 077 sortedRepresentation = Collections.unmodifiableList(sortedRepr); 078 // store the permutation of [0,1,...,n-1] list for toString() and isSame() methods 079 baseSeqPermutation = Collections.unmodifiableList( 080 decodeGeneric(baseSequence(getLength()), getRepresentation(), sortedRepresentation) 081 ); 082 } 083 084 /** 085 * Constructor. 086 * 087 * @param representation array of [0,1] values representing the permutation 088 * @throws InvalidRepresentationException iff the <code>representation</code> can not represent a valid chromosome 089 */ 090 public RandomKey(final Double[] representation) throws InvalidRepresentationException { 091 this(Arrays.asList(representation)); 092 } 093 094 /** 095 * {@inheritDoc} 096 */ 097 public List<T> decode(final List<T> sequence) { 098 return decodeGeneric(sequence, getRepresentation(), sortedRepresentation); 099 } 100 101 /** 102 * Decodes a permutation represented by <code>representation</code> and 103 * returns a (generic) list with the permuted values. 104 * 105 * @param <S> generic type of the sequence values 106 * @param sequence the unpermuted sequence 107 * @param representation representation of the permutation ([0,1] vector) 108 * @param sortedRepr sorted <code>representation</code> 109 * @return list with the sequence values permuted according to the representation 110 * @throws DimensionMismatchException iff the length of the <code>sequence</code>, 111 * <code>representation</code> or <code>sortedRepr</code> lists are not equal 112 */ 113 private static <S> List<S> decodeGeneric(final List<S> sequence, List<Double> representation, 114 final List<Double> sortedRepr) 115 throws DimensionMismatchException { 116 117 int l = sequence.size(); 118 119 // the size of the three lists must be equal 120 if (representation.size() != l) { 121 throw new DimensionMismatchException(representation.size(), l); 122 } 123 if (sortedRepr.size() != l) { 124 throw new DimensionMismatchException(sortedRepr.size(), l); 125 } 126 127 // do not modify the original representation 128 List<Double> reprCopy = new ArrayList<Double> (representation); 129 130 // now find the indices in the original repr and use them for permuting 131 List<S> res = new ArrayList<S> (l); 132 for (int i=0; i<l; i++) { 133 int index = reprCopy.indexOf(sortedRepr.get(i)); 134 res.add(sequence.get(index)); 135 reprCopy.set(index, null); 136 } 137 return res; 138 } 139 140 /** 141 * Returns <code>true</code> iff <code>another</code> is a RandomKey and 142 * encodes the same permutation. 143 * 144 * @param another chromosome to compare 145 * @return true iff chromosomes encode the same permutation 146 */ 147 @Override 148 protected boolean isSame(final Chromosome another) { 149 // type check 150 if (! (another instanceof RandomKey<?>)) { 151 return false; 152 } 153 RandomKey<?> anotherRk = (RandomKey<?>) another; 154 // size check 155 if (getLength() != anotherRk.getLength()) { 156 return false; 157 } 158 159 // two different representations can still encode the same permutation 160 // the ordering is what counts 161 List<Integer> thisPerm = this.baseSeqPermutation; 162 List<Integer> anotherPerm = anotherRk.baseSeqPermutation; 163 164 for (int i=0; i<getLength(); i++) { 165 if (thisPerm.get(i) != anotherPerm.get(i)) { 166 return false; 167 } 168 } 169 // the permutations are the same 170 return true; 171 } 172 173 /** 174 * {@inheritDoc} 175 */ 176 @Override 177 protected void checkValidity(final List<Double> chromosomeRepresentation) 178 throws InvalidRepresentationException { 179 180 for (double val : chromosomeRepresentation) { 181 if (val < 0 || val > 1) { 182 throw new InvalidRepresentationException(LocalizedFormats.OUT_OF_RANGE_SIMPLE, 183 val, 0, 1); 184 } 185 } 186 } 187 188 189 /** 190 * Generates a representation corresponding to a random permutation of 191 * length l which can be passed to the RandomKey constructor. 192 * 193 * @param l length of the permutation 194 * @return representation of a random permutation 195 */ 196 public static final List<Double> randomPermutation(final int l) { 197 List<Double> repr = new ArrayList<Double>(l); 198 for (int i=0; i<l; i++) { 199 repr.add(GeneticAlgorithm.getRandomGenerator().nextDouble()); 200 } 201 return repr; 202 } 203 204 /** 205 * Generates a representation corresponding to an identity permutation of 206 * length l which can be passed to the RandomKey constructor. 207 * 208 * @param l length of the permutation 209 * @return representation of an identity permutation 210 */ 211 public static final List<Double> identityPermutation(final int l) { 212 List<Double> repr = new ArrayList<Double>(l); 213 for (int i=0; i<l; i++) { 214 repr.add((double)i/l); 215 } 216 return repr; 217 } 218 219 /** 220 * Generates a representation of a permutation corresponding to the 221 * <code>data</code> sorted by <code>comparator</code>. The 222 * <code>data</code> is not modified during the process. 223 * 224 * This is useful if you want to inject some permutations to the initial 225 * population. 226 * 227 * @param <S> type of the data 228 * @param data list of data determining the order 229 * @param comparator how the data will be compared 230 * @return list representation of the permutation corresponding to the parameters 231 */ 232 public static <S> List<Double> comparatorPermutation(final List<S> data, 233 final Comparator<S> comparator) { 234 List<S> sortedData = new ArrayList<S>(data); 235 Collections.sort(sortedData, comparator); 236 237 return inducedPermutation(data, sortedData); 238 } 239 240 /** 241 * Generates a representation of a permutation corresponding to a 242 * permutation which yields <code>permutedData</code> when applied to 243 * <code>originalData</code>. 244 * 245 * This method can be viewed as an inverse to {@link #decode(List)}. 246 * 247 * @param <S> type of the data 248 * @param originalData the original, unpermuted data 249 * @param permutedData the data, somehow permuted 250 * @return representation of a permutation corresponding to the permutation 251 * <code>originalData -> permutedData</code> 252 * @throws DimensionMismatchException iff the length of <code>originalData</code> 253 * and <code>permutedData</code> lists are not equal 254 * @throws MathIllegalArgumentException iff the <code>permutedData</code> and 255 * <code>originalData</code> lists contain different data 256 */ 257 public static <S> List<Double> inducedPermutation(final List<S> originalData, 258 final List<S> permutedData) 259 throws DimensionMismatchException, MathIllegalArgumentException { 260 261 if (originalData.size() != permutedData.size()) { 262 throw new DimensionMismatchException(permutedData.size(), originalData.size()); 263 } 264 int l = originalData.size(); 265 266 List<S> origDataCopy = new ArrayList<S> (originalData); 267 268 Double[] res = new Double[l]; 269 for (int i=0; i<l; i++) { 270 int index = origDataCopy.indexOf(permutedData.get(i)); 271 if (index == -1) { 272 throw new MathIllegalArgumentException(LocalizedFormats.DIFFERENT_ORIG_AND_PERMUTED_DATA); 273 } 274 res[index] = (double) i / l; 275 origDataCopy.set(index, null); 276 } 277 return Arrays.asList(res); 278 } 279 280 /** {@inheritDoc} */ 281 @Override 282 public String toString() { 283 return String.format("(f=%s pi=(%s))", getFitness(), baseSeqPermutation); 284 } 285 286 /** 287 * Helper for constructor. Generates a list of natural numbers (0,1,...,l-1). 288 * 289 * @param l length of list to generate 290 * @return list of integers from 0 to l-1 291 */ 292 private static List<Integer> baseSequence(final int l) { 293 List<Integer> baseSequence = new ArrayList<Integer> (l); 294 for (int i=0; i<l; i++) { 295 baseSequence.add(i); 296 } 297 return baseSequence; 298 } 299}