SphericalCoordinates.java

  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. package org.apache.commons.math3.geometry.euclidean.threed;


  18. import java.io.Serializable;

  19. import org.apache.commons.math3.util.FastMath;

  20. /** This class provides conversions related to <a
  21.  * href="http://mathworld.wolfram.com/SphericalCoordinates.html">spherical coordinates</a>.
  22.  * <p>
  23.  * The conventions used here are the mathematical ones, i.e. spherical coordinates are
  24.  * related to Cartesian coordinates as follows:
  25.  * </p>
  26.  * <ul>
  27.  *   <li>x = r cos(&theta;) sin(&Phi;)</li>
  28.  *   <li>y = r sin(&theta;) sin(&Phi;)</li>
  29.  *   <li>z = r cos(&Phi;)</li>
  30.  * </ul>
  31.  * <ul>
  32.  *   <li>r       = &radic;(x<sup>2</sup>+y<sup>2</sup>+z<sup>2</sup>)</li>
  33.  *   <li>&theta; = atan2(y, x)</li>
  34.  *   <li>&Phi;   = acos(z/r)</li>
  35.  * </ul>
  36.  * <p>
  37.  * r is the radius, &theta; is the azimuthal angle in the x-y plane and &Phi; is the polar
  38.  * (co-latitude) angle. These conventions are <em>different</em> from the conventions used
  39.  * in physics (and in particular in spherical harmonics) where the meanings of &theta; and
  40.  * &Phi; are reversed.
  41.  * </p>
  42.  * <p>
  43.  * This class provides conversion of coordinates and also of gradient and Hessian
  44.  * between spherical and Cartesian coordinates.
  45.  * </p>
  46.  * @since 3.2
  47.  */
  48. public class SphericalCoordinates implements Serializable {

  49.     /** Serializable UID. */
  50.     private static final long serialVersionUID = 20130206L;

  51.     /** Cartesian coordinates. */
  52.     private final Vector3D v;

  53.     /** Radius. */
  54.     private final double r;

  55.     /** Azimuthal angle in the x-y plane &theta;. */
  56.     private final double theta;

  57.     /** Polar angle (co-latitude) &Phi;. */
  58.     private final double phi;

  59.     /** Jacobian of (r, &theta; &Phi). */
  60.     private double[][] jacobian;

  61.     /** Hessian of radius. */
  62.     private double[][] rHessian;

  63.     /** Hessian of azimuthal angle in the x-y plane &theta;. */
  64.     private double[][] thetaHessian;

  65.     /** Hessian of polar (co-latitude) angle &Phi;. */
  66.     private double[][] phiHessian;

  67.     /** Build a spherical coordinates transformer from Cartesian coordinates.
  68.      * @param v Cartesian coordinates
  69.      */
  70.     public SphericalCoordinates(final Vector3D v) {

  71.         // Cartesian coordinates
  72.         this.v = v;

  73.         // remaining spherical coordinates
  74.         this.r     = v.getNorm();
  75.         this.theta = v.getAlpha();
  76.         this.phi   = FastMath.acos(v.getZ() / r);

  77.     }

  78.     /** Build a spherical coordinates transformer from spherical coordinates.
  79.      * @param r radius
  80.      * @param theta azimuthal angle in x-y plane
  81.      * @param phi polar (co-latitude) angle
  82.      */
  83.     public SphericalCoordinates(final double r, final double theta, final double phi) {

  84.         final double cosTheta = FastMath.cos(theta);
  85.         final double sinTheta = FastMath.sin(theta);
  86.         final double cosPhi   = FastMath.cos(phi);
  87.         final double sinPhi   = FastMath.sin(phi);

  88.         // spherical coordinates
  89.         this.r     = r;
  90.         this.theta = theta;
  91.         this.phi   = phi;

  92.         // Cartesian coordinates
  93.         this.v  = new Vector3D(r * cosTheta * sinPhi,
  94.                                r * sinTheta * sinPhi,
  95.                                r * cosPhi);

  96.     }

  97.     /** Get the Cartesian coordinates.
  98.      * @return Cartesian coordinates
  99.      */
  100.     public Vector3D getCartesian() {
  101.         return v;
  102.     }

  103.     /** Get the radius.
  104.      * @return radius r
  105.      * @see #getTheta()
  106.      * @see #getPhi()
  107.      */
  108.     public double getR() {
  109.         return r;
  110.     }

  111.     /** Get the azimuthal angle in x-y plane.
  112.      * @return azimuthal angle in x-y plane &theta;
  113.      * @see #getR()
  114.      * @see #getPhi()
  115.      */
  116.     public double getTheta() {
  117.         return theta;
  118.     }

  119.     /** Get the polar (co-latitude) angle.
  120.      * @return polar (co-latitude) angle &Phi;
  121.      * @see #getR()
  122.      * @see #getTheta()
  123.      */
  124.     public double getPhi() {
  125.         return phi;
  126.     }

  127.     /** Convert a gradient with respect to spherical coordinates into a gradient
  128.      * with respect to Cartesian coordinates.
  129.      * @param sGradient gradient with respect to spherical coordinates
  130.      * {df/dr, df/d&theta;, df/d&Phi;}
  131.      * @return gradient with respect to Cartesian coordinates
  132.      * {df/dx, df/dy, df/dz}
  133.      */
  134.     public double[] toCartesianGradient(final double[] sGradient) {

  135.         // lazy evaluation of Jacobian
  136.         computeJacobian();

  137.         // compose derivatives as gradient^T . J
  138.         // the expressions have been simplified since we know jacobian[1][2] = dTheta/dZ = 0
  139.         return new double[] {
  140.             sGradient[0] * jacobian[0][0] + sGradient[1] * jacobian[1][0] + sGradient[2] * jacobian[2][0],
  141.             sGradient[0] * jacobian[0][1] + sGradient[1] * jacobian[1][1] + sGradient[2] * jacobian[2][1],
  142.             sGradient[0] * jacobian[0][2]                                 + sGradient[2] * jacobian[2][2]
  143.         };

  144.     }

  145.     /** Convert a Hessian with respect to spherical coordinates into a Hessian
  146.      * with respect to Cartesian coordinates.
  147.      * <p>
  148.      * As Hessian are always symmetric, we use only the lower left part of the provided
  149.      * spherical Hessian, so the upper part may not be initialized. However, we still
  150.      * do fill up the complete array we create, with guaranteed symmetry.
  151.      * </p>
  152.      * @param sHessian Hessian with respect to spherical coordinates
  153.      * {{d<sup>2</sup>f/dr<sup>2</sup>, d<sup>2</sup>f/drd&theta;, d<sup>2</sup>f/drd&Phi;},
  154.      *  {d<sup>2</sup>f/drd&theta;, d<sup>2</sup>f/d&theta;<sup>2</sup>, d<sup>2</sup>f/d&theta;d&Phi;},
  155.      *  {d<sup>2</sup>f/drd&Phi;, d<sup>2</sup>f/d&theta;d&Phi;, d<sup>2</sup>f/d&Phi;<sup>2</sup>}
  156.      * @param sGradient gradient with respect to spherical coordinates
  157.      * {df/dr, df/d&theta;, df/d&Phi;}
  158.      * @return Hessian with respect to Cartesian coordinates
  159.      * {{d<sup>2</sup>f/dx<sup>2</sup>, d<sup>2</sup>f/dxdy, d<sup>2</sup>f/dxdz},
  160.      *  {d<sup>2</sup>f/dxdy, d<sup>2</sup>f/dy<sup>2</sup>, d<sup>2</sup>f/dydz},
  161.      *  {d<sup>2</sup>f/dxdz, d<sup>2</sup>f/dydz, d<sup>2</sup>f/dz<sup>2</sup>}}
  162.      */
  163.     public double[][] toCartesianHessian(final double[][] sHessian, final double[] sGradient) {

  164.         computeJacobian();
  165.         computeHessians();

  166.         // compose derivative as J^T . H_f . J + df/dr H_r + df/dtheta H_theta + df/dphi H_phi
  167.         // the expressions have been simplified since we know jacobian[1][2] = dTheta/dZ = 0
  168.         // and H_theta is only a 2x2 matrix as it does not depend on z
  169.         final double[][] hj = new double[3][3];
  170.         final double[][] cHessian = new double[3][3];

  171.         // compute H_f . J
  172.         // beware we use ONLY the lower-left part of sHessian
  173.         hj[0][0] = sHessian[0][0] * jacobian[0][0] + sHessian[1][0] * jacobian[1][0] + sHessian[2][0] * jacobian[2][0];
  174.         hj[0][1] = sHessian[0][0] * jacobian[0][1] + sHessian[1][0] * jacobian[1][1] + sHessian[2][0] * jacobian[2][1];
  175.         hj[0][2] = sHessian[0][0] * jacobian[0][2]                                   + sHessian[2][0] * jacobian[2][2];
  176.         hj[1][0] = sHessian[1][0] * jacobian[0][0] + sHessian[1][1] * jacobian[1][0] + sHessian[2][1] * jacobian[2][0];
  177.         hj[1][1] = sHessian[1][0] * jacobian[0][1] + sHessian[1][1] * jacobian[1][1] + sHessian[2][1] * jacobian[2][1];
  178.         // don't compute hj[1][2] as it is not used below
  179.         hj[2][0] = sHessian[2][0] * jacobian[0][0] + sHessian[2][1] * jacobian[1][0] + sHessian[2][2] * jacobian[2][0];
  180.         hj[2][1] = sHessian[2][0] * jacobian[0][1] + sHessian[2][1] * jacobian[1][1] + sHessian[2][2] * jacobian[2][1];
  181.         hj[2][2] = sHessian[2][0] * jacobian[0][2]                                   + sHessian[2][2] * jacobian[2][2];

  182.         // compute lower-left part of J^T . H_f . J
  183.         cHessian[0][0] = jacobian[0][0] * hj[0][0] + jacobian[1][0] * hj[1][0] + jacobian[2][0] * hj[2][0];
  184.         cHessian[1][0] = jacobian[0][1] * hj[0][0] + jacobian[1][1] * hj[1][0] + jacobian[2][1] * hj[2][0];
  185.         cHessian[2][0] = jacobian[0][2] * hj[0][0]                             + jacobian[2][2] * hj[2][0];
  186.         cHessian[1][1] = jacobian[0][1] * hj[0][1] + jacobian[1][1] * hj[1][1] + jacobian[2][1] * hj[2][1];
  187.         cHessian[2][1] = jacobian[0][2] * hj[0][1]                             + jacobian[2][2] * hj[2][1];
  188.         cHessian[2][2] = jacobian[0][2] * hj[0][2]                             + jacobian[2][2] * hj[2][2];

  189.         // add gradient contribution
  190.         cHessian[0][0] += sGradient[0] * rHessian[0][0] + sGradient[1] * thetaHessian[0][0] + sGradient[2] * phiHessian[0][0];
  191.         cHessian[1][0] += sGradient[0] * rHessian[1][0] + sGradient[1] * thetaHessian[1][0] + sGradient[2] * phiHessian[1][0];
  192.         cHessian[2][0] += sGradient[0] * rHessian[2][0]                                     + sGradient[2] * phiHessian[2][0];
  193.         cHessian[1][1] += sGradient[0] * rHessian[1][1] + sGradient[1] * thetaHessian[1][1] + sGradient[2] * phiHessian[1][1];
  194.         cHessian[2][1] += sGradient[0] * rHessian[2][1]                                     + sGradient[2] * phiHessian[2][1];
  195.         cHessian[2][2] += sGradient[0] * rHessian[2][2]                                     + sGradient[2] * phiHessian[2][2];

  196.         // ensure symmetry
  197.         cHessian[0][1] = cHessian[1][0];
  198.         cHessian[0][2] = cHessian[2][0];
  199.         cHessian[1][2] = cHessian[2][1];

  200.         return cHessian;

  201.     }

  202.     /** Lazy evaluation of (r, &theta;, &phi;) Jacobian.
  203.      */
  204.     private void computeJacobian() {
  205.         if (jacobian == null) {

  206.             // intermediate variables
  207.             final double x    = v.getX();
  208.             final double y    = v.getY();
  209.             final double z    = v.getZ();
  210.             final double rho2 = x * x + y * y;
  211.             final double rho  = FastMath.sqrt(rho2);
  212.             final double r2   = rho2 + z * z;

  213.             jacobian = new double[3][3];

  214.             // row representing the gradient of r
  215.             jacobian[0][0] = x / r;
  216.             jacobian[0][1] = y / r;
  217.             jacobian[0][2] = z / r;

  218.             // row representing the gradient of theta
  219.             jacobian[1][0] = -y / rho2;
  220.             jacobian[1][1] =  x / rho2;
  221.             // jacobian[1][2] is already set to 0 at allocation time

  222.             // row representing the gradient of phi
  223.             jacobian[2][0] = x * z / (rho * r2);
  224.             jacobian[2][1] = y * z / (rho * r2);
  225.             jacobian[2][2] = -rho / r2;

  226.         }
  227.     }

  228.     /** Lazy evaluation of Hessians.
  229.      */
  230.     private void computeHessians() {

  231.         if (rHessian == null) {

  232.             // intermediate variables
  233.             final double x      = v.getX();
  234.             final double y      = v.getY();
  235.             final double z      = v.getZ();
  236.             final double x2     = x * x;
  237.             final double y2     = y * y;
  238.             final double z2     = z * z;
  239.             final double rho2   = x2 + y2;
  240.             final double rho    = FastMath.sqrt(rho2);
  241.             final double r2     = rho2 + z2;
  242.             final double xOr    = x / r;
  243.             final double yOr    = y / r;
  244.             final double zOr    = z / r;
  245.             final double xOrho2 = x / rho2;
  246.             final double yOrho2 = y / rho2;
  247.             final double xOr3   = xOr / r2;
  248.             final double yOr3   = yOr / r2;
  249.             final double zOr3   = zOr / r2;

  250.             // lower-left part of Hessian of r
  251.             rHessian = new double[3][3];
  252.             rHessian[0][0] = y * yOr3 + z * zOr3;
  253.             rHessian[1][0] = -x * yOr3;
  254.             rHessian[2][0] = -z * xOr3;
  255.             rHessian[1][1] = x * xOr3 + z * zOr3;
  256.             rHessian[2][1] = -y * zOr3;
  257.             rHessian[2][2] = x * xOr3 + y * yOr3;

  258.             // upper-right part is symmetric
  259.             rHessian[0][1] = rHessian[1][0];
  260.             rHessian[0][2] = rHessian[2][0];
  261.             rHessian[1][2] = rHessian[2][1];

  262.             // lower-left part of Hessian of azimuthal angle theta
  263.             thetaHessian = new double[2][2];
  264.             thetaHessian[0][0] = 2 * xOrho2 * yOrho2;
  265.             thetaHessian[1][0] = yOrho2 * yOrho2 - xOrho2 * xOrho2;
  266.             thetaHessian[1][1] = -2 * xOrho2 * yOrho2;

  267.             // upper-right part is symmetric
  268.             thetaHessian[0][1] = thetaHessian[1][0];

  269.             // lower-left part of Hessian of polar (co-latitude) angle phi
  270.             final double rhor2       = rho * r2;
  271.             final double rho2r2      = rho * rhor2;
  272.             final double rhor4       = rhor2 * r2;
  273.             final double rho3r4      = rhor4 * rho2;
  274.             final double r2P2rho2    = 3 * rho2 + z2;
  275.             phiHessian = new double[3][3];
  276.             phiHessian[0][0] = z * (rho2r2 - x2 * r2P2rho2) / rho3r4;
  277.             phiHessian[1][0] = -x * y * z * r2P2rho2 / rho3r4;
  278.             phiHessian[2][0] = x * (rho2 - z2) / rhor4;
  279.             phiHessian[1][1] = z * (rho2r2 - y2 * r2P2rho2) / rho3r4;
  280.             phiHessian[2][1] = y * (rho2 - z2) / rhor4;
  281.             phiHessian[2][2] = 2 * rho * zOr3 / r;

  282.             // upper-right part is symmetric
  283.             phiHessian[0][1] = phiHessian[1][0];
  284.             phiHessian[0][2] = phiHessian[2][0];
  285.             phiHessian[1][2] = phiHessian[2][1];

  286.         }

  287.     }

  288.     /**
  289.      * Replace the instance with a data transfer object for serialization.
  290.      * @return data transfer object that will be serialized
  291.      */
  292.     private Object writeReplace() {
  293.         return new DataTransferObject(v.getX(), v.getY(), v.getZ());
  294.     }

  295.     /** Internal class used only for serialization. */
  296.     private static class DataTransferObject implements Serializable {

  297.         /** Serializable UID. */
  298.         private static final long serialVersionUID = 20130206L;

  299.         /** Abscissa.
  300.          * @serial
  301.          */
  302.         private final double x;

  303.         /** Ordinate.
  304.          * @serial
  305.          */
  306.         private final double y;

  307.         /** Height.
  308.          * @serial
  309.          */
  310.         private final double z;

  311.         /** Simple constructor.
  312.          * @param x abscissa
  313.          * @param y ordinate
  314.          * @param z height
  315.          */
  316.         public DataTransferObject(final double x, final double y, final double z) {
  317.             this.x = x;
  318.             this.y = y;
  319.             this.z = z;
  320.         }

  321.         /** Replace the deserialized data transfer object with a {@link SphericalCoordinates}.
  322.          * @return replacement {@link SphericalCoordinates}
  323.          */
  324.         private Object readResolve() {
  325.             return new SphericalCoordinates(new Vector3D(x, y, z));
  326.         }

  327.     }

  328. }