001/* 002 * Java Genetic Algorithm Library (jenetics-8.2.0). 003 * Copyright (c) 2007-2025 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.stat; 021 022import static java.lang.Math.pow; 023import static java.lang.Math.sqrt; 024 025import java.util.random.RandomGenerator; 026 027import io.jenetics.util.DoubleRange; 028 029/** 030 * This class defines some default samplers. 031 * 032 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a> 033 * @version 8.0 034 * @since 8.0 035 */ 036public final class Samplers { 037 038 private Samplers() { 039 } 040 041 /** 042 * Return a new sampler for a <em>linear</em> distribution with the given 043 * {@code mean} value, when creating sample points for the <em>normalized</em> 044 * range {@code [0, 1)}. 045 * <p> 046 * <img src="doc-files/LinearDistributionPDF.svg" width="450" 047 * alt="Linear distribution sampler" > 048 * 049 * @param mean the mean value of the sampler distribution 050 * @return a new linear sampler with the given {@code mean} value 051 * @throws IllegalArgumentException if the given {@code mean} value is not 052 * within the range {@code [0, 1)} 053 */ 054 public static Sampler linear(final double mean) { 055 if (mean < 0 || mean >= 1 || !Double.isFinite(mean)) { 056 throw new IllegalArgumentException( 057 "Mean value not within allowed range [0, 1): %f." 058 .formatted(mean) 059 ); 060 } 061 062 final class Range { 063 static final double MIN = 0.0; 064 static final double MAX = Math.nextDown(1.0); 065 static final double LIMIT1 = 1.0 - 1.0/sqrt(2); 066 static final double LIMIT2 = 1.0/sqrt(2); 067 static double clamp(final double value) { 068 return Math.clamp(value, MIN, MAX); 069 } 070 } 071 072 if (Double.compare(mean, 0) == 0) { 073 return (random, range) -> range.min(); 074 } else if (mean == Range.MAX) { 075 return (random, range) -> Math.nextDown(range.max()); 076 } 077 078 final double b, m; 079 if (mean < Range.LIMIT1) { 080 b = (2 - sqrt(2))/mean; 081 m = -pow(b, 2)/2; 082 } else if (mean < Range.LIMIT2) { 083 b = (pow(mean, 2) - 0.5)/(pow(mean, 2) - mean); 084 m = 2 - 2*b; 085 } else if (mean < 1) { 086 b = 0.5*(1 - pow(((2 - sqrt(2))/(1 - mean) - 1), 2)); 087 m = -b + sqrt(1 - 2*b) + 1; 088 } else { 089 b = m = 1; 090 } 091 092 return (random, range) -> { 093 final var r = random.nextDouble(); 094 095 final double sample; 096 if (mean < 0.5) { 097 sample = Range.clamp((-b + sqrt(b*b + 2*r*m))/m); 098 } else if (mean == 0.5) { 099 sample = r; 100 } else { 101 sample = Range.clamp((b - sqrt(b*b + 2*m*(r - 1 + b + 0.5*m)))/-m); 102 } 103 104 return stretch(sample, range); 105 }; 106 } 107 108 /** 109 * Create a new sampler for a triangle distribution with the given 110 * parameters. All parameters must be within the <em>normalized</em> range 111 * {@code [0, 1]}. The sample value, returned by the 112 * {@link Sampler#sample(RandomGenerator, DoubleRange)} method, is then 113 * <em>stretched</em> to the desired range. 114 * <p> 115 * <img src="doc-files/TriangularDistributionPDF.svg" width="450" 116 * alt="Triangle distribution sampler" > 117 * 118 * @see #triangular(double) 119 * 120 * @param a the <em>normalized</em> start point of the triangle 121 * @param c the <em>normalized</em> middle point of the triangle 122 * @param b the <em>normalized</em> end point of the triangle 123 * @return a new triangle distribution sampler 124 * @throws IllegalArgumentException if one of the parameters is not within 125 * the range {@code [0, 1]} or {@code b <= a || c > b || c < a} 126 */ 127 public static Sampler 128 triangular(final double a, final double c, final double b) { 129 if (!Double.isFinite(a) || !Double.isFinite(b) || !Double.isFinite(c) || 130 a < 0 || b < 0 || c < 0 || 131 a > 1 || b > 1 || c > 1 || 132 b <= a || c > b || c < a) 133 { 134 throw new IllegalArgumentException( 135 "Invalid triangular: [a=%f, c=%f, b=%f].".formatted(a, c, b) 136 ); 137 } 138 139 final double fc = (c - a)/(b - a); 140 141 return (random, range) -> { 142 final var r = random.nextDouble(); 143 144 final double sample; 145 if (Double.compare(r, 0.0) == 0) { 146 sample = 0; 147 } else if (r < fc) { 148 sample = a + sqrt(r*(b - a)*(c - a)); 149 } else { 150 sample = b - sqrt((1 - r)*(b - a)*(b - c)); 151 } 152 153 return stretch(sample, range); 154 }; 155 } 156 157 /** 158 * Return a new sampler for a <em>normalized</em> triangle distribution 159 * with the points {@code [0, c, 1]}. 160 * 161 * @see #triangular(double, double, double) 162 * 163 * @param c the middle point of the triangle within the range {@code [0, 1]} 164 * @return a new triangle distribution sampler 165 * @throws IllegalArgumentException if c not within {@code [0, 1]} 166 */ 167 public static Sampler triangular(final double c) { 168 return triangular(0, c, 1.0); 169 } 170 171 private static double stretch(final double sample, DoubleRange range) { 172 return range.min() + sample*(range.max() - range.min()); 173 } 174 175} 176