sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/03: Cleanup in test and benchmark methods.
Date Mon, 29 Apr 2019 16:34:22 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git

commit 3ec160968286ea19a72eff75412e23f202586504
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Apr 29 10:48:11 2019 +0200

    Cleanup in test and benchmark methods.
---
 .../projection/ConformalProjectionTest.java        | 134 +++++++++------------
 .../projection/MercatorMethodComparison.java       | 107 +++++-----------
 2 files changed, 89 insertions(+), 152 deletions(-)

diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
index d024bef..651a031 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/ConformalProjectionTest.java
@@ -60,7 +60,7 @@ public final strictfp class ConformalProjectionTest extends TransformTestCase
{
      * }
      */
     @Test
-    public void testMath() {
+    public void verifyMath() {
         assertEquals("Forward 0°N",      0, log(tan(PI/4)),                   TOLERANCE);
         assertEquals("Inverse 0 m",      0, PI/2 - 2*atan(exp(0)),            TOLERANCE);
         assertEquals("Forward 90°S",     NEGATIVE_INFINITY, log(tan(0)),      TOLERANCE);
@@ -88,10 +88,29 @@ public final strictfp class ConformalProjectionTest extends TransformTestCase
{
     }
 
     /**
-     * Implementation of {@link #testExpOfNorthing()}.
-     * The {@link #transform} field must have been set before this method is called.
+     * Tests the {@link ConformalProjection#expOfNorthing(double, double)} function.
+     *
+     * {@preformat text
+     *   Forward:  y = -log(t(φ))
+     *   Inverse:  φ = φ(exp(-y))
+     * }
+     */
+    @Test
+    @DependsOnMethod("verifyMath")
+    public void verifyNorthingKnownValues() {
+        verifyNorthingKnownValues(false);                       // Spherical case
+        verifyNorthingKnownValues(true);                        // Ellipsoidal case
+    }
+
+    /**
+     * Tests {@link ConformalProjection#expOfNorthing(double, double)} on some known values.
+     *
+     * @param  ellipsoidal  {@code true} for an ellipsoidal case, or {@code false} for a
spherical case.
      */
-    private void doTestExpOfNorthing() {
+    private void verifyNorthingKnownValues(final boolean ellipsoidal) {
+        transform = new NoOp(ellipsoidal);
+        tolerance = TOLERANCE;
+
         assertEquals("f(NaN) = NaN",       NaN, expOfNorthing(NaN),               tolerance);
         assertEquals("f( ±∞) = NaN",       NaN, expOfNorthing(NEGATIVE_INFINITY), tolerance);
         assertEquals("f( ±∞) = NaN",       NaN, expOfNorthing(POSITIVE_INFINITY), tolerance);
@@ -135,91 +154,64 @@ public final strictfp class ConformalProjectionTest extends TransformTestCase
{
 
     /**
      * Computes {@link ConformalProjection#expOfNorthing(double, double)} for the given latitude.
+     * The {@link #transform} field must have been set before this method is invoked.
      *
      * @param  φ  the latitude in radians.
      * @return {@code Math.exp} of the Mercator projection of the given latitude.
-     */
-    private double expOfNorthing(final double φ) {
-        return expOfNorthing((ConformalProjection) transform, φ);
-    }
-
-    /**
-     * Computes {@link ConformalProjection#expOfNorthing(double, double)} for the given latitude.
      *
-     * @param  projection  the projection on which to invoke {@code expOfNorthing(…)}.
-     * @param  φ           the latitude in radians.
-     * @return {@code Math.exp} of the Mercator projection of the given latitude.
+     * @see #φ(double)
      */
-    private static double expOfNorthing(final ConformalProjection projection, final double
φ) {
+    private double expOfNorthing(final double φ) {
+        final ConformalProjection projection = (ConformalProjection) transform;
         return projection.expOfNorthing(φ, projection.eccentricity * sin(φ));
     }
 
     /**
-     * Tests the {@link ConformalProjection#expOfNorthing(double, double)} function.
-     *
-     * {@preformat text
-     *   Forward:  y = -log(t(φ))
-     *   Inverse:  φ = φ(exp(-y))
-     * }
-     */
-    @Test
-    @DependsOnMethod("testMath")
-    public void testExpOfNorthing() {
-        transform = new NoOp(false);                            // Spherical case
-        tolerance = TOLERANCE;
-        doTestExpOfNorthing();
-        transform = new NoOp(true);                             // Ellipsoidal case
-        doTestExpOfNorthing();
-    }
-
-    /**
      * Tests the {@link ConformalProjection#dy_dφ(double, double)} method.
      *
      * @throws TransformException if an error occurred while projecting a point.
      */
     @Test
-    @DependsOnMethod("testExpOfNorthing")
+    @DependsOnMethod("verifyNorthingKnownValues")
     public void test_dy_dφ() throws TransformException {
-        tolerance = 1E-7;
-        doTest_dy_dφ(new NoOp(false));                          // Spherical case
-        doTest_dy_dφ(new NoOp(true));                           // Ellipsoidal case
+        test_dy_dφ(false);                                      // Spherical case
+        test_dy_dφ(true);                                       // Ellipsoidal case
     }
 
     /**
-     * Implementation of {@link #test_dy_dφ()}.
+     * Tests the {@link ConformalProjection#dy_dφ(double, double)} method.
+     *
+     * @param  ellipsoidal  {@code true} for an ellipsoidal case, or {@code false} for a
spherical case.
      */
-    private void doTest_dy_dφ(final NoOp projection) throws TransformException {
+    private void test_dy_dφ(final boolean ellipsoidal) throws TransformException {
+        final NoOp projection = new NoOp(ellipsoidal);
         transform = new AbstractMathTransform1D() {
             @Override public double transform(final double φ) {
-                return expOfNorthing(projection, φ);
+                return projection.expOfNorthing(φ, projection.eccentricity * sin(φ));
             }
             @Override public double derivative(final double φ) {
                 final double sinφ = sin(φ);
-                return projection.dy_dφ(sinφ, cos(φ)) * expOfNorthing(projection, φ);
+                return projection.dy_dφ(sinφ, cos(φ)) * transform(φ);
             }
         };
-        verifyInDomain(-89 * (PI/180), 89 * (PI/180));          // Verify from 85°S to 85°N.
-    }
-
-    /**
-     * Convenience method invoking {@link TransformTestCase#verifyInDomain} for an 1D transform.
-     */
-    private void verifyInDomain(final double min, final double max) throws TransformException
{
-        derivativeDeltas = new double[] {2E-8};
         isInverseTransformSupported = false;
-        verifyInDomain(
-                new double[] {min},                             // Minimal value to test.
-                new double[] {max},                             // Maximal value to test.
-                new int[]    {100},                             // Number of points to test.
-                TestUtilities.createRandomNumberGenerator());
+        derivativeDeltas = new double[] {2E-8};
+        tolerance = 1E-7;
+        verifyInDomain(new double[] {-89 * (PI/180)},                  // Minimal value to
test.
+                       new double[] {+89 * (PI/180)},                  // Maximal value to
test.
+                       new int[]    {100},                             // Number of points
to test.
+                       TestUtilities.createRandomNumberGenerator());
     }
 
     /**
      * Computes {@link ConformalProjection#φ(double)}.
+     * The {@link #transform} field must have been set before this method is invoked.
      *
      * @param  expOfSouthing  the reciprocal of the value returned by {@link #expOfNorthing(double)}.
      * @return the latitude in radians.
      * @throws ProjectionException if the iteration does not converge.
+     *
+     * @see #expOfNorthing(double)
      */
     private double φ(final double expOfSouthing) throws ProjectionException {
         return ((ConformalProjection) transform).φ(expOfSouthing);
@@ -234,21 +226,19 @@ public final strictfp class ConformalProjectionTest extends TransformTestCase
{
      * @throws ProjectionException if an error occurred while projecting a point.
      */
     @Test
-    @DependsOnMethod("testExpOfNorthing")
+    @DependsOnMethod("verifyNorthingKnownValues")
     public void test_φ() throws ProjectionException {
-        transform = new NoOp(false);                            // Spherical case
-        tolerance = TOLERANCE;
-        doTest_φ();
-        transform = new NoOp(true);                             // Ellipsoidal case
-        tolerance = NormalizedProjection.ITERATION_TOLERANCE;
-        doTest_φ();
+        test_φ(false);                                          // Spherical case
+        test_φ(true);                                           // Ellipsoidal case
     }
 
     /**
-     * Implementation of {@link #test_φ()}.
-     * The {@link #transform} field must have been set before this method is called.
+     * Tests {@link ConformalProjection#φ(double)} on known values and as the reverse of
{@code expOfNorthing(φ)}.
      */
-    private void doTest_φ() throws ProjectionException {
+    private void test_φ(final boolean ellipsoidal) throws ProjectionException {
+        transform = new NoOp(ellipsoidal);
+        tolerance = ellipsoidal ? NormalizedProjection.ITERATION_TOLERANCE : TOLERANCE;
+
         assertEquals("φ(NaN) = NaN",    NaN,   φ(NaN),               tolerance);
         assertEquals("φ( ∞)  = -90°", -PI/2,   φ(POSITIVE_INFINITY), tolerance);
         assertEquals("φ( ∞)  = -90°", -PI/2,   φ(MAX_VALUE),         tolerance);
@@ -282,21 +272,18 @@ public final strictfp class ConformalProjectionTest extends TransformTestCase
{
      * <ol>
      *   <li>φ values computed by an iterative method.</li>
      *   <li>φ values computed by the series expansion given by EPSG guide.</li>
-     *   <li>φ values computed by modified form of series expansion, using trigonometric
identities.</li>
-     *   <li>φ values computed by the actual {@link ConformalProjection} implementation.</li>
+     *   <li>φ values computed by the actual {@link ConformalProjection} implementation,
+     *       which uses a modified form of series expansion using trigonometric identities.</li>
      * </ol>
      *
-     * {@link ConformalProjection#φ(double)} which uses a mix of 1 and 3 in the above list.
-     * See {@link MercatorMethodComparison} for a discussion.
-     *
      * @throws ProjectionException if an error occurred during computation of φ.
      *
      * @see MercatorMethodComparison
      */
     @Test
-    public void compareWithSeriesExpansion() throws ProjectionException {
+    public void compareReference() throws ProjectionException {
         final ConformalProjection projection = new NoOp(true);
-        final MercatorMethodComparison comparator = new MercatorMethodComparison(projection.eccentricitySquared);
+        final MercatorMethodComparison comparator = new MercatorMethodComparison(projection);
         final Random random = TestUtilities.createRandomNumberGenerator();
         final int numSamples = 2000;
         for (int i=0; i<numSamples; i++) {
@@ -304,19 +291,16 @@ public final strictfp class ConformalProjectionTest extends TransformTestCase
{
             final double t = 1 / comparator.expOfNorthing(φ);
             final double byIterativeMethod = comparator.byIterativeMethod(t);
             final double bySeriesExpansion = comparator.bySeriesExpansion(t);
-            final double byTrigoIdentities = comparator.usingTrigonometricIdentities(t);
             final double byImplementation  = projection.φ(t);
             assertEquals("Iterative method",  φ, byIterativeMethod, 1E-11);
             assertEquals("Series expansion",  φ, bySeriesExpansion, 1E-11);
-            assertEquals("Trigo. identities", φ, byTrigoIdentities, 1E-11);
             assertEquals("Implementation",    φ, byImplementation,  1E-11);
             /*
              * Verify that the formulas modified with trigonometric identities give the same
results
              * than the original formulas. The main purpose of this test is to detect mistake
during
              * the application of identities.
              */
-            assertEquals(byTrigoIdentities, bySeriesExpansion, 1E-15);  // Tolerance threshold
close to 1 ULP of 2π.
-            assertEquals(projection.φ(t),   byTrigoIdentities, 1E-15);
+            assertEquals(byImplementation, bySeriesExpansion, 1E-15);  // Tolerance threshold
close to 1 ULP of 2π.
         }
     }
 }
diff --git a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorMethodComparison.java
b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorMethodComparison.java
index dd31382..342fab2 100644
--- a/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorMethodComparison.java
+++ b/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/MercatorMethodComparison.java
@@ -23,7 +23,6 @@ import java.io.UncheckedIOException;
 import org.apache.sis.io.TableAppender;
 import org.apache.sis.math.Statistics;
 import org.apache.sis.math.StatisticsFormat;
-import org.apache.sis.util.ArraysExt;
 import org.apache.sis.internal.referencing.Resources;
 import org.apache.sis.internal.metadata.ReferencingServices;
 
@@ -41,9 +40,9 @@ import static org.apache.sis.internal.util.StandardDateFormat.NANOS_PER_SECOND;
  *   <li>an iterative process for solving equation (7-9) from Snyder, initially implemented
by USGS.</li>
  * </ul>
  *
- * In our measurements, both the iterative process (USGS) and the series expansion (EPSG)
have the
- * same accuracy when applied on the WGS84 ellipsoid. However the EPSG formula is 2 times
faster.
- * On the other hand, accuracy of the EPSG formula decreases when we increase the eccentricity,
+ * In our measurements, both the iterative process (USGS) and the series expansion (EPSG)
have the same accuracy
+ * when applied on the WGS84 ellipsoid. However the EPSG formula is 2 times faster on Java
8 (less on more recent
+ * Java versions). On the other hand, accuracy of the EPSG formula decreases when we increase
the eccentricity,
  * while the iterative process keeps its accuracy (at the cost of more iterations).
  * For the Earth (eccentricity of about 0.082) the errors are less than 0.01 millimetres.
  * But the errors become centimetric (for a hypothetical planet of the size of the Earth)
@@ -55,7 +54,7 @@ import static org.apache.sis.internal.util.StandardDateFormat.NANOS_PER_SECOND;
  * win in about 50% of cases. But as we increase the eccentricity, the iterative method wins
more often.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.6
+ * @version 1.0
  * @since   0.6
  * @module
  */
@@ -77,29 +76,19 @@ public final class MercatorMethodComparison {   // No 'strictfp' keyword
here si
     private final double c2χ, c4χ, c6χ, c8χ;
 
     /**
-     * Creates a new instance for the eccentricity of the WGS84 ellipsoid, which is approximately
0.08181919084262157.
-     * Reminder: the eccentricity of a sphere is 0.
+     * The actual implementation to compare with.
      */
-    public MercatorMethodComparison() {
-        this(0.00669437999014133);                      // Squared eccentricity.
-    }
+    private final ConformalProjection implementation;
 
     /**
      * Creates a new instance for the same eccentricity than the given projection.
      *
      * @param  projection  the projection from which to take the eccentricity.
      */
-    public MercatorMethodComparison(final NormalizedProjection projection) {
-        this(projection.eccentricitySquared);
-    }
-
-    /**
-     * Creates a new instance for the given squared eccentricity.
-     *
-     * @param  e2  the square of the eccentricity.
-     */
-    public MercatorMethodComparison(final double e2) {
-        eccentricity = sqrt(e2);
+    MercatorMethodComparison(final ConformalProjection projection) {
+        implementation = projection;
+        eccentricity = projection.eccentricity;
+        final double e2 = projection.eccentricitySquared;
         final double e4 = e2 * e2;
         final double e6 = e2 * e4;
         final double e8 = e4 * e4;
@@ -129,30 +118,6 @@ public final class MercatorMethodComparison {   // No 'strictfp' keyword
here si
     }
 
     /**
-     * Same formula than {@link #bySeriesExpansion(double)}, but replacing some sine by trigonometric
identities.
-     * The identities used are:
-     *
-     * <ul>
-     *   <li>sin(2⋅x) = 2⋅sin(x)⋅cos(x)</li>
-     *   <li>sin(3⋅x) = (3 - 4⋅sin²(x))⋅sin(x)</li>
-     *   <li>sin(4⋅x) = (4 - 8⋅sin²(x))⋅sin(x)⋅cos(x)</li>
-     * </ul>
-     *
-     * @param  t  the {@code expOfSouthing} parameter value.
-     * @return the latitude (in radians) for the given parameter.
-     */
-    public double usingTrigonometricIdentities(final double t) {
-        final double χ = PI/2 - 2*atan(t);
-        final double sin2χ     = sin(2*χ);
-        final double sin_cos2χ = cos(2*χ) * sin2χ;
-        final double sin_sin2χ = sin2χ * sin2χ;
-        return c8χ * (4 - 8*sin_sin2χ)*sin_cos2χ
-             + c6χ * (3 - 4*sin_sin2χ)*sin2χ
-             + c4χ * 2*sin_cos2χ
-             + c2χ * sin2χ + χ;
-    }
-
-    /**
      * Computes φ using the iterative method used by USGS.
      * This is the second part of the {@link ConformalProjection#φ(double)} method.
      *
@@ -193,34 +158,28 @@ public final class MercatorMethodComparison {   // No 'strictfp' keyword
here si
      * @throws ProjectionException if an error occurred during the calculation of φ.
      */
     public void printAccuracyComparison(final int numSamples) throws ProjectionException
{
-        compare(null, numSamples, null);
+        compare(numSamples, null);
     }
 
     /**
      * Implementation of {@link #printAccuracyComparison(int)} and {@link #printErrorForExcentricities(double,double)},
      * optionally with a comparison with {@link ConformalProjection}.
      */
-    private void compare(final ConformalProjection projection, final int numSamples, final
TableAppender summarize)
-            throws ProjectionException
-    {
+    private void compare(final int numSamples, final TableAppender summarize) throws ProjectionException
{
         final Statistics iterativeMethodErrors = new Statistics("Iterative method error");
         final Statistics seriesExpansionErrors = new Statistics("Series expansion error");
-        final Statistics usingTrigoIdentErrors = new Statistics("Using trigonometric identities");
-        final Statistics abstractLambertErrors = new Statistics("'ConformalProjection' error");
+        final Statistics implementationErrors  = new Statistics("Implementation error");
         final Random random = new Random();
         for (int i=0; i<numSamples; i++) {
             final double φ = random.nextDouble() * PI - PI/2;
             final double t = 1 / expOfNorthing(φ);
             final double byIterativeMethod = byIterativeMethod(t);
             final double bySeriesExpansion = bySeriesExpansion(t);
-            final double usingTrigoIdent = usingTrigonometricIdentities(t);
+            final double byImplementation  = implementation.φ(t);
 
             iterativeMethodErrors.accept(abs(φ - byIterativeMethod) / NormalizedProjection.ITERATION_TOLERANCE);
             seriesExpansionErrors.accept(abs(φ - bySeriesExpansion) / NormalizedProjection.ITERATION_TOLERANCE);
-            usingTrigoIdentErrors.accept(abs(φ - usingTrigoIdent)   / NormalizedProjection.ITERATION_TOLERANCE);
-            if (projection != null) {
-                abstractLambertErrors.accept(abs(φ - projection.φ(t)) / NormalizedProjection.ITERATION_TOLERANCE);
-            }
+            implementationErrors .accept(abs(φ - byImplementation)  / NormalizedProjection.ITERATION_TOLERANCE);
         }
         /*
          * At this point we finished to collect the statistics for the eccentricity of this
particular
@@ -238,12 +197,8 @@ public final class MercatorMethodComparison {   // No 'strictfp' keyword
here si
             Statistics[] stats = new Statistics[] {
                 iterativeMethodErrors,
                 seriesExpansionErrors,
-                usingTrigoIdentErrors,
-                abstractLambertErrors
+                implementationErrors,
             };
-            if (projection == null) {
-                stats = ArraysExt.remove(stats, 2, 1);
-            }
             out.println("Comparison of different ways to compute φ for eccentricity " +
eccentricity + '.');
             out.println("Values are in units of " + NormalizedProjection.ITERATION_TOLERANCE
+ " radians (about "
                     + round(toDegrees(NormalizedProjection.ITERATION_TOLERANCE) * 60 * ReferencingServices.NAUTICAL_MILE
* 1000)
@@ -288,8 +243,9 @@ public final class MercatorMethodComparison {   // No 'strictfp' keyword
here si
                 crossThreshold = true;
                 table.appendHorizontalSeparator();
             }
-            final MercatorMethodComparison alt = new MercatorMethodComparison(eccentricity
* eccentricity);
-            alt.compare(null, 10000, table);
+            final double semiMinor = sqrt(1 - eccentricity * eccentricity);
+            final MercatorMethodComparison alt = new MercatorMethodComparison(new NoOp(1,
semiMinor));
+            alt.compare(10000, table);
         }
         table.appendHorizontalSeparator();
         try {
@@ -321,13 +277,13 @@ public final class MercatorMethodComparison {   // No 'strictfp' keyword
here si
         }
         final long t2 = System.nanoTime();
         for (int i=0; i<t.length; i++) {
-            s2 += usingTrigonometricIdentities(t[i]);
+            s2 += implementation.φ(t[i]);
         }
         final long t3 = System.nanoTime();
         final float c = (t1 - t0) / 100f;
-        out.println("Iterative method:         " + ((t1 - t0) / (float) NANOS_PER_SECOND)
+ " seconds (" + round((t1 - t0) / c) + "%).");
-        out.println("Series expansion:         " + ((t2 - t1) / (float) NANOS_PER_SECOND)
+ " seconds (" + round((t2 - t1) / c) + "%).");
-        out.println("Trigonometric identities: " + ((t3 - t2) / (float) NANOS_PER_SECOND)
+ " seconds (" + round((t3 - t2) / c) + "%).");
+        out.println("Iterative method: " + ((t1 - t0) / (float) NANOS_PER_SECOND) + " seconds
(" + round((t1 - t0) / c) + "%).");
+        out.println("Series expansion: " + ((t2 - t1) / (float) NANOS_PER_SECOND) + " seconds
(" + round((t2 - t1) / c) + "%).");
+        out.println("Implementation:   " + ((t3 - t2) / (float) NANOS_PER_SECOND) + " seconds
(" + round((t3 - t2) / c) + "%).");
         out.println("Mean φ values: " + (s0 / t.length) + ", "
                                       + (s1 / t.length) + " and "
                                       + (s2 / t.length) + ".");
@@ -348,29 +304,26 @@ public final class MercatorMethodComparison {   // No 'strictfp' keyword
here si
         out.println("Comparison of the errors for a sphere.");
         out.println("The errors should be almost zero:");
         out.println();
-        ConformalProjection projection = new NoOp(false);
-        MercatorMethodComparison c = new MercatorMethodComparison(projection);
-        c.compare(projection, 10000, null);
+        MercatorMethodComparison c = new MercatorMethodComparison(new NoOp(false));
+        c.compare(10000, null);
 
         out.println();
         out.println("Comparison of the errors for the WGS84 eccentricity.");
         out.println("The 'ConformalProjection' errors should be the same than the series
expansion errors:");
         out.println();
-        projection = new NoOp(true);
-        c = new MercatorMethodComparison(projection);
-        c.compare(projection, 1000000, null);
+        c = new MercatorMethodComparison(new NoOp(true));
+        c.compare(1000000, null);
 
         out.println();
         out.println("Comparison of the errors for the eccentricity of an imaginary ellipsoid.");
         out.println("The 'ConformalProjection' errors should be the close to the iterative
method errors:");
         out.println();
-        projection = new NoOp(100, 95);
-        c = new MercatorMethodComparison(projection);
-        c.compare(projection, 1000000, null);
+        c = new MercatorMethodComparison(new NoOp(100, 95));
+        c.compare(1000000, null);
 
         out.println();
         out.println("Benchmarks");
-        c = new MercatorMethodComparison();
+        c = new MercatorMethodComparison(new NoOp(true));
         for (int i=0; i<4; i++) {
             System.gc();
             Thread.sleep(1000);


Mime
View raw message