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