| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.commons.rng.examples.jmh.sampling.shape; |
| |
| import org.apache.commons.rng.UniformRandomProvider; |
| import org.apache.commons.rng.sampling.UnitSphereSampler; |
| import org.apache.commons.rng.simple.RandomSource; |
| import org.openjdk.jmh.annotations.Benchmark; |
| import org.openjdk.jmh.annotations.BenchmarkMode; |
| import org.openjdk.jmh.annotations.Fork; |
| import org.openjdk.jmh.annotations.Level; |
| import org.openjdk.jmh.annotations.Measurement; |
| import org.openjdk.jmh.annotations.Mode; |
| import org.openjdk.jmh.annotations.OutputTimeUnit; |
| import org.openjdk.jmh.annotations.Param; |
| import org.openjdk.jmh.annotations.Scope; |
| import org.openjdk.jmh.annotations.Setup; |
| import org.openjdk.jmh.annotations.State; |
| import org.openjdk.jmh.annotations.Warmup; |
| import org.openjdk.jmh.infra.Blackhole; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Executes benchmark to compare the speed of generating samples within a tetrahedron. |
| */ |
| @BenchmarkMode(Mode.AverageTime) |
| @OutputTimeUnit(TimeUnit.NANOSECONDS) |
| @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) |
| @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) |
| @State(Scope.Benchmark) |
| @Fork(value = 1, jvmArgs = { "-server", "-Xms512M", "-Xmx512M" }) |
| public class TetrahedronSamplerBenchmark { |
| /** Name for the baseline method. */ |
| private static final String BASELINE = "Baseline"; |
| /** Name for the method using array coordinates. */ |
| private static final String ARRAY = "Array"; |
| /** Name for the method using non-array (primitive) coordinates. */ |
| private static final String NON_ARRAY = "NonArray"; |
| /** Name for the method using array coordinates and inline sample algorithm. */ |
| private static final String ARRAY_INLINE = "ArrayInline"; |
| /** Name for the method using non-array (primitive) coordinates and inline sample algorithm. */ |
| private static final String NON_ARRAY_INLINE = "NonArrayInline"; |
| /** Error message for an unknown sampler type. */ |
| private static final String UNKNOWN_SAMPLER = "Unknown sampler type: "; |
| |
| /** |
| * The sampler. |
| */ |
| private interface Sampler { |
| /** |
| * Gets the next sample. |
| * |
| * @return a random Cartesian point within the tetrahedron. |
| */ |
| double[] sample(); |
| } |
| |
| /** |
| * The base class for sampling from a tetrahedron. |
| * |
| * <ul> |
| * <li> |
| * Uses the algorithm described in: |
| * <blockquote> |
| * Rocchini, C. and Cignoni, P. (2001)<br> |
| * <i>Generating Random Points in a Tetrahedron</i>.<br> |
| * <strong>Journal of Graphics Tools</strong> 5(4), pp. 9-12. |
| * </blockquote> |
| * </li> |
| * </ul> |
| * |
| * @see <a href="https://doi.org/10.1080/10867651.2000.10487528"> |
| * Rocchini, C. & Cignoni, P. (2001) Journal of Graphics Tools 5, pp. 9-12</a> |
| */ |
| private abstract static class TetrahedronSampler implements Sampler { |
| /** The source of randomness. */ |
| private final UniformRandomProvider rng; |
| |
| /** |
| * @param rng Source of randomness. |
| */ |
| TetrahedronSampler(UniformRandomProvider rng) { |
| this.rng = rng; |
| } |
| |
| @Override |
| public double[] sample() { |
| double s = rng.nextDouble(); |
| double t = rng.nextDouble(); |
| final double u = rng.nextDouble(); |
| // Care is taken to ensure the 3 deviates remain in the 2^53 dyadic rationals in [0, 1). |
| // The following are exact for all the 2^53 dyadic rationals: |
| // 1 - u; u in [0, 1] |
| // u - 1; u in [0, 1] |
| // u + 1; u in [-1, 0] |
| // u + v; u in [-1, 0], v in [0, 1] |
| // u + v; u, v in [0, 1], u + v <= 1 |
| |
| // Cut and fold with the plane s + t = 1 |
| if (s + t > 1) { |
| // (s, t, u) = (1 - s, 1 - t, u) if s + t > 1 |
| s = 1 - s; |
| t = 1 - t; |
| } |
| // Now s + t <= 1. |
| // Cut and fold with the planes t + u = 1 and s + t + u = 1. |
| final double tpu = t + u; |
| final double sptpu = s + tpu; |
| if (sptpu > 1) { |
| if (tpu > 1) { |
| // (s, t, u) = (s, 1 - u, 1 - s - t) if t + u > 1 |
| // 1 - s - (1-u) - (1-s-t) == u - 1 + t |
| return createSample(u - 1 + t, s, 1 - u, 1 - s - t); |
| } |
| // (s, t, u) = (1 - t - u, t, s + t + u - 1) if t + u <= 1 |
| // 1 - (1-t-u) - t - (s+t+u-1) == 1 - s - t |
| return createSample(1 - s - t, 1 - tpu, t, s - 1 + tpu); |
| } |
| return createSample(1 - sptpu, s, t, u); |
| } |
| |
| /** |
| * Creates the sample given the random variates {@code s}, {@code t} and {@code u} in the |
| * interval {@code [0, 1]} and {@code s + t + u <= 1}. The sum {@code 1 - s - t - u} is |
| * provided. The sample can be obtained from the tetrahedron {@code abcd} using: |
| * |
| * <pre> |
| * p = (1 - s - t - u)a + sb + tc + ud |
| * </pre> |
| * |
| * @param p1msmtmu plus 1 minus s minus t minus u (1 - s - t - u) |
| * @param s the first variate s |
| * @param t the second variate t |
| * @param u the third variate u |
| * @return the sample |
| */ |
| protected abstract double[] createSample(double p1msmtmu, double s, double t, double u); |
| } |
| |
| /** |
| * Sample from a tetrahedron using array coordinates. |
| */ |
| private static class ArrayTetrahedronSampler extends TetrahedronSampler { |
| // CHECKSTYLE: stop JavadocVariableCheck |
| private final double[] a; |
| private final double[] b; |
| private final double[] c; |
| private final double[] d; |
| // CHECKSTYLE: resume JavadocVariableCheck |
| |
| /** |
| * @param a The first vertex. |
| * @param b The second vertex. |
| * @param c The third vertex. |
| * @param d The fourth vertex. |
| * @param rng Source of randomness. |
| */ |
| ArrayTetrahedronSampler(double[] a, double[] b, double[] c, double[] d, |
| UniformRandomProvider rng) { |
| super(rng); |
| this.a = a.clone(); |
| this.b = b.clone(); |
| this.c = c.clone(); |
| this.d = d.clone(); |
| } |
| |
| @Override |
| protected double[] createSample(double p1msmtmu, double s, double t, double u) { |
| return new double[] {p1msmtmu * a[0] + s * b[0] + t * c[0] + u * d[0], |
| p1msmtmu * a[1] + s * b[1] + t * c[1] + u * d[1], |
| p1msmtmu * a[2] + s * b[2] + t * c[2] + u * d[2]}; |
| } |
| } |
| |
| /** |
| * Sample from a tetrahedron using non-array coordinates. |
| */ |
| private static class NonArrayTetrahedronSampler extends TetrahedronSampler { |
| // CHECKSTYLE: stop JavadocVariableCheck |
| private final double ax; |
| private final double bx; |
| private final double cx; |
| private final double dx; |
| private final double ay; |
| private final double by; |
| private final double cy; |
| private final double dy; |
| private final double az; |
| private final double bz; |
| private final double cz; |
| private final double dz; |
| // CHECKSTYLE: resume JavadocVariableCheck |
| |
| /** |
| * @param a The first vertex. |
| * @param b The second vertex. |
| * @param c The third vertex. |
| * @param d The fourth vertex. |
| * @param rng Source of randomness. |
| */ |
| NonArrayTetrahedronSampler(double[] a, double[] b, double[] c, double[] d, |
| UniformRandomProvider rng) { |
| super(rng); |
| ax = a[0]; |
| ay = a[1]; |
| az = a[2]; |
| bx = b[0]; |
| by = b[1]; |
| bz = b[2]; |
| cx = c[0]; |
| cy = c[1]; |
| cz = c[2]; |
| dx = d[0]; |
| dy = d[1]; |
| dz = d[2]; |
| } |
| |
| @Override |
| protected double[] createSample(double p1msmtmu, double s, double t, double u) { |
| return new double[] {p1msmtmu * ax + s * bx + t * cx + u * dx, |
| p1msmtmu * ay + s * by + t * cy + u * dy, |
| p1msmtmu * az + s * bz + t * cz + u * dz}; |
| } |
| } |
| |
| /** |
| * Sample from a tetrahedron using array coordinates with an inline sample algorithm |
| * in-place of a method call. |
| */ |
| private static class ArrayInlineTetrahedronSampler implements Sampler { |
| // CHECKSTYLE: stop JavadocVariableCheck |
| private final double[] a; |
| private final double[] b; |
| private final double[] c; |
| private final double[] d; |
| private final UniformRandomProvider rng; |
| // CHECKSTYLE: resume JavadocVariableCheck |
| |
| /** |
| * @param a The first vertex. |
| * @param b The second vertex. |
| * @param c The third vertex. |
| * @param d The fourth vertex. |
| * @param rng Source of randomness. |
| */ |
| ArrayInlineTetrahedronSampler(double[] a, double[] b, double[] c, double[] d, |
| UniformRandomProvider rng) { |
| this.a = a.clone(); |
| this.b = b.clone(); |
| this.c = c.clone(); |
| this.d = d.clone(); |
| this.rng = rng; |
| } |
| |
| @Override |
| public double[] sample() { |
| double s = rng.nextDouble(); |
| double t = rng.nextDouble(); |
| double u = rng.nextDouble(); |
| |
| if (s + t > 1) { |
| // (s, t, u) = (1 - s, 1 - t, u) if s + t > 1 |
| s = 1 - s; |
| t = 1 - t; |
| } |
| |
| double p1msmtmu; |
| final double tpu = t + u; |
| final double sptpu = s + tpu; |
| if (sptpu > 1) { |
| if (tpu > 1) { |
| // (s, t, u) = (s, 1 - u, 1 - s - t) if t + u > 1 |
| final double tt = t; |
| p1msmtmu = u - 1 + t; |
| t = 1 - u; |
| u = 1 - s - tt; |
| } else { |
| // (s, t, u) = (1 - t - u, t, s + t + u - 1) if t + u <= 1 |
| final double ss = s; |
| p1msmtmu = 1 - s - t; |
| s = 1 - tpu; |
| u = ss - 1 + tpu; |
| } |
| } else { |
| p1msmtmu = 1 - sptpu; |
| } |
| |
| return new double[] {p1msmtmu * a[0] + s * b[0] + t * c[0] + u * d[0], |
| p1msmtmu * a[1] + s * b[1] + t * c[1] + u * d[1], |
| p1msmtmu * a[2] + s * b[2] + t * c[2] + u * d[2]}; |
| } |
| } |
| |
| /** |
| * Sample from a tetrahedron using non-array coordinates with an inline sample algorithm |
| * in-place of a method call. |
| */ |
| private static class NonArrayInlineTetrahedronSampler implements Sampler { |
| // CHECKSTYLE: stop JavadocVariableCheck |
| private final double ax; |
| private final double bx; |
| private final double cx; |
| private final double dx; |
| private final double ay; |
| private final double by; |
| private final double cy; |
| private final double dy; |
| private final double az; |
| private final double bz; |
| private final double cz; |
| private final double dz; |
| private final UniformRandomProvider rng; |
| // CHECKSTYLE: resume JavadocVariableCheck |
| |
| /** |
| * @param a The first vertex. |
| * @param b The second vertex. |
| * @param c The third vertex. |
| * @param d The fourth vertex. |
| * @param rng Source of randomness. |
| */ |
| NonArrayInlineTetrahedronSampler(double[] a, double[] b, double[] c, double[] d, |
| UniformRandomProvider rng) { |
| ax = a[0]; |
| ay = a[1]; |
| az = a[2]; |
| bx = b[0]; |
| by = b[1]; |
| bz = b[2]; |
| cx = c[0]; |
| cy = c[1]; |
| cz = c[2]; |
| dx = d[0]; |
| dy = d[1]; |
| dz = d[2]; |
| this.rng = rng; |
| } |
| |
| @Override |
| public double[] sample() { |
| double s = rng.nextDouble(); |
| double t = rng.nextDouble(); |
| double u = rng.nextDouble(); |
| |
| if (s + t > 1) { |
| // (s, t, u) = (1 - s, 1 - t, u) if s + t > 1 |
| s = 1 - s; |
| t = 1 - t; |
| } |
| |
| double p1msmtmu; |
| final double tpu = t + u; |
| final double sptpu = s + tpu; |
| if (sptpu > 1) { |
| if (tpu > 1) { |
| // (s, t, u) = (s, 1 - u, 1 - s - t) if t + u > 1 |
| final double tt = t; |
| p1msmtmu = u - 1 + t; |
| t = 1 - u; |
| u = 1 - s - tt; |
| } else { |
| // (s, t, u) = (1 - t - u, t, s + t + u - 1) if t + u <= 1 |
| final double ss = s; |
| p1msmtmu = 1 - s - t; |
| s = 1 - tpu; |
| u = ss - 1 + tpu; |
| } |
| } else { |
| p1msmtmu = 1 - sptpu; |
| } |
| |
| return new double[] {p1msmtmu * ax + s * bx + t * cx + u * dx, |
| p1msmtmu * ay + s * by + t * cy + u * dy, |
| p1msmtmu * az + s * bz + t * cz + u * dz}; |
| } |
| } |
| |
| /** |
| * Contains the sampler and the number of samples. |
| */ |
| @State(Scope.Benchmark) |
| public static class SamplerData { |
| /** The sampler. */ |
| private Sampler sampler; |
| |
| /** The number of samples. */ |
| @Param({"1", "10", "100", "1000", "10000"}) |
| private int size; |
| |
| /** The sampler type. */ |
| @Param({BASELINE, ARRAY, NON_ARRAY, ARRAY_INLINE, NON_ARRAY_INLINE}) |
| private String type; |
| |
| /** |
| * Gets the size. |
| * |
| * @return the size |
| */ |
| public int getSize() { |
| return size; |
| } |
| |
| /** |
| * Gets the sampler. |
| * |
| * @return the sampler |
| */ |
| public Sampler getSampler() { |
| return sampler; |
| } |
| |
| /** |
| * Create the source of randomness and the sampler. |
| */ |
| @Setup(Level.Iteration) |
| public void setup() { |
| // This could be configured using @Param |
| final UniformRandomProvider rng = RandomSource.create(RandomSource.XO_SHI_RO_256_PP); |
| final UnitSphereSampler s = UnitSphereSampler.of(3, rng); |
| final double[] a = s.nextVector(); |
| final double[] b = s.nextVector(); |
| final double[] c = s.nextVector(); |
| final double[] d = s.nextVector(); |
| sampler = createSampler(a, b, c, d, rng); |
| } |
| |
| /** |
| * Creates the tetrahedron sampler. |
| * |
| * @param a The first vertex. |
| * @param b The second vertex. |
| * @param c The third vertex. |
| * @param d The fourth vertex. |
| * @param rng the source of randomness |
| * @return the sampler |
| */ |
| private Sampler createSampler(double[] a, double[] b, double[] c, double[] d, |
| final UniformRandomProvider rng) { |
| if (BASELINE.equals(type)) { |
| return new Sampler() { |
| @Override |
| public double[] sample() { |
| final double s = rng.nextDouble(); |
| final double t = rng.nextDouble(); |
| final double u = rng.nextDouble(); |
| return new double[] {s, t, u}; |
| } |
| }; |
| } else if (ARRAY.equals(type)) { |
| return new ArrayTetrahedronSampler(a, b, c, d, rng); |
| } else if (NON_ARRAY.equals(type)) { |
| return new NonArrayTetrahedronSampler(a, b, c, d, rng); |
| } else if (ARRAY_INLINE.equals(type)) { |
| return new ArrayInlineTetrahedronSampler(a, b, c, d, rng); |
| } else if (NON_ARRAY_INLINE.equals(type)) { |
| return new NonArrayInlineTetrahedronSampler(a, b, c, d, rng); |
| } |
| throw new IllegalStateException(UNKNOWN_SAMPLER + type); |
| } |
| } |
| |
| /** |
| * Run the sampler for the configured number of samples. |
| * |
| * @param bh Data sink |
| * @param data Input data. |
| */ |
| @Benchmark |
| public void sample(Blackhole bh, SamplerData data) { |
| final Sampler sampler = data.getSampler(); |
| for (int i = data.getSize() - 1; i >= 0; i--) { |
| bh.consume(sampler.sample()); |
| } |
| } |
| } |