Source code for quiver.moduli

from itertools import combinations_with_replacement, product

from sage.arith.functions import lcm
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() (((2, 3),), ((1, 1), (1, 2)), ((2, 2), (0, 1)), ((2, 1), (0, 2)), ((1, 0), (1, 3)), ((1, 0), (1, 2), (0, 1)), ((1, 0), (1, 1), (0, 2)), ((2, 0), (0, 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=True): 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: True) 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() (((2, 3),), ((1, 1), (1, 2)), ((2, 2), (0, 1)), ((2, 1), (0, 2)), ((1, 0), (1, 3)), ((1, 0), (1, 2), (0, 1)), ((1, 0), (1, 1), (0, 2)), ((2, 0), (0, 3))) sage: X.all_harder_narasimhan_types(proper=True) (((1, 1), (1, 2)), ((2, 2), (0, 1)), ((2, 1), (0, 2)), ((1, 0), (1, 3)), ((1, 0), (1, 2), (0, 1)), ((1, 0), (1, 1), (0, 2)), ((2, 0), (0, 3))) 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() (((2, 3, 2),), ((1, 2, 1), (1, 1, 1)), ((1, 3, 1), (1, 0, 1)), ((2, 0, 1), (0, 3, 1)), ((2, 1, 1), (0, 2, 1)), ((2, 2, 1), (0, 1, 1)), ((2, 3, 1), (0, 0, 1)), ((0, 1, 0), (2, 2, 2)), ((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, 2, 0), (2, 1, 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, 3, 0), (2, 0, 2)), ((0, 3, 0), (2, 0, 1), (0, 0, 1)), ((1, 2, 0), (1, 1, 2)), ((1, 2, 0), (1, 0, 1), (0, 1, 1)), ((1, 2, 0), (1, 1, 1), (0, 0, 1)), ((1, 2, 0), (0, 1, 0), (1, 0, 2)), ((1, 2, 0), (0, 1, 0), (1, 0, 1), (0, 0, 1)), ((2, 3, 0), (0, 0, 2)), ((1, 1, 0), (1, 2, 2)), ((1, 1, 0), (1, 0, 1), (0, 2, 1)), ((1, 1, 0), (1, 1, 1), (0, 1, 1)), ((1, 1, 0), (1, 2, 1), (0, 0, 1)), ((1, 1, 0), (0, 1, 0), (1, 1, 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, 2, 0), (1, 0, 2)), ((1, 1, 0), (0, 2, 0), (1, 0, 1), (0, 0, 1)), ((1, 1, 0), (1, 2, 0), (0, 0, 2)), ((2, 2, 0), (0, 1, 2)), ((2, 2, 0), (0, 1, 1), (0, 0, 1)), ((2, 2, 0), (0, 1, 0), (0, 0, 2)), ((2, 1, 0), (0, 2, 2)), ((2, 1, 0), (0, 2, 1), (0, 0, 1)), ((2, 1, 0), (0, 1, 0), (0, 1, 2)), ((2, 1, 0), (0, 1, 0), (0, 1, 1), (0, 0, 1)), ((2, 1, 0), (0, 2, 0), (0, 0, 2)), ((1, 0, 0), (1, 3, 2)), ((1, 0, 0), (0, 3, 1), (1, 0, 1)), ((1, 0, 0), (1, 1, 1), (0, 2, 1)), ((1, 0, 0), (1, 2, 1), (0, 1, 1)), ((1, 0, 0), (1, 3, 1), (0, 0, 1)), ((1, 0, 0), (0, 1, 0), (1, 2, 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, 2, 0), (1, 1, 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, 3, 0), (1, 0, 2)), ((1, 0, 0), (0, 3, 0), (1, 0, 1), (0, 0, 1)), ((1, 0, 0), (1, 2, 0), (0, 1, 2)), ((1, 0, 0), (1, 2, 0), (0, 1, 1), (0, 0, 1)), ((1, 0, 0), (1, 2, 0), (0, 1, 0), (0, 0, 2)), ((1, 0, 0), (1, 1, 0), (0, 2, 2)), ((1, 0, 0), (1, 1, 0), (0, 2, 1), (0, 0, 1)), ((1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 1, 2)), ((1, 0, 0), (1, 1, 0), (0, 1, 0), (0, 1, 1), (0, 0, 1)), ((1, 0, 0), (1, 1, 0), (0, 2, 0), (0, 0, 2)), ((2, 0, 0), (0, 3, 2)), ((2, 0, 0), (0, 2, 1), (0, 1, 1)), ((2, 0, 0), (0, 3, 1), (0, 0, 1)), ((2, 0, 0), (0, 1, 0), (0, 2, 2)), ((2, 0, 0), (0, 1, 0), (0, 2, 1), (0, 0, 1)), ((2, 0, 0), (0, 2, 0), (0, 1, 2)), ((2, 0, 0), (0, 2, 0), (0, 1, 1), (0, 0, 1)), ((2, 0, 0), (0, 3, 0), (0, 0, 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: all_types = tuple(ds for ds in all_types if ds != (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] [0, 3, 4, 10, 8, 9, 12, 18] """ 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.general_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_type_weights(self, harder_narasimhan_type): r""" Returns the weights of the 1-PS lambda on the Harder-Narasimhan type. EXAMPLE: sage: from quiver import * sage: Q = GeneralizedKroneckerQuiver(3) sage: X = QuiverModuliSpace(Q, (2, 3)) sage: HN = X.all_harder_narasimhan_types(proper=True) sage: X.harder_narasimhan_type_weights(HN[0]) [3, -2] """ # setup shorthand Q, theta, denom = self._Q, self._theta, self._denom HN = harder_narasimhan_type weights = [Q.slope(HN[s], theta, denom=denom) for s in range(len(HN))] c = lcm([weight.denominator() for weight in weights]) return [x * c for x in weights]
[docs] def teleman_bound(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:`teleman_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.teleman_bound(dstar) for dstar in HN} {((1, 0), (1, 1), (0, 2)): 270, ((1, 0), (1, 2), (0, 1)): 100, ((1, 0), (1, 3)): 360, ((1, 1), (1, 2)): 15, ((2, 0), (0, 3)): 270, ((2, 1), (0, 2)): 100, ((2, 2), (0, 1)): 60} """ # setup shorthand Q = self._Q HN = harder_narasimhan_type weights = self.harder_narasimhan_type_weights(HN) return -sum( [ (weights[s] - weights[t]) * Q.euler_form(HN[s], HN[t]) for s in range(len(HN) - 1) for t in range(s + 1, len(HN)) ] )
[docs] def teleman_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.teleman_bounds() [15, 60, 100, 360, 100, 270, 270] sage: X.teleman_bounds(as_dict=True) {((1, 0), (1, 1), (0, 2)): 270, ((1, 0), (1, 2), (0, 1)): 100, ((1, 0), (1, 3)): 360, ((1, 1), (1, 2)): 15, ((2, 0), (0, 3)): 270, ((2, 1), (0, 2)): 100, ((2, 2), (0, 1)): 60} """ # this is only relevant on the unstable locus HNs = self.all_harder_narasimhan_types(proper=True) weights = [self.teleman_bound(dstar) for dstar in 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 """ weights = self.teleman_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) kweights = [self.harder_narasimhan_type_weights(hn) for hn in HNs] kweights = [kw[0] - kw[-1] for kw in kweights] return all(weights[i] > kweights[i] for i in range(len(HNs)))
[docs] def all_weights_endomorphisms_universal_bundle(self): r""" Returns the Teleman weights on the endomorphisms of the universal bundle. EXAMPLE: The 3-Kronecker quiver:: sage: from quiver import * sage: Q = GeneralizedKroneckerQuiver(3) sage: X = QuiverModuliSpace(Q, [2, 3]) sage: X.all_weights_endomorphisms_universal_bundle() {((1, 0), (1, 1), (0, 2)): [0, 15, 30, -15, 0, 15, -30, -15, 0], ((1, 0), (1, 2), (0, 1)): [0, 10, 15, -10, 0, 5, -15, -5, 0], ((1, 0), (1, 3)): [0, 45, -45, 0], ((1, 1), (1, 2)): [0, 5, -5, 0], ((2, 0), (0, 3)): [0, 15, -15, 0], ((2, 1), (0, 2)): [0, 10, -10, 0], ((2, 2), (0, 1)): [0, 15, -15, 0]} """ # setup shorthand HN = self.all_harder_narasimhan_types(proper=True) ks = {hn: self.harder_narasimhan_type_weights(hn) for hn in HN} return { hntype: [ ks[hntype][s] - ks[hntype][t] for s in range(len(hntype)) for t in range(len(hntype)) ] for hntype in HN }
[docs] def weights_endomorphisms_universal_bundle(self, i, j): r""" Returns the Teleman weights on :math:`U_{i}^{\vee} \otimes U_{j}`. EXAMPLE: The 3-Kronecker quiver:: sage: from quiver import * sage: Q = GeneralizedKroneckerQuiver(3) sage: X = QuiverModuliSpace(Q, (2, 3)) sage: X.weights_endomorphisms_universal_bundle(0,1) {((1, 0), (1, 1), (0, 2)): [-15, 0, -30, -15, -30, -15], ((1, 0), (1, 2), (0, 1)): [-10, 0, -10, 0, -15, -5], ((1, 0), (1, 3)): [-45, 0, -45, 0, -45, 0], ((1, 1), (1, 2)): [0, 5, -5, 0, -5, 0], ((2, 0), (0, 3)): [-15, -15, -15, -15, -15, -15], ((2, 1), (0, 2)): [0, 0, -10, -10, -10, -10], ((2, 2), (0, 1)): [0, 0, 0, 0, -15, -15]} sage: X.weights_endomorphisms_universal_bundle(0,0) {((1, 0), (1, 1), (0, 2)): [0, 15, -15, 0], ((1, 0), (1, 2), (0, 1)): [0, 10, -10, 0], ((1, 0), (1, 3)): [0, 45, -45, 0], ((1, 1), (1, 2)): [0, 5, -5, 0], ((2, 0), (0, 3)): [0, 0, 0, 0], ((2, 1), (0, 2)): [0, 0, 0, 0], ((2, 2), (0, 1)): [0, 0, 0, 0]} """ # setup shorthand d = self._d # check if i and j are vertices assert d[i] and d[j] HN = self.all_harder_narasimhan_types(proper=True) ks = {hn: self.harder_narasimhan_type_weights(hn) for hn in HN} return { hntype: [ ks[hntype][s] - ks[hntype][t] for s in range(len(hntype)) for l in range(hntype[s][j]) for t in range(len(hntype)) for g in range(hntype[t][i]) ] for hntype in HN }
[docs] def weights_universal_bundle(self, i, a=None): r""" Returns the weights of the 1-PS lambda on the universal bundle :math:`U_i` with the correct multiplicities. INPUT: - ``i`` -- index of the universal bundle - ``a`` -- (default: extended_gcd(d)[1]) a linearization defining :math:`U_i`. EXAMPLE: The 3-Kronecker quiver:: sage: from quiver import * sage: Q = GeneralizedKroneckerQuiver(3); d = (2, 3) sage: X = QuiverModuliSpace(Q, d) sage: X.weights_universal_bundle(0) {((1, 0), (1, 1), (0, 2)): [60, 45], ((1, 0), (1, 2), (0, 1)): [25, 15], ((1, 0), (1, 3)): [90, 45], ((1, 1), (1, 2)): [5, 0], ((2, 0), (0, 3)): [45, 45], ((2, 1), (0, 2)): [20, 20], ((2, 2), (0, 1)): [15, 15]} sage: X.weights_universal_bundle(1) {((1, 0), (1, 1), (0, 2)): [45, 30, 30], ((1, 0), (1, 2), (0, 1)): [15, 15, 10], ((1, 0), (1, 3)): [45, 45, 45], ((1, 1), (1, 2)): [5, 0, 0], ((2, 0), (0, 3)): [30, 30, 30], ((2, 1), (0, 2)): [20, 10, 10], ((2, 2), (0, 1)): [15, 15, 0]} """ # setup shorthand d = self._d # check if i is a vertex assert d[i] if a is None: a = extended_gcd(d)[1] HN = self.all_harder_narasimhan_types(proper=True) ks = {hn: self.harder_narasimhan_type_weights(hn) for hn in HN} constant_term = { hntype: sum( ks[hntype][i] * sum( a[k] * hntype[i][k] for k in range(len(hntype[i])) ) # this is a dot product for i in range(len(hntype)) ) for hntype in HN } return { hntype: [ ks[hntype][s] - constant_term[hntype] for s in range(len(hntype)) for l in range(hntype[s][i]) ] for hntype in HN }
[docs] def weights_canonical(self): r""" Returns the Teleman weight of the canonical bundle :math:`\omega` as computed in MR4352662_. This is only known if universal bundles exist, i.e., if :math:`\gcd(d) = 1`. EXAMPLE: The 3-Kronecker quiver:: sage: from quiver import * sage: Q = GeneralizedKroneckerQuiver(3); d = (2, 3) sage: X = QuiverModuliSpace(Q, d) sage: X.weights_canonical() {((1, 0), (1, 1), (0, 2)): [315], ((1, 0), (1, 2), (0, 1)): [120], ((1, 0), (1, 3)): [405], ((1, 1), (1, 2)): [15], ((2, 0), (0, 3)): [270], ((2, 1), (0, 2)): [120], ((2, 2), (0, 1)): [90]} .. _MR4352662: https://mathscinet.ams.org/mathscinet-getitem?mr=4352662 """ # setup shorthand Q, d = self._Q, self._d assert gcd(d) == 1, "universal bundles do not exist" HN = self.all_harder_narasimhan_types(proper=True) ks = {hn: self.harder_narasimhan_type_weights(hn) for hn in HN} dd = { hntype: sum(ks[hntype][i] * hntype[i] for i in range(len(hntype))) for hntype in HN } can = Q.canonical_stability_parameter(d) return { hntype: [ sum(can[i] * dd[hntype][i] for i in range(len(can))) ] # this is a dot product for hntype in HN }
""" 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 space. Abstract method, see the concrete implementations for details. .. SEEALSO:: - :meth:`QuiverModuliSpace.dimension` - :meth:`QuiverModuliStack.dimension` EXAMPLES: This is not implemented as it is ambiguous: it depends on whether we consider it as a variety or as a stack:: 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: it depends on whether we consider it as a variety or as a stack:: 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: it depends on whether we consider it as a variety or as a stack:: 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 the Picard group. 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() not in integrand.keys(): return 0 return integrand[self.dimension()] / self.point_class(chi=chi, classes=classes)
[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)
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. For internal use only. """ 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]