From 57c6481e5ef9d872bf90aebf1f7ce704c64c411d Mon Sep 17 00:00:00 2001
From: Jesse Mapel <jmapel@usgs.gov>
Date: Tue, 16 Jun 2020 13:24:11 -0700
Subject: [PATCH] Added Orientations operations (#360)

* Ticked version #

* Added Orientations::inverse

* Added Orientation inverse test

* Added Orientation multiplication operaotrs

* Added Vector operators

* Now using new operator

* Improved docs

* Small fix for exported target
---
 CMakeLists.txt                     |   5 +-
 cmake/config.cmake.in              |   1 +
 include/ale/Orientations.h         |  24 +++++++
 include/ale/Vectors.h              |  29 +++++++++
 setup.py                           |   2 +-
 src/Orientations.cpp               |  59 +++++++++++++++++
 src/Vectors.cpp                    |  19 ++++++
 tests/ctests/CMakeLists.txt        |   1 +
 tests/ctests/OrientationsTests.cpp | 100 ++++++++++++++++++++++++++---
 tests/ctests/VectorTests.cpp       |  55 ++++++++++++++++
 10 files changed, 283 insertions(+), 12 deletions(-)
 create mode 100644 src/Vectors.cpp
 create mode 100644 tests/ctests/VectorTests.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index a5c4adf..9c5ab7e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,7 +7,7 @@
 # Specify the required version of CMake.
 # cmake 3.10 required for ctest/gtest integration
 cmake_minimum_required(VERSION 3.10)
-project(ale VERSION 0.8.0 DESCRIPTION "Abstraction Library for Ephemerides ")
+project(ale VERSION 0.8.1 DESCRIPTION "Abstraction Library for Ephemerides ")
 
 # include what we need
 include(GNUInstallDirs)
@@ -53,7 +53,8 @@ set(ALE_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/InterpUtils.cpp
                   ${CMAKE_CURRENT_SOURCE_DIR}/src/Orientations.cpp
                   ${CMAKE_CURRENT_SOURCE_DIR}/src/States.cpp
                   ${CMAKE_CURRENT_SOURCE_DIR}/src/Isd.cpp
-                  ${CMAKE_CURRENT_SOURCE_DIR}/src/Util.cpp)
+                  ${CMAKE_CURRENT_SOURCE_DIR}/src/Util.cpp
+                  ${CMAKE_CURRENT_SOURCE_DIR}/src/Vectors.cpp)
 set(ALE_HEADER_FILES ${ALE_BUILD_INCLUDE_DIR}/InterpUtils.h
                      ${ALE_BUILD_INCLUDE_DIR}/Rotation.h
                      ${ALE_BUILD_INCLUDE_DIR}/Orientations.h
diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in
index c223d95..82500f6 100644
--- a/cmake/config.cmake.in
+++ b/cmake/config.cmake.in
@@ -3,5 +3,6 @@ set(${CMAKE_FIND_PACKAGE_NAME}_CONFIG ${CMAKE_CURRENT_LIST_FILE})
 find_package_handle_standard_args(@PROJECT_NAME@ CONFIG_MODE)
 
 if(NOT TARGET @PROJECT_NAME@::ale)
+    find_package(nlohmann_json REQUIRED)
     include("${CMAKE_CURRENT_LIST_DIR}/aleTargets.cmake")
 endif()
diff --git a/include/ale/Orientations.h b/include/ale/Orientations.h
index 3a253d1..ece9eef 100644
--- a/include/ale/Orientations.h
+++ b/include/ale/Orientations.h
@@ -71,15 +71,35 @@ namespace ale {
       bool invert=false
     ) const;
 
+    /**
+     * Add an additional constant multiplication.
+     * This is equivalent to left multiplication by a constant rotation
+     */
+    Orientations &addConstantRotation(const Rotation &addedConst);
+
     /**
      * Multiply this orientation by another orientation
      */
     Orientations &operator*=(const Orientations &rhs);
+
     /**
      * Multiply this orientation by a constant rotation
      */
     Orientations &operator*=(const Rotation &rhs);
 
+    /**
+     * Invert the orientations.
+     * Note that inverting a set of orientations twice does not result in
+     * the original orientations. the constant rotation is applied after the
+     * time dependent rotation. This means in the inverse, the constant
+     * rotation is applied first and then the time dependent rotation.
+     * So, the inverted set of orientations is entirely time dependent.
+     * Then, the original constant rotations cannot be recovered when inverting
+     * again. The set of orientations will still operate the same way, but its
+     * internal state will not be the same.
+     */
+     Orientations inverse() const;
+
   private:
     std::vector<Rotation> m_rotations;
     std::vector<ale::Vec3d> m_avs;
@@ -88,6 +108,10 @@ namespace ale {
     std::vector<int> m_constFrames;
     Rotation m_constRotation;
   };
+
+  Orientations operator*(Orientations lhs, const Rotation &rhs);
+  Orientations operator*(const Rotation &lhs, Orientations rhs);
+  Orientations operator*(Orientations lhs, const Orientations &rhs);
 }
 
 #endif
