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