Skip to content

Commit 2090362

Browse files
authored
Faster vector substitution (#165)
* Faster vector substitution * Add test * Bump lts in ci * Fix * Fix
1 parent e5df766 commit 2090362

File tree

3 files changed

+54
-16
lines changed

3 files changed

+54
-16
lines changed

.github/workflows/ci.yml

+1-1
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.6'
20+
- version: '1.10'
2121
os: ubuntu-latest
2222
arch: x64
2323
- version: '1'

src/subs.jl

+35-15
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,10 @@ function _add_variables!(p::PolyType, q::PolyType)
136136
return p
137137
end
138138

139-
function monoeval(z::Vector{Int}, vals::AbstractVector)
140-
@assert length(z) == length(vals)
139+
function _mono_eval(z::Union{Vector{Int},Tuple}, vals::AbstractVector)
140+
if length(z) != length(vals)
141+
error("Cannot evaluate a polynomial of `$(length(z))` variables with only `$(length(vals))` values.")
142+
end
141143
if isempty(z)
142144
return one(eltype(vals))^1
143145
end
@@ -154,24 +156,24 @@ function monoeval(z::Vector{Int}, vals::AbstractVector)
154156
return val
155157
end
156158

157-
_subs(st, ::Variable, vals) = monoeval([1], vals::AbstractVector)
158-
_subs(st, m::Monomial, vals) = monoeval(m.z, vals::AbstractVector)
159-
function _subs(st, t::_Term, vals)
160-
return MP.coefficient(t) * monoeval(MP.monomial(t).z, vals::AbstractVector)
159+
MP.substitute(::MP.AbstractSubstitutionType, ::Variable, vals::AbstractVector) = _mono_eval((1,), vals)
160+
MP.substitute(::MP.AbstractSubstitutionType, m::Monomial, vals::AbstractVector) = _mono_eval(m.z, vals)
161+
function MP.substitute(st::MP.AbstractSubstitutionType, t::_Term, vals::AbstractVector)
162+
return MP.coefficient(t) * MP.substitute(st, MP.monomial(t), vals)
161163
end
162-
function _subs(
164+
function MP.substitute(
163165
::MP.Eval,
164166
p::Polynomial{V,M,T},
165167
vals::AbstractVector{S},
166168
) where {V,M,T,S}
167169
# I need to check for iszero otherwise I get : ArgumentError: reducing over an empty collection is not allowed
168170
if iszero(p)
169-
zero(Base.promote_op(*, S, T))
171+
zero(MA.promote_operation(*, S, T))
170172
else
171-
sum(i -> p.a[i] * monoeval(p.x.Z[i], vals), eachindex(p.a))
173+
sum(i -> p.a[i] * _mono_eval(p.x.Z[i], vals), eachindex(p.a))
172174
end
173175
end
174-
function _subs(
176+
function MP.substitute(
175177
::MP.Subs,
176178
p::Polynomial{V,M,T},
177179
vals::AbstractVector{S},
@@ -182,7 +184,7 @@ function _subs(
182184
mergevars_of(Variable{V,M}, vals)[1],
183185
)
184186
for i in eachindex(p.a)
185-
MA.operate!(+, q, p.a[i] * monoeval(p.x.Z[i], vals))
187+
MA.operate!(+, q, p.a[i] * _mono_eval(p.x.Z[i], vals))
186188
end
187189
return q
188190
end
@@ -197,12 +199,30 @@ function MA.promote_operation(
197199
return MA.promote_operation(*, U, Monomial{V,M})
198200
end
199201

202+
function MP.substitute(
203+
st::MP.AbstractSubstitutionType,
204+
p::PolyType,
205+
s::MP.AbstractSubstitution...,
206+
)
207+
return MP.substitute(st, p, subsmap(st, MP.variables(p), s))
208+
end
209+
210+
# TODO resolve ambiguity. Can remove after:
211+
# https://github.com/JuliaAlgebra/MultivariatePolynomials.jl/pull/305
212+
function MP.substitute(
213+
st::MP.AbstractSubstitutionType,
214+
p::PolyType,
215+
s::MP.AbstractMultiSubstitution,
216+
)
217+
return MP.substitute(st, p, subsmap(st, MP.variables(p), (s,)))
218+
end
219+
200220
function MP.substitute(
201221
st::MP.AbstractSubstitutionType,
202222
p::PolyType,
203223
s::MP.Substitutions,
204224
)
205-
return _subs(st, p, subsmap(st, MP.variables(p), s))
225+
return MP.substitute(st, p, subsmap(st, MP.variables(p), s))
206226
end
207227

208228
(v::Variable)(s::MP.AbstractSubstitution...) = MP.substitute(MP.Eval(), v, s)
@@ -215,20 +235,20 @@ function (p::Monomial)(x::NTuple{N,<:Number}) where {N}
215235
return MP.substitute(MP.Eval(), p, variables(p) => x)
216236
end
217237
function (p::Monomial)(x::AbstractVector{<:Number})
218-
return MP.substitute(MP.Eval(), p, variables(p) => x)
238+
return MP.substitute(MP.Eval(), p, x)
219239
end
220240
(p::Monomial)(x::Number...) = MP.substitute(MP.Eval(), p, variables(p) => x)
221241
function (p::_Term)(x::NTuple{N,<:Number}) where {N}
222242
return MP.substitute(MP.Eval(), p, variables(p) => x)
223243
end
224244
function (p::_Term)(x::AbstractVector{<:Number})
225-
return MP.substitute(MP.Eval(), p, variables(p) => x)
245+
return MP.substitute(MP.Eval(), p, x)
226246
end
227247
(p::_Term)(x::Number...) = MP.substitute(MP.Eval(), p, variables(p) => x)
228248
function (p::Polynomial)(x::NTuple{N,<:Number}) where {N}
229249
return MP.substitute(MP.Eval(), p, variables(p) => x)
230250
end
231251
function (p::Polynomial)(x::AbstractVector{<:Number})
232-
return MP.substitute(MP.Eval(), p, variables(p) => x)
252+
return MP.substitute(MP.Eval(), p, x)
233253
end
234254
(p::Polynomial)(x::Number...) = MP.substitute(MP.Eval(), p, variables(p) => x)

test/runtests.jl

+18
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,25 @@ using MultivariatePolynomials
33
using Test
44
using LinearAlgebra
55

6+
function alloc_test_lt(f, n)
7+
f() # compile
8+
@test n >= @allocated f()
9+
end
10+
611
# TODO move to MP
12+
@testset "See https://github.com/jump-dev/SumOfSquares.jl/issues/388" begin
13+
@polyvar x[1:3]
14+
p = sum(x)
15+
v = map(_ -> 1, x)
16+
# I get 208 but let's give some margin
17+
alloc_test_lt(() -> substitute(Eval(), p, x => v), 300)
18+
alloc_test_lt(() -> p(x => v), 300)
19+
alloc_test_lt(() -> substitute(Eval(), p, v), 0)
20+
alloc_test_lt(() -> p(v), 0)
21+
err = ErrorException("Cannot evaluate a polynomial of `3` variables with only `2` values.")
22+
@test_throws err p([1, 2])
23+
end
24+
725
@testset "Issue #70" begin
826
@ncpolyvar y0 y1 x0 x1
927
p = x1 * x0 * x1

0 commit comments

Comments
 (0)