Source code for diamond.diamond

r"""
A tool to work with Hodge diamonds, comes with many varieties and constructions
built into it.

Hodge diamonds encode the Hodge numbers of a variety, and provide interesting
information about its structure. They provide a numerical incarnation of many
operations one can perform in algebraic geometry, such as blowups, projective
bundles, products. They are also computed for many more specific constructions
such as certain moduli spaces of sheaves, or flag varieties, ...

These Hodge numbers are defined as the dimensions of the sheaf cohomology of
exterior powers of the cotangent bundle, i.e.

.. MATH::

    \mathrm{h}^{p,q}(X)=\dim\mathrm{H}^q(X,\Omega_X^p)

Here $p$ and $q$ range from $0$ to $n=\\dim X$. These numbers satisfy additional
symmetry properties:

    * Hodge symmetry: $\\mathrm{h}^{p,q}(X)=\\mathrm{h}^{q,p}(X)$
    * Serre duality: $\\mathrm{h}^{p,q}(X)=\\mathrm{h}^{n-p,n-q}(X)$

Because of these symmetries they are usually displayed as a diamond (it's
really just a square tilted 45 degrees), so that for a surface it would be::

                        h^{2,2}
                h^{2,1}         h^{1,2}
        h^{2,0}         h^{1,1}         h^{0,2}
                h^{1,0}         h^{0,1}
                        h^{0,0}

One of their famous applications is the mirror symmetry prediction that every
Calabi-Yau 3-fold has a mirror Calabi-Yau threefold, which should imply that
their Hodge diamonds are transpositions. The first instance of this is the
quintic 3-fold and its mirror, whose Hodge diamonds are::

                    1
               0         0
          0         1         0
      1       101       101       1
          0         1         0
               0         0
                    1

and::

                   1
              0         0
          0       101       0
      1       1         1       1
          0       101       0
              0         0
                   1


The following are some very basic examples of operations and constructions one
can use within the Hodge diamond cutter. To get started we do::

    sage: from diamond import *

after starting Sage.

Pretty print the Hodge diamond of a genus 2 curve::

    sage: X = HodgeDiamond.from_matrix([[1, 2], [2, 1]])
    sage: print(X)
          1
      2       2
          1

Compute the Euler characteristic of the product of `X` with itself::

    sage: print((X*X).euler())
    4

Pretty print the Hodge diamond of the Hilbert square of a K3 surface::

    sage: S = HodgeDiamond.from_matrix([[1, 0, 1], [0, 20, 0], [1, 0, 1]])
    sage: print(hilbn(S, 2))
                        1
                   0         0
              1        21        1
          0        0         0        0
      1       21       232       21       1
          0        0         0        0
              1        21        1
                   0         0
                        1

It also possible to generate LaTeX code::

    sage: latex(K3().pprint())
    \begin{tabular}{ccccc}
     &  & $1$ &  &  \\
     & $0$ &  & $0$ &  \\
    $1$ &  & $20$ &  & $1$ \\
     & $0$ &  & $0$ &  \\
     &  & $1$ &  &  \\
    \end{tabular}

There are many varieties built in, e.g. the previously defined K3 surface can
be compared to the built-in one::

    sage: print(S == K3())
    True

Check out the [documentation](https://pbelmans.ncag.info/hodge-diamond-cutter)
for all the available functionality.

If you use the software in your work, please cite it as explained on
[Zenodo](https://doi.org/10.5281/zenodo.3893509).

AUTHORS:

- Pieter Belmans (2019-01-27): initial version
- Pieter Belmans (2020-06-16): the version which got assigned a DOI
- Pieter Belmans (2021-08-04): various additions, added unit tests and proper
  documentation
"""

# ****************************************************************************
#       Copyright (C) 2021 Pieter Belmans <pieterbelmans@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#                  https://www.gnu.org/licenses/
# ****************************************************************************
from sage.arith.misc import binomial, factorial, gcd
from sage.categories.cartesian_product import cartesian_product
from sage.categories.rings import Rings
from sage.combinat.composition import Compositions
from sage.combinat.integer_vector import IntegerVectors
from sage.combinat.partition import Partitions
from sage.combinat.root_system.dynkin_diagram import DynkinDiagram
from sage.combinat.q_analogues import q_binomial
from sage.combinat.subset import Subsets
from sage.graphs.digraph import DiGraph
from sage.matrix.constructor import matrix
from sage.matrix.special import diagonal_matrix
from sage.misc.cachefunc import cached_function
from sage.misc.misc_c import prod
from sage.misc.table import table
from sage.modules.free_module_element import vector
from sage.rings.function_field.constructor import FunctionField
from sage.rings.integer import Integer
from sage.rings.integer_ring import ZZ
from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.rings.polynomial.multi_polynomial import MPolynomial
from sage.rings.power_series_ring import PowerSeriesRing
from sage.rings.rational_field import QQ
from sage.structure.parent import Parent
from sage.structure.element import Element
from sage.misc.fast_methods import Singleton

from itertools import groupby


def _to_matrix(f):
    r"""
    Convert Hodge--Poincaré polynomial to matrix representation

    EXAMPLES::

        sage: from diamond import *
        sage: x,y = polygens(ZZ,'x,y')
        sage: f = 1+x**2+4*x*y+x**2*y**2+y**2
        sage: diamond._to_matrix(f)
        [1 0 1]
        [0 4 0]
        [1 0 1]
    """
    assert f in HodgeDiamond.R

    if f.is_zero():
        return matrix(ZZ, 1, 1, [0])

    # deal with the size of the diamond in this way because of the following example:
    # X = complete_intersection(5, 3)
    # X*X - hilbtwo(X)
    d = max(max(e) for e in f.exponents()) + 1
    return matrix(ZZ, d, d, lambda i, j: f[i, j])


