|
| 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