diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 000000000..b8adf3d8e --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,48 @@ +name: CI +on: + - push + - pull_request +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.version == 'nightly' }} + strategy: + fail-fast: false + matrix: + version: + - '1.5' + - '^1.6.0-0' + - 'nightly' + os: + - ubuntu-latest + - macOS-latest + - windows-latest + arch: + - x64 + - x86 + exclude: + - os: macOS-latest + arch: x86 + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: actions/cache@v1 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v1 + with: + file: lcov.info diff --git a/.github/workflows/CompactHelper.yml b/.github/workflows/CompactHelper.yml new file mode 100644 index 000000000..f198e907f --- /dev/null +++ b/.github/workflows/CompactHelper.yml @@ -0,0 +1,15 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Pkg.add("CompatHelper") + run: julia -e 'using Pkg; Pkg.add("CompatHelper")' + - name: CompatHelper.main() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index d77d3a0c3..778c06fed 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -1,9 +1,12 @@ name: TagBot on: - schedule: - - cron: 0 * * * * + issue_comment: + types: + - created + workflow_dispatch: jobs: TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' runs-on: ubuntu-latest steps: - uses: JuliaRegistries/TagBot@v1 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cc7d779ef..000000000 --- a/.travis.yml +++ /dev/null @@ -1,39 +0,0 @@ -language: julia - -sudo: required - -services: - - docker - -os: - - linux - - osx - -julia: - - 1.4 - - 1.5 - - nightly - -notifications: - email: false - -env: - global: - - DOCUMENTER_DEBUG=true - -matrix: - allow_failures: - - julia: nightly - include: - - stage: "Documentation" - julia: 1.3 - os: linux - script: - - julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - - julia --project=docs/ docs/make.jl - after_success: skip - -after_success: - - julia -e 'import Pkg; Pkg.add("Coverage"); using Coverage; Coveralls.submit(Coveralls.process_folder())' - -sudo: false diff --git a/LICENSE.md b/LICENSE.md index 0d79efcb7..3720b8107 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ `IntervalArithmetic.jl` is licensed under the MIT "Expat" License: -> Copyright (c) 2014-2020: David P. Sanders & Luis Benet +> Copyright (c) 2014-2021: David P. Sanders & Luis Benet > > Permission is hereby granted, free of charge, to any person obtaining > a copy of this software and associated documentation files (the diff --git a/Project.toml b/Project.toml index 559477e8a..191ab359d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "IntervalArithmetic" uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" repo = "https://github.com/JuliaIntervals/IntervalArithmetic.jl.git" -version = "0.17.5" +version = "0.18.2" [deps] CRlibm = "96374032-68de-5a5b-8d9e-752f78720389" @@ -21,7 +21,7 @@ Polynomials = "0.7" RecipesBase = "1.0" RoundingEmulator = "0.2" SetRounding = "0.2" -StaticArrays = "0.8, 0.9, 0.10, 0.11, 0.12" +StaticArrays = "0.8, 0.9, 0.10, 0.11, 0.12, 1.0" julia = "1.3, 1.4, 1.5" [extras] diff --git a/README.md b/README.md index af7577197..b54f0e6d6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ # IntervalArithmetic.jl # -[![Build Status](https://travis-ci.org/JuliaIntervals/IntervalArithmetic.jl.svg?branch=master)](https://travis-ci.org/JuliaIntervals/IntervalArithmetic.jl) -[![Coverage Status](https://coveralls.io/repos/github/JuliaIntervals/IntervalArithmetic.jl/badge.svg?branch=master)](https://coveralls.io/github/JuliaIntervals/IntervalArithmetic.jl?branch=master) -[![codecov](https://codecov.io/gh/JuliaIntervals/IntervalArithmetic.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaIntervals/IntervalArithmetic.jl) +[![Build Status](https://github.com/JuliaIntervals/IntervalArithmetic.jl/workflows/CI/badge.svg)](https://github.com/JuliaIntervals/IntervalArithmetic.jl/actions/workflows/CI.yml) +[![coverage](https://codecov.io/gh/JuliaIntervals/IntervalArithmetic.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaIntervals/IntervalArithmetic.jl) -[![](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaIntervals.github.io/IntervalArithmetic.jl/stable) -[![](https://img.shields.io/badge/docs-latest-blue.svg)](https://JuliaIntervals.github.io/IntervalArithmetic.jl/latest) +[![docs](https://img.shields.io/badge/docs-stable-blue.svg)](https://juliaintervals.github.io/pages/packages/intervalarithmetic/) [![DOI](https://zenodo.org/badge/87007945.svg)](https://zenodo.org/badge/latestdoi/87007945) @@ -26,6 +24,10 @@ The aim of the package is correctness over speed, although performance considera - Nikolay Kryukov - John Verzani +## Documentation +Documentation for the package is available [here](https://juliaintervals.github.io/pages/packages/intervalarithmetic/). + +The best way to learn how to use the package is to look at the tutorial, available in the organisation webpage [here](https://juliaintervals.github.io/pages/tutorials/tutorialArithmetic/). ## Installation To install the package, from within Julia do diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index dfb83b060..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,43 +0,0 @@ -environment: - matrix: - - julia_version: 1.4 - - julia_version: 1.5 - - julia_version: nightly - -platform: - - x86 # 32-bit - - x64 # 64-bit - -# # Uncomment the following lines to allow failures on nightly julia -# # (tests will run but not make your overall status red) -matrix: - allow_failures: - - julia_version: nightly - -branches: - only: - - master - - /release-.*/ - -notifications: - - provider: Email - on_build_success: false - on_build_failure: false - on_build_status_changed: false - -install: - - ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1")) - -build_script: - - echo "%JL_BUILD_SCRIPT%" - - C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%" - -test_script: - - echo "%JL_TEST_SCRIPT%" - - C:\julia\bin\julia -e "%JL_TEST_SCRIPT%" - -# # Uncomment to support code coverage upload. Should only be enabled for packages -# # which would have coverage gaps without running on Windows -# on_success: -# - echo "%JL_CODECOV_SCRIPT%" -# - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%" diff --git a/src/IntervalArithmetic.jl b/src/IntervalArithmetic.jl index aba8b4a6d..2015e60cd 100644 --- a/src/IntervalArithmetic.jl +++ b/src/IntervalArithmetic.jl @@ -22,7 +22,7 @@ import Base: +, -, *, /, //, fma, <, >, ==, !=, ⊆, ^, <=, in, zero, one, eps, typemin, typemax, abs, abs2, real, min, max, - sqrt, exp, log, sin, cos, tan, cot, inv, cbrt, csc, hypot, sec, + sqrt, exp, log, sin, cos, tan, cot, inv, cbrt, csc, hypot, sec, exp2, exp10, log2, log10, asin, acos, atan, sinh, cosh, tanh, coth, csch, sech, asinh, acosh, atanh, sinpi, cospi, @@ -86,7 +86,7 @@ import Base: rounding, setrounding, setprecision ## Multidimensional export - IntervalBox + IntervalBox, symmetric_box ## Decorations export diff --git a/src/decorations/functions.jl b/src/decorations/functions.jl index bdf37418a..8f3e2907b 100644 --- a/src/decorations/functions.jl +++ b/src/decorations/functions.jl @@ -47,13 +47,14 @@ in(x::T, a::DecoratedInterval) where T<:Real = in(x, interval(a)) ## scalar functions: mig, mag and friends scalar_functions = ( - :mig, :mag, :inf, :sup, :mid, :diam, :radius, :dist, :eps, :midpoint_radius + :mig, :mag, :inf, :sup, :mid, :diam, :radius, :eps, :midpoint_radius ) for f in scalar_functions @eval $(f)(xx::DecoratedInterval{T}) where T = $f(interval(xx)) end +dist(xx::DecoratedInterval, yy::DecoratedInterval) = dist(interval(xx), interval(yy)) ## Arithmetic function; / is treated separately arithm_functions = ( :+, :-, :* ) diff --git a/src/decorations/intervals.jl b/src/decorations/intervals.jl index 1a0fef058..6193af832 100644 --- a/src/decorations/intervals.jl +++ b/src/decorations/intervals.jl @@ -47,7 +47,7 @@ DecoratedInterval(I::Interval{T}, d::DECORATION) where T<:AbstractFloat = DecoratedInterval{T}(I, d) function DecoratedInterval(a::T, b::T, d::DECORATION) where T<:Real - a > b && return nai(T) + is_valid_interval(a, b) || return nai(T) DecoratedInterval(Interval(a,b), d) end @@ -63,7 +63,7 @@ DecoratedInterval(a::T, b::S, d::DECORATION) where {T<:Real, S<:Real} = DecoratedInterval(I::Interval) = DecoratedInterval(I, decoration(I)) function DecoratedInterval(a::T, b::T) where T<:Real - a > b && return nai(T) + is_valid_interval(a, b) || return nai(T) DecoratedInterval(Interval(a,b)) end diff --git a/src/display.jl b/src/display.jl index 329fa9c1e..ad1fe9f60 100644 --- a/src/display.jl +++ b/src/display.jl @@ -272,6 +272,11 @@ function subscriptify(n::Integer) join( [Char(subscript_0 + i) for i in dig]) end +function superscriptify(n::Integer) + exps = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'] + dig = reverse(digits(n)) + return join([exps[d+1] for d in dig]) +end # fall-back: representation(a::Interval{T}, format=nothing) where T = @@ -317,21 +322,51 @@ function representation(a::DecoratedInterval{T}, format=nothing) where T end -function representation(X::IntervalBox, format=nothing) +function representation(X::IntervalBox{N, T}, format=nothing) where {N, T} if format == nothing format = display_params.format # default end - if display_params.format == :full - return string("IntervalBox(", join(X.v, ", "), ")") + n = format == :full ? N : superscriptify(N) + + if isempty(X) + format == :full && return string("IntervalBox(∅, ", n, ")") + return string("∅", n) + end + + x = first(X) + if all(==(x), X) + if format == :full + return string("IntervalBox(", representation(x, format), ", ", n, ")") + elseif format == :midpoint + return string("(", representation(x, format), ")", n) + else + return string(representation(x, format), n) + end + end + if format == :full + full_str = representation.(X.v, :full) + return string("IntervalBox(", join(full_str, ", "), ")") + elseif format == :midpoint + return string("(", join(X.v, ") × ("), ")") else return join(X.v, " × ") end end +function representation(x::Complex{<:Interval}, format=nothing) + + if format == nothing + format = display_params.format + end + + format == :midpoint && return string('(', x.re, ')', " + ", '(', x.im, ')', "im") + + return string(x.re, " + ", x.im, "im") +end for T in (Interval, DecoratedInterval) @eval show(io::IO, a::$T{S}) where S = print(io, representation(a)) @@ -339,7 +374,9 @@ for T in (Interval, DecoratedInterval) @eval showfull(a::$T{S}) where S = showfull(stdout, a) end -T = IntervalBox -@eval show(io::IO, a::$T) = print(io, representation(a)) -@eval show(io::IO, ::MIME"text/plain", a::$T) = print(io, representation(a)) -@eval showfull(io::IO, a::$T) = print(io, representation(a, :full)) +for T in (IntervalBox, Complex{<:Interval}) + @eval show(io::IO, a::$T) = print(io, representation(a)) + @eval show(io::IO, ::MIME"text/plain", a::$T) = print(io, representation(a)) + @eval showfull(io::IO, a::$T) = print(io, representation(a, :full)) + @eval showfull(a::$T) = showfull(stdout, a) +end diff --git a/src/intervals/arithmetic.jl b/src/intervals/arithmetic.jl index fe93cba77..0041def0e 100644 --- a/src/intervals/arithmetic.jl +++ b/src/intervals/arithmetic.jl @@ -575,7 +575,7 @@ convert(::Type{Integer}, a::Interval) = isinteger(a) ? Splits `x` in `n` intervals of the same diameter, which are returned as a vector. """ -function mince(x::Interval, n) +@inline function mince(x::Interval, n) nodes = range(x.lo, x.hi, length = n+1) - return [Interval(nodes[i], nodes[i+1]) for i in 1:length(nodes)-1] + return Interval.(nodes[1:n], nodes[2:n+1]) end diff --git a/src/intervals/conversion.jl b/src/intervals/conversion.jl index 464966499..79d948f4f 100644 --- a/src/intervals/conversion.jl +++ b/src/intervals/conversion.jl @@ -150,3 +150,6 @@ atomic(::Type{Interval{Rational{T}}}, x::S) where {T<:Integer, S<:Float64} = atomic(::Type{Interval{Rational{T}}}, x::S) where {T<:Integer, S<:BigFloat} = Interval(rationalize(T, x)) + +atomic(::Type{Interval{Rational{T}}}, x::Interval{Rational{T}}) where {T<:Integer} = + Interval(x) diff --git a/src/intervals/intervals.jl b/src/intervals/intervals.jl index c45dca2d6..f9fb2d9c2 100644 --- a/src/intervals/intervals.jl +++ b/src/intervals/intervals.jl @@ -25,7 +25,8 @@ struct Interval{T<:Real} <: AbstractInterval{T} if is_valid_interval(a, b) new(a, b) else - throw(ArgumentError("Interval of form [$a, $b] not allowed. Must have a ≤ b to construct interval(a, b).")) + @warn "Invalid input, empty interval is returned" + return new(T(Inf), T(-Inf)) end end new(a, b) @@ -84,20 +85,10 @@ function is_valid_interval(a::Real, b::Real) # println("isvalid()") if isnan(a) || isnan(b) - if isnan(a) && isnan(b) - return true - else - return false - end + return false end - if a > b - if isinf(a) && isinf(b) - return true # empty interval = [∞,-∞] - else - return false - end - end + a > b && return false if a == Inf || b == -Inf return false @@ -109,11 +100,12 @@ end """ interval(a, b) -`interval(a, b)` checks whether [a, b] is a valid `Interval`, which is the case if `-∞ <= a <= b <= ∞`, using the (non-exported) `is_valid_interval` function. If so, then an `Interval(a, b)` object is returned; if not, then an error is thrown. +`interval(a, b)` checks whether [a, b] is a valid `Interval`, using the (non-exported) `is_valid_interval` function. If so, then an `Interval(a, b)` object is returned; if not, a warning is printed and the empty interval is returned. """ -function interval(a::Real, b::Real) +function interval(a::T, b::S) where {T<:Real, S<:Real} if !is_valid_interval(a, b) - throw(ArgumentError("`[$a, $b]` is not a valid interval. Need `a ≤ b` to construct `interval(a, b)`.")) + @warn "Invalid input, empty interval is returned" + return emptyinterval(promote_type(T, S)) end return Interval(a, b) @@ -146,21 +138,33 @@ include("complex.jl") # Syntax for intervals function ..(a::T, b::S) where {T, S} - interval(atomic(Interval{T}, a).lo, atomic(Interval{S}, b).hi) + if !is_valid_interval(a, b) + @warn "Invalid input, empty interval is returned" + return emptyinterval(promote_type(T, S)) + end + Interval(atomic(Interval{T}, a).lo, atomic(Interval{S}, b).hi) end function ..(a::T, b::Irrational{S}) where {T, S} + if !is_valid_interval(a, b) + @warn "Invalid input, empty interval is returned" + return emptyinterval(promote_type(T, Irrational{S})) + end R = promote_type(T, Irrational{S}) - interval(atomic(Interval{R}, a).lo, R(b, RoundUp)) + Interval(atomic(Interval{R}, a).lo, R(b, RoundUp)) end function ..(a::Irrational{T}, b::S) where {T, S} + if !is_valid_interval(a, b) + @warn "Invalid input, empty interval is returned" + return emptyinterval(promote_type(Irrational{T}, S)) + end R = promote_type(Irrational{T}, S) return Interval(R(a, RoundDown), atomic(Interval{R}, b).hi) end function ..(a::Irrational{T}, b::Irrational{S}) where {T, S} - return Interval(a, b) + return interval(a, b) end # ..(a::Integer, b::Integer) = interval(a, b) diff --git a/src/intervals/macros.jl b/src/intervals/macros.jl index 2190443e1..1340003d8 100644 --- a/src/intervals/macros.jl +++ b/src/intervals/macros.jl @@ -1,20 +1,21 @@ # This file is part of the IntervalArithmetic.jl package; MIT licensed -"""The `@interval` macro is the main method to create an interval. -It converts each expression into a narrow interval that is guaranteed to contain the true value passed by the user in the one or two expressions passed to it. +"""The `@interval` macro converts an expression into a narrow interval that is guaranteed to contain the true value of the expression. When passed two expressions, it takes the hull of the resulting intervals to give a guaranteed containing interval. Examples: ``` - @interval(0.1) - - @interval(0.1, 0.2) - - @interval(1/3, 1/6) + @interval sin(0.1) + cos(0.2) +``` - @interval(1/3^2) +is equivalent to ``` + sin(0.1..0.1) + cos(0.2..0.2) +``` + +NOTE! `@interval` should be used only to create intervals from an expression, as in the example +before. To construct an interval from single numbers, the `..` is preferred, e.g. `0.1..0.2` """ macro interval(expr1) make_interval(:(parameters.precision_type), expr1, ()) diff --git a/src/intervals/special.jl b/src/intervals/special.jl index bd17698d5..56d182f9d 100644 --- a/src/intervals/special.jl +++ b/src/intervals/special.jl @@ -9,6 +9,7 @@ larger than the upper one.""" emptyinterval(::Type{T}) where T<:Real = Interval{T}(Inf, -Inf) emptyinterval(x::Interval{T}) where T<:Real = emptyinterval(T) emptyinterval() = emptyinterval(precision(Interval)[1]) +emptyinterval(::Type{<:Integer}) = emptyinterval(Float64) const ∅ = emptyinterval(Float64) isempty(x::Interval) = x.lo == Inf && x.hi == -Inf @@ -20,6 +21,7 @@ const ∞ = Inf entireinterval(::Type{T}) where T<:Real = Interval{T}(-Inf, Inf) entireinterval(x::Interval{T}) where T<:Real = entireinterval(T) entireinterval() = entireinterval(precision(Interval)[1]) +entireinterval(::Type{<:Integer}) = entireinterval(Float64) isentire(x::Interval) = x.lo == -Inf && x.hi == Inf isinf(x::Interval) = isentire(x) diff --git a/src/multidim/intervalbox.jl b/src/multidim/intervalbox.jl index 9c1d26b0e..d154f7f8a 100644 --- a/src/multidim/intervalbox.jl +++ b/src/multidim/intervalbox.jl @@ -115,28 +115,48 @@ Base.:(==)(x::IntervalBox, y::IntervalBox) = x.v == y.v """ - mince(x::IntervalBox, n) + mince(x::IntervalBox, n::Int) Splits `x` in `n` intervals in each dimension of the same diameter. These intervals are combined in all possible `IntervalBox`-es, which are returned as a vector. """ -@generated function mince(x::IntervalBox{N,T}, n) where {N,T} - quote - nodes_matrix = Array{Interval{T},2}(undef, n, N) - for i in 1:N - nodes_matrix[1:n,i] .= mince(x[i], n) - end - - nodes = IntervalBox{$N,T}[] - Base.Cartesian.@nloops $N i _->(1:n) begin - Base.Cartesian.@nextract $N ival d->nodes_matrix[i_d, d] - ibox = Base.Cartesian.@ncall $N IntervalBox ival - push!(nodes, ibox) - end - nodes +@inline mince(x::IntervalBox{N,T}, n::Int) where {N,T} = + mince(x, ntuple(_ -> n, N)) + +""" + mince(x::IntervalBox, ncuts::::NTuple{N,Int}) + +Splits `x[i]` in `ncuts[i]` intervals . These intervals are +combined in all possible `IntervalBox`-es, which are returned +as a vector. +""" +@inline function mince(x::IntervalBox{N,T}, ncuts::NTuple{N,Int}) where {N,T} + minced_intervals = [mince(x[i], ncuts[i]) for i in 1:N] + minced_boxes = Vector{IntervalBox{N,T}}(undef, prod(ncuts)) + + for (k, cut_indices) in enumerate(CartesianIndices(ncuts)) + minced_boxes[k] = IntervalBox([minced_intervals[i][cut_indices[i]] for i in 1:N]) end + return minced_boxes end + hull(a::IntervalBox{N,T}, b::IntervalBox{N,T}) where {N,T} = IntervalBox(hull.(a[:], b[:])) hull(a::Vector{IntervalBox{N,T}}) where {N,T} = hull(a...) + +""" + zero(IntervalBox{N, T}) + +Return the zero interval box of dimension `N` in the numeric type `T`. +""" +zero(::Type{IntervalBox{N, T}}) where {N, T} = IntervalBox(zero(Interval{T}), N) +zero(x::IntervalBox{N, T}) where {N, T} = zero(typeof(x)) + +""" + symmetric_box(N, T) + +Return the symmetric interval box of dimension `N` in the numeric type `T`, +each side is `Interval(-1, 1)`. +""" +symmetric_box(N, ::Type{T}) where T<:Real = IntervalBox(Interval{T}(-1, 1), N) diff --git a/src/multidim/setdiff.jl b/src/multidim/setdiff.jl index cba7b7652..553edbba0 100644 --- a/src/multidim/setdiff.jl +++ b/src/multidim/setdiff.jl @@ -1,38 +1,23 @@ """ -Returns a list of pairs (interval, label) -label is 1 if the interval is *excluded* from the setdiff -label is 0 if the interval is included in the setdiff -label is -1 if the intersection of the two intervals was empty + _setdiff(x::Interval{T}, y::Interval{T}) + +Computes the set difference x\\y and always returns a tuple of two intervals. +If the set difference is only one interval or is empty, then the returned tuple contains 1 +or 2 empty intervals. """ -function labelled_setdiff(x::Interval{T}, y::Interval{T}) where T +function _setdiff(x::Interval{T}, y::Interval{T}) where T intersection = x ∩ y - isempty(intersection) && return [(x, -1)] - intersection == x && return [(x, 1)] + isempty(intersection) && return (x, emptyinterval(T)) + intersection == x && return (emptyinterval(T), emptyinterval(T)) # x is subset of y; setdiff is empty - x.lo == intersection.lo && return [(intersection, 1), (Interval(intersection.hi, x.hi), 0)] - x.hi == intersection.hi && return [(intersection, 1), (Interval(x.lo, intersection.lo), 0)] + x.lo == intersection.lo && return (Interval(intersection.hi, x.hi), emptyinterval(T)) + x.hi == intersection.hi && return (Interval(x.lo, intersection.lo), emptyinterval(T)) - return [(y, 1), - (Interval(x.lo, y.lo), 0), - (Interval(y.hi, x.hi), 0)] + return (Interval(x.lo, y.lo), Interval(y.hi, x.hi)) end -# function setdiff{N,T}(A::IntervalBox{N,T}, B::IntervalBox{N,T}) -# X = [labelled_setdiff(a,b) for (a, b) in zip(A, B)] -# lengths = map(length, X) -# index = ones(N) -# -# # index[j] represents which set we are looking at in direction j -#`` -# -# while index[1] <= N -# current_direction = 1 -# current_piece = [ X[1][index[1]] ] -# -# end -# end """ setdiff(A::IntervalBox{N,T}, B::IntervalBox{N,T}) @@ -44,65 +29,21 @@ Algorithm: Start from the total overlap (in all directions); expand each direction in turn. """ function setdiff(A::IntervalBox{N,T}, B::IntervalBox{N,T}) where {N,T} - X = [labelled_setdiff(a, b) for (a, b) in zip(A.v, B.v)] - # ordered such that the first in each is the excluded interval - - first = [ i[1] for i in X ] - labels = [i[2] for i in first] - - any(labels .== -1) && return [A] # no overlap - - # @assert all(labels .== 1) - - excluded = [i[1] for i in first] - result_list = IntervalBox{N,T}[] - - for dimension in N:-1:1 - for which in X[dimension][2:end] - excluded[dimension] = which[1] - push!(result_list, - IntervalBox(excluded[1:dimension]..., A[dimension+1:N]...)) + intersection = A ∩ B + isempty(intersection) && return [A] + + result_list = fill(IntervalBox(emptyinterval(T), N), 2*N) + offset = 0 + x = A.v + @inbounds for i = 1:N + tmp = _setdiff(A[i], B[i]) + @inbounds for j = 1:2 + x = setindex(x, tmp[j], i) + result_list[offset+j] = IntervalBox{N, T}(x) end + offset += 2 + x = setindex(x, intersection[i], i) end - - result_list - + filter!(!isempty, result_list) end - - -# """ -# setdiff(A::IntervalBox{2,T}, B::IntervalBox{2,T}) -# -# Returns a vector of `IntervalBox`es that are in the set difference `A \ B`, -# i.e. the set of `x` that are in `A` but not in `B`. -# """ -# function setdiff{T}(A::IntervalBox{2,T}, B::IntervalBox{2,T}) -# X = labelled_setdiff(A[1], B[1]) -# Y = labelled_setdiff(A[2], B[2]) -# -# results_list = typeof(A)[] -# -# for (x, label) in X -# label == -1 && return [A] # intersection in one direction empty, so total intersection empty -# -# if label == 0 -# push!(results_list, IntervalBox(x, A[2])) -# continue -# end -# -# # label is 1 here, so there is some intersection in the x direction -# for (y, label) in Y -# label == -1 && return [A] -# -# if label == 0 -# push!(results_list, IntervalBox(x, y)) -# continue -# end -# -# # label == 1: exclude this box since all labels are 1 -# end -# end -# -# return results_list -# end diff --git a/test/decoration_tests/decoration_tests.jl b/test/decoration_tests/decoration_tests.jl index 521d0e5c9..f9b876ec5 100644 --- a/test/decoration_tests/decoration_tests.jl +++ b/test/decoration_tests/decoration_tests.jl @@ -49,6 +49,20 @@ let b @test @decorated(-3, 2)^@decorated(0.0, 1.0) == DecoratedInterval(0.0,2.0, trv) @test @decorated(-3, 2)^Interval(-1.0, 1.0) == DecoratedInterval(0.0,Inf, trv) @test @decorated(-3, 2)^@decorated(-1.0, 1.0) == DecoratedInterval(0.0, Inf, trv) + + a = @decorated 1 2 + b = @decorated 3 4 + + @test dist(a, b) == 2.0 + + # invalid input + @test isnai(@decorated(3, 1, com)) + @test isnai(@decorated(3, 1)) + @test isnai(@decorated(Inf, Inf)) + @test isnai(@decorated(-Inf, -Inf)) + @test isnai(@decorated(NaN, 3)) + @test isnai(@decorated(3, NaN)) + @test isnai(@decorated(NaN, NaN)) end @testset "Convert string to DecoratedInterval" begin diff --git a/test/display_tests/display.jl b/test/display_tests/display.jl index 52fd19c86..9c31cf00d 100644 --- a/test/display_tests/display.jl +++ b/test/display_tests/display.jl @@ -85,6 +85,15 @@ setprecision(Interval, Float64) @test string(a) == "1.5f0 ± 0.5f0" end + @testset "Complex{Interval}" begin + a = Complex(Interval(0, 2), 1) + @test typeof(a) == Complex{Interval{Float64}} + setformat(:standard) + @test string(a) == "[0, 2] + [1, 1]im" + + setformat(:midpoint) + @test string(a) == "(1 ± 1) + (1 ± 0)im" + end setprecision(Interval, 256) @@ -163,11 +172,31 @@ setprecision(Interval, Float64) @test string(X) == "[1.09999, 1.20001] × [2.09999, 2.20001]" X = IntervalBox(-Inf..Inf, -Inf..Inf) - @test string(X) == "[-∞, ∞] × [-∞, ∞]" + @test string(X) == "[-∞, ∞]²" + + setformat(:full) + @test string(X) == "IntervalBox(Interval(-Inf, Inf), 2)" + + + setformat(:standard) + a = IntervalBox(1..2, 2..3) + @test string(a) == "[1, 2] × [2, 3]" + + b = IntervalBox(emptyinterval(), 2) + @test string(b) == "∅²" + + c = IntervalBox(1..2, 1) + @test string(c) == "[1, 2]¹" setformat(:full) - @test string(X) == "IntervalBox(Interval(-Inf, Inf), Interval(-Inf, Inf))" + @test string(a) == "IntervalBox(Interval(1.0, 2.0), Interval(2.0, 3.0))" + @test string(b) == "IntervalBox(∅, 2)" + @test string(c) == "IntervalBox(Interval(1.0, 2.0), 1)" + setformat(:midpoint) + @test string(a) == "(1.5 ± 0.5) × (2.5 ± 0.5)" + @test string(b) == "∅²" + @test string(c) == "(1.5 ± 0.5)¹" end end @@ -194,6 +223,14 @@ end setformat(decorations=true) @test string(x) == "[0, 1]₁₂₈_def" + a = IntervalBox(1..2, 2..3) + b = IntervalBox(emptyinterval(), 2) + c = IntervalBox(1..2, 1) + + @test sprint(showfull, a) == "IntervalBox(Interval(1.0, 2.0), Interval(2.0, 3.0))" + @test sprint(showfull, b) == "IntervalBox(∅, 2)" + @test sprint(showfull, c) == "IntervalBox(Interval(1.0, 2.0), 1)" + end @testset "@format tests" begin diff --git a/test/interval_tests/consistency.jl b/test/interval_tests/consistency.jl index ec4411adb..066052b76 100644 --- a/test/interval_tests/consistency.jl +++ b/test/interval_tests/consistency.jl @@ -39,7 +39,9 @@ setprecision(Interval, Float64) @test @biginterval(1, Inf) == Interval{BigFloat}(1.0, Inf) @test @biginterval(-Inf, 1) == Interval{BigFloat}(-Inf, 1.0) @test @interval(-Inf, Inf) == entireinterval(Float64) + @test entireinterval(Int) == entireinterval(Float64) @test emptyinterval(Rational{Int}) == ∅ + @test emptyinterval(Int) == ∅ @test 1 == zero(a)+one(b) @test Interval(0,1) + emptyinterval(a) == emptyinterval(a) @@ -380,10 +382,10 @@ setprecision(Interval, Float64) @test interval(1, 2) == Interval(1, 2) @test inf(Interval(3, 2)) == 3 - @test_throws ArgumentError interval(3, 2) + @test_logs (:warn,) @test isempty(interval(3, 2)) @test sup(Interval(Inf, Inf)) == Inf - @test_throws ArgumentError interval(Inf, Inf) + @test_logs (:warn,) @test isempty(interval(Inf, Inf)) end diff --git a/test/interval_tests/construction.jl b/test/interval_tests/construction.jl index e3e229bb9..db21c51b4 100644 --- a/test/interval_tests/construction.jl +++ b/test/interval_tests/construction.jl @@ -69,20 +69,20 @@ const eeuler = Base.MathConstants.e # Disallowed conversions with a > b - @test_throws ArgumentError interval(2, 1) - @test_throws ArgumentError interval(big(2), big(1)) - @test_throws ArgumentError interval(BigInt(1), 1//10) - @test_throws ArgumentError interval(1, 0.1) - @test_throws ArgumentError interval(big(1), big(0.1)) - - @test_throws ArgumentError @interval(2, 1) - @test_throws ArgumentError @interval(big(2), big(1)) - @test_throws ArgumentError @interval(big(1), 1//10) - @test_throws ArgumentError @interval(1, 0.1) - @test_throws ArgumentError @interval(big(1), big(0.1)) - @test_throws ArgumentError interval(Inf) - @test_throws ArgumentError interval(-Inf, -Inf) - @test_throws ArgumentError interval(Inf, Inf) + @test_logs (:warn,) @test isempty(interval(2, 1)) + @test_logs (:warn,) @test isempty(interval(big(2), big(1))) + @test_logs (:warn,) @test isempty(interval(BigInt(1), 1//10)) + @test_logs (:warn,) @test isempty(interval(1, 0.1)) + @test_logs (:warn,) @test isempty(interval(big(1), big(0.1))) + + @test_logs (:warn,) @test isempty(@interval(2, 1)) + @test_logs (:warn,) @test isempty(@interval(big(2), big(1))) + @test_logs (:warn,) @test isempty(@interval(big(1), 1//10)) + @test_logs (:warn,) @test isempty(@interval(1, 0.1)) + @test_logs (:warn,) @test isempty(@interval(big(1), big(0.1))) + @test_logs (:warn,) @test isempty(interval(Inf)) + @test_logs (:warn,) @test isempty(interval(-Inf, -Inf)) + @test_logs (:warn,) @test isempty(interval(Inf, Inf)) # Disallowed creation of interval witha a or b an interval @test_throws ArgumentError Interval(1, 1..2) @@ -241,6 +241,14 @@ end a = big(0.1)..2 @test typeof(a) == Interval{BigFloat} + + @test_logs (:warn, ) @test isempty(2..1) + @test_logs (:warn, ) @test isempty(π..1) + @test_logs (:warn, ) @test isempty(π..eeuler) + @test_logs (:warn, ) @test isempty(4..π) + @test_logs (:warn, ) @test isempty(NaN..3) + @test_logs (:warn, ) @test isempty(3..NaN) + @test 1..π == Interval(1, π) end @testset "± tests" begin @@ -291,9 +299,10 @@ end end # issue 192: -@testset "Disallow a single NaN in an interval" begin - @test_throws ArgumentError interval(NaN, 2) - @test_throws ArgumentError interval(Inf, NaN) +@testset "Disallow NaN in an interval" begin + @test_logs (:warn, ) @test isempty(interval(NaN, 2)) + @test_logs (:warn, ) @test isempty(interval(Inf, NaN)) + @test_logs (:warn, ) @test isempty(interval(NaN, NaN)) end # issue 206: @@ -341,7 +350,7 @@ end end @testset "a..b with a > b" begin - @test_throws ArgumentError 3..2 + @test_logs (:warn,) @test isempty(3..2) end @testset "Hashing of Intervals" begin @@ -373,5 +382,10 @@ import IntervalArithmetic: force_interval @test force_interval(4, Inf) == Interval(4, Inf) @test force_interval(Inf, 4) == Interval(4, Inf) @test force_interval(Inf, -Inf) == Interval(-Inf, Inf) - @test_throws ArgumentError force_interval(NaN, 3) + @test_logs (:warn,) @test isempty(force_interval(NaN, 3)) +end + +@testset "Zero interval" begin + @test zero(Interval{Float64}) === Interval{Float64}(0, 0) + @test zero(0 .. 1) === Interval{Float64}(0, 0) end diff --git a/test/interval_tests/numeric.jl b/test/interval_tests/numeric.jl index 93bd83ae7..da7fe2558 100644 --- a/test/interval_tests/numeric.jl +++ b/test/interval_tests/numeric.jl @@ -11,7 +11,7 @@ setprecision(Interval, Float64) y = 4..5 a = 3 b = 12 - + @test sqrt(sum(x.^2 .+ y.^2)) == 5..13 for i in 1:20 @@ -24,7 +24,7 @@ setprecision(Interval, Float64) end a = 4 - b = 5 + b = 5 for i in 1:20 @test y.+i == (a+i)..(b+i) end @@ -115,7 +115,7 @@ end @test Interval(1,2) ^ -3 == Interval(1/8, 1.0) @test Interval(0,3) ^ -3 == @interval(1/27, Inf) @test Interval(-1,2) ^ -3 == entireinterval() - @test_throws ArgumentError interval(-1, -2) ^ -3 # wrong way round + @test_logs (:warn, ) @test isempty(interval(-1, -2) ^ -3) @test Interval(-3,2) ^ (3//1) == Interval(-27, 8) @test Interval(0.0) ^ 1.1 == Interval(0, 0) @test Interval(0.0) ^ 0.0 == emptyinterval() @@ -208,6 +208,7 @@ end g = 1 // 3 @test @interval(f*g) == Interval(1.1111111111111109e-01, 1.1111111111111115e-01) + @test interval(f, g) - 1 == interval(-2 // 3, -2 // 3) @test big(1.)/9 ∈ @interval(f*g) @test @interval(1)/9 ⊆ @interval(f*g) @test @interval(1)/9 ≠ @interval(f*g) @@ -431,4 +432,4 @@ end @test nthroot(Interval{BigFloat}(-27, 27), -3) == Interval{BigFloat}(-Inf, Inf) @test nthroot(Interval{BigFloat}(-81, -16), -4) == ∅ @test nthroot(Interval{BigFloat}(-81, -16), 1) == Interval{BigFloat}(-81, -16) -end +end diff --git a/test/multidim_tests/multidim.jl b/test/multidim_tests/multidim.jl index ce7539b1a..dfe9ce775 100644 --- a/test/multidim_tests/multidim.jl +++ b/test/multidim_tests/multidim.jl @@ -108,34 +108,34 @@ end @testset "setdiff for IntervalBox" begin X = IntervalBox(2..4, 3..5) Y = IntervalBox(3..5, 4..6) - @test setdiff(X, Y) == [ IntervalBox(3..4, 3..4), - IntervalBox(2..3, 3..5) ] + @test Set(setdiff(X, Y)) == Set([ IntervalBox(3..4, 3..4), + IntervalBox(2..3, 3..5) ]) - @test setdiff(X.v, Y) == [ IntervalBox(3..4, 3..4), - IntervalBox(2..3, 3..5) ] + @test Set(setdiff(X.v, Y)) == Set([ IntervalBox(3..4, 3..4), + IntervalBox(2..3, 3..5) ]) - @test setdiff(X, Y.v) == [ IntervalBox(3..4, 3..4), - IntervalBox(2..3, 3..5) ] + @test Set(setdiff(X, Y.v)) == Set([ IntervalBox(3..4, 3..4), + IntervalBox(2..3, 3..5) ]) X = IntervalBox(2..5, 3..6) Y = IntervalBox(-10..10, 4..5) - @test setdiff(X, Y) == [ IntervalBox(2..5, 3..4), - IntervalBox(2..5, 5..6) ] + @test Set(setdiff(X, Y)) == Set([ IntervalBox(2..5, 3..4), + IntervalBox(2..5, 5..6) ]) X = IntervalBox(2..5, 3..6) Y = IntervalBox(4..6, 4..5) - @test setdiff(X, Y) == [ IntervalBox(4..5, 3..4), + @test Set(setdiff(X, Y)) == Set([ IntervalBox(4..5, 3..4), IntervalBox(4..5, 5..6), - IntervalBox(2..4, 3..6) ] + IntervalBox(2..4, 3..6) ]) X = IntervalBox(2..5, 3..6) Y = IntervalBox(3..4, 4..5) - @test setdiff(X, Y) == [ IntervalBox(3..4, 3..4), + @test Set(setdiff(X, Y)) == Set([ IntervalBox(3..4, 3..4), IntervalBox(3..4, 5..6), IntervalBox(2..3, 3..6), - IntervalBox(4..5, 3..6) ] + IntervalBox(4..5, 3..6) ]) X = IntervalBox(2..5, 3..6) @@ -150,14 +150,20 @@ end X = IntervalBox(1..4, 3..6, 7..10) Y = IntervalBox(2..3, 4..5, 8..9) - @test setdiff(X, Y) == [ IntervalBox(2..3, 4..5, 7..8), + @test Set(setdiff(X, Y)) == Set([ IntervalBox(2..3, 4..5, 7..8), IntervalBox(2..3, 4..5, 9..10), IntervalBox(2..3, 3..4, 7..10), IntervalBox(2..3, 5..6, 7..10), IntervalBox(1..2, 3..6, 7..10), - IntervalBox(3..4, 3..6, 7..10) ] + IntervalBox(3..4, 3..6, 7..10) ]) + X = IntervalBox(-Inf..Inf, 1..2) + Y = IntervalBox(1..2, -1..1.5) + + @test Set(setdiff(X, Y)) == Set([IntervalBox(-Inf..1, 1..2), + IntervalBox(2..Inf, 1..2), + IntervalBox(1..2, 1.5..2)]) end @testset "mid, diam, × for IntervalBox" begin @@ -300,18 +306,34 @@ end @test vb2 == vv @test hull(vb2...) == ib2 @test hull(vb2) == ib2 + @test mince(ib2, (4,4)) == vb2 + @test mince(ib2, (1,4)) == [ (-1 .. 1)×(-1 .. -0.5), (-1 .. 1)×(-0.5 .. 0), + (-1 .. 1)×(0 .. 0.5), (-1 .. 1)×(0.5 .. 1)] + @test hull(mince(ib2, (1,4))) == ib2 ib3 = IntervalBox(-1..1, 3) vb3 = mince(ib3, 4) @test length(vb3) == 4^3 @test hull(vb3...) == ib3 @test hull(vb3) == ib3 + @test mince(ib3, (4,4,4)) == vb3 + @test mince(ib3, (2,1,1)) == [(-1 .. 0)×(-1 .. 1)×(-1 .. 1), + (0 .. 1)×(-1 .. 1)×(-1 .. 1)] + @test hull(mince(ib3, (2,1,1))) == ib3 ib4 = IntervalBox(-1..1, 4) vb4 = mince(ib4, 4) @test length(vb4) == 4^4 @test hull(vb4...) == ib4 @test hull(vb4) == ib4 + @test mince(ib4,(4,4,4,4)) == vb4 + @test mince(ib4,(1,1,1,1)) == [ib4] +end + +@testset "Special box constructors" begin + @test zero(IntervalBox{2, Float64}) === IntervalBox(0 .. 0, 2) + @test zero((0..1) × (0..1)) === IntervalBox(0 .. 0, 2) + @test symmetric_box(2, Float64) === IntervalBox(-1 .. 1, 2) end end