You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2018/07/21 10:07:44 UTC

[commons-geometry] 06/15: GEOMETRY-7: adding previous spherical gradient and Hessian convertion to new SphericalDerivativeConverter class; removing old SphericalCoordinates class

This is an automated email from the ASF dual-hosted git repository.

erans pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-geometry.git

commit fefd035e263190d74aa02127fcd1ffcfdb3d227e
Author: Matt Juntunen <ma...@hotmail.com>
AuthorDate: Sun Jul 1 00:09:07 2018 -0400

    GEOMETRY-7: adding previous spherical gradient and Hessian convertion to new SphericalDerivativeConverter class; removing old SphericalCoordinates class
---
 ..._OLD.java => SphericalDerivativeConverter.java} | 253 +++++----------------
 .../euclidean/threed/SphericalCoordinatesTest.java |  16 ++
 .../threed/SphericalCoordinatesTest_OLD.java       |  84 -------
 .../threed/SphericalDerivativeConverterTest.java   | 187 +++++++++++++++
 4 files changed, 254 insertions(+), 286 deletions(-)

diff --git a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates_OLD.java b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalDerivativeConverter.java
similarity index 60%
rename from commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates_OLD.java
rename to commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalDerivativeConverter.java
index ac39a5e..45b8794 100644
--- a/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinates_OLD.java
+++ b/commons-geometry-euclidean/src/main/java/org/apache/commons/geometry/euclidean/threed/SphericalDerivativeConverter.java
@@ -16,52 +16,16 @@
  */
 package org.apache.commons.geometry.euclidean.threed;
 
-
-import java.io.Serializable;
-
-/** This class provides conversions related to <a
- * href="http://mathworld.wolfram.com/SphericalCoordinates.html">spherical coordinates</a>.
- * <p>
- * The conventions used here are the mathematical ones, i.e. spherical coordinates are
- * related to Cartesian coordinates as follows:
- * </p>
- * <ul>
- *   <li>x = r cos(&theta;) sin(&Phi;)</li>
- *   <li>y = r sin(&theta;) sin(&Phi;)</li>
- *   <li>z = r cos(&Phi;)</li>
- * </ul>
- * <ul>
- *   <li>r       = &radic;(x<sup>2</sup>+y<sup>2</sup>+z<sup>2</sup>)</li>
- *   <li>&theta; = atan2(y, x)</li>
- *   <li>&Phi;   = acos(z/r)</li>
- * </ul>
- * <p>
- * r is the radius, &theta; is the azimuthal angle in the x-y plane and &Phi; is the polar
- * (co-latitude) angle. These conventions are <em>different</em> from the conventions used
- * in physics (and in particular in spherical harmonics) where the meanings of &theta; and
- * &Phi; are reversed.
- * </p>
- * <p>
- * This class provides conversion of coordinates and also of gradient and Hessian
- * between spherical and Cartesian coordinates.
- * </p>
+/** Class containing methods for converting gradients and Hessian
+ * matrices from spherical to Cartesian coordinates.
  */
