From 691ca5a6286ca7c3eb78c37a9070b6273d2581e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Sun, 18 May 2025 22:13:06 +0200 Subject: [PATCH 1/7] Draft philosophy doc --- docs/make.jl | 1 + docs/src/philosophy.md | 127 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 docs/src/philosophy.md diff --git a/docs/make.jl b/docs/make.jl index 64f12dc48..48c7be6e8 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -19,6 +19,7 @@ makedocs(; pages = [ "Home" => "index.md", "Overview" => "intro.md", + "Philosophy" => "philosophy.md", "Manual" => [ "Constructing intervals" => "manual/construction.md", "Usage" => "manual/usage.md", diff --git a/docs/src/philosophy.md b/docs/src/philosophy.md new file mode 100644 index 000000000..d38e999cb --- /dev/null +++ b/docs/src/philosophy.md @@ -0,0 +1,127 @@ +# Philosophy + +The goal of the `Interval` type is to be directly used to replace floating point +number in arbitrary julia code, such that in any calculation, +the resulting intervals are guaranteed to bound the true image of the starting +intervals. + +Due to the way that the julia ecosystem has evolved, +this means that `Interval` **must** be a subtype of `Real`, +as it is the default supertype used to describe +"numbers that are not complex". +Then, for any function `f(x::Real)`, +we want the following to hold for all real `x` in the interval `X` +(note that it holds for **all** real numbers in `X`, +even those that can not be represented as floating point numbers): +```julia +f(x) ∈ f(X) +``` + +At first glance, this is reasonable: +all arithmetic operations are well-defined for both real numbers and intervals, +therefore we can use multiple dispatch to define the interval behavior of +operations such has `+`, `/`, `sin` or `log`. +Then a code written for `Real`s can be used as is with `Interval`s. + +However, being a `Real` means way more than just being compatible with +arithmetic operations. +`Real`s are also expected to + +1. Be compatible with any other `Number` through promotion. +2. Support comparison operations, such as `==` or `<`. +3. Act as a container of a single element, + e.g. `collect(x)` returns a 0-dimensional array containing `x`. + +Each of those points lead to specific design choice for `IntervalArithmetic.jl`, +choices that we detail below. + + +## Compatibility with other `Number`s + +In julia it is expected that `1 + 2.2` silently promoted the integer `1` +to a `Float64` to be able to perform the addition. +Following this logic, it means that `0.1 + interval(2.2, 2.3)` should +silently promote `0.1` to an interval. + +However, in this case we can not guarantee that `0.1` is known exactly, +because we do not know how it was produced in the first place. +Following the julia convention is thus in contradiction with providing +guaranteed result. + +In this case, we choose to be mostly silent, +the information that a non-interval of unknown origin is recorded in the `NG` flag, +but the calculation is not interrupted and no warning is printed. + + +## Comparison operators + +We can extend our above definition of the desired behavior for two real numbers +`x` and `y`, and their respective intervals `X` and `Y`. +With this, we want to have, for any function`f`, +for all `x` in `X` and all `y` in `Y`, +```julia +f(x, y) ∈ f(X, Y) +``` + +With this in mind, an operation such as `==` can easily be defined for intervals + +1. If the intervals are disjoints (`X ∩ Y === ∅`), then `X == Y` is `[false]`. +2. If the intervals both contain a single element, + and that element is the same for both, + `X == Y` is `[true]`. +3. Otherwise, we can not conclude anything, and `X == Y` must be `[false, true]`. + +Not that we use intervals in all case, because, according to our definition, +the true result must be contained in the returned interval. +However, this is not convenient, as any `if` statement would error when used +with an interval. +Instead, we have opted to return respectively `false` and `true` +for cases 1 and 2, and to immediately error otherwise. + +In this way, we can return a more informative error, +but we only do it when the result is ambiguous. + +This has a clear cost, however, in that some expected behaviors do not hold. +For example, an `Interval` is not equal to itself. + +```julia> X = interval(1, 2) +[1.0, 2.0]_com + +julia> X == X +ERROR: ArgumentError: `==` is purposely not supported when the intervals are overlapping. See instead `isequal_interval` +Stacktrace: + [1] ==(x::Interval{Float64}, y::Interval{Float64}) + @ IntervalArithmetic C:\Users\Kolaru\.julia\packages\IntervalArithmetic\XjBhk\src\intervals\real_interface.jl:86 + [2] top-level scope + @ REPL[6]:1. +``` + + +## Intervals as sets + +We have taken the perspective to always let `Interval`s act as if they were numbers. + +But they are also sets of numbers, +and it would be nice to use all set operations defined in julia on them. + +However, `Real` are also sets. For example, the following is valid + +```julia +julia> 3 in 3 +true +``` + +Then what should `3 in interval(2, 6)` do? + +For interval as a set, it is clearly `true`. +But for intervals as a subtype of `Real` this is equivalent to +```julia +3 == interval(2, 6) +``` +which must either be false (they are not the same things), +or error as the result can not be established. + +To be safe, we decided to go one step further and disable +**all** set operations from julia `Base` on intervals. +These operations can instead be performed with the specific `interval_*` function, +for example `in_interval`. \ No newline at end of file From 4b0afe3727db5261d2a8944b92196c7335c71742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Fri, 30 May 2025 16:23:10 +0200 Subject: [PATCH 2/7] Address comments --- docs/src/philosophy.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/src/philosophy.md b/docs/src/philosophy.md index d38e999cb..9000652d8 100644 --- a/docs/src/philosophy.md +++ b/docs/src/philosophy.md @@ -5,17 +5,19 @@ number in arbitrary julia code, such that in any calculation, the resulting intervals are guaranteed to bound the true image of the starting intervals. -Due to the way that the julia ecosystem has evolved, -this means that `Interval` **must** be a subtype of `Real`, -as it is the default supertype used to describe -"numbers that are not complex". +So, essentially, we would like `Interval` to act as numbers and +the julia ecosystem has evolved to use `Real` as the default supertype +for numerical types that are not complex. +Therefore, to ensure the widest compatiblity, +our `Interval` type must be a subtype of `Real`. + Then, for any function `f(x::Real)`, we want the following to hold for all real `x` in the interval `X` (note that it holds for **all** real numbers in `X`, even those that can not be represented as floating point numbers): -```julia -f(x) ∈ f(X) -``` +$$ +f(x) \in f(X), \qquad \forall x \in X. +$$ At first glance, this is reasonable: all arithmetic operations are well-defined for both real numbers and intervals, @@ -59,9 +61,9 @@ We can extend our above definition of the desired behavior for two real numbers `x` and `y`, and their respective intervals `X` and `Y`. With this, we want to have, for any function`f`, for all `x` in `X` and all `y` in `Y`, -```julia -f(x, y) ∈ f(X, Y) -``` +$$ +f(x, y) \in f(X, Y), \qquad \forall x \in X, y \in Y. +$$ With this in mind, an operation such as `==` can easily be defined for intervals From a0721507c73cac71af1e227f98297875517f42a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Sun, 1 Jun 2025 14:15:14 +0200 Subject: [PATCH 3/7] Complete doc and normlize set operations --- docs/src/manual/usage.md | 45 ++++++++++--------- docs/src/philosophy.md | 20 +++++++-- .../interval_operations/set_operations.jl | 20 ++++++++- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/docs/src/manual/usage.md b/docs/src/manual/usage.md index 332161dfc..b90bf10b4 100644 --- a/docs/src/manual/usage.md +++ b/docs/src/manual/usage.md @@ -91,37 +91,40 @@ sin(Y) -## Comparisons and set operations +## Comparisons -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. +If the result of a comparison can be established with guarantee, +it will be return, otherwise, an error is thrown. ```@repl usage interval(1) < interval(2) -precedes(interval(1), interval(2)) +interval(1, 5) < interval(7, 9) +interval(1, 5) < interval(4.99, 9) +interval(1.23) == interval(1.23) +interval(1.23) == interval(4.99, 9) +interval(1.23) == interval(1.2, 1.3) +``` + +In particular, `if ... else ... end` statements used for floating-points will often break with intervals. + +See [philosophy.md](@ref) for more details and why this choice was made. + + +## Set operations + +Set operations are all disallowed and error on intervals to avoid ambiguities. +To perform set operations on intervals, use the `*_interval` equivalent explicitely, +e.g. `issubset_interval` instead of `issubset`. + + +```@repl usage issubset(interval(1, 2), interval(2)) issubset_interval(interval(1, 2), interval(2)) intersect(interval(1, 2), interval(2)) intersect_interval(interval(1, 2), interval(2)) ``` -In particular, `if ... else ... end` statements used for floating-points will generally break with intervals. - -One can refer to the following: -- `<`: cannot be used with intervals. See instead [`isstrictless`](@ref) or [`strictprecedes`](@ref). -- `==`: 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). -- `iszero`, `isone`: allowed (equivalent to [`isthinzero`](@ref) and [`isthinone`](@ref) respectively). -- `isinteger`: cannot be used with intervals. See instead [`isthininteger`](ref). -- `isfinite`: cannot be used with intervals. See instead [`isbounded`](@ref). -- `isnan`: cannot be used with intervals. See instead [`isnai`](@ref). -- `in`: allowed if at least one argument is not an interval and the interval argument is a singleton. Otherwise, see [`in_interval`](@ref). -- `issubset`: cannot be used with intervals. See instead [`issubset_interval`](@ref). -- `isdisjoint`: cannot be used with intervals. See instead [`isdisjoint_interval`](@ref). -- `issetequal`: cannot be used with intervals. -- `isempty`: cannot be used with intervals. See instead [`isempty_interval`](@ref). -- `union`: cannot be used with intervals. See instead [`hull`](@ref). -- `intersect`: cannot be used with intervals. See instead [`intersect_interval`](@ref). -- `setdiff`: cannot be used with intervals. See instead [`interiordiff`](@ref). - +See [philosophy.md](@ref) for more details and why this choice was made. ## Piecewise functions diff --git a/docs/src/philosophy.md b/docs/src/philosophy.md index 9000652d8..d9f52d9c2 100644 --- a/docs/src/philosophy.md +++ b/docs/src/philosophy.md @@ -52,7 +52,11 @@ guaranteed result. In this case, we choose to be mostly silent, the information that a non-interval of unknown origin is recorded in the `NG` flag, -but the calculation is not interrupted and no warning is printed. +but the calculation is not interrupted, and no warning is printed. + +For convenience, we provide the [`ExactReal`](@ref) and [`@exact`](@ref) macro +to allow to explicitly mark a number as being exact, +and not produce the `NG` flag when mixed with intervals. ## Comparison operators @@ -125,5 +129,15 @@ or error as the result can not be established. To be safe, we decided to go one step further and disable **all** set operations from julia `Base` on intervals. -These operations can instead be performed with the specific `interval_*` function, -for example `in_interval`. \ No newline at end of file +These operations can instead be performed with the specific `*_interval` function, +for example `in_interval` as a replacement for `in`. + + +# Summary + +| | Functions | Behavior | Note | +| -- | -- | -- | -- | +| Arithmetic operations | `+`, `-`, `*`, `/`, `^` | Interval extension | Produce the `NG` flag when mixed with non-interval | +| Other numeric function | `sin`, `exp`, `sqrt`, etc. | Interval extension | | +| 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 | +| Set operations | `in`, `issubset`, `isdisjoint`, `issetequal`, `isempty`, `union`, `intersect`, `setdiff` | Always error | Use the `*_interval` function instead (e.g. [`in_interval`](@ref)) \ No newline at end of file diff --git a/src/intervals/interval_operations/set_operations.jl b/src/intervals/interval_operations/set_operations.jl index 8da27cb85..fcf2cf947 100644 --- a/src/intervals/interval_operations/set_operations.jl +++ b/src/intervals/interval_operations/set_operations.jl @@ -67,6 +67,15 @@ hull(x::Complex, y::Complex) = complex(hull(real(x), real(y)), hull(imag(x), ima hull(x::Real, y::Complex) = complex(hull(x, real(y)), hull(zero(x), imag(y))) hull(x::Complex, y::Real) = complex(hull(real(x), y), hull(imag(x), zero(y))) +""" + union_interval(x, y, z...) + +Return the union of the intervals. + +Alias of the [`hull`](@ref) function. +""" +union_interval(args...) = hull(args...) + """ interiordiff(x, y) @@ -83,7 +92,16 @@ interiordiff(x::AbstractVector, y::AbstractVector) = interiordiff!(Vector{promote_type(typeof(x), typeof(y))}(undef, 0), x, y) """ - interiordiff(x, y) + setdiff_interval(x, y) + +Set difference of the intervals `x` and `y`. + +Alias the [`interiordiff`](@ref) function. +""" +setdiff_interval(x::Interval, y::Interval) = interiordiff(x, y) + +""" + interiordiff!(x, y) In-place version of [`interiordiff`](@ref). """ From 4faaa41031dac0f867ea6dd1a015dfb3abff12df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Sun, 1 Jun 2025 14:31:18 +0200 Subject: [PATCH 4/7] Fix Documenter problems --- docs/src/manual/usage.md | 4 ++-- docs/src/philosophy.md | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/manual/usage.md b/docs/src/manual/usage.md index b90bf10b4..92e90da7a 100644 --- a/docs/src/manual/usage.md +++ b/docs/src/manual/usage.md @@ -107,7 +107,7 @@ interval(1.23) == interval(1.2, 1.3) In particular, `if ... else ... end` statements used for floating-points will often break with intervals. -See [philosophy.md](@ref) for more details and why this choice was made. +See [Philosophy](@ref) for more details and why this choice was made. ## Set operations @@ -124,7 +124,7 @@ intersect(interval(1, 2), interval(2)) intersect_interval(interval(1, 2), interval(2)) ``` -See [philosophy.md](@ref) for more details and why this choice was made. +See [Philosophy](@ref) for more details and why this choice was made. ## Piecewise functions diff --git a/docs/src/philosophy.md b/docs/src/philosophy.md index d9f52d9c2..dfdde4e85 100644 --- a/docs/src/philosophy.md +++ b/docs/src/philosophy.md @@ -15,9 +15,9 @@ Then, for any function `f(x::Real)`, we want the following to hold for all real `x` in the interval `X` (note that it holds for **all** real numbers in `X`, even those that can not be represented as floating point numbers): -$$ +```math f(x) \in f(X), \qquad \forall x \in X. -$$ +``` At first glance, this is reasonable: all arithmetic operations are well-defined for both real numbers and intervals, @@ -65,9 +65,9 @@ We can extend our above definition of the desired behavior for two real numbers `x` and `y`, and their respective intervals `X` and `Y`. With this, we want to have, for any function`f`, for all `x` in `X` and all `y` in `Y`, -$$ +``math f(x, y) \in f(X, Y), \qquad \forall x \in X, y \in Y. -$$ +`` With this in mind, an operation such as `==` can easily be defined for intervals @@ -136,7 +136,7 @@ for example `in_interval` as a replacement for `in`. # Summary | | Functions | Behavior | Note | -| -- | -- | -- | -- | +| :---- | :---- | :---- | :---- | | Arithmetic operations | `+`, `-`, `*`, `/`, `^` | Interval extension | Produce the `NG` flag when mixed with non-interval | | Other numeric function | `sin`, `exp`, `sqrt`, etc. | Interval extension | | | 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 | From e98731f2f101da85add7c497c8f0fa5e8eab0d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Sat, 7 Jun 2025 18:18:46 +0200 Subject: [PATCH 5/7] Fixes from Friday's call --- docs/src/manual/usage.md | 2 +- docs/src/philosophy.md | 11 ++++++++--- src/intervals/interval_operations/set_operations.jl | 11 +---------- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/src/manual/usage.md b/docs/src/manual/usage.md index 92e90da7a..fa794066a 100644 --- a/docs/src/manual/usage.md +++ b/docs/src/manual/usage.md @@ -113,7 +113,7 @@ See [Philosophy](@ref) for more details and why this choice was made. ## Set operations Set operations are all disallowed and error on intervals to avoid ambiguities. -To perform set operations on intervals, use the `*_interval` equivalent explicitely, +To perform set operations on intervals, use the `*_interval` equivalent explicitly, e.g. `issubset_interval` instead of `issubset`. diff --git a/docs/src/philosophy.md b/docs/src/philosophy.md index dfdde4e85..70db376f4 100644 --- a/docs/src/philosophy.md +++ b/docs/src/philosophy.md @@ -130,7 +130,11 @@ or error as the result can not be established. To be safe, we decided to go one step further and disable **all** set operations from julia `Base` on intervals. These operations can instead be performed with the specific `*_interval` function, -for example `in_interval` as a replacement for `in`. +for example `in_interval` as a replacement for `in`, +except for `setdiff`. +We can not meaningfully define the set difference of two intervals, +because our intervals are always closed, +while the result of `setdiff` can be open. # Summary @@ -139,5 +143,6 @@ for example `in_interval` as a replacement for `in`. | :---- | :---- | :---- | :---- | | Arithmetic operations | `+`, `-`, `*`, `/`, `^` | Interval extension | Produce the `NG` flag when mixed with non-interval | | Other numeric function | `sin`, `exp`, `sqrt`, etc. | Interval extension | | -| 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 | -| Set operations | `in`, `issubset`, `isdisjoint`, `issetequal`, `isempty`, `union`, `intersect`, `setdiff` | Always error | Use the `*_interval` function instead (e.g. [`in_interval`](@ref)) \ No newline at end of file +| 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 | +| Set operations | `in`, `issubset`, `isdisjoint`, `issetequal`, `isempty`, `union`, `intersect` | Always error | Use the `*_interval` function instead (e.g. [`in_interval`](@ref)) +| Exceptions | `≈`, `setdiff` | Always error | No meaningful interval extension | \ No newline at end of file diff --git a/src/intervals/interval_operations/set_operations.jl b/src/intervals/interval_operations/set_operations.jl index fcf2cf947..9e5521cf0 100644 --- a/src/intervals/interval_operations/set_operations.jl +++ b/src/intervals/interval_operations/set_operations.jl @@ -74,7 +74,7 @@ Return the union of the intervals. Alias of the [`hull`](@ref) function. """ -union_interval(args...) = hull(args...) +const union_interval = hull """ interiordiff(x, y) @@ -91,15 +91,6 @@ interiordiff(x::Interval, y::Interval) = interiordiff(x::AbstractVector, y::AbstractVector) = interiordiff!(Vector{promote_type(typeof(x), typeof(y))}(undef, 0), x, y) -""" - setdiff_interval(x, y) - -Set difference of the intervals `x` and `y`. - -Alias the [`interiordiff`](@ref) function. -""" -setdiff_interval(x::Interval, y::Interval) = interiordiff(x, y) - """ interiordiff!(x, y) From 09d83c9bb7c507ff47dcbe09344988ddda4e2da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Sat, 7 Jun 2025 18:22:50 +0200 Subject: [PATCH 6/7] Add missing export and functions --- src/intervals/interval_operations/boolean.jl | 9 +++++++++ src/intervals/intervals.jl | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/intervals/interval_operations/boolean.jl b/src/intervals/interval_operations/boolean.jl index 676447ee5..4e49ae577 100644 --- a/src/intervals/interval_operations/boolean.jl +++ b/src/intervals/interval_operations/boolean.jl @@ -35,6 +35,15 @@ isequal_interval(x, y, z, w...) = isequal_interval(x, y) & isequal_interval(y, z isequal_interval(x) = Base.Fix2(isequal_interval, x) +""" + issetequal_interval(x, y) + +Return whether the two interval are identical when considered as sets. + +Alias of the [`isequal_interval`](@ref) function. +""" +const issetequal_interval = isequal_interval + """ issubset_interval(x, y) diff --git a/src/intervals/intervals.jl b/src/intervals/intervals.jl index 374531089..3e645d03a 100644 --- a/src/intervals/intervals.jl +++ b/src/intervals/intervals.jl @@ -31,7 +31,7 @@ include("interval_operations/constants.jl") include("interval_operations/extended_div.jl") export extended_div include("interval_operations/boolean.jl") - export isequal_interval, issubset_interval, isstrictsubset, isinterior, + export isequal_interval, issetequal_interval, issubset_interval, isstrictsubset, isinterior, isdisjoint_interval, isweakless, isstrictless, precedes, strictprecedes, in_interval, isempty_interval, isentire_interval, isnai, isbounded, isunbounded, iscommon, isatomic, isthin, isthinzero, isthinone, @@ -42,6 +42,6 @@ include("interval_operations/overlap.jl") include("interval_operations/numeric.jl") export inf, sup, bounds, mid, diam, radius, midradius, mag, mig, dist include("interval_operations/set_operations.jl") - export intersect_interval, hull, interiordiff + export intersect_interval, hull, interiordiff, union_interval include("interval_operations/bisect.jl") export bisect, mince, mince! From e19e80cee0b6393faba25940e222349178d675cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Richard?= Date: Sun, 22 Jun 2025 14:13:33 +0200 Subject: [PATCH 7/7] Add decoration keyword argument to set operations --- .../interval_operations/cancellative.jl | 25 +++-- .../interval_operations/set_operations.jl | 106 +++++++++++------- 2 files changed, 81 insertions(+), 50 deletions(-) diff --git a/src/intervals/interval_operations/cancellative.jl b/src/intervals/interval_operations/cancellative.jl index 3b2e71e46..eeae2e1a3 100644 --- a/src/intervals/interval_operations/cancellative.jl +++ b/src/intervals/interval_operations/cancellative.jl @@ -3,11 +3,16 @@ # IEEE Standard 1788-2015 and required for set-based flavor in Section 10.5.6 """ - cancelminus(x, y) + cancelminus(x, y ; dec = :default) Compute the unique interval `z` such that `y + z == x`. -The result is decorated by at most `trv` (Section 11.7.1). +The keywork `dec` argument controls the decoration of the result, +it `dec` can be either the decoration of the output, +or a symbol: + - `:default`: if at least one of the input intervals is `ill`, + then the result is `ill`, otherwise it is `trv` (Section 11.7.1). + - `:auto`: the ouptut has the minimal decoration of the inputs. Implement the `cancelMinus` function of the IEEE Standard 1788-2015 (Section 9.2). """ @@ -36,23 +41,27 @@ function cancelminus(x::BareInterval{T}, y::BareInterval{T}) where {T<:NumTypes} end cancelminus(x::BareInterval, y::BareInterval) = cancelminus(promote(x, y)...) -function cancelminus(x::Interval, y::Interval) +function cancelminus(x::Interval, y::Interval ; dec = :default) r = cancelminus(bareinterval(x), bareinterval(y)) - d = min(decoration(x), decoration(y), decoration(r), trv) t = isguaranteed(x) & isguaranteed(y) - return _unsafe_interval(r, d, t) + return _unsafe_interval(r, set_decoration(dec, x, y, r), t) end """ - cancelplus(x, y) + cancelplus(x, y ; dec = :default) Compute the unique interval `z` such that `y - z == x`; this is semantically equivalent to `cancelminus(x, -y)`. -The result is decorated by at most `trv` (Section 11.7.1). +The keywork `dec` argument controls the decoration of the result, +it `dec` can be either the decoration of the output, +or a symbol: + - `:default`: if at least one of the input intervals is `ill`, + then the result is `ill`, otherwise it is `trv` (Section 11.7.1). + - `:auto`: the ouptut has the minimal decoration of the inputs. Implement the `cancelPlus` function of the IEEE Standard 1788-2015 (Section 9.2). """ cancelplus(x::BareInterval, y::BareInterval) = cancelminus(x, -y) -cancelplus(x::Interval, y::Interval) = cancelminus(x, -y) +cancelplus(x::Interval, y::Interval ; dec = :default) = cancelminus(x, -y ; dec) diff --git a/src/intervals/interval_operations/set_operations.jl b/src/intervals/interval_operations/set_operations.jl index 9e5521cf0..d8be74940 100644 --- a/src/intervals/interval_operations/set_operations.jl +++ b/src/intervals/interval_operations/set_operations.jl @@ -2,14 +2,31 @@ # of the IEEE Std 1788-2015 and required for set-based flavor in Section 10.5.7 # Some other (non required) related functions are also present +function set_decoration(dec, xs...) + if dec == :default + return min(trv, decoration.(xs)...) + elseif dec == :auto + return min(decoration.(xs)...) + end + throw(ArgumentError("unknown decoration option $dec. Valid options are :default or :auto, or a decoration.")) +end + +set_decoration(dec::Decoration, xs...) = dec + + """ - intersect_interval(x, y) + intersect_interval(x, y ; dec = :default) Returns the intersection of the intervals `x` and `y`, considered as (extended) sets of real numbers. That is, the set that contains the points common in `x` and `y`. -The result is decorated by at most `trv` (Section 11.7.1). +The keywork `dec` argument controls the decoration of the result, +it `dec` can be either the decoration of the output, +or a symbol: + - `:default`: if at least one of the input intervals is `ill`, + then the result is `ill`, otherwise it is `trv` (Section 11.7.1). + - `:auto`: the ouptut has the minimal decoration of the inputs. Implement the `intersection` function of the IEEE Standard 1788-2015 (Section 9.3). """ @@ -24,27 +41,31 @@ function intersect_interval(x::BareInterval{T}, y::BareInterval{T}) where {T<:Nu end intersect_interval(x::BareInterval, y::BareInterval) = intersect_interval(promote(x, y)...) -function intersect_interval(x::Interval{T}, y::Interval{S}) where {T<:NumTypes,S<:NumTypes} +function intersect_interval(x::Interval{T}, y::Interval{S} ; dec = :default) where {T<:NumTypes,S<:NumTypes} isnai(x) | isnai(y) && return nai(promote_type(T, S)) r = intersect_interval(bareinterval(x), bareinterval(y)) - d = min(decoration(x), decoration(y), trv) t = isguaranteed(x) & isguaranteed(y) - return _unsafe_interval(r, d, t) + return _unsafe_interval(r, set_decoration(dec, x, y), t) end -intersect_interval(x, y, z, w...) = reduce(intersect_interval, (x, y, z, w...)) -intersect_interval(x::Complex, y::Complex) = complex(intersect_interval(real(x), real(y)), intersect_interval(imag(x), imag(y))) -intersect_interval(x::Real, y::Complex) = complex(intersect_interval(x, real(y)), intersect_interval(zero(x), imag(y))) -intersect_interval(x::Complex, y::Real) = complex(intersect_interval(real(x), y), intersect_interval(imag(x), zero(y))) +intersect_interval(x, y, z, w... ; dec = :default) = reduce((a, b) -> intersect_interval(a, b ; dec), (x, y, z, w...)) +intersect_interval(x::Complex, y::Complex ; dec = :default) = complex(intersect_interval(real(x), real(y) ; dec), intersect_interval(imag(x), imag(y) ; dec)) +intersect_interval(x::Real, y::Complex ; dec = :default) = complex(intersect_interval(x, real(y) ; dec), intersect_interval(zero(x), imag(y) ; dec)) +intersect_interval(x::Complex, y::Real ; dec = :default) = complex(intersect_interval(real(x), y ; dec), intersect_interval(imag(x), zero(y) ; dec)) """ - hull(x, y) + hull(x, y ; dec = :default) Return the interval hull of the intervals `x` and `y`, considered as (extended) sets of real numbers, i.e. the smallest interval that contains all of `x` and `y`. -The result is decorated by at most `trv` (Section 11.7.1). +The keywork `dec` argument controls the decoration of the result, +it `dec` can be either the decoration of the output, +or a symbol: + - `:default`: if at least one of the input intervals is `ill`, + then the result is `ill`, otherwise it is `trv` (Section 11.7.1). + - `:auto`: the ouptut has the minimal decoration of the inputs. Implement the `convexHull` function of the IEEE Standard 1788-2015 (Section 9.3). """ @@ -54,18 +75,17 @@ function hull(x::BareInterval{T}, y::BareInterval{T}) where {T<:NumTypes} end hull(x::BareInterval, y::BareInterval) = hull(promote(x, y)...) -function hull(x::Interval{T}, y::Interval{S}) where {T<:NumTypes,S<:NumTypes} +function hull(x::Interval{T}, y::Interval{S} ; dec = :default) where {T<:NumTypes,S<:NumTypes} isnai(x) | isnai(y) && return nai(promote_type(T, S)) r = hull(bareinterval(x), bareinterval(y)) - d = min(decoration(x), decoration(y), trv) t = isguaranteed(x) & isguaranteed(y) - return _unsafe_interval(r, d, t) + return _unsafe_interval(r, set_decoration(dec, x, y), t) end -hull(x, y, z, w...) = reduce(hull, (x, y, z, w...)) -hull(x::Complex, y::Complex) = complex(hull(real(x), real(y)), hull(imag(x), imag(y))) -hull(x::Real, y::Complex) = complex(hull(x, real(y)), hull(zero(x), imag(y))) -hull(x::Complex, y::Real) = complex(hull(real(x), y), hull(imag(x), zero(y))) +hull(x, y, z, w... ; dec = :default) = reduce((a, b) -> hull(a, b ; dec), (x, y, z, w...)) +hull(x::Complex, y::Complex ; dec = :default) = complex(hull(real(x), real(y) ; dec), hull(imag(x), imag(y) ; dec)) +hull(x::Real, y::Complex ; dec = :default) = complex(hull(x, real(y) ; dec), hull(zero(x), imag(y) ; dec)) +hull(x::Complex, y::Real ; dec = :default) = complex(hull(real(x), y ; dec), hull(imag(x), zero(y) ; dec)) """ union_interval(x, y, z...) @@ -77,26 +97,33 @@ Alias of the [`hull`](@ref) function. const union_interval = hull """ - interiordiff(x, y) + interiordiff(x, y ; dec = :default) Remove the interior of `y` from `x`. If `x` and `y` are vectors, then they are treated as multi-dimensional intervals. + +The keywork `dec` argument controls the decoration of the result, +it `dec` can be either the decoration of the output, +or a symbol: + - `:default`: if at least one of the input intervals is `ill`, + then the result is `ill`, otherwise it is `trv` (Section 11.7.1). + - `:auto`: the ouptut has the minimal decoration of the inputs. """ interiordiff(x::BareInterval, y::BareInterval) = interiordiff!(Vector{promote_type(typeof(x), typeof(y))}(undef, 0), x, y) -interiordiff(x::Interval, y::Interval) = - interiordiff!(Vector{promote_type(typeof(x), typeof(y))}(undef, 0), x, y) +interiordiff(x::Interval, y::Interval ; dec = :default) = + interiordiff!(Vector{promote_type(typeof(x), typeof(y))}(undef, 0), x, y ; dec) -interiordiff(x::AbstractVector, y::AbstractVector) = - interiordiff!(Vector{promote_type(typeof(x), typeof(y))}(undef, 0), x, y) +interiordiff(x::AbstractVector, y::AbstractVector ; dec = :default) = + interiordiff!(Vector{promote_type(typeof(x), typeof(y))}(undef, 0), x, y ; dec) """ interiordiff!(x, y) In-place version of [`interiordiff`](@ref). """ -function interiordiff!(v::AbstractVector, x::BareInterval{T}, y::BareInterval{T}) where {T<:NumTypes} +function interiordiff!(v::AbstractVector, x::BareInterval{T}, y::BareInterval{T} ; dec = nothing) where {T<:NumTypes} if isinterior(x, y) empty!(v) elseif isdisjoint_interval(x, y) @@ -118,43 +145,38 @@ function interiordiff!(v::AbstractVector, x::BareInterval{T}, y::BareInterval{T} end return v end -interiordiff!(v::AbstractVector, x::BareInterval, y::BareInterval) = interiordiff!(v, promote(x, y)...) +interiordiff!(v::AbstractVector, x::BareInterval, y::BareInterval ; dec = nothing) = interiordiff!(v, promote(x, y)...) -function interiordiff!(v::AbstractVector, x::Interval{T}, y::Interval{T}) where {T<:NumTypes} +function interiordiff!(v::AbstractVector, x::Interval{T}, y::Interval{T} ; dec = :default) where {T<:NumTypes} if isinterior(x, y) empty!(v) elseif isdisjoint_interval(x, y) resize!(v, 1) @inbounds v[begin] = x else - d = min(decoration(x), decoration(y)) t = isguaranteed(x) & isguaranteed(y) inter = intersect_interval(x, y) if issubset_interval(y, x) resize!(v, 2) r1 = _unsafe_bareinterval(T, inf(x), inf(inter)) - d1 = min(d, decoration(r1), trv) - @inbounds v[begin] = _unsafe_interval(r1, d1, t) + @inbounds v[begin] = _unsafe_interval(r1, set_decoration(dec, x, y, r1), t) r2 = _unsafe_bareinterval(T, sup(inter), sup(x)) - d2 = min(d, decoration(r2), trv) - @inbounds v[end] = _unsafe_interval(r2, d2, t) + @inbounds v[end] = _unsafe_interval(r2, set_decoration(dec, x, y, r2), t) elseif isweakless(x, inter) resize!(v, 1) r1 = _unsafe_bareinterval(T, inf(x), inf(inter)) - d1 = min(d, decoration(r1), trv) - @inbounds v[begin] = _unsafe_interval(r1, d1, t) + @inbounds v[begin] = _unsafe_interval(r1, set_decoration(dec, x, y, r1), t) else resize!(v, 1) r2 = _unsafe_bareinterval(T, sup(inter), sup(x)) - d2 = min(d, decoration(r2), trv) - @inbounds v[begin] = _unsafe_interval(r2, d2, t) + @inbounds v[begin] = _unsafe_interval(r2, set_decoration(dec, x, y, r2), t) end end return v end -interiordiff!(v::AbstractVector, x::Interval, y::Interval) = interiordiff!(v, promote(x, y)...) +interiordiff!(v::AbstractVector, x::Interval, y::Interval ; dec = :default) = interiordiff!(v, promote(x, y)... ; dec) -function interiordiff!(v::AbstractVector{<:AbstractVector}, x::AbstractVector, y::AbstractVector) +function interiordiff!(v::AbstractVector{<:AbstractVector}, x::AbstractVector, y::AbstractVector ; dec = :default) # start from the total overlap (in all directions); expand each direction in turn N = length(x) @@ -171,7 +193,7 @@ function interiordiff!(v::AbstractVector{<:AbstractVector}, x::AbstractVector, y x_bis = copy(x) @inbounds for i ∈ eachindex(x, y) - h₁, h₂, inter = _interiordiff(x[i], y[i]) + h₁, h₂, inter = _interiordiff(x[i], y[i] ; dec) u₁ = similar(eltype(v), N) u₂ = similar(eltype(v), N) @inbounds for j ∈ eachindex(u₁) @@ -190,7 +212,7 @@ function interiordiff!(v::AbstractVector{<:AbstractVector}, x::AbstractVector, y return v end -function _interiordiff(x::BareInterval{T}, y::BareInterval{T}) where {T<:NumTypes} +function _interiordiff(x::BareInterval{T}, y::BareInterval{T} ; dec = nothing) where {T<:NumTypes} isdisjoint_interval(x, y) && return (x, emptyinterval(BareInterval{T}), emptyinterval(BareInterval{T})) inter = intersect_interval(x, y) @@ -202,11 +224,11 @@ function _interiordiff(x::BareInterval{T}, y::BareInterval{T}) where {T<:NumType return (_unsafe_bareinterval(T, inf(x), inf(y)), _unsafe_bareinterval(T, sup(y), sup(x)), inter) end -_interiordiff(x::BareInterval, y::BareInterval) = _interiordiff(promote(x, y)...) +_interiordiff(x::BareInterval, y::BareInterval ; dec = nothing) = _interiordiff(promote(x, y)... ; dec) -function _interiordiff(x::Interval, y::Interval) +function _interiordiff(x::Interval, y::Interval ; dec = :default) h₁, h₂, inter = _interiordiff(bareinterval(x), bareinterval(y)) - d = min(decoration(x), decoration(y), trv) + d = set_decoration(dec, x, y) t = isguaranteed(x) & isguaranteed(y) return (_unsafe_interval(h₁, d, t), _unsafe_interval(h₂, d, t), _unsafe_interval(inter, d, t)) end