DerivativeStateUtils.java

/* Copyright 2022-2025 Romain Serra
 * 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.utils;

import org.hipparchus.analysis.differentiation.Gradient;
import org.hipparchus.analysis.differentiation.GradientField;
import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
import org.hipparchus.linear.RealMatrix;
import org.orekit.attitudes.Attitude;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.attitudes.FieldAttitude;
import org.orekit.frames.Frame;
import org.orekit.orbits.FieldOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.PositionAngleBased;
import org.orekit.orbits.PositionAngleType;
import org.orekit.propagation.FieldSpacecraftState;
import org.orekit.propagation.SpacecraftState;
import org.orekit.time.FieldAbsoluteDate;

/**
 * Utility class used to convert state vectors in Taylor differential algebra.
 *
 * @author Romain Serra
 * @since 13.1
 * @see Gradient
 */
public class DerivativeStateUtils {

    /** Private constructor. */
    private DerivativeStateUtils() {
        // Empty constructor
    }

    /**
     * Method creating a Gradient version of the input state from a given transition matrix.
     * The number of independent variables equals the number of columns.
     * The number of rows tells how many state variables are considered to be the dependent variables in the Taylor differential algebra.
     * If the number of state variables is greater than 6, mass will be considered one.
     * Additional data and derivatives are ignored.
     * @param state full state
     * @param partialDerivatives Jacobian matrix of state variables to consider as dependent ones, w.r.t. unknown parameters
     * @param attitudeProvider provider to recompute attitude, can be null
     * @return fielded state
     * @see FieldSpacecraftState
     * @see SpacecraftState
     */
    public static FieldSpacecraftState<Gradient> buildSpacecraftStateTransitionGradient(final SpacecraftState state,
                                                                                        final RealMatrix partialDerivatives,
                                                                                        final AttitudeProvider attitudeProvider) {
        final int freeParameters = partialDerivatives.getColumnDimension();
        final GradientField field = GradientField.getField(freeParameters);
        final int j = partialDerivatives.getRowDimension();
        final double mass = state.getMass();
        final Gradient fieldMass = (j >= 7) ? new Gradient(mass, partialDerivatives.getRow(6)) : Gradient.constant(freeParameters, mass);
        if (state.isOrbitDefined()) {
            final double[] stateValues = new double[6];
            final double[] stateDerivatives = stateValues.clone();
            final Orbit orbit = state.getOrbit();
            final PositionAngleType positionAngleType = extractPositionAngleType(orbit);
            orbit.getType().mapOrbitToArray(orbit, positionAngleType, stateValues, stateDerivatives);
            final Gradient[] stateVariables = new Gradient[stateValues.length];
            for (int i = 0; i < stateVariables.length; i++) {
                stateVariables[i] = (i < freeParameters) ? new Gradient(stateValues[i], partialDerivatives.getRow(i)) :
                        Gradient.constant(freeParameters, stateValues[i]);
            }
            final FieldOrbit<Gradient> fieldOrbit = buildFieldOrbit(field, orbit, stateVariables, stateDerivatives);
            return buildFieldStateFromFieldOrbit(fieldOrbit, fieldMass, state.getAttitude(), attitudeProvider);
        } else {
            // state is not orbit defined
            final AbsolutePVCoordinates coordinates = state.getAbsPVA();
            final double[] constants = buildPVArray(coordinates.getPVCoordinates());
            final Gradient[] stateVariables = new Gradient[constants.length];
            for (int i = 0; i < stateVariables.length; i++) {
                stateVariables[i] = (i < freeParameters) ? new Gradient(constants[i], partialDerivatives.getRow(i)) :
                        Gradient.constant(freeParameters, constants[i]);
            }
            final FieldVector3D<Gradient> position = new FieldVector3D<>(stateVariables[0], stateVariables[1],
                    stateVariables[2]);
            final FieldVector3D<Gradient> velocity = new FieldVector3D<>(stateVariables[3], stateVariables[4],
                    stateVariables[5]);
            final FieldAbsolutePVCoordinates<Gradient> fieldPV = buildFieldAbsolutePV(position, velocity, coordinates);
            return buildFieldStateFromFieldPV(fieldPV, fieldMass, state.getAttitude(), attitudeProvider);
        }
    }

