SolidTidesField.java

  1. /* Copyright 2002-2013 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS 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.orekit.forces.gravity;

  18. import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
  19. import org.apache.commons.math3.util.FastMath;
  20. import org.orekit.bodies.CelestialBody;
  21. import org.orekit.errors.OrekitException;
  22. import org.orekit.forces.gravity.potential.NormalizedSphericalHarmonicsProvider;
  23. import org.orekit.forces.gravity.potential.TideSystem;
  24. import org.orekit.frames.Frame;
  25. import org.orekit.time.AbsoluteDate;
  26. import org.orekit.time.TimeFunction;
  27. import org.orekit.utils.LoveNumbers;

  28. /** Gravity field corresponding to solid tides.
  29.  * <p>
  30.  * This solid tides force model implementation corresponds to the method described
  31.  * in <a href="http://www.iers.org/nn_11216/IERS/EN/Publications/TechnicalNotes/tn36.html">
  32.  * IERS conventions (2010)</a>, chapter 6, section 6.2.
  33.  * </p>
  34.  * <p>
  35.  * The computation of the spherical harmonics part is done using the algorithm
  36.  * designed by S. A. Holmes and W. E. Featherstone from Department of Spatial Sciences,
  37.  * Curtin University of Technology, Perth, Australia and described in their 2002 paper:
  38.  * <a href="http://cct.gfy.ku.dk/publ_others/ccta1870.pdf">A unified approach to
  39.  * the Clenshaw summation and the recursive computation of very high degree and
  40.  * order normalised associated Legendre functions</a> (Journal of Geodesy (2002)
  41.  * 76: 279–299).
  42.  * </p>
  43.  * <p>
  44.  * Note that this class is <em>not</em> thread-safe, and that tides computation
  45.  * are computer intensive if repeated. So this class is really expected to
  46.  * be wrapped within a {@link
  47.  * org.orekit.forces.gravity.potential.CachedNormalizedSphericalHarmonicsProvider}
  48.  * that will both ensure thread safety and improve performances using caching.
  49.  * </p>
  50.  * @see SolidTides
  51.  * @author Luc Maisonobe
  52.  * @since 6.1
  53.  */
  54. class SolidTidesField implements NormalizedSphericalHarmonicsProvider {

  55.     /** Maximum degree for normalized Legendre functions. */
  56.     private static final int MAX_LEGENDRE_DEGREE = 4;

  57.     /** Love numbers. */
  58.     private final LoveNumbers love;

  59.     /** Function computing frequency dependent terms (ΔC₂₀, ΔC₂₁, ΔS₂₁, ΔC₂₂, ΔS₂₂). */
  60.     private final TimeFunction<double []> deltaCSFunction;

  61.     /** Permanent tide to be <em>removed</em> from ΔC₂₀ when zero-tide potentials are used. */
  62.     private final double deltaC20PermanentTide;

  63.     /** Function computing pole tide terms (ΔC₂₁, ΔS₂₁). */
  64.     private final TimeFunction<double []> poleTideFunction;

  65.     /** Rotating body frame. */
  66.     private final Frame centralBodyFrame;

  67.     /** Central body reference radius. */
  68.     private final double ae;

  69.     /** Central body attraction coefficient. */
  70.     private final double mu;

  71.     /** Tide system used in the central attraction model. */
  72.     private final TideSystem centralTideSystem;

  73.     /** Tide generating bodies (typically Sun and Moon). Read only after construction. */
  74.     private final CelestialBody[] bodies;

  75.     /** First recursion coefficients for tesseral terms. Read only after construction. */
  76.     private final double[][] anm;

  77.     /** Second recursion coefficients for tesseral terms. Read only after construction. */
  78.     private final double[][] bnm;

  79.     /** Third recursion coefficients for sectorial terms. Read only after construction. */
  80.     private final double[] dmm;

  81.     /** Simple constructor.
  82.      * @param love Love numbers
  83.      * @param deltaCSFunction function computing frequency dependent terms (ΔC₂₀, ΔC₂₁, ΔS₂₁, ΔC₂₂, ΔS₂₂)
  84.      * @param deltaC20PermanentTide permanent tide to be <em>removed</em> from ΔC₂₀ when zero-tide potentials are used
  85.      * @param poleTideFunction function computing pole tide terms (ΔC₂₁, ΔS₂₁), may be null
  86.      * @param centralBodyFrame rotating body frame
  87.      * @param ae central body reference radius
  88.      * @param mu central body attraction coefficient
  89.      * @param centralTideSystem tide system used in the central attraction model
  90.      * @param bodies tide generating bodies (typically Sun and Moon)
  91.      */
  92.     public SolidTidesField(final LoveNumbers love, final TimeFunction<double []> deltaCSFunction,
  93.                            final double deltaC20PermanentTide, final TimeFunction<double []> poleTideFunction,
  94.                            final Frame centralBodyFrame, final double ae, final double mu,
  95.                            final TideSystem centralTideSystem, final CelestialBody ... bodies) {

  96.         // store mode parameters
  97.         this.centralBodyFrame  = centralBodyFrame;
  98.         this.ae                = ae;
  99.         this.mu                = mu;
  100.         this.centralTideSystem = centralTideSystem;
  101.         this.bodies            = bodies;

  102.         // compute recursion coefficients for Legendre functions
  103.         this.anm               = buildTriangularArray(5, false);
  104.         this.bnm               = buildTriangularArray(5, false);
  105.         this.dmm               = new double[love.getSize()];
  106.         recursionCoefficients();

  107.         // Love numbers
  108.         this.love = love;

  109.         // frequency dependent terms
  110.         this.deltaCSFunction = deltaCSFunction;

  111.         // permanent tide
  112.         this.deltaC20PermanentTide = deltaC20PermanentTide;

  113.         // pole tide
  114.         this.poleTideFunction = poleTideFunction;

  115.     }

  116.     /** {@inheritDoc} */
  117.     @Override
  118.     public int getMaxDegree() {
  119.         return MAX_LEGENDRE_DEGREE;
  120.     }

  121.     /** {@inheritDoc} */
  122.     @Override
  123.     public int getMaxOrder() {
  124.         return MAX_LEGENDRE_DEGREE;
  125.     }

  126.     /** {@inheritDoc} */
  127.     @Override
  128.     public double getMu() {
  129.         return mu;
  130.     }

  131.     /** {@inheritDoc} */
  132.     @Override
  133.     public double getAe() {
  134.         return ae;
  135.     }

  136.     /** {@inheritDoc} */
  137.     @Override
  138.     public AbsoluteDate getReferenceDate() {
  139.         return AbsoluteDate.J2000_EPOCH;
  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     public double getOffset(final AbsoluteDate date) {
  144.         return date.durationFrom(AbsoluteDate.J2000_EPOCH);
  145.     }

  146.     /** {@inheritDoc} */
  147.     @Override
  148.     public TideSystem getTideSystem() {
  149.         // not really used here, but for consistency we can state that either
  150.         // we add the permanent tide or it was already in the central attraction
  151.         return TideSystem.ZERO_TIDE;
  152.     }

  153.     /** {@inheritDoc} */
  154.     @Override
  155.     @Deprecated
  156.     public double getNormalizedCnm(final double dateOffset, final int n, final int m)
  157.         throws OrekitException {
  158.         return onDate(getReferenceDate().shiftedBy(dateOffset)).getNormalizedCnm(n, m);
  159.     }

  160.     /** {@inheritDoc} */
  161.     @Override
  162.     @Deprecated
  163.     public double getNormalizedSnm(final double dateOffset, final int n, final int m)
  164.         throws OrekitException {
  165.         return onDate(getReferenceDate().shiftedBy(dateOffset)).getNormalizedSnm(n, m);
  166.     }

  167.     /** {@inheritDoc} */
  168.     @Override
  169.     public NormalizedSphericalHarmonics onDate(final AbsoluteDate date) throws OrekitException {

  170.         // computed Cnm and Snm coefficients
  171.         final double[][] cnm = buildTriangularArray(5, true);
  172.         final double[][] snm = buildTriangularArray(5, true);

  173.         // work array to hold Legendre coefficients
  174.         final double[][] pnm = buildTriangularArray(5, true);

  175.         // step 1: frequency independent part
  176.         // equations 6.6 (for degrees 2 and 3) and 6.7 (for degree 4) in IERS conventions 2010
  177.         for (final CelestialBody body : bodies) {

  178.             // compute tide generating body state
  179.             final Vector3D position = body.getPVCoordinates(date, centralBodyFrame).getPosition();

  180.             // compute polar coordinates
  181.             final double x    = position.getX();
  182.             final double y    = position.getY();
  183.             final double z    = position.getZ();
  184.             final double x2   = x * x;
  185.             final double y2   = y * y;
  186.             final double z2   = z * z;
  187.             final double r2   = x2 + y2 + z2;
  188.             final double r    = FastMath.sqrt (r2);
  189.             final double rho2 = x2 + y2;
  190.             final double rho  = FastMath.sqrt(rho2);

  191.             // evaluate Pnm
  192.             evaluateLegendre(z / r, rho / r, pnm);

  193.             // update spherical harmonic coefficients
  194.             frequencyIndependentPart(r, body.getGM(), x / rho, y / rho, pnm, cnm, snm);

  195.         }

  196.         // step 2: frequency dependent corrections
  197.         frequencyDependentPart(date, cnm, snm);

  198.         if (centralTideSystem == TideSystem.ZERO_TIDE) {
  199.             // step 3: remove permanent tide which is already considered
  200.             // in the central body gravity field
  201.             removePermanentTide(cnm);
  202.         }

  203.         if (poleTideFunction != null) {
  204.             // add pole tide
  205.             poleTide(date, cnm, snm);
  206.         }

  207.         return new TideHarmonics(date, cnm, snm);

  208.     }

  209.     /** Compute recursion coefficients. */
  210.     private void recursionCoefficients() {

  211.         // pre-compute the recursion coefficients
  212.         // (see equations 11 and 12 from Holmes and Featherstone paper)
  213.         for (int n = 0; n < anm.length; ++n) {
  214.             for (int m = 0; m < n; ++m) {
  215.                 final double f = (n - m) * (n + m );
  216.                 anm[n][m] = FastMath.sqrt((2 * n - 1) * (2 * n + 1) / f);
  217.                 bnm[n][m] = FastMath.sqrt((2 * n + 1) * (n + m - 1) * (n - m - 1) / (f * (2 * n - 3)));
  218.             }
  219.         }

  220.         // sectorial terms corresponding to equation 13 in Holmes and Featherstone paper
  221.         dmm[0] = Double.NaN; // dummy initialization for unused term
  222.         dmm[1] = Double.NaN; // dummy initialization for unused term
  223.         for (int m = 2; m < dmm.length; ++m) {
  224.             dmm[m] = FastMath.sqrt((2 * m + 1) / (2.0 * m));
  225.         }

  226.     }

  227.     /** Evaluate Legendre functions.
  228.      * @param t cos(&theta;), where &theta; is the polar angle
  229.      * @param u sin(&theta;), where &theta; is the polar angle
  230.      * @param pnm the computed coefficients. Modified in place.
  231.      */
  232.     private void evaluateLegendre(final double t, final double u, final double[][] pnm) {

  233.         // as the degree is very low, we use the standard forward column method
  234.         // and store everything (see equations 11 and 13 from Holmes and Featherstone paper)
  235.         pnm[0][0] = 1;
  236.         pnm[1][0] = anm[1][0] * t;
  237.         pnm[1][1] = FastMath.sqrt(3) * u;
  238.         for (int m = 2; m < dmm.length; ++m) {
  239.             pnm[m][m - 1] = anm[m][m - 1] * t * pnm[m - 1][m - 1];
  240.             pnm[m][m]     = dmm[m] * u * pnm[m - 1][m - 1];
  241.         }
  242.         for (int m = 0; m < dmm.length; ++m) {
  243.             for (int n = m + 2; n < pnm.length; ++n) {
  244.                 pnm[n][m] = anm[n][m] * t * pnm[n - 1][m] - bnm[n][m] * pnm[n - 2][m];
  245.             }
  246.         }

  247.     }

  248.     /** Update coefficients applying frequency independent step, for one tide generating body.
  249.      * @param r distance to tide generating body
  250.      * @param gm tide generating body attraction coefficient
  251.      * @param cosLambda cosine of the tide generating body longitude
  252.      * @param sinLambda sine of the tide generating body longitude
  253.      * @param pnm the Legendre coefficients. See {@link #evaluateLegendre(double, double, double[][])}.
  254.      * @param cnm the computed Cnm coefficients. Modified in place.
  255.      * @param snm the computed Snm coefficients. Modified in place.
  256.      */
  257.     private void frequencyIndependentPart(final double r,
  258.                                           final double gm,
  259.                                           final double cosLambda,
  260.                                           final double sinLambda,
  261.                                           final double[][] pnm,
  262.                                           final double[][] cnm,
  263.                                           final double[][] snm) {

  264.         final double rRatio = ae / r;
  265.         double fM           = gm / mu;
  266.         double cosMLambda   = 1;
  267.         double sinMLambda   = 0;
  268.         for (int m = 0; m < love.getSize(); ++m) {

  269.             double fNPlus1 = fM;
  270.             for (int n = m; n < love.getSize(); ++n) {
  271.                 fNPlus1 *= rRatio;
  272.                 final double coeff = (fNPlus1 / (2 * n + 1)) * pnm[n][m];
  273.                 final double cCos  = coeff * cosMLambda;
  274.                 final double cSin  = coeff * sinMLambda;

  275.                 // direct effect of degree n tides on degree n coefficients
  276.                 // equation 6.6 from IERS conventions (2010)
  277.                 final double kR = love.getReal(n, m);
  278.                 final double kI = love.getImaginary(n, m);
  279.                 cnm[n][m] += kR * cCos + kI * cSin;
  280.                 snm[n][m] += kR * cSin - kI * cCos;

  281.                 if (n == 2) {
  282.                     // indirect effect of degree 2 tides on degree 4 coefficients
  283.                     // equation 6.7 from IERS conventions (2010)
  284.                     final double kP = love.getPlus(n, m);
  285.                     cnm[4][m] += kP * cCos;
  286.                     snm[4][m] += kP * cSin;
  287.                 }

  288.             }

  289.             // prepare next iteration on order
  290.             final double tmp = cosMLambda * cosLambda - sinMLambda * sinLambda;
  291.             sinMLambda = sinMLambda * cosLambda + cosMLambda * sinLambda;
  292.             cosMLambda = tmp;
  293.             fM        *= rRatio;

  294.         }

  295.     }

  296.     /** Update coefficients applying frequency dependent step.
  297.      * @param date current date
  298.      * @param cnm the Cnm coefficients. Modified in place.
  299.      * @param snm the Snm coefficients. Modified in place.
  300.      */
  301.     private void frequencyDependentPart(final AbsoluteDate date,
  302.                                         final double[][] cnm,
  303.                                         final double[][] snm) {
  304.         final double[] deltaCS = deltaCSFunction.value(date);
  305.         cnm[2][0] += deltaCS[0]; // ΔC₂₀
  306.         cnm[2][1] += deltaCS[1]; // ΔC₂₁
  307.         snm[2][1] += deltaCS[2]; // ΔS₂₁
  308.         cnm[2][2] += deltaCS[3]; // ΔC₂₂
  309.         snm[2][2] += deltaCS[4]; // ΔS₂₂
  310.     }

  311.     /** Remove the permanent tide already counted in zero-tide central gravity fields.
  312.      * @param cnm the Cnm coefficients. Modified in place.
  313.      */
  314.     private void removePermanentTide(final double[][] cnm) {
  315.         cnm[2][0] -= deltaC20PermanentTide;
  316.     }

  317.     /** Update coefficients applying pole tide.
  318.      * @param date current date
  319.      * @param cnm the Cnm coefficients. Modified in place.
  320.      * @param snm the Snm coefficients. Modified in place.
  321.      */
  322.     private void poleTide(final AbsoluteDate date,
  323.                           final double[][] cnm,
  324.                           final double[][] snm) {
  325.         final double[] deltaCS = poleTideFunction.value(date);
  326.         cnm[2][1] += deltaCS[0]; // ΔC₂₁
  327.         snm[2][1] += deltaCS[1]; // ΔS₂₁
  328.     }

  329.     /** Create a triangular array.
  330.      * @param size array size
  331.      * @param withDiagonal if true, the array contains a[p][p] terms, otherwise each
  332.      * row ends up at a[p][p-1]
  333.      * @return new triangular array
  334.      */
  335.     private double[][] buildTriangularArray(final int size, final boolean withDiagonal) {
  336.         final double[][] array = new double[size][];
  337.         for (int i = 0; i < array.length; ++i) {
  338.             array[i] = new double[withDiagonal ? i + 1 : i];
  339.         }
  340.         return array;
  341.     }

  342.     /** The Tidal geopotential evaluated on a specific date. */
  343.     private static class TideHarmonics implements NormalizedSphericalHarmonics {

  344.         /** evaluation date. */
  345.         private final AbsoluteDate date;

  346.         /** Cached cnm. */
  347.         private final double[][] cnm;

  348.         /** Cached snm. */
  349.         private final double[][] snm;

  350.         /** Construct the tidal harmonics on the given date.
  351.          *
  352.          * @param date of evaluation
  353.          * @param cnm the Cnm coefficients. Not copied.
  354.          * @param snm the Snm coeffiecients. Not copied.
  355.          */
  356.         private TideHarmonics(final AbsoluteDate date,
  357.                               final double[][] cnm,
  358.                               final double[][] snm) {
  359.             this.date = date;
  360.             this.cnm = cnm;
  361.             this.snm = snm;
  362.         }

  363.         /** {@inheritDoc} */
  364.         @Override
  365.         public AbsoluteDate getDate() {
  366.             return date;
  367.         }

  368.         /** {@inheritDoc} */
  369.         @Override
  370.         public double getNormalizedCnm(final int n, final int m)
  371.             throws OrekitException {
  372.             return cnm[n][m];
  373.         }

  374.         /** {@inheritDoc} */
  375.         @Override
  376.         public double getNormalizedSnm(final int n, final int m)
  377.             throws OrekitException {
  378.             return snm[n][m];
  379.         }

  380.     }

  381. }