diff --git a/include/ale/Vectors.h b/include/ale/Vectors.h
index 3992073..dabde1e 100644
--- a/include/ale/Vectors.h
+++ b/include/ale/Vectors.h
@@ -2,6 +2,7 @@
 #define ALE_VECTORS_H
 
 #include <stdexcept>
+#include <vector>
 
 namespace ale {
   /** A 3D cartesian vector */
@@ -22,7 +23,35 @@ namespace ale {
 
     Vec3d(double x, double y, double z) : x(x), y(y), z(z) {};
     Vec3d() : x(0.0), y(0.0), z(0.0) {};
+    Vec3d &operator*=(double scalar) {
+      x *= scalar;
+      y *= scalar;
+      z *= scalar;
+      return *this;
+    };
+
+    Vec3d &operator+=(Vec3d addend) {
+      x += addend.x;
+      y += addend.y;
+      z += addend.z;
+      return *this;
+    };
+
+    Vec3d &operator-=(Vec3d addend) {
+      x -= addend.x;
+      y -= addend.y;
+      z -= addend.z;
+      return *this;
+    };
   };
+
+  Vec3d operator*(double scalar, Vec3d vec);
+
+  Vec3d operator*(Vec3d vec, double scalar);
+
+  Vec3d operator+(Vec3d leftVec, const Vec3d &rightVec);
+
+  Vec3d operator-(Vec3d leftVec, const Vec3d &rightVec);
 }
 
 #endif
diff --git a/setup.py b/setup.py
index 3b1a2b7..bb71daa 100644
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ import sys
 from setuptools import setup, find_packages
 
 NAME = "Ale"
-VERSION = "0.8.0"
+VERSION = "0.8.1"
 
 # To install the library, run the following
 #
diff --git a/src/Orientations.cpp b/src/Orientations.cpp
index 004b2c0..799f73a 100644
--- a/src/Orientations.cpp
+++ b/src/Orientations.cpp
@@ -102,6 +102,12 @@ namespace ale {
   }
 
 
+  Orientations &Orientations::addConstantRotation(const Rotation &addedConst) {
+    m_constRotation = addedConst * m_constRotation;
+    return *this;
+  }
+
+
   Orientations &Orientations::operator*=(const Orientations &rhs) {
     std::vector<double> mergedTimes = orderedVecMerge(m_times, rhs.m_times);
     std::vector<Rotation> mergedRotations;
@@ -144,4 +150,57 @@ namespace ale {
 
     return *this;
   }
+
+
+  Orientations Orientations::inverse() const {
+    std::vector<Rotation> newRotations;
+    // The time dependent rotation is applied and the constant rotation is applied second,
+    // so we have to subsume the constant rotations into the time dependent rotations
+    // in the inverse.
+    Rotation constInverseRotation = m_constRotation.inverse();
+    for (size_t i = 0; i < m_rotations.size(); i++) {
+      newRotations.push_back(m_rotations[i].inverse() * constInverseRotation);
+    }
+
+    std::vector<Vec3d> rotatedAvs;
+    for (size_t i = 0; i < m_avs.size(); i++) {
+      Vec3d rotatedAv = -1.0 * (m_constRotation * m_rotations[i])(m_avs[i]);
+      rotatedAvs.push_back(rotatedAv);
+    }
+
+    // Because the constant rotation was subsumed by the time dependet rotations, everything
+    // is a time dependent rotation in the inverse.
+    std::vector<int> newTimeDepFrames;
+    std::vector<int>::const_reverse_iterator timeDepIt = m_timeDepFrames.crbegin();
+    for (; timeDepIt != m_timeDepFrames.crend(); timeDepIt++) {
+      newTimeDepFrames.push_back(*timeDepIt);
+    }
+    std::vector<int>::const_reverse_iterator constIt = m_constFrames.crbegin();
+    // Skip the last frame in the constant list because it's the first frame
+    // in the time dependent list
+    if (constIt != m_constFrames.crend()) {
+      constIt++;
+    }
+    for (; constIt != m_constFrames.rend(); constIt++) {
+      newTimeDepFrames.push_back(*constIt);
+    }
+
+    return Orientations(newRotations, m_times, rotatedAvs, Rotation(1, 0, 0, 0),
+                        std::vector<int>(), newTimeDepFrames);
+  }
+
+
+  Orientations operator*(Orientations lhs, const Rotation &rhs) {
+    return lhs *= rhs;
+  }
+
+
+  Orientations operator*(const Rotation &lhs, Orientations rhs) {
+    return rhs.addConstantRotation(lhs);
+  }
+
+
+  Orientations operator*(Orientations lhs, const Orientations &rhs) {
+    return lhs *= rhs;
+  }
 }
