Skip to content

Commit

Permalink
Added ellipsoid class (#21)
Browse files Browse the repository at this point in the history
* Added ellipsoid class

* added doxygen to ellispoid methods that missed it
  • Loading branch information
EirikKolas authored Jul 15, 2024
1 parent 3d371c9 commit a8c1919
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 28 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include <Eigen/Dense>
#include <numbers>
#include <random>

namespace vortex::prob {
Expand Down Expand Up @@ -40,7 +41,7 @@ template <size_t n_dims> class MultiVarGauss {
{
Vec_n diff = x - mean();
double exponent = -0.5 * diff.transpose() * cov_inv() * diff;
return std::exp(exponent) / std::sqrt(std::pow(2 * M_PI, size()) * cov().determinant());
return std::exp(exponent) / std::sqrt(std::pow(2 * std::numbers::pi, size()) * cov().determinant());
}

/** Calculate the log likelihood of x given the Gaussian.
Expand All @@ -52,7 +53,7 @@ template <size_t n_dims> class MultiVarGauss {
{
Vec_n diff = x - mean();
double exponent = -0.5 * diff.transpose() * cov_inv() * diff;
return exponent - 0.5 * std::log(std::pow(2 * M_PI, size()) * cov().determinant());
return exponent - 0.5 * std::log(std::pow(2 * std::numbers::pi, size()) * cov().determinant());
}

Vec_n mean() const { return mean_; }
Expand Down
51 changes: 26 additions & 25 deletions vortex-filtering/include/vortex_filtering/types/type_aliases.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <vortex_filtering/probability/gaussian_mixture.hpp>
#include <vortex_filtering/probability/multi_var_gauss.hpp>

#define MATRIX_TYPES(t1, s1, t2, s2) \
#define MATRIX_TYPES(t1, s1, t2, s2) \
using Mat_##t1##t2 = Eigen::Matrix<double, s1, s2>; \
using Mat_##t2##t1 = Eigen::Matrix<double, s2, s1>;

Expand All @@ -16,17 +16,17 @@
using Gauss_##t1 = vortex::prob::Gauss<s1>; \
using GaussMix_##t1 = vortex::prob::GaussMix<s1>;

#define TWO_TYPES(t1, s1, t2, s2) \
ONE_TYPE(t1, s1) \
ONE_TYPE(t2, s2) \
#define TWO_TYPES(t1, s1, t2, s2) \
ONE_TYPE(t1, s1) \
ONE_TYPE(t2, s2) \
MATRIX_TYPES(t1, s1, t2, s2)

#define THREE_TYPES(t1, s1, t2, s2, t3, s3) \
ONE_TYPE(t1, s1) \
ONE_TYPE(t2, s2) \
ONE_TYPE(t3, s3) \
MATRIX_TYPES(t1, s1, t2, s2) \
MATRIX_TYPES(t1, s1, t3, s3) \
#define THREE_TYPES(t1, s1, t2, s2, t3, s3) \
ONE_TYPE(t1, s1) \
ONE_TYPE(t2, s2) \
ONE_TYPE(t3, s3) \
MATRIX_TYPES(t1, s1, t2, s2) \
MATRIX_TYPES(t1, s1, t3, s3) \
MATRIX_TYPES(t2, s2, t3, s3)

#define FOUR_TYPES(t1, s1, t2, s2, t3, s3, t4, s4) \
Expand All @@ -41,21 +41,21 @@
MATRIX_TYPES(t2, s2, t4, s4) \
MATRIX_TYPES(t3, s3, t4, s4)

#define FIVE_TYPES(t1, s1, t2, s2, t3, s3, t4, s4, t5, s5) \
ONE_TYPE(t1, s1) \
ONE_TYPE(t2, s2) \
ONE_TYPE(t3, s3) \
ONE_TYPE(t4, s4) \
ONE_TYPE(t5, s5) \
MATRIX_TYPES(t1, s1, t2, s2) \
MATRIX_TYPES(t1, s1, t3, s3) \
MATRIX_TYPES(t1, s1, t4, s4) \
MATRIX_TYPES(t1, s1, t5, s5) \
MATRIX_TYPES(t2, s2, t3, s3) \
MATRIX_TYPES(t2, s2, t4, s4) \
MATRIX_TYPES(t2, s2, t5, s5) \
MATRIX_TYPES(t3, s3, t4, s4) \
MATRIX_TYPES(t3, s3, t5, s5) \
#define FIVE_TYPES(t1, s1, t2, s2, t3, s3, t4, s4, t5, s5) \
ONE_TYPE(t1, s1) \
ONE_TYPE(t2, s2) \
ONE_TYPE(t3, s3) \
ONE_TYPE(t4, s4) \
ONE_TYPE(t5, s5) \
MATRIX_TYPES(t1, s1, t2, s2) \
MATRIX_TYPES(t1, s1, t3, s3) \
MATRIX_TYPES(t1, s1, t4, s4) \
MATRIX_TYPES(t1, s1, t5, s5) \
MATRIX_TYPES(t2, s2, t3, s3) \
MATRIX_TYPES(t2, s2, t4, s4) \
MATRIX_TYPES(t2, s2, t5, s5) \
MATRIX_TYPES(t3, s3, t4, s4) \
MATRIX_TYPES(t3, s3, t5, s5) \
MATRIX_TYPES(t4, s4, t5, s5)

#define ONE_TYPE_STRUCT(t1) \
Expand Down Expand Up @@ -96,6 +96,7 @@ ONE_TYPE_STRUCT(z)
ONE_TYPE_STRUCT(u)
ONE_TYPE_STRUCT(v)
ONE_TYPE_STRUCT(w)
ONE_TYPE_STRUCT(n)

TWO_TYPES_STRUCT(x, z) // Sensor model without noise
TWO_TYPES_STRUCT(x, v) // Dynamic model without input
Expand Down
5 changes: 5 additions & 0 deletions vortex-filtering/include/vortex_filtering/utils/ellipse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ class Ellipse {
*/
double angle_deg() const;

/** Get the area of the ellipse
* @return double
*/
double area() const;

private:
Eigen::Vector2d center_;
double a_;
Expand Down
72 changes: 72 additions & 0 deletions vortex-filtering/include/vortex_filtering/utils/ellipsoid.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#pragma once

#include <Eigen/Dense>
#include <cmath>
#include <numbers>
#include <vortex_filtering/probability/multi_var_gauss.hpp>
#include <vortex_filtering/types/type_aliases.hpp>

namespace vortex::utils {

template <size_t n_dims> class Ellipsoid {
public:
using T = Types_n<n_dims>;

/** Construct a new Ellipsoid object
* @param gauss n-dimensional Gaussian distribution
* @param scale_factor scale factor for the number of standard deviations (NB! This is slightly different from the ellipse scale factor)
*/
Ellipsoid(T::Gauss_n gauss, double scale_factor = 1.0)
: gauss_(gauss)
, scale_factor_(scale_factor)
{
}

/** Get the center of the ellipsoid
* @return T::Vec_n
*/
T::Vec_n center() const { return gauss_.mean(); }

/** Get the semi-major axis lengths of the ellipsoid sorted in descending order
* @return T::Vec_n
*/
T::Vec_n semi_axis_lengths() const
{
Eigen::SelfAdjointEigenSolver<typename T::Mat_nn> eigen_solver(gauss_.cov());
typename T::Vec_n eigenvalues = eigen_solver.eigenvalues();
typename T::Vec_n lengths = eigenvalues.array().sqrt().reverse();
return lengths * scale_factor_;
}

/** Get the axis lengths of the ellipsoid sorted in descending order
* @return T::Vec_n
*/
T::Vec_n axis_lengths() const { return 2.0 * semi_axis_lengths(); }

/** Get the orthonormal axes of the ellipsoid corresponding to the eigen values sorted in descending order
* @return T::Mat_nn
*/
T::Mat_nn orthonormal_axes() const
{
Eigen::SelfAdjointEigenSolver<typename T::Mat_nn> eigen_solver(gauss_.cov());
typename T::Mat_nn eigen_vectors = eigen_solver.eigenvectors().colwise().reverse();
return eigen_vectors;
}

/** Get the volume of the ellipsoid
* @return double
*/
double volume() const
{
constexpr double scaling = std::pow(std::numbers::pi, n_dims / 2.0) / std::tgamma(n_dims / 2.0 + 1);
typename T::Vec_n lengths = semi_axis_lengths();
double volume = scaling * lengths.prod();
return volume;
}

private:
typename T::Gauss_n gauss_;
double scale_factor_;
};

} // namespace vortex::utils
6 changes: 5 additions & 1 deletion vortex-filtering/src/utils/ellipse.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include <numbers>
#include <vortex_filtering/utils/ellipse.hpp>

using std::numbers::pi;

namespace vortex {
namespace utils {

Expand Down Expand Up @@ -32,7 +35,8 @@ double Ellipse::major_axis() const { return 2 * a_; }
double Ellipse::minor_axis() const { return 2 * b_; }
Eigen::Vector2d Ellipse::axes() const { return Eigen::Vector2d(2 * a_, 2 * b_); }
double Ellipse::angle_rad() const { return angle_; }
double Ellipse::angle_deg() const { return angle_ * 180 / M_PI; }
double Ellipse::angle_deg() const { return angle_ * 180 / pi; }
double Ellipse::area() const { return pi * a_ * b_; }

} // namespace utils
} // namespace vortex
37 changes: 37 additions & 0 deletions vortex-filtering/src/utils/gamma_function.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <cmath>
#include <numbers>
#include <limits>

using std::numbers::pi;

constexpr double gamma_function(double x) {
if (x == 0.0) {
return std::numeric_limits<double>::infinity();
}

if (x < 0.0) {
return pi / (std::sin(pi * x) * gamma_function(1 - x));
}

double result = 1.0;
while (x < 1.0) {
result /= x;
x += 1.0;
}

constexpr double p[] = {
0.99999999999980993, 676.5203681218851, -1259.1392167224028,
771.32342877765313, -176.61502916214059, 12.507343278686905,
-0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7
};

double y = x;
double tmp = x + 5.5;
tmp -= (x + 0.5) * std::log(tmp);
double ser = 1.000000000190015;
for (int i = 0; i < 9; ++i) {
ser += p[i] / ++y;
}

return std::exp(-tmp + std::log(2.5066282746310005 * ser / x));
}
1 change: 1 addition & 0 deletions vortex-filtering/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ ament_add_gtest(${PROJECT_NAME}_test

dynamic_model_test.cpp
ekf_test.cpp
ellipsoid_test.cpp
imm_test.cpp
ipda_test.cpp
numerical_integration_test.cpp
Expand Down
74 changes: 74 additions & 0 deletions vortex-filtering/test/ellipsoid_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "gtest_assertions.hpp"

#include <gtest/gtest.h>
#include <numbers>
#include <vortex_filtering/utils/ellipse.hpp>
#include <vortex_filtering/utils/ellipsoid.hpp>

TEST(Ellipse, constructorFromParams)
{
Eigen::Vector2d center(1.0, 2.0);
double a = 3.0;
double b = 2.0;
double angle = std::numbers::pi / 2.0;
Eigen::Matrix2d cov;
vortex::utils::Ellipse ellipse(center, a, b, angle);

EXPECT_EQ(ellipse.center(), center);
EXPECT_EQ(ellipse.a(), a);
EXPECT_EQ(ellipse.b(), b);
}

TEST(Ellipse, constructorFromGauss)
{
Eigen::Vector2d center(1.0, 2.0);
double a = 3.0;
double b = 2.0;
double angle = std::numbers::pi / 2.0;

Eigen::Matrix2d eigenvectors{{std::cos(angle), -std::sin(angle)}, {std::sin(angle), std::cos(angle)}};
Eigen::Vector2d eigenvalues = {a * a, b * b};

Eigen::Matrix2d cov = eigenvectors * eigenvalues.asDiagonal() * eigenvectors.transpose();

vortex::prob::Gauss2d gauss(center, cov);
vortex::utils::Ellipse ellipse(gauss);

EXPECT_EQ(ellipse.center(), center);
EXPECT_EQ(ellipse.a(), a);
EXPECT_EQ(ellipse.b(), b);
}

TEST(Ellipsoid, constructorFromGauss)
{
Eigen::Vector2d center(1.0, 2.0);
Eigen::Matrix2d cov{{1.0, 0.0}, {0.0, 9.0}};
vortex::prob::Gauss2d gauss(center, cov);
vortex::utils::Ellipsoid<2> ellipsoid(gauss);

EXPECT_EQ(ellipsoid.center(), center);
EXPECT_EQ(ellipsoid.semi_axis_lengths()(0), 3.0);
EXPECT_EQ(ellipsoid.semi_axis_lengths()(1), 1.0);
EXPECT_EQ(ellipsoid.volume(), std::numbers::pi * 3.0 * 1.0);
}

TEST(Ellipsoid, sameAsEllipse)
{
Eigen::Vector2d center(1.0, 2.0);
double a = 3.0;
double b = 2.0;
double angle = std::numbers::pi / 2.0;

Eigen::Matrix2d eigenvectors{{std::cos(angle), -std::sin(angle)}, {std::sin(angle), std::cos(angle)}};
Eigen::Vector2d eigenvalues = {a * a, b * b};

Eigen::Matrix2d cov = eigenvectors * eigenvalues.asDiagonal() * eigenvectors.transpose();

vortex::prob::Gauss2d gauss(center, cov);
vortex::utils::Ellipsoid<2> ellipsoid(gauss);
vortex::utils::Ellipse ellipse(gauss);

EXPECT_EQ(ellipsoid.center(), ellipse.center());
EXPECT_EQ(ellipsoid.axis_lengths(), ellipse.axes());
EXPECT_EQ(ellipsoid.volume(), ellipse.area());
}

0 comments on commit a8c1919

Please sign in to comment.