Skip to content
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

Improve alignment display #676

Merged
merged 4 commits into from
Sep 3, 2024
Merged
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
3 changes: 3 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ RoundingEmulator = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705"
[weakdeps]
DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01"

[extensions]
IntervalArithmeticDiffRulesExt = "DiffRules"
IntervalArithmeticForwardDiffExt = "ForwardDiff"
IntervalArithmeticsIntervalSetsExt = "IntervalSets"
IntervalArithmeticRecipesBaseExt = "RecipesBase"

[compat]
CRlibm_jll = "1"
DiffRules = "1"
ForwardDiff = "0.10"
IntervalSets = "0.7"
MacroTools = "0.5"
RecipesBase = "1"
RoundingEmulator = "0.2"
Expand Down
23 changes: 23 additions & 0 deletions ext/IntervalArithmeticsIntervalSetsExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module IntervalArithmeticsIntervalSetsExt

import IntervalSets as IS
import IntervalArithmetic as IA

IA.interval(i::IS.Interval{L,R,T}) where {L,R,T} = IA.interval(IA.promote_numtype(T, T), i)
function IA.interval(::Type{T}, i::IS.Interval{L,R}) where {T<:IA.NumTypes,L,R}
# infinite endpoints are always open in IA, finite always closed:
isinf(IS.leftendpoint(i)) != IS.isleftopen(i) && return IA.nai(T)
isinf(IS.rightendpoint(i)) != IS.isrightopen(i) && return IA.nai(T)
x = IA.interval(T, IS.endpoints(i)...)
return IA._unsafe_interval(IA.bareinterval(x), IA.decoration(x), false)
end

function IS.Interval(i::IA.Interval)
lo, hi = IA.bounds(i)
# infinite endpoints are always open in IA, finite always closed:
L = ifelse(isinf(lo), :open, :closed)
R = ifelse(isinf(hi), :open, :closed)
return IS.Interval{L,R}(lo, hi)
end

