diff --git a/CMakeLists.txt b/CMakeLists.txt
index 25e2b850ce1593711b470bcb2e9262e97ac2e155..b0008c84528c43c7b8c854cbdf23377efb1c6537 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.18)
+cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
 
 # default settings
 set(ARMARX_ENABLE_DEPENDENCY_VERSION_CHECK_DEFAULT FALSE)
@@ -25,18 +25,20 @@ armarx_find_package(PUBLIC VisionX QUIET)
 
 # - optional
 armarx_find_package(PUBLIC OpenMP QUIET)
-armarx_find_package(PUBLIC Ceres QUIET)
+armarx_find_package(PUBLIC Ceres 2.0.0 EXACT QUIET)
 armarx_find_package(PUBLIC VTK QUIET)
 armarx_find_package(PUBLIC SemanticObjectRelations QUIET)
 armarx_find_package(PUBLIC OpenCV QUIET)  # Required as RobotAPI is a legacy project.
 
+armarx_find_package(PUBLIC range-v3 CONFIG REQUIRED)
 
 add_subdirectory(etc)
 
-set(RANGES_VERBOSE_BUILD OFF)
-set(RANGES_RELEASE_BUILD ON)
-set(RANGES_CXX_STD 17)
-add_subdirectory(external)
+
+# set(RANGES_VERBOSE_BUILD OFF)
+# set(RANGES_RELEASE_BUILD ON)
+# set(RANGES_CXX_STD 17)
+# add_subdirectory(external)
 
 
 #include(FetchContent)
