Author: luc
Date: Fri Aug  5 15:02:56 2011
New Revision: 1154253

URL: http://svn.apache.org/viewvc?rev=1154253&view=rev
Log:
Use the new highly accurate linearCombination utility methods in 3D Euclidean 
geometry.

Modified:
    
commons/proper/math/trunk/src/main/java/org/apache/commons/math/geometry/euclidean/threed/Vector3D.java
    
commons/proper/math/trunk/src/test/java/org/apache/commons/math/geometry/euclidean/threed/Vector3DTest.java

Modified: 
commons/proper/math/trunk/src/main/java/org/apache/commons/math/geometry/euclidean/threed/Vector3D.java
URL: 
http://svn.apache.org/viewvc/commons/proper/math/trunk/src/main/java/org/apache/commons/math/geometry/euclidean/threed/Vector3D.java?rev=1154253&r1=1154252&r2=1154253&view=diff
==============================================================================
--- 
commons/proper/math/trunk/src/main/java/org/apache/commons/math/geometry/euclidean/threed/Vector3D.java
 (original)
+++ 
commons/proper/math/trunk/src/main/java/org/apache/commons/math/geometry/euclidean/threed/Vector3D.java
 Fri Aug  5 15:02:56 2011
@@ -132,9 +132,9 @@ public class Vector3D implements Seriali
      * @param u2 second base (unscaled) vector
      */
     public Vector3D(double a1, Vector3D u1, double a2, Vector3D u2) {
-        this.x = a1 * u1.x + a2 * u2.x;
-        this.y = a1 * u1.y + a2 * u2.y;
-        this.z = a1 * u1.z + a2 * u2.z;
+        this.x = MathUtils.linearCombination(a1, u1.x, a2, u2.x);
+        this.y = MathUtils.linearCombination(a1, u1.y, a2, u2.y);
+        this.z = MathUtils.linearCombination(a1, u1.z, a2, u2.z);
     }
 
     /** Linear constructor
@@ -149,9 +149,9 @@ public class Vector3D implements Seriali
      */
     public Vector3D(double a1, Vector3D u1, double a2, Vector3D u2,
                     double a3, Vector3D u3) {
-        this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x;
-        this.y = a1 * u1.y + a2 * u2.y + a3 * u3.y;
-        this.z = a1 * u1.z + a2 * u2.z + a3 * u3.z;
+        this.x = MathUtils.linearCombination(a1, u1.x, a2, u2.x, a3, u3.x);
+        this.y = MathUtils.linearCombination(a1, u1.y, a2, u2.y, a3, u3.y);
+        this.z = MathUtils.linearCombination(a1, u1.z, a2, u2.z, a3, u3.z);
     }
 
     /** Linear constructor
@@ -168,9 +168,9 @@ public class Vector3D implements Seriali
      */
     public Vector3D(double a1, Vector3D u1, double a2, Vector3D u2,
                     double a3, Vector3D u3, double a4, Vector3D u4) {
-        this.x = a1 * u1.x + a2 * u2.x + a3 * u3.x + a4 * u4.x;
-        this.y = a1 * u1.y + a2 * u2.y + a3 * u3.y + a4 * u4.y;
-        this.z = a1 * u1.z + a2 * u2.z + a3 * u3.z + a4 * u4.z;
+        this.x = MathUtils.linearCombination(a1, u1.x, a2, u2.x, a3, u3.x, a4, 
u4.x);
+        this.y = MathUtils.linearCombination(a1, u1.y, a2, u2.y, a3, u3.y, a4, 
u4.y);
+        this.z = MathUtils.linearCombination(a1, u1.z, a2, u2.z, a3, u3.z, a4, 
u4.z);
     }
 
     /** Get the abscissa of the vector.
@@ -214,11 +214,13 @@ public class Vector3D implements Seriali
 
     /** {@inheritDoc} */
     public double getNorm() {
+        // there are no cancellation problems here, so we use the 
straightforward formula
         return FastMath.sqrt (x * x + y * y + z * z);
     }
 
     /** {@inheritDoc} */
     public double getNormSq() {
+        // there are no cancellation problems here, so we use the 
straightforward formula
         return x * x + y * y + z * z;
     }
 
