Skip to content

Commit 15d8f4b

Browse files
authored
Merge pull request #381 from SimonRohou/codac2_dev
SepPolarCart + comparison Interval operators
2 parents 974ee29 + 82fd8a3 commit 15d8f4b

13 files changed

Lines changed: 261 additions & 54 deletions

File tree

doc/api/Doxyfile.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2502,7 +2502,7 @@ HIDE_UNDOC_RELATIONS = YES
25022502
# set to NO
25032503
# The default value is: NO.
25042504

2505-
HAVE_DOT = YES
2505+
HAVE_DOT = NO
25062506

25072507
# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
25082508
# to run in parallel. When set to 0 doxygen will base this on the number of

doc/manual/manual/intervals/Interval_class.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,20 @@ All standard arithmetic operations are supported in Codac, both element-wise and
243243
z = x / y % [1, 3]
244244

245245

246+
Interval comparisons
247+
--------------------
248+
249+
The comparison operators ``<`` and ``>`` are also overloaded for intervals.
250+
They return a :ref:`BoolInterval value <sec-intervals-boolinterval-class>` rather than a classical Boolean, in order to encode the set of feasible truth values when the exact operands are only known within interval bounds.
251+
252+
For instance, ``x < y`` returns:
253+
254+
- ``BoolInterval.EMPTY`` if one of the two intervals is empty;
255+
- ``BoolInterval.TRUE`` if :math:`\forall a\in[x],\forall b\in[y],\ a<b`;
256+
- ``BoolInterval.FALSE`` if :math:`\forall a\in[x],\forall b\in[y],\ a\geqslant b`;
257+
- ``BoolInterval.UNKNOWN`` otherwise.
258+
259+
246260
Unary and binary functions
247261
--------------------------
248262

python/src/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@
110110
separators/codac2_py_SepInter.cpp
111111
separators/codac2_py_SepInverse.cpp
112112
separators/codac2_py_SepNot.cpp
113+
separators/codac2_py_SepPolarCart.cpp
113114
separators/codac2_py_SepPolygon.cpp
114115
separators/codac2_py_SepProj.cpp
115116
separators/codac2_py_SepQInter.cpp

python/src/core/codac2_py_core.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ void export_SepCtcPair(py::module& m, py::class_<SepBase,pySep>& pysep);
148148
void export_SepInter(py::module& m, py::class_<SepBase,pySep>& sep);
149149
void export_SepInverse(py::module& m, py::class_<SepBase,pySep>& sep);
150150
void export_SepNot(py::module& m, py::class_<SepBase,pySep>& sep);
151+
void export_SepPolarCart(py::module& m, py::class_<SepBase,pySep>& pysep);
151152
void export_SepPolygon(py::module& m, py::class_<SepBase,pySep>& sep);
152153
void export_SepProj(py::module& m, py::class_<SepBase,pySep>& sep);
153154
void export_SepQInter(py::module& m, py::class_<SepBase,pySep>& sep);
@@ -331,6 +332,7 @@ PYBIND11_MODULE(_core, m)
331332
export_SepInter(m,py_sep);
332333
export_SepInverse(m,py_sep);
333334
export_SepNot(m,py_sep);
335+
export_SepPolarCart(m,py_sep);
334336
export_SepPolygon(m,py_sep);
335337
export_SepProj(m,py_sep);
336338
export_SepQInter(m,py_sep);

python/src/core/domains/interval/codac2_py_Interval.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ py::class_<Interval> export_Interval(py::module& m)
7676
BOOL_INTERVAL_OPERATORNEQ_CONST_INTERVAL_REF_CONST,
7777
"x"_a)
7878