diff --git a/source/armarx/navigation/algorithms/CMakeLists.txt b/source/armarx/navigation/algorithms/CMakeLists.txt
index c93bbffc732430e1462164322d70bdb8512bb461..bff2cb2ae4768543bebcaf17f3170674631c1284 100644
--- a/source/armarx/navigation/algorithms/CMakeLists.txt
+++ b/source/armarx/navigation/algorithms/CMakeLists.txt
@@ -1,10 +1,14 @@
 armarx_add_library(algorithms
     DEPENDENCIES
-        ArmarXCoreInterfaces
-        ArmarXCore
-        armarx_navigation::core
-        armarx_navigation::conversions
-        OpenMP::OpenMP_CXX
+        PUBLIC
+            ArmarXCoreInterfaces
+            ArmarXCore
+            armarx_navigation::core
+            armarx_navigation::conversions
+        PRIVATE
+            OpenMP::OpenMP_CXX
+            range-v3::range-v3
+            Ceres::ceres
     DEPENDENCIES_LEGACY
         OpenCV
     SOURCES
@@ -23,6 +27,7 @@ armarx_add_library(algorithms
         # smoothing
         ./smoothing/ChainApproximation.cpp
         ./smoothing/CircularPathSmoothing.cpp
+        ./math/ellipse.cpp
     HEADERS
         ./algorithms.h
         ./Costmap.h
@@ -39,8 +44,12 @@ armarx_add_library(algorithms
         # smoothing
         ./smoothing/ChainApproximation.h
         ./smoothing/CircularPathSmoothing.h
+        ./math/ellipse.h
 )
 
+#
+#   Tests
+#
 
 armarx_add_test(algorithms_spfa_test
     TEST_FILES
@@ -50,3 +59,19 @@ armarx_add_test(algorithms_spfa_test
     DEPENDENCIES_LEGACY
         OpenCV
 )
+
+
+armarx_add_test(algorithms_costmap_test
+    TEST_FILES
+        test/algorithms_costmap_test.cpp
+    DEPENDENCIES
+        armarx_navigation::algorithms
+)
+
+
+armarx_add_test(algorithms_math_ellipse_test
+    TEST_FILES
+        test/algorithms_math_ellipse_test.cpp
+    DEPENDENCIES
+        armarx_navigation::algorithms
+)
diff --git a/source/armarx/navigation/algorithms/math/ellipse.cpp b/source/armarx/navigation/algorithms/math/ellipse.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9c13587051c5679b4ae9c2e4935e52262dae92da
--- /dev/null
+++ b/source/armarx/navigation/algorithms/math/ellipse.cpp
@@ -0,0 +1,137 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * ArmarX is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author     Fabian Reister ( fabian dot reister at kit dot edu )
+ * @date       2022
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+#include "ellipse.h"
+
+#include <cmath>
+
+#include <ceres/autodiff_cost_function.h>
+#include <ceres/ceres.h>
+#include <ceres/types.h>
+
+#include <Eigen/Core>
+
+#include <SimoxUtility/math/periodic/periodic_clamp.h>
+
+namespace armarx::navigation::algorithms::math
+{
+
+    template <typename T>
+    T
+    distance(const Ellipse& a, const Ellipse& b, const T thetaA, const T thetaB)
+    {
+        const auto pA = a.getPosition(thetaA);
+        const auto pB = b.getPosition(thetaB);
+
+        const auto dX = pB.at(0) - pA.at(0);
+        const auto dY = pB.at(1) - pB.at(1);
+
+        return ceres::sqrt(dX * dX + dY * dY);
+    }
+
+    class DistanceBetweenEllipsesCostFunctor
+    {
+    public:
+        DistanceBetweenEllipsesCostFunctor(const Ellipse& ellipseA, const Ellipse& ellipseB) :
+            ellipseA(ellipseA), ellipseB(ellipseB)
+        {
+        }
+
+        template <typename T>
+        bool
+        operator()(const T* const x, const T* const y, T* e) const
+        {
+            const auto& thetaA = x[0];
+            const auto& thetaB = y[0];
+
+            e[0] = distance(ellipseA, ellipseB, thetaA, thetaB);
+            return true;
+        }
+
+    private:
+        const Ellipse ellipseA;
+        const Ellipse ellipseB;
+    };
+
+
+    std::array<double, 2>
+    guessInitialThetas(const Ellipse& ellipseA, const Ellipse& ellipseB)
+    {
+        const auto toEigen = [](const std::array<double, 2>& position) -> Eigen::Vector2f {
+            return {position.at(0), position.at(1)};
+        };
+
+        // vector from A's center to B's center ...
+        const Eigen::Vector2f a_V_b = toEigen(ellipseB.position) - toEigen(ellipseA.position);
+        // and its orientation
+        const auto yaw = std::atan2(a_V_b.y(), a_V_b.x());
+
+        // both initial thetas represent the intersection between the ellipse and the line connecting the centers
+        const double thetaA = yaw - ellipseA.yaw;
+        const double thetaB = yaw + M_PI - ellipseB.yaw;
+
+        return {simox::math::periodic_clamp(thetaA, -M_PI, M_PI),
+                simox::math::periodic_clamp(thetaB, -M_PI, M_PI)};
+    }
+
+
+    double
+    distance(const Ellipse& ellipseA, const Ellipse& ellipseB, const bool verbose)
+    {
+        ceres::Problem problem;
+
+        ceres::CostFunction* costFunction =
+            new ceres::AutoDiffCostFunction<DistanceBetweenEllipsesCostFunctor, 1, 1, 1>(
+                new DistanceBetweenEllipsesCostFunctor(ellipseA, ellipseB));
+
+        std::array<double, 2> thetas = guessInitialThetas(ellipseA, ellipseB);
+        if (verbose)
+        {
+            std::cout << "Initial thetas: " << thetas.at(0) << ", " << thetas.at(1);
+        }
+
+        problem.AddResidualBlock(costFunction, nullptr, {&thetas.at(0), &thetas.at(1)});
+
+        // Run the solver!
+        ceres::Solver::Options options;
+        options.linear_solver_type = ceres::DENSE_QR;
+        options.minimizer_progress_to_stdout = verbose;
+        options.max_num_iterations = 100;
+        options.function_tolerance = 0.001;
+
+        ceres::Solver::Summary summary;
+        Solve(options, &problem, &summary);
+
+
+        thetas.at(0) = simox::math::periodic_clamp(thetas.at(0), -M_PI, M_PI);
+        thetas.at(1) = simox::math::periodic_clamp(thetas.at(1), -M_PI, M_PI);
+
+        if (verbose)
+        {
+            std::cout << summary.FullReport() << "\n";
+            std::cout << "Optimal parameters: " << thetas.at(0) << ", " << thetas.at(1)
+                      << std::endl;
+        }
+
+        return distance(ellipseA, ellipseB, thetas.at(0), thetas.at(1));
+    }
+
+} // namespace armarx::navigation::algorithms::math
diff --git a/source/armarx/navigation/algorithms/math/ellipse.h b/source/armarx/navigation/algorithms/math/ellipse.h
new file mode 100644
index 0000000000000000000000000000000000000000..51e1c3ccc6ea0fdc1a1fc5b34c757cd2c3cf6689
--- /dev/null
+++ b/source/armarx/navigation/algorithms/math/ellipse.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <array>
+
+#include <ceres/jet.h>
+
+namespace armarx::navigation::algorithms::math
+{
+
+    class Ellipse
+    {
+
+    public:
+
+        Ellipse(const std::array<double, 2>& position,
+                const double yaw,
+                const double a,
+                const double b) :
+            position(position),
+            ceta{std::cos(yaw), std::sin(yaw)},
+            eta{-std::sin(yaw), std::cos(yaw)},
+            yaw(yaw),
+            a(a),
+            b(b)
+        {
+        }
+
+        template <typename T>
+        std::array<T, 2>
+        getPosition(const T theta) const
+        {
+            const auto cosTheta = ceres::cos(theta);
+            const auto sinTheta = ceres::sin(theta);
+
+            std::array<T, 2> pos;
+            pos.at(0) = position.at(0) + a * cosTheta * ceta.at(0) + b * sinTheta * eta.at(0);
+            pos.at(1) = position.at(1) + a * cosTheta * ceta.at(1) + b * sinTheta * eta.at(1);
+
+            return pos;
+        }
+
+        // center pose of the ellipse defined via
+        // ... its position
+        const std::array<double, 2> position;
+        // ... and two orthogonal vectors
+        const std::array<double, 2> ceta;
+        const std::array<double, 2> eta;
+        // ... and for simple access
+        const double yaw;
+
+        // these axis lengths correspond to ceta (-> a) and eta (-> b)
+        const double a;
+        const double b;
+    };
+
+  
+    /**
+     * @brief numerically approximates the distance between two ellipses
+     * 
+     * @param ellipseA, ellipseB: the ellipses
+     * @param verbose Will print optimization related things to std::cout if enabled
+     * @return double the distance
+     */
+    double distance(const Ellipse& ellipseA, const Ellipse& ellipseB, bool verbose = false);
+
+} // namespace armarx::navigation::algorithms::math
diff --git a/source/armarx/navigation/algorithms/test/algorithms_math_ellipse_test.cpp b/source/armarx/navigation/algorithms/test/algorithms_math_ellipse_test.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..af7d017079658e1101ed003b447e6a31a997dd2d
--- /dev/null
+++ b/source/armarx/navigation/algorithms/test/algorithms_math_ellipse_test.cpp
@@ -0,0 +1,45 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * ArmarX is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    Navigation::ArmarXObjects::algorithms
+ * @author     Fabian Reister ( fabian dot reister at kit dot edu )
+ * @date       2022
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+
+#define BOOST_TEST_MODULE Navigation::ArmarXLibraries::algorithms
+
+#define ARMARX_BOOST_TEST
+
+#include <armarx/navigation/Test.h>
+#include <armarx/navigation/algorithms/math/ellipse.h>
+
+
+BOOST_AUTO_TEST_CASE(testOptimizeDistance)
+{
+    // e1 = Ellipse(position=np.asarray([0, 0]), orientation=0.3, a=0.1, b=0.3)
+    armarx::navigation::algorithms::math::Ellipse ellipseA({0, 0}, 0.3, 0.1, 0.3);
+
+    // e2 = Ellipse(position=np.asarray([1, 0]), orientation=-0.1, a=0.1, b=0.3)
+    armarx::navigation::algorithms::math::Ellipse ellipseB({1, 0}, 0.1, 0.1, 0.3);
+
+    const double distance = armarx::navigation::algorithms::math::distance(ellipseA, ellipseB, true);
+    std::cout << "Distance is " << distance << std::endl;
+
+    BOOST_CHECK(distance > 0);
+    BOOST_CHECK(std::abs(distance - 0.766) < 0.01);
+}