From b2b9aa5ea2f2af6f5276cf33180049b9371a97c5 Mon Sep 17 00:00:00 2001
From: Hari Nair
+ * The method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
+ * characteristics are closely related.
+ *
+ * The method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
+ * characteristics are closely related.
+ *
+ * This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
+ * characteristics are closely related.
+ *
+ * This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
+ * characteristics are closely related.
+ *
+ * This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
+ * characteristics are closely related.
+ *
+ * This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
+ * characteristics are closely related.
+ *
+ * This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
+ * characteristics are closely related.
+ *
+ * This method utilizes {@link Arrays#binarySearch(double[], int, int, double)} so its performance
+ * characteristics are closely related.
+ *
+ * Elements are added to the collection via the {@link Collection#add(Object)} method in the order
+ * that the iterator generated from the supplied iterable produces them.
+ *
+ * Elements are added to the collection via the {@link Collection#add(Object)} method in the order
+ * that the iterator produces them.
+ *
+ * This package simply contains any additional capabilities that are supplied and designed to
+ * integrate with or into the general collections framework provided by Sun.
+ *
+ * Features include the following:
+ *
+ * This is a ragged array
+ * { {1, 2, 3} }
+ * { {3, 4} }
+ *
+ * This is not a ragged array
+ * { {1, 2, 3} }
+ * { {3, 4, 5} }
+ *
+ *
+ * @param r input array
+ * @param
+ * This is a ragged array
+ * { {1, 2, 3} }
+ * { {3, 4} }
+ *
+ * This is not a ragged array
+ * { {1, 2, 3} }
+ * { {3, 4, 5} }
+ *
+ *
+ * @param r input array
+ * @return true if the input is a ragged array, false if it is 'rectangular'
+ */
+ public static boolean isRagged(double[][] r) {
+
+ if (r.length > 0) {
+
+ int width0 = r[0].length;
+
+ for (int i = 0; i < r.length; i++) {
+
+ if (r[i].length != width0) {
+ return true;
+ }
+
+ }
+
+ }
+ return false;
+ }
+
+ /**
+ * Checks if an array is ragged.
+ *
+ * @param r input array
+ * @return true if the input is a ragged array, false if it is 'rectangular'
+ */
+ public static boolean isRagged(double[][][] r) {
+
+ if (r.length > 0 && r[0].length > 0) {
+
+ int width0 = r[0].length;
+ int depth0 = r[0][0].length;
+
+ for (int i = 0; i < r.length; i++) {
+
+ if (r[i].length != width0) {
+ return true;
+ }
+
+ for (int j = 0; j < r[0].length; j++) {
+ if (r[i][j].length != depth0) {
+ return true;
+ }
+ }
+
+ }
+
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/main/java/picante/collections/CollectionUtilities.java b/src/main/java/picante/collections/CollectionUtilities.java
new file mode 100644
index 0000000..4adf54e
--- /dev/null
+++ b/src/main/java/picante/collections/CollectionUtilities.java
@@ -0,0 +1,534 @@
+package picante.collections;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * This class consists exclusively of static methods that operate on or return collections. It
+ * contains polymorphic algorithms that operate on collections. It is much like the Collections
+ * class in the java.util package, only it provides other useful generic methods.
+ *
+ * The methods of this class all throw a NullPointerException if the collections or class objects
+ * provided to them are null.
+ */
+public class CollectionUtilities {
+
+ /**
+ * Determine the index of the last element less than or equal to the specified key. The list must
+ * be sorted into ascending order according to the natural ordering specified by the
+ * implementation of the Comparable interface. If it is not sorted, the results are undefined. If
+ * the list contains multiple elements equal to the specified object, the last one is guaranteed
+ * to be extracted.
+ *
+ * The method utilizes {@link java.util.Collections#binarySearch(List, Object)} so its performance
+ * characteristics are closely related.
+ *
+ * @param the type of the parent
+ * @param listOfChildren the children to convert
+ *
+ * @return the same list cast as a list of parent objects (type T)
+ */
+ @SuppressWarnings("unchecked")
+ public static listOfChildren) {
+ // recast as list of parent objects:
+ return (ImmutableList
+ *
+ *
+ * This class will manage the generation of index out of bounds exceptions, blocking access to + * invoking the {@link AbstractFixedLengthGaugedRetrievableWithExceptions#obtainRecord(int, Object)} + * or {@link AbstractFixedLengthGaugedRetrievableWithExceptions#obtainTime(int)} methods if the + * supplied index lies outside the specified range. It is simply a convenience class, if you do not + * wish to manage the error handling in your own code. + *
+ * + * @param+ * This class will manage the generation of index out of bounds exceptions, blocking access to + * invoking the {@link AbstractFixedLengthGaugedRetrievableWithExceptions#obtainRecord(int, Object)} + * method if the supplied index lies outside the specified range. It is simply a convenience class, + * if you do not wish to manage the error handling in your own code. + *
+ * + * @param+ * If the efficiency of searches over an implementation of the interface is a concern, or if binary + * search utilizing the {@link GaugedRetrievable#getGauge(int)} method is sufficient, then simply + * subclass this abstract class and supply the standard retrieval methods. + *
+ * + * @param+ * This class will manage the generation of index out of bounds exceptions, blocking access to + * invoking the {@link AbstractGaugedRetrievableWithExceptions#obtain(int, Object)} or + * {@link AbstractGaugedRetrievableWithExceptions#obtainGauge(int)} methods if the supplied index + * lies outside the range allowed by the {@link AbstractGaugedRetrievableWithExceptions#size()} + * method. It is simply a convenience class, if you do not wish to manage the error handling in your + * own code. + *
+ * + * @param+ * This class will manage the generation of index out of bounds exceptions, blocking access to + * invoking the {@link AbstractRetrievableWithExceptions#obtain(int, Object)} method if the supplied + * index lies outside the range allowed by the {@link AbstractRetrievableWithExceptions#size()} + * method. It is simply a convenience class allowing a user to skip implementing the necessary error + * handling code on their own. + *
+ * + * @param+ * Typically the gauge is measuring separation in time between records in a time series, but this is + * entirely abstract in that it could be temperatures or some other measurable quantity. This + * interface exists primarily to specify how the conversion from one object to the double gauge will + * occur. + *
+ * + * @param+ * This interface exists purely to allow functionality from the {@link Indexable} and + * {@link Retrievable} inheritance trees to share code. + *
+ * + */ +public interface Gauged { + + /** + * Retrieve the gauge (time) associated with a record at a particular index. + * + * @param index the index of interest + * + * @return the double capturing the measured value against which records are recorded, typically + * time. + * + * @throws IndexOutOfBoundsException if the index lies outside the range of acceptable values + * supporting the instance + */ + double getGauge(int index); + +} diff --git a/src/main/java/picante/data/list/GaugedRetrievable.java b/src/main/java/picante/data/list/GaugedRetrievable.java new file mode 100644 index 0000000..98be904 --- /dev/null +++ b/src/main/java/picante/data/list/GaugedRetrievable.java @@ -0,0 +1,13 @@ +package picante.data.list; + +/** + * Extension of the list interface that requires a companion method to obtain a gauged quantity + * (typically time) associated with a particular record. The elements of the {@link Retrievable} are + * assumed to be sorted in non-decreasing order against the gauge (and generally strictly + * increasing). + * + * @param+ * The methods on the {@link GaugedRetrievable} interface, specifically: + * {@link GaugedRetrievable#getGauge(int)} allow generic binary searching to be performed on the + * contents of any sequence of records against the gauge. However, this particular interface leaves + * it entirely up to the implementation as to how that search should be performed. It may be able to + * exploit additional information not captured by the generic interface, but known to the + * implementation to greatly improve performance. + *
+ * + * @param+ * The methods on the {@link GaugedRetrievable} interface, specifically: + * {@link GaugedRetrievable#getGauge(int)} allow generic binary searching to be performed on the + * contents of any sequence of records against the gauge. However, this particular interface leaves + * it entirely up to the implementation as to how that search should be performed. It may be able to + * exploit additional information not captured by the generic interface, but known to the + * implementation, to greatly improve performance. + *
+ * + * @param+ * This class is package private, as there is no need for external users to access it. The abstract + * classes provided in the package give indirect access to it, as do the static methods on various + * utility classes. + *
+ */ +class Searcher { + + private final GaugedRetrievable> list; + + Searcher(GaugedRetrievable> list) { + this.list = list; + } + + public int indexLastLessThanOrEqualTo(double time) { + int items = list.size(); + int begin = 0; + int end = items - 1; + int middle; + int j; + + /* + * Next handle the case where none of the elements in the array are less than the search value. + */ + double queryValue = list.getGauge(begin); + + if (time < queryValue) { + return -1; + } + + /* + * And the case where all of the elements in the array are less than or equal to the search + * value. + */ + queryValue = list.getGauge(end); + + if (time >= queryValue) { + return end; + } + + /* + * The boundary cases have been handled, initiate the search over the segment contents. + */ + while (items > 2) { + + j = items / 2; + middle = begin + j; + + queryValue = list.getGauge(middle); + + if (queryValue <= time) { + begin = middle; + } else { + end = middle; + } + + items = 1 + (end - begin); + } + + return begin; + + } + + public int indexLastLessThan(double time) { + + int items = list.size(); + int begin = 0; + int end = items - 1; + int middle; + int j; + + /* + * Next handle the case where none of the elements in the array are less than the search value. + */ + double queryValue = list.getGauge(begin); + + if (time <= queryValue) { + return -1; + } + + /* + * And the case where all of the elements in the array are less than the search value. + */ + queryValue = list.getGauge(end); + + if (queryValue < time) { + return end; + } + + /* + * The boundary cases have been handled, initiate the search over the segment contents. + */ + while (items > 2) { + + j = items / 2; + middle = begin + j; + + queryValue = list.getGauge(middle); + + if (queryValue < time) { + begin = middle; + } else { + end = middle; + } + + items = 1 + (end - begin); + + } + + return begin; + + } + +} diff --git a/src/main/java/picante/data/list/package-info.java b/src/main/java/picante/data/list/package-info.java new file mode 100644 index 0000000..fb73c61 --- /dev/null +++ b/src/main/java/picante/data/list/package-info.java @@ -0,0 +1,33 @@ +/** + * Package containing the generic crucible one dimensional, record based data model. + *+ * The primary "record" storage interface is: + *
+ * The next concept that is present in these packages is that of the "gauge". We struggled + * to come up with unique terminology that captures the essence of what we are trying to express + * here. The basic idea is that if you wish to interpolate between "records" then you + * likely need these records registered against some sort of domain or number line. This + * "domain" axis is the gauge, and it is an ordered association of records with a double + * precision number. Thus the gauge locates records on the table directly to a position on this + * domain number line or axis. Frequently the gauge is some measure of time, but the API makes no + * such restriction. Implementations of these gauged interfaces are expected to provide the records + * in an index order that is non-decreasing against the gauge. + *
+ *+ * Lastly, because at times if the implementations of these interfaces are backed by databases, + * files, or other means, there are a set of additional extensions that add methods to perform + * searches. Allowing implementors to provide search mechanisms that are most efficient for their + * respective data stores or implementations seemed appropriate. In most cases, binary search or + * direct index computation is sufficient. Abstract helper classes that provide commonly used + * searching algorithms are available in an additional sub-package. + *
+ */ +package picante.data.list; + diff --git a/src/main/java/picante/demo/Geometry.java b/src/main/java/picante/demo/Geometry.java new file mode 100644 index 0000000..3216c5d --- /dev/null +++ b/src/main/java/picante/demo/Geometry.java @@ -0,0 +1,172 @@ +package picante.demo; + +import com.google.common.collect.ImmutableList; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import picante.math.coords.CoordConverters; +import picante.math.coords.LatitudinalVector; +import picante.math.vectorspace.UnwritableVectorIJK; +import picante.math.vectorspace.VectorIJK; +import picante.mechanics.CelestialBodies; +import picante.mechanics.CelestialFrames; +import picante.mechanics.Coverage; +import picante.mechanics.EphemerisID; +import picante.mechanics.FrameID; +import picante.mechanics.FrameTransformFunction; +import picante.mechanics.PositionVectorFunction; +import picante.mechanics.providers.aberrated.AberratedEphemerisProvider; +import picante.mechanics.providers.aberrated.AberrationCorrection; +import picante.mechanics.utilities.SimpleEphemerisID; +import picante.spice.MetakernelReader; +import picante.spice.SpiceEnvironment; +import picante.spice.SpiceEnvironmentBuilder; +import picante.spice.adapters.AdapterInstantiationException; +import picante.spice.fov.FOV; +import picante.spice.fov.FOVFactory; +import picante.spice.kernel.KernelInstantiationException; +import picante.surfaces.Ellipsoid; +import picante.surfaces.Surfaces; +import picante.time.TimeConversion; +import picante.utilities.SPICEUtils; + +/** + * Picante version of SPICE example in the NAIF tutorials (e.g. IDL). + * See slide 23 for results to compare. + * + *This does not agree exactly with SPICE as the ellipsoid intersect calculations are done
+ * differently.
+ *
+ * @author Hari.Nair@jhuapl.edu
+ */
+public class Geometry {
+
+ public static void main(String[] args) {
+
+ EphemerisID bodyID = CelestialBodies.PHOEBE;
+ FrameID frameID = CelestialFrames.IAU_PHOEBE;
+ EphemerisID spacecraftID = new SimpleEphemerisID("CASSINI");
+ String instName = "CASSINI_ISS_NAC";
+ String utcString = "2004 jun 11 19:32:00";
+
+ // this is necessary to tell the SpiceEnvironment about objects that are not in the
+ // CelestialBodies enumeration
+ Map
+ * Another useful feature to add when creating classes that implement this pattern is the static + * copyOf method on the unwritable class. It should look something like this: + * + *
+ * public static UnwritableClass copyOf(UnwritableClass instance) {
+ * if ( instance.getClass().equals(UnwritableClass.class) {
+ * return instance;
+ * }
+ * return new UnwritableClass(instance); // the copy constructor
+ * }
+ *
+ *
+ *
+ *
+ * @author G.K.Stephens
+ *
+ * @param The unwritable version of the class that will be extending this interface.
+ * @param + * Code is only really placed in this package when it is useful in a program, and not just for the + * sake of codifying a concept. + *
+ */ +package picante.designpatterns; + diff --git a/src/main/java/picante/exceptions/BugException.java b/src/main/java/picante/exceptions/BugException.java new file mode 100644 index 0000000..1e3da0f --- /dev/null +++ b/src/main/java/picante/exceptions/BugException.java @@ -0,0 +1,33 @@ +package picante.exceptions; + +/** + * Runtime exception generated as a result of something failing that should never fail. + *+ * Prototypical example would be invoking the string constructor: new String(new byte[] {32, 32}, + * "ISO-8859-1") the constructor may throw an exception due to an unsupported character encoding, + * but this should never happen in practice. + *
+ * + */ +public class BugException extends PicanteRuntimeException { + + /** + * Default serial version UID. + */ + private static final long serialVersionUID = 1L; + + public BugException() {} + + public BugException(String message) { + super(message); + } + + public BugException(Throwable cause) { + super(cause); + } + + public BugException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/picante/exceptions/ExceptionFactory.java b/src/main/java/picante/exceptions/ExceptionFactory.java new file mode 100644 index 0000000..9fe78e0 --- /dev/null +++ b/src/main/java/picante/exceptions/ExceptionFactory.java @@ -0,0 +1,17 @@ +package picante.exceptions; + +/** + * Interface describing an exception factory, designed to convert one type of exceptions to another + * for abdicating control of exception generation to the user or implementor of an interface. + *+ * TODO: Provide a simple example. + *
+ * + * @param Input exception type + * @param
+ * This package contains the parent classes of exception classes created in support of the crucible
+ * library. In general, exceptions contained within the library should descend from either
+ * CrucibleException or CrucibleRuntimeException.
+ *
+ * This will allow developers utilizing the library the ability to trap exceptions specific to + * crucible itself without much difficulty. + *
+ * + * @see picante.exceptions.PicanteException + * @see picante.exceptions.PicanteRuntimeException + */ +package picante.exceptions; diff --git a/src/main/java/picante/junit/AssertTools.java b/src/main/java/picante/junit/AssertTools.java new file mode 100644 index 0000000..a290cb5 --- /dev/null +++ b/src/main/java/picante/junit/AssertTools.java @@ -0,0 +1,359 @@ +package picante.junit; + +import static org.junit.Assert.assertEquals; + +import java.awt.geom.Point2D; +import picante.math.intervals.UnwritableInterval; +import picante.math.vectorspace.RotationMatrixIJK; +import picante.math.vectorspace.UnwritableMatrixIJ; +import picante.math.vectorspace.UnwritableMatrixIJK; +import picante.math.vectorspace.UnwritableRotationMatrixIJK; +import picante.math.vectorspace.UnwritableVectorIJ; +import picante.math.vectorspace.UnwritableVectorIJK; +import picante.mechanics.UnwritableStateTransform; +import picante.mechanics.UnwritableStateVector; +import picante.mechanics.rotations.AxisAndAngle; + +public class AssertTools { + + /** + * Assert two doubles are exactly equal. + * + * @param expected Expected double value. + * @param actual Actual double value. + */ + public static void assertEqualDouble(double expected, double actual) { + assertEquals(expected, actual, 0.0); + } + + /** + * Assert two doubles are equivalent. + * + * @param expected Expected double value. + * @param actual Actual double value. + */ + public static void assertEquivalentDouble(double expected, double actual) { + assertEquals(expected, actual, Math.ulp(expected)); + } + + /** + * Assert two doubles are within the appropriate relative tolerance. + * + * @param message Failure message to emit + * @param expected Expected double value. + * @param actual Actual double value. + * @param delta Required relative tolerance. + */ + public static void assertRelativeEquality(String message, double expected, double actual, + double delta) { + if (expected != actual) { + assertEquals(message, expected, actual, + delta * Math.max(Math.abs(expected), Math.abs(actual))); + } + } + + + /** + * Assert two doubles are within the appropriate relative tolerance. + * + * @param expected Expected double value. + * @param actual Actual double value. + * @param delta Required relative tolerance. + */ + public static void assertRelativeEquality(double expected, double actual, double delta) { + if (expected != actual) { + assertEquals(expected, actual, delta * Math.max(Math.abs(expected), Math.abs(actual))); + } + } + + /** + * Assert two matrices are equivalent. + * + * @param expected Expected matrix. + * @param actual Actual matrix. + */ + public static void assertEquivalentMatrix(UnwritableMatrixIJK expected, + UnwritableMatrixIJK actual) { + assertEquivalentDouble(expected.getII(), actual.getII()); + assertEquivalentDouble(expected.getIJ(), actual.getIJ()); + assertEquivalentDouble(expected.getIK(), actual.getIK()); + assertEquivalentDouble(expected.getJI(), actual.getJI()); + assertEquivalentDouble(expected.getJJ(), actual.getJJ()); + assertEquivalentDouble(expected.getJK(), actual.getJK()); + assertEquivalentDouble(expected.getKI(), actual.getKI()); + assertEquivalentDouble(expected.getKJ(), actual.getKJ()); + assertEquivalentDouble(expected.getKK(), actual.getKK()); + } + + /** + * Assert two matrices are exactly equal. + * + * @param expected Expected matrix. + * @param actual Actual matrix. + */ + public static void assertEqualMatrix(UnwritableMatrixIJK expected, UnwritableMatrixIJK actual) { + assertEqualDouble(expected.getII(), actual.getII()); + assertEqualDouble(expected.getIJ(), actual.getIJ()); + assertEqualDouble(expected.getIK(), actual.getIK()); + assertEqualDouble(expected.getJI(), actual.getJI()); + assertEqualDouble(expected.getJJ(), actual.getJJ()); + assertEqualDouble(expected.getJK(), actual.getJK()); + assertEqualDouble(expected.getKI(), actual.getKI()); + assertEqualDouble(expected.getKJ(), actual.getKJ()); + assertEqualDouble(expected.getKK(), actual.getKK()); + } + + public static void assertComponentEquals(String message, UnwritableMatrixIJK expected, + UnwritableMatrixIJK actual, double delta) { + assertEquals(message, expected.getII(), actual.getII(), delta); + assertEquals(message, expected.getJI(), actual.getJI(), delta); + assertEquals(message, expected.getKI(), actual.getKI(), delta); + assertEquals(message, expected.getIJ(), actual.getIJ(), delta); + assertEquals(message, expected.getJJ(), actual.getJJ(), delta); + assertEquals(message, expected.getKJ(), actual.getKJ(), delta); + assertEquals(message, expected.getIK(), actual.getIK(), delta); + assertEquals(message, expected.getJK(), actual.getJK(), delta); + assertEquals(message, expected.getKK(), actual.getKK(), delta); + } + + public static void assertComponentEquals(UnwritableMatrixIJK expected, UnwritableMatrixIJK actual, + double delta) { + assertEquals(expected.getII(), actual.getII(), delta); + assertEquals(expected.getJI(), actual.getJI(), delta); + assertEquals(expected.getKI(), actual.getKI(), delta); + assertEquals(expected.getIJ(), actual.getIJ(), delta); + assertEquals(expected.getJJ(), actual.getJJ(), delta); + assertEquals(expected.getKJ(), actual.getKJ(), delta); + assertEquals(expected.getIK(), actual.getIK(), delta); + assertEquals(expected.getJK(), actual.getJK(), delta); + assertEquals(expected.getKK(), actual.getKK(), delta); + } + + public static void assertComponentRelativeEquality(UnwritableMatrixIJK expected, + UnwritableMatrixIJK actual, double delta) { + assertRelativeEquality(expected.getII(), actual.getII(), delta); + assertRelativeEquality(expected.getJI(), actual.getJI(), delta); + assertRelativeEquality(expected.getKI(), actual.getKI(), delta); + assertRelativeEquality(expected.getIJ(), actual.getIJ(), delta); + assertRelativeEquality(expected.getJJ(), actual.getJJ(), delta); + assertRelativeEquality(expected.getKJ(), actual.getKJ(), delta); + assertRelativeEquality(expected.getIK(), actual.getIK(), delta); + assertRelativeEquality(expected.getJK(), actual.getJK(), delta); + assertRelativeEquality(expected.getKK(), actual.getKK(), delta); + + } + + /** + * Assert two vectors are equivalent. + * + * @param expected Expected vector. + * @param actual Actual vector. + */ + public static void assertEquivalentVector(UnwritableVectorIJK expected, + UnwritableVectorIJK actual) { + assertEquivalentDouble(expected.getI(), actual.getI()); + assertEquivalentDouble(expected.getJ(), actual.getJ()); + assertEquivalentDouble(expected.getK(), actual.getK()); + } + + /** + * Assert two vectors are exactly equal. + * + * @param expected Expected vector. + * @param actual Actual vector. + */ + public static void assertEqualVector(UnwritableVectorIJK expected, UnwritableVectorIJK actual) { + assertEqualDouble(expected.getI(), actual.getI()); + assertEqualDouble(expected.getJ(), actual.getJ()); + assertEqualDouble(expected.getK(), actual.getK()); + } + + /** + * Assert component-wise equality with tolerance. + * + * @param expected Expected vector. + * @param actual Actual vector. + * @param delta Component-wise delta factor. + */ + public static void assertComponentEquals(UnwritableVectorIJK expected, UnwritableVectorIJK actual, + double delta) { + assertEquals(expected.getI(), actual.getI(), delta); + assertEquals(expected.getJ(), actual.getJ(), delta); + assertEquals(expected.getK(), actual.getK(), delta); + } + + public static void assertComponentRelativeEquality(UnwritableVectorIJK expected, + UnwritableVectorIJK actual, double delta) { + assertRelativeEquality(expected.getI(), actual.getI(), delta); + assertRelativeEquality(expected.getJ(), actual.getJ(), delta); + assertRelativeEquality(expected.getK(), actual.getK(), delta); + } + + public static void assertRotationAngleEquals(UnwritableRotationMatrixIJK expected, + UnwritableRotationMatrixIJK actual, double toleranceInRadians) { + assertEquals(0.0, + Math.abs(new AxisAndAngle(RotationMatrixIJK.mtxm(actual, expected)).getAngle()), + toleranceInRadians); + } + + /** + * Assert two matrices are equivalent. + * + * @param expected Expected matrix. + * @param actual Actual matrix. + */ + public static void assertEquivalentMatrix(UnwritableMatrixIJ expected, + UnwritableMatrixIJ actual) { + assertEquivalentDouble(expected.getII(), actual.getII()); + assertEquivalentDouble(expected.getIJ(), actual.getIJ()); + assertEquivalentDouble(expected.getJI(), actual.getJI()); + assertEquivalentDouble(expected.getJJ(), actual.getJJ()); + } + + /** + * Assert two matrices are exactly equal. + * + * @param expected Expected matrix. + * @param actual Actual matrix. + */ + public static void assertEqualMatrix(UnwritableMatrixIJ expected, UnwritableMatrixIJ actual) { + assertEqualDouble(expected.getII(), actual.getII()); + assertEqualDouble(expected.getIJ(), actual.getIJ()); + assertEqualDouble(expected.getJI(), actual.getJI()); + assertEqualDouble(expected.getJJ(), actual.getJJ()); + } + + public static void assertComponentEquals(String message, UnwritableMatrixIJ expected, + UnwritableMatrixIJ actual, double delta) { + assertEquals(message, expected.getII(), actual.getII(), delta); + assertEquals(message, expected.getJI(), actual.getJI(), delta); + assertEquals(message, expected.getIJ(), actual.getIJ(), delta); + assertEquals(message, expected.getJJ(), actual.getJJ(), delta); + } + + public static void assertComponentEquals(UnwritableMatrixIJ expected, UnwritableMatrixIJ actual, + double delta) { + assertEquals(expected.getII(), actual.getII(), delta); + assertEquals(expected.getJI(), actual.getJI(), delta); + assertEquals(expected.getIJ(), actual.getIJ(), delta); + assertEquals(expected.getJJ(), actual.getJJ(), delta); + } + + public static void assertComponentRelativeEquality(UnwritableMatrixIJ expected, + UnwritableMatrixIJ actual, double delta) { + assertRelativeEquality(expected.getII(), actual.getII(), delta); + assertRelativeEquality(expected.getJI(), actual.getJI(), delta); + assertRelativeEquality(expected.getIJ(), actual.getIJ(), delta); + assertRelativeEquality(expected.getJJ(), actual.getJJ(), delta); + } + + /** + * Assert two vectors are equivalent. + * + * @param expected Expected vector. + * @param actual Actual vector. + */ + public static void assertEquivalentVector(UnwritableVectorIJ expected, + UnwritableVectorIJ actual) { + assertEquivalentDouble(expected.getI(), actual.getI()); + assertEquivalentDouble(expected.getJ(), actual.getJ()); + } + + /** + * Assert two vectors are exactly equal. + * + * @param expected Expected vector. + * @param actual Actual vector. + */ + public static void assertEqualVector(UnwritableVectorIJ expected, UnwritableVectorIJ actual) { + assertEqualDouble(expected.getI(), actual.getI()); + assertEqualDouble(expected.getJ(), actual.getJ()); + } + + /** + * Assert component-wise equality with tolerance. + * + * @param expected Expected vector. + * @param actual Actual vector. + * @param delta Component-wise delta factor. + */ + public static void assertComponentEquals(UnwritableVectorIJ expected, UnwritableVectorIJ actual, + double delta) { + assertEquals(expected.getI(), actual.getI(), delta); + assertEquals(expected.getJ(), actual.getJ(), delta); + } + + public static void assertComponentRelativeEquality(UnwritableVectorIJ expected, + UnwritableVectorIJ actual, double delta) { + assertRelativeEquality(expected.getI(), actual.getI(), delta); + assertRelativeEquality(expected.getJ(), actual.getJ(), delta); + } + + public static void assertEquivalentStateTransform(UnwritableStateTransform expected, + UnwritableStateTransform actual) { + assertEquivalentMatrix(expected.getRotation(), actual.getRotation()); + assertEquivalentMatrix(expected.getRotationDerivative(), actual.getRotationDerivative()); + } + + public static void assertEqualStateTransform(UnwritableStateTransform expected, + UnwritableStateTransform actual) { + assertEqualMatrix(expected.getRotation(), actual.getRotation()); + assertEqualMatrix(expected.getRotationDerivative(), actual.getRotationDerivative()); + } + + public static void assertComponentEquals(UnwritableStateTransform expected, + UnwritableStateTransform actual, double delta) { + assertComponentEquals(expected.getRotation(), actual.getRotation(), delta); + assertComponentEquals(expected.getRotationDerivative(), actual.getRotationDerivative(), delta); + } + + public static void assertComponentEquals(String message, UnwritableStateTransform expected, + UnwritableStateTransform actual, double delta) { + assertComponentEquals(message, expected.getRotation(), actual.getRotation(), delta); + assertComponentEquals(message, expected.getRotationDerivative(), actual.getRotationDerivative(), + delta); + } + + public static void assertComponentRelativeEquality(UnwritableStateTransform expected, + UnwritableStateTransform actual, double delta) { + assertComponentRelativeEquality(expected.getRotation(), actual.getRotation(), delta); + assertComponentRelativeEquality(expected.getRotationDerivative(), + actual.getRotationDerivative(), delta); + } + + public static void assertEquivalentStateVector(UnwritableStateVector expected, + UnwritableStateVector actual) { + assertEquivalentVector(expected.getPosition(), actual.getPosition()); + assertEquivalentVector(expected.getVelocity(), actual.getVelocity()); + } + + public static void assertEqualStateVector(UnwritableStateVector expected, + UnwritableStateVector actual) { + assertEqualVector(expected.getPosition(), actual.getPosition()); + assertEqualVector(expected.getVelocity(), actual.getVelocity()); + } + + public static void assertComponentEquals(UnwritableStateVector expected, + UnwritableStateVector actual, double delta) { + assertComponentEquals(expected.getPosition(), actual.getPosition(), delta); + assertComponentEquals(expected.getVelocity(), actual.getVelocity(), delta); + } + + public static void assertComponentRelativeEquality(UnwritableStateVector expected, + UnwritableStateVector actual, double delta) { + assertComponentRelativeEquality(expected.getPosition(), actual.getPosition(), delta); + assertComponentRelativeEquality(expected.getVelocity(), actual.getVelocity(), delta); + } + + public static void assertEqualInterval(UnwritableInterval expected, UnwritableInterval actual, + double tolerance) { + assertEquals(expected.getBegin(), actual.getBegin(), tolerance); + assertEquals(expected.getEnd(), actual.getEnd(), tolerance); + } + + public static void assertComponentEquals(Point2D expected, Point2D actual, double delta) { + assertEquals(expected.getX(), actual.getX(), delta); + assertEquals(expected.getY(), actual.getY(), delta); + } + +} diff --git a/src/main/java/picante/junit/CaptureAndAnswer.java b/src/main/java/picante/junit/CaptureAndAnswer.java new file mode 100644 index 0000000..73fec5a --- /dev/null +++ b/src/main/java/picante/junit/CaptureAndAnswer.java @@ -0,0 +1,154 @@ +package picante.junit; + +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.easymock.IAnswer; +import org.easymock.IExpectationSetters; +import picante.designpatterns.Writable; + +/** + * This class exists to solve the problem in that arises when attempting to create mock + * implementations of classes or interfaces implementing the "buffer pattern". + *+ * Specifically, if you have a class or interface that you want to mock that has methods that look + * like this: + *
+ * + *
+ *
+ * interface BufferPatternExample {
+ *
+ * public Value get(double otherArg, Value buffer);
+ *
+ * }
+ *
+ *
+ *
+ * + * where the get method, by contract, is to mutate the contents of buffer and return a reference to + * it for convenience, it turns out mocking can be a bit tricky. + *
+ *+ * This simple abstract class presents the solution to the problem: + *
+ * + *
+ *
+ * BufferPatternExample mock = createMock(BufferPatternExample.class);
+ *
+ * CaptureAndAnswer<Value> capture = new CaptureAndAnswer<Value>() {
+ * public void set(Value captured) {
+ * ...mutate the contents of captured...
+ * }
+ * };
+ *
+ * expect(mock.get(eq(1.0), capture(capture.getCapture()))).andAnswer(capture);
+ * replay(mock);
+ *
+ * ...execute test code that triggers method call...
+ *
+ * verify(mock);
+ *
+ *
+ *
+ * + * Another, arguably awful, use case of this is for when the value is not returned. This is + * counter-intuitive, as it requires a certain ordering of methods when interfacing with EasyMock. + * Consider the following, similar interface: + *
+ * + *
+ *
+ * interface BufferPatternExample2 {
+ *
+ * public void get(double otherArg, Value buffer);
+ *
+ * }
+ *
+ *
+ * + * Since the get function does not return a value, this complicates the "andAnswer" part + * of the response. Fortunately EasyMock has a mechanism that provides for this, as an answer may + * generate an exception, so the code will be invoked even if the return type is not present in the + * function signature. For example: + *
+ * + *
+ *
+ * BufferPatternExample2 mock = createMock(BufferPatternExample2.class);
+ *
+ * CaptureAndAnswer<Value> capture = new CaptureAndAnswer<Value>() {
+ * public void set(Value captured) {
+ * ...mutate the contents of captured...
+ * }
+ * };
+ *
+ * mock.get(eq(1.0), capture(capture.getCapture()));
+ * expectLastCall().andAnswer(capture);
+ * replay(mock);
+ *
+ * ...execute test code that triggers method call...
+ *
+ * verify(mock);
+ *
+ *
+ * + * Note: in our experience working with EasyMock the {@link IExpectationSetters#andAnswer(IAnswer)} + * method must be invoked immediately after the {@link EasyMock#expectLastCall()} and prior to any + * of the other chained method options from the {@link IExpectationSetters} interface. + *
+ * + * @author G.K.Stephens + * + * @param
+ * Many of the classes and interfaces that are typically utilized in the buffer pattern this
+ * supporting class is designed to enable mocking support. So, in most cases, this static creation
+ * method should be sufficient to serve as a simple means of using this class.
+ *
+ * @param The "parent" class or interface used in {@link Writable} interface
+ * @param
+ * As of now, the implementation that we have chosen in
+ * JAFAMA
+ *
+ * I took the method comments from {@link FastMath}.
+ *
+ * @author G.K.Stephens
+ *
+ */
+public class PicanteMath {
+
+ /*
+ * Switches to the JDK (java.lang.Math) instead. As this should be only be set to true once, and
+ * because making this lockable would have performance implications, I choose to make this
+ * volatile instead of using AtomicBoolean.
+ */
+ private static volatile boolean USE_JDK_MATH = false;
+
+ /**
+ * Constructor should be private
+ */
+ private PicanteMath() {};
+
+ /**
+ * Calling this method uses the standard JDK (java.lang.Math) methods instead of the preferred
+ * Crucible math methods. This is intended to be called once at the beginning of the application.
+ * Therefore, and because of performance considerations, this has not been implemented in a thread
+ * safe way. To encourage safe use with this method, no method has been provided to switch back.
+ */
+ public static void useJdkMath() {
+ USE_JDK_MATH = true;
+ }
+
+ /**
+ * This shouldn't be called, it is only here for unit testing.
+ */
+ @VisibleForTesting
+ static void useFastMath() {
+ USE_JDK_MATH = false;
+ }
+
+ /**
+ * The {@code double} value that is closer than any other to e, the base of the natural
+ * logarithms.
+ */
+ public static final double E = Math.E;
+
+ /**
+ * The {@code double} value that is closer than any other to pi, the ratio of the
+ * circumference of a circle to its diameter.
+ */
+ public static final double PI = Math.PI;
+
+ /**
+ * @param angle Angle in radians.
+ * @return Angle sine.
+ */
+ public static double sin(double a) {
+ if (USE_JDK_MATH) {
+ return Math.sin(a);
+ }
+ return FastMath.sin(a);
+ }
+
+ /**
+ * @param angle Angle in radians.
+ * @return Angle cosine.
+ */
+ public static double cos(double a) {
+ if (USE_JDK_MATH) {
+ return Math.cos(a);
+ }
+ return FastMath.cos(a);
+ }
+
+ /**
+ * Can have very bad relative error near +-PI/2, but of the same magnitude than the relative delta
+ * between StrictMath.tan(PI/2) and StrictMath.tan(nextDown(PI/2)).
+ *
+ * @param angle Angle in radians.
+ * @return Angle tangent.
+ */
+ public static double tan(double a) {
+ if (USE_JDK_MATH) {
+ return Math.tan(a);
+ }
+ return FastMath.tan(a);
+ }
+
+ /**
+ * @param value Value in [-1,1].
+ * @return Value arcsine, in radians, in [-PI/2,PI/2].
+ */
+ public static double asin(double a) {
+ if (USE_JDK_MATH) {
+ return Math.asin(a);
+ }
+ return FastMath.asin(a);
+ }
+
+ /**
+ * @param value Value in [-1,1].
+ * @return Value arccosine, in radians, in [0,PI].
+ */
+ public static double acos(double a) {
+ if (USE_JDK_MATH) {
+ return Math.acos(a);
+ }
+ return FastMath.acos(a);
+ }
+
+ /**
+ * @param value A double value.
+ * @return Value arctangent, in radians, in [-PI/2,PI/2].
+ */
+ public static double atan(double a) {
+ if (USE_JDK_MATH) {
+ return Math.atan(a);
+ }
+ return FastMath.atan(a);
+ }
+
+ /**
+ * Gives same result as Math.toRadians for some particular values like 90.0, 180.0 or 360.0, but
+ * is faster (no division).
+ *
+ * @param angdeg Angle value in degrees.
+ * @return Angle value in radians.
+ */
+ public static double toRadians(double angdeg) {
+ if (USE_JDK_MATH) {
+ return Math.toRadians(angdeg);
+ }
+ return FastMath.toRadians(angdeg);
+ }
+
+ /**
+ * Gives same result as Math.toDegrees for some particular values like Math.PI/2, Math.PI or
+ * 2*Math.PI, but is faster (no division).
+ *
+ * @param angrad Angle value in radians.
+ * @return Angle value in degrees.
+ */
+ public static double toDegrees(double angrad) {
+ if (USE_JDK_MATH) {
+ return Math.toDegrees(angrad);
+ }
+ return FastMath.toDegrees(angrad);
+ }
+
+ /**
+ * @param value A double value.
+ * @return e^value.
+ */
+ public static double exp(double a) {
+ if (USE_JDK_MATH) {
+ return Math.exp(a);
+ }
+ return FastMath.exp(a);
+ }
+
+ /**
+ * @param value A double value.
+ * @return Value logarithm (base e).
+ */
+ public static double log(double a) {
+ if (USE_JDK_MATH) {
+ return Math.log(a);
+ }
+ return FastMath.log(a);
+ }
+
+ /**
+ * @param value A double value.
+ * @return Value logarithm (base 10).
+ */
+ public static double log10(double a) {
+ if (USE_JDK_MATH) {
+ return Math.log10(a);
+ }
+ return FastMath.log10(a);
+ }
+
+ /**
+ * @param value A double value.
+ * @return 1+Value logarithm (base e).
+ */
+ public static double log1p(double a) {
+ if (USE_JDK_MATH) {
+ return Math.log1p(a);
+ }
+ return FastMath.log1p(a);
+ }
+
+ /**
+ * @param value A double value.
+ * @return Value square root.
+ */
+ public static double sqrt(double a) {
+ if (USE_JDK_MATH) {
+ return Math.sqrt(a);
+ }
+ return FastMath.sqrt(a);
+ }
+
+ /**
+ * @param value A double value.
+ * @return Value cubic root.
+ */
+ public static double cbrt(double a) {
+ if (USE_JDK_MATH) {
+ return Math.cbrt(a);
+ }
+ return FastMath.cbrt(a);
+ }
+
+ public static double IEEEremainder(double f1, double f2) {
+ if (USE_JDK_MATH) {
+ return Math.IEEEremainder(f1, f2);
+ }
+ return FastMath.IEEEremainder(f1, f2);
+ }
+
+ /**
+ * @param value A double value.
+ * @return Ceiling of value.
+ */
+ public static double ceil(double a) {
+ if (USE_JDK_MATH) {
+ return Math.ceil(a);
+ }
+ return FastMath.ceil(a);
+ }
+
+ /**
+ * @param value A double value.
+ * @return Floor of value.
+ */
+ public static double floor(double a) {
+ if (USE_JDK_MATH) {
+ return Math.floor(a);
+ }
+ return FastMath.floor(a);
+ }
+
+ /**
+ * For special values for which multiple conventions could be adopted, behaves like
+ * Math.atan2(double,double).
+ *
+ * @param y Coordinate on y axis.
+ * @param x Coordinate on x axis.
+ * @return Angle from x axis positive side to (x,y) position, in radians, in [-PI,PI]. Angle
+ * measure is positive when going from x axis to y axis (positive sides).
+ */
+ public static double atan2(double y, double x) {
+ if (USE_JDK_MATH) {
+ return Math.atan2(y, x);
+ }
+ return FastMath.atan2(y, x);
+ }
+
+ /**
+ * 1e-13ish accuracy or better on whole double range.
+ *
+ * @param value A double value.
+ * @param power A power.
+ * @return value^power.
+ */
+ public static double pow(double a, double b) {
+ if (USE_JDK_MATH) {
+ return Math.pow(a, b);
+ }
+ return FastMath.pow(a, b);
+ }
+
+ /**
+ * Might have different semantics than Math.round(double), see bugs 6430675 and 8010430.
+ *
+ * @param value A double value.
+ * @return Value rounded to nearest long, choosing superior long in case two are equally close
+ * (i.e. rounding-up).
+ */
+ public static long round(double a) {
+ if (USE_JDK_MATH) {
+ return Math.round(a);
+ }
+ return FastMath.round(a);
+ }
+
+ /**
+ * @param value An int value.
+ * @return The absolute value, except if value is Integer.MIN_VALUE, for which it returns
+ * Integer.MIN_VALUE.
+ */
+ public static int abs(int a) {
+ return Math.abs(a);
+ }
+
+ /**
+ * Returns the absolute value of a {@code double} value. If the argument is not negative, the
+ * argument is returned. If the argument is negative, the negation of the argument is returned.
+ * Special cases:
+ *
+ * {@code Double.longBitsToDouble((Double.doubleToLongBits(a)<<1)>>>1)}
+ *
+ * @param a the argument whose absolute value is to be determined
+ * @return the absolute value of the argument.
+ */
+ public static double abs(double a) {
+ if (USE_JDK_MATH) {
+ return Math.abs(a);
+ }
+ return FastMath.abs(a);
+ }
+
+ /**
+ * Returns the greater of two {@code int} values. That is, the result is the argument closer to
+ * the value of {@link Integer#MAX_VALUE}. If the arguments have the same value, the result is
+ * that same value.
+ *
+ * @param a an argument.
+ * @param b another argument.
+ * @return the larger of {@code a} and {@code b}.
+ */
+ public static int max(int a, int b) {
+ return Math.max(a, b);
+ }
+
+ /**
+ * Returns the greater of two {@code double} values. That is, the result is the argument closer to
+ * positive infinity. If the arguments have the same value, the result is that same value. If
+ * either value is NaN, then the result is NaN. Unlike the numerical comparison operators, this
+ * method considers negative zero to be strictly smaller than positive zero. If one argument is
+ * positive zero and the other negative zero, the result is positive zero.
+ *
+ * @param a an argument.
+ * @param b another argument.
+ * @return the larger of {@code a} and {@code b}.
+ */
+ public static double max(double a, double b) {
+ if (USE_JDK_MATH) {
+ return Math.max(a, b);
+ }
+ return FastMath.max(a, b);
+ }
+
+ /**
+ * Returns the smaller of two {@code int} values. That is, the result the argument closer to the
+ * value of {@link Integer#MIN_VALUE}. If the arguments have the same value, the result is that
+ * same value.
+ *
+ * @param a an argument.
+ * @param b another argument.
+ * @return the smaller of {@code a} and {@code b}.
+ */
+ public static int min(int a, int b) {
+ return Math.min(a, b);
+ }
+
+ /**
+ * Returns the smaller of two {@code double} values. That is, the result is the value closer to
+ * negative infinity. If the arguments have the same value, the result is that same value. If
+ * either value is NaN, then the result is NaN. Unlike the numerical comparison operators, this
+ * method considers negative zero to be strictly smaller than positive zero. If one argument is
+ * positive zero and the other is negative zero, the result is negative zero.
+ *
+ * @param a an argument.
+ * @param b another argument.
+ * @return the smaller of {@code a} and {@code b}.
+ */
+ public static double min(double a, double b) {
+ if (USE_JDK_MATH) {
+ return Math.min(a, b);
+ }
+ return FastMath.min(a, b);
+ }
+
+ /**
+ * The ULP (Unit in the Last Place) is the distance to the next value larger in magnitude.
+ *
+ * @param value A double value.
+ * @return The size of an ulp of the specified value, or Double.MIN_VALUE if it is +-0.0, or
+ * +Infinity if it is +-Infinity, or NaN if it is NaN.
+ */
+ public static double ulp(double d) {
+ if (USE_JDK_MATH) {
+ return Math.ulp(d);
+ }
+ return FastMath.ulp(d);
+ }
+
+ /**
+ * @param value A double value.
+ * @return -1.0 if the specified value is < 0, 1.0 if it is > 0, and the value itself if it is NaN
+ * or +-0.0.
+ */
+ public static double signum(double d) {
+ if (USE_JDK_MATH) {
+ return Math.signum(d);
+ }
+ return FastMath.signum(d);
+ }
+
+ /**
+ * Some properties of sinh(x) = (exp(x)-exp(-x))/2:
+ *
+ *
+ * The constructors of this class are private and as such are unavailable for use. {@link Builder}
+ * is used to create instances of this class, as are the static "create" convenience
+ * methods present on this class.
+ *
+ * The value of variance, standard deviation, skewness and kurtosis may be either computed with the
+ * defining, biased operations or with the unbiased ones. See
+ * {@link Builder#useEstimator(Estimator)} for details.
+ *
+ * When a statistics instance is built, an estimator and a index tracker are utilized implicitly in
+ * its construction. These enumerations are captured in the instance for reference purposes and
+ * available via the methods {@link Statistics#getEstimator()} and {@link Statistics#getTracker()}.
+ *
+ * The value stored here is impacted by the estimator utilized to build the statistics. See
+ * {@link Estimator} for details.
+ *
+ * The value stored here is impacted by the estimator utilized to build the statistics. See
+ * {@link Estimator} for details.
+ *
+ * The value stored here is impacted by the estimator utilized to build the statistics. See
+ * {@link Estimator} for details.
+ *
+ * The value stored here is impacted by the estimator utilized to build the statistics. See
+ * {@link Estimator} for details.
+ *
+ * The kurtosis of a normal distribution is 3, so this value here subtracts 3 from it to yield an
+ * excess kurtosis of a normal distribution as 0.
+ *
+ * Depending on the index tracker utilized, this list may be empty, contain all of the indices at
+ * which minimums occurred, or a subset of these indices. If multiple iterators or iterables were
+ * used to accumulate the statistics, then the indices presented here start at 0 with the first
+ * value supplied to the builder in the accumulation process and increase monotonically.
+ *
+ * Depending on the index tracker utilized, this list may be empty, contain all of the indices at
+ * which maximums occurred, or a subset of these indices. If multiple iterators or iterables were
+ * used to accumulate the statistics, then the indices presented here start at 0 with the first
+ * value supplied to the builder in the accumulation process and increase monotonically.
+ *
+ * The builder has a few configuration options, namely specification of an index tracker
+ * {@link Tracker} and a higher order estimator {@link Estimator}. The tracker must be supplied to
+ * the constructor as it has a fundamental impact on the way the builder accumulates the indices
+ * of the extrema, so this decision must be made at construction time. On the other hand, the
+ * statistics estimation is performed just at build time, so it may be adjusted after the builder
+ * is created. {@link Builder#Builder()} describes the default options for the builder.
+ *
+ * If you are intending to accumulate statistics individually {@link Builder#accumulate(double)}
+ * or via multiple invocations of {@link Builder#accumulate(Iterable)} or
+ * {@link Builder#accumulate(Iterator)} the indices present in the Statistics class will be
+ * referenced assuming the first element starts with index 0 and increases from there. So the
+ * connection back to the indices of the various accumulation entry points is broken.
+ *
+ * Since the path is closed, the following should be true in general:
+ *
+ *
+ *
+ * In other words, the result is the same as the value of the expression:
+ *
+ * 1) defined on ]-Infinity,+Infinity[
+ * 2) result in ]-Infinity,+Infinity[
+ * 3) sinh(x) = -sinh(-x) (implies sinh(0) = 0)
+ * 4) sinh(epsilon) ~= epsilon
+ * 5) lim(sinh(x),x->+Infinity) = +Infinity
+ * (y increasing exponentially faster than x)
+ * 6) reaches +Infinity (double overflow) for x >= 710.475860073944,
+ * i.e. a bit further than exp(x)
+ *
+ *
+ * @param x A double value.
+ * @return Value hyperbolic sine.
+ */
+ public static double sinh(double x) {
+ if (USE_JDK_MATH) {
+ return Math.sinh(x);
+ }
+ return FastMath.sinh(x);
+ }
+
+ /**
+ * Some properties of cosh(x) = (exp(x)+exp(-x))/2:
+ *
+ *
+ * 1) defined on ]-Infinity,+Infinity[
+ * 2) result in [1,+Infinity[
+ * 3) cosh(0) = 1
+ * 4) cosh(x) = cosh(-x)
+ * 5) lim(cosh(x),x->+Infinity) = +Infinity
+ * (y increasing exponentially faster than x)
+ * 6) reaches +Infinity (double overflow) for x >= 710.475860073944,
+ * i.e. a bit further than exp(x)
+ *
+ *
+ * @param x A double value.
+ * @return Value hyperbolic cosine.
+ */
+ public static double cosh(double x) {
+ if (USE_JDK_MATH) {
+ return Math.cosh(x);
+ }
+ return FastMath.cosh(x);
+ }
+
+ /**
+ * Some properties of tanh(x) = sinh(x)/cosh(x) = (exp(2*x)-1)/(exp(2*x)+1):
+ *
+ *
+ * 1) defined on ]-Infinity,+Infinity[
+ * 2) result in ]-1,1[
+ * 3) tanh(x) = -tanh(-x) (implies tanh(0) = 0)
+ * 4) tanh(epsilon) ~= epsilon
+ * 5) lim(tanh(x),x->+Infinity) = 1
+ * 6) reaches 1 (double loss of precision) for x = 19.061547465398498
+ *
+ *
+ * @param x A double value.
+ * @return Value hyperbolic tangent.
+ */
+ public static double tanh(double x) {
+ if (USE_JDK_MATH) {
+ return Math.tanh(x);
+ }
+ return FastMath.tanh(x);
+ }
+
+ /**
+ * Some properties of acosh(x) = log(x + sqrt(x^2 - 1)):
+ *
+ *
+ * 1) defined on [1,+Infinity[
+ * 2) result in ]0,+Infinity[ (by convention, since cosh(x) = cosh(-x))
+ * 3) acosh(1) = 0
+ * 4) acosh(1+epsilon) ~= log(1 + sqrt(2*epsilon)) ~= sqrt(2*epsilon)
+ * 5) lim(acosh(x),x->+Infinity) = +Infinity
+ * (y increasing logarithmically slower than x)
+ *
+ *
+ * @param x A double value.
+ * @return Value hyperbolic arccosine.
+ */
+ public static double acosh(double x) {
+ return FastMath.acosh(x);
+ }
+
+ /**
+ * Some properties of asinh(x) = log(x + sqrt(x^2 + 1)):
+ *
+ *
+ * 1) defined on ]-Infinity,+Infinity[
+ * 2) result in ]-Infinity,+Infinity[
+ * 3) asinh(x) = -asinh(-x) (implies asinh(0) = 0)
+ * 4) asinh(epsilon) ~= epsilon
+ * 5) lim(asinh(x),x->+Infinity) = +Infinity
+ * (y increasing logarithmically slower than x)
+ *
+ *
+ * @param x A double value.
+ * @return Value hyperbolic arcsine.
+ */
+ public static double asinh(double x) {
+ return FastMath.asinh(x);
+ }
+
+ /**
+ * Some properties of atanh(x) = log((1+x)/(1-x))/2:
+ *
+ *
+ * 1) defined on ]-1,1[
+ * 2) result in ]-Infinity,+Infinity[
+ * 3) atanh(-1) = -Infinity (by continuity)
+ * 4) atanh(1) = +Infinity (by continuity)
+ * 5) atanh(epsilon) ~= epsilon
+ * 6) lim(atanh(x),x->1) = +Infinity
+ *
+ *
+ * @param x A double value.
+ * @return Value hyperbolic arctangent.
+ */
+ public static double atanh(double x) {
+ return FastMath.atanh(x);
+ }
+
+ /**
+ * @return sqrt(x^2+y^2) without intermediate overflow or underflow.
+ */
+ public static double hypot(double x, double y) {
+ if (USE_JDK_MATH) {
+ return Math.hypot(x, y);
+ }
+ return FastMath.hypot(x, y);
+ }
+
+ /**
+ * @param x A double value.
+ * @return The double mathematical integer closest to the specified value, choosing even one if
+ * two are equally close, or respectively NaN, +-Infinity or +-0.0 if the value is any of
+ * these.
+ */
+ public static double rint(double x) {
+ if (USE_JDK_MATH) {
+ return Math.rint(x);
+ }
+ return FastMath.rint(x);
+
+ }
+
+}
diff --git a/src/main/java/picante/math/Statistics.java b/src/main/java/picante/math/Statistics.java
new file mode 100644
index 0000000..7b8bcb0
--- /dev/null
+++ b/src/main/java/picante/math/Statistics.java
@@ -0,0 +1,729 @@
+package picante.math;
+
+import static picante.math.PicanteMath.pow;
+import static picante.math.PicanteMath.sqrt;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import picante.designpatterns.BuildFailedException;
+
+/**
+ * Immutable class capturing statistics of a sequence of double precision numbers.
+ *
+ * plot(getDomain().getBegin(), new Point2D.Double()).equals(plot(getDomain().getEnd(), new Point2D.Double())
+ *
+ *
+ * which basically asserts that the plotted point at the beginning and end of the domain is the
+ * same.
+ *
+ * In our current plan, these cones should either be convex or the complement of a convex cone. + * Algorithms for manipulating implementations of this interface may implicitly assume that is the + * case. + *
+ * + * @author C.M. O'Shea + * @author R.T. Poffenbarger + * + */ +public interface Cone { + + /** + * Retrieves the vector from the origin of the coordinate system to the cone's vertex. + * + * @return a vector, may be {@link VectorIJK#ZERO} + */ + UnwritableVectorIJK getVertex(); + + /** + * Retrieves a vector along the edge of the cone. + * + * @param parameter the value of the parameter specifying which edge vector to select. + * + * @return a vector, of non-zero length, that points from the vertex along the edge of the cone at + * parameter p + */ + UnwritableVectorIJK getEdge(double parameter); + + /** + * Retrieves the domain of the parameter p. + * + * @return an interval, where {@link UnwritableInterval#getBegin()} and + * {@link UnwritableInterval#getEnd()} map to the same edge vector along the cone. + */ + UnwritableInterval getParameterDomain(); + + /** + * Retrieves a vector in the interior of the cone. + * + * @return the vector + */ + UnwritableVectorIJK getInteriorPoint(); +} diff --git a/src/main/java/picante/math/cones/ConeFunction.java b/src/main/java/picante/math/cones/ConeFunction.java new file mode 100644 index 0000000..0b58897 --- /dev/null +++ b/src/main/java/picante/math/cones/ConeFunction.java @@ -0,0 +1,24 @@ +package picante.math.cones; + +/** + * A univariate function that returns a cone. + *+ * The parameter of the function would usually be time, to capture the evolution of a cone in time. + *
+ * + * @author C.M. O'Shea + * @author R.T. Poffenbarger + * + */ +public interface ConeFunction { + + /** + * Returns a cone as evaluated at the specified parameter t. + * + * @param t the parameter + * + * @return the cone + */ + Cone evaluate(double t); + +} diff --git a/src/main/java/picante/math/cones/Cones.java b/src/main/java/picante/math/cones/Cones.java new file mode 100644 index 0000000..9972c00 --- /dev/null +++ b/src/main/java/picante/math/cones/Cones.java @@ -0,0 +1,810 @@ +package picante.math.cones; + +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.operation.union.CascadedPolygonUnion; +import com.google.common.base.Preconditions; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import picante.math.coords.CoordConverters; +import picante.math.coords.LatitudinalVector; +import picante.math.intervals.UnwritableInterval; +import picante.math.vectorspace.RotationMatrixIJK; +import picante.math.vectorspace.UnwritableVectorIJK; +import picante.math.vectorspace.VectorIJK; +import picante.surfaces.Ellipse; +import picante.surfaces.Ellipsoid; +import picante.surfaces.NoIntersectionException; +import picante.surfaces.Plane; + +/** + * Static utility method collection for manipulating and working with {@link Cone}s and + * {@link ConeFunction}s + * + * @author C.M. O'Shea + * @author R.T. Poffenbarger + * + */ +public class Cones { + + /** + * Block construction of the class, there's no need for instances of it to exist. + */ + private Cones() {} + + /** + * Creates a constant {@link ConeFunction} + * + * @param cone the constant value + * + * @return a {@link ConeFunction} that returns the supplied cone for any provided input + */ + public static ConeFunction createConstant(Cone cone) { + return (t) -> cone; + } + + + /** + * Creates a {@link ClosedPathPlotter} from a cone with a vertex at the center of the map + * projected body. + * + * @param cone the cone to plot + * + * @return the closed path plotter + */ + static ClosedPathPlotter plot(Cone cone) { + return new ClosedPathPlotter() { + @Override + public Point2D plot(double p, Point2D buffer) { + return PathPlotters.convert(cone.getEdge(p), buffer); + } + + @Override + public boolean isReferencePointInterior() { + return true; + } + + @Override + public Point2D getReferencePoint(Point2D buffer) { + return PathPlotters.convert(cone.getInteriorPoint(), buffer); + } + + @Override + public UnwritableInterval getDomain() { + return cone.getParameterDomain(); + } + }; + } + + /** + * Create an {@link EllipticalCone}. The first generating vector of the ellipse is the projection + * of the reference vector onto the plane normal to the boresight, scaled by the tangent of the + * reference angle. The second generating vector is the unitized cross product of the first + * generating vector and the boresight, scaled by the tangent or the cross angle. + * + * @param vertex cone vertex + * @param boresight cone axis + * @param refVector + * @param refAngle + * @param crossAngle + * @return + */ + public static EllipticalCone createEllipticalCone(UnwritableVectorIJK vertex, + UnwritableVectorIJK boresight, UnwritableVectorIJK refVector, double refAngle, + double crossAngle) { + + UnwritableVectorIJK interiorPoint = boresight.createUnitized(); + VectorIJK axis1 = VectorIJK.planeProject(refVector, interiorPoint); + VectorIJK axis2 = VectorIJK.uCross(axis1, interiorPoint); + axis1.unitize().scale(Math.tan(refAngle)); + axis2.unitize().scale(Math.tan(crossAngle)); + + return new EllipticalCone(vertex, interiorPoint, axis1, axis2); + } + + /** + * Create an {@link EllipticalCone} from a vertex and Ellipse + */ + public static EllipticalCone createEllipticalCone(UnwritableVectorIJK bodyToConeVertex, + Ellipse limb) { + UnwritableVectorIJK semiMajor = new VectorIJK(limb.getSemiMajorAxis()); + UnwritableVectorIJK semiMinor = new VectorIJK(limb.getSemiMinorAxis()); + return new EllipticalCone(bodyToConeVertex, limb.getCenter(), semiMajor, semiMinor); + } + + + /** + * Create a {@link PolygonalCone} + */ + public static PolygonalCone createPolygonalCone(UnwritableVectorIJK vertex, + UnwritableVectorIJK interiorPt, List+ (1) Normalize BSIGHT, label it B. + + (2) Compute the unit vector in the plane defined by REFVEC + and B that is normal to B and pointing towards + REFVEC, label this B1. + + (3) Cross B and B1 to obtain B2. These three vectors + form a basis that is 'aligned' with the FOV cone. + + (4) Compute the inward normals to the sides of the + rectangular cone in a counter-clockwise order + about the boresight: + + NORMAL(1) = -COS(REFANG)*B1 + SIN(REFANG)*B + NORMAL(2) = -COS(CRSANG)*B2 + SIN(CRSANG)*B + NORMAL(3) = COS(REFANG)*B1 + SIN(REFANG)*B + NORMAL(4) = COS(CRSANG)*B2 + SIN(CRSANG)*B + + (5) Compute the appropriate cross products to obtain + a set of boundary corner vectors: + + BOUNDS(1) = NORMAL(1) x NORMAL(2) + BOUNDS(2) = NORMAL(2) x NORMAL(3) + BOUNDS(3) = NORMAL(3) x NORMAL(4) + BOUNDS(4) = NORMAL(4) x NORMAL(1) + + (6) Unitize BOUNDS. + *+ * + * + * @param vertex + * @param boresight axis of the cone + * @param refVector together with the boresight vector defines the plane in which refAngle is + * measured + * @param refAngle 1/2 of the total angular extent of the cone in the plane defined by the + * boresight and refVector + * @param crossAngle 1/2 of the total angular extent of the cone in the plane defined by the + * boresight and perpendicular to the refAngle plane + * @return + */ + public static PolygonalCone createRectangularCone(UnwritableVectorIJK vertex, + UnwritableVectorIJK boresight, UnwritableVectorIJK refVector, double refAngle, + double crossAngle) { + + UnwritableVectorIJK b = boresight.createUnitized(); + Plane p = new Plane(b, vertex); + VectorIJK b1 = p.projectOnto(refVector).unitize(); + VectorIJK b2 = VectorIJK.cross(b, b1); + + double cosRef = Math.cos(refAngle); + double sinRef = Math.sin(refAngle); + double cosCrs = Math.cos(crossAngle); + double sinCrs = Math.sin(crossAngle); + + VectorIJK normal1 = VectorIJK.combine(-cosRef, b1, sinRef, b); + VectorIJK normal2 = VectorIJK.combine(-cosCrs, b2, sinCrs, b); + VectorIJK normal3 = VectorIJK.combine(cosRef, b1, sinRef, b); + VectorIJK normal4 = VectorIJK.combine(cosCrs, b2, sinCrs, b); + + List
+ *
+ * If the above criteria aren't met by the input, then this function just returns + * {@link GeometryCollection} with the input list of {@link Geometry} instances. + *
+ */ +class GeometrySanitizer implements Function+ * This method uses {@link Class#equals(Object)}, because it treats sub-classes differently. That + * is to say, a {@link LinearRing} is not the same as a {@link LineString}. + *
+ * + * @param geometries + * + * @return true of all entries in geometries are the exactly same {@link Class}, false otherwise. + */ + Class extends Geometry> isHomogeneous(List extends Geometry> geometries) { + + Class extends Geometry> result = geometries.get(0).getClass(); + + for (Geometry geometry : geometries) { + if (!result.equals(geometry.getClass())) { + return null; + } + } + + return result; + + } + + Geometry processCoverings(List extends Geometry> rings) { + + /* + * This is a bit of a mess. Determine if all of the rings are independent or holes in other + * rings. If that's the case then return a MultiPolygon, otherwise return a MultiLineString. + */ + ImmutableListMultimap+ * This implementation should be thread-safe, from Martin Davis: "GeometryFactory is + * immutable so is thread safe. CoordinateSequenceFactory is an interface, so it's up to the + * implementation. But normally they should be thread-safe." + * (link) + *
+ */ + static GeometryFactory PACKED_DOUBLE_GEOMETRY_FACTORY = + new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING), 0, + ModifiedPackedCoordinateSequenceFactory.DOUBLE_FACTORY); + + /** + * Simple {@link PathIterator} filter that throws an exception when the path is done, i.e. empty. + */ + static Function+ * Individual points really have no place being in shapes that are to be rendered in Java2D, + * though the API allows for it. + *
+ */ + static Function+ * Isolated points are removed prior to conversion. Open paths are converted to + * {@link LineString}. Closed paths are converted to {@link Polygon}s. If multiple of either are + * required, then the corresponding {@link MultiLineString} or {@link MultiPolygon} are used + * instead. If anything goes unexpectedly, then an {@link UnsupportedOperationException} will be + * thrown or a {@link GeometryCollection} with a variety of different types will be returned. + *
+ * + * @return a new function to convert path iterators. + */ + public static Function+ * The purpose of this class is to work around the assignment problem, when a packed coordinate + * sequence of higher dimension has been requested to set one of lower dimension. The current JTS + * code fails, but this method will allow it--setting the higher dimensional component to + * {@link java.lang.Double#NaN}. + *
+ */ +class ModifiedPackedCoordinateSequence { + + public static class Double extends PackedCoordinateSequence.Double { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Safely converts an array of {@link Coordinate} to a double array, if the dimension of all + * input coordinates is less than that of the data store. + * + * @param coordinates + * @param dimension + * + * @return + */ + static double[] convert(Coordinate[] coordinates, int dimension) { + if (coordinates == null) { + coordinates = new Coordinate[0]; + } + + double[] coords = new double[coordinates.length * dimension]; + for (int i = 0; i < coordinates.length; i++) { + coords[i * dimension] = coordinates[i].x; + if (dimension >= 2) { + coords[i * dimension + 1] = coordinates[i].y; + } + if (dimension >= 3) { + coords[i * dimension + 2] = getOrdinate(coordinates[i], 2); + } + if (dimension >= 4) { + coords[i * dimension + 3] = getOrdinate(coordinates[i], 3); + } + } + return coords; + + } + + /** + * Retrieves the ordinate of the supplied coordinate, or NaN if it can not be provided. + * + * @param coord + * @param ordinate + * + * @return the value at ordinate in coord, or {@link java.lang.Double#NaN} if not available + */ + static double getOrdinate(Coordinate coord, int ordinate) { + + if (coord instanceof CoordinateXY) { + return java.lang.Double.NaN; + } + + if (ordinate == 2) { + return coord.getOrdinate(2); + } + + if (ordinate == 3) { + if (coord instanceof CoordinateXYZM) { + return coord.getOrdinate(3); + } + } + + return java.lang.Double.NaN; + + } + + Double(Coordinate[] coordinates, int dimension, int measures) { + this(convert(coordinates, dimension), dimension, measures); + } + + Double(Coordinate[] coordinates, int dimension) { + this(coordinates, dimension, 0); + } + + Double(Coordinate[] coordinates) { + this(coordinates, 3); + } + + Double(double[] coords, int dimension, int measures) { + super(coords, dimension, measures); + } + + Double(float[] coordinates, int dimension, int measures) { + super(coordinates, dimension, measures); + } + + Double(int size, int dimension, int measures) { + super(size, dimension, measures); + } + + + + } + + public static class Float extends PackedCoordinateSequence.Float { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * Safely converts an array of {@link Coordinate} to a float array, if the dimension of all + * input coordinates is less than that of the data store. + * + * @param coordinates + * @param dimension + * + * @return + */ + static float[] convert(Coordinate[] coordinates, int dimension) { + if (coordinates == null) { + coordinates = new Coordinate[0]; + } + + float[] coords = new float[coordinates.length * dimension]; + for (int i = 0; i < coordinates.length; i++) { + coords[i * dimension] = (float) coordinates[i].x; + if (dimension >= 2) { + coords[i * dimension + 1] = (float) coordinates[i].y; + } + if (dimension >= 3) { + coords[i * dimension + 2] = getOrdinate(coordinates[i], 2); + } + if (dimension >= 4) { + coords[i * dimension + 3] = getOrdinate(coordinates[i], 3); + } + } + return coords; + + } + + /** + * Retrieves the ordinate of the supplied coordinate, or NaN if it can not be provided. + * + * @param coord + * @param ordinate + * + * @return the value at ordinate in coord cast to a float, or {@link java.lang.Float#NaN} if not + * available + */ + static float getOrdinate(Coordinate coord, int ordinate) { + + if (coord instanceof CoordinateXY) { + return java.lang.Float.NaN; + } + + if (ordinate == 2) { + return (float) coord.getOrdinate(2); + } + + if (ordinate == 3) { + if (coord instanceof CoordinateXYZM) { + return (float) coord.getOrdinate(3); + } + } + + return java.lang.Float.NaN; + + } + + + Float(Coordinate[] coordinates, int dimension, int measures) { + this(convert(coordinates, dimension), dimension, measures); + } + + Float(Coordinate[] coordinates, int dimension) { + this(coordinates, dimension, 0); + } + + Float(Coordinate[] coordinates) { + this(coordinates, 3); + } + + Float(double[] coords, int dimension, int measures) { + super(coords, dimension, measures); + } + + Float(float[] coordinates, int dimension, int measures) { + super(coordinates, dimension, measures); + } + + Float(int size, int dimension, int measures) { + super(size, dimension, measures); + } + + + + } + +} diff --git a/src/main/java/picante/math/cones/ModifiedPackedCoordinateSequenceFactory.java b/src/main/java/picante/math/cones/ModifiedPackedCoordinateSequenceFactory.java new file mode 100644 index 0000000..d74ef7a --- /dev/null +++ b/src/main/java/picante/math/cones/ModifiedPackedCoordinateSequenceFactory.java @@ -0,0 +1,125 @@ +package picante.math.cones; + +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.CoordinateSequence; +import org.locationtech.jts.geom.Coordinates; +import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory; + +/** + * Implementation of {@link PackedCoordinateSequenceFactory} that provides + * {@link ModifiedPackedCoordinateSequence}s + */ +class ModifiedPackedCoordinateSequenceFactory extends PackedCoordinateSequenceFactory { + + private static final long serialVersionUID = 1L; + + public static final int DOUBLE = 0; + public static final int FLOAT = 1; + + public static final ModifiedPackedCoordinateSequenceFactory DOUBLE_FACTORY = + new ModifiedPackedCoordinateSequenceFactory(DOUBLE); + + public static final ModifiedPackedCoordinateSequenceFactory FLOAT_FACTORY = + new ModifiedPackedCoordinateSequenceFactory(FLOAT); + + private int type = DOUBLE; + + /** + * Creates a new PackedCoordinateSequenceFactory of type DOUBLE. + */ + public ModifiedPackedCoordinateSequenceFactory() { + this(DOUBLE); + } + + /** + * Creates a new PackedCoordinateSequenceFactory of the given type. Acceptable type values are + * {@linkplain PackedCoordinateSequenceFactory#Float}or + * {@linkplain PackedCoordinateSequenceFactory#Double} + */ + public ModifiedPackedCoordinateSequenceFactory(int type) { + this.type = type; + } + + @Override + public int getType() { + return super.getType(); + } + + @Override + public CoordinateSequence create(Coordinate[] coordinates) { + int dimension = 3; + int measures = 0; + if (coordinates != null && coordinates.length > 1 && coordinates[0] != null) { + Coordinate first = coordinates[0]; + dimension = Coordinates.dimension(first); + measures = Coordinates.measures(first); + } + if (type == DOUBLE) { + return new ModifiedPackedCoordinateSequence.Double(coordinates, dimension, measures); + } else { + return new ModifiedPackedCoordinateSequence.Float(coordinates, dimension, measures); + } + } + + @Override + public CoordinateSequence create(CoordinateSequence coordSeq) { + int dimension = coordSeq.getDimension(); + int measures = coordSeq.getMeasures(); + if (type == DOUBLE) { + return new ModifiedPackedCoordinateSequence.Double(coordSeq.toCoordinateArray(), dimension, + measures); + } else { + return new ModifiedPackedCoordinateSequence.Float(coordSeq.toCoordinateArray(), dimension, + measures); + } + } + + @Override + public CoordinateSequence create(double[] packedCoordinates, int dimension) { + return create(packedCoordinates, dimension, 0); + } + + @Override + public CoordinateSequence create(double[] packedCoordinates, int dimension, int measures) { + if (type == DOUBLE) { + return new ModifiedPackedCoordinateSequence.Double(packedCoordinates, dimension, measures); + } else { + return new ModifiedPackedCoordinateSequence.Float(packedCoordinates, dimension, measures); + } + } + + @Override + public CoordinateSequence create(float[] packedCoordinates, int dimension) { + return create(packedCoordinates, dimension, 0); + } + + @Override + public CoordinateSequence create(float[] packedCoordinates, int dimension, int measures) { + if (type == DOUBLE) { + return new ModifiedPackedCoordinateSequence.Double(packedCoordinates, dimension, measures); + } else { + return new ModifiedPackedCoordinateSequence.Float(packedCoordinates, dimension, measures); + } + } + + @Override + public CoordinateSequence create(int size, int dimension) { + if (type == DOUBLE) { + return new ModifiedPackedCoordinateSequence.Double(size, dimension, 0); + } else { + return new ModifiedPackedCoordinateSequence.Float(size, dimension, 0); + } + } + + @Override + public CoordinateSequence create(int size, int dimension, int measures) { + if (type == DOUBLE) { + return new ModifiedPackedCoordinateSequence.Double(size, dimension, measures); + } else { + return new ModifiedPackedCoordinateSequence.Float(size, dimension, measures); + } + } + + + +} diff --git a/src/main/java/picante/math/cones/PathPlotter.java b/src/main/java/picante/math/cones/PathPlotter.java new file mode 100644 index 0000000..851ea8e --- /dev/null +++ b/src/main/java/picante/math/cones/PathPlotter.java @@ -0,0 +1,35 @@ +package picante.math.cones; + +import java.awt.geom.Point2D; +import picante.math.intervals.UnwritableInterval; + +/** + * Interface describing a parameterized 2D path. + */ +interface PathPlotter { + + /** + * Computes a point along a parameterized path. + * + * @param p the value of the parameter at which to compute the point + * @param buffer a buffer to receive the results + * + * @return a reference to the supplied buffer for convenience of method chaining + * + * @throws IllegalArgumentException if p is not in the domain supported by this function. + */ + Point2D plot(double p, Point2D buffer); + + default Point2D plot(double p) { + return plot(p, new Point2D.Double()); + } + + /** + * Retrieves the domain over which the path's {@link PathPlotter#plot(double, Point2D)} function + * is to be invoked. + * + * @return the domain over which the plotter is to be plotted + */ + UnwritableInterval getDomain(); + +} diff --git a/src/main/java/picante/math/cones/PathPlotters.java b/src/main/java/picante/math/cones/PathPlotters.java new file mode 100644 index 0000000..b186b34 --- /dev/null +++ b/src/main/java/picante/math/cones/PathPlotters.java @@ -0,0 +1,129 @@ +package picante.math.cones; + +import java.awt.geom.Point2D; +import picante.math.coords.CoordConverters; +import picante.math.coords.LatitudinalVector; +import picante.math.intervals.UnwritableInterval; +import picante.math.vectorspace.UnwritableRotationMatrixIJK; +import picante.math.vectorspace.UnwritableVectorIJK; +import picante.math.vectorspace.VectorIJK; + +/** + * Class that contains a variety of utility methods for use with {@link PathPlotters}. + */ +class PathPlotters { + + private PathPlotters() {} + + /** + * Converts the supplied vector into a lon/lat Point2D + */ + public static Point2D convert(UnwritableVectorIJK vector) { + return convert(vector, new Point2D.Double()); + } + + /** + * Converts the supplied vector into a lon/lat Point2D + */ + public static Point2D convert(UnwritableVectorIJK vector, Point2D buffer) { + LatitudinalVector latVec = CoordConverters.convertToLatitudinal(vector); + buffer.setLocation(latVec.getLongitude(), latVec.getLatitude()); + return buffer; + } + + /** + * Rotates a {@link Point2D} containing a latitude and longitude from one frame to another in + * place. + * + * @param buffer the buffer containing the point prior to the rotation, and the rotated point + * after the method finishes execution + * + * @param rotationalToNewFrame the matrix rotation from the frame in which buffer is initially + * expressed to an alternate one + * + */ + static void rotatePointInPlace(Point2D buffer, UnwritableRotationMatrixIJK rotationalToNewFrame) { + UnwritableVectorIJK vector = + CoordConverters.convert(new LatitudinalVector(1.0, buffer.getY(), buffer.getX())); + VectorIJK rotated = rotationalToNewFrame.mxv(vector); + LatitudinalVector latLonNew = CoordConverters.convertToLatitudinal(rotated); + buffer.setLocation(latLonNew.getLongitude(), latLonNew.getLatitude()); + } + + + /** + * Applies a rotation matrix to a path plotter to produce a new path plotter. + * + * @param plotter the plotter to rotate + * + * @param fromPlotterToNewFrame rotation from the supplied plotter's frame to a new frame; a copy + * of this matrix is retained at construction time + * + * @return newly created, rotated {@link PathPlotter} + */ + public static PathPlotter rotate(final PathPlotter plotter, + final UnwritableRotationMatrixIJK fromPlotterToNewFrame) { + return new PathPlotter() { + + private final UnwritableRotationMatrixIJK matrix = + UnwritableRotationMatrixIJK.copyOf(fromPlotterToNewFrame); + + @Override + public Point2D plot(double p, Point2D buffer) { + plotter.plot(p, buffer); + rotatePointInPlace(buffer, matrix); + return buffer; + } + + @Override + public UnwritableInterval getDomain() { + return plotter.getDomain(); + } + }; + + } + + /** + * Applies a rotation matrix to a closed path plotter to produce a new one. + * + * @param plotter the plotter to rotate + * + * @param fromPlotterToNewFrame rotation from the supplied plotter's frame to a new frame; a copy + * of this matrix is retained at construction time + * + * @return newly created, rotated {@link PathPlotter} + */ + public static ClosedPathPlotter rotate(final ClosedPathPlotter plotter, + final UnwritableRotationMatrixIJK fromPlotterToNewFrame) { + + return new ClosedPathPlotter() { + + private final UnwritableRotationMatrixIJK matrix = + UnwritableRotationMatrixIJK.copyOf(fromPlotterToNewFrame); + + @Override + public Point2D plot(double p, Point2D buffer) { + plotter.plot(p, buffer); + rotatePointInPlace(buffer, matrix); + return buffer; + } + + @Override + public UnwritableInterval getDomain() { + return plotter.getDomain(); + } + + @Override + public boolean isReferencePointInterior() { + return plotter.isReferencePointInterior(); + } + + @Override + public Point2D getReferencePoint(Point2D buffer) { + plotter.getReferencePoint(buffer); + rotatePointInPlace(buffer, matrix); + return buffer; + } + }; + } +} diff --git a/src/main/java/picante/math/cones/PathSplitter.java b/src/main/java/picante/math/cones/PathSplitter.java new file mode 100644 index 0000000..1d7e13a --- /dev/null +++ b/src/main/java/picante/math/cones/PathSplitter.java @@ -0,0 +1,140 @@ +package picante.math.cones; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.awt.geom.PathIterator; +import java.util.List; +import java.util.function.Function; + +import org.locationtech.jts.geom.CoordinateSequence; +import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory; + +import com.google.common.collect.ImmutableList; +import com.google.common.primitives.ImmutableDoubleArray; + +/** + * Converts a {@link PathIterator} into a list of JTS {@link CoordinateSequence}s. + *+ * Any duplicate or unnecessary elements of the path are filtered out (i.e. duplicate + * {@link PathIterator#SEG_LINETO}). + *
+ */ +class PathSplitter implements Function+ * This API "abuses" the {@link Point2D} class as a vehicle for carrying longitude (x + * component) and latitude (y component). This was done intentionally to stick with the utilization + * of the {@link Graphics} classes for rendering. + *
+ * + * An interface that provides methods designed to work with open and closed parameterized paths in + * longitude and latitude into map projected space. The {@link PathPlotter} interface is used by + * these implementations in a specific way, as it is considered to be providing a curve + * parameterized in the longitude-latitude angle space. + *+ * The reason the {@link PathPlotter} interface is utilized here instead of a collection of discrete + * sampled points, is that this allows the map projection to decide to refine the step-size in the + * event that projection artifacts require an increased sampling in a region. If you are working + * with a discrete set of points, then take a look at + * {@link PathPlotters#create(crucible.core.data.list.indexable.Indexable)}. + *
+ *+ * Path plotters consumed by the implementations of this interface are expected to supply longitude + * as the x-coordinate of the point, and latitude as the y-coordinate of the point. Further, they + * may assume that longitudes must be confined to the range [-Pi,Pi], and latitudes should be + * confined to the range: [-Pi/2, Pi/2]. + *
+ * + */ +interface Projection { + + /** + * Projects a supplied longitude and latitude into the map projected space. + *+ * Implementors may rely upon longitude lying in the range: [-Math.PI, Math.PI] and latitude in + * the range: [-Math.PI/2.0, Math.PI/2.0] + *
+ * + * @param latLon longitude and latitude in radians + * @param buffer buffer to receive the results of the projection + * + * @return a reference to the supplied buffer for convenience in chaining method calls + */ + publicP project(Point2D latLon, P buffer); + + default Point2D project(Point2D latLon) { + return project(latLon, new Point2D.Double()); + } + + /** + * Takes a point expressed in the projection and inverts it back into longitude and latitude. + * + * @param mapLocation the location in the map projected coordinates + * @param buffer buffer to receive the results of the inversion in longitude latitude coordinates. + * + * @return a reference to the supplied buffer for convenience in chaining method calls + * + * @throws InversionFailedException if the supplied mapLocation is unable to be inverted, + * typically because it lies outside {@link #getValidRegion()}. + */ + public
P invert(Point2D mapLocation, P buffer); + + default Point2D invert(Point2D mapLocation) { + return invert(mapLocation, new Point2D.Double()); + } + + /** + * Returns the "reference" point of the projection, i.e. the point at which the map + * projection distortion is a minimum. Generally this is the central point of the projection, but + * it may not be the case always. + * + * @return a point in normalized coordinates; it may lie outside the [0,0]x[1,1] bounding box. + * This should be a newly created instance of Point2D every time this method is invoked + * for safety reasons. + */ + public Point2D getReference(); + + /** + * Returns the native aspect ratio of the projected space. + *
+ * Since the projected space is a (0,1) x (0,1) normalized space, this method returns the ratio of + * the width to the height. + *
+ * + * @return the number of width units divided by the number of equivalent height units. + */ + public double getNativeAspectRatio(); + + /** + * Returns the subset of the (0,1) x (0,1) normalized space that where the implementation will + * project into. + *+ * Some projections, due to their nature, will not fill the normalized space entirely. This method + * will give the boundary and interior of that region. + *
+ * + * @return + */ + public Shape getValidRegion(); + + /** + * Projects an open path specified in longitude and latitude into the map projected space. + * + * @param plotter the path plotter providing the parameterized function generating longitude and + * latitude of a path on the surface. + * @param step the requested "step-size" used to generate points. + * + * @return a reference to buffer, whose contents capture the open path projected into the map + * projected space. For map projections with branch cuts and other features, this may + * require multiple disconnected paths to properly capture. + * + * @throws IllegalArgumentException if step is not strictly positive + */ + public+ * Most implementations will simply return {@link Projection#getClass()}. + *
+ * + * @return the type of the original projection + */ + Class extends Projection> getOriginalClass(); + + /** + * Creates a new projection, wrapping the instance, where vectors derived from latitude/longitude + * pairs are rotated prior to application of the projection. + * + * @param rotation rotation from standard latitude/longitude, body-fixed coordinate system to the + * newly defined one. + * + * @return new instance of the projection + */ + Projection rotate(UnwritableRotationMatrixIJK rotation); + +} diff --git a/src/main/java/picante/math/cones/ProjectionBuilders.java b/src/main/java/picante/math/cones/ProjectionBuilders.java new file mode 100644 index 0000000..2a3c2d6 --- /dev/null +++ b/src/main/java/picante/math/cones/ProjectionBuilders.java @@ -0,0 +1,94 @@ +package picante.math.cones; + +import static com.google.common.base.Preconditions.checkArgument; +import static picante.units.FundamentalPhysicalConstants.TWOPI; +import picante.designpatterns.Builder; + +class ProjectionBuilders { + + private ProjectionBuilders() {} + + /** + * Creates a simple cylindrical projection that covers the entire body in longitude. + * + *+ * +========================================================+ + * ----|---+------------------------------------------------+---|---- Math.PI/2.0 + * | | | | + * +========================================================+ + * |---| | + * ^ | +---------------------|------x | +--- + * | | | | i+1 | | + * overPole | | |--- splitTolerance --| | | + * BorderPad | | on other | | + * | x branch | x + * | i | + * --------+------------------------------------------------+-------- -Math.PI/2.0 + * + * lowerBranchValue lowerBranchValue + (2 * Math.PI) + *+ * + *
+ * When the difference between two successive points in a {@link PathPlotter} exceeds the + * splitTolerance then the map projection will consider them to be connected across the branch + * cut. Typically this value should be something slightly larger than half the difference between + * the lowerBranchValue and upperBranchValue for the projection to work properly. + *
+ *+ * Note: the current implementation does not validate or otherwise alter the supplied latitude (y + * coordinate) values. These are unnecessary to manipulate in the projection. It does support + * paths that pass through the pole or that contain it, so long as they are simply connected. + * Paths that are not simply connected may work or may not. Further, each closed region should lie + * in a single hemisphere. + *
+ * + * @param lowerBranchValue the value representing the lower longitude of the branch cut in radians + * @param splitTolerance the value of the splitTolerance expressed in radians + * @param overPoleBorderPad the minimum amount of "closure" padding added to paths that + * enclose either pole specified in radians. + * + * @return the newly constructed map projection + * + * @throws IllegalArgumentException if lowerBranchLongitude lies outside the range [-2.0*Math.PI, + * 0.0] + */ + static class SimpleCylindrical implements BuilderP project(Point2D latLon, P buffer) { + /* + * Rotate to, then project + */ + Point2D rotatedLatLon = rotateTo(latLon); + shapeProjection.project(rotatedLatLon, buffer); + + return buffer; + } + + @Override + public
P invert(Point2D mapLocation, P buffer) {
+
+ /*
+ * Project, then rotate from
+ */
+ shapeProjection.invert(mapLocation, buffer);
+ buffer.setLocation(rotateFrom(buffer));
+ return buffer;
+ }
+
+ @Override
+ public double getNativeAspectRatio() {
+ return shapeProjection.getNativeAspectRatio();
+ }
+
+ @Override
+ public Shape getValidRegion() {
+ return shapeProjection.getValidRegion();
+ }
+
+ @Override
+ public
+ *
+ *
+ *
+ * Note: this method does NOT validate the contents of the ring, it just simply checks that + * the first and last entry in the supplied sequence match and that there are at least 4 entries. + *
+ * + * @param sequence candidate sequence of coordinates + * @param bufferA a coordinate buffer to use for retrieval from sequence + * @param bufferB a coordinate buffer to use for retrieval from sequence + * + * @return true if sequence might be a linear ring, false otherwise + */ + boolean isLinearRing(CoordinateSequence sequence, Coordinate bufferA, Coordinate bufferB) { + + if (sequence.size() < 4) { + return false; + } + + sequence.getCoordinate(0, bufferA); + sequence.getCoordinate(sequence.size() - 1, bufferB); + + return bufferA.equals(bufferB); + } + +} + diff --git a/src/main/java/picante/math/cones/SimpleCylindrical.java b/src/main/java/picante/math/cones/SimpleCylindrical.java new file mode 100644 index 0000000..ff4add1 --- /dev/null +++ b/src/main/java/picante/math/cones/SimpleCylindrical.java @@ -0,0 +1,490 @@ +package picante.math.cones; + +import static com.google.common.base.Preconditions.checkArgument; +import static picante.units.FundamentalPhysicalConstants.TWOPI; +import java.awt.Shape; +import java.awt.geom.Area; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import picante.exceptions.BugException; +import picante.math.intervals.UnwritableInterval; + +/** + * Simple "full sphere" cylindrical projection implementation. + *+ * This class is public to enable identification of its projection type by standard code + * introspection techniques, however it can only be created through the static methods implemented + * on the {@link Projections} class. + *
+ *+ * This code is derived from a slightly more generic implementation of this algorithm, so there are + * artifacts with regards to fields on the class that could be implementation constants. It was left + * this way so that the more general implementation could be easily extracted if necessary in the + * future. + *
+ *+ * TODO: Handle the multiple "wrapping" case. The current code when it unwinds a closed path does + * not properly allow it to be rendered unless it only moves left or right across the branch cut + * once. + *
+ */ +class SimpleCylindrical extends AbstractProjection { + + /** + * The value of the left branch in radians. This is necessary since this code is not only + * unraveling the branch cut wrapping, but performing the affine transformation from radians space + * to the normalized space. + */ + private final double branchLeftInRadians; + + /** + * The numeric value at which the left branch is defined. Note: in this implementation this value + * is always 0.0. + */ + private final double branchLeft; + + /** + * The numeric value at which the right branch is defined. Note: in this implementation this value + * is always 1.0. + */ + private final double branchRight; + + /** + * The value at which a step in a point plotter must exceed to be considered a branch crossing. + * This value should be something approximately 0.5 in this implementation for most applications. + */ + private final double split; + + /** + * The mid-point between the two branches, normally would be the average of branchLeft and + * branchRight which in this implementation is 0.5. + */ + private final double midPoint; + + /** + * The amount to shift by numerically when moving a point from one branch to another towards the + * right branch. In this implementation it is 1.0 (branchRight - branchLeft). + */ + private final double branchRightShift; + + /** + * The amount of horizontal (longitude) separation required between the first and final point of a + * closed path to be considered "open" and thus enclosing the pole. + */ + private final double overPoleBranchTestTolerance = (TWOPI - 0.01) / TWOPI; + + /** + * The amount of padding to add to tracks that enclose the pole. + */ + private final double overPoleBorderPadding; + + /** + * Package private constructor. + * + * @param branchLeftInRadians + * @param split + */ + SimpleCylindrical(double branchLeftInRadians, double split, double overPoleBorderPadding) { + super(); + + checkArgument(branchLeftInRadians >= -TWOPI); + checkArgument(branchLeftInRadians <= 0.0); + + checkArgument(split > 0); + checkArgument(split < TWOPI); + + checkArgument(overPoleBorderPadding > 0); + + this.branchLeftInRadians = branchLeftInRadians; + this.branchLeft = 0.0; + this.branchRight = 1.0; + this.split = split / TWOPI; + this.midPoint = 0.5; + this.branchRightShift = branchRight - branchLeft; + this.overPoleBorderPadding = overPoleBorderPadding / TWOPI; + } + + @Override + publicP project(Point2D latLon, P buffer) { + + /* + * Assuming the latitude and longitude lie in the appropriate ranges: + */ + double y = -1.0 / Math.PI * latLon.getY() + 0.5; + + double longitude = latLon.getX() - branchLeftInRadians; + + if (longitude < 0) { + longitude += TWOPI; + } else if (longitude > TWOPI) { + longitude -= TWOPI; + } + + double x = longitude / TWOPI; + + buffer.setLocation(x, y); + + return buffer; + } + + @Override + public
P invert(Point2D mapLocation, P buffer) {
+
+ double latitude = -Math.PI * (mapLocation.getY() - 0.5);
+
+ double longitude = mapLocation.getX() * TWOPI + branchLeftInRadians;
+
+ if (longitude < -Math.PI) {
+ longitude += TWOPI;
+ } else if (longitude > TWOPI) {
+ longitude -= TWOPI;
+ }
+
+ buffer.setLocation(longitude, latitude);
+
+ return buffer;
+ }
+
+ /**
+ * This method exists as the original code did not also perform the affine transformation scaling
+ * the latitude and longitude to the normalized space.
+ */
+ private PathPlotter transformToNormalizedCoordinates(final PathPlotter plotter) {
+ return new PathPlotter() {
+
+ @Override
+ public Point2D plot(double p, Point2D buffer) {
+
+ /*
+ * Plot the latLon based point.
+ */
+ plotter.plot(p, buffer);
+
+ /*
+ * Convert the point to the appropriate range.
+ */
+ project(buffer, buffer);
+
+ return buffer;
+ }
+
+ @Override
+ public UnwritableInterval getDomain() {
+ return plotter.getDomain();
+ }
+
+ };
+ }
+
+ @Override
+ public
+ * This is a simple utility method to consolidate the execution of the same code with different
+ * parameters in the pole shift combinations.
+ *
+ * This is different than {@link AffineTransform#createTransformedShape(java.awt.Shape)} because
+ * it returns a Path2D with the translated coordinates, instead of {@link Shape}.
+ *
+ * Key classes are:
+ *
+ * ============================================================================<=====+--outerLat
+ * |
+ * |
+ * --------+------------------------------------------------+-------- Math.PI/2.0 |
+ * | | ^
+ * | | |
+ * | | |
+ * . . . .+ . . . . . . .o--------------------->-----------+--------------x===>=====+--firstLat
+ * | | |
+ * | | firstLon
+ * | |
+ * | |
+ * --------+------------------------------------------------+-------- -Math.PI/2.0
+ *
+ * lowerBranchValue lowerBranchValue + (2 * Math.PI)
+ *
+ *
+ * @param path the path to close over the pole.
+ * @param firstLat the latitude of the first point in the enclosing box (generally path's end
+ * point y-value)
+ * @param lastLat the latitude of the last point in the enclosing box (generally path's start
+ * point y-value)
+ * @param branchShift the signed branch shift direction
+ * @param firstLon the longitude of the first padded point (usually path's end point x-value with
+ * a padding)
+ * @param lastLon the longitude of the last padded point (usually path's start point x-value with
+ * a padding applied along with a branch shift)
+ * @param outerLat the padded value of latitude
+ */
+ static void shiftAndCloseOverPole(Path2D path, double firstLat, double lastLat,
+ double branchShift, double firstLon, double lastLon, double outerLat) {
+
+ /*
+ * Append the shifted path to the start of the supplied path.
+ */
+ Path2D shifted = createShiftedPath(path, branchShift, 0);
+ shifted.append(path, true);
+ path.reset();
+ path.append(shifted, false);
+
+ /*
+ * Draw the box outside of the projection.
+ */
+ path.lineTo(firstLon, firstLat);
+ path.lineTo(firstLon, outerLat);
+ path.lineTo(lastLon, outerLat);
+ path.lineTo(lastLon, lastLat);
+
+ path.closePath();
+
+ }
+
+ /**
+ * Closes a path over the pole without any shifting.
+ *
+ * @param path the open path
+ * @param firstLat the latitude associated with the first closure point
+ * @param lastLat the latitude associated with the last closure point
+ * @param firstLon the longitude associated with the first closure point
+ * @param lastLon the longitude associated with the last closure point
+ * @param outerLat the latitude of the over the pole value to utilize
+ */
+ static void closeOverPole(Path2D path, double firstLat, double lastLat, double firstLon,
+ double lastLon, double outerLat) {
+ path.lineTo(firstLon, firstLat);
+ path.lineTo(firstLon, outerLat);
+ path.lineTo(lastLon, outerLat);
+ path.lineTo(lastLon, lastLat);
+ path.closePath();
+ }
+
+
+ /**
+ * Identifies the type of a closed path.
+ *
+ * @param path a closed path that has been enumerated from start to end
+ * @param overPoleBranchTestTolerance
+ * @param branchLeft
+ * @param branchRight
+ *
+ * @return the type of path for which closure is to be performed.
+ */
+ static SimpleCylindricalPathType identifyType(Path2D path, Point2D start, Point2D end,
+ double overPoleBranchTestTolerance, double branchLeft, double branchRight) {
+
+ /*
+ * Determine if the region contains either pole. If it does, then it will have a single branch
+ * path that does not "close" back onto itself; despite the fact it is actually closed.
+ */
+ if (Math.abs(end.getX() - start.getX()) > overPoleBranchTestTolerance) {
+
+ /*
+ * Examine the start of the path to determine which pole it is closer to. If it's exactly 0.5,
+ * assume it's enclosing the south pole.
+ *
+ * TODO: Consider alternatives, as this isn't really robust.
+ */
+ if (start.getY() < 0.5) {
+ if (end.getX() < branchLeft) {
+ return SimpleCylindricalPathType.NORTH_POLE_RIGHT_SHIFT;
+ } else if (end.getX() > branchRight) {
+ return SimpleCylindricalPathType.NORTH_POLE_LEFT_SHIFT;
+ } else {
+ return SimpleCylindricalPathType.NORTH_POLE_NO_SHIFT;
+ }
+ }
+
+ /*
+ * Handle the south pole cases.
+ */
+ if (end.getX() < branchLeft) {
+ return SimpleCylindricalPathType.SOUTH_POLE_RIGHT_SHIFT;
+ } else if (end.getX() > branchRight) {
+ return SimpleCylindricalPathType.SOUTH_POLE_LEFT_SHIFT;
+ } else {
+ return SimpleCylindricalPathType.SOUTH_POLE_NO_SHIFT;
+ }
+
+ }
+
+ /*
+ * It's not a polar situation, examine the bounding rectangle to determine which type of shift
+ * is necessary.
+ */
+ Rectangle2D bounds = path.getBounds2D();
+
+ if (bounds.getMinX() < branchLeft) {
+ return SimpleCylindricalPathType.RIGHT_SHIFT;
+ }
+ if (bounds.getMaxX() > branchRight) {
+ return SimpleCylindricalPathType.LEFT_SHIFT;
+ }
+ return SimpleCylindricalPathType.NO_SHIFT;
+
+ }
+
+ /**
+ * Creates a shifted version of the supplied path coordinates by the specified deltas in X and Y.
+ *
and vector
+ * field value
at that coordinate.
+ *
+ * @author G.K.Stephens
+ *
+ */
+public final class CartesianVectorFieldValue extends AbstractVectorFieldValue
+ * @param value a Cartesian vector field value
at that
+ * coordinate
+ */
+ public CartesianVectorFieldValue(UnwritableVectorIJK position, UnwritableVectorIJK value) {
+ super(UnwritableVectorIJK.copyOf(position), UnwritableVectorIJK.copyOf(value));
+ }
+
+}
diff --git a/src/main/java/picante/math/coords/CoordConverter.java b/src/main/java/picante/math/coords/CoordConverter.java
new file mode 100644
index 0000000..eb72c67
--- /dev/null
+++ b/src/main/java/picante/math/coords/CoordConverter.java
@@ -0,0 +1,53 @@
+package picante.math.coords;
+
+import picante.math.vectorspace.UnwritableVectorIJK;
+
+/**
+ * An interface which allows for the conversion between different coordinate systems to Cartesian.
+ * The four methods allow you to convert positions and states from Cartesian to the coordinate
+ * system and back. It is templated on the Unwritable and Writable version of the coordinate class.
+ *
+ * @author G.K.Stephens
+ *
+ * @param The Unwritable Coordinate class.
+ * @param
+ * .- -.
+ * | cos(long) -sin(long) 0 |
+ * | |
+ * | sin(long) cos(long) 0 |
+ * | |
+ * | 0 0 1 |
+ * `- -'
+ *
+ *
+ */
+ @Override
+ public MatrixIJK getTransformation(CylindricalVector coordPosition, MatrixIJK buffer) {
+
+ double lon = coordPosition.getLongitude();
+
+ double cosLon = cos(lon);
+ double sinLon = sin(lon);
+
+ double j_DX_DR = cosLon;
+ double j_DY_DR = sinLon;
+ double j_DZ_DR = 0.0;
+
+ double j_DX_DLON = -sinLon;
+ double j_DY_DLON = cosLon;
+ double j_DZ_DLON = 0.0;
+
+ double j_DX_DZ = 0.0;
+ double j_DY_DZ = 0.0;
+ double j_DZ_DZ = 1.0;
+
+ return buffer.setTo(j_DX_DR, j_DY_DR, j_DZ_DR, j_DX_DLON, j_DY_DLON, j_DZ_DLON, j_DX_DZ,
+ j_DY_DZ, j_DZ_DZ);
+ }
+
+ @Override
+ public MatrixIJK getInverseTransformation(CylindricalVector coordPosition, MatrixIJK buffer) {
+ return getTransformation(coordPosition, buffer).invort();
+ }
+
+ @Override
+ public UnwritableVectorIJK mxv(UnwritableMatrixIJK jacobian, CylindricalVector coordValue) {
+ return jacobian.mxv(coordValue.getVectorIJK());
+ }
+
+ @Override
+ public CylindricalVector mxv(UnwritableMatrixIJK inverseTransformation,
+ UnwritableVectorIJK cartVelocity) {
+ UnwritableVectorIJK vect = inverseTransformation.mxv(cartVelocity);
+ return new CylindricalVector(vect.getI(), vect.getJ(), vect.getK());
+ }
+
+}
diff --git a/src/main/java/picante/math/coords/CylindricalToCartesianJacobian.java b/src/main/java/picante/math/coords/CylindricalToCartesianJacobian.java
new file mode 100644
index 0000000..b4e8ab6
--- /dev/null
+++ b/src/main/java/picante/math/coords/CylindricalToCartesianJacobian.java
@@ -0,0 +1,102 @@
+package picante.math.coords;
+
+import static picante.math.PicanteMath.cos;
+import static picante.math.PicanteMath.sin;
+import picante.math.vectorspace.MatrixIJK;
+import picante.math.vectorspace.UnwritableMatrixIJK;
+import picante.math.vectorspace.UnwritableVectorIJK;
+
+class CylindricalToCartesianJacobian implements Transformation
+ * .- -.
+ * | dx/dr dx/dlong dx/dz |
+ * | |
+ * | dy/dr dy/dlong dy/dz |
+ * | |
+ * | dz/dr dz/dlong dz/dz |
+ * `- -'
+ *
+ * .- -.
+ * | cos(long) -sin(long)*r 0 |
+ * | |
+ * | sin(long) cos(long)*r 0 |
+ * | |
+ * | 0 0 1 |
+ * `- -'
+ *
+ *
+ */
+ @Override
+ public MatrixIJK getTransformation(CylindricalVector coordPosition, MatrixIJK buffer) {
+ /*
+ * from SPICE's routine in drdcyl.f
+ *
+ * JACOBI (DX,DR) = DCOS( LONG )
+ *
+ * JACOBI (DY,DR) = DSIN( LONG )
+ *
+ * JACOBI (DZ,DR) = 0.0D0
+ *
+ *
+ * JACOBI (DX,DLON) = -DSIN( LONG ) * R
+ *
+ * JACOBI (DY,DLON) = DCOS( LONG ) * R
+ *
+ * JACOBI (DZ,DLON) = 0.0D0
+ *
+ *
+ * JACOBI (DX,DZ) = 0.0D0
+ *
+ * JACOBI (DY,DZ) = 0.0D0
+ *
+ * JACOBI (DZ,DZ) = 1.0D0
+ */
+
+ double r = coordPosition.getCylindricalRadius();
+ double lon = coordPosition.getLongitude();
+
+ double cosLon = cos(lon);
+ double sinLon = sin(lon);
+
+ double j_DX_DR = cosLon;
+ double j_DY_DR = sinLon;
+ double j_DZ_DR = 0.0;
+
+ double j_DX_DLON = -sinLon * r;
+ double j_DY_DLON = cosLon * r;
+ double j_DZ_DLON = 0.0;
+
+ double j_DX_DZ = 0.0;
+ double j_DY_DZ = 0.0;
+ double j_DZ_DZ = 1.0;
+
+ return buffer.setTo(j_DX_DR, j_DY_DR, j_DZ_DR, j_DX_DLON, j_DY_DLON, j_DZ_DLON, j_DX_DZ,
+ j_DY_DZ, j_DZ_DZ);
+ }
+
+ @Override
+ public MatrixIJK getInverseTransformation(CylindricalVector coordPosition, MatrixIJK buffer) {
+ try {
+ return getTransformation(coordPosition, buffer).invort();
+ } catch (UnsupportedOperationException e) {
+ throw new PointOnAxisException(e);
+ }
+ }
+
+ @Override
+ public UnwritableVectorIJK mxv(UnwritableMatrixIJK jacobian, CylindricalVector coordVelocity) {
+ return jacobian.mxv(coordVelocity.getVectorIJK());
+ }
+
+ @Override
+ public CylindricalVector mxv(UnwritableMatrixIJK inverseJacobian,
+ UnwritableVectorIJK cartVelocity) {
+ UnwritableVectorIJK vect = inverseJacobian.mxv(cartVelocity);
+ return new CylindricalVector(vect.getI(), vect.getJ(), vect.getK());
+ }
+
+}
diff --git a/src/main/java/picante/math/coords/CylindricalVector.java b/src/main/java/picante/math/coords/CylindricalVector.java
new file mode 100644
index 0000000..ee936f3
--- /dev/null
+++ b/src/main/java/picante/math/coords/CylindricalVector.java
@@ -0,0 +1,51 @@
+package picante.math.coords;
+
+/**
+ * A class representing a vector in the cylindrical coordinate system.
+ *
+ * @author G.K.Stephens
+ *
+ */
+public final class CylindricalVector extends AbstractVector {
+
+ /**
+ * The ZERO vector.
+ */
+ public static final CylindricalVector ZERO = new CylindricalVector(0, 0, 0);
+
+ public CylindricalVector(double cylindricalRadius, double longInRadians, double height) {
+ super(cylindricalRadius, longInRadians, height);
+ }
+
+ // public CylindricalVector(double[] data) {
+ // super(data);
+ // }
+
+ /**
+ * @return the cylindrical radius (often denoted as r or ρ)
+ */
+ public final double getCylindricalRadius() {
+ return super.getI();
+ }
+
+ /**
+ * @return the longitude
+ */
+ public final double getLongitude() {
+ return super.getJ();
+ }
+
+ /**
+ * @return the height (often denoted as z)
+ */
+ public final double getHeight() {
+ return super.getK();
+ }
+
+ @Override
+ public String toString() {
+ return "CylindricalVector [cylindricalRadius: " + getCylindricalRadius() + ", longitude: "
+ + getLongitude() + ", height: " + getHeight() + "]";
+ }
+
+}
diff --git a/src/main/java/picante/math/coords/CylindricalVectorFieldValue.java b/src/main/java/picante/math/coords/CylindricalVectorFieldValue.java
new file mode 100644
index 0000000..c05cf95
--- /dev/null
+++ b/src/main/java/picante/math/coords/CylindricalVectorFieldValue.java
@@ -0,0 +1,21 @@
+package picante.math.coords;
+
+/**
+ * A container class for a cylindrical coordinate
and vector
+ * field value
at that coordinate.
+ *
+ * @author G.K.Stephens
+ *
+ */
+public final class CylindricalVectorFieldValue extends AbstractVectorFieldValue
+ * @param value a cylindrical vector field value
at that
+ * coordinate
+ */
+ public CylindricalVectorFieldValue(CylindricalVector position, CylindricalVector value) {
+ super(position, value);
+ }
+
+}
diff --git a/src/main/java/picante/math/coords/LatitudinalCoordConverter.java b/src/main/java/picante/math/coords/LatitudinalCoordConverter.java
new file mode 100644
index 0000000..9ed4937
--- /dev/null
+++ b/src/main/java/picante/math/coords/LatitudinalCoordConverter.java
@@ -0,0 +1,116 @@
+package picante.math.coords;
+
+import static picante.math.PicanteMath.abs;
+import static picante.math.PicanteMath.atan2;
+import static picante.math.PicanteMath.cos;
+import static picante.math.PicanteMath.max;
+import static picante.math.PicanteMath.sin;
+import static picante.math.PicanteMath.sqrt;
+import picante.math.vectorspace.UnwritableVectorIJK;
+
+class LatitudinalCoordConverter extends AbstractCoordConverter
and vector
+ * field value
at that coordinate.
+ *
+ * @author G.K.Stephens
+ *
+ */
+public final class SphericalVectorFieldValue extends AbstractVectorFieldValue
+ * @param value a spherical vector field value
at that
+ * coordinate
+ */
+ public SphericalVectorFieldValue(SphericalVector position, SphericalVector value) {
+ super(position, value);
+ }
+
+}
diff --git a/src/main/java/picante/math/coords/State.java b/src/main/java/picante/math/coords/State.java
new file mode 100644
index 0000000..b88d76f
--- /dev/null
+++ b/src/main/java/picante/math/coords/State.java
@@ -0,0 +1,28 @@
+package picante.math.coords;
+
+import picante.math.vectorspace.UnwritableVectorIJK;
+
+/**
+ * An interface which lays out the basic methods that should be on a state class.
+ *
+ * @author G.K.Stephens
+ *
+ * @param
+ * and vector field values
to cylindrical
+ *
and spherical
+ * coordinates and vector field values
,
+ *
and vice versa.
+ *
+ * @author G.K.Stephens
+ *
+ */
+public class VectorFieldValueConversions {
+
+ private final static CylindricalToCartesianBasisTransformation CYLINDRICAL =
+ new CylindricalToCartesianBasisTransformation();
+ private final static SphericalToCartesianBasisTransformation SPHERICAL =
+ new SphericalToCartesianBasisTransformation();
+
+ /**
+ * Private
+ */
+ private VectorFieldValueConversions() {}
+
+ /**
+ * Converts a cylindrical coordinate
and vector field value
+ *
at that coordinate to a Cartesian coordinate
+ *
and vector field value
+ *
.
+ *
+ * @param cylindrical a cylindrical coordinate
and vector
+ * field value
at that coordinate
+ * @return a Cartesian coordinate
and vector field value
+ *
at that coordinate
+ */
+ public static CartesianVectorFieldValue convert(CylindricalVectorFieldValue cylindrical) {
+
+ MatrixIJK matrix = new MatrixIJK();
+
+ UnwritableVectorIJK position = CoordConverters.convert(cylindrical.getPosition());
+
+ CYLINDRICAL.getTransformation(cylindrical.getPosition(), matrix);
+
+ UnwritableVectorIJK value = CYLINDRICAL.mxv(matrix, cylindrical.getValue());
+
+ return new CartesianVectorFieldValue(position, value);
+ }
+
+ /**
+ * Converts a cylindrical coordinate
and vector field value
+ *
at that coordinate to a Cartesian coordinate
+ *
and vector field value
+ *
.
+ *
+ * @param cylPos a cylindrical coordinate
+ * @param cylValue a cylindrical vector field value
at that
+ * coordinate
+ * @return a Cartesian coordinate
and vector field value
+ *
at that coordinate
+ */
+ public static CartesianVectorFieldValue convert(CylindricalVector cylPos,
+ CylindricalVector cylValue) {
+
+ MatrixIJK matrix = new MatrixIJK();
+
+ UnwritableVectorIJK position = CoordConverters.convert(cylPos);
+
+ CYLINDRICAL.getTransformation(cylPos, matrix);
+
+ UnwritableVectorIJK value = CYLINDRICAL.mxv(matrix, cylValue);
+
+ return new CartesianVectorFieldValue(position, value);
+ }
+
+ /**
+ * Converts a Cartesian coordinate
and vector field value
+ *
at that coordinate to a cylindrical coordinate
+ *
and vector field value
+ *
.
+ *
+ * @param Cartesian a Cartesian coordinate
and vector field
+ * value
at that coordinate
+ * @return a cylindrical coordinate
and vector field value
+ *
at that coordinate
+ */
+ public static CylindricalVectorFieldValue convertToCylindrical(
+ CartesianVectorFieldValue cartesian) {
+
+ MatrixIJK matrix = new MatrixIJK();
+
+ CylindricalVector position = CoordConverters.convertToCylindrical(cartesian.getPosition());
+
+ CYLINDRICAL.getInverseTransformation(position, matrix);
+
+ CylindricalVector value = CYLINDRICAL.mxv(matrix, cartesian.getValue());
+
+ return new CylindricalVectorFieldValue(position, value);
+ }
+
+ /**
+ * Converts a Cartesian coordinate
and vector field value
+ *
at that coordinate to a cylindrical coordinate
+ *
and vector field value
+ *
.
+ *
+ * @param cartPos a Cartesian coordinate
+ * @param cartValue a vector field value
at that coordinate
+ * @return a cylindrical coordinate
and vector field value
+ *
at that coordinate
+ */
+ public static CylindricalVectorFieldValue convertToCylindrical(UnwritableVectorIJK cartPos,
+ UnwritableVectorIJK cartValue) {
+
+ MatrixIJK matrix = new MatrixIJK();
+
+ CylindricalVector position = CoordConverters.convertToCylindrical(cartPos);
+
+ CYLINDRICAL.getInverseTransformation(position, matrix);
+
+ CylindricalVector value = CYLINDRICAL.mxv(matrix, cartValue);
+
+ return new CylindricalVectorFieldValue(position, value);
+ }
+
+ /**
+ * Converts a spherical coordinate
and vector field value
+ *
at that coordinate to a Cartesian coordinate
+ *
and vector field value
+ *
.
+ *
+ * @param spherical a spherical coordinate
and vector field
+ * value
at that coordinate
+ * @return a Cartesian coordinate
and vector field value
+ *
at that coordinate
+ */
+ public static CartesianVectorFieldValue convert(SphericalVectorFieldValue spherical) {
+
+ MatrixIJK matrix = new MatrixIJK();
+
+ UnwritableVectorIJK position = CoordConverters.convert(spherical.getPosition());
+
+ SPHERICAL.getTransformation(spherical.getPosition(), matrix);
+
+ UnwritableVectorIJK value = SPHERICAL.mxv(matrix, spherical.getValue());
+
+ return new CartesianVectorFieldValue(position, value);
+ }
+
+ /**
+ * Converts a spherical coordinate
and vector field value
+ *
at that coordinate to a Cartesian coordinate
+ *
and vector field value
+ *
.
+ *
+ * @param sphPos a spherical coordinate
+ * @param sphValue a vector field value
at that coordinate
+ * @return a Cartesian coordinate
and vector field value
+ *
at that coordinate
+ */
+ public static CartesianVectorFieldValue convert(SphericalVector sphPos,
+ SphericalVector sphValue) {
+
+ MatrixIJK matrix = new MatrixIJK();
+
+ UnwritableVectorIJK position = CoordConverters.convert(sphPos);
+
+ SPHERICAL.getTransformation(sphPos, matrix);
+
+ UnwritableVectorIJK value = SPHERICAL.mxv(matrix, sphValue);
+
+ return new CartesianVectorFieldValue(position, value);
+ }
+
+ /**
+ * Converts a Cartesian coordinate
and vector field value
+ *
at that coordinate to a spherical coordinate
+ *
and vector field value
+ *
.
+ *
+ * @param cartesian a Cartesian coordinate
and vector field
+ * value
at that coordinate
+ * @return a spherical coordinate
and vector field value
+ *
at that coordinate
+ */
+ public static SphericalVectorFieldValue convertToSpherical(CartesianVectorFieldValue cartesian) {
+
+ MatrixIJK matrix = new MatrixIJK();
+
+ SphericalVector position = CoordConverters.convertToSpherical(cartesian.getPosition());
+
+ SPHERICAL.getInverseTransformation(position, matrix);
+
+ SphericalVector value = SPHERICAL.mxv(matrix, cartesian.getValue());
+
+ return new SphericalVectorFieldValue(position, value);
+ }
+
+ /**
+ * Converts a Cartesian coordinate
and vector field value
+ *
at that coordinate to a spherical coordinate
+ *
and vector field value
+ *
.
+ *
+ * @param cartPos a Cartesian coordinate
+ * @param cartValue a vector field value
at that coordinate
+ * @return a spherical coordinate
and vector field value
+ *
at that coordinate
+ */
+ public static SphericalVectorFieldValue convertToSpherical(UnwritableVectorIJK cartPos,
+ UnwritableVectorIJK cartValue) {
+
+ MatrixIJK matrix = new MatrixIJK();
+
+ SphericalVector position = CoordConverters.convertToSpherical(cartPos);
+
+ SPHERICAL.getInverseTransformation(position, matrix);
+
+ SphericalVector value = SPHERICAL.mxv(matrix, cartValue);
+
+ return new SphericalVectorFieldValue(position, value);
+ }
+
+}
diff --git a/src/main/java/picante/math/coords/package-info.java b/src/main/java/picante/math/coords/package-info.java
new file mode 100644
index 0000000..39ed0fe
--- /dev/null
+++ b/src/main/java/picante/math/coords/package-info.java
@@ -0,0 +1,63 @@
+/**
+ * This package provides the ability to easily convert between Cartesian and several coordinate
+ * systems (such as Spherical) and the associated states.
+ *
+ *
+ *
+ * The user should be able to get most of the desired functionality by using the + * {@link picante.math.coords.deprecated.CoordConverters} class. This class contains static + * methods allowing for the conversion from Cartesian coordinates to the intended coordinate and + * vice versa. It also has the equivalent state conversion. If a user needs to convert between two + * coordinate systems, they are forced to go through Cartesian. If this becomes a performance burden + * for a user, the framework allows for direct coordinate conversion to be added. These static + * methods are intended to be thread safe, although they need system tested. + *
+ * + *+ * When the user asks for a conversion, they will receive a concrete container class containing the + * information for that coordinate e.g. SphericalCoord has three field for Radius, Colatitude, and + * Longitude. These classes mirror the {@link picante.math.vectorspace.VectorIJK} and the + * {@link picante.mechanics.StateVector} classes and also follow the + * {@link picante.designpatterns.Writable} pattern. + *
+ * + *+ * The only public classes are the before mentioned container classes and the + * {@link picante.math.coords.deprecated.CoordConverters} class. + *
+ * + *+ * Precautions were taken to make {@link picante.math.coords.deprecated.CoordConverters} + * thread safe + *
+ * + *+ * The work is done by implementations of the {@link picante.math.coords.CoordConverter} + * interface. This interface, along with its implementations are intended to be package private, + * although they may be useful in other contexts. The {@link picante.math.coords.Jacobian} + * interface, also a package private, is meant to assist the + * {@link picante.math.coords.CoordConverter} implementations by providing a way to manipulate + * the Jacobians. + *
+ * + *+ * If a new type is added to the CoordConverters, six classes must be added. Two Coordinate classes, + * one unwritable and one unwritable. The unwritable should extend the abstract helper class + * {@link picante.math.coords.Coordinate} and the writable should extend the writable, and + * make public the setters inherited from the abstract class. Two State classes, one unwritable and + * one unwritable. The state classes follow the same pattern, the unwritable extends an abstract + * helper class {@link picante.math.coords.State} and the writable extends the unwritable. One + * {@link picante.math.coords.CoordConverter} implementation, and one + * {@link picante.math.coords.Jacobian} although this is not specifically required, it should + * help. And then the four methods should be added to + * {@link picante.math.coords.deprecated.CoordConverters}. + *
+ */ +package picante.math.coords; diff --git a/src/main/java/picante/math/functions/BasicComputableDifferentiableUnivariateFunction.java b/src/main/java/picante/math/functions/BasicComputableDifferentiableUnivariateFunction.java new file mode 100644 index 0000000..8449c0d --- /dev/null +++ b/src/main/java/picante/math/functions/BasicComputableDifferentiableUnivariateFunction.java @@ -0,0 +1,39 @@ +package picante.math.functions; + +import picante.math.functions.Computable.ComputableResult; + +/** + * Basic abstract implementation of {@link ComputableDifferentiableUniveriateFunction} that ensures + * its contract is met. + * + * @author James Peachey + * + */ +public abstract class BasicComputableDifferentiableUnivariateFunction+ * Code that calls this method may not perform as quickly as a custom implementation of this + * method would, since the compute method also computes the derivative. Subclasses may optionally + * override this behavior to improve performance in such cases. + */ + @Override + public double evaluate(double t) { + return evaluate(compute(t)); + } + + /** + * {@inheritDoc} + *
+ * Code that calls this method is likely to perform as quickly as a custom implementation of this
+ * method would, since the compute method also computes the derivative. Subclasses may optionally
+ * override this behavior to improve performance if this proves not to be the case.
+ */
+ @Override
+ public double differentiate(double t) {
+ return differentiate(compute(t));
+ }
+
+}
diff --git a/src/main/java/picante/math/functions/BasicComputableDifferentiableVectorIJKFunction.java b/src/main/java/picante/math/functions/BasicComputableDifferentiableVectorIJKFunction.java
new file mode 100644
index 0000000..b1b44f1
--- /dev/null
+++ b/src/main/java/picante/math/functions/BasicComputableDifferentiableVectorIJKFunction.java
@@ -0,0 +1,40 @@
+package picante.math.functions;
+
+import picante.math.functions.Computable.ComputableResult;
+import picante.math.vectorspace.VectorIJK;
+
+/**
+ * Basic abstract implementation of {@link ComputableDifferentiableVectorIJKFunction} that ensures
+ * its contract is met.
+ *
+ * @author James Peachey
+ *
+ */
+public abstract class BasicComputableDifferentiableVectorIJKFunction
+ * Code that calls this method may not perform as quickly as a custom implementation of this
+ * method would, since the compute method also computes the derivative. Subclasses may optionally
+ * override this behavior to improve performance in such cases.
+ */
+ @Override
+ public final VectorIJK evaluate(double t, VectorIJK buffer) {
+ return evaluate(compute(t), buffer);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * Code that calls this method is likely to perform as quickly as a custom implementation of this
+ * method would, since the compute method also computes the derivative. Subclasses may optionally
+ * override this behavior to improve performance if this proves not to be the case.
+ */
+ @Override
+ public final VectorIJK differentiate(double t, VectorIJK buffer) {
+ return differentiate(compute(t), buffer);
+ }
+
+}
diff --git a/src/main/java/picante/math/functions/ChebyshevPolynomial.java b/src/main/java/picante/math/functions/ChebyshevPolynomial.java
new file mode 100644
index 0000000..da4fcec
--- /dev/null
+++ b/src/main/java/picante/math/functions/ChebyshevPolynomial.java
@@ -0,0 +1,179 @@
+package picante.math.functions;
+
+import java.util.Arrays;
+
+public class ChebyshevPolynomial implements DifferentiableUnivariateFunction {
+
+ private double[] coeffs;
+ private final double[] x2s = new double[2];
+ private int degree;
+
+ private double[] w = new double[3];
+ private double[] dw = new double[3];
+ private double[] tmp = new double[2];
+
+ public ChebyshevPolynomial() {
+ this.degree = 1;
+ this.coeffs = new double[2];
+ x2s[0] = 0.0;
+ x2s[1] = 1.0;
+ }
+
+ public ChebyshevPolynomial(int degree, double[] coeffs, double[] x2s) {
+ this.degree = degree;
+ this.coeffs = new double[degree + 1];
+ System.arraycopy(coeffs, 0, this.coeffs, 0, degree + 1);
+ System.arraycopy(x2s, 0, this.x2s, 0, 2);
+ }
+
+ public ChebyshevPolynomial(ChebyshevPolynomial polynomial) {
+ this(polynomial.degree, polynomial.coeffs, polynomial.x2s);
+ }
+
+ public int getNumberOfCoefficients() {
+ return degree + 1;
+ }
+
+ public int getDegree() {
+ return degree;
+ }
+
+ public void getCoefficients(double[] coeffs, double[] x2s) {
+ getCoefficients(coeffs, 0, x2s, 0);
+ }
+
+ public void getCoefficients(double[] coeffs, int coeffsOffset, double[] x2s, int x2sOffset) {
+ System.arraycopy(this.coeffs, 0, coeffs, coeffsOffset, degree + 1);
+ System.arraycopy(this.x2s, 0, x2s, x2sOffset, 2);
+ }
+
+ public void setCoefficients(ChebyshevPolynomial polynomial) {
+ setCoefficients(polynomial.degree, polynomial.coeffs, polynomial.x2s);
+ }
+
+ public void setCoefficients(int degree, double[] coeffs, double[] x2s) {
+ setCoefficients(degree, coeffs, 0, x2s, 0);
+ }
+
+ public void setCoefficients(int degree, double[] coeffs, int coeffsOffset, double[] x2s,
+ int x2sOffset) {
+
+ if (this.coeffs.length < degree + 1) {
+ this.coeffs = new double[degree + 1];
+ }
+ this.degree = degree;
+
+ System.arraycopy(coeffs, coeffsOffset, this.coeffs, 0, degree + 1);
+ System.arraycopy(x2s, x2sOffset, this.x2s, 0, 2);
+ }
+
+ public double[] evaluate(double t, double[] buffer) {
+
+ double s = (t - x2s[0]) / x2s[1];
+ double s2 = 2.0 * s;
+ int j = degree + 1;
+ w[0] = 0.0;
+ w[1] = 0.0;
+ dw[0] = 0.0;
+ dw[1] = 0.0;
+
+ while (j > 1) {
+
+ w[2] = w[1];
+ w[1] = w[0];
+ w[0] = coeffs[j - 1] + (s2 * w[1] - w[2]);
+
+ dw[2] = dw[1];
+ dw[1] = dw[0];
+ dw[0] = w[1] * 2.0 + dw[1] * s2 - dw[2];
+
+ j--;
+ }
+
+ buffer[0] = coeffs[0] + (s * w[0] - w[1]);
+ buffer[1] = w[0] + s * dw[0] - dw[1];
+ buffer[1] /= x2s[1];
+
+ return buffer;
+ }
+
+ @Override
+ public double evaluate(double t) {
+
+ double s = (t - x2s[0]) / x2s[1];
+ double s2 = 2.0 * s;
+ int j = degree + 1;
+ w[0] = 0.0;
+ w[1] = 0.0;
+
+ while (j > 1) {
+ w[2] = w[1];
+ w[1] = w[0];
+ w[0] = coeffs[j - 1] + (s2 * w[1] - w[2]);
+ j--;
+ }
+
+ return (s * w[0] - w[1]) + coeffs[0];
+ }
+
+ @Override
+ public double differentiate(double t) {
+ evaluate(t, tmp);
+ return tmp[1];
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+
+ result = prime * result + degree;
+ /*
+ * This code was taken from Arrays.hashCode(double[]), only adapted to consider only the valid
+ * elements of the double array.
+ *
+ * result = prime * result + Arrays.hashCode(coeffs);
+ */
+ int coeffsCode = 1;
+ for (int i = 0; i < degree + 1; i++) {
+ long bits = Double.doubleToLongBits(coeffs[i]);
+ coeffsCode = 31 * result + (int) (bits ^ (bits >>> 32));
+ }
+ result = prime * result + coeffsCode;
+ result = prime * result + Arrays.hashCode(x2s);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ChebyshevPolynomial other = (ChebyshevPolynomial) obj;
+ if (degree != other.degree) {
+ return false;
+ }
+
+ /*
+ * Arrays.equals(double[], double[]) is not sufficient, because the arrays may be of different
+ * lengths, but contain the same significant content.
+ */
+ for (int i = 0; i < degree + 1; i++) {
+ if (Double.doubleToLongBits(coeffs[i]) != Double.doubleToLongBits(other.coeffs[i])) {
+ return false;
+ }
+ }
+
+ if (!Arrays.equals(x2s, other.x2s)) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/picante/math/functions/Computable.java b/src/main/java/picante/math/functions/Computable.java
new file mode 100644
index 0000000..767f7e8
--- /dev/null
+++ b/src/main/java/picante/math/functions/Computable.java
@@ -0,0 +1,32 @@
+package picante.math.functions;
+
+/**
+ * Interface representing some computation performed as a function of a single parameter t. The
+ * result of the computation is in turn represented by a nested marker interface
+ * {@link ComputableResult}. Implementations of Computable and ComputableResult in general need to
+ * be developed in tandem. Abstracting the result returned allows computation for multiple related
+ * values to be performed in a single call to the compute method.
+ *
+ * @author James Peachey
+ *
+ */
+public interface Computable {
+
+ /**
+ * Marker interface representing the result of a computation for a particular value of the
+ * parameter t.
+ *
+ */
+ public interface ComputableResult {
+
+ }
+
+ /**
+ * Compute the result produced by this Computable for the provided input parameter, returned as an
+ * instance of the ComputableResult interface.
+ *
+ * @param t the input parameter value at which to compute the result
+ * @return the result of the computation
+ */
+ ComputableResult compute(double t);
+}
diff --git a/src/main/java/picante/math/functions/ComputableDifferentiableUnivariateFunction.java b/src/main/java/picante/math/functions/ComputableDifferentiableUnivariateFunction.java
new file mode 100644
index 0000000..87f6279
--- /dev/null
+++ b/src/main/java/picante/math/functions/ComputableDifferentiableUnivariateFunction.java
@@ -0,0 +1,53 @@
+package picante.math.functions;
+
+import picante.math.functions.Computable.ComputableResult;
+
+/**
+ * A {@link DifferentiableUnivariateFunction} that is also {@link Computable}. The purpose of this
+ * mix is to facilitate performance improvements in cases where 1) the function and its derivative
+ * are often both needed by calling code, and 2) it is more efficient to compute the function and
+ * its derivative in a single set of operations. To take advantage of such improvements, any method
+ * that accepts DifferentiableUnivariateFunction may safely be modified to check whether an object
+ * passed also implements this interface and include code optimized for this case.
+ *
+ * This interface is generic, templated on the specific type of the {@link ComputableResult}
+ * returned in order to provide some type safety.
+ *
+ * @author James Peachey
+ *
+ */
+public interface ComputableDifferentiableUnivariateFunction
+ * Overridden to guarantee results from implememtations of this computation may be used as input
+ * to methods defined on this interface.
+ */
+ @Override
+ R compute(double t);
+
+ /**
+ * Extract and return the value of the function from an object of {@link ComputableResult} that
+ * was previously returned by the compute(double t) method. If result = compute(t1) for a
+ * particular t1, it is required that evaluate(result) return the exact same value as
+ * evaluate(t1).
+ *
+ * @param prior the result returned by a prior call to compute(t1)
+ * @return the value of the function at the same value t1
+ */
+ double evaluate(R prior);
+
+ /**
+ * Extract and return the value of the derivative of the function from a {@link ComputableResult}
+ * that was previously returned by the compute(double t) method. If result = compute(t1) for a
+ * particular t1, it is required that differentiate(result) return the exact same value as
+ * differentiate(t1).
+ *
+ * @param prior the result returned by a previous call to compute(t1)
+ * @return the value of the derivative of the function at the same value t1
+ */
+ double differentiate(R prior);
+
+}
diff --git a/src/main/java/picante/math/functions/ComputableDifferentiableVectorIJKFunction.java b/src/main/java/picante/math/functions/ComputableDifferentiableVectorIJKFunction.java
new file mode 100644
index 0000000..1da173b
--- /dev/null
+++ b/src/main/java/picante/math/functions/ComputableDifferentiableVectorIJKFunction.java
@@ -0,0 +1,57 @@
+package picante.math.functions;
+
+import picante.math.functions.Computable.ComputableResult;
+import picante.math.vectorspace.VectorIJK;
+
+/**
+ * A {@link DifferentiableVectorIJKFunction} that is also {@link Computable}. The purpose of this
+ * mix is to facilitate performance improvements in cases where 1) the function and its derivative
+ * are often both needed by calling code, and 2) it is more efficient to compute the function and
+ * its derivative in a single set of operations. To take advantage of such improvements, any method
+ * that accepts DifferentiableVectorIJKFunction may safely be modified to check whether an object
+ * passed also implements this interface and include code optimized for this case.
+ *
+ * This interface is generic, templated on the specific type of the {@link ComputableResult}
+ * returned in order to provide some type safety.
+ *
+ * @author James Peachey
+ *
+ */
+public interface ComputableDifferentiableVectorIJKFunction
+ * Overridden to guarantee results from implememtations of this computation may be used as input
+ * to methods defined on this interface.
+ */
+ @Override
+ R compute(double t);
+
+ /**
+ * Extract and return the value of the vector function from a {@link ComputableResult} that was
+ * previously returned by the compute(double t) method. If result = compute(t1) for a particular
+ * t1, it is required that evaluate(result, buffer) return the exact same value as evaluate(t1,
+ * buffer).
+ *
+ * @param prior the result returned by a prior call to compute(t1)
+ * @param buffer the buffer to capture the resultant evaluation
+ * @return a reference to the buffer for convenience
+ */
+ VectorIJK evaluate(R prior, VectorIJK buffer);
+
+ /**
+ * Extract and return the value of the derivative of the vector function from a
+ * {@link ComputableResult} that was previously returned by the compute(double t) method. If
+ * result = differentiate(t1) for a particular t1, it is required that differentiate(result,
+ * buffer) return the exact same value as differentiate(t1, buffer).
+ *
+ * @param prior the result returned by a prior call to compute(t1)
+ * @param buffer the buffer to receive the results
+ *
+ * @return a reference to buffer for convenience
+ */
+ VectorIJK differentiate(R prior, VectorIJK buffer);
+
+}
diff --git a/src/main/java/picante/math/functions/DifferentiableMatrixIJKFunction.java b/src/main/java/picante/math/functions/DifferentiableMatrixIJKFunction.java
new file mode 100644
index 0000000..1e21995
--- /dev/null
+++ b/src/main/java/picante/math/functions/DifferentiableMatrixIJKFunction.java
@@ -0,0 +1,31 @@
+package picante.math.functions;
+
+import picante.math.vectorspace.MatrixIJK;
+
+/**
+ * Simple interface for describing a single variable, differentiable matrix function.
+ */
+public interface DifferentiableMatrixIJKFunction extends MatrixIJKFunction {
+
+ /**
+ * Evaluates the derivative of the matrix function.
+ *
+ * @param t the value of interest
+ *
+ * @return the resultant differentiation
+ */
+ public default MatrixIJK differentiate(double t) {
+ return differentiate(t, new MatrixIJK());
+ }
+
+ /**
+ * Evaluates the derivative of the matrix function.
+ *
+ * @param t the value of interest
+ * @param buffer the buffer to receive the results
+ *
+ * @return a reference to buffer for convenience
+ */
+ public MatrixIJK differentiate(double t, MatrixIJK buffer);
+
+}
diff --git a/src/main/java/picante/math/functions/DifferentiableMatrixIJKFunctions.java b/src/main/java/picante/math/functions/DifferentiableMatrixIJKFunctions.java
new file mode 100644
index 0000000..7f854fb
--- /dev/null
+++ b/src/main/java/picante/math/functions/DifferentiableMatrixIJKFunctions.java
@@ -0,0 +1,9 @@
+package picante.math.functions;
+
+public class DifferentiableMatrixIJKFunctions {
+
+ /**
+ * Constructor is private to block instantiations.
+ */
+ private DifferentiableMatrixIJKFunctions() {}
+}
diff --git a/src/main/java/picante/math/functions/DifferentiableRotationMatrixIJKFunction.java b/src/main/java/picante/math/functions/DifferentiableRotationMatrixIJKFunction.java
new file mode 100644
index 0000000..7d1af66
--- /dev/null
+++ b/src/main/java/picante/math/functions/DifferentiableRotationMatrixIJKFunction.java
@@ -0,0 +1,31 @@
+package picante.math.functions;
+
+import picante.math.vectorspace.MatrixIJK;
+
+/**
+ * Simple interface for describing a single variable, differentiable rotation matrix function.
+ */
+public interface DifferentiableRotationMatrixIJKFunction extends RotationMatrixIJKFunction {
+
+ /**
+ * Evaluates the derivative of the rotation matrix function.
+ *
+ * @param t the value of interest
+ *
+ * @return the resultant differentiation
+ */
+ public default MatrixIJK differentiate(double t) {
+ return differentiate(t, new MatrixIJK());
+ }
+
+ /**
+ * Evaluates the derivative of the rotation matrix function.
+ *
+ * @param t the value of interest
+ * @param buffer the buffer to receive the results
+ *
+ * @return a reference to buffer for convenience
+ */
+ public MatrixIJK differentiate(double t, MatrixIJK buffer);
+
+}
diff --git a/src/main/java/picante/math/functions/DifferentiableRotationMatrixIJKFunctions.java b/src/main/java/picante/math/functions/DifferentiableRotationMatrixIJKFunctions.java
new file mode 100644
index 0000000..46e3a02
--- /dev/null
+++ b/src/main/java/picante/math/functions/DifferentiableRotationMatrixIJKFunctions.java
@@ -0,0 +1,10 @@
+package picante.math.functions;
+
+public class DifferentiableRotationMatrixIJKFunctions {
+
+ /**
+ * Constructor is private to block instantiations.
+ */
+ private DifferentiableRotationMatrixIJKFunctions() {}
+
+}
diff --git a/src/main/java/picante/math/functions/DifferentiableUnivariateFunction.java b/src/main/java/picante/math/functions/DifferentiableUnivariateFunction.java
new file mode 100644
index 0000000..1c49bc9
--- /dev/null
+++ b/src/main/java/picante/math/functions/DifferentiableUnivariateFunction.java
@@ -0,0 +1,17 @@
+package picante.math.functions;
+
+/**
+ * Simple interface describing a single variable, differentiable function.
+ */
+public interface DifferentiableUnivariateFunction extends UnivariateFunction {
+
+ /**
+ * Evaluates the derivative of the univariate function.
+ *
+ * @param t the value of interest
+ *
+ * @return the derivative of the function evaluated at t
+ */
+ public double differentiate(double t);
+
+}
diff --git a/src/main/java/picante/math/functions/DifferentiableUnivariateFunctionAdaptorToComputable.java b/src/main/java/picante/math/functions/DifferentiableUnivariateFunctionAdaptorToComputable.java
new file mode 100644
index 0000000..a89db7e
--- /dev/null
+++ b/src/main/java/picante/math/functions/DifferentiableUnivariateFunctionAdaptorToComputable.java
@@ -0,0 +1,47 @@
+package picante.math.functions;
+
+public class DifferentiableUnivariateFunctionAdaptorToComputable
+ implements DifferentiableUnivariateFunction, Computable {
+
+ public static class Result implements Computable.ComputableResult {
+ private final double evaluated;
+ private final double differentiated;
+
+ protected Result(double evaluated, double differentiated) {
+ this.evaluated = evaluated;
+ this.differentiated = differentiated;
+ }
+
+ public double evaluated() {
+ return evaluated;
+ }
+
+ public double differentiated() {
+ return differentiated;
+ }
+
+ }
+
+ private final DifferentiableUnivariateFunction function;
+
+ public DifferentiableUnivariateFunctionAdaptorToComputable(
+ DifferentiableUnivariateFunction function) {
+ this.function = function;
+ }
+
+ @Override
+ public double evaluate(double t) {
+ return function.evaluate(t);
+ }
+
+ @Override
+ public double differentiate(double t) {
+ return function.differentiate(t);
+ }
+
+ @Override
+ public Result compute(double t) {
+ return new Result(evaluate(t), differentiate(t));
+ }
+
+}
diff --git a/src/main/java/picante/math/functions/DifferentiableUnivariateFunctions.java b/src/main/java/picante/math/functions/DifferentiableUnivariateFunctions.java
new file mode 100644
index 0000000..a6612d8
--- /dev/null
+++ b/src/main/java/picante/math/functions/DifferentiableUnivariateFunctions.java
@@ -0,0 +1,442 @@
+package picante.math.functions;
+
+import static picante.math.PicanteMath.cos;
+import static picante.math.PicanteMath.sin;
+import picante.exceptions.RuntimeInterruptedException;
+import picante.math.PicanteMath;
+
+/**
+ * Class of static utility methods for manipulating {@link DifferentiableUnivariateFunction}s.
+ *
+ * TODO: Consider reordering operations to preserve numerical precision.
+ *
+ */
+public class DifferentiableUnivariateFunctions {
+
+ /**
+ * Constructor is private to block instantiations.
+ */
+ private DifferentiableUnivariateFunctions() {}
+
+ /**
+ * Creates a constant valued, differentiable function.
+ *
+ * @param constant the constant value to assume.
+ *
+ * @return a newly created constant value, differentiable function.
+ */
+ public static DifferentiableUnivariateFunction create(final double constant) {
+ return new DifferentiableUnivariateFunction() {
+
+ @Override
+ public double evaluate(@SuppressWarnings("unused") double t) {
+ return constant;
+ }
+
+ @Override
+ public double differentiate(@SuppressWarnings("unused") double t) {
+ return 0;
+ }
+ };
+ }
+
+ /**
+ * Creates a univariate function from the supplied differentiable one by capturing the derivative
+ *
+ * @param function a differentiable function
+ *
+ * @return a function that is the derivative of the supplied function
+ */
+ public static UnivariateFunction derivative(final DifferentiableUnivariateFunction function) {
+ return new UnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return function.differentiate(t);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that adds two functions together.
+ *
+ * @param a a differentiable function
+ * @param b another differentiable function
+ *
+ * @return a newly created function that computes the sum
+ * This is the same as simply averaging the forward and backward derivative estimates usually
+ * utilized to numerically estimate derivatives.
+ *
+ * This variant is similar to
+ * {@link DifferentiableUnivariateFunctions#interruptibleFunction(DifferentiableUnivariateFunction, int)}
+ * except it checks {@link Thread#interrupted()} every call into
+ * {@link DifferentiableUnivariateFunction#evaluate(double)} or
+ * {@link DifferentiableUnivariateFunction#differentiate(double)}
+ *
+ * This is the same as simply averaging the forward and backward derivative estimates usually
+ * utilized to numerically estimate derivatives.
+ *
+ * This class was written largely with a specific focus in mind, so the organization of the internal
+ * data and methods used to set and retrieve that information were designed with that specific focus
+ * as the only goal. Please do not change the way this class works without talking to me first.
+ *
+ * There is no checking of the input x-values array. It should be a strictly monotonically
+ * increasing sequence of values.
+ *
+ * This class is probably headed for {@link picante.concurrent}, but not until it's been run
+ * through the ringer a bit first.
+ *
+ * This variant is similar to
+ * {@link UnivariateFunctions#interruptibleFunction(UnivariateFunction, int)} except it checks
+ * {@link Thread#interrupted()} every call into {@link UnivariateFunction#evaluate(double)}
+ *
+ * Note: the constant is copied into a local buffer in the returned function, so it is not a view.
+ *
+ * Note: if the function ever evaluates to {@link VectorIJK#ZERO} then the generated function will
+ * throw {@link UnsupportedOperationException}
+ *
+ * Note: the function copies the supplied vector, so it is not a view.
+ *
+ * Note: a copy of the supplied axis is made and stored in the returned function, so it is not a
+ * view.
+ *
+ * A copy of the supplied normal vector is created, so the function returned does not provide a
+ * view
+ *
+ * Note: a copy of the supplied vector is created, so the function returned does not provide a
+ * view
+ *
+ * Key interfaces are:
+ *
+ * Methods for adjusting the contents of an interval are included here, as well as general
+ * functionality applicable to intervals in general.
+ *
+ * Note: this is equivalent to calling this.set(begin,this.getEnd()).
+ *
+ * Note: this is equivalent to calling this.set(this.getBegin(), end).
+ *
+ * There are several convenience static methods on this class for creating and manipulating the
+ * immutable interval sets. However, if you are chasing efficiency, it may be better to utilize the
+ * corresponding {@link Builder} and its methods. It will require far less intermediate allocation
+ * of memory, much like {@link StringBuilder} does.
+ *
+ * In general, methods involving statistics of the interval lengths on an instance will throw a
+ * runtime exception if they are invoked on the empty set.
+ *
+ * The iteration order is defined from the first interval that occurs in time to the last. This is
+ * also followed by the {@link Retrievable} implementation. This class and its builder both
+ * implement {@link Retrievable} as a convenience mechanism to allow one to be passed in lieu of the
+ * other in applications where efficient utilization of memory and system resources are required.
+ *
+ * Iterates over the list of unique intervals contained within this set.
+ *
+ * This boolean is a simply subset test that allows all subsets.
+ *
+ * For convenience, the builder implements {@link Retrievable} and {@link Indexable}; however,
+ * each invocation of {@link Builder#get(int)} creates a new {@link UnwritableInterval}, unlike
+ * {@link IntervalSet#get(int)}.
+ *
+ * This is analogus to calling:
+ *
+ * This is analogous to calling:
+ * This is analogous to calling:
+ * Note: this is not a wrapper, but rather a snapshot of the contents of the supplied interval
+ * argument is created and converted into an interval set. Mutation of the supplied interval
+ * argument after creating the {@link IntervalSet} has no impact on the state of the returned
+ * object.
+ *
+ * Intervals may overlap, the resultant {@link IntervalSet} will be the union of the supplied
+ * intervals.
+ *
+ * Turns a list of doubles: {1,2,3,4,5,6} into an interval set: [1,2] [3,4] [5,6]
+ *
+ * If the supplied function is not monotonically increasing over the domain covered by this
+ * interval set, then the results are likely not to be what is expected. This method simply
+ * invokes the supplied function on the end points and accumulates the results in a newly created
+ * interval set.
+ *
+ * A common notation utilized in the documentation of this class is the standard mathematical usage
+ * of open and closed interval notation. For example, "(" and ")" indicate that
+ * the begin and end of an interval does not include the end point. Similarly "[" and
+ * "]" indicate the interval does include its ends. They may be mixed, like so: (a,b]
+ * which indicates the interval from a to b, excluding a and including b.
+ *
+ * The variety of containment operations included on this class may, at first appear confusing. They
+ * were largely included for completeness, and all of the interval methods assume that the argument
+ * to the method is a closed interval. {@link PicanteMath#ulp(double)} is useful for manipulating
+ * these situations, if you do wish to exclude the boundary. TODO: Consider adding
+ * createUlpContracted and createUlpExpanded methods?
+ *
+ * As with other weakly immutable classes, the unwritable parent is the less utilized of the two
+ * classes. {@link Interval} contains many useful static features applicable to both elements of the
+ * design pattern.
+ *
+ * This method makes an unwritable copy only if necessary. It tries to avoid making a copy
+ * wherever possible.
+ *
+ * These classes may be used to capture information about intervals on the real line. Compromises
+ * are made to accommodate the fact that the underlying data structure is based on finite precision
+ * numbers.
+ *
+ * Maintainer Note:This class is an implementation detail of the classes and methods provided
+ * by this package as a whole. Functionality present here should not be exposed outside of this
+ * package. Further, as methods defined here may be invoked from any class in this package, it must
+ * remain free of references to all other classes in this package. In other words, it is at the
+ * absolute bottom of the package layering structure.
+ *
+ * This class contains the mutator methods necessary to set or alter the internals of the parent
+ * classes fields.
+ *
+ * The values from the data array are copied into the matrix as follows:
+ * Note: No checks are done to verify that the columns are orthogonal.
+ *
+ * ( a + b ).
+ */
+ public static DifferentiableUnivariateFunction add(final DifferentiableUnivariateFunction a,
+ final DifferentiableUnivariateFunction b) {
+ return new DifferentiableUnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(t) + b.evaluate(t);
+ }
+
+ @Override
+ public double differentiate(double t) {
+ return a.differentiate(t) + b.differentiate(t);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that subtracts the right function from the left.
+ *
+ * @param a a differentiable function, the minuend
+ * @param b another differentiable function, the subtrahend
+ *
+ * @return a newly created function that computes the difference ( a - b )
+ */
+ public static DifferentiableUnivariateFunction subtract(final DifferentiableUnivariateFunction a,
+ final DifferentiableUnivariateFunction b) {
+
+ return new DifferentiableUnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(t) - b.evaluate(t);
+ }
+
+ @Override
+ public double differentiate(double t) {
+ return a.differentiate(t) - b.differentiate(t);
+ }
+ };
+
+ }
+
+ /**
+ * Creates a new function that computes the product of two functions.
+ *
+ * @param a a differentiable function
+ * @param b another differentiable function
+ *
+ * @return a newly created function that computes the product ( a * b ).
+ */
+ public static DifferentiableUnivariateFunction multiply(final DifferentiableUnivariateFunction a,
+ final DifferentiableUnivariateFunction b) {
+ return new DifferentiableUnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(t) * b.evaluate(t);
+ }
+
+ @Override
+ public double differentiate(double t) {
+ return a.evaluate(t) * b.differentiate(t) + a.differentiate(t) * b.evaluate(t);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that computes the quotient of two functions.
+ *
+ * @param a a differentiable function, the dividend
+ * @param b another differentiable function, the divisor
+ *
+ * @return a newly created function that computes the quotient ( a / b )
+ *
+ */
+ public static DifferentiableUnivariateFunction divide(final DifferentiableUnivariateFunction a,
+ final DifferentiableUnivariateFunction b) {
+
+ return new DifferentiableUnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(t) / b.evaluate(t);
+ }
+
+ @Override
+ public double differentiate(double t) {
+
+ double bValue = b.evaluate(t);
+
+ return (bValue * a.differentiate(t) - a.evaluate(t) * b.differentiate(t)) / bValue / bValue;
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that computes the composition of two functions.
+ *
+ * @param a the outer function in the composition, evaluated last
+ * @param b the inner function in the composition, evaluated first
+ *
+ * @return a newly created function that computes the composition a ( b ( t ) )
+ */
+ public static DifferentiableUnivariateFunction compose(final DifferentiableUnivariateFunction a,
+ final DifferentiableUnivariateFunction b) {
+ return new DifferentiableUnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(b.evaluate(t));
+ }
+
+ @Override
+ public double differentiate(double t) {
+ return a.differentiate(b.evaluate(t)) * b.differentiate(t);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that scales the supplied function by a constant.
+ *
+ * @param scale the scale factor to apply to the function
+ *
+ * @param function the function to scale
+ *
+ * @return a newly created function that computes the product scale * ( a )
+ */
+ public static DifferentiableUnivariateFunction scale(final double scale,
+ final DifferentiableUnivariateFunction function) {
+ return new DifferentiableUnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return scale * function.evaluate(t);
+ }
+
+ @Override
+ public double differentiate(double t) {
+ return scale * function.differentiate(t);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that negates the supplied function.
+ *
+ * @param function the function to negate
+ *
+ * @return a newly created function that computes -(a)
+ */
+ public static DifferentiableUnivariateFunction negate(
+ final DifferentiableUnivariateFunction function) {
+ return scale(-1.0, function);
+ }
+
+ /**
+ * Creates a function that estimates the derivative by utilizing a quadratic approximating
+ * function over some small, delta interval surrounding the value of interest.
+ * ( a + b ).
+ */
+ public static UnivariateFunction add(final UnivariateFunction a, final UnivariateFunction b) {
+ return new UnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(t) + b.evaluate(t);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that subtracts the right function from the left.
+ *
+ * @param a a univariate function, the minuend
+ * @param b another univariate function, the subtrahend
+ *
+ * @return a newly created function that computes the difference ( a - b )
+ */
+ public static UnivariateFunction subtract(final UnivariateFunction a,
+ final UnivariateFunction b) {
+ return new UnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(t) - b.evaluate(t);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that computes the product of two functions.
+ *
+ * @param a a univariate function
+ * @param b another univariate function
+ *
+ * @return a newly created function that computes the product ( a * b ).
+ */
+ public static UnivariateFunction multiply(final UnivariateFunction a,
+ final UnivariateFunction b) {
+ return new UnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(t) * b.evaluate(t);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that computes the quotient of two functions.
+ *
+ * @param a a univariate function, the dividend
+ * @param b another univariate function, the divisor
+ *
+ * @return a newly created function that computes the quotient ( a / b )
+ *
+ */
+ public static UnivariateFunction divide(final UnivariateFunction a, final UnivariateFunction b) {
+ return new UnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(t) / b.evaluate(t);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that computes the composition of two functions.
+ *
+ * @param a the outer function in the composition, evaluated last
+ * @param b the inner function in the composition, evaluated first
+ *
+ * @return a newly created function that computes the composition a ( b ( t ) )
+ */
+ public static UnivariateFunction compose(final UnivariateFunction a, final UnivariateFunction b) {
+ return new UnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(b.evaluate(t));
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that scales the supplied function by a constant.
+ *
+ * @param scale the scale factor to apply to the function
+ *
+ * @param function the function to scale
+ *
+ * @return a newly created function that computes the product scale * ( a )
+ */
+ public static UnivariateFunction scale(final double scale, final UnivariateFunction function) {
+ return new UnivariateFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return scale * function.evaluate(t);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function that negates the supplied function.
+ *
+ * @param function the function to negate
+ *
+ * @return a newly created function that computes -(a)
+ */
+ public static UnivariateFunction negate(final UnivariateFunction function) {
+ return scale(-1.0, function);
+ }
+
+ /**
+ * Simple interruptible delegate implementation
+ */
+ private static class InterruptibleFunctionDelegate implements UnivariateFunction {
+
+ private final RuntimeInterruptionChecker checker;
+ private final UnivariateFunction delegate;
+
+ InterruptibleFunctionDelegate(RuntimeInterruptionChecker checker, UnivariateFunction delegate) {
+ super();
+ this.checker = checker;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public double evaluate(double t) {
+ checker.processRuntimeInterruptCheckAndThrow();
+ return delegate.evaluate(t);
+ }
+
+ }
+
+ /**
+ * Wraps an existing function and provides a variant that will respond to
+ * {@link Thread#interrupt()} by throwing {@link RuntimeInterruptedException} on interrupt.
+ *
+ * @param function the function to wrap into an interruptible one
+ * @param evaluationCheckModulo the total number of function evaluations to execute before
+ * checking {@link Thread#interrupted()}
+ *
+ * @return the interruptible function
+ */
+ public static UnivariateFunction interruptibleFunction(UnivariateFunction function,
+ int evaluationCheckModulo) {
+ return new InterruptibleFunctionDelegate(
+ RuntimeInterruptionChecker.createWithCheckModulo(evaluationCheckModulo), function);
+ }
+
+ /**
+ * Wraps an existing function and provides a variant that will respond to
+ * {@link Thread#interrupt()} by throwing {@link RuntimeInterruptedException} on interrupt.
+ * a / ||a||
+ */
+ public static VectorIJKFunction unitize(final VectorIJKFunction function) {
+ return new VectorIJKFunction() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return function.evaluate(t, buffer).unitize();
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function by adding the two supplied functions.
+ *
+ * @param a a vector function
+ * @param b another vector function
+ *
+ * @return a newly created vector function that computes the component-wise sum
+ * ( a + b )
+ */
+ public static VectorIJKFunction add(final VectorIJKFunction a, final VectorIJKFunction b) {
+ return new AbstractImplementation() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ VectorIJK internalBuffer = getInternalBuffer();
+ a.evaluate(t, internalBuffer);
+ b.evaluate(t, buffer);
+ return VectorIJK.add(internalBuffer, buffer, buffer);
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function by subtracting the two supplied functions
+ *
+ * @param a a vector function, the minuend
+ * @param b another vector function, the subtrahend
+ *
+ * @return a newly created vector function that computes the difference ( a - b )
+ */
+ public static VectorIJKFunction subtract(final VectorIJKFunction a, final VectorIJKFunction b) {
+ return new AbstractImplementation() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ VectorIJK internalBuffer = getInternalBuffer();
+ a.evaluate(t, internalBuffer);
+ b.evaluate(t, buffer);
+ return VectorIJK.subtract(internalBuffer, buffer, buffer);
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function by multiplying the components of the supplied functions
+ *
+ * @param a a vector function
+ * @param b another vector function
+ *
+ * @return a newly created function that computes the component-wise product of a and b
+ */
+ public static VectorIJKFunction multiply(final VectorIJKFunction a, final VectorIJKFunction b) {
+ return new AbstractImplementation() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ VectorIJK internalBuffer = getInternalBuffer();
+ a.evaluate(t, internalBuffer);
+ b.evaluate(t, buffer);
+ return buffer.setTo(internalBuffer.getI() * buffer.getI(),
+ internalBuffer.getJ() * buffer.getJ(), internalBuffer.getK() * buffer.getK());
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function by dividing the components of the supplied functions
+ *
+ * @param a a vector function, the dividends
+ * @param b another vector function, the divisors
+ *
+ * @return a newly created function that computes the component-wise quotient of
+ * a / b
+ */
+ public static VectorIJKFunction divide(final VectorIJKFunction a, final VectorIJKFunction b) {
+ return new AbstractImplementation() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ VectorIJK internalBuffer = getInternalBuffer();
+ a.evaluate(t, internalBuffer);
+ b.evaluate(t, buffer);
+ return buffer.setTo(internalBuffer.getI() / buffer.getI(),
+ internalBuffer.getJ() / buffer.getJ(), internalBuffer.getK() / buffer.getK());
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function by composing the vector function with the univariate function.
+ *
+ * @param a the vector function
+ * @param b the univariate function
+ *
+ * @return a newly created function that computes the composition ( a(b(t)) )
+ */
+ public static VectorIJKFunction compose(final VectorIJKFunction a, final UnivariateFunction b) {
+ return new VectorIJKFunction() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return a.evaluate(b.evaluate(t), buffer);
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function by composing one vector function with another, component-wise.
+ *
+ * @param a a vector function
+ * @param b another vector function
+ *
+ * @return a newly created function that computes the composition, component-wise of a with b.
+ */
+ public static VectorIJKFunction compose(final VectorIJKFunction a, final VectorIJKFunction b) {
+ return new AbstractImplementationTwo() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ VectorIJK internalBuffer = getInternalBuffer();
+ VectorIJK otherBuffer = getOtherBuffer();
+ b.evaluate(t, internalBuffer);
+ a.evaluate(internalBuffer.getI(), otherBuffer);
+ buffer.setI(otherBuffer.getI());
+ a.evaluate(internalBuffer.getJ(), otherBuffer);
+ buffer.setJ(otherBuffer.getJ());
+ a.evaluate(internalBuffer.getK(), otherBuffer);
+ buffer.setK(otherBuffer.getK());
+ return buffer;
+ }
+ };
+
+ }
+
+ /**
+ * Creates a vector function by composing, component-wise a univariate function with each element
+ * of the vector.
+ *
+ * @param a a univariate function
+ * @param b the vector function
+ *
+ * @return a newly created function that computes ( a(b(t)) )
+ */
+ public static VectorIJKFunction compose(final UnivariateFunction a, final VectorIJKFunction b) {
+ return new AbstractImplementation() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ VectorIJK internalBuffer = getInternalBuffer();
+ b.evaluate(t, internalBuffer);
+ return buffer.setTo(a.evaluate(internalBuffer.getI()), a.evaluate(internalBuffer.getJ()),
+ a.evaluate(internalBuffer.getK()));
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function that is the negative of another.
+ *
+ * @param function the function to negate.
+ *
+ * @return a newly created function that computes -function
+ */
+ public static VectorIJKFunction negate(final VectorIJKFunction function) {
+ return new VectorIJKFunction() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return function.evaluate(t, buffer).negate();
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function scaled by a single scalar value
+ *
+ * @param scale the scalar multiple
+ * @param function the vector function
+ *
+ * @return a newly created function that computes scale * function
+ */
+ public static VectorIJKFunction scale(final double scale, final VectorIJKFunction function) {
+ return new VectorIJKFunction() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return function.evaluate(t, buffer).scale(scale);
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function scaled by a single scalar function
+ *
+ * @param scale the scalar function
+ * @param function the vector function
+ *
+ * @return a newly created function that computes scale(t) * function
+ */
+ public static VectorIJKFunction scale(final UnivariateFunction scale,
+ final VectorIJKFunction function) {
+ return new VectorIJKFunction() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return function.evaluate(t, buffer).scale(scale.evaluate(t));
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function scaled component-wise by a vector
+ * ( scaleA * a + scaleB * b )
+ */
+ public static VectorIJKFunction combine(final UnivariateFunction scaleA,
+ final VectorIJKFunction a, final UnivariateFunction scaleB, final VectorIJKFunction b) {
+
+ return new AbstractImplementation() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return VectorIJK.combine(scaleA.evaluate(t), a.evaluate(t, getInternalBuffer()),
+ scaleB.evaluate(t), b.evaluate(t, buffer), buffer);
+ }
+ };
+
+ }
+
+ /**
+ * Creates a vector function by linearly combining three existing vector functions.
+ *
+ * @param scaleA the scalar multiplier to apply to a
+ * @param a a vector function
+ * @param scaleB the scalar multiplier to apply to b
+ * @param b another vector function
+ * @param scaleC the scalar multiplier to apply to c
+ * @param c yet another vector function
+ *
+ * @return a newly created vector function that computes
+ * ( scaleA * a + scaleB * b + scaleC * c )
+ */
+ public static VectorIJKFunction combine(final UnivariateFunction scaleA,
+ final VectorIJKFunction a, final UnivariateFunction scaleB, final VectorIJKFunction b,
+ final UnivariateFunction scaleC, final VectorIJKFunction c) {
+
+ return new AbstractImplementationTwo() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return VectorIJK.combine(scaleA.evaluate(t), a.evaluate(t, getInternalBuffer()),
+ scaleB.evaluate(t), b.evaluate(t, getOtherBuffer()), scaleC.evaluate(t),
+ c.evaluate(t, buffer), buffer);
+ }
+ };
+
+ }
+
+ /**
+ * Creates a vector function by computing the unitized cross product of two existing functions
+ *
+ * @param a a vector function
+ * @param b the other vector function
+ *
+ * @return a newly created vector function that computes ( a x b )/||a||/||b||
+ */
+ public static VectorIJKFunction uCross(final VectorIJKFunction a, final VectorIJKFunction b) {
+ return new AbstractImplementationTwo() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return VectorIJK.uCross(a.evaluate(t, getInternalBuffer()), b.evaluate(t, getOtherBuffer()),
+ buffer);
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function by computing the cross product of two existing functions
+ *
+ * @param a a vector function
+ * @param b the other vector function
+ *
+ * @return a newly created vector function that computes ( a x b )
+ */
+ public static VectorIJKFunction cross(final VectorIJKFunction a, final VectorIJKFunction b) {
+ return new AbstractImplementation() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return VectorIJK.cross(a.evaluate(t, getInternalBuffer()), b.evaluate(t, buffer), buffer);
+ }
+ };
+ }
+
+ /**
+ * Creates a univariate function by computing the dot product of two vector functions
+ *
+ * @param a a vector function
+ * @param b another vector function
+ *
+ * @return a newly created vector function that computes the dot product
+ * $lt; a , b >
+ */
+ public static UnivariateFunction dot(final VectorIJKFunction a, final VectorIJKFunction b) {
+ return new AbstractFunctionTwo() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(t, getInternalBuffer()).getDot(b.evaluate(t, getOtherBuffer()));
+ }
+ };
+ }
+
+ /**
+ * Creates a univariate function from a vector function by computing its length
+ *
+ * @param function a vector function
+ *
+ * @return a function capturing the length of the supplied vector function
+ */
+ public static UnivariateFunction length(final VectorIJKFunction function) {
+ return new AbstractFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return function.evaluate(t, getInternalBuffer()).getLength();
+ }
+ };
+ }
+
+ /**
+ * Creates a univariate function from the two supplied vector functions by computing their angular
+ * separation
+ *
+ * @param a a vector function
+ * @param b another vector function
+ *
+ * @return a univariate function that computes the angular separation between a and b
+ */
+ public static UnivariateFunction separation(final VectorIJKFunction a,
+ final VectorIJKFunction b) {
+ return new AbstractFunctionTwo() {
+
+ @Override
+ public double evaluate(double t) {
+ return a.evaluate(t, getInternalBuffer()).getSeparation(b.evaluate(t, getOtherBuffer()));
+ }
+ };
+ }
+
+ /**
+ * Creates a univariate function from the ith component of the supplied vector function
+ *
+ * @param function the vector function
+ *
+ * @return a newly created univariate function capturing the ith component of the vector function
+ */
+ public static UnivariateFunction extractI(final VectorIJKFunction function) {
+ return new AbstractFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return function.evaluate(t, getInternalBuffer()).getI();
+ }
+ };
+ }
+
+ /**
+ * Creates a univariate function from the jth component of the supplied vector function
+ *
+ * @param function the vector function
+ *
+ * @return a newly created univariate function capturing the jth component of the vector function
+ */
+ public static UnivariateFunction extractJ(final VectorIJKFunction function) {
+ return new AbstractFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return function.evaluate(t, getInternalBuffer()).getJ();
+ }
+ };
+ }
+
+ /**
+ * Creates a univariate function from the kth component of the supplied vector function
+ *
+ * @param function the vector function
+ *
+ * @return a newly created univariate function capturing the kth component of the vector function
+ */
+ public static UnivariateFunction extractK(final VectorIJKFunction function) {
+ return new AbstractFunction() {
+
+ @Override
+ public double evaluate(double t) {
+ return function.evaluate(t, getInternalBuffer()).getK();
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function that rotates each result using the given rotation matrix
+ *
+ * @param function the vector function
+ * @param rotationMatrix the rotation to apply to each result from the function
+ *
+ * @return a newly created vector function that rotates its results by the given matrix
+ */
+ public static VectorIJKFunction rotate(final VectorIJKFunction function,
+ final UnwritableRotationMatrixIJK rotationMatrix) {
+ return new VectorIJKFunction() {
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ VectorIJK v = function.evaluate(t, buffer);
+ return rotationMatrix.mxv(v, v);
+ }
+ };
+ }
+
+ /**
+ * Creates a vector function that rotates each result using the given rotation matrix
+ *
+ * @param function the vector function
+ * @param rotationMatrix the rotation to apply to each result from the function
+ *
+ * @return a newly created vector function that rotates its results by the given matrix
+ */
+ public static VectorIJKFunction rotate(final VectorIJKFunction function,
+ final RotationMatrixIJKFunction rotationMatrixFunction) {
+ return new VectorIJKFunction() {
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ VectorIJK v = function.evaluate(t, buffer);
+ return rotationMatrixFunction.evaluate(t, new RotationMatrixIJK()).mxv(v, v);
+ }
+ };
+ }
+
+}
diff --git a/src/main/java/picante/math/functions/package-info.java b/src/main/java/picante/math/functions/package-info.java
new file mode 100644
index 0000000..3c6c7f5
--- /dev/null
+++ b/src/main/java/picante/math/functions/package-info.java
@@ -0,0 +1,18 @@
+/**
+ * Provides interfaces and standard implementations of mathematical functions.
+ *
+ *
+ * builder.complementAgainst(-Double.MAX_VALUE, Double.MAX_VALUE);
+ * builder.expand(delta/2.0,delta/2.0);
+ * builder.contract(delta/2.0,delta/2.0);
+ *
+ *
+ *
+ * @param value the value to clamp
+ *
+ * @return a value in the range [this.begin, this.end]
+ */
+ public double clamp(double value) {
+ return min(max(begin, value), end);
+ }
+
+ /**
+ * Clamps the supplied interval into the range supported by this interval. This function
+ * individually clamps the 'begin' and 'end' of the supplied interval; see
+ * {@link UnwritableInterval#clamp(double value)}.
+ *
+ *
+ *
+ * @param interval the interval to clamp
+ *
+ * @return an interval in the range [this.begin, this.end]
+ */
+ public UnwritableInterval clamp(UnwritableInterval interval) {
+ return new UnwritableInterval(clamp(interval.getBegin()), clamp(interval.getEnd()));
+ }
+
+ /**
+ * Does the interval contain the specified interval in a [,] fashion?
+ *
+ * @param begin the start of the closed interval to consider
+ * @param end the end of the closed interval to consider
+ *
+ * @return true if [begin,end] is completely contained within the instance interpreted as
+ * [this.begin,this.end]; false otherwise
+ */
+ public boolean closedContains(double begin, double end) {
+ checkArgument(begin <= end, "End precedes begin.");
+ return (begin >= this.begin) && (end <= this.end);
+ }
+
+ /**
+ * Does the interval contain the specified interval in a [,] fashion?
+ *
+ * @param interval the closed interval to consider for containment
+ *
+ * @return true if interval is completely contained within the instance interpreted as
+ * [this.begin, this.end]; false otherwise
+ */
+ public boolean closedContains(UnwritableInterval interval) {
+ return (interval.begin >= this.begin) && (interval.end <= this.end);
+ }
+
+ /**
+ * Does the interval contain the specified interval in a (,) fashion?
+ *
+ * @param begin the start of the closed interval to consider
+ * @param end the end of the closed interval to consider
+ *
+ * @return true if [begin,end] is completely contained within the instance interpreted as
+ * (this.begin,this.end); false otherwise
+ */
+ public boolean openContains(double begin, double end) {
+ checkArgument(begin <= end, "End precedes begin.");
+ return (begin > this.begin) && (end < this.end);
+ }
+
+ /**
+ * Does the interval contain the specified interval in a (,) fashion?
+ *
+ * @param interval the closed interval to consider for containment
+ *
+ * @return true if interval is completely contained within the instance interpreted as
+ * (this.begin, this.end); false otherwise
+ */
+ public boolean openContains(UnwritableInterval interval) {
+ return (interval.begin > this.begin) && (interval.end < this.end);
+ }
+
+ /**
+ * Does the interval contain the specified interval in a (,] fashion?
+ *
+ * @param begin the start of the closed interval to consider
+ * @param end the end of the closed interval to consider
+ *
+ * @return true if [begin,end] is completely contained within the instance interpreted as
+ * (this.begin,this.end]; false otherwise
+ */
+ public boolean beginOpenContains(double begin, double end) {
+ checkArgument(begin <= end, "End precedes begin.");
+ return (begin > this.begin) && (end <= this.end);
+ }
+
+ /**
+ * Does the interval contain the specified interval in a (,] fashion?
+ *
+ * @param interval the closed interval to consider for containment
+ *
+ * @return true if interval is completely contained within the instance interpreted as
+ * (this.begin, this.end]; false otherwise
+ */
+ public boolean beginOpenContains(UnwritableInterval interval) {
+ return (interval.begin > this.begin) && (interval.end <= this.end);
+ }
+
+ /**
+ * Does the interval contain the specified interval in a [,) fashion?
+ *
+ * @param begin the start of the closed interval to consider
+ * @param end the end of the closed interval to consider
+ *
+ * @return true if [begin,end] is completely contained within the instance interpreted as
+ * [this.begin,this.end); false otherwise
+ */
+ public boolean endOpenContains(double begin, double end) {
+ checkArgument(begin <= end, "End precedes begin.");
+ return (begin >= this.begin) && (end < this.end);
+ }
+
+ /**
+ * Does the interval contain the specified interval in a [,) fashion?
+ *
+ * @param interval the closed interval to consider for containment
+ *
+ * @return true if interval is completely contained within the instance interpreted as
+ * [this.begin,this.end); false otherwise
+ */
+ public boolean endOpenContains(UnwritableInterval interval) {
+ return (interval.begin >= this.begin) && (interval.end < this.end);
+ }
+
+ /**
+ * Does the interval intersect with the specified interval in a [,] fashion?
+ *
+ * @param begin the start of the closed interval to consider
+ * @param end the end of the closed interval to consider
+ *
+ * @return true if [begin,end] intersects with the instance interpreted as [this.begin,this.end];
+ * false otherwise
+ */
+ public boolean closedIntersects(double begin, double end) {
+ checkArgument(begin <= end, "End precedes begin.");
+ return this.begin == begin || (this.begin < begin ? this.end >= begin : end >= this.begin);
+ }
+
+ /**
+ * Does the interval intersect with the specified interval in a [,] fashion?
+ *
+ * @param interval the closed interval to consider for intersection
+ *
+ * @return true if interval intersects with the instance interpreted as [this.begin,this.end];
+ * false otherwise
+ */
+ public boolean closedIntersects(UnwritableInterval interval) {
+ return this.begin == interval.begin
+ || (this.begin < interval.begin ? this.end >= interval.begin : interval.end >= this.begin);
+ }
+
+ /**
+ * Does the interval intersect with the specified interval in a (,) fashion?
+ *
+ * @param begin the start of the closed interval to consider
+ * @param end the end of the closed interval to consider
+ *
+ * @return true if [begin,end] intersects with the instance interpreted as (this.begin,this.end);
+ * false otherwise
+ */
+ public boolean openIntersects(double begin, double end) {
+ checkArgument(begin <= end, "End precedes begin.");
+ return isSingleton() ? false
+ : (this.begin == begin && begin != end)
+ || (this.begin < begin ? this.end > begin : end > this.begin);
+ }
+
+ /**
+ * Does the interval intersect with the specified interval in a (,) fashion?
+ *
+ * @param interval the closed interval to consider for intersection
+ *
+ * @return true if interval intersects with the instance interpreted as (this.begin,this.end);
+ * false otherwise
+ */
+ public boolean openIntersects(UnwritableInterval interval) {
+ return isSingleton() ? false
+ : (this.begin == interval.begin && interval.begin != interval.end)
+ || (this.begin < interval.begin ? this.end > interval.begin
+ : interval.end > this.begin);
+ }
+
+ /**
+ * Does the interval intersect with the specified interval in a (,] fashion?
+ *
+ * @param begin the start of the closed interval to consider
+ * @param end the end of the closed interval to consider
+ *
+ * @return true if [begin,end] intersects with the instance interpreted as (this.begin,this.end];
+ * false otherwise
+ */
+ public boolean beginOpenIntersects(double begin, double end) {
+ checkArgument(begin <= end, "End precedes begin.");
+ return isSingleton() ? false
+ : (this.begin == begin && begin != end)
+ || (this.begin < begin ? this.end >= begin : end > this.begin);
+ }
+
+ /**
+ * Does the interval intersect with the specified interval in a (,] fashion?
+ *
+ * @param interval the closed interval to consider for intersection
+ *
+ * @return true if interval intersects with the instance interpreted as (this.begin,this.end];
+ * false otherwise
+ */
+ public boolean beginOpenIntersects(UnwritableInterval interval) {
+ return isSingleton() ? false
+ : (this.begin == interval.begin && interval.begin != interval.end)
+ || (this.begin < interval.begin ? this.end >= interval.begin
+ : interval.end > this.begin);
+ }
+
+ /**
+ * Does the interval intersect with the specified interval in a [,) fashion?
+ *
+ * @param begin the start of the closed interval to consider
+ * @param end the end of the closed interval to consider
+ *
+ * @return true if [begin,end] intersects with the instance interpreted as [this.begin,this.end);
+ * false otherwise
+ */
+ public boolean endOpenIntersects(double begin, double end) {
+ checkArgument(begin <= end, "End precedes begin.");
+ return isSingleton() ? false
+ : this.begin == begin || (this.begin < begin ? this.end > begin : end >= this.begin);
+ }
+
+ /**
+ * Does the interval intersect with the specified interval in a [,) fashion?
+ *
+ * @param interval the closed interval to consider for intersection
+ *
+ * @return true if interval intersects with the instance interpreted as [this.begin,this.end);
+ * false otherwise
+ */
+ public boolean endOpenIntersects(UnwritableInterval interval) {
+ return isSingleton() ? false
+ : this.begin == interval.begin || (this.begin < interval.begin ? this.end > interval.begin
+ : interval.end >= this.begin);
+ }
+
+ /**
+ * Evaluates the signed distance from a supplied value to the interval. If the value is
+ * closed-contained in the interval the distance is zero. If the value is before the interval the
+ * distance is positive, if it is after the distance is negative.
+ *
+ * @param value the value to evaluate the signed distance to the interval
+ *
+ * @return the signed distance from the supplied value to the interval, zero if the value is
+ * closed-contained in the interval, positive if before, negative if after
+ */
+ public double signedDistanceTo(double value) {
+ if (closedContains(value)) {
+ return 0.0;
+ }
+ return value < begin ? begin - value : end - value;
+ // return min(abs(begin - value), abs(end - value));
+ }
+
+ /**
+ * Makes an unwritable copy of the supplied interval.
+ *
+ *
+ *
+ *
+ *
+ * data[0][0]
+ * data[0][1]
+ *
+ *
+ * data[1][0]
+ * data[1][1]
+ *
+ *
+ *
+ *
+ *
+ * ii
+ * ij
+ *
+ *
+ * ji
+ * jj
+ *
+ *
+ *
+ *
| ii | + *ij | + *
| ji | + *jj | + *
+ *
+ *
| ii | + *ij | + *
| ji | + *jj | + *
+ *
+ *
| ii | + *ij | + *
| ji | + *jj | + *
+ *
+ *
| ii (0,0) | + *ij (0,1) | + *
| ji (1,0) | + *jj (1,1) | + *
+ *
+ *
| ii | + *ij | + *
| ji | + *jj | + *
+ *
+ *
| ii | + *ij | + *
| ji | + *jj | + *
+ *
+ *
| 0 | + *1 | + *
| ii | + *ij | + *
| ji | + *jj | + *
MatrixIJ containing the product.
+ *
+ * @see MatrixIJ#mxmt(UnwritableMatrixIJ, UnwritableMatrixIJ, MatrixIJ)
+ */
+ public static MatrixIJ mxmt(UnwritableMatrixIJ a, UnwritableMatrixIJ b) {
+ return mxmt(a, b, new MatrixIJ());
+ }
+
+ /**
+ * Compute the product of a matrix with the transpose of another matrix.
+ *
+ * @param a the left hand matrix
+ * @param b the right hand matrix to transpose, then multiply
+ * @param buffer the buffer to receive the product, a*transpose(b).
+ *
+ * @return a reference to buffer for convenience.
+ */
+ public static MatrixIJ mxmt(UnwritableMatrixIJ a, UnwritableMatrixIJ b, MatrixIJ buffer) {
+ double ii = a.ii * b.ii + a.ij * b.ij;
+ double ij = a.ii * b.ji + a.ij * b.jj;
+
+ double ji = a.ji * b.ii + a.jj * b.ij;
+ double jj = a.ji * b.ji + a.jj * b.jj;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+
+ return buffer;
+ }
+
+ /**
+ * Compute the product of a transpose of a matrix with another matrix.
+ *
+ * @param a the left hand matrix to transpose, then multiply
+ * @param b the right hand matrix
+ *
+ * @return a new MatrixIJ containing the product
+ *
+ * @see MatrixIJ#mtxm(UnwritableMatrixIJ, UnwritableMatrixIJ, MatrixIJ)
+ */
+ public static MatrixIJ mtxm(UnwritableMatrixIJ a, UnwritableMatrixIJ b) {
+ return mtxm(a, b, new MatrixIJ());
+ }
+
+ /**
+ * Compute the product of a transpose of a matrix with another matrix.
+ *
+ * @param a the left hand matrix to transpose, then multiply
+ * @param b the right hand matrix
+ * @param buffer the buffer to receive the product, transpose(a)*b.
+ *
+ * @return a reference to buffer for convenience
+ */
+ public static MatrixIJ mtxm(UnwritableMatrixIJ a, UnwritableMatrixIJ b, MatrixIJ buffer) {
+ double ii = a.ii * b.ii + a.ji * b.ji;
+ double ij = a.ii * b.ij + a.ji * b.jj;
+
+ double ji = a.ij * b.ii + a.jj * b.ji;
+ double jj = a.ij * b.ij + a.jj * b.jj;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+
+ return buffer;
+ }
+
+ /**
+ * Compute the product of two matrices.
+ *
+ * @param a the left hand matrix
+ * @param b the right hand matrix
+ *
+ * @return a new MatrixIJ containing the product (ab).
+ *
+ * @see MatrixIJ#mxm(UnwritableMatrixIJ, UnwritableMatrixIJ, MatrixIJ)
+ */
+ public static MatrixIJ mxm(UnwritableMatrixIJ a, UnwritableMatrixIJ b) {
+ return mxm(a, b, new MatrixIJ());
+ }
+
+ /**
+ * Compute the product of two matrices.
+ *
+ * @param a the left hand matrix
+ * @param b the right hand matrix
+ * @param buffer the buffer to receive the product, a*b
+ *
+ * @return a reference to buffer for convenience
+ */
+ public static MatrixIJ mxm(UnwritableMatrixIJ a, UnwritableMatrixIJ b, MatrixIJ buffer) {
+ double ii = a.ii * b.ii + a.ij * b.ji;
+ double ij = a.ii * b.ij + a.ij * b.jj;
+
+ double ji = a.ji * b.ii + a.jj * b.ji;
+ double jj = a.ji * b.ij + a.jj * b.jj;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+
+ return buffer;
+ }
+
+ /**
+ * Compute the sum of a pair of matrices multipled with another matrix transposed
+ *
+ * @param a left hand matrix in the first product
+ * @param b right hand matrix to transpose in the first product
+ * @param c left hand matrix in the second product
+ * @param d right hand matrix to transpose in the second product
+ *
+ * @return a new MatrixIJ containing (a x bt) + (c x dt)
+ *
+ * @see MatrixIJ#mxmtadd(UnwritableMatrixIJ, UnwritableMatrixIJ, UnwritableMatrixIJ,
+ * UnwritableMatrixIJ, MatrixIJ)
+ */
+ public static MatrixIJ mxmtadd(UnwritableMatrixIJ a, UnwritableMatrixIJ b, UnwritableMatrixIJ c,
+ UnwritableMatrixIJ d) {
+ return mxmtadd(a, b, c, d, new MatrixIJ());
+ }
+
+ /**
+ * Compute the sum of a pair of matrices multipled with another matrix transposed
+ *
+ * @param a left hand matrix in the first product
+ * @param b right hand matrix to transpose in the first product
+ * @param c left hand matrix in the second product
+ * @param d right hand matrix to transpose in the second product
+ * @param buffer buffer to receive the results of (a x bt) + (c x dt)
+ *
+ * @return reference to buffer for convenience
+ */
+ public static MatrixIJ mxmtadd(UnwritableMatrixIJ a, UnwritableMatrixIJ b, UnwritableMatrixIJ c,
+ UnwritableMatrixIJ d, MatrixIJ buffer) {
+
+ double ii = a.ii * b.ii + a.ij * b.ij;
+ double ij = a.ii * b.ji + a.ij * b.jj;
+
+ double ji = a.ji * b.ii + a.jj * b.ij;
+ double jj = a.ji * b.ji + a.jj * b.jj;
+
+ ii += c.ii * d.ii + c.ij * d.ij;
+ ij += c.ii * d.ji + c.ij * d.jj;
+
+ ji += c.ji * d.ii + c.jj * d.ij;
+ jj += c.ji * d.ji + c.jj * d.jj;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+
+ return buffer;
+
+ }
+
+ /**
+ * Compute the sum of a pair of matrix transposes multipled with another matrix
+ *
+ * @param a left hand matrix to transpose in the first product
+ * @param b right hand matrix in the first product
+ * @param c left hand matrix to transpose in the second product
+ * @param d right hand matrix in the second product
+ *
+ * @return a new MatrixIJ containing (at x b) + (ct x d)
+ *
+ * @see MatrixIJ#mtxmadd(UnwritableMatrixIJ, UnwritableMatrixIJ, UnwritableMatrixIJ,
+ * UnwritableMatrixIJ, MatrixIJ)
+ */
+ public static MatrixIJ mtxmadd(UnwritableMatrixIJ a, UnwritableMatrixIJ b, UnwritableMatrixIJ c,
+ UnwritableMatrixIJ d) {
+ return mtxmadd(a, b, c, d, new MatrixIJ());
+ }
+
+ /**
+ * Compute the sum of a pair of matrix transposes multipled with another matrix
+ *
+ * @param a left hand matrix to transpose in the first product
+ * @param b right hand matrix in the first product
+ * @param c left hand matrix to transpose in the second product
+ * @param d right hand matrix in the second product
+ * @param buffer buffer to receive the results of (at x b) + (ct x d)
+ *
+ * @return reference to buffer for convenience
+ */
+ public static MatrixIJ mtxmadd(UnwritableMatrixIJ a, UnwritableMatrixIJ b, UnwritableMatrixIJ c,
+ UnwritableMatrixIJ d, MatrixIJ buffer) {
+
+ double ii = a.ii * b.ii + a.ji * b.ji;
+ double ij = a.ii * b.ij + a.ji * b.jj;
+
+ double ji = a.ij * b.ii + a.jj * b.ji;
+ double jj = a.ij * b.ij + a.jj * b.jj;
+
+ ii += c.ii * d.ii + c.ji * d.ji;
+ ij += c.ii * d.ij + c.ji * d.jj;
+
+ ji += c.ij * d.ii + c.jj * d.ji;
+ jj += c.ij * d.ij + c.jj * d.jj;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+
+ return buffer;
+
+ }
+
+ /**
+ * Compute the sum of the products of two pairs of matrices.
+ *
+ * @param a left hand matrix in first product
+ * @param b right hand matrix in first product
+ * @param c left hand matrix in second product
+ * @param d right hand matrix in second product
+ *
+ * @return a new MatrixIJ containing (a x b) + (c x d)
+ *
+ * @see MatrixIJ#mxmadd(UnwritableMatrixIJ, UnwritableMatrixIJ, UnwritableMatrixIJ,
+ * UnwritableMatrixIJ, MatrixIJ)
+ */
+ public static MatrixIJ mxmadd(UnwritableMatrixIJ a, UnwritableMatrixIJ b, UnwritableMatrixIJ c,
+ UnwritableMatrixIJ d) {
+ return mxmadd(a, b, c, d, new MatrixIJ());
+ }
+
+ /**
+ * Compute the sum of the products of two pairs of matrices.
+ *
+ * @param a left hand matrix in first product
+ * @param b right hand matrix in first product
+ * @param c left hand matrix in second product
+ * @param d right hand matrix in second product
+ * @param buffer buffer to receive the results of (a x b) + (c x d)
+ *
+ * @return a reference to buffer for convenience
+ */
+ public static MatrixIJ mxmadd(UnwritableMatrixIJ a, UnwritableMatrixIJ b, UnwritableMatrixIJ c,
+ UnwritableMatrixIJ d, MatrixIJ buffer) {
+
+ double ii = a.ii * b.ii + a.ij * b.ji;
+ double ij = a.ii * b.ij + a.ij * b.jj;
+
+ double ji = a.ji * b.ii + a.jj * b.ji;
+ double jj = a.ji * b.ij + a.jj * b.jj;
+
+ ii += c.ii * d.ii + c.ij * d.ji;
+ ij += c.ii * d.ij + c.ij * d.jj;
+
+ ji += c.ji * d.ii + c.jj * d.ji;
+ jj += c.ji * d.ij + c.jj * d.jj;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+
+ return buffer;
+
+ }
+
+ /**
+ * Compute the component-wise difference of two matrices.
+ *
+ * @param a the minuend matrix
+ * @param b the subtrahend matrix
+ *
+ * @return a new MatrixIJ which contains (a - b)
+ *
+ * @see MatrixIJ#subtract(UnwritableMatrixIJ, UnwritableMatrixIJ, MatrixIJ)
+ */
+ public static MatrixIJ subtract(UnwritableMatrixIJ a, UnwritableMatrixIJ b) {
+ return subtract(a, b, new MatrixIJ());
+ }
+
+ /**
+ * Compute the component-wise difference of two matrices.
+ *
+ * @param a the minuend matrix
+ * @param b the subtrahend matrix
+ * @param buffer the buffer to receive the results of the subtraction
+ *
+ * @return a reference to buffer for convenience which now contains (a - b)
+ */
+ public static MatrixIJ subtract(UnwritableMatrixIJ a, UnwritableMatrixIJ b, MatrixIJ buffer) {
+ buffer.ii = a.ii - b.ii;
+ buffer.ji = a.ji - b.ji;
+ buffer.ij = a.ij - b.ij;
+ buffer.jj = a.jj - b.jj;
+
+ return buffer;
+ }
+
+ /**
+ * Compute component-wise sum of two matrices.
+ *
+ * @param a a matrix
+ * @param b another matrix
+ *
+ * @return a new MatrixIJ containing (a + b)
+ *
+ * @see MatrixIJ#add(UnwritableMatrixIJ, UnwritableMatrixIJ, MatrixIJ)
+ */
+ public static MatrixIJ add(UnwritableMatrixIJ a, UnwritableMatrixIJ b) {
+ return add(a, b, new MatrixIJ());
+ }
+
+ /**
+ * Compute component-wise sum of two matrices.
+ *
+ * @param a a matrix
+ * @param b another matrix
+ * @param buffer the buffer to receive a + b
+ *
+ * @return a reference to buffer for convenience
+ */
+ public static MatrixIJ add(UnwritableMatrixIJ a, UnwritableMatrixIJ b, MatrixIJ buffer) {
+ buffer.ii = a.ii + b.ii;
+ buffer.ji = a.ji + b.ji;
+ buffer.ij = a.ij + b.ij;
+ buffer.jj = a.jj + b.jj;
+
+ return buffer;
+ }
+
+ /**
+ * Diagonalizes a symmetric matrix.
+ * + * Diagonalization converts a matrix from it's symmetric form to an equivalent representation: + * + *
+ * T + * diagonalized = rotate * symmetricMatrix * rotate + *+ * + * where diagonlized is a matrix of the form: + * + *
+ * [ a 0 ] + * diagonalized = [ ] + * [ 0 b ] + *+ * + * and (a,b) are the eigenvalues of the matrix, and the columns of rotate are the eigenvectors + * corresponding to (a,b) respectively. + * + * + * + * @param symmetricMatrix a symmetric matrix + * @param eigenvalueBuffer the buffer to capture the eigenvalues + * @param eigenvectorBuffer the buffer to capture the eigenvectors, may overwrite symmetricMatrix + * + * @return a reference to eigenvectorBuffer for convenience + * + * @throws IllegalArgumentException if symmetricMatrix is not symmetric, i.e. + * symmetricMatrix.isSymmetric() is false. + */ + public static MatrixIJ diagonalizeSymmetricMatrix(UnwritableMatrixIJ symmetricMatrix, + VectorIJ eigenvalueBuffer, MatrixIJ eigenvectorBuffer) { + + checkArgument(symmetricMatrix.isSymmetric(), "Only able to diagonlize symmetric matrices"); + + /* + * Is the matrix already diagonal? If so, then don't do any heavy lifting. + */ + if (symmetricMatrix.ij == 0.0) { + eigenvalueBuffer.setTo(symmetricMatrix.ii, symmetricMatrix.jj); + eigenvectorBuffer.setTo(MatrixIJ.IDENTITY); + return eigenvectorBuffer; + } + + /* + * We only are going to use the upper triangle of the matrix. Determine a scale factor to + * improve numerical robustness. + */ + double scale = + max(abs(symmetricMatrix.ii), max(abs(symmetricMatrix.ij), abs(symmetricMatrix.jj))); + + double a = symmetricMatrix.ii / scale; + double b = symmetricMatrix.ij / scale; + double c = symmetricMatrix.jj / scale; + + /* + * Compute the eigenvalues of the scaled version of symmetricMatrix. The eigenvalues are simply + * the roots of the equation: + * + * determinant ( (1/scale) * symmetricMatrix - x * MatrixIJ.IDENTITY ) = 0 + * + * or equivalently: + * + * x*x - (a+c) *x + (ac - b*b) = 0 + */ + solveQuadratic(1.0, -(a + c), a * c - b * b, eigenvalueBuffer); + + double eigval1 = eigenvalueBuffer.i; + double eigval2 = eigenvalueBuffer.j; + + /* + * The ith component of the eigenvalueBuffer is the root corresponding to the positive + * discriminant term; this is guaranteed by method used to solve the quadratic equation. Now + * find the eigenvector corresponding to the eigenvalue of the smaller magnitude. We can unitize + * it and select an orthogonal unit vector so as to create the desired rotation matrix. + * + * There are two candidate eigenvectors, select the one involving the eigenvector of the larger + * magnitude. + */ + if ((abs(eigval1 - a)) >= (abs(eigval1) - c)) { + + /* + * In this case the second eigenvector component should be larger than |b|. Use Math.max() + * below to guard against reversal of the inequality due to round-off error. Abuse the + * eigenvalueBuffer temporarily to hold the eigenvector. + */ + eigenvalueBuffer.setTo(b, max(eigval1 - a, abs(b))); + eigenvalueBuffer.unitize(); + + eigenvectorBuffer.setTo(eigenvalueBuffer.getJ(), -eigenvalueBuffer.getI(), + eigenvalueBuffer.getI(), eigenvalueBuffer.getJ()); + + /* + * Swap the eigenvalues. + */ + eigenvalueBuffer.setTo(eigval2, eigval1); + + } else { + + eigenvalueBuffer.setTo(max(eigval1 - c, abs(b)), b); + eigenvalueBuffer.unitize(); + + eigenvectorBuffer.setTo(eigenvalueBuffer.getI(), eigenvalueBuffer.getJ(), + -eigenvalueBuffer.getJ(), eigenvalueBuffer.getI()); + + /* + * Restore the eigenvalues into their buffer. + */ + eigenvalueBuffer.setTo(eigval1, eigval2); + } + + /* + * Scale the eigenvalues back up to their appropriate values. + */ + eigenvalueBuffer.scale(scale); + + return eigenvectorBuffer; + + } + + /** + * Solves a quadratic equation of the form: + * + *
+ * 2 + * a * x + b * x + c = 0 + *+ * + * @param a the quadratic coefficient + * @param b the linear coefficient + * @param c the constant coefficient + * + * @param buffer the buffer to receive the real roots, the ith component will contain the positive + * discriminant term and the jth the negative. + * + * @return a reference to buffer for convenience + * + * @throws IllegalArgumentException if a and b are both 0.0, or if the roots are complex. + */ + static VectorIJ solveQuadratic(double a, double b, double c, VectorIJ buffer) { + + checkArgument((a != 0.0) || (b != 0.0), + "Both the linear and quadratic degree coefficients are zero."); + + double scale = max(abs(a), max(abs(b), abs(c))); + + /* + * If the coefficients can be scaled without zeroing any of them out, do so. The expression + * below only evaluates to true when the input variables can be safely scaled. + */ + if (!(((a != 0.0) && (a / scale == 0.0)) || ((b != 0.0) && (b / scale == 0.0)) + || ((c != 0.0) && (c / scale == 0.0)))) { + a /= scale; + b /= scale; + c /= scale; + } + + /* + * If the second degree coefficient is non-zero then we have a quadratic equation that needs + * factoring. + */ + if (a != 0.0) { + + double discriminant = b * b - 4 * a * c; + + /* + * Verify that the discriminant is positive or zero. + */ + checkArgument(discriminant >= 0.0, "Roots are not real."); + + /* + * Take advantage of the fact that c/a is the product of the roots to improve the accuracy of + * the root having the smaller magnitude. Compute the larger root first and then divide by c/a + * by it to obtain the smaller root. + */ + if (b < 0.0) { + + /* + * The ith component will contain the root of the larger magnitude. + */ + buffer.setI((-b + sqrt(discriminant)) / (2.0 * a)); + buffer.setJ((c / a) / buffer.getI()); + } else if (b > 0.0) { + + /* + * The jth component will contain the root of the larger magnitude. + */ + buffer.setJ((-b - sqrt(discriminant)) / (2.0 * a)); + buffer.setI((c / a) / buffer.getJ()); + } else { + + /* + * The roots have the same magnitude. + */ + buffer.setI(sqrt(discriminant) / (2.0 * a)); + buffer.setJ(-buffer.getI()); + } + + return buffer; + + } + + /* + * If we reach here, then the quadratic coefficient is zero, implying this is a simple linear + * equation. Since there is only one solution, set them both to the same value, the root. + */ + buffer.setI(-c / b); + buffer.setJ(buffer.getI()); + return buffer; + } + + /** + * Compute the product of the transpose of a matrix with a vector. + * + * @param m the matrix + * @param v the vector + * + * @return a new
VectorIJ containing the result.
+ *
+ * @see UnwritableMatrixIJ#mtxv(UnwritableVectorIJ)
+ */
+ @Deprecated
+ public static VectorIJ mtxv(UnwritableMatrixIJ m, UnwritableVectorIJ v) {
+ return m.mtxv(v, new VectorIJ());
+ }
+
+ /**
+ * Compute the product of the transpose of a matrix with a vector.
+ *
+ * @param m the matrix
+ * @param v the vector
+ * @param buffer the buffer to receive the product, transpose(m)*v
+ *
+ * @return a reference to buffer for convenience.
+ *
+ * @see UnwritableMatrixIJ#mtxv(UnwritableVectorIJ, VectorIJ)
+ */
+ @Deprecated
+ public static VectorIJ mtxv(UnwritableMatrixIJ m, UnwritableVectorIJ v, VectorIJ buffer) {
+ return m.mtxv(v, buffer);
+ }
+
+ /**
+ * Compute the product of a matrix with a vector.
+ *
+ * @param m the matrix
+ * @param v the vector
+ *
+ * @return a new VectorIJ containing the result.
+ *
+ * @see UnwritableMatrixIJ#mxv(UnwritableVectorIJ)
+ */
+ @Deprecated
+ public static VectorIJ mxv(UnwritableMatrixIJ m, UnwritableVectorIJ v) {
+ return m.mxv(v, new VectorIJ());
+ }
+
+ /**
+ * Compute the product of a matrix with a vector.
+ *
+ * @param m the matrix
+ * @param v the vector
+ * @param buffer the buffer to receive the product, m*v.
+ *
+ * @return a reference to buffer for convenience.
+ *
+ * @see UnwritableMatrixIJ#mxv(UnwritableVectorIJ, VectorIJ)
+ */
+ @Deprecated
+ public static VectorIJ mxv(UnwritableMatrixIJ m, UnwritableVectorIJ v, VectorIJ buffer) {
+ return m.mxv(v, buffer);
+ }
+
+}
diff --git a/src/main/java/picante/math/vectorspace/MatrixIJK.java b/src/main/java/picante/math/vectorspace/MatrixIJK.java
new file mode 100644
index 0000000..8ed3850
--- /dev/null
+++ b/src/main/java/picante/math/vectorspace/MatrixIJK.java
@@ -0,0 +1,1642 @@
+package picante.math.vectorspace;
+
+import static picante.math.PicanteMath.abs;
+import static picante.math.vectorspace.InternalOperations.computeNorm;
+import picante.designpatterns.Writable;
+
+/**
+ * A writable subclass of the unwritable 3D matrix parent completing one link in the
+ * weak-immutability design pattern.
+ * + * This class contains the mutator methods necessary to set or alter the internals of the parent + * classes fields. + *
+ */ +public class MatrixIJK extends UnwritableMatrixIJK + implements Writable.ImplementationInterface
+ * The values from the data array are copied into the matrix as follows:
+ *
| data[0][0] | + *data[0][1] | + *data[0][2] | + *
| data[1][0] | + *data[1][1] | + *data[1][2] | + *
| data[2][0] | + *data[2][1] | + *data[2][2] | + *
+ * Note: No checks are done to verify that the columns are orthogonal. + *
+ * + * @return a reference to the instance for convenience + * + * @throws UnsupportedOperationException if the lengths of any of the columns are zero or too + * small to properly invert multiplicatively in the space available to double precision + */ + public MatrixIJK invort() { + + transpose(); + + double length = computeNorm(this.ii, this.ij, this.ik); + + if ((length * INVORSION_BOUND < 1) || (length == 0)) { + throw new UnsupportedOperationException( + "ith column of matrix has length, " + length + ", for which there is no inverse."); + } + + this.ii /= length; + this.ii /= length; + this.ij /= length; + this.ij /= length; + this.ik /= length; + this.ik /= length; + + length = computeNorm(this.ji, this.jj, this.jk); + + if ((length * INVORSION_BOUND < 1) || (length == 0)) { + throw new UnsupportedOperationException( + "jth column of matrix has length, " + length + ", for which there is no inverse."); + } + + this.ji /= length; + this.ji /= length; + this.jj /= length; + this.jj /= length; + this.jk /= length; + this.jk /= length; + + length = computeNorm(this.ki, this.kj, this.kk); + + if ((length * INVORSION_BOUND < 1) || (length == 0)) { + throw new UnsupportedOperationException( + "kth column of matrix has length, " + length + ", for which there is no inverse."); + } + + this.ki /= length; + this.ki /= length; + this.kj /= length; + this.kj /= length; + this.kk /= length; + this.kk /= length; + + return this; + } + + /** + * Scales each component of the matrix by the supplied factor. + * + * @param scale the scale factor to apply + * + * @return a reference to the instance which now contains the scaled matrix. + */ + public MatrixIJK scale(double scale) { + + this.ii *= scale; + this.ji *= scale; + this.ki *= scale; + this.ij *= scale; + this.jj *= scale; + this.kj *= scale; + this.ik *= scale; + this.jk *= scale; + this.kk *= scale; + + return this; + } + + /** + * Scales each column of the matrix by the supplied factors. + * + * @param scaleI the ith column scale + * @param scaleJ the jth column scale + * @param scaleK the kth column scale + * + * @return a reference to the instance for convenience which contains the scaled matrix. + */ + public MatrixIJK scale(double scaleI, double scaleJ, double scaleK) { + + this.ii *= scaleI; + this.ji *= scaleI; + this.ki *= scaleI; + this.ij *= scaleJ; + this.jj *= scaleJ; + this.kj *= scaleJ; + this.ik *= scaleK; + this.jk *= scaleK; + this.kk *= scaleK; + + return this; + } + + /** + * Sets the ith row, ith column component. + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii (0,0) | + *ij (0,1) | + *ik (0,2) | + *
| ji (1,0) | + *jj (1,1) | + *jk (1,2) | + *
| ki (2,0) | + *kj (2,1) | + *kk (2,2) | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| 0 | + *1 | + *2 | + *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
MatrixIJK containing the product.
+ *
+ * @see MatrixIJK#mxmt(UnwritableMatrixIJK, UnwritableMatrixIJK, MatrixIJK)
+ */
+ public static MatrixIJK mxmt(UnwritableMatrixIJK a, UnwritableMatrixIJK b) {
+ return mxmt(a, b, new MatrixIJK());
+ }
+
+ /**
+ * Compute the product of a matrix with the transpose of another matrix.
+ *
+ * @param a the left hand matrix
+ * @param b the right hand matrix to transpose, then multiply
+ * @param buffer the buffer to receive the product, a*transpose(b).
+ *
+ * @return a reference to buffer for convenience.
+ */
+ public static MatrixIJK mxmt(UnwritableMatrixIJK a, UnwritableMatrixIJK b, MatrixIJK buffer) {
+ double ii = a.ii * b.ii + a.ij * b.ij + a.ik * b.ik;
+ double ij = a.ii * b.ji + a.ij * b.jj + a.ik * b.jk;
+ double ik = a.ii * b.ki + a.ij * b.kj + a.ik * b.kk;
+
+ double ji = a.ji * b.ii + a.jj * b.ij + a.jk * b.ik;
+ double jj = a.ji * b.ji + a.jj * b.jj + a.jk * b.jk;
+ double jk = a.ji * b.ki + a.jj * b.kj + a.jk * b.kk;
+
+ double ki = a.ki * b.ii + a.kj * b.ij + a.kk * b.ik;
+ double kj = a.ki * b.ji + a.kj * b.jj + a.kk * b.jk;
+ double kk = a.ki * b.ki + a.kj * b.kj + a.kk * b.kk;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+ buffer.ik = ik;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+ buffer.jk = jk;
+
+ buffer.ki = ki;
+ buffer.kj = kj;
+ buffer.kk = kk;
+ return buffer;
+ }
+
+ /**
+ * Compute the product of a transpose of a matrix with another matrix.
+ *
+ * @param a the left hand matrix to transpose, then multiply
+ * @param b the right hand matrix
+ *
+ * @return a new MatrixIJK containing the product
+ *
+ * @see MatrixIJK#mtxm(UnwritableMatrixIJK, UnwritableMatrixIJK, MatrixIJK)
+ */
+ public static MatrixIJK mtxm(UnwritableMatrixIJK a, UnwritableMatrixIJK b) {
+ return mtxm(a, b, new MatrixIJK());
+ }
+
+ /**
+ * Compute the product of a transpose of a matrix with another matrix.
+ *
+ * @param a the left hand matrix to transpose, then multiply
+ * @param b the right hand matrix
+ * @param buffer the buffer to receive the product, transpose(a)*b.
+ *
+ * @return a reference to buffer for convenience
+ */
+ public static MatrixIJK mtxm(UnwritableMatrixIJK a, UnwritableMatrixIJK b, MatrixIJK buffer) {
+ double ii = a.ii * b.ii + a.ji * b.ji + a.ki * b.ki;
+ double ij = a.ii * b.ij + a.ji * b.jj + a.ki * b.kj;
+ double ik = a.ii * b.ik + a.ji * b.jk + a.ki * b.kk;
+
+ double ji = a.ij * b.ii + a.jj * b.ji + a.kj * b.ki;
+ double jj = a.ij * b.ij + a.jj * b.jj + a.kj * b.kj;
+ double jk = a.ij * b.ik + a.jj * b.jk + a.kj * b.kk;
+
+ double ki = a.ik * b.ii + a.jk * b.ji + a.kk * b.ki;
+ double kj = a.ik * b.ij + a.jk * b.jj + a.kk * b.kj;
+ double kk = a.ik * b.ik + a.jk * b.jk + a.kk * b.kk;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+ buffer.ik = ik;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+ buffer.jk = jk;
+
+ buffer.ki = ki;
+ buffer.kj = kj;
+ buffer.kk = kk;
+ return buffer;
+ }
+
+ /**
+ * Compute the product of two matrices.
+ *
+ * @param a the left hand matrix
+ * @param b the right hand matrix
+ *
+ * @return a new MatrixIJK containing the product (ab).
+ *
+ * @see MatrixIJK#mxm(UnwritableMatrixIJK, UnwritableMatrixIJK, MatrixIJK)
+ */
+ public static MatrixIJK mxm(UnwritableMatrixIJK a, UnwritableMatrixIJK b) {
+ return mxm(a, b, new MatrixIJK());
+ }
+
+ /**
+ * Compute the product of two matrices.
+ *
+ * @param a the left hand matrix
+ * @param b the right hand matrix
+ * @param buffer the buffer to receive the product, a*b
+ *
+ * @return a reference to buffer for convenience
+ */
+ public static MatrixIJK mxm(UnwritableMatrixIJK a, UnwritableMatrixIJK b, MatrixIJK buffer) {
+ double ii = a.ii * b.ii + a.ij * b.ji + a.ik * b.ki;
+ double ij = a.ii * b.ij + a.ij * b.jj + a.ik * b.kj;
+ double ik = a.ii * b.ik + a.ij * b.jk + a.ik * b.kk;
+
+ double ji = a.ji * b.ii + a.jj * b.ji + a.jk * b.ki;
+ double jj = a.ji * b.ij + a.jj * b.jj + a.jk * b.kj;
+ double jk = a.ji * b.ik + a.jj * b.jk + a.jk * b.kk;
+
+ double ki = a.ki * b.ii + a.kj * b.ji + a.kk * b.ki;
+ double kj = a.ki * b.ij + a.kj * b.jj + a.kk * b.kj;
+ double kk = a.ki * b.ik + a.kj * b.jk + a.kk * b.kk;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+ buffer.ik = ik;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+ buffer.jk = jk;
+
+ buffer.ki = ki;
+ buffer.kj = kj;
+ buffer.kk = kk;
+
+ return buffer;
+ }
+
+ /**
+ * Compute the sum of a pair of matrices multipled with another matrix transposed
+ *
+ * @param a left hand matrix in the first product
+ * @param b right hand matrix to transpose in the first product
+ * @param c left hand matrix in the second product
+ * @param d right hand matrix to transpose in the second product
+ *
+ * @return a new MatrixIJK containing (a x bt) + (c x dt)
+ *
+ * @see MatrixIJK#mxmtadd(UnwritableMatrixIJK, UnwritableMatrixIJK, UnwritableMatrixIJK,
+ * UnwritableMatrixIJK, MatrixIJK)
+ */
+ public static MatrixIJK mxmtadd(UnwritableMatrixIJK a, UnwritableMatrixIJK b,
+ UnwritableMatrixIJK c, UnwritableMatrixIJK d) {
+ return mxmtadd(a, b, c, d, new MatrixIJK());
+ }
+
+ /**
+ * Compute the sum of a pair of matrices multipled with another matrix transposed
+ *
+ * @param a left hand matrix in the first product
+ * @param b right hand matrix to transpose in the first product
+ * @param c left hand matrix in the second product
+ * @param d right hand matrix to transpose in the second product
+ * @param buffer buffer to receive the results of (a x bt) + (c x dt)
+ *
+ * @return reference to buffer for convenience
+ */
+ public static MatrixIJK mxmtadd(UnwritableMatrixIJK a, UnwritableMatrixIJK b,
+ UnwritableMatrixIJK c, UnwritableMatrixIJK d, MatrixIJK buffer) {
+
+ double ii = a.ii * b.ii + a.ij * b.ij + a.ik * b.ik;
+ double ij = a.ii * b.ji + a.ij * b.jj + a.ik * b.jk;
+ double ik = a.ii * b.ki + a.ij * b.kj + a.ik * b.kk;
+
+ double ji = a.ji * b.ii + a.jj * b.ij + a.jk * b.ik;
+ double jj = a.ji * b.ji + a.jj * b.jj + a.jk * b.jk;
+ double jk = a.ji * b.ki + a.jj * b.kj + a.jk * b.kk;
+
+ double ki = a.ki * b.ii + a.kj * b.ij + a.kk * b.ik;
+ double kj = a.ki * b.ji + a.kj * b.jj + a.kk * b.jk;
+ double kk = a.ki * b.ki + a.kj * b.kj + a.kk * b.kk;
+
+ ii += c.ii * d.ii + c.ij * d.ij + c.ik * d.ik;
+ ij += c.ii * d.ji + c.ij * d.jj + c.ik * d.jk;
+ ik += c.ii * d.ki + c.ij * d.kj + c.ik * d.kk;
+
+ ji += c.ji * d.ii + c.jj * d.ij + c.jk * d.ik;
+ jj += c.ji * d.ji + c.jj * d.jj + c.jk * d.jk;
+ jk += c.ji * d.ki + c.jj * d.kj + c.jk * d.kk;
+
+ ki += c.ki * d.ii + c.kj * d.ij + c.kk * d.ik;
+ kj += c.ki * d.ji + c.kj * d.jj + c.kk * d.jk;
+ kk += c.ki * d.ki + c.kj * d.kj + c.kk * d.kk;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+ buffer.ik = ik;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+ buffer.jk = jk;
+
+ buffer.ki = ki;
+ buffer.kj = kj;
+ buffer.kk = kk;
+ return buffer;
+
+ }
+
+ /**
+ * Compute the sum of a pair of matrix transposes multipled with another matrix
+ *
+ * @param a left hand matrix to transpose in the first product
+ * @param b right hand matrix in the first product
+ * @param c left hand matrix to transpose in the second product
+ * @param d right hand matrix in the second product
+ *
+ * @return a new MatrixIJK containing (at x b) + (ct x d)
+ *
+ * @see MatrixIJK#mtxmadd(UnwritableMatrixIJK, UnwritableMatrixIJK, UnwritableMatrixIJK,
+ * UnwritableMatrixIJK, MatrixIJK)
+ */
+ public static MatrixIJK mtxmadd(UnwritableMatrixIJK a, UnwritableMatrixIJK b,
+ UnwritableMatrixIJK c, UnwritableMatrixIJK d) {
+ return mtxmadd(a, b, c, d, new MatrixIJK());
+ }
+
+ /**
+ * Compute the sum of a pair of matrix transposes multipled with another matrix
+ *
+ * @param a left hand matrix to transpose in the first product
+ * @param b right hand matrix in the first product
+ * @param c left hand matrix to transpose in the second product
+ * @param d right hand matrix in the second product
+ * @param buffer buffer to receive the results of (at x b) + (ct x d)
+ *
+ * @return reference to buffer for convenience
+ */
+ public static MatrixIJK mtxmadd(UnwritableMatrixIJK a, UnwritableMatrixIJK b,
+ UnwritableMatrixIJK c, UnwritableMatrixIJK d, MatrixIJK buffer) {
+
+ double ii = a.ii * b.ii + a.ji * b.ji + a.ki * b.ki;
+ double ij = a.ii * b.ij + a.ji * b.jj + a.ki * b.kj;
+ double ik = a.ii * b.ik + a.ji * b.jk + a.ki * b.kk;
+
+ double ji = a.ij * b.ii + a.jj * b.ji + a.kj * b.ki;
+ double jj = a.ij * b.ij + a.jj * b.jj + a.kj * b.kj;
+ double jk = a.ij * b.ik + a.jj * b.jk + a.kj * b.kk;
+
+ double ki = a.ik * b.ii + a.jk * b.ji + a.kk * b.ki;
+ double kj = a.ik * b.ij + a.jk * b.jj + a.kk * b.kj;
+ double kk = a.ik * b.ik + a.jk * b.jk + a.kk * b.kk;
+
+ ii += c.ii * d.ii + c.ji * d.ji + c.ki * d.ki;
+ ij += c.ii * d.ij + c.ji * d.jj + c.ki * d.kj;
+ ik += c.ii * d.ik + c.ji * d.jk + c.ki * d.kk;
+
+ ji += c.ij * d.ii + c.jj * d.ji + c.kj * d.ki;
+ jj += c.ij * d.ij + c.jj * d.jj + c.kj * d.kj;
+ jk += c.ij * d.ik + c.jj * d.jk + c.kj * d.kk;
+
+ ki += c.ik * d.ii + c.jk * d.ji + c.kk * d.ki;
+ kj += c.ik * d.ij + c.jk * d.jj + c.kk * d.kj;
+ kk += c.ik * d.ik + c.jk * d.jk + c.kk * d.kk;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+ buffer.ik = ik;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+ buffer.jk = jk;
+
+ buffer.ki = ki;
+ buffer.kj = kj;
+ buffer.kk = kk;
+ return buffer;
+
+ }
+
+ /**
+ * Compute the sum of the products of two pairs of matrices.
+ *
+ * @param a left hand matrix in first product
+ * @param b right hand matrix in first product
+ * @param c left hand matrix in second product
+ * @param d right hand matrix in second product
+ *
+ * @return a new MatrixIJK containing (a x b) + (c x d)
+ *
+ * @see MatrixIJK#mxmadd(UnwritableMatrixIJK, UnwritableMatrixIJK, UnwritableMatrixIJK,
+ * UnwritableMatrixIJK, MatrixIJK)
+ */
+ public static MatrixIJK mxmadd(UnwritableMatrixIJK a, UnwritableMatrixIJK b,
+ UnwritableMatrixIJK c, UnwritableMatrixIJK d) {
+ return mxmadd(a, b, c, d, new MatrixIJK());
+ }
+
+ /**
+ * Compute the sum of the products of two pairs of matrices.
+ *
+ * @param a left hand matrix in first product
+ * @param b right hand matrix in first product
+ * @param c left hand matrix in second product
+ * @param d right hand matrix in second product
+ * @param buffer buffer to receive the results of (a x b) + (c x d)
+ *
+ * @return a reference to buffer for convenience
+ */
+ public static MatrixIJK mxmadd(UnwritableMatrixIJK a, UnwritableMatrixIJK b,
+ UnwritableMatrixIJK c, UnwritableMatrixIJK d, MatrixIJK buffer) {
+
+ double ii = a.ii * b.ii + a.ij * b.ji + a.ik * b.ki;
+ double ij = a.ii * b.ij + a.ij * b.jj + a.ik * b.kj;
+ double ik = a.ii * b.ik + a.ij * b.jk + a.ik * b.kk;
+
+ double ji = a.ji * b.ii + a.jj * b.ji + a.jk * b.ki;
+ double jj = a.ji * b.ij + a.jj * b.jj + a.jk * b.kj;
+ double jk = a.ji * b.ik + a.jj * b.jk + a.jk * b.kk;
+
+ double ki = a.ki * b.ii + a.kj * b.ji + a.kk * b.ki;
+ double kj = a.ki * b.ij + a.kj * b.jj + a.kk * b.kj;
+ double kk = a.ki * b.ik + a.kj * b.jk + a.kk * b.kk;
+
+ ii += c.ii * d.ii + c.ij * d.ji + c.ik * d.ki;
+ ij += c.ii * d.ij + c.ij * d.jj + c.ik * d.kj;
+ ik += c.ii * d.ik + c.ij * d.jk + c.ik * d.kk;
+
+ ji += c.ji * d.ii + c.jj * d.ji + c.jk * d.ki;
+ jj += c.ji * d.ij + c.jj * d.jj + c.jk * d.kj;
+ jk += c.ji * d.ik + c.jj * d.jk + c.jk * d.kk;
+
+ ki += c.ki * d.ii + c.kj * d.ji + c.kk * d.ki;
+ kj += c.ki * d.ij + c.kj * d.jj + c.kk * d.kj;
+ kk += c.ki * d.ik + c.kj * d.jk + c.kk * d.kk;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+ buffer.ik = ik;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+ buffer.jk = jk;
+
+ buffer.ki = ki;
+ buffer.kj = kj;
+ buffer.kk = kk;
+
+ return buffer;
+
+ }
+
+ /**
+ * Compute the component-wise difference of two matrices.
+ *
+ * @param a the minuend matrix
+ * @param b the subtrahend matrix
+ *
+ * @return a new MatrixIJK which contains (a - b)
+ *
+ * @see MatrixIJK#subtract(UnwritableMatrixIJK, UnwritableMatrixIJK, MatrixIJK)
+ */
+ public static MatrixIJK subtract(UnwritableMatrixIJK a, UnwritableMatrixIJK b) {
+ return subtract(a, b, new MatrixIJK());
+ }
+
+ /**
+ * Compute the component-wise difference of two matrices.
+ *
+ * @param a the minuend matrix
+ * @param b the subtrahend matrix
+ * @param buffer the buffer to receive the results of the subtraction
+ *
+ * @return a reference to buffer for convenience which now contains (a - b)
+ */
+ public static MatrixIJK subtract(UnwritableMatrixIJK a, UnwritableMatrixIJK b, MatrixIJK buffer) {
+ buffer.ii = a.ii - b.ii;
+ buffer.ji = a.ji - b.ji;
+ buffer.ki = a.ki - b.ki;
+ buffer.ij = a.ij - b.ij;
+ buffer.jj = a.jj - b.jj;
+ buffer.kj = a.kj - b.kj;
+ buffer.ik = a.ik - b.ik;
+ buffer.jk = a.jk - b.jk;
+ buffer.kk = a.kk - b.kk;
+
+ return buffer;
+ }
+
+ /**
+ * Compute component-wise sum of two matrices.
+ *
+ * @param a a matrix
+ * @param b another matrix
+ *
+ * @return a new MatrixIJK containing (a + b)
+ *
+ * @see MatrixIJK#add(UnwritableMatrixIJK, UnwritableMatrixIJK, MatrixIJK)
+ */
+ public static MatrixIJK add(UnwritableMatrixIJK a, UnwritableMatrixIJK b) {
+ return add(a, b, new MatrixIJK());
+ }
+
+ /**
+ * Compute component-wise sum of two matrices.
+ *
+ * @param a a matrix
+ * @param b another matrix
+ * @param buffer the buffer to receive a + b
+ *
+ * @return a reference to buffer for convenience
+ */
+ public static MatrixIJK add(UnwritableMatrixIJK a, UnwritableMatrixIJK b, MatrixIJK buffer) {
+ buffer.ii = a.ii + b.ii;
+ buffer.ji = a.ji + b.ji;
+ buffer.ki = a.ki + b.ki;
+ buffer.ij = a.ij + b.ij;
+ buffer.jj = a.jj + b.jj;
+ buffer.kj = a.kj + b.kj;
+ buffer.ik = a.ik + b.ik;
+ buffer.jk = a.jk + b.jk;
+ buffer.kk = a.kk + b.kk;
+
+ return buffer;
+ }
+
+ /**
+ * Compute the product of the transpose of a matrix with a vector.
+ *
+ * @param m the matrix
+ * @param v the vector
+ *
+ * @return a new VectorIJK containing the result.
+ *
+ * @see UnwritableMatrixIJK#mtxv(UnwritableVectorIJK)
+ */
+ @Deprecated
+ public static VectorIJK mtxv(UnwritableMatrixIJK m, UnwritableVectorIJK v) {
+ return m.mtxv(v, new VectorIJK());
+ }
+
+ /**
+ * Compute the product of the transpose of a matrix with a vector.
+ *
+ * @param m the matrix
+ * @param v the vector
+ * @param buffer the buffer to receive the product, transpose(m)*v
+ *
+ * @return a reference to buffer for convenience.
+ *
+ * @see UnwritableMatrixIJK#mtxv(UnwritableVectorIJK, VectorIJK)
+ */
+ @Deprecated
+ public static VectorIJK mtxv(UnwritableMatrixIJK m, UnwritableVectorIJK v, VectorIJK buffer) {
+ return m.mtxv(v, buffer);
+ }
+
+ /**
+ * Compute the product of a matrix with a vector.
+ *
+ * @param m the matrix
+ * @param v the vector
+ *
+ * @return a new VectorIJK containing the result.
+ *
+ * @see UnwritableMatrixIJK#mxv(UnwritableVectorIJK)
+ */
+ @Deprecated
+ public static VectorIJK mxv(UnwritableMatrixIJK m, UnwritableVectorIJK v) {
+ return m.mxv(v, new VectorIJK());
+ }
+
+ /**
+ * Compute the product of a matrix with a vector.
+ *
+ * @param m the matrix
+ * @param v the vector
+ * @param buffer the buffer to receive the product, m*v.
+ *
+ * @return a reference to buffer for convenience.
+ *
+ * @see UnwritableMatrixIJK#mxv(UnwritableVectorIJK, VectorIJK)
+ */
+ @Deprecated
+ public static VectorIJK mxv(UnwritableMatrixIJK m, UnwritableVectorIJK v, VectorIJK buffer) {
+ return m.mxv(v, buffer);
+ }
+
+}
diff --git a/src/main/java/picante/math/vectorspace/RotationMatrixIJK.java b/src/main/java/picante/math/vectorspace/RotationMatrixIJK.java
new file mode 100644
index 0000000..59b9135
--- /dev/null
+++ b/src/main/java/picante/math/vectorspace/RotationMatrixIJK.java
@@ -0,0 +1,752 @@
+package picante.math.vectorspace;
+
+import static picante.math.vectorspace.InternalOperations.computeNorm;
+import picante.designpatterns.Writable;
+
+/**
+ * A writable subclass of the unwritable 3D rotation matrix parent completing another link the
+ * weak-immutability design pattern.
+ * + * This class contains the mutator methods necessary to set or alter the internals of the parent + * classes fields. Wherever these mutations may alter the matrix to become one that no longer + * qualifies as a rotation, the methods on this class validate the resultant content. This is + * accomplished through the fact that each setTo method invokes one particular setTo method: + * {@link RotationMatrixIJK#setTo(double, double, double, double, double, double, double, double, double)} + * . The validation code is confined to this method, allowing subclasses to override a single method + * on this class to remove the check code. + *
+ *+ * Similarly each constructor, save the copy constructor and default constructor, validate input + * content to ensure that it is a rotation matrix. Validation is consistent with the + * {@link UnwritableMatrixIJK#isRotation()} method. + *
+ */ +public class RotationMatrixIJK extends UnwritableRotationMatrixIJK + implements Writable.ImplementationInterface
+ * The values from the data array are copied into the matrix as follows:
+ *
| data[0][0] | + *data[0][1] | + *data[0][2] | + *
| data[1][0] | + *data[1][1] | + *data[1][2] | + *
| data[2][0] | + *data[2][1] | + *data[2][2] | + *
+ * Note:This constructor performs no validation on the input by design. + *
+ * + * @param matrix the rotation matrix whose contents are to be copied. + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public RotationMatrixIJK(UnwritableMatrixIJK matrix) { + super(matrix); + } + + /** + * Scaling constructor, creates a new matrix by applying a scalar multiple to the components of a + * pre-existing matrix. + * + * @param scale the scale factor to apply + * @param matrix the matrix whose components are to be scaled and copied. + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public RotationMatrixIJK(double scale, UnwritableMatrixIJK matrix) { + super(scale, matrix); + } + + /** + * Column scaling constructor, creates a new rotation matrix by applying scalar multiples to the + * columns of a pre-existing matrix. + * + * @param scaleI scale factor to apply to the ith column + * @param scaleJ scale factor to apply to the jth column + * @param scaleK scale factor to apply to the kth column + * @param matrix the matrix whose components are to be scaled and copied + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public RotationMatrixIJK(double scaleI, double scaleJ, double scaleK, + UnwritableMatrixIJK matrix) { + super(scaleI, scaleJ, scaleK, matrix); + } + + /** + * Column vector constructor, creates a new matrix by populating the columns of the rotation + * matrix with the supplied vectors. + * + * @param ithColumn the vector containing the ith column + * @param jthColumn the vector containing the jth column + * @param kthColumn the vector containing the kth column + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public RotationMatrixIJK(UnwritableVectorIJK ithColumn, UnwritableVectorIJK jthColumn, + UnwritableVectorIJK kthColumn) { + super(ithColumn, jthColumn, kthColumn); + } + + /** + * Scaled column vector constructor, creates a new rotation matrix by populating the columns of + * the matrix with scaled versions of the supplied vectors + * + * @param scaleI the scale factor to apply to the ith column + * @param ithColumn the vector containing the ith column + * @param scaleJ the scale factor to apply to the jth column + * @param jthColumn the vector containing the jth column + * @param scaleK the scale factor to apply to the kth column + * @param kthColumn the vector containing the kth column + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public RotationMatrixIJK(double scaleI, UnwritableVectorIJK ithColumn, double scaleJ, + UnwritableVectorIJK jthColumn, double scaleK, UnwritableVectorIJK kthColumn) { + super(scaleI, ithColumn, scaleJ, jthColumn, scaleK, kthColumn); + } + + /** + * {@inheritDoc} + * + * Note: this method is overridden to return an instance of the writable rotation subclass, rather + * than either of the two unwritable parents. + */ + @Override + public RotationMatrixIJK createSharpened() { + return new RotationMatrixIJK(this).sharpen(); + } + + /** + * {@inheritDoc} + * + * Note: this method is overridden to return an instance of the writable rotation subclass, rather + * than either of the two unwritable parents. + */ + @Override + public RotationMatrixIJK createTranspose() { + return new RotationMatrixIJK(this).transpose(); + } + + /** + * {@inheritDoc} + * + * Note: this method is overridden to return an instance of the writable rotation subclass, rather + * than either of the two unwritable parents. + */ + @Override + public RotationMatrixIJK createInverse() { + return createTranspose(); + } + + /** + * {@inheritDoc} + * + * Note: this method is overridden to return an instance of the writable rotation subclass, rather + * than either of the two unwritable parents. + */ + @Override + public RotationMatrixIJK createInverse(@SuppressWarnings("unused") double tolerance) { + return createTranspose(); + } + + /* + * TODO: Add createInvorted method. + */ + + /** + * Sharpens the contents of the rotation matrix in place. + *+ * Sharpening is a process that starts with a rotation matrix and modifies its contents to bring + * it as close to a rotation as possible given the limits of floating point precision in the + * implementation. There are many possible rotation matrices that are "sharpenings" of + * the general rotation matrix. As such, the implementation is unspecified here. The only claims + * this method makes are that the resultant matrix is as close or closer to a rotation than what + * you start with. + *
+ * + * @return a reference to the instance for convenience. + */ + public RotationMatrixIJK sharpen() { + + /* + * Normalize the first column vector of the matrix. + */ + double norm = computeNorm(ii, ji, ki); + ii /= norm; + ji /= norm; + ki /= norm; + + /* + * Define the third column of the matrix as the cross product of the first with the second. + */ + ik = ji * kj - ki * jj; + jk = ki * ij - ii * kj; + kk = ii * jj - ji * ij; + + /* + * Normalize the result. + */ + norm = computeNorm(ik, jk, kk); + ik /= norm; + jk /= norm; + kk /= norm; + + /* + * Lastly, cross the third vector with the first to replace the second. + */ + ij = jk * ki - kk * ji; + jj = kk * ii - ik * ki; + kj = ik * ji - jk * ii; + + norm = computeNorm(ij, jj, kj); + ij /= norm; + jj /= norm; + kj /= norm; + + return this; + + } + + /** + * Transpose the matrix. + * + * @return a reference to the instance for convenience, which now contains the transpose + */ + public RotationMatrixIJK transpose() { + double tmp = this.ij; + this.ij = this.ji; + this.ji = tmp; + + tmp = this.ik; + this.ik = this.ki; + this.ki = tmp; + + tmp = this.jk; + this.jk = this.kj; + this.kj = tmp; + + return this; + } + + /** + * Sets the components of this matrix to the supplied components + *+ * Note: Developers wishing to disable all of the "setTo" checking performed by methods on + * this class need only override this particular method and choose not to invoke the checkRotation + * method. + *
+ * + * @param ii ith row, ith column element + * @param ji jth row, ith column element + * @param ki kth row, ith column element + * @param ij ith row, jth column element + * @param jj jth row, jth column element + * @param kj kth row, jth column element + * @param ik ith row, kth column element + * @param jk jth row, kth column element + * @param kk kth row, kth column element + * + * @return a reference to the instance, for convenience, that contains the newly set matrix + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public final RotationMatrixIJK setTo(double ii, double ji, double ki, double ij, double jj, + double kj, double ik, double jk, double kk) { + try { + InternalOperations.checkRotation(ii, ji, ki, ij, jj, kj, ik, jk, kk, NORM_TOLERANCE, + DETERMINANT_TOLERANCE); + } catch (MalformedRotationException e) { + throw new IllegalArgumentException("Matrix components do not describe a rotation.", e); + } + this.ii = ii; + this.ji = ji; + this.ki = ki; + this.ij = ij; + this.jj = jj; + this.kj = kj; + this.ik = ik; + this.jk = jk; + this.kk = kk; + + return this; + } + + /** + * Sets the contents of this matrix to the upper three by three block of a supplied two + * dimensional array of doubles + * + * @param data the array to copy to the components of this instance + * + * @return a reference to this instance for convenience + * + * @throws IndexOutOfBoundsException if the supplied data array does not contain at least three + * arrays of arrays of length three or greater. + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public final RotationMatrixIJK setTo(double[][] data) { + setTo(data[0][0], data[1][0], data[2][0], data[0][1], data[1][1], data[2][1], data[0][2], + data[1][2], data[2][2]); + return this; + } + + /** + * Sets the columns of this matrix to the three specified vectors. + * + * @param ithColumn the vector containing the contents to set the ith column + * @param jthColumn the vector containing the contents to set the jth column + * @param kthColumn the vector containing the contents to set the kth column + * + * @return a reference to the instance for convenience + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + * + */ + public final RotationMatrixIJK setTo(UnwritableVectorIJK ithColumn, UnwritableVectorIJK jthColumn, + UnwritableVectorIJK kthColumn) { + return setTo(ithColumn.i, ithColumn.j, ithColumn.k, jthColumn.i, jthColumn.j, jthColumn.k, + kthColumn.i, kthColumn.j, kthColumn.k); + } + + /** + * Sets the columns of this matrix to the scaled versions of the supplied vectors. + * + * @param scaleI scale factor to apply to ithColumn + * @param ithColumn the ith column vector + * @param scaleJ scale factor to apply to jthColumn + * @param jthColumn the jth column vector + * @param scaleK scale factor to apply to kthColumn + * @param kthColumn the kth column vector + * + * @return a reference to the instance for convenience + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + * + */ + public final RotationMatrixIJK setTo(double scaleI, UnwritableVectorIJK ithColumn, double scaleJ, + UnwritableVectorIJK jthColumn, double scaleK, UnwritableVectorIJK kthColumn) { + return setTo(scaleI * ithColumn.i, scaleI * ithColumn.j, scaleI * ithColumn.k, + scaleJ * jthColumn.i, scaleJ * jthColumn.j, scaleJ * jthColumn.k, scaleK * kthColumn.i, + scaleK * kthColumn.j, scaleK * kthColumn.k); + } + + /** + * Sets the contents of this rotation matrix to match those of a supplied rotation matrix + * + * @param matrix the matrix to copy + * + * @return a reference to this instance for convenience that contains the supplied components + */ + @Override + public final RotationMatrixIJK setTo(UnwritableRotationMatrixIJK matrix) { + this.ii = matrix.ii; + this.ji = matrix.ji; + this.ki = matrix.ki; + this.ij = matrix.ij; + this.jj = matrix.jj; + this.kj = matrix.kj; + this.ik = matrix.ik; + this.jk = matrix.jk; + this.kk = matrix.kk; + + return this; + } + + /** + * Sets the contents of this matrix to match those of a supplied matrix + * + * @param matrix the matrix to copy + * + * @return a reference to this instance for convenience that contains the supplied components + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public final RotationMatrixIJK setTo(UnwritableMatrixIJK matrix) { + return setTo(matrix.ii, matrix.ji, matrix.ki, matrix.ij, matrix.jj, matrix.kj, matrix.ik, + matrix.jk, matrix.kk); + + } + + /** + * Sets the contents of this matrix to a sharpened version of a supplied rotation matrix. + * + * @param matrix a rotation matrix to sharpen + * + * @return a reference to the instance, with the contents set to the sharpened version of matrix + */ + public final RotationMatrixIJK setToSharpened(UnwritableRotationMatrixIJK matrix) { + setTo(matrix); + return sharpen(); + } + + /** + * Sets the contents of this matrix to the transpose of a supplied rotation matrix. + * + * @param matrix a rotation matrix to transpose + * + * @return a reference to the instance, with the contents set to the tranposed version of matrix + */ + public final RotationMatrixIJK setToTranspose(UnwritableRotationMatrixIJK matrix) { + setTo(matrix); + return transpose(); + } + + /** + * Sets the contents of this matrix to a sharpened version of a supplied matrix. + * + * @param matrix a matrix to sharpen + * + * @return a reference to the instance, with the contents set to the sharpened version of matrix + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public final RotationMatrixIJK setToSharpened(UnwritableMatrixIJK matrix) { + setTo(matrix); + return sharpen(); + } + + /** + * Sets the contents of this matrix to the transpose of a supplied matrix. + * + * @param matrix a matrix to transpose + * + * @return a reference to the instance, with the contents set to the tranposed version of matrix + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public final RotationMatrixIJK setToTranspose(UnwritableMatrixIJK matrix) { + setTo(matrix); + return transpose(); + } + + /** + * Compute the product of a rotation matrix with the transpose of another rotation matrix. + * + * @param a the left hand rotation matrix + * @param b the right hand rotation matrix to transpose, then multiply + * + * @return a newMatrixIJK containing the resultant product
+ *
+ * @see RotationMatrixIJK#mxmt(UnwritableRotationMatrixIJK, UnwritableRotationMatrixIJK,
+ * RotationMatrixIJK)
+ */
+ public static RotationMatrixIJK mxmt(UnwritableRotationMatrixIJK a,
+ UnwritableRotationMatrixIJK b) {
+ return mxmt(a, b, new RotationMatrixIJK());
+ }
+
+ /**
+ * Compute the product of a rotation matrix with the transpose of another rotation matrix.
+ *
+ * @param a the left hand rotation matrix
+ * @param b the right hand rotation matrix to transpose, then multiply
+ * @param buffer the buffer to receive the product, a*transpose(b).
+ *
+ * @return a reference to buffer for convenience.
+ */
+ public static RotationMatrixIJK mxmt(UnwritableRotationMatrixIJK a, UnwritableRotationMatrixIJK b,
+ RotationMatrixIJK buffer) {
+ double ii = a.ii * b.ii + a.ij * b.ij + a.ik * b.ik;
+ double ij = a.ii * b.ji + a.ij * b.jj + a.ik * b.jk;
+ double ik = a.ii * b.ki + a.ij * b.kj + a.ik * b.kk;
+
+ double ji = a.ji * b.ii + a.jj * b.ij + a.jk * b.ik;
+ double jj = a.ji * b.ji + a.jj * b.jj + a.jk * b.jk;
+ double jk = a.ji * b.ki + a.jj * b.kj + a.jk * b.kk;
+
+ double ki = a.ki * b.ii + a.kj * b.ij + a.kk * b.ik;
+ double kj = a.ki * b.ji + a.kj * b.jj + a.kk * b.jk;
+ double kk = a.ki * b.ki + a.kj * b.kj + a.kk * b.kk;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+ buffer.ik = ik;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+ buffer.jk = jk;
+
+ buffer.ki = ki;
+ buffer.kj = kj;
+ buffer.kk = kk;
+ return buffer;
+ }
+
+ /**
+ * Compute the product of a transpose of a rotation matrix with another rotation matrix.
+ *
+ * @param a the left hand rotation matrix to transpose, then multiply
+ * @param b the right hand rotation matrix
+ *
+ * @return a new MatrixIJK containing the product
+ *
+ * @see RotationMatrixIJK#mtxm(UnwritableRotationMatrixIJK, UnwritableRotationMatrixIJK,
+ * RotationMatrixIJK)
+ */
+ public static RotationMatrixIJK mtxm(UnwritableRotationMatrixIJK a,
+ UnwritableRotationMatrixIJK b) {
+ return mtxm(a, b, new RotationMatrixIJK());
+ }
+
+ /**
+ * Compute the product of a transpose of a rotation matrix with another rotation matrix.
+ *
+ * @param a the left hand rotation matrix to transpose, then multiply
+ * @param b the right hand rotation matrix
+ * @param buffer the buffer to receive the product, transpose(a)*b.
+ *
+ * @return a reference to buffer for convenience
+ */
+ public static RotationMatrixIJK mtxm(UnwritableRotationMatrixIJK a, UnwritableRotationMatrixIJK b,
+ RotationMatrixIJK buffer) {
+ double ii = a.ii * b.ii + a.ji * b.ji + a.ki * b.ki;
+ double ij = a.ii * b.ij + a.ji * b.jj + a.ki * b.kj;
+ double ik = a.ii * b.ik + a.ji * b.jk + a.ki * b.kk;
+
+ double ji = a.ij * b.ii + a.jj * b.ji + a.kj * b.ki;
+ double jj = a.ij * b.ij + a.jj * b.jj + a.kj * b.kj;
+ double jk = a.ij * b.ik + a.jj * b.jk + a.kj * b.kk;
+
+ double ki = a.ik * b.ii + a.jk * b.ji + a.kk * b.ki;
+ double kj = a.ik * b.ij + a.jk * b.jj + a.kk * b.kj;
+ double kk = a.ik * b.ik + a.jk * b.jk + a.kk * b.kk;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+ buffer.ik = ik;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+ buffer.jk = jk;
+
+ buffer.ki = ki;
+ buffer.kj = kj;
+ buffer.kk = kk;
+ return buffer;
+ }
+
+ /**
+ * Compute the product of two rotation matrices.
+ *
+ * @param a the left hand rotation matrix
+ * @param b the right hand rotation matrix
+ *
+ * @return a new RotationMatrixIJK containing the product (ab)
+ *
+ * @see RotationMatrixIJK#mxm(UnwritableRotationMatrixIJK, UnwritableRotationMatrixIJK,
+ * RotationMatrixIJK)
+ */
+ public static RotationMatrixIJK mxm(UnwritableRotationMatrixIJK a,
+ UnwritableRotationMatrixIJK b) {
+ return mxm(a, b, new RotationMatrixIJK());
+ }
+
+ /**
+ * Compute the product of two rotation matrices.
+ *
+ * @param a the left hand rotation matrix
+ * @param b the right hand rotation matrix
+ * @param buffer the buffer to receive the product, a*b.
+ *
+ * @return a reference to buffer for convenience
+ */
+ public static RotationMatrixIJK mxm(UnwritableRotationMatrixIJK a, UnwritableRotationMatrixIJK b,
+ RotationMatrixIJK buffer) {
+ double ii = a.ii * b.ii + a.ij * b.ji + a.ik * b.ki;
+ double ij = a.ii * b.ij + a.ij * b.jj + a.ik * b.kj;
+ double ik = a.ii * b.ik + a.ij * b.jk + a.ik * b.kk;
+
+ double ji = a.ji * b.ii + a.jj * b.ji + a.jk * b.ki;
+ double jj = a.ji * b.ij + a.jj * b.jj + a.jk * b.kj;
+ double jk = a.ji * b.ik + a.jj * b.jk + a.jk * b.kk;
+
+ double ki = a.ki * b.ii + a.kj * b.ji + a.kk * b.ki;
+ double kj = a.ki * b.ij + a.kj * b.jj + a.kk * b.kj;
+ double kk = a.ki * b.ik + a.kj * b.jk + a.kk * b.kk;
+
+ buffer.ii = ii;
+ buffer.ij = ij;
+ buffer.ik = ik;
+
+ buffer.ji = ji;
+ buffer.jj = jj;
+ buffer.jk = jk;
+
+ buffer.ki = ki;
+ buffer.kj = kj;
+ buffer.kk = kk;
+ return buffer;
+ }
+
+ /**
+ * Creates a sharpened matrix from the supplied inputs without performing any checks on the
+ * inputs.
+ *
+ * @param ii
+ * @param ji
+ * @param ki
+ * @param ij
+ * @param jj
+ * @param kj
+ * @param ik
+ * @param jk
+ * @param kk
+ *
+ * @return
+ *
+ * @throws IllegalArgumentException if, after sharpening, the resultant matrix is still not a
+ * rotation.
+ */
+ public static RotationMatrixIJK createSharpened(double ii, double ji, double ki, double ij,
+ double jj, double kj, double ik, double jk, double kk) {
+
+ /*
+ * This is necessary to be able to leave the variable names as would be expected on this class,
+ * since they are shadowed by the fields on the anonymous inner class used to subvert the
+ * rotation check.
+ */
+ final double aii = ii;
+ final double aji = ji;
+ final double aki = ki;
+ final double aij = ij;
+ final double ajj = jj;
+ final double akj = kj;
+ final double aik = ik;
+ final double ajk = jk;
+ final double akk = kk;
+
+ /*
+ * By pass the constructor check, and execute sharpen directly.
+ */
+ RotationMatrixIJK source = new RotationMatrixIJK() {
+ {
+ this.ii = aii;
+ this.ji = aji;
+ this.ki = aki;
+ this.ij = aij;
+ this.jj = ajj;
+ this.kj = akj;
+ this.ik = aik;
+ this.jk = ajk;
+ this.kk = akk;
+ }
+ };
+
+ /*
+ * Check the determinant of source to see that it is at least postiive.
+ */
+ if (source.getDeterminant() <= 0) {
+ throw new IllegalArgumentException(
+ "Source has a determinant that is not strictly positive. Unable to sharpen source into a rotation matrix.");
+ }
+
+ /*
+ * Return an actual RotationMatrixIJK, not the anonymous subclass.
+ */
+ source.sharpen();
+ return new RotationMatrixIJK(source.ii, source.ji, source.ki, source.ij, source.jj, source.kj,
+ source.ik, source.jk, source.kk);
+ }
+
+}
diff --git a/src/main/java/picante/math/vectorspace/UnwritableMatrixIJ.java b/src/main/java/picante/math/vectorspace/UnwritableMatrixIJ.java
new file mode 100644
index 0000000..0b65ef8
--- /dev/null
+++ b/src/main/java/picante/math/vectorspace/UnwritableMatrixIJ.java
@@ -0,0 +1,693 @@
+package picante.math.vectorspace;
+
+import static picante.math.PicanteMath.abs;
+import static picante.math.vectorspace.InternalOperations.computeDeterminant;
+import static picante.math.vectorspace.InternalOperations.computeNorm;
+
+/**
+ * A weakly immutable 2-dimensional matrix designed to properly support several writable subclasses.
+ * + * Note:Subclass implementers, you should only use the protected fields in this class to + * store the contents of the matrix components, otherwise all of the methods here and in the + * operations class may break. + *
+ *+ * The basic data fields on this class are marked as protected to allow direct access to them + * through subclassing. This will get around any performance issues that one may have in utilizing + * this matrix arithmetic toolkit due to the enforcement of access to the component values through + * accessor methods. + *
+ *+ * Note, the equals and hashcode implementations in this class support proper comparisons between + * subclasses of this class and this class. The reason this works is because by design the only + * member variables of this class live in the parent class. If one subclasses this class and defines + * additional members then this will most certainly break the implementation presented here. + *
+ *
+ * The protected fields in this matrix are arranged in the following manner:
+ *
| ii | + *ij | + *
| ji | + *jj | > + *
+ * The values from the data array are copied into the matrix as follows:
+ *
| data[0][0] | + *data[0][1] | + *
| data[1][0] | + *data[1][1] | + *
+ * If this method is invoked on matrices whose columns are not orthogonal, the resultant matrix is + * likely not the inverse sought. Use the more general {@link UnwritableMatrixIJ#createInverse()} + * method instead. + *
+ * + * @return a newly created matrix, that is the inverse of this matrix if it meets the + * orthogonality condition + * + * @throws UnsupportedOperationException if the lengths of any of the columns are zero or too + * small to properly invert multiplicatively in the space available to double precision. + */ + public UnwritableMatrixIJ createInvorted() { + + /* + * First create the transpose, then all that's left is to scale the rows appropriately. + */ + UnwritableMatrixIJ matrix = this.createTranspose(); + + double length = computeNorm(matrix.ii, matrix.ij); + + if ((length * INVORSION_BOUND < 1) || (length == 0)) { + throw new UnsupportedOperationException( + "ith column of matrix has length, " + length + ", for which there is no inverse."); + } + + matrix.ii /= length; + matrix.ii /= length; + matrix.ij /= length; + matrix.ij /= length; + + length = computeNorm(matrix.ji, matrix.jj); + + if ((length * INVORSION_BOUND < 1) || (length == 0)) { + throw new UnsupportedOperationException( + "jth column of matrix has length, " + length + ", for which there is no inverse."); + } + + matrix.ji /= length; + matrix.ji /= length; + matrix.jj /= length; + matrix.jj /= length; + + return matrix; + } + + /** + * Gets the ith row, ith column component. + *
+ *
+ *
| ii | + *ij | + *
| ji | + *jj | + *
+ *
+ *
| ii | + *ij | + *
| ji | + *jj | + *
+ *
+ *
| ii | + *ij | + *
| ji | + *jj | + *
+ *
+ *
| ii | + *ij | + *
| ji | + *jj | + *
+ *
+ *
| ii (0,0) | + *ij (0,1) | + *
| ji (1,0) | + *jj (1,1) | + *
VectorIJ containing the result.
+ *
+ * @see mxv
+ */
+ public VectorIJ mxv(UnwritableVectorIJ v) {
+ return mxv(v, new VectorIJ());
+ }
+
+ /**
+ * Compute the product of this matrix with a vector.
+ *
+ * @param v the vector
+ * @param buffer the buffer to receive the product, m*v.
+ *
+ * @return a reference to buffer for convenience.
+ */
+ public VectorIJ mxv(UnwritableVectorIJ v, VectorIJ buffer) {
+
+ double i = ii * v.i + ij * v.j;
+ double j = ji * v.i + jj * v.j;
+ buffer.i = i;
+ buffer.j = j;
+ return buffer;
+ }
+
+ /**
+ * Compute the product of the transpose of a matrix with a vector.
+ *
+ * @param v the vector
+ *
+ * @return a new VectorIJ containing the result.
+ *
+ * @see mtxv
+ */
+ public VectorIJ mtxv(UnwritableVectorIJ v) {
+ return mtxv(v, new VectorIJ());
+ }
+
+ /**
+ * Compute the product of the transpose of a matrix with a vector.
+ *
+ * @param v the vector
+ * @param buffer the buffer to receive the product, transpose(m)*v
+ *
+ * @return a reference to buffer for convenience.
+ */
+ public VectorIJ mtxv(UnwritableVectorIJ v, VectorIJ buffer) {
+
+ double i = ii * v.i + ji * v.j;
+ double j = ij * v.i + jj * v.j;
+ buffer.i = i;
+ buffer.j = j;
+ return buffer;
+ }
+
+ /**
+ * Makes an unwritable copy of the supplied matrix.
+ * + * This method makes an unwritable copy only if necessary. It tries to avoid making a copy + * wherever possible. + *
+ * + * @param matrix a matrix to copy. + * + * @return either a reference to matrix (if matrix is already only an instance of + * {@link UnwritableMatrixIJ}, otherwise an unwritable copy of matrix's contents + */ + public static UnwritableMatrixIJ copyOf(UnwritableMatrixIJ matrix) { + if (matrix.getClass().equals(UnwritableMatrixIJ.class)) { + return matrix; + } + return new UnwritableMatrixIJ(matrix); + } + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(ii); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(ij); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(ji); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(jj); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof UnwritableMatrixIJ)) { + return false; + } + final UnwritableMatrixIJ other = (UnwritableMatrixIJ) obj; + if (Double.doubleToLongBits(ii) != Double.doubleToLongBits(other.ii)) { + return false; + } + if (Double.doubleToLongBits(ij) != Double.doubleToLongBits(other.ij)) { + return false; + } + if (Double.doubleToLongBits(ji) != Double.doubleToLongBits(other.ji)) { + return false; + } + if (Double.doubleToLongBits(jj) != Double.doubleToLongBits(other.jj)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "[" + ii + "," + ji + ";" + ij + "," + jj + "]"; + } + +} diff --git a/src/main/java/picante/math/vectorspace/UnwritableMatrixIJK.java b/src/main/java/picante/math/vectorspace/UnwritableMatrixIJK.java new file mode 100644 index 0000000..779c260 --- /dev/null +++ b/src/main/java/picante/math/vectorspace/UnwritableMatrixIJK.java @@ -0,0 +1,1166 @@ +package picante.math.vectorspace; + +import static picante.math.PicanteMath.abs; +import static picante.math.vectorspace.InternalOperations.computeDeterminant; +import static picante.math.vectorspace.InternalOperations.computeNorm; + +/** + * A weakly immutable 3-dimensional matrix designed to properly support several writable subclasses. + *+ * Note:Subclass implementers, you should only use the protected fields in this class to + * store the contents of the matrix components, otherwise all of the methods here and in the + * operations class may break. + *
+ *+ * The basic data fields on this class are marked as protected to allow direct access to them + * through subclassing. This will get around any performance issues that one may have in utilizing + * this matrix arithmetic toolkit due to the enforcement of access to the component values through + * accessor methods. + *
+ *+ * Note, the equals and hashcode implementations in this class support proper comparisons between + * subclasses of this class and this class. The reason this works is because by design the only + * member variables of this class live in the parent class. If one subclasses this class and defines + * additional members then this will most certainly break the implementation presented here. + *
+ *
+ * The protected fields in this matrix are arranged in the following manner:
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ * The values from the data array are copied into the matrix as follows:
+ *
| data[0][0] | + *data[0][1] | + *data[0][2] | + *
| data[1][0] | + *data[1][1] | + *data[1][2] | + *
| data[2][0] | + *data[2][1] | + *data[2][2] | + *
+ * If this method is invoked on matrices whose columns are not orthogonal, the resultant matrix is + * likely not the inverse sought. Use the more general {@link UnwritableMatrixIJK#createInverse()} + * method instead. + *
+ * + * @return a newly created matrix, that is the inverse of this matrix if it meets the + * orthogonality condition + * + * @throws UnsupportedOperationException if the lengths of any of the columns are zero or too + * small to properly invert multiplicatively in the space available to double precision. + */ + public UnwritableMatrixIJK createInvorted() { + + /* + * First create the transpose, then all that's left is to scale the rows appropriately. + */ + UnwritableMatrixIJK matrix = this.createTranspose(); + + double length = computeNorm(matrix.ii, matrix.ij, matrix.ik); + + if ((length * INVORSION_BOUND < 1) || (length == 0)) { + throw new UnsupportedOperationException( + "ith column of matrix has length, " + length + ", for which there is no inverse."); + } + + matrix.ii /= length; + matrix.ii /= length; + matrix.ij /= length; + matrix.ij /= length; + matrix.ik /= length; + matrix.ik /= length; + + length = computeNorm(matrix.ji, matrix.jj, matrix.jk); + + if ((length * INVORSION_BOUND < 1) || (length == 0)) { + throw new UnsupportedOperationException( + "jth column of matrix has length, " + length + ", for which there is no inverse."); + } + + matrix.ji /= length; + matrix.ji /= length; + matrix.jj /= length; + matrix.jj /= length; + matrix.jk /= length; + matrix.jk /= length; + + length = computeNorm(matrix.ki, matrix.kj, matrix.kk); + + if ((length * INVORSION_BOUND < 1) || (length == 0)) { + throw new UnsupportedOperationException( + "kth column of matrix has length, " + length + ", for which there is no inverse."); + } + + matrix.ki /= length; + matrix.ki /= length; + matrix.kj /= length; + matrix.kj /= length; + matrix.kk /= length; + matrix.kk /= length; + + return matrix; + } + + /** + * Gets the ith row, ith column component. + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii | + *ij | + *ik | + *
| ji | + *jj | + *jk | + *
| ki | + *kj | + *kk | + *
+ *
+ *
| ii (0,0) | + *ij (0,1) | + *ik (0,2) | + *
| ji (1,0) | + *jj (1,1) | + *jk (1,2) | + *
| ki (2,0) | + *kj (2,1) | + *kk (2,2) | + *
inversionTolerance?
+ *
+ * @param inversionTolerance specifies how far off zero the determinant of the instance is allowed
+ * to be
+ *
+ * @return true if the matrix is invertible, false otherwise
+ */
+ public boolean isInvertible(double inversionTolerance) {
+ double absDet = abs(getDeterminant());
+ if (absDet < inversionTolerance) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Are the columns of this matrix orthogonal so that the determinant (after columns have been
+ * unitized) is equal ±1 within a tolerance of {@value #DETERMINANT_TOLERANCE}?
+ * + * If this returns true, this matrix may be more efficiently inverted using + * {@link #createInvorted()}. + *
+ * Otherwise, this matrix may only be inverted (assuming its determinant is non-zero) using
+ * {@link #createInverse()}.
+ *
+ * @return true if the matrix columns are orthonormal, false otherwise
+ */
+ public boolean hasOrthogonalColumns() {
+ return hasOrthogonalColumns(DETERMINANT_TOLERANCE);
+ }
+
+ /**
+ * Are the columns of this matrix orthogonal so that the determinant (after columns have been
+ * unitized) is equal ±1 within a tolerance of determinantTolerance?
+ *
+ * If this returns true, this matrix may be more efficiently inverted using + * {@link #createInvorted()}. + *
+ * Otherwise, this matrix may only be inverted (assuming its determinant is non-zero) using
+ * {@link #createInverse()}.
+ *
+ * @param determinantTolerance specifies how far off unity the determinant of the instance (after
+ * columns have been unitized) is allowed to be
+ *
+ * @return true if the matrix columns are orthogonal, false otherwise
+ */
+ public boolean hasOrthogonalColumns(double determinantTolerance) {
+ return createUnitizedColumns().isOrthogonal(determinantTolerance);
+ }
+
+ /**
+ * Are the columns of this matrix orthonormal so that the determinant is equal
+ * ±1 within a tolerance of {@value #DETERMINANT_TOLERANCE}?
+ *
+ * @return true if the matrix columns are orthonormal, false otherwise
+ */
+ public boolean isOrthogonal() {
+ return isOrthogonal(DETERMINANT_TOLERANCE);
+ }
+
+ /**
+ * Are the columns of this matrix orthonormal so that the determinant is equal
+ * ±1 within the tolerance specified by determinantTolerance?
+ *
+ * @param determinantTolerance specifies how far off unity the determinant of the instance is
+ * allowed to be
+ *
+ * @return true if the matrix columns are orthonormal, false otherwise
+ */
+ public boolean isOrthogonal(double determinantTolerance) {
+ double absErr = abs(abs(getDeterminant()) - 1);
+ if (absErr > determinantTolerance) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Do the components of the instance represent a rotation subject to the default norm
+ * {@link #NORM_TOLERANCE} and determinant {@value #DETERMINANT_TOLERANCE} tolerances.
+ *
+ * @return true if the matrix components capture a rotation, false otherwise
+ */
+ public boolean isRotation() {
+ return isRotation(NORM_TOLERANCE, DETERMINANT_TOLERANCE);
+ }
+
+ /**
+ * Do the components of the instance represent a rotation subject to the supplied tolerances
+ *
+ * @param normTolerance specifies how far off unity the norms of the column vectors are allowed to
+ * be
+ * @param determinantTolerance specifies how far off unity the determinant of the instance is
+ * allowed to be
+ *
+ * @return true if the matrix components capture a rotation, false otherwise
+ */
+ public boolean isRotation(double normTolerance, double determinantTolerance) {
+ try {
+ InternalOperations.checkRotation(ii, ji, ki, ij, jj, kj, ik, jk, kk, normTolerance,
+ determinantTolerance);
+ return true;
+ } catch (MalformedRotationException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Compute the product of this matrix with a vector.
+ *
+ * @param v the vector
+ *
+ * @return a new VectorIJK containing the result.
+ *
+ * @see UnwritableMatrixIJK#mxv(UnwritableVectorIJK, VectorIJK)
+ */
+ public VectorIJK mxv(UnwritableVectorIJK v) {
+ return mxv(v, new VectorIJK());
+ }
+
+ /**
+ * Compute the product of this matrix with a vector.
+ *
+ * @param v the vector
+ * @param buffer the buffer to receive the product, m*v.
+ *
+ * @return a reference to buffer for convenience.
+ */
+ public VectorIJK mxv(UnwritableVectorIJK v, VectorIJK buffer) {
+
+ double i = ii * v.i + ij * v.j + ik * v.k;
+ double j = ji * v.i + jj * v.j + jk * v.k;
+ buffer.k = ki * v.i + kj * v.j + kk * v.k;
+ buffer.i = i;
+ buffer.j = j;
+ return buffer;
+ }
+
+ /**
+ * Compute the product of the transpose of a matrix with a vector.
+ *
+ * @param v the vector
+ *
+ * @return a new VectorIJK containing the result.
+ *
+ * @see UnwritableMatrixIJK#mtxv(UnwritableVectorIJK, VectorIJK)
+ */
+ public VectorIJK mtxv(UnwritableVectorIJK v) {
+ return mtxv(v, new VectorIJK());
+ }
+
+ /**
+ * Compute the product of the transpose of a matrix with a vector.
+ *
+ * @param v the vector
+ * @param buffer the buffer to receive the product, transpose(m)*v
+ *
+ * @return a reference to buffer for convenience.
+ */
+ public VectorIJK mtxv(UnwritableVectorIJK v, VectorIJK buffer) {
+
+ double i = ii * v.i + ji * v.j + ki * v.k;
+ double j = ij * v.i + jj * v.j + kj * v.k;
+ buffer.k = ik * v.i + jk * v.j + kk * v.k;
+ buffer.i = i;
+ buffer.j = j;
+ return buffer;
+ }
+
+ /**
+ * Makes an unwritable copy of the supplied matrix.
+ *
+ * This method makes an unwritable copy only if necessary. It tries to avoid making a copy + * wherever possible. + *
+ * + * @param matrix a matrix to copy. + * + * @return either a reference to matrix (if matrix is already only an instance of + * {@link UnwritableMatrixIJK}, otherwise an unwritable copy of matrix's contents + */ + public static UnwritableMatrixIJK copyOf(UnwritableMatrixIJK matrix) { + if (matrix.getClass().equals(UnwritableMatrixIJK.class)) { + return matrix; + } + return new UnwritableMatrixIJK(matrix); + } + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(ii); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(ij); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(ik); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(ji); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(jj); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(jk); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(ki); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(kj); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(kk); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof UnwritableMatrixIJK)) { + return false; + } + final UnwritableMatrixIJK other = (UnwritableMatrixIJK) obj; + if (Double.doubleToLongBits(ii) != Double.doubleToLongBits(other.ii)) { + return false; + } + if (Double.doubleToLongBits(ij) != Double.doubleToLongBits(other.ij)) { + return false; + } + if (Double.doubleToLongBits(ik) != Double.doubleToLongBits(other.ik)) { + return false; + } + if (Double.doubleToLongBits(ji) != Double.doubleToLongBits(other.ji)) { + return false; + } + if (Double.doubleToLongBits(jj) != Double.doubleToLongBits(other.jj)) { + return false; + } + if (Double.doubleToLongBits(jk) != Double.doubleToLongBits(other.jk)) { + return false; + } + if (Double.doubleToLongBits(ki) != Double.doubleToLongBits(other.ki)) { + return false; + } + if (Double.doubleToLongBits(kj) != Double.doubleToLongBits(other.kj)) { + return false; + } + if (Double.doubleToLongBits(kk) != Double.doubleToLongBits(other.kk)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "[" + ii + "," + ji + "," + ki + ";" + ij + "," + jj + "," + kj + ";" + ik + "," + jk + + "," + kk + "]"; + } + +} diff --git a/src/main/java/picante/math/vectorspace/UnwritableRotationMatrixIJK.java b/src/main/java/picante/math/vectorspace/UnwritableRotationMatrixIJK.java new file mode 100644 index 0000000..1bae1be --- /dev/null +++ b/src/main/java/picante/math/vectorspace/UnwritableRotationMatrixIJK.java @@ -0,0 +1,332 @@ +package picante.math.vectorspace; + +import static picante.math.vectorspace.InternalOperations.computeNorm; + +/** + * A weakly immutable extension of the unwritable matrix class designed to add rotation matrix + * specific functionality. + *+ * Note: The constructors of this class that take arguments that may specify matrices which + * are not rotations, validate the supplied input to ensure that it is sufficiently close to a + * rotation. If it passes the check, the content are not modified to bring it closer to a + * rotation. See {@link #createSharpened()} for details on how to do this. The check invoked by this + * routine is consistent with {@link UnwritableMatrixIJK#isRotation()}. If you do not desire this + * particular behavior, then simply subclass this class or the provided writable subclass to suit + * your needs. + *
+ *+ * Another point worth making about this class and its writable subclass, they derive their + * definition of equals and hashcode from the top level unwritable matrix class. The consequence of + * this is that only the components of the actual matrix itself are used in the comparision. As + * such, it is possible to construct a matrix that is equivalent to a rotation matrix and vice + * versa. It was decided at the time these classes were created that this would be the appropriate + * default or desired behavior. If you have a specific need to treat matrices that have equal + * components but live in different class representations in this inheritance hierarchy, then it + * would be best to place them in containers and define equality on the containers. In practice, + * this should not be necessary. + *
+ */ +public class UnwritableRotationMatrixIJK extends UnwritableMatrixIJK { + + /** + * Method that validate supplied input content is sufficiently close to a rotation matrix. + * + * @param matrix the matrix content from the parent class to validate + * + * @throws MalformedRotationException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + protected static void checkRotation(UnwritableMatrixIJK matrix) + throws MalformedRotationException { + InternalOperations.checkRotation(matrix.ii, matrix.ji, matrix.ki, matrix.ij, matrix.jj, + matrix.kj, matrix.ik, matrix.jk, matrix.kk, NORM_TOLERANCE, DETERMINANT_TOLERANCE); + } + + /** + * Protected no argument, no operation constructor for subclasses to utilize. + */ + protected UnwritableRotationMatrixIJK() { + super(); + } + + /** + * Constructs a rotation matrix from the supplied double values. + * + * @param ii ith row, ith column element + * @param ji jth row, ith column element + * @param ki kth row, ith column element + * @param ij ith row, jth column element + * @param jj jth row, jth column element + * @param kj kth row, jth column element + * @param ik ith row, kth column element + * @param jk jth row, kth column element + * @param kk kth row, kth column element + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + * + */ + public UnwritableRotationMatrixIJK(double ii, double ji, double ki, double ij, double jj, + double kj, double ik, double jk, double kk) { + super(ii, ji, ki, ij, jj, kj, ik, jk, kk); + try { + checkRotation(this); + } catch (MalformedRotationException e) { + throw new IllegalArgumentException("Matrix components do not describe a rotation.", e); + } + } + + /** + * Constructs a matrix from the upper three by three block of a two dimensional array of doubles. + * + *
+ * The values from the data array are copied into the matrix as follows:
+ *
| data[0][0] | + *data[0][1] | + *data[0][2] | + *
| data[1][0] | + *data[1][1] | + *data[1][2] | + *
| data[2][0] | + *data[2][1] | + *data[2][2] | + *
+ * Note:This constructor performs no validation on the input by design. + *
+ * + * @param matrix the rotation matrix whose contents are to be copied. + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public UnwritableRotationMatrixIJK(UnwritableMatrixIJK matrix) { + this(matrix.ii, matrix.ji, matrix.ki, matrix.ij, matrix.jj, matrix.kj, matrix.ik, matrix.jk, + matrix.kk); + } + + /** + * Scaling constructor, creates a new matrix by applying a scalar multiple to the components of a + * pre-existing matrix. + * + * @param scale the scale factor to apply + * @param matrix the matrix whose components are to be scaled and copied. + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public UnwritableRotationMatrixIJK(double scale, UnwritableMatrixIJK matrix) { + this(scale * matrix.ii, scale * matrix.ji, scale * matrix.ki, scale * matrix.ij, + scale * matrix.jj, scale * matrix.kj, scale * matrix.ik, scale * matrix.jk, + scale * matrix.kk); + } + + /** + * Column scaling constructor, creates a new rotation matrix by applying scalar multiples to the + * columns of a pre-existing matrix. + * + * @param scaleI scale factor to apply to the ith column + * @param scaleJ scale factor to apply to the jth column + * @param scaleK scale factor to apply to the kth column + * @param matrix the matrix whose components are to be scaled and copied + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public UnwritableRotationMatrixIJK(double scaleI, double scaleJ, double scaleK, + UnwritableMatrixIJK matrix) { + this(scaleI * matrix.ii, scaleI * matrix.ji, scaleI * matrix.ki, scaleJ * matrix.ij, + scaleJ * matrix.jj, scaleJ * matrix.kj, scaleK * matrix.ik, scaleK * matrix.jk, + scaleK * matrix.kk); + } + + /** + * Column vector constructor, creates a new matrix by populating the columns of the rotation + * matrix with the supplied vectors. + * + * @param ithColumn the vector containing the ith column + * @param jthColumn the vector containing the jth column + * @param kthColumn the vector containing the kth column + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public UnwritableRotationMatrixIJK(UnwritableVectorIJK ithColumn, UnwritableVectorIJK jthColumn, + UnwritableVectorIJK kthColumn) { + this(ithColumn.i, ithColumn.j, ithColumn.k, jthColumn.i, jthColumn.j, jthColumn.k, kthColumn.i, + kthColumn.j, kthColumn.k); + } + + /** + * Scaled column vector constructor, creates a new rotation matrix by populating the columns of + * the matrix with scaled versions of the supplied vectors + * + * @param scaleI the scale factor to apply to the ith column + * @param ithColumn the vector containing the ith column + * @param scaleJ the scale factor to apply to the jth column + * @param jthColumn the vector containing the jth column + * @param scaleK the scale factor to apply to the kth column + * @param kthColumn the vector containing the kth column + * + * @throws IllegalArgumentException if either the columns of the supplied matrix have norms that + * are not within {@link UnwritableMatrixIJK#NORM_TOLERANCE} or if the determinant is not + * within {@link UnwritableMatrixIJK#DETERMINANT_TOLERANCE}. + */ + public UnwritableRotationMatrixIJK(double scaleI, UnwritableVectorIJK ithColumn, double scaleJ, + UnwritableVectorIJK jthColumn, double scaleK, UnwritableVectorIJK kthColumn) { + this(scaleI * ithColumn.i, scaleI * ithColumn.j, scaleI * ithColumn.k, scaleJ * jthColumn.i, + scaleJ * jthColumn.j, scaleJ * jthColumn.k, scaleK * kthColumn.i, scaleK * kthColumn.j, + scaleK * kthColumn.k); + } + + /** + * Creates a new, sharpened copy of the existing rotation matrix. + *+ * Sharpening is a process that starts with a rotation matrix and modifies its contents to bring + * it as close to a rotation as possible given the limits of floating point precision in the + * implementation. There are many possible rotation matrices that are "sharpenings" of + * the general rotation matrix. As such, the implementation is unspecified here. The only claims + * this method makes are that the resultant matrix is as close or closer to a rotation than what + * you start with. + *
+ * + * @return the sharpened version of the instance. + */ + public UnwritableRotationMatrixIJK createSharpened() { + + UnwritableRotationMatrixIJK result = new UnwritableRotationMatrixIJK(this); + + /* + * Normalize the first column vector of the matrix. + */ + double norm = computeNorm(result.ii, result.ji, result.ki); + result.ii /= norm; + result.ji /= norm; + result.ki /= norm; + + /* + * Define the third column of the matrix as the cross product of the first with the second. + */ + result.ik = result.ji * result.kj - result.ki * result.jj; + result.jk = result.ki * result.ij - result.ii * result.kj; + result.kk = result.ii * result.jj - result.ji * result.ij; + + /* + * Normalize the result. + */ + norm = computeNorm(result.ik, result.jk, result.kk); + result.ik /= norm; + result.jk /= norm; + result.kk /= norm; + + /* + * Lastly, cross the third vector with the first to replace the second. + */ + result.ij = result.jk * result.ki - result.kk * result.ji; + result.jj = result.kk * result.ii - result.ik * result.ki; + result.kj = result.ik * result.ji - result.jk * result.ii; + + norm = computeNorm(result.ij, result.jj, result.kj); + result.ij /= norm; + result.jj /= norm; + result.kj /= norm; + + return result; + } + + /** + * {@inheritDoc} + * + * Note: this method is overridden to return an instance of the unwritable rotation subclass + * rather than the unwritable plain matrix parent. + */ + @Override + public UnwritableRotationMatrixIJK createTranspose() { + return new UnwritableRotationMatrixIJK(this.ii, this.ij, this.ik, this.ji, this.jj, this.jk, + this.ki, this.kj, this.kk); + } + + /** + * {@inheritDoc} + * + * Note: this method is overridden to return an instance of the unwritable rotation subclass + * rather than the unwritable plain matrix parent. + */ + @Override + public UnwritableRotationMatrixIJK createInverse() { + /* + * Matrix inversion in the special case of rotation matrices is transposition. + */ + return createTranspose(); + } + + /** + * {@inheritDoc} + * + * Note: this method is overridden to return an instance of the unwritable rotation subclass + * rather than the unwritable plain matrix parent. + */ + @Override + public UnwritableRotationMatrixIJK createInverse(@SuppressWarnings("unused") double tolerance) { + return createTranspose(); + } + + /** + * Makes an unwritable copy of the supplied rotation matrix. + *+ * This method makes an unwritable copy only if necessary. It tries to avoid making a copy + * wherever possible. + *
+ * + * @param matrix an matrix to copy. + * + * @return either a reference to matrix (if matrix is already only an instance of + * {@link UnwritableMatrixIJK}, otherwise an unwritable copy of matrix's contents + */ + public static UnwritableRotationMatrixIJK copyOf(UnwritableRotationMatrixIJK matrix) { + if (matrix.getClass().equals(UnwritableRotationMatrixIJK.class)) { + return matrix; + } + return new UnwritableRotationMatrixIJK(matrix); + } +} diff --git a/src/main/java/picante/math/vectorspace/UnwritableVectorIJ.java b/src/main/java/picante/math/vectorspace/UnwritableVectorIJ.java new file mode 100644 index 0000000..4bb3e54 --- /dev/null +++ b/src/main/java/picante/math/vectorspace/UnwritableVectorIJ.java @@ -0,0 +1,292 @@ +package picante.math.vectorspace; + +import static com.google.common.base.Preconditions.checkElementIndex; +import static picante.math.PicanteMath.PI; +import static picante.math.PicanteMath.asin; +import static picante.math.vectorspace.InternalOperations.computeNorm; +import static picante.units.FundamentalPhysicalConstants.HALFPI; +import picante.exceptions.BugException; + +/** + * A weakly immutable 2-dimensional vector designed to properly support a writable subclass. + *+ * Note:Subclass implementers, you should only use the protected fields in this class to + * store the contents of the vector components, otherwise all of the methods here and in the + * operations class may break. + *
+ *+ * The basic data fields on this class are marked as protected to allow direct access to them + * through subclassing. This will get around any performance issues that one may have in using this + * vector arithmetic toolkit due to the enforcement of access to the component values through + * accessor methods. + *
+ *+ * Note, the equals and hashcode implementations in this class support proper comparisons between + * subclasses of this class and this class. The reason this works, is because by design the only + * member variables of this class live in the parent class. If one subclasses this class and defines + * additional members then this will most certainly break the implementation presented here. + *
+ * + * This was a simple copy and paste of the {@link UnwritableVectorIJK} class. + * + * @author G.K.Stephens + */ +public class UnwritableVectorIJ { + + /** + * The ith component of the vector, synonymous with the "X-axis". + */ + protected double i; + + /** + * The jth component of the vector, synonymous with the "Y-axis". + */ + protected double j; + + /** + * Constructs a vector from the two basic components. + * + * @param i the ith component + * @param j the jth component + */ + public UnwritableVectorIJ(double i, double j) { + super(); + this.i = i; + this.j = j; + } + + /** + * Constructs a vector from the first two elements of an array of doubles. + * + * @param data the array of doubles + * + * @throws IndexOutOfBoundsException if the supplied data array does not contain at least two + * elements + */ + public UnwritableVectorIJ(double[] data) { + this(data[0], data[1]); + } + + /** + * Constructs a vector from the two elements of an array of double starting with the offset index. + * + * @param offset index into the data array to copy into the ith component. + * + * @param data the array of doubles. + * + * @throws IndexOutOfBoundsException if the supplied data array does not contain two elements at + * indices offset through offset + 2 + */ + public UnwritableVectorIJ(int offset, double[] data) { + this(data[offset], data[offset + 1]); + } + + /** + * Copy constructor, creates a vector by copying the values of a pre-existing one. + * + * @param vector the vector whose contents are to be copied + */ + public UnwritableVectorIJ(UnwritableVectorIJ vector) { + this(vector.i, vector.j); + } + + /** + * Scaling constructor, creates a new vector by applying a scalar multiple to the components of a + * pre-existing vector. + * + * @param scale the scale factor to apply + * @param vector the vector whose contents are to be scaled + */ + public UnwritableVectorIJ(double scale, UnwritableVectorIJ vector) { + this(scale * vector.i, scale * vector.j); + } + + /** + * Creates a new, unit length copy of the existing vector. + *
+ * This code is just a convenience method that implements:
+ * new UnwritableVectorIJK(1.0/this.getLength(), this) in a safe manner.
+ *
+ * Convenience method for: new UnwritableVectorIJK(-1.0, this).
+ *
+ * This method makes an unwritable copy only if necessary. It tries to avoid making a copy + * wherever possible. + *
+ * + * @param vector a vector to copy. + * + * @return either a reference to vector (if vector is already only an instance of + * {@link UnwritableVectorIJ}, otherwise an unwritable copy of vector's contents + */ + public static UnwritableVectorIJ copyOf(UnwritableVectorIJ vector) { + if (vector.getClass().equals(UnwritableVectorIJ.class)) { + return vector; + } + return new UnwritableVectorIJ(vector); + } + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(i); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(j); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof UnwritableVectorIJ)) { + return false; + } + final UnwritableVectorIJ other = (UnwritableVectorIJ) obj; + if (Double.doubleToLongBits(i) != Double.doubleToLongBits(other.i)) { + return false; + } + if (Double.doubleToLongBits(j) != Double.doubleToLongBits(other.j)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "[" + i + "," + j + "]"; + } + +} diff --git a/src/main/java/picante/math/vectorspace/UnwritableVectorIJK.java b/src/main/java/picante/math/vectorspace/UnwritableVectorIJK.java new file mode 100644 index 0000000..56a4690 --- /dev/null +++ b/src/main/java/picante/math/vectorspace/UnwritableVectorIJK.java @@ -0,0 +1,375 @@ +package picante.math.vectorspace; + +import static com.google.common.base.Preconditions.checkElementIndex; +import static picante.math.PicanteMath.PI; +import static picante.math.PicanteMath.asin; +import static picante.math.vectorspace.InternalOperations.computeNorm; +import picante.exceptions.BugException; +import picante.units.FundamentalPhysicalConstants; + +/** + * A weakly immutable 3-dimensional vector designed to properly support a writable subclass. + *+ * Note:Subclass implementers, you should only use the protected fields in this class to + * store the contents of the vector components, otherwise all of the methods here and in the + * operations class may break. + *
+ *+ * The basic data fields on this class are marked as protected to allow direct access to them + * through subclassing. This will get around any performance issues that one may have in using this + * vector arithmetic toolkit due to the enforcement of access to the component values through + * accessor methods. + *
+ *+ * Note, the equals and hashcode implementations in this class support proper comparisons between + * subclasses of this class and this class. The reason this works, is because by design the only + * member variables of this class live in the parent class. If one subclasses this class and defines + * additional members then this will most certainly break the implementation presented here. + *
+ */ +public class UnwritableVectorIJK { + + /** + * The ith component of the vector, synonymous with the "X-axis". + */ + protected double i; + + /** + * The jth component of the vector, synonymous with the "Y-axis". + */ + protected double j; + + /** + * The kth component of the vector, synonymous with the "Z-axis". + */ + protected double k; + + /** + * Constructs a vector from the three basic components. + * + * @param i the ith component + * @param j the jth component + * @param k the kth component + */ + public UnwritableVectorIJK(double i, double j, double k) { + super(); + this.i = i; + this.j = j; + this.k = k; + } + + /** + * Constructs a vector from the first three elements of an array of doubles. + * + * @param data the array of doubles + * + * @throws IndexOutOfBoundsException if the supplied data array does not contain at least three + * elements + */ + public UnwritableVectorIJK(double[] data) { + this(data[0], data[1], data[2]); + } + + /** + * Constructs a vector from the three elements of an array of double starting with the offset + * index. + * + * @param offset index into the data array to copy into the ith component. + * + * @param data the array of doubles. + * + * @throws IndexOutOfBoundsException if the supplied data array does not contain three elements at + * indices offset through offset + 2 + */ + public UnwritableVectorIJK(int offset, double[] data) { + this(data[offset], data[offset + 1], data[offset + 2]); + } + + /** + * Copy constructor, creates a vector by copying the values of a pre-existing one. + * + * @param vector the vector whose contents are to be copied + */ + public UnwritableVectorIJK(UnwritableVectorIJK vector) { + this(vector.i, vector.j, vector.k); + } + + /** + * Scaling constructor, creates a new vector by applying a scalar multiple to the components of a + * pre-existing vector. + * + * @param scale the scale factor to apply + * @param vector the vector whose contents are to be scaled + */ + public UnwritableVectorIJK(double scale, UnwritableVectorIJK vector) { + this(scale * vector.i, scale * vector.j, scale * vector.k); + } + + /** + * Creates a new, unit length copy of the existing vector. + *
+ * This code is just a convenience method that implements:
+ * new UnwritableVectorIJK(1.0/this.getLength(), this) in a safe manner.
+ *
+ * Convenience method for: new UnwritableVectorIJK(-1.0, this).
+ *
+ * Convenience method for: new UnwritableVectorIJK(scale, this).
+ *
+ * Note: this really is simply {@link FundamentalPhysicalConstants#HALFPI} - + * {@link UnwritableVectorIJK#getSeparation(UnwritableVectorIJK)}, but is useful as a convenience + * method. + *
+ * + * @param normal the normal to the plane + * + * @return the angular separation of this vector and the plane with the specified normal. Positive + * values lie on the same side as normal, negative on the other. + */ + public double getSeparationOutOfPlane(UnwritableVectorIJK normal) { + return FundamentalPhysicalConstants.HALFPI - getSeparation(normal); + } + + /** + * Compute the distance between this instance and another vector. + * + * @param vector + * + * @return the distance between vector and this instance in radians. + */ + public double getDistance(UnwritableVectorIJK vector) { + return VectorIJK.subtract(this, vector).getLength(); + } + + + /** + * Compute the minimum value in this vector + * + * @return the min value + */ + public double min() { + return Math.min(i, Math.min(j, k)); + } + + /** + * Compute the maximum value in this vector + * + * @return the min value + */ + public double max() { + return Math.max(i, Math.max(j, k)); + } + + /** + * Makes an unwritable copy of the supplied vector. + *+ * This method makes an unwritable copy only if necessary. It tries to avoid making a copy + * wherever possible. + *
+ * + * @param vector a vector to copy. + * + * @return either a reference to vector (if vector is already only an instance of + * {@link UnwritableVectorIJK}, otherwise an unwritable copy of vector's contents + */ + public static UnwritableVectorIJK copyOf(UnwritableVectorIJK vector) { + if (vector.getClass().equals(UnwritableVectorIJK.class)) { + return vector; + } + return new UnwritableVectorIJK(vector); + } + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(i); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(j); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(k); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof UnwritableVectorIJK)) { + return false; + } + final UnwritableVectorIJK other = (UnwritableVectorIJK) obj; + if (Double.doubleToLongBits(i) != Double.doubleToLongBits(other.i)) { + return false; + } + if (Double.doubleToLongBits(j) != Double.doubleToLongBits(other.j)) { + return false; + } + if (Double.doubleToLongBits(k) != Double.doubleToLongBits(other.k)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "[" + i + "," + j + "," + k + "]"; + } + +} diff --git a/src/main/java/picante/math/vectorspace/VectorIJ.java b/src/main/java/picante/math/vectorspace/VectorIJ.java new file mode 100644 index 0000000..52c41cc --- /dev/null +++ b/src/main/java/picante/math/vectorspace/VectorIJ.java @@ -0,0 +1,801 @@ +package picante.math.vectorspace; + +import static com.google.common.base.Preconditions.checkElementIndex; +import static picante.math.vectorspace.InternalOperations.absMaxComponent; +import static picante.math.vectorspace.InternalOperations.computeNorm; +import picante.designpatterns.Writable; +import picante.exceptions.BugException; + +/** + * Writable subclass of the unwritable 2D vector parent completing the implementation of the weak + * immutability design pattern. + *+ * This class contains the mutator methods necessary to set or alter the internals of the parent + * classes fields. + *
+ * + * This was a simple copy and paste of the {@link VectorIJK} class. The cross product methods return + * a {@link VectorIJK}. + * + * TODO add rotation methods if when needed + * + * @author G.K.Stephens copy + */ +public class VectorIJ extends UnwritableVectorIJ + implements Writable.ImplementationInterfaceVectorIJ containing the results of the projection
+ *
+ * @throws IllegalArgumentException if normal is equal to {@link ZERO}
+ *
+ * @see lineProject
+ */
+ public static VectorIJ lineProject(UnwritableVectorIJ vector, UnwritableVectorIJ normal) {
+ return lineProject(vector, normal, new VectorIJ());
+ }
+
+ /**
+ * Compute the projection of one vector onto the line normal to another.
+ * + * Algebraicly, this routine effectively computes: + * + *
+ * <vector, to> * to + * vector - --------------------- + * || to || + *+ * + * where <,> denotes the standard scalar product and ||x|| the norm of x. For numeric + * precision reasons the implementation may vary slightly from the above prescription. + * + * + * @param vector the vector to project + * @param normal the normal to the line to project vector onto + * @param buffer the buffer to receive the contents of the projection + * + * @return a reference to buffer for convenience + * + * @throws IllegalArgumentException if normal is equal to {@link ZERO} + */ + public static VectorIJ lineProject(UnwritableVectorIJ vector, UnwritableVectorIJ normal, + VectorIJ buffer) { + + double maxVector = absMaxComponent(vector.i, vector.j); + + /* + * There are two unusual cases that require special treatment. The first is if the normal vector + * is the zero vector. Fortunately, the necessary exception is generated by the project() + * method. So, start by performing the necessary projection. Buffer the components of vector, in + * case buffer and vector are the same object. + */ + double vi = vector.i; + double vj = vector.j; + + project(vector, normal, buffer); + + /* + * The second unusual case is when vector itself is zero. This is simple enough, the zero vector + * projects as the zero vector. + */ + if (maxVector == 0.0) { + buffer.clear(); + return buffer; + } + + /* + * Scale buffer and the v components by 1.0/maxVector to bring them closer to similar + * magnitudes. + */ + vi /= maxVector; + vj /= maxVector; + buffer.i /= maxVector; + buffer.j /= maxVector; + + /* + * Subtract buffer from the v components to place the result in the line. + */ + buffer.i = vi - buffer.i; + buffer.j = vj - buffer.j; + + /* + * Rescale the result. + */ + buffer.scale(maxVector); + + return buffer; + + } + + /** + * Compute the projection of one vector onto another. + * + * @param vector the vector to project + * @param onto the vector onto which vector is to be projected + * + * @return a new
VectorIJ containing the results of the projection
+ *
+ * @throws IllegalArgumentException if onto is the equal to {@link ZERO}.
+ *
+ * @see project
+ */
+ public static VectorIJ project(UnwritableVectorIJ vector, UnwritableVectorIJ onto) {
+ return project(vector, onto, new VectorIJ());
+
+ }
+
+ /**
+ * Compute the projection of one vector onto another.
+ * + * Algebraicly, this routine effectively computes: + * + *
+ * <vector, onto> * onto + * --------------------- + * || onto || + *+ * + * where <,> denotes the standard scalar product and ||x|| the norm of x. For numeric + * precision reasons the implementation may vary slightly from the above prescription. + * + * + * @param vector the vector to project + * @param onto the vector onto which vector is to be projected + * @param buffer the buffer to receive the contents of the projection + * + * @return a reference to buffer for convenience + * + * @throws IllegalArgumentException if onto is the equal to {@link ZERO}. + */ + public static VectorIJ project(UnwritableVectorIJ vector, UnwritableVectorIJ onto, + VectorIJ buffer) { + + double maxVector = absMaxComponent(vector.i, vector.j); + double maxOnto = absMaxComponent(onto.i, onto.j); + + if (maxOnto == 0) { + throw new IllegalArgumentException("Unable to project vector onto the zero vector."); + } + + if (maxVector == 0) { + buffer.clear(); + return buffer; + } + + double r1 = onto.i / maxOnto; + double r2 = onto.j / maxOnto; + + double t1 = vector.i / maxVector; + double t2 = vector.j / maxVector; + + double scaleFactor = (t1 * r1 + t2 * r2) * maxVector / (r1 * r1 + r2 * r2); + + buffer.i = r1; + buffer.j = r2; + + buffer.scale(scaleFactor); + return buffer; + } + + /** + * Linearly combine three vectors. + * + * @param scaleA the scale factor for vector a + * @param a a vector + * @param scaleB the scale vector for vector b + * @param b another vector + * @param scaleC the scale factor for vector c + * @param c the third vector + * + * @return a new
VectorIJ which now contains ( scaleA*a + scaleB*b + scaleC*c )
+ *
+ * @see combine
+ */
+ public static VectorIJ combine(double scaleA, UnwritableVectorIJ a, double scaleB,
+ UnwritableVectorIJ b, double scaleC, UnwritableVectorIJ c) {
+ return combine(scaleA, a, scaleB, b, scaleC, c, new VectorIJ());
+ }
+
+ /**
+ * Linearly combine three vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param buffer the buffer to receive the results of the combination
+ *
+ * @return a reference to buffer for convenience which now contains ( scaleA*a + scaleB*b +
+ * scaleC*c )
+ */
+ public static VectorIJ combine(double scaleA, UnwritableVectorIJ a, double scaleB,
+ UnwritableVectorIJ b, double scaleC, UnwritableVectorIJ c, VectorIJ buffer) {
+
+ buffer.i = scaleA * a.i + scaleB * b.i + scaleC * c.i;
+ buffer.j = scaleA * a.j + scaleB * b.j + scaleC * c.j;
+
+ return buffer;
+ }
+
+ /**
+ * Linearly combine two vectors.
+ *
+ * @param scaleA a scale factor to apply to the vector a
+ * @param a one vector
+ * @param scaleB a scale factor to apply to the vector b
+ * @param b another vector
+ *
+ * @return a new VectorIJ which now contains ( scaleA*a + scaleB*b )
+ *
+ * @see combine
+ */
+ public static VectorIJ combine(double scaleA, UnwritableVectorIJ a, double scaleB,
+ UnwritableVectorIJ b) {
+ return combine(scaleA, a, scaleB, b, new VectorIJ());
+ }
+
+ /**
+ * Linearly combine two vectors.
+ *
+ * @param scaleA a scale factor to apply to the vector a
+ * @param a one vector
+ * @param scaleB a scale factor to apply to the vector b
+ * @param b another vector
+ * @param buffer the buffer to receive the contents of the combination
+ *
+ * @return a reference to buffer for convenience which now contains ( scaleA*a + scaleB*b )
+ */
+ public static VectorIJ combine(double scaleA, UnwritableVectorIJ a, double scaleB,
+ UnwritableVectorIJ b, VectorIJ buffer) {
+
+ buffer.i = scaleA * a.i + scaleB * b.i;
+ buffer.j = scaleA * a.j + scaleB * b.j;
+
+ return buffer;
+ }
+
+ /**
+ * Compute the cross product of a and b; unitize the result.
+ *
+ * @param a the left hand vector to cross
+ * @param b the right hand vector to cross
+ *
+ * @return a new VectorIJ which now contains (a x b)/||a||/||b||.
+ *
+ * @throws IllegalArgumentException if either a or b are equivalent to the {@link ZERO}
+ *
+ * @throws UnsupportedOperationException if the result of crossing a with b results in
+ * {@link ZERO}
+ *
+ * @see uCross
+ */
+ public static VectorIJK uCross(UnwritableVectorIJ a, UnwritableVectorIJ b) {
+ return uCross(a, b, new VectorIJK());
+ }
+
+ /**
+ * Compute the cross product of a and b; unitize the result.
+ *
+ * @param a the left hand vector to cross
+ * @param b the right hand vector to cross
+ * @param buffer the buffer to receive the contents of the unitized cross product
+ * @return a reference to buffer for convenience which now contains (a x b)/||a||/||b||.
+ *
+ * @throws IllegalArgumentException if either a or b are equivalent to the {@link ZERO}
+ *
+ * @throws UnsupportedOperationException if the result of crossing a with b results in
+ * {@link ZERO}
+ */
+ public static VectorIJK uCross(UnwritableVectorIJ a, UnwritableVectorIJ b, VectorIJK buffer) {
+
+ /*
+ * We should scale each vector by its maximal component.
+ */
+ double amax = absMaxComponent(a.i, a.j);
+ double bmax = absMaxComponent(b.i, b.j);
+
+ if ((amax == 0.0) || (bmax == 0.0)) {
+ throw new IllegalArgumentException("At least one input vector is of zero"
+ + " length. Unable to unitize resultant" + " cross product.");
+ }
+
+ double ti = 0.0;
+ double tj = 0.0;
+ double tk = (a.i / amax) * (b.j / bmax) - (a.j / amax) * (b.i / bmax);
+
+ buffer.setI(ti);
+ buffer.setJ(tj);
+ buffer.setK(tk);
+
+ return buffer.unitize();
+ }
+
+ /**
+ * Compute the cross product of a and b.
+ *
+ * @param a the left hand vector to cross
+ * @param b the right hand vector to cross
+ *
+ * @return a new VectorIJK which now contains (a x b)
+ *
+ * @see cross
+ */
+ public static VectorIJK cross(UnwritableVectorIJ a, UnwritableVectorIJ b) {
+ return cross(a, b, new VectorIJK());
+ }
+
+ /**
+ * Compute the cross product of a and b.
+ *
+ * @param a the left hand vector to cross
+ * @param b the right hand vector to cross
+ * @param buffer the buffer to receive the contents of the cross product
+ *
+ * @return a reference to buffer for convenience which now contains (a x b)
+ */
+ public static VectorIJK cross(UnwritableVectorIJ a, UnwritableVectorIJ b, VectorIJK buffer) {
+
+ double ti = 0.0;
+ double tj = 0.0;
+ double tk = a.i * b.j - a.j * b.i;
+
+ buffer.setI(ti);
+ buffer.setJ(tj);
+ buffer.setK(tk);
+
+ return buffer;
+ }
+
+ /**
+ * Subtract one vector from another.
+ *
+ * @param a the minuend
+ * @param b the subtrahend
+ *
+ * @return a new VectorIJ which now contains (a - b)
+ *
+ * @see subtract
+ */
+ public static VectorIJ subtract(UnwritableVectorIJ a, UnwritableVectorIJ b) {
+ return subtract(a, b, new VectorIJ());
+ }
+
+ /**
+ * Subtract one vector from another.
+ *
+ * @param a the minuend
+ * @param b the subtrahend
+ * @param buffer the buffer to receive the results of the subtraction
+ *
+ * @return a reference to buffer for convenience which now contains (a - b)
+ */
+ public static VectorIJ subtract(UnwritableVectorIJ a, UnwritableVectorIJ b, VectorIJ buffer) {
+ buffer.i = a.i - b.i;
+ buffer.j = a.j - b.j;
+
+ return buffer;
+ }
+
+ /**
+ * Add two vectors.
+ *
+ * @param a a vector
+ * @param b another vector
+ *
+ * @return a new VectorIJ which now contains (a + b).
+ *
+ * @see add
+ */
+ public static VectorIJ add(UnwritableVectorIJ a, UnwritableVectorIJ b) {
+ return add(a, b, new VectorIJ());
+ }
+
+ /**
+ * Add two vectors.
+ *
+ * @param a a vector
+ * @param b another vector
+ * @param buffer the buffer to receive the results of the addition
+ *
+ * @return a reference to buffer for convenience which now contains (a + b).
+ */
+ public static VectorIJ add(UnwritableVectorIJ a, UnwritableVectorIJ b, VectorIJ buffer) {
+ buffer.i = a.i + b.i;
+ buffer.j = a.j + b.j;
+
+ return buffer;
+ }
+
+ /**
+ * Adds all the vectors in an {@link Iterable} of vectors.
+ *
+ * @param vectors an {@link Iterable} of vectors to be added
+ * @param buffer the buffer to receive the results of the addition
+ *
+ * @return a reference to buffer for convenience which now contains (a + b + ... + n).
+ */
+ public static VectorIJ addAll(Iterable extends UnwritableVectorIJ> vectors, VectorIJ buffer) {
+ double sumI = 0.0;
+ double sumJ = 0.0;
+
+ for (UnwritableVectorIJ vector : vectors) {
+ sumI += vector.i;
+ sumJ += vector.j;
+ }
+
+ return buffer.setTo(sumI, sumJ);
+ }
+
+ /**
+ * Adds all the vectors in an {@link Iterable} of vectors.
+ *
+ * @param vectors an {@link Iterable} of vectors to be added
+ *
+ * @return a new VectorIJ for convenience which now contains (a + b + ... + n).
+ *
+ * @see VectorIJ#addAll(Iterable, VectorIJ)
+ */
+ public static VectorIJ addAll(Iterable extends UnwritableVectorIJ> vectors) {
+ return addAll(vectors, new VectorIJ());
+ }
+
+ /**
+ * Performs a component wise root sum square of two vectors (add in quadrature).
+ *
+ * @param a a vector
+ * @param b another vector
+ *
+ * @return a new VectorIJ which now contains (sqrt(ai + bi ) ,
+ * sqrt(aj + bj)).
+ *
+ * @see VectorIJ#addRSS(UnwritableVectorIJ, UnwritableVectorIJ, VectorIJ)
+ */
+ public static VectorIJ addRSS(UnwritableVectorIJ a, UnwritableVectorIJ b) {
+ return addRSS(a, b, new VectorIJ());
+ }
+
+ /**
+ * Performs a component wise root sum square of two vectors (add in quadrature).
+ *
+ * @param a a vector
+ * @param b another vector
+ * @param buffer the buffer to receive the results of the root sum square
+ *
+ * @return a reference to buffer for convenience which now contains (sqrt(ai +
+ * bi ) , sqrt(aj + bj)).
+ */
+ public static VectorIJ addRSS(UnwritableVectorIJ a, UnwritableVectorIJ b, VectorIJ buffer) {
+
+ double i = computeNorm(a.i, b.i);
+ double j = computeNorm(a.j, b.j);
+
+ return buffer.setTo(i, j);
+ }
+
+}
diff --git a/src/main/java/picante/math/vectorspace/VectorIJK.java b/src/main/java/picante/math/vectorspace/VectorIJK.java
new file mode 100644
index 0000000..8d12342
--- /dev/null
+++ b/src/main/java/picante/math/vectorspace/VectorIJK.java
@@ -0,0 +1,1304 @@
+package picante.math.vectorspace;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkElementIndex;
+import static picante.math.PicanteMath.cos;
+import static picante.math.PicanteMath.sin;
+import static picante.math.vectorspace.InternalOperations.absMaxComponent;
+import static picante.math.vectorspace.InternalOperations.computeNorm;
+import picante.designpatterns.Writable;
+import picante.exceptions.BugException;
+
+/**
+ * Writable subclass of the unwritable 3D vector parent completing the implementation of the weak
+ * immutability design pattern.
+ * + * This class contains the mutator methods necessary to set or alter the internals of the parent + * classes fields. + *
+ */ +public class VectorIJK extends UnwritableVectorIJK + implements Writable.ImplementationInterface+ * An example is perhaps the most straightforward means to explain this methods action. Given an + * axis (0,0,1) and a rotation angle of PI/2, this method does the following: + * + *
| vector | + *buffer | + *
|---|---|
| ( 1, 2, 3 ) | + *( -2, 1, 3 ) | + *
| ( 1, 0, 0 ) | + *( 0, 1, 0 ) | + *
| ( 0, 1, 0 ) | + *( -1, 0, 0 ) | + *
VectorIJK containing the results of the projection
+ *
+ * @throws IllegalArgumentException if normal is equal to {@link VectorIJK#ZERO}
+ *
+ * @see VectorIJK#planeProject(UnwritableVectorIJK, UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK planeProject(UnwritableVectorIJK vector, UnwritableVectorIJK normal) {
+ return planeProject(vector, normal, new VectorIJK());
+ }
+
+ /**
+ * Compute the projection of one vector onto the plane normal to another.
+ * + * Algebraicly, this routine effectively computes: + * + *
+ * <vector, to> * to + * vector - --------------------- + * || to || + *+ * + * where <,> denotes the standard scalar product and ||x|| the norm of x. For numeric + * precision reasons the implementation may vary slightly from the above prescription. + * + * + * @param vector the vector to project + * @param normal the normal to the plane to project vector onto + * @param buffer the buffer to receive the contents of the projection + * + * @return a reference to buffer for convenience + * + * @throws IllegalArgumentException if normal is equal to {@link VectorIJK#ZERO} + */ + public static VectorIJK planeProject(UnwritableVectorIJK vector, UnwritableVectorIJK normal, + VectorIJK buffer) { + + /* + * If the supplied normal vector is the zero vector, generate the necessary exception. + */ + checkArgument(!normal.equals(VectorIJK.ZERO), "Normal must not be the zero vector."); + + double maxVector = absMaxComponent(vector.i, vector.j, vector.k); + + /* + * Check to see if maxVector is zero length. If it is, populate buffer with VectorIJK.ZERO. + */ + if (maxVector == 0.0) { + buffer.clear(); + return buffer; + } + + /* + * Create a scaled copy of the input vector to project. + */ + VectorIJK scaledVector = + new VectorIJK(vector.i / maxVector, vector.j / maxVector, vector.k / maxVector); + + project(scaledVector, normal, buffer); + + /* + * The second unusual case is when vector itself is zero. This is simple enough, the zero vector + * projects as the zero vector. + */ + if (maxVector == 0.0) { + buffer.clear(); + return buffer; + } + + /* + * Subtract buffer from the v components to place the result in the plane. + */ + VectorIJK.subtract(scaledVector, buffer, buffer); + + /* + * Rescale the result. + */ + buffer.scale(maxVector); + + return buffer; + + } + + /** + * Compute the projection of one vector onto another. + * + * @param vector the vector to project + * @param onto the vector onto which vector is to be projected + * + * @return a new
VectorIJK containing the results of the projection
+ *
+ * @throws IllegalArgumentException if onto is the equal to {@link VectorIJK#ZERO}.
+ *
+ * @see VectorIJK#project(UnwritableVectorIJK, UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK project(UnwritableVectorIJK vector, UnwritableVectorIJK onto) {
+ return project(vector, onto, new VectorIJK());
+
+ }
+
+ /**
+ * Compute the projection of one vector onto another.
+ * + * Algebraicly, this routine effectively computes: + * + *
+ * <vector, onto> * onto + * --------------------- + * || onto || + *+ * + * where <,> denotes the standard scalar product and ||x|| the norm of x. For numeric + * precision reasons the implementation may vary slightly from the above prescription. + * + * + * @param vector the vector to project + * @param onto the vector onto which vector is to be projected + * @param buffer the buffer to receive the contents of the projection + * + * @return a reference to buffer for convenience + * + * @throws IllegalArgumentException if onto is the equal to {@link VectorIJK#ZERO}. + */ + public static VectorIJK project(UnwritableVectorIJK vector, UnwritableVectorIJK onto, + VectorIJK buffer) { + + double maxVector = absMaxComponent(vector.i, vector.j, vector.k); + double maxOnto = absMaxComponent(onto.i, onto.j, onto.k); + + if (maxOnto == 0) { + throw new IllegalArgumentException("Unable to project vector onto the zero vector."); + } + + if (maxVector == 0) { + buffer.clear(); + return buffer; + } + + double r1 = onto.i / maxOnto; + double r2 = onto.j / maxOnto; + double r3 = onto.k / maxOnto; + + double t1 = vector.i / maxVector; + double t2 = vector.j / maxVector; + double t3 = vector.k / maxVector; + + double scaleFactor = (t1 * r1 + t2 * r2 + t3 * r3) * maxVector / (r1 * r1 + r2 * r2 + r3 * r3); + + buffer.i = r1; + buffer.j = r2; + buffer.k = r3; + + buffer.scale(scaleFactor); + return buffer; + } + + /** + * Linearly combine eight vectors. + * + * @param scaleA the scale factor for vector a + * @param a a vector + * @param scaleB the scale vector for vector b + * @param b another vector + * @param scaleC the scale factor for vector c + * @param c the third vector + * @param scaleD the scale factor for vector d + * @param d the fourth vector + * @param scaleE the scale factor for vector e + * @param e the fifth vector + * @param scaleF the scale factor for vector f + * @param f the sixth vector + * @param scaleG the scale factor for vector g + * @param g the seventh vector + * @param scaleH the scale factor for vector h + * @param h the eighth vector + * + * @return a new
VectorIJK which now contains ( scaleA*a + scaleB*b + scaleC*c +
+ * scaleD*d + scaleE*e + scaleF*f + scaleG*g + scaleH*h)
+ *
+ * @see VectorIJK#combine(double, UnwritableVectorIJK, double, UnwritableVectorIJK, double,
+ * UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, double scaleD,
+ UnwritableVectorIJK d, double scaleE, UnwritableVectorIJK e, double scaleF,
+ UnwritableVectorIJK f, double scaleG, UnwritableVectorIJK g, double scaleH,
+ UnwritableVectorIJK h) {
+ return combine(scaleA, a, scaleB, b, scaleC, c, scaleD, d, scaleE, e, scaleF, f, scaleG, g,
+ scaleH, h, new VectorIJK());
+ }
+
+ /**
+ * Linearly combine eight vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param scaleD the scale factor for vector d
+ * @param d the fourth vector
+ * @param scaleE the scale factor for vector e
+ * @param e the fifth vector
+ * @param scaleF the scale factor for vector f
+ * @param f the sixth vector
+ * @param scaleG the scale factor for vector g
+ * @param g the seventh vector
+ * @param scaleH the scale factor for vector h
+ * @param h the eighth vector
+ * @param buffer the buffer to receive the results of the combination
+ *
+ * @return a reference to buffer for convenience which now contains ( scaleA*a + scaleB*b +
+ * scaleC*c + scaleD*d + scaleE*e + scaleF*f + scaleG*g + scaleH*h)
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, double scaleD,
+ UnwritableVectorIJK d, double scaleE, UnwritableVectorIJK e, double scaleF,
+ UnwritableVectorIJK f, double scaleG, UnwritableVectorIJK g, double scaleH,
+ UnwritableVectorIJK h, VectorIJK buffer) {
+
+ buffer.i = scaleA * a.i + scaleB * b.i + scaleC * c.i + scaleD * d.i + scaleE * e.i
+ + scaleF * f.i + scaleG * g.i + scaleH * h.i;
+ buffer.j = scaleA * a.j + scaleB * b.j + scaleC * c.j + scaleD * d.j + scaleE * e.j
+ + scaleF * f.j + scaleG * g.j + scaleH * h.j;
+ buffer.k = scaleA * a.k + scaleB * b.k + scaleC * c.k + scaleD * d.k + scaleE * e.k
+ + scaleF * f.k + scaleG * g.k + scaleH * h.k;
+
+ return buffer;
+ }
+
+ /**
+ * Linearly combine seven vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param scaleD the scale factor for vector d
+ * @param d the fourth vector
+ * @param scaleE the scale factor for vector e
+ * @param e the fifth vector
+ * @param scaleF the scale factor for vector f
+ * @param f the sixth vector
+ * @param scaleG the scale factor for vector g
+ * @param g the sixth vector
+ *
+ * @return a new VectorIJK which now contains ( scaleA*a + scaleB*b + scaleC*c +
+ * scaleD*d + scaleE*e + scaleF*f + scaleG*g )
+ *
+ * @see VectorIJK#combine(double, UnwritableVectorIJK, double, UnwritableVectorIJK, double,
+ * UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, double scaleD,
+ UnwritableVectorIJK d, double scaleE, UnwritableVectorIJK e, double scaleF,
+ UnwritableVectorIJK f, double scaleG, UnwritableVectorIJK g) {
+ return combine(scaleA, a, scaleB, b, scaleC, c, scaleD, d, scaleE, e, scaleF, f, scaleG, g,
+ new VectorIJK());
+ }
+
+ /**
+ * Linearly combine seven vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param scaleD the scale factor for vector d
+ * @param d the fourth vector
+ * @param scaleE the scale factor for vector e
+ * @param e the fifth vector
+ * @param scaleF the scale factor for vector f
+ * @param f the sixth vector
+ * @param scaleG the scale factor for vector g
+ * @param g the sixth vector
+ * @param buffer the buffer to receive the results of the combination
+ *
+ * @return a reference to buffer for convenience which now contains ( scaleA*a + scaleB*b +
+ * scaleC*c + scaleD*d + scaleE*e + scaleF*f + scaleG*g )
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, double scaleD,
+ UnwritableVectorIJK d, double scaleE, UnwritableVectorIJK e, double scaleF,
+ UnwritableVectorIJK f, double scaleG, UnwritableVectorIJK g, VectorIJK buffer) {
+
+ buffer.i = scaleA * a.i + scaleB * b.i + scaleC * c.i + scaleD * d.i + scaleE * e.i
+ + scaleF * f.i + scaleG * g.i;
+ buffer.j = scaleA * a.j + scaleB * b.j + scaleC * c.j + scaleD * d.j + scaleE * e.j
+ + scaleF * f.j + scaleG * g.j;
+ buffer.k = scaleA * a.k + scaleB * b.k + scaleC * c.k + scaleD * d.k + scaleE * e.k
+ + scaleF * f.k + scaleG * g.k;
+
+ return buffer;
+ }
+
+ /**
+ * Linearly combine six vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param scaleD the scale factor for vector d
+ * @param d the fourth vector
+ * @param scaleE the scale factor for vector e
+ * @param e the fifth vector
+ * @param scaleF the scale factor for vector f
+ * @param f the sixth vector
+ *
+ * @return a new VectorIJK which now contains ( scaleA*a + scaleB*b + scaleC*c +
+ * scaleD*d + scaleE*e + scaleF*f )
+ *
+ * @see VectorIJK#combine(double, UnwritableVectorIJK, double, UnwritableVectorIJK, double,
+ * UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, double scaleD,
+ UnwritableVectorIJK d, double scaleE, UnwritableVectorIJK e, double scaleF,
+ UnwritableVectorIJK f) {
+ return combine(scaleA, a, scaleB, b, scaleC, c, scaleD, d, scaleE, e, scaleF, f,
+ new VectorIJK());
+ }
+
+ /**
+ * Linearly combine six vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param scaleD the scale factor for vector d
+ * @param d the fourth vector
+ * @param scaleE the scale factor for vector e
+ * @param e the fifth vector
+ * @param scaleF the scale factor for vector f
+ * @param f the sixth vector
+ * @param buffer the buffer to receive the results of the combination
+ *
+ * @return a reference to buffer for convenience which now contains ( scaleA*a + scaleB*b +
+ * scaleC*c + scaleD*d + scaleE*e + scaleF*f )
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, double scaleD,
+ UnwritableVectorIJK d, double scaleE, UnwritableVectorIJK e, double scaleF,
+ UnwritableVectorIJK f, VectorIJK buffer) {
+
+ buffer.i =
+ scaleA * a.i + scaleB * b.i + scaleC * c.i + scaleD * d.i + scaleE * e.i + scaleF * f.i;
+ buffer.j =
+ scaleA * a.j + scaleB * b.j + scaleC * c.j + scaleD * d.j + scaleE * e.j + scaleF * f.j;
+ buffer.k =
+ scaleA * a.k + scaleB * b.k + scaleC * c.k + scaleD * d.k + scaleE * e.k + scaleF * f.k;
+
+ return buffer;
+ }
+
+ /**
+ * Linearly combine five vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param scaleD the scale factor for vector d
+ * @param d the fourth vector
+ * @param scaleE the scale factor for vector e
+ * @param e the fifth vector
+ *
+ * @return a new VectorIJK which now contains ( scaleA*a + scaleB*b + scaleC*c +
+ * scaleD*d + scaleE*e )
+ *
+ * @see VectorIJK#combine(double, UnwritableVectorIJK, double, UnwritableVectorIJK, double,
+ * UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, double scaleD,
+ UnwritableVectorIJK d, double scaleE, UnwritableVectorIJK e) {
+ return combine(scaleA, a, scaleB, b, scaleC, c, scaleD, d, scaleE, e, new VectorIJK());
+ }
+
+ /**
+ * Linearly combine five vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param scaleD the scale factor for vector d
+ * @param d the fourth vector
+ * @param scaleE the scale factor for vector e
+ * @param e the fifth vector
+ * @param buffer the buffer to receive the results of the combination
+ *
+ * @return a reference to buffer for convenience which now contains ( scaleA*a + scaleB*b +
+ * scaleC*c + scaleD*d + scaleE*e )
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, double scaleD,
+ UnwritableVectorIJK d, double scaleE, UnwritableVectorIJK e, VectorIJK buffer) {
+
+ buffer.i = scaleA * a.i + scaleB * b.i + scaleC * c.i + scaleD * d.i + scaleE * e.i;
+ buffer.j = scaleA * a.j + scaleB * b.j + scaleC * c.j + scaleD * d.j + scaleE * e.j;
+ buffer.k = scaleA * a.k + scaleB * b.k + scaleC * c.k + scaleD * d.k + scaleE * e.k;
+
+ return buffer;
+ }
+
+ /**
+ * Linearly combine four vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param scaleD the scale factor for vector d
+ * @param d the fourth vector
+ *
+ * @return a new VectorIJK which now contains ( scaleA*a + scaleB*b + scaleC*c +
+ * scaleD*d)
+ *
+ * @see VectorIJK#combine(double, UnwritableVectorIJK, double, UnwritableVectorIJK, double,
+ * UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, double scaleD,
+ UnwritableVectorIJK d) {
+ return combine(scaleA, a, scaleB, b, scaleC, c, scaleD, d, new VectorIJK());
+ }
+
+ /**
+ * Linearly combine four vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param scaleD the scale factor for vector d
+ * @param d the fourth vector
+ * @param buffer the buffer to receive the results of the combination
+ *
+ * @return a reference to buffer for convenience which now contains ( scaleA*a + scaleB*b +
+ * scaleC*c + scaleD*d)
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, double scaleD,
+ UnwritableVectorIJK d, VectorIJK buffer) {
+
+ buffer.i = scaleA * a.i + scaleB * b.i + scaleC * c.i + scaleD * d.i;
+ buffer.j = scaleA * a.j + scaleB * b.j + scaleC * c.j + scaleD * d.j;
+ buffer.k = scaleA * a.k + scaleB * b.k + scaleC * c.k + scaleD * d.k;
+
+ return buffer;
+ }
+
+ /**
+ * Linearly combine three vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ *
+ * @return a new VectorIJK which now contains ( scaleA*a + scaleB*b + scaleC*c )
+ *
+ * @see VectorIJK#combine(double, UnwritableVectorIJK, double, UnwritableVectorIJK, double,
+ * UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c) {
+ return combine(scaleA, a, scaleB, b, scaleC, c, new VectorIJK());
+ }
+
+ /**
+ * Linearly combine three vectors.
+ *
+ * @param scaleA the scale factor for vector a
+ * @param a a vector
+ * @param scaleB the scale vector for vector b
+ * @param b another vector
+ * @param scaleC the scale factor for vector c
+ * @param c the third vector
+ * @param buffer the buffer to receive the results of the combination
+ *
+ * @return a reference to buffer for convenience which now contains ( scaleA*a + scaleB*b +
+ * scaleC*c )
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, double scaleC, UnwritableVectorIJK c, VectorIJK buffer) {
+
+ buffer.i = scaleA * a.i + scaleB * b.i + scaleC * c.i;
+ buffer.j = scaleA * a.j + scaleB * b.j + scaleC * c.j;
+ buffer.k = scaleA * a.k + scaleB * b.k + scaleC * c.k;
+
+ return buffer;
+ }
+
+ /**
+ * Linearly combine two vectors.
+ *
+ * @param scaleA a scale factor to apply to the vector a
+ * @param a one vector
+ * @param scaleB a scale factor to apply to the vector b
+ * @param b another vector
+ *
+ * @return a new VectorIJK which now contains ( scaleA*a + scaleB*b )
+ *
+ * @see VectorIJK#combine(double, UnwritableVectorIJK, double, UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b) {
+ return combine(scaleA, a, scaleB, b, new VectorIJK());
+ }
+
+ /**
+ * Linearly combine two vectors.
+ *
+ * @param scaleA a scale factor to apply to the vector a
+ * @param a one vector
+ * @param scaleB a scale factor to apply to the vector b
+ * @param b another vector
+ * @param buffer the buffer to receive the contents of the combination
+ *
+ * @return a reference to buffer for convenience which now contains ( scaleA*a + scaleB*b )
+ */
+ public static VectorIJK combine(double scaleA, UnwritableVectorIJK a, double scaleB,
+ UnwritableVectorIJK b, VectorIJK buffer) {
+
+ buffer.i = scaleA * a.i + scaleB * b.i;
+ buffer.j = scaleA * a.j + scaleB * b.j;
+ buffer.k = scaleA * a.k + scaleB * b.k;
+
+ return buffer;
+ }
+
+ /**
+ * Compute the cross product of a and b; unitize the result.
+ *
+ * @param a the left hand vector to cross
+ * @param b the right hand vector to cross
+ *
+ * @return a new VectorIJK which now contains (a x b)/||a||/||b||.
+ *
+ * @throws IllegalArgumentException if either a or b are equivalent to the {@link VectorIJK#ZERO}
+ *
+ * @throws UnsupportedOperationException if the result of crossing a with b results in
+ * {@link VectorIJK#ZERO}
+ *
+ * @see VectorIJK#uCross(UnwritableVectorIJK, UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK uCross(UnwritableVectorIJK a, UnwritableVectorIJK b) {
+ return uCross(a, b, new VectorIJK());
+ }
+
+ /**
+ * Compute the cross product of a and b; unitize the result.
+ *
+ * @param a the left hand vector to cross
+ * @param b the right hand vector to cross
+ * @param buffer the buffer to receive the contents of the unitized cross product
+ * @return a reference to buffer for convenience which now contains (a x b)/||a||/||b||.
+ *
+ * @throws IllegalArgumentException if either a or b are equivalent to the {@link VectorIJK#ZERO}
+ *
+ * @throws UnsupportedOperationException if the result of crossing a with b results in
+ * {@link VectorIJK#ZERO}
+ */
+ public static VectorIJK uCross(UnwritableVectorIJK a, UnwritableVectorIJK b, VectorIJK buffer) {
+
+ /*
+ * We should scale each vector by its maximal component.
+ */
+ double amax = absMaxComponent(a.i, a.j, a.k);
+ double bmax = absMaxComponent(b.i, b.j, b.k);
+
+ if ((amax == 0.0) || (bmax == 0.0)) {
+ throw new IllegalArgumentException("At least one input vector is of zero"
+ + " length. Unable to unitize resultant" + " cross product.");
+ }
+
+ double ti = (a.j / amax) * (b.k / bmax) - (a.k / amax) * (b.j / bmax);
+ double tj = (a.k / amax) * (b.i / bmax) - (a.i / amax) * (b.k / bmax);
+ double tk = (a.i / amax) * (b.j / bmax) - (a.j / amax) * (b.i / bmax);
+
+ buffer.i = ti;
+ buffer.j = tj;
+ buffer.k = tk;
+
+ return buffer.unitize();
+ }
+
+ /**
+ * Compute the cross product of a and b.
+ *
+ * @param a the left hand vector to cross
+ * @param b the right hand vector to cross
+ *
+ * @return a new VectorIJK which now contains (a x b)
+ *
+ * @see VectorIJK#cross(UnwritableVectorIJK, UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK cross(UnwritableVectorIJK a, UnwritableVectorIJK b) {
+ return cross(a, b, new VectorIJK());
+ }
+
+ /**
+ * Compute the cross product of a and b.
+ *
+ * @param a the left hand vector to cross
+ * @param b the right hand vector to cross
+ * @param buffer the buffer to receive the contents of the cross product
+ *
+ * @return a reference to buffer for convenience which now contains (a x b)
+ */
+ public static VectorIJK cross(UnwritableVectorIJK a, UnwritableVectorIJK b, VectorIJK buffer) {
+
+ double ti = a.j * b.k - a.k * b.j;
+ double tj = a.k * b.i - a.i * b.k;
+ double tk = a.i * b.j - a.j * b.i;
+
+ buffer.i = ti;
+ buffer.j = tj;
+ buffer.k = tk;
+
+ return buffer;
+ }
+
+
+ /**
+ * Create the Pointwise (aka Hadamard aka Schur) product of two vectors by multiplying their
+ * corresponding components
+ *
+ * @param a a vector
+ * @param b another vector
+ *
+ * @return a new VectorIJK which now contains (a .* b).
+ *
+ * @see VectorIJK#pointwiseMultiply(UnwritableVectorIJK, UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK pointwiseMultiply(UnwritableVectorIJK a, UnwritableVectorIJK b) {
+ return pointwiseMultiply(a, b, new VectorIJK());
+ }
+
+
+ /**
+ * Create the Pointwise (aka Hadamard aka Schur) product of two vectors by multiplying their
+ * corresponding components
+ *
+ * @param a a vector
+ * @param b another vector
+ * @param buffer the buffer to receive the results of the pointwise multiplication
+ *
+ * @return a reference to buffer for convenience which now contains (a .* b).
+ */
+ public static VectorIJK pointwiseMultiply(UnwritableVectorIJK a, UnwritableVectorIJK b,
+ VectorIJK buffer) {
+ buffer.i = a.i * b.i;
+ buffer.j = a.j * b.j;
+ buffer.k = a.k * b.k;
+
+ return buffer;
+
+ }
+
+ /**
+ * Subtract one vector from another.
+ *
+ * @param a the minuend
+ * @param b the subtrahend
+ *
+ * @return a new VectorIJK which now contains (a - b)
+ *
+ * @see VectorIJK#subtract(UnwritableVectorIJK, UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK subtract(UnwritableVectorIJK a, UnwritableVectorIJK b) {
+ return subtract(a, b, new VectorIJK());
+ }
+
+ /**
+ * Subtract one vector from another.
+ *
+ * @param a the minuend
+ * @param b the subtrahend
+ * @param buffer the buffer to receive the results of the subtraction
+ *
+ * @return a reference to buffer for convenience which now contains (a - b)
+ */
+ public static VectorIJK subtract(UnwritableVectorIJK a, UnwritableVectorIJK b, VectorIJK buffer) {
+ buffer.i = a.i - b.i;
+ buffer.j = a.j - b.j;
+ buffer.k = a.k - b.k;
+
+ return buffer;
+ }
+
+ /**
+ * Add two vectors.
+ *
+ * @param a a vector
+ * @param b another vector
+ *
+ * @return a new VectorIJK which now contains (a + b).
+ *
+ * @see VectorIJK#add(UnwritableVectorIJK, UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK add(UnwritableVectorIJK a, UnwritableVectorIJK b) {
+ return add(a, b, new VectorIJK());
+ }
+
+ /**
+ * Add two vectors.
+ *
+ * @param a a vector
+ * @param b another vector
+ * @param buffer the buffer to receive the results of the addition
+ *
+ * @return a reference to buffer for convenience which now contains (a + b).
+ */
+ public static VectorIJK add(UnwritableVectorIJK a, UnwritableVectorIJK b, VectorIJK buffer) {
+ buffer.i = a.i + b.i;
+ buffer.j = a.j + b.j;
+ buffer.k = a.k + b.k;
+
+ return buffer;
+ }
+
+ /**
+ * Adds all the vectors in an {@link Iterable} of vectors.
+ *
+ * @param vectors an {@link Iterable} of vectors to be added
+ * @param buffer the buffer to receive the results of the addition
+ *
+ * @return a reference to buffer for convenience which now contains (a + b + ... + n).
+ */
+ public static VectorIJK addAll(Iterable extends UnwritableVectorIJK> vectors,
+ VectorIJK buffer) {
+ double sumI = 0.0;
+ double sumJ = 0.0;
+ double sumK = 0.0;
+
+ for (UnwritableVectorIJK vector : vectors) {
+ sumI += vector.i;
+ sumJ += vector.j;
+ sumK += vector.k;
+ }
+
+ return buffer.setTo(sumI, sumJ, sumK);
+ }
+
+ /**
+ * Adds all the vectors in an {@link Iterable} of vectors.
+ *
+ * @param vectors an {@link Iterable} of vectors to be added
+ *
+ * @return a new VectorIJK for convenience which now contains (a + b + ... + n).
+ *
+ * @see VectorIJK#addAll(Iterable, VectorIJK)
+ */
+ public static VectorIJK addAll(Iterable extends UnwritableVectorIJK> vectors) {
+ return addAll(vectors, new VectorIJK());
+ }
+
+ /**
+ * Performs a component wise root sum square of two vectors (add in quadrature).
+ *
+ * @param a a vector
+ * @param b another vector
+ *
+ * @return a new VectorIJK which now contains (sqrt(ai + bi ) ,
+ * sqrt(aj + bj) , sqrt(ak + bk )).
+ *
+ * @see VectorIJK#addRSS(UnwritableVectorIJK, UnwritableVectorIJK, VectorIJK)
+ */
+ public static VectorIJK addRSS(UnwritableVectorIJK a, UnwritableVectorIJK b) {
+ return addRSS(a, b, new VectorIJK());
+ }
+
+ /**
+ * Performs a component wise root sum square of two vectors (add in quadrature).
+ *
+ * @param a a vector
+ * @param b another vector
+ * @param buffer the buffer to receive the results of the root sum square
+ *
+ * @return a reference to buffer for convenience which now contains (sqrt(ai + b
+ * i ) , sqrt(aj + bj) , sqrt(ak + b
+ * k )).
+ */
+ public static VectorIJK addRSS(UnwritableVectorIJK a, UnwritableVectorIJK b, VectorIJK buffer) {
+
+ double i = computeNorm(a.i, b.i);
+ double j = computeNorm(a.j, b.j);
+ double k = computeNorm(a.k, b.k);
+
+ return buffer.setTo(i, j, k);
+ }
+
+
+}
diff --git a/src/main/java/picante/math/vectorspace/package-info.java b/src/main/java/picante/math/vectorspace/package-info.java
new file mode 100644
index 0000000..844cf58
--- /dev/null
+++ b/src/main/java/picante/math/vectorspace/package-info.java
@@ -0,0 +1,19 @@
+/**
+ * Contains a basic set of classes capturing components of an elementary three dimensional and two
+ * dimensional vector space and a subset of linear transformations on it.
+ * + * These two and three dimensional constructs are called out specifically rather than a treatment of + * the general n-dimensional case given the focus of the library on dealing with the physical world. + * For a treatment of n-dimensional vector spaces, packages like jama are helpful. + *
+ *+ * The classes in this package implement a particular form of the weak immutability design pattern. + *
+ *+ * TODO: Document the fact mtxv and mxv are not where Jon and Grant expected them to be!. The + * classes in this package follow another slightly unusual design pattern that turns out to be + * useful for mathematical constructs like vectors and matrices. If you are looking for binary or + * tertiary operations on the + *
+ */ +package picante.math.vectorspace; diff --git a/src/main/java/picante/mechanics/AbstractCoverage.java b/src/main/java/picante/mechanics/AbstractCoverage.java new file mode 100644 index 0000000..c8c3cff --- /dev/null +++ b/src/main/java/picante/mechanics/AbstractCoverage.java @@ -0,0 +1,29 @@ +package picante.mechanics; + +/** + * Helper implementation of the Coverage interface that provides properly implemented + * {@link Object#toString()}, {@link Object#equals(Object)}, and {@link Object#hashCode()} methods. + * + * @see Coverages#equalsImplementation(Coverage, Object) + * @see Coverages#toStringImplementation(Coverage) + * @see Coverages#hashCodeImplementation(Coverage) + * + */ +public abstract class AbstractCoverage implements Coverage { + + @Override + public int hashCode() { + return Coverages.hashCodeImplementation(this); + } + + @Override + public boolean equals(Object obj) { + return Coverages.equalsImplementation(this, obj); + } + + @Override + public String toString() { + return Coverages.toStringImplementation(this); + } + +} diff --git a/src/main/java/picante/mechanics/AbstractPositionVectorFunctionWrapper.java b/src/main/java/picante/mechanics/AbstractPositionVectorFunctionWrapper.java new file mode 100644 index 0000000..e8911b3 --- /dev/null +++ b/src/main/java/picante/mechanics/AbstractPositionVectorFunctionWrapper.java @@ -0,0 +1,40 @@ +package picante.mechanics; + +/** + * Provides a basic wrapper or view of an existing position vector function, but leaves the vector + * evaluation method unspecified. + */ +abstract class AbstractPositionVectorFunctionWrapper implements PositionVectorFunction { + + private final PositionVectorFunction function; + + /** + * Constructs the delegation for coverage and IDs. + * + * @param function the function to delegate to + */ + public AbstractPositionVectorFunctionWrapper(PositionVectorFunction function) { + this.function = function; + } + + @Override + public EphemerisID getObserverID() { + return function.getObserverID(); + } + + @Override + public EphemerisID getTargetID() { + return function.getTargetID(); + } + + @Override + public FrameID getFrameID() { + return function.getFrameID(); + } + + @Override + public Coverage getCoverage() { + return function.getCoverage(); + } + +} diff --git a/src/main/java/picante/mechanics/AbstractReversedFrameTransformFunctionWrapper.java b/src/main/java/picante/mechanics/AbstractReversedFrameTransformFunctionWrapper.java new file mode 100644 index 0000000..bbb9110 --- /dev/null +++ b/src/main/java/picante/mechanics/AbstractReversedFrameTransformFunctionWrapper.java @@ -0,0 +1,28 @@ +package picante.mechanics; + + +abstract class AbstractReversedFrameTransformFunctionWrapper implements FrameTransformFunction { + + private final FrameTransformFunction function; + + public AbstractReversedFrameTransformFunctionWrapper(FrameTransformFunction function) { + super(); + this.function = function; + } + + @Override + public FrameID getFromID() { + return function.getToID(); + } + + @Override + public FrameID getToID() { + return function.getFromID(); + } + + @Override + public Coverage getCoverage() { + return function.getCoverage(); + } + +} diff --git a/src/main/java/picante/mechanics/AbstractReversedPositionVectorFunctionWrapper.java b/src/main/java/picante/mechanics/AbstractReversedPositionVectorFunctionWrapper.java new file mode 100644 index 0000000..b4c3b13 --- /dev/null +++ b/src/main/java/picante/mechanics/AbstractReversedPositionVectorFunctionWrapper.java @@ -0,0 +1,41 @@ +package picante.mechanics; + +/** + * Provides a basic wrapper or view of an existing position vector function, but inverts the sense + * of the observer and target ID. This is particularly useful for implementing negated vector + * functions. + */ +abstract class AbstractReversedPositionVectorFunctionWrapper implements PositionVectorFunction { + + private final PositionVectorFunction function; + + /** + * Creates a delegating wrapper, with the to and from IDs reversed. + * + * @param function the function to delegate coverage and IDs to + */ + public AbstractReversedPositionVectorFunctionWrapper(PositionVectorFunction function) { + this.function = function; + } + + @Override + public EphemerisID getObserverID() { + return function.getTargetID(); + } + + @Override + public EphemerisID getTargetID() { + return function.getObserverID(); + } + + @Override + public FrameID getFrameID() { + return function.getFrameID(); + } + + @Override + public Coverage getCoverage() { + return function.getCoverage(); + } + +} diff --git a/src/main/java/picante/mechanics/AbstractReversedStateTransformFunctionWrapper.java b/src/main/java/picante/mechanics/AbstractReversedStateTransformFunctionWrapper.java new file mode 100644 index 0000000..9c12f07 --- /dev/null +++ b/src/main/java/picante/mechanics/AbstractReversedStateTransformFunctionWrapper.java @@ -0,0 +1,10 @@ +package picante.mechanics; + +abstract class AbstractReversedStateTransformFunctionWrapper + extends AbstractReversedFrameTransformFunctionWrapper implements StateTransformFunction { + + public AbstractReversedStateTransformFunctionWrapper(FrameTransformFunction function) { + super(function); + } + +} diff --git a/src/main/java/picante/mechanics/AbstractReversedStateVectorFunctionWrapper.java b/src/main/java/picante/mechanics/AbstractReversedStateVectorFunctionWrapper.java new file mode 100644 index 0000000..0e43112 --- /dev/null +++ b/src/main/java/picante/mechanics/AbstractReversedStateVectorFunctionWrapper.java @@ -0,0 +1,19 @@ +package picante.mechanics; + +/** + * Trivial extension of the {@link AbstractReversedPositionVectorFunctionWrapper} that incorporates + * the {@link StateVectorFunction} interface + */ +abstract class AbstractReversedStateVectorFunctionWrapper + extends AbstractReversedPositionVectorFunctionWrapper implements StateVectorFunction { + + /** + * Creates the delegate wrapper with to and from IDs reversed. + * + * @param function the function to delegate to + */ + public AbstractReversedStateVectorFunctionWrapper(PositionVectorFunction function) { + super(function); + } + +} diff --git a/src/main/java/picante/mechanics/AbstractStateVectorFunctionWrapper.java b/src/main/java/picante/mechanics/AbstractStateVectorFunctionWrapper.java new file mode 100644 index 0000000..02b09d7 --- /dev/null +++ b/src/main/java/picante/mechanics/AbstractStateVectorFunctionWrapper.java @@ -0,0 +1,19 @@ +package picante.mechanics; + +/** + * Trivial extension of the {@link AbstractPositionVectorFunctionWrapper} that incorporates the + * {@link StateVectorFunction} interface. + */ +abstract class AbstractStateVectorFunctionWrapper extends AbstractPositionVectorFunctionWrapper + implements StateVectorFunction { + + /** + * Constructs the delegating view. + * + * @param function the position vector function to delegate for coverage and various IDs. + */ + public AbstractStateVectorFunctionWrapper(PositionVectorFunction function) { + super(function); + } + +} diff --git a/src/main/java/picante/mechanics/CelestialBodies.java b/src/main/java/picante/mechanics/CelestialBodies.java new file mode 100644 index 0000000..791d717 --- /dev/null +++ b/src/main/java/picante/mechanics/CelestialBodies.java @@ -0,0 +1,21 @@ +package picante.mechanics; + +/** + * This enumeration provides implementations of the EphemerisID interface for all of the built-in + * (recognized or standard) celestial bodies. + *+ * At the moment it includes all of the planetary system barycenters, the solar system barycenter, + * the planets, the moons built into the SPICE Toolkit, as well as a few select smaller bodies. The + * choice here was a bit arbitrary, and was a specific subset of the bodies built directly into + * SPICE. + *
+ */ +public enum CelestialBodies implements EphemerisID { + SOLAR_SYSTEM_BARYCENTER, MERCURY_BARYCENTER, VENUS_BARYCENTER, EARTH_BARYCENTER, MARS_BARYCENTER, JUPITER_BARYCENTER, SATURN_BARYCENTER, URANUS_BARYCENTER, NEPTUNE_BARYCENTER, PLUTO_BARYCENTER, SUN, MERCURY, VENUS, EARTH, MOON, MARS, PHOBOS, DEIMOS, JUPITER, IO, EUROPA, GANYMEDE, CALLISTO, AMALTHEA, HIMALIA, ELARA, PASIPHAE, SINOPE, LYSITHEA, CARME, ANANKE, LEDA, THEBE, ADRASTEA, METIS, CALLIRRHOE, THEMISTO, MEGACLITE, TAYGETE, CHALDENE, HARPALYKE, KALYKE, IOCASTE, ERINOME, ISONOE, PRAXIDIKE, AUTONOE, THYONE, HERMIPPE, AITNE, EURYDOME, EUANTHE, EUPORIE, ORTHOSIE, SPONDE, KALE, PASITHEE, HEGEMONE, MNEME, AOEDE, THELXINOE, ARCHE, KALLICHORE, HELIKE, CARPO, EUKELADE, CYLLENE, KORE, HERSE, DIA, SATURN, MIMAS, ENCELADUS, TETHYS, DIONE, RHEA, TITAN, HYPERION, IAPETUS, PHOEBE, JANUS, EPIMETHEUS, HELENE, TELESTO, CALYPSO, ATLAS, PROMETHEUS, PANDORA, PAN, YMIR, PAALIAQ, TARVOS, IJIRAQ, SUTTUNGR, KIVIUQ, MUNDILFARI, ALBIORIX, SKATHI, ERRIAPUS, SIARNAQ, THRYMR, NARVI, METHONE, PALLENE, POLYDEUCES, DAPHNIS, AEGIR, BEBHIONN, BERGELMIR, BESTLA, FARBAUTI, FENRIR, FORNJOT, HATI, HYRROKKIN, KARI, LOGE, SKOLL, SURTUR, ANTHE, JARNSAXA, GREIP, TARQEQ, AEGAEON, URANUS, ARIEL, UMBRIEL, TITANIA, OBERON, MIRANDA, CORDELIA, OPHELIA, BIANCA, CRESSIDA, DESDEMONA, JULIET, PORTIA, ROSALIND, BELINDA, PUCK, CALIBAN, SYCORAX, PROSPERO, SETEBOS, STEPHANO, TRINCULO, FRANCISCO, MARGARET, FERDINAND, PERDITA, MAB, CUPID, NEPTUNE, TRITON, NEREID, NAIAD, THALASSA, DESPINA, GALATEA, LARISSA, PROTEUS, HALIMEDE, PSAMATHE, SAO, LAOMEDEIA, NESO, PLUTO, CHARON, NIX, HYDRA, KERBEROS, STYX, GASPRA, IDA, EROS, BORRELLY, TEMPEL_1, VESTA, ITOKAWA, CERES, PALLAS, LUTETIA, DAVIDA, STEINS, BENNU, A52_EUROPA, RYUGU, ARROKOTH, DIDYMOS_BARYCENTER, DIDYMOS, DIMORPHOS, DONALDJOHANSON, EURYBATES, EURYBATES_BARYCENTER, QUETA, POLYMELE, LEUCUS, ORUS, PATROCLUS_BARYCENTER, PATROCLUS, MENOETIUS; + + @Override + public String getName() { + return name(); + } + +} diff --git a/src/main/java/picante/mechanics/CelestialFrames.java b/src/main/java/picante/mechanics/CelestialFrames.java new file mode 100644 index 0000000..baeb675 --- /dev/null +++ b/src/main/java/picante/mechanics/CelestialFrames.java @@ -0,0 +1,76 @@ +package picante.mechanics; + +import com.google.common.collect.ImmutableMap; + +/** + * This enumeration provides an implementation of the FrameID interface for all of the built-in + * (recognized or standard) celestial frames. + *+ * At the moment it includes all of the inertial frames defined in the SPICE Toolkit, and the + * built-in body-fixed frame definitions. + *
+ */ +public enum CelestialFrames implements FrameID { + + J2000(true), B1950(true), FK4(true), DE118(true), DE96(true), DE102(true), DE108(true), DE111( + true), DE114(true), DE122(true), DE125(true), DE130(true), GALACTIC(true), DE200(true), DE202( + true), MARSIAU(true), ECLIPJ2000(true), ECLIPB1950(true), DE140(true), DE142(true), DE143( + true), IAU_MERCURY_BARYCENTER, IAU_VENUS_BARYCENTER, IAU_EARTH_BARYCENTER, IAU_MARS_BARYCENTER, IAU_JUPITER_BARYCENTER, IAU_SATURN_BARYCENTER, IAU_URANUS_BARYCENTER, IAU_NEPTUNE_BARYCENTER, IAU_PLUTO_BARYCENTER, IAU_SUN, IAU_MERCURY, IAU_VENUS, IAU_EARTH, IAU_MARS, IAU_JUPITER, IAU_SATURN, IAU_URANUS, IAU_NEPTUNE, IAU_PLUTO, IAU_MOON, IAU_PHOBOS, IAU_DEIMOS, IAU_IO, IAU_EUROPA, IAU_GANYMEDE, IAU_CALLISTO, IAU_AMALTHEA, IAU_HIMALIA, IAU_ELARA, IAU_PASIPHAE, IAU_SINOPE, IAU_LYSITHEA, IAU_CARME, IAU_ANANKE, IAU_LEDA, IAU_THEBE, IAU_ADRASTEA, IAU_METIS, IAU_MIMAS, IAU_ENCELADUS, IAU_TETHYS, IAU_DIONE, IAU_RHEA, IAU_TITAN, IAU_HYPERION, IAU_IAPETUS, IAU_PHOEBE, IAU_JANUS, IAU_EPIMETHEUS, IAU_HELENE, IAU_TELESTO, IAU_CALYPSO, IAU_ATLAS, IAU_PROMETHEUS, IAU_PANDORA, IAU_ARIEL, IAU_UMBRIEL, IAU_TITANIA, IAU_OBERON, IAU_MIRANDA, IAU_CORDELIA, IAU_OPHELIA, IAU_BIANCA, IAU_CRESSIDA, IAU_DESDEMONA, IAU_JULIET, IAU_PORTIA, IAU_ROSALIND, IAU_BELINDA, IAU_PUCK, IAU_TRITON, IAU_NEREID, IAU_NAIAD, IAU_THALASSA, IAU_DESPINA, IAU_GALATEA, IAU_LARISSA, IAU_PROTEUS, IAU_CHARON, EARTH_FIXED, IAU_PAN, IAU_GASPRA, IAU_IDA, IAU_EROS, IAU_CALLIRRHOE, IAU_THEMISTO, IAU_MEGACLITE, IAU_TAYGETE, IAU_CHALDENE, IAU_HARPALYKE, IAU_KALYKE, IAU_IOCASTE, IAU_ERINOME, IAU_ISONOE, IAU_PRAXIDIKE, IAU_BORRELLY, IAU_TEMPEL_1, IAU_VESTA, IAU_ITOKAWA, IAU_CERES, IAU_PALLAS, IAU_LUTETIA, IAU_DAVIDA, IAU_STEINS, IAU_BENNU, IAU_A52_EUROPA, IAU_NIX, IAU_HYDRA, IAU_RYUGU, IAU_ARROKOTH, IAU_DIDYMOS_BARYCENTER, IAU_DIDYMOS, IAU_DIMORPHOS, IAU_DONALDJOHANSON, IAU_EURYBATES, IAU_EURYBATES_BARYCENTER, IAU_QUETA, IAU_POLYMELE, IAU_LEUCUS, IAU_ORUS, IAU_PATROCLUS_BARYCENTER, IAU_PATROCLUS, IAU_MENOETIUS, ITRF93; + + private final boolean inertial; + + private CelestialFrames() { + this.inertial = false; + } + + private CelestialFrames(boolean inertial) { + this.inertial = inertial; + } + + @Override + public String getName() { + return name(); + } + + @Override + public boolean isInertial() { + return inertial; + } + + /** + * Mapping of frames to their natural "centers" as would be useful in performing + * aberration corrections through the frame. + */ + public static final ImmutableMap+ * The mechanics framework does not specify precisely what the double precision "time" + * input means. It is largely up to the user of the framework to decide and load sources into it + * that are all using a consistent, matching definition. In general the implementations provided by + * the crucible library should be utilizing seconds past J2000.0 expressed in barycentric dynamical + * time, or TDB. This is consistent with the SPICE Toolkit, among other JPL navigation products. + *
+ *+ * Implementors of this interface should read the specification carefully, as consistency of the + * implementation is necessary for proper functioning of the mechanics framework. There are several + * useful implementations of the coverage interface exposed in the the {@link Coverages} static + * utility class. It maybe useful to start there before attempting to implement this interface + * yourself. The reason this interface exists is to enable implementors of ephemeris and frame + * sources completely freedom with regards to how requests for coverage information are satisfied: + * both in terms of memory and performance. If the {@link IntervalSet} class meets your coverage + * implementation needs, then have a look at {@link Coverages#create(IntervalSet)}; it will save you + * the trouble of implementing this interface. + *
+ *+ * Further implementors are strongly encouraged to override equals and hashCode and utilize the + * static methods {@link Coverages#equalsImplementation(Coverage, Object)} and + * {@link Coverages#hashCodeImplementation(Coverage)} + *
+ *+ * With regards to mutability: the coverage API does not provide any sort of listener mechanism for + * notification if a coverage instance undergoes a change. It was largely intended for + * implementations to be invariant through an application run, and some of the mechanics framework + * software may depend on this fact (caching values for performance enhancement, etc.). That said, + * there is no specific restriction that the implementation be invariant over time. Code in the + * framework that relies upon this fact should document it clearly, and obviously. + *
+ *+ * The exceptions allowed by this interface are all runtime exceptions, and as such are not + * enforceable. However, as an implementor it is your responsibility to ensure that the methods you + * provide here throw the appropriate runtime exceptions. In general the framework does not rely on + * the specific exception, but it's still a good idea to throw the appropriate type. + *
+ *+ * TODO: Sort this out: There is an efficiency issue latent in the implementation of this interface + * and its ultimate conversion to an {@link IntervalSet} or {@link Iterable} of + * {@link UnwritableInterval}. The standard way an iteration over the intervals of coverage is + * performed is through the {@link Coverage#hasNextInterval(double)} and + * {@link Coverage#getNextInterval(double, Interval)} methods. Implementors of these methods are + * left with no choice but to perform a search as they do not know an iteration is being performed, + * so we end up with something that is O(n*log(n)) assuming binary search on the implementation + * side. If a future version of this interface requires it implements {@link Iterable} we may be + * better off, but it could result in more complicated implementations. For now I'm leaving this as + * it is, but it is an issue that should be sorted out and locked down. + *
+ */ +public interface Coverage { + + /** + * Coverage indicating all possible times. + */ + public static final Coverage ALL_TIME = new Coverage() { + + @Override + public boolean contains(@SuppressWarnings("unused") double time) { + return true; + } + + @Override + public Interval getBoundingInterval(Interval buffer) { + buffer.set(-Double.MAX_VALUE, Double.MAX_VALUE); + return buffer; + } + + @Override + public Interval getBracketingInterval(@SuppressWarnings("unused") double time, + Interval buffer) { + buffer.set(-Double.MAX_VALUE, Double.MAX_VALUE); + return buffer; + } + + @Override + public boolean hasNextInterval(@SuppressWarnings("unused") double time) { + return false; + } + + @Override + public Interval getNextInterval(@SuppressWarnings("unused") double time, + @SuppressWarnings("unused") Interval buffer) { + throw new TimeOutOfBoundsException( + "This schedule covers all valid times, so there is only one interval."); + } + + @Override + public boolean equals(Object object) { + return Coverages.equalsImplementation(this, object); + } + + @Override + public int hashCode() { + return Coverages.hashCodeImplementation(this); + } + + }; + + /** + * Determines if the coverage of the data source contains the requested time. + * + * @param time the time of interest + * + * @return true if the coverage contains time, false otherwise + */ + public boolean contains(double time); + + /** + * Retrieve the interval that bounds the entire coverage. In general the start of the bounding + * interval must be equal to (or at worst precede) the start of the first interval of coverage. + * Similarly the end of the bounding interval must be equal to (or at worst follow) the end of the + * final interval of coverage. + * + * @param buffer a buffer to receive the time interval. + * + * @return a reference to buffer for chaining convenience + */ + public Interval getBoundingInterval(Interval buffer); + + /** + * Obtain an interval, of contiguous coverage, that brackets the requested time and is contained + * entirely within the coverage of the instance. + * + * @param time the time of interest + * @param buffer a buffer to receive the bracketing interval + * + * @return a reference to buffer for convenience + * + * @throws TimeOutOfBoundsException if the supplied time argument lies outside an interval + * contained in the coverage + */ + public Interval getBracketingInterval(double time, Interval buffer); + + /** + * Determines whether an additional interval of coverage follows the supplied time. + * + * @param time the time of interest + * + * @return true, if {@link Coverage#getNextInterval(double, Interval)} will succeed with time as + * it's argument; false otherwise. + */ + public boolean hasNextInterval(double time); + + /** + * Obtain the next interval of coverage that follows the supplied time. + * + * @param time the time of interest + * @param buffer a buffer to receive the bracketing interval + * + * @return a reference to buffer for convenience + * + * @throws TimeOutOfBoundsException if the supplied time argument lies within or after the last + * actual interval of coverage supported by the instance. + */ + public Interval getNextInterval(double time, Interval buffer); + + /** + * {@inheritDoc} + * + * This method should be implemented, and users are strongly encouraged to utilize: + * {@link Coverages#equalsImplementation(Coverage, Object)}. + */ + @Override + public boolean equals(Object object); + + /** + * {@inheritDoc} + * + * This method should be implemented, and users are strongly encouraged to utilize: + * {@link Coverages#hashCodeImplementation(Coverage)} + */ + @Override + public int hashCode(); + +} diff --git a/src/main/java/picante/mechanics/Coverages.java b/src/main/java/picante/mechanics/Coverages.java new file mode 100644 index 0000000..88d69f2 --- /dev/null +++ b/src/main/java/picante/mechanics/Coverages.java @@ -0,0 +1,861 @@ +package picante.mechanics; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static picante.math.PicanteMath.min; +import static picante.math.PicanteMath.ulp; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.MoreObjects.ToStringHelper; +import com.google.common.collect.UnmodifiableIterator; +import picante.math.intervals.Interval; +import picante.math.intervals.IntervalSet; +import picante.math.intervals.UnwritableInterval; + +/** + * Collection of static utility methods for working with implementations of the {@link Coverage} + * interface. + */ +public class Coverages { + + /** + * Lock up the constructor as this is a static utility method. + */ + private Coverages() {} + + /** + * Adapts an {@link IntervalSet} to a {@link Coverage}. + * + * @param set the set to adapt + * + * @return an implementation of the interface utilizing the contents of the interval set. + * + * @throws IllegalArgumentException if the supplied set is empty. Coverages must contain at least + * one point of validity. + */ + public static Coverage create(IntervalSet set) { + return new IntervalSetCoverage(set); + } + + public static Interval getFirstInterval(Coverage coverage, Interval buffer) { + + /* + * This is a bit tricky, mainly because of how the Coverage interface method semantics are + * defined. We know the individual intervals within coverage must be a subset of the bounding + * interval. + */ + coverage.getBoundingInterval(buffer); + + double start = buffer.getBegin(); + + /* + * However, we do not know whether it is a proper subset or not. Determine if that is the case + * first. + */ + if (coverage.contains(start)) { + return coverage.getBracketingInterval(start, buffer); + } + + /* + * If we reach here, then the individual intervals in the coverage, particularly the first are + * proper subsets of the bounding interval. Fetch the next interval. + */ + return coverage.getNextInterval(start, buffer); + } + + /** + * Returns the tight boundary for the supplied coverage. Coverage implementations are allowed to + * supply a loose boundary in the {@link Coverage#getBoundingInterval(Interval)} method. While, in + * general, this is a bad idea for performance reasons it is permitted by the interface. This + * method extracts the tight bounds of the supplied coverage. + * + * @param coverage + * @param buffer + * + * @return a reference to buffer for convenience + */ + public static Interval tightBoundary(Coverage coverage, Interval buffer) { + + coverage.getBoundingInterval(buffer); + + double end = buffer.getEnd(); + double begin = buffer.getBegin(); + + boolean containsBegin = coverage.contains(begin); + boolean containsEnd = coverage.contains(end); + + /* + * Simple case is that the boundary is contained in the coverage. + */ + if (containsBegin && containsEnd) { + return buffer; + } + + /* + * At this point we have to tighten one end of the boundary or both. The beginning is simple, + * just pop the next interval out of the coverage instance. + */ + if (!containsBegin) { + begin = coverage.getNextInterval(begin, buffer).getBegin(); + } + + /* + * This isn't too helpful, as the only way to handle this is to iterate through all of the + * intervals in the coverage instance. + */ + if (!containsEnd) { + double time = begin - ulp(begin); + + while (coverage.hasNextInterval(time)) { + coverage.getNextInterval(time, buffer); + time = buffer.getEnd(); + } + end = time; + } + + buffer.set(begin, end); + return buffer; + } + + /** + * Captures a snapshot of the supplied coverage into an interval set. + * + * @param coverage the coverage to copy + * + * @return a newly created interval set containing the intervals of coverage specified by coverage + */ + public static IntervalSet snapshotToIntervalSet(Coverage coverage) { + + /* + * Don't do the work unless it's absolutely necessary. + */ + if (coverage instanceof IntervalSetCoverage) { + return ((IntervalSetCoverage) coverage).getSet(); + } + + IntervalSet.Builder builder = IntervalSet.builder(); + + Interval buffer = new Interval(); + + /* + * Fetch the first interval, then bootstrap our way to the end using the usual hasNext getNext + * mantra. + */ + builder.add(getFirstInterval(coverage, buffer)); + + double time = buffer.getEnd(); + + while (coverage.hasNextInterval(time)) { + builder.add(coverage.getNextInterval(time, buffer)); + time = buffer.getEnd(); + } + + return builder.build(); + } + + /** + * Creates an implementation of the coverage interface that captures a snapshot of the supplied + * interval. + * + * @param interval the interval from which a coverage is to be created + * + * @return a newly created coverage instance capturing the supplied interval + */ + public static Coverage createSnapshot(UnwritableInterval interval) { + return new IntervalCoverage(interval); + } + + /** + * Creates an implementation of the coverage interface that captures the specified interval + * + * @param begin the start of the coverage region + * @param end the end of the coverage region + * + * @return a newly created coverage instance that captures the interval [begin,end]. + * + * @throws IllegalArgumentException if end < begin + */ + public static Coverage create(double begin, double end) { + return new IntervalCoverage(begin, end); + } + + /** + * Tests if a coverage instance consists of a single interval of coverage. + * + * @param coverage the coverage instance to test + * + * @return true if the coverage instance consists of a single interval of coverage, false + * otherwise. Note: the interval may be a singleton. + */ + public static boolean isSingleInterval(Coverage coverage) { + + /* + * Handle two trivial cases first. + */ + if (coverage instanceof IntervalCoverage) { + return true; + } + if (coverage instanceof IntervalSetCoverage) { + return ((IntervalSetCoverage) coverage).getSet().size() == 1; + } + + Interval buffer = new Interval(); + + getFirstInterval(coverage, buffer); + return !coverage.hasNextInterval(buffer.getEnd()); + } + + /** + * Wraps the coverage instance into an {@link Iterable}. If coverage mutates while an iteration is + * on-going, then the behavior of the resultant iterator is unspecified. + * + * @param coverage the coverage to wrap + * + * @return a newly created {@link Iterable} that produces intervals from coverage + */ + public static Iterable+ * TODO: As implemented, the instance returned by this method is horribly inefficient. + *
+ * + * @param a a coverage instance + * @param others an array of other coverage instances + * + * @return a derived coverage implementation that captures the union of all the supplied coverages + */ + public static Coverage union(final Coverage a, final Coverage... others) { + + /* + * TODO: Check to see if the inputs are all instances of IntervalCoverage or + * IntervalSetCoverage. We can greatly accelerate the performance of the resultant coverage if + * this is the case. + */ + + return new AbstractCoverage() { + + private Coverage createCoverage() { + IntervalSet.Builder builder = IntervalSet.builder(Coverages.snapshotToIntervalSet(a)); + for (Coverage c : others) { + builder.union(Coverages.snapshotToIntervalSet(c)); + } + return Coverages.create(builder.build()); + } + + @Override + public boolean hasNextInterval(double time) { + return createCoverage().hasNextInterval(time); + } + + @Override + public Interval getNextInterval(double time, Interval buffer) { + return createCoverage().getNextInterval(time, buffer); + } + + @Override + public Interval getBracketingInterval(double time, Interval buffer) { + return createCoverage().getBracketingInterval(time, buffer); + } + + @Override + public Interval getBoundingInterval(Interval buffer) { + return createCoverage().getBoundingInterval(buffer); + } + + @Override + public boolean contains(double time) { + if (a.contains(time)) { + return true; + } + for (Coverage c : others) { + if (c.contains(time)) { + return true; + } + } + return false; + } + + }; + } + + /** + * Creates a new coverage instance that is the union of the supplied coverages. + *+ * TODO: As implemented, the instance returned by this method is horribly inefficient. + *
+ * + * @param a a coverage instance + * @param b another coverage instance + * + * @return a derived implementation of {@link Coverage} that captures the union of the supplied + * coverages + */ + public static Coverage union(final Coverage a, final Coverage b) { + + /* + * TODO: Check to see if the inputs are all instances of IntervalCoverage or + * IntervalSetCoverage. We can greatly accelerate the performance of the resultant coverage if + * this is the case. + */ + + return new AbstractCoverage() { + + private Coverage createCoverage() { + IntervalSet.Builder builder = IntervalSet.builder(Coverages.snapshotToIntervalSet(a)); + builder.union(Coverages.snapshotToIntervalSet(b)); + return Coverages.create(builder.build()); + } + + @Override + public boolean contains(double time) { + return a.contains(time) || b.contains(time); + } + + @Override + public Interval getBoundingInterval(Interval buffer) { + return createCoverage().getBoundingInterval(buffer); + } + + @Override + public Interval getBracketingInterval(double time, Interval buffer) { + return createCoverage().getBracketingInterval(time, buffer); + } + + @Override + public boolean hasNextInterval(double time) { + return createCoverage().hasNextInterval(time); + } + + @Override + public Interval getNextInterval(double time, Interval buffer) { + return createCoverage().getNextInterval(time, buffer); + } + + }; + } + + /** + * Creates a new coverage by intersecting the supplied coverages. + *+ * TODO: As implemented the instance returned by this method is horribly inefficient. + *
+ * + * @param a a coverage instance + * @param others an array of other coverage instances + * + * @return an implementation of {@link Coverage} that provides the intersection of the supplied + * coverages. As the empty set is not supported by the {@link Coverage} interface, if this + * derived coverage mutates in such a way as to become empty, the methods on the instance + * may begin to throw {@link IllegalStateException}. + * + * @throws IllegalArgumentException if the supplied coverages do not intersect + */ + public static Coverage intersect(final Coverage a, final Coverage... others) { + + /* + * TODO: Check to see if the inputs are all instances of IntervalCoverage or + * IntervalSetCoverage. We can greatly accelerate the performance of the resultant coverage if + * this is the case. + */ + + checkArgument(intersects(a, others), + "Coverages do not intersect. Empty set coverages are not permitted."); + + return new AbstractCoverage() { + + private Coverage createCoverage() { + IntervalSet.Builder builder = IntervalSet.builder(Coverages.snapshotToIntervalSet(a)); + for (Coverage c : others) { + builder.intersect(Coverages.snapshotToIntervalSet(c)); + } + IntervalSet set = builder.build(); + checkState(!set.isEmpty(), "Intersected coverage has become empty."); + return Coverages.create(builder.build()); + } + + @Override + public boolean hasNextInterval(double time) { + return createCoverage().hasNextInterval(time); + } + + @Override + public Interval getNextInterval(double time, Interval buffer) { + return createCoverage().getNextInterval(time, buffer); + } + + @Override + public Interval getBracketingInterval(double time, Interval buffer) { + return createCoverage().getBracketingInterval(time, buffer); + } + + @Override + public Interval getBoundingInterval(Interval buffer) { + return createCoverage().getBoundingInterval(buffer); + } + + @Override + public boolean contains(double time) { + if (a.contains(time)) { + for (Coverage c : others) { + if (!c.contains(time)) { + return false; + } + } + } + return true; + } + + }; + } + + /** + * Creates a coverage that is the intersection of the supplied coverages. + *+ * TODO: The instance returned from this method is horribly inefficient. + *
+ * + * @param a a coverage instance + * @param b another coverage instance + * + * @return an implementation of the {@link Coverage} interface that provides the intersection of a + * and b. As the empty set is not supported by the {@link Coverage} interface, if a and b + * mutate in such a way as to become empty, the methods on the implementation may throw + * {@link IllegalStateException}. + * + * @throws IllegalArgumentException if a and b do not intersect. + */ + public static Coverage intersect(final Coverage a, final Coverage b) { + + /* + * Simple check, if a and b represent the exact same instance, no work is to be done. + */ + if (a == b) { + return a; + } + + /* + * TODO: Check to see if the inputs are all instances of IntervalCoverage or + * IntervalSetCoverage. We can greatly accelerate the performance of the resultant coverage if + * this is the case. + */ + + /* + * Check the intersection of a and b. If they do not currently intersect, then + */ + checkArgument(intersects(a, b), + "Coverages do not intersect. Empty coverages are not permitted."); + + return new AbstractCoverage() { + + private Coverage createCoverage() { + IntervalSet.Builder builder = IntervalSet.builder(Coverages.snapshotToIntervalSet(a)); + builder.intersect(Coverages.snapshotToIntervalSet(b)); + + IntervalSet set = builder.build(); + checkState(!set.isEmpty(), "Intersected coverage has become empty."); + + return Coverages.create(set); + } + + @Override + public boolean contains(double time) { + return a.contains(time) && b.contains(time); + } + + @Override + public Interval getBoundingInterval(Interval buffer) { + return createCoverage().getBoundingInterval(buffer); + } + + @Override + public Interval getBracketingInterval(double time, Interval buffer) { + return createCoverage().getBracketingInterval(time, buffer); + } + + @Override + public boolean hasNextInterval(double time) { + return createCoverage().hasNextInterval(time); + } + + @Override + public Interval getNextInterval(double time, Interval buffer) { + return createCoverage().getNextInterval(time, buffer); + } + + }; + } + + /** + * Determines whether the sequence of coverages intersect. + * + * @param a a coverage object + * @param others other coverage objects + * + * @return true if a and others have non-empty intersection, false otherwise + */ + public static boolean intersects(Coverage a, Coverage... others) { + IntervalSet.Builder builder = IntervalSet.builder(snapshotToIntervalSet(a)); + for (Coverage c : others) { + builder.intersect(snapshotToIntervalSet(c)); + } + return !(builder.size() == 0); + } + + /** + * Determines whether two coverage instances intersect. + * + * @param a a coverage object + * @param b another coverage object + * + * @return true if a and b have non-empty intersection, false otherwise + */ + public static boolean intersects(Coverage a, Coverage b) { + IntervalSet setA = snapshotToIntervalSet(a); + IntervalSet setB = snapshotToIntervalSet(b); + return !setA.intersect(setB).isEmpty(); + } + + /** + * An implementation of {@link Coverage#toString()}. + * + * @param coverage the coverage object for which a string representation is desired + * + * @return the string representation, in general of the form "ClassName{[interval], + * [interval]...}" + */ + public static String toStringImplementation(Coverage coverage) { + ToStringHelper helper = MoreObjects.toStringHelper(coverage); + + for (UnwritableInterval interval : iterable(coverage)) { + helper.addValue(interval); + } + + return helper.toString(); + } + + /** + * An implementation of {@link Coverage#equals(Object)}. Note: two coverage instances are equal if + * and only if all of the intervals contained in one are present in the other. They may have + * different {@link Coverage#getBoundingInterval(Interval)} specifications, but are still equal. + * + * @param coverage the coverage object upon which equals is invoked. + * @param object the object to determine equality with coverage + * + * @return true if the coverage objects are equivalent, false otherwise + */ + public static boolean equalsImplementation(Coverage coverage, Object object) { + + if (object == checkNotNull(coverage)) { + return true; + } + if (!(object instanceof Coverage)) { + return false; + } + + Coverage o = (Coverage) object; + + /* + * Unfortunately we can't check the boundary interval, because they are not required to be + * tightly bound to the actual coverage which is what is in question here. Start the deep + * comparison. + */ + Interval coverageBuffer = new Interval(); + Interval oBuffer = new Interval(); + + /* + * Start the loop, but handle the first interval separately. + */ + getFirstInterval(coverage, coverageBuffer); + getFirstInterval(o, oBuffer); + + if (!coverageBuffer.equals(oBuffer)) { + return false; + } + + /* + * Enter the loop at the minimum time necessary to pop the next interval in either of the two + * coverages. + */ + double time = min(coverageBuffer.getEnd(), oBuffer.getEnd()); + + boolean coverageHasNext = coverage.hasNextInterval(time); + boolean oHasNext = o.hasNextInterval(time); + + while (coverageHasNext && oHasNext) { + coverage.getNextInterval(time, coverageBuffer); + o.getNextInterval(time, oBuffer); + + if (!coverageBuffer.equals(oBuffer)) { + return false; + } + + time = coverageBuffer.getEnd(); + coverageHasNext = coverage.hasNextInterval(time); + oHasNext = o.hasNextInterval(time); + } + + /* + * If we reach here then the coverage instances are equivalent only if they both do not have any + * remaining intervals. + */ + return (!coverageHasNext) && (!oHasNext); + } + + /** + * An implementation of {@link Coverage#hashCode()} consistent with + * {@link Coverages#equalsImplementation(Coverage, Object)}. + * + * @param coverage the coverage object for which the hashCode is to be computed + * + * @return the hashCode + */ + public static int hashCodeImplementation(Coverage coverage) { + + int hashCode = 1; + Interval buffer = new Interval(); + + /* + * Handle the first interval separately. + */ + getFirstInterval(coverage, buffer); + double time = buffer.getEnd(); + hashCode = 31 * hashCode + buffer.hashCode(); + + while (coverage.hasNextInterval(time)) { + coverage.getNextInterval(time, buffer); + hashCode = 31 * hashCode + buffer.hashCode(); + time = buffer.getEnd(); + } + + return hashCode; + } + +} diff --git a/src/main/java/picante/mechanics/EphemerisAndFrameProvider.java b/src/main/java/picante/mechanics/EphemerisAndFrameProvider.java new file mode 100644 index 0000000..948d8ca --- /dev/null +++ b/src/main/java/picante/mechanics/EphemerisAndFrameProvider.java @@ -0,0 +1,5 @@ +package picante.mechanics; + +public interface EphemerisAndFrameProvider extends FrameProvider, EphemerisProvider { + +} diff --git a/src/main/java/picante/mechanics/EphemerisEvaluationException.java b/src/main/java/picante/mechanics/EphemerisEvaluationException.java new file mode 100644 index 0000000..60315c3 --- /dev/null +++ b/src/main/java/picante/mechanics/EphemerisEvaluationException.java @@ -0,0 +1,34 @@ +package picante.mechanics; + +/** + * If an attempt to evaluate a position or state vector function fails for any reason, this runtime + * exception or derivatives of it should be thrown to indicate the failure. + *+ * The reason this is a runtime exception is that requesting position or state transforms from the + * associated functions is something that in many applications will occur frequently and in a + * distributed sense throughout the code. As such, these methods should generate runtime exceptions + * only, so as not to annoy developers utilizing these methods in the interface. + *
+ */ +public class EphemerisEvaluationException extends EvaluationRuntimeException { + + /** + * Default serial version ID. + */ + private static final long serialVersionUID = 1L; + + public EphemerisEvaluationException() {} + + public EphemerisEvaluationException(String message) { + super(message); + } + + public EphemerisEvaluationException(Throwable cause) { + super(cause); + } + + public EphemerisEvaluationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/picante/mechanics/EphemerisID.java b/src/main/java/picante/mechanics/EphemerisID.java new file mode 100644 index 0000000..e933cdb --- /dev/null +++ b/src/main/java/picante/mechanics/EphemerisID.java @@ -0,0 +1,20 @@ +package picante.mechanics; + +/** + * Interface defining an ephemeris ID code, used to reference specific frames in the mechanics + * package. + *+ * TODO: Mention the equals() comparision method vs. ==, as this will be important. TODO: Mention + * the immutability requirement that could orphan entries in a hash table + *
+ */ +public interface EphemerisID { + + /** + * Obtain the name of the object associated with this ID. + * + * @return the name + */ + public String getName(); + +} diff --git a/src/main/java/picante/mechanics/EphemerisProvider.java b/src/main/java/picante/mechanics/EphemerisProvider.java new file mode 100644 index 0000000..8abee0e --- /dev/null +++ b/src/main/java/picante/mechanics/EphemerisProvider.java @@ -0,0 +1,112 @@ +package picante.mechanics; + +import java.util.List; +import java.util.Set; + +/** + * An interface describing a source of derived ephemeris position and state functions. + * + *
+ * Implementor Notes: Choices available: ignore coverage argument supplied to create
+ * functions. Level of thread safety (none, accessing create methods, accessing transform methods.)
+ * Whether the internal frame provider is always to be that of a particular implementation that
+ * functions well with your implementation. Does your implementation do any checking at construction
+ * time to verify that the links can be met over the requested times?
+ */
+public interface EphemerisProvider {
+
+ /**
+ * Retrieves the list of ephemeris sources in load order from the provider.
+ *
+ * @param buffer a list to which the sources of the ephemeris provider are to be appended. The
+ * first entry added to the list is the lowest priority, the last the highest.
+ *
+ * @return a reference to buffer for convenience
+ */
+ public List
+ * NoteAll this implies is that a frame source contains some data connecting to this
+ * particular frame. It may not be possible to connect two arbitrary frames at any time of which
+ * the provider is aware.
+ *
+ * The reason this is a runtime exception is that requesting state or frame transforms from the
+ * associated functions is something that in many applications will occur frequently and in a
+ * distributed sense throughout the code. As such, these methods should generate runtime exceptions
+ * only, so as not to annoy developers utilizing these methods in the interface.
+ *
+ * TODO: Mention the equals() comparison method, vs. ==, as this will be important.
+ *
+ * Note: This is just an optimization hint, indicating to the frame system that, because
+ * the frame is inertial, certain shortcuts with regards to computation of states can be taken.
+ * So, it is possible that an inertial frame code might return
+ * Implementor Notes: Choices available: ignore coverage argument supplied to the create
+ * functions. Level of thread safety (none, accessing create methods, accessing transform methods)
+ *
+ * Note:All this implies is that a frame source contains some data connecting to this
+ * particular frame. It may not be possible to connect two arbitrary frames at any time of which
+ * the provider is aware.
+ *
+ * As a consequence of this inversion, the to and from ID fields are swapped.
+ *
+ * The resultant function will throw {@link FrameEvaluationException} if the supplied time lies
+ * outside the supplied coverage.
+ *
+ * This class is package private, as consumers of it should not care that the underlying
+ * implementation is backed by a single interval. If they do, the method:
+ * {@link Coverages#isSingleInterval(Coverage)} exists for that purpose.
+ *
+ * This class is package private, as consumers of it should not care that the underlying
+ * implementation is backed by an interval set.
+ *
+ * Recall that the frame chaining system defines a tree of leaf to node connections, where each
+ * link in the tree is an individual {@link FrameTransformFunction}. This method simply looks for
+ * all sources that have frame as their leaf {@link FrameTransformFunction#getFromID()}, and
+ * snapshots the aggregated coverage.
+ *
+ * If you are connecting one frame to another through multiple links in a tree, this only tells
+ * one part of the coverage story. It's entirely possible that some link further up the tree will
+ * have gaps independent of those reported here.
+ *
+ * Recall that the ephemeris chaining system defines a tree of leaf to node connections, where
+ * each link in the tree is an individual {@link PositionVectorFunction}. This method simply looks
+ * for all sources that have object as their leaf {@link PositionVectorFunction#getTargetID()},
+ * and snapshots the aggregated coverage.
+ *
+ * If you are connecting one object to another through multiple links in a tree, this only tells
+ * one part of the coverage story. It's entirely possible that the underlying required frames will
+ * have a coverage gap, or some link further up the tree will have gaps independent of those
+ * reported by this method.
+ *
+ * This method does not create implementations that are the most efficient with regards to
+ * interfacing with the Collections API (i.e. equals and hashCode), but they should be fast
+ * enough.
+ *
+ * @param
+ * As a consequence of this negation, the observer and target ID fields are swapped.
+ *
+ * The resultant function reports transform toID as its frameID.
+ *
+ * The resultant function will generate {@link EphemerisEvaluationException} if the supplied time
+ * is out of the bounds of the coverage
+ *
+ * The fields containing the actual data are defined in the parent class, per the usual weak
+ * immutability pattern.
+ *
+ * Due to the block nature of a state transform, and the individual constraints on the blocks, one
+ * can show that the following is true:
+ *
+ *
+ * Diagrams borrowed directly from NAIF's INVSTM routine in SPICELIB.
+ *
+ * As a consequence of this inversion, the to and from ID fields are swapped.
+ *
+ * As the rotation is fixed, the rotation derivative components will always evaluate to
+ * {@link MatrixIJK#ZEROS}.
+ *
+ * As the rotation is fixed, the rotation derivative components will always evaluate to
+ * {@link MatrixIJK#ZEROS}.
+ *
+ * The resultant function will throw {@link FrameEvaluationException} if the supplied time lies
+ * outside the supplied coverage.
+ *
+ * This function estimates the derivative of the supplied frame transform function by assuming a
+ * constant angular rate of rotation between the requested time and the requested time + delta.
+ * This is the value used to populate the derivative components of the {@link StateTransform}.
+ *
+ * The requested value of delta (including its sign) is used everywhere it is possible to do so.
+ * If time + delta lies outside the interval of coverage containing time, the code will attempt to
+ * use time - delta to estimate the derivative instead.
+ *
+ * This function estimates the derivative of the supplied frame transform function by assuming a
+ * constant angular rate of rotation between the requested time and the requested time +
+ * delta(time). This is the value used to populate the derivative components of the
+ * {@link StateTransform}.
+ *
+ * The value of delta(time) (including its sign) is used everywhere it is possible to do so. If
+ * time + delta(time) lies outside the interval of coverage containing time, the code will attempt
+ * to use time - delta(time) to estimate the derivative instead.
+ *
+ * The fields containing the actual data are defined in the parent class, per the usual weak
+ * immutability pattern.
+ *
+ * As a consequence of this negation, the observer and target ID fields are swapped.
+ *
+ * The resultant function reports transform toID as its frameID.
+ *
+ * As the vector is fixed, the velocity components will always evaluate to {@link VectorIJK#ZERO}.
+ *
+ * As the vector is fixed, the velocity components will always evaluate to {@link VectorIJK#ZERO}.
+ *
+ * The resultant function will generate {@link EphemerisEvaluationException} if the supplied time
+ * is not contained within the supplied coverage.
+ *
+ * The class is simply a container for two matrices, a rotation and it's derivative. This class
+ * provides functions to obtain unwritable views of the internal memory allocated by the class.
+ *
+ * Note: You are obtaining an unwritable view of the actual buffers allocated in the class when you
+ * call the appropriate get methods. As a direct consequence of this, if you are holding onto a
+ * reference to the writable subclass the contents of the buffer could change out from underneath
+ * you. If this is a concern then copy the contents of the buffer upon receiving them.
+ *
+ * This method makes an unwritable copy only if necessary. It tries to avoid making a copy
+ * wherever possible.
+ *
+ * The class is simply a container for two vectors, a state's position and it's time derivative.
+ * This class provides functions to obtain unwritable views of the internal memory allocated by the
+ * class.
+ *
+ * Note: When you utilize the get methods on this class, you are receiving an actual unwritable view
+ * of the actual buffers allocated in the class. As a direct consequence of this, if you are holding
+ * onto a reference to the writable subclass, the contents of the buffer could change out from
+ * underneath you. If this is a concern, then copy the contents of what you retrieved into memory
+ * outside the control of this class.
+ *
+ * The dot product can be computed by retrieving the position vectors directly and applying
+ * {@link VectorIJK#getDot(UnwritableVectorIJK)} or using the convenience method:
+ * {@link UnwritableStateVector#getDot(UnwritableStateVector)} that does this for you.
+ *
+ * This method computes the value of the derivative of the norm of the position components of the
+ * state vector utilizing the velocity components like so:
+ *
+ * id loaded into the instance of
+ * the provider
+ */
+ public boolean isAwareOf(EphemerisID id);
+
+ /**
+ * Retrieves an unmodifiable copy of the frame provider supporting the frame transformations
+ * required to create the various ephemeris functions generated by the instance.
+ *
+ * @return a reference to the internal frame provider
+ */
+ public FrameProvider getFrameProvider();
+
+ /**
+ * Create a derived state vector function that links a supplied target to a supplied observer in
+ * the desired frame over the supplied time domain.
+ *
+ * @param target the ephemeris ID of the object to which the position vectors point.
+ * @param observer the ephemeris ID of the object from which the position vectors emanate.
+ * @param frame the frame ID of the frame in which the observer to target position vector and its
+ * time derivative are to be expressed
+ * @param domain the time domain over which the position vectors are expected to be computed.
+ * Note: Implementors may ignore this argument entirely, so as such consider it a
+ * suggestion at best. If you are working with a strict, construction time checking
+ * implementation, you must supply a subset of the available coverage loaded into the
+ * provider or obtain a resultant exception
+ *
+ * @return a newly created, derived implementation of a state vector function connecting the
+ * observer to the target in the requested frame
+ * over times specified by domain.
+ *
+ */
+ public StateVectorFunction createStateVectorFunction(EphemerisID target, EphemerisID observer,
+ FrameID frame, Coverage domain);
+
+
+ /**
+ * Create a derived position vector function that links a supplied target to a supplied observer
+ * in the desired frame over the supplied time domain.
+ *
+ * @param target the ephemeris ID of the object to which the position vector function vectors
+ * point.
+ * @param observer the ephemeris ID of the object from which the position vector function vectors
+ * emanate.
+ * @param frame the frame ID of the frame in which the observer to target position vector is to be
+ * expressed
+ * @param domain the time domain over which the position vectors are expected to be computed.
+ * Note: Implementors may ignore this argument entirely, so as such consider it a
+ * suggestion at best. If you are working with a strict, construction time checking
+ * implementation, you must supply a subset of the available coverage loaded into the
+ * provider or obtain a resultant exception
+ *
+ * @return a newly created, derived implementation of a position vector function connecting the
+ * observer to the target in the requested frame
+ * over times specified by domain.
+ *
+ */
+ public PositionVectorFunction createPositionVectorFunction(EphemerisID target,
+ EphemerisID observer, FrameID frame, Coverage domain);
+
+
+}
diff --git a/src/main/java/picante/mechanics/EphemerisSourceIOException.java b/src/main/java/picante/mechanics/EphemerisSourceIOException.java
new file mode 100644
index 0000000..585e154
--- /dev/null
+++ b/src/main/java/picante/mechanics/EphemerisSourceIOException.java
@@ -0,0 +1,27 @@
+package picante.mechanics;
+
+/**
+ * Exception generated when accessing the content of an ephemeris source fails for any reason.
+ */
+public class EphemerisSourceIOException extends SourceException {
+
+ /**
+ * Default serial version ID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ public EphemerisSourceIOException() {}
+
+ public EphemerisSourceIOException(String message) {
+ super(message);
+ }
+
+ public EphemerisSourceIOException(Throwable cause) {
+ super(cause);
+ }
+
+ public EphemerisSourceIOException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/main/java/picante/mechanics/EphemerisSourceLinkException.java b/src/main/java/picante/mechanics/EphemerisSourceLinkException.java
new file mode 100644
index 0000000..e71dcf0
--- /dev/null
+++ b/src/main/java/picante/mechanics/EphemerisSourceLinkException.java
@@ -0,0 +1,28 @@
+package picante.mechanics;
+
+/**
+ * Exception generated when an ephemeris provider is unable to link the requested target and
+ * observer in the creation of either a position or state function.
+ */
+public class EphemerisSourceLinkException extends SourceException {
+
+ /**
+ * Default serial version ID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ public EphemerisSourceLinkException() {}
+
+ public EphemerisSourceLinkException(String message) {
+ super(message);
+ }
+
+ public EphemerisSourceLinkException(Throwable cause) {
+ super(cause);
+ }
+
+ public EphemerisSourceLinkException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/main/java/picante/mechanics/EvaluationRuntimeException.java b/src/main/java/picante/mechanics/EvaluationRuntimeException.java
new file mode 100644
index 0000000..1e2b2a5
--- /dev/null
+++ b/src/main/java/picante/mechanics/EvaluationRuntimeException.java
@@ -0,0 +1,30 @@
+package picante.mechanics;
+
+import picante.exceptions.PicanteRuntimeException;
+
+/**
+ * An exception indicating an evaluation of an ephemeris or frame function has failed for any
+ * reason.
+ */
+public class EvaluationRuntimeException extends PicanteRuntimeException {
+
+ /**
+ * Default serial version ID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ public EvaluationRuntimeException() {}
+
+ public EvaluationRuntimeException(String message) {
+ super(message);
+ }
+
+ public EvaluationRuntimeException(Throwable cause) {
+ super(cause);
+ }
+
+ public EvaluationRuntimeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/main/java/picante/mechanics/ForwardingStateVectorFunction.java b/src/main/java/picante/mechanics/ForwardingStateVectorFunction.java
new file mode 100644
index 0000000..0ae5310
--- /dev/null
+++ b/src/main/java/picante/mechanics/ForwardingStateVectorFunction.java
@@ -0,0 +1,46 @@
+package picante.mechanics;
+
+import com.google.common.base.Preconditions;
+import picante.math.vectorspace.VectorIJK;
+
+public class ForwardingStateVectorFunction implements StateVectorFunction {
+
+ protected final StateVectorFunction delegate;
+
+ public ForwardingStateVectorFunction(StateVectorFunction delegate) {
+ super();
+ this.delegate = Preconditions.checkNotNull(delegate);
+ }
+
+ @Override
+ public EphemerisID getObserverID() {
+ return delegate.getObserverID();
+ }
+
+ @Override
+ public EphemerisID getTargetID() {
+ return delegate.getTargetID();
+ }
+
+ @Override
+ public FrameID getFrameID() {
+ return delegate.getFrameID();
+ }
+
+ @Override
+ public Coverage getCoverage() {
+ return delegate.getCoverage();
+ }
+
+ @Override
+ public VectorIJK getPosition(double time, VectorIJK buffer) {
+ return delegate.getPosition(time, buffer);
+ }
+
+
+ @Override
+ public StateVector getState(double time, StateVector buffer) {
+ return delegate.getState(time, buffer);
+ }
+
+}
diff --git a/src/main/java/picante/mechanics/FrameEvaluationException.java b/src/main/java/picante/mechanics/FrameEvaluationException.java
new file mode 100644
index 0000000..15d904a
--- /dev/null
+++ b/src/main/java/picante/mechanics/FrameEvaluationException.java
@@ -0,0 +1,34 @@
+package picante.mechanics;
+
+/**
+ * If an attempt to evaluate a frame or state transform function fails for any reason, this runtime
+ * exception or derivatives of it should be thrown to indicate the failure.
+ * false.
+ * id loaded into the provider
+ */
+ public boolean isAwareOf(FrameID id);
+
+ /**
+ * Create a derived frame transform function that produces rotations from the supplied fromID to
+ * the toID which is valid over the supplied domain.
+ *
+ * @param fromID the frame ID of the originating frame of the transform. Vectors in this frame can
+ * be rotated directly to the toID frame by application of the transforms produced by this
+ * function.
+ * @param toID the frame ID of the destination frame of the transform. Vectors in the fromID frame
+ * are rotated into this frame by application of the transforms produced by the requested
+ * function
+ * @param domain the time domain over which transforms are expected to be computed. Note:
+ * Implementors may opt to ignore this argument entirely, so as such consider it a
+ * suggestion. If you are working with a strict, construction time checking implementation,
+ * you must supply a subset of the available coverage loaded into the provider or obtain a
+ * resultant exception.
+ *
+ * @return a newly created, derived implementation of a frame transform function connecting fromID
+ * to toID over times specified by domain.
+ *
+ */
+ public FrameTransformFunction createFrameTransformFunction(FrameID fromID, FrameID toID,
+ Coverage domain);
+
+ /**
+ * Create a derived state transform function that produces state transformations from the supplied
+ * fromID to the toID which is valid over the supplied domain.
+ *
+ * @param fromID the frame ID of the originating frame of the transform. States in this frame can
+ * be transformed directly to the toID frame by application of the transforms produced by
+ * this function.
+ * @param toID the frame ID of the destination frame of the transform. States in the fromID frame
+ * are transformed into this frame by application of the transforms produced by the
+ * requested function
+ * @param domain the time domain over which transforms are expected to be computed. Note:
+ * Implementors may opt to ignore this argument entirely, so as such consider it a
+ * suggestion. If you are working with a strict, construction time checking implementation,
+ * you must supply a subset of the available coverage loaded into the provider or obtain a
+ * resultant exception.
+ *
+ * @return a newly created, derived implementation of a state transform function connecting fromID
+ * to toID over times specified by domain.
+ *
+ */
+ public StateTransformFunction createStateTransformFunction(FrameID fromID, FrameID toID,
+ Coverage domain);
+}
diff --git a/src/main/java/picante/mechanics/FrameSourceIOException.java b/src/main/java/picante/mechanics/FrameSourceIOException.java
new file mode 100644
index 0000000..f09a659
--- /dev/null
+++ b/src/main/java/picante/mechanics/FrameSourceIOException.java
@@ -0,0 +1,27 @@
+package picante.mechanics;
+
+/**
+ * Exception generated when accessing the content of a frame source fails for any reason.
+ */
+public class FrameSourceIOException extends SourceException {
+
+ /**
+ * Default serial version ID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ public FrameSourceIOException() {}
+
+ public FrameSourceIOException(String message) {
+ super(message);
+ }
+
+ public FrameSourceIOException(Throwable cause) {
+ super(cause);
+ }
+
+ public FrameSourceIOException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/main/java/picante/mechanics/FrameSourceLinkException.java b/src/main/java/picante/mechanics/FrameSourceLinkException.java
new file mode 100644
index 0000000..8c18744
--- /dev/null
+++ b/src/main/java/picante/mechanics/FrameSourceLinkException.java
@@ -0,0 +1,28 @@
+package picante.mechanics;
+
+/**
+ * Exception generated when a frame provider is unable to link the requested frames in the creation
+ * of a frame transform or state transform function.
+ */
+public class FrameSourceLinkException extends SourceException {
+
+ /**
+ * Default serial version ID.
+ */
+ private static final long serialVersionUID = 1L;
+
+ public FrameSourceLinkException() {}
+
+ public FrameSourceLinkException(String message) {
+ super(message);
+ }
+
+ public FrameSourceLinkException(Throwable cause) {
+ super(cause);
+ }
+
+ public FrameSourceLinkException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/main/java/picante/mechanics/FrameTransformFunction.java b/src/main/java/picante/mechanics/FrameTransformFunction.java
new file mode 100644
index 0000000..f1b0c39
--- /dev/null
+++ b/src/main/java/picante/mechanics/FrameTransformFunction.java
@@ -0,0 +1,70 @@
+package picante.mechanics;
+
+import picante.math.vectorspace.RotationMatrixIJK;
+
+/**
+ * Interface that describes a function that computes the frame transformation from one frame to
+ * another at the requested time. The frame transform rotates vectors from an originating frame,
+ * designated by the fromID to a destination frame, designated by the toID
+ */
+public interface FrameTransformFunction {
+
+ /**
+ * Retrieves the frame ID of the originating frame.
+ *
+ * @return the ID
+ */
+ public FrameID getFromID();
+
+ /**
+ * Retrieves the frame ID of the destination frame.
+ *
+ * @return the ID
+ */
+ public FrameID getToID();
+
+ /**
+ * Retrieves the coverage over which the frame transformation is valid.
+ *
+ * @return the coverage
+ */
+ public Coverage getCoverage();
+
+ /**
+ * Obtain the frame transform between the fromID and the toID at the
+ * requested time.
+ *
+ * @param time the time at which the frame transform is to be computed. This should be contained
+ * within the coverage reported by the function via the
+ * {@link FrameTransformFunction#getCoverage()} method.
+ *
+ * @return the result frame transformation.
+ *
+ * @throws FrameEvaluationException a runtime exception, if anything goes awry in the computation
+ * of the frame transform. Implementors are encouraged to subclass this exception for
+ * their own purposes
+ */
+ public default RotationMatrixIJK getTransform(double time) {
+ return getTransform(time, new RotationMatrixIJK());
+ }
+
+ /**
+ * Obtain the frame transform between the fromID and the toID at the
+ * requested time.
+ *
+ * @param time the time at which the frame transform is to be computed. This should be contained
+ * within the coverage reported by the function via the
+ * {@link FrameTransformFunction#getCoverage()} method.
+ *
+ * @param buffer a buffer to capture the result frame transformation, or null if the underlying
+ * implementation is to create a new instance and return it instead.
+ *
+ * @return a reference to buffer for convenience.
+ *
+ * @throws FrameEvaluationException a runtime exception, if anything goes awry in the computation
+ * of the frame transform. Implementors are encouraged to subclass this exception for
+ * their own purposes
+ */
+ public RotationMatrixIJK getTransform(double time, RotationMatrixIJK buffer);
+
+}
diff --git a/src/main/java/picante/mechanics/FrameTransformFunctions.java b/src/main/java/picante/mechanics/FrameTransformFunctions.java
new file mode 100644
index 0000000..499c0d4
--- /dev/null
+++ b/src/main/java/picante/mechanics/FrameTransformFunctions.java
@@ -0,0 +1,321 @@
+package picante.mechanics;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import picante.math.functions.RotationMatrixIJKFunction;
+import picante.math.vectorspace.RotationMatrixIJK;
+import picante.math.vectorspace.UnwritableRotationMatrixIJK;
+
+/**
+ * Class containing static utility methods for manipulating and working with
+ * {@link FrameTransformFunction}s.
+ */
+public class FrameTransformFunctions {
+
+ /**
+ * Static utility method umbrella, can not be instantiated.
+ */
+ private FrameTransformFunctions() {}
+
+ /**
+ * Creates a new function from the source by inverting (transposing) the supplied transformation.
+ * Coverage interface that captures a single interval in
+ * time.
+ *
+ *
+ *
+ * TODO: (added by JonV) talk to Scott about possibly separating out the identity stuff from
+ * the function stuff
+ */
+public interface PositionVectorFunction {
+
+ /**
+ * Retrieves the ephemeris ID of the observer object.
+ *
+ * @return the ID
+ */
+ public EphemerisID getObserverID();
+
+ /**
+ * Retrieves the ephemeris ID of the target object.
+ *
+ * @return the ID
+ */
+ public EphemerisID getTargetID();
+
+ /**
+ * Retrieves the frame ID of the frame in which the vector from the observer to the target is
+ * computed.
+ *
+ * @return the ID
+ */
+ public FrameID getFrameID();
+
+ /**
+ * Retrieves the coverage over which the position vector function is valid.
+ *
+ * @return the coverage
+ */
+ public Coverage getCoverage();
+
+ /**
+ * Obtain the position vector connecting the
+ * OBSERVER --------------> TARGET
+ * observerID to the targetID
+ * in the specified frameID at the requested time.
+ *
+ * @param time the time at which the position vector is to be computed. This should be contained
+ * within the coverage reported by the function via the
+ * {@link PositionVectorFunction#getCoverage()} method.
+ *
+ * @return the resultant position vector.
+ *
+ * @throws EphemerisEvaluationException a runtime exception, if anything goes awry in the
+ * computation of the position vector. Implementors are encouraged to subclass this
+ * exception for their own purposes.
+ */
+ public default VectorIJK getPosition(double time) {
+ return getPosition(time, new VectorIJK());
+ }
+
+ /**
+ * Obtain the position vector connecting the observerID to the targetID
+ * in the specified frameID at the requested time.
+ *
+ * @param time the time at which the position vector is to be computed. This should be contained
+ * within the coverage reported by the function via the
+ * {@link PositionVectorFunction#getCoverage()} method.
+ *
+ * @param buffer a buffer to capture the resultant position vector, or null if the underlying
+ * implementation is to create a new instance and return it instead.
+ *
+ * @return a reference to buffer for convenience.
+ *
+ * @throws EphemerisEvaluationException a runtime exception, if anything goes awry in the
+ * computation of the position vector. Implementors are encouraged to subclass this
+ * exception for their own purposes.
+ */
+ public VectorIJK getPosition(double time, VectorIJK buffer);
+
+}
diff --git a/src/main/java/picante/mechanics/PositionVectorFunctions.java b/src/main/java/picante/mechanics/PositionVectorFunctions.java
new file mode 100644
index 0000000..cb18651
--- /dev/null
+++ b/src/main/java/picante/mechanics/PositionVectorFunctions.java
@@ -0,0 +1,218 @@
+package picante.mechanics;
+
+import picante.math.functions.VectorIJKFunction;
+import picante.math.vectorspace.RotationMatrixIJK;
+import picante.math.vectorspace.UnwritableVectorIJK;
+import picante.math.vectorspace.VectorIJK;
+
+/**
+ * Class capturing static utility methods useful for manipulating and working with
+ * {@link PositionVectorFunction}s.
+ */
+public class PositionVectorFunctions {
+
+ /**
+ * Static utility method umbrella, can not be instantiated.
+ */
+ private PositionVectorFunctions() {}
+
+ /**
+ * Adapts the supplied position vector function to the {@link VectorIJKFunction} interface.
+ *
+ * @param function the function to adapt
+ *
+ * @return an adapter that passes the time argument through to the
+ * {@link PositionVectorFunction#getPosition(double, VectorIJK)} method.
+ */
+ public static VectorIJKFunction adapt(final PositionVectorFunction function) {
+ return new VectorIJKFunction() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return function.getPosition(t, buffer);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function from the source by applying a scale factor to the returned position
+ * vector.
+ *
+ * @param function the function to scale
+ * @param scale the positive scale factor to apply to function's evaluations
+ *
+ * @return a newly created function that wraps the supplied function and scales the output on
+ * evaluation
+ *
+ * @throws IllegalArgumentException if scale is not strictly positive.
+ */
+ public static PositionVectorFunction scale(final PositionVectorFunction function,
+ final double scale) {
+
+ /*
+ * Check to see if the scale argument is less than zero, if it is throw an illegal argument
+ * exception as this method is only designed to apply positive scals to the function.
+ */
+ if (scale <= 0.0) {
+ throw new IllegalArgumentException(
+ "Scale factor: " + scale + " is invalid, as it is less than or equal to 0.0");
+ }
+
+ return new AbstractPositionVectorFunctionWrapper(function) {
+ @Override
+ public VectorIJK getPosition(double time, VectorIJK buffer) {
+ buffer = buffer == null ? new VectorIJK() : buffer;
+ function.getPosition(time, buffer);
+ return buffer.scale(scale);
+ }
+ };
+ }
+
+ /**
+ * Creates a new function from the source by negating the vector.
+ * String containing the name: filename, database name, etc. of a
+ * particular source.
+ */
+ public String getName();
+
+}
diff --git a/src/main/java/picante/mechanics/SourceMetaDatas.java b/src/main/java/picante/mechanics/SourceMetaDatas.java
new file mode 100644
index 0000000..16cf19b
--- /dev/null
+++ b/src/main/java/picante/mechanics/SourceMetaDatas.java
@@ -0,0 +1,15 @@
+package picante.mechanics;
+
+public class SourceMetaDatas {
+ /**
+ * simplest metadata implementation (uses the given name)
+ */
+ public static SourceMetaData create(final String name) {
+ return new SourceMetaData() {
+ @Override
+ public String getName() {
+ return name;
+ }
+ };
+ }
+}
diff --git a/src/main/java/picante/mechanics/StateTransform.java b/src/main/java/picante/mechanics/StateTransform.java
new file mode 100644
index 0000000..dc45346
--- /dev/null
+++ b/src/main/java/picante/mechanics/StateTransform.java
@@ -0,0 +1,322 @@
+package picante.mechanics;
+
+import picante.designpatterns.Writable;
+import picante.math.vectorspace.MatrixIJK;
+import picante.math.vectorspace.RotationMatrixIJK;
+import picante.math.vectorspace.UnwritableMatrixIJK;
+import picante.math.vectorspace.UnwritableRotationMatrixIJK;
+import picante.math.vectorspace.VectorIJK;
+
+// TODO: Consider adding create*() style methods as useful.
+
+/**
+ * The writable child of a container class capturing the matrix components of a state vector
+ * coordinate transformation.
+ *
+ * - -
+ * | : |
+ * | R : 0 |
+ * |.......:......|
+ * | : |
+ * | W*R : R |
+ * | : |
+ * - -
+ *
+ *
+ * has the inverse:
+ *
+ *
+ * - -
+ * | t : |
+ * | R : 0 |
+ * |.......:......|
+ * | t: t |
+ * | (W*R) : R |
+ * | : |
+ * - -
+ *
+ *
+ * fromID and the toID at the
+ * requested time.
+ *
+ * @param time the time at which the state transform is to be computed. This should be contained
+ * within the coverage reported by the function via the
+ * {@link FrameTransformFunction#getCoverage()} method.
+ *
+ * @return the resultant state transform.
+ *
+ * @throws FrameEvaluationException a runtime exception, if anything goes awry in the computation
+ * of the state transform. Implementors are encouraged to subclass this exception for
+ * their own purposes.
+ */
+ public default StateTransform getStateTransform(double time) {
+ return getStateTransform(time, new StateTransform());
+ };
+
+ /**
+ * Obtain the state transform between the fromID and the toID at the
+ * requested time.
+ *
+ * @param time the time at which the state transform is to be computed. This should be contained
+ * within the coverage reported by the function via the
+ * {@link FrameTransformFunction#getCoverage()} method.
+ *
+ * @param buffer a buffer to capture the resultant state transform, or null if the underlying
+ * implementation is to create a new instance and return it instead.
+ *
+ * @return a reference to buffer for convenience.
+ *
+ * @throws FrameEvaluationException a runtime exception, if anything goes awry in the computation
+ * of the state transform. Implementors are encouraged to subclass this exception for
+ * their own purposes.
+ */
+ public StateTransform getStateTransform(double time, StateTransform buffer);
+
+}
diff --git a/src/main/java/picante/mechanics/StateTransformFunctions.java b/src/main/java/picante/mechanics/StateTransformFunctions.java
new file mode 100644
index 0000000..3ee7c44
--- /dev/null
+++ b/src/main/java/picante/mechanics/StateTransformFunctions.java
@@ -0,0 +1,482 @@
+package picante.mechanics;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import picante.math.PicanteMath;
+import picante.math.functions.DifferentiableRotationMatrixIJKFunction;
+import picante.math.functions.UnivariateFunction;
+import picante.math.functions.UnivariateFunctions;
+import picante.math.vectorspace.MatrixIJK;
+import picante.math.vectorspace.RotationMatrixIJK;
+import picante.math.vectorspace.UnwritableRotationMatrixIJK;
+import picante.math.vectorspace.VectorIJK;
+import picante.mechanics.rotations.AxisAndAngle;
+import picante.mechanics.rotations.MatrixWrapper;
+import picante.mechanics.rotations.WrapperWithRate;
+
+/**
+ * Class containing static utility methods for manipulating and working with
+ * {@link StateTransformFunction}s.
+ */
+public class StateTransformFunctions {
+
+ /**
+ * Static utility method umbrella, can not be instantiated.
+ */
+ private StateTransformFunctions() {}
+
+ /**
+ * Creates a new function from the source by inverting the supplied transformation.
+ * StateVector which now contains ( scaleA*a + scaleB*b ).
+ *
+ * @see StateVector#combine(double, UnwritableStateVector, double, UnwritableStateVector,
+ * StateVector)
+ */
+ public static StateVector combine(double scaleA, UnwritableStateVector a, double scaleB,
+ UnwritableStateVector b) {
+ return combine(scaleA, a, scaleB, b, new StateVector());
+ }
+
+ /**
+ * Linearly combine two state vectors.
+ *
+ * @param scaleA the scale factor for state vector a
+ * @param a a vector
+ * @param scaleB the scale factor for state vector b
+ * @param b another vector
+ * @param buffer the buffer to receive the results of the combination
+ *
+ * @return a reference to buffer for convenience which now contains ( scaleA*a + scaleB*b )
+ */
+ public static StateVector combine(double scaleA, UnwritableStateVector a, double scaleB,
+ UnwritableStateVector b, StateVector buffer) {
+ VectorIJK.combine(scaleA, a.getPosition(), scaleB, b.getPosition(), buffer.getPosition());
+ VectorIJK.combine(scaleA, a.getVelocity(), scaleB, b.getVelocity(), buffer.getVelocity());
+ return buffer;
+ }
+
+
+
+}
diff --git a/src/main/java/picante/mechanics/StateVectorFunction.java b/src/main/java/picante/mechanics/StateVectorFunction.java
new file mode 100644
index 0000000..71b1b50
--- /dev/null
+++ b/src/main/java/picante/mechanics/StateVectorFunction.java
@@ -0,0 +1,48 @@
+package picante.mechanics;
+
+/**
+ * Interface that describes a function that computes the state, position and velocity, of a target
+ * relative to an observer in a specific frame at the requested time. As states are merely positions
+ * with temporal derivative information, this interface inherits directly from the position one. As
+ * with the position function, the vector tail is at the observer and the head at the target.
+ */
+public interface StateVectorFunction extends PositionVectorFunction {
+
+ /**
+ * Obtain the state of the targetID relative to the observerID in the
+ * frameID frame at the requested time.
+ *
+ * @param time the time at which the state vector is to be computed. This should be contained
+ * within the coverage reported by the function via the
+ * {@link PositionVectorFunction#getCoverage()} method.
+ *
+ * @return the resultant state vector.
+ *
+ * @throws EphemerisEvaluationException a runtime exception, if anything goes awry in the
+ * computation of the state transform. Implementors are encouraged to subclass this
+ * exception for their own purposes.
+ */
+ public default StateVector getState(double time) {
+ return getState(time, new StateVector());
+ }
+
+ /**
+ * Obtain the state of the targetID relative to the observerID in the
+ * frameID frame at the requested time.
+ *
+ * @param time the time at which the state vector is to be computed. This should be contained
+ * within the coverage reported by the function via the
+ * {@link PositionVectorFunction#getCoverage()} method.
+ *
+ * @param buffer a buffer to capture the resultant state vector, or null if the underlying
+ * implementation is to create a new instance and return it instead.
+ *
+ * @return a reference to buffer for convenience.
+ *
+ * @throws EphemerisEvaluationException a runtime exception, if anything goes awry in the
+ * computation of the state transform. Implementors are encouraged to subclass this
+ * exception for their own purposes.
+ */
+ public StateVector getState(double time, StateVector buffer);
+
+}
diff --git a/src/main/java/picante/mechanics/StateVectorFunctions.java b/src/main/java/picante/mechanics/StateVectorFunctions.java
new file mode 100644
index 0000000..a4ea52b
--- /dev/null
+++ b/src/main/java/picante/mechanics/StateVectorFunctions.java
@@ -0,0 +1,374 @@
+package picante.mechanics;
+
+import picante.math.PicanteMath;
+import picante.math.functions.DifferentiableVectorIJKFunction;
+import picante.math.functions.VectorIJKFunction;
+import picante.math.vectorspace.RotationMatrixIJK;
+import picante.math.vectorspace.UnwritableVectorIJK;
+import picante.math.vectorspace.VectorIJK;
+
+/**
+ * Class capturing static utility methods useful for manipulating and working with
+ * {@link StateVectorFunction}s.
+ */
+public class StateVectorFunctions {
+
+ /**
+ * Static utility umbrella, can not be instantiated.
+ */
+ private StateVectorFunctions() {}
+
+ /**
+ * Adapts the supplied state vector function to the {@link DifferentiableVectorIJKFunction}
+ * interface.
+ *
+ * @param function the function to adapt
+ *
+ * @return an adapter that passes the time argument through to the
+ * {@link StateVectorFunction#getPosition(double, VectorIJK)} method and
+ * {@link StateVectorFunction#getState(double, StateVector)}.
+ */
+ public static DifferentiableVectorIJKFunction adapt(final StateVectorFunction function) {
+ return new DifferentiableVectorIJKFunction() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return function.getPosition(t, buffer);
+ }
+
+ @Override
+ public VectorIJK differentiate(double t, VectorIJK buffer) {
+ return buffer.setTo(function.getState(t, new StateVector()).getVelocity());
+ }
+ };
+ }
+
+ /**
+ * Adapts the supplied state vector function to the {@link VectorIJKFunction} interface for the
+ * velocity component of the state.
+ *
+ * @param function the function to adapt
+ *
+ * @return an adapter that passes the time argument through to the
+ * {@link StateVectorFunction#getState(double, StateVector)} to capture the velocity
+ */
+ public static VectorIJKFunction adaptVelocity(final StateVectorFunction function) {
+ return new VectorIJKFunction() {
+
+ @Override
+ public VectorIJK evaluate(double t, VectorIJK buffer) {
+ return buffer.setTo(function.getState(t, new StateVector()).getVelocity());
+ }
+ };
+ }
+
+ /**
+ * Creates a new function from the source by applying a scale factor to the position and velocity
+ * components of the evaluated state.
+ *
+ * @param function the function to scale
+ * @param scale the strictly positive scale factor to apply
+ *
+ * @return a newly created function that wraps the supplied function and scales the output on
+ * evaluation
+ *
+ * @throws IllegalArgumentException if the supplied scale factor is not strictly positive
+ */
+ public static StateVectorFunction scale(final StateVectorFunction function, final double scale) {
+ return scale(function, scale, scale);
+ }
+
+ /**
+ * Creates a new function from the source by applying independent scale factors to the position
+ * and velocity components of the evaluated state.
+ *
+ * @param function the function to scale
+ * @param positionScale the strictly positive scale factor for the position
+ * @param velocityScale the strictly positive scale factor for the velocity
+ *
+ * @return a newly created function that wraps the supplied function and scales the output on
+ * evaluation
+ *
+ * @throws IllegalArgumentException if either of the two supplied scale factors are not strictly
+ * positive
+ */
+ public static StateVectorFunction scale(final StateVectorFunction function,
+ final double positionScale, final double velocityScale) {
+
+ /*
+ * Check to see if the scale argument is less than zero, if it is throw an illegal argument
+ * exception as this method is only designed to apply positive scales to the function.
+ */
+ if (positionScale <= 0.0) {
+ throw new IllegalArgumentException("Position scale factor: " + positionScale
+ + " is invalid, as it is less than or equal to 0.0");
+ }
+
+ if (velocityScale <= 0.0) {
+ throw new IllegalArgumentException("Velocity scale factor: " + velocityScale
+ + " is invalid, as it is less than or equal to 0.0");
+ }
+
+ return new AbstractStateVectorFunctionWrapper(function) {
+
+ @Override
+ public VectorIJK getPosition(double time, VectorIJK buffer) {
+ buffer = buffer == null ? new VectorIJK() : buffer;
+ function.getPosition(time, buffer);
+ return buffer.scale(positionScale);
+ }
+
+ @Override
+ public StateVector getState(double time, StateVector buffer) {
+ buffer = buffer == null ? new StateVector() : buffer;
+ function.getState(time, buffer);
+ buffer.getPosition().scale(positionScale);
+ buffer.getVelocity().scale(velocityScale);
+ return buffer;
+ }
+ };
+
+ }
+
+ /**
+ * Creates a new function from the source by negating the vector.
+ *
+ * d||x|| < x, v >
+ * ------ = ------ = < xhat, v >
+ * ds 1/2
+ * < x, x >
+ *
+ *
+ *
+ * where,
+ *
+ *
+ *
+ * 1/2 2 2 2 1/2
+ * ||x|| = < x, x > = ( x1 + x2 + x3 )
+ *
+ * v = ( dx1, dx2, dx3 )
+ * --- --- ---
+ * ds ds ds
+ *
+ *
+ *
+ * This method makes an unwritable copy only if necessary. It tries to avoid making a copy + * wherever possible. + *
+ * + * @param vector a vector to copy. + * + * @return either a reference to vector (if vector is already only an instance of + * {@link UnwritableStateVector}, otherwise an unwritable copy of vector's contents + */ + public static UnwritableStateVector copyOf(UnwritableStateVector vector) { + if (vector.getClass().equals(UnwritableStateVector.class)) { + return vector; + } + return new UnwritableStateVector(vector); + } + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((position == null) ? 0 : position.hashCode()); + result = prime * result + ((velocity == null) ? 0 : velocity.hashCode()); + return result; + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof UnwritableStateVector)) { + return false; + } + UnwritableStateVector other = (UnwritableStateVector) obj; + if (position == null) { + if (other.position != null) { + return false; + } + } else if (!position.equals(other.position)) { + return false; + } + if (velocity == null) { + if (other.velocity != null) { + return false; + } + } else if (!velocity.equals(other.velocity)) { + return false; + } + return true; + } + + @Override + public final String toString() { + return "[" + position.getI() + "," + position.getJ() + "," + position.getK() + "; " + + velocity.getI() + "," + velocity.getJ() + "," + velocity.getK() + "]"; + } + + /** + * Convenience method that unitizes a state vector. Note: this mutates the state vector in place, + * which is a violation of the unwritable state vector contract. This should only be utilized with + * mutable vectors or for buffer instances created in this class as workspaces only. + */ + static void unitize(UnwritableStateVector stateToUnitize) { + unitizeAsState(stateToUnitize.position, stateToUnitize.velocity); + } + + /** + * Method that unitizes in place, the position and velocity components of a state vector. + * + * @param position the position component of the state + * @param velocity the velocity component of the state + */ + static void unitizeAsState(VectorIJK position, VectorIJK velocity) { + double norm = position.getLength(); + + if (norm == 0.0) { + throw new UnsupportedOperationException( + "Unable to unitize position. Instance is zero length."); + } + + position.scale(1.0 / norm); + VectorIJK.planeProject(velocity, position, velocity); + velocity.scale(1.0 / norm); + } + +} diff --git a/src/main/java/picante/mechanics/package-info.java b/src/main/java/picante/mechanics/package-info.java new file mode 100644 index 0000000..6558d68 --- /dev/null +++ b/src/main/java/picante/mechanics/package-info.java @@ -0,0 +1,43 @@ +/** + * Package containing the elemental classes and interfaces necessary to implement state querying + * functionality with a focus on celestial bodies and spacecraft. + *+ * Key interfaces are: + *
+ * In general the above interfaces provide users with the bulk of their needs with regards to + * accessing information provided by the code in this package. There are other interfaces and + * classes that may be of general use, but this documentation focuses on getting users up to speed + * quickly. + *
+ *+ * This package provides the interfaces necessary to interact with ephemeris and frame data. + * However, actual implementations of these interfaces provided by the library are found in packages + * underneath the crucible.mechanics.implementation package. For example, + * {@link picante.mechanics.providers.reference.ReferenceEphemerisProvider}. The details of + * instantiating various providers are left up to the specific implementation. + *
+ *+ * The state transform and state vector classes violate some of the basic principles of the weak + * immutability design pattern, in that they hand out references to their internals. This was a + * design decision made to accomodate the fact that these classes really act as buffers to move + * information around in the library. For convenience and performance reasons, these fields are + * exposed through the basic accessor methods. If they were not, implementors of most (if not all) + * of the interfaces in this package would be required to create or store local memory to inject + * values into the buffers. + *
+ *+ * A quick look in the crucible.mechanics.utilities package may save you some time. There are + * several classes there that may assist in implementing or altering these interfaces in ways that + * are generally useful. + *
+ */ +package picante.mechanics; diff --git a/src/main/java/picante/mechanics/providers/aberrated/Aberrated.java b/src/main/java/picante/mechanics/providers/aberrated/Aberrated.java new file mode 100644 index 0000000..174a687 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/Aberrated.java @@ -0,0 +1,17 @@ +package picante.mechanics.providers.aberrated; + +/** + * Simple interface to add the {@link AberrationCorrection} retrieval method to the extensions of + * the vector function interfaces. + */ +public interface Aberrated { + + /** + * Retrieves the correction that is being applied to the position and/or states returned from this + * function. + * + * @return the correction + */ + public AberrationCorrection getCorrection(); + +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/AberratedEphemerisProvider.java b/src/main/java/picante/mechanics/providers/aberrated/AberratedEphemerisProvider.java new file mode 100644 index 0000000..47582d0 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/AberratedEphemerisProvider.java @@ -0,0 +1,183 @@ +package picante.mechanics.providers.aberrated; + +import java.util.Map; +import picante.math.functions.DifferentiableUnivariateFunction; +import picante.mechanics.CelestialBodies; +import picante.mechanics.CelestialFrames; +import picante.mechanics.Coverage; +import picante.mechanics.EphemerisAndFrameProvider; +import picante.mechanics.EphemerisID; +import picante.mechanics.EphemerisProvider; +import picante.mechanics.EphemerisSourceIOException; +import picante.mechanics.EphemerisSourceLinkException; +import picante.mechanics.FrameID; +import picante.mechanics.FrameProvider; + +/** + * Extension of {@link EphemerisAndFrameProvider} interface that provides additional methods for + * computing aberration corrected states and positions. + */ +public interface AberratedEphemerisProvider extends EphemerisAndFrameProvider { + + /** + * Creates an instance of a Newtonian aberration corrected ephemeris provider + * + * @param ephemerisProvider the ephemeris provider to which geometric requests for positions and + * states are delegated + * @param frameProvider the frame provider to which transform requests are delegated + * @param frameCenterMap the map of frame IDs to ephemeris IDs which define the frame centers for + * the purpose of light time correction + * + * @return a provider that uses a single iteraton of the light time contraction map to estimate + * light times and the default inertial frame {@link CelestialFrames#J2000} centered on + * the quasi-inertial reference point {@link CelestialBodies#SOLAR_SYSTEM_BARYCENTER} + */ + public static AberratedEphemerisProvider createSingleIteration( + EphemerisProvider ephemerisProvider, FrameProvider frameProvider, + Map extends FrameID, ? extends EphemerisID> frameCenterMap) { + return new NewtonianAberratedEphemerisProvider(ephemerisProvider, frameProvider, + CelestialBodies.SOLAR_SYSTEM_BARYCENTER, CelestialFrames.J2000, frameCenterMap, 1, 1.0); + } + + public static AberratedEphemerisProvider createSingleIteration(EphemerisAndFrameProvider provider, + Map extends FrameID, ? extends EphemerisID> frameCenterMap) { + return createSingleIteration(provider, provider, frameCenterMap); + } + + /** + * Creates an instance of a Newtonian aberration corrected ephemeris provider + * + * @param ephemerisProvider the ephemeris provider to which geometric requests for positions and + * states are delegated + * @param frameProvider the frame provider to which transform requests are delegated + * @param frameCenterMap the map of frame IDs to ephemeris IDs which define the frame centers for + * the purpose of light time correction + * + * @return a provider that uses a single iteraton of the light time contraction map to estimate + * light times and the default inertial frame {@link CelestialFrames#J2000} centered on + * the quasi-inertial reference point {@link CelestialBodies#SOLAR_SYSTEM_BARYCENTER} + */ + public static AberratedEphemerisProvider createTripleIteration( + EphemerisProvider ephemerisProvider, FrameProvider frameProvider, + Map extends FrameID, ? extends EphemerisID> frameCenterMap) { + return new NewtonianAberratedEphemerisProvider(ephemerisProvider, frameProvider, + CelestialBodies.SOLAR_SYSTEM_BARYCENTER, CelestialFrames.J2000, frameCenterMap, 3, 1.0); + } + + public static AberratedEphemerisProvider createTripleIteration(EphemerisAndFrameProvider provider, + Map extends FrameID, ? extends EphemerisID> frameCenterMap) { + return createTripleIteration(provider, provider, frameCenterMap); + } + + + /** + * Returns the {@link EphemerisID} for the inertial reference point used in light time and stellar + * aberration corrections. + * + * @return the ephemerisID + */ + EphemerisID getInertialReferenceEphemerisID(); + + /** + * Returns the {@link FrameID} for the inertial reference frame used in light time and stellar + * aberration corrections. + * + * @return the frameID + */ + FrameID getInertialReferenceFrameID(); + + /** + * Retrieves the map of frame IDs to their corresponding "center" ephemeris IDs used in + * light time corrections through the frame. + * + * @return an unmodifiable view of the internal map held by the instance + */ + Map+ * Note: if a function is requested with {@link AberrationCorrection#NONE} the method + * {@link AberratedPositionVectorFunction#getLightTime(double)} will always throw a + * {@link UnsupportedOperationException}, since it is impossible to provide light time without + * knowing the direction in which light is traveling. + *
+ * + * @param target the target of interest + * @param observer the observer, at which all supplied times are evaluated + * @param frame the reference frame + * @param domain the time domain over which the function is to be queried. + * @param correction the correction to apply to the implementation + * + * @return a newly created function that applies the requested aberration corrections + * + * @throws EphemerisSourceIOException if an I/O error occurs in constructing the function + * @throws EphemerisSourceLinkException if a data source is missing or otherwise unable to satisfy + * the request + */ + AberratedPositionVectorFunction createAberratedPositionVectorFunction(EphemerisID target, + EphemerisID observer, FrameID frame, Coverage domain, AberrationCorrection correction) + throws EphemerisSourceIOException, EphemerisSourceLinkException; + + /** + * Creates a state vector function with the requested aberration corrections applied. + *+ * Note: if a function is requested with {@link AberrationCorrection#NONE} the methods + * {@link AberratedStateVectorFunction#getLightTime(double)} + * {@link AberratedStateVectorFunction#getLightTimeDerivative(double)} will always throw a + * {@link UnsupportedOperationException}, since it is impossible to provide light time without + * knowing the direction in which light is traveling. + *
+ * + * @param target the target of interest + * @param observer the observer, at which all supplied times are evaluated + * @param frame the reference frame + * @param domain the time domain over which the function is to be queried. + * @param correction the correction to apply to the implementation + * + * @return a newly created function that applies the requested aberration corrections + * + * @throws EphemerisSourceIOException if an I/O error occurs in constructing the function + * @throws EphemerisSourceLinkException if a data source is missing or otherwise unable to satisfy + * the request + */ + AberratedStateVectorFunction createAberratedStateVectorFunction(EphemerisID target, + EphemerisID observer, FrameID frame, Coverage domain, AberrationCorrection correction) + throws EphemerisSourceIOException, EphemerisSourceLinkException; + + /** + * Convenience method that creates a {@link DifferentiableUnivariateFunction} that provides the + * light time between the observer and the target, where the time is specified at the observer. + * + * @param target + * @param observer + * @param frame + * @param domain + * @param correction + * @return + * @throws EphemerisSourceIOException + * @throws EphemerisSourceLinkException + */ + DifferentiableUnivariateFunction createLightTimeFunction(EphemerisID target, EphemerisID observer, + Coverage domain, AberrationCorrection correction) + throws EphemerisSourceIOException, EphemerisSourceLinkException; + + /** + * Convenience method that creates a {@link DifferentiableUnivariateFunction} that provides the + * time at which light leaves {@link AberrationCorrection#isReceipt()} or arrives + * {@link AberrationCorrection#isTransmission()} at a target. + * + * @param target + * @param observer + * @param frame + * @param domain + * @param correction + * @return + * @throws EphemerisSourceIOException + * @throws EphemerisSourceLinkException + */ + DifferentiableUnivariateFunction createLightTimeAdjustingFunction(EphemerisID target, + EphemerisID observer, Coverage domain, AberrationCorrection correction) + throws EphemerisSourceIOException, EphemerisSourceLinkException; + +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/AberratedPositionVectorFunction.java b/src/main/java/picante/mechanics/providers/aberrated/AberratedPositionVectorFunction.java new file mode 100644 index 0000000..55b8cbb --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/AberratedPositionVectorFunction.java @@ -0,0 +1,30 @@ +package picante.mechanics.providers.aberrated; + +import picante.mechanics.PositionVectorFunction; + +/** + * Extension of the {@link PositionVectorFunction} interface that provides a method to compute the + * light time to the target. When receiving an instance of this method, it is safe to assume that + * the positions returned from the + * {@link PositionVectorFunction#getPosition(double, picante.math.vectorspace.VectorIJK)} + * include the corrections described by {@link AberratedPositionVectorFunction#getCorrection()} + */ +public interface AberratedPositionVectorFunction extends PositionVectorFunction, Aberrated { + + /** + * Computes the one-way light time for a the observer and target, per the correction described by + * {@link AberratedPositionVectorFunction#getCorrection()}. + * + * @param time the time at {@link AberratedPositionVectorFunction#getObserverID()} at which to + * compute the one way light time + * + * @return the one-way light time, which is always a positive number. In the transmission case, + * this is the amount to add to time at which a signal departing from + * {@link AberratedPositionVectorFunction#getObserverID()} arrives at + * {@link AberratedPositionVectorFunction#getTargetID()}. In the receipt case, this is the + * amount to subtract to determine the time at which a signal departing the target arrives + * at the observer. + */ + public double getLightTime(double time); + +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/AberratedStateVectorFunction.java b/src/main/java/picante/mechanics/providers/aberrated/AberratedStateVectorFunction.java new file mode 100644 index 0000000..157eb40 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/AberratedStateVectorFunction.java @@ -0,0 +1,29 @@ +package picante.mechanics.providers.aberrated; + +import picante.mechanics.StateVectorFunction; + +/** + * Extension of the {@link StateVectorFunction} interface that provides a method to compute the + * light time to the target. When receiving an instance of this method, it is safe to assume that + * the positions returned from the + * {@link StateVectorFunction#getPosition(double, picante.math.vectorspace.VectorIJK)} or + * states from {@link StateVectorFunction#getState(double, picante.mechanics.StateVector)} + * include the corrections described by {@link AberratedPositionVectorFunction#getCorrection()} + */ +public interface AberratedStateVectorFunction + extends AberratedPositionVectorFunction, StateVectorFunction, Aberrated { + + /** + * Computes the derivative of the one-way light time for a the observer and target, per the + * correction described by {@link AberratedPositionVectorFunction#getCorrection()}. + * + * @param time the time at {@link AberratedPositionVectorFunction#getObserverID()} at which to + * compute the one way light time + * + * @return the derivative of the one-way light time + * + * @see AberratedPositionVectorFunction#getLightTime(double) + */ + public double getLightTimeDerivative(double time); + +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/AberrationCorrection.java b/src/main/java/picante/mechanics/providers/aberrated/AberrationCorrection.java new file mode 100644 index 0000000..316c2c4 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/AberrationCorrection.java @@ -0,0 +1,123 @@ +package picante.mechanics.providers.aberrated; + +import static com.google.common.base.Preconditions.checkArgument; +import picante.exceptions.BugException; +import picante.units.FundamentalPhysicalConstants; + +/** + * Enumeration describing the various times of aberration corrections supported by the + * {@link AberratedEphemerisProvider}. + *+ * In general when working with aberration corrected vector functions, it is rarely correct to + * request a light time only correction. In practice one should choose from: + *
+ * The other options are provided for completeness, and are useful under specific circumstances. The + * stellar aberration correction has no impact on the computed value of the light time or its time + * derivative. + *
+ */ +public enum AberrationCorrection { + + /** + * Apply both light time and stellar aberration corrections in the receipt case. + */ + LT_S(false, true, false), + + /** + * Apply only light time corrections in the receipt case. + */ + LT(false, false, false), + + /** + * Apply no corrections. Sometimes this is referred to as geometric. + */ + NONE(true, false, false), + + /** + * Apply only light time corrections in the transmission case. + */ + XLT(false, false, true), + + /** + * Apply both light time and stellar aberration corrections in the transmission case. + */ + XLT_S(false, true, true); + + /** + * Since the {@link FundamentalPhysicalConstants#SPEED_OF_LIGHT_IN_VACUUM_M_per_SEC} is not + * provided in units of km/sec, convert it here once for use within this package. + */ + static final double SPEED_OF_LIGHT = + FundamentalPhysicalConstants.SPEED_OF_LIGHT_IN_VACUUM_M_per_SEC / 1000.0; + + private final boolean isGeometric; + private final boolean useStellarAberration; + private final boolean isTransmission; + + private AberrationCorrection(boolean isGeometric, boolean useStellarAberration, + boolean isTransmission) { + this.isGeometric = isGeometric; + this.useStellarAberration = useStellarAberration; + this.isTransmission = isTransmission; + } + + /** + * Indicates whether the correction is geometric. + */ + public boolean isGeometric() { + return isGeometric; + } + + /** + * Indicates whether the correction includes stellar aberration. + */ + public boolean useStellarAberration() { + return useStellarAberration; + } + + /** + * Indicates whether the correction is for the transmission case (photons traveling from the + * observer to the target). + */ + public boolean isTransmission() { + return isTransmission; + } + + /** + * Indicates whether the correction is for the receipt case (photons traveling from the target to + * the observer). + */ + public boolean isReceipt() { + return !isTransmission; + } + + /** + * Strips the stellar aberration correction from the supplied correction. + * + * @param correction + * + * @return LT if input was LT_S, XLT if input was XLT_S + * + * @throws IllegalArgumentException if the supplied correction does not include stellar aberration + * {@link AberrationCorrection#useStellarAberration()} + */ + static AberrationCorrection stripStellarAberration(AberrationCorrection correction) { + checkArgument(correction.useStellarAberration, "Correction must have stellar aberration."); + switch (correction) { + case LT_S: + return LT; + case XLT_S: + return XLT; + default: + throw new BugException("A correction that has stellar aberration has " + + "been defined that isn't handled by this method. " + "This is clearly a bug."); + } + + } +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/EqualObserverTargetAberratedStateVectorFunction.java b/src/main/java/picante/mechanics/providers/aberrated/EqualObserverTargetAberratedStateVectorFunction.java new file mode 100644 index 0000000..ea20140 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/EqualObserverTargetAberratedStateVectorFunction.java @@ -0,0 +1,53 @@ +package picante.mechanics.providers.aberrated; + +import static com.google.common.base.Preconditions.checkNotNull; +import picante.math.vectorspace.VectorIJK; +import picante.mechanics.Coverage; +import picante.mechanics.EphemerisID; +import picante.mechanics.FrameID; +import picante.mechanics.StateVector; +import picante.mechanics.utilities.AbstractPositionVectorFunction; + +/** + * This class exists just to inject zeros into the appropriate places, in the event that the center + * of a frame is coincident with the observer requested. + */ +class EqualObserverTargetAberratedStateVectorFunction extends AbstractPositionVectorFunction + implements AberratedStateVectorFunction { + + private final AberrationCorrection correction; + + EqualObserverTargetAberratedStateVectorFunction(EphemerisID targetID, EphemerisID observerID, + FrameID frameID, Coverage coverage, AberrationCorrection correction) { + super(targetID, observerID, frameID, coverage); + this.correction = checkNotNull(correction); + } + + @Override + public double getLightTime(@SuppressWarnings("unused") double time) { + return 0; + } + + @Override + public VectorIJK getPosition(@SuppressWarnings("unused") double time, VectorIJK buffer) { + buffer = buffer == null ? new VectorIJK() : buffer; + return buffer.setTo(VectorIJK.ZERO); + } + + @Override + public AberrationCorrection getCorrection() { + return correction; + } + + @Override + public StateVector getState(@SuppressWarnings("unused") double time, StateVector buffer) { + buffer = buffer == null ? new StateVector() : buffer; + return buffer.setTo(StateVector.ZERO); + } + + @Override + public double getLightTimeDerivative(@SuppressWarnings("unused") double time) { + return 0; + } + +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/GeometricPositionVectorFunction.java b/src/main/java/picante/mechanics/providers/aberrated/GeometricPositionVectorFunction.java new file mode 100644 index 0000000..1c50417 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/GeometricPositionVectorFunction.java @@ -0,0 +1,59 @@ +package picante.mechanics.providers.aberrated; + +import static com.google.common.base.Preconditions.checkNotNull; +import picante.math.vectorspace.VectorIJK; +import picante.mechanics.Coverage; +import picante.mechanics.EphemerisID; +import picante.mechanics.FrameID; +import picante.mechanics.PositionVectorFunction; + +class GeometricPositionVectorFunction implements AberratedPositionVectorFunction { + + private final PositionVectorFunction delegate; + + GeometricPositionVectorFunction(PositionVectorFunction delegate) { + super(); + this.delegate = checkNotNull(delegate); + } + + @Override + public EphemerisID getObserverID() { + return delegate.getObserverID(); + } + + @Override + public EphemerisID getTargetID() { + return delegate.getTargetID(); + } + + @Override + public FrameID getFrameID() { + return delegate.getFrameID(); + } + + @Override + public Coverage getCoverage() { + return delegate.getCoverage(); + } + + @Override + public AberrationCorrection getCorrection() { + return AberrationCorrection.NONE; + } + + @Override + public VectorIJK getPosition(double time, VectorIJK buffer) { + return delegate.getPosition(time, buffer); + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException this is a geometric state, there is no light time. + */ + @Override + public double getLightTime(@SuppressWarnings("unused") double time) { + throw new UnsupportedOperationException("Geometric positions can not produce light times."); + } + +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/GeometricStateVectorFunction.java b/src/main/java/picante/mechanics/providers/aberrated/GeometricStateVectorFunction.java new file mode 100644 index 0000000..1367e89 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/GeometricStateVectorFunction.java @@ -0,0 +1,33 @@ +package picante.mechanics.providers.aberrated; + +import static com.google.common.base.Preconditions.checkNotNull; +import picante.mechanics.StateVector; +import picante.mechanics.StateVectorFunction; + +class GeometricStateVectorFunction extends GeometricPositionVectorFunction + implements AberratedStateVectorFunction { + + private final StateVectorFunction delegate; + + GeometricStateVectorFunction(StateVectorFunction delegate) { + super(delegate); + this.delegate = checkNotNull(delegate); + } + + @Override + public StateVector getState(double time, StateVector buffer) { + return delegate.getState(time, buffer); + } + + /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException as geometric states are unable to provide light time + * derivatives + */ + @Override + public double getLightTimeDerivative(@SuppressWarnings("unused") double time) { + throw new UnsupportedOperationException( + "Geometric states can not provide light time or its derivative."); + } +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/InertialLightTimePositionVectorFunction.java b/src/main/java/picante/mechanics/providers/aberrated/InertialLightTimePositionVectorFunction.java new file mode 100644 index 0000000..058e8d5 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/InertialLightTimePositionVectorFunction.java @@ -0,0 +1,133 @@ +package picante.mechanics.providers.aberrated; + +import static com.google.common.base.Preconditions.checkArgument; +import picante.math.vectorspace.VectorIJK; +import picante.mechanics.Coverage; +import picante.mechanics.Coverages; +import picante.mechanics.EphemerisID; +import picante.mechanics.FrameID; +import picante.mechanics.PositionVectorFunction; + +class InertialLightTimePositionVectorFunction implements AberratedPositionVectorFunction { + + private final PositionVectorFunction observer; + private final PositionVectorFunction target; + + private final Coverage coverage; + private final AberrationCorrection correction; + final int numberOfIterations; + final double lightTimeSign; + + InertialLightTimePositionVectorFunction(PositionVectorFunction observer, + PositionVectorFunction target, AberrationCorrection correction, int numberOfIterations) { + super(); + + checkArgument(observer.getObserverID().equals(target.getObserverID()), + "Target and observer functions must have equivalent observerID. Observer=%s. Target=%s", + observer.getObserverID(), target.getObserverID()); + + checkArgument(observer.getFrameID().equals(target.getFrameID()), + "Target and observer frameID must match. Observer=%s. Target=%s", observer.getFrameID(), + target.getFrameID()); + + checkArgument(observer.getFrameID().isInertial(), + "Target and observer reference frame must be inertial."); + + checkArgument(correction == AberrationCorrection.LT || correction == AberrationCorrection.XLT, + "Requested correction must be either %s or %s, was %s", AberrationCorrection.LT, + AberrationCorrection.XLT, correction); + + checkArgument(numberOfIterations > 0, "Number of iterations must exceed 0, was %s", + numberOfIterations); + + this.observer = observer; + this.target = target; + this.coverage = Coverages.intersect(target.getCoverage(), observer.getCoverage()); + this.correction = correction; + this.numberOfIterations = numberOfIterations; + /* + * Set the sign of the light time correction. If it's transmission we will be adding time to the + * target ephemeris query to locate where it will be when photons arrive. + */ + this.lightTimeSign = correction.isTransmission() ? 1 : -1; + } + + @Override + public EphemerisID getObserverID() { + return observer.getTargetID(); + } + + @Override + public EphemerisID getTargetID() { + return target.getTargetID(); + } + + @Override + public FrameID getFrameID() { + return observer.getFrameID(); + } + + @Override + public Coverage getCoverage() { + return coverage; + } + + @Override + public AberrationCorrection getCorrection() { + return correction; + } + + @Override + public VectorIJK getPosition(double time, VectorIJK buffer) { + + buffer = buffer == null ? new VectorIJK() : buffer; + + computeLightTimeCorrectedState(time, buffer); + return buffer; + } + + @Override + public double getLightTime(double time) { + return computeLightTimeCorrectedState(time, new VectorIJK()); + } + + /** + * Internal method that consolidates the computation of the light time and the light time + * corrected state. + * + * @param time + * @param buffer + * + * @return + */ + private double computeLightTimeCorrectedState(double time, VectorIJK buffer) { + + /* + * Look up the position of the observer relative to the inertial reference. This vector remains + * a constant during the computation, as time is specified at the observer. + */ + VectorIJK observerPosition = observer.getPosition(time, new VectorIJK()); + + /* + * Fetch the position of the target relative to the inertial reference. Subtract the observer's + * position from it. + */ + target.getPosition(time, buffer); + VectorIJK.subtract(buffer, observerPosition, buffer); + + double lightTime = buffer.getLength() / AberrationCorrection.SPEED_OF_LIGHT; + + /* + * We've done one iteration already, perform any additional iterations requested. + */ + for (int i = 0; i < numberOfIterations; i++) { + target.getPosition(time + lightTimeSign * lightTime, buffer); + VectorIJK.subtract(buffer, observerPosition, buffer); + lightTime = buffer.getLength() / AberrationCorrection.SPEED_OF_LIGHT; + } + + return lightTime; + + } + +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/InertialLightTimeStateVectorFunction.java b/src/main/java/picante/mechanics/providers/aberrated/InertialLightTimeStateVectorFunction.java new file mode 100644 index 0000000..1fa777c --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/InertialLightTimeStateVectorFunction.java @@ -0,0 +1,224 @@ +package picante.mechanics.providers.aberrated; + +import static com.google.common.base.Preconditions.checkState; +import picante.math.vectorspace.VectorIJK; +import picante.mechanics.StateVector; +import picante.mechanics.StateVectorFunction; +import picante.mechanics.UnwritableStateVector; + +class InertialLightTimeStateVectorFunction extends InertialLightTimePositionVectorFunction + implements AberratedStateVectorFunction { + + private final double TOL = 1.0E-10; + + private final StateVectorFunction observer; + private final StateVectorFunction target; + + InertialLightTimeStateVectorFunction(StateVectorFunction observer, StateVectorFunction target, + AberrationCorrection correction, int numberOfIterations) { + super(observer, target, correction, numberOfIterations); + this.observer = observer; + this.target = target; + } + + @Override + public StateVector getState(double time, StateVector buffer) { + + buffer = buffer == null ? new StateVector() : buffer; + + StateVector observer = new StateVector(); + StateVector target = new StateVector(); + + /* + * Compute the position of the observer at time relative to the inertial reference, the target + * at the light time adjusted position relative to the inertial reference, and the target at the + * light time relative to the observer at time. Note: this will only partially populate the + * velocity components of buffer, as it neglects the light time derivative. + */ + computeVectors(time, observer, target, buffer); + + /* + * Compute the light time derivative, and correct the velocity components of buffer. + */ + double dlt = computeLightTimeDerviative(target, buffer); + VectorIJK.combine(1.0 + lightTimeSign * dlt, target.getVelocity(), -1.0, observer.getVelocity(), + buffer.getVelocity()); + + return buffer; + } + + /** + * Computes the observer relative to the reference point at time, the target relative to the + * reference point at time adjusted for light time, and the target relative to the observer + * without adjusting the velocity terms to account for the light time derivative. + */ + private void computeVectors(double time, StateVector observer, StateVector target, + StateVector buffer) { + /* + * Look up the position of the observer relative to the inertial reference. This vector remains + * a constant during the computation, as time is specified at the observer. + */ + this.observer.getState(time, observer); + + /* + * And fetch the position of the target relative to the inertial reference. Subtract the + * observer's position from it. + */ + this.target.getState(time, target); + StateVector.subtract(target, observer, buffer); + + double lightTime = buffer.getLength() / AberrationCorrection.SPEED_OF_LIGHT; + + /* + * We've completed one iteration already, perform any additional iterations requested. + */ + for (int i = 0; i < numberOfIterations; i++) { + this.target.getState(time + lightTimeSign * lightTime, target); + StateVector.subtract(target, observer, buffer); + lightTime = buffer.getLength() / AberrationCorrection.SPEED_OF_LIGHT; + } + + } + + /** + * Compute the derivative of the light time with respect to time. + * + *+ * Derive the formula for this quantity for the reception case. Let: + *
+ * The light-time corrected position of the target relative to the observer at observation time + * ET, given the one-way light time LT is: + * + *
+ * PTARG(ET + S * LT) - POBS(ET) + *+ * + * + *
+ * The light-time corrected velocity of the target relative to the observer at observation time ET + * is: + * + *
+ * VTARG(ET + S * LT) * (1 + S * d(LT) / d(ET)) - VOBS(ET) + *+ * + * + *
+ * We need to compute dLT/dt. Below, we use the facts that, for a time-dependent vector X(t), + * + *
+ * ||X|| =+ * + * Newtonian light time equation: + * + *** (1/2) + * + * d(||X||)/dt = (1/2) **(-1/2) * 2 * + * + * = **(-1/2) * + * + * = / ||X|| + *
+ * LT = (1/c) * || PTARG(ET+S*LT) - POBS(ET)|| + *+ * + * Differentiate both sides: + * + *
+ * dLT/dt = (1/c) * ( 1 / || PTARG(ET+S*LT) - POBS(ET) || ) + * + * * < PTARG(ET+S*LT) - POBS(ET), + * VTARG(ET+S*LT)*(1+S*d(LT)/d(ET)) - VOBS(ET) > + * + * = (1/c) * ( 1 / || PTARG(ET+S*LT) - POBS(ET) || ) + * + * * ( < PTARG(ET+S*LT) - POBS(ET), + * VTARG(ET+S*LT) - VOBS(ET) > + * + * + < PTARG(ET+S*LT) - POBS(ET), + * VTARG(ET+S*LT) > * (S*d(LT)/d(ET)) ) + *+ * + * + *
+ * Let: + * + *
+ * A = (1/c) * ( 1 / || PTARG(ET+S*LT) - POBS(ET) || ) + * + * B = < PTARG(ET+S*LT) - POBS(ET), VTARG(ET+S*LT) - VOBS(ET) > + * + * C = < PTARG(ET+S*LT) - POBS(ET), VTARG(ET+S*LT) > + *+ * + * Then + * + *
+ * d(LT)/d(ET) = A * ( B + C * S*d(LT)/d(ET) ) + *+ * + * which implies + * + *
+ * d(LT)/d(ET) = A*B / ( 1 - S*C*A ) + *+ * + * + * + * @param target the state of the target relative to an inertial reference point in an inertial + * frame + * @param buffer the state of the target relative to the observer expressed in the same inertial + * frame + * @return the light time derivative + */ + private double computeLightTimeDerviative(UnwritableStateVector target, + UnwritableStateVector buffer) { + + double a = 1.0 / (AberrationCorrection.SPEED_OF_LIGHT * buffer.getLength()); + + double b = buffer.getPosition().getDot(buffer.getVelocity()); + + double c = buffer.getPosition().getDot(target.getVelocity()); + + checkState(lightTimeSign * c * a <= 1.0 - TOL, + "Target range rate maximum is approaching the speed of" + + " light. Unable to estimate light time derivative."); + + return (a * b) / (1.0 - lightTimeSign * c * a); + + } + + @Override + public double getLightTimeDerivative(double time) { + + StateVector target = new StateVector(); + StateVector buffer = new StateVector(); + + /* + * Compute the position of the observer at time relative to the inertial reference, the target + * at the light time adjusted position relative to the inertial reference, and the target at the + * light time relative to the observer at time. Note: this will only partially populate the + * velocity components of buffer, as it neglects the light time derivative. + */ + computeVectors(time, new StateVector(), target, buffer); + + /* + * Compute the light time derivative, and correct the velocity components of buffer. + */ + return computeLightTimeDerviative(target, buffer); + + } + +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/InertialStellarAberratedPositionVectorFunction.java b/src/main/java/picante/mechanics/providers/aberrated/InertialStellarAberratedPositionVectorFunction.java new file mode 100644 index 0000000..b566924 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/InertialStellarAberratedPositionVectorFunction.java @@ -0,0 +1,132 @@ +package picante.mechanics.providers.aberrated; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; +import static picante.math.PicanteMath.asin; +import picante.math.functions.DifferentiableVectorIJKFunction; +import picante.math.vectorspace.VectorIJK; +import picante.mechanics.Coverage; +import picante.mechanics.EphemerisID; +import picante.mechanics.FrameID; + +class InertialStellarAberratedPositionVectorFunction implements AberratedPositionVectorFunction { + + private final InertialLightTimePositionVectorFunction ltFunction; + private final DifferentiableVectorIJKFunction observerPosition; + private final AberrationCorrection correction; + + InertialStellarAberratedPositionVectorFunction(InertialLightTimePositionVectorFunction ltFunction, + DifferentiableVectorIJKFunction obsVelocity, AberrationCorrection correction) { + super(); + this.ltFunction = checkNotNull(ltFunction); + this.observerPosition = checkNotNull(obsVelocity); + /* + * Only permit corrections that use stellar aberration. + */ + checkArgument(correction.useStellarAberration()); + this.correction = correction; + } + + @Override + public EphemerisID getObserverID() { + return ltFunction.getObserverID(); + } + + @Override + public EphemerisID getTargetID() { + return ltFunction.getTargetID(); + } + + @Override + public FrameID getFrameID() { + return ltFunction.getFrameID(); + } + + @Override + public Coverage getCoverage() { + return ltFunction.getCoverage(); + } + + @Override + public AberrationCorrection getCorrection() { + return correction; + } + + /** + * Compute the stellar aberration corrected position. + *
+ * Let r be the vector from the observer to the object, and v be - the velocity of the observer + * with respect to the inertial reference point. Let w be the angle between them. The aberration + * angle phi is given by + * + *
+ * sin(phi) = v sin(w) / c + *+ * + * Let h be the vector given by the cross product + * + *
+ * h = r X v + *+ * + * Rotate r by phi radians about h to obtain the apparent position of the object. + * + * {@inheritDoc} + */ + @Override + public VectorIJK getPosition(double time, VectorIJK buffer) { + + buffer = buffer == null ? new VectorIJK() : buffer; + + /* + * Compute the unit vector in the direction of the uncorrected vector. + */ + ltFunction.getPosition(time, buffer); + VectorIJK bufferHat = buffer.createUnitized(); + + /* + * Compute the observer's velocity, and scale it by the speed of light. + */ + VectorIJK velocity = observerPosition.differentiate(time, new VectorIJK()); + velocity.scale(1.0 / AberrationCorrection.SPEED_OF_LIGHT); + + /* + * Negate the velocity, if the correction requested is the transmission case. This will produce + * the correct result. + */ + if (correction.isTransmission()) { + velocity.negate(); + } + + /* + * Check that the observer's velocity does not exceed the speed of light. This doesn't need to + * be extremely accurate, since things generally aren't flitting around at or near that speed. + */ + checkState(velocity.getDot(velocity) < 1.0, "Velocity of observer exceeds the speed of light."); + + /* + * Compute the cross product of the unit vector with the scaled observer's velocity. + */ + VectorIJK.cross(bufferHat, velocity, velocity); + + double sinPhi = velocity.getLength(); + + /* + * If the look direction lies along the velocity vector (sinPhi = 0) there's no correction to + * apply. + */ + if (sinPhi != 0.0) { + double phi = asin(sinPhi); + VectorIJK.rotate(buffer, velocity, phi, buffer); + } + + return buffer; + } + + @Override + public double getLightTime(double time) { + return ltFunction.getLightTime(time); + } + +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/InertialStellarAberratedStateVectorFunction.java b/src/main/java/picante/mechanics/providers/aberrated/InertialStellarAberratedStateVectorFunction.java new file mode 100644 index 0000000..1d9092c --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/InertialStellarAberratedStateVectorFunction.java @@ -0,0 +1,475 @@ +package picante.mechanics.providers.aberrated; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static picante.math.PicanteMath.asin; +import static picante.math.PicanteMath.max; +import static picante.math.PicanteMath.sqrt; +import picante.math.functions.DifferentiableVectorIJKFunction; +import picante.math.vectorspace.UnwritableVectorIJK; +import picante.math.vectorspace.VectorIJK; +import picante.mechanics.Coverage; +import picante.mechanics.EphemerisID; +import picante.mechanics.FrameID; +import picante.mechanics.StateVector; +import picante.mechanics.UnwritableStateVector; + +class InertialStellarAberratedStateVectorFunction implements AberratedStateVectorFunction { + + /** + * Let PHI be the (non-negative) rotation angle of the stellar aberration correction; then + * SEPARATION_LIMIT is a limit on how close PHI may be to zero radians while stellar aberration + * velocity is computed analytically. When sin(PHI) is lees than SEPARATION_LIMIT, the velocity + * must be computed numerically. + */ + private static final double SEPARATION_LIMIT = 1e-6; + + private static final double[] SIGNS = new double[] {-1.0, 1.0}; + + private final InertialLightTimeStateVectorFunction ltFunction; + private final DifferentiableVectorIJKFunction observerVelocity; + private final AberrationCorrection correction; + private final double derivativeDeltaT; + + InertialStellarAberratedStateVectorFunction(InertialLightTimeStateVectorFunction ltFunction, + DifferentiableVectorIJKFunction obsAcceleration, AberrationCorrection correction, + double derivativeDeltaT) { + super(); + this.ltFunction = ltFunction; + this.observerVelocity = obsAcceleration; + /* + * Only permit corrections that use stellar aberration. + */ + checkArgument(correction.useStellarAberration()); + this.correction = correction; + checkArgument(derivativeDeltaT > 0.0); + this.derivativeDeltaT = derivativeDeltaT; + } + + @Override + public EphemerisID getObserverID() { + return ltFunction.getObserverID(); + } + + @Override + public EphemerisID getTargetID() { + return ltFunction.getTargetID(); + } + + @Override + public FrameID getFrameID() { + return ltFunction.getFrameID(); + } + + @Override + public Coverage getCoverage() { + return ltFunction.getCoverage(); + } + + @Override + public AberrationCorrection getCorrection() { + return correction; + } + + @Override + public double getLightTime(double time) { + return ltFunction.getLightTime(time); + } + + @Override + public double getLightTimeDerivative(double time) { + return ltFunction.getLightTimeDerivative(time); + } + + /** + * This code should directly inherit from + * {@link InertialStellarAberratedPositionVectorFunction#getPosition(double, VectorIJK)} . + * However, we have the actual velocity vector here so modify the code to utilize it instead of + * the derivative approximation. + */ + @Override + public VectorIJK getPosition(double time, VectorIJK buffer) { + + buffer = buffer == null ? new VectorIJK() : buffer; + + /* + * Compute the unit vector in the direction of the uncorrected vector. + */ + ltFunction.getPosition(time, buffer); + VectorIJK bufferHat = buffer.createUnitized(); + + /* + * Compute the observer's velocity, and scale it by the speed of light. + */ + VectorIJK velocity = observerVelocity.evaluate(time, new VectorIJK()); + velocity.scale(1.0 / AberrationCorrection.SPEED_OF_LIGHT); + + /* + * Negate the velocity, if the correction requested is the transmission case. This will produce + * the correct result. + */ + if (correction.isTransmission()) { + velocity.negate(); + } + + /* + * Check that the observer's velocity does not exceed the speed of light. This doesn't need to + * be extremely accurate, since things generally aren't flitting around at or near that speed. + */ + checkState(velocity.getDot(velocity) < 1.0, "Velocity of observer exceeds the speed of light."); + + /* + * Compute the cross product of the unit vector with the scaled observer's velocity. + */ + VectorIJK.cross(bufferHat, velocity, velocity); + + double sinPhi = velocity.getLength(); + + /* + * If the look direction lies along the velocity vector (sinPhi = 0) there's no correction to + * apply. + */ + if (sinPhi != 0.0) { + double phi = asin(sinPhi); + VectorIJK.rotate(buffer, velocity, phi, buffer); + } + + return buffer; + + } + + /** + * Compute the stellar aberration corrected state vector. The math used to derive this follows: + *
+ * In the discussion below, the dot product of vectors X and Y is denoted by + * + *
+ *+ * + * The speed of light is denoted by the lower case letter "c." BTW, variable names used here are + * case-sensitive: upper case "C" represents a different quantity which is unrelated to the speed + * of light. + * + * Variable names ending in "HAT" denote unit vectors. Variable names starting with "D" denote + * derivatives with respect to time. + * + * We'll compute the correction SCORR and its derivative with respect to time DSCORR for the + * reception case. In the transmission case, we perform the same computation with the negatives of + * the observer velocity and acceleration. + * + * In the code below, we'll store the position and velocity portions of the input observer-target + * state STARG in the variables PTARG and VTARG, respectively. + * + * Let VP be the component of VOBS orthogonal to PTARG. VP is defined as + * + *+ *
+ * VOBS - < VOBS, RHAT > RHAT (1) + *+ * + * where RHAT is the unit vector + * + *
+ * PTARG/||PTARG|| + *+ * + * Then + * + *
+ * ||VP||/c (2) + *+ * + * is the magnitude of + * + *
+ * s = sin( phi ) (3) + *+ * + * where phi is the stellar aberration correction angle. We'll need the derivative with respect to + * time of (2). + * + * Differentiating (1) with respect to time yields the velocity DVP, where, letting + * + *
+ * DRHAT = d(RHAT) / dt + * VPHAT = VP / ||VP|| + * DVPMAG = d( ||VP|| ) / dt + *+ * + * we have + * + *
+ * DVP = d(VP)/dt + * + * = ACCOBS - ( (+ * + * and + * + *+ )*RHAT + * + * DRHAT ) (4) + *
+ * DVPMAG = < DVP, VPHAT > (5) + *+ * + * Now we can find the derivative with respect to time of the stellar aberration angle phi: + * + *
+ * ds/dt = d(sin(phi))/dt = d(phi)/dt * cos(phi) (6) + *+ * + * Using (2) and (5), we have for positive phi, + * + *
+ * ds/dt = (1/c)*DVPMAG = (1/c)*+ * + * Then for positive phi + * + *(7) + *
+ * d(phi)/dt = (1/cos(phi)) * (1/c) *+ * + * Equation (8) is well-defined as along as VP is non-zero: if VP is the zero vector, VPHAT is + * undefined. We'll treat the singular and near-singular cases separately. + * + * The aberration correction itself is a rotation by angle phi from RHAT towards VP, so the + * corrected vector is + * + *(8) + *
+ * ( sin(phi)*VPHAT + cos(phi)*RHAT ) * ||PTARG|| + *+ * + * and we can express the offset of the corrected vector from PTARG, which is the output SCORR, as + * + *
+ * SCORR = + * + * ( sin(phi)*VPHAT + (cos(phi)-1)*RHAT ) * ||PTARG|| (9) + *+ * + * Let DPTMAG be defined as + * + *
+ * DPTMAG = d ( ||PTARG|| ) / dt (10) + *+ * + * Then the derivative with respect to time of SCORR is + * + *
+ * DSCORR = + * + * ( sin(phi)*DVPHAT + * + * + cos(phi)*d(phi)/dt * VPHAT + * + * + (cos(phi) - 1) * DRHAT + * + * + ( -sin(phi)*d(phi)/dt ) * RHAT ) * ||PTARG|| + * + * + ( sin(phi)*VPHAT + (cos(phi)-1)*RHAT ) * DPTMAG (11) + *+ * + * + * {@inheritDoc} + */ + StateVector computeCorrection(UnwritableVectorIJK accobs, UnwritableVectorIJK velobs, + UnwritableStateVector starg, StateVector buffer) { + + /* + * Follow the SPICE FORTRAN source conventions here, as this algorithm is sufficiently + * complicated it will be difficult to trace otherwise. + * + * Compute RHAT, VHAT, VP, DPTMAG. RHAT and VHAT reside in the newly created bufferHat state + * vector: + */ + VectorIJK lcvobs = new VectorIJK(velobs); + VectorIJK lcacc = new VectorIJK(accobs); + + if (correction.isTransmission()) { + lcvobs.negate(); + lcacc.negate(); + } + + buffer.setTo(starg); + VectorIJK ptarg = buffer.getPosition(); + VectorIJK vtarg = buffer.getVelocity(); + + StateVector srHat = buffer.createUnitized(); + VectorIJK rhat = srHat.getPosition(); + VectorIJK drhat = srHat.getVelocity(); + + VectorIJK vp = VectorIJK.planeProject(lcvobs, rhat); + + double dptmag = buffer.getVelocity().getDot(rhat); + + /* + * Compute sin(phi) and cos(phi). Note that phi is always close to zero for realistic inputs + * (||VOBS|| << CLIGHT). So the cosine term should be positive. + */ + double s = vp.getLength() / AberrationCorrection.SPEED_OF_LIGHT; + double c = sqrt(max(0.0, 1 - s * s)); + + /* + * Compute the unit vector VPHAT and the stellar aberration correction. + */ + VectorIJK vpHat = new VectorIJK(vp); + if (vp.equals(VectorIJK.ZERO)) { + vpHat.setTo(VectorIJK.ZERO); + } else { + vpHat.unitize(); + } + + /* + * Now utilize equation (9) above in the javadoc to obtain the stellar aberration correction. + */ + double ptgmag = ptarg.getLength(); + + VectorIJK scorr = VectorIJK.combine(ptgmag * s, vpHat, ptgmag * (c - 1.0), rhat); + + /* + * Now use sine as an estimate of PHI to decide if we're going to differentiate the stellar + * aberration correction analytically or numerically. + * + * Note that sine is non-negative by construction, so no need for |sine| + */ + if (s >= SEPARATION_LIMIT) { + + return analyticalDerivative(lcacc, lcvobs, rhat, drhat, vp, vpHat, s, c, ptgmag, dptmag, + scorr, buffer); + + } + + return numericalDerivative(derivativeDeltaT, SIGNS, lcacc, lcvobs, ptarg, vtarg, drhat, vp, + vpHat, scorr, buffer); + } + + static StateVector analyticalDerivative(VectorIJK lcacc, VectorIJK lcvobs, VectorIJK rhat, + VectorIJK drhat, VectorIJK vp, VectorIJK vpHat, double s, double c, double ptgmag, + double dptmag, VectorIJK scorr, StateVector buffer) { + + /* + * This is the analytic case. Compute DVP--the derivative of VP with respect to time. Recall + * equation (4) from the javadoc above: + */ + VectorIJK dvp = VectorIJK.combine(1.0, lcacc, -lcvobs.getDot(drhat) - lcacc.getDot(rhat), rhat, + -lcvobs.getDot(rhat), drhat); + + /* + * Assemble the state vector for vp. Unitize it. + */ + StateVector sHat = new StateVector(vp, dvp); + sHat.unitize(); + VectorIJK dvphat = sHat.getVelocity(); + + /* + * Compute DVPHAT, the derivative of VPHAT. + */ + double dphi = (1.0 / (c * AberrationCorrection.SPEED_OF_LIGHT)) * dvp.getDot(vpHat); + + VectorIJK term1 = VectorIJK.combine(s, dvphat, c * dphi, vpHat); + VectorIJK term2 = VectorIJK.combine(c - 1.0, drhat, (-s) * dphi, rhat); + VectorIJK term3 = VectorIJK.add(term1, term2); + + VectorIJK dscorr = + VectorIJK.combine(ptgmag, term3, dptmag * s, vpHat, dptmag * (c - 1.0), rhat); + + buffer.setPosition(scorr); + buffer.setVelocity(dscorr); + + return buffer; + + } + + static StateVector numericalDerivative(double derivativeDeltaT, double[] SIGNS, VectorIJK lcacc, + VectorIJK lcvobs, VectorIJK ptarg, VectorIJK vtarg, VectorIJK rhat, VectorIJK vp, + VectorIJK vpHat, VectorIJK scorr, StateVector buffer) { + VectorIJK[] saoff = new VectorIJK[SIGNS.length]; + + for (int i = 0; i < SIGNS.length; i++) { + + /* + * Estimate the observer's velocity relative to the solar system barycenter at the current + * epoch. We use the local copies of the input velocity and acceleration to make a linear + * estimate. + */ + VectorIJK evobs = VectorIJK.combine(1.0, lcvobs, SIGNS[i] * derivativeDeltaT, lcacc); + + /* + * Estimate the observer-target vector. We use the observer target state velocity to make a + * linear estimate. + */ + VectorIJK eptarg = VectorIJK.combine(1.0, ptarg, SIGNS[i] * derivativeDeltaT, vtarg); + + /* + * Let rhat be the unit observer-target position. Compute the component of the observer's + * velocity that is perpendicular to the target position; call this vector vp. Also compute + * the unit vector in the direction of vp. + */ + rhat.setToUnitized(eptarg); + VectorIJK.planeProject(evobs, rhat, vp); + + if (vp.equals(VectorIJK.ZERO)) { + vp.setTo(VectorIJK.ZERO); + } else { + vpHat.setToUnitized(vp); + } + + /* + * Compute the sine and cosine of the correction angle. + */ + double s = vp.getLength() / AberrationCorrection.SPEED_OF_LIGHT; + double c = sqrt(max(0.0, 1.0 - s * s)); + + /* + * Compute the offset vector of the correction. + */ + double ptgmag = eptarg.getLength(); + + saoff[i] = VectorIJK.combine(ptgmag * s, vpHat, ptgmag * (c - 1.0), rhat); + + } + + /* + * Estimate the derivative. + */ + VectorIJK dscorr = + VectorIJK.combine(0.5 / derivativeDeltaT, saoff[1], -0.5 / derivativeDeltaT, saoff[0]); + + buffer.setPosition(scorr); + buffer.setVelocity(dscorr); + + return buffer; + + } + + @Override + public StateVector getState(double time, StateVector buffer) { + + buffer = buffer == null ? new StateVector() : buffer; + + /* + * Lookup the light time corrected state vector of interest. + */ + ltFunction.getState(time, buffer); + + /* + * Compute the observer's acceleration and velocity. + */ + VectorIJK velocity = observerVelocity.evaluate(time, new VectorIJK()); + VectorIJK acceleration = observerVelocity.differentiate(time, new VectorIJK()); + + /* + * Estimate the stellar aberration correction. + */ + StateVector correction = computeCorrection(acceleration, velocity, buffer, new StateVector()); + + /* + * Adding the stellar aberration correction to the light time corrected target position yields + * the position corrected for both light time and stellar aberration. + */ + StateVector.add(buffer, correction, buffer); + + return buffer; + } + +} diff --git a/src/main/java/picante/mechanics/providers/aberrated/NewtonianAberratedEphemerisProvider.java b/src/main/java/picante/mechanics/providers/aberrated/NewtonianAberratedEphemerisProvider.java new file mode 100644 index 0000000..23209c2 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/aberrated/NewtonianAberratedEphemerisProvider.java @@ -0,0 +1,429 @@ +package picante.mechanics.providers.aberrated; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.ImmutableMap; +import picante.exceptions.BugException; +import picante.math.functions.DifferentiableUnivariateFunction; +import picante.math.functions.DifferentiableVectorIJKFunction; +import picante.math.functions.DifferentiableVectorIJKFunctions; +import picante.mechanics.Coverage; +import picante.mechanics.EphemerisAndFrameProvider; +import picante.mechanics.EphemerisID; +import picante.mechanics.EphemerisProvider; +import picante.mechanics.EphemerisSourceIOException; +import picante.mechanics.EphemerisSourceLinkException; +import picante.mechanics.FrameID; +import picante.mechanics.FrameProvider; +import picante.mechanics.FrameSourceIOException; +import picante.mechanics.FrameSourceLinkException; +import picante.mechanics.FrameTransformFunction; +import picante.mechanics.PositionVectorFunction; +import picante.mechanics.PositionVectorFunctions; +import picante.mechanics.StateTransformFunction; +import picante.mechanics.StateVectorFunction; +import picante.mechanics.StateVectorFunctions; + +/** + * An extension of the {@link EphemerisAndFrameProvider} that provides additional methods for + * obtaining aberration corrected positions and states in inertial and non-inertial frames. + *
+ * Due to the way the crucible ephemeris system supports "position-only" ephemerides, + * there is an inherent disagreement between the position vector functions provided by this API and + * the SPICE way of performing these computations. In order to properly compute the stellar + * aberration corrected position of one object relative to another, the velocity of the observer + * relative to an inertial reference is required. This implementation numerically differentiates the + * position. However, if you request the state vector function, the position retrieval method will + * utilize the velocity available from the supporting data. + *
+ */ +class NewtonianAberratedEphemerisProvider + implements EphemerisAndFrameProvider, AberratedEphemerisProvider { + + private final EphemerisProvider ephemerisDelegate; + private final FrameProvider frameDelegate; + + private final EphemerisID inertialPoint; + private final FrameID inertialFrame; + + private final ImmutableMap+ * With regards to thread safety, instances of this provider may be used to create functions from + * multiple threads safely. The individual functions created are thread safe. + *
+ */ +public class LockableEphemerisProvider implements EphemerisAndFrameProvider { + + /** + * Creates an implementation of the {@link StateVectorFunction} interface that is utilized purely + * as an entry in a list ofStateVectorFunctions or
+ * PositionVectorFunctions as a marker.
+ *
+ * @return an implementation of the StateVectorFunction that throws exceptions whenever its
+ * methods are invoked.
+ */
+ private static StateVectorFunction createMarkerFunction() {
+ return new StateVectorFunction() {
+
+ private BugException createException() {
+ return new BugException("Implementation error. This method should never be invoked.");
+ }
+
+ @Override
+ public StateVector getState(@SuppressWarnings("unused") double time,
+ @SuppressWarnings("unused") StateVector buffer) {
+ throw createException();
+ }
+
+ @Override
+ public VectorIJK getPosition(@SuppressWarnings("unused") double time,
+ @SuppressWarnings("unused") VectorIJK buffer) {
+ throw createException();
+ }
+
+ @Override
+ public Coverage getCoverage() {
+ throw createException();
+ }
+
+ @Override
+ public FrameID getFrameID() {
+ throw createException();
+ }
+
+ @Override
+ public EphemerisID getObserverID() {
+ throw createException();
+ }
+
+ @Override
+ public EphemerisID getTargetID() {
+ throw createException();
+ }
+
+ };
+ }
+
+ /**
+ * An instance of the {@link CodeProvider} interface suitable for usage in
+ * ChainLinkEngines that link PositionVectorFunction s together.
+ */
+ private static final EphemerisCodeProvider CODE_PROVIDER = new EphemerisCodeProvider();
+
+ /**
+ * Marker instance used to indicate the separation between the forward evaluation of a linked
+ * chain and the backward evaluation. Functions that occur prior to this in a chained list of
+ * functions can be chained directly by the function returned. Functions after, must be negated
+ * prior to their combination.
+ */
+ static final StateVectorFunction SEPARATOR = createMarkerFunction();
+
+ /**
+ * Marker instance used to indicate the separation between the extent of the forward chain and the
+ * extent of the backward chain in the event that a connection is unable to be located. Primarily
+ * intended to create more useful error messages in the event of a broken link.
+ */
+ static final StateVectorFunction BROKEN_LINK = createMarkerFunction();
+
+ /**
+ * Instance of the chain link engine used to connect any state vector or position vector functions
+ * provided to the constructor of this class.
+ */
+ private final ChainLinkEngine+ * This is the most commonly, and performant, lock type to utilize. It does require, however; that + * all functions within a source supplied to the constructor are thread independent. + *
+ * + * @param sources a list of sources, in order from lowest priority (low index) to highest priority + * (high index). + * @param frameSources a list of frame sources, in order from lowest priority (low index) to + * highest priority (high index), that will be used internally to construct a + *LockableFrameProvider to support the state and position vector chaining
+ * performed by functions created by this provider
+ *
+ */
+ public LockableEphemerisProvider(List extends PositionVectorFunction> sources,
+ List extends FrameTransformFunction> frameSources) {
+ this(sources, frameSources, LockType.FUNCTION);
+ }
+
+ /**
+ * Constructs an instance of the lockable ephemeris provider from the supplied list of sources.
+ *
+ * @param sources a list of sources, in order from lowest priority (low index) to highest priority
+ * (high index).
+ * @param frameSources a list of frame sources, in order from lowest priority (low index) to
+ * highest priority (high index), that will be used internally to construct a
+ * ReferenceFrameProvider to support the state and position vector chaining
+ * performed by functions created by this provider
+ * @param lockType the instance of the {@link LockType} enumeration specifying which object to
+ * utilize for locking sources before evaluation
+ *
+ */
+
+ public LockableEphemerisProvider(List extends PositionVectorFunction> sources,
+ List extends FrameTransformFunction> frameSources, LockSupplier lockSupplier) {
+
+ /*
+ * Capture the list of sources in load priority order.
+ */
+ this.sources = ImmutableList.copyOf(sources);
+
+ ImmutableSet.Builder+ * With regards to thread safety, instances of this provider may be used to create functions from + * multiple threads safely. The individual functions created are thread safe. + *
+ */ +public class LockableFrameProvider implements FrameProvider { + + /** + * Creates an implementation of the {@link StateTransformFunction} interface that is utilized + * purely as an entry in a list ofStateTransformFunctions or
+ * FrameTransformFunctions as a marker.
+ *
+ * @return an implementation of the StateTransformFunction that throws exceptions whenever its
+ * methods are invoked
+ */
+ private static StateTransformFunction createMarkerFunction() {
+ return new StateTransformFunction() {
+
+ private BugException createException() {
+ return new BugException("Implementation error. This method should never be invoked.");
+ }
+
+ @Override
+ public StateTransform getStateTransform(@SuppressWarnings("unused") double time,
+ @SuppressWarnings("unused") StateTransform buffer) {
+ throw createException();
+ }
+
+ @Override
+ public Coverage getCoverage() {
+ throw createException();
+ }
+
+ @Override
+ public FrameID getFromID() {
+ throw createException();
+ }
+
+ @Override
+ public FrameID getToID() {
+ throw createException();
+ }
+
+ @Override
+ public RotationMatrixIJK getTransform(@SuppressWarnings("unused") double time,
+ @SuppressWarnings("unused") RotationMatrixIJK buffer) {
+ throw createException();
+ }
+
+ };
+ }
+
+ /**
+ * An instance of the {@link CodeProvider} interface suitable for usage in
+ * ChainLinkEngines that link FrameTransformFunctions together.
+ */
+ private final static FrameCodeProvider CODE_PROVIDER = new FrameCodeProvider();
+
+ /**
+ * Marker instance used to indicate the separation between the forward evaluation of a linked
+ * chain and the backward evaluation. Functions that occur prior to this in a chained list of
+ * functions can be chained directly by the transform returned. Functions after, must be inverted
+ * prior to their combination.
+ */
+ final static StateTransformFunction SEPARATOR = createMarkerFunction();
+
+ /**
+ * Marker instance used to indicate the separation between the extent of the forward chain and the
+ * extent of the backward chain in the event that a connection is unable to be located. Primarily
+ * intended to create more useful error messages in the event of a broken link.
+ */
+ final static StateTransformFunction BROKEN_LINK = createMarkerFunction();
+
+ /**
+ * Instance of the chain link engine used to connect any frame or state transform functions
+ * provided to the constructor of this class.
+ */
+ private final ChainLinkEngine+ * This is the most commonly, and performant, lock type to utilize. It does require, however; that + * all functions within a source supplied to the constructor are thread independent. + *
+ * + * @param sources a list of sources, in order from lowest priority (low index) to highest priority + * (high index). + * + * + */ + public LockableFrameProvider(List extends FrameTransformFunction> sources) { + this(sources, LockType.FUNCTION); + } + + /** + * Constructs an instance of the lockable frame provider from the supplied list of sources. + * + * @param sources a list of sources, in order from lowest priority (low index) to highest priority + * (high index). + * @param lockType the type of the lock to utilize for synchronization + * + * + */ + + + public LockableFrameProvider(List extends FrameTransformFunction> sources, + LockSupplier lockSupplier) { + + + /* + * Capture the list of sources in load priority order. + */ + this.sources = ImmutableList.copyOf(sources); + + ImmutableSet.Builder+ * Due to the way in which calculations are buffered, this implementation of the frame transform + * function interface is not safely accessible from multiple threads. + *
+ * + * @paramReferenceStateTransformFunction to
+ * inherit from this class directly, which greatly simplifies its implementation.
+ */
+class LockableFrameTransformFunction+ * Due to the way in which calculations are buffered, this implementation of the position vector + * function interface is not safely accessible from multiple threads. + *
+ * + * @param this class is generic only to allow the constructor to be supplied an instance of the + * chain link engine that links state vector functions instead of position vector functions. + * Essentially this allows theReferenceStateVectorFunction to inherit from this
+ * class directly, which greatly simplifies its implementation.
+ */
+class LockablePositionVectorFunction
+ implements PositionVectorFunction {
+
+ /**
+ * Reference to an instance of a chain link engine supplied by the reference ephemeris provider at
+ * construction time.
+ */
+ final ChainLinkEnginefromID to toID in place
+ */
+ private void transformVectorInPlace(FrameID fromID, FrameID toID, double time, VectorIJK vector,
+ RotationMatrixIJK matrix) {
+
+ /*
+ * Check to see if anything should be done.
+ */
+ if (fromID.equals(toID)) {
+ return;
+ }
+
+ /*
+ * Compute the transformation.
+ */
+ getTransform(fromID, toID).getTransform(time, matrix).mxv(vector, vector);
+
+ }
+
+ /**
+ * Compute the frame transform function that connects two frame IDs.
+ *
+ * @param fromID the frame ID of the frame in which this transform receives vectors
+ * @param toID the frame ID of the frame into which this transform takes vectors
+ *
+ * @return a frame transform function that performs the desired rotation.
+ */
+ FrameTransformFunction getTransform(FrameID fromID, FrameID toID) {
+ try {
+ return provider.createFrameTransformFunction(fromID, toID, Coverage.ALL_TIME);
+ } catch (SourceException e) {
+ throw new EphemerisEvaluationException("Unable to derive frame transformation.", e);
+ }
+ }
+
+}
diff --git a/src/main/java/picante/mechanics/providers/lockable/LockableStateTransformFunction.java b/src/main/java/picante/mechanics/providers/lockable/LockableStateTransformFunction.java
new file mode 100644
index 0000000..85417bd
--- /dev/null
+++ b/src/main/java/picante/mechanics/providers/lockable/LockableStateTransformFunction.java
@@ -0,0 +1,93 @@
+package picante.mechanics.providers.lockable;
+
+import java.util.List;
+import java.util.ListIterator;
+import com.google.common.collect.Lists;
+import picante.math.vectorspace.MatrixIJK;
+import picante.math.vectorspace.RotationMatrixIJK;
+import picante.mechanics.FrameID;
+import picante.mechanics.StateTransform;
+import picante.mechanics.StateTransformFunction;
+import picante.mechanics.utilities.ChainLinkEngine;
+
+/**
+ * Implementation of the state transform function that is backed by the reference implementation's
+ * chain link engine. Requests made to this function for state or frame transforms are evaluated at
+ * the time of request, and as such may result in runtime exceptions.
+ * + * Due to the way in which calculations are buffered, this implementation of the state transform + * function interface is not safely accessible from multiple threads. + *
+ *+ * Note, this implementation inherits directly from the reference implementation of the frame + * transform function. It, however, utilizes the state transform function engine supplied to this + * constructor. So a frame transform function created from the same reference implementation may + * produce different results than a state transform function produced from the same provider that is + * used as a frame transform function. + *
+ */ +class LockableStateTransformFunction extends LockableFrameTransformFunction+ * Due to the way in which calculations are buffered, this implementation of the state vector + * function interface is not safely accessible from multiple threads. + *
+ *+ * Note, this implementation inherits directly from the reference implementation of the position + * vector function. It, however, utilizes the state vector function engine supplied to this + * constructor. So a position vector function created from the same reference implementation may + * produce different results than a state vector function produced from the same put that is used. + *
+ */ +class LockableStateVectorFunction extends LockablePositionVectorFunctionfromID to toID in place
+ */
+ private void transformStateInPlace(FrameID fromID, FrameID toID, double time, StateVector state,
+ StateTransform xform) {
+
+ /*
+ * Check to see if anything should be done.
+ */
+ if (fromID.equals(toID)) {
+ return;
+ }
+
+ /*
+ * Compute the transformation.
+ */
+ getTransform(fromID, toID).getStateTransform(time, xform).mxv(state, state);
+ }
+
+ /**
+ * Overrides the transform function retrieval method to only allow state transforms available from
+ * the provider to be returned.
+ */
+ @Override
+ StateTransformFunction getTransform(FrameID fromID, FrameID toID) {
+ try {
+ return provider.createStateTransformFunction(fromID, toID, Coverage.ALL_TIME);
+ } catch (SourceException e) {
+ throw new EphemerisEvaluationException("Unable to derive state transformation.", e);
+ }
+ }
+
+}
diff --git a/src/main/java/picante/mechanics/providers/lockable/LockingDelegateFrameTransformFunction.java b/src/main/java/picante/mechanics/providers/lockable/LockingDelegateFrameTransformFunction.java
new file mode 100644
index 0000000..172d51f
--- /dev/null
+++ b/src/main/java/picante/mechanics/providers/lockable/LockingDelegateFrameTransformFunction.java
@@ -0,0 +1,115 @@
+package picante.mechanics.providers.lockable;
+
+import picante.math.intervals.Interval;
+import picante.math.vectorspace.RotationMatrixIJK;
+import picante.mechanics.Coverage;
+import picante.mechanics.FrameID;
+import picante.mechanics.FrameTransformFunction;
+
+/**
+ * Implementation of the frame transform function interface that provides synchronization against a
+ * supplied lock object.
+ * + * Note: the function interrogates the delegate at construction time for the frame ID codes. The + * results are cached and utilized to implement the delegate methods that retrieve them. This was + * done largely to codify the fact that these IDs are used as keys in maps created at construction + * time. + *
+ *+ * This class also implements the {@link Coverage} interface and returns itself in the + * {@link FrameTransformFunction#getCoverage()} method. This was done to consolidate the + * synchronization code into a single class. + *
+ */ +class LockingDelegateFrameTransformFunction implements FrameTransformFunction, Lockable, Coverage { + + /** + * The object upon which to synchronize. + */ + private final Object lock; + + private final FrameID fromID; + private final FrameID toID; + + /** + * The delegate function + */ + private final FrameTransformFunction delegate; + + /** + * Creates a locking delegate that synchronizes on the supplied lock. + * + * @param lock the lock to utilize in synchronization + * @param delegate the delegate to wrap + */ + LockingDelegateFrameTransformFunction(Object lock, FrameTransformFunction delegate) { + super(); + this.lock = lock; + this.fromID = delegate.getFromID(); + this.toID = delegate.getToID(); + this.delegate = delegate; + } + + @Override + public Object getLock() { + return lock; + } + + @Override + public FrameID getFromID() { + return fromID; + } + + @Override + public FrameID getToID() { + return toID; + } + + @Override + public Coverage getCoverage() { + return this; + } + + @Override + public RotationMatrixIJK getTransform(double time, RotationMatrixIJK buffer) { + synchronized (getLock()) { + return delegate.getTransform(time, buffer); + } + } + + @Override + public boolean contains(double time) { + synchronized (getLock()) { + return delegate.getCoverage().contains(time); + } + } + + @Override + public Interval getBoundingInterval(Interval buffer) { + synchronized (getLock()) { + return delegate.getCoverage().getBoundingInterval(buffer); + } + } + + @Override + public Interval getBracketingInterval(double time, Interval buffer) { + synchronized (getLock()) { + return delegate.getCoverage().getBracketingInterval(time, buffer); + } + } + + @Override + public boolean hasNextInterval(double time) { + synchronized (getLock()) { + return delegate.getCoverage().hasNextInterval(time); + } + } + + @Override + public Interval getNextInterval(double time, Interval buffer) { + synchronized (getLock()) { + return delegate.getCoverage().getNextInterval(time, buffer); + } + } + +} diff --git a/src/main/java/picante/mechanics/providers/lockable/LockingDelegatePositionVectorFunction.java b/src/main/java/picante/mechanics/providers/lockable/LockingDelegatePositionVectorFunction.java new file mode 100644 index 0000000..c34aa34 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/lockable/LockingDelegatePositionVectorFunction.java @@ -0,0 +1,123 @@ +package picante.mechanics.providers.lockable; + +import picante.math.intervals.Interval; +import picante.math.vectorspace.VectorIJK; +import picante.mechanics.Coverage; +import picante.mechanics.EphemerisID; +import picante.mechanics.FrameID; +import picante.mechanics.PositionVectorFunction; + +/** + * Implementation of the position vector function interface that provides synchronization against a + * supplied lock object. + *+ * Note: the function interrogates the delegate at construction time for the frame and ephemeris ID + * codes. The results are cached and utilized to implement the delegate methods that retrieve them. + * This was done largely to codify the fact that these IDs are used as keys in maps created at + * construction time. + *
+ *+ * This class also implements the {@link Coverage} interface and returns itself in the + * {@link PositionVectorFunction#getCoverage()} method. This was done to consolidate the + * synchronization code into a single class. + *
+ */ +class LockingDelegatePositionVectorFunction implements PositionVectorFunction, Lockable, Coverage { + + /** + * The object upon which to synchronize. + */ + private final Object lock; + + /** + * The delegate function + */ + private final PositionVectorFunction delegate; + + private final FrameID frameID; + private final EphemerisID observerID; + private final EphemerisID targetID; + + /** + * Creates a locking delegate that synchronizes on the supplied lock. + * + * @param lock the lock to utilize in synchronization + * @param delegate the delegate to wrap + */ + LockingDelegatePositionVectorFunction(Object lock, PositionVectorFunction delegate) { + super(); + this.lock = lock; + this.delegate = delegate; + this.frameID = delegate.getFrameID(); + this.observerID = delegate.getObserverID(); + this.targetID = delegate.getTargetID(); + } + + @Override + public EphemerisID getObserverID() { + return observerID; + } + + @Override + public EphemerisID getTargetID() { + return targetID; + } + + @Override + public FrameID getFrameID() { + return frameID; + } + + @Override + public Coverage getCoverage() { + return this; + } + + @Override + public VectorIJK getPosition(double time, VectorIJK buffer) { + synchronized (getLock()) { + return delegate.getPosition(time, buffer); + } + } + + @Override + public Object getLock() { + return lock; + } + + @Override + public boolean contains(double time) { + synchronized (getLock()) { + return delegate.getCoverage().contains(time); + } + } + + @Override + public Interval getBoundingInterval(Interval buffer) { + synchronized (getLock()) { + return delegate.getCoverage().getBoundingInterval(buffer); + } + } + + @Override + public Interval getBracketingInterval(double time, Interval buffer) { + synchronized (getLock()) { + return delegate.getCoverage().getBracketingInterval(time, buffer); + } + } + + @Override + public boolean hasNextInterval(double time) { + synchronized (getLock()) { + return delegate.getCoverage().hasNextInterval(time); + } + } + + @Override + public Interval getNextInterval(double time, Interval buffer) { + synchronized (getLock()) { + return delegate.getCoverage().getNextInterval(time, buffer); + } + } + +} diff --git a/src/main/java/picante/mechanics/providers/lockable/LockingDelegateStateTransformFunction.java b/src/main/java/picante/mechanics/providers/lockable/LockingDelegateStateTransformFunction.java new file mode 100644 index 0000000..982dd7a --- /dev/null +++ b/src/main/java/picante/mechanics/providers/lockable/LockingDelegateStateTransformFunction.java @@ -0,0 +1,33 @@ +package picante.mechanics.providers.lockable; + +import picante.mechanics.StateTransform; +import picante.mechanics.StateTransformFunction; + +/** + * Extension of the frame transform function that provides locking synchronization for the state + * transform function. + */ +class LockingDelegateStateTransformFunction extends LockingDelegateFrameTransformFunction + implements StateTransformFunction { + + private final StateTransformFunction delegate; + + /** + * Creates a locking delegate that synchronizes on the supplied lock. + * + * @param lock the lock to utilize in synchronization + * @param delegate the delegate to wrap + */ + LockingDelegateStateTransformFunction(Object lock, StateTransformFunction delegate) { + super(lock, delegate); + this.delegate = delegate; + } + + @Override + public StateTransform getStateTransform(double time, StateTransform buffer) { + synchronized (getLock()) { + return delegate.getStateTransform(time, buffer); + } + } + +} diff --git a/src/main/java/picante/mechanics/providers/lockable/LockingDelegateStateVectorFunction.java b/src/main/java/picante/mechanics/providers/lockable/LockingDelegateStateVectorFunction.java new file mode 100644 index 0000000..b317c1c --- /dev/null +++ b/src/main/java/picante/mechanics/providers/lockable/LockingDelegateStateVectorFunction.java @@ -0,0 +1,33 @@ +package picante.mechanics.providers.lockable; + +import picante.mechanics.StateVector; +import picante.mechanics.StateVectorFunction; + +/** + * Extension of the position vector function delegate that provides state vector function + * capabilities with locking. + */ +class LockingDelegateStateVectorFunction extends LockingDelegatePositionVectorFunction + implements StateVectorFunction, Lockable { + + private final StateVectorFunction delegate; + + /** + * Creates a locking delegate that synchronizes on the supplied lock. + * + * @param lock the lock to utilize in synchronization + * @param delegate the delegate to wrap + */ + LockingDelegateStateVectorFunction(Object lock, StateVectorFunction delegate) { + super(lock, delegate); + this.delegate = delegate; + } + + @Override + public StateVector getState(double time, StateVector buffer) { + synchronized (getLock()) { + return delegate.getState(time, buffer); + } + } + +} diff --git a/src/main/java/picante/mechanics/providers/lockable/package-info.java b/src/main/java/picante/mechanics/providers/lockable/package-info.java new file mode 100644 index 0000000..adc5c7f --- /dev/null +++ b/src/main/java/picante/mechanics/providers/lockable/package-info.java @@ -0,0 +1,9 @@ +/** + * Defines an implementation that behaves largely as the reference implementation, but provides + * thread-safety through locking. + *+ * TODO: Discuss provided functionality, expected usage, limitations, etc. + *
+ */ +package picante.mechanics.providers.lockable; + diff --git a/src/main/java/picante/mechanics/providers/package-info.java b/src/main/java/picante/mechanics/providers/package-info.java new file mode 100644 index 0000000..6ea6a45 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/package-info.java @@ -0,0 +1,4 @@ +/** + * Implementations of the mechanics provider interfaces are provided in subpackages of this package. + */ +package picante.mechanics.providers; diff --git a/src/main/java/picante/mechanics/providers/reference/ChainLinkEngine.java b/src/main/java/picante/mechanics/providers/reference/ChainLinkEngine.java new file mode 100644 index 0000000..0fd5433 --- /dev/null +++ b/src/main/java/picante/mechanics/providers/reference/ChainLinkEngine.java @@ -0,0 +1,364 @@ +package picante.mechanics.providers.reference; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Package private class that implements the chain linkage algorithm utilized by both the reference + * implementations of the ephemeris and frame provider interfaces. The class is parameterized to + * allow it to function with any sort of time varying tree structure with load priority conflict + * resolution. + * + * In a nut shell, this class takes a list of functions that connect "leaf" nodes to + * "node" nodes and provides an interface to connect them. Conflicts in definitions, i.e. + * two functions provide a definition for the same leaf node are resolved through load order. Namely + * the last applicable function provided at construction time is the first applicable. + *+ * Note:This implementation of the link engine places a few constraints on the function list + * provided to the constructor which are not enforced or checked for in the implementation. In the + * most general case, at any requested time, there may be no functions that close nodes in the tree + * into a cycle. I.e.: + * + *
+ *
+ * A -> B -> C -> D -> Ap
+ * ˆ |
+ * +-------------------+
+ *
+ *
+ *
+ * This can happen if Ap directly connects to A, or if the IDs for A and Ap are such that
+ * A.equals(Ap), or Ap.equals(A). In the event this happens, then the response of the code is
+ * unspecified. At worst, it may enter into an infinite loop evaluating links in the chain.
+ *
+ *
+ * @param Codes used to define nodes in the tree
+ * @param List interface.
+ */
+ private List+ * Note: This implementation performs no checks if the user supplies two equal codes (i.e. + * leaf.equals(node) or node.equals(leaf)) into this method. Checks of this nature should be + * managed by the caller of this method, and the results of such a call are indeterminate. + * Further, the implementation requires that any linkage between nodes at a given time do not + * result in any repeat of a code. Namely: + * + *
+ *
+ * A -> B -> C -> Ap
+ *
+ *
+ *
+ * where A.equals(Ap)
+ *
+ *
+ * @param leaf the ID code of the leaf node from which to start the chain
+ *
+ * @param node the ID code of the node node where the chain ends
+ *
+ * @param time the "time" of interest, which is used to determine the desired function
+ *
+ * @param buffer a list of functions that upon completion contains the linkage from leaf to node.
+ * The contents of the list are cleared at the start of execution of this method. In
+ * addition to the base functions, two separation functions are inserted into the chain in
+ * exceptional cases. If the link requires a forward chain and a backward chain, the
+ * separator function is inserted into the lists between these two chains. If there is no
+ * link, the broken link function is inserted between the forward and backward chain.
+ *
+ * @return true if the linkage is properly determined, false otherwise
+ */
+ public boolean populateLinkage(I leaf, I node, double time, List+ * With regards to thread safety, instances of this provider may be used to create vector functions + * from multiple threads safely. However, the individual vector functions created are not thread + * safe. In general you need multiple copies of the same function to perform evaluation from + * different threads. + *
+ */ +public class ReferenceEphemerisProvider implements EphemerisAndFrameProvider { + + /** + * Creates an implementation of the {@link StateVectorFunction} interface that is utilized purely + * as an entry in a list ofStateVectorFunctions or
+ * PositionVectorFunctions as a marker.
+ *
+ * @return an implementation of the StateVectorFunction that throws exceptions whenever its
+ * methods are invoked.
+ */
+ private static StateVectorFunction createMarkerFunction() {
+ return new StateVectorFunction() {
+
+ private UnsupportedOperationException bugException = new UnsupportedOperationException(
+ "Implementation error. This method should never be invoked.");
+
+ @Override
+ public StateVector getState(@SuppressWarnings("unused") double time,
+ @SuppressWarnings("unused") StateVector buffer) {
+ throw bugException;
+ }
+
+ @Override
+ public VectorIJK getPosition(@SuppressWarnings("unused") double time,
+ @SuppressWarnings("unused") VectorIJK buffer) {
+ throw bugException;
+ }
+
+ @Override
+ public Coverage getCoverage() {
+ throw bugException;
+ }
+
+ @Override
+ public FrameID getFrameID() {
+ throw bugException;
+ }
+
+ @Override
+ public EphemerisID getObserverID() {
+ throw bugException;
+ }
+
+ @Override
+ public EphemerisID getTargetID() {
+ throw bugException;
+ }
+
+ };
+ }
+
+ /**
+ * An instance of the {@link CodeProvider} interface suitable for usage in
+ * ChainLinkEngines that link PositionVectorFunction s together.
+ */
+ private static final EphemerisCodeProviderChainLinkEngines that link StateVectorFunctions together.
+ */
+ private static final EphemerisCodeProviderReferenceFrameProvider to support the state and position vector chaining
+ * performed by functions created by this provider
+ *
+ */
+ public ReferenceEphemerisProvider(List extends PositionVectorFunction> sources,
+ List extends FrameTransformFunction> frameSources) {
+
+ /*
+ * Capture the list of sources in load priority order.
+ */
+ this.sources = ImmutableList.copyOf(sources);
+ this.knownObjects = new HashSet+ * With regards to thread safety, instances of this provider may be used to create transform + * functions from multiple threads safely. However, the individual transform functions created are + * not thread safe. In general you need multiple copies of the same transform to evaluate from + * different threads. + *
+ */ +public class ReferenceFrameProvider implements FrameProvider { + + /** + * Creates an implementation of the {@link StateTransformFunction} interface that is utilized + * purely as an entry in a list ofStateTransformFunctions or
+ * FrameTransformFunctions as a marker.
+ *
+ * @return an implementation of the StateTransformFunction that throws exceptions whenever its
+ * methods are invoked
+ */
+ private static StateTransformFunction createMarkerFunction() {
+ return new StateTransformFunction() {
+
+ private UnsupportedOperationException bugException = new UnsupportedOperationException(
+ "Implementation error. This method should never be invoked.");
+
+ @Override
+ public StateTransform getStateTransform(@SuppressWarnings("unused") double time,
+ @SuppressWarnings("unused") StateTransform buffer) {
+ throw bugException;
+ }
+
+ @Override
+ public Coverage getCoverage() {
+ throw bugException;
+ }
+
+ @Override
+ public FrameID getFromID() {
+ throw bugException;
+ }
+
+ @Override
+ public FrameID getToID() {
+ throw bugException;
+ }
+
+ @Override
+ public RotationMatrixIJK getTransform(@SuppressWarnings("unused") double time,
+ @SuppressWarnings("unused") RotationMatrixIJK buffer) {
+ throw bugException;
+ }
+
+ };
+ }
+
+ /**
+ * An instance of the {@link CodeProvider} interface suitable for usage in
+ * ChainLinkEngines that link FrameTransformFunctions together.
+ */
+ private final static FrameCodeProviderChainLinkEngines that link StateTransformFunctions together.
+ */
+ private final static FrameCodeProvider