@@ -251,8 +253,7 @@ public class Vector3D implements Seriali
 
     /** {@inheritDoc} */
     public Vector3D add(double factor, final Vector<Euclidean3D> v) {
-        final Vector3D v3 = (Vector3D) v;
-        return new Vector3D(x + factor * v3.x, y + factor * v3.y, z + factor * 
v3.z);
+        return new Vector3D(1, this, factor, (Vector3D) v);
     }
 
     /** {@inheritDoc} */
@@ -263,8 +264,7 @@ public class Vector3D implements Seriali
 
     /** {@inheritDoc} */
     public Vector3D subtract(final double factor, final Vector<Euclidean3D> v) 
{
-        final Vector3D v3 = (Vector3D) v;
-        return new Vector3D(x - factor * v3.x, y - factor * v3.y, z - factor * 
v3.z);
+        return new Vector3D(1, this, -factor, (Vector3D) v);
     }
 
     /** {@inheritDoc} */
@@ -328,7 +328,7 @@ public class Vector3D implements Seriali
             throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
         }
 
-        double dot = dotProduct(v1, v2);
+        double dot = v1.dotProduct(v2);
         double threshold = normProduct * 0.9999;
         if ((dot < -threshold) || (dot > threshold)) {
             // the vectors are almost aligned, compute using the sine
@@ -416,52 +416,28 @@ public class Vector3D implements Seriali
         return 643 * (164 * MathUtils.hash(x) +  3 * MathUtils.hash(y) +  
MathUtils.hash(z));
     }
 
-    /** {@inheritDoc} */
+    /** {@inheritDoc}
+     * <p>
+     * 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.
+     * </p>
+     * @see MathUtils#linearCombination(double, double, double, double, 
double, double)
+     */
     public double dotProduct(final Vector<Euclidean3D> v) {
         final Vector3D v3 = (Vector3D) v;
-        return x * v3.x + y * v3.y + z * v3.z;
+        return MathUtils.linearCombination(x, v3.x, y, v3.y, z, v3.z);
     }
 
     /** Compute the cross-product of the instance with another vector.
-     * @param v other vectorvector
+     * @param v other vector
      * @return the cross product this ^ v as a new Vector3D
      */
     public Vector3D crossProduct(final Vector<Euclidean3D> v) {
         final Vector3D v3 = (Vector3D) v;
-
-        final double n1 = getNormSq();
-        final double n2 = v.getNormSq();
-        if ((n1 * n2) < MathUtils.SAFE_MIN) {
-            return ZERO;
-        }
-
-        // rescale both vectors without losing precision,
-        // to ensure their norm are the same order of magnitude
-        final int deltaExp = (FastMath.getExponent(n1) - 
FastMath.getExponent(n2)) / 4;
-        final double x1    = FastMath.scalb(x,   -deltaExp);
-        final double y1    = FastMath.scalb(y,   -deltaExp);
-        final double z1    = FastMath.scalb(z,   -deltaExp);
-        final double x2    = FastMath.scalb(v3.x, deltaExp);
-        final double y2    = FastMath.scalb(v3.y, deltaExp);
-        final double z2    = FastMath.scalb(v3.z, deltaExp);
-
-        // we reduce cancellation errors by preconditioning,
-        // we replace v1 by v3 = v1 - rho v2 with rho chosen in order to 
compute
-        // v3 without loss of precision. See Kahan lecture
-        // "Computing Cross-Products and Rotations in 2- and 3-Dimensional 
Euclidean Spaces"
-        // available at http://www.cs.berkeley.edu/~wkahan/MathH110/Cross.pdf
-
-        // compute rho as an 8 bits approximation of v1.v2 / v2.v2
-        final double ratio = (x1 * x2 + y1 * y2 + z1 * z2) / 
FastMath.scalb(n2, 2 * deltaExp);
-        final double rho   = FastMath.rint(256 * ratio) / 256;
-
-        final double x3 = x1 - rho * x2;
-        final double y3 = y1 - rho * y2;
-        final double z3 = z1 - rho * z2;
-
-        // compute cross product from v3 and v2 instead of v1 and v2
-        return new Vector3D(y3 * z2 - z3 * y2, z3 * x2 - x3 * z2, x3 * y2 - y3 
* x2);
-
+        return new Vector3D(MathUtils.linearCombination(y, v3.z, -z, v3.y),
+                            MathUtils.linearCombination(z, v3.x, -x, v3.z),
+                            MathUtils.linearCombination(x, v3.y, -y, v3.x));
     }
 
     /** {@inheritDoc} */

