SmallManeuverAnalyticalModel.java

  1. /* Copyright 2002-2025 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.forces.maneuvers;

  18. import java.util.Arrays;

  19. import org.hipparchus.geometry.euclidean.threed.Vector3D;
  20. import org.hipparchus.util.FastMath;
  21. import org.orekit.frames.Frame;
  22. import org.orekit.orbits.Orbit;
  23. import org.orekit.orbits.OrbitType;
  24. import org.orekit.orbits.PositionAngleType;
  25. import org.orekit.propagation.SpacecraftState;
  26. import org.orekit.propagation.analytical.AdapterPropagator;
  27. import org.orekit.time.AbsoluteDate;
  28. import org.orekit.utils.Constants;

  29. /** Analytical model for small maneuvers.
  30.  * <p>The aim of this model is to compute quickly the effect at date t₁
  31.  * of a small maneuver performed at an earlier date t₀. Both the
  32.  * direct effect of the maneuver and the Jacobian of this effect with respect to
  33.  * maneuver parameters are available.
  34.  * </p>
  35.  * <p>These effect are computed analytically using two Jacobian matrices:
  36.  * <ol>
  37.  *   <li>J₀: Jacobian of Keplerian or equinoctial elements with respect
  38.  *   to Cartesian parameters at date t₀ allows to compute
  39.  *   maneuver effect as a change in orbital elements at maneuver date t₀,</li>
  40.  *   <li>J<sub>1/0</sub>: Jacobian of Keplerian or equinoctial elements
  41.  *   at date t₁ with respect to Keplerian or equinoctial elements
  42.  *   at date t₀ allows to propagate the change in orbital elements
  43.  *   to final date t₁.</li>
  44.  * </ol>
  45.  *
  46.  * <p>
  47.  * The second Jacobian, J<sub>1/0</sub>, is computed using a simple Keplerian
  48.  * model, i.e. it is the identity except for the mean motion row which also includes
  49.  * an off-diagonal element due to semi-major axis change.
  50.  * </p>
  51.  * <p>
  52.  * The orbital elements change at date t₁ can be added to orbital elements
  53.  * extracted from state, and the final elements taking account the changes are then
  54.  * converted back to appropriate type, which may be different from Keplerian or
  55.  * equinoctial elements.
  56.  * </p>
  57.  * <p>
  58.  * Note that this model takes <em>only</em> Keplerian effects into account. This means
  59.  * that using only this class to compute an inclination maneuver in Low Earth Orbit will
  60.  * <em>not</em> change ascending node drift rate despite inclination has changed (the
  61.  * same would be true for a semi-major axis change of course). In order to take this
  62.  * drift into account, an instance of {@link
  63.  * org.orekit.propagation.analytical.J2DifferentialEffect J2DifferentialEffect}
  64.  * must be used together with an instance of this class.
  65.  * </p>
  66.  * @author Luc Maisonobe
  67.  */
  68. public class SmallManeuverAnalyticalModel implements AdapterPropagator.DifferentialEffect {

  69.     /** State at maneuver date (before maneuver occurrence). */
  70.     private final SpacecraftState state0;

  71.     /** Inertial velocity increment. */
  72.     private final Vector3D inertialDV;

  73.     /** Mass change ratio. */
  74.     private final double massRatio;

  75.     /** Type of orbit used for internal Jacobians. */
  76.     private final OrbitType type;

  77.     /** Initial Keplerian (or equinoctial) Jacobian with respect to maneuver. */
  78.     private final double[][] j0;

  79.     /** Time derivative of the initial Keplerian (or equinoctial) Jacobian with respect to maneuver. */
  80.     private double[][] j0Dot;

  81.     /** Mean anomaly change factor. */
  82.     private final double ksi;

  83.     /** Build a maneuver defined in spacecraft frame with default orbit type.
  84.      * @param state0 state at maneuver date, <em>before</em> the maneuver
  85.      * is performed
  86.      * @param dV velocity increment in spacecraft frame
  87.      * @param isp engine specific impulse (s)
  88.      */
  89.     public SmallManeuverAnalyticalModel(final SpacecraftState state0,
  90.                                         final Vector3D dV, final double isp) {
  91.         this(state0, state0.getFrame(),
  92.              state0.getAttitude().getRotation().applyInverseTo(dV),
  93.              isp);
  94.     }

  95.     /** Build a maneuver defined in spacecraft frame.
  96.      * @param state0 state at maneuver date, <em>before</em> the maneuver
  97.      * is performed
  98.      * @param orbitType orbit type to be used later on in Jacobian conversions
  99.      * @param dV velocity increment in spacecraft frame
  100.      * @param isp engine specific impulse (s)
  101.      * @since 12.1 orbit type added as input
  102.      */
  103.     public SmallManeuverAnalyticalModel(final SpacecraftState state0, final OrbitType orbitType,
  104.                                         final Vector3D dV, final double isp) {
  105.         this(state0, orbitType, state0.getFrame(),
  106.              state0.getAttitude().getRotation().applyInverseTo(dV),
  107.              isp);
  108.     }

  109.     /** Build a maneuver defined in user-specified frame.
  110.      * @param state0 state at maneuver date, <em>before</em> the maneuver
  111.      * is performed
  112.      * @param frame frame in which velocity increment is defined
  113.      * @param dV velocity increment in specified frame
  114.      * @param isp engine specific impulse (s)
  115.      */
  116.     public SmallManeuverAnalyticalModel(final SpacecraftState state0, final Frame frame,
  117.                                         final Vector3D dV, final double isp) {
  118.         // No orbit type specified, use equinoctial orbit type if possible, Keplerian if nearly hyperbolic orbits
  119.         this(state0, (state0.getOrbit().getE() < 0.9) ? OrbitType.EQUINOCTIAL : OrbitType.KEPLERIAN, frame, dV, isp);
  120.     }

  121.     /** Build a maneuver defined in user-specified frame.
  122.      * @param state0 state at maneuver date, <em>before</em> the maneuver
  123.      * is performed
  124.      * @param orbitType orbit type to be used later on in Jacobian conversions
  125.      * @param frame frame in which velocity increment is defined
  126.      * @param dV velocity increment in specified frame
  127.      * @param isp engine specific impulse (s)
  128.      * @since 12.1 orbit type added as input
  129.      */
  130.     public SmallManeuverAnalyticalModel(final SpacecraftState state0, final OrbitType orbitType,
  131.                                         final Frame frame, final Vector3D dV, final double isp) {

  132.         this.state0    = state0;
  133.         this.massRatio = FastMath.exp(-dV.getNorm() / (Constants.G0_STANDARD_GRAVITY * isp));
  134.         this.type = orbitType;

  135.         // compute initial Jacobian
  136.         final double[][] fullJacobian = new double[6][6];
  137.         j0 = new double[6][3];
  138.         final Orbit orbit0 = orbitType.convertType(state0.getOrbit());
  139.         orbit0.getJacobianWrtCartesian(PositionAngleType.MEAN, fullJacobian);
  140.         for (int i = 0; i < j0.length; ++i) {
  141.             System.arraycopy(fullJacobian[i], 3, j0[i], 0, 3);
  142.         }

  143.         // use lazy evaluation for j0Dot, as it is used only when Jacobians are evaluated
  144.         j0Dot = null;

  145.         // compute maneuver effect on Keplerian (or equinoctial) elements
  146.         inertialDV = frame.getStaticTransformTo(state0.getFrame(), state0.getDate())
  147.                         .transformVector(dV);

  148.         // compute mean anomaly change: dM(t1) = dM(t0) + ksi * da * (t1 - t0)
  149.         final Orbit orbit = state0.getOrbit();
  150.         final double mu = orbit.getMu();
  151.         final double a  = orbit.getA();
  152.         ksi = -1.5 * FastMath.sqrt(mu / a) / (a * a);

  153.     }

  154.     /** Get the date of the maneuver.
  155.      * @return date of the maneuver
  156.      */
  157.     public AbsoluteDate getDate() {
  158.         return state0.getDate();
  159.     }

  160.     /** Get the inertial velocity increment of the maneuver.
  161.      * @return velocity increment in a state-dependent inertial frame
  162.      * @see #getInertialFrame()
  163.      */
  164.     public Vector3D getInertialDV() {
  165.         return inertialDV;
  166.     }

  167.     /** Get the inertial frame in which the velocity increment is defined.
  168.      * @return inertial frame in which the velocity increment is defined
  169.      * @see #getInertialDV()
  170.      */
  171.     public Frame getInertialFrame() {
  172.         return state0.getFrame();
  173.     }

  174.     /** Compute the effect of the maneuver on an orbit.
  175.      * @param orbit1 original orbit at t₁, without maneuver
  176.      * @return orbit at t₁, taking the maneuver
  177.      * into account if t₁ &gt; t₀
  178.      * @see #apply(SpacecraftState)
  179.      * @see #getJacobian(Orbit, PositionAngleType, double[][])
  180.      */
  181.     public Orbit apply(final Orbit orbit1) {

  182.         if (orbit1.getDate().compareTo(state0.getDate()) <= 0) {
  183.             // the maneuver has not occurred yet, don't change anything
  184.             return orbit1;
  185.         }

  186.         return orbit1.getType().convertType(updateOrbit(orbit1));

  187.     }

  188.     /** Compute the effect of the maneuver on a spacecraft state.
  189.      * @param state1 original spacecraft state at t₁,
  190.      * without maneuver
  191.      * @return spacecraft state at t₁, taking the maneuver
  192.      * into account if t₁ &gt; t₀
  193.      * @see #apply(Orbit)
  194.      * @see #getJacobian(Orbit, PositionAngleType, double[][])
  195.      */
  196.     public SpacecraftState apply(final SpacecraftState state1) {

  197.         if (state1.getDate().compareTo(state0.getDate()) <= 0) {
  198.             // the maneuver has not occurred yet, don't change anything
  199.             return state1;
  200.         }

  201.         return new SpacecraftState(state1.getOrbit().getType().convertType(updateOrbit(state1.getOrbit())),
  202.                                    state1.getAttitude()).withMass(updateMass(state1.getMass()));

  203.     }

  204.     /** Compute the effect of the maneuver on an orbit.
  205.      * @param orbit1 original orbit at t₁, without maneuver
  206.      * @return orbit at t₁, always taking the maneuver into account, always in the internal type
  207.      */
  208.     private Orbit updateOrbit(final Orbit orbit1) {

  209.         // compute maneuver effect
  210.         final double dt = orbit1.getDate().durationFrom(state0.getDate());
  211.         final double x  = inertialDV.getX();
  212.         final double y  = inertialDV.getY();
  213.         final double z  = inertialDV.getZ();
  214.         final double[] delta = new double[6];
  215.         for (int i = 0; i < delta.length; ++i) {
  216.             delta[i] = j0[i][0] * x + j0[i][1] * y + j0[i][2] * z;
  217.         }
  218.         delta[5] += ksi * delta[0] * dt;

  219.         // convert current orbital state to Keplerian or equinoctial elements
  220.         final double[] parameters    = new double[6];
  221.         type.mapOrbitToArray(type.convertType(orbit1), PositionAngleType.MEAN, parameters, null);
  222.         for (int i = 0; i < delta.length; ++i) {
  223.             parameters[i] += delta[i];
  224.         }

  225.         // build updated orbit as Keplerian or equinoctial elements
  226.         return type.mapArrayToOrbit(parameters, null, PositionAngleType.MEAN,
  227.                                     orbit1.getDate(), orbit1.getMu(), orbit1.getFrame());

  228.     }

  229.     /** Compute the Jacobian of the orbit with respect to maneuver parameters.
  230.      * <p>
  231.      * The Jacobian matrix is a 6x4 matrix. Element jacobian[i][j] corresponds to
  232.      * the partial derivative of orbital parameter i with respect to maneuver
  233.      * parameter j. The rows order is the same order as used in {@link
  234.      * Orbit#getJacobianWrtCartesian(PositionAngleType, double[][]) Orbit.getJacobianWrtCartesian}
  235.      * method. Columns (0, 1, 2) correspond to the velocity increment coordinates
  236.      * (ΔV<sub>x</sub>, ΔV<sub>y</sub>, ΔV<sub>z</sub>) in the
  237.      * inertial frame returned by {@link #getInertialFrame()}, and column 3
  238.      * corresponds to the maneuver date t₀.
  239.      * </p>
  240.      * @param orbit1 original orbit at t₁, without maneuver
  241.      * @param positionAngleType type of the position angle to use
  242.      * @param jacobian placeholder 6x4 (or larger) matrix to be filled with the Jacobian, if matrix
  243.      * is larger than 6x4, only the 6x4 upper left corner will be modified
  244.      * @see #apply(Orbit)
  245.      */
  246.     public void getJacobian(final Orbit orbit1, final PositionAngleType positionAngleType,
  247.                             final double[][] jacobian) {

  248.         final double dt = orbit1.getDate().durationFrom(state0.getDate());
  249.         if (dt < 0) {
  250.             // the maneuver has not occurred yet, Jacobian is null
  251.             for (int i = 0; i < 6; ++i) {
  252.                 Arrays.fill(jacobian[i], 0, 4, 0.0);
  253.             }
  254.             return;
  255.         }

  256.         // derivatives of Keplerian/equinoctial elements with respect to velocity increment
  257.         final double x  = inertialDV.getX();
  258.         final double y  = inertialDV.getY();
  259.         final double z  = inertialDV.getZ();
  260.         for (int i = 0; i < 6; ++i) {
  261.             System.arraycopy(j0[i], 0, jacobian[i], 0, 3);
  262.         }
  263.         for (int j = 0; j < 3; ++j) {
  264.             jacobian[5][j] += ksi * dt * j0[0][j];
  265.         }

  266.         // derivatives of Keplerian/equinoctial elements with respect to date
  267.         evaluateJ0Dot();
  268.         for (int i = 0; i < 6; ++i) {
  269.             jacobian[i][3] = j0Dot[i][0] * x + j0Dot[i][1] * y + j0Dot[i][2] * z;
  270.         }
  271.         final double da = j0[0][0] * x + j0[0][1] * y + j0[0][2] * z;
  272.         jacobian[5][3] += ksi * (jacobian[0][3] * dt - da);

  273.         if (orbit1.getType() != type || positionAngleType != PositionAngleType.MEAN) {

  274.             // convert to derivatives of Cartesian parameters
  275.             final double[][] j2         = new double[6][6];
  276.             final double[][] pvJacobian = new double[6][4];
  277.             final Orbit updated         = updateOrbit(orbit1);
  278.             updated.getJacobianWrtParameters(PositionAngleType.MEAN, j2);
  279.             for (int i = 0; i < 6; ++i) {
  280.                 for (int j = 0; j < 4; ++j) {
  281.                     pvJacobian[i][j] = j2[i][0] * jacobian[0][j] + j2[i][1] * jacobian[1][j] +
  282.                                     j2[i][2] * jacobian[2][j] + j2[i][3] * jacobian[3][j] +
  283.                                     j2[i][4] * jacobian[4][j] + j2[i][5] * jacobian[5][j];
  284.                 }
  285.             }

  286.             // convert to derivatives of specified parameters
  287.             final double[][] j3 = new double[6][6];
  288.             orbit1.getType().convertType(updated).getJacobianWrtCartesian(positionAngleType, j3);
  289.             for (int j = 0; j < 4; ++j) {
  290.                 for (int i = 0; i < 6; ++i) {
  291.                     jacobian[i][j] = j3[i][0] * pvJacobian[0][j] + j3[i][1] * pvJacobian[1][j] +
  292.                                     j3[i][2] * pvJacobian[2][j] + j3[i][3] * pvJacobian[3][j] +
  293.                                     j3[i][4] * pvJacobian[4][j] + j3[i][5] * pvJacobian[5][j];
  294.                 }
  295.             }

  296.         }

  297.     }

  298.     /** Lazy evaluation of the initial Jacobian time derivative.
  299.      */
  300.     private void evaluateJ0Dot() {

  301.         if (j0Dot == null) {

  302.             j0Dot = new double[6][3];
  303.             final double dt = 1.0e-5 / state0.getOrbit().getKeplerianMeanMotion();
  304.             final Orbit orbit = type.convertType(state0.getOrbit());

  305.             // compute shifted Jacobians
  306.             final double[][] j0m1 = new double[6][6];
  307.             orbit.shiftedBy(-1 * dt).getJacobianWrtCartesian(PositionAngleType.MEAN, j0m1);
  308.             final double[][] j0p1 = new double[6][6];
  309.             orbit.shiftedBy(+1 * dt).getJacobianWrtCartesian(PositionAngleType.MEAN, j0p1);

  310.             // evaluate derivative by finite differences
  311.             for (int i = 0; i < j0Dot.length; ++i) {
  312.                 final double[] m1Row    = j0m1[i];
  313.                 final double[] p1Row    = j0p1[i];
  314.                 final double[] j0DotRow = j0Dot[i];
  315.                 for (int j = 0; j < 3; ++j) {
  316.                     j0DotRow[j] = (p1Row[j + 3] - m1Row[j + 3]) / (2 * dt);
  317.                 }
  318.             }

  319.         }

  320.     }

  321.     /** Update a spacecraft mass due to maneuver.
  322.      * @param mass masse before maneuver
  323.      * @return mass after maneuver
  324.      */
  325.     public double updateMass(final double mass) {
  326.         return massRatio * mass;
  327.     }

  328. }