This library provides several abstract domains for interval reasoning over integer and real numbers. We aim to be general enough to be reused across fields:
- Constraint programming: Enforce bound(Z) and bound(R) consistencies on interval domains (see e.g. Choi et al. 2006).
- Abstract interpretation: Provide interval abstractions of integer and real numbers, including forward and backward interval arithmetics (see e.g. Cousot, 2021).
- Conflict-free replicated data type: Provide simple anonymous state-based CRDTs with
LBandUB. - Asynchronous fixpoint iterations: Provide lock-free join and meet operations, compatible with execution on GPU hardware (see Talbot et al., 2022).
We propose abstractions of the universe of discourse of integers
-
Lower bound abstract universe:
LBapproximates a set$S \subseteq \mathbb{A}$ by taking its lower bound$\mathit{min}~S$ if it is a bounded set, and$-\infty$ otherwise. -
Upper bound abstract universe:
UBapproximates a set$S \subseteq \mathbb{A}$ by taking its upper bound$\mathit{max}~S$ if it is a bounded set, and$\infty$ otherwise. -
Boolean abstract universes:
LB<bool>is a two-elements lattice with the top element tofalseand bottom totrue. Dually, we haveUB<bool>where top istrueand bottom isfalse. -
Integer interval abstract universe:
ZIntervalapproximates the lower and upper bounds of a set$S \subseteq \mathbb{Z}$ . -
Floating-point interval abstract universe:
FIntervalapproximates the lower and upper bounds of a set$S \subseteq \mathbb{R}$ .
We represent infinities I we rely on std::numeric_limits<I>::min() and std::numeric_limits<I>::max(), and for floating-point type F we rely on -std::numeric_limits<F>::infinity() and std::numeric_limits<F>::infinity().
We have a unified interface for standard lattice operations.
Let L be one of the abstract universes above.
| Operation | Mathematical notation | Programming notation |
|---|---|---|
| Bottom | L::bot() |
|
| Top |
L::top() or default constructor L()
|
|
| Partial order | a.leq(b) |
|
| Strict partial order | a.lt(b) |
|
| Meet |
meet(a,b) or a.meet(b) (in-place) |
|
| Join |
join(a,b) or a.join(b) (in-place) |
The following operations are mostly there for optimization purposes, but could be easily recovered from the previous lattice operations.
| Operation | Mathematical notation | Programming notation |
|---|---|---|
| Equality | a == b |
|
| Disequality | a != b |
|
| Meet bottom |
a.meet_bot() (in-place) |
|
| Join top |
a.join_top() (in-place) |
|
| Bottom test | a.is_bot() |
|
| Top test | a.is_top() |
In this library, the bottom element of the interval abstract universe denotes the equivalence class of all empty intervals is_bot is true on all such intervals.
The lattice operations are defined in the following table.
| Operation | Definition | Programming notation |
|---|---|---|
a.is_bot() |
||
a.eq(b) |
||
a.leq(b) |
||
a.lt(b) |
||
meet(a,b) or a.meet(b) (in-place) |
||
|
|
join(a,b) or a.join(b) (in-place) |
With the join defined as:
The lattice operations must take care of the cases where the arguments are in the bottom equivalence class.
If the user is sure none of the arguments is bottom, we provide more efficient versions named *_nobot (e.g. join_nobot) which simplifies the definition above by ignoring the bottom cases.
Also known as abstract transformers in the field of abstract interpretation. In constraint programming, those operations can be used to design propagators, see below.
Note: To project the interval of an expression, e.g. y + z, initialize the result variable to top (e.g. ZInterval<int> x;) and call the forward operator on it: x.add(y,z).
In the following, we let x,y,z be integer intervals of type ZInterval.
| Operation | Constraint | Forward operator | Left backward operator | Right backward operator |
|---|---|---|---|---|
| Identity | x.meet(y) |
y.meet(x) |
n/a | |
| Negation | x.neg(y) |
y.neg(x) |
n/a | |
| Addition | x.add(y,z) |
y.sub(x,z) |
z.sub(x,y) |
|
| Subtraction | x.sub(y,z) |
y.add(x,z) |
z.sub(y,x) |
|
| Multiplication | x.mul(y, z) |
y.mul_back(x,z) |
z.mul_back(x,y) |
|
| Floor division |
x.fdiv(y, z)1
|
y.fdiv_num(x, z)1
|
z.fdiv_den(x,y) |
|
| Ceiling division |
x.cdiv(y, z)1
|
y.cdiv_num(x, z)1
|
z.cdiv_den(x,y) |
|
| Truncated division |
x.tdiv(y, z)1
|
y.tdiv_num(x, z)1
|
z.tdiv_den(x,y) |
|
| Euclidean division |
x.ediv(y, z)1
|
y.ediv_num(x, z)1
|
z.ediv_den(x,y) |
|
| Maximum | x.max(y, z) |
y.max_back(x,z) |
z.max_back(x,y) |
|
| Minimum | x.min(y, z) |
y.min_back(x,z) |
z.min_back(x,y) |
|
| Reified Equality |
x.req(y, z)2
|
y.req_back(x,z)2
|
z.req_back(x,y)2
|
|
| Reified Inequality |
x.rleq(y, z)2
|
y.rleq_lback(x,z)2
|
z.rleq_rback(x,y)2
|
Let
- Semantics of floor division
- Semantics of ceiling division
- Semantics of truncated division
- Semantics of Euclidean division
In the following, we let x,y,z be floating-point intervals of type FInterval.
| Operation | Constraint | Forward operator | Left backward operator | Right backward operator |
|---|---|---|---|---|
| Identity | x.meet(y) |
y.meet(x) |
n/a | |
| Negation | x.neg(y) |
y.neg(x) |
n/a | |
| Addition | x.add(y,z) |
y.sub(x,z) |
z.sub(x,y) |
|
| Subtraction | x.sub(y,z) |
y.add(x,z) |
z.sub(y,x) |
|
| Multiplication | x.mul(y, z) |
y.mul_back(x,z) |
z.mul_back(x,y) |
|
| Division | x.div(y, z) |
y.mul(x, z) |
z.div_rback(y,x) |
|
| Maximum | x.max(y, z) |
y.max_back(x,z) |
z.max_back(x,y) |
|
| Minimum | x.min(y, z) |
y.min_back(x,z) |
z.min_back(x,y) |
|
| Reified Equality | x.req(y, z) |
y.req_back(x,z) |
z.req_back(x,y) |
|
| Reified Inequality | x.rleq(y, z) |
y.rleq_lback(x,z) |
z.rleq_rback(x,y) |
The addition, subtraction, multiplication and division forward operators are based on Hickey et al., "Interval Arithmetic: From Principles to Implementation", JACM, 2001.
We have implemented various interval propagators for ternary arithmetic constraints of the form
Let x,y,z be integer intervals of type ZInterval.
| Constraint | Propagator | Best? |
|---|---|---|
tell::zadd(x, y, z) |
Yes | |
tell::zsub(x, y, z) |
Yes | |
tell::zmul(x, y, z) |
No | |
tell::zfdiv(x, y, z) |
Yes3 | |
tell::zcdiv(x, y, z) |
Yes3 | |
tell::ztdiv(x, y, z) |
Yes3 | |
tell::zediv(x, y, z) |
Yes3 | |
tell::zmax(x, y, z) |
Yes | |
tell::zmin(x, y, z) |
Yes | |
tell::zreq(x, y, z) |
Yes | |
tell::zrleq(x, y, z) |
Yes |
Propagators enforcing Bound(R) consistency: They are available under the namespace boundr::tell with the same names as above. They are less precise than the propagators above and are mainly available for research purposes and for testing propagators on FInterval.
Let x,y,z be integer intervals of type ZInterval.
| Constraint | Entailment test |
|---|---|
ask::zadd(x, y, z) |
|
ask::zsub(x, y, z) |
|
ask::zmul(x, y, z) |
|
ask::zfdiv(x, y, z) |
|
ask::zcdiv(x, y, z) |
|
ask::ztdiv(x, y, z) |
|
ask::zediv(x, y, z) |
|
ask::zmax(x, y, z) |
|
ask::zmin(x, y, z) |
|
ask::zreq(x, y, z) |
|
ask::zrleq(x, y, z) |
Let x,y,z be floating-point intervals of type FInterval.
| Constraint | Propagator | Best? |
|---|---|---|
tell::fadd(x, y, z) |
Yes | |
tell::fsub(x, y, z) |
Yes | |
tell::fmul(x, y, z) |
No | |
tell::fdiv(x, y, z) |
Yes | |
tell::fmax(x, y, z) |
Yes | |
tell::fmin(x, y, z) |
Yes | |
tell::freq(x, y, z) |
Yes | |
tell::frleq(x, y, z) |
Yes |
Let x,y,z be floating-point intervals of type FInterval.
| Constraint | Entailment test |
|---|---|
ask::fadd(x, y, z) |
|
ask::fsub(x, y, z) |
|
ask::fmul(x, y, z) |
|
ask::fdiv(x, y, z) |
|
ask::zmax(x, y, z) |
|
ask::zmin(x, y, z) |
|
ask::zreq(x, y, z) |
|
ask::zrleq(x, y, z) |
Footnotes
-
Precondition: the lower and upper bounds of the denominator
zmust be different from zero. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Precondition: the domain of
xmust be included in[0,1]. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 -
This is currently a conjecture from experimental results. Faster but less precise propagators, postfixed by
_fast(e.g.tell::fdiv_fast) are also available: they do not perform asplitjoinoperation onz. ↩ ↩2 ↩3 ↩4