-public class SphericalCoordinates_OLD implements Serializable {
-
-    /** Serializable UID. */
-    private static final long serialVersionUID = 20130206L;
-
-    /** Cartesian coordinates. */
-    private final Vector3D v;
+public class SphericalDerivativeConverter {
 
-    /** Radius. */
-    private final double r;
+    /** Spherical coordinates. */
+    private final SphericalCoordinates spherical;
 
-    /** Azimuthal angle in the x-y plane &theta;. */
-    private final double theta;
-
-    /** Polar angle (co-latitude) &Phi;. */
-    private final double phi;
+    /** Cartesian vector equivalent to spherical coordinates. */
+    private final Vector3D vector;
 
     /** Jacobian of (r, &theta; &Phi;). */
     private double[][] jacobian;
@@ -75,77 +39,26 @@ public class SphericalCoordinates_OLD implements Serializable {
     /** Hessian of polar (co-latitude) angle &Phi;. */
     private double[][] phiHessian;
 
-    /** Build a spherical coordinates transformer from Cartesian coordinates.
-     * @param v Cartesian coordinates
-     */
-    public SphericalCoordinates_OLD(final Vector3D v) {
-
-        // Cartesian coordinates
-        this.v = v;
-
-        // remaining spherical coordinates
-        this.r     = v.getNorm();
-        this.theta = 0.0; //v.getAlpha();
-        this.phi   = Math.acos(v.getZ() / r);
-
-    }
-
-    /** Build a spherical coordinates transformer from spherical coordinates.
-     * @param r radius
-     * @param theta azimuthal angle in x-y plane
-     * @param phi polar (co-latitude) angle
-     */
-    public SphericalCoordinates_OLD(final double r, final double theta, final double phi) {
-
-        final double cosTheta = Math.cos(theta);
-        final double sinTheta = Math.sin(theta);
-        final double cosPhi   = Math.cos(phi);
-        final double sinPhi   = Math.sin(phi);
+    public SphericalDerivativeConverter(SphericalCoordinates spherical) {
+        this.spherical = spherical;
+        this.vector = spherical.toVector();
 
-        // spherical coordinates
-        this.r     = r;
-        this.theta = theta;
-        this.phi   = phi;
-
-        // Cartesian coordinates
-        this.v  = Vector3D.of(r * cosTheta * sinPhi,
-                               r * sinTheta * sinPhi,
-                               r * cosPhi);
-
-    }
-
-    /** Get the Cartesian coordinates.
-     * @return Cartesian coordinates
-     */
-    public Vector3D getCartesian() {
-        return v;
-    }
-
-    /** Get the radius.
-     * @return radius r
-     * @see #getTheta()
-     * @see #getPhi()
-     */
-    public double getR() {
-        return r;
+        computeJacobian();
     }
 
-    /** Get the azimuthal angle in x-y plane.
-     * @return azimuthal angle in x-y plane &theta;
-     * @see #getR()
-     * @see #getPhi()
+    /** Return the {@link SphericalCoordinates} for this instance.
+     * @return spherical coordinates for this instance
      */
-    public double getTheta() {
-        return theta;
+    public SphericalCoordinates getSpherical() {
+        return spherical;
     }
 
-    /** Get the polar (co-latitude) angle.
-     * @return polar (co-latitude) angle &Phi;
-     * @see #getR()
-     * @see #getTheta()
+    /** Return the {@link Vector3D} for this instance. This vector is
+     * equivalent to the spherical coordinates.
+     * @return vector for this instance
      */
-    public double getPhi() {
-        return phi;
+    public Vector3D getVector() {
+        return vector;
     }
 
     /** Convert a gradient with respect to spherical coordinates into a gradient
@@ -156,10 +69,6 @@ public class SphericalCoordinates_OLD implements Serializable {
      * {df/dx, df/dy, df/dz}
      */
     public double[] toCartesianGradient(final double[] sGradient) {
-
-        // lazy evaluation of Jacobian
-        computeJacobian();
-
         // compose derivatives as gradient^T . J
         // the expressions have been simplified since we know jacobian[1][2] = dTheta/dZ = 0
         return new double[] {
@@ -167,7 +76,6 @@ public class SphericalCoordinates_OLD implements Serializable {
             sGradient[0] * jacobian[0][1] + sGradient[1] * jacobian[1][1] + sGradient[2] * jacobian[2][1],
             sGradient[0] * jacobian[0][2]                                 + sGradient[2] * jacobian[2][2]
         };
-
     }
 
     /** Convert a Hessian with respect to spherical coordinates into a Hessian
@@ -189,8 +97,6 @@ public class SphericalCoordinates_OLD implements Serializable {
      *  {d<sup>2</sup>f/dxdz, d<sup>2</sup>f/dydz, d<sup>2</sup>f/dz<sup>2</sup>}}
      */
     public double[][] toCartesianHessian(final double[][] sHessian, final double[] sGradient) {
-
-        computeJacobian();
         computeHessians();
 
         // compose derivative as J^T . H_f . J + df/dr H_r + df/dtheta H_theta + df/dphi H_phi
@@ -233,52 +139,47 @@ public class SphericalCoordinates_OLD implements Serializable {
         cHessian[1][2] = cHessian[2][1];
 
         return cHessian;
-
     }
 
-    /** Lazy evaluation of (r, &theta;, &phi;) Jacobian.
-     */
+    /** Evaluates (r, &theta;, &phi;) Jacobian. */
     private void computeJacobian() {
-        if (jacobian == null) {
 
-            // intermediate variables
-            final double x    = v.getX();
-            final double y    = v.getY();
-            final double z    = v.getZ();
-            final double rho2 = x * x + y * y;
-            final double rho  = Math.sqrt(rho2);
-            final double r2   = rho2 + z * z;
-
-            jacobian = new double[3][3];
-
-            // row representing the gradient of r
-            jacobian[0][0] = x / r;
-            jacobian[0][1] = y / r;
-            jacobian[0][2] = z / r;
-
-            // row representing the gradient of theta
-            jacobian[1][0] = -y / rho2;
-            jacobian[1][1] =  x / rho2;
-            // jacobian[1][2] is already set to 0 at allocation time
-
-            // row representing the gradient of phi
-            jacobian[2][0] = x * z / (rho * r2);
-            jacobian[2][1] = y * z / (rho * r2);
-            jacobian[2][2] = -rho / r2;
-
-        }
+        // intermediate variables
+        final double r    = spherical.getRadius();
+        final double x    = vector.getX();
+        final double y    = vector.getY();
+        final double z    = vector.getZ();
+        final double rho2 = x * x + y * y;
+        final double rho  = Math.sqrt(rho2);
+        final double r2   = rho2 + z * z;
+
+        jacobian = new double[3][3];
+
+        // row representing the gradient of r
+        jacobian[0][0] = x / r;
+        jacobian[0][1] = y / r;
+        jacobian[0][2] = z / r;
+
+        // row representing the gradient of theta
+        jacobian[1][0] = -y / rho2;
+        jacobian[1][1] =  x / rho2;
+        // jacobian[1][2] is already set to 0 at allocation time
+
+        // row representing the gradient of phi
+        jacobian[2][0] = x * z / (rho * r2);
+        jacobian[2][1] = y * z / (rho * r2);
+        jacobian[2][2] = -rho / r2;
     }
 
-    /** Lazy evaluation of Hessians.
-     */
+    /** Lazy evaluation of Hessians. */
     private void computeHessians() {
-
         if (rHessian == null) {
 
             // intermediate variables
-            final double x      = v.getX();
-            final double y      = v.getY();
-            final double z      = v.getZ();
+            final double r      = spherical.getRadius();
+            final double x      = vector.getX();
+            final double y      = vector.getY();
+            final double z      = vector.getZ();
             final double x2     = x * x;
             final double y2     = y * y;
             final double z2     = z * z;
@@ -335,58 +236,6 @@ public class SphericalCoordinates_OLD implements Serializable {
             phiHessian[0][1] = phiHessian[1][0];
             phiHessian[0][2] = phiHessian[2][0];
             phiHessian[1][2] = phiHessian[2][1];
-
         }
-
-    }
-
-    /**
-     * Replace the instance with a data transfer object for serialization.
-     * @return data transfer object that will be serialized
-     */
-    private Object writeReplace() {
-        return new DataTransferObject(v.getX(), v.getY(), v.getZ());
     }
-
-    /** Internal class used only for serialization. */
-    private static class DataTransferObject implements Serializable {
-
-        /** Serializable UID. */
-        private static final long serialVersionUID = 20130206L;
-
-        /** Abscissa.
-         * @serial
-         */
-        private final double x;
-
-        /** Ordinate.
-         * @serial
-         */
-        private final double y;
-
-        /** Height.
-         * @serial
-         */
-        private final double z;
-
-        /** Simple constructor.
-         * @param x abscissa
-         * @param y ordinate
-         * @param z height
-         */
-        DataTransferObject(final double x, final double y, final double z) {
-            this.x = x;
-            this.y = y;
-            this.z = z;
-        }
-
-        /** Replace the deserialized data transfer object with a {@link SphericalCoordinates_OLD}.
-         * @return replacement {@link SphericalCoordinates_OLD}
-         */
-        private Object readResolve() {
-            return new SphericalCoordinates_OLD(Vector3D.of(x, y, z));
-        }
-
-    }
-
 }
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java
index d1d9f01..37d381d 100644
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest.java
@@ -1,3 +1,19 @@
+/*
+ * 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.geometry.euclidean.threed;
 
 import java.util.regex.Pattern;
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest_OLD.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest_OLD.java
deleted file mode 100644
index 999111c..0000000
--- a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalCoordinatesTest_OLD.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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.geometry.euclidean.threed;
-
-import org.apache.commons.geometry.euclidean.threed.SphericalCoordinates_OLD;
-import org.apache.commons.geometry.euclidean.threed.Vector3D;
-import org.apache.commons.geometry.core.GeometryTestUtils;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class SphericalCoordinatesTest_OLD {
-
-    @Test
-    public void testCoordinatesStoC() {
-        double piO2 = 0.5 * Math.PI;
-        SphericalCoordinates_OLD sc1 = new SphericalCoordinates_OLD(2.0, 0, piO2);
-        Assert.assertEquals(0, sc1.getCartesian().distance(Vector3D.of(2, 0, 0)), 1.0e-10);
-        SphericalCoordinates_OLD sc2 = new SphericalCoordinates_OLD(2.0, piO2, piO2);
-        Assert.assertEquals(0, sc2.getCartesian().distance(Vector3D.of(0, 2, 0)), 1.0e-10);
-        SphericalCoordinates_OLD sc3 = new SphericalCoordinates_OLD(2.0, Math.PI, piO2);
-        Assert.assertEquals(0, sc3.getCartesian().distance(Vector3D.of(-2, 0, 0)), 1.0e-10);
-        SphericalCoordinates_OLD sc4 = new SphericalCoordinates_OLD(2.0, -piO2, piO2);
-        Assert.assertEquals(0, sc4.getCartesian().distance(Vector3D.of(0, -2, 0)), 1.0e-10);
-        SphericalCoordinates_OLD sc5 = new SphericalCoordinates_OLD(2.0, 1.23456, 0);
-        Assert.assertEquals(0, sc5.getCartesian().distance(Vector3D.of(0, 0, 2)), 1.0e-10);
-        SphericalCoordinates_OLD sc6 = new SphericalCoordinates_OLD(2.0, 6.54321, Math.PI);
-        Assert.assertEquals(0, sc6.getCartesian().distance(Vector3D.of(0, 0, -2)), 1.0e-10);
-    }
-
-    @Test
-    public void testCoordinatesCtoS() {
-        double piO2 = 0.5 * Math.PI;
-        SphericalCoordinates_OLD sc1 = new SphericalCoordinates_OLD(Vector3D.of(2, 0, 0));
-        Assert.assertEquals(2,           sc1.getR(),     1.0e-10);
-        Assert.assertEquals(0,           sc1.getTheta(), 1.0e-10);
-        Assert.assertEquals(piO2,        sc1.getPhi(),   1.0e-10);
-        SphericalCoordinates_OLD sc2 = new SphericalCoordinates_OLD(Vector3D.of(0, 2, 0));
-        Assert.assertEquals(2,           sc2.getR(),     1.0e-10);
-        Assert.assertEquals(piO2,        sc2.getTheta(), 1.0e-10);
-        Assert.assertEquals(piO2,        sc2.getPhi(),   1.0e-10);
-        SphericalCoordinates_OLD sc3 = new SphericalCoordinates_OLD(Vector3D.of(-2, 0, 0));
-        Assert.assertEquals(2,           sc3.getR(),     1.0e-10);
-        Assert.assertEquals(Math.PI, sc3.getTheta(), 1.0e-10);
-        Assert.assertEquals(piO2,        sc3.getPhi(),   1.0e-10);
-        SphericalCoordinates_OLD sc4 = new SphericalCoordinates_OLD(Vector3D.of(0, -2, 0));
-        Assert.assertEquals(2,           sc4.getR(),     1.0e-10);
-        Assert.assertEquals(-piO2,       sc4.getTheta(), 1.0e-10);
-        Assert.assertEquals(piO2,        sc4.getPhi(),   1.0e-10);
-        SphericalCoordinates_OLD sc5 = new SphericalCoordinates_OLD(Vector3D.of(0, 0, 2));
-        Assert.assertEquals(2,           sc5.getR(),     1.0e-10);
-        //  don't check theta on poles, as it is singular
-        Assert.assertEquals(0,           sc5.getPhi(),   1.0e-10);
-        SphericalCoordinates_OLD sc6 = new SphericalCoordinates_OLD(Vector3D.of(0, 0, -2));
-        Assert.assertEquals(2,           sc6.getR(),     1.0e-10);
-        //  don't check theta on poles, as it is singular
-        Assert.assertEquals(Math.PI, sc6.getPhi(),   1.0e-10);
-    }
-
-    @Test
-    public void testSerialization() {
-        SphericalCoordinates_OLD a = new SphericalCoordinates_OLD(3, 2, 1);
-        SphericalCoordinates_OLD b = (SphericalCoordinates_OLD) GeometryTestUtils.serializeAndRecover(a);
-        Assert.assertEquals(0, a.getCartesian().distance(b.getCartesian()), 1.0e-10);
-        Assert.assertEquals(a.getR(),     b.getR(),     1.0e-10);
-        Assert.assertEquals(a.getTheta(), b.getTheta(), 1.0e-10);
-        Assert.assertEquals(a.getPhi(),   b.getPhi(),   1.0e-10);
-    }
-
-}
diff --git a/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalDerivativeConverterTest.java b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalDerivativeConverterTest.java
new file mode 100644
index 0000000..4b9bcbd
--- /dev/null
+++ b/commons-geometry-euclidean/src/test/java/org/apache/commons/geometry/euclidean/threed/SphericalDerivativeConverterTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.geometry.euclidean.threed;
+
+import org.apache.commons.geometry.core.Geometry;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SphericalDerivativeConverterTest {
+
+    private static final double EPS = 1e-10;
+
+    @Test
+    public void testConstructor() {
+        // arrange
+        SphericalCoordinates sc = SphericalCoordinates.of(2, Geometry.PI, Geometry.HALF_PI);
+
+        // act
+        SphericalDerivativeConverter conv = new SphericalDerivativeConverter(sc);
+
+        // assert
+        checkSpherical(conv.getSpherical(), 2, Geometry.PI, Geometry.HALF_PI);
+        checkVector(conv.getVector(), -2, 0, 0);
+    }
+
+    @Test
+    public void testToCartesianGradient() {
+        // NOTE: The following set of test data is taken from the original test for this code in commons-math.
+        // The test in that project generated and checked the inputs on the fly using the commons-math differentiation
+        // classes. However, since we don't have the benefit of those here, we're using some selected data points
+        // from that test.
+
+        // act/assert
+        checkToCartesianGradient(
+                SphericalCoordinates.of(0.2, 0.1, 0.1),
+                new double[] { 3.1274095413292105E-4, -1.724542757978006E-6, 1.5102449769881866E-4 },
+                new double[] { 0.0007872851, -0.0000078127, 0.0002357921 });
+
+        checkToCartesianGradient(
+                SphericalCoordinates.of(0.2, 0.1, 1.6),
+                new double[] { -7.825830329191124E-8, 7.798528724837122E-10, -4.027286034178383E-7 },
+                new double[] { -0.0000000197, 0.0000000019, 0.0000020151 });
+
+        checkToCartesianGradient(
+                SphericalCoordinates.of(0.2, 1.6, 0.1),
+                new double[] { -9.075903886546823E-6, -1.5573157416535893E-5, -4.352284221940998E-6 },
+                new double[] { 0.0007802833, 0.0000002252, -0.000006858 });
+
+        checkToCartesianGradient(
+                SphericalCoordinates.of(0.2, 2.4, 2.4),
+                new double[] { 6.045188551967462E-4, 2.944844493772992E-5, 5.207279563401837E-5 },
+                new double[] { -0.0003067696, -0.0000146129, -0.0006216347 });
+
+        checkToCartesianGradient(
+                SphericalCoordinates.of(9.2, 5.5, 2.4),
+                new double[] { 27.09285722408859, 327.829199283976, 422.53939642005736 },
+                new double[] { 26.1884919572, 48.3685006936, -51.0009075025 });
+    }
+
+    private void checkToCartesianGradient(SphericalCoordinates spherical, double[] sGradient, double[] cGradient) {
+        SphericalDerivativeConverter conv = new SphericalDerivativeConverter(spherical);
+
+        double[] result = conv.toCartesianGradient(sGradient);
+
+        Assert.assertArrayEquals(cGradient, result, EPS);
+    }
+
+    @Test
+    public void testToCartesianHessian() {
+        // NOTE: The following set of test data is taken from the original test for this code in commons-math.
+        // The test in that project generated and checked the inputs on the fly using the commons-math differentiation
+        // classes. However, since we don't have the benefit of those here, we're using some selected data points
+        // from that test.
+        //
+        // The NaN values in the input spherical Hessians are only present to ensure that the upper-right
+        // part of the matrix is not used in the calculation.
+
+        // act/assert
+        checkToCartesianHessian(
+                SphericalCoordinates.of(0.2, 0.0, 0.1),
+                new double[] { 3.147028015595093E-4, -1.5708927954007288E-7, 1.5209020574753025E-4 },
+                new double[][] {
+                    { 0.004720542023392639, Double.NaN, Double.NaN },
+                    { -3.927231988501822E-6, -1.5732003526076452E-5, Double.NaN },
+                    { 0.0030418041149506037, -3.0840214797113795E-6, -1.56400962465978E-4 }
+                },
+                new double[][] {
+                    { 0.0, -3.940348984959686E-4, 0.011880399467047453 },
+                    { -3.940348984959686E-4, 7.867570038987733E-6, -1.1860608699245036E-4 },
+                    { 0.011880399467047453, -1.1860608699245036E-4, 0.002384031969540735 }
+                });
+
+        checkToCartesianHessian(
+                SphericalCoordinates.of(0.2, 0.2, 1.7),
+                new double[] { -6.492205616890373E-6, 9.721055406032577E-8, -7.490005649457144E-6 },
+                new double[][] {
+                    { -9.660140526063848E-5, Double.NaN, Double.NaN },
+                    { 2.087263937942704E-6, 3.0135301759512823E-7, Double.NaN },
+                    { -1.4908056742242714E-4, 2.228225255291761E-6, -1.1271700251178201E-4 }
+                },
+                new double[][] {
+                    { 0.0, 8.228328248729827E-7, 1.9536195257978514E-4 },
+                    { 8.228328248729827E-7, -1.568516517220037E-7, -1.862033454396115E-5 },
+                    { 1.9536195257978514E-4, -1.862033454396115E-5, -0.0029473017314775615 }
+                });
+
+        checkToCartesianHessian(
+                SphericalCoordinates.of(0.2, 1.6, 0.1),
+                new double[] { -9.075903886546686E-6, -1.5573157416535897E-5, -4.352284221940931E-6 },
+                new double[][] {
+                    { -1.3557892633841054E-4, Double.NaN, Double.NaN },
+                    { -3.106944464923055E-4, 4.4143436330613375E-7, Double.NaN },
+                    { -8.660889278565699E-5, -1.489922640116937E-4, 5.374400993902801E-6 }
+                },
+                new double[][] {
+                    { 0.0, -3.862868527078941E-4, 0.011763015339492582 },
+                    { -3.862868527078941E-4, -2.229868350965674E-7, 3.395142163599996E-6 },
+                    { 0.011763015339492582, 3.395142163599996E-6, -6.892478835391066E-5 }
+                });
+
+        checkToCartesianHessian(
+                SphericalCoordinates.of(0.2, 2.4, 2.5),
+                new double[] { 6.911538590806891E-4, 3.344602742543664E-5, 3.330643810411849E-5 },
+                new double[][] {
+                    { 0.010200457858547542, Double.NaN, Double.NaN },
+                    { 6.695363800209198E-4, -3.070347513695088E-5, Double.NaN },
+                    { 6.68380906286568E-4, 3.001744637007274E-5, -2.273032055462482E-4 }
+                },
+                new double[][] {
+                    { 0.0, 1.9000713243497378E-4, 0.007402721147059207 },
+                    { 1.9000713243497378E-4, 1.6118798431431763E-5, 3.139960286869248E-4 },
+                    { 0.007402721147059207, 3.139960286869248E-4, 0.008155571186075681 }
+                });
+
+        checkToCartesianHessian(
+                SphericalCoordinates.of(9.2, 5.6, 2.5),
+                new double[] { 41.42645719593436, 859.1407583470807, 939.7112322238082 },
+                new double[][] {
+                    { 11.642163255436742, Double.NaN, Double.NaN },
+                    { 54.8154280776715, 5286.1651942531325, Double.NaN },
+                    { 60.370567966140726, 4700.570567363823, 4929.996883244262 }
+                },
+                new double[][] {
+                    { 0.0, 36.772022140868714, -22.087375306566134 },
+                    { 36.772022140868714, 212.8111723550033, -63.91326828897971 },
+                    { -22.087375306566134, -63.91326828897971, 25.593304575600133 }
+                });
+    }
+
+    private void checkToCartesianHessian(SphericalCoordinates spherical, double[] sGradient,
+            double[][] sHessian, double[][] cHessian) {
+        SphericalDerivativeConverter conv = new SphericalDerivativeConverter(spherical);
+
+        double[][] result = conv.toCartesianHessian(sHessian, sGradient);
+
+        Assert.assertEquals(cHessian.length, result.length);
+        for (int i=0; i<cHessian.length; ++i) {
+            Assert.assertArrayEquals("Hessians differ at row " + i, cHessian[i], result[i], EPS);
+        }
+    }
+
+    private void checkSpherical(SphericalCoordinates c, double radius, double azimuth, double polar) {
+        Assert.assertEquals(radius, c.getRadius(), EPS);
+        Assert.assertEquals(azimuth, c.getAzimuth(), EPS);
+        Assert.assertEquals(polar, c.getPolar(), EPS);
+    }
+
+    private void checkVector(Vector3D v, double x, double y, double z) {
+        Assert.assertEquals(x, v.getX(), EPS);
+        Assert.assertEquals(y, v.getY(), EPS);
+        Assert.assertEquals(z, v.getZ(), EPS);
+    }
+}