diff --git a/src/Vectors.cpp b/src/Vectors.cpp
new file mode 100644
index 0000000..396ffee
--- /dev/null
+++ b/src/Vectors.cpp
@@ -0,0 +1,19 @@
+#include "ale/Vectors.h"
+
+namespace ale {
+  Vec3d operator*(double scalar, Vec3d vec) {
+    return vec *= scalar;
+  }
+
+  Vec3d operator*(Vec3d vec, double scalar) {
+    return vec *= scalar;
+  }
+
+  Vec3d operator+(Vec3d leftVec, const Vec3d &rightVec) {
+    return leftVec += rightVec;
+  }
+
+  Vec3d operator-(Vec3d leftVec, const Vec3d &rightVec) {
+    return leftVec -= rightVec;
+  }
+}
diff --git a/tests/ctests/CMakeLists.txt b/tests/ctests/CMakeLists.txt
index cf4ab52..bb61043 100644
--- a/tests/ctests/CMakeLists.txt
+++ b/tests/ctests/CMakeLists.txt
@@ -6,6 +6,7 @@ set (ALE_TEST_SOURCE ${CMAKE_SOURCE_DIR}/tests/ctests/IsdTests.cpp
                      ${CMAKE_SOURCE_DIR}/tests/ctests/RotationTests.cpp
                      ${CMAKE_SOURCE_DIR}/tests/ctests/StatesTests.cpp
                      ${CMAKE_SOURCE_DIR}/tests/ctests/TestInterpUtils.cpp
+                     ${CMAKE_SOURCE_DIR}/tests/ctests/VectorTests.cpp
                      ${CMAKE_SOURCE_DIR}/tests/ctests/TestMain.cpp)
 
 if(ALE_BUILD_LOAD)