79+
.def(py::self < py::self,
80+
BOOLINTERVAL_INTERVAL_OPERATOR__CONST_INTERVAL_REF_CONST,
81+
"x"_a)
82+
83+
.def(py::self > py::self,
84+
BOOLINTERVAL_INTERVAL_OPERATOR_CONST_INTERVAL_REF_CONST,
85+
"x"_a)
86+
7987
.def("lb", &Interval::lb,
8088
DOUBLE_INTERVAL_LB_CONST)
8189

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Codac binding (core)
3+
* ----------------------------------------------------------------------------
4+
* \date 2026
5+
* \author Simon Rohou
6+
* \copyright Copyright 2026 Codac Team
7+
* \license GNU Lesser General Public License (LGPL)
8+
*/
9+
10+
#include <pybind11/pybind11.h>
11+
#include <pybind11/operators.h>
12+
#include <pybind11/stl.h>
13+
#include <codac2_template_tools.h>
14+
#include <codac2_SepPolarCart.h>
15+
#include "codac2_py_Sep.h"
16+
#include "codac2_py_SepPolarCart_docs.h" // Generated file from Doxygen XML (doxygen2docstring.py):
17+
18+
using namespace std;
19+
using namespace codac2;
20+
namespace py = pybind11;
21+
using namespace pybind11::literals;
22+
23+
void export_SepPolarCart(py::module& m, py::class_<SepBase,pySep>& pysep)
24+
{
25+
py::class_<SepPolarCart> exported(m, "SepPolarCart", pysep, SEPPOLARCART_MAIN);
26+
exported
27+
28+
.def(py::init([](const SepBase& s1) {
29+
return std::make_unique<SepPolarCart>(s1.copy());
30+
}),
31+
SEPPOLARCART_SEPPOLARCART_CONST_S_REF,
32+
"s1"_a)
33+
34+
.def("separate", &SepPolarCart::separate,
35+
BOXPAIR_SEPPOLARCART_SEPARATE_CONST_INTERVALVECTOR_REF_CONST,
36+
"x"_a)
37+
38+
;
39+
}

