TidalCorrection.java

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

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.math3.util.FastMath;
import org.orekit.errors.OrekitException;
import org.orekit.errors.TimeStampedCacheException;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeStamped;
import org.orekit.utils.Constants;
import org.orekit.utils.OrekitConfiguration;
import org.orekit.utils.GenericTimeStampedCache;
import org.orekit.utils.TimeStampedCache;
import org.orekit.utils.TimeStampedGenerator;


/** Compute tidal correction to the pole motion.
 * <p>This class computes the diurnal and semidiurnal variations in the
 * Earth orientation. It is a java translation of the fortran subroutine
 * found at ftp://tai.bipm.org/iers/conv2003/chapter8/ortho_eop.f.</p>
 * @author Pascal Parraud
 * @author Evan Ward
 * @deprecated as of 6.1 replaced by {@link org.orekit.utils.IERSConventions#getEOPTidalCorrection()}
 */
@Deprecated
public class TidalCorrection {

    /** pi;/2. */
    private static final double HALF_PI = FastMath.PI / 2.0;

    /** Angular units conversion factor. */
    private static final double MICRO_ARC_SECONDS_TO_RADIANS = Constants.ARC_SECONDS_TO_RADIANS * 1.0e-6;

    /** Time units conversion factor. */
    private static final double MICRO_SECONDS_TO_SECONDS = 1.0e-6;

    /** Number of interpolation points to use. */
    private static final int INTERPOLATION_POINTS = 8;

    /** Step size in days of the interpolation data points. */
    private static final double STEP_SIZE = 3.0 / 32.0;

    /** Difference in days between the modified julian day epoch and the year 1960. */
    private static final double MJD_TO_1960 = 37076.5;

    /** HS parameter. */
    private static final double[] HS = {
        -001.94, -001.25, -006.64, -001.51, -008.02,
        -009.47, -050.20, -001.80, -009.54, +001.52,
        -049.45, -262.21, +001.70, +003.43, +001.94,
        +001.37, +007.41, +020.62, +004.14, +003.94,
        -007.14, +001.37, -122.03, +001.02, +002.89,
        -007.30, +368.78, +050.01, -001.08, +002.93,
        +005.25, +003.95, +020.62, +004.09, +003.42,
        +001.69, +011.29, +007.23, +001.51, +002.16,
        +001.38, +001.80, +004.67, +016.01, +019.32,
        +001.30, -001.02, -004.51, +120.99, +001.13,
        +022.98, +001.06, -001.90, -002.18, -023.58,
        +631.92, +001.92, -004.66, -017.86, +004.47,
        +001.97, +017.20, +294.00, -002.46, -001.02,
        +079.96, +023.83, +002.59, +004.47, +001.95,
        +001.17
    };

    /** PHASE parameter. */
    private static final double[] PHASE = {
        +09.0899831 - HALF_PI, +08.8234208 - HALF_PI, +12.1189598 - HALF_PI,
        +01.4425700 - HALF_PI, +04.7381090 - HALF_PI, +04.4715466 - HALF_PI,
        +07.7670857 - HALF_PI, -02.9093042 - HALF_PI, +00.3862349 - HALF_PI,
        -03.1758666 - HALF_PI, +00.1196725 - HALF_PI, +03.4152116 - HALF_PI,
        +12.8946194 - HALF_PI, +05.5137686 - HALF_PI, +06.4441883 - HALF_PI,
        -04.2322016 - HALF_PI, -00.9366625 - HALF_PI, +08.5427453 - HALF_PI,
        +11.8382843 - HALF_PI, +01.1618945 - HALF_PI, +05.9693878 - HALF_PI,
        -01.2032249 - HALF_PI, +02.0923141 - HALF_PI, -01.7847596 - HALF_PI,
        +08.0679449 - HALF_PI, +00.8953321 - HALF_PI, +04.1908712 - HALF_PI,
        +07.4864102 - HALF_PI, +10.7819493 - HALF_PI, +00.3137975 - HALF_PI,
        +06.2894282 - HALF_PI, +07.2198478 - HALF_PI, -00.1610030 - HALF_PI,
        +03.1345361 - HALF_PI, +02.8679737 - HALF_PI, -04.5128771 - HALF_PI,
        +04.9665307 - HALF_PI, +08.2620698 - HALF_PI, +11.5576089 - HALF_PI,
        +00.6146566 - HALF_PI, +03.9101957 - HALF_PI,
        +20.6617051, +13.2808543, +16.3098310, +08.9289802, +05.0519065,
        +15.8350306, +08.6624178, +11.9579569, +08.0808832, +04.5771061,
        +00.7000324, +14.9869335, +11.4831564, +04.3105437, +07.6060827,
        +03.7290090, +10.6350594, +03.2542086, +12.7336164, +16.0291555,
        +10.1602590, +06.2831853, +02.4061116, +05.0862033, +08.3817423,
        +11.6772814, +14.9728205, +04.0298682, +07.3254073, +09.1574019
    };

