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