Skip to content

Commit

Permalink
refactor to improve randomness
Browse files Browse the repository at this point in the history
  • Loading branch information
cicirello committed Jul 25, 2023
1 parent d614d94 commit 1c3f04f
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@

package org.cicirello.search.operators.permutations;

import org.cicirello.math.rand.RandomIndexer;
import org.cicirello.math.rand.EnhancedSplittableGenerator;
import org.cicirello.permutations.Permutation;
import org.cicirello.permutations.PermutationBinaryOperator;
import org.cicirello.search.internal.RandomnessFactory;
import org.cicirello.search.operators.CrossoverOperator;
import org.cicirello.util.IntegerArray;

Expand Down Expand Up @@ -66,8 +67,16 @@
public final class NonWrappingOrderCrossover
implements CrossoverOperator<Permutation>, PermutationBinaryOperator {

private final EnhancedSplittableGenerator generator;

/** Constructs a non-wrapping order crossover (NWOX) operator. */
public NonWrappingOrderCrossover() {}
public NonWrappingOrderCrossover() {
generator = RandomnessFactory.createEnhancedSplittableGenerator();
}

private NonWrappingOrderCrossover(NonWrappingOrderCrossover other) {
generator = other.generator.split();
}

@Override
public void cross(Permutation c1, Permutation c2) {
Expand All @@ -83,8 +92,8 @@ public void cross(Permutation c1, Permutation c2) {
*/
@Override
public void apply(int[] raw1, int[] raw2) {
int i = RandomIndexer.nextInt(raw1.length);
int j = RandomIndexer.nextInt(raw1.length);
int i = generator.nextInt(raw1.length);
int j = generator.nextInt(raw1.length);
if (j < i) {
int temp = i;
i = j;
Expand Down Expand Up @@ -118,7 +127,6 @@ public void apply(int[] raw1, int[] raw2) {

@Override
public NonWrappingOrderCrossover split() {
// doesn't maintain any state, so safe to return this
return this;
return new NonWrappingOrderCrossover(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@

package org.cicirello.search.operators.permutations;

import org.cicirello.math.rand.RandomIndexer;
import org.cicirello.math.rand.EnhancedSplittableGenerator;
import org.cicirello.permutations.Permutation;
import org.cicirello.permutations.PermutationBinaryOperator;
import org.cicirello.search.internal.RandomnessFactory;
import org.cicirello.search.operators.CrossoverOperator;
import org.cicirello.util.IntegerArray;

Expand Down Expand Up @@ -59,8 +60,16 @@
public final class OrderCrossover
implements CrossoverOperator<Permutation>, PermutationBinaryOperator {

private final EnhancedSplittableGenerator generator;

/** Constructs an order crossover (OX) operator. */
public OrderCrossover() {}
public OrderCrossover() {
generator = RandomnessFactory.createEnhancedSplittableGenerator();
}

private OrderCrossover(OrderCrossover other) {
generator = other.generator.split();
}

@Override
public void cross(Permutation c1, Permutation c2) {
Expand All @@ -76,8 +85,8 @@ public void cross(Permutation c1, Permutation c2) {
*/
@Override
public void apply(int[] raw1, int[] raw2) {
int i = RandomIndexer.nextInt(raw1.length);
int j = RandomIndexer.nextInt(raw1.length);
int i = generator.nextInt(raw1.length);
int j = generator.nextInt(raw1.length);
if (j < i) {
int temp = i;
i = j;
Expand Down Expand Up @@ -111,7 +120,6 @@ public void apply(int[] raw1, int[] raw2) {

@Override
public OrderCrossover split() {
// doesn't maintain any state, so safe to return this
return this;
return new OrderCrossover(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@

package org.cicirello.search.operators.permutations;

import org.cicirello.math.rand.RandomIndexer;
import org.cicirello.math.rand.EnhancedSplittableGenerator;
import org.cicirello.permutations.Permutation;
import org.cicirello.permutations.PermutationFullBinaryOperator;
import org.cicirello.search.internal.RandomnessFactory;
import org.cicirello.search.operators.CrossoverOperator;
import org.cicirello.util.IntegerArray;

Expand Down Expand Up @@ -76,6 +77,7 @@ public final class OrderCrossoverTwo
implements CrossoverOperator<Permutation>, PermutationFullBinaryOperator {

private final double u;
private final EnhancedSplittableGenerator generator;

/**
* Constructs Syswerda's order crossover operator, often referred to as OX2. Uses a default U=0.5.
Expand All @@ -94,6 +96,12 @@ public OrderCrossoverTwo() {
public OrderCrossoverTwo(double u) {
if (u <= 0 || u >= 1.0) throw new IllegalArgumentException("u must be: 0.0 < u < 1.0");
this.u = u;
generator = RandomnessFactory.createEnhancedSplittableGenerator();
}

private OrderCrossoverTwo(OrderCrossoverTwo other) {
generator = other.generator.split();
u = other.u;
}

@Override
Expand All @@ -103,8 +111,7 @@ public void cross(Permutation c1, Permutation c2) {

@Override
public OrderCrossoverTwo split() {
// doesn't maintain any mutable state, so safe to return this
return this;
return new OrderCrossoverTwo(this);
}

/**
Expand All @@ -118,7 +125,7 @@ public OrderCrossoverTwo split() {
*/
@Override
public void apply(int[] raw1, int[] raw2, Permutation p1, Permutation p2) {
internalCross(raw1, raw2, p1, p2, RandomIndexer.arrayMask(raw1.length, u));
internalCross(raw1, raw2, p1, p2, generator.arrayMask(raw1.length, u));
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public void testNWOX() {
validateOrderingUOBX(parent2, p1, fixedPoints);
}
}
assertSame(nwox, nwox.split());
NonWrappingOrderCrossover s = nwox.split();
assertNotSame(nwox, s);
final int n = 2000;
final int RUNS = 4;
boolean passed = false;
Expand Down Expand Up @@ -86,6 +87,35 @@ public void testNWOX() {
// assertTrue(validateOrderingNWOX(parent2, p1, startAndEnd), "This may infrequently result in a
// false failure because test case heuristically guesses where the random cross region was.
// Rerun if fails.");
passed = false;
for (int run = 0; run < RUNS && !passed; run++) {
Permutation p1 = new Permutation(n);
Permutation p2 = new Permutation(n);
Permutation parent1 = new Permutation(p1);
Permutation parent2 = new Permutation(p2);
s.cross(parent1, parent2);
assertTrue(validPermutation(parent1));
assertTrue(validPermutation(parent2));
boolean[] fixedPoints = findFixedPoints(parent1, parent2, p1, p2);
int[] startAndEnd = findStartAndEnd(fixedPoints);
passed =
validateOrderingNWOX(parent1, p2, startAndEnd)
&& validateOrderingNWOX(parent2, p1, startAndEnd);
}
// The following may on infrequent occasions exhibit a false failure.
// This is due to the above findStartAndEnd heuristically guessing what
// the random cross region was. I believe the probability of a false
// failure is very approximately (2/n)^RUNS, which for 3 runs of n=2000 is about 1 in
// 1000000000. Rerun if fails.
assertTrue(
passed,
"This may infrequently result in a false failure because test case heuristically guesses where the random cross region was. Rerun if fails.");
// assertTrue(validateOrderingNWOX(parent1, p2, startAndEnd), "This may infrequently result in a
// false failure because test case heuristically guesses where the random cross region was.
// Rerun if fails.");
// assertTrue(validateOrderingNWOX(parent2, p1, startAndEnd), "This may infrequently result in a
// false failure because test case heuristically guesses where the random cross region was.
// Rerun if fails.");
}

@Test
Expand All @@ -100,7 +130,17 @@ public void testNWOXIdenticalParents() {
assertEquals(p1, parent1);
assertEquals(p2, parent2);
}
assertSame(nwox, nwox.split());
NonWrappingOrderCrossover s = nwox.split();
assertNotSame(nwox, s);
for (int n = 1; n <= 32; n *= 2) {
Permutation p1 = new Permutation(n);
Permutation p2 = new Permutation(p1);
Permutation parent1 = new Permutation(p1);
Permutation parent2 = new Permutation(p2);
s.cross(parent1, parent2);
assertEquals(p1, parent1);
assertEquals(p2, parent2);
}
}

private boolean validateOrderingNWOX(Permutation child, Permutation order, int[] startAndEnd) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Chips-n-Salsa: A library of parallel self-adaptive local search algorithms.
* Copyright (C) 2002-2022 Vincent A. Cicirello
* Copyright (C) 2002-2023 Vincent A. Cicirello
*
* This file is part of Chips-n-Salsa (https://chips-n-salsa.cicirello.org/).
*
Expand Down Expand Up @@ -45,7 +45,17 @@ public void testOX2IdenticalParents() {
assertEquals(p1, parent1);
assertEquals(p2, parent2);
}
assertSame(ox2, ox2.split());
OrderCrossoverTwo s = ox2.split();
assertNotSame(ox2, s);
for (int n = 1; n <= 32; n *= 2) {
Permutation p1 = new Permutation(n);
Permutation p2 = new Permutation(p1);
Permutation parent1 = new Permutation(p1);
Permutation parent2 = new Permutation(p2);
s.cross(parent1, parent2);
assertEquals(p1, parent1);
assertEquals(p2, parent2);
}
}

@Test
Expand All @@ -62,6 +72,19 @@ public void testOX2Near0U() {
assertEquals(p1, parent1);
assertEquals(p2, parent2);
}
OrderCrossoverTwo s = ox2.split();
assertNotSame(ox2, s);
for (int n = 1; n <= 32; n *= 2) {
Permutation p1 = new Permutation(n);
Permutation p2 = new Permutation(n);
Permutation parent1 = new Permutation(p1);
Permutation parent2 = new Permutation(p2);
s.cross(parent1, parent2);
// the near 0 u should essentially keep all of the parents
// other than a low probability statistical anomaly
assertEquals(p1, parent1);
assertEquals(p2, parent2);
}
}

@Test
Expand All @@ -78,6 +101,19 @@ public void testOX2Near1U() {
assertEquals(p2, parent1);
assertEquals(p1, parent2);
}
OrderCrossoverTwo s = ox2.split();
assertNotSame(ox2, s);
for (int n = 1; n <= 32; n *= 2) {
Permutation p1 = new Permutation(n);
Permutation p2 = new Permutation(n);
Permutation parent1 = new Permutation(p1);
Permutation parent2 = new Permutation(p2);
s.cross(parent1, parent2);
// the near 1.0 u should essentially swap the parents
// other than a low probability statistical anomaly
assertEquals(p2, parent1);
assertEquals(p1, parent2);
}
}

@Test
Expand Down Expand Up @@ -119,6 +155,47 @@ public void testOX2Validity() {
}
}

@Test
public void testOX2ValiditySplit() {
// Validates children as valid permutations only.
// Does not validate behavior of the OX2.

OrderCrossoverTwo original = new OrderCrossoverTwo();
OrderCrossoverTwo ox2 = original.split();

for (int n = 1; n <= 32; n *= 2) {
Permutation p1 = new Permutation(n);
Permutation p2 = new Permutation(n);
Permutation parent1 = new Permutation(p1);
Permutation parent2 = new Permutation(p2);
ox2.cross(parent1, parent2);
assertTrue(validPermutation(parent1));
assertTrue(validPermutation(parent2));
}

ox2 = new OrderCrossoverTwo(0.25);
for (int n = 1; n <= 32; n *= 2) {
Permutation p1 = new Permutation(n);
Permutation p2 = new Permutation(n);
Permutation parent1 = new Permutation(p1);
Permutation parent2 = new Permutation(p2);
ox2.cross(parent1, parent2);
assertTrue(validPermutation(parent1));
assertTrue(validPermutation(parent2));
}

ox2 = new OrderCrossoverTwo(0.75);
for (int n = 1; n <= 32; n *= 2) {
Permutation p1 = new Permutation(n);
Permutation p2 = new Permutation(n);
Permutation parent1 = new Permutation(p1);
Permutation parent2 = new Permutation(p2);
ox2.cross(parent1, parent2);
assertTrue(validPermutation(parent1));
assertTrue(validPermutation(parent2));
}
}

@Test
public void testInternalCrossOX2() {
OrderCrossoverTwo ox2 = new OrderCrossoverTwo();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Chips-n-Salsa: A library of parallel self-adaptive local search algorithms.
* Copyright (C) 2002-2022 Vincent A. Cicirello
* Copyright (C) 2002-2023 Vincent A. Cicirello
*
* This file is part of Chips-n-Salsa (https://chips-n-salsa.cicirello.org/).
*
Expand Down Expand Up @@ -53,7 +53,8 @@ public void testOX() {
// }
}
}
assertSame(ox, ox.split());
OrderCrossover s = ox.split();
assertNotSame(ox, s);
final int n = 2000;
final int RUNS = 4;
boolean passed = false;
Expand Down Expand Up @@ -85,6 +86,35 @@ public void testOX() {
// assertTrue(validateOrderingOX(parent2, p1, startAndEnd), "This may infrequently result in a
// false failure because test case heuristically guesses where the random cross region was.
// Rerun if fails.");
passed = false;
for (int run = 0; run < RUNS && !passed; run++) {
Permutation p1 = new Permutation(n);
Permutation p2 = new Permutation(n);
Permutation parent1 = new Permutation(p1);
Permutation parent2 = new Permutation(p2);
s.cross(parent1, parent2);
assertTrue(validPermutation(parent1));
assertTrue(validPermutation(parent2));
boolean[] fixedPoints = findFixedPoints(parent1, parent2, p1, p2);
int[] startAndEnd = findStartAndEnd(fixedPoints);
passed =
validateOrderingOX(parent1, p2, startAndEnd)
&& validateOrderingOX(parent2, p1, startAndEnd);
}
// The following may on infrequent occasions exhibit a false failure.
// This is due to the above findStartAndEnd heuristically guessing what
// the random cross region was. I believe the probability of a false
// failure is very approximately (2/n)^RUNS, which for 3 runs of n=2000 is about 1 in
// 1000000000. Rerun if fails.
assertTrue(
passed,
"This may infrequently result in a false failure because test case heuristically guesses where the random cross region was. Rerun if fails.");
// assertTrue(validateOrderingOX(parent1, p2, startAndEnd), "This may infrequently result in a
// false failure because test case heuristically guesses where the random cross region was.
// Rerun if fails.");
// assertTrue(validateOrderingOX(parent2, p1, startAndEnd), "This may infrequently result in a
// false failure because test case heuristically guesses where the random cross region was.
// Rerun if fails.");
}

private boolean validateOrderingOX(Permutation child, Permutation order, int[] startAndEnd) {
Expand Down

0 comments on commit 1c3f04f

Please sign in to comment.