    /** FREQUENCY parameter. */
    private static final double[] FREQUENCY = {
        05.18688050, 05.38346657, 05.38439079, 05.41398343, 05.41490765,
        05.61149372, 05.61241794, 05.64201057, 05.64293479, 05.83859664,
        05.83952086, 05.84044508, 05.84433381, 05.87485066, 06.03795537,
        06.06754801, 06.06847223, 06.07236095, 06.07328517, 06.10287781,
        06.24878055, 06.26505830, 06.26598252, 06.28318449, 06.28318613,
        06.29946388, 06.30038810, 06.30131232, 06.30223654, 06.31759007,
        06.33479368, 06.49789839, 06.52841524, 06.52933946, 06.72592553,
        06.75644239, 06.76033111, 06.76125533, 06.76217955, 06.98835826,
        06.98928248, 11.45675174, 11.48726860, 11.68477889, 11.71529575,
        11.73249771, 11.89560406, 11.91188181, 11.91280603, 11.93000800,
        11.94332289, 11.96052486, 12.11031632, 12.12363121, 12.13990896,
        12.14083318, 12.15803515, 12.33834347, 12.36886033, 12.37274905,
        12.37367327, 12.54916865, 12.56637061, 12.58357258, 12.59985198,
        12.60077620, 12.60170041, 12.60262463, 12.82880334, 12.82972756,
        13.06071921
    };

    /** Orthotide weight factors. */
    private static final double[] SP = {
        0.0298,
        0.1408,
        0.0805,
        0.6002,
        0.3025,
        0.1517,
        0.0200,
        0.0905,
        0.0638,
        0.3476,
        0.1645,
        0.0923
    };

    /** Orthoweights for X polar motion. */
    private static final double[] ORTHOWX = {
        -06.77832 * MICRO_ARC_SECONDS_TO_RADIANS,
        -14.86323 * MICRO_ARC_SECONDS_TO_RADIANS,
        +00.47884 * MICRO_ARC_SECONDS_TO_RADIANS,
        -01.45303 * MICRO_ARC_SECONDS_TO_RADIANS,
        +00.16406 * MICRO_ARC_SECONDS_TO_RADIANS,
        +00.42030 * MICRO_ARC_SECONDS_TO_RADIANS,
        +00.09398 * MICRO_ARC_SECONDS_TO_RADIANS,
        +25.73054 * MICRO_ARC_SECONDS_TO_RADIANS,
        -04.77974 * MICRO_ARC_SECONDS_TO_RADIANS,
        +00.28080 * MICRO_ARC_SECONDS_TO_RADIANS,
        +01.94539 * MICRO_ARC_SECONDS_TO_RADIANS,
        -00.73089 * MICRO_ARC_SECONDS_TO_RADIANS
    };

    /** Orthoweights for Y polar motion. */
    private static final double[] ORTHOWY = {
        +14.86283 * MICRO_ARC_SECONDS_TO_RADIANS,
        -06.77846 * MICRO_ARC_SECONDS_TO_RADIANS,
        +01.45234 * MICRO_ARC_SECONDS_TO_RADIANS,
        +00.47888 * MICRO_ARC_SECONDS_TO_RADIANS,
        -00.42056 * MICRO_ARC_SECONDS_TO_RADIANS,
        +00.16469 * MICRO_ARC_SECONDS_TO_RADIANS,
        +15.30276 * MICRO_ARC_SECONDS_TO_RADIANS,
        -04.30615 * MICRO_ARC_SECONDS_TO_RADIANS,
        +00.07564 * MICRO_ARC_SECONDS_TO_RADIANS,
        +02.28321 * MICRO_ARC_SECONDS_TO_RADIANS,
        -00.45717 * MICRO_ARC_SECONDS_TO_RADIANS,
        -01.62010 * MICRO_ARC_SECONDS_TO_RADIANS
    };

