FieldAngularCoordinates.java

  1. /* Copyright 2002-2017 CS Systèmes d'Information
  2.  * Licensed to CS Systèmes d'Information (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.RealFieldElement;
  20. import org.hipparchus.exception.LocalizedCoreFormats;
  21. import org.hipparchus.exception.MathIllegalArgumentException;
  22. import org.hipparchus.geometry.euclidean.threed.FieldRotation;
  23. import org.hipparchus.geometry.euclidean.threed.FieldVector3D;
  24. import org.hipparchus.geometry.euclidean.threed.RotationConvention;
  25. import org.hipparchus.linear.FieldDecompositionSolver;
  26. import org.hipparchus.linear.FieldMatrix;
  27. import org.hipparchus.linear.FieldQRDecomposition;
  28. import org.hipparchus.linear.FieldVector;
  29. import org.hipparchus.linear.MatrixUtils;
  30. import org.hipparchus.util.MathArrays;
  31. import org.orekit.errors.OrekitException;

  32. /** Simple container for rotation / rotation rate pairs, using {@link
  33.  * RealFieldElement}.
  34.  * <p>
  35.  * The state can be slightly shifted to close dates. This shift is based on
  36.  * a simple quadratic model. It is <em>not</em> intended as a replacement for
  37.  * proper attitude propagation but should be sufficient for either small
  38.  * time shifts or coarse accuracy.
  39.  * </p>
  40.  * <p>
  41.  * This class is the angular counterpart to {@link FieldPVCoordinates}.
  42.  * </p>
  43.  * <p>Instances of this class are guaranteed to be immutable.</p>
  44.  * @param <T> the type of the field elements
  45.  * @author Luc Maisonobe
  46.  * @since 6.0
  47.  * @see AngularCoordinates
  48.  */
  49. public class FieldAngularCoordinates<T extends RealFieldElement<T>> {


  50.     /** rotation. */
  51.     private final FieldRotation<T> rotation;

  52.     /** rotation rate. */
  53.     private final FieldVector3D<T> rotationRate;

  54.     /** rotation acceleration. */
  55.     private final FieldVector3D<T> rotationAcceleration;

  56.     /** Builds a rotation/rotation rate pair.
  57.      * @param rotation rotation
  58.      * @param rotationRate rotation rate Ω (rad/s)
  59.      */
  60.     public FieldAngularCoordinates(final FieldRotation<T> rotation,
  61.                                    final FieldVector3D<T> rotationRate) {
  62.         this(rotation, rotationRate,
  63.              new FieldVector3D<>(rotation.getQ0().getField().getZero(),
  64.                                  rotation.getQ0().getField().getZero(),
  65.                                  rotation.getQ0().getField().getZero()));
  66.     }

  67.     /** Builds a rotation / rotation rate / rotation acceleration triplet.
  68.      * @param rotation i.e. the orientation of the vehicle
  69.      * @param rotationRate rotation rate rate Ω, i.e. the spin vector (rad/s)
  70.      * @param rotationAcceleration angular acceleration vector dΩ/dt (rad²/s²)
  71.      */
  72.     public FieldAngularCoordinates(final FieldRotation<T> rotation,
  73.                                    final FieldVector3D<T> rotationRate,
  74.                                    final FieldVector3D<T> rotationAcceleration) {
  75.         this.rotation             = rotation;
  76.         this.rotationRate         = rotationRate;
  77.         this.rotationAcceleration = rotationAcceleration;
  78.     }

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

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

  104.         try {
  105.             // find the initial fixed rotation
  106.             rotation = new FieldRotation<>(u1.getPosition(), u2.getPosition(),
  107.                                            v1.getPosition(), v2.getPosition());

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


  113.             rotationRate = inverseCrossProducts(v1.getPosition(), ru1Dot.subtract(v1.getVelocity()),
  114.                                                 v2.getPosition(), ru2Dot.subtract(v2.getVelocity()),
  115.                                                 tolerance);


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

  128.         } catch (MathIllegalArgumentException miae) {
  129.             throw new OrekitException(miae);
  130.         }

  131.     }

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

  141.     /** Fixed orientation parallel with reference frame
  142.      * (identity rotation, zero rotation rate and acceleration).
  143.      * @param field field for the components
  144.      * @param <T> the type of the field elements
  145.      * @return a new fixed orientation parallel with reference frame
  146.      */
  147.     public static <T extends RealFieldElement<T>> FieldAngularCoordinates<T> getIdentity(final Field<T> field) {
  148.         return new FieldAngularCoordinates<>(field, AngularCoordinates.IDENTITY);
  149.     }

  150.     /** Find a vector from two known cross products.
  151.      * <p>
  152.      * We want to find Ω such that: Ω ⨯ v₁ = c₁ and Ω ⨯ v₂ = c₂
  153.      * </p>
  154.      * <p>
  155.      * The first equation (Ω ⨯ v₁ = c₁) will always be fulfilled exactly,
  156.      * and the second one will be fulfilled if possible.
  157.      * </p>
  158.      * @param v1 vector forming the first known cross product
  159.      * @param c1 know vector for cross product Ω ⨯ v₁
  160.      * @param v2 vector forming the second known cross product
  161.      * @param c2 know vector for cross product Ω ⨯ v₂
  162.      * @param tolerance relative tolerance factor used to check singularities
  163.      * @param <T> the type of the field elements
  164.      * @return vector Ω such that: Ω ⨯ v₁ = c₁ and Ω ⨯ v₂ = c₂
  165.      * @exception MathIllegalArgumentException if vectors are inconsistent and
  166.      * no solution can be found
  167.      */
  168.     private static <T extends RealFieldElement<T>> FieldVector3D<T> inverseCrossProducts(final FieldVector3D<T> v1, final FieldVector3D<T> c1,
  169.                                                                                          final FieldVector3D<T> v2, final FieldVector3D<T> c2,
  170.                                                                                          final double tolerance)
  171.         throws MathIllegalArgumentException {

  172.         final T v12 = v1.getNormSq();
  173.         final T v1n = v12.sqrt();
  174.         final T v22 = v2.getNormSq();
  175.         final T v2n = v22.sqrt();
  176.         final T threshold;
  177.         if (v1n.getReal() >= v2n.getReal()) {
  178.             threshold = v1n.multiply(tolerance);
  179.         }
  180.         else {
  181.             threshold = v2n.multiply(tolerance);
  182.         }
  183.         FieldVector3D<T> omega = null;

  184.         try {
  185.             // create the over-determined linear system representing the two cross products
  186.             final FieldMatrix<T> m = MatrixUtils.createFieldMatrix(v12.getField(), 6, 3);
  187.             m.setEntry(0, 1, v1.getZ());
  188.             m.setEntry(0, 2, v1.getY().negate());
  189.             m.setEntry(1, 0, v1.getZ().negate());
  190.             m.setEntry(1, 2, v1.getX());
  191.             m.setEntry(2, 0, v1.getY());
  192.             m.setEntry(2, 1, v1.getX().negate());
  193.             m.setEntry(3, 1, v2.getZ());
  194.             m.setEntry(3, 2, v2.getY().negate());
  195.             m.setEntry(4, 0, v2.getZ().negate());
  196.             m.setEntry(4, 2, v2.getX());
  197.             m.setEntry(5, 0, v2.getY());
  198.             m.setEntry(5, 1, v2.getX().negate());

  199.             final T[] kk = MathArrays.buildArray(v2n.getField(), 6);
  200.             kk[0] = c1.getX();
  201.             kk[1] = c1.getY();
  202.             kk[2] = c1.getZ();
  203.             kk[3] = c2.getX();
  204.             kk[4] = c2.getY();
  205.             kk[5] = c2.getZ();
  206.             final FieldVector<T> rhs = MatrixUtils.createFieldVector(kk);

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

  211.         } catch (MathIllegalArgumentException miae) {
  212.             if (miae.getSpecifier() == LocalizedCoreFormats.SINGULAR_MATRIX) {

  213.                 // handle some special cases for which we can compute a solution
  214.                 final T c12 = c1.getNormSq();
  215.                 final T c1n = c12.sqrt();
  216.                 final T c22 = c2.getNormSq();
  217.                 final T c2n = c22.sqrt();
  218.                 if (c1n.getReal() <= threshold.getReal() && c2n.getReal() <= threshold.getReal()) {
  219.                     // simple special case, velocities are cancelled
  220.                     return new FieldVector3D<>(v12.getField().getZero(), v12.getField().getZero(), v12.getField().getZero());
  221.                 } else if (v1n.getReal() <= threshold.getReal() && c1n.getReal() >= threshold.getReal()) {
  222.                     // this is inconsistent, if v₁ is zero, c₁ must be 0 too
  223.                     throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE,
  224.                                                            c1n.getReal(), 0, true);
  225.                 } else if (v2n.getReal() <= threshold.getReal() && c2n.getReal() >= threshold.getReal()) {
  226.                     // this is inconsistent, if v₂ is zero, c₂ must be 0 too
  227.                     throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE,
  228.                                                            c2n.getReal(), 0, true);
  229.                 } else if (v1.crossProduct(v1).getNorm().getReal() <= threshold.getReal() && v12.getReal() > threshold.getReal()) {
  230.                     // simple special case, v₂ is redundant with v₁, we just ignore it
  231.                     // use the simplest Ω: orthogonal to both v₁ and c₁
  232.                     omega = new FieldVector3D<>(v12.reciprocal(), v1.crossProduct(c1));
  233.                 }
  234.             } else {
  235.                 throw miae;
  236.             }
  237.         }
  238.         // check results
  239.         final T d1 = FieldVector3D.distance(omega.crossProduct(v1), c1);
  240.         if (d1.getReal() > threshold.getReal()) {
  241.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE, 0, true);
  242.         }

  243.         final T d2 = FieldVector3D.distance(omega.crossProduct(v2), c2);
  244.         if (d2.getReal() > threshold.getReal()) {
  245.             throw new MathIllegalArgumentException(LocalizedCoreFormats.NUMBER_TOO_LARGE, 0, true);
  246.         }

  247.         return omega;

  248.     }

  249.     /** Estimate rotation rate between two orientations.
  250.      * <p>Estimation is based on a simple fixed rate rotation
  251.      * during the time interval between the two orientations.</p>
  252.      * @param start start orientation
  253.      * @param end end orientation
  254.      * @param dt time elapsed between the dates of the two orientations
  255.      * @param <T> the type of the field elements
  256.      * @return rotation rate allowing to go from start to end orientations
  257.      */
  258.     public static <T extends RealFieldElement<T>>
  259.         FieldVector3D<T> estimateRate(final FieldRotation<T> start,
  260.                                       final FieldRotation<T> end,
  261.                                       final double dt) {
  262.         return estimateRate(start, end, start.getQ0().getField().getZero().add(dt));
  263.     }

  264.     /** Estimate rotation rate between two orientations.
  265.      * <p>Estimation is based on a simple fixed rate rotation
  266.      * during the time interval between the two orientations.</p>
  267.      * @param start start orientation
  268.      * @param end end orientation
  269.      * @param dt time elapsed between the dates of the two orientations
  270.      * @param <T> the type of the field elements
  271.      * @return rotation rate allowing to go from start to end orientations
  272.      */
  273.     public static <T extends RealFieldElement<T>>
  274.         FieldVector3D<T> estimateRate(final FieldRotation<T> start,
  275.                                       final FieldRotation<T> end,
  276.                                       final T dt) {
  277.         final FieldRotation<T> evolution = start.compose(end.revert(), RotationConvention.VECTOR_OPERATOR);
  278.         return new FieldVector3D<>(evolution.getAngle().divide(dt),
  279.                                    evolution.getAxis(RotationConvention.VECTOR_OPERATOR));
  280.     }

  281.     /**
  282.      * Revert a rotation / rotation rate / rotation acceleration triplet.
  283.      *
  284.      * <p> Build a triplet which reverse the effect of another triplet.
  285.      *
  286.      * @return a new triplet whose effect is the reverse of the effect
  287.      * of the instance
  288.      */
  289.     public FieldAngularCoordinates<T> revert() {
  290.         return new FieldAngularCoordinates<>(rotation.revert(),
  291.                                              rotation.applyInverseTo(rotationRate.negate()),
  292.                                              rotation.applyInverseTo(rotationAcceleration.negate()));
  293.     }

  294.     /** Get a time-shifted state.
  295.      * <p>
  296.      * The state can be slightly shifted to close dates. This shift is based on
  297.      * a simple quadratic model. It is <em>not</em> intended as a replacement for
  298.      * proper attitude propagation but should be sufficient for either small
  299.      * time shifts or coarse accuracy.
  300.      * </p>
  301.      * @param dt time shift in seconds
  302.      * @return a new state, shifted with respect to the instance (which is immutable)
  303.      */
  304.     public FieldAngularCoordinates<T> shiftedBy(final double dt) {
  305.         return shiftedBy(rotation.getQ0().getField().getZero().add(dt));
  306.     }

  307.     /** Get a time-shifted state.
  308.      * <p>
  309.      * The state can be slightly shifted to close dates. This shift is based on
  310.      * a simple quadratic model. It is <em>not</em> intended as a replacement for
  311.      * proper attitude propagation but should be sufficient for either small
  312.      * time shifts or coarse accuracy.
  313.      * </p>
  314.      * @param dt time shift in seconds
  315.      * @return a new state, shifted with respect to the instance (which is immutable)
  316.      */
  317.     public FieldAngularCoordinates<T> shiftedBy(final T dt) {

  318.         // the shiftedBy method is based on a local approximation.
  319.         // It considers separately the contribution of the constant
  320.         // rotation, the linear contribution or the rate and the
  321.         // quadratic contribution of the acceleration. The rate
  322.         // and acceleration contributions are small rotations as long
  323.         // as the time shift is small, which is the crux of the algorithm.
  324.         // Small rotations are almost commutative, so we append these small
  325.         // contributions one after the other, as if they really occurred
  326.         // successively, despite this is not what really happens.

  327.         // compute the linear contribution first, ignoring acceleration
  328.         // BEWARE: there is really a minus sign here, because if
  329.         // the target frame rotates in one direction, the vectors in the origin
  330.         // frame seem to rotate in the opposite direction
  331.         final T rate = rotationRate.getNorm();
  332.         final T zero = rate.getField().getZero();
  333.         final T one  = rate.getField().getOne();
  334.         final FieldRotation<T> rateContribution = (rate.getReal() == 0.0) ?
  335.                                                   new FieldRotation<>(one, zero, zero, zero, false) :
  336.                                                   new FieldRotation<>(rotationRate,
  337.                                                                       rate.multiply(dt),
  338.                                                                       RotationConvention.FRAME_TRANSFORM);

  339.         // append rotation and rate contribution
  340.         final FieldAngularCoordinates<T> linearPart =
  341.                 new FieldAngularCoordinates<>(rateContribution.compose(rotation, RotationConvention.VECTOR_OPERATOR),
  342.                                               rotationRate);

  343.         final T acc  = rotationAcceleration.getNorm();
  344.         if (acc.getReal() == 0.0) {
  345.             // no acceleration, the linear part is sufficient
  346.             return linearPart;
  347.         }

  348.         // compute the quadratic contribution, ignoring initial rotation and rotation rate
  349.         // BEWARE: there is really a minus sign here, because if
  350.         // the target frame rotates in one direction, the vectors in the origin
  351.         // frame seem to rotate in the opposite direction
  352.         final FieldAngularCoordinates<T> quadraticContribution =
  353.                 new FieldAngularCoordinates<>(new FieldRotation<>(rotationAcceleration,
  354.                                                                   acc.multiply(dt.multiply(0.5).multiply(dt)),
  355.                                                                   RotationConvention.FRAME_TRANSFORM),
  356.                                               new FieldVector3D<>(dt, rotationAcceleration),
  357.                                               rotationAcceleration);

  358.         // the quadratic contribution is a small rotation:
  359.         // its initial angle and angular rate are both zero.
  360.         // small rotations are almost commutative, so we append the small
  361.         // quadratic part after the linear part as a simple offset
  362.         return quadraticContribution.addOffset(linearPart);

  363.     }

  364.     /** Get the rotation.
  365.      * @return the rotation.
  366.      */
  367.     public FieldRotation<T> getRotation() {
  368.         return rotation;
  369.     }

  370.     /** Get the rotation rate.
  371.      * @return the rotation rate vector (rad/s).
  372.      */
  373.     public FieldVector3D<T> getRotationRate() {
  374.         return rotationRate;
  375.     }

  376.     /** Get the rotation acceleration.
  377.      * @return the rotation acceleration vector dΩ/dt (rad²/s²).
  378.      */
  379.     public FieldVector3D<T> getRotationAcceleration() {
  380.         return rotationAcceleration;
  381.     }

  382.     /** Add an offset from the instance.
  383.      * <p>
  384.      * We consider here that the offset rotation is applied first and the
  385.      * instance is applied afterward. Note that angular coordinates do <em>not</em>
  386.      * commute under this operation, i.e. {@code a.addOffset(b)} and {@code
  387.      * b.addOffset(a)} lead to <em>different</em> results in most cases.
  388.      * </p>
  389.      * <p>
  390.      * The two methods {@link #addOffset(FieldAngularCoordinates) addOffset} and
  391.      * {@link #subtractOffset(FieldAngularCoordinates) subtractOffset} are designed
  392.      * so that round trip applications are possible. This means that both {@code
  393.      * ac1.subtractOffset(ac2).addOffset(ac2)} and {@code
  394.      * ac1.addOffset(ac2).subtractOffset(ac2)} return angular coordinates equal to ac1.
  395.      * </p>
  396.      * @param offset offset to subtract
  397.      * @return new instance, with offset subtracted
  398.      * @see #subtractOffset(FieldAngularCoordinates)
  399.      */
  400.     public FieldAngularCoordinates<T> addOffset(final FieldAngularCoordinates<T> offset) {
  401.         final FieldVector3D<T> rOmega    = rotation.applyTo(offset.rotationRate);
  402.         final FieldVector3D<T> rOmegaDot = rotation.applyTo(offset.rotationAcceleration);
  403.         return new FieldAngularCoordinates<>(rotation.compose(offset.rotation, RotationConvention.VECTOR_OPERATOR),
  404.                                              rotationRate.add(rOmega),
  405.                                              new FieldVector3D<>( 1.0, rotationAcceleration,
  406.                                                                   1.0, rOmegaDot,
  407.                                                                  -1.0, FieldVector3D.crossProduct(rotationRate, rOmega)));
  408.     }

  409.     /** Subtract an offset from the instance.
  410.      * <p>
  411.      * We consider here that the offset Rotation is applied first and the
  412.      * instance is applied afterward. Note that angular coordinates do <em>not</em>
  413.      * commute under this operation, i.e. {@code a.subtractOffset(b)} and {@code
  414.      * b.subtractOffset(a)} lead to <em>different</em> results in most cases.
  415.      * </p>
  416.      * <p>
  417.      * The two methods {@link #addOffset(FieldAngularCoordinates) addOffset} and
  418.      * {@link #subtractOffset(FieldAngularCoordinates) subtractOffset} are designed
  419.      * so that round trip applications are possible. This means that both {@code
  420.      * ac1.subtractOffset(ac2).addOffset(ac2)} and {@code
  421.      * ac1.addOffset(ac2).subtractOffset(ac2)} return angular coordinates equal to ac1.
  422.      * </p>
  423.      * @param offset offset to subtract
  424.      * @return new instance, with offset subtracted
  425.      * @see #addOffset(FieldAngularCoordinates)
  426.      */
  427.     public FieldAngularCoordinates<T> subtractOffset(final FieldAngularCoordinates<T> offset) {
  428.         return addOffset(offset.revert());
  429.     }

  430.     /** Convert to a regular angular coordinates.
  431.      * @return a regular angular coordinates
  432.      */
  433.     public AngularCoordinates toAngularCoordinates() {
  434.         return new AngularCoordinates(rotation.toRotation(), rotationRate.toVector3D(),
  435.                                       rotationAcceleration.toVector3D());
  436.     }

  437.     /** Apply the rotation to a pv coordinates.
  438.      * @param pv vector to apply the rotation to
  439.      * @return a new pv coordinates which is the image of u by the rotation
  440.      */
  441.     public FieldPVCoordinates<T> applyTo(final PVCoordinates pv) {

  442.         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
  443.         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
  444.         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
  445.         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
  446.         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
  447.         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
  448.         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
  449.                                                                   -2, crossV,
  450.                                                                   -1, crossCrossP,
  451.                                                                   -1, crossDotP);

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

  453.     }

  454.     /** Apply the rotation to a pv coordinates.
  455.      * @param pv vector to apply the rotation to
  456.      * @return a new pv coordinates which is the image of u by the rotation
  457.      */
  458.     public TimeStampedFieldPVCoordinates<T> applyTo(final TimeStampedPVCoordinates pv) {

  459.         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
  460.         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
  461.         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
  462.         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
  463.         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
  464.         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
  465.         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
  466.                                                                   -2, crossV,
  467.                                                                   -1, crossCrossP,
  468.                                                                   -1, crossDotP);

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

  470.     }

  471.     /** Apply the rotation to a pv coordinates.
  472.      * @param pv vector to apply the rotation to
  473.      * @return a new pv coordinates which is the image of u by the rotation
  474.      * @since 9.0
  475.      */
  476.     public FieldPVCoordinates<T> applyTo(final FieldPVCoordinates<T> pv) {

  477.         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
  478.         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
  479.         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
  480.         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
  481.         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
  482.         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
  483.         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
  484.                                                                   -2, crossV,
  485.                                                                   -1, crossCrossP,
  486.                                                                   -1, crossDotP);

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

  488.     }

  489.     /** Apply the rotation to a pv coordinates.
  490.      * @param pv vector to apply the rotation to
  491.      * @return a new pv coordinates which is the image of u by the rotation
  492.      * @since 9.0
  493.      */
  494.     public TimeStampedFieldPVCoordinates<T> applyTo(final TimeStampedFieldPVCoordinates<T> pv) {

  495.         final FieldVector3D<T> transformedP = rotation.applyTo(pv.getPosition());
  496.         final FieldVector3D<T> crossP       = FieldVector3D.crossProduct(rotationRate, transformedP);
  497.         final FieldVector3D<T> transformedV = rotation.applyTo(pv.getVelocity()).subtract(crossP);
  498.         final FieldVector3D<T> crossV       = FieldVector3D.crossProduct(rotationRate, transformedV);
  499.         final FieldVector3D<T> crossCrossP  = FieldVector3D.crossProduct(rotationRate, crossP);
  500.         final FieldVector3D<T> crossDotP    = FieldVector3D.crossProduct(rotationAcceleration, transformedP);
  501.         final FieldVector3D<T> transformedA = new FieldVector3D<>( 1, rotation.applyTo(pv.getAcceleration()),
  502.                                                                   -2, crossV,
  503.                                                                   -1, crossCrossP,
  504.                                                                   -1, crossDotP);

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

  506.     }

  507.     /** Convert rotation, rate and acceleration to modified Rodrigues vector and derivatives.
  508.      * <p>
  509.      * The modified Rodrigues vector is tan(θ/4) u where θ and u are the
  510.      * rotation angle and axis respectively.
  511.      * </p>
  512.      * @param sign multiplicative sign for quaternion components
  513.      * @return modified Rodrigues vector and derivatives (vector on row 0, first derivative
  514.      * on row 1, second derivative on row 2)
  515.      * @see #createFromModifiedRodrigues(RealFieldElement[][])
  516.      * @since 9.0
  517.      */
  518.     public T[][] getModifiedRodrigues(final double sign) {

  519.         final T q0    = getRotation().getQ0().multiply(sign);
  520.         final T q1    = getRotation().getQ1().multiply(sign);
  521.         final T q2    = getRotation().getQ2().multiply(sign);
  522.         final T q3    = getRotation().getQ3().multiply(sign);
  523.         final T oX    = getRotationRate().getX();
  524.         final T oY    = getRotationRate().getY();
  525.         final T oZ    = getRotationRate().getZ();
  526.         final T oXDot = getRotationAcceleration().getX();
  527.         final T oYDot = getRotationAcceleration().getY();
  528.         final T oZDot = getRotationAcceleration().getZ();

  529.         // first time-derivatives of the quaternion
  530.         final T q0Dot = q0.linearCombination(q1.negate(), oX, q2.negate(), oY, q3.negate(), oZ).multiply(0.5);
  531.         final T q1Dot = q0.linearCombination( q0, oX, q3.negate(), oY,  q2, oZ).multiply(0.5);
  532.         final T q2Dot = q0.linearCombination( q3, oX,  q0, oY, q1.negate(), oZ).multiply(0.5);
  533.         final T q3Dot = q0.linearCombination(q2.negate(), oX,  q1, oY,  q0, oZ).multiply(0.5);

  534.         // second time-derivatives of the quaternion
  535.         final T q0DotDot = linearCombination(q1, oXDot, q2, oYDot, q3, oZDot,
  536.                                              q1Dot, oX, q2Dot, oY, q3Dot, oZ).
  537.                            multiply(-0.5);
  538.         final T q1DotDot = linearCombination(q0, oXDot, q2, oZDot, q3.negate(), oYDot,
  539.                                              q0Dot, oX, q2Dot, oZ, q3Dot.negate(), oY).
  540.                            multiply(0.5);
  541.         final T q2DotDot = linearCombination(q0, oYDot, q3, oXDot, q1.negate(), oZDot,
  542.                                              q0Dot, oY, q3Dot, oX, q1Dot.negate(), oZ).
  543.                            multiply(0.5);
  544.         final T q3DotDot = linearCombination(q0, oZDot, q1, oYDot, q2.negate(), oXDot,
  545.                                              q0Dot, oZ, q1Dot, oY, q2Dot.negate(), oX).
  546.                            multiply(0.5);

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

  553.         final T r1       = inv.multiply(q1);
  554.         final T r2       = inv.multiply(q2);
  555.         final T r3       = inv.multiply(q3);

  556.         final T mInvR1   = inv.multiply(r1).negate();
  557.         final T mInvR2   = inv.multiply(r2).negate();
  558.         final T mInvR3   = inv.multiply(r3).negate();

  559.         final T r1Dot    = q0.linearCombination(inv, q1Dot, mInvR1, q0Dot);
  560.         final T r2Dot    = q0.linearCombination(inv, q2Dot, mInvR2, q0Dot);
  561.         final T r3Dot    = q0.linearCombination(inv, q3Dot, mInvR3, q0Dot);

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

  565.         final T[][] rodrigues = MathArrays.buildArray(q0.getField(), 3, 3);
  566.         rodrigues[0][0] = r1;
  567.         rodrigues[0][1] = r2;
  568.         rodrigues[0][2] = r3;
  569.         rodrigues[1][0] = r1Dot;
  570.         rodrigues[1][1] = r2Dot;
  571.         rodrigues[1][2] = r3Dot;
  572.         rodrigues[2][0] = r1DotDot;
  573.         rodrigues[2][1] = r2DotDot;
  574.         rodrigues[2][2] = r3DotDot;
  575.         return rodrigues;

  576.     }

  577.     /**
  578.      * Compute a linear combination.
  579.      * @param a1 first factor of the first term
  580.      * @param b1 second factor of the first term
  581.      * @param a2 first factor of the second term
  582.      * @param b2 second factor of the second term
  583.      * @param a3 first factor of the third term
  584.      * @param b3 second factor of the third term
  585.      * @param a4 first factor of the fourth term
  586.      * @param b4 second factor of the fourth term
  587.      * @param a5 first factor of the fifth term
  588.      * @param b5 second factor of the fifth term
  589.      * @param a6 first factor of the sixth term
  590.      * @param b6 second factor of the sicth term
  591.      * @return a<sub>1</sub>&times;b<sub>1</sub> + a<sub>2</sub>&times;b<sub>2</sub> +
  592.      * a<sub>3</sub>&times;b<sub>3</sub> + a<sub>4</sub>&times;b<sub>4</sub> +
  593.      * a<sub>5</sub>&times;b<sub>5</sub> + a<sub>6</sub>&times;b<sub>6</sub>
  594.      */
  595.     private T linearCombination(final T a1, final T b1, final T a2, final T b2, final T a3, final T b3,
  596.                                 final T a4, final T b4, final T a5, final T b5, final T a6, final T b6) {

  597.         final T[] a = MathArrays.buildArray(a1.getField(), 6);
  598.         a[0] = a1;
  599.         a[1] = a2;
  600.         a[2] = a3;
  601.         a[3] = a4;
  602.         a[4] = a5;
  603.         a[5] = a6;

  604.         final T[] b = MathArrays.buildArray(b1.getField(), 6);
  605.         b[0] = b1;
  606.         b[1] = b2;
  607.         b[2] = b3;
  608.         b[3] = b4;
  609.         b[4] = b5;
  610.         b[5] = b6;

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

  612.     }

  613.     /** Convert a modified Rodrigues vector and derivatives to angular coordinates.
  614.      * @param r modified Rodrigues vector (with first and second times derivatives)
  615.      * @param <T> the type of the field elements
  616.      * @return angular coordinates
  617.      * @see #getModifiedRodrigues(double)
  618.      * @since 9.0
  619.      */
  620.     public static <T extends RealFieldElement<T>>  FieldAngularCoordinates<T> createFromModifiedRodrigues(final T[][] r) {

  621.         // rotation
  622.         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]));
  623.         final T oPQ0     = rSquared.add(1).reciprocal().multiply(2);
  624.         final T q0       = oPQ0.subtract(1);
  625.         final T q1       = oPQ0.multiply(r[0][0]);
  626.         final T q2       = oPQ0.multiply(r[0][1]);
  627.         final T q3       = oPQ0.multiply(r[0][2]);

  628.         // rotation rate
  629.         final T oPQ02    = oPQ0.multiply(oPQ0);
  630.         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();
  631.         final T q1Dot    = oPQ0.multiply(r[1][0]).add(r[0][0].multiply(q0Dot));
  632.         final T q2Dot    = oPQ0.multiply(r[1][1]).add(r[0][1].multiply(q0Dot));
  633.         final T q3Dot    = oPQ0.multiply(r[1][2]).add(r[0][2].multiply(q0Dot));
  634.         final T oX       = q0.linearCombination(q1.negate(), q0Dot,  q0, q1Dot,  q3, q2Dot, q2.negate(), q3Dot).multiply(2);
  635.         final T oY       = q0.linearCombination(q2.negate(), q0Dot, q3.negate(), q1Dot,  q0, q2Dot,  q1, q3Dot).multiply(2);
  636.         final T oZ       = q0.linearCombination(q3.negate(), q0Dot,  q2, q1Dot, q1.negate(), q2Dot,  q0, q3Dot).multiply(2);

  637.         // rotation acceleration
  638.         final T q0DotDot = q0.subtract(1).negate().divide(oPQ0).multiply(q0Dot).multiply(q0Dot).
  639.                            subtract(oPQ02.multiply(q0.linearCombination(r[0][0], r[2][0], r[0][1], r[2][1], r[0][2], r[2][2]))).
  640.                            subtract(q1Dot.multiply(q1Dot).add(q2Dot.multiply(q2Dot)).add(q3Dot.multiply(q3Dot)));
  641.         final T q1DotDot = q0.linearCombination(oPQ0, r[2][0], r[1][0].add(r[1][0]), q0Dot, r[0][0], q0DotDot);
  642.         final T q2DotDot = q0.linearCombination(oPQ0, r[2][1], r[1][1].add(r[1][1]), q0Dot, r[0][1], q0DotDot);
  643.         final T q3DotDot = q0.linearCombination(oPQ0, r[2][2], r[1][2].add(r[1][2]), q0Dot, r[0][2], q0DotDot);
  644.         final T oXDot    = q0.linearCombination(q1.negate(), q0DotDot,  q0, q1DotDot,  q3, q2DotDot, q2.negate(), q3DotDot).multiply(2);
  645.         final T oYDot    = q0.linearCombination(q2.negate(), q0DotDot, q3.negate(), q1DotDot,  q0, q2DotDot,  q1, q3DotDot).multiply(2);
  646.         final T oZDot    = q0.linearCombination(q3.negate(), q0DotDot,  q2, q1DotDot, q1.negate(), q2DotDot,  q0, q3DotDot).multiply(2);

  647.         return new FieldAngularCoordinates<>(new FieldRotation<>(q0, q1, q2, q3, false),
  648.                                              new FieldVector3D<>(oX, oY, oZ),
  649.                                              new FieldVector3D<>(oXDot, oYDot, oZDot));

  650.     }

  651. }