BoxSampler.java

  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. package org.apache.commons.rng.sampling.shape;

  18. import org.apache.commons.rng.UniformRandomProvider;
  19. import org.apache.commons.rng.sampling.SharedStateObjectSampler;

  20. /**
  21.  * Generate points uniformly distributed within a n-dimension box (hyperrectangle).
  22.  *
  23.  * <p>Sampling uses:</p>
  24.  *
  25.  * <ul>
  26.  *   <li>{@link UniformRandomProvider#nextDouble()}
  27.  * </ul>
  28.  *
  29.  * @see <a href="https://en.wikipedia.org/wiki/Hyperrectangle">Hyperrectangle (Wikipedia)</a>
  30.  * @since 1.4
  31.  */
  32. public abstract class BoxSampler implements SharedStateObjectSampler<double[]> {
  33.     /** The dimension for 2D sampling. */
  34.     private static final int TWO_D = 2;
  35.     /** The dimension for 3D sampling. */
  36.     private static final int THREE_D = 3;
  37.     /** The source of randomness. */
  38.     private final UniformRandomProvider rng;

  39.     // The following code defines a point within the range ab:
  40.     // p = (1 - u)a + ub, u in [0, 1]
  41.     //
  42.     // This is the same method used in the
  43.     // o.a.c.rng.sampling.distribution.ContinuousUniformSampler but extended to N-dimensions.

  44.     /**
  45.      * Sample uniformly from a box in 2D. This is an non-array based specialisation of
  46.      * {@link BoxSamplerND} for performance.
  47.      */
  48.     private static final class BoxSampler2D extends BoxSampler {
  49.         /** The x component of bound a. */
  50.         private final double ax;
  51.         /** The y component of bound a. */
  52.         private final double ay;
  53.         /** The x component of bound b. */
  54.         private final double bx;
  55.         /** The y component of bound b. */
  56.         private final double by;

  57.         /**
  58.          * @param rng Source of randomness.
  59.          * @param a Bound a.
  60.          * @param b Bound b.
  61.          */
  62.         BoxSampler2D(UniformRandomProvider rng, double[] a, double[] b) {
  63.             super(rng);
  64.             ax = a[0];
  65.             ay = a[1];
  66.             bx = b[0];
  67.             by = b[1];
  68.         }

  69.         /**
  70.          * @param rng Source of randomness.
  71.          * @param source Source to copy.
  72.          */
  73.         BoxSampler2D(UniformRandomProvider rng, BoxSampler2D source) {
  74.             super(rng);
  75.             ax = source.ax;
  76.             ay = source.ay;
  77.             bx = source.bx;
  78.             by = source.by;
  79.         }

  80.         @Override
  81.         public double[] sample() {
  82.             return new double[] {createSample(ax, bx),
  83.                                  createSample(ay, by)};
  84.         }

  85.         @Override
  86.         public BoxSampler withUniformRandomProvider(UniformRandomProvider rng) {
  87.             return new BoxSampler2D(rng, this);
  88.         }
  89.     }

  90.     /**
  91.      * Sample uniformly from a box in 3D. This is an non-array based specialisation of
  92.      * {@link BoxSamplerND} for performance.
  93.      */
  94.     private static final class BoxSampler3D extends BoxSampler {
  95.         /** The x component of bound a. */
  96.         private final double ax;
  97.         /** The y component of bound a. */
  98.         private final double ay;
  99.         /** The z component of bound a. */
  100.         private final double az;
  101.         /** The x component of bound b. */
  102.         private final double bx;
  103.         /** The y component of bound b. */
  104.         private final double by;
  105.         /** The z component of bound b. */
  106.         private final double bz;

  107.         /**
  108.          * @param rng Source of randomness.
  109.          * @param a Bound a.
  110.          * @param b Bound b.
  111.          */
  112.         BoxSampler3D(UniformRandomProvider rng, double[] a, double[] b) {
  113.             super(rng);
  114.             ax = a[0];
  115.             ay = a[1];
  116.             az = a[2];
  117.             bx = b[0];
  118.             by = b[1];
  119.             bz = b[2];
  120.         }

  121.         /**
  122.          * @param rng Source of randomness.
  123.          * @param source Source to copy.
  124.          */
  125.         BoxSampler3D(UniformRandomProvider rng, BoxSampler3D source) {
  126.             super(rng);
  127.             ax = source.ax;
  128.             ay = source.ay;
  129.             az = source.az;
  130.             bx = source.bx;
  131.             by = source.by;
  132.             bz = source.bz;
  133.         }

  134.         @Override
  135.         public double[] sample() {
  136.             return new double[] {createSample(ax, bx),
  137.                                  createSample(ay, by),
  138.                                  createSample(az, bz)};
  139.         }

  140.         @Override
  141.         public BoxSampler withUniformRandomProvider(UniformRandomProvider rng) {
  142.             return new BoxSampler3D(rng, this);
  143.         }
  144.     }

  145.     /**
  146.      * Sample uniformly from a box in ND.
  147.      */
  148.     private static final class BoxSamplerND extends BoxSampler {
  149.         /** Bound a. */
  150.         private final double[] a;
  151.         /** Bound b. */
  152.         private final double[] b;

  153.         /**
  154.          * @param rng Source of randomness.
  155.          * @param a Bound a.
  156.          * @param b Bound b.
  157.          */
  158.         BoxSamplerND(UniformRandomProvider rng, double[] a, double[] b) {
  159.             super(rng);
  160.             // Defensive copy
  161.             this.a = a.clone();
  162.             this.b = b.clone();
  163.         }

  164.         /**
  165.          * @param rng Source of randomness.
  166.          * @param source Source to copy.
  167.          */
  168.         BoxSamplerND(UniformRandomProvider rng, BoxSamplerND source) {
  169.             super(rng);
  170.             // Shared state is immutable
  171.             a = source.a;
  172.             b = source.b;
  173.         }

  174.         @Override
  175.         public double[] sample() {
  176.             final double[] x = new double[a.length];
  177.             for (int i = 0; i < x.length; i++) {
  178.                 x[i] = createSample(a[i], b[i]);
  179.             }
  180.             return x;
  181.         }

  182.         @Override
  183.         public BoxSampler withUniformRandomProvider(UniformRandomProvider rng) {
  184.             return new BoxSamplerND(rng, this);
  185.         }
  186.     }

  187.     /**
  188.      * @param rng Source of randomness.
  189.      */
  190.     BoxSampler(UniformRandomProvider rng) {
  191.         this.rng = rng;
  192.     }

  193.     /**
  194.      * @return a random Cartesian coordinate within the box.
  195.      */
  196.     @Override
  197.     public abstract double[] sample();

  198.     /**
  199.      * Creates the sample between bound a and b.
  200.      *
  201.      * @param a Bound a
  202.      * @param b Bound b
  203.      * @return the sample
  204.      */
  205.     double createSample(double a, double b) {
  206.         final double u = rng.nextDouble();
  207.         return (1.0 - u) * a + u * b;
  208.     }

  209.     /** {@inheritDoc} */
  210.     // Redeclare the signature to return a BoxSampler not a SharedStateObjectSampler<double[]>
  211.     @Override
  212.     public abstract BoxSampler withUniformRandomProvider(UniformRandomProvider rng);

  213.     /**
  214.      * Create a box sampler with bounds {@code a} and {@code b}.
  215.      * Sampled points are uniformly distributed within the box defined by the bounds.
  216.      *
  217.      * <p>Sampling is supported in dimensions of 2 or above. Single dimension sampling
  218.      * can be performed using a {@link LineSampler}.
  219.      *
  220.      * <p>Note: There is no requirement that {@code a <= b}. The samples will be uniformly
  221.      * distributed in the range {@code a} to {@code b} for each dimension.
  222.      *
  223.      * @param rng Source of randomness.
  224.      * @param a Bound a.
  225.      * @param b Bound b.
  226.      * @return the sampler
  227.      * @throws IllegalArgumentException If the bounds do not have the same
  228.      * dimension; the dimension is less than 2; or bounds have non-finite coordinates.
  229.      */
  230.     public static BoxSampler of(UniformRandomProvider rng,
  231.                                 double[] a,
  232.                                 double[] b) {
  233.         final int dimension = a.length;
  234.         if (dimension != b.length) {
  235.             throw new IllegalArgumentException(
  236.                 new StringBuilder("Mismatch of box dimensions: ").append(dimension).append(',')
  237.                                                                  .append(b.length).toString());
  238.         }
  239.         // Detect non-finite bounds
  240.         Coordinates.requireFinite(a, "Bound a");
  241.         Coordinates.requireFinite(b, "Bound b");
  242.         // Low dimension specialisations
  243.         if (dimension == TWO_D) {
  244.             return new BoxSampler2D(rng, a, b);
  245.         } else if (dimension == THREE_D) {
  246.             return new BoxSampler3D(rng, a, b);
  247.         } else if (dimension > THREE_D) {
  248.             return new BoxSamplerND(rng, a, b);
  249.         }
  250.         // Less than 2D
  251.         throw new IllegalArgumentException("Unsupported dimension: " + dimension);
  252.     }
  253. }