From 16ee3a86fd6c07d06022ff33a5a31a0e4ee8d5bd Mon Sep 17 00:00:00 2001 From: John Abbott Date: Mon, 7 Aug 2023 11:53:57 +0200 Subject: [PATCH 01/51] Preliminary tests for unified hilbert_series --- test/Rings/hilbert.jl | 60 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 test/Rings/hilbert.jl diff --git a/test/Rings/hilbert.jl b/test/Rings/hilbert.jl new file mode 100644 index 000000000000..dde604a3e13b --- /dev/null +++ b/test/Rings/hilbert.jl @@ -0,0 +1,60 @@ +@testset "Abbott Hilbert series" begin + P, x = graded_polynomial_ring(QQ, 5, "x"); + G = + [ + x[1]^30*x[2]^16*x[3]^7*x[4]^72*x[5]^31, + x[1]^11*x[2]^20*x[3]^29*x[4]^93*x[5]^5, + x[1]^5*x[2]^9*x[3]^43*x[4]^7*x[5]^97, + x[1]^57*x[2]^6*x[3]^7*x[4]^56*x[5]^37, + x[1]^22*x[2]^40*x[3]^27*x[4]^67*x[5]^18, + x[1]^5*x[2]^5*x[3]^7*x[4]^78*x[5]^81, + x[1]^85*x[2]^15*x[3]^12*x[4]^41*x[5]^37, + x[1]^13*x[2]^35*x[3]^2*x[4]^64*x[5]^84, + x[1]^31*x[2]^57*x[3]^12*x[4]^93*x[5]^6, + x[1]^37*x[2]^59*x[3]^88*x[4]^16*x[5]^7, + x[1]^47*x[2]^63*x[3]^32*x[4]^28*x[5]^41, + x[1]^18*x[2]^19*x[3]^95*x[4]^7*x[5]^73, + x[1]^37*x[2]^84*x[3]^11*x[4]^48*x[5]^32, + x[1]^85*x[2]*x[3]^73*x[4]^24*x[5]^31, + x[1]^59*x[2]^56*x[3]^41*x[4]^12*x[5]^50, + x[1]^58*x[2]^36*x[3]*x[4]^35*x[5]^89, + x[1]^71*x[2]^12*x[3]^36*x[4]^76*x[5]^25, + x[1]^58*x[2]^32*x[3]^85*x[4]^44*x[5], + x[1]^61*x[2]^81*x[3]^15*x[4]^59*x[5]^8, + x[1]^84*x[2]^49*x[3]^32*x[4]^52*x[5]^7, + x[1]^40*x[2]^3*x[3]^54*x[4]^39*x[5]^89, + x[1]^12*x[2]^52*x[3]^100*x[4]^49*x[5]^14, + x[1]^75*x[2]^78*x[3]^34*x[4]*x[5]^41, + x[1]^46*x[2]^22*x[3]^99*x[4]^49*x[5]^14, + x[1]^95*x[2]^11*x[3]^63*x[4]^52*x[5]^10, + x[1]^32*x[2]^47*x[3]^97*x[4]^32*x[5]^26, + x[1]^52*x[2]^64*x[3]^62*x[4]^13*x[5]^47, + x[1]^70*x[2]^46*x[3]^90*x[4]^13*x[5]^21, + x[1]^3*x[2]^67*x[3]^90*x[4]^45*x[5]^52, + x[1]^17*x[2]^100*x[3]^58*x[4]^62*x[5]^21, + x[1]^45*x[2]^54*x[3]^65*x[4]^64*x[5]^32, + x[1]^24*x[2]^85*x[3]^27*x[4]^49*x[5]^76, + x[1]^28*x[2]^83*x[3]^9*x[4]^97*x[5]^45, + x[1]^40*x[2]^52*x[3]^99*x[4]^27*x[5]^49, + x[1]^88*x[2]^36*x[3]^26*x[4]^30*x[5]^90, + x[1]^36*x[2]^68*x[3]^76*x[4]^81*x[5]^9, + x[1]^48*x[2]^25*x[3]^80*x[4]^40*x[5]^83, + x[1]^93*x[2]^14*x[3]^80*x[4]^89*x[5]^3, + x[1]^5*x[2]^61*x[3]^65*x[4]^65*x[5]^94, + x[1]^48*x[2]^91*x[3]^70*x[4]^66*x[5]^16, + x[1]^39*x[2]^79*x[3]^98*x[4]^2*x[5]^75, + x[1]^4*x[2]^60*x[3]^74*x[4]^56*x[5]^100, + x[1]^59*x[2]^100*x[3]^74*x[4]^51*x[5]^12, + x[1]^90*x[2]^61*x[3]^85*x[4]^43*x[5]^19, + x[1]^44*x[2]^97*x[3]^39*x[4]^27*x[5]^97, + x[1]^91*x[2]^37*x[3]^2*x[4]^97*x[5]^80, + x[1]^67*x[2]^44*x[3]^99*x[4]^5*x[5]^93, + x[1]^95*x[2]^93*x[3]^88*x[4]^30*x[5]^2, + x[1]^91*x[3]^84*x[4]^59*x[5]^76, + x[1]^62*x[2]^3*x[3]^96*x[4]^72*x[5]^84 + ]; + + I = ideal(P,G); + PmodI, _ = quo(P,I); + HS = hilbert_series(PmodI); +end From a961ce578acc9c339dbf824e02aec41a70354622 Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Mon, 7 Aug 2023 11:57:47 +0200 Subject: [PATCH 02/51] Include tests. --- test/Rings/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Rings/runtests.jl b/test/Rings/runtests.jl index 1cf01fd9a6f9..be400c544920 100644 --- a/test/Rings/runtests.jl +++ b/test/Rings/runtests.jl @@ -30,3 +30,4 @@ include("PBWAlgebraQuo.jl") include("FreeAssAlgIdeal.jl") include("binomial-ideals.jl") +include("hilbert.jl") From 3bb7d084c4e8ee3511049845dad0ecd708638ca7 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Mon, 7 Aug 2023 12:18:02 +0200 Subject: [PATCH 03/51] Hooked in Abbott code --- src/Rings/Rings.jl | 1 + src/Rings/hilbert.jl | 1031 ++++++++++++++++++++++++++++ src/Rings/mpoly-affine-algebras.jl | 27 +- test/Rings/hilbert.jl | 3 +- 4 files changed, 1049 insertions(+), 13 deletions(-) create mode 100644 src/Rings/hilbert.jl diff --git a/src/Rings/Rings.jl b/src/Rings/Rings.jl index 441288d21936..8d6e2817b63a 100644 --- a/src/Rings/Rings.jl +++ b/src/Rings/Rings.jl @@ -33,4 +33,5 @@ include("AlgClosureFp.jl") include("PBWAlgebra.jl") include("PBWAlgebraQuo.jl") include("FreeAssAlgIdeal.jl") +include("hilbert.jl") diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl new file mode 100644 index 000000000000..7fe800d2a199 --- /dev/null +++ b/src/Rings/hilbert.jl @@ -0,0 +1,1031 @@ +#import Pkg; Pkg.add("AbstractAlgebra"); +#import AbstractAlgebra: degree; +###??? import AbstractAlgebra: is_one; #is_one; + +# A function similar to this is in StatsBase.jl but I wish to avoid +# having an external dependency (for just 1 simple fn!) + +# Generate random m-subset of {1,2,3,...,n} +# Result is a Int64[]; entries are NOT SORTED!! +function random_subset(n::Int64, m::Int64) + # assume n >= 1, m >= 0 and m <= n + if m == 0 #=then=# + return Int64[]; + end #=if=# + L = collect(1:n); + if m == n #=then=# + return L; + end #=if=# + for j in 1:m #=do=# + k = rand(j:n); + L[j],L[k] = L[k],L[j]; # just a SWAP + end #=for=# + L = first(L,m); + #?? sort!(L); ?? + return L; +end #=function=# + +# There must be a better way...! +# Split a "list" into 2 parts determined by a predicate. +# Returns 2-tuple: list-of-sat-elems, list-of-unsat-elems +function filter2(pred::Function, L::Vector) + sat = []; + unsat = []; + for x in L #=do=# + if pred(x) + push!(sat,x); + else + push!(unsat,x); + end #=if=# + end #=for=# + return sat,unsat; +end #=function=# + +############################################ +# Code for representing & manipulating PPs (power products, aka. monomials) +# All this code is "local" to this file, & not exported! + +# type alias +const HSNumVar = AbstractAlgebra.Generic.LaurentMPolyWrap{QQFieldElem, QQMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{QQFieldElem, QQMPolyRing}} + + +# Each PP is represented as Vector{PP_exponent} + +PP_exponent = Int64; # UInt ??? Strange: Int32 was slower on my machine ?!? + + +#= mutable =# struct PP + expv::Vector{PP_exponent}; +end # struct + +function Base.copy(t::PP) + return PP(copy(t.expv)); +end #=function=# + +# RETURN VALUE??? Perhaps index or 0? (or -1?) +function IsSimplePowerPP(t::PP) + CountNZ = 0; + for i in 1:length(t.expv) #do + @inbounds if (t.expv[i] == 0) continue; end #if + if (CountNZ > 0) return false; end #if + CountNZ = i; + end #for + if (CountNZ != 0) return true; end #if MAYBE RETURN index & exp??? + return false; # because t == 1 +end #function + +# Should be is_one, but julia complained :-( +function isone(t::PP) + return all(t.expv .== 0); +end #function + +function degree(t::PP) + return sum(t.expv); +end #function + +function IsDivisible(t::PP, s::PP) # is t divisible by s + n = length(t.expv); # assume equal to length(s.expv); + for i in 1:n #do + @inbounds if t.expv[i] < s.expv[i] #then + return false; + end #if + end #for + return true; +end #function + + +# modifies first arg +function mult_by_var!(t::PP, j::Int64) + @inbounds t.expv[j] += 1; +end #function + +function mult(t1::PP, t2::PP) + # ASSUMES: length(t1.expv) == length(t2.expv) + return PP(t1.expv + t2.expv); +end #function + + +function divide(t1::PP, t2::PP) + # ASSUMES: length(t1.expv) == length(t2.expv), also that t1 is mult of t2 + return PP(t1.expv - t2.expv); +end #function + +function is_coprime(t1::PP, t2::PP) + # ASSUMES: length(t1.expv) == length(t2.expv) + n = length(t1.expv); + for i in 1:n #do + @inbounds if t1.expv[i] != 0 && t2.expv[i] != 0 #=then=# + return false; + end #=if=# + end #for + return true; +end #function + +function lcm(t1::PP, t2::PP) + # ASSUMES: length(t1.expv) == length(t2.expv) + n = length(t1.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds expv[i] = max(t1.expv[i], t2.expv[i]); + end #for + return PP(expv); +end #function + +function gcd(t1::PP, t2::PP) + # ASSUMES: length(t1.expv) == length(t2.expv) + n = length(t1.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds expv[i] = min(t1.expv[i], t2.expv[i]); + end #for + return PP(expv); +end #function + +function gcd3(t1::PP, t2::PP, t3::PP) + # ASSUMES: length(t1.expv) == length(t2.expv) == length(t3.expv) + n = length(t1.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds expv[i] = min(t1.expv[i], t2.expv[i], t3.expv[i]); + end #for + return PP(expv); +end #function + +function colon(t1::PP, t2::PP) + # ASSUMES: length(t1.expv) == length(t2.expv) + n = length(t1.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds expv[i] = max(0, t1.expv[i]-t2.expv[i]); + end #for + return PP(expv); +end #function + +function saturatePP(t1::PP, t2::PP) + # ASSUMES: length(t1.expv) == length(t2.expv) + n = length(t1.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds if t2.expv[i] == 0 #then + @inbounds expv[i] = t1.expv[i]; + end #if + end #for + return PP(expv); +end #function + +function radical(t::PP) + n = length(t.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds if t.expv[i] > 0 #then + expv[i] = 1; + end #if + end #for + return PP(expv); +end #function + + +# if t1 == t2 then returns false. +function DegRevLexLess(t1::PP, t2::PP) + d1 = sum(t1.expv); + d2 = sum(t2.expv); + if d1 != d2 #=then=# return (d1 < d2); end #=if=# + nvars = length(t1.expv); + for i in nvars:-1:1 #=do=# + if t1.expv[i] != t2.expv[i] #=then=# + return (t1.expv[i] > t2.expv[i]); + end #=if=# + end #=for=# + return false; +end #=function=# + +# Test whether PP involves at least 1 indet from the list K (of indexes) +function involves(t::PP, K::Vector{Int64}) # K is index set + # ASSUMES: indexes in K are all in range + for i in K #do + if t.expv[i] != 0 #then + return true; + end #if + end #for + return false; +end #function + + +function Base.show(io::IO, t::PP) + if (all(t.expv .== 0)) # (isone(PP)) # why doesn't isone work ??? + print(io, "1"); + return; + end #if + str = ""; + n = length(t.expv); + for i in 1:n #do + if (t.expv[i] != 0) #then + if (str != "") str = str * "*"; end #if + str = str * "x[$(i)]"; + if (t.expv[i] > 1) #then + str = str * "^$(t.expv[i])"; + end #if + end #if + end #for + print(io, str); +end #function + + +# interreduce list of PPs; equiv find min set of gens for PP monoideal +function interreduce(L::Vector{PP}) # L is list of PPs + sort!(L, by=degree); + MinGens = PP[];##empty Vector{PP}(); + for t in L #do + discard = false; + for s in MinGens #do + if IsDivisible(t,s) #then + discard = true; + break; + end #if + end #for + if !discard #then + push!(MinGens, t); + end #if + end # for + return MinGens; +end # function + + +# Is t a multiple of at least one element of L? +# Add degree truncation??? +function NotMultOf(L::Vector{PP}, t::PP) + for s in L #=do=# + if IsDivisible(t,s) + return false; + end #=if=# + end #=for=# + return true; +end #=function=# + + + +# "project" PP onto sub-monoid of PPs gen by indets in indexes +function ProjectIndets(t::PP, indexes::Vector{Int}) + # # expv = [0 for _ in 1:length(indexes)]; + # # for i in 1:length(indexes) #do + # # expv[i] = t.expv[indexes[i]]; + # # end #for + # # return PP(expv); + return PP([t.expv[k] for k in indexes]); +end #function + + +# NOT SURE THIS IS USEFUL: how many indets are really needed in the list L? +function TrueNumVars(L::Vector{PP}) + # assume L non empty? + if isempty(L) return 0; end #if +## MaxNumVars = length(L[1].expv); +## AllVars = PP([1 for _ in 1:MaxNumVars]); + t = radical(L[1]); + for j in 2:length(L) #do + t = radical(mult(t,L[j])); + end #for + return degree(t); +end #function + + +#----------------------------------------------------------------------------- +# This function is also private/local to this file. + +# Each PP in the list L is interpreted as saying that all indets +# involved in that PP are "connected". The task is to find (minimal) +# connected componnts of indets. + +# Connected components of variables + +# Input: non-empty list of PPs +# Output: list of lists of var indexes, each sublist is a connected component +function ConnectedComponents(L::Vector{PP}) + ConnCompt::Vector{Vector{Int64}} = []; + nvars = length(L[1].expv); + IgnoreVar = [ false for _ in 1:nvars]; + VarAppears = copy(IgnoreVar); + for t in L #do + for j in 1:nvars #do + @inbounds if t.expv[j] != 0 #then + @inbounds VarAppears[j] = true; + end #if + end #for + end #for + CountIgnore = 0; + for j in 1:nvars #do + @inbounds if !VarAppears[j] #then + @inbounds IgnoreVar[j] = true; + CountIgnore += 1; + end #if + end #for +###println("CountIgnore=$(CountIgnore)"); +###println("nvars=$(nvars)"); + while CountIgnore < nvars #do + ## Maybe use findfirst instead of loop below? + # j=1; + # while IgnoreVar[j] #do + # j += 1; + # end #while + j = findfirst(!, IgnoreVar); # j is index of some var which appears in at least one PP + k = findfirst((t -> t.expv[j] != 0), L); # pick some PP involving j-th var + lcm = L[k]; +###println("lcm=$(lcm)"); + DoAnotherIteration = true; + while DoAnotherIteration #do + DoAnotherIteration = false; + for t in L #do + if is_coprime(lcm,t) continue; end + s = saturatePP(t,lcm); + if isone(s) continue; end + lcm = mult(lcm,s); ### lcm *= s; + DoAnotherIteration = true; + end #for + end #while + vars = filter((k -> lcm.expv[k] > 0), 1:nvars); + # remove conn compt from L??? +#seems to be slower with this line ?!? L = filter((t -> is_coprime(t,lcm)), L); + push!(ConnCompt, vars); + for k in vars #do + IgnoreVar[k] = true; + CountIgnore += 1; + end #for + end #while + return ConnCompt; +end #function + +############################################################################# +#----------------------------------------------------------------------------- + +# Base cases for HibertFn -- see Bigatti 1997 (JPAA) + +# Data needed: +# convention: indets are called x[1] to x[n] -- start from 1 because julia does +# (only at topmost call) Weight matrix: col k is weight of x[k] +# PP: internal repr is expv Vector{PP_exponent} equiv to Vector{Int} or similar +# Gens: interreduced non-empty list of PPs +# HS "indets": seems useful to have power-prods of them by the corr weights. + +# Case gens are simple powers +function HSNum_base_SimplePowers(SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T is list of HSNum PPs, one for each grading dim + ans = 1; #one(T[1]) ??? + for t in SimplePPs #do + k = findfirst(entry -> (entry > 0), t.expv); + ans = ans * (1 - T[k]^t.expv[k]); # ???? ans -= ans*T[k]^t; + end #for + return ans; +end #function + + +function HSNum_base_case1(t::PP, SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T is list of HSNum PPs, one for each grading dim +# println("HSNum_base_case1: t = $(t)"); +# println("HSNum_base_case1: SimplePPs = $(SimplePPs)"); + # t is not a "simple power", all the others are + ans = HSNum_base_SimplePowers(SimplePPs, T); +### println("HSNum_base_case1: init ans = $(ans)"); + ReducedSimplePPs::Vector{PP} = []; # Vector{PP} + for j in 1:length(SimplePPs) #do + @inbounds e = SimplePPs[j].expv; + k = findfirst((entry -> (entry > 0)), e); # ispositive + if t.expv[k] == 0 #=then=# push!(ReducedSimplePPs,SimplePPs[j]); continue; end #=if=# # no need to make a copy + tt = copy(SimplePPs[j]); + tt.expv[k] -= t.expv[k]; # guaranteed > 0 +## println("in loop: SimplePPs = $(SimplePPs)"); + push!(ReducedSimplePPs, tt); + end #for +# println("HSNum_base_case1: input SimplePPs = $(SimplePPs)"); +# println("HSNum_base_case1: ReducedSimplePPs = $(ReducedSimplePPs)"); + e = t.expv; + nvars = length(e); + @inbounds scale = prod([T[k]^e[k] for k in 1:nvars]); +### println("HSNum_base_case1: scale = $(scale)"); + ans = ans - scale * HSNum_base_SimplePowers(ReducedSimplePPs, T) +###println("HSNum_base_case1: final ans = $(ans)"); + return ans; +end # function + + +## CC contains at least 2 connected components (each compt repr as Vector{Int64} of the variable indexes in the compt) +function HSNum_splitting_case(CC::Vector{Vector{Int64}}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) + HSNumList = []; ## list of HSNums + # Now find any simple PPs which are indep of the conn compts found + nvars = length(NonSimplePPs[1].expv); + FoundVars = PP([0 for _ in 1:nvars]); # will be prod of all vars appearing in some conn compt + for IndexSet in CC #=do=# + for j in IndexSet #=do=# + mult_by_var!(FoundVars, j); + end #=for=# + end #=for=# + IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs); + for IndexSet in CC #do + SubsetNonSimplePPs = filter((t -> involves(t,IndexSet)), NonSimplePPs); + SubsetSimplePPs = filter((t -> involves(t,IndexSet)), SimplePPs); +##println(" -- SPLIT -- recursive call to LOOP Simple=$(SubsetSimplePPs) NonSimple=$(SubsetGens)"); + push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetNonSimplePPs, T, PivotStrategy)); +#? SubsetGens = [ProjectIndets(t, IndexSet) for t in SubsetGens]; +#? SubsetSimplePPs = [ProjectIndets(t, IndexSet) for t in SubsetSimplePPs]; +#? push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetGens, [T[k] for k in IndexSet], PivotStrategy)); + end #for + HSNum_combined = prod(HSNumList); + if !isempty(IsolatedSimplePPs) #then + HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy); +## HSNum_combined *= prod([HSNum_loop([t],PP[],T,PivotStrategy) for t in IsolatedSimplePPs]); + end #if + return HSNum_combined; +end #=function=# + + +function HSNum_total_splitting_case(VarIndexes::Vector{Int64}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) + HSNumList = []; ## list of HSNums + # Now find any simple PPs which are indep of the conn compts found + nvars = length(NonSimplePPs[1].expv); + FoundVars = PP([0 for _ in 1:nvars]); # will be prod of all vars appearing in some conn compt + for i in VarIndexes #=do=# + mult_by_var!(FoundVars, i); + end #=for=# + IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs); + for t in NonSimplePPs #do + SubsetSimplePPs = filter((s -> !is_coprime(s,t)), SimplePPs); +##println(" -- SPLIT -- recursive call to LOOP Simple=$(SubsetSimplePPs) NonSimple=$(SubsetGens)"); + push!(HSNumList, HSNum_base_case1(t, SubsetSimplePPs, T)); + end #for + HSNum_combined = prod(HSNumList); + if !isempty(IsolatedSimplePPs) #then + HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy); +## HSNum_combined *= prod([HSNum_loop([t],PP[],T) for t in IsolatedSimplePPs]); + end #if + return HSNum_combined; +end #=function=# + +# term-order corr to matrix with -1 on anti-diag: [[0,0,-1],[0,-1,0],...] +function IsRevLexSmaller(t1::PP, t2::PP) + n = length(t1.expv); + for j in n:-1:1 #=do=# + if t1.expv[j] != t2.expv[j] #=then=# return (t1.expv[j] > t2.expv[j]); end #=if=# + end #=for=# + return false; # t1 and t2 were equal (should not happen in this code) +end #=function=# + +function RevLexMin(L::Vector{PP}) + # assume length(L) > 0 + if isempty(L) #=then=# return L[1]; end #=if=# + IndexMin = 1; + for j in 2:length(L) #=do=# + if !IsRevLexSmaller(L[j], L[IndexMin]) + IndexMin = j; + end #=if=# + end #=for=# + return L[IndexMin]; +end #=function=# + +function RevLexMax(L::Vector{PP}) + # assume length(L) > 0 + if length(L) == 1 #=then=# return L[1]; end #=if=# + IndexMax = 1; + for j in 2:length(L) #=do=# + if !IsRevLexSmaller(L[IndexMax], L[j]) + IndexMax = j; + end #=if=# + end #=for=# + return L[IndexMax]; +end #=function=# + +function HSNum_Bayer_Stillman(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}) +##println("HSNum_BS: Simple: $(SimplePPs)"); +##println("HSNum_BS: NonSimple: $(NonSimplePPs)"); + # Maybe sort the gens??? + if isempty(NonSimplePPs) #=then=# return HSNum_base_SimplePowers(SimplePPs, T); end #=if=# + if length(NonSimplePPs) == 1 #=then=# return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T); end #=if=# + # NonSimplePPs contains at least 2 elements +# BSPivot = last(NonSimplePPs); # pick one somehow -- this is just simple to impl +#?? BSPivot = RevLexMin(NonSimplePPs); # VERY SLOW on Hilbert-test-rnd6.jl + BSPivot = RevLexMax(NonSimplePPs); +#VERY SLOW?!? BSPivot = RevLexMax(vcat(SimplePPs,NonSimplePPs)); # VERY SLOW on Hilbert-test-rnd6.jl +println("BSPivot = $(BSPivot)"); + NonSPP = filter((t -> (t != BSPivot)), NonSimplePPs); + SPP = SimplePPs;#filter((t -> (t != BSPivot)), SimplePPs); + part1 = HSNum_loop(SPP, NonSPP, T, :bayer_stillman); + ReducedPPs = interreduce([colon(t,BSPivot) for t in vcat(SPP,NonSPP)]); + NewSimplePPs, NewNonSimplePPs = SeparateSimplePPs(ReducedPPs); + part2 = HSNum_loop(NewSimplePPs, NewNonSimplePPs, T, :bayer_stillman); + e = BSPivot.expv; + return part1 - prod([T[k]^e[k] for k in 1:length(e)])*part2; +end #=function=# + +#-------------------------------------------- +# Pivot selection strategies + +# [[AUXILIARY FN]] +# Return 2-tuple: +# (part 1) list of indexes of the indets which appear in +# the greatest number of PPs in gens. +# (part 2) corr freq +function HSNum_most_freq_indets(gens::Vector{PP}) + # ASSUMES: gens is non-empty + nvars = length(gens[1].expv); + freq = [0 for i in 1:nvars]; # Vector{Int} or Vector{UInt} ??? + for t in gens #do + e = t.expv; + for i in 1:nvars #do + @inbounds if (e[i] != 0) #then + @inbounds freq[i] += 1 + end #if + end #for + end #for + MaxFreq = maximum(freq); +### if MaxFreq == 1 #=then=# println("MaxFreq = 1"); end #=if=# + MostFreq = findall((x -> x==MaxFreq), freq); + return MostFreq, MaxFreq; +end #=function=# + + +# Returns index of the indet +function HSNum_most_freq_indet1(gens::Vector{PP}) + # ASSUMES: gens is non-empty + MostFreq,_ = HSNum_most_freq_indets(gens); + return MostFreq[1]; +end #=function=# + +# Returns index of the indet +function HSNum_most_freq_indet_rnd(gens::Vector{PP}) + # ASSUMES: gens is non-empty + MostFreq,_ = HSNum_most_freq_indets(gens); + return rand(MostFreq); +end #=function=# + + + +function HSNum_choose_pivot_indet(MostFreq::Vector{Int64}, gens::Vector{PP}) + PivotIndet = rand(MostFreq); ##HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 + nvars = length(gens[1].expv); + PivotExpv = [0 for _ in 1:nvars]; + PivotExpv[PivotIndet] = 1; + PivotPP = PP(PivotExpv); +end #function + +function HSNum_choose_pivot_simple_power_median(MostFreq::Vector{Int64}, gens::Vector{PP}) + # simple-power-pivot from Bigatti JPAA, 1997 + PivotIndet = rand(MostFreq); ##HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 + exps = [t.expv[PivotIndet] for t in gens]; + exps = filter((e -> e>0), exps); + sort!(exps); + exp = exps[div(1+length(exps),2)]; # "median" + nvars = length(gens[1].expv); + PivotExpv = [0 for _ in 1:nvars]; + PivotExpv[PivotIndet] = exp; + PivotPP = PP(PivotExpv); +end #function + +function HSNum_choose_pivot_simple_power_max(MostFreq::Vector{Int64}, gens::Vector{PP}) + # simple-power-pivot from Bigatti JPAA, 1997 + PivotIndet = rand(MostFreq); ##HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 + exps = [t.expv[PivotIndet] for t in gens]; + exp = max(exps...); + nvars = length(gens[1].expv); + PivotExpv = [0 for _ in 1:nvars]; + PivotExpv[PivotIndet] = exp; + PivotPP = PP(PivotExpv); +end #function + +function HSNum_choose_pivot_gcd2simple(MostFreq::Vector{Int64}, gens::Vector{PP}) + PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); + return cand[1]; # can happen only if there is a "simple splitting case" + end #=if=# + nvars = length(gens[1].expv); + expv = [0 for _ in 1:nvars]; + expv[PivotIndet] = min(cand[1].expv[PivotIndet], cand[2].expv[PivotIndet]); + return PP(expv); +end #function + +function HSNum_choose_pivot_gcd2max(MostFreq::Vector{Int64}, gens::Vector{PP}) + PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); + return cand[1]; # can happen only if there is a "simple splitting case" + end #=if=# + pick2 = [cand[k] for k in random_subset(length(cand),2)]; + t = gcd(pick2[1], pick2[2]); +# println("BEFORE: t= $(t)"); + d = 0; for i in 1:length(t.expv) #=do=# d = max(d,t.expv[i]); end #=for=# + for i in 1:length(t.expv) #=do=# if t.expv[i] < d #=then=# t.expv[i]=0; end #=if=# end #=for=# +# println("AFTER: t= $(t)"); + return t; +end #function + + +# May produce a non-simple pivot!!! +function HSNum_choose_pivot_gcd3(MostFreq::Vector{Int64}, gens::Vector{PP}) + PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); + return cand[1]; # can happen only if there is a "simple splitting case" + end #=if=# + if length(cand) == 2 #=then=# + return gcd(cand[1], cand[2]); + end #=if=# + pick3 = [cand[k] for k in random_subset(length(cand),3)]; + return gcd3(pick3[1], pick3[2], pick3[3]); +end #function + + +function HSNum_choose_pivot_gcd3simple(MostFreq::Vector{Int64}, gens::Vector{PP}) + PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); + return cand[1]; # can happen only if there is a "simple splitting case" + end #=if=# + if length(cand) == 2 #=then=# + t = gcd(cand[1], cand[2]); + else + pick3 = [cand[k] for k in random_subset(length(cand),3)]; + t = gcd3(pick3[1], pick3[2], pick3[3]); + end #=if=# +# println("BEFORE: t= $(t)"); + j = 1; + d = t.expv[1]; + for i in 2:length(t.expv) #=do=# + if t.expv[i] <= d #=then=# + t.expv[i] = 0; + continue; + end #=if=# + t.expv[j] = 0; + j=i; + d = t.expv[i]; + end #=for=# +# println("AFTER: t= $(t)"); + return t; +end #function + +function HSNum_choose_pivot_gcd3max(MostFreq::Vector{Int64}, gens::Vector{PP}) + PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); + return cand[1]; # can happen only if there is a "simple splitting case" + end #=if=# + if length(cand) == 2 #=then=# + t = gcd(cand[1], cand[2]); + else + pick3 = [cand[k] for k in random_subset(length(cand),3)]; + t = gcd3(pick3[1], pick3[2], pick3[3]); + end #=if=# +# println("BEFORE: t= $(t)"); + d = 0; for i in 1:length(t.expv) #=do=# d = max(d,t.expv[i]); end #=for=# + for i in 1:length(t.expv) #=do=# if t.expv[i] < d #=then=# t.expv[i]=0; end #=if=# end #=for=# +# println("AFTER: t= $(t)"); + return t; +end #function + + +# May produce a non-simple pivot!!! +function HSNum_choose_pivot_gcd4(MostFreq::Vector{Int64}, gens::Vector{PP}) + PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); + return cand[1]; # can happen only if there is a "simple splitting case" + end #=if=# + if length(cand) == 2 #=then=# + return gcd(cand[1], cand[2]); + end #=if=# + if length(cand) ==3 #=then=# + return gcd3(cand[1], cand[2], cand[3]); + end #=if=# + pick4 = [cand[k] for k in random_subset(length(cand),4)]; + return gcd(gcd(pick4[1], pick4[2]), gcd(pick4[3],pick4[4])); +end #function + + + +# Assume SimplePPs+NonSimplePPs are interreduced +function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) +# println("LOOP: SimplePPs=$(SimplePPs)"); +# println("LOOP: first <=5 NonSimplePPs=$(first(NonSimplePPs,5))"); +# CHECK=vcat(SimplePPs,NonSimplePPs);if length(CHECK) != length(interreduce(CHECK)) #=then=# println("LOOP: !!!BAD INPUT!!!"); error("BANG!"); end #=if=# + # Check if we have base case 0 + if isempty(NonSimplePPs) #then + # println("LOOP: END OF CALL --> delegate base case 0"); + return HSNum_base_SimplePowers(SimplePPs, T); + end #if + # Check if we have base case 1 + if length(NonSimplePPs) == 1 #then + # println("LOOP: END OF CALL --> delegate base case 1"); + return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T); + end #if + # ---------------------- + MostFreq,freq = HSNum_most_freq_indets(NonSimplePPs); + if freq == 1 #=then=# + # total splitting case +#print("+"); + return HSNum_total_splitting_case(MostFreq, SimplePPs, NonSimplePPs, T, PivotStrategy); + end #=if=# + + if PivotStrategy == :bayer_stillman #=then=# + return HSNum_Bayer_Stillman(SimplePPs, NonSimplePPs, T); + end #=if=# + # Check for "splitting case" +if #=length(NonSimplePPs[1].expv) > 4 &&=# length(NonSimplePPs) <= length(NonSimplePPs[1].expv)#=nvars=# #then + CC = ConnectedComponents(NonSimplePPs); +else CC = [] + end #if + if #=false &&=# length(CC) > 1 #then +### print("*");#println("LOOP: END OF CALL --> delegate SPLITTING CASE $(CC)"); + return HSNum_splitting_case(CC, SimplePPs, NonSimplePPs, T, PivotStrategy); + end #if (splitting case) + # ---------------------- + # Pivot case: first do the ideal sum, then do ideal quotient + # (the ideas are relatively simple, the code is long, tedious, and a bit fiddly) +# # REDUCE BIG EXPS -- THIS IDEA SEEMS TO WORK DISAPPONTINGLY ON THE RND TEST EXAMPLES +# nvars = length(NonSimplePPs[1].expv); +# TopPP = PP([0 for _ in 1:nvars]); +# for t in NonSimplePPs #=do=# TopPP = lcm(TopPP, t); end #=for=# +# DegBound = 9; BigExp = max(TopPP.expv...); +# if false && BigExp > DegBound #=then=# +# expv = [0 for _ in 1:nvars]; +# for i in 1:nvars #=do=# if TopPP.expv[i] == BigExp #=then=# expv[i] = div(1+TopPP.expv[i],2); break; end #=if=# +# ##NO for i in 1:nvars #=do=# if TopPP.expv[i] > 9 #=then=# expv[i] = div(1+TopPP.expv[i],2); #=break;=# end #=if=# +# end #=for=# +# PivotPP = PP(expv); +# #println("BIG pivot $(PivotPP)"); +# else +# PivotPP = HSNum_choose_pivot_gcd2simple(MostFreq, NonSimplePPs) + if PivotStrategy == :indet + PivotPP = HSNum_choose_pivot_indet(MostFreq, NonSimplePPs) +end #=if=# + if PivotStrategy == :simple_power_max + PivotPP = HSNum_choose_pivot_simple_power_max(MostFreq, NonSimplePPs) +end #=if=# + if PivotStrategy == :gcd2simple + PivotPP = HSNum_choose_pivot_gcd2simple(MostFreq, NonSimplePPs) +end #=if=# + if PivotStrategy == :gcd2max + PivotPP = HSNum_choose_pivot_gcd2max(MostFreq, NonSimplePPs) +end #=if=# + if PivotStrategy == :gcd3 + PivotPP = HSNum_choose_pivot_gcd3(MostFreq, NonSimplePPs) +end #=if=# + if PivotStrategy == :gcd3simple + PivotPP = HSNum_choose_pivot_gcd3simple(MostFreq, NonSimplePPs) +end #=if=# + if PivotStrategy == :gcd3max + PivotPP = HSNum_choose_pivot_gcd3max(MostFreq, NonSimplePPs) +end #=if=# + if PivotStrategy == :gcd4 + PivotPP = HSNum_choose_pivot_gcd4(MostFreq, NonSimplePPs) +end #=if=# + if PivotStrategy == :simple_power_median || PivotStrategy == :auto + PivotPP = HSNum_choose_pivot_simple_power_median(MostFreq, NonSimplePPs) + end #=if=# +# end #=if=# +##=DEVELOP/DEBUG=# if (degree(PivotPP) > 3 && !IsSimplePowerPP(PivotPP)) println(">>>>>PIVOT<<<<< $(PivotPP)"); end; + PivotIsSimple = IsSimplePowerPP(PivotPP); + PivotIndex = findfirst((e -> e>0), PivotPP.expv); # used only if PivotIsSimple == true + USE_SAFE_VERSION_SUM=false; + if USE_SAFE_VERSION_SUM #=then=# + # Safe but slow version: just add new gen, then interreduce + RecurseSum = vcat(SimplePPs, NonSimplePPs); + push!(RecurseSum, PivotPP); + RecurseSum = interreduce(RecurseSum); + RecurseSum_SimplePPs_OLD, RecurseSum_NonSimplePPs_OLD = SeparateSimplePPs(RecurseSum); + RecurseSum_NonSimplePPs = RecurseSum_NonSimplePPs_OLD; + RecurseSum_SimplePPs = RecurseSum_SimplePPs_OLD; + else # use "clever" version: + # We know that SimplePPs + NonSimplePPs are already interreduced + if PivotIsSimple #=then=# + RecurseSum_SimplePPs_NEW = copy(SimplePPs); + k = findfirst((t -> t.expv[PivotIndex] > 0), SimplePPs); + if k === nothing + push!(RecurseSum_SimplePPs_NEW, PivotPP); + else + RecurseSum_SimplePPs_NEW[k] = PivotPP; + end #if + RecurseSum_NonSimplePPs_NEW = filter((t -> t.expv[PivotIndex] < PivotPP.expv[PivotIndex]), NonSimplePPs); + else # PivotPP is not simple -- so this is the "general case" + RecurseSum_SimplePPs_NEW = copy(SimplePPs); # need to copy? + RecurseSum_NonSimplePPs_NEW = filter((t -> !IsDivisible(t,PivotPP)), NonSimplePPs); + push!(RecurseSum_NonSimplePPs_NEW, PivotPP); + end #=if=# + RecurseSum_NonSimplePPs = RecurseSum_NonSimplePPs_NEW; + RecurseSum_SimplePPs = RecurseSum_SimplePPs_NEW; + end #=if=# # USE_SAFE_VERSION_SUM + # println(" SUM recurses on:"); + # println(" SimplePPs = $(RecurseSum_SimplePPs)"); + # println(" NonSimplePPs = $(RecurseSum_NonSimplePPs)"); + + # Check that both approaches gave equivalent answers (well, not obviously inequivalent) + # # if length(RecurseSum_NonSimplePPs_NEW) != length(RecurseSum_NonSimplePPs_OLD) || length(RecurseSum_SimplePPs_NEW) != length(RecurseSum_SimplePPs_OLD) #=then=# + # # println("!!!DIFFER!!!"); + # # println("PivotPP = $(PivotPP)"); + # # println("Simple_NEW = $(RecurseSum_SimplePPs_NEW)"); + # # println("Simple_OLD = $(RecurseSum_SimplePPs_OLD)"); + # # println("NonSimple_NEW = $(RecurseSum_NonSimplePPs_NEW)"); + # # println("NonSimple_OLD = $(RecurseSum_NonSimplePPs_OLD)"); + # # end #=if=# + + # Now do the quotient... + # Now get SimplePPs & NonSimplePPs for the quotient while limiting amount of interreduction + USE_SAFE_VERSION_QUOT = false; + if USE_SAFE_VERSION_QUOT #=then=# + #=SAFE VERSION: simpler but probably slower=# + RecurseQuot = [colon(t,PivotPP) for t in vcat(SimplePPs,NonSimplePPs)]; + RecurseQuot = interreduce(RecurseQuot); + RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs = SeparateSimplePPs(RecurseQuot); + else # Use "smart version" + if !PivotIsSimple #then # GENERAL CASE (e.g. if not PivotIsSimple) + # Clever approach (for non-simple pivots) taken from Bigatti 1997 paper (p.247 just after Prop 1 to end of sect 5) + # println("QUOT: NON-SIMPLE PIVOT $(PivotPP)"); + # println(" init SimplePPs = $(SimplePPs)"); + # println(" init NonSimplePPs = $(NonSimplePPs)"); + BM = PP[]; NotBM = PP[]; + PivotPlus = mult(PivotPP, radical(PivotPP)); + for t in NonSimplePPs #=do=# if IsDivisible(t,PivotPlus) #=then=# push!(BM,t); else push!(NotBM, t); end #=if=# end #=for=# + ## println("PivotPP is $(PivotPP)"); + ## println("PivotPlus is $(PivotPlus)"); + # println(" NotBM is $(NotBM)"); + BM = [divide(t,PivotPP) for t in BM]; # divide is same as colon here + NotBM = vcat(NotBM, SimplePPs); + NotBM_mixed = PP[]; NotBM_coprime = PP[]; + for t in NotBM #=do=# if is_coprime(t,PivotPP) #=then=# push!(NotBM_coprime,t); else push!(NotBM_mixed, colon(t,PivotPP)); end #=if=# end #=for=# + # At this poiint we have 3 disjoint lists of PPs: BM (big multiples), NotBM_coprime, NotBM_mixed + # In all cases the PPs have been colon-ed by PivotPP + # println(" NotBM_mixed = $(NotBM_mixed)"); + # println(" NotBM_coprime = $(NotBM_coprime)"); + NotBM_mixed = interreduce(NotBM_mixed); # cannot easily be "clever" here + # println(" NotBM_mixed INTERRED = $(NotBM_mixed)"); + filter!((t -> NotMultOf(NotBM_mixed,t)), NotBM_coprime); + # println(" NotBM_coprime FILTERED is $(NotBM_coprime)"); + ## if !isempty(BM) println("BM is $(BM)"); else print("*"); end + RecurseQuot = vcat(NotBM_coprime, NotBM_mixed); # already interreduced +##DEBUGGING if length(RecurseQuot) != length(interreduce(RecurseQuot)) #=then=# println(">>>>>>DIFFER<<<<<< RQ=$(length(RecurseQuot)) interr=$(length(interreduce(RecurseQuot)))"); end #=if=# + RQ_SimplePPs, RQ_NonSimplePPs = SeparateSimplePPs(RecurseQuot); + RecurseQuot_SimplePPs = RQ_SimplePPs; ###???interreduce(vcat(RQ_SimplePPs, [colon(t,PivotPP) for t in SimplePPs])); + RecurseQuot_NonSimplePPs = vcat(BM, RQ_NonSimplePPs); + ### RecurseQuot = vcat(NotBM, [colon(t,PivotPP) for t in SimplePPs]); RecurseQuot = interreduce(RecurseQuot); RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs = SeparateSimplePPs(RecurseQuot); RecurseQuot_NonSimplePPs = vcat(RecurseQuot_NonSimplePPs, BM); # Be cleverer about interreduced + # println("QUOT FINAL: PivotPP=$(PivotPP)"); + # println("QUOT FINAL: RecurseQuot_NonSimplePPs=$(RecurseQuot_NonSimplePPs)"); + # println("QUOT FINAL: RecurseQuot_SimplePPs=$(RecurseQuot_SimplePPs)"); +##CHECK=vcat(RecurseQuot_SimplePPs,RecurseQuot_NonSimplePPs);if length(CHECK) != length(interreduce(CHECK)) #=then=# println("LOOP: QUOT(S) FINAL !!!BAD OUTPUT!!!"); error("BANG!"); end #=if=# + else # Clever approach when PivotIsSimple + # println("QUOT: SIMPLE PIVOT $(PivotPP)"); + # println(" init SimplePPs = $(SimplePPs)"); + # println(" init NonSimplePPs = $(NonSimplePPs)"); + # The idea behind this code is fairly simple; sadly the code itself is not :-( + RecurseQuot_SimplePPs = copy(SimplePPs); + k = findfirst((t -> t.expv[PivotIndex] > 0), RecurseQuot_SimplePPs); + if !(k === nothing) + RecurseQuot_SimplePPs[k] = copy(RecurseQuot_SimplePPs[k]); #???copy needed??? + RecurseQuot_SimplePPs[k].expv[PivotIndex] -= PivotPP.expv[PivotIndex]; + end #if + DegPivot = degree(PivotPP); + NonSimpleTbl = [PP[] for _ in 0:DegPivot]; ## WARNING: indexes are offset by 1 -- thanks Julia! + NonSimple1 = PP[]; # will contain all PPs divisible by PivotPP^(1+epsilon) + for t in NonSimplePPs #=do=# + degt = t.expv[PivotIndex]; + if degt > DegPivot #=then=# + push!(NonSimple1, divide(t, PivotPP)); + else + push!(NonSimpleTbl[degt+1], colon(t,PivotPP)); + end #=if=# + end #=for=# + ## println("PivotPP = $(PivotPP)"); + # println("(Quot simple) TBL: $(NonSimpleTbl)"); + NonSimple2 = NonSimpleTbl[DegPivot+1]; + for i in DegPivot:-1:1 #=do=# + NewPPs = filter((t -> NotMultOf(NonSimple2,t)), NonSimpleTbl[i]); + NonSimple2 = vcat(NonSimple2, NewPPs); + end #=for=# + ## println("After interred: NonSimple1=$(NonSimple1)"); + ## println("After interred: NonSimple2=$(NonSimple2)"); + NewSimplePPs = filter(IsSimplePowerPP, NonSimple2); + NonSimple2 = filter(!IsSimplePowerPP, NonSimple2); ## SeparateSimplePPs??? + if !isempty(NewSimplePPs) #=then=# + RecurseQuot_SimplePPs = interreduce(vcat(RecurseQuot_SimplePPs, NewSimplePPs)); + end #=if=# + RecurseQuot_NonSimplePPs = vcat(NonSimple1, NonSimple2); + # println("QUOT(S) FINAL: PivotPP=$(PivotPP)"); + # println("QUOT(S) FINAL: RecurseQuot_NonSimplePPs=$(RecurseQuot_NonSimplePPs)"); + # println("QUOT(S) FINAL: RecurseQuot_SimplePPs=$(RecurseQuot_SimplePPs)"); +##CHECK=vcat(RecurseQuot_SimplePPs,RecurseQuot_NonSimplePPs);if length(CHECK) != length(interreduce(CHECK)) #=then=# println("LOOP: QUOT(S) FINAL !!!BAD OUTPUT!!!"); error("BANG!"); end #=if=# + end #=if=# #PivotIsSimple + end #=if=# # end of USE_SAFE_VERSION_QUOT + # Now put the two pieces together: + nvars = length(PivotPP.expv); + scale = prod([T[k]^PivotPP.expv[k] for k in 1:nvars]); + # println("RECURSION:"); + # println(" SUM recursion: simple $(RecurseSum_SimplePPs)"); + # println(" SUM recursion: nonsimple $(RecurseSum_NonSimplePPs)"); + # println(" QUOT recursion: simple $(RecurseQuot_SimplePPs)"); + # println(" QUOT recursion: nonsimple $(RecurseQuot_NonSimplePPs)"); + HSNum_sum = HSNum_loop(RecurseSum_SimplePPs, RecurseSum_NonSimplePPs, T, PivotStrategy); + # println("RECURSION: after HSNum_sum"); + # println(" SUM recursion: simple $(RecurseSum_SimplePPs)"); + # println(" SUM recursion: nonsimple $(RecurseSum_NonSimplePPs)"); + # println(" QUOT recursion: simple $(RecurseQuot_SimplePPs)"); + # println(" QUOT recursion: nonsimple $(RecurseQuot_NonSimplePPs)"); + HSNum_quot = HSNum_loop(RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs, T, PivotStrategy); + # println("LOOP: END OF CALL"); + return HSNum_sum + scale*HSNum_quot; +end #function + + +function SeparateSimplePPs(gens::Vector{PP}) + SimplePPs::Vector{PP} = []; + NonSimplePPs::Vector{PP} = []; + for g in gens #do + if IsSimplePowerPP(g) #then + push!(SimplePPs, g) + else + push!(NonSimplePPs, g) + end #if + end #for + return SimplePPs, NonSimplePPs; +end #function + + +# Check args: either throws or returns nothing. +function HSNum_CheckArgs(gens::Vector{PP}, W::Vector{Vector{Int}}) + if isempty(gens) throw("HSNum: need at least 1 generator"); end #if + if isempty(W) throw("HSNum: weight matrix must have at least 1 row"); end #if + nvars = length(gens[1].expv); + if !all((t -> length(t.expv)==nvars), gens) + throw("HSNum: generators must all have same size exponent vectors"); + end #if + if !all((row -> length(row)==nvars), W) + throw("HSNum: weight matrix must have 1 column for each variable") + end #if + # Zero weights are allowed??? + # Args are OK, so simply return (without throwing) +end # function + + +function HSNum(gens::Vector{PP}, W::Vector{Vector{Int}}, PivotStrategy::Symbol) +### println("HSNum: gens = $(gens)"); +### println("HSNum: W = $(W)"); + HSNum_CheckArgs(gens, W); + # Grading is over ZZ^m + m = size(W)[1]; # NumRows + ncols = size(W[1])[1]; # how brain-damaged is Julia??? + nvars = length(gens[1].expv); + if ncols != nvars #then + throw(ArgumentError("weights matrix has wrong number of columns ($(ncols)); should be same as number of variables ($(nvars))")); + end #if + HPRing, t = LaurentPolynomialRing(QQ, ["t$k" for k in 1:m]); + T = [one(HPRing) for k in 1:nvars]; + for k in 1:nvars #do + s = one(HPRing); + for j in 1:m #do + s *= t[j]^W[j][k]; + end #for + T[k] = s; + end #for + # Now have T[1] = t1^W[1,1] * t2^W[2,1] * ..., etc +### println("HSNum: T vector is $(T)"); + SimplePPs,NonSimplePPs = SeparateSimplePPs(interreduce(gens)); + sort!(NonSimplePPs, lt=DegRevLexLess); # recommended by Bayer+Stillman (for their criterion) + return HSNum_loop(SimplePPs, NonSimplePPs, T, PivotStrategy); +end #function + +#----------------------------------------------------------------------------- +# This fn copied from GradedModule.jl (in dir OSCAR/HILBERT/) +function gen_repr(d) + grading_dim = length(gens(parent(d))); + return [getindex(d,k) for k in 1:grading_dim]; +end #function + +function HSNum_fudge(PmodI::MPolyQuoRing, PivotStrategy::Symbol = :auto) +if PivotStrategy == :indet return nothing; end + I = PmodI.I; + P = base_ring(I);##parent(gens(I)[1]); # there MUST be a better way!! + nvars = length(gens(P)); + grading_dim = length(gens(parent(degree(gen(P,1))))); # better way??? + weights = [degree(var) for var in gens(P)]; + W = [[0 for _ in 1:nvars] for _ in 1:grading_dim]; + for i in 1:nvars #do + expv = [Int64(exp) for exp in gen_repr(degree(gen(P,i)))]; + for j in 1:grading_dim #do + W[j][i] = expv[j]; + end #for + end #for + # ## W = [[Int64(exp) for exp in gen_repr(d)] for d in weights]; + # W=[] + # for d in weights #do + # expv = [Int64(exp) for exp in gen_repr(d)]; + # if isempty(W) #then + # W = expv; + # else + # W = hcat(W, expv); + # end #if + # end #for + # W = [W[:,i] for i in 1:size(W,2)] + # # ?transpose? hcat +## println("W is $(W)"); + LTs = gens(leading_ideal(I)); + PPs = [PP(degrees(t)) for t in LTs]; + return HSNum(PPs, W, PivotStrategy); +end #function diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index 752a02059c8c..beb24c2822e6 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -109,18 +109,21 @@ julia> hilbert_series(A) ``` """ function hilbert_series(A::MPolyQuoRing) - if iszero(A.I) - R = base_ring(A.I) - @req is_z_graded(R) "The base ring must be ZZ-graded" - W = R.d - W = [Int(W[i][1]) for i = 1:ngens(R)] - @req minimum(W) > 0 "The weights must be positive" - Zt, t = ZZ["t"] - den = prod([1-t^Int(w[1]) for w in R.d]) - return (one(parent(t)), den) - end - H = HilbertData(A.I) - return hilbert_series(H) + R = base_ring(A.I) + if !is_z_graded(R) + return Oscar.HSNum_fudge(A) + end + if iszero(A.I) + @req is_z_graded(R) "The base ring must be ZZ-graded" + W = R.d + W = [Int(W[i][1]) for i = 1:ngens(R)] + @req minimum(W) > 0 "The weights must be positive" + Zt, t = ZZ["t"] + den = prod([1-t^Int(w[1]) for w in R.d]) + return (one(parent(t)), den) + end + H = HilbertData(A.I) + return hilbert_series(H) end diff --git a/test/Rings/hilbert.jl b/test/Rings/hilbert.jl index dde604a3e13b..209bfb65c368 100644 --- a/test/Rings/hilbert.jl +++ b/test/Rings/hilbert.jl @@ -1,5 +1,5 @@ @testset "Abbott Hilbert series" begin - P, x = graded_polynomial_ring(QQ, 5, "x"); + P, x = graded_polynomial_ring(QQ, 5, "x", [1 1 1 1 1; 1 -2 3 -4 5]); G = [ x[1]^30*x[2]^16*x[3]^7*x[4]^72*x[5]^31, @@ -57,4 +57,5 @@ I = ideal(P,G); PmodI, _ = quo(P,I); HS = hilbert_series(PmodI); + HS = Oscar.HSNum_fudge(PmodI); end From 0735b9db6f24ef7df9c43fd4efa23c467cc209c1 Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Mon, 7 Aug 2023 14:12:58 +0200 Subject: [PATCH 04/51] Extract my implementation of hilbert series into a separate file. --- src/Rings/Rings.jl | 1 + src/Rings/hilbert_zach.jl | 435 ++++++++++++++++++++++++++++++++++++++ src/Rings/mpoly-graded.jl | 428 ------------------------------------- 3 files changed, 436 insertions(+), 428 deletions(-) create mode 100644 src/Rings/hilbert_zach.jl diff --git a/src/Rings/Rings.jl b/src/Rings/Rings.jl index 441288d21936..3719a3c7c435 100644 --- a/src/Rings/Rings.jl +++ b/src/Rings/Rings.jl @@ -3,6 +3,7 @@ include("orderings.jl") include("mpoly.jl") include("mpoly_types.jl") include("mpoly-graded.jl") +include("hilbert_zach.jl") include("mpoly-ideals.jl") include("groebner.jl") include("solving.jl") diff --git a/src/Rings/hilbert_zach.jl b/src/Rings/hilbert_zach.jl new file mode 100644 index 000000000000..d3073a954aff --- /dev/null +++ b/src/Rings/hilbert_zach.jl @@ -0,0 +1,435 @@ +######################################################################## +# Backend functionality for computation of multivariate Hilbert series +# due to Matthias Zach +######################################################################## + + +####################################################################### +# 06.10.2022 +# The following internal routine can probably still be tuned. +# +# Compared to the implementation in Singular, it is still about 10 +# times slower. We spotted the following possible deficits: +# +# 1) The returned polynomials are not yet built with MPolyBuildCtx. +# We tried that, but as for now, the code for the build context +# is even slower than the direct implementation that is in place +# now. Once MPolyBuildCtx is tuned, we should try that again; +# the code snippets are still there. In total, building the return +# values takes up more than one third of the computation time +# at this moment. +# +# 2) Numerous allocations for integer vectors. The code below +# performs lots of iterative allocations for lists of integer +# vectors. If we constructed a container data structure to +# maintain this list internally and do the allocations at once +# (for instance in a big matrix), this could significantly +# speed up the code. However, that is too much work for +# the time being, as long as a high-performing Hilbert series +# computation is not of technical importance. +# +# 3) Singular uses bitmasking for exponent vectors to decide on +# divisibility more quickly. This is particularly important in +# the method _divide_by. For singular, this leads to a speedup +# of factor 5. Here, only less than one third of the time is +# spent in `_divide_by`, so it does not seem overly important +# at this point, but might become relevant in the future. +# A particular modification of the singular version of bitmasking +# is to compute means for the exponents occurring for each variable +# and set the bits depending on whether a given exponent is greater +# or less than that mean value. + +function _hilbert_numerator_from_leading_exponents( + a::Vector{Vector{Int}}, + weight_matrix::Matrix{Int}, + return_ring::Ring, + #algorithm=:generator + #algorithm=:custom + #algorithm=:gcd + #algorithm=:indeterminate + #algorithm=:cocoa + algorithm::Symbol # =:BayerStillmanA, # This is by far the fastest strategy. Should be used. + # short exponent vectors where the k-th bit indicates that the k-th + # exponent is non-zero. + ) + t = gens(return_ring) + ret = _hilbert_numerator_trivial_cases(a, weight_matrix, return_ring, t) + ret !== nothing && return ret + + if algorithm == :BayerStillmanA + return _hilbert_numerator_bayer_stillman(a, weight_matrix, return_ring, t) + elseif algorithm == :custom + return _hilbert_numerator_custom(a, weight_matrix, return_ring, t) + elseif algorithm == :gcd # see Remark 5.3.11 + return _hilbert_numerator_gcd(a, weight_matrix, return_ring, t) + elseif algorithm == :generator # just choosing on random generator, cf. Remark 5.3.8 + return _hilbert_numerator_generator(a, weight_matrix, return_ring, t) + elseif algorithm == :indeterminate # see Remark 5.3.8 + return _hilbert_numerator_indeterminate(a, weight_matrix, return_ring, t) + elseif algorithm == :cocoa # see Remark 5.3.14 + return _hilbert_numerator_cocoa(a, weight_matrix, return_ring, t) + end + error("invalid algorithm") +end + +# compute t ^ (weight_matrix * expvec), where t == gens(S) +function _expvec_to_poly(S::Ring, t::Vector, weight_matrix::Matrix{Int}, expvec::Vector{Int}) + o = one(coefficient_ring(S)) # TODO: cache this ?! + return S([o], [weight_matrix * expvec]) +end + +# special case for univariate polynomial ring +function _expvec_to_poly(S::PolyRing, t::Vector, weight_matrix::Matrix{Int}, expvec::Vector{Int}) + @assert length(t) == 1 + @assert size(weight_matrix) == (1, length(expvec)) + + # compute the dot-product of weight_matrix[1,:] and expvec, but faster than dot + # TODO: what about overflows? + s = weight_matrix[1,1] * expvec[1] + for i in 2:length(expvec) + @inbounds s += weight_matrix[1,i] * expvec[i] + end + return t[1]^s +end + +function _hilbert_numerator_trivial_cases( + a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, + S::Ring, t::Vector, oneS = one(S) + ) + length(a) == 0 && return oneS + + # See Proposition 5.3.6 + if _are_pairwise_coprime(a) + return prod(oneS - _expvec_to_poly(S, t, weight_matrix, e) for e in a) + end + + return nothing +end + + +function _hilbert_numerator_cocoa( + a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, + S::Ring, t::Vector + ) + ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t) + ret !== nothing && return ret + + n = length(a) + m = length(a[1]) + counters = [0 for i in 1:m] + for e in a + counters += [iszero(k) ? 0 : 1 for k in e] + end + j = _find_maximum(counters) + + p = a[rand(1:n)] + while p[j] == 0 + p = a[rand(1:n)] + end + + q = a[rand(1:n)] + while q[j] == 0 || p == q + q = a[rand(1:n)] + end + + pivot = [0 for i in 1:m] + pivot[j] = minimum([p[j], q[j]]) + + + ### Assembly of the quotient ideal with less generators + rhs = [e for e in a if !_divides(e, pivot)] + push!(rhs, pivot) + + ### Assembly of the division ideal with less total degree + lhs = _divide_by_monomial_power(a, j, pivot[j]) + + f = one(S) + for i in 1:nvars(S) + z = t[i] + f *= z^(sum([pivot[j]*weight_matrix[i, j] for j in 1:length(pivot)])) + end + + return _hilbert_numerator_cocoa(rhs, weight_matrix, S, t) + f*_hilbert_numerator_cocoa(lhs, weight_matrix, S, t) +end + +function _hilbert_numerator_indeterminate( + a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, + S::Ring, t::Vector + ) + ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t) + ret !== nothing && return ret + + e = first(a) + found_at = findfirst(!iszero, e)::Int64 + pivot = zero(e) + pivot[found_at] = 1 + + ### Assembly of the quotient ideal with less generators + rhs = [e for e in a if e[found_at] == 0] + push!(rhs, pivot) + + ### Assembly of the division ideal with less total degree + lhs = _divide_by(a, pivot) + + f = one(S) + for i in 1:nvars(S) + z = t[i] + f *= z^(sum([pivot[j]*weight_matrix[i, j] for j in 1:length(pivot)])) + end + + return _hilbert_numerator_indeterminate(rhs, weight_matrix, S, t) + f*_hilbert_numerator_indeterminate(lhs, weight_matrix, S, t) +end + +function _hilbert_numerator_generator( + a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, + S::Ring, t::Vector + ) + ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t) + ret !== nothing && return ret + + b = copy(a) + pivot = pop!(b) + + f = one(S) + for i in 1:nvars(S) + z = t[i] + f *= z^(sum([pivot[j]*weight_matrix[i, j] for j in 1:length(pivot)])) + end + + c = _divide_by(b, pivot) + p1 = _hilbert_numerator_generator(b, weight_matrix, S, t) + p2 = _hilbert_numerator_generator(c, weight_matrix, S, t) + + return p1 - f * p2 +end + +function _hilbert_numerator_gcd( + a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, + S::Ring, t::Vector + ) + ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t) + ret !== nothing && return ret + + n = length(a) + counters = [0 for i in 1:length(a[1])] + for e in a + counters += [iszero(k) ? 0 : 1 for k in e] + end + j = _find_maximum(counters) + + p = a[rand(1:n)] + while p[j] == 0 + p = a[rand(1:n)] + end + + q = a[rand(1:n)] + while q[j] == 0 || p == q + q = a[rand(1:n)] + end + + pivot = _gcd(p, q) + + ### Assembly of the quotient ideal with less generators + rhs = [e for e in a if !_divides(e, pivot)] + push!(rhs, pivot) + + ### Assembly of the division ideal with less total degree + lhs = _divide_by(a, pivot) + + f = one(S) + for i in 1:nvars(S) + z = t[i] + f *= z^(sum([pivot[j]*weight_matrix[i, j] for j in 1:length(pivot)])) + end + + return _hilbert_numerator_gcd(rhs, weight_matrix, S, t) + f*_hilbert_numerator_gcd(lhs, weight_matrix, S, t) +end + +function _hilbert_numerator_custom( + a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, + S::Ring, t::Vector + ) + ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t) + ret !== nothing && return ret + + p = Vector{Int}() + q = Vector{Int}() + max_deg = 0 + for i in 1:length(a) + b = a[i] + for j in i+1:length(a) + c = a[j] + r = _gcd(b, c) + if sum(r) > max_deg + max_deg = sum(r) + p = b + q = c + end + end + end + + ### Assembly of the quotient ideal with less generators + pivot = _gcd(p, q) + rhs = [e for e in a if !_divides(e, pivot)] + push!(rhs, pivot) + + ### Assembly of the division ideal with less total degree + lhs = _divide_by(a, pivot) + + f = one(S) + for i in 1:nvars(S) + z = t[i] + f *= z^(sum([pivot[j]*weight_matrix[i, j] for j in 1:length(pivot)])) + end + + return _hilbert_numerator_custom(rhs, weight_matrix, S, t) + f*_hilbert_numerator_custom(lhs, weight_matrix, S, t) +end + +function _hilbert_numerator_bayer_stillman( + a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, + S::Ring, t::Vector, oneS = one(S) + ) + ########################################################################### + # For this strategy see + # + # Bayer, Stillman: Computation of Hilber Series + # J. Symbolic Computation (1992) No. 14, pp. 31--50 + # + # Algorithm 2.6, page 35 + ########################################################################### + ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t, oneS) + ret !== nothing && return ret + + # make sure we have lexicographically ordered monomials + sort!(a, alg=QuickSort) + + # initialize the result + h = oneS - _expvec_to_poly(S, t, weight_matrix, a[1]) + linear_mons = Vector{Int}() + for i in 2:length(a) + J = _divide_by(a[1:i-1], a[i]) + empty!(linear_mons) + J1 = filter!(J) do m + k = findfirst(!iszero, m) + k === nothing && return false # filter out zero vector + if m[k] == 1 && findnext(!iszero, m, k + 1) === nothing + push!(linear_mons, k) + return false + end + return true + end + q = _hilbert_numerator_bayer_stillman(J1, weight_matrix, S, t, oneS) + for k in linear_mons + q *= (oneS - prod(t[i]^weight_matrix[i, k] for i in 1:length(t))) + end + + h -= q * _expvec_to_poly(S, t, weight_matrix, a[i]) + end + return h +end + +function _find_maximum(a::Vector{Int}) + m = a[1] + j = 1 + for i in 1:length(a) + if a[i] > m + m = a[i] + j = i + end + end + return j +end + +### This implements the speedup from the CoCoA strategy, +# see Exercise 4 in Section 5.3 +function _divide_by_monomial_power(a::Vector{Vector{Int}}, j::Int, k::Int) + divides(a::Vector{Int}, b::Vector{Int}) = all(k->(k>=0), b[1:j-1] - a[1:j-1]) && all(k->(k>=0), b[j+1:end] - a[j+1:end]) + kept = [e for e in a if e[j] == 0] + for i in 1:k + next_slice = [e for e in a if e[j] == i] + still_kept = next_slice + for e in kept + all(v->!divides(v, e), next_slice) && push!(still_kept, e) + end + kept = still_kept + end + still_kept = vcat(still_kept, [e for e in a if e[j] > k]) + + result = Vector{Vector{Int}}() + for e in still_kept + v = copy(e) + v[j] = (e[j] > k ? e[j] - k : 0) + push!(result, v) + end + return result +end + +######################################################################## +# Compute the Hilbert series from the monomial leading ideal using +# different bisection strategies along the lines of +# [Kreuzer, Robbiano: Computational Commutative Algebra 2, Springer] +# Section 5.3. +######################################################################## + +_divides(a::Vector{Int}, b::Vector{Int}) = all(k->(a[k]>=b[k]), 1:length(a)) + +function _gcd(a::Vector{Int}, b::Vector{Int}) + return [a[k] > b[k] ? b[k] : a[k] for k in 1:length(a)] +end + +function _are_pairwise_coprime(a::Vector{Vector{Int}}) + length(a) <= 1 && return true + n = length(a) + m = length(a[1]) + for i in 1:n-1 + for j in i+1:n + all(k -> a[i][k] == 0 || a[j][k] == 0, 1:m) || return false + end + end + return true +end + +### Assume that a is minimal. We divide by `pivot` and return +# a minimal set of generators for the resulting monomial ideal. +function _divide_by(a::Vector{Vector{Int}}, pivot::Vector{Int}) + # The good ones will contribute to a minimal generating + # set of the lhs ideal. + # + # The bad monomials come from those which hop over the boundaries of + # the monomial diagram by the shift. Their span has a new + # generator which is collected in `bad` a priori. It is checked + # whether they become superfluous and if not, they are added to + # the good ones. + good = sizehint!(Vector{Vector{Int}}(), length(a)) + bad = sizehint!(Vector{Vector{Int}}(), length(a)) + for e in a + if _divides(e, pivot) + push!(good, e - pivot) + else + push!(bad, e) + end + end + + # pre-allocate m so that don't need to allocate it again each loop iteration + m = similar(pivot) + for e in bad + # the next line computers m = [k < 0 ? 0 : k for k in e] + # but without allocations + for i in 1:length(e) + m[i] = max(e[i] - pivot[i], 0) + end + + # check whether the new monomial m is already in the span + # of the good ones. If yes, discard it. If not, discard those + # elements of the good ones that are in the span of m and put + # m in the list of good ones instead. + if all(x->!_divides(m, x), good) + # Remove those 'good' elements which are multiples of m + filter!(x -> !_divides(x, m), good) + push!(good, copy(m)) + end + end + + return good +end + diff --git a/src/Rings/mpoly-graded.jl b/src/Rings/mpoly-graded.jl index 81a2b23d53b1..3b2465fcbcdc 100644 --- a/src/Rings/mpoly-graded.jl +++ b/src/Rings/mpoly-graded.jl @@ -1543,434 +1543,6 @@ function Base.show(io::IO, h::HilbertData) print(io, "Hilbert Series for $(h.I), data: $(h.data), weights: $(h.weights)") ###new end -######################################################################## -# Compute the Hilbert series from the monomial leading ideal using -# different bisection strategies along the lines of -# [Kreuzer, Robbiano: Computational Commutative Algebra 2, Springer] -# Section 5.3. -######################################################################## - -_divides(a::Vector{Int}, b::Vector{Int}) = all(k->(a[k]>=b[k]), 1:length(a)) - -function _gcd(a::Vector{Int}, b::Vector{Int}) - return [a[k] > b[k] ? b[k] : a[k] for k in 1:length(a)] -end - -function _are_pairwise_coprime(a::Vector{Vector{Int}}) - length(a) <= 1 && return true - n = length(a) - m = length(a[1]) - for i in 1:n-1 - for j in i+1:n - all(k -> a[i][k] == 0 || a[j][k] == 0, 1:m) || return false - end - end - return true -end - -### Assume that a is minimal. We divide by `pivot` and return -# a minimal set of generators for the resulting monomial ideal. -function _divide_by(a::Vector{Vector{Int}}, pivot::Vector{Int}) - # The good ones will contribute to a minimal generating - # set of the lhs ideal. - # - # The bad monomials come from those which hop over the boundaries of - # the monomial diagram by the shift. Their span has a new - # generator which is collected in `bad` a priori. It is checked - # whether they become superfluous and if not, they are added to - # the good ones. - good = sizehint!(Vector{Vector{Int}}(), length(a)) - bad = sizehint!(Vector{Vector{Int}}(), length(a)) - for e in a - if _divides(e, pivot) - push!(good, e - pivot) - else - push!(bad, e) - end - end - - # pre-allocate m so that don't need to allocate it again each loop iteration - m = similar(pivot) - for e in bad - # the next line computers m = [k < 0 ? 0 : k for k in e] - # but without allocations - for i in 1:length(e) - m[i] = max(e[i] - pivot[i], 0) - end - - # check whether the new monomial m is already in the span - # of the good ones. If yes, discard it. If not, discard those - # elements of the good ones that are in the span of m and put - # m in the list of good ones instead. - if all(x->!_divides(m, x), good) - # Remove those 'good' elements which are multiples of m - filter!(x -> !_divides(x, m), good) - push!(good, copy(m)) - end - end - - return good -end - -### This implements the speedup from the CoCoA strategy, -# see Exercise 4 in Section 5.3 -function _divide_by_monomial_power(a::Vector{Vector{Int}}, j::Int, k::Int) - divides(a::Vector{Int}, b::Vector{Int}) = all(k->(k>=0), b[1:j-1] - a[1:j-1]) && all(k->(k>=0), b[j+1:end] - a[j+1:end]) - kept = [e for e in a if e[j] == 0] - for i in 1:k - next_slice = [e for e in a if e[j] == i] - still_kept = next_slice - for e in kept - all(v->!divides(v, e), next_slice) && push!(still_kept, e) - end - kept = still_kept - end - still_kept = vcat(still_kept, [e for e in a if e[j] > k]) - - result = Vector{Vector{Int}}() - for e in still_kept - v = copy(e) - v[j] = (e[j] > k ? e[j] - k : 0) - push!(result, v) - end - return result -end - -function _find_maximum(a::Vector{Int}) - m = a[1] - j = 1 - for i in 1:length(a) - if a[i] > m - m = a[i] - j = i - end - end - return j -end - -####################################################################### -# 06.10.2022 -# The following internal routine can probably still be tuned. -# -# Compared to the implementation in Singular, it is still about 10 -# times slower. We spotted the following possible deficits: -# -# 1) The returned polynomials are not yet built with MPolyBuildCtx. -# We tried that, but as for now, the code for the build context -# is even slower than the direct implementation that is in place -# now. Once MPolyBuildCtx is tuned, we should try that again; -# the code snippets are still there. In total, building the return -# values takes up more than one third of the computation time -# at this moment. -# -# 2) Numerous allocations for integer vectors. The code below -# performs lots of iterative allocations for lists of integer -# vectors. If we constructed a container data structure to -# maintain this list internally and do the allocations at once -# (for instance in a big matrix), this could significantly -# speed up the code. However, that is too much work for -# the time being, as long as a high-performing Hilbert series -# computation is not of technical importance. -# -# 3) Singular uses bitmasking for exponent vectors to decide on -# divisibility more quickly. This is particularly important in -# the method _divide_by. For singular, this leads to a speedup -# of factor 5. Here, only less than one third of the time is -# spent in `_divide_by`, so it does not seem overly important -# at this point, but might become relevant in the future. -# A particular modification of the singular version of bitmasking -# is to compute means for the exponents occurring for each variable -# and set the bits depending on whether a given exponent is greater -# or less than that mean value. - -function _hilbert_numerator_from_leading_exponents( - a::Vector{Vector{Int}}, - weight_matrix::Matrix{Int}, - return_ring::Ring, - #algorithm=:generator - #algorithm=:custom - #algorithm=:gcd - #algorithm=:indeterminate - #algorithm=:cocoa - algorithm::Symbol # =:BayerStillmanA, # This is by far the fastest strategy. Should be used. - # short exponent vectors where the k-th bit indicates that the k-th - # exponent is non-zero. - ) - t = gens(return_ring) - ret = _hilbert_numerator_trivial_cases(a, weight_matrix, return_ring, t) - ret !== nothing && return ret - - if algorithm == :BayerStillmanA - return _hilbert_numerator_bayer_stillman(a, weight_matrix, return_ring, t) - elseif algorithm == :custom - return _hilbert_numerator_custom(a, weight_matrix, return_ring, t) - elseif algorithm == :gcd # see Remark 5.3.11 - return _hilbert_numerator_gcd(a, weight_matrix, return_ring, t) - elseif algorithm == :generator # just choosing on random generator, cf. Remark 5.3.8 - return _hilbert_numerator_generator(a, weight_matrix, return_ring, t) - elseif algorithm == :indeterminate # see Remark 5.3.8 - return _hilbert_numerator_indeterminate(a, weight_matrix, return_ring, t) - elseif algorithm == :cocoa # see Remark 5.3.14 - return _hilbert_numerator_cocoa(a, weight_matrix, return_ring, t) - end - error("invalid algorithm") -end - -# compute t ^ (weight_matrix * expvec), where t == gens(S) -function _expvec_to_poly(S::Ring, t::Vector, weight_matrix::Matrix{Int}, expvec::Vector{Int}) - o = one(coefficient_ring(S)) # TODO: cache this ?! - return S([o], [weight_matrix * expvec]) -end - -# special case for univariate polynomial ring -function _expvec_to_poly(S::PolyRing, t::Vector, weight_matrix::Matrix{Int}, expvec::Vector{Int}) - @assert length(t) == 1 - @assert size(weight_matrix) == (1, length(expvec)) - - # compute the dot-product of weight_matrix[1,:] and expvec, but faster than dot - # TODO: what about overflows? - s = weight_matrix[1,1] * expvec[1] - for i in 2:length(expvec) - @inbounds s += weight_matrix[1,i] * expvec[i] - end - return t[1]^s -end - -function _hilbert_numerator_trivial_cases( - a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, - S::Ring, t::Vector, oneS = one(S) - ) - length(a) == 0 && return oneS - - # See Proposition 5.3.6 - if _are_pairwise_coprime(a) - return prod(oneS - _expvec_to_poly(S, t, weight_matrix, e) for e in a) - end - - return nothing -end - - -function _hilbert_numerator_cocoa( - a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, - S::Ring, t::Vector - ) - ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t) - ret !== nothing && return ret - - n = length(a) - m = length(a[1]) - counters = [0 for i in 1:m] - for e in a - counters += [iszero(k) ? 0 : 1 for k in e] - end - j = _find_maximum(counters) - - p = a[rand(1:n)] - while p[j] == 0 - p = a[rand(1:n)] - end - - q = a[rand(1:n)] - while q[j] == 0 || p == q - q = a[rand(1:n)] - end - - pivot = [0 for i in 1:m] - pivot[j] = minimum([p[j], q[j]]) - - - ### Assembly of the quotient ideal with less generators - rhs = [e for e in a if !_divides(e, pivot)] - push!(rhs, pivot) - - ### Assembly of the division ideal with less total degree - lhs = _divide_by_monomial_power(a, j, pivot[j]) - - f = one(S) - for i in 1:nvars(S) - z = t[i] - f *= z^(sum([pivot[j]*weight_matrix[i, j] for j in 1:length(pivot)])) - end - - return _hilbert_numerator_cocoa(rhs, weight_matrix, S, t) + f*_hilbert_numerator_cocoa(lhs, weight_matrix, S, t) -end - -function _hilbert_numerator_indeterminate( - a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, - S::Ring, t::Vector - ) - ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t) - ret !== nothing && return ret - - e = first(a) - found_at = findfirst(!iszero, e)::Int64 - pivot = zero(e) - pivot[found_at] = 1 - - ### Assembly of the quotient ideal with less generators - rhs = [e for e in a if e[found_at] == 0] - push!(rhs, pivot) - - ### Assembly of the division ideal with less total degree - lhs = _divide_by(a, pivot) - - f = one(S) - for i in 1:nvars(S) - z = t[i] - f *= z^(sum([pivot[j]*weight_matrix[i, j] for j in 1:length(pivot)])) - end - - return _hilbert_numerator_indeterminate(rhs, weight_matrix, S, t) + f*_hilbert_numerator_indeterminate(lhs, weight_matrix, S, t) -end - -function _hilbert_numerator_generator( - a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, - S::Ring, t::Vector - ) - ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t) - ret !== nothing && return ret - - b = copy(a) - pivot = pop!(b) - - f = one(S) - for i in 1:nvars(S) - z = t[i] - f *= z^(sum([pivot[j]*weight_matrix[i, j] for j in 1:length(pivot)])) - end - - c = _divide_by(b, pivot) - p1 = _hilbert_numerator_generator(b, weight_matrix, S, t) - p2 = _hilbert_numerator_generator(c, weight_matrix, S, t) - - return p1 - f * p2 -end - -function _hilbert_numerator_gcd( - a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, - S::Ring, t::Vector - ) - ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t) - ret !== nothing && return ret - - n = length(a) - counters = [0 for i in 1:length(a[1])] - for e in a - counters += [iszero(k) ? 0 : 1 for k in e] - end - j = _find_maximum(counters) - - p = a[rand(1:n)] - while p[j] == 0 - p = a[rand(1:n)] - end - - q = a[rand(1:n)] - while q[j] == 0 || p == q - q = a[rand(1:n)] - end - - pivot = _gcd(p, q) - - ### Assembly of the quotient ideal with less generators - rhs = [e for e in a if !_divides(e, pivot)] - push!(rhs, pivot) - - ### Assembly of the division ideal with less total degree - lhs = _divide_by(a, pivot) - - f = one(S) - for i in 1:nvars(S) - z = t[i] - f *= z^(sum([pivot[j]*weight_matrix[i, j] for j in 1:length(pivot)])) - end - - return _hilbert_numerator_gcd(rhs, weight_matrix, S, t) + f*_hilbert_numerator_gcd(lhs, weight_matrix, S, t) -end - -function _hilbert_numerator_custom( - a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, - S::Ring, t::Vector - ) - ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t) - ret !== nothing && return ret - - p = Vector{Int}() - q = Vector{Int}() - max_deg = 0 - for i in 1:length(a) - b = a[i] - for j in i+1:length(a) - c = a[j] - r = _gcd(b, c) - if sum(r) > max_deg - max_deg = sum(r) - p = b - q = c - end - end - end - - ### Assembly of the quotient ideal with less generators - pivot = _gcd(p, q) - rhs = [e for e in a if !_divides(e, pivot)] - push!(rhs, pivot) - - ### Assembly of the division ideal with less total degree - lhs = _divide_by(a, pivot) - - f = one(S) - for i in 1:nvars(S) - z = t[i] - f *= z^(sum([pivot[j]*weight_matrix[i, j] for j in 1:length(pivot)])) - end - - return _hilbert_numerator_custom(rhs, weight_matrix, S, t) + f*_hilbert_numerator_custom(lhs, weight_matrix, S, t) -end - -function _hilbert_numerator_bayer_stillman( - a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, - S::Ring, t::Vector, oneS = one(S) - ) - ########################################################################### - # For this strategy see - # - # Bayer, Stillman: Computation of Hilber Series - # J. Symbolic Computation (1992) No. 14, pp. 31--50 - # - # Algorithm 2.6, page 35 - ########################################################################### - ret = _hilbert_numerator_trivial_cases(a, weight_matrix, S, t, oneS) - ret !== nothing && return ret - - # make sure we have lexicographically ordered monomials - sort!(a, alg=QuickSort) - - # initialize the result - h = oneS - _expvec_to_poly(S, t, weight_matrix, a[1]) - linear_mons = Vector{Int}() - for i in 2:length(a) - J = _divide_by(a[1:i-1], a[i]) - empty!(linear_mons) - J1 = filter!(J) do m - k = findfirst(!iszero, m) - k === nothing && return false # filter out zero vector - if m[k] == 1 && findnext(!iszero, m, k + 1) === nothing - push!(linear_mons, k) - return false - end - return true - end - q = _hilbert_numerator_bayer_stillman(J1, weight_matrix, S, t, oneS) - for k in linear_mons - q *= (oneS - prod(t[i]^weight_matrix[i, k] for i in 1:length(t))) - end - - h -= q * _expvec_to_poly(S, t, weight_matrix, a[i]) - end - return h -end ############################################################################ ### Homogenization and Dehomogenization From 67dee24803d6497701ee2fe3aab9ba9d59c20435 Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Mon, 7 Aug 2023 14:18:06 +0200 Subject: [PATCH 05/51] Add some comments. --- src/Rings/hilbert_zach.jl | 59 ++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/src/Rings/hilbert_zach.jl b/src/Rings/hilbert_zach.jl index d3073a954aff..fe953076ba79 100644 --- a/src/Rings/hilbert_zach.jl +++ b/src/Rings/hilbert_zach.jl @@ -72,25 +72,15 @@ function _hilbert_numerator_from_leading_exponents( error("invalid algorithm") end -# compute t ^ (weight_matrix * expvec), where t == gens(S) -function _expvec_to_poly(S::Ring, t::Vector, weight_matrix::Matrix{Int}, expvec::Vector{Int}) - o = one(coefficient_ring(S)) # TODO: cache this ?! - return S([o], [weight_matrix * expvec]) -end - -# special case for univariate polynomial ring -function _expvec_to_poly(S::PolyRing, t::Vector, weight_matrix::Matrix{Int}, expvec::Vector{Int}) - @assert length(t) == 1 - @assert size(weight_matrix) == (1, length(expvec)) - # compute the dot-product of weight_matrix[1,:] and expvec, but faster than dot - # TODO: what about overflows? - s = weight_matrix[1,1] * expvec[1] - for i in 2:length(expvec) - @inbounds s += weight_matrix[1,i] * expvec[i] - end - return t[1]^s -end +######################################################################## +# Implementations of the different algorithms below +# +# Compute the Hilbert series from the monomial leading ideal using +# different bisection strategies along the lines of +# [Kreuzer, Robbiano: Computational Commutative Algebra 2, Springer] +# Section 5.3. +######################################################################## function _hilbert_numerator_trivial_cases( a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, @@ -106,7 +96,6 @@ function _hilbert_numerator_trivial_cases( return nothing end - function _hilbert_numerator_cocoa( a::Vector{Vector{Int}}, weight_matrix::Matrix{Int}, S::Ring, t::Vector @@ -328,6 +317,31 @@ function _hilbert_numerator_bayer_stillman( return h end + +######################################################################## +# Auxiliary helper functions below +######################################################################## + +### compute t ^ (weight_matrix * expvec), where t == gens(S) +function _expvec_to_poly(S::Ring, t::Vector, weight_matrix::Matrix{Int}, expvec::Vector{Int}) + o = one(coefficient_ring(S)) # TODO: cache this ?! + return S([o], [weight_matrix * expvec]) +end + +### special case for univariate polynomial ring +function _expvec_to_poly(S::PolyRing, t::Vector, weight_matrix::Matrix{Int}, expvec::Vector{Int}) + @assert length(t) == 1 + @assert size(weight_matrix) == (1, length(expvec)) + + # compute the dot-product of weight_matrix[1,:] and expvec, but faster than dot + # TODO: what about overflows? + s = weight_matrix[1,1] * expvec[1] + for i in 2:length(expvec) + @inbounds s += weight_matrix[1,i] * expvec[i] + end + return t[1]^s +end + function _find_maximum(a::Vector{Int}) m = a[1] j = 1 @@ -364,13 +378,6 @@ function _divide_by_monomial_power(a::Vector{Vector{Int}}, j::Int, k::Int) return result end -######################################################################## -# Compute the Hilbert series from the monomial leading ideal using -# different bisection strategies along the lines of -# [Kreuzer, Robbiano: Computational Commutative Algebra 2, Springer] -# Section 5.3. -######################################################################## - _divides(a::Vector{Int}, b::Vector{Int}) = all(k->(a[k]>=b[k]), 1:length(a)) function _gcd(a::Vector{Int}, b::Vector{Int}) From dc227fc8566af8c10f5ee95222dfc10a1607034d Mon Sep 17 00:00:00 2001 From: John Abbott Date: Mon, 7 Aug 2023 14:59:57 +0200 Subject: [PATCH 06/51] New scope for verbosity in hilbert series numerator code --- src/Oscar.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Oscar.jl b/src/Oscar.jl index b52ea57cd22a..bf6fb6bd1dcf 100644 --- a/src/Oscar.jl +++ b/src/Oscar.jl @@ -112,6 +112,8 @@ function __init__() add_verbose_scope(:Blowup) add_assert_scope(:Blowup) + add_verbose_scope(:hilbert) + add_assert_scope(:hilbert) add_verbose_scope(:GlobalTateModel) add_verbose_scope(:WeierstrassModel) From 8fdacaebd7c15995b2a07cdac9277df89e7480b5 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Mon, 7 Aug 2023 15:00:55 +0200 Subject: [PATCH 07/51] Extensive cleaning; working towards OSCAR coding stds --- src/Rings/hilbert.jl | 1491 ++++++++++++++++++++---------------------- 1 file changed, 722 insertions(+), 769 deletions(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index 7fe800d2a199..7d08b29cbaa8 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -1,47 +1,49 @@ -#import Pkg; Pkg.add("AbstractAlgebra"); -#import AbstractAlgebra: degree; -###??? import AbstractAlgebra: is_one; #is_one; +################################################################## +# Auxiliary functions +# Perhaps doxument them and make them publicly available? +# random_subset: # A function similar to this is in StatsBase.jl but I wish to avoid # having an external dependency (for just 1 simple fn!) # Generate random m-subset of {1,2,3,...,n} # Result is a Int64[]; entries are NOT SORTED!! function random_subset(n::Int64, m::Int64) - # assume n >= 1, m >= 0 and m <= n - if m == 0 #=then=# - return Int64[]; - end #=if=# - L = collect(1:n); - if m == n #=then=# - return L; - end #=if=# - for j in 1:m #=do=# - k = rand(j:n); - L[j],L[k] = L[k],L[j]; # just a SWAP - end #=for=# - L = first(L,m); - #?? sort!(L); ?? + # assume n >= 1, m >= 0 and m <= n + if m == 0 #=then=# + return Int64[]; + end #=if=# + L = collect(1:n); + if m == n #=then=# return L; + end #=if=# + for j in 1:m #=do=# + k = rand(j:n); + L[j],L[k] = L[k],L[j]; # just a SWAP + end #=for=# + L = first(L,m); + #?? sort!(L); ?? + return L; end #=function=# # There must be a better way...! # Split a "list" into 2 parts determined by a predicate. # Returns 2-tuple: list-of-sat-elems, list-of-unsat-elems function filter2(pred::Function, L::Vector) - sat = []; - unsat = []; - for x in L #=do=# - if pred(x) - push!(sat,x); - else - push!(unsat,x); - end #=if=# - end #=for=# - return sat,unsat; + sat = []; + unsat = []; + for x in L #=do=# + if pred(x) + push!(sat,x); + else + push!(unsat,x); + end #=if=# + end #=for=# + return sat,unsat; end #=function=# -############################################ + +################################################################## # Code for representing & manipulating PPs (power products, aka. monomials) # All this code is "local" to this file, & not exported! @@ -55,461 +57,460 @@ PP_exponent = Int64; # UInt ??? Strange: Int32 was slower on my machine ?!? #= mutable =# struct PP - expv::Vector{PP_exponent}; + expv::Vector{PP_exponent}; end # struct function Base.copy(t::PP) - return PP(copy(t.expv)); + return PP(copy(t.expv)); end #=function=# # RETURN VALUE??? Perhaps index or 0? (or -1?) function IsSimplePowerPP(t::PP) - CountNZ = 0; - for i in 1:length(t.expv) #do - @inbounds if (t.expv[i] == 0) continue; end #if - if (CountNZ > 0) return false; end #if - CountNZ = i; - end #for - if (CountNZ != 0) return true; end #if MAYBE RETURN index & exp??? - return false; # because t == 1 + CountNZ = 0; + for i in 1:length(t.expv) #do + @inbounds if (t.expv[i] == 0) continue; end #if + if (CountNZ > 0) return false; end #if + CountNZ = i; + end #for + if (CountNZ != 0) return true; end #if MAYBE RETURN index & exp??? + return false; # because t == 1 end #function # Should be is_one, but julia complained :-( function isone(t::PP) - return all(t.expv .== 0); + return all(t.expv .== 0); end #function function degree(t::PP) - return sum(t.expv); + return sum(t.expv); end #function function IsDivisible(t::PP, s::PP) # is t divisible by s - n = length(t.expv); # assume equal to length(s.expv); - for i in 1:n #do - @inbounds if t.expv[i] < s.expv[i] #then - return false; - end #if - end #for - return true; + n = length(t.expv); # assume equal to length(s.expv); + for i in 1:n #do + @inbounds if t.expv[i] < s.expv[i] #then + return false; + end #if + end #for + return true; end #function # modifies first arg function mult_by_var!(t::PP, j::Int64) - @inbounds t.expv[j] += 1; + @inbounds t.expv[j] += 1; end #function function mult(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) - return PP(t1.expv + t2.expv); + # ASSUMES: length(t1.expv) == length(t2.expv) + return PP(t1.expv + t2.expv); end #function function divide(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv), also that t1 is mult of t2 - return PP(t1.expv - t2.expv); + # ASSUMES: length(t1.expv) == length(t2.expv), also that t1 is mult of t2 + return PP(t1.expv - t2.expv); end #function function is_coprime(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) - n = length(t1.expv); - for i in 1:n #do - @inbounds if t1.expv[i] != 0 && t2.expv[i] != 0 #=then=# - return false; - end #=if=# - end #for - return true; + # ASSUMES: length(t1.expv) == length(t2.expv) + n = length(t1.expv); + for i in 1:n #do + @inbounds if t1.expv[i] != 0 && t2.expv[i] != 0 #=then=# + return false; + end #=if=# + end #for + return true; end #function function lcm(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) - n = length(t1.expv); - expv = [0 for _ in 1:n]; - for i in 1:n #do - @inbounds expv[i] = max(t1.expv[i], t2.expv[i]); - end #for - return PP(expv); + # ASSUMES: length(t1.expv) == length(t2.expv) + n = length(t1.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds expv[i] = max(t1.expv[i], t2.expv[i]); + end #for + return PP(expv); end #function function gcd(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) - n = length(t1.expv); - expv = [0 for _ in 1:n]; - for i in 1:n #do - @inbounds expv[i] = min(t1.expv[i], t2.expv[i]); - end #for - return PP(expv); + # ASSUMES: length(t1.expv) == length(t2.expv) + n = length(t1.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds expv[i] = min(t1.expv[i], t2.expv[i]); + end #for + return PP(expv); end #function function gcd3(t1::PP, t2::PP, t3::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) == length(t3.expv) - n = length(t1.expv); - expv = [0 for _ in 1:n]; - for i in 1:n #do - @inbounds expv[i] = min(t1.expv[i], t2.expv[i], t3.expv[i]); - end #for - return PP(expv); + # ASSUMES: length(t1.expv) == length(t2.expv) == length(t3.expv) + n = length(t1.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds expv[i] = min(t1.expv[i], t2.expv[i], t3.expv[i]); + end #for + return PP(expv); end #function function colon(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) - n = length(t1.expv); - expv = [0 for _ in 1:n]; - for i in 1:n #do - @inbounds expv[i] = max(0, t1.expv[i]-t2.expv[i]); - end #for - return PP(expv); + # ASSUMES: length(t1.expv) == length(t2.expv) + n = length(t1.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds expv[i] = max(0, t1.expv[i]-t2.expv[i]); + end #for + return PP(expv); end #function function saturatePP(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) - n = length(t1.expv); - expv = [0 for _ in 1:n]; - for i in 1:n #do - @inbounds if t2.expv[i] == 0 #then - @inbounds expv[i] = t1.expv[i]; - end #if - end #for - return PP(expv); + # ASSUMES: length(t1.expv) == length(t2.expv) + n = length(t1.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds if t2.expv[i] == 0 #then + @inbounds expv[i] = t1.expv[i]; + end #if + end #for + return PP(expv); end #function function radical(t::PP) - n = length(t.expv); - expv = [0 for _ in 1:n]; - for i in 1:n #do - @inbounds if t.expv[i] > 0 #then - expv[i] = 1; - end #if - end #for - return PP(expv); + n = length(t.expv); + expv = [0 for _ in 1:n]; + for i in 1:n #do + @inbounds if t.expv[i] > 0 #then + expv[i] = 1; + end #if + end #for + return PP(expv); end #function # if t1 == t2 then returns false. function DegRevLexLess(t1::PP, t2::PP) - d1 = sum(t1.expv); - d2 = sum(t2.expv); - if d1 != d2 #=then=# return (d1 < d2); end #=if=# - nvars = length(t1.expv); - for i in nvars:-1:1 #=do=# - if t1.expv[i] != t2.expv[i] #=then=# - return (t1.expv[i] > t2.expv[i]); - end #=if=# - end #=for=# - return false; + d1 = sum(t1.expv); + d2 = sum(t2.expv); + if d1 != d2 #=then=# return (d1 < d2); end #=if=# + nvars = length(t1.expv); + for i in nvars:-1:1 #=do=# + if t1.expv[i] != t2.expv[i] #=then=# + return (t1.expv[i] > t2.expv[i]); + end #=if=# + end #=for=# + return false; end #=function=# # Test whether PP involves at least 1 indet from the list K (of indexes) function involves(t::PP, K::Vector{Int64}) # K is index set - # ASSUMES: indexes in K are all in range - for i in K #do - if t.expv[i] != 0 #then - return true; - end #if - end #for - return false; + # ASSUMES: indexes in K are all in range + for i in K #do + if t.expv[i] != 0 #then + return true; + end #if + end #for + return false; end #function function Base.show(io::IO, t::PP) - if (all(t.expv .== 0)) # (isone(PP)) # why doesn't isone work ??? - print(io, "1"); - return; + if (all(t.expv .== 0)) # (isone(PP)) # why doesn't isone work ??? + print(io, "1"); + return; + end #if + str = ""; + n = length(t.expv); + for i in 1:n #do + if (t.expv[i] != 0) #then + if (str != "") str = str * "*"; end #if + str = str * "x[$(i)]"; + if (t.expv[i] > 1) #then + str = str * "^$(t.expv[i])"; + end #if end #if - str = ""; - n = length(t.expv); - for i in 1:n #do - if (t.expv[i] != 0) #then - if (str != "") str = str * "*"; end #if - str = str * "x[$(i)]"; - if (t.expv[i] > 1) #then - str = str * "^$(t.expv[i])"; - end #if - end #if - end #for - print(io, str); + end #for + print(io, str); end #function +####################################################### +# Interreduction of a list of PPs + # interreduce list of PPs; equiv find min set of gens for PP monoideal function interreduce(L::Vector{PP}) # L is list of PPs - sort!(L, by=degree); - MinGens = PP[];##empty Vector{PP}(); - for t in L #do - discard = false; - for s in MinGens #do - if IsDivisible(t,s) #then - discard = true; - break; - end #if - end #for - if !discard #then - push!(MinGens, t); - end #if - end # for - return MinGens; + sort!(L, by=degree); + MinGens = PP[];##empty Vector{PP}(); + for t in L #do + discard = false; + for s in MinGens #do + if IsDivisible(t,s) #then + discard = true; + break; + end #if + end #for + if !discard #then + push!(MinGens, t); + end #if + end # for + return MinGens; end # function # Is t a multiple of at least one element of L? # Add degree truncation??? function NotMultOf(L::Vector{PP}, t::PP) - for s in L #=do=# - if IsDivisible(t,s) - return false; - end #=if=# - end #=for=# - return true; + for s in L #=do=# + if IsDivisible(t,s) + return false; + end #=if=# + end #=for=# + return true; end #=function=# # "project" PP onto sub-monoid of PPs gen by indets in indexes function ProjectIndets(t::PP, indexes::Vector{Int}) - # # expv = [0 for _ in 1:length(indexes)]; - # # for i in 1:length(indexes) #do - # # expv[i] = t.expv[indexes[i]]; - # # end #for - # # return PP(expv); - return PP([t.expv[k] for k in indexes]); + # # expv = [0 for _ in 1:length(indexes)]; + # # for i in 1:length(indexes) #do + # # expv[i] = t.expv[indexes[i]]; + # # end #for + # # return PP(expv); + return PP([t.expv[k] for k in indexes]); end #function # NOT SURE THIS IS USEFUL: how many indets are really needed in the list L? function TrueNumVars(L::Vector{PP}) - # assume L non empty? - if isempty(L) return 0; end #if -## MaxNumVars = length(L[1].expv); -## AllVars = PP([1 for _ in 1:MaxNumVars]); - t = radical(L[1]); - for j in 2:length(L) #do - t = radical(mult(t,L[j])); - end #for - return degree(t); + # assume L non empty? + if isempty(L) return 0; end #if + ## MaxNumVars = length(L[1].expv); + ## AllVars = PP([1 for _ in 1:MaxNumVars]); + t = radical(L[1]); + for j in 2:length(L) #do + t = radical(mult(t,L[j])); + end #for + return degree(t); end #function -#----------------------------------------------------------------------------- +################################################################### # This function is also private/local to this file. -# Each PP in the list L is interpreted as saying that all indets +# Think of a graph where each vertex is labelled by an indeterminate. +# Each PP in the list L is interpreted as saying that all indeterminates # involved in that PP are "connected". The task is to find (minimal) -# connected componnts of indets. - -# Connected components of variables +# connected components of the entire graph. # Input: non-empty list of PPs # Output: list of lists of var indexes, each sublist is a connected component function ConnectedComponents(L::Vector{PP}) - ConnCompt::Vector{Vector{Int64}} = []; - nvars = length(L[1].expv); - IgnoreVar = [ false for _ in 1:nvars]; - VarAppears = copy(IgnoreVar); - for t in L #do - for j in 1:nvars #do - @inbounds if t.expv[j] != 0 #then - @inbounds VarAppears[j] = true; - end #if - end #for - end #for - CountIgnore = 0; + ConnCompt::Vector{Vector{Int64}} = []; + nvars = length(L[1].expv); + IgnoreVar = [ false for _ in 1:nvars]; + VarAppears = copy(IgnoreVar); + for t in L #do for j in 1:nvars #do - @inbounds if !VarAppears[j] #then - @inbounds IgnoreVar[j] = true; - CountIgnore += 1; - end #if + @inbounds if t.expv[j] != 0 #then + @inbounds VarAppears[j] = true; + end #if end #for -###println("CountIgnore=$(CountIgnore)"); -###println("nvars=$(nvars)"); - while CountIgnore < nvars #do - ## Maybe use findfirst instead of loop below? - # j=1; - # while IgnoreVar[j] #do - # j += 1; - # end #while - j = findfirst(!, IgnoreVar); # j is index of some var which appears in at least one PP - k = findfirst((t -> t.expv[j] != 0), L); # pick some PP involving j-th var - lcm = L[k]; -###println("lcm=$(lcm)"); + end #for + CountIgnore = 0; + for j in 1:nvars #do + @inbounds if !VarAppears[j] #then + @inbounds IgnoreVar[j] = true; + CountIgnore += 1; + end #if + end #for + while CountIgnore < nvars #do + j = findfirst(!, IgnoreVar); # j is index of some var which appears in at least one PP + k = findfirst((t -> t.expv[j] != 0), L); # pick some PP involving j-th var + lcm = L[k]; + DoAnotherIteration = true; + while DoAnotherIteration #do + DoAnotherIteration = false; + for t in L #do + if is_coprime(lcm,t) continue; end + s = saturatePP(t,lcm); + if isone(s) continue; end + lcm = mult(lcm,s); ### lcm *= s; DoAnotherIteration = true; - while DoAnotherIteration #do - DoAnotherIteration = false; - for t in L #do - if is_coprime(lcm,t) continue; end - s = saturatePP(t,lcm); - if isone(s) continue; end - lcm = mult(lcm,s); ### lcm *= s; - DoAnotherIteration = true; - end #for - end #while - vars = filter((k -> lcm.expv[k] > 0), 1:nvars); - # remove conn compt from L??? -#seems to be slower with this line ?!? L = filter((t -> is_coprime(t,lcm)), L); - push!(ConnCompt, vars); - for k in vars #do - IgnoreVar[k] = true; - CountIgnore += 1; - end #for + end #for end #while - return ConnCompt; + vars = filter((k -> lcm.expv[k] > 0), 1:nvars); + # remove conn compt just found from L??? + #seems to be slower with this line ?!? L = filter((t -> is_coprime(t,lcm)), L); + push!(ConnCompt, vars); + for k in vars #do + IgnoreVar[k] = true; + CountIgnore += 1; + end #for + end #while + return ConnCompt; end #function + ############################################################################# +# Implementation of "Hilbert series numerator": +# main reference Bigatti 1997 (JPAA) "Computation of Hilbert-Poincare series" #----------------------------------------------------------------------------- -# Base cases for HibertFn -- see Bigatti 1997 (JPAA) +# Two base cases for HibertFn # Data needed: -# convention: indets are called x[1] to x[n] -- start from 1 because julia does -# (only at topmost call) Weight matrix: col k is weight of x[k] -# PP: internal repr is expv Vector{PP_exponent} equiv to Vector{Int} or similar -# Gens: interreduced non-empty list of PPs -# HS "indets": seems useful to have power-prods of them by the corr weights. +# convention: indets are called x[1] to x[n] -- start from 1 because julia does +# Weight matrix: col k is weight of x[k] (only at topmost call) +# PP: internal repr is expv Vector{PP_exponent} equiv to Vector{Int} or similar +# Gens: +# SimplePPs: PPs which are "simple" power of an indet +# NonSimplePPs: PPs which are divisible by at least 2 distinct indets +# SimplePPs union NonSimplePPs is interreduced & non-empty!! +# HS "indets": seems useful to have power-prods of them by the corr weights -- parameter T in the fn defns # Case gens are simple powers function HSNum_base_SimplePowers(SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T is list of HSNum PPs, one for each grading dim - ans = 1; #one(T[1]) ??? - for t in SimplePPs #do - k = findfirst(entry -> (entry > 0), t.expv); - ans = ans * (1 - T[k]^t.expv[k]); # ???? ans -= ans*T[k]^t; - end #for - return ans; + ans = 1; #one(T[1]) ??? + for t in SimplePPs #do + k = findfirst(entry -> (entry > 0), t.expv); + ans = ans * (1 - T[k]^t.expv[k]); # ???? ans -= ans*T[k]^t; + end #for + return ans; end #function function HSNum_base_case1(t::PP, SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T is list of HSNum PPs, one for each grading dim -# println("HSNum_base_case1: t = $(t)"); -# println("HSNum_base_case1: SimplePPs = $(SimplePPs)"); - # t is not a "simple power", all the others are - ans = HSNum_base_SimplePowers(SimplePPs, T); -### println("HSNum_base_case1: init ans = $(ans)"); - ReducedSimplePPs::Vector{PP} = []; # Vector{PP} - for j in 1:length(SimplePPs) #do - @inbounds e = SimplePPs[j].expv; - k = findfirst((entry -> (entry > 0)), e); # ispositive - if t.expv[k] == 0 #=then=# push!(ReducedSimplePPs,SimplePPs[j]); continue; end #=if=# # no need to make a copy - tt = copy(SimplePPs[j]); - tt.expv[k] -= t.expv[k]; # guaranteed > 0 -## println("in loop: SimplePPs = $(SimplePPs)"); - push!(ReducedSimplePPs, tt); - end #for -# println("HSNum_base_case1: input SimplePPs = $(SimplePPs)"); -# println("HSNum_base_case1: ReducedSimplePPs = $(ReducedSimplePPs)"); - e = t.expv; - nvars = length(e); - @inbounds scale = prod([T[k]^e[k] for k in 1:nvars]); -### println("HSNum_base_case1: scale = $(scale)"); - ans = ans - scale * HSNum_base_SimplePowers(ReducedSimplePPs, T) -###println("HSNum_base_case1: final ans = $(ans)"); - return ans; + # t is not a "simple power", all the others are + @vprint :hilbert 1 "HSNum_base_case1: t = $(t)"; + @vprint :hilbert 1 "HSNum_base_case1: SimplePPs = $(SimplePPs)"; + ans = HSNum_base_SimplePowers(SimplePPs, T); + ReducedSimplePPs::Vector{PP} = []; + for j in 1:length(SimplePPs) #do + @inbounds e = SimplePPs[j].expv; + k = findfirst((entry -> (entry > 0)), e); # ispositive + if t.expv[k] == 0 #=then=# push!(ReducedSimplePPs,SimplePPs[j]); continue; end #=if=# # no need to make a copy + tt = copy(SimplePPs[j]); + tt.expv[k] -= t.expv[k]; # guaranteed > 0 + push!(ReducedSimplePPs, tt); + end #for + e = t.expv; + nvars = length(e); + @inbounds scale = prod([T[k]^e[k] for k in 1:nvars]); + ans = ans - scale * HSNum_base_SimplePowers(ReducedSimplePPs, T) + @vprint :hilbert 1 "HSNum_base_case1: returning $(ans)"); + return ans; end # function ## CC contains at least 2 connected components (each compt repr as Vector{Int64} of the variable indexes in the compt) function HSNum_splitting_case(CC::Vector{Vector{Int64}}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) - HSNumList = []; ## list of HSNums - # Now find any simple PPs which are indep of the conn compts found - nvars = length(NonSimplePPs[1].expv); - FoundVars = PP([0 for _ in 1:nvars]); # will be prod of all vars appearing in some conn compt - for IndexSet in CC #=do=# - for j in IndexSet #=do=# - mult_by_var!(FoundVars, j); - end #=for=# + @vprint :hilbert 1 "Splitting case: CC = $(CC)"; + HSNumList = []; ## list of HSNums + # Now find any simple PPs which are indep of the conn compts found + nvars = length(NonSimplePPs[1].expv); + FoundVars = PP([0 for _ in 1:nvars]); # will be prod of all vars appearing in some conn compt + for IndexSet in CC #=do=# + for j in IndexSet #=do=# + mult_by_var!(FoundVars, j); end #=for=# - IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs); - for IndexSet in CC #do - SubsetNonSimplePPs = filter((t -> involves(t,IndexSet)), NonSimplePPs); - SubsetSimplePPs = filter((t -> involves(t,IndexSet)), SimplePPs); -##println(" -- SPLIT -- recursive call to LOOP Simple=$(SubsetSimplePPs) NonSimple=$(SubsetGens)"); - push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetNonSimplePPs, T, PivotStrategy)); -#? SubsetGens = [ProjectIndets(t, IndexSet) for t in SubsetGens]; -#? SubsetSimplePPs = [ProjectIndets(t, IndexSet) for t in SubsetSimplePPs]; -#? push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetGens, [T[k] for k in IndexSet], PivotStrategy)); - end #for - HSNum_combined = prod(HSNumList); - if !isempty(IsolatedSimplePPs) #then - HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy); -## HSNum_combined *= prod([HSNum_loop([t],PP[],T,PivotStrategy) for t in IsolatedSimplePPs]); - end #if - return HSNum_combined; + end #=for=# + IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs); + for IndexSet in CC #do + SubsetNonSimplePPs = filter((t -> involves(t,IndexSet)), NonSimplePPs); + SubsetSimplePPs = filter((t -> involves(t,IndexSet)), SimplePPs); + push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetNonSimplePPs, T, PivotStrategy)); + # Next 3 lines commented out: seemed to bring no benefit (??but why??) + #? SubsetNonSimplePPs = [ProjectIndets(t, IndexSet) for t in SubsetNonSimplePPs]; + #? SubsetSimplePPs = [ProjectIndets(t, IndexSet) for t in SubsetSimplePPs]; + #? push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetGens, [T[k] for k in IndexSet], PivotStrategy)); + end #for + HSNum_combined = prod(HSNumList); + if !isempty(IsolatedSimplePPs) #then + HSNum_combined *= HSNum_base_SimplePowers(IsolatedSimplePPs, T); + ##OLD HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy); + end #if + return HSNum_combined; end #=function=# function HSNum_total_splitting_case(VarIndexes::Vector{Int64}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) - HSNumList = []; ## list of HSNums - # Now find any simple PPs which are indep of the conn compts found - nvars = length(NonSimplePPs[1].expv); - FoundVars = PP([0 for _ in 1:nvars]); # will be prod of all vars appearing in some conn compt - for i in VarIndexes #=do=# - mult_by_var!(FoundVars, i); - end #=for=# - IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs); - for t in NonSimplePPs #do - SubsetSimplePPs = filter((s -> !is_coprime(s,t)), SimplePPs); -##println(" -- SPLIT -- recursive call to LOOP Simple=$(SubsetSimplePPs) NonSimple=$(SubsetGens)"); - push!(HSNumList, HSNum_base_case1(t, SubsetSimplePPs, T)); - end #for - HSNum_combined = prod(HSNumList); - if !isempty(IsolatedSimplePPs) #then - HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy); -## HSNum_combined *= prod([HSNum_loop([t],PP[],T) for t in IsolatedSimplePPs]); - end #if - return HSNum_combined; + @vprint :hilbert 1 "Total splitting case: VarIndexes = $(VarIndexes)"; + HSNumList = []; ## list of HSNums + # Now find any simple PPs which are indep of the conn compts found + nvars = length(NonSimplePPs[1].expv); + FoundVars = PP([0 for _ in 1:nvars]); # will be prod of all vars appearing in some conn compt + for i in VarIndexes #=do=# + mult_by_var!(FoundVars, i); + end #=for=# + IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs); + for t in NonSimplePPs #do + SubsetSimplePPs = filter((s -> !is_coprime(s,t)), SimplePPs); + push!(HSNumList, HSNum_base_case1(t, SubsetSimplePPs, T)); + end #for + HSNum_combined = prod(HSNumList); + if !isempty(IsolatedSimplePPs) #then + HSNum_combined *= HSNum_base_SimplePowers(IsolatedSimplePPs, T); + ##OLD HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy); + end #if + return HSNum_combined; end #=function=# +#------------------------------------------------------------------ +# Pivot selection strategies: + # term-order corr to matrix with -1 on anti-diag: [[0,0,-1],[0,-1,0],...] function IsRevLexSmaller(t1::PP, t2::PP) - n = length(t1.expv); - for j in n:-1:1 #=do=# - if t1.expv[j] != t2.expv[j] #=then=# return (t1.expv[j] > t2.expv[j]); end #=if=# - end #=for=# - return false; # t1 and t2 were equal (should not happen in this code) + n = length(t1.expv); + for j in n:-1:1 #=do=# + if t1.expv[j] != t2.expv[j] #=then=# return (t1.expv[j] > t2.expv[j]); end #=if=# + end #=for=# + return false; # t1 and t2 were equal (should not happen in this code) end #=function=# function RevLexMin(L::Vector{PP}) - # assume length(L) > 0 - if isempty(L) #=then=# return L[1]; end #=if=# - IndexMin = 1; - for j in 2:length(L) #=do=# - if !IsRevLexSmaller(L[j], L[IndexMin]) - IndexMin = j; - end #=if=# - end #=for=# - return L[IndexMin]; + # assume length(L) > 0 + if isempty(L) #=then=# return L[1]; end #=if=# + IndexMin = 1; + for j in 2:length(L) #=do=# + if !IsRevLexSmaller(L[j], L[IndexMin]) + IndexMin = j; + end #=if=# + end #=for=# + return L[IndexMin]; end #=function=# function RevLexMax(L::Vector{PP}) - # assume length(L) > 0 - if length(L) == 1 #=then=# return L[1]; end #=if=# - IndexMax = 1; - for j in 2:length(L) #=do=# - if !IsRevLexSmaller(L[IndexMax], L[j]) - IndexMax = j; - end #=if=# - end #=for=# - return L[IndexMax]; + # assume length(L) > 0 + if length(L) == 1 #=then=# return L[1]; end #=if=# + IndexMax = 1; + for j in 2:length(L) #=do=# + if !IsRevLexSmaller(L[IndexMax], L[j]) + IndexMax = j; + end #=if=# + end #=for=# + return L[IndexMax]; end #=function=# function HSNum_Bayer_Stillman(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}) -##println("HSNum_BS: Simple: $(SimplePPs)"); -##println("HSNum_BS: NonSimple: $(NonSimplePPs)"); - # Maybe sort the gens??? - if isempty(NonSimplePPs) #=then=# return HSNum_base_SimplePowers(SimplePPs, T); end #=if=# - if length(NonSimplePPs) == 1 #=then=# return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T); end #=if=# - # NonSimplePPs contains at least 2 elements -# BSPivot = last(NonSimplePPs); # pick one somehow -- this is just simple to impl -#?? BSPivot = RevLexMin(NonSimplePPs); # VERY SLOW on Hilbert-test-rnd6.jl - BSPivot = RevLexMax(NonSimplePPs); -#VERY SLOW?!? BSPivot = RevLexMax(vcat(SimplePPs,NonSimplePPs)); # VERY SLOW on Hilbert-test-rnd6.jl -println("BSPivot = $(BSPivot)"); - NonSPP = filter((t -> (t != BSPivot)), NonSimplePPs); - SPP = SimplePPs;#filter((t -> (t != BSPivot)), SimplePPs); - part1 = HSNum_loop(SPP, NonSPP, T, :bayer_stillman); - ReducedPPs = interreduce([colon(t,BSPivot) for t in vcat(SPP,NonSPP)]); - NewSimplePPs, NewNonSimplePPs = SeparateSimplePPs(ReducedPPs); - part2 = HSNum_loop(NewSimplePPs, NewNonSimplePPs, T, :bayer_stillman); - e = BSPivot.expv; - return part1 - prod([T[k]^e[k] for k in 1:length(e)])*part2; + ##println("HSNum_BS: Simple: $(SimplePPs)"); + ##println("HSNum_BS: NonSimple: $(NonSimplePPs)"); + # Maybe sort the gens??? + if isempty(NonSimplePPs) #=then=# return HSNum_base_SimplePowers(SimplePPs, T); end #=if=# + if length(NonSimplePPs) == 1 #=then=# return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T); end #=if=# + # NonSimplePPs contains at least 2 elements + # BSPivot = last(NonSimplePPs); # pick one somehow -- this is just simple to impl + #?? BSPivot = RevLexMin(NonSimplePPs); # VERY SLOW on Hilbert-test-rnd6.jl + BSPivot = RevLexMax(NonSimplePPs); + #VERY SLOW?!? BSPivot = RevLexMax(vcat(SimplePPs,NonSimplePPs)); # VERY SLOW on Hilbert-test-rnd6.jl + println("BSPivot = $(BSPivot)"); + NonSPP = filter((t -> (t != BSPivot)), NonSimplePPs); + SPP = SimplePPs;#filter((t -> (t != BSPivot)), SimplePPs); + part1 = HSNum_loop(SPP, NonSPP, T, :bayer_stillman); + ReducedPPs = interreduce([colon(t,BSPivot) for t in vcat(SPP,NonSPP)]); + NewSimplePPs, NewNonSimplePPs = SeparateSimplePPs(ReducedPPs); + part2 = HSNum_loop(NewSimplePPs, NewNonSimplePPs, T, :bayer_stillman); + e = BSPivot.expv; + return part1 - prod([T[k]^e[k] for k in 1:length(e)])*part2; end #=function=# #-------------------------------------------- @@ -521,511 +522,463 @@ end #=function=# # the greatest number of PPs in gens. # (part 2) corr freq function HSNum_most_freq_indets(gens::Vector{PP}) - # ASSUMES: gens is non-empty - nvars = length(gens[1].expv); - freq = [0 for i in 1:nvars]; # Vector{Int} or Vector{UInt} ??? - for t in gens #do - e = t.expv; - for i in 1:nvars #do - @inbounds if (e[i] != 0) #then - @inbounds freq[i] += 1 - end #if - end #for + # ASSUMES: gens is non-empty + nvars = length(gens[1].expv); + freq = [0 for i in 1:nvars]; # Vector{Int} or Vector{UInt} ??? + for t in gens #do + e = t.expv; + for i in 1:nvars #do + @inbounds if (e[i] != 0) #then + @inbounds freq[i] += 1 + end #if end #for - MaxFreq = maximum(freq); -### if MaxFreq == 1 #=then=# println("MaxFreq = 1"); end #=if=# - MostFreq = findall((x -> x==MaxFreq), freq); - return MostFreq, MaxFreq; + end #for + MaxFreq = maximum(freq); + MostFreq = findall((x -> x==MaxFreq), freq); + return MostFreq, MaxFreq; end #=function=# # Returns index of the indet function HSNum_most_freq_indet1(gens::Vector{PP}) - # ASSUMES: gens is non-empty - MostFreq,_ = HSNum_most_freq_indets(gens); - return MostFreq[1]; + # ASSUMES: gens is non-empty + MostFreq,_ = HSNum_most_freq_indets(gens); + return MostFreq[1]; end #=function=# # Returns index of the indet function HSNum_most_freq_indet_rnd(gens::Vector{PP}) - # ASSUMES: gens is non-empty - MostFreq,_ = HSNum_most_freq_indets(gens); - return rand(MostFreq); + # ASSUMES: gens is non-empty + MostFreq,_ = HSNum_most_freq_indets(gens); + return rand(MostFreq); end #=function=# function HSNum_choose_pivot_indet(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); ##HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 - nvars = length(gens[1].expv); - PivotExpv = [0 for _ in 1:nvars]; - PivotExpv[PivotIndet] = 1; - PivotPP = PP(PivotExpv); + PivotIndet = rand(MostFreq); ##HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 + nvars = length(gens[1].expv); + PivotExpv = [0 for _ in 1:nvars]; + PivotExpv[PivotIndet] = 1; + PivotPP = PP(PivotExpv); end #function function HSNum_choose_pivot_simple_power_median(MostFreq::Vector{Int64}, gens::Vector{PP}) - # simple-power-pivot from Bigatti JPAA, 1997 - PivotIndet = rand(MostFreq); ##HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 - exps = [t.expv[PivotIndet] for t in gens]; - exps = filter((e -> e>0), exps); - sort!(exps); - exp = exps[div(1+length(exps),2)]; # "median" - nvars = length(gens[1].expv); - PivotExpv = [0 for _ in 1:nvars]; - PivotExpv[PivotIndet] = exp; - PivotPP = PP(PivotExpv); + # simple-power-pivot from Bigatti JPAA, 1997 + PivotIndet = rand(MostFreq); + exps = [t.expv[PivotIndet] for t in gens]; + exps = filter((e -> e>0), exps); + sort!(exps); + exp = exps[div(1+length(exps),2)]; # "median" + nvars = length(gens[1].expv); + PivotExpv = [0 for _ in 1:nvars]; + PivotExpv[PivotIndet] = exp; + PivotPP = PP(PivotExpv); end #function function HSNum_choose_pivot_simple_power_max(MostFreq::Vector{Int64}, gens::Vector{PP}) - # simple-power-pivot from Bigatti JPAA, 1997 - PivotIndet = rand(MostFreq); ##HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 - exps = [t.expv[PivotIndet] for t in gens]; - exp = max(exps...); - nvars = length(gens[1].expv); - PivotExpv = [0 for _ in 1:nvars]; - PivotExpv[PivotIndet] = exp; - PivotPP = PP(PivotExpv); + # simple-power-pivot from Bigatti JPAA, 1997 + PivotIndet = rand(MostFreq); + exps = [t.expv[PivotIndet] for t in gens]; + exp = max(exps...); + nvars = length(gens[1].expv); + PivotExpv = [0 for _ in 1:nvars]; + PivotExpv[PivotIndet] = exp; + PivotPP = PP(PivotExpv); end #function function HSNum_choose_pivot_gcd2simple(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 - cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# - println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); - return cand[1]; # can happen only if there is a "simple splitting case" - end #=if=# - nvars = length(gens[1].expv); - expv = [0 for _ in 1:nvars]; - expv[PivotIndet] = min(cand[1].expv[PivotIndet], cand[2].expv[PivotIndet]); - return PP(expv); + PivotIndet = rand(MostFreq); + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! + end #=if=# + nvars = length(gens[1].expv); + expv = [0 for _ in 1:nvars]; + expv[PivotIndet] = min(cand[1].expv[PivotIndet], cand[2].expv[PivotIndet]); + return PP(expv); end #function function HSNum_choose_pivot_gcd2max(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 - cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# - println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); - return cand[1]; # can happen only if there is a "simple splitting case" + PivotIndet = rand(MostFreq); + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! + end #=if=# + pick2 = [cand[k] for k in random_subset(length(cand),2)]; + t = gcd(pick2[1], pick2[2]); + d = 0; for i in 1:length(t.expv) #=do=# d = max(d,t.expv[i]); end #=for=# + for i in 1:length(t.expv) #=do=# + if t.expv[i] < d #=then=# + t.expv[i]=0; end #=if=# - pick2 = [cand[k] for k in random_subset(length(cand),2)]; - t = gcd(pick2[1], pick2[2]); -# println("BEFORE: t= $(t)"); - d = 0; for i in 1:length(t.expv) #=do=# d = max(d,t.expv[i]); end #=for=# - for i in 1:length(t.expv) #=do=# if t.expv[i] < d #=then=# t.expv[i]=0; end #=if=# end #=for=# -# println("AFTER: t= $(t)"); - return t; + end #=for=# + return t; end #function # May produce a non-simple pivot!!! function HSNum_choose_pivot_gcd3(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 - cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# - println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); - return cand[1]; # can happen only if there is a "simple splitting case" - end #=if=# - if length(cand) == 2 #=then=# - return gcd(cand[1], cand[2]); - end #=if=# - pick3 = [cand[k] for k in random_subset(length(cand),3)]; - return gcd3(pick3[1], pick3[2], pick3[3]); + PivotIndet = rand(MostFreq); + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! + end #=if=# + if length(cand) == 2 #=then=# + return gcd(cand[1], cand[2]); + end #=if=# + pick3 = [cand[k] for k in random_subset(length(cand),3)]; + return gcd3(pick3[1], pick3[2], pick3[3]); end #function function HSNum_choose_pivot_gcd3simple(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 - cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# - println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); - return cand[1]; # can happen only if there is a "simple splitting case" - end #=if=# - if length(cand) == 2 #=then=# - t = gcd(cand[1], cand[2]); - else + PivotIndet = rand(MostFreq); + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! + end #=if=# + if length(cand) == 2 #=then=# + t = gcd(cand[1], cand[2]); + else pick3 = [cand[k] for k in random_subset(length(cand),3)]; t = gcd3(pick3[1], pick3[2], pick3[3]); + end #=if=# + # println("BEFORE: t= $(t)"); + j = 1; + d = t.expv[1]; + for i in 2:length(t.expv) #=do=# + if t.expv[i] <= d #=then=# + t.expv[i] = 0; + continue; end #=if=# -# println("BEFORE: t= $(t)"); - j = 1; - d = t.expv[1]; - for i in 2:length(t.expv) #=do=# - if t.expv[i] <= d #=then=# - t.expv[i] = 0; - continue; - end #=if=# - t.expv[j] = 0; - j=i; - d = t.expv[i]; - end #=for=# -# println("AFTER: t= $(t)"); - return t; + t.expv[j] = 0; + j=i; + d = t.expv[i]; + end #=for=# + # println("AFTER: t= $(t)"); + return t; end #function function HSNum_choose_pivot_gcd3max(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 - cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# - println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); - return cand[1]; # can happen only if there is a "simple splitting case" - end #=if=# - if length(cand) == 2 #=then=# - t = gcd(cand[1], cand[2]); - else + PivotIndet = rand(MostFreq); + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! + end #=if=# + if length(cand) == 2 #=then=# + t = gcd(cand[1], cand[2]); + else pick3 = [cand[k] for k in random_subset(length(cand),3)]; t = gcd3(pick3[1], pick3[2], pick3[3]); + end #=if=# + d = 0; for i in 1:length(t.expv) #=do=# d = max(d,t.expv[i]); end #=for=# + for i in 1:length(t.expv) #=do=# + if t.expv[i] < d #=then=# + t.expv[i]=0; end #=if=# -# println("BEFORE: t= $(t)"); - d = 0; for i in 1:length(t.expv) #=do=# d = max(d,t.expv[i]); end #=for=# - for i in 1:length(t.expv) #=do=# if t.expv[i] < d #=then=# t.expv[i]=0; end #=if=# end #=for=# -# println("AFTER: t= $(t)"); - return t; + end #=for=# + return t; end #function # May produce a non-simple pivot!!! function HSNum_choose_pivot_gcd4(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); ##???HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 - cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# - println(">>>>>> SHOULD NEVER HAPPEN <<<<<<"); error("!OUCH!"); - return cand[1]; # can happen only if there is a "simple splitting case" - end #=if=# - if length(cand) == 2 #=then=# - return gcd(cand[1], cand[2]); - end #=if=# - if length(cand) ==3 #=then=# - return gcd3(cand[1], cand[2], cand[3]); - end #=if=# - pick4 = [cand[k] for k in random_subset(length(cand),4)]; - return gcd(gcd(pick4[1], pick4[2]), gcd(pick4[3],pick4[4])); + PivotIndet = rand(MostFreq); + cand = filter((t -> t.expv[PivotIndet]>0), gens); + if length(cand) == 1 #=then=# + error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! + end #=if=# + if length(cand) == 2 #=then=# + return gcd(cand[1], cand[2]); + end #=if=# + if length(cand) ==3 #=then=# + return gcd3(cand[1], cand[2], cand[3]); + end #=if=# + pick4 = [cand[k] for k in random_subset(length(cand),4)]; + return gcd(gcd(pick4[1], pick4[2]), gcd(pick4[3],pick4[4])); end #function # Assume SimplePPs+NonSimplePPs are interreduced function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) -# println("LOOP: SimplePPs=$(SimplePPs)"); -# println("LOOP: first <=5 NonSimplePPs=$(first(NonSimplePPs,5))"); -# CHECK=vcat(SimplePPs,NonSimplePPs);if length(CHECK) != length(interreduce(CHECK)) #=then=# println("LOOP: !!!BAD INPUT!!!"); error("BANG!"); end #=if=# - # Check if we have base case 0 - if isempty(NonSimplePPs) #then - # println("LOOP: END OF CALL --> delegate base case 0"); - return HSNum_base_SimplePowers(SimplePPs, T); - end #if - # Check if we have base case 1 - if length(NonSimplePPs) == 1 #then - # println("LOOP: END OF CALL --> delegate base case 1"); - return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T); - end #if - # ---------------------- - MostFreq,freq = HSNum_most_freq_indets(NonSimplePPs); - if freq == 1 #=then=# - # total splitting case -#print("+"); - return HSNum_total_splitting_case(MostFreq, SimplePPs, NonSimplePPs, T, PivotStrategy); - end #=if=# - - if PivotStrategy == :bayer_stillman #=then=# - return HSNum_Bayer_Stillman(SimplePPs, NonSimplePPs, T); - end #=if=# - # Check for "splitting case" -if #=length(NonSimplePPs[1].expv) > 4 &&=# length(NonSimplePPs) <= length(NonSimplePPs[1].expv)#=nvars=# #then + @vprint :hilbert 1 "HSNum_loop: SimplePPs=$(SimplePPs)"; + @vprint :hilbert 1 "HSNum_loop: NonSimplePPs=$(NonSimplePPs)"; +# @vprint :hilbert 1 "LOOP: first <=5 NonSimplePPs=$(first(NonSimplePPs,5))"; + # Check if we have base case 0 + if isempty(NonSimplePPs) #then + @vprint :hilbert 1 "HSNum_loop: --> delegate base case 0"); + return HSNum_base_SimplePowers(SimplePPs, T); + end #if + # Check if we have base case 1 + if length(NonSimplePPs) == 1 #then + @vprint :hilbert 1 "HSNum_loop: --> delegate base case 1"); + return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T); + end #if + # ---------------------- + MostFreq,freq = HSNum_most_freq_indets(NonSimplePPs); + if freq == 1 #=then=# + return HSNum_total_splitting_case(MostFreq, SimplePPs, NonSimplePPs, T, PivotStrategy); + end #=if=# + + if PivotStrategy == :bayer_stillman #=then=# + return HSNum_Bayer_Stillman(SimplePPs, NonSimplePPs, T); + end #=if=# + # Check for "splitting case" + if length(NonSimplePPs) <= length(NonSimplePPs[1].expv)#=nvars=# #=then=# CC = ConnectedComponents(NonSimplePPs); -else CC = [] - end #if - if #=false &&=# length(CC) > 1 #then -### print("*");#println("LOOP: END OF CALL --> delegate SPLITTING CASE $(CC)"); - return HSNum_splitting_case(CC, SimplePPs, NonSimplePPs, T, PivotStrategy); - end #if (splitting case) - # ---------------------- - # Pivot case: first do the ideal sum, then do ideal quotient - # (the ideas are relatively simple, the code is long, tedious, and a bit fiddly) -# # REDUCE BIG EXPS -- THIS IDEA SEEMS TO WORK DISAPPONTINGLY ON THE RND TEST EXAMPLES -# nvars = length(NonSimplePPs[1].expv); -# TopPP = PP([0 for _ in 1:nvars]); -# for t in NonSimplePPs #=do=# TopPP = lcm(TopPP, t); end #=for=# -# DegBound = 9; BigExp = max(TopPP.expv...); -# if false && BigExp > DegBound #=then=# -# expv = [0 for _ in 1:nvars]; -# for i in 1:nvars #=do=# if TopPP.expv[i] == BigExp #=then=# expv[i] = div(1+TopPP.expv[i],2); break; end #=if=# -# ##NO for i in 1:nvars #=do=# if TopPP.expv[i] > 9 #=then=# expv[i] = div(1+TopPP.expv[i],2); #=break;=# end #=if=# -# end #=for=# -# PivotPP = PP(expv); -# #println("BIG pivot $(PivotPP)"); -# else -# PivotPP = HSNum_choose_pivot_gcd2simple(MostFreq, NonSimplePPs) - if PivotStrategy == :indet - PivotPP = HSNum_choose_pivot_indet(MostFreq, NonSimplePPs) -end #=if=# - if PivotStrategy == :simple_power_max - PivotPP = HSNum_choose_pivot_simple_power_max(MostFreq, NonSimplePPs) -end #=if=# - if PivotStrategy == :gcd2simple - PivotPP = HSNum_choose_pivot_gcd2simple(MostFreq, NonSimplePPs) -end #=if=# - if PivotStrategy == :gcd2max - PivotPP = HSNum_choose_pivot_gcd2max(MostFreq, NonSimplePPs) -end #=if=# - if PivotStrategy == :gcd3 - PivotPP = HSNum_choose_pivot_gcd3(MostFreq, NonSimplePPs) -end #=if=# - if PivotStrategy == :gcd3simple - PivotPP = HSNum_choose_pivot_gcd3simple(MostFreq, NonSimplePPs) -end #=if=# - if PivotStrategy == :gcd3max - PivotPP = HSNum_choose_pivot_gcd3max(MostFreq, NonSimplePPs) -end #=if=# - if PivotStrategy == :gcd4 - PivotPP = HSNum_choose_pivot_gcd4(MostFreq, NonSimplePPs) -end #=if=# - if PivotStrategy == :simple_power_median || PivotStrategy == :auto - PivotPP = HSNum_choose_pivot_simple_power_median(MostFreq, NonSimplePPs) - end #=if=# -# end #=if=# -##=DEVELOP/DEBUG=# if (degree(PivotPP) > 3 && !IsSimplePowerPP(PivotPP)) println(">>>>>PIVOT<<<<< $(PivotPP)"); end; - PivotIsSimple = IsSimplePowerPP(PivotPP); - PivotIndex = findfirst((e -> e>0), PivotPP.expv); # used only if PivotIsSimple == true - USE_SAFE_VERSION_SUM=false; - if USE_SAFE_VERSION_SUM #=then=# - # Safe but slow version: just add new gen, then interreduce - RecurseSum = vcat(SimplePPs, NonSimplePPs); - push!(RecurseSum, PivotPP); - RecurseSum = interreduce(RecurseSum); - RecurseSum_SimplePPs_OLD, RecurseSum_NonSimplePPs_OLD = SeparateSimplePPs(RecurseSum); + else CC = [] + end #if + if length(CC) > 1 #then + @vprint :hilbert 1 "HSNum_loop: --> delegate Splitting case"); + return HSNum_splitting_case(CC, SimplePPs, NonSimplePPs, T, PivotStrategy); + end #if (splitting case) + # ---------------------- + # Pivot case: first do the ideal sum, then do ideal quotient + # (the ideas are relatively simple, the code is long, tedious, and a bit fiddly) + if PivotStrategy == :indet + PivotPP = HSNum_choose_pivot_indet(MostFreq, NonSimplePPs) + end #=if=# + if PivotStrategy == :simple_power_max + PivotPP = HSNum_choose_pivot_simple_power_max(MostFreq, NonSimplePPs) + end #=if=# + if PivotStrategy == :gcd2simple + PivotPP = HSNum_choose_pivot_gcd2simple(MostFreq, NonSimplePPs) + end #=if=# + if PivotStrategy == :gcd2max + PivotPP = HSNum_choose_pivot_gcd2max(MostFreq, NonSimplePPs) + end #=if=# + if PivotStrategy == :gcd3 + PivotPP = HSNum_choose_pivot_gcd3(MostFreq, NonSimplePPs) + end #=if=# + if PivotStrategy == :gcd3simple + PivotPP = HSNum_choose_pivot_gcd3simple(MostFreq, NonSimplePPs) + end #=if=# + if PivotStrategy == :gcd3max + PivotPP = HSNum_choose_pivot_gcd3max(MostFreq, NonSimplePPs) + end #=if=# + if PivotStrategy == :gcd4 + PivotPP = HSNum_choose_pivot_gcd4(MostFreq, NonSimplePPs) + end #=if=# + if PivotStrategy == :simple_power_median || PivotStrategy == :auto + PivotPP = HSNum_choose_pivot_simple_power_median(MostFreq, NonSimplePPs) + end #=if=# + # end #=if=# + @vprint :hilbert 1 "HSNum_loop: pivot = $(PivotPP)"; + PivotIsSimple = IsSimplePowerPP(PivotPP); + PivotIndex = findfirst((e -> e>0), PivotPP.expv); # used only if PivotIsSimple == true + USE_SAFE_VERSION_SUM=false; + if USE_SAFE_VERSION_SUM #=then=# + # Safe but slow version: just add new gen, then interreduce + RecurseSum = vcat(SimplePPs, NonSimplePPs); + push!(RecurseSum, PivotPP); + RecurseSum = interreduce(RecurseSum); + RecurseSum_SimplePPs_OLD, RecurseSum_NonSimplePPs_OLD = SeparateSimplePPs(RecurseSum); RecurseSum_NonSimplePPs = RecurseSum_NonSimplePPs_OLD; RecurseSum_SimplePPs = RecurseSum_SimplePPs_OLD; - else # use "clever" version: - # We know that SimplePPs + NonSimplePPs are already interreduced - if PivotIsSimple #=then=# - RecurseSum_SimplePPs_NEW = copy(SimplePPs); - k = findfirst((t -> t.expv[PivotIndex] > 0), SimplePPs); - if k === nothing - push!(RecurseSum_SimplePPs_NEW, PivotPP); - else - RecurseSum_SimplePPs_NEW[k] = PivotPP; - end #if - RecurseSum_NonSimplePPs_NEW = filter((t -> t.expv[PivotIndex] < PivotPP.expv[PivotIndex]), NonSimplePPs); - else # PivotPP is not simple -- so this is the "general case" - RecurseSum_SimplePPs_NEW = copy(SimplePPs); # need to copy? - RecurseSum_NonSimplePPs_NEW = filter((t -> !IsDivisible(t,PivotPP)), NonSimplePPs); - push!(RecurseSum_NonSimplePPs_NEW, PivotPP); - end #=if=# + else # use "clever" version: + # We know that SimplePPs + NonSimplePPs are already interreduced + if PivotIsSimple #=then=# + RecurseSum_SimplePPs_NEW = copy(SimplePPs); + k = findfirst((t -> t.expv[PivotIndex] > 0), SimplePPs); + if k === nothing + push!(RecurseSum_SimplePPs_NEW, PivotPP); + else + RecurseSum_SimplePPs_NEW[k] = PivotPP; + end #if + RecurseSum_NonSimplePPs_NEW = filter((t -> t.expv[PivotIndex] < PivotPP.expv[PivotIndex]), NonSimplePPs); + else # PivotPP is not simple -- so this is the "general case" + RecurseSum_SimplePPs_NEW = copy(SimplePPs); # need to copy? + RecurseSum_NonSimplePPs_NEW = filter((t -> !IsDivisible(t,PivotPP)), NonSimplePPs); + push!(RecurseSum_NonSimplePPs_NEW, PivotPP); + end #=if=# RecurseSum_NonSimplePPs = RecurseSum_NonSimplePPs_NEW; RecurseSum_SimplePPs = RecurseSum_SimplePPs_NEW; - end #=if=# # USE_SAFE_VERSION_SUM - # println(" SUM recurses on:"); - # println(" SimplePPs = $(RecurseSum_SimplePPs)"); - # println(" NonSimplePPs = $(RecurseSum_NonSimplePPs)"); - - # Check that both approaches gave equivalent answers (well, not obviously inequivalent) - # # if length(RecurseSum_NonSimplePPs_NEW) != length(RecurseSum_NonSimplePPs_OLD) || length(RecurseSum_SimplePPs_NEW) != length(RecurseSum_SimplePPs_OLD) #=then=# - # # println("!!!DIFFER!!!"); - # # println("PivotPP = $(PivotPP)"); - # # println("Simple_NEW = $(RecurseSum_SimplePPs_NEW)"); - # # println("Simple_OLD = $(RecurseSum_SimplePPs_OLD)"); - # # println("NonSimple_NEW = $(RecurseSum_NonSimplePPs_NEW)"); - # # println("NonSimple_OLD = $(RecurseSum_NonSimplePPs_OLD)"); - # # end #=if=# - - # Now do the quotient... - # Now get SimplePPs & NonSimplePPs for the quotient while limiting amount of interreduction - USE_SAFE_VERSION_QUOT = false; - if USE_SAFE_VERSION_QUOT #=then=# - #=SAFE VERSION: simpler but probably slower=# - RecurseQuot = [colon(t,PivotPP) for t in vcat(SimplePPs,NonSimplePPs)]; - RecurseQuot = interreduce(RecurseQuot); - RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs = SeparateSimplePPs(RecurseQuot); - else # Use "smart version" - if !PivotIsSimple #then # GENERAL CASE (e.g. if not PivotIsSimple) - # Clever approach (for non-simple pivots) taken from Bigatti 1997 paper (p.247 just after Prop 1 to end of sect 5) - # println("QUOT: NON-SIMPLE PIVOT $(PivotPP)"); - # println(" init SimplePPs = $(SimplePPs)"); - # println(" init NonSimplePPs = $(NonSimplePPs)"); - BM = PP[]; NotBM = PP[]; - PivotPlus = mult(PivotPP, radical(PivotPP)); - for t in NonSimplePPs #=do=# if IsDivisible(t,PivotPlus) #=then=# push!(BM,t); else push!(NotBM, t); end #=if=# end #=for=# - ## println("PivotPP is $(PivotPP)"); - ## println("PivotPlus is $(PivotPlus)"); - # println(" NotBM is $(NotBM)"); - BM = [divide(t,PivotPP) for t in BM]; # divide is same as colon here - NotBM = vcat(NotBM, SimplePPs); - NotBM_mixed = PP[]; NotBM_coprime = PP[]; - for t in NotBM #=do=# if is_coprime(t,PivotPP) #=then=# push!(NotBM_coprime,t); else push!(NotBM_mixed, colon(t,PivotPP)); end #=if=# end #=for=# - # At this poiint we have 3 disjoint lists of PPs: BM (big multiples), NotBM_coprime, NotBM_mixed - # In all cases the PPs have been colon-ed by PivotPP - # println(" NotBM_mixed = $(NotBM_mixed)"); - # println(" NotBM_coprime = $(NotBM_coprime)"); - NotBM_mixed = interreduce(NotBM_mixed); # cannot easily be "clever" here - # println(" NotBM_mixed INTERRED = $(NotBM_mixed)"); - filter!((t -> NotMultOf(NotBM_mixed,t)), NotBM_coprime); - # println(" NotBM_coprime FILTERED is $(NotBM_coprime)"); - ## if !isempty(BM) println("BM is $(BM)"); else print("*"); end - RecurseQuot = vcat(NotBM_coprime, NotBM_mixed); # already interreduced -##DEBUGGING if length(RecurseQuot) != length(interreduce(RecurseQuot)) #=then=# println(">>>>>>DIFFER<<<<<< RQ=$(length(RecurseQuot)) interr=$(length(interreduce(RecurseQuot)))"); end #=if=# - RQ_SimplePPs, RQ_NonSimplePPs = SeparateSimplePPs(RecurseQuot); - RecurseQuot_SimplePPs = RQ_SimplePPs; ###???interreduce(vcat(RQ_SimplePPs, [colon(t,PivotPP) for t in SimplePPs])); - RecurseQuot_NonSimplePPs = vcat(BM, RQ_NonSimplePPs); - ### RecurseQuot = vcat(NotBM, [colon(t,PivotPP) for t in SimplePPs]); RecurseQuot = interreduce(RecurseQuot); RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs = SeparateSimplePPs(RecurseQuot); RecurseQuot_NonSimplePPs = vcat(RecurseQuot_NonSimplePPs, BM); # Be cleverer about interreduced - # println("QUOT FINAL: PivotPP=$(PivotPP)"); - # println("QUOT FINAL: RecurseQuot_NonSimplePPs=$(RecurseQuot_NonSimplePPs)"); - # println("QUOT FINAL: RecurseQuot_SimplePPs=$(RecurseQuot_SimplePPs)"); -##CHECK=vcat(RecurseQuot_SimplePPs,RecurseQuot_NonSimplePPs);if length(CHECK) != length(interreduce(CHECK)) #=then=# println("LOOP: QUOT(S) FINAL !!!BAD OUTPUT!!!"); error("BANG!"); end #=if=# - else # Clever approach when PivotIsSimple - # println("QUOT: SIMPLE PIVOT $(PivotPP)"); - # println(" init SimplePPs = $(SimplePPs)"); - # println(" init NonSimplePPs = $(NonSimplePPs)"); - # The idea behind this code is fairly simple; sadly the code itself is not :-( - RecurseQuot_SimplePPs = copy(SimplePPs); - k = findfirst((t -> t.expv[PivotIndex] > 0), RecurseQuot_SimplePPs); - if !(k === nothing) - RecurseQuot_SimplePPs[k] = copy(RecurseQuot_SimplePPs[k]); #???copy needed??? - RecurseQuot_SimplePPs[k].expv[PivotIndex] -= PivotPP.expv[PivotIndex]; - end #if - DegPivot = degree(PivotPP); - NonSimpleTbl = [PP[] for _ in 0:DegPivot]; ## WARNING: indexes are offset by 1 -- thanks Julia! - NonSimple1 = PP[]; # will contain all PPs divisible by PivotPP^(1+epsilon) - for t in NonSimplePPs #=do=# - degt = t.expv[PivotIndex]; - if degt > DegPivot #=then=# - push!(NonSimple1, divide(t, PivotPP)); - else - push!(NonSimpleTbl[degt+1], colon(t,PivotPP)); - end #=if=# - end #=for=# - ## println("PivotPP = $(PivotPP)"); - # println("(Quot simple) TBL: $(NonSimpleTbl)"); - NonSimple2 = NonSimpleTbl[DegPivot+1]; - for i in DegPivot:-1:1 #=do=# - NewPPs = filter((t -> NotMultOf(NonSimple2,t)), NonSimpleTbl[i]); - NonSimple2 = vcat(NonSimple2, NewPPs); - end #=for=# - ## println("After interred: NonSimple1=$(NonSimple1)"); - ## println("After interred: NonSimple2=$(NonSimple2)"); - NewSimplePPs = filter(IsSimplePowerPP, NonSimple2); - NonSimple2 = filter(!IsSimplePowerPP, NonSimple2); ## SeparateSimplePPs??? - if !isempty(NewSimplePPs) #=then=# - RecurseQuot_SimplePPs = interreduce(vcat(RecurseQuot_SimplePPs, NewSimplePPs)); - end #=if=# - RecurseQuot_NonSimplePPs = vcat(NonSimple1, NonSimple2); - # println("QUOT(S) FINAL: PivotPP=$(PivotPP)"); - # println("QUOT(S) FINAL: RecurseQuot_NonSimplePPs=$(RecurseQuot_NonSimplePPs)"); - # println("QUOT(S) FINAL: RecurseQuot_SimplePPs=$(RecurseQuot_SimplePPs)"); -##CHECK=vcat(RecurseQuot_SimplePPs,RecurseQuot_NonSimplePPs);if length(CHECK) != length(interreduce(CHECK)) #=then=# println("LOOP: QUOT(S) FINAL !!!BAD OUTPUT!!!"); error("BANG!"); end #=if=# - end #=if=# #PivotIsSimple - end #=if=# # end of USE_SAFE_VERSION_QUOT - # Now put the two pieces together: - nvars = length(PivotPP.expv); - scale = prod([T[k]^PivotPP.expv[k] for k in 1:nvars]); - # println("RECURSION:"); - # println(" SUM recursion: simple $(RecurseSum_SimplePPs)"); - # println(" SUM recursion: nonsimple $(RecurseSum_NonSimplePPs)"); - # println(" QUOT recursion: simple $(RecurseQuot_SimplePPs)"); - # println(" QUOT recursion: nonsimple $(RecurseQuot_NonSimplePPs)"); - HSNum_sum = HSNum_loop(RecurseSum_SimplePPs, RecurseSum_NonSimplePPs, T, PivotStrategy); - # println("RECURSION: after HSNum_sum"); - # println(" SUM recursion: simple $(RecurseSum_SimplePPs)"); - # println(" SUM recursion: nonsimple $(RecurseSum_NonSimplePPs)"); - # println(" QUOT recursion: simple $(RecurseQuot_SimplePPs)"); - # println(" QUOT recursion: nonsimple $(RecurseQuot_NonSimplePPs)"); - HSNum_quot = HSNum_loop(RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs, T, PivotStrategy); - # println("LOOP: END OF CALL"); - return HSNum_sum + scale*HSNum_quot; + end #=if=# # USE_SAFE_VERSION_SUM + + # Now do the quotient... + # Now get SimplePPs & NonSimplePPs for the quotient while limiting amount of interreduction + USE_SAFE_VERSION_QUOT = false; + if USE_SAFE_VERSION_QUOT #=then=# + #=SAFE VERSION: simpler but probably slower=# + RecurseQuot = [colon(t,PivotPP) for t in vcat(SimplePPs,NonSimplePPs)]; + RecurseQuot = interreduce(RecurseQuot); + RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs = SeparateSimplePPs(RecurseQuot); + else # Use "smart version" + if !PivotIsSimple #then # GENERAL CASE (e.g. if not PivotIsSimple) + # Clever approach (for non-simple pivots) taken from Bigatti 1997 paper (p.247 just after Prop 1 to end of sect 5) + # ???Maybe use filter2 (see start of this file) instead of loop below??? + BM = PP[]; NotBM = PP[]; + PivotPlus = mult(PivotPP, radical(PivotPP)); + for t in NonSimplePPs #=do=# + if IsDivisible(t,PivotPlus) #=then=# + push!(BM,t); + else + push!(NotBM,t); + end #=if=# + end #=for=# + BM = [divide(t,PivotPP) for t in BM]; # divide is same as colon here + NotBM = vcat(NotBM, SimplePPs); + NotBM_mixed = PP[]; NotBM_coprime = PP[]; + for t in NotBM #=do=# + if is_coprime(t,PivotPP) #=then=# + push!(NotBM_coprime,t); + else + push!(NotBM_mixed, colon(t,PivotPP)); + end #=if=# + end #=for=# + # At this poiint we have 3 disjoint lists of PPs: BM (big multiples), NotBM_coprime, NotBM_mixed + # In all cases the PPs have been colon-ed by PivotPP + NotBM_mixed = interreduce(NotBM_mixed); # cannot easily be "clever" here + filter!((t -> NotMultOf(NotBM_mixed,t)), NotBM_coprime); + RecurseQuot = vcat(NotBM_coprime, NotBM_mixed); # already interreduced + RQ_SimplePPs, RQ_NonSimplePPs = SeparateSimplePPs(RecurseQuot); + RecurseQuot_SimplePPs = RQ_SimplePPs; + RecurseQuot_NonSimplePPs = vcat(BM, RQ_NonSimplePPs); + else # Clever approach when PivotIsSimple + # The idea behind this code is fairly simple; sadly the code itself is not :-( + RecurseQuot_SimplePPs = copy(SimplePPs); + k = findfirst((t -> t.expv[PivotIndex] > 0), RecurseQuot_SimplePPs); + if !(k === nothing) + RecurseQuot_SimplePPs[k] = copy(RecurseQuot_SimplePPs[k]); + RecurseQuot_SimplePPs[k].expv[PivotIndex] -= PivotPP.expv[PivotIndex]; + end #if + DegPivot = degree(PivotPP); + NonSimpleTbl = [PP[] for _ in 0:DegPivot]; ## WARNING: indexes are offset by 1 -- thanks Julia! + NonSimple1 = PP[]; # will contain all PPs divisible by PivotPP^(1+epsilon) + for t in NonSimplePPs #=do=# + degt = t.expv[PivotIndex]; + if degt > DegPivot #=then=# + push!(NonSimple1, divide(t, PivotPP)); + else + push!(NonSimpleTbl[degt+1], colon(t,PivotPP)); + end #=if=# + end #=for=# + NonSimple2 = NonSimpleTbl[DegPivot+1]; + for i in DegPivot:-1:1 #=do=# + NewPPs = filter((t -> NotMultOf(NonSimple2,t)), NonSimpleTbl[i]); + NonSimple2 = vcat(NonSimple2, NewPPs); + end #=for=# + NewSimplePPs = filter(IsSimplePowerPP, NonSimple2); + NonSimple2 = filter(!IsSimplePowerPP, NonSimple2); ## SeparateSimplePPs??? + if !isempty(NewSimplePPs) #=then=# + RecurseQuot_SimplePPs = interreduce(vcat(RecurseQuot_SimplePPs, NewSimplePPs)); + end #=if=# + RecurseQuot_NonSimplePPs = vcat(NonSimple1, NonSimple2); + end #=if=# #PivotIsSimple + end #=if=# # end of USE_SAFE_VERSION_QUOT + # Now put the two pieces together: + nvars = length(PivotPP.expv); + scale = prod([T[k]^PivotPP.expv[k] for k in 1:nvars]); + @vprint :hilbert 2 "HSNum_loop: SUM recursion: simple $(RecurseSum_SimplePPs)"; + @vprint :hilbert 2 "HSNum_loop: SUM recursion: nonsimple $(RecurseSum_NonSimplePPs)"; + @vprint :hilbert 2 "HSNum_loop: QUOT recursion: simple $(RecurseQuot_SimplePPs)"; + @vprint :hilbert 2 "HSNum_loop: QUOT recursion: nonsimple $(RecurseQuot_NonSimplePPs)"; + + HSNum_sum = HSNum_loop(RecurseSum_SimplePPs, RecurseSum_NonSimplePPs, T, PivotStrategy); + HSNum_quot = HSNum_loop(RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs, T, PivotStrategy); + @vprint :hilbert 1 "HSNum_loop: END OF CALL"); + return HSNum_sum + scale*HSNum_quot; end #function - + function SeparateSimplePPs(gens::Vector{PP}) - SimplePPs::Vector{PP} = []; - NonSimplePPs::Vector{PP} = []; - for g in gens #do - if IsSimplePowerPP(g) #then - push!(SimplePPs, g) - else - push!(NonSimplePPs, g) - end #if - end #for - return SimplePPs, NonSimplePPs; + SimplePPs::Vector{PP} = []; + NonSimplePPs::Vector{PP} = []; + for g in gens #do + if IsSimplePowerPP(g) #then + push!(SimplePPs, g) + else + push!(NonSimplePPs, g) + end #if + end #for + return SimplePPs, NonSimplePPs; end #function # Check args: either throws or returns nothing. function HSNum_CheckArgs(gens::Vector{PP}, W::Vector{Vector{Int}}) - if isempty(gens) throw("HSNum: need at least 1 generator"); end #if - if isempty(W) throw("HSNum: weight matrix must have at least 1 row"); end #if - nvars = length(gens[1].expv); - if !all((t -> length(t.expv)==nvars), gens) - throw("HSNum: generators must all have same size exponent vectors"); - end #if - if !all((row -> length(row)==nvars), W) - throw("HSNum: weight matrix must have 1 column for each variable") - end #if - # Zero weights are allowed??? - # Args are OK, so simply return (without throwing) + if isempty(gens) #=then=# + throw("HSNum: need at least 1 generator"); + end #=if=# + if isempty(W) #=then=# + throw("HSNum: weight matrix must have at least 1 row"); + end #=if=# + nvars = length(gens[1].expv); + if !all((t -> length(t.expv)==nvars), gens) + throw("HSNum: generators must all have same size exponent vectors"); + end #if + if !all((row -> length(row)==nvars), W) + throw("HSNum: weight matrix must have 1 column for each variable") + end #if + # Zero weights are allowed??? + # Args are OK, so simply return (without throwing) end # function function HSNum(gens::Vector{PP}, W::Vector{Vector{Int}}, PivotStrategy::Symbol) -### println("HSNum: gens = $(gens)"); -### println("HSNum: W = $(W)"); - HSNum_CheckArgs(gens, W); - # Grading is over ZZ^m - m = size(W)[1]; # NumRows - ncols = size(W[1])[1]; # how brain-damaged is Julia??? - nvars = length(gens[1].expv); - if ncols != nvars #then - throw(ArgumentError("weights matrix has wrong number of columns ($(ncols)); should be same as number of variables ($(nvars))")); - end #if - HPRing, t = LaurentPolynomialRing(QQ, ["t$k" for k in 1:m]); - T = [one(HPRing) for k in 1:nvars]; - for k in 1:nvars #do - s = one(HPRing); - for j in 1:m #do - s *= t[j]^W[j][k]; - end #for - T[k] = s; + ### println("HSNum: gens = $(gens)"); + ### println("HSNum: W = $(W)"); + HSNum_CheckArgs(gens, W); + # Grading is over ZZ^m + m = size(W)[1]; # NumRows + ncols = size(W[1])[1]; # how brain-damaged is Julia??? + nvars = length(gens[1].expv); + if ncols != nvars #then + throw(ArgumentError("weights matrix has wrong number of columns ($(ncols)); should be same as number of variables ($(nvars))")); + end #if + HPRing, t = LaurentPolynomialRing(QQ, ["t$k" for k in 1:m]); + T = [one(HPRing) for k in 1:nvars]; + for k in 1:nvars #do + s = one(HPRing); + for j in 1:m #do + s *= t[j]^W[j][k]; end #for - # Now have T[1] = t1^W[1,1] * t2^W[2,1] * ..., etc -### println("HSNum: T vector is $(T)"); - SimplePPs,NonSimplePPs = SeparateSimplePPs(interreduce(gens)); - sort!(NonSimplePPs, lt=DegRevLexLess); # recommended by Bayer+Stillman (for their criterion) - return HSNum_loop(SimplePPs, NonSimplePPs, T, PivotStrategy); + T[k] = s; + end #for + # Now have T[1] = t1^W[1,1] * t2^W[2,1] * ..., etc + ### println("HSNum: T vector is $(T)"); + SimplePPs,NonSimplePPs = SeparateSimplePPs(interreduce(gens)); + sort!(NonSimplePPs, lt=DegRevLexLess); # recommended by Bayer+Stillman (for their criterion) + return HSNum_loop(SimplePPs, NonSimplePPs, T, PivotStrategy); end #function #----------------------------------------------------------------------------- # This fn copied from GradedModule.jl (in dir OSCAR/HILBERT/) function gen_repr(d) - grading_dim = length(gens(parent(d))); - return [getindex(d,k) for k in 1:grading_dim]; + grading_dim = length(gens(parent(d))); + return [getindex(d,k) for k in 1:grading_dim]; end #function function HSNum_fudge(PmodI::MPolyQuoRing, PivotStrategy::Symbol = :auto) -if PivotStrategy == :indet return nothing; end - I = PmodI.I; - P = base_ring(I);##parent(gens(I)[1]); # there MUST be a better way!! - nvars = length(gens(P)); - grading_dim = length(gens(parent(degree(gen(P,1))))); # better way??? - weights = [degree(var) for var in gens(P)]; - W = [[0 for _ in 1:nvars] for _ in 1:grading_dim]; - for i in 1:nvars #do - expv = [Int64(exp) for exp in gen_repr(degree(gen(P,i)))]; - for j in 1:grading_dim #do - W[j][i] = expv[j]; - end #for + if PivotStrategy == :indet return nothing; end + I = PmodI.I; + P = base_ring(I);##parent(gens(I)[1]); # there MUST be a better way!! + nvars = length(gens(P)); + grading_dim = length(gens(parent(degree(gen(P,1))))); # better way??? + weights = [degree(var) for var in gens(P)]; + W = [[0 for _ in 1:nvars] for _ in 1:grading_dim]; + for i in 1:nvars #do + expv = [Int64(exp) for exp in gen_repr(degree(gen(P,i)))]; + for j in 1:grading_dim #do + W[j][i] = expv[j]; end #for - # ## W = [[Int64(exp) for exp in gen_repr(d)] for d in weights]; - # W=[] - # for d in weights #do - # expv = [Int64(exp) for exp in gen_repr(d)]; - # if isempty(W) #then - # W = expv; - # else - # W = hcat(W, expv); - # end #if - # end #for - # W = [W[:,i] for i in 1:size(W,2)] - # # ?transpose? hcat -## println("W is $(W)"); - LTs = gens(leading_ideal(I)); - PPs = [PP(degrees(t)) for t in LTs]; - return HSNum(PPs, W, PivotStrategy); + end #for + # ## W = [[Int64(exp) for exp in gen_repr(d)] for d in weights]; + # W=[] + # for d in weights #do + # expv = [Int64(exp) for exp in gen_repr(d)]; + # if isempty(W) #then + # W = expv; + # else + # W = hcat(W, expv); + # end #if + # end #for + # W = [W[:,i] for i in 1:size(W,2)] + # # ?transpose? hcat + ## println("W is $(W)"); + LTs = gens(leading_ideal(I)); + PPs = [PP(degrees(t)) for t in LTs]; + return HSNum(PPs, W, PivotStrategy); end #function From b190fe83ba9da93eff734cccf996a651765f9a9b Mon Sep 17 00:00:00 2001 From: John Abbott Date: Mon, 7 Aug 2023 15:05:16 +0200 Subject: [PATCH 08/51] Fixed minor typos --- src/Rings/hilbert.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index 7d08b29cbaa8..6667ea277b23 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -397,7 +397,7 @@ function HSNum_base_case1(t::PP, SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T nvars = length(e); @inbounds scale = prod([T[k]^e[k] for k in 1:nvars]); ans = ans - scale * HSNum_base_SimplePowers(ReducedSimplePPs, T) - @vprint :hilbert 1 "HSNum_base_case1: returning $(ans)"); + @vprint :hilbert 1 "HSNum_base_case1: returning $(ans)"; return ans; end # function @@ -708,12 +708,12 @@ function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{ # @vprint :hilbert 1 "LOOP: first <=5 NonSimplePPs=$(first(NonSimplePPs,5))"; # Check if we have base case 0 if isempty(NonSimplePPs) #then - @vprint :hilbert 1 "HSNum_loop: --> delegate base case 0"); + @vprint :hilbert 1 "HSNum_loop: --> delegate base case 0"; return HSNum_base_SimplePowers(SimplePPs, T); end #if # Check if we have base case 1 if length(NonSimplePPs) == 1 #then - @vprint :hilbert 1 "HSNum_loop: --> delegate base case 1"); + @vprint :hilbert 1 "HSNum_loop: --> delegate base case 1"; return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T); end #if # ---------------------- @@ -731,7 +731,7 @@ function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{ else CC = [] end #if if length(CC) > 1 #then - @vprint :hilbert 1 "HSNum_loop: --> delegate Splitting case"); + @vprint :hilbert 1 "HSNum_loop: --> delegate Splitting case"; return HSNum_splitting_case(CC, SimplePPs, NonSimplePPs, T, PivotStrategy); end #if (splitting case) # ---------------------- @@ -878,7 +878,7 @@ function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{ HSNum_sum = HSNum_loop(RecurseSum_SimplePPs, RecurseSum_NonSimplePPs, T, PivotStrategy); HSNum_quot = HSNum_loop(RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs, T, PivotStrategy); - @vprint :hilbert 1 "HSNum_loop: END OF CALL"); + @vprint :hilbert 1 "HSNum_loop: END OF CALL"; return HSNum_sum + scale*HSNum_quot; end #function From dd5fb6aa232b6639da5d445b3ec1dc475e836a39 Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Mon, 7 Aug 2023 16:08:51 +0200 Subject: [PATCH 09/51] Minor modification in method selection and cwlean up of docstrings. --- src/Rings/mpoly-affine-algebras.jl | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index beb24c2822e6..a7de6d620a5f 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -76,8 +76,10 @@ end ################################################################################## +# TODO: The function below now also works for rings which are not standard graded +# by virtue of Abbott's implementation. Clean up the docstring accordingly. @doc raw""" - hilbert_series(A::MPolyQuoRing) + hilbert_series(A::MPolyQuoRing; backend::Symbol=:Singular, algorithm::Symbol=:BayerStillmanA) Given a $\mathbb Z$-graded affine algebra $A = R/I$ over a field $K$, where the grading is inherited from a $\mathbb Z$-grading on the polynomial ring $R$ defined by assigning @@ -91,6 +93,11 @@ where $n$ is the number of variables of $R$, and $w_1, \dots, w_n$ are the assig See also `hilbert_series_reduced`. +!!! note + The advanced user can select different backends for the computation (`:Singular` and + `:Abbott` for the moment), as well as different algorithms. The latter might be + ignored for certain backends. + # Examples ```jldoctest julia> R, (w, x, y, z) = graded_polynomial_ring(QQ, ["w", "x", "y", "z"]); @@ -108,13 +115,9 @@ julia> hilbert_series(A) (-t^6 + 1, -t^6 + t^5 + t^4 - t^2 - t + 1) ``` """ -function hilbert_series(A::MPolyQuoRing) +function hilbert_series(A::MPolyQuoRing; backend::Symbol=:Singular, algorithm::Symbol=:BayerStillmanA) R = base_ring(A.I) - if !is_z_graded(R) - return Oscar.HSNum_fudge(A) - end - if iszero(A.I) - @req is_z_graded(R) "The base ring must be ZZ-graded" + if is_z_graded(R) || iszero(A.I) W = R.d W = [Int(W[i][1]) for i = 1:ngens(R)] @req minimum(W) > 0 "The weights must be positive" @@ -122,8 +125,12 @@ function hilbert_series(A::MPolyQuoRing) den = prod([1-t^Int(w[1]) for w in R.d]) return (one(parent(t)), den) end - H = HilbertData(A.I) - return hilbert_series(H) + if backend == :Abbott + return Oscar.HSNum_fudge(A) + elseif backend == :Singular + H = HilbertData(A.I) + return hilbert_series(H) + end end From 9cc846278a13874e9fb21d721112694a2bb37231 Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Mon, 7 Aug 2023 16:57:27 +0200 Subject: [PATCH 10/51] Return the denominator for the hilbert series in factorized form. --- src/Rings/mpoly-affine-algebras.jl | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index a7de6d620a5f..6c4de56a8fc0 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -464,22 +464,40 @@ function multi_hilbert_series(A::MPolyQuoRing; algorithm::Symbol=:BayerStillmanA VAR = [_make_variable("t", i) for i = 1:m] end S, _ = LaurentPolynomialRing(ZZ, VAR) - q = one(S) + fac_dict = Dict{elem_type(S), Integer}() for i = 1:n e = [Int(MI[i, :][j]) for j = 1:m] B = MPolyBuildCtx(S) push_term!(B, 1, e) - q = q*(1-finish(B)) + new_fac = 1-finish(B) + if haskey(fac_dict, new_fac) + fac_dict[new_fac] = fact_dict[new_fac] + 1 + else + fac_dict[new_fac] = 1 + end end + fac_denom = FacElem(S, fac_dict) + # Old method below without factorization; left for debugging + # q = one(S) + # for i = 1:n + # e = [Int(MI[i, :][j]) for j = 1:m] + # B = MPolyBuildCtx(S) + # push_term!(B, 1, e) + # q = q*(1-finish(B)) + # end + # @assert _evaluate(fac_denom) == q if iszero(I) p = one(S) else LI = leading_ideal(I, ordering=degrevlex(gens(R))) p = _numerator_monomial_multi_hilbert_series(LI, S, m, algorithm=algorithm) end - return (p, q), (H, iso) + return (p, fac_denom), (H, iso) end +# Helper function because the usual evaluation is broken for Laurent polynomials. +_evaluate(a::FacElem) = prod(b^k for (b, k) in a; init=one(base_ring(a))) + ### TODO: original version of multi_hilbert_series based on moving things to the positive orthant #function multi_hilbert_series(A::MPolyQuoRing) From 73f0d1916dbbbe0471222bf9a8f27ff0d0bd4119 Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Mon, 7 Aug 2023 17:00:06 +0200 Subject: [PATCH 11/51] Adjust tests. --- test/Rings/hilbert.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/Rings/hilbert.jl b/test/Rings/hilbert.jl index 209bfb65c368..9a4af766f93d 100644 --- a/test/Rings/hilbert.jl +++ b/test/Rings/hilbert.jl @@ -1,4 +1,4 @@ -@testset "Abbott Hilbert series" begin +@testset "Hilbert series" begin P, x = graded_polynomial_ring(QQ, 5, "x", [1 1 1 1 1; 1 -2 3 -4 5]); G = [ @@ -56,6 +56,5 @@ I = ideal(P,G); PmodI, _ = quo(P,I); - HS = hilbert_series(PmodI); - HS = Oscar.HSNum_fudge(PmodI); + HS = multi_hilbert_series(PmodI); end From f54e2ef0ca2728017925779170f79923a2b6decc Mon Sep 17 00:00:00 2001 From: John Abbott Date: Tue, 8 Aug 2023 11:48:12 +0200 Subject: [PATCH 12/51] Renaming (to OSCAR conventions); syntactic cleaning --- src/Rings/hilbert.jl | 891 +++++++++++++++++++++++-------------------- 1 file changed, 467 insertions(+), 424 deletions(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index 6667ea277b23..aa47669afa3b 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -1,6 +1,6 @@ ################################################################## # Auxiliary functions -# Perhaps doxument them and make them publicly available? +# Perhaps document them and make them publicly available? # random_subset: # A function similar to this is in StatsBase.jl but I wish to avoid @@ -10,21 +10,21 @@ # Result is a Int64[]; entries are NOT SORTED!! function random_subset(n::Int64, m::Int64) # assume n >= 1, m >= 0 and m <= n - if m == 0 #=then=# + if m == 0 return Int64[]; - end #=if=# + end L = collect(1:n); - if m == n #=then=# + if m == n return L; - end #=if=# - for j in 1:m #=do=# + end + for j in 1:m k = rand(j:n); L[j],L[k] = L[k],L[j]; # just a SWAP - end #=for=# + end L = first(L,m); #?? sort!(L); ?? return L; -end #=function=# +end # There must be a better way...! # Split a "list" into 2 parts determined by a predicate. @@ -32,15 +32,15 @@ end #=function=# function filter2(pred::Function, L::Vector) sat = []; unsat = []; - for x in L #=do=# + for x in L if pred(x) push!(sat,x); else push!(unsat,x); - end #=if=# - end #=for=# + end + end return sat,unsat; -end #=function=# +end ################################################################## @@ -58,240 +58,256 @@ PP_exponent = Int64; # UInt ??? Strange: Int32 was slower on my machine ?!? #= mutable =# struct PP expv::Vector{PP_exponent}; -end # struct +end function Base.copy(t::PP) return PP(copy(t.expv)); -end #=function=# +end -# RETURN VALUE??? Perhaps index or 0? (or -1?) -function IsSimplePowerPP(t::PP) - CountNZ = 0; - for i in 1:length(t.expv) #do - @inbounds if (t.expv[i] == 0) continue; end #if - if (CountNZ > 0) return false; end #if - CountNZ = i; - end #for - if (CountNZ != 0) return true; end #if MAYBE RETURN index & exp??? - return false; # because t == 1 -end #function +function length(t::PP) + return length(t.expv) +end + +function getindex(t::PP, i::Int) + return t.expv[i] +end + +function setindex!(t::PP, i::Int, d::Int) + return t.expv[i] = d +end # Should be is_one, but julia complained :-( function isone(t::PP) return all(t.expv .== 0); -end #function +end function degree(t::PP) return sum(t.expv); -end #function +end + -function IsDivisible(t::PP, s::PP) # is t divisible by s +# RETURN VALUE??? Perhaps index or 0? (or -1?) +function is_simple_power_pp(t::PP) + CountNZ = 0; + for i in 1:length(t.expv) + @inbounds if (t.expv[i] == 0) continue; end + if (CountNZ > 0) return false; end + CountNZ = i; + end + if (CountNZ != 0) return true; end # MAYBE RETURN index & exp??? + return false; # because t == 1 +end + + +function is_divisible(t::PP, s::PP) # is t divisible by s n = length(t.expv); # assume equal to length(s.expv); - for i in 1:n #do - @inbounds if t.expv[i] < s.expv[i] #then + for i in 1:n + @inbounds if t.expv[i] < s.expv[i] return false; - end #if - end #for + end + end return true; -end #function +end # modifies first arg function mult_by_var!(t::PP, j::Int64) @inbounds t.expv[j] += 1; -end #function +end function mult(t1::PP, t2::PP) # ASSUMES: length(t1.expv) == length(t2.expv) return PP(t1.expv + t2.expv); -end #function +end function divide(t1::PP, t2::PP) # ASSUMES: length(t1.expv) == length(t2.expv), also that t1 is mult of t2 return PP(t1.expv - t2.expv); -end #function +end function is_coprime(t1::PP, t2::PP) # ASSUMES: length(t1.expv) == length(t2.expv) n = length(t1.expv); - for i in 1:n #do - @inbounds if t1.expv[i] != 0 && t2.expv[i] != 0 #=then=# + for i in 1:n + @inbounds if t1.expv[i] != 0 && t2.expv[i] != 0 return false; - end #=if=# - end #for + end + end return true; -end #function +end function lcm(t1::PP, t2::PP) # ASSUMES: length(t1.expv) == length(t2.expv) n = length(t1.expv); expv = [0 for _ in 1:n]; - for i in 1:n #do + for i in 1:n @inbounds expv[i] = max(t1.expv[i], t2.expv[i]); - end #for + end return PP(expv); -end #function +end function gcd(t1::PP, t2::PP) # ASSUMES: length(t1.expv) == length(t2.expv) n = length(t1.expv); expv = [0 for _ in 1:n]; - for i in 1:n #do + for i in 1:n @inbounds expv[i] = min(t1.expv[i], t2.expv[i]); - end #for + end return PP(expv); -end #function +end function gcd3(t1::PP, t2::PP, t3::PP) # ASSUMES: length(t1.expv) == length(t2.expv) == length(t3.expv) n = length(t1.expv); expv = [0 for _ in 1:n]; - for i in 1:n #do + for i in 1:n @inbounds expv[i] = min(t1.expv[i], t2.expv[i], t3.expv[i]); - end #for + end return PP(expv); -end #function +end + +# Computes t1/gcd(t1,t2) function colon(t1::PP, t2::PP) # ASSUMES: length(t1.expv) == length(t2.expv) n = length(t1.expv); expv = [0 for _ in 1:n]; - for i in 1:n #do + for i in 1:n @inbounds expv[i] = max(0, t1.expv[i]-t2.expv[i]); - end #for + end return PP(expv); -end #function +end + +# Computes t1/gcd(t1,t2^infty) function saturatePP(t1::PP, t2::PP) # ASSUMES: length(t1.expv) == length(t2.expv) n = length(t1.expv); expv = [0 for _ in 1:n]; - for i in 1:n #do - @inbounds if t2.expv[i] == 0 #then - @inbounds expv[i] = t1.expv[i]; - end #if - end #for + for i in 1:n + @inbounds if t2.expv[i] == 0 +##OLD @inbounds expv[i] = t1.expv[i]; ## + @inbounds expv[i] = t1[i]; + end + end return PP(expv); -end #function +end +# Computes radical = product of vars which divide t function radical(t::PP) n = length(t.expv); expv = [0 for _ in 1:n]; - for i in 1:n #do - @inbounds if t.expv[i] > 0 #then + for i in 1:n + @inbounds if t.expv[i] > 0 expv[i] = 1; - end #if - end #for + end + end return PP(expv); -end #function +end -# if t1 == t2 then returns false. -function DegRevLexLess(t1::PP, t2::PP) +# Test if t1 < t1 in DegRevLex ordering; +# NB if t1 == t2 then returns false. +function deg_rev_lex_less(t1::PP, t2::PP) d1 = sum(t1.expv); d2 = sum(t2.expv); - if d1 != d2 #=then=# return (d1 < d2); end #=if=# + if d1 != d2 return (d1 < d2); end nvars = length(t1.expv); - for i in nvars:-1:1 #=do=# - if t1.expv[i] != t2.expv[i] #=then=# + for i in nvars:-1:1 + if t1.expv[i] != t2.expv[i] return (t1.expv[i] > t2.expv[i]); - end #=if=# - end #=for=# + end + end return false; -end #=function=# +end + -# Test whether PP involves at least 1 indet from the list K (of indexes) -function involves(t::PP, K::Vector{Int64}) # K is index set - # ASSUMES: indexes in K are all in range - for i in K #do - if t.expv[i] != 0 #then +# Test whether PP involves at least 1 indet with index in IndexList +function involves(t::PP, IndexList::Vector{Int64}) + # ASSUMES: indexes in IndexList are all in range + for i in IndexList + @inbounds if t.expv[i] != 0 return true; - end #if - end #for + end + end return false; -end #function +end function Base.show(io::IO, t::PP) - if (all(t.expv .== 0)) # (isone(PP)) # why doesn't isone work ??? + if all(t.expv .== 0) # isone(PP) # why doesn't this work ??? print(io, "1"); return; - end #if + end str = ""; n = length(t.expv); - for i in 1:n #do - if (t.expv[i] != 0) #then - if (str != "") str = str * "*"; end #if + for i in 1:n + if (t.expv[i] != 0) + if (str != "") str = str * "*"; end str = str * "x[$(i)]"; - if (t.expv[i] > 1) #then + if (t.expv[i] > 1) str = str * "^$(t.expv[i])"; - end #if - end #if - end #for + end + end + end print(io, str); -end #function +end ####################################################### # Interreduction of a list of PPs -# interreduce list of PPs; equiv find min set of gens for PP monoideal -function interreduce(L::Vector{PP}) # L is list of PPs +# interreduce list of PPs; equiv find min set of gens for monoideal gen by L +function interreduce(L::Vector{PP}) sort!(L, by=degree); - MinGens = PP[];##empty Vector{PP}(); - for t in L #do + MinGens = PP[]; + for t in L discard = false; - for s in MinGens #do - if IsDivisible(t,s) #then + for s in MinGens + if is_divisible(t,s) discard = true; break; - end #if - end #for - if !discard #then + end + end + if !discard push!(MinGens, t); - end #if - end # for + end + end return MinGens; -end # function +end # Is t a multiple of at least one element of L? # Add degree truncation??? -function NotMultOf(L::Vector{PP}, t::PP) - for s in L #=do=# - if IsDivisible(t,s) +function not_mult_of_any(L::Vector{PP}, t::PP) + for s in L + if is_divisible(t,s) return false; - end #=if=# - end #=for=# + end + end return true; -end #=function=# +end # "project" PP onto sub-monoid of PPs gen by indets in indexes -function ProjectIndets(t::PP, indexes::Vector{Int}) - # # expv = [0 for _ in 1:length(indexes)]; - # # for i in 1:length(indexes) #do - # # expv[i] = t.expv[indexes[i]]; - # # end #for - # # return PP(expv); - return PP([t.expv[k] for k in indexes]); -end #function +function project_indets(t::PP, indexes::Vector{Int}) + return PP([t[k] for k in indexes]); +end # NOT SURE THIS IS USEFUL: how many indets are really needed in the list L? -function TrueNumVars(L::Vector{PP}) - # assume L non empty? - if isempty(L) return 0; end #if - ## MaxNumVars = length(L[1].expv); - ## AllVars = PP([1 for _ in 1:MaxNumVars]); - t = radical(L[1]); - for j in 2:length(L) #do - t = radical(mult(t,L[j])); - end #for - return degree(t); -end #function +function true_num_vars(L::Vector{PP}) + if isempty(L) + return 0 + end + t = radical(L[1]) + for j in 2:length(L) + t = lcm(t, radical(L[j])) + end + return degree(t) +end ################################################################### @@ -304,51 +320,51 @@ end #function # Input: non-empty list of PPs # Output: list of lists of var indexes, each sublist is a connected component -function ConnectedComponents(L::Vector{PP}) +function connected_components(L::Vector{PP}) ConnCompt::Vector{Vector{Int64}} = []; nvars = length(L[1].expv); IgnoreVar = [ false for _ in 1:nvars]; VarAppears = copy(IgnoreVar); - for t in L #do - for j in 1:nvars #do - @inbounds if t.expv[j] != 0 #then + for t in L + for j in 1:nvars + @inbounds if t.expv[j] != 0 @inbounds VarAppears[j] = true; - end #if - end #for - end #for + end + end + end CountIgnore = 0; - for j in 1:nvars #do - @inbounds if !VarAppears[j] #then + for j in 1:nvars + @inbounds if !VarAppears[j] @inbounds IgnoreVar[j] = true; CountIgnore += 1; - end #if - end #for - while CountIgnore < nvars #do + end + end + while CountIgnore < nvars j = findfirst(!, IgnoreVar); # j is index of some var which appears in at least one PP k = findfirst((t -> t.expv[j] != 0), L); # pick some PP involving j-th var lcm = L[k]; DoAnotherIteration = true; - while DoAnotherIteration #do + while DoAnotherIteration DoAnotherIteration = false; - for t in L #do + for t in L if is_coprime(lcm,t) continue; end s = saturatePP(t,lcm); if isone(s) continue; end lcm = mult(lcm,s); ### lcm *= s; DoAnotherIteration = true; - end #for + end end #while vars = filter((k -> lcm.expv[k] > 0), 1:nvars); # remove conn compt just found from L??? #seems to be slower with this line ?!? L = filter((t -> is_coprime(t,lcm)), L); push!(ConnCompt, vars); - for k in vars #do + for k in vars IgnoreVar[k] = true; CountIgnore += 1; - end #for + end end #while return ConnCompt; -end #function +end ############################################################################# @@ -371,138 +387,143 @@ end #function # Case gens are simple powers function HSNum_base_SimplePowers(SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T is list of HSNum PPs, one for each grading dim ans = 1; #one(T[1]) ??? - for t in SimplePPs #do + for t in SimplePPs k = findfirst(entry -> (entry > 0), t.expv); ans = ans * (1 - T[k]^t.expv[k]); # ???? ans -= ans*T[k]^t; - end #for + end return ans; -end #function +end function HSNum_base_case1(t::PP, SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T is list of HSNum PPs, one for each grading dim # t is not a "simple power", all the others are - @vprint :hilbert 1 "HSNum_base_case1: t = $(t)"; - @vprint :hilbert 1 "HSNum_base_case1: SimplePPs = $(SimplePPs)"; + @vprintln :hilbert 1 "HSNum_base_case1: t = $(t)"; + @vprintln :hilbert 1 "HSNum_base_case1: SimplePPs = $(SimplePPs)"; ans = HSNum_base_SimplePowers(SimplePPs, T); ReducedSimplePPs::Vector{PP} = []; - for j in 1:length(SimplePPs) #do + for j in 1:length(SimplePPs) @inbounds e = SimplePPs[j].expv; k = findfirst((entry -> (entry > 0)), e); # ispositive - if t.expv[k] == 0 #=then=# push!(ReducedSimplePPs,SimplePPs[j]); continue; end #=if=# # no need to make a copy + if t.expv[k] == 0 push!(ReducedSimplePPs,SimplePPs[j]); continue; end # no need to make a copy tt = copy(SimplePPs[j]); tt.expv[k] -= t.expv[k]; # guaranteed > 0 push!(ReducedSimplePPs, tt); - end #for + end e = t.expv; nvars = length(e); @inbounds scale = prod([T[k]^e[k] for k in 1:nvars]); ans = ans - scale * HSNum_base_SimplePowers(ReducedSimplePPs, T) - @vprint :hilbert 1 "HSNum_base_case1: returning $(ans)"; + @vprintln :hilbert 1 "HSNum_base_case1: returning $(ans)"; return ans; end # function ## CC contains at least 2 connected components (each compt repr as Vector{Int64} of the variable indexes in the compt) function HSNum_splitting_case(CC::Vector{Vector{Int64}}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) - @vprint :hilbert 1 "Splitting case: CC = $(CC)"; + @vprintln :hilbert 1 "Splitting case: CC = $(CC)"; HSNumList = []; ## list of HSNums # Now find any simple PPs which are indep of the conn compts found nvars = length(NonSimplePPs[1].expv); FoundVars = PP([0 for _ in 1:nvars]); # will be prod of all vars appearing in some conn compt - for IndexSet in CC #=do=# - for j in IndexSet #=do=# + for IndexSet in CC + for j in IndexSet mult_by_var!(FoundVars, j); - end #=for=# - end #=for=# + end + end IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs); - for IndexSet in CC #do + for IndexSet in CC SubsetNonSimplePPs = filter((t -> involves(t,IndexSet)), NonSimplePPs); SubsetSimplePPs = filter((t -> involves(t,IndexSet)), SimplePPs); push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetNonSimplePPs, T, PivotStrategy)); # Next 3 lines commented out: seemed to bring no benefit (??but why??) - #? SubsetNonSimplePPs = [ProjectIndets(t, IndexSet) for t in SubsetNonSimplePPs]; - #? SubsetSimplePPs = [ProjectIndets(t, IndexSet) for t in SubsetSimplePPs]; + #? SubsetNonSimplePPs = [project_indets(t, IndexSet) for t in SubsetNonSimplePPs]; + #? SubsetSimplePPs = [project_indets(t, IndexSet) for t in SubsetSimplePPs]; #? push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetGens, [T[k] for k in IndexSet], PivotStrategy)); - end #for + end HSNum_combined = prod(HSNumList); - if !isempty(IsolatedSimplePPs) #then + if !isempty(IsolatedSimplePPs) HSNum_combined *= HSNum_base_SimplePowers(IsolatedSimplePPs, T); ##OLD HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy); - end #if + end return HSNum_combined; -end #=function=# +end function HSNum_total_splitting_case(VarIndexes::Vector{Int64}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) - @vprint :hilbert 1 "Total splitting case: VarIndexes = $(VarIndexes)"; + @vprintln :hilbert 1 "Total splitting case: VarIndexes = $(VarIndexes)"; HSNumList = []; ## list of HSNums # Now find any simple PPs which are indep of the conn compts found nvars = length(NonSimplePPs[1].expv); FoundVars = PP([0 for _ in 1:nvars]); # will be prod of all vars appearing in some conn compt - for i in VarIndexes #=do=# + for i in VarIndexes mult_by_var!(FoundVars, i); - end #=for=# + end IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs); - for t in NonSimplePPs #do + for t in NonSimplePPs SubsetSimplePPs = filter((s -> !is_coprime(s,t)), SimplePPs); push!(HSNumList, HSNum_base_case1(t, SubsetSimplePPs, T)); - end #for + end HSNum_combined = prod(HSNumList); - if !isempty(IsolatedSimplePPs) #then + if !isempty(IsolatedSimplePPs) HSNum_combined *= HSNum_base_SimplePowers(IsolatedSimplePPs, T); ##OLD HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy); - end #if + end return HSNum_combined; -end #=function=# +end -#------------------------------------------------------------------ -# Pivot selection strategies: -# term-order corr to matrix with -1 on anti-diag: [[0,0,-1],[0,-1,0],...] -function IsRevLexSmaller(t1::PP, t2::PP) - n = length(t1.expv); - for j in n:-1:1 #=do=# - if t1.expv[j] != t2.expv[j] #=then=# return (t1.expv[j] > t2.expv[j]); end #=if=# - end #=for=# +#------------------------------------------------------------------ +# Pivot selection strategies: (see Bigatti 1997 paper) + +# term-order corr to matrix with -1 on anti-diag: [[0,0,-1], [0,-1,0],...] +function is_rev_lex_smaller(t1::PP, t2::PP) + n = length(t1); + for j in n:-1:1 + if t1[j] != t2[j] return (t1[j] > t2[j]); end + end return false; # t1 and t2 were equal (should not happen in this code) -end #=function=# +end -function RevLexMin(L::Vector{PP}) +function rev_lex_min(L::Vector{PP}) # assume length(L) > 0 - if isempty(L) #=then=# return L[1]; end #=if=# + if isempty(L) return L[1]; end IndexMin = 1; - for j in 2:length(L) #=do=# - if !IsRevLexSmaller(L[j], L[IndexMin]) + for j in 2:length(L) + if !is_rev_lex_smaller(L[j], L[IndexMin]) IndexMin = j; - end #=if=# - end #=for=# + end + end return L[IndexMin]; -end #=function=# +end -function RevLexMax(L::Vector{PP}) +function rev_lex_max(L::Vector{PP}) # assume length(L) > 0 - if length(L) == 1 #=then=# return L[1]; end #=if=# + if length(L) == 1 return L[1]; end IndexMax = 1; - for j in 2:length(L) #=do=# - if !IsRevLexSmaller(L[IndexMax], L[j]) + for j in 2:length(L) + if !is_rev_lex_smaller(L[IndexMax], L[j]) IndexMax = j; - end #=if=# - end #=for=# + end + end return L[IndexMax]; -end #=function=# +end -function HSNum_Bayer_Stillman(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}) +function HSNum_bayer_stillman(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}) ##println("HSNum_BS: Simple: $(SimplePPs)"); ##println("HSNum_BS: NonSimple: $(NonSimplePPs)"); # Maybe sort the gens??? - if isempty(NonSimplePPs) #=then=# return HSNum_base_SimplePowers(SimplePPs, T); end #=if=# - if length(NonSimplePPs) == 1 #=then=# return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T); end #=if=# + if isempty(NonSimplePPs) + return HSNum_base_SimplePowers(SimplePPs, T) + end + if length(NonSimplePPs) == 1 + return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T) + end # NonSimplePPs contains at least 2 elements # BSPivot = last(NonSimplePPs); # pick one somehow -- this is just simple to impl - #?? BSPivot = RevLexMin(NonSimplePPs); # VERY SLOW on Hilbert-test-rnd6.jl - BSPivot = RevLexMax(NonSimplePPs); - #VERY SLOW?!? BSPivot = RevLexMax(vcat(SimplePPs,NonSimplePPs)); # VERY SLOW on Hilbert-test-rnd6.jl - println("BSPivot = $(BSPivot)"); + #?? BSPivot = rev_lex_min(NonSimplePPs); # VERY SLOW on Hilbert-test-rnd6.jl + BSPivot = rev_lex_max(NonSimplePPs); + #VERY SLOW?!? BSPivot = rev_lex_max(vcat(SimplePPs,NonSimplePPs)); # VERY SLOW on Hilbert-test-rnd6.jl + @vprintln :hilbert 2 "BSPivot = $(BSPivot)"; NonSPP = filter((t -> (t != BSPivot)), NonSimplePPs); SPP = SimplePPs;#filter((t -> (t != BSPivot)), SimplePPs); part1 = HSNum_loop(SPP, NonSPP, T, :bayer_stillman); @@ -511,7 +532,7 @@ function HSNum_Bayer_Stillman(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, part2 = HSNum_loop(NewSimplePPs, NewNonSimplePPs, T, :bayer_stillman); e = BSPivot.expv; return part1 - prod([T[k]^e[k] for k in 1:length(e)])*part2; -end #=function=# +end #-------------------------------------------- # Pivot selection strategies @@ -523,20 +544,19 @@ end #=function=# # (part 2) corr freq function HSNum_most_freq_indets(gens::Vector{PP}) # ASSUMES: gens is non-empty - nvars = length(gens[1].expv); + nvars = length(gens[1]); freq = [0 for i in 1:nvars]; # Vector{Int} or Vector{UInt} ??? - for t in gens #do - e = t.expv; - for i in 1:nvars #do - @inbounds if (e[i] != 0) #then + for t in gens + for i in 1:nvars + @inbounds if (t[i] != 0) @inbounds freq[i] += 1 - end #if - end #for - end #for + end + end + end MaxFreq = maximum(freq); MostFreq = findall((x -> x==MaxFreq), freq); return MostFreq, MaxFreq; -end #=function=# +end # Returns index of the indet @@ -544,232 +564,232 @@ function HSNum_most_freq_indet1(gens::Vector{PP}) # ASSUMES: gens is non-empty MostFreq,_ = HSNum_most_freq_indets(gens); return MostFreq[1]; -end #=function=# +end # Returns index of the indet function HSNum_most_freq_indet_rnd(gens::Vector{PP}) # ASSUMES: gens is non-empty MostFreq,_ = HSNum_most_freq_indets(gens); return rand(MostFreq); -end #=function=# +end -function HSNum_choose_pivot_indet(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); ##HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 - nvars = length(gens[1].expv); - PivotExpv = [0 for _ in 1:nvars]; - PivotExpv[PivotIndet] = 1; - PivotPP = PP(PivotExpv); -end #function +# THIS PIVOT STRATEGY WAS AWFULLY SLOW! +# function HSNum_choose_pivot_indet(MostFreq::Vector{Int64}, gens::Vector{PP}) +# PivotIndet = rand(MostFreq); ##HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 +# nvars = length(gens[1].expv); +# PivotExpv = [0 for _ in 1:nvars]; +# PivotExpv[PivotIndet] = 1; +# PivotPP = PP(PivotExpv); +# end function HSNum_choose_pivot_simple_power_median(MostFreq::Vector{Int64}, gens::Vector{PP}) - # simple-power-pivot from Bigatti JPAA, 1997 + # variant of simple-power-pivot from Bigatti JPAA, 1997 PivotIndet = rand(MostFreq); exps = [t.expv[PivotIndet] for t in gens]; exps = filter((e -> e>0), exps); sort!(exps); exp = exps[div(1+length(exps),2)]; # "median" - nvars = length(gens[1].expv); + nvars = length(gens[1]); PivotExpv = [0 for _ in 1:nvars]; PivotExpv[PivotIndet] = exp; PivotPP = PP(PivotExpv); -end #function +end function HSNum_choose_pivot_simple_power_max(MostFreq::Vector{Int64}, gens::Vector{PP}) - # simple-power-pivot from Bigatti JPAA, 1997 + # variant of simple-power-pivot from Bigatti JPAA, 1997 PivotIndet = rand(MostFreq); exps = [t.expv[PivotIndet] for t in gens]; exp = max(exps...); - nvars = length(gens[1].expv); + nvars = length(gens[1]); PivotExpv = [0 for _ in 1:nvars]; PivotExpv[PivotIndet] = exp; PivotPP = PP(PivotExpv); -end #function +end function HSNum_choose_pivot_gcd2simple(MostFreq::Vector{Int64}, gens::Vector{PP}) + # simple-power-pivot from Bigatti JPAA, 1997 PivotIndet = rand(MostFreq); cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# + if length(cand) == 1 error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! - end #=if=# - nvars = length(gens[1].expv); + end + nvars = length(gens[1]); expv = [0 for _ in 1:nvars]; expv[PivotIndet] = min(cand[1].expv[PivotIndet], cand[2].expv[PivotIndet]); return PP(expv); -end #function +end function HSNum_choose_pivot_gcd2max(MostFreq::Vector{Int64}, gens::Vector{PP}) PivotIndet = rand(MostFreq); cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# + if length(cand) == 1 error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! - end #=if=# + end pick2 = [cand[k] for k in random_subset(length(cand),2)]; - t = gcd(pick2[1], pick2[2]); - d = 0; for i in 1:length(t.expv) #=do=# d = max(d,t.expv[i]); end #=for=# - for i in 1:length(t.expv) #=do=# - if t.expv[i] < d #=then=# + t = gcd(pick2...); + nvars = length(t); + d = 0; for i in 1:nvars d = max(d,t.expv[i]); end + for i in 1:nvars + if t[i] < d t.expv[i]=0; - end #=if=# - end #=for=# + end + end return t; -end #function +end -# May produce a non-simple pivot!!! -function HSNum_choose_pivot_gcd3(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); - cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# - error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! - end #=if=# - if length(cand) == 2 #=then=# - return gcd(cand[1], cand[2]); - end #=if=# - pick3 = [cand[k] for k in random_subset(length(cand),3)]; - return gcd3(pick3[1], pick3[2], pick3[3]); -end #function +# # May produce a non-simple pivot!!! +# function HSNum_choose_pivot_gcd3(MostFreq::Vector{Int64}, gens::Vector{PP}) +# PivotIndet = rand(MostFreq); +# cand = filter((t -> t.expv[PivotIndet]>0), gens); +# if length(cand) == 1 +# error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! +# end +# if length(cand) == 2 +# return gcd(cand[1], cand[2]); +# end +# pick3 = [cand[k] for k in random_subset(length(cand),3)]; +# return gcd3(pick3[1], pick3[2], pick3[3]); +# end function HSNum_choose_pivot_gcd3simple(MostFreq::Vector{Int64}, gens::Vector{PP}) PivotIndet = rand(MostFreq); cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# + if length(cand) == 1 error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! - end #=if=# - if length(cand) == 2 #=then=# + end + if length(cand) == 2 t = gcd(cand[1], cand[2]); else pick3 = [cand[k] for k in random_subset(length(cand),3)]; t = gcd3(pick3[1], pick3[2], pick3[3]); - end #=if=# + end # println("BEFORE: t= $(t)"); j = 1; d = t.expv[1]; - for i in 2:length(t.expv) #=do=# - if t.expv[i] <= d #=then=# + for i in 2:length(t.expv) + if t.expv[i] <= d t.expv[i] = 0; continue; - end #=if=# + end t.expv[j] = 0; j=i; d = t.expv[i]; - end #=for=# + end # println("AFTER: t= $(t)"); return t; -end #function +end function HSNum_choose_pivot_gcd3max(MostFreq::Vector{Int64}, gens::Vector{PP}) PivotIndet = rand(MostFreq); cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# + if length(cand) == 1 error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! - end #=if=# - if length(cand) == 2 #=then=# + end + if length(cand) == 2 t = gcd(cand[1], cand[2]); else pick3 = [cand[k] for k in random_subset(length(cand),3)]; t = gcd3(pick3[1], pick3[2], pick3[3]); - end #=if=# - d = 0; for i in 1:length(t.expv) #=do=# d = max(d,t.expv[i]); end #=for=# - for i in 1:length(t.expv) #=do=# - if t.expv[i] < d #=then=# + end + d = 0; for i in 1:length(t.expv) d = max(d,t.expv[i]); end + for i in 1:length(t.expv) + if t.expv[i] < d t.expv[i]=0; - end #=if=# - end #=for=# + end + end return t; -end #function +end -# May produce a non-simple pivot!!! -function HSNum_choose_pivot_gcd4(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); - cand = filter((t -> t.expv[PivotIndet]>0), gens); - if length(cand) == 1 #=then=# - error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! - end #=if=# - if length(cand) == 2 #=then=# - return gcd(cand[1], cand[2]); - end #=if=# - if length(cand) ==3 #=then=# - return gcd3(cand[1], cand[2], cand[3]); - end #=if=# - pick4 = [cand[k] for k in random_subset(length(cand),4)]; - return gcd(gcd(pick4[1], pick4[2]), gcd(pick4[3],pick4[4])); -end #function +# # May produce a non-simple pivot!!! +# function HSNum_choose_pivot_gcd4(MostFreq::Vector{Int64}, gens::Vector{PP}) +# PivotIndet = rand(MostFreq); +# cand = filter((t -> t.expv[PivotIndet]>0), gens); +# if length(cand) == 1 +# error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! +# end +# if length(cand) == 2 +# return gcd(cand[1], cand[2]); +# end +# if length(cand) ==3 +# return gcd3(cand[1], cand[2], cand[3]); +# end +# pick4 = [cand[k] for k in random_subset(length(cand),4)]; +# return gcd(gcd(pick4[1], pick4[2]), gcd(pick4[3],pick4[4])); +# end # Assume SimplePPs+NonSimplePPs are interreduced function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) - @vprint :hilbert 1 "HSNum_loop: SimplePPs=$(SimplePPs)"; - @vprint :hilbert 1 "HSNum_loop: NonSimplePPs=$(NonSimplePPs)"; -# @vprint :hilbert 1 "LOOP: first <=5 NonSimplePPs=$(first(NonSimplePPs,5))"; + @vprintln :hilbert 1 "HSNum_loop: SimplePPs=$(SimplePPs)"; + @vprintln :hilbert 1 "HSNum_loop: NonSimplePPs=$(NonSimplePPs)"; +# @vprintln :hilbert 1 "LOOP: first <=5 NonSimplePPs=$(first(NonSimplePPs,5))"; # Check if we have base case 0 - if isempty(NonSimplePPs) #then - @vprint :hilbert 1 "HSNum_loop: --> delegate base case 0"; + if isempty(NonSimplePPs) + @vprintln :hilbert 1 "HSNum_loop: --> delegate base case 0"; return HSNum_base_SimplePowers(SimplePPs, T); - end #if + end # Check if we have base case 1 - if length(NonSimplePPs) == 1 #then - @vprint :hilbert 1 "HSNum_loop: --> delegate base case 1"; + if length(NonSimplePPs) == 1 + @vprintln :hilbert 1 "HSNum_loop: --> delegate base case 1"; return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T); - end #if + end # ---------------------- MostFreq,freq = HSNum_most_freq_indets(NonSimplePPs); - if freq == 1 #=then=# + if freq == 1 return HSNum_total_splitting_case(MostFreq, SimplePPs, NonSimplePPs, T, PivotStrategy); - end #=if=# - - if PivotStrategy == :bayer_stillman #=then=# - return HSNum_Bayer_Stillman(SimplePPs, NonSimplePPs, T); - end #=if=# + end + + if PivotStrategy == :bayer_stillman + return HSNum_bayer_stillman(SimplePPs, NonSimplePPs, T); + end # Check for "splitting case" - if length(NonSimplePPs) <= length(NonSimplePPs[1].expv)#=nvars=# #=then=# - CC = ConnectedComponents(NonSimplePPs); + if length(NonSimplePPs) <= length(NonSimplePPs[1].expv)#=nvars=# + CC = connected_components(NonSimplePPs); else CC = [] - end #if - if length(CC) > 1 #then - @vprint :hilbert 1 "HSNum_loop: --> delegate Splitting case"; + end + if length(CC) > 1 + @vprintln :hilbert 1 "HSNum_loop: --> delegate Splitting case"; return HSNum_splitting_case(CC, SimplePPs, NonSimplePPs, T, PivotStrategy); - end #if (splitting case) + end # ---------------------- # Pivot case: first do the ideal sum, then do ideal quotient # (the ideas are relatively simple, the code is long, tedious, and a bit fiddly) - if PivotStrategy == :indet - PivotPP = HSNum_choose_pivot_indet(MostFreq, NonSimplePPs) - end #=if=# + if PivotStrategy == :simple_power_median || PivotStrategy == :auto + PivotPP = HSNum_choose_pivot_simple_power_median(MostFreq, NonSimplePPs) + end if PivotStrategy == :simple_power_max PivotPP = HSNum_choose_pivot_simple_power_max(MostFreq, NonSimplePPs) - end #=if=# + end if PivotStrategy == :gcd2simple PivotPP = HSNum_choose_pivot_gcd2simple(MostFreq, NonSimplePPs) - end #=if=# + end if PivotStrategy == :gcd2max PivotPP = HSNum_choose_pivot_gcd2max(MostFreq, NonSimplePPs) - end #=if=# - if PivotStrategy == :gcd3 - PivotPP = HSNum_choose_pivot_gcd3(MostFreq, NonSimplePPs) - end #=if=# + end + # if PivotStrategy == :gcd3 + # PivotPP = HSNum_choose_pivot_gcd3(MostFreq, NonSimplePPs) + # end if PivotStrategy == :gcd3simple PivotPP = HSNum_choose_pivot_gcd3simple(MostFreq, NonSimplePPs) - end #=if=# + end if PivotStrategy == :gcd3max PivotPP = HSNum_choose_pivot_gcd3max(MostFreq, NonSimplePPs) - end #=if=# - if PivotStrategy == :gcd4 - PivotPP = HSNum_choose_pivot_gcd4(MostFreq, NonSimplePPs) - end #=if=# - if PivotStrategy == :simple_power_median || PivotStrategy == :auto - PivotPP = HSNum_choose_pivot_simple_power_median(MostFreq, NonSimplePPs) - end #=if=# - # end #=if=# - @vprint :hilbert 1 "HSNum_loop: pivot = $(PivotPP)"; - PivotIsSimple = IsSimplePowerPP(PivotPP); + end + # if PivotStrategy == :gcd4 + # PivotPP = HSNum_choose_pivot_gcd4(MostFreq, NonSimplePPs) + # end + # end + @vprintln :hilbert 1 "HSNum_loop: pivot = $(PivotPP)"; + PivotIsSimple = is_simple_power_pp(PivotPP); PivotIndex = findfirst((e -> e>0), PivotPP.expv); # used only if PivotIsSimple == true USE_SAFE_VERSION_SUM=false; - if USE_SAFE_VERSION_SUM #=then=# + if USE_SAFE_VERSION_SUM # Safe but slow version: just add new gen, then interreduce RecurseSum = vcat(SimplePPs, NonSimplePPs); push!(RecurseSum, PivotPP); @@ -779,59 +799,60 @@ function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{ RecurseSum_SimplePPs = RecurseSum_SimplePPs_OLD; else # use "clever" version: # We know that SimplePPs + NonSimplePPs are already interreduced - if PivotIsSimple #=then=# + if PivotIsSimple RecurseSum_SimplePPs_NEW = copy(SimplePPs); k = findfirst((t -> t.expv[PivotIndex] > 0), SimplePPs); if k === nothing push!(RecurseSum_SimplePPs_NEW, PivotPP); else RecurseSum_SimplePPs_NEW[k] = PivotPP; - end #if + end RecurseSum_NonSimplePPs_NEW = filter((t -> t.expv[PivotIndex] < PivotPP.expv[PivotIndex]), NonSimplePPs); else # PivotPP is not simple -- so this is the "general case" RecurseSum_SimplePPs_NEW = copy(SimplePPs); # need to copy? - RecurseSum_NonSimplePPs_NEW = filter((t -> !IsDivisible(t,PivotPP)), NonSimplePPs); + RecurseSum_NonSimplePPs_NEW = filter((t -> !is_divisible(t,PivotPP)), NonSimplePPs); push!(RecurseSum_NonSimplePPs_NEW, PivotPP); - end #=if=# + end RecurseSum_NonSimplePPs = RecurseSum_NonSimplePPs_NEW; RecurseSum_SimplePPs = RecurseSum_SimplePPs_NEW; - end #=if=# # USE_SAFE_VERSION_SUM + end # USE_SAFE_VERSION_SUM # Now do the quotient... # Now get SimplePPs & NonSimplePPs for the quotient while limiting amount of interreduction USE_SAFE_VERSION_QUOT = false; - if USE_SAFE_VERSION_QUOT #=then=# + if USE_SAFE_VERSION_QUOT #=SAFE VERSION: simpler but probably slower=# RecurseQuot = [colon(t,PivotPP) for t in vcat(SimplePPs,NonSimplePPs)]; RecurseQuot = interreduce(RecurseQuot); RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs = SeparateSimplePPs(RecurseQuot); else # Use "smart version" - if !PivotIsSimple #then # GENERAL CASE (e.g. if not PivotIsSimple) + if !PivotIsSimple # GENERAL CASE (e.g. if not PivotIsSimple) # Clever approach (for non-simple pivots) taken from Bigatti 1997 paper (p.247 just after Prop 1 to end of sect 5) # ???Maybe use filter2 (see start of this file) instead of loop below??? BM = PP[]; NotBM = PP[]; PivotPlus = mult(PivotPP, radical(PivotPP)); - for t in NonSimplePPs #=do=# - if IsDivisible(t,PivotPlus) #=then=# + for t in NonSimplePPs + if is_divisible(t,PivotPlus) push!(BM,t); else push!(NotBM,t); - end #=if=# - end #=for=# + end + end + # BM is short for "big multiple" (see Bigatti 97, p.247 between Prop 1 and Prop 2) BM = [divide(t,PivotPP) for t in BM]; # divide is same as colon here NotBM = vcat(NotBM, SimplePPs); NotBM_mixed = PP[]; NotBM_coprime = PP[]; - for t in NotBM #=do=# - if is_coprime(t,PivotPP) #=then=# + for t in NotBM + if is_coprime(t,PivotPP) push!(NotBM_coprime,t); else push!(NotBM_mixed, colon(t,PivotPP)); - end #=if=# - end #=for=# + end + end # At this poiint we have 3 disjoint lists of PPs: BM (big multiples), NotBM_coprime, NotBM_mixed # In all cases the PPs have been colon-ed by PivotPP NotBM_mixed = interreduce(NotBM_mixed); # cannot easily be "clever" here - filter!((t -> NotMultOf(NotBM_mixed,t)), NotBM_coprime); + filter!((t -> not_mult_of_any(NotBM_mixed,t)), NotBM_coprime); RecurseQuot = vcat(NotBM_coprime, NotBM_mixed); # already interreduced RQ_SimplePPs, RQ_NonSimplePPs = SeparateSimplePPs(RecurseQuot); RecurseQuot_SimplePPs = RQ_SimplePPs; @@ -843,142 +864,164 @@ function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{ if !(k === nothing) RecurseQuot_SimplePPs[k] = copy(RecurseQuot_SimplePPs[k]); RecurseQuot_SimplePPs[k].expv[PivotIndex] -= PivotPP.expv[PivotIndex]; - end #if + end DegPivot = degree(PivotPP); NonSimpleTbl = [PP[] for _ in 0:DegPivot]; ## WARNING: indexes are offset by 1 -- thanks Julia! NonSimple1 = PP[]; # will contain all PPs divisible by PivotPP^(1+epsilon) - for t in NonSimplePPs #=do=# + for t in NonSimplePPs degt = t.expv[PivotIndex]; - if degt > DegPivot #=then=# + if degt > DegPivot push!(NonSimple1, divide(t, PivotPP)); else push!(NonSimpleTbl[degt+1], colon(t,PivotPP)); - end #=if=# - end #=for=# + end + end NonSimple2 = NonSimpleTbl[DegPivot+1]; - for i in DegPivot:-1:1 #=do=# - NewPPs = filter((t -> NotMultOf(NonSimple2,t)), NonSimpleTbl[i]); + for i in DegPivot:-1:1 + NewPPs = filter((t -> not_mult_of_any(NonSimple2,t)), NonSimpleTbl[i]); NonSimple2 = vcat(NonSimple2, NewPPs); - end #=for=# - NewSimplePPs = filter(IsSimplePowerPP, NonSimple2); - NonSimple2 = filter(!IsSimplePowerPP, NonSimple2); ## SeparateSimplePPs??? - if !isempty(NewSimplePPs) #=then=# + end + NewSimplePPs = filter(is_simple_power_pp, NonSimple2); ## Use instead + NonSimple2 = filter(!is_simple_power_pp, NonSimple2); ## SeparateSimplePPs??? + if !isempty(NewSimplePPs) RecurseQuot_SimplePPs = interreduce(vcat(RecurseQuot_SimplePPs, NewSimplePPs)); - end #=if=# + end RecurseQuot_NonSimplePPs = vcat(NonSimple1, NonSimple2); - end #=if=# #PivotIsSimple - end #=if=# # end of USE_SAFE_VERSION_QUOT + end #PivotIsSimple + end # USE_SAFE_VERSION_QUOT # Now put the two pieces together: nvars = length(PivotPP.expv); scale = prod([T[k]^PivotPP.expv[k] for k in 1:nvars]); - @vprint :hilbert 2 "HSNum_loop: SUM recursion: simple $(RecurseSum_SimplePPs)"; - @vprint :hilbert 2 "HSNum_loop: SUM recursion: nonsimple $(RecurseSum_NonSimplePPs)"; - @vprint :hilbert 2 "HSNum_loop: QUOT recursion: simple $(RecurseQuot_SimplePPs)"; - @vprint :hilbert 2 "HSNum_loop: QUOT recursion: nonsimple $(RecurseQuot_NonSimplePPs)"; + @vprintln :hilbert 2 "HSNum_loop: SUM recursion: simple $(RecurseSum_SimplePPs)"; + @vprintln :hilbert 2 "HSNum_loop: SUM recursion: nonsimple $(RecurseSum_NonSimplePPs)"; + @vprintln :hilbert 2 "HSNum_loop: QUOT recursion: simple $(RecurseQuot_SimplePPs)"; + @vprintln :hilbert 2 "HSNum_loop: QUOT recursion: nonsimple $(RecurseQuot_NonSimplePPs)"; HSNum_sum = HSNum_loop(RecurseSum_SimplePPs, RecurseSum_NonSimplePPs, T, PivotStrategy); HSNum_quot = HSNum_loop(RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs, T, PivotStrategy); - @vprint :hilbert 1 "HSNum_loop: END OF CALL"; + @vprintln :hilbert 1 "HSNum_loop: END OF CALL"; return HSNum_sum + scale*HSNum_quot; -end #function +end -function SeparateSimplePPs(gens::Vector{PP}) +function separate_simple_pps(gens::Vector{PP}) SimplePPs::Vector{PP} = []; NonSimplePPs::Vector{PP} = []; - for g in gens #do - if IsSimplePowerPP(g) #then + for g in gens + if is_simple_power_pp(g) push!(SimplePPs, g) else push!(NonSimplePPs, g) - end #if - end #for + end + end return SimplePPs, NonSimplePPs; -end #function +end # Check args: either throws or returns nothing. -function HSNum_CheckArgs(gens::Vector{PP}, W::Vector{Vector{Int}}) - if isempty(gens) #=then=# +function HSNum_check_args(gens::Vector{PP}, W::Vector{Vector{Int}}) + if isempty(gens) throw("HSNum: need at least 1 generator"); - end #=if=# - if isempty(W) #=then=# + end + if isempty(W) throw("HSNum: weight matrix must have at least 1 row"); - end #=if=# + end nvars = length(gens[1].expv); if !all((t -> length(t.expv)==nvars), gens) throw("HSNum: generators must all have same size exponent vectors"); - end #if + end if !all((row -> length(row)==nvars), W) throw("HSNum: weight matrix must have 1 column for each variable") - end #if + end # Zero weights are allowed??? # Args are OK, so simply return (without throwing) -end # function +end -function HSNum(gens::Vector{PP}, W::Vector{Vector{Int}}, PivotStrategy::Symbol) - ### println("HSNum: gens = $(gens)"); - ### println("HSNum: W = $(W)"); - HSNum_CheckArgs(gens, W); +function HSNum(PP_gens::Vector{PP}, W::Vector{Vector{Int}}, PivotStrategy::Symbol) + # ASSUME W is "rectangular" + @vprintln :hilbert 1 "HSNum: PP_gens = $(PP_gens)"; + @vprintln :hilbert 1 "HSNum: weight matrix W = $(W)"; + HSNum_check_args(PP_gens, W); #throws or does nothing # Grading is over ZZ^m - m = size(W)[1]; # NumRows - ncols = size(W[1])[1]; # how brain-damaged is Julia??? - nvars = length(gens[1].expv); - if ncols != nvars #then + m = length(W); # NumRows + ncols = length(W[1]); + nvars = length(PP_gens[1]); + if ncols != nvars throw(ArgumentError("weights matrix has wrong number of columns ($(ncols)); should be same as number of variables ($(nvars))")); - end #if + end HPRing, t = LaurentPolynomialRing(QQ, ["t$k" for k in 1:m]); T = [one(HPRing) for k in 1:nvars]; - for k in 1:nvars #do + for k in 1:nvars s = one(HPRing); - for j in 1:m #do + for j in 1:m s *= t[j]^W[j][k]; - end #for + end T[k] = s; - end #for + end # Now have T[1] = t1^W[1,1] * t2^W[2,1] * ..., etc - ### println("HSNum: T vector is $(T)"); - SimplePPs,NonSimplePPs = SeparateSimplePPs(interreduce(gens)); - sort!(NonSimplePPs, lt=DegRevLexLess); # recommended by Bayer+Stillman (for their criterion) + SimplePPs,NonSimplePPs = separate_simple_pps(interreduce(PP_gens)); + sort!(NonSimplePPs, lt=deg_rev_lex_less); # recommended by Bayer+Stillman (for their criterion) return HSNum_loop(SimplePPs, NonSimplePPs, T, PivotStrategy); -end #function +end #----------------------------------------------------------------------------- # This fn copied from GradedModule.jl (in dir OSCAR/HILBERT/) function gen_repr(d) grading_dim = length(gens(parent(d))); - return [getindex(d,k) for k in 1:grading_dim]; -end #function + return [d[k] for k in 1:grading_dim]; +end + +@doc raw""" + HSNum_fudge(A::MPolyQuoRing; pivot_strategy::Symbol = :auto) + +Compute numerator of Hilbert series of the quotient `A`. +Result is a pair: `N, D` being the numerator `N` (as a laurent polynomial) and the denominator `D` as +a factor list of laurent polynomials. + +!!! note + Applied to an ideal `I`, the function first homogenizes the generators of `I` in the extended ring. + It then creates the ideal generated by these homogenizations, and saturates this ideal + with respect to the ideal which is generated by the homogenizing variables. + +# Examples +```jldoctest +julia> R, (x,y,z) = graded_polynomial_ring(QQ, ["x", "y","z"]) +(Graded multivariate polynomial ring in 3 variables over QQ, MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}[x, y, z]) + +julia> I = ideal(R, [x^3+y^2*z, y^3+x*z^2, z^3+x^2*y]); + +julia> RmodI,_ = quo(R,I); + +julia> HSNum_fudge(RmodI) +-t1^9 + 3*t1^6 - 3*t1^3 + 1 + +julia> R, (x,y,z) = graded_polynomial_ring(QQ, ["x", "y","z"], [1 2 3; 3 2 1]) +(Graded multivariate polynomial ring in 3 variables over QQ, MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}[x, y, z]) + +julia> I = ideal(R, [x*z+y^2, y^6+x^3*z^3, z^6, x^6]); + +julia> RmodI,_ = quo(R,I); + +julia> HSNum_fudge(RmodI) +-t1^28*t2^28 + t1^24*t2^24 + t1^22*t2^10 - t1^18*t2^6 + t1^10*t2^22 - t1^6*t2^18 - t1^4*t2^4 + 1 -function HSNum_fudge(PmodI::MPolyQuoRing, PivotStrategy::Symbol = :auto) - if PivotStrategy == :indet return nothing; end - I = PmodI.I; - P = base_ring(I);##parent(gens(I)[1]); # there MUST be a better way!! +``` +""" +function HSNum_fudge(PmodI::MPolyQuoRing; pivot_strategy::Symbol = :auto) + I = modulus(PmodI); + P = base_ring(I); nvars = length(gens(P)); grading_dim = length(gens(parent(degree(gen(P,1))))); # better way??? weights = [degree(var) for var in gens(P)]; W = [[0 for _ in 1:nvars] for _ in 1:grading_dim]; - for i in 1:nvars #do + for i in 1:nvars expv = [Int64(exp) for exp in gen_repr(degree(gen(P,i)))]; - for j in 1:grading_dim #do + for j in 1:grading_dim W[j][i] = expv[j]; - end #for - end #for - # ## W = [[Int64(exp) for exp in gen_repr(d)] for d in weights]; - # W=[] - # for d in weights #do - # expv = [Int64(exp) for exp in gen_repr(d)]; - # if isempty(W) #then - # W = expv; - # else - # W = hcat(W, expv); - # end #if - # end #for - # W = [W[:,i] for i in 1:size(W,2)] - # # ?transpose? hcat - ## println("W is $(W)"); + end + end LTs = gens(leading_ideal(I)); PPs = [PP(degrees(t)) for t in LTs]; - return HSNum(PPs, W, PivotStrategy); -end #function + return HSNum(PPs, W, pivot_strategy); +end From c3aa5060013f445b5eb1cab9a1313a5473668df4 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Tue, 8 Aug 2023 14:18:15 +0200 Subject: [PATCH 13/51] Fixed bug in ideal(0) short-cut --- src/Rings/mpoly-affine-algebras.jl | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index 6c4de56a8fc0..267bd296dab1 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -115,18 +115,19 @@ julia> hilbert_series(A) (-t^6 + 1, -t^6 + t^5 + t^4 - t^2 - t + 1) ``` """ -function hilbert_series(A::MPolyQuoRing; backend::Symbol=:Singular, algorithm::Symbol=:BayerStillmanA) +function hilbert_series(A::MPolyQuoRing; backend::Symbol=:Singular, algorithm::Symbol=:BayerStillmanA, parent::Union{Nothing,Ring}=nothing) R = base_ring(A.I) - if is_z_graded(R) || iszero(A.I) + if is_z_graded(R) && iszero(A.I) W = R.d W = [Int(W[i][1]) for i = 1:ngens(R)] @req minimum(W) > 0 "The weights must be positive" - Zt, t = ZZ["t"] + Zt,t = (parent === nothing) ? ZZ["t"] : (parent, first(gens(parent))); +# Zt, t = ZZ["t"] den = prod([1-t^Int(w[1]) for w in R.d]) return (one(parent(t)), den) end if backend == :Abbott - return Oscar.HSNum_fudge(A) + return Oscar.HSNum_fudge(A; parent=parent) elseif backend == :Singular H = HilbertData(A.I) return hilbert_series(H) From fad443418c575bef35c3e463c64d1f3e3c5b175e Mon Sep 17 00:00:00 2001 From: John Abbott Date: Tue, 8 Aug 2023 16:13:23 +0200 Subject: [PATCH 14/51] First implementation of hilbert series numerator for modules; more cleaning in the ideal code --- src/Modules/Modules.jl | 1 + src/Modules/hilbert.jl | 74 ++++ src/Rings/hilbert.jl | 828 +++++++++++++++++++++-------------------- 3 files changed, 498 insertions(+), 405 deletions(-) create mode 100644 src/Modules/hilbert.jl diff --git a/src/Modules/Modules.jl b/src/Modules/Modules.jl index 36d2eef8f135..2250bd9944a2 100644 --- a/src/Modules/Modules.jl +++ b/src/Modules/Modules.jl @@ -1,4 +1,5 @@ include("ModuleTypes.jl") +include("hilbert.jl") include("UngradedModules.jl") include("homological-algebra.jl") include("FreeModElem-orderings.jl") diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl new file mode 100644 index 000000000000..06d417cf7946 --- /dev/null +++ b/src/Modules/hilbert.jl @@ -0,0 +1,74 @@ +# This cannot be included in src/Rings/hilbert.jl because the include +# order defined in src/Oscar.jl includes ring stuff before module stuff +# (and it probably would not work if we inverted the inclusion order). + +# ================================================================== +# Hilbert series numerator for modules + +function _power_product(T, expv) + return prod([T[k]^expv[k] for k in 1:length(expv)]); +end + + +@doc raw""" + HSNum_module(M::SubquoModule) + +Compute numerator of Hilbert series of the subquotient `M`. +Result is a pair: `N, D` being the numerator `N` (as a laurent polynomial) and the denominator `D` as +a factor list of laurent polynomials. + +!!! note + Applied to a homogeneous subquotient `M`, the function first computes a Groebner basis to + obtain the leading term module; the rest of the computation uses this latter module + (sliced into ideals, one for each ambient free module component). + +# Examples +```jldoctest +julia> R, _ = polynomial_ring(QQ, ["x", "y", "z"]); + +julia> Z = abelian_group(0); + +julia> Rg, (x, y, z) = grade(R, [Z[1],Z[1],Z[1]]); + +julia> F = graded_free_module(Rg, 1); + +julia> A = Rg[x; y]; + +julia> B = Rg[x^2; y^3; z^4]; + +julia> M = SubquoModule(F, A, B) +-t^9 + t^7 + 2*t^6 - t^5 - t^3 - 2*t^2 + 2*t + +``` +""" +function HSNum_module(SubM::SubquoModule{T}) where T <: MPolyRingElem + C,phi = present_as_cokernel(SubM, :with_morphism); # phi is the morphism + LM = leading_module(C.quo); + F = ambient_free_module(C.quo); + rk = rank(F); + P = base_ring(C.quo); + GensLM = gens(LM); + L = [[] for _ in 1:rk]; # L[k] is list of monomial gens for k-th cooord + # Nested loop below extracts the coordinate monomial ideals -- is there a better way? + for g in GensLM + SR = coordinates(ambient_representative(g)); # should have length = 1 + for j in 1:rk + if SR[j] != 0 + push!(L[j], SR[j]); + end + end + end + IdealList = [ideal(P,G) for G in L]; +## HSeriesList = [hilbert_series(quo(P,I)[1])[1] for I in IdealList]; # hilbert_series returns (numer, denom) pair; we keep just the numer (as denom is not really interesting here) + HSeriesList = [HSNum_fudge(quo(P,I)[1]) for I in IdealList]; + shifts = [degree(phi(g)) for g in gens(F)]; + @vprintln :hilbert 1 "HSNum_module: shifts are $(shifts)"; + shift_expv = [gen_repr(d) for d in shifts]; + @vprintln :hilbert 1 "HSNum_module: shift_expv are $(shift_expv)"; + HSeriesRing = parent(HSeriesList[1]); + @vprintln :hilbert 1 "HSNum_module: HSeriesRing = $(HSeriesRing)"; + t = gens(HSeriesRing); + ScaleFactor = [_power_product(t,e) for e in shift_expv]; + result = sum([ScaleFactor[k]*HSeriesList[k] for k in 1:rk]); + return result; +end diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index aa47669afa3b..d6d4a0487890 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -11,35 +11,35 @@ function random_subset(n::Int64, m::Int64) # assume n >= 1, m >= 0 and m <= n if m == 0 - return Int64[]; + return Int64[] end - L = collect(1:n); + L = collect(1:n) if m == n - return L; + return L end for j in 1:m - k = rand(j:n); - L[j],L[k] = L[k],L[j]; # just a SWAP + k = rand(j:n) + L[j],L[k] = L[k],L[j] # just a SWAP end - L = first(L,m); - #?? sort!(L); ?? - return L; + L = first(L,m) + #?? sort!(L) ?? + return L end # There must be a better way...! # Split a "list" into 2 parts determined by a predicate. # Returns 2-tuple: list-of-sat-elems, list-of-unsat-elems function filter2(pred::Function, L::Vector) - sat = []; - unsat = []; + sat = [] + unsat = [] for x in L if pred(x) - push!(sat,x); + push!(sat,x) else - push!(unsat,x); + push!(unsat,x) end end - return sat,unsat; + return sat,unsat end @@ -57,11 +57,11 @@ PP_exponent = Int64; # UInt ??? Strange: Int32 was slower on my machine ?!? #= mutable =# struct PP - expv::Vector{PP_exponent}; + expv::Vector{PP_exponent} end function Base.copy(t::PP) - return PP(copy(t.expv)); + return PP(copy(t.expv)) end function length(t::PP) @@ -78,148 +78,147 @@ end # Should be is_one, but julia complained :-( function isone(t::PP) - return all(t.expv .== 0); + return all(t.expv .== 0) end function degree(t::PP) - return sum(t.expv); + return sum(t.expv) end # RETURN VALUE??? Perhaps index or 0? (or -1?) function is_simple_power_pp(t::PP) - CountNZ = 0; - for i in 1:length(t.expv) - @inbounds if (t.expv[i] == 0) continue; end - if (CountNZ > 0) return false; end - CountNZ = i; - end - if (CountNZ != 0) return true; end # MAYBE RETURN index & exp??? - return false; # because t == 1 + CountNZ = 0 + for i in 1:length(t) + @inbounds if (t[i] == 0) continue end + if (CountNZ > 0) return false end + CountNZ = i + end + if (CountNZ != 0) return true end # MAYBE RETURN index & exp??? + return false # because t == 1 end function is_divisible(t::PP, s::PP) # is t divisible by s - n = length(t.expv); # assume equal to length(s.expv); - for i in 1:n - @inbounds if t.expv[i] < s.expv[i] - return false; + nvars = length(t) # assume equal to length(s) + for i in 1:nvars + @inbounds if t[i] < s[i] + return false end end - return true; + return true end # modifies first arg function mult_by_var!(t::PP, j::Int64) - @inbounds t.expv[j] += 1; + @inbounds t.expv[j] += 1 end function mult(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) - return PP(t1.expv + t2.expv); + # ASSUMES: length(t1) == length(t2) + return PP(t1.expv + t2.expv) end function divide(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv), also that t1 is mult of t2 - return PP(t1.expv - t2.expv); + # ASSUMES: length(t1) == length(t2), also that t1 is mult of t2 + return PP(t1.expv - t2.expv) end function is_coprime(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) - n = length(t1.expv); - for i in 1:n - @inbounds if t1.expv[i] != 0 && t2.expv[i] != 0 - return false; + # ASSUMES: length(t1) == length(t2) + nvars = length(t1) + for i in 1:nvars + @inbounds if t1[i] != 0 && t2[i] != 0 + return false end end - return true; + return true end function lcm(t1::PP, t2::PP) # ASSUMES: length(t1.expv) == length(t2.expv) - n = length(t1.expv); - expv = [0 for _ in 1:n]; - for i in 1:n - @inbounds expv[i] = max(t1.expv[i], t2.expv[i]); + nvars = length(t1) + expv = [0 for _ in 1:nvars] + for i in 1:nvars + @inbounds expv[i] = max(t1[i], t2[i]) end - return PP(expv); + return PP(expv) end function gcd(t1::PP, t2::PP) # ASSUMES: length(t1.expv) == length(t2.expv) - n = length(t1.expv); - expv = [0 for _ in 1:n]; - for i in 1:n - @inbounds expv[i] = min(t1.expv[i], t2.expv[i]); + nvars = length(t1) + expv = [0 for _ in 1:nvars] + for i in 1:nnvars + @inbounds expv[i] = min(t1[i], t2[i]) end - return PP(expv); + return PP(expv) end function gcd3(t1::PP, t2::PP, t3::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) == length(t3.expv) - n = length(t1.expv); - expv = [0 for _ in 1:n]; - for i in 1:n - @inbounds expv[i] = min(t1.expv[i], t2.expv[i], t3.expv[i]); + # ASSUMES: length(t1) == length(t2) == length(t3) + nvars = length(t1) + expv = [0 for _ in 1:nvars] + for i in 1:nvars + @inbounds expv[i] = min(t1[i], t2[i], t3[i]) end - return PP(expv); + return PP(expv) end # Computes t1/gcd(t1,t2) function colon(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) - n = length(t1.expv); - expv = [0 for _ in 1:n]; - for i in 1:n - @inbounds expv[i] = max(0, t1.expv[i]-t2.expv[i]); + # ASSUMES: length(t1) == length(t2) + nvars = length(t1) + expv = [0 for _ in 1:nvars] + for i in 1:nvars + @inbounds expv[i] = max(0, t1[i]-t2[i]) end - return PP(expv); + return PP(expv) end # Computes t1/gcd(t1,t2^infty) function saturatePP(t1::PP, t2::PP) - # ASSUMES: length(t1.expv) == length(t2.expv) - n = length(t1.expv); - expv = [0 for _ in 1:n]; - for i in 1:n - @inbounds if t2.expv[i] == 0 -##OLD @inbounds expv[i] = t1.expv[i]; ## - @inbounds expv[i] = t1[i]; + # ASSUMES: length(t1) == length(t2) + nvars = length(t1) + expv = [0 for _ in 1:nvars] + for i in 1:nvars + @inbounds if t2[i] == 0 + @inbounds expv[i] = t1[i] end end - return PP(expv); + return PP(expv) end # Computes radical = product of vars which divide t function radical(t::PP) - n = length(t.expv); - expv = [0 for _ in 1:n]; - for i in 1:n - @inbounds if t.expv[i] > 0 - expv[i] = 1; + nvars = length(t) + expv = [0 for _ in 1:nvars] + for i in 1:nvars + @inbounds if t[i] > 0 + expv[i] = 1 end end - return PP(expv); + return PP(expv) end # Test if t1 < t1 in DegRevLex ordering; # NB if t1 == t2 then returns false. function deg_rev_lex_less(t1::PP, t2::PP) - d1 = sum(t1.expv); - d2 = sum(t2.expv); - if d1 != d2 return (d1 < d2); end - nvars = length(t1.expv); + d1 = degree(t1) + d2 = degree(t2) + if d1 != d2 return (d1 < d2) end + nvars = length(t1) for i in nvars:-1:1 - if t1.expv[i] != t2.expv[i] - return (t1.expv[i] > t2.expv[i]); + if t1[i] != t2[i] + return (t1[i] > t2[i]) end end - return false; + return false end @@ -227,31 +226,33 @@ end function involves(t::PP, IndexList::Vector{Int64}) # ASSUMES: indexes in IndexList are all in range for i in IndexList - @inbounds if t.expv[i] != 0 - return true; + @inbounds if t[i] != 0 + return true end end - return false; + return false end function Base.show(io::IO, t::PP) if all(t.expv .== 0) # isone(PP) # why doesn't this work ??? - print(io, "1"); - return; - end - str = ""; - n = length(t.expv); - for i in 1:n - if (t.expv[i] != 0) - if (str != "") str = str * "*"; end - str = str * "x[$(i)]"; - if (t.expv[i] > 1) - str = str * "^$(t.expv[i])"; + print(io, "1") + return + end + str = "" + nvars = length(t) + for i in 1:nvars + if (t[i] != 0) + if (str != "") + str = str * "*" + end + str = str * "x[$(i)]" + if (t[i] > 1) + str = str * "^$(t[i])" end end end - print(io, str); + print(io, str) end @@ -260,21 +261,21 @@ end # interreduce list of PPs; equiv find min set of gens for monoideal gen by L function interreduce(L::Vector{PP}) - sort!(L, by=degree); - MinGens = PP[]; + sort!(L, by=degree) + MinGens = PP[] for t in L - discard = false; + discard = false for s in MinGens if is_divisible(t,s) - discard = true; - break; + discard = true + break end end if !discard - push!(MinGens, t); + push!(MinGens, t) end end - return MinGens; + return MinGens end @@ -283,17 +284,17 @@ end function not_mult_of_any(L::Vector{PP}, t::PP) for s in L if is_divisible(t,s) - return false; + return false end end - return true; + return true end # "project" PP onto sub-monoid of PPs gen by indets in indexes function project_indets(t::PP, indexes::Vector{Int}) - return PP([t[k] for k in indexes]); + return PP([t[k] for k in indexes]) end @@ -321,49 +322,49 @@ end # Input: non-empty list of PPs # Output: list of lists of var indexes, each sublist is a connected component function connected_components(L::Vector{PP}) - ConnCompt::Vector{Vector{Int64}} = []; - nvars = length(L[1].expv); - IgnoreVar = [ false for _ in 1:nvars]; - VarAppears = copy(IgnoreVar); + ConnCompt::Vector{Vector{Int64}} = [] + nvars = length(L[1]) + IgnoreVar = [ false for _ in 1:nvars] + VarAppears = copy(IgnoreVar) for t in L for j in 1:nvars - @inbounds if t.expv[j] != 0 - @inbounds VarAppears[j] = true; + @inbounds if t[j] != 0 + @inbounds VarAppears[j] = true end end end - CountIgnore = 0; + CountIgnore = 0 for j in 1:nvars @inbounds if !VarAppears[j] - @inbounds IgnoreVar[j] = true; - CountIgnore += 1; + @inbounds IgnoreVar[j] = true + CountIgnore += 1 end end while CountIgnore < nvars - j = findfirst(!, IgnoreVar); # j is index of some var which appears in at least one PP - k = findfirst((t -> t.expv[j] != 0), L); # pick some PP involving j-th var - lcm = L[k]; - DoAnotherIteration = true; + j = findfirst(!, IgnoreVar) # j is index of some var which appears in at least one PP + k = findfirst((t -> t[j] != 0), L) # pick some PP involving j-th var + lcm = L[k] + DoAnotherIteration = true while DoAnotherIteration - DoAnotherIteration = false; + DoAnotherIteration = false for t in L - if is_coprime(lcm,t) continue; end - s = saturatePP(t,lcm); - if isone(s) continue; end - lcm = mult(lcm,s); ### lcm *= s; - DoAnotherIteration = true; + if is_coprime(lcm,t) continue end + s = saturatePP(t,lcm) + if isone(s) continue end + lcm = mult(lcm,s) ### lcm *= s + DoAnotherIteration = true end end #while - vars = filter((k -> lcm.expv[k] > 0), 1:nvars); + vars = filter((k -> lcm[k] > 0), 1:nvars) # remove conn compt just found from L??? - #seems to be slower with this line ?!? L = filter((t -> is_coprime(t,lcm)), L); - push!(ConnCompt, vars); + #seems to be slower with this line ?!? L = filter((t -> is_coprime(t,lcm)), L) + push!(ConnCompt, vars) for k in vars - IgnoreVar[k] = true; - CountIgnore += 1; + IgnoreVar[k] = true + CountIgnore += 1 end end #while - return ConnCompt; + return ConnCompt end @@ -386,12 +387,12 @@ end # Case gens are simple powers function HSNum_base_SimplePowers(SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T is list of HSNum PPs, one for each grading dim - ans = 1; #one(T[1]) ??? + ans = 1 #one(T[1]) ??? for t in SimplePPs - k = findfirst(entry -> (entry > 0), t.expv); - ans = ans * (1 - T[k]^t.expv[k]); # ???? ans -= ans*T[k]^t; + k = findfirst(entry -> (entry > 0), t.expv) + ans = ans * (1 - T[k]^t[k]) # ???? ans -= ans*T[k]^t[k] end - return ans; + return ans end @@ -399,76 +400,78 @@ function HSNum_base_case1(t::PP, SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T # t is not a "simple power", all the others are @vprintln :hilbert 1 "HSNum_base_case1: t = $(t)"; @vprintln :hilbert 1 "HSNum_base_case1: SimplePPs = $(SimplePPs)"; - ans = HSNum_base_SimplePowers(SimplePPs, T); - ReducedSimplePPs::Vector{PP} = []; + ans = HSNum_base_SimplePowers(SimplePPs, T) + ReducedSimplePPs::Vector{PP} = [] for j in 1:length(SimplePPs) - @inbounds e = SimplePPs[j].expv; - k = findfirst((entry -> (entry > 0)), e); # ispositive - if t.expv[k] == 0 push!(ReducedSimplePPs,SimplePPs[j]); continue; end # no need to make a copy - tt = copy(SimplePPs[j]); - tt.expv[k] -= t.expv[k]; # guaranteed > 0 - push!(ReducedSimplePPs, tt); - end - e = t.expv; - nvars = length(e); - @inbounds scale = prod([T[k]^e[k] for k in 1:nvars]); + @inbounds e = SimplePPs[j].expv + k = findfirst((entry -> (entry > 0)), e) # ispositive + if t[k] == 0 + push!(ReducedSimplePPs,SimplePPs[j]) + continue + end # no need to make a copy + tt = copy(SimplePPs[j]) + tt.expv[k] -= t[k] # guaranteed > 0 + push!(ReducedSimplePPs, tt) + end + nvars = length(t) + @inbounds scale = prod([T[k]^t[k] for k in 1:nvars]) ans = ans - scale * HSNum_base_SimplePowers(ReducedSimplePPs, T) @vprintln :hilbert 1 "HSNum_base_case1: returning $(ans)"; - return ans; + return ans end # function ## CC contains at least 2 connected components (each compt repr as Vector{Int64} of the variable indexes in the compt) function HSNum_splitting_case(CC::Vector{Vector{Int64}}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) @vprintln :hilbert 1 "Splitting case: CC = $(CC)"; - HSNumList = []; ## list of HSNums + HSNumList = [] ## list of HSNums # Now find any simple PPs which are indep of the conn compts found - nvars = length(NonSimplePPs[1].expv); - FoundVars = PP([0 for _ in 1:nvars]); # will be prod of all vars appearing in some conn compt + nvars = length(NonSimplePPs[1]) + FoundVars = PP([0 for _ in 1:nvars]) # will be prod of all vars appearing in some conn compt for IndexSet in CC for j in IndexSet - mult_by_var!(FoundVars, j); + mult_by_var!(FoundVars, j) end end - IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs); + IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs) for IndexSet in CC - SubsetNonSimplePPs = filter((t -> involves(t,IndexSet)), NonSimplePPs); - SubsetSimplePPs = filter((t -> involves(t,IndexSet)), SimplePPs); - push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetNonSimplePPs, T, PivotStrategy)); + SubsetNonSimplePPs = filter((t -> involves(t,IndexSet)), NonSimplePPs) + SubsetSimplePPs = filter((t -> involves(t,IndexSet)), SimplePPs) + push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetNonSimplePPs, T, PivotStrategy)) # Next 3 lines commented out: seemed to bring no benefit (??but why??) - #? SubsetNonSimplePPs = [project_indets(t, IndexSet) for t in SubsetNonSimplePPs]; - #? SubsetSimplePPs = [project_indets(t, IndexSet) for t in SubsetSimplePPs]; - #? push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetGens, [T[k] for k in IndexSet], PivotStrategy)); + #? SubsetNonSimplePPs = [project_indets(t, IndexSet) for t in SubsetNonSimplePPs] + #? SubsetSimplePPs = [project_indets(t, IndexSet) for t in SubsetSimplePPs] + #? push!(HSNumList, HSNum_loop(SubsetSimplePPs, SubsetGens, [T[k] for k in IndexSet], PivotStrategy)) end - HSNum_combined = prod(HSNumList); + HSNum_combined = prod(HSNumList) if !isempty(IsolatedSimplePPs) - HSNum_combined *= HSNum_base_SimplePowers(IsolatedSimplePPs, T); - ##OLD HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy); + HSNum_combined *= HSNum_base_SimplePowers(IsolatedSimplePPs, T) + ##OLD HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy) end - return HSNum_combined; + return HSNum_combined end function HSNum_total_splitting_case(VarIndexes::Vector{Int64}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) @vprintln :hilbert 1 "Total splitting case: VarIndexes = $(VarIndexes)"; - HSNumList = []; ## list of HSNums + HSNumList = [] ## list of HSNums # Now find any simple PPs which are indep of the conn compts found - nvars = length(NonSimplePPs[1].expv); - FoundVars = PP([0 for _ in 1:nvars]); # will be prod of all vars appearing in some conn compt + nvars = length(NonSimplePPs[1]) + FoundVars = PP([0 for _ in 1:nvars]) # will be prod of all vars appearing in some conn compt for i in VarIndexes - mult_by_var!(FoundVars, i); + mult_by_var!(FoundVars, i) end - IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs); + IsolatedSimplePPs = filter((t -> is_coprime(t,FoundVars)), SimplePPs) for t in NonSimplePPs - SubsetSimplePPs = filter((s -> !is_coprime(s,t)), SimplePPs); - push!(HSNumList, HSNum_base_case1(t, SubsetSimplePPs, T)); + SubsetSimplePPs = filter((s -> !is_coprime(s,t)), SimplePPs) + push!(HSNumList, HSNum_base_case1(t, SubsetSimplePPs, T)) end - HSNum_combined = prod(HSNumList); + HSNum_combined = prod(HSNumList) if !isempty(IsolatedSimplePPs) - HSNum_combined *= HSNum_base_SimplePowers(IsolatedSimplePPs, T); - ##OLD HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy); + HSNum_combined *= HSNum_base_SimplePowers(IsolatedSimplePPs, T) + ##OLD HSNum_combined *= HSNum_loop(IsolatedSimplePPs, PP[], T, PivotStrategy) end - return HSNum_combined; + return HSNum_combined end @@ -477,40 +480,42 @@ end # term-order corr to matrix with -1 on anti-diag: [[0,0,-1], [0,-1,0],...] function is_rev_lex_smaller(t1::PP, t2::PP) - n = length(t1); + n = length(t1) for j in n:-1:1 - if t1[j] != t2[j] return (t1[j] > t2[j]); end + if t1[j] != t2[j] + return (t1[j] > t2[j]) + end end - return false; # t1 and t2 were equal (should not happen in this code) + return false # t1 and t2 were equal (should not happen in this code) end function rev_lex_min(L::Vector{PP}) # assume length(L) > 0 - if isempty(L) return L[1]; end - IndexMin = 1; + if isempty(L) return L[1] end + IndexMin = 1 for j in 2:length(L) if !is_rev_lex_smaller(L[j], L[IndexMin]) - IndexMin = j; + IndexMin = j end end - return L[IndexMin]; + return L[IndexMin] end function rev_lex_max(L::Vector{PP}) # assume length(L) > 0 - if length(L) == 1 return L[1]; end - IndexMax = 1; + if length(L) == 1 return L[1] end + IndexMax = 1 for j in 2:length(L) if !is_rev_lex_smaller(L[IndexMax], L[j]) - IndexMax = j; + IndexMax = j end end - return L[IndexMax]; + return L[IndexMax] end function HSNum_bayer_stillman(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}) - ##println("HSNum_BS: Simple: $(SimplePPs)"); - ##println("HSNum_BS: NonSimple: $(NonSimplePPs)"); + ##println("HSNum_BS: Simple: $(SimplePPs)") + ##println("HSNum_BS: NonSimple: $(NonSimplePPs)") # Maybe sort the gens??? if isempty(NonSimplePPs) return HSNum_base_SimplePowers(SimplePPs, T) @@ -519,19 +524,19 @@ function HSNum_bayer_stillman(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T) end # NonSimplePPs contains at least 2 elements - # BSPivot = last(NonSimplePPs); # pick one somehow -- this is just simple to impl - #?? BSPivot = rev_lex_min(NonSimplePPs); # VERY SLOW on Hilbert-test-rnd6.jl - BSPivot = rev_lex_max(NonSimplePPs); - #VERY SLOW?!? BSPivot = rev_lex_max(vcat(SimplePPs,NonSimplePPs)); # VERY SLOW on Hilbert-test-rnd6.jl + # BSPivot = last(NonSimplePPs) # pick one somehow -- this is just simple to impl + #?? BSPivot = rev_lex_min(NonSimplePPs) # VERY SLOW on Hilbert-test-rnd6.jl + BSPivot = rev_lex_max(NonSimplePPs) + #VERY SLOW?!? BSPivot = rev_lex_max(vcat(SimplePPs,NonSimplePPs)) # VERY SLOW on Hilbert-test-rnd6.jl @vprintln :hilbert 2 "BSPivot = $(BSPivot)"; - NonSPP = filter((t -> (t != BSPivot)), NonSimplePPs); - SPP = SimplePPs;#filter((t -> (t != BSPivot)), SimplePPs); - part1 = HSNum_loop(SPP, NonSPP, T, :bayer_stillman); - ReducedPPs = interreduce([colon(t,BSPivot) for t in vcat(SPP,NonSPP)]); - NewSimplePPs, NewNonSimplePPs = SeparateSimplePPs(ReducedPPs); - part2 = HSNum_loop(NewSimplePPs, NewNonSimplePPs, T, :bayer_stillman); - e = BSPivot.expv; - return part1 - prod([T[k]^e[k] for k in 1:length(e)])*part2; + NonSPP = filter((t -> (t != BSPivot)), NonSimplePPs) + SPP = SimplePPs#filter((t -> (t != BSPivot)), SimplePPs) + part1 = HSNum_loop(SPP, NonSPP, T, :bayer_stillman) + ReducedPPs = interreduce([colon(t,BSPivot) for t in vcat(SPP,NonSPP)]) + NewSimplePPs, NewNonSimplePPs = SeparateSimplePPs(ReducedPPs) + part2 = HSNum_loop(NewSimplePPs, NewNonSimplePPs, T, :bayer_stillman) + nvars = length(BSPivot) + return part1 - prod([T[k]^BSPivot[k] for k in 1:nvars])*part2 end #-------------------------------------------- @@ -544,8 +549,8 @@ end # (part 2) corr freq function HSNum_most_freq_indets(gens::Vector{PP}) # ASSUMES: gens is non-empty - nvars = length(gens[1]); - freq = [0 for i in 1:nvars]; # Vector{Int} or Vector{UInt} ??? + nvars = length(gens[1]) + freq = [0 for i in 1:nvars] # Vector{Int} or Vector{UInt} ??? for t in gens for i in 1:nvars @inbounds if (t[i] != 0) @@ -553,209 +558,211 @@ function HSNum_most_freq_indets(gens::Vector{PP}) end end end - MaxFreq = maximum(freq); - MostFreq = findall((x -> x==MaxFreq), freq); - return MostFreq, MaxFreq; + MaxFreq = maximum(freq) + MostFreq = findall((x -> x==MaxFreq), freq) + return MostFreq, MaxFreq end # Returns index of the indet function HSNum_most_freq_indet1(gens::Vector{PP}) # ASSUMES: gens is non-empty - MostFreq,_ = HSNum_most_freq_indets(gens); - return MostFreq[1]; + MostFreq,_ = HSNum_most_freq_indets(gens) + return MostFreq[1] end # Returns index of the indet function HSNum_most_freq_indet_rnd(gens::Vector{PP}) # ASSUMES: gens is non-empty - MostFreq,_ = HSNum_most_freq_indets(gens); - return rand(MostFreq); + MostFreq,_ = HSNum_most_freq_indets(gens) + return rand(MostFreq) end # THIS PIVOT STRATEGY WAS AWFULLY SLOW! # function HSNum_choose_pivot_indet(MostFreq::Vector{Int64}, gens::Vector{PP}) -# PivotIndet = rand(MostFreq); ##HSNum_most_freq_indet_rnd(gens); # or HSNum_most_freq_indet1 -# nvars = length(gens[1].expv); -# PivotExpv = [0 for _ in 1:nvars]; -# PivotExpv[PivotIndet] = 1; -# PivotPP = PP(PivotExpv); +# PivotIndet = rand(MostFreq) ##HSNum_most_freq_indet_rnd(gens) # or HSNum_most_freq_indet1 +# nvars = length(gens[1]) +# PivotExpv = [0 for _ in 1:nvars] +# PivotExpv[PivotIndet] = 1 +# PivotPP = PP(PivotExpv) # end function HSNum_choose_pivot_simple_power_median(MostFreq::Vector{Int64}, gens::Vector{PP}) # variant of simple-power-pivot from Bigatti JPAA, 1997 - PivotIndet = rand(MostFreq); - exps = [t.expv[PivotIndet] for t in gens]; - exps = filter((e -> e>0), exps); - sort!(exps); - exp = exps[div(1+length(exps),2)]; # "median" - nvars = length(gens[1]); - PivotExpv = [0 for _ in 1:nvars]; - PivotExpv[PivotIndet] = exp; - PivotPP = PP(PivotExpv); + PivotIndet = rand(MostFreq) + exps = [t[PivotIndet] for t in gens] + exps = filter((e -> e>0), exps) + sort!(exps) + exp = exps[div(1+length(exps),2)] # "median" + nvars = length(gens[1]) + PivotExpv = [0 for _ in 1:nvars] + PivotExpv[PivotIndet] = exp + PivotPP = PP(PivotExpv) end function HSNum_choose_pivot_simple_power_max(MostFreq::Vector{Int64}, gens::Vector{PP}) # variant of simple-power-pivot from Bigatti JPAA, 1997 - PivotIndet = rand(MostFreq); - exps = [t.expv[PivotIndet] for t in gens]; - exp = max(exps...); - nvars = length(gens[1]); - PivotExpv = [0 for _ in 1:nvars]; - PivotExpv[PivotIndet] = exp; - PivotPP = PP(PivotExpv); + PivotIndet = rand(MostFreq) + exps = [t[PivotIndet] for t in gens] + exp = max(exps...) + nvars = length(gens[1]) + PivotExpv = [0 for _ in 1:nvars] + PivotExpv[PivotIndet] = exp + PivotPP = PP(PivotExpv) end function HSNum_choose_pivot_gcd2simple(MostFreq::Vector{Int64}, gens::Vector{PP}) # simple-power-pivot from Bigatti JPAA, 1997 - PivotIndet = rand(MostFreq); - cand = filter((t -> t.expv[PivotIndet]>0), gens); + PivotIndet = rand(MostFreq) + cand = filter((t -> t[PivotIndet]>0), gens) if length(cand) == 1 - error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! + error("Failed to detect total splitting case") ### !!SHOULD NEVER GET HERE!! end - nvars = length(gens[1]); - expv = [0 for _ in 1:nvars]; - expv[PivotIndet] = min(cand[1].expv[PivotIndet], cand[2].expv[PivotIndet]); - return PP(expv); + nvars = length(gens[1]) + expv = [0 for _ in 1:nvars] + expv[PivotIndet] = min(cand[1].expv[PivotIndet], cand[2].expv[PivotIndet]) + return PP(expv) end function HSNum_choose_pivot_gcd2max(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); - cand = filter((t -> t.expv[PivotIndet]>0), gens); + PivotIndet = rand(MostFreq) + cand = filter((t -> t[PivotIndet]>0), gens) if length(cand) == 1 - error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! + error("Failed to detect total splitting case") ### !!SHOULD NEVER GET HERE!! end - pick2 = [cand[k] for k in random_subset(length(cand),2)]; - t = gcd(pick2...); - nvars = length(t); - d = 0; for i in 1:nvars d = max(d,t.expv[i]); end + pick2 = [cand[k] for k in random_subset(length(cand),2)] + t = gcd(pick2...) + nvars = length(t) + d = maximum(t.expv) for i in 1:nvars if t[i] < d - t.expv[i]=0; + t.expv[i]=0 end end - return t; + return t end # # May produce a non-simple pivot!!! # function HSNum_choose_pivot_gcd3(MostFreq::Vector{Int64}, gens::Vector{PP}) -# PivotIndet = rand(MostFreq); -# cand = filter((t -> t.expv[PivotIndet]>0), gens); +# PivotIndet = rand(MostFreq) +# cand = filter((t -> t[PivotIndet]>0), gens) # if length(cand) == 1 -# error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! +# error("Failed to detect total splitting case") ### !!SHOULD NEVER GET HERE!! # end # if length(cand) == 2 -# return gcd(cand[1], cand[2]); +# return gcd(cand[1], cand[2]) # end -# pick3 = [cand[k] for k in random_subset(length(cand),3)]; -# return gcd3(pick3[1], pick3[2], pick3[3]); +# pick3 = [cand[k] for k in random_subset(length(cand),3)] +# return gcd3(pick3[1], pick3[2], pick3[3]) # end function HSNum_choose_pivot_gcd3simple(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); - cand = filter((t -> t.expv[PivotIndet]>0), gens); + PivotIndet = rand(MostFreq) + cand = filter((t -> t[PivotIndet]>0), gens) if length(cand) == 1 - error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! + error("Failed to detect total splitting case") ### !!SHOULD NEVER GET HERE!! end if length(cand) == 2 - t = gcd(cand[1], cand[2]); + t = gcd(cand[1], cand[2]) else - pick3 = [cand[k] for k in random_subset(length(cand),3)]; - t = gcd3(pick3[1], pick3[2], pick3[3]); - end - # println("BEFORE: t= $(t)"); - j = 1; - d = t.expv[1]; - for i in 2:length(t.expv) - if t.expv[i] <= d - t.expv[i] = 0; - continue; + pick3 = [cand[k] for k in random_subset(length(cand),3)] + t = gcd3(pick3...) ## t = gcd3(pick3[1], pick3[2], pick3[3]) + end + # t is gcd of 3 rnd PPs (or of 2 if there are only 2) + # Now set all exps to 0 except the first max one. + j = 1 + d = t[1] + for i in 2:length(t) + if t[i] <= d + t.expv[i] = 0 + continue end - t.expv[j] = 0; - j=i; - d = t.expv[i]; + t.expv[j] = 0 + j = i + d = t[i] end - # println("AFTER: t= $(t)"); - return t; + return t end + function HSNum_choose_pivot_gcd3max(MostFreq::Vector{Int64}, gens::Vector{PP}) - PivotIndet = rand(MostFreq); - cand = filter((t -> t.expv[PivotIndet]>0), gens); + PivotIndet = rand(MostFreq) + cand = filter((t -> t[PivotIndet]>0), gens) if length(cand) == 1 - error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! + error("Failed to detect total splitting case") ### !!SHOULD NEVER GET HERE!! end if length(cand) == 2 - t = gcd(cand[1], cand[2]); + t = gcd(cand[1], cand[2]) else - pick3 = [cand[k] for k in random_subset(length(cand),3)]; - t = gcd3(pick3[1], pick3[2], pick3[3]); + pick3 = [cand[k] for k in random_subset(length(cand),3)] + t = gcd3(pick3...) ## t = gcd3(pick3[1], pick3[2], pick3[3]) end - d = 0; for i in 1:length(t.expv) d = max(d,t.expv[i]); end - for i in 1:length(t.expv) - if t.expv[i] < d - t.expv[i]=0; + d = maximum(t.expv) + # Now set to 0 all exps which are less than the max + for i in 1:length(t) + if t[i] < d + t.expv[i]=0 end end - return t; + return t end # # May produce a non-simple pivot!!! # function HSNum_choose_pivot_gcd4(MostFreq::Vector{Int64}, gens::Vector{PP}) -# PivotIndet = rand(MostFreq); -# cand = filter((t -> t.expv[PivotIndet]>0), gens); +# PivotIndet = rand(MostFreq) +# cand = filter((t -> t[PivotIndet]>0), gens) # if length(cand) == 1 -# error("Failed to detect total splitting case"); ### !!SHOULD NEVER GET HERE!! +# error("Failed to detect total splitting case") ### !!SHOULD NEVER GET HERE!! # end # if length(cand) == 2 -# return gcd(cand[1], cand[2]); +# return gcd(cand[1], cand[2]) # end # if length(cand) ==3 -# return gcd3(cand[1], cand[2], cand[3]); +# return gcd3(cand[1], cand[2], cand[3]) # end -# pick4 = [cand[k] for k in random_subset(length(cand),4)]; -# return gcd(gcd(pick4[1], pick4[2]), gcd(pick4[3],pick4[4])); +# pick4 = [cand[k] for k in random_subset(length(cand),4)] +# return gcd(gcd(pick4[1], pick4[2]), gcd(pick4[3],pick4[4])) # end # Assume SimplePPs+NonSimplePPs are interreduced function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) - @vprintln :hilbert 1 "HSNum_loop: SimplePPs=$(SimplePPs)"; - @vprintln :hilbert 1 "HSNum_loop: NonSimplePPs=$(NonSimplePPs)"; -# @vprintln :hilbert 1 "LOOP: first <=5 NonSimplePPs=$(first(NonSimplePPs,5))"; + @vprintln :hilbert 1 "HSNum_loop: SimplePPs=$(SimplePPs)" + @vprintln :hilbert 1 "HSNum_loop: NonSimplePPs=$(NonSimplePPs)" +# @vprintln :hilbert 1 "LOOP: first <=5 NonSimplePPs=$(first(NonSimplePPs,5))" # Check if we have base case 0 if isempty(NonSimplePPs) - @vprintln :hilbert 1 "HSNum_loop: --> delegate base case 0"; - return HSNum_base_SimplePowers(SimplePPs, T); + @vprintln :hilbert 1 "HSNum_loop: --> delegate base case 0" + return HSNum_base_SimplePowers(SimplePPs, T) end # Check if we have base case 1 if length(NonSimplePPs) == 1 - @vprintln :hilbert 1 "HSNum_loop: --> delegate base case 1"; - return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T); + @vprintln :hilbert 1 "HSNum_loop: --> delegate base case 1" + return HSNum_base_case1(NonSimplePPs[1], SimplePPs, T) end # ---------------------- - MostFreq,freq = HSNum_most_freq_indets(NonSimplePPs); + MostFreq,freq = HSNum_most_freq_indets(NonSimplePPs) if freq == 1 - return HSNum_total_splitting_case(MostFreq, SimplePPs, NonSimplePPs, T, PivotStrategy); + return HSNum_total_splitting_case(MostFreq, SimplePPs, NonSimplePPs, T, PivotStrategy) end if PivotStrategy == :bayer_stillman - return HSNum_bayer_stillman(SimplePPs, NonSimplePPs, T); + return HSNum_bayer_stillman(SimplePPs, NonSimplePPs, T) end # Check for "splitting case" - if length(NonSimplePPs) <= length(NonSimplePPs[1].expv)#=nvars=# - CC = connected_components(NonSimplePPs); + if length(NonSimplePPs) <= length(NonSimplePPs[1])#=nvars=# + CC = connected_components(NonSimplePPs) else CC = [] end if length(CC) > 1 - @vprintln :hilbert 1 "HSNum_loop: --> delegate Splitting case"; - return HSNum_splitting_case(CC, SimplePPs, NonSimplePPs, T, PivotStrategy); + @vprintln :hilbert 1 "HSNum_loop: --> delegate Splitting case" + return HSNum_splitting_case(CC, SimplePPs, NonSimplePPs, T, PivotStrategy) end # ---------------------- # Pivot case: first do the ideal sum, then do ideal quotient @@ -785,128 +792,133 @@ function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{ # PivotPP = HSNum_choose_pivot_gcd4(MostFreq, NonSimplePPs) # end # end - @vprintln :hilbert 1 "HSNum_loop: pivot = $(PivotPP)"; - PivotIsSimple = is_simple_power_pp(PivotPP); - PivotIndex = findfirst((e -> e>0), PivotPP.expv); # used only if PivotIsSimple == true - USE_SAFE_VERSION_SUM=false; + @vprintln :hilbert 1 "HSNum_loop: pivot = $(PivotPP)" + PivotIsSimple = is_simple_power_pp(PivotPP) + PivotIndex = findfirst((e -> e>0), PivotPP.expv) # used only if PivotIsSimple == true + USE_SAFE_VERSION_SUM=false if USE_SAFE_VERSION_SUM # Safe but slow version: just add new gen, then interreduce - RecurseSum = vcat(SimplePPs, NonSimplePPs); - push!(RecurseSum, PivotPP); - RecurseSum = interreduce(RecurseSum); - RecurseSum_SimplePPs_OLD, RecurseSum_NonSimplePPs_OLD = SeparateSimplePPs(RecurseSum); - RecurseSum_NonSimplePPs = RecurseSum_NonSimplePPs_OLD; - RecurseSum_SimplePPs = RecurseSum_SimplePPs_OLD; + RecurseSum = vcat(SimplePPs, NonSimplePPs) + push!(RecurseSum, PivotPP) + RecurseSum = interreduce(RecurseSum) + RecurseSum_SimplePPs_OLD, RecurseSum_NonSimplePPs_OLD = SeparateSimplePPs(RecurseSum) + RecurseSum_NonSimplePPs = RecurseSum_NonSimplePPs_OLD + RecurseSum_SimplePPs = RecurseSum_SimplePPs_OLD else # use "clever" version: # We know that SimplePPs + NonSimplePPs are already interreduced if PivotIsSimple - RecurseSum_SimplePPs_NEW = copy(SimplePPs); - k = findfirst((t -> t.expv[PivotIndex] > 0), SimplePPs); + RecurseSum_SimplePPs_NEW = copy(SimplePPs) + k = findfirst((t -> t[PivotIndex] > 0), SimplePPs) if k === nothing - push!(RecurseSum_SimplePPs_NEW, PivotPP); + push!(RecurseSum_SimplePPs_NEW, PivotPP) else - RecurseSum_SimplePPs_NEW[k] = PivotPP; + RecurseSum_SimplePPs_NEW[k] = PivotPP end - RecurseSum_NonSimplePPs_NEW = filter((t -> t.expv[PivotIndex] < PivotPP.expv[PivotIndex]), NonSimplePPs); + RecurseSum_NonSimplePPs_NEW = filter((t -> t[PivotIndex] < PivotPP[PivotIndex]), NonSimplePPs) else # PivotPP is not simple -- so this is the "general case" - RecurseSum_SimplePPs_NEW = copy(SimplePPs); # need to copy? - RecurseSum_NonSimplePPs_NEW = filter((t -> !is_divisible(t,PivotPP)), NonSimplePPs); - push!(RecurseSum_NonSimplePPs_NEW, PivotPP); + RecurseSum_SimplePPs_NEW = copy(SimplePPs) # need to copy? + RecurseSum_NonSimplePPs_NEW = filter((t -> !is_divisible(t,PivotPP)), NonSimplePPs) + push!(RecurseSum_NonSimplePPs_NEW, PivotPP) end - RecurseSum_NonSimplePPs = RecurseSum_NonSimplePPs_NEW; - RecurseSum_SimplePPs = RecurseSum_SimplePPs_NEW; + RecurseSum_NonSimplePPs = RecurseSum_NonSimplePPs_NEW + RecurseSum_SimplePPs = RecurseSum_SimplePPs_NEW end # USE_SAFE_VERSION_SUM # Now do the quotient... # Now get SimplePPs & NonSimplePPs for the quotient while limiting amount of interreduction - USE_SAFE_VERSION_QUOT = false; + USE_SAFE_VERSION_QUOT = false if USE_SAFE_VERSION_QUOT #=SAFE VERSION: simpler but probably slower=# - RecurseQuot = [colon(t,PivotPP) for t in vcat(SimplePPs,NonSimplePPs)]; - RecurseQuot = interreduce(RecurseQuot); - RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs = SeparateSimplePPs(RecurseQuot); + RecurseQuot = [colon(t,PivotPP) for t in vcat(SimplePPs,NonSimplePPs)] + RecurseQuot = interreduce(RecurseQuot) + RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs = SeparateSimplePPs(RecurseQuot) else # Use "smart version" if !PivotIsSimple # GENERAL CASE (e.g. if not PivotIsSimple) # Clever approach (for non-simple pivots) taken from Bigatti 1997 paper (p.247 just after Prop 1 to end of sect 5) # ???Maybe use filter2 (see start of this file) instead of loop below??? - BM = PP[]; NotBM = PP[]; - PivotPlus = mult(PivotPP, radical(PivotPP)); + BM = PP[] + NotBM = PP[] + PivotPlus = mult(PivotPP, radical(PivotPP)) for t in NonSimplePPs if is_divisible(t,PivotPlus) - push!(BM,t); + push!(BM,t) else - push!(NotBM,t); + push!(NotBM,t) end end # BM is short for "big multiple" (see Bigatti 97, p.247 between Prop 1 and Prop 2) - BM = [divide(t,PivotPP) for t in BM]; # divide is same as colon here - NotBM = vcat(NotBM, SimplePPs); - NotBM_mixed = PP[]; NotBM_coprime = PP[]; + BM = [divide(t,PivotPP) for t in BM] # divide is same as colon here + NotBM = vcat(NotBM, SimplePPs) + NotBM_mixed = PP[] + NotBM_coprime = PP[] for t in NotBM if is_coprime(t,PivotPP) - push!(NotBM_coprime,t); + push!(NotBM_coprime,t) else - push!(NotBM_mixed, colon(t,PivotPP)); + push!(NotBM_mixed, colon(t,PivotPP)) end end - # At this poiint we have 3 disjoint lists of PPs: BM (big multiples), NotBM_coprime, NotBM_mixed + # At this poiint we have 3 disjoint lists of PPs: + # BM (big multiples), + # NotBM_coprime, + # NotBM_mixed # In all cases the PPs have been colon-ed by PivotPP - NotBM_mixed = interreduce(NotBM_mixed); # cannot easily be "clever" here - filter!((t -> not_mult_of_any(NotBM_mixed,t)), NotBM_coprime); - RecurseQuot = vcat(NotBM_coprime, NotBM_mixed); # already interreduced - RQ_SimplePPs, RQ_NonSimplePPs = SeparateSimplePPs(RecurseQuot); - RecurseQuot_SimplePPs = RQ_SimplePPs; - RecurseQuot_NonSimplePPs = vcat(BM, RQ_NonSimplePPs); + NotBM_mixed = interreduce(NotBM_mixed) # cannot easily be "clever" here + filter!((t -> not_mult_of_any(NotBM_mixed,t)), NotBM_coprime) + RecurseQuot = vcat(NotBM_coprime, NotBM_mixed) # already interreduced + RQ_SimplePPs, RQ_NonSimplePPs = SeparateSimplePPs(RecurseQuot) + RecurseQuot_SimplePPs = RQ_SimplePPs + RecurseQuot_NonSimplePPs = vcat(BM, RQ_NonSimplePPs) else # Clever approach when PivotIsSimple # The idea behind this code is fairly simple; sadly the code itself is not :-( - RecurseQuot_SimplePPs = copy(SimplePPs); - k = findfirst((t -> t.expv[PivotIndex] > 0), RecurseQuot_SimplePPs); + RecurseQuot_SimplePPs = copy(SimplePPs) + k = findfirst((t -> t[PivotIndex] > 0), RecurseQuot_SimplePPs) if !(k === nothing) - RecurseQuot_SimplePPs[k] = copy(RecurseQuot_SimplePPs[k]); - RecurseQuot_SimplePPs[k].expv[PivotIndex] -= PivotPP.expv[PivotIndex]; + RecurseQuot_SimplePPs[k] = copy(RecurseQuot_SimplePPs[k]) + RecurseQuot_SimplePPs[k].expv[PivotIndex] -= PivotPP[PivotIndex] end - DegPivot = degree(PivotPP); - NonSimpleTbl = [PP[] for _ in 0:DegPivot]; ## WARNING: indexes are offset by 1 -- thanks Julia! - NonSimple1 = PP[]; # will contain all PPs divisible by PivotPP^(1+epsilon) + DegPivot = degree(PivotPP) + NonSimpleTbl = [PP[] for _ in 0:DegPivot] ## WARNING: indexes are offset by 1 -- thanks Julia! + NonSimple1 = PP[] # will contain all PPs divisible by PivotPP^(1+epsilon) for t in NonSimplePPs - degt = t.expv[PivotIndex]; + degt = t[PivotIndex] if degt > DegPivot - push!(NonSimple1, divide(t, PivotPP)); + push!(NonSimple1, divide(t, PivotPP)) else - push!(NonSimpleTbl[degt+1], colon(t,PivotPP)); + push!(NonSimpleTbl[degt+1], colon(t,PivotPP)) end end - NonSimple2 = NonSimpleTbl[DegPivot+1]; + NonSimple2 = NonSimpleTbl[DegPivot+1] for i in DegPivot:-1:1 - NewPPs = filter((t -> not_mult_of_any(NonSimple2,t)), NonSimpleTbl[i]); - NonSimple2 = vcat(NonSimple2, NewPPs); + NewPPs = filter((t -> not_mult_of_any(NonSimple2,t)), NonSimpleTbl[i]) + NonSimple2 = vcat(NonSimple2, NewPPs) end - NewSimplePPs = filter(is_simple_power_pp, NonSimple2); ## Use instead - NonSimple2 = filter(!is_simple_power_pp, NonSimple2); ## SeparateSimplePPs??? + NewSimplePPs = filter(is_simple_power_pp, NonSimple2) ## Use instead + NonSimple2 = filter(!is_simple_power_pp, NonSimple2) ## SeparateSimplePPs??? if !isempty(NewSimplePPs) - RecurseQuot_SimplePPs = interreduce(vcat(RecurseQuot_SimplePPs, NewSimplePPs)); + RecurseQuot_SimplePPs = interreduce(vcat(RecurseQuot_SimplePPs, NewSimplePPs)) end - RecurseQuot_NonSimplePPs = vcat(NonSimple1, NonSimple2); + RecurseQuot_NonSimplePPs = vcat(NonSimple1, NonSimple2) end #PivotIsSimple end # USE_SAFE_VERSION_QUOT # Now put the two pieces together: - nvars = length(PivotPP.expv); - scale = prod([T[k]^PivotPP.expv[k] for k in 1:nvars]); + nvars = length(PivotPP) + scale = prod([T[k]^PivotPP[k] for k in 1:nvars]) @vprintln :hilbert 2 "HSNum_loop: SUM recursion: simple $(RecurseSum_SimplePPs)"; @vprintln :hilbert 2 "HSNum_loop: SUM recursion: nonsimple $(RecurseSum_NonSimplePPs)"; @vprintln :hilbert 2 "HSNum_loop: QUOT recursion: simple $(RecurseQuot_SimplePPs)"; @vprintln :hilbert 2 "HSNum_loop: QUOT recursion: nonsimple $(RecurseQuot_NonSimplePPs)"; - HSNum_sum = HSNum_loop(RecurseSum_SimplePPs, RecurseSum_NonSimplePPs, T, PivotStrategy); - HSNum_quot = HSNum_loop(RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs, T, PivotStrategy); + HSNum_sum = HSNum_loop(RecurseSum_SimplePPs, RecurseSum_NonSimplePPs, T, PivotStrategy) + HSNum_quot = HSNum_loop(RecurseQuot_SimplePPs, RecurseQuot_NonSimplePPs, T, PivotStrategy) @vprintln :hilbert 1 "HSNum_loop: END OF CALL"; - return HSNum_sum + scale*HSNum_quot; + return HSNum_sum + scale*HSNum_quot end function separate_simple_pps(gens::Vector{PP}) - SimplePPs::Vector{PP} = []; - NonSimplePPs::Vector{PP} = []; + SimplePPs::Vector{PP} = [] + NonSimplePPs::Vector{PP} = [] for g in gens if is_simple_power_pp(g) push!(SimplePPs, g) @@ -914,7 +926,7 @@ function separate_simple_pps(gens::Vector{PP}) push!(NonSimplePPs, g) end end - return SimplePPs, NonSimplePPs; + return SimplePPs, NonSimplePPs end @@ -924,11 +936,11 @@ function HSNum_check_args(gens::Vector{PP}, W::Vector{Vector{Int}}) throw("HSNum: need at least 1 generator"); end if isempty(W) - throw("HSNum: weight matrix must have at least 1 row"); + throw("HSNum: weight matrix must have at least 1 row") end - nvars = length(gens[1].expv); - if !all((t -> length(t.expv)==nvars), gens) - throw("HSNum: generators must all have same size exponent vectors"); + nvars = length(gens[1]) + if !all((t -> length(t)==nvars), gens) + throw("HSNum: generators must all have same size exponent vectors") end if !all((row -> length(row)==nvars), W) throw("HSNum: weight matrix must have 1 column for each variable") @@ -938,38 +950,41 @@ function HSNum_check_args(gens::Vector{PP}, W::Vector{Vector{Int}}) end -function HSNum(PP_gens::Vector{PP}, W::Vector{Vector{Int}}, PivotStrategy::Symbol) + +function HSNum(PP_gens::Vector{PP}, W::Vector{Vector{Int}}, PivotStrategy::Symbol; parent::Union{Nothing,Ring} = nothing) # ASSUME W is "rectangular" @vprintln :hilbert 1 "HSNum: PP_gens = $(PP_gens)"; @vprintln :hilbert 1 "HSNum: weight matrix W = $(W)"; - HSNum_check_args(PP_gens, W); #throws or does nothing + HSNum_check_args(PP_gens, W) #throws or does nothing # Grading is over ZZ^m - m = length(W); # NumRows - ncols = length(W[1]); - nvars = length(PP_gens[1]); + m = length(W) # NumRows + ncols = length(W[1]) + nvars = length(PP_gens[1]) if ncols != nvars - throw(ArgumentError("weights matrix has wrong number of columns ($(ncols)); should be same as number of variables ($(nvars))")); + throw(ArgumentError("weights matrix has wrong number of columns ($(ncols)); should be same as number of variables ($(nvars))")) end - HPRing, t = LaurentPolynomialRing(QQ, ["t$k" for k in 1:m]); - T = [one(HPRing) for k in 1:nvars]; + HPRingVarNames = (m==1) ? [:t] : [ _make_variable("t", k) for k in 1:m] #used only if parent == nothing + HPRing, t = (parent === nothing) ? LaurentPolynomialRing(QQ, HPRingVarNames) : (parent,gens(parent)) + @assert length(t) >= m "supplied Hilbert series ring contains too few variables" + T = [one(HPRing) for k in 1:nvars] for k in 1:nvars - s = one(HPRing); + s = one(HPRing) for j in 1:m - s *= t[j]^W[j][k]; + s *= t[j]^W[j][k] end - T[k] = s; + T[k] = s end # Now have T[1] = t1^W[1,1] * t2^W[2,1] * ..., etc - SimplePPs,NonSimplePPs = separate_simple_pps(interreduce(PP_gens)); - sort!(NonSimplePPs, lt=deg_rev_lex_less); # recommended by Bayer+Stillman (for their criterion) - return HSNum_loop(SimplePPs, NonSimplePPs, T, PivotStrategy); + SimplePPs,NonSimplePPs = separate_simple_pps(interreduce(PP_gens)) + sort!(NonSimplePPs, lt=deg_rev_lex_less) # recommended by Bayer+Stillman (for their criterion) + return HSNum_loop(SimplePPs, NonSimplePPs, T, PivotStrategy) end #----------------------------------------------------------------------------- # This fn copied from GradedModule.jl (in dir OSCAR/HILBERT/) function gen_repr(d) - grading_dim = length(gens(parent(d))); - return [d[k] for k in 1:grading_dim]; + grading_dim = length(gens(parent(d))) + return [d[k] for k in 1:grading_dim] end @doc raw""" @@ -993,8 +1008,8 @@ julia> I = ideal(R, [x^3+y^2*z, y^3+x*z^2, z^3+x^2*y]); julia> RmodI,_ = quo(R,I); -julia> HSNum_fudge(RmodI) --t1^9 + 3*t1^6 - 3*t1^3 + 1 +julia> Oscar.HSNum_fudge(RmodI) +-t^9 + 3*t^6 - 3*t^3 + 1 julia> R, (x,y,z) = graded_polynomial_ring(QQ, ["x", "y","z"], [1 2 3; 3 2 1]) (Graded multivariate polynomial ring in 3 variables over QQ, MPolyDecRingElem{QQFieldElem, QQMPolyRingElem}[x, y, z]) @@ -1004,24 +1019,27 @@ julia> I = ideal(R, [x*z+y^2, y^6+x^3*z^3, z^6, x^6]); julia> RmodI,_ = quo(R,I); julia> HSNum_fudge(RmodI) --t1^28*t2^28 + t1^24*t2^24 + t1^22*t2^10 - t1^18*t2^6 + t1^10*t2^22 - t1^6*t2^18 - t1^4*t2^4 + 1 +-t[1]^28*t[2]^28 + t[1]^24*t[2]^24 + t[1]^22*t[2]^10 - t[1]^18*t[2]^6 + t[1]^10*t[2]^22 - t[1]^6*t[2]^18 - t[1]^4*t[2]^4 + 1 ``` """ -function HSNum_fudge(PmodI::MPolyQuoRing; pivot_strategy::Symbol = :auto) - I = modulus(PmodI); - P = base_ring(I); - nvars = length(gens(P)); - grading_dim = length(gens(parent(degree(gen(P,1))))); # better way??? - weights = [degree(var) for var in gens(P)]; - W = [[0 for _ in 1:nvars] for _ in 1:grading_dim]; +function HSNum_fudge(PmodI::MPolyQuoRing; pivot_strategy::Symbol = :auto, parent::Union{Nothing,Ring} = nothing) + I = modulus(PmodI) + P = base_ring(I) + nvars = length(gens(P)) + grading_dim = length(gens(Oscar.parent(degree(gen(P,1))))) # better way??? + weights = [degree(var) for var in gens(P)] + W = [[0 for _ in 1:nvars] for _ in 1:grading_dim] for i in 1:nvars - expv = [Int64(exp) for exp in gen_repr(degree(gen(P,i)))]; + expv = [Int64(exp) for exp in gen_repr(degree(gen(P,i)))] for j in 1:grading_dim - W[j][i] = expv[j]; + W[j][i] = expv[j] end end - LTs = gens(leading_ideal(I)); - PPs = [PP(degrees(t)) for t in LTs]; - return HSNum(PPs, W, pivot_strategy); + LTs = gens(leading_ideal(I)) + PPs = [PP(degrees(t)) for t in LTs] + return HSNum(PPs, W, pivot_strategy; parent=parent) end + + + From 62d034afef0a3f46d9b1659dc931b02f45c807ee Mon Sep 17 00:00:00 2001 From: John Abbott Date: Wed, 9 Aug 2023 11:00:02 +0200 Subject: [PATCH 15/51] Fixed small typo --- src/Rings/mpoly-affine-algebras.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index 267bd296dab1..9eba89d0eb87 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -115,7 +115,7 @@ julia> hilbert_series(A) (-t^6 + 1, -t^6 + t^5 + t^4 - t^2 - t + 1) ``` """ -function hilbert_series(A::MPolyQuoRing; backend::Symbol=:Singular, algorithm::Symbol=:BayerStillmanA, parent::Union{Nothing,Ring}=nothing) +function hilbert_series(A::MPolyQuoRing; #=backend::Symbol=:Singular, algorithm::Symbol=:BayerStillmanA,=# parent::Union{Nothing,Ring}=nothing) R = base_ring(A.I) if is_z_graded(R) && iszero(A.I) W = R.d @@ -126,12 +126,12 @@ function hilbert_series(A::MPolyQuoRing; backend::Symbol=:Singular, algorithm::S den = prod([1-t^Int(w[1]) for w in R.d]) return (one(parent(t)), den) end - if backend == :Abbott +# if backend == :Abbott return Oscar.HSNum_fudge(A; parent=parent) - elseif backend == :Singular - H = HilbertData(A.I) - return hilbert_series(H) - end +# elseif backend == :Singular +# H = HilbertData(A.I) +# return hilbert_series(H) +# end end @@ -472,7 +472,7 @@ function multi_hilbert_series(A::MPolyQuoRing; algorithm::Symbol=:BayerStillmanA push_term!(B, 1, e) new_fac = 1-finish(B) if haskey(fac_dict, new_fac) - fac_dict[new_fac] = fact_dict[new_fac] + 1 + fac_dict[new_fac] = fac_dict[new_fac] + 1 else fac_dict[new_fac] = 1 end From 7893ac179c0d4d674d293d78bc7a5582df66bf71 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Wed, 9 Aug 2023 11:31:04 +0200 Subject: [PATCH 16/51] First step towards unifying hilbert_series interface --- src/Rings/mpoly-affine-algebras.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index 9eba89d0eb87..e7377f54ef40 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -126,8 +126,10 @@ function hilbert_series(A::MPolyQuoRing; #=backend::Symbol=:Singular, algorithm: den = prod([1-t^Int(w[1]) for w in R.d]) return (one(parent(t)), den) end + (numer,denom),(_,_) = multi_hilbert_series(A; parent=parent) + return numer,denom # if backend == :Abbott - return Oscar.HSNum_fudge(A; parent=parent) +# return Oscar.HSNum_fudge(A; parent=parent) # elseif backend == :Singular # H = HilbertData(A.I) # return hilbert_series(H) @@ -430,7 +432,7 @@ Codomain: G ``` """ -function multi_hilbert_series(A::MPolyQuoRing; algorithm::Symbol=:BayerStillmanA) +function multi_hilbert_series(A::MPolyQuoRing; algorithm::Symbol=:BayerStillmanA, parent::Union{Nothing,Ring}=nothing) R = base_ring(A) I = A.I @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" From 88b389b7683307e4fc3124f59308d6a6ff91d880 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Wed, 9 Aug 2023 11:42:13 +0200 Subject: [PATCH 17/51] Corrected doc; renamed local var --- src/Modules/hilbert.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index 06d417cf7946..29a2cdd2cb12 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -36,7 +36,9 @@ julia> A = Rg[x; y]; julia> B = Rg[x^2; y^3; z^4]; -julia> M = SubquoModule(F, A, B) +julia> M = SubquoModule(F, A, B); + +julia> Oscar.HSNum_module(M) -t^9 + t^7 + 2*t^6 - t^5 - t^3 - 2*t^2 + 2*t ``` @@ -45,21 +47,20 @@ function HSNum_module(SubM::SubquoModule{T}) where T <: MPolyRingElem C,phi = present_as_cokernel(SubM, :with_morphism); # phi is the morphism LM = leading_module(C.quo); F = ambient_free_module(C.quo); - rk = rank(F); + r = rank(F); P = base_ring(C.quo); GensLM = gens(LM); - L = [[] for _ in 1:rk]; # L[k] is list of monomial gens for k-th cooord + L = [[] for _ in 1:r]; # L[k] is list of monomial gens for k-th cooord # Nested loop below extracts the coordinate monomial ideals -- is there a better way? for g in GensLM SR = coordinates(ambient_representative(g)); # should have length = 1 - for j in 1:rk + for j in 1:r if SR[j] != 0 push!(L[j], SR[j]); end end end IdealList = [ideal(P,G) for G in L]; -## HSeriesList = [hilbert_series(quo(P,I)[1])[1] for I in IdealList]; # hilbert_series returns (numer, denom) pair; we keep just the numer (as denom is not really interesting here) HSeriesList = [HSNum_fudge(quo(P,I)[1]) for I in IdealList]; shifts = [degree(phi(g)) for g in gens(F)]; @vprintln :hilbert 1 "HSNum_module: shifts are $(shifts)"; @@ -69,6 +70,6 @@ function HSNum_module(SubM::SubquoModule{T}) where T <: MPolyRingElem @vprintln :hilbert 1 "HSNum_module: HSeriesRing = $(HSeriesRing)"; t = gens(HSeriesRing); ScaleFactor = [_power_product(t,e) for e in shift_expv]; - result = sum([ScaleFactor[k]*HSeriesList[k] for k in 1:rk]); + result = sum([ScaleFactor[k]*HSeriesList[k] for k in 1:r]); return result; end From 96ca8f626f9cf26dc4c8c9fe53d636f6df5b6217 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Wed, 9 Aug 2023 11:53:56 +0200 Subject: [PATCH 18/51] Added kwarg parent; corrected indentation --- src/Modules/hilbert.jl | 65 +++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index 29a2cdd2cb12..d112bfbeb3f2 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -6,16 +6,16 @@ # Hilbert series numerator for modules function _power_product(T, expv) - return prod([T[k]^expv[k] for k in 1:length(expv)]); + return prod([T[k]^expv[k] for k in 1:length(expv)]); end @doc raw""" HSNum_module(M::SubquoModule) + HSNum_module(M::SubquoModule; parent::Ring) -Compute numerator of Hilbert series of the subquotient `M`. -Result is a pair: `N, D` being the numerator `N` (as a laurent polynomial) and the denominator `D` as -a factor list of laurent polynomials. +Compute numerator of Hilbert series of the subquotient `M`. If the kwarg `parent` +is supplied the Hilbert series numerator is computed in the ring `parent`. !!! note Applied to a homogeneous subquotient `M`, the function first computes a Groebner basis to @@ -43,33 +43,34 @@ julia> Oscar.HSNum_module(M) ``` """ -function HSNum_module(SubM::SubquoModule{T}) where T <: MPolyRingElem - C,phi = present_as_cokernel(SubM, :with_morphism); # phi is the morphism - LM = leading_module(C.quo); - F = ambient_free_module(C.quo); - r = rank(F); - P = base_ring(C.quo); - GensLM = gens(LM); - L = [[] for _ in 1:r]; # L[k] is list of monomial gens for k-th cooord - # Nested loop below extracts the coordinate monomial ideals -- is there a better way? - for g in GensLM - SR = coordinates(ambient_representative(g)); # should have length = 1 - for j in 1:r - if SR[j] != 0 - push!(L[j], SR[j]); - end - end +function HSNum_module(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing) where T <: MPolyRingElem + C,phi = present_as_cokernel(SubM, :with_morphism); # phi is the morphism + LM = leading_module(C.quo); + F = ambient_free_module(C.quo); + r = rank(F); + P = base_ring(C.quo); + GensLM = gens(LM); + L = [[] for _ in 1:r]; # L[k] is list of monomial gens for k-th cooord + # Nested loop below extracts the coordinate monomial ideals -- is there a better way? + for g in GensLM + SR = coordinates(ambient_representative(g)); # should have length = 1 + for j in 1:r + if SR[j] != 0 + push!(L[j], SR[j]); + end end - IdealList = [ideal(P,G) for G in L]; - HSeriesList = [HSNum_fudge(quo(P,I)[1]) for I in IdealList]; - shifts = [degree(phi(g)) for g in gens(F)]; - @vprintln :hilbert 1 "HSNum_module: shifts are $(shifts)"; - shift_expv = [gen_repr(d) for d in shifts]; - @vprintln :hilbert 1 "HSNum_module: shift_expv are $(shift_expv)"; - HSeriesRing = parent(HSeriesList[1]); - @vprintln :hilbert 1 "HSNum_module: HSeriesRing = $(HSeriesRing)"; - t = gens(HSeriesRing); - ScaleFactor = [_power_product(t,e) for e in shift_expv]; - result = sum([ScaleFactor[k]*HSeriesList[k] for k in 1:r]); - return result; + end + IdealList = [ideal(P,G) for G in L]; + # If paerent === nothing, should we compute a HSNum for some ideal first then use its ring for all later calls? + HSeriesList = [HSNum_fudge(quo(P,I)[1]; parent=parent) for I in IdealList]; + shifts = [degree(phi(g)) for g in gens(F)]; + @vprintln :hilbert 1 "HSNum_module: shifts are $(shifts)"; + shift_expv = [gen_repr(d) for d in shifts]; + @vprintln :hilbert 1 "HSNum_module: shift_expv are $(shift_expv)"; + HSeriesRing = parent(HSeriesList[1]); + @vprintln :hilbert 1 "HSNum_module: HSeriesRing = $(HSeriesRing)"; + t = gens(HSeriesRing); + ScaleFactor = [_power_product(t,e) for e in shift_expv]; + result = sum([ScaleFactor[k]*HSeriesList[k] for k in 1:r]); + return result; end From 25f220526b8bf479c97fde9fca05d5abc707115e Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Wed, 9 Aug 2023 12:26:16 +0200 Subject: [PATCH 19/51] Get rid of restrictive types. --- src/Rings/hilbert.jl | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index d6d4a0487890..e0b426b1ad5e 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -47,9 +47,6 @@ end # Code for representing & manipulating PPs (power products, aka. monomials) # All this code is "local" to this file, & not exported! -# type alias -const HSNumVar = AbstractAlgebra.Generic.LaurentMPolyWrap{QQFieldElem, QQMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{QQFieldElem, QQMPolyRing}} - # Each PP is represented as Vector{PP_exponent} @@ -386,7 +383,7 @@ end # HS "indets": seems useful to have power-prods of them by the corr weights -- parameter T in the fn defns # Case gens are simple powers -function HSNum_base_SimplePowers(SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T is list of HSNum PPs, one for each grading dim +function HSNum_base_SimplePowers(SimplePPs::Vector{PP}, T::Vector{RingElemType}) where {RingElemType <: RingElem} # T is list of HSNum PPs, one for each grading dim ans = 1 #one(T[1]) ??? for t in SimplePPs k = findfirst(entry -> (entry > 0), t.expv) @@ -396,7 +393,7 @@ function HSNum_base_SimplePowers(SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T end -function HSNum_base_case1(t::PP, SimplePPs::Vector{PP}, T::Vector{HSNumVar}) # T is list of HSNum PPs, one for each grading dim +function HSNum_base_case1(t::PP, SimplePPs::Vector{PP}, T::Vector{RingElemType}) where {RingElemType <: RingElem} # T is list of HSNum PPs, one for each grading dim # t is not a "simple power", all the others are @vprintln :hilbert 1 "HSNum_base_case1: t = $(t)"; @vprintln :hilbert 1 "HSNum_base_case1: SimplePPs = $(SimplePPs)"; @@ -422,7 +419,7 @@ end # function ## CC contains at least 2 connected components (each compt repr as Vector{Int64} of the variable indexes in the compt) -function HSNum_splitting_case(CC::Vector{Vector{Int64}}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) +function HSNum_splitting_case(CC::Vector{Vector{Int64}}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{RET}, PivotStrategy::Symbol) where {RET <: RingElem} @vprintln :hilbert 1 "Splitting case: CC = $(CC)"; HSNumList = [] ## list of HSNums # Now find any simple PPs which are indep of the conn compts found @@ -452,7 +449,7 @@ function HSNum_splitting_case(CC::Vector{Vector{Int64}}, SimplePPs::Vector{PP}, end -function HSNum_total_splitting_case(VarIndexes::Vector{Int64}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) +function HSNum_total_splitting_case(VarIndexes::Vector{Int64}, SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{RET}, PivotStrategy::Symbol) where {RET <: RingElem} @vprintln :hilbert 1 "Total splitting case: VarIndexes = $(VarIndexes)"; HSNumList = [] ## list of HSNums # Now find any simple PPs which are indep of the conn compts found @@ -513,7 +510,7 @@ function rev_lex_max(L::Vector{PP}) return L[IndexMax] end -function HSNum_bayer_stillman(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}) +function HSNum_bayer_stillman(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{RET}) where {RET <: RingElem} ##println("HSNum_BS: Simple: $(SimplePPs)") ##println("HSNum_BS: NonSimple: $(NonSimplePPs)") # Maybe sort the gens??? @@ -732,7 +729,7 @@ end # Assume SimplePPs+NonSimplePPs are interreduced -function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{HSNumVar}, PivotStrategy::Symbol) +function HSNum_loop(SimplePPs::Vector{PP}, NonSimplePPs::Vector{PP}, T::Vector{RET}, PivotStrategy::Symbol) where {RET <: RingElem} @vprintln :hilbert 1 "HSNum_loop: SimplePPs=$(SimplePPs)" @vprintln :hilbert 1 "HSNum_loop: NonSimplePPs=$(NonSimplePPs)" # @vprintln :hilbert 1 "LOOP: first <=5 NonSimplePPs=$(first(NonSimplePPs,5))" From 536903ef1fe148287eb7751c50ca6bcab9f5eefb Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Wed, 9 Aug 2023 12:26:39 +0200 Subject: [PATCH 20/51] Clean up the multi_hilbert_series. --- src/Rings/mpoly-affine-algebras.jl | 152 ++++++++++++++++++----------- 1 file changed, 96 insertions(+), 56 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index e7377f54ef40..b9884bf40c12 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -136,6 +136,9 @@ function hilbert_series(A::MPolyQuoRing; #=backend::Symbol=:Singular, algorithm: # end end +# TODO: The method below is missing. It should be made better and put to the correct place (AA). +ngens(S::AbstractAlgebra.Generic.LaurentMPolyWrapRing) = length(gens(S)) + @doc raw""" hilbert_series_reduced(A::MPolyQuoRing) @@ -432,70 +435,107 @@ Codomain: G ``` """ -function multi_hilbert_series(A::MPolyQuoRing; algorithm::Symbol=:BayerStillmanA, parent::Union{Nothing,Ring}=nothing) - R = base_ring(A) - I = A.I - @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" - @req is_positively_graded(R) "The base ring must be positively graded" - - G = grading_group(R) - if !is_zm_graded(R) - H, iso = snf(G) - V = [preimage(iso, x) for x in gens(G)] - isoinv = hom(G, H, V) - W = R.d - W = [isoinv(W[i]) for i = 1:length(W)] - S, _ = graded_polynomial_ring(coefficient_ring(R), symbols(R), W) - change = hom(R, S, gens(S)) - I = change(A.I) - R = S - else - H, iso = G, identity_map(G) - end - m = ngens(grading_group(R)) - n = ngens(R) - W = R.d - MI = Matrix{Int}(undef, n, m) - for i=1:n - for j=1:m - MI[i, j] = Int(W[i][j]) - end - end - if m == 1 +function multi_hilbert_series( + A::MPolyQuoRing; + algorithm::Symbol=:BayerStillmanA, + backend::Symbol=:Abbott, + parent::Union{Nothing, Ring}=nothing + ) + R = base_ring(A) + I = modulus(A) + @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" + @req is_positively_graded(R) "The base ring must be positively graded" + + G = grading_group(R) + + # Wrap the case where G is abstractly isomorphic to ℤᵐ, but not realized as a + # free Abelian group. + # + # We use the Smith normal form to get there, recreate the graded ring with the + # free grading group, do the computation there and return the isomorphism for + # the grading. + if !is_zm_graded(R) + H, iso = snf(G) + V = [preimage(iso, x) for x in gens(G)] + isoinv = hom(G, H, V) + W = R.d + W = [isoinv(W[i]) for i = 1:length(W)] + S, _ = graded_polynomial_ring(coefficient_ring(R), symbols(R), W) + change = hom(R, S, gens(S)) + J = change(I) + AA, _ = quo(S, J) + change_res = hom(A, AA, gens(AA), check=false) + change_res_inv = hom(AA, A, gens(A), check=false) + (num, denom), _ = multi_hilbert_series(AA; algorithm, parent) + return (num, denom), (H, iso) + end + + # Now we may assume that the grading group is free Abelian. + m = ngens(grading_group(R)) + n = ngens(R) + if parent !== nothing + # The following line might complain in unforeseen ways in case + # the argument for `parent` was too unreasonable. However, we would + # like to keep the possibilities for that input rather broad. + @req ngens(parent) >= m "parent ring of the output does not contain sufficiently many variables" + else + # If the parent of the output was not specified, recreate it as a Laurent polynomial ring + if m == 1 VAR = [:t] - else + else VAR = [_make_variable("t", i) for i = 1:m] - end - S, _ = LaurentPolynomialRing(ZZ, VAR) - fac_dict = Dict{elem_type(S), Integer}() - for i = 1:n - e = [Int(MI[i, :][j]) for j = 1:m] - B = MPolyBuildCtx(S) - push_term!(B, 1, e) - new_fac = 1-finish(B) - if haskey(fac_dict, new_fac) - fac_dict[new_fac] = fac_dict[new_fac] + 1 - else - fac_dict[new_fac] = 1 - end - end - fac_denom = FacElem(S, fac_dict) - # Old method below without factorization; left for debugging - # q = one(S) + end + parent, _ = LaurentPolynomialRing(ZZ, VAR) + end + + # Extract a matrix from the grading group + W = R.d + MI = Matrix{Int}(undef, n, m) + for i=1:n + for j=1:m + MI[i, j] = Int(W[i][j]) + end + end + + # Prepare the denominator as a factorized element + fac_dict = Dict{elem_type(parent), Integer}() + for i = 1:n + e = [Int(MI[i, :][j]) for j = 1:m] + B = MPolyBuildCtx(parent) + push_term!(B, 1, e) + new_fac = 1-finish(B) + if haskey(fac_dict, new_fac) + fac_dict[new_fac] = fac_dict[new_fac] + 1 + else + fac_dict[new_fac] = 1 + end + end + fac_denom = FacElem(parent, fac_dict) + # Old method below without factorization; left for debugging + # q = one(parent) # for i = 1:n # e = [Int(MI[i, :][j]) for j = 1:m] - # B = MPolyBuildCtx(S) + # B = MPolyBuildCtx(parent) # push_term!(B, 1, e) # q = q*(1-finish(B)) # end # @assert _evaluate(fac_denom) == q - if iszero(I) - p = one(S) - else - LI = leading_ideal(I, ordering=degrevlex(gens(R))) - p = _numerator_monomial_multi_hilbert_series(LI, S, m, algorithm=algorithm) - end - return (p, fac_denom), (H, iso) + + # Shortcut for the trivial case + iszero(I) && return (one(parent), fac_denom), (G, identity_map(G)) + + # In general refer to internal methods for monomial ideals + # TODO: Shouldn't the ordering be adapted to the grading in some sense? + p = one(parent) + if backend == :Zach + LI = leading_ideal(I, ordering=degrevlex(gens(R))) + p = _numerator_monomial_multi_hilbert_series(LI, parent, m, algorithm=algorithm) + elseif backend == :Abbott + p = HSNum_fudge(A; parent) + else + error("backend not found") + end + return (p, fac_denom), (G, identity_map(G)) end # Helper function because the usual evaluation is broken for Laurent polynomials. From 2964068cc2101beb73e400fdb97297ccdb4154c0 Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Wed, 9 Aug 2023 12:27:02 +0200 Subject: [PATCH 21/51] Update tests. --- test/Rings/hilbert.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/Rings/hilbert.jl b/test/Rings/hilbert.jl index 9a4af766f93d..9859250cbe1d 100644 --- a/test/Rings/hilbert.jl +++ b/test/Rings/hilbert.jl @@ -57,4 +57,8 @@ I = ideal(P,G); PmodI, _ = quo(P,I); HS = multi_hilbert_series(PmodI); + num = HS[1][1] + S = parent(num) + HS2 = multi_hilbert_series(PmodI, parent=S, backend=:Zach) + @test HS2[1][1] == num end From fb8903d951d93d370246b844b35701be2be55fa1 Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Wed, 9 Aug 2023 13:45:44 +0200 Subject: [PATCH 22/51] Clean up the original hilbert_series. --- src/Rings/mpoly-affine-algebras.jl | 29 ++++++++++++++++------------- test/Rings/hilbert.jl | 11 +++++++++++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index b9884bf40c12..85d59df3d8a0 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -117,27 +117,24 @@ julia> hilbert_series(A) """ function hilbert_series(A::MPolyQuoRing; #=backend::Symbol=:Singular, algorithm::Symbol=:BayerStillmanA,=# parent::Union{Nothing,Ring}=nothing) R = base_ring(A.I) - if is_z_graded(R) && iszero(A.I) + @req is_z_graded(R) "ring must be graded by the integers" + parent, t = (parent === nothing) ? polynomial_ring(ZZ, "t") : (parent, first(gens(parent))); + if iszero(A.I) W = R.d W = [Int(W[i][1]) for i = 1:ngens(R)] @req minimum(W) > 0 "The weights must be positive" - Zt,t = (parent === nothing) ? ZZ["t"] : (parent, first(gens(parent))); -# Zt, t = ZZ["t"] den = prod([1-t^Int(w[1]) for w in R.d]) return (one(parent(t)), den) end - (numer,denom),(_,_) = multi_hilbert_series(A; parent=parent) + + (numer, denom), _ = multi_hilbert_series(A; parent=parent) return numer,denom -# if backend == :Abbott -# return Oscar.HSNum_fudge(A; parent=parent) -# elseif backend == :Singular -# H = HilbertData(A.I) -# return hilbert_series(H) -# end end # TODO: The method below is missing. It should be made better and put to the correct place (AA). ngens(S::AbstractAlgebra.Generic.LaurentMPolyWrapRing) = length(gens(S)) +ngens(S::AbstractAlgebra.Generic.LaurentPolyWrapRing) = 1 +ngens(P::PolyRing) = 1 @doc raw""" @@ -501,9 +498,15 @@ function multi_hilbert_series( fac_dict = Dict{elem_type(parent), Integer}() for i = 1:n e = [Int(MI[i, :][j]) for j = 1:m] - B = MPolyBuildCtx(parent) - push_term!(B, 1, e) - new_fac = 1-finish(B) + new_fac = one(parent) + if isone(length(e)) + # We can't use MPolyBuildCtx in the univariate case + new_fac = new_fac - first(gens(parent))^first(e) + else + B = MPolyBuildCtx(parent) + push_term!(B, 1, e) + new_fac = 1-finish(B) + end if haskey(fac_dict, new_fac) fac_dict[new_fac] = fac_dict[new_fac] + 1 else diff --git a/test/Rings/hilbert.jl b/test/Rings/hilbert.jl index 9859250cbe1d..e2cfdc8e7c4e 100644 --- a/test/Rings/hilbert.jl +++ b/test/Rings/hilbert.jl @@ -61,4 +61,15 @@ S = parent(num) HS2 = multi_hilbert_series(PmodI, parent=S, backend=:Zach) @test HS2[1][1] == num + + P2, x = graded_polynomial_ring(QQ, 5, "x", [1 1 1 1 1]) + G = P2.(G) + + I = ideal(P2,G); + PmodI, _ = quo(P2,I); + HS = hilbert_series(PmodI); + num = HS[1] + L, q = LaurentPolynomialRing(ZZ, "q") + HS2 = hilbert_series(PmodI, parent=L) + @test HS2[1] == evaluate(num, q) end From f1cb501da32b7aaa6fd6111b770fe31191f48ac3 Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Wed, 9 Aug 2023 14:29:28 +0200 Subject: [PATCH 23/51] Add some cleanup. --- src/Rings/mpoly-affine-algebras.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index 85d59df3d8a0..04d5f591dbd7 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -119,10 +119,10 @@ function hilbert_series(A::MPolyQuoRing; #=backend::Symbol=:Singular, algorithm: R = base_ring(A.I) @req is_z_graded(R) "ring must be graded by the integers" parent, t = (parent === nothing) ? polynomial_ring(ZZ, "t") : (parent, first(gens(parent))); + W = R.d + W = [Int(W[i][1]) for i = 1:ngens(R)] + @req minimum(W) > 0 "The weights must be positive" if iszero(A.I) - W = R.d - W = [Int(W[i][1]) for i = 1:ngens(R)] - @req minimum(W) > 0 "The weights must be positive" den = prod([1-t^Int(w[1]) for w in R.d]) return (one(parent(t)), den) end @@ -534,6 +534,7 @@ function multi_hilbert_series( LI = leading_ideal(I, ordering=degrevlex(gens(R))) p = _numerator_monomial_multi_hilbert_series(LI, parent, m, algorithm=algorithm) elseif backend == :Abbott + # TODO: Pass on the `algorithm` keyword argument also here. p = HSNum_fudge(A; parent) else error("backend not found") From 0588ca528f5f46a560dd20bbbcf88933167b8084 Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Wed, 9 Aug 2023 14:32:26 +0200 Subject: [PATCH 24/51] Update tests. --- test/Rings/hilbert.jl | 147 +++++++++++++++++++++++++++--------------- 1 file changed, 94 insertions(+), 53 deletions(-) diff --git a/test/Rings/hilbert.jl b/test/Rings/hilbert.jl index e2cfdc8e7c4e..6c0907e44948 100644 --- a/test/Rings/hilbert.jl +++ b/test/Rings/hilbert.jl @@ -1,58 +1,58 @@ @testset "Hilbert series" begin P, x = graded_polynomial_ring(QQ, 5, "x", [1 1 1 1 1; 1 -2 3 -4 5]); G = - [ - x[1]^30*x[2]^16*x[3]^7*x[4]^72*x[5]^31, - x[1]^11*x[2]^20*x[3]^29*x[4]^93*x[5]^5, - x[1]^5*x[2]^9*x[3]^43*x[4]^7*x[5]^97, - x[1]^57*x[2]^6*x[3]^7*x[4]^56*x[5]^37, - x[1]^22*x[2]^40*x[3]^27*x[4]^67*x[5]^18, - x[1]^5*x[2]^5*x[3]^7*x[4]^78*x[5]^81, - x[1]^85*x[2]^15*x[3]^12*x[4]^41*x[5]^37, - x[1]^13*x[2]^35*x[3]^2*x[4]^64*x[5]^84, - x[1]^31*x[2]^57*x[3]^12*x[4]^93*x[5]^6, - x[1]^37*x[2]^59*x[3]^88*x[4]^16*x[5]^7, - x[1]^47*x[2]^63*x[3]^32*x[4]^28*x[5]^41, - x[1]^18*x[2]^19*x[3]^95*x[4]^7*x[5]^73, - x[1]^37*x[2]^84*x[3]^11*x[4]^48*x[5]^32, - x[1]^85*x[2]*x[3]^73*x[4]^24*x[5]^31, - x[1]^59*x[2]^56*x[3]^41*x[4]^12*x[5]^50, - x[1]^58*x[2]^36*x[3]*x[4]^35*x[5]^89, - x[1]^71*x[2]^12*x[3]^36*x[4]^76*x[5]^25, - x[1]^58*x[2]^32*x[3]^85*x[4]^44*x[5], - x[1]^61*x[2]^81*x[3]^15*x[4]^59*x[5]^8, - x[1]^84*x[2]^49*x[3]^32*x[4]^52*x[5]^7, - x[1]^40*x[2]^3*x[3]^54*x[4]^39*x[5]^89, - x[1]^12*x[2]^52*x[3]^100*x[4]^49*x[5]^14, - x[1]^75*x[2]^78*x[3]^34*x[4]*x[5]^41, - x[1]^46*x[2]^22*x[3]^99*x[4]^49*x[5]^14, - x[1]^95*x[2]^11*x[3]^63*x[4]^52*x[5]^10, - x[1]^32*x[2]^47*x[3]^97*x[4]^32*x[5]^26, - x[1]^52*x[2]^64*x[3]^62*x[4]^13*x[5]^47, - x[1]^70*x[2]^46*x[3]^90*x[4]^13*x[5]^21, - x[1]^3*x[2]^67*x[3]^90*x[4]^45*x[5]^52, - x[1]^17*x[2]^100*x[3]^58*x[4]^62*x[5]^21, - x[1]^45*x[2]^54*x[3]^65*x[4]^64*x[5]^32, - x[1]^24*x[2]^85*x[3]^27*x[4]^49*x[5]^76, - x[1]^28*x[2]^83*x[3]^9*x[4]^97*x[5]^45, - x[1]^40*x[2]^52*x[3]^99*x[4]^27*x[5]^49, - x[1]^88*x[2]^36*x[3]^26*x[4]^30*x[5]^90, - x[1]^36*x[2]^68*x[3]^76*x[4]^81*x[5]^9, - x[1]^48*x[2]^25*x[3]^80*x[4]^40*x[5]^83, - x[1]^93*x[2]^14*x[3]^80*x[4]^89*x[5]^3, - x[1]^5*x[2]^61*x[3]^65*x[4]^65*x[5]^94, - x[1]^48*x[2]^91*x[3]^70*x[4]^66*x[5]^16, - x[1]^39*x[2]^79*x[3]^98*x[4]^2*x[5]^75, - x[1]^4*x[2]^60*x[3]^74*x[4]^56*x[5]^100, - x[1]^59*x[2]^100*x[3]^74*x[4]^51*x[5]^12, - x[1]^90*x[2]^61*x[3]^85*x[4]^43*x[5]^19, - x[1]^44*x[2]^97*x[3]^39*x[4]^27*x[5]^97, - x[1]^91*x[2]^37*x[3]^2*x[4]^97*x[5]^80, - x[1]^67*x[2]^44*x[3]^99*x[4]^5*x[5]^93, - x[1]^95*x[2]^93*x[3]^88*x[4]^30*x[5]^2, - x[1]^91*x[3]^84*x[4]^59*x[5]^76, - x[1]^62*x[2]^3*x[3]^96*x[4]^72*x[5]^84 - ]; + [ + x[1]^30*x[2]^16*x[3]^7*x[4]^72*x[5]^31, + x[1]^11*x[2]^20*x[3]^29*x[4]^93*x[5]^5, + x[1]^5*x[2]^9*x[3]^43*x[4]^7*x[5]^97, + x[1]^57*x[2]^6*x[3]^7*x[4]^56*x[5]^37, + x[1]^22*x[2]^40*x[3]^27*x[4]^67*x[5]^18, + x[1]^5*x[2]^5*x[3]^7*x[4]^78*x[5]^81, + x[1]^85*x[2]^15*x[3]^12*x[4]^41*x[5]^37, + x[1]^13*x[2]^35*x[3]^2*x[4]^64*x[5]^84, + x[1]^31*x[2]^57*x[3]^12*x[4]^93*x[5]^6, + x[1]^37*x[2]^59*x[3]^88*x[4]^16*x[5]^7, + x[1]^47*x[2]^63*x[3]^32*x[4]^28*x[5]^41, + x[1]^18*x[2]^19*x[3]^95*x[4]^7*x[5]^73, + x[1]^37*x[2]^84*x[3]^11*x[4]^48*x[5]^32, + x[1]^85*x[2]*x[3]^73*x[4]^24*x[5]^31, + x[1]^59*x[2]^56*x[3]^41*x[4]^12*x[5]^50, + x[1]^58*x[2]^36*x[3]*x[4]^35*x[5]^89, + x[1]^71*x[2]^12*x[3]^36*x[4]^76*x[5]^25, + x[1]^58*x[2]^32*x[3]^85*x[4]^44*x[5], + x[1]^61*x[2]^81*x[3]^15*x[4]^59*x[5]^8, + x[1]^84*x[2]^49*x[3]^32*x[4]^52*x[5]^7, + x[1]^40*x[2]^3*x[3]^54*x[4]^39*x[5]^89, + x[1]^12*x[2]^52*x[3]^100*x[4]^49*x[5]^14, + x[1]^75*x[2]^78*x[3]^34*x[4]*x[5]^41, + x[1]^46*x[2]^22*x[3]^99*x[4]^49*x[5]^14, + x[1]^95*x[2]^11*x[3]^63*x[4]^52*x[5]^10, + x[1]^32*x[2]^47*x[3]^97*x[4]^32*x[5]^26, + x[1]^52*x[2]^64*x[3]^62*x[4]^13*x[5]^47, + x[1]^70*x[2]^46*x[3]^90*x[4]^13*x[5]^21, + x[1]^3*x[2]^67*x[3]^90*x[4]^45*x[5]^52, + x[1]^17*x[2]^100*x[3]^58*x[4]^62*x[5]^21, + x[1]^45*x[2]^54*x[3]^65*x[4]^64*x[5]^32, + x[1]^24*x[2]^85*x[3]^27*x[4]^49*x[5]^76, + x[1]^28*x[2]^83*x[3]^9*x[4]^97*x[5]^45, + x[1]^40*x[2]^52*x[3]^99*x[4]^27*x[5]^49, + x[1]^88*x[2]^36*x[3]^26*x[4]^30*x[5]^90, + x[1]^36*x[2]^68*x[3]^76*x[4]^81*x[5]^9, + x[1]^48*x[2]^25*x[3]^80*x[4]^40*x[5]^83, + x[1]^93*x[2]^14*x[3]^80*x[4]^89*x[5]^3, + x[1]^5*x[2]^61*x[3]^65*x[4]^65*x[5]^94, + x[1]^48*x[2]^91*x[3]^70*x[4]^66*x[5]^16, + x[1]^39*x[2]^79*x[3]^98*x[4]^2*x[5]^75, + x[1]^4*x[2]^60*x[3]^74*x[4]^56*x[5]^100, + x[1]^59*x[2]^100*x[3]^74*x[4]^51*x[5]^12, + x[1]^90*x[2]^61*x[3]^85*x[4]^43*x[5]^19, + x[1]^44*x[2]^97*x[3]^39*x[4]^27*x[5]^97, + x[1]^91*x[2]^37*x[3]^2*x[4]^97*x[5]^80, + x[1]^67*x[2]^44*x[3]^99*x[4]^5*x[5]^93, + x[1]^95*x[2]^93*x[3]^88*x[4]^30*x[5]^2, + x[1]^91*x[3]^84*x[4]^59*x[5]^76, + x[1]^62*x[2]^3*x[3]^96*x[4]^72*x[5]^84 + ]; I = ideal(P,G); PmodI, _ = quo(P,I); @@ -61,7 +61,7 @@ S = parent(num) HS2 = multi_hilbert_series(PmodI, parent=S, backend=:Zach) @test HS2[1][1] == num - + P2, x = graded_polynomial_ring(QQ, 5, "x", [1 1 1 1 1]) G = P2.(G) @@ -73,3 +73,44 @@ HS2 = hilbert_series(PmodI, parent=L) @test HS2[1] == evaluate(num, q) end + +@testset "second round of Hilbert series" begin + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) + J = ideal(R, [y-x^2, z-x^3]) + I = homogenization(J, "w") + A, _ = quo(base_ring(I), I) + num, denom = hilbert_series(A) + S, t = LaurentPolynomialRing(ZZ, "t") + num2, denom2 = hilbert_series(A, parent=S) + Smult, (T,) = polynomial_ring(ZZ, ["t"]) + num3, denom3 = hilbert_series(A, parent=Smult) + @test num3 == evaluate(num, T) + @test num2 == evaluate(num, t) + + RR, (X, Y) = graded_polynomial_ring(QQ, ["X", "Y"], [-1, -1]) + JJ = ideal(RR, X^2 - Y^2) + A, _ = quo(base_ring(JJ), JJ) + (num, denom), _= multi_hilbert_series(A) + S, t = LaurentPolynomialRing(ZZ, "t") + (num2, denom2), _ = multi_hilbert_series(A, parent=S) + Smult, (T,) = polynomial_ring(ZZ, ["t"]) + (num3, denom3), _ = multi_hilbert_series(A, parent=Smult) + @test num == evaluate(num2, T) + @test num3 == evaluate(num2, t) + + G = free_abelian_group(2) + G, _ = quo(G, [G[1]-3*G[2]]) + RR, (X, Y) = graded_polynomial_ring(QQ, ["X", "Y"], [G[1], G[2]]) + JJ = ideal(RR, X^2 - Y^6) + A, _ = quo(base_ring(JJ), JJ) + (num, denom), (H, iso) = multi_hilbert_series(A) + @test is_free(H) && isone(rank(H)) + S, t = LaurentPolynomialRing(ZZ, "t") + (num2, denom2), (H, iso) = multi_hilbert_series(A, parent=S) + @test is_free(H) && isone(rank(H)) + Smult, (T,) = polynomial_ring(ZZ, ["t"]) + (num3, denom3), (H, iso) = multi_hilbert_series(A, parent=Smult) + @test is_free(H) && isone(rank(H)) + @test num == evaluate(num2, T) + @test num3 == evaluate(num2, first(gens(parent(num3)))) +end From 375c09c88b617df9d090cfa94c24c08274898afc Mon Sep 17 00:00:00 2001 From: John Abbott Date: Wed, 9 Aug 2023 14:44:11 +0200 Subject: [PATCH 25/51] Added test_throws --- test/Rings/hilbert.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/Rings/hilbert.jl b/test/Rings/hilbert.jl index 6c0907e44948..4c7a75dc98cc 100644 --- a/test/Rings/hilbert.jl +++ b/test/Rings/hilbert.jl @@ -94,9 +94,7 @@ end S, t = LaurentPolynomialRing(ZZ, "t") (num2, denom2), _ = multi_hilbert_series(A, parent=S) Smult, (T,) = polynomial_ring(ZZ, ["t"]) - (num3, denom3), _ = multi_hilbert_series(A, parent=Smult) - @test num == evaluate(num2, T) - @test num3 == evaluate(num2, t) + @test_throws DomainError multi_hilbert_series(A, parent=Smult) G = free_abelian_group(2) G, _ = quo(G, [G[1]-3*G[2]]) From d45ac8b662cc82f9353652db4d96b5fc283f7dcf Mon Sep 17 00:00:00 2001 From: John Abbott Date: Wed, 9 Aug 2023 17:31:46 +0200 Subject: [PATCH 26/51] Handle R^0 correctly; new UI to allow freemodule as arg --- src/Modules/hilbert.jl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index d112bfbeb3f2..c35b92d6b65a 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -49,6 +49,10 @@ function HSNum_module(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothi F = ambient_free_module(C.quo); r = rank(F); P = base_ring(C.quo); + # short-cut for module R^0 (otherwise defn if HSeriesRing below gives index error) + if iszero(r) + return HSNum_fudge(quo(P,ideal(P,[1]))[1]; parent=parent) + end GensLM = gens(LM); L = [[] for _ in 1:r]; # L[k] is list of monomial gens for k-th cooord # Nested loop below extracts the coordinate monomial ideals -- is there a better way? @@ -61,16 +65,21 @@ function HSNum_module(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothi end end IdealList = [ideal(P,G) for G in L]; - # If paerent === nothing, should we compute a HSNum for some ideal first then use its ring for all later calls? + # If parent === nothing, should we compute a HSNum for some ideal first then use its ring for all later calls? HSeriesList = [HSNum_fudge(quo(P,I)[1]; parent=parent) for I in IdealList]; shifts = [degree(phi(g)) for g in gens(F)]; @vprintln :hilbert 1 "HSNum_module: shifts are $(shifts)"; shift_expv = [gen_repr(d) for d in shifts]; @vprintln :hilbert 1 "HSNum_module: shift_expv are $(shift_expv)"; - HSeriesRing = parent(HSeriesList[1]); + HSeriesRing = Oscar.parent(HSeriesList[1]); @vprintln :hilbert 1 "HSNum_module: HSeriesRing = $(HSeriesRing)"; t = gens(HSeriesRing); ScaleFactor = [_power_product(t,e) for e in shift_expv]; result = sum([ScaleFactor[k]*HSeriesList[k] for k in 1:r]); return result; end + +function HSNum_module(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothing) where T <: MPolyRingElem + # ASSUME F is graded free module + return HSNum_module(sub(F,gens(F))[1]; parent=parent) +end From 92dd69c72dcc503351360b30d6841b850f947040 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Wed, 9 Aug 2023 17:33:31 +0200 Subject: [PATCH 27/51] Now allow R/ideal() as input; partial prototype for denom --- src/Rings/hilbert.jl | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index e0b426b1ad5e..81838e80f410 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -384,7 +384,7 @@ end # Case gens are simple powers function HSNum_base_SimplePowers(SimplePPs::Vector{PP}, T::Vector{RingElemType}) where {RingElemType <: RingElem} # T is list of HSNum PPs, one for each grading dim - ans = 1 #one(T[1]) ??? + ans = one(T[1]) for t in SimplePPs k = findfirst(entry -> (entry > 0), t.expv) ans = ans * (1 - T[k]^t[k]) # ???? ans -= ans*T[k]^t[k] @@ -929,20 +929,16 @@ end # Check args: either throws or returns nothing. function HSNum_check_args(gens::Vector{PP}, W::Vector{Vector{Int}}) - if isempty(gens) - throw("HSNum: need at least 1 generator"); - end if isempty(W) throw("HSNum: weight matrix must have at least 1 row") end - nvars = length(gens[1]) - if !all((t -> length(t)==nvars), gens) - throw("HSNum: generators must all have same size exponent vectors") - end + nvars = length(W[1]) if !all((row -> length(row)==nvars), W) throw("HSNum: weight matrix must have 1 column for each variable") end - # Zero weights are allowed??? + if !all((t -> length(t)==nvars), gens) # OK also if isempty(gens) + throw("HSNum: generators must all have same size exponent vectors") + end # Args are OK, so simply return (without throwing) end @@ -956,10 +952,10 @@ function HSNum(PP_gens::Vector{PP}, W::Vector{Vector{Int}}, PivotStrategy::Symbo # Grading is over ZZ^m m = length(W) # NumRows ncols = length(W[1]) - nvars = length(PP_gens[1]) - if ncols != nvars - throw(ArgumentError("weights matrix has wrong number of columns ($(ncols)); should be same as number of variables ($(nvars))")) - end + nvars = ncols + # if ncols != nvars + # throw(ArgumentError("weights matrix has wrong number of columns ($(ncols)); should be same as number of variables ($(nvars))")) + # end HPRingVarNames = (m==1) ? [:t] : [ _make_variable("t", k) for k in 1:m] #used only if parent == nothing HPRing, t = (parent === nothing) ? LaurentPolynomialRing(QQ, HPRingVarNames) : (parent,gens(parent)) @assert length(t) >= m "supplied Hilbert series ring contains too few variables" @@ -1040,3 +1036,23 @@ end +# ???Is the grading by rows or by cols??? +function hilbert_series_denominator(HSRing::Ring, W::Matrix{Int}) # S is PolynomialRing or LaurentPolynomialRing + n = nrows(W) + m = ncols(W) + @req length(gens(HSRing)) >= n "Hilbert series ring has too few variables" + fac_dict = Dict{elem_type(HSRing), Integer}() + for i = 1:n + e = [Int(W[i, :][j]) for j = 1:m] + B = MPolyBuildCtx(HSRing) + push_term!(B, 1, e) + new_fac = 1-finish(B) + if haskey(fac_dict, new_fac) + fac_dict[new_fac] = fac_dict[new_fac] + 1 + else + fac_dict[new_fac] = 1 + end + end + fac_denom = FacElem(HSRing, fac_dict) + return fac_denom +end From fda8825b3986dcfc6785035a77239ca1d40fcbb9 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Wed, 9 Aug 2023 17:34:42 +0200 Subject: [PATCH 28/51] Commented out short-cut for ideal(0) because it got denom wrong --- src/Rings/mpoly-affine-algebras.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index 04d5f591dbd7..eac63eaff9f7 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -122,10 +122,10 @@ function hilbert_series(A::MPolyQuoRing; #=backend::Symbol=:Singular, algorithm: W = R.d W = [Int(W[i][1]) for i = 1:ngens(R)] @req minimum(W) > 0 "The weights must be positive" - if iszero(A.I) - den = prod([1-t^Int(w[1]) for w in R.d]) - return (one(parent(t)), den) - end + # if iszero(A.I) + # den = prod([1-t^Int(w[1]) for w in R.d]) + # return (one(parent(t)), den) + # end (numer, denom), _ = multi_hilbert_series(A; parent=parent) return numer,denom From a6a3e0c3767584e302f0bef4db7639474e41f638 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 10:34:46 +0200 Subject: [PATCH 29/51] Renamed HSNum_fudge to HSNum_abbott --- src/Modules/hilbert.jl | 4 ++-- src/Rings/hilbert.jl | 8 ++++---- src/Rings/mpoly-affine-algebras.jl | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index c35b92d6b65a..0fc78cd271b6 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -51,7 +51,7 @@ function HSNum_module(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothi P = base_ring(C.quo); # short-cut for module R^0 (otherwise defn if HSeriesRing below gives index error) if iszero(r) - return HSNum_fudge(quo(P,ideal(P,[1]))[1]; parent=parent) + return HSNum_abbott(quo(P,ideal(P,[1]))[1]; parent=parent) end GensLM = gens(LM); L = [[] for _ in 1:r]; # L[k] is list of monomial gens for k-th cooord @@ -66,7 +66,7 @@ function HSNum_module(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothi end IdealList = [ideal(P,G) for G in L]; # If parent === nothing, should we compute a HSNum for some ideal first then use its ring for all later calls? - HSeriesList = [HSNum_fudge(quo(P,I)[1]; parent=parent) for I in IdealList]; + HSeriesList = [HSNum_abbott(quo(P,I)[1]; parent=parent) for I in IdealList]; shifts = [degree(phi(g)) for g in gens(F)]; @vprintln :hilbert 1 "HSNum_module: shifts are $(shifts)"; shift_expv = [gen_repr(d) for d in shifts]; diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index 81838e80f410..ace0d81de405 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -981,7 +981,7 @@ function gen_repr(d) end @doc raw""" - HSNum_fudge(A::MPolyQuoRing; pivot_strategy::Symbol = :auto) + HSNum_abbott(A::MPolyQuoRing; pivot_strategy::Symbol = :auto) Compute numerator of Hilbert series of the quotient `A`. Result is a pair: `N, D` being the numerator `N` (as a laurent polynomial) and the denominator `D` as @@ -1001,7 +1001,7 @@ julia> I = ideal(R, [x^3+y^2*z, y^3+x*z^2, z^3+x^2*y]); julia> RmodI,_ = quo(R,I); -julia> Oscar.HSNum_fudge(RmodI) +julia> Oscar.HSNum_abbott(RmodI) -t^9 + 3*t^6 - 3*t^3 + 1 julia> R, (x,y,z) = graded_polynomial_ring(QQ, ["x", "y","z"], [1 2 3; 3 2 1]) @@ -1011,12 +1011,12 @@ julia> I = ideal(R, [x*z+y^2, y^6+x^3*z^3, z^6, x^6]); julia> RmodI,_ = quo(R,I); -julia> HSNum_fudge(RmodI) +julia> Oscar.HSNum_abbott(RmodI) -t[1]^28*t[2]^28 + t[1]^24*t[2]^24 + t[1]^22*t[2]^10 - t[1]^18*t[2]^6 + t[1]^10*t[2]^22 - t[1]^6*t[2]^18 - t[1]^4*t[2]^4 + 1 ``` """ -function HSNum_fudge(PmodI::MPolyQuoRing; pivot_strategy::Symbol = :auto, parent::Union{Nothing,Ring} = nothing) +function HSNum_abbott(PmodI::MPolyQuoRing; pivot_strategy::Symbol = :auto, parent::Union{Nothing,Ring} = nothing) I = modulus(PmodI) P = base_ring(I) nvars = length(gens(P)) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index 0d9767d4ca03..214f6f612b46 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -638,7 +638,7 @@ function multi_hilbert_series( p = _numerator_monomial_multi_hilbert_series(LI, parent, m, algorithm=algorithm) elseif backend == :Abbott # TODO: Pass on the `algorithm` keyword argument also here. - p = HSNum_fudge(A; parent) + p = HSNum_abbott(A; parent) else error("backend not found") end From 3f0576fd211cde70031daa2dda0a6c23371efba3 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 13:26:11 +0200 Subject: [PATCH 30/51] First phase of refactoring --- src/Rings/hilbert.jl | 64 ++++++++++----- src/Rings/mpoly-affine-algebras.jl | 125 +++++++++++++++-------------- 2 files changed, 111 insertions(+), 78 deletions(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index ace0d81de405..bb66deeb3b51 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -1036,23 +1036,49 @@ end -# ???Is the grading by rows or by cols??? -function hilbert_series_denominator(HSRing::Ring, W::Matrix{Int}) # S is PolynomialRing or LaurentPolynomialRing - n = nrows(W) - m = ncols(W) - @req length(gens(HSRing)) >= n "Hilbert series ring has too few variables" - fac_dict = Dict{elem_type(HSRing), Integer}() - for i = 1:n - e = [Int(W[i, :][j]) for j = 1:m] - B = MPolyBuildCtx(HSRing) - push_term!(B, 1, e) - new_fac = 1-finish(B) - if haskey(fac_dict, new_fac) - fac_dict[new_fac] = fac_dict[new_fac] + 1 - else - fac_dict[new_fac] = 1 - end - end - fac_denom = FacElem(HSRing, fac_dict) - return fac_denom +# W[k] is ZZ^m-grading of x[k] +# Expect that HSRing is PolynomialRing or LaurentPolynomialRing +function _hilbert_series_denominator(HSRing::Ring, W::Vector{Vector{Int}}) + n = length(W) + m = length(W[1]) + @req all(r -> length(r)==m, W) "Grading VecVec must be rectangular" + @req length(gens(HSRing)) >= m "Hilbert series ring has too few variables" + fac_dict = Dict{elem_type(HSRing), Integer}() + for i = 1:n + # adjoin factor 1 - prod(t_j^W[i,j]) + B = MPolyBuildCtx(HSRing) + push_term!(B, 1, W[i]) + new_fac = 1-finish(B) + if haskey(fac_dict, new_fac) + fac_dict[new_fac] += 1 # coalesce identical factors + else + fac_dict[new_fac] = 1 + end + end + return FacElem(HSRing, fac_dict) +end + + +function _hilbert_series_ring(parent::Union{Nothing, Ring}, m::Int) + if parent !== nothing + # Caller supplied a Ring; check that it is reasonable. + # The following line might complain in unforeseen ways in case + # the argument for `parent` was too unreasonable. However, we would + # like to keep the possibilities for that input rather broad. + @req ngens(parent) >= m "parent ring of the output does not contain sufficiently many variables" + return parent + end + # Caller did not supply a Ring, so we create one. + if m == 1 + VAR = [:t] + else + VAR = [_make_variable("t", i) for i = 1:m] + end + HSRing, _ = LaurentPolynomialRing(ZZ, VAR) + return HSRing end + +# !!! Note +# We create a Laurent poly ring even if all weights are non-negative +# because negative exponents may be needed when dealing with modules +# with shifts. diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index 214f6f612b46..e9018dc3fbcd 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -471,7 +471,7 @@ end Return the Hilbert series of the positively graded affine algebra `A`. !!! note - The advanced user can select a `algorithm` for the computation; + The advanced user can select an `algorithm` for the computation; see the code for details. # Examples @@ -491,7 +491,8 @@ julia> H[1][1] -t[1]^7*t[2]^-2 + t[1]^6*t[2]^-1 + t[1]^6*t[2]^-2 + t[1]^5*t[2]^-4 - t[1]^4 + t[1]^4*t[2]^-2 - t[1]^4*t[2]^-4 - t[1]^3*t[2]^-1 - t[1]^3*t[2]^-2 + 1 julia> H[1][2] --t[1]^3*t[2]^-1 + t[1]^2 + 2*t[1]^2*t[2]^-1 - 2*t[1] - t[1]*t[2]^-1 + 1 +Factored element with data +Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{ZZRingElem, ZZMPolyRing}}, ZZRingElem}(-t[1] + 1 => 2, -t[1]*t[2]^-1 + 1 => 1) julia> H[2][1] GrpAb: Z^2 @@ -520,7 +521,8 @@ julia> H[1][1] 2*t^3 - 3*t^2 + 1 julia> H[1][2] -t^4 - 4*t^3 + 6*t^2 - 4*t + 1 +Factored element with data +Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{ZZRingElem, ZZMPolyRing}}, ZZRingElem}(-t + 1 => 4) julia> H[2][1] GrpAb: Z @@ -544,9 +546,7 @@ function multi_hilbert_series( R = base_ring(A) I = modulus(A) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" - @req is_positively_graded(R) "The base ring must be positively graded" - - G = grading_group(R) +## @req is_positively_graded(R) "The base ring must be positively graded" # Wrap the case where G is abstractly isomorphic to ℤᵐ, but not realized as a # free Abelian group. @@ -554,69 +554,76 @@ function multi_hilbert_series( # We use the Smith normal form to get there, recreate the graded ring with the # free grading group, do the computation there and return the isomorphism for # the grading. + G = grading_group(R) if !is_zm_graded(R) H, iso = snf(G) V = [preimage(iso, x) for x in gens(G)] isoinv = hom(G, H, V) - W = R.d - W = [isoinv(W[i]) for i = 1:length(W)] +# W = R.d + W = [isoinv(R.d[i]) for i = 1:length(W)] S, _ = graded_polynomial_ring(coefficient_ring(R), symbols(R), W) - change = hom(R, S, gens(S)) - J = change(I) + map_into_S = hom(R, S, gens(S)) + J = map_into_S(I) AA, _ = quo(S, J) - change_res = hom(A, AA, gens(AA), check=false) - change_res_inv = hom(AA, A, gens(A), check=false) +#?? change_res = hom(A, AA, gens(AA); check=false) +#?? change_res_inv = hom(AA, A, gens(A); check=false) (num, denom), _ = multi_hilbert_series(AA; algorithm, parent) return (num, denom), (H, iso) end # Now we may assume that the grading group is free Abelian. - m = ngens(grading_group(R)) + m = ngens(G) n = ngens(R) - if parent !== nothing - # The following line might complain in unforeseen ways in case - # the argument for `parent` was too unreasonable. However, we would - # like to keep the possibilities for that input rather broad. - @req ngens(parent) >= m "parent ring of the output does not contain sufficiently many variables" - else - # If the parent of the output was not specified, recreate it as a Laurent polynomial ring - if m == 1 - VAR = [:t] - else - VAR = [_make_variable("t", i) for i = 1:m] - end - parent, _ = LaurentPolynomialRing(ZZ, VAR) - end + HSRing = _hilbert_series_ring(parent, m) + println("HSRING = $(HSRing)") + # if parent !== nothing + # # The following line might complain in unforeseen ways in case + # # the argument for `parent` was too unreasonable. However, we would + # # like to keep the possibilities for that input rather broad. + # @req ngens(parent) >= m "parent ring of the output does not contain sufficiently many variables" + # else + # # If the parent of the output was not specified, recreate it as a Laurent polynomial ring + # if m == 1 + # VAR = [:t] + # else + # VAR = [_make_variable("t", i) for i = 1:m] + # end + # parent, _ = LaurentPolynomialRing(ZZ, VAR) + # end + # Get the weights as Int values: W[k] contain the weight(s) of x[k] + W = [[ Int(R.d[i][j]) for j in 1:m] for i in 1:n] # Extract a matrix from the grading group - W = R.d - MI = Matrix{Int}(undef, n, m) - for i=1:n - for j=1:m - MI[i, j] = Int(W[i][j]) - end - end + # W = R.d + # MI = Matrix{Int}(undef, n, m) + # for i=1:n + # for j=1:m + # MI[i, j] = Int(W[i][j]) + # end + # end + + fac_denom = _hilbert_series_denominator(HSRing, W) + # # Prepare the denominator as a factorized element + # fac_dict = Dict{elem_type(parent), Integer}() + # for i = 1:n + # e = [Int(MI[i, :][j]) for j = 1:m] + # new_fac = one(parent) + # if isone(length(e)) + # # We can't use MPolyBuildCtx in the univariate case + # new_fac = new_fac - first(gens(parent))^first(e) + # else + # B = MPolyBuildCtx(parent) + # push_term!(B, 1, e) + # new_fac = 1-finish(B) + # end + # if haskey(fac_dict, new_fac) + # fac_dict[new_fac] = fac_dict[new_fac] + 1 + # else + # fac_dict[new_fac] = 1 + # end + # end + # fac_denom = FacElem(parent, fac_dict) - # Prepare the denominator as a factorized element - fac_dict = Dict{elem_type(parent), Integer}() - for i = 1:n - e = [Int(MI[i, :][j]) for j = 1:m] - new_fac = one(parent) - if isone(length(e)) - # We can't use MPolyBuildCtx in the univariate case - new_fac = new_fac - first(gens(parent))^first(e) - else - B = MPolyBuildCtx(parent) - push_term!(B, 1, e) - new_fac = 1-finish(B) - end - if haskey(fac_dict, new_fac) - fac_dict[new_fac] = fac_dict[new_fac] + 1 - else - fac_dict[new_fac] = 1 - end - end - fac_denom = FacElem(parent, fac_dict) # Old method below without factorization; left for debugging # q = one(parent) # for i = 1:n @@ -632,17 +639,17 @@ function multi_hilbert_series( # In general refer to internal methods for monomial ideals # TODO: Shouldn't the ordering be adapted to the grading in some sense? - p = one(parent) + numer = one(HSRing) if backend == :Zach - LI = leading_ideal(I, ordering=degrevlex(gens(R))) - p = _numerator_monomial_multi_hilbert_series(LI, parent, m, algorithm=algorithm) + LI = leading_ideal(I; ordering=degrevlex(gens(R))) + numer = _numerator_monomial_multi_hilbert_series(LI, parent, m; algorithm=algorithm) elseif backend == :Abbott # TODO: Pass on the `algorithm` keyword argument also here. - p = HSNum_abbott(A; parent) + numer = HSNum_abbott(A; parent) else error("backend not found") end - return (p, fac_denom), (G, identity_map(G)) + return (numer, fac_denom), (G, identity_map(G)) end # Helper function because the usual evaluation is broken for Laurent polynomials. From 72289f21a070d1a6b18c08bde163f3276b49b447 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 14:42:35 +0200 Subject: [PATCH 31/51] Main fn is now called hilbert_series; cannot handle non ZZ^m grading --- src/Modules/hilbert.jl | 73 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index 0fc78cd271b6..f109556a8523 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -11,11 +11,10 @@ end @doc raw""" - HSNum_module(M::SubquoModule) - HSNum_module(M::SubquoModule; parent::Ring) + hilbert_series(M::SubquoModule; parent::Union{Nothing,Ring} = nothing) -Compute numerator of Hilbert series of the subquotient `M`. If the kwarg `parent` -is supplied the Hilbert series numerator is computed in the ring `parent`. +Compute a pair `(N,D)` where `N` and `D` are the non-reduced numerator and denominator of the Hilbert +series of the subquotient `M`. If the kwarg `parent` is supplied `N` and `D` are computed in the ring `parent`. !!! note Applied to a homogeneous subquotient `M`, the function first computes a Groebner basis to @@ -38,12 +37,17 @@ julia> B = Rg[x^2; y^3; z^4]; julia> M = SubquoModule(F, A, B); -julia> Oscar.HSNum_module(M) --t^9 + t^7 + 2*t^6 - t^5 - t^3 - 2*t^2 + 2*t +julia> hilbert_series(M) +((-t^9 + t^7 + 2*t^6 - t^5 - t^3 - 2*t^2 + 2*t, Factored element with data +Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{ZZRingElem, ZZMPolyRing}}, ZZRingElem}(-t + 1 => 3)), (GrpAb: Z, Identity map with + +Domain: +======= +GrpAb: Z)) ``` """ -function HSNum_module(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing) where T <: MPolyRingElem +function HSNum_module(SubM::SubquoModule{T}, HSRing::Ring) where T <: MPolyRingElem C,phi = present_as_cokernel(SubM, :with_morphism); # phi is the morphism LM = leading_module(C.quo); F = ambient_free_module(C.quo); @@ -51,7 +55,7 @@ function HSNum_module(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothi P = base_ring(C.quo); # short-cut for module R^0 (otherwise defn if HSeriesRing below gives index error) if iszero(r) - return HSNum_abbott(quo(P,ideal(P,[1]))[1]; parent=parent) + return HSNum_abbott(quo(P,ideal(P,[1]))[1], HSRing) end GensLM = gens(LM); L = [[] for _ in 1:r]; # L[k] is list of monomial gens for k-th cooord @@ -65,8 +69,8 @@ function HSNum_module(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothi end end IdealList = [ideal(P,G) for G in L]; - # If parent === nothing, should we compute a HSNum for some ideal first then use its ring for all later calls? - HSeriesList = [HSNum_abbott(quo(P,I)[1]; parent=parent) for I in IdealList]; +### # If parent === nothing, should we compute a HSNum for some ideal first then use its ring for all later calls? + HSeriesList = [HSNum_abbott(quo(P,I)[1], HSRing) for I in IdealList]; shifts = [degree(phi(g)) for g in gens(F)]; @vprintln :hilbert 1 "HSNum_module: shifts are $(shifts)"; shift_expv = [gen_repr(d) for d in shifts]; @@ -79,7 +83,50 @@ function HSNum_module(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothi return result; end -function HSNum_module(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothing) where T <: MPolyRingElem - # ASSUME F is graded free module - return HSNum_module(sub(F,gens(F))[1]; parent=parent) +# function HSNum_module(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothing) where T <: MPolyRingElem +# # ASSUME F is graded free module +# return HSNum_module(sub(F,gens(F))[1]; parent=parent) +# end + +function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing #=, backend::Symbol = :Abbott=#) where T <: MPolyRingElem + R = base_ring(SubM) + @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" +##??? @req is_positively_graded(R) "The base ring must be positively graded" + + # Wrap the case where G is abstractly isomorphic to ℤᵐ, but not realized as a + # free Abelian group. + # + # We use the Smith normal form to get there, recreate the graded ring with the + # free grading group, do the computation there and return the isomorphism for + # the grading. + G = grading_group(R) + if !is_zm_graded(R) + H, iso = snf(G) + V = [preimage(iso, x) for x in gens(G)] + isoinv = hom(G, H, V) +# W = R.d + W = [isoinv(R.d[i]) for i = 1:length(W)] + S, _ = graded_polynomial_ring(coefficient_ring(R), symbols(R), W) + map_into_S = hom(R, S, gens(S)) + J = map_into_S(modulus(SubM)) ##????? + SubM2, _ = quo(S, J) +#?? change_res = hom(A, AA, gens(AA); check=false) +#?? change_res_inv = hom(AA, A, gens(A); check=false) + (numer, denom), _ = multi_hilbert_series(SubM2; algorithm, parent) + return (numer, denom), (H, iso) + end + + # Now we may assume that the grading group is free Abelian. + m = ngens(G) + n = ngens(R) + HSRing = _hilbert_series_ring(parent, m) + # Get the weights as Int values: W[k] contain the weight(s) of x[k] + W = [[ Int(R.d[i][j]) for j in 1:m] for i in 1:n] + fac_denom = _hilbert_series_denominator(HSRing, W) + numer = HSNum_module(SubM, HSRing #=; backend=backend=#) + return (numer, fac_denom), (G, identity_map(G)) +end + +function hilbert_series(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothing) where T <: MPolyRingElem + return hilbert_series(sub(F,gens(F))[1]; parent=parent) end From 56c32cb1979da2d49bb11b3d876cfe2dc16cd21b Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 14:44:02 +0200 Subject: [PATCH 32/51] Tidying & renaming; internal fns now require HSRing arg --- src/Rings/hilbert.jl | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index bb66deeb3b51..2d2f036beecf 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -944,24 +944,25 @@ end -function HSNum(PP_gens::Vector{PP}, W::Vector{Vector{Int}}, PivotStrategy::Symbol; parent::Union{Nothing,Ring} = nothing) +function HSNum_abbott_PPs(PP_gens::Vector{PP}, W::Vector{Vector{Int}}, PivotStrategy::Symbol, HSRing::Ring) # ASSUME W is "rectangular" @vprintln :hilbert 1 "HSNum: PP_gens = $(PP_gens)"; @vprintln :hilbert 1 "HSNum: weight matrix W = $(W)"; HSNum_check_args(PP_gens, W) #throws or does nothing # Grading is over ZZ^m - m = length(W) # NumRows + m = length(W) ncols = length(W[1]) nvars = ncols # if ncols != nvars # throw(ArgumentError("weights matrix has wrong number of columns ($(ncols)); should be same as number of variables ($(nvars))")) # end - HPRingVarNames = (m==1) ? [:t] : [ _make_variable("t", k) for k in 1:m] #used only if parent == nothing - HPRing, t = (parent === nothing) ? LaurentPolynomialRing(QQ, HPRingVarNames) : (parent,gens(parent)) +# HPRingVarNames = (m==1) ? [:t] : [ _make_variable("t", k) for k in 1:m] #used only if parent == nothing +## HPRing, t = (parent === nothing) ? LaurentPolynomialRing(QQ, HPRingVarNames) : (parent,gens(parent)) + t = gens(HSRing) @assert length(t) >= m "supplied Hilbert series ring contains too few variables" - T = [one(HPRing) for k in 1:nvars] + T = [one(HSRing) for k in 1:nvars] for k in 1:nvars - s = one(HPRing) + s = one(HSRing) for j in 1:m s *= t[j]^W[j][k] end @@ -1016,7 +1017,7 @@ julia> Oscar.HSNum_abbott(RmodI) ``` """ -function HSNum_abbott(PmodI::MPolyQuoRing; pivot_strategy::Symbol = :auto, parent::Union{Nothing,Ring} = nothing) +function HSNum_abbott(PmodI::MPolyQuoRing, HSRing::Ring; pivot_strategy::Symbol = :auto) I = modulus(PmodI) P = base_ring(I) nvars = length(gens(P)) @@ -1031,7 +1032,7 @@ function HSNum_abbott(PmodI::MPolyQuoRing; pivot_strategy::Symbol = :auto, paren end LTs = gens(leading_ideal(I)) PPs = [PP(degrees(t)) for t in LTs] - return HSNum(PPs, W, pivot_strategy; parent=parent) + return HSNum_abbott_PPs(PPs, W, pivot_strategy, HSRing) end From 327ae9c935268eeddbb6ea8c94afabe908f5393c Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 14:45:18 +0200 Subject: [PATCH 33/51] Scattered tidying inside multi_hilbert_series --- src/Rings/mpoly-affine-algebras.jl | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index e9018dc3fbcd..c1c915b485fc 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -546,7 +546,7 @@ function multi_hilbert_series( R = base_ring(A) I = modulus(A) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" -## @req is_positively_graded(R) "The base ring must be positively graded" +##??? @req is_positively_graded(R) "The base ring must be positively graded" # Wrap the case where G is abstractly isomorphic to ℤᵐ, but not realized as a # free Abelian group. @@ -560,22 +560,21 @@ function multi_hilbert_series( V = [preimage(iso, x) for x in gens(G)] isoinv = hom(G, H, V) # W = R.d - W = [isoinv(R.d[i]) for i = 1:length(W)] + W = [isoinv(R.d[i]) for i = 1:length(R.d)] S, _ = graded_polynomial_ring(coefficient_ring(R), symbols(R), W) map_into_S = hom(R, S, gens(S)) J = map_into_S(I) AA, _ = quo(S, J) #?? change_res = hom(A, AA, gens(AA); check=false) #?? change_res_inv = hom(AA, A, gens(A); check=false) - (num, denom), _ = multi_hilbert_series(AA; algorithm, parent) - return (num, denom), (H, iso) + (numer, denom), _ = multi_hilbert_series(AA; algorithm, parent) + return (numer, denom), (H, iso) end # Now we may assume that the grading group is free Abelian. m = ngens(G) n = ngens(R) HSRing = _hilbert_series_ring(parent, m) - println("HSRING = $(HSRing)") # if parent !== nothing # # The following line might complain in unforeseen ways in case # # the argument for `parent` was too unreasonable. However, we would @@ -635,17 +634,17 @@ function multi_hilbert_series( # @assert _evaluate(fac_denom) == q # Shortcut for the trivial case - iszero(I) && return (one(parent), fac_denom), (G, identity_map(G)) + iszero(I) && return (one(HSRing), fac_denom), (G, identity_map(G)) # In general refer to internal methods for monomial ideals # TODO: Shouldn't the ordering be adapted to the grading in some sense? numer = one(HSRing) if backend == :Zach LI = leading_ideal(I; ordering=degrevlex(gens(R))) - numer = _numerator_monomial_multi_hilbert_series(LI, parent, m; algorithm=algorithm) + numer = _numerator_monomial_multi_hilbert_series(LI, HSRing, m; algorithm=algorithm) elseif backend == :Abbott # TODO: Pass on the `algorithm` keyword argument also here. - numer = HSNum_abbott(A; parent) + numer = HSNum_abbott(A, HSRing) else error("backend not found") end From d56a2fec36753036b655439963033322ad41e963 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 16:41:21 +0200 Subject: [PATCH 34/51] hilbert_series: Now has kwargs backend & parent; not yet working for non-ZZ^m-graded rings --- src/Modules/hilbert.jl | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index f109556a8523..e282ce5d94b3 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -47,7 +47,7 @@ GrpAb: Z)) ``` """ -function HSNum_module(SubM::SubquoModule{T}, HSRing::Ring) where T <: MPolyRingElem +function HSNum_module(SubM::SubquoModule{T}, HSRing::Ring, backend::Symbol=:Abbott) where T <: MPolyRingElem C,phi = present_as_cokernel(SubM, :with_morphism); # phi is the morphism LM = leading_module(C.quo); F = ambient_free_module(C.quo); @@ -69,13 +69,13 @@ function HSNum_module(SubM::SubquoModule{T}, HSRing::Ring) where T <: MPolyRing end end IdealList = [ideal(P,G) for G in L]; -### # If parent === nothing, should we compute a HSNum for some ideal first then use its ring for all later calls? - HSeriesList = [HSNum_abbott(quo(P,I)[1], HSRing) for I in IdealList]; +# HSeriesList = [HSNum_abbott(quo(P,I)[1], HSRing) for I in IdealList]; + HSeriesList = [multi_hilbert_series(quo(P,I)[1]; parent=HSRing, backend=backend)[1][1] for I in IdealList]; shifts = [degree(phi(g)) for g in gens(F)]; @vprintln :hilbert 1 "HSNum_module: shifts are $(shifts)"; shift_expv = [gen_repr(d) for d in shifts]; @vprintln :hilbert 1 "HSNum_module: shift_expv are $(shift_expv)"; - HSeriesRing = Oscar.parent(HSeriesList[1]); + HSeriesRing = parent(HSeriesList[1]); @vprintln :hilbert 1 "HSNum_module: HSeriesRing = $(HSeriesRing)"; t = gens(HSeriesRing); ScaleFactor = [_power_product(t,e) for e in shift_expv]; @@ -83,12 +83,13 @@ function HSNum_module(SubM::SubquoModule{T}, HSRing::Ring) where T <: MPolyRing return result; end +# No longer needed # function HSNum_module(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothing) where T <: MPolyRingElem # # ASSUME F is graded free module # return HSNum_module(sub(F,gens(F))[1]; parent=parent) # end -function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing #=, backend::Symbol = :Abbott=#) where T <: MPolyRingElem +function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem R = base_ring(SubM) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" ##??? @req is_positively_graded(R) "The base ring must be positively graded" @@ -101,18 +102,15 @@ function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = not # the grading. G = grading_group(R) if !is_zm_graded(R) +# error("non-ZZ^m-grading not yet implemented for modules") H, iso = snf(G) V = [preimage(iso, x) for x in gens(G)] isoinv = hom(G, H, V) -# W = R.d - W = [isoinv(R.d[i]) for i = 1:length(W)] + W = [isoinv(R.d[i]) for i = 1:ngens(R)] S, _ = graded_polynomial_ring(coefficient_ring(R), symbols(R), W) map_into_S = hom(R, S, gens(S)) - J = map_into_S(modulus(SubM)) ##????? - SubM2, _ = quo(S, J) -#?? change_res = hom(A, AA, gens(AA); check=false) -#?? change_res_inv = hom(AA, A, gens(A); check=false) - (numer, denom), _ = multi_hilbert_series(SubM2; algorithm, parent) + SubM2,_ = change_base_ring(map_into_S,SubM) + (numer, denom), _ = hilbert_series(SubM2; parent=parent, backend=backend) return (numer, denom), (H, iso) end @@ -120,13 +118,13 @@ function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = not m = ngens(G) n = ngens(R) HSRing = _hilbert_series_ring(parent, m) - # Get the weights as Int values: W[k] contain the weight(s) of x[k] + # Get the weights as Int values: W[k] contains the weight(s) of x[k] W = [[ Int(R.d[i][j]) for j in 1:m] for i in 1:n] - fac_denom = _hilbert_series_denominator(HSRing, W) - numer = HSNum_module(SubM, HSRing #=; backend=backend=#) - return (numer, fac_denom), (G, identity_map(G)) + denom = _hilbert_series_denominator(HSRing, W) + numer = HSNum_module(SubM, HSRing, backend) + return (numer, denom), (G, identity_map(G)) end -function hilbert_series(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothing) where T <: MPolyRingElem - return hilbert_series(sub(F,gens(F))[1]; parent=parent) +function hilbert_series(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem + return hilbert_series(sub(F,gens(F))[1]; parent=parent, backend=backend) end From 446face1e426d448359b76b8d59f410094cf2afd Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 16:42:04 +0200 Subject: [PATCH 35/51] Better comment --- src/Rings/hilbert.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index 2d2f036beecf..3f906cd628ca 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -1046,7 +1046,7 @@ function _hilbert_series_denominator(HSRing::Ring, W::Vector{Vector{Int}}) @req length(gens(HSRing)) >= m "Hilbert series ring has too few variables" fac_dict = Dict{elem_type(HSRing), Integer}() for i = 1:n - # adjoin factor 1 - prod(t_j^W[i,j]) + # adjoin factor ( 1 - prod(t_j^W[i,j]) ) B = MPolyBuildCtx(HSRing) push_term!(B, 1, W[i]) new_fac = 1-finish(B) From 59e718aba9926dd57e593fa3430b68ad346e772c Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 16:43:28 +0200 Subject: [PATCH 36/51] Improved example in doc; removed cruft --- src/Rings/mpoly-affine-algebras.jl | 66 +++++------------------------- 1 file changed, 10 insertions(+), 56 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index c1c915b485fc..a7a5ab0ef1dc 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -466,9 +466,9 @@ end @doc raw""" - multi_hilbert_series(A::MPolyQuoRing; algorithm::Symbol=:BayerStillmanA) + multi_hilbert_series(A::MPolyQuoRing; algorithm::Symbol=:BayerStillmanA, parent::Union{Nothing,Ring}=nothing) -Return the Hilbert series of the positively graded affine algebra `A`. +Return the Hilbert series of the graded affine algebra `A`. !!! note The advanced user can select an `algorithm` for the computation; @@ -515,19 +515,19 @@ julia> R, (w, x, y, z) = graded_polynomial_ring(QQ, ["w", "x", "y", "z"], W); julia> A, _ = quo(R, ideal(R, [w*y-x^2, w*z-x*y, x*z-y^2])); -julia> H = multi_hilbert_series(A); +julia> (num, den), (H, iso) = multi_hilbert_series(A); -julia> H[1][1] +julia> num 2*t^3 - 3*t^2 + 1 -julia> H[1][2] +julia> den Factored element with data Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{ZZRingElem, ZZMPolyRing}}, ZZRingElem}(-t + 1 => 4) -julia> H[2][1] +julia> H GrpAb: Z -julia> H[2][2] +julia> iso Map with following data Domain: ======= @@ -546,7 +546,6 @@ function multi_hilbert_series( R = base_ring(A) I = modulus(A) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" -##??? @req is_positively_graded(R) "The base ring must be positively graded" # Wrap the case where G is abstractly isomorphic to ℤᵐ, but not realized as a # free Abelian group. @@ -575,53 +574,10 @@ function multi_hilbert_series( m = ngens(G) n = ngens(R) HSRing = _hilbert_series_ring(parent, m) - # if parent !== nothing - # # The following line might complain in unforeseen ways in case - # # the argument for `parent` was too unreasonable. However, we would - # # like to keep the possibilities for that input rather broad. - # @req ngens(parent) >= m "parent ring of the output does not contain sufficiently many variables" - # else - # # If the parent of the output was not specified, recreate it as a Laurent polynomial ring - # if m == 1 - # VAR = [:t] - # else - # VAR = [_make_variable("t", i) for i = 1:m] - # end - # parent, _ = LaurentPolynomialRing(ZZ, VAR) - # end # Get the weights as Int values: W[k] contain the weight(s) of x[k] W = [[ Int(R.d[i][j]) for j in 1:m] for i in 1:n] - # Extract a matrix from the grading group - # W = R.d - # MI = Matrix{Int}(undef, n, m) - # for i=1:n - # for j=1:m - # MI[i, j] = Int(W[i][j]) - # end - # end - fac_denom = _hilbert_series_denominator(HSRing, W) - # # Prepare the denominator as a factorized element - # fac_dict = Dict{elem_type(parent), Integer}() - # for i = 1:n - # e = [Int(MI[i, :][j]) for j = 1:m] - # new_fac = one(parent) - # if isone(length(e)) - # # We can't use MPolyBuildCtx in the univariate case - # new_fac = new_fac - first(gens(parent))^first(e) - # else - # B = MPolyBuildCtx(parent) - # push_term!(B, 1, e) - # new_fac = 1-finish(B) - # end - # if haskey(fac_dict, new_fac) - # fac_dict[new_fac] = fac_dict[new_fac] + 1 - # else - # fac_dict[new_fac] = 1 - # end - # end - # fac_denom = FacElem(parent, fac_dict) # Old method below without factorization; left for debugging # q = one(parent) @@ -631,7 +587,7 @@ function multi_hilbert_series( # push_term!(B, 1, e) # q = q*(1-finish(B)) # end - # @assert _evaluate(fac_denom) == q + # @assert evaluate(fac_denom) == q # Shortcut for the trivial case iszero(I) && return (one(HSRing), fac_denom), (G, identity_map(G)) @@ -640,19 +596,17 @@ function multi_hilbert_series( # TODO: Shouldn't the ordering be adapted to the grading in some sense? numer = one(HSRing) if backend == :Zach - LI = leading_ideal(I; ordering=degrevlex(gens(R))) + LI = leading_ideal(I; ordering=degrevlex(gens(R))) # ??? better not to specify the grading ??? numer = _numerator_monomial_multi_hilbert_series(LI, HSRing, m; algorithm=algorithm) elseif backend == :Abbott # TODO: Pass on the `algorithm` keyword argument also here. numer = HSNum_abbott(A, HSRing) else - error("backend not found") + error("backend ($(backend)) not found") end return (numer, fac_denom), (G, identity_map(G)) end -# Helper function because the usual evaluation is broken for Laurent polynomials. -_evaluate(a::FacElem) = prod(b^k for (b, k) in a; init=one(base_ring(a))) ### TODO: original version of multi_hilbert_series based on moving things to the positive orthant From 13ec90e2fb71be4b32c32ef18b79a9fb1e4c12c6 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 17:39:30 +0200 Subject: [PATCH 37/51] Removed cruft --- src/Rings/hilbert.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index 3f906cd628ca..967e2023b788 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -953,11 +953,6 @@ function HSNum_abbott_PPs(PP_gens::Vector{PP}, W::Vector{Vector{Int}}, PivotStra m = length(W) ncols = length(W[1]) nvars = ncols - # if ncols != nvars - # throw(ArgumentError("weights matrix has wrong number of columns ($(ncols)); should be same as number of variables ($(nvars))")) - # end -# HPRingVarNames = (m==1) ? [:t] : [ _make_variable("t", k) for k in 1:m] #used only if parent == nothing -## HPRing, t = (parent === nothing) ? LaurentPolynomialRing(QQ, HPRingVarNames) : (parent,gens(parent)) t = gens(HSRing) @assert length(t) >= m "supplied Hilbert series ring contains too few variables" T = [one(HSRing) for k in 1:nvars] From 2d080958b88e0ebd21a1a2a9879bf335b373ca26 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 17:40:24 +0200 Subject: [PATCH 38/51] Removed cruft --- src/Rings/mpoly-affine-algebras.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index a7a5ab0ef1dc..4f610ef00586 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -558,14 +558,11 @@ function multi_hilbert_series( H, iso = snf(G) V = [preimage(iso, x) for x in gens(G)] isoinv = hom(G, H, V) -# W = R.d W = [isoinv(R.d[i]) for i = 1:length(R.d)] S, _ = graded_polynomial_ring(coefficient_ring(R), symbols(R), W) map_into_S = hom(R, S, gens(S)) J = map_into_S(I) AA, _ = quo(S, J) -#?? change_res = hom(A, AA, gens(AA); check=false) -#?? change_res_inv = hom(AA, A, gens(A); check=false) (numer, denom), _ = multi_hilbert_series(AA; algorithm, parent) return (numer, denom), (H, iso) end From ff019d2b0fc6d539defa5e941531a5f1173b7e91 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Thu, 10 Aug 2023 17:41:40 +0200 Subject: [PATCH 39/51] Fixed rank 0 case; added comments --- src/Modules/hilbert.jl | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index e282ce5d94b3..93e9ac2c4483 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -37,13 +37,14 @@ julia> B = Rg[x^2; y^3; z^4]; julia> M = SubquoModule(F, A, B); -julia> hilbert_series(M) -((-t^9 + t^7 + 2*t^6 - t^5 - t^3 - 2*t^2 + 2*t, Factored element with data -Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{ZZRingElem, ZZMPolyRing}}, ZZRingElem}(-t + 1 => 3)), (GrpAb: Z, Identity map with +julia> (num,den),_ = hilbert_series(M); -Domain: -======= -GrpAb: Z)) +julia> num +-t^9 + t^7 + 2*t^6 - t^5 - t^3 - 2*t^2 + 2*t + +julia> den +Factored element with data +Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{ZZRingElem, ZZMPolyRing}}, ZZRingElem}(-t + 1 => 3) ``` """ @@ -53,12 +54,12 @@ function HSNum_module(SubM::SubquoModule{T}, HSRing::Ring, backend::Symbol=:Abbo F = ambient_free_module(C.quo); r = rank(F); P = base_ring(C.quo); - # short-cut for module R^0 (otherwise defn if HSeriesRing below gives index error) + # short-cut for module R^0 (to avoid problems with empty sum below) if iszero(r) - return HSNum_abbott(quo(P,ideal(P,[1]))[1], HSRing) + return multi_hilbert_series(quo(P,ideal(P,[1]))[1]; parent=HSRing, backend=backend) end GensLM = gens(LM); - L = [[] for _ in 1:r]; # L[k] is list of monomial gens for k-th cooord + L = [[] for _ in 1:r]; # L[k] will be list of monomial gens for k-th cooord # Nested loop below extracts the coordinate monomial ideals -- is there a better way? for g in GensLM SR = coordinates(ambient_representative(g)); # should have length = 1 @@ -69,15 +70,14 @@ function HSNum_module(SubM::SubquoModule{T}, HSRing::Ring, backend::Symbol=:Abbo end end IdealList = [ideal(P,G) for G in L]; -# HSeriesList = [HSNum_abbott(quo(P,I)[1], HSRing) for I in IdealList]; HSeriesList = [multi_hilbert_series(quo(P,I)[1]; parent=HSRing, backend=backend)[1][1] for I in IdealList]; shifts = [degree(phi(g)) for g in gens(F)]; @vprintln :hilbert 1 "HSNum_module: shifts are $(shifts)"; shift_expv = [gen_repr(d) for d in shifts]; @vprintln :hilbert 1 "HSNum_module: shift_expv are $(shift_expv)"; - HSeriesRing = parent(HSeriesList[1]); - @vprintln :hilbert 1 "HSNum_module: HSeriesRing = $(HSeriesRing)"; - t = gens(HSeriesRing); +### HSeriesRing = parent(HSeriesList[1]); +### @vprintln :hilbert 1 "HSNum_module: HSeriesRing = $(HSeriesRing)"; + t = gens(HSRing); ScaleFactor = [_power_product(t,e) for e in shift_expv]; result = sum([ScaleFactor[k]*HSeriesList[k] for k in 1:r]); return result; @@ -92,7 +92,6 @@ end function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem R = base_ring(SubM) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" -##??? @req is_positively_graded(R) "The base ring must be positively graded" # Wrap the case where G is abstractly isomorphic to ℤᵐ, but not realized as a # free Abelian group. @@ -102,14 +101,14 @@ function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = not # the grading. G = grading_group(R) if !is_zm_graded(R) -# error("non-ZZ^m-grading not yet implemented for modules") + error("non-ZZ^m-grading not yet implemented for modules") ### Needs improvement to change_base_ring (see below) H, iso = snf(G) V = [preimage(iso, x) for x in gens(G)] isoinv = hom(G, H, V) W = [isoinv(R.d[i]) for i = 1:ngens(R)] S, _ = graded_polynomial_ring(coefficient_ring(R), symbols(R), W) map_into_S = hom(R, S, gens(S)) - SubM2,_ = change_base_ring(map_into_S,SubM) + SubM2,_ = change_base_ring(map_into_S,SubM) # !!! BUG this seems to forget that things are graded BUG !!! (numer, denom), _ = hilbert_series(SubM2; parent=parent, backend=backend) return (numer, denom), (H, iso) end From 11fa50bedc51e2e5f8e4b62fe3d96d8840305b06 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Fri, 11 Aug 2023 11:03:54 +0200 Subject: [PATCH 40/51] Disabled MPolyBuildCtx in HS_denominator: gives error for univariate poly rings --- src/Rings/hilbert.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index 967e2023b788..6f28472cf9ad 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -1039,12 +1039,14 @@ function _hilbert_series_denominator(HSRing::Ring, W::Vector{Vector{Int}}) m = length(W[1]) @req all(r -> length(r)==m, W) "Grading VecVec must be rectangular" @req length(gens(HSRing)) >= m "Hilbert series ring has too few variables" + t = gens(HSRing) fac_dict = Dict{elem_type(HSRing), Integer}() for i = 1:n # adjoin factor ( 1 - prod(t_j^W[i,j]) ) - B = MPolyBuildCtx(HSRing) - push_term!(B, 1, W[i]) - new_fac = 1-finish(B) + new_fac = 1 - prod([t[k]^W[i][k] for k in 1:m]); + # B = MPolyBuildCtx(HSRing) + # push_term!(B, 1, W[i]) # ???BUG??? DOES NOT WORK if HSRing is Univariate poly ring over ZZ + # new_fac = 1-finish(B) if haskey(fac_dict, new_fac) fac_dict[new_fac] += 1 # coalesce identical factors else From 0ecea3a2b132a46ae6ff712556ad8f9843cccb52 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Fri, 11 Aug 2023 14:06:31 +0200 Subject: [PATCH 41/51] Syntactic cleaning --- test/Rings/hilbert.jl | 83 ++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/test/Rings/hilbert.jl b/test/Rings/hilbert.jl index 4c7a75dc98cc..0dce2c506af1 100644 --- a/test/Rings/hilbert.jl +++ b/test/Rings/hilbert.jl @@ -56,59 +56,60 @@ I = ideal(P,G); PmodI, _ = quo(P,I); - HS = multi_hilbert_series(PmodI); - num = HS[1][1] - S = parent(num) - HS2 = multi_hilbert_series(PmodI, parent=S, backend=:Zach) - @test HS2[1][1] == num + (num1,_), (_,_) = multi_hilbert_series(PmodI); + S = parent(num1) + (num2,_), (_,_) = multi_hilbert_series(PmodI; parent=S, backend=:Zach); + @test num2 == num1 P2, x = graded_polynomial_ring(QQ, 5, "x", [1 1 1 1 1]) - G = P2.(G) + G = P2.(G); I = ideal(P2,G); PmodI, _ = quo(P2,I); - HS = hilbert_series(PmodI); - num = HS[1] - L, q = LaurentPolynomialRing(ZZ, "q") - HS2 = hilbert_series(PmodI, parent=L) - @test HS2[1] == evaluate(num, q) + num1, _ = hilbert_series(PmodI); + L, q = LaurentPolynomialRing(ZZ, "q"); + num2, _ = hilbert_series(PmodI; parent=L); + @test num2 == evaluate(num1, q) end @testset "second round of Hilbert series" begin - R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) - J = ideal(R, [y-x^2, z-x^3]) - I = homogenization(J, "w") - A, _ = quo(base_ring(I), I) - num, denom = hilbert_series(A) - S, t = LaurentPolynomialRing(ZZ, "t") - num2, denom2 = hilbert_series(A, parent=S) - Smult, (T,) = polynomial_ring(ZZ, ["t"]) - num3, denom3 = hilbert_series(A, parent=Smult) - @test num3 == evaluate(num, T) - @test num2 == evaluate(num, t) + # Standard graded + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]); + J = ideal(R, [y-x^2, z-x^3]); + I = homogenization(J, "w"); + A, _ = quo(base_ring(I), I); + numer1, denom1 = hilbert_series(A); + S, t = LaurentPolynomialRing(ZZ, "t"); + numer2, denom2 = hilbert_series(A; parent=S); + Smult, (T,) = polynomial_ring(ZZ, ["t"]); + numer3, denom3 = hilbert_series(A; parent=Smult); + @test numer3 == evaluate(numer1, T) + @test numer2 == evaluate(numer1, t) + # Negative grading RR, (X, Y) = graded_polynomial_ring(QQ, ["X", "Y"], [-1, -1]) - JJ = ideal(RR, X^2 - Y^2) - A, _ = quo(base_ring(JJ), JJ) - (num, denom), _= multi_hilbert_series(A) - S, t = LaurentPolynomialRing(ZZ, "t") - (num2, denom2), _ = multi_hilbert_series(A, parent=S) - Smult, (T,) = polynomial_ring(ZZ, ["t"]) - @test_throws DomainError multi_hilbert_series(A, parent=Smult) + JJ = ideal(RR, X^2 - Y^2); + A, _ = quo(base_ring(JJ), JJ); + (numer1, denom1), _ = multi_hilbert_series(A); + S, t = LaurentPolynomialRing(ZZ, "t"); + (numer2, denom2), _ = multi_hilbert_series(A; parent=S); + Smult, (T,) = polynomial_ring(ZZ, ["t"]); + @test_throws DomainError multi_hilbert_series(A; parent=Smult) - G = free_abelian_group(2) - G, _ = quo(G, [G[1]-3*G[2]]) - RR, (X, Y) = graded_polynomial_ring(QQ, ["X", "Y"], [G[1], G[2]]) - JJ = ideal(RR, X^2 - Y^6) - A, _ = quo(base_ring(JJ), JJ) - (num, denom), (H, iso) = multi_hilbert_series(A) + # Graded by commutative group + G = free_abelian_group(2); + G, _ = quo(G, [G[1]-3*G[2]]); + RR, (X, Y) = graded_polynomial_ring(QQ, ["X", "Y"], [G[1], G[2]]); + JJ = ideal(RR, X^2 - Y^6); + A, _ = quo(base_ring(JJ), JJ); + (numer1, denom1), (H, iso) = multi_hilbert_series(A); @test is_free(H) && isone(rank(H)) - S, t = LaurentPolynomialRing(ZZ, "t") - (num2, denom2), (H, iso) = multi_hilbert_series(A, parent=S) + S, t = LaurentPolynomialRing(ZZ, "t"); + (numer2, denom2), (H, iso) = multi_hilbert_series(A; parent=S); @test is_free(H) && isone(rank(H)) - Smult, (T,) = polynomial_ring(ZZ, ["t"]) - (num3, denom3), (H, iso) = multi_hilbert_series(A, parent=Smult) + Smult, (T,) = polynomial_ring(ZZ, ["t"]); + (numer3, denom3), (H, iso) = multi_hilbert_series(A; parent=Smult); @test is_free(H) && isone(rank(H)) - @test num == evaluate(num2, T) - @test num3 == evaluate(num2, first(gens(parent(num3)))) + @test numer1 == evaluate(numer2, T) + @test numer3 == evaluate(numer2, first(gens(parent(numer3)))) end From d5cce04d4fae0052987f8c5e9d8c7a84ad70c312 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Fri, 11 Aug 2023 14:08:41 +0200 Subject: [PATCH 42/51] Improved comment --- src/Rings/hilbert.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index 6f28472cf9ad..6cbd73b97650 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -1044,8 +1044,9 @@ function _hilbert_series_denominator(HSRing::Ring, W::Vector{Vector{Int}}) for i = 1:n # adjoin factor ( 1 - prod(t_j^W[i,j]) ) new_fac = 1 - prod([t[k]^W[i][k] for k in 1:m]); + # ???BUG??? DOES NOT WORK if HSRing is Univariate poly ring over ZZ # B = MPolyBuildCtx(HSRing) - # push_term!(B, 1, W[i]) # ???BUG??? DOES NOT WORK if HSRing is Univariate poly ring over ZZ + # push_term!(B, 1, W[i]) # new_fac = 1-finish(B) if haskey(fac_dict, new_fac) fac_dict[new_fac] += 1 # coalesce identical factors From 17a640ed6aa57a19eaf170f9c34884226afc21b5 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Fri, 11 Aug 2023 14:09:14 +0200 Subject: [PATCH 43/51] Added new test hilbert.jl --- test/Modules/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Modules/runtests.jl b/test/Modules/runtests.jl index 3169bee901cf..2584fb970b49 100644 --- a/test/Modules/runtests.jl +++ b/test/Modules/runtests.jl @@ -4,6 +4,7 @@ using Test include("UngradedModules.jl") include("FreeModElem-orderings.jl") include("ModulesGraded.jl") +include("hilbert.jl") include("module-localizations.jl") include("local_rings.jl") include("MPolyQuo.jl") From aa69d1cbc201b55008e8ba14dffb23a492a9fdcc Mon Sep 17 00:00:00 2001 From: John Abbott Date: Fri, 11 Aug 2023 14:10:40 +0200 Subject: [PATCH 44/51] Now have hilbert_series and multi_hilbert_series (for modules) --- src/Modules/hilbert.jl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index 93e9ac2c4483..49ebb1627d60 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -56,7 +56,7 @@ function HSNum_module(SubM::SubquoModule{T}, HSRing::Ring, backend::Symbol=:Abbo P = base_ring(C.quo); # short-cut for module R^0 (to avoid problems with empty sum below) if iszero(r) - return multi_hilbert_series(quo(P,ideal(P,[1]))[1]; parent=HSRing, backend=backend) + return multi_hilbert_series(quo(P,ideal(P,[1]))[1]; parent=HSRing, backend=backend)[1][1] end GensLM = gens(LM); L = [[] for _ in 1:r]; # L[k] will be list of monomial gens for k-th cooord @@ -89,7 +89,7 @@ end # return HSNum_module(sub(F,gens(F))[1]; parent=parent) # end -function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem +function multi_hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem R = base_ring(SubM) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" @@ -124,6 +124,15 @@ function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = not return (numer, denom), (G, identity_map(G)) end +function multi_hilbert_series(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem + return multi_hilbert_series(sub(F,gens(F))[1]; parent=parent, backend=backend) +end + +function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem + HS, _ = multi_hilbert_series(SubM; parent=parent, backend=backend) + return HS +end + function hilbert_series(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem return hilbert_series(sub(F,gens(F))[1]; parent=parent, backend=backend) end From b9e7e03c5a1c25c1b88484761c7f33ea3f1e619f Mon Sep 17 00:00:00 2001 From: John Abbott Date: Fri, 11 Aug 2023 14:14:16 +0200 Subject: [PATCH 45/51] Changed fn prototype for graded_map (line 575): AbstractFreeModElem instead of FreeModElem --- src/Modules/ModulesGraded.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/ModulesGraded.jl b/src/Modules/ModulesGraded.jl index 9101db932f54..4e71c929e708 100644 --- a/src/Modules/ModulesGraded.jl +++ b/src/Modules/ModulesGraded.jl @@ -572,7 +572,7 @@ function graded_map(F::FreeMod{T}, A::MatrixElem{T}) where {T <: RingElement} return phi end -function graded_map(F::FreeMod{T}, V::Vector{<:FreeModElem{T}}) where {T <: RingElement} +function graded_map(F::FreeMod{T}, V::Vector{<:AbstractFreeModElem{T}}) where {T <: RingElement} R = base_ring(F) G = grading_group(R) nrows = length(V) From fa8dc4165f550275eaecae3fbd7cf021bbff8838 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Fri, 11 Aug 2023 14:50:07 +0200 Subject: [PATCH 46/51] Corrected doc string; added new doc string --- src/Modules/hilbert.jl | 121 +++++++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 40 deletions(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index 49ebb1627d60..ed6bbba3c220 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -10,44 +10,6 @@ function _power_product(T, expv) end -@doc raw""" - hilbert_series(M::SubquoModule; parent::Union{Nothing,Ring} = nothing) - -Compute a pair `(N,D)` where `N` and `D` are the non-reduced numerator and denominator of the Hilbert -series of the subquotient `M`. If the kwarg `parent` is supplied `N` and `D` are computed in the ring `parent`. - -!!! note - Applied to a homogeneous subquotient `M`, the function first computes a Groebner basis to - obtain the leading term module; the rest of the computation uses this latter module - (sliced into ideals, one for each ambient free module component). - -# Examples -```jldoctest -julia> R, _ = polynomial_ring(QQ, ["x", "y", "z"]); - -julia> Z = abelian_group(0); - -julia> Rg, (x, y, z) = grade(R, [Z[1],Z[1],Z[1]]); - -julia> F = graded_free_module(Rg, 1); - -julia> A = Rg[x; y]; - -julia> B = Rg[x^2; y^3; z^4]; - -julia> M = SubquoModule(F, A, B); - -julia> (num,den),_ = hilbert_series(M); - -julia> num --t^9 + t^7 + 2*t^6 - t^5 - t^3 - 2*t^2 + 2*t - -julia> den -Factored element with data -Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{ZZRingElem, ZZMPolyRing}}, ZZRingElem}(-t + 1 => 3) - -``` -""" function HSNum_module(SubM::SubquoModule{T}, HSRing::Ring, backend::Symbol=:Abbott) where T <: MPolyRingElem C,phi = present_as_cokernel(SubM, :with_morphism); # phi is the morphism LM = leading_module(C.quo); @@ -89,6 +51,46 @@ end # return HSNum_module(sub(F,gens(F))[1]; parent=parent) # end +@doc raw""" + multi_hilbert_series(M::SubquoModule; parent::Union{Nothing,Ring} = nothing) + +Compute a pair of pairs `(N,D),(H,iso)` where `N` and `D` are the non-reduced numerator and denominator of the Hilbert +series of the subquotient `M`, and `H` is the SNF of the grading group together with the identifying isomorphism `iso`. +If the kwarg `parent` is supplied `N` and `D` are computed in the ring `parent`. + +!!! note + CURRENT LIMITATION: the grading must be over ZZ^m (more general gradings are not yet supported) + +!!! note + Applied to a homogeneous subquotient `M`, the function first computes a Groebner basis to + obtain the leading term module; the rest of the computation uses this latter module + (sliced into ideals, one for each ambient free module component). + +# Examples +```jldoctest +julia> R, _ = polynomial_ring(QQ, ["x", "y", "z"]); + +julia> Rg, (x, y, z) = grade(R, [4,3,2]); + +julia> F = graded_free_module(Rg, 1); + +julia> A = Rg[x; y]; + +julia> B = Rg[x^2; y^3; z^4]; + +julia> M = SubquoModule(F, A, B); + +julia> (num,den),_ = multi_hilbert_series(M); + +julia> num +-t^25 + 2*t^17 + t^16 + t^15 - t^12 - t^11 - t^9 - t^8 - t^7 + t^4 + t^3 + +julia> den +Factored element with data +Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{ZZRingElem, ZZMPolyRing}}, ZZRingElem}(-t^4 + 1 => 1, -t^3 + 1 => 1, -t^2 + 1 => 1) + +``` +""" function multi_hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem R = base_ring(SubM) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" @@ -101,14 +103,14 @@ function multi_hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} # the grading. G = grading_group(R) if !is_zm_graded(R) - error("non-ZZ^m-grading not yet implemented for modules") ### Needs improvement to change_base_ring (see below) + error("non-ZZ^m-grading not yet implemented for modules (see issue #2657)") ### Needs improvement to change_base_ring (see below) H, iso = snf(G) V = [preimage(iso, x) for x in gens(G)] isoinv = hom(G, H, V) W = [isoinv(R.d[i]) for i = 1:ngens(R)] S, _ = graded_polynomial_ring(coefficient_ring(R), symbols(R), W) map_into_S = hom(R, S, gens(S)) - SubM2,_ = change_base_ring(map_into_S,SubM) # !!! BUG this seems to forget that things are graded BUG !!! + SubM2,_ = change_base_ring(map_into_S,SubM) # !!! BUG this seems to forget that things are graded BUG (issue #2657) !!! (numer, denom), _ = hilbert_series(SubM2; parent=parent, backend=backend) return (numer, denom), (H, iso) end @@ -128,6 +130,45 @@ function multi_hilbert_series(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothi return multi_hilbert_series(sub(F,gens(F))[1]; parent=parent, backend=backend) end + +@doc raw""" + hilbert_series(M::SubquoModule; parent::Union{Nothing,Ring} = nothing) + +Compute a pair `(N,D)` where `N` and `D` are the non-reduced numerator and denominator of the Hilbert +series of the subquotient `M`. If the kwarg `parent` is supplied `N` and `D` are computed in the ring `parent`. + +!!! note + Applied to a homogeneous subquotient `M`, the function first computes a Groebner basis to + obtain the leading term module; the rest of the computation uses this latter module + (sliced into ideals, one for each ambient free module component). + +# Examples +```jldoctest +julia> R, _ = polynomial_ring(QQ, ["x", "y", "z"]); + +julia> Z = abelian_group(0); + +julia> Rg, (x, y, z) = grade(R, [Z[1],Z[1],Z[1]]); + +julia> F = graded_free_module(Rg, 1); + +julia> A = Rg[x; y]; + +julia> B = Rg[x^2; y^3; z^4]; + +julia> M = SubquoModule(F, A, B); + +julia> num,den = hilbert_series(M); + +julia> num +-t^9 + t^7 + 2*t^6 - t^5 - t^3 - 2*t^2 + 2*t + +julia> den +Factored element with data +Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{ZZRingElem, ZZMPolyRing}}, ZZRingElem}(-t + 1 => 3) + +``` +""" function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem HS, _ = multi_hilbert_series(SubM; parent=parent, backend=backend) return HS From 41472e10f4c955e5a1fe593b0ff66e39533dfab7 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Fri, 11 Aug 2023 15:41:20 +0200 Subject: [PATCH 47/51] Corrected doctest --- src/Rings/hilbert.jl | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Rings/hilbert.jl b/src/Rings/hilbert.jl index 6cbd73b97650..f4bd52e4acca 100644 --- a/src/Rings/hilbert.jl +++ b/src/Rings/hilbert.jl @@ -977,8 +977,9 @@ function gen_repr(d) end @doc raw""" - HSNum_abbott(A::MPolyQuoRing; pivot_strategy::Symbol = :auto) + HSNum_abbott(A::MPolyQuoRing, HSRing::Ring; pivot_strategy::Symbol = :auto) +Don't use this internal function: use `hilbert_series` or `multi_hilbert_series` instead. Compute numerator of Hilbert series of the quotient `A`. Result is a pair: `N, D` being the numerator `N` (as a laurent polynomial) and the denominator `D` as a factor list of laurent polynomials. @@ -997,7 +998,9 @@ julia> I = ideal(R, [x^3+y^2*z, y^3+x*z^2, z^3+x^2*y]); julia> RmodI,_ = quo(R,I); -julia> Oscar.HSNum_abbott(RmodI) +julia> HSRing1,_ = polynomial_ring(ZZ, "t"); + +julia> Oscar.HSNum_abbott(RmodI, HSRing1) -t^9 + 3*t^6 - 3*t^3 + 1 julia> R, (x,y,z) = graded_polynomial_ring(QQ, ["x", "y","z"], [1 2 3; 3 2 1]) @@ -1007,7 +1010,9 @@ julia> I = ideal(R, [x*z+y^2, y^6+x^3*z^3, z^6, x^6]); julia> RmodI,_ = quo(R,I); -julia> Oscar.HSNum_abbott(RmodI) +julia> HSRing2,_ = polynomial_ring(ZZ, ["t[1]", "t[2]"]); + +julia> Oscar.HSNum_abbott(RmodI, HSRing2) -t[1]^28*t[2]^28 + t[1]^24*t[2]^24 + t[1]^22*t[2]^10 - t[1]^18*t[2]^6 + t[1]^10*t[2]^22 - t[1]^6*t[2]^18 - t[1]^4*t[2]^4 + 1 ``` From 25e42d99697718819deea3720de46e1df5ac24e0 Mon Sep 17 00:00:00 2001 From: John Abbott Date: Fri, 11 Aug 2023 15:47:04 +0200 Subject: [PATCH 48/51] New tests (for hilbert_series) --- test/Modules/hilbert.jl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/Modules/hilbert.jl diff --git a/test/Modules/hilbert.jl b/test/Modules/hilbert.jl new file mode 100644 index 000000000000..839744ca12f7 --- /dev/null +++ b/test/Modules/hilbert.jl @@ -0,0 +1,25 @@ +@testset "Hilbert series and free resolution" begin + R, _ = polynomial_ring(QQ, ["x", "y", "z"]); + Z = abelian_group(0); + Rg, (x, y, z) = grade(R, [3*Z[1],Z[1],-Z[1]]); + F = graded_free_module(Rg, 1); + A = Rg[x; y]; + B = Rg[x^2+y^6; y^7; z^4]; + M = SubquoModule(F, A, B); + fr = free_resolution(M) + + # There is no length(fr), so instead we do the following + indexes = range(fr.C); + fr_len = first(indexes) + + num,_ = hilbert_series(fr[fr_len]) + for i in fr_len:-1:1 + phi = map(fr,i) + N = cokernel(phi) + numer,denom = hilbert_series(N) + numer_next,_ = hilbert_series(fr[i-1]) + @test numer == numer_next- num + num = numer + end + +end From 490e4cc05fcb91a7e6307096ff7d89a5c96f1f6c Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Fri, 11 Aug 2023 17:23:56 +0200 Subject: [PATCH 49/51] Use univariate laurent polynomials as default in the module case. --- src/Modules/hilbert.jl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index ed6bbba3c220..13be37bfe1d3 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -54,7 +54,7 @@ end @doc raw""" multi_hilbert_series(M::SubquoModule; parent::Union{Nothing,Ring} = nothing) -Compute a pair of pairs `(N,D),(H,iso)` where `N` and `D` are the non-reduced numerator and denominator of the Hilbert +Compute a pair of pairs `(N ,D), (H ,iso)` where `N` and `D` are the non-reduced numerator and denominator of the Hilbert series of the subquotient `M`, and `H` is the SNF of the grading group together with the identifying isomorphism `iso`. If the kwarg `parent` is supplied `N` and `D` are computed in the ring `parent`. @@ -91,7 +91,7 @@ Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, Abstr ``` """ -function multi_hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem +function multi_hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing, Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem R = base_ring(SubM) @req coefficient_ring(R) isa AbstractAlgebra.Field "The coefficient ring must be a field" @@ -170,10 +170,18 @@ Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, Abstr ``` """ function hilbert_series(SubM::SubquoModule{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem + @req is_z_graded(base_ring(SubM)) "ring must be ZZ-graded; use `multi_hilbert_series` otherwise" + if parent === nothing + parent, _ = LaurentPolynomialRing(ZZ, :t) + end HS, _ = multi_hilbert_series(SubM; parent=parent, backend=backend) return HS end function hilbert_series(F::FreeMod{T}; parent::Union{Nothing,Ring} = nothing, backend::Symbol = :Abbott) where T <: MPolyRingElem + @req is_z_graded(base_ring(F)) "ring must be ZZ-graded; use `multi_hilbert_series` otherwise" + if parent === nothing + parent, _ = LaurentPolynomialRing(ZZ, :t) + end return hilbert_series(sub(F,gens(F))[1]; parent=parent, backend=backend) end From 7ec551dd25dc2ce9c728f2fcac5f58aa52e4128f Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Fri, 11 Aug 2023 17:40:24 +0200 Subject: [PATCH 50/51] Fix doctests. --- src/Rings/mpoly-affine-algebras.jl | 38 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/Rings/mpoly-affine-algebras.jl b/src/Rings/mpoly-affine-algebras.jl index 4f610ef00586..300c4715c43d 100644 --- a/src/Rings/mpoly-affine-algebras.jl +++ b/src/Rings/mpoly-affine-algebras.jl @@ -208,14 +208,16 @@ julia> R, (w, x, y, z) = graded_polynomial_ring(QQ, ["w", "x", "y", "z"]); julia> A, _ = quo(R, ideal(R, [w*y-x^2, w*z-x*y, x*z-y^2])); julia> hilbert_series(A) -(2*t^3 - 3*t^2 + 1, t^4 - 4*t^3 + 6*t^2 - 4*t + 1) +(2*t^3 - 3*t^2 + 1, Factored element with data +Dict{ZZPolyRingElem, ZZRingElem}(-t + 1 => 4)) julia> R, (x, y, z) = graded_polynomial_ring(QQ, ["x", "y", "z"], [1, 2, 3]); julia> A, _ = quo(R, ideal(R, [x*y*z])); julia> hilbert_series(A) -(-t^6 + 1, -t^6 + t^5 + t^4 - t^2 - t + 1) +(-t^6 + 1, Factored element with data +Dict{ZZPolyRingElem, ZZRingElem}(-t^2 + 1 => 1, -t + 1 => 1, -t^3 + 1 => 1)) ``` """ function hilbert_series(A::MPolyQuoRing; #=backend::Symbol=:Singular, algorithm::Symbol=:BayerStillmanA,=# parent::Union{Nothing,Ring}=nothing) @@ -265,7 +267,8 @@ julia> R, (x, y, z) = graded_polynomial_ring(QQ, ["x", "y", "z"], [1, 2, 3]); julia> A, _ = quo(R, ideal(R, [x*y*z])); julia> hilbert_series(A) -(-t^6 + 1, -t^6 + t^5 + t^4 - t^2 - t + 1) +(-t^6 + 1, Factored element with data +Dict{ZZPolyRingElem, ZZRingElem}(-t^2 + 1 => 1, -t + 1 => 1, -t^3 + 1 => 1)) julia> hilbert_series_reduced(A) (t^2 - t + 1, t^2 - 2*t + 1) @@ -304,18 +307,18 @@ julia> hilbert_series_expanded(A, 5) ``` """ function hilbert_series_expanded(A::MPolyQuoRing, d::Int) - if iszero(A.I) - R = base_ring(A.I) - @req is_z_graded(R) "The base ring must be ZZ-graded" - W = R.d - W = [Int(W[i][1]) for i = 1:ngens(R)] - @req minimum(W) > 0 "The weights must be positive" - H = hilbert_series(A) - T, t = power_series_ring(QQ, d+1, "t") - return _rational_function_to_power_series(T, H[1], H[2]) - end - H = HilbertData(A.I) - return hilbert_series_expanded(H, d) + if iszero(modulus(A)) + R = base_ring(A) + @req is_z_graded(R) "The base ring must be ZZ-graded" + W = R.d + W = [Int(W[i][1]) for i = 1:ngens(R)] + @req minimum(W) > 0 "The weights must be positive" + num, denom = hilbert_series(A) + T, t = power_series_ring(QQ, d+1, "t") + return _rational_function_to_power_series(T, num, evaluate(denom)) + end + H = HilbertData(A.I) + return hilbert_series_expanded(H, d) end @doc raw""" @@ -531,7 +534,7 @@ julia> iso Map with following data Domain: ======= -Abelian group with structure: Z +H Codomain: ========= G @@ -687,6 +690,7 @@ julia> A, _ = quo(R, I); julia> H = multi_hilbert_series_reduced(A); + julia> H[1][1] -t[1]^5*t[2]^-1 + t[1]^3 + t[1]^3*t[2]^-3 + t[1]^2 + t[1]^2*t[2]^-1 + t[1]^2*t[2]^-2 + t[1] + t[1]*t[2]^-1 + 1 @@ -737,7 +741,7 @@ G """ function multi_hilbert_series_reduced(A::MPolyQuoRing; algorithm::Symbol=:BayerStillmanA) (p, q), (H, iso) = multi_hilbert_series(A, algorithm=algorithm) - f = p//q + f = p//evaluate(q) p = numerator(f) q = denominator(f) sig = coeff(q.mpoly, -1 .* q.mindegs) From a148caf401e619a0ac6103c0ce1c5c2037cec39b Mon Sep 17 00:00:00 2001 From: Matthias Zach Date: Fri, 11 Aug 2023 20:03:54 +0200 Subject: [PATCH 51/51] Fix doctests once more. --- src/Modules/hilbert.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Modules/hilbert.jl b/src/Modules/hilbert.jl index 13be37bfe1d3..155e7279c06c 100644 --- a/src/Modules/hilbert.jl +++ b/src/Modules/hilbert.jl @@ -165,7 +165,7 @@ julia> num julia> den Factored element with data -Dict{AbstractAlgebra.Generic.LaurentMPolyWrap{ZZRingElem, ZZMPolyRingElem, AbstractAlgebra.Generic.LaurentMPolyWrapRing{ZZRingElem, ZZMPolyRing}}, ZZRingElem}(-t + 1 => 3) +Dict{AbstractAlgebra.Generic.LaurentPolyWrap{ZZRingElem, ZZPolyRingElem, AbstractAlgebra.Generic.LaurentPolyWrapRing{ZZRingElem, ZZPolyRing}}, ZZRingElem}(-t + 1 => 3) ``` """