Modified: 
commons/proper/math/trunk/src/test/java/org/apache/commons/math/geometry/euclidean/threed/Vector3DTest.java
URL: 
http://svn.apache.org/viewvc/commons/proper/math/trunk/src/test/java/org/apache/commons/math/geometry/euclidean/threed/Vector3DTest.java?rev=1154253&r1=1154252&r2=1154253&view=diff
==============================================================================
--- 
commons/proper/math/trunk/src/test/java/org/apache/commons/math/geometry/euclidean/threed/Vector3DTest.java
 (original)
+++ 
commons/proper/math/trunk/src/test/java/org/apache/commons/math/geometry/euclidean/threed/Vector3DTest.java
 Fri Aug  5 15:02:56 2011
@@ -17,10 +17,9 @@
 
 package org.apache.commons.math.geometry.euclidean.threed;
 
-import org.apache.commons.math.geometry.euclidean.threed.Vector3D;
-import org.apache.commons.math.util.FastMath;
 import org.apache.commons.math.exception.MathArithmeticException;
-
+import org.apache.commons.math.random.Well1024a;
+import org.apache.commons.math.util.FastMath;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -236,6 +235,83 @@ public class Vector3DTest {
         }
     }
 
+    @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...
+        Vector3D u1 = new Vector3D(-1321008684645961.0 /  268435456.0,
+                                   -5774608829631843.0 /  268435456.0,
+                                   -7645843051051357.0 / 8589934592.0);
+        Vector3D u2 = new Vector3D(-5712344449280879.0 /    2097152.0,
+                                   -4550117129121957.0 /    2097152.0,
+                                    8846951984510141.0 /     131072.0);
+        double sNaive = u1.getX() * u2.getX() + u1.getY() * u2.getY() + 
u1.getZ() * u2.getZ();
+        double sAccurate = u1.dotProduct(u2);
+        Assert.assertEquals(0.0, sNaive, 1.0e-30);
+        Assert.assertEquals(-2088690039198397.0 / 1125899906842624.0, 
sAccurate, 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;
+            double sAccurate = new Vector3D(ux, uy, uz).dotProduct(new 
Vector3D(vx, vy, vz));
+            Assert.assertEquals(sNaive, sAccurate, 2.5e-16 * sAccurate);
+        }
+    }
+
+    @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 Vector3D u1 = new Vector3D(-1321008684645961.0 /   268435456.0,
+                                         -5774608829631843.0 /   268435456.0,
+                                         -7645843051051357.0 /  8589934592.0);
+        final Vector3D u2 = new Vector3D( 1796571811118507.0 /  2147483648.0,
+                                          7853468008299307.0 /  2147483648.0,
+                                          2599586637357461.0 / 17179869184.0);
+        final Vector3D u3 = new Vector3D(12753243807587107.0 / 
18446744073709551616.0, 
+                                         -2313766922703915.0 / 
18446744073709551616.0, 
+                                          -227970081415313.0 /   
288230376151711744.0);
+        Vector3D cNaive = new Vector3D(u1.getY() * u2.getZ() - u1.getZ() * 
u2.getY(),
+                                       u1.getZ() * u2.getX() - u1.getX() * 
u2.getZ(),
+                                       u1.getX() * u2.getY() - u1.getY() * 
u2.getX());
+        Vector3D cAccurate = u1.crossProduct(u2);
+        Assert.assertTrue(u3.distance(cNaive) > 2.9 * u3.getNorm());
+        Assert.assertEquals(0.0, u3.distance(cAccurate), 1.0e-30 * 
cAccurate.getNorm());
+    }
+
+    @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 = 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();
+            Vector3D cNaive = new Vector3D(uy * vz - uz * vy, uz * vx - ux * 
vz, ux * vy - uy * vx);
+            Vector3D cAccurate = new Vector3D(ux, uy, uz).crossProduct(new 
Vector3D(vx, vy, vz));
+            Assert.assertEquals(0.0, cAccurate.distance(cNaive), 6.0e-15 * 
cAccurate.getNorm());
+        }
+    }
+
     private void checkVector(Vector3D v, double x, double y, double z) {
         Assert.assertEquals(x, v.getX(), 1.0e-12);
         Assert.assertEquals(y, v.getY(), 1.0e-12);


Reply via email to