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