Skip to content

Commit dc8111d

Browse files
authored
Document package design choices (#719)
1 parent a8ccabf commit dc8111d

File tree

7 files changed

+275
-74
lines changed

7 files changed

+275
-74
lines changed

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ makedocs(;
1919
pages = [
2020
"Home" => "index.md",
2121
"Overview" => "intro.md",
22+
"Philosophy" => "philosophy.md",
2223
"Manual" => [
2324
"Constructing intervals" => "manual/construction.md",
2425
"Usage" => "manual/usage.md",

docs/src/manual/usage.md

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -91,37 +91,40 @@ sin(Y)
9191

9292

9393

94-
## Comparisons and set operations
94+
## Comparisons
9595

96-
All comparisons and set operations for `Real` have been purposely disallowed to prevent silent errors. For instance, `x == y` does not implies `x - y == 0` for non-singleton intervals.
96+
If the result of a comparison can be established with guarantee,
97+
it will be return, otherwise, an error is thrown.
9798

9899
```@repl usage
99100
interval(1) < interval(2)
100-
precedes(interval(1), interval(2))
101+
interval(1, 5) < interval(7, 9)
102+
interval(1, 5) < interval(4.99, 9)
103+
interval(1.23) == interval(1.23)
104+
interval(1.23) == interval(4.99, 9)
105+
interval(1.23) == interval(1.2, 1.3)
106+
```
107+
108+
In particular, `if ... else ... end` statements used for floating-points will often break with intervals.
109+
110+
See [Philosophy](@ref) for more details and why this choice was made.
111+
112+
113+
## Set operations
114+
115+
Set operations are all disallowed and error on intervals to avoid ambiguities.
116+
To perform set operations on intervals, use the `*_interval` equivalent explicitly,
117+
e.g. `issubset_interval` instead of `issubset`.
118+
119+
120+
```@repl usage
101121
issubset(interval(1, 2), interval(2))
102122
issubset_interval(interval(1, 2), interval(2))
103123
intersect(interval(1, 2), interval(2))
104124
intersect_interval(interval(1, 2), interval(2))
105125
```
106126

107-
In particular, `if ... else ... end` statements used for floating-points will generally break with intervals.
108-
109-
One can refer to the following:
110-
- `<`: cannot be used with intervals. See instead [`isstrictless`](@ref) or [`strictprecedes`](@ref).
111-
- `==`: allowed if the arguments are singleton intervals, or if at least one argument is not an interval (equivalent to [`isthin`](@ref)). Otherwise, see [`isequal_interval`](@ref).
112-
- `iszero`, `isone`: allowed (equivalent to [`isthinzero`](@ref) and [`isthinone`](@ref) respectively).
113-
- `isinteger`: cannot be used with intervals. See instead [`isthininteger`](ref).
114-
- `isfinite`: cannot be used with intervals. See instead [`isbounded`](@ref).
115-
- `isnan`: cannot be used with intervals. See instead [`isnai`](@ref).
116-
- `in`: allowed if at least one argument is not an interval and the interval argument is a singleton. Otherwise, see [`in_interval`](@ref).
117-
- `issubset`: cannot be used with intervals. See instead [`issubset_interval`](@ref).
118-
- `isdisjoint`: cannot be used with intervals. See instead [`isdisjoint_interval`](@ref).
119-
- `issetequal`: cannot be used with intervals.
120-
- `isempty`: cannot be used with intervals. See instead [`isempty_interval`](@ref).
121-
- `union`: cannot be used with intervals. See instead [`hull`](@ref).
122-
- `intersect`: cannot be used with intervals. See instead [`intersect_interval`](@ref).
123-
- `setdiff`: cannot be used with intervals. See instead [`interiordiff`](@ref).
124-
127+
See [Philosophy](@ref) for more details and why this choice was made.
125128

126129

127130
## Piecewise functions

docs/src/philosophy.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Philosophy
2+
3+
The goal of the `Interval` type is to be directly used to replace floating point
4+
number in arbitrary julia code, such that in any calculation,
5+
the resulting intervals are guaranteed to bound the true image of the starting
6+
intervals.
7+
8+
So, essentially, we would like `Interval` to act as numbers and
9+
the julia ecosystem has evolved to use `Real` as the default supertype
10+
for numerical types that are not complex.
11+
Therefore, to ensure the widest compatiblity,
12+
our `Interval` type must be a subtype of `Real`.
13+
14+
Then, for any function `f(x::Real)`,
15+
we want the following to hold for all real `x` in the interval `X`
16+
(note that it holds for **all** real numbers in `X`,
17+
even those that can not be represented as floating point numbers):
18+
```math
19+
f(x) \in f(X), \qquad \forall x \in X.
20+
```
21+
22+
At first glance, this is reasonable:
23+
all arithmetic operations are well-defined for both real numbers and intervals,
24+
therefore we can use multiple dispatch to define the interval behavior of
25+
operations such has `+`, `/`, `sin` or `log`.
26+
Then a code written for `Real`s can be used as is with `Interval`s.
27+
28+
However, being a `Real` means way more than just being compatible with
29+
arithmetic operations.
30+
`Real`s are also expected to
31+
32+
1. Be compatible with any other `Number` through promotion.
33+
2. Support comparison operations, such as `==` or `<`.
34+
3. Act as a container of a single element,
35+
e.g. `collect(x)` returns a 0-dimensional array containing `x`.
36+
37+
Each of those points lead to specific design choice for `IntervalArithmetic.jl`,
38+
choices that we detail below.
39+
40+
41+
## Compatibility with other `Number`s
42+
43+
In julia it is expected that `1 + 2.2` silently promoted the integer `1`
44+
to a `Float64` to be able to perform the addition.
45+
Following this logic, it means that `0.1 + interval(2.2, 2.3)` should
46+
silently promote `0.1` to an interval.
47+
48+
However, in this case we can not guarantee that `0.1` is known exactly,
49+
because we do not know how it was produced in the first place.
50+
Following the julia convention is thus in contradiction with providing
51+
guaranteed result.
52+
53+
In this case, we choose to be mostly silent,
54+
the information that a non-interval of unknown origin is recorded in the `NG` flag,
55+
but the calculation is not interrupted, and no warning is printed.
56+
57+
For convenience, we provide the [`ExactReal`](@ref) and [`@exact`](@ref) macro
58+
to allow to explicitly mark a number as being exact,
59+
and not produce the `NG` flag when mixed with intervals.
60+
61+
62+
## Comparison operators
63+
64+
We can extend our above definition of the desired behavior for two real numbers
65+
`x` and `y`, and their respective intervals `X` and `Y`.
66+
With this, we want to have, for any function`f`,
67+
for all `x` in `X` and all `y` in `Y`,
68+
``math
69+
f(x, y) \in f(X, Y), \qquad \forall x \in X, y \in Y.
70+
``
71+
72+
With this in mind, an operation such as `==` can easily be defined for intervals
73+
74+
1. If the intervals are disjoints (`X ∩ Y === ∅`), then `X == Y` is `[false]`.
75+
2. If the intervals both contain a single element,
76+
and that element is the same for both,
77+
`X == Y` is `[true]`.
78+
3. Otherwise, we can not conclude anything, and `X == Y` must be `[false, true]`.
79+
80+
Not that we use intervals in all case, because, according to our definition,
81+
the true result must be contained in the returned interval.
82+
However, this is not convenient, as any `if` statement would error when used
83+
with an interval.
84+
Instead, we have opted to return respectively `false` and `true`
85+
for cases 1 and 2, and to immediately error otherwise.
86+
87+
In this way, we can return a more informative error,
88+
but we only do it when the result is ambiguous.
89+
90+
This has a clear cost, however, in that some expected behaviors do not hold.
91+
For example, an `Interval` is not equal to itself.
92+
93+
```julia> X = interval(1, 2)
94+
[1.0, 2.0]_com
95+
96+
julia> X == X
97+
ERROR: ArgumentError: `==` is purposely not supported when the intervals are overlapping. See instead `isequal_interval`
98+
Stacktrace:
99+
[1] ==(x::Interval{Float64}, y::Interval{Float64})
100+
@ IntervalArithmetic C:\Users\Kolaru\.julia\packages\IntervalArithmetic\XjBhk\src\intervals\real_interface.jl:86
101+
[2] top-level scope
102+
@ REPL[6]:1.
103+
```
104+
105+
106+
## Intervals as sets
107+
108+
We have taken the perspective to always let `Interval`s act as if they were numbers.
109+
110+
But they are also sets of numbers,
111+
and it would be nice to use all set operations defined in julia on them.
112+
113+
However, `Real` are also sets. For example, the following is valid
114+
115+
```julia
116+
julia> 3 in 3
117+
true
118+
```
119+
120+
Then what should `3 in interval(2, 6)` do?
121+
122+
For interval as a set, it is clearly `true`.
123+
But for intervals as a subtype of `Real` this is equivalent to
124+
```julia
125+
3 == interval(2, 6)
126+
```
127+
which must either be false (they are not the same things),
128+
or error as the result can not be established.
129+
130+
To be safe, we decided to go one step further and disable
131+
**all** set operations from julia `Base` on intervals.
132+
These operations can instead be performed with the specific `*_interval` function,
133+
for example `in_interval` as a replacement for `in`,
134+
except for `setdiff`.
135+
We can not meaningfully define the set difference of two intervals,
136+
because our intervals are always closed,
137+
while the result of `setdiff` can be open.
138+
139+
140+
# Summary
141+
142+
| | Functions | Behavior | Note |
143+
| :---- | :---- | :---- | :---- |
144+
| Arithmetic operations | `+`, `-`, `*`, `/`, `^` | Interval extension | Produce the `NG` flag when mixed with non-interval |
145+
| Other numeric function | `sin`, `exp`, `sqrt`, etc. | Interval extension | |
146+
| Boolean operations | `==`, `<`, `<=`, `iszero`, `isnan`, `isinteger`, `isfinite` | Error if the result can not be guaranteed to be either `true` or `false` | See [`isequal_interval`](@ref) to test equality of intervals, and [`isbounded`](@ref) to test the finiteness of the elements |
147+
| Set operations | `in`, `issubset`, `isdisjoint`, `issetequal`, `isempty`, `union`, `intersect` | Always error | Use the `*_interval` function instead (e.g. [`in_interval`](@ref))
148+
| Exceptions | ``, `setdiff` | Always error | No meaningful interval extension |

src/intervals/interval_operations/boolean.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ isequal_interval(x, y, z, w...) = isequal_interval(x, y) & isequal_interval(y, z
3535

3636
isequal_interval(x) = Base.Fix2(isequal_interval, x)
3737

38+
"""
39+
issetequal_interval(x, y)
40+
41+
Return whether the two interval are identical when considered as sets.
42+
43+
Alias of the [`isequal_interval`](@ref) function.
44+
"""
45+
const issetequal_interval = isequal_interval
46+
3847
"""
3948
issubset_interval(x, y)
4049

src/intervals/interval_operations/cancellative.jl

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
# IEEE Standard 1788-2015 and required for set-based flavor in Section 10.5.6
44

55
"""
6-
cancelminus(x, y)
6+
cancelminus(x, y ; dec = :default)
77
88
Compute the unique interval `z` such that `y + z == x`.
99
10-
The result is decorated by at most `trv` (Section 11.7.1).
10+
The keywork `dec` argument controls the decoration of the result,
11+
it `dec` can be either the decoration of the output,
12+
or a symbol:
13+
- `:default`: if at least one of the input intervals is `ill`,
14+
then the result is `ill`, otherwise it is `trv` (Section 11.7.1).
15+
- `:auto`: the ouptut has the minimal decoration of the inputs.
1116
1217
Implement the `cancelMinus` function of the IEEE Standard 1788-2015 (Section 9.2).
1318
"""
@@ -36,23 +41,27 @@ function cancelminus(x::BareInterval{T}, y::BareInterval{T}) where {T<:NumTypes}
3641
end
3742
cancelminus(x::BareInterval, y::BareInterval) = cancelminus(promote(x, y)...)
3843

39-
function cancelminus(x::Interval, y::Interval)
44+
function cancelminus(x::Interval, y::Interval ; dec = :default)
4045
r = cancelminus(bareinterval(x), bareinterval(y))
41-
d = min(decoration(x), decoration(y), decoration(r), trv)
4246
t = isguaranteed(x) & isguaranteed(y)
43-
return _unsafe_interval(r, d, t)
47+
return _unsafe_interval(r, set_decoration(dec, x, y, r), t)
4448
end
4549

4650
"""
47-
cancelplus(x, y)
51+
cancelplus(x, y ; dec = :default)
4852
4953
Compute the unique interval `z` such that `y - z == x`; this is semantically
5054
equivalent to `cancelminus(x, -y)`.
5155
52-
The result is decorated by at most `trv` (Section 11.7.1).
56+
The keywork `dec` argument controls the decoration of the result,
57+
it `dec` can be either the decoration of the output,
58+
or a symbol:
59+
- `:default`: if at least one of the input intervals is `ill`,
60+
then the result is `ill`, otherwise it is `trv` (Section 11.7.1).
61+
- `:auto`: the ouptut has the minimal decoration of the inputs.
5362
5463
Implement the `cancelPlus` function of the IEEE Standard 1788-2015 (Section 9.2).
5564
"""
5665
cancelplus(x::BareInterval, y::BareInterval) = cancelminus(x, -y)
5766

58-
cancelplus(x::Interval, y::Interval) = cancelminus(x, -y)
67+
cancelplus(x::Interval, y::Interval ; dec = :default) = cancelminus(x, -y ; dec)

0 commit comments

Comments
 (0)