FieldAngularCoordinates.java

  1. /* Copyright 2002-2025 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. import org.hipparchus.Field;
  19. import org.hipparchus.CalculusFieldElement;
  20. import org.hipparchus.analysis.differentiation.FDSFactory;
  21. import org.hipparchus.analysis.differentiation.FieldDerivative;
  22. import org.hipparchus.analysis.differentiation.FieldDerivativeStructure;
  23. import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative1;
  24. import org.hipparchus.analysis.differentiation.FieldUnivariateDerivative2;
  25. import org.hipparchus.analysis.differentiation.UnivariateDerivative1;
  26. import org.hipparchus.analysis.differentiation.UnivariateDerivative2;
  27. import org.hipparchus.exception.LocalizedCoreFormats;
  28. import org.hipparchus.exception.MathIllegalArgumentException;
  29. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  30. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  31. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  32. import org.hipparchus.linear.FieldDecompositionSolver;
  33. import org.hipparchus.linear.FieldMatrix;
  34. import org.hipparchus.linear.FieldQRDecomposition;
  35. import org.hipparchus.linear.FieldVector;
  36. import org.hipparchus.linear.MatrixUtils;
  37. import org.hipparchus.util.MathArrays;
  38. import org.orekit.errors.OrekitException;
  39. import org.orekit.errors.OrekitMessages;
  40. import org.orekit.time.FieldTimeShiftable;

  41. /** Simple container for rotation / rotation rate pairs, using {@link
  42.  * CalculusFieldElement}.
  43.  * <p>
  44.  * The state can be slightly shifted to close dates. This shift is based on
  45.  * a simple quadratic model. It is <em>not</em> intended as a replacement for
  46.  * proper attitude propagation but should be sufficient for either small
  47.  * time shifts or coarse accuracy.
  48.  * </p>
  49.  * <p>
  50.  * This class is the angular counterpart to {@link FieldPVCoordinates}.
  51.  * </p>
  52.  * <p>Instances of this class are guaranteed to be immutable.</p>
  53.  * @param <T> the type of the field elements
  54.  * @author Luc Maisonobe
  55.  * @since 6.0
  56.  * @see AngularCoordinates
  57.  */
  58. public class FieldAngularCoordinates<T extends CalculusFieldElement<T>>
  59.         implements FieldTimeShiftable<FieldAngularCoordinates<T>, T> {

  60.     /** rotation. */
  61.     private final FieldRotation<T> rotation;

  62.     /** rotation rate. */
  63.     private final FieldVector3D<T> rotationRate;

  64.     /** rotation acceleration. */
  65.     private final FieldVector3D<T> rotationAcceleration;

  66.     /** Builds a rotation/rotation rate pair.
  67.      * @param rotation rotation
  68.      * @param rotationRate rotation rate Ω (rad/s)
  69.      */
  70.     public FieldAngularCoordinates(final FieldRotation<T> rotation,
  71.                                    final FieldVector3D<T> rotationRate) {
  72.         this(rotation, rotationRate, FieldVector3D.getZero(rotation.getQ0().getField()));
  73.     }

  74.     /** Builds a rotation / rotation rate / rotation acceleration triplet.
  75.      * @param rotation i.e. the orientation of the vehicle
  76.      * @param rotationRate rotation rate Ω, i.e. the spin vector (rad/s)
  77.      * @param rotationAcceleration angular acceleration vector dΩ/dt (rad/s²)
  78.      */
  79.     public FieldAngularCoordinates(final FieldRotation<T> rotation,
  80.                                    final FieldVector3D<T> rotationRate,
  81.                                    final FieldVector3D<T> rotationAcceleration) {
  82.         this.rotation             = rotation;
  83.         this.rotationRate         = rotationRate;
  84.         this.rotationAcceleration = rotationAcceleration;
  85.     }

  86.     /** Build the rotation that transforms a pair of pv coordinates into another one.

  87.      * <p><em>WARNING</em>! This method requires much more stringent assumptions on
  88.      * its parameters than the similar {@link FieldRotation#FieldRotation(FieldVector3D, FieldVector3D,
  89.      * FieldVector3D, FieldVector3D) constructor} from the {@link FieldRotation FieldRotation} class.
  90.      * As far as the FieldRotation constructor is concerned, the {@code v₂} vector from
  91.      * the second pair can be slightly misaligned. The FieldRotation constructor will
  92.      * compensate for this misalignment and create a rotation that ensure {@code
  93.      * v₁ = r(u₁)} and {@code v₂ ∈ plane (r(u₁), r(u₂))}. <em>THIS IS NOT
  94.      * TRUE ANYMORE IN THIS CLASS</em>! As derivatives are involved and must be
  95.      * preserved, this constructor works <em>only</em> if the two pairs are fully
  96.      * consistent, i.e. if a rotation exists that fulfill all the requirements: {@code
  97.      * v₁ = r(u₁)}, {@code v₂ = r(u₂)}, {@code dv₁/dt = dr(u₁)/dt}, {@code dv₂/dt
  98.      * = dr(u₂)/dt}, {@code d²v₁/dt² = d²r(u₁)/dt²}, {@code d²v₂/dt² = d²r(u₂)/dt²}.</p>
  99.      * @param u1 first vector of the origin pair
  100.      * @param u2 second vector of the origin pair
  101.      * @param v1 desired image of u1 by the rotation
  102.      * @param v2 desired image of u2 by the rotation
  103.      * @param tolerance relative tolerance factor used to check singularities
  104.      */
  105.     public FieldAngularCoordinates(final FieldPVCoordinates<T> u1, final FieldPVCoordinates<T> u2,
  106.                                    final FieldPVCoordinates<T> v1, final FieldPVCoordinates<T> v2,
  107.                                    final double tolerance) {

  108.         try {
  109.             // find the initial fixed rotation
  110.             rotation = new FieldRotation<>(u1.getPosition(), u2.getPosition(),
  111.                                            v1.getPosition(), v2.getPosition());

  112.             // find rotation rate Ω such that
  113.             //  Ω ⨯ v₁ = r(dot(u₁)) - dot(v₁)
  114.             //  Ω ⨯ v₂ = r(dot(u₂)) - dot(v₂)
  115.             final FieldVector3D<T> ru1Dot = rotation.applyTo(u1.getVelocity());
  116.             final FieldVector3D<T> ru2Dot = rotation.applyTo(u2.getVelocity());


  117.             rotationRate = inverseCrossProducts(v1.getPosition(), ru1Dot.subtract(v1.getVelocity()),
  118.                                                 v2.getPosition(), ru2Dot.subtract(v2.getVelocity()),
  119.                                                 tolerance);


  120.             // find rotation acceleration dot(Ω) such that
  121.             // dot(Ω) ⨯ v₁ = r(dotdot(u₁)) - 2 Ω ⨯ dot(v₁) - Ω ⨯  (Ω ⨯ v₁) - dotdot(v₁)
  122.             // dot(Ω) ⨯ v₂ = r(dotdot(u₂)) - 2 Ω ⨯ dot(v₂) - Ω ⨯  (Ω ⨯ v₂) - dotdot(v₂)
  123.             final FieldVector3D<T> ru1DotDot = rotation.applyTo(u1.getAcceleration());
  124.             final FieldVector3D<T> oDotv1    = FieldVector3D.crossProduct(rotationRate, v1.getVelocity());
  125.             final FieldVector3D<T> oov1      = FieldVector3D.crossProduct(rotationRate, rotationRate.crossProduct(v1.getPosition()));
  126.             final FieldVector3D<T> c1        = new FieldVector3D<>(1, ru1DotDot, -2, oDotv1, -1, oov1, -1, v1.getAcceleration());
  127.             final FieldVector3D<T> ru2DotDot = rotation.applyTo(u2.getAcceleration());
  128.             final FieldVector3D<T> oDotv2    = FieldVector3D.crossProduct(rotationRate, v2.getVelocity());
  129.             final FieldVector3D<T> oov2      = FieldVector3D.crossProduct(rotationRate, rotationRate.crossProduct( v2.getPosition()));
  130.             final FieldVector3D<T> c2        = new FieldVector3D<>(1, ru2DotDot, -2, oDotv2, -1, oov2, -1, v2.getAcceleration());
  131.             rotationAcceleration     = inverseCrossProducts(v1.getPosition(), c1, v2.getPosition(), c2, tolerance);

  132.         } catch (MathIllegalArgumentException miae) {
  133.             throw new OrekitException(miae);
  134.         }

  135.     }

  136.     /** Builds a FieldAngularCoordinates from a field and a regular AngularCoordinates.
  137.      * @param field field for the components
  138.      * @param ang AngularCoordinates to convert
  139.      */
  140.     public FieldAngularCoordinates(final Field<T> field, final AngularCoordinates ang) {
  141.         this.rotation             = new FieldRotation<>(field, ang.getRotation());
  142.         this.rotationRate         = new FieldVector3D<>(field, ang.getRotationRate());
  143.         this.rotationAcceleration = new FieldVector3D<>(field, ang.getRotationAcceleration());
  144.     }

  145.     /** Builds a FieldAngularCoordinates from  a {@link FieldRotation}&lt;{@link FieldDerivativeStructure}&gt;.
  146.      * <p>
  147.      * The rotation components must have time as their only derivation parameter and
  148.      * have consistent derivation orders.
  149.      * </p>
  150.      * @param r rotation with time-derivatives embedded within the coordinates
  151.      * @param <U> type of the derivative
  152.      * @since 9.2
  153.      */
  154.     public <U extends FieldDerivative<T, U>> FieldAngularCoordinates(final FieldRotation<U> r) {

  155.         final T q0       = r.getQ0().getValue();
  156.         final T q1       = r.getQ1().getValue();
  157.         final T q2       = r.getQ2().getValue();
  158.         final T q3       = r.getQ3().getValue();

  159.         rotation     = new FieldRotation<>(q0, q1, q2, q3, false);
  160.         if (r.getQ0().getOrder() >= 1) {
  161.             final T q0Dot    = r.getQ0().getPartialDerivative(1);
  162.             final T q1Dot    = r.getQ1().getPartialDerivative(1);
  163.             final T q2Dot    = r.getQ2().getPartialDerivative(1);
  164.             final T q3Dot    = r.getQ3().getPartialDerivative(1);
  165.             rotationRate =
  166.                     new FieldVector3D<>(q0.linearCombination(q1.negate(), q0Dot, q0,          q1Dot,
  167.                                                              q3,          q2Dot, q2.negate(), q3Dot).multiply(2),
  168.                                         q0.linearCombination(q2.negate(), q0Dot, q3.negate(), q1Dot,
  169.                                                              q0,          q2Dot, q1,          q3Dot).multiply(2),
  170.                                         q0.linearCombination(q3.negate(), q0Dot, q2,          q1Dot,
  171.                                                              q1.negate(), q2Dot, q0,          q3Dot).multiply(2));
  172.             if (r.getQ0().getOrder() >= 2) {
  173.                 final T q0DotDot = r.getQ0().getPartialDerivative(2);
  174.                 final T q1DotDot = r.getQ1().getPartialDerivative(2);
  175.                 final T q2DotDot = r.getQ2().getPartialDerivative(2);
  176.                 final T q3DotDot = r.getQ3().getPartialDerivative(2);
  177.                 rotationAcceleration =
  178.                         new FieldVector3D<>(q0.linearCombination(q1.negate(), q0DotDot, q0,          q1DotDot,
  179.                                                                  q3,          q2DotDot, q2.negate(), q3DotDot).multiply(2),
  180.                                             q0.linearCombination(q2.negate(), q0DotDot, q3.negate(), q1DotDot,
  181.                                                                  q0,          q2DotDot, q1,          q3DotDot).multiply(2),
  182.                                             q0.linearCombination(q3.negate(), q0DotDot, q2,          q1DotDot,
  183.                                                                  q1.negate(), q2DotDot, q0,          q3DotDot).multiply(2));
  184.             } else {
  185.                 rotationAcceleration = FieldVector3D.getZero(q0.getField());
  186.             }
  187.         } else {
  188.             rotationRate         = FieldVector3D.getZero(q0.getField());
  189.             rotationAcceleration = FieldVector3D.getZero(q0.getField());
  190.         }

  191.     }

  192.     /** Fixed orientation parallel with reference frame
  193.      * (identity rotation, zero rotation rate and acceleration).
  194.      * @param field field for the components
  195.      * @param <T> the type of the field elements
  196.      * @return a new fixed orientation parallel with reference frame
  197.      */
  198.     public static <T extends CalculusFieldElement<T>> FieldAngularCoordinates<T> getIdentity(final Field<T> field) {
  199.         return new FieldAngularCoordinates<>(field, AngularCoordinates.IDENTITY);
  200.     }

  201.     /** Find a vector from two known cross products.
  202.      * <p>
  203.      * We want to find Ω such that: Ω ⨯ v₁ = c₁ and Ω ⨯ v₂ = c₂
  204.      * </p>
  205.      * <p>
  206.      * The first equation (Ω ⨯ v₁ = c₁) will always be fulfilled exactly,
  207.      * and the second one will be fulfilled if possible.
  208.      * </p>
  209.      * @param v1 vector forming the first known cross product
  210.      * @param c1 know vector for cross product Ω ⨯ v₁
  211.      * @param v2 vector forming the second known cross product
  212.      * @param c2 know vector for cross product Ω ⨯ v₂
  213.      * @param tolerance relative tolerance factor used to check singularities
  214.      * @param <T> the type of the field elements
  215.      * @return vector Ω such that: Ω ⨯ v₁ = c₁ and Ω ⨯ v₂ = c₂
  216.      * @exception MathIllegalArgumentException if vectors are inconsistent and
  217.      * no solution can be found
  218.      */
  219.     private static <T extends CalculusFieldElement<T>> FieldVector3D<T> inverseCrossProducts(final FieldVector3D<T> v1, final FieldVector3D<T> c1,
  220.                                                                                          final FieldVector3D<T> v2, final FieldVector3D<T> c2,
  221.                                                                                          final double tolerance)
  222.         throws MathIllegalArgumentException {

  223.         final T v12 = v1.getNormSq();
  224.         final T v1n = v12.sqrt();
  225.         final T v22 = v2.getNormSq();
  226.         final T v2n = v22.sqrt();
  227.         final T threshold;
  228.         if (v1n.getReal() >= v2n.getReal()) {
  229.             threshold = v1n.multiply(tolerance);
  230.         }
  231.         else {
  232.             threshold = v2n.multiply(tolerance);
  233.         }
  234.         FieldVector3D<T> omega = null;

  235.         try {
  236.             // create the over-determined linear system representing the two cross products
  237.             final FieldMatrix<T> m = MatrixUtils.createFieldMatrix(v12.getField(), 6, 3);
  238.             m.setEntry(0, 1, v1.getZ());
  239.             m.setEntry(0, 2, v1.getY().negate());
  240.             m.setEntry(1, 0, v1.getZ().negate());
  241.             m.setEntry(1, 2, v1.getX());
  242.             m.setEntry(2, 0, v1.getY());
  243.             m.setEntry(2, 1, v1.getX().negate());
  244.             m.setEntry(3, 1, v2.getZ());
  245.             m.setEntry(3, 2, v2.getY().negate());
  246.             m.setEntry(4, 0, v2.getZ().negate());
  247.             m.setEntry(4, 2, v2.getX());
  248.             m.setEntry(5, 0, v2.getY());
  249.             m.setEntry(5, 1, v2.getX().negate());

  250.             final T[] kk = MathArrays.buildArray(v2n.getField(), 6);
  251.             kk[0] = c1.getX();
  252.             kk[1] = c1.getY();
  253.             kk[2] = c1.getZ();
  254.             kk[3] = c2.getX();
  255.             kk[4] = c2.getY();
  256.             kk[5] = c2.getZ();
  257.             final FieldVector<T> rhs = MatrixUtils.createFieldVector(kk);

  258.             // find the best solution we can
  259.             final FieldDecompositionSolver<T> solver = new FieldQRDecomposition<>(m).getSolver();
  260.             final FieldVector<T> v = solver.solve(rhs);
  261.             omega = new FieldVector3D<>(v.getEntry(0), v.getEntry(1), v.getEntry(2));

  262.         } catch (MathIllegalArgumentException miae) {
  263.             if (miae.getSpecifier() == LocalizedCoreFormats.SINGULAR_MATRIX) {

  264.                 // handle some special cases for which we can compute a solution
  265.                 final T c12 = c1.getNormSq();
  266.                 final T c1n = c12.sqrt();
  267.                 final T c22 = c2.getNormSq();
  268.                 final T c2n = c22.sqrt();
  269.                 if (c1n.getReal() <= threshold.getReal() && c2n.getReal() <= threshold.getReal()) {
  270.                     // simple special case, velocities are cancelled
  271.                     return new FieldVector3D<>(v12.getField().getZero(), v12.getField().getZero(), v12.getField().getZero());
  272.                 } else if (v1n.getReal() <= threshold.getReal() && c1n.getReal() >= threshold.getReal()) {
  273.                     // this is inconsistent, if v₁ is zero, c₁ must be 0 too
  274.                     throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE,
  275.                                                            c1n.getReal(), 0, true);
  276.                 } else if (v2n.getReal() <= threshold.getReal() && c2n.getReal() >= threshold.getReal()) {
  277.                     // this is inconsistent, if v₂ is zero, c₂ must be 0 too
  278.                     throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE,
  279.                                                            c2n.getReal(), 0, true);
  280.                 } else if (v1.crossProduct(v1).getNorm().getReal() <= threshold.getReal() && v12.getReal() > threshold.getReal()) {
  281.                     // simple special case, v₂ is redundant with v₁, we just ignore it
  282.                     // use the simplest Ω: orthogonal to both v₁ and c₁
  283.                     omega = new FieldVector3D<>(v12.reciprocal(), v1.crossProduct(c1));
  284.                 } else {
  285.                     throw miae;
  286.                 }
  287.             } else {
  288.                 throw miae;
  289.             }
  290.         }
  291.         // check results
  292.         final T d1 = FieldVector3D.distance(omega.crossProduct(v1), c1);
  293.         if (d1.getReal() > threshold.getReal()) {
  294.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE, 0, true);
  295.         }

  296.         final T d2 = FieldVector3D.distance(omega.crossProduct(v2), c2);
  297.         if (d2.getReal() > threshold.getReal()) {
  298.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE, 0, true);
  299.         }

  300.         return omega;

  301.     }

  302.     /** Transform the instance to a {@link FieldRotation}&lt;{@link FieldDerivativeStructure}&gt;.
  303.      * <p>
  304.      * The {@link FieldDerivativeStructure} coordinates correspond to time-derivatives up
  305.      * to the user-specified order.
  306.      * </p>
  307.      * @param order derivation order for the vector components
  308.      * @return rotation with time-derivatives embedded within the coordinates
  309.           * @since 9.2
  310.      */
  311.     public FieldRotation<FieldDerivativeStructure<T>> toDerivativeStructureRotation(final int order) {

  312.         // quaternion components
  313.         final T q0 = rotation.getQ0();
  314.         final T q1 = rotation.getQ1();
  315.         final T q2 = rotation.getQ2();
  316.         final T q3 = rotation.getQ3();

  317.         // first time-derivatives of the quaternion
  318.         final T oX    = rotationRate.getX();
  319.         final T oY    = rotationRate.getY();
  320.         final T oZ    = rotationRate.getZ();
  321.         final T q0Dot = q0.linearCombination(q1.negate(), oX, q2.negate(), oY, q3.negate(), oZ).multiply(0.5);
  322.         final T q1Dot = q0.linearCombination(q0,          oX, q3.negate(), oY, q2,          oZ).multiply(0.5);
  323.         final T q2Dot = q0.linearCombination(q3,          oX, q0,          oY, q1.negate(), oZ).multiply(0.5);
  324.         final T q3Dot = q0.linearCombination(q2.negate(), oX, q1,          oY, q0,          oZ).multiply(0.5);

  325.         // second time-derivatives of the quaternion
  326.         final T oXDot = rotationAcceleration.getX();
  327.         final T oYDot = rotationAcceleration.getY();
  328.         final T oZDot = rotationAcceleration.getZ();
  329.         final T q0DotDot = q0.linearCombination(array6(q1, q2,  q3, q1Dot, q2Dot,  q3Dot),
  330.                                                 array6(oXDot, oYDot, oZDot, oX, oY, oZ)).multiply(-0.5);
  331.         final T q1DotDot = q0.linearCombination(array6(q0, q2, q3.negate(), q0Dot, q2Dot, q3Dot.negate()),
  332.                                                 array6(oXDot, oZDot, oYDot, oX, oZ, oY)).multiply(0.5);
  333.         final T q2DotDot = q0.linearCombination(array6(q0, q3, q1.negate(), q0Dot, q3Dot, q1Dot.negate()),
  334.                                                 array6(oYDot, oXDot, oZDot, oY, oX, oZ)).multiply(0.5);
  335.         final T q3DotDot = q0.linearCombination(array6(q0, q1, q2.negate(), q0Dot, q1Dot, q2Dot.negate()),
  336.                                                 array6(oZDot, oYDot, oXDot, oZ, oY, oX)).multiply(0.5);

  337.         final FDSFactory<T> factory;
  338.         final FieldDerivativeStructure<T> q0DS;
  339.         final FieldDerivativeStructure<T> q1DS;
  340.         final FieldDerivativeStructure<T> q2DS;
  341.         final FieldDerivativeStructure<T> q3DS;
  342.         switch (order) {
  343.             case 0 :
  344.                 factory = new FDSFactory<>(q0.getField(), 1, order);
  345.                 q0DS = factory.build(q0);
  346.                 q1DS = factory.build(q1);
  347.                 q2DS = factory.build(q2);
  348.                 q3DS = factory.build(q3);
  349.                 break;
  350.             case 1 :
  351.                 factory = new FDSFactory<>(q0.getField(), 1, order);
  352.                 q0DS = factory.build(q0, q0Dot);
  353.                 q1DS = factory.build(q1, q1Dot);
  354.                 q2DS = factory.build(q2, q2Dot);
  355.                 q3DS = factory.build(q3, q3Dot);
  356.                 break;
  357.             case 2 :
  358.                 factory = new FDSFactory<>(q0.getField(), 1, order);
  359.                 q0DS = factory.build(q0, q0Dot, q0DotDot);
  360.                 q1DS = factory.build(q1, q1Dot, q1DotDot);
  361.                 q2DS = factory.build(q2, q2Dot, q2DotDot);
  362.                 q3DS = factory.build(q3, q3Dot, q3DotDot);
  363.                 break;
  364.             default :
  365.                 throw new OrekitException(OrekitMessages.OUT_OF_RANGE_DERIVATION_ORDER, order);
  366.         }

  367.         return new FieldRotation<>(q0DS, q1DS, q2DS, q3DS, false);

  368.     }

  369.     /** Transform the instance to a {@link FieldRotation}&lt;{@link UnivariateDerivative1}&gt;.
  370.      * <p>
  371.      * The {@link UnivariateDerivative1} coordinates correspond to time-derivatives up
  372.      * to the order 1.
  373.      * </p>
  374.      * @return rotation with time-derivatives embedded within the coordinates
  375.      */
  376.     public FieldRotation<FieldUnivariateDerivative1<T>> toUnivariateDerivative1Rotation() {

  377.         // quaternion components
  378.         final T q0 = rotation.getQ0();
  379.         final T q1 = rotation.getQ1();
  380.         final T q2 = rotation.getQ2();
  381.         final T q3 = rotation.getQ3();

  382.         // first time-derivatives of the quaternion
  383.         final T oX    = rotationRate.getX();
  384.         final T oY    = rotationRate.getY();
  385.         final T oZ    = rotationRate.getZ();
  386.         final T q0Dot = q0.linearCombination(q1.negate(), oX, q2.negate(), oY, q3.negate(), oZ).multiply(0.5);
  387.         final T q1Dot = q0.linearCombination(q0,          oX, q3.negate(), oY, q2,          oZ).multiply(0.5);
  388.         final T q2Dot = q0.linearCombination(q3,          oX, q0,          oY, q1.negate(), oZ).multiply(0.5);
  389.         final T q3Dot = q0.linearCombination(q2.negate(), oX, q1,          oY, q0,          oZ).multiply(0.5);

  390.         final FieldUnivariateDerivative1<T> q0UD = new FieldUnivariateDerivative1<>(q0, q0Dot);
  391.         final FieldUnivariateDerivative1<T> q1UD = new FieldUnivariateDerivative1<>(q1, q1Dot);
  392.         final FieldUnivariateDerivative1<T> q2UD = new FieldUnivariateDerivative1<>(q2, q2Dot);
  393.         final FieldUnivariateDerivative1<T> q3UD = new FieldUnivariateDerivative1<>(q3, q3Dot);

  394.         return new FieldRotation<>(q0UD, q1UD, q2UD, q3UD, false);

  395.     }

  396.     /** Transform the instance to a {@link FieldRotation}&lt;{@link UnivariateDerivative2}&gt;.
  397.      * <p>
  398.      * The {@link UnivariateDerivative2} coordinates correspond to time-derivatives up
  399.      * to the order 2.
  400.      * </p>
  401.      * @return rotation with time-derivatives embedded within the coordinates
  402.      */
  403.     public FieldRotation<FieldUnivariateDerivative2<T>> toUnivariateDerivative2Rotation() {

  404.         // quaternion components
  405.         final T q0 = rotation.getQ0();
  406.         final T q1 = rotation.getQ1();
  407.         final T q2 = rotation.getQ2();
  408.         final T q3 = rotation.getQ3();

  409.         // first time-derivatives of the quaternion
  410.         final T oX    = rotationRate.getX();
  411.         final T oY    = rotationRate.getY();
  412.         final T oZ    = rotationRate.getZ();
  413.         final T q0Dot = q0.linearCombination(q1.negate(), oX, q2.negate(), oY, q3.negate(), oZ).multiply(0.5);
  414.         final T q1Dot = q0.linearCombination(q0,          oX, q3.negate(), oY, q2,          oZ).multiply(0.5);
  415.         final T q2Dot = q0.linearCombination(q3,          oX, q0,          oY, q1.negate(), oZ).multiply(0.5);
  416.         final T q3Dot = q0.linearCombination(q2.negate(), oX, q1,          oY, q0,          oZ).multiply(0.5);

  417.         // second time-derivatives of the quaternion
  418.         final T oXDot = rotationAcceleration.getX();
  419.         final T oYDot = rotationAcceleration.getY();
  420.         final T oZDot = rotationAcceleration.getZ();
  421.         final T q0DotDot = q0.linearCombination(array6(q1, q2,  q3, q1Dot, q2Dot,  q3Dot),
  422.                                                 array6(oXDot, oYDot, oZDot, oX, oY, oZ)).multiply(-0.5);
  423.         final T q1DotDot = q0.linearCombination(array6(q0, q2, q3.negate(), q0Dot, q2Dot, q3Dot.negate()),
  424.                                                 array6(oXDot, oZDot, oYDot, oX, oZ, oY)).multiply(0.5);
  425.         final T q2DotDot = q0.linearCombination(array6(q0, q3, q1.negate(), q0Dot, q3Dot, q1Dot.negate()),
  426.                                                 array6(oYDot, oXDot, oZDot, oY, oX, oZ)).multiply(0.5);
  427.         final T q3DotDot = q0.linearCombination(array6(q0, q1, q2.negate(), q0Dot, q1Dot, q2Dot.negate()),
  428.                                                 array6(oZDot, oYDot, oXDot, oZ, oY, oX)).multiply(0.5);

  429.         final FieldUnivariateDerivative2<T> q0UD = new FieldUnivariateDerivative2<>(q0, q0Dot, q0DotDot);
  430.         final FieldUnivariateDerivative2<T> q1UD = new FieldUnivariateDerivative2<>(q1, q1Dot, q1DotDot);
  431.         final FieldUnivariateDerivative2<T> q2UD = new FieldUnivariateDerivative2<>(q2, q2Dot, q2DotDot);
  432.         final FieldUnivariateDerivative2<T> q3UD = new FieldUnivariateDerivative2<>(q3, q3Dot, q3DotDot);

  433.         return new FieldRotation<>(q0UD, q1UD, q2UD, q3UD, false);

  434.     }

  435.     /** Build an arry of 6 elements.
  436.      * @param e1 first element
  437.      * @param e2 second element
  438.      * @param e3 third element
  439.      * @param e4 fourth element
  440.      * @param e5 fifth element
  441.      * @param e6 sixth element
  442.      * @return a new array
  443.      * @since 9.2
  444.      */
  445.     private T[] array6(final T e1, final T e2, final T e3, final T e4, final T e5, final T e6) {
  446.         final T[] array = MathArrays.buildArray(e1.getField(), 6);
  447.         array[0] = e1;
  448.         array[1] = e2;
  449.         array[2] = e3;
  450.         array[3] = e4;
  451.         array[4] = e5;
  452.         array[5] = e6;
  453.         return array;
  454.     }

  455.     /** Estimate rotation rate between two orientations.
  456.      * <p>Estimation is based on a simple fixed rate rotation
  457.      * during the time interval between the two orientations.</p>
  458.      * @param start start orientation
  459.      * @param end end orientation
  460.      * @param dt time elapsed between the dates of the two orientations
  461.      * @param <T> the type of the field elements
  462.      * @return rotation rate allowing to go from start to end orientations
  463.      */
  464.     public static <T extends CalculusFieldElement<T>>
  465.         FieldVector3D<T> estimateRate(final FieldRotation<T> start,
  466.                                       final FieldRotation<T> end,
  467.                                       final double dt) {
  468.         return estimateRate(start, end, start.getQ0().getField().getZero().newInstance(dt));
  469.     }

  470.     /** Estimate rotation rate between two orientations.
  471.      * <p>Estimation is based on a simple fixed rate rotation
  472.      * during the time interval between the two orientations.</p>
  473.      * @param start start orientation
  474.      * @param end end orientation
  475.      * @param dt time elapsed between the dates of the two orientations
  476.      * @param <T> the type of the field elements
  477.      * @return rotation rate allowing to go from start to end orientations
  478.      */
  479.     public static <T extends CalculusFieldElement<T>>
  480.         FieldVector3D<T> estimateRate(final FieldRotation<T> start,
  481.                                       final FieldRotation<T> end,
  482.                                       final T dt) {
  483.         final FieldRotation<T> evolution = start.compose(end.revert(), RotationConvention.VECTOR_OPERATOR);
  484.         return new FieldVector3D<>(evolution.getAngle().divide(dt),
  485.                                    evolution.getAxis(RotationConvention.VECTOR_OPERATOR));
  486.     }

  487.     /**
  488.      * Revert a rotation / rotation rate / rotation acceleration triplet.
  489.      *
  490.      * <p> Build a triplet which reverse the effect of another triplet.
  491.      *
  492.      * @return a new triplet whose effect is the reverse of the effect
  493.      * of the instance
  494.      */
  495.     public FieldAngularCoordinates<T> revert() {
  496.         return new FieldAngularCoordinates<>(rotation.revert(),
  497.                                              rotation.applyInverseTo(rotationRate.negate()),
  498.                                              rotation.applyInverseTo(rotationAcceleration.negate()));
  499.     }

  500.     /** Get a time-shifted rotation. Same as {@link #shiftedBy(double)} except
  501.      * only the shifted rotation is computed.
  502.      * <p>
  503.      * The state can be slightly shifted to close dates. This shift is based on
  504.      * an approximate solution of the fixed acceleration motion. It is <em>not</em>
  505.      * intended as a replacement for proper attitude propagation but should be
  506.      * sufficient for either small time shifts or coarse accuracy.
  507.      * </p>
  508.      * @param dt time shift in seconds
  509.      * @return a new state, shifted with respect to the instance (which is immutable)
  510.      * @see  #shiftedBy(CalculusFieldElement)
  511.      * @since 11.2
  512.      */
  513.     public FieldRotation<T> rotationShiftedBy(final T dt) {

  514.         // the shiftedBy method is based on a local approximation.
  515.         // It considers separately the contribution of the constant
  516.         // rotation, the linear contribution or the rate and the
  517.         // quadratic contribution of the acceleration. The rate
  518.         // and acceleration contributions are small rotations as long
  519.         // as the time shift is small, which is the crux of the algorithm.
  520.         // Small rotations are almost commutative, so we append these small
  521.         // contributions one after the other, as if they really occurred
  522.         // successively, despite this is not what really happens.

  523.         // compute the linear contribution first, ignoring acceleration
  524.         // BEWARE: there is really a minus sign here, because if
  525.         // the target frame rotates in one direction, the vectors in the origin
  526.         // frame seem to rotate in the opposite direction
  527.         final T rate = rotationRate.getNorm();
  528.         final FieldRotation<T> rateContribution = (rate.getReal() == 0.0) ?
  529.                 FieldRotation.getIdentity(dt.getField()) :
  530.                 new FieldRotation<>(rotationRate, rate.multiply(dt), RotationConvention.FRAME_TRANSFORM);

  531.         // append rotation and rate contribution
  532.         final FieldRotation<T> linearPart =
  533.                 rateContribution.compose(rotation, RotationConvention.VECTOR_OPERATOR);

  534.         final T acc  = rotationAcceleration.getNorm();
  535.         if (acc.getReal() == 0.0) {
  536.             // no acceleration, the linear part is sufficient
  537.             return linearPart;
  538.         }

  539.         // compute the quadratic contribution, ignoring initial rotation and rotation rate
  540.         // BEWARE: there is really a minus sign here, because if
  541.         // the target frame rotates in one direction, the vectors in the origin
  542.         // frame seem to rotate in the opposite direction
  543.         final FieldRotation<T> quadraticContribution =
  544.                 new FieldRotation<>(rotationAcceleration,
  545.                         acc.multiply(dt.square()).multiply(0.5),
  546.                         RotationConvention.FRAME_TRANSFORM);

  547.         // the quadratic contribution is a small rotation:
  548.         // its initial angle and angular rate are both zero.
  549.         // small rotations are almost commutative, so we append the small
  550.         // quadratic part after the linear part as a simple offset
  551.         return quadraticContribution
  552.                 .compose(linearPart, RotationConvention.VECTOR_OPERATOR);

  553.     }

  554.     /** Get a time-shifted state.
  555.      * <p>
  556.      * The state can be slightly shifted to close dates. This shift is based on
  557.      * a simple quadratic model. It is <em>not</em> intended as a replacement for
  558.      * proper attitude propagation but should be sufficient for either small
  559.      * time shifts or coarse accuracy.
  560.      * </p>
  561.      * @param dt time shift in seconds
  562.      * @return a new state, shifted with respect to the instance (which is immutable)
  563.      */
  564.     @Override
  565.     public FieldAngularCoordinates<T> shiftedBy(final double dt) {
  566.         return shiftedBy(rotation.getQ0().getField().getZero().newInstance(dt));
  567.     }

  568.     /** Get a time-shifted state.
  569.      * <p>
  570.      * The state can be slightly shifted to close dates. This shift is based on
  571.      * a simple quadratic model. It is <em>not</em> intended as a replacement for
  572.      * proper attitude propagation but should be sufficient for either small
  573.      * time shifts or coarse accuracy.
  574.      * </p>
  575.      * @param dt time shift in seconds
  576.      * @return a new state, shifted with respect to the instance (which is immutable)
  577.      */
  578.     @Override
  579.     public FieldAngularCoordinates<T> shiftedBy(final T dt) {

  580.         // the shiftedBy method is based on a local approximation.
  581.         // It considers separately the contribution of the constant
  582.         // rotation, the linear contribution or the rate and the
  583.         // quadratic contribution of the acceleration. The rate
  584.         // and acceleration contributions are small rotations as long
  585.         // as the time shift is small, which is the crux of the algorithm.
  586.         // Small rotations are almost commutative, so we append these small
  587.         // contributions one after the other, as if they really occurred
  588.         // successively, despite this is not what really happens.

  589.         // compute the linear contribution first, ignoring acceleration
  590.         // BEWARE: there is really a minus sign here, because if
  591.         // the target frame rotates in one direction, the vectors in the origin
  592.         // frame seem to rotate in the opposite direction
  593.         final T rate = rotationRate.getNorm();
  594.         final T zero = rate.getField().getZero();
  595.         final T one  = rate.getField().getOne();
  596.         final FieldRotation<T> rateContribution = (rate.getReal() == 0.0) ?
  597.                                                   new FieldRotation<>(one, zero, zero, zero, false) :
  598.                                                   new FieldRotation<>(rotationRate,
  599.                                                                       rate.multiply(dt),
  600.                                                                       RotationConvention.FRAME_TRANSFORM);

  601.         // append rotation and rate contribution
  602.         final FieldAngularCoordinates<T> linearPart =
  603.                 new FieldAngularCoordinates<>(rateContribution.compose(rotation, RotationConvention.VECTOR_OPERATOR),
  604.                                               rotationRate);

  605.         final T acc  = rotationAcceleration.getNorm();
  606.         if (acc.getReal() == 0.0) {
  607.             // no acceleration, the linear part is sufficient
  608.             return linearPart;
  609.         }

  610.         // compute the quadratic contribution, ignoring initial rotation and rotation rate
  611.         // BEWARE: there is really a minus sign here, because if
  612.         // the target frame rotates in one direction, the vectors in the origin
  613.         // frame seem to rotate in the opposite direction
  614.         final FieldAngularCoordinates<T> quadraticContribution =
  615.                 new FieldAngularCoordinates<>(new FieldRotation<>(rotationAcceleration,
  616.                                                                   acc.multiply(dt.square().multiply(0.5)),
  617.                                                                   RotationConvention.FRAME_TRANSFORM),
  618.                                               new FieldVector3D<>(dt, rotationAcceleration),
  619.                                               rotationAcceleration);

  620.         // the quadratic contribution is a small rotation:
  621.         // its initial angle and angular rate are both zero.
  622.         // small rotations are almost commutative, so we append the small
  623.         // quadratic part after the linear part as a simple offset
  624.         return quadraticContribution.addOffset(linearPart);

  625.     }

  626.     /** Get the rotation.
  627.      * @return the rotation.
  628.      */
  629.     public FieldRotation<T> getRotation() {
  630.         return rotation;
  631.     }

  632.     /** Get the rotation rate.
  633.      * @return the rotation rate vector (rad/s).
  634.      */
  635.     public FieldVector3D<T> getRotationRate() {
  636.         return rotationRate;
  637.     }

  638.     /** Get the rotation acceleration.
  639.      * @return the rotation acceleration vector dΩ/dt (rad/s²).
  640.      */
  641.     public FieldVector3D<T> getRotationAcceleration() {
  642.         return rotationAcceleration;
  643.     }

  644.     /** Add an offset from the instance.
  645.      * <p>
  646.      * We consider here that the offset rotation is applied first and the
  647.      * instance is applied afterward. Note that angular coordinates do <em>not</em>
  648.      * commute under this operation, i.e. {@code a.addOffset(b)} and {@code
  649.      * b.addOffset(a)} lead to <em>different</em> results in most cases.
  650.      * </p>
  651.      * <p>
  652.      * The two methods {@link #addOffset(FieldAngularCoordinates) addOffset} and
  653.      * {@link #subtractOffset(FieldAngularCoordinates) subtractOffset} are designed
  654.      * so that round trip applications are possible. This means that both {@code
  655.      * ac1.subtractOffset(ac2).addOffset(ac2)} and {@code
  656.      * ac1.addOffset(ac2).subtractOffset(ac2)} return angular coordinates equal to ac1.
  657.      * </p>
  658.      * @param offset offset to subtract
  659.      * @return new instance, with offset subtracted
  660.      * @see #subtractOffset(FieldAngularCoordinates)
  661.      */
  662.     public FieldAngularCoordinates<T> addOffset(final FieldAngularCoordinates<T> offset) {
  663.         final FieldVector3D<T> rOmega    = rotation.applyTo(offset.rotationRate);
  664.         final FieldVector3D<T> rOmegaDot = rotation.applyTo(offset.rotationAcceleration);
  665.         return new FieldAngularCoordinates<>(rotation.compose(offset.rotation, RotationConvention.VECTOR_OPERATOR),
  666.                                              rotationRate.add(rOmega),
  667.                                              new FieldVector3D<>( 1.0, rotationAcceleration,
  668.                                                                   1.0, rOmegaDot,
  669.                                                                  -1.0, FieldVector3D.crossProduct(rotationRate, rOmega)));
  670.     }

  671.     /** Subtract an offset from the instance.
  672.      * <p>
  673.      * We consider here that the offset Rotation is applied first and the
  674.      * instance is applied afterward. Note that angular coordinates do <em>not</em>
  675.      * commute under this operation, i.e. {@code a.subtractOffset(b)} and {@code
  676.      * b.subtractOffset(a)} lead to <em>different</em> results in most cases.
  677.      * </p>
  678.      * <p>
  679.      * The two methods {@link #addOffset(FieldAngularCoordinates) addOffset} and
  680.      * {@link #subtractOffset(FieldAngularCoordinates) subtractOffset} are designed
  681.      * so that round trip applications are possible. This means that both {@code
  682.      * ac1.subtractOffset(ac2).addOffset(ac2)} and {@code
  683.      * ac1.addOffset(ac2).subtractOffset(ac2)} return angular coordinates equal to ac1.
  684.      * </p>
  685.      * @param offset offset to subtract
  686.      * @return new instance, with offset subtracted
  687.      * @see #addOffset(FieldAngularCoordinates)
  688.      */
  689.     public FieldAngularCoordinates<T> subtractOffset(final FieldAngularCoordinates<T> offset) {
  690.         return addOffset(offset.revert());
  691.     }

  692.     /** Convert to a regular angular coordinates.
  693.      * @return a regular angular coordinates
  694.      */
  695.     public AngularCoordinates toAngularCoordinates() {
  696.         return new AngularCoordinates(rotation.toRotation(), rotationRate.toVector3D(),
  697.                                       rotationAcceleration.toVector3D());
  698.     }

  699.     /** Apply the rotation to a pv coordinates.
  700.      * @param pv vector to apply the rotation to
  701.      * @return a new pv coordinates which is the image of pv by the rotation
  702.      */
  703.     public FieldPVCoordinates<T> applyTo(final PVCoordinates pv) {

  704.         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
  705.         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
  706.         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
  707.         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
  708.         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
  709.         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
  710.         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
  711.                                                                   -2, crossV,
  712.                                                                   -1, crossCrossP,
  713.                                                                   -1, crossDotP);

  714.         return new FieldPVCoordinates<>(transformedP, transformedV, transformedA);

  715.     }

  716.     /** Apply the rotation to a pv coordinates.
  717.      * @param pv vector to apply the rotation to
  718.      * @return a new pv coordinates which is the image of pv by the rotation
  719.      */
  720.     public TimeStampedFieldPVCoordinates<T> applyTo(final TimeStampedPVCoordinates pv) {

  721.         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
  722.         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
  723.         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
  724.         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
  725.         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
  726.         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
  727.         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
  728.                                                                   -2, crossV,
  729.                                                                   -1, crossCrossP,
  730.                                                                   -1, crossDotP);

  731.         return new TimeStampedFieldPVCoordinates<>(pv.getDate(), transformedP, transformedV, transformedA);

  732.     }

  733.     /** Apply the rotation to a pv coordinates.
  734.      * @param pv vector to apply the rotation to
  735.      * @return a new pv coordinates which is the image of pv by the rotation
  736.      * @since 9.0
  737.      */
  738.     public FieldPVCoordinates<T> applyTo(final FieldPVCoordinates<T> pv) {

  739.         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
  740.         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
  741.         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
  742.         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
  743.         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
  744.         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
  745.         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
  746.                                                                   -2, crossV,
  747.                                                                   -1, crossCrossP,
  748.                                                                   -1, crossDotP);

  749.         return new FieldPVCoordinates<>(transformedP, transformedV, transformedA);

  750.     }

  751.     /** Apply the rotation to a pv coordinates.
  752.      * @param pv vector to apply the rotation to
  753.      * @return a new pv coordinates which is the image of pv by the rotation
  754.      * @since 9.0
  755.      */
  756.     public TimeStampedFieldPVCoordinates<T> applyTo(final TimeStampedFieldPVCoordinates<T> pv) {

  757.         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
  758.         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
  759.         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
  760.         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
  761.         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
  762.         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
  763.         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
  764.                                                                   -2, crossV,
  765.                                                                   -1, crossCrossP,
  766.                                                                   -1, crossDotP);

  767.         return new TimeStampedFieldPVCoordinates<>(pv.getDate(), transformedP, transformedV, transformedA);

  768.     }

  769.     /** Convert rotation, rate and acceleration to modified Rodrigues vector and derivatives.
  770.      * <p>
  771.      * The modified Rodrigues vector is tan(θ/4) u where θ and u are the
  772.      * rotation angle and axis respectively.
  773.      * </p>
  774.      * @param sign multiplicative sign for quaternion components
  775.      * @return modified Rodrigues vector and derivatives (vector on row 0, first derivative
  776.      * on row 1, second derivative on row 2)
  777.      * @see #createFromModifiedRodrigues(CalculusFieldElement[][])
  778.      * @since 9.0
  779.      */
  780.     public T[][] getModifiedRodrigues(final double sign) {

  781.         final T q0    = getRotation().getQ0().multiply(sign);
  782.         final T q1    = getRotation().getQ1().multiply(sign);
  783.         final T q2    = getRotation().getQ2().multiply(sign);
  784.         final T q3    = getRotation().getQ3().multiply(sign);
  785.         final T oX    = getRotationRate().getX();
  786.         final T oY    = getRotationRate().getY();
  787.         final T oZ    = getRotationRate().getZ();
  788.         final T oXDot = getRotationAcceleration().getX();
  789.         final T oYDot = getRotationAcceleration().getY();
  790.         final T oZDot = getRotationAcceleration().getZ();

  791.         // first time-derivatives of the quaternion
  792.         final T q0Dot = q0.linearCombination(q1.negate(), oX, q2.negate(), oY, q3.negate(), oZ).multiply(0.5);
  793.         final T q1Dot = q0.linearCombination( q0, oX, q3.negate(), oY,  q2, oZ).multiply(0.5);
  794.         final T q2Dot = q0.linearCombination( q3, oX,  q0, oY, q1.negate(), oZ).multiply(0.5);
  795.         final T q3Dot = q0.linearCombination(q2.negate(), oX,  q1, oY,  q0, oZ).multiply(0.5);

  796.         // second time-derivatives of the quaternion
  797.         final T q0DotDot = linearCombination(q1, oXDot, q2, oYDot, q3, oZDot,
  798.                                              q1Dot, oX, q2Dot, oY, q3Dot, oZ).
  799.                            multiply(-0.5);
  800.         final T q1DotDot = linearCombination(q0, oXDot, q2, oZDot, q3.negate(), oYDot,
  801.                                              q0Dot, oX, q2Dot, oZ, q3Dot.negate(), oY).
  802.                            multiply(0.5);
  803.         final T q2DotDot = linearCombination(q0, oYDot, q3, oXDot, q1.negate(), oZDot,
  804.                                              q0Dot, oY, q3Dot, oX, q1Dot.negate(), oZ).
  805.                            multiply(0.5);
  806.         final T q3DotDot = linearCombination(q0, oZDot, q1, oYDot, q2.negate(), oXDot,
  807.                                              q0Dot, oZ, q1Dot, oY, q2Dot.negate(), oX).
  808.                            multiply(0.5);

  809.         // the modified Rodrigues is tan(θ/4) u where θ and u are the rotation angle and axis respectively
  810.         // this can be rewritten using quaternion components:
  811.         //      r (q₁ / (1+q₀), q₂ / (1+q₀), q₃ / (1+q₀))
  812.         // applying the derivation chain rule to previous expression gives rDot and rDotDot
  813.         final T inv          = q0.add(1).reciprocal();
  814.         final T mTwoInvQ0Dot = inv.multiply(q0Dot).multiply(-2);

  815.         final T r1       = inv.multiply(q1);
  816.         final T r2       = inv.multiply(q2);
  817.         final T r3       = inv.multiply(q3);

  818.         final T mInvR1   = inv.multiply(r1).negate();
  819.         final T mInvR2   = inv.multiply(r2).negate();
  820.         final T mInvR3   = inv.multiply(r3).negate();

  821.         final T r1Dot    = q0.linearCombination(inv, q1Dot, mInvR1, q0Dot);
  822.         final T r2Dot    = q0.linearCombination(inv, q2Dot, mInvR2, q0Dot);
  823.         final T r3Dot    = q0.linearCombination(inv, q3Dot, mInvR3, q0Dot);

  824.         final T r1DotDot = q0.linearCombination(inv, q1DotDot, mTwoInvQ0Dot, r1Dot, mInvR1, q0DotDot);
  825.         final T r2DotDot = q0.linearCombination(inv, q2DotDot, mTwoInvQ0Dot, r2Dot, mInvR2, q0DotDot);
  826.         final T r3DotDot = q0.linearCombination(inv, q3DotDot, mTwoInvQ0Dot, r3Dot, mInvR3, q0DotDot);

  827.         final T[][] rodrigues = MathArrays.buildArray(q0.getField(), 3, 3);
  828.         rodrigues[0][0] = r1;
  829.         rodrigues[0][1] = r2;
  830.         rodrigues[0][2] = r3;
  831.         rodrigues[1][0] = r1Dot;
  832.         rodrigues[1][1] = r2Dot;
  833.         rodrigues[1][2] = r3Dot;
  834.         rodrigues[2][0] = r1DotDot;
  835.         rodrigues[2][1] = r2DotDot;
  836.         rodrigues[2][2] = r3DotDot;
  837.         return rodrigues;

  838.     }

  839.     /**
  840.      * Compute a linear combination.
  841.      * @param a1 first factor of the first term
  842.      * @param b1 second factor of the first term
  843.      * @param a2 first factor of the second term
  844.      * @param b2 second factor of the second term
  845.      * @param a3 first factor of the third term
  846.      * @param b3 second factor of the third term
  847.      * @param a4 first factor of the fourth term
  848.      * @param b4 second factor of the fourth term
  849.      * @param a5 first factor of the fifth term
  850.      * @param b5 second factor of the fifth term
  851.      * @param a6 first factor of the sixth term
  852.      * @param b6 second factor of the sicth term
  853.      * @return a<sub>1</sub>&times;b<sub>1</sub> + a<sub>2</sub>&times;b<sub>2</sub> +
  854.      * a<sub>3</sub>&times;b<sub>3</sub> + a<sub>4</sub>&times;b<sub>4</sub> +
  855.      * a<sub>5</sub>&times;b<sub>5</sub> + a<sub>6</sub>&times;b<sub>6</sub>
  856.      */
  857.     private T linearCombination(final T a1, final T b1, final T a2, final T b2, final T a3, final T b3,
  858.                                 final T a4, final T b4, final T a5, final T b5, final T a6, final T b6) {

  859.         final T[] a = MathArrays.buildArray(a1.getField(), 6);
  860.         a[0] = a1;
  861.         a[1] = a2;
  862.         a[2] = a3;
  863.         a[3] = a4;
  864.         a[4] = a5;
  865.         a[5] = a6;

  866.         final T[] b = MathArrays.buildArray(b1.getField(), 6);
  867.         b[0] = b1;
  868.         b[1] = b2;
  869.         b[2] = b3;
  870.         b[3] = b4;
  871.         b[4] = b5;
  872.         b[5] = b6;

  873.         return a1.linearCombination(a, b);

  874.     }

  875.     /** Convert a modified Rodrigues vector and derivatives to angular coordinates.
  876.      * @param r modified Rodrigues vector (with first and second times derivatives)
  877.      * @param <T> the type of the field elements
  878.      * @return angular coordinates
  879.      * @see #getModifiedRodrigues(double)
  880.      * @since 9.0
  881.      */
  882.     public static <T extends CalculusFieldElement<T>>  FieldAngularCoordinates<T> createFromModifiedRodrigues(final T[][] r) {

  883.         // rotation
  884.         final T rSquared = r[0][0].square().add(r[0][1].square()).add(r[0][2].square());
  885.         final T oPQ0     = rSquared.add(1).reciprocal().multiply(2);
  886.         final T q0       = oPQ0.subtract(1);
  887.         final T q1       = oPQ0.multiply(r[0][0]);
  888.         final T q2       = oPQ0.multiply(r[0][1]);
  889.         final T q3       = oPQ0.multiply(r[0][2]);

  890.         // rotation rate
  891.         final T oPQ02    = oPQ0.multiply(oPQ0);
  892.         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();
  893.         final T q1Dot    = oPQ0.multiply(r[1][0]).add(r[0][0].multiply(q0Dot));
  894.         final T q2Dot    = oPQ0.multiply(r[1][1]).add(r[0][1].multiply(q0Dot));
  895.         final T q3Dot    = oPQ0.multiply(r[1][2]).add(r[0][2].multiply(q0Dot));
  896.         final T oX       = q0.linearCombination(q1.negate(), q0Dot,  q0, q1Dot,  q3, q2Dot, q2.negate(), q3Dot).multiply(2);
  897.         final T oY       = q0.linearCombination(q2.negate(), q0Dot, q3.negate(), q1Dot,  q0, q2Dot,  q1, q3Dot).multiply(2);
  898.         final T oZ       = q0.linearCombination(q3.negate(), q0Dot,  q2, q1Dot, q1.negate(), q2Dot,  q0, q3Dot).multiply(2);

  899.         // rotation acceleration
  900.         final T q0DotDot = q0.subtract(1).negate().divide(oPQ0).multiply(q0Dot.square()).
  901.                            subtract(oPQ02.multiply(q0.linearCombination(r[0][0], r[2][0], r[0][1], r[2][1], r[0][2], r[2][2]))).
  902.                            subtract(q1Dot.square().add(q2Dot.square()).add(q3Dot.square()));
  903.         final T q1DotDot = q0.linearCombination(oPQ0, r[2][0], r[1][0].add(r[1][0]), q0Dot, r[0][0], q0DotDot);
  904.         final T q2DotDot = q0.linearCombination(oPQ0, r[2][1], r[1][1].add(r[1][1]), q0Dot, r[0][1], q0DotDot);
  905.         final T q3DotDot = q0.linearCombination(oPQ0, r[2][2], r[1][2].add(r[1][2]), q0Dot, r[0][2], q0DotDot);
  906.         final T oXDot    = q0.linearCombination(q1.negate(), q0DotDot,  q0, q1DotDot,  q3, q2DotDot, q2.negate(), q3DotDot).multiply(2);
  907.         final T oYDot    = q0.linearCombination(q2.negate(), q0DotDot, q3.negate(), q1DotDot,  q0, q2DotDot,  q1, q3DotDot).multiply(2);
  908.         final T oZDot    = q0.linearCombination(q3.negate(), q0DotDot,  q2, q1DotDot, q1.negate(), q2DotDot,  q0, q3DotDot).multiply(2);

  909.         return new FieldAngularCoordinates<>(new FieldRotation<>(q0, q1, q2, q3, false),
  910.                                              new FieldVector3D<>(oX, oY, oZ),
  911.                                              new FieldVector3D<>(oXDot, oYDot, oZDot));

  912.     }

  913. }