Skip to content
Open
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
8 changes: 7 additions & 1 deletion src/methods/risch/rational_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,14 @@ function LogToReal(t::SumOfLogTerms; symbols=[:α, :β]) #{T, PP}) where {T<:Fie
SumOfRealTerms(t.R, t.S, P, Q, FunctionTerm(log, 1, A^2+B^2), LogToAtan(A, B))
end

# Only real numbers have a sign. `QQBarFieldElem` throws
# `DomainError: comparing nonreal numbers` when compared with `<`, so a nonreal
# coefficient is reported as not negative rather than making the comparison.
has_negative_sign(c) = c < 0
has_negative_sign(c::QQBarFieldElem) = iszero(imag(c)) && real(c) < 0

function positive_constant_coefficient(f::PolyRingElem)
if constant_coefficient(f)<0
if has_negative_sign(constant_coefficient(f))
return -f
else
return f
Expand Down
62 changes: 62 additions & 0 deletions test/methods/risch/test_algebraic_closure.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Test
using SymbolicIntegration
using Symbolics
import Nemo

# `RischMethod(use_algebraic_closure=true)` used to throw
# `DomainError: comparing nonreal numbers` as soon as the roots were not real,
# because a nonreal coefficient was compared with `<` to decide a sign.
# See https://github.com/JuliaSymbolics/SymbolicIntegration.jl/issues/13

@testset "[Risch] Algebraic closure with nonreal roots" begin
@variables x

@testset "has_negative_sign" begin
QQBar = Nemo.algebraic_closure(Nemo.QQ)

# Rational coefficients keep the plain comparison.
@test SymbolicIntegration.has_negative_sign(Nemo.QQ(-3))
@test !SymbolicIntegration.has_negative_sign(Nemo.QQ(3))
@test !SymbolicIntegration.has_negative_sign(Nemo.QQ(0))

# Real algebraic numbers compare by their real value.
@test SymbolicIntegration.has_negative_sign(QQBar(-3))
@test !SymbolicIntegration.has_negative_sign(QQBar(3))

# Nonreal algebraic numbers have no sign, and must not be compared.
kx, X = Nemo.polynomial_ring(Nemo.QQ, :x)
for r in Nemo.roots(QQBar, X^2 + 1)
@test !SymbolicIntegration.has_negative_sign(r)
end
for r in Nemo.roots(QQBar, X^2 + X + 1) # nonreal, nonzero real part
@test !SymbolicIntegration.has_negative_sign(r)
end
end

@testset "Integration with nonreal roots" begin
method = RischMethod(use_algebraic_closure = true, catch_errors = false)
for f in [
1 / (x^2 + 1), 1 / (x^2 + 2), 1 / (x^3 - 1), 1 / (x^2 - 2),
1 / (x^2 + x + 1), (x + 1) / (x^2 + 4),
]
result = integrate(f, x, method)
@test !isnothing(result)
# Rendering the result is part of the contract: the comparison that
# used to fail is also reached from `show`.
@test string(result) isa String
end
end

@testset "Known remaining limitation" begin
# 1/(x^4 + 1) still raises from a different nonreal comparison, reached
# when the degree-4 `Root` placeholder in the result is rendered.
# `integrate` itself succeeds; only displaying the result throws, with a
# `DomainError` carrying the offending nonreal root. Asserting the
# exception type pins the current limitation without suppressing an
# unrelated failure: when rendering is fixed, this test fails and says so.
method = RischMethod(use_algebraic_closure = true, catch_errors = false)
result = integrate(1 / (x^4 + 1), x, method)
@test !isnothing(result)
@test_throws DomainError string(result)
end
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ end
include("methods/risch/test_algorithm_internals.jl")
include("methods/risch/test_textbook_transcendentals.jl")
include("methods/risch/test_trig_integrals.jl")
include("methods/risch/test_algebraic_closure.jl")

# test internals of rulebased methods
include("methods/rule_based/test_rule2.jl")
Expand Down
Loading