Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New method to convert toric divisors into Weil divisors #3076

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion experimental/Schemes/AlgebraicCycles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function coeff(D::AbsAlgebraicCycle, I::IdealSheaf)
end

function is_effective(A::AbsAlgebraicCycle)
return all(coeff(A, I)>=0 for A in components(A))
return all(coeff(A, I)>=0 for I in components(A))
end

function Base.:<=(A::AbsAlgebraicCycle,B::AbsAlgebraicCycle)
Expand Down
134 changes: 112 additions & 22 deletions experimental/Schemes/NormalToricVarieties/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,29 +51,119 @@
# too. On the other hand, none of the vⱼ was in τ⟂ for j > s,
# so neither can be any convex combination, but the trivial one.
# This proves the claim. Now (*) follows directly.
function _torusinvariant_weil_divisors(X::NormalToricVariety; check::Bool=false)
if has_attribute(X, :_torusinvariant_weil_divisors)
return get_attribute(X, :_torusinvariant_weil_divisors)::Vector{<:AbsWeilDivisor}
end
ray_list = rays(polyhedral_fan(X))
ideal_sheaves = Vector{IdealSheaf}()
for tau in ray_list
tau_dual = polarize(cone(tau))
ideal_dict = IdDict{AbsSpec, Ideal}()
for U in affine_charts(X)
if !(tau in cone(U))
ideal_dict[U] = ideal(OO(U), one(OO(U)))
continue
function _torusinvariant_weil_divisors(X::NormalToricVariety; check::Bool=false, algorithm::Symbol=:via_polymake)
return get_attribute!(X, :_torusinvariant_weil_divisors) do
ray_list = rays(polyhedral_fan(X))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this function works as long as X is simplicial? So maybe:

Suggested change
ray_list = rays(polyhedral_fan(X))
@req is_simplicial(X) "Variety must be simplicial"
ray_list = rays(polyhedral_fan(X))

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @lkastner is the right person to ask. If this applies, we should add an @check.

ideal_sheaves = Vector{IdealSheaf}()
if algorithm == :via_polymake
ideal_sheaves = [_ideal_sheaf_via_polymake(X, i; check) for i in 1:length(ray_list)]
elseif algorithm == :via_oscar
for tau in ray_list
tau_dual = polarize(cone(tau))
ideal_dict = IdDict{AbsSpec, Ideal}()
for U in affine_charts(X)
if !(tau in cone(U))
ideal_dict[U] = ideal(OO(U), one(OO(U)))
continue
end
sigma_dual = weight_cone(U)
hb = hilbert_basis(sigma_dual)
x = gens(OO(U))
ideal_dict[U] = ideal(OO(U), [x[i] for i in 1:length(x) if !(-hb[i] in tau_dual)])
end
push!(ideal_sheaves, IdealSheaf(X, ideal_dict; check))
end
else
error("algorithm not recognized")

Check warning on line 77 in experimental/Schemes/NormalToricVarieties/attributes.jl

View check run for this annotation

Codecov / codecov/patch

experimental/Schemes/NormalToricVarieties/attributes.jl#L77

Added line #L77 was not covered by tests
end
generating_divisors = [WeilDivisor(X, ZZ, IdDict{IdealSheaf, ZZRingElem}(I => one(ZZ))) for I in ideal_sheaves]
result = generating_divisors
return result
end::Vector{<:AbsWeilDivisor}
end

function _ideal_sheaf_via_polymake(X::NormalToricVariety, i::Int; check::Bool=false)
return _ideal_sheaf_via_polymake(X, [i==j ? one(ZZ) : zero(ZZ) for j in 1:nrays(polyhedral_fan(X))])
end

function _ideal_sheaf_via_polymake(X::NormalToricVariety, c::Vector{ZZRingElem}; check::Bool=false)
# Input:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise here:

Suggested change
# Input:
@req is_simpicial(X) "Variety must be simplicial"
# Input:

