You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2018/12/14 04:34:16 UTC

[commons-numbers] 02/02: NUMBERS-76: Refactoring so class can be final (ValJO criterion).

This is an automated email from the ASF dual-hosted git repository.

erans pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git

commit c0bf9f7d93c9f5fd09bd123a089614727ee1ad6e
Author: Gilles Sadowski <gi...@harfang.homelinux.org>
AuthorDate: Fri Dec 14 05:24:40 2018 +0100

    NUMBERS-76: Refactoring so class can be final (ValJO criterion).
    
    Instead of overriding methods, specialized implementations are triggered according to a "type" instance field.
    
    A couple of methods and a constant were renamed.
---
 .../commons/numbers/quaternion/Quaternion.java     | 342 +++++++++++----------
 .../commons/numbers/quaternion/QuaternionTest.java |  28 +-
 2 files changed, 189 insertions(+), 181 deletions(-)

diff --git a/commons-numbers-quaternion/src/main/java/org/apache/commons/numbers/quaternion/Quaternion.java b/commons-numbers-quaternion/src/main/java/org/apache/commons/numbers/quaternion/Quaternion.java
index 25badc1..7d555fb 100644
--- a/commons-numbers-quaternion/src/main/java/org/apache/commons/numbers/quaternion/Quaternion.java
+++ b/commons-numbers-quaternion/src/main/java/org/apache/commons/numbers/quaternion/Quaternion.java
@@ -18,6 +18,8 @@
 package org.apache.commons.numbers.quaternion;
 
 import java.util.Arrays;
+import java.util.function.ToDoubleFunction;
+import java.util.function.BiPredicate;
 import java.io.Serializable;
 import org.apache.commons.numbers.core.Precision;
 
@@ -27,17 +29,17 @@ import org.apache.commons.numbers.core.Precision;
  *
  * <p>Instance of this class are guaranteed to be immutable.</p>
  */
