NonKeplerianDriversFactory.java

/* Copyright 2022-2025 Thales Alenia Space
 * 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.gnss.data;

import org.hipparchus.CalculusFieldElement;
import org.hipparchus.Field;
import org.hipparchus.analysis.differentiation.FieldGradient;
import org.hipparchus.analysis.differentiation.FieldGradientField;
import org.hipparchus.analysis.differentiation.Gradient;
import org.hipparchus.util.FastMath;
import org.hipparchus.util.MathArrays;
import org.orekit.utils.Constants;
import org.orekit.utils.ParameterDriver;

import java.util.Arrays;
import java.util.List;

/** Factory for non-Keplerian drivers.
 * @since 14.0
 */
public class NonKeplerianDriversFactory {

    /** Name for time parameter. */
    public static final String TIME = "GnssTime";

    /** Name for change rate in semi-major axis parameter. */
    public static final String A_DOT = "GnssADot";

    /** Name for delta of satellite mean motion. */
    public static final String DELTA_N0 = "GnssDeltaN0";

    /** Name for change rate in Δn₀. */
    public static final String DELTA_N0_DOT = "GnssDeltaN0Dot";

    /** Name for inclination rate parameter. */
    public static final String INCLINATION_RATE = "GnssInclinationRate";

    /** Name for longitude rate parameter. */
    public static final String LONGITUDE_RATE = "GnssLongitudeRate";

    /** Name for cosine of latitude argument harmonic parameter. */
    public static final String LATITUDE_COSINE = "GnssLatitudeCosine";

    /** Name for sine of latitude argument harmonic parameter. */
    public static final String LATITUDE_SINE = "GnssLatitudeSine";

    /** Name for cosine of orbit radius harmonic parameter. */
    public static final String RADIUS_COSINE = "GnssRadiusCosine";

    /** Name for sine of orbit radius harmonic parameter. */
    public static final String RADIUS_SINE = "GnssRadiusSine";

    /** Name for cosine of inclination harmonic parameter. */
    public static final String INCLINATION_COSINE = "GnssInclinationCosine";

    /** Name for sine of inclination harmonic parameter. */
    public static final String INCLINATION_SINE = "GnssInclinationSine";

    /** Name for zero-th order clock correction parameter. */
    public static final String AF0 = "GnssClock0";

    /** Name for first order clock correction parameter. */
    public static final String AF1 = "GnssClock1";

    /** Name for second order clock correction parameter. */
    public static final String AF2 = "GnssClock2";

    /** Index of time in the list returned by {@link #getParametersDrivers()}. */
    public static final int TIME_INDEX = 0;

    /** Index of change rate in semi-major axis parameter in the list returned by {@link #getParametersDrivers()}. */
    public static final int A_DOT_INDEX = TIME_INDEX + 1;

    /** Index of delta of satellite mean motion in the list returned by {@link #getParametersDrivers()}. */
    public static final int DELTA_N0_INDEX = A_DOT_INDEX + 1;

    /** Index of change rate in Δn₀ in the list returned by {@link #getParametersDrivers()}. */
    public static final int DELTA_N0_DOT_INDEX = DELTA_N0_INDEX + 1;

    /** Index of inclination rate in the list returned by {@link #getParametersDrivers()}. */
    public static final int I_DOT_INDEX = DELTA_N0_DOT_INDEX + 1;

    /** Index of longitude rate in the list returned by {@link #getParametersDrivers()}. */
    public static final int OMEGA_DOT_INDEX = I_DOT_INDEX + 1;

    /** Index of cosine on latitude argument in the list returned by {@link #getParametersDrivers()}. */
    public static final int CUC_INDEX = OMEGA_DOT_INDEX + 1;

    /** Index of sine on latitude argument in the list returned by {@link #getParametersDrivers()}. */
    public static final int CUS_INDEX = CUC_INDEX + 1;

    /** Index of cosine on radius in the list returned by {@link #getParametersDrivers()}. */
    public static final int CRC_INDEX = CUS_INDEX + 1;

    /** Index of sine on radius in the list returned by {@link #getParametersDrivers()}. */
    public static final int CRS_INDEX = CRC_INDEX + 1;

    /** Index of cosine on inclination in the list returned by {@link #getParametersDrivers()}. */
    public static final int CIC_INDEX = CRS_INDEX + 1;

