diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c74916c..0d407338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] - 2024-11-11 +## [Unreleased] - 2024-12-12 ### Added @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * NaiveGenerationalEvolutionaryAlgorithm removed. This is not a breaking change. See changelog and release notes for release 6.3.0, where it was simultaneously introduced and deprecated for reason, which also indicates what should be used instead. ### Fixed +* Refactored RealVectorInitializer based on RefactorFirst analysis. ### Dependencies * Bump org.cicirello:rho-mu from 4.1.0 to 4.2.0 diff --git a/src/main/java/org/cicirello/search/operators/reals/RealVectorInitializer.java b/src/main/java/org/cicirello/search/operators/reals/RealVectorInitializer.java index e454e754..24a7fb77 100644 --- a/src/main/java/org/cicirello/search/operators/reals/RealVectorInitializer.java +++ b/src/main/java/org/cicirello/search/operators/reals/RealVectorInitializer.java @@ -1,6 +1,6 @@ /* * Chips-n-Salsa: A library of parallel self-adaptive local search algorithms. - * Copyright (C) 2002-2023 Vincent A. Cicirello + * Copyright (C) 2002-2024 Vincent A. Cicirello * * This file is part of Chips-n-Salsa (https://chips-n-salsa.cicirello.org/). * @@ -20,6 +20,8 @@ package org.cicirello.search.operators.reals; +import java.util.function.BiConsumer; +import java.util.function.Function; import org.cicirello.math.rand.EnhancedSplittableGenerator; import org.cicirello.search.internal.RandomnessFactory; import org.cicirello.search.operators.Initializer; @@ -39,12 +41,12 @@ */ public final class RealVectorInitializer implements Initializer { + private final EnhancedSplittableGenerator generator; private final double[] x; - private final double[] a; - private final double[] b; + private final BiConsumer initializer; + private final Function creator; private final double[] min; private final double[] max; - private final EnhancedSplittableGenerator generator; /** * Construct a RealVectorInitializer that generates random solutions such that the values of all n @@ -60,12 +62,14 @@ public final class RealVectorInitializer implements Initializer { * @throws NegativeArraySizeException if n < 0 */ public RealVectorInitializer(int n, double a, double b) { - if (a >= b) throw new IllegalArgumentException("a must be less than b"); + if (a >= b) { + throw new IllegalArgumentException("a must be less than b"); + } x = new double[n]; - this.a = new double[] {a}; - this.b = new double[] {b}; - min = max = null; + initializer = (x, generator) -> singleIntervalInit(generator, x, a, b); + creator = x -> new RealVector(x); generator = RandomnessFactory.createEnhancedSplittableGenerator(); + min = max = null; } /** @@ -85,16 +89,14 @@ public RealVectorInitializer(int n, double a, double b) { * i, such that a[i] ≥ b[i]. */ public RealVectorInitializer(double[] a, double[] b) { - if (a.length != b.length) - throw new IllegalArgumentException("lengths of a and b must be identical"); - for (int i = 0; i < a.length; i++) { - if (a[i] >= b[i]) throw new IllegalArgumentException("a[i] must be less than b[i]"); - } + validateAB(a, b); x = new double[a.length]; - this.a = a.clone(); - this.b = b.clone(); - min = max = null; + final double[] a2 = a.clone(); + final double[] b2 = b.clone(); + initializer = (x, generator) -> multiIntervalInit(generator, x, a2, b2); + creator = x -> new RealVector(x); generator = RandomnessFactory.createEnhancedSplittableGenerator(); + min = max = null; } /** @@ -114,14 +116,19 @@ public RealVectorInitializer(double[] a, double[] b) { * @throws NegativeArraySizeException if n < 0 */ public RealVectorInitializer(int n, double a, double b, double min, double max) { - if (a >= b) throw new IllegalArgumentException("a must be less than b"); - if (min > max) throw new IllegalArgumentException("min must be less than or equal to max"); + if (a >= b) { + throw new IllegalArgumentException("a must be less than b"); + } + if (min > max) { + throw new IllegalArgumentException("min must be less than or equal to max"); + } x = new double[n]; - this.a = new double[] {a <= min ? min : a}; - this.b = new double[] {b > max ? max + Math.ulp(max) : b}; - this.min = new double[] {min}; - this.max = new double[] {max}; + final double a2 = Math.max(a, min); + final double b2 = Math.min(b, max + Math.ulp(max)); + initializer = (x, generator) -> singleIntervalInit(generator, x, a2, b2); + creator = x -> new BoundedRealVector(x, min, max); generator = RandomnessFactory.createEnhancedSplittableGenerator(); + this.min = this.max = null; } /** @@ -144,22 +151,21 @@ public RealVectorInitializer(int n, double a, double b, double min, double max) * i, such that a[i] ≥ b[i]; or if min > max. */ public RealVectorInitializer(double[] a, double[] b, double min, double max) { - if (a.length != b.length) - throw new IllegalArgumentException("lengths of a and b must be identical"); - if (min > max) throw new IllegalArgumentException("min must be less than or equal to max"); - for (int i = 0; i < a.length; i++) { - if (a[i] >= b[i]) throw new IllegalArgumentException("a[i] must be less than b[i]"); + if (min > max) { + throw new IllegalArgumentException("min must be less than or equal to max"); } + validateAB(a, b); x = new double[a.length]; - this.a = new double[a.length]; - this.b = new double[b.length]; - for (int i = 0; i < a.length; i++) { - this.a[i] = a[i] <= min ? min : a[i]; - this.b[i] = b[i] > max ? max + Math.ulp(max) : b[i]; + final double[] a2 = a.clone(); + final double[] b2 = b.clone(); + for (int i = 0; i < a2.length; i++) { + a2[i] = Math.max(a2[i], min); + b2[i] = Math.min(b2[i], max + Math.ulp(max)); } - this.min = new double[] {min}; - this.max = new double[] {max}; + initializer = (x, generator) -> multiIntervalInit(generator, x, a2, b2); + creator = x -> new BoundedRealVector(x, min, max); generator = RandomnessFactory.createEnhancedSplittableGenerator(); + this.min = this.max = null; } /** @@ -184,56 +190,45 @@ public RealVectorInitializer(double[] a, double[] b, double min, double max) { * i, such that a[i] ≥ b[i] or min[i] > max[i]. */ public RealVectorInitializer(double[] a, double[] b, double[] min, double[] max) { - if (a.length != b.length || min.length != max.length || a.length != min.length) { + if (min.length != max.length || a.length != min.length) { throw new IllegalArgumentException("lengths of a, b, min, and max must be identical"); } - for (int i = 0; i < a.length; i++) { - if (a[i] >= b[i]) { - throw new IllegalArgumentException("a[i] must be less than b[i]"); - } + validateAB(a, b); + for (int i = 0; i < min.length; i++) { if (min[i] > max[i]) { throw new IllegalArgumentException("min[i] must be less than or equal to max[i]"); } } x = new double[a.length]; - this.a = new double[a.length]; - this.b = new double[b.length]; + final double[] a2 = a.clone(); + final double[] b2 = b.clone(); for (int i = 0; i < a.length; i++) { - this.a[i] = a[i] <= min[i] ? min[i] : a[i]; - this.b[i] = b[i] > max[i] ? max[i] + Math.ulp(max[i]) : b[i]; + a2[i] = Math.max(a2[i], min[i]); + b2[i] = Math.min(b2[i], max[i] + Math.ulp(max[i])); } + initializer = (x, generator) -> multiIntervalInit(generator, x, a2, b2); this.min = min.clone(); this.max = max.clone(); + creator = x -> new MultiBoundedRealVector(x); generator = RandomnessFactory.createEnhancedSplittableGenerator(); } private RealVectorInitializer(RealVectorInitializer other) { - min = other.min == null ? null : other.min.clone(); - max = other.max == null ? null : other.max.clone(); - a = other.a.clone(); - b = other.b.clone(); - x = new double[a.length]; + // these should be safe to share + min = other.min; + max = other.max; + initializer = other.initializer; + creator = other.creator; + + // each instance needs their own independent instances of these + x = new double[other.x.length]; generator = other.generator.split(); } @Override public final RealVector createCandidateSolution() { - if (a.length > 1) { - for (int i = 0; i < x.length; i++) { - x[i] = generator.nextDouble(a[i], b[i]); - } - } else { - for (int i = 0; i < x.length; i++) { - x[i] = generator.nextDouble(a[0], b[0]); - } - } - if (min != null) { - return min.length > 1 - ? new MultiBoundedRealVector(x) - : new BoundedRealVector(x, min[0], max[0]); - } else { - return new RealVector(x); - } + initializer.accept(x, generator); + return creator.apply(x); } @Override @@ -241,6 +236,31 @@ public RealVectorInitializer split() { return new RealVectorInitializer(this); } + private static void singleIntervalInit( + EnhancedSplittableGenerator generator, double[] x, double a, double b) { + for (int i = 0; i < x.length; i++) { + x[i] = generator.nextDouble(a, b); + } + } + + private static void multiIntervalInit( + EnhancedSplittableGenerator generator, double[] x, double[] a, double[] b) { + for (int i = 0; i < x.length; i++) { + x[i] = generator.nextDouble(a[i], b[i]); + } + } + + private static void validateAB(double[] a, double[] b) { + if (a.length != b.length) { + throw new IllegalArgumentException("lengths of a and b must be identical"); + } + for (int i = 0; i < a.length; i++) { + if (a[i] >= b[i]) { + throw new IllegalArgumentException("a[i] must be less than b[i]"); + } + } + } + /** * Internal class for representing the input to a multivariate function, where the input values * are bounded between a specified minimum and maximum value. If an attempt is made to set any of @@ -286,9 +306,7 @@ public MultiBoundedRealVector(MultiBoundedRealVector other) { */ @Override public final void set(int i, double value) { - if (value < min[i]) super.set(i, min[i]); - else if (value > max[i]) super.set(i, max[i]); - else super.set(i, value); + super.set(i, Math.max(min[i], Math.min(value, max[i]))); } /*