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. & 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 }