%hide
from sage.graphs.digraph import DiGraph
from sage.rings.finite_rings.integer_mod_ring import Integers
from sage.matrix.constructor import Matrix
from sage.structure.sage_object import SageObject
from sage.structure.parent import Parent
from sage.structure.element import Element
from sage.modules.free_module_morphism import FreeModuleMorphism
class Quiver(DiGraph):
"""
A Quiver is a directed graph used for representation theory. In sage a quiver
is different from a directed graph in the following ways:
* The vertices of a DiGraph are arbitrary sage objects, but the vertices
of a quiver must be labeled 1, 2, ..., n.
* DiGraphs can have cycles (paths that start and end at the same vertex)
and even loops (edges whose initial and terminal vertices are equal).
In this implementation a quiver must be acyclic (and can not have
loops).
* The edges of a DiGraph are labeled with arbitrary sage objects or None
if no label is specified. Each edge of a quiver must be labeled with a
nonempty string and distinct edges must have distinct labels.
INPUT:
See the documentation of DiGraph()
OUTPUT:
- Quiver
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q1 = Quiver({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}})
"""
###########################################################################
# #
# PRIVATE FUNCTIONS #
# These functions are not meant to be seen by the end user. #
# #
###########################################################################
def __init__(self, data=None, pos=None, loops=False, format=None,
boundary=[], weighted=True, implementation='c_graph',
sparse=True, vertex_labels=True, multiedges=True, **kwds):
# Use the Digraph construction
super(Quiver, self).__init__(data, pos, False, format, boundary,
True, implementation, sparse, vertex_labels, **kwds)
# Ensure the input is valid
self._assert_valid_quiver()
def _assert_valid_quiver(self):
"""
Raises an exception if self does not satisfy the following:
* Each edge must have a unique string label.
* Labels of edges can't begin with 'e_'.
* Vertices must be labeled by the integers 1, 2, ..., n.
* Q must be acyclic (no loops).
"""
# Check that it's directed, acyclic, and has no loops
if not self.is_directed_acyclic():
raise ValueError("Representation Quiver must be directed acyclic.")
if self.has_loops():
raise NotImplementedError("Representation Quiver may not have loops.")
# Check that vertices are labeled 1,2,3,... and that edge labels are
# unique
if list(self) != range(1, self.num_verts() + 1):
raise ValueError("Vertices of Representation Quiver must be labeled by integers 1.." + str(self.num_verts()) + ".")
if len(set(self.edges())) != self.num_edges():
raise ValueError("Edge labels of Representation Quiver must be unique.")
# Check that edges are labeled with nonempty strings and don't begin
# with 'e_'
for x in self.edge_labels():
if not isinstance(x, str) or x == '':
raise ValueError("Edge labels of Representation Quiver must be nonempty strings.")
if x[0:2] == 'e_':
raise ValueError("Edge labels of Representation Quiver cannot begin with 'e_'.")
def _repr_(self):
return "Quiver on " + str(len(self)) + " vertices"
###########################################################################
# #
# GRAPH THEORETIC FUNCTIONS #
# These functions involve the graph theoretic structure of the quiver. #
# #
###########################################################################
def all_edge_paths(self, start=None, end=None):
"""
Returns a list of all edge paths between a pair of vertices (start, end).
INPUT:
- ``start`` - integer or None (default: None), the initial vertex of the paths in
the output. If None is given then the initial vertex is arbitrary.
- ``end`` - integer or None (default: None), the terminal vertex of the paths in
the output. If None is given then the terminal vertex is arbitrary.
OUTPUT:
- a list of edge paths (also lists) in the quiver
.. NOTE::
This function differs from all_paths in that paths are given as lists of
edges instead of lists of vertices. If there are multiple edges between
two vertices all_paths will not differentiate between them but
all_edge_paths will.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}})
sage: Q.all_edge_paths(1, 3)
[[(1, 2, 'b'), (2, 3, 'd')], [(1, 2, 'a'), (2, 3, 'd')], [(1, 3, 'c')]]
If start=end then a list containing only the trivial path at that vertex is
returned. A trivial path is a list containing only the "edge" (v, v) for some
v::
sage: Q.all_edge_paths(1, 1)
[[(1, 1)]]
The empty list is returned if there are no paths between the given vertices::
sage: Q.all_edge_paths(3, 1)
[]
If end=None then all edge paths beginning at start are returned, including the
trivial path::
sage: Q.all_edge_paths(2)
[[(2, 2)], [(2, 3, 'd')]]
If start=None then all edge paths ending at end are returned, including the
trivial path. Note that the two edges from vertex 1 to vertex 2 count as two
different edge paths::
sage: Q.all_edge_paths(None, 2)
[[(1, 2, 'b')], [(1, 2, 'a')], [(2, 2)]]
sage: Q.all_edge_paths(end=2)
[[(1, 2, 'b')], [(1, 2, 'a')], [(2, 2)]]
If start=end=None then all edge paths are returned, including trivial paths::
sage: Q.all_edge_paths()
[[(1, 1)], [(1, 2, 'b')], [(1, 2, 'a')], [(1, 2, 'b'), (2, 3, 'd')], [(1, 2, 'a'), (2, 3, 'd')], [(1, 3, 'c')], [(2, 2)], [(2, 3, 'd')], [(3, 3)]]
The vertex given must be a vertex of the quiver::
sage: Q.all_edge_paths(1, 4)
Traceback (most recent call last):
...
ValueError: The end vertex 4 is not a vertex of the quiver.
"""
# Check that given arguments are vertices
if start is not None and start not in self:
raise ValueError("The start vertex " + str(start) + " is not a vertex of the quiver.")
if end is not None and end not in self:
raise ValueError("The end vertex " + str(end) + " is not a vertex of the quiver.")
# Handle start=None
if start == None:
results = []
for v in self:
results += self.all_edge_paths(v, end)
return results
# Handle end=None
if end == None:
results = []
for v in self:
results += self.all_edge_paths(start, v)
return results
# Handle the trivial case
if start == end:
return [[(start, end)]]
# This function will recursively convert a path given in terms of
# vertices to a list of paths given in terms of edges.
def v_to_e(path):
if len(path) == 1:
return [[]]
paths = []
for a in self.edge_label(path[0], path[1]):
for b in v_to_e(path[1:]):
paths.append([(path[0], path[1], a)] + b)
return paths
# For each vertex path we append the resulting edge paths
result = []
for path in self.all_paths(start, end):
result += v_to_e(path)
# The result is all paths from start to end
return result
def to_directed(self):
"""
Returns the underlying Digraph.
OUTPUT:
- a DiGraph with the same edges and vertices as the quiver
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a']}})
sage: type(Q.to_directed()) == DiGraph
True
"""
return DiGraph(self)
def is_source(self, vertex):
"""
Tests whether the vertex is a source in the quiver.
INPUT:
- ``vertex`` - integer, the vertex to be tested
OUTPUT:
- bool, True if the vertex is not the terminus of any edge, False if there is
an edge terminating at the vertex
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c']}})
sage: Q.is_source(1)
True
sage: Q.is_source(2)
False
sage: Q.is_source(3)
False
"""
return len(self.neighbors_in(vertex)) == 0
def is_sink(self, vertex):
"""
Tests whether the vertex is a sink in the quiver.
INPUT:
- ``vertex`` - integer, the vertex to be tested
OUTPUT:
- bool, True if the vertex is not the source of any edge, False if there is an
edge beginning at the vertex
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c']}})
sage: Q.is_sink(1)
False
sage: Q.is_sink(2)
False
sage: Q.is_sink(3)
True
"""
return len(self.neighbors_out(vertex)) == 0
def sources(self):
"""
Returns a list of sources of the quiver.
OUTPUT:
- list, the vertices of the quiver that have no edges going into them
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{3:['a']}, 2:{3:['b']}})
sage: Q.sources()
[1, 2]
sage: T = Quiver({1:{}})
sage: T.sources()
[1]
"""
return [x for x in self if self.is_source(x)]
def sinks(self):
"""
Returns a list of sinks of the quiver.
OUTPUT:
- list, the vertices of the quiver that have no edges beginning at them
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{3:['a']}, 2:{3:['b']}})
sage: Q.sinks()
[3]
sage: T = Quiver({1:{}})
sage: T.sinks()
[1]
"""
return [x for x in self if self.is_sink(x)]
def all_paths(self, start=None, end=None):
"""
Returns a list of all vertex paths between a pair of vertices (start, end).
INPUT:
- ``start`` - integer or None (default: None), the initial vertex of the paths in
the output. If None is given then the initial vertex is arbitrary.
- ``end`` - integer or None (default: None), the terminal vertex of the paths in
the output. If None is given then the terminal vertex is arbitrary.
OUTPUT:
- a list of vertex paths (also lists) in the quiver
.. NOTE::
This function differs from all_edge_paths in that paths are given as lists
of vertices instead of lists of edges. If there are multiple edges between
two vertices all_paths will not differentiate between them but
all_edge_paths will.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}})
sage: Q.all_paths(1, 3)
[[1, 2, 3], [1, 3]]
If there are no paths an empty list is returned::
sage: Q.all_paths(3, 1)
[]
If start=end then a list containing only the trivial vertex path is returned.
A trivial vertex path is just a list containing a single vertex::
sage: Q.all_paths(2, 2)
[[2]]
If end=None then all vertex paths begining at start are returned, including
trivial paths::
sage: Q.all_paths(2)
[[2], [2, 3]]
If start=None then all vertex paths ending at end are returned, including
trivial paths. Note that even though there are two edges from vertex 1 to
vertex 2 there is only one vertex path::
sage: Q.all_paths(None, 2)
[[1, 2], [2]]
sage: Q.all_paths(end=2)
[[1, 2], [2]]
If start=end=None then all vertex paths are returned, including trivial paths::
sage: Q.all_paths()
[[1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
The vertex given must be a vertex of the quiver::
sage: Q.all_paths(1, 4)
Traceback (most recent call last):
...
ValueError: The end vertex 4 is not a vertex of the quiver.
"""
# This function modifies the sage version by allowing start or end=None and not
# producing an error when start=end.
# Check that given arguments are vertices
if start is not None and start not in self:
raise ValueError("The start vertex " + str(start) + " is not a vertex of the quiver.")
if end is not None and end not in self:
raise ValueError("The end vertex " + str(end) + " is not a vertex of the quiver.")
# Handle start=None
if start == None:
results = []
for v in self:
results += self.all_paths(v, end)
return results
# Handle end=None
if end == None:
results = []
for v in self:
results += self.all_paths(start, v)
return results
# Handle start=end
if start == end:
return [[start]]
# Otherwise call the sage version
return super(Quiver, self).all_paths(start, end)
def reverse(self):
"""
Returns a copy of the quiver with edges reversed in direction.
OUTPUT:
- Quiver
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: Qrev = Q.reverse()
sage: Q.edges()
[(1, 2, 'a'), (1, 2, 'b')]
sage: Qrev.edges()
[(2, 1, 'a'), (2, 1, 'b')]
Reversing a quiver twice does not return the original quiver, but returns a
quiver that is equal to the original quiver::
sage: Qrevrev = Qrev.reverse()
sage: Qrevrev is Q
False
sage: Qrevrev == Q
True
"""
return Quiver(DiGraph.reverse(self))
###########################################################################
# #
# REPRESENTATION THEORETIC FUNCTIONS #
# These functions involve representations of quivers. #
# #
###########################################################################
def representation(self, k, spaces={}, maps={}):
"""
Returns a representation of the quiver.
For more information see the QuiverRep documentation.
"""
return QuiverRep(k, self, spaces, maps)
def S(self, k, vertex):
"""
Returns the simple module over k at the given vertex.
INPUT:
- ``k`` - ring, the base ring of the representation
- ``vertex`` - integer, a vertex of the quiver
OUTPUT:
- QuiverRep, the simple module at vertex with base ring k
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c','d']}})
sage: S1 = Q.S(GF(3), 1)
sage: Q.S(ZZ, 3).dimension_vector()
(0, 0, 1)
sage: Q.S(ZZ, 1).dimension_vector()
(1, 0, 0)
The vertex given must be a vertex of the quiver::
sage: Q.S(QQ, 4)
Traceback (most recent call last):
...
ValueError: Must specify a valid vertex of the quiver.
"""
# Raise an error if the given vertex is not a vertex
if vertex not in self:
raise ValueError("Must specify a valid vertex of the quiver.")
# This is the module with k at the given vertex and zero elsewhere. As
# all maps are zero we only need to specify that the given vertex has
# dimension 1 and the constructor will zero out everything else.
return QuiverRep(k, self, {vertex: 1})
def P(self, k, vertex):
"""
Returns the indecomposable projective module over k at the given vertex.
INPUT:
- ``k`` - ring, the base ring of the representation
- ``vertex`` - integer, a vertex of the quiver
OUTPUT:
- QuiverRep, the indecomposable projective module at vertex with base ring k
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c','d']}})
sage: P2 = Q.P(GF(3), 2)
sage: Q.P(ZZ, 3).dimension_vector()
(0, 0, 1)
sage: Q.P(ZZ, 1).dimension_vector()
(1, 2, 4)
The vertex given must be a vertex of the quiver::
sage: Q.P(QQ, 4)
Traceback (most recent call last):
...
ValueError: Must specify a valid vertex of the quiver.
"""
# Raise an error if the given vertex is not a vertex
if vertex not in self:
raise ValueError("Must specify a valid vertex of the quiver.")
# bases = {}
# spaces = {}
# mats = {}
#
# # The basis of P(vertex) at x is the set of all paths vertex->x
# for x in self:
# bases[x] = self.all_edge_paths(vertex, x)
# spaces[x] = k**len(bases[x])
#
# # mats will hold the matrices describing the maps on each vertex
# for e in self.edges():
# # We start with a zero matrix and put in 1's as needed
# mats[e] = [[k(0)]*len(bases[e[1]]) for i in range(0, len(bases[e[0]]))]
#
# # The edge map acts by taking a basis element (a path) and adding
# # that edge onto the end to form another path, which is the image
# # of the edge map. As this map takes basis elements to basis
# # elements the matrix consists of 0's and 1's. Specifically there
# # is a 1 at position (i,j) of the matrix if the edge map sends the
# # ith basis element to the jth.
#
# for x in range(0, len(bases[e[0]])):
# if bases[e[0]][x][0][0] == bases[e[0]][x][0][1]:
# # When we start with the trivial path we expect the image
# # to be the edge that we are currently multiplying by
# mats[e][x][bases[e[1]].index([e])] = k(1)
# else:
# # Otherwise we just add the edge onto the end of the path
# mats[e][x][bases[e[1]].index(bases[e[0]][x] + [e])] = k(1)
#
# # Convert the matrices to a dictionary of homomorphisms and create the
# # module.
# maps = dict((e, spaces[e[0]].hom(mats[e], spaces[e[1]])) for e in self.edges())
# return QuiverRep(k, self, spaces, maps)
return QuiverRep_with_path_basis(k, self, [[(vertex, vertex)]])
def I(self, k, vertex):
"""
Returns the indecomposable injective module over k at the given vertex.
INPUT:
- ``k`` - ring, the base ring of the representation
- ``vertex`` - integer, a vertex of the quiver
OUTPUT:
- QuiverRep, the indecomposable injective module at vertex with base ring k
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c','d']}})
sage: I2 = Q.I(GF(3), 2)
sage: Q.I(ZZ, 3).dimension_vector()
(4, 2, 1)
sage: Q.I(ZZ, 1).dimension_vector()
(1, 0, 0)
The vertex given must be a vertex of the quiver::
sage: Q.I(QQ, 4)
Traceback (most recent call last):
...
ValueError: Must specify a valid vertex of the quiver.
"""
# Raise an error if the given vertex is not a vertex
if vertex not in self:
raise ValueError("Must specify a valid vertex of the quiver.")
bases = {}
spaces = {}
mats = {}
# The basis of I(vertex) at x is the set of all paths x->vertex
for x in self:
bases[x] = self.all_edge_paths(x, vertex)
spaces[x] = k**len(bases[x])
# mats will hold the matrices describing the maps on each vertex
for e in self.edges():
#We start with a zero matrix and put in 1's as needed
mats[e] = [[k(0)]*len(bases[e[1]]) for i in range(0, len(bases[e[0]]))]
# The edge map acts by removing its edge from the beginning of a
# path. If it is not the first edge in the path then the path goes
# to zero. This map takes basis elements to basis elements so the
# matrix describing it is zero save a 1 in the (i,j)th position if
# the ith basis element is sent to the jth.
for x in range(0, len(bases[e[0]])):
if bases[e[0]][x][0] == e:
if len(bases[e[0]][x]) == 1:
# If we remove the only edge from a path the result is
# a trivial path which we must create
mats[e][x][bases[e[1]].index([(e[1], e[1])])] = k(1)
else:
# Otherwise we can just take the first element out of
# the list
mats[e][x][bases[e[1]].index(bases[e[0]][x][1:])] = k(1)
# Convert the matrices to a dictionary of homomorphisms and create the
# module.
maps = dict((e, spaces[e[0]].hom(mats[e], spaces[e[1]])) for e in self.edges())
return QuiverRep(k, self, spaces, maps)
class QuiverRep(Parent):
"""
A quiver representation is a diagram in the category of vector spaces whose
underlying graph is the quiver. Giving a finite dimensional representation
is equivalent to giving a finite dimensional right module for the path
algebra of the quiver.
INPUT:
- ``k`` - ring, the base ring of the representation
- ``Q`` - Quiver, the quiver of the representation
- ``spaces`` - dict (default: empty), a dictionary associating to each vertex a
free module over the base ring k. Not all vertices must be specified,
unspecified vertices are automatically set to k^0. Keys of the dictionary
that don't correspond to vertices are ignored.
- ``maps`` - dict (default: empty), a dictionary associating to each edge a map
whose domain and codomain are the spaces associated to the initial and
terminal vertex of the edge respectively. Not all edges must be specified,
unspecified edges are automatically set to the zero map. Keys of the
dictionary that don't correspond to edges are ignored.
OUTPUT:
- QuiverRep
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep
sage: Q = Quiver({1:{2:['a']}})
sage: M = QuiverRep(GF(5), Q)
sage: M.is_zero()
True
sage: P = Q.P(GF(3), 1)
sage: R = P.radical()
sage: R.is_zero()
False
sage: (P/R).is_simple()
True
sage: P == R
False
"""
###########################################################################
# #
# PRIVATE FUNCTIONS #
# These functions are not meant to be seen by the end user. #
# #
###########################################################################
def __init__(self, k, Q, spaces={}, maps={}):
# This class can handle representations over
# an arbitrary base ring, not necessarily a field, so long as sage can
# construct free modules over that ring. The data of a representation is held
# in the following private variables:
#
# * _quiver
# The quiver of the representation.
# * _base_ring
# The base ring of the representation.
# * _spaces
# A dictionary which associates to each vertex of the quiver a free
# module over the base ring.
# * _maps
# A dictionary which associates to each edge of the quiver a homomorphism
# whose domain and codomain equal the initial and terminal vertex of the
# edge.
self._quiver = Q
self._base_ring = k
self._spaces = {}
self._maps = {}
# If the vertex is not specified set it as a free module of rank 0, if
# an integer is given set it as a free module of that rank, otherwise
# assume the object is a module and assign it to the vertex.
for x in Q:
if x not in spaces:
self._spaces[x] = k**0
elif spaces[x] in Integers():
self._spaces[x] = k**spaces[x]
else:
self._spaces[x] = spaces[x]
# The preferred method of specifying an edge is as a tuple (i, t, l)
# where i is the initial vertex, t is the terminal vertex, and l is the
# label. This is the form in which quiver.edges() and other such
# functions give the edge. But here edges can be specified by giving
# only the two vertices or giving only the edge label.
for x in Q.edges():
if x in maps:
e = maps[x]
elif (x[0], x[1]) in maps:
e = maps[(x[0], x[1])]
elif x[2] in maps:
e = maps[x[2]]
else:
e = self._spaces[x[0]].Hom(self._spaces[x[1]]).zero_element()
#If a morphism is specified use it, otherwise assume the hom
# function can coerce the object to a morphism. Matrices and the
# zero and one of the base ring are valid inputs (one is valid only
# when the domain and codomain are equal.
if isinstance(e, FreeModuleMorphism):
self._maps[x] = e
else:
self._maps[x] = self._spaces[x[0]].hom(e, self._spaces[x[1]])
self._assert_valid_quiverrep()
def _assert_valid_quiverrep(self):
"""
Raises an error if the representation is not well defined.
Specifically it checks the map assigned to each edge. The domain and codomain
must equal the modules assigned to the initial and terminal vertices of the
edge.
"""
for x in self._quiver.edges():
if self._maps[x].domain() != self._spaces[x[0]]:
raise ValueError("Domain of map at edge '" + str(x[2]) + "' does not match.")
if self._maps[x].codomain() != self._spaces[x[1]]:
raise ValueError("Codomain of map at edge '" + str(x[2]) + "' does not match.")
def _repr_(self):
return "Representation with dimension vector " + str(self.dimension_vector())
def __eq__(self, other):
# This overloads the == operator
# Check that quivers are equal
if self._quiver != other._quiver:
return False
# Check that modules assigned to vertices are equal
for v in self._quiver:
if self._spaces[v] != other._spaces[v]:
return False
# Check that maps assigned to edges are equal
for e in self._quiver.edges():
if self._maps[e] != other._maps[e]:
return False
return True
def __ne__(self, other):
# This overloads the != operator
# Check that quivers are equal
if self._quiver != other._quiver:
return True
# Check that modules assigned to vertices are equal
for v in self._quiver:
if self._spaces[v] != other._spaces[v]:
return True
# Check that maps assigned to edges are equal
for e in self._quiver.edges():
if self._maps[e] != other._maps[e]:
return True
return False
def __div__(self, sub):
# This and __Truediv__ below together overload the / operator.
return self.quotient(sub)
def __truediv__(self, other):
return self.quotient(sub)
def __contains__(self, element):
# This overloads the 'in' operator
# Representations only contain elements from the same quiver
if not isinstance(element, QuiverRepElement) or self._quiver != element._quiver:
return False
# The element is in the representation if and only if the element at
# each vertex is in the space assigned to that vertex
for v in self._quiver:
if not element._elems[v] in self._spaces[v]:
return False
return True
def _submodule(self, spaces={}):
"""
Returns the submodule specified by the data.
This differs from self.submodule in that it assumes the data correctly
specifies a submodule whereas self.submodule returns the smallest submodule
containing the data.
"""
# Add zero submodules
for v in self._quiver:
if v not in spaces:
spaces[v] = self._spaces[v].zero_submodule()
# Create edge homomorphisms restricted to the new domains and codomains
maps = {}
for e in self._quiver.edges():
maps[e] = self._maps[e].restrict_domain(spaces[e[0]]).restrict_codomain(spaces[e[1]])
return QuiverRep(self._base_ring, self._quiver, spaces, maps)
###########################################################################
# #
# ACCESS FUNCTIONS #
# These functions are used to view and modify the representation data. #
# #
###########################################################################
def set_space(self, vertex, space):
"""
Sets the free module at the given vertex to be the given space.
INPUT:
- ``vertex`` - integer, a vertex of the quiver of the module
- ``space`` - free module over the base ring
.. NOTES::
Any edges into or out of the vertex are given the zero morphism. This
ensures that self remains a valid quiver representation.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b'], 3:['c']}, 2:{3:['d']}})
sage: P = Q.P(GF(3), 1)
sage: P
Representation with dimension vector (1, 2, 3)
sage: P.get_map((1, 2, 'a')).matrix()
[0 1]
sage: P.set_space(2, GF(3)^5)
sage: P
Representation with dimension vector (1, 5, 3)
sage: P.get_map((1, 2, 'a')).matrix()
[0 0 0 0 0]
"""
self._spaces[vertex] = space
for x in self._quiver.edges():
if x[0] == vertex or x[1] == vertex:
self._maps[x] = self._spaces[x[0]].Hom(self._spaces[x[1]]).zero_element()
def set_map(self, edge, map):
"""
Sets the map at the given edge.
INPUT:
- ``edge`` - tuple of the form (initial vertex, terminal vertex, label) specifying
the edge to be set
- ``map`` - FreeModuleMorphism with the appropriate domain and codomain or an
element that the FreeModuleMorphism constructor recognizes as input in order
to create a FreeModuleMorphism, for example a matrix of the appropriate
dimensions
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b'], 3:['c']}, 2:{3:['d']}})
sage: P = Q.P(GF(3), 1)
sage: P.get_map((1, 2, 'a')).matrix()
[0 1]
sage: h = P.get_space(1).hom(0, P.get_space(2))
sage: P.set_map((1, 2, 'a'), h)
sage: P.get_map((1, 2, 'a')).matrix()
[0 0]
sage: P.set_map((1, 2, 'a'), [[1, 1]])
sage: P.get_map((1, 2, 'a')).matrix()
[1 1]
When giving a morphism the domain and codomain must equal the spaces set to the
initial and terminal vertices respectively::
sage: h = P.get_space(2).hom(1, P.get_space(2))
sage: P.set_map((1, 2, 'a'), h)
Traceback (most recent call last):
...
ValueError: Domain of map does not match corresponding module.
"""
# If a homomorphism is specified use it and check that the domain and
# codomain is is correct. Otherwise create a homomorphism. The domain
# and codomain will then automatically be correct and don't need to be
# checked.
if isinstance(map, FreeModuleMorphism):
self._maps[edge] = map
if map.domain() != self._spaces[edge[0]]:
raise ValueError("Domain of map does not match corresponding module.")
if map.codomain() != self._spaces[edge[1]]:
raise ValueError("Co domain of map does not match corresponding module.")
else:
self._maps[edge] = self._spaces[edge[0]].hom(map, self._spaces[edge[1]])
def get_space(self, vertex):
"""
Returns the module associated to the given vertex.
INPUT:
- ``vertex`` - integer, a vertex of the quiver of the module
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a'], 3:['b']}})
sage: Q.P(QQ, 1).get_space(1)
Vector space of dimension 1 over Rational Field
"""
return self._spaces[vertex]
def get_map(self, edge):
"""
Returns the map associated to the given edge.
INPUT:
- ``edge`` - tuple of the form (initial vertex, terminal vertex, label) specifying
the edge whose map is returned
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: Q.P(ZZ, 1).get_map((1, 2, 'a'))
Free module morphism defined by the matrix
[0 1]
Domain: Ambient free module of rank 1 over the principal ideal domain ...
Codomain: Ambient free module of rank 2 over the principal ideal domain ...
"""
return self._maps[edge]
def quiver(self):
"""
Returns the quiver of the representation.
OUTPUT:
- Quiver
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep
sage: Q = Quiver({1:{2:['a']}})
sage: M = QuiverRep(GF(5), Q)
sage: M.quiver() is Q
True
"""
return self._quiver
def base_ring(self):
"""
Returns the base ring of the representation.
OUTPUT:
- ring
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep
sage: Q = Quiver({1:{2:['a']}})
sage: M = QuiverRep(GF(5), Q)
sage: M.base_ring() is GF(5)
True
"""
return self._base_ring
###########################################################################
# #
# DATA FUNCTIONS #
# These functions return data collected from the representation. #
# #
###########################################################################
def dimension(self, vertex=None):
"""
Returns the dimension of the space associated to the given vertex.
INPUT:
- ``vertex`` - integer or None (default: None), the given vertex
OUTPUT:
- integer, the dimension over the base ring of the space associated to the
given vertex. If vertex=None then the dimension over the base ring of the
module is returned
EXAMPLES:
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: P = Q.P(GF(2), 1)
sage: P.dimension(1)
1
sage: P.dimension(2)
2
sage: P.dimension()
3
"""
if vertex == None:
# Sum the dimensions of each vertex
dim = 0
for x in self._quiver:
dim += self._spaces[x].dimension()
return dim
else:
# Return the dimension of just the one vertex
return self._spaces[vertex].dimension()
def dimension_vector(self):
"""
Returns the dimension vector of the representation.
OUTPUT:
- tuple
.. NOTE::
The order of the entries in the tuple matches the order given by calling
the vertices() method on the quiver.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: P = Q.P(GF(2), 1)
sage: P.dimension_vector()
(1, 2)
sage: Q.vertices()
[1, 2]
sage: P.set_space(1, GF(2)^4)
sage: P.dimension_vector()
(4, 2)
"""
return tuple(self._spaces[x].dimension() for x in self._quiver)
def is_zero(self):
"""
Tests whether the representation is zero.
OUTPUT:
- bool
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: M = QuiverRep(ZZ, Q)
sage: N = QuiverRep(ZZ, Q, {1: 1})
sage: M
Representation with dimension vector (0, 0)
sage: N
Representation with dimension vector (1, 0)
sage: M.is_zero()
True
sage: N.is_zero()
False
"""
return self.dimension() == 0
def is_simple(self):
"""
Tests whether the representation is simple.
OUTPUT:
- bool
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: Q.P(RR, 1).is_simple()
False
sage: Q.S(RR, 1).is_simple()
True
"""
# A module for an acyclic quiver is simple if and only if it has total
# dimension 1.
return self.dimension() == 1
def is_semisimple(self):
"""
Tests whether the representation is semisimple.
OUTPUT:
- bool
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: M = Q.P(QQ, 1)
sage: (M/M.radical()).is_semisimple()
True
"""
# A quiver representation is semisimple if and only if the zero map is
# assigned to each edge.
for x in self._quiver.edges():
if self._maps[x] != 0:
return False
return True
def an_element(self):
"""
Returns an element of self.
OUTPUT:
- QuiverRepElement
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: M = Q.P(QQ, 1)
sage: M.an_element()
Element of quiver representation
"""
# Here we just the the an_element function from each space.
elements = dict((v, self._spaces[v].an_element()) for v in self._quiver)
return QuiverRepElement(self, elements)
def zero_element(self):
"""
Returns the zero element.
OUTPUT:
- QuiverRepElement
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: M = Q.P(QQ, 1)
sage: M.zero_element().is_zero()
True
"""
# If we don't specify elements this constructor automatically returns
# the zero element.
return QuiverRepElement(self)
def support(self):
"""
Returns the support of self as a list.
OUTPUT:
- list, the vertices of the representation that have nonzero spaces associated
to them
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a']}, 3:{2:['b'], 4:['c']}})
sage: M = Q.P(QQ, 3)
sage: M
Representation with dimension vector (0, 1, 1, 1)
sage: M.support()
[2, 3, 4]
"""
sup = []
for v in self._quiver:
if self._spaces[v].dimension() != 0:
sup.append(v)
return sup
def gens(self, names='v'):
"""
Returns a list of generators.
INPUT:
- ``names`` - an iterable variable of length equal to the number of generators or a
string (default: 'v'), gives the names of the generators either by giving a
name to each generator or by giving a name to which an index will be appended
OUTPUT:
- list of QuiverRepElement objects, the linear generators of the module (over
the base ring)
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: M = Q.P(QQ, 1)
sage: M.gens()
[v_0, v_1, v_2]
If a string is given then it is used as the name of each generator, with the
index of the generator appended in order to differentiate them::
sage: M.gens('generator')
[generator_0, generator_1, generator_2]
If a list or other iterable variable is given then each generator is named
using the appropriate entry. The length of the variable must equal the number
of generators (the dimension of the module)::
sage: M.gens(['w', 'x', 'y', 'z'])
Traceback (most recent call last):
...
TypeError: can only concatenate list (not "str") to list
sage: M.gens(['x', 'y', 'z'])
[x, y, z]
Strings are iterable, so if the length of the string is equal to the number of
generators then the characters of the string will be used as the names::
sage: M.gens('xyz')
[x, y, z]
"""
# Use names as a list if and only if it is the correct length
uselist = (len(names) == self.dimension())
i = 0
# Create bases for each space and construct QuiverRepElements from
# them.
basis = []
for v in self._quiver:
for m in self._spaces[v].gens():
if uselist:
basis.append(QuiverRepElement(self, {v: m},names[i]))
else:
basis.append(QuiverRepElement(self, {v: m},names + "_" + str(i)))
i += 1
return basis
def coordinates(self, vector):
"""
Returns the coordinates when vector is expressed in terms of the gens.
INPUT:
- ``vector`` - QuiverRepElement
OUTPUT:
- list, the coefficients when the vector is expressed as a linear combination
of the generators of the module
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: M = Q.P(QQ, 1)
sage: x, y, z = M.gens('xyz')
sage: M.coordinates(x - y + z)
[1, -1, 1]
sage: M.coordinates(M.an_element())
[1, 1, 0]
sage: M.an_element() == x + y
True
"""
# Just use the coordinates functions on each space
coords = []
for v in self._quiver:
coords += self._spaces[v].coordinates(vector._elems[v])
return coords
###########################################################################
# #
# CONSTRUCTION FUNCTIONS #
# These functions create and return submodules and homomorphisms. #
# #
###########################################################################
def submodule(self, elements=[], spaces=None):
"""
Returns the submodule generated by the data.
INPUT:
- ``elements`` - a collection of QuiverRepElements (default: empty list), each
should be an element of self
- ``spaces`` - dictionary (default: empty), this dictionary should contain entries
of the form {v: S} where v is a vertex of the quiver and S is a subspace of
the vector space associated to v
OUTPUT:
- QuiverRep, the smallest subspace of self containing the given elements and
the given subspaces
.. NOTE::
This function returns only a QuiverRep object ``sub``. The inclusion map
of ``sub`` into ``M``=self can be obtained by calling ``sub.canonical_map(M)``.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep
sage: Q = Quiver({1:{3:['a']}, 2:{3:['b']}})
sage: M = QuiverRep(QQ, Q, {1: 2, 2: 3, 3: 2})
sage: M.set_map((1, 3, 'a'), 1)
sage: M.set_map((2, 3, 'b'), [[1, 0], [0, 0], [0, 0]])
sage: v = M.an_element()
sage: M.submodule([v])
Representation with dimension vector (1, 1, 1)
The smallest submodule containing the vector space at vertex 1 also contains
the image of the rank 1 homomorphism associated to the edge (1, 3, 'a')::
sage: M.submodule(spaces={1: QQ^2})
Representation with dimension vector (2, 0, 2)
The smallest submodule containing the vector space at vertex 2 also contains
the entire vector space associated to vertex 3 because there is an isomorphism
associated to the edge (2, 3, 'b')::
sage: M.submodule(spaces={2: QQ^3})
Representation with dimension vector (0, 3, 1)
As v is not already contained in this submodule adding it as a generator yields
a larger submodule::
sage: v.support()
[1, 2, 3]
sage: M.submodule([v], {2: QQ^3})
Representation with dimension vector (1, 3, 1)
Giving no generating data yields the zero submodule::
sage: M.submodule().is_zero()
True
If the given data generates all of M then the result is M::
sage: M.submodule(M.gens()) is M
True
"""
if spaces is None:
spaces = {}
# For each vertex generate a submodule from the given data
dim = old_dim = 0
for v in self._quiver:
#Start with the zero submodule if no space is specified
if v not in spaces:
spaces[v] = self._spaces[v].zero_submodule()
# Sum this with the submodule generated by the given elements.
# Note that we are only taking the part of the element at the
# vertex v. We can always multiply an element of a quiver
# representation by a constant path so we don't need to worry about
# subspaces being embedded diagonally across multiple vertices.
spaces[v] += self._spaces[v].submodule([m._elems[v] for m in elements])
dim += spaces[v].dimension()
#Now to enlarge the subspace to a submodule we sum a subspace at a
# vertex with the images of the subspaces at adjacent vertices. The
# dimension of the subspace will strictly increase until we generate a
# submodule. At that point the dimension stabilizes and we can exit
# the loop.
while old_dim != dim:
old_dim, dim = dim, 0
# First sum the subspaces
for e in self._quiver.edges():
spaces[e[1]] += self._maps[e](spaces[e[0]])
# Then get the resulting dimensions
for v in self._quiver:
dim += spaces[v].dimension()
# Return self if the entire module was generated, otherwise return a
# submodule
if dim == self.dimension():
return self
else:
return self._submodule(spaces)
def quotient(self, sub, check=True):
"""
Returns the quotient of self by the submodule sub.
INPUT:
- ``sub`` - QuiverRep, this must be a submodule of self, meaning the space
associated to each vertex v of sub is a subspace of the space associated to v
in self and the map associated to each edge e of sub is the restriction of
the map associated to e in self
- ``check`` - bool, if True then sub is checked to verify that it is indeed a
submodule of self and an error is raised if it is not
OUTPUT:
- QuiverRep, the quotient module self/sub
.. NOTE::
This function returns only a QuiverRep object ``quot``. The inclusion map of
``quot`` into ``M``=self can be obtained by calling ``quot.canonical_map(M)``.
EXAMPLES:
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c']}})
sage: M = Q.I(GF(3), 3)
sage: N = Q.S(GF(3), 3)
sage: M.quotient(N)
Representation with dimension vector (2, 1, 0)
sage: M.quotient(M.radical())
Representation with dimension vector (2, 0, 0)
"""
# First form the quotient space at each vertex
spaces = {}
for v in self._quiver:
spaces[v] = self._spaces[v].quotient(sub._spaces[v], check)
# Check the maps of sub if desired
if check:
for e in self._quiver.edges():
for x in sub._spaces[e[0]].gens():
if sub._maps[e](x) != self._maps[e](x):
raise ValueError("The quotient method was not passed a submodule.")
# Then pass the edge maps to the quotient
maps = {}
for e in self._quiver.edges():
# Sage can automatically coerce an element of a module to an
# element of a quotient of that module but not the other way
# around. So in order to pass a map to the quotient we need to
# construct the quotient map for the domain so that we can take
# inverse images to lift elments. As sage can coerce to a quotient
# this is easy, we just send generators to themselves and set the
# domain to be the quotient.
factor = self._spaces[e[0]].hom(self._spaces[e[0]].gens(), spaces[e[0]])
# Now we create a homomorphism by specifying the images of
# generators. Each generator is lifted to the original domain and
# mapped over using the original map. The codomain is set as the
# quotient so sage will take care of pushing the result to the
# quotient in the codomain.
maps[e] = spaces[e[0]].hom([self._maps[e](factor.lift(x)) for x in spaces[e[0]].gens()], spaces[e[1]])
return QuiverRep(self._base_ring, self._quiver, spaces, maps)
def socle(self):
"""
The socle of self.
OUTPUT:
- QuiverRep, the socle
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c']}})
sage: M = Q.P(QQ, 1)
sage: M.socle()
Representation with dimension vector (0, 0, 2)
"""
# The socle of a representation is the intersection of the kernels of
# all the edge maps. The empty intersection is defined to be the
# entire space so this is what we start with.
spaces = self._spaces.copy()
for e in self._quiver.edges():
spaces[e[0]] = spaces[e[0]].intersection(self._maps[e].kernel())
return self._submodule(spaces)
def radical(self):
"""
Returns the Jacobson radical of self.
OUTPUT:
- QuiverRep, the socle
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c']}})
sage: M = Q.P(QQ, 1)
sage: M.radical()
Representation with dimension vector (0, 2, 2)
"""
#The Jacobson radical of a representation is the sum of the images of
# all of the edge maps. The empty sum is defined to be zero so this is
# what we start with.
spaces = dict((v, self._spaces[v].zero_submodule()) for v in self._quiver)
for e in self._quiver.edges():
spaces[e[1]] += self._maps[e].image()
return self._submodule(spaces)
def top(self):
"""
Returns the top of self.
OUTPUT:
- QuiverRep, the quotient of self by its radical
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c']}})
sage: M = Q.P(QQ, 1)
sage: M.top()
Representation with dimension vector (1, 0, 0)
sage: M.top() == M/M.radical()
True
"""
return self.quotient(self.radical())
def zero_submodule(self):
"""
Returns the zero submodule.
OUTPUT:
- QuiverRep, the quotient of self by its radical
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c']}})
sage: M = Q.P(QQ, 1)
sage: M.zero_submodule()
Representation with dimension vector (0, 0, 0)
sage: M.zero_submodule().is_zero()
True
"""
# When no data is specified this constructor automatically returns the
# zero submodule
return self._submodule()
def hom(self, codomain, maps):
"""
Returns a homomorphism from self to codomain defined by maps.
For more information see the QuiverRepHom documentation.
"""
return QuiverRepHom(self, codomain, maps)
def canonical_map(self, codomain):
"""
Induces a canonical map from self to codomain.
INPUT:
- ``codomain`` - QuiverRep, the codomain of the map
OUTPUT:
- QuiverRepHom, a map from self to the codomain
.. NOTES::
The map is defined by letting sage coerce elements of the spaces of self
into elements of the spaces of codomain. This is guarenteed to produce a
well defined homomorphism in the case where self was created as a submodule
of the codomain, when the codomain was created as a factor module of self,
or when the codomain is zero. In other cases this may still create a
homomorphism, but if sage does not know how to perform the coercion or if
the resulting map is not a homomorphism then an error is raised.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c']}})
sage: M = Q.P(QQ, 1)
sage: S = M.radical()
sage: S.canonical_map(M)
Homomorphism of representations of Quiver on 3 vertices
sage: M.canonical_map(M/S)
Homomorphism of representations of Quiver on 3 vertices
In this example sage coerces a map but the result is not a homomorphism::
sage: M.canonical_map(S)
Traceback (most recent call last):
...
ValueError: The diagram of edge (1, 2, 'a') does not commute.
In this example sage cannot coerce a map::
sage: N = Q.P(QQ, 3)
sage: M.canonical_map(N)
Traceback (most recent call last):
...
TypeError: entries has the wrong length
"""
maps = {}
for v in self._quiver:
if codomain._spaces[v].dimension() == 0:
# Create the zero map if the codomain is zero
maps[v] = self._spaces[v].hom(self._base_ring(0), codomain._spaces[v])
else:
# Otherwise let sage try and coerce the generators into the codomain
maps[v] = self._spaces[v].hom(self._spaces[v].gens(), codomain._spaces[v])
return QuiverRepHom(self, codomain, maps)
def D(self):
"""
Computes the dual DM = Hom_k(M, k) of the module M=self over the base ring k.
OUTPUT:
- QuiverRep, the dual representation
.. NOTES::
If e is an edge of the quiver Q then we let (fe)(m) = f(me). This gives DM
a module structure over the opposite quiver Q.reverse().
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c']}})
sage: M = Q.P(QQ, 1)
sage: M.D()
Representation with dimension vector (1, 2, 2)
sage: M.D().quiver() == Q.reverse()
True
"""
# This module is formed by taking the transpose of the edge maps.
spaces = self._spaces.copy()
maps = dict(((e[1], e[0], e[2]), self._spaces[e[1]].hom(self._maps[e].matrix().transpose(), self._spaces[e[0]]))
for e in self._quiver.edges())
return QuiverRep(self._base_ring, self._quiver.reverse(), spaces, maps)
def Hom(self, codomain):
"""
Returns the hom space from self to codomain.
For more information see the QuiverHomSpace documentation.
"""
return QuiverHomSpace(self, codomain)
###########################################################################
# #
# ADDITIONAL OPERATIONS #
# These functions operations that are not implemented via binary #
# operators. #
# #
###########################################################################
def right_edge_action(self, element, edge):
"""
Returns the result of element*edge.
INPUT:
- ``element`` - QuiverRepElement, an element of self
- ``edge`` - an edge of the quiver (a tuple) or a list of edges in the quiver.
Such a list can be empty and can contain trivial paths (tuples of the form
(v, v) where v is a vertex of the quiver)
OUTPUT:
- QuiverRepElement, the result of element*edge when edge is considered an
element of the path algebra of the quiver
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a','b']}, 2:{3:['c']}})
sage: M = Q.P(QQ, 1)
sage: v = M.an_element()
sage: v.support()
[1, 2, 3]
sage: M.right_edge_action(v, (1, 1)).support()
[1]
sage: M.right_edge_action(v, [(1, 1)]).support()
[1]
sage: M.right_edge_action(v, [(1, 1), (2, 2)]).support()
[]
sage: M.right_edge_action(v, [(1, 1), (1, 2, 'a')]).support()
[2]
sage: M.right_edge_action(v, (1, 2, 'a')) == M.right_edge_action(v, [(1, 1), (1, 2, 'a'), (2, 2)])
True
"""
# Deal with lists by calling this function recursively
if isinstance(edge, list):
if len(edge) == 0:
return element;
else:
return self.right_edge_action(self.right_edge_action(element, edge[0]), edge[1:])
#After acting by an edge an element is only supported in one vertex so
# start with the zero vector and add in that vertex.
result = self.zero_element()
if edge[0] == edge[1]:
#Acting by a trivial path
result._elems[edge[0]] = element._elems[edge[0]]
else:
#Acting by an edge
result._elems[edge[1]] = self.get_map(edge)(element._elems[edge[0]])
return result
class QuiverRep_with_path_basis(QuiverRep):
"""
The basis of the module must be closed under right multiplication by an
edge; that is, appending any edge to the end of any path in the basis must
result in either an invalid path or a valid path also contained in the
basis of the module. If the supplied basis is not closed under right
multiplication then the resulting module will have as basis the closure of
the supplied list of paths.
INPUT:
- ``k`` - ring, the base ring of the representation
- ``Q`` - Quiver, the quiver of the representation
- ``basis`` - list (default: empty), should be a list of paths (also lists) in the
quiver Q. Entries that do not represent valid paths are ignored and duplicate
paths are deleted. The closure of this list under right multiplication forms the
basis of the resulting representation.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep_with_path_basis
sage: Q1 = Quiver({1:{2:['a']}})
sage: P1 = QuiverRep_with_path_basis(QQ, Q1, [[(1, 1)]])
sage: P1.dimension()
2
sage: kQ = QuiverRep_with_path_basis(QQ, Q1, [[(1, 1)], [(2, 2)], [(1, 1), (1, 2, 'a'), (2, 2)], [(1, 2, 'a')]])
sage: kQ.dimension()
3
sage: Q2 = Quiver({1:{2:['a'], 3:['b', 'c']}, 2:{3:['d']}})
sage: M = QuiverRep_with_path_basis(QQ, Q2, [[(2, 2)], [(1, 2, 'a')]])
sage: M.dimension_vector()
(0, 2, 2)
sage: N = QuiverRep_with_path_basis(QQ, Q2, [[(2, 2)], [(1, 2, 'a'), (2, 3, 'd')]])
sage: N.dimension_vector()
(0, 1, 2)
"""
# This class implements quiver representations whose bases correspond to
# paths in the path algebra and whose maps are path multiplication. The
# main advantage to having such a basis is that a homomorphism can be
# defined by giving a single element in the codomain. This class derives
# from the QuiverRep class and the following methods and private variables
# have been added:
#
# * _bases
# A dictionary associating to each vertex a list of paths (also lists)
# which correspond to the basis elements of the space assigned to that
# vertex. Each list must be of positive length so trivial paths are
# represented by a list containing the "edge" (vertex, vertex).
# * _create_map_matrix
# Creates the matrix of an edge map.
def __init__(self, k, Q, basis):
self._quiver = Q
self._base_ring = k
# Add paths to the basis dictionary, the terminal vertex is the key.
# At least one path must be added to generate a basis
valid_input = False
self._bases = dict((v, []) for v in Q)
for path in basis:
# If consecutive edges don't match then the path is zero, don't add
# to basis
good_path = True
for i in range(1, len(path)):
if path[i - 1][1] != path[i][0]:
good_path = False
break
if not good_path:
continue
# Add the path
self._bases[path[-1][1]].append(path[:])
valid_input = True
# Check that the basis was valid
if not valid_input:
raise ValueError("The basis contains no valid paths in the quiver.")
# Delete unnecessary (v, v) edges from paths
for v in Q:
for path in self._bases[v]:
i = 0
while i < len(path):
# Make sure you don't delete the last edge in a trivial
# path
if len(path) != 1 and path[i][0] == path[i][1]:
del path[i]
else:
i += 1
# Delete duplicates
for v in Q:
self._bases[v] = sorted(self._bases[v])
i = 1
while i < len(self._bases[v]):
if self._bases[v][i - 1] == self._bases[v][i]:
self._bases[v].pop(i)
else:
i += 1
# Add additional paths as needed to generate a valid basis
for v in Q:
for path in self._bases[v]:
for path2 in Q.all_edge_paths(path[-1][1]):
# Multiplying by the trivial path does nothing.
if path2[0][0] == path2[0][1]:
continue
# Multiplying a trivial path by p yields p
elif path[0][0] == path[0][1]:
if path2 not in self._bases[path2[-1][1]]:
self._bases[path2[-1][1]].append(path2)
# Otherwise we're appending two non-trivial paths, make
# sure the result is in the basis.
elif path + path2 not in self._bases[path2[-1][1]]:
self._bases[path2[len(path2) - 1][1]].append(path + path2)
# Create the matrixes of the maps
maps = {}
for e in Q.edges():
# Start with the zero matrix and fill in from there
mat = [[self._base_ring(0)]*len(self._bases[e[1]]) for i in range(0, len(self._bases[e[0]]))]
for i in range(0, len(self._bases[e[0]])):
# Get the new path, remember that the result of multiplying a
# trivial path by an e is the e
if self._bases[e[0]][i][0][0] == self._bases[e[0]][i][0][1]:
new_path = [e]
else:
new_path = self._bases[e[0]][i] + [e]
# Add an entry to the matrix coresponding to where the new path is found
j = self._bases[e[1]].index(new_path)
mat[i][j] = self._base_ring(1)
# Add the matrix
maps[e] = Matrix(len(self._bases[e[0]]), len(self._bases[e[1]]), mat)
# Create the spaces and then the representation
spaces = dict((v, len(self._bases[v])) for v in Q)
super(QuiverRep_with_path_basis, self).__init__(k, Q, spaces, maps)
def _deconvert(self):
"""
Makes self a QuiverRep class and no longer a QuiverRep_with_path_basis.
TESTS::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRep_with_path_basis
sage: Q = Quiver({1:{2:['a']}})
sage: P = QuiverRep_with_path_basis(QQ, Q, [[(2, 2)]])
sage: type(P) == QuiverRep_with_path_basis
True
sage: P._deconvert()
sage: type(P) == QuiverRep
True
sage: P.is_left_module()
Traceback (most recent call last):
...
AttributeError: 'QuiverRep' object has no attribute 'is_left_module'
"""
del self._bases
self.__class__ = QuiverRep
def is_left_module(self):
"""
Tests whether the basis is closed under left multiplication.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep_with_path_basis
sage: Q1 = Quiver({1:{2:['a']}})
sage: P2 = QuiverRep_with_path_basis(QQ, Q1, [[(2, 2)]])
sage: P2.is_left_module()
False
sage: kQ = QuiverRep_with_path_basis(QQ, Q1, [[(1, 1)], [(2, 2)]])
sage: kQ.is_left_module()
True
sage: Q2 = Quiver({1:{2:['a'], 3:['b', 'c']}, 2:{3:['d']}})
sage: M = QuiverRep_with_path_basis(QQ, Q2, [[(2, 2)], [(1, 2, 'a')]])
sage: M.is_left_module()
True
sage: N = QuiverRep_with_path_basis(QQ, Q2, [[(2, 2)], [(1, 2, 'a'), (2, 3, 'd')]])
sage: N.is_left_module()
False
"""
for v in self._quiver:
for path in self._bases[v]:
for e in self._quiver.edges():
# Skip if mult is trivial
if e[1] != path[0][0]:
continue
# If it's a trivial path test if the edge is a basis
if path[0][0] == path[0][1]:
if [e] not in self._bases[e[1]]:
return False
# Otherwise add the edge to the beginning and test
elif [e] + path not in self._bases[path[-1][1]]:
return False
# All tests passed
return True
def set_map(self, edge, map):
"""
Sets the map at the given edge.
INPUT:
- ``edge`` - tuple of the form (initial vertex, terminal vertex, label) specifying
the edge to be set
- ``map`` - FreeModuleMorphism with the appropriate domain and codomain or an
element that the FreeModuleMorphism constructor recognizes as input in order
to create a FreeModuleMorphism, for example a matrix of the appropriate
dimensions
.. WARNING::
After calling this function the module will no longer be of type
QuiverRep_with_path_basis, it will be a QuiverRep.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRep_with_path_basis
sage: Q = Quiver({1:{2:['a']}})
sage: P = QuiverRep_with_path_basis(QQ, Q, [[(1, 1)]])
sage: type(P) == QuiverRep
False
sage: type(P) == QuiverRep_with_path_basis
True
sage: P.get_map((1, 2, 'a'))
Free module morphism defined by the matrix
[1]
Domain: Vector space of dimension 1 over Rational Field
Codomain: Vector space of dimension 1 over Rational Field
sage: P.set_map((1, 2, 'a'), 0)
sage: P.get_map((1, 2, 'a'))
Free module morphism defined by the matrix
[0]
Domain: Vector space of dimension 1 over Rational Field
Codomain: Vector space of dimension 1 over Rational Field
sage: type(P) == QuiverRep
True
sage: type(P) == QuiverRep_with_path_basis
False
"""
super(QuiverRep_with_path_basis, self).set_map(edge, map)
self._deconvert()
def set_space(self, vertex, space):
"""
Sets the free module at the given vertex to be the given space.
INPUT:
- ``vertex`` - integer, a vertex of the quiver of the module
- ``space`` - free module over the base ring
.. NOTES::
Any edges into or out of the vertex are given the zero morphism. This
ensures that self remains a valid quiver representation.
.. WARNING::
After calling this function the module will no longer be of type
QuiverRep_with_path_basis, it will be a QuiverRep.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRep_with_path_basis
sage: Q = Quiver({1:{2:['a']}})
sage: P = QuiverRep_with_path_basis(QQ, Q, [[(1, 1)]])
sage: type(P) == QuiverRep
False
sage: type(P) == QuiverRep_with_path_basis
True
sage: P.get_space(2)
Vector space of dimension 1 over Rational Field
sage: P.set_space(2, QQ^0)
sage: P.get_space(2)
Vector space of dimension 0 over Rational Field
sage: type(P) == QuiverRep
True
sage: type(P) == QuiverRep_with_path_basis
False
"""
super(QuiverRep_with_path_basis, self).set_space(vertex, space)
self._deconvert()
class QuiverHomSpace(SageObject):
"""
A homomorphism of quiver representations is for each vertex of the quiver a
homomorphism of the spaces assigned to those vertices such that these
homomorphisms commute with the edge maps. This class handles the set of all
such maps, Hom_Q(M, N).
INPUT:
- ``domain`` - QuiverRep, the domain of the homomorphism space
- ``codomain`` - QuiverRep, the codomain of the homomorphism space
OUTPUT:
- QuiverHomSpace, the homomorphism space Hom_Q(domain, codomain)
.. NOTES::
The quivers of the domain and codomain must be equal or a ValueError is
raised.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverHomSpace
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: H = QuiverHomSpace(Q.S(QQ, 2), Q.P(QQ, 1))
sage: H.dimension()
2
sage: H.gens()
[Homomorphism of representations of Quiver on 2 vertices, Homomorphism of representations of Quiver on 2 vertices]
"""
###########################################################################
# #
# PRIVATE FUNCTIONS #
# These functions are not meant to be seen by the end user. #
# #
###########################################################################
def __init__(self, domain, codomain):
# The data in the class is stored in the following private variables:
#
# * _base_ring
# The base ring of the representations M and N.
# * _codomain
# The QuiverRep object of the codomain N.
# * _domain
# The QuiverRep object of the domain M.
# * _quiver
# The quiver of the representations M and N.
# * _space
# A free module with ambient space.
#
# The free module _space is the homomorphism space. The ambient space
# is k^n where k is the base ring and n is the sum of the dimensions of
# the spaces of homomorphisms between the free modules attached in M
# and N to the vertices of the quiver. Each coordinate represents a
# single entry in one of those matrices.
# Get the quiver and base ring and check they they are the same for
# both modules
self._quiver = domain._quiver
self._base_ring = domain._base_ring
self._domain = domain
self._codomain = codomain
if self._quiver != codomain._quiver:
raise ValueError("Representations are not over the same base ring.")
if self._base_ring != codomain._base_ring:
raise ValueError("Representations are not over the same base ring.")
# To compute the Hom Space we set up a 'generic' homomorphism where the
# maps at each vertex are described by matrices whose entries are
# variables. Then the commutativity of edge diagrams gives us a
# system of equations whose solution space is the Hom Space we're
# looking for. The variables will be numbered consecutively starting
# at 0, ordered first by the vertex the matrix occurs at, then by row
# then by column. We'll have to keep track of which variables
# correspond to which matrices.
# eqs will count the number of equations in our system of equations,
# varstart will be a dictionary that associates to each vertex the
# number of the variable located at (0, 0) in the matrix assigned to
# that vertex.
eqs = 0
verts = domain._quiver.num_verts()
varstart = {1: 0}
# First assign to varstart the dimension of the matrix assigned to the
# previous vertex.
for v in domain._quiver:
varstart[v+1] = domain._spaces[v].dimension()*codomain._spaces[v].dimension()
for e in domain._quiver.edges():
eqs += domain._spaces[e[0]].dimension()*codomain._spaces[e[1]].dimension()
# After this cascading sum varstart[v] will be the sum of the
# dimensions of the matrixes assigned to vertices ordered before v.
# This is equal to the number of the first variable assigned to v.
for i in range(3, verts + 2):
varstart[i] += varstart[i-1]
# This will be the coefficient matrix for the system of equations. We
# start with all zeros and will fill in as we go. We think of this
# matrix as acting on the right so the columns correspond to equations,
# the rows correspond to variables, and .kernel() will give a right
# kernel as is needed.
coef_mat = [[domain._base_ring(0)]*eqs for i in range(0, varstart[verts+1])]
# row keeps track of what equation we are on. If the maps X and Y are
# assigned to an edge e and A and B are the matrices of variables that
# describe the generic maps between the initial and final vertices of e
# then commutativity of the edge diagram is described by the equation
# AY = XB, or
#
# Sum_k A_ik*Y_kj - Sum_k X_ikB_kj == 0 for all i and j.
#
# Below we loop through these values of i,j,k and write the
# coefficients of the equation above into the coefficient matrix.
eqn = 0
for e in domain._quiver.edges():
X = domain._maps[e].matrix()
Y = codomain._maps[e].matrix()
for i in range(0, X.nrows()):
for j in range(0, Y.ncols()):
for k in range(0, Y.nrows()):
coef_mat[varstart[e[0]] + i*Y.nrows() + k][eqn] = Y[k][j]
for k in range(0, X.ncols()):
coef_mat[varstart[e[1]] + k*Y.ncols() + j][eqn] = -X[i][k]
eqn += 1
# Now we can create the hom space
self._space = Matrix(domain._base_ring, coef_mat).kernel()
def _repr_(self):
return "Dimension " + str(self._space.dimension()) + " QuiverHomSpace"
def __contains__(self, map):
# This overloads the in operator
# First check the type
if not isinstance(map, QuiverRepHom):
return False
# Then check the quivers, domain, and codomain
if self._quiver != map._quiver or self._domain != map._domain or self._codomain != map._codomain:
return False
# Finally check the vector
return map._vector in self._space
###########################################################################
# #
# ACCESS FUNCTIONS #
# These functions are used to view and modify the representation data. #
# #
###########################################################################
def base_ring(self):
"""
Returns the base ring of the representations.
OUTPUT:
- ring, the base ring of the representations
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverHomSpace
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: H = QuiverHomSpace(Q.S(QQ, 2), Q.P(QQ, 1))
sage: H.base_ring()
Rational Field
"""
return self._base_ring
def quiver(self):
"""
Returns the quiver of the representations.
OUTPUT:
- Quiver, the quiver of the representations
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverHomSpace
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: H = QuiverHomSpace(Q.S(QQ, 2), Q.P(QQ, 1))
sage: H.quiver() is Q
True
"""
return self._quiver
def domain(self):
"""
Returns the domain of the hom space.
OUTPUT:
- QuiverRep, the domain of the Hom space
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverHomSpace
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: S = Q.S(QQ, 2)
sage: H = QuiverHomSpace(S, Q.P(QQ, 1))
sage: H.domain() is S
True
"""
return self._domain
def codomain(self):
"""
Returns the codomain of the hom space.
OUTPUT:
- QuiverRep, the codomain of the Hom space
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverHomSpace
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: P = Q.P(QQ, 1)
sage: H = QuiverHomSpace(Q.S(QQ, 2), P)
sage: H.codomain() is P
True
"""
return self._codomain
###########################################################################
# #
# DATA FUNCTIONS #
# These functions return data collected from the representation. #
# #
###########################################################################
def dimension(self):
"""
Returns the dimension of the hom space.
OUTPUT:
- integer, the dimension
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverHomSpace
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: H = QuiverHomSpace(Q.S(QQ, 2), Q.P(QQ, 1))
sage: H.dimension()
2
"""
return self._space.dimension()
def gens(self):
"""
Returns a list of generators of the hom space
OUTPUT:
- list of QuiverRepHom objects, the generators
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverHomSpace
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: H = QuiverHomSpace(Q.S(QQ, 2), Q.P(QQ, 1))
sage: H.gens()
[Homomorphism of representations of Quiver on 2 vertices, Homomorphism of representations of Quiver on 2 vertices]
"""
return [QuiverRepHom(self._domain, self._codomain, f) for f in self._space.gens()]
def coordinates(self, map):
"""
Returns the coordinates of the map when expressed in terms of gens.
INTPUT:
- ``map`` - QuiverRepHom
OUTPUT:
- list, the coordinates of the given map when written in terms of the
generators of the QuiverHomSpace
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverHomSpace
sage: Q = Quiver({1:{2:['a', 'b']}})
sage: S = Q.S(QQ, 2)
sage: P = Q.P(QQ, 1)
sage: H = QuiverHomSpace(S, P)
sage: x, y = H.gens()
sage: f = S.hom(P, {2: [[1,-1]]})
sage: H.coordinates(f)
[1, -1]
"""
#Use the coordinates function on space
return self._space.coordinates(map._vector)
class QuiverRepHom(SageObject):
"""
A homomorphism of quiver representations is for each vertex of the quiver a
homomorphism of the spaces assigned to those vertices such that these
homomorphisms commute with the edge maps. The domain and codomain of the
homomorphism are required to be representations over the same quiver with
the same base ring.
INPUT:
- ``domain`` - QuiverRep, the domain of the homomorphism
- ``codomain`` - QuiverRep, the codomain of the homomorphism
- ``maps`` dict (default: empty), a dictionary associating to each vertex of the
quiver a homomorphism with domain and codomain the spaces associated to this
vertex in the domain and codomain modules respectively, or a matrix defining
such a homomorphism, or an object that sage can construct such a matrix from.
Not all vertices must be specified, unspecified vertices are assigned the
zero map. Keys not corresponding to vertices of the quiver are ignored.
OUTPUT:
- QuiverRepHom
.. NOTES::
The initial input must specify a valid homomorphism of representations (the
edge diagrams must commute). When set_map is used to alter the maps at
certain it is not always possible to do this in a way that produces a valid
homomorphism at every step, so set_map does not check that the result is a
well defined homomorphism. It only checks that the dimension of the domain
and codomain is correct. After all changes have been made use valid_hom()
and assert_valid_hom() to ensure that the end result is a well defined
homomorphism.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: spaces = {1: QQ^2, 2: QQ^2, 3:QQ^1}
sage: maps = {(1, 2, 'a'): [[1, 0], [0, 0]], (1, 2, 'b'): [[0, 0], [0, 1]], (2, 3, 'c'): [[1], [1]]}
sage: M = QuiverRep(QQ, Q, spaces, maps)
sage: spaces2 = {2: QQ^1, 3: QQ^1}
sage: S = QuiverRep(QQ, Q, spaces2)
sage: f = QuiverRepHom(S, M)
sage: f.is_zero()
True
sage: maps2 = {2:[1, -1], 3:1}
sage: g = QuiverRepHom(S, M, maps2)
sage: g.valid_hom()
True
sage: g.set_map(2, [1, 0])
sage: g.valid_hom()
False
sage: g.assert_valid_hom()
Traceback (most recent call last):
...
ValueError: The diagram of edge (2, 3, 'c') does not commute.
"""
###########################################################################
# #
# PRIVATE FUNCTIONS #
# These functions are not meant to be seen by the end user. #
# #
###########################################################################
def __init__(self, domain, codomain, maps={}):
# The data of a representation is held in the following private
# variables:
#
# * _quiver
# The quiver of the representation.
# * _base_ring
# The base ring of the representation.
# * _domain
# The QuiverRep object that is the domain of the homomorphism.
# * _codomain
# The QuiverRep object that is the codomain of the homomorphism.
# * _vector
# A vector in some free module over the base ring of a length such
# that each coordinate corresponds to an entry in the matrix of a
# homomorphism attached to a vertex.
#
# The variable maps can also be a vector of appropriate length. When
# this is the case it will be loaded directly into _vector and then
# assert_valid_hom is called.
self._domain = domain
self._codomain = codomain
self._quiver = domain._quiver
self._base_ring = domain._base_ring
# Check that the quiver and base ring match
if codomain._quiver != self._quiver:
raise ValueError("The quivers of the domain and codomain must be equal.")
if codomain._base_ring != self._base_ring:
raise ValueError("The base ring of the domain and codomain must be equal.")
# Get the dimensions of the spaces
mat_dims = {}
domain_dims = {}
codomain_dims = {}
for v in self._quiver:
domain_dims[v] = domain._spaces[v].dimension()
codomain_dims[v] = codomain._spaces[v].dimension()
mat_dims[v] = domain_dims[v]*codomain_dims[v]
total_dim = sum(mat_dims.values())
# Handle the case when maps is a vector
if maps in self._base_ring**total_dim:
self._vector = maps
self.assert_valid_hom()
return
# Get the coordinates of the vector
vector = []
for v in self._quiver:
if v in maps:
if isinstance(maps[v], FreeModuleMorphism):
m = maps[v].matrix()
else:
m = Matrix(self._base_ring, domain_dims[v], codomain_dims[v], maps[v])
else:
m = Matrix(self._base_ring, domain_dims[v], codomain_dims[v], self._base_ring(0))
for i in range(0, domain_dims[v]):
vector += list(m[i])
# Wrap as a vector and then check it
self._vector = (self._base_ring**total_dim)(vector)
self.assert_valid_hom()
def _repr_(self):
return "Homomorphism of representations of " + self._quiver.__repr__()
def __call__(self, x):
# This function overloads functional notation f(x)
elements = dict((v, x._elems[v]*self.get_matrix(v)) for v in self._quiver)
return QuiverRepElement(self._codomain, elements)
def __add__(left, right):
# This function overloads the + operator
new_vector = left._vector + right._vector
return QuiverRepHom(left._domain, left._codomain, new_vector)
def __iadd__(self, other):
# This function overrides the += operator
self._vector += other._vector
return self
def __sub__(left, right):
# This function overloads the - operator
new_vector = left._vector - right._vector
return QuiverRepHom(left._domain, left._codomain, new_vector)
def __isub__(self, other):
# This function overrides the -= operator
self._vector -= other._vector
return self
def __neg__(self):
# This function overrides the unary - operator
return QuiverRepHom(self._domain, self._codomain, -self._vector)
def __pos__(self):
# This function overrides the unary + operator
return self
def __eq__(self, other):
# This function overrides the == operator
# A homomorphism can only be equal to another homomorphism between the
# same domain and codomain
if not isinstance(other, QuiverRepHom) or self._domain != other._domain or self._codomain != other._codomain:
return False
# If all that holds just check the vectors
return self._vector == other._vector
def __ne__(self, other):
# This function overrides the != operator
# A homomorphism can only be equal to another homomorphism between the
# same domain and codomain
if not isinstance(other, QuiverRepHom) or self._domain != other._domain or self._codomain != other._codomain:
return True
# If all that holds just check the vectors
return self._vector != other._vector
###########################################################################
# #
# WELL DEFINEDNESS FUNCTIONS #
# These functions test and assert well definedness of the #
# homomorphism. #
# #
###########################################################################
def assert_valid_hom(self):
"""
Raises a ValueError if the homomorphism is not well defined.
Specifically it checks that the domain and codomains of the maps are correct
and that the edge diagrams commute.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: spaces = {1: QQ^2, 2: QQ^2, 3:QQ^1}
sage: maps = {(1, 2, 'a'): [[1, 0], [0, 0]], (1, 2, 'b'): [[0, 0], [0, 1]], (2, 3, 'c'): [[1], [1]]}
sage: M = QuiverRep(QQ, Q, spaces, maps)
sage: spaces2 = {2: QQ^1, 3: QQ^1}
sage: S = QuiverRep(QQ, Q, spaces2)
sage: maps2 = {2:[1, -1], 3:1}
sage: g = QuiverRepHom(S, M, maps2)
sage: g.assert_valid_hom()
sage: g.set_map(2, [1, 0])
sage: g.assert_valid_hom()
Traceback (most recent call last):
...
ValueError: The diagram of edge (2, 3, 'c') does not commute.
"""
# Check that the domain and codomains dimensions add correctly
totaldim = 0
for v in self._quiver:
totaldim += self._domain._spaces[v].dimension()*self._codomain._spaces[v].dimension()
if totaldim != len(self._vector):
raise ValueError("Dimensions do not match domain and codomain.")
# Check that the edge diagrams commute
for e in self._quiver.edges():
if self.get_matrix(e[0])*self._codomain._maps[e].matrix() != self._domain._maps[e].matrix()*self.get_matrix(e[1]):
raise ValueError("The diagram of edge " + str(e) + " does not commute.")
def valid_hom(self):
"""
Tests whether the homomorphism is well defined.
Specifically it checks that the domain and codomains of the maps are correct
and that the edge diagrams commute.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: spaces = {1: QQ^2, 2: QQ^2, 3:QQ^1}
sage: maps = {(1, 2, 'a'): [[1, 0], [0, 0]], (1, 2, 'b'): [[0, 0], [0, 1]], (2, 3, 'c'): [[1], [1]]}
sage: M = QuiverRep(QQ, Q, spaces, maps)
sage: spaces2 = {2: QQ^1, 3: QQ^1}
sage: S = QuiverRep(QQ, Q, spaces2)
sage: maps2 = {2:[1, -1], 3:1}
sage: g = QuiverRepHom(S, M, maps2)
sage: g.valid_hom()
True
sage: g.set_map(2, [1, 0])
sage: g.valid_hom()
False
"""
# Check that the domain and codomains dimensions add correctly
totaldim = 0
for v in self._quiver:
totaldim += self._domain._spaces[v].dimension()*self._codomain._spaces[v].dimension()
if totaldim != len(self._vector):
return False
# Check that the edge diagrams commute
for e in self._quiver.edges():
if self.get_matrix(e[0])*self._codomain._maps[e].matrix() != self._domain._maps[e].matrix()*self.get_matrix(e[1]):
return False
return True
###########################################################################
# #
# ACCESS FUNCTIONS #
# These functions are used to view and modify the homomorphism data. #
# #
###########################################################################
def domain(self):
"""
Returns the domain of the homomorphism.
OUTPUT:
- QuiverRep, the domain
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: spaces = {1: QQ^2, 2: QQ^2, 3:QQ^1}
sage: maps = {(1, 2, 'a'): [[1, 0], [0, 0]], (1, 2, 'b'): [[0, 0], [0, 1]], (2, 3, 'c'): [[1], [1]]}
sage: M = QuiverRep(QQ, Q, spaces, maps)
sage: S = QuiverRep(QQ, Q)
sage: g = QuiverRepHom(M, S)
sage: g.domain() is M
True
"""
return self._domain
def codomain(self):
"""
Returns the codomain of the homomorphism.
OUTPUT:
- QuiverRep, the codomain
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: spaces = {1: QQ^2, 2: QQ^2, 3:QQ^1}
sage: maps = {(1, 2, 'a'): [[1, 0], [0, 0]], (1, 2, 'b'): [[0, 0], [0, 1]], (2, 3, 'c'): [[1], [1]]}
sage: M = QuiverRep(QQ, Q, spaces, maps)
sage: S = QuiverRep(QQ, Q)
sage: g = QuiverRepHom(S, M)
sage: g.codomain() is M
True
"""
return self._codomain
def get_matrix(self, vertex):
"""
Returns the matrix of the homomorphism attached to vertex.
INPUT:
- ``vertex`` - integer, a vertex of the quiver
OUTPUT:
- matrix, the matrix representing the homomorphism associated to the given
vertex
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: S = P/P.radical()
sage: f = P.canonical_map(S)
sage: f.get_matrix(1)
[1]
"""
# Get dimensions
startdim = 0
for v in self._quiver:
if v == vertex:
break
startdim += self._domain._spaces[v].dimension()*self._codomain._spaces[v].dimension()
rows = self._domain._spaces[vertex].dimension()
cols = self._codomain._spaces[vertex].dimension()
# Slice out the matrix and return
mat = list(self._vector[startdim:startdim + rows*cols])
return Matrix(self._base_ring, rows, cols, mat)
def get_map(self, vertex):
"""
Returns the homomorphism at the given vertex.
INPUT:
- ``vertex`` - integer, a vertex of the quiver
OUTPUT:
- homomorphism, the homomorphism associated to the given vertex
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: S = P/P.radical()
sage: f = P.canonical_map(S)
sage: f.get_map(1)
Free module morphism defined by the matrix
[1]
Domain: Vector space of dimension 1 over Rational Field
Codomain: Vector space quotient V/W of dimension 1 over Rational Field ...
"""
return self._domain._spaces[vertex].hom(self.get_matrix(vertex), self._codomain._spaces[vertex])
def set_map(self, vertex, map):
"""
Sets the map at the given vertex.
INPUT:
- ``vertex`` - integer, a vertex of the quiver
- ``map`` - homomorphism or matrix or object sage can construct a matrix from, the
map to be associated to the given quiver or a matrix describing that map or an
object from which sage can construct a matrix describing the map
.. NOTES::
Internally only the matrix describing this map is saved so this function
does not check that the domain and codomains match the appropriate spaces,
it only checks that the dimensions of these spaces are correct.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: S = P/P.radical()
sage: f = P.canonical_map(S)
sage: f.set_map(1, 0)
sage: f.get_matrix(1)
[0]
"""
# Get dimensions
startdim = 0
for v in self._quiver:
if v == vertex:
break
startdim += self._domain._spaces[v].dimension()*self._codomain._spaces[v].dimension()
rows = self._domain._spaces[vertex].dimension()
cols = self._codomain._spaces[vertex].dimension()
total = rows*cols
# Get the matrix
if isinstance(map, FreeModuleMorphism):
m = map.matrix()
else:
m = Matrix(self._base_ring, rows, cols, map)
# Collapse it to a vector and assign it
vector = []
for row in m:
vector += list(row)
self._vector[startdim:startdim + total] = vector
def quiver(self):
"""
Return the quiver of the representations in the domain/codomain.
OUTPUT:
- Quiver, the quiver of the representations in the domain and codomain
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: f = QuiverRepHom(P, P, {1: 1, 2: 1, 3: 1})
sage: f.quiver() is Q
True
"""
return self._quiver
def base_ring(self):
"""
Return the base ring of the representation in the codomain.
OUTPUT:
- ring, the base ring of the codomain
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: f = QuiverRepHom(P, P, {1: 1, 2: 1, 3: 1})
sage: f.base_ring() is QQ
True
"""
return self._base_ring
###########################################################################
# #
# DATA FUNCTIONS #
# These functions return data collected from the homomorphism. #
# #
###########################################################################
def is_monomorphism(self):
"""
Tests whether the homomorphism is injective.
OUTPUT:
- bool, True if the homomorphism is injective, False otherwise
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: f = QuiverRepHom(P, P, {1: 1, 2: 1, 3: 1})
sage: f.is_monomorphism()
True
sage: g = QuiverRepHom(P, P)
sage: g.is_monomorphism()
False
"""
# The homomorphism is injective if and only if it is injective at every
# vertex
for v in self._quiver:
if self.get_matrix(v).nullity() != 0:
return False
return True
def is_epimorphism(self):
"""
Tests whether the homomorphism is surjective.
OUTPUT:
- bool, True if the homomorphism is surjective, False otherwise
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: f = QuiverRepHom(P, P, {1: 1, 2: 1, 3: 1})
sage: f.is_epimorphism()
True
sage: g = QuiverRepHom(P, P)
sage: g.is_epimorphism()
False
"""
# The homomorphism is surjective if and only if it is surjective at
# every vertex
for v in self._quiver:
m = self.get_matrix(v)
if m.rank() != m.ncols():
return False
return True
def is_isomorphism(self):
"""
Tests whether the homomorphism is an isomorphism.
OUTPUT:
- bool, True if the homomorphism is bijective, False otherwise
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: f = QuiverRepHom(P, P, {1: 1, 2: 1, 3: 1})
sage: f.is_isomorphism()
True
sage: g = QuiverRepHom(P, P)
sage: g.is_isomorphism()
False
"""
# It's an iso if and only if it's an iso at every vertex
for v in self._quiver:
if not self.get_matrix(v).is_invertible():
return False
return True
def is_zero(self):
"""
Tests whether the homomorphism is the zero homomorphism.
OUTPUT:
- bool, True if the homomorphism is zero, False otherwise
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: f = QuiverRepHom(P, P, {1: 1, 2: 1, 3: 1})
sage: f.is_zero()
False
sage: g = QuiverRepHom(P, P)
sage: g.is_zero()
True
"""
# The homomorphism is zero if and only if it is zero at every vertex
for v in self._quiver:
if not self.get_matrix(v).is_zero():
return False
return True
def is_endomorphism(self):
"""
Tests whether the homomorphism is an endomorphism.
OUTPUT:
- bool, True if the domain equals the codomain, False otherwise
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: f = QuiverRepHom(P, P, {1: 1, 2: 1, 3: 1})
sage: f.is_endomorphism()
True
sage: S = P/P.radical()
sage: g = P.canonical_map(S)
sage: g.is_endomorphism()
False
"""
return self._domain == self._codomain
def rank(self):
"""
Returns the rank.
OUTPUT:
- integer, the rank
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: S = P/P.radical()
sage: f = P.canonical_map(S)
sage: f.rank()
1
"""
# The rank is the sum of the ranks at each vertex
r = 0
for v in self._quiver:
r += self.get_matrix(v).rank()
return r
###########################################################################
# #
# CONSTRUCTION FUNCTIONS #
# These functions create new homomorphisms and representations from #
# the given homomorphism. #
# #
###########################################################################
def kernel(self):
"""
Returns the kernel of self.
OUTPUT:
- QuiverRep, the kernel
.. NOTES::
To get the inclusion map of the kernel, ``K``, into the domain, ``D``, use
``K.canonical_map(D)``.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: spaces = {1: QQ^2, 2: QQ^2, 3:QQ^1}
sage: maps = {(1, 2, 'a'): [[1, 0], [0, 0]], (1, 2, 'b'): [[0, 0], [0, 1]], (2, 3, 'c'): [[1], [1]]}
sage: M = QuiverRep(QQ, Q, spaces, maps)
sage: spaces2 = {2: QQ^2, 3: QQ^1}
sage: N = QuiverRep(QQ, Q, spaces2, {(2, 3, 'c'): [[1], [0]]})
sage: maps2 = {2:[[1, 0], [0, 0]], 3:1}
sage: g = QuiverRepHom(N, M, maps2)
sage: g.kernel().dimension_vector()
(0, 1, 0)
"""
spaces = dict((v, self.get_matrix(v).kernel()) for v in self._quiver)
return self._domain._submodule(spaces)
def image(self):
"""
Returns the image of self.
OUTPUT:
- QuiverRep, the image
.. NOTES::
To get the inclusion map of the image, ``I``, into the codomain, ``C``, use
``I.canonical_map(C)``.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: spaces = {1: QQ^2, 2: QQ^2, 3:QQ^1}
sage: maps = {(1, 2, 'a'): [[1, 0], [0, 0]], (1, 2, 'b'): [[0, 0], [0, 1]], (2, 3, 'c'): [[1], [1]]}
sage: M = QuiverRep(QQ, Q, spaces, maps)
sage: spaces2 = {2: QQ^2, 3: QQ^1}
sage: N = QuiverRep(QQ, Q, spaces2, {(2, 3, 'c'): [[1], [0]]})
sage: maps2 = {2:[[1, 0], [0, 0]], 3:1}
sage: g = QuiverRepHom(N, M, maps2)
sage: g.image().dimension_vector()
(0, 1, 1)
"""
spaces = dict((v, self.get_matrix(v).image()) for v in self._quiver)
return self._codomain._submodule(spaces)
def cokernel(self):
"""
Returns the cokernel of self.
OUTPUT:
- QuiverRep, the cokernel
.. NOTES::
To get the factor map of the codomain, ``D``, onto the cokernel, ``C``, use
``D.canonical_map(C)``.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepHom
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: spaces = {1: QQ^2, 2: QQ^2, 3:QQ^1}
sage: maps = {(1, 2, 'a'): [[1, 0], [0, 0]], (1, 2, 'b'): [[0, 0], [0, 1]], (2, 3, 'c'): [[1], [1]]}
sage: M = QuiverRep(QQ, Q, spaces, maps)
sage: spaces2 = {2: QQ^2, 3: QQ^1}
sage: N = QuiverRep(QQ, Q, spaces2, {(2, 3, 'c'): [[1], [0]]})
sage: maps2 = {2:[[1, 0], [0, 0]], 3:1}
sage: g = QuiverRepHom(N, M, maps2)
sage: g.cokernel().dimension_vector()
(2, 1, 0)
"""
return self._codomain.quotient(self.image())
def D(self):
"""
Computes the linear dual Df:DN->DM of self = f:M->N.
D(-) is the linear dual Hom_k(-, k). If e is an edge of the quiver Q then we
let (fa)(m) = f(ma). This gives DM the structure of a module over the opposite
quiver Q.reverse().
OUTPUT:
- QuiverRepHom, the map Df:DN->DM
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a', 'b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: S = P/P.radical()
sage: f = P.canonical_map(S)
sage: f.is_epimorphism()
True
sage: g = f.D()
sage: g.is_monomorphism()
True
sage: g.quiver() == Q.reverse()
True
sage: g.D() == f
True
"""
# The effect of the functor D is that it just transposes the matrix of
# a hom
maps = dict((v, self.get_matrix(v).transpose()) for v in self._quiver)
return QuiverRepHom(self._codomain.D(), self._domain.D(), maps)
###########################################################################
# #
# ADDITIONAL OPERATIONS #
# These functions operations that are not implemented via binary #
# operators. #
# #
###########################################################################
def scalar_mult(self, scalar):
"""
Returns the result of the scalar multiplcation scalar*self.
"""
return QuiverRepHom(self._domain, self._codomain, scalar*self._vector)
def iscalar_mult(self, scalar):
"""
Multiplies self by scalar in place.
"""
self._vector *= scalar
class QuiverRepElement(Element):
"""
An element of a quiver representation is a choice of element from each of the
spaces assigned to the vertices of the quiver. Addition, subtraction, and
scalar multiplication of these elements is done pointwise within these spaces.
INPUT:
- ``module`` - QuiverRep (default: None), the module to which the element belongs
- ``elements`` - dict (default: empty), a dictionary associating to each vertex a
vector or an object from which sage can create a vector. Not all vertices must
be specified, unspecified vertices will be assigned the zero vector of the space
associated to that vertex in the given module. Keys that do not correspond to a
vertex are ignored.
- ``name`` - string (default: None), the name of the element
OUTPUT:
- QuiverRepElement
.. NOTES::
The constructor needs to know the quiver in order to create an element of a
representation over that quiver. The default is to read this information
from ``module`` as well as to fill in unspecified vectors with the zeros of the
spaces in ``module``. If ``module``=None then ``quiver`` MUST be a quiver and each
vertex MUST be specified or an error will result. If both ``module`` and
``quiver`` are given then ``quiver`` is ignored.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepElement
sage: Q = Quiver({1:{2:['a'], 3:['b']}, 2:{3:['c']}})
sage: spaces = dict((v, GF(3)^2) for v in Q)
sage: M = QuiverRep(GF(3), Q, spaces)
sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)}
sage: QuiverRepElement(M, elems)
Element of quiver representation
sage: v = QuiverRepElement(M, elems, 'v')
sage: v
v
sage: (v + v + v).is_zero()
True
"""
###########################################################################
# #
# PRIVATE FUNCTIONS #
# These functions are not meant to be seen by the end user. #
# #
###########################################################################
def __init__(self, module, elements={}, name=None):
"""
Creates an element of the given module.
An element of a quiver representation is just a choice of element at each
vertex. The dictionary elems should be of the form {vertex: element}.
If there are unspecified elements then module must be a module with a
zero_element method. Unspecified elements are then set to zero and keys
not corresponding to vertices are ignored.
If the module is specified the quiver can be unspecified and the quiver
of the module will be taken.
"""
# The data describing an element is held in the following private
# variables:
#
# * _elems
# A dictionary that assigns to each vertex of the quiver a choice
# of element from the space assigned to that vertex in the parent
# representation.
# * _quiver
# The quiver of the representation.
super(QuiverRepElement, self).__init__(module)
self._elems = {}
self._quiver = module._quiver
for v in self._quiver:
if v in elements:
self._elems[v] = module._spaces[v](elements[v])
else:
self._elems[v] = module._spaces[v].zero()
# Assign a name if supplied
if name is not None:
self.rename(name)
def _repr_(self):
return "Element of quiver representation"
def __add__(left, right):
# This overrides the + operator
elements = {}
for v in left._quiver:
elements[v] = left._elems[v] + right._elems[v]
return QuiverRepElement(left.parent(), elements)
def __iadd__(self, right):
# This overrides the += operator
for v in self._quiver:
self._elems[v] += right._elems[v]
return self
def __sub__(left, right):
# This overrides the - operator
elements = {}
for v in left._quiver:
elements[v] = left._elems[v] - right._elems[v]
return QuiverRepElement(left.parent(), elements)
def __isub__(self, right):
# This overrides the -= operator
for v in self._quiver:
self._elems[v] -= right._elems[v]
return self
def __pos__(self):
# This function overrides the unary + operator
return self
def __neg__(self):
# This function overrides the unary - operator
elements = {}
for v in self._quiver:
elements[v] = -self._elems[v]
return QuiverRepElement(self.parent(), elements)
def __eq__(self, other):
# This overrides the == operator
# Return False if being compared to something other than a
# QuiverRepElement or if comparing two elements from representations
# with different quivers
if not isinstance(other, QuiverRepElement) or self._quiver != other._quiver:
return False
# Return False if the elements differ at any vertex
for v in self._quiver:
if self._elems[v] != other._elems[v]:
return False
return True
def __ne__(self, other):
# This overrides the != operator
# Return True if being compared to something other than a
# QuiverRepElement or if comparing two elements from representations
# with different quivers
if not isinstance(other, QuiverRepElement) or self._quiver != other._quiver:
return True
# Return True if the elements differ at any vertex
for v in self._quiver:
if self._elems[v] != other._elems[v]:
return True
return False
###########################################################################
# #
# ACCESS FUNCTIONS #
# These functions are used to view and modify the representation data. #
# #
###########################################################################
def quiver(self):
"""
Returns the quiver of the representation.
OUTPUT:
- Quiver, the quiver of the representation
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver
sage: Q = Quiver({1:{2:['a'], 3:['b']}, 2:{3:['c']}})
sage: P = Q.P(QQ, 1)
sage: v = P.an_element()
sage: v.quiver() is Q
True
"""
return self._quiver
def get_element(self, vertex):
"""
Returns the element at the given vertex.
INPUT:
- ``vertex`` - integer, a vertex of the quiver
OUTPUT:
- vector, the vaector assigned to the given vertex
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepElement
sage: Q = Quiver({1:{2:['a'], 3:['b']}, 2:{3:['c']}})
sage: spaces = dict((v, GF(3)^2) for v in Q)
sage: M = QuiverRep(GF(3), Q, spaces)
sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)}
sage: v = QuiverRepElement(M, elems)
sage: v.get_element(1)
(1, 0)
sage: v.get_element(3)
(2, 1)
"""
return self._elems[vertex]
def set_element(self, vector, vertex):
"""
Sets the element at the given vertex.
INPUT:
- ``vector`` - a vector or an object from which the space associated to the given
vertex in the parent can create a vector
- ``vertex`` - integer, a vertex of the quiver
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepElement
sage: Q = Quiver({1:{2:['a'], 3:['b']}, 2:{3:['c']}})
sage: spaces = dict((v, GF(3)^2) for v in Q)
sage: M = QuiverRep(GF(3), Q, spaces)
sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)}
sage: v = QuiverRepElement(M, elems)
sage: v.get_element(1)
(1, 0)
sage: v.set_element((1, 1), 1)
sage: v.get_element(1)
(1, 1)
"""
self._elems[vertex] = self.parent()._spaces[vertex](vector)
###########################################################################
# #
# DATA FUNCTIONS #
# These functions return data collected from the homomorphism. #
# #
###########################################################################
def is_zero(self):
"""
Tests whether self is zero.
OUTPUT:
- bool, True is the element is the zero element, False otherwise
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepElement
sage: Q = Quiver({1:{2:['a'], 3:['b']}, 2:{3:['c']}})
sage: spaces = dict((v, GF(3)^2) for v in Q)
sage: M = QuiverRep(GF(3), Q, spaces)
sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)}
sage: v = QuiverRepElement(M, elems)
sage: v.is_zero()
False
sage: w = QuiverRepElement(M)
sage: w.is_zero()
True
"""
for v in self._quiver:
if not self._elems[v].is_zero():
return False
return True
def support(self):
"""
Returns the support of self as a list.
The support is the set of vertices to which a nonzero vector is associated.
OUTPUT
- list, the support
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepElement
sage: Q = Quiver({1:{2:['a'], 3:['b']}, 2:{3:['c']}})
sage: spaces = dict((v, GF(3)^2) for v in Q)
sage: M = QuiverRep(GF(3), Q, spaces)
sage: elems = {1: (1, 0), 2: (0, 0), 3: (2, 1)}
sage: v = QuiverRepElement(M, elems)
sage: v.support()
[1, 3]
"""
sup = []
for v in self._quiver:
if not self._elems[v].is_zero():
sup.append(v)
return sup
###########################################################################
# #
# ADDITIONAL OPERATIONS #
# These functions operations that are not implemented via binary #
# operators. #
# #
###########################################################################
def copy(self):
"""
Returns a copy of self.
EXAMPLES::
sage: from sage.modules.quiver_module import Quiver, QuiverRep, QuiverRepElement
sage: Q = Quiver({1:{2:['a'], 3:['b']}, 2:{3:['c']}})
sage: spaces = dict((v, GF(3)^2) for v in Q)
sage: M = QuiverRep(GF(3), Q, spaces)
sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)}
sage: v = QuiverRepElement(M, elems)
sage: w = v.copy()
sage: w.set_element((0, 0), 1)
sage: w.get_element(1)
(0, 0)
sage: v.get_element(1)
(1, 0)
"""
if hasattr(self, '__custom_name'):
name = self.__custom_name
else:
name = None
return QuiverRepElement(self.parent(), self._elems.copy())
def scalar_mult(self, scalar):
"""
Returns the result of the scalar multiplcation scalar*self.
"""
elements = {}
for v in self._quiver:
elements[v] = scalar*self._elems[v]
return QuiverRepElement(self.parent(), elements)
def iscalar_mult(self, scalar):
"""
Multiplies self by scalar in place.
"""
for v in self._quiver:
self._elems[v] *= scalar