# - X is the variety and it comes with a polyhedral fan F
# - c is an integer vector describing a divisor as a formal linear
# combination of the rays r in the fan F.
#
# Output:
# an IdealSheaf representing this divisor, but not a divisor itself, yet
#
# The rays correspond to 'primitive divisors' from which everything
# else can be composed in the toric case. If we can write down ideal
# sheaves for these, we can hence do so for every divisor.
@assert all(x->x>=0, c) "divisor must be effective"
ray_list = rays(polyhedral_fan(X)) # All rays of the polyhedral fan of X
ideal_dict = IdDict{AbsSpec, Ideal}() # The final output: A list of ideals, one for each
# affine_chart of X

for U in affine_charts(X) # Iterate through the charts

# Populate a dict for the mapping of the local rays of the cone of U
# to the rays of the fan
sigma = cone(U)
sigma_dual = weight_cone(U)
r_sigma = rays(sigma) # The list of rays appearing in the cone of U
div_dict = Dict{RayVector, RayVector}()
index_dict = Vector{Int}() # A list mapping the index i of the i-th ray in sigma
# to the index k of the same ray in the list `ray_list`
# of all rays of the fan
# Populate that dictionary
for (i, r) in enumerate(r_sigma)
k = findfirst(s->s==r, ray_list)
k === nothing && error("ray not found")
div_dict[r] = ray_list[k]
push!(index_dict, k)
end

# If the above list is empty that means the divisor is
# not supported in this chart.
if isempty(index_dict)
ideal_dict[U] = ideal(OO(U), one(OO(U)))
continue

Check warning on line 129 in experimental/Schemes/NormalToricVarieties/attributes.jl

View check run for this annotation

Codecov / codecov/patch

experimental/Schemes/NormalToricVarieties/attributes.jl#L128-L129

Added lines #L128 - L129 were not covered by tests
end

# We extract what is locally visible of the given divisor in this
# chart and w.r.t the local enumeration of the rays.
loc_c = -c[index_dict] # The local vector of the linear combination
# The internal representations in Polymake
# require us to switch the sign here!
# Reason: The MODULE_GENERATORS below give the
# monomial as a generator f of the fractional ideal
# for the divisor D. But we need a sheaf of ideals
# so that div(f) >= -D, D = V(I). That inverts the
# sign.
loc_div = toric_divisor(U, loc_c) # The toric local representation of this divisor

# A is a matrix and its rows are the coordinates of the lattice
# points generating the local ideal in the `weight_cone` of `U`.
A = pm_object(loc_div).MODULE_GENERATORS
# We need to convert them to their representations in the
# hilbert basis for this chart.
hb = hilbert_basis(U)::ZZMatrix
x = gens(OO(U))
ideal_gens = elem_type(OO(U))[]
for i in 1:nrows(A)
# Manually convert the rows of A to a ZZMatrix so that solve_mixed will take it
# Why don't we do A all at once? Because `solve_mixed` won't accept actual matrices
# as second argument, unless they are 1×n.
b = zero_matrix(ZZ, ncols(A), 1)
for j in 1:ncols(A)
b[j, 1] = ZZ(A[i, j])
end
sigma_dual = weight_cone(U)
hb = hilbert_basis(sigma_dual)
x = gens(OO(U))
ideal_dict[U] = ideal(OO(U), [x[i] for i in 1:length(x) if !(-hb[i] in tau_dual)])
c_in_hb = solve_mixed(ZZMatrix, transpose(hb), b, identity_matrix(ZZ, nrows(hb)), zero_matrix(ZZ, nrows(hb), 1))
# c_in_hb might have several rows accounting for different solutions.
# We only need one of them and we chose the first.
push!(ideal_gens, prod(y^k for (y, k) in zip(x, c_in_hb[1, :]); init=one(OO(U))))
end
push!(ideal_sheaves, IdealSheaf(X, ideal_dict; check))
ideal_dict[U] = ideal(OO(U), ideal_gens)
end
generating_divisors = [WeilDivisor(X, ZZ, IdDict{IdealSheaf, ZZRingElem}(I => one(ZZ))) for I in ideal_sheaves]
result = generating_divisors
set_attribute!(X, :_torusinvariant_weil_divisors=>result)
return result
return IdealSheaf(X, ideal_dict; check)
end

29 changes: 20 additions & 9 deletions experimental/Schemes/ToricDivisors/attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,24 @@
end

