TetrahedronSampler.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
  22.  * <a href="https://en.wikipedia.org/wiki/Tetrahedron">tetrahedron</a>.
  23.  *
  24.  * <ul>
  25.  *  <li>
  26.  *   Uses the algorithm described in:
  27.  *   <blockquote>
  28.  *    Rocchini, C. and Cignoni, P. (2001)<br>
  29.  *    <i>Generating Random Points in a Tetrahedron</i>.<br>
  30.  *    <strong>Journal of Graphics Tools</strong> 5(4), pp. 9-12.
  31.  *   </blockquote>
  32.  *  </li>
  33.  * </ul>
  34.  *
  35.  * <p>Sampling uses:</p>
  36.  *
  37.  * <ul>
  38.  *   <li>{@link UniformRandomProvider#nextDouble()}
  39.  * </ul>
  40.  *
  41.  * @see <a href="https://doi.org/10.1080/10867651.2000.10487528">
  42.  *   Rocchini, C. &amp; Cignoni, P. (2001) Journal of Graphics Tools 5, pp. 9-12</a>
  43.  * @since 1.4
  44.  */
  45. public class TetrahedronSampler implements SharedStateObjectSampler<double[]> {
  46.     /** The dimension for 3D sampling. */
  47.     private static final int THREE_D = 3;
  48.     /** The name of vertex a. */
  49.     private static final String VERTEX_A = "Vertex a";
  50.     /** The name of vertex b. */
  51.     private static final String VERTEX_B = "Vertex b";
  52.     /** The name of vertex c. */
  53.     private static final String VERTEX_C = "Vertex c";
  54.     /** The name of vertex d. */
  55.     private static final String VERTEX_D = "Vertex d";

  56.     /** The first vertex. */
  57.     private final double[] a;
  58.     /** The second vertex. */
  59.     private final double[] b;
  60.     /** The third vertex. */
  61.     private final double[] c;
  62.     /** The fourth vertex. */
  63.     private final double[] d;
  64.     /** The source of randomness. */
  65.     private final UniformRandomProvider rng;

  66.     /**
  67.      * @param rng Source of randomness.
  68.      * @param a The first vertex.
  69.      * @param b The second vertex.
  70.      * @param c The third vertex.
  71.      * @param d The fourth vertex.
  72.      */
  73.     TetrahedronSampler(UniformRandomProvider rng, double[] a, double[] b, double[] c, double[] d) {
  74.         // Defensive copy
  75.         this.a = a.clone();
  76.         this.b = b.clone();
  77.         this.c = c.clone();
  78.         this.d = d.clone();
  79.         this.rng = rng;
  80.     }

  81.     /**
  82.      * @param rng Generator of uniformly distributed random numbers
  83.      * @param source Source to copy.
  84.      */
  85.     TetrahedronSampler(UniformRandomProvider rng, TetrahedronSampler source) {
  86.         // Shared state is immutable
  87.         a = source.a;
  88.         b = source.b;
  89.         c = source.c;
  90.         d = source.d;
  91.         this.rng = rng;
  92.     }

  93.     /**
  94.      * @return a random Cartesian point within the tetrahedron.
  95.      */
  96.     @Override
  97.     public double[] sample() {
  98.         double s = rng.nextDouble();
  99.         double t = rng.nextDouble();
  100.         final double u = rng.nextDouble();
  101.         // Care is taken to ensure the 3 deviates remain in the 2^53 dyadic rationals in [0, 1).
  102.         // The following are exact for all the 2^53 dyadic rationals:
  103.         // 1 - u; u in [0, 1]
  104.         // u - 1; u in [0, 1]
  105.         // u + 1; u in [-1, 0]
  106.         // u + v; u in [-1, 0], v in [0, 1]
  107.         // u + v; u, v in [0, 1], u + v <= 1

  108.         // Cut and fold with the plane s + t = 1
  109.         if (s + t > 1) {
  110.             // (s, t, u) = (1 - s, 1 - t, u)                if s + t > 1
  111.             s = 1 - s;
  112.             t = 1 - t;
  113.         }
  114.         // Now s + t <= 1.
  115.         // Cut and fold with the planes t + u = 1 and s + t + u = 1.
  116.         final double tpu = t + u;
  117.         final double sptpu = s + tpu;
  118.         if (sptpu > 1) {
  119.             if (tpu > 1) {
  120.                 // (s, t, u) = (s, 1 - u, 1 - s - t)        if t + u > 1
  121.                 // 1 - s - (1-u) - (1-s-t) == u - 1 + t
  122.                 return createSample(u - 1 + t, s, 1 - u, 1 - s - t);
  123.             }
  124.             // (s, t, u) = (1 - t - u, t, s + t + u - 1)    if t + u <= 1
  125.             // 1 - (1-t-u) - t - (s+t+u-1) == 1 - s - t
  126.             return createSample(1 - s - t, 1 - tpu, t, s - 1 + tpu);
  127.         }
  128.         return createSample(1 - sptpu, s, t, u);
  129.     }

  130.     /**
  131.      * Creates the sample given the random variates {@code s}, {@code t} and {@code u} in the
  132.      * interval {@code [0, 1]} and {@code s + t + u <= 1}. The sum {@code 1 - s - t - u} is
  133.      * provided. The sample can be obtained from the tetrahedron {@code abcd} using:
  134.      *
  135.      * <pre>
  136.      * p = (1 - s - t - u)a + sb + tc + ud
  137.      * </pre>
  138.      *
  139.      * @param p1msmtmu plus 1 minus s minus t minus u ({@code 1 - s - t - u})
  140.      * @param s the first variate s
  141.      * @param t the second variate t
  142.      * @param u the third variate u
  143.      * @return the sample
  144.      */
  145.     private double[] createSample(double p1msmtmu, double s, double t, double u) {
  146.         // From the barycentric coordinates s,t,u create the point by moving along
  147.         // vectors ab, ac and ad.
  148.         // Here we do not compute the vectors and use the original vertices:
  149.         // p = a + s(b-a) + t(c-a) + u(d-a)
  150.         //   = (1-s-t-u)a + sb + tc + ud
  151.         return new double[] {p1msmtmu * a[0] + s * b[0] + t * c[0] + u * d[0],
  152.                              p1msmtmu * a[1] + s * b[1] + t * c[1] + u * d[1],
  153.                              p1msmtmu * a[2] + s * b[2] + t * c[2] + u * d[2]};
  154.     }

  155.     /** {@inheritDoc} */
  156.     @Override
  157.     public TetrahedronSampler withUniformRandomProvider(UniformRandomProvider rng) {
  158.         return new TetrahedronSampler(rng, this);
  159.     }

  160.     /**
  161.      * Create a tetrahedron sampler with vertices {@code a}, {@code b}, {@code c} and {@code d}.
  162.      * Sampled points are uniformly distributed within the tetrahedron.
  163.      *
  164.      * <p>No test for a volume is performed. If the vertices are coplanar the sampling
  165.      * distribution is undefined.
  166.      *
  167.      * @param rng Source of randomness.
  168.      * @param a The first vertex.
  169.      * @param b The second vertex.
  170.      * @param c The third vertex.
  171.      * @param d The fourth vertex.
  172.      * @return the sampler
  173.      * @throws IllegalArgumentException If the vertices do not have length 3;
  174.      * or vertices have non-finite coordinates
  175.      */
  176.     public static TetrahedronSampler of(UniformRandomProvider rng,
  177.                                         double[] a,
  178.                                         double[] b,
  179.                                         double[] c,
  180.                                         double[] d) {
  181.         // Must be 3D
  182.         Coordinates.requireLength(a, THREE_D, VERTEX_A);
  183.         Coordinates.requireLength(b, THREE_D, VERTEX_B);
  184.         Coordinates.requireLength(c, THREE_D, VERTEX_C);
  185.         Coordinates.requireLength(d, THREE_D, VERTEX_D);
  186.         // Detect non-finite vertices
  187.         Coordinates.requireFinite(a, VERTEX_A);
  188.         Coordinates.requireFinite(b, VERTEX_B);
  189.         Coordinates.requireFinite(c, VERTEX_C);
  190.         Coordinates.requireFinite(d, VERTEX_D);
  191.         return new TetrahedronSampler(rng, a, b, c, d);
  192.     }
  193. }