SingleParameterFitter.java
/* Copyright 2023 Luc Maisonobe
* Licensed to CS GROUP (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.io.Serializable;
import java.util.List;
import java.util.ListIterator;
import java.util.function.ToDoubleFunction;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.MathUtils;
import org.orekit.time.AbsoluteDate;
import org.orekit.utils.Constants;
import org.orekit.utils.SecularAndHarmonic;
/** Fitter for one Earth Orientation Parameter.
* @see PredictedEOPHistory
* @see EOPFitter
* @see SecularAndHarmonic
* @since 12.0
* @author Luc Maisonobe
*/
public class SingleParameterFitter implements Serializable {
/** Sun pulsation, one year period. */
public static final double SUN_PULSATION = MathUtils.TWO_PI / Constants.JULIAN_YEAR;
/** Moon pulsation (one Moon draconic period). */
public static final double MOON_DRACONIC_PULSATION = MathUtils.TWO_PI / (27.212221 * Constants.JULIAN_DAY);
/** Serializable UID. */
private static final long serialVersionUID = 20230309L;
/** Duration of the fitting window at the end of the raw history (s). */
private final double fittingDuration;
/** Time constant of the exponential decay weight. */
private final double timeConstant;
/** Convergence on fitted parameter. */
private final double convergence;
/** Degree of the polynomial model. */
private final int degree;
/** Pulsations of harmonic part (rad/s). */
private final double[] pulsations;
/** Simple constructor.
* @param fittingDuration duration of the fitting window at the end of the raw history (s)
* @param timeConstant time constant \(\tau\) of the exponential decay weight, point weight is \(e^{\frac{t-t_0}{\tau}}\),
* i.e. points far in the past before \(t_0\) have smaller weights
* @param convergence convergence on fitted parameter
* @param degree degree of the polynomial model
* @param pulsations pulsations of harmonic part (rad/s)
* @see #createDefaultDut1FitterShortTermPrediction()
* @see #createDefaultDut1FitterLongTermPrediction()
* @see #createDefaultPoleFitterShortTermPrediction()
* @see #createDefaultPoleFitterLongTermPrediction()
* @see #createDefaultNutationFitterShortTermPrediction()
* @see #createDefaultNutationFitterLongTermPrediction()
* @see SecularAndHarmonic
*/
public SingleParameterFitter(final double fittingDuration, final double timeConstant, final double convergence,
final int degree, final double... pulsations) {
this.fittingDuration = fittingDuration;
this.timeConstant = timeConstant;
this.convergence = convergence;
this.degree = degree;
this.pulsations = pulsations.clone();
}
/** Perform secular and harmonic fitting.
* @param rawHistory EOP history to fit
* @param extractor extractor for Earth Orientation Parameter
* @return configured fitter
*/
public SecularAndHarmonic fit(final EOPHistory rawHistory, final ToDoubleFunction<EOPEntry> extractor) {
final List<EOPEntry> rawEntries = rawHistory.getEntries();
final EOPEntry last = rawEntries.get(rawEntries.size() - 1);
// create fitter
final SecularAndHarmonic sh = new SecularAndHarmonic(degree, pulsations);
// set up convergence
sh.setConvergenceRMS(convergence);
// set up reference date and initial guess to a constant value
final double[] initialGuess = new double[degree + 1 + 2 * pulsations.length];
initialGuess[0] = extractor.applyAsDouble(last);
sh.resetFitting(last.getDate(), initialGuess);
// sample history
final AbsoluteDate fitStart = last.getDate().shiftedBy(-fittingDuration);
final ListIterator<EOPEntry> backwardIterator = rawEntries.listIterator(rawEntries.size());
while (backwardIterator.hasPrevious()) {
final EOPEntry entry = backwardIterator.previous();
if (entry.getDate().isAfterOrEqualTo(fitStart)) {
// the entry belongs to the fitting interval
sh.addWeightedPoint(entry.getDate(), extractor.applyAsDouble(entry),
FastMath.exp(entry.getDate().durationFrom(fitStart) / timeConstant));
} else {
// we have processed all entries from the fitting interval
break;
}
}
// perform fitting
sh.fit();
return sh;
}
/** Create fitter with default parameters adapted for fitting orientation parameters dUT1 and LOD
* for short term prediction.
* <p>
* The main difference between these settings and {@link #createDefaultDut1FitterLongTermPrediction()
* the settings for long prediction} is the much smaller \(\tau\). This means more
* weight is set to the points at the end of the history, hence forcing the fitted prediction
* model to be closer to these points, hence the prediction error to be smaller just after
* raw history end. On the other hand, this implies that the model will diverge on long term.
* These settings are intended when prediction is used for at most 5 days after raw EOP end.
* </p>
* <ul>
* <li>fitting duration set to one {@link Constants#JULIAN_YEAR year}</li>
* <li>time constant \(\tau\) of the exponential decay set to 6 {@link Constants#JULIAN_DAY days}</li>
* <li>convergence set to 10⁻¹² s</li>
* <li>polynomial part set to degree 3</li>
* <li>one harmonic term at {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #MOON_DRACONIC_PULSATION}}</li>
* </ul>
* @return fitter with default configuration for orientation parameters dUT1 and LOD
* @see #createDefaultDut1FitterShortTermPrediction()
*/
public static SingleParameterFitter createDefaultDut1FitterShortTermPrediction() {
return new SingleParameterFitter(Constants.JULIAN_YEAR, 6 * Constants.JULIAN_DAY, 1.0e-12, 3,
SUN_PULSATION, 2 * SUN_PULSATION, 3 * SUN_PULSATION,
MOON_DRACONIC_PULSATION, 2 * MOON_DRACONIC_PULSATION, 3 * MOON_DRACONIC_PULSATION);
}
/** Create fitter with default parameters adapted for fitting orientation parameters dUT1 and LOD
* for long term prediction.
* <p>
* The main difference between these settings and {@link #createDefaultDut1FitterShortTermPrediction()
* the settings for short prediction} is the much larger \(\tau\). This means weight
* is spread throughout history, hence forcing the fitted prediction model to be remain very stable
* on the long term. On the other hand, this implies that the model will start with already a much
* larger error just after raw history end.
* These settings are intended when prediction is used for 5 days after raw EOP end or more.
* </p>
* <ul>
* <li>fitting duration set to three {@link Constants#JULIAN_YEAR years}</li>
* <li>time constant \(\tau\) of the exponential decay set to 60 {@link Constants#JULIAN_DAY days}</li>
* <li>convergence set to 10⁻¹² s</li>
* <li>polynomial part set to degree 3</li>
* <li>one harmonic term at {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #MOON_DRACONIC_PULSATION}}</li>
* </ul>
* @return fitter with default configuration for orientation parameters dUT1 and LOD
* @see #createDefaultDut1FitterShortTermPrediction()
*/
public static SingleParameterFitter createDefaultDut1FitterLongTermPrediction() {
return new SingleParameterFitter(3 * Constants.JULIAN_YEAR, 60 * Constants.JULIAN_DAY, 1.0e-12, 3,
SUN_PULSATION, 2 * SUN_PULSATION, 3 * SUN_PULSATION,
MOON_DRACONIC_PULSATION, 2 * MOON_DRACONIC_PULSATION, 3 * MOON_DRACONIC_PULSATION);
}
/** Create fitter with default parameters adapted for fitting pole parameters Xp and Yp
* for long term prediction.
* <p>
* The main difference between these settings and {@link #createDefaultPoleFitterLongTermPrediction()
* the settings for long prediction} is the much smaller \(\tau\). This means more
* weight is set to the points at the end of the history, hence forcing the fitted prediction
* model to be closer to these points, hence the prediction error to be smaller just after
* raw history end. On the other hand, this implies that the model will diverge on long term.
* These settings are intended when prediction is used for at most 5 days after raw EOP end.
* </p>
* <ul>
* <li>fitting duration set to one {@link Constants#JULIAN_YEAR year}</li>
* <li>time constant \(\tau\) of the exponential decay set to 12 {@link Constants#JULIAN_DAY days}</li>
* <li>convergence set to 10⁻¹² rad</li>
* <li>polynomial part set to degree 3</li>
* <li>one harmonic term at {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #MOON_DRACONIC_PULSATION}}</li>
* </ul>
* @return fitter with default configuration for pole parameters Xp and Yp
*/
public static SingleParameterFitter createDefaultPoleFitterShortTermPrediction() {
return new SingleParameterFitter(Constants.JULIAN_YEAR, 12 * Constants.JULIAN_DAY, 1.0e-12, 3,
SUN_PULSATION, 2 * SUN_PULSATION, 3 * SUN_PULSATION,
MOON_DRACONIC_PULSATION, 2 * MOON_DRACONIC_PULSATION, 3 * MOON_DRACONIC_PULSATION);
}
/** Create fitter with default parameters adapted for fitting pole parameters Xp and Yp
* for long term prediction.
* <p>
* The main difference between these settings and {@link #createDefaultPoleFitterShortTermPrediction()
* the settings for short prediction} is the much larger \(\tau\). This means weight
* is spread throughout history, hence forcing the fitted prediction model to be remain very stable
* on the long term. On the other hand, this implies that the model will start with already a much
* larger error just after raw history end.
* These settings are intended when prediction is used for 5 days after raw EOP end or more.
* </p>
* <ul>
* <li>fitting duration set to three {@link Constants#JULIAN_YEAR years}</li>
* <li>time constant \(\tau\) of the exponential decay set to 60 {@link Constants#JULIAN_DAY days}</li>
* <li>convergence set to 10⁻¹² rad</li>
* <li>polynomial part set to degree 3</li>
* <li>one harmonic term at {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #MOON_DRACONIC_PULSATION}}</li>
* </ul>
* @return fitter with default configuration for pole parameters Xp and Yp
*/
public static SingleParameterFitter createDefaultPoleFitterLongTermPrediction() {
return new SingleParameterFitter(3 * Constants.JULIAN_YEAR, 60 * Constants.JULIAN_DAY, 1.0e-12, 3,
SUN_PULSATION, 2 * SUN_PULSATION, 3 * SUN_PULSATION,
MOON_DRACONIC_PULSATION, 2 * MOON_DRACONIC_PULSATION, 3 * MOON_DRACONIC_PULSATION);
}
/** Create fitter with default parameters adapted for fitting nutation parameters dx and dy
* for long term prediction.
* <p>
* The main difference between these settings and {@link #createDefaultNutationFitterLongTermPrediction()
* the settings for long prediction} is the much smaller \(\tau\). This means more
* weight is set to the points at the end of the history, hence forcing the fitted prediction
* model to be closer to these points, hence the prediction error to be smaller just after
* raw history end. On the other hand, this implies that the model will diverge on long term.
* These settings are intended when prediction is used for at most 5 days after raw EOP end.
* </p>
* <ul>
* <li>fitting duration set to one {@link Constants#JULIAN_YEAR year}</li>
* <li>time constant \(\tau\) of the exponential decay set to 12 {@link Constants#JULIAN_DAY days}</li>
* <li>convergence set to 10⁻¹² s</li>
* <li>polynomial part set to degree 3</li>
* <li>one harmonic term at {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #MOON_DRACONIC_PULSATION}}</li>
* </ul>
* @return fitter with default configuration for pole nutation parameters dx and dy
*/
public static SingleParameterFitter createDefaultNutationFitterShortTermPrediction() {
return new SingleParameterFitter(Constants.JULIAN_YEAR, 12 * Constants.JULIAN_DAY, 1.0e-12, 3,
SUN_PULSATION, 2 * SUN_PULSATION, 3 * SUN_PULSATION,
MOON_DRACONIC_PULSATION, 2 * MOON_DRACONIC_PULSATION, 3 * MOON_DRACONIC_PULSATION);
}
/** Create fitter with default parameters adapted for fitting nutation parameters dx and dy
* for long term prediction.
* <p>
* The main difference between these settings and {@link #createDefaultNutationFitterShortTermPrediction()
* the settings for short prediction} is the much larger \(\tau\). This means weight
* is spread throughout history, hence forcing the fitted prediction model to be remain very stable
* on the long term. On the other hand, this implies that the model will start with already a much
* larger error just after raw history end.
* These settings are intended when prediction is used for 5 days after raw EOP end or more.
* </p>
* <ul>
* <li>fitting duration set to three {@link Constants#JULIAN_YEAR years}</li>
* <li>time constant \(\tau\) of the exponential decay set to 60 {@link Constants#JULIAN_DAY days}</li>
* <li>convergence set to 10⁻¹² s</li>
* <li>polynomial part set to degree 3</li>
* <li>one harmonic term at {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #SUN_PULSATION}}</li>
* <li>one harmonic term at {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 2 times {@link #MOON_DRACONIC_PULSATION}}</li>
* <li>one harmonic term at 3 times {@link #MOON_DRACONIC_PULSATION}}</li>
* </ul>
* @return fitter with default configuration for pole nutation parameters dx and dy
*/
public static SingleParameterFitter createDefaultNutationFitterLongTermPrediction() {
return new SingleParameterFitter(3 * Constants.JULIAN_YEAR, 60 * Constants.JULIAN_DAY, 1.0e-12, 3,
SUN_PULSATION, 2 * SUN_PULSATION, 3 * SUN_PULSATION,
MOON_DRACONIC_PULSATION, 2 * MOON_DRACONIC_PULSATION, 3 * MOON_DRACONIC_PULSATION);
}
}