src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@
241241
${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepInter.h
242242
${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepInverse.h
243243
${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepNot.h
244+
${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepPolarCart.cpp
245+
${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepPolarCart.h
244246
${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepPolygon.cpp
245247
${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepPolygon.h
246248
${CMAKE_CURRENT_SOURCE_DIR}/separators/codac2_SepProj.cpp

src/core/domains/interval/codac2_Interval.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "codac2_Domain.h"
2222
#include "codac2_assert.h"
2323
#include "codac2_TypeInfo.h"
24+
#include "codac2_BoolInterval.h"
2425

2526
namespace codac2
2627
{
@@ -160,6 +161,40 @@ namespace codac2
160161
*/
161162
bool operator!=(const Interval& x) const;
162163

164+
/**
165+
* \brief Comparison (strict less-than) between this and x
166+
*
167+
* The returned ``BoolInterval`` encloses the truth value of
168+
* \f$t<s\f$ for \f$t\in[\mathrm{this}]\f$ and \f$s\in[x]\f$.
169+
*
170+
* \note Returns:
171+
* - ``BoolInterval::EMPTY`` if this or x is empty
172+
* - ``BoolInterval::TRUE`` iff \f$\mathrm{ub}([\mathrm{this}])<\mathrm{lb}([x])\f$
173+
* - ``BoolInterval::FALSE`` iff \f$\mathrm{ub}([x])\leq\mathrm{lb}([\mathrm{this}])\f$
174+
* - ``BoolInterval::UNKNOWN`` otherwise
175+
*
176+
* \param x interval to be compared with
177+
* \return interval Boolean result
178+
*/
179+
BoolInterval operator<(const Interval& x) const;
180+
181+
/**
182+
* \brief Comparison (strict greater-than) between this and x
183+
*
184+
* The returned BoolInterval encloses the truth value of
185+
* \f$t>s\f$ for \f$t\in[\mathrm{this}]\f$ and \f$s\in[x]\f$.
186+
*
187+
* \note Returns:
188+
* - ``BoolInterval::EMPTY`` if this or x is empty
189+
* - ``BoolInterval::TRUE`` iff \f$\mathrm{lb}([\mathrm{this}])>\mathrm{ub}([x])\f$
190+
* - ``BoolInterval::FALSE`` iff \f$\mathrm{lb}([x])\geq\mathrm{ub}([\mathrm{this}])\f$
191+
* - ``BoolInterval::UNKNOWN`` otherwise
192+
*
193+
* \param x interval to be compared with
194+
* \return interval Boolean result
195+
*/
196+
BoolInterval operator>(const Interval& x) const;
197+
163198
/**
164199
* \brief Returns the lower bound of this
165200
*

src/core/domains/interval/codac2_Interval_impl.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,22 @@ namespace codac2
107107
return !(*this == x);
108108
}
109109

110+
inline BoolInterval Interval::operator<(const Interval& x) const
111+
{
112+
if(is_empty() || x.is_empty())
113+
return BoolInterval::EMPTY;
114+
if(this->ub() < x.lb())
115+
return BoolInterval::TRUE;
116+
if(x.ub() <= this->lb())
117+
return BoolInterval::FALSE;
118+
return BoolInterval::UNKNOWN;
119+
}
120+
121+
inline BoolInterval Interval::operator>(const Interval& x) const
122+
{
123+
return x < *this;
124+
}
125+
110126
inline double Interval::lb() const
111127
{
112128
return gaol::interval::left();
Lines changed: 91 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,115 @@
11
/**
2-
* codac2_SepPolarCart.cpp
2+
* \file codac2_SepPolarCart.cpp
33
* ----------------------------------------------------------------------------
44
* \date 2026
5-
* \author Benoît Desrochers, (Simon Rohou)
5+
* \author Simon Rohou, from a former implementation of Benoît Desrochers
66
* \copyright Copyright 2026 Codac Team
77
* \license GNU Lesser General Public License (LGPL)
88
*/
99

1010
#include "codac2_SepPolarCart.h"
1111
#include "codac2_CtcPolar.h"
12+
#include "codac2_cart_prod.h"
13+
#include "codac2_hull.h"
1214

1315
using namespace codac2;
1416

15-
BoxPair SepPolarCart::separate(const IntervalVector& x) const
17+
/**
18+
* Contracts x with the Cartesian projection of the complement of a polar box.
19+
*
20+
* For a polar box [rho]x[theta], the complement is represented as the union
21+
* of four polar boxes:
22+
* - rho <= rho.lb(), with theta in [theta]
23+
* - rho >= rho.ub(), with theta in [theta]
24+
* - theta before theta.lb(), with rho in R+
25+
* - theta after theta.ub(), with rho in R+
26+
*
27+
* The angular complement is computed in the 2*pi-wide window centered on [theta].
28+
*/
29+
IntervalVector contract_polar_box_complement(
30+
const CtcPolar& ctc_polar,
31+
const IntervalVector& x,
32+
const IntervalVector& x_pol)
1633
{
1734
assert_release(x.size() == 2);
35+
assert_release(x_pol.size() == 2);
1836

19-
CtcPolar ctc_polar;
20-
IntervalVector x_cart(2), x_pol(x);
37+
if(x.is_empty())
38+
return x;
2139

22-
ctc_polar.contract(x_cart[0], x_cart[1], x_pol[0], x_pol[1]);
23-
auto x_cart_sep = _sep.front()->separate(x_cart);
24-
BoxPair x_polar_sep { x, x };
25-
ctc_polar.contract(x_cart_sep.inner[0], x_cart_sep.inner[1], x_polar_sep.inner[0], x_polar_sep.inner[1]);
26-
ctc_polar.contract(x_cart_sep.outer[0], x_cart_sep.outer[1], x_polar_sep.outer[0], x_polar_sep.outer[1]);
40+
// Complement of the empty polar box: no point is removed by the inner
41+
// contractor, so the Cartesian box is left unchanged.
42+
if(x_pol.is_empty())
43+
return x;
2744

28-
return x_polar_sep;
29-
}
45+
const Interval& rho = x_pol[0];
46+
const Interval& theta = x_pol[1];
3047

48+
std::list<IntervalVector> parts;
3149

32-
SepPolarXY::SepPolarXY(Interval rho, Interval theta) : rho(rho), theta(theta), Sep(2) {
33-
rho_m = Interval(0, rho.lb());
34-
rho_p = Interval(rho.ub(), POS_INFINITY);
35-
double limit = theta.mid() - M_PI;
36-
theta_m = Interval(limit, theta.lb());
37-
theta_p = Interval(theta.ub(), limit + 2*M_PI);
38-
cmpl = Interval(0, 2*M_PI);
39-
}
50+
auto add_polar_part = [&ctc_polar,&parts](const IntervalVector& x,
51+
const Interval& rho,
52+
const Interval& theta)
53+
{
54+
if(rho.is_empty() || theta.is_empty())
55+
return;
4056

57+
Interval rho_(rho), theta_(theta);
58+
IntervalVector x_(x);
59+
ctc_polar.contract(x_[0],x_[1],rho_,theta_);
60+
parts.push_back(x_);
61+
};
4162

42-
void SepPolarXY::contractOut(IntervalVector &x_out){
43-
Interval th = this->theta;
44-
Interval r = this->rho;
45-
this->ctc.contract(x_out[0], x_out[1], r, th);
46-
if(x_out[0].is_empty() || x_out[1].is_empty())
47-
x_out.set_empty();
48-
}
63+
// Radial complement, restricted to the current angular sector. The angular
64+
// outside parts below cover the remaining angles.
65+
if(std::isfinite(rho.lb()) && rho.lb() >= 0)
66+
add_polar_part(x, {0,rho.lb()}, theta);
67+
68+
if(std::isfinite(rho.ub()))
69+
add_polar_part(x, {rho.ub(),oo}, theta);
4970

71+
// Angular complement. If [theta] already covers at least one full turn,
72+
// there is no angular complement in the 2*pi-periodic sense.
73+
if(std::isfinite(theta.lb()) && std::isfinite(theta.ub())
74+
&& theta.ub() - theta.lb() < 2*PI)
75+
{
76+
double limit = theta.mid()-PI;
77+
add_polar_part(x, {0,oo}, {limit,theta.lb()});
78+
add_polar_part(x, {0,oo}, {theta.ub(),limit+2*PI});
79+
}
5080

51-
void SepPolarXY::contractIn(IntervalVector &x_in){
52-
Interval x1(x_in[0]); Interval y1(x_in[1]);
53-
Interval x2(x_in[0]); Interval y2(x_in[1]);
54-
Interval x3(x_in[0]); Interval y3(x_in[1]);
55-
Interval x4(x_in[0]); Interval y4(x_in[1]);
56-
57-
Interval ALLREALS1 = Interval::POS_REALS;
58-
Interval ALLREALS2 = Interval::POS_REALS;
59-
Interval cmpl1(cmpl);
60-
Interval cmpl2(cmpl);
61-
Interval theta_m_tmp(theta_m);
62-
Interval theta_p_tmp(theta_p);
63-
Interval rho_m_tmp(rho_m);
64-
Interval rho_p_tmp(rho_p);
65-
66-
this->ctc.contract(x1, y1, ALLREALS1, theta_m_tmp);
67-
this->ctc.contract(x2, y2, ALLREALS2, theta_p_tmp);
68-
this->ctc.contract(x3, y3, rho_m_tmp, cmpl1);
69-
this->ctc.contract(x4, y4, rho_p_tmp, cmpl2);
70-
x_in[0] &= (x1 | x2 | x3 | x4);
71-
x_in[1] &= (y1 | y2 | y3 | y4);
72-
if(x_in[0].is_empty() || x_in[1].is_empty())
73-
x_in.set_empty();
81+
return x & hull(parts);
7482
}
83+
84+
BoxPair SepPolarCart::separate(const IntervalVector& x) const
85+
{
86+
assert_release(x.size() == 2);
87+
88+
BoxPair x_cart_sep { x, x };
89+
90+
if(x.is_empty())
91+
return x_cart_sep;
92+
93+
CtcPolar ctc_polar;
94+
IntervalVector x_cart(x), x_pol(2);
95+
96+
// Cartesian input -> polar enclosure
97+
ctc_polar.contract(x_cart[0], x_cart[1], x_pol[0], x_pol[1]);
98+
99+
if(x_cart.is_empty() || x_pol.is_empty())
100+
return x_cart_sep;
101+
102+
// Separation in the polar space
103+
const BoxPair x_pol_sep = _sep.front()->separate(x_pol);
104+
105+
// Outer contraction: keep the Cartesian points whose polar coordinates may
106+
// belong to the polar separator output
107+
IntervalVector copy_x_pol_sep = x_pol_sep.outer.subvector(0,1);
108+
ctc_polar.contract(x_cart_sep.outer[0], x_cart_sep.outer[1], copy_x_pol_sep[0], copy_x_pol_sep[1]);
109+
110+
// Inner contraction: remove points belonging to the polar set by contracting
111+
// with the complement of the polar outer box
112+
x_cart_sep.inner = contract_polar_box_complement(ctc_polar, x_cart_sep.inner, x_pol_sep.outer);
113+
114+
return x_cart_sep;
115+
}

0 commit comments

Comments
 (0)