From b1a4e8f2f962a51d075e341b3305553b1a2cf6bc Mon Sep 17 00:00:00 2001 From: Luc Maisonobe Date: Mon, 18 Feb 2013 13:58:21 +0000 Subject: [PATCH] Added partial derivatives computation for 3D vectors and rotations. git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@1447263 13f79535-47bb-0310-9956-ffa450edef68 --- src/changes/changes.xml | 5 +- .../geometry/euclidean/threed/RotationDS.java | 1211 +++++++++++++++++ .../geometry/euclidean/threed/Vector3DDS.java | 1087 +++++++++++++++ .../euclidean/threed/RotationDSTest.java | 841 ++++++++++++ .../euclidean/threed/Vector3DDSTest.java | 733 ++++++++++ 5 files changed, 3876 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/apache/commons/math3/geometry/euclidean/threed/RotationDS.java create mode 100644 src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DDS.java create mode 100644 src/test/java/org/apache/commons/math3/geometry/euclidean/threed/RotationDSTest.java create mode 100644 src/test/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DDSTest.java diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 3820d35b7..06e9e1736 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -55,7 +55,10 @@ This is a minor release: It combines bug fixes and new features. Changes to existing features were made in a backwards-compatible way such as to allow drop-in replacement of the v3.1[.1] JAR file. "> - + + Added partial derivatives computation for 3D vectors and rotations. + + Fixed DerivativeStructure.atan2 for special cases when both arguments are +/-0. diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/RotationDS.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/RotationDS.java new file mode 100644 index 000000000..485dca054 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/RotationDS.java @@ -0,0 +1,1211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.geometry.euclidean.threed; + +import java.io.Serializable; + +import org.apache.commons.math3.Field; +import org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import org.apache.commons.math3.exception.MathArithmeticException; +import org.apache.commons.math3.exception.MathIllegalArgumentException; +import org.apache.commons.math3.exception.util.LocalizedFormats; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathArrays; + +/** + * This class is a re-implementation of {@link Rotation} using {@link DerivativeStructure}. + *

Instance of this class are guaranteed to be immutable.

