from itertools import combinations_with_replacement, product
from sage.arith.misc import bernoulli, factorial, gcd, xgcd
from sage.combinat.partition import Partitions
from sage.combinat.permutation import Permutations
from sage.combinat.schubert_polynomial import SchubertPolynomialRing
from sage.combinat.sf.sf import SymmetricFunctions
from sage.combinat.tuple import UnorderedTuples
from sage.matrix.constructor import matrix
from sage.misc.misc_c import prod
from sage.modules.free_module_element import vector, zero_vector
from sage.rings.function_field.constructor import FunctionField
from sage.rings.infinity import Infinity
from sage.rings.integer_ring import ZZ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.rings.polynomial.term_order import TermOrder
from sage.rings.quotient_ring import QuotientRing
from sage.rings.rational_field import QQ
from sage.structure.element import Element
from . import Quiver
"""Defines how permutations are multiplied."""
Permutations.options(mult="r2l")
[docs]
class QuiverModuli(Element):
[docs]
def __init__(self, Q, d, theta=None, denom=sum, condition="semistable"):
r"""
Constructor for an abstract quiver moduli space.
This base class contains everything that is common between
- quiver moduli spaces, i.e., varieties
- quiver moduli stacks
INPUT:
- ``Q`` -- quiver
- ``d`` --- dimension vector
- ``theta`` -- stability parameter (default: canonical stability parameter)
- ``denom`` -- denominator for slope stability (default: ``sum``), needs to be
effective on the simple roots
- ``condition`` -- whether to include all semistables, or only stables
(default: "semistable")
See :class:`QuiverModuliSpace` and :class:`QuiverModuliStack` for more details.
EXAMPLES:
We can instantiate an abstract quiver moduli space::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuli(Q, (2, 3))
sage: X
abstract moduli of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
It has functionality common to both varieties and stacks, i.e., when it really
concerns something involving the representation variety::
sage: X.all_harder_narasimhan_types()
[((1, 0), (1, 1), (0, 2)),
((1, 0), (1, 2), (0, 1)),
((1, 0), (1, 3)),
((1, 1), (1, 2)),
((2, 0), (0, 3)),
((2, 1), (0, 2)),
((2, 2), (0, 1)),
((2, 3),)]
But things like dimension depend on whether we consider it as a variety or as
a stack, and thus these are not implemented::
sage: X.dimension()
Traceback (most recent call last):
...
NotImplementedError
"""
if theta is None:
theta = Q.canonical_stability_parameter(d)
assert Q._is_dimension_vector(d), "``d`` needs to be a dimension vector"
assert Q._is_vector(theta), "`theta` needs to be a stability parameter"
assert condition in [
"semistable",
"stable",
], "condition needs to be (semi)stable"
assert all(
denom(Q._coerce_dimension_vector(Q.simple_root(i))) > 0
for i in Q.vertices()
), "denominator needs to be effective"
assert (
Q._coerce_dimension_vector(d) * Q._coerce_vector(theta) == 0
), "for the moment we require that `theta(d) == 0`"
self._Q = Q
self._d = d
self._theta = theta
self._denom = denom
self._condition = condition
def __repr_helper(self, description):
r"""
Standard format for shorthand string presentation.
EXAMPLES:
A Kronecker moduli space with non-standard description::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuli(Q, (2, 3))
sage: print(X._QuiverModuli__repr_helper("Kronecker moduli space"))
Kronecker moduli space of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
"""
output = "{} of {} representations, with".format(description, self._condition)
output += "\n- Q = {}\n- d = {}\n- θ = {}".format(
self._Q.repr(), self._d, self._theta
)
return output
def _repr_(self):
r"""
Give a shorthand string presentation for an abstract quiver moduli space.
EXAMPLES:
A Kronecker moduli space::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuli(Q, (2, 3))
abstract moduli of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
"""
if self.get_custom_name():
return self.get_custom_name()
return self.__repr_helper("abstract moduli")
[docs]
def repr(self):
r"""
Give a shorthand string presentation for an abstract quiver moduli space.
EXAMPLES:
A Kronecker moduli space::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuli(Q, (2, 3))
abstract moduli of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
"""
return self._repr_()
[docs]
def to_space(self):
r"""
Make the abstract quiver moduli a variety.
This is an explicit way of casting an abstract :class:`QuiverModuli`
to a :class:`QuiverModuliSpace`.
EXAMPLES:
From an abstract quiver moduli to a space::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuli(Q, (2, 3))
sage: X.to_space()
moduli space of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
From a stack to a space::
sage: X = QuiverModuliStack(Q, (2, 3))
sage: X.to_space()
moduli space of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
"""
return QuiverModuliSpace(
self._Q, self._d, self._theta, self._denom, self._condition
)
[docs]
def to_stack(self):
r"""
Make the abstract quiver moduli a stack.
This is an explicit way of casting an abstract :class:`QuiverModuli`
to a :class:`QuiverModuliStack`.
EXAMPLES:
From an abstract quiver moduli to a stack::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuli(Q, (2, 3))
sage: X.to_stack()
moduli stack of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
From a space to a stack::
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: X.to_stack()
moduli stack of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
"""
return QuiverModuliStack(
self._Q, self._d, self._theta, self._denom, self._condition
)
[docs]
def quiver(self):
r"""
Returns the quiver of the moduli space.
OUTPUT: the underlying quiver as an instance of the :class:`Quiver` class
EXAMPLES:
The quiver of a Kronecker moduli space::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuli(Q, (2, 3))
sage: Q == X.quiver()
True
"""
return self._Q
[docs]
def dimension_vector(self):
r"""
Returns the dimension vector of the moduli space.
OUTPUT: the dimension vector
EXAMPLES:
The dimension vector of a Kronecker moduli space::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuli(Q, (2, 3))
sage: X.dimension_vector()
(2, 3)
The dimension vector is stored in the same format as it was given::
sage: Q = Quiver.from_string("foo---bar", forget_labels=False)
sage: X = QuiverModuli(Q, {"foo": 2, "bar": 3})
sage: X.dimension_vector()
{'bar': 3, 'foo': 2}
"""
return self._d
[docs]
def stability_parameter(self):
r"""
Returns the stability parameter of the moduli space.
OUTPUT: the stability parameter
EXAMPLES:
The stability parameter of a Kronecker moduli space::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuli(Q, (2, 3), (3, -2))
sage: X.stability_parameter()
(3, -2)
The stability parameter is stored in the same format as it was given::
sage: Q = Quiver.from_string("foo---bar", forget_labels=False)
sage: d, theta = {"foo": 2, "bar": 3}, {"foo": 3, "bar": -2}
sage: X = QuiverModuliSpace(Q, d, theta);
sage: X.stability_parameter()
{'bar': -2, 'foo': 3}
"""
return self._theta
[docs]
def denominator(self):
r"""
Returns the denominator of the slope function :math:`\mu_{\theta}`.
OUTPUT: the denominator as a function
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: X.denominator()
<built-in function sum>
"""
return self._denom
[docs]
def is_nonempty(self) -> bool:
r"""
Checks if the moduli space is nonempty.
OUTPUT: whether there exist stable/semistable representations, according
to the condition
EXAMPLES:
The 3-Kronecker quiver for `d = (2, 3)` has stable representations::
sage: from quiver import *
sage: Q, d = GeneralizedKroneckerQuiver(3), (2, 3)
sage: X = QuiverModuliSpace(Q, d, condition="stable"); X.is_nonempty()
True
The Jordan quiver does not have stable representations, but it has semistable
ones::
sage: Q = JordanQuiver()
sage: X = QuiverModuliSpace(Q, [3], condition="stable")
sage: X.is_nonempty()
False
sage: X = QuiverModuliSpace(Q, [3], condition="semistable")
sage: X.is_nonempty()
True
"""
if self._condition == "stable":
return self._Q.has_stable_representation(self._d, self._theta)
if self._condition == "semistable":
return self._Q.has_semistable_representation(self._d, self._theta)
[docs]
def is_theta_coprime(self) -> bool:
r"""
Checks whether the combination of `d` and `theta` is coprime.
This just calls :meth:`Quiver.is_theta_coprime` for the data defining the
moduli space.
EXAMPLES:
A coprime example::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3)).is_theta_coprime()
True
And a non-example::
sage: QuiverModuliSpace(Q, (3, 3)).is_theta_coprime()
False
"""
return self._Q.is_theta_coprime(self._d, self._theta)
"""
Harder--Narasimhan stratification
"""
[docs]
def all_harder_narasimhan_types(self, proper=False, sorted=False):
r"""
Returns the list of all Harder--Narasimhan types.
A Harder--Narasimhan (HN) type of `d` with respect to :math:`\theta`
is a sequence :math:`{\bf d}^* = ({\bf d}^1,...,{\bf d}^s)` of dimension vectors
such that
- :math:`{\bf d}^1 + ... + {\bf d}^s = {\bf d}`
- :math:`\mu_{\theta}({\bf d}^1) > ... > \mu_{\theta}({\bf d}^s)`
- Every :math:`{\bf d}^k` is :math:`\theta`-semistable.
INPUT:
- ``proper`` -- (default: False) whether to exclude the HN-type corresponding
to the stable locus
- ``sorted`` -- (default: False) whether to sort the HN-types according to the
given slope
OUTPUT: list of tuples of dimension vectors encoding HN-types
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: X.all_harder_narasimhan_types()
[((1, 0), (1, 1), (0, 2)),
((1, 0), (1, 2), (0, 1)),
((1, 0), (1, 3)),
((1, 1), (1, 2)),
((2, 0), (0, 3)),
((2, 1), (0, 2)),
((2, 2), (0, 1)),
((2, 3),)]
sage: X.all_harder_narasimhan_types(proper=True)
[((1, 0), (1, 1), (0, 2)),
((1, 0), (1, 2), (0, 1)),
((1, 0), (1, 3)),
((1, 1), (1, 2)),
((2, 0), (0, 3)),
((2, 1), (0, 2)),
((2, 2), (0, 1))]
sage: d = (2, 3)
sage: theta = -Q.canonical_stability_parameter(d)
sage: Y = QuiverModuliSpace(Q, d, theta)
sage: Y.all_harder_narasimhan_types()
[((0, 3), (2, 0))]
A 3-vertex quiver::
sage: from quiver import *
sage: Q = ThreeVertexQuiver(2, 3, 4)
sage: Z = QuiverModuliSpace(Q, (2, 3, 2))
sage: Z.all_harder_narasimhan_types()
[((0, 1, 0), (1, 2, 1), (1, 0, 1)),
((0, 1, 0), (2, 0, 1), (0, 2, 1)),
((0, 1, 0), (2, 1, 1), (0, 1, 1)),
((0, 1, 0), (2, 2, 1), (0, 0, 1)),
((0, 1, 0), (2, 2, 2)),
((0, 2, 0), (1, 1, 1), (1, 0, 1)),
((0, 2, 0), (2, 0, 1), (0, 1, 1)),
((0, 2, 0), (2, 1, 1), (0, 0, 1)),
((0, 2, 0), (2, 1, 2)),
((0, 3, 0), (2, 0, 1), (0, 0, 1)),
((0, 3, 0), (2, 0, 2)),
((1, 0, 0), (0, 1, 0), (1, 0, 1), (0, 2, 1)),
((1, 0, 0), (0, 1, 0), (1, 1, 1), (0, 1, 1)),
((1, 0, 0), (0, 1, 0), (1, 2, 1), (0, 0, 1)),
((1, 0, 0), (0, 1, 0), (1, 2, 2)),
((1, 0, 0), (0, 2, 0), (1, 0, 1), (0, 1, 1)),
((1, 0, 0), (0, 2, 0), (1, 1, 1), (0, 0, 1)),
((1, 0, 0), (0, 2, 0), (1, 1, 2)),
((1, 0, 0), (0, 3, 0), (1, 0, 1), (0, 0, 1)),
((1, 0, 0), (0, 3, 0), (1, 0, 2)),
((1, 0, 0), (0, 3, 1), (1, 0, 1)),
((1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 1, 1), (0, 0, 1)),
((1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 1, 2)),
((1, 0, 0), (1, 1, 0), (0, 2, 0), (0, 0, 2)),
((1, 0, 0), (1, 1, 0), (0, 2, 1), (0, 0, 1)),
((1, 0, 0), (1, 1, 0), (0, 2, 2)),
((1, 0, 0), (1, 1, 1), (0, 2, 1)),
((1, 0, 0), (1, 2, 0), (0, 1, 0), (0, 0, 2)),
((1, 0, 0), (1, 2, 0), (0, 1, 1), (0, 0, 1)),
((1, 0, 0), (1, 2, 0), (0, 1, 2)),
((1, 0, 0), (1, 2, 1), (0, 1, 1)),
((1, 0, 0), (1, 3, 1), (0, 0, 1)),
((1, 0, 0), (1, 3, 2)),
((1, 1, 0), (0, 1, 0), (1, 0, 1), (0, 1, 1)),
((1, 1, 0), (0, 1, 0), (1, 1, 1), (0, 0, 1)),
((1, 1, 0), (0, 1, 0), (1, 1, 2)),
((1, 1, 0), (0, 2, 0), (1, 0, 1), (0, 0, 1)),
((1, 1, 0), (0, 2, 0), (1, 0, 2)),
((1, 1, 0), (1, 0, 1), (0, 2, 1)),
((1, 1, 0), (1, 1, 1), (0, 1, 1)),
((1, 1, 0), (1, 2, 0), (0, 0, 2)),
((1, 1, 0), (1, 2, 1), (0, 0, 1)),
((1, 1, 0), (1, 2, 2)),
((1, 2, 0), (0, 1, 0), (1, 0, 1), (0, 0, 1)),
((1, 2, 0), (0, 1, 0), (1, 0, 2)),
((1, 2, 0), (1, 0, 1), (0, 1, 1)),
((1, 2, 0), (1, 1, 1), (0, 0, 1)),
((1, 2, 0), (1, 1, 2)),
((1, 2, 1), (1, 1, 1)),
((1, 3, 1), (1, 0, 1)),
((2, 0, 0), (0, 1, 0), (0, 2, 1), (0, 0, 1)),
((2, 0, 0), (0, 1, 0), (0, 2, 2)),
((2, 0, 0), (0, 2, 0), (0, 1, 1), (0, 0, 1)),
((2, 0, 0), (0, 2, 0), (0, 1, 2)),
((2, 0, 0), (0, 2, 1), (0, 1, 1)),
((2, 0, 0), (0, 3, 0), (0, 0, 2)),
((2, 0, 0), (0, 3, 1), (0, 0, 1)),
((2, 0, 0), (0, 3, 2)),
((2, 0, 1), (0, 3, 1)),
((2, 1, 0), (0, 1, 0), (0, 1, 1), (0, 0, 1)),
((2, 1, 0), (0, 1, 0), (0, 1, 2)),
((2, 1, 0), (0, 2, 0), (0, 0, 2)),
((2, 1, 0), (0, 2, 1), (0, 0, 1)),
((2, 1, 0), (0, 2, 2)),
((2, 1, 1), (0, 2, 1)),
((2, 2, 0), (0, 1, 0), (0, 0, 2)),
((2, 2, 0), (0, 1, 1), (0, 0, 1)),
((2, 2, 0), (0, 1, 2)),
((2, 2, 1), (0, 1, 1)),
((2, 3, 0), (0, 0, 2)),
((2, 3, 1), (0, 0, 1)),
((2, 3, 2),)]
"""
d = self._Q._coerce_dimension_vector(self._d)
theta = self._Q._coerce_vector(self._theta)
all_types = self._Q._all_harder_narasimhan_types(
d, theta, denom=self._denom, sorted=sorted
)
if proper and (d,) in all_types:
all_types.remove((d,))
return all_types
[docs]
def is_harder_narasimhan_type(self, dstar) -> bool:
r"""
Checks if ``dstar`` is a Harder--Narasimhan type.
A Harder--Narasimhan (HN) type of :math`{\bf d}` with respect to :math:`\theta`
is a sequence :math:`{\bf d}^* = ({\bf d}^1,...,{\bf d}^s)` of dimension vectors
such that
- :math:`{\bf d}^1 + ... + {\bf d}^s = {\bf d}`
- :math:`\mu_{\theta}({\bf d}^1) > ... > \mu_{\theta}({\bf d}^s)`
- Every :math:`{\bf d}^k` is :math:`\theta`-semistable.
INPUT:
- ``dstar`` -- list of dimension vectors
OUTPUT: whether ``dstar`` is a valid HN type for the moduli space
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: HNs = X.all_harder_narasimhan_types()
sage: all(X.is_harder_narasimhan_type(dstar) for dstar in HNs)
True
sage: dstar = [(1, 0), (1, 0), (0, 3)]
sage: X.is_harder_narasimhan_type(dstar)
False
sage: X.is_harder_narasimhan_type([Q.zero_vector()])
False
"""
# setup shorthand
Q, d, theta, denom = (
self._Q,
self._d,
self._theta,
self._denom,
)
assert all(
Q._is_dimension_vector(di) for di in dstar
), "elements of ``dstar`` need to be dimension vectors"
dstar = list(map(lambda di: Q._coerce_dimension_vector(di), dstar))
# first condition: sum to dimension vector
if Q._coerce_dimension_vector(d) != sum(dstar):
return False
# second condition: decreasing slopes
if not all(
(
Q.slope(dstar[i], theta, denom=denom)
> Q.slope(dstar[i + 1], theta, denom=denom)
)
for i in range(len(dstar) - 1)
):
return False
# third condition
if not all(
Q.has_semistable_representation(di, theta, denom=denom) for di in dstar
):
return False
return True
[docs]
def codimension_of_harder_narasimhan_stratum(self, dstar, secure=False):
r"""
Computes the codimension of the HN stratum of ``dstar``
inside the representation variety :math:`R(Q,{\bf d})`.
INPUT:
- ``dstar`` -- the HN type as a list of dimension vectors
- ``secure`` -- whether to first check it is an HN-type (default: False)
OUTPUT: codimension as an integer
By default, the method does not check if ``dstar`` is a valid HN type.
This can be enabled by passing ``secure=True``.
The codimension of the HN stratum of
:math:`{\bf d}^* = ({\bf d}^1,...,{\bf d}^s)` is given by
.. MATH::
- \sum_{k < l} \langle {\bf d}^k,{\bf d}^l\rangle
INPUT:
- ``dstar`` -- list of dimension vectors
- ``secure`` -- whether to check ``dstar`` is an HN-type (default: False)
OUTPUT: codimension of the HN-stratum
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: HNs = X.all_harder_narasimhan_types()
sage: [X.codimension_of_harder_narasimhan_stratum(dstar) for dstar in HNs]
[12, 9, 8, 3, 18, 10, 4, 0]
"""
Q = self._Q
assert all(
Q._is_dimension_vector(di) for di in dstar
), "elements of ``dstar`` need to be dimension vectors"
if secure:
assert self.is_harder_narasimhan_type(dstar), "``dstar`` must be HN-type"
return -sum(
Q.euler_form(dstar[k], dstar[l])
for k in range(len(dstar) - 1)
for l in range(k + 1, len(dstar))
)
[docs]
def codimension_unstable_locus(self):
r"""
Computes codimension of the unstable locus inside the representation variety.
This is the minimum of the codimensions of the proper Harder--Narasimhan strata
of the representation variety.
OUTPUT: codimension of the unstable locus
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: X.codimension_unstable_locus()
3
A 3-vertex quiver::
sage: Q = ThreeVertexQuiver(1, 6, 1)
sage: X = QuiverModuliSpace(Q, (1, 6, 6))
sage: X.codimension_unstable_locus()
1
The :math:`\mathrm{A}_2` quiver is of finite type::
sage: Q = GeneralizedKroneckerQuiver(1)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: X.codimension_unstable_locus()
0
"""
HNs = self.all_harder_narasimhan_types(proper=True)
# note that while the HN types and strata depend on the denominator
# the maximum of their codimensions does not
return min(
self.codimension_of_harder_narasimhan_stratum(dstar, secure=False)
for dstar in HNs
)
"""
Luna
"""
[docs]
def all_luna_types(self, exclude_stable=False):
r"""
Returns the unordered list of all Luna types of ``d`` for ``theta``.
INPUT:
- ``exclude_stable`` -- whether to exclude the stable Luna type ``{d: [1]}``
(default: False)
OUTPUT: the list of all the Luna types as dictionaries.
The Luna stratification of the representation variety concerns the étale-local
structure of the moduli space of semistable quiver representations. It is
studied in MR1972892_, and for more details one is referred there.
.. _MR1972892: https://mathscinet.ams.org/mathscinet/relay-station?mr=1972892
A Luna type of :math:`{\bf d}` for :math:`\theta` is an unordered sequence
:math:`(({\bf d}^1,m_1),...,({\bf d}^s,m_s))` of pairs of dimension vectors
:math:`{\bf d}^k` and positive integers :math:`m_k` such that
- :math:`m_1{\bf d}^1 + ... + m_s{\bf d}^s = {\bf d}`,
- :math:`\mu_{\theta}({\bf d}^k) = \mu_{\theta}({\bf d})`, and
- all the :math:`{\bf d}^k` admit a :math:`\theta`-stable representation.
Note that a pair :math:`({\bf d}^i, m_i)`
can appear multiple times in a Luna type, and the same dimension vector
:math:`{\bf d}^i` can appear coupled with different integers.
IMPLEMENTATION:
Here a Luna type is a dictionary
``{d^1: p^1, ... d^s: p^s}``
whose keys are dimension vectors :math:`{\bf d}^k` and values are non-empty
lists of positive integers
``p^k = [p_{k, 1}, ..., p_{k, t_k}]``.
The corresponding Luna type is then the unordered sequence of tuples
.. MATH::
({\bf d}^1, p_{1, 1}), \dots, ({\bf d}^1, p_{1, t_1}), \dots
({\bf d}^s, p_{s, 1}), \dots, ({\bf d}^s, p_{s, t_s}),
such that
.. MATH::
(p_{1, 1} + \dots + p_{1, t_1}) \cdot {\bf d}^1 + \dots +
(p_{s, 1} + \dots + p_{s, t_s}) \cdot {\bf d}^s = {\bf d}.
ALGORITHM:
The way we compute the Luna types of a quiver moduli space is taken from
Section 4 in MR2511752_.
.. _MR2511752: https://mathscinet.ams.org/mathscinet/relay-station?mr=2511752
EXAMPLES:
The Kronecker quiver::
sage: from quiver import *
sage: Q = KroneckerQuiver()
sage: X = QuiverModuliSpace(Q, (3, 3), (1, -1))
sage: X.all_luna_types()
[{(1, 1): [3]}, {(1, 1): [2, 1]}, {(1, 1): [1, 1, 1]}]
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (3, 3), (1, -1))
sage: X.all_luna_types()
[{(3, 3): [1]},
{(1, 1): [1], (2, 2): [1]},
{(1, 1): [3]},
{(1, 1): [2, 1]},
{(1, 1): [1, 1, 1]}]
The zero vector::
sage: from quiver import *
sage: Q = KroneckerQuiver()
sage: X = QuiverModuliSpace(Q, (0, 0), (1, -1))
sage: X.all_luna_types()
[{(0, 0): [1]}]
"""
# setup shorthand
Q, d, theta, denom = (
self._Q,
self._d,
self._theta,
self._denom,
)
d = Q._coerce_dimension_vector(d)
if d == Q.zero_vector():
# Q.zero_vector() can't be hashed a priori
z = Q._coerce_vector(Q.zero_vector())
return [{z: [1]}]
# we will build all possible Luna types from the bottom up
Ls = []
# start with all subdimension vectors
ds = Q.all_subdimension_vectors(d, nonzero=True, forget_labels=True)
# look for subdimension vectors with the same slope as ``d``
# and which admit a stable representation:
# this encodes the second and third condition in the definition
same_slope = filter(
lambda e: Q.slope(e, theta, denom=denom) == Q.slope(d, theta, denom=denom)
and Q.has_stable_representation(e, theta, denom=denom),
ds,
)
same_slope = list(same_slope)
# bounds how long a Luna type can be
bound = (sum(d) / min(sum(e) for e in same_slope)).ceil()
for i in range(1, bound + 1):
for tau in combinations_with_replacement(same_slope, i):
# first condition is not satisfied
if not sum(tau) == d:
continue
# from tau we build all possible Luna types
partial = {}
for taui in tuple(tau):
if taui in partial.keys():
partial[taui] += 1
else:
partial[taui] = 1
# partial has the form
# {d^1: Partitions(p^1), ..., d^s: Partitions(p^s)}
for key in partial.keys():
partial[key] = Partitions(partial[key]).list()
# we add all possible Luna types we can build to our list
Ls += [
dict(zip(partial.keys(), values))
for values in product(*partial.values())
]
stable = {d: [1]}
if exclude_stable and stable in Ls:
Ls.remove(stable)
return Ls
[docs]
def is_luna_type(self, tau) -> bool:
r"""
Checks if ``tau`` is a Luna type.
INPUT:
- ``tau`` -- Luna type encoded by a dictionary of multiplicities indexed by
dimension vectors
OUTPUT: whether ``tau`` is a Luna type
For a description of Luna types, see :meth:`all_luna_types`.
EXAMPLES:
The Kronecker quiver::
sage: from quiver import *
sage: Q = KroneckerQuiver()
sage: X = QuiverModuliSpace(Q, (3, 3), (1, -1))
sage: Ls = X.all_luna_types()
sage: all(X.is_luna_type(tau) for tau in Ls)
True
The 3-Kronecker quiver with zero vector::
sage: from quiver import *
sage: Q = KroneckerQuiver()
sage: X = QuiverModuliSpace(Q, (0, 0), (1, -1))
sage: X.is_luna_type({Q.zero_vector(): [1]})
True
"""
Q, d, theta, denom = (
self._Q,
self._d,
self._theta,
self._denom,
)
d = Q._coerce_dimension_vector(d)
assert all(
Q._is_dimension_vector(dk) for dk in tau.keys()
), "elements of ``tau`` need to be dimension vectors"
if d == Q.zero_vector():
# Q.zero_vector() can't be hashed a priori
z = Q._coerce_vector(Q.zero_vector())
return tau == {z: [1]}
# we check the 3 conditions in that order
return d == sum(sum(m) * dk for (dk, m) in tau.items()) and all(
Q.slope(dk, theta, denom=denom) == Q.slope(d, theta, denom=denom)
and Q.has_semistable_representation(dk, theta, denom=denom)
for dk in tau.keys()
)
[docs]
def dimension_of_luna_stratum(self, tau, secure=True):
r"""
Computes the dimension of the Luna stratum :math:`S_\tau`.
INPUT:
- ``tau`` -- Luna type encoded by a dictionary of multiplicities indexed by
dimension vectors
- ``secure`` -- whether to first check it is a Luna type (default: False)
OUTPUT: dimension of the corresponding Luna stratum
The dimension of the Luna stratum of ``tau = {d^1: p^1,...,d^s: p^s}`` is
.. MATH::
\sum_k l(p^k)(1 - \langle {\bf d}^k,{\bf d}^k\rangle),
where for a partition :math:`p = (n_1,...,n_l)`,
the length `l(p)` is `l`, i.e., the number of summands.
EXAMPLES:
The Kronecker quiver::
sage: from quiver import *
sage: Q = KroneckerQuiver()
sage: X = QuiverModuliSpace(Q, (2, 2), (1, -1))
sage: Ls = X.all_luna_types(); Ls
[{(1, 1): [2]}, {(1, 1): [1, 1]}]
sage: [X.dimension_of_luna_stratum(tau) for tau in Ls]
[1, 2]
"""
if secure:
assert self.is_luna_type(tau), "``tau`` needs to be a Luna type"
return sum(len(tau[di]) * (1 - self._Q.euler_form(di, di)) for di in tau.keys())
[docs]
def local_quiver_setting(self, tau, secure=True):
r"""
Returns the local quiver and dimension vector for the given Luna type.
The local quiver describes the singularities of a moduli space,
and is introduced and studied in studied in MR1972892_.
.. _MR1972892: https://mathscinet.ams.org/mathscinet/relay-station?mr=1972892
INPUT:
- ``tau`` -- Luna type encoded by a dictionary of multiplicities indexed by
dimension vectors
- ``secure`` -- whether to first check it is a Luna type (default: False)
OUTPUT: tuple consisting of a Quiver object and a dimension vector
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 2), (1, -1))
sage: Ls = X.all_luna_types(); Ls
[{(2, 2): [1]}, {(1, 1): [2]}, {(1, 1): [1, 1]}]
sage: Qloc, dloc = X.local_quiver_setting(Ls[0]);
sage: Qloc.adjacency_matrix(), dloc
([4], (1))
sage: Qloc, dloc = X.local_quiver_setting(Ls[1]);
sage: Qloc.adjacency_matrix(), dloc
([1], (2))
sage: Qloc, dloc = X.local_quiver_setting(Ls[2]);
sage: Qloc.adjacency_matrix(), dloc
(
[1 1]
[1 1], (1, 1)
)
"""
if secure:
assert self.is_luna_type(tau), "``tau`` needs to be a Luna type"
Q = self._Q
# we use the order of vertices provided by ``tau.keys()`` for Qloc and dloc
A = matrix(
[
[Q.generic_ext(dp, eq) for eq in tau.keys() for n in tau[eq]]
for dp in tau.keys()
for m in tau[dp]
]
)
Qloc = Quiver(A)
dloc = vector(m for dp in tau.keys() for m in tau[dp])
return Qloc, dloc
def _codimension_inverse_image_luna_stratum(self, tau):
r"""
Computes the codimension of the preimage of the Luna stratum
This is the codimension of :math:`\pi^{-1}(S_{tau})`
inside:math:`R(Q,{\bf d})` where
.. MATH::
\pi\colon R(Q,{\bf d})^{\theta{\rm-sst}}\to M^{\theta{\rm-sst}}(Q,{\bf d})
is the semistable quotient map.
INPUT:
- ``tau`` -- Luna type encoded by a dictionary of multiplicities indexed by
dimension vectors
OUTPUT: the codimension of the inverse image of the Luna stratum
For ``tau = {d^1: p^1,...,d^s: p^s}``
the codimension of :math:`\pi^{-1}(S_{tau})` is
.. MATH::
-\langle {\bf d},{\bf d} \rangle + \sum_{k=1}^s
(\langle {\bf d}^k,{\bf d}^k\rangle - l(p^k) + ||p^k||^2) -
\dim N(Q_{tau}, {\mathbf{d}_{tau}),
where for a partition :math:`p = (n_1,...,n_l)`, we define
:math:`||p||^2 = \sum_v n_v^2`
and :math:`N(Q_{\tau}, d_{\tau})` is the nullcone of the local quiver setting.
This is currently not working properly because we cannot compute the dimension
of the nullcone::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (3, 3))
sage: Ls = X.all_luna_types()
sage: X._codimension_inverse_image_luna_stratum(Ls[0])
Traceback (most recent call last):
...
NotImplementedError
"""
# setup shorthand
Q, d = self._Q, self._d
Qtau, dtau = self.local_quiver_setting(tau, secure=False)
return (
-Q.euler_form(d, d)
+ sum(
[
Q.euler_form(dk, dk) - sum(m) + sum([nkv**2 for nkv in m])
for (dk, m) in tau.items()
]
)
- Qtau.dimension_nullcone(dtau)
)
[docs]
def codimension_properly_semistable_locus(self):
r"""
Computes the codimension of :math:`R^{\theta\rm-sst}(Q,{\bf d})
\setminus R^{\theta\rm-st}(Q,{\bf d})` inside :math:`R(Q,{\bf d})`.
OUTPUT: codimension of the properly semistable locus
The codimension of the properly semistable locus
is the minimal codimension of the inverse image
of the non-stable Luna strata.
EXAMPLES:
If the semistable locus is the stable locus the codimension is -Infinity::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3)).codimension_properly_semistable_locus()
-Infinity
This is currently not working properly because we cannot compute the dimension
of the nullcone::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (3, 3)).codimension_properly_semistable_locus()
Traceback (most recent call last):
...
NotImplementedError
"""
Ls = self.all_luna_types(exclude_stable=True)
codimensions = [self._codimension_inverse_image_luna_stratum(tau) for tau in Ls]
return min(codimensions, default=-Infinity)
"""
(Semi-)stability
"""
[docs]
def semistable_equals_stable(self):
r"""
Checks whether every semistable representation is stable
for the given stability parameter.
Every :math:`\theta`-semistable representation is
:math:`\theta`-stable if and only if
there are no Luna types other than (possibly) ``{d: [1]}``.
OUTPUT: whether every theta-semistable representation is :math:`\theta`-stable
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (3, 3))
sage: X.semistable_equals_stable()
False
sage: Y = QuiverModuliSpace(Q, (2, 3))
sage: Y.semistable_equals_stable()
True
A double framed example as in arXiv.2311.17004_::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q = Q.framed_quiver((1, 0)).coframed_quiver((0, 0, 1))
sage: d = (1, 2, 3, 1)
sage: theta = (1, 300, -200, -1)
sage: X = QuiverModuliSpace(Q, d, theta)
sage: X.is_theta_coprime()
False
sage: X.semistable_equals_stable()
True
.. _arXiv.2311.17004: https://doi.org/10.48550/arXiv.2311.17004
"""
# setup shorthand
Q, d, theta, denom = self._Q, self._d, self._theta, self._denom
d = Q._coerce_dimension_vector(d)
# the computation of all Luna types takes so much time
# thus we should first tests if ``d`` is ``theta``-coprime
if self.is_theta_coprime():
return True
# this is probably the fastest way as checking theta-coprimality is fast
# whereas checking for existence of a semi-stable representation
# is a bit slower
if not Q.has_semistable_representation(d, theta, denom=denom):
return True
else:
Ls = self.all_luna_types(exclude_stable=True)
return not Ls # this checks if the list is empty
"""
Ample stability
"""
[docs]
def is_amply_stable(self) -> bool:
r"""Checks if the dimension vector is amply stable for the stability parameter
By definition, a dimension vector :math`{\bf d}` is :math:`\theta`-amply stable
if the codimension of the :math:`\theta`-semistable locus
inside:math:`R(Q,{\bf d})` is at least 2.
OUTPUT: whether the data for the quiver moduli space is amply stable
EXAMPLES:
3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3)).is_amply_stable()
True
sage: QuiverModuliSpace(Q, (2, 3), [-3, 2]).is_amply_stable()
False
A three-vertex example from the rigidity paper::
sage: Q = ThreeVertexQuiver(1, 6, 1)
sage: QuiverModuliSpace(Q, [1, 6, 6]).is_amply_stable()
False
"""
HNs = self.all_harder_narasimhan_types(proper=True)
return (
min(
self.codimension_of_harder_narasimhan_stratum(dstar, secure=False)
for dstar in HNs
)
>= 2
)
[docs]
def is_strongly_amply_stable(self) -> bool:
r"""Checks if the dimension vector is strongly amply stable for the stability
parameter
We call :math:`{\bf d}` strongly amply stable for :math:`\theta` if
:math:`\langle{\bf e},{\bf d}-{\bf e}\rangle \leq -2`
holds for all subdimension vectors :math:`{\bf e}` of :math:`{\bf d}` for which
:math:`\mu_{\theta}({\bf e})\geq\mu_{\theta}({\bf d})`.
OUTPUT: whether the data for the quiver moduli space is strongly amply stable
EXAMPLES:
3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3)).is_strongly_amply_stable()
True
A 3-vertex quiver::
sage: from quiver import *
sage: Q = ThreeVertexQuiver(5, 1, 1)
sage: X = QuiverModuliSpace(Q, [4, 1, 4])
sage: X.is_amply_stable()
True
sage: X.is_strongly_amply_stable()
False
"""
# setup shorthand
Q, d, theta, denom = (
self._Q,
self._d,
self._theta,
self._denom,
)
d = Q._coerce_dimension_vector(d)
# subdimension vectors of smaller slope
slope = Q.slope(d, theta=theta, denom=denom)
es = filter(
lambda e: Q.slope(e, theta=theta, denom=denom) >= slope,
Q.all_subdimension_vectors(
d, proper=True, nonzero=True, forget_labels=True
),
)
return all(Q.euler_form(e, d - e) <= -2 for e in es)
"""
Methods related to Teleman quantization
"""
[docs]
def harder_narasimhan_weight(self, harder_narasimhan_type):
r"""
Returns the Teleman weight of a Harder-Narasimhan type
INPUT:
- ``harder_narasimhan_type`` -- list of vectors of Ints
OUTPUT: weight as a fraction
The weight of a Harder-Narasimhan type :math:`{\bf d}^*`
is the weight of the associated 1-PS :math:`\lambda` acting on
:math:`\det(N_{S/R})^{\vee}|_Z`, where `S` is the
corresponding Harder--Narasimhan stratum.
.. SEEALSO:: :meth:`all_weight_bounds`, :meth:`if_rigidity_inequality_holds`
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: HN = X.all_harder_narasimhan_types(proper=True)
sage: {dstar: X.harder_narasimhan_weight(dstar) for dstar in HN}
{((1, 0), (1, 1), (0, 2)): 135,
((1, 0), (1, 2), (0, 1)): 100,
((1, 0), (1, 3)): 90,
((1, 1), (1, 2)): 15/2,
((2, 0), (0, 3)): 270,
((2, 1), (0, 2)): 100,
((2, 2), (0, 1)): 30}
"""
# setup shorthand
Q, theta, denom = self._Q, self._theta, self._denom
HN = harder_narasimhan_type
return -sum(
[
(
Q.slope(HN[s], theta, denom=denom)
- Q.slope(HN[t], theta, denom=denom)
)
* Q.euler_form(HN[s], HN[t])
for s in range(len(HN) - 1)
for t in range(s + 1, len(HN))
]
)
[docs]
def all_weight_bounds(self, as_dict=False):
r"""
Returns the list of all weights appearing in Teleman quantization.
For each HN type, the 1-PS lambda acts on :math:`\det(N_{S/R}^{\vee}|_Z)`
with a certain weight. Teleman quantization gives a numerical condition
involving these weights to compute cohomology on the quotient.
INPUT:
- ``as_dict`` -- (default: False) when True it will give a dict whose keys are
the HN-types and whose values are the weights
EXAMPLES:
The 6-dimensional 3-Kronecker example::
sage: from quiver import *
sage: X = QuiverModuliSpace(KroneckerQuiver(3), (2, 3))
sage: X.all_weight_bounds()
[135, 100, 90, 15/2, 270, 100, 30]
sage: X.all_weight_bounds(as_dict=True)
{((1, 0), (1, 1), (0, 2)): 135,
((1, 0), (1, 2), (0, 1)): 100,
((1, 0), (1, 3)): 90,
((1, 1), (1, 2)): 15/2,
((2, 0), (0, 3)): 270,
((2, 1), (0, 2)): 100,
((2, 2), (0, 1)): 30}
"""
# this is only relevant on the unstable locus
HNs = self.all_harder_narasimhan_types(proper=True)
weights = map(lambda dstar: self.harder_narasimhan_weight(dstar), HNs)
if as_dict:
return dict(zip(HNs, weights))
return list(weights)
[docs]
def if_rigidity_inequality_holds(self) -> bool:
r"""
OUTPUT: whether the rigidity inequality holds on the given moduli
If the weights of the 1-PS lambda on :math:`\det(N_{S/R}|_Z)` for each HN type
are all strictly larger than the weights of the tensors of the universal bundles
:math:`U_i^\vee \otimes U_j`,
then the resulting moduli space is infinitesimally rigid.
EXAMPLES:
Kronecker moduli satisfy the rigidity inequality::
sage: from quiver import *
sage: X = QuiverModuliSpace(KroneckerQuiver(3), (2, 3))
sage: X.if_rigidity_inequality_holds()
True
The following 3-vertex example does not (however, it is rigid by other means)::
sage: X = QuiverModuliSpace(ThreeVertexQuiver(1, 6, 1), [1, 6, 6])
sage: X.if_rigidity_inequality_holds()
False
"""
# setup shorthand
Q, theta, denom = self._Q, self._theta, self._denom
weights = self.all_weight_bounds()
# we compute the maximum weight of the tensors of the universal bundles
# this is only relevant on the unstable locus
HNs = self.all_harder_narasimhan_types(proper=True)
tensor_weights = list(
map(
lambda dstar: Q.slope(dstar[0], theta, denom=denom)
- Q.slope(dstar[-1], theta, denom=denom),
HNs,
)
)
return all(weights[i] > tensor_weights[i] for i in range(len(HNs)))
"""
Tautological relations
"""
def _all_forbidden_subdimension_vectors(self):
r"""Returns the list of all forbidden subdimension vectors
These are the dimension vectors `d'` of d for which
- :math:`\mu_{\theta}(d') > \mu_{\theta}(d)` (in the semistable case)
- or for which :math:`\mu_{\theta}(d') >= \mu_{\theta}(d)` (in the stable case).
OUTPUT: list of forbidden subdimension vectors vectors
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, [3, 3], [1, -1], condition="semistable")
sage: X._all_forbidden_subdimension_vectors()
[(1, 0), (2, 0), (2, 1), (3, 0), (3, 1), (3, 2)]
sage: X = QuiverModuliSpace(Q, [3, 3], [1, -1], condition="stable")
sage: X._all_forbidden_subdimension_vectors()
[(1, 0), (1, 1), (2, 0), (2, 1), (2, 2), (3, 0), (3, 1), (3, 2)]
"""
# setup shorthand
Q, d, theta, denom, condition = (
self._Q,
self._d,
self._theta,
self._denom,
self._condition,
)
es = Q.all_subdimension_vectors(d, proper=True, nonzero=True)
slope = Q.slope(d, theta, denom=denom)
if condition == "semistable":
return list(filter(lambda e: Q.slope(e, theta, denom=denom) > slope, es))
elif condition == "stable":
return list(filter(lambda e: Q.slope(e, theta, denom=denom) >= slope, es))
def _all_minimal_forbidden_subdimension_vectors(self):
r"""Returns the list of all `minimal` forbidden subdimension vectors
Minimality is with respect to the partial order :math`e\ll d` which means
:math:`e_i \leq d_i` for every source `i`, :math:`e_j \geq d_j`
for every sink `j`, and :math:`e_k = d_k` for every vertex which is neither
a source nor a sink. See also :meth:`Quiver.division_order`.
OUTPUT: list of minimal forbidden dimension vectors
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (3, 3))
sage: X._all_minimal_forbidden_subdimension_vectors()
[(1, 0), (2, 1), (3, 2)]
sage: Y = QuiverModuliSpace(Q, (3, 3), condition="stable")
sage: Y._all_minimal_forbidden_subdimension_vectors()
[(1, 1), (2, 2)]
"""
# setup shorthand
Q = self._Q
forbidden = self._all_forbidden_subdimension_vectors()
def is_minimal(e):
return not any(
Q.division_order(f, e)
for f in list(filter(lambda f: f != e, forbidden))
)
return list(filter(is_minimal, forbidden))
def __generator(self, R, i, r):
r"""
Returns the appropriate generator of R.
This is a repeatedly used helper function in dealing with Chow rings.
EXAMPLES:
We index some generators of the polynomial ring defining the Chow ring of the
Kronecker 6-fold::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: chi = (-1, 1)
sage: A = X.chow_ring(chi=chi, classes=["x1", "x2", "y1", "y2", "y3"])
sage: R = A.defining_ideal().ring()
sage: R
Multivariate Polynomial Ring in x1, x2, y1, y2, y3 over Rational Field
sage: X._QuiverModuli__generator(R, 0, 0)
x1
sage: X._QuiverModuli__generator(R, 1, 2)
y3
"""
# setup shorthand
Q, d = self._Q, self._d
d = Q._coerce_dimension_vector(d)
return R.gen(r + sum(d[j] for j in range(i)))
[docs]
def tautological_ideal(self, use_roots=False, classes=None, roots=None):
r"""
Returns the tautological presentation of the Chow ring of the moduli space.
INPUT:
- ``use_roots`` -- (default: False) whether to return the relations in Chern
roots
- ``classes`` -- (default: None) optional list of strings to name the Chern
classes
- ``roots`` -- (default: None) optional list of strings to name the Chern roots
OUTPUT: ideal of a polynomial ring
EXAMPLES:
The tautological ideal for our favourite 6-fold has 9 non-zero generators::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: len(X.tautological_ideal().gens())
9
"""
return self.__tautological_ideal_helper(
use_roots=use_roots, classes=classes, roots=roots
)["ideal"]
def __tautological_ideal_helper(self, use_roots=False, classes=None, roots=None):
r"""
Helper function for the tautological ideal.
INPUT:
- ``use_roots`` -- (default: False) whether to return the relations in Chern
roots
- ``classes`` -- (default: None) optional list of strings to name the Chern
classes
- ``roots`` -- (default: None) optional list of strings to name the Chern roots
OUTPUT: dictionary with keys "ideal", "inclusion" and "ambient_ring"
EXAMPLES:
The tautological ideal for our favourite 6-fold has 9 non-zero generators::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: X._QuiverModuli__tautological_ideal_helper()["ambient_ring"]
Multivariate Polynomial Ring in t0_1, t0_2, t1_1, t1_2, t1_3
over Rational Field
.. SEEALSO:: :meth:`tautological_ideal`
"""
# setup shorthand
Q, d = self._Q, self._d
d = Q._coerce_dimension_vector(d)
if classes is None:
classes = [
"x{}_{}".format(i, r)
for i in range(Q.number_of_vertices())
for r in range(1, d[i] + 1)
]
if roots is None:
roots = [
"t{}_{}".format(i, r)
for i in range(Q.number_of_vertices())
for r in range(1, d[i] + 1)
]
assert len(classes) == sum(d), "number of classes must be number of generators"
assert len(roots) == sum(d), "number of roots must be number of generators"
R = PolynomialRing(QQ, roots)
r"""Generators of the tautological ideal regarded upstairs, i.e. in A*([R/T]).
For a forbidden subdimension vector e of d, the forbidden polynomial in Chern
roots is given by :math:`\prod_{a: i \to j} \prod_{r=1}^{e_i}
\prod_{s=e_j+1}^{d_j} (tj_s - ti_r) =
\prod_{i,j} \prod_{r=1}^{e_i} \prod_{s=e_j+1}^{d_j} (tj_s - ti_r)^{a_{ij}}."""
forbidden_polynomials = [
prod(
prod(
(self.__generator(R, j, s) - self.__generator(R, i, r))
** Q.adjacency_matrix()[i, j]
for r in range(e[i])
for s in range(e[j], d[j])
)
for i in range(Q.number_of_vertices())
for j in range(Q.number_of_vertices())
)
for e in self._all_minimal_forbidden_subdimension_vectors()
]
# the user wants to have the ideal in `R`
if use_roots:
return {"ideal": R.ideal(forbidden_polynomials), "ambient_ring": R}
# delta is the discriminant: precomputed for antisymmetrization(f)
delta = prod(
prod(
self.__generator(R, i, l) - self.__generator(R, i, k)
for k in range(d[i])
for l in range(k + 1, d[i])
)
for i in range(Q.number_of_vertices())
)
# longest is the longest Weyl group element
# regarding W as a subgroup of S_{sum d_i}
longest = []
r = 0
for i in range(Q.number_of_vertices()):
longest = longest + list(reversed(range(r + 1, r + d[i] + 1)))
r += d[i]
# Weyl group: precomputed for antisymmetrization(f)
W = Permutations(bruhat_smaller=longest)
def antisymmetrization(f):
r"""The antisymmetrization of a polynomial `f` is the symmetrization
divided by the discriminant."""
def permute(f, w):
return f.subs({R.gen(i): R.gen(w[i] - 1) for i in range(R.ngens())})
return sum(w.sign() * permute(f, w) for w in W) // delta
# we construct the Schubert basis of CH^*([R/T]) over CH^*([R/G])
X = SchubertPolynomialRing(ZZ)
def B(i):
return [X(p).expand() for p in Permutations(d[i])]
Bprime = [
[
f.parent().hom(
[self.__generator(R, i, r) for r in range(f.parent().ngens())],
R,
)(f)
for f in B(i)
]
for i in Q.support(d)
]
# take a list of lists of elements of a ring and multiply them recursively
# multiplying each element of the first list with the products of the remaining
# there might be a more Pythonic way of doing this, but it'll do for now
def product_lists(L):
n = len(L)
assert n > 0
if n == 1:
return L[0]
else:
P = product_lists([L[i] for i in range(n - 1)])
return [p * l for p in P for l in L[n - 1]]
schubert = product_lists(Bprime)
# define A = CH^*([R/G])
degrees = []
for i in range(Q.number_of_vertices()):
degrees = degrees + list(range(1, d[i] + 1))
A = PolynomialRing(QQ, classes, order=TermOrder("wdegrevlex", degrees))
E = SymmetricFunctions(ZZ).e()
"""The Chern classes of U_i on [R/G] are the elementary symmetric functions
in the Chern roots ti_1,...,ti_{d_i}."""
elementarySymmetric = []
for i in range(Q.number_of_vertices()):
elementarySymmetric = elementarySymmetric + [
E([k]).expand(
d[i],
alphabet=[self.__generator(R, i, r) for r in range(d[i])],
)
for k in range(1, d[i] + 1)
]
"""Map xi_r to the r-th elementary symmetric function
in ti_1,...,ti_{d_i}."""
inclusion = A.hom(elementarySymmetric, R)
"""Tautological relations in Chern classes."""
tautological = [
antisymmetrization(b * f) for b in schubert for f in forbidden_polynomials
]
tautological = [inclusion.inverse_image(g) for g in tautological]
# get rid of zeroes
tautological = [f for f in tautological if f]
return {
"ideal": A.ideal(tautological),
"ambient_ring": R,
"inclusion": inclusion,
}
[docs]
def dimension(self) -> int:
r"""
Returns the dimension of the moduli stack.
.. SEEALSO::
- :meth:`QuiverModuliSpace.dimension`
- :meth:`QuiverModuliStack.dimension`
EXAMPLES:
This is not implemented as it is ambiguous::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuli(Q, (2, 3)).dimension()
Traceback (most recent call last):
...
NotImplementedError
"""
raise NotImplementedError()
[docs]
def is_smooth(self) -> bool:
r"""
Checks if the moduli space is smooth.
Abstract method, see the concrete implementations for details.
.. SEEALSO::
- :meth:`QuiverModuliSpace.is_smooth`
- :meth:`QuiverModuliStack.is_smooth`
This is not implemented as it is ambiguous::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuli(Q, (2, 3)).is_smooth()
Traceback (most recent call last):
...
NotImplementedError
"""
raise NotImplementedError()
[docs]
def chow_ring(self):
r"""
Returns the Chow ring of the moduli space.
Abstract method, see the concrete implementations for details.
.. SEEALSO::
- :meth:`QuiverModuliSpace.chow_ring`
- :meth:`QuiverModuliStack.chow_ring`
EXAMPLES:
This is not implemented as it is ambiguous::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuli(Q, (2, 3)).is_smooth()
Traceback (most recent call last):
...
NotImplementedError
"""
raise NotImplementedError()
[docs]
class QuiverModuliSpace(QuiverModuli):
[docs]
def __init__(self, Q, d, theta=None, denom=sum, condition="semistable"):
r"""Constructor for a quiver moduli space
This is the quiver moduli space as a variety.
INPUT:
- ``Q`` -- quiver
- ``d`` --- dimension vector
- ``theta`` -- stability parameter (default: canonical stability parameter)
- ``denom`` -- denominator for slope stability (default: ``sum``), needs to be
effective on the simple roots
- ``condition`` -- whether to include all semistables, or only stables
(default: "semistable")
EXAMPLES:
An example::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3))
moduli space of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
"""
QuiverModuli.__init__(
self,
Q,
d,
theta=theta,
denom=denom,
condition=condition,
)
def _repr_(self):
r"""
Give a shorthand string presentation for the quiver moduli space
EXAMPLES:
A Kronecker moduli space::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3))
moduli space of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
"""
if self.get_custom_name():
return self.get_custom_name()
return super()._QuiverModuli__repr_helper("moduli space")
[docs]
def repr(self):
r"""
Give a shorthand string presentation for the quiver moduli space
EXAMPLES:
A Kronecker moduli space::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3))
moduli space of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
"""
return self._repr_()
[docs]
def dimension(self):
r"""
Computes the dimension of the moduli space :math:`M^{\theta-(s)st}(Q,{\bf d})`.
This involves several cases:
- If there are :math:`\theta`-stable representations then
:math:`\dim M^{\theta\rm-sst}(Q,{\bf d}) =
M^{\theta-st}(Q,{\bf d}) = 1 - \langle {\bf d},{\bf d}\rangle`;
- if there are no :math:`\theta`-stable representations then
:math:`\dim M^{\theta-st}(Q,{\bf d}) = -\infty` by convention,
and we define :math:`\dim M^{\theta\rm\rm-sst} =
\mathrm{max}_{\tau} \{\dim S_{\tau}\}`,
the maximum of the dimension of all Luna strata.
EXAMPLES
The A2-quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(1)
sage: X = QuiverModuliSpace(Q, [1, 1], condition="stable")
sage: X.dimension()
0
sage: X = QuiverModuliSpace(Q, [1, 1], condition="semistable")
sage: X.dimension()
0
sage: X = QuiverModuliSpace(Q, [2, 2], condition="stable")
sage: X.dimension()
-Infinity
sage: X = QuiverModuliSpace(Q, [2, 2], condition="semistable")
sage: X.dimension()
0
The Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(2)
sage: X = QuiverModuliSpace(Q, [1, 1], [1, -1], condition="stable")
sage: X.dimension()
1
sage: X = QuiverModuliSpace(Q, [1, 1], [1, -1], condition="semistable")
sage: X.dimension()
1
sage: X = QuiverModuliSpace(Q, [2, 2], [1, -1], condition="stable")
sage: X.dimension()
-Infinity
sage: X = QuiverModuliSpace(Q, [2, 2], [1, -1], condition="semistable")
sage: X.dimension()
2
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3), condition="semistable")
sage: X.dimension()
6
sage: X = QuiverModuliSpace(Q, [3, 3],condition="semistable")
sage: X.dimension()
10
sage: X = QuiverModuliSpace(Q, [1, 3],condition="stable")
sage: X.dimension()
0
sage: X = QuiverModuliSpace(Q, [1, 4],condition="stable")
sage: X.dimension()
-Infinity
sage: X = QuiverModuliSpace(Q, [1, 4],condition="semistable")
sage: X.dimension()
-Infinity
The Jordan quiver::
sage: QuiverModuliSpace(JordanQuiver(1), (0,)).dimension()
0
sage: X = QuiverModuliSpace(JordanQuiver(1), (0,), condition="stable")
sage: X.dimension()
-Infinity
sage: QuiverModuliSpace(JordanQuiver(1), (1,)).dimension()
1
sage: QuiverModuliSpace(JordanQuiver(1), (2,)).dimension()
2
sage: QuiverModuliSpace(JordanQuiver(1), (3,)).dimension()
3
sage: QuiverModuliSpace(JordanQuiver(1), (4,)).dimension()
4
Some generalized Jordan quivers::
sage: QuiverModuliSpace(JordanQuiver(2), (0,)).dimension()
0
sage: QuiverModuliSpace(JordanQuiver(2), (1,)).dimension()
2
sage: QuiverModuliSpace(JordanQuiver(2), (2,)).dimension()
5
sage: QuiverModuliSpace(JordanQuiver(2), (3,)).dimension()
10
sage: QuiverModuliSpace(JordanQuiver(2), (4,)).dimension()
17
More generalized Jordan quivers::
sage: QuiverModuliSpace(JordanQuiver(3), (0,)).dimension()
0
sage: QuiverModuliSpace(JordanQuiver(3), (1,)).dimension()
3
sage: QuiverModuliSpace(JordanQuiver(3), (2,)).dimension()
9
sage: QuiverModuliSpace(JordanQuiver(3), (3,)).dimension()
19
sage: QuiverModuliSpace(JordanQuiver(3), (4,)).dimension()
33
"""
# setup shorthand
Q, d, theta = (
self._Q,
self._d,
self._theta,
)
# the zero dimension vector only has the zero representation which is semistable
# but not stable
if Q._coerce_dimension_vector(d) == Q.zero_vector():
if self._condition == "semistable":
return 0
return -Infinity
# if there are stable representations then both the stable and
# the semi-stable moduli space have dimension `1-<d,d>`
if Q.has_stable_representation(d, theta):
return 1 - Q.euler_form(d, d)
# stable locus is empty
if self._condition == "stable":
return -Infinity
# we care about the semistable locus
if Q.has_semistable_representation(d, theta):
# in this case the dimension is given by
# the maximum of the dimensions of the Luna strata
return max(
self.dimension_of_luna_stratum(tau) for tau in self.all_luna_types()
)
# semistable locus is also empty
return -Infinity
[docs]
def poincare_polynomial(self):
r"""
Returns the Poincare polynomial of the moduli space.
OUTPUT: Poincaré polynomial in the variable ``q``
The Poincare polynomial is defined as
.. MATH::
P_X(q) = \sum_{i \geq 0} (-1)^i \dim{\rm H}^i(X;\mathbb{C}) q^{i/2}
For a quiver moduli space whose dimension vector is
:math:`\theta`-coprime, the odd cohomology vanishes
and this is a polynomial in :math:`q`.
ALGORITHM:
Corollary 6.9 in MR1974891_.
.. _MR1974891: https://mathscinet.ams.org/mathscinet/relay-station?mr=1974891
EXAMPLES:
Some Kronecker quivers::
sage: from quiver import *
sage: Q = KroneckerQuiver()
sage: X = QuiverModuliSpace(Q, (1, 1))
sage: X.poincare_polynomial()
q + 1
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: X.poincare_polynomial()
q^6 + q^5 + 3*q^4 + 3*q^3 + 3*q^2 + q + 1
sage: Q = SubspaceQuiver(5)
sage: X = QuiverModuliSpace(Q, (1, 1, 1, 1, 1, 2))
sage: X.poincare_polynomial()
q^2 + 5*q + 1
"""
# setup shorthand
Q, d, theta = self._Q, self._d, self._theta
d = Q._coerce_dimension_vector(d)
theta = Q._coerce_vector(theta)
assert self.is_theta_coprime(), "need coprime"
k = FunctionField(QQ, "L")
K = FunctionField(QQ, "q")
q = K.gen(0)
f = k.hom(q, K)
X = QuiverModuliStack(Q, d, theta, condition="semistable")
P = (1 - q) * f(X.motive())
assert P.denominator() == 1, "must live in the polynomial ring"
return P.numerator()
[docs]
def betti_numbers(self):
r"""
Returns the Betti numbers of the moduli space.
OUTPUT: Betti numbers of the moduli space
ALGORITHM:
Corollary 6.9 in MR1974891_.
.. _MR1974891: https://mathscinet.ams.org/mathscinet/relay-station?mr=1974891
EXAMPLES:
Some Kronecker quivers::
sage: from quiver import *
sage: Q = KroneckerQuiver()
sage: X = QuiverModuliSpace(Q, (1, 1), condition="semistable")
sage: X.poincare_polynomial()
q + 1
sage: X.betti_numbers()
[1, 0, 1]
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3), condition="semistable")
sage: X.betti_numbers()
[1, 0, 1, 0, 3, 0, 3, 0, 3, 0, 1, 0, 1]
"""
# setup shorthand
Q, d, theta = self._Q, self._d, self._theta
d = Q._coerce_dimension_vector(d)
theta = Q._coerce_vector(theta)
assert self.is_theta_coprime(), "need coprime"
N = self.dimension()
K = FunctionField(QQ, "q")
L = FunctionField(QQ, "v")
v = L.gen(0)
ext = K.hom(v**2, L)
# p is the prime place of the DVR associated with v
p = v.zeros()[0]
f = ext(self.poincare_polynomial())
betti = [f.evaluate(p)]
for i in range(2 * N):
f = (f - f.evaluate(p)) / v
betti = betti + [f.evaluate(p)]
return betti
[docs]
def is_smooth(self) -> bool:
r"""
Returns whether the moduli space is smooth.
This is easy if the condition is ``"stable"``, because this moduli space
is always smooth. In the ``"semistable"`` case there is an algorithm,
by combining the work of Adriaenssens--Le Bruyn and Bocklandt,
which is currently not implemented.
EXAMPLES:
Some 3-Kronecker example::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3)).is_smooth()
True
sage: QuiverModuliSpace(Q, (2, 3), condition="stable").is_smooth()
True
sage: QuiverModuliSpace(Q, (3, 3), condition="stable").is_smooth()
True
sage: QuiverModuliSpace(Q, (3, 3)).is_smooth()
Traceback (most recent call last):
...
NotImplementedError
"""
# stable locus is always smooth
if self._condition == "stable":
return True
# if we have semistables, it is more subtle
# this guarantees smoothness without an expensive calculation
if self._Q.is_theta_coprime(self._d, self._theta):
return True
# also guarantees smoothness
if self.semistable_equals_stable():
return True
# need to combine the local quivers from Adriaenssens--Le Bruyn
# with Bocklandt's criterion for smoothness
# see https://github.com/QuiverTools/QuiverTools/issues/24
raise NotImplementedError()
[docs]
def semisimple_moduli_space(self):
r"""
Return the moduli space with ``theta`` replaced by zero.
This is the moduli space of semisimple representations for the same quiver
and the same dimension vector.
EXAMPLES:
For an acyclic quiver this moduli space is a point::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: X.semisimple_moduli_space().dimension()
0
For a quiver with oriented cycles we get an affine variety::
sage: Q = JordanQuiver(2)
sage: X = QuiverModuliSpace(Q, (3,))
sage: X.dimension()
10
"""
# setup shorthand
Q, d = (
self._Q,
self._d,
)
return QuiverModuliSpace(Q, d, theta=Q.zero_vector())
[docs]
def is_projective(self) -> bool:
r"""
Check whether the moduli space is projective
EXAMPLES:
For acyclic quivers the semistable moduli space is always projective::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3)).is_projective()
True
If we have strictly semistable representations, then the stable moduli space
is only quasiprojective but not projective::
sage: QuiverModuliSpace(Q, (3, 3), condition="stable").is_projective()
False
In pathological cases we can have that the affine moduli space of semisimples
is reduced to a point, and the projective-over-affine becomes projective::
sage: Q = CyclicQuiver(3)
sage: QuiverModuliSpace(Q, (2, 0, 2)).is_projective()
True
For the zero dimension vector we get either a point or an empty space, which is
always projective::
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (0, 0)).is_projective()
True
sage: QuiverModuliSpace(Q, (0, 0), condition="stable").is_projective()
True
"""
# setup shorthand
Q, condition = self._Q, self._condition
# in the acyclic case the semistable moduli space is always projective
# the stable moduli space is projective if semistability is stability
if Q.is_acyclic():
if condition == "semistable":
return True
if condition == "stable":
return self.semistable_equals_stable()
# so now Q has oriented cycles: the moduli space is projective-over-affine
# if we have semistable, or quasiprojective-over-affine is we have stable
# it suffices that the affine is just a point then
if condition == "semistable":
return self.semisimple_moduli_space().dimension() <= 0
if condition == "stable":
return (
self.semisimple_moduli_space().dimension() <= 0
and self.semistable_equals_stable()
)
[docs]
def picard_rank(self):
r"""
Computes the Picard rank of the moduli space.
We compute this as the Betti number :math:`\mathrm{b}_2`.
EXAMPLES:
Kronecker moduli are rank 1::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3)).picard_rank()
1
"""
assert self.is_smooth and self.is_projective(), "must be smooth and projective"
return self.betti_numbers()[2]
[docs]
def index(self):
r"""
Computes the index of the moduli space
The index is the largest integer dividing the canonical divisor in Pic.
For now this is only implemented for the canonical stability condition.
EXAMPLES:
The usual 3-Kronecker example::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliSpace(Q, (2, 3)).index()
3
Subspace quiver moduli have index 1::
sage: Q = SubspaceQuiver(7)
sage: QuiverModuliSpace(Q, (1, 1, 1, 1, 1, 1, 1, 2)).index()
1
"""
# setup shorthand
Q, d, theta = self._Q, self._d, self._theta
if (
theta == Q.canonical_stability_parameter(d)
and self.is_theta_coprime()
and self.is_amply_stable()
):
return gcd(Q._coerce_vector(theta))
raise NotImplementedError()
[docs]
def chow_ring(self, chi=None, classes=None):
r"""
Returns the Chow ring of the moduli space.
For a given datum :math:`(Q, {\bf d}, \theta)` such that
:math:`Q` is acyclic and :math:`{\bf d}` is :math:`\theta`-coprime,
the Chow ring of the moduli space of quiver representations
is described in MR3318266_ and arXiv.2307.01711_.
.. _MR3318266: https://mathscinet.ams.org/mathscinet-getitem?mr=3318266
.. _arXiv.2307.01711: https://doi.org/10.48550/arXiv.2307.01711
Let
.. MATH::
R = \bigotimes{i \in Q_0} \mathbb{Q}[x_{i, 1}, \dots, x_{i,d_i}]
Let :math:`e_{i, j}` be the elementary symmetric function of degree :math:`j`
in :math:`d_i` variables, and let :math:`\xi_{i, j}` be
:math:`e_{i, j}(x_{i, 1},\dots,x_{i, d_i})`.
We denote by :math:`A` the ring of invariants
.. MATH::
A := R^{S_{\bf d}} = \mathbb{Q}[\xi_{i, j}],
where :math:`S_{\bf d} = \prod_{i \in Q_0} S_{{\bf d}_i}` acts by permuting
the variables.
The ring :math:`\operatorname{CH}(M^{\theta-st}(Q,{\bf d}))` is a quotient
of `A` by two types of relations:
a single linear relation, given by the choice of linearization upon which
the universal bundles are constructed, and the so-called
tautological relations, which we define below.
The *linear relation* given by the linearization `a` is the identity
:math:`\sum_{i \in Q_0} a_i c_1(U_i) = 0` in :math:`A`.
A subdimension vector :math:`{\bf e}` of :math:`{\bf d}` is said to be
"forbidden" if :math:`\mu_{\theta}({\bf e}) > \mu_{\theta}({\bf d})`.
One actually only needs to consider forbidden dimension vectors that are minimal
with respect to a certain partial order, see :meth:`Quiver.division_order`.
We define the *tautological ideal* :math:`I_{\rm taut}` of `R` as the ideal
generated by the polynomials
.. MATH::
\prod_{a\in Q_1}\prod_{k=1}^{e_{s(a)}}
\prod_{\ell=d_{t(a)}+1}^{d_{t(a)}}
\left( x_{t(a),\ell}-x_{s(a),k} \right),
for every forbidden subdimension vector `e` of `d`.
The tautological relations in `A` are then given by the image of
:math:`I_{\rm taut}` under the `antisymmetrization` map
.. MATH::
\rho : R \to A: \frac{1}{\delta}
\sum_{\sigma \in S_{\bf d}} sign(\sigma) \sigma \cdot f,
where :math:`\delta` is the discriminant
:math:`\prod_{i\in Q_0}\prod_{1\leq k<\ell\leq d_i}(x_{i,\ell}-x_{i,k})`.
The Chow ring :math:`\operatorname{CH}(M^{\theta\rm-st}(Q,{\bf d}))` is then
the quotient of `A` by :math:`(\sum_{i\in Q_0} a_i c_1(U_i)) + \rho(I_{taut})`.
INPUT:
- ``chi`` -- choice of linearization, we need that :math:`\chi({\bf d})=1`
- ``classes`` -- list of generators for the polynomial ring (default: None)
OUTPUT: ring
EXAMPLES:
The Kronecker quiver::
sage: from quiver import *
sage: Q= KroneckerQuiver()
sage: X = QuiverModuliSpace(Q, (1, 1))
sage: chi = (1, 0)
sage: A = X.chow_ring(chi=chi)
sage: I = A.defining_ideal()
sage: [I.normal_basis(i) for i in range(X.dimension()+1)]
[[1], [x1_1]]
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: chi = (-1, 1)
sage: A = X.chow_ring(chi=chi)
sage: I = A.defining_ideal()
sage: [I.normal_basis(i) for i in range(X.dimension()+1)]
[[1],
[x1_1],
[x0_2, x1_1^2, x1_2],
[x1_1^3, x1_1*x1_2, x1_3],
[x1_1^2*x1_2, x1_2^2, x1_1*x1_3],
[x1_2*x1_3],
[x1_3^2]]
The 5-subspace quiver::
sage: from quiver import *
sage: Q, d = SubspaceQuiver(5), (1, 1, 1, 1, 1, 2)
sage: theta = (2, 2, 2, 2, 2, -5)
sage: X = QuiverModuliSpace(Q, d, theta, condition="semistable")
sage: chi = (-1, -1, -1, -1, -1, 3)
sage: A = X.chow_ring(chi=chi)
sage: I = A.defining_ideal()
sage: [I.normal_basis(i) for i in range(X.dimension()+1)]
[[1], [x1_1, x2_1, x3_1, x4_1, x5_1], [x5_2]]
The ideal Chow ring for our favourite 6-fold has 10 generators, 9 from the
tautological ideal, and 1 linear relation::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: chi = (-1, 1)
sage: R = X.chow_ring(chi=chi);
sage: R.ambient()
Multivariate Polynomial Ring in x0_1, x0_2, x1_1, x1_2, x1_3
over Rational Field
sage: len(R.defining_ideal().gens())
10
"""
Q, d, theta = self._Q, self._d, self._theta
n = Q.number_of_vertices()
d = Q._coerce_dimension_vector(d)
theta = Q._coerce_vector(theta)
# this implementation only works if d is theta-coprime
# which implies that d is indivisible.
assert Q.is_theta_coprime(d, theta), "need coprime"
def extended_gcd(x):
r"""
Computes the gcd and the Bezout coefficients of a list of integers.
This exists for two integers but seemingly not for more than two.
"""
n = len(x)
if n == 1:
return [x, [1]]
if n == 2:
(g, a, b) = xgcd(x[0], x[1])
return [g, [a, b]]
if n > 2:
(g, a, b) = xgcd(x[0], x[1])
y = [g] + [x[i] for i in range(2, n)]
[d, c] = extended_gcd(y)
m = [c[0] * a, c[0] * b] + [c[i] for i in range(1, n - 1)]
return [d, m]
# if a linearization is not given we compute one here
if chi is None:
[g, m] = extended_gcd(d.list())
chi = vector(m)
chi = Q._coerce_vector(chi)
# make sure that chi has weight one, i.e., provides a retraction for
# X*(PG) --> X*(G).
assert chi * d == 1
tautological = self.tautological_ideal(use_roots=False, classes=classes)
A = tautological.ring()
linear = A.ideal(
sum(chi[i] * self._QuiverModuli__generator(A, i, 0) for i in range(n))
)
return QuotientRing(A, tautological + linear, names=classes)
[docs]
def chern_class_line_bundle(self, eta, classes=None):
r"""
Returns the first Chern class of the line bundle
.. MATH::
L(\eta) = \bigotimes_{i \in Q_0} \det(U_i)^{-\eta_i},
where :math:`\eta` is a character of :math:`PG_d`.
INPUT:
- ``eta`` -- character of :math:`PG_d` as vector in :math:`\mathbb{Z}Q_0`
EXAMPLES:
On the Kronecker 6-fold we can take the canonical line bundle, which we can
see to have index 3::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: eta = Q.canonical_stability_parameter((2, 3))
sage: X.chern_class_line_bundle(eta)
-3*x1_1bar
"""
# setup shorthand
Q, d = self._Q, self._d
d = Q._coerce_dimension_vector(d)
A = self.chow_ring(chi=None, classes=classes)
return -sum(
eta[i] * A.gen(sum(d[j] for j in range(i)))
for i in range(Q.number_of_vertices())
)
[docs]
def chern_character_line_bundle(self, eta, classes=None):
r"""
Computes the Chern character of L(eta).
The Chern character of a line bundle `L` with first Chern class `x`
is given by :math:`e^x = 1 + x + \frac{x^2}{2} + \frac{x^3}{6} + \dots`
EXAMPLES:
On the Kronecker 6-fold the canonical line bundle has the following Chern
character::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: eta = Q.canonical_stability_parameter((2, 3))
sage: X.chern_character_line_bundle(eta)
4617/80*x1_3bar^2 - 1539/40*x1_2bar*x1_3bar + 81/8*x1_1bar^2*x1_2bar
- 27/8*x1_2bar^2 - 27/4*x1_1bar*x1_3bar - 9/2*x1_1bar^3 + 9/2*x1_1bar^2
- 3*x1_1bar + 1
"""
x = self.chern_class_line_bundle(eta, classes=classes)
return sum(x**i / factorial(i) for i in range(self.dimension() + 1))
[docs]
def total_chern_class_universal(self, i, chi, classes=None):
r"""
Gives the total Chern class of the universal bundle :math:`U_i(chi)`.
EXAMPLES:
The two summands for the Kronecker 6-fold::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: chi = (-1, 1)
sage: X.total_chern_class_universal(0, chi)
x0_2bar + 2*x1_1bar + 1
sage: X.total_chern_class_universal(1, chi)
x0_2bar + x1_1bar + 1
"""
# setup shorthand
Q, d = self._Q, self._d
d = Q._coerce_dimension_vector(self._d)
A = self.chow_ring(chi, classes=classes)
return 1 + sum(
A.gen(r + sum(d[j] for j in range(i - 1))) for r in range(d[i - 1])
)
[docs]
def point_class(self, chi=None, classes=None):
r"""
Returns the point class as an expression in Chern classes of the
:math:`U_i` (``chi``).
INPUT:
- ``chi`` -- linearization of the universal bundles (default: None)
The point class is given as the homogeneous component of degree
:math:`\dim X` of the expression
.. MATH::
\prod_{a \in Q_1} c(U_{t(a)})^{d_{s(a)}} / (\prod_{i \in Q_0} c(U_i)^{d_i})
EXAMPLES
:math:`\mathbb{P}^7` as a quiver moduli space
of a generalized Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(8)
sage: X = QuiverModuliSpace(Q, (1, 1))
sage: chi = (1, 0)
sage: X.point_class(chi, classes=["o", "h"])
h^7
Our favorite 6-fold::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: chi = (-1, 1)
sage: X.point_class(chi, classes=["x1", "x2", "y1", "y2", "y3"])
y3^2
A moduli space of the 5-subspace quiver;
it agrees with the blow-up of :math:`\mathbb{P}^2` in 4 points
in general position::
sage: from quiver import *
sage: Q = SubspaceQuiver(5)
sage: theta = (2, 2, 2, 2, 2, -5)
sage: X = QuiverModuliSpace(Q, (1, 1, 1, 1, 1, 2))
sage: chi = (-1, -1, -1, -1, -1, 3)
sage: X.point_class(chi, classes=['x1', 'x2', 'x3', 'x4', 'x5', 'y', 'z'])
1/2*z
If we don't specify ``chi`` a default that will still work is used, but the
results do depend on it::
sage: X.point_class(classes=['x1', 'x2', 'x3', 'x4', 'x5', 'y', 'z'])
-1/3*y^2
"""
# setup shorthand
Q, d = self._Q, self._d
d = Q._coerce_dimension_vector(d)
A = self.chow_ring(chi=chi, classes=classes)
section = A.lifting_map() # a choice of a section of pi
p = prod(
self.total_chern_class_universal(j + 1, chi, classes=classes)
** (d * Q.adjacency_matrix().column(j))
for j in range(Q.number_of_vertices())
)
q = prod(
self.total_chern_class_universal(i + 1, chi, classes=classes) ** d[i]
for i in range(Q.number_of_vertices())
)
quotient = p / q
pi = A.cover() # the quotient map
return pi(section(quotient).homogeneous_components()[self.dimension()])
[docs]
def degree(self, eta=None, classes=None):
r"""
Computes the degree of the line bundle given by eta.
INPUT:
- ``eta`` -- class of line bundle (default: anticanonical line bundle
- ``classes`` -- variables to be used (default: None)
EXAMPLES:
Rederive a calculation from arXiv.2307.01711_::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: d = (2, 3)
sage: X = QuiverModuliSpace(Q, d)
sage: eta = Q.canonical_stability_parameter(d)
sage: eta = eta / 3
sage: X.degree(eta)
57
.. _arXiv.2307.01711: https://doi.org/10.48550/arXiv.2307.01711
"""
if eta is None:
eta = self._Q.canonical_stability_parameter(self._d)
c = self.chern_class_line_bundle(eta, classes=classes)
p = self.point_class(classes=classes)
return c ** self.dimension() / p
[docs]
def todd_class(self, chi=None, classes=None):
r"""
The Todd class of `X` is the Todd class of the tangent bundle.
INPUT:
- ``chi`` -- linearization of the universal bundles (default: None)
- ``classes`` -- variables to be used (default: None)
OUTPUT: the Todd class as an element of the Chow ring
The Todd class is computed in arXiv.2307.01711_. It is given by the formula
.. MATH::
td(X) =
(\prod_{a:i \to j \in Q_1} \prod_{p=1}^{d_j} \prod_{q=1}^{d_i} Q(t_{j,q} -
t_{i,p}))/(\prod_{i \in Q_0} \prod_{p,q=1}^{d_i} Q(t_{i,q} - t_{i,p}))
EXAMPLES:
An example from arXiv.2307.01711_::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: X.todd_class()
x1_3bar^2 - 77/60*x1_2bar*x1_3bar + 823/1440*x1_1bar^2*x1_2bar -
823/4320*x1_2bar^2 - 257/4320*x1_1bar*x1_3bar - 17/32*x1_1bar^3 -
15/32*x1_3bar + 5/12*x0_2bar + x1_1bar^2 - 3/2*x1_1bar + 1
.. _arXiv.2307.01711: https://doi.org/10.48550/arXiv.2307.01711
"""
def todd_Q(t, n):
r"""
We call the series :math:`Q(t) = t/(1-e^{-t})` the Todd generating series.
The function computes the terms of this series up to degree n.
We use this instead of the more conventional notation `Q` to avoid a
clash with the notation for the quiver.
"""
return sum(
(-1) ** i * (bernoulli(i) * t**i) / factorial(i) for i in range(n + 1)
)
def truncate(f, n):
r"""
Takes an element in a graded ring and discards all homogeneous components
of degree > n
"""
components = f.homogeneous_components()
keys = [i for i in components]
return sum(components[i] for i in filter(lambda i: i <= n, keys))
# setup shorthand
Q, d = self._Q, self._d
A = self.chow_ring(chi=chi, classes=classes)
taut = self._QuiverModuli__tautological_ideal_helper(
use_roots=False, classes=classes, roots=None
)
R, inclusion = taut["ambient_ring"], taut["inclusion"]
n = self.dimension()
def short_t(i, p):
r"""
Shorthand for the generators of the ambient ring
from which the Chow ring is constructed
"""
return self._QuiverModuli__generator(R, i, p)
num = 1
den = 1
# truncating after each step
# massively cuts runtime
for a in Q.arrows():
i, j = a
for p in range(d[i]):
for q in range(d[j]):
num *= todd_Q(short_t(j, q) - short_t(i, p), n)
num = truncate(num, n)
for i in range(Q.number_of_vertices()):
for p in range(d[i]):
for q in range(d[i]):
den *= todd_Q(short_t(i, q) - short_t(i, p), n)
den = truncate(den, n)
num = inclusion.inverse_image(num)
den = inclusion.inverse_image(den)
# return an element in the Chow ring
return A(num) / A(den)
[docs]
def integral(self, L, chi=None, classes=None):
r"""
Integrates the Todd class against an element of the Chow ring.
INPUT:
- ``L`` -- element of the Chow ring
- ``chi`` -- linearization of the universal bundles (default: None)
- ``classes`` -- variables to be used (default: None)
OUTPUT: the integral of :math:`td(X) \cdot L` over the moduli space
EXAMPLES:
The integral of :math:`\mathcal{O}(i)` on the projective line for some `i`::
sage: from quiver import *
sage: Q = KroneckerQuiver()
sage: X = QuiverModuliSpace(Q, (1, 1))
sage: L = X.chern_character_line_bundle((1, -1))
sage: [X.integral(L ** i) for i in range(-5, 5)]
[-4, -3, -2, -1, 0, 1, 2, 3, 4, 5]
Hilbert series for the 3-Kronecker quiver as in arXiv.2307.01711_::
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliSpace(Q, (2, 3))
sage: L = Q.canonical_stability_parameter((2, 3)) / 3
sage: O = X.chern_character_line_bundle(L)
sage: [X.integral(O ** i) for i in range(5)]
[1, 20, 148, 664, 2206]
.. _arXiv.2307.01711: https://doi.org/10.48550/arXiv.2307.01711
"""
integrand = (
(self.todd_class(chi=chi, classes=classes) * L)
.lift()
.homogeneous_components()
)
if self.dimension() in integrand.keys():
return integrand[self.dimension()] / self.point_class(
chi=chi, classes=classes
)
return 0
[docs]
class QuiverModuliStack(QuiverModuli):
[docs]
def __init__(self, Q, d, theta=None, denom=sum, condition="semistable"):
r"""
Constructor for a quiver moduli stack.
This is the quiver moduli space as a stack.
INPUT:
- ``Q`` -- quiver
- ``d`` --- dimension vector
- ``theta`` -- stability parameter (default: canonical stability parameter)
- ``denom`` -- denominator for slope stability (default: ``sum``), needs to be
effective on the simple roots
- ``condition`` -- whether to include all semistables, or only stables
(default: "semistable")
EXAMPLES:
An example::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuliStack(Q, (2, 3))
"""
QuiverModuli.__init__(self, Q, d, theta=theta, denom=denom, condition=condition)
def _repr_(self):
r""".
Give a shorthand string presentation for the quiver moduli stack
EXAMPLES:
A Kronecker moduli stack::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliStack(Q, (2, 3))
moduli stack of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
"""
if self.get_custom_name():
return self.get_custom_name()
return super()._QuiverModuli__repr_helper("moduli stack")
[docs]
def repr(self):
r"""
Give a shorthand string presentation for a quiver moduli stack.
EXAMPLES:
A Kronecker moduli spac::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: QuiverModuliStack(Q, (2, 3))
moduli stack of semistable representations, with
- Q = 3-Kronecker quiver
- d = (2, 3)
- θ = (9, -6)
"""
return self._repr_()
[docs]
def dimension(self):
r"""
Computes the dimension of the moduli stack :math:`[R^{(s)st}/G]`.
This is the dimension of a quotient stack, thus we use
.. MATH::
dim [R^{{\rm (s)st}}/G] = dim R^{{\rm (s)st}} - dim G
The dimension turns out to be :math:`-\langle d,d\rangle`
if the (semi-)stable locus is non-empty.
EXAMPLES:
The dimension of a moduli space of stable is off by one from the moduli stack
because of the generic stabilizer being 1-dimensional::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuli(Q, (2, 3))
sage: X.to_stack().dimension()
5
sage: X.to_space().dimension()
6
"""
# setup shorthand
Q, d = self._Q, self._d
if self.is_nonempty():
return -Q.euler_form(d, d)
else:
return -Infinity
[docs]
def is_smooth(self) -> bool:
r"""
Return whether the stack is smooth.
The stack is a quotient of a smooth variety, thus it is always smooth.
EXAMPLES:
Nothing interesting to see here::
sage: from quiver import *
sage: QuiverModuliSpace(KroneckerQuiver(3), (2, 3)).is_smooth()
True
"""
return True
[docs]
def motive(self):
r"""Gives an expression for the motive of the semistable moduli stack
This really lives inside an appropriate localization of :math:`K_0(Var)`,
but it only involves the Lefschetz class.
EXAMPLES:
Loop quivers::
sage: from quiver import *
sage: Q = LoopQuiver(0)
sage: X = QuiverModuliStack(Q, (2,), (0,))
sage: X.motive()
1/(L^4 - L^3 - L^2 + L)
sage: Q = LoopQuiver(1)
sage: X = QuiverModuliStack(Q, (2,), (0,))
sage: X.motive()
L^3/(L^3 - L^2 - L + 1)
The 3-Kronecker quiver::
sage: Q = GeneralizedKroneckerQuiver(3)
sage: X = QuiverModuliStack(Q, (2, 3))
sage: X.motive()
(-L^6 - L^5 - 3*L^4 - 3*L^3 - 3*L^2 - L - 1)/(L - 1)
"""
# only for semistable.
# for stable, we don't know what the motive is: it's not pure in general.
assert self._condition == "semistable"
# setup shorthand
Q, d, theta = self._Q, self._d, self._theta
d = Q._coerce_dimension_vector(d)
d = Q._coerce_dimension_vector(d)
theta = Q._coerce_vector(theta)
K = FunctionField(QQ, "L")
L = K.gen(0)
if theta == Q.zero_vector():
return L ** (-Q.tits_form(d)) / prod(
prod(1 - L ** (-nu) for nu in range(1, d[i] + 1))
for i in range(Q.number_of_vertices())
)
# start with all subdimension vectors
ds = Q.all_subdimension_vectors(d, proper=True, nonzero=True)
# only consider those of greater slope
ds = list(filter(lambda e: Q.slope(e, theta) > Q.slope(d, theta), ds))
# put zero and ``d`` back in and sort them conveniently
ds = ds + [Q.zero_vector(), d]
ds.sort(key=(lambda e: Q._deglex_key(e, b=max(d) + 1)))
# Now define a matrix T of size NxN whose entry at position (i,j) is
# L^<e-f,e>*mot(f-e) if e = I[i] is a subdimension vector of f = I[j]
# and 0 otherwise
T = matrix(K, len(ds))
for i, j in UnorderedTuples(range(len(ds)), 2):
e, f = ds[i], ds[j]
if not Q.is_subdimension_vector(e, f):
continue
T[i, j] = (
L ** (Q.euler_form(e - f, e))
* QuiverModuliStack(
Q, f - e, Q.zero_vector(), condition="semistable"
).motive()
)
# solve system of linear equations T*x = e_N
# and extract entry 0 of the solution x.
y = zero_vector(len(ds))
y[len(ds) - 1] = 1
x = T.solve_right(y)
return x[0]
[docs]
def chow_ring(self, classes=None):
r"""Returns the Chow ring of the quotient stack.
INPUT:
- ``classes`` -- variables to be used (default: None)
EXAMPLES:
The Chow ring of the stack defining the Kronecker 6-fold has as its defining
ideal the tautological ideal::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: X = QuiverModuliStack(Q, (2, 3))
sage: X.tautological_ideal() == X.chow_ring().defining_ideal()
True
"""
tautological = self.tautological_ideal(use_roots=False, classes=classes)
return QuotientRing(tautological.ring(), tautological, names=classes)