Skip to content

Commit 79f164d

Browse files
authored
Add default term and polynomial representations (#199)
* Add default term and polynomial representations * Add missing termtype * Fix deprecation * Fix * Remove unused * Fix * Fixes * Fixes * Fix * Test zero-allocation for trivial converts * Readd conversion * Drop Julia v1.0
1 parent 0a75624 commit 79f164d

19 files changed

+439
-38
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- version: '1'
1818
os: ubuntu-latest
1919
arch: x64
20-
- version: '1.0'
20+
- version: '1.6'
2121
os: ubuntu-latest
2222
arch: x64
2323
- version: '1'
@@ -42,6 +42,16 @@ jobs:
4242
${{ runner.os }}-test-${{ env.cache-name }}-
4343
${{ runner.os }}-test-
4444
${{ runner.os }}-
45+
- name: TP#master
46+
shell: julia --project=@. {0}
47+
run: |
48+
using Pkg
49+
Pkg.add(PackageSpec(name="TypedPolynomials", rev="bl/refactor"))
50+
- name: DP#master
51+
shell: julia --project=@. {0}
52+
run: |
53+
using Pkg
54+
Pkg.add(PackageSpec(name="DynamicPolynomials", rev="bl/refactor"))
4555
- uses: julia-actions/julia-buildpkg@v1
4656
- uses: julia-actions/julia-runtest@v1
4757
with:

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ DataStructures = "0.17.7, 0.18"
1616
DynamicPolynomials = "0.4.5"
1717
MutableArithmetics = "0.3, 1"
1818
TypedPolynomials = "0.3.1"
19-
julia = "1"
19+
julia = "1.6"
2020

2121
[extras]
2222
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"

src/MultivariatePolynomials.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ include("differentiation.jl")
8787
include("division.jl")
8888
include("gcd.jl")
8989
include("det.jl")
90-
9190
include("chain_rules.jl")
9291

92+
include("default_term.jl")
93+
include("sequences.jl")
94+
include("lazy_iterators.jl")
95+
include("default_polynomial.jl")
96+
9397
end # module

src/conversion.jl

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ export variable, convert_to_constant
22

33
function convertconstant end
44
Base.convert(::Type{P}, α) where P<:APL = convertconstant(P, α)
5-
function Base.convert(::Type{P}, p::P) where {T, P<:AbstractPolynomial{T}}
6-
return p
5+
function convertconstant(::Type{TT}, α) where {T,TT<:AbstractTerm{T}}
6+
return term(convert(T, α), constantmonomial(TT))
7+
end
8+
function convertconstant(::Type{PT}, α) where {PT<:AbstractPolynomial}
9+
return convert(PT, convert(termtype(PT), α))
710
end
811
function Base.convert(::Type{P}, p::APL) where {T, P<:AbstractPolynomial{T}}
9-
return convert(P, polynomial(p, T))
12+
error("`convert` not implemented for $P")
1013
end
1114

1215
function Base.convert(::Type{V}, mono::AbstractMonomial) where V <: AbstractVariable
@@ -37,10 +40,10 @@ function Base.convert(::Type{M}, t::AbstractTerm) where M <: AbstractMonomialLik
3740
end
3841
end
3942
function Base.convert(TT::Type{<:AbstractTerm{T}}, m::AbstractMonomialLike) where T
40-
return convert(TT, one(T) * m)
43+
return convert(TT, term(one(T), convert(monomialtype(TT), m)))
4144
end
4245
function Base.convert(TT::Type{<:AbstractTerm{T}}, t::AbstractTerm) where T
43-
return convert(TT, convert(T, coefficient(t)) * monomial(t))
46+
return convert(TT, term(convert(T, coefficient(t)), convert(monomialtype(TT), monomial(t))))
4447
end
4548

4649
# Base.convert(::Type{T}, t::T) where {T <: AbstractTerm} is ambiguous with above method.
@@ -79,4 +82,5 @@ function convert_to_constant(p::APL{S}) where {S}
7982
return convert_to_constant(S, p)
8083
end
8184

85+
# Also covers, e.g., `convert(APL, ::P)` where `P<:APL`
8286
Base.convert(::Type{PT}, p::PT) where {PT<:APL} = p

src/default_polynomial.jl

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
struct Polynomial{CoeffType,T<:AbstractTerm{CoeffType},V<:AbstractVector{T}} <: AbstractPolynomial{CoeffType}
2+
terms::V
3+
4+
Polynomial{C, T, V}(terms::AbstractVector{T}) where {C, T, V} = new{C, T, V}(terms)
5+
end
6+
7+
function coefficients(p::Polynomial)
8+
return LazyMap{coefficienttype(p)}(coefficient, terms(p))
9+
end
10+
function monomials(p::Polynomial)
11+
return LazyMap{monomialtype(p)}(monomial, terms(p))
12+
end
13+
14+
const VectorPolynomial{C,T} = Polynomial{C,T,Vector{T}}
15+
16+
termtype(::Type{<:Polynomial{C,T}}) where {C,T} = T
17+
terms(p::Polynomial) = p.terms
18+
constantmonomial(::Union{Polynomial{C,TT},Type{Polynomial{C,TT}}}) where {C,TT} = constantmonomial(TT)
19+
Base.convert(::Type{Polynomial{C,TT,V}}, p::Polynomial{C,TT,V}) where {C,TT,V} = p
20+
function Base.convert(PT::Type{Polynomial{C,TT,V}}, p::AbstractPolynomialLike) where {C,TT,V}
21+
return PT(convert(V, terms(p)))
22+
end
23+
function Base.convert(::Type{Polynomial{C}}, p::AbstractPolynomialLike) where {C}
24+
TT = termtype(p, C)
25+
return convert(Polynomial{C,TT,Vector{TT}}, p)
26+
end
27+
function Base.convert(::Type{Polynomial}, p::AbstractPolynomialLike)
28+
return convert(Polynomial{coefficienttype(p)}, p)
29+
end
30+
31+
32+
_change_eltype(::Type{<:Vector}, ::Type{T}) where T = Vector{T}
33+
function polynomialtype(::Type{Polynomial{C, T, V}}, ::Type{NewC}) where {C, T, V, NewC}
34+
NewT = termtype(T, NewC)
35+
Polynomial{NewC, NewT, _change_eltype(V, NewT)}
36+
end
37+
38+
function Base.promote_rule(::Type{Polynomial}, ::Type{<:AbstractPolynomialLike})
39+
return Polynomial
40+
end
41+
function Base.promote_rule(::Type{<:AbstractPolynomialLike}, ::Type{Polynomial})
42+
return Polynomial
43+
end
44+
function promote_rule_constant(::Type{T}, ::Type{Polynomial}) where T
45+
return Any
46+
# `convert(Polynomial{T}, ::T)` cannot be implemented as we don't know the type of the term
47+
#return Polynomial{T}
48+
end
49+
50+
(p::Polynomial)(s...) = substitute(Eval(), p, s)
51+
52+
function Base.one(::Type{P}) where {C,TT,P<:Polynomial{C,TT}}
53+
return convert(P, one(TT))
54+
end
55+
Base.one(p::Polynomial) = one(typeof(p))
56+
57+
Base.zero(::Type{Polynomial{C,T,A}}) where {C,T,A} = Polynomial{C,T,A}(A())
58+
Base.zero(t::Polynomial) = zero(typeof(t))
59+
60+
jointerms(terms1::AbstractArray{<:Term}, terms2::AbstractArray{<:Term}) = Sequences.mergesorted(terms1, terms2, compare, combine)
61+
function jointerms!(output::AbstractArray{<:Term}, terms1::AbstractArray{<:Term}, terms2::AbstractArray{<:Term})
62+
resize!(output, length(terms1) + length(terms2))
63+
Sequences.mergesorted!(output, terms1, terms2, compare, combine)
64+
end
65+
66+
Base.:(+)(p1::Polynomial, p2::Polynomial) = polynomial!(jointerms(terms(p1), terms(p2)), SortedUniqState())
67+
Base.:(+)(p1::Polynomial, p2::Polynomial{<:LinearAlgebra.UniformScaling}) = p1 + mapcoefficientsnz(J -> J.λ, p2)
68+
Base.:(+)(p1::Polynomial{<:LinearAlgebra.UniformScaling}, p2::Polynomial) = mapcoefficientsnz(J -> J.λ, p1) + p2
69+
Base.:(+)(p1::Polynomial{<:LinearAlgebra.UniformScaling}, p2::Polynomial{<:LinearAlgebra.UniformScaling}) = mapcoefficientsnz(J -> J.λ, p1) + p2
70+
function MA.operate_to!(result::Polynomial, ::typeof(+), p1::Polynomial, p2::Polynomial)
71+
if result === p1 || result === p2
72+
error("Cannot call `operate_to!(output, +, p, q)` with `output` equal to `p` or `q`, call `operate!` instead.")
73+
end
74+
jointerms!(result.terms, terms(p1), terms(p2))
75+
result
76+
end
77+
function MA.operate_to!(result::Polynomial, ::typeof(*), p::Polynomial, t::AbstractTermLike)
78+
if iszero(t)
79+
MA.operate!(zero, result)
80+
else
81+
resize!(result.terms, nterms(p))
82+
for i in eachindex(p.terms)
83+
# TODO could use MA.mul_to!! for indices that were presents in `result` before the `resize!`.
84+
result.terms[i] = p.terms[i] * t
85+
end
86+
return result
87+
end
88+
end
89+
function MA.operate_to!(result::Polynomial, ::typeof(*), t::AbstractTermLike, p::Polynomial)
90+
if iszero(t)
91+
MA.operate!(zero, result)
92+
else
93+
resize!(result.terms, nterms(p))
94+
for i in eachindex(p.terms)
95+
# TODO could use MA.mul_to!! for indices that were presents in `result` before the `resize!`.
96+
result.terms[i] = t * p.terms[i]
97+
end
98+
return result
99+
end
100+
end
101+
Base.:(-)(p1::Polynomial, p2::Polynomial) = polynomial!(jointerms(terms(p1), (-).(terms(p2))))
102+
Base.:(-)(p1::Polynomial, p2::Polynomial{<:LinearAlgebra.UniformScaling}) = p1 - mapcoefficientsnz(J -> J.λ, p2)
103+
Base.:(-)(p1::Polynomial{<:LinearAlgebra.UniformScaling}, p2::Polynomial) = mapcoefficientsnz(J -> J.λ, p1) - p2
104+
Base.:(-)(p1::Polynomial{<:LinearAlgebra.UniformScaling}, p2::Polynomial{<:LinearAlgebra.UniformScaling}) = mapcoefficientsnz(J -> J.λ, p1) - p2
105+
106+
LinearAlgebra.adjoint(x::Polynomial) = polynomial!(adjoint.(terms(x)))
107+
108+
function mapcoefficients(f::Function, p::Polynomial; nonzero = false)
109+
terms = map(p.terms) do term
110+
mapcoefficients(f, term)
111+
end
112+
if !nonzero
113+
filter!(!iszero, terms)
114+
end
115+
return polynomial!(terms)
116+
end
117+
function mapcoefficients!(f::Function, p::Polynomial; nonzero = false)
118+
for i in eachindex(p.terms)
119+
t = p.terms[i]
120+
p.terms[i] = Term(f(coefficient(t)), monomial(t))
121+
end
122+
if !nonzero
123+
filter!(!iszero, p.terms)
124+
end
125+
return p
126+
end
127+
128+
function mapcoefficients_to!(output::Polynomial, f::Function, p::Polynomial; nonzero = false)
129+
resize!(output.terms, nterms(p))
130+
for i in eachindex(p.terms)
131+
t = p.terms[i]
132+
output.terms[i] = Term(f(coefficient(t)), monomial(t))
133+
end
134+
if !nonzero
135+
filter!(!iszero, output.terms)
136+
end
137+
return output
138+
end
139+
function mapcoefficients_to!(output::Polynomial, f::Function, p::AbstractPolynomialLike; nonzero = false)
140+
return mapcoefficients_to!(output, f, polynomial(p); nonzero = false)
141+
end
142+
143+
# The polynomials can be mutated.
144+
MA.mutability(::Type{<:VectorPolynomial}) = MA.IsMutable()
145+
146+
# Terms are not mutable under the MutableArithmetics API
147+
function MA.mutable_copy(p::VectorPolynomial{C,TT}) where {C,TT}
148+
return VectorPolynomial{C,TT}([TT(MA.copy_if_mutable(coefficient(term)), monomial(term)) for term in terms(p)])
149+
end
150+
Base.copy(p::VectorPolynomial) = MA.mutable_copy(p)
151+
152+
function grlex end
153+
function MA.operate!(op::Union{typeof(+), typeof(-)}, p::Polynomial{T,TT}, q::Polynomial) where {T,TT}
154+
get1(i) = p.terms[i]
155+
function get2(i)
156+
t = q.terms[i]
157+
TT(MA.scaling_convert(T, MA.operate(op, coefficient(t))), monomial(t))
158+
end
159+
set(i, t::TT) = p.terms[i] = t
160+
push(t::TT) = push!(p.terms, t)
161+
compare_monomials(t::TT, j::Int) = grlex(monomial(q.terms[j]), monomial(t))
162+
compare_monomials(i::Int, j::Int) = compare_monomials(get1(i), j)
163+
combine(i::Int, j::Int) = p.terms[i] = Term(MA.operate!!(op, coefficient(p.terms[i]), coefficient(q.terms[j])), monomial(p.terms[i]))
164+
combine(t::TT, j::Int) = TT(MA.operate!!(op, coefficient(t), coefficient(q.terms[j])), monomial(t))
165+
resize(n) = resize!(p.terms, n)
166+
# We can modify the coefficient since it's the result of `combine`.
167+
keep(t::Term) = !MA.iszero!!(coefficient(t))
168+
keep(i::Int) = !MA.iszero!!(coefficient(p.terms[i]))
169+
polynomial_merge!(
170+
nterms(p), nterms(q), get1, get2, set, push,
171+
compare_monomials, combine, keep, resize
172+
)
173+
return p
174+
end
175+
function MA.operate_to!(output::Polynomial, ::typeof(*), p::Polynomial, q::Polynomial)
176+
empty!(output.terms)
177+
mul_to_terms!(output.terms, p, q)
178+
sort!(output.terms, lt=(>))
179+
uniqterms!(output.terms)
180+
return output
181+
end
182+
function MA.operate!(::typeof(*), p::Polynomial, q::Polynomial)
183+
return MA.operate_to!(p, *, MA.mutable_copy(p), q)
184+
end
185+
186+
function MA.operate!(::typeof(zero), p::Polynomial)
187+
empty!(p.terms)
188+
return p
189+
end
190+
function MA.operate!(::typeof(one), p::Polynomial{T}) where T
191+
if isempty(p.terms)
192+
push!(p.terms, constantterm(one(T), p))
193+
else
194+
t = p.terms[1]
195+
p.terms[1] = Term(MA.one!!(coefficient(t)), constantmonomial(t))
196+
resize!(p.terms, 1)
197+
end
198+
return p
199+
end
200+
201+
function MA.operate!(::typeof(removeleadingterm), p::Polynomial)
202+
deleteat!(p.terms, 1)
203+
return p
204+
end

src/default_term.jl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
struct Term{CoeffType, M <: AbstractMonomial} <: AbstractTerm{CoeffType}
2+
coefficient::CoeffType
3+
monomial::M
4+
end
5+
6+
coefficient(t::Term) = t.coefficient
7+
monomial(t::Term) = t.monomial
8+
termtype(::Type{<:Term{C,M}}, ::Type{T}) where {C,M,T} = Term{T,M}
9+
monomialtype(::Type{<:Term{C,M}}) where {C,M} = M
10+
function Base.copy(t::Term)
11+
return Term(copy(t.coefficient), copy(t.monomial))
12+
end
13+
14+
(t::Term)(s...) = substitute(Eval(), t, s)
15+
16+
LinearAlgebra.adjoint(t::Term) = Term(adjoint(coefficient(t)), monomial(t))
17+
18+
Base.convert(::Type{Term{T,M}}, m::AbstractMonomialLike) where {T, M} = Term(one(T), convert(M, m))
19+
convertconstant(::Type{Term{C,M} where C}, α) where M = convert(Term{typeof(α),M}, α)
20+
21+
Base.promote_rule(::Type{Term{C,M1} where {C}}, M2::Type{<:AbstractMonomialLike}) where {M1} = (Term{C,promote_type(M1, M2)} where {C})
22+
Base.promote_rule(M1::Type{<:AbstractMonomialLike}, ::Type{Term{C,M2} where {C}}) where {M2} = (Term{C,promote_type(M1, M2)} where {C})
23+
Base.promote_rule(::Type{Term{C,M1} where {C}}, ::Type{Term{T,M2}}) where {T,M1,M2} = (Term{C,promote_type(M1, M2)} where {C})
24+
Base.promote_rule(::Type{Term{T,M2}}, ::Type{Term{C,M1} where {C}}) where {T,M1,M2} = (Term{C,promote_type(M1, M2)} where {C})
25+
promote_rule_constant(::Type{T}, TT::Type{Term{C,M} where C}) where {T, M} = Any
26+
27+
combine(t1::Term, t2::Term) = combine(promote(t1, t2)...)
28+
combine(t1::T, t2::T) where {T <: Term} = Term(t1.coefficient + t2.coefficient, t1.monomial)
29+
compare(t1::Term, t2::Term) = monomial(t1) > monomial(t2)
30+
function MA.promote_operation(::typeof(combine), ::Type{Term{S, M1}}, ::Type{Term{T, M2}}) where {S, T, M1, M2}
31+
return Term{MA.promote_operation(+, S, T), promote_type(M1, M2)}
32+
end
33+
34+
function MA.mutability(::Type{Term{C,T}}) where {C,T}
35+
if MA.mutability(C) isa MA.IsMutable && MA.mutability(T) isa MA.IsMutable
36+
return MA.IsMutable()
37+
else
38+
return MA.IsNotMutable()
39+
end
40+
end

src/lazy_iterators.jl

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Copied from MathOptInterface
2+
"""
3+
struct LazyMap{T, VT}
4+
f::Function
5+
data::VT
6+
end
7+
8+
Iterator over the elements of `data` mapped by `f`. This is similar to
9+
`Base.Generator(f, data)` except that the `eltype` of a `LazyMap` is given at
10+
construction while the `eltype` of `Base.Generator(f, data)` is `Any`.
11+
"""
12+
struct LazyMap{T,VT,F}
13+
f::F
14+
data::VT
15+
end
16+
17+
function LazyMap{T}(f, data) where {T}
18+
return LazyMap{T,typeof(data),typeof(f)}(f, data)
19+
end
20+
21+
Base.size(it::LazyMap) = size(it.data)
22+
23+
Base.length(it::LazyMap) = length(it.data)
24+
25+
function Base.iterate(it::LazyMap, args...)
26+
elem_state = iterate(it.data, args...)
27+
if elem_state === nothing
28+
return
29+
else
30+
return it.f(elem_state[1]), elem_state[2]
31+
end
32+
end
33+
34+
Base.IteratorSize(it::LazyMap) = Base.IteratorSize(it.data)
35+
36+
Base.eltype(::LazyMap{T}) where {T} = T
37+
38+
Base.getindex(it::LazyMap, i) = it.f(getindex(it.data, i))
39+
40+
Base.eachindex(it::LazyMap) = Base.eachindex(it.data)
41+
42+
function Iterators.reverse(it::LazyMap{T}) where {T}
43+
return LazyMap{T}(it.f, Iterators.reverse(it.data))
44+
end

0 commit comments

Comments
 (0)