from sage.arith.misc import gcd
from sage.categories.cartesian_product import cartesian_product
from sage.combinat.root_system.cartan_matrix import CartanMatrix
from sage.graphs.digraph import DiGraph
from sage.matrix.constructor import matrix
from sage.matrix.special import zero_matrix
from sage.misc.cachefunc import cached_method
from sage.modules.free_module_element import vector
from sage.rings.integer_ring import ZZ
from sage.structure.element import Element
[docs]
class Quiver(Element):
r"""
A quiver is a (finite) directed multigraph. It is an important tool in the
representation theory of (finite-dimensional) algebras, because it allows one to
construct the path algebra, whose modules are equivalently described as
representations of the quiver. These in turn can be classified using moduli spaces
of quiver representations.
For an introduction to the subject one is referred to
* Harm Derksen and Jerzy Weyman: An introduction to quiver representations
* Markus Reineke: Moduli of representations of quivers
or one of the many other resources that exist.
"""
[docs]
def __init__(self, G, name=None):
r"""Constructor for a quiver.
This takes a directed graph as input. If it is not a `DiGraph` instance,
it is interpreted it as an adjacency matrix.
For other constructions, see
- :meth:`Quiver.from_digraph`
- :meth:`Quiver.from_matrix`
- :meth:`Quiver.from_string`
INPUT:
- ``G`` -- directed graph
- ``name`` -- optional name for the quiver
EXAMPLES:
The 3-Kronecker quiver from an adjacency matrix::
sage: from quiver import *
sage: Q = Quiver([[0, 3], [0, 0]]); Q
a quiver with 2 vertices and 3 arrows
"""
if isinstance(G, DiGraph):
# it is the user's responsibility to not change the graph afterwards
self.__G = G
else:
self.__G = DiGraph(matrix(G), immutable=True)
# if name is None this doesn't do anything
self.rename(name)
# for caching purposes: order along the specified order of vertices
self.__M = self.__G.adjacency_matrix(vertices=self.__G.vertices(sort=False))
[docs]
@classmethod
def from_digraph(cls, G, name=None):
r"""Construct a quiver from a `DiGraph` object.
INPUT:
- ``G`` -- directed graph as a `DiGraph` object
- ``name`` -- optional name for the quiver
OUTPUT: the quiver.
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: M = [[0, 3], [0, 0]]
sage: Quiver.from_digraph(DiGraph(matrix(M))) == Quiver.from_matrix(M)
True
"""
return cls(G, name)
[docs]
@classmethod
def from_matrix(cls, M, name=None):
r"""Construct a quiver from its adjacency matrix.
INPUT:
- ``M`` -- adjacency matrix of the quiver
- ``name`` -- optional name for the quiver
OUTPUT: the quiver.
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = Quiver.from_matrix([[0, 3], [0, 0]]); Q.adjacency_matrix()
[0 3]
[0 0]
"""
return cls(matrix(M), name)
[docs]
@classmethod
def from_string(cls, Q: str, forget_labels=True, name=None):
r"""Construct a quiver from a comma-separated list of chains like ``i-j-k-...``
You specify an arrow from ``i`` to ``j`` by writing ``i-j``.
Multiple arrows are specified by repeating the hyphen, so that ``1--2`` is the
Kronecker quiver. If you write ``i-j-k`` then you have 1 arrow from ``i`` to
``j`` and one from ``j`` to ``k``. The full quiver is specified by concatenating
(multiple) arrows by commas.
The values for a vertex can be anything, and the chosen names will be used for
the vertices in the underlying graph. Labels are cast to an integer, if
possible, and otherwise to strings.
INPUT:
- ``Q`` -- a string of the format described above giving a quiver
- ``forget_labels`` -- (default: True): whether to use labels for vertices or
to number them ``0,...,n-1``
- ``name`` -- optional name for the quiver
OUTPUT: the quiver
EXAMPLES:
The 3-Kronecker quiver defined in two different ways::
sage: from quiver import *
sage: Quiver.from_matrix([[0, 3], [0, 0]]) == Quiver.from_string("a---b")
True
A more complicated example::
sage: Q = Quiver.from_string("a--b-3,a---3,3-a")
sage: Q.adjacency_matrix()
[0 2 3]
[0 0 1]
[1 0 0]
sage: Q.vertices()
[0, 1, 2]
The actual labeling we use doesn't matter for the isomorphism type of the
quiver::
sage: from quiver import *
sage: Quiver.from_matrix([[0, 3], [0, 0]]) == Quiver.from_string("12---b")
True
However, it does influence the labels of the vertex if we choose so::
sage: Quiver.from_string("12---b", forget_labels=False).vertices()
[12, 'b']
sage: Quiver.from_string("foo---bar", forget_labels=False).vertices()
['foo', 'bar']
"""
# remove all whitespace from the string
Q = "".join(Q.split())
# determine the vertices used in the string, preserving the order
vertices = list(
dict.fromkeys(
vertex
for chain in Q.split(",")
for vertex in chain.split("-")
if vertex # this filters out "" from chained hyphens
)
)
# adjacency matrix to be built
M = zero_matrix(len(vertices))
for chain in Q.split(","):
pieces = chain.split("-")
source = vertices.index(pieces[0])
number = 1
for piece in pieces[1:]:
# if the string is empty we increase the number of arrows counter
if not piece:
number += 1
# if the string is non-empty we treat it as a label
# this means we add the appropriate number of arrows
# and make the target the source to start the process again
if piece:
target = vertices.index(piece)
M[source, target] += number
number = 1
source, target = target, None
G = DiGraph(M)
# attempt to cast vertex labels to integers, otherwise to strings
if not forget_labels:
labels = []
for vertex in vertices:
try:
labels.append(int(vertex))
except ValueError:
labels.append(str(vertex))
G.relabel(perm=labels, inplace=True)
return cls.from_digraph(G, name)
def _repr_(self) -> str:
r"""
Give a shorthand string presentation for the quiver
If a name is set, use that, if not, just give information on number of vertices
and arrows.
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = Quiver.from_string("1---2"); Q
a quiver with 2 vertices and 3 arrows
sage: Q.rename("3-Kronecker quiver"); Q
3-Kronecker quiver
Renaming and resetting the name::
sage: Q = Quiver.from_string("1---2")
sage: Q.get_custom_name()
sage: Q.rename("3-Kronecker quiver")
sage: Q.get_custom_name()
'3-Kronecker quiver'
sage: Q.reset_name()
sage: Q.get_custom_name()
sage: Q
a quiver with 2 vertices and 3 arrows
"""
if self.get_custom_name():
return self.get_custom_name()
return "a quiver with {} vertices and {} arrows".format(
self.adjacency_matrix().nrows(), sum(sum(self.adjacency_matrix()))
)
[docs]
def __str__(self) -> str:
r"""
Detailed description of the quiver
Everything you get from :meth:`Quiver.repr()` together with the adjacency
matrix.
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = Quiver.from_string("1---2"); print(Q)
a quiver with 2 vertices and 3 arrows
adjacency matrix:
[0 3]
[0 0]
sage: Q.rename("3-Kronecker quiver"); print(Q)
3-Kronecker quiver
adjacency matrix:
[0 3]
[0 0]
"""
return "{}\nadjacency matrix:\n{}".format(self.repr(), self.adjacency_matrix())
[docs]
def repr(self) -> str:
r"""
Basic description of the quiver
To override the output, one uses :meth:`Quiver.rename` from the `Element`
class. The output of :meth:`Quiver.repr` is that of
:meth:`Quiver.get_custom_name` if it is set, else it is the default specifying
the number of vertices and arrows.
OUTPUT: a basic description of the quiver
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = Quiver.from_string("1---2"); Q
a quiver with 2 vertices and 3 arrows
sage: Q.rename("3-Kronecker quiver"); Q
3-Kronecker quiver
Renaming and resetting the name::
sage: Q = Quiver.from_string("1---2")
sage: Q.get_custom_name() is None
True
sage: Q.rename("3-Kronecker quiver")
sage: Q.get_custom_name()
'3-Kronecker quiver'
sage: Q.reset_name()
sage: Q.get_custom_name() is None
True
sage: Q
a quiver with 2 vertices and 3 arrows
"""
return self._repr_()
[docs]
def str(self) -> str:
r"""
Full description of the quiver
This combines the output of :meth:`Quiver.repr` with the adjacency matrix.
OUTPUT: a complete description of the quiver
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = Quiver.from_string("1---2"); print(Q)
a quiver with 2 vertices and 3 arrows
adjacency matrix:
[0 3]
[0 0]
sage: Q.rename("3-Kronecker quiver"); print(Q)
3-Kronecker quiver
adjacency matrix:
[0 3]
[0 0]
"""
return self.__str__()
[docs]
def __eq__(self, other) -> bool:
r"""
Checks for equality of quivers.
Equality here refers to equality of adjacency matrices,
but disregarding the name of the quiver.
INPUT:
- ``other`` -- Quiver; the quiver to compare against
OUTPUT: whether the adjacency matrices are the same
EXAMPLES:
The 2-Kronecker quiver and the generalized Kronecker quiver are the same::
sage: from quiver import *
sage: KroneckerQuiver() == GeneralizedKroneckerQuiver(2)
True
"""
return self.adjacency_matrix() == other.adjacency_matrix()
def _is_vector(self, x):
r"""
Checks whether ``x`` is an element of :math:`\mathbb{Z}Q_0`
If the quiver doesn't use vertex labels we check that it has the right length.
If the quiver uses vertex labels, we check that ``x`` is a dict with the right
set of keys.
We actually do not care whether the values are in :math:`\mathbb{Z}`.
INPUT:
- ``x`` -- vector
OUTPUT: whether ``x`` can be used as a dimension vector for the quiver
EXAMPLES:
Some basic examples::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: Q._is_vector((2, 3))
True
sage: Q._is_vector((0, 0))
True
sage: Q._is_vector((-2, -2))
True
sage: Q._is_vector((1, 2, 3))
False
We allow non-integral values, because this can be useful for stability::
sage: Q._is_vector((1/2, 3))
True
An example with vertex labels::
sage: Q = Quiver.from_string("foo---bar", forget_labels=False)
sage: Q._is_vector({"foo" : 0, "bar" : 0})
True
sage: Q._is_vector({"bar" : 0, "foo" : 0})
True
sage: Q._is_vector({"baz" : 0, "ofo" : 0})
False
"""
if isinstance(x, dict):
return set(x.keys()) == set(self.vertices())
return len(x) == self.number_of_vertices()
def _is_dimension_vector(self, d):
r"""
Checks whether ``d`` is a dimension vector of the quiver
If the quiver doesn't use vertex labels we check that it has the right length
and has positive entries.
If the quiver uses vertex labels, we check that ``d`` is a dict with the right
set of keys and positive entries.
We only check for non-negativity, not for integrality.
INPUT:
- ``d`` -- dimension vector
OUTPUT: whether ``d`` can be used as a dimension vector for the quiver
EXAMPLES:
Some basic examples::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: Q._is_dimension_vector((2, 3))
True
sage: Q._is_dimension_vector((0, 0))
True
sage: Q._is_dimension_vector((-2, -2))
False
sage: Q._is_dimension_vector((1, 2, 3))
False
An example with vertex labels::
sage: Q = Quiver.from_string("foo---bar", forget_labels=False)
sage: Q._is_dimension_vector({"foo" : 2, "bar" : 3})
True
sage: Q._is_dimension_vector({"bar" : 0, "foo" : -1})
False
sage: Q._is_dimension_vector({"baz" : 0, "ofo" : 0})
False
"""
if not self._is_vector(d):
return False
if isinstance(d, dict):
return all(di >= 0 for di in d.values())
return all(di >= 0 for di in d)
def _coerce_dimension_vector(self, d):
r"""
Coerces ``d`` to be a dimension vector of the quiver
The input ``d`` must be a data structure that is indexed
by the vertices of the quiver, so most likely a dict, list, or vector.
It is coerced to a vector, see :meth:`Quiver._coerce_vector`.
As a consistency check we verify that all entries are non-negative,
raising a `ValueError` if it isn't the case.
INPUT:
- ``d``: a candidate dimension vector
OUTPUT: either a dict or vector
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q._coerce_dimension_vector((1, 2))
(1, 2)
sage: Q._coerce_dimension_vector((1, 2, 3, 4))
Traceback (most recent call last):
...
ValueError: The input is not an element of :math:`\mathbb{Z}Q_0`.
sage: Q._coerce_dimension_vector((1, -3))
Traceback (most recent call last):
...
ValueError: The input is not a dimension vector of the quiver.
"""
d = self._coerce_vector(d)
if all(di >= 0 for di in d):
return d
else:
raise ValueError("The input is not a dimension vector of the quiver.")
def _coerce_vector(self, x):
r"""
Coerces ``x`` to be a vector in :math:`\mathbb{Z}Q_0`.
The input ``x`` must be a data structure that is indexed by
the vertices of the quiver,
so most likely a dict, list, tuple, or vector.
It raises a `ValueError` if it is not a data structure of length the number
of vertices in the quiver.
INPUT:
- ``x``: a list, tuple, or dict of integers
OUTPUT: a Sage vector if ``x`` is an element of :math:`\mathbb{Z}Q_0`
EXAMPLES:
The 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q._coerce_vector((-1, 2))
(-1, 2)
sage: Q._coerce_vector((1, 2, 3, 4))
Traceback (most recent call last):
...
ValueError: The input is not an element of :math:`\mathbb{Z}Q_0`.
"""
if len(x) != self.number_of_vertices():
raise ValueError(r"The input is not an element of :math:`\mathbb{Z}Q_0`.")
if isinstance(x, list) or isinstance(x, tuple):
x = vector(ZZ, x)
elif isinstance(x, dict):
x = vector(ZZ, [x[i] for i in self.vertices()])
# so that it can be used for hashing
x.set_immutable()
return x
"""
Basic graph-theoretic properties of the quiver
"""
[docs]
def adjacency_matrix(self):
r"""
Returns the adjacency matrix of the quiver.
OUTPUT: The square matrix ``M`` whose entry ``M[i,j]`` is the number of arrows
from the vertex ``i`` to the vertex ``j``
EXAMPLES:
The adjacency matrix of a quiver construct from an adjacency matrix::
sage: from quiver import *
sage: M = matrix([[0, 3], [0, 0]])
sage: M == Quiver(M).adjacency_matrix()
True
"""
return self.__M
[docs]
def graph(self):
r"""
Return the underlying graph of the quiver
OUTPUT: the underlying quiver as a DiGraph object
EXAMPLES:
The underlying graph of the quiver from a directed graph is that graph::
sage: from quiver import *
sage: G = DiGraph(matrix([[0, 3], [0, 0]]))
sage: G == Quiver.from_digraph(G).graph()
True
"""
return self.__G
[docs]
def vertices(self):
r"""
Return the vertices of the quiver
If the quiver is created from a DiGraph or string, the vertices are labelled
using the data in the DiGraph or string, as explained in
:meth:`Quiver.from_digraph` or :meth:`Quiver.from_string`.
If the quiver is created from a matrix, the vertices are labelled from `0`
to `n-1`, where `n` is the number of rows or columns in the matrix.
OUTPUT: the vertices in the underlying graph
EXAMPLES:
Usually the vertices will be just integers::
sage: from quiver import *
sage: Quiver([[0, 3], [0, 0]]).vertices()
[0, 1]
We can have non-trivial labels for a quiver::
sage: Quiver.from_string("foo---bar", forget_labels=False).vertices()
['foo', 'bar']
"""
return self.graph().vertices(sort=False)
[docs]
def number_of_vertices(self) -> int:
r"""Returns the number of vertices
OUTPUT: the number of vertices
EXAMPLES:
There are 3 vertices in a 3-vertex quiver::
sage: from quiver import *
sage: ThreeVertexQuiver(1, 2, 4).number_of_vertices()
3
"""
return self.graph().order()
def __has_vertex_labels(self) -> bool:
r"""Check whether vertex labels are used
EXAMPLES:
With vertex labels::
sage: from quiver import *
sage: Q = KroneckerQuiver(2)
sage: Q._Quiver__has_vertex_labels()
False
With vertex labels::
sage: Q = Quiver.from_string("foo---bar", forget_labels=False)
sage: Q._Quiver__has_vertex_labels()
True
"""
return self.vertices() != list(range(self.number_of_vertices()))
[docs]
def number_of_arrows(self) -> int:
r"""Returns the number of arrows
OUTPUT: the number of arrows
EXAMPLES:
There are 7 arrows in this 3-vertex quiver::
sage: from quiver import *
sage: ThreeVertexQuiver(1, 2, 4).number_of_arrows()
7
"""
return self.graph().size()
[docs]
def arrows(self):
r"""
Return the arrows of the quiver
OUTPUT: list of the arrows in the underlying graph as tuples (source, target)
EXAMPLES:
The arrows of the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.arrows()
[(0, 1), (0, 1), (0, 1)]
Arrows respect the labeling of the vertices if one is given::
sage: Q = Quiver.from_string("foo---bar,bar-foo", forget_labels=False)
sage: Q.arrows()
[('foo', 'bar'),
('foo', 'bar'),
('foo', 'bar'),
('bar', 'foo')]
"""
return self.graph().edges(labels=False)
[docs]
def is_acyclic(self) -> bool:
r"""Returns whether the quiver is acyclic.
OUTPUT: True if the quiver is acyclic, False otherwise.
EXAMPLES:
An acyclic graph::
sage: from quiver import *
sage: KroneckerQuiver(3).is_acyclic()
True
A non-acyclic graph::
sage: GeneralizedJordanQuiver(5).is_acyclic()
False
"""
return self.graph().is_directed_acyclic()
[docs]
def is_connected(self) -> bool:
r"""Returns whether the underlying graph of the quiver is connected or not.
OUTPUT: True if the quiver is connected, False otherwise.
EXAMPLES:
The n-Kronecker quivers are connected::
sage: from quiver import *
sage: KroneckerQuiver(4).is_connected()
True
The loop quivers are connected::
sage: GeneralizedJordanQuiver(3).is_connected()
True
"""
return self.graph().is_connected()
[docs]
def is_finite_type(self) -> bool:
r"""
Returns whether the quiver is of finite type representation type.
This is the case if and only the connected components of the underlying
undirected graph are isomorphic to Dynkin diagrams.
EXAMPLES:
The generalized Kronecker quiver is finite only for :math:`m=1`::
sage: from quiver import *
sage: GeneralizedKroneckerQuiver(1).is_finite_type()
True
sage: GeneralizedKroneckerQuiver(2).is_finite_type()
False
sage: GeneralizedKroneckerQuiver(3).is_finite_type()
False
"""
return CartanMatrix(self.cartan_matrix()).is_finite()
[docs]
def is_tame_type(self) -> bool:
r"""
Returns whether the quiver is of tame type representation type.
This is the case if and only the connected components of the underlying
undirected graph are isomorphic to (extended) Dynkin diagrams, with at least one
being extended Dynkin.
EXAMPLES:
The generalized Kronecker quiver is tame only for :math:`m=2`::
sage: from quiver import *
sage: GeneralizedKroneckerQuiver(1).is_tame_type()
False
sage: GeneralizedKroneckerQuiver(2).is_tame_type()
True
sage: GeneralizedKroneckerQuiver(3).is_tame_type()
False
"""
M = CartanMatrix(self.cartan_matrix())
return M.is_affine() and not M.is_finite()
[docs]
def is_wild_type(self) -> bool:
r"""
Returns whether the quiver is of wild type representation type.
This is the case if and only the connected components of the underlying
undirected graph are not all isomorphic to (extended) Dynkin diagrams.
EXAMPLES:
The generalized Kronecker quiver is wild for all :math:`m\geq 3`::
sage: from quiver import *
sage: GeneralizedKroneckerQuiver(1).is_wild_type()
False
sage: GeneralizedKroneckerQuiver(2).is_wild_type()
False
sage: GeneralizedKroneckerQuiver(3).is_wild_type()
True
"""
return not self.is_finite_type() and not self.is_tame_type()
"""
Some graph-theoretic properties of the quiver
"""
[docs]
def in_degree(self, i):
r"""Returns the in-degree of a vertex.
The in-degree of ``i`` is the number of incoming arrows at ``i``.
The parameter ``i`` must be an element of the vertices of the underlying graph.
If constructed from a matrix or string, ``i`` can go from `0` to
`n-1` where `n` is the number of vertices in the graph.
INPUT:
- ``i`` -- a vertex of the underlying graph
OUTPUT: The in-degree of the vertex ``i``
EXAMPLES:
In the 3-Kronecker quiver the in-degree is either 0 or 3::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.in_degree(0)
0
sage: Q.in_degree(1)
3
If we specified a non-standard labeling on the vertices we must use it::
sage: Q = Quiver.from_string("a---b", forget_labels=False)
sage: Q.in_degree("a")
0
sage: Q.in_degree("b")
3
"""
return self.graph().in_degree(i)
[docs]
def out_degree(self, i):
r"""Returns the out-degree of a vertex.
The parameter ``i`` must be an element of the vertices of the underlying graph.
If constructed from a matrix or string, ``i`` can go from `0` to
`n-1` where `n` is the number of vertices in the graph.
The out-degree of ``i`` is the number of outgoing arrows at ``i``.
INPUT:
- ``i`` -- a vertex of the underlying graph
OUTPUT: The out-degree of the vertex ``i``
EXAMPLES:
In the 3-Kronecker quiver the out-degree is either 3 or 0::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.out_degree(0)
3
sage: Q.out_degree(1)
0
If we specified a non-standard labeling on the vertices we must use it::
sage: Q = Quiver.from_string("a---b", forget_labels=False)
sage: Q.out_degree("a")
3
sage: Q.out_degree("b")
0
"""
return self.graph().out_degree(i)
[docs]
def is_source(self, i) -> bool:
"""Checks if ``i`` is a source of the quiver
The vertex ``i`` is a source if there are no incoming arrows at ``i``.
INPUT:
- ``i`` -- a vertex of the quiver
OUTPUT: whether ``i`` is a source of the quiver
EXAMPLES:
The 3-Kronecker quiver has one source::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.is_source(0)
True
sage: Q.is_source(1)
False
If we specified a non-standard labeling on the vertices we must use it::
sage: Q = Quiver.from_string("a---b", forget_labels=False)
sage: Q.is_source("a")
True
sage: Q.is_source("b")
False
"""
return self.in_degree(i) == 0
[docs]
def is_sink(self, i) -> bool:
"""Checks if ``i`` is a sink of the quiver
The vertex ``i`` is a sink if there are no outgoing arrows out of ``i``.
INPUT:
- ``i`` -- a vertex of the quiver
OUTPUT: whether ``i`` is a sink of the quiver
EXAMPLES
The 3-Kronecker quiver has one sink::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.is_sink(0)
False
sage: Q.is_sink(1)
True
If we specified a non-standard labeling on the vertices we must use it::
sage: Q = Quiver.from_string("a---b", forget_labels=False)
sage: Q.is_sink("a")
False
sage: Q.is_sink("b")
True
"""
return self.out_degree(i) == 0
[docs]
def sources(self):
r"""Return the vertices which are sources in the quiver
OUTPUT: the list of vertices without incoming edges
EXAMPLES:
The 3-Kronecker quiver has one source::
sage: from quiver import *
sage: GeneralizedKroneckerQuiver(3).sources()
[0]
It is possible that a quiver has no sources::
sage: JordanQuiver().sources()
[]
"""
return list(filter(lambda i: self.is_source(i), self.vertices()))
[docs]
def sinks(self):
r"""Return the vertices which are sinks in the quiver
OUTPUT: the list of vertices without incoming edges
EXAMPLES:
The 3-Kronecker quiver has one source::
sage: from quiver import *
sage: GeneralizedKroneckerQuiver(3).sources()
[0]
It is possible that a quiver has no sinks::
sage: JordanQuiver().sinks()
[]
"""
return list(filter(lambda i: self.is_sink(i), self.vertices()))
"""
Basic representation-theoretical properties of the quiver
"""
[docs]
def euler_matrix(self):
r"""Returns the Euler matrix of the quiver
This is the matrix representing the Euler form, defined by
.. MATH::
\langle\mathbf{d},\mathbf{e}\rangle=
\sum_{i\in Q_0}d_i e_i-\sum_{\alpha\in Q_1}d_{s(\alpha)}e_{t(\alpha)}
In the basis given by the vertices, it can be written as the difference
of the identity matrix and the adjacency matrix.
OUTPUT: the Euler matrix of the quiver
EXAMPLES:
The Kronecker 3-quiver::
sage: from quiver import *
sage: GeneralizedKroneckerQuiver(3).euler_matrix()
[ 1 -3]
[ 0 1]
It uses the basis of the vertices, so it agrees with this alternative
definition::
sage: Quiver.from_string("foo---bar", forget_labels=False).euler_matrix()
[ 1 -3]
[ 0 1]
"""
return matrix.identity(self.number_of_vertices()) - self.adjacency_matrix()
[docs]
def cartan_matrix(self):
r"""Returns the Cartan matrix of the quiver
This is the matrix representing the symmetrization of the Euler form,
see :meth:`Quiver.euler_matrix`
OUTPUT: the Cartan matrix of the quiver
EXAMPLES:
The Kronecker 3-quiver::
sage: from quiver import *
sage: GeneralizedKroneckerQuiver(3).cartan_matrix()
[ 2 -3]
[-3 2]
"""
return self.euler_matrix() + self.euler_matrix().transpose()
"""
Constructing new quivers out of old
"""
[docs]
def opposite_quiver(self):
r"""
Returns the opposite quiver
The opposite quiver is the quiver with all arrows reversed.
Its adjacency matrix is given by the transpose of the adjacency matrix.
OUTPUT: the opposite quiver
EXAMPLES:
The opposite of the 3-Kronecker quiver::
sage: from quiver import *
sage: print(GeneralizedKroneckerQuiver(3).opposite_quiver())
opposite of 3-Kronecker quiver
adjacency matrix:
[0 0]
[3 0]
It preserves the labelling of the vertices::
sage: Q = Quiver.from_string("foo---bar", forget_labels=False)
sage: Qopp = Q.opposite_quiver()
sage: Qopp.vertices()
['foo', 'bar']
sage: Qopp.adjacency_matrix()
[0 0]
[3 0]
"""
name = None
if self.get_custom_name():
name = "opposite of " + self.get_custom_name()
return Quiver.from_digraph(self.graph().reverse(), name)
[docs]
def doubled_quiver(self):
r"""
Returns the doubled quiver
The double of a quiver is the quiver where for each arrow
we add an arrow in the opposite direction.
Its adjacency matrix is the sum of the adjacency matrix
of the original quiver and its transpose.
OUTPUT: the doubled quiver
EXAMPLES:
The double of the 3-Kronecker quiver::
sage: from quiver import *
sage: print(GeneralizedKroneckerQuiver(3).doubled_quiver())
double of 3-Kronecker quiver
adjacency matrix:
[0 3]
[3 0]
It preserves the labelling of the vertices::
sage: Q = Quiver.from_string("foo---bar", forget_labels=False)
sage: Qbar = Q.doubled_quiver()
sage: Qbar.vertices()
['foo', 'bar']
sage: Qbar.adjacency_matrix()
[0 3]
[3 0]
"""
G = DiGraph(self.graph())
G.add_edges(self.opposite_quiver().graph().edges())
name = None
if self.get_custom_name():
name = "double of " + self.get_custom_name()
return Quiver.from_digraph(G, name)
[docs]
def framed_quiver(self, framing, vertex="-oo"):
r"""
Returns the framed quiver with framing vector ``framing``
The optional parameter ``vertex`` determines the name of the framing vertex,
which defaults to `-oo`.
The framed quiver has one additional vertex, and :math:`f_i` many arrows from
the framing vertex to :math:`i`, for every :math:`i\in Q_0`.
INPUT:
- ``framing`` -- list of non-negative integers saying how many arrows from the
framed vertex to ``i``
- ``vertex`` (default: "-oo") -- name of the framing vertex
OUTPUT: the framed quiver
EXAMPLES:
Framing the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3).framed_quiver([1, 0])
sage: print(Q)
framing of 3-Kronecker quiver
adjacency matrix:
[0 1 0]
[0 0 3]
[0 0 0]
sage: Q.vertices()
['-oo', 0, 1]
sage: Q = GeneralizedKroneckerQuiver(3).framed_quiver([2, 2], vertex="a")
sage: print(Q)
framing of 3-Kronecker quiver
adjacency matrix:
[0 2 2]
[0 0 3]
[0 0 0]
sage: Q.vertices()
['a', 0, 1]
If you frame twice it will have to use a different vertex label::
sage: Q = GeneralizedKroneckerQuiver(3).framed_quiver([2, 2])
sage: Q.framed_quiver([1, 1, 1]).vertices()
Traceback (most recent call last):
...
ValueError: -oo is already a vertex
"""
if vertex in self.vertices():
raise ValueError("{} is already a vertex".format(vertex))
framing = self._coerce_dimension_vector(framing)
G = DiGraph(self.graph())
# adding framing the vertex (as the _first_ vertex by default)
G.add_vertex(vertex)
# adding the arrows according to the framing vector
for i, v in enumerate(self.vertices()):
G.add_edges([(vertex, v)] * framing[i])
name = None
if self.get_custom_name():
name = "framing of " + self.get_custom_name()
return Quiver.from_digraph(G, name)
[docs]
def coframed_quiver(self, coframing, vertex="+oo"):
r"""
Returns the coframed quiver with coframing vector ``coframing``
The optional parameter ``vertex`` determines the name of the coframing vertex,
which defaults to `+oo`.
The coframed quiver has one additional vertex, and :math:`f_i` many arrows from
the vertex `i` to the coframed vertex, for every :math:`i\in Q_0`.
INPUT:
- ``coframing`` -- list of non-negative integers saying how many arrows go from
the framed vertex to `i`
- ``vertex`` (default: None) -- name of the framing vertex
OUTPUT: the framed quiver
EXAMPLES:
Coframing the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3).coframed_quiver([1, 0])
sage: print(Q)
coframing of 3-Kronecker quiver
adjacency matrix:
[0 3 1]
[0 0 0]
[0 0 0]
sage: Q.vertices()
[0, 1, '+oo']
sage: Q = GeneralizedKroneckerQuiver(3).coframed_quiver([2, 2], vertex="a")
sage: print(Q)
coframing of 3-Kronecker quiver
adjacency matrix:
[0 3 2]
[0 0 2]
[0 0 0]
sage: Q.vertices()
[0, 1, 'a']
If you coframe twice it will have to use a different vertex label::
sage: Q = GeneralizedKroneckerQuiver(3).coframed_quiver([2, 2])
sage: Q.coframed_quiver([1, 1, 1]).vertices()
Traceback (most recent call last):
...
ValueError: +oo is already a vertex
"""
if vertex in self.vertices():
raise ValueError("{} is already a vertex".format(vertex))
coframing = self._coerce_dimension_vector(coframing)
# build the graph from the ground up
G = DiGraph([self.vertices() + [vertex], []], multiedges=True, loops=True)
# there doesn't seem to be a way to save the order of vertices otherwise
# so make sure the coframing vertex appears last
permutation = dict(zip(G.vertices(), self.vertices() + [vertex]))
G.relabel(perm=permutation, inplace=True)
# adding the existing arrows
G.add_edges(self.graph().edges())
# adding the arrows according to the framing vector
for i, v in enumerate(self.vertices()):
G.add_edges([(v, vertex)] * coframing[i])
name = None
if self.get_custom_name():
name = "coframing of " + self.get_custom_name()
return Quiver.from_digraph(G, name)
[docs]
def full_subquiver(self, vertices):
r"""Returns the full subquiver supported on the given set of vertices
INPUT:
- ``vertices``: list of vertices for the subquiver
OUTPUT: the full subquiver on the specified vertices
EXAMPLES:
Some basic examples::
sage: from quiver import *
sage: Q = ThreeVertexQuiver(2, 3, 4)
sage: print(Q.full_subquiver([0, 1]))
full subquiver of an acyclic 3-vertex quiver of type (2, 3, 4)
adjacency matrix:
[0 2]
[0 0]
sage: print(Q.full_subquiver([0, 2]))
full subquiver of an acyclic 3-vertex quiver of type (2, 3, 4)
adjacency matrix:
[0 3]
[0 0]
If we specified a non-standard labeling on the vertices we must use it::
sage: Q = Quiver.from_string("a--b----c,a---c", forget_labels=False)
sage: Q == ThreeVertexQuiver(2, 3, 4)
True
sage: print(Q.full_subquiver(["a", "b"]))
a quiver with 2 vertices and 2 arrows
adjacency matrix:
[0 2]
[0 0]
sage: print(Q.full_subquiver(["a", "c"]))
a quiver with 2 vertices and 3 arrows
adjacency matrix:
[0 3]
[0 0]
"""
name = None
if self.get_custom_name():
name = "full subquiver of " + self.get_custom_name()
return Quiver.from_digraph(self.graph().subgraph(vertices=vertices), name)
"""
Dimension vectors and roots
"""
[docs]
def zero_vector(self):
r"""
Returns the zero dimension vector.
The output is adapted to the vertices.
OUTPUT: the zero dimension vector
EXAMPLES:
Usually it is an actual vector::
sage: from quiver import *
sage: KroneckerQuiver(3).zero_vector()
(0, 0)
sage: type(KroneckerQuiver(3).zero_vector())
<class 'sage.modules.vector_integer_dense.Vector_integer_dense'>
But if the quiver has custom vertex labels it is a dict::
sage: Q = Quiver.from_string("a--b----c,a---c", forget_labels=False)
sage: Q.zero_vector()
{'a': 0, 'b': 0, 'c': 0}
"""
return self.__zero_vector()
@cached_method
def __zero_vector(self):
r"""The cacheable implementation of :meth:`Quiver.zero_vector`
EXAMPLES:
The zero vector of the 3-Kronecker quiver::
sage: from quiver import *
sage: KroneckerQuiver(3).zero_vector()
(0, 0)
If we specified a non-standard labeling on the vertices it is used::
sage: Q = Quiver.from_string("a--b----c,a---c", forget_labels=False)
sage: Q._Quiver__zero_vector()
{'a': 0, 'b': 0, 'c': 0}
"""
if self.__has_vertex_labels():
return {i: 0 for i in self.vertices()}
return vector([0] * self.number_of_vertices(), immutable=True)
[docs]
def thin_dimension_vector(self):
r"""
Returns the thin dimension vector, i.e., all ones
The output is adapted to the vertices.
OUTPUT: the thin dimension vector
EXAMPLES:
Usually it is an actual vector::
sage: from quiver import *
sage: KroneckerQuiver(3).thin_dimension_vector()
(1, 1)
sage: type(KroneckerQuiver(3).thin_dimension_vector())
<class 'sage.modules.vector_integer_dense.Vector_integer_dense'>
But if the quiver has custom vertex labels it is a dict::
sage: Q = Quiver.from_string("a--b----c,a---c", forget_labels=False)
sage: Q.thin_dimension_vector()
{'a': 1, 'b': 1, 'c': 1}
"""
return self.__thin_dimension_vector()
@cached_method
def __thin_dimension_vector(self):
r"""The cacheable implementation of :meth:`Quiver.thin_dimension_vector`
EXAMPLES:
The thin dimension vector of the 3-Kronecker quiver::
sage: from quiver import *
sage: KroneckerQuiver(3).thin_dimension_vector()
(1, 1)
If we specified a non-standard labeling on the vertices it is used::
sage: Q = Quiver.from_string("a--b----c,a---c", forget_labels=False)
sage: Q._Quiver__thin_dimension_vector()
{'a': 1, 'b': 1, 'c': 1}
"""
if self.__has_vertex_labels():
return {i: 1 for i in self.vertices()}
return vector([1] * self.number_of_vertices(), immutable=True)
[docs]
def simple_root(self, i):
r"""
Returns the simple root at the vertex ``i``
The output is adapted to the vertices.
OUTPUT: the simple root at the vertex ``i``
EXAMPLES:
Usually it is an actual vector::
sage: from quiver import *
sage: KroneckerQuiver(3).simple_root(1)
(0, 1)
sage: type(KroneckerQuiver(3).simple_root(1))
<class 'sage.modules.vector_integer_dense.Vector_integer_dense'>
But if the quiver has custom vertex labels it is a dict::
sage: Q = Quiver.from_string("a--b----c,a---c", forget_labels=False)
sage: Q.simple_root("b")
{'a': 0, 'b': 1, 'c': 0}
"""
return self.__simple_root(i)
@cached_method
def __simple_root(self, i):
r"""The cacheable implementation of :meth:`Quiver.simple_root`
EXAMPLES:
The simple root at the source of the 3-Kronecker quiver::
sage: from quiver import *
sage: KroneckerQuiver(3).simple_root(0)
(1, 0)
If we specified a non-standard labeling on the vertices it is used::
sage: Q = Quiver.from_string("a--b----c,a---c", forget_labels=False)
sage: Q._Quiver__simple_root("a")
{'a': 1, 'b': 0, 'c': 0}
"""
if self.__has_vertex_labels():
root = {i: 0 for i in self.vertices()}
root[i] = 1
return root
root = vector([0] * self.number_of_vertices())
root[i] = 1
root.set_immutable()
return root
[docs]
def is_root(self, x) -> bool:
r"""Checks whether ``x`` is a root of the underlying diagram of the quiver.
A root is a non-zero vector `x` in :math:`\mathbb{Z}Q_0` such that
the Tits form of `x` is at most 1.
INPUT:
- ``x``: integer vector
OUTPUT: whether ``x`` is a root
EXAMPLES:
Some roots and non-roots for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: Q.is_root((2, 3))
True
sage: Q.is_root(Q.zero_vector())
False
sage: Q.is_root((4, 1))
False
"""
x = self._coerce_vector(x)
return any(x) and self.tits_form(x) <= 1
[docs]
def is_real_root(self, x) -> bool:
r"""Checks whether ``x`` is a real root of the underlying diagram of the quiver.
A root is called real if its Tits form equals 1.
INPUT:
- ``x``: integer vector
OUTPUT: whether ``x`` is a real root
EXAMPLES:
Some real and non-real for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: Q.is_real_root((2, 3))
False
sage: Q.is_real_root(Q.zero_vector())
False
sage: Q.is_real_root((3, 1))
True
"""
x = self._coerce_vector(x)
return self.tits_form(x) == 1
[docs]
def is_imaginary_root(self, x) -> bool:
r"""Checks whether ``x`` is a imaginary root of the quiver.
A root is called imaginary if its Tits form is non-positive.
INPUT:
- ``x``: integer vector
OUTPUT: whether ``x`` is an imaginary root
EXAMPLES:
Some imaginary roots and non imaginary roots for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: Q.is_imaginary_root((2, 3))
True
sage: Q.is_imaginary_root(Q.zero_vector())
False
sage: Q.is_imaginary_root((4, 1))
False
"""
x = self._coerce_vector(x)
return any(x) and self.tits_form(x) <= 0
[docs]
def is_schur_root(self, d) -> bool:
r"""Checks if ``d`` is a Schur root.
INPUT:
- ``d``: dimension vector
OUTPUT: whether ``d`` is an imaginary root
A Schur root is a dimension vector which admits a Schurian representation,
i.e., a representation whose endomorphism ring is the field itself.
It is necessarily indecomposable.
By MR1162487_ :math:`{\bf d}` is a Schur root if and only if it admits a stable
representation for the canonical stability parameter.
.. _MR1162487: https://mathscinet.ams.org/mathscinet/relay-station?mr=1162487
EXAMPLES:
The dimension vector `(2, 3)` is Schurian for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.is_schur_root([2, 3])
True
Examples from Derksen--Weyman's book (Example 11.1.4)::
sage: from quiver import *
sage: Q = ThreeVertexQuiver(1, 1, 1)
sage: Q.is_schur_root((1, 1, 2))
True
sage: Q.is_schur_root((1, 2, 1))
False
sage: Q.is_schur_root((1, 1, 1))
True
sage: Q.is_schur_root((2, 2, 2))
False
"""
d = self._coerce_dimension_vector(d)
theta = self.canonical_stability_parameter(d)
return self.has_stable_representation(d, theta)
[docs]
def slope(self, d, theta=None, denom=sum):
r"""
Returns the slope of ``d`` with respect to ``theta``
The slope is defined as the value of ``theta(d)`` divided by the total dimension
of `d` ``sum(d)``. It is possible to vary the denominator, to use a function
more general than the sum.
INPUT:
- ``d`` -- dimension vector
- ``theta`` -- (default: canonical stability parameter) stability parameter
- ``denom`` -- (default: sum) the denominator function
OUTPUT: the slope of ``d`` with respect to ``theta`` and optional ``denom``
EXAMPLES:
Some slopes for the Kronecker quiver, first for the canonical stability
parameter, then for some other::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: d = (2, 3)
sage: Q.slope(d, (9, -6))
0
sage: Q.slope(d)
0
sage: Q.slope(d, (2, -2))
-2/5
We can use for instance a constant denominator::
sage: constant = lambda di: 1
sage: Q.slope(d, Q.canonical_stability_parameter(d), denom=constant)
0
The only dependence on the quiver is the set of vertices, so if we don't
use vertex labels, the choice of quiver doesn't matter::
sage: d, theta = (2, 3), (9, -6)
sage: KroneckerQuiver(3).slope(d, theta)
0
"""
d = self._coerce_dimension_vector(d)
assert denom(d) > 0, "denominator needs to be strictly positive on ``d``"
if theta is None:
theta = self.canonical_stability_parameter(d)
theta = self._coerce_vector(theta)
return (theta * d) / denom(d)
[docs]
def is_subdimension_vector(self, e, d):
r"""
Determine whether ``e`` is a subdimension vector of ``d``
INPUT:
-- ``e`` -- dimension vector
-- ``d`` -- dimension vector
OUTPUT: whether ``e`` is a subdimension vector of ``d``
EXAMPLES:
Some basic examples::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: Q.is_subdimension_vector((1, 2), (2, 3))
True
sage: Q.is_subdimension_vector((2, 3), (2, 3))
True
sage: Q.is_subdimension_vector((6, 6), (2, 3))
False
We can also work with vertex labels::
sage: Q = Quiver.from_string("a--b----c,a---c", forget_labels=False)
sage: d = {"a" : 3, "b" : 3, "c" : 3}
sage: e = {"a" : 1, "b" : 2, "c" : 3}
sage: Q.is_subdimension_vector(e, d)
True
sage: Q.is_subdimension_vector(d, e)
False
"""
d = self._coerce_dimension_vector(d)
e = self._coerce_dimension_vector(e)
return all(ei <= di for (ei, di) in zip(e, d))
def _deglex_key(self, e, b=None) -> int:
r"""
An integer representation of a dimension vector
This is the base-b expansion of a dimension vector.
This is a function which satisfies
e <_{deglex} d iff deglex_key(e) < deglex_key(d),
provided that b >> 0.
For b >> 0 the deglex order is a _total_ order which extends the usual
entry-wise partial order on dimension vectors.
INPUT:
- ``e`` -- dimension vector
- ``b`` -- the "base" of the key (default: `max(e)+1`)
OUTPUT: the base-`b` expansion of the dimension vector
EXAMPLES:
If we let `b` be the largest entry plus one we get a good key, at least for
subdimension vectors of the original one::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: d = (2, 3)
sage: Q._deglex_key(d, max(d) + 1)
91
sage: d = (3, 3)
sage: Q._deglex_key(d)
111
"""
e = self._coerce_dimension_vector(e)
if b is None:
b = max(e) + 1
n = self.number_of_vertices()
return (
sum(ei * b ** (n - i - 1) for (i, ei) in enumerate(e))
+ sum(self._coerce_dimension_vector(e)) * b**n
)
[docs]
def all_subdimension_vectors(
self, d, proper=False, nonzero=False, forget_labels=False
):
r"""
Returns the list of all subdimension vectors of ``d``.
INPUT:
- ``d`` -- dimension vector
- ``proper`` (default: False) -- whether to exclude ``d``
- ``nonzero`` (default: False) -- whether to exclude the zero vector
- ``forget_labels`` (default: False) -- whether to forget the vertex labels
OUTPUT: all subdimension vectors of ``d`` (maybe excluding zero and/or ``d``)
EXAMPLES:
The usual use cases::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: Q.all_subdimension_vectors((2, 3))
[(0, 0),
(0, 1),
(0, 2),
(0, 3),
(1, 0),
(1, 1),
(1, 2),
(1, 3),
(2, 0),
(2, 1),
(2, 2),
(2, 3)]
sage: Q.all_subdimension_vectors((2, 3), proper=True)
[(0, 0),
(0, 1),
(0, 2),
(0, 3),
(1, 0),
(1, 1),
(1, 2),
(1, 3),
(2, 0),
(2, 1),
(2, 2)]
sage: Q.all_subdimension_vectors((2, 3), nonzero=True)
[(0, 1),
(0, 2),
(0, 3),
(1, 0),
(1, 1),
(1, 2),
(1, 3),
(2, 0),
(2, 1),
(2, 2),
(2, 3)]
sage: Q.all_subdimension_vectors((2, 3), proper=True, nonzero=True)
[(0, 1),
(0, 2),
(0, 3),
(1, 0),
(1, 1),
(1, 2),
(1, 3),
(2, 0),
(2, 1),
(2, 2)]
Some exceptional cases::
sage: Q.all_subdimension_vectors(Q.zero_vector())
[(0, 0)]
sage: Q.all_subdimension_vectors(Q.zero_vector(), proper=True)
[]
If we work with labeled vertices, then we get a list of dicts::
sage: Q = Quiver.from_string("a---b", forget_labels=False)
sage: Q.all_subdimension_vectors((1, 2))
[{'a': 0, 'b': 0},
{'a': 0, 'b': 1},
{'a': 0, 'b': 2},
{'a': 1, 'b': 0},
{'a': 1, 'b': 1},
{'a': 1, 'b': 2}]
sage: Q.all_subdimension_vectors((1, 2), forget_labels=True)
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]
"""
assert self._is_dimension_vector(d), "``d`` needs to be a dimension vector"
# if zero dimension vector we deal with it separately
if sum(self._coerce_dimension_vector(d)) == 0:
if proper or nonzero:
return []
return [d]
vectors = list(cartesian_product([range(di + 1) for di in d]))
if proper:
vectors = vectors[:-1]
if nonzero:
vectors = vectors[1:]
if self.__has_vertex_labels() and not forget_labels:
return list(map(lambda e: dict(zip(self.vertices(), e)), vectors))
return list(map(vector, vectors))
[docs]
def is_theta_coprime(self, d, theta=None) -> bool:
r"""Checks if ``d`` is ``theta``-coprime.
A dimension vector `d` is :math:`\theta`-coprime if
:math:`\mu_{\theta}(e)\neq \mu_{\theta}(e)`
for all proper non-zero subdimension vectors e of d.
The default value for ``theta`` is the canonical stability parameter,
see :meth:`canonical_stability_parameter`.
INPUT:
- ``d`` -- dimension vector
- ``theta`` -- (default: canonical stability paramter) stability parameter
EXAMPLES:
Examples of coprimality::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: d = (2, 3)
sage: Q.is_theta_coprime(d, Q.canonical_stability_parameter(d))
True
sage: Q.is_theta_coprime(d)
True
sage: Q.is_theta_coprime((3, 3), (1, -1))
False
"""
if theta is None:
theta = self.canonical_stability_parameter(d)
assert self._is_dimension_vector(d), "``d`` needs to be a dimension vector"
assert self._is_vector(theta), "`theta` needs to be a stability parameter"
vectors = self.all_subdimension_vectors(d, proper=True, nonzero=True)
return all(self.slope(d, theta) != self.slope(e, theta) for e in vectors)
[docs]
def is_indivisible(self, d) -> bool:
"""
Checks if the gcd of all the entries of ``d`` is 1
INPUT:
-- ``d`` -- dimension vector
OUTPUT: whether the dimension vector is indivisible
EXAMPLES:
Two examples with the Kronecker quiver::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: Q.is_indivisible((2, 3))
True
sage: Q.is_indivisible((2, 2))
False
"""
return gcd(self._coerce_dimension_vector(d)) == 1
[docs]
def support(self, d):
r"""Returns the support of the dimension vector.
INPUT:
- ``d``: dimension vector
OUTPUT: subset of vertices in the underlying graph in the support
The support is the set :math:`\{ i \in Q_0 \mid d_i > 0 \}`.
EXAMPLES:
The support is the set of vertices for which the value of the dimension
vector is nonzero::
sage: from quiver import *
sage: Q = ThreeVertexQuiver(2, 0, 4)
sage: d = (1, 1, 1)
sage: Q.support(d)
[0, 1, 2]
sage: d = (1, 0, 1)
sage: Q.support(d)
[0, 2]
It takes into account vertex labels::
sage: Q = Quiver.from_string("a--b----c,a---c", forget_labels=False)
sage: d = {"a": 2, "b": 3, "c": 0}
sage: Q.support(d)
['a', 'b']
"""
assert self._is_dimension_vector(d), "``d`` needs to be a dimension vector"
return [i for i in self.vertices() if d[i] > 0]
[docs]
def in_fundamental_domain(self, d, depth=0):
r"""Checks if a dimension vector is in the fundamental domain.
The fundamental domain of :math:`Q` is the set of dimension vectors :math:`d`
such that
- :math:`\operatorname{supp}(\mathbf{d})` is connected
- :math:`\langle d,e_i\rangle + \langle e_i,d\rangle\leq 0` for every simple
root
Every :math:`d` in the fundamental domain is an imaginary root and the set of
imaginary roots is the Weyl group saturation of the fundamental domain.
If :math:`d` is in the fundamental domain then it is Schurian and a general
representation of dimension vector :math:`d` is stable for the canonical
stability parameter.
The optional parameter ``depth`` allows to make the inequality stricter.
INPUT:
- ``d``: dimension vector
- ``depth`` (default: 0) -- how deep the vector should be in the domain
OUTPUT: whether ``d`` is in the (interior of) the fundamental domain
EXAMPLES:
The fundamental domain of the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.in_fundamental_domain((1, 1))
True
sage: Q.in_fundamental_domain((1, 2))
False
sage: Q.in_fundamental_domain((2, 3))
True
The same calculation now with vertex labels::
sage: Q = Quiver.from_string("a---b", forget_labels=False)
sage: Q.in_fundamental_domain({"a" : 1, "b" : 1})
True
sage: Q.in_fundamental_domain({"a" : 1, "b" : 2})
False
sage: Q.in_fundamental_domain({"a" : 2, "b" : 3})
True
We test for dimension vectors in the strict interior, where the depth is
equal to 1::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.in_fundamental_domain((1, 1), depth=1)
True
sage: Q.in_fundamental_domain((2, 3), depth=1)
False
"""
assert self._is_dimension_vector(d), "``d`` needs to be a dimension vector"
# check if `\langle d,e_i\rangle + \langle e_i,d\rangle \leq 0`
# for all vertices `i\in Q_0`
inequality = all(
self.symmetrized_euler_form(d, self.simple_root(i)) <= -depth
for i in self.vertices()
)
# check if the support is connected
connected = self.full_subquiver(self.support(d)).is_connected()
return inequality and connected
[docs]
def division_order(self, d, e):
r"""
Checks if :math:`d\ll e`
This means that
- :math:`d_i \leq e_i` for every source `i`,
- :math:`d_j \geq e_j` for every sink `j`, and
- :math:`d_k = e_k` for every vertex `k` which is neither a source nor a sink.
This is used when dealing with Chow rings of quiver moduli, see also
:meth:`QuiverModuli.chow_ring` and
:meth:`QuiverModuli._all_minimal_forbidden_subdimension_vectors`.
EXAMPLES:
The division order on some dimension vectors for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: d = (1, 1)
sage: e = (2, 1)
sage: f = (2, 2)
sage: Q.division_order(d, e)
True
sage: Q.division_order(e, d)
False
sage: Q.division_order(d, f)
False
sage: Q.division_order(f, d)
False
sage: Q.division_order(e, f)
False
sage: Q.division_order(f, e)
True
The division order on some dimension vectors for a 3-vertex quiver::
sage: Q = ThreeVertexQuiver(2, 2, 2)
sage: d = (1, 1, 1)
sage: e = (1, 2, 1)
sage: Q.division_order(d, e)
False
sage: Q.division_order(e, d)
False
"""
d = self._coerce_dimension_vector(d)
e = self._coerce_dimension_vector(e)
return (
all(d[i] <= e[i] for i in self.sources())
and all(d[i] >= e[i] for i in self.sinks())
and all(
d[i] == e[i]
for i in self.vertices()
if i not in self.sources() and i not in self.sinks()
)
)
"""
Generic subdimension vectors and generic Hom and Ext
"""
[docs]
def is_generic_subdimension_vector(self, e, d) -> bool:
r"""Checks if e is a generic subdimension vector of d.
INPUT:
- ``e``: dimension vector for the subrepresentation
- ``d``: dimension vector for the ambient representation
OUTPUT: whether e is a generic subdimension vector of d
A dimension vector `e` is a generic subdimension vector of `d`
if a generic representation of dimension vector `d` possesses
a subrepresentation of dimension vector `e`.
By MR1162487_ `e` is a generic subdimension vector of `d` if and only if `e` is
a subdimension vector of `d` and :math:`\langle f,d-e\rangle` is non-negative
for all generic subdimension vectors `f` of `e`.
.. _MR1162487: https://mathscinet.ams.org/mathscinet/relay-station?mr=1162487
EXAMPLES:
Some examples on loop quivers::
sage: from quiver import *
sage: Q = LoopQuiver(1)
sage: ds = [vector([i]) for i in range(3)]
sage: for (e, d) in cartesian_product([ds, ds]):
....: if not Q.is_subdimension_vector(e, d): continue
....: print("{} is generic subdimension vector of {}: {}".format(
....: e, d, Q.is_generic_subdimension_vector(e,d))
....: )
(0) is generic subdimension vector of (0): True
(0) is generic subdimension vector of (1): True
(0) is generic subdimension vector of (2): True
(1) is generic subdimension vector of (1): True
(1) is generic subdimension vector of (2): True
(2) is generic subdimension vector of (2): True
sage: Q = LoopQuiver(2)
sage: for (e, d) in cartesian_product([ds]*2):
....: if not Q.is_subdimension_vector(e, d): continue
....: print("{} is generic subdimension vector of {}: {}".format(
....: e, d, Q.is_generic_subdimension_vector(e,d))
....: )
(0) is generic subdimension vector of (0): True
(0) is generic subdimension vector of (1): True
(0) is generic subdimension vector of (2): True
(1) is generic subdimension vector of (1): True
(1) is generic subdimension vector of (2): False
(2) is generic subdimension vector of (2): True
Some examples on generalized Kronecker quivers::
sage: Q = GeneralizedKroneckerQuiver(1)
sage: ds = Tuples(range(3), 2)
sage: for (e, d) in cartesian_product([ds]*2):
....: if not Q.is_subdimension_vector(e, d): continue
....: print("{} is generic subdimension vector of {}: {}".format(
....: e, d, Q.is_generic_subdimension_vector(e,d))
....: )
(0, 0) is generic subdimension vector of (0, 0): True
(0, 0) is generic subdimension vector of (1, 0): True
(0, 0) is generic subdimension vector of (2, 0): True
(0, 0) is generic subdimension vector of (0, 1): True
(0, 0) is generic subdimension vector of (1, 1): True
(0, 0) is generic subdimension vector of (2, 1): True
(0, 0) is generic subdimension vector of (0, 2): True
(0, 0) is generic subdimension vector of (1, 2): True
(0, 0) is generic subdimension vector of (2, 2): True
(1, 0) is generic subdimension vector of (1, 0): True
(1, 0) is generic subdimension vector of (2, 0): True
(1, 0) is generic subdimension vector of (1, 1): False
(1, 0) is generic subdimension vector of (2, 1): True
(1, 0) is generic subdimension vector of (1, 2): False
(1, 0) is generic subdimension vector of (2, 2): False
(2, 0) is generic subdimension vector of (2, 0): True
(2, 0) is generic subdimension vector of (2, 1): False
(2, 0) is generic subdimension vector of (2, 2): False
(0, 1) is generic subdimension vector of (0, 1): True
(0, 1) is generic subdimension vector of (1, 1): True
(0, 1) is generic subdimension vector of (2, 1): True
(0, 1) is generic subdimension vector of (0, 2): True
(0, 1) is generic subdimension vector of (1, 2): True
(0, 1) is generic subdimension vector of (2, 2): True
(1, 1) is generic subdimension vector of (1, 1): True
(1, 1) is generic subdimension vector of (2, 1): True
(1, 1) is generic subdimension vector of (1, 2): True
(1, 1) is generic subdimension vector of (2, 2): True
(2, 1) is generic subdimension vector of (2, 1): True
(2, 1) is generic subdimension vector of (2, 2): False
(0, 2) is generic subdimension vector of (0, 2): True
(0, 2) is generic subdimension vector of (1, 2): True
(0, 2) is generic subdimension vector of (2, 2): True
(1, 2) is generic subdimension vector of (1, 2): True
(1, 2) is generic subdimension vector of (2, 2): True
(2, 2) is generic subdimension vector of (2, 2): True
sage: Q = GeneralizedKroneckerQuiver(2)
sage: for (e, d) in cartesian_product([ds]*2):
....: if not Q.is_subdimension_vector(e, d): continue
....: print("{} is generic subdimension vector of {}: {}".format(
....: e, d, Q.is_generic_subdimension_vector(e,d))
....: )
(0, 0) is generic subdimension vector of (0, 0): True
(0, 0) is generic subdimension vector of (1, 0): True
(0, 0) is generic subdimension vector of (2, 0): True
(0, 0) is generic subdimension vector of (0, 1): True
(0, 0) is generic subdimension vector of (1, 1): True
(0, 0) is generic subdimension vector of (2, 1): True
(0, 0) is generic subdimension vector of (0, 2): True
(0, 0) is generic subdimension vector of (1, 2): True
(0, 0) is generic subdimension vector of (2, 2): True
(1, 0) is generic subdimension vector of (1, 0): True
(1, 0) is generic subdimension vector of (2, 0): True
(1, 0) is generic subdimension vector of (1, 1): False
(1, 0) is generic subdimension vector of (2, 1): False
(1, 0) is generic subdimension vector of (1, 2): False
(1, 0) is generic subdimension vector of (2, 2): False
(2, 0) is generic subdimension vector of (2, 0): True
(2, 0) is generic subdimension vector of (2, 1): False
(2, 0) is generic subdimension vector of (2, 2): False
(0, 1) is generic subdimension vector of (0, 1): True
(0, 1) is generic subdimension vector of (1, 1): True
(0, 1) is generic subdimension vector of (2, 1): True
(0, 1) is generic subdimension vector of (0, 2): True
(0, 1) is generic subdimension vector of (1, 2): True
(0, 1) is generic subdimension vector of (2, 2): True
(1, 1) is generic subdimension vector of (1, 1): True
(1, 1) is generic subdimension vector of (2, 1): True
(1, 1) is generic subdimension vector of (1, 2): False
(1, 1) is generic subdimension vector of (2, 2): True
(2, 1) is generic subdimension vector of (2, 1): True
(2, 1) is generic subdimension vector of (2, 2): False
(0, 2) is generic subdimension vector of (0, 2): True
(0, 2) is generic subdimension vector of (1, 2): True
(0, 2) is generic subdimension vector of (2, 2): True
(1, 2) is generic subdimension vector of (1, 2): True
(1, 2) is generic subdimension vector of (2, 2): True
(2, 2) is generic subdimension vector of (2, 2): True
"""
return self.__is_generic_subdimension_vector(e, d)
@cached_method(
key=lambda self, e, d: (self._coerce_vector(e), self._coerce_vector(d))
)
def __is_generic_subdimension_vector(self, e, d) -> bool:
r"""
The cacheable implementation of :meth:`Quiver.is_generic_subdimension_vector`.
EXAMPLES:
Generic subdimension vectors for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q, d, theta = GeneralizedKroneckerQuiver(3), (2, 3), (3, -2)
sage: for e in Q.all_subdimension_vectors(d):
....: print("{} is generic subdimension vector of {}: {}".format(
....: e, d, Q._Quiver__is_generic_subdimension_vector(e, d))
....: )
(0, 0) is generic subdimension vector of (2, 3): True
(0, 1) is generic subdimension vector of (2, 3): True
(0, 2) is generic subdimension vector of (2, 3): True
(0, 3) is generic subdimension vector of (2, 3): True
(1, 0) is generic subdimension vector of (2, 3): False
(1, 1) is generic subdimension vector of (2, 3): False
(1, 2) is generic subdimension vector of (2, 3): True
(1, 3) is generic subdimension vector of (2, 3): True
(2, 0) is generic subdimension vector of (2, 3): False
(2, 1) is generic subdimension vector of (2, 3): False
(2, 2) is generic subdimension vector of (2, 3): False
(2, 3) is generic subdimension vector of (2, 3): True
"""
d = self._coerce_dimension_vector(d)
e = self._coerce_dimension_vector(e)
if e == d or all(ei == 0 for ei in e):
return True
if not self.is_subdimension_vector(e, d):
return False
ds = filter(
lambda eprime: self.euler_form(eprime, d - e) < 0,
self.all_subdimension_vectors(e),
)
return not any(self.is_generic_subdimension_vector(eprime, e) for eprime in ds)
[docs]
def all_generic_subdimension_vectors(self, d, proper=False, nonzero=False):
r"""Returns the list of all generic subdimension vectors of ``d``.
INPUT:
- ``d``: dimension vector
OUTPUT: list of vectors
EXAMPLES:
Some n-Kronecker quivers::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(1)
sage: d = (3, 3)
sage: Q.all_generic_subdimension_vectors(d)
[(0, 0),
(0, 1),
(0, 2),
(0, 3),
(1, 1),
(1, 2),
(1, 3),
(2, 2),
(2, 3),
(3, 3)]
sage: Q = GeneralizedKroneckerQuiver(2)
sage: Q.all_generic_subdimension_vectors(d)
[(0, 0),
(0, 1),
(0, 2),
(0, 3),
(1, 1),
(1, 2),
(1, 3),
(2, 2),
(2, 3),
(3, 3)]
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.all_generic_subdimension_vectors(d)
[(0, 0), (0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 3)]
sage: Q.all_generic_subdimension_vectors(d, nonzero=True)
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3), (3, 3)]
sage: Q.all_generic_subdimension_vectors(d, proper=True)
[(0, 0), (0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
"""
d = self._coerce_dimension_vector(d)
return list(
filter(
lambda e: self.is_generic_subdimension_vector(e, d),
self.all_subdimension_vectors(
d, proper=proper, nonzero=nonzero, forget_labels=True
),
)
)
[docs]
def generic_ext(self, d, e):
r"""
Computes :math:`\operatorname{ext}(d, e)`.
INPUT:
- ``d``: dimension vector
- ``e``: dimension vector
OUTPUT: dimension of the generic ext
According to Theorem 5.4 in Schofield's 'General representations of quivers',
we have
.. MATH::
\operatorname{ext}(a,b) =
\operatorname{max}\{-\langle c,b\rangle\},
where :math:`c` runs over the generic subdimension vectors of :math:`a`.
EXAMPLES:
Generic ext on the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: ds = [Q.simple_root(0), Q.simple_root(1), Q.thin_dimension_vector()]
sage: for (d, e) in cartesian_product([ds]*2):
....: print("ext({}, {}) = {}".format(d, e, Q.generic_ext(d, e)))
ext((1, 0), (1, 0)) = 0
ext((1, 0), (0, 1)) = 3
ext((1, 0), (1, 1)) = 2
ext((0, 1), (1, 0)) = 0
ext((0, 1), (0, 1)) = 0
ext((0, 1), (1, 1)) = 0
ext((1, 1), (1, 0)) = 0
ext((1, 1), (0, 1)) = 2
ext((1, 1), (1, 1)) = 1
"""
d = self._coerce_dimension_vector(d)
e = self._coerce_dimension_vector(e)
return max(
-self.euler_form(f, e) for f in self.all_generic_subdimension_vectors(d)
)
[docs]
def generic_hom(self, d, e):
r"""
Computes :math:`\operatorname{hom}(d, e)`.
INPUT:
- ``d``: dimension vector
- ``e``: dimension vector
OUTPUT: dimension of the generic hom
There is a non-empty open subset `U` of :math:`R(Q,d) \times R(Q,e)` such that
.. MATH::
\operatorname{dim}(\operatorname{Ext}(M,N)) = \operatorname{ext}(d,e),
i.e., :math:`\operatorname{dim}(\operatorname{Ext}(M,N))` is minimal for all
`(M,N)` in `U`.
Therefore, :math:`\operatorname{dim}(\operatorname{Hom}(M,N)) =
\langle a,b\rangle + \operatorname{dim}(\operatorname{Ext}(M,N))`
is minimal, and
:math:`\operatorname{hom}(a,b) = \langle a,b\rangle + \operatorname{ext}(a,b)`.
EXAMPLES:
Generic hom on the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: ds = [Q.simple_root(0), Q.simple_root(1), Q.thin_dimension_vector()]
sage: for (d, e) in cartesian_product([ds]*2):
....: print("hom({}, {}) = {}".format(d, e, Q.generic_hom(d, e)))
hom((1, 0), (1, 0)) = 1
hom((1, 0), (0, 1)) = 0
hom((1, 0), (1, 1)) = 0
hom((0, 1), (1, 0)) = 0
hom((0, 1), (0, 1)) = 1
hom((0, 1), (1, 1)) = 1
hom((1, 1), (1, 0)) = 1
hom((1, 1), (0, 1)) = 0
hom((1, 1), (1, 1)) = 0
"""
d = self._coerce_dimension_vector(d)
e = self._coerce_dimension_vector(e)
return self.euler_form(d, e) + self.generic_ext(d, e)
"""
Harder--Narasimhan types
"""
@cached_method
def _all_harder_narasimhan_types(self, d, theta, denom=sum, sorted=False):
r"""Returns the list of all Harder--Narasimhan types of d.
INPUT:
- ``d`` -- dimension vector
- ``theta` -- stability parameter
- ``denom`` -- the denominator function (default: sum)
OUTPUT: list of Harder--Narasimhan types
EXAMPLES:
The Harder--Narasimhan types for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: d = (2, 3)
sage: theta = (3, -2)
sage: Q._all_harder_narasimhan_types(d, theta)
[((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),)]
.. NOTE ::
This is a method of Quiver so that its results can be more efficiently cached.
See :meth:`QuiverModuli.all_harder_narasimhan_types()` for the better location
to call it from.
"""
d = self._coerce_dimension_vector(d)
theta = self._coerce_vector(theta)
ds = self.all_subdimension_vectors(d, proper=True, nonzero=True)
ds = filter(
lambda e: self.slope(e, theta, denom=denom)
> self.slope(d, theta, denom=denom),
ds,
)
ds = filter(
lambda e: self.has_semistable_representation(e, theta, denom=denom),
ds,
)
ds = list(ds)
if sorted:
ds.sort(key=(lambda e: self.slope(e, theta, denom=denom)))
all_types = []
for e in ds:
for estar in filter(
lambda fstar: self.slope(e, theta, denom=denom)
> self.slope(fstar[0], theta, denom=denom),
self._all_harder_narasimhan_types(d - e, theta, denom=denom),
):
all_types.append((e,) + estar)
if self.has_semistable_representation(d, theta, denom=denom):
all_types.append((d,))
return all_types
"""
(Semi-)stability
"""
[docs]
def canonical_stability_parameter(self, d):
r"""
Returns the canonical stability parameter for ``d``
INPUT:
- ``d``: dimension vector
OUTPUT: canonical stability parameter
The canonical stability parameter is given by
:math:`\langle d,-\rangle - \langle -,d\rangle`.
EXAMPLES:
Our usual example of the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: Q.canonical_stability_parameter((2, 3))
(9, -6)
For the 5-subspace quiver::
sage: Q = SubspaceQuiver(5)
sage: Q.canonical_stability_parameter((1, 1, 1, 1, 1, 2))
(2, 2, 2, 2, 2, -5)
It takes vertex labels (if present) into account::
sage: Q = Quiver.from_string("foo---bar", forget_labels=False)
sage: Q.canonical_stability_parameter((2, 3))
{'bar': -6, 'foo': 9}
EXAMPLES:
Canonical stability parameter for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q, d = GeneralizedKroneckerQuiver(3), (2, 3)
sage: Q.canonical_stability_parameter(d)
(9, -6)
This method also works with vertex labels::
sage: from quiver import *
sage: Q = Quiver.from_string("foo---bar", forget_labels=False)
sage: d = {"foo": 2, "bar": 3}
sage: Q.canonical_stability_parameter(d)
{'bar': -6, 'foo': 9}
"""
d = self._coerce_dimension_vector(d)
theta = vector(d) * (-self.euler_matrix().transpose() + self.euler_matrix())
if self.__has_vertex_labels():
return dict(zip(self.vertices(), theta))
return theta
[docs]
def has_semistable_representation(self, d, theta=None, denom=sum):
r"""Checks if there is a ``theta``-semistable of dimension vector ``d``
INPUT:
- ``d``: dimension vector
- ``theta`` (default: canonical stability parameter): stability parameter
OUTPUT: whether there is a ``theta``-semistable of dimension vector ``d``
By MR1162487_ a dimension vector `d` admits a :math:`\theta`-semi-stable
representation if and only if :math:`\mu_{\theta}(e) \leq \mu_{\theta}(d)` for
all generic subdimension vectors `e` of `d`.
.. _MR1162487: https://mathscinet.ams.org/mathscinet/relay-station?mr=1162487
EXAMPLES:
Semistables for the :math:`\mathrm{A}_2` quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(1)
sage: Q.has_semistable_representation((1, 1), (1, -1))
True
sage: Q.has_semistable_representation((2, 2), (1, -1))
True
sage: Q.has_semistable_representation((1, 2), (1, -1))
False
sage: Q.has_semistable_representation((0, 0), (1, -1))
True
Semistables for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.has_semistable_representation((2, 3))
True
sage: Q.has_semistable_representation((1, 4), (-3, 2))
False
"""
if theta is None:
theta = self.canonical_stability_parameter(d)
d = self._coerce_dimension_vector(d)
theta = self._coerce_vector(theta)
return all(
self.slope(e, theta, denom=denom) <= self.slope(d, theta, denom=denom)
for e in self.all_generic_subdimension_vectors(d, nonzero=True)
)
[docs]
def has_stable_representation(self, d, theta=None, denom=sum):
r"""
Checks if there is a ``theta``-stable representation of ``d``
INPUT:
- ``d``: dimension vector
- ``theta`` (default: canonical stability parameter): stability parameter
OUTPUT: whether there is a ``theta``-stable of dimension vector ``d``
By MR1162487_ `d` admits a theta-stable representation if and only if
:math:`\mu_{\theta}(e) < \mu_{\theta}(d)` for all proper generic subdimension
vectors :math:`e` of :math:`d`.
.. _MR1162487: https://mathscinet.ams.org/mathscinet/relay-station?mr=1162487
EXAMPLES:
Stables for the :math:`\mathrm{A}_2` quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(1)
sage: theta = (1, -1)
sage: Q.has_stable_representation((1, 1), theta)
True
sage: Q.has_stable_representation((2, 2), theta)
False
sage: Q.has_stable_representation((0, 0), theta)
False
Stables for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: d = (2, 3)
sage: theta = Q.canonical_stability_parameter(d)
sage: Q.has_stable_representation(d, theta)
True
sage: Q.has_stable_representation(d)
True
"""
if theta is None:
theta = self.canonical_stability_parameter(d)
d = self._coerce_dimension_vector(d)
theta = self._coerce_vector(theta)
if d == self._coerce_dimension_vector(self.zero_vector()):
return False
return all(
self.slope(e, theta, denom=denom) < self.slope(d, theta, denom=denom)
for e in self.all_generic_subdimension_vectors(d, proper=True, nonzero=True)
)
"""
Canonical decomposition
"""
[docs]
def canonical_decomposition(self, d):
r"""
Computes the canonical decomposition of a dimension vector.
INPUT:
- ``d``: dimension vector
OUTPUT: canonical decomposition as list of dimension vectors
The canonical decomposition of a dimension vector `d` is the unique
decomposition :math:`d = e_1 + e_2 + ... + e_k` such that
:math:`e_1, e_2, ..., e_k` are such that for all
:math:`i \neq j, \mathrm{ext}(e_i, e_j) = \mathrm{ext}(e_j, e_i) = 0`.
The general representation of dimension vector `d` is isomorphic to the direct
sum of representations of dimension vectors :math:`e_1, e_2, ..., e_k`.
EXAMPLES:
Canonical decomposition of the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(3)
sage: Q.canonical_decomposition((2, 3))
[(2, 3)]
sage: for d in Q.all_subdimension_vectors((5, 5)):
....: print(Q.canonical_decomposition(d))
[(0, 0)]
[(0, 1)]
[(0, 1), (0, 1)]
[(0, 1), (0, 1), (0, 1)]
[(0, 1), (0, 1), (0, 1), (0, 1)]
[(0, 1), (0, 1), (0, 1), (0, 1), (0, 1)]
[(1, 0)]
[(1, 1)]
[(1, 2)]
[(1, 3)]
[(0, 1), (1, 3)]
[(0, 1), (0, 1), (1, 3)]
[(1, 0), (1, 0)]
[(2, 1)]
[(2, 2)]
[(2, 3)]
[(2, 4)]
[(2, 5)]
[(1, 0), (1, 0), (1, 0)]
[(3, 1)]
[(3, 2)]
[(3, 3)]
[(3, 4)]
[(3, 5)]
[(1, 0), (1, 0), (1, 0), (1, 0)]
[(1, 0), (3, 1)]
[(4, 2)]
[(4, 3)]
[(4, 4)]
[(4, 5)]
[(1, 0), (1, 0), (1, 0), (1, 0), (1, 0)]
[(1, 0), (1, 0), (3, 1)]
[(5, 2)]
[(5, 3)]
[(5, 4)]
[(5, 5)]
"""
return self.__canonical_decomposition(d)
@cached_method(key=lambda self, d: self._coerce_vector(d))
def __canonical_decomposition(self, d):
r"""The cacheable implementation of :meth:`Quiver.canonical_decomposition`
EXAMPLES:
Canonical decomposition of `(5, 3)` for the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = GeneralizedKroneckerQuiver(2)
sage: Q._Quiver__canonical_decomposition((5, 5))
[(1, 1), (1, 1), (1, 1), (1, 1), (1, 1)]
"""
d = self._coerce_dimension_vector(d)
ds = self.all_generic_subdimension_vectors(d, proper=True, nonzero=True)
for e in ds:
if d - e in ds:
return self.canonical_decomposition(e) + self.canonical_decomposition(
d - e
)
return [d]
[docs]
def dimension_nullcone(self, d):
r"""
Returns the dimension of the nullcone
The nullcone is the set of all nilpotent representations.
INPUT:
- ``d`` -- dimension vector
OUTPUT: dimension of the nullcone
EXAMPLES:
The usual example of the 3-Kronecker quiver::
sage: from quiver import *
sage: Q = KroneckerQuiver(3)
sage: Q.dimension_nullcone((2, 3))
18
"""
d = self._coerce_dimension_vector(d)
if self.is_acyclic():
return d * self.adjacency_matrix() * d
else:
raise NotImplementedError()
[docs]
def first_hochschild_cohomology(self):
r"""
Compute the dimension of the first Hochschild cohomology
This uses the formula of Happel from Proposition 1.6 in MR1035222_.
One needs the quiver to be acyclic for this, otherwise it is not necessarily
finite-dimensional.
EXAMPLES:
The first Hochschild cohomology of the `m`-th generalized Kronecker quiver
is the dimension of :math:`\mathrm{PGL}_{m+1}`::
sage: from quiver import *
sage: GeneralizedKroneckerQuiver(3).first_hochschild_cohomology()
8
The first Hochschild cohomology vanishes if and only if the quiver is a tree::
sage: from quiver import *
sage: SubspaceQuiver(7).first_hochschild_cohomology()
0
.. _MR1035222: https://mathscinet.ams.org/mathscinet/relay-station?mr=1035222
"""
assert self.is_acyclic(), "the quiver needs to be acyclic"
return (
1
- self.number_of_vertices()
+ sum(
len(self.graph().all_paths(a[0], a[1], use_multiedges=True))
for a in self.graph().edges()
)
)