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