    /** Index of sine on inclination in the list returned by {@link #getParametersDrivers()}. */
    public static final int CIS_INDEX = CIC_INDEX + 1;

    /** Index of zero-th order clock correction in the list returned by {@link #getParametersDrivers()}. */
    public static final int AF0_INDEX = CIS_INDEX + 1;

    /** Index of first order clock correction in the list returned by {@link #getParametersDrivers()}. */
    public static final int AF1_INDEX = AF0_INDEX + 1;

    /** Index of second order clock correction in the list returned by {@link #getParametersDrivers()}. */
    public static final int AF2_INDEX = AF1_INDEX + 1;

    /** Size of parameters array. */
    public static final int SIZE = AF2_INDEX + 1;

    /** Reference time. */
    private final ParameterDriver timeDriver;

    /** Change rate in semi-major axis (m/s). */
    private final ParameterDriver aDotDriver;

    /** Delta of satellite mean motion. */
    private final ParameterDriver deltaN0Driver;

    /** Change rate in Δn₀. */
    private final ParameterDriver deltaN0DotDriver;

    /** Inclination rate (rad/s). */
    private final ParameterDriver iDotDriver;

    /** Rate of right ascension (rad/s). */
    private final ParameterDriver domDriver;

    /** Amplitude of the cosine harmonic correction term to the argument of latitude. */
    private final ParameterDriver cucDriver;

    /** Amplitude of the sine harmonic correction term to the argument of latitude. */
    private final ParameterDriver cusDriver;

    /** Amplitude of the cosine harmonic correction term to the orbit radius. */
    private final ParameterDriver crcDriver;

    /** Amplitude of the sine harmonic correction term to the orbit radius. */
    private final ParameterDriver crsDriver;

    /** Amplitude of the cosine harmonic correction term to the inclination. */
    private final ParameterDriver cicDriver;

    /** Amplitude of the sine harmonic correction term to the inclination. */
    private final ParameterDriver cisDriver;

    /** SV zero-th order clock correction (s). */
    private final ParameterDriver af0Driver;

    /** SV first order clock correction (s/s). */
    private final ParameterDriver af1Driver;

    /** SV second order clock correction (s/s²). */
    private final ParameterDriver af2Driver;

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

