Quiver Crash?

114 days ago by tom

%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 
       
Q = Quiver({1:{2:['a']}}) P = Q.P(GF(3), 1) R = P.radical() print P.quotient(R) print "haven't crashed..." 
       
Representation with dimension vector (1, 0)
haven't crashed...
Representation with dimension vector (1, 0)
haven't crashed...