From f8393cd7c462a40c6cca83094ac35330766a634d Mon Sep 17 00:00:00 2001 From: Pau Hebrero <65550121+phc1990@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:05:11 +0200 Subject: [PATCH] feat: add Interval clip, sort, merge, gaps, or, and functionalities (#147) * feat: add Interval clip, sort, merge get_gaps functionality * feat: suggestions * fix: fix gap intervals * feat: add Interval logical OR conjunction * feat: add Logical AND conjunction for intervals * build: add interval logical conjunction bindings * chore: apply PR suggestions --------- Co-authored-by: Vishwa Shah --- .../Object/Interval.cpp | 20 +- bindings/python/test/object/test_interval.py | 278 ++++++++------ .../Mathematics/Object/Interval.hpp | 126 +++++++ .../Mathematics/Object/Interval.tpp | 342 +++++++++++++++++- .../Mathematics/Object/Interval.test.cpp | 239 ++++++++++++ 5 files changed, 887 insertions(+), 118 deletions(-) diff --git a/bindings/python/src/OpenSpaceToolkitMathematicsPy/Object/Interval.cpp b/bindings/python/src/OpenSpaceToolkitMathematicsPy/Object/Interval.cpp index e8ac157d..1585f2ee 100644 --- a/bindings/python/src/OpenSpaceToolkitMathematicsPy/Object/Interval.cpp +++ b/bindings/python/src/OpenSpaceToolkitMathematicsPy/Object/Interval.cpp @@ -44,7 +44,25 @@ inline void OpenSpaceToolkitMathematicsPy_Object_Interval(pybind11::module& aMod // Define static methods .def_static("undefined", &Interval::Undefined) - .def_static("closed", &Interval::Closed, arg("lower_bound"), arg("upper_bound")); + .def_static("closed", &Interval::Closed, arg("lower_bound"), arg("upper_bound")) + .def_static("open", &Interval::Open, arg("lower_bound"), arg("upper_bound")) + .def_static("half_open_left", &Interval::HalfOpenLeft, arg("lower_bound"), arg("upper_bound")) + .def_static("half_open_right", &Interval::HalfOpenRight, arg("lower_bound"), arg("upper_bound")) + .def_static("clip", &Interval::Clip, arg("intervals"), arg("clipping_interval")) + .def_static( + "sort", &Interval::Sort, arg("intervals"), arg("by_lower_bound") = true, arg("ascending") = true + ) + .def_static("merge", &Interval::Merge, arg("intervals")) + .def_static( + "get_gaps", + &Interval::GetGaps, + arg("intervals"), + arg_v("bound", Interval::Undefined(), "RealInterval.Undefined()") + ) + .def_static("logical_or", &Interval::LogicalOr, arg("intervals_1"), arg("intervals_2")) + .def_static("logical_and", &Interval::LogicalAnd, arg("intervals_1"), arg("intervals_2")) + + ; // Add other interval types // ... diff --git a/bindings/python/test/object/test_interval.py b/bindings/python/test/object/test_interval.py index 261120c0..74941d40 100644 --- a/bindings/python/test/object/test_interval.py +++ b/bindings/python/test/object/test_interval.py @@ -2,18 +2,15 @@ import pytest -import ostk.mathematics as mathematics +from ostk.core.type import Real +from ostk.core.type import String -from ostk.core.type import Real, String - - -RealInterval = mathematics.object.RealInterval -Type = RealInterval.Type +from ostk.mathematics.object import RealInterval class TestInterval: def test_type(self): - enum_members = Type.__members__ + enum_members = RealInterval.Type.__members__ assert list(enum_members.keys()) == [ "Undefined", @@ -23,19 +20,19 @@ def test_type(self): "HalfOpenRight", ] assert list(enum_members.values()) == [ - Type.Undefined, - Type.Closed, - Type.Open, - Type.HalfOpenLeft, - Type.HalfOpenRight, + RealInterval.Type.Undefined, + RealInterval.Type.Closed, + RealInterval.Type.Open, + RealInterval.Type.HalfOpenLeft, + RealInterval.Type.HalfOpenRight, ] def test_default_constructor(self): # Input types for RealInterval - interval_1 = RealInterval(-4.31, 1.0, Type.Open) - interval_2 = RealInterval(-2.0, -1.0, Type.Closed) - interval_3 = RealInterval(3.5, 4567.35566, Type.HalfOpenRight) - interval_4 = RealInterval(1.45, 1.45, Type.Closed) + interval_1 = RealInterval(-4.31, 1.0, RealInterval.Type.Open) + interval_2 = RealInterval(-2.0, -1.0, RealInterval.Type.Closed) + interval_3 = RealInterval(3.5, 4567.35566, RealInterval.Type.HalfOpenRight) + interval_4 = RealInterval(1.45, 1.45, RealInterval.Type.Closed) assert isinstance(interval_1, RealInterval) assert isinstance(interval_2, RealInterval) @@ -47,12 +44,14 @@ def test_default_constructor(self): assert interval_4 is not None with pytest.raises(TypeError): - interval = RealInterval(3.0, 1, Type.Closed) + interval = RealInterval(3.0, 1, RealInterval.Type.Closed) - interval_5 = RealInterval(Real(-4.31), Real(1.0), Type.Open) - interval_6 = RealInterval(Real(-2.0), Real(-1.0), Type.Closed) - interval_7 = RealInterval(Real(3.5), Real(4567.35566), Type.HalfOpenRight) - interval_8 = RealInterval(Real(1.45), Real(1.45), Type.Closed) + interval_5 = RealInterval(Real(-4.31), Real(1.0), RealInterval.Type.Open) + interval_6 = RealInterval(Real(-2.0), Real(-1.0), RealInterval.Type.Closed) + interval_7 = RealInterval( + Real(3.5), Real(4567.35566), RealInterval.Type.HalfOpenRight + ) + interval_8 = RealInterval(Real(1.45), Real(1.45), RealInterval.Type.Closed) assert isinstance(interval_5, RealInterval) assert isinstance(interval_6, RealInterval) @@ -68,11 +67,11 @@ def test_default_constructor(self): b = 3.0 # Types of RealInterval - interval_undefined = RealInterval(a, b, Type.Undefined) - interval_closed = RealInterval(a, b, Type.Closed) - interval_open = RealInterval(a, b, Type.Open) - interval_halfopenleft = RealInterval(a, b, Type.HalfOpenLeft) - interval_halfopenright = RealInterval(a, b, Type.HalfOpenRight) + interval_undefined = RealInterval(a, b, RealInterval.Type.Undefined) + interval_closed = RealInterval(a, b, RealInterval.Type.Closed) + interval_open = RealInterval(a, b, RealInterval.Type.Open) + interval_halfopenleft = RealInterval(a, b, RealInterval.Type.HalfOpenLeft) + interval_halfopenright = RealInterval(a, b, RealInterval.Type.HalfOpenRight) assert isinstance(interval_undefined, RealInterval) assert isinstance(interval_closed, RealInterval) @@ -83,16 +82,16 @@ def test_default_constructor(self): # Unvalid interval definition with pytest.raises(RuntimeError): - invalid_interval_1 = RealInterval(4.8, 3.5, Type.Open) + invalid_interval_1 = RealInterval(4.8, 3.5, RealInterval.Type.Open) with pytest.raises(RuntimeError): - invalid_interval_2 = RealInterval(4.8, 3.5, Type.Closed) + invalid_interval_2 = RealInterval(4.8, 3.5, RealInterval.Type.Closed) with pytest.raises(RuntimeError): - invalid_interval_3 = RealInterval(4.8, 3.5, Type.HalfOpenLeft) + invalid_interval_3 = RealInterval(4.8, 3.5, RealInterval.Type.HalfOpenLeft) with pytest.raises(RuntimeError): - invalid_interval_4 = RealInterval(4.8, 3.5, Type.HalfOpenRight) + invalid_interval_4 = RealInterval(4.8, 3.5, RealInterval.Type.HalfOpenRight) def test_undefined_constructor(self): undefined_interval = RealInterval.undefined() @@ -101,11 +100,20 @@ def test_undefined_constructor(self): assert undefined_interval is not None assert undefined_interval.is_defined() is False - def test_closed_constructor(self): + @pytest.mark.parametrize( + "static_func", + [ + (RealInterval.closed), + (RealInterval.open), + (RealInterval.half_open_left), + (RealInterval.half_open_right), + ], + ) + def test_static_constructors(self, static_func): a = -3.1 b = 45.6 - closed_interval = RealInterval.closed(a, b) + closed_interval = static_func(a, b) assert isinstance(closed_interval, RealInterval) assert closed_interval is not None @@ -119,11 +127,11 @@ def test_is_defined(self): b = 3.0 # Types of RealInterval - interval_undefined = RealInterval(a, b, Type.Undefined) - interval_closed = RealInterval(a, b, Type.Closed) - interval_open = RealInterval(a, b, Type.Open) - interval_halfopenleft = RealInterval(a, b, Type.HalfOpenLeft) - interval_halfopenright = RealInterval(a, b, Type.HalfOpenRight) + interval_undefined = RealInterval(a, b, RealInterval.Type.Undefined) + interval_closed = RealInterval(a, b, RealInterval.Type.Closed) + interval_open = RealInterval(a, b, RealInterval.Type.Open) + interval_halfopenleft = RealInterval(a, b, RealInterval.Type.HalfOpenLeft) + interval_halfopenright = RealInterval(a, b, RealInterval.Type.HalfOpenRight) assert interval_undefined.is_defined() is False assert interval_closed.is_defined() is True @@ -140,21 +148,21 @@ def test_intersects(self): b = 3.0 # Types of RealInterval - interval_undefined = RealInterval(a, b, Type.Undefined) - interval_closed = RealInterval(a, b, Type.Closed) - interval_open = RealInterval(a, b, Type.Open) - interval_halfopenleft = RealInterval(a, b, Type.HalfOpenLeft) - interval_halfopenright = RealInterval(a, b, Type.HalfOpenRight) + interval_undefined = RealInterval(a, b, RealInterval.Type.Undefined) + interval_closed = RealInterval(a, b, RealInterval.Type.Closed) + interval_open = RealInterval(a, b, RealInterval.Type.Open) + interval_halfopenleft = RealInterval(a, b, RealInterval.Type.HalfOpenLeft) + interval_halfopenright = RealInterval(a, b, RealInterval.Type.HalfOpenRight) # Define Test Intervals - interval_left = RealInterval(-5.0, -4.5, Type.Closed) - interval_intersects_left = RealInterval(-5.0, -4.26, Type.Closed) - interval_right = RealInterval(4.56, 4.67, Type.Closed) - interval_intersects_right = RealInterval(2.78, 46.09, Type.Closed) - interval_between = RealInterval(-3.4, 2.45, Type.Closed) - interval_bigger = RealInterval(-45.0, 34.12, Type.Closed) - - # Add test cases with contained intervals not Type.Closed... + interval_left = RealInterval(-5.0, -4.5, RealInterval.Type.Closed) + interval_intersects_left = RealInterval(-5.0, -4.26, RealInterval.Type.Closed) + interval_right = RealInterval(4.56, 4.67, RealInterval.Type.Closed) + interval_intersects_right = RealInterval(2.78, 46.09, RealInterval.Type.Closed) + interval_between = RealInterval(-3.4, 2.45, RealInterval.Type.Closed) + interval_bigger = RealInterval(-45.0, 34.12, RealInterval.Type.Closed) + + # Add test cases with contained intervals not RealInterval.Type.Closed... # Add test cases with open intervals and half open intervals... # Test intersects on undefined @@ -205,11 +213,11 @@ def test_contains_real(self): b = 3.0 # Types of RealInterval - interval_undefined = RealInterval(a, b, Type.Undefined) - interval_closed = RealInterval(a, b, Type.Closed) - interval_open = RealInterval(a, b, Type.Open) - interval_halfopenleft = RealInterval(a, b, Type.HalfOpenLeft) - interval_halfopenright = RealInterval(a, b, Type.HalfOpenRight) + interval_undefined = RealInterval(a, b, RealInterval.Type.Undefined) + interval_closed = RealInterval(a, b, RealInterval.Type.Closed) + interval_open = RealInterval(a, b, RealInterval.Type.Open) + interval_halfopenleft = RealInterval(a, b, RealInterval.Type.HalfOpenLeft) + interval_halfopenright = RealInterval(a, b, RealInterval.Type.HalfOpenRight) # Define Reals real_left = -5.43 @@ -266,21 +274,21 @@ def test_contains_interval(self): b = 3.0 # Types of RealInterval - interval_undefined = RealInterval(a, b, Type.Undefined) - interval_closed = RealInterval(a, b, Type.Closed) - interval_open = RealInterval(a, b, Type.Open) - interval_halfopenleft = RealInterval(a, b, Type.HalfOpenLeft) - interval_halfopenright = RealInterval(a, b, Type.HalfOpenRight) + interval_undefined = RealInterval(a, b, RealInterval.Type.Undefined) + interval_closed = RealInterval(a, b, RealInterval.Type.Closed) + interval_open = RealInterval(a, b, RealInterval.Type.Open) + interval_halfopenleft = RealInterval(a, b, RealInterval.Type.HalfOpenLeft) + interval_halfopenright = RealInterval(a, b, RealInterval.Type.HalfOpenRight) # Define Test Intervals - interval_left = RealInterval(-5.0, -4.5, Type.Closed) - interval_intersects_left = RealInterval(-5.0, -4.56, Type.Closed) - interval_right = RealInterval(4.56, 4.67, Type.Closed) - interval_intersects_right = RealInterval(2.78, 46.09, Type.Closed) - interval_between = RealInterval(-3.4, 2.45, Type.Closed) - interval_bigger = RealInterval(-45.0, 34.12, Type.Closed) + interval_left = RealInterval(-5.0, -4.5, RealInterval.Type.Closed) + interval_intersects_left = RealInterval(-5.0, -4.56, RealInterval.Type.Closed) + interval_right = RealInterval(4.56, 4.67, RealInterval.Type.Closed) + interval_intersects_right = RealInterval(2.78, 46.09, RealInterval.Type.Closed) + interval_between = RealInterval(-3.4, 2.45, RealInterval.Type.Closed) + interval_bigger = RealInterval(-45.0, 34.12, RealInterval.Type.Closed) - # Add test cases with contained intervals not Type.Closed... + # Add test cases with contained intervals not RealInterval.Type.Closed... # Test contains on undefined @@ -329,11 +337,11 @@ def test_get_bounds(self): b = 3.0 # Types of RealInterval - interval_undefined = RealInterval(a, b, Type.Undefined) - interval_closed = RealInterval(a, b, Type.Closed) - interval_open = RealInterval(a, b, Type.Open) - interval_halfopenleft = RealInterval(a, b, Type.HalfOpenLeft) - interval_halfopenright = RealInterval(a, b, Type.HalfOpenRight) + interval_undefined = RealInterval(a, b, RealInterval.Type.Undefined) + interval_closed = RealInterval(a, b, RealInterval.Type.Closed) + interval_open = RealInterval(a, b, RealInterval.Type.Open) + interval_halfopenleft = RealInterval(a, b, RealInterval.Type.HalfOpenLeft) + interval_halfopenright = RealInterval(a, b, RealInterval.Type.HalfOpenRight) # get_lower_bound with pytest.raises(RuntimeError): @@ -359,11 +367,11 @@ def test_to_string(self): b = 3.0 # Types of RealInterval - interval_undefined = RealInterval(a, b, Type.Undefined) - interval_closed = RealInterval(a, b, Type.Closed) - interval_open = RealInterval(a, b, Type.Open) - interval_halfopenleft = RealInterval(a, b, Type.HalfOpenLeft) - interval_halfopenright = RealInterval(a, b, Type.HalfOpenRight) + interval_undefined = RealInterval(a, b, RealInterval.Type.Undefined) + interval_closed = RealInterval(a, b, RealInterval.Type.Closed) + interval_open = RealInterval(a, b, RealInterval.Type.Open) + interval_halfopenleft = RealInterval(a, b, RealInterval.Type.HalfOpenLeft) + interval_halfopenright = RealInterval(a, b, RealInterval.Type.HalfOpenRight) with pytest.raises(RuntimeError): interval_undefined.to_string() @@ -377,24 +385,24 @@ def test_to_string(self): def test_get_intersection_with(self): test_cases = [ ( - RealInterval(0.0, 10.0, Type.HalfOpenLeft), - RealInterval(5.0, 7.0, Type.HalfOpenLeft), - RealInterval(5.0, 7.0, Type.HalfOpenLeft), + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenLeft), + RealInterval(5.0, 7.0, RealInterval.Type.HalfOpenLeft), + RealInterval(5.0, 7.0, RealInterval.Type.HalfOpenLeft), ), ( - RealInterval(0.0, 10.0, Type.HalfOpenRight), - RealInterval(-15.0, 25.0, Type.HalfOpenRight), - RealInterval(0.0, 10.0, Type.HalfOpenRight), + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenRight), + RealInterval(-15.0, 25.0, RealInterval.Type.HalfOpenRight), + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenRight), ), ( - RealInterval(0.0, 10.0, Type.Open), - RealInterval(-5.0, 7.0, Type.Open), - RealInterval(0.0, 7.0, Type.Open), + RealInterval(0.0, 10.0, RealInterval.Type.Open), + RealInterval(-5.0, 7.0, RealInterval.Type.Open), + RealInterval(0.0, 7.0, RealInterval.Type.Open), ), ( - RealInterval(0.0, 10.0, Type.Closed), - RealInterval(5.0, 15.0, Type.Closed), - RealInterval(5.0, 10.0, Type.Closed), + RealInterval(0.0, 10.0, RealInterval.Type.Closed), + RealInterval(5.0, 15.0, RealInterval.Type.Closed), + RealInterval(5.0, 10.0, RealInterval.Type.Closed), ), ] @@ -410,24 +418,24 @@ def test_get_intersection_with(self): def test_get_union_with(self): test_cases = [ ( - RealInterval(0.0, 10.0, Type.HalfOpenLeft), - RealInterval(5.0, 7.0, Type.HalfOpenLeft), - RealInterval(0.0, 10.0, Type.HalfOpenLeft), + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenLeft), + RealInterval(5.0, 7.0, RealInterval.Type.HalfOpenLeft), + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenLeft), ), ( - RealInterval(0.0, 10.0, Type.HalfOpenRight), - RealInterval(-15.0, 25.0, Type.HalfOpenRight), - RealInterval(-15.0, 25.0, Type.HalfOpenRight), + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenRight), + RealInterval(-15.0, 25.0, RealInterval.Type.HalfOpenRight), + RealInterval(-15.0, 25.0, RealInterval.Type.HalfOpenRight), ), ( - RealInterval(0.0, 10.0, Type.Open), - RealInterval(-5.0, 7.0, Type.Open), - RealInterval(-5.0, 10.0, Type.Open), + RealInterval(0.0, 10.0, RealInterval.Type.Open), + RealInterval(-5.0, 7.0, RealInterval.Type.Open), + RealInterval(-5.0, 10.0, RealInterval.Type.Open), ), ( - RealInterval(0.0, 10.0, Type.Closed), - RealInterval(5.0, 15.0, Type.Closed), - RealInterval(0.0, 15.0, Type.Closed), + RealInterval(0.0, 10.0, RealInterval.Type.Closed), + RealInterval(5.0, 15.0, RealInterval.Type.Closed), + RealInterval(0.0, 15.0, RealInterval.Type.Closed), ), ] @@ -437,3 +445,71 @@ def test_get_union_with(self): expected_interval = test_case[2] assert first_interval.get_union_with(second_interval) == expected_interval + + def test_clip(self): + intervals: list[RealInterval] = [ + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenLeft), + RealInterval(6.5, 8.0, RealInterval.Type.HalfOpenLeft), + ] + clipping_interval: RealInterval = RealInterval.closed(6.0, 7.0) + + assert RealInterval.clip(intervals, clipping_interval) is not None + + def test_sort(self): + intervals: list[RealInterval] = [ + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenLeft), + RealInterval(10.0, 20.0, RealInterval.Type.HalfOpenLeft), + ] + assert RealInterval.sort(intervals) is not None + assert RealInterval.sort(intervals, False) is not None + assert RealInterval.sort(intervals, False, False) is not None + + def test_merge(self): + intervals: list[RealInterval] = [ + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenLeft), + RealInterval(5.0, 7.0, RealInterval.Type.HalfOpenLeft), + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenLeft), + ] + + assert RealInterval.merge(intervals) is not None + + def test_get_gaps(self): + intervals: list[RealInterval] = [ + RealInterval(0.0, 10.0, RealInterval.Type.HalfOpenLeft), + RealInterval(5.0, 7.0, RealInterval.Type.HalfOpenLeft), + RealInterval(5.0, 15.0, RealInterval.Type.HalfOpenLeft), + ] + + assert RealInterval.get_gaps(intervals) is not None + + assert ( + RealInterval.get_gaps(intervals, RealInterval.closed(-5.0, 25.0)) is not None + ) + + def test_logical_or(self): + intervals_1: list[RealInterval] = [ + RealInterval(8.0, 9.0, RealInterval.Type.Closed), + RealInterval(0.0, 1.0, RealInterval.Type.HalfOpenLeft), + RealInterval(2.0, 3.0, RealInterval.Type.Open), + ] + + intervals_2: list[RealInterval] = [ + RealInterval(0.5, 3.5, RealInterval.Type.Open), + RealInterval(5.0, 7.0, RealInterval.Type.HalfOpenLeft), + ] + + assert RealInterval.logical_or(intervals_1, intervals_2) is not None + + def test_logical_and(self): + intervals_1: list[RealInterval] = [ + RealInterval(8.0, 9.0, RealInterval.Type.Closed), + RealInterval(0.0, 1.0, RealInterval.Type.HalfOpenLeft), + RealInterval(2.0, 3.0, RealInterval.Type.Open), + ] + + intervals_2: list[RealInterval] = [ + RealInterval(0.5, 3.5, RealInterval.Type.Open), + RealInterval(5.0, 7.0, RealInterval.Type.HalfOpenLeft), + ] + + assert RealInterval.logical_and(intervals_1, intervals_2) is not None diff --git a/include/OpenSpaceToolkit/Mathematics/Object/Interval.hpp b/include/OpenSpaceToolkit/Mathematics/Object/Interval.hpp index a63c24b8..2b4d1380 100644 --- a/include/OpenSpaceToolkit/Mathematics/Object/Interval.hpp +++ b/include/OpenSpaceToolkit/Mathematics/Object/Interval.hpp @@ -331,6 +331,129 @@ class Interval : public IntervalBase static Interval HalfOpenRight(const T& aLowerBound, const T& anUpperBound); + /// @brief Creates a new array of Intervals clipped by the provided Interval + /// + /// @code + /// Array> unclipped = { + /// Interval::Closed(-1.0, 1.0), + /// Interval::Closed(2.0, 4.0), + /// }; + /// const Interval clipper = Interval::Closed(0.0, 3.0); + /// Array> clipped = Interval::Clip(unclipped, clipper); + /// // {[0.0, 1.0], [2.0, 3.0]} + /// @endcode + /// + /// @return Clipped array + + static ctnr::Array> Clip( + const ctnr::Array>& anIntervalArray, const Interval& anInterval + ); + + /// @brief Creates a new sorted array, the order based on the inputted parameters + /// + /// @code + /// Array> unsorted = { + /// Interval::Closed(-1.0, 1.0), + /// Interval::Closed(-3.0, 0.0), + /// }; + /// Array> sorted = Interval::Sort(unsorted); // {[-3.0, 0.0], [-1.0, 1.0]} + /// @endcode + /// + /// @return Sorted array + + static ctnr::Array> Sort( + const ctnr::Array>& anIntervalArray, const bool& byLowerBound = true, const bool& ascending = true + ); + + /// @brief Creates a new array without any overlaps and sorted by their lower bound + /// + /// @code + /// Array> array = { + /// Interval::Closed(-1.0, 1.0), + /// Interval::Closed(-2.0, 0.0), + /// Interval::Closed(3.0, 4.0), + /// }; + /// Array> merged = Interval::Merge(array); // {[-2.0, 1.0], [3.0, 4.0]} + /// @endcode + /// + /// @return Merged array + + static ctnr::Array> Merge(const ctnr::Array>& anIntervalArray); + + /// @brief Creates a new array that contains the gaps between an array of intervals. If + /// an interval is actually defined, only intervals intersecting with the interval are + /// considered and potential leading and trailing gaps w.r.t. the interval boundaries are + /// included. The intervals are previously merged and sorted. + /// @code + /// Array> intervals = { + /// Interval::Closed(-1.0, 1.0), + /// Interval::Closed(2.0, 4.0), + /// }; + /// const Interval bounds = Interval::Closed(1.5, 4.5) + /// Array> gaps = Interval::GetGaps(intervals, bounds); + /// // {[1.5, 2.0], [4.0, 4.5]} + /// @endcode + /// + /// @return Gaps array + + static ctnr::Array> GetGaps( + const ctnr::Array>& anIntervalArray, const Interval& anInterval = Interval::Undefined() + ); + + /// @brief Creates a new array that contains the intervals representing a logical 'OR' conjunction + /// between the two arrays. The resulting array will contain the union of intervals of the + /// provided arrays, i.e. intervals that overlap with intervals from one of the arrays OR the + /// other. + /// @code + /// Array> anIntervalArray = { + /// Interval::Closed(-1.0, 1.0), + /// Interval::Closed(2.0, 4.0), + /// }; + /// + /// Array> anotherIntervalArray = { + /// Interval::Open(0.5, 1.5), + /// Interval::Open(3.0, 5.0), + /// Interval::Open(7.0, 8.0), + /// }; + /// + /// Array> logicalOr = Interval::LogicalOr(anIntervalArray, + /// anotherIntervalArray); + /// // {[-1.0, 1.5), [2.0, 5.0), (7.0, 8.0)} + /// @endcode + /// + /// @return Logical 'OR' array + + static ctnr::Array> LogicalOr( + const ctnr::Array>& anIntervalArray, const ctnr::Array>& anotherIntervalArray + ); + + /// @brief Creates a new array that contains the intervals representing a logical 'AND' conjunction + /// between the two arrays. The rsulting array will contain the intersection of intervals of the + /// provided arrays, i.e. intervals that overal with intervals from one of the arrays AND the + /// other. + /// @code + /// Array> anIntervalArray = { + /// Interval::Closed(-1.0, 1.0), + /// Interval::Closed(2.0, 4.0), + /// }; + /// + /// Array> anotherIntervalArray = { + /// Interval::Open(0.5, 1.5), + /// Interval::Open(3.0, 5.0), + /// Interval::Open(7.0, 8.0), + /// }; + /// + /// Array> logicalAnd = Interval::LogicalAnd(anIntervalArray, + /// anotherIntervalArray); + /// // {(0.5, 1.0), [2.0, 4.0]} + /// @endcode + /// + /// @return Logical 'AND' array + + static ctnr::Array> LogicalAnd( + const ctnr::Array>& anIntervalArray, const ctnr::Array>& anotherIntervalArray + ); + /// @brief Constructs an interval from a given string /// /// @code @@ -356,6 +479,9 @@ class Interval : public IntervalBase private: bool checkAgainstLowerBound(const T& aValue, const bool& isOpen, const bool& isUpperBound) const; bool checkAgainstUpperBound(const T& aValue, const bool& isOpen, const bool& isLowerBound) const; + static Interval buildInterval( + const T& lowerBound, const bool& openLowerBound, const T& upperBound, const bool& openUpperBound + ); Interval::Type type_; diff --git a/src/OpenSpaceToolkit/Mathematics/Object/Interval.tpp b/src/OpenSpaceToolkit/Mathematics/Object/Interval.tpp index 65e70fa0..43ada741 100644 --- a/src/OpenSpaceToolkit/Mathematics/Object/Interval.tpp +++ b/src/OpenSpaceToolkit/Mathematics/Object/Interval.tpp @@ -1,6 +1,7 @@ /// Apache License 2.0 #include +#include #include #include @@ -12,6 +13,8 @@ namespace mathematics namespace object { +using ostk::core::type::Size; + template Interval::Interval(const T& aLowerBound, const T& anUpperBound, const Interval::Type& anIntervalType) : type_(anIntervalType), @@ -360,22 +363,7 @@ Interval Interval::getUnionWith(const Interval& anInterval) const } } - if (openLowerBound && openUpperBound) - { - return Interval::Open(lowerBound, upperBound); - } - - if (openLowerBound) - { - return Interval::HalfOpenLeft(lowerBound, upperBound); - } - - if (openUpperBound) - { - return Interval::HalfOpenRight(lowerBound, upperBound); - } - - return Interval::Closed(lowerBound, upperBound); + return Interval::buildInterval(lowerBound, openLowerBound, upperBound, openUpperBound); } return Interval::Undefined(); @@ -595,6 +583,305 @@ Interval Interval::HalfOpenRight(const T& aLowerBound, const T& anUpperBou return Interval(aLowerBound, anUpperBound, Interval::Type::HalfOpenRight); } +template +ctnr::Array> Interval::Clip( + const ctnr::Array>& anIntervalArray, const Interval& anInterval +) +{ + if (!anInterval.isDefined()) + { + throw ostk::core::error::runtime::Undefined("Interval"); + } + + ctnr::Array> clipped; + + for (Size i = 0; i < anIntervalArray.getSize(); ++i) + { + const auto checkIntersection = anIntervalArray[i].getIntersectionWith(anInterval); + if (checkIntersection.isDefined()) + { + clipped.add(checkIntersection); + } + } + + return clipped; +} + +template +ctnr::Array> Interval::Sort( + const ctnr::Array>& anIntervalArray, const bool& byLowerBound, const bool& ascending +) +{ + ctnr::Array> sorted = anIntervalArray; + + if (anIntervalArray.size() == 1 && !anIntervalArray[0].isDefined()) + { + throw ostk::core::error::runtime::Undefined("Interval"); + } + + const auto comparator = [byLowerBound, ascending](const Interval& anInterval, const Interval& anotherInterval) + { + if (!anInterval.isDefined() || !anotherInterval.isDefined()) + { + throw ostk::core::error::runtime::Undefined("Interval"); + } + + if (byLowerBound) + { + return ascending + ? anInterval.lowerBound_ anotherInterval + .lowerBound_; + } + + return ascending + ? anInterval.upperBound_ anotherInterval.upperBound_; + }; + + std::sort(sorted.begin(), sorted.end(), comparator); + + return sorted; +} + +template +ctnr::Array> Interval::Merge(const ctnr::Array>& anIntervalArray) +{ + if (anIntervalArray.empty()) + { + return {}; + } + + ctnr::Array> sorted = Interval::Sort(anIntervalArray); + + ctnr::Array> merged; + Interval nextUnion = sorted[0]; + + for (Size i = 1; i < sorted.size(); ++i) + { + auto checkUnion = nextUnion.getUnionWith(sorted[i]); + if (checkUnion.isDefined()) + { + nextUnion = checkUnion; + } + else + { + merged.add(nextUnion); + nextUnion = sorted[i]; + } + } + + merged.add(nextUnion); + + return merged; +} + +template +ctnr::Array> Interval::GetGaps( + const ctnr::Array>& anIntervalArray, const Interval& anInterval +) +{ + ctnr::Array> sanitized = Interval::Merge(anIntervalArray); + if (anInterval.isDefined()) + { + sanitized = Interval::Clip(sanitized, anInterval); + } + + if (sanitized.size() == 0) + { + if (anInterval.isDefined()) + { + return {anInterval}; + } + return {}; + } + + ctnr::Array> gaps; + + // Deal with potential leading gap + if (anInterval.isDefined() && sanitized[0].lowerBound_ > anInterval.lowerBound_) + { + const Interval upperInterval = sanitized[0]; + const T lowerBound = anInterval.lowerBound_; + const T upperBound = upperInterval.lowerBound_; + + bool openLowerBound = true; + if (anInterval.type_ == Interval::Type::Closed || anInterval.type_ == Interval::Type::HalfOpenRight) + { + openLowerBound = false; + } + + bool openUpperBound = false; + if (upperInterval.type_ == Interval::Type::Closed || upperInterval.type_ == Interval::Type::HalfOpenRight) + { + openUpperBound = true; + } + + gaps.add(Interval::buildInterval(lowerBound, openLowerBound, upperBound, openUpperBound)); + } + + // Deal with intermediate gaps + for (Size i = 1; i < sanitized.size(); ++i) + { + const Interval lowerInterval = sanitized[i - 1]; + const Interval upperInterval = sanitized[i]; + const T lowerBound = lowerInterval.upperBound_; + const T upperBound = upperInterval.lowerBound_; + + bool openLowerBound = false; + if (lowerInterval.type_ == Interval::Type::Closed || lowerInterval.type_ == Interval::Type::HalfOpenLeft) + { + openLowerBound = true; + } + + bool openUpperBound = false; + if (upperInterval.type_ == Interval::Type::Closed || upperInterval.type_ == Interval::Type::HalfOpenRight) + { + openUpperBound = true; + } + + gaps.add(Interval::buildInterval(lowerBound, openLowerBound, upperBound, openUpperBound)); + } + + // Deal with potential trailing gap + if (anInterval.isDefined() && sanitized[sanitized.size() - 1].upperBound_ < anInterval.upperBound_) + { + const Interval lowerInterval = sanitized[sanitized.size() - 1]; + const T lowerBound = lowerInterval.upperBound_; + const T upperBound = anInterval.upperBound_; + + bool openLowerBound = false; + if (lowerInterval.type_ == Interval::Type::Closed || lowerInterval.type_ == Interval::Type::HalfOpenLeft) + { + openLowerBound = true; + } + + bool openUpperBound = true; + if (anInterval.type_ == Interval::Type::Closed || anInterval.type_ == Interval::Type::HalfOpenLeft) + { + openUpperBound = false; + } + + gaps.add(Interval::buildInterval(lowerBound, openLowerBound, upperBound, openUpperBound)); + } + + return gaps; +} + +template +ctnr::Array> Interval::LogicalOr( + const ctnr::Array>& anIntervalArray, const ctnr::Array>& anotherIntervalArray +) +{ + ctnr::Array> array; + + for (Size i = 0; i < anIntervalArray.size(); ++i) + { + array.add(anIntervalArray[i]); + } + + for (Size i = 0; i < anotherIntervalArray.size(); ++i) + { + array.add(anotherIntervalArray[i]); + } + + return Interval::Merge(array); +} + +template +ctnr::Array> Interval::LogicalAnd( + const ctnr::Array>& anIntervalArray, const ctnr::Array>& anotherIntervalArray +) +{ + const ctnr::Array> aSanitizedArray = Interval::Merge(anIntervalArray); + const ctnr::Array> anotherSanitizedArray = Interval::Merge(anotherIntervalArray); + + if (aSanitizedArray.size() == 0 or anotherSanitizedArray.size() == 0) + { + return {}; + } + + // Determine Span lower bound + bool openLowerBound = true; + T lowerBound = aSanitizedArray[0].lowerBound_; + + if (aSanitizedArray[0].lowerBound_ < anotherSanitizedArray[0].lowerBound_) + { + if (aSanitizedArray[0].type_ == Interval::Type::Closed || + aSanitizedArray[0].type_ == Interval::Type::HalfOpenRight) + { + openLowerBound = false; + } + } + + else if (aSanitizedArray[0].lowerBound_ > anotherSanitizedArray[0].lowerBound_) + { + lowerBound = anotherSanitizedArray[0].lowerBound_; + + if (anotherSanitizedArray[0].type_ == Interval::Type::Closed || + anotherSanitizedArray[0].type_ == Interval::Type::HalfOpenRight) + { + openLowerBound = false; + } + } + + else + { + if ((aSanitizedArray[0].type_ == Interval::Type::Closed || + aSanitizedArray[0].type_ == Interval::Type::HalfOpenRight) && + (anotherSanitizedArray[0].type_ == Interval::Type::Closed || + anotherSanitizedArray[0].type_ == Interval::Type::HalfOpenRight)) + { + openLowerBound = false; + } + } + + // Determine Span upper bound + bool openUpperBound = true; + T upperBound = aSanitizedArray[aSanitizedArray.size() - 1].upperBound_; + ; + + if (aSanitizedArray[aSanitizedArray.size() - 1].upperBound_ > + anotherSanitizedArray[anotherSanitizedArray.size() - 1].upperBound_) + { + if (aSanitizedArray[aSanitizedArray.size() - 1].type_ == Interval::Type::Closed || + aSanitizedArray[aSanitizedArray.size() - 1].type_ == Interval::Type::HalfOpenLeft) + { + openUpperBound = false; + } + } + + else if (aSanitizedArray[aSanitizedArray.size() - 1].upperBound_ < + anotherSanitizedArray[anotherSanitizedArray.size() - 1].upperBound_) + { + upperBound = anotherSanitizedArray[anotherSanitizedArray.size() - 1].upperBound_; + + if (anotherSanitizedArray[anotherSanitizedArray.size() - 1].type_ == Interval::Type::Closed || + anotherSanitizedArray[anotherSanitizedArray.size() - 1].type_ == Interval::Type::HalfOpenLeft) + { + openUpperBound = false; + } + } + + else + { + if ((aSanitizedArray[aSanitizedArray.size() - 1].type_ == Interval::Type::Closed || + aSanitizedArray[aSanitizedArray.size() - 1].type_ == Interval::Type::HalfOpenLeft) && + (anotherSanitizedArray[anotherSanitizedArray.size() - 1].type_ == Interval::Type::Closed || + anotherSanitizedArray[anotherSanitizedArray.size() - 1].type_ == Interval::Type::HalfOpenLeft)) + { + openUpperBound = false; + } + } + + const Interval span = Interval::buildInterval(lowerBound, openLowerBound, upperBound, openUpperBound); + + return Interval::GetGaps( + Interval::LogicalOr( + Interval::GetGaps(aSanitizedArray, span), Interval::GetGaps(anotherSanitizedArray, span) + ), + span + ); +} + // template // Interval Interval::Parse ( const types::String& aString ) // { @@ -685,6 +972,29 @@ bool Interval::checkAgainstUpperBound(const T& aValue, const bool& isOpen, co } } +template +Interval Interval::buildInterval( + const T& lowerBound, const bool& openLowerBound, const T& upperBound, const bool& openUpperBound +) +{ + if (openLowerBound && openUpperBound) + { + return Interval::Open(lowerBound, upperBound); + } + + if (openLowerBound) + { + return Interval::HalfOpenLeft(lowerBound, upperBound); + } + + if (openUpperBound) + { + return Interval::HalfOpenRight(lowerBound, upperBound); + } + + return Interval::Closed(lowerBound, upperBound); +} + } // namespace object } // namespace mathematics } // namespace ostk diff --git a/test/OpenSpaceToolkit/Mathematics/Object/Interval.test.cpp b/test/OpenSpaceToolkit/Mathematics/Object/Interval.test.cpp index 88644f96..b3d894fe 100644 --- a/test/OpenSpaceToolkit/Mathematics/Object/Interval.test.cpp +++ b/test/OpenSpaceToolkit/Mathematics/Object/Interval.test.cpp @@ -10,6 +10,7 @@ using ostk::core::type::Real; using ostk::mathematics::object::Interval; +namespace ctnr = ostk::core::container; TEST(OpenSpaceToolkit_Mathematics_Object_Interval, Constructor) { @@ -1999,6 +2000,244 @@ TEST(OpenSpaceToolkit_Mathematics_Object_Interval, HalfOpenRight) } } +TEST(OpenSpaceToolkit_Mathematics_Object_Interval, Clip) +{ + { + EXPECT_ANY_THROW(Interval::Clip({Interval::Undefined()}, Interval::Closed(0.0, 1.0))); + EXPECT_ANY_THROW(Interval::Clip( + {Interval::Closed(0.0, 1.0), Interval::Closed(0.0, 1.0)}, Interval::Undefined() + )); + EXPECT_ANY_THROW(Interval::Clip( + {Interval::Closed(0.0, 1.0), Interval::Undefined()}, Interval::Closed(0.0, 1.0) + )); + + EXPECT_EQ(ctnr::Array>(), Interval::Clip({}, Interval::Closed(0.0, 1.0))); + } + + { + ctnr::Array> intervals = { + Interval::Open(1.0, 2.0), Interval::Closed(1.5, 5.0), Interval::Closed(3.0, 4.0) + }; + ctnr::Array> clippedArray = { + Interval::HalfOpenRight(2, 5.0), Interval::Closed(3.0, 4.0) + }; + + EXPECT_EQ(clippedArray, Interval::Clip(intervals, Interval::HalfOpenRight(2.0, 5.0))); + } +} + +TEST(OpenSpaceToolkit_Mathematics_Object_Interval, Sort) +{ + { + EXPECT_ANY_THROW(Interval::Sort({Interval::Undefined()})); + EXPECT_ANY_THROW(Interval::Sort({Interval::Closed(0.0, 1.0), Interval::Undefined()})); + + EXPECT_EQ(ctnr::Array>(), Interval::Sort({})); + } + + // defaults + { + ctnr::Array> intervals = {Interval::Closed(0.0, 1.0), Interval::Closed(2.0, 3.0)}; + ctnr::Array> sortedArray = {Interval::Closed(0.0, 1.0), Interval::Closed(2.0, 3.0)}; + + EXPECT_EQ(sortedArray, Interval::Sort(intervals)); + } + + { + ctnr::Array> intervals = {Interval::Closed(2.0, 3.0), Interval::Open(0.0, 1.0)}; + ctnr::Array> sortedArray = {Interval::Open(0.0, 1.0), Interval::Closed(2.0, 3.0)}; + + EXPECT_EQ(sortedArray, Interval::Sort(intervals)); + } + + // by upper bound + { + ctnr::Array> intervals = {Interval::Closed(0.0, 7.0), Interval::Open(2.0, 3.0)}; + ctnr::Array> sortedArray = {Interval::Open(2.0, 3.0), Interval::Closed(0.0, 7.0)}; + + EXPECT_EQ(sortedArray, Interval::Sort(intervals, false)); + } + + // descending + { + ctnr::Array> intervals = {Interval::Closed(2.0, 3.0), Interval::Open(0.0, 1.0)}; + ctnr::Array> sortedArray = {Interval::Closed(2.0, 3.0), Interval::Open(0.0, 1.0)}; + + EXPECT_EQ(sortedArray, Interval::Sort(intervals, true, false)); + } +} + +TEST(OpenSpaceToolkit_Mathematics_Object_Interval, Merge) +{ + { + EXPECT_ANY_THROW(Interval::Merge({Interval::Undefined()})); + EXPECT_ANY_THROW(Interval::Merge({Interval::Closed(0.0, 1.0), Interval::Undefined()})); + + EXPECT_EQ(ctnr::Array>(), Interval::Merge({})); + } + + { + ctnr::Array> unmergedArray = { + Interval::Closed(0.0, 3.0), + Interval::Open(0.0, 2.0), + Interval::HalfOpenLeft(4.0, 5.0), + Interval::Closed(0.5, 1.0), + Interval::HalfOpenRight(3.0, 3.5) + }; + ctnr::Array> mergedArray = { + Interval::HalfOpenRight(0.0, 3.5), Interval::HalfOpenLeft(4.0, 5.0) + }; + + EXPECT_EQ(mergedArray, Interval::Merge(unmergedArray)); + } +} + +TEST(OpenSpaceToolkit_Mathematics_Object_Interval, Gaps) +{ + { + EXPECT_ANY_THROW(Interval::GetGaps({Interval::Undefined()}, Interval::Closed(0.0, 1.0))); + + EXPECT_EQ(ctnr::Array>(), Interval::GetGaps({})); + + ctnr::Array> gapsArray = {Interval::Closed(0.0, 1.0)}; + EXPECT_EQ(gapsArray, Interval::GetGaps({}, {Interval::Closed(0.0, 1.0)})); + } + + { + ctnr::Array> intervals = { + Interval::Closed(2.0, 3.0), + Interval::Open(0.0, 1.0), + Interval::HalfOpenLeft(6.0, 7.0), + Interval::HalfOpenRight(4.0, 5.0), + Interval::Open(8.0, 9.0) + }; + ctnr::Array> gapsArray = { + Interval::HalfOpenRight(1.0, 2.0), + Interval::Open(3.0, 4.0), + Interval::Closed(5.0, 6.0), + Interval::HalfOpenLeft(7.0, 8.0) + }; + + EXPECT_EQ(gapsArray, Interval::GetGaps(intervals)); + } + + { + ctnr::Array> intervals = { + Interval::Closed(2.0, 3.0), + Interval::Open(0.0, 1.0), + Interval::HalfOpenLeft(6.0, 7.0), + Interval::HalfOpenRight(4.0, 5.0), + Interval::Open(8.0, 9.0) + }; + ctnr::Array> gapsArray = { + Interval::HalfOpenLeft(-1.0, 0.0), + Interval::HalfOpenRight(1.0, 2.0), + Interval::Open(3.0, 4.0), + Interval::Closed(5.0, 6.0), + Interval::HalfOpenLeft(7.0, 8.0), + Interval::Closed(9.0, 10.0), + }; + + EXPECT_EQ(gapsArray, Interval::GetGaps(intervals, Interval::HalfOpenLeft(-1.0, 10.0))); + } +} + +TEST(OpenSpaceToolkit_Mathematics_Object_Interval, LogicalOr) +{ + { + EXPECT_ANY_THROW(Interval::LogicalOr({Interval::Undefined()}, {})); + EXPECT_ANY_THROW(Interval::LogicalOr({}, {Interval::Undefined()})); + + EXPECT_EQ(ctnr::Array>(), Interval::LogicalOr({}, {})); + + ctnr::Array> logicalOrArray = {Interval::Closed(0.0, 1.0)}; + EXPECT_EQ(logicalOrArray, Interval::LogicalOr({}, logicalOrArray)); + EXPECT_EQ(logicalOrArray, Interval::LogicalOr(logicalOrArray, {})); + } + + { + ctnr::Array> anArray = { + Interval::Closed(2.0, 3.0), + Interval::Open(0.0, 3.0), + Interval::HalfOpenLeft(6.0, 7.0), + Interval::Open(8.0, 9.0), + Interval::HalfOpenLeft(4.0, 5.0) + }; + ctnr::Array> anotherArray = { + Interval::HalfOpenLeft(10.0, 11.0), + Interval::HalfOpenRight(-1.0, 2.0), + Interval::Open(5.0, 7.5) + }; + ctnr::Array> logicalOrArray = { + Interval::Closed(-1.0, 3.0), + Interval::Open(4.0, 7.5), + Interval::Open(8.0, 9.0), + Interval::HalfOpenLeft(10.0, 11.0) + }; + + EXPECT_EQ(logicalOrArray, Interval::LogicalOr(anArray, anotherArray)); + } +} + +TEST(OpenSpaceToolkit_Mathematics_Object_Interval, LogicalAnd) +{ + { + EXPECT_ANY_THROW(Interval::LogicalAnd({Interval::Undefined()}, {})); + EXPECT_ANY_THROW(Interval::LogicalAnd({}, {Interval::Undefined()})); + + EXPECT_EQ(ctnr::Array>(), Interval::LogicalAnd({}, {})); + EXPECT_EQ(ctnr::Array>(), Interval::LogicalAnd({Interval::Closed(0.0, 1.0)}, {})); + EXPECT_EQ(ctnr::Array>(), Interval::LogicalAnd({}, {Interval::Closed(0.0, 1.0)})); + } + + { + ctnr::Array> open = {Interval::Open(0.0, 1.0)}; + ctnr::Array> halfOpenLeft = {Interval::HalfOpenLeft(0.0, 1.0)}; + ctnr::Array> halfOpenRight = {Interval::HalfOpenRight(0.0, 1.0)}; + ctnr::Array> closed = {Interval::Closed(0.0, 1.0)}; + + EXPECT_EQ(open, Interval::LogicalAnd(open, open)); + EXPECT_EQ(open, Interval::LogicalAnd(open, halfOpenLeft)); + EXPECT_EQ(open, Interval::LogicalAnd(open, halfOpenRight)); + EXPECT_EQ(open, Interval::LogicalAnd(open, closed)); + + EXPECT_EQ(open, Interval::LogicalAnd(halfOpenLeft, open)); + EXPECT_EQ(halfOpenLeft, Interval::LogicalAnd(halfOpenLeft, halfOpenLeft)); + EXPECT_EQ(open, Interval::LogicalAnd(halfOpenLeft, halfOpenRight)); + EXPECT_EQ(halfOpenLeft, Interval::LogicalAnd(halfOpenLeft, closed)); + + EXPECT_EQ(open, Interval::LogicalAnd(halfOpenRight, open)); + EXPECT_EQ(open, Interval::LogicalAnd(halfOpenRight, halfOpenLeft)); + EXPECT_EQ(halfOpenRight, Interval::LogicalAnd(halfOpenRight, halfOpenRight)); + EXPECT_EQ(halfOpenRight, Interval::LogicalAnd(halfOpenRight, closed)); + + EXPECT_EQ(open, Interval::LogicalAnd(closed, open)); + EXPECT_EQ(halfOpenLeft, Interval::LogicalAnd(closed, halfOpenLeft)); + EXPECT_EQ(halfOpenRight, Interval::LogicalAnd(closed, halfOpenRight)); + EXPECT_EQ(closed, Interval::LogicalAnd(closed, closed)); + } + + { + ctnr::Array> anArray = { + Interval::Closed(2.0, 3.0), + Interval::Open(0.0, 3.0), + Interval::HalfOpenLeft(6.0, 7.0), + Interval::Open(8.0, 9.0), + Interval::HalfOpenLeft(4.0, 5.0) + }; + ctnr::Array> anotherArray = { + Interval::HalfOpenLeft(10.0, 11.0), + Interval::HalfOpenRight(-1.0, 2.0), + Interval::Open(5.0, 7.5) + }; + ctnr::Array> logicalAndArray = { + Interval::Open(0.0, 2.0), Interval::HalfOpenLeft(6.0, 7.0) + }; + + EXPECT_EQ(logicalAndArray, Interval::LogicalAnd(anArray, anotherArray)); + } +} + // TEST (OpenSpaceToolkit_Mathematics_Object_Interval, Parse) // {