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