SecularAndHarmonic.java

  1. /* Copyright 2002-2018 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.utils;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.List;

  21. import org.hipparchus.analysis.ParametricUnivariateFunction;
  22. import org.hipparchus.fitting.AbstractCurveFitter;
  23. import org.hipparchus.fitting.PolynomialCurveFitter;
  24. import org.hipparchus.fitting.WeightedObservedPoint;
  25. import org.hipparchus.linear.DiagonalMatrix;
  26. import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresBuilder;
  27. import org.hipparchus.optim.nonlinear.vector.leastsquares.LeastSquaresProblem;
  28. import org.hipparchus.util.FastMath;
  29. import org.orekit.time.AbsoluteDate;

  30. /** Class for fitting evolution of osculating orbital parameters.
  31.  * <p>
  32.  * This class allows conversion from osculating parameters to mean parameters.
  33.  * </p>
  34.  *
  35.  * @author Luc Maisonobe
  36.  */
  37. public class SecularAndHarmonic {

  38.     /** Degree of polynomial secular part. */
  39.     private final int secularDegree;

  40.     /** Pulsations of harmonic part. */
  41.     private final double[] pulsations;

  42.     /** Reference date for the model. */
  43.     private AbsoluteDate reference;

  44.     /** Fitted parameters. */
  45.     private double[] fitted;

  46.     /** Observed points. */
  47.     private List<WeightedObservedPoint> observedPoints;

  48.     /** Simple constructor.
  49.      * @param secularDegree degree of polynomial secular part
  50.      * @param pulsations pulsations of harmonic part
  51.      */
  52.     public SecularAndHarmonic(final int secularDegree, final double... pulsations) {
  53.         this.secularDegree  = secularDegree;
  54.         this.pulsations     = pulsations.clone();
  55.         this.observedPoints = new ArrayList<WeightedObservedPoint>();
  56.     }

  57.     /** Reset fitting.
  58.      * @param date reference date
  59.      * @param initialGuess initial guess for the parameters
  60.      * @see #getReferenceDate()
  61.      */
  62.     public void resetFitting(final AbsoluteDate date, final double... initialGuess) {
  63.         reference = date;
  64.         fitted    = initialGuess.clone();
  65.         observedPoints.clear();
  66.     }

  67.     /** Add a fitting point.
  68.      * @param date date of the point
  69.      * @param osculatingValue osculating value
  70.      */
  71.     public void addPoint(final AbsoluteDate date, final double osculatingValue) {
  72.         observedPoints.add(new WeightedObservedPoint(1.0, date.durationFrom(reference), osculatingValue));
  73.     }

  74.     /** Get the reference date.
  75.      * @return reference date
  76.      * @see #resetFitting(AbsoluteDate, double...)
  77.      */
  78.     public AbsoluteDate getReferenceDate() {
  79.         return reference;
  80.     }

  81.     /** Get an upper bound of the fitted harmonic amplitude.
  82.      * @return upper bound of the fitted harmonic amplitude
  83.      */
  84.     public double getHarmonicAmplitude() {
  85.         double amplitude = 0;
  86.         for (int i = 0; i < pulsations.length; ++i) {
  87.             amplitude += FastMath.hypot(fitted[secularDegree + 2 * i + 1],
  88.                                         fitted[secularDegree + 2 * i + 2]);
  89.         }
  90.         return amplitude;
  91.     }

  92.     /** Fit parameters.
  93.      * @see #getFittedParameters()
  94.      */
  95.     public void fit() {

  96.         final AbstractCurveFitter fitter = new AbstractCurveFitter() {
  97.             /** {@inheritDoc} */
  98.             @Override
  99.             protected LeastSquaresProblem getProblem(final Collection<WeightedObservedPoint> observations) {
  100.                 // Prepare least-squares problem.
  101.                 final int len = observations.size();
  102.                 final double[] target  = new double[len];
  103.                 final double[] weights = new double[len];

  104.                 int i = 0;
  105.                 for (final WeightedObservedPoint obs : observations) {
  106.                     target[i]  = obs.getY();
  107.                     weights[i] = obs.getWeight();
  108.                     ++i;
  109.                 }

  110.                 final AbstractCurveFitter.TheoreticalValuesFunction model =
  111.                         new AbstractCurveFitter.TheoreticalValuesFunction(new LocalParametricFunction(), observations);

  112.                 // build a new least squares problem set up to fit a secular and harmonic curve to the observed points
  113.                 return new LeastSquaresBuilder().
  114.                         maxEvaluations(Integer.MAX_VALUE).
  115.                         maxIterations(Integer.MAX_VALUE).
  116.                         start(fitted).
  117.                         target(target).
  118.                         weight(new DiagonalMatrix(weights)).
  119.                         model(model.getModelFunction(), model.getModelFunctionJacobian()).
  120.                         build();

  121.             }
  122.         };

  123.         fitted = fitter.fit(observedPoints);

  124.     }

  125.     /** Local parametric function used for fitting. */
  126.     private class LocalParametricFunction implements ParametricUnivariateFunction {

  127.         /** {@inheritDoc} */
  128.         public double value(final double x, final double... parameters) {
  129.             return truncatedValue(secularDegree, pulsations.length, x, parameters);
  130.         }

  131.         /** {@inheritDoc} */
  132.         public double[] gradient(final double x, final double... parameters) {
  133.             final double[] gradient = new double[secularDegree + 1 + 2 * pulsations.length];

  134.             // secular part
  135.             double xN = 1.0;
  136.             for (int i = 0; i <= secularDegree; ++i) {
  137.                 gradient[i] = xN;
  138.                 xN *= x;
  139.             }

  140.             // harmonic part
  141.             for (int i = 0; i < pulsations.length; ++i) {
  142.                 gradient[secularDegree + 2 * i + 1] = FastMath.cos(pulsations[i] * x);
  143.                 gradient[secularDegree + 2 * i + 2] = FastMath.sin(pulsations[i] * x);
  144.             }

  145.             return gradient;
  146.         }

  147.     }

  148.     /** Get a copy of the last fitted parameters.
  149.      * @return copy of the last fitted parameters.
  150.      * @see #fit()
  151.      */
  152.     public double[] getFittedParameters() {
  153.         return fitted.clone();
  154.     }

  155.     /** Get fitted osculating value.
  156.      * @param date current date
  157.      * @return osculating value at current date
  158.      */
  159.     public double osculatingValue(final AbsoluteDate date) {
  160.         return truncatedValue(secularDegree, pulsations.length,
  161.                               date.durationFrom(reference), fitted);
  162.     }

  163.     /** Get fitted osculating derivative.
  164.      * @param date current date
  165.      * @return osculating derivative at current date
  166.      */
  167.     public double osculatingDerivative(final AbsoluteDate date) {
  168.         return truncatedDerivative(secularDegree, pulsations.length,
  169.                                    date.durationFrom(reference), fitted);
  170.     }

  171.     /** Get fitted osculating second derivative.
  172.      * @param date current date
  173.      * @return osculating second derivative at current date
  174.      */
  175.     public double osculatingSecondDerivative(final AbsoluteDate date) {
  176.         return truncatedSecondDerivative(secularDegree, pulsations.length,
  177.                                          date.durationFrom(reference), fitted);
  178.     }

  179.     /** Get mean value, truncated to first components.
  180.      * @param date current date
  181.      * @param degree degree of polynomial secular part to consider
  182.      * @param harmonics number of harmonics terms to consider
  183.      * @return mean value at current date
  184.      */
  185.     public double meanValue(final AbsoluteDate date, final int degree, final int harmonics) {
  186.         return truncatedValue(degree, harmonics, date.durationFrom(reference), fitted);
  187.     }

  188.     /** Get mean derivative, truncated to first components.
  189.      * @param date current date
  190.      * @param degree degree of polynomial secular part to consider
  191.      * @param harmonics number of harmonics terms to consider
  192.      * @return mean derivative at current date
  193.      */
  194.     public double meanDerivative(final AbsoluteDate date, final int degree, final int harmonics) {
  195.         return truncatedDerivative(degree, harmonics, date.durationFrom(reference), fitted);
  196.     }

  197.     /** Approximate an already fitted model to polynomial only terms.
  198.      * <p>
  199.      * This method is mainly used in order to combine the large amplitude long
  200.      * periods with the secular part as a new approximate polynomial model over
  201.      * some time range. This should be used rather than simply extracting the
  202.      * polynomial coefficients from {@link #getFittedParameters()} when some
  203.      * periodic terms amplitudes are large (for example Sun resonance effects
  204.      * on local solar time in sun synchronous orbits). In theses cases, the pure
  205.      * polynomial secular part in the coefficients may be far from the mean model.
  206.      * </p>
  207.      * @param combinedDegree desired degree for the combined polynomial
  208.      * @param combinedReference desired reference date for the combined polynomial
  209.      * @param meanDegree degree of polynomial secular part to consider
  210.      * @param meanHarmonics number of harmonics terms to consider
  211.      * @param start start date of the approximation time range
  212.      * @param end end date of the approximation time range
  213.      * @param step sampling step
  214.      * @return coefficients of the approximate polynomial (in increasing degree order),
  215.      * using the user provided reference date
  216.      */
  217.     public double[] approximateAsPolynomialOnly(final int combinedDegree, final AbsoluteDate combinedReference,
  218.                                                 final int meanDegree, final int meanHarmonics,
  219.                                                 final AbsoluteDate start, final AbsoluteDate end,
  220.                                                 final double step) {
  221.         final List<WeightedObservedPoint> points = new ArrayList<WeightedObservedPoint>();
  222.         for (AbsoluteDate date = start; date.compareTo(end) < 0; date = date.shiftedBy(step)) {
  223.             points.add(new WeightedObservedPoint(1.0,
  224.                                                  date.durationFrom(combinedReference),
  225.                                                  meanValue(date, meanDegree, meanHarmonics)));
  226.         }
  227.         return PolynomialCurveFitter.create(combinedDegree).fit(points);
  228.     }

  229.     /** Get mean second derivative, truncated to first components.
  230.      * @param date current date
  231.      * @param degree degree of polynomial secular part
  232.      * @param harmonics number of harmonics terms to consider
  233.      * @return mean second derivative at current date
  234.      */
  235.     public double meanSecondDerivative(final AbsoluteDate date, final int degree, final int harmonics) {
  236.         return truncatedSecondDerivative(degree, harmonics, date.durationFrom(reference), fitted);
  237.     }

  238.     /** Get value truncated to first components.
  239.      * @param degree degree of polynomial secular part
  240.      * @param harmonics number of harmonics terms to consider
  241.      * @param time time parameter
  242.      * @param parameters models parameters (must include all parameters,
  243.      * including the ones ignored due to model truncation)
  244.      * @return truncated value
  245.      */
  246.     private double truncatedValue(final int degree, final int harmonics,
  247.                                   final double time, final double... parameters) {

  248.         double value = 0;

  249.         // secular part
  250.         double tN = 1.0;
  251.         for (int i = 0; i <= degree; ++i) {
  252.             value += parameters[i] * tN;
  253.             tN    *= time;
  254.         }

  255.         // harmonic part
  256.         for (int i = 0; i < harmonics; ++i) {
  257.             value += parameters[secularDegree + 2 * i + 1] * FastMath.cos(pulsations[i] * time) +
  258.                      parameters[secularDegree + 2 * i + 2] * FastMath.sin(pulsations[i] * time);
  259.         }

  260.         return value;

  261.     }

  262.     /** Get derivative truncated to first components.
  263.      * @param degree degree of polynomial secular part
  264.      * @param harmonics number of harmonics terms to consider
  265.      * @param time time parameter
  266.      * @param parameters models parameters (must include all parameters,
  267.      * including the ones ignored due to model truncation)
  268.      * @return truncated derivative
  269.      */
  270.     private double truncatedDerivative(final int degree, final int harmonics,
  271.                                        final double time, final double... parameters) {

  272.         double derivative = 0;

  273.         // secular part
  274.         double tN = 1.0;
  275.         for (int i = 1; i <= degree; ++i) {
  276.             derivative += i * parameters[i] * tN;
  277.             tN         *= time;
  278.         }

  279.         // harmonic part
  280.         for (int i = 0; i < harmonics; ++i) {
  281.             derivative += pulsations[i] * (-parameters[secularDegree + 2 * i + 1] * FastMath.sin(pulsations[i] * time) +
  282.                                             parameters[secularDegree + 2 * i + 2] * FastMath.cos(pulsations[i] * time));
  283.         }

  284.         return derivative;

  285.     }

  286.     /** Get second derivative truncated to first components.
  287.      * @param degree degree of polynomial secular part
  288.      * @param harmonics number of harmonics terms to consider
  289.      * @param time time parameter
  290.      * @param parameters models parameters (must include all parameters,
  291.      * including the ones ignored due to model truncation)
  292.      * @return truncated second derivative
  293.      */
  294.     private double truncatedSecondDerivative(final int degree, final int harmonics,
  295.                                              final double time, final double... parameters) {

  296.         double d2 = 0;

  297.         // secular part
  298.         double tN = 1.0;
  299.         for (int i = 2; i <= degree; ++i) {
  300.             d2 += (i - 1) * i * parameters[i] * tN;
  301.             tN *= time;
  302.         }

  303.         // harmonic part
  304.         for (int i = 0; i < harmonics; ++i) {
  305.             d2 += -pulsations[i] * pulsations[i] *
  306.                   (parameters[secularDegree + 2 * i + 1] * FastMath.cos(pulsations[i] * time) +
  307.                    parameters[secularDegree + 2 * i + 2] * FastMath.sin(pulsations[i] * time));
  308.         }

  309.         return d2;

  310.     }

  311. }