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
25   * <a href="https://en.wikipedia.org/wiki/Tetrahedron">tetrahedron</a>.
26   *
27   * <ul>
28   *  <li>
29   *   Uses the algorithm described in:
30   *   <blockquote>
31   *    Rocchini, C. and Cignoni, P. (2001)<br>
32   *    <i>Generating Random Points in a Tetrahedron</i>.<br>
33   *    <strong>Journal of Graphics Tools</strong> 5(4), pp. 9-12.
34   *   </blockquote>
35   *  </li>
36   * </ul>
37   *
38   * <p>Sampling uses:</p>
39   *
40   * <ul>
41   *   <li>{@link UniformRandomProvider#nextDouble()}
42   * </ul>
43   *
44   * @see <a href="https://doi.org/10.1080/10867651.2000.10487528">
45   *   Rocchini, C. &amp; Cignoni, P. (2001) Journal of Graphics Tools 5, pp. 9-12</a>
46   * @since 1.4
47   */
48  public class TetrahedronSampler implements SharedStateObjectSampler<double[]> {
49      /** The dimension for 3D sampling. */
50      private static final int THREE_D = 3;
51      /** The name of vertex a. */
52      private static final String VERTEX_A = "Vertex a";
53      /** The name of vertex b. */
54      private static final String VERTEX_B = "Vertex b";
55      /** The name of vertex c. */
56      private static final String VERTEX_C = "Vertex c";
57      /** The name of vertex d. */
58      private static final String VERTEX_D = "Vertex d";
59  
60      /** The first vertex. */
61      private final double[] a;
62      /** The second vertex. */
63      private final double[] b;
64      /** The third vertex. */
65      private final double[] c;
66      /** The fourth vertex. */
67      private final double[] d;
68      /** The source of randomness. */
69      private final UniformRandomProvider rng;
70  
71      /**
72       * @param rng Source of randomness.
73       * @param a The first vertex.
74       * @param b The second vertex.
75       * @param c The third vertex.
76       * @param d The fourth vertex.
77       */
78      TetrahedronSampler(UniformRandomProvider rng, double[] a, double[] b, double[] c, double[] d) {
79          // Defensive copy
80          this.a = a.clone();
81          this.b = b.clone();
82          this.c = c.clone();
83          this.d = d.clone();
84          this.rng = rng;
85      }
86  
87      /**
88       * @param rng Generator of uniformly distributed random numbers
89       * @param source Source to copy.
90       */
91      TetrahedronSampler(UniformRandomProvider rng, TetrahedronSampler source) {
92          // Shared state is immutable
93          a = source.a;
94          b = source.b;
95          c = source.c;
96          d = source.d;
97          this.rng = rng;
98      }
99  
100     /**
101      * @return a random Cartesian point within the tetrahedron.
102      */
103     @Override
104     public double[] sample() {
105         double s = rng.nextDouble();
106         double t = rng.nextDouble();
107         final double u = rng.nextDouble();
108         // Care is taken to ensure the 3 deviates remain in the 2^53 dyadic rationals in [0, 1).
109         // The following are exact for all the 2^53 dyadic rationals:
110         // 1 - u; u in [0, 1]
111         // u - 1; u in [0, 1]
112         // u + 1; u in [-1, 0]
113         // u + v; u in [-1, 0], v in [0, 1]
114         // u + v; u, v in [0, 1], u + v <= 1
115 
116         // Cut and fold with the plane s + t = 1
117         if (s + t > 1) {
118             // (s, t, u) = (1 - s, 1 - t, u)                if s + t > 1
119             s = 1 - s;
120             t = 1 - t;
121         }
122         // Now s + t <= 1.
123         // Cut and fold with the planes t + u = 1 and s + t + u = 1.
124         final double tpu = t + u;
125         final double sptpu = s + tpu;
126         if (sptpu > 1) {
127             if (tpu > 1) {
128                 // (s, t, u) = (s, 1 - u, 1 - s - t)        if t + u > 1
129                 // 1 - s - (1-u) - (1-s-t) == u - 1 + t
130                 return createSample(u - 1 + t, s, 1 - u, 1 - s - t);
131             }
132             // (s, t, u) = (1 - t - u, t, s + t + u - 1)    if t + u <= 1
133             // 1 - (1-t-u) - t - (s+t+u-1) == 1 - s - t
134             return createSample(1 - s - t, 1 - tpu, t, s - 1 + tpu);
135         }
136         return createSample(1 - sptpu, s, t, u);
137     }
138 
139     /**
140      * Creates the sample given the random variates {@code s}, {@code t} and {@code u} in the
141      * interval {@code [0, 1]} and {@code s + t + u <= 1}. The sum {@code 1 - s - t - u} is
142      * provided. The sample can be obtained from the tetrahedron {@code abcd} using:
143      *
144      * <pre>
145      * p = (1 - s - t - u)a + sb + tc + ud
146      * </pre>
147      *
148      * @param p1msmtmu plus 1 minus s minus t minus u ({@code 1 - s - t - u})
149      * @param s the first variate s
150      * @param t the second variate t
151      * @param u the third variate u
152      * @return the sample
153      */
154     private double[] createSample(double p1msmtmu, double s, double t, double u) {
155         // From the barycentric coordinates s,t,u create the point by moving along
156         // vectors ab, ac and ad.
157         // Here we do not compute the vectors and use the original vertices:
158         // p = a + s(b-a) + t(c-a) + u(d-a)
159         //   = (1-s-t-u)a + sb + tc + ud
160         return new double[] {p1msmtmu * a[0] + s * b[0] + t * c[0] + u * d[0],
161                              p1msmtmu * a[1] + s * b[1] + t * c[1] + u * d[1],
162                              p1msmtmu * a[2] + s * b[2] + t * c[2] + u * d[2]};
163     }
164 
165     /** {@inheritDoc} */
166     @Override
167     public TetrahedronSampler withUniformRandomProvider(UniformRandomProvider rng) {
168         return new TetrahedronSampler(rng, this);
169     }
170 
171     /**
172      * Create a tetrahedron sampler with vertices {@code a}, {@code b}, {@code c} and {@code d}.
173      * Sampled points are uniformly distributed within the tetrahedron.
174      *
175      * <p>No test for a volume is performed. If the vertices are coplanar the sampling
176      * distribution is undefined.
177      *
178      * @param rng Source of randomness.
179      * @param a The first vertex.
180      * @param b The second vertex.
181      * @param c The third vertex.
182      * @param d The fourth vertex.
183      * @return the sampler
184      * @throws IllegalArgumentException If the vertices do not have length 3;
185      * or vertices have non-finite coordinates
186      */
187     public static TetrahedronSampler of(UniformRandomProvider rng,
188                                         double[] a,
189                                         double[] b,
190                                         double[] c,
191                                         double[] d) {
192         // Must be 3D
193         Coordinates.requireLength(a, THREE_D, VERTEX_A);
194         Coordinates.requireLength(b, THREE_D, VERTEX_B);
195         Coordinates.requireLength(c, THREE_D, VERTEX_C);
196         Coordinates.requireLength(d, THREE_D, VERTEX_D);
197         // Detect non-finite vertices
198         Coordinates.requireFinite(a, VERTEX_A);
199         Coordinates.requireFinite(b, VERTEX_B);
200         Coordinates.requireFinite(c, VERTEX_C);
201         Coordinates.requireFinite(d, VERTEX_D);
202         return new TetrahedronSampler(rng, a, b, c, d);
203     }
204 }