Skip to content

Commit 118e0d0

Browse files
authored
Improve support for complex intervals (#636)
1 parent c55dd23 commit 118e0d0

File tree

8 files changed

+78
-14
lines changed

8 files changed

+78
-14
lines changed

src/display.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,6 @@ function _round_string_down(s::String)
369369
idx == 1 ? "." : "",
370370
'9'^(len - (idx + 1)))
371371
return string(new_mantissa, 'e', exponent)
372-
373372
end
374373
end
375374
end

src/intervals/arithmetic/hyperbolic.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ for f ∈ (:sinh, :tanh, :asinh)
2525
end
2626
end
2727

28+
Base.sinh(x::Complex{Interval{T}}) where {T<:NumTypes} = (exp(x) - exp(-x)) / interval(T, 2)
29+
30+
Base.tanh(x::Complex{<:Interval}) = sinh(x) / cosh(x)
31+
2832
"""
2933
cosh(::BareInterval)
3034
cosh(::Interval)
@@ -44,6 +48,8 @@ function Base.cosh(x::Interval)
4448
return _unsafe_interval(r, d, isguaranteed(x))
4549
end
4650

51+
Base.cosh(x::Complex{Interval{T}}) where {T<:NumTypes} = (exp(x) + exp(-x)) / interval(T, 2)
52+
4753
"""
4854
coth(::BareInterval)
4955
coth(::Interval)