    /**
     * Method creating a Gradient version of the input state, using the state vector as the independent variables of a first-
     * order Taylor algebra. If the number of variables is greater than 6, mass will be considered one.
     * Additional data and derivatives are ignored.
     * @param field gradient field
     * @param state full state
     * @param attitudeProvider provider to recompute attitude, can be null
     * @return fielded state
     * @see FieldSpacecraftState
     * @see SpacecraftState
     */
    public static FieldSpacecraftState<Gradient> buildSpacecraftStateGradient(final GradientField field,
                                                                              final SpacecraftState state,
                                                                              final AttitudeProvider attitudeProvider) {
        final int freeParameters = field.getZero().getFreeParameters();
        final double mass = state.getMass();
        final Gradient fieldMass = (freeParameters >= 7) ? Gradient.variable(freeParameters, 6, mass) :
                Gradient.constant(freeParameters, mass);
        final Attitude oldAttitude = state.getAttitude();
        if (state.isOrbitDefined()) {
            final FieldOrbit<Gradient> fieldOrbit = buildOrbitGradient(field, state.getOrbit());
            return buildFieldStateFromFieldOrbit(fieldOrbit, fieldMass, oldAttitude, attitudeProvider);
        } else {
            // state is not orbit defined
            final FieldAbsolutePVCoordinates<Gradient> fieldPV = buildAbsolutePVGradient(field, state.getAbsPVA());
            return buildFieldStateFromFieldPV(fieldPV, fieldMass, oldAttitude, attitudeProvider);
        }
    }

    /**
     * Add or recompute attitude to fill in full state from orbit.
     * @param fieldOrbit orbit
     * @param fieldMass mass
     * @param attitude constant attitude
     * @param attitudeProvider provider to recompute attitude, can be null
     * @return state
     */
    private static FieldSpacecraftState<Gradient> buildFieldStateFromFieldOrbit(final FieldOrbit<Gradient> fieldOrbit,
                                                                                final Gradient fieldMass, final Attitude attitude,
                                                                                final AttitudeProvider attitudeProvider) {
        final FieldAttitude<Gradient> fieldAttitude = (attitudeProvider == null) ?
                new FieldAttitude<>(fieldMass.getField(), attitude) :
                attitudeProvider.getAttitude(fieldOrbit, fieldOrbit.getDate(), fieldOrbit.getFrame());
        return new FieldSpacecraftState<>(fieldOrbit, fieldAttitude).withMass(fieldMass);
    }

    /**
     * Add or recompute attitude to fill in full state.
     * @param fieldPV coordinates
     * @param fieldMass mass
     * @param attitude constant attitude
     * @param attitudeProvider provider to recompute attitude, can be null
     * @return state
     */
    private static FieldSpacecraftState<Gradient> buildFieldStateFromFieldPV(final FieldAbsolutePVCoordinates<Gradient> fieldPV,
                                                                             final Gradient fieldMass, final Attitude attitude,
                                                                             final AttitudeProvider attitudeProvider) {
        final FieldAttitude<Gradient> fieldAttitude = (attitudeProvider == null) ?
                new FieldAttitude<>(fieldMass.getField(), attitude) :
                attitudeProvider.getAttitude(fieldPV, fieldPV.getDate(), fieldPV.getFrame());
        return new FieldSpacecraftState<>(fieldPV, fieldAttitude).withMass(fieldMass);
    }

    /**
     * Method creating a Gradient version of the input orbit, using the state vector as the independent variables of a first-
     * order Taylor algebra. If the number of variables is greater than 6, mass will be considered one.
     * @param field gradient field
     * @param orbit orbit
     * @return fielded orbit
     * @see org.orekit.orbits.FieldOrbit
     * @see org.orekit.orbits.Orbit
     */
    public static FieldOrbit<Gradient> buildOrbitGradient(final GradientField field, final Orbit orbit) {
        final int freeParameters = field.getZero().getFreeParameters();
        final double[] stateValues = new double[6];
        final double[] stateDerivatives = stateValues.clone();
        final PositionAngleType positionAngleType = extractPositionAngleType(orbit);
        orbit.getType().mapOrbitToArray(orbit, positionAngleType, stateValues, stateDerivatives);
        final Gradient[] stateVariables = new Gradient[stateValues.length];
        for (int i = 0; i < stateVariables.length; i++) {
            stateVariables[i] = (i < freeParameters) ? Gradient.variable(freeParameters, i, stateValues[i]) :
                    Gradient.constant(freeParameters, stateValues[i]);
        }
        return buildFieldOrbit(field, orbit, stateVariables, stateDerivatives);
    }