# For method delegation.
function underlying_divisor(td::ToricDivisor; check::Bool=false)
if has_attribute(td, :underlying_divisor)
return get_attribute(td, :underlying_divisor)::WeilDivisor
end
X = scheme(td)
generating_divisors = _torusinvariant_weil_divisors(X; check)
result = sum(a*D for (a, D) in zip(coefficients(td), generating_divisors))
set_attribute!(td, :underlying_divisor=>result)
return result
function underlying_divisor(td::ToricDivisor; check::Bool=false, algorithm::Symbol=:direct)
return get_attribute!(td, :underlying_divisor) do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could likewise have a check here for a simplicial toric variety, but this will be triggered by the other methods later down this method anyways, unless I am mistaken.

X = scheme(td)
@assert !iszero(coefficients(td)) "divisor must be non-trivial"
if algorithm == :direct
a = coefficients(td)::Vector{ZZRingElem}
pos = [(c > 0 ? c : zero(ZZ)) for c in a]
neg = [(c < 0 ? -c : zero(ZZ)) for c in a]
is_zero(pos) && return -WeilDivisor(_ideal_sheaf_via_polymake(X, neg))
is_zero(neg) && return WeilDivisor(_ideal_sheaf_via_polymake(X, pos))
pos_div = WeilDivisor(_ideal_sheaf_via_polymake(X, pos))
neg_div = WeilDivisor(_ideal_sheaf_via_polymake(X, neg))
return pos_div - neg_div

Check warning on line 68 in experimental/Schemes/ToricDivisors/attributes.jl

View check run for this annotation

Codecov / codecov/patch

experimental/Schemes/ToricDivisors/attributes.jl#L66-L68

Added lines #L66 - L68 were not covered by tests
else
g = _torusinvariant_weil_divisors(X; algorithm)
generating_divisors = _torusinvariant_weil_divisors(X; check, algorithm)
return sum(a*D for (a, D) in zip(coefficients(td), generating_divisors))
end
end::WeilDivisor
end

39 changes: 36 additions & 3 deletions test/AlgebraicGeometry/ToricVarieties/toric_schemes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,42 @@ end
end

@testset "toric divisors to weil divisors" begin
IP = weighted_projective_space(NormalToricVariety, [3, 4, 23])
IP = weighted_projective_space(NormalToricVariety, [3, 2, 5])
w = canonical_divisor(IP)
D = Oscar.Oscar.underlying_divisor(w; check=true)
D = Oscar.underlying_divisor(w; check=true)
D2 = Oscar.underlying_divisor(w; algorithm=:via_polymake, check=true)
D3 = Oscar.underlying_divisor(w; algorithm=:via_oscar, check=true)
@test D == D2 == D3

@test w == forget_toric_structure(w)
@test w + D == 2*w
@test w + D == 2*Oscar.underlying_divisor(w)

prim = Oscar._torusinvariant_weil_divisors(IP; check=true)
# Delete the cache manually
delete!(IP.__attrs, :_torusinvariant_weil_divisors)
prim2 = Oscar._torusinvariant_weil_divisors(IP; check=true, algorithm=:via_oscar)
@test prim == prim2
@test prim[1] !== prim2[1]

D = WeilDivisor(Oscar._ideal_sheaf_via_polymake(IP, ZZ.([2, 3, 7])))
D2 = 2 * prim[1] + 3 * prim[2] + 7 * prim[3]

@test is_effective(D)
@test is_effective(D2)

#@test D == D2 # Test takes too long
IP = projective_space(NormalToricVariety, 1)
w = canonical_divisor(IP)
K0 = Oscar.underlying_divisor(w, algorithm=:via_polymake)
delete!(w.__attrs, :underlying_divisor)
K1 = Oscar.underlying_divisor(w, algorithm=:direct)
delete!(w.__attrs, :underlying_divisor)
K2 = Oscar.underlying_divisor(w, algorithm=:via_oscar)
delete!(w.__attrs, :underlying_divisor)

@test K1 == K2
@test K1 == K0
@test K2 == K0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this last line necessary? In theory, K1 == K2 and K0 == K1 implies K0 == K2. Maybe this is also true in OSCAR (for deeper reasons)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These trigger different calls for the comparison. So they test different code, indeed.


@test w == Oscar.underlying_divisor(w)
end
Loading