[docs] class HodgeDiamond(Element): r""" This class implements some methods to work with Hodge diamonds. """ #: polynomial ring used internally for the Hodge-Poincaré polynomial #: and can be used externally to create new polynomials (and thus diamonds) R = PolynomialRing(ZZ, ("x", "y")) #: variables in the polynomial ring for Hodge-Poincaré polynomials x, y = R.gens() #: static configuration variable for pretty-printing, see :func:`HodgeDiamond.pprint` hide_zeroes = None #: static configuration variable for pretty-printing, see :func:`HodgeDiamond.pprint` quarter = None
[docs] def __init__(self, parent, m): r""" Constructor for a Hodge diamond if you know what you are doing This just uses the given matrix. It is probably advised to use the class methods * :meth:`HodgeDiamond.from_matrix` * :meth:`HodgeDiamond.from_polynomial` in most cases though. """ # matrix representation of the Hodge diamond is used internally self._m = m self._size = m.ncols() - 1 Element.__init__(self, parent)
[docs] @classmethod def from_matrix(cls, m, from_variety=False): r""" Construct a Hodge diamond from a matrix INPUT: - ``m`` -- square integer matrix representing a Hodge diamond - ``from_variety`` (default: False) -- whether a check should be performed that it comes from a variety EXAMPLES: Hodge diamond of a K3 surface:: sage: from diamond import * sage: S = HodgeDiamond.from_matrix([[1, 0, 1], [0, 20, 0], [1, 0, 1]]) sage: S == K3() True The following fails as the lack of symmetry prevents a geometric origin:: sage: HodgeDiamond.from_matrix([[1, 2], [0, 1]], from_variety=True) Traceback (most recent call last): ... AssertionError: The matrix does not satisfy the conditions satisfied by the Hodge diamond of a smooth projective variety. """ return HodgeDiamondRing()(m, from_variety=from_variety)
[docs] @classmethod def from_polynomial(cls, f, from_variety=False): r""" Construct a Hodge diamond from a Hodge--Poincaré polynomial INPUT: - ``f`` -- an integer polynomial in the ring ``HodgeDiamond.R`` representing the Hodge--Poincaré polynomial - ``from_variety`` (default: False) -- whether a check should be performed that it comes from a variety EXAMPLES: Hodge diamond of a K3 surface:: sage: from diamond import * sage: x, y = (HodgeDiamond.x, HodgeDiamond.y) sage: S = HodgeDiamond.from_polynomial(1 + x**2 + 20*x*y + y**2 + x**2 * y**2) sage: S == K3() True The following fails as the lack of symmetry prevents a geometric origin:: sage: HodgeDiamond.from_polynomial(1 + x, from_variety=True) Traceback (most recent call last): ... AssertionError: The matrix does not satisfy the conditions satisfied by the Hodge diamond of a smooth projective variety. """ return HodgeDiamondRing()(f, from_variety=from_variety)
@property def polynomial(self): r"""The Hodge--Poincaré polynomial describing the Hodge diamond :getter: returns the Hodge--Poincaré polynomial :setter: sets the Hodge diamond using a Hodge--Poincaré polynomial :type: element of :attr:`HodgeDiamond.R` EXAMPLES: The Hodge--Poincaré polynomial of a K3 surface:: sage: from diamond import * sage: print(K3().polynomial) x^2*y^2 + x^2 + 20*x*y + y^2 + 1 Modifying the Hodge diamond of the projective plane:: sage: X = Pn(2) sage: X.polynomial = X.polynomial + X.x * X.y sage: print(X) 1 0 0 0 2 0 0 0 1 """ M = self.matrix return self.R( {(i, j): M[i, j] for i in range(M.nrows()) for j in range(M.ncols())} ) @polynomial.setter def polynomial(self, f): r"""Setter for the Hodge--Poincaré polynomial""" self.matrix = _to_matrix(f) @property def matrix(self): r"""The matrix describing the Hodge diamond :getter: returns the matrix :setter: sets the Hodge diamond using a matrix :type: square matrix of integers """ return self._m @matrix.setter def matrix(self, m): r"""Setter for the Hodge diamond as a matrix""" m = matrix(m) assert m.base_ring() == ZZ, "Entries need to be integers" assert m.is_square() self._m = m self.__normalise()
[docs] def size(self): r"""Internal method to determine the (relevant) size of the Hodge diamond""" return self._size
def __normalise(self): r"""Internal method to get rid of trailing zeros""" self._m = _to_matrix(self.polynomial) self._size = self._m.ncols() - 1
[docs] def __eq__(self, other): r"""Check whether two Hodge diamonds are equal This compares the Hodge polynomials, not the possibly oversized matrices describing the Hodge diamond. EXAMPLES: A quartic surface is a K3 surface:: sage: from diamond import * sage: K3() == hypersurface(4, 2) True """ if not isinstance(other, HodgeDiamond): return False return self.polynomial == other.polynomial
[docs] def __ne__(self, other): r"""Check whether two Hodge diamonds are not equal EXAMPLES: The projective line is not a genus 2 curve:: sage: from diamond import * sage: Pn(1) != curve(2) True The point is not the Lefschetz class:: sage: point() != lefschetz() True """ return not self == other
def _add_(self, other): r"""Add two Hodge diamonds together This corresponds to taking the disjoint union of varieties, or the direct sum of the Hodge structure. EXAMPLES: Hodge diamond of the projective line is the sum of that of a point and the Lefschetz diamond:: sage: from diamond import * sage: Pn(1) == 1 + lefschetz() True Adding zero doesn't do anything:: sage: K3() + zero() == K3() True """ return self.parent()(self.polynomial + other.polynomial) def _sub_(self, other): r"""Subtract two Hodge diamonds EXAMPLES: Hodge diamond of the projective line is the sum of that of a point and the Lefschetz diamond, but now we check it the other way around:: sage: from diamond import * sage: Pn(1) - 1 == lefschetz() True """ return self.parent()(self.polynomial - other.polynomial) def _mul_(self, other): r"""Multiply two Hodge diamonds This corresponds to taking the product of two varieties. EXAMPLES: The quadric surface is the product of two projective lines:: sage: from diamond import * sage: Pn(1) * Pn(1) == hypersurface(2, 2) True The product is commutative:: sage: K3() * curve(5) == curve(5) * K3() True The point is the unit:: sage: K3() * point() == point() * K3() == K3() True TESTS:: sage: 2 * K3() == K3() + K3() True """ if not isinstance(other, HodgeDiamond): # in the rare case someone does X*3 instead of 3*X return other * self return self.parent()(self.polynomial * other.polynomial)
[docs] def __pow__(self, power): r"""Raise a Hodge diamond to a power This corresponds to iterated multiplication. INPUT: - ``power`` -- exponent for the iterated multiplication EXAMPLES: The product of 2 K3 surfaces in two ways:: sage: from diamond import * sage: K3()**2 == K3()*K3() True """ return self.parent()(self.polynomial**power)
[docs] def __call__(self, i, y=None): r""" The calling operator either does a Lefschetz twist, or an evaluation If one parameter is present, then twist by a power of the Lefschetz Hodge diamond. If two parameters are present, then evaluate the Hodge-Poincaré polynomial Negative values are allowed to untwist, up to the appropriate power. INPUT: - ``i`` -- integer denoting the power of the Lefschetz class, or value for the first variable - ``y`` -- value of the second variable (default: ``None``), if it is non-zero then ``i`` is reinterpreted as the value of the first variable EXAMPLES: The Lefschetz class is by definition the twist of the point:: sage: from diamond import * sage: lefschetz() == point()(1) True We can reconstruct projective space as a sum of twists of the point:: sage: Pn(10) == sum(point()(i) for i in range(11)) True If we supply two parameters we are evaluation the Hodge-Poincaré polynomial, e.g. to find the Euler characteristic:: sage: Pn(10)(1, 1) == 11 True """ if y is None: assert i >= -self.lefschetz_power() return self.parent()(self.R(self.polynomial * self.x**i * self.y**i)) x = i return self.polynomial(x, y)
[docs] def __getitem__(self, index): r"""Get (p, q)th entry of Hodge diamond or the ith row of the Hodge diamond""" # first try it as (p, q) try: p, q = index if p < 0 or q < 0: return 0 else: try: return self.matrix[p, q] except IndexError: return 0 # now we assume it's an integer: this is equivalent to HodgeDiamond.row(p) except TypeError: # we could do something smarter, but this is it for now return [self.matrix[p, index - p] for p in range(index + 1)]
def _repr_(self): r"""Output diagnostic information This is a one-line string giving some basic information about the Hodge diamond. You'll see this when you just evaluate something which returns a Hodge diamond. To see something more useful, you'll likely want to use * :meth:`HodgeDiamond.__str__` via `print` * :meth:`HodgeDiamond.pprint` * :meth:`HodgeDiamond.polynomial` It is also possible to override this output by using the built-in functionality for parents and renaming. EXAMPLES: The projective line:: sage: from diamond import * sage: Pn(1) Hodge diamond of size 2 and dimension 1 We give it a more descriptive name:: sage: P1 = Pn(1) sage: P1.rename("The projective line") sage: P1 The projective line """ return ( f"Hodge diamond of size {self._size + 1} and dimension {self.dimension()}" ) @property def name(self): return self.__repr__()
[docs] def __str__(self): r"""Pretty print Hodge diamond This gets called when you specifically print the object. EXAMPLES: The projective line:: sage: from diamond import * sage: print(Pn(1)) 1 0 0 1 """ return str(self.pprint())
def __table(self, hide_zeroes=None, quarter=None): r"""Generate a table object for the Hodge diamond""" # take the default values from the static variables if hide_zeroes is None: if HodgeDiamond.hide_zeroes is None: hide_zeroes = False else: hide_zeroes = HodgeDiamond.hide_zeroes if quarter is None: if HodgeDiamond.quarter is None: quarter = False else: quarter = HodgeDiamond.quarter d = self._size T = [] if self.is_zero(): T = [[0]] else: for i in range(2 * d + 1): row = [""] * (abs(d - i)) for j in range(max(0, i - d), min(i, d) + 1): row.extend([self.matrix[j, i - j], ""]) T.append(row) # making sure all rows have same length for i in range(len(T)): T[i].extend([""] * (2 * d - len(T[i]) + 1)) T[i] = T[i][: 2 * d + 1] # replace zeroes by spaces, if requested if hide_zeroes: T = [[t if t != 0 else "" for t in row] for row in T] # only print the top-left quarter, if requested if quarter: T = [[T[i][j] for j in range(d + 1)] for i in range(d + 1)] # determine the minimum number of leading and trailing empty strings # and remove them to align better to the left if hide_zeroes: empty = [] for row in T: (leading, trailing) = (0, 0) groups = [(k, len(list(g))) for k, g in groupby(row)] if groups[0][0] == "": leading = groups[0][1] if groups[-1][0] == "": trailing = groups[-1][1] empty.append( (leading, trailing), ) leading = min(a for (a, _) in empty) trailing = min(b for (_, b) in empty) for i in range(len(T)): T[i] = T[i][leading : len(T) - trailing] return table(T, align="center")
[docs] def pprint(self, format="table", hide_zeroes=None, quarter=None): r"""Pretty print the Hodge diamond INPUT: - ``format`` -- output format (default: `"table"`), if table it pretty prints a Hodge diamond; all else defaults to the polynomial - ``hide_zeroes`` (default: False) -- whether to hide the zeroes if `"table"` is used as format - ``quarter`` (default: False) -- whether to only print the top-left quarter if `"table"` is used for the format The parameters ``hide_zeroes`` and ``quarter`` can be set using a static variable, in which case providing them will override this value (if it is set). EXAMPLES: The projective line:: sage: from diamond import * sage: Pn(1).pprint() 1 0 0 1 sage: Pn(1).pprint(format="polynomial") x*y + 1 Don't print the zeroes:: sage: from diamond import * sage: (Pn(2) * curve(3)).pprint(hide_zeroes=True) 1 3 3 2 3 3 2 3 3 1 Only print the top-left quarter:: sage: from diamond import * sage: (Pn(2) * curve(3)).pprint(quarter=True) 1 3 0 2 0 3 Only print the top-left quarter whilst hiding zeroes:: sage: from diamond import * sage: (Pn(2) * curve(3)).pprint(hide_zeroes=True, quarter=True) 1 3 2 3 """ if format == "table": return self.__table(hide_zeroes=hide_zeroes, quarter=quarter) else: return self.polynomial
def __is_positive(self): r"""Check whether all entries are positive integers""" return all(hpq >= 0 for hpq in self.matrix.coefficients())
[docs] def is_hodge_symmetric(self): r"""Check whether the Hodge diamond satisfies Hodge symmetry This checks the equality .. MATH:: \mathrm{h}^{p,q}(X)=\mathrm{h}^{q,p}(X) for $p,q=0,\\ldots,\\dim X$. Almost all of the constructions provided with the library satisfy Hodge symmetry, because we (somewhat implicitly) work with things which are (or behave like) smooth projective varieties over a field of characteristic zero. Over the complex numbers this can fail for non-Kähler manifolds, such as the Hopf surface. In positive characteristic this can fail too, with an example given by classical and singular Enriques surfaces in characteristic 2, see [MR0491720] and Proposition 1.4.2 in [MR0986969] * [MR0491720] Bombieri--Mumford, Enriques' classification of surfaces in char. p. III. * [MR0986969] Cossec--Dolgachev, Enriques surfaces I, Progress in Mathematics, 1989 EXAMPLES: Constructions satisfy this property:: sage: from diamond import * sage: Pn(5).is_hodge_symmetric() True The Hopf surface over the complex numbers:: sage: S = HodgeDiamond.from_matrix([[1, 0, 0], [1, 0, 1], [0, 0, 1]]) sage: print(S) 1 0 1 0 0 0 1 0 1 sage: S.is_hodge_symmetric() False Classical and singular Enriques surfaces in characteristic 2 (which are smooth, despite their name) also have a Hodge diamond violating Hodge symmetry:: sage: enriques(two="classical").is_hodge_symmetric() False sage: enriques(two="singular").is_hodge_symmetric() False sage: enriques(two="supersingular").is_hodge_symmetric() True """ return self.matrix.is_symmetric()
[docs] def is_serre_symmetric(self): r"""Check whether the Hodge diamond satisfies Serre symmetry This checks the equality .. MATH:: \mathrm{h}^{p,q}(X)=\mathrm{h}^{\dim X-p,\dim X-q}(X) for $p,q=0,\\ldots,\\dim X$. Because Serre duality holds for all smooth projective varieties, independent of the characteristic, and also for non-Kähler varieties there are no examples where this condition fails. It can of course fail for motivic pieces, for silly reasons. EXAMPLES: The Hilbert scheme of 4 points on a K3 surface satisfies the symmetry:: sage: from diamond import * sage: hilbn(K3(), 4).is_serre_symmetric() True The Lefschetz diamond fails it for silly reasons:: sage: lefschetz().is_serre_symmetric() False """ d = self._size return all( self.matrix[p, q] == self.matrix[d - p, d - q] for p in range(d + 1) for q in range(d + 1) )
[docs] def betti(self): r"""Betti numbers of the Hodge diamond This gives an integer vector. EXAMPLES: Betti numbers of a K3 surface:: sage: from diamond import * sage: K3().betti() [1, 0, 22, 0, 1] The second Betti number of the Hilbert scheme of points on a K3 surface is 23, not 22:: sage: [hilbn(K3(), n).betti()[2] for n in range(2, 10)] [23, 23, 23, 23, 23, 23, 23, 23] """ d = self._size return [ ZZ.sum(self.matrix[j, i - j] for j in range(max(0, i - d), min(i, d) + 1)) for i in range(2 * d + 1) ]
[docs] def middle(self): r"""Middle cohomology of the Hodge diamond For smooth projective varieties the middle cohomology sits in degree equal to the dimension. EXAMPLES: There is an interesting link between K3 surfaces and cubic fourfolds which can be seen on the level of middle cohomology:: sage: from diamond import * sage: (hypersurface(3, 4) - lefschetz()**2).middle() [0, 1, 20, 1, 0] sage: K3().middle() [1, 20, 1] """ d = self._size return [self.matrix[i, d - i] for i in range(d + 1)]
[docs] def row(self, i, truncate=False): r"""Get the ith row of the Hodge diamond For smooth projective varieties these are the Hodge numbers of the Hodge structure on the cohomology in degree `i`. Alternatively, you can use ``HodgeDiamond.__getitem__`` with a single index `i` (but then you have to truncate yourself). INPUT: - ``i`` -- the row of the Hodge diamond - ``truncate`` (default: False) -- whether you want to omit leading and trailing zeroes EXAMPLES: For a smooth projective variety the middle cohomology is the row sitting in the middle dimension:: sage: from diamond import * sage: hypersurface(3, 4).middle() == hypersurface(3, 4).row(4) True If you don't want to truncate, ``HodgeDiamond.__getitem__`` gives the same functionality:: sage: from diamond import * sage: hypersurface(3, 4).row(4) == hypersurface(3, 4)[4] True For the moduli space of vector bundles on a curve, the cohomology in degree 3 is the same as the cohomology of the curve in degree 1:: sage: from diamond import * sage: moduli_vector_bundles(3, 1, 9).row(3, truncate=True) [9, 9] """ row = [self.matrix[j, i - j] for j in range(i + 1)] if truncate: while row[0] == 0 and row[-1] == 0: row = row[1:-1] return row
[docs] def signature(self): r"""The signature of the Hodge diamond This is the index of the intersection form on middle cohomology taken with real coefficients. By the Hodge index theorem it is given by the formula in Theorem 6.33 of Voisin's first book on Hodge theory. .. MATH:: \sigma=\sum_{p,q=0}^{\dim X}(-1)^p\mathrm{h}^{p,q} This of course only makes sense if the diamond comes from a compact Kähler manifold. EXAMPLES: sage: from diamond import * sage: K3().signature() -16 """ assert self.arises_from_variety() d = self._size return sum((-1) ** p * self[p, q] for p in range(d + 1) for q in range(d + 1))
[docs] def euler(self): r"""The topological Euler characteristic of the Hodge diamond This is the alternating sum of the Betti numbers, so that .. MATH:: \chi_{\mathrm{top}}=\sum_{p,q=0}^{\dim X}(-1)^{p+q}\mathrm{h}^{p,q} EXAMPLES: The Euler characteristic of projective space grows linearly:: sage: from diamond import * sage: [Pn(n).euler() for n in range(10)] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] For Hilbert schemes of points of K3 surfaces these are the coefficients of the series expansion of the Dedekind eta-function, see A006922 in the OEIS:: sage: [hilbn(K3(), n).euler() for n in range(10)] [1, 24, 324, 3200, 25650, 176256, 1073720, 5930496, 30178575, 143184000] """ return ZZ.sum((-1) ** i * bi for i, bi in enumerate(self.betti()))
[docs] def holomorphic_euler(self): r"""Holomorphic Euler characteristic This is the Euler characteristic of the structure sheaf, so .. MATH:: \chi(X)=\sum_{i=0}^{\dim X}(-1)^i\mathrm{h}^{0,i}(X) EXAMPLES: For projective space it is 1:: sage: from diamond import * sage: all(Pn(n).holomorphic_euler() == 1 for n in range(10)) True For a hyperkähler variety of dimension $2n$ this number is $n+1$:: sage: all(K3n(n).holomorphic_euler() == n+1 for n in range(5)) True """ return ZZ.sum((-1) ** i * self.matrix[i, 0] for i in range(self.matrix.nrows()))
[docs] def hirzebruch(self): r"""Hirzebruch's \chi_y genus For a smooth projective variety $X$ Hirzebruch's $\\chi_y$-genus is defined as .. MATH:: \chi_y(X)=\sum_{p,q=0}^{\dim X}(-1)^{p+q}\mathrm{h}^{p,q}(X)y^p which shows it is the specialisation of the Hodge-Poincaré polynomial for $x=-1$. A further specialisation to $y=-1$ gives the Euler characteristic. EXAMPLES: For a K3 surface we have:: sage: from diamond import * sage: K3().hirzebruch() 2*y^2 - 20*y + 2 sage: K3().hirzebruch().subs(y=-1) == K3().euler() True For the Hilbert square of a K3 surface we get:: sage: hilbn(K3(), 2).hirzebruch() 3*y^4 - 42*y^3 + 234*y^2 - 42*y + 3 sage: hilbn(K3(), 2).hirzebruch().subs(y=-1) == hilbn(K3(), 2).euler() True """ return self.polynomial.subs(x=-1)
[docs] def homological_unit(self): r"""Dimensions of $\\mathrm{H}^\\bullet(X,\\mathcal{O}_X)$ A notion introduced by Abuaf. """ return self.matrix.row(0)
[docs] def hochschild(self): r"""Dimensions of the Hochschild homology Columns of the Hodge diamond are Hochschild homology, by the Hochschild- Kostant-Rosenberg theorem. """ d = self._size return HochschildHomology.from_list( [ ZZ.sum( self.matrix[d - i + j, j] for j in range(max(0, i - d), min(i, d) + 1) ) for i in range(2 * d + 1) ] )
[docs] def hh(self): r"""Shorthand for :meth:`HodgeDiamond.hochschild`""" return self.hochschild()
[docs] def arises_from_variety(self): r"""Check whether the Hodge diamond can arise from a smooth projective variety The constraints are: - satisfy Hodge symmetry - satisfy Serre symmetry - there is no Lefschetz twist """ return ( self.is_hodge_symmetric() and self.is_serre_symmetric() and self.lefschetz_power() == 0 )
[docs] def is_zero(self): r"""Check whether the Hodge diamond is identically zero""" return self.matrix.is_zero()
[docs] def lefschetz_power(self): r""" Return the twist by the Lefschetz motive that is present In other words, we see how divisible the Hodge--Poincaré polynomial is with respect to the monomial $x^iy^i$ """ if self.is_zero(): return 0 i = 0 while (self.x**i * self.y**i).divides(self.polynomial): i += 1 return i - 1
[docs] def dimension(self): r"""Dimension of the Hodge diamond This takes twists by the Lefschetz class into account: we untwist by the maximal power and only then determine how big the diamond is. EXAMPLES: A point is 0-dimensional:: sage: from diamond import * sage: point().dimension() 0 The Lefschetz diamond is also 0-dimensional:: sage: print(lefschetz()) 0 0 0 1 sage: lefschetz().dimension() 0 """ assert self.is_hodge_symmetric() if self.is_zero(): return -1 return ( max( [ i for i in range(self.matrix.ncols()) if not self.matrix.column(i).is_zero() ] ) - self.lefschetz_power() )
[docs] def level(self): r"""Compute the level (or complexity) of the Hodge diamond This is a measure of the width of the non-zero part of the Hodge diamond. EXAMPLES: The simplest case is projective space, with level zero:: sage: from diamond import * sage: all(Pn(n).level() == 0 for n in range(10)) True For intersections of 2 quadrics it alternates between zero and one:: sage: all(complete_intersection([2,2], 2*n).level() == 0 for n in range(5)) True sage: all(complete_intersection([2,2], 2*n+1).level() == 1 for n in range(5)) True A Calabi-Yau variety (e.g. a hypersurface of degree $n+1$ in $\\mathbb{P}^n$) has maximal level:: sage: all(hypersurface(n+2, n).level() == n for n in range(10)) True """ return max( abs(m.degrees()[0] - m.degrees()[1]) for m in self.polynomial.monomials() )
[docs] def blowup(self, other, codim=None): r"""Compute Hodge diamond of blowup No consistency checks are performed, this just naively applies the blowup formula from Hodge theory. INPUT: - ``other`` -- Hodge diamond of the center of the blowup - ``codim`` -- codimension of the center (optional), in case it is not the Hodge diamond of an honest variety EXAMPLES: A cubic surface is the blowup of $\\mathbb{P}^2$ in 6 points:: sage: from diamond import * sage: Pn(2).blowup(6*point()) == hypersurface(3, 2) True """ # let's guess the codimension if codim is None: codim = self.dimension() - other.dimension() return self + sum(other(i) for i in range(1, codim))
[docs] def bundle(self, rank): r"""Compute the Hodge diamond of a projective bundle This applies the bundle formula from Hodge theory without any consistency checks. INPUT: - ``rank``: rank of the vector bundle on ``self`` EXAMPLES: A projective bundle on a point is a projective space:: sage: from diamond import * sage: point().bundle(3) == Pn(2) True A quadric surface is a $\\mathbb{P}^1$-bundle on $\\mathbb{P}^1$:: sage: Pn(1).bundle(2) == hypersurface(2, 2) True """ return sum(self(i) for i in range(rank))
[docs] def mirror(self): r"""Compute the mirror Hodge diamond EXAMPLES: The mirror to a quintic 3-fold is the following:: sage: from diamond import * sage: print(hypersurface(5, 3).mirror()) 1 0 0 0 101 0 1 1 1 1 0 101 0 0 0 1 """ assert self.arises_from_variety() n = self.dimension() x, y = self.x, self.y return self.parent()( sum( cf * x ** (n - exp[0]) * y ** exp[1] for exp, cf in self.polynomial.dict().items() ) )
class HodgeDiamondRing(Singleton, Parent): def __init__(self): """ TESTS:: sage: from diamond import * sage: H = HodgeDiamondRing() sage: TestSuite(H).run() # not tested (works only in the console) """ Parent.__init__(self, category=Rings().Commutative()) def _repr_(self) -> str: """ """ return "Ring of Hodge diamonds" def _element_constructor_(self, *args, **keywords): m = args[0] if m in ZZ: m = matrix(ZZ, 1, 1, [m]) elif isinstance(m, MPolynomial): m = _to_matrix(m) elif isinstance(m, (list, tuple)): m = matrix(m) elt = self.element_class(self, m) if keywords.get("from_variety", False): assert elt.arises_from_variety(), """The matrix does not satisfy the conditions satisfied by the Hodge diamond of a smooth projective variety.""" return elt def from_matrix(self, m, from_variety=False): diamond = self.element_class(self, matrix(m)) # get rid of trailing zeroes from the diamond diamond.matrix = _to_matrix(diamond.polynomial) return diamond def one(self): return point() def zero(self): return zero() def an_element(self): return K3() def _coerce_map_from_(self, R): return R is ZZ Element = HodgeDiamond
[docs] class HochschildHomology(Element): r""" This class implements some methods to work with (the dimensions of) Hochschild homology spaces, associated to the :class:`HodgeDiamond` class. The documentation is not intended to be complete, as this is mostly for my own sake. """ # exposes R and t for external use R = LaurentPolynomialRing(ZZ, "t") t = R.gen() def __init__(self, parent, L): r""" Constructor for Hochschild homology dimensions of smooth and proper dg categories, so that Serre duality holds. INPUT: - ``L`` -- a list of integers of length 2n+1 representing $\\mathrm{HH}_{-n}$ to $\\mathrm{HH}_n$, such that ``L[i] == L[2n - i]`` EXAMPLES:: sage: from diamond import * sage: HochschildHomology.from_list([1,0,22,0,1]) Hochschild homology vector of dimension 2 TESTS:: sage: from diamond import * sage: k = K3().hh() sage: k + k Hochschild homology vector of dimension 2 sage: k*k Hochschild homology vector of dimension 4 sage: k**2 Hochschild homology vector of dimension 4 sage: k.sym(2) Hochschild homology vector of dimension 4 sage: 1 + k Hochschild homology vector of dimension 2 sage: sum(k for i in range(2)) Hochschild homology vector of dimension 2 sage: 3*k Hochschild homology vector of dimension 2 """ assert len(L) % 2, "length needs to be odd, to reflect Serre duality" assert all( L[i] == L[len(L) - i - 1] for i in range(len(L)) ), "Serre duality is not satisfied" self._L = L Element.__init__(self, parent)
[docs] @classmethod def from_list(cls, L): r""" Constructor for Hochschild homology dimensions from a list. INPUT: - ``L`` -- a list of integers representing $\\mathrm{HH}_{-n}$ to $\\mathrm{HH}_n$ EXAMPLES:: sage: from diamond import * sage: HochschildHomology.from_list([1,0,22,0,1]) Hochschild homology vector of dimension 2 """ return HochschildHomologies()(L)
[docs] @classmethod def from_positive(cls, L): r""" Constructor for Hochschild homology dimensions from a list when only the positive part is given. INPUT: - ``L`` -- a list of integers representing $\\mathrm{HH}_0$ to $\\mathrm{HH}_n$ EXAMPLES:: sage: from diamond import * sage: HochschildHomology.from_positive([22,0,1]) Hochschild homology vector of dimension 2 """ return HochschildHomologies()(L, positive=True)
[docs] @classmethod def from_polynomial(cls, f): """ Constructor for Hochschild homology dimensions from Hochschild--Poincaré Laurent polynomial INPUT - ``f`` -- the Hochschild--Poincaré Laurent polynomial EXAMPLES:: sage: from diamond import * sage: x = LaurentPolynomialRing(ZZ, 'x').gen() sage: HochschildHomology.from_polynomial(x**-2+20+x**2) Hochschild homology vector of dimension 2 """ return HochschildHomologies()(f)
@property def polynomial(self): """ EXAMPLES:: sage: from diamond import * sage: h = HochschildHomology.from_list([1,0,22,0,1]) sage: h.polynomial t^-2 + 22 + t^2 """ return HochschildHomology.R( {i: self[i] for i in range(-self.dimension(), self.dimension() + 1)} ) def _repr_(self) -> str: """ EXAMPLES:: sage: from diamond import * sage: HochschildHomology.from_list([1,0,22,0,1]) Hochschild homology vector of dimension 2 """ return f"Hochschild homology vector of dimension {self.dimension()}" def __str__(self) -> str: """ EXAMPLES:: sage: from diamond import * sage: print(HochschildHomology.from_list([1,0,22,0,1])) -2 -1 0 1 2 1 0 22 0 1 """ return str(self.pprint()) def __table(self): if self.is_zero(): return table([[0], [0]]) indices = list(range(-self.dimension(), self.dimension() + 1)) return table([indices, [self[i] for i in indices]]) def pprint(self, output="table"): if output == "table": return self.__table() return self._L
[docs] def dimension(self): r"""Largest index ``i`` such that $\\mathrm{HH}_i\\neq 0$ EXAMPLES:: sage: from diamond import * sage: h = HochschildHomology.from_list([1,0,22,0,1]) sage: h.dimension() 2 """ if self.is_zero(): return -1 return (len(self._L) // 2) - min([i for i, d in enumerate(self._L) if d != 0])
[docs] def is_zero(self): """ EXAMPLES:: sage: from diamond import * sage: h = HochschildHomology.from_list([1,0,22,0,1]) sage: h.is_zero() False """ return all(cf == 0 for cf in self._L)
[docs] def euler(self): """ Euler characteristic of Hochschild homology EXAMPLES:: sage: from diamond import * sage: h = HochschildHomology.from_list([1,0,22,0,1]) sage: h.euler() 24 """ return self.polynomial(-ZZ.one())
def _add_(self, other): return HochschildHomology.from_polynomial(self.polynomial + other.polynomial) def _sub_(self, other): return HochschildHomology.from_polynomial(self.polynomial - other.polynomial) def _mul_(self, other): return HochschildHomology.from_polynomial(self.polynomial * other.polynomial) def __pow__(self, i): return HochschildHomology.from_polynomial(self.polynomial**i) def __eq__(self, other): if not isinstance(other, HochschildHomology): return False return self.polynomial == other.polynomial def __ne__(self, other): return not self == other def __getitem__(self, i): if i > len(self._L) // 2: return 0 return self._L[len(self._L) // 2 - i] def __iter__(self): return self._L.__iter__()
[docs] def symmetric_power(self, k): r""" Hochschild homology of the Ganter--Kapranov symmetric power of a smooth and proper dg category This is possibly only a heuristic (I didn't check for proofs in the literature) based on the decomposition of Hochschild homology for a quotient stack, as discussed in the paper of Polishchuk--Van den Bergh. EXAMPLES:: sage: from diamond import * sage: k = K3().hh() sage: k.symmetric_power(2) Hochschild homology vector of dimension 4 sage: print(_) -4 -3 -2 -1 0 1 2 3 4 1 0 23 0 276 0 23 0 1 """ def summand(f, k): assert all(c > 0 for c in f.coefficients()) t = HochschildHomology.t # trivial case if f.number_of_terms() == 0: return f # base case elif f.number_of_terms() == 1: i = f.exponents()[0] a = f.coefficients()[0] if i % 2 == 0: return binomial(a + k - 1, k) * t ** (k * i) else: return binomial(a, k) * t ** (k * i) # general case else: # splitting f into monomial and the difference g = f.coefficients()[0] * t ** (f.exponents()[0]) h = f - g return sum(summand(g, j) * summand(h, k - j) for j in range(k + 1)) # see the object C^{(\lambda)} in the Polishchuk--Van den Bergh paper return HochschildHomology.from_polynomial( sum( prod(summand(self.polynomial, ri) for ri in P.to_exp()) for P in Partitions(k) ) )
[docs] def sym(self, k): """Shorthand for ```HochschildHomology.symmetric_power```""" return self.symmetric_power(k)
class HochschildHomologies(Singleton, Parent): def __init__(self): """ TESTS:: sage: from diamond import * sage: H = HochschildHomologies() sage: TestSuite(H).run() # not tested (works only in the console) """ Parent.__init__(self, category=Rings().Commutative()) def _repr_(self) -> str: """ TESTS:: sage: from diamond import * sage: HochschildHomologies() Ring of Hochschild homology vectors """ return "Ring of Hochschild homology vectors" def _element_constructor_(self, *args, **keywords): if len(args) == 1: args = args[0] if args in ZZ: args = [args] if isinstance(args, (tuple, list)): if keywords.get("positive", False): # right half of the list L = list(reversed(args))[:-1] + args else: # full list L = args else: # a Laurent polynomial L = list(args) return self.element_class(self, L) def one(self): return self.element_class(self, [1]) def zero(self): return self.element_class(self, [0]) def _coerce_map_from_(self, R): return R is ZZ def an_element(self): return K3().hochschild() Element = HochschildHomology # ==== Now a collection of diamonds ====
[docs] def zero(): r"""Hodge diamond for the empty space EXAMPLES: Zero:: sage: from diamond import * sage: print(zero()) 0 """ return HodgeDiamond.from_matrix(matrix([[0]]), from_variety=True)
[docs] def point(): r"""Hodge diamond for the point EXAMPLES: The point:: sage: from diamond import * sage: print(point()) 1 """ return HodgeDiamond.from_matrix(matrix([[1]]), from_variety=True)
[docs] def lefschetz(): r"""Hodge diamond for the Lefschetz motive This is the Hodge-Poincaré polynomial of the affine line. EXAMPLES: The affine line:: sage: from diamond import * sage: print(lefschetz()) 0 0 0 1 We can take powers of it, to get the Hodge-Poincaré polynomial for higher- dimensional affine spaces:: sage: print(lefschetz()**3) 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 """ return point()(1)
[docs] def Pn(n): r""" Hodge diamond for projective space of dimension $n$ INPUT: - ``n``: dimension, non-negative integer EXAMPLES: The zero-dimensional case is a point:: sage: from diamond import * sage: Pn(0) == point() True In general projective space is the sum of powers of the Lefschetz class:: sage: all(Pn(n) == sum([lefschetz()**i for i in range(n + 1)]) for n in range(1, 10)) True """ assert n >= 0 return HodgeDiamond.from_matrix(matrix.identity(n + 1), from_variety=True)
[docs] def curve(genus): """ Hodge diamond for a curve of a given genus. INPUT: - ``genus``: the genus of the curve, non-negative integer EXAMPLE: A curve of genus 0 is the 1-dimensional projective space:: sage: from diamond import * sage: curve(0) == Pn(1) True A curve of genus 1 is an abelian variety of dimension 1:: sage: curve(1) == abelian(1) True A curve of genus 2:: sage: print(curve(2)) 1 2 2 1 """ assert genus >= 0 return HodgeDiamond.from_matrix(matrix([[1, genus], [genus, 1]]), from_variety=True)
[docs] def surface(genus, irregularity, h11): r""" Hodge diamond for a surface $S$ with given invariants These invariants are the geometric genus, the irregularity and the middle Hodge numbers ``h11``. INPUT: - ``genus`` -- geometric genus of the surface, $\\dim\\mathrm{H}^2(S,\\mathcal{O}_S)$, a non-negative integer - ``irregularity`` -- irregularity of the surface, $\\dim\\mathrm{H}^1(S,\\mathcal{O}_S)$, a non-negative integer - ``h11`` -- middle Hodge number, $\\dim\\mathrm{H}^1(S,\\Omega_S^1)$, a non-negative integer EXAMPLES: The projective plane:: sage: from diamond import * sage: Pn(2) == surface(0, 0, 1) True A K3 surface:: sage: K3() == surface(1, 0, 20) True """ assert genus >= 0 assert irregularity >= 0 assert h11 >= 0 pg = genus q = irregularity return HodgeDiamond.from_matrix( matrix([[1, q, pg], [q, h11, q], [pg, q, 1]]), from_variety=True )
[docs] def symmetric_power(n, genus): r""" Hodge diamond for the nth symmetric power of a curve of given genus For the proof, see Example 1.1(1) of [MR2777820]. An earlier reference, probably in Macdonald, should exist. * [MR2777820] Laurentiu--Schuermann, Hirzebruch invariants of symmetric products. Topology of algebraic varieties and singularities, 163–177, Contemp. Math., 538, Amer. Math. Soc., 2011. INPUT: - ``n`` -- exponent of the symmetric power - ``genus`` -- genus of the curve, a non-negative integer EXAMPLES: The symmetric square of a genus 3 curve:: sage: from diamond import * sage: print(symmetric_power(2, 3)) 1 3 3 3 10 3 3 3 1 If $n=1$ we get the curve back:: sage: all(symmetric_power(1, g) == curve(g) for g in range(10)) True If $n=0$ we get the point:: sage: symmetric_power(0, 4) == point() True If $n<0$ we have the empty space:: sage: symmetric_power(-1, 4) == zero() True """ def hpq(g, n, p, q): assert p <= n assert q <= n if p <= q and p + q <= n: return sum([binomial(g, p - k) * binomial(g, q - k) for k in range(p + 1)]) if p > q: return hpq(g, n, q, p) if p + q > n: return hpq(g, n, n - p, n - q) if n < 0: return zero() M = matrix(n + 1) for i in range(n + 1): for j in range(n + 1): M[i, j] = hpq(genus, n, i, j) return HodgeDiamond.from_matrix(M, from_variety=True)
[docs] def jacobian(genus): """ Hodge diamond for the Jacobian of a genus $g$ curve This is an abelian variety of dimension `genus`, so we call :func:`abelian` INPUT: - ``genus`` -- genus of the curve EXAMPLES: The Jacobian of a genus 3 curve:: sage: from diamond import * sage: print(jacobian(3)) 1 3 3 3 9 3 1 9 9 1 3 9 3 3 3 1 For the projective line we get a point:: sage: jacobian(0) == point() True The Jacobian of an elliptic curve is isomorphic to it:: sage: jacobian(1) == curve(1) True """ return abelian(genus)
[docs] def abelian(dimension): r""" Hodge diamond for an abelian variety of a given dimension. The $g$th power of an elliptic curve is an abelian variety of the given dimension, so we just use this computation method. INPUT: - ``dimension`` -- dimension of the abelian variety EXAMPLES: A 1-dimensional abelian variety is an elliptic curve:: sage: from diamond import * sage: abelian(1) == curve(1) True A 2-dimensional abelian variety is a surface with known Hodge numbers:: sage: abelian(2) == surface(1, 2, 4) True """ return curve(1) ** dimension
[docs] def kummer_resolution(dimension): """ Hodge diamond for the standard resolution of the Kummer variety of an abelian variety of a given dimension. There's an invariant part (Hodge numbers of even degree) and the resolution of the $2^2g$ singularities is added. INPUT: - ``dimension`` -- dimension of the abelian variety taken as input EXAMPLES: The Kummer resolution of the involution on an abelian surface is a K3 surface:: sage: from diamond import * sage: kummer_resolution(2) == K3() True """ g = dimension invariant = sum( [ jacobian(g).polynomial.monomial_coefficient(m) * m for m in jacobian(g).polynomial.monomials() if m.degree() % 2 == 0 ] ) return HodgeDiamond.from_polynomial(invariant) + sum( [2 ** (2 * g) * point()(i) for i in range(1, g)] )
[docs] def moduli_vector_bundles(rank, degree, genus): r""" Hodge diamond for the moduli space of vector bundles of given rank and fixed determinant of given degree on a curve of a given genus. For the proof, see Corollary 5.1 of [MR1817504]. * [MR1817504] del Baño, On the Chow motive of some moduli spaces. J. Reine Angew. Math. 532 (2001), 105–132. If the Hodge diamond for the moduli space with non-fixed determinant of degree `d` is required, this can be obtained by:: jacobian(g) * moduli_vector_bundles(r, d, g) INPUT: - `rank` -- rank of the bundles, at least 2 - `degree` -- degree of the fixed determinant, coprime to rank - `genus` -- genus of the curve, at least 2 EXAMPLES: The case of rank 2, degree 1 and genus 2 is famously the intersection of 2 quadrics in $\\mathbb{P}^5$:: sage: from diamond import * sage: moduli_vector_bundles(2, 1, 2) == complete_intersection([2, 2], 3) True """ r = rank d = degree g = genus assert r >= 2, "rank needs to be at least 2" assert g >= 2, "genus needs to be at least 2" assert gcd(r, d) == 1, "rank and degree need to be coprime" R = HodgeDiamond.R x = HodgeDiamond.x y = HodgeDiamond.y def bracket(x): """Return the decimal part of a fraction.""" return x - x.floor() def one(C, g): """Return the first factor in del Bano's formula.""" return ( (-1) ** (len(C) - 1) * ((1 + x) ** g * (1 + y) ** g) ** (len(C) - 1) / (1 - x * y) ** (len(C) - 1) ) # already corrected the factor from the Jacobian def two(C, g): """Return the second factor in del Bano's formula.""" return prod( [ prod( [ (1 + x**i * y ** (i + 1)) ** g * (1 + x ** (i + 1) * y**i) ** g / ((1 - x**i * y**i) * (1 - x ** (i + 1) * y ** (i + 1))) for i in range(1, C[j]) ] ) for j in range(len(C)) ] ) def three(C, g): """Return the third factor in del Bano's formula.""" return prod([1 / (1 - (x * y) ** (C[j] + C[j + 1])) for j in range(len(C) - 1)]) def four(C, d, g): """Return the fourth factor in del Bano's formula.""" exponent = sum( [sum([(g - 1) * C[i] * C[j] for i in range(j)]) for j in range(len(C))] ) + sum( [ (C[i] + C[i + 1]) * bracket(-(sum(C[0 : i + 1]) * d / C.size())) for i in range(len(C) - 1) ] ) return (x * y) ** exponent return HodgeDiamond.from_polynomial( R( sum( [ one(C, g) * two(C, g) * three(C, g) * four(C, d, g) for C in Compositions(r) ] ) ), from_variety=True, )
[docs] def seshadris_desingularisation(genus): r""" Hodge diamond for Seshadri's desingularisation of the moduli space of rank 2 bundles with trivial determinant on a curve of a given genus $g$. For the statement, see Corollary 3.18 of [MR1895918]. * [MR1895918] del Baño, On the motive of moduli spaces of rank two vector bundles over a curve. Compositio Math. 131 (2002), 1-30. INPUT: - ``genus`` -- the genus $g$, at least 2 EXAMPLES: For $g=2$ nothing needs to be desingularised, and the answer is $\\mathbb{P}^3$:: sage: from diamond import * sage: seshadris_desingularisation(2) == Pn(3) True Already for $g=3$ the result is not a familiar variety, so we just check the Euler characteristic:: sage: seshadris_desingularisation(3).euler() == 112 True """ g = genus assert g >= 2, "genus needs to be at least 2" R = HodgeDiamond.R x = HodgeDiamond.x y = HodgeDiamond.y L = x * y A = (1 + x) * (1 + y) B = (1 - x) * (1 - y) one = ((1 + x * L) ** g * (1 + y * L) ** g - L**g * A**g) / ((1 - L) * (1 - L**2)) two = (A**g * (L - L**g) / (1 - L) + (A**g + B**g) / 2) / ( 1 + L ) # fixed typo in del Bano: compare to 3.12, motive of P^(g-2) three = (A**g - 2 ** (2 * g)) / 2 * ((1 - L ** (g - 1)) / (1 - L)) ** 2 four = ( (B**g / 2 - 2 ** (2 * g - 1)) * (1 - L ** (2 * g - 2)) / (1 - L**2) ) # fixed typo in del Bano: compare to 3.14, power of L at the end five = ( (1 - L**g) * (1 - L ** (g - 1)) * (1 - L ** (g - 2)) / ((1 - L) * (1 - L**2) * (1 - L**3)) + (1 - L**g) * (1 - L ** (g - 1)) / ((1 - L) * (1 - L**2)) * L ** (g - 2) ) * 2 ** (2 * g) return HodgeDiamond.from_polynomial(R(one - two + three + four + five))
[docs] def moduli_parabolic_vector_bundles_rank_two(genus, alpha): r""" Hodge diamond for the moduli space of parabolic rank 2 bundles with fixed determinant of odd degree on a curve of genus $g$. See Corollary 5.34 of [2011.14872]. * [2011.14872] Fu--Hoskins--Pepin Lehalleur, Motives of moduli spaces of bundles on curves via variation of stability and flips This is not a proof of the formula we implemented per se, but it should be correct. Also, it could be that the choice of weights give something singular / stacky. Then it'll give bad output without warning. You have been warned. INPUT: - ``genus`` -- the genus of the curve - ``alpha`` -- the weights of the parabolic bundles """ total = sum(alpha) N = len(alpha) rN = range(N) def d(j, alpha): return len( [ 1 for I in Subsets(rN) if (len(I) - j) % 2 == 0 and j - 1 < (len(I) + total - 2 * sum(alpha[i] for i in I)) < j + 1 ] ) def c(j, alpha): return binomial(N, j) - d(j, alpha) def b(j, alpha): return sum(((i + 2) // 2) * c(j - i, alpha) for i in range(j + 1)) N = len(alpha) if genus == 0: M = zero() elif genus == 1: M = curve(1) elif genus >= 2: M = moduli_vector_bundles(2, 1, genus) result = M * (Pn(1) ** N) + sum( [b(j, alpha) * jacobian(genus)(genus + j) for j in range(N - 2)] ) assert result.arises_from_variety() return result
[docs] def fano_variety_intersection_quadrics_odd(g, k): r""" Hodge diamond for the Fano variety of $k$-planes on the intersection of two quadrics in $\\mathbb{P}^{2g+1}$, using [MR3689749]. We have that for $k=g-2$ we get M_C(2,L) as above, for deg L odd. * [MR3689749] Chen--Vilonen--Xue, On the cohomology of Fano varieties and the Springer correspondence, Adv. Math. 318 (2017), 515–533. EXAMPLES: For $k=0$ we have the intersection of two quadrics:: sage: from diamond import * sage: fano_variety_intersection_quadrics_odd(2, 0) == complete_intersection([2, 2], 3) True sage: fano_variety_intersection_quadrics_odd(5, 0) == complete_intersection([2, 2], 9) True For $k=g-2$ we recover the moduli space of rank 2 bundles with odd determinant on a curve of genus $g$:: sage: from diamond import * sage: fano_variety_intersection_quadrics_odd(11, 9) == moduli_vector_bundles(2, 1, 11) True For $k=g-1$ it is the Jacobian of $C$:: sage: from diamond import * sage: fano_variety_intersection_quadrics_odd(12, 11) == jacobian(12) True For other $k$ it is an interesting variety:: sage: from diamond import * sage: fano_variety_intersection_quadrics_odd(12, 9) Hodge diamond of size 51 and dimension 50 """ assert g >= 2, "genus needs to be at least 2" assert k in range(g), "non-empty only from 0 to g-1" if k == g - 1: return jacobian(g) # go back to the notation of Chen--Vilonen--Xue i = g - k # dimension of the Fano variety d = (g - i + 1) * (2 * i - 1) # multiplicity N_i(k, j) as in Theorem 1.1 of [MR3689749] def N(i, k, j): R = PowerSeriesRing(ZZ, default_prec=2 * d + 1) q = R.gen(0) return ( q ** (-(j - i + 1) * (2 * i - 1)) * (1 - q ** (4 * j)) * prod([1 - q ** (2 * l) for l in range(j - i + 2, i + j - 1)]) / prod([1 - q ** (2 * l) for l in range(1, 2 * i - 1)]) )[k] x, y = (HodgeDiamond.x, HodgeDiamond.y) polynomial = 0 for k in range(2 * d + 1): for j in range(i - 1, g + 1): if N(i, d - k, j) == 0: continue # the `g-j`th exterior power of the first cohomology of the curve # is the `g-j`th cohomology of the Jacobian dimensions = jacobian(g).row(g - j) # turn the dimensions into a polynomial piece = sum( [dimensions[m] * x**m * y ** ((g - j) - m) for m in range(g - j + 1)] ) # the appropriate Lefschetz twist to put it in the right cohomological degree twist = x ** ((k - (g - j)) / 2) * y ** ((k - (g - j)) / 2) # they reindex using `d-k` polynomial = polynomial + N(i, d - k, j) * piece * twist return HodgeDiamond.from_polynomial(polynomial, from_variety=True)
[docs] def fano_variety_intersection_quadrics_even(g, k): r""" Hodge diamond for the Fano variety of $i-1$-planes on the intersection of two quadrics in $\\mathbb{P}^{2g+1}$, using [1510.05986v3]. * [1510.05986v3] Chen--Vilonen--Xue, Springer correspondence, hyperelliptic curves, and cohomology of Fano varieties INPUT: - ``g`` -- half of the dimension of the quadrics - ``k`` -- dimension of the linear subspaces on the intersection of quadrics, at most $g-1$ EXAMPLES: For $k=0$ we have the intersection of two quadrics:: sage: from diamond import * sage: fano_variety_intersection_quadrics_even(2, 0) == complete_intersection([2, 2], 2) True sage: fano_variety_intersection_quadrics_even(5, 0) == complete_intersection([2, 2], 8) True We have that for $k = g-2$ we get the moduli space of parabolic bundles on $\\mathbb{P}^1$ with weight $1/2$ in $2g+3$ points:: sage: from diamond import * sage: moduli_parabolic_vector_bundles_rank_two(0, [1/2]*5) == fano_variety_intersection_quadrics_even(2, 0) True sage: moduli_parabolic_vector_bundles_rank_two(0, [1/2]*9) == fano_variety_intersection_quadrics_even(4, 2) True For $k=g-1$ we get a finite reduced scheme of length $4^g$:: sage: from diamond import * sage: print(fano_variety_intersection_quadrics_even(4, 3)) 256 """ assert g >= 2, "genus needs to be at least 2" assert k in range(g), "non-empty only from 0 to g-1" def M(k, j): index = k - j * (g - i) if index < 0: return 0 else: return q_binomial(2 * g - i - j, i - j).padded_list(index + 1)[index] i = k + 1 x, _ = (HodgeDiamond.x, HodgeDiamond.y) R = x.parent() polynomial = R( { (k, k): sum(M(k, j) * binomial(2 * g + 1, j) for j in range(i + 1)) for k in range(i * (2 * g - 2 * i) + 1) } ) return HodgeDiamond.from_polynomial(polynomial, from_variety=True)
[docs] def quot_scheme_curve(genus, length, rank): """ Hodge diamond for the Quot scheme of zero-dimensional quotients of given length of a vector bundle of given rank on a curve of given genus. For the proof, see Proposition 4.5 of [1907.00826] (or rather, the reference [Bif89] in there) * [1907.00826] Bagnarol--Fantechi--Perroni, On the motive of zero-dimensional Quot schemes on a curve """ def dn(P): # shift in indexing because we start at 0 return sum(i * ni for i, ni in enumerate(P)) return sum( [ prod([symmetric_power(ni, genus) for ni in P])(dn(P)) for P in IntegerVectors(length, rank) ] )
def hilbtwo(X): """ Hodge diamond for the Hilbert square of any smooth projective variety For the proof, see e.g. lemma 2.6 of [MR2506383], or the corollary on page 507 of [MR1382733] (but it can be said to be classical). * [MR2506383] Muñoz--Ortega--Vázquez-Gallo, Hodge polynomials of the moduli spaces of triples of rank (2,2). Q. J. Math. 60 (2009), no. 2, 235–272. * [MR1382733] Cheah, On the cohomology of Hilbert schemes of points, J. Algebraic Geom. 5 (1996), no. 3, 479-511 INPUT: - ``X`` - Hodge diamond of the smooth projective variety EXAMPLES: We recover the Hilbert square of a surface:: sage: from diamond import * sage: hilbtwo(K3()) == hilbn(K3(), 2) True """ assert X.arises_from_variety() d = X.dimension() return HodgeDiamond.from_polynomial( X.R( ((X.polynomial) ** 2 + X.polynomial(-X.x**2, -X.y**2)) / 2 + sum([X.x**i * X.y**i * X.polynomial for i in range(1, d)]) ), from_variety=True, ) def hilbthree(X): """ Hodge diamond of the Hilbert cube of any smooth projective variety The corollary on page 507 of [MR1382733]. * [MR1382733] Cheah, On the cohomology of Hilbert schemes of points, J. Algebraic Geom. 5 (1996), no. 3, 479-511 INPUT: - ``X`` - Hodge diamond of the smooth projective variety EXAMPLES: We recover the Hilbert cube of a surface:: sage: from diamond import * sage: hilbthree(K3()) == hilbn(K3(), 3) True """ assert X.arises_from_variety() d = X.dimension() X2 = X**2 R = HodgeDiamond.R return HodgeDiamond.from_polynomial( (X**3).polynomial / 6 + X.polynomial * X.polynomial(-X.x**2, -X.y**2) / 2 + X.polynomial(X.x**3, X.y**3) / 3 + R.sum(X2(i).polynomial for i in range(1, d)) + R.sum(X(i + j).polynomial for i in range(1, d) for j in range(i, d)), from_variety=True, )
[docs] def K3n(n): r""" Hodge diamond of the Hilbert scheme of $n$ points on a K3 surface This is the first family of hyperkähler varieties, constructed by Beauville. INPUT: - ``n`` -- number of points EXAMPLES: For $n=1$ we have a K3 surface:: sage: from diamond import * sage: K3n(1) == K3() True For $n\\geq 2$ we have second Betti number 23:: sage: all(K3n(n).betti()[2] == 23 for n in range(2, 5)) True """ return hilbn(K3(), n)
[docs] def generalised_kummer(n): r""" Hodge diamond of the $n$th generalised Kummer variety For the proof, see Corollary 1 of [MR1219901]. * [MR1219901] Göttsche--Soergel, Perverse sheaves and the cohomology of Hilbert schemes of smooth algebraic surfaces. Math. Ann. 296 (1993), no. 2, 235–245. EXAMPLES: The first generalised Kummer is just a point:: sage: from diamond import * sage: generalised_kummer(1) == point() True The second generalised Kummer is the Kummer K3 surface:: sage: generalised_kummer(2) == K3() True The higher generalised Kummers are hyperkähler varieties with second Betti number 7:: sage: all(generalised_kummer(n).betti()[2] == 7 for n in range(3, 10)) True """ x = HodgeDiamond.x y = HodgeDiamond.y def product(n): hd = sum( gcd(a := ap.to_exp_dict()) ** 4 * (x * y) ** (n - sum(a.values())) * prod( [ sum( [ prod( [ ~((j**bj) * factorial(bj)) * ((1 - x**j) * (1 - y**j)) ** (2 * bj) for j, bj in b.to_exp_dict().items() ] ) for b in Partitions(ai) ] ) for ai in a.values() ] ) for ap in Partitions(n) ) return HodgeDiamond.R(hd(-x, -y)) # Göttsche--Soergel gives the polynomial for A\times Kum^n A, so we quotient out A return HodgeDiamond.from_polynomial(product(n) // product(1), from_variety=True)
[docs] def ogrady6(): r""" Hodge diamond for O'Grady's exceptional 6-dimensional hyperkähler variety For the proof, see Theorem 1.1 of [MR3798592]. * [MR3798592] Mongardi--Rapagnetta--Saccà, The Hodge diamond of O'Grady's six-dimensional example. Compos. Math. 154 (2018), no. 5, 984–1013. EXAMPLES: The second Betti number is 8:: sage: from diamond import * sage: ogrady6().betti()[2] == 8 True """ H = HodgeDiamond.from_matrix return H( [ [1, 0, 1, 0, 1, 0, 1], [0, 6, 0, 12, 0, 6, 0], [1, 0, 173, 0, 173, 0, 1], [0, 12, 0, 1144, 0, 12, 0], [1, 0, 173, 0, 173, 0, 1], [0, 6, 0, 12, 0, 6, 0], [1, 0, 1, 0, 1, 0, 1], ], from_variety=True, )
[docs] def ogrady10(): """ Hodge diamond for O'Grady's exceptional 10-dimensional hyperkähler variety For the proof, see theorem A of [1905.03217] * [1905.03217] de Cataldo--Rapagnetta--Saccà, The Hodge numbers of O'Grady 10 via Ngô strings EXAMPLES: The second Betti number is 24:: sage: from diamond import * sage: ogrady10().betti()[2] == 24 True """ H = HodgeDiamond.from_matrix return H( [ [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], [0, 22, 0, 22, 0, 23, 0, 22, 0, 22, 0], [1, 0, 254, 0, 276, 0, 276, 0, 254, 0, 1], [0, 22, 0, 2299, 0, 2531, 0, 2299, 0, 22, 0], [1, 0, 276, 0, 16490, 0, 16490, 0, 276, 0, 1], [0, 23, 0, 2531, 0, 88024, 0, 2531, 0, 23, 0], [1, 0, 276, 0, 16490, 0, 16490, 0, 276, 0, 1], [0, 22, 0, 2299, 0, 2531, 0, 2299, 0, 22, 0], [1, 0, 254, 0, 276, 0, 276, 0, 254, 0, 1], [0, 22, 0, 22, 0, 23, 0, 22, 0, 22, 0], [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], ], from_variety=True, )
[docs] def hilbn(surface, n): """ Hodge diamond for Hilbert scheme of ``n`` points on a smooth projective surface ``S`` For the proof, see Theorem 2.3.14 of [MR1312161]. * [MR1312161] Göttsche, Hilbert schemes of zero-dimensional subschemes of smooth varieties. Lecture Notes in Mathematics, 1572. Springer-Verlag, Berlin, 1994. x+196 pp. INPUT: - ``S`` -- Hodge diamond for smooth projective surface - ``n`` -- number of points """ assert surface.arises_from_variety() assert surface.dimension() == 2 ring_ab = PolynomialRing(ZZ, "a,b") a, b = ring_ab.gens() R = PowerSeriesRing(ring_ab, "t", default_prec=n + 1) t = R.gen().O(n + 1) series = R.one().O(n + 1) # theorem 2.3.14 of Göttsche's book for k in range(1, n + 1): for p in range(3): for q in range(3): eps_pq = (-1) ** (p + q + 1) term = (1 + eps_pq * a ** (p + k - 1) * b ** (q + k - 1) * t**k).O( n + 1 ) series *= term ** (eps_pq * surface[p, q]) coeff_n = series[n] # read off Hodge diamond from the (truncated) series M = matrix(2 * n + 1) for p in range(2 * n + 1): for q in range(2 * n + 1): M[p, q] = coeff_n.coefficient( [min(p, 2 * n - q), min(q, 2 * n - p)] ) # use Serre duality return HodgeDiamond.from_matrix(M, from_variety=True)
[docs] def nestedhilbn(surface, n): """ Hodge diamond for the nested Hilbert scheme ``S^{[n-1,n]}`` This is the unique nested Hilbert scheme of a smooth projective surface ``S`` which is itself smooth (of dimension $2n$) """ assert surface.arises_from_variety() assert surface.dimension() == 2 ring_xy = PolynomialRing(ZZ, "x,y") x, y = ring_xy.gens() R = PowerSeriesRing(ring_xy, "t", default_prec=n + 1) t = R.gen().O(n + 1) series = R.one().O(n + 1) for k in range(1, n + 1): for p in range(3): for q in range(3): s_pq = surface[p, q] if p + q % 2: term = (1 + x ** (p + k - 1) * y ** (q + k - 1) * t**k).O(n + 1) series *= term**s_pq else: term = (1 - x ** (p + k - 1) * y ** (q + k - 1) * t**k).O(n + 1) series *= term ** (-s_pq) series = series * R(surface.polynomial) * t / (1 - x * y * t) top_poly = series[n] # read off Hodge diamond from the (truncated) series M = matrix(2 * n + 1) for p in range(2 * n + 1): for q in range(2 * n + 1): M[p, q] = top_poly.coefficient([min(p, 2 * n - q), min(q, 2 * n - p)]) # use Serre duality return HodgeDiamond.from_matrix(M, from_variety=True)
[docs] def complete_intersection(degrees, dimension): r""" Hodge diamond for a complete intersection of multidegree $(d_1,\\ldots,d_k)$ in $\\mathbb{P}^{n+k}$ For a proof, see théorème 2.3 of exposé XI in SGA7. INPUT: - ``degrees`` -- the multidegree, if it is an integer we interpret it as a hypersurface - ``dimension`` -- the dimension of the complete intersection (not of the ambient space) EXAMPLES: For multidegrees $(1,\\ldots,1) we get a lower-dimension projective space:: sage: from diamond import * sage: complete_intersection(1, 2) == Pn(2) True sage: complete_intersection([1, 1], 2) == Pn(2) True sage: complete_intersection([1, 1], 5) == Pn(5) True The Euler characteristics of cubic hypersurfaces:: sage: [complete_intersection(3, n).euler() for n in range(10)] [3, 0, 9, -6, 27, -36, 93, -162, 351, -672] The Euler characteristics of intersections of 2 quadrics:: sage: [complete_intersection([2, 2], n).euler() for n in range(10)] [4, 0, 8, 0, 12, 0, 16, 0, 20, 0] """ # hypersurface as complete intersection try: degrees = list(degrees) except TypeError: degrees = [degrees] R = PowerSeriesRing(ZZ, ("a", "b"), default_prec=dimension + 2) a, b = R.gens() H = ~((1 + a) * (1 + b)) * ( prod( [ ((1 + a) ** di - (1 + b) ** di) / (a * (1 + b) ** di - b * (1 + a) ** di) for di in degrees ] ) - 1 ) + ~(1 - a * b) H = H.polynomial() M = matrix.identity(dimension + 1) for i in range(dimension + 1): M[i, dimension - i] = H.coefficient([i, dimension - i]) return HodgeDiamond.from_matrix(M, from_variety=True)
[docs] def hypersurface(degree, dimension): r"""Shorthand for a complete intersection of the given dimension where $k=1$""" return complete_intersection(degree, dimension)
[docs] def K3(): """ Hodge diamond for a K3 surface EXAMPLES: The K3 surface:: sage: from diamond import * sage: print(K3()) 1 0 0 1 20 1 0 0 1 """ dia = complete_intersection(4, 2) dia.rename("Hodge diamond of K3 surface") return dia
[docs] def enriques(two=None): r"""Hodge diamond for an Enriques surface It is possible to ask for the Hodge diamond of a classical or (super)singular Enriques surface in characteristic 2. In characteristic 2 the invariants are given in Proposition 1.4.2 of [MR0986969]. * [MR0986969] Cossec--Dolgachev, Enriques surfaces I, Progress in Mathematics, 1989 INPUT: - ``two`` -- optional parameter to indicate the type of surface in characteristic 2 possible values are `"classical"`, `"singular"`, `"supersingular"` EXAMPLES: An ordinary Enriques surface:: sage: from diamond import * sage: print(enriques()) 1 0 0 0 10 0 0 0 1 Enriques surfaces in characteristic 2:: sage: print(enriques(two="classical")) 1 0 1 0 12 0 1 0 1 sage: print(enriques(two="singular")) 1 1 0 1 10 1 0 1 1 sage: print(enriques(two="supersingular")) 1 1 1 1 12 1 1 1 1 """ if two: if two == "classical": return HodgeDiamond.from_matrix([[1, 0, 0], [1, 12, 1], [0, 0, 1]]) if two == "singular": return HodgeDiamond.from_matrix([[1, 1, 1], [0, 10, 0], [1, 1, 1]]) if two == "supersingular": return HodgeDiamond.from_matrix([[1, 1, 1], [1, 12, 1], [1, 1, 1]]) raise ValueError("invalid choice for characteristic 2") return surface(0, 0, 10)
[docs] def ruled(genus): r"""Hodge diamond for a ruled surface These are $\\mathbb{P}^1$-bundles over a curve of given genus. INPUT: - ``genus`` -- genus of the base curve EXAMPLES: For genus 0 we get Hirzebruch surfaces, whose Hodge diamond is that of the quadric surface:: sage: from diamond import * sage: ruled(0) == hypersurface(2, 2) True For higher genus the Hodge diamond looks as follows:: sage: print(ruled(5)) 1 5 5 0 2 0 5 5 1 """ return surface(0, genus, 2)
[docs] def weighted_hypersurface(degree, weights): """ Hodge diamond for a weighted hypersurface of degree ``d`` in ``P(w_0,...,w_n)`` This implements Theorem 7.2 of Fletcher's notes, Working with weighted complete intersections. INPUT: - ``degree`` -- degree of the hypersurface - ``weights`` -- the weights of the weighted projective space, if it is an integer we interpret it as the dimension of P^n EXAMPLES: Elliptic curves can be realised as hypersurfaces in 3 ways:: sage: from diamond import * sage: weighted_hypersurface(3, 2) == weighted_hypersurface(3, [1, 1, 1]) True sage: weighted_hypersurface(3, 2) == curve(1) True sage: weighted_hypersurface(4, [1, 1, 2]) == curve(1) True sage: weighted_hypersurface(6, [1, 2, 3]) == curve(1) True The Fano 3-fold 1.1 is a weighted hypersurface:: sage: fano_threefold(1, 1) == weighted_hypersurface(6, [1,1,1,1,3]) True If the variety is only quasismooth, not smooth, then we have to interpret the Hodge numbers accordingly. For instance, number 2 on Reid's list of 95 K3s has middle Hodge number 19, because the surface is has one node. sage: weighted_hypersurface(5, [1, 1, 1, 2]).middle() [1, 19, 1] """ # weights should be interpreted as dimension of unweighted P^n if isinstance(weights, Integer): weights = [1] * (weights + 1) n = len(weights) - 1 def hij(d, W, i, j): if i + j != n - 1 and i != j: return 0 if i + j != n - 1 and i == j: return 1 w = sum(W) R = PowerSeriesRing(ZZ, default_prec=j * d + d + 1) t = R.gen(0) H = prod((1 - t ** (d - wi)) / (1 - t**wi) for wi in W) if i + j == n - 1 and i != j: return H[j * d + d - w] if i + j == n - 1 and i == j: return H[j * d + d - w] + 1 M = matrix.identity(n) for i in range(n): M[i, n - i - 1] = hij(degree, weights, i, n - i - 1) return HodgeDiamond.from_matrix(M, from_variety=True)
[docs] def cyclic_cover(ramification_degree, cover_degree, weights): r""" Hodge diamond of a cyclic cover of weighted projective space Implementation taken from https://github.com/jxxcarlson/math_research/blob/master/hodge.sage. INPUT: - ``ramification_degree`` -- degree of the ramification divisor - ``cover_degree`` -- size of the cover - ``weights`` -- the weights of the weighted projective space, if it is an integer we interpret it as the dimension of ``P^n`` EXAMPLES: Some K3 surfaces are double covers of $\\mathbb{P}^2$ in a sextic curve:: sage: from diamond import * sage: cyclic_cover(6, 2, 2) == K3() True The Fano 3-fold 1.1 is a double cover of $\\mathbb{P}^3$ in a sextic:: sage: cyclic_cover(6, 2, 3) == fano_threefold(1, 1) True """ # weights should be interpreted as dimension of unweighted P^n if isinstance(weights, Integer): weights = [1] * (weights + 1) weights.append(ramification_degree // cover_degree) return weighted_hypersurface(ramification_degree, weights)
[docs] def partial_flag_variety(D, I): r""" Hodge diamond of a partial flag variety G/P This is computed by counting the number of Schubert cells in the appropriate dimension. INPUT: - ``D`` -- Dynkin type - ``I`` -- indices of vertices to be omitted in defining the parabolic subgroup EXAMPLES: An absolute baby case is projective space:: sage: from diamond import * sage: partial_flag_variety("A5", [2,3,4,5]) == Pn(5) True The next easiest case are quadrics:: sage: partial_flag_variety("B5", [2,3,4,5]) == hypersurface(2, 9) True sage: partial_flag_variety("D5", [2,3,4,5]) == hypersurface(2, 8) True """ R = PolynomialRing(ZZ, "x") x = R.gen() D = DynkinDiagram(D) WG = D.root_system().root_lattice().weyl_group() P = prod(sum(x**i for i in range(n)) for n in WG.degrees()) if not I: Q = 1 else: WL = D.subtype(I).root_system().root_lattice().weyl_group() Q = prod(sum(x**i for i in range(n)) for n in WL.degrees()) return HodgeDiamond.from_matrix( diagonal_matrix((P // Q).coefficients()), from_variety=True )
[docs] def generalised_grassmannian(D, k): r""" Hodge diamond of the generalised Grassmannian of type D modulo the maximal parabolic subgroup $P_k$. This is just shorthand for :func:`partial_flag_variety(D, I)` where ``I`` is the complement of a singleton. INPUT: - ``D`` -- Dynkin type - ``k`` -- the vertex in the Dynkin diagram defining the maximal parabolic """ return partial_flag_variety(D, [i for i in range(1, int(D[1:]) + 1) if i != k])
[docs] def grassmannian(k, n): r""" Hodge diamond of the Grassmannian $\\operatorname{Gr}(k,n)$ of $k$-dimensional subspaces in an $n$-dimensional vector space INPUT: - ``k`` -- dimension of the subspaces - ``n`` -- dimension of the ambient vector space EXAMPLES: Grassmannians are projective spaces if `k` is one or `n-1`:: sage: from diamond import * sage: grassmannian(1, 5) == Pn(4) True sage: grassmannian(7, 8) == Pn(7) True The Grassmannian of 2-planes in a 4-dimensional vector space is the Kleiin quadric:: sage: grassmannian(2, 4) == hypersurface(2, 4) True """ assert 0 <= k <= n if n in [0, k]: return point() x, y = HodgeDiamond.x, HodgeDiamond.y return HodgeDiamond.from_polynomial(q_binomial(n, k)(q=x * y))
[docs] def orthogonal_grassmannian(k, n): r""" Hodge diamond of the orthogonal Grassmannian $\\operatorname{OGr}(k, n)$ of $k$-dimensional subspaces in an $n$-dimensional vector space isotropic with respect to a non-degenerate symmetric bilinear form INPUT: - ``k`` -- dimension of the subspaces - ``n`` -- dimension of the ambient vector space """ if n % 2 == 0: assert k < n // 2 else: assert k <= n // 2 if n % 2 == 0: D = "D" + str(n // 2) if k - 1 == n // 2: # exceptional case: need submaximal parabolic I = list(range(1, n // 2 - 1)) else: I = [i for i in range(1, n // 2 + 1) if i != k] return partial_flag_variety(D, I) else: D = "B" + str(n // 2) I = [i for i in range(1, n // 2 + 1) if i != k] return partial_flag_variety(D, I)
[docs] def symplectic_grassmannian(k, n): r""" Hodge diamond of the symplectic Grassmannian $\\operatorname{SGr}(k, n)$ of $k$-dimensional subspaces in an $n$-dimensional vector space isotropic with respect to a non-degenerate skew-symmetric bilinear form INPUT: - ``k`` -- dimension of the subspaces - ``n`` -- dimension of the ambient vector space """ assert n % 2 == 0 D = "C" + str(n // 2) I = [i for i in range(1, n // 2 + 1) if i != k] return partial_flag_variety(D, I)
[docs] def lagrangian_grassmannian(n): """Shorthand for the symplectic Grassmannian of Lagrangian subspaces""" return symplectic_grassmannian(n, 2 * n)
[docs] def horospherical(D, y=0, z=0): r""" Horospherical varieties as discussed in [1803.05063], with labelling and notation as in op. cit. INPUT: - ``D``: either a Dynkin type from the (small) list of allowed types in the classification _or_ a plaintext label from X1(n), X2, X3(n,m), X4, X5 - ``y``: index for the parabolic subgroup for Y, see classification - ``z``: index for the parabolic subgroup for the closed orbit Z ``y`` and ``z`` must be omitted if a plaintext description is given. * [1803.05063] Gonzales--Pech--Perrin--Samokhin, Geometry of horospherical varieties of Picard rank one """ # treat D as the plaintext description of a horospherical variety # not supposed to be 100% robust if y == 0 and z == 0: X = D # rename for less confusion i = int(X[1]) if i == 1: n = X[3:-1] return horospherical("B" + n, int(n) - 1, int(n)) if i == 2: return horospherical("B3", 1, 3) if i == 3: n = X[3:].split(",")[0] m = int(X[:-1].split(",")[1]) return horospherical("C" + n, m, m - 1) if i == 4: return horospherical("F4", 2, 3) if i == 5: return horospherical("G2", 1, 2) # didn't recognise it so far, so must be wrong raise Exception # determine the Hodge diamond from the blowup description Y = generalised_grassmannian(D, y) Z = generalised_grassmannian(D, z) n = int(D[1:]) if D[0] == "B": assert (n == 3 and y == 1 and z == 3) or (n >= 3 and y == n - 1 and z == n) if n == 3 and y == 1: dimension = 9 else: dimension = n * (n + 3) / 2 elif D[0] == "C": assert n >= 2 and y in range(2, n + 1) and z == y - 1 dimension = y * (2 * n + 1 - y) - y * (y - 1) / 2 elif D == "F4": assert y == 2 and z == 3 dimension = 23 elif D == "G2": assert y == 1 and z == 2 dimension = 7 codimXY = dimension - Y.dimension() codimXZ = dimension - Z.dimension() return Y.bundle(codimXY + 1) + Z - Z.bundle(codimXZ)
[docs] def odd_symplectic_grassmannian(k, n): r""" Hodge diamond of the odd symplectic Grassmannian $\\operatorname{SGr}(k,n)$ Here $n$ is odd. This is just shorthand for a call to :func:`horospherical_variety` for type C, with parameters $\lfloor n/2\rlfloor$, and $Y$ and $Z$ determined by $k$ and $k - 1$. """ assert n % 2 == 1 return horospherical("C" + str(n // 2), k, k - 1)
[docs] def gushel_mukai(n): r""" Hodge diamond for a smooth $n$-dimensional Gushel--Mukai variety See Proposition 3.1 of [1605.05648v3]. * [1605.05648v3] Debarre--Kuznetsov, Gushel-Mukai varieties: linear spaces and periods INPUT: - ``n`` - the dimension, where $n=1,\\ldots,6$ """ assert n in range( 1, 7 ), """There is no Gushel--Mukai variety of this dimension""" if n == 1: return curve(6) if n == 2: return K3() if n == 3: return curve(10)(1) + lefschetz() ** 0 + lefschetz() ** 3 if n == 4: return K3()(1) + lefschetz() ** 0 + 2 * lefschetz() ** 2 + lefschetz() ** 4 if n == 5: return curve(10)(2) + Pn(5) return K3()(2) + lefschetz() ** 3 + Pn(6)
[docs] def fano_variety_lines_cubic(n): r""" Hodge diamond for the Fano variety of lines on a smooth $n$-dimensional cubic hypersurface. This follows from the "beautiful formula" or X-F(X)-relation due to Galkin--Shinder, Theorem 5.1 of [1405.5154v2]. * [1405.5154v2] Galkin--Shinder, The Fano variety of lines and rationality problem for a cubic hypersurface INPUT: - ``n`` -- the dimension, where ``n`` is at least 2 EXAMPLES: There are 27 lines on a cubic surface:: sage: from diamond import * sage: fano_variety_lines_cubic(2) == 27*point() True The Fano surface of lines on a cubic threefold is a surface of general type:: sage: fano_variety_lines_cubic(3) == surface(10, 5, 25) True The Fano fourfold of lines on a cubic fourfold is deformation equivalent to the Hilbert square on a K3 surface:: sage: fano_variety_lines_cubic(4) == hilbn(K3(), 2) True """ assert n >= 2 X = hypersurface(3, n) return (hilbtwo(X) - Pn(n) * X)(-2)
[docs] def Mzeronbar(n): r""" Hodge diamond for the moduli space of $n$-pointed stable curves of genus 0 Taken from (0.12) in [MR1363064]. Keel's original paper has a recursion on page 550, but that seems to not work. * [MR1363064] Manin, Generating functions in algebraic geometry and sums over trees EXAMPLES: The first few cases are a point, the projective line, and the blowup of $\\mathbb{P}^2$ in 4 points:: sage: from diamond import * sage: Mzeronbar(3) == point() True sage: Mzeronbar(4) == Pn(1) True sage: Mzeronbar(5) == Pn(2).blowup(4*point()) True """ assert n >= 2 x = HodgeDiamond.x y = HodgeDiamond.y def Manin(n): if n in [2, 3]: return HodgeDiamond.R.one() else: return Manin(n - 1) + x * y * sum( binomial(n - 2, i) * Manin(i + 1) * Manin(n - i) for i in range(2, n - 1) ) return HodgeDiamond.from_polynomial(Manin(n))
# make Theta into slope stability def slope(Theta): r"""Helper function to turn stability for quiver representations in slope stability""" return lambda d: sum(a * b for (a, b) in zip(Theta, d)) / sum(d)
[docs] def quiver_moduli(Q, d, **kwargs): r""" Hodge diamond for the moduli space of semistable quiver representations for a quiver Q, dimension vector d, and slope-stability condition mu. Taken from Corollary 6.9 of [MR1974891] * [MR1974891] Reineke, The Harder-Narasimhan system in quantum groups and cohomology of quiver moduli. INPUT: - ``Q`` -- adjacency matrix of an acyclic quiver - ``d`` -- dimension vector - ``mu`` -- stability condition, these can be produced using :func:`slope`, if left unspecified then the canonical stability condition is used The canonical stability condition for dimension vector `d` is given by the antisymmetrised Euler form pairing with `d`. EXAMPLES: Let's consider moduli spaces for the Kronecker quiver:: sage: from diamond import * sage: def kronecker(d): return matrix([[0, d], [0, 0]]) For the 2-Kronecker quiver and dimension vector `(1,1)` a representation is given by 2 scalars, and the stability condition `(1,-1)` encodes that they are not both zero. This way we obtain the projective line:: sage: quiver_moduli(kronecker(2), (1, 1), mu=slope((1, -1))) == Pn(1) True There is only one relevant stability chamber here, which contains the canonical stability condition. Thus we get the same result not specifying the stability function:: sage: quiver_moduli(kronecker(2), (1, 1)) == Pn(1) True Similar to the first example, the $d$-Kronecker quiver gives rise to projective spaces:: sage: all(quiver_moduli(kronecker(d), (1, 1)) == Pn(d - 1) for d in range(3, 10)) True We can also realise Grassmannians using the $d$-Kronecker quiver, for dimension vector $(1,k)$ and stability condition $(k,1)$ (or the canonical stability condition) we get the Grassmannian $\\operatorname{Gr}(k,d)$:: sage: quiver_moduli(kronecker(4), (1, 2), mu=slope((2, 1))) == grassmannian(2, 4) True sage: quiver_moduli(kronecker(7), (1, 3)) == grassmannian(3, 7) True Any stability function in the same chamber gives the same variety:: sage: quiver_moduli(kronecker(7), (1, 3)) == quiver_moduli(kronecker(7), (1, 3), mu=slope((1, -1))) True The following is an example of wall-crossing, and thus different varieties:: sage: M = matrix([[0, 1, 1], [0, 0, 2], [0, 0, 0]]) sage: quiver_moduli(M, (1, 1, 1)) == Pn(2).blowup(point()) True sage: quiver_moduli(M, (1, 1, 1), mu=slope((1, 0, 0))) == Pn(2) True The flag variety $\\operatorname{Fl}(n,r_1,\\ldots,r_s)$ is also a quiver moduli space, for $\\mathrm{A}_s$ quiver prefixed with an $n$-Kronecker quiver, dimension vector $(1,r_1,\\ldots,r_s)$ with $r_1>\\ldots>r_s$ and stability condition the indicator function at the first vertex:: sage: def flags(n, s): return matrix(ZZ, s+1, s+1, lambda i,j: 0 if i != j-1 else (n if i == 0 else 1)) sage: quiver_moduli(flags(4, 1), (1, 1)) == Pn(3) True sage: quiver_moduli(flags(3, 2), (1, 2, 1)) == fano_threefold(2, 32) True sage: quiver_moduli(flags(5, 3), (1, 4, 3, 1)) == partial_flag_variety("A4", [2]) True """ K = FunctionField(QQ, "v") v = K.gen(0) # we will use v, not v^2, throughout # solve Ax=b for A upper triangular via back substitution def solve(A, b): assert A.is_square() and A.nrows() == len(b) n = len(b) - 1 x = [0] * (n + 1) # start x[n] = b[n] / A[n, n] # induct for i in range(n - 1, -1, -1): x[i] = (b[i] - sum([A[i, j] * x[j] for j in range(i + 1, n + 1)])) / A[i, i] return x @cached_function def GL(n): r"""Cardinality of general linear group $\\mathrm{GL}_n(\\mathbb{F}_v)$""" return prod([v**n - v**k for k in range(n)]) # not caching this one seems faster # @cached_function def Rd(Q, d): """Cardinality of ``R_d`` from Definition 3.1""" return v ** sum([d[i] * d[j] * Q[i, j] for i, j in Q.dict()]) @cached_function def Gd(d): """Cardinality of ``G_d`` from Definition 3.1""" return prod([GL(di) for di in d]) def Id(Q, d, mu): """Returns the indexing set from Corollary 5.5 These are the dimension vectors smaller than ``d`` whose slope is bigger than ``d``, together with the zero dimension vector and ``d`` itself. """ # all possible dimension vectors e <= d E = cartesian_product([range(di + 1) for di in d]) # predicate from Corollary 5.5, E[0] is the zero dimension vector return [E[0]] + list(filter(lambda e: mu(e) > mu(d), E[1:])) + [d] def Td(Q, d, mu): """Returns the upper triangular transfer matrix from Corollary 5.5""" # Euler form chi = matrix.identity(len(d)) - Q # indexing set for the transfer matrix I = Id(Q, d, mu) # make them vectors now so that we only do it once I = list(map(vector, I)) def entry(Q, e, f): """Entry of the transfer matrix, as per Corollary 6.9""" fe = f - e if all(fei >= 0 for fei in fe): return v ** (-fe * chi * e) * Rd(Q, fe) / Gd(fe) return 0 T = matrix(K, len(I), len(I)) for i, Ii in enumerate(I): for j in range(i, len(I)): # upper triangular T[i, j] = entry(Q, Ii, I[j]) return T Q = DiGraph(matrix(Q)) # see Section 2 assert Q.is_directed_acyclic(), "Q needs to be acyclic" # see Definition 6.3 and following lemmas assert gcd(d) == 1, "dimension vector is not coprime" # if mu is not provided we resort to the canonical stability condition mu = kwargs.get("mu", None) if mu is None: A = matrix.identity(len(d)) - Q.adjacency_matrix() mu = slope(vector(d) * A - A * vector(d)) # (0,d)-entry of the inverse of the transfer matrix, as per Corollary 6.9 T = Td(Q.adjacency_matrix(), d, mu) # doing a back-substitution is faster result = solve(T, [0] * (T.nrows() - 1) + [1])[0] * (1 - v) # result = T.inverse()[0,-1] * (1 - v) assert result.denominator() == 1, "result needs to be a polynomial" x = HodgeDiamond.x y = HodgeDiamond.y result = HodgeDiamond.R(result.numerator().subs(v=x * y)) return HodgeDiamond.from_polynomial(result)
[docs] def fano_threefold(rho, ID): r""" Hodge diamond of a Fano threefold INPUT: - ``rho`` - Picard rank - ``ID`` - numbering from the Mori-Mukai classification EXAMPLES: The 17th Fano 3-fold of rank 1 is projective threespace:: sage: from diamond import * sage: print(fano_threefold(1, 17)) 1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 1 The 4th Fano 3-fold of rank 1 is an intersection of 3 quadrics:: sage: fano_threefold(1, 4) == complete_intersection((2, 2, 2), 3) True The 27th Fano 3-fold of rank 3 is the triple product of projective lines:: sage: fano_threefold(3, 27) == Pn(1)**3 True """ h12 = { (1, 1): 52, (1, 2): 30, (1, 3): 20, (1, 4): 14, (1, 5): 10, (1, 6): 7, (1, 7): 5, (1, 8): 3, (1, 9): 2, (1, 10): 0, (1, 11): 21, (1, 12): 10, (1, 13): 5, (1, 14): 2, (1, 15): 0, (1, 16): 0, (1, 17): 0, (2, 1): 22, (2, 2): 20, (2, 3): 11, (2, 4): 10, (2, 5): 6, (2, 6): 9, (2, 7): 5, (2, 8): 9, (2, 9): 5, (2, 10): 3, (2, 11): 5, (2, 12): 3, (2, 13): 2, (2, 14): 1, (2, 15): 4, (2, 16): 2, (2, 17): 1, (2, 18): 2, (2, 19): 2, (2, 20): 0, (2, 21): 0, (2, 22): 0, (2, 23): 1, (2, 24): 0, (2, 25): 1, (2, 26): 0, (2, 27): 0, (2, 28): 1, (2, 29): 0, (2, 30): 0, (2, 31): 0, (2, 32): 0, (2, 33): 0, (2, 34): 0, (2, 35): 0, (2, 36): 0, (3, 1): 8, (3, 2): 3, (3, 3): 3, (3, 4): 2, (3, 5): 0, (3, 6): 1, (3, 7): 1, (3, 8): 0, (3, 9): 3, (3, 10): 0, (3, 11): 1, (3, 12): 0, (3, 13): 0, (3, 14): 1, (3, 15): 0, (3, 16): 0, (3, 17): 0, (3, 18): 0, (3, 19): 0, (3, 20): 0, (3, 21): 0, (3, 22): 0, (3, 23): 0, (3, 24): 0, (3, 25): 0, (3, 26): 0, (3, 27): 0, (3, 28): 0, (3, 29): 0, (3, 30): 0, (3, 31): 0, (4, 1): 1, (4, 2): 1, (4, 3): 0, (4, 4): 0, (4, 5): 0, (4, 6): 0, (4, 7): 0, (4, 8): 0, (4, 9): 0, (4, 10): 0, (4, 11): 0, (4, 12): 0, (4, 13): 0, (5, 1): 0, (5, 2): 0, (5, 3): 0, (6, 1): 0, (7, 1): 0, (8, 1): 0, (9, 1): 0, (10, 1): 0, } M = matrix( [ [1, 0, 0, 0], [0, rho, h12[(rho, ID)], 0], [0, h12[(rho, ID)], rho, 0], [0, 0, 0, 1], ] ) return HodgeDiamond.from_matrix(M, from_variety=True)