Home > other >  Intersecting dfferent interval types with Boost ICL
Intersecting dfferent interval types with Boost ICL

Time:03-02

I would like to check whether a boost::icl::right_open_interval<double> i1 and a boost::icl::closed_interval<double> i2 have an intersection. Unfortunately, the boost::icl::intersects(i1, i2) function only works when i1 and i2 share the same type. I was wondering

  1. why different interval types are not supported, as it seems natural for an interval library, and
  2. if there is a nice way to generalize the above function to different interval types.

CodePudding user response:

There type model the Static Interval concept:

There is also the Dynamic Interval concept, which basically adds runtime state indicating the interval_bounds:

using icl::interval_bounds;
using std::is_same_v;

using I  = icl::interval<double>;
using CI = I::interval_type;

auto a = I::right_open(3, M_PI);
auto b = I::closed(3.14, 5);
auto c = I::left_open(7, std::numeric_limits<double>::infinity());

for (auto lhs : {a, b, c})
    for (auto rhs : {a, b, c})
        std::cout << lhs << " & " << rhs << " = " << (lhs & rhs) << "" << std::endl;

Prints

[3,3.14159) & [3,3.14159) = [3,3.14159)
[3,3.14159) & [3.14,5] = [3.14,3.14159)
[3,3.14159) & (7,inf] = ()
[3.14,5] & [3,3.14159) = [3.14,3.14159)
[3.14,5] & [3.14,5] = [3.14,5]
[3.14,5] & (7,inf] = (]
(7,inf] & [3,3.14159) = ()
(7,inf] & [3.14,5] = (]
(7,inf] & (7,inf] = (7,inf]

The effective types are all the same:

static_assert(is_same_v<decltype(a), CI>);
static_assert(is_same_v<decltype(b), CI>);
static_assert(is_same_v<decltype(c), CI>);
static_assert(is_same_v<CI, icl::continuous_interval<double>>);

You can use this for your use case:

auto i1 = icl::right_open_interval<double>(3, M_PI);
auto i2 = icl::closed_interval<double>(3.14, 5);
auto i3 = icl::left_open_interval(7.0, std::numeric_limits<double>::infinity());

As you know they do NOT have the same type:

static_assert(not is_same_v<decltype(i1), decltype(i2)>);
static_assert(not is_same_v<decltype(i1), decltype(i3)>);
static_assert(not is_same_v<decltype(i2), decltype(i3)>);

These static interval types exist so the library can have absolutely minimum runtime overhead/storage. Imagine storing a million intervals in an array. If you know the type, do you really want to incur the size overhead:

static_assert(sizeof(i1) == sizeof(i2));
static_assert(sizeof(i2) == sizeof(i3));
static_assert(sizeof(I::interval_type) == sizeof(i1)   8);

Of course not.

However, some operations are only statically implemented for homogenous operands, as you found out. Generic code can find out the bounds type for any given interval type:

static_assert(icl::interval_bound_type<decltype(i1)>::value == interval_bounds::static_right_open);
static_assert(icl::interval_bound_type<decltype(i2)>::value == interval_bounds::static_closed);
static_assert(icl::interval_bound_type<decltype(i3)>::value == interval_bounds::static_left_open);
static_assert(icl::interval_bound_type<CI>::value == interval_bounds::dynamic);

So, you can convert to suit your needs:

template <typename I>
auto make_dynamic(I const& in) {
    static_assert(icl::is_interval<I>::value);
    static_assert(icl::has_static_bounds<I>::value);

    using T = icl::interval_traits<I>;
    using D = typename T::domain_type;
    using R = std::conditional_t<icl::is_continuous<D>::value,
                                 icl::continuous_interval<D>,
                                 icl::discrete_interval<D>>;

    constexpr auto b = icl::interval_bound_type<I>::value;
    return icl::dynamic_interval_traits<R>::construct(
        T::lower(in), T::upper(in), static_cast<icl::interval_bounds>(b));
}

And then:

for (auto lhs : {make_dynamic(i1), make_dynamic(i2), make_dynamic(i3)})
    for (auto rhs : {make_dynamic(i1), make_dynamic(i2), make_dynamic(i3)})
    {
        std::cout << lhs << " & " << rhs << " = " << (lhs & rhs) << "" << std::endl;
        converted_results.push_back(lhs & rhs);
    }

Prints the exact same as above:

[3,3.14159) & [3,3.14159) = [3,3.14159)
[3,3.14159) & [3.14,5] = [3.14,3.14159)
[3,3.14159) & (7,inf] = ()
[3.14,5] & [3,3.14159) = [3.14,3.14159)
[3.14,5] & [3.14,5] = [3.14,5]
[3.14,5] & (7,inf] = (]
(7,inf] & [3,3.14159) = ()
(7,inf] & [3.14,5] = (]
(7,inf] & (7,inf] = (7,inf]

Here's an applied example showing how create a mapping of intervals:

using Set = std::set<char>;
boost::icl::interval_map<double, Set> s;
s.add({a, Set{'a'}});
s.add({b, Set{'b'}});
s.add({c, Set{'c'}});

std::cout << "s: " << s << "\n";

Prints:

s: {([3,3.14)->{a })([3.14,3.14159)->{a b })([3.14159,5]->{b })((7,inf]->{c })}

See all the code Live On Coliru

Recommendation

This all looks a bit clumsy, right. Therefore I'd suggest picking a camp: dynamic bounds or static bounds.

