1   /* Copyright 2002-2026 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  
19  
20  import org.hipparchus.CalculusFieldElement;
21  import org.hipparchus.Field;
22  import org.hipparchus.exception.LocalizedCoreFormats;
23  import org.hipparchus.exception.MathIllegalArgumentException;
24  import org.hipparchus.exception.MathIllegalStateException;
25  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
26  import org.hipparchus.util.MathArrays;
27  import org.orekit.attitudes.FieldAttitude;
28  import org.orekit.errors.OrekitException;
29  import org.orekit.errors.OrekitIllegalArgumentException;
30  import org.orekit.errors.OrekitIllegalStateException;
31  import org.orekit.errors.OrekitMessages;
32  import org.orekit.frames.FieldStaticTransform;
33  import org.orekit.frames.FieldTransform;
34  import org.orekit.frames.Frame;
35  import org.orekit.orbits.FieldOrbit;
36  import org.orekit.orbits.Orbit;
37  import org.orekit.time.FieldAbsoluteDate;
38  import org.orekit.time.FieldTimeShiftable;
39  import org.orekit.time.FieldTimeStamped;
40  import org.orekit.utils.DataDictionary;
41  import org.orekit.utils.DoubleArrayDictionary;
42  import org.orekit.utils.FieldAbsolutePVCoordinates;
43  import org.orekit.utils.FieldArrayDictionary;
44  import org.orekit.utils.FieldDataDictionary;
45  import org.orekit.utils.FieldPVCoordinates;
46  import org.orekit.utils.TimeStampedFieldPVCoordinates;
47  import org.orekit.utils.TimeStampedPVCoordinates;
48  
49  /** This class is the representation of a complete state holding orbit, attitude
50   * and mass information at a given date, meant primarily for propagation.
51   *
52   * <p>It contains an {@link FieldOrbit}, or a {@link FieldAbsolutePVCoordinates} if there
53   * is no definite central body, plus the current mass and attitude at the intrinsic
54   * {@link FieldAbsoluteDate}. Quantities are guaranteed to be consistent in terms
55   * of date and reference frame. The spacecraft state may also contain additional
56   * states, which are simply named double arrays which can hold any user-defined
57   * data.
58   * </p>
59   * <p>
60   * The state can be slightly shifted to close dates. This actual shift varies
61   * between {@link FieldOrbit} and {@link FieldAbsolutePVCoordinates}.
62   * For attitude it is a linear extrapolation taking the spin rate into account
63   * and no mass change. It is <em>not</em> intended as a replacement for proper
64   * orbit and attitude propagation but should be sufficient for either small
65   * time shifts or coarse accuracy.
66   * </p>
67   * <p>
68   * The instance {@code FieldSpacecraftState} is guaranteed to be immutable.
69   * </p>
70   * @see org.orekit.propagation.numerical.NumericalPropagator
71   * @see SpacecraftState
72   * @author Fabien Maussion
73   * @author V&eacute;ronique Pommier-Maurussane
74   * @author Luc Maisonobe
75   * @author Vincent Mouraux
76   * @param <T> type of the field elements
77   */
78  public class FieldSpacecraftState <T extends CalculusFieldElement<T>>
79      implements FieldTimeStamped<T>, FieldTimeShiftable<FieldSpacecraftState<T>, T> {
80  
81      /** Default mass. */
82      private static final double DEFAULT_MASS = 1000.0;
83  
84      /**
85       * tolerance on date comparison in {@link #checkConsistency(FieldOrbit, FieldAttitude)}. 100 ns
86       * corresponds to sub-mm accuracy at LEO orbital velocities.
87       */
88      private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;
89  
90      /** Orbital state. */
91      private final FieldOrbit<T> orbit;
92  
93      /** Trajectory state, when it is not an orbit. */
94      private final FieldAbsolutePVCoordinates<T> absPva;
95  
96      /** Field attitude. */
97      private final FieldAttitude<T> attitude;
98  
99      /** Current mass (kg). */
100     private final T mass;
101 
102     /** Current mass rate (kg/s). */
103     private final T massRate;
104 
105     /** Additional data, can be any object (String, T[], etc.). */
106     private final FieldDataDictionary<T> additional;
107 
108     /** Additional states derivatives.
109      * @since 11.1
110      */
111     private final FieldArrayDictionary<T> additionalDot;
112 
113     /** Build a spacecraft state from orbit only.
114      * <p>FieldAttitude and mass are set to unspecified non-null arbitrary values.</p>
115      * @param orbit the orbit
116      */
117     public FieldSpacecraftState(final FieldOrbit<T> orbit) {
118         this(orbit, SpacecraftState.getDefaultAttitudeProvider(orbit.getFrame())
119                         .getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
120              orbit.getA().getField().getZero().newInstance(DEFAULT_MASS), null, null);
121     }
122 
123     /** Build a spacecraft state from orbit and attitude. Kept for performance.
124      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
125      * @param orbit the orbit
126      * @param attitude attitude
127      * @exception IllegalArgumentException if orbit and attitude dates
128      * or frames are not equal
129      */
130     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude)
131         throws IllegalArgumentException {
132         this(orbit, attitude, orbit.getA().getField().getZero().newInstance(DEFAULT_MASS), null, null);
133         checkConsistency(orbit, attitude);
134     }
135 
136     /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives.
137      * @param orbit the orbit
138      * @param attitude attitude
139      * @param mass the mass (kg)
140      * @param additional additional states (may be null if no additional states are available)
141      * @param additionalDot additional states derivatives(may be null if no additional states derivative sare available)
142      * @exception IllegalArgumentException if orbit and attitude dates
143      * or frames are not equal
144      * @since 11.1
145      */
146     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final T mass,
147                                 final FieldDataDictionary<T> additional,
148                                 final FieldArrayDictionary<T> additionalDot)
149         throws IllegalArgumentException {
150         this(orbit, null, attitude, mass, mass.getField().getZero(), additional, additionalDot, true);
151     }
152 
153     /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives.
154      * @param orbit the orbit
155      * @param attitude attitude
156      * @param mass the mass (kg)
157      * @param massRate the mass rate (kg/s)
158      * @param additional additional states (may be null if no additional states are available)
159      * @param additionalDot additional states derivatives(may be null if no additional states derivative sare available)
160      * @exception IllegalArgumentException if orbit and attitude dates
161      * or frames are not equal
162      * @since 14.0
163      */
164     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final T mass,
165                                 final T massRate, final FieldDataDictionary<T> additional,
166                                 final FieldArrayDictionary<T> additionalDot)
167             throws IllegalArgumentException {
168         this(orbit, null, attitude, mass, massRate, additional, additionalDot, true);
169         checkConsistency(orbit, attitude);
170     }
171 
172     /** Convert a {@link FieldSpacecraftState}.
173      * @param field field to which the elements belong
174      * @param state state to convert
175      */
176     public FieldSpacecraftState(final Field<T> field, final SpacecraftState state) {
177 
178         if (state.isOrbitDefined()) {
179 
180             final Orbit nonFieldOrbit = state.getOrbit();
181             this.orbit    = nonFieldOrbit.getType().convertToFieldOrbit(field, nonFieldOrbit);
182             this.absPva   = null;
183 
184         } else {
185             final TimeStampedPVCoordinates tspva = state.getPVCoordinates();
186             final FieldVector3D<T> position = new FieldVector3D<>(field, tspva.getPosition());
187             final FieldVector3D<T> velocity = new FieldVector3D<>(field, tspva.getVelocity());
188             final FieldVector3D<T> acceleration = new FieldVector3D<>(field, tspva.getAcceleration());
189             final FieldPVCoordinates<T> pva = new FieldPVCoordinates<>(position, velocity, acceleration);
190             final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, state.getDate());
191             this.orbit  = null;
192             this.absPva = new FieldAbsolutePVCoordinates<>(state.getFrame(), dateF, pva);
193         }
194 
195         this.attitude = new FieldAttitude<>(field, state.getAttitude());
196         this.mass     = field.getZero().newInstance(state.getMass());
197         this.massRate = this.mass.newInstance(state.getMassRate());
198 
199         final DataDictionary additionalD = state.getAdditionalDataValues();
200         if (additionalD.size() == 0) {
201             this.additional = new FieldDataDictionary<>(field);
202         } else {
203             this.additional = new FieldDataDictionary<>(field, additionalD.size());
204             for (final DataDictionary.Entry entry : additionalD.getData()) {
205                 if (entry.getValue() instanceof final double[] realValues) {
206                     final T[] fieldArray = MathArrays.buildArray(field, realValues.length);
207                     for (int i = 0; i < fieldArray.length; i++) {
208                         fieldArray[i] = field.getZero().add(realValues[i]);
209                     }
210                     this.additional.put(entry.getKey(), fieldArray);
211                 } else {
212                     this.additional.put(entry.getKey(), entry.getValue());
213                 }
214             }
215         }
216         final DoubleArrayDictionary additionalDotD = state.getAdditionalStatesDerivatives();
217         if (additionalDotD.size() == 0) {
218             this.additionalDot = new FieldArrayDictionary<>(field);
219         } else {
220             this.additionalDot = new FieldArrayDictionary<>(field, additionalDotD.size());
221             for (final DoubleArrayDictionary.Entry entry : additionalDotD.getData()) {
222                 this.additionalDot.put(entry.getKey(), entry.getValue());
223             }
224         }
225 
226     }
227 
228     /** Build a spacecraft state from absolute coordinates only.
229      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
230      * @param absPva position-velocity-acceleration
231      */
232     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva) {
233         this(absPva,
234              SpacecraftState.getDefaultAttitudeProvider(absPva.getFrame()).
235                      getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
236              absPva.getDate().getField().getZero().newInstance(DEFAULT_MASS), null, null);
237     }
238 
239     /** Build a spacecraft state from absolute coordinates and attitude. Kept for performance.
240      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
241      * @param absPva position-velocity-acceleration
242      * @param attitude attitude
243      * @exception IllegalArgumentException if orbit and attitude dates
244      * or frames are not equal
245      */
246     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude)
247         throws IllegalArgumentException {
248         this(absPva, attitude, absPva.getDate().getField().getZero().newInstance(DEFAULT_MASS), null, null);
249         checkConsistency(absPva, attitude);
250     }
251 
252     /** Build a spacecraft state from absolute coordinates, attitude and mass.
253      * @param absPva position-velocity-acceleration
254      * @param attitude attitude
255      * @param mass the mass (kg)
256      * @param additional additional states (may be null if no additional states are available)
257      * @param additionalDot additional states derivatives(may be null if no additional states derivatives are available)
258      * @exception IllegalArgumentException if orbit and attitude dates
259      * or frames are not equal
260      * @since 11.1
261      */
262     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude, final T mass,
263                                 final FieldDataDictionary<T> additional, final FieldArrayDictionary<T> additionalDot)
264         throws IllegalArgumentException {
265         this(absPva, attitude, mass, mass.getField().getZero(), additional, additionalDot);
266         checkConsistency(absPva, attitude);
267     }
268 
269     /** Build a spacecraft state from absolute coordinates, attitude, mass and mass rate.
270      * @param absPva position-velocity-acceleration
271      * @param attitude attitude
272      * @param mass the mass (kg)
273      * @param massRate mass rate (kg/s)
274      * @param additional additional states (may be null if no additional states are available)
275      * @param additionalDot additional states derivatives(may be null if no additional states derivatives are available)
276      * @exception IllegalArgumentException if orbit and attitude dates or frames are not equal
277      * @since 14.0
278      */
279     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude,
280                                 final T mass, final T massRate,
281                                 final FieldDataDictionary<T> additional, final FieldArrayDictionary<T> additionalDot)
282             throws IllegalArgumentException {
283         this(null, absPva, attitude, mass, massRate, additional, additionalDot, true);
284         checkConsistency(absPva, attitude);
285     }
286 
287     /** Full, private constructor.
288      * @param orbit the orbit
289      * @param absPva absolute position-velocity
290      * @param attitude attitude
291      * @param mass the mass (kg)
292      * @param massRate the mass rate (kg/s)
293      * @param additional additional data (may be null if no additional states are available)
294      * @param additionalDot additional states derivatives (may be null if no additional states derivatives are available)
295      * @param deepCopy flag to copy dictionaries (additional data and derivatives)
296      * @exception IllegalArgumentException if orbit and attitude dates
297      * or frames are not equal
298      * @since 13.1.1
299      */
300     private FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAbsolutePVCoordinates<T> absPva,
301                                  final FieldAttitude<T> attitude, final T mass, final T massRate,
302                                  final FieldDataDictionary<T> additional, final FieldArrayDictionary<T> additionalDot,
303                                  final boolean deepCopy)
304             throws IllegalArgumentException {
305         this.orbit      = orbit;
306         this.absPva     = absPva;
307         this.attitude   = attitude;
308         this.mass       = mass;
309         this.massRate   = massRate;
310         if (deepCopy) {
311             this.additional = additional == null ? new FieldDataDictionary<>(mass.getField()) : new FieldDataDictionary<>(additional);
312             this.additionalDot = additionalDot == null ? new FieldArrayDictionary<>(mass.getField()) : new FieldArrayDictionary<>(additionalDot);
313         } else {
314             this.additional = additional == null ? new FieldDataDictionary<>(mass.getField()) : additional;
315             this.additionalDot = additionalDot == null ? new FieldArrayDictionary<>(mass.getField()) : additionalDot;
316         }
317     }
318 
319     /**
320      * Create a new instance with input mass.
321      * @param newMass mass
322      * @return new state
323      * @since 13.0
324      */
325     public FieldSpacecraftState<T> withMass(final T newMass) {
326         return new FieldSpacecraftState<>(orbit, absPva, attitude, newMass, massRate, additional, additionalDot, false);
327     }
328 
329     /**
330      * Create a new instance with input mass.
331      * @param newMassRate mass rate
332      * @return new state
333      * @since 14.0
334      */
335     public FieldSpacecraftState<T> withMassRate(final T newMassRate) {
336         return new FieldSpacecraftState<>(orbit, absPva, attitude, mass, newMassRate, additional, additionalDot, false);
337     }
338 
339     /**
340      * Create a new instance with input attitude.
341      * @param newAttitude attitude
342      * @return new state
343      * @since 13.0
344      */
345     public FieldSpacecraftState<T> withAttitude(final FieldAttitude<T> newAttitude) {
346         if (isOrbitDefined()) {
347             checkConsistency(orbit, newAttitude);
348         } else {
349             checkConsistency(absPva, newAttitude);
350         }
351         return new FieldSpacecraftState<>(orbit, absPva, newAttitude, mass, massRate, additional, additionalDot, false);
352     }
353 
354     /**
355      * Create a new instance with input additional data.
356      * @param newAdditional data
357      * @return new state
358      * @since 13.0
359      */
360     public FieldSpacecraftState<T> withAdditionalData(final FieldDataDictionary<T> newAdditional) {
361         return new FieldSpacecraftState<>(orbit, absPva, attitude, mass, massRate, newAdditional, additionalDot, false);
362     }
363 
364     /**
365      * Create a new instance with input additional data.
366      * @param newAdditionalDot additional derivatives
367      * @return new state
368      * @since 13.0
369      */
370     public FieldSpacecraftState<T> withAdditionalStatesDerivatives(final FieldArrayDictionary<T> newAdditionalDot) {
371         return new FieldSpacecraftState<>(orbit, absPva, attitude, mass, massRate, additional, newAdditionalDot, false);
372     }
373 
374     /** Add an additional data.
375      * <p>
376      * {@link FieldSpacecraftState SpacecraftState} instances are immutable,
377      * so this method does <em>not</em> change the instance, but rather
378      * creates a new instance, which has the same orbit, attitude, mass
379      * and additional states as the original instance, except it also
380      * has the specified state. If the original instance already had an
381      * additional state with the same name, it will be overridden. If it
382      * did not have any additional state with that name, the new instance
383      * will have one more additional state than the original instance.
384      * </p>
385      * @param name name of the additional data (names containing "orekit"
386      *      * with any case are reserved for the library internal use)
387      * @param value value of the additional data
388      * @return a new instance, with the additional data added
389      * @see #hasAdditionalData(String)
390      * @see #getAdditionalData(String)
391      * @see #getAdditionalDataValues()
392      */
393     @SuppressWarnings("unchecked") // cast including generic type is checked and unitary tested
394     public final FieldSpacecraftState<T> addAdditionalData(final String name, final Object value) {
395         final FieldDataDictionary<T> newDict = new FieldDataDictionary<>(additional);
396         if (value instanceof CalculusFieldElement[]) {
397             final CalculusFieldElement<T>[] valueArray = (CalculusFieldElement<T>[]) value;
398             newDict.put(name, valueArray.clone());
399         } else if (value instanceof CalculusFieldElement) {
400             final CalculusFieldElement<T>[] valueArray = MathArrays.buildArray(mass.getField(), 1);
401             valueArray[0] = (CalculusFieldElement<T>) value;
402             newDict.put(name, valueArray);
403         } else {
404             newDict.put(name, value);
405         }
406         return withAdditionalData(newDict);
407     }
408 
409     /** Add an additional state derivative.
410     * {@link FieldSpacecraftState FieldSpacecraftState} instances are immutable,
411      * so this method does <em>not</em> change the instance, but rather
412      * creates a new instance, which has the same components as the original
413      * instance, except it also has the specified state derivative. If the
414      * original instance already had an additional state derivative with the
415      * same name, it will be overridden. If it did not have any additional
416      * state derivative with that name, the new instance will have one more
417      * additional state derivative than the original instance.
418      * @param name name of the additional state derivative
419      * @param value value of the additional state derivative
420      * @return a new instance, with the additional state derivative added
421      * @see #hasAdditionalStateDerivative(String)
422      * @see #getAdditionalStateDerivative(String)
423      * @see #getAdditionalStatesDerivatives()
424      */
425     @SafeVarargs
426     public final FieldSpacecraftState<T> addAdditionalStateDerivative(final String name, final T... value) {
427         final FieldArrayDictionary<T> newDict = new FieldArrayDictionary<>(additionalDot);
428         newDict.put(name, value.clone());
429         return withAdditionalStatesDerivatives(newDict);
430     }
431 
432     /** Check orbit and attitude dates are equal.
433      * @param orbitN the orbit
434      * @param attitudeN attitude
435      * @exception IllegalArgumentException if orbit and attitude dates
436      * are not equal
437      */
438     private void checkConsistency(final FieldOrbit<T> orbitN, final FieldAttitude<T> attitudeN)
439         throws IllegalArgumentException {
440         checkDateAndFrameConsistency(attitudeN, orbitN.getDate(), orbitN.getFrame());
441     }
442 
443     /** Check if the state contains an orbit part.
444      * <p>
445      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
446      * position-velocity-acceleration} or an {@link FieldOrbit orbit}.
447      * </p>
448      * @return true if state contains an orbit (in which case {@link #getOrbit()}
449      * will not throw an exception), or false if the state contains an
450      * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
451      * will not throw an exception)
452      */
453     public boolean isOrbitDefined() {
454         return orbit != null;
455     }
456 
457     /**
458      * Check FieldAbsolutePVCoordinates and attitude dates are equal.
459      * @param absPva   position-velocity-acceleration
460      * @param attitude attitude
461      * @param <T>      the type of the field elements
462      * @exception IllegalArgumentException if orbit and attitude dates are not equal
463      */
464     private static <T extends CalculusFieldElement<T>> void checkConsistency(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude)
465         throws IllegalArgumentException {
466         checkDateAndFrameConsistency(attitude, absPva.getDate(), absPva.getFrame());
467     }
468 
469     /** Check attitude frame and epoch.
470      * @param attitude attitude
471      * @param date epoch to verify
472      * @param frame frame to verify
473      * @param <T> type of the elements
474      */
475     private static <T extends CalculusFieldElement<T>> void checkDateAndFrameConsistency(final FieldAttitude<T> attitude,
476                                                                                          final FieldAbsoluteDate<T> date,
477                                                                                          final Frame frame) {
478         if (date.durationFrom(attitude.getDate()).abs().getReal() >
479                 DATE_INCONSISTENCY_THRESHOLD) {
480             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
481                     date, attitude.getDate());
482         }
483         if (frame != attitude.getReferenceFrame()) {
484             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
485                     frame.getName(),
486                     attitude.getReferenceFrame().getName());
487         }
488     }
489 
490     /** Get a time-shifted state.
491      * <p>
492      * The state can be slightly shifted to close dates. This shift is based on
493      * a simple Keplerian model for orbit and Taylor series for absolute position-velocity.
494      * A linear extrapolation for attitude is used, taking the spin rate into account.
495      * The mass is changed linearly, as well as additional states
496      * if their derivatives are available. It is <em>not</em> intended as a replacement for proper orbit
497      * and attitude propagation but should be sufficient for small time shifts
498      * or coarse accuracy.
499      * </p>
500      * <p>
501      * As a rough order of magnitude, the following table shows the extrapolation
502      * errors obtained between this simple shift method and an {@link
503      * org.orekit.propagation.numerical.FieldNumericalPropagator numerical
504      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
505      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
506      * Beware that these results will be different for other orbits.
507      * </p>
508      * <table border="1">
509      * <caption>Extrapolation Error</caption>
510      * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
511      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
512      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
513      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
514      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
515      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
516      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
517      * </table>
518      * @param dt time shift in seconds
519      * @return a new state, shifted with respect to the instance (which is immutable)
520      */
521     @Override
522     public FieldSpacecraftState<T> shiftedBy(final double dt) {
523         if (isOrbitDefined()) {
524             return new FieldSpacecraftState<>(orbit.shiftedBy(dt), null, attitude.shiftedBy(dt), mass.add(massRate.multiply(dt)),
525                                               massRate, shiftAdditional(dt), new FieldArrayDictionary<>(additionalDot), false);
526         } else {
527             return new FieldSpacecraftState<>(null, absPva.shiftedBy(dt), attitude.shiftedBy(dt), mass.add(massRate.multiply(dt)),
528                                               massRate, shiftAdditional(dt), new FieldArrayDictionary<>(additionalDot), false);
529         }
530     }
531 
532     /** Get a time-shifted state.
533      * <p>
534      * The state can be slightly shifted to close dates. This shift is based on
535      * a simple Keplerian model for orbit and Taylor series for absolute position-velocity.
536      * A linear extrapolation for attitude is used, taking the spin rate into account.
537      * The mass is changed linearly, as well as additional states
538      * if their derivatives are available. It is <em>not</em> intended as a replacement for proper orbit
539      * and attitude propagation but should be sufficient for small time shifts
540      * or coarse accuracy.
541      * </p>
542      * <p>
543      * As a rough order of magnitude, the following table shows the extrapolation
544      * errors obtained between this simple shift method and an {@link
545      * org.orekit.propagation.numerical.FieldNumericalPropagator numerical
546      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
547      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
548      * Beware that these results will be different for other orbits.
549      * </p>
550      * <table border="1">
551      * <caption>Extrapolation Error</caption>
552      * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
553      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
554      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
555      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
556      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
557      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
558      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
559      * </table>
560      * @param dt time shift in seconds
561      * @return a new state, shifted with respect to the instance (which is immutable)
562      */
563     @Override
564     public FieldSpacecraftState<T> shiftedBy(final T dt) {
565         if (isOrbitDefined()) {
566             return new FieldSpacecraftState<>(orbit.shiftedBy(dt), null, attitude.shiftedBy(dt), mass.add(massRate.multiply(dt)),
567                                               massRate, shiftAdditional(dt), new FieldArrayDictionary<>(additionalDot), false);
568         } else {
569             return new FieldSpacecraftState<>(null, absPva.shiftedBy(dt), attitude.shiftedBy(dt), mass.add(massRate.multiply(dt)),
570                                               massRate, shiftAdditional(dt), new FieldArrayDictionary<>(additionalDot), false);
571         }
572     }
573 
574     /** Shift additional data.
575      * @param dt time shift in seconds
576      * @return shifted additional data
577      * @since 11.1.1
578      */
579     private FieldDataDictionary<T> shiftAdditional(final double dt) {
580 
581         // fast handling when there are no derivatives at all
582         if (additionalDot.size() == 0) {
583             return additional;
584         }
585 
586         // there are derivatives, we need to take them into account in the additional state
587         final FieldDataDictionary<T> shifted = new FieldDataDictionary<>(additional);
588         for (final FieldArrayDictionary<T>.Entry dotEntry : additionalDot.getData()) {
589             final FieldDataDictionary<T>.Entry entry = shifted.getEntry(dotEntry.getKey());
590             if (entry != null) {
591                 entry.scaledIncrement(dt, dotEntry);
592             }
593         }
594 
595         return shifted;
596 
597     }
598 
599     /** Shift additional states.
600      * @param dt time shift in seconds
601      * @return shifted additional states
602      * @since 11.1.1
603      */
604     private FieldDataDictionary<T> shiftAdditional(final T dt) {
605 
606         // fast handling when there are no derivatives at all
607         if (additionalDot.size() == 0) {
608             return additional;
609         }
610 
611         // there are derivatives, we need to take them into account in the additional state
612         final FieldDataDictionary<T> shifted = new FieldDataDictionary<>(additional);
613         for (final FieldArrayDictionary<T>.Entry dotEntry : additionalDot.getData()) {
614             final FieldDataDictionary<T>.Entry entry = shifted.getEntry(dotEntry.getKey());
615             if (entry != null) {
616                 entry.scaledIncrement(dt, dotEntry);
617             }
618         }
619 
620         return shifted;
621 
622     }
623 
624     /** Get the absolute position-velocity-acceleration.
625      * <p>
626      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
627      * position-velocity-acceleration} or an {@link FieldOrbit orbit}. Which
628      * one is present can be checked using {@link #isOrbitDefined()}.
629      * </p>
630      * @return absolute position-velocity-acceleration
631      * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
632      * which mean the state rather contains an {@link FieldOrbit}
633      * @see #isOrbitDefined()
634      * @see #getOrbit()
635      */
636     public FieldAbsolutePVCoordinates<T> getAbsPVA() throws OrekitIllegalStateException {
637         if (isOrbitDefined()) {
638             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
639         }
640         return absPva;
641     }
642 
643     /** Get the current orbit.
644      * <p>
645      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
646      * position-velocity-acceleration} or an {@link FieldOrbit orbit}. Which
647      * one is present can be checked using {@link #isOrbitDefined()}.
648      * </p>
649      * @return the orbit
650      * @exception OrekitIllegalStateException if orbit is null,
651      * which means the state rather contains an {@link FieldAbsolutePVCoordinates absolute
652      * position-velocity-acceleration}
653      * @see #isOrbitDefined()
654      * @see #getAbsPVA()
655      */
656     public FieldOrbit<T> getOrbit() throws OrekitIllegalStateException {
657         if (orbit == null) {
658             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
659         }
660         return orbit;
661     }
662 
663     /** {@inheritDoc} */
664     @Override
665     public FieldAbsoluteDate<T> getDate() {
666         return isOrbitDefined() ? orbit.getDate() : absPva.getDate();
667     }
668 
669     /** Get the defining frame.
670      * @return the frame in which state is defined
671      */
672     public Frame getFrame() {
673         return isOrbitDefined() ? orbit.getFrame() : absPva.getFrame();
674     }
675 
676 
677     /** Check if an additional data is available.
678      * @param name name of the additional data
679      * @return true if the additional data is available
680      * @see #addAdditionalData(String, Object)
681      * @see #getAdditionalData(String)
682      * @see #getAdditionalDataValues()
683      */
684     public boolean hasAdditionalData(final String name) {
685         return additional.getEntry(name) != null;
686     }
687 
688     /** Check if an additional state derivative is available.
689      * @param name name of the additional state derivative
690      * @return true if the additional state derivative is available
691      * @see #addAdditionalStateDerivative(String, CalculusFieldElement...)
692      * @see #getAdditionalStateDerivative(String)
693      * @see #getAdditionalStatesDerivatives()
694      */
695     public boolean hasAdditionalStateDerivative(final String name) {
696         return additionalDot.getEntry(name) != null;
697     }
698 
699     /** Check if two instances have the same set of additional states available.
700      * <p>
701      * Only the names and dimensions of the additional states are compared,
702      * not their values.
703      * </p>
704      * @param state state to compare to instance
705      * @exception MathIllegalArgumentException if an additional state does not have
706      * the same dimension in both states
707      */
708     @SuppressWarnings("unchecked") // cast including generic type is checked and unitary tested
709     public void ensureCompatibleAdditionalStates(final FieldSpacecraftState<T> state)
710         throws MathIllegalArgumentException {
711 
712         // check instance additional states is a subset of the other one
713         for (final FieldDataDictionary<T>.Entry entry : additional.getData()) {
714             final Object other = state.additional.get(entry.getKey());
715             if (other == null) {
716                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
717                                           entry.getKey());
718             }
719             if (other instanceof CalculusFieldElement[]) {
720                 final CalculusFieldElement<T>[] arrayOther = (CalculusFieldElement<T>[]) other;
721                 final CalculusFieldElement<T>[] arrayEntry = (CalculusFieldElement<T>[]) entry.getValue();
722                 if (arrayEntry.length != arrayOther.length) {
723                     throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH, arrayOther.length, arrayEntry.length);
724                 }
725             }
726         }
727 
728         // check instance additional states derivatives is a subset of the other one
729         for (final FieldArrayDictionary<T>.Entry entry : additionalDot.getData()) {
730             final T[] other = state.additionalDot.get(entry.getKey());
731             if (other == null) {
732                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
733                                           entry.getKey());
734             }
735             if (other.length != entry.getValue().length) {
736                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
737                                                     other.length, entry.getValue().length);
738             }
739         }
740 
741         if (state.additional.size() > additional.size()) {
742             // the other state has more additional states
743             for (final FieldDataDictionary<T>.Entry entry : state.additional.getData()) {
744                 if (additional.getEntry(entry.getKey()) == null) {
745                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
746                                               entry.getKey());
747                 }
748             }
749         }
750 
751         if (state.additionalDot.size() > additionalDot.size()) {
752             // the other state has more additional states
753             for (final FieldArrayDictionary<T>.Entry entry : state.additionalDot.getData()) {
754                 if (additionalDot.getEntry(entry.getKey()) == null) {
755                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA,
756                                               entry.getKey());
757                 }
758             }
759         }
760 
761     }
762 
763     /**
764      * Get an additional state.
765      *
766      * @param name name of the additional state
767      * @return value of the additional state
768      * @see #hasAdditionalData(String)
769      * @see #getAdditionalDataValues()
770      */
771     @SuppressWarnings("unchecked") // cast including generic type is checked and unitary tested
772     public T[] getAdditionalState(final String name) {
773         final Object data = getAdditionalData(name);
774         if (data instanceof CalculusFieldElement[]) {
775             return (T[]) data;
776         } else if (data instanceof CalculusFieldElement) {
777             final T[] values = MathArrays.buildArray(mass.getField(), 1);
778             values[0] = (T) data;
779             return values;
780         } else {
781             throw new OrekitException(OrekitMessages.ADDITIONAL_STATE_BAD_TYPE, name);
782         }
783     }
784 
785 
786     /**
787      * Get an additional data.
788      *
789      * @param name name of the additional state
790      * @return value of the additional state
791      * @see #addAdditionalData(String, Object)
792      * @see #hasAdditionalData(String)
793      * @see #getAdditionalDataValues()
794      * @since 13.0
795      */
796     public Object getAdditionalData(final String name) {
797         final FieldDataDictionary<T>.Entry entry = additional.getEntry(name);
798         if (entry == null) {
799             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name);
800         }
801         return entry.getValue();
802     }
803 
804     /** Get an additional state derivative.
805      * @param name name of the additional state derivative
806      * @return value of the additional state derivative
807      * @see #addAdditionalStateDerivative(String, CalculusFieldElement...)
808      * @see #hasAdditionalStateDerivative(String)
809      * @see #getAdditionalStatesDerivatives()
810      * @since 11.1
811      */
812     public T[] getAdditionalStateDerivative(final String name) {
813         final FieldArrayDictionary<T>.Entry entry = additionalDot.getEntry(name);
814         if (entry == null) {
815             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_DATA, name);
816         }
817         return entry.getValue();
818     }
819 
820     /** Get an unmodifiable map of additional states.
821      * @return unmodifiable map of additional states
822      * @see #addAdditionalData(String, Object)
823      * @see #hasAdditionalData(String)
824      * @see #getAdditionalData(String)
825      * @since 11.1
826      */
827     public FieldDataDictionary<T> getAdditionalDataValues() {
828         return additional;
829     }
830 
831     /** Get an unmodifiable map of additional states derivatives.
832      * @return unmodifiable map of additional states derivatives
833      * @see #addAdditionalStateDerivative(String, CalculusFieldElement...)
834      * @see #hasAdditionalStateDerivative(String)
835      * @see #getAdditionalStateDerivative(String)
836     * @since 11.1
837       */
838     public FieldArrayDictionary<T> getAdditionalStatesDerivatives() {
839         return additionalDot.unmodifiableView();
840     }
841 
842     /** Compute the transform from state defining frame to spacecraft frame.
843      * <p>The spacecraft frame origin is at the point defined by the orbit,
844      * and its orientation is defined by the attitude.</p>
845      * @return transform from specified frame to current spacecraft frame
846      */
847     public FieldTransform<T> toTransform() {
848         final TimeStampedFieldPVCoordinates<T> pv = getPVCoordinates();
849         return new FieldTransform<>(pv.getDate(),
850                                     new FieldTransform<>(pv.getDate(), pv.negate()),
851                                     new FieldTransform<>(pv.getDate(), attitude.getOrientation()));
852     }
853 
854     /** Compute the static transform from state defining frame to spacecraft frame.
855      * @return static transform from specified frame to current spacecraft frame
856      * @see #toTransform()
857      * @since 12.0
858      */
859     public FieldStaticTransform<T> toStaticTransform() {
860         return FieldStaticTransform.of(getDate(), getPosition().negate(), attitude.getRotation());
861     }
862 
863     /** Get the position in state definition frame.
864      * @return position in state definition frame
865      * @since 12.0
866      */
867     public FieldVector3D<T> getPosition() {
868         return isOrbitDefined() ? orbit.getPosition() : absPva.getPosition();
869     }
870 
871     /** Get the velocity in state definition frame.
872      * @return velocity in state definition frame
873      * @since 13.1
874      */
875     public FieldVector3D<T> getVelocity() {
876         return isOrbitDefined() ? orbit.getVelocity() : absPva.getVelocity();
877     }
878 
879     /** Get the {@link TimeStampedFieldPVCoordinates} in orbit definition frame.
880      * <p>
881      * Compute the position and velocity of the satellite. This method caches its
882      * results, and recompute them only when the method is called with a new value
883      * for mu. The result is provided as a reference to the internally cached
884      * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
885      * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
886      * </p>
887      * @return pvCoordinates in orbit definition frame
888      */
889     public TimeStampedFieldPVCoordinates<T> getPVCoordinates() {
890         return isOrbitDefined() ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
891     }
892 
893     /** Get the position in given output frame.
894      * @param outputFrame frame in which position should be defined
895      * @return position in given output frame
896      * @since 12.0
897      * @see #getPVCoordinates(Frame)
898      */
899     public FieldVector3D<T> getPosition(final Frame outputFrame) {
900         return isOrbitDefined() ? orbit.getPosition(outputFrame) : absPva.getPosition(outputFrame);
901     }
902 
903     /** Get the {@link TimeStampedFieldPVCoordinates} in given output frame.
904      * <p>
905      * Compute the position and velocity of the satellite. This method caches its
906      * results, and recompute them only when the method is called with a new value
907      * for mu. The result is provided as a reference to the internally cached
908      * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
909      * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
910      * </p>
911      * @param outputFrame frame in which coordinates should be defined
912      * @return pvCoordinates in orbit definition frame
913      */
914     public TimeStampedFieldPVCoordinates<T> getPVCoordinates(final Frame outputFrame) {
915         return isOrbitDefined() ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
916     }
917 
918     /** Get the attitude.
919      * @return the attitude.
920      */
921     public FieldAttitude<T> getAttitude() {
922         return attitude;
923     }
924 
925     /** Gets the current mass.
926      * @return the mass (kg)
927      */
928     public T getMass() {
929         return mass;
930     }
931 
932     /** Gets the current mass rate.
933      * @return the mass (kg/S)
934      * @since 14.0
935      */
936     public T getMassRate() {
937         return massRate;
938     }
939 
940     /**To convert a FieldSpacecraftState instance into a SpacecraftState instance.
941      *
942      * @return SpacecraftState instance with the same properties
943      */
944     @SuppressWarnings("unchecked") // cast including generic type is checked and unitary tested
945     public SpacecraftState toSpacecraftState() {
946         final DataDictionary dictionary;
947         if (additional.size() == 0) {
948             dictionary = new DataDictionary();
949         } else {
950             dictionary = new DataDictionary(additional.size());
951             for (final FieldDataDictionary<T>.Entry entry : additional.getData()) {
952                 if (entry.getValue() instanceof CalculusFieldElement[]) {
953                     final CalculusFieldElement<T>[] entryArray  = (CalculusFieldElement<T>[]) entry.getValue();
954                     final double[] realArray = new double[entryArray.length];
955                     for (int k = 0; k < realArray.length; ++k) {
956                         realArray[k] = entryArray[k].getReal();
957                     }
958                     dictionary.put(entry.getKey(), realArray);
959                 } else {
960                     dictionary.put(entry.getKey(), entry.getValue());
961                 }
962             }
963         }
964         final DoubleArrayDictionary dictionaryDot;
965         if (additionalDot.size() == 0) {
966             dictionaryDot = new DoubleArrayDictionary();
967         } else {
968             dictionaryDot = new DoubleArrayDictionary(additionalDot.size());
969             for (final FieldArrayDictionary<T>.Entry entry : additionalDot.getData()) {
970                 final double[] array = new double[entry.getValue().length];
971                 for (int k = 0; k < array.length; ++k) {
972                     array[k] = entry.getValue()[k].getReal();
973                 }
974                 dictionaryDot.put(entry.getKey(), array);
975             }
976         }
977         if (isOrbitDefined()) {
978             return new SpacecraftState(orbit.toOrbit(), attitude.toAttitude(),
979                                        mass.getReal(), dictionary, dictionaryDot);
980         } else {
981             return new SpacecraftState(absPva.toAbsolutePVCoordinates(),
982                                        attitude.toAttitude(), mass.getReal(),
983                                        dictionary, dictionaryDot);
984         }
985     }
986 
987     @Override
988     public String toString() {
989         return "FieldSpacecraftState{" +
990                 "orbit=" + orbit +
991                 ", absPva=" + absPva +
992                 ", attitude=" + attitude +
993                 ", mass=" + mass +
994                 ", massRate=" + massRate +
995                 ", additional=" + additional +
996                 ", additionalDot=" + additionalDot +
997                 '}';
998     }
999 
1000 }