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