    /** Orthoweights for UT1. */
    private static final double[] ORTHOWT = {
        -1.76335 *  MICRO_SECONDS_TO_SECONDS,
        +1.03364 *  MICRO_SECONDS_TO_SECONDS,
        -0.27553 *  MICRO_SECONDS_TO_SECONDS,
        +0.34569 *  MICRO_SECONDS_TO_SECONDS,
        -0.12343 *  MICRO_SECONDS_TO_SECONDS,
        -0.10146 *  MICRO_SECONDS_TO_SECONDS,
        -0.47119 *  MICRO_SECONDS_TO_SECONDS,
        +1.28997 *  MICRO_SECONDS_TO_SECONDS,
        -0.19336 *  MICRO_SECONDS_TO_SECONDS,
        +0.02724 *  MICRO_SECONDS_TO_SECONDS,
        +0.08955 *  MICRO_SECONDS_TO_SECONDS,
        +0.04726 *  MICRO_SECONDS_TO_SECONDS
    };

    /** Cache of computed tidal corrections. */
    private final TimeStampedCache<CorrectionData> cache;

    /** Simple constructor.
     */
    public TidalCorrection() {

        // create cache
        cache = new GenericTimeStampedCache<CorrectionData>(INTERPOLATION_POINTS,
                                                     OrekitConfiguration.getCacheSlotsNumber(),
                                                     Constants.JULIAN_YEAR, 7 * Constants.JULIAN_DAY,
                                                     new Generator(), CorrectionData.class);

    }

    /** Get the dUT1 value.
     * @param date date at which the value is desired
     * @return dUT1 in seconds
     */
    public double getDUT1(final AbsoluteDate date) {

        try {
            final double t       = toDay(date);
            final double tCenter = toDayQuantum(t);

            final int n    = INTERPOLATION_POINTS;
            final int nM12 = (n - 1) / 2;

            final List<CorrectionData> corrections = cache.getNeighbors(date);

            // copy points to a temporary array
            final double[] dtNeville = new double[n];
            for (int i = 0; i < n; i++) {
                dtNeville[i] = corrections.get(i).dt;
            }

            // interpolate corrections using Neville's algorithm
            final double theta = (t - tCenter) / STEP_SIZE;
            for (int j = 1; j < n; ++j) {
                for (int i = n - 1; i >= j; --i) {
                    final double c1 = (theta + nM12 - i + j) / j;
                    final double c2 = (theta + nM12 - i) / j;
                    dtNeville[i] = c1 * dtNeville[i] - c2 * dtNeville[i - 1];
                }
            }

            return dtNeville[n - 1];
        } catch (TimeStampedCacheException tce) {
            // this should never happen as the generator is not bounded
            throw OrekitException.createInternalError(tce);
        }

    }

    /** Get the pole IERS Reference Pole correction.
     * @param date date at which the correction is desired
     * @return pole correction
     */
    public  PoleCorrection getPoleCorrection(final AbsoluteDate date) {

        try {
            final double t       = toDay(date);
            final double tCenter = toDayQuantum(t);

            final int n    = INTERPOLATION_POINTS;
            final int nM12 = (n - 1) / 2;

            final List<CorrectionData> corrections = this.cache.getNeighbors(date);

            // copy points to a temporary array
            final double[] dxNeville = new double[n];
            final double[] dyNeville = new double[n];
            for (int i = 0; i < n; i++) {
                final CorrectionData correction = corrections.get(i);
                dxNeville[i] = correction.dx;
                dyNeville[i] = correction.dy;
            }

            // interpolate corrections using Neville's algorithm
            final double theta = (t - tCenter) / STEP_SIZE;
            for (int j = 1; j < n; ++j) {
                for (int i = n - 1; i >= j; --i) {
                    final double c1 = (theta + nM12 - i + j) / j;
                    final double c2 = (theta + nM12 - i) / j;
                    dxNeville[i] = c1 * dxNeville[i] - c2 * dxNeville[i - 1];
                    dyNeville[i] = c1 * dyNeville[i] - c2 * dyNeville[i - 1];
                }
            }

            return new PoleCorrection(dxNeville[n - 1], dyNeville[n - 1]);
        } catch (TimeStampedCacheException tce) {
            // this should never happen as the generator is not bounded
            throw OrekitException.createInternalError(tce);
        }

    }

