1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.rng.sampling.shape;
19
20 import org.apache.commons.rng.UniformRandomProvider;
21 import org.apache.commons.rng.sampling.SharedStateObjectSampler;
22
23 /**
24 * Generate points uniformly distributed within a n-dimension box (hyperrectangle).
25 *
26 * <p>Sampling uses:</p>
27 *
28 * <ul>
29 * <li>{@link UniformRandomProvider#nextDouble()}
30 * </ul>
31 *
32 * @see <a href="https://en.wikipedia.org/wiki/Hyperrectangle">Hyperrectangle (Wikipedia)</a>
33 * @since 1.4
34 */
35 public abstract class BoxSampler implements SharedStateObjectSampler<double[]> {
36 /** The dimension for 2D sampling. */
37 private static final int TWO_D = 2;
38 /** The dimension for 3D sampling. */
39 private static final int THREE_D = 3;
40 /** The source of randomness. */
41 private final UniformRandomProvider rng;
42
43 // The following code defines a point within the range ab:
44 // p = (1 - u)a + ub, u in [0, 1]
45 //
46 // This is the same method used in the
47 // o.a.c.rng.sampling.distribution.ContinuousUniformSampler but extended to N-dimensions.
48
49 /**
50 * Sample uniformly from a box in 2D. This is an non-array based specialisation of
51 * {@link BoxSamplerND} for performance.
52 */
53 private static final class BoxSampler2D extends BoxSampler {
54 /** The x component of bound a. */
55 private final double ax;
56 /** The y component of bound a. */
57 private final double ay;
58 /** The x component of bound b. */
59 private final double bx;
60 /** The y component of bound b. */
61 private final double by;
62
63 /**
64 * @param rng Source of randomness.
65 * @param a Bound a.
66 * @param b Bound b.
67 */
68 BoxSampler2D(UniformRandomProvider rng, double[] a, double[] b) {
69 super(rng);
70 ax = a[0];
71 ay = a[1];
72 bx = b[0];
73 by = b[1];
74 }
75
76 /**
77 * @param rng Source of randomness.
78 * @param source Source to copy.
79 */
80 BoxSampler2D(UniformRandomProvider rng, BoxSampler2D source) {
81 super(rng);
82 ax = source.ax;
83 ay = source.ay;
84 bx = source.bx;
85 by = source.by;
86 }
87
88 @Override
89 public double[] sample() {
90 return new double[] {createSample(ax, bx),
91 createSample(ay, by)};
92 }
93
94 @Override
95 public BoxSampler withUniformRandomProvider(UniformRandomProvider rng) {
96 return new BoxSampler2D(rng, this);
97 }
98 }
99
100 /**
101 * Sample uniformly from a box in 3D. This is an non-array based specialisation of
102 * {@link BoxSamplerND} for performance.
103 */
104 private static final class BoxSampler3D extends BoxSampler {
105 /** The x component of bound a. */
106 private final double ax;
107 /** The y component of bound a. */
108 private final double ay;
109 /** The z component of bound a. */
110 private final double az;
111 /** The x component of bound b. */
112 private final double bx;
113 /** The y component of bound b. */
114 private final double by;
115 /** The z component of bound b. */
116 private final double bz;
117
118 /**
119 * @param rng Source of randomness.
120 * @param a Bound a.
121 * @param b Bound b.
122 */
123 BoxSampler3D(UniformRandomProvider rng, double[] a, double[] b) {
124 super(rng);
125 ax = a[0];
126 ay = a[1];
127 az = a[2];
128 bx = b[0];
129 by = b[1];
130 bz = b[2];
131 }
132
133 /**
134 * @param rng Source of randomness.
135 * @param source Source to copy.
136 */
137 BoxSampler3D(UniformRandomProvider rng, BoxSampler3D source) {
138 super(rng);
139 ax = source.ax;
140 ay = source.ay;
141 az = source.az;
142 bx = source.bx;
143 by = source.by;
144 bz = source.bz;
145 }
146
147 @Override
148 public double[] sample() {
149 return new double[] {createSample(ax, bx),
150 createSample(ay, by),
151 createSample(az, bz)};
152 }
153
154 @Override
155 public BoxSampler withUniformRandomProvider(UniformRandomProvider rng) {
156 return new BoxSampler3D(rng, this);
157 }
158 }
159
160 /**
161 * Sample uniformly from a box in ND.
162 */
163 private static final class BoxSamplerND extends BoxSampler {
164 /** Bound a. */
165 private final double[] a;
166 /** Bound b. */
167 private final double[] b;
168
169 /**
170 * @param rng Source of randomness.
171 * @param a Bound a.
172 * @param b Bound b.
173 */
174 BoxSamplerND(UniformRandomProvider rng, double[] a, double[] b) {
175 super(rng);
176 // Defensive copy
177 this.a = a.clone();
178 this.b = b.clone();
179 }
180
181 /**
182 * @param rng Source of randomness.
183 * @param source Source to copy.
184 */
185 BoxSamplerND(UniformRandomProvider rng, BoxSamplerND source) {
186 super(rng);
187 // Shared state is immutable
188 a = source.a;
189 b = source.b;
190 }
191
192 @Override
193 public double[] sample() {
194 final double[] x = new double[a.length];
195 for (int i = 0; i < x.length; i++) {
196 x[i] = createSample(a[i], b[i]);
197 }
198 return x;
199 }
200
201 @Override
202 public BoxSampler withUniformRandomProvider(UniformRandomProvider rng) {
203 return new BoxSamplerND(rng, this);
204 }
205 }
206
207 /**
208 * @param rng Source of randomness.
209 */
210 BoxSampler(UniformRandomProvider rng) {
211 this.rng = rng;
212 }
213
214 /**
215 * @return a random Cartesian coordinate within the box.
216 */
217 @Override
218 public abstract double[] sample();
219
220 /**
221 * Creates the sample between bound a and b.
222 *
223 * @param a Bound a
224 * @param b Bound b
225 * @return the sample
226 */
227 double createSample(double a, double b) {
228 final double u = rng.nextDouble();
229 return (1.0 - u) * a + u * b;
230 }
231
232 /** {@inheritDoc} */
233 // Redeclare the signature to return a BoxSampler not a SharedStateObjectSampler<double[]>
234 @Override
235 public abstract BoxSampler withUniformRandomProvider(UniformRandomProvider rng);
236
237 /**
238 * Create a box sampler with bounds {@code a} and {@code b}.
239 * Sampled points are uniformly distributed within the box defined by the bounds.
240 *
241 * <p>Sampling is supported in dimensions of 2 or above. Single dimension sampling
242 * can be performed using a {@link LineSampler}.
243 *
244 * <p>Note: There is no requirement that {@code a <= b}. The samples will be uniformly
245 * distributed in the range {@code a} to {@code b} for each dimension.
246 *
247 * @param rng Source of randomness.
248 * @param a Bound a.
249 * @param b Bound b.
250 * @return the sampler
251 * @throws IllegalArgumentException If the bounds do not have the same
252 * dimension; the dimension is less than 2; or bounds have non-finite coordinates.
253 */
254 public static BoxSampler of(UniformRandomProvider rng,
255 double[] a,
256 double[] b) {
257 final int dimension = a.length;
258 if (dimension != b.length) {
259 throw new IllegalArgumentException(
260 new StringBuilder("Mismatch of box dimensions: ").append(dimension).append(',')
261 .append(b.length).toString());
262 }
263 // Detect non-finite bounds
264 Coordinates.requireFinite(a, "Bound a");
265 Coordinates.requireFinite(b, "Bound b");
266 // Low dimension specialisations
267 if (dimension == TWO_D) {
268 return new BoxSampler2D(rng, a, b);
269 } else if (dimension == THREE_D) {
270 return new BoxSampler3D(rng, a, b);
271 } else if (dimension > THREE_D) {
272 return new BoxSamplerND(rng, a, b);
273 }
274 // Less than 2D
275 throw new IllegalArgumentException("Unsupported dimension: " + dimension);
276 }
277 }