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