+ * + * @version $Id$ + * @see Vector3DDSDS + * @see RotationOrder + * @since 3.2 + */ + +public class RotationDS implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = 20130215l; + + /** Scalar coordinate of the quaternion. */ + private final DerivativeStructure q0; + + /** First coordinate of the vectorial part of the quaternion. */ + private final DerivativeStructure q1; + + /** Second coordinate of the vectorial part of the quaternion. */ + private final DerivativeStructure q2; + + /** Third coordinate of the vectorial part of the quaternion. */ + private final DerivativeStructure q3; + + /** Build a rotation from the quaternion coordinates. + *

A rotation can be built from a normalized quaternion, + * i.e. a quaternion for which q02 + + * q12 + q22 + + * q32 = 1. If the quaternion is not normalized, + * the constructor can normalize it in a preprocessing step.

+ *

Note that some conventions put the scalar part of the quaternion + * as the 4th component and the vector part as the first three + * components. This is not our convention. We put the scalar part + * as the first component.

+ * @param q0 scalar part of the quaternion + * @param q1 first coordinate of the vectorial part of the quaternion + * @param q2 second coordinate of the vectorial part of the quaternion + * @param q3 third coordinate of the vectorial part of the quaternion + * @param needsNormalization if true, the coordinates are considered + * not to be normalized, a normalization preprocessing step is performed + * before using them + */ + public RotationDS(final DerivativeStructure q0, final DerivativeStructure q1, + final DerivativeStructure q2, final DerivativeStructure q3, + final boolean needsNormalization) { + + if (needsNormalization) { + // normalization preprocessing + final DerivativeStructure inv = + q0.multiply(q0).add(q1.multiply(q1)).add(q2.multiply(q2)).add(q3.multiply(q3)).sqrt().reciprocal(); + this.q0 = inv.multiply(q0); + this.q1 = inv.multiply(q1); + this.q2 = inv.multiply(q2); + this.q3 = inv.multiply(q3); + } else { + this.q0 = q0; + this.q1 = q1; + this.q2 = q2; + this.q3 = q3; + } + + } + + /** Build a rotation from an axis and an angle. + *

We use the convention that angles are oriented according to + * the effect of the rotation on vectors around the axis. That means + * that if (i, j, k) is a direct frame and if we first provide +k as + * the axis and π/2 as the angle to this constructor, and then + * {@link #applyTo(Vector3DDS) apply} the instance to +i, we will get + * +j.

+ *

Another way to represent our convention is to say that a rotation + * of angle θ about the unit vector (x, y, z) is the same as the + * rotation build from quaternion components { cos(-θ/2), + * x * sin(-θ/2), y * sin(-θ/2), z * sin(-θ/2) }. + * Note the minus sign on the angle!

+ *

On the one hand this convention is consistent with a vectorial + * perspective (moving vectors in fixed frames), on the other hand it + * is different from conventions with a frame perspective (fixed vectors + * viewed from different frames) like the ones used for example in spacecraft + * attitude community or in the graphics community.

+ * @param axis axis around which to rotate + * @param angle rotation angle. + * @exception MathIllegalArgumentException if the axis norm is zero + */ + public RotationDS(final Vector3DDS axis, final DerivativeStructure angle) + throws MathIllegalArgumentException { + + final DerivativeStructure norm = axis.getNorm(); + if (norm.getValue() == 0) { + throw new MathIllegalArgumentException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_AXIS); + } + + final DerivativeStructure halfAngle = angle.multiply(-0.5); + final DerivativeStructure coeff = halfAngle.sin().divide(norm); + + q0 = halfAngle.cos(); + q1 = coeff.multiply(axis.getX()); + q2 = coeff.multiply(axis.getY()); + q3 = coeff.multiply(axis.getZ()); + + } + + /** Build a rotation from a 3X3 matrix. + + *

Rotation matrices are orthogonal matrices, i.e. unit matrices + * (which are matrices for which m.mT = I) with real + * coefficients. The module of the determinant of unit matrices is + * 1, among the orthogonal 3X3 matrices, only the ones having a + * positive determinant (+1) are rotation matrices.

+ + *

When a rotation is defined by a matrix with truncated values + * (typically when it is extracted from a technical sheet where only + * four to five significant digits are available), the matrix is not + * orthogonal anymore. This constructor handles this case + * transparently by using a copy of the given matrix and applying a + * correction to the copy in order to perfect its orthogonality. If + * the Frobenius norm of the correction needed is above the given + * threshold, then the matrix is considered to be too far from a + * true rotation matrix and an exception is thrown.

+ + * @param m rotation matrix + * @param threshold convergence threshold for the iterative + * orthogonality correction (convergence is reached when the + * difference between two steps of the Frobenius norm of the + * correction is below this threshold) + + * @exception NotARotationMatrixException if the matrix is not a 3X3 + * matrix, or if it cannot be transformed into an orthogonal matrix + * with the given threshold, or if the determinant of the resulting + * orthogonal matrix is negative + + */ + public RotationDS(final DerivativeStructure[][] m, final double threshold) + throws NotARotationMatrixException { + + // dimension check + if ((m.length != 3) || (m[0].length != 3) || + (m[1].length != 3) || (m[2].length != 3)) { + throw new NotARotationMatrixException( + LocalizedFormats.ROTATION_MATRIX_DIMENSIONS, + m.length, m[0].length); + } + + // compute a "close" orthogonal matrix + final DerivativeStructure[][] ort = orthogonalizeMatrix(m, threshold); + + // check the sign of the determinant + final DerivativeStructure d0 = ort[1][1].multiply(ort[2][2]).subtract(ort[2][1].multiply(ort[1][2])); + final DerivativeStructure d1 = ort[0][1].multiply(ort[2][2]).subtract(ort[2][1].multiply(ort[0][2])); + final DerivativeStructure d2 = ort[0][1].multiply(ort[1][2]).subtract(ort[1][1].multiply(ort[0][2])); + final DerivativeStructure det = + ort[0][0].multiply(d0).subtract(ort[1][0].multiply(d1)).add(ort[2][0].multiply(d2)); + if (det.getValue() < 0.0) { + throw new NotARotationMatrixException( + LocalizedFormats.CLOSEST_ORTHOGONAL_MATRIX_HAS_NEGATIVE_DETERMINANT, + det); + } + + final DerivativeStructure[] quat = mat2quat(ort); + q0 = quat[0]; + q1 = quat[1]; + q2 = quat[2]; + q3 = quat[3]; + + } + + /** Build the rotation that transforms a pair of vector into another pair. + + *

Except for possible scale factors, if the instance were applied to + * the pair (u1, u2) it will produce the pair + * (v1, v2).

+ + *

If the angular separation between u1 and u2 is + * not the same as the angular separation between v1 and + * v2, then a corrected v'2 will be used rather than + * v2, the corrected vector will be in the (v1, + * v2) plane.

+ + * @param u1 first vector of the origin pair + * @param u2 second vector of the origin pair + * @param v1 desired image of u1 by the rotation + * @param v2 desired image of u2 by the rotation + * @exception MathArithmeticException if the norm of one of the vectors is zero, + * or if one of the pair is degenerated (i.e. the vectors of the pair are colinear) + */ + public RotationDS(Vector3DDS u1, Vector3DDS u2, Vector3DDS v1, Vector3DDS v2) + throws MathArithmeticException { + + // build orthonormalized base from u1, u2 + // this fails when vectors are null or colinear, which is forbidden to define a rotation + final Vector3DDS u3 = u1.crossProduct(u2).normalize(); + u2 = u3.crossProduct(u1).normalize(); + u1 = u1.normalize(); + + // build an orthonormalized base from v1, v2 + // this fails when vectors are null or colinear, which is forbidden to define a rotation + final Vector3DDS v3 = v1.crossProduct(v2).normalize(); + v2 = v3.crossProduct(v1).normalize(); + v1 = v1.normalize(); + + // buid a matrix transforming the first base into the second one + final DerivativeStructure[][] m = new DerivativeStructure[][] { + { + MathArrays.linearCombination(u1.getX(), v1.getX(), u2.getX(), v2.getX(), u3.getX(), v3.getX()), + MathArrays.linearCombination(u1.getY(), v1.getX(), u2.getY(), v2.getX(), u3.getY(), v3.getX()), + MathArrays.linearCombination(u1.getZ(), v1.getX(), u2.getZ(), v2.getX(), u3.getZ(), v3.getX()) + }, + { + MathArrays.linearCombination(u1.getX(), v1.getY(), u2.getX(), v2.getY(), u3.getX(), v3.getY()), + MathArrays.linearCombination(u1.getY(), v1.getY(), u2.getY(), v2.getY(), u3.getY(), v3.getY()), + MathArrays.linearCombination(u1.getZ(), v1.getY(), u2.getZ(), v2.getY(), u3.getZ(), v3.getY()) + }, + { + MathArrays.linearCombination(u1.getX(), v1.getZ(), u2.getX(), v2.getZ(), u3.getX(), v3.getZ()), + MathArrays.linearCombination(u1.getY(), v1.getZ(), u2.getY(), v2.getZ(), u3.getY(), v3.getZ()), + MathArrays.linearCombination(u1.getZ(), v1.getZ(), u2.getZ(), v2.getZ(), u3.getZ(), v3.getZ()) + } + }; + + DerivativeStructure[] quat = mat2quat(m); + q0 = quat[0]; + q1 = quat[1]; + q2 = quat[2]; + q3 = quat[3]; + + } + + /** Build one of the rotations that transform one vector into another one. + + *

Except for a possible scale factor, if the instance were + * applied to the vector u it will produce the vector v. There is an + * infinite number of such rotations, this constructor choose the + * one with the smallest associated angle (i.e. the one whose axis + * is orthogonal to the (u, v) plane). If u and v are colinear, an + * arbitrary rotation axis is chosen.

+ + * @param u origin vector + * @param v desired image of u by the rotation + * @exception MathArithmeticException if the norm of one of the vectors is zero + */ + public RotationDS(final Vector3DDS u, final Vector3DDS v) throws MathArithmeticException { + + final DerivativeStructure normProduct = u.getNorm().multiply(v.getNorm()); + if (normProduct.getValue() == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR); + } + + final DerivativeStructure dot = u.dotProduct(v); + + if (dot.getValue() < ((2.0e-15 - 1.0) * normProduct.getValue())) { + // special case u = -v: we select a PI angle rotation around + // an arbitrary vector orthogonal to u + final Vector3DDS w = u.orthogonal(); + q0 = normProduct.getField().getZero(); + q1 = w.getX().negate(); + q2 = w.getY().negate(); + q3 = w.getZ().negate(); + } else { + // general case: (u, v) defines a plane, we select + // the shortest possible rotation: axis orthogonal to this plane + q0 = dot.divide(normProduct).add(1.0).multiply(0.5).sqrt(); + final DerivativeStructure coeff = q0.multiply(normProduct).multiply(2.0).reciprocal(); + final Vector3DDS q = v.crossProduct(u); + q1 = coeff.multiply(q.getX()); + q2 = coeff.multiply(q.getY()); + q3 = coeff.multiply(q.getZ()); + } + + } + + /** Build a rotation from three Cardan or Euler elementary rotations. + + *

Cardan rotations are three successive rotations around the + * canonical axes X, Y and Z, each axis being used once. There are + * 6 such sets of rotations (XYZ, XZY, YXZ, YZX, ZXY and ZYX). Euler + * rotations are three successive rotations around the canonical + * axes X, Y and Z, the first and last rotations being around the + * same axis. There are 6 such sets of rotations (XYX, XZX, YXY, + * YZY, ZXZ and ZYZ), the most popular one being ZXZ.

+ *

Beware that many people routinely use the term Euler angles even + * for what really are Cardan angles (this confusion is especially + * widespread in the aerospace business where Roll, Pitch and Yaw angles + * are often wrongly tagged as Euler angles).

+ + * @param order order of rotations to use + * @param alpha1 angle of the first elementary rotation + * @param alpha2 angle of the second elementary rotation + * @param alpha3 angle of the third elementary rotation + */ + public RotationDS(final RotationOrder order, final DerivativeStructure alpha1, + final DerivativeStructure alpha2, final DerivativeStructure alpha3) { + final int p = alpha1.getFreeParameters(); + final int o = alpha1.getOrder(); + final RotationDS r1 = + new RotationDS(new Vector3DDS(new DerivativeStructure(p, o, order.getA1().getX()), + new DerivativeStructure(p, o, order.getA1().getY()), + new DerivativeStructure(p, o, order.getA1().getZ())), + alpha1); + final RotationDS r2 = + new RotationDS(new Vector3DDS(new DerivativeStructure(p, o, order.getA2().getX()), + new DerivativeStructure(p, o, order.getA2().getY()), + new DerivativeStructure(p, o, order.getA2().getZ())), + alpha2); + final RotationDS r3 = + new RotationDS(new Vector3DDS(new DerivativeStructure(p, o, order.getA3().getX()), + new DerivativeStructure(p, o, order.getA3().getY()), + new DerivativeStructure(p, o, order.getA3().getZ())), + alpha3); + final RotationDS composed = r1.applyTo(r2.applyTo(r3)); + q0 = composed.q0; + q1 = composed.q1; + q2 = composed.q2; + q3 = composed.q3; + } + + /** Convert an orthogonal rotation matrix to a quaternion. + * @param ort orthogonal rotation matrix + * @return quaternion corresponding to the matrix + */ + private static DerivativeStructure[] mat2quat(final DerivativeStructure[][] ort) { + + final DerivativeStructure[] quat = new DerivativeStructure[4]; + + // There are different ways to compute the quaternions elements + // from the matrix. They all involve computing one element from + // the diagonal of the matrix, and computing the three other ones + // using a formula involving a division by the first element, + // which unfortunately can be zero. Since the norm of the + // quaternion is 1, we know at least one element has an absolute + // value greater or equal to 0.5, so it is always possible to + // select the right formula and avoid division by zero and even + // numerical inaccuracy. Checking the elements in turn and using + // the first one greater than 0.45 is safe (this leads to a simple + // test since qi = 0.45 implies 4 qi^2 - 1 = -0.19) + DerivativeStructure s = ort[0][0].add(ort[1][1]).add(ort[2][2]); + if (s.getValue() > -0.19) { + // compute q0 and deduce q1, q2 and q3 + quat[0] = s.add(1.0).sqrt().multiply(0.5); + DerivativeStructure inv = quat[0].reciprocal().multiply(0.25); + quat[1] = inv.multiply(ort[1][2].subtract(ort[2][1])); + quat[2] = inv.multiply(ort[2][0].subtract(ort[0][2])); + quat[3] = inv.multiply(ort[0][1].subtract(ort[1][0])); + } else { + s = ort[0][0].subtract(ort[1][1]).subtract(ort[2][2]); + if (s.getValue() > -0.19) { + // compute q1 and deduce q0, q2 and q3 + quat[1] = s.add(1.0).sqrt().multiply(0.5); + DerivativeStructure inv = quat[1].reciprocal().multiply(0.25); + quat[0] = inv.multiply(ort[1][2].subtract(ort[2][1])); + quat[2] = inv.multiply(ort[0][1].add(ort[1][0])); + quat[3] = inv.multiply(ort[0][2].add(ort[2][0])); + } else { + s = ort[1][1].subtract(ort[0][0]).subtract(ort[2][2]); + if (s.getValue() > -0.19) { + // compute q2 and deduce q0, q1 and q3 + quat[2] = s.add(1.0).sqrt().multiply(0.5); + DerivativeStructure inv = quat[2].reciprocal().multiply(0.25); + quat[0] = inv.multiply(ort[2][0].subtract(ort[0][2])); + quat[1] = inv.multiply(ort[0][1].add(ort[1][0])); + quat[3] = inv.multiply(ort[2][1].add(ort[1][2])); + } else { + // compute q3 and deduce q0, q1 and q2 + s = ort[2][2].subtract(ort[0][0]).subtract(ort[1][1]); + quat[3] = s.add(1.0).sqrt().multiply(0.5); + DerivativeStructure inv = quat[3].reciprocal().multiply(0.25); + quat[0] = inv.multiply(ort[0][1].subtract(ort[1][0])); + quat[1] = inv.multiply(ort[0][2].add(ort[2][0])); + quat[2] = inv.multiply(ort[2][1].add(ort[1][2])); + } + } + } + + return quat; + + } + + /** Revert a rotation. + * Build a rotation which reverse the effect of another + * rotation. This means that if r(u) = v, then r.revert(v) = u. The + * instance is not changed. + * @return a new rotation whose effect is the reverse of the effect + * of the instance + */ + public RotationDS revert() { + return new RotationDS(q0.negate(), q1, q2, q3, false); + } + + /** Get the scalar coordinate of the quaternion. + * @return scalar coordinate of the quaternion + */ + public DerivativeStructure getQ0() { + return q0; + } + + /** Get the first coordinate of the vectorial part of the quaternion. + * @return first coordinate of the vectorial part of the quaternion + */ + public DerivativeStructure getQ1() { + return q1; + } + + /** Get the second coordinate of the vectorial part of the quaternion. + * @return second coordinate of the vectorial part of the quaternion + */ + public DerivativeStructure getQ2() { + return q2; + } + + /** Get the third coordinate of the vectorial part of the quaternion. + * @return third coordinate of the vectorial part of the quaternion + */ + public DerivativeStructure getQ3() { + return q3; + } + + /** Get the normalized axis of the rotation. + * @return normalized axis of the rotation + * @see #Rotation(Vector3DDS, DerivativeStructure) + */ + public Vector3DDS getAxis() { + final DerivativeStructure squaredSine = q1.multiply(q1).add(q2.multiply(q2)).add(q3.multiply(q3)); + if (squaredSine.getValue() == 0) { + final Field field = squaredSine.getField(); + return new Vector3DDS(field.getOne(), field.getZero(), field.getZero()); + } else if (q0.getValue() < 0) { + DerivativeStructure inverse = squaredSine.sqrt().reciprocal(); + return new Vector3DDS(q1.multiply(inverse), q2.multiply(inverse), q3.multiply(inverse)); + } + final DerivativeStructure inverse = squaredSine.sqrt().reciprocal().negate(); + return new Vector3DDS(q1.multiply(inverse), q2.multiply(inverse), q3.multiply(inverse)); + } + + /** Get the angle of the rotation. + * @return angle of the rotation (between 0 and π) + * @see #Rotation(Vector3DDS, DerivativeStructure) + */ + public DerivativeStructure getAngle() { + if ((q0.getValue() < -0.1) || (q0.getValue() > 0.1)) { + return q1.multiply(q1).add(q2.multiply(q2)).add(q3.multiply(q3)).sqrt().asin().multiply(2); + } else if (q0.getValue() < 0) { + return q0.negate().acos().multiply(2); + } + return q0.acos().multiply(2); + } + + /** Get the Cardan or Euler angles corresponding to the instance. + + *

The equations show that each rotation can be defined by two + * different values of the Cardan or Euler angles set. For example + * if Cardan angles are used, the rotation defined by the angles + * a1, a2 and a3 is the same as + * the rotation defined by the angles π + a1, π + * - a2 and π + a3. This method implements + * the following arbitrary choices:

+ *
    + *
  • for Cardan angles, the chosen set is the one for which the + * second angle is between -π/2 and π/2 (i.e its cosine is + * positive),
  • + *
  • for Euler angles, the chosen set is the one for which the + * second angle is between 0 and π (i.e its sine is positive).
  • + *
+ + *

Cardan and Euler angle have a very disappointing drawback: all + * of them have singularities. This means that if the instance is + * too close to the singularities corresponding to the given + * rotation order, it will be impossible to retrieve the angles. For + * Cardan angles, this is often called gimbal lock. There is + * nothing to do to prevent this, it is an intrinsic problem + * with Cardan and Euler representation (but not a problem with the + * rotation itself, which is perfectly well defined). For Cardan + * angles, singularities occur when the second angle is close to + * -π/2 or +π/2, for Euler angle singularities occur when the + * second angle is close to 0 or π, this implies that the identity + * rotation is always singular for Euler angles!

+ + * @param order rotation order to use + * @return an array of three angles, in the order specified by the set + * @exception CardanEulerSingularityException if the rotation is + * singular with respect to the angles set specified + */ + public DerivativeStructure[] getAngles(final RotationOrder order) + throws CardanEulerSingularityException { + + if (order == RotationOrder.XYZ) { + + // r (+K) coordinates are : + // sin (theta), -cos (theta) sin (phi), cos (theta) cos (phi) + // (-r) (+I) coordinates are : + // cos (psi) cos (theta), -sin (psi) cos (theta), sin (theta) + final // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + Vector3DDS v1 = applyTo(vector(0, 0, 1)); + final Vector3DDS v2 = applyInverseTo(vector(1, 0, 0)); + if ((v2.getZ().getValue() < -0.9999999999) || (v2.getZ().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getY().negate(), v1.getZ()), + v2.getZ().asin(), + DerivativeStructure.atan2(v2.getY().negate(), v2.getX()) + }; + + } else if (order == RotationOrder.XZY) { + + // r (+J) coordinates are : + // -sin (psi), cos (psi) cos (phi), cos (psi) sin (phi) + // (-r) (+I) coordinates are : + // cos (theta) cos (psi), -sin (psi), sin (theta) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + final Vector3DDS v1 = applyTo(vector(0, 1, 0)); + final Vector3DDS v2 = applyInverseTo(vector(1, 0, 0)); + if ((v2.getY().getValue() < -0.9999999999) || (v2.getY().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getZ(), v1.getY()), + v2.getY().asin().negate(), + DerivativeStructure.atan2(v2.getZ(), v2.getX()) + }; + + } else if (order == RotationOrder.YXZ) { + + // r (+K) coordinates are : + // cos (phi) sin (theta), -sin (phi), cos (phi) cos (theta) + // (-r) (+J) coordinates are : + // sin (psi) cos (phi), cos (psi) cos (phi), -sin (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + final Vector3DDS v1 = applyTo(vector(0, 0, 1)); + final Vector3DDS v2 = applyInverseTo(vector(0, 1, 0)); + if ((v2.getZ().getValue() < -0.9999999999) || (v2.getZ().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getX(), v1.getZ()), + v2.getZ().asin().negate(), + DerivativeStructure.atan2(v2.getX(), v2.getY()) + }; + + } else if (order == RotationOrder.YZX) { + + // r (+I) coordinates are : + // cos (psi) cos (theta), sin (psi), -cos (psi) sin (theta) + // (-r) (+J) coordinates are : + // sin (psi), cos (phi) cos (psi), -sin (phi) cos (psi) + // and we can choose to have psi in the interval [-PI/2 ; +PI/2] + final Vector3DDS v1 = applyTo(vector(1, 0, 0)); + final Vector3DDS v2 = applyInverseTo(vector(0, 1, 0)); + if ((v2.getX().getValue() < -0.9999999999) || (v2.getX().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getZ().negate(), v1.getX()), + v2.getX().asin(), + DerivativeStructure.atan2(v2.getZ().negate(), v2.getY()) + }; + + } else if (order == RotationOrder.ZXY) { + + // r (+J) coordinates are : + // -cos (phi) sin (psi), cos (phi) cos (psi), sin (phi) + // (-r) (+K) coordinates are : + // -sin (theta) cos (phi), sin (phi), cos (theta) cos (phi) + // and we can choose to have phi in the interval [-PI/2 ; +PI/2] + final Vector3DDS v1 = applyTo(vector(0, 1, 0)); + final Vector3DDS v2 = applyInverseTo(vector(0, 0, 1)); + if ((v2.getY().getValue() < -0.9999999999) || (v2.getY().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getX().negate(), v1.getY()), + v2.getY().asin(), + DerivativeStructure.atan2(v2.getX().negate(), v2.getZ()) + }; + + } else if (order == RotationOrder.ZYX) { + + // r (+I) coordinates are : + // cos (theta) cos (psi), cos (theta) sin (psi), -sin (theta) + // (-r) (+K) coordinates are : + // -sin (theta), sin (phi) cos (theta), cos (phi) cos (theta) + // and we can choose to have theta in the interval [-PI/2 ; +PI/2] + final Vector3DDS v1 = applyTo(vector(1, 0, 0)); + final Vector3DDS v2 = applyInverseTo(vector(0, 0, 1)); + if ((v2.getX().getValue() < -0.9999999999) || (v2.getX().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(true); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getY(), v1.getX()), + v2.getX().asin().negate(), + DerivativeStructure.atan2(v2.getY(), v2.getZ()) + }; + + } else if (order == RotationOrder.XYX) { + + // r (+I) coordinates are : + // cos (theta), sin (phi1) sin (theta), -cos (phi1) sin (theta) + // (-r) (+I) coordinates are : + // cos (theta), sin (theta) sin (phi2), sin (theta) cos (phi2) + // and we can choose to have theta in the interval [0 ; PI] + final Vector3DDS v1 = applyTo(vector(1, 0, 0)); + final Vector3DDS v2 = applyInverseTo(vector(1, 0, 0)); + if ((v2.getX().getValue() < -0.9999999999) || (v2.getX().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getY(), v1.getZ().negate()), + v2.getX().acos(), + DerivativeStructure.atan2(v2.getY(), v2.getZ()) + }; + + } else if (order == RotationOrder.XZX) { + + // r (+I) coordinates are : + // cos (psi), cos (phi1) sin (psi), sin (phi1) sin (psi) + // (-r) (+I) coordinates are : + // cos (psi), -sin (psi) cos (phi2), sin (psi) sin (phi2) + // and we can choose to have psi in the interval [0 ; PI] + final Vector3DDS v1 = applyTo(vector(1, 0, 0)); + final Vector3DDS v2 = applyInverseTo(vector(1, 0, 0)); + if ((v2.getX().getValue() < -0.9999999999) || (v2.getX().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getZ(), v1.getY()), + v2.getX().acos(), + DerivativeStructure.atan2(v2.getZ(), v2.getY().negate()) + }; + + } else if (order == RotationOrder.YXY) { + + // r (+J) coordinates are : + // sin (theta1) sin (phi), cos (phi), cos (theta1) sin (phi) + // (-r) (+J) coordinates are : + // sin (phi) sin (theta2), cos (phi), -sin (phi) cos (theta2) + // and we can choose to have phi in the interval [0 ; PI] + final Vector3DDS v1 = applyTo(vector(0, 1, 0)); + final Vector3DDS v2 = applyInverseTo(vector(0, 1, 0)); + if ((v2.getY().getValue() < -0.9999999999) || (v2.getY().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getX(), v1.getZ()), + v2.getY().acos(), + DerivativeStructure.atan2(v2.getX(), v2.getZ().negate()) + }; + + } else if (order == RotationOrder.YZY) { + + // r (+J) coordinates are : + // -cos (theta1) sin (psi), cos (psi), sin (theta1) sin (psi) + // (-r) (+J) coordinates are : + // sin (psi) cos (theta2), cos (psi), sin (psi) sin (theta2) + // and we can choose to have psi in the interval [0 ; PI] + final Vector3DDS v1 = applyTo(vector(0, 1, 0)); + final Vector3DDS v2 = applyInverseTo(vector(0, 1, 0)); + if ((v2.getY().getValue() < -0.9999999999) || (v2.getY().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getZ(), v1.getX().negate()), + v2.getY().acos(), + DerivativeStructure.atan2(v2.getZ(), v2.getX()) + }; + + } else if (order == RotationOrder.ZXZ) { + + // r (+K) coordinates are : + // sin (psi1) sin (phi), -cos (psi1) sin (phi), cos (phi) + // (-r) (+K) coordinates are : + // sin (phi) sin (psi2), sin (phi) cos (psi2), cos (phi) + // and we can choose to have phi in the interval [0 ; PI] + final Vector3DDS v1 = applyTo(vector(0, 0, 1)); + final Vector3DDS v2 = applyInverseTo(vector(0, 0, 1)); + if ((v2.getZ().getValue() < -0.9999999999) || (v2.getZ().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getX(), v1.getY().negate()), + v2.getZ().acos(), + DerivativeStructure.atan2(v2.getX(), v2.getY()) + }; + + } else { // last possibility is ZYZ + + // r (+K) coordinates are : + // cos (psi1) sin (theta), sin (psi1) sin (theta), cos (theta) + // (-r) (+K) coordinates are : + // -sin (theta) cos (psi2), sin (theta) sin (psi2), cos (theta) + // and we can choose to have theta in the interval [0 ; PI] + final Vector3DDS v1 = applyTo(vector(0, 0, 1)); + final Vector3DDS v2 = applyInverseTo(vector(0, 0, 1)); + if ((v2.getZ().getValue() < -0.9999999999) || (v2.getZ().getValue() > 0.9999999999)) { + throw new CardanEulerSingularityException(false); + } + return new DerivativeStructure[] { + DerivativeStructure.atan2(v1.getY(), v1.getX()), + v2.getZ().acos(), + DerivativeStructure.atan2(v2.getY(), v2.getX().negate()) + }; + + } + + } + + /** Create a constant vector with appropriate derivation parameters. + * @param x abscissa + * @param y ordinate + * @param z height + * @return a constant vector + */ + private Vector3DDS vector(final double x, final double y, final double z) { + final int parameters = q0.getFreeParameters(); + final int order = q0.getOrder(); + return new Vector3DDS(new DerivativeStructure(parameters, order, x), + new DerivativeStructure(parameters, order, y), + new DerivativeStructure(parameters, order, z)); + } + + /** Get the 3X3 matrix corresponding to the instance + * @return the matrix corresponding to the instance + */ + public DerivativeStructure[][] getMatrix() { + + // products + final DerivativeStructure q0q0 = q0.multiply(q0); + final DerivativeStructure q0q1 = q0.multiply(q1); + final DerivativeStructure q0q2 = q0.multiply(q2); + final DerivativeStructure q0q3 = q0.multiply(q3); + final DerivativeStructure q1q1 = q1.multiply(q1); + final DerivativeStructure q1q2 = q1.multiply(q2); + final DerivativeStructure q1q3 = q1.multiply(q3); + final DerivativeStructure q2q2 = q2.multiply(q2); + final DerivativeStructure q2q3 = q2.multiply(q3); + final DerivativeStructure q3q3 = q3.multiply(q3); + + // create the matrix + final DerivativeStructure[][] m = new DerivativeStructure[3][]; + m[0] = new DerivativeStructure[3]; + m[1] = new DerivativeStructure[3]; + m[2] = new DerivativeStructure[3]; + + m [0][0] = q0q0.add(q1q1).multiply(2).subtract(1); + m [1][0] = q1q2.subtract(q0q3).multiply(2); + m [2][0] = q1q3.add(q0q2).multiply(2); + + m [0][1] = q1q2.add(q0q3).multiply(2); + m [1][1] = q0q0.add(q2q2).multiply(2).subtract(1); + m [2][1] = q2q3.subtract(q0q1).multiply(2); + + m [0][2] = q1q3.subtract(q0q2).multiply(2); + m [1][2] = q2q3.add(q0q1).multiply(2); + m [2][2] = q0q0.add(q3q3).multiply(2).subtract(1); + + return m; + + } + + /** Apply the rotation to a vector. + * @param u vector to apply the rotation to + * @return a new vector which is the image of u by the rotation + */ + public Vector3DDS applyTo(final Vector3DDS u) { + + final DerivativeStructure x = u.getX(); + final DerivativeStructure y = u.getY(); + final DerivativeStructure z = u.getZ(); + + final DerivativeStructure s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + + return new Vector3DDS(q0.multiply(x.multiply(q0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x), + q0.multiply(y.multiply(q0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y), + q0.multiply(z.multiply(q0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z)); + + } + + /** Apply the rotation to a vector. + * @param u vector to apply the rotation to + * @return a new vector which is the image of u by the rotation + */ + public Vector3DDS applyTo(final Vector3D u) { + + final double x = u.getX(); + final double y = u.getY(); + final double z = u.getZ(); + + final DerivativeStructure s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + + return new Vector3DDS(q0.multiply(q0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x), + q0.multiply(q0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y), + q0.multiply(q0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z)); + + } + + /** Apply the rotation to a vector stored in an array. + * @param in an array with three items which stores vector to rotate + * @param out an array with three items to put result to (it can be the same + * array as in) + */ + public void applyTo(final DerivativeStructure[] in, final DerivativeStructure[] out) { + + final DerivativeStructure x = in[0]; + final DerivativeStructure y = in[1]; + final DerivativeStructure z = in[2]; + + final DerivativeStructure s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + + out[0] = q0.multiply(x.multiply(q0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x); + out[1] = q0.multiply(y.multiply(q0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y); + out[2] = q0.multiply(z.multiply(q0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z); + + } + + /** Apply the rotation to a vector stored in an array. + * @param in an array with three items which stores vector to rotate + * @param out an array with three items to put result to + */ + public void applyTo(final double[] in, final DerivativeStructure[] out) { + + final double x = in[0]; + final double y = in[1]; + final double z = in[2]; + + final DerivativeStructure s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + + out[0] = q0.multiply(q0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x); + out[1] = q0.multiply(q0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y); + out[2] = q0.multiply(q0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z); + + } + + /** Apply a rotation to a vector. + * @param r rotation to apply + * @param u vector to apply the rotation to + * @return a new vector which is the image of u by the rotation + */ + public static Vector3DDS applyTo(final Rotation r, final Vector3DDS u) { + + final DerivativeStructure x = u.getX(); + final DerivativeStructure y = u.getY(); + final DerivativeStructure z = u.getZ(); + + final DerivativeStructure s = x.multiply(r.getQ1()).add(y.multiply(r.getQ2())).add(z.multiply(r.getQ3())); + + return new Vector3DDS(x.multiply(r.getQ0()).subtract(z.multiply(r.getQ2()).subtract(y.multiply(r.getQ3()))).multiply(r.getQ0()).add(s.multiply(r.getQ1())).multiply(2).subtract(x), + y.multiply(r.getQ0()).subtract(x.multiply(r.getQ3()).subtract(z.multiply(r.getQ1()))).multiply(r.getQ0()).add(s.multiply(r.getQ2())).multiply(2).subtract(y), + z.multiply(r.getQ0()).subtract(y.multiply(r.getQ1()).subtract(x.multiply(r.getQ2()))).multiply(r.getQ0()).add(s.multiply(r.getQ3())).multiply(2).subtract(z)); + + } + + /** Apply the inverse of the rotation to a vector. + * @param u vector to apply the inverse of the rotation to + * @return a new vector which such that u is its image by the rotation + */ + public Vector3DDS applyInverseTo(final Vector3DDS u) { + + final DerivativeStructure x = u.getX(); + final DerivativeStructure y = u.getY(); + final DerivativeStructure z = u.getZ(); + + final DerivativeStructure s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + final DerivativeStructure m0 = q0.negate(); + + return new Vector3DDS(m0.multiply(x.multiply(m0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x), + m0.multiply(y.multiply(m0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y), + m0.multiply(z.multiply(m0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z)); + + } + + /** Apply the inverse of the rotation to a vector. + * @param u vector to apply the inverse of the rotation to + * @return a new vector which such that u is its image by the rotation + */ + public Vector3DDS applyInverseTo(final Vector3D u) { + + final double x = u.getX(); + final double y = u.getY(); + final double z = u.getZ(); + + final DerivativeStructure s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + final DerivativeStructure m0 = q0.negate(); + + return new Vector3DDS(m0.multiply(m0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x), + m0.multiply(m0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y), + m0.multiply(m0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z)); + + } + + /** Apply the inverse of the rotation to a vector stored in an array. + * @param in an array with three items which stores vector to rotate + * @param out an array with three items to put result to (it can be the same + * array as in) + */ + public void applyInverseTo(final DerivativeStructure[] in, final DerivativeStructure[] out) { + + final DerivativeStructure x = in[0]; + final DerivativeStructure y = in[1]; + final DerivativeStructure z = in[2]; + + final DerivativeStructure s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + final DerivativeStructure m0 = q0.negate(); + + out[0] = m0.multiply(x.multiply(m0).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x); + out[1] = m0.multiply(y.multiply(m0).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y); + out[2] = m0.multiply(z.multiply(m0).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z); + + } + + /** Apply the inverse of the rotation to a vector stored in an array. + * @param in an array with three items which stores vector to rotate + * @param out an array with three items to put result to + */ + public void applyInverseTo(final double[] in, final DerivativeStructure[] out) { + + final double x = in[0]; + final double y = in[1]; + final double z = in[2]; + + final DerivativeStructure s = q1.multiply(x).add(q2.multiply(y)).add(q3.multiply(z)); + final DerivativeStructure m0 = q0.negate(); + + out[0] = m0.multiply(m0.multiply(x).subtract(q2.multiply(z).subtract(q3.multiply(y)))).add(s.multiply(q1)).multiply(2).subtract(x); + out[1] = m0.multiply(m0.multiply(y).subtract(q3.multiply(x).subtract(q1.multiply(z)))).add(s.multiply(q2)).multiply(2).subtract(y); + out[2] = m0.multiply(m0.multiply(z).subtract(q1.multiply(y).subtract(q2.multiply(x)))).add(s.multiply(q3)).multiply(2).subtract(z); + + } + + /** Apply the inverse of a rotation to a vector. + * @param r rotation to apply + * @param u vector to apply the inverse of the rotation to + * @return a new vector which such that u is its image by the rotation + */ + public static Vector3DDS applyInverseTo(final Rotation r, final Vector3DDS u) { + + final DerivativeStructure x = u.getX(); + final DerivativeStructure y = u.getY(); + final DerivativeStructure z = u.getZ(); + + final DerivativeStructure s = x.multiply(r.getQ1()).add(y.multiply(r.getQ2())).add(z.multiply(r.getQ3())); + final double m0 = -r.getQ0(); + + return new Vector3DDS(x.multiply(m0).subtract(z.multiply(r.getQ2()).subtract(y.multiply(r.getQ3()))).multiply(m0).add(s.multiply(r.getQ1())).multiply(2).subtract(x), + y.multiply(m0).subtract(x.multiply(r.getQ3()).subtract(z.multiply(r.getQ1()))).multiply(m0).add(s.multiply(r.getQ2())).multiply(2).subtract(y), + z.multiply(m0).subtract(y.multiply(r.getQ1()).subtract(x.multiply(r.getQ2()))).multiply(m0).add(s.multiply(r.getQ3())).multiply(2).subtract(z)); + + } + + /** Apply the instance to another rotation. + * Applying the instance to a rotation is computing the composition + * in an order compliant with the following rule : let u be any + * vector and v its image by r (i.e. r.applyTo(u) = v), let w be the image + * of v by the instance (i.e. applyTo(v) = w), then w = comp.applyTo(u), + * where comp = applyTo(r). + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the instance + */ + public RotationDS applyTo(final RotationDS r) { + return new RotationDS(r.q0.multiply(q0).subtract(r.q1.multiply(q1).add(r.q2.multiply(q2)).add(r.q3.multiply(q3))), + r.q1.multiply(q0).add(r.q0.multiply(q1)).add(r.q2.multiply(q3).subtract(r.q3.multiply(q2))), + r.q2.multiply(q0).add(r.q0.multiply(q2)).add(r.q3.multiply(q1).subtract(r.q1.multiply(q3))), + r.q3.multiply(q0).add(r.q0.multiply(q3)).add(r.q1.multiply(q2).subtract(r.q2.multiply(q1))), + false); + } + + /** Apply the instance to another rotation. + * Applying the instance to a rotation is computing the composition + * in an order compliant with the following rule : let u be any + * vector and v its image by r (i.e. r.applyTo(u) = v), let w be the image + * of v by the instance (i.e. applyTo(v) = w), then w = comp.applyTo(u), + * where comp = applyTo(r). + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the instance + */ + public RotationDS applyTo(final Rotation r) { + return new RotationDS(q0.multiply(r.getQ0()).subtract(q1.multiply(r.getQ1()).add(q2.multiply(r.getQ2())).add(q3.multiply(r.getQ3()))), + q0.multiply(r.getQ1()).add(q1.multiply(r.getQ0())).add(q3.multiply(r.getQ2()).subtract(q2.multiply(r.getQ3()))), + q0.multiply(r.getQ2()).add(q2.multiply(r.getQ0())).add(q1.multiply(r.getQ3()).subtract(q3.multiply(r.getQ1()))), + q0.multiply(r.getQ3()).add(q3.multiply(r.getQ0())).add(q2.multiply(r.getQ1()).subtract(q1.multiply(r.getQ2()))), + false); + } + + /** Apply a rotation to another rotation. + * Applying a rotation to another rotation is computing the composition + * in an order compliant with the following rule : let u be any + * vector and v its image by rInner (i.e. rInner.applyTo(u) = v), let w be the image + * of v by rOuter (i.e. rOuter.applyTo(v) = w), then w = comp.applyTo(u), + * where comp = applyTo(rOuter, rInner). + * @param r1 rotation to apply + * @param rInner rotation to apply the rotation to + * @return a new rotation which is the composition of r by the instance + */ + public static RotationDS applyTo(final Rotation r1, final RotationDS rInner) { + return new RotationDS(rInner.q0.multiply(r1.getQ0()).subtract(rInner.q1.multiply(r1.getQ1()).add(rInner.q2.multiply(r1.getQ2())).add(rInner.q3.multiply(r1.getQ3()))), + rInner.q1.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ1())).add(rInner.q2.multiply(r1.getQ3()).subtract(rInner.q3.multiply(r1.getQ2()))), + rInner.q2.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ2())).add(rInner.q3.multiply(r1.getQ1()).subtract(rInner.q1.multiply(r1.getQ3()))), + rInner.q3.multiply(r1.getQ0()).add(rInner.q0.multiply(r1.getQ3())).add(rInner.q1.multiply(r1.getQ2()).subtract(rInner.q2.multiply(r1.getQ1()))), + false); + } + + /** Apply the inverse of the instance to another rotation. + * Applying the inverse of the instance to a rotation is computing + * the composition in an order compliant with the following rule : + * let u be any vector and v its image by r (i.e. r.applyTo(u) = v), + * let w be the inverse image of v by the instance + * (i.e. applyInverseTo(v) = w), then w = comp.applyTo(u), where + * comp = applyInverseTo(r). + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public RotationDS applyInverseTo(final RotationDS r) { + return new RotationDS(r.q0.multiply(q0).add(r.q1.multiply(q1).add(r.q2.multiply(q2)).add(r.q3.multiply(q3))).negate(), + r.q0.multiply(q1).add(r.q2.multiply(q3).subtract(r.q3.multiply(q2))).subtract(r.q1.multiply(q0)), + r.q0.multiply(q2).add(r.q3.multiply(q1).subtract(r.q1.multiply(q3))).subtract(r.q2.multiply(q0)), + r.q0.multiply(q3).add(r.q1.multiply(q2).subtract(r.q2.multiply(q1))).subtract(r.q3.multiply(q0)), + false); + } + + /** Apply the inverse of the instance to another rotation. + * Applying the inverse of the instance to a rotation is computing + * the composition in an order compliant with the following rule : + * let u be any vector and v its image by r (i.e. r.applyTo(u) = v), + * let w be the inverse image of v by the instance + * (i.e. applyInverseTo(v) = w), then w = comp.applyTo(u), where + * comp = applyInverseTo(r). + * @param r rotation to apply the rotation to + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public RotationDS applyInverseTo(final Rotation r) { + return new RotationDS(q0.multiply(r.getQ0()).add(q1.multiply(r.getQ1()).add(q2.multiply(r.getQ2())).add(q3.multiply(r.getQ3()))).negate(), + q1.multiply(r.getQ0()).add(q3.multiply(r.getQ2()).subtract(q2.multiply(r.getQ3()))).subtract(q0.multiply(r.getQ1())), + q2.multiply(r.getQ0()).add(q1.multiply(r.getQ3()).subtract(q3.multiply(r.getQ1()))).subtract(q0.multiply(r.getQ2())), + q3.multiply(r.getQ0()).add(q2.multiply(r.getQ1()).subtract(q1.multiply(r.getQ2()))).subtract(q0.multiply(r.getQ3())), + false); + } + + /** Apply the inverse of a rotation to another rotation. + * Applying the inverse of a rotation to another rotation is computing + * the composition in an order compliant with the following rule : + * let u be any vector and v its image by rInner (i.e. rInner.applyTo(u) = v), + * let w be the inverse image of v by rOuter + * (i.e. rOuter.applyInverseTo(v) = w), then w = comp.applyTo(u), where + * comp = applyInverseTo(rOuter, rInner). + * @param rOuter rotation to apply the rotation to + * @param rInner rotation to apply the rotation to + * @return a new rotation which is the composition of r by the inverse + * of the instance + */ + public static RotationDS applyInverseTo(final Rotation rOuter, final RotationDS rInner) { + return new RotationDS(rInner.q0.multiply(rOuter.getQ0()).add(rInner.q1.multiply(rOuter.getQ1()).add(rInner.q2.multiply(rOuter.getQ2())).add(rInner.q3.multiply(rOuter.getQ3()))).negate(), + rInner.q0.multiply(rOuter.getQ1()).add(rInner.q2.multiply(rOuter.getQ3()).subtract(rInner.q3.multiply(rOuter.getQ2()))).subtract(rInner.q1.multiply(rOuter.getQ0())), + rInner.q0.multiply(rOuter.getQ2()).add(rInner.q3.multiply(rOuter.getQ1()).subtract(rInner.q1.multiply(rOuter.getQ3()))).subtract(rInner.q2.multiply(rOuter.getQ0())), + rInner.q0.multiply(rOuter.getQ3()).add(rInner.q1.multiply(rOuter.getQ2()).subtract(rInner.q2.multiply(rOuter.getQ1()))).subtract(rInner.q3.multiply(rOuter.getQ0())), + false); + } + + /** Perfect orthogonality on a 3X3 matrix. + * @param m initial matrix (not exactly orthogonal) + * @param threshold convergence threshold for the iterative + * orthogonality correction (convergence is reached when the + * difference between two steps of the Frobenius norm of the + * correction is below this threshold) + * @return an orthogonal matrix close to m + * @exception NotARotationMatrixException if the matrix cannot be + * orthogonalized with the given threshold after 10 iterations + */ + private DerivativeStructure[][] orthogonalizeMatrix(final DerivativeStructure[][] m, + final double threshold) + throws NotARotationMatrixException { + + DerivativeStructure x00 = m[0][0]; + DerivativeStructure x01 = m[0][1]; + DerivativeStructure x02 = m[0][2]; + DerivativeStructure x10 = m[1][0]; + DerivativeStructure x11 = m[1][1]; + DerivativeStructure x12 = m[1][2]; + DerivativeStructure x20 = m[2][0]; + DerivativeStructure x21 = m[2][1]; + DerivativeStructure x22 = m[2][2]; + double fn = 0; + double fn1; + + final DerivativeStructure[][] o = new DerivativeStructure[3][3]; + + // iterative correction: Xn+1 = Xn - 0.5 * (Xn.Mt.Xn - M) + int i = 0; + while (++i < 11) { + + // Mt.Xn + final DerivativeStructure mx00 = m[0][0].multiply(x00).add(m[1][0].multiply(x10)).add(m[2][0].multiply(x20)); + final DerivativeStructure mx10 = m[0][1].multiply(x00).add(m[1][1].multiply(x10)).add(m[2][1].multiply(x20)); + final DerivativeStructure mx20 = m[0][2].multiply(x00).add(m[1][2].multiply(x10)).add(m[2][2].multiply(x20)); + final DerivativeStructure mx01 = m[0][0].multiply(x01).add(m[1][0].multiply(x11)).add(m[2][0].multiply(x21)); + final DerivativeStructure mx11 = m[0][1].multiply(x01).add(m[1][1].multiply(x11)).add(m[2][1].multiply(x21)); + final DerivativeStructure mx21 = m[0][2].multiply(x01).add(m[1][2].multiply(x11)).add(m[2][2].multiply(x21)); + final DerivativeStructure mx02 = m[0][0].multiply(x02).add(m[1][0].multiply(x12)).add(m[2][0].multiply(x22)); + final DerivativeStructure mx12 = m[0][1].multiply(x02).add(m[1][1].multiply(x12)).add(m[2][1].multiply(x22)); + final DerivativeStructure mx22 = m[0][2].multiply(x02).add(m[1][2].multiply(x12)).add(m[2][2].multiply(x22)); + + // Xn+1 + o[0][0] = x00.subtract(x00.multiply(mx00).add(x01.multiply(mx10)).add(x02.multiply(mx20)).subtract(m[0][0]).multiply(0.5)); + o[0][1] = x01.subtract(x00.multiply(mx01).add(x01.multiply(mx11)).add(x02.multiply(mx21)).subtract(m[0][1]).multiply(0.5)); + o[0][2] = x02.subtract(x00.multiply(mx02).add(x01.multiply(mx12)).add(x02.multiply(mx22)).subtract(m[0][2]).multiply(0.5)); + o[1][0] = x10.subtract(x10.multiply(mx00).add(x11.multiply(mx10)).add(x12.multiply(mx20)).subtract(m[1][0]).multiply(0.5)); + o[1][1] = x11.subtract(x10.multiply(mx01).add(x11.multiply(mx11)).add(x12.multiply(mx21)).subtract(m[1][1]).multiply(0.5)); + o[1][2] = x12.subtract(x10.multiply(mx02).add(x11.multiply(mx12)).add(x12.multiply(mx22)).subtract(m[1][2]).multiply(0.5)); + o[2][0] = x20.subtract(x20.multiply(mx00).add(x21.multiply(mx10)).add(x22.multiply(mx20)).subtract(m[2][0]).multiply(0.5)); + o[2][1] = x21.subtract(x20.multiply(mx01).add(x21.multiply(mx11)).add(x22.multiply(mx21)).subtract(m[2][1]).multiply(0.5)); + o[2][2] = x22.subtract(x20.multiply(mx02).add(x21.multiply(mx12)).add(x22.multiply(mx22)).subtract(m[2][2]).multiply(0.5)); + + // correction on each elements + final double corr00 = o[0][0].getValue() - m[0][0].getValue(); + final double corr01 = o[0][1].getValue() - m[0][1].getValue(); + final double corr02 = o[0][2].getValue() - m[0][2].getValue(); + final double corr10 = o[1][0].getValue() - m[1][0].getValue(); + final double corr11 = o[1][1].getValue() - m[1][1].getValue(); + final double corr12 = o[1][2].getValue() - m[1][2].getValue(); + final double corr20 = o[2][0].getValue() - m[2][0].getValue(); + final double corr21 = o[2][1].getValue() - m[2][1].getValue(); + final double corr22 = o[2][2].getValue() - m[2][2].getValue(); + + // Frobenius norm of the correction + fn1 = corr00 * corr00 + corr01 * corr01 + corr02 * corr02 + + corr10 * corr10 + corr11 * corr11 + corr12 * corr12 + + corr20 * corr20 + corr21 * corr21 + corr22 * corr22; + + // convergence test + if (FastMath.abs(fn1 - fn) <= threshold) { + return o; + } + + // prepare next iteration + x00 = o[0][0]; + x01 = o[0][1]; + x02 = o[0][2]; + x10 = o[1][0]; + x11 = o[1][1]; + x12 = o[1][2]; + x20 = o[2][0]; + x21 = o[2][1]; + x22 = o[2][2]; + fn = fn1; + + } + + // the algorithm did not converge after 10 iterations + throw new NotARotationMatrixException(LocalizedFormats.UNABLE_TO_ORTHOGONOLIZE_MATRIX, + i - 1); + + } + + /** Compute the distance between two rotations. + *

The distance is intended here as a way to check if two + * rotations are almost similar (i.e. they transform vectors the same way) + * or very different. It is mathematically defined as the angle of + * the rotation r that prepended to one of the rotations gives the other + * one:

+ *
+     *        r1(r) = r2
+     * 
+ *

This distance is an angle between 0 and π. Its value is the smallest + * possible upper bound of the angle in radians between r1(v) + * and r2(v) for all possible vectors v. This upper bound is + * reached for some v. The distance is equal to 0 if and only if the two + * rotations are identical.

+ *

Comparing two rotations should always be done using this value rather + * than for example comparing the components of the quaternions. It is much + * more stable, and has a geometric meaning. Also comparing quaternions + * components is error prone since for example quaternions (0.36, 0.48, -0.48, -0.64) + * and (-0.36, -0.48, 0.48, 0.64) represent exactly the same rotation despite + * their components are different (they are exact opposites).

+ * @param r1 first rotation + * @param r2 second rotation + * @return distance between r1 and r2 + */ + public static DerivativeStructure distance(final RotationDS r1, final RotationDS r2) { + return r1.applyInverseTo(r2).getAngle(); + } + +} diff --git a/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DDS.java b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DDS.java new file mode 100644 index 000000000..31e392ebb --- /dev/null +++ b/src/main/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DDS.java @@ -0,0 +1,1087 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.geometry.euclidean.threed; + +import java.io.Serializable; +import java.text.NumberFormat; + +import org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MathArithmeticException; +import org.apache.commons.math3.exception.util.LocalizedFormats; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathArrays; + +/** + * This class is a re-implementation of {@link Vector3D} using {@link DerivativeStructure}. + *

Instance of this class are guaranteed to be immutable.

+ * @version $Id$ + * @since 3.2 + */ +public class Vector3DDS implements Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 20130214L; + + /** Abscissa. */ + private final DerivativeStructure x; + + /** Ordinate. */ + private final DerivativeStructure y; + + /** Height. */ + private final DerivativeStructure z; + + /** Simple constructor. + * Build a vector from its coordinates + * @param x abscissa + * @param y ordinate + * @param z height + * @see #getX() + * @see #getY() + * @see #getZ() + */ + public Vector3DDS(final DerivativeStructure x, + final DerivativeStructure y, + final DerivativeStructure z) { + this.x = x; + this.y = y; + this.z = z; + } + + /** Simple constructor. + * Build a vector from its coordinates + * @param v coordinates array + * @exception DimensionMismatchException if array does not have 3 elements + * @see #toArray() + */ + public Vector3DDS(final DerivativeStructure[] v) throws DimensionMismatchException { + if (v.length != 3) { + throw new DimensionMismatchException(v.length, 3); + } + this.x = v[0]; + this.y = v[1]; + this.z = v[2]; + } + + /** Simple constructor. + * Build a vector from its azimuthal coordinates + * @param alpha azimuth (α) around Z + * (0 is +X, π/2 is +Y, π is -X and 3π/2 is -Y) + * @param delta elevation (δ) above (XY) plane, from -π/2 to +π/2 + * @see #getAlpha() + * @see #getDelta() + */ + public Vector3DDS(final DerivativeStructure alpha, final DerivativeStructure delta) { + DerivativeStructure cosDelta = delta.cos(); + this.x = alpha.cos().multiply(cosDelta); + this.y = alpha.sin().multiply(cosDelta); + this.z = delta.sin(); + } + + /** Multiplicative constructor + * Build a vector from another one and a scale factor. + * The vector built will be a * u + * @param a scale factor + * @param u base (unscaled) vector + */ + public Vector3DDS(final DerivativeStructure a, final Vector3DDS u) { + this.x = a.multiply(u.x); + this.y = a.multiply(u.y); + this.z = a.multiply(u.z); + } + + /** Multiplicative constructor + * Build a vector from another one and a scale factor. + * The vector built will be a * u + * @param a scale factor + * @param u base (unscaled) vector + */ + public Vector3DDS(final DerivativeStructure a, final Vector3D u) { + this.x = a.multiply(u.getX()); + this.y = a.multiply(u.getY()); + this.z = a.multiply(u.getZ()); + } + + /** Multiplicative constructor + * Build a vector from another one and a scale factor. + * The vector built will be a * u + * @param a scale factor + * @param u base (unscaled) vector + */ + public Vector3DDS(final double a, final Vector3DDS u) { + this.x = u.x.multiply(a); + this.y = u.y.multiply(a); + this.z = u.z.multiply(a); + } + + /** Linear constructor + * Build a vector from two other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + */ + public Vector3DDS(final DerivativeStructure a1, final Vector3DDS u1, + final DerivativeStructure a2, final Vector3DDS u2) { + this.x = a1.multiply(u1.x).add(a2.multiply(u2.x)); + this.y = a1.multiply(u1.y).add(a2.multiply(u2.y)); + this.z = a1.multiply(u1.z).add(a2.multiply(u2.z)); + } + + /** Linear constructor + * Build a vector from two other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + */ + public Vector3DDS(final DerivativeStructure a1, final Vector3D u1, + final DerivativeStructure a2, final Vector3D u2) { + this.x = a1.multiply(u1.getX()).add(a2.multiply(u2.getX())); + this.y = a1.multiply(u1.getY()).add(a2.multiply(u2.getY())); + this.z = a1.multiply(u1.getZ()).add(a2.multiply(u2.getZ())); + } + + /** Linear constructor + * Build a vector from two other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + */ + public Vector3DDS(final double a1, final Vector3DDS u1, + final double a2, final Vector3DDS u2) { + this.x = u1.x.multiply(a1).add(u2.x.multiply(a2)); + this.y = u1.y.multiply(a1).add(u2.y.multiply(a2)); + this.z = u1.z.multiply(a1).add(u2.z.multiply(a2)); + } + + /** Linear constructor + * Build a vector from three other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + */ + public Vector3DDS(final DerivativeStructure a1, final Vector3DDS u1, + final DerivativeStructure a2, final Vector3DDS u2, + final DerivativeStructure a3, final Vector3DDS u3) { + this.x = a1.multiply(u1.x).add(a2.multiply(u2.x)).add(a3.multiply(u3.x)); + this.y = a1.multiply(u1.y).add(a2.multiply(u2.y)).add(a3.multiply(u3.y)); + this.z = a1.multiply(u1.z).add(a2.multiply(u2.z)).add(a3.multiply(u3.z)); + } + + /** Linear constructor + * Build a vector from three other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + */ + public Vector3DDS(final DerivativeStructure a1, final Vector3D u1, + final DerivativeStructure a2, final Vector3D u2, + final DerivativeStructure a3, final Vector3D u3) { + this.x = a1.multiply(u1.getX()).add(a2.multiply(u2.getX())).add(a3.multiply(u3.getX())); + this.y = a1.multiply(u1.getY()).add(a2.multiply(u2.getY())).add(a3.multiply(u3.getY())); + this.z = a1.multiply(u1.getZ()).add(a2.multiply(u2.getZ())).add(a3.multiply(u3.getZ())); + } + + /** Linear constructor + * Build a vector from three other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + */ + public Vector3DDS(final double a1, final Vector3DDS u1, + final double a2, final Vector3DDS u2, + final double a3, final Vector3DDS u3) { + this.x = u1.x.multiply(a1).add(u2.x.multiply(a2)).add(u3.x.multiply(a3)); + this.y = u1.y.multiply(a1).add(u2.y.multiply(a2)).add(u3.y.multiply(a3)); + this.z = u1.z.multiply(a1).add(u2.z.multiply(a2)).add(u3.z.multiply(a3)); + } + + /** Linear constructor + * Build a vector from four other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + * @param a4 fourth scale factor + * @param u4 fourth base (unscaled) vector + */ + public Vector3DDS(final DerivativeStructure a1, final Vector3DDS u1, + final DerivativeStructure a2, final Vector3DDS u2, + final DerivativeStructure a3, final Vector3DDS u3, + final DerivativeStructure a4, final Vector3DDS u4) { + this.x = a1.multiply(u1.x).add(a2.multiply(u2.x)).add(a3.multiply(u3.x)).add(a4.multiply(u4.x)); + this.y = a1.multiply(u1.y).add(a2.multiply(u2.y)).add(a3.multiply(u3.y)).add(a4.multiply(u4.y)); + this.z = a1.multiply(u1.z).add(a2.multiply(u2.z)).add(a3.multiply(u3.z)).add(a4.multiply(u4.z)); + } + + /** Linear constructor + * Build a vector from four other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + * @param a4 fourth scale factor + * @param u4 fourth base (unscaled) vector + */ + public Vector3DDS(final DerivativeStructure a1, final Vector3D u1, + final DerivativeStructure a2, final Vector3D u2, + final DerivativeStructure a3, final Vector3D u3, + final DerivativeStructure a4, final Vector3D u4) { + this.x = a1.multiply(u1.getX()).add(a2.multiply(u2.getX())).add(a3.multiply(u3.getX())).add(a4.multiply(u4.getX())); + this.y = a1.multiply(u1.getY()).add(a2.multiply(u2.getY())).add(a3.multiply(u3.getY())).add(a4.multiply(u4.getY())); + this.z = a1.multiply(u1.getZ()).add(a2.multiply(u2.getZ())).add(a3.multiply(u3.getZ())).add(a4.multiply(u4.getZ())); + } + + /** Linear constructor + * Build a vector from four other ones and corresponding scale factors. + * The vector built will be a1 * u1 + a2 * u2 + a3 * u3 + a4 * u4 + * @param a1 first scale factor + * @param u1 first base (unscaled) vector + * @param a2 second scale factor + * @param u2 second base (unscaled) vector + * @param a3 third scale factor + * @param u3 third base (unscaled) vector + * @param a4 fourth scale factor + * @param u4 fourth base (unscaled) vector + */ + public Vector3DDS(final double a1, final Vector3DDS u1, + final double a2, final Vector3DDS u2, + final double a3, final Vector3DDS u3, + final double a4, final Vector3DDS u4) { + this.x = u1.x.multiply(a1).add(u2.x.multiply(a2)).add(u3.x.multiply(a3)).add(u4.x.multiply(a4)); + this.y = u1.y.multiply(a1).add(u2.y.multiply(a2)).add(u3.y.multiply(a3)).add(u4.y.multiply(a4)); + this.z = u1.z.multiply(a1).add(u2.z.multiply(a2)).add(u3.z.multiply(a3)).add(u4.z.multiply(a4)); + } + + /** Get the abscissa of the vector. + * @return abscissa of the vector + * @see #Vector3D(DerivativeStructure, DerivativeStructure, DerivativeStructure) + */ + public DerivativeStructure getX() { + return x; + } + + /** Get the ordinate of the vector. + * @return ordinate of the vector + * @see #Vector3D(DerivativeStructure, DerivativeStructure, DerivativeStructure) + */ + public DerivativeStructure getY() { + return y; + } + + /** Get the height of the vector. + * @return height of the vector + * @see #Vector3D(DerivativeStructure, DerivativeStructure, DerivativeStructure) + */ + public DerivativeStructure getZ() { + return z; + } + + /** Get the vector coordinates as a dimension 3 array. + * @return vector coordinates + * @see #Vector3D(DerivativeStructure[]) + */ + public DerivativeStructure[] toArray() { + return new DerivativeStructure[] { x, y, z }; + } + + /** Convert to a constant vector without derivatives. + * @return a constant vector + */ + public Vector3D toVector3D() { + return new Vector3D(x.getValue(), y.getValue(), z.getValue()); + } + + /** Get the L1 norm for the vector. + * @return L1 norm for the vector + */ + public DerivativeStructure getNorm1() { + return x.abs().add(y.abs()).add(z.abs()); + } + + /** Get the L2 norm for the vector. + * @return Euclidean norm for the vector + */ + public DerivativeStructure getNorm() { + // there are no cancellation problems here, so we use the straightforward formula + return x.multiply(x).add(y.multiply(y)).add(z.multiply(z)).sqrt(); + } + + /** Get the square of the norm for the vector. + * @return square of the Euclidean norm for the vector + */ + public DerivativeStructure getNormSq() { + // there are no cancellation problems here, so we use the straightforward formula + return x.multiply(x).add(y.multiply(y)).add(z.multiply(z)); + } + + /** Get the L norm for the vector. + * @return L norm for the vector + */ + public DerivativeStructure getNormInf() { + final DerivativeStructure xAbs = x.abs(); + final DerivativeStructure yAbs = y.abs(); + final DerivativeStructure zAbs = z.abs(); + if (xAbs.getValue() <= yAbs.getValue()) { + if (yAbs.getValue() <= zAbs.getValue()) { + return zAbs; + } else { + return yAbs; + } + } else { + if (xAbs.getValue() <= zAbs.getValue()) { + return zAbs; + } else { + return xAbs; + } + } + } + + /** Get the azimuth of the vector. + * @return azimuth (α) of the vector, between -π and +π + * @see #Vector3D(DerivativeStructure, DerivativeStructure) + */ + public DerivativeStructure getAlpha() { + return DerivativeStructure.atan2(y, x); + } + + /** Get the elevation of the vector. + * @return elevation (δ) of the vector, between -π/2 and +π/2 + * @see #Vector3D(DerivativeStructure, DerivativeStructure) + */ + public DerivativeStructure getDelta() { + return z.divide(getNorm()).asin(); + } + + /** Add a vector to the instance. + * @param v vector to add + * @return a new vector + */ + public Vector3DDS add(final Vector3DDS v) { + return new Vector3DDS(x.add(v.x), y.add(v.y), z.add(v.z)); + } + + /** Add a vector to the instance. + * @param v vector to add + * @return a new vector + */ + public Vector3DDS add(final Vector3D v) { + return new Vector3DDS(x.add(v.getX()), y.add(v.getY()), z.add(v.getZ())); + } + + /** Add a scaled vector to the instance. + * @param factor scale factor to apply to v before adding it + * @param v vector to add + * @return a new vector + */ + public Vector3DDS add(final DerivativeStructure factor, final Vector3DDS v) { + return new Vector3DDS(x.add(factor.multiply(v.x)), + y.add(factor.multiply(v.y)), + z.add(factor.multiply(v.z))); + } + + /** Add a scaled vector to the instance. + * @param factor scale factor to apply to v before adding it + * @param v vector to add + * @return a new vector + */ + public Vector3DDS add(final DerivativeStructure factor, final Vector3D v) { + return new Vector3DDS(x.add(factor.multiply(v.getX())), + y.add(factor.multiply(v.getY())), + z.add(factor.multiply(v.getZ()))); + } + + /** Add a scaled vector to the instance. + * @param factor scale factor to apply to v before adding it + * @param v vector to add + * @return a new vector + */ + public Vector3DDS add(final double factor, final Vector3DDS v) { + return new Vector3DDS(x.add(v.x.multiply(factor)), + y.add(v.y.multiply(factor)), + z.add(v.z.multiply(factor))); + } + + /** Add a scaled vector to the instance. + * @param factor scale factor to apply to v before adding it + * @param v vector to add + * @return a new vector + */ + public Vector3DDS add(final double factor, final Vector3D v) { + return new Vector3DDS(x.add(factor * v.getX()), + y.add(factor * v.getY()), + z.add(factor * v.getZ())); + } + + /** Subtract a vector from the instance. + * @param v vector to subtract + * @return a new vector + */ + public Vector3DDS subtract(final Vector3DDS v) { + return new Vector3DDS(x.subtract(v.x), y.subtract(v.y), z.subtract(v.z)); + } + + /** Subtract a vector from the instance. + * @param v vector to subtract + * @return a new vector + */ + public Vector3DDS subtract(final Vector3D v) { + return new Vector3DDS(x.subtract(v.getX()), y.subtract(v.getY()), z.subtract(v.getZ())); + } + + /** Subtract a scaled vector from the instance. + * @param factor scale factor to apply to v before subtracting it + * @param v vector to subtract + * @return a new vector + */ + public Vector3DDS subtract(final DerivativeStructure factor, final Vector3DDS v) { + return new Vector3DDS(x.subtract(factor.multiply(v.x)), + y.subtract(factor.multiply(v.y)), + z.subtract(factor.multiply(v.z))); + } + + /** Subtract a scaled vector from the instance. + * @param factor scale factor to apply to v before subtracting it + * @param v vector to subtract + * @return a new vector + */ + public Vector3DDS subtract(final DerivativeStructure factor, final Vector3D v) { + return new Vector3DDS(x.subtract(factor.multiply(v.getX())), + y.subtract(factor.multiply(v.getY())), + z.subtract(factor.multiply(v.getZ()))); + } + + /** Subtract a scaled vector from the instance. + * @param factor scale factor to apply to v before subtracting it + * @param v vector to subtract + * @return a new vector + */ + public Vector3DDS subtract(final double factor, final Vector3DDS v) { + return new Vector3DDS(x.subtract(v.x.multiply(factor)), + y.subtract(v.y.multiply(factor)), + z.subtract(v.z.multiply(factor))); + } + + /** Subtract a scaled vector from the instance. + * @param factor scale factor to apply to v before subtracting it + * @param v vector to subtract + * @return a new vector + */ + public Vector3DDS subtract(final double factor, final Vector3D v) { + return new Vector3DDS(x.subtract(factor * v.getX()), + y.subtract(factor * v.getY()), + z.subtract(factor * v.getZ())); + } + + /** Get a normalized vector aligned with the instance. + * @return a new normalized vector + * @exception MathArithmeticException if the norm is zero + */ + public Vector3DDS normalize() throws MathArithmeticException { + final DerivativeStructure s = getNorm(); + if (s.getValue() == 0) { + throw new MathArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR); + } + return scalarMultiply(s.reciprocal()); + } + + /** Get a vector orthogonal to the instance. + *

There are an infinite number of normalized vectors orthogonal + * to the instance. This method picks up one of them almost + * arbitrarily. It is useful when one needs to compute a reference + * frame with one of the axes in a predefined direction. The + * following example shows how to build a frame having the k axis + * aligned with the known vector u : + *


+     *   Vector3D k = u.normalize();
+     *   Vector3D i = k.orthogonal();
+     *   Vector3D j = Vector3D.crossProduct(k, i);
+     * 

+ * @return a new normalized vector orthogonal to the instance + * @exception MathArithmeticException if the norm of the instance is null + */ + public Vector3DDS orthogonal() throws MathArithmeticException { + + final double threshold = 0.6 * getNorm().getValue(); + if (threshold == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + + if (FastMath.abs(x.getValue()) <= threshold) { + final DerivativeStructure inverse = y.multiply(y).add(z.multiply(z)).sqrt().reciprocal(); + return new Vector3DDS(inverse.getField().getZero(), inverse.multiply(z), inverse.multiply(y).negate()); + } else if (FastMath.abs(y.getValue()) <= threshold) { + final DerivativeStructure inverse = x.multiply(x).add(z.multiply(z)).sqrt().reciprocal(); + return new Vector3DDS(inverse.multiply(z).negate(), inverse.getField().getZero(), inverse.multiply(x)); + } else { + final DerivativeStructure inverse = x.multiply(x).add(y.multiply(y)).sqrt().reciprocal(); + return new Vector3DDS(inverse.multiply(y), inverse.multiply(x).negate(), inverse.getField().getZero()); + } + + } + + /** Compute the angular separation between two vectors. + *

This method computes the angular separation between two + * vectors using the dot product for well separated vectors and the + * cross product for almost aligned vectors. This allows to have a + * good accuracy in all cases, even for vectors very close to each + * other.

+ * @param v1 first vector + * @param v2 second vector + * @return angular separation between v1 and v2 + * @exception MathArithmeticException if either vector has a null norm + */ + public static DerivativeStructure angle(Vector3DDS v1, Vector3DDS v2) throws MathArithmeticException { + + final DerivativeStructure normProduct = v1.getNorm().multiply(v2.getNorm()); + if (normProduct.getValue() == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_NORM); + } + + final DerivativeStructure dot = v1.dotProduct(v2); + final double threshold = normProduct.getValue() * 0.9999; + if ((dot.getValue() < -threshold) || (dot.getValue() > threshold)) { + // the vectors are almost aligned, compute using the sine + Vector3DDS v3 = crossProduct(v1, v2); + if (dot.getValue() >= 0) { + return v3.getNorm().divide(normProduct).asin(); + } + return v3.getNorm().divide(normProduct).asin().subtract(FastMath.PI).negate(); + } + + // the vectors are sufficiently separated to use the cosine + return dot.divide(normProduct).acos(); + + } + + /** Get the opposite of the instance. + * @return a new vector which is opposite to the instance + */ + public Vector3DDS negate() { + return new Vector3DDS(x.negate(), y.negate(), z.negate()); + } + + /** Multiply the instance by a scalar. + * @param a scalar + * @return a new vector + */ + public Vector3DDS scalarMultiply(final DerivativeStructure a) { + return new Vector3DDS(x.multiply(a), y.multiply(a), z.multiply(a)); + } + + /** Multiply the instance by a scalar. + * @param a scalar + * @return a new vector + */ + public Vector3DDS scalarMultiply(final double a) { + return new Vector3DDS(x.multiply(a), y.multiply(a), z.multiply(a)); + } + + /** + * Returns true if any coordinate of this vector is NaN; false otherwise + * @return true if any coordinate of this vector is NaN; false otherwise + */ + public boolean isNaN() { + return Double.isNaN(x.getValue()) || Double.isNaN(y.getValue()) || Double.isNaN(z.getValue()); + } + + /** + * Returns true if any coordinate of this vector is infinite and none are NaN; + * false otherwise + * @return true if any coordinate of this vector is infinite and none are NaN; + * false otherwise + */ + public boolean isInfinite() { + return !isNaN() && (Double.isInfinite(x.getValue()) || Double.isInfinite(y.getValue()) || Double.isInfinite(z.getValue())); + } + + /** + * Test for the equality of two 3D vectors. + *

+ * If all coordinates of two 3D vectors are exactly the same, and none are + * DerivativeStructure.NaN, the two 3D vectors are considered to be equal. + *

+ *

+ * NaN coordinates are considered to affect globally the vector + * and be equals to each other - i.e, if either (or all) coordinates of the + * 3D vector are equal to DerivativeStructure.NaN, the 3D vector is equal to + * {@link #NaN}. + *

+ * + * @param other Object to test for equality to this + * @return true if two 3D vector objects are equal, false if + * object is null, not an instance of Vector3D, or + * not equal to this Vector3D instance + * + */ + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + + if (other instanceof Vector3DDS) { + final Vector3DDS rhs = (Vector3DDS)other; + if (rhs.isNaN()) { + return this.isNaN(); + } + + return MathArrays.equals(x.getAllDerivatives(), rhs.x.getAllDerivatives()) && + MathArrays.equals(y.getAllDerivatives(), rhs.y.getAllDerivatives()) && + MathArrays.equals(z.getAllDerivatives(), rhs.z.getAllDerivatives()); + + } + return false; + } + + /** + * Get a hashCode for the 3D vector. + *

+ * All NaN values have the same hash code.

+ * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + if (isNaN()) { + return 409; + } + return 311 * (107 * x.hashCode() + 83 * y.hashCode() + z.hashCode()); + } + + /** Compute the dot-product of the instance and another vector. + *

+ * The implementation uses specific multiplication and addition + * algorithms to preserve accuracy and reduce cancellation effects. + * It should be very accurate even for nearly orthogonal vectors. + *

+ * @see MathArrays#linearCombination(double, double, double, double, double, double) + * @param v second vector + * @return the dot product this.v + */ + public DerivativeStructure dotProduct(final Vector3DDS v) { + return MathArrays.linearCombination(x, v.x, y, v.y, z, v.z); + } + + /** Compute the dot-product of the instance and another vector. + *

+ * The implementation uses specific multiplication and addition + * algorithms to preserve accuracy and reduce cancellation effects. + * It should be very accurate even for nearly orthogonal vectors. + *

+ * @see MathArrays#linearCombination(double, double, double, double, double, double) + * @param v second vector + * @return the dot product this.v + */ + public DerivativeStructure dotProduct(final Vector3D v) { + return MathArrays.linearCombination(v.getX(), x, v.getY(), y, v.getZ(), z); + } + + /** Compute the cross-product of the instance with another vector. + * @param v other vector + * @return the cross product this ^ v as a new Vector3D + */ + public Vector3DDS crossProduct(final Vector3DDS v) { + return new Vector3DDS(MathArrays.linearCombination(y, v.z, z.negate(), v.y), + MathArrays.linearCombination(z, v.x, x.negate(), v.z), + MathArrays.linearCombination(x, v.y, y.negate(), v.x)); + } + + /** Compute the cross-product of the instance with another vector. + * @param v other vector + * @return the cross product this ^ v as a new Vector3D + */ + public Vector3DDS crossProduct(final Vector3D v) { + return new Vector3DDS(MathArrays.linearCombination(v.getZ(), y, v.getY(), z.negate()), + MathArrays.linearCombination(v.getX(), z, v.getZ(), x.negate()), + MathArrays.linearCombination(v.getY(), x, v.getX(), y.negate())); + } + + /** Compute the distance between the instance and another vector according to the L1 norm. + *

Calling this method is equivalent to calling: + * q.subtract(p).getNorm1() except that no intermediate + * vector is built

+ * @param v second vector + * @return the distance between the instance and p according to the L1 norm + */ + public DerivativeStructure distance1(final Vector3DDS v) { + final DerivativeStructure dx = v.x.subtract(x).abs(); + final DerivativeStructure dy = v.y.subtract(y).abs(); + final DerivativeStructure dz = v.z.subtract(z).abs(); + return dx.add(dy).add(dz); + } + + /** Compute the distance between the instance and another vector according to the L1 norm. + *

Calling this method is equivalent to calling: + * q.subtract(p).getNorm1() except that no intermediate + * vector is built

+ * @param v second vector + * @return the distance between the instance and p according to the L1 norm + */ + public DerivativeStructure distance1(final Vector3D v) { + final DerivativeStructure dx = x.subtract(v.getX()).abs(); + final DerivativeStructure dy = y.subtract(v.getY()).abs(); + final DerivativeStructure dz = z.subtract(v.getZ()).abs(); + return dx.add(dy).add(dz); + } + + /** Compute the distance between the instance and another vector according to the L2 norm. + *

Calling this method is equivalent to calling: + * q.subtract(p).getNorm() except that no intermediate + * vector is built

+ * @param v second vector + * @return the distance between the instance and p according to the L2 norm + */ + public DerivativeStructure distance(final Vector3DDS v) { + final DerivativeStructure dx = v.x.subtract(x); + final DerivativeStructure dy = v.y.subtract(y); + final DerivativeStructure dz = v.z.subtract(z); + return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)).sqrt(); + } + + /** Compute the distance between the instance and another vector according to the L2 norm. + *

Calling this method is equivalent to calling: + * q.subtract(p).getNorm() except that no intermediate + * vector is built

+ * @param v second vector + * @return the distance between the instance and p according to the L2 norm + */ + public DerivativeStructure distance(final Vector3D v) { + final DerivativeStructure dx = x.subtract(v.getX()); + final DerivativeStructure dy = y.subtract(v.getY()); + final DerivativeStructure dz = z.subtract(v.getZ()); + return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)).sqrt(); + } + + /** Compute the distance between the instance and another vector according to the L norm. + *

Calling this method is equivalent to calling: + * q.subtract(p).getNormInf() except that no intermediate + * vector is built

+ * @param v second vector + * @return the distance between the instance and p according to the L norm + */ + public DerivativeStructure distanceInf(final Vector3DDS v) { + final DerivativeStructure dx = v.x.subtract(x).abs(); + final DerivativeStructure dy = v.y.subtract(y).abs(); + final DerivativeStructure dz = v.z.subtract(z).abs(); + if (dx.getValue() <= dy.getValue()) { + if (dy.getValue() <= dz.getValue()) { + return dz; + } else { + return dy; + } + } else { + if (dx.getValue() <= dz.getValue()) { + return dz; + } else { + return dx; + } + } + } + + /** Compute the distance between the instance and another vector according to the L norm. + *

Calling this method is equivalent to calling: + * q.subtract(p).getNormInf() except that no intermediate + * vector is built

+ * @param v second vector + * @return the distance between the instance and p according to the L norm + */ + public DerivativeStructure distanceInf(final Vector3D v) { + final DerivativeStructure dx = x.subtract(v.getX()).abs(); + final DerivativeStructure dy = y.subtract(v.getY()).abs(); + final DerivativeStructure dz = z.subtract(v.getZ()).abs(); + if (dx.getValue() <= dy.getValue()) { + if (dy.getValue() <= dz.getValue()) { + return dz; + } else { + return dy; + } + } else { + if (dx.getValue() <= dz.getValue()) { + return dz; + } else { + return dx; + } + } + } + + /** Compute the square of the distance between the instance and another vector. + *

Calling this method is equivalent to calling: + * q.subtract(p).getNormSq() except that no intermediate + * vector is built

+ * @param v second vector + * @return the square of the distance between the instance and p + */ + public DerivativeStructure distanceSq(final Vector3DDS v) { + final DerivativeStructure dx = v.x.subtract(x); + final DerivativeStructure dy = v.y.subtract(y); + final DerivativeStructure dz = v.z.subtract(z); + return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)); + } + + /** Compute the square of the distance between the instance and another vector. + *

Calling this method is equivalent to calling: + * q.subtract(p).getNormSq() except that no intermediate + * vector is built

+ * @param v second vector + * @return the square of the distance between the instance and p + */ + public DerivativeStructure distanceSq(final Vector3D v) { + final DerivativeStructure dx = x.subtract(v.getX()); + final DerivativeStructure dy = y.subtract(v.getY()); + final DerivativeStructure dz = z.subtract(v.getZ()); + return dx.multiply(dx).add(dy.multiply(dy)).add(dz.multiply(dz)); + } + + /** Compute the dot-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @return the dot product v1.v2 + */ + public static DerivativeStructure dotProduct(Vector3DDS v1, Vector3DDS v2) { + return v1.dotProduct(v2); + } + + /** Compute the dot-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @return the dot product v1.v2 + */ + public static DerivativeStructure dotProduct(Vector3DDS v1, Vector3D v2) { + return v1.dotProduct(v2); + } + + /** Compute the dot-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @return the dot product v1.v2 + */ + public static DerivativeStructure dotProduct(Vector3D v1, Vector3DDS v2) { + return v2.dotProduct(v1); + } + + /** Compute the cross-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @return the cross product v1 ^ v2 as a new Vector + */ + public static Vector3DDS crossProduct(final Vector3DDS v1, final Vector3DDS v2) { + return v1.crossProduct(v2); + } + + /** Compute the cross-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @return the cross product v1 ^ v2 as a new Vector + */ + public static Vector3DDS crossProduct(final Vector3DDS v1, final Vector3D v2) { + return v1.crossProduct(v2); + } + + /** Compute the cross-product of two vectors. + * @param v1 first vector + * @param v2 second vector + * @return the cross product v1 ^ v2 as a new Vector + */ + public static Vector3DDS crossProduct(final Vector3D v1, final Vector3DDS v2) { + return v2.crossProduct(v1).negate(); + } + + /** Compute the distance between two vectors according to the L1 norm. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm1() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L1 norm + */ + public static DerivativeStructure distance1(Vector3DDS v1, Vector3DDS v2) { + return v1.distance1(v2); + } + + /** Compute the distance between two vectors according to the L1 norm. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm1() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L1 norm + */ + public static DerivativeStructure distance1(Vector3DDS v1, Vector3D v2) { + return v1.distance1(v2); + } + + /** Compute the distance between two vectors according to the L1 norm. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm1() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L1 norm + */ + public static DerivativeStructure distance1(Vector3D v1, Vector3DDS v2) { + return v2.distance1(v1); + } + + /** Compute the distance between two vectors according to the L2 norm. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L2 norm + */ + public static DerivativeStructure distance(Vector3DDS v1, Vector3DDS v2) { + return v1.distance(v2); + } + + /** Compute the distance between two vectors according to the L2 norm. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L2 norm + */ + public static DerivativeStructure distance(Vector3DDS v1, Vector3D v2) { + return v1.distance(v2); + } + + /** Compute the distance between two vectors according to the L2 norm. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNorm() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L2 norm + */ + public static DerivativeStructure distance(Vector3D v1, Vector3DDS v2) { + return v2.distance(v1); + } + + /** Compute the distance between two vectors according to the L norm. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNormInf() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L norm + */ + public static DerivativeStructure distanceInf(Vector3DDS v1, Vector3DDS v2) { + return v1.distanceInf(v2); + } + + /** Compute the distance between two vectors according to the L norm. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNormInf() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L norm + */ + public static DerivativeStructure distanceInf(Vector3DDS v1, Vector3D v2) { + return v1.distanceInf(v2); + } + + /** Compute the distance between two vectors according to the L norm. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNormInf() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the distance between v1 and v2 according to the L norm + */ + public static DerivativeStructure distanceInf(Vector3D v1, Vector3DDS v2) { + return v2.distanceInf(v1); + } + + /** Compute the square of the distance between two vectors. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNormSq() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the square of the distance between v1 and v2 + */ + public static DerivativeStructure distanceSq(Vector3DDS v1, Vector3DDS v2) { + return v1.distanceSq(v2); + } + + /** Compute the square of the distance between two vectors. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNormSq() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the square of the distance between v1 and v2 + */ + public static DerivativeStructure distanceSq(Vector3DDS v1, Vector3D v2) { + return v1.distanceSq(v2); + } + + /** Compute the square of the distance between two vectors. + *

Calling this method is equivalent to calling: + * v1.subtract(v2).getNormSq() except that no intermediate + * vector is built

+ * @param v1 first vector + * @param v2 second vector + * @return the square of the distance between v1 and v2 + */ + public static DerivativeStructure distanceSq(Vector3D v1, Vector3DDS v2) { + return v2.distanceSq(v1); + } + + /** Get a string representation of this vector. + * @return a string representation of this vector + */ + @Override + public String toString() { + return Vector3DFormat.getInstance().format(toVector3D()); + } + + /** {@inheritDoc} */ + public String toString(final NumberFormat format) { + return new Vector3DFormat(format).format(toVector3D()); + } + +} diff --git a/src/test/java/org/apache/commons/math3/geometry/euclidean/threed/RotationDSTest.java b/src/test/java/org/apache/commons/math3/geometry/euclidean/threed/RotationDSTest.java new file mode 100644 index 000000000..cd1497694 --- /dev/null +++ b/src/test/java/org/apache/commons/math3/geometry/euclidean/threed/RotationDSTest.java @@ -0,0 +1,841 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.geometry.euclidean.threed; + +import org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import org.apache.commons.math3.exception.MathArithmeticException; +import org.apache.commons.math3.exception.MathIllegalArgumentException; +import org.apache.commons.math3.linear.MatrixUtils; +import org.apache.commons.math3.linear.RealMatrix; +import org.apache.commons.math3.random.UnitSphereRandomVectorGenerator; +import org.apache.commons.math3.random.Well1024a; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathUtils; +import org.junit.Assert; +import org.junit.Test; + + +public class RotationDSTest { + + @Test + public void testIdentity() { + + RotationDS r = createRotation(1, 0, 0, 0, false); + checkVector(r.applyTo(createVector(1, 0, 0)), createVector(1, 0, 0)); + checkVector(r.applyTo(createVector(0, 1, 0)), createVector(0, 1, 0)); + checkVector(r.applyTo(createVector(0, 0, 1)), createVector(0, 0, 1)); + checkAngle(r.getAngle(), 0); + + r = createRotation(-1, 0, 0, 0, false); + checkVector(r.applyTo(createVector(1, 0, 0)), createVector(1, 0, 0)); + checkVector(r.applyTo(createVector(0, 1, 0)), createVector(0, 1, 0)); + checkVector(r.applyTo(createVector(0, 0, 1)), createVector(0, 0, 1)); + checkAngle(r.getAngle(), 0); + + r = createRotation(42, 0, 0, 0, true); + checkVector(r.applyTo(createVector(1, 0, 0)), createVector(1, 0, 0)); + checkVector(r.applyTo(createVector(0, 1, 0)), createVector(0, 1, 0)); + checkVector(r.applyTo(createVector(0, 0, 1)), createVector(0, 0, 1)); + checkAngle(r.getAngle(), 0); + + } + + @Test + public void testAxisAngle() throws MathIllegalArgumentException { + + RotationDS r = new RotationDS(createAxis(10, 10, 10), createAngle(2 * FastMath.PI / 3)); + checkVector(r.applyTo(createVector(1, 0, 0)), createVector(0, 1, 0)); + checkVector(r.applyTo(createVector(0, 1, 0)), createVector(0, 0, 1)); + checkVector(r.applyTo(createVector(0, 0, 1)), createVector(1, 0, 0)); + double s = 1 / FastMath.sqrt(3); + checkVector(r.getAxis(), createVector(s, s, s)); + checkAngle(r.getAngle(), 2 * FastMath.PI / 3); + + try { + new RotationDS(createAxis(0, 0, 0), createAngle(2 * FastMath.PI / 3)); + Assert.fail("an exception should have been thrown"); + } catch (MathIllegalArgumentException e) { + } + + r = new RotationDS(createAxis(0, 0, 1), createAngle(1.5 * FastMath.PI)); + checkVector(r.getAxis(), createVector(0, 0, -1)); + checkAngle(r.getAngle(), 0.5 * FastMath.PI); + + r = new RotationDS(createAxis(0, 1, 0), createAngle(FastMath.PI)); + checkVector(r.getAxis(), createVector(0, 1, 0)); + checkAngle(r.getAngle(), FastMath.PI); + + checkVector(createRotation(1, 0, 0, 0, false).getAxis(), createVector(1, 0, 0)); + + } + + @Test + public void testRevert() { + double a = 0.001; + double b = 0.36; + double c = 0.48; + double d = 0.8; + RotationDS r = createRotation(a, b, c, d, true); + double a2 = a * a; + double b2 = b * b; + double c2 = c * c; + double d2 = d * d; + double den = (a2 + b2 + c2 + d2) * FastMath.sqrt(a2 + b2 + c2 + d2); + Assert.assertEquals((b2 + c2 + d2) / den, r.getQ0().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(-a * b / den, r.getQ0().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(-a * c / den, r.getQ0().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(-a * d / den, r.getQ0().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + Assert.assertEquals(-b * a / den, r.getQ1().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals((a2 + c2 + d2) / den, r.getQ1().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(-b * c / den, r.getQ1().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(-b * d / den, r.getQ1().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + Assert.assertEquals(-c * a / den, r.getQ2().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(-c * b / den, r.getQ2().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals((a2 + b2 + d2) / den, r.getQ2().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(-c * d / den, r.getQ2().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + Assert.assertEquals(-d * a / den, r.getQ3().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(-d * b / den, r.getQ3().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(-d * c / den, r.getQ3().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals((a2 + b2 + c2) / den, r.getQ3().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + RotationDS reverted = r.revert(); + RotationDS rrT = r.applyTo(reverted); + checkRotationDS(rrT, 1, 0, 0, 0); + Assert.assertEquals(0, rrT.getQ0().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ0().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ0().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ0().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + Assert.assertEquals(0, rrT.getQ1().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ1().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ1().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ1().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + Assert.assertEquals(0, rrT.getQ2().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ2().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ2().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ2().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + Assert.assertEquals(0, rrT.getQ3().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ3().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ3().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(0, rrT.getQ3().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + RotationDS rTr = reverted.applyTo(r); + checkRotationDS(rTr, 1, 0, 0, 0); + Assert.assertEquals(0, rTr.getQ0().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ0().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ0().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ0().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + Assert.assertEquals(0, rTr.getQ1().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ1().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ1().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ1().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + Assert.assertEquals(0, rTr.getQ2().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ2().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ2().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ2().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + Assert.assertEquals(0, rTr.getQ3().getPartialDerivative(1, 0, 0, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ3().getPartialDerivative(0, 1, 0, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ3().getPartialDerivative(0, 0, 1, 0), 1.0e-15); + Assert.assertEquals(0, rTr.getQ3().getPartialDerivative(0, 0, 0, 1), 1.0e-15); + Assert.assertEquals(r.getAngle().getValue(), reverted.getAngle().getValue(), 1.0e-15); + Assert.assertEquals(-1, Vector3DDS.dotProduct(r.getAxis(), reverted.getAxis()).getValue(), 1.0e-15); + } + + @Test + public void testVectorOnePair() throws MathArithmeticException { + + Vector3DDS u = createVector(3, 2, 1); + Vector3DDS v = createVector(-4, 2, 2); + RotationDS r = new RotationDS(u, v); + checkVector(r.applyTo(u.scalarMultiply(v.getNorm())), v.scalarMultiply(u.getNorm())); + + checkAngle(new RotationDS(u, u.negate()).getAngle(), FastMath.PI); + + try { + new RotationDS(u, createVector(0, 0, 0)); + Assert.fail("an exception should have been thrown"); + } catch (MathArithmeticException e) { + // expected behavior + } + + } + + @Test + public void testVectorTwoPairs() throws MathArithmeticException { + + Vector3DDS u1 = createVector(3, 0, 0); + Vector3DDS u2 = createVector(0, 5, 0); + Vector3DDS v1 = createVector(0, 0, 2); + Vector3DDS v2 = createVector(-2, 0, 2); + RotationDS r = new RotationDS(u1, u2, v1, v2); + checkVector(r.applyTo(createVector(1, 0, 0)), createVector(0, 0, 1)); + checkVector(r.applyTo(createVector(0, 1, 0)), createVector(-1, 0, 0)); + + r = new RotationDS(u1, u2, u1.negate(), u2.negate()); + Vector3DDS axis = r.getAxis(); + if (Vector3DDS.dotProduct(axis, createVector(0, 0, 1)).getValue() > 0) { + checkVector(axis, createVector(0, 0, 1)); + } else { + checkVector(axis, createVector(0, 0, -1)); + } + checkAngle(r.getAngle(), FastMath.PI); + + double sqrt = FastMath.sqrt(2) / 2; + r = new RotationDS(createVector(1, 0, 0), createVector(0, 1, 0), + createVector(0.5, 0.5, sqrt), + createVector(0.5, 0.5, -sqrt)); + checkRotationDS(r, sqrt, 0.5, 0.5, 0); + + r = new RotationDS(u1, u2, u1, Vector3DDS.crossProduct(u1, u2)); + checkRotationDS(r, sqrt, -sqrt, 0, 0); + + checkRotationDS(new RotationDS(u1, u2, u1, u2), 1, 0, 0, 0); + + try { + new RotationDS(u1, u2, createVector(0, 0, 0), v2); + Assert.fail("an exception should have been thrown"); + } catch (MathArithmeticException e) { + // expected behavior + } + + } + + @Test + public void testMatrix() + throws NotARotationMatrixException { + + try { + createRotation(new double[][] { + { 0.0, 1.0, 0.0 }, + { 1.0, 0.0, 0.0 } + }, 1.0e-7); + Assert.fail("Expecting NotARotationMatrixException"); + } catch (NotARotationMatrixException nrme) { + // expected behavior + } + + try { + createRotation(new double[][] { + { 0.445888, 0.797184, -0.407040 }, + { 0.821760, -0.184320, 0.539200 }, + { -0.354816, 0.574912, 0.737280 } + }, 1.0e-7); + Assert.fail("Expecting NotARotationMatrixException"); + } catch (NotARotationMatrixException nrme) { + // expected behavior + } + + try { + createRotation(new double[][] { + { 0.4, 0.8, -0.4 }, + { -0.4, 0.6, 0.7 }, + { 0.8, -0.2, 0.5 } + }, 1.0e-15); + Assert.fail("Expecting NotARotationMatrixException"); + } catch (NotARotationMatrixException nrme) { + // expected behavior + } + + checkRotationDS(createRotation(new double[][] { + { 0.445888, 0.797184, -0.407040 }, + { -0.354816, 0.574912, 0.737280 }, + { 0.821760, -0.184320, 0.539200 } + }, 1.0e-10), + 0.8, 0.288, 0.384, 0.36); + + checkRotationDS(createRotation(new double[][] { + { 0.539200, 0.737280, 0.407040 }, + { 0.184320, -0.574912, 0.797184 }, + { 0.821760, -0.354816, -0.445888 } + }, 1.0e-10), + 0.36, 0.8, 0.288, 0.384); + + checkRotationDS(createRotation(new double[][] { + { -0.445888, 0.797184, -0.407040 }, + { 0.354816, 0.574912, 0.737280 }, + { 0.821760, 0.184320, -0.539200 } + }, 1.0e-10), + 0.384, 0.36, 0.8, 0.288); + + checkRotationDS(createRotation(new double[][] { + { -0.539200, 0.737280, 0.407040 }, + { -0.184320, -0.574912, 0.797184 }, + { 0.821760, 0.354816, 0.445888 } + }, 1.0e-10), + 0.288, 0.384, 0.36, 0.8); + + double[][] m1 = { { 0.0, 1.0, 0.0 }, + { 0.0, 0.0, 1.0 }, + { 1.0, 0.0, 0.0 } }; + RotationDS r = createRotation(m1, 1.0e-7); + checkVector(r.applyTo(createVector(1, 0, 0)), createVector(0, 0, 1)); + checkVector(r.applyTo(createVector(0, 1, 0)), createVector(1, 0, 0)); + checkVector(r.applyTo(createVector(0, 0, 1)), createVector(0, 1, 0)); + + double[][] m2 = { { 0.83203, -0.55012, -0.07139 }, + { 0.48293, 0.78164, -0.39474 }, + { 0.27296, 0.29396, 0.91602 } }; + r = createRotation(m2, 1.0e-12); + + DerivativeStructure[][] m3 = r.getMatrix(); + double d00 = m2[0][0] - m3[0][0].getValue(); + double d01 = m2[0][1] - m3[0][1].getValue(); + double d02 = m2[0][2] - m3[0][2].getValue(); + double d10 = m2[1][0] - m3[1][0].getValue(); + double d11 = m2[1][1] - m3[1][1].getValue(); + double d12 = m2[1][2] - m3[1][2].getValue(); + double d20 = m2[2][0] - m3[2][0].getValue(); + double d21 = m2[2][1] - m3[2][1].getValue(); + double d22 = m2[2][2] - m3[2][2].getValue(); + + Assert.assertTrue(FastMath.abs(d00) < 6.0e-6); + Assert.assertTrue(FastMath.abs(d01) < 6.0e-6); + Assert.assertTrue(FastMath.abs(d02) < 6.0e-6); + Assert.assertTrue(FastMath.abs(d10) < 6.0e-6); + Assert.assertTrue(FastMath.abs(d11) < 6.0e-6); + Assert.assertTrue(FastMath.abs(d12) < 6.0e-6); + Assert.assertTrue(FastMath.abs(d20) < 6.0e-6); + Assert.assertTrue(FastMath.abs(d21) < 6.0e-6); + Assert.assertTrue(FastMath.abs(d22) < 6.0e-6); + + Assert.assertTrue(FastMath.abs(d00) > 4.0e-7); + Assert.assertTrue(FastMath.abs(d01) > 4.0e-7); + Assert.assertTrue(FastMath.abs(d02) > 4.0e-7); + Assert.assertTrue(FastMath.abs(d10) > 4.0e-7); + Assert.assertTrue(FastMath.abs(d11) > 4.0e-7); + Assert.assertTrue(FastMath.abs(d12) > 4.0e-7); + Assert.assertTrue(FastMath.abs(d20) > 4.0e-7); + Assert.assertTrue(FastMath.abs(d21) > 4.0e-7); + Assert.assertTrue(FastMath.abs(d22) > 4.0e-7); + + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + double m3tm3 = m3[i][0].getValue() * m3[j][0].getValue() + + m3[i][1].getValue() * m3[j][1].getValue() + + m3[i][2].getValue() * m3[j][2].getValue(); + if (i == j) { + Assert.assertTrue(FastMath.abs(m3tm3 - 1.0) < 1.0e-10); + } else { + Assert.assertTrue(FastMath.abs(m3tm3) < 1.0e-10); + } + } + } + + checkVector(r.applyTo(createVector(1, 0, 0)), + new Vector3DDS(m3[0][0], m3[1][0], m3[2][0])); + checkVector(r.applyTo(createVector(0, 1, 0)), + new Vector3DDS(m3[0][1], m3[1][1], m3[2][1])); + checkVector(r.applyTo(createVector(0, 0, 1)), + new Vector3DDS(m3[0][2], m3[1][2], m3[2][2])); + + double[][] m4 = { { 1.0, 0.0, 0.0 }, + { 0.0, -1.0, 0.0 }, + { 0.0, 0.0, -1.0 } }; + r = createRotation(m4, 1.0e-7); + checkAngle(r.getAngle(), FastMath.PI); + + try { + double[][] m5 = { { 0.0, 0.0, 1.0 }, + { 0.0, 1.0, 0.0 }, + { 1.0, 0.0, 0.0 } }; + r = createRotation(m5, 1.0e-7); + Assert.fail("got " + r + ", should have caught an exception"); + } catch (NotARotationMatrixException e) { + // expected + } + + } + + @Test + public void testAngles() + throws CardanEulerSingularityException { + + RotationOrder[] CardanOrders = { + RotationOrder.XYZ, RotationOrder.XZY, RotationOrder.YXZ, + RotationOrder.YZX, RotationOrder.ZXY, RotationOrder.ZYX + }; + + for (int i = 0; i < CardanOrders.length; ++i) { + for (double alpha1 = 0.1; alpha1 < 6.2; alpha1 += 0.3) { + for (double alpha2 = -1.55; alpha2 < 1.55; alpha2 += 0.3) { + for (double alpha3 = 0.1; alpha3 < 6.2; alpha3 += 0.3) { + RotationDS r = new RotationDS(CardanOrders[i], + new DerivativeStructure(3, 1, 0, alpha1), + new DerivativeStructure(3, 1, 1, alpha2), + new DerivativeStructure(3, 1, 2, alpha3)); + DerivativeStructure[] angles = r.getAngles(CardanOrders[i]); + checkAngle(angles[0], alpha1); + checkAngle(angles[1], alpha2); + checkAngle(angles[2], alpha3); + } + } + } + } + + RotationOrder[] EulerOrders = { + RotationOrder.XYX, RotationOrder.XZX, RotationOrder.YXY, + RotationOrder.YZY, RotationOrder.ZXZ, RotationOrder.ZYZ + }; + + for (int i = 0; i < EulerOrders.length; ++i) { + for (double alpha1 = 0.1; alpha1 < 6.2; alpha1 += 0.3) { + for (double alpha2 = 0.05; alpha2 < 3.1; alpha2 += 0.3) { + for (double alpha3 = 0.1; alpha3 < 6.2; alpha3 += 0.3) { + RotationDS r = new RotationDS(EulerOrders[i], + new DerivativeStructure(3, 1, 0, alpha1), + new DerivativeStructure(3, 1, 1, alpha2), + new DerivativeStructure(3, 1, 2, alpha3)); + DerivativeStructure[] angles = r.getAngles(EulerOrders[i]); + checkAngle(angles[0], alpha1); + checkAngle(angles[1], alpha2); + checkAngle(angles[2], alpha3); + } + } + } + } + + } + + @Test + public void testSingularities() { + + RotationOrder[] CardanOrders = { + RotationOrder.XYZ, RotationOrder.XZY, RotationOrder.YXZ, + RotationOrder.YZX, RotationOrder.ZXY, RotationOrder.ZYX + }; + + double[] singularCardanAngle = { FastMath.PI / 2, -FastMath.PI / 2 }; + for (int i = 0; i < CardanOrders.length; ++i) { + for (int j = 0; j < singularCardanAngle.length; ++j) { + RotationDS r = new RotationDS(CardanOrders[i], + new DerivativeStructure(3, 1, 0, 0.1), + new DerivativeStructure(3, 1, 1, singularCardanAngle[j]), + new DerivativeStructure(3, 1, 2, 0.3)); + try { + r.getAngles(CardanOrders[i]); + Assert.fail("an exception should have been caught"); + } catch (CardanEulerSingularityException cese) { + // expected behavior + } + } + } + + RotationOrder[] EulerOrders = { + RotationOrder.XYX, RotationOrder.XZX, RotationOrder.YXY, + RotationOrder.YZY, RotationOrder.ZXZ, RotationOrder.ZYZ + }; + + double[] singularEulerAngle = { 0, FastMath.PI }; + for (int i = 0; i < EulerOrders.length; ++i) { + for (int j = 0; j < singularEulerAngle.length; ++j) { + RotationDS r = new RotationDS(EulerOrders[i], + new DerivativeStructure(3, 1, 0, 0.1), + new DerivativeStructure(3, 1, 1, singularEulerAngle[j]), + new DerivativeStructure(3, 1, 2, 0.3)); + try { + r.getAngles(EulerOrders[i]); + Assert.fail("an exception should have been caught"); + } catch (CardanEulerSingularityException cese) { + // expected behavior + } + } + } + + + } + + @Test + public void testQuaternion() throws MathIllegalArgumentException { + + RotationDS r1 = new RotationDS(createVector(2, -3, 5), createAngle(1.7)); + double n = 23.5; + RotationDS r2 = new RotationDS(r1.getQ0().multiply(n), r1.getQ1().multiply(n), + r1.getQ2().multiply(n), r1.getQ3().multiply(n), + true); + for (double x = -0.9; x < 0.9; x += 0.2) { + for (double y = -0.9; y < 0.9; y += 0.2) { + for (double z = -0.9; z < 0.9; z += 0.2) { + Vector3DDS u = createVector(x, y, z); + checkVector(r2.applyTo(u), r1.applyTo(u)); + } + } + } + + r1 = createRotation(0.288, 0.384, 0.36, 0.8, false); + checkRotationDS(r1, + -r1.getQ0().getValue(), -r1.getQ1().getValue(), + -r1.getQ2().getValue(), -r1.getQ3().getValue()); + + } + + @Test + public void testCompose() throws MathIllegalArgumentException { + + RotationDS r1 = new RotationDS(createVector(2, -3, 5), createAngle(1.7)); + RotationDS r2 = new RotationDS(createVector(-1, 3, 2), createAngle(0.3)); + RotationDS r3 = r2.applyTo(r1); + RotationDS r3Double = r2.applyTo(new Rotation(r1.getQ0().getValue(), + r1.getQ1().getValue(), + r1.getQ2().getValue(), + r1.getQ3().getValue(), + false)); + + for (double x = -0.9; x < 0.9; x += 0.2) { + for (double y = -0.9; y < 0.9; y += 0.2) { + for (double z = -0.9; z < 0.9; z += 0.2) { + Vector3DDS u = createVector(x, y, z); + checkVector(r2.applyTo(r1.applyTo(u)), r3.applyTo(u)); + checkVector(r2.applyTo(r1.applyTo(u)), r3Double.applyTo(u)); + } + } + } + + } + + @Test + public void testComposeInverse() throws MathIllegalArgumentException { + + RotationDS r1 = new RotationDS(createVector(2, -3, 5), createAngle(1.7)); + RotationDS r2 = new RotationDS(createVector(-1, 3, 2), createAngle(0.3)); + RotationDS r3 = r2.applyInverseTo(r1); + RotationDS r3Double = r2.applyInverseTo(new Rotation(r1.getQ0().getValue(), + r1.getQ1().getValue(), + r1.getQ2().getValue(), + r1.getQ3().getValue(), + false)); + + for (double x = -0.9; x < 0.9; x += 0.2) { + for (double y = -0.9; y < 0.9; y += 0.2) { + for (double z = -0.9; z < 0.9; z += 0.2) { + Vector3DDS u = createVector(x, y, z); + checkVector(r2.applyInverseTo(r1.applyTo(u)), r3.applyTo(u)); + checkVector(r2.applyInverseTo(r1.applyTo(u)), r3Double.applyTo(u)); + } + } + } + + } + + @Test + public void testDoubleVectors() throws MathIllegalArgumentException { + + Well1024a random = new Well1024a(0x180b41cfeeffaf67l); + UnitSphereRandomVectorGenerator g = new UnitSphereRandomVectorGenerator(3, random); + for (int i = 0; i < 10; ++i) { + double[] unit = g.nextVector(); + RotationDS r = new RotationDS(createVector(unit[0], unit[1], unit[2]), + createAngle(random.nextDouble())); + + for (double x = -0.9; x < 0.9; x += 0.2) { + for (double y = -0.9; y < 0.9; y += 0.2) { + for (double z = -0.9; z < 0.9; z += 0.2) { + Vector3DDS uds = createVector(x, y, z); + Vector3DDS ruds = r.applyTo(uds); + Vector3DDS rIuds = r.applyInverseTo(uds); + Vector3D u = new Vector3D(x, y, z); + Vector3DDS ru = r.applyTo(u); + Vector3DDS rIu = r.applyInverseTo(u); + DerivativeStructure[] ruArray = new DerivativeStructure[3]; + r.applyTo(new double[] { x, y, z}, ruArray); + DerivativeStructure[] rIuArray = new DerivativeStructure[3]; + r.applyInverseTo(new double[] { x, y, z}, rIuArray); + checkVector(ruds, ru); + checkVector(ruds, new Vector3DDS(ruArray)); + checkVector(rIuds, rIu); + checkVector(rIuds, new Vector3DDS(rIuArray)); + } + } + } + } + + } + + @Test + public void testDoubleRotations() throws MathIllegalArgumentException { + + Well1024a random = new Well1024a(0x180b41cfeeffaf67l); + UnitSphereRandomVectorGenerator g = new UnitSphereRandomVectorGenerator(3, random); + for (int i = 0; i < 10; ++i) { + double[] unit1 = g.nextVector(); + Rotation r1 = new Rotation(new Vector3D(unit1[0], unit1[1], unit1[2]), + random.nextDouble()); + RotationDS r1Prime = new RotationDS(new DerivativeStructure(4, 1, 0, r1.getQ0()), + new DerivativeStructure(4, 1, 1, r1.getQ1()), + new DerivativeStructure(4, 1, 2, r1.getQ2()), + new DerivativeStructure(4, 1, 3, r1.getQ3()), + false); + double[] unit2 = g.nextVector(); + RotationDS r2 = new RotationDS(createVector(unit2[0], unit2[1], unit2[2]), + createAngle(random.nextDouble())); + + RotationDS rA = RotationDS.applyTo(r1, r2); + RotationDS rB = r1Prime.applyTo(r2); + RotationDS rC = RotationDS.applyInverseTo(r1, r2); + RotationDS rD = r1Prime.applyInverseTo(r2); + + for (double x = -0.9; x < 0.9; x += 0.2) { + for (double y = -0.9; y < 0.9; y += 0.2) { + for (double z = -0.9; z < 0.9; z += 0.2) { + + Vector3DDS uds = createVector(x, y, z); + checkVector(r1Prime.applyTo(uds), RotationDS.applyTo(r1, uds)); + checkVector(r1Prime.applyInverseTo(uds), RotationDS.applyInverseTo(r1, uds)); + checkVector(rA.applyTo(uds), rB.applyTo(uds)); + checkVector(rA.applyInverseTo(uds), rB.applyInverseTo(uds)); + checkVector(rC.applyTo(uds), rD.applyTo(uds)); + checkVector(rC.applyInverseTo(uds), rD.applyInverseTo(uds)); + + } + } + } + } + + } + + @Test + public void testDerivatives() { + + double eps = 5.0e-16; + double kx = 2; + double ky = -3; + double kz = 5; + double n2 = kx * kx + ky * ky + kz * kz; + double n = FastMath.sqrt(n2); + double theta = 1.7; + double cosTheta = FastMath.cos(theta); + double sinTheta = FastMath.sin(theta); + RotationDS r = new RotationDS(createAxis(kx, ky, kz), createAngle(theta)); + Vector3D a = new Vector3D(kx / n, ky / n, kz / n); + + // Jacobian of the normalized rotation axis a with respect to the Cartesian vector k + RealMatrix dadk = MatrixUtils.createRealMatrix(new double[][] { + { (ky * ky + kz * kz) / ( n * n2), -kx * ky / ( n * n2), -kx * kz / ( n * n2) }, + { -kx * ky / ( n * n2), (kx * kx + kz * kz) / ( n * n2), -ky * kz / ( n * n2) }, + { -kx * kz / ( n * n2), -ky * kz / ( n * n2), (kx * kx + ky * ky) / ( n * n2) } + }); + + for (double x = -0.9; x < 0.9; x += 0.2) { + for (double y = -0.9; y < 0.9; y += 0.2) { + for (double z = -0.9; z < 0.9; z += 0.2) { + Vector3D u = new Vector3D(x, y, z); + Vector3DDS v = r.applyTo(createVector(x, y, z)); + + // explicit formula for rotation of vector u around axis a with angle theta + double dot = Vector3D.dotProduct(u, a); + Vector3D cross = Vector3D.crossProduct(a, u); + double c1 = 1 - cosTheta; + double c2 = c1 * dot; + Vector3D rt = new Vector3D(cosTheta, u, c2, a, sinTheta, cross); + Assert.assertEquals(rt.getX(), v.getX().getValue(), eps); + Assert.assertEquals(rt.getY(), v.getY().getValue(), eps); + Assert.assertEquals(rt.getZ(), v.getZ().getValue(), eps); + + // Jacobian of the image v = r(u) with respect to rotation axis a + // (analytical differentiation of the explicit formula) + RealMatrix dvda = MatrixUtils.createRealMatrix(new double[][] { + { c1 * x * a.getX() + c2, c1 * y * a.getX() + sinTheta * z, c1 * z * a.getX() - sinTheta * y }, + { c1 * x * a.getY() - sinTheta * z, c1 * y * a.getY() + c2, c1 * z * a.getY() + sinTheta * x }, + { c1 * x * a.getZ() + sinTheta * y, c1 * y * a.getZ() - sinTheta * x, c1 * z * a.getZ() + c2 } + }); + + // compose Jacobians + RealMatrix dvdk = dvda.multiply(dadk); + + // derivatives with respect to un-normalized axis + Assert.assertEquals(dvdk.getEntry(0, 0), v.getX().getPartialDerivative(1, 0, 0, 0), eps); + Assert.assertEquals(dvdk.getEntry(0, 1), v.getX().getPartialDerivative(0, 1, 0, 0), eps); + Assert.assertEquals(dvdk.getEntry(0, 2), v.getX().getPartialDerivative(0, 0, 1, 0), eps); + Assert.assertEquals(dvdk.getEntry(1, 0), v.getY().getPartialDerivative(1, 0, 0, 0), eps); + Assert.assertEquals(dvdk.getEntry(1, 1), v.getY().getPartialDerivative(0, 1, 0, 0), eps); + Assert.assertEquals(dvdk.getEntry(1, 2), v.getY().getPartialDerivative(0, 0, 1, 0), eps); + Assert.assertEquals(dvdk.getEntry(2, 0), v.getZ().getPartialDerivative(1, 0, 0, 0), eps); + Assert.assertEquals(dvdk.getEntry(2, 1), v.getZ().getPartialDerivative(0, 1, 0, 0), eps); + Assert.assertEquals(dvdk.getEntry(2, 2), v.getZ().getPartialDerivative(0, 0, 1, 0), eps); + + // derivative with respect to rotation angle + // (analytical differentiation of the explicit formula) + Vector3D dvdTheta = + new Vector3D(-sinTheta, u, sinTheta * dot, a, cosTheta, cross); + Assert.assertEquals(dvdTheta.getX(), v.getX().getPartialDerivative(0, 0, 0, 1), eps); + Assert.assertEquals(dvdTheta.getY(), v.getY().getPartialDerivative(0, 0, 0, 1), eps); + Assert.assertEquals(dvdTheta.getZ(), v.getZ().getPartialDerivative(0, 0, 0, 1), eps); + + } + } + } + } + + @Test + public void testArray() throws MathIllegalArgumentException { + + RotationDS r = new RotationDS(createAxis(2, -3, 5), createAngle(1.7)); + + for (double x = -0.9; x < 0.9; x += 0.2) { + for (double y = -0.9; y < 0.9; y += 0.2) { + for (double z = -0.9; z < 0.9; z += 0.2) { + Vector3DDS u = createVector(x, y, z); + Vector3DDS v = r.applyTo(u); + DerivativeStructure[] out = new DerivativeStructure[3]; + r.applyTo(new DerivativeStructure[] { u.getX(), u.getY(), u.getZ() }, out); + Assert.assertEquals(v.getX().getValue(), out[0].getValue(), 1.0e-10); + Assert.assertEquals(v.getY().getValue(), out[1].getValue(), 1.0e-10); + Assert.assertEquals(v.getZ().getValue(), out[2].getValue(), 1.0e-10); + r.applyInverseTo(out, out); + Assert.assertEquals(u.getX().getValue(), out[0].getValue(), 1.0e-10); + Assert.assertEquals(u.getY().getValue(), out[1].getValue(), 1.0e-10); + Assert.assertEquals(u.getZ().getValue(), out[2].getValue(), 1.0e-10); + } + } + } + + } + + @Test + public void testApplyInverseTo() throws MathIllegalArgumentException { + + DerivativeStructure[] in = new DerivativeStructure[3]; + DerivativeStructure[] out = new DerivativeStructure[3]; + DerivativeStructure[] rebuilt = new DerivativeStructure[3]; + RotationDS r = new RotationDS(createVector(2, -3, 5), createAngle(1.7)); + for (double lambda = 0; lambda < 6.2; lambda += 0.2) { + for (double phi = -1.55; phi < 1.55; phi += 0.2) { + Vector3DDS u = createVector(FastMath.cos(lambda) * FastMath.cos(phi), + FastMath.sin(lambda) * FastMath.cos(phi), + FastMath.sin(phi)); + r.applyInverseTo(r.applyTo(u)); + checkVector(u, r.applyInverseTo(r.applyTo(u))); + checkVector(u, r.applyTo(r.applyInverseTo(u))); + in[0] = u.getX(); + in[1] = u.getY(); + in[2] = u.getZ(); + r.applyTo(in, out); + r.applyInverseTo(out, rebuilt); + Assert.assertEquals(in[0].getValue(), rebuilt[0].getValue(), 1.0e-12); + Assert.assertEquals(in[1].getValue(), rebuilt[1].getValue(), 1.0e-12); + Assert.assertEquals(in[2].getValue(), rebuilt[2].getValue(), 1.0e-12); + } + } + + r = createRotation(1, 0, 0, 0, false); + for (double lambda = 0; lambda < 6.2; lambda += 0.2) { + for (double phi = -1.55; phi < 1.55; phi += 0.2) { + Vector3DDS u = createVector(FastMath.cos(lambda) * FastMath.cos(phi), + FastMath.sin(lambda) * FastMath.cos(phi), + FastMath.sin(phi)); + checkVector(u, r.applyInverseTo(r.applyTo(u))); + checkVector(u, r.applyTo(r.applyInverseTo(u))); + } + } + + r = new RotationDS(createVector(0, 0, 1), createAngle(FastMath.PI)); + for (double lambda = 0; lambda < 6.2; lambda += 0.2) { + for (double phi = -1.55; phi < 1.55; phi += 0.2) { + Vector3DDS u = createVector(FastMath.cos(lambda) * FastMath.cos(phi), + FastMath.sin(lambda) * FastMath.cos(phi), + FastMath.sin(phi)); + checkVector(u, r.applyInverseTo(r.applyTo(u))); + checkVector(u, r.applyTo(r.applyInverseTo(u))); + } + } + + } + + @Test + public void testIssue639() throws MathArithmeticException{ + Vector3DDS u1 = createVector(-1321008684645961.0 / 268435456.0, + -5774608829631843.0 / 268435456.0, + -3822921525525679.0 / 4294967296.0); + Vector3DDS u2 =createVector( -5712344449280879.0 / 2097152.0, + -2275058564560979.0 / 1048576.0, + 4423475992255071.0 / 65536.0); + RotationDS rot = new RotationDS(u1, u2, createVector(1, 0, 0),createVector(0, 0, 1)); + Assert.assertEquals( 0.6228370359608200639829222, rot.getQ0().getValue(), 1.0e-15); + Assert.assertEquals( 0.0257707621456498790029987, rot.getQ1().getValue(), 1.0e-15); + Assert.assertEquals(-0.0000000002503012255839931, rot.getQ2().getValue(), 1.0e-15); + Assert.assertEquals(-0.7819270390861109450724902, rot.getQ3().getValue(), 1.0e-15); + } + + @Test + public void testIssue801() throws MathArithmeticException { + Vector3DDS u1 = createVector(0.9999988431610581, -0.0015210774290851095, 0.0); + Vector3DDS u2 = createVector(0.0, 0.0, 1.0); + + Vector3DDS v1 = createVector(0.9999999999999999, 0.0, 0.0); + Vector3DDS v2 = createVector(0.0, 0.0, -1.0); + + RotationDS quat = new RotationDS(u1, u2, v1, v2); + double q2 = quat.getQ0().getValue() * quat.getQ0().getValue() + + quat.getQ1().getValue() * quat.getQ1().getValue() + + quat.getQ2().getValue() * quat.getQ2().getValue() + + quat.getQ3().getValue() * quat.getQ3().getValue(); + Assert.assertEquals(1.0, q2, 1.0e-14); + Assert.assertEquals(0.0, Vector3DDS.angle(v1, quat.applyTo(u1)).getValue(), 1.0e-14); + Assert.assertEquals(0.0, Vector3DDS.angle(v2, quat.applyTo(u2)).getValue(), 1.0e-14); + + } + + private void checkAngle(DerivativeStructure a1, double a2) { + Assert.assertEquals(a1.getValue(), MathUtils.normalizeAngle(a2, a1.getValue()), 1.0e-10); + } + + private void checkRotationDS(RotationDS r, double q0, double q1, double q2, double q3) { + RotationDS rPrime = createRotation(q0, q1, q2, q3, false); + Assert.assertEquals(0, RotationDS.distance(r, rPrime).getValue(), 1.0e-12); + } + + private RotationDS createRotation(double q0, double q1, double q2, double q3, + boolean needsNormalization) { + return new RotationDS(new DerivativeStructure(4, 1, 0, q0), + new DerivativeStructure(4, 1, 1, q1), + new DerivativeStructure(4, 1, 2, q2), + new DerivativeStructure(4, 1, 3, q3), + needsNormalization); + } + + private RotationDS createRotation(double[][] m, double threshold) { + DerivativeStructure[][] mds = new DerivativeStructure[m.length][m[0].length]; + int index = 0; + for (int i = 0; i < m.length; ++i) { + for (int j = 0; j < m[i].length; ++j) { + mds[i][j] = new DerivativeStructure(4, 1, index, m[i][j]); + index = (index + 1) % 4; + } + } + return new RotationDS(mds, threshold); + } + + private Vector3DDS createVector(double x, double y, double z) { + return new Vector3DDS(new DerivativeStructure(4, 1, x), + new DerivativeStructure(4, 1, y), + new DerivativeStructure(4, 1, z)); + } + + private Vector3DDS createAxis(double x, double y, double z) { + return new Vector3DDS(new DerivativeStructure(4, 1, 0, x), + new DerivativeStructure(4, 1, 1, y), + new DerivativeStructure(4, 1, 2, z)); + } + + private DerivativeStructure createAngle(double alpha) { + return new DerivativeStructure(4, 1, 3, alpha); + } + + private void checkVector(Vector3DDS u, Vector3DDS v) { + Assert.assertEquals(u.getX().getValue(), v.getX().getValue(), 1.0e-12); + Assert.assertEquals(u.getY().getValue(), v.getY().getValue(), 1.0e-12); + Assert.assertEquals(u.getZ().getValue(), v.getZ().getValue(), 1.0e-12); + } + +} diff --git a/src/test/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DDSTest.java b/src/test/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DDSTest.java new file mode 100644 index 000000000..58ff03234 --- /dev/null +++ b/src/test/java/org/apache/commons/math3/geometry/euclidean/threed/Vector3DDSTest.java @@ -0,0 +1,733 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.math3.geometry.euclidean.threed; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.util.Locale; + +import org.apache.commons.math3.analysis.differentiation.DerivativeStructure; +import org.apache.commons.math3.exception.DimensionMismatchException; +import org.apache.commons.math3.exception.MathArithmeticException; +import org.apache.commons.math3.random.Well1024a; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Precision; +import org.junit.Assert; +import org.junit.Test; + +public class Vector3DDSTest { + @Test + public void testConstructors() throws DimensionMismatchException { + double cosAlpha = 1 / 2.0; + double sinAlpha = FastMath.sqrt(3) / 2.0; + double cosDelta = FastMath.sqrt(2) / 2.0; + double sinDelta = -FastMath.sqrt(2) / 2.0; + Vector3DDS u = new Vector3DDS(2, + new Vector3DDS(new DerivativeStructure(2, 1, 0, FastMath.PI / 3), + new DerivativeStructure(2, 1, 1, -FastMath.PI / 4))); + checkVector(u, 2 * cosAlpha * cosDelta, 2 * sinAlpha * cosDelta, 2 * sinDelta); + Assert.assertEquals(-2 * sinAlpha * cosDelta, u.getX().getPartialDerivative(1, 0), 1.0e-12); + Assert.assertEquals(+2 * cosAlpha * cosDelta, u.getY().getPartialDerivative(1, 0), 1.0e-12); + Assert.assertEquals(0, u.getZ().getPartialDerivative(1, 0), 1.0e-12); + Assert.assertEquals(-2 * cosAlpha * sinDelta, u.getX().getPartialDerivative(0, 1), 1.0e-12); + Assert.assertEquals(-2 * sinAlpha * sinDelta, u.getY().getPartialDerivative(0, 1), 1.0e-12); + Assert.assertEquals(2 * cosDelta, u.getZ().getPartialDerivative(0, 1), 1.0e-12); + + checkVector(new Vector3DDS(2, createVector(1, 0, 0, 3)), + 2, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2); + checkVector(new Vector3DDS(new DerivativeStructure(4, 1, 3, 2.0), + createVector(1, 0, 0, 4)), + 2, 0, 0, 2, 0, 0, 1, 0, 2, 0, 0, 0, 0, 2, 0); + checkVector(new Vector3DDS(new DerivativeStructure(4, 1, 3, 2.0), + new Vector3D(1, 0, 0)), + 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0); + + checkVector(new Vector3DDS(2, createVector(1, 0, 0, 3), + -3, createVector(0, 0, -1, 3)), + 2, 0, 3, -1, 0, 0, 0, -1, 0, 0, 0, -1); + checkVector(new Vector3DDS(new DerivativeStructure(4, 1, 3, 2.0), + createVector(1, 0, 0, 4), + new DerivativeStructure(4, 1, 3, -3.0), + createVector(0, 0, -1, 4)), + 2, 0, 3, -1, 0, 0, 1, 0, -1, 0, 0, 0, 0, -1, -1); + checkVector(new Vector3DDS(new DerivativeStructure(4, 1, 3, 2.0), + new Vector3D(1, 0, 0), + new DerivativeStructure(4, 1, 3, -3.0), + new Vector3D(0, 0, -1)), + 2, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -1); + + checkVector(new Vector3DDS(2, createVector(1, 0, 0, 3), + 5, createVector(0, 1, 0, 3), + -3, createVector(0, 0, -1, 3)), + 2, 5, 3, 4, 0, 0, 0, 4, 0, 0, 0, 4); + checkVector(new Vector3DDS(new DerivativeStructure(4, 1, 3, 2.0), + createVector(1, 0, 0, 4), + new DerivativeStructure(4, 1, 3, 5.0), + createVector(0, 1, 0, 4), + new DerivativeStructure(4, 1, 3, -3.0), + createVector(0, 0, -1, 4)), + 2, 5, 3, 4, 0, 0, 1, 0, 4, 0, 1, 0, 0, 4, -1); + checkVector(new Vector3DDS(new DerivativeStructure(4, 1, 3, 2.0), + new Vector3D(1, 0, 0), + new DerivativeStructure(4, 1, 3, 5.0), + new Vector3D(0, 1, 0), + new DerivativeStructure(4, 1, 3, -3.0), + new Vector3D(0, 0, -1)), + 2, 5, 3, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, -1); + + checkVector(new Vector3DDS(2, createVector(1, 0, 0, 3), + 5, createVector(0, 1, 0, 3), + 5, createVector(0, -1, 0, 3), + -3, createVector(0, 0, -1, 3)), + 2, 0, 3, 9, 0, 0, 0, 9, 0, 0, 0, 9); + checkVector(new Vector3DDS(new DerivativeStructure(4, 1, 3, 2.0), + createVector(1, 0, 0, 4), + new DerivativeStructure(4, 1, 3, 5.0), + createVector(0, 1, 0, 4), + new DerivativeStructure(4, 1, 3, 5.0), + createVector(0, -1, 0, 4), + new DerivativeStructure(4, 1, 3, -3.0), + createVector(0, 0, -1, 4)), + 2, 0, 3, 9, 0, 0, 1, 0, 9, 0, 0, 0, 0, 9, -1); + checkVector(new Vector3DDS(new DerivativeStructure(4, 1, 3, 2.0), + new Vector3D(1, 0, 0), + new DerivativeStructure(4, 1, 3, 5.0), + new Vector3D(0, 1, 0), + new DerivativeStructure(4, 1, 3, 5.0), + new Vector3D(0, -1, 0), + new DerivativeStructure(4, 1, 3, -3.0), + new Vector3D(0, 0, -1)), + 2, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -1); + + checkVector(new Vector3DDS(new DerivativeStructure[] { + new DerivativeStructure(3, 1, 2, 2), + new DerivativeStructure(3, 1, 1, 5), + new DerivativeStructure(3, 1, 0, -3) + }), + 2, 5, -3, 0, 0, 1, 0, 1, 0, 1, 0, 0); + + } + + @Test + public void testEquals() { + Vector3DDS u1 = createVector(1, 2, 3, 3); + Vector3DDS v = createVector(1, 2, 3 + 10 * Precision.EPSILON, 3); + Assert.assertTrue(u1.equals(u1)); + Assert.assertTrue(u1.equals(new Vector3DDS(new DerivativeStructure(3, 1, 0, 1.0), + new DerivativeStructure(3, 1, 1, 2.0), + new DerivativeStructure(3, 1, 2, 3.0)))); + Assert.assertFalse(u1.equals(new Vector3DDS(new DerivativeStructure(3, 1, 1.0), + new DerivativeStructure(3, 1, 1, 2.0), + new DerivativeStructure(3, 1, 2, 3.0)))); + Assert.assertFalse(u1.equals(new Vector3DDS(new DerivativeStructure(3, 1, 0, 1.0), + new DerivativeStructure(3, 1, 2.0), + new DerivativeStructure(3, 1, 2, 3.0)))); + Assert.assertFalse(u1.equals(new Vector3DDS(new DerivativeStructure(3, 1, 0, 1.0), + new DerivativeStructure(3, 1, 1, 2.0), + new DerivativeStructure(3, 1, 3.0)))); + Assert.assertFalse(u1.equals(v)); + Assert.assertFalse(u1.equals(u1.toVector3D())); + Assert.assertTrue(createVector(0, Double.NaN, 0, 3).equals(createVector(0, 0, Double.NaN, 3))); + } + + @Test + public void testHash() { + Assert.assertEquals(createVector(0, Double.NaN, 0, 3).hashCode(), createVector(0, 0, Double.NaN, 3).hashCode()); + Vector3DDS u = createVector(1, 2, 3, 3); + Vector3DDS v = createVector(1, 2, 3 + 10 * Precision.EPSILON, 3); + Assert.assertTrue(u.hashCode() != v.hashCode()); + } + + @Test + public void testInfinite() { + Assert.assertTrue(createVector(1, 1, Double.NEGATIVE_INFINITY, 3).isInfinite()); + Assert.assertTrue(createVector(1, Double.NEGATIVE_INFINITY, 1, 3).isInfinite()); + Assert.assertTrue(createVector(Double.NEGATIVE_INFINITY, 1, 1, 3).isInfinite()); + Assert.assertFalse(createVector(1, 1, 2, 3).isInfinite()); + Assert.assertFalse(createVector(1, Double.NaN, Double.NEGATIVE_INFINITY, 3).isInfinite()); + } + + @Test + public void testNaN() { + Assert.assertTrue(createVector(1, 1, Double.NaN, 3).isNaN()); + Assert.assertTrue(createVector(1, Double.NaN, 1, 3).isNaN()); + Assert.assertTrue(createVector(Double.NaN, 1, 1, 3).isNaN()); + Assert.assertFalse(createVector(1, 1, 2, 3).isNaN()); + Assert.assertFalse(createVector(1, 1, Double.NEGATIVE_INFINITY, 3).isNaN()); + } + + @Test + public void testToString() { + Assert.assertEquals("{3; 2; 1}", createVector(3, 2, 1, 3).toString()); + NumberFormat format = new DecimalFormat("0.000", new DecimalFormatSymbols(Locale.US)); + Assert.assertEquals("{3.000; 2.000; 1.000}", createVector(3, 2, 1, 3).toString(format)); + } + + @Test(expected=DimensionMismatchException.class) + public void testWrongDimension() throws DimensionMismatchException { + new Vector3DDS(new DerivativeStructure[] { + new DerivativeStructure(3, 1, 0, 2), + new DerivativeStructure(3, 1, 0, 5) + }); + } + + @Test + public void testCoordinates() { + Vector3DDS v = createVector(1, 2, 3, 3); + Assert.assertTrue(FastMath.abs(v.getX().getValue() - 1) < 1.0e-12); + Assert.assertTrue(FastMath.abs(v.getY().getValue() - 2) < 1.0e-12); + Assert.assertTrue(FastMath.abs(v.getZ().getValue() - 3) < 1.0e-12); + DerivativeStructure[] coordinates = v.toArray(); + Assert.assertTrue(FastMath.abs(coordinates[0].getValue() - 1) < 1.0e-12); + Assert.assertTrue(FastMath.abs(coordinates[1].getValue() - 2) < 1.0e-12); + Assert.assertTrue(FastMath.abs(coordinates[2].getValue() - 3) < 1.0e-12); + } + + @Test + public void testNorm1() { + Assert.assertEquals( 0.0, createVector(0, 0, 0, 3).getNorm1().getValue(), 0); + Assert.assertEquals( 6.0, createVector(1, -2, 3, 3).getNorm1().getValue(), 0); + Assert.assertEquals( 1.0, createVector(1, -2, 3, 3).getNorm1().getPartialDerivative(1, 0, 0), 0); + Assert.assertEquals(-1.0, createVector(1, -2, 3, 3).getNorm1().getPartialDerivative(0, 1, 0), 0); + Assert.assertEquals( 1.0, createVector(1, -2, 3, 3).getNorm1().getPartialDerivative(0, 0, 1), 0); + } + + @Test + public void testNorm() { + double r = FastMath.sqrt(14); + Assert.assertEquals(0.0, createVector(0, 0, 0, 3).getNorm().getValue(), 0); + Assert.assertEquals(r, createVector(1, 2, 3, 3).getNorm().getValue(), 1.0e-12); + Assert.assertEquals( 1.0 / r, createVector(1, 2, 3, 3).getNorm().getPartialDerivative(1, 0, 0), 0); + Assert.assertEquals( 2.0 / r, createVector(1, 2, 3, 3).getNorm().getPartialDerivative(0, 1, 0), 0); + Assert.assertEquals( 3.0 / r, createVector(1, 2, 3, 3).getNorm().getPartialDerivative(0, 0, 1), 0); + } + + @Test + public void testNormSq() { + Assert.assertEquals(0.0, createVector(0, 0, 0, 3).getNormSq().getValue(), 0); + Assert.assertEquals(14, createVector(1, 2, 3, 3).getNormSq().getValue(), 1.0e-12); + Assert.assertEquals( 2, createVector(1, 2, 3, 3).getNormSq().getPartialDerivative(1, 0, 0), 0); + Assert.assertEquals( 4, createVector(1, 2, 3, 3).getNormSq().getPartialDerivative(0, 1, 0), 0); + Assert.assertEquals( 6, createVector(1, 2, 3, 3).getNormSq().getPartialDerivative(0, 0, 1), 0); + } + + @Test + public void testNormInf() { + Assert.assertEquals( 0.0, createVector(0, 0, 0, 3).getNormInf().getValue(), 0); + Assert.assertEquals( 3.0, createVector(1, -2, 3, 3).getNormInf().getValue(), 0); + Assert.assertEquals( 0.0, createVector(1, -2, 3, 3).getNormInf().getPartialDerivative(1, 0, 0), 0); + Assert.assertEquals( 0.0, createVector(1, -2, 3, 3).getNormInf().getPartialDerivative(0, 1, 0), 0); + Assert.assertEquals( 1.0, createVector(1, -2, 3, 3).getNormInf().getPartialDerivative(0, 0, 1), 0); + Assert.assertEquals( 3.0, createVector(2, -1, 3, 3).getNormInf().getValue(), 0); + Assert.assertEquals( 0.0, createVector(2, -1, 3, 3).getNormInf().getPartialDerivative(1, 0, 0), 0); + Assert.assertEquals( 0.0, createVector(2, -1, 3, 3).getNormInf().getPartialDerivative(0, 1, 0), 0); + Assert.assertEquals( 1.0, createVector(2, -1, 3, 3).getNormInf().getPartialDerivative(0, 0, 1), 0); + Assert.assertEquals( 3.0, createVector(1, -3, 2, 3).getNormInf().getValue(), 0); + Assert.assertEquals( 0.0, createVector(1, -3, 2, 3).getNormInf().getPartialDerivative(1, 0, 0), 0); + Assert.assertEquals(-1.0, createVector(1, -3, 2, 3).getNormInf().getPartialDerivative(0, 1, 0), 0); + Assert.assertEquals( 0.0, createVector(1, -3, 2, 3).getNormInf().getPartialDerivative(0, 0, 1), 0); + Assert.assertEquals( 3.0, createVector(2, -3, 1, 3).getNormInf().getValue(), 0); + Assert.assertEquals( 0.0, createVector(2, -3, 1, 3).getNormInf().getPartialDerivative(1, 0, 0), 0); + Assert.assertEquals(-1.0, createVector(2, -3, 1, 3).getNormInf().getPartialDerivative(0, 1, 0), 0); + Assert.assertEquals( 0.0, createVector(2, -3, 1, 3).getNormInf().getPartialDerivative(0, 0, 1), 0); + Assert.assertEquals( 3.0, createVector(3, -1, 2, 3).getNormInf().getValue(), 0); + Assert.assertEquals( 1.0, createVector(3, -1, 2, 3).getNormInf().getPartialDerivative(1, 0, 0), 0); + Assert.assertEquals( 0.0, createVector(3, -1, 2, 3).getNormInf().getPartialDerivative(0, 1, 0), 0); + Assert.assertEquals( 0.0, createVector(3, -1, 2, 3).getNormInf().getPartialDerivative(0, 0, 1), 0); + Assert.assertEquals( 3.0, createVector(3, -2, 1, 3).getNormInf().getValue(), 0); + Assert.assertEquals( 1.0, createVector(3, -2, 1, 3).getNormInf().getPartialDerivative(1, 0, 0), 0); + Assert.assertEquals( 0.0, createVector(3, -2, 1, 3).getNormInf().getPartialDerivative(0, 1, 0), 0); + Assert.assertEquals( 0.0, createVector(3, -2, 1, 3).getNormInf().getPartialDerivative(0, 0, 1), 0); + } + + @Test + public void testDistance1() { + Vector3DDS v1 = createVector(1, -2, 3, 3); + Vector3DDS v2 = createVector(-4, 2, 0, 3); + Assert.assertEquals(0.0, Vector3DDS.distance1(createVector(-1, 0, 0, 3), createVector(-1, 0, 0, 3)).getValue(), 0); + DerivativeStructure distance = Vector3DDS.distance1(v1, v2); + Assert.assertEquals(12.0, distance.getValue(), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(0, 0, 1), 1.0e-12); + distance = Vector3DDS.distance1(v1, new Vector3D(-4, 2, 0)); + Assert.assertEquals(12.0, distance.getValue(), 1.0e-12); + Assert.assertEquals( 1, distance.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(-1, distance.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals( 1, distance.getPartialDerivative(0, 0, 1), 1.0e-12); + distance = Vector3DDS.distance1(new Vector3D(-4, 2, 0), v1); + Assert.assertEquals(12.0, distance.getValue(), 1.0e-12); + Assert.assertEquals( 1, distance.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(-1, distance.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals( 1, distance.getPartialDerivative(0, 0, 1), 1.0e-12); + Assert.assertEquals(v1.subtract(v2).getNorm1().getValue(), Vector3DDS.distance1(v1, v2).getValue(), 1.0e-12); + } + + @Test + public void testDistance() { + Vector3DDS v1 = createVector(1, -2, 3, 3); + Vector3DDS v2 = createVector(-4, 2, 0, 3); + Assert.assertEquals(0.0, Vector3DDS.distance(createVector(-1, 0, 0, 3), createVector(-1, 0, 0, 3)).getValue(), 0); + DerivativeStructure distance = Vector3DDS.distance(v1, v2); + Assert.assertEquals(FastMath.sqrt(50), distance.getValue(), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(0, 0, 1), 1.0e-12); + distance = Vector3DDS.distance(v1, new Vector3D(-4, 2, 0)); + Assert.assertEquals(FastMath.sqrt(50), distance.getValue(), 1.0e-12); + Assert.assertEquals( 5 / FastMath.sqrt(50), distance.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(-4 / FastMath.sqrt(50), distance.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals( 3 / FastMath.sqrt(50), distance.getPartialDerivative(0, 0, 1), 1.0e-12); + distance = Vector3DDS.distance(new Vector3D(-4, 2, 0), v1); + Assert.assertEquals(FastMath.sqrt(50), distance.getValue(), 1.0e-12); + Assert.assertEquals( 5 / FastMath.sqrt(50), distance.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(-4 / FastMath.sqrt(50), distance.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals( 3 / FastMath.sqrt(50), distance.getPartialDerivative(0, 0, 1), 1.0e-12); + Assert.assertEquals(v1.subtract(v2).getNorm().getValue(), Vector3DDS.distance(v1, v2).getValue(), 1.0e-12); + } + + @Test + public void testDistanceSq() { + Vector3DDS v1 = createVector(1, -2, 3, 3); + Vector3DDS v2 = createVector(-4, 2, 0, 3); + Assert.assertEquals(0.0, Vector3DDS.distanceSq(createVector(-1, 0, 0, 3), createVector(-1, 0, 0, 3)).getValue(), 0); + DerivativeStructure distanceSq = Vector3DDS.distanceSq(v1, v2); + Assert.assertEquals(50.0, distanceSq.getValue(), 1.0e-12); + Assert.assertEquals(0, distanceSq.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(0, distanceSq.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals(0, distanceSq.getPartialDerivative(0, 0, 1), 1.0e-12); + distanceSq = Vector3DDS.distanceSq(v1, new Vector3D(-4, 2, 0)); + Assert.assertEquals(50.0, distanceSq.getValue(), 1.0e-12); + Assert.assertEquals(10, distanceSq.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(-8, distanceSq.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals( 6, distanceSq.getPartialDerivative(0, 0, 1), 1.0e-12); + distanceSq = Vector3DDS.distanceSq(new Vector3D(-4, 2, 0), v1); + Assert.assertEquals(50.0, distanceSq.getValue(), 1.0e-12); + Assert.assertEquals(10, distanceSq.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(-8, distanceSq.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals( 6, distanceSq.getPartialDerivative(0, 0, 1), 1.0e-12); + Assert.assertEquals(Vector3DDS.distance(v1, v2).multiply(Vector3DDS.distance(v1, v2)).getValue(), + Vector3DDS.distanceSq(v1, v2).getValue(), 1.0e-12); + } + + @Test + public void testDistanceInf() { + Vector3DDS v1 = createVector(1, -2, 3, 3); + Vector3DDS v2 = createVector(-4, 2, 0, 3); + Assert.assertEquals(0.0, Vector3DDS.distanceInf(createVector(-1, 0, 0, 3), createVector(-1, 0, 0, 3)).getValue(), 0); + DerivativeStructure distance = Vector3DDS.distanceInf(v1, v2); + Assert.assertEquals(5.0, distance.getValue(), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(0, 0, 1), 1.0e-12); + distance = Vector3DDS.distanceInf(v1, new Vector3D(-4, 2, 0)); + Assert.assertEquals(5.0, distance.getValue(), 1.0e-12); + Assert.assertEquals(1, distance.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(0, 0, 1), 1.0e-12); + distance = Vector3DDS.distanceInf(new Vector3D(-4, 2, 0), v1); + Assert.assertEquals(5.0, distance.getValue(), 1.0e-12); + Assert.assertEquals(1, distance.getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals(0, distance.getPartialDerivative(0, 0, 1), 1.0e-12); + Assert.assertEquals(v1.subtract(v2).getNormInf().getValue(), Vector3DDS.distanceInf(v1, v2).getValue(), 1.0e-12); + + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector( 1, -2, 3, 3), + createVector(-4, 2, 0, 3)).getValue(), + 1.0e-12); + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector( 1, 3, -2, 3), + createVector(-4, 0, 2, 3)).getValue(), + 1.0e-12); + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector(-2, 1, 3, 3), + createVector( 2, -4, 0, 3)).getValue(), + 1.0e-12); + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector(-2, 3, 1, 3), + createVector( 2, 0, -4, 3)).getValue(), + 1.0e-12); + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector(3, -2, 1, 3), + createVector(0, 2, -4, 3)).getValue(), + 1.0e-12); + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector(3, 1, -2, 3), + createVector(0, -4, 2, 3)).getValue(), + 1.0e-12); + + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector( 1, -2, 3, 3), + new Vector3D(-4, 2, 0)).getValue(), + 1.0e-12); + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector( 1, 3, -2, 3), + new Vector3D(-4, 0, 2)).getValue(), + 1.0e-12); + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector(-2, 1, 3, 3), + new Vector3D( 2, -4, 0)).getValue(), + 1.0e-12); + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector(-2, 3, 1, 3), + new Vector3D( 2, 0, -4)).getValue(), + 1.0e-12); + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector(3, -2, 1, 3), + new Vector3D(0, 2, -4)).getValue(), + 1.0e-12); + Assert.assertEquals(5.0, + Vector3DDS.distanceInf(createVector(3, 1, -2, 3), + new Vector3D(0, -4, 2)).getValue(), + 1.0e-12); + + } + + @Test + public void testSubtract() { + Vector3DDS v1 = createVector(1, 2, 3, 3); + Vector3DDS v2 = createVector(-3, -2, -1, 3); + v1 = v1.subtract(v2); + checkVector(v1, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + checkVector(v2.subtract(v1), -7, -6, -5, 1, 0, 0, 0, 1, 0, 0, 0, 1); + checkVector(v2.subtract(new Vector3D(4, 4, 4)), -7, -6, -5, 1, 0, 0, 0, 1, 0, 0, 0, 1); + checkVector(v2.subtract(3, v1), -15, -14, -13, 1, 0, 0, 0, 1, 0, 0, 0, 1); + checkVector(v2.subtract(3, new Vector3D(4, 4, 4)), -15, -14, -13, 1, 0, 0, 0, 1, 0, 0, 0, 1); + checkVector(v2.subtract(new DerivativeStructure(3, 1, 2, 3), new Vector3D(4, 4, 4)), + -15, -14, -13, 1, 0, -4, 0, 1, -4, 0, 0, -3); + + checkVector(createVector(1, 2, 3, 4).subtract(new DerivativeStructure(4, 1, 3, 5.0), + createVector(3, -2, 1, 4)), + -14, 12, -2, + -4, 0, 0, -3, + 0, -4, 0, 2, + 0, 0, -4, -1); + + } + + @Test + public void testAdd() { + Vector3DDS v1 = createVector(1, 2, 3, 3); + Vector3DDS v2 = createVector(-3, -2, -1, 3); + v1 = v1.add(v2); + checkVector(v1, -2, 0, 2, 2, 0, 0, 0, 2, 0, 0, 0, 2); + + checkVector(v2.add(v1), -5, -2, 1, 3, 0, 0, 0, 3, 0, 0, 0, 3); + checkVector(v2.add(new Vector3D(-2, 0, 2)), -5, -2, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1); + checkVector(v2.add(3, v1), -9, -2, 5, 7, 0, 0, 0, 7, 0, 0, 0, 7); + checkVector(v2.add(3, new Vector3D(-2, 0, 2)), -9, -2, 5, 1, 0, 0, 0, 1, 0, 0, 0, 1); + checkVector(v2.add(new DerivativeStructure(3, 1, 2, 3), new Vector3D(-2, 0, 2)), + -9, -2, 5, 1, 0, -2, 0, 1, 0, 0, 0, 3); + + checkVector(createVector(1, 2, 3, 4).add(new DerivativeStructure(4, 1, 3, 5.0), + createVector(3, -2, 1, 4)), + 16, -8, 8, + 6, 0, 0, 3, + 0, 6, 0, -2, + 0, 0, 6, 1); + + } + + @Test + public void testScalarProduct() { + Vector3DDS v = createVector(1, 2, 3, 3); + v = v.scalarMultiply(3); + checkVector(v, 3, 6, 9); + + checkVector(v.scalarMultiply(0.5), 1.5, 3, 4.5); + } + + @Test + public void testVectorialProducts() { + Vector3DDS v1 = createVector(2, 1, -4, 3); + Vector3DDS v2 = createVector(3, 1, -1, 3); + + Assert.assertTrue(FastMath.abs(Vector3DDS.dotProduct(v1, v2).getValue() - 11) < 1.0e-12); + + Vector3DDS v3 = Vector3DDS.crossProduct(v1, v2); + checkVector(v3, 3, -10, -1); + + Assert.assertTrue(FastMath.abs(Vector3DDS.dotProduct(v1, v3).getValue()) < 1.0e-12); + Assert.assertTrue(FastMath.abs(Vector3DDS.dotProduct(v2, v3).getValue()) < 1.0e-12); + } + + @Test + public void testCrossProductCancellation() { + Vector3DDS v1 = createVector(9070467121.0, 4535233560.0, 1, 3); + Vector3DDS v2 = createVector(9070467123.0, 4535233561.0, 1, 3); + checkVector(Vector3DDS.crossProduct(v1, v2), -1, 2, 1); + + double scale = FastMath.scalb(1.0, 100); + Vector3DDS big1 = new Vector3DDS(scale, v1); + Vector3DDS small2 = new Vector3DDS(1 / scale, v2); + checkVector(Vector3DDS.crossProduct(big1, small2), -1, 2, 1); + + } + + @Test + public void testAngular() { + Assert.assertEquals(0, createVector(1, 0, 0, 3).getAlpha().getValue(), 1.0e-10); + Assert.assertEquals(0, createVector(1, 0, 0, 3).getDelta().getValue(), 1.0e-10); + Assert.assertEquals(FastMath.PI / 2, createVector(0, 1, 0, 3).getAlpha().getValue(), 1.0e-10); + Assert.assertEquals(0, createVector(0, 1, 0, 3).getDelta().getValue(), 1.0e-10); + Assert.assertEquals(FastMath.PI / 2, createVector(0, 0, 1, 3).getDelta().getValue(), 1.0e-10); + + Vector3DDS u = createVector(-1, 1, -1, 3); + Assert.assertEquals(3 * FastMath.PI /4, u.getAlpha().getValue(), 1.0e-10); + Assert.assertEquals(-1.0 / FastMath.sqrt(3), u.getDelta().sin().getValue(), 1.0e-10); + } + + @Test + public void testAngularSeparation() throws MathArithmeticException { + Vector3DDS v1 = createVector(2, -1, 4, 3); + + Vector3DDS k = v1.normalize(); + Vector3DDS i = k.orthogonal(); + Vector3DDS v2 = k.scalarMultiply(FastMath.cos(1.2)).add(i.scalarMultiply(FastMath.sin(1.2))); + + Assert.assertTrue(FastMath.abs(Vector3DDS.angle(v1, v2).getValue() - 1.2) < 1.0e-12); + } + + @Test + public void testNormalize() throws MathArithmeticException { + Assert.assertEquals(1.0, createVector(5, -4, 2, 3).normalize().getNorm().getValue(), 1.0e-12); + try { + createVector(0, 0, 0, 3).normalize(); + Assert.fail("an exception should have been thrown"); + } catch (MathArithmeticException ae) { + // expected behavior + } + } + + @Test + public void testNegate() { + checkVector(createVector(0.1, 2.5, 1.3, 3).negate(), + -0.1, -2.5, -1.3, -1, 0, 0, 0, -1, 0, 0, 0, -1); + } + + @Test + public void testOrthogonal() throws MathArithmeticException { + Vector3DDS v1 = createVector(0.1, 2.5, 1.3, 3); + Assert.assertEquals(0.0, Vector3DDS.dotProduct(v1, v1.orthogonal()).getValue(), 1.0e-12); + Vector3DDS v2 = createVector(2.3, -0.003, 7.6, 3); + Assert.assertEquals(0.0, Vector3DDS.dotProduct(v2, v2.orthogonal()).getValue(), 1.0e-12); + Vector3DDS v3 = createVector(-1.7, 1.4, 0.2, 3); + Assert.assertEquals(0.0, Vector3DDS.dotProduct(v3, v3.orthogonal()).getValue(), 1.0e-12); + Vector3DDS v4 = createVector(4.2, 0.1, -1.8, 3); + Assert.assertEquals(0.0, Vector3DDS.dotProduct(v4, v4.orthogonal()).getValue(), 1.0e-12); + try { + createVector(0, 0, 0, 3).orthogonal(); + Assert.fail("an exception should have been thrown"); + } catch (MathArithmeticException ae) { + // expected behavior + } + } + + @Test + public void testAngle() throws MathArithmeticException { + Assert.assertEquals(0.22572612855273393616, + Vector3DDS.angle(createVector(1, 2, 3, 3), createVector(4, 5, 6, 3)).getValue(), + 1.0e-12); + Assert.assertEquals(7.98595620686106654517199e-8, + Vector3DDS.angle(createVector(1, 2, 3, 3), createVector(2, 4, 6.000001, 3)).getValue(), + 1.0e-12); + Assert.assertEquals(3.14159257373023116985197793156, + Vector3DDS.angle(createVector(1, 2, 3, 3), createVector(-2, -4, -6.000001, 3)).getValue(), + 1.0e-12); + try { + Vector3DDS.angle(createVector(0, 0, 0, 3), createVector(1, 0, 0, 3)); + Assert.fail("an exception should have been thrown"); + } catch (MathArithmeticException ae) { + // expected behavior + } + } + + @Test + public void testAccurateDotProduct() { + // the following two vectors are nearly but not exactly orthogonal + // naive dot product (i.e. computing u1.x * u2.x + u1.y * u2.y + u1.z * u2.z + // leads to a result of 0.0, instead of the correct -1.855129... + Vector3DDS u1 = createVector(-1321008684645961.0 / 268435456.0, + -5774608829631843.0 / 268435456.0, + -7645843051051357.0 / 8589934592.0, 3); + Vector3DDS u2 = createVector(-5712344449280879.0 / 2097152.0, + -4550117129121957.0 / 2097152.0, + 8846951984510141.0 / 131072.0, 3); + DerivativeStructure sNaive = u1.getX().multiply(u2.getX()).add(u1.getY().multiply(u2.getY())).add(u1.getZ().multiply(u2.getZ())); + DerivativeStructure sAccurate = u1.dotProduct(u2); + Assert.assertEquals(0.0, sNaive.getValue(), 1.0e-30); + Assert.assertEquals(-2088690039198397.0 / 1125899906842624.0, sAccurate.getValue(), 1.0e-16); + } + + @Test + public void testDotProduct() { + // we compare accurate versus naive dot product implementations + // on regular vectors (i.e. not extreme cases like in the previous test) + Well1024a random = new Well1024a(553267312521321234l); + for (int i = 0; i < 10000; ++i) { + double ux = 10000 * random.nextDouble(); + double uy = 10000 * random.nextDouble(); + double uz = 10000 * random.nextDouble(); + double vx = 10000 * random.nextDouble(); + double vy = 10000 * random.nextDouble(); + double vz = 10000 * random.nextDouble(); + double sNaive = ux * vx + uy * vy + uz * vz; + + Vector3DDS uds = createVector(ux, uy, uz, 3); + Vector3DDS vds = createVector(vx, vy, vz, 3); + Vector3D v = new Vector3D(vx, vy, vz); + + DerivativeStructure sAccurate = Vector3DDS.dotProduct(uds, vds); + Assert.assertEquals(sNaive, sAccurate.getValue(), 2.5e-16 * sNaive); + Assert.assertEquals(ux + vx, sAccurate.getPartialDerivative(1, 0, 0), 2.5e-16 * sNaive); + Assert.assertEquals(uy + vy, sAccurate.getPartialDerivative(0, 1, 0), 2.5e-16 * sNaive); + Assert.assertEquals(uz + vz, sAccurate.getPartialDerivative(0, 0, 1), 2.5e-16 * sNaive); + + sAccurate = Vector3DDS.dotProduct(uds, v); + Assert.assertEquals(sNaive, sAccurate.getValue(), 2.5e-16 * sNaive); + Assert.assertEquals(vx, sAccurate.getPartialDerivative(1, 0, 0), 2.5e-16 * sNaive); + Assert.assertEquals(vy, sAccurate.getPartialDerivative(0, 1, 0), 2.5e-16 * sNaive); + Assert.assertEquals(vz, sAccurate.getPartialDerivative(0, 0, 1), 2.5e-16 * sNaive); + + sAccurate = Vector3DDS.dotProduct(v, uds); + Assert.assertEquals(sNaive, sAccurate.getValue(), 2.5e-16 * sNaive); + Assert.assertEquals(vx, sAccurate.getPartialDerivative(1, 0, 0), 2.5e-16 * sNaive); + Assert.assertEquals(vy, sAccurate.getPartialDerivative(0, 1, 0), 2.5e-16 * sNaive); + Assert.assertEquals(vz, sAccurate.getPartialDerivative(0, 0, 1), 2.5e-16 * sNaive); + + } + } + + @Test + public void testAccurateCrossProduct() { + // the vectors u1 and u2 are nearly but not exactly anti-parallel + // (7.31e-16 degrees from 180 degrees) naive cross product (i.e. + // computing u1.x * u2.x + u1.y * u2.y + u1.z * u2.z + // leads to a result of [0.0009765, -0.0001220, -0.0039062], + // instead of the correct [0.0006913, -0.0001254, -0.0007909] + final Vector3DDS u1 = createVector(-1321008684645961.0 / 268435456.0, + -5774608829631843.0 / 268435456.0, + -7645843051051357.0 / 8589934592.0, 3); + final Vector3DDS u2 = createVector( 1796571811118507.0 / 2147483648.0, + 7853468008299307.0 / 2147483648.0, + 2599586637357461.0 / 17179869184.0, 3); + final Vector3DDS u3 = createVector(12753243807587107.0 / 18446744073709551616.0, + -2313766922703915.0 / 18446744073709551616.0, + -227970081415313.0 / 288230376151711744.0, 3); + Vector3DDS cNaive = new Vector3DDS(u1.getY().multiply(u2.getZ()).subtract(u1.getZ().multiply(u2.getY())), + u1.getZ().multiply(u2.getX()).subtract(u1.getX().multiply(u2.getZ())), + u1.getX().multiply(u2.getY()).subtract(u1.getY().multiply(u2.getX()))); + Vector3DDS cAccurate = u1.crossProduct(u2); + Assert.assertTrue(u3.distance(cNaive).getValue() > 2.9 * u3.getNorm().getValue()); + Assert.assertEquals(0.0, u3.distance(cAccurate).getValue(), 1.0e-30 * cAccurate.getNorm().getValue()); + } + + @Test + public void testCrossProduct() { + // we compare accurate versus naive cross product implementations + // on regular vectors (i.e. not extreme cases like in the previous test) + Well1024a random = new Well1024a(885362227452043214l); + for (int i = 0; i < 10000; ++i) { + double ux = random.nextDouble(); + double uy = random.nextDouble(); + double uz = random.nextDouble(); + double vx = random.nextDouble(); + double vy = random.nextDouble(); + double vz = random.nextDouble(); + Vector3D cNaive = new Vector3D(uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx); + + Vector3DDS uds = createVector(ux, uy, uz, 3); + Vector3DDS vds = createVector(vx, vy, vz, 3); + Vector3D v = new Vector3D(vx, vy, vz); + + checkVector(Vector3DDS.crossProduct(uds, vds), + cNaive.getX(), cNaive.getY(), cNaive.getZ(), + 0, vz - uz, uy - vy, + uz - vz, 0, vx - ux, + vy - uy, ux - vx, 0); + + checkVector(Vector3DDS.crossProduct(uds, v), + cNaive.getX(), cNaive.getY(), cNaive.getZ(), + 0, vz, -vy, + -vz, 0, vx, + vy, -vx, 0); + + checkVector(Vector3DDS.crossProduct(v, uds), + -cNaive.getX(), -cNaive.getY(), -cNaive.getZ(), + 0, -vz, vy, + vz, 0, -vx, + -vy, vx, 0); + + } + } + + private Vector3DDS createVector(double x, double y, double z, int params) { + return new Vector3DDS(new DerivativeStructure(params, 1, 0, x), + new DerivativeStructure(params, 1, 1, y), + new DerivativeStructure(params, 1, 2, z)); + } + + private void checkVector(Vector3DDS v, double x, double y, double z) { + Assert.assertEquals(x, v.getX().getValue(), 1.0e-12); + Assert.assertEquals(y, v.getY().getValue(), 1.0e-12); + Assert.assertEquals(z, v.getZ().getValue(), 1.0e-12); + } + + private void checkVector(Vector3DDS v, double x, double y, double z, + double dxdx, double dxdy, double dxdz, + double dydx, double dydy, double dydz, + double dzdx, double dzdy, double dzdz) { + Assert.assertEquals(x, v.getX().getValue(), 1.0e-12); + Assert.assertEquals(y, v.getY().getValue(), 1.0e-12); + Assert.assertEquals(z, v.getZ().getValue(), 1.0e-12); + Assert.assertEquals(dxdx, v.getX().getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(dxdy, v.getX().getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals(dxdz, v.getX().getPartialDerivative(0, 0, 1), 1.0e-12); + Assert.assertEquals(dydx, v.getY().getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(dydy, v.getY().getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals(dydz, v.getY().getPartialDerivative(0, 0, 1), 1.0e-12); + Assert.assertEquals(dzdx, v.getZ().getPartialDerivative(1, 0, 0), 1.0e-12); + Assert.assertEquals(dzdy, v.getZ().getPartialDerivative(0, 1, 0), 1.0e-12); + Assert.assertEquals(dzdz, v.getZ().getPartialDerivative(0, 0, 1), 1.0e-12); + } + + private void checkVector(Vector3DDS v, double x, double y, double z, + double dxdx, double dxdy, double dxdz, double dxdt, + double dydx, double dydy, double dydz, double dydt, + double dzdx, double dzdy, double dzdz, double dzdt) { + Assert.assertEquals(x, v.getX().getValue(), 1.0e-12); + Assert.assertEquals(y, v.getY().getValue(), 1.0e-12); + Assert.assertEquals(z, v.getZ().getValue(), 1.0e-12); + Assert.assertEquals(dxdx, v.getX().getPartialDerivative(1, 0, 0, 0), 1.0e-12); + Assert.assertEquals(dxdy, v.getX().getPartialDerivative(0, 1, 0, 0), 1.0e-12); + Assert.assertEquals(dxdz, v.getX().getPartialDerivative(0, 0, 1, 0), 1.0e-12); + Assert.assertEquals(dxdt, v.getX().getPartialDerivative(0, 0, 0, 1), 1.0e-12); + Assert.assertEquals(dydx, v.getY().getPartialDerivative(1, 0, 0, 0), 1.0e-12); + Assert.assertEquals(dydy, v.getY().getPartialDerivative(0, 1, 0, 0), 1.0e-12); + Assert.assertEquals(dydz, v.getY().getPartialDerivative(0, 0, 1, 0), 1.0e-12); + Assert.assertEquals(dydt, v.getY().getPartialDerivative(0, 0, 0, 1), 1.0e-12); + Assert.assertEquals(dzdx, v.getZ().getPartialDerivative(1, 0, 0, 0), 1.0e-12); + Assert.assertEquals(dzdy, v.getZ().getPartialDerivative(0, 1, 0, 0), 1.0e-12); + Assert.assertEquals(dzdz, v.getZ().getPartialDerivative(0, 0, 1, 0), 1.0e-12); + Assert.assertEquals(dzdt, v.getZ().getPartialDerivative(0, 0, 0, 1), 1.0e-12); + } + +}