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.examples.jmh.sampling.shape;
19  
20  import org.apache.commons.rng.UniformRandomProvider;
21  import org.apache.commons.rng.sampling.ObjectSampler;
22  import org.apache.commons.rng.sampling.UnitSphereSampler;
23  import org.apache.commons.rng.simple.RandomSource;
24  import org.openjdk.jmh.annotations.Benchmark;
25  import org.openjdk.jmh.annotations.BenchmarkMode;
26  import org.openjdk.jmh.annotations.Fork;
27  import org.openjdk.jmh.annotations.Level;
28  import org.openjdk.jmh.annotations.Measurement;
29  import org.openjdk.jmh.annotations.Mode;
30  import org.openjdk.jmh.annotations.OutputTimeUnit;
31  import org.openjdk.jmh.annotations.Param;
32  import org.openjdk.jmh.annotations.Scope;
33  import org.openjdk.jmh.annotations.Setup;
34  import org.openjdk.jmh.annotations.State;
35  import org.openjdk.jmh.annotations.Warmup;
36  import org.openjdk.jmh.infra.Blackhole;
37  import java.util.concurrent.TimeUnit;
38  
39  /**
40   * Executes benchmark to compare the speed of generating samples within an N-dimension triangle.
41   */
42  @BenchmarkMode(Mode.AverageTime)
43  @OutputTimeUnit(TimeUnit.NANOSECONDS)
44  @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
45  @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
46  @State(Scope.Benchmark)
47  @Fork(value = 1, jvmArgs = { "-server", "-Xms512M", "-Xmx512M" })
48  public class TriangleSamplerBenchmark {
49      /** Name for the baseline method. */
50      private static final String BASELINE = "Baseline";
51      /** Name for the baseline method including a 50:50 if statement. */
52      private static final String BASELINE_IF = "BaselineIf";
53      /** Name for the method using vectors. */
54      private static final String VECTORS = "Vectors";
55      /** Name for the method using the coordinates. */
56      private static final String COORDINATES = "Coordinates";
57      /** Error message for an unknown sampler type. */
58      private static final String UNKNOWN_SAMPLER = "Unknown sampler type: ";
59  
60      /**
61       * Base class for a sampler using vectors: {@code p = a + sv + tw}.
62       */
63      private abstract static class VectorTriangleSampler implements ObjectSampler<double[]> {
64          /** The source of randomness. */
65          private final UniformRandomProvider rng;
66  
67          /**
68           * Create an instance.
69           *
70           * @param rng the source of randomness
71           */
72          VectorTriangleSampler(UniformRandomProvider rng) {
73              this.rng = rng;
74          }
75  
76          /**
77           * @return a random Cartesian point inside the triangle.
78           */
79          @Override
80          public double[] sample() {
81              final double s = rng.nextDouble();
82              final double t = rng.nextDouble();
83              if (s + t > 1) {
84                  return createSample(1.0 - s, 1.0 - t);
85              }
86              return createSample(s, t);
87          }
88  
89          /**
90           * Creates the sample given the random variates {@code s} and {@code t} in the
91           * interval {@code [0, 1]} and {@code s + t <= 1}.
92           * The sample can be obtained from the triangle abc with v = b-a and w = c-a using:
93           * <pre>
94           * p = a + sv + tw
95           * </pre>
96           *
97           * @param s the first variate s
98           * @param t the second variate t
99           * @return the sample
100          */
101         protected abstract double[] createSample(double s, double t);
102     }
103 
104     /**
105      * Base class for a sampler using coordinates: {@code a(1 - s - t) + sb + tc}.
106      */
107     private abstract static class CoordinateTriangleSampler implements ObjectSampler<double[]> {
108         /** The source of randomness. */
109         private final UniformRandomProvider rng;
110 
111         /**
112          * Create an instance.
113          *
114          * @param rng the source of randomness
115          */
116         CoordinateTriangleSampler(UniformRandomProvider rng) {
117             this.rng = rng;
118         }
119 
120         /**
121          * @return a random Cartesian point inside the triangle.
122          */
123         @Override
124         public double[] sample() {
125             final double s = rng.nextDouble();
126             final double t = rng.nextDouble();
127             final double spt = s + t;
128             if (spt > 1) {
129                 // Transform: s1 = 1 - s; t1 = 1 - t.
130                 // Compute: 1 - s1 - t1
131                 // Do not assume (1 - (1-s) - (1-t)) is (s + t - 1), i.e. (spt - 1.0),
132                 // to avoid loss of a random bit due to rounding when s + t > 1.
133                 // An exact sum is (s - 1 + t).
134                 return createSample(s - 1.0 + t, 1.0 - s, 1.0 - t);
135             }
136             // Here s + t is exact so can be subtracted to make 1 - s - t
137             return createSample(1.0 - spt, s, t);
138         }
139 
140         /**
141          * Creates the sample given the random variates {@code s} and {@code t} in the
142          * interval {@code [0, 1]} and {@code s + t <= 1}. The sum {@code 1 - s - t} is provided.
143          * The sample can be obtained from the triangle abc using:
144          * <pre>
145          * p = a(1 - s - t) + sb + tc
146          * </pre>
147          *
148          * @param p1msmt plus 1 minus s minus t (1 - s - t)
149          * @param s the first variate s
150          * @param t the second variate t
151          * @return the sample
152          */
153         protected abstract double[] createSample(double p1msmt, double s, double t);
154     }
155 
156     /**
157      * Base class for the sampler data.
158      * Contains the source of randomness and the number of samples.
159      * The sampler should be created by a sub-class of the data.
160      */
161     @State(Scope.Benchmark)
162     public abstract static class SamplerData {
163         /** The sampler. */
164         private ObjectSampler<double[]> sampler;
165 
166         /** The number of samples. */
167         @Param({"1000"})
168         private int size;
169 
170         /**
171          * Gets the size.
172          *
173          * @return the size
174          */
175         public int getSize() {
176             return size;
177         }
178 
179         /**
180          * Gets the sampler.
181          *
182          * @return the sampler
183          */
184         public ObjectSampler<double[]> getSampler() {
185             return sampler;
186         }
187 
188         /**
189          * Create the source of randomness and the sampler.
190          */
191         @Setup(Level.Iteration)
192         public void setup() {
193             // This could be configured using @Param
194             final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
195             final int dimension = getDimension();
196             final UnitSphereSampler s = UnitSphereSampler.of(rng, dimension);
197             final double[] a = s.sample();
198             final double[] b = s.sample();
199             final double[] c = s.sample();
200             sampler = createSampler(rng, a, b, c);
201         }
202 
203         /**
204          * Gets the dimension of the triangle vertices.
205          *
206          * @return the dimension
207          */
208         protected abstract int getDimension();
209 
210         /**
211          * Creates the triangle sampler.
212          *
213          * @param rng the source of randomness
214          * @param a The first vertex.
215          * @param b The second vertex.
216          * @param c The third vertex.
217          * @return the sampler
218          */
219         protected abstract ObjectSampler<double[]> createSampler(UniformRandomProvider rng,
220                                                                  double[] a, double[] b, double[] c);
221     }
222 
223     /**
224      * The 2D triangle sampler.
225      */
226     @State(Scope.Benchmark)
227     public static class Sampler2D extends SamplerData {
228         /** The sampler type. */
229         @Param({BASELINE, BASELINE_IF, VECTORS, COORDINATES})
230         private String type;
231 
232         /** {@inheritDoc} */
233         @Override
234         protected int getDimension() {
235             return 2;
236         }
237 
238         /** {@inheritDoc} */
239         @Override
240         protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng,
241                                                         final double[] a, final double[] b, final double[] c) {
242             if (BASELINE.equals(type)) {
243                 return () -> {
244                     final double s = rng.nextDouble();
245                     final double t = rng.nextDouble();
246                     return new double[] {s, t};
247                 };
248             } else if (BASELINE_IF.equals(type)) {
249                 return () -> {
250                     final double s = rng.nextDouble();
251                     final double t = rng.nextDouble();
252                     if (s + t > 1) {
253                         return new double[] {s, t};
254                     }
255                     return new double[] {t, s};
256                 };
257             } else if (VECTORS.equals(type)) {
258                 return new VectorTriangleSampler2D(rng, a, b, c);
259             } else if (COORDINATES.equals(type)) {
260                 return new CoordinateTriangleSampler2D(rng, a, b, c);
261             }
262             throw new IllegalStateException(UNKNOWN_SAMPLER + type);
263         }
264 
265         /**
266          * Sample vectors in 2D.
267          */
268         private static class VectorTriangleSampler2D extends VectorTriangleSampler {
269             // CHECKSTYLE: stop JavadocVariableCheck
270             private final double ax;
271             private final double ay;
272             private final double vx;
273             private final double vy;
274             private final double wx;
275             private final double wy;
276             // CHECKSTYLE: resume JavadocVariableCheck
277 
278             /**
279              * @param rng the source of randomness
280              * @param a The first vertex.
281              * @param b The second vertex.
282              * @param c The third vertex.
283              */
284             VectorTriangleSampler2D(UniformRandomProvider rng, double[] a, double[] b, double[] c) {
285                 super(rng);
286                 ax = a[0];
287                 ay = a[1];
288                 vx = b[0] - ax;
289                 vy = b[1] - ay;
290                 wx = c[0] - ax;
291                 wy = c[1] - ay;
292             }
293 
294             @Override
295             protected double[] createSample(double s, double t) {
296                 return new double[] {ax + s * vx + t * wx,
297                                      ay + s * vy + t * wy};
298             }
299         }
300 
301         /**
302          * Sample using coordinates in 2D.
303          */
304         private static class CoordinateTriangleSampler2D extends CoordinateTriangleSampler {
305             // CHECKSTYLE: stop JavadocVariableCheck
306             private final double ax;
307             private final double ay;
308             private final double bx;
309             private final double by;
310             private final double cx;
311             private final double cy;
312             // CHECKSTYLE: resume JavadocVariableCheck
313 
314             /**
315              * @param rng the source of randomness
316              * @param a The first vertex.
317              * @param b The second vertex.
318              * @param c The third vertex.
319              */
320             CoordinateTriangleSampler2D(UniformRandomProvider rng, double[] a, double[] b, double[] c) {
321                 super(rng);
322                 ax = a[0];
323                 ay = a[1];
324                 bx = b[0];
325                 by = b[1];
326                 cx = c[0];
327                 cy = c[1];
328             }
329 
330             @Override
331             protected double[] createSample(double p1msmt, double s, double t) {
332                 return new double[] {p1msmt * ax + s * bx + t * cx,
333                                      p1msmt * ay + s * by + t * cy};
334             }
335         }    }
336 
337     /**
338      * The 3D triangle sampler.
339      */
340     @State(Scope.Benchmark)
341     public static class Sampler3D extends SamplerData {
342         /** The sampler type. */
343         @Param({BASELINE, BASELINE_IF, VECTORS, COORDINATES})
344         private String type;
345 
346         /** {@inheritDoc} */
347         @Override
348         protected int getDimension() {
349             return 3;
350         }
351 
352         /** {@inheritDoc} */
353         @Override
354         protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng,
355                                                         final double[] a, final double[] b, final double[] c) {
356             if (BASELINE.equals(type)) {
357                 return () -> {
358                     final double s = rng.nextDouble();
359                     final double t = rng.nextDouble();
360                     return new double[] {s, t, s};
361                 };
362             } else if (BASELINE_IF.equals(type)) {
363                 return () -> {
364                     final double s = rng.nextDouble();
365                     final double t = rng.nextDouble();
366                     if (s + t > 1) {
367                         return new double[] {s, t, s};
368                     }
369                     return new double[] {t, s, t};
370                 };
371             } else if (VECTORS.equals(type)) {
372                 return new VectorTriangleSampler3D(rng, a, b, c);
373             } else if (COORDINATES.equals(type)) {
374                 return new CoordinateTriangleSampler3D(rng, a, b, c);
375             }
376             throw new IllegalStateException(UNKNOWN_SAMPLER + type);
377         }
378 
379         /**
380          * Sample vectors in 3D.
381          */
382         private static class VectorTriangleSampler3D extends VectorTriangleSampler {
383             // CHECKSTYLE: stop JavadocVariableCheck
384             private final double ax;
385             private final double ay;
386             private final double az;
387             private final double vx;
388             private final double vy;
389             private final double vz;
390             private final double wx;
391             private final double wy;
392             private final double wz;
393             // CHECKSTYLE: resume JavadocVariableCheck
394 
395             /**
396              * @param rng the source of randomness
397              * @param a The first vertex.
398              * @param b The second vertex.
399              * @param c The third vertex.
400              */
401             VectorTriangleSampler3D(UniformRandomProvider rng, double[] a, double[] b, double[] c) {
402                 super(rng);
403                 ax = a[0];
404                 ay = a[1];
405                 az = a[2];
406                 vx = b[0] - ax;
407                 vy = b[1] - ay;
408                 vz = b[2] - az;
409                 wx = c[0] - ax;
410                 wy = c[1] - ay;
411                 wz = c[2] - az;
412             }
413 
414             @Override
415             protected double[] createSample(double s, double t) {
416                 return new double[] {ax + s * vx + t * wx,
417                                      ay + s * vy + t * wy,
418                                      az + s * vz + t * wz};
419             }
420         }
421 
422         /**
423          * Sample using coordinates in 3D.
424          */
425         private static class CoordinateTriangleSampler3D extends CoordinateTriangleSampler {
426             // CHECKSTYLE: stop JavadocVariableCheck
427             private final double ax;
428             private final double ay;
429             private final double az;
430             private final double bx;
431             private final double by;
432             private final double bz;
433             private final double cx;
434             private final double cy;
435             private final double cz;
436             // CHECKSTYLE: resume JavadocVariableCheck
437 
438             /**
439              * @param rng the source of randomness
440              * @param a The first vertex.
441              * @param b The second vertex.
442              * @param c The third vertex.
443              */
444             CoordinateTriangleSampler3D(UniformRandomProvider rng, double[] a, double[] b, double[] c) {
445                 super(rng);
446                 ax = a[0];
447                 ay = a[1];
448                 az = a[2];
449                 bx = b[0];
450                 by = b[1];
451                 bz = b[2];
452                 cx = c[0];
453                 cy = c[1];
454                 cz = c[2];
455             }
456 
457             @Override
458             protected double[] createSample(double p1msmt, double s, double t) {
459                 return new double[] {p1msmt * ax + s * bx + t * cx,
460                                      p1msmt * ay + s * by + t * cy,
461                                      p1msmt * az + s * bz + t * cz};
462             }
463         }
464     }
465 
466     /**
467      * The ND triangle sampler.
468      */
469     @State(Scope.Benchmark)
470     public static class SamplerND extends SamplerData {
471         /** The number of dimensions. */
472         @Param({"2", "3", "4", "8", "16", "32"})
473         private int dimension;
474         /** The sampler type. */
475         @Param({BASELINE, BASELINE_IF, VECTORS, COORDINATES})
476         private String type;
477 
478         /** {@inheritDoc} */
479         @Override
480         protected int getDimension() {
481             return dimension;
482         }
483 
484         /** {@inheritDoc} */
485         @Override
486         protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng,
487                                                         final double[] a, final double[] b, final double[] c) {
488             if (BASELINE.equals(type)) {
489                 return () -> {
490                     double s = rng.nextDouble();
491                     double t = rng.nextDouble();
492                     final double[] x = new double[a.length];
493                     for (int i = 0; i < x.length; i++) {
494                         x[i] = s;
495                         s = t;
496                         t = x[i];
497                     }
498                     return x;
499                 };
500             } else if (BASELINE_IF.equals(type)) {
501                 return () -> {
502                     double s = rng.nextDouble();
503                     double t = rng.nextDouble();
504                     final double[] x = new double[a.length];
505                     if (s + t > 1) {
506                         for (int i = 0; i < x.length; i++) {
507                             x[i] = t;
508                             t = s;
509                             s = x[i];
510                         }
511                         return x;
512                     }
513                     for (int i = 0; i < x.length; i++) {
514                         x[i] = s;
515                         s = t;
516                         t = x[i];
517                     }
518                     return x;
519                 };
520             } else if (VECTORS.equals(type)) {
521                 return new VectorTriangleSamplerND(rng, a, b, c);
522             } else if (COORDINATES.equals(type)) {
523                 return new CoordinateTriangleSamplerND(rng, a, b, c);
524             }
525             throw new IllegalStateException(UNKNOWN_SAMPLER + type);
526         }
527 
528         /**
529          * Sample vectors in ND.
530          */
531         private static class VectorTriangleSamplerND extends VectorTriangleSampler {
532             // CHECKSTYLE: stop JavadocVariableCheck
533             private final double[] a;
534             private final double[] v;
535             private final double[] w;
536             // CHECKSTYLE: resume JavadocVariableCheck
537 
538             /**
539              * @param rng the source of randomness
540              * @param a The first vertex.
541              * @param b The second vertex.
542              * @param c The third vertex.
543              */
544             VectorTriangleSamplerND(UniformRandomProvider rng, double[] a, double[] b, double[] c) {
545                 super(rng);
546                 this.a = a.clone();
547                 v = new double[a.length];
548                 w = new double[a.length];
549                 for (int i = 0; i < a.length; i++) {
550                     v[i] = b[i] - a[i];
551                     w[i] = c[i] - a[i];
552                 }
553             }
554 
555             @Override
556             protected double[] createSample(double s, double t) {
557                 final double[] x = new double[a.length];
558                 for (int i = 0; i < x.length; i++) {
559                     x[i] = a[i] + s * v[i] + t * w[i];
560                 }
561                 return x;
562             }
563         }
564 
565         /**
566          * Sample using coordinates in ND.
567          */
568         private static class CoordinateTriangleSamplerND extends CoordinateTriangleSampler {
569             // CHECKSTYLE: stop JavadocVariableCheck
570             private final double[] a;
571             private final double[] b;
572             private final double[] c;
573             // CHECKSTYLE: resume JavadocVariableCheck
574 
575             /**
576              * @param rng the source of randomness
577              * @param a The first vertex.
578              * @param b The second vertex.
579              * @param c The third vertex.
580              */
581             CoordinateTriangleSamplerND(UniformRandomProvider rng, double[] a, double[] b, double[] c) {
582                 super(rng);
583                 this.a = a.clone();
584                 this.b = b.clone();
585                 this.c = c.clone();
586             }
587 
588             @Override
589             protected double[] createSample(double p1msmt, double s, double t) {
590                 final double[] x = new double[a.length];
591                 for (int i = 0; i < x.length; i++) {
592                     x[i] = p1msmt * a[i] + s * b[i] + t * c[i];
593                 }
594                 return x;
595             }
596         }
597     }
598 
599     /**
600      * Run the sampler for the configured number of samples.
601      *
602      * @param bh Data sink
603      * @param data Input data.
604      */
605     private static void runSampler(Blackhole bh, SamplerData data) {
606         final ObjectSampler<double[]> sampler = data.getSampler();
607         for (int i = data.getSize() - 1; i >= 0; i--) {
608             bh.consume(sampler.sample());
609         }
610     }
611 
612     /**
613      * Generation of uniform samples from a 2D triangle.
614      *
615      * @param bh Data sink
616      * @param data Input data.
617      */
618     @Benchmark
619     public void create2D(Blackhole bh, Sampler2D data) {
620         runSampler(bh, data);
621     }
622 
623     /**
624      * Generation of uniform samples from a 3D triangle.
625      *
626      * @param bh Data sink
627      * @param data Input data.
628      */
629     @Benchmark
630     public void create3D(Blackhole bh, Sampler3D data) {
631         runSampler(bh, data);
632     }
633 
634     /**
635      * Generation of uniform samples from an ND triangle.
636      *
637      * @param bh Data sink
638      * @param data Input data.
639      */
640     @Benchmark
641     public void createND(Blackhole bh, SamplerND data) {
642         runSampler(bh, data);
643     }
644 }