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