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