FieldSpacecraftState.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.propagation;


  18. import org.hipparchus.CalculusFieldElement;
  19. import org.hipparchus.Field;
  20. import org.hipparchus.exception.LocalizedCoreFormats;
  21. import org.hipparchus.exception.MathIllegalArgumentException;
  22. import org.hipparchus.exception.MathIllegalStateException;
  23. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  24. import org.hipparchus.util.MathArrays;
  25. import org.orekit.attitudes.FieldAttitude;
  26. import org.orekit.errors.OrekitException;
  27. import org.orekit.errors.OrekitIllegalArgumentException;
  28. import org.orekit.errors.OrekitIllegalStateException;
  29. import org.orekit.errors.OrekitMessages;
  30. import org.orekit.frames.FieldStaticTransform;
  31. import org.orekit.frames.FieldTransform;
  32. import org.orekit.frames.Frame;
  33. import org.orekit.orbits.FieldOrbit;
  34. import org.orekit.orbits.Orbit;
  35. import org.orekit.time.FieldAbsoluteDate;
  36. import org.orekit.time.FieldTimeShiftable;
  37. import org.orekit.time.FieldTimeStamped;
  38. import org.orekit.utils.DataDictionary;
  39. import org.orekit.utils.DoubleArrayDictionary;
  40. import org.orekit.utils.FieldAbsolutePVCoordinates;
  41. import org.orekit.utils.FieldArrayDictionary;
  42. import org.orekit.utils.FieldDataDictionary;
  43. import org.orekit.utils.FieldPVCoordinates;
  44. import org.orekit.utils.TimeStampedFieldPVCoordinates;
  45. import org.orekit.utils.TimeStampedPVCoordinates;

  46. /** This class is the representation of a complete state holding orbit, attitude
  47.  * and mass information at a given date, meant primarily for propagation.
  48.  *
  49.  * <p>It contains an {@link FieldOrbit}, or a {@link FieldAbsolutePVCoordinates} if there
  50.  * is no definite central body, plus the current mass and attitude at the intrinsic
  51.  * {@link FieldAbsoluteDate}. Quantities are guaranteed to be consistent in terms
  52.  * of date and reference frame. The spacecraft state may also contain additional
  53.  * states, which are simply named double arrays which can hold any user-defined
  54.  * data.
  55.  * </p>
  56.  * <p>
  57.  * The state can be slightly shifted to close dates. This actual shift varies
  58.  * between {@link FieldOrbit} and {@link FieldAbsolutePVCoordinates}.
  59.  * For attitude it is a linear extrapolation taking the spin rate into account
  60.  * and no mass change. It is <em>not</em> intended as a replacement for proper
  61.  * orbit and attitude propagation but should be sufficient for either small
  62.  * time shifts or coarse accuracy.
  63.  * </p>
  64.  * <p>
  65.  * The instance {@code FieldSpacecraftState} is guaranteed to be immutable.
  66.  * </p>
  67.  * @see org.orekit.propagation.numerical.NumericalPropagator
  68.  * @see SpacecraftState
  69.  * @author Fabien Maussion
  70.  * @author V&eacute;ronique Pommier-Maurussane
  71.  * @author Luc Maisonobe
  72.  * @author Vincent Mouraux
  73.  * @param <T> type of the field elements
  74.  */
  75. public class FieldSpacecraftState <T extends CalculusFieldElement<T>>
  76.     implements FieldTimeStamped<T>, FieldTimeShiftable<FieldSpacecraftState<T>, T> {

  77.     /** Default mass. */
  78.     private static final double DEFAULT_MASS = 1000.0;

  79.     /**
  80.      * tolerance on date comparison in {@link #checkConsistency(FieldOrbit, FieldAttitude)}. 100 ns
  81.      * corresponds to sub-mm accuracy at LEO orbital velocities.
  82.      */
  83.     private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;

  84.     /** Orbital state. */
  85.     private final FieldOrbit<T> orbit;

  86.     /** Trajectory state, when it is not an orbit. */
  87.     private final FieldAbsolutePVCoordinates<T> absPva;

  88.     /** FieldAttitude<T>. */
  89.     private final FieldAttitude<T> attitude;

  90.     /** Current mass (kg). */
  91.     private final T mass;

  92.     /** Additional data, can be any object (String, T[], etc.). */
  93.     private final FieldDataDictionary<T> additional;

  94.     /** Additional states derivatives.
  95.      * @since 11.1
  96.      */
  97.     private final FieldArrayDictionary<T> additionalDot;

  98.     /** Build a spacecraft state from orbit only.
  99.      * <p>FieldAttitude and mass are set to unspecified non-null arbitrary values.</p>
  100.      * @param orbit the orbit
  101.      */
  102.     public FieldSpacecraftState(final FieldOrbit<T> orbit) {
  103.         this(orbit, SpacecraftState.getDefaultAttitudeProvider(orbit.getFrame())
  104.                         .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  105.              orbit.getA().getField().getZero().newInstance(DEFAULT_MASS), null, null);
  106.     }

  107.     /** Build a spacecraft state from orbit and attitude. Kept for performance.
  108.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  109.      * @param orbit the orbit
  110.      * @param attitude attitude
  111.      * @exception IllegalArgumentException if orbit and attitude dates
  112.      * or frames are not equal
  113.      */
  114.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude)
  115.         throws IllegalArgumentException {
  116.         this(orbit, attitude, orbit.getA().getField().getZero().newInstance(DEFAULT_MASS), null, null);
  117.     }

  118.     /** Create a new instance from orbit and mass.
  119.      * <p>FieldAttitude law is set to an unspecified default attitude.</p>
  120.      * @param orbit the orbit
  121.      * @param mass the mass (kg)
  122.      * @deprecated since 13.0, use withXXX
  123.      */
  124.     @Deprecated
  125.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final T mass) {
  126.         this(orbit, SpacecraftState.getDefaultAttitudeProvider(orbit.getFrame())
  127.                         .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
  128.              mass, null, null);
  129.     }

  130.     /** Build a spacecraft state from orbit, attitude and mass.
  131.      * @param orbit the orbit
  132.      * @param attitude attitude
  133.      * @param mass the mass (kg)
  134.      * @exception IllegalArgumentException if orbit and attitude dates
  135.      * or frames are not equal
  136.      * @deprecated since 13.0, use withXXX
  137.      */
  138.     @Deprecated
  139.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final T mass)
  140.         throws IllegalArgumentException {
  141.         this(orbit, attitude, mass, null, null);
  142.     }

  143.     /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives.
  144.      * @param orbit the orbit
  145.      * @param attitude attitude
  146.      * @param mass the mass (kg)
  147.      * @param additional additional states (may be null if no additional states are available)
  148.      * @param additionalDot additional states derivatives(may be null if no additional states derivative sare available)
  149.      * @exception IllegalArgumentException if orbit and attitude dates
  150.      * or frames are not equal
  151.      * @since 11.1
  152.      */
  153.     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final T mass,
  154.                                 final FieldDataDictionary<T> additional,
  155.                                 final FieldArrayDictionary<T> additionalDot)
  156.         throws IllegalArgumentException {
  157.         checkConsistency(orbit, attitude);
  158.         this.orbit      = orbit;
  159.         this.attitude   = attitude;
  160.         this.mass       = mass;
  161.         this.absPva     = null;
  162.         this.additional = additional == null ? new FieldDataDictionary<>(orbit.getDate().getField()) : new FieldDataDictionary<>(additional);
  163.         this.additionalDot = additionalDot == null ? new FieldArrayDictionary<>(orbit.getDate().getField()) : new FieldArrayDictionary<>(additionalDot);

  164.     }

  165.     /** Convert a {@link FieldSpacecraftState}.
  166.      * @param field field to which the elements belong
  167.      * @param state state to convert
  168.      */
  169.     public FieldSpacecraftState(final Field<T> field, final SpacecraftState state) {

  170.         if (state.isOrbitDefined()) {

  171.             final Orbit nonFieldOrbit = state.getOrbit();
  172.             this.orbit    = nonFieldOrbit.getType().convertToFieldOrbit(field, nonFieldOrbit);
  173.             this.absPva   = null;

  174.         } else {
  175.             final TimeStampedPVCoordinates tspva = state.getPVCoordinates();
  176.             final FieldVector3D<T> position = new FieldVector3D<>(field, tspva.getPosition());
  177.             final FieldVector3D<T> velocity = new FieldVector3D<>(field, tspva.getVelocity());
  178.             final FieldVector3D<T> acceleration = new FieldVector3D<>(field, tspva.getAcceleration());
  179.             final FieldPVCoordinates<T> pva = new FieldPVCoordinates<>(position, velocity, acceleration);
  180.             final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, state.getDate());
  181.             this.orbit  = null;
  182.             this.absPva = new FieldAbsolutePVCoordinates<>(state.getFrame(), dateF, pva);
  183.         }

  184.         this.attitude = new FieldAttitude<>(field, state.getAttitude());
  185.         this.mass     = field.getZero().newInstance(state.getMass());

  186.         final DataDictionary additionalD = state.getAdditionalDataValues();
  187.         if (additionalD.size() == 0) {
  188.             this.additional = new FieldDataDictionary<>(field);
  189.         } else {
  190.             this.additional = new FieldDataDictionary<>(field, additionalD.size());
  191.             for (final DataDictionary.Entry entry : additionalD.getData()) {
  192.                 if (entry.getValue() instanceof double[]) {
  193.                     final double[] realValues = (double[]) entry.getValue();
  194.                     final T[] fieldArray = MathArrays.buildArray(field, realValues.length);
  195.                     for (int i = 0; i < fieldArray.length; i++) {
  196.                         fieldArray[i] = field.getZero().add(realValues[i]);
  197.                     }
  198.                     this.additional.put(entry.getKey(), fieldArray);
  199.                 } else {
  200.                     this.additional.put(entry.getKey(), entry.getValue());
  201.                 }
  202.             }
  203.         }
  204.         final DoubleArrayDictionary additionalDotD = state.getAdditionalStatesDerivatives();
  205.         if (additionalDotD.size() == 0) {
  206.             this.additionalDot = new FieldArrayDictionary<>(field);
  207.         } else {
  208.             this.additionalDot = new FieldArrayDictionary<>(field, additionalDotD.size());
  209.             for (final DoubleArrayDictionary.Entry entry : additionalDotD.getData()) {
  210.                 this.additionalDot.put(entry.getKey(), entry.getValue());
  211.             }
  212.         }

  213.     }

  214.     /** Build a spacecraft state from orbit only.
  215.      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
  216.      * @param absPva position-velocity-acceleration
  217.      */
  218.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva) {
  219.         this(absPva,
  220.              SpacecraftState.getDefaultAttitudeProvider(absPva.getFrame()).
  221.                      getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  222.              absPva.getDate().getField().getZero().newInstance(DEFAULT_MASS), null, null);
  223.     }

  224.     /** Build a spacecraft state from orbit and attitude. Kept for performance.
  225.      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
  226.      * @param absPva position-velocity-acceleration
  227.      * @param attitude attitude
  228.      * @exception IllegalArgumentException if orbit and attitude dates
  229.      * or frames are not equal
  230.      */
  231.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude)
  232.         throws IllegalArgumentException {
  233.         this(absPva, attitude, absPva.getDate().getField().getZero().newInstance(DEFAULT_MASS), null, null);
  234.     }

  235.     /** Create a new instance from orbit and mass.
  236.      * <p>Attitude law is set to an unspecified default attitude.</p>
  237.      * @param absPva position-velocity-acceleration
  238.      * @param mass the mass (kg)
  239.      * @deprecated since 13.0, use withXXX
  240.      */
  241.     @Deprecated
  242.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final T mass) {
  243.         this(absPva, SpacecraftState.getDefaultAttitudeProvider(absPva.getFrame())
  244.                         .getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
  245.              mass, null, null);
  246.     }

  247.     /** Build a spacecraft state from orbit, attitude and mass.
  248.      * @param absPva position-velocity-acceleration
  249.      * @param attitude attitude
  250.      * @param mass the mass (kg)
  251.      * @exception IllegalArgumentException if orbit and attitude dates
  252.      * or frames are not equal
  253.      * @deprecated since 13.0, use withXXX
  254.      */
  255.     @Deprecated
  256.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude, final T mass)
  257.         throws IllegalArgumentException {
  258.         this(absPva, attitude, mass, null, null);
  259.     }

  260.     /** Build a spacecraft state from orbit, attitude and mass.
  261.      * @param absPva position-velocity-acceleration
  262.      * @param attitude attitude
  263.      * @param mass the mass (kg)
  264.      * @param additional additional states (may be null if no additional states are available)
  265.      * @param additionalDot additional states derivatives(may be null if no additional states derivatives are available)
  266.      * @exception IllegalArgumentException if orbit and attitude dates
  267.      * or frames are not equal
  268.      * @since 11.1
  269.      */
  270.     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude, final T mass,
  271.                                 final FieldDataDictionary<T> additional, final FieldArrayDictionary<T> additionalDot)
  272.         throws IllegalArgumentException {
  273.         checkConsistency(absPva, attitude);
  274.         this.orbit      = null;
  275.         this.absPva     = absPva;
  276.         this.attitude   = attitude;
  277.         this.mass       = mass;
  278.         this.additional = additional == null ? new FieldDataDictionary<>(absPva.getDate().getField()) : new FieldDataDictionary<>(additional);
  279.         this.additionalDot = additionalDot == null ? new FieldArrayDictionary<>(absPva.getDate().getField()) : new FieldArrayDictionary<>(additionalDot);
  280.     }

  281.     /**
  282.      * Create a new instance with input mass.
  283.      * @param newMass mass
  284.      * @return new state
  285.      * @since 13.0
  286.      */
  287.     public FieldSpacecraftState<T> withMass(final T newMass) {
  288.         if (isOrbitDefined()) {
  289.             return new FieldSpacecraftState<>(orbit, attitude, newMass, additional, additionalDot);
  290.         } else {
  291.             return new FieldSpacecraftState<>(absPva, attitude, newMass, additional, additionalDot);
  292.         }
  293.     }

  294.     /**
  295.      * Create a new instance with input attitude.
  296.      * @param newAttitude attitude
  297.      * @return new state
  298.      * @since 13.0
  299.      */
  300.     public FieldSpacecraftState<T> withAttitude(final FieldAttitude<T> newAttitude) {
  301.         if (isOrbitDefined()) {
  302.             return new FieldSpacecraftState<>(orbit, newAttitude, mass, additional, additionalDot);
  303.         } else {
  304.             return new FieldSpacecraftState<>(absPva, newAttitude, mass, additional, additionalDot);
  305.         }
  306.     }

  307.     /**
  308.      * Create a new instance with input additional data.
  309.      * @param newAdditional data
  310.      * @return new state
  311.      * @since 13.0
  312.      */
  313.     public FieldSpacecraftState<T> withAdditionalData(final FieldDataDictionary<T> newAdditional) {
  314.         if (isOrbitDefined()) {
  315.             return new FieldSpacecraftState<>(orbit, attitude, mass, newAdditional, additionalDot);
  316.         } else {
  317.             return new FieldSpacecraftState<>(absPva, attitude, mass, newAdditional, additionalDot);
  318.         }
  319.     }

  320.     /**
  321.      * Create a new instance with input additional data.
  322.      * @param newAdditionalDot additional derivatives
  323.      * @return new state
  324.      * @since 13.0
  325.      */
  326.     public FieldSpacecraftState<T> withAdditionalStatesDerivatives(final FieldArrayDictionary<T> newAdditionalDot) {
  327.         if (isOrbitDefined()) {
  328.             return new FieldSpacecraftState<>(orbit, attitude, mass, additional, newAdditionalDot);
  329.         } else {
  330.             return new FieldSpacecraftState<>(absPva, attitude, mass, additional, newAdditionalDot);
  331.         }
  332.     }

  333.     /** Add an additional data.
  334.      * <p>
  335.      * {@link FieldSpacecraftState SpacecraftState} instances are immutable,
  336.      * so this method does <em>not</em> change the instance, but rather
  337.      * creates a new instance, which has the same orbit, attitude, mass
  338.      * and additional states as the original instance, except it also
  339.      * has the specified state. If the original instance already had an
  340.      * additional state with the same name, it will be overridden. If it
  341.      * did not have any additional state with that name, the new instance
  342.      * will have one more additional state than the original instance.
  343.      * </p>
  344.      * @param name name of the additional data (names containing "orekit"
  345.      *      * with any case are reserved for the library internal use)
  346.      * @param value value of the additional data
  347.      * @return a new instance, with the additional data added
  348.      * @see #hasAdditionalData(String)
  349.      * @see #getAdditionalData(String)
  350.      * @see #getAdditionalDataValues()
  351.      */
  352.     @SuppressWarnings("unchecked") // cast including generic type is checked and unitary tested
  353.     public final FieldSpacecraftState<T> addAdditionalData(final String name, final Object value) {
  354.         final FieldDataDictionary<T> newDict = new FieldDataDictionary<>(additional);
  355.         if (value instanceof CalculusFieldElement[]) {
  356.             final CalculusFieldElement<T>[] valueArray = (CalculusFieldElement<T>[]) value;
  357.             newDict.put(name, valueArray.clone());
  358.         } else if (value instanceof CalculusFieldElement) {
  359.             final CalculusFieldElement<T>[] valueArray = MathArrays.buildArray(mass.getField(), 1);
  360.             valueArray[0] = (CalculusFieldElement<T>) value;
  361.             newDict.put(name, valueArray);
  362.         } else {
  363.             newDict.put(name, value);
  364.         }
  365.         return withAdditionalData(newDict);
  366.     }

  367.     /** Add an additional state derivative.
  368.     * {@link FieldSpacecraftState FieldSpacecraftState} instances are immutable,
  369.      * so this method does <em>not</em> change the instance, but rather
  370.      * creates a new instance, which has the same components as the original
  371.      * instance, except it also has the specified state derivative. If the
  372.      * original instance already had an additional state derivative with the
  373.      * same name, it will be overridden. If it did not have any additional
  374.      * state derivative with that name, the new instance will have one more
  375.      * additional state derivative than the original instance.
  376.      * @param name name of the additional state derivative
  377.      * @param value value of the additional state derivative
  378.      * @return a new instance, with the additional state derivative added
  379.      * @see #hasAdditionalStateDerivative(String)
  380.      * @see #getAdditionalStateDerivative(String)
  381.      * @see #getAdditionalStatesDerivatives()
  382.      */
  383.     @SafeVarargs
  384.     public final FieldSpacecraftState<T> addAdditionalStateDerivative(final String name, final T... value) {
  385.         final FieldArrayDictionary<T> newDict = new FieldArrayDictionary<>(additionalDot);
  386.         newDict.put(name, value.clone());
  387.         return withAdditionalStatesDerivatives(newDict);
  388.     }

  389.     /** Check orbit and attitude dates are equal.
  390.      * @param orbitN the orbit
  391.      * @param attitudeN attitude
  392.      * @exception IllegalArgumentException if orbit and attitude dates
  393.      * are not equal
  394.      */
  395.     private void checkConsistency(final FieldOrbit<T> orbitN, final FieldAttitude<T> attitudeN)
  396.         throws IllegalArgumentException {
  397.         checkDateAndFrameConsistency(attitudeN, orbitN.getDate(), orbitN.getFrame());
  398.     }

  399.     /** Check if the state contains an orbit part.
  400.      * <p>
  401.      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
  402.      * position-velocity-acceleration} or an {@link FieldOrbit orbit}.
  403.      * </p>
  404.      * @return true if state contains an orbit (in which case {@link #getOrbit()}
  405.      * will not throw an exception), or false if the state contains an
  406.      * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
  407.      * will not throw an exception)
  408.      */
  409.     public boolean isOrbitDefined() {
  410.         return orbit != null;
  411.     }

  412.     /**
  413.      * Check FieldAbsolutePVCoordinates and attitude dates are equal.
  414.      * @param absPva   position-velocity-acceleration
  415.      * @param attitude attitude
  416.      * @param <T>      the type of the field elements
  417.      * @exception IllegalArgumentException if orbit and attitude dates are not equal
  418.      */
  419.     private static <T extends CalculusFieldElement<T>> void checkConsistency(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude)
  420.         throws IllegalArgumentException {
  421.         checkDateAndFrameConsistency(attitude, absPva.getDate(), absPva.getFrame());
  422.     }

  423.     /** Check attitude frame and epoch.
  424.      * @param attitude attitude
  425.      * @param date epoch to verify
  426.      * @param frame frame to verify
  427.      * @param <T> type of the elements
  428.      */
  429.     private static <T extends CalculusFieldElement<T>> void checkDateAndFrameConsistency(final FieldAttitude<T> attitude,
  430.                                                                                          final FieldAbsoluteDate<T> date,
  431.                                                                                          final Frame frame) {
  432.         if (date.durationFrom(attitude.getDate()).abs().getReal() >
  433.                 DATE_INCONSISTENCY_THRESHOLD) {
  434.             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
  435.                     date, attitude.getDate());
  436.         }
  437.         if (frame != attitude.getReferenceFrame()) {
  438.             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
  439.                     frame.getName(),
  440.                     attitude.getReferenceFrame().getName());
  441.         }
  442.     }

  443.     /** Get a time-shifted state.
  444.      * <p>
  445.      * The state can be slightly shifted to close dates. This shift is based on
  446.      * a simple Keplerian model for orbit, a linear extrapolation for attitude
  447.      * taking the spin rate into account and neither mass nor additional states
  448.      * changes. It is <em>not</em> intended as a replacement for proper orbit
  449.      * and attitude propagation but should be sufficient for small time shifts
  450.      * or coarse accuracy.
  451.      * </p>
  452.      * <p>
  453.      * As a rough order of magnitude, the following table shows the extrapolation
  454.      * errors obtained between this simple shift method and an {@link
  455.      * org.orekit.propagation.numerical.FieldNumericalPropagator numerical
  456.      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
  457.      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
  458.      * Beware that these results will be different for other orbits.
  459.      * </p>
  460.      * <table border="1">
  461.      * <caption>Extrapolation Error</caption>
  462.      * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
  463.      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
  464.      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
  465.      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
  466.      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
  467.      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
  468.      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
  469.      * </table>
  470.      * @param dt time shift in seconds
  471.      * @return a new state, shifted with respect to the instance (which is immutable)
  472.      * except for the mass which stay unchanged
  473.      */
  474.     @Override
  475.     public FieldSpacecraftState<T> shiftedBy(final double dt) {
  476.         if (isOrbitDefined()) {
  477.             return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  478.                                               mass, shiftAdditional(dt), additionalDot);
  479.         } else {
  480.             return new FieldSpacecraftState<>(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
  481.                                               mass, shiftAdditional(dt), additionalDot);
  482.         }
  483.     }

  484.     /** Get a time-shifted state.
  485.      * <p>
  486.      * The state can be slightly shifted to close dates. This shift is based on
  487.      * a simple Keplerian model for orbit, a linear extrapolation for attitude
  488.      * taking the spin rate into account and neither mass nor additional states
  489.      * changes. It is <em>not</em> intended as a replacement for proper orbit
  490.      * and attitude propagation but should be sufficient for small time shifts
  491.      * or coarse accuracy.
  492.      * </p>
  493.      * <p>
  494.      * As a rough order of magnitude, the following table shows the extrapolation
  495.      * errors obtained between this simple shift method and an {@link
  496.      * org.orekit.propagation.numerical.FieldNumericalPropagator numerical
  497.      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
  498.      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
  499.      * Beware that these results will be different for other orbits.
  500.      * </p>
  501.      * <table border="1">
  502.      * <caption>Extrapolation Error</caption>
  503.      * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
  504.      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
  505.      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
  506.      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
  507.      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
  508.      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
  509.      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
  510.      * </table>
  511.      * @param dt time shift in seconds
  512.      * @return a new state, shifted with respect to the instance (which is immutable)
  513.      * except for the mass which stay unchanged
  514.      */
  515.     @Override
  516.     public FieldSpacecraftState<T> shiftedBy(final T dt) {
  517.         if (isOrbitDefined()) {
  518.             return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
  519.                                               mass, shiftAdditional(dt), additionalDot);
  520.         } else {
  521.             return new FieldSpacecraftState<>(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
  522.                                               mass, shiftAdditional(dt), additionalDot);
  523.         }
  524.     }

  525.     /** Shift additional data.
  526.      * @param dt time shift in seconds
  527.      * @return shifted additional data
  528.      * @since 11.1.1
  529.      */
  530.     private FieldDataDictionary<T> shiftAdditional(final double dt) {

  531.         // fast handling when there are no derivatives at all
  532.         if (additionalDot.size() == 0) {
  533.             return additional;
  534.         }

  535.         // there are derivatives, we need to take them into account in the additional state
  536.         final FieldDataDictionary<T> shifted = new FieldDataDictionary<>(additional);
  537.         for (final FieldArrayDictionary<T>.Entry dotEntry : additionalDot.getData()) {
  538.             final FieldDataDictionary<T>.Entry entry = shifted.getEntry(dotEntry.getKey());
  539.             if (entry != null) {
  540.                 entry.scaledIncrement(dt, dotEntry);
  541.             }
  542.         }

  543.         return shifted;

  544.     }

  545.     /** Shift additional states.
  546.      * @param dt time shift in seconds
  547.      * @return shifted additional states
  548.      * @since 11.1.1
  549.      */
  550.     private FieldDataDictionary<T> shiftAdditional(final T dt) {

  551.         // fast handling when there are no derivatives at all
  552.         if (additionalDot.size() == 0) {
  553.             return additional;
  554.         }

  555.         // there are derivatives, we need to take them into account in the additional state
  556.         final FieldDataDictionary<T> shifted = new FieldDataDictionary<>(additional);
  557.         for (final FieldArrayDictionary<T>.Entry dotEntry : additionalDot.getData()) {
  558.             final FieldDataDictionary<T>.Entry entry = shifted.getEntry(dotEntry.getKey());
  559.             if (entry != null) {
  560.                 entry.scaledIncrement(dt, dotEntry);
  561.             }
  562.         }

  563.         return shifted;

  564.     }

  565.     /** Get the absolute position-velocity-acceleration.
  566.      * <p>
  567.      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
  568.      * position-velocity-acceleration} or an {@link FieldOrbit orbit}. Which
  569.      * one is present can be checked using {@link #isOrbitDefined()}.
  570.      * </p>
  571.      * @return absolute position-velocity-acceleration
  572.      * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
  573.      * which mean the state rather contains an {@link FieldOrbit}
  574.      * @see #isOrbitDefined()
  575.      * @see #getOrbit()
  576.      */
  577.     public FieldAbsolutePVCoordinates<T> getAbsPVA() throws OrekitIllegalStateException {
  578.         if (isOrbitDefined()) {
  579.             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
  580.         }
  581.         return absPva;
  582.     }

  583.     /** Get the current orbit.
  584.      * <p>
  585.      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
  586.      * position-velocity-acceleration} or an {@link FieldOrbit orbit}. Which
  587.      * one is present can be checked using {@link #isOrbitDefined()}.
  588.      * </p>
  589.      * @return the orbit
  590.      * @exception OrekitIllegalStateException if orbit is null,
  591.      * which means the state rather contains an {@link FieldAbsolutePVCoordinates absolute
  592.      * position-velocity-acceleration}
  593.      * @see #isOrbitDefined()
  594.      * @see #getAbsPVA()
  595.      */
  596.     public FieldOrbit<T> getOrbit() throws OrekitIllegalStateException {
  597.         if (orbit == null) {
  598.             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
  599.         }
  600.         return orbit;
  601.     }

  602.     /** {@inheritDoc} */
  603.     @Override
  604.     public FieldAbsoluteDate<T> getDate() {
  605.         return isOrbitDefined() ? orbit.getDate() : absPva.getDate();
  606.     }

  607.     /** Get the defining frame.
  608.      * @return the frame in which state is defined
  609.      */
  610.     public Frame getFrame() {
  611.         return isOrbitDefined() ? orbit.getFrame() : absPva.getFrame();
  612.     }


  613.     /** Check if an additional data is available.
  614.      * @param name name of the additional data
  615.      * @return true if the additional data is available
  616.      * @see #addAdditionalData(String, Object)
  617.      * @see #getAdditionalData(String)
  618.      * @see #getAdditionalDataValues()
  619.      */
  620.     public boolean hasAdditionalData(final String name) {
  621.         return additional.getEntry(name) != null;
  622.     }

  623.     /** Check if an additional state derivative is available.
  624.      * @param name name of the additional state derivative
  625.      * @return true if the additional state derivative is available
  626.      * @see #addAdditionalStateDerivative(String, CalculusFieldElement...)
  627.      * @see #getAdditionalStateDerivative(String)
  628.      * @see #getAdditionalStatesDerivatives()
  629.      */
  630.     public boolean hasAdditionalStateDerivative(final String name) {
  631.         return additionalDot.getEntry(name) != null;
  632.     }

  633.     /** Check if two instances have the same set of additional states available.
  634.      * <p>
  635.      * Only the names and dimensions of the additional states are compared,
  636.      * not their values.
  637.      * </p>
  638.      * @param state state to compare to instance
  639.      * @exception MathIllegalArgumentException if an additional state does not have
  640.      * the same dimension in both states
  641.      */
  642.     @SuppressWarnings("unchecked") // cast including generic type is checked and unitary tested
  643.     public void ensureCompatibleAdditionalStates(final FieldSpacecraftState<T> state)
  644.         throws MathIllegalArgumentException {

  645.         // check instance additional states is a subset of the other one
  646.         for (final FieldDataDictionary<T>.Entry entry : additional.getData()) {
  647.             final Object other = state.additional.get(entry.getKey());
  648.             if (other == null) {
  649.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
  650.                                           entry.getKey());
  651.             }
  652.             if (other instanceof CalculusFieldElement[]) {
  653.                 final CalculusFieldElement<T>[] arrayOther = (CalculusFieldElement<T>[]) other;
  654.                 final CalculusFieldElement<T>[] arrayEntry = (CalculusFieldElement<T>[]) entry.getValue();
  655.                 if (arrayEntry.length != arrayOther.length) {
  656.                     throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH, arrayOther.length, arrayEntry.length);
  657.                 }
  658.             }
  659.         }

  660.         // check instance additional states derivatives is a subset of the other one
  661.         for (final FieldArrayDictionary<T>.Entry entry : additionalDot.getData()) {
  662.             final T[] other = state.additionalDot.get(entry.getKey());
  663.             if (other == null) {
  664.                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
  665.                                           entry.getKey());
  666.             }
  667.             if (other.length != entry.getValue().length) {
  668.                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  669.                                                     other.length, entry.getValue().length);
  670.             }
  671.         }

  672.         if (state.additional.size() > additional.size()) {
  673.             // the other state has more additional states
  674.             for (final FieldDataDictionary<T>.Entry entry : state.additional.getData()) {
  675.                 if (additional.getEntry(entry.getKey()) == null) {
  676.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
  677.                                               entry.getKey());
  678.                 }
  679.             }
  680.         }

  681.         if (state.additionalDot.size() > additionalDot.size()) {
  682.             // the other state has more additional states
  683.             for (final FieldArrayDictionary<T>.Entry entry : state.additionalDot.getData()) {
  684.                 if (additionalDot.getEntry(entry.getKey()) == null) {
  685.                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
  686.                                               entry.getKey());
  687.                 }
  688.             }
  689.         }

  690.     }

  691.     /**
  692.      * Get an additional state.
  693.      *
  694.      * @param name name of the additional state
  695.      * @return value of the additional state
  696.      * @see #hasAdditionalData(String)
  697.      * @see #getAdditionalDataValues()
  698.      */
  699.     @SuppressWarnings("unchecked") // cast including generic type is checked and unitary tested
  700.     public T[] getAdditionalState(final String name) {
  701.         final Object data = getAdditionalData(name);
  702.         if (data instanceof CalculusFieldElement[]) {
  703.             return (T[]) data;
  704.         } else if (data instanceof CalculusFieldElement) {
  705.             final T[] values = MathArrays.buildArray(mass.getField(), 1);
  706.             values[0] = (T) data;
  707.             return values;
  708.         } else {
  709.             throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_BAD_TYPE, name);
  710.         }
  711.     }


  712.     /**
  713.      * Get an additional data.
  714.      *
  715.      * @param name name of the additional state
  716.      * @return value of the additional state
  717.      * @see #addAdditionalData(String, Object)
  718.      * @see #hasAdditionalData(String)
  719.      * @see #getAdditionalDataValues()
  720.      * @since 13.0
  721.      */
  722.     public Object getAdditionalData(final String name) {
  723.         final FieldDataDictionary<T>.Entry entry = additional.getEntry(name);
  724.         if (entry == null) {
  725.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name);
  726.         }
  727.         return entry.getValue();
  728.     }

  729.     /** Get an additional state derivative.
  730.      * @param name name of the additional state derivative
  731.      * @return value of the additional state derivative
  732.      * @see #addAdditionalStateDerivative(String, CalculusFieldElement...)
  733.      * @see #hasAdditionalStateDerivative(String)
  734.      * @see #getAdditionalStatesDerivatives()
  735.      * @since 11.1
  736.      */
  737.     public T[] getAdditionalStateDerivative(final String name) {
  738.         final FieldArrayDictionary<T>.Entry entry = additionalDot.getEntry(name);
  739.         if (entry == null) {
  740.             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name);
  741.         }
  742.         return entry.getValue();
  743.     }

  744.     /** Get an unmodifiable map of additional states.
  745.      * @return unmodifiable map of additional states
  746.      * @see #addAdditionalData(String, Object)
  747.      * @see #hasAdditionalData(String)
  748.      * @see #getAdditionalData(String)
  749.      * @since 11.1
  750.      */
  751.     public FieldDataDictionary<T> getAdditionalDataValues() {
  752.         return additional;
  753.     }

  754.     /** Get an unmodifiable map of additional states derivatives.
  755.      * @return unmodifiable map of additional states derivatives
  756.      * @see #addAdditionalStateDerivative(String, CalculusFieldElement...)
  757.      * @see #hasAdditionalStateDerivative(String)
  758.      * @see #getAdditionalStateDerivative(String)
  759.     * @since 11.1
  760.       */
  761.     public FieldArrayDictionary<T> getAdditionalStatesDerivatives() {
  762.         return additionalDot.unmodifiableView();
  763.     }

  764.     /** Compute the transform from state defining frame to spacecraft frame.
  765.      * <p>The spacecraft frame origin is at the point defined by the orbit,
  766.      * and its orientation is defined by the attitude.</p>
  767.      * @return transform from specified frame to current spacecraft frame
  768.      */
  769.     public FieldTransform<T> toTransform() {
  770.         final TimeStampedFieldPVCoordinates<T> pv = getPVCoordinates();
  771.         return new FieldTransform<>(pv.getDate(),
  772.                                     new FieldTransform<>(pv.getDate(), pv.negate()),
  773.                                     new FieldTransform<>(pv.getDate(), attitude.getOrientation()));
  774.     }

  775.     /** Compute the static transform from state defining frame to spacecraft frame.
  776.      * @return static transform from specified frame to current spacecraft frame
  777.      * @see #toTransform()
  778.      * @since 12.0
  779.      */
  780.     public FieldStaticTransform<T> toStaticTransform() {
  781.         return FieldStaticTransform.of(getDate(), getPosition().negate(), attitude.getRotation());
  782.     }

  783.     /** Get the position in orbit definition frame.
  784.      * @return position in orbit definition frame
  785.      * @since 12.0
  786.      */
  787.     public FieldVector3D<T> getPosition() {
  788.         return isOrbitDefined() ? orbit.getPosition() : absPva.getPosition();
  789.     }

  790.     /** Get the {@link TimeStampedFieldPVCoordinates} in orbit definition frame.
  791.      * <p>
  792.      * Compute the position and velocity of the satellite. This method caches its
  793.      * results, and recompute them only when the method is called with a new value
  794.      * for mu. The result is provided as a reference to the internally cached
  795.      * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
  796.      * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
  797.      * </p>
  798.      * @return pvCoordinates in orbit definition frame
  799.      */
  800.     public TimeStampedFieldPVCoordinates<T> getPVCoordinates() {
  801.         return isOrbitDefined() ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
  802.     }

  803.     /** Get the position in given output frame.
  804.      * @param outputFrame frame in which position should be defined
  805.      * @return position in given output frame
  806.      * @since 12.0
  807.      * @see #getPVCoordinates(Frame)
  808.      */
  809.     public FieldVector3D<T> getPosition(final Frame outputFrame) {
  810.         return isOrbitDefined() ? orbit.getPosition(outputFrame) : absPva.getPosition(outputFrame);
  811.     }

  812.     /** Get the {@link TimeStampedFieldPVCoordinates} in given output frame.
  813.      * <p>
  814.      * Compute the position and velocity of the satellite. This method caches its
  815.      * results, and recompute them only when the method is called with a new value
  816.      * for mu. The result is provided as a reference to the internally cached
  817.      * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
  818.      * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
  819.      * </p>
  820.      * @param outputFrame frame in which coordinates should be defined
  821.      * @return pvCoordinates in orbit definition frame
  822.      */
  823.     public TimeStampedFieldPVCoordinates<T> getPVCoordinates(final Frame outputFrame) {
  824.         return isOrbitDefined() ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
  825.     }

  826.     /** Get the attitude.
  827.      * @return the attitude.
  828.      */
  829.     public FieldAttitude<T> getAttitude() {
  830.         return attitude;
  831.     }

  832.     /** Gets the current mass.
  833.      * @return the mass (kg)
  834.      */
  835.     public T getMass() {
  836.         return mass;
  837.     }

  838.     /**To convert a FieldSpacecraftState instance into a SpacecraftState instance.
  839.      *
  840.      * @return SpacecraftState instance with the same properties
  841.      */
  842.     @SuppressWarnings("unchecked") // cast including generic type is checked and unitary tested
  843.     public SpacecraftState toSpacecraftState() {
  844.         final DataDictionary dictionary;
  845.         if (additional.size() == 0) {
  846.             dictionary = new DataDictionary();
  847.         } else {
  848.             dictionary = new DataDictionary(additional.size());
  849.             for (final FieldDataDictionary<T>.Entry entry : additional.getData()) {
  850.                 if (entry.getValue() instanceof CalculusFieldElement[]) {
  851.                     final CalculusFieldElement<T>[] entryArray  = (CalculusFieldElement<T>[]) entry.getValue();
  852.                     final double[] realArray = new double[entryArray.length];
  853.                     for (int k = 0; k < realArray.length; ++k) {
  854.                         realArray[k] = entryArray[k].getReal();
  855.                     }
  856.                     dictionary.put(entry.getKey(), realArray);
  857.                 } else {
  858.                     dictionary.put(entry.getKey(), entry.getValue());
  859.                 }
  860.             }
  861.         }
  862.         final DoubleArrayDictionary dictionaryDot;
  863.         if (additionalDot.size() == 0) {
  864.             dictionaryDot = new DoubleArrayDictionary();
  865.         } else {
  866.             dictionaryDot = new DoubleArrayDictionary(additionalDot.size());
  867.             for (final FieldArrayDictionary<T>.Entry entry : additionalDot.getData()) {
  868.                 final double[] array = new double[entry.getValue().length];
  869.                 for (int k = 0; k < array.length; ++k) {
  870.                     array[k] = entry.getValue()[k].getReal();
  871.                 }
  872.                 dictionaryDot.put(entry.getKey(), array);
  873.             }
  874.         }
  875.         if (isOrbitDefined()) {
  876.             return new SpacecraftState(orbit.toOrbit(), attitude.toAttitude(),
  877.                                        mass.getReal(), dictionary, dictionaryDot);
  878.         } else {
  879.             return new SpacecraftState(absPva.toAbsolutePVCoordinates(),
  880.                                        attitude.toAttitude(), mass.getReal(),
  881.                                        dictionary, dictionaryDot);
  882.         }
  883.     }

  884.     @Override
  885.     public String toString() {
  886.         return "FieldSpacecraftState{" +
  887.                 "orbit=" + orbit +
  888.                 ", attitude=" + attitude +
  889.                 ", mass=" + mass +
  890.                 ", additional=" + additional +
  891.                 ", additionalDot=" + additionalDot +
  892.                 '}';
  893.     }

  894. }