FieldBrouwerLyddanePropagator.java
/* Copyright 2002-2022 CS GROUP
* 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.propagation.analytical;
import java.util.Collections;
import java.util.List;
import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative2;
import org.hipparchus.util.CombinatoricsUtils;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.FieldSinCos;
import org.hipparchus.util.MathUtils;
import org.hipparchus.util.Precision;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.attitudes.InertialProvider;
import org.orekit.errors.OrekitException;
import org.orekit.errors.OrekitMessages;
import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider;
import org.orekit.forces.gravity.potential.UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics;
import org.orekit.orbits.FieldKeplerianOrbit;
import org.orekit.orbits.FieldOrbit;
import org.orekit.orbits.OrbitType;
import org.orekit.orbits.PositionAngle;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.PropagationType;
import org.orekit.propagation.analytical.tle.FieldTLE;
import org.orekit.time.FieldAbsoluteDate;
import org.orekit.utils.FieldTimeSpanMap;
import org.orekit.utils.ParameterDriver;
/** This class propagates a {@link org.orekit.propagation.FieldSpacecraftState}
* using the analytical Brouwer-Lyddane model (from J2 to J5 zonal harmonics).
* <p>
* At the opposite of the {@link FieldEcksteinHechlerPropagator}, the Brouwer-Lyddane model is
* suited for elliptical orbits, there is no problem having a rather small eccentricity or inclination
* (Lyddane helped to solve this issue with the Brouwer model). Singularity for the critical
* inclination i = 63.4° is avoided using the method developed in Warren Phipps' 1992 thesis.
* <p>
* By default, Brouwer-Lyddane model considers only the perturbations due to zonal harmonics.
* However, for low Earth orbits, the magnitude of the perturbative acceleration due to
* atmospheric drag can be significant. Warren Phipps' 1992 thesis considered the atmospheric
* drag by time derivatives of the <i>mean</i> mean anomaly using the catch-all coefficient
* {@link #M2Driver}.
*
* Usually, M2 is adjusted during an orbit determination process and it represents the
* combination of all unmodeled secular along-track effects (i.e. not just the atmospheric drag).
* The behavior of M2 is close to the {@link FieldTLE#getBStar()} parameter for the TLE.
*
* If the value of M2 is equal to {@link BrouwerLyddanePropagator#M2 0.0}, the along-track secular
* effects are not considered in the dynamical model. Typical values for M2 are not known.
* It depends on the orbit type. However, the value of M2 must be very small (e.g. between 1.0e-14 and 1.0e-15).
* The unit of M2 is rad/s².
*
* The along-track effects, represented by the secular rates of the mean semi-major axis
* and eccentricity, are computed following Eq. 2.38, 2.41, and 2.45 of Warren Phipps' thesis.
*
* @see "Brouwer, Dirk. Solution of the problem of artificial satellite theory without drag.
* YALE UNIV NEW HAVEN CT NEW HAVEN United States, 1959."
*
* @see "Lyddane, R. H. Small eccentricities or inclinations in the Brouwer theory of the
* artificial satellite. The Astronomical Journal 68 (1963): 555."
*
* @see "Phipps Jr, Warren E. Parallelization of the Navy Space Surveillance Center
* (NAVSPASUR) Satellite Model. NAVAL POSTGRADUATE SCHOOL MONTEREY CA, 1992."
*
* @author Melina Vanel
* @author Bryan Cazabonne
* @since 11.1
*/
public class FieldBrouwerLyddanePropagator<T extends CalculusFieldElement<T>> extends FieldAbstractAnalyticalPropagator<T> {
/** Default convergence threshold for mean parameters conversion. */
private static final double EPSILON_DEFAULT = 1.0e-13;
/** Default value for maxIterations. */
private static final int MAX_ITERATIONS_DEFAULT = 200;
/** Parameters scaling factor.
* <p>
* We use a power of 2 to avoid numeric noise introduction
* in the multiplications/divisions sequences.
* </p>
*/
private static final double SCALE = FastMath.scalb(1.0, -20);
/** Beta constant used by T2 function. */
private static final double BETA = FastMath.scalb(100, -11);
/** Initial Brouwer-Lyddane model. */
private FieldBLModel<T> initialModel;
/** All models. */
private transient FieldTimeSpanMap<FieldBLModel<T>, T> models;
/** Reference radius of the central body attraction model (m). */
private double referenceRadius;
/** Central attraction coefficient (m³/s²). */
private T mu;
/** Un-normalized zonal coefficients. */
private double[] ck0;
/** Empirical coefficient used in the drag modeling. */
private final ParameterDriver M2Driver;
/** Build a propagator from orbit and potential provider.
* <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
*
* <p>Using this constructor, an initial osculating orbit is considered.</p>
*
* @param initialOrbit initial orbit
* @param provider for un-normalized zonal coefficients
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
* @see #FieldBrouwerLyddanePropagator(FieldOrbit, UnnormalizedSphericalHarmonicsProvider, PropagationType, double)
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final UnnormalizedSphericalHarmonicsProvider provider,
final double M2) {
this(initialOrbit, InertialProvider.of(initialOrbit.getFrame()),
initialOrbit.getMu().newInstance(DEFAULT_MASS), provider,
provider.onDate(initialOrbit.getDate().toAbsoluteDate()), M2);
}
/**
* Private helper constructor.
* <p>Using this constructor, an initial osculating orbit is considered.</p>
* @param initialOrbit initial orbit
* @param attitude attitude provider
* @param mass spacecraft mass
* @param provider for un-normalized zonal coefficients
* @param harmonics {@code provider.onDate(initialOrbit.getDate())}
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
* @see #FieldBrouwerLyddanePropagator(FieldOrbit, AttitudeProvider, CalculusFieldElement,
* UnnormalizedSphericalHarmonicsProvider, UnnormalizedSphericalHarmonicsProvider.UnnormalizedSphericalHarmonics, PropagationType, double)
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final AttitudeProvider attitude,
final T mass,
final UnnormalizedSphericalHarmonicsProvider provider,
final UnnormalizedSphericalHarmonics harmonics,
final double M2) {
this(initialOrbit, attitude, mass, provider.getAe(), initialOrbit.getMu().newInstance(provider.getMu()),
harmonics.getUnnormalizedCnm(2, 0),
harmonics.getUnnormalizedCnm(3, 0),
harmonics.getUnnormalizedCnm(4, 0),
harmonics.getUnnormalizedCnm(5, 0),
M2);
}
/** Build a propagator from orbit and potential.
* <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
* <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
* are related to both the normalized coefficients
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
* and the J<sub>n</sub> one as follows:</p>
*
* <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
*
* <p> C<sub>n,0</sub> = -J<sub>n</sub>
*
* <p>Using this constructor, an initial osculating orbit is considered.</p>
*
* @param initialOrbit initial orbit
* @param referenceRadius reference radius of the Earth for the potential model (m)
* @param mu central attraction coefficient (m³/s²)
* @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
* @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
* @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
* @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
* @see org.orekit.utils.Constants
* @see #FieldBrouwerLyddanePropagator(FieldOrbit, AttitudeProvider, double, CalculusFieldElement, double, double, double, double, double)
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final double referenceRadius, final T mu,
final double c20, final double c30, final double c40,
final double c50, final double M2) {
this(initialOrbit, InertialProvider.of(initialOrbit.getFrame()),
initialOrbit.getMu().newInstance(DEFAULT_MASS),
referenceRadius, mu, c20, c30, c40, c50, M2);
}
/** Build a propagator from orbit, mass and potential provider.
* <p>Attitude law is set to an unspecified non-null arbitrary value.</p>
*
* <p>Using this constructor, an initial osculating orbit is considered.</p>
*
* @param initialOrbit initial orbit
* @param mass spacecraft mass
* @param provider for un-normalized zonal coefficients
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
* @see #FieldBrouwerLyddanePropagator(FieldOrbit, AttitudeProvider, CalculusFieldElement, UnnormalizedSphericalHarmonicsProvider, double)
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit, final T mass,
final UnnormalizedSphericalHarmonicsProvider provider,
final double M2) {
this(initialOrbit, InertialProvider.of(initialOrbit.getFrame()),
mass, provider, provider.onDate(initialOrbit.getDate().toAbsoluteDate()), M2);
}
/** Build a propagator from orbit, mass and potential.
* <p>Attitude law is set to an unspecified non-null arbitrary value.</p>
* <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
* are related to both the normalized coefficients
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
* and the J<sub>n</sub> one as follows:</p>
*
* <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
*
* <p> C<sub>n,0</sub> = -J<sub>n</sub>
*
* <p>Using this constructor, an initial osculating orbit is considered.</p>
*
* @param initialOrbit initial orbit
* @param mass spacecraft mass
* @param referenceRadius reference radius of the Earth for the potential model (m)
* @param mu central attraction coefficient (m³/s²)
* @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
* @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
* @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
* @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
* @see #FieldBrouwerLyddanePropagator(FieldOrbit, AttitudeProvider, CalculusFieldElement, double, CalculusFieldElement, double, double, double, double, double)
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit, final T mass,
final double referenceRadius, final T mu,
final double c20, final double c30, final double c40,
final double c50, final double M2) {
this(initialOrbit, InertialProvider.of(initialOrbit.getFrame()),
mass, referenceRadius, mu, c20, c30, c40, c50, M2);
}
/** Build a propagator from orbit, attitude provider and potential provider.
* <p>Mass is set to an unspecified non-null arbitrary value.</p>
* <p>Using this constructor, an initial osculating orbit is considered.</p>
* @param initialOrbit initial orbit
* @param attitudeProv attitude provider
* @param provider for un-normalized zonal coefficients
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final AttitudeProvider attitudeProv,
final UnnormalizedSphericalHarmonicsProvider provider,
final double M2) {
this(initialOrbit, attitudeProv, initialOrbit.getMu().newInstance(DEFAULT_MASS), provider,
provider.onDate(initialOrbit.getDate().toAbsoluteDate()), M2);
}
/** Build a propagator from orbit, attitude provider and potential.
* <p>Mass is set to an unspecified non-null arbitrary value.</p>
* <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
* are related to both the normalized coefficients
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
* and the J<sub>n</sub> one as follows:</p>
*
* <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
*
* <p> C<sub>n,0</sub> = -J<sub>n</sub>
*
* <p>Using this constructor, an initial osculating orbit is considered.</p>
*
* @param initialOrbit initial orbit
* @param attitudeProv attitude provider
* @param referenceRadius reference radius of the Earth for the potential model (m)
* @param mu central attraction coefficient (m³/s²)
* @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
* @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
* @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
* @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final AttitudeProvider attitudeProv,
final double referenceRadius, final T mu,
final double c20, final double c30, final double c40,
final double c50, final double M2) {
this(initialOrbit, attitudeProv, initialOrbit.getMu().newInstance(DEFAULT_MASS),
referenceRadius, mu, c20, c30, c40, c50, M2);
}
/** Build a propagator from orbit, attitude provider, mass and potential provider.
* <p>Using this constructor, an initial osculating orbit is considered.</p>
* @param initialOrbit initial orbit
* @param attitudeProv attitude provider
* @param mass spacecraft mass
* @param provider for un-normalized zonal coefficients
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
* @see #FieldBrouwerLyddanePropagator(FieldOrbit, AttitudeProvider, CalculusFieldElement, UnnormalizedSphericalHarmonicsProvider, PropagationType, double)
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final AttitudeProvider attitudeProv,
final T mass,
final UnnormalizedSphericalHarmonicsProvider provider,
final double M2) {
this(initialOrbit, attitudeProv, mass, provider, provider.onDate(initialOrbit.getDate().toAbsoluteDate()), M2);
}
/** Build a propagator from orbit, attitude provider, mass and potential.
* <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
* are related to both the normalized coefficients
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
* and the J<sub>n</sub> one as follows:</p>
*
* <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
*
* <p> C<sub>n,0</sub> = -J<sub>n</sub>
*
* <p>Using this constructor, an initial osculating orbit is considered.</p>
*
* @param initialOrbit initial orbit
* @param attitudeProv attitude provider
* @param mass spacecraft mass
* @param referenceRadius reference radius of the Earth for the potential model (m)
* @param mu central attraction coefficient (m³/s²)
* @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
* @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
* @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
* @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
* @see #FieldBrouwerLyddanePropagator(FieldOrbit, AttitudeProvider, CalculusFieldElement, double, CalculusFieldElement, double, double, double, double, PropagationType, double)
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final AttitudeProvider attitudeProv,
final T mass,
final double referenceRadius, final T mu,
final double c20, final double c30, final double c40,
final double c50, final double M2) {
this(initialOrbit, attitudeProv, mass, referenceRadius, mu, c20, c30, c40, c50, PropagationType.OSCULATING, M2);
}
/** Build a propagator from orbit and potential provider.
* <p>Mass and attitude provider are set to unspecified non-null arbitrary values.</p>
*
* <p>Using this constructor, it is possible to define the initial orbit as
* a mean Brouwer-Lyddane orbit or an osculating one.</p>
*
* @param initialOrbit initial orbit
* @param provider for un-normalized zonal coefficients
* @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final UnnormalizedSphericalHarmonicsProvider provider,
final PropagationType initialType,
final double M2) {
this(initialOrbit, InertialProvider.of(initialOrbit.getFrame()),
initialOrbit.getMu().newInstance(DEFAULT_MASS), provider,
provider.onDate(initialOrbit.getDate().toAbsoluteDate()), initialType, M2);
}
/** Build a propagator from orbit, attitude provider, mass and potential provider.
* <p>Using this constructor, it is possible to define the initial orbit as
* a mean Brouwer-Lyddane orbit or an osculating one.</p>
* @param initialOrbit initial orbit
* @param attitudeProv attitude provider
* @param mass spacecraft mass
* @param provider for un-normalized zonal coefficients
* @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final AttitudeProvider attitudeProv,
final T mass,
final UnnormalizedSphericalHarmonicsProvider provider,
final PropagationType initialType,
final double M2) {
this(initialOrbit, attitudeProv, mass, provider, provider.onDate(initialOrbit.getDate().toAbsoluteDate()), initialType, M2);
}
/**
* Private helper constructor.
* <p>Using this constructor, it is possible to define the initial orbit as
* a mean Brouwer-Lyddane orbit or an osculating one.</p>
* @param initialOrbit initial orbit
* @param attitude attitude provider
* @param mass spacecraft mass
* @param provider for un-normalized zonal coefficients
* @param harmonics {@code provider.onDate(initialOrbit.getDate())}
* @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final AttitudeProvider attitude,
final T mass,
final UnnormalizedSphericalHarmonicsProvider provider,
final UnnormalizedSphericalHarmonics harmonics,
final PropagationType initialType,
final double M2) {
this(initialOrbit, attitude, mass, provider.getAe(), initialOrbit.getMu().newInstance(provider.getMu()),
harmonics.getUnnormalizedCnm(2, 0),
harmonics.getUnnormalizedCnm(3, 0),
harmonics.getUnnormalizedCnm(4, 0),
harmonics.getUnnormalizedCnm(5, 0),
initialType, M2);
}
/** Build a propagator from orbit, attitude provider, mass and potential.
* <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
* are related to both the normalized coefficients
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
* and the J<sub>n</sub> one as follows:</p>
*
* <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
*
* <p> C<sub>n,0</sub> = -J<sub>n</sub>
*
* <p>Using this constructor, it is possible to define the initial orbit as
* a mean Brouwer-Lyddane orbit or an osculating one.</p>
*
* @param initialOrbit initial orbit
* @param attitudeProv attitude provider
* @param mass spacecraft mass
* @param referenceRadius reference radius of the Earth for the potential model (m)
* @param mu central attraction coefficient (m³/s²)
* @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
* @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
* @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
* @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
* @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final AttitudeProvider attitudeProv,
final T mass,
final double referenceRadius, final T mu,
final double c20, final double c30, final double c40,
final double c50,
final PropagationType initialType,
final double M2) {
this(initialOrbit, attitudeProv, mass, referenceRadius, mu,
c20, c30, c40, c50, initialType, M2, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
}
/** Build a propagator from orbit, attitude provider, mass and potential.
* <p>The C<sub>n,0</sub> coefficients are the denormalized zonal coefficients, they
* are related to both the normalized coefficients
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
* and the J<sub>n</sub> one as follows:</p>
*
* <p> C<sub>n,0</sub> = [(2-δ<sub>0,m</sub>)(2n+1)(n-m)!/(n+m)!]<sup>½</sup>
* <span style="text-decoration: overline">C</span><sub>n,0</sub>
*
* <p> C<sub>n,0</sub> = -J<sub>n</sub>
*
* <p>Using this constructor, it is possible to define the initial orbit as
* a mean Brouwer-Lyddane orbit or an osculating one.</p>
*
* @param initialOrbit initial orbit
* @param attitudeProv attitude provider
* @param mass spacecraft mass
* @param referenceRadius reference radius of the Earth for the potential model (m)
* @param mu central attraction coefficient (m³/s²)
* @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
* @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
* @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
* @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
* @param initialType initial orbit type (mean Brouwer-Lyddane orbit or osculating orbit)
* @param M2 value of empirical drag coefficient in rad/s².
* If equal to {@link BrouwerLyddanePropagator#M2} drag is not computed
* @param epsilon convergence threshold for mean parameters conversion
* @param maxIterations maximum iterations for mean parameters conversion
* @since 11.2
*/
public FieldBrouwerLyddanePropagator(final FieldOrbit<T> initialOrbit,
final AttitudeProvider attitudeProv,
final T mass,
final double referenceRadius, final T mu,
final double c20, final double c30, final double c40,
final double c50,
final PropagationType initialType,
final double M2, final double epsilon, final int maxIterations) {
super(mass.getField(), attitudeProv);
// store model coefficients
this.referenceRadius = referenceRadius;
this.mu = mu;
this.ck0 = new double[] {0.0, 0.0, c20, c30, c40, c50};
// initialize M2 driver
this.M2Driver = new ParameterDriver(BrouwerLyddanePropagator.M2_NAME, M2, SCALE,
Double.NEGATIVE_INFINITY,
Double.POSITIVE_INFINITY);
// compute mean parameters if needed
resetInitialState(new FieldSpacecraftState<>(initialOrbit,
attitudeProv.getAttitude(initialOrbit,
initialOrbit.getDate(),
initialOrbit.getFrame()),
mass),
initialType, epsilon, maxIterations);
}
/** Conversion from osculating to mean orbit.
* <p>
* Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
* osculating SpacecraftState in input.
* </p>
* <p>
* Since the osculating orbit is obtained with the computation of
* short-periodic variation, the resulting output will depend on
* both the gravity field parameterized in input and the
* atmospheric drag represented by the {@code m2} parameter.
* </p>
* <p>
* The computation is done through a fixed-point iteration process.
* </p>
* @param <T> type of the filed elements
* @param osculating osculating orbit to convert
* @param provider for un-normalized zonal coefficients
* @param harmonics {@code provider.onDate(osculating.getDate())}
* @param M2Value value of empirical drag coefficient in rad/s².
* If equal to {@code BrouwerLyddanePropagator.M2} drag is not considered
* @return mean orbit in a Brouwer-Lyddane sense
* @since 11.2
*/
public static <T extends CalculusFieldElement<T>> FieldKeplerianOrbit<T> computeMeanOrbit(final FieldOrbit<T> osculating,
final UnnormalizedSphericalHarmonicsProvider provider,
final UnnormalizedSphericalHarmonics harmonics,
final double M2Value) {
return computeMeanOrbit(osculating, provider, harmonics, M2Value, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
}
/** Conversion from osculating to mean orbit.
* <p>
* Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
* osculating SpacecraftState in input.
* </p>
* <p>
* Since the osculating orbit is obtained with the computation of
* short-periodic variation, the resulting output will depend on
* both the gravity field parameterized in input and the
* atmospheric drag represented by the {@code m2} parameter.
* </p>
* <p>
* The computation is done through a fixed-point iteration process.
* </p>
* @param <T> type of the filed elements
* @param osculating osculating orbit to convert
* @param provider for un-normalized zonal coefficients
* @param harmonics {@code provider.onDate(osculating.getDate())}
* @param M2Value value of empirical drag coefficient in rad/s².
* If equal to {@code BrouwerLyddanePropagator.M2} drag is not considered
* @param epsilon convergence threshold for mean parameters conversion
* @param maxIterations maximum iterations for mean parameters conversion
* @return mean orbit in a Brouwer-Lyddane sense
* @since 11.2
*/
public static <T extends CalculusFieldElement<T>> FieldKeplerianOrbit<T> computeMeanOrbit(final FieldOrbit<T> osculating,
final UnnormalizedSphericalHarmonicsProvider provider,
final UnnormalizedSphericalHarmonics harmonics,
final double M2Value,
final double epsilon, final int maxIterations) {
return computeMeanOrbit(osculating,
provider.getAe(), provider.getMu(),
harmonics.getUnnormalizedCnm(2, 0),
harmonics.getUnnormalizedCnm(3, 0),
harmonics.getUnnormalizedCnm(4, 0),
harmonics.getUnnormalizedCnm(5, 0),
M2Value, epsilon, maxIterations);
}
/** Conversion from osculating to mean orbit.
* <p>
* Compute mean orbit <b>in a Brouwer-Lyddane sense</b>, corresponding to the
* osculating SpacecraftState in input.
* </p>
* <p>
* Since the osculating orbit is obtained with the computation of
* short-periodic variation, the resulting output will depend on
* both the gravity field parameterized in input and the
* atmospheric drag represented by the {@code m2} parameter.
* </p>
* <p>
* The computation is done through a fixed-point iteration process.
* </p>
* @param <T> type of the filed elements
* @param osculating osculating orbit to convert
* @param referenceRadius reference radius of the Earth for the potential model (m)
* @param mu central attraction coefficient (m³/s²)
* @param c20 un-normalized zonal coefficient (about -1.08e-3 for Earth)
* @param c30 un-normalized zonal coefficient (about +2.53e-6 for Earth)
* @param c40 un-normalized zonal coefficient (about +1.62e-6 for Earth)
* @param c50 un-normalized zonal coefficient (about +2.28e-7 for Earth)
* @param M2Value value of empirical drag coefficient in rad/s².
* If equal to {@code BrouwerLyddanePropagator.M2} drag is not considered
* @param epsilon convergence threshold for mean parameters conversion
* @param maxIterations maximum iterations for mean parameters conversion
* @return mean orbit in a Brouwer-Lyddane sense
* @since 11.2
*/
public static <T extends CalculusFieldElement<T>> FieldKeplerianOrbit<T> computeMeanOrbit(final FieldOrbit<T> osculating,
final double referenceRadius, final double mu,
final double c20, final double c30, final double c40,
final double c50, final double M2Value,
final double epsilon, final int maxIterations) {
final FieldBrouwerLyddanePropagator<T> propagator =
new FieldBrouwerLyddanePropagator<>(osculating,
InertialProvider.of(osculating.getFrame()),
osculating.getMu().newInstance(DEFAULT_MASS),
referenceRadius, osculating.getMu().newInstance(mu),
c20, c30, c40, c50,
PropagationType.OSCULATING, M2Value,
epsilon, maxIterations);
return propagator.initialModel.mean;
}
/** {@inheritDoc}
* <p>The new initial state to consider
* must be defined with an osculating orbit.</p>
* @see #resetInitialState(FieldSpacecraftState, PropagationType)
*/
@Override
public void resetInitialState(final FieldSpacecraftState<T> state) {
resetInitialState(state, PropagationType.OSCULATING);
}
/** Reset the propagator initial state.
* @param state new initial state to consider
* @param stateType mean Brouwer-Lyddane orbit or osculating orbit
*/
public void resetInitialState(final FieldSpacecraftState<T> state, final PropagationType stateType) {
resetInitialState(state, stateType, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
}
/** Reset the propagator initial state.
* @param state new initial state to consider
* @param stateType mean Brouwer-Lyddane orbit or osculating orbit
* @param epsilon convergence threshold for mean parameters conversion
* @param maxIterations maximum iterations for mean parameters conversion
* @since 11.2
*/
public void resetInitialState(final FieldSpacecraftState<T> state, final PropagationType stateType,
final double epsilon, final int maxIterations) {
super.resetInitialState(state);
final FieldKeplerianOrbit<T> keplerian = (FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(state.getOrbit());
this.initialModel = (stateType == PropagationType.MEAN) ?
new FieldBLModel<>(keplerian, state.getMass(), referenceRadius, mu, ck0) :
computeMeanParameters(keplerian, state.getMass(), epsilon, maxIterations);
this.models = new FieldTimeSpanMap<FieldBLModel<T>, T>(initialModel, state.getA().getField());
}
/** {@inheritDoc} */
@Override
protected void resetIntermediateState(final FieldSpacecraftState<T> state, final boolean forward) {
resetIntermediateState(state, forward, EPSILON_DEFAULT, MAX_ITERATIONS_DEFAULT);
}
/** Reset an intermediate state.
* @param state new intermediate state to consider
* @param forward if true, the intermediate state is valid for
* propagations after itself
* @param epsilon convergence threshold for mean parameters conversion
* @param maxIterations maximum iterations for mean parameters conversion
* @since 11.2
*/
protected void resetIntermediateState(final FieldSpacecraftState<T> state, final boolean forward,
final double epsilon, final int maxIterations) {
final FieldBLModel<T> newModel = computeMeanParameters((FieldKeplerianOrbit<T>) OrbitType.KEPLERIAN.convertType(state.getOrbit()),
state.getMass(), epsilon, maxIterations);
if (forward) {
models.addValidAfter(newModel, state.getDate());
} else {
models.addValidBefore(newModel, state.getDate());
}
stateChanged(state);
}
/** Compute mean parameters according to the Brouwer-Lyddane analytical model computation
* in order to do the propagation.
* @param osculating osculating orbit
* @param mass constant mass
* @param epsilon convergence threshold for mean parameters conversion
* @param maxIterations maximum iterations for mean parameters conversion
* @return Brouwer-Lyddane mean model
*/
private FieldBLModel<T> computeMeanParameters(final FieldKeplerianOrbit<T> osculating, final T mass,
final double epsilon, final int maxIterations) {
// sanity check
if (osculating.getA().getReal() < referenceRadius) {
throw new OrekitException(OrekitMessages.TRAJECTORY_INSIDE_BRILLOUIN_SPHERE,
osculating.getA());
}
final Field<T> field = mass.getField();
final T one = field.getOne();
final T zero = field.getZero();
// rough initialization of the mean parameters
FieldBLModel<T> current = new FieldBLModel<T>(osculating, mass, referenceRadius, mu, ck0);
// threshold for each parameter
final T thresholdA = current.mean.getA().abs().add(1.0).multiply(epsilon);
final T thresholdE = current.mean.getE().add(1.0).multiply(epsilon);
final T thresholdAngles = one.getPi().multiply(epsilon);
int i = 0;
while (i++ < maxIterations) {
// recompute the osculating parameters from the current mean parameters
final FieldKeplerianOrbit<T> parameters = current.propagateParameters(current.mean.getDate(), getParameters(mass.getField()));
// adapted parameters residuals
final T deltaA = osculating.getA() .subtract(parameters.getA());
final T deltaE = osculating.getE() .subtract(parameters.getE());
final T deltaI = osculating.getI() .subtract(parameters.getI());
final T deltaOmega = MathUtils.normalizeAngle(osculating.getPerigeeArgument().subtract(parameters.getPerigeeArgument()), zero);
final T deltaRAAN = MathUtils.normalizeAngle(osculating.getRightAscensionOfAscendingNode().subtract(parameters.getRightAscensionOfAscendingNode()), zero);
final T deltaAnom = MathUtils.normalizeAngle(osculating.getMeanAnomaly().subtract(parameters.getMeanAnomaly()), zero);
// update mean parameters
current = new FieldBLModel<T>(new FieldKeplerianOrbit<T>(current.mean.getA() .add(deltaA),
FastMath.max(current.mean.getE().add(deltaE), zero),
current.mean.getI() .add(deltaI),
current.mean.getPerigeeArgument() .add(deltaOmega),
current.mean.getRightAscensionOfAscendingNode().add(deltaRAAN),
current.mean.getMeanAnomaly() .add(deltaAnom),
PositionAngle.MEAN,
current.mean.getFrame(),
current.mean.getDate(), mu),
mass, referenceRadius, mu, ck0);
// check convergence
if (FastMath.abs(deltaA.getReal()) < thresholdA.getReal() &&
FastMath.abs(deltaE.getReal()) < thresholdE.getReal() &&
FastMath.abs(deltaI.getReal()) < thresholdAngles.getReal() &&
FastMath.abs(deltaOmega.getReal()) < thresholdAngles.getReal() &&
FastMath.abs(deltaRAAN.getReal()) < thresholdAngles.getReal() &&
FastMath.abs(deltaAnom.getReal()) < thresholdAngles.getReal()) {
return current;
}
}
throw new OrekitException(OrekitMessages.UNABLE_TO_COMPUTE_BROUWER_LYDDANE_MEAN_PARAMETERS, i);
}
/** {@inheritDoc} */
public FieldKeplerianOrbit<T> propagateOrbit(final FieldAbsoluteDate<T> date, final T[] parameters) {
// compute Cartesian parameters, taking derivatives into account
final FieldBLModel<T> current = models.get(date);
return current.propagateParameters(date, parameters);
}
/**
* Get the value of the M2 drag parameter.
* @return the value of the M2 drag parameter
*/
public double getM2() {
return M2Driver.getValue();
}
/** Local class for Brouwer-Lyddane model. */
private static class FieldBLModel<T extends CalculusFieldElement<T>> {
/** Mean orbit. */
private final FieldKeplerianOrbit<T> mean;
/** Constant mass. */
private final T mass;
/** Central attraction coefficient. */
private final T mu;
// CHECKSTYLE: stop JavadocVariable check
// preprocessed values
// Constant for secular terms l'', g'', h''
// l standing for true anomaly, g for perigee argument and h for raan
private final T xnotDot;
private final T n;
private final T lt;
private final T gt;
private final T ht;
// Long periodT
private final T dei3sg;
private final T de2sg;
private final T deisg;
private final T de;
private final T dlgs2g;
private final T dlgc3g;
private final T dlgcg;
private final T dh2sgcg;
private final T dhsgcg;
private final T dhcg;
// Short perioTs
private final T aC;
private final T aCbis;
private final T ac2g2f;
private final T eC;
private final T ecf;
private final T e2cf;
private final T e3cf;
private final T ec2f2g;
private final T ecfc2f2g;
private final T e2cfc2f2g;
private final T e3cfc2f2g;
private final T ec2gf;
private final T ec2g3f;
private final T ide;
private final T isfs2f2g;
private final T icfc2f2g;
private final T ic2f2g;
private final T glf;
private final T gll;
private final T glsf;
private final T glosf;
private final T gls2f2g;
private final T gls2gf;
private final T glos2gf;
private final T gls2g3f;
private final T glos2g3f;
private final T hf;
private final T hl;
private final T hsf;
private final T hcfs2g2f;
private final T hs2g2f;
private final T hsfc2g2f;
private final T edls2g;
private final T edlcg;
private final T edlc3g;
private final T edlsf;
private final T edls2gf;
private final T edls2g3f;
// Drag terms
private final T aRate;
private final T eRate;
// CHECKSTYLE: resume JavadocVariable check
/** Create a model for specified mean orbit.
* @param mean mean Fieldorbit
* @param mass constant mass
* @param referenceRadius reference radius of the central body attraction model (m)
* @param mu central attraction coefficient (m³/s²)
* @param ck0 un-normalized zonal coefficients
*/
FieldBLModel(final FieldKeplerianOrbit<T> mean, final T mass,
final double referenceRadius, final T mu, final double[] ck0) {
this.mean = mean;
this.mass = mass;
this.mu = mu;
final T one = mass.getField().getOne();
final T app = mean.getA();
xnotDot = mu.divide(app).sqrt().divide(app);
// preliminary processing
final T q = app.divide(referenceRadius).reciprocal();
T ql = q.multiply(q);
final T y2 = ql.multiply(-0.5 * ck0[2]);
n = ((mean.getE().multiply(mean.getE()).negate()).add(1.0)).sqrt();
final T n2 = n.multiply(n);
final T n3 = n2.multiply(n);
final T n4 = n2.multiply(n2);
final T n6 = n4.multiply(n2);
final T n8 = n4.multiply(n4);
final T n10 = n8.multiply(n2);
final T yp2 = y2.divide(n4);
ql = ql.multiply(q);
final T yp3 = ql.multiply(ck0[3]).divide(n6);
ql = ql.multiply(q);
final T yp4 = ql.multiply(0.375 * ck0[4]).divide(n8);
ql = ql.multiply(q);
final T yp5 = ql.multiply(ck0[5]).divide(n10);
final FieldSinCos<T> sc = FastMath.sinCos(mean.getI());
final T sinI1 = sc.sin();
final T sinI2 = sinI1.multiply(sinI1);
final T cosI1 = sc.cos();
final T cosI2 = cosI1.multiply(cosI1);
final T cosI3 = cosI2.multiply(cosI1);
final T cosI4 = cosI2.multiply(cosI2);
final T cosI6 = cosI4.multiply(cosI2);
final T C5c2 = T2(cosI1).reciprocal();
final T C3c2 = cosI2.multiply(3.0).subtract(1.0);
final T epp = mean.getE();
final T epp2 = epp.multiply(epp);
final T epp3 = epp2.multiply(epp);
final T epp4 = epp2.multiply(epp2);
if (epp.getReal() >= 1) {
// Only for elliptical (e < 1) orbits
throw new OrekitException(OrekitMessages.TOO_LARGE_ECCENTRICITY_FOR_PROPAGATION_MODEL,
mean.getE().getReal());
}
// secular multiplicative
lt = one.add(yp2.multiply(n).multiply(C3c2).multiply(1.5)).
add(yp2.multiply(0.09375).multiply(yp2).multiply(n).multiply(n2.multiply(25.0).add(n.multiply(16.0)).add(-15.0).
add(cosI2.multiply(n2.multiply(-90.0).add(n.multiply(-96.0)).add(30.0))).
add(cosI4.multiply(n2.multiply(25.0).add(n.multiply(144.0)).add(105.0))))).
add(yp4.multiply(0.9375).multiply(n).multiply(epp2).multiply(cosI4.multiply(35.0).add(cosI2.multiply(-30.0)).add(3.0)));
gt = yp2.multiply(-1.5).multiply(C5c2).
add(yp2.multiply(0.09375).multiply(yp2).multiply(n2.multiply(25.0).add(n.multiply(24.0)).add(-35.0).
add(cosI2.multiply(n2.multiply(-126.0).add(n.multiply(-192.0)).add(90.0))).
add(cosI4.multiply(n2.multiply(45.0).add(n.multiply(360.0)).add(385.0))))).
add(yp4.multiply(0.3125).multiply(n2.multiply(-9.0).add(21.0).
add(cosI2.multiply(n2.multiply(126.0).add(-270.0))).
add(cosI4.multiply(n2.multiply(-189.0).add(385.0)))));
ht = yp2.multiply(-3.0).multiply(cosI1).
add(yp2.multiply(0.375).multiply(yp2).multiply(cosI1.multiply(n2.multiply(9.0).add(n.multiply(12.0)).add(-5.0)).
add(cosI3.multiply(n2.multiply(-5.0).add(n.multiply(-36.0)).add(-35.0))))).
add(yp4.multiply(1.25).multiply(cosI1).multiply(n2.multiply(-3.0).add(5.0)).multiply(cosI2.multiply(-7.0).add(3.0)));
final T cA = one.subtract(cosI2.multiply(11.0)).subtract(cosI4.multiply(40.0).divide(C5c2));
final T cB = one.subtract(cosI2.multiply(3.0)).subtract(cosI4.multiply(8.0).divide(C5c2));
final T cC = one.subtract(cosI2.multiply(9.0)).subtract(cosI4.multiply(24.0).divide(C5c2));
final T cD = one.subtract(cosI2.multiply(5.0)).subtract(cosI4.multiply(16.0).divide(C5c2));
final T qyp2_4 = yp2.multiply(3.0).multiply(yp2).multiply(cA).
subtract(yp4.multiply(10.0).multiply(cB));
final T qyp52 = cosI1.multiply(epp3).multiply(cD.multiply(0.5).divide(sinI1).
add(sinI1.multiply(cosI4.divide(C5c2).divide(C5c2).multiply(80.0).
add(cosI2.divide(C5c2).multiply(32.0).
add(5.0)))));
final T qyp22 = epp2.add(2.0).subtract(epp2.multiply(3.0).add(2.0).multiply(11.0).multiply(cosI2)).
subtract(epp2.multiply(5.0).add(2.0).multiply(40.0).multiply(cosI4.divide(C5c2))).
subtract(epp2.multiply(400.0).multiply(cosI6).divide(C5c2).divide(C5c2));
final T qyp42 = one.divide(5.0).multiply(qyp22.
add(one.multiply(4.0).multiply(epp2.
add(2.0).
subtract(cosI2.multiply(epp2.multiply(3.0).add(2.0))))));
final T qyp52bis = cosI1.multiply(sinI1).multiply(epp).multiply(epp2.multiply(3.0).add(4.0)).
multiply(cosI4.divide(C5c2).divide(C5c2).multiply(40.0).
add(cosI2.divide(C5c2).multiply(16.0)).
add(3.0));
// long periodic multiplicative
dei3sg = yp5.divide(yp2).multiply(35.0 / 96.0).multiply(epp2).multiply(n2).multiply(cD).multiply(sinI1);
de2sg = qyp2_4.divide(yp2).multiply(epp).multiply(n2).multiply(-1.0 / 12.0);
deisg = sinI1.multiply(yp5.multiply(-35.0 / 128.0).divide(yp2).multiply(epp2).multiply(n2).multiply(cD).
add(n2.multiply(0.25).divide(yp2).multiply(yp3.add(yp5.multiply(5.0 / 16.0).multiply(epp2.multiply(3.0).add(4.0)).multiply(cC)))));
de = epp2.multiply(n2).multiply(qyp2_4).divide(24.0).divide(yp2);
final T qyp52quotient = epp.multiply(epp4.multiply(81.0).add(-32.0)).divide(n.multiply(epp2.multiply(9.0).add(4.0)).add(epp2.multiply(3.0)).add(4.0));
dlgs2g = yp2.multiply(48.0).reciprocal().multiply(yp2.multiply(-3.0).multiply(yp2).multiply(qyp22).
add(yp4.multiply(10.0).multiply(qyp42))).
add(n3.divide(yp2).multiply(qyp2_4).divide(24.0));
dlgc3g = yp5.multiply(35.0 / 384.0).divide(yp2).multiply(n3).multiply(epp).multiply(cD).multiply(sinI1).
add(yp5.multiply(35.0 / 1152.0).divide(yp2).multiply(qyp52.multiply(2.0).multiply(cosI1).subtract(epp.multiply(cD).multiply(sinI1).multiply(epp2.multiply(2.0).add(3.0)))));
dlgcg = yp3.negate().multiply(cosI2).multiply(epp).divide(yp2.multiply(sinI1).multiply(4.0)).
add(yp5.divide(yp2).multiply(0.078125).multiply(cC).multiply(cosI2.divide(sinI1).multiply(epp.negate()).multiply(epp2.multiply(3.0).add(4.0)).
add(sinI1.multiply(epp2).multiply(epp2.multiply(9.0).add(26.0)))).
subtract(yp5.divide(yp2).multiply(0.46875).multiply(qyp52bis).multiply(cosI1)).
add(yp3.divide(yp2).multiply(0.25).multiply(sinI1).multiply(epp).divide(n3.add(1.0)).multiply(epp2.multiply(epp2.subtract(3.0)).add(3.0))).
add(yp5.divide(yp2).multiply(0.078125).multiply(n2).multiply(cC).multiply(qyp52quotient).multiply(sinI1)));
final T qyp24 = yp2.multiply(yp2).multiply(3.0).multiply(cosI4.divide(sinI2).multiply(200.0).add(cosI2.divide(sinI1).multiply(80.0)).add(11.0)).
subtract(yp4.multiply(10.0).multiply(cosI4.divide(sinI2).multiply(40.0).add(cosI2.divide(sinI1).multiply(16.0)).add(3.0)));
dh2sgcg = yp5.divide(yp2).multiply(qyp52).multiply(35.0 / 144.0);
dhsgcg = qyp24.multiply(cosI1).multiply(epp2.negate()).divide(yp2.multiply(12.0));
dhcg = yp5.divide(yp2).multiply(qyp52).multiply(-35.0 / 576.0).
add(cosI1.multiply(epp).divide(yp2.multiply(sinI1).multiply(4.0)).multiply(yp3.add(yp5.multiply(0.3125).multiply(cC).multiply(epp2.multiply(3.0).add(4.0))))).
add(yp5.multiply(qyp52bis).multiply(1.875).divide(yp2.multiply(4.0)));
// short periodic multiplicative
aC = yp2.negate().multiply(C3c2).multiply(app).divide(n3);
aCbis = y2.multiply(app).multiply(C3c2);
ac2g2f = y2.multiply(app).multiply(sinI2).multiply(3.0);
T qe = y2.multiply(C3c2).multiply(0.5).multiply(n2).divide(n6);
eC = qe.multiply(epp).divide(n3.add(1.0)).multiply(epp2.multiply(epp2.subtract(3.0)).add(3.0));
ecf = qe.multiply(3.0);
e2cf = qe.multiply(3.0).multiply(epp);
e3cf = qe.multiply(epp2);
qe = y2.multiply(0.5).multiply(n2).multiply(3.0).multiply(cosI2.negate().add(1.0)).divide(n6);
ec2f2g = qe.multiply(epp);
ecfc2f2g = qe.multiply(3.0);
e2cfc2f2g = qe.multiply(3.0).multiply(epp);
e3cfc2f2g = qe.multiply(epp2);
qe = yp2.multiply(-0.5).multiply(n2).multiply(cosI2.negate().add(1.0));
ec2gf = qe.multiply(3.0);
ec2g3f = qe;
T qi = yp2.multiply(epp).multiply(cosI1).multiply(sinI1);
ide = cosI1.multiply(epp.negate()).divide(sinI1.multiply(n2));
isfs2f2g = qi;
icfc2f2g = qi.multiply(2.0);
qi = yp2.multiply(cosI1).multiply(sinI1);
ic2f2g = qi.multiply(1.5);
T qgl1 = yp2.multiply(0.25);
T qgl2 = yp2.multiply(epp).multiply(n2).multiply(0.25).divide(n.add(1.0));
glf = qgl1.multiply(C5c2).multiply(-6.0);
gll = qgl1.multiply(C5c2).multiply(6.0);
glsf = qgl1.multiply(C5c2).multiply(-6.0).multiply(epp).
add(qgl2.multiply(C3c2).multiply(2.0));
glosf = qgl2.multiply(C3c2).multiply(2.0);
qgl1 = qgl1.multiply(cosI2.multiply(-5.0).add(3.0));
qgl2 = qgl2.multiply(3.0).multiply(cosI2.negate().add(1.0));
gls2f2g = qgl1.multiply(3.0);
gls2gf = qgl1.multiply(3.0).multiply(epp).
add(qgl2);
glos2gf = qgl2.negate();
gls2g3f = qgl1.multiply(epp).
add(qgl2.multiply(1.0 / 3.0));
glos2g3f = qgl2;
final T qh = yp2.multiply(cosI1).multiply(3.0);
hf = qh.negate();
hl = qh;
hsf = qh.multiply(epp).negate();
hcfs2g2f = yp2.multiply(cosI1).multiply(epp).multiply(2.0);
hs2g2f = yp2.multiply(cosI1).multiply(1.5);
hsfc2g2f = yp2.multiply(cosI1).multiply(epp).negate();
final T qedl = yp2.multiply(n3).multiply(-0.25);
edls2g = qyp2_4.multiply(1.0 / 24.0).multiply(epp).multiply(n3).divide(yp2);
edlcg = yp3.divide(yp2).multiply(-0.25).multiply(n3).multiply(sinI1).
subtract(yp5.divide(yp2).multiply(0.078125).multiply(n3).multiply(sinI1).multiply(cC).multiply(epp2.multiply(9.0).add(4.0)));
edlc3g = yp5.divide(yp2).multiply(n3).multiply(epp2).multiply(cD).multiply(sinI1).multiply(35.0 / 384.0);
edlsf = qedl.multiply(C3c2).multiply(2.0);
edls2gf = qedl.multiply(3.0).multiply(cosI2.negate().add(1.0));
edls2g3f = qedl.multiply(1.0 / 3.0);
// secular rates of the mean semi-major axis and eccentricity
// Eq. 2.41 and Eq. 2.45 of Phipps' 1992 thesis
aRate = app.multiply(-4.0).divide(xnotDot.multiply(3.0));
eRate = epp.multiply(n).multiply(n).multiply(-4.0).divide(xnotDot.multiply(3.0));
}
/**
* Accurate computation of E - e sin(E).
*
* @param E eccentric anomaly
* @return E - e sin(E)
*/
private FieldUnivariateDerivative2<T> eMeSinE(final FieldUnivariateDerivative2<T> E) {
FieldUnivariateDerivative2<T> x = E.sin().multiply(mean.getE().negate().add(1.0));
final FieldUnivariateDerivative2<T> mE2 = E.negate().multiply(E);
FieldUnivariateDerivative2<T> term = E;
FieldUnivariateDerivative2<T> d = E.getField().getZero();
// the inequality test below IS intentional and should NOT be replaced by a check with a small tolerance
for (FieldUnivariateDerivative2<T> x0 = d.add(Double.NaN); !Double.valueOf(x.getValue().getReal()).equals(Double.valueOf(x0.getValue().getReal()));) {
d = d.add(2);
term = term.multiply(mE2.divide(d.multiply(d.add(1))));
x0 = x;
x = x.subtract(term);
}
return x;
}
/**
* Gets eccentric anomaly from mean anomaly.
* <p>The algorithm used to solve the Kepler equation has been published in:
* "Procedures for solving Kepler's Equation", A. W. Odell and R. H. Gooding,
* Celestial Mechanics 38 (1986) 307-334</p>
* <p>It has been copied from the OREKIT library (KeplerianOrbit class).</p>
*
* @param mk the mean anomaly (rad)
* @return the eccentric anomaly (rad)
*/
private FieldUnivariateDerivative2<T> getEccentricAnomaly(final FieldUnivariateDerivative2<T> mk) {
final double k1 = 3 * FastMath.PI + 2;
final double k2 = FastMath.PI - 1;
final double k3 = 6 * FastMath.PI - 1;
final double A = 3.0 * k2 * k2 / k1;
final double B = k3 * k3 / (6.0 * k1);
final T zero = mean.getE().getField().getZero();
// reduce M to [-PI PI] interval
final FieldUnivariateDerivative2<T> reducedM = new FieldUnivariateDerivative2<T>(MathUtils.normalizeAngle(mk.getValue(), zero ),
mk.getFirstDerivative(),
mk.getSecondDerivative());
// compute start value according to A. W. Odell and R. H. Gooding S12 starter
FieldUnivariateDerivative2<T> ek;
if (reducedM.getValue().abs().getReal() < 1.0 / 6.0) {
if (reducedM.getValue().abs().getReal() < Precision.SAFE_MIN) {
// this is an Orekit change to the S12 starter.
// If reducedM is 0.0, the derivative of cbrt is infinite which induces NaN appearing later in
// the computation. As in this case E and M are almost equal, we initialize ek with reducedM
ek = reducedM;
} else {
// this is the standard S12 starter
ek = reducedM.add(reducedM.multiply(6).cbrt().subtract(reducedM).multiply(mean.getE()));
}
} else {
if (reducedM.getValue().getReal() < 0) {
final FieldUnivariateDerivative2<T> w = reducedM.add(FastMath.PI);
ek = reducedM.add(w.multiply(-A).divide(w.subtract(B)).subtract(FastMath.PI).subtract(reducedM).multiply(mean.getE()));
} else {
final FieldUnivariateDerivative2<T> minusW = reducedM.subtract(FastMath.PI);
ek = reducedM.add(minusW.multiply(A).divide(minusW.add(B)).add(FastMath.PI).subtract(reducedM).multiply(mean.getE()));
}
}
final T e1 = mean.getE().negate().add(1.0);
final boolean noCancellationRisk = (e1.add(ek.getValue().multiply(ek.getValue())).getReal() / 6) >= 0.1;
// perform two iterations, each consisting of one Halley step and one Newton-Raphson step
for (int j = 0; j < 2; ++j) {
final FieldUnivariateDerivative2<T> f;
FieldUnivariateDerivative2<T> fd;
final FieldUnivariateDerivative2<T> fdd = ek.sin().multiply(mean.getE());
final FieldUnivariateDerivative2<T> fddd = ek.cos().multiply(mean.getE());
if (noCancellationRisk) {
f = ek.subtract(fdd).subtract(reducedM);
fd = fddd.subtract(1).negate();
} else {
f = eMeSinE(ek).subtract(reducedM);
final FieldUnivariateDerivative2<T> s = ek.multiply(0.5).sin();
fd = s.multiply(s).multiply(mean.getE().multiply(2.0)).add(e1);
}
final FieldUnivariateDerivative2<T> dee = f.multiply(fd).divide(f.multiply(0.5).multiply(fdd).subtract(fd.multiply(fd)));
// update eccentric anomaly, using expressions that limit underflow problems
final FieldUnivariateDerivative2<T> w = fd.add(dee.multiply(0.5).multiply(fdd.add(dee.multiply(fdd).divide(3))));
fd = fd.add(dee.multiply(fdd.add(dee.multiply(0.5).multiply(fdd))));
ek = ek.subtract(f.subtract(dee.multiply(fd.subtract(w))).divide(fd));
}
// expand the result back to original range
ek = ek.add(mk.getValue().subtract(reducedM.getValue()));
// Returns the eccentric anomaly
return ek;
}
/**
* This method is used in Brouwer-Lyddane model to avoid singularity at the
* critical inclination (i = 63.4°).
* <p>
* This method, based on Warren Phipps's 1992 thesis (Eq. 2.47 and 2.48),
* approximate the factor (1.0 - 5.0 * cos²(inc))^-1 (causing the singularity)
* by a function, named T2 in the thesis.
* </p>
* @param cosInc cosine of the mean inclination
* @return an approximation of (1.0 - 5.0 * cos²(inc))^-1 term
*/
private T T2(final T cosInc) {
// X = (1.0 - 5.0 * cos²(inc))
final T x = cosInc.multiply(cosInc).multiply(-5.0).add(1.0);
final T x2 = x.multiply(x);
// Eq. 2.48
T sum = x.getField().getZero();
for (int i = 0; i <= 12; i++) {
final double sign = i % 2 == 0 ? +1.0 : -1.0;
sum = sum.add(FastMath.pow(x2, i).multiply(FastMath.pow(BETA, i)).multiply(sign).divide(CombinatoricsUtils.factorialDouble(i + 1)));
}
// Right term of equation 2.47
T product = x.getField().getOne();
for (int i = 0; i <= 10; i++) {
product = product.multiply(FastMath.exp(x2.multiply(BETA).multiply(FastMath.scalb(-1.0, i))).add(1.0));
}
// Return (Eq. 2.47)
return x.multiply(BETA).multiply(sum).multiply(product);
}
/** Extrapolate an orbit up to a specific target date.
* @param date target date for the orbit
* @param parameters model parameters
* @return propagated parameters
*/
public FieldKeplerianOrbit<T> propagateParameters(final FieldAbsoluteDate<T> date, final T[] parameters) {
// Field
final Field<T> field = date.getField();
final T one = field.getOne();
final T zero = field.getZero();
// Empirical drag coefficient M2
final T m2 = parameters[0];
// Keplerian evolution
final FieldUnivariateDerivative2<T> dt = new FieldUnivariateDerivative2<>(date.durationFrom(mean.getDate()), one, zero);
final FieldUnivariateDerivative2<T> xnot = dt.multiply(xnotDot);
//____________________________________
// secular effects
// mean mean anomaly
final FieldUnivariateDerivative2<T> dtM2 = dt.multiply(m2);
final FieldUnivariateDerivative2<T> dt2M2 = dt.multiply(dtM2);
final FieldUnivariateDerivative2<T> lpp = new FieldUnivariateDerivative2<T>(MathUtils.normalizeAngle(mean.getMeanAnomaly().add(lt.multiply(xnot.getValue())).add(dt2M2.getValue()), zero),
lt.multiply(xnotDot).add(dtM2.multiply(2.0).getValue()),
m2.multiply(2.0));
// mean argument of perigee
final FieldUnivariateDerivative2<T> gpp = new FieldUnivariateDerivative2<T>(MathUtils.normalizeAngle(mean.getPerigeeArgument().add(gt.multiply(xnot.getValue())), zero),
gt.multiply(xnotDot),
zero);
// mean longitude of ascending node
final FieldUnivariateDerivative2<T> hpp = new FieldUnivariateDerivative2<T>(MathUtils.normalizeAngle(mean.getRightAscensionOfAscendingNode().add(ht.multiply(xnot.getValue())), zero),
ht.multiply(xnotDot),
zero);
// ________________________________________________
// secular rates of the mean semi-major axis and eccentricity
// semi-major axis
final FieldUnivariateDerivative2<T> appDrag = dt.multiply(aRate.multiply(m2));
// eccentricity
final FieldUnivariateDerivative2<T> eppDrag = dt.multiply(eRate.multiply(m2));
//____________________________________
// Long periodical terms
final FieldUnivariateDerivative2<T> cg1 = gpp.cos();
final FieldUnivariateDerivative2<T> sg1 = gpp.sin();
final FieldUnivariateDerivative2<T> c2g = cg1.multiply(cg1).subtract(sg1.multiply(sg1));
final FieldUnivariateDerivative2<T> s2g = cg1.multiply(sg1).add(sg1.multiply(cg1));
final FieldUnivariateDerivative2<T> c3g = c2g.multiply(cg1).subtract(s2g.multiply(sg1));
final FieldUnivariateDerivative2<T> sg2 = sg1.multiply(sg1);
final FieldUnivariateDerivative2<T> sg3 = sg1.multiply(sg2);
// de eccentricity
final FieldUnivariateDerivative2<T> d1e = sg3.multiply(dei3sg).
add(sg1.multiply(deisg)).
add(sg2.multiply(de2sg)).
add(de);
// l' + g'
final FieldUnivariateDerivative2<T> lp_p_gp = s2g.multiply(dlgs2g).
add(c3g.multiply(dlgc3g)).
add(cg1.multiply(dlgcg)).
add(lpp).
add(gpp);
// h'
final FieldUnivariateDerivative2<T> hp = sg2.multiply(cg1).multiply(dh2sgcg).
add(sg1.multiply(cg1).multiply(dhsgcg)).
add(cg1.multiply(dhcg)).
add(hpp);
// Short periodical terms
// eccentric anomaly
final FieldUnivariateDerivative2<T> Ep = getEccentricAnomaly(lpp);
final FieldUnivariateDerivative2<T> cf1 = (Ep.cos().subtract(mean.getE())).divide(Ep.cos().multiply(mean.getE().negate()).add(1.0));
final FieldUnivariateDerivative2<T> sf1 = (Ep.sin().multiply(n)).divide(Ep.cos().multiply(mean.getE().negate()).add(1.0));
final FieldUnivariateDerivative2<T> f = FastMath.atan2(sf1, cf1);
final FieldUnivariateDerivative2<T> c2f = cf1.multiply(cf1).subtract(sf1.multiply(sf1));
final FieldUnivariateDerivative2<T> s2f = cf1.multiply(sf1).add(sf1.multiply(cf1));
final FieldUnivariateDerivative2<T> c3f = c2f.multiply(cf1).subtract(s2f.multiply(sf1));
final FieldUnivariateDerivative2<T> s3f = c2f.multiply(sf1).add(s2f.multiply(cf1));
final FieldUnivariateDerivative2<T> cf2 = cf1.multiply(cf1);
final FieldUnivariateDerivative2<T> cf3 = cf1.multiply(cf2);
final FieldUnivariateDerivative2<T> c2g1f = cf1.multiply(c2g).subtract(sf1.multiply(s2g));
final FieldUnivariateDerivative2<T> c2g2f = c2f.multiply(c2g).subtract(s2f.multiply(s2g));
final FieldUnivariateDerivative2<T> c2g3f = c3f.multiply(c2g).subtract(s3f.multiply(s2g));
final FieldUnivariateDerivative2<T> s2g1f = cf1.multiply(s2g).add(c2g.multiply(sf1));
final FieldUnivariateDerivative2<T> s2g2f = c2f.multiply(s2g).add(c2g.multiply(s2f));
final FieldUnivariateDerivative2<T> s2g3f = c3f.multiply(s2g).add(c2g.multiply(s3f));
final FieldUnivariateDerivative2<T> eE = (Ep.cos().multiply(mean.getE().negate()).add(1.0)).reciprocal();
final FieldUnivariateDerivative2<T> eE3 = eE.multiply(eE).multiply(eE);
final FieldUnivariateDerivative2<T> sigma = eE.multiply(n.multiply(n)).multiply(eE).add(eE);
// Semi-major axis
final FieldUnivariateDerivative2<T> a = eE3.multiply(aCbis).add(appDrag.add(mean.getA())).
add(aC).
add(eE3.multiply(c2g2f).multiply(ac2g2f));
// Eccentricity
final FieldUnivariateDerivative2<T> e = d1e.add(eppDrag.add(mean.getE())).
add(eC).
add(cf1.multiply(ecf)).
add(cf2.multiply(e2cf)).
add(cf3.multiply(e3cf)).
add(c2g2f.multiply(ec2f2g)).
add(c2g2f.multiply(cf1).multiply(ecfc2f2g)).
add(c2g2f.multiply(cf2).multiply(e2cfc2f2g)).
add(c2g2f.multiply(cf3).multiply(e3cfc2f2g)).
add(c2g1f.multiply(ec2gf)).
add(c2g3f.multiply(ec2g3f));
// Inclination
final FieldUnivariateDerivative2<T> i = d1e.multiply(ide).
add(mean.getI()).
add(sf1.multiply(s2g2f.multiply(isfs2f2g))).
add(cf1.multiply(c2g2f.multiply(icfc2f2g))).
add(c2g2f.multiply(ic2f2g));
// Argument of perigee + True anomaly
final FieldUnivariateDerivative2<T> g_p_l = lp_p_gp.add(f.multiply(glf)).
add(lpp.multiply(gll)).
add(sf1.multiply(glsf)).
add(sigma.multiply(sf1).multiply(glosf)).
add(s2g2f.multiply(gls2f2g)).
add(s2g1f.multiply(gls2gf)).
add(sigma.multiply(s2g1f).multiply(glos2gf)).
add(s2g3f.multiply(gls2g3f)).
add(sigma.multiply(s2g3f).multiply(glos2g3f));
// Longitude of ascending node
final FieldUnivariateDerivative2<T> h = hp.add(f.multiply(hf)).
add(lpp.multiply(hl)).
add(sf1.multiply(hsf)).
add(cf1.multiply(s2g2f).multiply(hcfs2g2f)).
add(s2g2f.multiply(hs2g2f)).
add(c2g2f.multiply(sf1).multiply(hsfc2g2f));
final FieldUnivariateDerivative2<T> edl = s2g.multiply(edls2g).
add(cg1.multiply(edlcg)).
add(c3g.multiply(edlc3g)).
add(sf1.multiply(edlsf)).
add(s2g1f.multiply(edls2gf)).
add(s2g3f.multiply(edls2g3f)).
add(sf1.multiply(sigma).multiply(edlsf)).
add(s2g1f.multiply(sigma).multiply(edls2gf.negate())).
add(s2g3f.multiply(sigma).multiply(edls2g3f.multiply(3.0)));
final FieldUnivariateDerivative2<T> A = e.multiply(lpp.cos()).subtract(edl.multiply(lpp.sin()));
final FieldUnivariateDerivative2<T> B = e.multiply(lpp.sin()).add(edl.multiply(lpp.cos()));
// True anomaly
final FieldUnivariateDerivative2<T> l = FastMath.atan2(B, A);
// Argument of perigee
final FieldUnivariateDerivative2<T> g = g_p_l.subtract(l);
// Return a keplerian orbit
return new FieldKeplerianOrbit<>(a.getValue(), e.getValue(), i.getValue(),
g.getValue(), h.getValue(), l.getValue(),
a.getFirstDerivative(), e.getFirstDerivative(), i.getFirstDerivative(),
g.getFirstDerivative(), h.getFirstDerivative(), l.getFirstDerivative(),
PositionAngle.MEAN, mean.getFrame(), date, this.mu);
}
}
/** {@inheritDoc} */
@Override
protected T getMass(final FieldAbsoluteDate<T> date) {
return models.get(date).mass;
}
/** {@inheritDoc} */
@Override
protected List<ParameterDriver> getParametersDrivers() {
return Collections.singletonList(M2Driver);
}
}