diff --git a/tests/ctests/OrientationsTests.cpp b/tests/ctests/OrientationsTests.cpp
index 0b9d511..31d5e40 100644
--- a/tests/ctests/OrientationsTests.cpp
+++ b/tests/ctests/OrientationsTests.cpp
@@ -34,7 +34,9 @@ class ConstOrientationTest : public OrientationTest{
     void SetUp() override {
       OrientationTest::SetUp();
       constRotation = Rotation(0, 1, 0, 0);
-      constOrientations = Orientations(rotations, times, avs, constRotation);
+      vector<int> constFrames = {-74021, -74020, -74000};
+      vector<int> timeDepFrames = {-74000, -74900, 1};
+      constOrientations = Orientations(rotations, times, avs, constRotation, constFrames, timeDepFrames);
     }
 
     Rotation constRotation;
@@ -108,21 +110,47 @@ TEST_F(OrientationTest, RotateAt) {
 }
 
 TEST_F(OrientationTest, RotationMultiplication) {
-  Rotation rhs( 0.5, 0.5, 0.5, 0.5);
-  orientations *= rhs;
-  vector<Rotation> outputRotations = orientations.getRotations();
+  vector<Rotation> originalRotations = orientations.getRotations();
+  vector<double> originalConstQuats = orientations.getConstantRotation().toQuaternion();
+  Rotation constRot( 0.5, 0.5, 0.5, 0.5);
   vector<vector<double>> expectedQuats = {
     {-0.5, 0.5, 0.5, 0.5},
     {-1.0, 0.0, 0.0, 0.0},
     { 0.5, 0.5, 0.5, 0.5}
   };
-  for (size_t i = 0; i < outputRotations.size(); i++) {
-    vector<double> quats = outputRotations[i].toQuaternion();
+
+  Orientations rightMultiplied = orientations * constRot;
+  vector<Rotation> outputRightRotations = rightMultiplied.getRotations();
+  ASSERT_EQ(expectedQuats.size(), outputRightRotations.size());
+  for (size_t i = 0; i < outputRightRotations.size(); i++) {
+    vector<double> quats = outputRightRotations[i].toQuaternion();
     EXPECT_EQ(expectedQuats[i][0], quats[0]);
     EXPECT_EQ(expectedQuats[i][1], quats[1]);
     EXPECT_EQ(expectedQuats[i][2], quats[2]);
     EXPECT_EQ(expectedQuats[i][3], quats[3]);
   }
+  vector<double> outputRightConstQuats = rightMultiplied.getConstantRotation().toQuaternion();
+  EXPECT_EQ(originalConstQuats[0], outputRightConstQuats[0]);
+  EXPECT_EQ(originalConstQuats[1], outputRightConstQuats[1]);
+  EXPECT_EQ(originalConstQuats[2], outputRightConstQuats[2]);
+  EXPECT_EQ(originalConstQuats[3], outputRightConstQuats[3]);
+
+  Orientations leftMultiplied = constRot * orientations;
+  vector<Rotation> outputLeftRotations = leftMultiplied.getRotations();
+  ASSERT_EQ(originalRotations.size(), outputLeftRotations.size());
+  for (size_t i = 0; i < outputLeftRotations.size(); i++) {
+    vector<double> originalQuats = originalRotations[i].toQuaternion();
+    vector<double> quats = outputLeftRotations[i].toQuaternion();
+    EXPECT_EQ(originalQuats[0], quats[0]);
+    EXPECT_EQ(originalQuats[1], quats[1]);
+    EXPECT_EQ(originalQuats[2], quats[2]);
+    EXPECT_EQ(originalQuats[3], quats[3]);
+  }
+  vector<double> outputLeftConstQuats = leftMultiplied.getConstantRotation().toQuaternion();
+  EXPECT_EQ(0.5, outputLeftConstQuats[0]);
+  EXPECT_EQ(0.5, outputLeftConstQuats[1]);
+  EXPECT_EQ(0.5, outputLeftConstQuats[2]);
+  EXPECT_EQ(0.5, outputLeftConstQuats[3]);
 }
 
 TEST_F(OrientationTest, OrientationMultiplication) {
@@ -162,8 +190,8 @@ TEST_F(ConstOrientationTest, RotateAt) {
 }
 
 TEST_F(ConstOrientationTest, OrientationMultiplication) {
-  constOrientations *= orientations;
-  vector<Rotation> outputRotations = constOrientations.getRotations();
+  Orientations multOrientation = constOrientations * orientations;
+  vector<Rotation> outputRotations = multOrientation.getRotations();
   vector<vector<double>> expectedQuats = {
     {-0.5, 0.5, 0.5, 0.5},
     {-0.5,-0.5,-0.5,-0.5},
@@ -176,9 +204,63 @@ TEST_F(ConstOrientationTest, OrientationMultiplication) {
     EXPECT_EQ(expectedQuats[i][2], quats[2]);
     EXPECT_EQ(expectedQuats[i][3], quats[3]);
   }
-  vector<double> constQuats = constOrientations.getConstantRotation().toQuaternion();
+  vector<double> constQuats = multOrientation.getConstantRotation().toQuaternion();
   EXPECT_EQ(constQuats[0], 0);
   EXPECT_EQ(constQuats[1], 1);
   EXPECT_EQ(constQuats[2], 0);
   EXPECT_EQ(constQuats[3], 0);
 }
+
+TEST_F(ConstOrientationTest, OrientationInverse) {
+  Orientations inverseOrientation = constOrientations.inverse();
+
+  vector<int> constFrames = inverseOrientation.getConstantFrames();
+  ASSERT_EQ(constFrames.size(), 0);
+
+  vector<int> timeDepFrames = inverseOrientation.getTimeDependentFrames();
+  ASSERT_EQ(timeDepFrames.size(), 5);
+  EXPECT_EQ(timeDepFrames[0], 1);
+  EXPECT_EQ(timeDepFrames[1], -74900);
+  EXPECT_EQ(timeDepFrames[2], -74000);
+  EXPECT_EQ(timeDepFrames[3], -74020);
+  EXPECT_EQ(timeDepFrames[4], -74021);
+
+  vector<double> newTimes = inverseOrientation.getTimes();
+  ASSERT_EQ(newTimes.size(), 3);
+  EXPECT_EQ(newTimes[0], 0.0);
+  EXPECT_EQ(newTimes[1], 2.0);
+  EXPECT_EQ(newTimes[2], 4.0);
+
+  vector<Rotation> newRotations = inverseOrientation.getRotations();
+  vector<vector<double>> expectedQuats = {
+    {-0.5, -0.5, 0.5, -0.5},
+    {-0.5, 0.5, 0.5, -0.5},
+    {0.0, -1.0, 0.0, 0.0}
+  };
+  ASSERT_EQ(newRotations.size(), expectedQuats.size());
+  for (size_t i = 0; i < newRotations.size(); i++) {
+    vector<double> newQuats = newRotations[i].toQuaternion();
+    ASSERT_EQ(newQuats.size(), 4) << "Rotation " << i;
+    EXPECT_EQ(newQuats[0], expectedQuats[i][0]) << "Rotation " << i;
+    EXPECT_EQ(newQuats[1], expectedQuats[i][1]) << "Rotation " << i;
+    EXPECT_EQ(newQuats[2], expectedQuats[i][2]) << "Rotation " << i;
+    EXPECT_EQ(newQuats[3], expectedQuats[i][3]) << "Rotation " << i;
+  }
+
+  vector<Vec3d> newAvs = inverseOrientation.getAngularVelocities();
+  vector<Vec3d> expectedAvs = {
+    Vec3d(-2.0 / 3.0 * M_PI, 2.0 / 3.0 * M_PI, 2.0 / 3.0 * M_PI),
+    Vec3d(-2.0 / 3.0 * M_PI, 2.0 / 3.0 * M_PI, 2.0 / 3.0 * M_PI),
+    Vec3d(-2.0 / 3.0 * M_PI, 2.0 / 3.0 * M_PI, 2.0 / 3.0 * M_PI)
+  };
+  ASSERT_EQ(newAvs.size(), expectedAvs.size());
+  EXPECT_EQ(newAvs[0].x, expectedAvs[0].x);
+  EXPECT_EQ(newAvs[0].y, expectedAvs[0].y);
+  EXPECT_EQ(newAvs[0].z, expectedAvs[0].z);
+  EXPECT_EQ(newAvs[1].x, expectedAvs[1].x);
+  EXPECT_EQ(newAvs[1].y, expectedAvs[1].y);
+  EXPECT_EQ(newAvs[1].z, expectedAvs[1].z);
+  EXPECT_EQ(newAvs[2].x, expectedAvs[2].x);
+  EXPECT_EQ(newAvs[2].y, expectedAvs[2].y);
+  EXPECT_EQ(newAvs[2].z, expectedAvs[2].z);
+}
diff --git a/tests/ctests/VectorTests.cpp b/tests/ctests/VectorTests.cpp
new file mode 100644
index 0000000..ca6aa4d
--- /dev/null
+++ b/tests/ctests/VectorTests.cpp
@@ -0,0 +1,55 @@
+#include "gmock/gmock.h"
+
+#include "ale/Vectors.h"
+
+using namespace std;
+using namespace ale;
+
+TEST(Vector, multiplication) {
+  Vec3d testVec(1.0, 2.0, 3.0);
+
+  Vec3d scaledVec = 2.0 * testVec;
+  EXPECT_EQ(scaledVec.x, 2.0);
+  EXPECT_EQ(scaledVec.y, 4.0);
+  EXPECT_EQ(scaledVec.z, 6.0);
+
+  Vec3d rightMultiplied = testVec * 2.0;
+  EXPECT_EQ(rightMultiplied.x, 2.0);
+  EXPECT_EQ(rightMultiplied.y, 4.0);
+  EXPECT_EQ(rightMultiplied.z, 6.0);
+
+  testVec *= -1;
+  EXPECT_EQ(testVec.x, -1.0);
+  EXPECT_EQ(testVec.y, -2.0);
+  EXPECT_EQ(testVec.z, -3.0);
+}
+
+TEST(Vector, addition) {
+  Vec3d vec1(1.0, 2.0, 3.0);
+  Vec3d vec2(5.0, 7.0, 9.0);
+
+  Vec3d sumVec = vec1 + vec2;
+  EXPECT_EQ(sumVec.x, 6.0);
+  EXPECT_EQ(sumVec.y, 9.0);
+  EXPECT_EQ(sumVec.z, 12.0);
+
+  vec1 += vec2;
+  EXPECT_EQ(vec1.x, 6.0);
+  EXPECT_EQ(vec1.y, 9.0);
+  EXPECT_EQ(vec1.z, 12.0);
+}
+
+TEST(Vector, subtraction) {
+  Vec3d vec1(1.0, 2.0, 3.0);
+  Vec3d vec2(5.0, 7.0, 9.0);
+
+  Vec3d diffVec = vec1 - vec2;
+  EXPECT_EQ(diffVec.x, -4.0);
+  EXPECT_EQ(diffVec.y, -5.0);
+  EXPECT_EQ(diffVec.z, -6.0);
+
+  vec1 -= vec2;
+  EXPECT_EQ(vec1.x, -4.0);
+  EXPECT_EQ(vec1.y, -5.0);
+  EXPECT_EQ(vec1.z, -6.0);
+}
-- 
GitLab