r"""
Compute twisted Hodge numbers for Hilbert schemes of points
We implement the twisted Hodge number formula for Hilbert schemes of points on surfaces,
as
* stated in Conjecture E of [2309.06244],
* proven in Theorem 1.1 of [2412.09975]
It reads
.. MATH::
\sum_{n\geq 0}
\sum_{p=0}^{2n}
\sum_{q=0}^{2n}
\mathrm{h}^{p,q}(\mathop{\rm Hilb}\nolimits^nS,{L}_n)x^py^qt^n
=
\prod_{k\ge 1}
\prod_{p=0}^2
\prod_{q=0}^2
\left(
1-(-1)^{p+q}x^{p+k-1}y^{q+k-1}t^k
\right)^{-(-1)^{p+q}\mathrm{h}^{p,q}(S,{L}^{\otimes k})}.
Here, $L$ is a line bundle on a smooth projective surface $S$ (or a compact complex
surface), $\\operatorname{Hilb}^nS$ is the Hilbert scheme of $n$ points on $S$,
and $L_n$ is the induced line bundle on the Hilbert scheme. One is referred to
[2412.09975] for more details.
This formula computes the twisted Hodge numbers of $L_n$ on the Hilbert scheme, i.e.,
.. MATH::
\mathrm{h}^{p,q}(\mathop{\rm Hilb}\nolimits^nS,L_n)
An interesting example where things can be computed also using an explicit description
of the Hilbert scheme is $\\operatorname{Hilb}^2\\mathbb{P}^2$, where the Hochschild
cohomology (or rather, Hochschild--Kostant--Rosenberg decomposition) is computed,
which corresponds to the twisted Hodge diamond using the anticanonical line bundle,
cf. [Section 4.2, 2309.06244]::
sage: from twisted_hilbert import *
sage: S = CompleteIntersectionSurface([], 3) # anticanonical twist
sage: TwistedHilbertSchemeDiamond(S, 2).as_parallelogram()
1
0 8
0 10 38
0 0 35 80
0 0 0 28 55
0 0 0 0
0 0 0
0 0
0
* [2309.06244] Pieter Belmans, Lie Fu, Andreas Krug,
Hochschild cohomology of Hilbert schemes of points on surfaces
`arXiv:2309.06244 <https://arxiv.org/abs/2309.06244>`_
* [2412.09975] Lie Fu: Twisted Hodge numbers and deformation theory of Hilbert schemes
of points on surfaces via Hodge modules
[arXiv:2412.09975](https://arxiv.org/abs/2412.09975)
AUTHORS:
- Pieter Belmans (2024-12-12): initial version
"""
import twisted_ci
from sage.arith.misc import binomial
from sage.categories.cartesian_product import cartesian_product
from sage.categories.rings import Rings
from sage.matrix.constructor import matrix
from sage.misc.fast_methods import Singleton
from sage.misc.table import table
from sage.rings.integer_ring import ZZ
from sage.rings.polynomial.multi_polynomial import MPolynomial
from sage.rings.power_series_ring import PowerSeriesRing
from sage.structure.element import Element
from sage.structure.parent import Parent
def twisted_hodge_diamond(S, n):
r"""
Computes the twisted Hodge diamond of a Hilbert scheme of points
This implements the twisted Hodge number formula described above, to compute
the entries of the twisted Hodge diamond for a given number of points,
i.e., the matrix describing
.. MATH::
\mathrm{h}^{p,q}(\mathop{\rm Hilb}\nolimits^nS,L_n)
INPUT:
- ``S`` -- sequence of twisted Hodge diamonds of the surface
- ``n`` -- the number of points on the Hilbert scheme
EXAMPLES::
sage: from twisted_hilbert import *
sage: P2 = [None, None, None]
sage: P2[0] = matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
sage: P2[1] = matrix([[10, 0, 0], [8, 0, 0], [1, 0, 0]])
sage: P2[2] = matrix([[28, 0, 0], [35, 0, 0], [10, 0, 0]])
sage: twisted_hodge_diamond(P2, 2)
[55 0 0 0 0]
[80 28 0 0 0]
[38 35 0 0 0]
[ 8 10 0 0 0]
[ 1 0 0 0 0]
"""
assert len(S) >= n + 1, "need enough entries in list"
A = PowerSeriesRing(ZZ, ("x", "y"), default_prec=(2 * n + 1))
B = PowerSeriesRing(A, "t", default_prec=n + 1)
x, y = A.gens()
t = B.gen()
H = B(1)
for k in range(1, n + 1):
for p, q in cartesian_product([range(3), range(3)]):
H *= (
1 - (-1) ** (p + q) * x ** (p + k - 1) * y ** (q + k - 1) * t**k
) ** (-((-1) ** (p + q)) * S[k][p, q])
M = matrix.zero(ZZ, 2 * n + 1)
for p, q in cartesian_product([range(2 * n + 1), range(2 * n + 1)]):
try:
M[p, q] = H.dict()[n].dict()[A(x**p * y**q).exponents()[0]]
except KeyError:
pass
return M
[docs]
class TwistedHodgeDiamond(Element):
r"""
Container structure for twisted Hodge diamonds.
EXAMPLES:
The twisted Hodge diamond for the projective plane and anticanonical bundle::
sage: from twisted_hilbert import *
sage: H = TwistedHodgeDiamond.from_matrix([[10, 0, 0], [8, 0, 0], [1, 0, 0]])
sage: H
twisted Hodge diamond
sage: H.pprint()
0
0 0
1 0 0
8 0
10
Notice how (twisted) Hodge diamond are printed in a funny way, with
$\\mathrm{h}^{0,0}$ at the bottom.
"""
[docs]
def __init__(self, parent, M):
"""
Constructor for a TwistedHodgeDiamond (not to be called directly)
INPUT:
- ``M`` -- matrix encoding twisted Hodge diamond
This function should not be called directly, use the class method
:meth:`TwistedHodgeDiamond.from_matrix` instead.
"""
self.__M = M
Element.__init__(self, parent)
[docs]
@classmethod
def from_matrix(cls, M):
r"""
Construct a twisted Hodge diamond from a matrix
INPUT:
- ``M`` -- square matrix encoding twisted Hodge diamond
EXAMPLES:
The twisted Hodge diamond for the projective plane and anticanonical bundle::
sage: from twisted_hilbert import *
sage: H = TwistedHodgeDiamond.from_matrix([[10, 0, 0], [8, 0, 0], [1, 0, 0]])
sage: H.pprint()
0
0 0
1 0 0
8 0
10
"""
M = matrix(M)
assert M.is_square()
return TwistedHodgeDiamondRing()(M)
[docs]
def dimension(self):
r"""Dimension of the variety underlying the twisted Hodge diamond
EXAMPLES::
sage: from twisted_hilbert import *
sage: EnriquesSurface()[0].dimension()
2
sage: TwistedHilbertSchemeDiamond(EnriquesSurface(), 3).dimension()
6
"""
return self.__M.nrows() - 1
[docs]
def pprint(self):
r"""Pretty print the twisted Hodge diamond
EXAMPLES:
The twisted Hodge diamond for the projective plane and anticanonical bundle::
sage: from twisted_hilbert import *
sage: H = TwistedHodgeDiamond.from_matrix([[10, 0, 0], [8, 0, 0], [1, 0, 0]])
sage: H.pprint()
0
0 0
1 0 0
8 0
10
"""
T = []
d = self.dimension()
for i in reversed(range(2 * d + 1)):
row = [""] * abs(d - i)
for j in range(max(0, i - d), min(i, d) + 1):
row.extend([self.__M[i - j, j], ""])
T.append(row)
# padding all rows to full length
for i in range(len(T)):
T[i].extend([""] * (2 * d - len(T[i]) + 1))
return table(T, align="center")
[docs]
def as_parallelogram(self):
r"""Return the twisted Hodge diamond as polyvector parallelogram
It is up to the user to make sure that the twisted Hodge diamond is computed
using the anticanonical bundle.
EXAMPLES:
Our favourite example is still $\\operatorname{Hilb}^2\\mathbb{P}^2$::
sage: from twisted_hilbert import *
sage: H = TwistedHilbertSchemeDiamond(CompleteIntersectionSurface([], 3), 2)
sage: H.as_parallelogram()
1
0 8
0 10 38
0 0 35 80
0 0 0 28 55
0 0 0 0
0 0 0
0 0
0
"""
T = []
d = self.dimension()
for n in range(2 * d + 1):
T.append([])
for p in range(d + 1):
q = n - p
if q in range(d + 1) and p in range(d + 1):
T[-1].append(self[d - p, q])
else:
T[-1].append("")
return table(T, align="center")
def _repr_(self):
r"""Output diagnostic information
This is a one-line string giving some basic information about the twisted Hodge
diamond. You'll see this when you just evaluate something which returns
a twisted Hodge diamond. To see something more useful, you'll likely want to use
* :meth:`TwistedHodgeDiamond.__str__` via `print`
* :meth:`TwistedHodgeDiamond.pprint`
It is also possible to override this output by using the built-in
functionality for parents and renaming.
EXAMPLES:
The default behavior::
sage: from twisted_hilbert import *
sage: TwistedHodgeDiamond.from_matrix([[10, 0, 0], [8, 0, 0], [1, 0, 0]])
twisted Hodge diamond
Special constructors give additional information::
sage: CompleteIntersectionSurface(3, 2)[2]
twisted Hodge diamond for complete intersection of degree [3] and L=O(4)
"""
return "twisted Hodge diamond"
[docs]
def __str__(self):
r"""Pretty print a twisted Hodge diamond
This gets called when you specifically print the object.
EXAMPLES:
The twisted Hodge diamond for the projective plane and anticanonical bundle::
sage: from twisted_hilbert import *
sage: H = TwistedHodgeDiamond.from_matrix([[10, 0, 0], [8, 0, 0], [1, 0, 0]])
sage: print(H)
0
0 0
1 0 0
8 0
10
"""
return str(self.pprint())
[docs]
def __eq__(self, other):
r"""Compare two twisted Hodge diamonds
INPUT:
- ``other`` -- the other twisted Hodge diamond
EXAMPLES:
Twisted Hodge diamonds for bielliptic surfaces are (not) the same::
sage: from twisted_hilbert import *
sage: BiellipticSurface(2)[0] == BiellipticSurface(3)[0]
True
sage: BiellipticSurface(2)[1] == BiellipticSurface(3)[1]
False
"""
return self.__M == other.__M
[docs]
def __getitem__(self, key):
r"""Return $\\mathrm{h}^{p,q}(X,L)$
This is
.. MATH::
\dim\mathrm{H}^q(X,\Omega_X^p\otimes L)
corresponding to the entry indexed by $p$ and $q$ in the matrix, where we
take `key=(p,q)`.
INPUT:
- ``key``: tuple of indices for the twisted Hodge diamond
EXAMPLES:
The twisted Hodge diamond for the projective plane and anticanonical bundle::
sage: from twisted_hilbert import *
sage: H = TwistedHodgeDiamond.from_matrix([[10, 0, 0], [8, 0, 0], [1, 0, 0]])
sage: H[0, 0]
10
sage: H[1, 0]
8
"""
return self.__M[key[0], key[1]]
class TwistedHodgeDiamondRing(Singleton, Parent):
def __init__(self):
Parent.__init__(self, category=Rings().Commutative())
def _element_constructor_(self, *args, **keywords):
m = args[0]
if m in ZZ:
m = matrix(ZZ, 1, 1, [m])
elif isinstance(m, MPolynomial):
raise NotImplementedError()
elif isinstance(m, (list, tuple)):
m = matrix(m)
elt = self.element_class(self, m)
return elt
def from_matrix(self, M):
return self.element_class(self, matrix(M))
def _repr_(self) -> str:
return "Ring of twisted Hodge diamonds"
Element = TwistedHodgeDiamond
[docs]
class TwistedSurfaceDiamonds:
r"""Encodes twisted Hodge diamonds of surface and powers of a line bundle
This makes it possible to implement both a class that knows all about a surface
and one that only contains a finite amount of data.
"""
[docs]
@classmethod
def from_list(cls, diamonds):
r"""
Construct a :class:`TwistedSurfaceDiamonds` object from a list of matrices
This is the basic approach, and limits the calculation of twisted Hodge
numbers to however many entries are provided.
INPUT:
- ``diamonds`` -- list of 3x3 matrices with twisted Hodge numbers
EXAMPLES:
Twisted Hodge numbers of the projective plane::
sage: from twisted_hilbert import *
sage: P2 = [None, None, None]
sage: P2[0] = matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
sage: P2[1] = matrix([[10, 0, 0], [8, 0, 0], [1, 0, 0]])
sage: P2[2] = matrix([[28, 0, 0], [35, 0, 0], [10, 0, 0]])
sage: S = TwistedSurfaceDiamonds.from_list(P2)
"""
pair = TwistedSurfaceDiamonds()
pair.__diamonds = list(map(TwistedHodgeDiamond.from_matrix, diamonds))
return pair
[docs]
def __getitem__(self, k):
r"""Get the twisted Hodge diamond for the line bundle ``L^k``"""
return self.__L[k]
[docs]
class CompleteIntersectionSurface(TwistedSurfaceDiamonds):
r"""
:class:`TwistedSurfaceDiamonds` for a complete intersection
It is possible to vary the line bundle being used to any power of $\\mathcal{O}(1)$
"""
__d = None
__i = 1
[docs]
def __init__(self, d, i=1):
r"""
Construct a complete intersection
The twisted Hodge numbers for complete intersection surfaces are computed
using [twisted-hodge-ci].
* [twisted-hodge-ci] Twisted Hodge numbers for complete intersections
`twisted-hodge-ci <https://github.com/pbelmans/twisted-hodge-ci>`_
INPUT:
- ``d`` -- degree, or list of degrees
- ``i`` (default: 1) -- power of $\\mathcal{O}(1)$ to be used
EXAMPLES:
Anticanonically twisted projective plane::
sage: from twisted_hilbert import *
sage: CompleteIntersectionSurface([], 3)[1].pprint()
0
0 0
1 0 0
8 0
10
Quadric surface with default twist::
sage: CompleteIntersectionSurface(2)[1].pprint()
0
0 0
0 0 0
0 0
4
del Pezzo surface of degree 4 with default (and anticanonical) twist::
sage: CompleteIntersectionSurface([2, 2])[1].pprint()
0
0 0
1 2 0
0 0
5
"""
try:
len(d)
except TypeError:
d = [d]
self.__d = d
self.__i = i
[docs]
def __getitem__(self, k):
# TODO make this cleaner...
H = twisted_ci.TwistedHodgeDiamond((len(self.__d) + 2, self.__d), k * self.__i)
H = TwistedHodgeDiamond.from_matrix(H._TwistedHodgeDiamond__M)
H.rename(
"twisted Hodge diamond for complete intersection of "
+ f"degree {self.__d} and L=O({self.__i * k})"
)
return H
[docs]
class ProductSurface(TwistedSurfaceDiamonds):
r"""
:class:`TwistedSurfaceDiamonds` for the product of two curves
The line bundle is the anticanonical bundle.
"""
[docs]
def __init__(self, g, h):
assert g >= 0 and h >= 0, "genera need to be positive"
self.__g = g
self.__h = h
@classmethod
def __H(cls, g, i):
r"""Cohomology of $i$th power of anticanonical line bundle on genus $g$ curve
Helper function, not to be called directly.
EXAMPLES:
The projective line:
sage: from twisted_hilbert import *
sage: ProductSurface._ProductSurface__H(0, 0)
[1, 0]
sage: ProductSurface._ProductSurface__H(0, 3)
[7, 0]
sage: ProductSurface._ProductSurface__H(0, -3)
[0, 5]
On an elliptic curve:
sage: ProductSurface._ProductSurface__H(1, 0)
[1, 1]
sage: ProductSurface._ProductSurface__H(1, 5)
[1, 1]
On a curve of genus 3:
sage: from twisted_hilbert import *
sage: ProductSurface._ProductSurface__H(3, 0)
[1, 3]
sage: ProductSurface._ProductSurface__H(3, -1)
[3, 1]
sage: ProductSurface._ProductSurface__H(3, 3)
[0, 14]
sage: ProductSurface._ProductSurface__H(3, -3)
[10, 0]
"""
if g == 0:
if i <= -1:
return [0, -2 * i - 1]
if i >= 0:
return [2 * i + 1, 0]
if g == 1:
return [1, 1]
if g >= 2:
if i <= -2:
return [(-2 * i - 1) * (g - 1), 0]
if i == -1:
return [g, 1]
if i == 0:
return [1, g]
if i >= 1:
return [0, (2 * i + 1) * (g - 1)]
[docs]
def __getitem__(self, k):
r"""Get the twisted Hodge diamond for the kth power of the anticanonical bundle
- ``k`` -- power of the anticanonical bundle
EXAMPLES:
The quadric is the product of two curves of genus 0::
sage: from twisted_hilbert import *
sage: S = ProductSurface(0, 0)
sage: T = CompleteIntersectionSurface(2, 2)
sage: T[0] == S[0]
True
sage: T[3] == S[3]
True
sage: T[-5] == S[-5]
True
"""
# introduce shorthands
g = self.__g
h = self.__h
H = ProductSurface.__H
return TwistedHodgeDiamond.from_matrix(
[
[
H(g, k)[0] * H(h, k)[0],
H(g, k)[0] * H(h, k)[1] + H(g, k)[1] * H(h, k)[0],
H(g, k)[1] * H(h, k)[1],
],
[
H(g, k)[0] * H(h, k - 1)[0] + H(g, k - 1)[0] * H(h, k)[0],
H(g, k)[0] * H(h, k - 1)[1]
+ H(g, k)[1] * H(h, k - 1)[0]
+ H(g, k - 1)[0] * H(h, k)[1]
+ H(g, k - 1)[1] * H(h, k)[0],
H(g, k)[1] * H(h, k - 1)[1] + H(g, k - 1)[1] * H(h, k)[1],
],
[
H(g, k - 1)[0] * H(h, k - 1)[0],
H(g, k - 1)[0] * H(h, k - 1)[1] + H(g, k - 1)[1] * H(h, k - 1)[0],
H(g, k - 1)[1] * H(h, k - 1)[1],
],
]
)
[docs]
class BiellipticSurface(TwistedSurfaceDiamonds):
r"""
:class:`TwistedSurfaceDiamonds` for a bielliptic surface
The line bundle is the anticanonical line bundle
"""
[docs]
def __init__(self, order):
assert order in [2, 3, 4, 6]
self.__order = order
[docs]
def __getitem__(self, k):
r"""Get the twisted Hodge diamond for the kth power of the anticanonical bundle
INPUT:
- ``k`` -- power of the anticanonical bundle
The calculations are based on the basic results of Section 4.3 of [2309.06244],
and provide an independent verification of the conclusions.
EXAMPLES:
The bigraded version of Lemma 4.7::
sage: from twisted_hilbert import *
sage: BiellipticSurface(2)[1].as_parallelogram()
1
1 1
0 2 0
1 1
1
sage: BiellipticSurface(3)[1].as_parallelogram()
1
1 1
0 1 0
0 0
0
sage: BiellipticSurface(4)[1].as_parallelogram()
1
1 1
0 1 0
0 0
0
sage: BiellipticSurface(6)[1].as_parallelogram()
1
1 1
0 1 0
0 0
0
The bigraded version of Proposition 4.9::
sage: S = BiellipticSurface(2)
sage: TwistedHilbertSchemeDiamond(S, 2).as_parallelogram()
1
1 1
0 3 0
0 4 4 0
0 2 8 2 0
0 4 4 0
0 3 0
1 1
1
sage: S = BiellipticSurface(3)
sage: TwistedHilbertSchemeDiamond(S, 2).as_parallelogram()
1
1 1
0 2 0
0 1 1 0
0 0 2 0 0
0 1 1 0
0 1 0
0 0
0
sage: S = BiellipticSurface(4)
sage: TwistedHilbertSchemeDiamond(S, 2).as_parallelogram()
1
1 1
0 2 0
0 1 1 0
0 0 1 0 0
0 0 0 0
0 0 0
0 0
0
sage: S = BiellipticSurface(6)
sage: TwistedHilbertSchemeDiamond(S, 2).as_parallelogram()
1
1 1
0 2 0
0 1 1 0
0 0 1 0 0
0 0 0 0
0 0 0
0 0
0
"""
if self.__order == 2:
diamonds = [
matrix([[1, 1, 0], [1, 2, 1], [0, 1, 1]]),
matrix([[0, 1, 1], [1, 2, 1], [1, 1, 0]]),
]
return TwistedHodgeDiamond.from_matrix(diamonds[k % 2])
if self.__order == 3:
diamonds = [
matrix([[1, 1, 0], [1, 2, 1], [0, 1, 1]]),
matrix([[0, 0, 0], [1, 1, 0], [1, 1, 0]]),
matrix([[0, 1, 1], [0, 1, 1], [0, 0, 0]]),
]
return TwistedHodgeDiamond.from_matrix(diamonds[k % 3])
if self.__order == 4:
diamonds = [
matrix([[1, 1, 0], [1, 2, 1], [0, 1, 1]]),
matrix([[0, 0, 0], [1, 1, 0], [1, 1, 0]]),
matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]]),
matrix([[0, 1, 1], [0, 1, 1], [0, 0, 0]]),
]
return TwistedHodgeDiamond.from_matrix(diamonds[k % 4])
if self.__order == 6:
diamonds = [
matrix([[1, 1, 0], [1, 2, 1], [0, 1, 1]]),
matrix([[0, 0, 0], [1, 1, 0], [1, 1, 0]]),
matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]]),
matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]]),
matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]]),
matrix([[0, 1, 1], [0, 1, 1], [0, 0, 0]]),
]
return TwistedHodgeDiamond.from_matrix(diamonds[k % 6])
[docs]
class EnriquesSurface(TwistedSurfaceDiamonds):
r"""
:class:`TwistedSurfaceDiamonds` for an Enriques surface
The line bundle is the (anti)canonical line bundle
EXAMPLES:
The following is Appendix B of [AJM.2017.v21.n6.a4]::
sage: from twisted_hilbert import *
sage: TwistedHilbertSchemeDiamond(EnriquesSurface(), 2)[3, 1]
10
sage: TwistedHilbertSchemeDiamond(EnriquesSurface(), 2).pprint()
0
0 0
0 1 0
0 0 0 0
1 10 66 10 1
0 0 0 0
0 1 0
0 0
0
* [AJM.2017.v21.n6.a4] Taro Hayashi,
Universal covering Calabi–Yau manifolds of the Hilbert schemes of points of
Enriques surfaces
`AJM.2017.v21.n6.a4 <https://dx.doi.org/10.4310/AJM.2017.v21.n6.a4>`_
"""
[docs]
def __getitem__(self, k):
if k % 2 == 0:
return TwistedHodgeDiamond.from_matrix([[1, 0, 0], [0, 10, 0], [0, 0, 1]])
if k % 2 == 1:
return TwistedHodgeDiamond.from_matrix([[0, 0, 1], [0, 10, 0], [1, 0, 0]])