    /** Convert an {@link AbsoluteDate} to days past the epoch.
     * @param date the date to convert
     * @return days past the epoch, including the fractional part
     */
    private static double toDay(final AbsoluteDate date) {
        return date.durationFrom(AbsoluteDate.MODIFIED_JULIAN_EPOCH) / Constants.JULIAN_DAY - MJD_TO_1960;
    }

    /** Convert days to an {@link AbsoluteDate}.
     * @param t the time in days
     * @return the date corresponding to {@code t}
     */
    private static AbsoluteDate toDate(final double t) {
        return AbsoluteDate.MODIFIED_JULIAN_EPOCH.shiftedBy(Constants.JULIAN_DAY * (t + MJD_TO_1960));
    }

    /** Convert a day to the closest quantum in terms of {@link #STEP_SIZE}.
     * @param t the day to convert
     * @return the closest quantum before t, still in days
     */
    private static double toDayQuantum(final double t) {
        return STEP_SIZE * FastMath.floor(t / STEP_SIZE);
    }

    /** Compute the partials of the tidal variations to the orthoweights.
     * @param t offset from reference epoch in days
     * @return the pole and UT1 correction
     */
    private static CorrectionData computeCorrections(final double t) {

        // compute the time dependent potential matrix
        final double d60A = t + 2;
        final double d60B = t;
        final double d60C = t - 2;

        double anm00 = 0;
        double anm01 = 0;
        double anm02 = 0;
        double bnm00 = 0;
        double bnm01 = 0;
        double bnm02 = 0;
        for (int j = 0; j < 41; j++) {

            final double hsj = HS[j];
            final double pj  = PHASE[j];
            final double fj  = FREQUENCY[j];

            final double alphaA = pj + fj * d60A;
            anm00 += hsj * FastMath.cos(alphaA);
            bnm00 -= hsj * FastMath.sin(alphaA);

            final double alphaB = pj + fj * d60B;
            anm01 += hsj * FastMath.cos(alphaB);
            bnm01 -= hsj * FastMath.sin(alphaB);

            final double alphaC = pj + fj * d60C;
            anm02 += hsj * FastMath.cos(alphaC);
            bnm02 -= hsj * FastMath.sin(alphaC);

        }

        double anm10 = 0;
        double anm11 = 0;
        double anm12 = 0;
        double bnm10 = 0;
        double bnm11 = 0;
        double bnm12 = 0;
        for (int j = 41; j < HS.length; j++) {

            final double hsj = HS[j];
            final double pj  = PHASE[j];
            final double fj  = FREQUENCY[j];

            final double alphaA = pj + fj * d60A;
            anm10 += hsj * FastMath.cos(alphaA);
            bnm10 -= hsj * FastMath.sin(alphaA);

            final double alphaB = pj + fj * d60B;
            anm11 += hsj * FastMath.cos(alphaB);
            bnm11 -= hsj * FastMath.sin(alphaB);

            final double alphaC = pj + fj * d60C;
            anm12 += hsj * FastMath.cos(alphaC);
            bnm12 -= hsj * FastMath.sin(alphaC);

        }

        // orthogonalize the response terms ...
        final double ap0 = anm02 + anm00;
        final double am0 = anm02 - anm00;
        final double bp0 = bnm02 + bnm00;
        final double bm0 = bnm02 - bnm00;
        final double ap1 = anm12 + anm10;
        final double am1 = anm12 - anm10;
        final double bp1 = bnm12 + bnm10;
        final double bm1 = bnm12 - bnm10;

        // ... and fill partials vector
        final double partials0  = SP[0] * anm01;
        final double partials1  = SP[0] * bnm01;
        final double partials2  = SP[1] * anm01 - SP[2] * ap0;
        final double partials3  = SP[1] * bnm01 - SP[2] * bp0;
        final double partials4  = SP[3] * anm01 - SP[4] * ap0 + SP[5] * bm0;
        final double partials5  = SP[3] * bnm01 - SP[4] * bp0 - SP[5] * am0;
        final double partials6  = SP[6] * anm11;
        final double partials7  = SP[6] * bnm11;
        final double partials8  = SP[7] * anm11 - SP[8] * ap1;
        final double partials9  = SP[7] * bnm11 - SP[8] * bp1;
        final double partials10 = SP[9] * anm11 - SP[10] * ap1 + SP[11] * bm1;
        final double partials11 = SP[9] * bnm11 - SP[10] * bp1 - SP[11] * am1;

        // combine partials to set up corrections
        final double dx =
                    partials0 * ORTHOWX[0] + partials1  * ORTHOWX[1]  + partials2  * ORTHOWX[2] +
                    partials3 * ORTHOWX[3] + partials4  * ORTHOWX[4]  + partials5  * ORTHOWX[5] +
                    partials6 * ORTHOWX[6] + partials7  * ORTHOWX[7]  + partials8  * ORTHOWX[8] +
                    partials9 * ORTHOWX[9] + partials10 * ORTHOWX[10] + partials11 * ORTHOWX[11];
        final double dy =
                    partials0 * ORTHOWY[0] + partials1  * ORTHOWY[1]  + partials2  * ORTHOWY[2] +
                    partials3 * ORTHOWY[3] + partials4  * ORTHOWY[4]  + partials5  * ORTHOWY[5] +
                    partials6 * ORTHOWY[6] + partials7  * ORTHOWY[7]  + partials8  * ORTHOWY[8] +
                    partials9 * ORTHOWY[9] + partials10 * ORTHOWY[10] + partials11 * ORTHOWY[11];
        final double dt =
                    partials0 * ORTHOWT[0] + partials1  * ORTHOWT[1]  + partials2  * ORTHOWT[2] +
                    partials3 * ORTHOWT[3] + partials4  * ORTHOWT[4]  + partials5  * ORTHOWT[5] +
                    partials6 * ORTHOWT[6] + partials7  * ORTHOWT[7]  + partials8  * ORTHOWT[8] +
                    partials9 * ORTHOWT[9] + partials10 * ORTHOWT[10] + partials11 * ORTHOWT[11];

        return new CorrectionData(toDate(t), dx, dy, dt);

    }

