001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  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
018package org.apache.commons.rng.sampling.shape;
019
020import org.apache.commons.rng.UniformRandomProvider;
021import org.apache.commons.rng.sampling.SharedStateObjectSampler;
022
023/**
024 * Generate points uniformly distributed within a n-dimension box (hyperrectangle).
025 *
026 * <p>Sampling uses:</p>
027 *
028 * <ul>
029 *   <li>{@link UniformRandomProvider#nextDouble()}
030 * </ul>
031 *
032 * @see <a href="https://en.wikipedia.org/wiki/Hyperrectangle">Hyperrectangle (Wikipedia)</a>
033 * @since 1.4
034 */
035public abstract class BoxSampler implements SharedStateObjectSampler<double[]> {
036    /** The dimension for 2D sampling. */
037    private static final int TWO_D = 2;
038    /** The dimension for 3D sampling. */
039    private static final int THREE_D = 3;
040    /** The source of randomness. */
041    private final UniformRandomProvider rng;
042
043    // The following code defines a point within the range ab:
044    // p = (1 - u)a + ub, u in [0, 1]
045    //
046    // This is the same method used in the
047    // o.a.c.rng.sampling.distribution.ContinuousUniformSampler but extended to N-dimensions.
048
049    /**
050     * Sample uniformly from a box in 2D. This is an non-array based specialisation of
051     * {@link BoxSamplerND} for performance.
052     */
053    private static class BoxSampler2D extends BoxSampler {
054        /** The x component of bound a. */
055        private final double ax;
056        /** The y component of bound a. */
057        private final double ay;
058        /** The x component of bound b. */
059        private final double bx;
060        /** The y component of bound b. */
061        private final double by;
062
063        /**
064         * @param rng Source of randomness.
065         * @param a Bound a.
066         * @param b Bound b.
067         */
068        BoxSampler2D(UniformRandomProvider rng, double[] a, double[] b) {
069            super(rng);
070            ax = a[0];
071            ay = a[1];
072            bx = b[0];
073            by = b[1];
074        }
075
076        /**
077         * @param rng Source of randomness.
078         * @param source Source to copy.
079         */
080        BoxSampler2D(UniformRandomProvider rng, BoxSampler2D source) {
081            super(rng);
082            ax = source.ax;
083            ay = source.ay;
084            bx = source.bx;
085            by = source.by;
086        }
087
088        @Override
089        public double[] sample() {
090            return new double[] {createSample(ax, bx),
091                                 createSample(ay, by)};
092        }
093
094        @Override
095        public BoxSampler withUniformRandomProvider(UniformRandomProvider rng) {
096            return new BoxSampler2D(rng, this);
097        }
098    }
099
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}