If you really want static bounds, consider defining BOOST_ICL_USE_STATIC_BOUNDED_INTERVALS:

// If this macro is defined, right_open_interval with static interval borders
// will be used as default for all interval containers. 
// BOOST_ICL_USE_STATIC_BOUNDED_INTERVALS should be defined in the application
// before other includes from the ITL
//#define BOOST_ICL_USE_STATIC_BOUNDED_INTERVALS
// If BOOST_ICL_USE_STATIC_BOUNDED_INTERVALS is NOT defined, ITL uses intervals
// with dynamic borders as default.

Note this has the effect of e.g. making interval sets/maps using right-open intervals always. See a more elaborate example: https://www.boost.org/doc/libs/1_78_0/libs/icl/doc/html/boost_icl/examples/static_interval.html, and the Interface documentation.

Listing

For future reference:

#include <boost/icl/interval_map.hpp>
#include <iostream>
namespace icl = boost::icl;

template <typename I>
auto make_dynamic(I const& in) {
    static_assert(icl::is_interval<I>::value);
    static_assert(icl::has_static_bounds<I>::value);

    using T = icl::interval_traits<I>;
    using D = typename T::domain_type;
    using R = std::conditional_t<icl::is_continuous<I>::value,
                                 icl::continuous_interval<D>,
                                 icl::discrete_interval<D>>;

    constexpr auto b = icl::interval_bound_type<I>::value;
    return icl::dynamic_interval_traits<R>::construct(
        T::lower(in), T::upper(in), static_cast<icl::interval_bounds>(b));
}

int main() {
    using icl::interval_bounds;
    using std::is_same_v;

    using I  = icl::interval<double>;
    using CI = I::interval_type;

    auto a = I::right_open(3, M_PI);
    auto b = I::closed(3.14, 5);
    auto c = I::left_open(7, std::numeric_limits<double>::infinity());

    std::cout << "--------------" << std::endl;
    std::vector<CI> dynamic_results;
    for (auto lhs : {a, b, c})
        for (auto rhs : {a, b, c})
        {
            std::cout << lhs << " & " << rhs << " = " << (lhs & rhs) << "" << std::endl;
            dynamic_results.push_back(lhs & rhs);
        }

    static_assert(is_same_v<decltype(a), CI>);
    static_assert(is_same_v<decltype(b), CI>);
    static_assert(is_same_v<decltype(c), CI>);
    static_assert(is_same_v<CI, icl::continuous_interval<double>>);


    auto i1 = icl::right_open_interval<double>(3, M_PI);
    auto i2 = icl::closed_interval<double>(3.14, 5);
    auto i3 = icl::left_open_interval(7.0, std::numeric_limits<double>::infinity());
    static_assert(not is_same_v<decltype(i1), decltype(i2)>);
    static_assert(not is_same_v<decltype(i1), decltype(i3)>);
    static_assert(not is_same_v<decltype(i2), decltype(i3)>);
    static_assert(sizeof(i1) == sizeof(i2));
    static_assert(sizeof(i2) == sizeof(i3));
    static_assert(sizeof(I::interval_type) == sizeof(i1)   8);

    static_assert(icl::interval_bound_type<decltype(i1)>::value == interval_bounds::static_right_open);
    static_assert(icl::interval_bound_type<decltype(i2)>::value == interval_bounds::static_closed);
    static_assert(icl::interval_bound_type<decltype(i3)>::value == interval_bounds::static_left_open);
    static_assert(icl::interval_bound_type<CI>::value == interval_bounds::dynamic);
    
    std::cout << "--------------" << std::endl;
    std::vector<CI> converted_results;
    for (auto lhs : {make_dynamic(i1), make_dynamic(i2), make_dynamic(i3)})
        for (auto rhs : {make_dynamic(i1), make_dynamic(i2), make_dynamic(i3)})
        {
            std::cout << lhs << " & " << rhs << " = " << (lhs & rhs) << "" << std::endl;
            converted_results.push_back(lhs & rhs);
        }

    assert(dynamic_results == converted_results);

    using Set = std::set<char>;
    boost::icl::interval_map<double, Set> s;
    s.add({a, Set{'a'}});
    s.add({b, Set{'b'}});
    s.add({c, Set{'c'}});

    std::cout << "s: " << s << "\n";
}

Prints

--------------
[3,3.14159) & [3,3.14159) = [3,3.14159)
[3,3.14159) & [3.14,5] = [3.14,3.14159)
[3,3.14159) & (7,inf] = ()
[3.14,5] & [3,3.14159) = [3.14,3.14159)
[3.14,5] & [3.14,5] = [3.14,5]
[3.14,5] & (7,inf] = (]
(7,inf] & [3,3.14159) = ()
(7,inf] & [3.14,5] = (]
(7,inf] & (7,inf] = (7,inf]
--------------
[3,3.14159) & [3,3.14159) = [3,3.14159)
[3,3.14159) & [3.14,5] = [3.14,3.14159)
[3,3.14159) & (7,inf] = ()
[3.14,5] & [3,3.14159) = [3.14,3.14159)
[3.14,5] & [3.14,5] = [3.14,5]
[3.14,5] & (7,inf] = (]
(7,inf] & [3,3.14159) = ()
(7,inf] & [3.14,5] = (]
(7,inf] & (7,inf] = (7,inf]
s: {([3,3.14)->{a })([3.14,3.14159)->{a b })([3.14159,5]->{b })((7,inf]->{c })}
  • Related