001/* 002 * Java Genetic Algorithm Library (jenetics-9.0.0). 003 * Copyright (c) 2007-2026 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.ext; 021 022import static java.lang.Math.abs; 023import static java.lang.Math.clamp; 024import static java.lang.Math.pow; 025import static java.lang.String.format; 026 027import io.jenetics.Crossover; 028import io.jenetics.NumericGene; 029import io.jenetics.internal.math.Randoms; 030import io.jenetics.internal.util.Counter; 031import io.jenetics.internal.util.Requires; 032import io.jenetics.util.MSeq; 033import io.jenetics.util.RandomRegistry; 034 035/** 036 * Performs the simulated binary crossover (SBX) on a {@code Chromosome} of 037 * {@link NumericGene}s such that each position is either crossed contracted or 038 * expanded with a certain probability. The probability distribution is designed 039 * such that the children will lie closer to their parents, as is the case with 040 * the single-point binary crossover. 041 * <p> 042 * It is implemented as described in Deb, K. and Agrawal, R. B. 1995. Simulated 043 * binary crossover for continuous search space. Complex Systems, 9, pp. 115-148. 044 * 045 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 046 * @since 3.5 047 * @version 6.0 048 */ 049public class SimulatedBinaryCrossover< 050 G extends NumericGene<?, G>, 051 C extends Comparable<? super C> 052> 053 extends Crossover<G, C> 054{ 055 private final double _contiguity; 056 057 /** 058 * Create a new <i>simulated binary crossover</i> alterer with the given 059 * parameters. 060 * 061 * @param probability the recombination probability 062 * @param contiguity the contiguity value that specifies how close a child 063 * should be to its parents (larger value means closer). The value 064 * must be greater or equal than 0. Typical values are in the range 065 * [2..5]. 066 * @throws IllegalArgumentException if the {@code probability} is not in the 067 * valid range of {@code [0, 1]} 068 * @throws IllegalArgumentException if {@code contiguity} is smaller than 069 * zero 070 */ 071 public SimulatedBinaryCrossover( 072 final double probability, 073 final double contiguity 074 ) { 075 super(probability); 076 _contiguity = Requires.nonNegative(contiguity); 077 } 078 079 /** 080 * Create a new <i>simulated binary crossover</i> alterer with the given 081 * parameters. The <i>contiguity</i> value is set to {@code 2.5}. 082 * 083 * @param probability the recombination probability 084 * @throws IllegalArgumentException if the {@code probability} is not in the 085 * valid range of {@code [0, 1]} 086 * @throws IllegalArgumentException if {@code contiguity} is smaller than 087 * zero 088 */ 089 public SimulatedBinaryCrossover(final double probability) { 090 this(probability, 2.5); 091 } 092 093 /** 094 * Return the <i>contiguity</i> value of the crossover. 095 * 096 * @return the <i>contiguity</i> value of the crossover 097 */ 098 public double contiguity() { 099 return _contiguity; 100 } 101 102 @Override 103 protected int crossover(final MSeq<G> that, final MSeq<G> other) { 104 return Randoms.indexes(RandomRegistry.random(), that.length(), 0.5) 105 .peek(i -> crossover(that, other, i)) 106 .collect(Counter::new, Counter::inc, Counter::sum) 107 .intValue(); 108 } 109 110 private void crossover(final MSeq<G> that, final MSeq<G> other, final int i) { 111 final var random = RandomRegistry.random(); 112 113 final double u = random.nextDouble(); 114 final double beta; 115 if (u < 0.5) { 116 // If u is smaller than 0.5 perform a contracting crossover. 117 beta = pow(2*u, 1.0/(_contiguity + 1)); 118 } else if (u > 0.5) { 119 // Otherwise, perform an expanding crossover. 120 beta = pow(0.5/(1.0 - u), 1.0/(_contiguity + 1)); 121 } else { 122 beta = 1; 123 } 124 125 final double v1 = that.get(i).doubleValue(); 126 final double v2 = other.get(i).doubleValue(); 127 final double v = random.nextBoolean() 128 ? ((v1 - v2)*0.5) - beta*0.5*abs(v1 - v2) 129 : ((v1 - v2)*0.5) + beta*0.5*abs(v1 - v2); 130 131 final double min = that.get(i).min().doubleValue(); 132 final double max = that.get(i).max().doubleValue(); 133 that.set(i, that.get(i).newInstance(clamp(v, min, max))); 134 } 135 136 @Override 137 public String toString() { 138 return format( 139 "SimulatedBinaryCrossover[p=%f, c=%f]", 140 _probability, _contiguity 141 ); 142 } 143 144}