1   /* Copyright 2002-2022 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 java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.stream.Stream;
25  
26  import org.hipparchus.CalculusFieldElement;
27  import org.hipparchus.Field;
28  import org.hipparchus.analysis.interpolation.FieldHermiteInterpolator;
29  import org.hipparchus.exception.LocalizedCoreFormats;
30  import org.hipparchus.exception.MathIllegalArgumentException;
31  import org.hipparchus.exception.MathIllegalStateException;
32  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
33  import org.hipparchus.util.FastMath;
34  import org.hipparchus.util.MathArrays;
35  import org.orekit.attitudes.FieldAttitude;
36  import org.orekit.attitudes.LofOffset;
37  import org.orekit.errors.OrekitException;
38  import org.orekit.errors.OrekitIllegalArgumentException;
39  import org.orekit.errors.OrekitIllegalStateException;
40  import org.orekit.errors.OrekitMessages;
41  import org.orekit.frames.FieldTransform;
42  import org.orekit.frames.Frame;
43  import org.orekit.frames.LOFType;
44  import org.orekit.orbits.FieldOrbit;
45  import org.orekit.orbits.PositionAngle;
46  import org.orekit.time.FieldAbsoluteDate;
47  import org.orekit.time.FieldTimeInterpolable;
48  import org.orekit.time.FieldTimeShiftable;
49  import org.orekit.time.FieldTimeStamped;
50  import org.orekit.utils.DoubleArrayDictionary;
51  import org.orekit.utils.FieldAbsolutePVCoordinates;
52  import org.orekit.utils.FieldArrayDictionary;
53  import org.orekit.utils.FieldPVCoordinates;
54  import org.orekit.utils.TimeStampedFieldPVCoordinates;
55  
56  
57  /** This class is the representation of a complete state holding orbit, attitude
58   * and mass information at a given date.
59   *
60   * <p>It contains an {@link FieldOrbit orbital state} at a current
61   * {@link FieldAbsoluteDate} both handled by an {@link FieldOrbit}, plus the current
62   * mass and attitude. FieldOrbit and state are guaranteed to be consistent in terms
63   * of date and reference frame. The spacecraft state may also contain additional
64   * states, which are simply named double arrays which can hold any user-defined
65   * data.
66   * </p>
67   * <p>
68   * The state can be slightly shifted to close dates. This shift is based on
69   * a simple Keplerian model for orbit, a linear extrapolation for attitude
70   * taking the spin rate into account and no mass change. It is <em>not</em>
71   * intended as a replacement for proper orbit and attitude propagation but
72   * should be sufficient for either small time shifts or coarse accuracy.
73   * </p>
74   * <p>
75   * The instance {@code FieldSpacecraftState} is guaranteed to be immutable.
76   * </p>
77   * @see org.orekit.propagation.numerical.NumericalPropagator
78   * @author Fabien Maussion
79   * @author V&eacute;ronique Pommier-Maurussane
80   * @author Luc Maisonobe
81   * @author Vincent Mouraux
82   */
83  public class FieldSpacecraftState <T extends CalculusFieldElement<T>>
84      implements FieldTimeStamped<T>, FieldTimeShiftable<FieldSpacecraftState<T>, T>, FieldTimeInterpolable<FieldSpacecraftState<T>, T> {
85  
86      /** Default mass. */
87      private static final double DEFAULT_MASS = 1000.0;
88  
89      /**
90       * tolerance on date comparison in {@link #checkConsistency(FieldOrbit<T>, FieldAttitude<T>)}. 100 ns
91       * corresponds to sub-mm accuracy at LEO orbital velocities.
92       */
93      private static final double DATE_INCONSISTENCY_THRESHOLD = 100e-9;
94  
95      /** Orbital state. */
96      private final FieldOrbit<T> orbit;
97  
98      /** Trajectory state, when it is not an orbit. */
99      private final FieldAbsolutePVCoordinates<T> absPva;
100 
101     /** FieldAttitude<T>. */
102     private final FieldAttitude<T> attitude;
103 
104     /** Current mass (kg). */
105     private final T mass;
106 
107     /** Additional states. */
108     private final FieldArrayDictionary<T> additional;
109 
110     /** Additional states derivatives.
111      * @since 11.1
112      */
113     private final FieldArrayDictionary<T> additionalDot;
114 
115     /** Build a spacecraft state from orbit only.
116      * <p>FieldAttitude and mass are set to unspecified non-null arbitrary values.</p>
117      * @param orbit the orbit
118      */
119     public FieldSpacecraftState(final FieldOrbit<T> orbit) {
120         this(orbit,
121              new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
122              orbit.getA().getField().getZero().add(DEFAULT_MASS), (FieldArrayDictionary<T>) null);
123     }
124 
125     /** Build a spacecraft state from orbit and attitude.
126      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
127      * @param orbit the orbit
128      * @param attitude attitude
129      * @exception IllegalArgumentException if orbit and attitude dates
130      * or frames are not equal
131      */
132     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude)
133         throws IllegalArgumentException {
134         this(orbit, attitude, orbit.getA().getField().getZero().add(DEFAULT_MASS), (FieldArrayDictionary<T>) null);
135     }
136 
137     /** Create a new instance from orbit and mass.
138      * <p>FieldAttitude law is set to an unspecified default attitude.</p>
139      * @param orbit the orbit
140      * @param mass the mass (kg)
141      */
142     public FieldSpacecraftState(final FieldOrbit<T> orbit, final T mass) {
143         this(orbit,
144              new LofOffset(orbit.getFrame(),
145                            LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
146              mass, (FieldArrayDictionary<T>) null);
147     }
148 
149     /** Build a spacecraft state from orbit, attitude and mass.
150      * @param orbit the orbit
151      * @param attitude attitude
152      * @param mass the mass (kg)
153      * @exception IllegalArgumentException if orbit and attitude dates
154      * or frames are not equal
155      */
156     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final T mass)
157         throws IllegalArgumentException {
158         this(orbit, attitude, mass, (FieldArrayDictionary<T>) null);
159     }
160 
161     /** Build a spacecraft state from orbit and additional states.
162      * <p>FieldAttitude and mass are set to unspecified non-null arbitrary values.</p>
163      * @param orbit the orbit
164      * @param additional additional states
165      * @deprecated as of 11.1, replacezd by {@link #FieldSpacecraftState(FieldOrbit, FieldArrayDictionary)}
166      */
167     @Deprecated
168     public FieldSpacecraftState(final FieldOrbit<T> orbit, final Map<String, T[]> additional) {
169         this(orbit, new FieldArrayDictionary<>(orbit.getDate().getField(), additional));
170     }
171 
172     /** Build a spacecraft state from orbit and additional states.
173      * <p>FieldAttitude and mass are set to unspecified non-null arbitrary values.</p>
174      * @param orbit the orbit
175      * @param additional additional states
176      * @since 11.1
177      */
178     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldArrayDictionary<T> additional) {
179         this(orbit,
180              new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
181              orbit.getA().getField().getZero().add(DEFAULT_MASS), additional);
182     }
183 
184     /** Build a spacecraft state from orbit attitude and additional states.
185      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
186      * @param orbit the orbit
187      * @param attitude attitude
188      * @param additional additional states
189      * @exception IllegalArgumentException if orbit and attitude dates
190      * or frames are not equal
191      * @deprecated as of 11.1, replaced by {@link #FieldSpacecraftState(FieldOrbit, FieldAttitude, FieldArrayDictionary)}
192      */
193     @Deprecated
194     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final Map<String, T[]> additional)
195         throws IllegalArgumentException {
196         this(orbit, attitude, new FieldArrayDictionary<>(orbit.getDate().getField(), additional));
197     }
198 
199     /** Build a spacecraft state from orbit attitude and additional states.
200      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
201      * @param orbit the orbit
202      * @param attitude attitude
203      * @param additional additional states
204      * @exception IllegalArgumentException if orbit and attitude dates
205      * or frames are not equal
206      * @since 11.1
207      */
208     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final FieldArrayDictionary<T> additional)
209         throws IllegalArgumentException {
210         this(orbit, attitude, orbit.getA().getField().getZero().add(DEFAULT_MASS), additional);
211     }
212 
213     /** Create a new instance from orbit, mass and additional states.
214      * <p>FieldAttitude law is set to an unspecified default attitude.</p>
215      * @param orbit the orbit
216      * @param mass the mass (kg)
217      * @param additional additional states
218      * @deprecated as of 11.1, replaced by {@link #FieldSpacecraftState(FieldOrbit, CalculusFieldElement, FieldArrayDictionary)}
219      */
220     @Deprecated
221     public FieldSpacecraftState(final FieldOrbit<T> orbit, final T mass, final Map<String, T[]> additional) {
222         this(orbit, mass, new FieldArrayDictionary<>(orbit.getDate().getField(), additional));
223     }
224 
225     /** Create a new instance from orbit, mass and additional states.
226      * <p>FieldAttitude law is set to an unspecified default attitude.</p>
227      * @param orbit the orbit
228      * @param mass the mass (kg)
229      * @param additional additional states
230      * @since 11.1
231      */
232     public FieldSpacecraftState(final FieldOrbit<T> orbit, final T mass, final FieldArrayDictionary<T> additional) {
233         this(orbit,
234              new LofOffset(orbit.getFrame(), LOFType.LVLH_CCSDS).getAttitude(orbit, orbit.getDate(), orbit.getFrame()),
235              mass, additional);
236     }
237 
238     /** Build a spacecraft state from orbit, attitude, mass and additional states.
239      * @param orbit the orbit
240      * @param attitude attitude
241      * @param mass the mass (kg)
242      * @param additional additional states (may be null if no additional states are available)
243      * @deprecated as of 11.1, replaced by {@link #FieldSpacecraftState(FieldOrbit, FieldAttitude, CalculusFieldElement, FieldArrayDictionary)}
244      */
245     @Deprecated
246     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude,
247                                 final T mass, final Map<String, T[]> additional) {
248         this(orbit, attitude, mass, new FieldArrayDictionary<>(orbit.getDate().getField(), additional));
249     }
250 
251     /** Build a spacecraft state from orbit, attitude, mass and additional states.
252      * @param orbit the orbit
253      * @param attitude attitude
254      * @param mass the mass (kg)
255      * @param additional additional states (may be null if no additional states are available)
256      * @since 11.1
257      */
258     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude,
259                                 final T mass, final FieldArrayDictionary<T> additional) {
260         this(orbit, attitude, mass, additional, null);
261     }
262 
263     /** Build a spacecraft state from orbit, attitude, mass, additional states and derivatives.
264      * @param orbit the orbit
265      * @param attitude attitude
266      * @param mass the mass (kg)
267      * @param additional additional states (may be null if no additional states are available)
268      * @param additionalDot additional states derivatives(may be null if no additional states derivative sare available)
269      * @exception IllegalArgumentException if orbit and attitude dates
270      * or frames are not equal
271      * @since 11.1
272      */
273     public FieldSpacecraftState(final FieldOrbit<T> orbit, final FieldAttitude<T> attitude, final T mass,
274                                 final FieldArrayDictionary<T> additional,
275                                 final FieldArrayDictionary<T> additionalDot)
276         throws IllegalArgumentException {
277         checkConsistency(orbit, attitude);
278         this.orbit      = orbit;
279         this.attitude   = attitude;
280         this.mass       = mass;
281         this.absPva     = null;
282 
283         if (additional == null) {
284             this.additional = new FieldArrayDictionary<>(orbit.getDate().getField());
285         } else {
286             this.additional = new FieldArrayDictionary<>(additional);
287         }
288 
289         if (additionalDot == null) {
290             this.additionalDot = new FieldArrayDictionary<>(orbit.getDate().getField());
291         } else {
292 
293             this.additionalDot = new FieldArrayDictionary<>(additionalDot);
294         }
295 
296     }
297 
298     /** Convert a {@link FieldSpacecraftState}.
299      * @param field field to which the elements belong
300      * @param state state to convert
301      */
302     public FieldSpacecraftState(final Field<T> field, final SpacecraftState state) {
303 
304         if (state.isOrbitDefined()) {
305             final double[] stateD    = new double[6];
306             final double[] stateDotD = state.getOrbit().hasDerivatives() ? new double[6] : null;
307             state.getOrbit().getType().mapOrbitToArray(state.getOrbit(), PositionAngle.TRUE, stateD, stateDotD);
308             final T[] stateF    = MathArrays.buildArray(field, 6);
309             for (int i = 0; i < stateD.length; ++i) {
310                 stateF[i]    = field.getZero().add(stateD[i]);
311             }
312             final T[] stateDotF;
313             if (stateDotD == null) {
314                 stateDotF = null;
315             } else {
316                 stateDotF = MathArrays.buildArray(field, 6);
317                 for (int i = 0; i < stateDotD.length; ++i) {
318                     stateDotF[i] = field.getZero().add(stateDotD[i]);
319                 }
320             }
321 
322             final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, state.getDate());
323 
324             this.orbit    = state.getOrbit().getType().mapArrayToOrbit(stateF, stateDotF,
325                                                                        PositionAngle.TRUE,
326                                                                        dateF, field.getZero().add(state.getMu()), state.getFrame());
327             this.absPva   = null;
328 
329         } else {
330             final T zero = field.getZero();
331             final T x = zero.add(state.getPVCoordinates().getPosition().getX());
332             final T y = zero.add(state.getPVCoordinates().getPosition().getY());
333             final T z = zero.add(state.getPVCoordinates().getPosition().getZ());
334             final T vx = zero.add(state.getPVCoordinates().getVelocity().getX());
335             final T vy = zero.add(state.getPVCoordinates().getVelocity().getY());
336             final T vz = zero.add(state.getPVCoordinates().getVelocity().getZ());
337             final T ax = zero.add(state.getPVCoordinates().getAcceleration().getX());
338             final T ay = zero.add(state.getPVCoordinates().getAcceleration().getY());
339             final T az = zero.add(state.getPVCoordinates().getAcceleration().getZ());
340             final FieldPVCoordinates<T> pva_f = new FieldPVCoordinates<>(new FieldVector3D<>(x, y, z),
341                                                                          new FieldVector3D<>(vx, vy, vz),
342                                                                          new FieldVector3D<>(ax, ay, az));
343             final FieldAbsoluteDate<T> dateF = new FieldAbsoluteDate<>(field, state.getDate());
344             this.orbit  = null;
345             this.absPva = new FieldAbsolutePVCoordinates<>(state.getFrame(), dateF, pva_f);
346         }
347 
348         this.attitude = new FieldAttitude<>(field, state.getAttitude());
349         this.mass     = field.getZero().add(state.getMass());
350 
351         final DoubleArrayDictionary additionalD = state.getAdditionalStatesValues();
352         if (additionalD.size() == 0) {
353             this.additional = new FieldArrayDictionary<>(field);
354         } else {
355             this.additional = new FieldArrayDictionary<>(field, additionalD.size());
356             for (final DoubleArrayDictionary.Entry entry : additionalD.getData()) {
357                 this.additional.put(entry.getKey(), entry.getValue());
358             }
359         }
360         final DoubleArrayDictionary additionalDotD = state.getAdditionalStatesDerivatives();
361         if (additionalDotD.size() == 0) {
362             this.additionalDot = new FieldArrayDictionary<>(field);
363         } else {
364             this.additionalDot = new FieldArrayDictionary<>(field, additionalDotD.size());
365             for (final DoubleArrayDictionary.Entry entry : additionalDotD.getData()) {
366                 this.additionalDot.put(entry.getKey(), entry.getValue());
367             }
368         }
369 
370     }
371 
372     /** Build a spacecraft state from orbit only.
373      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
374      * @param absPva position-velocity-acceleration
375      */
376     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva) {
377         this(absPva,
378              new LofOffset(absPva.getFrame(),
379                            LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
380              absPva.getDate().getField().getZero().add(DEFAULT_MASS), (FieldArrayDictionary<T>) null);
381     }
382 
383     /** Build a spacecraft state from orbit and attitude.
384      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
385      * @param absPva position-velocity-acceleration
386      * @param attitude attitude
387      * @exception IllegalArgumentException if orbit and attitude dates
388      * or frames are not equal
389      */
390     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude)
391         throws IllegalArgumentException {
392         this(absPva, attitude, absPva.getDate().getField().getZero().add(DEFAULT_MASS), (FieldArrayDictionary<T>) null);
393     }
394 
395     /** Create a new instance from orbit and mass.
396      * <p>Attitude law is set to an unspecified default attitude.</p>
397      * @param absPva position-velocity-acceleration
398      * @param mass the mass (kg)
399      */
400     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final T mass) {
401         this(absPva,
402              new LofOffset(absPva.getFrame(),
403                            LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
404              mass, (FieldArrayDictionary<T>) null);
405     }
406 
407     /** Build a spacecraft state from orbit, attitude and mass.
408      * @param absPva position-velocity-acceleration
409      * @param attitude attitude
410      * @param mass the mass (kg)
411      * @exception IllegalArgumentException if orbit and attitude dates
412      * or frames are not equal
413      */
414     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude, final T mass)
415         throws IllegalArgumentException {
416         this(absPva, attitude, mass, (FieldArrayDictionary<T>) null);
417     }
418 
419     /** Build a spacecraft state from orbit only.
420      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
421      * @param absPva position-velocity-acceleration
422      * @param additional additional states
423      * @deprecated as of 11.1, replaced by {@link #FieldSpacecraftState(FieldAbsolutePVCoordinates, FieldArrayDictionary)}
424      */
425     @Deprecated
426     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final Map<String, T[]> additional) {
427         this(absPva, new FieldArrayDictionary<>(absPva.getDate().getField(), additional));
428     }
429 
430     /** Build a spacecraft state from orbit only.
431      * <p>Attitude and mass are set to unspecified non-null arbitrary values.</p>
432      * @param absPva position-velocity-acceleration
433      * @param additional additional states
434      * @since 11.1
435      */
436     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldArrayDictionary<T> additional) {
437         this(absPva,
438              new LofOffset(absPva.getFrame(),
439                            LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
440              absPva.getDate().getField().getZero().add(DEFAULT_MASS), additional);
441     }
442 
443     /** Build a spacecraft state from orbit and attitude.
444      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
445      * @param absPva position-velocity-acceleration
446      * @param attitude attitude
447      * @param additional additional states
448      * @exception IllegalArgumentException if orbit and attitude dates
449      * or frames are not equal
450      * @deprecated as of 11.1, replaced by {@link #FieldSpacecraftState(FieldAbsolutePVCoordinates, FieldAttitude, FieldArrayDictionary)}
451      */
452     @Deprecated
453     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude,
454                                 final Map<String, T[]> additional)
455         throws IllegalArgumentException {
456         this(absPva, attitude, new FieldArrayDictionary<>(absPva.getDate().getField(), additional));
457     }
458 
459     /** Build a spacecraft state from orbit and attitude.
460      * <p>Mass is set to an unspecified non-null arbitrary value.</p>
461      * @param absPva position-velocity-acceleration
462      * @param attitude attitude
463      * @param additional additional states
464      * @exception IllegalArgumentException if orbit and attitude dates
465      * or frames are not equal
466      * @since 11.1
467      */
468     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude,
469                                 final FieldArrayDictionary<T> additional)
470         throws IllegalArgumentException {
471         this(absPva, attitude, absPva.getDate().getField().getZero().add(DEFAULT_MASS), additional);
472     }
473 
474     /** Create a new instance from orbit and mass.
475      * <p>Attitude law is set to an unspecified default attitude.</p>
476      * @param absPva position-velocity-acceleration
477      * @param mass the mass (kg)
478      * @param additional additional states
479      * @deprecated as of 11.1, replaced by {@link #FieldSpacecraftState(FieldAbsolutePVCoordinates, CalculusFieldElement, FieldArrayDictionary)}
480      */
481     @Deprecated
482     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final T mass, final Map<String, T[]> additional) {
483         this(absPva, mass, new FieldArrayDictionary<>(absPva.getDate().getField(), additional));
484     }
485 
486     /** Create a new instance from orbit and mass.
487      * <p>Attitude law is set to an unspecified default attitude.</p>
488      * @param absPva position-velocity-acceleration
489      * @param mass the mass (kg)
490      * @param additional additional states
491      * @since 11.1
492      */
493     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final T mass, final FieldArrayDictionary<T> additional) {
494         this(absPva,
495              new LofOffset(absPva.getFrame(),
496                            LOFType.LVLH_CCSDS).getAttitude(absPva, absPva.getDate(), absPva.getFrame()),
497              mass, additional);
498     }
499 
500     /** Build a spacecraft state from orbit, attitude and mass.
501      * @param absPva position-velocity-acceleration
502      * @param attitude attitude
503      * @param mass the mass (kg)
504      * @param additional additional states (may be null if no additional states are available)
505      * @deprecated as of 11.1, replaced by {@link #FieldSpacecraftState(FieldAbsolutePVCoordinates, FieldAttitude, CalculusFieldElement, FieldArrayDictionary)}
506      */
507     @Deprecated
508     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude,
509                            final T mass, final Map<String, T[]> additional) {
510         this(absPva, attitude, mass, new FieldArrayDictionary<>(absPva.getDate().getField(), additional));
511     }
512 
513     /** Build a spacecraft state from orbit, attitude and mass.
514      * @param absPva position-velocity-acceleration
515      * @param attitude attitude
516      * @param mass the mass (kg)
517      * @param additional additional states (may be null if no additional states are available)
518      * @since 11.1
519      */
520     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude,
521                            final T mass, final FieldArrayDictionary<T> additional) {
522         this(absPva, attitude, mass, additional, null);
523     }
524 
525     /** Build a spacecraft state from orbit, attitude and mass.
526      * @param absPva position-velocity-acceleration
527      * @param attitude attitude
528      * @param mass the mass (kg)
529      * @param additional additional states (may be null if no additional states are available)
530      * @param additionalDot additional states derivatives(may be null if no additional states derivatives are available)
531      * @exception IllegalArgumentException if orbit and attitude dates
532      * or frames are not equal
533      * @since 11.1
534      */
535     public FieldSpacecraftState(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude, final T mass,
536                                 final FieldArrayDictionary<T> additional, final FieldArrayDictionary<T> additionalDot)
537         throws IllegalArgumentException {
538         checkConsistency(absPva, attitude);
539         this.orbit      = null;
540         this.absPva     = absPva;
541         this.attitude   = attitude;
542         this.mass       = mass;
543         if (additional == null) {
544             this.additional = new FieldArrayDictionary<T>(absPva.getDate().getField());
545         } else {
546             this.additional = new FieldArrayDictionary<T>(additional);
547         }
548         if (additionalDot == null) {
549             this.additionalDot = new FieldArrayDictionary<T>(absPva.getDate().getField());
550         } else {
551             this.additionalDot = new FieldArrayDictionary<T>(additionalDot);
552         }
553     }
554 
555     /** Add an additional state.
556      * <p>
557      * {@link FieldSpacecraftState SpacecraftState} instances are immutable,
558      * so this method does <em>not</em> change the instance, but rather
559      * creates a new instance, which has the same orbit, attitude, mass
560      * and additional states as the original instance, except it also
561      * has the specified state. If the original instance already had an
562      * additional state with the same name, it will be overridden. If it
563      * did not have any additional state with that name, the new instance
564      * will have one more additional state than the original instance.
565      * </p>
566      * @param name name of the additional state
567      * @param value value of the additional state
568      * @return a new instance, with the additional state added
569      * @see #hasAdditionalState(String)
570      * @see #getAdditionalState(String)
571      * @see #getAdditionalStates()
572      */
573     @SafeVarargs
574     public final FieldSpacecraftState<T> addAdditionalState(final String name, final T... value) {
575         final FieldArrayDictionary<T> newDict = new FieldArrayDictionary<>(additional);
576         newDict.put(name, value.clone());
577         if (absPva == null) {
578             return new FieldSpacecraftState<>(orbit, attitude, mass, newDict, additionalDot);
579         } else {
580             return new FieldSpacecraftState<>(absPva, attitude, mass, newDict, additionalDot);
581         }
582     }
583 
584     /** Add an additional state derivative.
585     * {@link FieldSpacecraftState FieldSpacecraftState} instances are immutable,
586      * so this method does <em>not</em> change the instance, but rather
587      * creates a new instance, which has the same components as the original
588      * instance, except it also has the specified state derivative. If the
589      * original instance already had an additional state derivative with the
590      * same name, it will be overridden. If it did not have any additional
591      * state derivative with that name, the new instance will have one more
592      * additional state derivative than the original instance.
593      * @param name name of the additional state derivative
594      * @param value value of the additional state derivative
595      * @return a new instance, with the additional state derivative added
596      * @see #hasAdditionalStateDerivative(String)
597      * @see #getAdditionalStateDerivative(String)
598      * @see #getAdditionalStatesDerivatives()
599      */
600     @SafeVarargs
601     public final FieldSpacecraftState<T> addAdditionalStateDerivative(final String name, final T... value) {
602         final FieldArrayDictionary<T> newDict = new FieldArrayDictionary<>(additionalDot);
603         newDict.put(name, value.clone());
604         if (absPva == null) {
605             return new FieldSpacecraftState<>(orbit, attitude, mass, additional, newDict);
606         } else {
607             return new FieldSpacecraftState<>(absPva, attitude, mass, additional, newDict);
608         }
609     }
610 
611     /** Check orbit and attitude dates are equal.
612      * @param orbitN the orbit
613      * @param attitudeN attitude
614      * @exception IllegalArgumentException if orbit and attitude dates
615      * are not equal
616      */
617     private void checkConsistency(final FieldOrbit<T> orbitN, final FieldAttitude<T> attitudeN)
618         throws IllegalArgumentException {
619         if (orbitN.getDate().durationFrom(attitudeN.getDate()).abs().getReal() >
620             DATE_INCONSISTENCY_THRESHOLD) {
621 
622             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
623                                                      orbitN.getDate(), attitudeN.getDate());
624         }
625 
626         if (orbitN.getFrame() != attitudeN.getReferenceFrame()) {
627             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
628                                                      orbitN.getFrame().getName(),
629                                                      attitudeN.getReferenceFrame().getName());
630         }
631     }
632 
633     /** Check if the state contains an orbit part.
634      * <p>
635      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
636      * position-velocity-acceleration} or an {@link FieldOrbit orbit}.
637      * </p>
638      * @return true if state contains an orbit (in which case {@link #getOrbit()}
639      * will not throw an exception), or false if the state contains an
640      * absolut position-velocity-acceleration (in which case {@link #getAbsPVA()}
641      * will not throw an exception)
642      */
643     public boolean isOrbitDefined() {
644         return orbit != null;
645     }
646 
647     /**
648      * Check FieldAbsolutePVCoordinates and attitude dates are equal.
649      * @param absPva   position-velocity-acceleration
650      * @param attitude attitude
651      * @param <T>      the type of the field elements
652      * @exception IllegalArgumentException if orbit and attitude dates are not equal
653      */
654     private static <T extends CalculusFieldElement<T>> void checkConsistency(final FieldAbsolutePVCoordinates<T> absPva, final FieldAttitude<T> attitude)
655         throws IllegalArgumentException {
656         if (FastMath.abs(absPva.getDate().durationFrom(attitude.getDate())).getReal() >
657             DATE_INCONSISTENCY_THRESHOLD) {
658             throw new OrekitIllegalArgumentException(OrekitMessages.ORBIT_AND_ATTITUDE_DATES_MISMATCH,
659                                                      absPva.getDate(), attitude.getDate());
660         }
661         if (absPva.getFrame() != attitude.getReferenceFrame()) {
662             throw new OrekitIllegalArgumentException(OrekitMessages.FRAMES_MISMATCH,
663                                                      absPva.getFrame().getName(),
664                                                      attitude.getReferenceFrame().getName());
665         }
666     }
667 
668     /** Get a time-shifted state.
669      * <p>
670      * The state can be slightly shifted to close dates. This shift is based on
671      * a simple Keplerian model for orbit, a linear extrapolation for attitude
672      * taking the spin rate into account and neither mass nor additional states
673      * changes. It is <em>not</em> intended as a replacement for proper orbit
674      * and attitude propagation but should be sufficient for small time shifts
675      * or coarse accuracy.
676      * </p>
677      * <p>
678      * As a rough order of magnitude, the following table shows the extrapolation
679      * errors obtained between this simple shift method and an {@link
680      * org.orekit.propagation.numerical.FieldNumericalPropagator numerical
681      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
682      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
683      * Beware that these results will be different for other orbits.
684      * </p>
685      * <table border="1">
686      * <caption>Extrapolation Error</caption>
687      * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
688      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
689      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
690      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
691      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
692      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
693      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
694      * </table>
695      * @param dt time shift in seconds
696      * @return a new state, shifted with respect to the instance (which is immutable)
697      * except for the mass which stay unchanged
698      */
699     public FieldSpacecraftState<T> shiftedBy(final double dt) {
700         if (absPva == null) {
701             return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
702                                               mass, shiftAdditional(dt), additionalDot);
703         } else {
704             return new FieldSpacecraftState<>(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
705                                               mass, shiftAdditional(dt), additionalDot);
706         }
707     }
708 
709     /** Get a time-shifted state.
710      * <p>
711      * The state can be slightly shifted to close dates. This shift is based on
712      * a simple Keplerian model for orbit, a linear extrapolation for attitude
713      * taking the spin rate into account and neither mass nor additional states
714      * changes. It is <em>not</em> intended as a replacement for proper orbit
715      * and attitude propagation but should be sufficient for small time shifts
716      * or coarse accuracy.
717      * </p>
718      * <p>
719      * As a rough order of magnitude, the following table shows the extrapolation
720      * errors obtained between this simple shift method and an {@link
721      * org.orekit.propagation.numerical.FieldNumericalPropagator numerical
722      * propagator} for a low Earth Sun Synchronous Orbit, with a 20x20 gravity field,
723      * Sun and Moon third bodies attractions, drag and solar radiation pressure.
724      * Beware that these results will be different for other orbits.
725      * </p>
726      * <table border="1">
727      * <caption>Extrapolation Error</caption>
728      * <tr style="background-color: #ccccff;"><th>interpolation time (s)</th>
729      * <th>position error without derivatives (m)</th><th>position error with derivatives (m)</th></tr>
730      * <tr><td style="background-color: #eeeeff; padding:5px"> 60</td><td>  18</td><td> 1.1</td></tr>
731      * <tr><td style="background-color: #eeeeff; padding:5px">120</td><td>  72</td><td> 9.1</td></tr>
732      * <tr><td style="background-color: #eeeeff; padding:5px">300</td><td> 447</td><td> 140</td></tr>
733      * <tr><td style="background-color: #eeeeff; padding:5px">600</td><td>1601</td><td>1067</td></tr>
734      * <tr><td style="background-color: #eeeeff; padding:5px">900</td><td>3141</td><td>3307</td></tr>
735      * </table>
736      * @param dt time shift in seconds
737      * @return a new state, shifted with respect to the instance (which is immutable)
738      * except for the mass which stay unchanged
739      */
740     public FieldSpacecraftState<T> shiftedBy(final T dt) {
741         if (absPva == null) {
742             return new FieldSpacecraftState<>(orbit.shiftedBy(dt), attitude.shiftedBy(dt),
743                                               mass, shiftAdditional(dt), additionalDot);
744         } else {
745             return new FieldSpacecraftState<>(absPva.shiftedBy(dt), attitude.shiftedBy(dt),
746                                               mass, shiftAdditional(dt), additionalDot);
747         }
748     }
749 
750     /** Shift additional states.
751      * @param dt time shift in seconds
752      * @return shifted additional states
753      * @since 11.1.1
754      */
755     private FieldArrayDictionary<T> shiftAdditional(final double dt) {
756 
757         // fast handling when there are no derivatives at all
758         if (additionalDot.size() == 0) {
759             return additional;
760         }
761 
762         // there are derivatives, we need to take them into account in the additional state
763         final FieldArrayDictionary<T> shifted = new FieldArrayDictionary<T>(additional);
764         for (final FieldArrayDictionary<T>.Entry dotEntry : additionalDot.getData()) {
765             final FieldArrayDictionary<T>.Entry entry = shifted.getEntry(dotEntry.getKey());
766             if (entry != null) {
767                 entry.scaledIncrement(dt, dotEntry);
768             }
769         }
770 
771         return shifted;
772 
773     }
774 
775     /** Shift additional states.
776      * @param dt time shift in seconds
777      * @return shifted additional states
778      * @since 11.1.1
779      */
780     private FieldArrayDictionary<T> shiftAdditional(final T dt) {
781 
782         // fast handling when there are no derivatives at all
783         if (additionalDot.size() == 0) {
784             return additional;
785         }
786 
787         // there are derivatives, we need to take them into account in the additional state
788         final FieldArrayDictionary<T> shifted = new FieldArrayDictionary<T>(additional);
789         for (final FieldArrayDictionary<T>.Entry dotEntry : additionalDot.getData()) {
790             final FieldArrayDictionary<T>.Entry entry = shifted.getEntry(dotEntry.getKey());
791             if (entry != null) {
792                 entry.scaledIncrement(dt, dotEntry);
793             }
794         }
795 
796         return shifted;
797 
798     }
799 
800     /** {@inheritDoc}
801      * <p>
802      * The additional states that are interpolated are the ones already present
803      * in the instance. The sample instances must therefore have at least the same
804      * additional states has the instance. They may have more additional states,
805      * but the extra ones will be ignored.
806      * </p>
807      * <p>
808      * The instance and all the sample instances <em>must</em> be based on similar
809      * trajectory data, i.e. they must either all be based on orbits or all be based
810      * on absolute position-velocity-acceleration. Any inconsistency will trigger
811      * an {@link OrekitIllegalStateException}.
812      * </p>
813      * <p>
814      * As this implementation of interpolation is polynomial, it should be used only
815      * with small samples (about 10-20 points) in order to avoid <a
816      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
817      * and numerical problems (including NaN appearing).
818      * </p>
819      * @exception OrekitIllegalStateException if some instances are not based on
820      * similar trajectory data
821      */
822     public FieldSpacecraftState<T> interpolate(final FieldAbsoluteDate<T> date,
823                                                final Stream<FieldSpacecraftState<T>> sample) {
824 
825         // prepare interpolators
826         final List<FieldOrbit<T>> orbits;
827         final List<FieldAbsolutePVCoordinates<T>> absPvas;
828         if (isOrbitDefined()) {
829             orbits  = new ArrayList<FieldOrbit<T>>();
830             absPvas = null;
831         } else {
832             orbits  = null;
833             absPvas = new ArrayList<FieldAbsolutePVCoordinates<T>>();
834         }
835         final List<FieldAttitude<T>> attitudes = new ArrayList<>();
836         final FieldHermiteInterpolator<T> massInterpolator = new FieldHermiteInterpolator<>();
837         final List<FieldArrayDictionary<T>.Entry> addionalEntries = additional.getData();
838         final Map<String, FieldHermiteInterpolator<T>> additionalInterpolators =
839                 new HashMap<String, FieldHermiteInterpolator<T>>(additional.size());
840         for (final FieldArrayDictionary<T>.Entry entry : addionalEntries) {
841             additionalInterpolators.put(entry.getKey(), new FieldHermiteInterpolator<>());
842         }
843         final List<FieldArrayDictionary<T>.Entry> addionalDotEntries = additionalDot.getData();
844         final Map<String, FieldHermiteInterpolator<T>> additionalDotInterpolators =
845                 new HashMap<String, FieldHermiteInterpolator<T>>(additionalDot.size());
846         for (final FieldArrayDictionary<T>.Entry entry : addionalDotEntries) {
847             additionalDotInterpolators.put(entry.getKey(), new FieldHermiteInterpolator<>());
848         }
849 
850         // extract sample data
851         sample.forEach(state -> {
852             final T deltaT = state.getDate().durationFrom(date);
853             if (isOrbitDefined()) {
854                 orbits.add(state.getOrbit());
855             } else {
856                 absPvas.add(state.getAbsPVA());
857             }
858             attitudes.add(state.getAttitude());
859             final T[] mm = MathArrays.buildArray(date.getField(), 1);
860             mm[0] = state.getMass();
861             massInterpolator.addSamplePoint(deltaT,
862                                             mm);
863             for (final Map.Entry<String, FieldHermiteInterpolator<T>> entry : additionalInterpolators.entrySet()) {
864                 entry.getValue().addSamplePoint(deltaT, state.getAdditionalState(entry.getKey()));
865             }
866             for (final Map.Entry<String, FieldHermiteInterpolator<T>> entry : additionalDotInterpolators.entrySet()) {
867                 entry.getValue().addSamplePoint(deltaT, state.getAdditionalStateDerivative(entry.getKey()));
868             }
869         });
870 
871         // perform interpolations
872         final FieldOrbit<T> interpolatedOrbit;
873         final FieldAbsolutePVCoordinates<T> interpolatedAbsPva;
874         if (isOrbitDefined()) {
875             interpolatedOrbit  = orbit.interpolate(date, orbits);
876             interpolatedAbsPva = null;
877         } else {
878             interpolatedOrbit  = null;
879             interpolatedAbsPva = absPva.interpolate(date, absPvas);
880         }
881         final FieldAttitude<T> interpolatedAttitude = attitude.interpolate(date, attitudes);
882         final T interpolatedMass       = massInterpolator.value(date.getField().getZero())[0];
883         final FieldArrayDictionary<T> interpolatedAdditional;
884         if (additionalInterpolators.isEmpty()) {
885             interpolatedAdditional = null;
886         } else {
887             interpolatedAdditional = new FieldArrayDictionary<>(date.getField(), additionalInterpolators.size());
888             for (final Map.Entry<String, FieldHermiteInterpolator<T>> entry : additionalInterpolators.entrySet()) {
889                 interpolatedAdditional.put(entry.getKey(), entry.getValue().value(date.getField().getZero()));
890             }
891         }
892         final FieldArrayDictionary<T> interpolatedAdditionalDot;
893         if (additionalDotInterpolators.isEmpty()) {
894             interpolatedAdditionalDot = null;
895         } else {
896             interpolatedAdditionalDot = new FieldArrayDictionary<>(date.getField(), additionalDotInterpolators.size());
897             for (final Map.Entry<String, FieldHermiteInterpolator<T>> entry : additionalDotInterpolators.entrySet()) {
898                 interpolatedAdditionalDot.put(entry.getKey(), entry.getValue().value(date.getField().getZero()));
899             }
900         }
901 
902         // create the complete interpolated state
903         if (isOrbitDefined()) {
904             return new FieldSpacecraftState<>(interpolatedOrbit, interpolatedAttitude, interpolatedMass,
905                                               interpolatedAdditional, interpolatedAdditionalDot);
906         } else {
907             return new FieldSpacecraftState<>(interpolatedAbsPva, interpolatedAttitude, interpolatedMass,
908                                               interpolatedAdditional, interpolatedAdditionalDot);
909         }
910 
911     }
912 
913     /** Get the absolute position-velocity-acceleration.
914      * <p>
915      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
916      * position-velocity-acceleration} or an {@link FieldOrbit orbit}. Which
917      * one is present can be checked using {@link #isOrbitDefined()}.
918      * </p>
919      * @return absolute position-velocity-acceleration
920      * @exception OrekitIllegalStateException if position-velocity-acceleration is null,
921      * which mean the state rather contains an {@link FieldOrbit}
922      * @see #isOrbitDefined()
923      * @see #getOrbit()
924      */
925     public FieldAbsolutePVCoordinates<T> getAbsPVA() throws OrekitIllegalStateException {
926         if (absPva == null) {
927             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ABSOLUTE_PVCOORDINATES);
928         }
929         return absPva;
930     }
931 
932     /** Get the current orbit.
933      * <p>
934      * A state contains either an {@link FieldAbsolutePVCoordinates absolute
935      * position-velocity-acceleration} or an {@link FieldOrbit orbit}. Which
936      * one is present can be checked using {@link #isOrbitDefined()}.
937      * </p>
938      * @return the orbit
939      * @exception OrekitIllegalStateException if orbit is null,
940      * which means the state rather contains an {@link FieldAbsolutePVCoordinates absolute
941      * position-velocity-acceleration}
942      * @see #isOrbitDefined()
943      * @see #getAbsPVA()
944      */
945     public FieldOrbit<T> getOrbit() throws OrekitIllegalStateException {
946         if (orbit == null) {
947             throw new OrekitIllegalStateException(OrekitMessages.UNDEFINED_ORBIT);
948         }
949         return orbit;
950     }
951 
952     /** Get the date.
953      * @return date
954      */
955     public FieldAbsoluteDate<T> getDate() {
956         return (absPva == null) ? orbit.getDate() : absPva.getDate();
957     }
958 
959     /** Get the defining frame.
960      * @return the frame in which state is defined
961      */
962     public Frame getFrame() {
963         return (absPva == null) ? orbit.getFrame() : absPva.getFrame();
964     }
965 
966 
967     /** Check if an additional state is available.
968      * @param name name of the additional state
969      * @return true if the additional state is available
970      * @see #addAdditionalState(String, CalculusFieldElement...)
971      * @see #getAdditionalState(String)
972      * @see #getAdditionalStates()
973      */
974     public boolean hasAdditionalState(final String name) {
975         return additional.getEntry(name) != null;
976     }
977 
978     /** Check if an additional state derivative is available.
979      * @param name name of the additional state derivative
980      * @return true if the additional state derivative is available
981      * @see #addAdditionalStateDerivative(String, CalculusFieldElement...)
982      * @see #getAdditionalStateDerivative(String)
983      * @see #getAdditionalStatesDerivatives()
984      */
985     public boolean hasAdditionalStateDerivative(final String name) {
986         return additionalDot.getEntry(name) != null;
987     }
988 
989     /** Check if two instances have the same set of additional states available.
990      * <p>
991      * Only the names and dimensions of the additional states are compared,
992      * not their values.
993      * </p>
994      * @param state state to compare to instance
995      * @exception MathIllegalArgumentException if an additional state does not have
996      * the same dimension in both states
997      */
998     public void ensureCompatibleAdditionalStates(final FieldSpacecraftState<T> state)
999         throws MathIllegalArgumentException {
1000 
1001         // check instance additional states is a subset of the other one
1002         for (final FieldArrayDictionary<T>.Entry entry : additional.getData()) {
1003             final T[] other = state.additional.get(entry.getKey());
1004             if (other == null) {
1005                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
1006                                           entry.getKey());
1007             }
1008             if (other.length != entry.getValue().length) {
1009                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
1010                                                     other.length, entry.getValue().length);
1011             }
1012         }
1013 
1014         // check instance additional states derivatives is a subset of the other one
1015         for (final FieldArrayDictionary<T>.Entry entry : additionalDot.getData()) {
1016             final T[] other = state.additionalDot.get(entry.getKey());
1017             if (other == null) {
1018                 throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
1019                                           entry.getKey());
1020             }
1021             if (other.length != entry.getValue().length) {
1022                 throw new MathIllegalStateException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
1023                                                     other.length, entry.getValue().length);
1024             }
1025         }
1026 
1027         if (state.additional.size() > additional.size()) {
1028             // the other state has more additional states
1029             for (final FieldArrayDictionary<T>.Entry entry : state.additional.getData()) {
1030                 if (additional.getEntry(entry.getKey()) == null) {
1031                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
1032                                               entry.getKey());
1033                 }
1034             }
1035         }
1036 
1037         if (state.additionalDot.size() > additionalDot.size()) {
1038             // the other state has more additional states
1039             for (final FieldArrayDictionary<T>.Entry entry : state.additionalDot.getData()) {
1040                 if (additionalDot.getEntry(entry.getKey()) == null) {
1041                     throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE,
1042                                               entry.getKey());
1043                 }
1044             }
1045         }
1046 
1047     }
1048 
1049     /** Get an additional state.
1050      * @param name name of the additional state
1051      * @return value of the additional state
1052           * @see #addAdditionalState(String, CalculusFieldElement...)
1053      * @see #hasAdditionalState(String)
1054      * @see #getAdditionalStates()
1055      */
1056     public T[] getAdditionalState(final String name) {
1057         final FieldArrayDictionary<T>.Entry entry = additional.getEntry(name);
1058         if (entry == null) {
1059             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
1060         }
1061         return entry.getValue();
1062     }
1063 
1064     /** Get an additional state derivative.
1065      * @param name name of the additional state derivative
1066      * @return value of the additional state derivative
1067      * @see #addAdditionalStateDerivative(String, CalculusFieldElement...)
1068      * @see #hasAdditionalStateDerivative(String)
1069      * @see #getAdditionalStatesDerivatives()
1070      * @since 11.1
1071      */
1072     public T[] getAdditionalStateDerivative(final String name) {
1073         final FieldArrayDictionary<T>.Entry entry = additionalDot.getEntry(name);
1074         if (entry == null) {
1075             throw new OrekitException(OrekitMessages.UNKNOWN_ADDITIONAL_STATE, name);
1076         }
1077         return entry.getValue();
1078     }
1079 
1080     /** Get an unmodifiable map of additional states.
1081      * @return unmodifiable map of additional states
1082      * @see #addAdditionalState(String, CalculusFieldElement...)
1083      * @see #hasAdditionalState(String)
1084      * @see #getAdditionalState(String)
1085      * @deprecated as of 11.1, replaced by {@link #getAdditionalStatesValues()}
1086      */
1087     @Deprecated
1088     public Map<String, T[]> getAdditionalStates() {
1089         return getAdditionalStatesValues().toMap();
1090     }
1091 
1092     /** Get an unmodifiable map of additional states.
1093      * @return unmodifiable map of additional states
1094      * @see #addAdditionalState(String, CalculusFieldElement...)
1095      * @see #hasAdditionalState(String)
1096      * @see #getAdditionalState(String)
1097      * @since 11.1
1098      */
1099     public FieldArrayDictionary<T> getAdditionalStatesValues() {
1100         return additional.unmodifiableView();
1101     }
1102 
1103     /** Get an unmodifiable map of additional states derivatives.
1104      * @return unmodifiable map of additional states derivatives
1105      * @see #addAdditionalStateDerivative(String, CalculusFieldElement...)
1106      * @see #hasAdditionalStateDerivative(String)
1107      * @see #getAdditionalStateDerivative(String)
1108     * @since 11.1
1109       */
1110     public FieldArrayDictionary<T> getAdditionalStatesDerivatives() {
1111         return additionalDot.unmodifiableView();
1112     }
1113 
1114     /** Compute the transform from state defining frame to spacecraft frame.
1115      * <p>The spacecraft frame origin is at the point defined by the orbit,
1116      * and its orientation is defined by the attitude.</p>
1117      * @return transform from specified frame to current spacecraft frame
1118      */
1119     public FieldTransform<T> toTransform() {
1120         final TimeStampedFieldPVCoordinates<T> pv = getPVCoordinates();
1121         return new FieldTransform<>(pv.getDate(),
1122                                     new FieldTransform<>(pv.getDate(), pv.negate()),
1123                                     new FieldTransform<>(pv.getDate(), attitude.getOrientation()));
1124     }
1125 
1126     /** Get the central attraction coefficient.
1127      * @return mu central attraction coefficient (m^3/s^2), or {code Double.NaN} if the
1128      * state is contains an absolute position-velocity-acceleration rather than an orbit
1129      */
1130     public T getMu() {
1131         return (absPva == null) ? orbit.getMu() : absPva.getDate().getField().getZero().add(Double.NaN);
1132     }
1133 
1134     /** Get the Keplerian period.
1135      * <p>The Keplerian period is computed directly from semi major axis
1136      * and central acceleration constant.</p>
1137      * @return keplerian period in seconds, or {code Double.NaN} if the
1138      * state is contains an absolute position-velocity-acceleration rather
1139      * than an orbit
1140      */
1141     public T getKeplerianPeriod() {
1142         return (absPva == null) ? orbit.getKeplerianPeriod() : absPva.getDate().getField().getZero().add(Double.NaN);
1143     }
1144 
1145     /** Get the Keplerian mean motion.
1146      * <p>The Keplerian mean motion is computed directly from semi major axis
1147      * and central acceleration constant.</p>
1148      * @return keplerian mean motion in radians per second, or {code Double.NaN} if the
1149      * state is contains an absolute position-velocity-acceleration rather
1150      * than an orbit
1151      */
1152     public T getKeplerianMeanMotion() {
1153         return (absPva == null) ? orbit.getKeplerianMeanMotion() : absPva.getDate().getField().getZero().add(Double.NaN);
1154     }
1155 
1156     /** Get the semi-major axis.
1157      * @return semi-major axis (m), or {code Double.NaN} if the
1158      * state is contains an absolute position-velocity-acceleration rather
1159      * than an orbit
1160      */
1161     public T getA() {
1162         return (absPva == null) ? orbit.getA() : absPva.getDate().getField().getZero().add(Double.NaN);
1163     }
1164 
1165     /** Get the first component of the eccentricity vector (as per equinoctial parameters).
1166      * @return e cos(ω + Ω), first component of eccentricity vector, or {code Double.NaN} if the
1167      * state is contains an absolute position-velocity-acceleration rather
1168      * than an orbit
1169      * @see #getE()
1170      */
1171     public T getEquinoctialEx() {
1172         return (absPva == null) ? orbit.getEquinoctialEx() : absPva.getDate().getField().getZero().add(Double.NaN);
1173     }
1174 
1175     /** Get the second component of the eccentricity vector (as per equinoctial parameters).
1176      * @return e sin(ω + Ω), second component of the eccentricity vector, or {code Double.NaN} if the
1177      * state is contains an absolute position-velocity-acceleration rather
1178      * than an orbit
1179      * @see #getE()
1180      */
1181     public T getEquinoctialEy() {
1182         return (absPva == null) ? orbit.getEquinoctialEy() : absPva.getDate().getField().getZero().add(Double.NaN);
1183     }
1184 
1185     /** Get the first component of the inclination vector (as per equinoctial parameters).
1186      * @return tan(i/2) cos(Ω), first component of the inclination vector, or {code Double.NaN} if the
1187      * state is contains an absolute position-velocity-acceleration rather
1188      * than an orbit
1189      * @see #getI()
1190      */
1191     public T getHx() {
1192         return (absPva == null) ? orbit.getHx() : absPva.getDate().getField().getZero().add(Double.NaN);
1193     }
1194 
1195     /** Get the second component of the inclination vector (as per equinoctial parameters).
1196      * @return tan(i/2) sin(Ω), second component of the inclination vector, or {code Double.NaN} if the
1197      * state is contains an absolute position-velocity-acceleration rather
1198      * than an orbit
1199      * @see #getI()
1200      */
1201     public T getHy() {
1202         return (absPva == null) ? orbit.getHy() : absPva.getDate().getField().getZero().add(Double.NaN);
1203     }
1204 
1205     /** Get the true latitude argument (as per equinoctial parameters).
1206      * @return v + ω + Ω true longitude argument (rad), or {code Double.NaN} if the
1207      * state is contains an absolute position-velocity-acceleration rather
1208      * than an orbit
1209      * @see #getLE()
1210      * @see #getLM()
1211      */
1212     public T getLv() {
1213         return (absPva == null) ? orbit.getLv() : absPva.getDate().getField().getZero().add(Double.NaN);
1214     }
1215 
1216     /** Get the eccentric latitude argument (as per equinoctial parameters).
1217      * @return E + ω + Ω eccentric longitude argument (rad), or {code Double.NaN} if the
1218      * state is contains an absolute position-velocity-acceleration rather
1219      * than an orbit
1220      * @see #getLv()
1221      * @see #getLM()
1222      */
1223     public T getLE() {
1224         return (absPva == null) ? orbit.getLE() : absPva.getDate().getField().getZero().add(Double.NaN);
1225     }
1226 
1227     /** Get the mean longitude argument (as per equinoctial parameters).
1228      * @return M + ω + Ω mean latitude argument (rad), or {code Double.NaN} if the
1229      * state is contains an absolute position-velocity-acceleration rather
1230      * than an orbit
1231      * @see #getLv()
1232      * @see #getLE()
1233      */
1234     public T getLM() {
1235         return (absPva == null) ? orbit.getLM() : absPva.getDate().getField().getZero().add(Double.NaN);
1236     }
1237 
1238     // Additional orbital elements
1239 
1240     /** Get the eccentricity.
1241      * @return eccentricity, or {code Double.NaN} if the
1242      * state is contains an absolute position-velocity-acceleration rather
1243      * than an orbit
1244      * @see #getEquinoctialEx()
1245      * @see #getEquinoctialEy()
1246      */
1247     public T getE() {
1248         return (absPva == null) ? orbit.getE() : absPva.getDate().getField().getZero().add(Double.NaN);
1249     }
1250 
1251     /** Get the inclination.
1252      * @return inclination (rad)
1253      * @see #getHx()
1254      * @see #getHy()
1255      */
1256     public T getI() {
1257         return (absPva == null) ? orbit.getI() : absPva.getDate().getField().getZero().add(Double.NaN);
1258     }
1259 
1260     /** Get the {@link TimeStampedFieldPVCoordinates} in orbit definition frame.
1261      * <p>
1262      * Compute the position and velocity of the satellite. This method caches its
1263      * results, and recompute them only when the method is called with a new value
1264      * for mu. The result is provided as a reference to the internally cached
1265      * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
1266      * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
1267      * </p>
1268      * @return pvCoordinates in orbit definition frame
1269      */
1270     public TimeStampedFieldPVCoordinates<T> getPVCoordinates() {
1271         return (absPva == null) ? orbit.getPVCoordinates() : absPva.getPVCoordinates();
1272     }
1273 
1274     /** Get the {@link TimeStampedFieldPVCoordinates} in given output frame.
1275      * <p>
1276      * Compute the position and velocity of the satellite. This method caches its
1277      * results, and recompute them only when the method is called with a new value
1278      * for mu. The result is provided as a reference to the internally cached
1279      * {@link TimeStampedFieldPVCoordinates}, so the caller is responsible to copy it in a separate
1280      * {@link TimeStampedFieldPVCoordinates} if it needs to keep the value for a while.
1281      * </p>
1282      * @param outputFrame frame in which coordinates should be defined
1283      * @return pvCoordinates in orbit definition frame
1284      */
1285     public TimeStampedFieldPVCoordinates<T>getPVCoordinates(final Frame outputFrame) {
1286         return (absPva == null) ? orbit.getPVCoordinates(outputFrame) : absPva.getPVCoordinates(outputFrame);
1287     }
1288 
1289     /** Get the attitude.
1290      * @return the attitude.
1291      */
1292     public FieldAttitude<T> getAttitude() {
1293         return attitude;
1294     }
1295 
1296     /** Gets the current mass.
1297      * @return the mass (kg)
1298      */
1299     public T getMass() {
1300         return mass;
1301     }
1302 
1303     /**To convert a FieldSpacecraftState instance into a SpacecraftState instance.
1304      *
1305      * @return SpacecraftState instance with the same properties
1306      */
1307     public SpacecraftState toSpacecraftState() {
1308         final DoubleArrayDictionary dictionary;
1309         if (additional.size() == 0) {
1310             dictionary = new DoubleArrayDictionary();
1311         } else {
1312             dictionary = new DoubleArrayDictionary(additional.size());
1313             for (final FieldArrayDictionary<T>.Entry entry : additional.getData()) {
1314                 final double[] array = new double[entry.getValue().length];
1315                 for (int k = 0; k < array.length; ++k) {
1316                     array[k] = entry.getValue()[k].getReal();
1317                 }
1318                 dictionary.put(entry.getKey(), array);
1319             }
1320         }
1321         final DoubleArrayDictionary dictionaryDot;
1322         if (additionalDot.size() == 0) {
1323             dictionaryDot = new DoubleArrayDictionary();
1324         } else {
1325             dictionaryDot = new DoubleArrayDictionary(additionalDot.size());
1326             for (final FieldArrayDictionary<T>.Entry entry : additionalDot.getData()) {
1327                 final double[] array = new double[entry.getValue().length];
1328                 for (int k = 0; k < array.length; ++k) {
1329                     array[k] = entry.getValue()[k].getReal();
1330                 }
1331                 dictionaryDot.put(entry.getKey(), array);
1332             }
1333         }
1334         if (isOrbitDefined()) {
1335             return new SpacecraftState(orbit.toOrbit(), attitude.toAttitude(),
1336                                        mass.getReal(), dictionary, dictionaryDot);
1337         } else {
1338             return new SpacecraftState(absPva.toAbsolutePVCoordinates(),
1339                                        attitude.toAttitude(), mass.getReal(),
1340                                        dictionary, dictionaryDot);
1341         }
1342     }
1343 
1344     @Override
1345     public String toString() {
1346         return "FieldSpacecraftState{" +
1347                 "orbit=" + orbit +
1348                 ", attitude=" + attitude +
1349                 ", mass=" + mass +
1350                 ", additional=" + additional +
1351                 ", additionalDot=" + additionalDot +
1352                 '}';
1353     }
1354 
1355 }