Skip to content

Commit 735af8b

Browse files
committed
Add parametric ideal and interpolation-based algorithms
1 parent 3b251d1 commit 735af8b

14 files changed

Lines changed: 785 additions & 28 deletions

src/AlgebraicSolving.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ include("algorithms/decomposition.jl")
1717
include("algorithms/param-curve.jl")
1818
include("algorithms/hilbert.jl")
1919
include("algorithms/sampling.jl")
20+
#= param-ideal =#
21+
include("algorithms/param-ideal/groebner-bases.jl")
22+
include("algorithms/param-ideal/multiplication-matrices.jl")
23+
include("algorithms/param-ideal/hermite-matrices.jl")
24+
include("algorithms/param-ideal/parametrizations.jl")
2025
#= siggb =#
2126
include("siggb/siggb.jl")
2227
#= progress =#
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
function _map_exponent_vectors(f, p::MPolyRingElem, R::MPolyRing)::MPolyRingElem
2+
cvzip = zip(coefficients(p), exponent_vectors(p))
3+
M = MPolyBuildCtx(R)
4+
for (c, v) in cvzip
5+
push_term!(M, coefficient_ring(R)(c), f(v))
6+
end
7+
finish(M)
8+
end
9+
10+
function _change_ring(p::MPolyRingElem, R::MPolyRing)::MPolyRingElem
11+
n = number_of_variables(parent(p))
12+
n′ = number_of_variables(R)
13+
if n == 0 || n′ == 0
14+
return R(constant_coefficient(p))
15+
end
16+
indices = Vector{Int}()
17+
for s in symbols(R)
18+
i = findfirst(==(s), symbols(parent(p)))
19+
if isnothing(i)
20+
push!(indices, 0)
21+
else
22+
push!(indices, i)
23+
end
24+
end
25+
return _map_exponent_vectors(v -> [i == 0 ? 0 : v[i] for i in indices], p, R)
26+
end
27+
28+
function _change_ring(v::Vector{<:MPolyRingElem}, R::MPolyRing)::Vector{<:MPolyRingElem}
29+
map(p -> _change_ring(p, R), v)
30+
end
31+
32+
function _change_ring(A::MatSpaceElem{<:MPolyRingElem}, R::MPolyRing)::MatSpaceElem{<:MPolyRingElem}
33+
map(f -> _change_ring(f, R), A)
34+
end
35+
36+
function _saturate(
37+
f::Vector{QQMPolyRingElem},
38+
g::QQMPolyRingElem;
39+
worker_pool::AbstractWorkerPool=default_worker_pool()
40+
)::Vector{QQMPolyRingElem}
41+
@assert !isempty(f)
42+
R = parent(f[1])
43+
n = number_of_variables(R)
44+
R′, vars = polynomial_ring(QQ, [:sat; symbols(R)])
45+
f′ = _change_ring(f, R′)
46+
g′ = _change_ring(g, R′)
47+
gb = AlgebraicSolving.groebner_basis(
48+
AlgebraicSolving.Ideal([f′; vars[1] * g′ - 1]);
49+
eliminate=1,
50+
worker_pool=worker_pool
51+
)
52+
gb = _change_ring(gb, R)
53+
gb
54+
end
55+
56+
function _radical(
57+
f::Vector{QQMPolyRingElem};
58+
worker_pool::AbstractWorkerPool=default_worker_pool()
59+
)::Vector{QQMPolyRingElem}
60+
@assert !isempty(f)
61+
R = parent(f[1])
62+
n = number_of_variables(R)
63+
s = symbols(R)
64+
rads = Vector{QQMPolyRingElem}()
65+
for _ in 1:n
66+
R′, _ = polynomial_ring(QQ, s, internal_ordering=internal_ordering(R))
67+
disc = AlgebraicSolving.groebner_basis(
68+
AlgebraicSolving.Ideal(_change_ring(f, R′));
69+
eliminate=n - 1,
70+
worker_pool=worker_pool
71+
)
72+
r = R′(1)
73+
for (p, _) in factor(prod(disc))
74+
r *= p
75+
end
76+
push!(rads, _change_ring(r, R))
77+
s = [s[2:end]; s[1]]
78+
end
79+
gb = AlgebraicSolving.groebner_basis(
80+
AlgebraicSolving.Ideal([f; rads]);
81+
worker_pool=worker_pool
82+
)
83+
gb = _change_ring(gb, R)
84+
gb
85+
end
86+
87+
function groebner_basis(
88+
I::ParametricIdeal{K},
89+
vals::Vector{QQFieldElem};
90+
worker_pool::AbstractWorkerPool=default_worker_pool()
91+
)::Vector{QQMPolyRingElem} where {K<:FracFieldElem}
92+
gb_spec_hash = hash((I.gens, vals))
93+
if haskey(I.gb_spec, gb_spec_hash)
94+
return I.gb_spec[gb_spec_hash]
95+
end
96+
n = I.num_vars
97+
elim = number_of_variables(parent(I.gens[1])) - n
98+
R = parent(I.gens[1])
99+
R₀, _ = polynomial_ring(QQ, symbols(R)[1:end], internal_ordering=:degrevlex)
100+
R₁, _ = polynomial_ring(QQ, I.symbols, internal_ordering=:degrevlex)
101+
fₓ = _change_ring(map(p -> map_coefficients(k -> evaluate(k, vals), p), I.gens), R₀)
102+
gb = AlgebraicSolving.groebner_basis(
103+
AlgebraicSolving.Ideal(fₓ);
104+
eliminate=elim,
105+
worker_pool=worker_pool
106+
)
107+
gb = _change_ring(gb, R₁)
108+
for sat in I.sats
109+
gb = _saturate(gb, _change_ring(map_coefficients(k -> evaluate(k, vals), sum(sat .^ 2)), R₁); worker_pool=worker_pool)
110+
end
111+
if I.radicalize
112+
gb = _radical(gb; worker_pool=worker_pool)
113+
end
114+
gb = map(p -> p / leading_coefficient(p), gb)
115+
lock(I.gb_spec_lock) do
116+
I.gb_spec[gb_spec_hash] = gb
117+
end
118+
gb
119+
end
120+
121+
function _is_zero_dimensional(gb::Vector{T})::Bool where {T<:MPolyRingElem}
122+
if isempty(gb)
123+
return false
124+
end
125+
R = parent(gb[1])
126+
n = number_of_variables(R)
127+
has_power = falses(n)
128+
for f in gb
129+
indices = var_indices(Nemo.leading_monomial(f))
130+
if isempty(indices)
131+
return true
132+
end
133+
if length(indices) == 1
134+
has_power[indices[1]] = true
135+
end
136+
end
137+
all(has_power)
138+
end
139+
140+
function _monomial_basis(gb::Vector{T})::Vector{T} where {T<:MPolyRingElem}
141+
if !_is_zero_dimensional(gb)
142+
error("The ideal is not zero-dimensional, so a finite monomial basis does not exist.")
143+
end
144+
R = parent(gb[1])
145+
n = number_of_variables(R)
146+
lms = [Nemo.leading_monomial(f) for f in gb]
147+
basis = typeof(gb[1])[]
148+
queue = [one(R)]
149+
while !isempty(queue)
150+
u = popfirst!(queue)
151+
if all(!divides(u, lm)[1] for lm in lms) && !(u in basis)
152+
push!(basis, u)
153+
for i in n:-1:1
154+
push!(queue, u * gens(R)[i])
155+
end
156+
end
157+
end
158+
basis
159+
end
160+
161+
function _expected_degree(
162+
I::ParametricIdeal{K};
163+
worker_pool::AbstractWorkerPool=default_worker_pool()
164+
)::Int where {K<:FracFieldElem}
165+
gb = groebner_basis(I, QQ.(I.shift); worker_pool=worker_pool)
166+
b = _monomial_basis(gb)
167+
length(b)
168+
end
169+
170+
@doc Markdown.doc"""
171+
groebner_basis(I::ParametricIdeal{K}; <keyword arguments>) -> Vector{Generic.MPoly{FracFieldElem{QQMPolyRingElem}}}
172+
173+
Computes a parametric Gröbner basis of the parametric ideal `I` w.r.t. the degree reverse lexicographic ordering. The Gröbner basis is computed by evaluating the ideal at a number of parameter values and interpolating the coefficients of the resulting Gröbner bases.
174+
175+
# Arguments
176+
- `retry::Int=10`: the maximum number of consecutive failures allowed when computing the Gröbner basis or interpolating the coefficients.
177+
- `nr_thrds::Int=1`: the number of threads to use for parallel computations.
178+
- `worker_pool::AbstractWorkerPool=default_worker_pool()`: the worker pool to use for parallel computations.
179+
- `show_progress::Bool=false`: whether to show a progress bar while computing the Gröbner basis.
180+
"""
181+
function groebner_basis(
182+
I::ParametricIdeal{K};
183+
retry::Int=10,
184+
nr_thrds::Int=1,
185+
worker_pool::AbstractWorkerPool=default_worker_pool(),
186+
show_progress::Bool=false
187+
)::Vector{Generic.MPoly{FracFieldElem{QQMPolyRingElem}}} where {K<:FracFieldElem}
188+
input_hash = hash(I.gens)
189+
if haskey(I.gb, input_hash)
190+
return I.gb[input_hash]
191+
end
192+
R = parent(I.gens[1])
193+
R₂ = base_ring(I.base_frac_field)
194+
R₄, _ = polynomial_ring(I.base_frac_field, I.symbols, internal_ordering=internal_ordering(R))
195+
gb_shift_mons = nothing
196+
function bb(v)
197+
gb = groebner_basis(I, v; worker_pool=worker_pool)
198+
if !isnothing(gb_shift_mons)
199+
gb_mons = [collect(monomials(p)) for p in gb]
200+
if gb_mons != gb_shift_mons
201+
error("Error evaluating black box function: the Gröbner basis has unexpected monomial structure at parameter values $(v).")
202+
end
203+
end
204+
gb
205+
end
206+
gb_shift = bb(QQ.(I.shift))
207+
gb_shift_mons = [collect(monomials(p)) for p in gb_shift]
208+
pgb = fill(R₄(0), length(gb_shift))
209+
len = length(gb_shift)
210+
len_mons = length.(gb_shift)
211+
for i in 1:len
212+
for j in 1:len_mons[i]
213+
denom = R₂(1)
214+
c = Interpolation.cuyt_lee(
215+
R₂, v -> evaluate(denom, v) * coeff(bb(v)[i], j);
216+
initial_shift=I.shift,
217+
retry=retry,
218+
nr_thrds=nr_thrds,
219+
show_progress=show_progress,
220+
desc="Parametric Gröbner basis, size=($len,$(maximum(len_mons))), index=($i,$j)"
221+
)
222+
pgb[i] += R₄(c) / denom * _change_ring(Nemo.monomial(gb_shift[i], j), R₄)
223+
end
224+
end
225+
lock(I.gb_lock) do
226+
I.gb[input_hash] = pgb
227+
end
228+
pgb
229+
end

0 commit comments

Comments
 (0)