    /** A data cache container for tidal correction data.
     */
    private static class CorrectionData implements TimeStamped {

        /** date the correction is valid. */
        private final AbsoluteDate date;

        /** x component of the pole correction. */
        private final double dx;

        /** y component of the pole correction. */
        private final double dy;

        /** time component of the correction. */
        private final double dt;

        /** Create a new correction with the given data.
         * @param date date the correction is valid
         * @param dx x component of the pole correction
         * @param dy y component of the pole correction
         * @param dt time component of the correction
         */
        public CorrectionData(final AbsoluteDate date, final double dx, final double dy, final double dt) {
            this.date = date;
            this.dx   = dx;
            this.dy   = dy;
            this.dt   = dt;
        }

        /** {@inheritDoc} */
        public AbsoluteDate getDate() {
            return date;
        }
    }

    /** Generates {@link CorrectionData} for a {@link GenericTimeStampedCache}.
     */
    private static class Generator implements TimeStampedGenerator<CorrectionData> {

        /**
         * {@inheritDoc}
         * <p>
         * <b>Note:</b> this {@link Generator} generates the minimum points necessary to
         * cover the range (existing, date].
         */
        public List<CorrectionData> generate(final CorrectionData existing, final AbsoluteDate date)
            throws TimeStampedCacheException {

            // date in days
            double tStart;
            double tEnd;

            if (existing == null) {
                // set tStart and tEnd so that n points are generated
                final int nM12 = (INTERPOLATION_POINTS - 1) / 2;
                // days to subtract from tStart
                final double extraBefore = STEP_SIZE * nM12;
                // days to add to tEnd
                final double extraAfter = STEP_SIZE * (INTERPOLATION_POINTS - nM12);
                tStart = toDayQuantum(toDay(date)) - extraBefore;
                tEnd   = toDayQuantum(toDay(date) + extraAfter);
            } else if (existing.getDate().compareTo(date) > 0) {
                // existing is after date
                tStart = toDayQuantum(toDay(date));
                tEnd   = toDayQuantum(toDay(existing.getDate()));
            } else {
                // existing is before or same as date
                tStart = toDayQuantum(toDay(existing.getDate())) + STEP_SIZE;
                tEnd   = toDayQuantum(toDay(date)) + STEP_SIZE;
            }

            // n is number of points to generate. (tEnd - tStart) / STEP_SIZE should
            // already be *very* close to an integer
            final int n = (int) FastMath.round((tEnd - tStart) / STEP_SIZE);

            // list of generated points
            final List<CorrectionData> generated = new ArrayList<CorrectionData>(n);

            // compute new reference points in [tStart, tEnd)
            for (int i = 0; i < n; ++i) {
                generated.add(computeCorrections(tStart + i * STEP_SIZE));
            }

            return generated;
        }
    }
}