        // propagation drivers
        this.timeDriver       = new ParameterDriver(TIME,                0.0, FastMath.scalb(1.0, -10),
                                                    0, 7 * Constants.JULIAN_DAY);
        this.aDotDriver       = new ParameterDriver(A_DOT,               0.0, FastMath.scalb(1.0, -10),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.deltaN0Driver    = new ParameterDriver(DELTA_N0,            0.0, FastMath.scalb(1.0, -36),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.deltaN0DotDriver = new ParameterDriver(DELTA_N0_DOT,        0.0, FastMath.scalb(1.0, -46),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.iDotDriver       = new ParameterDriver(INCLINATION_RATE,    0.0, FastMath.scalb(1.0, -34),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.domDriver        = new ParameterDriver(LONGITUDE_RATE,      0.0, FastMath.scalb(1.0, -34),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.cucDriver        = new ParameterDriver(LATITUDE_COSINE,     0.0, FastMath.scalb(1.0, -24),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.cusDriver        = new ParameterDriver(LATITUDE_SINE,       0.0, FastMath.scalb(1.0, -24),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.crcDriver        = new ParameterDriver(RADIUS_COSINE,       0.0, FastMath.scalb(1.0,   0),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.crsDriver        = new ParameterDriver(RADIUS_SINE,         0.0, FastMath.scalb(1.0,   0),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.cicDriver        = new ParameterDriver(INCLINATION_COSINE,  0.0, FastMath.scalb(1.0, -24),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.cisDriver        = new ParameterDriver(INCLINATION_SINE,    0.0, FastMath.scalb(1.0, -24),
                                                    Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

        // clock drivers
        this.af0Driver = new ParameterDriver(AF0, 0.0, FastMath.scalb(1.0, -26),
                                             Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.af1Driver = new ParameterDriver(AF1, 0.0, FastMath.scalb(1.0, -42),
                                             Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
        this.af2Driver = new ParameterDriver(AF2, 0.0, FastMath.scalb(1.0, -58),
                                             Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

    }

    /** Reset the parameters drivers from existing elements.
     * @param elements elements to use for reset
     */
    public void reset(final GNSSOrbitalElements<?> elements) {
        reset(timeDriver,       elements.getTimeOfEphemeris().getSecondsInWeek());
        reset(aDotDriver,       elements.getADot());
        reset(deltaN0Driver,    elements.getDeltaN0());
        reset(deltaN0DotDriver, elements.getDeltaN0Dot());
        reset(iDotDriver,       elements.getIDot());
        reset(domDriver,        elements.getOmegaDot());
        reset(cucDriver,        elements.getCuc());
        reset(cusDriver,        elements.getCus());
        reset(crcDriver,        elements.getCrc());
        reset(crsDriver,        elements.getCrs());
        reset(cicDriver,        elements.getCic());
        reset(cisDriver,        elements.getCis());
        reset(af0Driver,        elements.getAf0());
        reset(af1Driver,        elements.getAf1());
        reset(af2Driver,        elements.getAf2());
    }

    /** Reset the parameters drivers from existing elements.
     * @param elements elements to use for reset
     */
    public void reset(final FieldGnssOrbitalElements<?, ?> elements) {
        reset(timeDriver,       elements.getTimeOfEphemeris().getGnssDate().getSecondsInWeek());
        reset(aDotDriver,       elements.getADot().getReal());
        reset(deltaN0Driver,    elements.getDeltaN0().getReal());
        reset(deltaN0DotDriver, elements.getDeltaN0Dot().getReal());
        reset(iDotDriver,       elements.getIDot().getReal());
        reset(domDriver,        elements.getOmegaDot().getReal());
        reset(cucDriver,        elements.getCuc().getReal());
        reset(cusDriver,        elements.getCus().getReal());
        reset(crcDriver,        elements.getCrc().getReal());
        reset(crsDriver,        elements.getCrs().getReal());
        reset(cicDriver,        elements.getCic().getReal());
        reset(cisDriver,        elements.getCis().getReal());
        reset(af0Driver,        elements.getAf0().getReal());
        reset(af1Driver,        elements.getAf1().getReal());
        reset(af2Driver,        elements.getAf2().getReal());
    }

    /** Reset one driver.
     * @param driver driver to reset
     * @param value new value (also used as reference)
     */
    private void reset (final ParameterDriver driver, final double value) {
        driver.setValue(value);
        driver.setReferenceValue(value);
    }

    /** Get the 15 drivers for the non-Keplerian parameters.
     * <p>
     * Only the 15 non-Keplerian parameters (12 evolution parameters and 3 clock parameters)
     * are listed here:
     * Time driver at index {@link #TIME_INDEX},
     * ADot driver at index {@link #A_DOT_INDEX},
     * DeltaN0 driver at index {@link #DELTA_N0_INDEX},
     * DeltaN0Dot driver at index {@link #DELTA_N0_DOT_INDEX},
     * IDot driver at index {@link #I_DOT_INDEX},
     * OmegaDot driver at index {@link #OMEGA_DOT_INDEX},
     * Cuc driver at index {@link #CUC_INDEX},
     * Cus driver at index {@link #CUS_INDEX},
     * Crc driver at index {@link #CRC_INDEX},
     * Crs driver at index {@link #CRS_INDEX},
     * Cic driver at index {@link #CIC_INDEX},
     * Cis driver at index {@link #CIS_INDEX},
     * af0 driver at index {@link #AF0_INDEX},
     * af1 driver at index {@link #AF1_INDEX},
     * and af2 driver at index {@link #AF2_INDEX}.
     * </p>
     * @return 15 drivers for the non-Keplerian parameters
     */
    public List<ParameterDriver> getParametersDrivers() {

        // ensure the parameters are really at the advertised indices
        final ParameterDriver[] array = new ParameterDriver[SIZE];

        array[TIME_INDEX]         = timeDriver;
        array[A_DOT_INDEX]        = aDotDriver;
        array[DELTA_N0_INDEX]     = deltaN0Driver;
        array[DELTA_N0_DOT_INDEX] = deltaN0DotDriver;
        array[I_DOT_INDEX]        = iDotDriver;
        array[OMEGA_DOT_INDEX]    = domDriver;
        array[CUC_INDEX]          = cucDriver;
        array[CUS_INDEX]          = cusDriver;
        array[CRC_INDEX]          = crcDriver;
        array[CRS_INDEX]          = crsDriver;
        array[CIC_INDEX]          = cicDriver;
        array[CIS_INDEX]          = cisDriver;

        array[AF0_INDEX]         = af0Driver;
        array[AF1_INDEX]         = af1Driver;
        array[AF2_INDEX]         = af2Driver;

        return Arrays.asList(array);

    }

    /** Get driver for reference time of the GNSS orbit as a duration from week start.
     * @return driver for reference time of the GNSS orbit (s)
     */
    public ParameterDriver getTimeDriver() {
        return timeDriver;
    }

    /** Get driver for change rate in semi-major axis.
     * @return driver for the change rate in semi-major axis
     */
    public ParameterDriver getADotDriver() {
        return aDotDriver;
    }

    /** Get driver for the delta of satellite mean motion.
     * @return driver for the delta of satellite mean motion
     */
    public ParameterDriver getDeltaN0Driver() {
        return deltaN0Driver;
    }

    /** Get driver for the change rate in Δn₀.
     * @return driver for change rate in Δn₀
     */
    public ParameterDriver getDeltaN0DotDriver() {
        return deltaN0DotDriver;
    }

    /** Get driver for rate of inclination angle.
     * @return driver for rate of inclination angle (rad/s)
     */
    public ParameterDriver getIDotDriver() {
        return iDotDriver;
    }

    /** Get driver for rate of right ascension.
     * @return driver for rate of right ascension (rad/s)
     */
    public ParameterDriver getOmegaDotDriver() {
        return domDriver;
    }

    /** Get driver for amplitude of the cosine harmonic correction term to the argument of latitude.
     * @return driver for amplitude of the cosine harmonic correction term to the argument of latitude (rad)
     */
    public ParameterDriver getCucDriver() {
        return cucDriver;
    }

    /** Get driver for amplitude of the sine harmonic correction term to the argument of latitude.
     * @return driver for amplitude of the sine harmonic correction term to the argument of latitude (rad)
     */
    public ParameterDriver getCusDriver() {
        return cusDriver;
    }

    /** Get driver for amplitude of the cosine harmonic correction term to the orbit radius.
     * @return driver for amplitude of the cosine harmonic correction term to the orbit radius (m)
     */
    public ParameterDriver getCrcDriver() {
        return crcDriver;
    }

    /** Get driver for amplitude of the sine harmonic correction term to the orbit radius.
     * @return driver for amplitude of the sine harmonic correction term to the orbit radius (m)
     */
    public ParameterDriver getCrsDriver() {
        return crsDriver;
    }

    /** Get driver for amplitude of the cosine harmonic correction term to the angle of inclination.
     * @return driver for amplitude of the cosine harmonic correction term to the angle of inclination (rad)
     */
    public ParameterDriver getCicDriver() {
        return cicDriver;
    }

    /** Get driver for amplitude of the sine harmonic correction term to the angle of inclination.
     * @return driver for amplitude of the sine harmonic correction term to the angle of inclination (rad)
     */
    public ParameterDriver getCisDriver() {
        return cisDriver;
    }

    /** Get driver for SV zero-th order clock correction.
     * @return driver for SV zero-th order clock correction (s)
     */
    public ParameterDriver getAf0Driver() {
        return af0Driver;
    }

    /** Get driver for SV first order clock correction.
     * @return driver for SV first order clock correction (s/s)
     */
    public ParameterDriver getAf1Driver() {
        return af1Driver;
    }

    /** Get driver for SV second order clock correction.
     * @return driver for SV second order clock correction (s/s²)
     */
    public ParameterDriver getAf2Driver() {
        return af2Driver;
    }

    /** Get the non-Keplerian elements as gradient variables or constants, depending on selection status.
     * @param freeParameters total number of free parameters in the gradient
     * @return non-Keplerian elements as gradient variables or constants
     */
    public Gradient[] toGradients(final int freeParameters) {
        final Filler filler = new Filler(freeParameters);
        filler.manage(timeDriver,       TIME_INDEX);
        filler.manage(aDotDriver,       A_DOT_INDEX);
        filler.manage(deltaN0Driver,    DELTA_N0_INDEX);
        filler.manage(deltaN0DotDriver, DELTA_N0_DOT_INDEX);
        filler.manage(iDotDriver,       I_DOT_INDEX);
        filler.manage(domDriver,        OMEGA_DOT_INDEX);
        filler.manage(cucDriver,        CUC_INDEX);
        filler.manage(cusDriver,        CUS_INDEX);
        filler.manage(crcDriver,        CRC_INDEX);
        filler.manage(crsDriver,        CRS_INDEX);
        filler.manage(cicDriver,        CIC_INDEX);
        filler.manage(cisDriver,        CIS_INDEX);
        filler.manage(af0Driver,        AF0_INDEX);
        filler.manage(af1Driver,        AF1_INDEX);
        filler.manage(af2Driver,        AF2_INDEX);
        return filler.gradients;
    }

    /** Get the non-Keplerian elements as gradient variables or constants, depending on selection status.
     * @param <T> type of the field elements
     * @param field field
     * @param freeParameters total number of free parameters in the gradient
     * @return non-Keplerian elements as gradient variables or constants
     */
    public <T extends CalculusFieldElement<T>> FieldGradient<T>[] toGradients(final Field<T> field,
                                                                              final int freeParameters) {
        final FieldFiller<T> filler = new FieldFiller<>(field, freeParameters);
        filler.manage(timeDriver,       TIME_INDEX);
        filler.manage(aDotDriver,       A_DOT_INDEX);
        filler.manage(deltaN0Driver,    DELTA_N0_INDEX);
        filler.manage(deltaN0DotDriver, DELTA_N0_DOT_INDEX);
        filler.manage(iDotDriver,       I_DOT_INDEX);
        filler.manage(domDriver,        OMEGA_DOT_INDEX);
        filler.manage(cucDriver,        CUC_INDEX);
        filler.manage(cusDriver,        CUS_INDEX);
        filler.manage(crcDriver,        CRC_INDEX);
        filler.manage(crsDriver,        CRS_INDEX);
        filler.manage(cicDriver,        CIC_INDEX);
        filler.manage(cisDriver,        CIS_INDEX);
        filler.manage(af0Driver,        AF0_INDEX);
        filler.manage(af1Driver,        AF1_INDEX);
        filler.manage(af2Driver,        AF2_INDEX);
        return filler.gradients;
    }

    /** Array filler for gradients.
     * @since 14.0
     */
    private static class Filler {

        /** Total number of free parameters in the gradient. */
        private final int freeParameters;

        /** Gradient array. */
        private final Gradient[] gradients;

        /** Partial derivative index. */
        private int derivative;

        /** Simple constructor.
          * @param freeParameters total number of free parameters in the gradient
          */
        Filler(final int freeParameters) {
            this.freeParameters = freeParameters;
            this.gradients      = new Gradient[SIZE];
            this.derivative     = 6;
        }

        /** Manage one driver.
         * @param driver driver to manage
         * @param index index of the driver in the array
         */
        private void manage(final ParameterDriver driver, final int index) {
            if (driver.isSelected()) {
                // this driver should be managed as a variable
                gradients[index] = Gradient.variable(freeParameters, derivative, driver.getValue());
                ++derivative;
            } else {
                // this driver should be managed as a constant
                gradients[index] = Gradient.constant(freeParameters, driver.getValue());
            }
        }

    }

    /** Array filler for gradients.
     * @param <T> field to which elements belong
     * @since 14.0
     */
    private static class FieldFiller<T extends CalculusFieldElement<T>> {

        /** Field. */
        private final Field<T> field;

        /** Total number of free parameters in the gradient. */
        private final int freeParameters;

        /** Gradient array. */
        private final FieldGradient<T>[] gradients;

        /** Partial derivative index. */
        private int derivative;

        /** Simple constructor.
         * @param field field
         * @param freeParameters total number of free parameters in the gradient
         */
        FieldFiller(final Field<T> field, final int freeParameters) {
            this.field          = field;
            this.freeParameters = freeParameters;
            this.gradients      = MathArrays.buildArray(FieldGradientField.getField(field, freeParameters), SIZE);
            this.derivative     = 6;
        }

        /** Manage one driver.
         * @param driver driver to manage
         * @param index index of the driver in the array
         */
        private void manage(final ParameterDriver driver, final int index) {
            if (driver.isSelected()) {
                // this driver should be managed as a variable
                gradients[index] = FieldGradient.variable(freeParameters, derivative,
                                                          field.getZero().newInstance(driver.getValue()));
                ++derivative;
            } else {
                // this driver should be managed as a constant
                gradients[index] = FieldGradient.constant(freeParameters,
                                                          field.getZero().newInstance(driver.getValue()));
            }
        }

    }

}