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.utils;
18  
19  import org.hipparchus.Field;
20  import org.hipparchus.CalculusFieldElement;
21  import org.hipparchus.analysis.differentiation.FDSFactory;
22  import org.hipparchus.analysis.differentiation.FieldDerivative;
23  import org.hipparchus.analysis.differentiation.FieldDerivativeStructure;
24  import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative1;
25  import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative2;
26  import org.hipparchus.analysis.differentiation.UnivariateDerivative1;
27  import org.hipparchus.analysis.differentiation.UnivariateDerivative2;
28  import org.hipparchus.exception.LocalizedCoreFormats;
29  import org.hipparchus.exception.MathIllegalArgumentException;
30  import org.hipparchus.geometry.euclidean.threed.FieldRotation;
31  import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
32  import org.hipparchus.geometry.euclidean.threed.RotationConvention;
33  import org.hipparchus.linear.FieldDecompositionSolver;
34  import org.hipparchus.linear.FieldMatrix;
35  import org.hipparchus.linear.FieldQRDecomposition;
36  import org.hipparchus.linear.FieldVector;
37  import org.hipparchus.linear.MatrixUtils;
38  import org.hipparchus.util.MathArrays;
39  import org.orekit.errors.OrekitException;
40  import org.orekit.errors.OrekitMessages;
41  import org.orekit.time.FieldTimeShiftable;
42  
43  /** Simple container for rotation / rotation rate pairs, using {@link
44   * CalculusFieldElement}.
45   * <p>
46   * The state can be slightly shifted to close dates. This shift is based on
47   * a simple quadratic model. It is <em>not</em> intended as a replacement for
48   * proper attitude propagation but should be sufficient for either small
49   * time shifts or coarse accuracy.
50   * </p>
51   * <p>
52   * This class is the angular counterpart to {@link FieldPVCoordinates}.
53   * </p>
54   * <p>Instances of this class are guaranteed to be immutable.</p>
55   * @param <T> the type of the field elements
56   * @author Luc Maisonobe
57   * @since 6.0
58   * @see AngularCoordinates
59   */
60  public class FieldAngularCoordinates<T extends CalculusFieldElement<T>>
61          implements FieldTimeShiftable<FieldAngularCoordinates<T>, T> {
62  
63      /** rotation. */
64      private final FieldRotation<T> rotation;
65  
66      /** rotation rate. */
67      private final FieldVector3D<T> rotationRate;
68  
69      /** rotation acceleration. */
70      private final FieldVector3D<T> rotationAcceleration;
71  
72      /** Builds a rotation/rotation rate pair.
73       * @param rotation rotation
74       * @param rotationRate rotation rate Ω (rad/s)
75       */
76      public FieldAngularCoordinates(final FieldRotation<T> rotation,
77                                     final FieldVector3D<T> rotationRate) {
78          this(rotation, rotationRate, FieldVector3D.getZero(rotation.getQ0().getField()));
79      }
80  
81      /** Builds a rotation / rotation rate / rotation acceleration triplet.
82       * @param rotation i.e. the orientation of the vehicle
83       * @param rotationRate rotation rate Ω, i.e. the spin vector (rad/s)
84       * @param rotationAcceleration angular acceleration vector dΩ/dt (rad/s²)
85       */
86      public FieldAngularCoordinates(final FieldRotation<T> rotation,
87                                     final FieldVector3D<T> rotationRate,
88                                     final FieldVector3D<T> rotationAcceleration) {
89          this.rotation             = rotation;
90          this.rotationRate         = rotationRate;
91          this.rotationAcceleration = rotationAcceleration;
92      }
93  
94      /** Build the rotation that transforms a pair of pv coordinates into another one.
95  
96       * <p><em>WARNING</em>! This method requires much more stringent assumptions on
97       * its parameters than the similar {@link FieldRotation#FieldRotation(FieldVector3D, FieldVector3D,
98       * FieldVector3D, FieldVector3D) constructor} from the {@link FieldRotation FieldRotation} class.
99       * As far as the FieldRotation constructor is concerned, the {@code v₂} vector from
100      * the second pair can be slightly misaligned. The FieldRotation constructor will
101      * compensate for this misalignment and create a rotation that ensure {@code
102      * v₁ = r(u₁)} and {@code v₂ ∈ plane (r(u₁), r(u₂))}. <em>THIS IS NOT
103      * TRUE ANYMORE IN THIS CLASS</em>! As derivatives are involved and must be
104      * preserved, this constructor works <em>only</em> if the two pairs are fully
105      * consistent, i.e. if a rotation exists that fulfill all the requirements: {@code
106      * v₁ = r(u₁)}, {@code v₂ = r(u₂)}, {@code dv₁/dt = dr(u₁)/dt}, {@code dv₂/dt
107      * = dr(u₂)/dt}, {@code d²v₁/dt² = d²r(u₁)/dt²}, {@code d²v₂/dt² = d²r(u₂)/dt²}.</p>
108      * @param u1 first vector of the origin pair
109      * @param u2 second vector of the origin pair
110      * @param v1 desired image of u1 by the rotation
111      * @param v2 desired image of u2 by the rotation
112      * @param tolerance relative tolerance factor used to check singularities
113      */
114     public FieldAngularCoordinates(final FieldPVCoordinates<T> u1, final FieldPVCoordinates<T> u2,
115                                    final FieldPVCoordinates<T> v1, final FieldPVCoordinates<T> v2,
116                                    final double tolerance) {
117 
118         try {
119             // find the initial fixed rotation
120             rotation = new FieldRotation<>(u1.getPosition(), u2.getPosition(),
121                                            v1.getPosition(), v2.getPosition());
122 
123             // find rotation rate Ω such that
124             //  Ω ⨯ v₁ = r(dot(u₁)) - dot(v₁)
125             //  Ω ⨯ v₂ = r(dot(u₂)) - dot(v₂)
126             final FieldVector3D<T> ru1Dot = rotation.applyTo(u1.getVelocity());
127             final FieldVector3D<T> ru2Dot = rotation.applyTo(u2.getVelocity());
128 
129 
130             rotationRate = inverseCrossProducts(v1.getPosition(), ru1Dot.subtract(v1.getVelocity()),
131                                                 v2.getPosition(), ru2Dot.subtract(v2.getVelocity()),
132                                                 tolerance);
133 
134 
135             // find rotation acceleration dot(Ω) such that
136             // dot(Ω) ⨯ v₁ = r(dotdot(u₁)) - 2 Ω ⨯ dot(v₁) - Ω ⨯  (Ω ⨯ v₁) - dotdot(v₁)
137             // dot(Ω) ⨯ v₂ = r(dotdot(u₂)) - 2 Ω ⨯ dot(v₂) - Ω ⨯  (Ω ⨯ v₂) - dotdot(v₂)
138             final FieldVector3D<T> ru1DotDot = rotation.applyTo(u1.getAcceleration());
139             final FieldVector3D<T> oDotv1    = FieldVector3D.crossProduct(rotationRate, v1.getVelocity());
140             final FieldVector3D<T> oov1      = FieldVector3D.crossProduct(rotationRate, rotationRate.crossProduct(v1.getPosition()));
141             final FieldVector3D<T> c1        = new FieldVector3D<>(1, ru1DotDot, -2, oDotv1, -1, oov1, -1, v1.getAcceleration());
142             final FieldVector3D<T> ru2DotDot = rotation.applyTo(u2.getAcceleration());
143             final FieldVector3D<T> oDotv2    = FieldVector3D.crossProduct(rotationRate, v2.getVelocity());
144             final FieldVector3D<T> oov2      = FieldVector3D.crossProduct(rotationRate, rotationRate.crossProduct( v2.getPosition()));
145             final FieldVector3D<T> c2        = new FieldVector3D<>(1, ru2DotDot, -2, oDotv2, -1, oov2, -1, v2.getAcceleration());
146             rotationAcceleration     = inverseCrossProducts(v1.getPosition(), c1, v2.getPosition(), c2, tolerance);
147 
148         } catch (MathIllegalArgumentException miae) {
149             throw new OrekitException(miae);
150         }
151 
152     }
153 
154     /** Builds a FieldAngularCoordinates from a field and a regular AngularCoordinates.
155      * @param field field for the components
156      * @param ang AngularCoordinates to convert
157      */
158     public FieldAngularCoordinates(final Field<T> field, final AngularCoordinates ang) {
159         this.rotation             = new FieldRotation<>(field, ang.getRotation());
160         this.rotationRate         = new FieldVector3D<>(field, ang.getRotationRate());
161         this.rotationAcceleration = new FieldVector3D<>(field, ang.getRotationAcceleration());
162     }
163 
164     /** Builds a FieldAngularCoordinates from  a {@link FieldRotation}&lt;{@link FieldDerivativeStructure}&gt;.
165      * <p>
166      * The rotation components must have time as their only derivation parameter and
167      * have consistent derivation orders.
168      * </p>
169      * @param r rotation with time-derivatives embedded within the coordinates
170      * @param <U> type of the derivative
171      * @since 9.2
172      */
173     public <U extends FieldDerivative<T, U>> FieldAngularCoordinates(final FieldRotation<U> r) {
174 
175         final T q0       = r.getQ0().getValue();
176         final T q1       = r.getQ1().getValue();
177         final T q2       = r.getQ2().getValue();
178         final T q3       = r.getQ3().getValue();
179 
180         rotation     = new FieldRotation<>(q0, q1, q2, q3, false);
181         if (r.getQ0().getOrder() >= 1) {
182             final T q0Dot    = r.getQ0().getPartialDerivative(1);
183             final T q1Dot    = r.getQ1().getPartialDerivative(1);
184             final T q2Dot    = r.getQ2().getPartialDerivative(1);
185             final T q3Dot    = r.getQ3().getPartialDerivative(1);
186             rotationRate =
187                     new FieldVector3D<>(q0.linearCombination(q1.negate(), q0Dot, q0,          q1Dot,
188                                                              q3,          q2Dot, q2.negate(), q3Dot).multiply(2),
189                                         q0.linearCombination(q2.negate(), q0Dot, q3.negate(), q1Dot,
190                                                              q0,          q2Dot, q1,          q3Dot).multiply(2),
191                                         q0.linearCombination(q3.negate(), q0Dot, q2,          q1Dot,
192                                                              q1.negate(), q2Dot, q0,          q3Dot).multiply(2));
193             if (r.getQ0().getOrder() >= 2) {
194                 final T q0DotDot = r.getQ0().getPartialDerivative(2);
195                 final T q1DotDot = r.getQ1().getPartialDerivative(2);
196                 final T q2DotDot = r.getQ2().getPartialDerivative(2);
197                 final T q3DotDot = r.getQ3().getPartialDerivative(2);
198                 rotationAcceleration =
199                         new FieldVector3D<>(q0.linearCombination(q1.negate(), q0DotDot, q0,          q1DotDot,
200                                                                  q3,          q2DotDot, q2.negate(), q3DotDot).multiply(2),
201                                             q0.linearCombination(q2.negate(), q0DotDot, q3.negate(), q1DotDot,
202                                                                  q0,          q2DotDot, q1,          q3DotDot).multiply(2),
203                                             q0.linearCombination(q3.negate(), q0DotDot, q2,          q1DotDot,
204                                                                  q1.negate(), q2DotDot, q0,          q3DotDot).multiply(2));
205             } else {
206                 rotationAcceleration = FieldVector3D.getZero(q0.getField());
207             }
208         } else {
209             rotationRate         = FieldVector3D.getZero(q0.getField());
210             rotationAcceleration = FieldVector3D.getZero(q0.getField());
211         }
212 
213     }
214 
215     /** Fixed orientation parallel with reference frame
216      * (identity rotation, zero rotation rate and acceleration).
217      * @param field field for the components
218      * @param <T> the type of the field elements
219      * @return a new fixed orientation parallel with reference frame
220      */
221     public static <T extends CalculusFieldElement<T>> FieldAngularCoordinates<T> getIdentity(final Field<T> field) {
222         return new FieldAngularCoordinates<>(field, AngularCoordinates.IDENTITY);
223     }
224 
225     /** Find a vector from two known cross products.
226      * <p>
227      * We want to find Ω such that: Ω ⨯ v₁ = c₁ and Ω ⨯ v₂ = c₂
228      * </p>
229      * <p>
230      * The first equation (Ω ⨯ v₁ = c₁) will always be fulfilled exactly,
231      * and the second one will be fulfilled if possible.
232      * </p>
233      * @param v1 vector forming the first known cross product
234      * @param c1 know vector for cross product Ω ⨯ v₁
235      * @param v2 vector forming the second known cross product
236      * @param c2 know vector for cross product Ω ⨯ v₂
237      * @param tolerance relative tolerance factor used to check singularities
238      * @param <T> the type of the field elements
239      * @return vector Ω such that: Ω ⨯ v₁ = c₁ and Ω ⨯ v₂ = c₂
240      * @exception MathIllegalArgumentException if vectors are inconsistent and
241      * no solution can be found
242      */
243     private static <T extends CalculusFieldElement<T>> FieldVector3D<T> inverseCrossProducts(final FieldVector3D<T> v1, final FieldVector3D<T> c1,
244                                                                                          final FieldVector3D<T> v2, final FieldVector3D<T> c2,
245                                                                                          final double tolerance)
246         throws MathIllegalArgumentException {
247 
248         final T v12 = v1.getNormSq();
249         final T v1n = v12.sqrt();
250         final T v22 = v2.getNormSq();
251         final T v2n = v22.sqrt();
252         final T threshold;
253         if (v1n.getReal() >= v2n.getReal()) {
254             threshold = v1n.multiply(tolerance);
255         }
256         else {
257             threshold = v2n.multiply(tolerance);
258         }
259         FieldVector3D<T> omega = null;
260 
261         try {
262             // create the over-determined linear system representing the two cross products
263             final FieldMatrix<T> m = MatrixUtils.createFieldMatrix(v12.getField(), 6, 3);
264             m.setEntry(0, 1, v1.getZ());
265             m.setEntry(0, 2, v1.getY().negate());
266             m.setEntry(1, 0, v1.getZ().negate());
267             m.setEntry(1, 2, v1.getX());
268             m.setEntry(2, 0, v1.getY());
269             m.setEntry(2, 1, v1.getX().negate());
270             m.setEntry(3, 1, v2.getZ());
271             m.setEntry(3, 2, v2.getY().negate());
272             m.setEntry(4, 0, v2.getZ().negate());
273             m.setEntry(4, 2, v2.getX());
274             m.setEntry(5, 0, v2.getY());
275             m.setEntry(5, 1, v2.getX().negate());
276 
277             final T[] kk = MathArrays.buildArray(v2n.getField(), 6);
278             kk[0] = c1.getX();
279             kk[1] = c1.getY();
280             kk[2] = c1.getZ();
281             kk[3] = c2.getX();
282             kk[4] = c2.getY();
283             kk[5] = c2.getZ();
284             final FieldVector<T> rhs = MatrixUtils.createFieldVector(kk);
285 
286             // find the best solution we can
287             final FieldDecompositionSolver<T> solver = new FieldQRDecomposition<>(m).getSolver();
288             final FieldVector<T> v = solver.solve(rhs);
289             omega = new FieldVector3D<>(v.getEntry(0), v.getEntry(1), v.getEntry(2));
290 
291         } catch (MathIllegalArgumentException miae) {
292             if (miae.getSpecifier() == LocalizedCoreFormats.SINGULAR_MATRIX) {
293 
294                 // handle some special cases for which we can compute a solution
295                 final T c12 = c1.getNormSq();
296                 final T c1n = c12.sqrt();
297                 final T c22 = c2.getNormSq();
298                 final T c2n = c22.sqrt();
299                 if (c1n.getReal() <= threshold.getReal() && c2n.getReal() <= threshold.getReal()) {
300                     // simple special case, velocities are cancelled
301                     return new FieldVector3D<>(v12.getField().getZero(), v12.getField().getZero(), v12.getField().getZero());
302                 } else if (v1n.getReal() <= threshold.getReal() && c1n.getReal() >= threshold.getReal()) {
303                     // this is inconsistent, if v₁ is zero, c₁ must be 0 too
304                     throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE,
305                                                            c1n.getReal(), 0, true);
306                 } else if (v2n.getReal() <= threshold.getReal() && c2n.getReal() >= threshold.getReal()) {
307                     // this is inconsistent, if v₂ is zero, c₂ must be 0 too
308                     throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE,
309                                                            c2n.getReal(), 0, true);
310                 } else if (v1.crossProduct(v1).getNorm().getReal() <= threshold.getReal() && v12.getReal() > threshold.getReal()) {
311                     // simple special case, v₂ is redundant with v₁, we just ignore it
312                     // use the simplest Ω: orthogonal to both v₁ and c₁
313                     omega = new FieldVector3D<>(v12.reciprocal(), v1.crossProduct(c1));
314                 } else {
315                     throw miae;
316                 }
317             } else {
318                 throw miae;
319             }
320         }
321         // check results
322         final T d1 = FieldVector3D.distance(omega.crossProduct(v1), c1);
323         if (d1.getReal() > threshold.getReal()) {
324             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE, 0, true);
325         }
326 
327         final T d2 = FieldVector3D.distance(omega.crossProduct(v2), c2);
328         if (d2.getReal() > threshold.getReal()) {
329             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE, 0, true);
330         }
331 
332         return omega;
333 
334     }
335 
336     /** Transform the instance to a {@link FieldRotation}&lt;{@link FieldDerivativeStructure}&gt;.
337      * <p>
338      * The {@link FieldDerivativeStructure} coordinates correspond to time-derivatives up
339      * to the user-specified order.
340      * </p>
341      * @param order derivation order for the vector components
342      * @return rotation with time-derivatives embedded within the coordinates
343           * @since 9.2
344      */
345     public FieldRotation<FieldDerivativeStructure<T>> toDerivativeStructureRotation(final int order) {
346 
347         // quaternion components
348         final T q0 = rotation.getQ0();
349         final T q1 = rotation.getQ1();
350         final T q2 = rotation.getQ2();
351         final T q3 = rotation.getQ3();
352 
353         // first time-derivatives of the quaternion
354         final T oX    = rotationRate.getX();
355         final T oY    = rotationRate.getY();
356         final T oZ    = rotationRate.getZ();
357         final T q0Dot = q0.linearCombination(q1.negate(), oX, q2.negate(), oY, q3.negate(), oZ).multiply(0.5);
358         final T q1Dot = q0.linearCombination(q0,          oX, q3.negate(), oY, q2,          oZ).multiply(0.5);
359         final T q2Dot = q0.linearCombination(q3,          oX, q0,          oY, q1.negate(), oZ).multiply(0.5);
360         final T q3Dot = q0.linearCombination(q2.negate(), oX, q1,          oY, q0,          oZ).multiply(0.5);
361 
362         // second time-derivatives of the quaternion
363         final T oXDot = rotationAcceleration.getX();
364         final T oYDot = rotationAcceleration.getY();
365         final T oZDot = rotationAcceleration.getZ();
366         final T q0DotDot = q0.linearCombination(array6(q1, q2,  q3, q1Dot, q2Dot,  q3Dot),
367                                                 array6(oXDot, oYDot, oZDot, oX, oY, oZ)).multiply(-0.5);
368         final T q1DotDot = q0.linearCombination(array6(q0, q2, q3.negate(), q0Dot, q2Dot, q3Dot.negate()),
369                                                 array6(oXDot, oZDot, oYDot, oX, oZ, oY)).multiply(0.5);
370         final T q2DotDot = q0.linearCombination(array6(q0, q3, q1.negate(), q0Dot, q3Dot, q1Dot.negate()),
371                                                 array6(oYDot, oXDot, oZDot, oY, oX, oZ)).multiply(0.5);
372         final T q3DotDot = q0.linearCombination(array6(q0, q1, q2.negate(), q0Dot, q1Dot, q2Dot.negate()),
373                                                 array6(oZDot, oYDot, oXDot, oZ, oY, oX)).multiply(0.5);
374 
375         final FDSFactory<T> factory;
376         final FieldDerivativeStructure<T> q0DS;
377         final FieldDerivativeStructure<T> q1DS;
378         final FieldDerivativeStructure<T> q2DS;
379         final FieldDerivativeStructure<T> q3DS;
380         switch (order) {
381             case 0 :
382                 factory = new FDSFactory<>(q0.getField(), 1, order);
383                 q0DS = factory.build(q0);
384                 q1DS = factory.build(q1);
385                 q2DS = factory.build(q2);
386                 q3DS = factory.build(q3);
387                 break;
388             case 1 :
389                 factory = new FDSFactory<>(q0.getField(), 1, order);
390                 q0DS = factory.build(q0, q0Dot);
391                 q1DS = factory.build(q1, q1Dot);
392                 q2DS = factory.build(q2, q2Dot);
393                 q3DS = factory.build(q3, q3Dot);
394                 break;
395             case 2 :
396                 factory = new FDSFactory<>(q0.getField(), 1, order);
397                 q0DS = factory.build(q0, q0Dot, q0DotDot);
398                 q1DS = factory.build(q1, q1Dot, q1DotDot);
399                 q2DS = factory.build(q2, q2Dot, q2DotDot);
400                 q3DS = factory.build(q3, q3Dot, q3DotDot);
401                 break;
402             default :
403                 throw new OrekitException(OrekitMessages.OUT_OF_RANGE_DERIVATION_ORDER, order);
404         }
405 
406         return new FieldRotation<>(q0DS, q1DS, q2DS, q3DS, false);
407 
408     }
409 
410     /** Transform the instance to a {@link FieldRotation}&lt;{@link UnivariateDerivative1}&gt;.
411      * <p>
412      * The {@link UnivariateDerivative1} coordinates correspond to time-derivatives up
413      * to the order 1.
414      * </p>
415      * @return rotation with time-derivatives embedded within the coordinates
416      */
417     public FieldRotation<FieldUnivariateDerivative1<T>> toUnivariateDerivative1Rotation() {
418 
419         // quaternion components
420         final T q0 = rotation.getQ0();
421         final T q1 = rotation.getQ1();
422         final T q2 = rotation.getQ2();
423         final T q3 = rotation.getQ3();
424 
425         // first time-derivatives of the quaternion
426         final T oX    = rotationRate.getX();
427         final T oY    = rotationRate.getY();
428         final T oZ    = rotationRate.getZ();
429         final T q0Dot = q0.linearCombination(q1.negate(), oX, q2.negate(), oY, q3.negate(), oZ).multiply(0.5);
430         final T q1Dot = q0.linearCombination(q0,          oX, q3.negate(), oY, q2,          oZ).multiply(0.5);
431         final T q2Dot = q0.linearCombination(q3,          oX, q0,          oY, q1.negate(), oZ).multiply(0.5);
432         final T q3Dot = q0.linearCombination(q2.negate(), oX, q1,          oY, q0,          oZ).multiply(0.5);
433 
434         final FieldUnivariateDerivative1<T> q0UD = new FieldUnivariateDerivative1<>(q0, q0Dot);
435         final FieldUnivariateDerivative1<T> q1UD = new FieldUnivariateDerivative1<>(q1, q1Dot);
436         final FieldUnivariateDerivative1<T> q2UD = new FieldUnivariateDerivative1<>(q2, q2Dot);
437         final FieldUnivariateDerivative1<T> q3UD = new FieldUnivariateDerivative1<>(q3, q3Dot);
438 
439         return new FieldRotation<>(q0UD, q1UD, q2UD, q3UD, false);
440 
441     }
442 
443     /** Transform the instance to a {@link FieldRotation}&lt;{@link UnivariateDerivative2}&gt;.
444      * <p>
445      * The {@link UnivariateDerivative2} coordinates correspond to time-derivatives up
446      * to the order 2.
447      * </p>
448      * @return rotation with time-derivatives embedded within the coordinates
449      */
450     public FieldRotation<FieldUnivariateDerivative2<T>> toUnivariateDerivative2Rotation() {
451 
452         // quaternion components
453         final T q0 = rotation.getQ0();
454         final T q1 = rotation.getQ1();
455         final T q2 = rotation.getQ2();
456         final T q3 = rotation.getQ3();
457 
458         // first time-derivatives of the quaternion
459         final T oX    = rotationRate.getX();
460         final T oY    = rotationRate.getY();
461         final T oZ    = rotationRate.getZ();
462         final T q0Dot = q0.linearCombination(q1.negate(), oX, q2.negate(), oY, q3.negate(), oZ).multiply(0.5);
463         final T q1Dot = q0.linearCombination(q0,          oX, q3.negate(), oY, q2,          oZ).multiply(0.5);
464         final T q2Dot = q0.linearCombination(q3,          oX, q0,          oY, q1.negate(), oZ).multiply(0.5);
465         final T q3Dot = q0.linearCombination(q2.negate(), oX, q1,          oY, q0,          oZ).multiply(0.5);
466 
467         // second time-derivatives of the quaternion
468         final T oXDot = rotationAcceleration.getX();
469         final T oYDot = rotationAcceleration.getY();
470         final T oZDot = rotationAcceleration.getZ();
471         final T q0DotDot = q0.linearCombination(array6(q1, q2,  q3, q1Dot, q2Dot,  q3Dot),
472                                                 array6(oXDot, oYDot, oZDot, oX, oY, oZ)).multiply(-0.5);
473         final T q1DotDot = q0.linearCombination(array6(q0, q2, q3.negate(), q0Dot, q2Dot, q3Dot.negate()),
474                                                 array6(oXDot, oZDot, oYDot, oX, oZ, oY)).multiply(0.5);
475         final T q2DotDot = q0.linearCombination(array6(q0, q3, q1.negate(), q0Dot, q3Dot, q1Dot.negate()),
476                                                 array6(oYDot, oXDot, oZDot, oY, oX, oZ)).multiply(0.5);
477         final T q3DotDot = q0.linearCombination(array6(q0, q1, q2.negate(), q0Dot, q1Dot, q2Dot.negate()),
478                                                 array6(oZDot, oYDot, oXDot, oZ, oY, oX)).multiply(0.5);
479 
480         final FieldUnivariateDerivative2<T> q0UD = new FieldUnivariateDerivative2<>(q0, q0Dot, q0DotDot);
481         final FieldUnivariateDerivative2<T> q1UD = new FieldUnivariateDerivative2<>(q1, q1Dot, q1DotDot);
482         final FieldUnivariateDerivative2<T> q2UD = new FieldUnivariateDerivative2<>(q2, q2Dot, q2DotDot);
483         final FieldUnivariateDerivative2<T> q3UD = new FieldUnivariateDerivative2<>(q3, q3Dot, q3DotDot);
484 
485         return new FieldRotation<>(q0UD, q1UD, q2UD, q3UD, false);
486 
487     }
488 
489     /** Build an arry of 6 elements.
490      * @param e1 first element
491      * @param e2 second element
492      * @param e3 third element
493      * @param e4 fourth element
494      * @param e5 fifth element
495      * @param e6 sixth element
496      * @return a new array
497      * @since 9.2
498      */
499     private T[] array6(final T e1, final T e2, final T e3, final T e4, final T e5, final T e6) {
500         final T[] array = MathArrays.buildArray(e1.getField(), 6);
501         array[0] = e1;
502         array[1] = e2;
503         array[2] = e3;
504         array[3] = e4;
505         array[4] = e5;
506         array[5] = e6;
507         return array;
508     }
509 
510     /** Estimate rotation rate between two orientations.
511      * <p>Estimation is based on a simple fixed rate rotation
512      * during the time interval between the two orientations.</p>
513      * @param start start orientation
514      * @param end end orientation
515      * @param dt time elapsed between the dates of the two orientations
516      * @param <T> the type of the field elements
517      * @return rotation rate allowing to go from start to end orientations
518      */
519     public static <T extends CalculusFieldElement<T>>
520         FieldVector3D<T> estimateRate(final FieldRotation<T> start,
521                                       final FieldRotation<T> end,
522                                       final double dt) {
523         return estimateRate(start, end, start.getQ0().getField().getZero().newInstance(dt));
524     }
525 
526     /** Estimate rotation rate between two orientations.
527      * <p>Estimation is based on a simple fixed rate rotation
528      * during the time interval between the two orientations.</p>
529      * @param start start orientation
530      * @param end end orientation
531      * @param dt time elapsed between the dates of the two orientations
532      * @param <T> the type of the field elements
533      * @return rotation rate allowing to go from start to end orientations
534      */
535     public static <T extends CalculusFieldElement<T>>
536         FieldVector3D<T> estimateRate(final FieldRotation<T> start,
537                                       final FieldRotation<T> end,
538                                       final T dt) {
539         final FieldRotation<T> evolution = start.compose(end.revert(), RotationConvention.VECTOR_OPERATOR);
540         return new FieldVector3D<>(evolution.getAngle().divide(dt),
541                                    evolution.getAxis(RotationConvention.VECTOR_OPERATOR));
542     }
543 
544     /**
545      * Revert a rotation / rotation rate / rotation acceleration triplet.
546      *
547      * <p> Build a triplet which reverse the effect of another triplet.
548      *
549      * @return a new triplet whose effect is the reverse of the effect
550      * of the instance
551      */
552     public FieldAngularCoordinates<T> revert() {
553         return new FieldAngularCoordinates<>(rotation.revert(),
554                                              rotation.applyInverseTo(rotationRate.negate()),
555                                              rotation.applyInverseTo(rotationAcceleration.negate()));
556     }
557 
558     /** Get a time-shifted rotation. Same as {@link #shiftedBy(double)} except
559      * only the shifted rotation is computed.
560      * <p>
561      * The state can be slightly shifted to close dates. This shift is based on
562      * an approximate solution of the fixed acceleration motion. It is <em>not</em>
563      * intended as a replacement for proper attitude propagation but should be
564      * sufficient for either small time shifts or coarse accuracy.
565      * </p>
566      * @param dt time shift in seconds
567      * @return a new state, shifted with respect to the instance (which is immutable)
568      * @see  #shiftedBy(CalculusFieldElement)
569      * @since 11.2
570      */
571     public FieldRotation<T> rotationShiftedBy(final T dt) {
572 
573         // the shiftedBy method is based on a local approximation.
574         // It considers separately the contribution of the constant
575         // rotation, the linear contribution or the rate and the
576         // quadratic contribution of the acceleration. The rate
577         // and acceleration contributions are small rotations as long
578         // as the time shift is small, which is the crux of the algorithm.
579         // Small rotations are almost commutative, so we append these small
580         // contributions one after the other, as if they really occurred
581         // successively, despite this is not what really happens.
582 
583         // compute the linear contribution first, ignoring acceleration
584         // BEWARE: there is really a minus sign here, because if
585         // the target frame rotates in one direction, the vectors in the origin
586         // frame seem to rotate in the opposite direction
587         final T rate = rotationRate.getNorm();
588         final FieldRotation<T> rateContribution = (rate.getReal() == 0.0) ?
589                 FieldRotation.getIdentity(dt.getField()) :
590                 new FieldRotation<>(rotationRate, rate.multiply(dt), RotationConvention.FRAME_TRANSFORM);
591 
592         // append rotation and rate contribution
593         final FieldRotation<T> linearPart =
594                 rateContribution.compose(rotation, RotationConvention.VECTOR_OPERATOR);
595 
596         final T acc  = rotationAcceleration.getNorm();
597         if (acc.getReal() == 0.0) {
598             // no acceleration, the linear part is sufficient
599             return linearPart;
600         }
601 
602         // compute the quadratic contribution, ignoring initial rotation and rotation rate
603         // BEWARE: there is really a minus sign here, because if
604         // the target frame rotates in one direction, the vectors in the origin
605         // frame seem to rotate in the opposite direction
606         final FieldRotation<T> quadraticContribution =
607                 new FieldRotation<>(rotationAcceleration,
608                         acc.multiply(dt.square()).multiply(0.5),
609                         RotationConvention.FRAME_TRANSFORM);
610 
611         // the quadratic contribution is a small rotation:
612         // its initial angle and angular rate are both zero.
613         // small rotations are almost commutative, so we append the small
614         // quadratic part after the linear part as a simple offset
615         return quadraticContribution
616                 .compose(linearPart, RotationConvention.VECTOR_OPERATOR);
617 
618     }
619 
620     /** Get a time-shifted state.
621      * <p>
622      * The state can be slightly shifted to close dates. This shift is based on
623      * a simple quadratic model. It is <em>not</em> intended as a replacement for
624      * proper attitude propagation but should be sufficient for either small
625      * time shifts or coarse accuracy.
626      * </p>
627      * @param dt time shift in seconds
628      * @return a new state, shifted with respect to the instance (which is immutable)
629      */
630     @Override
631     public FieldAngularCoordinates<T> shiftedBy(final double dt) {
632         return shiftedBy(rotation.getQ0().getField().getZero().newInstance(dt));
633     }
634 
635     /** Get a time-shifted state.
636      * <p>
637      * The state can be slightly shifted to close dates. This shift is based on
638      * a simple quadratic model. It is <em>not</em> intended as a replacement for
639      * proper attitude propagation but should be sufficient for either small
640      * time shifts or coarse accuracy.
641      * </p>
642      * @param dt time shift in seconds
643      * @return a new state, shifted with respect to the instance (which is immutable)
644      */
645     @Override
646     public FieldAngularCoordinates<T> shiftedBy(final T dt) {
647 
648         // the shiftedBy method is based on a local approximation.
649         // It considers separately the contribution of the constant
650         // rotation, the linear contribution or the rate and the
651         // quadratic contribution of the acceleration. The rate
652         // and acceleration contributions are small rotations as long
653         // as the time shift is small, which is the crux of the algorithm.
654         // Small rotations are almost commutative, so we append these small
655         // contributions one after the other, as if they really occurred
656         // successively, despite this is not what really happens.
657 
658         // compute the linear contribution first, ignoring acceleration
659         // BEWARE: there is really a minus sign here, because if
660         // the target frame rotates in one direction, the vectors in the origin
661         // frame seem to rotate in the opposite direction
662         final T rate = rotationRate.getNorm();
663         final T zero = rate.getField().getZero();
664         final T one  = rate.getField().getOne();
665         final FieldRotation<T> rateContribution = (rate.getReal() == 0.0) ?
666                                                   new FieldRotation<>(one, zero, zero, zero, false) :
667                                                   new FieldRotation<>(rotationRate,
668                                                                       rate.multiply(dt),
669                                                                       RotationConvention.FRAME_TRANSFORM);
670 
671         // append rotation and rate contribution
672         final FieldAngularCoordinates<T> linearPart =
673                 new FieldAngularCoordinates<>(rateContribution.compose(rotation, RotationConvention.VECTOR_OPERATOR),
674                                               rotationRate);
675 
676         final T acc  = rotationAcceleration.getNorm();
677         if (acc.getReal() == 0.0) {
678             // no acceleration, the linear part is sufficient
679             return linearPart;
680         }
681 
682         // compute the quadratic contribution, ignoring initial rotation and rotation rate
683         // BEWARE: there is really a minus sign here, because if
684         // the target frame rotates in one direction, the vectors in the origin
685         // frame seem to rotate in the opposite direction
686         final FieldAngularCoordinates<T> quadraticContribution =
687                 new FieldAngularCoordinates<>(new FieldRotation<>(rotationAcceleration,
688                                                                   acc.multiply(dt.square().multiply(0.5)),
689                                                                   RotationConvention.FRAME_TRANSFORM),
690                                               new FieldVector3D<>(dt, rotationAcceleration),
691                                               rotationAcceleration);
692 
693         // the quadratic contribution is a small rotation:
694         // its initial angle and angular rate are both zero.
695         // small rotations are almost commutative, so we append the small
696         // quadratic part after the linear part as a simple offset
697         return quadraticContribution.addOffset(linearPart);
698 
699     }
700 
701     /** Get the rotation.
702      * @return the rotation.
703      */
704     public FieldRotation<T> getRotation() {
705         return rotation;
706     }
707 
708     /** Get the rotation rate.
709      * @return the rotation rate vector (rad/s).
710      */
711     public FieldVector3D<T> getRotationRate() {
712         return rotationRate;
713     }
714 
715     /** Get the rotation acceleration.
716      * @return the rotation acceleration vector dΩ/dt (rad/s²).
717      */
718     public FieldVector3D<T> getRotationAcceleration() {
719         return rotationAcceleration;
720     }
721 
722     /** Add an offset from the instance.
723      * <p>
724      * We consider here that the offset rotation is applied first and the
725      * instance is applied afterward. Note that angular coordinates do <em>not</em>
726      * commute under this operation, i.e. {@code a.addOffset(b)} and {@code
727      * b.addOffset(a)} lead to <em>different</em> results in most cases.
728      * </p>
729      * <p>
730      * The two methods {@link #addOffset(FieldAngularCoordinates) addOffset} and
731      * {@link #subtractOffset(FieldAngularCoordinates) subtractOffset} are designed
732      * so that round trip applications are possible. This means that both {@code
733      * ac1.subtractOffset(ac2).addOffset(ac2)} and {@code
734      * ac1.addOffset(ac2).subtractOffset(ac2)} return angular coordinates equal to ac1.
735      * </p>
736      * @param offset offset to subtract
737      * @return new instance, with offset subtracted
738      * @see #subtractOffset(FieldAngularCoordinates)
739      */
740     public FieldAngularCoordinates<T> addOffset(final FieldAngularCoordinates<T> offset) {
741         final FieldVector3D<T> rOmega    = rotation.applyTo(offset.rotationRate);
742         final FieldVector3D<T> rOmegaDot = rotation.applyTo(offset.rotationAcceleration);
743         return new FieldAngularCoordinates<>(rotation.compose(offset.rotation, RotationConvention.VECTOR_OPERATOR),
744                                              rotationRate.add(rOmega),
745                                              new FieldVector3D<>( 1.0, rotationAcceleration,
746                                                                   1.0, rOmegaDot,
747                                                                  -1.0, FieldVector3D.crossProduct(rotationRate, rOmega)));
748     }
749 
750     /** Subtract an offset from the instance.
751      * <p>
752      * We consider here that the offset Rotation is applied first and the
753      * instance is applied afterward. Note that angular coordinates do <em>not</em>
754      * commute under this operation, i.e. {@code a.subtractOffset(b)} and {@code
755      * b.subtractOffset(a)} lead to <em>different</em> results in most cases.
756      * </p>
757      * <p>
758      * The two methods {@link #addOffset(FieldAngularCoordinates) addOffset} and
759      * {@link #subtractOffset(FieldAngularCoordinates) subtractOffset} are designed
760      * so that round trip applications are possible. This means that both {@code
761      * ac1.subtractOffset(ac2).addOffset(ac2)} and {@code
762      * ac1.addOffset(ac2).subtractOffset(ac2)} return angular coordinates equal to ac1.
763      * </p>
764      * @param offset offset to subtract
765      * @return new instance, with offset subtracted
766      * @see #addOffset(FieldAngularCoordinates)
767      */
768     public FieldAngularCoordinates<T> subtractOffset(final FieldAngularCoordinates<T> offset) {
769         return addOffset(offset.revert());
770     }
771 
772     /** Convert to a regular angular coordinates.
773      * @return a regular angular coordinates
774      */
775     public AngularCoordinates toAngularCoordinates() {
776         return new AngularCoordinates(rotation.toRotation(), rotationRate.toVector3D(),
777                                       rotationAcceleration.toVector3D());
778     }
779 
780     /** Apply the rotation to a pv coordinates.
781      * @param pv vector to apply the rotation to
782      * @return a new pv coordinates which is the image of pv by the rotation
783      */
784     public FieldPVCoordinates<T> applyTo(final PVCoordinates pv) {
785 
786         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
787         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
788         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
789         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
790         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
791         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
792         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
793                                                                   -2, crossV,
794                                                                   -1, crossCrossP,
795                                                                   -1, crossDotP);
796 
797         return new FieldPVCoordinates<>(transformedP, transformedV, transformedA);
798 
799     }
800 
801     /** Apply the rotation to a pv coordinates.
802      * @param pv vector to apply the rotation to
803      * @return a new pv coordinates which is the image of pv by the rotation
804      */
805     public TimeStampedFieldPVCoordinates<T> applyTo(final TimeStampedPVCoordinates pv) {
806 
807         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
808         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
809         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
810         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
811         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
812         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
813         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
814                                                                   -2, crossV,
815                                                                   -1, crossCrossP,
816                                                                   -1, crossDotP);
817 
818         return new TimeStampedFieldPVCoordinates<>(pv.getDate(), transformedP, transformedV, transformedA);
819 
820     }
821 
822     /** Apply the rotation to a pv coordinates.
823      * @param pv vector to apply the rotation to
824      * @return a new pv coordinates which is the image of pv by the rotation
825      * @since 9.0
826      */
827     public FieldPVCoordinates<T> applyTo(final FieldPVCoordinates<T> pv) {
828 
829         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
830         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
831         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
832         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
833         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
834         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
835         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
836                                                                   -2, crossV,
837                                                                   -1, crossCrossP,
838                                                                   -1, crossDotP);
839 
840         return new FieldPVCoordinates<>(transformedP, transformedV, transformedA);
841 
842     }
843 
844     /** Apply the rotation to a pv coordinates.
845      * @param pv vector to apply the rotation to
846      * @return a new pv coordinates which is the image of pv by the rotation
847      * @since 9.0
848      */
849     public TimeStampedFieldPVCoordinates<T> applyTo(final TimeStampedFieldPVCoordinates<T> pv) {
850 
851         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
852         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
853         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
854         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
855         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
856         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
857         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
858                                                                   -2, crossV,
859                                                                   -1, crossCrossP,
860                                                                   -1, crossDotP);
861 
862         return new TimeStampedFieldPVCoordinates<>(pv.getDate(), transformedP, transformedV, transformedA);
863 
864     }
865 
866     /** Convert rotation, rate and acceleration to modified Rodrigues vector and derivatives.
867      * <p>
868      * The modified Rodrigues vector is tan(θ/4) u where θ and u are the
869      * rotation angle and axis respectively.
870      * </p>
871      * @param sign multiplicative sign for quaternion components
872      * @return modified Rodrigues vector and derivatives (vector on row 0, first derivative
873      * on row 1, second derivative on row 2)
874      * @see #createFromModifiedRodrigues(CalculusFieldElement[][])
875      * @since 9.0
876      */
877     public T[][] getModifiedRodrigues(final double sign) {
878 
879         final T q0    = getRotation().getQ0().multiply(sign);
880         final T q1    = getRotation().getQ1().multiply(sign);
881         final T q2    = getRotation().getQ2().multiply(sign);
882         final T q3    = getRotation().getQ3().multiply(sign);
883         final T oX    = getRotationRate().getX();
884         final T oY    = getRotationRate().getY();
885         final T oZ    = getRotationRate().getZ();
886         final T oXDot = getRotationAcceleration().getX();
887         final T oYDot = getRotationAcceleration().getY();
888         final T oZDot = getRotationAcceleration().getZ();
889 
890         // first time-derivatives of the quaternion
891         final T q0Dot = q0.linearCombination(q1.negate(), oX, q2.negate(), oY, q3.negate(), oZ).multiply(0.5);
892         final T q1Dot = q0.linearCombination( q0, oX, q3.negate(), oY,  q2, oZ).multiply(0.5);
893         final T q2Dot = q0.linearCombination( q3, oX,  q0, oY, q1.negate(), oZ).multiply(0.5);
894         final T q3Dot = q0.linearCombination(q2.negate(), oX,  q1, oY,  q0, oZ).multiply(0.5);
895 
896         // second time-derivatives of the quaternion
897         final T q0DotDot = linearCombination(q1, oXDot, q2, oYDot, q3, oZDot,
898                                              q1Dot, oX, q2Dot, oY, q3Dot, oZ).
899                            multiply(-0.5);
900         final T q1DotDot = linearCombination(q0, oXDot, q2, oZDot, q3.negate(), oYDot,
901                                              q0Dot, oX, q2Dot, oZ, q3Dot.negate(), oY).
902                            multiply(0.5);
903         final T q2DotDot = linearCombination(q0, oYDot, q3, oXDot, q1.negate(), oZDot,
904                                              q0Dot, oY, q3Dot, oX, q1Dot.negate(), oZ).
905                            multiply(0.5);
906         final T q3DotDot = linearCombination(q0, oZDot, q1, oYDot, q2.negate(), oXDot,
907                                              q0Dot, oZ, q1Dot, oY, q2Dot.negate(), oX).
908                            multiply(0.5);
909 
910         // the modified Rodrigues is tan(θ/4) u where θ and u are the rotation angle and axis respectively
911         // this can be rewritten using quaternion components:
912         //      r (q₁ / (1+q₀), q₂ / (1+q₀), q₃ / (1+q₀))
913         // applying the derivation chain rule to previous expression gives rDot and rDotDot
914         final T inv          = q0.add(1).reciprocal();
915         final T mTwoInvQ0Dot = inv.multiply(q0Dot).multiply(-2);
916 
917         final T r1       = inv.multiply(q1);
918         final T r2       = inv.multiply(q2);
919         final T r3       = inv.multiply(q3);
920 
921         final T mInvR1   = inv.multiply(r1).negate();
922         final T mInvR2   = inv.multiply(r2).negate();
923         final T mInvR3   = inv.multiply(r3).negate();
924 
925         final T r1Dot    = q0.linearCombination(inv, q1Dot, mInvR1, q0Dot);
926         final T r2Dot    = q0.linearCombination(inv, q2Dot, mInvR2, q0Dot);
927         final T r3Dot    = q0.linearCombination(inv, q3Dot, mInvR3, q0Dot);
928 
929         final T r1DotDot = q0.linearCombination(inv, q1DotDot, mTwoInvQ0Dot, r1Dot, mInvR1, q0DotDot);
930         final T r2DotDot = q0.linearCombination(inv, q2DotDot, mTwoInvQ0Dot, r2Dot, mInvR2, q0DotDot);
931         final T r3DotDot = q0.linearCombination(inv, q3DotDot, mTwoInvQ0Dot, r3Dot, mInvR3, q0DotDot);
932 
933         final T[][] rodrigues = MathArrays.buildArray(q0.getField(), 3, 3);
934         rodrigues[0][0] = r1;
935         rodrigues[0][1] = r2;
936         rodrigues[0][2] = r3;
937         rodrigues[1][0] = r1Dot;
938         rodrigues[1][1] = r2Dot;
939         rodrigues[1][2] = r3Dot;
940         rodrigues[2][0] = r1DotDot;
941         rodrigues[2][1] = r2DotDot;
942         rodrigues[2][2] = r3DotDot;
943         return rodrigues;
944 
945     }
946 
947     /**
948      * Compute a linear combination.
949      * @param a1 first factor of the first term
950      * @param b1 second factor of the first term
951      * @param a2 first factor of the second term
952      * @param b2 second factor of the second term
953      * @param a3 first factor of the third term
954      * @param b3 second factor of the third term
955      * @param a4 first factor of the fourth term
956      * @param b4 second factor of the fourth term
957      * @param a5 first factor of the fifth term
958      * @param b5 second factor of the fifth term
959      * @param a6 first factor of the sixth term
960      * @param b6 second factor of the sicth term
961      * @return a<sub>1</sub>&times;b<sub>1</sub> + a<sub>2</sub>&times;b<sub>2</sub> +
962      * a<sub>3</sub>&times;b<sub>3</sub> + a<sub>4</sub>&times;b<sub>4</sub> +
963      * a<sub>5</sub>&times;b<sub>5</sub> + a<sub>6</sub>&times;b<sub>6</sub>
964      */
965     private T linearCombination(final T a1, final T b1, final T a2, final T b2, final T a3, final T b3,
966                                 final T a4, final T b4, final T a5, final T b5, final T a6, final T b6) {
967 
968         final T[] a = MathArrays.buildArray(a1.getField(), 6);
969         a[0] = a1;
970         a[1] = a2;
971         a[2] = a3;
972         a[3] = a4;
973         a[4] = a5;
974         a[5] = a6;
975 
976         final T[] b = MathArrays.buildArray(b1.getField(), 6);
977         b[0] = b1;
978         b[1] = b2;
979         b[2] = b3;
980         b[3] = b4;
981         b[4] = b5;
982         b[5] = b6;
983 
984         return a1.linearCombination(a, b);
985 
986     }
987 
988     /** Convert a modified Rodrigues vector and derivatives to angular coordinates.
989      * @param r modified Rodrigues vector (with first and second times derivatives)
990      * @param <T> the type of the field elements
991      * @return angular coordinates
992      * @see #getModifiedRodrigues(double)
993      * @since 9.0
994      */
995     public static <T extends CalculusFieldElement<T>>  FieldAngularCoordinates<T> createFromModifiedRodrigues(final T[][] r) {
996 
997         // rotation
998         final T rSquared = r[0][0].square().add(r[0][1].square()).add(r[0][2].square());
999         final T oPQ0     = rSquared.add(1).reciprocal().multiply(2);
1000         final T q0       = oPQ0.subtract(1);
1001         final T q1       = oPQ0.multiply(r[0][0]);
1002         final T q2       = oPQ0.multiply(r[0][1]);
1003         final T q3       = oPQ0.multiply(r[0][2]);
1004 
1005         // rotation rate
1006         final T oPQ02    = oPQ0.multiply(oPQ0);
1007         final T q0Dot    = oPQ02.multiply(q0.linearCombination(r[0][0], r[1][0], r[0][1], r[1][1],  r[0][2], r[1][2])).negate();
1008         final T q1Dot    = oPQ0.multiply(r[1][0]).add(r[0][0].multiply(q0Dot));
1009         final T q2Dot    = oPQ0.multiply(r[1][1]).add(r[0][1].multiply(q0Dot));
1010         final T q3Dot    = oPQ0.multiply(r[1][2]).add(r[0][2].multiply(q0Dot));
1011         final T oX       = q0.linearCombination(q1.negate(), q0Dot,  q0, q1Dot,  q3, q2Dot, q2.negate(), q3Dot).multiply(2);
1012         final T oY       = q0.linearCombination(q2.negate(), q0Dot, q3.negate(), q1Dot,  q0, q2Dot,  q1, q3Dot).multiply(2);
1013         final T oZ       = q0.linearCombination(q3.negate(), q0Dot,  q2, q1Dot, q1.negate(), q2Dot,  q0, q3Dot).multiply(2);
1014 
1015         // rotation acceleration
1016         final T q0DotDot = q0.subtract(1).negate().divide(oPQ0).multiply(q0Dot.square()).
1017                            subtract(oPQ02.multiply(q0.linearCombination(r[0][0], r[2][0], r[0][1], r[2][1], r[0][2], r[2][2]))).
1018                            subtract(q1Dot.square().add(q2Dot.square()).add(q3Dot.square()));
1019         final T q1DotDot = q0.linearCombination(oPQ0, r[2][0], r[1][0].add(r[1][0]), q0Dot, r[0][0], q0DotDot);
1020         final T q2DotDot = q0.linearCombination(oPQ0, r[2][1], r[1][1].add(r[1][1]), q0Dot, r[0][1], q0DotDot);
1021         final T q3DotDot = q0.linearCombination(oPQ0, r[2][2], r[1][2].add(r[1][2]), q0Dot, r[0][2], q0DotDot);
1022         final T oXDot    = q0.linearCombination(q1.negate(), q0DotDot,  q0, q1DotDot,  q3, q2DotDot, q2.negate(), q3DotDot).multiply(2);
1023         final T oYDot    = q0.linearCombination(q2.negate(), q0DotDot, q3.negate(), q1DotDot,  q0, q2DotDot,  q1, q3DotDot).multiply(2);
1024         final T oZDot    = q0.linearCombination(q3.negate(), q0DotDot,  q2, q1DotDot, q1.negate(), q2DotDot,  q0, q3DotDot).multiply(2);
1025 
1026         return new FieldAngularCoordinates<>(new FieldRotation<>(q0, q1, q2, q3, false),
1027                                              new FieldVector3D<>(oX, oY, oZ),
1028                                              new FieldVector3D<>(oXDot, oYDot, oZDot));
1029 
1030     }
1031 
1032 }