Skip to content

Commit d9dceb0

Browse files
authored
Implement multivariate gcd (#161)
* Implement multivariate gcd * Replace union by a more predictable selection * Fixes * Simplify for Rational as well * Smarter choice of monomial * Add inflate/deflate * Type-stable powers * Fix * Add Combinatorics for test * Fixes * Fix tests for TypedPolynomials * Fixes for TypedPolynomials * Exclude Rational{BigInt} gcd tests for Julia v1.0 * Clean up and add shortcut for constant * Warn about nonuniqueness of gcd * WIP * Parametrize with algo * Fix * Fixes
1 parent 6315b50 commit d9dceb0

File tree

14 files changed

+924
-134
lines changed

14 files changed

+924
-134
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ julia = "1"
1818

1919
[extras]
2020
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
21+
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
2122
DynamicPolynomials = "7c1d4256-1411-5781-91ec-d7bc3513ac07"
2223
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2324
TypedPolynomials = "afbbf031-7a57-5f58-a1b9-b774a0fad08d"
2425

2526
[targets]
26-
test = ["BenchmarkTools", "DynamicPolynomials", "Test", "TypedPolynomials"]
27+
test = ["BenchmarkTools", "Combinatorics", "DynamicPolynomials", "Test", "TypedPolynomials"]

perf/gcd.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using DynamicPolynomials
2+
3+
function bench()
4+
o = one(Rational{BigInt})
5+
@polyvar x y z
6+
a = (o * x + o * y^2) * (o * z^3 + o * y^2 + o * x)
7+
@show a
8+
b = (o * x + o * y + o * z) * (o * x^2 + o * y)
9+
@show b
10+
@time gcd(a, b)
11+
end

src/MultivariatePolynomials.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ include("comparison.jl")
8484
include("substitution.jl")
8585
include("differentiation.jl")
8686
include("division.jl")
87+
include("gcd.jl")
8788
include("det.jl")
8889

8990
end # module

src/division.jl

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,81 @@ Base.gcd(m1::AbstractMonomialLike, m2::AbstractMonomialLike) = mapexponents(min,
1919
Base.lcm(m1::AbstractMonomialLike, m2::AbstractMonomialLike) = mapexponents(max, m1, m2)
2020

2121
# _div(a, b) assumes that b divides a
22+
_div(a::Union{Rational, AbstractFloat}, b) = a / b
23+
_div(a, b) = div(a, b)
2224
_div(m1::AbstractMonomialLike, m2::AbstractMonomialLike) = mapexponents(-, m1, m2)
2325
function _div(t::AbstractTerm, m::AbstractMonomial)
24-
coefficient(t) * _div(monomial(t), m)
26+
term(coefficient(t), _div(monomial(t), m))
2527
end
2628
function _div(t1::AbstractTermLike, t2::AbstractTermLike)
27-
(coefficient(t1) / coefficient(t2)) * _div(monomial(t1), monomial(t2))
29+
term(_div(coefficient(t1), coefficient(t2)), _div(monomial(t1), monomial(t2)))
30+
end
31+
function _div(f::APL, g::APL)
32+
lt = leadingterm(g)
33+
rf = MA.copy_if_mutable(f)
34+
rg = removeleadingterm(g)
35+
q = zero(rf)
36+
while !iszero(rf)
37+
ltf = leadingterm(rf)
38+
qt = _div(ltf, lt)
39+
q = MA.add!(q, qt)
40+
rf = MA.operate!(removeleadingterm, rf)
41+
rf = MA.operate!(MA.sub_mul, rf, qt, rg)
42+
end
43+
return q
2844
end
2945

3046
Base.div(f::APL, g::Union{APL, AbstractVector{<:APL}}; kwargs...) = divrem(f, g; kwargs...)[1]
3147
Base.rem(f::APL, g::Union{APL, AbstractVector{<:APL}}; kwargs...) = divrem(f, g; kwargs...)[2]
3248

33-
proddiff(x, y) = x/y - x/y
49+
# FIXME What should we do for `Rational` ?
50+
function pseudo_rem(f::APL, g::APL{<:Union{Rational, AbstractFloat}}, algo)
51+
return true, rem(f, g)
52+
end
53+
54+
function pseudo_rem(f::APL, g::APL, algo)
55+
ltg = leadingterm(g)
56+
rg = removeleadingterm(g)
57+
ltf = leadingterm(f)
58+
if !divides(monomial(ltg), ltf)
59+
return false, f
60+
end
61+
while divides(monomial(ltg), ltf)
62+
new_f = constantterm(coefficient(ltg), f) * removeleadingterm(f)
63+
new_g = term(coefficient(ltf), _div(monomial(ltf), monomial(ltg))) * rg
64+
# Check with `::` that we don't have any type unstability on this variable.
65+
f = (new_f - new_g)::typeof(f)
66+
if algo.primitive_rem
67+
f = primitive_part(f, algo)::typeof(f)
68+
end
69+
if algo.skip_last && maxdegree(f) == maxdegree(g)
70+
break
71+
end
72+
ltf = leadingterm(f)
73+
end
74+
return true, f
75+
end
76+
function MA.promote_operation(
77+
::typeof(pseudo_rem),
78+
::Type{P},
79+
::Type{Q},
80+
) where {T,S<:Union{Rational, AbstractFloat},P<:APL{T},Q<:APL{S}}
81+
return MA.promote_operation(rem, P, Q)
82+
end
83+
function MA.promote_operation(::typeof(pseudo_rem), ::Type{P}, ::Type{Q}) where {T,S,P<:APL{T},Q<:APL{S}}
84+
U1 = MA.promote_operation(*, S, T)
85+
U2 = MA.promote_operation(*, T, S)
86+
# `promote_type(P, Q)` is needed for TypedPolynomials in case they use different variables
87+
return polynomialtype(promote_type(P, Q), MA.promote_operation(-, U1, U2))
88+
end
89+
90+
function MA.promote_operation(::Union{typeof(div), typeof(rem)}, ::Type{P}, g::Type{Q}) where {T,S,P<:APL{T},Q<:APL{S}}
91+
U = MA.promote_operation(/, T, S)
92+
# `promote_type(P, Q)` is needed for TypedPolynomials in case they use different variables
93+
return polynomialtype(promote_type(P, Q), MA.promote_operation(-, U, U))
94+
end
3495
function Base.divrem(f::APL{T}, g::APL{S}; kwargs...) where {T, S}
35-
# `promote_type(typeof(f), typeof(g))` is needed for TypedPolynomials in case they use different variables
36-
rf = convert(polynomialtype(promote_type(typeof(f), typeof(g)), Base.promote_op(proddiff, T, S)), MA.copy_if_mutable(f))
96+
rf = convert(MA.promote_operation(div, typeof(f), typeof(g)), MA.copy_if_mutable(f))
3797
q = zero(rf)
3898
r = zero(rf)
3999
lt = leadingterm(g)
@@ -61,8 +121,7 @@ function Base.divrem(f::APL{T}, g::APL{S}; kwargs...) where {T, S}
61121
q, r
62122
end
63123
function Base.divrem(f::APL{T}, g::AbstractVector{<:APL{S}}; kwargs...) where {T, S}
64-
# `promote_type(typeof(f), eltype(g))` is needed for TypedPolynomials in case they use different variables
65-
rf = convert(polynomialtype(promote_type(typeof(f), eltype(g)), Base.promote_op(proddiff, T, S)), MA.copy_if_mutable(f))
124+
rf = convert(MA.promote_operation(div, typeof(f), eltype(g)), MA.copy_if_mutable(f))
66125
r = zero(rf)
67126
q = similar(g, typeof(rf))
68127
for i in eachindex(q)
@@ -105,12 +164,3 @@ function Base.divrem(f::APL{T}, g::AbstractVector{<:APL{S}}; kwargs...) where {T
105164
end
106165
q, r
107166
end
108-
109-
function Base.gcd(p::AbstractPolynomialLike{T}, q::AbstractPolynomialLike{S}) where {T, S}
110-
if isapproxzero(q)
111-
convert(polynomialtype(p, Base.promote_op(proddiff, T, S)), p)
112-
else
113-
gcd(q, rem(p, q))
114-
end
115-
end
116-
Base.lcm(p::AbstractPolynomialLike, q::AbstractPolynomialLike) = p * div(q, gcd(p, q))

0 commit comments

Comments
 (0)