View Javadoc
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 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 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 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 }