src/intervals/arithmetic/power.jl

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ Interval{Float64}(-Inf, Inf, trv)
4848
```
4949
"""
5050
function Base.:^(x::BareInterval, y::BareInterval)
51-
isthininteger(y) && return _select_pown(power_mode(), x, Integer(sup(y)))
52-
return _select_pow(power_mode(), x, y)
51+
isthininteger(y) || return _select_pow(power_mode(), x, y)
52+
return _select_pown(power_mode(), x, Integer(sup(y)))
5353
end
5454

5555
function Base.:^(x::Interval, y::Interval)
@@ -65,9 +65,17 @@ Base.:^(x::Interval, n::Integer) = ^(x, n//one(n))
6565
Base.:^(x::Rational, y::Interval) = ^(convert(Interval{typeof(x)}, x), y)
6666
Base.:^(x::Interval, y::Rational) = ^(x, convert(Interval{typeof(y)}, y))
6767

68+
Base.:^(x::Complex{Interval{T}}, y::Complex{Interval{T}}) where {T<:NumTypes} = exp(y * log(x))
69+
Base.:^(x::Complex{<:Interval}, y::Complex{<:Interval}) = ^(promote(x, y)...)
70+
Base.:^(x::Complex{<:Interval}, y::Real) = ^(promote(x, y)...)
71+
Base.:^(x::Real, y::Complex{<:Interval}) = ^(promote(x, y)...)
72+
# needed to avoid method ambiguities
73+
Base.:^(x::Complex{<:Interval}, n::Integer) = ^(promote(x, n)...)
74+
6875
# overwrite behaviour for small integer powers from https://github.com/JuliaLang/julia/pull/24240
6976
# Base.literal_pow(::typeof(^), x::Interval, ::Val{n}) where {n} = x^n
7077
Base.literal_pow(::typeof(^), x::Interval, ::Val{n}) where {n} = _select_pown(power_mode(), x, n)
78+
Base.literal_pow(::typeof(^), x::Complex{Interval{T}}, ::Val{n}) where {T<:NumTypes,n} = exp(interval(T, n) * log(x))
7179

7280
# helper functions for power
7381

@@ -413,6 +421,14 @@ for f ∈ (:cbrt, :exp, :exp2, :exp10, :expm1)
413421
end
414422
end
415423

424+
Base.exp(x::Complex{<:Interval}) = exp(real(x)) * cis(imag(x))
425+
426+
Base.exp2(x::Complex{<:Interval}) = exp2(real(x)) * cis(imag(x) * log(interval(numtype(x), 2)))
427+
428+
Base.exp10(x::Complex{<:Interval}) = exp10(real(x)) * cis(imag(x) * log(interval(numtype(x), 10)))
429+
430+
Base.expm1(x::Complex{<:Interval}) = exp(x) - interval(numtype(x), 1)
431+
416432
#
417433

418434
for f (:log, :log2, :log10)
@@ -437,6 +453,12 @@ for f ∈ (:log, :log2, :log10)
437453
end
438454
end
439455

456+
Base.log(x::Complex{<:Interval}) = complex(log(abs(x)), angle(x))
457+
458+
Base.log2(x::Complex{<:Interval}) = complex(log2(abs(x)), angle(x)/log(interval(numtype(x), 2)))
459+
460+
Base.log10(x::Complex{<:Interval}) = complex(log10(abs(x)), angle(x)/log(interval(numtype(x), 10)))
461+
440462
function Base.log1p(x::BareInterval{T}) where {T<:AbstractFloat}
441463
domain = _unsafe_bareinterval(T, -one(T), typemax(T))
442464
x = intersect_interval(x, domain)
@@ -454,3 +476,5 @@ function Base.log1p(x::Interval{T}) where {T<:NumTypes}
454476
d = min(d, ifelse(isinterior(bx, domain), d, trv))
455477
return _unsafe_interval(r, d, isguaranteed(x))
456478
end
479+
480+
Base.log1p(x::Complex{<:Interval}) = log(interval(numtype(x), 1) + x)

src/intervals/arithmetic/trigonometric.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ function Base.sin(x::Interval)
9999
return _unsafe_interval(r, d, isguaranteed(x))
100100
end
101101

102+
Base.sin(x::Complex{Interval{T}}) where {T<:NumTypes} = complex(sin(real(x)) * cosh(imag(x)), cos(real(x)) * sinh(imag(x)))
103+
102104
# not in the IEEE Standard 1788-2015
103105

104106
if Int == Int32 && VERSION < v"1.10"
@@ -147,6 +149,9 @@ function Base.sinpi(x::Interval)
147149
return _unsafe_interval(r, d, isguaranteed(x))
148150
end
149151

152+
Base.sinpi(x::Complex{Interval{T}}) where {T<:NumTypes} =
153+
complex(sinpi(real(x)) * cosh(imag(x) * interval(T, π)), cospi(real(x)) * sinh(imag(x) * interval(T, π)))
154+
150155
"""
151156
cos(::BareInterval)
152157
cos(::Interval)
@@ -196,6 +201,8 @@ function Base.cos(x::Interval)
196201
return _unsafe_interval(r, d, isguaranteed(x))
197202
end
198203

204+
Base.cos(x::Complex{Interval{T}}) where {T<:NumTypes} = complex(cos(real(x)) * cosh(imag(x)), -sin(real(x)) * sinh(imag(x)))
205+
199206
# not in the IEEE Standard 1788-2015
200207

201208
if Int == Int32 && VERSION < v"1.10"
@@ -246,6 +253,9 @@ function Base.cospi(x::Interval)
246253
return _unsafe_interval(r, d, isguaranteed(x))
247254
end
248255

256+
Base.cospi(x::Complex{Interval{T}}) where {T<:NumTypes} =
257+
complex(cospi(real(x)) * cosh(imag(x) * interval(T, π)), -sinpi(real(x)) * sinh(imag(x) * interval(T, π)))
258+
249259
"""
250260
tan(::BareInterval)
251261
tan(::Interval)
@@ -282,6 +292,8 @@ function Base.tan(x::Interval)
282292
return _unsafe_interval(r, d, isguaranteed(x))
283293
end
284294

295+
Base.tan(x::Complex{<:Interval}) = sin(x) / cos(x)
296+
285297
"""
286298
cot(::BareInterval)
287299
cot(::Interval)

src/intervals/interval_operations/numeric.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ See also: [`inf`](@ref), [`sup`](@ref), [`bounds`](@ref), [`diam`](@ref),
8888
[`radius`](@ref) and [`midradius`](@ref).
8989
"""
9090
function mid(x::BareInterval{T}, α = 0.5) where {T<:AbstractFloat}
91-
0 α 1 || throw(DomainError(α, "α must be between 0 and 1"))
91+
0 α 1 || return throw(DomainError(α, "α must be between 0 and 1"))
9292
isempty_interval(x) && return convert(T, NaN)
9393
if isentire_interval(x)
9494
α == 0.5 && return zero(T)
@@ -105,7 +105,7 @@ function mid(x::BareInterval{T}, α = 0.5) where {T<:AbstractFloat}
105105
end
106106
end
107107
function mid(x::BareInterval{T}, α = 1//2) where {T<:Rational}
108-
0 α 1 || throw(DomainError(α, "α must be between 0 and 1"))
108+
0 α 1 || return throw(DomainError(α, "α must be between 0 and 1"))
109109
isempty_interval(x) && return throw(ArgumentError("cannot compute the midpoint of empty intervals; cannot return a `Rational` NaN"))
110110
if isentire_interval(x)
111111
α == 0.5 && return zero(T)
@@ -123,12 +123,12 @@ function mid(x::BareInterval{T}, α = 1//2) where {T<:Rational}
123123
end
124124

125125
function mid(x::Interval{T}, α = 0.5) where {T<:AbstractFloat}
126-
0 α 1 || throw(DomainError(α, "α must be between 0 and 1"))
126+
0 α 1 || return throw(DomainError(α, "α must be between 0 and 1"))
127127
isnai(x) && return convert(T, NaN)
128128
return mid(bareinterval(x), α)
129129
end
130130
function mid(x::Interval{<:Rational}, α = 1//2)
131-
0 α 1 || throw(DomainError(α, "α must be between 0 and 1"))
131+
0 α 1 || return throw(DomainError(α, "α must be between 0 and 1"))
132132
isnai(x) && return throw(ArgumentError("cannot compute the midpoint of an NaI; cannot return a `Rational` NaN"))
133133
return mid(bareinterval(x), α)
134134
end

test/interval_tests/consistency.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,20 @@
290290

291291
# Test putting functions in interval:
292292
@test issubset_interval(log(interval(-2, 5)), interval(-Inf, log(interval(5))))
293+
294+
#
295+
296+
x = interval(0, 3) + interval(0, 4)*interval(im)
297+
@test isequal_interval(abs2(x), interval(0, 25))
298+
@test isequal_interval(abs(x), interval(0, 5))
299+
300+
y = interval(-1, 1) + interval(-2, 2)*interval(im)
301+
@test inf(abs(y)) == 0
302+
@test inf(abs2(y)) == 0
303+
304+
@test mag(x) == 4
305+
@test mig(x) == 0
306+
@test mid(x) == 1.5 + 2im
293307
end
294308

295309
@testset "Interval power of an interval" begin

test/interval_tests/power.jl

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,16 @@ end
8484
@test isequal_interval(pow(interval(-3, 2), interval(-1, 1)), interval(0, Inf, IntervalArithmetic.trv))
8585
end
8686

87-
# @testset "Complex{<:Interval}" begin
88-
# a = interval(3 + 4im)
89-
# b = exp(a)
90-
# @test real(b) == interval(-13.12878308146216, -13.128783081462153)
91-
# @test imag(b) == interval(-15.200784463067956, -15.20078446306795)
92-
# end
87+
@testset "Complex{<:Interval}" begin
88+
a = interval(3 + 4im)
89+
b = exp(a)
90+
@test isequal_interval(real(b), interval(-13.12878308146216, -13.128783081462153))
91+
@test isequal_interval(imag(b), interval(-15.200784463067956, -15.20078446306795))
92+
93+
z = exp(-im * interval(π))
94+
@test in_interval(-1, real(z))
95+
@test in_interval(0, imag(z))
96+
end
9397

9498
@testset "Literal powers" begin
9599
x = interval(-1,1)
@@ -100,4 +104,4 @@ end
100104
@test_broken isguaranteed(x ^ 2305843009213693952)
101105
@test isequal_interval(x^2, interval(0,1))
102106
@test isequal_interval(x^3, x)
103-
end
107+
end

test/interval_tests/trigonometric.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ end
1919
@test issubset_interval(sin(interval(BigFloat, 0.5, 8.5)), sin(interval(0.5, 8.5)))
2020
@test issubset_interval(sin(interval(BigFloat, -4.5, 0.1)), sin(interval(-4.5, 0.1)))
2121
@test issubset_interval(sin(interval(BigFloat, 1.3, 6.3)), sin(interval(1.3, 6.3)))
22+
23+
#
24+
25+
z = interval(3, 1e-7; format = :midpoint) + interval(4, 1e-7; format = :midpoint) * interval(im)
26+
@test issubset_interval(sin(z), complex(sin(real(z)) * cosh(imag(z)), sinh(imag(z)) * cos(real(z))))
2227
end
2328

2429
@testset "cos" begin

0 commit comments

Comments
 (0)