1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.rng.sampling.shape;
18
19 import java.util.Arrays;
20 import org.apache.commons.math3.stat.inference.ChiSquareTest;
21 import org.apache.commons.rng.UniformRandomProvider;
22 import org.apache.commons.rng.sampling.RandomAssert;
23 import org.junit.jupiter.api.Assertions;
24 import org.junit.jupiter.api.Test;
25
26
27
28
29 class TetrahedronSamplerTest {
30
31
32
33 @Test
34 void testInvalidDimensionThrows() {
35 final UniformRandomProvider rng = RandomAssert.seededRNG();
36 final double[] ok = new double[3];
37 final double[] bad = new double[2];
38 final double[][] c = {ok, ok, ok, ok};
39 for (int i = 0; i < c.length; i++) {
40 final int ii = i;
41 c[i] = bad;
42 Assertions.assertThrows(IllegalArgumentException.class,
43 () -> TetrahedronSampler.of(rng, c[0], c[1], c[2], c[3]),
44 () -> String.format("Did not detect invalid dimension for vertex: %d", ii));
45 c[i] = ok;
46 }
47 }
48
49
50
51
52 @Test
53 void testNonFiniteVertexCoordinates() {
54 final UniformRandomProvider rng = RandomAssert.seededRNG();
55
56 final double[][] c = new double[][] {
57 {1, 1, 1}, {1, -1, 1}, {-1, 1, 1}, {1, 1, -1}
58 };
59 Assertions.assertNotNull(TetrahedronSampler.of(rng, c[0], c[1], c[2], c[3]));
60 final double[] bad = {Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN};
61 for (int i = 0; i < c.length; i++) {
62 final int ii = i;
63 for (int j = 0; j < c[0].length; j++) {
64 final int jj = j;
65 for (final double d : bad) {
66 final double value = c[i][j];
67 c[i][j] = d;
68 Assertions.assertThrows(IllegalArgumentException.class,
69 () -> TetrahedronSampler.of(rng, c[0], c[1], c[2], c[3]),
70 () -> String.format("Did not detect non-finite coordinate: %d,%d = %s", ii, jj, d));
71 c[i][j] = value;
72 }
73 }
74 }
75 }
76
77
78
79
80
81 @Test
82 void testExtremeValueCoordinates() {
83
84 final double[][] c1 = new double[][] {
85 {1, 1, 1}, {1, -1, -1}, {-1, -1, 1}, {-1, 1, -1}
86 };
87 final double[][] c2 = new double[4][3];
88
89 final double scale = 0x1.0p1023;
90 for (int i = 0; i < c1.length; i++) {
91
92 for (int j = 0; j < 3; j++) {
93 c2[i][j] = c1[i][j] * scale;
94 }
95 }
96
97 Assertions.assertEquals(Double.NEGATIVE_INFINITY, c2[2][0] - c2[0][0],
98 "Expect vector c - a to be infinite in the x dimension");
99 Assertions.assertEquals(Double.NEGATIVE_INFINITY, c2[2][1] - c2[3][1],
100 "Expect vector c - d to be infinite in the y dimension");
101 Assertions.assertEquals(Double.POSITIVE_INFINITY, c2[2][2] - c2[1][2],
102 "Expect vector c - b to be infinite in the z dimension");
103
104 final TetrahedronSampler sampler1 = TetrahedronSampler.of(
105 RandomAssert.seededRNG(), c1[0], c1[1], c1[2], c1[3]);
106 final TetrahedronSampler sampler2 = TetrahedronSampler.of(
107 RandomAssert.seededRNG(), c2[0], c2[1], c2[2], c2[3]);
108
109 for (int n = 0; n < 10; n++) {
110 final double[] a = sampler1.sample();
111 final double[] b = sampler2.sample();
112 for (int i = 0; i < a.length; i++) {
113 a[i] *= scale;
114 }
115 Assertions.assertArrayEquals(a, b);
116 }
117 }
118
119
120
121
122
123 @Test
124 void testDistribution() {
125
126 final double lx = -1;
127 final double ly = -2;
128 final double lz = 1;
129 final double ux = 3;
130 final double uy = 4;
131 final double uz = 5;
132
133
134 final double[] a = {lx, ly, lz};
135 final double[] b = {ux, ly, lz};
136 final double[] c = {ux, uy, lz};
137 final double[] d = {lx, uy, lz};
138 final double[] e = {lx, ly, uz};
139 final double[] f = {ux, ly, uz};
140 final double[] g = {ux, uy, uz};
141 final double[] h = {lx, uy, uz};
142
143
144 final int bins = 10;
145
146 final int samplesPerBin = 12;
147
148 final double sx = bins / (ux - lx);
149 final double sy = bins / (uy - ly);
150 final double sz = bins / (uz - lz);
151
152
153
154 final int binsXy = bins * bins;
155
156
157 final double[] expected = new double[binsXy * bins];
158 Arrays.fill(expected, 1);
159
160
161 final UniformRandomProvider rng = RandomAssert.createRNG();
162
163
164
165
166
167
168 final TetrahedronSampler[] samplers = {TetrahedronSampler.of(rng, d, f, b, c),
169 TetrahedronSampler.of(rng, d, f, c, g),
170 TetrahedronSampler.of(rng, d, f, g, h),
171 TetrahedronSampler.of(rng, d, f, h, e),
172 TetrahedronSampler.of(rng, d, f, e, a),
173 TetrahedronSampler.of(rng, d, f, a, b)};
174
175
176
177 final Tetrahedron[] tetrahedrons = {new Tetrahedron(d, f, b, c),
178 new Tetrahedron(d, f, c, g),
179 new Tetrahedron(d, f, g, h),
180 new Tetrahedron(d, f, h, e),
181 new Tetrahedron(d, f, e, a),
182 new Tetrahedron(d, f, a, b)};
183
184 final int samples = expected.length * samplesPerBin;
185 for (int n = 0; n < 1; n++) {
186
187 final long[] observed = new long[expected.length];
188
189 for (int i = 0; i < samples; i += 6) {
190 for (int j = 0; j < 6; j++) {
191 addObservation(samplers[j].sample(), observed, bins, binsXy,
192 lx, ly, lz, sx, sy, sz, tetrahedrons[j]);
193 }
194 }
195 final double p = new ChiSquareTest().chiSquareTest(expected, observed);
196 Assertions.assertFalse(p < 0.001, () -> "p-value too small: " + p);
197 }
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221 private static void addObservation(double[] v, long[] observed,
222 int binsX, int binsXy,
223 double lx, double ly, double lz,
224 double sx, double sy, double sz,
225 Tetrahedron tetrahedron) {
226 Assertions.assertEquals(3, v.length);
227
228 Assertions.assertTrue(tetrahedron.contains(v), "Not inside the tetrahedron");
229 final double x = v[0];
230 final double y = v[1];
231 final double z = v[2];
232
233 final int binx = (int) ((x - lx) * sx);
234 final int biny = (int) ((y - ly) * sy);
235 final int binz = (int) ((z - lz) * sz);
236 observed[binz * binsXy + biny * binsX + binx]++;
237 }
238
239
240
241
242
243 @Test
244 void testSharedStateSampler() {
245 final UniformRandomProvider rng1 = RandomAssert.seededRNG();
246 final UniformRandomProvider rng2 = RandomAssert.seededRNG();
247 final double[] c1 = createCoordinate(-1);
248 final double[] c2 = createCoordinate(2);
249 final double[] c3 = createCoordinate(-3);
250 final double[] c4 = createCoordinate(4);
251 final TetrahedronSampler sampler1 = TetrahedronSampler.of(rng1, c1, c2, c3, c4);
252 final TetrahedronSampler sampler2 = sampler1.withUniformRandomProvider(rng2);
253 RandomAssert.assertProduceSameSequence(sampler1, sampler2);
254 }
255
256
257
258
259 @Test
260 void testChangedInputCoordinates() {
261 final UniformRandomProvider rng1 = RandomAssert.seededRNG();
262 final UniformRandomProvider rng2 = RandomAssert.seededRNG();
263 final double[] c1 = createCoordinate(1);
264 final double[] c2 = createCoordinate(2);
265 final double[] c3 = createCoordinate(-3);
266 final double[] c4 = createCoordinate(-4);
267 final TetrahedronSampler sampler1 = TetrahedronSampler.of(rng1, c1, c2, c3, c4);
268
269
270
271 final double offset = 10;
272 for (int i = 0; i < 3; i++) {
273 c1[i] += offset;
274 c2[i] += offset;
275 c3[i] += offset;
276 c4[i] += offset;
277 }
278 final TetrahedronSampler sampler2 = TetrahedronSampler.of(rng2, c1, c2, c3, c4);
279 for (int n = 0; n < 5; n++) {
280 final double[] s1 = sampler1.sample();
281 final double[] s2 = sampler2.sample();
282 Assertions.assertEquals(3, s1.length);
283 Assertions.assertEquals(3, s2.length);
284 for (int i = 0; i < 3; i++) {
285 Assertions.assertEquals(s1[i] + offset, s2[i], 1e-10);
286 }
287 }
288 }
289
290
291
292
293 @Test
294 void testTetrahedronContains() {
295 final double[][] c1 = new double[][] {
296 {1, 1, 1}, {1, -1, -1}, {-1, -1, 1}, {-1, 1, -1}
297 };
298 final Tetrahedron tetrahedron = new Tetrahedron(c1[0], c1[1], c1[2], c1[3]);
299
300 final double epsilon = 1e-14;
301
302 for (int i = 0; i < 4; i++) {
303 Assertions.assertTrue(tetrahedron.contains(c1[i], epsilon));
304 }
305
306 Assertions.assertTrue(tetrahedron.contains(new double[] {1, 0, 0}, epsilon));
307 Assertions.assertTrue(tetrahedron.contains(new double[] {0.5, 0.5, 1}, epsilon));
308
309 Assertions.assertTrue(tetrahedron.contains(new double[] {1 - 1e-10, 0, 0}));
310 Assertions.assertTrue(tetrahedron.contains(new double[] {0.5, 0.5, 1 - 1e-10}));
311
312 Assertions.assertFalse(tetrahedron.contains(new double[] {1, 0, 1e-10}, epsilon));
313 Assertions.assertFalse(tetrahedron.contains(new double[] {0.5, 0.5 + 1e-10, 1}, epsilon));
314
315 double x = 1.0 / 3;
316 Assertions.assertTrue(tetrahedron.contains(new double[] {x, -x, x}, epsilon));
317 Assertions.assertTrue(tetrahedron.contains(new double[] {-x, -x, -x}, epsilon));
318 Assertions.assertTrue(tetrahedron.contains(new double[] {x, x, -x}, epsilon));
319 Assertions.assertTrue(tetrahedron.contains(new double[] {-x, x, x}, epsilon));
320
321 x += 1e-10;
322 Assertions.assertFalse(tetrahedron.contains(new double[] {x, -x, x}, epsilon));
323 Assertions.assertFalse(tetrahedron.contains(new double[] {-x, -x, -x}, epsilon));
324 Assertions.assertFalse(tetrahedron.contains(new double[] {x, x, -x}, epsilon));
325 Assertions.assertFalse(tetrahedron.contains(new double[] {-x, x, x}, epsilon));
326
327 Assertions.assertTrue(tetrahedron.contains(new double[] {0, 0, 0}));
328 Assertions.assertTrue(tetrahedron.contains(new double[] {0.5, 0.25, -0.1}));
329
330 Assertions.assertFalse(tetrahedron.contains(new double[] {0, 20, 0}));
331 Assertions.assertFalse(tetrahedron.contains(new double[] {-20, 0, 0}));
332 Assertions.assertFalse(tetrahedron.contains(new double[] {6, 6, 4}));
333 }
334
335
336
337
338
339
340
341
342 private static double[] createCoordinate(double x) {
343 final double[] coord = new double[3];
344 for (int i = 0; i < 3; i++) {
345 coord[0] = x + i;
346 }
347 return coord;
348 }
349
350
351
352
353
354
355
356
357
358 private static final class Tetrahedron {
359
360 private final double[][] n;
361
362 private final double[] d;
363
364
365
366
367
368
369
370
371
372 Tetrahedron(double[] v1, double[] v2, double[] v3, double[] v4) {
373
374 final double[][] x = new double[][] {
375 centre(v1, v2, v3),
376 centre(v2, v3, v4),
377 centre(v3, v4, v1),
378 centre(v4, v1, v2)
379 };
380
381
382 n = new double[][] {
383 normal(v1, v2, v3),
384 normal(v2, v3, v4),
385 normal(v3, v4, v1),
386 normal(v4, v1, v2)
387 };
388
389
390
391
392
393
394 d = new double[] {
395 -dot(n[0], x[0]),
396 -dot(n[1], x[1]),
397 -dot(n[2], x[2]),
398 -dot(n[3], x[3]),
399 };
400
401
402
403
404
405
406
407
408
409 final double[][] other = {v4, v1, v2, v3};
410 for (int i = 0; i < 4; i++) {
411 if (dot(n[i], other[i]) > -d[i]) {
412
413 n[i][0] = -n[i][0];
414 n[i][1] = -n[i][1];
415 n[i][2] = -n[i][2];
416 d[i] = -d[i];
417 }
418 }
419 }
420
421
422
423
424
425
426
427
428
429 private static double[] centre(double[] a, double[] b, double[] c) {
430 return new double[] {
431 (a[0] + b[0] + c[0]) / 3,
432 (a[1] + b[1] + c[1]) / 3,
433 (a[2] + b[2] + c[2]) / 3
434 };
435 }
436
437
438
439
440
441
442
443
444
445 private static double[] normal(double[] a, double[] b, double[] c) {
446 final double[] v1 = subtract(b, a);
447 final double[] v2 = subtract(c, a);
448
449 final double[] normal = {
450 v1[1] * v2[2] - v1[2] * v2[1],
451 v1[2] * v2[0] - v1[0] * v2[2],
452 v1[0] * v2[1] - v1[1] * v2[0]
453 };
454
455 final double scale = 1.0 / Math.sqrt(dot(normal, normal));
456 normal[0] *= scale;
457 normal[1] *= scale;
458 normal[2] *= scale;
459 return normal;
460 }
461
462
463
464
465
466
467
468
469
470
471
472
473 private static double dot(double[] a, double[] b) {
474 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
475 }
476
477
478
479
480
481
482
483
484 private static double[] subtract(double[] a, double[] b) {
485 return new double[] {
486 a[0] - b[0],
487 a[1] - b[1],
488 a[2] - b[2]
489 };
490 }
491
492
493
494
495
496
497
498 boolean contains(double[] x) {
499
500 for (int i = 0; i < 4; i++) {
501
502
503
504
505
506 if (dot(n[i], x) > -d[i]) {
507 return false;
508 }
509 }
510 return true;
511 }
512
513
514
515
516
517
518
519
520
521 boolean contains(double[] x, double epsilon) {
522 for (int i = 0; i < 4; i++) {
523
524 if (dot(n[i], x) > epsilon - d[i]) {
525 return false;
526 }
527 }
528 return true;
529 }
530 }
531 }