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.  *      https://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.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */
  21. package org.hipparchus.geometry.euclidean.threed;


  22. import java.io.Serializable;

  23. import org.hipparchus.util.FastMath;
  24. import org.hipparchus.util.SinCos;

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

  53.     /** Serializable UID. */
  54.     private static final long serialVersionUID = 20130206L;

  55.     /** Cartesian coordinates. */
  56.     private final Vector3D v;

  57.     /** Radius. */
  58.     private final double r;

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

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

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

  65.     /** Hessian of radius. */
  66.     private double[][] rHessian;

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

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

  71.     /** Build a spherical coordinates transformer from Cartesian coordinates.
  72.      * @param v Cartesian coordinates
  73.      */
  74.     public SphericalCoordinates(final Vector3D v) {

  75.         // Cartesian coordinates
  76.         this.v = v;

  77.         // remaining spherical coordinates
  78.         this.r     = v.getNorm();
  79.         this.theta = v.getAlpha();
  80.         this.phi   = FastMath.acos(v.getZ() / r);

  81.     }

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

  88.         final SinCos sinCosTheta = FastMath.sinCos(theta);
  89.         final SinCos sinCosPhi   = FastMath.sinCos(phi);

  90.         // spherical coordinates
  91.         this.r     = r;
  92.         this.theta = theta;
  93.         this.phi   = phi;

  94.         // Cartesian coordinates
  95.         this.v  = new Vector3D(r * sinCosTheta.cos() * sinCosPhi.sin(),
  96.                                r * sinCosTheta.sin() * sinCosPhi.sin(),
  97.                                r * sinCosPhi.cos());

  98.     }

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

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

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

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

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

  137.         // lazy evaluation of Jacobian
  138.         computeJacobian();

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

  146.     }

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

  166.         computeJacobian();
  167.         computeHessians();

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

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

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

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

  198.         // ensure symmetry
  199.         cHessian[0][1] = cHessian[1][0];
  200.         cHessian[0][2] = cHessian[2][0];
  201.         cHessian[1][2] = cHessian[2][1];

  202.         return cHessian;

  203.     }

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

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

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

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

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

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

  228.         }
  229.     }

  230.     /** Lazy evaluation of Hessians.
  231.      */
  232.     private void computeHessians() {

  233.         if (rHessian == null) {

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

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

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

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

  269.             // upper-right part is symmetric
  270.             thetaHessian[0][1] = thetaHessian[1][0];

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

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

  288.         }

  289.     }

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

  297.     /** Internal class used only for serialization. */
  298.     private static class DataTransferObject implements Serializable {

  299.         /** Serializable UID. */
  300.         private static final long serialVersionUID = 20130206L;

  301.         /** Abscissa.
  302.          * @serial
  303.          */
  304.         private final double x;

  305.         /** Ordinate.
  306.          * @serial
  307.          */
  308.         private final double y;

  309.         /** Height.
  310.          * @serial
  311.          */
  312.         private final double z;

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

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

  329.     }

  330. }