FieldAngularCoordinates.java

  1. /* Copyright 2002-2024 CS GROUP
  2.  * Licensed to CS GROUP (CS) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * CS licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *   http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.orekit.utils;

  18. 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,
  73.              new FieldVector3D<>(rotation.getQ0().getField().getZero(),
  74.                                  rotation.getQ0().getField().getZero(),
  75.                                  rotation.getQ0().getField().getZero()));
  76.     }

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

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

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

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

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


  120.             rotationRate = inverseCrossProducts(v1.getPosition(), ru1Dot.subtract(v1.getVelocity()),
  121.                                                 v2.getPosition(), ru2Dot.subtract(v2.getVelocity()),
  122.                                                 tolerance);


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

  135.         } catch (MathIllegalArgumentException miae) {
  136.             throw new OrekitException(miae);
  137.         }

  138.     }

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

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

  158.         final T q0       = r.getQ0().getValue();
  159.         final T q1       = r.getQ1().getValue();
  160.         final T q2       = r.getQ2().getValue();
  161.         final T q3       = r.getQ3().getValue();

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

  194.     }

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

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

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

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

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

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

  265.         } catch (MathIllegalArgumentException miae) {
  266.             if (miae.getSpecifier() == LocalizedCoreFormats.SINGULAR_MATRIX) {

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

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

  303.         return omega;

  304.     }

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

  315.         // quaternion components
  316.         final T q0 = rotation.getQ0();
  317.         final T q1 = rotation.getQ1();
  318.         final T q2 = rotation.getQ2();
  319.         final T q3 = rotation.getQ3();

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

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

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

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

  371.     }

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

  380.         // quaternion components
  381.         final T q0 = rotation.getQ0();
  382.         final T q1 = rotation.getQ1();
  383.         final T q2 = rotation.getQ2();
  384.         final T q3 = rotation.getQ3();

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

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

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

  398.     }

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

  407.         // quaternion components
  408.         final T q0 = rotation.getQ0();
  409.         final T q1 = rotation.getQ1();
  410.         final T q2 = rotation.getQ2();
  411.         final T q3 = rotation.getQ3();

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

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

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

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

  437.     }

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

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

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

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

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

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

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

  534.         // append rotation and rate contribution
  535.         final FieldRotation<T> linearPart =
  536.                 rateContribution.compose(rotation, RotationConvention.VECTOR_OPERATOR);

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

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

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

  556.     }

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

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

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

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

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

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

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

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

  628.     }

  629.     /** Get the rotation.
  630.      * @return the rotation.
  631.      */
  632.     public FieldRotation<T> getRotation() {
  633.         return rotation;
  634.     }

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

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

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

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

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

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

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

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

  718.     }

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

  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.         return new TimeStampedFieldPVCoordinates<>(pv.getDate(), transformedP, transformedV, transformedA);

  735.     }

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

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

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

  753.     }

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

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

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

  771.     }

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

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

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

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

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

  818.         final T r1       = inv.multiply(q1);
  819.         final T r2       = inv.multiply(q2);
  820.         final T r3       = inv.multiply(q3);

  821.         final T mInvR1   = inv.multiply(r1).negate();
  822.         final T mInvR2   = inv.multiply(r2).negate();
  823.         final T mInvR3   = inv.multiply(r3).negate();

  824.         final T r1Dot    = q0.linearCombination(inv, q1Dot, mInvR1, q0Dot);
  825.         final T r2Dot    = q0.linearCombination(inv, q2Dot, mInvR2, q0Dot);
  826.         final T r3Dot    = q0.linearCombination(inv, q3Dot, mInvR3, q0Dot);

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

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

  841.     }

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

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

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

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

  877.     }

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

  886.         // rotation
  887.         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]));
  888.         final T oPQ0     = rSquared.add(1).reciprocal().multiply(2);
  889.         final T q0       = oPQ0.subtract(1);
  890.         final T q1       = oPQ0.multiply(r[0][0]);
  891.         final T q2       = oPQ0.multiply(r[0][1]);
  892.         final T q3       = oPQ0.multiply(r[0][2]);

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

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

  912.         return new FieldAngularCoordinates<>(new FieldRotation<>(q0, q1, q2, q3, false),
  913.                                              new FieldVector3D<>(oX, oY, oZ),
  914.                                              new FieldVector3D<>(oXDot, oYDot, oZDot));

  915.     }

  916. }