1   /* Copyright 2002-2021 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 java.util.Arrays;
21  import java.util.stream.Stream;
22  
23  import org.hipparchus.Field;
24  import org.hipparchus.CalculusFieldElement;
25  import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative2;
26  import org.hipparchus.exception.LocalizedCoreFormats;
27  import org.hipparchus.exception.MathIllegalStateException;
28  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
29  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
30  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
31  import org.hipparchus.geometry.euclidean.threed.Vector3D;
32  import org.hipparchus.util.FastMath;
33  import org.hipparchus.util.FieldSinCos;
34  import org.hipparchus.util.MathArrays;
35  import org.orekit.errors.OrekitMessages;
36  import org.orekit.frames.Frame;
37  import org.orekit.time.AbsoluteDate;
38  import org.orekit.time.FieldAbsoluteDate;
39  import org.orekit.utils.CartesianDerivativesFilter;
40  import org.orekit.utils.FieldPVCoordinates;
41  import org.orekit.utils.PVCoordinates;
42  import org.orekit.utils.TimeStampedFieldPVCoordinates;
43  
44  
45  /** This class holds Cartesian orbital parameters.
46  
47   * <p>
48   * The parameters used internally are the Cartesian coordinates:
49   *   <ul>
50   *     <li>x</li>
51   *     <li>y</li>
52   *     <li>z</li>
53   *     <li>xDot</li>
54   *     <li>yDot</li>
55   *     <li>zDot</li>
56   *   </ul>
57   * contained in {@link PVCoordinates}.
58  
59   * <p>
60   * Note that the implementation of this class delegates all non-Cartesian related
61   * computations ({@link #getA()}, {@link #getEquinoctialEx()}, ...) to an underlying
62   * instance of the {@link EquinoctialOrbit} class. This implies that using this class
63   * only for analytical computations which are always based on non-Cartesian
64   * parameters is perfectly possible but somewhat sub-optimal.
65   * </p>
66   * <p>
67   * The instance <code>CartesianOrbit</code> is guaranteed to be immutable.
68   * </p>
69   * @see    Orbit
70   * @see    KeplerianOrbit
71   * @see    CircularOrbit
72   * @see    EquinoctialOrbit
73   * @author Luc Maisonobe
74   * @author Guylaine Prat
75   * @author Fabien Maussion
76   * @author V&eacute;ronique Pommier-Maurussane
77   * @since 9.0
78   */
79  public class FieldCartesianOrbit<T extends CalculusFieldElement<T>> extends FieldOrbit<T> {
80  
81      /** Indicator for non-Keplerian acceleration. */
82      private final transient boolean hasNonKeplerianAcceleration;
83  
84      /** Underlying equinoctial orbit to which high-level methods are delegated. */
85      private transient FieldEquinoctialOrbit<T> equinoctial;
86  
87      /** Field used by this class.*/
88      private final Field<T> field;
89  
90      /** Zero. (could be usefull)*/
91      private final T zero;
92  
93      /** One. (could be useful)*/
94      private final T one;
95  
96      /** Constructor from Cartesian parameters.
97       *
98       * <p> The acceleration provided in {@code pvCoordinates} is accessible using
99       * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
100      * use {@code mu} and the position to compute the acceleration, including
101      * {@link #shiftedBy(CalculusFieldElement)} and {@link #getPVCoordinates(FieldAbsoluteDate, Frame)}.
102      *
103      * @param pvaCoordinates the position, velocity and acceleration of the satellite.
104      * @param frame the frame in which the {@link PVCoordinates} are defined
105      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
106      * @param mu central attraction coefficient (m³/s²)
107      * @exception IllegalArgumentException if frame is not a {@link
108      * Frame#isPseudoInertial pseudo-inertial frame}
109      */
110     public FieldCartesianOrbit(final TimeStampedFieldPVCoordinates<T> pvaCoordinates,
111                                final Frame frame, final T mu)
112         throws IllegalArgumentException {
113         super(pvaCoordinates, frame, mu);
114         hasNonKeplerianAcceleration = hasNonKeplerianAcceleration(pvaCoordinates, mu);
115         equinoctial = null;
116         field = pvaCoordinates.getPosition().getX().getField();
117         zero = field.getZero();
118         one = field.getOne();
119     }
120 
121     /** Constructor from Cartesian parameters.
122      *
123      * <p> The acceleration provided in {@code pvCoordinates} is accessible using
124      * {@link #getPVCoordinates()} and {@link #getPVCoordinates(Frame)}. All other methods
125      * use {@code mu} and the position to compute the acceleration, including
126      * {@link #shiftedBy(CalculusFieldElement)} and {@link #getPVCoordinates(FieldAbsoluteDate, Frame)}.
127      *
128      * @param pvaCoordinates the position and velocity of the satellite.
129      * @param frame the frame in which the {@link PVCoordinates} are defined
130      * (<em>must</em> be a {@link Frame#isPseudoInertial pseudo-inertial frame})
131      * @param date date of the orbital parameters
132      * @param mu central attraction coefficient (m³/s²)
133      * @exception IllegalArgumentException if frame is not a {@link
134      * Frame#isPseudoInertial pseudo-inertial frame}
135      */
136     public FieldCartesianOrbit(final FieldPVCoordinates<T> pvaCoordinates, final Frame frame,
137                                final FieldAbsoluteDate<T> date, final T mu)
138         throws IllegalArgumentException {
139         this(new TimeStampedFieldPVCoordinates<>(date, pvaCoordinates), frame, mu);
140     }
141 
142     /** Constructor from any kind of orbital parameters.
143      * @param op orbital parameters to copy
144      */
145     public FieldCartesianOrbit(final FieldOrbit<T> op) {
146         super(op.getPVCoordinates(), op.getFrame(), op.getMu());
147         hasNonKeplerianAcceleration = op.hasDerivatives();
148         if (op instanceof FieldEquinoctialOrbit) {
149             equinoctial = (FieldEquinoctialOrbit<T>) op;
150         } else if (op instanceof FieldCartesianOrbit) {
151             equinoctial = ((FieldCartesianOrbit<T>) op).equinoctial;
152         } else {
153             equinoctial = null;
154         }
155         field = op.getA().getField();
156         zero = field.getZero();
157         one = field.getOne();
158     }
159 
160     /** {@inheritDoc} */
161     public OrbitType getType() {
162         return OrbitType.CARTESIAN;
163     }
164 
165     /** Lazy evaluation of equinoctial parameters. */
166     private void initEquinoctial() {
167         if (equinoctial == null) {
168             if (hasDerivatives()) {
169                 // getPVCoordinates includes accelerations that will be interpreted as derivatives
170                 equinoctial = new FieldEquinoctialOrbit<>(getPVCoordinates(), getFrame(), getDate(), getMu());
171             } else {
172                 // get rid of Keplerian acceleration so we don't assume
173                 // we have derivatives when in fact we don't have them
174                 equinoctial = new FieldEquinoctialOrbit<>(new FieldPVCoordinates<>(getPVCoordinates().getPosition(),
175                                                                                    getPVCoordinates().getVelocity()),
176                                                           getFrame(), getDate(), getMu());
177             }
178         }
179     }
180 
181     /** Get the position/velocity with derivatives.
182      * @return position/velocity with derivatives
183      * @since 10.2
184      */
185     private FieldPVCoordinates<FieldUnivariateDerivative2<T>> getPVDerivatives() {
186         // PVA coordinates
187         final FieldPVCoordinates<T> pva = getPVCoordinates();
188         final FieldVector3D<T>      p   = pva.getPosition();
189         final FieldVector3D<T>      v   = pva.getVelocity();
190         final FieldVector3D<T>      a   = pva.getAcceleration();
191         // Field coordinates
192         final FieldVector3D<FieldUnivariateDerivative2<T>> pG = new FieldVector3D<>(new FieldUnivariateDerivative2<>(p.getX(), v.getX(), a.getX()),
193                                                              new FieldUnivariateDerivative2<>(p.getY(), v.getY(), a.getY()),
194                                                              new FieldUnivariateDerivative2<>(p.getZ(), v.getZ(), a.getZ()));
195         final FieldVector3D<FieldUnivariateDerivative2<T>> vG = new FieldVector3D<>(new FieldUnivariateDerivative2<>(v.getX(), a.getX(), zero),
196                                                              new FieldUnivariateDerivative2<>(v.getY(), a.getY(), zero),
197                                                              new FieldUnivariateDerivative2<>(v.getZ(), a.getZ(), zero));
198         return new FieldPVCoordinates<>(pG, vG);
199     }
200 
201     /** {@inheritDoc} */
202     public T getA() {
203         // lazy evaluation of semi-major axis
204         final T r  = getPVCoordinates().getPosition().getNorm();
205         final T V2 = getPVCoordinates().getVelocity().getNormSq();
206         return r.divide(r.negate().multiply(V2).divide(getMu()).add(2));
207     }
208 
209     /** {@inheritDoc} */
210     public T getADot() {
211         if (hasDerivatives()) {
212             final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
213             final FieldUnivariateDerivative2<T> r  = pv.getPosition().getNorm();
214             final FieldUnivariateDerivative2<T> V2 = pv.getVelocity().getNormSq();
215             final FieldUnivariateDerivative2<T> a  = r.divide(r.multiply(V2).divide(getMu()).subtract(2).negate());
216             return a.getDerivative(1);
217         } else {
218             return null;
219         }
220     }
221 
222     /** {@inheritDoc} */
223     public T getE() {
224         final T muA = getA().multiply(getMu());
225         if (muA.getReal() > 0) {
226             // elliptic or circular orbit
227             final FieldVector3D<T> pvP   = getPVCoordinates().getPosition();
228             final FieldVector3D<T> pvV   = getPVCoordinates().getVelocity();
229             final T rV2OnMu = pvP.getNorm().multiply(pvV.getNormSq()).divide(getMu());
230             final T eSE     = FieldVector3D.dotProduct(pvP, pvV).divide(muA.sqrt());
231             final T eCE     = rV2OnMu.subtract(1);
232             return (eCE.multiply(eCE).add(eSE.multiply(eSE))).sqrt();
233         } else {
234             // hyperbolic orbit
235             final FieldVector3D<T> pvM = getPVCoordinates().getMomentum();
236             return pvM.getNormSq().divide(muA).negate().add(1).sqrt();
237         }
238     }
239 
240     /** {@inheritDoc} */
241     public T getEDot() {
242         if (hasDerivatives()) {
243             final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
244             final FieldUnivariateDerivative2<T> r       = pv.getPosition().getNorm();
245             final FieldUnivariateDerivative2<T> V2      = pv.getVelocity().getNormSq();
246             final FieldUnivariateDerivative2<T> rV2OnMu = r.multiply(V2).divide(getMu());
247             final FieldUnivariateDerivative2<T> a       = r.divide(rV2OnMu.negate().add(2));
248             final FieldUnivariateDerivative2<T> eSE     = FieldVector3D.dotProduct(pv.getPosition(), pv.getVelocity()).divide(a.multiply(getMu()).sqrt());
249             final FieldUnivariateDerivative2<T> eCE     = rV2OnMu.subtract(1);
250             final FieldUnivariateDerivative2<T> e       = eCE.multiply(eCE).add(eSE.multiply(eSE)).sqrt();
251             return e.getDerivative(1);
252         } else {
253             return null;
254         }
255     }
256 
257     /** {@inheritDoc} */
258     public T getI() {
259         return FieldVector3D.angle(new FieldVector3D<>(zero, zero, one), getPVCoordinates().getMomentum());
260     }
261 
262     /** {@inheritDoc} */
263     public T getIDot() {
264         if (hasDerivatives()) {
265             final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
266             final FieldVector3D<FieldUnivariateDerivative2<T>> momentum =
267                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity());
268             final FieldUnivariateDerivative2<T> i = FieldVector3D.angle(Vector3D.PLUS_K, momentum);
269             return i.getDerivative(1);
270         } else {
271             return null;
272         }
273     }
274 
275     /** {@inheritDoc} */
276     public T getEquinoctialEx() {
277         initEquinoctial();
278         return equinoctial.getEquinoctialEx();
279     }
280 
281     /** {@inheritDoc} */
282     public T getEquinoctialExDot() {
283         initEquinoctial();
284         return equinoctial.getEquinoctialExDot();
285     }
286 
287     /** {@inheritDoc} */
288     public T getEquinoctialEy() {
289         initEquinoctial();
290         return equinoctial.getEquinoctialEy();
291     }
292 
293     /** {@inheritDoc} */
294     public T getEquinoctialEyDot() {
295         initEquinoctial();
296         return equinoctial.getEquinoctialEyDot();
297     }
298 
299     /** {@inheritDoc} */
300     public T getHx() {
301         final FieldVector3D<T> w = getPVCoordinates().getMomentum().normalize();
302         // Check for equatorial retrograde orbit
303         final double x = w.getX().getReal();
304         final double y = w.getY().getReal();
305         final double z = w.getZ().getReal();
306         if ((x * x + y * y) == 0 && z < 0) {
307             return zero.add(Double.NaN);
308         }
309         return w.getY().negate().divide(w.getZ().add(1));
310     }
311 
312     /** {@inheritDoc} */
313     public T getHxDot() {
314         if (hasDerivatives()) {
315             final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
316             final FieldVector3D<FieldUnivariateDerivative2<T>> w =
317                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
318             // Check for equatorial retrograde orbit
319             final double x = w.getX().getValue().getReal();
320             final double y = w.getY().getValue().getReal();
321             final double z = w.getZ().getValue().getReal();
322             if ((x * x + y * y) == 0 && z < 0) {
323                 return zero.add(Double.NaN);
324             }
325             final FieldUnivariateDerivative2<T> hx = w.getY().negate().divide(w.getZ().add(1));
326             return hx.getDerivative(1);
327         } else {
328             return null;
329         }
330     }
331 
332     /** {@inheritDoc} */
333     public T getHy() {
334         final FieldVector3D<T> w = getPVCoordinates().getMomentum().normalize();
335         // Check for equatorial retrograde orbit
336         final double x = w.getX().getReal();
337         final double y = w.getY().getReal();
338         final double z = w.getZ().getReal();
339         if ((x * x + y * y) == 0 && z < 0) {
340             return zero.add(Double.NaN);
341         }
342         return  w.getX().divide(w.getZ().add(1));
343     }
344 
345     /** {@inheritDoc} */
346     public T getHyDot() {
347         if (hasDerivatives()) {
348             final FieldPVCoordinates<FieldUnivariateDerivative2<T>> pv = getPVDerivatives();
349             final FieldVector3D<FieldUnivariateDerivative2<T>> w =
350                             FieldVector3D.crossProduct(pv.getPosition(), pv.getVelocity()).normalize();
351             // Check for equatorial retrograde orbit
352             final double x = w.getX().getValue().getReal();
353             final double y = w.getY().getValue().getReal();
354             final double z = w.getZ().getValue().getReal();
355             if ((x * x + y * y) == 0 && z < 0) {
356                 return zero.add(Double.NaN);
357             }
358             final FieldUnivariateDerivative2<T> hy = w.getX().divide(w.getZ().add(1));
359             return hy.getDerivative(1);
360         } else {
361             return null;
362         }
363     }
364 
365     /** {@inheritDoc} */
366     public T getLv() {
367         initEquinoctial();
368         return equinoctial.getLv();
369     }
370 
371     /** {@inheritDoc} */
372     public T getLvDot() {
373         initEquinoctial();
374         return equinoctial.getLvDot();
375     }
376 
377     /** {@inheritDoc} */
378     public T getLE() {
379         initEquinoctial();
380         return equinoctial.getLE();
381     }
382 
383     /** {@inheritDoc} */
384     public T getLEDot() {
385         initEquinoctial();
386         return equinoctial.getLEDot();
387     }
388 
389     /** {@inheritDoc} */
390     public T getLM() {
391         initEquinoctial();
392         return equinoctial.getLM();
393     }
394 
395     /** {@inheritDoc} */
396     public T getLMDot() {
397         initEquinoctial();
398         return equinoctial.getLMDot();
399     }
400 
401     /** {@inheritDoc} */
402     public boolean hasDerivatives() {
403         return hasNonKeplerianAcceleration;
404     }
405 
406     /** {@inheritDoc} */
407     protected TimeStampedFieldPVCoordinates<T> initPVCoordinates() {
408         // nothing to do here, as the canonical elements are already the Cartesian ones
409         return getPVCoordinates();
410     }
411 
412     /** {@inheritDoc} */
413     public FieldCartesianOrbit<T> shiftedBy(final double dt) {
414         return shiftedBy(getDate().getField().getZero().add(dt));
415     }
416 
417     /** {@inheritDoc} */
418     public FieldCartesianOrbit<T> shiftedBy(final T dt) {
419         final FieldPVCoordinates<T> shiftedPV = (getA().getReal() < 0) ? shiftPVHyperbolic(dt) : shiftPVElliptic(dt);
420         return new FieldCartesianOrbit<>(shiftedPV, getFrame(), getDate().shiftedBy(dt), getMu());
421     }
422 
423     /** {@inheritDoc}
424      * <p>
425      * The interpolated instance is created by polynomial Hermite interpolation
426      * ensuring velocity remains the exact derivative of position.
427      * </p>
428      * <p>
429      * As this implementation of interpolation is polynomial, it should be used only
430      * with small samples (about 10-20 points) in order to avoid <a
431      * href="http://en.wikipedia.org/wiki/Runge%27s_phenomenon">Runge's phenomenon</a>
432      * and numerical problems (including NaN appearing).
433      * </p>
434      * <p>
435      * If orbit interpolation on large samples is needed, using the {@link
436      * org.orekit.propagation.analytical.Ephemeris} class is a better way than using this
437      * low-level interpolation. The Ephemeris class automatically handles selection of
438      * a neighboring sub-sample with a predefined number of point from a large global sample
439      * in a thread-safe way.
440      * </p>
441      */
442     public FieldCartesianOrbit<T> interpolate(final FieldAbsoluteDate<T> date, final Stream<FieldOrbit<T>> sample) {
443         final TimeStampedFieldPVCoordinates<T> interpolated =
444                 TimeStampedFieldPVCoordinates.interpolate(date, CartesianDerivativesFilter.USE_PVA,
445                                                           sample.map(orbit -> orbit.getPVCoordinates()));
446         return new FieldCartesianOrbit<>(interpolated, getFrame(), date, getMu());
447     }
448 
449     /** Compute shifted position and velocity in elliptic case.
450      * @param dt time shift
451      * @return shifted position and velocity
452      */
453     private FieldPVCoordinates<T> shiftPVElliptic(final T dt) {
454 
455         // preliminary computation
456         final FieldVector3D<T> pvP   = getPVCoordinates().getPosition();
457         final FieldVector3D<T> pvV   = getPVCoordinates().getVelocity();
458         final T r2      = pvP.getNormSq();
459         final T r       = r2.sqrt();
460         final T rV2OnMu = r.multiply(pvV.getNormSq()).divide(getMu());
461         final T a       = r.divide(rV2OnMu.negate().add(2));
462         final T eSE     = FieldVector3D.dotProduct(pvP, pvV).divide(a.multiply(getMu()).sqrt());
463         final T eCE     = rV2OnMu.subtract(1);
464         final T e2      = eCE.multiply(eCE).add(eSE.multiply(eSE));
465 
466         // we can use any arbitrary reference 2D frame in the orbital plane
467         // in order to simplify some equations below, we use the current position as the u axis
468         final FieldVector3D<T> u     = pvP.normalize();
469         final FieldVector3D<T> v     = FieldVector3D.crossProduct(FieldVector3D.crossProduct(pvP, pvV), u).normalize();
470 
471         // the following equations rely on the specific choice of u explained above,
472         // some coefficients that vanish to 0 in this case have already been removed here
473         final T              ex        = eCE.subtract(e2).multiply(a).divide(r);
474         final T              s         = e2.negate().add(1).sqrt();
475         final T              ey        = s.negate().multiply(eSE).multiply(a).divide(r);
476         final T              beta      = s.add(1).reciprocal();
477         final T              thetaE0   = ey.add(eSE.multiply(beta).multiply(ex)).atan2(r.divide(a).add(ex).subtract(eSE.multiply(beta).multiply(ey)));
478         final FieldSinCos<T> scThetaE0 = FastMath.sinCos(thetaE0);
479         final T              thetaM0   = thetaE0.subtract(ex.multiply(scThetaE0.sin())).add(ey.multiply(scThetaE0.cos()));
480 
481         // compute in-plane shifted eccentric argument
482         final T              sqrtMmuOA = a.reciprocal().multiply(getMu()).sqrt();
483         final T              thetaM1   = thetaM0.add(sqrtMmuOA.divide(a).multiply(dt));
484         final T              thetaE1   = meanToEccentric(thetaM1, ex, ey);
485         final FieldSinCos<T> scTE      = FastMath.sinCos(thetaE1);
486         final T              cTE       = scTE.cos();
487         final T              sTE       = scTE.sin();
488 
489         // compute shifted in-plane Cartesian coordinates
490         final T exey   = ex.multiply(ey);
491         final T exCeyS = ex.multiply(cTE).add(ey.multiply(sTE));
492         final T x      = a.multiply(beta.multiply(ey).multiply(ey).negate().add(1).multiply(cTE).add(beta.multiply(exey).multiply(sTE)).subtract(ex));
493         final T y      = a.multiply(beta.multiply(ex).multiply(ex).negate().add(1).multiply(sTE).add(beta.multiply(exey).multiply(cTE)).subtract(ey));
494         final T factor = sqrtMmuOA.divide(exCeyS.negate().add(1));
495         final T xDot   = factor.multiply(beta.multiply(ey).multiply(exCeyS).subtract(sTE));
496         final T yDot   = factor.multiply(cTE.subtract(beta.multiply(ex).multiply(exCeyS)));
497 
498         final FieldVector3D<T> shiftedP = new FieldVector3D<>(x, u, y, v);
499         final FieldVector3D<T> shiftedV = new FieldVector3D<>(xDot, u, yDot, v);
500         if (hasNonKeplerianAcceleration) {
501 
502             // extract non-Keplerian part of the initial acceleration
503             final FieldVector3D<T> nonKeplerianAcceleration = new FieldVector3D<>(one, getPVCoordinates().getAcceleration(),
504                                                                                   r.multiply(r2).reciprocal().multiply(getMu()), pvP);
505 
506             // add the quadratic motion due to the non-Keplerian acceleration to the Keplerian motion
507             final FieldVector3D<T> fixedP   = new FieldVector3D<>(one, shiftedP,
508                                                                   dt.multiply(dt).multiply(0.5), nonKeplerianAcceleration);
509             final T                fixedR2 = fixedP.getNormSq();
510             final T                fixedR  = fixedR2.sqrt();
511             final FieldVector3D<T> fixedV  = new FieldVector3D<>(one, shiftedV,
512                                                                  dt, nonKeplerianAcceleration);
513             final FieldVector3D<T> fixedA  = new FieldVector3D<>(fixedR.multiply(fixedR2).reciprocal().multiply(getMu().negate()), shiftedP,
514                                                                  one, nonKeplerianAcceleration);
515 
516             return new FieldPVCoordinates<>(fixedP, fixedV, fixedA);
517 
518         } else {
519             // don't include acceleration,
520             // so the shifted orbit is not considered to have derivatives
521             return new FieldPVCoordinates<>(shiftedP, shiftedV);
522         }
523 
524     }
525 
526     /** Compute shifted position and velocity in hyperbolic case.
527      * @param dt time shift
528      * @return shifted position and velocity
529      */
530     private FieldPVCoordinates<T> shiftPVHyperbolic(final T dt) {
531 
532         final FieldPVCoordinates<T> pv = getPVCoordinates();
533         final FieldVector3D<T> pvP   = pv.getPosition();
534         final FieldVector3D<T> pvV   = pv.getVelocity();
535         final FieldVector3D<T> pvM   = pv.getMomentum();
536         final T r2      = pvP.getNormSq();
537         final T r       = r2.sqrt();
538         final T rV2OnMu = r.multiply(pvV.getNormSq()).divide(getMu());
539         final T a       = getA();
540         final T muA     = a.multiply(getMu());
541         final T e       = one.subtract(FieldVector3D.dotProduct(pvM, pvM).divide(muA)).sqrt();
542         final T sqrt    = e.add(1).divide(e.subtract(1)).sqrt();
543 
544         // compute mean anomaly
545         final T eSH     = FieldVector3D.dotProduct(pvP, pvV).divide(muA.negate().sqrt());
546         final T eCH     = rV2OnMu.subtract(1);
547         final T H0      = eCH.add(eSH).divide(eCH.subtract(eSH)).log().divide(2);
548         final T M0      = e.multiply(H0.sinh()).subtract(H0);
549 
550         // find canonical 2D frame with p pointing to perigee
551         final T v0      = sqrt.multiply(H0.divide(2).tanh()).atan().multiply(2);
552         final FieldVector3D<T> p     = new FieldRotation<>(pvM, v0, RotationConvention.FRAME_TRANSFORM).applyTo(pvP).normalize();
553         final FieldVector3D<T> q     = FieldVector3D.crossProduct(pvM, p).normalize();
554 
555         // compute shifted eccentric anomaly
556         final T M1      = M0.add(getKeplerianMeanMotion().multiply(dt));
557         final T H1      = meanToHyperbolicEccentric(M1, e);
558 
559         // compute shifted in-plane Cartesian coordinates
560         final T cH     = H1.cosh();
561         final T sH     = H1.sinh();
562         final T sE2m1  = e.subtract(1).multiply(e.add(1)).sqrt();
563 
564         // coordinates of position and velocity in the orbital plane
565         final T x      = a.multiply(cH.subtract(e));
566         final T y      = a.negate().multiply(sE2m1).multiply(sH);
567         final T factor = getMu().divide(a.negate()).sqrt().divide(e.multiply(cH).subtract(1));
568         final T xDot   = factor.negate().multiply(sH);
569         final T yDot   =  factor.multiply(sE2m1).multiply(cH);
570 
571         final FieldVector3D<T> shiftedP = new FieldVector3D<>(x, p, y, q);
572         final FieldVector3D<T> shiftedV = new FieldVector3D<>(xDot, p, yDot, q);
573         if (hasNonKeplerianAcceleration) {
574 
575             // extract non-Keplerian part of the initial acceleration
576             final FieldVector3D<T> nonKeplerianAcceleration = new FieldVector3D<>(one, getPVCoordinates().getAcceleration(),
577                                                                                   r.multiply(r2).reciprocal().multiply(getMu()), pvP);
578 
579             // add the quadratic motion due to the non-Keplerian acceleration to the Keplerian motion
580             final FieldVector3D<T> fixedP   = new FieldVector3D<>(one, shiftedP,
581                                                                   dt.multiply(dt).multiply(0.5), nonKeplerianAcceleration);
582             final T                fixedR2 = fixedP.getNormSq();
583             final T                fixedR  = fixedR2.sqrt();
584             final FieldVector3D<T> fixedV  = new FieldVector3D<>(one, shiftedV,
585                                                                  dt, nonKeplerianAcceleration);
586             final FieldVector3D<T> fixedA  = new FieldVector3D<>(fixedR.multiply(fixedR2).reciprocal().multiply(getMu().negate()), shiftedP,
587                                                                  one, nonKeplerianAcceleration);
588 
589             return new FieldPVCoordinates<>(fixedP, fixedV, fixedA);
590 
591         } else {
592             // don't include acceleration,
593             // so the shifted orbit is not considered to have derivatives
594             return new FieldPVCoordinates<>(shiftedP, shiftedV);
595         }
596 
597     }
598 
599     /** Computes the eccentric in-plane argument from the mean in-plane argument.
600      * @param thetaM = mean in-plane argument (rad)
601      * @param ex first component of eccentricity vector
602      * @param ey second component of eccentricity vector
603      * @return the eccentric in-plane argument.
604      */
605     private T meanToEccentric(final T thetaM, final T ex, final T ey) {
606         // Generalization of Kepler equation to in-plane parameters
607         // with thetaE = eta + E and
608         //      thetaM = eta + M = thetaE - ex.sin(thetaE) + ey.cos(thetaE)
609         // and eta being counted from an arbitrary reference in the orbital plane
610         T thetaE        = thetaM;
611         T thetaEMthetaM = zero;
612         int    iter          = 0;
613         do {
614             final FieldSinCos<T> scThetaE = FastMath.sinCos(thetaE);
615 
616             final T f2 = ex.multiply(scThetaE.sin()).subtract(ey.multiply(scThetaE.cos()));
617             final T f1 = one.subtract(ex.multiply(scThetaE.cos())).subtract(ey.multiply(scThetaE.sin()));
618             final T f0 = thetaEMthetaM.subtract(f2);
619 
620             final T f12 = f1.multiply(2.0);
621             final T shift = f0.multiply(f12).divide(f1.multiply(f12).subtract(f0.multiply(f2)));
622 
623             thetaEMthetaM = thetaEMthetaM.subtract(shift);
624             thetaE         = thetaM.add(thetaEMthetaM);
625 
626             if (FastMath.abs(shift.getReal()) <= 1.0e-12) {
627                 return thetaE;
628             }
629 
630         } while (++iter < 50);
631 
632         throw new MathIllegalStateException(LocalizedCoreFormats.CONVERGENCE_FAILED);
633 
634     }
635 
636     /** Computes the hyperbolic eccentric anomaly from the mean anomaly.
637      * <p>
638      * The algorithm used here for solving hyperbolic Kepler equation is
639      * Danby's iterative method (3rd order) with Vallado's initial guess.
640      * </p>
641      * @param M mean anomaly (rad)
642      * @param ecc eccentricity
643      * @return the hyperbolic eccentric anomaly
644      */
645     private T meanToHyperbolicEccentric(final T M, final T ecc) {
646 
647         // Resolution of hyperbolic Kepler equation for Keplerian parameters
648 
649         // Field value of pi
650         final T pi = ecc.getPi();
651 
652         // Initial guess
653         T H;
654         if (ecc.getReal() < 1.6) {
655             if (-pi.getReal() < M.getReal() && M.getReal() < 0. || M.getReal() > pi.getReal()) {
656                 H = M.subtract(ecc);
657             } else {
658                 H = M.add(ecc);
659             }
660         } else {
661             if (ecc.getReal() < 3.6 && FastMath.abs(M.getReal()) > pi.getReal()) {
662                 H = M.subtract(ecc.copySign(M));
663             } else {
664                 H = M.divide(ecc.subtract(1.));
665             }
666         }
667 
668         // Iterative computation
669         int iter = 0;
670         do {
671             final T f3  = ecc.multiply(H.cosh());
672             final T f2  = ecc.multiply(H.sinh());
673             final T f1  = f3.subtract(1.);
674             final T f0  = f2.subtract(H).subtract(M);
675             final T f12 = f1.multiply(2);
676             final T d   = f0.divide(f12);
677             final T fdf = f1.subtract(d.multiply(f2));
678             final T ds  = f0.divide(fdf);
679 
680             final T shift = f0.divide(fdf.add(ds.multiply(ds).multiply(f3.divide(6.))));
681 
682             H = H.subtract(shift);
683 
684             if (FastMath.abs(shift.getReal()) <= 1.0e-12) {
685                 return H;
686             }
687 
688         } while (++iter < 50);
689 
690         throw new MathIllegalStateException(OrekitMessages.UNABLE_TO_COMPUTE_HYPERBOLIC_ECCENTRIC_ANOMALY,
691                                             iter);
692     }
693 
694     /** Create a 6x6 identity matrix.
695      * @return 6x6 identity matrix
696      */
697     private T[][] create6x6Identity() {
698         // this is the fastest way to set the 6x6 identity matrix
699         final T[][] identity = MathArrays.buildArray(field, 6, 6);
700         for (int i = 0; i < 6; i++) {
701             Arrays.fill(identity[i], zero);
702             identity[i][i] = one;
703         }
704         return identity;
705     }
706 
707     @Override
708     protected T[][] computeJacobianMeanWrtCartesian() {
709         return create6x6Identity();
710     }
711 
712     @Override
713     protected T[][] computeJacobianEccentricWrtCartesian() {
714         return create6x6Identity();
715     }
716 
717     @Override
718     protected T[][] computeJacobianTrueWrtCartesian() {
719         return create6x6Identity();
720     }
721 
722     /** {@inheritDoc} */
723     public void addKeplerContribution(final PositionAngle type, final T gm,
724                                       final T[] pDot) {
725 
726         final FieldPVCoordinates<T> pv = getPVCoordinates();
727 
728         // position derivative is velocity
729         final FieldVector3D<T> velocity = pv.getVelocity();
730         pDot[0] = pDot[0].add(velocity.getX());
731         pDot[1] = pDot[1].add(velocity.getY());
732         pDot[2] = pDot[2].add(velocity.getZ());
733 
734         // velocity derivative is Newtonian acceleration
735         final FieldVector3D<T> position = pv.getPosition();
736         final T r2         = position.getNormSq();
737         final T coeff      = r2.multiply(r2.sqrt()).reciprocal().negate().multiply(gm);
738         pDot[3] = pDot[3].add(coeff.multiply(position.getX()));
739         pDot[4] = pDot[4].add(coeff.multiply(position.getY()));
740         pDot[5] = pDot[5].add(coeff.multiply(position.getZ()));
741 
742     }
743 
744     /**  Returns a string representation of this Orbit object.
745      * @return a string representation of this object
746      */
747     public String toString() {
748         // use only the six defining elements, like the other Orbit.toString() methods
749         final String comma = ", ";
750         final PVCoordinates pv = getPVCoordinates().toPVCoordinates();
751         final Vector3D p = pv.getPosition();
752         final Vector3D v = pv.getVelocity();
753         return "Cartesian parameters: {P(" +
754                 p.getX() + comma +
755                 p.getY() + comma +
756                 p.getZ() + "), V(" +
757                 v.getX() + comma +
758                 v.getY() + comma +
759                 v.getZ() + ")}";
760     }
761 
762     @Override
763     public CartesianOrbit toOrbit() {
764         final PVCoordinates pv = getPVCoordinates().toPVCoordinates();
765         final AbsoluteDate date = getPVCoordinates().getDate().toAbsoluteDate();
766         if (hasDerivatives()) {
767             // getPVCoordinates includes accelerations that will be interpreted as derivatives
768             return new CartesianOrbit(pv, getFrame(), date, getMu().getReal());
769         } else {
770             // get rid of Keplerian acceleration so we don't assume
771             // we have derivatives when in fact we don't have them
772             return new CartesianOrbit(new PVCoordinates(pv.getPosition(), pv.getVelocity()),
773                                       getFrame(), date, getMu().getReal());
774         }
775     }
776 
777 }