    /**
     * Create the field orbit.
     * @param field gradient field
     * @param orbit orbit
     * @param stateVariables state in Taylor differential algebra
     * @param stateDerivatives state derivatives
     * @return orbit in Taylor differential algebra
     */
    private static FieldOrbit<Gradient> buildFieldOrbit(final GradientField field, final Orbit orbit,
                                                        final Gradient[] stateVariables, final double[] stateDerivatives) {
        final FieldAbsoluteDate<Gradient> date = new FieldAbsoluteDate<>(field, orbit.getDate());
        final int freeParameters = field.getZero().getFreeParameters();
        final Gradient mu = Gradient.constant(freeParameters, orbit.getMu());
        final Gradient[] fieldStateDerivatives = new Gradient[stateVariables.length];
        for (int i = 0; i < stateVariables.length; i++) {
            fieldStateDerivatives[i] = Gradient.constant(freeParameters, stateDerivatives[i]);
        }
        final PositionAngleType positionAngleType = extractPositionAngleType(orbit);
        final Frame frame = orbit.getFrame();
        return orbit.getType().mapArrayToOrbit(stateVariables, fieldStateDerivatives, positionAngleType, date, mu, frame);
    }

    /**
     * Extract position angle type.
     * @param orbit orbit
     * @return angle type
     */
    private static PositionAngleType extractPositionAngleType(final Orbit orbit) {
        if (orbit instanceof PositionAngleBased<?>) {
            final PositionAngleBased<?> positionAngleBased = (PositionAngleBased<?>) orbit;
            return positionAngleBased.getCachedPositionAngleType();
        }
        return null;
    }

    /**
     * Method creating a Gradient version of the input coordinates, using the state vector as the independent variables of a first-
     * order Taylor algebra. If the number of variables is greater than 6, mass will be considered one.
     * @param field gradient field
     * @param coordinates absolute coordinates
     * @return fielded coordinates
     * @see AbsolutePVCoordinates
     * @see FieldAbsolutePVCoordinates
     */
    public static FieldAbsolutePVCoordinates<Gradient> buildAbsolutePVGradient(final GradientField field,
                                                                               final AbsolutePVCoordinates coordinates) {
        final int freeParameters = field.getZero().getFreeParameters();
        final double[] constants = buildPVArray(coordinates.getPVCoordinates());
        final Gradient[] stateVariables = new Gradient[constants.length];
        for (int i = 0; i < stateVariables.length; i++) {
            stateVariables[i] = (i < freeParameters) ? Gradient.variable(freeParameters, i, constants[i]) :
                    Gradient.constant(freeParameters, constants[i]);
        }
        final FieldVector3D<Gradient> position = new FieldVector3D<>(stateVariables[0], stateVariables[1],
                stateVariables[2]);
        final FieldVector3D<Gradient> velocity = new FieldVector3D<>(stateVariables[3], stateVariables[4],
                stateVariables[5]);
        return buildFieldAbsolutePV(position, velocity, coordinates);
    }

    /**
     * Build array from position-velocity.
     * @param pvCoordinates coordinates
     * @return array
     */
    private static double[] buildPVArray(final PVCoordinates pvCoordinates) {
        final double[] constants = new double[6];
        System.arraycopy(pvCoordinates.getPosition().toArray(), 0, constants, 0, 3);
        System.arraycopy(pvCoordinates.getVelocity().toArray(), 0, constants, 3, 3);
        return constants;
    }

    /**
     * Create field position-velocity.
     * @param fieldPosition position in Taylor differential algebra
     * @param fieldVelocity velocity in Taylor differential algebra
     * @param coordinates coordinates
     * @return fielded position-velocity
     */
    private static FieldAbsolutePVCoordinates<Gradient> buildFieldAbsolutePV(final FieldVector3D<Gradient> fieldPosition,
                                                                             final FieldVector3D<Gradient> fieldVelocity,
                                                                             final AbsolutePVCoordinates coordinates) {
        final GradientField field = fieldPosition.getX().getField();
        final FieldVector3D<Gradient> acceleration = new FieldVector3D<>(field, coordinates.getAcceleration());
        final FieldAbsoluteDate<Gradient> date = new FieldAbsoluteDate<>(field, coordinates.getDate());
        return new FieldAbsolutePVCoordinates<>(coordinates.getFrame(), date, fieldPosition, fieldVelocity, acceleration);
    }
}