-public abstract class Quaternion implements Serializable {
+public class Quaternion implements Serializable {
     /** Zero quaternion. */
     public static final Quaternion ZERO = of(0, 0, 0, 0);
     /** Identity quaternion. */
-    public static final Quaternion IDENTITY = new PositivePolarForm(of(1, 0, 0, 0));
+    public static final Quaternion ONE = new Quaternion(Type.POSITIVE_POLAR_FORM, 1, 0, 0, 0);
     /** i */
-    public static final Quaternion I = new PositivePolarForm(of(0, 1, 0, 0));
+    public static final Quaternion I = new Quaternion(Type.POSITIVE_POLAR_FORM, 0, 1, 0, 0);
     /** j */
-    public static final Quaternion J = new PositivePolarForm(of(0, 0, 1, 0));
+    public static final Quaternion J = new Quaternion(Type.POSITIVE_POLAR_FORM, 0, 0, 1, 0);
     /** k */
-    public static final Quaternion K = new PositivePolarForm(of(0, 0, 0, 1));
+    public static final Quaternion K = new Quaternion(Type.POSITIVE_POLAR_FORM, 0, 0, 0, 1);
 
     /** Serializable version identifier. */
     private static final long serialVersionUID = 20170118L;
@@ -51,6 +53,8 @@ public abstract class Quaternion implements Serializable {
     /** {@link #toString() String representation}. */
     private static final String FORMAT_SEP = " ";
 
+    /** For enabling specialized method implementations. */
+    private final Type type;
     /** First component (scalar part). */
     private final double w;
     /** Second component (first vector part). */
@@ -61,17 +65,105 @@ public abstract class Quaternion implements Serializable {
     private final double z;
 
     /**
+     * For enabling optimized implementations.
+     */
+    private enum Type {
+        /** Default implementation. */
+        DEFAULT(Default.NORMSQ,
+                Default.NORM,
+                Default.IS_UNIT),
+        /** Quaternion has unit norm. */
+        NORMALIZED(Normalized.NORM,
+                   Normalized.NORM,
+                   Normalized.IS_UNIT),
+        /** Quaternion has positive scalar part. */
+        POSITIVE_POLAR_FORM(Normalized.NORM,
+                            Normalized.NORM,
+                            Normalized.IS_UNIT);
+
+        /** Default implementations. */
+        private static final class Default {
+            /** {@link Quaternion#normSq()}. */
+            static final ToDoubleFunction<Quaternion> NORMSQ = q -> {
+                return q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z;
+            };
+            /** {@link Quaternion#norm()}. */
+            private static final ToDoubleFunction<Quaternion> NORM = q -> {
+                return Math.sqrt(NORMSQ.applyAsDouble(q));
+            };
+            /** {@link Quaternion#isUnit()}. */
+            private static final BiPredicate<Quaternion, Double> IS_UNIT = (q, eps) -> {
+                return Precision.equals(NORM.applyAsDouble(q), 1d, eps);
+            };
+        };
+        /** Implementations for normalized quaternions. */
+        private static final class Normalized {
+            /** {@link Quaternion#norm()} returns 1. */
+            static final ToDoubleFunction<Quaternion> NORM = q -> 1;
+            /** {@link Quaternion#isUnit()} returns 1. */
+            static final BiPredicate<Quaternion, Double> IS_UNIT = (q, eps) -> true;
+        };
+
+        /** {@link Quaternion#normSq()}. */
+        private final ToDoubleFunction<Quaternion> normSq;
+        /** {@link Quaternion#norm()}. */
+        private final ToDoubleFunction<Quaternion> norm;
+        /** {@link Quaternion#isUnit()}. */
+        private final BiPredicate<Quaternion, Double> isUnit;
+
+        /**
+         * @param normSq {@code normSq} method.
+         * @param norm {@code norm} method.
+         * @param isUnit {@code isUnit} method.
+         */
+        Type(ToDoubleFunction<Quaternion> normSq,
+             ToDoubleFunction<Quaternion> norm,
+             BiPredicate<Quaternion, Double> isUnit)  {
+            this.normSq = normSq;
+            this.norm = norm;
+            this.isUnit = isUnit;
+        }
+
+        /**
+         * @param q Quaternion.
+         * @return the norm squared.
+         */
+        double normSq(Quaternion q) {
+            return normSq.applyAsDouble(q);
+        }
+        /**
+         * @param q Quaternion.
+         * @return the norm.
+         */
+        double norm(Quaternion q) {
+            return norm.applyAsDouble(q);
+        }
+        /**
+         * @param q Quaternion.
+         * @param eps Tolerance.
+         * @return whether {@code q} has unit norm within the allowed tolerance.
+         */
+        boolean isUnit(Quaternion q,
+                       double eps) {
+            return isUnit.test(q, eps);
+        }
+    }
+
+    /**
      * Builds a quaternion from its components.
      *
+     * @param type Quaternion type.
      * @param a Scalar component.
      * @param b First vector component.
      * @param c Second vector component.
      * @param d Third vector component.
      */
-    private Quaternion(final double a,
+    private Quaternion(Type type,
+                       final double a,
                        final double b,
                        final double c,
                        final double d) {
+        this.type = type;
         w = a;
         x = b;
         y = c;
@@ -79,6 +171,21 @@ public abstract class Quaternion implements Serializable {
     }
 
     /**
+     * Copies the given quaternion, but change its {@link Type}.
+     *
+     * @param type Quaternion type.
+     * @param q Quaternion whose components will be copied.
+     */
+    private Quaternion(Type type,
+                       Quaternion q) {
+        this.type = type;
+        w = q.w;
+        x = q.x;
+        y = q.y;
+        z = q.z;
+    }
+
+    /**
      * Builds a quaternion from its components.
      *
      * @param a Scalar component.
@@ -91,7 +198,8 @@ public abstract class Quaternion implements Serializable {
                                 final double b,
                                 final double c,
                                 final double d) {
-        return new Default(a, b, c, d);
+        return new Quaternion(Type.DEFAULT,
+                              a, b, c, d);
     }
 
     /**
@@ -254,7 +362,7 @@ public abstract class Quaternion implements Serializable {
      * @return the norm.
      */
     public double norm() {
-        return Math.sqrt(normSq());
+        return type.norm(this);
     }
 
     /**
@@ -263,10 +371,7 @@ public abstract class Quaternion implements Serializable {
      * @return the square of the norm.
      */
     public double normSq() {
-        return w * w +
-            x * x +
-            y * y +
-            z * z;
+        return type.normSq(this);
     }
 
     /**
@@ -277,18 +382,24 @@ public abstract class Quaternion implements Serializable {
      * @throws IllegalStateException if the norm of the quaternion is near zero.
      */
     public Quaternion normalize() {
-        final double norm = norm();
+        switch (type) {
+        case NORMALIZED:
+        case POSITIVE_POLAR_FORM:
+            return this;
+        case DEFAULT:
+            final double norm = norm();
 
-        if (norm < Precision.SAFE_MIN) {
-            throw new IllegalStateException(ZERO_NORM_MSG);
-        }
+            if (norm < Precision.SAFE_MIN) {
+                throw new IllegalStateException(ZERO_NORM_MSG);
+            }
 
-        final Quaternion unit = divide(norm);
+            final Quaternion unit = divide(norm);
 
-        if (w >= 0) {
-            return new PositivePolarForm(unit);
-        } else {
-            return new Normalized(unit);
+            return (w >= 0 ?
+                    new Quaternion(Type.POSITIVE_POLAR_FORM, unit) :
+                    new Quaternion(Type.NORMALIZED, unit));
+        default:
+            throw new IllegalStateException(); // Should never happen.
         }
     }
 
@@ -344,8 +455,8 @@ public abstract class Quaternion implements Serializable {
      * @return {@code true} if the norm is 1 within the given tolerance,
      * {@code false} otherwise
      */
-    public boolean isUnitQuaternion(double eps) {
-        return Precision.equals(norm(), 1d, eps);
+    public boolean isUnit(double eps) {
+        return type.isUnit(this, eps);
     }
 
     /**
@@ -355,7 +466,7 @@ public abstract class Quaternion implements Serializable {
      * @param eps Tolerance (absolute error).
      * @return {@code true} if the scalar part of the quaternion is zero.
      */
-    public boolean isPureQuaternion(double eps) {
+    public boolean isPure(double eps) {
         return Math.abs(w) <= eps;
     }
 
@@ -365,12 +476,21 @@ public abstract class Quaternion implements Serializable {
      * @return the unit quaternion with positive scalar part.
      */
     public Quaternion positivePolarForm() {
-        if (w >= 0) {
-            return normalize();
-        } else {
-            // The quaternion of rotation (normalized quaternion) q and -q
-            // are equivalent (i.e. represent the same rotation).
-            return negate().normalize();
+        switch (type) {
+        case POSITIVE_POLAR_FORM:
+            return this;
+        case NORMALIZED:
+            return (w >= 0 ?
+                    new Quaternion(Type.POSITIVE_POLAR_FORM, this) :
+                    new Quaternion(Type.POSITIVE_POLAR_FORM, negate()));
+        case DEFAULT:
+            return (w >= 0 ?
+                    normalize() :
+                    // The quaternion of rotation (normalized quaternion) q and -q
+                    // are equivalent (i.e. represent the same rotation).
+                    negate().normalize());
+        default:
+            throw new IllegalStateException(); // Should never happen.
         }
     }
 
@@ -381,7 +501,15 @@ public abstract class Quaternion implements Serializable {
      * sign to this one.
      */
     public Quaternion negate() {
-        return of(-w, -x, -y, -z);
+        switch (type) {
+        case POSITIVE_POLAR_FORM:
+        case NORMALIZED:
+            return new Quaternion(Type.NORMALIZED, -w, -x, -y, -z);
+        case DEFAULT:
+            return new Quaternion(Type.DEFAULT, -w, -x, -y, -z);
+        default:
+            throw new IllegalStateException(); // Should never happen.
+        }
     }
 
     /**
@@ -392,15 +520,23 @@ public abstract class Quaternion implements Serializable {
      * @throws IllegalArgumentException if the norm (squared) of the quaternion is zero.
      */
     public Quaternion inverse() {
-        final double squareNorm = normSq();
-        if (squareNorm < Precision.SAFE_MIN) {
-            throw new IllegalStateException(ZERO_NORM_MSG);
+        switch (type) {
+        case POSITIVE_POLAR_FORM:
+        case NORMALIZED:
+            return new Quaternion(type, w, -x, -y, -z);
+        case DEFAULT:
+            final double squareNorm = normSq();
+            if (squareNorm < Precision.SAFE_MIN) {
+                throw new IllegalStateException(ZERO_NORM_MSG);
+            }
+
+            return of(w / squareNorm,
+                      -x / squareNorm,
+                      -y / squareNorm,
+                      -z / squareNorm);
+        default:
+            throw new IllegalStateException(); // Should never happen.
         }
-
-        return of(w / squareNorm,
-                  -x / squareNorm,
-                  -y / squareNorm,
-                  -z / squareNorm);
     }
 
     /**
@@ -572,132 +708,4 @@ public abstract class Quaternion implements Serializable {
             super(msg);
         }
     }
-
-    /**
-     * Default implementation.
-     */
-    private static class Default extends Quaternion {
-        /**
-         * Builds a quaternion from its components.
-         *
-         * @param a Scalar component.
-         * @param b First vector component.
-         * @param c Second vector component.
-         * @param d Third vector component.
-         */
-        Default(final double a,
-                final double b,
-                final double c,
-                final double d) {
-            super(a, b, c, d);
-        }
-     }
-
-    /**
-     * Special implementation for unit quaternions.
-     */
-    private static class Normalized extends Default {
-        /**
-         * Builds a quaternion from its components.
-         * It is the caller's responsibility to ensure that the arguments
-         * do actually form a normalized quaternion.
-         *
-         * @param a Scalar component.
-         * @param b First vector component.
-         * @param c Second vector component.
-         * @param d Third vector component.
-         */
-        Normalized(final double a,
-                   final double b,
-                   final double c,
-                   final double d) {
-            super(a, b, c, d);
-        }
-
-        /**
-         * Builds a normalized quaternion.
-         * It is the caller's responsibility to ensure that {@code q}
-         * is actually a normalized quaternion.
-         *
-         * @param q Unit quaternion.
-         */
-        Normalized(Quaternion q) {
-            super(q.getW(), q.getX(), q.getY(), q.getZ());
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public Quaternion normalize() {
-            return this;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public double norm() {
-            return 1;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public double normSq() {
-            return 1;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean isUnitQuaternion(double eps) {
-            return true;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public Quaternion inverse() {
-            return new Normalized(getW(), -getX(), -getY(), -getZ());
-        }
-    }
-
-    /**
-     * Special implementation for positive polar forms.
-     */
-    private static class PositivePolarForm extends Normalized {
-        /**
-         * Builds a quaternion from its components.
-         * It is the caller's responsibility to ensure that the arguments
-         * do actually constitute a positive polar form.
-         *
-         * @param a Scalar component.
-         * @param b First vector component.
-         * @param c Second vector component.
-         * @param d Third vector component.
-         */
-        PositivePolarForm(final double a,
-                          final double b,
-                          final double c,
-                          final double d) {
-            super(a, b, c, d);
-        }
-
-        /**
-         * Builds a positive polar form.
-         * It is the caller's responsibility to ensure that {@code q}
-         * is actually a positive polar form.
-         *
-         * @param q Positive polar form.
-         */
-        PositivePolarForm(Quaternion q) {
-            super(q);
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public Quaternion positivePolarForm() {
-            return this;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public Quaternion inverse() {
-            return new PositivePolarForm(getW(), -getX(), -getY(), -getZ());
-        }
-    }
 }
diff --git a/commons-numbers-quaternion/src/test/java/org/apache/commons/numbers/quaternion/QuaternionTest.java b/commons-numbers-quaternion/src/test/java/org/apache/commons/numbers/quaternion/QuaternionTest.java
index caa202d..aa89b1d 100644
--- a/commons-numbers-quaternion/src/test/java/org/apache/commons/numbers/quaternion/QuaternionTest.java
+++ b/commons-numbers-quaternion/src/test/java/org/apache/commons/numbers/quaternion/QuaternionTest.java
@@ -34,8 +34,8 @@ public class QuaternionTest {
 
     @Test
     public void testUnitQuaternions() {
-        Assert.assertEquals(1, Quaternion.IDENTITY.norm(), 0d);
-        Assert.assertTrue(Quaternion.IDENTITY.normalize() == Quaternion.IDENTITY);
+        Assert.assertEquals(1, Quaternion.ONE.norm(), 0d);
+        Assert.assertTrue(Quaternion.ONE.normalize() == Quaternion.ONE);
 
         Assert.assertEquals(1, Quaternion.I.norm(), 0d);
         Assert.assertTrue(Quaternion.I.normalize() == Quaternion.I);
@@ -417,37 +417,37 @@ public class QuaternionTest {
     }
 
     @Test
-    public final void testIsUnitQuaternion() {
+    public final void testIsUnit() {
         final Random r = new Random(48);
         final int numberOfTrials = 1000;
         for (int i = 0; i < numberOfTrials; i++) {
             final Quaternion q1 = Quaternion.of(r.nextDouble(), r.nextDouble(), r.nextDouble(), r.nextDouble());
             final Quaternion q2 = q1.normalize();
-            Assert.assertTrue(q2.isUnitQuaternion(COMPARISON_EPS));
+            Assert.assertTrue(q2.isUnit(COMPARISON_EPS));
         }
 
         final Quaternion q = Quaternion.of(1, 1, 1, 1);
-        Assert.assertFalse(q.isUnitQuaternion(COMPARISON_EPS));
+        Assert.assertFalse(q.isUnit(COMPARISON_EPS));
     }
 
     @Test
-    public final void testIsPureQuaternion() {
+    public final void testIsPure() {
         final Quaternion q1 = Quaternion.of(0, 5, 4, 8);
-        Assert.assertTrue(q1.isPureQuaternion(EPS));
+        Assert.assertTrue(q1.isPure(EPS));
 
         final Quaternion q2 = Quaternion.of(0 - EPS, 5, 4, 8);
-        Assert.assertTrue(q2.isPureQuaternion(EPS));
+        Assert.assertTrue(q2.isPure(EPS));
 
         final Quaternion q3 = Quaternion.of(0 - 1.1 * EPS, 5, 4, 8);
-        Assert.assertFalse(q3.isPureQuaternion(EPS));
+        Assert.assertFalse(q3.isPure(EPS));
 
         final Random r = new Random(48);
         final double[] v = {r.nextDouble(), r.nextDouble(), r.nextDouble()};
         final Quaternion q4 = Quaternion.of(v);
-        Assert.assertTrue(q4.isPureQuaternion(0));
+        Assert.assertTrue(q4.isPure(0));
 
         final Quaternion q5 = Quaternion.of(0, v);
-        Assert.assertTrue(q5.isPureQuaternion(0));
+        Assert.assertTrue(q5.isPure(0));
     }
 
     @Test
@@ -496,7 +496,7 @@ public class QuaternionTest {
                                                 2 * (r.nextDouble() - 0.5), 2 * (r.nextDouble() - 0.5));
             final Quaternion qP = q.positivePolarForm();
 
-            Assert.assertTrue(qP.isUnitQuaternion(COMPARISON_EPS));
+            Assert.assertTrue(qP.isUnit(COMPARISON_EPS));
             Assert.assertTrue(qP.getW() >= 0);
 
             final Rotation rot = new Rotation(q.getW(), q.getX(), q.getY(), q.getZ(), true);
@@ -545,7 +545,7 @@ public class QuaternionTest {
         final Quaternion invQ = Quaternion.of(-1.2, 3.4, -5.6, -7.8).normalize().inverse();
         final Quaternion q = invQ.inverse();
         final Quaternion result = q.multiply(invQ);
-        Assert.assertTrue(result.toString(), Quaternion.IDENTITY.equals(result, EPS));
+        Assert.assertTrue(result.toString(), Quaternion.ONE.equals(result, EPS));
     }
 
     @Test
@@ -553,7 +553,7 @@ public class QuaternionTest {
         final Quaternion invQ = Quaternion.of(1.2, -3.4, 5.6, -7.8).positivePolarForm().inverse();
         final Quaternion q = invQ.inverse();
         final Quaternion result = q.multiply(invQ);
-        Assert.assertTrue(result.toString(), Quaternion.IDENTITY.equals(result, EPS));
+        Assert.assertTrue(result.toString(), Quaternion.ONE.equals(result, EPS));
     }
 
     @Test