Skip to content

Release 1.0 #698

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
fail-fast: false
matrix:
version:
- '1.9'
- '1.10'
- '1'
- 'nightly'
os:
Expand Down
10 changes: 6 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ version = "0.22.30"
CRlibm = "96374032-68de-5a5b-8d9e-752f78720389"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
OpenBLASConsistentFPCSR_jll = "6cdc7f73-28fd-5e50-80fb-958a8875b1af"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
RoundingEmulator = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705"

[weakdeps]
Expand All @@ -28,12 +29,13 @@ IntervalArithmeticSparseArraysExt = "SparseArrays"
[compat]
CRlibm = "1.0.2"
DiffRules = "1"
ForwardDiff = "0.10, 1"
ForwardDiff = "1"
IntervalSets = "0.7"
LinearAlgebra = "1.9"
LinearAlgebra = "1.10"
MacroTools = "0.5"
OpenBLASConsistentFPCSR_jll = "0.3.29"
Printf = "1.10"
RecipesBase = "1"
RoundingEmulator = "0.2"
SparseArrays = "1.9.0"
julia = "1.9"
SparseArrays = "1.10"
julia = "1.10"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The official documentation is available online: https://juliaintervals.github.io

## Installation

The IntervalArithmetic.jl package requires to [install Julia](https://julialang.org/downloads/) (v1.9 or above).
The IntervalArithmetic.jl package requires to [install Julia](https://julialang.org/downloads/) (v1.10 or above).

Then, start Julia and execute the following command in the REPL:

Expand Down
2 changes: 1 addition & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ IntervalArithmetic.jl is a Julia package for validated numerics in Julia. All ca
## Installation

```@repl
using Pkg # Julia v1.9 or above
using Pkg # Julia v1.10 or above
redirect_stderr(devnull) do # hide
Pkg.add("IntervalArithmetic")
end # hide
Expand Down
9 changes: 5 additions & 4 deletions ext/IntervalArithmeticForwardDiffExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ Base.promote_rule(::Type{ExactReal{S}}, ::Type{Dual{T, V, N}}) where {S<:Real, T
Base.promote_rule(::Type{Dual{T, V, N}}, ::Type{ExactReal{S}}) where {S<:Real, T, V, N} =
Dual{T,ExactReal{IntervalArithmetic.promote_numtype(V, S)},N}

Base.:(==)(x::Union{BareInterval,Interval}, y::Dual) = x == value(y)
Base.:(==)(x::Dual, y::Union{BareInterval,Interval}) = value(x) == y
Base.:(==)(x::Interval, y::Dual) = x == value(y)
Base.:(==)(x::Dual, y::Interval) = value(x) == y
Base.:<(x::Interval, y::Dual) = x < value(y)
Base.:<(x::Dual, y::Interval) = value(x) < y

function Base.:(^)(x::Dual{Txy,<:Interval}, y::Dual{Txy,<:Interval}) where {Txy}
vx, vy = value(x), value(y)
Expand Down Expand Up @@ -91,10 +93,9 @@ function Base.:(^)(x::ExactReal, y::Dual{<:Ty}) where {Ty}
end
end


# Piecewise functions

function (constant::Constant)(::Dual{T, Interval{S}}) where {T, S}
function (constant::Constant)(::Dual{T,Interval{S}}) where {T, S}
return Dual{T}(interval(S, constant.value), interval(S, 0.0))
end

Expand Down
4 changes: 3 additions & 1 deletion src/IntervalArithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ include("piecewise.jl")

#

import Printf

include("display.jl")
export setdisplay

Expand Down Expand Up @@ -205,7 +207,7 @@ Configure the default behavior for:
definition of matrix multiplication. Learn more:
[`IntervalArithmetic.MatMulMode`](@ref).
"""
function configure(; numtype::Type{<:NumTypes}=Float64, flavor::Symbol=:set_based, rounding::Symbol=:correct, power::Symbol=:fast, matmul::Symbol=:slow)
function configure(; numtype::Type{<:NumTypes}=Float64, flavor::Symbol=:set_based, rounding::Symbol=:correct, power::Symbol=:fast, matmul::Symbol=:fast)
configure_numtype(numtype)
configure_flavor(flavor)
configure_rounding(rounding)
Expand Down
64 changes: 32 additions & 32 deletions src/display.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Display options:
- significant digits: 6

julia> x = interval(0.1, 0.3)
[0.0999999, 0.300001]_com
[0.1, 0.3]_com

julia> setdisplay(:full)
Display options:
Expand All @@ -69,7 +69,7 @@ Display options:
- significant digits: 3

julia> x
[0.0999, 0.301]_com
[0.1, 0.3]_com

julia> setdisplay(; decorations = false)
Display options:
Expand All @@ -79,7 +79,7 @@ Display options:
- significant digits: 3

julia> x
[0.0999, 0.301]
[0.1, 0.3]
```
"""
function setdisplay(format::Symbol = display_options.format;
Expand Down Expand Up @@ -312,41 +312,41 @@ end
# code inspired by `_string(x::BigFloat, k::Integer)` in base/mpfr.jl

function _round_string(x::T, sigdigits::Int, r::RoundingMode) where {T<:AbstractFloat}
str_x = string(x)
str_digits = split(contains(str_x, '.') ? split(str_x, '.'; limit = 2)[2] : str_x, 'e'; limit = 2)[1]
len = length(str_digits)
if isinteger(x) && sigdigits ≥ len # `x` is exactly representable
return replace(_round_string(big(x), length(str_x), RoundNearest), "e-0" => "e-")
elseif ispow2(abs(x)) && sigdigits ≥ len # `x` is exactly representable
return replace(_round_string(big(x), len + 1, RoundNearest), "e-0" => "e-")
else
return _round_string(big(x), sigdigits, r)
end
end
!isfinite(x) && return string(x)

_round_string(x::BigFloat, sigdigits::Int, ::RoundingMode{:Nearest}) =
Base.MPFR._string(x, sigdigits-1) # `sigdigits-1` digits after the decimal
ndigits = ceil(Int, precision(x) * log10(T(2)))
sci_str = Printf.@sprintf("%.*e", ndigits, x)

function _round_string(x::BigFloat, sigdigits::Int, r::RoundingMode)
if !isfinite(x)
return string(Float64(x))
else
str_x = string(x)
str_digits = split(split(str_x, '.'; limit = 2)[2], 'e'; limit = 2)[1]
len = length(str_digits)
if isinteger(x) && sigdigits ≥ len # `x` is exactly representable
return _round_string(big(x), length(str_x), RoundNearest)
elseif ispow2(abs(x)) && sigdigits ≥ len # `x` is exactly representable
return _round_string(big(x), len + 1, RoundNearest)
else
# `sigdigits` digits after the decimal
str = Base.MPFR.string_mpfr(x, "%.$(sigdigits)Re")
rounded_str = _round_string(str, r)
return Base.MPFR._prettify_bigfloat(rounded_str)
if abs(x) < floatmax(T)
ndigits_ = ndigits - 1
sci_str_ = Printf.@sprintf("%.*e", ndigits_, x)

if parse(T, sci_str) == parse(T, sci_str_)
ndigits = ndigits_
sci_str = sci_str_
end
end

mantissa = split(sci_str, 'e')[1]

mantissa_digits = replace(mantissa, "." => "")

significant_digits = length(rstrip(mantissa_digits, '0'))

is_representable = significant_digits ≤ sigdigits

# `min(sigdigits-1, ndigits)` ensure we do not exceed the precision of `x`
str = is_representable ? Printf.@sprintf("%.*e", min(sigdigits-1, ndigits), x) : _round_string(Printf.@sprintf("%.*e", sigdigits, x), r)

return Base.MPFR._prettify_bigfloat(str)
end

_round_string(x::AbstractFloat, sigdigits::Int, r::RoundingMode{:Nearest}) =
_round_string(big(x), sigdigits, r)

_round_string(x::BigFloat, sigdigits::Int, ::RoundingMode{:Nearest}) =
Base.MPFR._string(x, sigdigits-1) # `sigdigits-1` digits after the decimal

_round_string(s::String, ::RoundingMode{:Up}) =
startswith(s, '-') ? string('-', _round_string_down(s[2:end])) : _round_string_up(s)

Expand Down
137 changes: 59 additions & 78 deletions src/intervals/real_interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,96 +78,77 @@ Base.hash(x::Interval, h::UInt) = hash(sup(x), hash(inf(x), hash(Interval, h)))

#

for T ∈ (:BareInterval, :Interval)
@eval begin
function Base.:(==)(x::$T, y::$T) # also returned when calling `≤`, `≥`, `isequal`
isthin(x) && return sup(x) == y
isthin(y) && return x == sup(y)
return throw(ArgumentError("`==` is purposely not supported when the intervals are overlapping. See instead `isequal_interval`"))
end

Base.:<(::$T, ::$T) = # also returned when calling `isless`, `>`
throw(ArgumentError("`<` is purposely not supported for intervals. See instead `isstrictless`, `strictprecedes`"))

Base.isdisjoint(::$T, ::$T) =
throw(ArgumentError("`isdisjoint` is purposely not supported for intervals. See instead `isdisjoint_interval`"))

Base.issubset(::$T, ::$T) =
throw(ArgumentError("`issubset` is purposely not supported for intervals. See instead `issubset_interval`"))

Base.issetequal(::$T, ::$T) =
throw(ArgumentError("`issetequal` is purposely not supported for intervals. See instead `isequal_interval`"))

Base.in(::$T, ::$T) =
throw(ArgumentError("`in` is purposely not supported for intervals. See instead `in_interval`"))

Base.isempty(::$T) =
throw(ArgumentError("`isempty` is purposely not supported for intervals. See instead `isempty_interval`"))

Base.isfinite(::$T) = # also returned when calling `isinf`
throw(ArgumentError("`isfinite` is purposely not supported for intervals. See instead `isbounded`"))

Base.isnan(::$T) =
throw(ArgumentError("`isnan` is purposely not supported for intervals. See instead `isnai`"))

Base.intersect(::$T) =
throw(ArgumentError("`intersect` is purposely not supported for intervals. See instead `intersect_interval`"))

Base.union!(::BitSet, ::$T) = # needed to resolve ambiguity
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))
Base.union!(::AbstractSet, ::$T) = # also returned when calling `intersect`, `symdiff` with intervals
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))
Base.union!(::AbstractVector{S}, ::$T) where {S} =
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))
Base.union!(::AbstractVector{S}, ::$T, ::Any, ::Any...) where {S} =
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))
Base.union!(::AbstractVector{S}, ::$T, ::$T, ::Any...) where {S} =
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))
Base.union!(::AbstractVector{S}, ::Any, ::$T, ::Any...) where {S} =
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))

Base.setdiff(::$T) =
throw(ArgumentError("`setdiff` is purposely not supported for intervals. See instead `interiordiff`"))
Base.setdiff!(::AbstractSet, ::$T) =
throw(ArgumentError("`setdiff!` is purposely not supported for intervals. See instead `interiordiff`"))
end
function Base.:(==)(x::Interval, y::Interval) # also returned when calling `≤`, `≥`, `isequal`
isthin(x) && return sup(x) == y
isthin(y) && return x == sup(y)
isdisjoint_interval(x, y) && return false
return throw(ArgumentError("`==` is purposely not supported when the intervals are overlapping. See instead `isequal_interval`"))
end
Base.union!(::AbstractVector{S}, ::BareInterval, ::Interval, ::Any...) where {S} =

function Base.:<(x::Interval, y::Interval)
isthin(x) && return sup(x) < y
isthin(y) && return x < sup(y)
strictprecedes(x, y) && return true
strictprecedes(y, x) && return false
return throw(ArgumentError("`<` is purposely not supported when the intervals are overlapping. See instead `strictprecedes`"))
end

# Base.isdisjoint(::Interval, ::Interval) =
# throw(ArgumentError("`isdisjoint` is purposely not supported for intervals. See instead `isdisjoint_interval`"))

# Base.issubset(::Interval, ::Interval) =
# throw(ArgumentError("`issubset` is purposely not supported for intervals. See instead `issubset_interval`"))

# Base.issetequal(::Interval, ::Interval) =
# throw(ArgumentError("`issetequal` is purposely not supported for intervals. See instead `isequal_interval`"))

# Base.in(::Interval, ::Interval) =
# throw(ArgumentError("`in` is purposely not supported for intervals. See instead `in_interval`"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These overrides should be brought back to be consistent with #719


Base.isempty(::Interval) =
throw(ArgumentError("`isempty` is purposely not supported for intervals. See instead `isempty_interval`"))

Base.isfinite(::Interval) = # also returned when calling `isinf`
throw(ArgumentError("`isfinite` is purposely not supported for intervals. See instead `isbounded`"))

Base.isnan(::Interval) =
throw(ArgumentError("`isnan` is purposely not supported for intervals. See instead `isnai`"))

Base.intersect(::Interval) =
throw(ArgumentError("`intersect` is purposely not supported for intervals. See instead `intersect_interval`"))

Base.union!(::BitSet, ::Interval) = # needed to resolve ambiguity
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))
Base.union!(::AbstractSet, ::Interval) = # also returned when calling `intersect`, `symdiff` with intervals
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))
Base.union!(::AbstractVector{S}, ::Interval) where {S} =
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))
Base.union!(::AbstractVector{S}, ::Interval, ::BareInterval, ::Any...) where {S} =
Base.union!(::AbstractVector{S}, ::Interval, ::Any, ::Any...) where {S} =
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))
Base.union!(::AbstractVector{S}, ::Interval, ::Interval, ::Any...) where {S} =
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))
Base.union!(::AbstractVector{S}, ::Any, ::Interval, ::Any...) where {S} =
throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`"))

Base.setdiff(::Interval) =
throw(ArgumentError("`setdiff` is purposely not supported for intervals. See instead `interiordiff`"))
Base.setdiff!(::AbstractSet, ::Interval) =
throw(ArgumentError("`setdiff!` is purposely not supported for intervals. See instead `interiordiff`"))

# pointwise equality

"""
==(::BareInterval, ::Number)
==(::Number, ::BareInterval)
==(::Interval, ::Number)
==(::Number, ::Interval)

Test whether an interval is the singleton of a given number. In other words, the
result is true if and only if the interval contains only that number.

!!! note
Comparison between intervals is purposely disallowed. Indeed, equality
between non-singleton intervals has distinct properties, notably ``x = y``
does not imply ``x - y = 0``. See instead [`isequal_interval`](@ref).
"""
Base.:(==)(x::Union{BareInterval,Interval}, y::Number) = isthin(x, y)
Base.:(==)(x::Number, y::Union{BareInterval,Interval}) = y == x
Base.:(==)(x::Interval, y::Number) = !isthin(x) & in_interval(y, x) ? throw(ArgumentError("`==` is purposely not supported when the number is contained in the interval. See instead `isthin`")) : isthin(x, y)
Base.:(==)(x::Number, y::Interval) = y == x
# needed to resolve ambiguity from irrationals.jl
Base.:(==)(x::Interval, y::AbstractIrrational) = isthin(x, y)
Base.:(==)(x::Interval, y::AbstractIrrational) = !isthin(x) & in_interval(y, x) ? throw(ArgumentError("`==` is purposely not supported when the number is contained in the interval. See instead `isthin`")) : isthin(x, y)
Base.:(==)(x::AbstractIrrational, y::Interval) = y == x
# needed to resolve ambiguity from complex.jl
Base.:(==)(x::Interval, y::Complex) = isreal(y) & (real(y) == x)
Base.:(==)(x::Complex, y::Interval) = y == x

# follows docstring of `Base.iszero`
Base.iszero(x::Union{BareInterval,Interval}) = isthinzero(x)

# follows docstring of `Base.isone`
Base.isone(x::Union{BareInterval,Interval}) = isthinone(x)
Base.:<(x::Interval, y::Real) = (!isthin(x) & in_interval(y, x)) | isempty_interval(x) ? throw(ArgumentError("`<` is purposely not supported when the number is contained in the interval, or if the interval is empty")) : sup(x) < y
Base.:<(x::Real, y::Interval) = (!isthin(y) & in_interval(x, y)) | isempty_interval(y) ? throw(ArgumentError("`<` is purposely not supported when the number is contained in the interval, or if the interval is empty")) : x < inf(y)
Comment on lines +150 to +151
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Base.:<(x::Interval, y::Real) = (!isthin(x) & in_interval(y, x)) | isempty_interval(x) ? throw(ArgumentError("`<` is purposely not supported when the number is contained in the interval, or if the interval is empty")) : sup(x) < y
Base.:<(x::Real, y::Interval) = (!isthin(y) & in_interval(x, y)) | isempty_interval(y) ? throw(ArgumentError("`<` is purposely not supported when the number is contained in the interval, or if the interval is empty")) : x < inf(y)
Base.:<(x::Interval, y::Real) = ((!isthin(x) & in_interval(y, x)) | isempty_interval(x)) ? throw(ArgumentError("`<` is purposely not supported when the number is contained in the interval, or if the interval is empty")) : sup(x) < y
Base.:<(x::Real, y::Interval) = ((!isthin(y) & in_interval(x, y)) | isempty_interval(y)) ? throw(ArgumentError("`<` is purposely not supported when the number is contained in the interval, or if the interval is empty")) : x < inf(y)

I got tripped by the precedence of ? compared to |.

Why are | and & used instead of || and && here ?



# follows docstring of `Base.isinteger`
Base.isinteger(x::Union{BareInterval,Interval}) = isthininteger(x)
Base.isinteger(x::Interval) = !isthin(x) & !isdisjoint_interval(x, floor(x), ceil(x)) ? throw(ArgumentError("`isinteger` is purposely not supported for non-thin containing at least one integer. See instead `isthininteger`")) : isthininteger(x)
38 changes: 18 additions & 20 deletions test/aqua.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,25 @@ using Test
using IntervalArithmetic
using Aqua

if VERSION ≥ v"1.10"
@testset "Aqua tests (performance)" begin
# This tests that we don't accidentally run into
# https://github.com/JuliaLang/julia/issues/29393
# Aqua.test_unbound_args(IntervalArithmetic)
ua = Aqua.detect_unbound_args_recursively(IntervalArithmetic)
@test length(ua) == 0
@testset "Aqua tests (performance)" begin
# This tests that we don't accidentally run into
# https://github.com/JuliaLang/julia/issues/29393
# Aqua.test_unbound_args(IntervalArithmetic)
ua = Aqua.detect_unbound_args_recursively(IntervalArithmetic)
@test length(ua) == 0

# See: https://github.com/SciML/OrdinaryDiffEq.jl/issues/1750
# Test that we're not introducing method ambiguities across deps
ambs = Aqua.detect_ambiguities(IntervalArithmetic; recursive = true)
pkg_match(pkgname, pkdir::Nothing) = false
pkg_match(pkgname, pkdir::AbstractString) = occursin(pkgname, pkdir)
filter!(x -> pkg_match("IntervalArithmetic", pkgdir(last(x).module)), ambs)
for method_ambiguity ∈ ambs
@show method_ambiguity
end
@test length(ambs) == 0
# See: https://github.com/SciML/OrdinaryDiffEq.jl/issues/1750
# Test that we're not introducing method ambiguities across deps
ambs = Aqua.detect_ambiguities(IntervalArithmetic; recursive = true)
pkg_match(pkgname, pkdir::Nothing) = false
pkg_match(pkgname, pkdir::AbstractString) = occursin(pkgname, pkdir)
filter!(x -> pkg_match("IntervalArithmetic", pkgdir(last(x).module)), ambs)
for method_ambiguity ∈ ambs
@show method_ambiguity
end
@test length(ambs) == 0
end

@testset "Aqua tests (additional)" begin
Aqua.test_all(IntervalArithmetic)
end
@testset "Aqua tests (additional)" begin
Aqua.test_all(IntervalArithmetic; ambiguities = VERSION ≥ v"1.11")
end
Loading