end
226 changes: 123 additions & 103 deletions src/display.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,181 +111,201 @@ Base.show(io::IO, ::MIME"text/plain", a::Union{BareInterval,Interval,Complex{<:I
function _str_repr(a::BareInterval{T}, format::Symbol) where {T<:NumTypes}
# `format` is either `:infsup`, `:midpoint` or `:full`
str_interval = _str_basic_repr(a, format)
format === :full && return string("BareInterval{", T, "}(", str_interval, ')')
return str_interval
end

function _str_repr(a::BareInterval{BigFloat}, format::Symbol)
# `format` is either `:infsup`, `:midpoint` or `:full`
str_interval = _str_basic_repr(a, format)
format === :full && return string("BareInterval{BigFloat}(", str_interval, ')')
if format === :midpoint && str_interval != "∅"
str_interval = string('(', str_interval, ')')
end
return string(str_interval, _str_precision(a))
((format === :full) & (str_interval != "∅")) && return string("BareInterval{", T, "}(", str_interval, ')')
return _str_precision(str_interval, a, format)
end

function _str_repr(a::Interval{T}, format::Symbol) where {T<:NumTypes}
# `format` is either `:infsup`, `:midpoint` or `:full`
str_interval = _str_basic_repr(a.bareinterval, format) # use `a.bareinterval` to not print a warning if `a` is an NaI
if isguaranteed(a) || !display_options.ng_flag
format === :full && return string("Interval{", T, "}(", str_interval, ", ", decoration(a), ')')
display_options.decorations || return str_interval
if format === :midpoint && str_interval != "∅"
str_interval = string('(', str_interval, ')')
end
return string(str_interval, '_', decoration(a))
if format === :full && str_interval != "∅"
str_interval = string("Interval{", T, "}(", str_interval, ", ", decoration(a), ')')
else
format === :full && return string("Interval{", T, "}(", str_interval, ", ", decoration(a), ", NG)")
if format === :midpoint && str_interval != "∅"
str_interval = _str_precision(str_interval, a, format)
if format === :midpoint && str_interval != "∅" && T !== BigFloat && (display_options.decorations | (display_options.ng_flag & !isguaranteed(a)))
str_interval = string('(', str_interval, ')')
end
display_options.decorations || return string(str_interval, "_NG")
return string(str_interval, '_', decoration(a), "_NG")
str_interval = ifelse(display_options.decorations, string(str_interval, '_', decoration(a)), str_interval)
end
return ifelse(display_options.ng_flag & !isguaranteed(a), string(str_interval, "_NG"), str_interval)
end

function _str_repr(a::Interval{BigFloat}, format::Symbol)
function _str_repr(x::Complex{Interval{T}}, format::Symbol) where {T<:NumTypes}
# `format` is either `:infsup`, `:midpoint` or `:full`
str_interval = _str_basic_repr(a.bareinterval, format) # use `a.bareinterval` to not print a warning if `a` is an NaI
if isguaranteed(a) || !display_options.ng_flag
format === :full && return string("Interval{BigFloat}(", str_interval, ", ", decoration(a), ')')
if format === :midpoint && str_interval != "∅"
str_interval = string('(', str_interval, ')')
str_imag = _str_repr(imag(x), format)
if format === :full
if !isthinzero(imag(x)) && sup(imag(x)) ≤ 0 && !isempty_interval(imag(x))
c1, c2 = split(str_imag, ", "; limit = 2)
l = findfirst('-', c1)
ll = ifelse(occursin(',', c2), findfirst(',', c2), findfirst(')', c2))
return string(_str_repr(real(x), format), " - im*", view(c1, 1:l-1), view(c2, 1+!iszero(sup(imag(x))):ll-1), ", ", view(c1, l+1:lastindex(c1)), view(c2, ll:lastindex(c2)))
else
return string(_str_repr(real(x), format), " + im*", str_imag)
end
display_options.decorations || return string(str_interval, _str_precision(a))
return string(str_interval, _str_precision(a), '_', decoration(a))
else
format === :full && return string("Interval{", BigFloat, "}(", str_interval, ", ", decoration(a), ", NG)")
if format === :midpoint && str_interval != "∅"
str_interval = string('(', str_interval, ')')
elseif format === :midpoint
str_real = _str_repr(real(x), format)
if !startswith(str_real, '(')
str_real = string('(', str_real, ')')
end
display_options.decorations || return string(str_interval, _str_precision(a), "_NG")
return string(str_interval, _str_precision(a), '_', decoration(a), "_NG")
end
end

function _str_repr(x::Complex{<:Interval}, format::Symbol)
# `format` is either `:infsup`, `:midpoint` or `:full`
if format === :full
return string(_str_repr(real(x), format), " + ", _str_repr(imag(x), format), "im")
elseif format === :infsup
((!isguaranteed(x) & display_options.ng_flag) | display_options.decorations) && return string(_str_repr(real(x), format), " + (", _str_repr(imag(x), format), ")im")
return string(_str_repr(real(x), format), " + ", _str_repr(imag(x), format), "im")
if !startswith(str_imag, '(')
str_imag = string('(', str_imag, ')')
end
startswith(str_imag, "(-") && return string(str_real, " - im*(", view(str_imag, 3:lastindex(str_imag)))
return string(str_real, " + im*", str_imag)
else
((!isguaranteed(x) & display_options.ng_flag) | display_options.decorations) && return string(_str_repr(real(x), format), " + (", _str_repr(imag(x), format), ")im")
return string('(', _str_repr(real(x), format), ") + (", _str_repr(imag(x), format), ")im")
if !isthinzero(imag(x)) && sup(imag(x)) ≤ 0 && !isempty_interval(imag(x))
c1, c2 = split(str_imag, ", ")
l = findfirst(t -> (t == ']') | (t == ')'), c2)
return string(_str_repr(real(x), format), " - im*", _flipl(c2[l]), view(c2, 1+!iszero(sup(imag(x))):l-1), ", ", view(c1, 3:lastindex(c1)), _flipr(c1[1]), view(c2, l+1:lastindex(c2)))
else
return string(_str_repr(real(x), format), " + im*", str_imag)
end
end
end

function _str_repr(x::Complex{Interval{BigFloat}}, format::Symbol)
# `format` is either `:infsup`, `:midpoint` or `:full`
((!isguaranteed(x) & display_options.ng_flag) | display_options.decorations) && return string(_str_repr(real(x), format), " + (", _str_repr(imag(x), format), ")im")
return string(_str_repr(real(x), format), " + ", _str_repr(imag(x), format), "im")
end
_flipl(char) = ifelse(char == ']', '[', '(')
_flipr(char) = ifelse(char == '[', ']', ')')

#

function _str_precision(x)
_str_precision(str_interval, x, format) = str_interval

function _str_precision(str_interval, x::Union{BareInterval{BigFloat},Interval{BigFloat}}, format)
plo = precision(inf(x))
phi = precision(sup(x))
pstr = _subscriptify(plo)
plo == phi && return pstr
return string(pstr, "_", _subscriptify(phi))
if format === :midpoint && str_interval != "∅"
str_interval = string('(', str_interval, ')')
end
str_interval = string(str_interval, pstr)
return ifelse(plo == phi, str_interval, string(str_interval, '_', _subscriptify(phi)))
end

#

function _str_basic_repr(a::BareInterval{<:AbstractFloat}, format::Symbol)
# `format` is either `:infsup`, `:midpoint` or `:full`
# do not use `inf(a)` to avoid `-0.0`
isempty_interval(a) && return "∅"
lo, hi = bounds(a) # do not use `inf(a)` to avoid `-0.0`
sigdigits = display_options.sigdigits
if format === :full
return string(a.lo, ", ", sup(a))
str_lo = string(lo)
# str_lo = ifelse(lo ≥ 0, string('+', str_lo), str_lo)
str_hi = string(hi)
# str_hi = ifelse(hi ≥ 0, string('+', str_hi), str_hi)
return string(str_lo, ", ", str_hi)
elseif format === :midpoint
m = _round_string(mid(a), sigdigits, RoundNearest)
r = _round_string(radius(a), sigdigits, RoundUp)
output = string(m, " ± ", r)
m = mid(a)
str_m = _round_string(m, sigdigits, RoundNearest)
# str_m = ifelse(m ≥ 0, string('+', str_m), str_m)
output = string(str_m, " ± ", _round_string(radius(a), sigdigits, RoundUp))
return replace(output, "Inf" => '∞')
else
lo = _round_string(a.lo, sigdigits, RoundDown)
hi = _round_string(sup(a), sigdigits, RoundUp)
output = string('[', lo, ", ", hi, ']')
str_lo = _round_string(lo, sigdigits, RoundDown)
# str_lo = ifelse(lo ≥ 0, string('+', str_lo), str_lo)
str_hi = _round_string(hi, sigdigits, RoundUp)
# str_hi = ifelse(hi ≥ 0, string('+', str_hi), str_hi)
output = string('[', str_lo, ", ", str_hi, ']')
return replace(output, "Inf]" => "∞)", "[-Inf" => "(-∞")
end
end

function _str_basic_repr(a::BareInterval{Float32}, format::Symbol)
# `format` is either `:infsup`, `:midpoint` or `:full`
# do not use `inf(a)` to avoid `-0.0`
isempty_interval(a) && return "∅"
lo, hi = bounds(a) # do not use `inf(a)` to avoid `-0.0`
sigdigits = display_options.sigdigits
if format === :full
lo = replace(string(a.lo, "f0"), "NaNf0" => "NaN32", "Inff0" => "Inf32")
if contains(lo, 'e')
lo = replace(lo, 'e' => 'f', "f0" => "")
str_lo = string(lo)
str_lo = replace(string(str_lo, "f0"), "NaNf0" => "NaN32", "Inff0" => "Inf32")
if contains(str_lo, 'e')
str_lo = replace(str_lo, 'e' => 'f', "f0" => "")
end
hi = replace(string(sup(a), "f0"), "NaNf0" => "NaN32", "Inff0" => "Inf32")
if contains(hi, 'e')
hi = replace(hi, 'e' => 'f', "f0" => "")
# str_lo = ifelse(lo ≥ 0, string('+', str_lo), str_lo)
str_hi = string(hi)
str_hi = replace(string(str_hi, "f0"), "NaNf0" => "NaN32", "Inff0" => "Inf32")
if contains(str_hi, 'e')
str_hi = replace(str_hi, 'e' => 'f', "f0" => "")
end
return string(lo, ", ", hi)
# str_hi = ifelse(hi ≥ 0, string('+', str_hi), str_hi)
return string(str_lo, ", ", str_hi)
elseif format === :midpoint
m = _round_string(mid(a), sigdigits, RoundNearest)
m = replace(string(m, "f0"), "NaNf0" => "NaN32", "Inff0" => "Inf32")
if contains(m, 'e')
m = replace(m, 'e' => 'f', "f0" => "")
m = mid(a)
str_m = _round_string(m, sigdigits, RoundNearest)
str_m = replace(string(str_m, "f0"), "NaNf0" => "NaN32", "Inff0" => "Inf32")
if contains(str_m, 'e')
str_m = replace(str_m, 'e' => 'f', "f0" => "")
end
r = _round_string(radius(a), sigdigits, RoundUp)
r = replace(string(r, "f0"), "NaNf0" => "NaN32", "Inff0" => "Inf32")
if contains(r, 'e')
r = replace(r, 'e' => 'f', "f0" => "")
# str_m = ifelse(m ≥ 0, string('+', str_m), str_m)
str_r = _round_string(radius(a), sigdigits, RoundUp)
str_r = replace(string(str_r, "f0"), "NaNf0" => "NaN32", "Inff0" => "Inf32")
if contains(str_r, 'e')
str_r = replace(str_r, 'e' => 'f', "f0" => "")
end
return string(m, " ± ", r)
return string(str_m, " ± ", str_r)
else
lo = _round_string(a.lo, sigdigits, RoundDown)
lo = replace(string('[', lo, "f0"), "NaNf0" => "NaN32", "[-Inff0" => "(-∞")
if contains(lo, 'e')
lo = replace(lo, 'e' => 'f', "f0" => "")
str_lo = _round_string(lo, sigdigits, RoundDown)
str_lo = replace(string('[', str_lo, "f0"), "NaNf0" => "NaN32", "[-Inff0" => "(-∞")
if contains(str_lo, 'e')
str_lo = replace(str_lo, 'e' => 'f', "f0" => "")
end
hi = _round_string(sup(a), sigdigits, RoundUp)
hi = replace(string(hi, "f0]"), "NaNf0" => "NaN32", "Inff0]" => "∞)")
if contains(hi, 'e')
hi = replace(hi, 'e' => 'f', "f0" => "")
# str_lo = ifelse(lo ≥ 0, string('+', str_lo), str_lo)
str_hi = _round_string(hi, sigdigits, RoundUp)
str_hi = replace(string(str_hi, "f0]"), "NaNf0" => "NaN32", "Inff0]" => "∞)")
if contains(str_hi, 'e')
str_hi = replace(str_hi, 'e' => 'f', "f0" => "")
end
return string(lo, ", ", hi)
# str_hi = ifelse(hi ≥ 0, string('+', str_hi), str_hi)
return string(str_lo, ", ", str_hi)
end
end

function _str_basic_repr(a::BareInterval{Float16}, format::Symbol)
# `format` is either `:infsup`, `:midpoint` or `:full`
# do not use `inf(a)` to avoid `-0.0`
isempty_interval(a) && return "∅"
lo, hi = bounds(a) # do not use `inf(a)` to avoid `-0.0`
sigdigits = display_options.sigdigits
if format === :full
output = string("Float16(", a.lo, "), Float16(", sup(a), ')')
str_lo = string(lo)
# str_lo = ifelse(lo ≥ 0, string('+', str_lo), str_lo)
str_hi = string(hi)
# str_hi = ifelse(hi ≥ 0, string('+', str_hi), str_hi)
output = string("Float16(", str_lo, "), Float16(", str_hi, ')')
return replace(output, "Float16(NaN)" => "NaN16", "Float16(-Inf)" => "-Inf16", "Float16(Inf)" => "Inf16")
elseif format === :midpoint
m = _round_string(mid(a), sigdigits, RoundNearest)
r = _round_string(radius(a), sigdigits, RoundUp)
output = string("Float16(", m, ") ± Float16(", r, ')')
m = mid(a)
str_m = _round_string(mid(a), sigdigits, RoundNearest)
# str_m = ifelse(m ≥ 0, string('+', str_m), str_m)
output = string("Float16(", str_m, ") ± Float16(", _round_string(radius(a), sigdigits, RoundUp), ')')
return replace(output, "Float16(NaN)" => "NaN16", "Float16(Inf)" => '∞')
else
lo = _round_string(a.lo, sigdigits, RoundDown)
hi = _round_string(sup(a), sigdigits, RoundUp)
output = string("[Float16(", lo, "), Float16(", hi, ")]")
str_lo = _round_string(lo, sigdigits, RoundDown)
# str_lo = ifelse(lo ≥ 0, string('+', str_lo), str_lo)
str_hi = _round_string(sup(a), sigdigits, RoundUp)
# str_hi = ifelse(hi ≥ 0, string('+', str_hi), str_hi)
output = string("[Float16(", str_lo, "), Float16(", str_hi, ")]")
return replace(output, "Float16(NaN)" => "NaN16", "[Float16(-Inf)" => "(-∞", "Float16(Inf)]" => "∞)")
end
end

function _str_basic_repr(a::BareInterval{<:Rational}, format::Symbol)
# `format` is either `:infsup`, `:midpoint` or `:full`
# do not use `inf(a)` to avoid `-0.0`
isempty_interval(a) && return "∅"
format === :full && return string(a.lo, ", ", sup(a))
format === :midpoint && return string(mid(a), " ± ", radius(a))
return string('[', a.lo, ", ", sup(a), ']')
if format === :midpoint
m = mid(a)
str_m = string(m)
# str_m = ifelse(m ≥ 0, string('+', str_m), str_m)
return string(str_m, " ± ", radius(a))
else
lo, hi = bounds(a) # do not use `inf(a)` to avoid `-0.0`
str_lo = string(lo)
# str_lo = ifelse(lo ≥ 0, string('+', str_lo), str_lo)
str_hi = string(hi)
# str_hi = ifelse(hi ≥ 0, string('+', str_hi), str_hi)
output = string(str_lo, ", ", str_hi)
output = ifelse(format === :full, output, string('[', output, ']'))
return output
end
end

# round to the prescribed significant digits
Expand Down
6 changes: 3 additions & 3 deletions src/intervals/exact_literals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ producing the "NG" flag.
julia> setdisplay(:full);

julia> 0.5 * interval(1)
Interval{Float64}(0.5, 0.5, com, NG)
Interval{Float64}(0.5, 0.5, com)_NG

julia> ExactReal(0.5) * interval(1)
Interval{Float64}(0.5, 0.5, com)

julia> [1, interval(2)]
2-element Vector{Interval{Float64}}:
Interval{Float64}(1.0, 1.0, com, NG)
Interval{Float64}(1.0, 1.0, com)_NG
Interval{Float64}(2.0, 2.0, com)

julia> [ExactReal(1), interval(2)]
Expand Down Expand Up @@ -199,7 +199,7 @@ julia> f(x) = 1.2*x + 0.1
f (generic function with 1 method)

julia> f(interval(1, 2))
Interval{Float64}(1.2999999999999998, 2.5, com, NG)
Interval{Float64}(1.2999999999999998, 2.5, com)_NG

julia> @exact g(x) = 1.2*x + 0.1
g (generic function with 1 method)
Expand Down
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[deps]
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Loading
Loading