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.orbits;
18
19
20 import org.hipparchus.CalculusFieldElement;
21 import org.hipparchus.Field;
22 import org.hipparchus.analysis.differentiation.Derivative;
23 import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
24 import org.hipparchus.linear.FieldDecompositionSolver;
25 import org.hipparchus.linear.FieldLUDecomposition;
26 import org.hipparchus.linear.FieldMatrix;
27 import org.hipparchus.linear.FieldVector;
28 import org.hipparchus.linear.MatrixUtils;
29 import org.hipparchus.util.FastMath;
30 import org.hipparchus.util.MathArrays;
31 import org.orekit.errors.OrekitIllegalArgumentException;
32 import org.orekit.errors.OrekitMessages;
33 import org.orekit.frames.FieldKinematicTransform;
34 import org.orekit.frames.FieldStaticTransform;
35 import org.orekit.frames.FieldTransform;
36 import org.orekit.frames.Frame;
37 import org.orekit.time.FieldAbsoluteDate;
38 import org.orekit.time.TimeOffset;
39 import org.orekit.utils.FieldPVCoordinates;
40 import org.orekit.utils.ShiftableFieldPVCoordinatesHolder;
41 import org.orekit.utils.TimeStampedFieldPVCoordinates;
42 import org.orekit.utils.TimeStampedPVCoordinates;
43
44 /**
45 * This class handles orbital parameters.
46
47 * <p>
48 * For user convenience, both the Cartesian and the equinoctial elements
49 * are provided by this class, regardless of the canonical representation
50 * implemented in the derived class (which may be classical Keplerian
51 * elements for example).
52 * </p>
53 * <p>
54 * The parameters are defined in a frame specified by the user. It is important
55 * to make sure this frame is consistent: it probably is inertial and centered
56 * on the central body. This information is used for example by some
57 * force models.
58 * </p>
59 * <p>
60 * Instance of this class are guaranteed to be immutable.
61 * </p>
62 * @author Luc Maisonobe
63 * @author Guylaine Prat
64 * @author Fabien Maussion
65 * @author Véronique Pommier-Maurussane
66 * @since 9.0
67 * @see Orbit
68 * @param <T> type of the field elements
69 */
70 public abstract class FieldOrbit<T extends CalculusFieldElement<T>>
71 implements FieldOrbitalState<T>, ShiftableFieldPVCoordinatesHolder<FieldOrbit<T>, T> {
72
73 /** Absolute tolerance when checking if the rate of the position angle is Keplerian or not. */
74 protected static final double TOLERANCE_POSITION_ANGLE_RATE = 1e-15;
75
76 /** Frame in which are defined the orbital parameters. */
77 private final Frame frame;
78
79 /** Date of the orbital parameters. */
80 private final FieldAbsoluteDate<T> date;
81
82 /** Value of mu used to compute position and velocity (m³/s²). */
83 private final T mu;
84
85 /** Field-valued one.
86 * @since 12.0
87 * */
88 private final T one;
89
90 /** Field-valued zero.
91 * @since 12.0
92 * */
93 private final T zero;
94
95 /** Field used by this class.
96 * @since 12.0
97 */
98 private final Field<T> field;
99
100 /** Computed position.
101 * @since 12.0
102 */
103 private FieldVector3D<T> position;
104
105 /** Computed PVCoordinates. */
106 private TimeStampedFieldPVCoordinates<T> pvCoordinates;
107
108 /** Jacobian of the orbital parameters with mean angle with respect to the Cartesian coordinates. */
109 private T[][] jacobianMeanWrtCartesian;
110
111 /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with mean angle. */
112 private T[][] jacobianWrtParametersMean;
113
114 /** Jacobian of the orbital parameters with eccentric angle with respect to the Cartesian coordinates. */
115 private T[][] jacobianEccentricWrtCartesian;
116
117 /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with eccentric angle. */
118 private T[][] jacobianWrtParametersEccentric;
119
120 /** Jacobian of the orbital parameters with true angle with respect to the Cartesian coordinates. */
121 private T[][] jacobianTrueWrtCartesian;
122
123 /** Jacobian of the Cartesian coordinates with respect to the orbital parameters with true angle. */
124 private T[][] jacobianWrtParametersTrue;
125
126 /** Default constructor.
127 * Build a new instance with arbitrary default elements.
128 * @param frame the frame in which the parameters are defined
129 * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
130 * @param date date of the orbital parameters
131 * @param mu central attraction coefficient (m^3/s^2)
132 * @exception IllegalArgumentException if frame is not a {@link
133 * Frame#isPseudoInertial pseudo-inertial frame}
134 */
135 protected FieldOrbit(final Frame frame, final FieldAbsoluteDate<T> date, final T mu)
136 throws IllegalArgumentException {
137 ensurePseudoInertialFrame(frame);
138 this.field = mu.getField();
139 this.zero = this.field.getZero();
140 this.one = this.field.getOne();
141 this.date = date;
142 this.mu = mu;
143 this.pvCoordinates = null;
144 this.frame = frame;
145 jacobianMeanWrtCartesian = null;
146 jacobianWrtParametersMean = null;
147 jacobianEccentricWrtCartesian = null;
148 jacobianWrtParametersEccentric = null;
149 jacobianTrueWrtCartesian = null;
150 jacobianWrtParametersTrue = null;
151 }
152
153 /** Set the orbit from Cartesian parameters.
154 *
155 * <p> The acceleration provided in {@code FieldPVCoordinates} is accessible using
156 * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
157 * use {@code mu} and the position to compute the acceleration, including
158 * {@link #shiftedBy(CalculusFieldElement)} and {@link #getPVCoordinates(FieldAbsoluteDate, Frame)}.
159 *
160 * @param fieldPVCoordinates the position and velocity in the inertial frame
161 * @param frame the frame in which the {@link TimeStampedPVCoordinates} are defined
162 * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
163 * @param mu central attraction coefficient (m^3/s^2)
164 * @exception IllegalArgumentException if frame is not a {@link
165 * Frame#isPseudoInertial pseudo-inertial frame}
166 */
167 protected FieldOrbit(final TimeStampedFieldPVCoordinates<T> fieldPVCoordinates, final Frame frame, final T mu)
168 throws IllegalArgumentException {
169 ensurePseudoInertialFrame(frame);
170 this.field = mu.getField();
171 this.zero = this.field.getZero();
172 this.one = this.field.getOne();
173 this.date = fieldPVCoordinates.getDate();
174 this.mu = mu;
175 if (fieldPVCoordinates.getAcceleration().getNorm2Sq().getReal() == 0.0) {
176 // the acceleration was not provided,
177 // compute it from Newtonian attraction
178 final T r2 = fieldPVCoordinates.getPosition().getNorm2Sq();
179 final T r3 = r2.multiply(r2.sqrt());
180 this.pvCoordinates = new TimeStampedFieldPVCoordinates<>(fieldPVCoordinates.getDate(),
181 fieldPVCoordinates.getPosition(),
182 fieldPVCoordinates.getVelocity(),
183 new FieldVector3D<>(r3.reciprocal().multiply(mu.negate()), fieldPVCoordinates.getPosition()));
184 } else {
185 this.pvCoordinates = fieldPVCoordinates;
186 }
187 this.frame = frame;
188 }
189
190 /** Compute non-Keplerian part of the acceleration from first time derivatives.
191 * @return non-Keplerian part of the acceleration
192 * @since 13.1
193 */
194 protected FieldVector3D<T> nonKeplerianAcceleration() {
195
196 final T[][] dPdC = MathArrays.buildArray(getField(), 6, 6);
197 final PositionAngleType positionAngleType = PositionAngleType.MEAN;
198 getJacobianWrtCartesian(positionAngleType, dPdC);
199 final FieldMatrix<T> subMatrix = MatrixUtils.createFieldMatrix(dPdC);
200
201 final FieldDecompositionSolver<T> solver = getDecompositionSolver(subMatrix);
202
203 final T[] derivatives = dPdC[0].clone();
204 getType().mapOrbitToArray(this, positionAngleType, derivatives.clone(), derivatives);
205 derivatives[5] = derivatives[5].subtract(getKeplerianMeanMotion());
206
207 final FieldVector<T> solution = solver.solve(MatrixUtils.createFieldVector(derivatives));
208 // solution is vector-acceleration vector
209 return new FieldVector3D<>(solution.getEntry(3), solution.getEntry(4), solution.getEntry(5));
210
211 }
212
213 /** Check if Cartesian coordinates include non-Keplerian acceleration.
214 * @param pva Cartesian coordinates
215 * @param mu central attraction coefficient
216 * @param <T> type of the field elements
217 * @return true if Cartesian coordinates include non-Keplerian acceleration
218 */
219 protected static <T extends CalculusFieldElement<T>> boolean hasNonKeplerianAcceleration(final FieldPVCoordinates<T> pva, final T mu) {
220
221 if (mu.getField().getZero() instanceof Derivative<?>) {
222 return Orbit.hasNonKeplerianAcceleration(pva.toPVCoordinates(), mu.getReal()); // for performance
223
224 } else {
225 final FieldVector3D<T> a = pva.getAcceleration();
226 if (a == null) {
227 return false;
228 }
229
230 final FieldVector3D<T> p = pva.getPosition();
231 final T r2 = p.getNorm2Sq();
232
233 // Check if acceleration is relatively close to 0 compared to the Keplerian acceleration
234 final double tolerance = mu.getReal() * 1e-9;
235 final FieldVector3D<T> aTimesR2 = a.scalarMultiply(r2);
236 if (aTimesR2.getNorm().getReal() < tolerance) {
237 return false;
238 }
239
240 if ((aTimesR2.add(p.normalize().scalarMultiply(mu))).getNorm().getReal() > tolerance) {
241 // we have a relevant acceleration, we can compute derivatives
242 return true;
243 } else {
244 // the provided acceleration is either too small to be reliable (probably even 0), or NaN
245 return false;
246 }
247 }
248
249 }
250
251
252 /**
253 * Compute corrected shift from non-Keplerian part.
254 * @param keplerianShifted Keplerian shift
255 * @param dt time shift
256 * @return corrected position-velocity-acceleration vector
257 * @since 14.0
258 */
259 protected FieldPVCoordinates<T> shiftPVNonKeplerian(final FieldPVCoordinates<T> keplerianShifted, final T dt) {
260 // extract non-Keplerian acceleration from first time derivatives
261 final FieldVector3D<T> nonKeplerianAcceleration = nonKeplerianAcceleration();
262
263 // add second order effect of non-Keplerian acceleration to Keplerian-only shift
264 final FieldVector3D<T> fixedV = nonKeplerianAcceleration.scalarMultiply(dt).add(keplerianShifted.getVelocity());
265 final FieldVector3D<T> fixedP = nonKeplerianAcceleration.scalarMultiply(dt.square().divide(2.))
266 .add(keplerianShifted.getPosition());
267 final T fixedR2 = fixedP.getNorm2Sq();
268 final T fixedR = FastMath.sqrt(fixedR2);
269 final FieldVector3D<T> fixedA = nonKeplerianAcceleration.add(new FieldVector3D<>(getMu().negate().divide(fixedR.multiply(fixedR2)),
270 keplerianShifted.getPosition()));
271 return new FieldPVCoordinates<>(fixedP, fixedV, fixedA);
272 }
273
274 /** Returns true if and only if the orbit is elliptical i.e. has a non-negative semi-major axis.
275 * @return true if getA() is strictly greater than 0
276 * @since 12.0
277 */
278 public boolean isElliptical() {
279 return getA().getReal() > 0.;
280 }
281
282 /** Get the orbit type.
283 * @return orbit type
284 */
285 public abstract OrbitParamsType getType();
286
287 /** Ensure the defining frame is a pseudo-inertial frame.
288 * @param frame frame to check
289 * @exception IllegalArgumentException if frame is not a {@link
290 * Frame#isPseudoInertial pseudo-inertial frame}
291 */
292 private static void ensurePseudoInertialFrame(final Frame frame)
293 throws IllegalArgumentException {
294 if (!frame.isPseudoInertial()) {
295 throw new OrekitIllegalArgumentException(OrekitMessages.NON_PSEUDO_INERTIAL_FRAME,
296 frame.getName());
297 }
298 }
299
300 /** Get the frame in which the orbital parameters are defined.
301 * @return frame in which the orbital parameters are defined
302 */
303 public Frame getFrame() {
304 return frame;
305 }
306
307 /**Transforms the FieldOrbit instance into an Orbit instance.
308 * @return Orbit instance with same properties
309 */
310 public abstract Orbit toOrbit();
311
312 /** Get the semi-major axis.
313 * <p>Note that the semi-major axis is considered negative for hyperbolic orbits.</p>
314 * @return semi-major axis (m)
315 */
316 public abstract T getA();
317
318 /** Get the semi-major axis derivative.
319 * <p>Note that the semi-major axis is considered negative for hyperbolic orbits.</p>
320 * <p>
321 * If the orbit was created without derivatives, the value returned is null.
322 * </p>
323 * @return semi-major axis derivative (m/s)
324 */
325 public abstract T getADot();
326
327 /** Get the first component of the equinoctial eccentricity vector.
328 * @return first component of the equinoctial eccentricity vector
329 */
330 public abstract T getEquinoctialEx();
331
332 /** Get the first component of the equinoctial eccentricity vector derivative.
333 * <p>
334 * If the orbit was created without derivatives, the value returned is null.
335 * </p>
336 * @return first component of the equinoctial eccentricity vector derivative
337 */
338 public abstract T getEquinoctialExDot();
339
340 /** Get the second component of the equinoctial eccentricity vector.
341 * @return second component of the equinoctial eccentricity vector
342 */
343 public abstract T getEquinoctialEy();
344
345 /** Get the second component of the equinoctial eccentricity vector derivative.
346 * <p>
347 * If the orbit was created without derivatives, the value returned is null.
348 * </p>
349 * @return second component of the equinoctial eccentricity vector derivative
350 */
351 public abstract T getEquinoctialEyDot();
352
353 /** Get the first component of the inclination vector.
354 * @return first component of the inclination vector
355 */
356 public abstract T getHx();
357
358 /** Get the first component of the inclination vector derivative.
359 * <p>
360 * If the orbit was created without derivatives, the value returned is null.
361 * </p>
362 * @return first component of the inclination vector derivative
363 */
364 public abstract T getHxDot();
365
366 /** Get the second component of the inclination vector.
367 * @return second component of the inclination vector
368 */
369 public abstract T getHy();
370
371 /** Get the second component of the inclination vector derivative.
372 * @return second component of the inclination vector derivative
373 */
374 public abstract T getHyDot();
375
376 /** Get the eccentric longitude argument.
377 * @return E + ω + Ω eccentric longitude argument (rad)
378 */
379 public abstract T getLE();
380
381 /** Get the eccentric longitude argument derivative.
382 * <p>
383 * If the orbit was created without derivatives, the value returned is null.
384 * </p>
385 * @return d(E + ω + Ω)/dt eccentric longitude argument derivative (rad/s)
386 */
387 public abstract T getLEDot();
388
389 /** Get the true longitude argument.
390 * @return v + ω + Ω true longitude argument (rad)
391 */
392 public abstract T getLv();
393
394 /** Get the true longitude argument derivative.
395 * <p>
396 * If the orbit was created without derivatives, the value returned is null.
397 * </p>
398 * @return d(v + ω + Ω)/dt true longitude argument derivative (rad/s)
399 */
400 public abstract T getLvDot();
401
402 /** Get the mean longitude argument.
403 * @return M + ω + Ω mean longitude argument (rad)
404 */
405 public abstract T getLM();
406
407 /** Get the mean longitude argument derivative.
408 * <p>
409 * If the orbit was created without derivatives, the value returned is null.
410 * </p>
411 * @return d(M + ω + Ω)/dt mean longitude argument derivative (rad/s)
412 */
413 public abstract T getLMDot();
414
415 // Additional orbital elements
416
417 /** Get the eccentricity.
418 * @return eccentricity
419 */
420 public abstract T getE();
421
422 /** Get the eccentricity derivative.
423 * <p>
424 * If the orbit was created without derivatives, the value returned is null.
425 * </p>
426 * @return eccentricity derivative
427 */
428 public abstract T getEDot();
429
430 /** Get the inclination.
431 * <p>
432 * If the orbit was created without derivatives, the value returned is null.
433 * </p>
434 * @return inclination (rad)
435 */
436 public abstract T getI();
437
438 /** Get the inclination derivative.
439 * @return inclination derivative (rad/s)
440 */
441 public abstract T getIDot();
442
443 /** Check if orbit includes non-Keplerian rates.
444 * @return true if orbit includes non-Keplerian derivatives
445 * @see #getADot()
446 * @see #getEquinoctialExDot()
447 * @see #getEquinoctialEyDot()
448 * @see #getHxDot()
449 * @see #getHyDot()
450 * @see #getLEDot()
451 * @see #getLvDot()
452 * @see #getLMDot()
453 * @see #getEDot()
454 * @see #getIDot()
455 * @since 13.0
456 */
457 public boolean hasNonKeplerianAcceleration() {
458 return hasNonKeplerianAcceleration(getPVCoordinates(), getMu());
459 }
460
461 /** Get the central attraction coefficient used for position and velocity conversions (m³/s²).
462 * @return central attraction coefficient used for position and velocity conversions (m³/s²)
463 */
464 public T getMu() {
465 return mu;
466 }
467
468 /** Get the Keplerian period.
469 * <p>The Keplerian period is computed directly from semi major axis
470 * and central acceleration constant.</p>
471 * @return Keplerian period in seconds, or positive infinity for hyperbolic orbits
472 */
473 public T getKeplerianPeriod() {
474 final T a = getA();
475 return isElliptical() ?
476 a.multiply(a.getPi().multiply(2.0)).multiply(a.divide(mu).sqrt()) :
477 zero.add(Double.POSITIVE_INFINITY);
478 }
479
480 /** Get the Keplerian mean motion.
481 * <p>The Keplerian mean motion is computed directly from semi major axis
482 * and central acceleration constant.</p>
483 * @return Keplerian mean motion in radians per second
484 */
485 public T getKeplerianMeanMotion() {
486 final T absA = getA().abs();
487 return absA.reciprocal().multiply(mu).sqrt().divide(absA);
488 }
489
490 /** Get the derivative of the mean anomaly with respect to the semi major axis.
491 * @return derivative of the mean anomaly with respect to the semi major axis
492 */
493 public T getMeanAnomalyDotWrtA() {
494 return getKeplerianMeanMotion().divide(getA()).multiply(-1.5);
495 }
496
497 /** Get the date of orbital parameters.
498 * @return date of the orbital parameters
499 */
500 public FieldAbsoluteDate<T> getDate() {
501 return date;
502 }
503
504 /** Get the {@link TimeStampedPVCoordinates} in a specified frame.
505 * @param outputFrame frame in which the position/velocity coordinates shall be computed
506 * @return FieldPVCoordinates in the specified output frame
507 * @see #getPVCoordinates()
508 */
509 @Override
510 public TimeStampedFieldPVCoordinates<T> getPVCoordinates(final Frame outputFrame) {
511 if (pvCoordinates == null) {
512 pvCoordinates = initPVCoordinates();
513 }
514
515 // If output frame requested is the same as definition frame,
516 // PV coordinates are returned directly
517 if (outputFrame == frame) {
518 return pvCoordinates;
519 }
520
521 // Else, PV coordinates are transformed to output frame
522 final FieldTransform<T> t = frame.getTransformTo(outputFrame, date);
523 return t.transformPVCoordinates(pvCoordinates);
524 }
525
526 /** {@inheritDoc} */
527 @Override
528 public FieldVector3D<T> getVelocity(final FieldAbsoluteDate<T> otherDate, final Frame otherFrame) {
529 final FieldPVCoordinates<T> pv = getPVCoordinates(otherDate, frame);
530 if (otherFrame == getFrame()) {
531 return pv.getVelocity();
532 }
533 final FieldKinematicTransform<T> kinematicTransform = getFrame().getKinematicTransformTo(otherFrame, date);
534 return kinematicTransform.transformOnlyPV(pv).getVelocity();
535 }
536
537 /** {@inheritDoc} */
538 @Override
539 public FieldVector3D<T> getPosition(final FieldAbsoluteDate<T> otherDate, final Frame otherFrame) {
540 // Get field and express dt as T
541 final T dt = otherDate.durationFrom(date);
542
543 // use Keplerian-only motion
544 final FieldOrbit<T> keplerianShifted = keplerianShiftedBy(dt);
545
546 // Non-Keplerian acceleration shall be considered
547 if (!dt.isZero() && hasNonKeplerianAcceleration()) {
548 // extract non-Keplerian acceleration from first time derivatives
549 final FieldVector3D<T> nonKeplerianAcceleration = nonKeplerianAcceleration();
550 // add second order effect of non-Keplerian acceleration to Keplerian-only shift
551 final FieldVector3D<T> shiftedPosition = nonKeplerianAcceleration.scalarMultiply(dt.square().divide(2.))
552 .add(keplerianShifted.getPosition());
553 if (otherFrame == getFrame()) {
554 return shiftedPosition;
555 } else {
556 return getFrame().getStaticTransformTo(otherFrame, otherDate).transformPosition(shiftedPosition);
557 }
558 }
559 // Keplerian-only motion is all we can do
560 else {
561 return keplerianShifted.getPosition(otherFrame);
562 }
563
564 }
565
566 /** Get the position in a specified frame.
567 * @param outputFrame frame in which the position coordinates shall be computed
568 * @return position in the specified output frame
569 * @see #getPosition()
570 * @since 12.0
571 */
572 @Override
573 public FieldVector3D<T> getPosition(final Frame outputFrame) {
574 if (position == null) {
575 position = initPosition();
576 }
577
578 // If output frame requested is the same as definition frame,
579 // Position vector is returned directly
580 if (outputFrame == frame) {
581 return position;
582 }
583
584 // Else, position vector is transformed to output frame
585 final FieldStaticTransform<T> t = frame.getStaticTransformTo(outputFrame, date);
586 return t.transformPosition(position);
587
588 }
589
590 /** Get the position in definition frame.
591 * @return position in the definition frame
592 * @see #getPVCoordinates()
593 * @since 12.0
594 */
595 @Override
596 public FieldVector3D<T> getPosition() {
597 if (position == null) {
598 position = initPosition();
599 }
600 return position;
601 }
602
603 /** Get the {@link TimeStampedPVCoordinates} in definition frame.
604 * @return FieldPVCoordinates in the definition frame
605 * @see #getPVCoordinates(Frame)
606 */
607 public TimeStampedFieldPVCoordinates<T> getPVCoordinates() {
608 if (pvCoordinates == null) {
609 pvCoordinates = initPVCoordinates();
610 position = pvCoordinates.getPosition();
611 }
612 return pvCoordinates;
613 }
614
615 /** Getter for Field-valued one.
616 * @return one
617 * */
618 protected T getOne() {
619 return one;
620 }
621
622 /** Getter for Field-valued zero.
623 * @return zero
624 * */
625 protected T getZero() {
626 return zero;
627 }
628
629 /** Getter for Field.
630 * @return CalculusField
631 * */
632 protected Field<T> getField() {
633 return field;
634 }
635
636 /** Compute the position coordinates from the canonical parameters.
637 * @return computed position coordinates
638 * @since 12.0
639 */
640 protected abstract FieldVector3D<T> initPosition();
641
642 /** Compute the position/velocity coordinates from the canonical parameters.
643 * @return computed position/velocity coordinates
644 */
645 protected abstract TimeStampedFieldPVCoordinates<T> initPVCoordinates();
646
647 /**
648 * Create a new object representing the same physical orbital state, but attached to a different reference frame.
649 * If the new frame is not inertial, an exception will be thrown.
650 *
651 * @param inertialFrame reference frame of output orbit
652 * @return orbit with different frame
653 * @since 13.0
654 */
655 public abstract FieldOrbit<T> inFrame(Frame inertialFrame);
656
657 /** Get a time-shifted orbit.
658 * <p>
659 * The orbit can be slightly shifted to close dates. This shift is based on
660 * a simple Keplerian model. It is <em>not</em> intended as a replacement
661 * for proper orbit and attitude propagation but should be sufficient for
662 * small time shifts or coarse accuracy.
663 * </p>
664 * @param dt time shift in seconds
665 * @return a new orbit, shifted with respect to the instance (which is immutable)
666 */
667 public abstract FieldOrbit<T> shiftedBy(T dt);
668
669 /** Get a time-shifted orbit.
670 * <p>
671 * The orbit can be slightly shifted to close dates. This shift is based on
672 * a simple Keplerian model. It is <em>not</em> intended as a replacement
673 * for proper orbit and attitude propagation but should be sufficient for
674 * small time shifts or coarse accuracy.
675 * </p>
676 * @param dt time shift in seconds
677 * @return a new orbit, shifted with respect to the instance (which is immutable)
678 */
679 public abstract FieldOrbit<T> shiftedBy(double dt);
680
681 /** Get a time-shifted orbit.
682 * <p>
683 * The orbit can be slightly shifted to close dates. This shift is based on
684 * a simple Keplerian model. It is <em>not</em> intended as a replacement
685 * for proper orbit and attitude propagation but should be sufficient for
686 * small time shifts or coarse accuracy.
687 * </p>
688 * @param dt time shift in seconds
689 * @return a new orbit, shifted with respect to the instance (which is immutable)
690 */
691 public abstract FieldOrbit<T> shiftedBy(TimeOffset dt);
692
693 /** Get a time-shifted orbit according to Keplerian motion only.
694 * @param dt time shift in seconds
695 * @return a new orbit, shifted with respect to the instance (which is immutable)
696 * @since 14.0
697 */
698 protected abstract FieldOrbit<T> keplerianShiftedBy(T dt);
699
700 /** Compute the Jacobian of the orbital parameters with respect to the Cartesian parameters.
701 * <p>
702 * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
703 * respect to Cartesian coordinate j. This means each row corresponds to one orbital parameter
704 * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
705 * </p>
706 * @param type type of the position angle to use
707 * @param jacobian placeholder 6x6 (or larger) matrix to be filled with the Jacobian, if matrix
708 * is larger than 6x6, only the 6x6 upper left corner will be modified
709 */
710 public void getJacobianWrtCartesian(final PositionAngleType type, final T[][] jacobian) {
711
712 final T[][] cachedJacobian;
713 synchronized (this) {
714 cachedJacobian = switch (type) {
715 case MEAN -> {
716 if (jacobianMeanWrtCartesian == null) {
717 // first call, we need to compute the Jacobian and cache it
718 jacobianMeanWrtCartesian = computeJacobianMeanWrtCartesian();
719 }
720 yield jacobianMeanWrtCartesian;
721 }
722 case ECCENTRIC -> {
723 if (jacobianEccentricWrtCartesian == null) {
724 // first call, we need to compute the Jacobian and cache it
725 jacobianEccentricWrtCartesian = computeJacobianEccentricWrtCartesian();
726 }
727 yield jacobianEccentricWrtCartesian;
728 }
729 case TRUE -> {
730 if (jacobianTrueWrtCartesian == null) {
731 // first call, we need to compute the Jacobian and cache it
732 jacobianTrueWrtCartesian = computeJacobianTrueWrtCartesian();
733 }
734 yield jacobianTrueWrtCartesian;
735 }
736 };
737 }
738
739 // fill the user provided array
740 for (int i = 0; i < cachedJacobian.length; ++i) {
741 System.arraycopy(cachedJacobian[i], 0, jacobian[i], 0, cachedJacobian[i].length);
742 }
743
744 }
745
746 /** Compute the Jacobian of the Cartesian parameters with respect to the orbital parameters.
747 * <p>
748 * Element {@code jacobian[i][j]} is the derivative of Cartesian coordinate i of the orbit with
749 * respect to orbital parameter j. This means each row corresponds to one Cartesian coordinate
750 * x, y, z, xdot, ydot, zdot whereas columns 0 to 5 correspond to the orbital parameters.
751 * </p>
752 * @param type type of the position angle to use
753 * @param jacobian placeholder 6x6 (or larger) matrix to be filled with the Jacobian, if matrix
754 * is larger than 6x6, only the 6x6 upper left corner will be modified
755 */
756 public void getJacobianWrtParameters(final PositionAngleType type, final T[][] jacobian) {
757
758 final T[][] cachedJacobian;
759 synchronized (this) {
760 cachedJacobian = switch (type) {
761 case MEAN -> {
762 if (jacobianWrtParametersMean == null) {
763 // first call, we need to compute the Jacobian and cache it
764 jacobianWrtParametersMean = createInverseJacobian(type);
765 }
766 yield jacobianWrtParametersMean;
767 }
768 case ECCENTRIC -> {
769 if (jacobianWrtParametersEccentric == null) {
770 // first call, we need to compute the Jacobian and cache it
771 jacobianWrtParametersEccentric = createInverseJacobian(type);
772 }
773 yield jacobianWrtParametersEccentric;
774 }
775 case TRUE -> {
776 if (jacobianWrtParametersTrue == null) {
777 // first call, we need to compute the Jacobian and cache it
778 jacobianWrtParametersTrue = createInverseJacobian(type);
779 }
780 yield jacobianWrtParametersTrue;
781 }
782 };
783 }
784
785 // fill the user-provided array
786 for (int i = 0; i < cachedJacobian.length; ++i) {
787 System.arraycopy(cachedJacobian[i], 0, jacobian[i], 0, cachedJacobian[i].length);
788 }
789
790 }
791
792 /** Create an inverse Jacobian.
793 * @param type type of the position angle to use
794 * @return inverse Jacobian
795 */
796 private T[][] createInverseJacobian(final PositionAngleType type) {
797
798 // get the direct Jacobian
799 final T[][] directJacobian = MathArrays.buildArray(getA().getField(), 6, 6);
800 getJacobianWrtCartesian(type, directJacobian);
801
802 // invert the direct Jacobian
803 final FieldMatrix<T> matrix = MatrixUtils.createFieldMatrix(directJacobian);
804 final FieldDecompositionSolver<T> solver = getDecompositionSolver(matrix);
805 return solver.getInverse().getData();
806
807 }
808
809 /**
810 * Method to build a matrix decomposition solver.
811 * @param matrix matrix
812 * @return solver
813 * @since 13.1
814 */
815 protected FieldDecompositionSolver<T> getDecompositionSolver(final FieldMatrix<T> matrix) {
816 return new FieldLUDecomposition<>(matrix).getSolver();
817 }
818
819 /** Compute the Jacobian of the orbital parameters with mean angle with respect to the Cartesian parameters.
820 * <p>
821 * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
822 * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter
823 * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
824 * </p>
825 * @return 6x6 Jacobian matrix
826 * @see #computeJacobianEccentricWrtCartesian()
827 * @see #computeJacobianTrueWrtCartesian()
828 */
829 protected abstract T[][] computeJacobianMeanWrtCartesian();
830
831 /** Compute the Jacobian of the orbital parameters with eccentric angle with respect to the Cartesian parameters.
832 * <p>
833 * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
834 * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter
835 * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
836 * </p>
837 * @return 6x6 Jacobian matrix
838 * @see #computeJacobianMeanWrtCartesian()
839 * @see #computeJacobianTrueWrtCartesian()
840 */
841 protected abstract T[][] computeJacobianEccentricWrtCartesian();
842
843 /** Compute the Jacobian of the orbital parameters with true angle with respect to the Cartesian parameters.
844 * <p>
845 * Element {@code jacobian[i][j]} is the derivative of parameter i of the orbit with
846 * respect to Cartesian coordinate j. This means each row correspond to one orbital parameter
847 * whereas columns 0 to 5 correspond to the Cartesian coordinates x, y, z, xDot, yDot and zDot.
848 * </p>
849 * @return 6x6 Jacobian matrix
850 * @see #computeJacobianMeanWrtCartesian()
851 * @see #computeJacobianEccentricWrtCartesian()
852 */
853 protected abstract T[][] computeJacobianTrueWrtCartesian();
854
855 /** Add the contribution of the Keplerian motion to parameters derivatives.
856 * <p>
857 * This method is used by integration-based propagators to evaluate the part of Keplerian
858 * motion to evolution of the orbital state.
859 * </p>
860 * @param type type of the position angle in the state
861 * @param gm attraction coefficient to use
862 * @param pDot array containing orbital state derivatives to update (the Keplerian
863 * part must be <em>added</em> to the array components, as the array may already
864 * contain some non-zero elements corresponding to non-Keplerian parts)
865 */
866 public abstract void addKeplerContribution(PositionAngleType type, T gm, T[] pDot);
867
868 /** Fill a Jacobian half row with a single vector.
869 * @param a coefficient of the vector
870 * @param v vector
871 * @param row Jacobian matrix row
872 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
873 */
874
875 protected void fillHalfRow(final T a, final FieldVector3D<T> v, final T[] row, final int j) {
876 row[j] = a.multiply(v.getX());
877 row[j + 1] = a.multiply(v.getY());
878 row[j + 2] = a.multiply(v.getZ());
879 }
880
881 /** Fill a Jacobian half row with a linear combination of vectors.
882 * @param a1 coefficient of the first vector
883 * @param v1 first vector
884 * @param a2 coefficient of the second vector
885 * @param v2 second vector
886 * @param row Jacobian matrix row
887 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
888 */
889 protected void fillHalfRow(final T a1, final FieldVector3D<T> v1, final T a2, final FieldVector3D<T> v2,
890 final T[] row, final int j) {
891 row[j] = a1.linearCombination(a1, v1.getX(), a2, v2.getX());
892 row[j + 1] = a1.linearCombination(a1, v1.getY(), a2, v2.getY());
893 row[j + 2] = a1.linearCombination(a1, v1.getZ(), a2, v2.getZ());
894
895 }
896
897 /** Fill a Jacobian half row with a linear combination of vectors.
898 * @param a1 coefficient of the first vector
899 * @param v1 first vector
900 * @param a2 coefficient of the second vector
901 * @param v2 second vector
902 * @param a3 coefficient of the third vector
903 * @param v3 third vector
904 * @param row Jacobian matrix row
905 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
906 */
907 protected void fillHalfRow(final T a1, final FieldVector3D<T> v1,
908 final T a2, final FieldVector3D<T> v2,
909 final T a3, final FieldVector3D<T> v3,
910 final T[] row, final int j) {
911 row[j] = a1.linearCombination(a1, v1.getX(), a2, v2.getX(), a3, v3.getX());
912 row[j + 1] = a1.linearCombination(a1, v1.getY(), a2, v2.getY(), a3, v3.getY());
913 row[j + 2] = a1.linearCombination(a1, v1.getZ(), a2, v2.getZ(), a3, v3.getZ());
914
915 }
916
917 /** Fill a Jacobian half row with a linear combination of vectors.
918 * @param a1 coefficient of the first vector
919 * @param v1 first vector
920 * @param a2 coefficient of the second vector
921 * @param v2 second vector
922 * @param a3 coefficient of the third vector
923 * @param v3 third vector
924 * @param a4 coefficient of the fourth vector
925 * @param v4 fourth vector
926 * @param row Jacobian matrix row
927 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
928 */
929 protected void fillHalfRow(final T a1, final FieldVector3D<T> v1, final T a2, final FieldVector3D<T> v2,
930 final T a3, final FieldVector3D<T> v3, final T a4, final FieldVector3D<T> v4,
931 final T[] row, final int j) {
932 row[j] = a1.linearCombination(a1, v1.getX(), a2, v2.getX(), a3, v3.getX(), a4, v4.getX());
933 row[j + 1] = a1.linearCombination(a1, v1.getY(), a2, v2.getY(), a3, v3.getY(), a4, v4.getY());
934 row[j + 2] = a1.linearCombination(a1, v1.getZ(), a2, v2.getZ(), a3, v3.getZ(), a4, v4.getZ());
935
936 }
937
938 /** Fill a Jacobian half row with a linear combination of vectors.
939 * @param a1 coefficient of the first vector
940 * @param v1 first vector
941 * @param a2 coefficient of the second vector
942 * @param v2 second vector
943 * @param a3 coefficient of the third vector
944 * @param v3 third vector
945 * @param a4 coefficient of the fourth vector
946 * @param v4 fourth vector
947 * @param a5 coefficient of the fifth vector
948 * @param v5 fifth vector
949 * @param row Jacobian matrix row
950 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
951 */
952 protected void fillHalfRow(final T a1, final FieldVector3D<T> v1, final T a2, final FieldVector3D<T> v2,
953 final T a3, final FieldVector3D<T> v3, final T a4, final FieldVector3D<T> v4,
954 final T a5, final FieldVector3D<T> v5,
955 final T[] row, final int j) {
956 row[j] = a1.linearCombination(a1, v1.getX(), a2, v2.getX(), a3, v3.getX(), a4, v4.getX()).add(a5.multiply(v5.getX()));
957 row[j + 1] = a1.linearCombination(a1, v1.getY(), a2, v2.getY(), a3, v3.getY(), a4, v4.getY()).add(a5.multiply(v5.getY()));
958 row[j + 2] = a1.linearCombination(a1, v1.getZ(), a2, v2.getZ(), a3, v3.getZ(), a4, v4.getZ()).add(a5.multiply(v5.getZ()));
959
960 }
961
962 /** Fill a Jacobian half row with a linear combination of vectors.
963 * @param a1 coefficient of the first vector
964 * @param v1 first vector
965 * @param a2 coefficient of the second vector
966 * @param v2 second vector
967 * @param a3 coefficient of the third vector
968 * @param v3 third vector
969 * @param a4 coefficient of the fourth vector
970 * @param v4 fourth vector
971 * @param a5 coefficient of the fifth vector
972 * @param v5 fifth vector
973 * @param a6 coefficient of the sixth vector
974 * @param v6 sixth vector
975 * @param row Jacobian matrix row
976 * @param j index of the first element to set (row[j], row[j+1] and row[j+2] will all be set)
977 */
978 protected void fillHalfRow(final T a1, final FieldVector3D<T> v1, final T a2, final FieldVector3D<T> v2,
979 final T a3, final FieldVector3D<T> v3, final T a4, final FieldVector3D<T> v4,
980 final T a5, final FieldVector3D<T> v5, final T a6, final FieldVector3D<T> v6,
981 final T[] row, final int j) {
982 row[j] = a1.linearCombination(a1, v1.getX(), a2, v2.getX(), a3, v3.getX(), a4, v4.getX()).add(a1.linearCombination(a5, v5.getX(), a6, v6.getX()));
983 row[j + 1] = a1.linearCombination(a1, v1.getY(), a2, v2.getY(), a3, v3.getY(), a4, v4.getY()).add(a1.linearCombination(a5, v5.getY(), a6, v6.getY()));
984 row[j + 2] = a1.linearCombination(a1, v1.getZ(), a2, v2.getZ(), a3, v3.getZ(), a4, v4.getZ()).add(a1.linearCombination(a5, v5.getZ(), a6, v6.getZ()));
985
986 }
987
988
989 }