diff --git a/.gitignore b/.gitignore index 96a94d25..89b5f199 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,13 @@ Manifest.toml LocalPreferences.toml -.vscode .benchmarkci + +# ignore coverage +*.cov +*.info +*.png +*.html +*.css + +# ignore editor settings +.vscode diff --git a/Project.toml b/Project.toml index aca06b3c..da34329f 100644 --- a/Project.toml +++ b/Project.toml @@ -29,7 +29,6 @@ DynamicPolynomials = "0.6.0" Nemo = "0.45.4, 0.46, 0.47" PrecompileTools = "1" Primes = "0.5" -TestSetExtensions = "2" julia = "1.10" [extras] @@ -38,7 +37,6 @@ DynamicPolynomials = "7c1d4256-1411-5781-91ec-d7bc3513ac07" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Nemo = "2edaba10-b0f1-5616-af89-8c11ac63239a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" [targets] -test = ["DynamicPolynomials", "BenchmarkTools", "InteractiveUtils", "Test", "TestSetExtensions", "Nemo"] +test = ["DynamicPolynomials", "BenchmarkTools", "InteractiveUtils", "Test", "Nemo"] diff --git a/benchmark/CI-scripts/.runtests.jl.swp b/benchmark/CI-scripts/.runtests.jl.swp new file mode 100644 index 00000000..6a197cb9 Binary files /dev/null and b/benchmark/CI-scripts/.runtests.jl.swp differ diff --git a/benchmark/CI-scripts/runtests.jl b/benchmark/CI-scripts/runtests.jl index 1459aa2c..bfc66bf0 100644 --- a/benchmark/CI-scripts/runtests.jl +++ b/benchmark/CI-scripts/runtests.jl @@ -105,7 +105,7 @@ function clean_data(results) ) append!(results_master[j], times_master) append!(results_nightly[j], times_nightly) - results_problems[j] = join(split(problem_name_master, ","), ", ") + results_problems[j] = join(split(problem_name_master, ","), " ") results_types[j] = type end end @@ -118,6 +118,7 @@ function compare() results_problems, results_types, results_master, results_nightly = clean_data(results) table = Matrix{Any}(undef, length(results_master), 4) fail = false + tolerance = 0.02 for (i, (master, nightly)) in enumerate(zip(results_master, results_nightly)) if results_types[i] == "time" master = 1e9 .* master @@ -133,22 +134,22 @@ function compare() 0, "insignificant" elseif (1 + MAX_DEVIATION) * m1 < m2 fail = true - 2, "slower❌" + 2, "worse❌" elseif m1 > (1 + MAX_DEVIATION) * m2 - 1, "faster✅" + 1, "better✅" else - 0, "insignificant" + 0, "don't care" end elseif results_types[i] == "allocs" label_master = mean(master) label_nightly = mean(nightly) - indicator = if label_master < label_nightly + indicator = if label_master < (1 - tolerance) * label_nightly fail = true 2, "worse❌" - elseif label_master > label_nightly + elseif label_master > (1 + tolerance) * label_nightly 1, "better✅" else - 0, "same" + 0, "don't care" end else error("Beda!") @@ -176,7 +177,7 @@ function post(fail, table) println(io, "No regressions detected✅") end table_header = ["Problem", "Master", "This commit", "Result"] - pretty_table(io, table, header=table_header) + pretty_table(io, table, header=table_header, alignment=[:l, :r, :r, :r]) comment_str = String(take!(io)) println(comment_str) end diff --git a/ext/GroebnerDynamicPolynomialsExt.jl b/ext/GroebnerDynamicPolynomialsExt.jl index f94edaf6..1de334f5 100644 --- a/ext/GroebnerDynamicPolynomialsExt.jl +++ b/ext/GroebnerDynamicPolynomialsExt.jl @@ -80,7 +80,7 @@ function io_extract_ring(orig_polys::Vector{<:AbstractPolynomialLike{T}}) where nv = MultivariatePolynomials.nvariables(orig_polys) ord = dp_ord_to_symbol(MultivariatePolynomials.ordering(orig_polys[1])) ord_typed = dp_ordering_sym2typed(ord) - Groebner.PolyRing{typeof(ord_typed), UInt}(nv, ord_typed, UInt(0)) + Groebner.PolyRing(nv, ord_typed, UInt(0)) end function io_extract_coeffs(ring, orig_polys) diff --git a/src/Groebner.jl b/src/Groebner.jl index 7e68bd47..18e617c5 100644 --- a/src/Groebner.jl +++ b/src/Groebner.jl @@ -93,13 +93,14 @@ include("monomials/exponent_vector.jl") include("monomials/packed_vector.jl") include("arithmetic/CompositeNumber.jl") +include("arithmetic/CoeffGeneric.jl") # Defines some type aliases include("types.jl") # Fast arithmetic modulo a prime include("arithmetic/Zp.jl") -include("arithmetic/QQ.jl") +include("arithmetic/generic.jl") # Intermediate representation include("input_output/intermediate.jl") diff --git a/src/arithmetic/CoeffGeneric.jl b/src/arithmetic/CoeffGeneric.jl new file mode 100644 index 00000000..1554a29f --- /dev/null +++ b/src/arithmetic/CoeffGeneric.jl @@ -0,0 +1,23 @@ +# This file is a part of Groebner.jl. License is GNU GPL v2. + +struct CoeffGeneric{T} + data::T +end + +generic_coeff_data(x) = x +generic_coeff_data(x::CoeffGeneric) = x.data + +CoeffGeneric(x::CoeffGeneric{T}) where {T} = x +CoeffGeneric{T}(x::CoeffGeneric{T}) where {T} = x + +Base.:+(x::CoeffGeneric, y::CoeffGeneric) = CoeffGeneric(x.data + y.data) +Base.:-(x::CoeffGeneric, y::CoeffGeneric) = CoeffGeneric(x.data - y.data) +Base.:*(x::CoeffGeneric, y::CoeffGeneric) = CoeffGeneric(x.data * y.data) +Base.inv(x::CoeffGeneric) = CoeffGeneric(inv(x.data)) +Base.:-(x::CoeffGeneric) = zero(x) - x + +Base.one(x::CoeffGeneric{T}) where {T} = CoeffGeneric{T}(one(x.data)) +Base.zero(x::CoeffGeneric{T}) where {T} = CoeffGeneric{T}(zero(x.data)) + +Base.isone(x::CoeffGeneric) = isone(x.data) +Base.iszero(x::CoeffGeneric) = iszero(x.data) diff --git a/src/arithmetic/CompositeNumber.jl b/src/arithmetic/CompositeNumber.jl index ab17c8a3..667738f6 100644 --- a/src/arithmetic/CompositeNumber.jl +++ b/src/arithmetic/CompositeNumber.jl @@ -10,10 +10,6 @@ function CompositeNumber{N, T}(a::CompositeNumber{N, U}) where {N, T, U} CompositeNumber{N, T}(a.data .% T) end -function CompositeNumber{N, FP}(a::CompositeNumber{N, U}) where {N, FP <: AbstractFloat, U} - CompositeNumber{N, FP}(a.data) -end - Base.convert(::Type{CompositeNumber{N, T}}, a::CompositeNumber{N, U}) where {N, T, U} = CompositeNumber{N, T}(map(x -> convert(T, x), a.data)) @@ -37,6 +33,9 @@ Base.isless(x::U, ci::CompositeNumber{N, T}) where {N, T, U <: Number} = all(x . Base.iszero(ci::CompositeNumber) = all(iszero, ci.data) Base.isone(ci::CompositeNumber) = all(isone, ci.data) +Base.zero(x::CompositeNumber{N, T}) where {N, T} = zero(typeof(x)) +Base.one(x::CompositeNumber{N, T}) where {N, T} = one(typeof(x)) + Base.zero(::Type{CompositeNumber{N, T}}) where {N, T} = CompositeNumber(ntuple(_ -> zero(T), Val(N))) Base.one(::Type{CompositeNumber{N, T}}) where {N, T} = CompositeNumber(ntuple(_ -> one(T), N)) diff --git a/src/arithmetic/QQ.jl b/src/arithmetic/generic.jl similarity index 75% rename from src/arithmetic/QQ.jl rename to src/arithmetic/generic.jl index 9ae26809..5efce673 100644 --- a/src/arithmetic/QQ.jl +++ b/src/arithmetic/generic.jl @@ -23,3 +23,11 @@ function select_arithmetic( @invariant iszero(characteristic) ArithmeticQQ() end + +# Arithmetic with generic coefficients. + +struct ArithmeticGeneric{CoeffType, AccumType} <: AbstractArithmetic{CoeffType, AccumType} end + +function select_arithmetic(::Type{CoeffGeneric}, _, _, _) + ArithmeticGeneric{CoeffGeneric, CoeffGeneric}() +end diff --git a/src/f4/basis.jl b/src/f4/basis.jl index c8e1628b..07f61301 100644 --- a/src/f4/basis.jl +++ b/src/f4/basis.jl @@ -172,7 +172,7 @@ function basis_well_formed(ring::PolyRing, basis::Basis, hashtable::MonomialHash ) && error("Bad polynomial") for j in 1:length(basis.coeffs[i]) iszero(basis.coeffs[i][j]) && error("Coefficient is zero") - (ring.ch > 0) && + (ring.ground == :zp) && !(basis.coeffs[i][j] < ring.ch) && error("Coefficients must be normalized") (basis.monoms[i][j] > hashtable.load) && error("Bad monomial") @@ -405,9 +405,9 @@ end function basis_make_monic!( basis::Basis{C}, - arithmetic::AbstractArithmeticQQ, + arithmetic::AbstractArithmetic, changematrix::Bool -) where {C <: CoeffQQ} +) where {C <: Union{CoeffQQ, CoeffGeneric}} cfs = basis.coeffs @inbounds for i in 1:(basis.n_filled) !isassigned(cfs, i) && continue diff --git a/src/f4/linalg/backend.jl b/src/f4/linalg/backend.jl index 85e665c7..552a8666 100644 --- a/src/f4/linalg/backend.jl +++ b/src/f4/linalg/backend.jl @@ -57,7 +57,7 @@ end function linalg_reduce_matrix_lower_part!( matrix::MacaulayMatrix{CoeffType}, basis::Basis{CoeffType}, - arithmetic::AbstractArithmetic{AccumType, CoeffType} + arithmetic::AbstractArithmetic{AccumType} ) where {CoeffType <: Coeff, AccumType <: Coeff} _, ncols = size(matrix) _, nlow = matrix_nrows_filled(matrix) @@ -68,7 +68,7 @@ function linalg_reduce_matrix_lower_part!( # Allocate the buffers # TODO: allocate just once, and then reuse for later iterations of F4 - row = zeros(AccumType, ncols) + row = [AccumType(zero(basis.coeffs[1][1])) for _ in 1:ncols] new_sparse_row_support, new_sparse_row_coeffs = linalg_new_empty_sparse_row(CoeffType) # At each iteration, take a row from the lower part of the matrix (from the @@ -127,7 +127,7 @@ end function linalg_interreduce_matrix_pivots!( matrix::MacaulayMatrix{CoeffType}, basis::Basis{CoeffType}, - arithmetic::AbstractArithmetic{AccumType, CoeffType}; + arithmetic::AbstractArithmetic{AccumType}; reversed_rows::Bool=false ) where {CoeffType <: Coeff, AccumType <: Coeff} _, ncols = size(matrix) @@ -141,7 +141,7 @@ function linalg_interreduce_matrix_pivots!( any_zeroed = false # Allocate the buffers - row = zeros(AccumType, ncols) + row = [AccumType(zero(basis.coeffs[1][1])) for _ in 1:ncols] # Indices of rows that did no reduce to zero not_reduced_to_zero = Vector{Int}(undef, nright) @@ -209,10 +209,10 @@ function linalg_interreduce_matrix_pivots!( true, any_zeroed, not_reduced_to_zero end -function linalg_reduce_matrix_lower_part_invariant_pivots!( +function linalg_reduce_matrix_lower_part_do_not_modify_pivots!( matrix::MacaulayMatrix{CoeffType}, basis::Basis{CoeffType}, - arithmetic::AbstractArithmetic{AccumType, CoeffType} + arithmetic::AbstractArithmetic{AccumType} ) where {CoeffType <: Coeff, AccumType <: Coeff} _, ncols = size(matrix) _, nlow = matrix_nrows_filled(matrix) @@ -222,7 +222,7 @@ function linalg_reduce_matrix_lower_part_invariant_pivots!( resize!(matrix.some_coeffs, nlow) # Allocate the buffers - row = zeros(AccumType, ncols) + row = [AccumType(zero(basis.coeffs[1][1])) for _ in 1:ncols] new_sparse_row_support, new_sparse_row_coeffs = linalg_new_empty_sparse_row(CoeffType) @inbounds for i in 1:nlow @@ -262,20 +262,19 @@ function linalg_reduce_matrix_lower_part_invariant_pivots!( matrix.npivots = matrix.nrows_filled_lower = matrix.nrows_filled_lower end -function linalg_reduce_matrix_lower_part_any_nonzero!( +function linalg_reduce_matrix_lower_part_all_zero!( matrix::MacaulayMatrix{CoeffType}, basis::Basis{CoeffType}, - arithmetic::AbstractArithmetic{AccumType, CoeffType} + arithmetic::AbstractArithmetic{AccumType} ) where {CoeffType <: Coeff, AccumType <: Coeff} _, ncols = size(matrix) _, nlow = matrix_nrows_filled(matrix) # Prepare the matrix pivots, row_idx_to_coeffs = linalg_prepare_matrix_pivots!(matrix) - resize!(matrix.some_coeffs, nlow) # Allocate the buffers - row = zeros(AccumType, ncols) + row = [AccumType(zero(basis.coeffs[1][1])) for _ in 1:ncols] new_sparse_row_support, new_sparse_row_coeffs = linalg_new_empty_sparse_row(CoeffType) @inbounds for i in 1:nlow @@ -330,10 +329,7 @@ function linalg_prepare_matrix_pivots!(matrix::MacaulayMatrix) pivots, lower_to_coeffs end -function linalg_prepare_matrix_pivots_in_interreduction!( - matrix::MacaulayMatrix, - basis::Basis{C} -) where {C} +function linalg_prepare_matrix_pivots_in_interreduction!(matrix::MacaulayMatrix, basis::Basis) _, ncols = size(matrix) nup, nlow = matrix_nrows_filled(matrix) @@ -603,7 +599,7 @@ function linalg_reduce_dense_row_by_pivots_sparse!( false end -# Specialization for arithmetic in the rational numbers +# Generic fallback. function linalg_reduce_dense_row_by_pivots_sparse!( new_sparse_row_support::Vector{I}, new_sparse_row_coeffs::Vector{C}, @@ -613,7 +609,7 @@ function linalg_reduce_dense_row_by_pivots_sparse!( pivots::Vector{Vector{I}}, start_column::Integer, end_column::Integer, - arithmetic::AbstractArithmeticQQ, + arithmetic::AbstractArithmetic, active_reducers=nothing; ignore_column::Integer=-1 ) where {I, C <: Coeff} @@ -704,7 +700,7 @@ function linalg_row_make_monic!( ) where {A <: Union{CoeffZp, CompositeCoeffZp}, T <: Union{CoeffZp, CompositeCoeffZp}} @invariant !iszero(row[first_nnz_index]) - lead = row[first_nnz_index] + @inbounds lead = row[first_nnz_index] isone(lead) && return lead @inbounds pinv = inv_mod_p(A(lead), arithmetic) % T @@ -716,18 +712,19 @@ function linalg_row_make_monic!( pinv end +# Generic fallback function linalg_row_make_monic!( row::Vector{T}, - arithmetic::AbstractArithmeticQQ{T}, + arithmetic::AbstractArithmetic, first_nnz_index::Int=1 -) where {T <: CoeffQQ} +) where {T <: Coeff} @invariant !iszero(row[first_nnz_index]) - lead = row[first_nnz_index] + @inbounds lead = row[first_nnz_index] isone(lead) && return lead @inbounds pinv = inv(lead) - @inbounds row[1] = one(T) + @inbounds row[1] = one(row[1]) @inbounds for i in 2:length(row) row[i] = row[i] * pinv end @@ -735,8 +732,8 @@ function linalg_row_make_monic!( pinv end -# Linear combination of dense vector and sparse vector modulo a prime. -# The most generic version. +# Linear combination of dense vector and sparse vector +# with explicit reduction modulo a prime. function linalg_vector_addmul_sparsedense_mod_p!( row::Vector{A}, indices::Vector{I}, @@ -759,7 +756,6 @@ function linalg_vector_addmul_sparsedense_mod_p!( end # Linear combination of dense vector and sparse vector. -# The most generic version. function linalg_vector_addmul_sparsedense!( row::Vector{A}, indices::Vector{I}, @@ -782,7 +778,6 @@ function linalg_vector_addmul_sparsedense!( end # Linear combination of dense vector and sparse vector. -# Specialization for SignedArithmeticZp. function linalg_vector_addmul_sparsedense!( row::Vector{A}, indices::Vector{I}, @@ -793,8 +788,8 @@ function linalg_vector_addmul_sparsedense!( @invariant length(indices) == length(coeffs) @invariant !isempty(indices) - # NOTE: mul is guaranteed to be < typemax(T) p2 = arithmetic.p2 + @invariant row[indices[1]] < typemax(T) @inbounds mul = row[indices[1]] % T @fastmath @inbounds for j in 1:length(indices) @@ -808,7 +803,6 @@ function linalg_vector_addmul_sparsedense!( end # Linear combination of dense vector and sparse vector. -# Specialization for SignedCompositeArithmeticZp. function linalg_vector_addmul_sparsedense!( row::Vector{CompositeNumber{N, A}}, indices::Vector{I}, @@ -819,8 +813,8 @@ function linalg_vector_addmul_sparsedense!( @invariant length(indices) == length(coeffs) @invariant !isempty(indices) - # NOTE: mul is guaranteed to be < typemax(T) p2 = arithmetic.p2s + @invariant row[indices[1]] < typemax(T) @inbounds mul = row[indices[1]].data .% T @fastmath @inbounds for j in 1:length(indices) @@ -833,14 +827,13 @@ function linalg_vector_addmul_sparsedense!( nothing end -# Linear combination of dense vector and sparse vector. -# Specialization for AbstractArithmeticQQ. +# Generic fallback. function linalg_vector_addmul_sparsedense!( - row::Vector{T}, + row::Vector{A}, indices::Vector{I}, coeffs::Vector{T}, - arithmetic::AbstractArithmeticQQ{T} -) where {I, T <: CoeffQQ} + arithmetic::AbstractArithmetic +) where {I, A <: Coeff, T <: Coeff} @invariant isone(coeffs[1]) @invariant length(indices) == length(coeffs) @invariant !isempty(indices) @@ -848,23 +841,24 @@ function linalg_vector_addmul_sparsedense!( @inbounds mul = -row[indices[1]] @inbounds for j in 1:length(indices) idx = indices[j] - row[idx] = row[idx] + mul * coeffs[j] + row[idx] = row[idx] + A(mul) * coeffs[j] end nothing end -# Load the contiguous coefficients from `coeffs` into dense `row` at `indices`. -# Zero the entries of `row` before that. +# Loads the given sparse row into the given dense row. +# Zero out the entries of the dense row before that. function linalg_load_sparse_row!( row::Vector{A}, indices::Vector{I}, coeffs::Vector{T} -) where {I, A <: Union{CoeffZp, CompositeCoeffZp}, T <: Union{CoeffZp, CompositeCoeffZp}} +) where {I, A <: Coeff, T <: Coeff} @invariant length(indices) == length(coeffs) + @inbounds z = zero(row[1]) @inbounds for i in 1:length(row) - row[i] = zero(A) + row[i] = z end @inbounds for j in 1:length(indices) @@ -874,77 +868,54 @@ function linalg_load_sparse_row!( nothing end -# Load the contiguous coefficients from `coeffs` into dense `row` at `indices`. -# Zero the entries of `row` before that. -function linalg_load_sparse_row!( - row::Vector{T}, - indices::Vector{I}, - coeffs::Vector{T} -) where {I, T <: CoeffQQ} - @invariant length(indices) == length(coeffs) - - row .= T(0) - @inbounds for j in 1:length(indices) - row[indices[j]] = coeffs[j] - end - - nothing -end - -# Traverses the dense `row` at positions `from..to` and extracts all nonzero -# entries to the given sparse row. Returns the number of extracted nonzero -# elements. +# Extracts nonzeroes from the given dense row into the given sparse row. +# Returns the number of extracted nonzeroes. function linalg_extract_sparse_row!( indices::Vector{I}, coeffs::Vector{T}, row::Vector{A}, - from::J, - to::J -) where {I, J, T <: Coeff, A <: Coeff} - # NOTE: assumes that the sparse row has the necessary capacity allocated + from::Int, + to::Int +) where {I, T <: Coeff, A <: Coeff} + # NOTE: assumes that the sparse row has the necessary capacity @invariant length(indices) == length(coeffs) @invariant 1 <= from <= to <= length(row) - z = zero(A) - j = 1 + nnz = 1 @inbounds for i in from:to - if row[i] != z - indices[j] = i - # row[i] is expected to be less than typemax(T) - coeffs[j] = row[i] % T - j += 1 + if !iszero(row[i]) + indices[nnz] = i + @invariant row[i] <= typemax(T) + coeffs[nnz] = row[i] % T + nnz += 1 end end - @invariant j - 1 <= length(coeffs) - j - 1 + @invariant nnz - 1 <= length(coeffs) + nnz - 1 end -# Traverses the dense `row` at positions `from..to` and extracts all nonzero -# entries to the given sparse row. Returns the number of extracted nonzero -# elements. -# Specialization with eltype(coeffs) == eltype(row) +# Generic fallback. function linalg_extract_sparse_row!( indices::Vector{I}, coeffs::Vector{T}, row::Vector{T}, - from::J, - to::J -) where {I, J, T <: Coeff} - # NOTE: assumes that the sparse row has the necessary capacity allocated + from::Int, + to::Int +) where {I, T <: Coeff} + # NOTE: assumes that the sparse row has the necessary capacity @invariant length(indices) == length(coeffs) @invariant 1 <= from <= to <= length(row) - z = zero(T) - j = 1 + nnz = 1 @inbounds for i in from:to - if row[i] != z - indices[j] = i - coeffs[j] = row[i] - j += 1 + if !iszero(row[i]) + indices[nnz] = i + coeffs[nnz] = row[i] + nnz += 1 end end - @invariant j - 1 <= length(coeffs) - j - 1 + @invariant nnz - 1 <= length(coeffs) + nnz - 1 end diff --git a/src/f4/linalg/linalg.jl b/src/f4/linalg/linalg.jl index 890322b7..d53a025d 100644 --- a/src/f4/linalg/linalg.jl +++ b/src/f4/linalg/linalg.jl @@ -243,11 +243,11 @@ end function _linalg_normalform!(matrix::MacaulayMatrix, basis::Basis, arithmetic::AbstractArithmetic) sort_matrix_upper_rows!(matrix) - linalg_reduce_matrix_lower_part_invariant_pivots!(matrix, basis, arithmetic) + linalg_reduce_matrix_lower_part_do_not_modify_pivots!(matrix, basis, arithmetic) end function _linalg_isgroebner!(matrix::MacaulayMatrix, basis::Basis, arithmetic::AbstractArithmetic) sort_matrix_upper_rows!(matrix) sort_matrix_lower_rows!(matrix) - linalg_reduce_matrix_lower_part_any_nonzero!(matrix, basis, arithmetic) + linalg_reduce_matrix_lower_part_all_zero!(matrix, basis, arithmetic) end diff --git a/src/f4/trace.jl b/src/f4/trace.jl index 22e22e47..21d7ed1a 100644 --- a/src/f4/trace.jl +++ b/src/f4/trace.jl @@ -104,7 +104,7 @@ function trace_deepcopy( Trace( trace.stopwatch, trace.empty, - PolyRing(trace.ring.nvars, trace.ring.ord, trace.ring.ch), + PolyRing(trace.ring.nvars, trace.ring.ord, trace.ring.ch, trace.ring.ground), deepcopy(trace.original_ord), basis_deepcopy(trace.input_basis), basis_deepcopy(trace.buf_basis), @@ -179,7 +179,7 @@ function trace_copy( new_representation = PolynomialRepresentation(trace.representation.monomtype, C2, using_wide_type_for_coeffs) - new_ring = PolyRing(trace.ring.nvars, trace.ring.ord, zero(C2)) + new_ring = PolyRing(trace.ring.nvars, trace.ring.ord, zero(C2), trace.ring.ground) Trace( trace.stopwatch, @@ -229,16 +229,16 @@ mutable struct WrappedTrace # For each type of coefficients, we maintain a separate tracer object recorded_traces::Dict{Any, Any} - sys_support::Vector{Vector{Vector{IRexponent}}} - gb_support::Vector{Vector{Vector{IRexponent}}} + sys_support::Vector{Vector{Vector{IR_exponent}}} + gb_support::Vector{Vector{Vector{IR_exponent}}} function WrappedTrace( trace::Trace{C1, C2, M, Ord1, Ord2} ) where {C1 <: Coeff, C2 <: Coeff, M <: Monom, Ord1, Ord2} new( Dict{Any, Any}((C1, 42) => trace), - Vector{Vector{Vector{IRexponent}}}(), - Vector{Vector{Vector{IRexponent}}}() + Vector{Vector{Vector{IR_exponent}}}(), + Vector{Vector{Vector{IR_exponent}}}() ) end end @@ -294,8 +294,12 @@ function get_trace!(wrapped_trace::WrappedTrace, ring::PolyRing, params::Algorit deepcopy=false ) new_trace.ring.ord != ring.ord && throw(DomainError("ordering invalid in trace")) - new_trace.ring = - PolyRing(ring.nvars, ring.ord, convert(params.representation.coefftype, ring.ch)) + new_trace.ring = PolyRing( + ring.nvars, + ring.ord, + convert(params.representation.coefftype, ring.ch), + ring.ground + ) wrapped_trace.recorded_traces[(params.representation.coefftype, 1)] = new_trace # the resulting trace may be in a invalid state, and needs to be filled with diff --git a/src/groebner/groebner.jl b/src/groebner/groebner.jl index c42fd311..ac37956c 100644 --- a/src/groebner/groebner.jl +++ b/src/groebner/groebner.jl @@ -104,6 +104,21 @@ function _groebner2( gbmonoms, gbcoeffs end +### +# Groebner basis over generic field. Calls F4 directly. + +function _groebner2( + ring::PolyRing, + monoms::Vector{Vector{M}}, + coeffs::Vector{Vector{C}}, + params::AlgorithmParameters +) where {M <: Monom, C <: CoeffGeneric} + basis, pairset, hashtable = f4_initialize_structs(ring, monoms, coeffs, params) + f4!(ring, basis, pairset, hashtable, params) + gbmonoms, gbcoeffs = basis_export_data(basis, hashtable) + gbmonoms, gbcoeffs +end + ### # Groebner basis over Q. diff --git a/src/groebner/homogenization.jl b/src/groebner/homogenization.jl index d3c79853..bbbf20e0 100644 --- a/src/groebner/homogenization.jl +++ b/src/groebner/homogenization.jl @@ -69,7 +69,7 @@ function homogenize_generators!( end # TODO: clarify the order of variables new_ord = extend_ordering_in_homogenization(ring.nvars, ring.ord) - new_ring = PolyRing(new_nvars, new_ord, ring.ch) + new_ring = PolyRing(new_nvars, new_ord, ring.ch, ring.ground) term_permutation = sort_input_terms_to_change_ordering!(new_monoms, coeffs, new_ord) sat_var_index = new_nvars new_ring_sat, new_monoms, coeffs = @@ -107,7 +107,7 @@ function dehomogenize_generators!( deleteat!(new_monoms, reduced_to_zero) deleteat!(coeffs, reduced_to_zero) new_ord = restrict_ordering_in_dehomogenization(ring_desat.ord) - new_ring = PolyRing(new_nvars, new_ord, ring_desat.ch) + new_ring = PolyRing(new_nvars, new_ord, ring_desat.ch, ring.ground) sort_input_terms_to_change_ordering!(new_monoms, coeffs, new_ord) params.target_ord = new_ring.ord new_ring, new_monoms, coeffs @@ -152,7 +152,7 @@ function desaturate_generators!( resize!(new_sparse_row_coeffs, new_size) resize!(new_monoms, new_size) new_ord = restrict_ordering_in_desaturation(ring.ord) - new_ring = PolyRing(new_nvars, new_ord, ring.ch) + new_ring = PolyRing(new_nvars, new_ord, ring.ch, ring.ground) params.target_ord = new_ring.ord new_ring, new_monoms, new_sparse_row_coeffs end @@ -189,13 +189,19 @@ function saturate_generators_by_variable!( new_poly_monoms[1] = lead_monom new_poly_monoms[2] = const_monom new_poly_coeffs = Vector{C}(undef, 2) - new_poly_coeffs[1] = one(C) - # NOTE: minus one in the current ground field - new_poly_coeffs[2] = iszero(ring.ch) ? -one(C) : (ring.ch - one(ring.ch)) + new_poly_coeffs[1] = one(coeffs[1][1]) + minus_one = if ring.ground == :qq + -one(coeffs[1][1]) + elseif ring.ground == :zp + ring.ch - one(coeffs[1][1]) + else + -one(coeffs[1][1]) + end + new_poly_coeffs[2] = minus_one push!(new_monoms, new_poly_monoms) push!(coeffs, new_poly_coeffs) new_ord = extend_ordering_in_saturation(ring.nvars, ring.ord) - new_ring = PolyRing(new_nvars, new_ord, ring.ch) + new_ring = PolyRing(new_nvars, new_ord, ring.ch, ring.ground) params.target_ord = new_ring.ord new_ring, new_monoms, coeffs end diff --git a/src/groebner/isgroebner.jl b/src/groebner/isgroebner.jl index a1d57850..a34e110a 100644 --- a/src/groebner/isgroebner.jl +++ b/src/groebner/isgroebner.jl @@ -57,6 +57,18 @@ function _isgroebner2( res end +# Generic fields +function _isgroebner2( + ring::PolyRing, + monoms::Vector{Vector{M}}, + coeffs::Vector{Vector{C}}, + params::AlgorithmParameters +) where {M <: Monom, C <: CoeffGeneric} + basis, pairset, hashtable = f4_initialize_structs(ring, monoms, coeffs, params) + res = f4_isgroebner!(ring, basis, pairset, hashtable, params.arithmetic) + res +end + # Rational numbers function _isgroebner2( ring::PolyRing, diff --git a/src/groebner/learn_apply.jl b/src/groebner/learn_apply.jl index 97d24120..55efb689 100644 --- a/src/groebner/learn_apply.jl +++ b/src/groebner/learn_apply.jl @@ -213,9 +213,9 @@ function __groebner_apply1!( if params.homogenize new_ord = extend_ordering_in_homogenization(ring.nvars, ring.ord) - ring = PolyRing(ring.nvars + 1, new_ord, ring.ch) + ring = PolyRing(ring.nvars + 1, new_ord, ring.ch, ring.ground) new_ord = extend_ordering_in_saturation(ring.nvars, ring.ord) - ring = PolyRing(ring.nvars + 1, new_ord, ring.ch) + ring = PolyRing(ring.nvars + 1, new_ord, ring.ch, ring.ground) end trace = get_trace!(wrapped_trace, ring, params) diff --git a/src/groebner/modular.jl b/src/groebner/modular.jl index 1ec85933..cacdda35 100644 --- a/src/groebner/modular.jl +++ b/src/groebner/modular.jl @@ -174,7 +174,7 @@ function modular_reduce_mod_p!( coeffs_ff[i][j] = CoeffModular(buf) end end - ring_ff = PolyRing(ring.nvars, ring.ord, UInt(prime)) + ring_ff = PolyRing(ring.nvars, ring.ord, UInt(prime), :zp) ring_ff, coeffs_ff end @@ -217,7 +217,7 @@ function modular_reduce_mod_p_in_batch!( coeffs_ff_xn[i][j] = CompositeNumber{N, T}(data) end end - ring_ff_4x = PolyRing(ring.nvars, ring.ord, CompositeNumber{N, T}(prime_xn)) + ring_ff_4x = PolyRing(ring.nvars, ring.ord, CompositeNumber{N, T}(prime_xn), :zp) basis_ff_4x = basis_deep_copy_with_new_coeffs(basis, coeffs_ff_xn) ring_ff_4x, basis_ff_4x @@ -403,7 +403,7 @@ function modular_lift_certify_check!( params::AlgorithmParameters ) gb_qq = basis_deep_copy_with_new_coeffs(gb_ff, state.gb_coeffs_qq) - ring_qq = PolyRing(ring.nvars, ring.ord, 0) + ring_qq = PolyRing(ring.nvars, ring.ord, 0, :qq) input_qq = basis_deepcopy(input_qq) # Check that some polynomial is not reduced to zero f4_normalform!(ring_qq, gb_qq, input_qq, hashtable, params.arithmetic) diff --git a/src/groebner/parameters.jl b/src/groebner/parameters.jl index 97d1e757..3301f62e 100644 --- a/src/groebner/parameters.jl +++ b/src/groebner/parameters.jl @@ -22,6 +22,7 @@ end function param_select_polynomial_representation( char::Coeff, nvars::Int, + ground::Symbol, ordering::AbstractMonomialOrdering, homogenize::Bool, monoms::Symbol, @@ -33,7 +34,7 @@ function param_select_polynomial_representation( end monomtype = param_select_monomtype(char, nvars, ordering, homogenize, hint, monoms) coefftype, using_wide_type_for_coeffs = - param_select_coefftype(char, nvars, ordering, homogenize, hint, monoms, arithmetic) + param_select_coefftype(char, nvars, ground, ordering, homogenize, hint, monoms, arithmetic) PolynomialRepresentation(monomtype, coefftype, using_wide_type_for_coeffs) end @@ -124,6 +125,7 @@ end function param_select_coefftype( char::Coeff, nvars::Int, + ground::Symbol, ordering::AbstractMonomialOrdering, homogenize::Bool, hint::Symbol, @@ -131,6 +133,9 @@ function param_select_coefftype( arithmetic::Symbol; using_wide_type_for_coeffs::Bool=false ) + if ground == :generic + return CoeffGeneric, true + end if iszero(char) return Rational{BigInt}, true end @@ -238,8 +243,13 @@ function AlgorithmParameters(ring::PolyRing, kwargs::KeywordArguments; hint=:non end end + # Over Z_p, linalg = :randomized signals randomized linear algebra. + # Over Q, linalg = :randomized has almost no effect since multi-modular + # tracing is used by default. Still, we say "almost" since some routines in + # multi-modular tracing may compute Groebner bases in Zp for + # checking/verification, and they benefit from linalg = :randomized. linalg = kwargs.linalg - if !iszero(ring.ch) && (linalg === :randomized || linalg === :auto) + if ring.ground === :zp && (linalg === :randomized || linalg === :auto) # Do not use randomized linear algebra if the field characteristic is # too small. # TODO: In the future, it would be good to adapt randomized linear @@ -253,6 +263,9 @@ function AlgorithmParameters(ring::PolyRing, kwargs::KeywordArguments; hint=:non linalg = :deterministic end end + if ring.ground == :generic + linalg = :deterministic + end if linalg === :auto linalg = :randomized end @@ -262,6 +275,7 @@ function AlgorithmParameters(ring::PolyRing, kwargs::KeywordArguments; hint=:non representation = param_select_polynomial_representation( ring.ch, ring.nvars, + ring.ground, target_ord, homogenize, kwargs.monoms, @@ -276,11 +290,6 @@ function AlgorithmParameters(ring::PolyRing, kwargs::KeywordArguments; hint=:non representation.using_wide_type_for_coeffs ) - ground = :zp - if iszero(ring.ch) - ground = :qq - end - reduced = kwargs.reduced threaded = kwargs.threaded @@ -296,13 +305,15 @@ function AlgorithmParameters(ring::PolyRing, kwargs::KeywordArguments; hint=:non threaded = :no end - if ground === :zp + if ring.ground === :zp threaded_f4 = threaded threaded_multimodular = :no - else - @assert ground === :qq + elseif ring.ground == :qq threaded_f4 = :no threaded_multimodular = threaded + else + threaded_f4 = :no + threaded_multimodular = :no end # By default, modular computation uses learn & apply @@ -325,6 +336,15 @@ function AlgorithmParameters(ring::PolyRing, kwargs::KeywordArguments; hint=:non if !(target_ord isa DegRevLex) throw(DomainError("Only DegRevLex is supported with changematrix = true.")) end + if (ring.ground == :generic) + throw(DomainError("Generic fields are not supported with changematrix = true.")) + end + end + + if kwargs.function_id == :groebner_learn || kwargs.function_id == :groebner_apply! + if ring.ground == :generic + throw(DomainError("Generic fields are not supported with learn & apply.")) + end end AlgorithmParameters( diff --git a/src/input_output/AbstractAlgebra.jl b/src/input_output/AbstractAlgebra.jl index c68b76af..425ceea2 100644 --- a/src/input_output/AbstractAlgebra.jl +++ b/src/input_output/AbstractAlgebra.jl @@ -16,10 +16,13 @@ aa_is_multivariate_ring(ring) = AbstractAlgebra.elem_type(ring) <: AbstractAlgeb function io_convert_polynomials_to_ir(polynomials, options::KeywordArguments) isempty(polynomials) && throw(DomainError("Empty input.")) ring = io_extract_ring(polynomials) + if options._generic + ring.ground = :generic + end coeffs = io_extract_coeffs_ir(ring, polynomials) reversed_order, var_to_index, monoms = io_extract_monoms_ir(ring, polynomials) @invariant length(coeffs) == length(monoms) - ring = PolyRing(ring.nvars, ordering_transform(ring.ord, var_to_index), ring.ch) + ring = PolyRing(ring.nvars, ordering_transform(ring.ord, var_to_index), ring.ch, ring.ground) options.ordering = ordering_transform(options.ordering, var_to_index) ring, monoms, coeffs, options end @@ -40,10 +43,44 @@ function io_extract_ring(polynomials) f -> f isa AbstractAlgebra.MPolyRingElem || f isa AbstractAlgebra.PolyRingElem, polynomials ) - throw(DomainError("Unknown type.")) + throw(DomainError("Unknown type of polynomials: $(typeof(polynomials)).")) + end + R = AbstractAlgebra.parent(first(polynomials)) + K = AbstractAlgebra.base_ring(R) + if !(K isa AbstractAlgebra.Field) + throw(DomainError("Coefficient ring must be a field, but got: $K")) + end + ch = AbstractAlgebra.characteristic(R) + ground = iszero(ch) ? :qq : :zp + # Only characteristics < 2^64 are supported natively + if (ch > typemax(UInt64)) + ground = :generic + end + if !iszero(ch) + # Only prime fields are supported natively + if !(hasmethod(AbstractAlgebra.degree, (typeof(K),))) + ground = :generic + elseif !isone(AbstractAlgebra.degree(K)) + ground = :generic + end + else + # Supported implementations of rationals are AbstractAlgebra.QQ or Nemo.QQ + if !(hasmethod(AbstractAlgebra.base_ring, (typeof(K),))) + ground = :generic + else + base = AbstractAlgebra.base_ring(K) + if !(hasmethod(AbstractAlgebra.base_ring, (typeof(base),))) + ground = :generic + elseif !(AbstractAlgebra.base_ring(base) == Union{}) + ground = :generic + end + end + end + if ground == :generic + @warn "Groebner.jl does not have a native implementation for the given field: $K.\n" * + "Falling back to a generic implementation (may be slow).\n" * + "If this is unexpected, please consider submitting a GitHub issue." maxlog = 1 end - _io_check_input(polynomials) - R = parent(first(polynomials)) nv = AbstractAlgebra.nvars(R) # lex is the default ordering on univariate polynomials ord = if aa_is_multivariate_ring(R) @@ -53,12 +90,15 @@ function io_extract_ring(polynomials) end # type unstable: ordT = ordering_sym2typed(ord) - ch = AbstractAlgebra.characteristic(R) - PolyRing{typeof(ordT), UInt}(nv, ordT, UInt(ch)) + ch_uint = ground == :zp ? UInt(ch) : UInt(0) + ring = PolyRing(nv, ordT, ch_uint, ground) + ring end function io_extract_coeffs_ir(ring::PolyRing, polys) - if ring.ch > 0 + if ring.ground == :generic + io_extract_coeffs_ir_generic(ring, polys) + elseif ring.ground == :zp io_extract_coeffs_ir_ff(ring, polys) else io_extract_coeffs_ir_qq(ring, polys) @@ -100,6 +140,22 @@ function io_extract_coeffs_ir_qq(ring::PolyRing, polys) res end +function io_extract_coeffs_ir_generic(ring::PolyRing, polys) + T = AbstractAlgebra.elem_type(AbstractAlgebra.base_ring(polys[1])) + res = Vector{Vector{CoeffGeneric{T}}}(undef, length(polys)) + for i in 1:length(polys) + poly = polys[i] + if !aa_is_multivariate_ring(parent(polys[1])) + res[i] = map(CoeffGeneric{T}, collect(AbstractAlgebra.coefficients(poly))) + reverse!(res[i]) + filter!(!iszero, res[i]) + else + res[i] = map(CoeffGeneric{T}, collect(AbstractAlgebra.coefficients(poly))) + end + end + res +end + function io_extract_monoms_ir(ring::PolyRing, polys) ring_aa = AbstractAlgebra.parent(polys[1]) v = AbstractAlgebra.gens(ring_aa) @@ -125,23 +181,6 @@ end ### # Converting from AbstractAlgebra to internal representation. -function _io_check_input(polynomials::Vector{T}) where {T} - R = AbstractAlgebra.parent(first(polynomials)) - K = AbstractAlgebra.base_ring(R) - if !(K isa AbstractAlgebra.Field) - throw(DomainError("Coefficient ring must be a field: $K")) - end - if (AbstractAlgebra.characteristic(K) > typemax(UInt64)) - throw(DomainError("Field characteristic must be less than 2^64")) - end - if !iszero(AbstractAlgebra.characteristic(K)) - if !isone(AbstractAlgebra.degree(K)) - throw(DomainError("Non-prime coefficient fields are not supported")) - end - end - true -end - function ordering_sym2typed(ord::Symbol) if !(ord in aa_supported_orderings) __throw_input_not_supported(ord, "Not a supported ordering.") @@ -178,7 +217,7 @@ function _io_convert_ir_to_polynomials( end cfs = zeros(ground, Int(sum(gbexps[i][1]) + 1)) for (idx, j) in enumerate(gbexps[i]) - cfs[sum(j) + 1] = ground(gbcoeffs[i][idx]) + cfs[sum(j) + 1] = ground(generic_coeff_data(gbcoeffs[i][idx])) end exported[i] = origring(cfs) end @@ -197,7 +236,7 @@ function _io_convert_ir_to_polynomials( ground = AbstractAlgebra.base_ring(origring) exported = Vector{elem_type(origring)}(undef, length(gbexps)) @inbounds for i in 1:length(gbexps) - cfs = map(ground, gbcoeffs[i]) + cfs = map(ground ∘ generic_coeff_data, gbcoeffs[i]) exps = Vector{Vector{Int}}(undef, length(gbcoeffs[i])) for jt in 1:length(gbcoeffs[i]) exps[jt] = gbexps[i][jt] @@ -269,7 +308,7 @@ function _io_convert_ir_to_polynomials( exported = Vector{elem_type(origring)}(undef, length(gbexps)) tmp = Vector{aa_exponent_type}(undef, nv) @inbounds for i in 1:length(gbexps) - cfs = map(ground, gbcoeffs[i]) + cfs = map(ground ∘ generic_coeff_data, gbcoeffs[i]) exps = Matrix{aa_exponent_type}(undef, nv + 1, length(gbcoeffs[i])) for jt in 1:length(gbcoeffs[i]) for k in 1:length(tmp) @@ -295,7 +334,7 @@ function _io_convert_ir_to_polynomials( exported = Vector{elem_type(origring)}(undef, length(gbexps)) tmp = Vector{aa_exponent_type}(undef, nv) @inbounds for i in 1:length(gbexps) - cfs = map(ground, gbcoeffs[i]) + cfs = map(ground ∘ generic_coeff_data, gbcoeffs[i]) exps = Matrix{aa_exponent_type}(undef, nv, length(gbcoeffs[i])) for jt in 1:length(gbcoeffs[i]) exps[end:-1:1, jt] .= gbexps[i][jt] @@ -318,7 +357,7 @@ function _io_convert_ir_to_polynomials( exported = Vector{elem_type(origring)}(undef, length(gbexps)) tmp = Vector{aa_exponent_type}(undef, nv) @inbounds for i in 1:length(gbexps) - cfs = map(ground, gbcoeffs[i]) + cfs = map(ground ∘ generic_coeff_data, gbcoeffs[i]) exps = Matrix{aa_exponent_type}(undef, nv + 1, length(gbcoeffs[i])) for jt in 1:length(gbcoeffs[i]) # monom_to_vector!(tmp, gbexps[i][jt]) @@ -343,7 +382,7 @@ function _io_convert_ir_to_polynomials( exported = Vector{elem_type(origring)}(undef, length(gbexps)) tmp = Vector{aa_exponent_type}(undef, nv) @inbounds for i in 1:length(gbexps) - cfs = map(ground, gbcoeffs[i]) + cfs = map(ground ∘ generic_coeff_data, gbcoeffs[i]) exps = Vector{Vector{Int}}(undef, length(gbcoeffs[i])) for jt in 1:length(gbcoeffs[i]) exps[jt] = gbexps[i][jt] diff --git a/src/input_output/intermediate.jl b/src/input_output/intermediate.jl index 09ff41d4..ed3e5b84 100644 --- a/src/input_output/intermediate.jl +++ b/src/input_output/intermediate.jl @@ -7,7 +7,8 @@ # an array of exponent vectors and an array of coefficients. Zero polynomial is # represented with two empty arrays. -const IRexponent = UInt32 +const IR_exponent = UInt32 +const IR_coeff = Union{Number, CoeffGeneric} # Intermediate representation (ir) enforces a number assumtpions: # - Intermediate representation owns the memory. @@ -23,14 +24,27 @@ mutable struct PolyRing{Ord <: AbstractMonomialOrdering, C <: Union{CoeffZp, Com nvars::Int ord::Ord ch::C + ground::Symbol + + PolyRing(nvars::Int, ord, ch) = PolyRing(nvars, ord, ch, iszero(ch) ? :qq : :zp) + + function PolyRing(nvars::Int, ord::Ord, ch::C, ground::Symbol) where {Ord, C} + !(ground in (:zp, :qq, :generic)) && throw(DomainError("Invalid ground field.")) + !(nvars >= 0) && throw(DomainError("The number of variables must be non-negative.")) + new{Ord, C}(nvars, ord, ch, ground) + end end -Base.:(==)(r1::PolyRing, r2::PolyRing) = r1.nvars == r2.nvars && r1.ord == r2.ord && r1.ch == r2.ch +Base.:(==)(r1::PolyRing, r2::PolyRing) = + r1.nvars == r2.nvars && r1.ord == r2.ord && r1.ch == r2.ch && r1.ground == r2.ground ir_is_valid_basic(batch) = throw(DomainError("Invalid IR, unknown types.")) ir_is_valid_basic(ring, monoms, coeffs) = throw(DomainError("Invalid IR, unknown types.")) function ir_is_valid_basic(batch::NTuple{N, T}) where {N, T} + for el in batch + length(el) != 3 && throw(DomainError("Invalid IR.")) + end for el in batch ir_is_valid_basic(el...) end @@ -38,39 +52,40 @@ end function ir_is_valid_basic( ring::PolyRing, - monoms::Vector{Vector{Vector{T}}}, + monoms::Vector{Vector{Vector{E}}}, coeffs::Vector{Vector{C}} -) where {T <: Integer, C <: Number} +) where {E <: Integer, C <: IR_coeff} !(length(monoms) == length(coeffs)) && throw(DomainError("Invalid IR.")) isempty(monoms) && throw(DomainError("Invalid IR.")) !(ring.nvars >= 0) && throw(DomainError("The number of variables must be non-negative.")) !(ring.ch >= 0) && throw(DomainError("Field characteristic must be nonnegative.")) - if ring.ch > 0 + if ring.ground == :zp !(C <: Integer) && throw(DomainError("Coefficients must be integers.")) (C <: BigInt) && throw(DomainError("Coefficients must fit in a machine register.")) !(ring.ch <= typemax(C)) && throw(DomainError("Invalid IR.")) + !Primes.isprime(ring.ch) && throw(DomainError("Ring characteristic must be prime")) + elseif ring.ground == :qq + !(C <: Rational || C <: Integer || C <: CoeffGeneric) && + throw(DomainError("Coefficients must be integer or rational.")) else - !(C <: Rational || C <: Integer) && - throw(DomainError("Coefficients must be integer or rationals.")) + !(C <: CoeffGeneric) && throw(DomainError("Coefficients must be CoeffGeneric.")) end (ring.ord == InputOrdering()) && throw(DomainError("Invalid monomial ordering.")) vars = ordering_variables(ring.ord) - if !isempty(vars) - if !(Set(vars) == Set(1:(ring.nvars))) - throw( - DomainError( - "Invalid monomial ordering. Expected variables to be numbers from 1 to $(ring.nvars), got $(vars)" - ) + if !isempty(vars) && !(Set(vars) == Set(1:(ring.nvars))) + throw( + DomainError( + "Invalid monomial ordering. Expected variables to be numbers from 1 to $(ring.nvars), but got: $(vars)" ) - end + ) end end function ir_is_valid( ring::PolyRing, - monoms::Vector{Vector{Vector{T}}}, + monoms::Vector{Vector{Vector{I}}}, coeffs::Vector{Vector{C}} -) where {T <: Integer, C <: Number} +) where {I <: Integer, C <: IR_coeff} ir_is_valid_basic(ring, monoms, coeffs) for i in 1:length(monoms) !(length(monoms[i]) == length(coeffs[i])) && throw(DomainError("Invalid IR.")) @@ -78,7 +93,7 @@ function ir_is_valid( !(length(monoms[i][j]) == ring.nvars) && throw(DomainError("Invalid IR.")) !(all(>=(0), monoms[i][j])) && throw(DomainError("Invalid IR.")) iszero(coeffs[i][j]) && throw(DomainError("Invalid IR")) # can be relaxed - if (ring.ch > 0) + if (ring.ground == :zp) !(0 < coeffs[i][j] < ring.ch) && throw(DomainError("Invalid IR.")) end end @@ -88,9 +103,9 @@ end function ir_ensure_valid( ring::PolyRing, - monoms::Vector{Vector{Vector{M}}}, + monoms::Vector{Vector{Vector{I}}}, coeffs::Vector{Vector{C}} -) where {M <: Integer, C <: Coeff} +) where {I <: Integer, C <: Coeff} ir_is_valid_basic(ring, monoms, coeffs) # Copy input new_monoms, new_coeffs = empty(monoms), empty(coeffs) @@ -108,7 +123,7 @@ function ir_ensure_valid( # Normalize for i in 1:length(new_monoms) for j in 1:length(new_monoms[i]) - if ring.ch > 0 + if ring.ground == :zp new_coeffs[i][j] = mod(new_coeffs[i][j], ring.ch) end end @@ -147,7 +162,7 @@ function ir_ensure_valid( end _new_coeffs[i][slow_idx] = Base.Checked.checked_add(_new_coeffs[i][slow_idx], new_coeffs[i][fast_idx]) - if ring.ch > 0 && _new_coeffs[i][slow_idx] >= ring.ch + if ring.ground == :zp && _new_coeffs[i][slow_idx] >= ring.ch _new_coeffs[i][slow_idx] -= ring.ch @invariant _new_coeffs[i][slow_idx] < ring.ch end @@ -196,7 +211,7 @@ function ir_extract_coeffs_raw!(trace, coeffs::Vector{Vector{C}}) where {C <: Co end if trace.homogenize - @invariant !iszero(trace.ring.ch) + @invariant trace.ring.ground == :zp trace.buf_basis.coeffs[length(coeffs) + 1][1] = one(CoeffsType) trace.buf_basis.coeffs[length(coeffs) + 1][2] = trace.ring.ch - one(typeof(trace.ring.ch)) end @@ -209,7 +224,7 @@ end function ir_pack_coeffs(batch::NTuple{N, T}) where {N, T} ring = batch[1][1] ch = CompositeNumber(map(el -> el[1].ch, batch)) - new_ring = PolyRing(ring.nvars, ring.ord, ch) + new_ring = PolyRing(ring.nvars, ring.ord, ch, ring.ground) monoms = batch[1][2] coeffs = Vector{Vector{CompositeNumber{N, UInt64}}}(undef, length(monoms)) @assert allequal(map(el -> el[2], batch)) @@ -251,13 +266,18 @@ function ir_convert_ir_to_internal( ) where {M <: Monom, C <: Coeff} repr = params.representation monoms2 = Vector{Vector{repr.monomtype}}(undef, length(monoms)) - coeffs2 = Vector{Vector{repr.coefftype}}(undef, length(monoms)) + CT = repr.coefftype + if !isconcretetype(CT) + CT = C + end + + coeffs2 = Vector{Vector{CT}}(undef, length(monoms)) @inbounds for i in 1:length(monoms) monoms2[i] = Vector{repr.monomtype}(undef, length(monoms[i])) - coeffs2[i] = Vector{repr.coefftype}(undef, length(monoms[i])) + coeffs2[i] = Vector{CT}(undef, length(monoms[i])) for j in 1:length(monoms[i]) monoms2[i][j] = monom_construct_from_vector(repr.monomtype, monoms[i][j]) - coeffs2[i][j] = repr.coefftype(coeffs[i][j]) + coeffs2[i][j] = CT(coeffs[i][j]) end end ring2, term_sorting_permutations = ir_set_monomial_ordering!(ring, monoms2, coeffs2, params) @@ -270,16 +290,16 @@ function ir_convert_internal_to_ir( coeffs::Vector{Vector{C}}, params ) where {M <: Monom, C <: Coeff} - monoms2 = Vector{Vector{Vector{IRexponent}}}(undef, length(monoms)) + monoms2 = Vector{Vector{Vector{IR_exponent}}}(undef, length(monoms)) if eltype(eltype(coeffs)) <: AbstractFloat coeffs2 = map(cc -> map(c -> UInt(c), cc), coeffs) else coeffs2 = coeffs end @inbounds for i in 1:length(monoms) - monoms2[i] = Vector{Vector{IRexponent}}(undef, length(monoms[i])) + monoms2[i] = Vector{Vector{IR_exponent}}(undef, length(monoms[i])) for j in 1:length(monoms[i]) - monoms2[i][j] = Vector{IRexponent}(undef, ring.nvars) + monoms2[i][j] = Vector{IR_exponent}(undef, ring.nvars) monom_to_vector!(monoms2[i][j], monoms[i][j]) end end @@ -299,7 +319,7 @@ function ir_set_monomial_ordering!( # No reordering of terms needed return ring, Vector{Vector{Int}}() end - ring = PolyRing(ring.nvars, params.target_ord, ring.ch) + ring = PolyRing(ring.nvars, params.target_ord, ring.ch, ring.ground) permutations = sort_input_terms_to_change_ordering!(monoms, coeffs, params.target_ord) ring, permutations end diff --git a/src/monomials/exponent_vector.jl b/src/monomials/exponent_vector.jl index 3be42e20..922fc9a6 100644 --- a/src/monomials/exponent_vector.jl +++ b/src/monomials/exponent_vector.jl @@ -20,7 +20,7 @@ struct MonomialDegreeOverflow <: Exception msg::String end -Base.showerror(io::IO, e::MonomialDegreeOverflow) = print(io, e.msg) +Base.showerror(io::IO, e::MonomialDegreeOverflow) = print(io, typeof(e), ": ", e.msg) @noinline __throw_monom_overflow_error(c, B) = throw(MonomialDegreeOverflow("Overflow may happen with the entry $c of type $B.")) @@ -47,14 +47,6 @@ monom_entrytype(::Type{ExponentVector{T}}) where {T} = T monom_copy(pv::ExponentVector) = Base.copy(pv) -function monom_copy!(dst::ExponentVector, src::ExponentVector) - @invariant length(dst) == length(src) - @inbounds for i in 1:length(src) - dst[i] = src[i] - end - dst -end - monom_construct_const(::Type{ExponentVector{T}}, n::Integer) where {T} = zeros(T, n + 1) function monom_construct_from_vector(::Type{ExponentVector{T}}, ev::Vector{U}) where {T, U} @@ -72,6 +64,11 @@ end function monom_to_vector!(tmp::Vector{M}, pv::ExponentVector{T}) where {M, T} @invariant length(tmp) == length(pv) - 1 + if promote_type(M, T) !== M + if !(all(i -> pv[i] < typemax(M), 2:length(pv))) + __throw_monom_overflow_error(-1, M) + end + end @inbounds tmp[1:end] = pv[2:end] tmp end @@ -88,7 +85,7 @@ function monom_hash(x::ExponentVector{T}, b::Vector{MH}) where {T, MH} @invariant length(x) == length(b) h = zero(MH) @inbounds for i in 1:length(x) - h += MH(x[i]) * b[i] + h += (x[i] % MH) * b[i] end mod(h, MonomHash) end diff --git a/src/monomials/packed_vector.jl b/src/monomials/packed_vector.jl index df6ee386..6b51484d 100644 --- a/src/monomials/packed_vector.jl +++ b/src/monomials/packed_vector.jl @@ -57,7 +57,6 @@ for (op, n) in _defined_packed_tuples monom_max_vars(::Type{$op{T, B}}) where {T, B} = $n * packed_elperchunk(T, B) - 1 monom_totaldeg(a::$op{T, B}) where {T, B} = a.a1 >> (8 * (sizeof(T) - sizeof(B))) monom_copy(a::$op{T, B}) where {T, B} = a - monom_copy!(b::$op{T, B}, a::$op{T, B}) where {T, B} = a monom_entrytype(a::$op{T, B}) where {T, B} = B monom_entrytype(a::Type{$op{T, B}}) where {T, B} = B end diff --git a/src/types.jl b/src/types.jl index e8b501b8..69844ef1 100644 --- a/src/types.jl +++ b/src/types.jl @@ -22,7 +22,7 @@ const CoeffQQ = Union{Rational{BigInt}} const CoeffZZ = Union{BigInt} # All supported coefficient types in F4. -const Coeff = Union{CoeffZp, CompositeCoeffZp, CoeffQQ, CoeffZZ} +const Coeff = Union{CoeffZp, CompositeCoeffZp, CoeffQQ, CoeffZZ, CoeffGeneric{T} where {T}} # Coefficient type used in a single run of classic modular computation const CoeffModular = UInt64 diff --git a/src/utils/keywords.jl b/src/utils/keywords.jl index 33d4b85f..0c87728a 100644 --- a/src/utils/keywords.jl +++ b/src/utils/keywords.jl @@ -23,7 +23,8 @@ const _supported_kw_args = ( threaded = :auto, homogenize = :auto, batched = true, - changematrix = false + changematrix = false, + _generic = false ), normalform = ( check = false, @@ -96,9 +97,7 @@ mutable struct KeywordArguments check::Bool homogenize::Symbol changematrix::Bool - - KeywordArguments(function_id::Symbol; passthrough...) = - KeywordArguments(function_id, passthrough) + _generic::Bool function KeywordArguments(function_id::Symbol, kws) @assert haskey(_supported_kw_args, function_id) @@ -169,6 +168,8 @@ mutable struct KeywordArguments changematrix = get(kws, :changematrix, get(default_kw_args, :changematrix, false)) + _generic = get(kws, :_generic, get(default_kw_args, :_generic, false)) + new( function_id, reduced, @@ -184,7 +185,8 @@ mutable struct KeywordArguments batched, check, homogenize, - changematrix + changematrix, + _generic ) end end diff --git a/test/groebner.jl b/test/groebner.jl index 93e10cd0..93f0cb72 100644 --- a/test/groebner.jl +++ b/test/groebner.jl @@ -1,7 +1,7 @@ using AbstractAlgebra using Base.Threads using Combinatorics, Primes, Random -using Test, TestSetExtensions +using Test @testset "groebner basic" begin R, (x, y) = polynomial_ring(GF(2^31 - 1), ["x", "y"], internal_ordering=:degrevlex) @@ -24,23 +24,25 @@ using Test, TestSetExtensions fs = [x1 * x2^2 + x1 * x2, x1^2 * x2^2 + 2 * x1 * x2, 2 * x1^2 * x2^2 + x1 * x2] @test Groebner.groebner(fs) == [x1 * x2] + + @test_throws AssertionError Groebner.groebner([x1, x2], this_keyword_does_not_exist=":^(") end -@testset "groebner low level" begin - get_data(sys, T) = - (map(f -> collect(exponent_vectors(f)), sys), map(f -> collect(T.(coefficients(f))), sys)) - - function test_low_level_interface(ring, sys; passthrough...) - T(x) = base_ring(parent(sys[1])) == QQ ? Rational{BigInt}(x) : UInt64(lift(x)) - monoms, coeffs = get_data(sys, T) - gb_monoms1, gb_coeffs1 = Groebner.groebner(ring, monoms, coeffs; passthrough...) - gb = Groebner.groebner(sys; passthrough...) - gb_monoms2, gb_coeffs2 = get_data(gb, T) - @test (gb_monoms1, gb_coeffs1) == (gb_monoms2, gb_coeffs2) - @test Groebner.isgroebner(ring, gb_monoms1, gb_coeffs1) - @test all(iszero, Groebner.normalform(ring, gb_monoms1, gb_coeffs1, ring, monoms, coeffs)) - end +get_data(sys, T) = + (map(f -> collect(exponent_vectors(f)), sys), map(f -> collect(T.(coefficients(f))), sys)) + +function test_low_level_interface(ring, sys; passthrough...) + T(x) = base_ring(parent(sys[1])) == QQ ? Rational{BigInt}(x) : UInt64(lift(x)) + monoms, coeffs = get_data(sys, T) + gb_monoms1, gb_coeffs1 = Groebner.groebner(ring, monoms, coeffs; passthrough...) + gb = Groebner.groebner(sys; passthrough...) + gb_monoms2, gb_coeffs2 = get_data(gb, T) + @test (gb_monoms1, gb_coeffs1) == (gb_monoms2, gb_coeffs2) + @test Groebner.isgroebner(ring, gb_monoms1, gb_coeffs1) + @test all(iszero, Groebner.normalform(ring, gb_monoms1, gb_coeffs1, ring, monoms, coeffs)) +end +@testset "groebner low level" begin R, (x, y) = polynomial_ring(GF(2^31 - 1), ["x", "y"], internal_ordering=:degrevlex) ring_ff = Groebner.PolyRing(2, Groebner.DegRevLex(), 2^31 - 1) ring_ff2 = Groebner.PolyRing(2, Groebner.DegRevLex(), 2^32 - 5) @@ -61,6 +63,11 @@ end [[[0, 0]]], [[1]] ) + @test_throws DomainError Groebner.groebner( + Groebner.PolyRing(2, Groebner.DegRevLex(), 33), + [[[0, 0]]], + [[1]] + ) @test_throws DomainError Groebner.groebner( Groebner.PolyRing(2, Groebner.DegRevLex(3, 2, 1), 0), [[[0, 0]]], @@ -154,6 +161,46 @@ end @test gb == Groebner.groebner(ring, monoms, coeffs) end +@testset "groebner generic" begin + # GB over + # - Zp + # - Zp for large p + # - QQ + n = 5 + syss = [ + Groebner.Examples.cyclicn(n, k=GF(2^40 + 15)), + Groebner.Examples.cyclicn(n, k=GF(2^40 + 15), internal_ordering=:lex), + Groebner.Examples.cyclicn(n, k=GF(2)), + Groebner.Examples.cyclicn(n, k=GF(nextprime(big(2)^1000))), + Groebner.Examples.cyclicn(n, k=GF(nextprime(big(2)^1000)), internal_ordering=:lex), + Groebner.Examples.cyclicn(n, k=QQ) + ] + for sys in syss + ord = internal_ordering(parent(sys[1])) == :lex ? Groebner.Lex() : Groebner.DegRevLex() + ring = Groebner.PolyRing(n, ord, 0, :generic) + exps, cfs = get_data(sys, Groebner.CoeffGeneric) + gbexps, gbcfs = groebner(ring, exps, cfs) + gb = groebner(sys) + @test isgroebner(gb) + @test gbexps == get_data(gb, identity)[1] + @test isgroebner(ring, gbexps, gbcfs) + @test all(isempty, normalform(ring, gbexps, gbcfs, ring, exps, cfs)[1]) + @test map(c -> Groebner.generic_coeff_data.(c), gbcfs) == get_data(gb, identity)[2] + end + + # GB over QQ(a,b) + R_param, (a, b) = QQ["a", "b"] + R, (x, y, z) = fraction_field(R_param)["x", "y", "z"] + F = [x^2 + x + (a + 1), x * y + b * y * z + 1 // (a * b), x * z + z + b] + @test groebner(F) == [ + z^2 + b // (a + 1) * z + b^2 // (a + 1), + y + (-a - 1) // (a^2 * b^2 + a * b^4 + a * b^2) * z - 1 // (a^2 * b + a * b^3 + a * b), + x + (-a - 1) // b * z + ] + + @test groebner([R(a - b), R(a)]) == [one(R)] +end + @testset "groebner reduced=false" begin R, (x, y) = polynomial_ring(GF(2^31 - 1), ["x", "y"], internal_ordering=:lex) @@ -390,6 +437,10 @@ end @testset "monomial overflow" begin R, (x, y, z) = polynomial_ring(GF(2^31 - 1), ["x", "y", "z"], internal_ordering=:degrevlex) + + @test groebner([x^(2^31)]) == [x^2^31] + @test_throws Groebner.MonomialDegreeOverflow groebner([x^(2^33)]) + for monoms in [:auto, :dense, :packed] gb_1 = [x * y^100 + y, x^100 * y + y^100, y^199 + 2147483646 * x^99 * y] gb_2 = [x * y^200 + y, x^200 * y + y^200, y^399 + 2147483646 * x^199 * y] @@ -964,6 +1015,7 @@ end ans = [x[div(n, 2) - i + 2] - cf(i, n) for i in 1:(div(n, 2) + 1)] + @test Groebner.groebner(f, ordering=Groebner.DegRevLex()) == ans @test Groebner.isgroebner(gb) @test all(iszero, Groebner.normalform(gb, f)) @test gb == ans @@ -971,9 +1023,11 @@ end # up to 63 test_n_variables(8) + test_n_variables(13) test_n_variables(16) + test_n_variables(20) test_n_variables(32) - for n in 2:5:63 + for n in 32:8:63 test_n_variables(n) end @@ -983,8 +1037,8 @@ end test_n_variables(n) end - # up to 511 - for n in [128, 256, 257, 511] + # up to 257 + for n in [128, 256, 257] @info "Variables:" n R, x = polynomial_ring(QQ, ["x$i" for i in 1:n]) f = x diff --git a/test/input_output/AbstractAlgebra.jl b/test/input_output/AbstractAlgebra.jl index 89040282..505db30c 100644 --- a/test/input_output/AbstractAlgebra.jl +++ b/test/input_output/AbstractAlgebra.jl @@ -16,8 +16,8 @@ import Primes end @testset "AbstractAlgebra.jl, input-output" begin - R, (x, y) = AbstractAlgebra.GF(Primes.nextprime(BigInt(2)^100))["x", "y"] - @test_throws DomainError Groebner.groebner([x, y]) + # R, (x, y) = AbstractAlgebra.GF(Primes.nextprime(BigInt(2)^100))["x", "y"] + # @test_throws DomainError Groebner.groebner([x, y]) R, (x, y) = AbstractAlgebra.ZZ["x", "y"] @test_throws DomainError Groebner.groebner([x, y]) diff --git a/test/input_output/Nemo.jl b/test/input_output/Nemo.jl index 650ebe4f..142938bb 100644 --- a/test/input_output/Nemo.jl +++ b/test/input_output/Nemo.jl @@ -3,7 +3,7 @@ import Nemo, Primes @testset "Nemo.jl, univariate" begin for ff in [Nemo.Native.GF(2^62 + 135), Nemo.GF(2^62 + 135)] - R, x = polynomial_ring(ff, "x") + R, x = Nemo.polynomial_ring(ff, "x") @test Groebner.groebner([R(2)]) == [R(1)] @test Groebner.groebner([R(0), R(0)]) == [R(0)] @test Groebner.groebner([R(0), R(3), R(0)]) == [R(1)] @@ -19,18 +19,56 @@ import Nemo, Primes Nemo.QQ ] for gb_ord in [Groebner.Lex(), Groebner.DegLex(), Groebner.DegRevLex()] - R, x = polynomial_ring(ff, "x") + R, x = Nemo.polynomial_ring(ff, "x") @test Groebner.groebner([x^2 - 4, x + 2], ordering=gb_ord) == [x + 2] end end end +@testset "Nemo.jl, generic" begin + # Test generic coefficients with some interesting fields from Nemo.jl. + + # Single extension + K, a = Nemo.finite_field(3, 2, "a") + R, (X, Y) = K["X", "Y"] + @test groebner([a * X - Y, X * Y - 1]) == [Y^2 + 2 * a, X + (2 * a + 1) * Y] + + # TODO: Tower of extensions + # K, a = Nemo.finite_field(3, 2, "a") + # Kx, x = Nemo.polynomial_ring(K, "x") + # L, b = Nemo.finite_field(x^3 + x^2 + x + 2, "b") + # R, (X, Y) = L["X", "Y"] + # sys = [a * b * X - Y, X * Y - 1] + # res = [Y^2 + b^5 + b^4 + 2*b^2 + 2*b + 2, X + (b^3 + 2*b^2 + b)*Y] + # @test groebner(sys) == res + + # Q bar + Q_bar = Nemo.algebraic_closure(Nemo.QQ) + R, (X, Y) = Q_bar["X", "Y"] + e2 = Nemo.root_of_unity(Q_bar, 5, 2) + e3 = Nemo.root_of_unity(Q_bar, 5, 3) + e4 = Nemo.root_of_unity(Q_bar, 5, 4) + @test groebner([e3 * X - e2 * Y]) == [X - e4 * Y] + + # Cyclic extension + K, a = Nemo.cyclotomic_field(5) + R, (X, Y) = K["X", "Y"] + @test groebner([a * X + 1]) == [X - a^3 - a^2 - a - 1] + @test isgroebner([X - a^3 - a^2 - a - 1]) + @test normalform([X - a^3 - a^2 - a - 1], [X, X - a^3 - a^2 - a - 1]) == + [R(a^3 + a^2 + a + 1), R(0)] + @test_throws DomainError groebner_with_change_matrix( + [X - a^3 - a^2 - a - 1], + ordering=Groebner.DegRevLex() + ) +end + @testset "Nemo.jl, input-output" begin - R, (x, y) = Nemo.GF(Nemo.ZZRingElem(Primes.nextprime(BigInt(2)^100)))["x", "y"] - @test_throws DomainError Groebner.groebner([x, y]) + # R, (x, y) = Nemo.GF(Nemo.ZZRingElem(Primes.nextprime(BigInt(2)^100)))["x", "y"] + # @test_throws DomainError Groebner.groebner([x, y]) - R, (x, y) = Nemo.GF(2, 2)["x", "y"] - @test_throws DomainError Groebner.groebner([x, y]) + # R, (x, y) = Nemo.GF(2, 2)["x", "y"] + # @test_throws DomainError Groebner.groebner([x, y]) R, (x, y) = Nemo.ZZ["x", "y"] @test_throws DomainError Groebner.groebner([x, y]) @@ -55,11 +93,11 @@ end for ord in nemo_orderings_to_test for ground in nemo_grounds_to_test - R, x = polynomial_ring(ground, "x") + R, x = Nemo.polynomial_ring(ground, "x") gb = Groebner.groebner([(x - 1) * (x + 8), (x + 8) * (x + 10)]) @test gb == [(x + 8)] - R, (x, y) = polynomial_ring(ground, ["x", "y"], internal_ordering=ord) + R, (x, y) = Nemo.polynomial_ring(ground, ["x", "y"], internal_ordering=ord) fs = [x^2 * y + 3, (2^31 - 5) * x - (2^31 - 4) * y] gb = Groebner.groebner(fs) @test parent(gb[1]) == R @@ -84,7 +122,7 @@ end # Test for different Groebner.jl orderings for nemo_ord in [:lex, :deglex, :degrevlex] for ground in nemo_grounds_to_test - R, (x,) = polynomial_ring(ground, ["x"], internal_ordering=nemo_ord) + R, (x,) = Nemo.polynomial_ring(ground, ["x"], internal_ordering=nemo_ord) for gb_ord in [ Groebner.Lex(), Groebner.DegLex(), @@ -97,7 +135,7 @@ end @test gb == [x^2] end - R, (x, y) = polynomial_ring(ground, ["x", "y"], internal_ordering=nemo_ord) + R, (x, y) = Nemo.polynomial_ring(ground, ["x", "y"], internal_ordering=nemo_ord) fs = [x^2 + 3, y - 1] for gb_ord in [ Groebner.Lex(), diff --git a/test/learn_and_apply.jl b/test/learn_and_apply.jl index 2216742f..86df8e90 100644 --- a/test/learn_and_apply.jl +++ b/test/learn_and_apply.jl @@ -215,6 +215,11 @@ end end end +@testset "learn & apply, generic" begin + R, (x, y) = AbstractAlgebra.GF(nextprime(big(2)^100))["x", "y"] + @test_throws DomainError groebner_learn([x, y]) +end + @testset "learn & apply, orderings" begin K = GF(2^31 - 1) R, (x, y) = polynomial_ring(K, ["x", "y"], internal_ordering=:lex) @@ -564,22 +569,26 @@ end ring1 = Groebner.PolyRing(2, Groebner.DegLex(), 7) ring2 = Groebner.PolyRing(2, Groebner.DegLex(), 11) ring3 = Groebner.PolyRing(2, Groebner.DegLex(), 13) - ring4 = Groebner.PolyRing(2, Groebner.Lex(1) * Groebner.Lex(2), 17) + ring4 = Groebner.PolyRing(2, Groebner.DegLex(), 17) trace, (gb0...) = Groebner.groebner_learn(ring0, [[[0, 0], [1, 1]]], [[1, -1]]) @test gb0 == ([[[1, 1], [0, 0]]], [[1, 4]]) - flag1, gb1 = Groebner.groebner_apply!(trace, ring1, [[[0, 0], [1, 1]]], [[1, -1]]) - flag2, gb2 = Groebner.groebner_apply!(trace, ring2, [[[1, 1], [0, 0]]], [[-1, 1]]) - flag3, gb3 = Groebner.groebner_apply!(trace, ring3, [[[0, 0], [0, 1], [1, 1]]], [[1, 0, -1]]) - flag4, gb4 = Groebner.groebner_apply!(trace, ring4, [[[0, 0], [1, 1]]], [[1, -1]]) - @test flag1 && flag2 && flag3 && flag4 + flag, (gb1, gb2, gb3, gb4) = Groebner.groebner_apply!( + trace, + ( + (ring1, [[[0, 0], [1, 1]]], [[1, -1]]), + (ring2, [[[1, 1], [0, 0]]], [[-1, 1]]), + (ring3, [[[0, 0], [0, 1], [1, 1]]], [[1, 0, -1]]), + (ring4, [[[0, 0], [1, 1]]], [[1, -1]]) + ) + ) + @test flag @test gb1 == ([[1, 6]]) @test gb2 == ([[1, 10]]) @test gb3 == ([[1, 12]]) @test gb4 == ([[1, 16]]) - flag1, gb1 = Groebner.groebner_apply!(trace, ring1, [[[0, 0]]], [[1]]) - flag2, gb2 = Groebner.groebner_apply!(trace, ring2, [[[1, 1], [0, 0]]], [[-1, 0]]) - flag3, gb3 = Groebner.groebner_apply!(trace, ring3, [[[0, 0], [0, 1], [1, 1]]], [[1, 0, -1]]) - flag4, (gb4...) = Groebner.groebner_apply!(trace, ring4, [[[0, 0], [1, 1]]], [[1, -1]]) - @test !flag1 && !flag2 && flag3 && flag4 + @test_throws AssertionError Groebner.groebner_apply!( + trace, + ((ring1, [[[0, 0]]], [[1]]), (ring2, [[[1, 1], [0, 0]]], [[-1, 1]])) + ) end diff --git a/test/runtests.jl b/test/runtests.jl index 3727e93c..16653526 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using Test, TestSetExtensions +using Test using InteractiveUtils, Random using AbstractAlgebra @@ -34,34 +34,38 @@ end @test isempty(Test.detect_ambiguities(Groebner)) @time @testset "All tests" verbose = true begin - @time @includetests ["arithmetic"] + @time include("arithmetic.jl") # Different implementations of a monomial - @time @includetests ["monoms/exponentvector", "monoms/packedtuples"] - @time @includetests ["monoms/monom_arithmetic", "monoms/monom_orders"] + @time include("monoms/exponentvector.jl") + @time include("monoms/packedtuples.jl") + @time include("monoms/monom_arithmetic.jl") + @time include("monoms/monom_orders.jl") - @time @includetests ["groebner"] + @time include("groebner.jl") - @time @includetests ["learn_and_apply"] + @time include("learn_and_apply.jl") - @time @includetests ["isgroebner"] + @time include("isgroebner.jl") - @time @includetests ["normalform"] + @time include("normalform.jl") # Test for different frontends: # - AbstractAlgebra.jl # - Nemo.jl # - DynamicPolynomials.jl - @time @includetests ["input_output/AbstractAlgebra"] + @time include("input_output/AbstractAlgebra.jl") if try_import(:DynamicPolynomials) - @time @includetests ["input_output/GroebnerDynamicPolynomialsExt"] + @info "Testing frontend: DynamicPolynomials.jl" + @time include("input_output/GroebnerDynamicPolynomialsExt.jl") end if try_import(:Nemo) - @time @includetests ["input_output/Nemo"] + @info "Testing frontend: Nemo.jl" + @time include("input_output/Nemo.jl") end - @time @includetests ["output_inferred"] + @time include("output_inferred.jl") # test for regressions - @time @includetests ["regressions"] + @time include("regressions.jl") end