001/* 002 * Java Genetic Algorithm Library (jenetics-8.1.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 */ 020package io.jenetics; 021 022import static java.lang.String.format; 023import static io.jenetics.internal.util.Arrays.shuffle; 024import static io.jenetics.internal.util.Bits.getAndSet; 025import static io.jenetics.internal.util.SerialIO.readInt; 026import static io.jenetics.internal.util.SerialIO.writeInt; 027 028import java.io.IOException; 029import java.io.InvalidObjectException; 030import java.io.ObjectInput; 031import java.io.ObjectInputStream; 032import java.io.ObjectOutput; 033import java.io.Serial; 034import java.io.Serializable; 035import java.util.stream.Collectors; 036import java.util.stream.IntStream; 037 038import io.jenetics.internal.math.Subsets; 039import io.jenetics.internal.util.Bits; 040import io.jenetics.internal.util.Requires; 041import io.jenetics.util.ISeq; 042import io.jenetics.util.IntRange; 043import io.jenetics.util.MSeq; 044import io.jenetics.util.RandomRegistry; 045 046/** 047 * This chromosome can be used to model permutations of a given (sub) set of 048 * alleles. 049 * {@snippet lang="java": 050 * final ISeq<String> alleles = ISeq.of("one", "two", "three", "four", "five"); 051 * 052 * // Create a new randomly permuted chromosome from the given alleles. 053 * final PermutationChromosome<String> ch1 = PermutationChromosome.of(alleles); 054 * System.out.println(ch1); 055 * System.out.println(ch1.newInstance()); 056 * 057 * // Create a new randomly permuted chromosome from a subset of the given alleles. 058 * final PermutationChromosome<String> ch2 = PermutationChromosome.of(alleles, 3); 059 * System.out.println(ch2); 060 * System.out.println(ch2.newInstance()); 061 * 062 * // Console output: 063 * // > three|two|one|five|four 064 * // > two|one|four|five|three 065 * // > three|one|five 066 * // > five|three|one 067 * } 068 * 069 * Usable {@link Alterer} for this chromosome: 070 * <ul> 071 * <li>{@link PartiallyMatchedCrossover}</li> 072 * <li>{@link SwapMutator}</li> 073 * </ul> 074 * <p> 075 * <em><b>Implementation note 1:</b> 076 * The factory methods of the {@link AbstractChromosome} has been overridden so 077 * that no invalid permutation will be created. 078 * </em> 079 * 080 * <p> 081 * <em><b>Implementation note 2:</b> 082 * This class uses an algorithm for choosing subsets which is based on a 083 * FORTRAN77 version, originally implemented by Albert Nijenhuis, Herbert Wilf. 084 * The actual Java implementation is based on the C++ version by John Burkardt. 085 * </em> 086 * <br> 087 * <em><a href="https://people.sc.fsu.edu/~burkardt/c_src/subset/subset.html"> 088 * Reference:</a></em> 089 * Albert Nijenhuis, Herbert Wilf, 090 * Combinatorial Algorithms for Computers and Calculators, 091 * Second Edition, 092 * Academic Press, 1978, 093 * ISBN: 0-12-519260-6, 094 * LC: QA164.N54. 095 * </p> 096 * 097 * 098 * @see EnumGene 099 * @see PartiallyMatchedCrossover 100 * @see SwapMutator 101 * 102 * @implNote 103 * This class is immutable and thread-safe. 104 * 105 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 106 * @since 1.0 107 * @version 6.0 108 */ 109public final class PermutationChromosome<T> 110 extends AbstractChromosome<EnumGene<T>> 111 implements Serializable 112{ 113 @Serial 114 private static final long serialVersionUID = 2L; 115 116 private final ISeq<T> _validAlleles; 117 118 // Private primary constructor. 119 private PermutationChromosome( 120 final ISeq<EnumGene<T>> genes, 121 final Boolean valid 122 ) { 123 super(genes); 124 125 assert !genes.isEmpty(); 126 _validAlleles = genes.get(0).validAlleles(); 127 _valid = valid; 128 } 129 130 /** 131 * Create a new {@code PermutationChromosome} from the given {@code genes}. 132 * If the given {@code genes} sequence contains duplicate entries, the 133 * created {@code PermutationChromosome} will be invalid 134 * ({@code ch.isValid() == false}). 135 * 136 * @param genes the enum genes the new chromosome consists of 137 * @throws NullPointerException if the given {@code genes} are null 138 * @throws IllegalArgumentException if the given {@code genes} sequence is 139 * empty 140 */ 141 public PermutationChromosome(final ISeq<EnumGene<T>> genes) { 142 this(genes, null); 143 } 144 145 /** 146 * Return the sequence of the valid alleles of this chromosome. 147 * 148 * @return the sequence of valid alleles of this chromosome 149 */ 150 public ISeq<T> validAlleles() { 151 return _validAlleles; 152 } 153 154 /** 155 * Check if this chromosome represents still a valid permutation (or subset) 156 * of the given valid alleles. 157 */ 158 @Override 159 public boolean isValid() { 160 if (_valid == null) { 161 final byte[] check = Bits.newArray(_validAlleles.length()); 162 _valid = _genes.forAll(g -> !getAndSet(check, g.alleleIndex())); 163 } 164 165 return _valid; 166 } 167 168 /** 169 * Create a new, <em>random</em> chromosome. 170 */ 171 @Override 172 public PermutationChromosome<T> newInstance() { 173 return of(_validAlleles, length()); 174 } 175 176 @Override 177 public PermutationChromosome<T> newInstance(final ISeq<EnumGene<T>> genes) { 178 return new PermutationChromosome<>(genes); 179 } 180 181 @Override 182 public String toString() { 183 return _genes.stream() 184 .map(g -> g.allele().toString()) 185 .collect(Collectors.joining("|")); 186 } 187 188 /** 189 * Create a new, random chromosome with the given valid alleles and the 190 * desired length. 191 * <p> 192 * The following example shows how to create a {@code PermutationChromosome} 193 * for encoding a subset problem (of a fixed {@code length}). 194 * {@snippet lang="java": 195 * final ISeq<String> basicSet = ISeq.of("a", "b", "c", "d", "e", "f"); 196 * 197 * // The chromosome has a length of 3 and will only contain values from the 198 * // given basic-set, with no duplicates. 199 * final PermutationChromosome<String> ch = PermutationChromosome.of(basicSet, 3); 200 * } 201 * 202 * @since 3.4 203 * 204 * @param <T> the allele type 205 * @param alleles the base-set of the valid alleles 206 * @param length the length of the created chromosomes 207 * @return a new chromosome with the given valid alleles and the desired 208 * length 209 * @throws IllegalArgumentException if {@code alleles.size() < length}, 210 * {@code length <= 0} or {@code alleles.size()*length} will 211 * cause an integer overflow. 212 * @throws NullPointerException if one of the arguments is {@code null} 213 */ 214 public static <T> PermutationChromosome<T> of( 215 final ISeq<? extends T> alleles, 216 final int length 217 ) { 218 Requires.positive(length); 219 if (length > alleles.size()) { 220 throw new IllegalArgumentException(format( 221 "The sub-set size must be be greater then the base-set: %d > %d", 222 length, alleles.size() 223 )); 224 } 225 226 final var rnd = RandomRegistry.random(); 227 final int[] subset = Subsets.next(rnd, alleles.size(), length); 228 shuffle(subset, rnd); 229 230 final ISeq<EnumGene<T>> genes = IntStream.of(subset) 231 .mapToObj(i -> EnumGene.<T>of(i, alleles)) 232 .collect(ISeq.toISeq()); 233 234 return new PermutationChromosome<>(genes, true); 235 } 236 237 /** 238 * Create a new, random chromosome with the given valid alleles. 239 * 240 * @param <T> the gene type of the chromosome 241 * @param alleles the valid alleles used for this permutation arrays. 242 * @return a new chromosome with the given alleles 243 * @throws IllegalArgumentException if the given allele sequence is empty. 244 */ 245 public static <T> PermutationChromosome<T> 246 of(final ISeq<? extends T> alleles) { 247 return of(alleles, alleles.size()); 248 } 249 250 /** 251 * Create a new, random chromosome with the given valid alleles. 252 * 253 * @since 2.0 254 * 255 * @param <T> the gene type of the chromosome 256 * @param alleles the valid alleles used for this permutation arrays. 257 * @return a new chromosome with the given alleles 258 * @throws IllegalArgumentException if the given allele array is empty. 259 * @throws NullPointerException if one of the alleles is {@code null} 260 */ 261 @SafeVarargs 262 public static <T> PermutationChromosome<T> of(final T... alleles) { 263 return of(ISeq.of(alleles)); 264 } 265 266 /** 267 * Create a integer permutation chromosome with the given length. 268 * 269 * @param length the chromosome length. 270 * @return a integer permutation chromosome with the given length. 271 * @throws IllegalArgumentException if {@code length <= 0}. 272 */ 273 public static PermutationChromosome<Integer> ofInteger(final int length) { 274 return ofInteger(0, Requires.positive(length)); 275 } 276 277 /** 278 * Create an integer permutation chromosome with the given range. 279 * 280 * @since 2.0 281 * 282 * @param start the start of the integer range (inclusively) of the returned 283 * chromosome. 284 * @param end the end of the integer range (exclusively) of the returned 285 * chromosome. 286 * @return a integer permutation chromosome with the given integer range 287 * values. 288 * @throws IllegalArgumentException if {@code start >= end} or 289 * {@code start <= 0} 290 */ 291 public static PermutationChromosome<Integer> 292 ofInteger(final int start, final int end) { 293 if (end <= start) { 294 throw new IllegalArgumentException(format( 295 "end <= start: %d <= %d", end, start 296 )); 297 } 298 299 return ofInteger(IntRange.of(start, end), end - start); 300 } 301 302 /** 303 * Create an integer permutation chromosome with the given range and length 304 * 305 * @since 3.4 306 * 307 * @param range the value range 308 * @param length the chromosome length 309 * @return a new integer permutation chromosome 310 * @throws NullPointerException if the given {@code range} is {@code null} 311 * @throws IllegalArgumentException if 312 * {@code range.getMax() - range.getMin() < length}, 313 * {@code length <= 0} or 314 * {@code (range.getMax() - range.getMin())*length} will cause an 315 * integer overflow. 316 */ 317 public static PermutationChromosome<Integer> 318 ofInteger(final IntRange range, final int length) { 319 return of( 320 range.stream().boxed().collect(ISeq.toISeq()), 321 length 322 ); 323 } 324 325 /* ************************************************************************* 326 * Java object serialization 327 * ************************************************************************/ 328 329 @Serial 330 private Object writeReplace() { 331 return new SerialProxy(SerialProxy.PERMUTATION_CHROMOSOME, this); 332 } 333 334 @Serial 335 private void readObject(final ObjectInputStream stream) 336 throws InvalidObjectException 337 { 338 throw new InvalidObjectException("Serialization proxy required."); 339 } 340 341 void write(final ObjectOutput out) throws IOException { 342 out.writeObject(_validAlleles); 343 for (EnumGene<?> gene : _genes) { 344 writeInt(gene.alleleIndex(), out); 345 } 346 } 347 348 @SuppressWarnings({"unchecked", "rawtypes"}) 349 static PermutationChromosome read(final ObjectInput in) 350 throws IOException, ClassNotFoundException 351 { 352 final ISeq validAlleles = (ISeq)in.readObject(); 353 final MSeq genes = MSeq.ofLength(validAlleles.length()); 354 for (int i = 0, n = validAlleles.length(); i < n; ++i) { 355 genes.set(i, new EnumGene(readInt(in), validAlleles)); 356 } 357 358 return new PermutationChromosome(genes.toISeq()); 359 } 360 361}