diff --git a/src/sage/graphs/digraph.py b/src/sage/graphs/digraph.py index 10a06a92be9..fc54889c63e 100644 --- a/src/sage/graphs/digraph.py +++ b/src/sage/graphs/digraph.py @@ -30,12 +30,13 @@ :meth:`~DiGraph.outgoing_edge_iterator` | Return an iterator over all departing edges from vertices :meth:`~DiGraph.incoming_edges` | Returns a list of edges arriving at vertices. :meth:`~DiGraph.incoming_edge_iterator` | Return an iterator over all arriving edges from vertices + :meth:`~DiGraph.sources` | Returns the list of all sources (vertices without incoming edges) of this digraph. + :meth:`~DiGraph.sinks` | Returns the list of all sinks (vertices without outoing edges) of this digraph. :meth:`~DiGraph.to_undirected` | Returns an undirected version of the graph. :meth:`~DiGraph.to_directed` | Since the graph is already directed, simply returns a copy of itself. :meth:`~DiGraph.is_directed` | Since digraph is directed, returns True. :meth:`~DiGraph.dig6_string` | Returns the dig6 representation of the digraph as an ASCII string. - **Paths and cycles:** .. csv-table:: @@ -48,6 +49,14 @@ :meth:`~DiGraph.all_cycles_iterator` | Returns an iterator over all the cycles of self starting :meth:`~DiGraph.all_simple_cycles` | Returns a list of all simple cycles of self. +**Representation theory:** + +.. csv-table:: + :class: contentstable + :widths: 30, 70 + :delim: | + + :meth:`~Digraph.path_semigroup` | Returns the (partial) semigroup formed by the paths of the digraph. **Connectivity:** @@ -1550,6 +1559,44 @@ def out_degree_sequence(self): """ return sorted(self.out_degree_iterator(), reverse=True) + def sources(self): + r""" + Returns a list of sources of the digraph. + + OUTPUT: + + - list, the vertices of the digraph that have no edges going into them + + EXAMPLES:: + + sage: G = DiGraph({1:{3:['a']}, 2:{3:['b']}}) + sage: G.sources() + [1, 2] + sage: T = DiGraph({1:{}}) + sage: T.sources() + [1] + """ + return [x for x in self if self.in_degree(x)==0] + + def sinks(self): + """ + Returns a list of sinks of the digraph. + + OUTPUT: + + - list, the vertices of the digraph that have no edges beginning at them + + EXAMPLES:: + + sage: G = DiGraph({1:{3:['a']}, 2:{3:['b']}}) + sage: G.sinks() + [3] + sage: T = DiGraph({1:{}}) + sage: T.sinks() + [1] + """ + return [x for x in self if self.out_degree(x)==0] + def feedback_edge_set(self, constraint_generation= True, value_only=False, solver=None, verbose=0): r""" @@ -2840,6 +2887,24 @@ def all_simple_cycles(self, starting_vertices=None, rooted=False, """ return list(self.all_cycles_iterator(starting_vertices=starting_vertices, simple=True, rooted=rooted, max_length=max_length, trivial=trivial)) + def path_semigroup(self): + """ + The partial semigroup formed by the paths of this quiver. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','c']}, 2:{3:['b']}}) + sage: F = Q.path_semigroup(); F + Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices + sage: list(F) + [e_1, e_2, e_3, a, c, b, a*b, c*b] + + """ + from sage.quivers.path_semigroup import PathSemigroup + # If self is immutable, then the copy is really cheap: + # __copy__ just returns self. + return PathSemigroup(self.copy(immutable=True)) + ### Directed Acyclic Graphs (DAGs) def topological_sort(self, implementation = "default"): diff --git a/src/sage/quivers/__init__.py b/src/sage/quivers/__init__.py new file mode 100644 index 00000000000..a3ef0437989 --- /dev/null +++ b/src/sage/quivers/__init__.py @@ -0,0 +1,452 @@ +r""" +This module contains tools for computing with quiver representations. + +AUTHOR: + +- Jim Stark (2012-03-04): Initial implementation of acyclic quivers without + relations. +- Simon King (2013-05, 2014-02): Split code up. Allow cyclic quivers where + possible. + +A Quiver is a directed graph used for representation theory. In our +representation theoretic code, it is assumed that + +- the vertices of the quiver are labelled by integers, and + +- each edge of the quiver is labelled with a nonempty string. The label cannot + begin with 'e_' or contain '*' and distinct edges must have distinct labels. + +As far as the :class:`~sage.graphs.digraph.DiGraph` class is concerned, a +path is a finite list of pairwise distinct vertices `v_1, ..., v_n` such +that there exists an edge from `v_i` to `v_{i + 1}`. If there are +multiple edges between the same two vertices this does not contribute +additional paths as listed by the DiGraph class; for example only two +paths are listed from 1 to 3 in Q:: + + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}) + sage: Q.edges() + [(1, 2, 'a'), (1, 2, 'b'), (1, 3, 'c'), (2, 3, 'd')] + sage: Q.all_paths(1, 3) + [[1, 2, 3], [1, 3]] + +The notion of a path in a quiver (in representation theory) is +fundamentally different in several aspects. First, paths are no longer +required to have distinct vertices, or even distinct edges; thus, "path" +in quiver theory is closer to the notion of "walk" in graph theory. +Furthermore, paths in quiver theory "know" their edges, so parallel edges +between the same two vertices of a Quiver make different paths. But +paths in quiver theory also "know" their vertices, so that a length-`0` +path from `a` to `a` is not the same as a length-`0` path from `b` to `b` +for `a \neq b`. +Formally, we say that a path is given by two vertices, ``start`` and +``end``, and a finite (possibly empty) list of edges `e_1, e_2, ..., e_n` +such that the initial vertex of `e_1` is ``start``, the final vertex of `e_i` +is the initial vertex of `e_{i + 1}`, and the final vertex of `e_n` is +``end``. In the case where no edges are specified, we must have +``start = end`` and the path is called the trivial path at the given vertex. + +Quiver paths in the sense stated above correspond to the elements of a +partial semigroup, with multiplication of paths given by concatenation. Hence, +rather than overloading the method name inherited from DiGraph or inventing a +new method name, we move this functionality to this so-called *path +semigroup*. Note that with this definition there are three paths from 1 to 3 +in our example:: + + sage: Q.path_semigroup().all_paths(1, 3) + [a*d, b*d, c] + +The returned paths are of type :class:`~sage.quivers.paths.QuiverPath`, which +are elements in the path semigroup that is associated with the quiver (a +partial semigroup, which does not generally have a neutral element). You can +specify a QuiverPath by giving an edge or a list of edges, passed as arguments +to the path semigroup containing this path. Here an edge is a tuple of +the form ``(i, j, l)``, where ``i`` and ``j`` are vertices and ``l`` is the +label of an edge from i to j:: + + sage: p = Q.path_semigroup()([(1, 2, 'a'), (2, 3, 'd')]) + sage: p + a*d + +Trivial paths are indicated by passing the tuple ``(vertex, vertex)``:: + + sage: Q.path_semigroup()((6, 6)) + e_6 + +Trivial "edges" can occur in the input. They are simply deleted if their +vertex matches the start and end vertex of adjacent edges. Here is an +alternative way to define a path:: + + sage: PQ = Q.path_semigroup() + sage: q = PQ([(1, 1), (1, 2, 'a'), (2, 2), (2, 3, 'd'), (3, 3)]) + sage: p == q + True + +If the vertex of a trivial path does not match with adjacent edges, or if two +adjacent edges do not match, an error is raised. + +:: + + sage: inv1 = PQ([(1, 2, 'a'), (1, 1)]) + Traceback (most recent call last): + ... + ValueError: Cannot interpret [(1, 2, 'a'), (1, 1)] as element of + Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices + sage: inv2 = PQ([(1, 2, 'a'), (1, 2, 'a')]) + Traceback (most recent call last): + ... + ValueError: Cannot interpret [(1, 2, 'a'), (1, 2, 'a')] as element of + Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices + sage: inv3 = PQ([(1, 2, 'x')]) + Traceback (most recent call last): + ... + ValueError: Cannot interpret [(1, 2, 'x')] as element of + Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices + +The `*` operator is concatenation of paths. If the two paths do not compose, +then the result is ``None`` (whence the "partial" in "partial semigroup"). :: + + sage: print p*q + None + +Let us now construct a larger quiver:: + + sage: Qbig = DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}, 3:{4:['e']}, 4:{5:['f']}, 5:{1:['g']} }) + sage: Pbig = Qbig.path_semigroup() + +Since ``Q`` is a sub-digraph of ``Qbig``, we have a coercion of the associated +path semigroups:: + + sage: Pbig.has_coerce_map_from(PQ) + True + +In particular, `p` is considered to be an element of ``Pbig``, and can be +composed with paths that were defined for the larger quiver:: + + sage: p in Pbig + True + sage: p*Pbig([(3, 4, 'e')]) + a*d*e + sage: Pbig([(4, 5, 'f'), (5, 1, 'g')])*p + f*g*a*d + +The length of a path is the number of edges in that path:: + + sage: len(p) + 2 + sage: triv = PQ((1, 1)) + sage: len(triv) + 0 + +List index and slice notation can be used to access the edges in a path. +QuiverPaths can also be iterated over. Trivial paths have no elements:: + + sage: for x in p: print x + (1, 2, 'a') + (2, 3, 'd') + sage: triv[:] + [] + +There are methods giving the initial and terminal vertex of a path:: + + sage: p.initial_vertex() + 1 + sage: p.terminal_vertex() + 3 + +QuiverPaths form the basis of the quiver algebra of a quiver. Given a +field `k` and a Quiver `Q`, the quiver algebra `kQ` is, as a vector space, +the free `k`-vector space whose basis is the set of all paths in `Q`. +Multiplication is defined on this basis and extended bilinearly. The +product of two basis elements is given by path composition when it +makes sense and is set to be zero otherwise. Specifically, if the +terminal vertex of the left path equals the initial vertex of the right +path, then their product is the concatenation of the two paths, and +otherwise their product is zero. In sage, quiver algebras +are handled by the :class:`QuiverAlgebra` class:: + + sage: A = PQ.algebra(GF(7)) + sage: A + Path algebra of Multi-digraph on 3 vertices over Finite Field of size 7 + +Quivers have a method that creates their algebra over a given field (or, +more generally, commutative ring). Note that QuiverAlgebras are uniquely +defined by their Quiver and field, and play nicely with coercions of the +underlying path semigroups:: + + sage: A is PQ.algebra(GF(7)) + True + sage: A is PQ.algebra(RR) + False + sage: Q1 = Q.copy() + sage: Q1.add_vertex(4) + sage: PQ1 = Q1.path_semigroup() + sage: A is PQ1.algebra(GF(7)) + False + sage: Pbig.algebra(GF(7)).has_coerce_map_from(A) + True + +The QuiverAlgebra can create elements from QuiverPaths or from elements of the +base ring:: + + sage: A(5) + 5*e_1 + 5*e_2 + 5*e_3 + sage: r = PQ([(1, 2, 'b'), (2, 3, 'd')]) + sage: e2 = PQ((2, 2)) + sage: x = A(p) + A(e2) + sage: x + e_2 + a*d + sage: y = A(p) + A(r) + sage: y + a*d + b*d + +QuiverAlgebras are `\NN`-graded algebras. The grading is given by +assigning to each basis element the length of the path corresponding to +that basis element:: + + sage: x.is_homogeneous() + False + sage: x.degree() + Traceback (most recent call last): + ... + ValueError: Element is not homogeneous. + sage: y.is_homogeneous() + True + sage: y.degree() + 2 + sage: A[1] + Free module spanned by [a, b, c, d] over Finite Field of size 7 + sage: A[2] + Free module spanned by [a*d, b*d] over Finite Field of size 7 + +The category of right modules over a given quiver algebra is equivalent to the +category of representations of that quiver. A quiver representation is a +diagram in the category of vector spaces whose underlying graph is the quiver. +So to each vertex of the quiver we assign a vector space and to each edge of +the quiver a linear map between the vector spaces assigned to the start and end +vertices of that edge. To create the zero representation we just specify the +base ring and the path semigroup:: + + sage: Z = Q1.path_semigroup().representation(GF(5)) + sage: Z.is_zero() + True + +To each vertex of a Quiver there is associated a simple module, an +indecomposable projective, and an indecomposable injective, and these can +be created from the Quiver:: + + sage: S = PQ.S(GF(3), 1) + sage: I = PQ.I(QQ, 2) + sage: P = PQ.P(GF(3), 1) + +Radicals, socles, tops, and quotients can all be computed and we can test if +modules are simple or semisimple, get their dimension, and test for equality. +Like Quivers, :class:`~sage.quivers.representation.QuiverRep` objects are +unique and therefore equal if and only if they are identical:: + + sage: P.is_simple() + False + sage: P.dimension() + 6 + sage: R = P.radical() + sage: P.socle() + Representation with dimension vector (0, 0, 3) + sage: (P/R).is_simple() + True + sage: P == R + False + sage: P.top() is P/R + True + +There are special methods to deal with modules that are given as right ideals +in the quiver algebra. To create such a module pass the keyword option='paths' +along with a path or list of paths that generate the desired ideal:: + + sage: M = PQ.representation(QQ, [[(1, 1)], [(1, 2, 'a')]], option='paths') + sage: M.dimension_vector() + (1, 2, 3) + +There are also special methods to deal with modules that are given as the +linear dual of a right ideal in the quiver algebra. To create such a +module, pass the keyword ``option='dual paths'`` to the constructor along +with a path or list of paths. The module returned is the dual of the +ideal created in the opposite quiver by the reverses of the given paths:: + + sage: D = PQ.representation(QQ, [[(1, 1)], [(1, 2, 'a')]], option='dual paths') + sage: D.dimension_vector() + (2, 0, 0) + +For modules that are not a standard module or an ideal of the quiver algebra +:class:`~sage.quivers.representation.QuiverRep` can take as input two +dictionaries. The first associates to each vertex a vector space or an +integer (the desired dimension of the vector space), the second associates to +each edge a map or a matrix or something from which sage can construct a map:: + + sage: PQ2 = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: M2 = PQ2.representation(QQ, {1: QQ^2, 2: QQ^1}, {(1, 2, 'a'): [1, 0], (1, 2, 'b'): [0, 1]}) + sage: M.get_space(2) + Vector space of dimension 2 over Rational Field + sage: M2.get_space(2) + Vector space of dimension 1 over Rational Field + sage: M.get_map((1, 2, 'a')) + Vector space morphism represented by the matrix: + [1 0] + Domain: Vector space of dimension 1 over Rational Field + Codomain: Vector space of dimension 2 over Rational Field + +A homomorphism between two quiver representations is given by homomorphisms +between the spaces assigned to the vertices of those representations such that +those homomorphisms commute with the edge maps of the representations. The +homomorphisms are created in the usual Sage syntax, the defining data given by +a dictionary associating maps to vertices:: + + sage: P2 = PQ2.P(QQ, 1) + sage: f = P2.hom({1:[1, 1], 2:[[1], [1]]}, M2) + +When the domain is given as a right ideal in the quiver algebra we can also +create a homomorphism by just giving a single element in the codomain. The map +is then induced by acting on that element:: + + sage: x = P2.gens('x')[0] + sage: x + x_0 + sage: f == P2.hom(f(x), M2) + True + +As you can see, the above homomorphisms can be applied to elements. Just +like elements, addition is defined via the + operator. On elements scalar +multiplication is defined via the `*` operator but on homomorphisms `*` +defines composition, so scalar multiplication is done using a method:: + + sage: g = f + f + sage: g == f.scalar_mult(2) + True + sage: g == 2*f # This multiplies the map with the scalar 2 + True + sage: g(x) == 2*f(x) # This applies the map, then multiplies by the scalar + True + +The ``direct_sum`` method for modules returns only the resulting module by +default. But can also return the projection and inclusion homomorphisms into +the various factors:: + + sage: N2, inclusions, projections = M2.direct_sum([P2], return_maps=True) + sage: inclusions[0].domain() is M2 + True + sage: projections[0].codomain() is M2 + True + sage: (projections[0]*inclusions[0]).is_isomorphism() + True + +As you see above we can determine if a given map is an isomorphism. Testing +for injectivity and surjectivity works as well:: + + sage: f.is_injective() + False + sage: f.is_surjective() + False + +We can create all the standard modules associated to maps:: + + sage: f.kernel() + Representation with dimension vector (0, 1) + sage: f.cokernel() + Representation with dimension vector (1, 0) + sage: im = f.image() + sage: im + Representation with dimension vector (1, 1) + +These methods, as well as the ``submodule`` and ``quotient`` methods that are +defined for representations, return only the resulting representation. To get +the inclusion map of a submodule or the factor homomorphism of a quotient use +``coerce_map_from``:: + + sage: incl = M2.coerce_map_from(im) + sage: incl.domain() is im + True + sage: incl.codomain() is M2 + True + sage: incl.is_injective() + True + +Both :class:`~sage.quivers.representation.QuiverRep` objects and +:class:`~sage.quivers.homspace.QuiverRepHom` objects have ``linear_dual`` and +``algebraic_dual`` methods. The ``linear_dual`` method applies the functor +`Hom_k(..., k)` where `k` is the base ring of the representation, and the +``algebraic_dual`` method applies the functor `Hom_Q(..., kQ)` where `kQ` +is the quiver algebra. Both these functors yield left modules. A left +module is equivalent to a right module over the opposite algebra, and the +opposite of a quiver algebra is the algebra of the opposite quiver, so both +these methods yield modules and representations of the opposite quiver:: + + sage: f.linear_dual() + Homomorphism of representations of Reverse of (): Multi-digraph on 2 vertices + sage: D = M2.algebraic_dual() + sage: D.quiver() is PQ2.reverse().quiver() + True + +.. TODO:: + + Change the wording ``Reverse of ()`` into something more meaningful. + +There is a method returning the projective cover of any module. Note that this +method returns the homomorphism; to get the module take the domain of the +homomorphism:: + + sage: cov = M2.projective_cover() + sage: cov + Homomorphism of representations of Multi-digraph on 2 vertices + sage: cov.domain() + Representation with dimension vector (2, 4) + +As projective covers are computable, so are the transpose and Auslander-Reiten +translates of modules:: + + sage: M2.transpose() + Representation with dimension vector (4, 3) + sage: PQ2.I(QQ, 1).AR_translate() + Representation with dimension vector (3, 2) + +We have already used the ``gens`` method above to get an element of a quiver +representation. An element of a quiver representation is simply a choice of +element from each of the spaces assigned to the vertices of the quiver. +Addition, subtraction, and scalar multiplication are performed pointwise and +implemented by the usual operators:: + + sage: M2.dimension_vector() + (2, 1) + sage: x, y, z = M2.gens('xyz') + sage: 2*x + y != x + 2*y + True + +To create a specific element of a given representation we just specify the +representation and a dictionary associating to each vertex an element of the +space associated to that vertex in the representation:: + + sage: w = M2({1:(1, -1), 2:(3,)}) + sage: w.get_element(1) + (1, -1) + +The right action of a quiver algebra on an element is implemented via the `*` +operator:: + + sage: A2 = x.quiver().path_semigroup().algebra(QQ) + sage: a = A2((1, 2, 'a')) + sage: x*a == z + True +""" + +#***************************************************************************** +# Copyright (C) 2012 Jim Stark +# 2013, 2014 Simon King +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** diff --git a/src/sage/quivers/algebra.py b/src/sage/quivers/algebra.py new file mode 100644 index 00000000000..bbc7b3a927d --- /dev/null +++ b/src/sage/quivers/algebra.py @@ -0,0 +1,590 @@ +from sage.misc.cachefunc import cached_method +from sage.combinat.free_module import CombinatorialFreeModule, CombinatorialFreeModuleElement + +class PathAlgebra(CombinatorialFreeModule): + """ + Create the path algebra of a Quiver over a given field. + + Given a Quiver `Q` and a field `k`, the path algebra `kQ` is defined as + follows. As a vector space it has basis the set of all paths in `Q`. + Multiplication is defined on this basis and extended bilinearly. If `p` + is a path with terminal vertex `t` and `q` is a path with initial vertex + `i` then the product `p*q` is defined to be the composition of the paths `p` + and `q` if `t = i` and `0` otherwise. + + INPUT: + + - `k` - field (or commutative ring), the base field of the path algebra. + + - `P` - the path semigroup of a quiver `Q`. + + OUTPUT: + + - Path algebra `kP` + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: A = P.algebra(GF(7)) + sage: A + Path algebra of Multi-digraph on 3 vertices over Finite Field of size 7 + sage: A.variable_names() + ('e_1', 'e_2', 'e_3', 'a', 'b') + + Note that path algebras are uniquely defined by their quiver and field:: + + sage: A is P.algebra(GF(7)) + True + sage: A is P.algebra(RR) + False + sage: A is DiGraph({1:{2:['a']}}).path_semigroup().algebra(GF(7)) + False + + The path algebra of an acyclic quiver has a finite basis:: + + sage: A.dimension() + 6 + sage: list(A.basis()) + [e_1, e_2, e_3, a, b, a*b] + + The path algebra can create elements from paths or from elements of the + base ring:: + + sage: A(5) + 5*e_1 + 5*e_2 + 5*e_3 + sage: S = A.semigroup() + sage: S + Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices + sage: p = S((1, 2, 'a')) + sage: r = S((2, 3, 'b')) + sage: e2 = S((2, 2)) + sage: x = A(p) + A(e2) + sage: x + e_2 + a + sage: y = A(p) + A(r) + sage: y + a + b + + Path algebras are graded algebras. The grading is given by assigning to each + basis element the length of the path corresponding to that basis element:: + + sage: x.is_homogeneous() + False + sage: x.degree() + Traceback (most recent call last): + ... + ValueError: Element is not homogeneous. + sage: y.is_homogeneous() + True + sage: y.degree() + 1 + sage: A[1] + Free module spanned by [a, b] over Finite Field of size 7 + sage: A[2] + Free module spanned by [a*b] over Finite Field of size 7 + + TESTS:: + + sage: TestSuite(A).run() + + """ + + ########################################################################### + # # + # PRIVATE FUNCTIONS # + # These functions are not meant to be seen by the end user. # + # # + ########################################################################### + + def __init__(self, k, P): + """ + Creates a :class:`PathAlgebra` object. Type PathAlgebra? for more information. + + INPUT: + + - ``k``, a commutative ring + - ``P``, the partial semigroup formed by the paths of a quiver. + + TESTS:: + + sage: P = DiGraph({1:{2:['a']}, 2:{3:['b', 'c']}, 4:{}}).path_semigroup() + sage: P.algebra(GF(5)) + Path algebra of Multi-digraph on 4 vertices over Finite Field of size 5 + """ + # The following hidden methods are relevant: + # + # - _base + # The base ring of the path algebra. + # - _basis_keys + # Finite enumerated set containing the QuiverPaths that form the + # basis. + # - _quiver + # The quiver of the path algebra + # - _semigroup + # Shortcut for _quiver.semigroup() + + from sage.categories.graded_algebras_with_basis import GradedAlgebrasWithBasis + self._quiver = P.quiver() + self._semigroup = P + super(PathAlgebra, self).__init__(k, self._semigroup, + prefix='', + element_class=self.Element, + category=GradedAlgebrasWithBasis(k), + bracket=False) + self._assign_names(self._semigroup.variable_names()) + + @cached_method + def gens(self): + """ + Generators of this algebra (idempotents and arrows). + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a']}, 2:{3:['b', 'c']}, 4:{}}).path_semigroup() + sage: A = P.algebra(GF(5)) + sage: A.variable_names() + ('e_1', 'e_2', 'e_3', 'e_4', 'a', 'b', 'c') + sage: A.gens() + (e_1, e_2, e_3, e_4, a, b, c) + + """ + return tuple(self._from_dict( {index: self.base_ring().one()}, remove_zeros = False ) for index in self._semigroup.gens()) + + @cached_method + def arrows(self): + """ + Arrows of this algebra (corresponding to edges of the underlying quiver). + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a']}, 2:{3:['b', 'c']}, 4:{}}).path_semigroup() + sage: A = P.algebra(GF(5)) + sage: A.arrows() + (a, b, c) + + """ + return tuple(self._from_dict( {index: self.base_ring().one()}, remove_zeros = False ) for index in self._semigroup.arrows()) + + @cached_method + def idempotents(self): + """ + Idempotents of this algebra (corresponding to vertices of the + underlying quiver). + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a']}, 2:{3:['b', 'c']}, 4:{}}).path_semigroup() + sage: A = P.algebra(GF(5)) + sage: A.idempotents() + (e_1, e_2, e_3, e_4) + + """ + return tuple(self._from_dict( {index: self.base_ring().one()}, remove_zeros = False ) for index in self._semigroup.idempotents()) + + + def gen(self, i): + """ + `i`-th generator of this algebra. + + This is an idempotent (corresponding to a trivial path at a + vertex) if `i < n` (where `n` is the number of vertices of the + quiver), and a single-edge path otherwise. + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a']}, 2:{3:['b', 'c']}, 4:{}}).path_semigroup() + sage: A = P.algebra(GF(5)) + sage: A.gens() + (e_1, e_2, e_3, e_4, a, b, c) + sage: A.gen(2) + e_3 + sage: A.gen(5) + b + + """ + return self._from_dict( {self._semigroup.gen(i): self.base_ring().one()}, remove_zeros = False ) + + def ngens(self): + """ + Number of generators of this algebra. + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a']}, 2:{3:['b', 'c']}, 4:{}}).path_semigroup() + sage: A = P.algebra(GF(5)) + sage: A.ngens() + 7 + + """ + return self._semigroup.ngens() + + def _element_constructor_(self, x): + """ + Attempt to construct an element of ``self`` from `x`. + + TESTS:: + + sage: A = DiGraph({1:{2:['a']}}).path_semigroup().algebra(QQ) + sage: B = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup().algebra(QQ) + sage: x = A((1, 2, 'a')) + 1 # indirect doctest + sage: x + e_1 + e_2 + a + sage: B(x) # indirect doctest + e_1 + e_2 + a + sage: A(1) # indirect doctest + e_1 + e_2 + """ + from sage.quivers.paths import QuiverPath + # If it's an element of another path algebra, do a linear combination + # of the basis + if isinstance(x, CombinatorialFreeModuleElement) and isinstance(x.parent(), PathAlgebra): + coeffs = x.monomial_coefficients() + result = self.zero() + for key in coeffs: + result += coeffs[key]*self.monomial(key) + return result + + # If it's a QuiverPath return the associated basis element + if isinstance(x, QuiverPath): + return self.monomial(x) + + # If it's a tuple or a list try and create a QuiverPath from it and + # then return the associated basis element + if isinstance(x, tuple) or isinstance(x, list): + return self.monomial(self._semigroup(x)) + + # Otherwise let CombinatorialFreeModule try + return super(PathAlgebra, self)._element_constructor_(x) + + def _coerce_map_from_(self, other): + """ + ``True`` if there is a coercion from ``other`` to ``self``. + + The algebras that coerce into a path algebra are rings `k` or path + algebras `kQ` such that `k` has a coercion into the base ring of + ``self`` and `Q` is a subquiver of the quiver of ``self``. + + In particular, the path semigroup of a subquiver coerces into the + algebra. + + TESTS:: + + sage: P1 = DiGraph({1:{2:['a']}}).path_semigroup() + sage: P2 = DiGraph({1:{2:['a','b']}}).path_semigroup() + sage: A1 = P1.algebra(GF(3)) + sage: A2 = P2.algebra(GF(3)) + sage: A1.coerce_map_from(A2) # indirect doctest + sage: A2.coerce_map_from(A1) # indirect doctest + Conversion map: + From: Path algebra of Multi-digraph on 2 vertices over Finite Field of size 3 + To: Path algebra of Multi-digraph on 2 vertices over Finite Field of size 3 + sage: A1.coerce_map_from(ZZ) # indirect doctest + Composite map: + From: Integer Ring + To: Path algebra of Multi-digraph on 2 vertices over Finite Field of size 3 + Defn: Natural morphism: + From: Integer Ring + To: Finite Field of size 3 + then + Generic morphism: + From: Finite Field of size 3 + To: Path algebra of Multi-digraph on 2 vertices over Finite Field of size 3 + sage: A1.coerce_map_from(QQ) # indirect doctest + sage: A1.coerce_map_from(ZZ) + Composite map: + From: Integer Ring + To: Path algebra of Multi-digraph on 2 vertices over Finite Field of size 3 + Defn: Natural morphism: + From: Integer Ring + To: Finite Field of size 3 + then + Generic morphism: + From: Finite Field of size 3 + To: Path algebra of Multi-digraph on 2 vertices over Finite Field of size 3 + + :: + + sage: A2.coerce_map_from(P1) + Conversion map: + From: Partial semigroup formed by the directed paths of Multi-digraph on 2 vertices + To: Path algebra of Multi-digraph on 2 vertices over Finite Field of size 3 + sage: a = P1(P1.arrows()[0]); a + a + sage: A2.one() * a == a # indirect doctest + True + + """ + + if isinstance(other, PathAlgebra) and self._base.has_coerce_map_from(other._base): + OQ = other._quiver + SQ = self._quiver + SQE = SQ.edges() + if all(v in SQ for v in OQ.vertices()) and all(e in SQE for e in OQ.edges()): + return True + if self._semigroup.has_coerce_map_from(other): + return True + return self._base.has_coerce_map_from(other) + + def _repr_(self): + """ + Default string representation. + + TESTS:: + + sage: P = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: P.algebra(RR) # indirect doctest + Path algebra of Multi-digraph on 3 vertices over Real Field with 53 bits of precision + """ + + return "Path algebra of {0} over {1}".format(self._quiver, self._base) + + ########################################################################### + # # + # CATEGORY METHODS # + # These functions are used by the category to implement the algebra # + # structure. # + # # + ########################################################################### + + def _monomial(self, index): + """ + This method makes sure that the invalid path evaluates as zero. + + TESTS:: + + sage: P = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: P.algebra(ZZ)(P((1,2,'a'))*P((2,3,'b'))) + a*b + sage: P.algebra(ZZ)(P((2,3,'b')))*P.algebra(ZZ)(P((1,2,'a'))) # indirect doctest + 0 + + The following was an issue during work at :trac:`12630`:: + + sage: P1 = DiGraph({1:{2:['a']}}).path_semigroup() + sage: P2 = DiGraph({1:{2:['a','b']}}).path_semigroup() + sage: A1 = P1.algebra(GF(3)) + sage: A2 = P2.algebra(GF(3)) + sage: b = P2.arrows()[1]; b + b + sage: A1(b) + Traceback (most recent call last): + ... + ValueError: Cannot interpret b as element of Partial semigroup + formed by the directed paths of Multi-digraph on 2 vertices + """ + if index is not None: + return self._from_dict( {self._semigroup(index): self.base_ring().one()}, remove_zeros = False ) + return self.zero() + + def product_on_basis(self, p1, p2): + """ + Return the product ``p1*p2`` in the path algebra. + + INPUT: + + - ``p1``, ``p2`` - QuiverPaths + + OUTPUT: + + - CombinatorialFreeModuleElement + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}, 3:{4:['c']}}).path_semigroup() + sage: p1 = Q((1, 2, 'a')) + sage: p2 = Q([(2, 3, 'b'), (3, 4, 'c')]) + sage: A = Q.algebra(QQ) + sage: A.product_on_basis(p1, p2) + a*b*c + sage: A.product_on_basis(p2, p1) + 0 + + """ + PSG = self._semigroup + p = PSG(p1)*PSG(p2) + if p is not None: + return self.basis()[p] + else: + return self.zero() + + def degree_on_basis(self, p): + """ + Return the degree of the monomial specified by the path ``p``. + + EXAMPLES:: + + sage: A = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup().algebra(QQ) + sage: A.degree_on_basis((1, 1)) + 0 + sage: A.degree_on_basis((1, 2, 'a')) + 1 + sage: A.degree_on_basis([(1, 2, 'a'), (2, 3, 'b')]) + 2 + """ + return len(self._semigroup(p)) + + def one(self): + """ + Return the multiplicative identity element. + + The multiplicative identity of a path algebra is the sum of the basis + elements corresponding to the trivial paths at each vertex. + + EXAMPLES:: + + sage: A = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup().algebra(QQ) + sage: A.one() + e_1 + e_2 + e_3 + """ + return self.sum_of_monomials([self._semigroup([(v, v)], check=False) + for v in self._quiver]) + + ########################################################################### + # # + # DATA FUNCTIONS # + # These functions return data and subspaces of the path algebra. # + # # + ########################################################################### + + def quiver(self): + """ + Return the quiver from which the algebra ``self`` was formed. + + OUTPUT: + + - Quiver + + EXAMPLES: + + sage: P = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: A = P.algebra(GF(3)) + sage: A.quiver() is P.quiver() + True + """ + return self._quiver + + def semigroup(self): + """ + Return the (partial) semigroup from which the algebra ``self`` was + constructed. + + NOTE: + + The partial semigroup is formed by the paths of a quiver, multiplied + by concatenation. If the quiver has more than a single vertex, then + multiplication in the path semigroup is not always defined. + + OUTPUT: + + - The path semigroup from which ``self`` was formed (a partial + semigroup). + + EXAMPLES: + + sage: P = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: A = P.algebra(GF(3)) + sage: A.semigroup() is P + True + """ + return self._semigroup + + def homogeneous_component(self, n): + """ + Return the `n`-th homogeneous piece of the path algebra. + + INPUT: + + - ``n`` - integer + + OUTPUT: + + - CombinatorialFreeModule, module spanned by the paths of length + `n` in the quiver. + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a'], 3:['b']}, 2:{4:['c']}, 3:{4:['d']}}).path_semigroup() + sage: A = P.algebra(GF(7)) + sage: A.homogeneous_component(2) + Free module spanned by [a*c, b*d] over Finite Field of size 7 + + sage: D = DiGraph({1: {2: 'a'}, 2: {3: 'b'}, 3: {1: 'c'}}) + sage: P = D.path_semigroup() + sage: A = P.algebra(ZZ) + sage: A.homogeneous_component(3) + Free module spanned by [a*b*c, b*c*a, c*a*b] over Integer Ring + """ + basis = [] + for v in self._semigroup._quiver: + basis.extend(self._semigroup.iter_paths_by_length_and_startpoint(n, v)) + M = CombinatorialFreeModule(self._base, basis, prefix='', bracket=False) + M._name = "Free module spanned by {0}".format(basis) + return M + + __getitem__ = homogeneous_component + + ########################################################################### + # # + # ELEMENT CLASS # + # The class of elements of the path algebra. # + # # + ########################################################################### + + class Element(CombinatorialFreeModuleElement): + def is_homogeneous(self): + """ + Return ``True`` if and only if this element is homogeneous. + + EXAMPLES:: + + sage: A = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup().algebra(QQ) + sage: (A((1, 2, 'a')) + A((1, 2, 'b'))).is_homogeneous() + True + sage: (A((1, 1)) + A((1, 2, 'a'))).is_homogeneous() + False + """ + + # Get the support, the zero element is homogeneous + paths = self.support() + if not paths: + return True + + # Compare the rest of the paths, they must be the same length + for p in paths[1:]: + if len(p) != len(paths[0]): + return False + + return True + + def degree(self): + """ + The degree of ``self``, if ``self`` is homogeneous. + + EXAMPLES:: + + sage: A = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup().algebra(QQ) + sage: A((1, 1)).degree() + 0 + sage: (A((1, 2, 'a')) + A((1, 2, 'b'))).degree() + 1 + + An error is raised if the element is not homogeneous:: + + sage: (A((1, 1)) + A((1, 2, 'a'))).degree() + Traceback (most recent call last): + ... + ValueError: Element is not homogeneous. + """ + + # Deal with zero + paths = self.support() + if not paths: + raise ValueError("The zero element does not have a well-defined degree.") + + # Check that the element is homogeneous + for p in paths[1:]: + if len(p) != len(paths[0]): + raise ValueError("Element is not homogeneous.") + + return len(paths[0]) + diff --git a/src/sage/quivers/homspace.py b/src/sage/quivers/homspace.py new file mode 100644 index 00000000000..87ef2f19b8e --- /dev/null +++ b/src/sage/quivers/homspace.py @@ -0,0 +1,663 @@ +#***************************************************************************** +# Copyright (C) 2012 Jim Stark +# 2013 Simon King +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.categories.homset import Homset +from sage.quivers.morphism import QuiverRepHom +from sage.misc.cachefunc import cached_method + +class QuiverHomSpace(Homset): + """ + A homomorphism of quiver representations (of one and the same quiver) + is given by specifying, for each vertex of the quiver, a homomorphism + of the spaces assigned to this vertex 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)`` + + .. NOTE:: + + The quivers of the domain and codomain must be equal or a + ``ValueError`` is raised. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: H = Q.S(QQ, 2).Hom(Q.P(QQ, 1)) + sage: H.dimension() + 2 + sage: H.gens() + [Homomorphism of representations of Multi-digraph on 2 vertices, + Homomorphism of representations of Multi-digraph on 2 vertices] + sage: TestSuite(H).run() + + """ + Element = QuiverRepHom + + ########################################################################### + # # + # PRIVATE FUNCTIONS # + # These functions are not meant to be seen by the end user. # + # # + ########################################################################### + + def __init__(self, domain, codomain, category=None): + """ + Type QuiverHomSpace? for more information. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: H = Q.S(QQ, 2).Hom(Q.P(QQ, 1)) + sage: H.dimension() + 2 + sage: H.gens() + [Homomorphism of representations of Multi-digraph on 2 vertices, + Homomorphism of representations of Multi-digraph on 2 vertices] + """ + # The data in the class is stored in the following private variables: + # + # * _base + # 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 + if domain._semigroup != codomain._semigroup: + raise ValueError("Representations are not over the same quiver.") + self._quiver = domain._quiver + self._semigroup = domain._semigroup + + # Check that the bases are compatible, and then initialise the homset: + if codomain._base_ring != domain._base_ring: + raise ValueError("Representations are not over the same base ring.") + Homset.__init__(self, domain, codomain, category=category, base = domain._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 list whose ith entry is the number of the + # variable located at (0, 0) in the matrix assigned to the + # ith vertex. (So varstart[0] will be 0.) + eqs = 0 + verts = domain._quiver.vertices() + varstart = [0]*(len(verts) + 1) + + # First assign to varstart the dimension of the matrix assigned to the + # previous vertex. + for v in verts: + varstart[verts.index(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 matrices assigned to vertices ordered before v. + # This is equal to the number of the first variable assigned to v. + for i in range(2, len(varstart)): + 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. + from sage.matrix.constructor import Matrix + coef_mat = Matrix(codomain._base_ring, varstart[-1], eqs) + + # eqn 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 at 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_ik*B_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[verts.index(e[0])] + i*Y.nrows() + k, eqn] = Y[k, j] + for k in range(0, X.ncols()): + coef_mat[varstart[verts.index(e[1])] + k*Y.ncols() + j, eqn] = -X[i, k] + eqn += 1 + + # Now we can create the hom space + self._space = coef_mat.kernel() + + # Bind identity if domain = codomain + if domain is codomain: + self.identity = self._identity + + @cached_method + def zero(self): + """ + Return the zero morphism. + + .. NOTE: + + It is needed to override the method inherited from + the category of modules, because it would create + a morphism that is of the wrong type and does not + comply with :class:`~sage.quivers.morphism.QuiverRepHom`. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: H = Q.S(QQ, 2).Hom(Q.P(QQ, 1)) + sage: H.zero() + H.an_element() == H.an_element() + True + sage: isinstance(H.zero(), H.element_class) + True + + """ + return self() + + def _coerce_map_from_(self, other): + """ + A coercion exists if and only if ``other``` is also a + QuiverHomSpace and there is a coercion from the domain of ``self`` + to the domain of ``other`` and from the codomain of ``other`` to + the codomain of ``self```. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: S = Q.S(QQ, 1) + sage: H1 = P.Hom(S) + sage: H2 = (P/P.radical()).Hom(S) + sage: H1.coerce_map_from(H2) # indirect doctest + Conversion map: + From: Dimension 1 QuiverHomSpace + To: Dimension 1 QuiverHomSpace + """ + + if not isinstance(other, QuiverHomSpace): + return False + if not other._domain.has_coerce_map_from(self._domain): + return False + if not self._codomain.has_coerce_map_from(other._codomain): + return False + return True + + def __call__(self, *data, **kwds): + r""" + A homomorphism of quiver representations (of one and the same + quiver) is given by specifying, for each vertex of the quiver, a + homomorphism of the spaces assigned to this vertex 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: + + Usually, one would provide a single dict, list, QuiverRepElement + or QuiverRepHom as arguments. The semantics is as follows: + + - list: ``data`` can be a list of images for the generators of + the domain. "Generators" means the output of the ``gens()`` + method. An error will be generated if the map so defined + is not equivariant with respect to the action of the quiver. + - dictionary: ``data`` can be a dictionary associating to each + vertex of the quiver either 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, and keys not corresponding + to vertices of the quiver are ignored. An error will be + generated if these maps do not commute with the edge maps of + the domain and codomain. + - QuiverRepElement: if the domain is a QuiverRep_with_path_basis + then ``data`` can be a single QuiverRepElement belonging to + the codomain. The map is then defined by sending each path, + ``p``, in the basis to ``data*p``. If ``data`` is not an + element of the codomain or the domain is not a + QuiverRep_with_path_basis then an error will be generated. + - QuiverRepHom: the input can also be a map `f : D \to C` such + that there is a coercion from the domain of ``self`` to ``D`` + and from ``C`` to the codomain of ``self``. The composition + of these maps is the result. + + If there additionally are keyword arguments or if a QuiverRepHom can + not be created from the data, then the default call method of + :class:`~sage.categories.homset.Homset` is called instead. + + OUTPUT: + + - QuiverRepHom + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: H = S.Hom(M) + + With no additional data this creates the zero map:: + + sage: f = H() # indirect doctest + sage: f.is_zero() + True + + We must specify maps at the vertices to get a nonzero + homomorphism. Note that if the dimensions of the spaces assigned + to the domain and codomain of a vertex are equal then Sage will + construct the identity matrix from ``1``:: + + sage: maps2 = {2:[1, -1], 3:1} + sage: g = H(maps2) # indirect doctest + + Here we create the same map by specifying images for the generators:: + + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: h = H([x, y]) # indirect doctest + sage: g == h + True + + Here is an example of the same with a bigger identity matrix:: + + sage: spaces3 = {2: QQ^2, 3: QQ^2} + sage: maps3 = {(2, 3, 'c'): [[1, 0], [1, 0]]} + sage: S3 = Q.representation(QQ, spaces3, maps3) + sage: h3 = S3.Hom(M)({2: 1, 3: [[1], [0]]}) + sage: h3.get_map(2) + Vector space morphism represented by the matrix: + [1 0] + [0 1] + Domain: Vector space of dimension 2 over Rational Field + Codomain: Vector space of dimension 2 over Rational Field + + If the domain is a module of type QuiverRep_with_path_basis (for + example, the indecomposable projectives) we can create maps by + specifying a single image:: + + sage: Proj = Q.P(GF(7), 3) + sage: Simp = Q.S(GF(7), 3) + sage: im = Simp({3: (1,)}) + sage: H2 = Proj.Hom(Simp) + sage: H2(im).is_surjective() # indirect doctest + True + """ + if kwds or (len(data)>1): + return super(Homset,self).__call__(*data,**kwds) + + if not data: + return self.natural_map() + + data0 = data[0] + if data0 is None or data0 == 0: + data0 = {} + try: + return self.element_class(self._domain, self._codomain, data0) + except StandardError: + return super(QuiverHomSpace,self).__call__(*data,**kwds) + + def _repr_(self): + """ + Default string representation. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: Q.P(GF(3), 2).Hom(Q.S(GF(3), 2)) # indirect doctest + Dimension 1 QuiverHomSpace + """ + + return "Dimension " + str(self._space.dimension()) + " QuiverHomSpace" + + def natural_map(self): + """ + The natural map from domain to codomain. + + This is the zero map. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: S.hom(M) # indirect doctest + Homomorphism of representations of Multi-digraph on 3 vertices + sage: S.hom(M) == S.Hom(M).natural_map() + True + + """ + return self.element_class(self._domain, self._codomain, {}) + + def _identity(self): + """ + Return the identity map. + + OUTPUT: + + - QuiverRepHom + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: H = P.Hom(P) + sage: f = H.identity() # indirect doctest + sage: f.is_isomorphism() + True + """ + + from sage.matrix.constructor import Matrix + maps = dict((v, Matrix(self._domain._spaces[v].dimension(), + self._domain._spaces[v].dimension(), self._base.one())) + for v in self._quiver) + return self.element_class(self._domain, self._codomain, maps) + + ########################################################################### + # # + # ACCESS FUNCTIONS # + # These functions are used to view and modify the representation data. # + # # + ########################################################################### + + def base_ring(self): + """ + Return the base ring of the representations. + + OUTPUT: + + - ring, the base ring of the representations + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: H = Q.S(QQ, 2).Hom(Q.P(QQ, 1)) + sage: H.base_ring() + Rational Field + """ + + return self._base + + def quiver(self): + """ + Return the quiver of the representations. + + OUTPUT: + + - Quiver, the quiver of the representations + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: H = P.S(QQ, 2).Hom(P.P(QQ, 1)) + sage: H.quiver() is P.quiver() + True + """ + + return self._quiver + + def domain(self): + """ + Return the domain of the hom space. + + OUTPUT: + + - QuiverRep, the domain of the Hom space + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: S = Q.S(QQ, 2) + sage: H = S.Hom(Q.P(QQ, 1)) + sage: H.domain() is S + True + """ + + return self._domain + + def codomain(self): + """ + Return the codomain of the hom space. + + OUTPUT: + + - QuiverRep, the codomain of the Hom space + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: H = Q.S(QQ, 2).Hom(P) + sage: H.codomain() is P + True + """ + + return self._codomain + + ########################################################################### + # # + # DATA FUNCTIONS # + # These functions return data collected from the representation. # + # # + ########################################################################### + + def dimension(self): + """ + Return the dimension of the hom space. + + OUTPUT: + + - integer, the dimension + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: H = Q.S(QQ, 2).Hom(Q.P(QQ, 1)) + sage: H.dimension() + 2 + """ + + return self._space.dimension() + + def gens(self): + """ + Return a list of generators of the hom space (as a `k`-vector + space). + + OUTPUT: + + - list of QuiverRepHom objects, the generators + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: H = Q.S(QQ, 2).Hom(Q.P(QQ, 1)) + sage: H.gens() + [Homomorphism of representations of Multi-digraph on 2 vertices, + Homomorphism of representations of Multi-digraph on 2 vertices] + """ + + return [self.element_class(self._domain, self._codomain, f) for f in self._space.gens()] + + def coordinates(self, hom): + """ + Return the coordinates of the map when expressed in terms of the + generators (i. e., the output of the ``gens`` method) of the + hom space. + + INTPUT: + + - ``hom`` - QuiverRepHom + + OUTPUT: + + - list, the coordinates of the given map when written in terms of the + generators of the QuiverHomSpace + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: S = Q.S(QQ, 2) + sage: P = Q.P(QQ, 1) + sage: H = S.Hom(P) + sage: f = S.hom({2: [[1,-1]]}, P) + sage: H.coordinates(f) + [1, -1] + """ + + #Use the coordinates function on space + return self._space.coordinates(hom._vector) + + ########################################################################### + # # + # CONSTRUCTION FUNCTIONS # + # These functions create and return modules and homomorphisms. # + # # + ########################################################################### + + def _an_element_(self): + """ + Returns a homomorphism in the Hom space. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: S = Q.S(QQ, 2) + sage: P = Q.P(QQ, 1) + sage: H = S.Hom(P) + sage: H.an_element() in H # indirect doctest + True + """ + return self.element_class(self._domain, self._codomain, self._space.an_element()) + + def left_module(self, basis=False): + """ + Create the QuiverRep of ``self`` as a module over the opposite + quiver. + + INPUT: + + - ``basis`` - bool. If ``False``, then only the module is + returned. If ``True``, then a tuple is returned. The first + element is the QuiverRep and the second element is a + dictionary which associates to each vertex a list. The + elements of this list are the homomorphisms which correspond to + the basis elements of that vertex in the module. + + OUTPUT: + + - QuiverRep or tuple + + .. WARNING:: + + The codomain of the Hom space must be a left module. + + .. NOTE:: + + The left action of a path `e` on a map `f` is given by + `(ef)(m) = ef(m)`. This gives the Hom space its structure as + a left module over the path algebra. This is then converted to + a right module over the path algebra of the opposite quiver + ``Q.reverse()`` and returned. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b'], 3: ['c', 'd']}, 2:{3:['e']}}).path_semigroup() + sage: P = Q.P(GF(3), 3) + sage: A = Q.free_module(GF(3)) + sage: H = P.Hom(A) + sage: H.dimension() + 6 + sage: M, basis_dict = H.left_module(true) + sage: M.dimension_vector() + (4, 1, 1) + sage: Q.reverse().P(GF(3), 3).dimension_vector() + (4, 1, 1) + + As lists start indexing at 0 the `i`-th vertex corresponds to the + `(i-1)`-th entry of the dimension vector:: + + sage: len(basis_dict[2]) == M.dimension_vector()[1] + True + """ + + from sage.quivers.representation import QuiverRep + if not self._codomain.is_left_module(): + raise ValueError("The codomain must be a left module.") + + # Create the spaces + spaces = {} + for v in self._quiver: + im_gens = [self([self._codomain.left_edge_action((v, v), f(x)) + for x in self._domain.gens()])._vector + for f in self.gens()] + spaces[v] = self._space.submodule(im_gens) + + # Create the maps + maps = {} + for e in self._quiver.edges(): + e_op = (e[1], e[0], e[2]) + maps[e_op] = [] + for vec in spaces[e[1]].gens(): + vec_im = spaces[e_op[1]].coordinate_vector(self([self._codomain.left_edge_action(e, self(vec)(x)) + for x in self._domain.gens()])._vector) + maps[e_op].append(vec_im) + + # Create and return the module (and the dict if desired) + if basis: + basis_dict = {} + for v in self._quiver: + basis_dict[v] = [self.element_class(self._domain, self._codomain, vec) for vec in spaces[v].gens()] + return (QuiverRep(self._base, self._semigroup.reverse(), spaces, maps), basis_dict) + else: + return QuiverRep(self._base, self._semigroup.reverse(), spaces, maps) + diff --git a/src/sage/quivers/morphism.py b/src/sage/quivers/morphism.py new file mode 100644 index 00000000000..7ed209c1dbb --- /dev/null +++ b/src/sage/quivers/morphism.py @@ -0,0 +1,1297 @@ +from sage.categories.morphism import CallMorphism +from sage.matrix.constructor import Matrix + +class QuiverRepHom(CallMorphism): + r""" + A homomorphism of quiver representations (of one and the same quiver) + is given by specifying, for each vertex of the quiver, a homomorphism + of the spaces assigned to this vertex such that these homomorphisms + commute with the edge maps. The domain and codomain of the + homomorphism are required to be representations of the same quiver + over the same base ring. + + INPUT: + + - ``domain`` - QuiverRep, the domain of the homomorphism + + - ``codomain`` - QuiverRep, the codomain of the homomorphism + + - ``data`` - dict, list, or QuiverRepElement (default: empty dict), + with the following meaning: + - list: ``data`` can be a list of images for the generators of + the domain. "Generators" means the output of the ``gens()`` + method. An error will be generated if the map so defined + is not equivariant with respect to the action of the quiver. + - dictionary: ``data`` can be a dictionary associating to each + vertex of the quiver either 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, and keys not corresponding + to vertices of the quiver are ignored. An error will be + generated if these maps do not commute with the edge maps of + the domain and codomain. + - QuiverRepElement: if the domain is a QuiverRep_with_path_basis + then ``data`` can be a single QuiverRepElement belonging to + the codomain. The map is then defined by sending each path, + ``p``, in the basis to ``data*p``. If ``data`` is not an + element of the codomain or the domain is not a + QuiverRep_with_path_basis then an error will be generated. + - QuiverRepHom: the input can also be a map `f : D \to C` such + that there is a coercion from the domain of ``self`` to ``D`` + and from ``C`` to the codomain of ``self``. The composition + of these maps is the result. + + OUTPUT: + + - QuiverRepHom + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + + With no additional data this creates the zero map:: + + sage: f = S.hom(M) + sage: f.is_zero() + True + + We must specify maps at the vertices to get a nonzero homomorphism. Note that + if the dimensions of the spaces assigned to the domain and codomain of a vertex + are equal then Sage will construct the identity matrix from ``1``:: + + sage: maps2 = {2:[1, -1], 3:1} + sage: g = S.hom(maps2, M) + + Here we create the same map by specifying images for the generators:: + + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: h = S.hom([x, y], M) + sage: g == h + True + + If the domain is a module of type QuiverRep_with_path_basis (for example, the + indecomposable projectives) we can create maps by specifying a single image:: + + sage: Proj = Q.P(GF(7), 3) + sage: Simp = Q.S(GF(7), 3) + sage: im = Simp({3: (1,)}) + sage: Proj.hom(im, Simp).is_surjective() + True + """ + + ########################################################################### + # # + # PRIVATE FUNCTIONS # + # These functions are not meant to be seen by the end user. # + # # + ########################################################################### + + def __init__(self, domain, codomain, data={}): + """ + Type QuiverRepHom? for more information. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: f = S.hom(M) + sage: f.is_zero() + True + sage: maps2 = {2:[1, -1], 3:1} + sage: g = S.hom(maps2, M) + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: h = S.hom([x, y], M) + sage: g == h + True + sage: Proj = Q.P(GF(7), 3) + sage: Simp = Q.S(GF(7), 3) + sage: im = Simp({3: (1,)}) + sage: Proj.hom(im, Simp).is_surjective() + True + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: H1 = Q.P(GF(3), 2).Hom(Q.S(GF(3), 2)) + sage: H2 = Q.P(GF(3), 2).Hom(Q.S(GF(3), 1)) + sage: H1.an_element() in H1 # indirect doctest + True + + """ + # 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 data 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. + + from sage.quivers.representation import QuiverRepElement, QuiverRep_with_path_basis + + 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 data is a vector + if data in self._base_ring**total_dim: + self._vector = data + self._assert_valid_hom() + super(QuiverRepHom, self).__init__(domain.Hom(codomain)) + return + + # If data is not a dict, create one + if isinstance(data, dict): + maps_dict = data + else: + # If data is not a list create one, then create a dict from it + if isinstance(data, list): + im_list = data + else: + # If data is a QuiverRepHom, create a list from it + if isinstance(data, QuiverRepHom): + f = data._domain.coerce_map_from(domain) + g = self._codomain.coerce_map_from(data._codomain) + im_list = [g(data(f(x))) for x in domain.gens()] + + # The only case left is that data is a QuiverRepElement + else: + if not isinstance(data, QuiverRepElement): + raise TypeError("Input data must be dictionary, list, " + + "QuiverRepElement or vector.") + if not isinstance(domain, QuiverRep_with_path_basis): + raise TypeError("If data is a QuiverRepElement then domain " + + "must be a QuiverRep_with_path_basis.") + if data not in codomain: + raise ValueError("If data is a QuiverRepElement then it must " + + "be an element of codomain.") + im_list = [codomain.right_edge_action(data, p) for v in domain._quiver for p in domain._bases[v]] + + # WARNING: This code assumes that the function QuiverRep.gens() returns + # the generators ordered first by vertex and then by the order of the + # gens() method of the space associated to that vertex. In particular + # this is the order that corresponds to how maps are represented via + # matrices + + # Get the gens of the domain and check that im_list is the right length + dom_gens = domain.gens() + if len(im_list) != len(dom_gens): + raise ValueError("Domain is dimension " + str(len(dom_gens)) + " but only " + str(len(im_list)) + + " images were supplied.") + + # Get the matrices of the maps + start_index = 0 + maps_dict = {} + for v in self._quiver: + maps_dict[v] = [] + dim = domain._spaces[v].dimension() + for i in range(start_index, start_index + dim): + if len(im_list[i].support()) != 0 and im_list[i].support() != [v]: + # If the element doesn't have the correct support raise + # an error here, otherwise we might create a valid hom + # that does not map the generators to the supplied + # images + raise ValueError("Generator supported at vertex " + str(v) + + " cannot map to element with support " + str(im_list[i].support())) + else: + # If the support works out add the images coordinates + # as a row of the matrix + maps_dict[v].append(codomain._spaces[v].coordinates(im_list[i]._elems[v])) + + start_index += dim + + # Get the coordinates of the vector + from sage.categories.morphism import is_Morphism + vector = [] + for v in self._quiver: + if v in maps_dict: + if is_Morphism(maps_dict[v]): + if hasattr(maps_dict[v], 'matrix'): + m = maps_dict[v].matrix() + else: + gens_images = [codomain._spaces[v].coordinate_vector(maps_dict[v](x)) + for x in domain._spaces[v].gens()] + m = Matrix(self._base_ring, domain_dims[v], codomain_dims[v], gens_images) + else: + m = Matrix(self._base_ring, domain_dims[v], codomain_dims[v], maps_dict[v]) + else: + m = Matrix(self._base_ring, domain_dims[v], codomain_dims[v]) + for i in range(0, domain_dims[v]): + vector += list(m[i]) + + # Wrap as a vector, check it, and return + self._vector = (self._base_ring**total_dim)(vector) + self._assert_valid_hom() + super(QuiverRepHom, self).__init__(domain.Hom(codomain)) + + def _repr_(self): + """ + Default string representation. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: S.hom(M) # indirect doctest + Homomorphism of representations of Multi-digraph on 3 vertices + """ + + return "Homomorphism of representations of " + self._quiver.__repr__() + + def _call_(self, x): + """ + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: h = S.hom([x, y], M) + sage: h(S.gens()[0]) == x + True + sage: h(S.gens()[1]) == y + True + + The following was an issue during work on :trac:`12630`:: + + sage: Q = DiGraph({1: {}}).path_semigroup() + sage: M = Q.I(GF(3), 1) + sage: m = M.an_element() + sage: R = M.quotient(M) + sage: R(m) + Element of quiver representation + + """ + + from sage.quivers.representation import QuiverRepElement + # Check the input + if not isinstance(x, QuiverRepElement): + raise ValueError("QuiverRepHom can only be called on QuiverRepElement") + + elements = dict((v, self.get_map(v)(x._elems[v])) for v in self._quiver) + return self._codomain(elements) + + def __add__(left, right): + """ + This function overloads the + operator. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = M({2: (1, -1)}) + sage: z = M.zero() + sage: h = S.hom([x, z], M) + sage: g = S.hom([z, z], M) + sage: f = g + h + sage: f(S.gens()[0]) == x + True + sage: f(S.gens()[1]) == z + True + """ + + new_vector = left._vector + right._vector + return left._domain.hom(new_vector, left._codomain) + + def __iadd__(self, other): + """ + This function overloads the += operator. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = M({2: (1, -1)}) + sage: z = M.zero() + sage: h = S.hom([x, z], M) + sage: g = S.hom([z, z], M) + sage: g += h + sage: g(S.gens()[0]) == x + True + sage: g(S.gens()[1]) == z + True + """ + + self._vector += other._vector + + return self + + def __sub__(left, right): + """ + This function overloads the - operator. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: z = M.zero() + sage: h = S.hom([x, z], M) + sage: g = S.hom([z, y], M) + sage: f = h - g + sage: f(S.gens()[0]) == x + True + sage: f(S.gens()[1]) == -y + True + """ + + new_vector = left._vector - right._vector + return left._domain.hom(new_vector, left._codomain) + + def __isub__(self, other): + """ + This function overloads the -= operator. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: z = M.zero() + sage: h = S.hom([x, z], M) + sage: g = S.hom([z, y], M) + sage: h -= g + sage: h(S.gens()[0]) == x + True + sage: h(S.gens()[1]) == -y + True + """ + + self._vector -= other._vector + + return self + + def __neg__(self): + """ + This function overrides the unary - operator + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: h = S.hom([x, y], M) + sage: g = -h + sage: g(S.gens()[0]) == -x + True + sage: g(S.gens()[1]) == -y + True + """ + + return self._domain.hom(-self._vector, self._codomain) + + def __pos__(self): + """ + This function overrides the unary + operator + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: h = S.hom([x, y], M) + sage: g = +h + sage: g == h + True + """ + + return self + + def __eq__(self, other): + """ + This function overrides the == operator + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: g = S.hom([x, y], M) + sage: h = S.hom([x, y], M) + sage: g == h + True + """ + + from sage.quivers.morphism import QuiverRepHom + # 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 + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = M({2: (1, -1)}) + sage: y = M({3: (1,)}) + sage: z = M.zero() + sage: g = S.hom([x, y], M) + sage: h = S.hom([x, z], M) + sage: g != h + True + """ + + from sage.quivers.morphism import QuiverRepHom + # 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 + + def __mul__(self, other): + """ + This function overrides the * operator + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: x = S.gens()[0] + sage: y = S.gens()[1] + sage: g = S.hom([x, y], S) + sage: h = S.hom(S) + sage: (g*h).is_zero() + True + """ + + maps = dict((v, other.get_matrix(v)*self.get_matrix(v)) for v in self._quiver) + return other._domain.hom(maps, self._codomain) + + ########################################################################### + # # + # 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: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^1, 3: QQ^1} + sage: S = Q.representation(QQ, spaces2) + sage: maps2 = {2:[1, -1], 3:1} + sage: g = S.hom(maps2, M) # indirect doctest + sage: f = S.hom(maps2, S) # indirect doctest + Traceback (most recent call last): + ... + TypeError: Unable to coerce x (={...}) to a morphism in Dimension 2 QuiverHomSpace + + """ + + # 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.") + + ########################################################################### + # # + # ACCESS FUNCTIONS # + # These functions are used to view the homomorphism data. # + # # + ########################################################################### + + def domain(self): + """ + Return the domain of the homomorphism. + + OUTPUT: + + - QuiverRep, the domain + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: S = Q.representation(QQ) + sage: g = M.hom(S) + sage: g.domain() is M + True + """ + + return self._domain + + def codomain(self): + """ + Return the codomain of the homomorphism. + + OUTPUT: + + - QuiverRep, the codomain + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: S = Q.representation(QQ) + sage: g = S.hom(M) + sage: g.codomain() is M + True + """ + + return self._codomain + + def get_matrix(self, vertex): + """ + Return the matrix of the homomorphism attached to vertex + ``vertex``. + + INPUT: + + - ``vertex`` - integer, a vertex of the quiver + + OUTPUT: + + - matrix, the matrix representing the homomorphism associated to the given + vertex + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: I = Q.I(QQ, 3) + sage: M = I/I.radical() + sage: f = M.coerce_map_from(I) + sage: f.get_matrix(1) + [1 0] + [0 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 + return Matrix(self._base_ring, rows, cols, self._vector.list()[startdim:startdim + rows*cols]) + + def get_map(self, vertex): + """ + Return the homomorphism at the given vertex ``vertex``. + + INPUT: + + - ``vertex`` - integer, a vertex of the quiver + + OUTPUT: + + - homomorphism, the homomorphism associated to the given vertex + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: S = P/P.radical() + sage: f = S.coerce_map_from(P) + sage: f.get_map(1).is_bijective() + True + """ + + return self._domain._spaces[vertex].hom(self.get_matrix(vertex), self._codomain._spaces[vertex]) + + 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: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: f = P.hom({1: 1, 2: 1, 3: 1}, P) + sage: f.quiver() is Q.quiver() + 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: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: f = P.hom({1: 1, 2: 1, 3: 1}, P) + sage: f.base_ring() is QQ + True + """ + + return self._base_ring + + ########################################################################### + # # + # DATA FUNCTIONS # + # These functions return data collected from the homomorphism. # + # # + ########################################################################### + + def is_injective(self): + """ + Test whether the homomorphism is injective. + + OUTPUT: + + - bool, True if the homomorphism is injective, False otherwise + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: f = P.hom({1: 1, 2: 1, 3: 1}, P) + sage: f.is_injective() + True + sage: g = P.hom(P) + sage: g.is_injective() + 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_surjective(self): + """ + Test whether the homomorphism is surjective. + + OUTPUT: + + - bool, True if the homomorphism is surjective, False otherwise + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: f = P.hom({1: 1, 2: 1, 3: 1}, P) + sage: f.is_surjective() + True + sage: g = P.hom(P) + sage: g.is_surjective() + 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): + """ + Test whether the homomorphism is an isomorphism. + + OUTPUT: + + - bool, ``True`` if the homomorphism is bijective, ``False`` + otherwise + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: f = P.hom({1: 1, 2: 1, 3: 1}, P) + sage: f.is_isomorphism() + True + sage: g = P.hom(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): + """ + Test whether the homomorphism is the zero homomorphism. + + OUTPUT: + + - bool, ``True`` if the homomorphism is zero, ``False`` otherwise + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: f = P.hom({1: 1, 2: 1, 3: 1}, P) + sage: f.is_zero() + False + sage: g = P.hom(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): + """ + Test whether the homomorphism is an endomorphism. + + OUTPUT: + + - bool, ``True`` if the domain equals the codomain, ``False`` + otherwise + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: f = P.hom({1: 1, 2: 1, 3: 1}, P) + sage: f.is_endomorphism() + True + sage: S = P/P.radical() + sage: g = S.coerce_map_from(P) + sage: g.is_endomorphism() + False + """ + + return self._domain == self._codomain + + def rank(self): + """ + Return the rank of the homomorphism ``self`` (as a `k`-linear + map). + + OUTPUT: + + - integer, the rank + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: S = P/P.radical() + sage: f = S.coerce_map_from(P) + sage: assert(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, representations, and # + # elements from the given homomorphism. # + # # + ########################################################################### + + def kernel(self): + """ + Return the kernel of ``self``. + + OUTPUT: + + - QuiverRep, the kernel + + .. NOTE:: + + To get the inclusion map of the kernel, ``K``, into the + domain, ``D``, use ``D.coerce_map_from(K)``. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^2, 3: QQ^1} + sage: N = Q.representation(QQ, spaces2, {(2, 3, 'c'): [[1], [0]]}) + sage: maps2 = {2:[[1, 0], [0, 0]], 3:1} + sage: g = N.hom(maps2, M) + sage: g.kernel().dimension_vector() + (0, 1, 0) + """ + + spaces = dict((v, self.get_map(v).kernel()) for v in self._quiver) + return self._domain._submodule(spaces) + + def image(self): + """ + Return the image of ``self``. + + OUTPUT: + + - QuiverRep, the image + + .. NOTE:: + + To get the inclusion map of the image, ``I``, into the + codomain, ``C``, use ``C.coerce_map_from(I)``. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^2, 3: QQ^1} + sage: N = Q.representation(QQ, spaces2, {(2, 3, 'c'): [[1], [0]]}) + sage: maps2 = {2:[[1, 0], [0, 0]], 3:1} + sage: g = N.hom(maps2, M) + sage: g.image().dimension_vector() + (0, 1, 1) + """ + + spaces = dict((v, self.get_map(v).image()) for v in self._quiver) + return self._codomain._submodule(spaces) + + def cokernel(self): + """ + Return the cokernel of ``self``. + + OUTPUT: + + - QuiverRep, the cokernel + + .. NOTE:: + + To get the factor map of the codomain, ``D``, onto the + cokernel, ``C``, use ``C.coerce_map_from(D)``. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + 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 = Q.representation(QQ, spaces, maps) + sage: spaces2 = {2: QQ^2, 3: QQ^1} + sage: N = Q.representation(QQ, spaces2, {(2, 3, 'c'): [[1], [0]]}) + sage: maps2 = {2:[[1, 0], [0, 0]], 3:1} + sage: g = N.hom(maps2, M) + sage: g.cokernel().dimension_vector() + (2, 1, 0) + """ + + return self._codomain.quotient(self.image()) + + def linear_dual(self): + r""" + Compute the linear dual `Df : DN \to DM` of + ``self`` = `f : M \to N` where `D(-) = Hom_k(-, k)`. + + OUTPUT: + + - QuiverRepHom, the map `Df : DN \to DM` + + .. NOTE:: + + If `e` is an edge of the quiver `Q` and `g` is an element of + `Hom_k(N, k)` then we let `(ga)(m) = g(ma)`. This gives + `Hom_k(N, k)` its structure as a module over the opposite + quiver ``Q.reverse()``. The map `Hom_k(N, k) \to Hom_k(M, k)` + returned sends `g` to `gf`. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: S = P/P.radical() + sage: f = S.coerce_map_from(P) + + The dual of a surjective map is injective and vice versa:: + + sage: f.is_surjective() + True + sage: g = f.linear_dual() + sage: g.is_injective() + True + + The dual of a right module is a left module for the same quiver, Sage + represents this as a right module for the opposite quiver:: + + sage: g.quiver().path_semigroup() is Q.reverse() + True + + The double dual of a map is the original representation:: + + sage: g.linear_dual() == 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 self._codomain.linear_dual().hom(maps, self._domain.linear_dual()) + + def algebraic_dual(self): + r""" + Compute the algebraic dual `f^t : N^t \to M^t` of + ``self`` = `f : M \to N` where `(-)^t = Hom_Q(-, kQ)`. + + OUTPUT: + + - QuiverRepHom, the map `f^t : N^t \to M^t` + + .. NOTE:: + + If `e` is an edge of the quiver `Q` and `g` is an element of + `Hom_Q(N, kQ)` then we let `(ge)(m) = eg(m)`. This gives + `Hom_Q(N, kQ)` its structure as a module over the opposite + quiver ``Q.reverse()``. The map + `Hom_Q(N, kQ) \to Hom_Q(M, kQ)` returned sends `g` to `gf`. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b','c','d']}, 2:{4:['e','f']}, 3:{4:['g']}, 5:{2:['h','i']}}).path_semigroup() + sage: P1 = Q.P(QQ, 4) + sage: P1.algebraic_dual() + Representation with dimension vector (5, 2, 1, 1, 4) + + The algebraic dual of an indecomposable projective is the indecomposable + projective of the same vertex in the opposite quiver. + + sage: Q.reverse().P(QQ, 4) + Representation with dimension vector (5, 2, 1, 1, 4) + """ + + # Get the domain, its basis, and the codomain + domain, domain_gens = self._codomain.algebraic_dual(True) + codomain, co_domain_gens = self._domain.algebraic_dual(True) + + # Find the images in the domain and create the module + # H = QuiverHomSpace(self._domain, self._quiver.free_module(self._base_ring)) + im_gens = [codomain({v: (g*self)._vector}) + for v in self._quiver for g in domain_gens[v]] + return domain.hom(im_gens, codomain) + + def direct_sum(self, maps, return_maps=False, pinch=None): + r""" + Return the direct sum of ``self`` with the maps in the list ``maps``. + + INPUT: + + - ``maps`` - QuiverRepHom or list of QuiverRepHoms + + - ``return_maps`` - bool (default: ``False``). If ``False``, then + the return value is a QuiverRepHom which is the direct sum of + ``self`` with the QuiverRepHoms in ``maps``. + If ``True``, then the return value is a tuple of length either 3 + or 5. The first entry of the tuple is the QuiverRepHom giving + the direct sum. If ``pinch`` is either ``None`` or + ``'codomain'`` then the next two entries in the tuple are lists + giving respectively the inclusion and the projection maps for + the factors of the direct sum. Summands are ordered as given + in maps with ``self`` as the zeroth summand. If ``pinch`` is + either ``None`` or ``'domain'`` then the next two entries in the + tuple are the inclusion and projection maps for the codomain. + Thus if ``pinch`` is ``None`` then the tuple will have length 5. + If ``pinch`` is either ``'domain'`` or ``'codomain'`` then the + tuple will have length 3. + + - ``pinch`` - string or ``None`` (default: ``None``). If this is + equal to ``'domain'``, then the domains of ``self`` and the + given maps must be equal. The direct sum of `f: A \to B` and + `g: A \to C` returned is then the map `A \to B \oplus C` defined + by sending `x` to `(f(x), g(x))`. If ``pinch`` equals + ``'codomain'``, then the codomains of ``self`` and the given + maps must be equal. The direct sum of `f: A \to C` and + `g: B \to C` returned is then the map `A \oplus B \to C` defined + by sending `(x, y)` to `f(x) + g(y)`. Finally, if ``pinch`` is + anything other than ``'domain'`` or ``'codomain'``, then the + direct sum of `f: A \to B` and `g: C \to D` returned is the map + `A \oplus C \to B \oplus D` defined by sending `(x, y)` to + `(f(x), g(y))`. + + OUTPUT: + + - QuiverRepHom or tuple + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: P1 = Q.P(GF(3), 1) + sage: P2 = Q.P(GF(3), 2) + sage: S1 = P1/P1.radical() + sage: S2 = P2/P2.radical() + sage: pi1 = S1.coerce_map_from(P1) + sage: pi2 = S2.coerce_map_from(P2) + sage: f = pi1.direct_sum(pi2) + sage: f.domain().dimension_vector() == Q.free_module(GF(3)).dimension_vector() + True + sage: f.is_surjective() + True + sage: id = P1.Hom(P1).identity() + sage: g = pi1.direct_sum(id, pinch='domain') + sage: g.is_surjective() + False + """ + + from sage.quivers.morphism import QuiverRepHom + # Get the list of maps to be summed + if isinstance(maps, QuiverRepHom): + maplist = [self, maps] + else: + maplist = [self] + maps + + # Check that the quivers/base rings are the same. If pinching also + # check that the domain/codomains are correct + for x in maplist: + if not isinstance(x, QuiverRepHom): + raise TypeError("maps must be a QuiverRepHom or list of QuiverRepHoms") + if self._quiver is not x._quiver: + raise ValueError("Cannot direct sum maps from different quivers") + if self._base_ring is not x._base_ring: + raise ValueError("Base rings must be identical") + if pinch == 'domain' and self._domain is not x._domain: + raise ValueError("Cannot pinch maps, domains do not agree") + if pinch == 'codomain' and self._codomain is not x._codomain: + raise ValueError("Cannot pinch maps, codomains do not agree") + + # Get the sums and their maps + if pinch == 'domain': + domain = self._domain + else: + domain, d_incl, d_proj = self._domain.direct_sum([x._domain for x in maplist[1:]], return_maps=True) + if pinch == 'codomain': + codomain = self._codomain + else: + codomain, c_incl, c_proj = self._codomain.direct_sum([x._codomain for x in maplist[1:]], return_maps=True) + + # Start with the zero map + result = domain.hom(codomain) + + # Add each factor + for i in range(0, len(maplist)): + if pinch == 'domain': + result += c_incl[i]*maplist[i] + elif pinch == 'codomain': + result += maplist[i]*d_proj[i] + else: + result += c_incl[i]*maplist[i]*d_proj[i] + + # Return the results + if return_maps: + if pinch == 'domain': + return (result, c_incl, c_proj) + elif pinch == 'codomain': + return (result, d_incl, d_proj) + else: + return (result, d_incl, d_proj, c_incl, c_proj) + else: + return result + + def lift(self, x): + """ + Given an element `x` of the image, return an element of the domain + that maps onto it under ``self``. + + INPUT: + + - ``x`` - QuiverRepElement + + OUTPUT: + + - QuiverRepElement + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c','d']}}).path_semigroup() + sage: P = Q.P(RR, 3) + sage: S = P/P.radical() + sage: proj = S.coerce_map_from(P) + sage: x = S.an_element() + sage: y = proj.lift(x) + sage: proj(y) == x + True + sage: zero = S.hom(S, {}) + sage: zero.lift(x) + Traceback (most recent call last): + ... + ValueError: element is not in the image + """ + + # Lift at each vertex + elems = dict((v, self.get_map(v).lift(x._elems[v])) for v in self._quiver) + return self._domain(elems) + + ########################################################################### + # # + # ADDITIONAL OPERATIONS # + # These functions operations that are not implemented via binary # + # operators. # + # # + ########################################################################### + + def scalar_mult(self, scalar): + """ + Return the result of the scalar multiplcation ``scalar*self``, + where ``scalar`` is an element of the base ring `k`. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}}).path_semigroup() + sage: M = Q.P(QQ, 1) + sage: f = M.Hom(M).an_element() + sage: x = M.an_element() + sage: g = f.scalar_mult(6) + sage: g(x) == 6*f(x) + True + """ + + return self._domain.hom(scalar*self._vector, self._codomain) + + def iscalar_mult(self, scalar): + """ + Multiply ``self`` by ``scalar`` in place. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}}).path_semigroup() + sage: M = Q.P(QQ, 1) + sage: f = M.Hom(M).an_element() + sage: x = M.an_element() + sage: y = f(x) + sage: f.iscalar_mult(6) + sage: f(x) == 6*y + True + """ + + self._vector *= scalar diff --git a/src/sage/quivers/path_semigroup.py b/src/sage/quivers/path_semigroup.py new file mode 100644 index 00000000000..c07c61e8cef --- /dev/null +++ b/src/sage/quivers/path_semigroup.py @@ -0,0 +1,868 @@ +from sage.rings.integer_ring import ZZ +from sage.structure.parent import Parent +from sage.structure.unique_representation import UniqueRepresentation +from sage.categories.semigroups import Semigroups +from sage.categories.monoids import Monoids +from sage.categories.infinite_enumerated_sets import InfiniteEnumeratedSets +from sage.categories.finite_enumerated_sets import FiniteEnumeratedSets +from sage.misc.cachefunc import cached_method +from paths import QuiverPath +from representation import QuiverRep + +class PathSemigroup(UniqueRepresentation, Parent): + r""" + The partial semigroup that is given by the directed paths of a quiver, + subject to concatenation. + + See :mod:`~sage.quivers.__init__` for a definition of this + semigroup and of the notion of a path in a quiver. + + Note that a *partial semigroup* here is defined as a set `G` with a + partial binary operation `G \times G \to G \cup \{\mbox{None}\}`, + which is written infix as a `*` sign and satisfies associativity in + the following sense: If `a`, `b` and `c` are three elements of `G`, + and if one of the products `(a*b)*c` and `a*(b*c)` exists, then so + does the other and the two products are equal. A partial semigroup + is not required to have a neutral element (and this one usually has + no such element). + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}) + sage: S = Q.path_semigroup() + sage: S + Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices + sage: S.variable_names() + ('e_1', 'e_2', 'e_3', 'a', 'b', 'c', 'd') + sage: S.gens() + (e_1, e_2, e_3, a, b, c, d) + sage: S.category() + Join of Category of semigroups and Category of finite enumerated sets + + In the test suite, we skip the associativity test, as in this example the + paths used for testing can't be concatenated:: + + sage: TestSuite(S).run(skip=['_test_associativity']) + + If there is only a single vertex, the partial semigroup is a monoid. If + the underlying quiver has cycles or loops, then the (partial) semigroup + only is an infinite enumerated set. This time, there is no need to skip + tests:: + + sage: Q = DiGraph({1:{1:['a', 'b', 'c', 'd']}}) + sage: M = Q.path_semigroup() + sage: M + Monoid formed by the directed paths of Looped multi-digraph on 1 vertex + sage: M.category() + Join of Category of monoids and Category of infinite enumerated sets + sage: TestSuite(M).run() + + """ + + Element = QuiverPath + + def __init__(self, Q): + """ + INPUT: + + - a :class:`~sage.graphs.digraph.DiGraph`. + + EXAMPLES: + + Note that usually a path semigroup is created using + :meth:`sage.graphs.digraph.DiGraph.path_semigroup`. Here, we + demonstrate the direct construction:: + + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}, immutable=True) + sage: from sage.quivers.path_semigroup import PathSemigroup + sage: P = PathSemigroup(Q) + sage: P is DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}).path_semigroup() # indirect doctest + True + sage: P + Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices + + While hardly of any use, it is possible to construct the path + semigroup of an empty quiver (it is, of course, empty):: + + sage: D = DiGraph({}) + sage: A = D.path_semigroup(); A + Partial semigroup formed by the directed paths of Digraph on 0 vertices + sage: A.list() + [] + """ + ######### + ## Verify that the graph labels are acceptable for this implementation ## + # Check that edges are labelled with nonempty strings and don't begin + # with 'e_' or contain '*' + for x in Q.edge_labels(): + if not isinstance(x, str) or x == '': + raise ValueError("Edge labels of the digraph must be nonempty strings.") + if x[0:2] == 'e_': + raise ValueError("Edge labels of the digraph must not begin with 'e_'.") + if x.find('*') != -1: + raise ValueError("Edge labels of the digraph must not contain '*'.") + # Check validity of input: vertices have to be labelled 1,2,3,... and + # edge labels must be unique + for v in Q: + if v not in ZZ: + raise ValueError("Vertices of the digraph must be labelled by integers.") + if len(set(Q.edge_labels())) != Q.num_edges(): + raise ValueError("Edge labels of the digraph must be unique.") + nvert = len(Q.vertices()) + ## Determine the category which this (partial) semigroup belongs to + if Q.is_directed_acyclic() and not Q.has_loops(): + cat = FiniteEnumeratedSets() + else: + cat = InfiniteEnumeratedSets() + names=['e_{0}'.format(v) for v in Q.vertices()] + [e[2] for e in Q.edges()] + self._quiver = Q + if nvert == 1: + Parent.__init__(self, names=names, category=cat.join([cat,Monoids()])) + else: + # TODO: Make this the category of "partial semigroups" + Parent.__init__(self, names=names, category=cat.join([cat,Semigroups()])) + + def _repr_(self): + """ + String representation. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}) + sage: Q.path_semigroup() + Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices + sage: Q = DiGraph({1:{1:['a','b', 'c', 'd']}}) + sage: Q.path_semigroup() + Monoid formed by the directed paths of Looped multi-digraph on 1 vertex + + """ + if self._quiver.num_verts() != 1: + return "Partial semigroup formed by the directed paths of %s"%self._quiver + return "Monoid formed by the directed paths of %s"%self._quiver + + def _coerce_map_from_(self, other): + """ + A coercion from `A` to `B` exists if the underlying quiver + of `A` is a sub-quiver of the underlying quiver of `B` (preserving + names). + + EXAMPLES:: + + sage: Q1 = DiGraph({1:{2:['a','b'], 3:['c']}, 3:{1:['d']}}) + sage: Q2 = DiGraph({1:{2:['a'], 3:['c']}}) + sage: Q3 = DiGraph({1:{2:['a','x'], 3:['c']}, 3:{1:['d']}}) + sage: P1 = Q1.path_semigroup() + sage: P2 = Q2.path_semigroup() + sage: P3 = Q3.path_semigroup() + sage: P1.has_coerce_map_from(P2) # indirect doctest + True + sage: P1.has_coerce_map_from(P3) + False + sage: d = P1([(3,1,'d')]); d + d + sage: c = P2([(1,3,'c')]); c + c + sage: c.parent() is P1 + False + sage: c in P1 # indirect doctest + True + sage: d*c # indirect doctest + d*c + sage: (d*c).parent() is P1 + True + sage: c3 = P3([(1,3,'c')]); c3 + c + sage: c3 in P1 # indirect doctest + False + sage: d*c3 + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': + 'Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices' + and 'Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices' + + """ + if not isinstance(other, PathSemigroup): + return + # This is what we would like to do: + # return other.quiver().is_subgraph(self._quiver, induced=False) + # However, this is deprecated for non-induced subgraphs + # of directed multi-graphs. + # + # We ignore the deprecation and do what the deprecated method is doing + # internally, directly using the backend to make things faster. + sQ = self._quiver._backend + oQ = other.quiver()._backend + if sQ.num_verts() < oQ.num_verts(): + return False + if any(not sQ.has_vertex(v) for v in oQ.iterator_verts(None)): + return False + return all(sQ.has_edge(*e) for e in oQ.iterator_out_edges(oQ.iterator_verts(None), True)) + + @cached_method + def arrows(self): + """ + The elements corresponding to edges of the underlying quiver. + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a','b'], 3:['c']}, 3:{1:['d']}}).path_semigroup() + sage: P.arrows() + (a, b, c, d) + """ + return tuple(self([e]) for e in self._quiver.edges()) + + @cached_method + def idempotents(self): + """ + The idempotents corresponding to the vertices of the underlying quiver. + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a','b'], 3:['c']}, 3:{1:['d']}}).path_semigroup() + sage: P.idempotents() + (e_1, e_2, e_3) + + """ + return tuple(self((v,v)) for v in self._quiver.vertices()) + + def ngens(self): + """ + The number of generators (:meth:`arrows` and :meth:`idempotents`) + + EXAMPLES:: + + sage: F = DiGraph({1:{2:['a','b'], 3:['c']}, 3:{1:['d']}}).path_semigroup() + sage: F.ngens() + 7 + + """ + Q = self._quiver + return Q.num_verts() + Q.num_edges() + + @cached_method + def gen(self, i): + """ + Generator number `i` + + INPUT: + + - ``i`` -- integer + + OUTPUT: + + An idempotent, if `i` is smaller than the number of vertices, + or an arrow otherwise. + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a','b'], 3:['c']}, 3:{1:['d']}}).path_semigroup() + sage: P.1 # indirect doctest + e_2 + sage: P.idempotents()[1] + e_2 + sage: P.5 + c + sage: P.gens()[5] + c + + """ + Q = self._quiver + nv = Q.num_verts() + if i < nv: + v = Q.vertices()[i] + return self((v,v)) + e = Q.edges()[i-nv] + return self([e]) + + def gens(self): + """ + The tuple of generators. + + .. NOTE: + + This coincides with the sum of the output of + :meth:`idempotents` and :meth:`arrows`. + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a','b'], 3:['c']}, 3:{1:['d']}}).path_semigroup() + sage: P.gens() + (e_1, e_2, e_3, a, b, c, d) + sage: P.gens() == P.idempotents() + P.arrows() + True + + """ + return self.idempotents() + self.arrows() + + def is_finite(self): + """ + This partial semigroup is finite if and only if the underlying + quiver is acyclic. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}) + sage: Q.path_semigroup().is_finite() + True + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 3:{1:['d']}}) + sage: Q.path_semigroup().is_finite() + False + + """ + return self._quiver.is_directed_acyclic() and not self._quiver.has_loops() + + def __len__(self): + """ + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}) + sage: F = Q.path_semigroup() + sage: len(F) + 9 + sage: list(F) + [e_1, e_2, e_3, a, b, c, d, a*d, b*d] + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 3:{1:['d']}}) + sage: F = Q.path_semigroup() + sage: len(F) + Traceback (most recent call last): + ... + ValueError: The underlying quiver has cycles, thus, there may be an infinity of directed paths + + """ + return len(self.all_paths()) + + def cardinality(self): + """ + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}) + sage: F = Q.path_semigroup() + sage: F.cardinality() + 9 + sage: A = F.algebra(QQ) + sage: list(A.basis()) + [e_1, e_2, e_3, a, b, c, d, a*d, b*d] + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 3:{1:['d']}}) + sage: F = Q.path_semigroup() + sage: F.cardinality() + +Infinity + sage: A = F.algebra(QQ) + sage: list(A.basis()) + Traceback (most recent call last): + ... + NotImplementedError: infinite list + + """ + from sage.all import ZZ + if self._quiver.is_directed_acyclic() and not self._quiver.has_loops(): + return ZZ(len(self)) + from sage.rings.infinity import Infinity + return Infinity + + def __iter__(self): + """ + Iterate over the elements of ``self``, i.e., over quiver paths. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}) + sage: P = Q.path_semigroup() + sage: list(P) + [e_1, e_2, e_3, a, b, c, d, a*d, b*d] + + The elements are sorted by length. Of course, the list of elements + is infinite for quivers with cycles. :: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['d']}, 3:{1:['c']}}) + sage: P = Q.path_semigroup() + sage: P.is_finite() + False + sage: list(P) + Traceback (most recent call last): + ... + ValueError: The underlying quiver has cycles, thus, there may be an infinity of directed paths + + However, one can iterate:: + + sage: counter = 0 + sage: for p in P: + ....: counter += 1 + ....: print p + ....: if counter==20: + ....: break + e_1 + e_2 + e_3 + a + b + d + c + a*d + b*d + d*c + c*a + c*b + a*d*c + b*d*c + d*c*a + d*c*b + c*a*d + c*b*d + a*d*c*a + a*d*c*b + + """ + d = 0 + length_d_available = True + while length_d_available: + length_d_available = False + for v in self._quiver.vertices(): + for w in self.iter_paths_by_length_and_startpoint(d,v): + length_d_available = True + yield w + d += 1 + + def iter_paths_by_length_and_startpoint(self, d, v): + """ + An iterator over quiver paths with a fixed length and start point. + + INPUT: + + - ``d`` -- an integer, the path length + - ``v`` -- a vertex, start point of the paths + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['d']}, 3:{1:['c']}}) + sage: P = Q.path_semigroup() + sage: P.is_finite() + False + sage: list(P.iter_paths_by_length_and_startpoint(4,1)) + [a*d*c*a, a*d*c*b, b*d*c*a, b*d*c*b] + sage: list(P.iter_paths_by_length_and_startpoint(5,1)) + [a*d*c*a*d, a*d*c*b*d, b*d*c*a*d, b*d*c*b*d] + sage: list(P.iter_paths_by_length_and_startpoint(5,2)) + [d*c*a*d*c, d*c*b*d*c] + + TEST:: + + sage: Q = DiGraph({1:{1:['a','b', 'c', 'd']}}) + sage: P = Q.path_semigroup() + sage: list(P.iter_paths_by_length_and_startpoint(2,1)) + [a*a, + a*b, + a*c, + a*d, + b*a, + b*b, + b*c, + b*d, + c*a, + c*b, + c*c, + c*d, + d*a, + d*b, + d*c, + d*d] + sage: len(list(P.iter_paths_by_length_and_startpoint(2,1))) + 16 + + """ + # iterate over length d paths starting at vertex v + if not d>=0: + raise ValueError("Path length must be a non-negative integer") + if v not in self._quiver: + raise ValueError("The starting point %s is not a vertex of the underlying quiver"%v) + if not d: + yield self([(v,v)],check=False) + else: + for w in self.iter_paths_by_length_and_startpoint(d-1, v): + for a in self._quiver._backend.iterator_out_edges([w.terminal_vertex()],True): + yield self(list(w)+[a],check=False) + + def iter_paths_by_length_and_endpoint(self, d, v): + """ + An iterator over quiver paths with a fixed length and end point. + + INPUT: + + - ``d`` -- an integer, the path length + - ``v`` -- a vertex, end point of the paths + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['d']}, 3:{1:['c']}}) + sage: F = Q.path_semigroup() + sage: F.is_finite() + False + sage: list(F.iter_paths_by_length_and_endpoint(4,1)) + [c*a*d*c, c*b*d*c] + sage: list(F.iter_paths_by_length_and_endpoint(5,1)) + [d*c*a*d*c, d*c*b*d*c] + sage: list(F.iter_paths_by_length_and_endpoint(5,2)) + [c*a*d*c*a, c*b*d*c*a, c*a*d*c*b, c*b*d*c*b] + + """ + # iterate over length d paths ending at vertex v + if not d>=0: + raise ValueError("Path length must be a non-negative integer") + if v not in self._quiver: + raise ValueError("The starting point %s is not a vertex of the underlying quiver"%v) + if not d: + yield self([(v,v)],check=False) + else: + for w in self.iter_paths_by_length_and_endpoint(d-1, v): + for a in self._quiver._backend.iterator_in_edges([w.initial_vertex()],True): + yield self([a]+list(w),check=False) + + def quiver(self): + """ + The underlying quiver (i.e., digraph) of this path semigroup. + + .. NOTE: + + The returned digraph always is an immutable copy of the originally + given digraph. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['d']}, 3:{1:['c']}}) + sage: F = Q.path_semigroup() + sage: F.quiver() == Q + True + """ + return self._quiver + + def reverse(self): + """ + The path semigroup of the reverse quiver. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['d']}, 3:{1:['c']}}) + sage: F = Q.path_semigroup() + sage: F.reverse() is Q.reverse().path_semigroup() + True + + """ + return self._quiver.reverse().path_semigroup() + + def algebra(self, k): + """ + Path algebra of the underlying quiver. + + INPUT: + + - ``k`` -- a commutative ring + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['d']}, 3:{1:['c']}}) + sage: P = Q.path_semigroup() + sage: P.algebra(GF(3)) + Path algebra of Multi-digraph on 3 vertices over Finite Field of size 3 + + """ + from sage.quivers.algebra import PathAlgebra + return PathAlgebra(k, self) + + ########################################################################### + # # + # REPRESENTATION THEORETIC FUNCTIONS # + # These functions involve the representation theory of quivers. # + # # + ########################################################################### + + def representation(self, k, *args, **kwds): + """ + Return a representation of the quiver. + + For more information see the + :class:`~sage.quivers.representation.QuiverRep` documentation. + + TESTS:: + + sage: Q = DiGraph({1:{3:['a']}, 2:{3:['b']}}).path_semigroup() + sage: spaces = {1: QQ^2, 2: QQ^3, 3: QQ^2} + sage: maps = {(1, 3, 'a'): (QQ^2).Hom(QQ^2).identity(), (2, 3, 'b'): [[1, 0], [0, 0], [0, 0]]} + sage: M = Q.representation(QQ, spaces, maps) + sage: M + Representation with dimension vector (2, 3, 2) + + """ + return QuiverRep(k, self, *args, **kwds) + + def S(self, k, vertex): + """ + Returns the simple module over `k` at the given vertex + ``vertex``. + + This module is literally simple only when `k` is a field. + + INPUT: + + - `k` -- ring, the base ring of the representation + + - ``vertex`` -- integer, a vertex of the quiver + + OUTPUT: + + - :class:`~sage.quivers.representation.QuiverRep`, the simple module + at ``vertex`` with base ring `k` + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a','b']}, 2:{3:['c','d']}}).path_semigroup() + sage: S1 = P.S(GF(3), 1) + sage: P.S(ZZ, 3).dimension_vector() + (0, 0, 1) + sage: P.S(ZZ, 1).dimension_vector() + (1, 0, 0) + + The vertex given must be a vertex of the quiver:: + + sage: P.S(QQ, 4) + Traceback (most recent call last): + ... + ValueError: Must specify a valid vertex of the quiver. + """ + if vertex not in self._quiver: + 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): + """ + Return the indecomposable projective module over `k` at the given + vertex ``vertex``. + + This module is literally indecomposable only when `k` is a field. + + INPUT: + + - `k` -- ring, the base ring of the representation + + - ``vertex`` -- integer, a vertex of the quiver + + OUTPUT: + + - :class:`~sage.quivers.representation.QuiverRep`, the indecomposable + projective module at ``vertex`` with base ring `k` + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c','d']}}).path_semigroup() + 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. + """ + if vertex not in self._quiver: + raise ValueError("Must specify a valid vertex of the quiver.") + return QuiverRep(k, self, [[(vertex, vertex)]], option='paths') + + def I(self, k, vertex): + """ + Return the indecomposable injective module over `k` at the + given vertex ``vertex``. + + This module is literally indecomposable only when `k` is a field. + + INPUT: + + - `k` - ring, the base ring of the representation + + - ``vertex`` - integer, a vertex of the quiver + + OUTPUT: + + - :class:`~sage.quivers.representation.QuiverRep`, the indecomposable + injective module at vertex ``vertex`` with base ring `k` + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c','d']}}).path_semigroup() + 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. + """ + if vertex not in self._quiver: + raise ValueError("Must specify a valid vertex of the quiver.") + return QuiverRep(k, self, [[(vertex, vertex)]], option='dual paths') + + def free_module(self, k): + """ + Return a free module of rank `1` over ``kP``, where `P` is + ``self``. (In other words, the regular representation.) + + INPUT: + + - ``k`` - ring, the base ring of the representation. + + OUTPUT: + + - :class:`~sage.quivers.representation.QuiverRep_with_path_basis`, the + path algebra considered as a right module over itself. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b'], 3: ['c', 'd']}, 2:{3:['e']}}).path_semigroup() + sage: Q.free_module(GF(3)).dimension_vector() + (1, 3, 6) + """ + return QuiverRep(k, self, [[(v, v)] for v in self._quiver], option='paths') + + + def all_paths(self, start=None, end=None): + """ + List of all 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: + + - list of paths, excluding the invalid path + + .. TODO:: + + This currently does not work for quivers with cycles, even if + there are only finitely many paths from ``start`` to ``end``. + + .. NOTE:: + + If there are multiple edges between two vertices, the method + :meth:`sage.graphs.digraph.all_paths` will not differentiate + between them. But this method, which is not for digraphs but for + their path semigroup associated with them, will. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 2:{3:['d']}}) + sage: F = Q.path_semigroup() + sage: F.all_paths(1, 3) + [a*d, b*d, c] + + If ``start=end`` then we expect only the trivial path at that vertex:: + + sage: F.all_paths(1, 1) + [e_1] + + The empty list is returned if there are no paths between the given vertices:: + + sage: F.all_paths(3, 1) + [] + + If ``end=None`` then all edge paths beginning at ``start`` are + returned, including the trivial path:: + + sage: F.all_paths(2) + [e_2, 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: F.all_paths(None, 2) + [a, b, e_2] + sage: F.all_paths(end=2) + [a, b, e_2] + + If ``start=end=None`` then all edge paths are returned, including + trivial paths:: + + sage: F.all_paths() + [e_1, a, b, a*d, b*d, c, e_2, d, e_3] + + The vertex given must be a vertex of the quiver:: + + sage: F.all_paths(1, 4) + Traceback (most recent call last): + ... + ValueError: The end vertex 4 is not a vertex of the quiver. + + If the underlying quiver is cyclic, a ``NotImplementedError`` is raised:: + + sage: Q = DiGraph({1:{2:['a','b'], 3:['c']}, 3:{1:['d']}}) + sage: F = Q.path_semigroup() + sage: F.all_paths() + Traceback (most recent call last): + ... + ValueError: The underlying quiver has cycles, thus, there may be an infinity of directed paths + + """ + # Check that given arguments are vertices + if start is not None and start not in self._quiver: + raise ValueError("The start vertex " + str(start) + " is not a vertex of the quiver.") + if end is not None and end not in self._quiver: + raise ValueError("The end vertex " + str(end) + " is not a vertex of the quiver.") + + # Handle quivers with cycles + Q = self._quiver + if not (Q.is_directed_acyclic()): + raise ValueError("The underlying quiver has cycles, thus, there may be an infinity of directed paths") + + # Handle start=None + if start is None: + results = [] + for v in Q: + results += self.all_paths(v, end) + return results + + # Handle end=None + if end is None: + results = [] + for v in Q: + results += self.all_paths(start, v) + return results + + # Handle the trivial case + if start == end: + return [self([(start, end)],check=False)] + + # This function will recursively convert a path given in terms of + # vertices to a list of QuiverPaths. + def _v_to_e(path): + if len(path) == 1: + return [self([(path[0], path[0])], check=False)] + paths = [] + for a in Q.edge_label(path[0], path[1]): + for b in _v_to_e(path[1:]): + paths.append(self([(path[0], path[1], a)] + list(b), check=False)) + return paths + + # For each vertex path we append the resulting edge paths + result = [] + for path in Q.all_paths(start, end): + result += _v_to_e(path) + + # The result is all paths from start to end + return result diff --git a/src/sage/quivers/paths.py b/src/sage/quivers/paths.py new file mode 100644 index 00000000000..7be416bf846 --- /dev/null +++ b/src/sage/quivers/paths.py @@ -0,0 +1,514 @@ +#***************************************************************************** +# Copyright (C) 2012 Jim Stark +# 2013 Simon King +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.structure.element import MonoidElement +from sage.rings.integer_ring import ZZ + +class QuiverPath(MonoidElement): + """ + Class for paths in a quiver. + + A path is given by two vertices, ``start`` and ``end``, and a finite + (possibly empty) list of edges `e_1, e_2, ..., e_n` such that the + initial vertex of `e_1` is ``start``, the final vertex of `e_i` is + the initial vertex of `e_{i + 1}`, and the final vertex of `e_n` is + ``end``. In the case where no edges are specified, we must have + ``start = end`` and the path is called the trivial path at the given + vertex. + + INPUT: + + - ``parent`` -- the path semigroup associated with a quiver; this is + where the path will live + - ``path`` -- tuple or iterable. If ``path`` is a tuple then it is + assumed to be of the form ``(vertex, vertex)`` or + ``(start, end, label)``. In the first case the trivial path at the + given vertex is created. In the second case a path consisting of + just the given edge is created. If ``path`` is not a tuple then it + is assumed to be an iterable variable giving the edges of a path, + where each edge is in one of the two forms above. + - ``check`` -- boolean (default: ``True``). If it is ``False``, no + sanity check will be performed on the given iterable. + + OUTPUT: + + - QuiverPath + + NOTE: + + Do *not* use this constructor directly! Instead, pass the input to the + path semigroup that shall be the parent of this path. + + EXAMPLES: + + Specify a path by giving a list of edges:: + + sage: Q = DiGraph({1:{2:['a','d'], 3:['e']}, 2:{3:['b']}, 3:{1:['f'], 4:['c']}}) + sage: F = Q.path_semigroup() + sage: p = F([(1, 2, 'a'), (2, 2), (2, 3, 'b')]) + sage: p + a*b + + Paths are not *unique*, but different representations of "the same" path + yield *equal* paths:: + + sage: q = F([(1, 1), (1, 2, 'a'), (2, 3, 'b'), (3, 3)]) + sage: p is q + False + sage: p == q + True + + The `*` operator is concatenation of paths. If the two paths do not + compose, its result is ``None``:: + + sage: print(p*q) + None + sage: p*F((3, 4, 'c')) + a*b*c + sage: F([(2,3,'b'), (3,1,'f')])*p + b*f*a*b + + The length of a path is the number of edges in that path. Trivial paths + are therefore length-`0`:: + + sage: len(p) + 2 + sage: triv = F((1, 1)) + sage: len(triv) + 0 + + List index and slice notation can be used to access the edges in a path. + QuiverPaths can also be iterated over. Trivial paths have no elements:: + + sage: for x in p: print x + (1, 2, 'a') + (2, 3, 'b') + sage: triv[:] + [] + + There are methods giving the initial and terminal vertex of a path:: + + sage: p.initial_vertex() + 1 + sage: p.terminal_vertex() + 3 + """ + def __init__(self, parent, path, check=True): + """ + Creates a path object. Type QuiverPath? for more information. + + TESTS:: + + sage: from sage.quivers.paths import QuiverPath + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: p = Q([(1, 1), (1, 1)]) + sage: Q([(1,3,'x')]) + Traceback (most recent call last): + ... + ValueError: Cannot interpret [(1, 3, 'x')] as element of + Partial semigroup formed by the directed paths of Multi-digraph on 3 vertices + + Note that QuiverPath should not be called directly, because + the elements of the path semigroup associated with a quiver + use a sub-class of QuiverPath. Nonetheless, just for test, we + show that it *is* possible to create a path in a deprecated way:: + + sage: p == QuiverPath(Q, (1, 1)) + True + sage: Q([(1, 1), (1, 2, 'a'), (2, 2), (2, 3, 'b'), (3, 3)])._path + ((1, 2, 'a'), (2, 3, 'b')) + """ + MonoidElement.__init__(self, parent=parent) + + # Normalise the given path, unless it is asserted that the input is + # fine + if not check: + self._path = tuple(path) + return + if path == 1: + # We do not guarantee that there is only one vertex. + # However, this element certainly exists. + v = parent.quiver().vertices()[0] + self._path = ((v,v),) + return + E = parent.quiver().edges() + if isinstance(path, QuiverPath): + if path.parent() is parent: + self._path = path._path + return + new_path = list(path._path) + # A tuple is assumed to be an edge, anything else is assumed to be a + # list of edges + elif isinstance(path, tuple): + new_path = [path] + else: + new_path = list(path) + + # Check that each edge in the path is valid + good = True + for x in new_path: + if (len(x) < 2 or x[0] not in ZZ or x[1] not in ZZ + or len(x) == 2 and x[0] != x[1] + or len(x) == 3 and x not in E + or len(x) > 3): + good = False + break + if not good: + raise ValueError("Cannot interpret %s as element of %s"%(path,parent)) + # Delete trivial edges, and clear the path if not valid + i = 0 + while i + 1 < len(new_path): + if new_path[i][1] != new_path[i + 1][0]: + raise ValueError("Cannot interpret %s as element of %s"%(path,parent)) + elif len(new_path[i])!=3: + del new_path[i] + else: + i += 1 + if len(new_path) > 1 and len(new_path[-1])!=3: + del new_path[-1] + self._path = tuple(new_path) + + def _repr_(self): + """ + Default representation of a path. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: Q([(1, 2, 'a'), (2, 3, 'b')]) # indirect doctest + a*b + sage: Q((1, 1)) # indirect doctest + e_1 + """ + if len(self._path[0])!=3: + return 'e_{0}'.format(self._path[0][0]) + else: + return '*'.join([e[2] for e in self._path]) + + def __len__(self): + """ + Return the length of the path. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: len(Q([(1, 2, 'a'), (2, 3, 'b')])) + 2 + sage: len(Q((1, 1))) + 0 + sage: len(Q((1, 2, 'a'))) + 1 + """ + if (not self._path) or (len(self._path[0])==2): + return 0 + else: + return len(self._path) + + def deg(self): + """ + Return the degree of the path, which is the same as its length. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: Q([(1, 2, 'a'), (2, 3, 'b')]).deg() + 2 + sage: Q((1, 1)).deg() + 0 + sage: Q((1, 2, 'a')).deg() + 1 + """ + return len(self) + + def __nonzero__(self): + """ + Implements boolean values for the object. + + NOTE: + + The boolean value is always True, since the partial semigroup formed + by the paths of a quiver does not contain zero. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: a = Q((1, 2, 'a')) + sage: b = Q((2, 3, 'b')) + sage: bool(a*b) + True + sage: bool(Q.idempotents()[0]) + True + + """ + return True + + def __cmp__(self, other): + """ + Comparison for QuiverPaths. + + As usual in Sage, the ``__cmp__`` method of a Python sub-class of + :class:`sage.structure.element.Element` can assume that both arguments + belong to the same parent. + + If the QuiverPaths are unequal then one of the following data (listed + in order of preferance) is unequal and used for comparison: + + - Length of the paths + - Edge sequence of the paths. + + .. NOTE:: + + This code is used by CombinatorialFreeModule to order the monomials + when printing elements of path algebras. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c'], 4:['d']}}).path_semigroup() + sage: a = Q((1, 2, 'a')) + sage: b = Q((1, 2, 'b')) + sage: c = Q((2, 3, 'c')) + sage: d = Q((2, 4, 'd')) + sage: e = Q.idempotents()[3] + sage: e < a + True + sage: a < e + False + sage: d < a*c + True + sage: a*c < d + False + sage: a < b + True + sage: b < a + False + sage: a*c < a*d + True + sage: a*d < a*c + False + sage: a < a + False + """ + # Since QuiverPath inherits from Element, it is guaranteed that + # both arguments are elements of the same path semigroup + s_p = self._path + o_p = other._path + + # Compare lengths if different + c = cmp(len(s_p),len(o_p)) + if c: + return c + + # Trivial paths should be smaller than non-trivial paths + c = cmp(len(s_p[0]),len(o_p[0])) + if c: + return c + + # Now we have two non-trivial paths. Compare internal tuple + return cmp(s_p, o_p) + + def __getitem__(self, *args): + """ + Implements index notation. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}, 3:{4:['c']}}).path_semigroup() + sage: p = Q([(1, 2, 'a'), (2, 3, 'b'), (3, 4, 'c')]) + sage: p + a*b*c + sage: p[0] + (1, 2, 'a') + sage: p[-1] + (3, 4, 'c') + sage: p[1:] + ((2, 3, 'b'), (3, 4, 'c')) + """ + + if self._path and self._path[0][0] == self._path[0][1]: + return list().__getitem__(*args) + else: + return self._path.__getitem__(*args) + + def __iter__(self): + """ + Implements iteration over the path. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}, 3:{4:['c']}}).path_semigroup() + sage: p = Q([(1, 2, 'a'), (2, 3, 'b'), (3, 4, 'c')]) + sage: for e in p: print e + (1, 2, 'a') + (2, 3, 'b') + (3, 4, 'c') + """ + + # Return an iterator over an empty tuple for trivial paths, otherwise + # return an iterator for _path as a list + if not len(self): + return list().__iter__() + else: + return list(self._path).__iter__() + + def _mul_(self, other): + """ + Compose two paths. + + NOTE: + + ``None`` is returned if the terminal vertex of the first path does + not coincide with the initial vertex of the second path. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}, 3:{4:['c']}, 4:{5:['d']}}).path_semigroup() + sage: x = Q([(1, 2, 'a'), (2, 3, 'b')]) + sage: y = Q([(3, 4, 'c'), (4, 5, 'd')]) + sage: print y*x + None + sage: x*y + a*b*c*d + sage: x*Q((3, 4, 'c')) + a*b*c + sage: x*Q([(3, 4, 'c'), (4, 5, 'd')]) + a*b*c*d + sage: x*6 + Traceback (most recent call last): + ... + TypeError: unsupported operand parent(s) for '*': + 'Partial semigroup formed by the directed paths of Multi-digraph on 5 vertices' + and 'Integer Ring' + """ + # By Sage's coercion model, both paths belong to the same quiver + # In particular, both are QuiverPath + Q = self.parent() + if self.terminal_vertex()!=other.initial_vertex(): + return None + if len(self._path[0])<3: + return other + elif len(other._path[0])<3: + return self + return Q(self._path+other._path, check=False) + + def __mod__(self, other): + """ + Return ``self`` with ``other`` deleted from the beginning. + + If ``other`` is not the beginning of ``self`` then ``None`` is + returned. Deleting the trivial path at vertex `v` from a path that + begins at `v` does not change the path. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: p = Q([(1, 2, 'a'), (2, 3, 'b')]) + sage: a = Q((1, 2, 'a')) + sage: b = Q((2, 3, 'b')) + sage: e1 = Q((1, 1)) + sage: e2 = Q((2, 2)) + sage: p % a + b + sage: print p % b + None + sage: p % e1 + a*b + sage: print p % e2 + None + + """ + Q = self.parent() + # Convert other to a QuiverPath + oth = Q(other) + if self._path==oth._path: + v = self._path[-1][1] + return Q(((v, v),), check=False) + + # Handle trivial paths + if oth._path[0][0] == oth._path[0][1]: + if self._path[0][0] == oth._path[0][0]: + return self + else: + return None + + # If other is the beginning, return the rest + if self._path[:len(oth._path)] == oth._path: + return Q(list(self._path[len(oth._path):]), check=False) + else: + return None + + def initial_vertex(self): + """ + Return the initial vertex of the path. + + OUTPUT: + + - integer, the label of the initial vertex + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: y = Q([(1, 2, 'a'), (2, 3, 'b')]) + sage: y.initial_vertex() + 1 + + """ + return self._path[0][0] + + def terminal_vertex(self): + """ + Return the terminal vertex of the path. + + OUTPUT: + + - integer, the label of the terminal vertex + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: y = Q([(1, 2, 'a'), (2, 3, 'b')]) + sage: y.terminal_vertex() + 3 + """ + return self._path[-1][1] + + def reverse(self): + """ + Return the path along the same edges in reverse order in the + opposite quiver. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a']}, 2:{3:['b']}}).path_semigroup() + sage: p = Q([(1, 2, 'a'), (2, 3, 'b')]) + sage: p.reverse() + b*a + sage: p.reverse().parent() is Q.reverse() + True + sage: e = Q.idempotents()[0] + sage: e + e_1 + sage: e.reverse() + e_1 + + """ + Q = self.parent().reverse() + # Handle trivial paths + if self._path[0][0] == self._path[0][1]: + return Q(((self._path[0][0],self._path[0][0]),), check=False) + + # Reverse all the edges in the path, then reverse the path + new_path = [(e[1], e[0], e[2]) for e in self._path] + return Q(reversed(new_path), check=False) diff --git a/src/sage/quivers/representation.py b/src/sage/quivers/representation.py new file mode 100644 index 00000000000..c5f53ed8946 --- /dev/null +++ b/src/sage/quivers/representation.py @@ -0,0 +1,2631 @@ +#***************************************************************************** +# Copyright (C) 2012 Jim Stark +# 2013 Simon King +# +# Distributed under the terms of the GNU General Public License (GPL) +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# +# See the GNU General Public License for more details; the full text +# is available at: +# +# http://www.gnu.org/licenses/ +#***************************************************************************** +from sage.structure.factory import UniqueFactory +from sage.modules.module import Module +from sage.modules.module_element import ModuleElement +from sage.misc.cachefunc import cached_method +from sage.misc.fast_methods import WithEqualityById + +class QuiverRepFactory(UniqueFactory): + """ + 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: + + The first two arguments specify the base ring and the Quiver, and they are + always required: + + - ``k`` - ring, the base ring of the representation + + - ``P`` - the partial semigroup formed by the paths of the quiver of the + representation + + Then to specify the spaces and maps associated to the Quiver there are three + possible options. The first is the ``values`` option, where the next two + arguments give the data to be assigned. The following can either be the next + two entries in the argument list or they can be passed by keyword. If the + argument list is long enough the keywords are ignored; the keywords are only + checked in the event that the argument list does not have enough entries after + ``P``. + + - ``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. + + The second option is the ``paths`` option which creates a module by generating a + right ideal from a list of paths. Thus the basis elements of this module + correspond to paths of the quiver and the maps are given by right + multiplication by the corresponding edge. As above this can be passed either + as the next entry in the argument list or as a keyword. The keyword is only + checked if there is no entry in the argument list after ``Q``. + + - ``basis`` - list, a nonempty list of paths in the quiver Q. Entries that do not + represent valid paths are ignored and duplicate paths are deleted. There + must be at least one valid path in the list or a ValueError is raised. The + closure of this list under right multiplication forms the basis of the + resulting representation. + + The third option is the ``dual paths`` option which creates the dual of a left + ideal in the quiver algebra. Thus the basis elements of this module correspond + to paths of the quiver and the maps are given by deleting the corresponding + edge from the start of the path (the edge map is zero on a path if that edge is + not the initial edge of the path). As above this can be passed either as the + next entry in the argument list or as a keyword. + + - ``basis`` - list, a nonempty list of paths in the quiver Q. Entries that do not + represent valid paths are ignored and duplicate paths are deleted. There + must be at least one valid path in the list or a ValueError is raised. The + closure of this list under left multiplication of edges forms the basis of + the resulting representation. + + Using the second and third options requires that the following keyword be + passed to the constructor. This must be passed as a keyword. + + - ``option`` - string (default: ``None``), either 'values' or 'paths' or + 'dual paths'. ``None`` is equivalent to 'values'. + + OUTPUT: + + - QuiverRep + + EXAMPLES:: + + sage: Q1 = DiGraph({1:{2:['a']}}).path_semigroup() + + When the ``option`` keyword is not supplied the constructor uses the 'values' + option and expects the spaces and maps to be specified. If no maps or spaces + are given the zero module is created:: + + sage: M = Q1.representation(GF(5)) + sage: M.is_zero() + True + + The simple modules, indecomposable projectives, and indecomposable injectives are + examples of quiver representations:: + + sage: S = Q1.S(GF(3), 1) + sage: I = Q1.I(QQ, 2) + sage: P = Q1.P(GF(3), 1) + + Various standard submodules can be computed, such as radicals and socles. We can + also form quotients and test for certain attributes such as semisimplicity:: + + sage: R = P.radical() + sage: R.is_zero() + False + sage: (P/R).is_simple() + True + sage: P == R + False + + With the option 'paths' the input data should be a list of QuiverPaths or + things that QuiverPaths can be constructed from. The resulting module is the + submodule generated by these paths in the quiver algebra, when considered as a + right module over itself:: + + sage: P1 = Q1.representation(QQ, [[(1, 1)]], option='paths') + sage: P1.dimension() + 2 + + In the following example, the 3rd and 4th paths are actually the same, + so the duplicate is removed:: + + sage: N = Q1.representation(QQ, [[(1, 1)], [(2, 2)], [(1, 1), (1, 2, 'a'), (2, 2)], [(1, 2, 'a')]], option='paths') + sage: N.dimension() + 3 + + The dimension at each vertex equals the number of paths in the closed basis whose + terminal point is that vertex:: + + sage: Q2 = DiGraph({1:{2:['a'], 3:['b', 'c']}, 2:{3:['d']}}).path_semigroup() + sage: M = Q2.representation(QQ, [[(2, 2)], [(1, 2, 'a')]], option='paths') + sage: M.dimension_vector() + (0, 2, 2) + sage: N = Q2.representation(QQ, [[(2, 2)], [(1, 2, 'a'), (2, 3, 'd')]], option='paths') + sage: N.dimension_vector() + (0, 1, 2) + """ + + def create_key(self, k, P, *args, **kwds): + """ + Return a key for the specified module. + + The key is a tuple. The first and second entries are the base ring + ``k`` and the partial semigroup ``P`` formed by the paths of a quiver. + The third entry is the 'option' and the remaining entries depend on + that option. If the option is 'values' and the Quiver has n vertices + then the next n entries are the vector spaces to be assigned to those + vertices. After that are the matrices of the maps assigned to edges, + listed in the same order that ``Q.edges()`` uses. If the option is + 'paths' or 'dual paths' then the next entry is a tuple containing a + sorted list of the paths that form a basis of the Quiver. + + INPUT: + + - See the class documentation + + OUTPUT: + + - tuple + + EXAMPLES:: + + sage: P = DiGraph({1:{2:['a']}}).path_semigroup() + sage: from sage.quivers.representation import QuiverRep + sage: QuiverRep.create_key(GF(5), P) + (Finite Field of size 5, Partial semigroup formed by the directed paths of Multi-digraph on 2 vertices, 'values', Vector space of dimension 0 over Finite Field of size 5, Vector space of dimension 0 over Finite Field of size 5, []) + """ + key = [k, P] + Q = P.quiver() + if 'option' in kwds and (kwds['option'] == 'paths' or kwds['option'] == 'dual paths'): + # Follow the 'paths' specification for the input + key.append(kwds['option']) + if args: + basis = args[0] + else: + basis = kwds['basis'] + + # Add as QuiverPaths to a set + paths = set() + for p in basis: + paths.add(P(p)) + + if kwds['option'] == 'paths': + # Close the set under right mult by edges + edges = [P(e) for e in Q.edges()] + just_added = paths + while just_added: + to_be_added = [] + for e in edges: + for p in just_added: + pe = p*e + if pe is not None and pe not in paths: + to_be_added.append(pe) + + paths.update(to_be_added) + just_added = to_be_added + + if kwds['option'] == 'dual paths': + # Close the set under left mult by edges + edges = [P(e) for e in Q.edges()] + just_added = paths + while just_added: + to_be_added = [] + for e in edges: + for p in just_added: + ep = e*p + if ep is not None and ep not in paths: + to_be_added.append(ep) + + paths.update(to_be_added) + just_added = to_be_added + + # Add to the key + key.append(tuple(sorted(paths))) + + else: + # Assume the input type the 'values' option + key.append('values') + if args: + spaces = args[0] + elif 'spaces' in kwds: + spaces = kwds['spaces'] + else: + spaces = {} + if len(args) > 1: + maps = args[1] + elif 'maps' in kwds: + maps = kwds['maps'] + else: + 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. + from sage.rings.finite_rings.integer_mod_ring import Integers + verts = Q.vertices() + for x in verts: + if x not in spaces: + key.append(k**0) + elif spaces[x] in Integers(): + key.append(k**spaces[x]) + else: + key.append(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. + # + # Note that the first space is assigned to key[3] and the first + # vertex is 1 so the space assigned to vertex v is key[2 + v] + from sage.matrix.constructor import Matrix + from sage.categories.morphism import is_Morphism + 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 = Matrix(k, key[3 + verts.index(x[0])].dimension(), key[3 + verts.index(x[1])].dimension()) + + # If a morphism is specified take it's matrix. Create one if + # needed. Otherwise assume the Matrix function can convert the + # object to a Matrix. + if is_Morphism(e): + if hasattr(e, 'matrix'): + key.append(e.matrix()) + else: + gens_images = [key[3 + verts.index(x[1])].coordinate_vector(e(x)) + for x in key[3 + verts.index(x[0])].gens()] + key.append(Matrix(k, key[3 + verts.index(x[0])].dimension(), + key[3 + verts.index(x[1])].dimension(), gens_images)) + else: + key.append(Matrix(k, key[3 + verts.index(x[0])].dimension(), + key[3 + verts.index(x[1])].dimension(), e)) + + # Make sure the matrix is immutable so it hashes + key[-1].set_immutable() + + # Wrap as a tuple and return + return tuple(key) + + def create_object(self, version, key, **extra_args): + """ + Create a QuiverRep_generic or QuiverRep_with_path_basis object from the key. + + The key is a tuple. The first and second entries are the base ring + ``k`` and the Quiver ``Q``. The third entry is the 'option' and the + remaining entries depend on that option. If the option is 'values' + and the Quiver has n vertices then the next n entries are the vector + spaces to be assigned to those vertices. After that are the matrices + of the maps assigned to edges, listed in the same order that + ``Q.edges()`` uses. If the option is 'paths' or 'dual paths' then the + next entry is a tuple containing a sorted list of the paths that form + a basis of the Quiver. + + INPUT: + + - ``version`` - The version of sage, this is currently ignored. + + - ``key`` - tuple + + OUTPUT: + + - QuiverRep_generic or QuiverRep_with_path_basis object + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: from sage.quivers.representation import QuiverRep + sage: key = QuiverRep.create_key(GF(5), Q) + sage: QuiverRep.create_object(Q.version(), key) + Representation with dimension vector (0, 0) + """ + + if len(key) < 4: + raise ValueError("Invalid key used in QuiverRepFactory!") + + # Get the quiver + P = key[1] + Q = P.quiver() + + if key[2] == 'values': + # Get the spaces + spaces = {} + i = 3 + for v in Q: + spaces[v] = key[i] + i += 1 + + # Get the maps + maps = {} + for e in Q.edges(): + maps[e] = key[i] + i += 1 + + # Create and return the module + return QuiverRep_generic(key[0], P, spaces, maps) + + elif key[2] == 'paths': + # Create and return the module + return QuiverRep_with_path_basis(key[0], P, key[3]) + + elif key[2] == 'dual paths': + # Create and return the module + return QuiverRep_with_dual_path_basis(key[0], P, key[3]) + + else: + raise ValueError("Invalid key used in QuiverRepFactory!") + +QuiverRep = QuiverRepFactory("sage.quivers.representation.QuiverRep") + +######################################################################### +## Elements + +class QuiverRepElement(ModuleElement): + """ + 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 + + .. NOTE:: + + 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: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: M(elems) + Element of quiver representation + sage: v = 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, parent, elements=None, name=None): + """ + Type QuiverRepElement? for more information. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: M() + Element of quiver representation + sage: v = M(elems, 'v') + sage: v + v + sage: (v + v + v).is_zero() + True + """ + # In the default call method, the default value of the first argument is zero + if not elements: + elements = {} + # 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__(parent) + + self._elems = {} + self._quiver = parent._quiver + for v in self._quiver: + if v in elements: + self._elems[v] = parent._spaces[v](elements[v]) + else: + self._elems[v] = parent._spaces[v].zero() + + # Assign a name if supplied + if name is not None: + self.rename(name) + + def _repr_(self): + """ + Default string representation. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: Q.P(QQ, 3).an_element() # indirect doctest + Element of quiver representation + """ + return "Element of quiver representation" + + def _add_(left, right): + """ + This overrides the + operator. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: v = M(elems) + sage: (v + v + v).is_zero() # indirect doctest + True + """ + + elements = {} + for v in left._quiver: + elements[v] = left._elems[v] + right._elems[v] + + return left.parent()(elements) + + def _sub_(left, right): + """ + This overrides the - operator. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: v = M(elems) + sage: (v - v).is_zero() # indirect doctest + True + """ + + elements = {} + for v in left._quiver: + elements[v] = left._elems[v] - right._elems[v] + + return left.parent()(elements) + + def _neg_(self): + """ + This overrides the unary - operator. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: v = M(elems) + sage: v == -v # indirect doctest + False + """ + + elements = {} + for v in self._quiver: + elements[v] = -self._elems[v] + + return self.parent()(elements) + + def __mul__(self, other): + """ + Implements * for right multiplication by Quiver Algebra elements. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: A = Q.algebra(QQ) + sage: m = P.an_element() + sage: a = A((1, 2, 'a')) + sage: e1 = A((1, 1)) + sage: m.support() + [1, 2] + sage: (m*a).support() + [2] + sage: (m*e1).support() + [1] + sage: (m*(a + e1)).support() + [1, 2] + """ + + # Make sure the input is an element of the quiver algebra and get the + # coefficients of the monomials in it + parent = self.parent() + mons = parent._base(other).monomial_coefficients() + result = parent() # this must not be the cached parent.zero(), + # since otherwise it gets changed in place!! + + for path in mons: + # Multiply by the scalar + x = mons[path]*self._elems[path.initial_vertex()] + + # If the edge isn't trivial apply the corresponding maps + if len(path) > 0: + for edge in path: + x = parent.get_map(edge)(x) + + # Sum the results of acting by each monomial + result._elems[path.terminal_vertex()] += x + + return result + + @cached_method + def __hash__(self): + """ + The hash only depends on the elements assigned to each vertex of the + underlying quiver. + + NOTE: + + The default hash inherited from :class:`~sage.structure.element.Element` + would depend on the name of the element and would thus be mutable. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{4:['c']}, 3:{4:['d']}}).path_semigroup() + sage: P = Q.P(QQ, 2) + sage: v = P() # let's not muddy P.zero(), which is cached + sage: h1 = hash(v) + sage: v.rename('foobar') + sage: h1 == hash(v) + True + + """ + return hash(frozenset((v,tuple(self._elems[v])) for v in self._quiver)) + + def __eq__(self, other): + """ + This overrides the == operator. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: v = M(elems) + sage: w = M(elems) + sage: v == w + True + sage: v += w + sage: v == w + False + """ + + # 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. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: v = M(elems) + sage: w = M(elems) + sage: v != w + False + sage: v += w + sage: v != w + True + """ + + # 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): + """ + Return the quiver of the representation. + + OUTPUT: + + - Quiver, the quiver of the representation + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: P = Q.P(QQ, 1) + sage: v = P.an_element() + sage: v.quiver() is Q.quiver() + True + """ + + return self._quiver + + def get_element(self, vertex): + """ + Return the element at the given vertex. + + INPUT: + + - ``vertex`` - integer, a vertex of the quiver + + OUTPUT: + + - vector, the vector assigned to the given vertex + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: v = 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): + """ + Set the value of the element ``self`` at the given vertex + ``vertex`` to ``vector`` (while the values at all other vertices + remain unchanged). + + 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 + + .. WARNING:: + + Only use this method if you know what you are doing. In particular, + do not apply it to an element that is cached somewhere (such as + :meth:`zero`). + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: v = 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): + """ + Test whether ``self`` is zero. + + OUTPUT: + + - bool, ``True`` if the element is the zero element, ``False`` + otherwise + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: v = M(elems) + sage: v.is_zero() + False + sage: w = M() + sage: w.is_zero() + True + + TESTS:: + + sage: M = Q.P(QQ, 1) + sage: M.zero().is_zero() + True + + """ + + for v in self._quiver: + if not self._elems[v].is_zero(): + return False + + return True + + def support(self): + """ + Return 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: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 0), 3: (2, 1)} + sage: v = 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): + """ + Return a copy of ``self``. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{3:['c']}}).path_semigroup() + sage: spaces = dict((v, GF(3)^2) for v in Q.quiver()) + sage: M = Q.representation(GF(3), spaces) + sage: elems = {1: (1, 0), 2: (0, 1), 3: (2, 1)} + sage: v = 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 self.parent()(self._elems.copy(), name) + +#################################################################### +# The representations + +class QuiverRep_generic(WithEqualityById, Module): + """ + This function should not be called by the user. + + Call QuiverRep with option='values' (which is the default) instead. + + INPUT: + + - ``k`` - ring, the base ring of the representation + + - ``P`` - the path semigroup of the quiver `Q` of the representation + + - ``spaces`` - dict (default: empty), a dictionary associating to each + vertex a free module over the base ring `k`. Not all vertices need + to 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 need to 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: Q = DiGraph({1:{3:['a']}, 2:{3:['b']}}).path_semigroup() + sage: spaces = {1: QQ^2, 2: QQ^3, 3: QQ^2} + sage: maps = {(1, 3, 'a'): (QQ^2).Hom(QQ^2).identity(), (2, 3, 'b'): [[1, 0], [0, 0], [0, 0]]} + sage: M = Q.representation(QQ, spaces, maps) + + :: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: P = Q.P(GF(3), 1) + sage: I = Q.I(QQ, 1) + sage: P.an_element() in P + True + sage: I.an_element() in P + False + + TESTS:: + + sage: TestSuite(M).run() + sage: TestSuite(P).run() + sage: TestSuite(I).run() + sage: TestSuite(Q.S(ZZ,2)).run() + + """ + Element = QuiverRepElement + + ########################################################################### + # # + # PRIVATE FUNCTIONS # + # These functions are not meant to be seen by the end user. # + # # + ########################################################################### + + def __init__(self, k, P, spaces, maps): + """ + Type QuiverRep? for more information. + + NOTE: + + Use :meth:`~sage.quivers.quiver.Quiver.representation` to create a + representation. The following is just a test, but it is not the + recommended way of creating a representation. + + TESTS:: + + sage: from sage.quivers.representation import QuiverRep_generic + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: QuiverRep_generic(GF(5), Q, {1: GF(5)^2, 2: GF(5)^3}, {}) + Representation with dimension vector (2, 3) + + """ + # 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 + # The quiver algebra of _quiver over _base_ring + # * _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. + Q = P.quiver() + self._semigroup = P + 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. + from sage.rings.finite_rings.integer_mod_ring import Integers + 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 convert 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). + from sage.categories.morphism import Morphism + if isinstance(e, Morphism): + self._maps[x] = e + else: + self._maps[x] = self._spaces[x[0]].hom(e, self._spaces[x[1]]) + + self._assert_valid_quiverrep() + + super(QuiverRep_generic, self).__init__(P.algebra(k)) + + def _assert_valid_quiverrep(self): + """ + Raise 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. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: M = Q.P(GF(3), 2) # indirect doctest + + Due to unique representation, we will cause bugs in later code if we modify M + to be an invalid representation. So we make sure to store the original values + and replace them after testing:: + + sage: sv = M._spaces[1] + sage: M._spaces[1] = 0 + sage: M._assert_valid_quiverrep() + Traceback (most recent call last): + ... + ValueError: Domain of map at edge 'a' does not match. + sage: M._spaces[1] = sv + sage: M = Q.P(GF(3), 2) + sage: sv = M._maps[(1, 2, 'a')] + sage: M._maps[(1, 2, 'a')] = (QQ^2).Hom(QQ^1).zero_element() + sage: M._assert_valid_quiverrep() + Traceback (most recent call last): + ... + ValueError: Domain of map at edge 'a' does not match. + sage: M._maps[(1, 2, 'a')] = sv + """ + + 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): + """ + Default string representation. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: Q.P(GF(3), 2) # indirect doctest + Representation with dimension vector (0, 1) + """ + + return "Representation with dimension vector " + str(self.dimension_vector()) + + def __div__(self, sub): + """ + This and :meth:`__truediv__` below together overload the / + operator. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: P = Q.P(GF(3), 1) + sage: R = P.radical() + sage: (P/R).is_simple() + True + """ + return self.quotient(sub) + + def __truediv__(self, sub): + """ + This and :meth:`__div__` above together overload the / operator. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: P = Q.P(GF(3), 1) + sage: R = P.radical() + sage: (P/R).is_simple() + True + """ + return self.quotient(sub) + + 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. + + TESTS:: + + sage: Q = DiGraph({1:{3:['a']}, 2:{3:['b']}}).path_semigroup() + sage: spaces = {1: QQ^2, 2: QQ^3, 3: QQ^2} + sage: maps = {(1, 3, 'a'): (QQ^2).Hom(QQ^2).identity(), (2, 3, 'b'): [[1, 0], [0, 0], [0, 0]]} + sage: M = Q.representation(QQ, spaces, maps) + sage: v = M.an_element() + sage: M.submodule([v]) # indirect doctest + Representation with dimension vector (1, 1, 1) + sage: M.submodule(spaces={1: QQ^2}) # indirect doctest + Representation with dimension vector (2, 0, 2) + sage: M.submodule(spaces={2: QQ^3}) # indirect doctest + Representation with dimension vector (0, 3, 1) + sage: v.support() + [1, 2, 3] + sage: M.submodule([v], {2: QQ^3}) # indirect doctest + Representation with dimension vector (1, 3, 1) + sage: M.submodule().is_zero() # indirect doctest + True + sage: M.submodule(M.gens()) is M # indirect doctest + True + """ + + # 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 self._semigroup.representation(self._base_ring, spaces, maps) + + def _coerce_map_from_(self, domain): + """ + Return either a QuiverRepHom from ``domain`` to ``self``, or + ``False``. + + .. NOTE:: + + This function simply tries to coerce a map at each vertex and then check if + the result is a valid homomorphism. If it is, then that homomorphism is + returned. If it is not or if no coercion was possible then it returns + ``False``. + + INPUT: + + - ``domain`` - a Sage object + + OUTPUT: + + - QuiverRepHom or bool + + TESTS:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c']}}).path_semigroup() + sage: M = Q.P(QQ, 1) + sage: S = M.radical() + sage: M.coerce_map_from(S) # indirect doctest + Homomorphism of representations of Multi-digraph on 3 vertices + sage: (M/S).coerce_map_from(M) # indirect doctest + Homomorphism of representations of Multi-digraph on 3 vertices + + Here sage coerces a map but the result is not a homomorphism:: + + sage: S.coerce_map_from(M) # indirect doctest + + Here sage cannot coerce a map:: + + sage: N = Q.P(QQ, 3) + sage: N.coerce_map_from(M) # indirect doctest + """ + + # Domain must be a QuiverRep + if not isinstance(domain, QuiverRep_generic): + return False + + # Coerce a map at each vertex, return false if it fails + maps = {} + for v in self._quiver: + maps[v] = self._spaces[v].coerce_map_from(domain._spaces[v]) + if maps[v] is None or maps[v] is False: + return False + + # Create and return the hom, return False if it wasn't valid + try: + return domain.hom(maps, self) + except ValueError: + return False + + def _Hom_(self, codomain, category): + """ + This function is used by the coercion model. + + INPUT: + + - ``codomain`` - QuiverRepHom + + - ``category`` - This input is ignored + + EXAMPLES:: + + sage: from sage.categories.homset import Hom + sage: Q = DiGraph({1:{2:['a', 'b']}, 2:{3:['c', 'd']}}).path_semigroup() + sage: P = Q.P(GF(3), 2) + sage: S = P/P.radical() + sage: Hom(P, S) # indirect doctest + Dimension 1 QuiverHomSpace + """ + from sage.quivers.homspace import QuiverHomSpace + + if not isinstance(codomain, QuiverRep_generic): + raise TypeError("codomain must be of type QuiverRep_generic") + + return QuiverHomSpace(self, codomain) + + ########################################################################### + # # + # ACCESS FUNCTIONS # + # These functions are used to view the representation data. # + # # + ########################################################################### + + def get_space(self, vertex): + """ + Return the module associated to the given vertex ``vertex``. + + INPUT: + + - ``vertex`` - integer, a vertex of the quiver of the module + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}}).path_semigroup() + 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): + """ + Return the map associated to the given edge ``edge``. + + INPUT: + + - ``edge`` - tuple of the form + (initial vertex, terminal vertex, label) specifying the edge + whose map is returned + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: Q.P(ZZ, 1).get_map((1, 2, 'a')) + Free module morphism defined by the matrix + [1 0] + Domain: Ambient free module of rank 1 over the principal ideal domain Integer Ring + Codomain: Ambient free module of rank 2 over the principal ideal domain Integer Ring + """ + + return self._maps[edge] + + def quiver(self): + """ + Return the quiver of the representation. + + OUTPUT: + + - Quiver + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: M = Q.representation(GF(5)) + sage: M.quiver() is Q.quiver() + True + """ + + return self._quiver + + def base_ring(self): + """ + Return the base ring of the representation. + + OUTPUT: + + - ring + + .. TODO:: + + Get rid of this method. The convention for modules in Sage is + that the base ring is the same as the base of the module. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a']}}).path_semigroup() + sage: M = Q.representation(GF(5)) + 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): + """ + Return the dimension of the space associated to the given vertex + ``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: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: P = Q.P(GF(2), 1) + sage: P.dimension(1) + 1 + sage: P.dimension(2) + 2 + + The total dimension of the module is the sum of the dimensions + at each vertex:: + + sage: P.dimension() + 3 + """ + + if vertex is 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): + """ + Return 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: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: P = Q.P(GF(2), 1) + sage: P.dimension_vector() + (1, 2) + + Each coordinate of the dimension vector is the dimension of the space + associated to that coordinate:: + + sage: P.get_space(2).dimension() + 2 + """ + + return tuple(self._spaces[x].dimension() for x in self._quiver) + + def is_zero(self): + """ + Test whether the representation is zero. + + OUTPUT: + + - bool + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: M = Q.representation(ZZ) + sage: N = Q.representation(ZZ, {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): + """ + Test whether the representation is simple. + + OUTPUT: + + - bool + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + 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): + """ + Test whether the representation is semisimple. + + OUTPUT: + + - bool + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + 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 not self._maps[x].is_zero(): + return False + return True + + def an_element(self): + """ + Return an element of ``self``. + + OUTPUT: + + - QuiverRepElement + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: M = Q.P(QQ, 1) + sage: M.an_element() + Element of quiver representation + """ + + # Here we just use the an_element function from each space. + elements = dict((v, self._spaces[v].an_element()) for v in self._quiver) + return self(elements) + + def support(self): + """ + Return the support of ``self`` as a list. + + OUTPUT: + + - list, the vertices of the representation that have nonzero spaces associated + to them + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a']}, 3:{2:['b'], 4:['c']}}).path_semigroup() + 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'): + """ + Return a list of generators of ``self`` as a `k`-module. + + 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) + + .. NOTE:: + + The generators are ordered first by vertex and then by the order given by + the gens() method of the space associated to that vertex. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + 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. Other functions depend on the ordering of generators produced + # by this code. Make sure you know which they are before you change + # anything + basis = [] + for v in self._quiver: + for m in self._spaces[v].gens(): + if uselist: + basis.append(self({v: m}, names[i])) + else: + basis.append(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: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + 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 + + def linear_combination_of_basis(self, coordinates): + """ + Return the linear combination of the basis for ``self`` given + by ``coordinates``. + + INPUT: + + - ``coordinates`` -- list, a list whose length is the dimension of + ``self``. The `i`-th element of this list defines the + coefficient of the `i`-th basis vector in the linear + combination. + + OUTPUT: + + - QuiverRepElement + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: M = Q.P(QQ, 1) + sage: x, y, z = M.gens('xyz') + sage: e = x - y + z + sage: M.coordinates(e) + [1, -1, 1] + sage: M.linear_combination_of_basis([1, -1, 1]) == e + True + """ + + # Make sure the input is valid + gens = self.gens() + if len(gens) != len(coordinates): + raise ValueError("The coordinates do not match the dimension of the module.") + + result = self() # this must not be self.zero(), which is cached + for i in range(0, len(gens)): + result += coordinates[i]*gens[i] + + return result + + ########################################################################### + # # + # 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 subrepresentation 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 ``M.coerce_map_from(sub)``. + + EXAMPLES:: + + sage: Q = DiGraph({1:{3:['a']}, 2:{3:['b']}}).path_semigroup() + sage: spaces = {1: QQ^2, 2: QQ^3, 3: QQ^2} + sage: maps = {(1, 3, 'a'): (QQ^2).Hom(QQ^2).identity(), (2, 3, 'b'): [[1, 0], [0, 0], [0, 0]]} + sage: M = Q.representation(QQ, spaces, maps) + 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 entire vector space associated to vertex 3 + because there is an isomorphism 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 image of the rank 1 homomorphism 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): + """ + Return 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 + projection map from ``self`` to ``quot`` can be obtained by + calling ``quot.coerce_map_from(self)``. + + EXAMPLES: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c']}}).path_semigroup() + 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) + sage: M.quotient(M) + Representation with dimension vector (0, 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. + + # TODO: This 'if' shouldn't need to be here, but sage crashes when + # coercing elements into a quotient that is zero. Once Trac ticket + # 12413 gets fixed only the else should need to execute. + # NOTE: This is no longer necessary. Keep around for speed or + # remove? -- darij, 16 Feb 2014 + if spaces[e[1]].dimension() == 0: + maps[e] = spaces[e[0]].Hom(spaces[e[1]]).zero_element() + else: + 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 self._semigroup.representation(self._base_ring, spaces, maps) + + def socle(self): + """ + The socle of ``self``. + + OUTPUT: + + - QuiverRep, the socle + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c']}}).path_semigroup() + 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 Jacobson radical + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c']}}).path_semigroup() + 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: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c']}}).path_semigroup() + 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 of ``self``. + + OUTPUT: + + - QuiverRep, the zero submodule of ``self``. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c']}}).path_semigroup() + 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 linear_dual(self): + """ + Compute the linear dual `Hom_k(M, k)` of the module + `M` = ``self`` over the base ring `k`. + + OUTPUT: + + - QuiverRep, the dual representation + + .. NOTE:: + + If `e` is an edge of the quiver `Q` then we let + `(fe)(m) = f(me)`. This gives `Hom_k(M, k)` a module + structure over the opposite quiver ``Q.reverse()``. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c']}}).path_semigroup() + sage: M = Q.P(QQ, 1) + sage: M.linear_dual() + Representation with dimension vector (1, 2, 2) + sage: M.linear_dual().quiver() is Q.reverse().quiver() + 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()) + + # Reverse the bases if present + if hasattr(self, '_bases'): + bases = {} + basis = [] + for v in self._bases: + bases[v] = [p.reverse() for p in self._bases[v]] + basis.extend(bases[v]) + + if isinstance(self, QuiverRep_with_path_basis): + result = self._semigroup.reverse().representation(self._base_ring, basis, option='dual paths') + result._maps = maps + result._bases = bases + elif isinstance(self, QuiverRep_with_dual_path_basis): + result = self._semigroup.reverse().representation(self._base_ring, basis, option='paths') + result._maps = maps + result._bases = bases + else: + result = self._semigroup.reverse().representation(self._base_ring, spaces, maps) + + return result + + def algebraic_dual(self, basis=False): + """ + Compute the algebraic dual `Hom_Q(M, kQ)` of the module + `M` = ``self``. + + INPUT: + + - ``basis`` - bool. If ``False``, then only the module is + returned. If ``True``, then a tuple is returned. The first + element is the QuiverRep and the second element is a dictionary + which associates to each vertex a list. The elements of this + list are the homomorphisms which correspond to the basis + elements of that vertex in the module. + + OUTPUT: + + - QuiverRep or tuple + + .. NOTE:: + + Here `kQ` is the path algebra considered as a right module + over itself. If `e` is an edge of the quiver `Q` then we let + `(fe)(m) = ef(m)`. This gives `Hom_Q(M, kQ)` a module + structure over the opposite quiver ``Q.reverse()``. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b'], 3: ['c', 'd']}, 2:{3:['e']}}).path_semigroup() + sage: Q.free_module(GF(7)).algebraic_dual().dimension_vector() + (7, 2, 1) + """ + from sage.quivers.homspace import QuiverHomSpace + + return QuiverHomSpace(self, self._semigroup.free_module(self._base_ring)).left_module(basis) + + def Hom(self, codomain): + """ + Return the hom space from ``self`` to ``codomain``. + + For more information see the QuiverHomSpace documentation. + + TESTS:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: Q.S(QQ, 2).Hom(Q.P(QQ, 1)) + Dimension 2 QuiverHomSpace + """ + from sage.quivers.homspace import QuiverHomSpace + + return QuiverHomSpace(self, codomain) + + def direct_sum(self, modules, return_maps=False): + """ + Return the direct sum of ``self`` with the given modules + ``modules``. + + The modules must be modules over the same quiver and base ring. + + INPUT: + + - ``modules`` - QuiverRep or list of QuiverReps + + - ``return_maps`` - Boolean (default: False). If ``False``, then + the output is a single QuiverRep object which is the direct sum + of ``self`` with the given module or modules. If ``True``, then + the output is a list ``[sum, iota, pi]``. The first entry + ``sum`` is the direct sum of ``self`` with the given module or + modules. Both ``iota`` and ``pi`` are lists of QuiverRepHoms + with one entry for each summand; ``iota[i]`` is the inclusion + map and ``pi[i]`` is the projection map of the `i`-th summand. + The summands are ordered as given with ``self`` being the zeroth + summand. + + OUTPUT: + + - QuiverRep or tuple + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a'], 3:['b']}, 2:{4:['c']}, 3:{4:['d']}}).path_semigroup() + sage: P1 = Q.P(QQ, 1) + sage: P2 = Q.P(QQ, 2) + sage: S = P1.direct_sum(P2) + sage: P1.dimension_vector() + (1, 1, 1, 2) + sage: P2.dimension_vector() + (0, 1, 0, 1) + sage: S.dimension_vector() + (1, 2, 1, 3) + sage: S, iota, pi = P1.direct_sum(P2, return_maps=True) + sage: iota[0].domain() is P1 + True + sage: iota[1].domain() is P2 + True + sage: pi[0].codomain() is P1 + True + sage: pi[1].codomain() is P2 + True + sage: iota[0].codomain() is S + True + sage: iota[1].codomain() is S + True + sage: pi[0].domain() is S + True + sage: pi[1].domain() is S + True + sage: iota[0].get_matrix(4) + [1 0 0] + [0 1 0] + sage: pi[0].get_matrix(4) + [1 0] + [0 1] + [0 0] + sage: P1prime = S/iota[1].image() + sage: f = P1prime.coerce_map_from(S) + sage: g = f*iota[0] + sage: g.is_isomorphism() + True + """ + + # Convert a single module into a length 1 list and check validity + if isinstance(modules, QuiverRep_generic): + mods = [self, modules] + else: + mods = [self] + list(modules) + for i in range(1, len(mods)): + if not isinstance(mods[i], QuiverRep_generic): + raise ValueError("modules must be a QuiverRep or a list of QuiverReps") + if self._quiver is not mods[i]._quiver: + raise ValueError("Cannot direct sum modules of different quivers") + if self._base_ring is not mods[i]._base_ring: + raise ValueError("Cannot direct sum modules with different base rings") + + # Get the dimensions of all spaces at each vertex + dims = dict((v, [x._spaces[v].dimension() for x in mods]) for v in self._quiver) + + # Create spaces of the correct dimensions + spaces = dict((v, self._base_ring**sum(dims[v])) for v in self._quiver) + + # Take block sums of matrices to form the maps + from sage.matrix.constructor import block_diagonal_matrix + maps = {} + for e in self._quiver.edges(): + maps[e] = block_diagonal_matrix([x._maps[e].matrix() for x in mods], subdivide=False) + + # Create the QuiverRep, return if the maps aren't wanted + result = self._semigroup.representation(self._base_ring, spaces, maps) + if not return_maps: + return result + + # Create the inclusions and projections + from sage.matrix.constructor import block_matrix, Matrix + from sage.rings.integer import Integer + iota = [] + pi = [] + for i in range(0, len(mods)): + incl_maps = {} + proj_maps = {} + for v in self._quiver: + # Create the maps using block matrices + pre_dims = sum(dims[v][:i]) + post_dims = sum(dims[v][i + 1:]) + incl_maps[v] = block_matrix(1, 3, [Matrix(dims[v][i], pre_dims), + Matrix(dims[v][i], dims[v][i], Integer(1)), + Matrix(dims[v][i], post_dims)]) + proj_maps[v] = block_matrix(3, 1, [Matrix(pre_dims, dims[v][i]), + Matrix(dims[v][i], dims[v][i], Integer(1)), + Matrix(post_dims, dims[v][i])]) + # These matrices are over the integers, and get coerced + # into the appropriate base ring at a later stage. + # Might make trouble if the integer 1 does not coerce to + # the 1 of the base ring; is that a real issue? + # -- darij, 19 Feb 2014 + + # Create and save the QuiverRepHom + + iota.append(mods[i].hom(incl_maps, result)) + pi.append(result.hom(proj_maps, mods[i])) + + # Return all the data + return [result, iota, pi] + + def projective_cover(self, return_maps=False): + """ + Return the projective cover of ``self``. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c','d']}}).path_semigroup() + sage: S1 = Q.S(GF(3), 1) + sage: f1 = S1.projective_cover() + sage: f1.is_surjective() + True + sage: f1._domain + Representation with dimension vector (1, 2, 4) + sage: Q.P(GF(3), 1) + Representation with dimension vector (1, 2, 4) + """ + + # Deal with the zero module + if self.dimension() == 0: + return self.coerce_map_from(self) + + # Get the projection onto the top and generators + top = self.top() + proj_to_top = top.coerce_map_from(self) + gens = top.gens() + + # Lift each of the generators + lifts = [proj_to_top.lift(x) for x in gens] + + # Get projective summands of the cover + Ps = [self._semigroup.P(self._base_ring, x.support()[0]) for x in gens] + + # Factor the maps through self + maps = [Ps[i].hom(lifts[i], self) for i in range(0, len(gens))] + + # Sum them and return + return maps[0].direct_sum(maps[1:], return_maps, 'codomain') + + def transpose(self): + r""" + Return the transpose of ``self``. + + The transpose, `\mbox{Tr} M`, of a module `M` is defined as + follows. Let `p: P_1 \to P_2` be the second map in a minimal + projective presentation `P_1 \to P_2 \to M \to 0` of `M`. + If `p^t` is the algebraic dual of `p` then define + `\mbox{Tr} M = \mbox{coker} p^t`. + + OUTPUT: + + - QuiverRep + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: M = Q.representation(GF(3), {1: 1, 2: 1}, {(1, 2, 'a'): 1}) + sage: M.transpose() + Representation with dimension vector (1, 1) + """ + + # Get the second map in the minimal projective resolution of self. + # Note we create this map as the projective cover of a kernel so we + # need to alter the codomain to be the projective and not the kernel. + p1 = self.projective_cover() + k = p1.kernel() + p = p1.domain().coerce_map_from(k)*k.projective_cover() + + # Return the cokernel + return p.algebraic_dual().cokernel() + + def AR_translate(self): + """ + Return the Auslander-Reiten translate of ``self``. + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a', 'b']}}).path_semigroup() + sage: M = Q.representation(GF(3), {1: 1, 2: 1}, {(1, 2, 'a'): 1}) + sage: tauM = M.AR_translate() + sage: tauM + Representation with dimension vector (1, 1) + sage: tauM.get_map((1, 2, 'a')).matrix() + [1] + sage: tauM.get_map((1, 2, 'b')).matrix() + [0] + + The module M above is its own AR translate. This is not always true:: + + sage: Q2 = DiGraph({3:{1:['b']}, 5:{3:['a']}}).path_semigroup() + sage: Q2.S(QQ, 5).AR_translate() + Representation with dimension vector (0, 1, 0) + """ + + return self.transpose().linear_dual() + + ########################################################################### + # # + # ADDITIONAL OPERATIONS # + # These functions operations that are not implemented via binary # + # operators. # + # # + ########################################################################### + + def right_edge_action(self, element, path): + """ + Return the result of ``element*path``. + + INPUT: + + - ``element`` - QuiverRepElement, an element of ``self`` + + - ``path`` - QuiverPath or list of tuples + + OUTPUT: + + - QuiverRepElement, the result of ``element*path`` when ``path`` + is considered an element of the path algebra of the quiver + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c']}}).path_semigroup() + 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), (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 + """ + + # Convert to a QuiverPath + qpath = self._semigroup(path) + + # Invalid paths are zero in the quiver algebra + result = self() # this must not be self.zero(), which is cached + if not qpath: + return result + + # Start with the element at the initial vertex + x = element._elems[qpath.initial_vertex()] + + # Act by each edge + for e in qpath: + x = self.get_map(e)(x) + + # Assign the result to the terminal vertex and return + result._elems[qpath.terminal_vertex()] = x + return result + +class QuiverRep_with_path_basis(QuiverRep_generic): + """ + 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. + + INPUT: + + - ``k`` - ring, the base ring of the representation + + - ``P`` - the path semigroup of the quiver `Q` 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. + """ + # 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 private methods and 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. + # + # If the right closure of the basis is also closed under left mult by an + # edge then the object will have the following private variable: + # + # * _left_action_mats + # A dictionary of dictionaries. If e is an edge or trivial path of + # the quiver then _left_action_mats[e] is a dictionary that + # associates a matrix to each vertex. The left action of e on a + # QuiverRepElement is given by multiplying, on the left, the vector + # associated to vertex v in the QuiverRepElement by the matrix + # _left_action_mats[e][v] + + def __init__(self, k, P, basis): + """ + Type QuiverRep_with_path_basis? for more information. + + TESTS:: + + sage: Q1 = DiGraph({1:{2:['a']}}).path_semigroup() + sage: P1 = Q1.representation(QQ, [[(1, 1)]], option='paths') + sage: P1.dimension() + 2 + sage: kQ = Q1.representation(QQ, [[(1, 1)], [(2, 2)], [(1, 1), (1, 2, 'a'), (2, 2)], [(1, 2, 'a')]], option='paths') + sage: kQ.dimension() + 3 + sage: Q2 = DiGraph({1:{2:['a'], 3:['b', 'c']}, 2:{3:['d']}}).path_semigroup() + sage: M = Q2.representation(QQ, [[(2, 2)], [(1, 2, 'a')]], option='paths') + sage: M.dimension_vector() + (0, 2, 2) + sage: N = Q2.representation(QQ, [[(2, 2)], [(1, 2, 'a'), (2, 3, 'd')]], option='paths') + sage: N.dimension_vector() + (0, 1, 2) + """ + + self._quiver = Q = P.quiver() + self._base_ring = k + + # Add the paths to the basis dictionary. The terminal vertex is the + # key + self._bases = dict((v, []) for v in Q) + for path in basis: + if path: + self._bases[path.terminal_vertex()].append(path) + + # Create the matrices of the maps + from sage.matrix.constructor import Matrix + maps = {} + for e in Q.edges(): + arrow = P(e) + # Start with the zero matrix and fill in from there + maps[e] = Matrix(self._base_ring, len(self._bases[e[0]]), len(self._bases[e[1]])) + for i in range(0, len(self._bases[e[0]])): + # Add an entry to the matrix coresponding to where the new path is found + j = self._bases[e[1]].index(self._bases[e[0]][i]*arrow) + maps[e][i, j] = self._base_ring.one() + + # 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, P, spaces, maps) + + # Try and create the matrices for the left edge action of edges. If it + # fails just return, there's no edge action and the construction is + # done + action_mats = {} + for e in self._quiver.edges(): + action_mats[e] = {} + for v in self._quiver: + # Start with the zero matrix and fill in + l = len(self._bases[v]) + action_mats[e][v] = Matrix(self._base_ring, l, l) + + for j in range(0, l): + if e[1] == self._bases[v][j].initial_vertex(): + try: + action_mats[e][v][self._bases[v].index(P(e)*self._bases[v][j]), j] = self._base_ring.one() + except ValueError: + # There is no left action + return + + # If haven't returned yet then there is a left action. Create the + # matrices for acting by trivial paths + for vert in self._quiver: + e = (vert, vert) + action_mats[e] = {} + for v in self._quiver: + # Start with the zero matrix and fill in + l = len(self._bases[v]) + action_mats[e][v] = Matrix(self._base_ring, l, l) + + # Paths not beginning at vert are sent to zero, paths beginning + # at vert are fixed + for i in range(0, l): + if self._bases[v][i].initial_vertex() == vert: + action_mats[e][v][i, i] = self._base_ring.one() + + # Define the method and save the matrices + self.left_edge_action = self._left_edge_action + self._left_action_mats = action_mats + + def _left_edge_action(self, edge, element): + """ + Return the result of ``edge*element``. + + 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 (in which case no action + is performed) and can contain trivial paths (tuples of the form + `(v, v)` where `v` is a vertex of the quiver) + + OUTPUT: + + - QuiverRepElement, the result of ``edge*element`` when ``edge`` + is considered an element of the path algebra of the quiver + + EXAMPLES:: + + sage: Q = DiGraph({1:{2:['a','b']}, 2:{3:['c']}}).path_semigroup() + sage: M = Q.representation(QQ, [[(1, 1)], [(2, 2)], [(3, 3)]], option='paths') + sage: v = M.an_element() + sage: v.support() + [1, 2, 3] + + The sum of all trivial paths is the identity element under this action:: + + sage: x = M.left_edge_action((1, 1), v) # indirect doctest + sage: y = M.left_edge_action((2, 2), v) # indirect doctest + sage: z = M.left_edge_action((3, 3), v) # indirect doctest + sage: x + y + z == v + True + + Note that the action only depends on the element of the path algebra + that the input specifies:: + + sage: a = M.left_edge_action([(1, 1), (1, 2, 'a'), (2, 2)], v) # indirect doctest + sage: b = M.left_edge_action((1, 2, 'a'), v) # indirect doctest + sage: a == b + True + + These two edges multiply to zero in the path algebra:: + + sage: M.left_edge_action([(1, 2, 'a'), (1, 2, 'b')], v).is_zero() # indirect doctest + True + """ + + # Deal with lists by calling this function recursively + if isinstance(edge, list): + if len(edge) == 0: + return element; + else: + return self.left_edge_action(edge[:-1], self.left_edge_action(edge[-1], element)) + + # Now we are just acting by a single edge + elems = dict((v, self._left_action_mats[edge][v]*element._elems[v]) for v in self._quiver) + return self(elems) + + def is_left_module(self): + """ + Tests whether the basis is closed under left multiplication. + + EXAMPLES:: + + sage: Q1 = DiGraph({1:{2:['a']}}).path_semigroup() + sage: P2 = Q1.representation(QQ, [[(2, 2)]], option='paths') + sage: P2.is_left_module() + False + + The supplied basis is not closed under left multiplication, but it's not closed + under right multiplication either. When the closure under right multiplication + is taken the result is also closed under left multiplication and therefore + produces a left module structure:: + + sage: kQ = Q1.representation(QQ, [[(1, 1)], [(2, 2)]], option='paths') + sage: kQ.is_left_module() + True + + Taking the right closure of a left closed set produces another left closed set:: + + sage: Q2 = DiGraph({1:{2:['a'], 3:['b', 'c']}, 2:{3:['d']}}).path_semigroup() + sage: M = Q2.representation(QQ, [[(2, 2)], [(1, 2, 'a')]], option='paths') + sage: M.is_left_module() + True + + Note that the second path is length 2, so even though the edge (1, 2, 'a') + appears in the input the path [(1, 2, 'a')] is not in the right closure:: + + sage: N = Q2.representation(QQ, [[(2, 2)], [(1, 2, 'a'), (2, 3, 'd')]], option='paths') + sage: N.is_left_module() + False + """ + + return hasattr(self, 'left_edge_action') + +class QuiverRep_with_dual_path_basis(QuiverRep_generic): + """ + The basis of the module must be closed under left deletion of an edge; that + is, deleting any edge from the beginning of any path in the basis must + result in a path also contained in the basis of the module. + + INPUT: + + - ``k`` - ring, the base ring of the representation + + - ``P`` - the path semigroup of the quiver `Q` 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 left deletion forms the basis of the resulting + representation. + """ + # This class implements quiver representations whose bases correspond to + # paths in the path algebra and whose maps are edge deletion. The + # main advantage to having such a basis is that a homomorphism can be + # defined by giving a single element in the domain. 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 which + # correspond to the basis elements of the space assigned to that + # vertex. + + def __init__(self, k, P, basis): + """ + Type QuiverRep_with_dual_path_basis? for more information. + + TESTS:: + + sage: Q1 = DiGraph({1:{2:['a']}}).path_semigroup() + sage: I2 = Q1.representation(QQ, [(2, 2)], option='dual paths') + sage: I2.dimension() + 2 + sage: kQdual = Q1.representation(QQ, [[(1, 1)], [(2, 2)], [(1, 1), (1, 2, 'a'), (2, 2)], [(1, 2, 'a')]], option='dual paths') + sage: kQdual.dimension() + 3 + sage: Q2 = DiGraph({1:{2:['a'], 3:['b', 'c']}, 2:{3:['d']}}).path_semigroup() + sage: M = Q2.representation(QQ, [[(1, 2, 'a'), (2, 3, 'd')], [(1, 3, 'b')]], option='dual paths') + sage: M.dimension_vector() + (2, 0, 0) + sage: N = Q2.representation(QQ, [[(2, 2)], [(1, 2, 'a'), (2, 3, 'd')]], option='dual paths') + sage: N.dimension_vector() + (2, 1, 0) + """ + + self._quiver = Q = P.quiver() + self._base_ring = k + + # Add the paths to the basis dictionary. The initial vertex is the + # key + self._bases = dict((v, []) for v in Q) + for path in basis: + if path: + self._bases[path.initial_vertex()].append(path) + + # Create the matrices of the maps + from sage.matrix.constructor import Matrix + maps = {} + for e in Q.edges(): + arrow = P(e) + # Start with the zero matrix and fill in from there + maps[e] = Matrix(self._base_ring, len(self._bases[e[0]]), len(self._bases[e[1]])) + for i in range(0, len(self._bases[e[0]])): + # Add an entry to the matrix coresponding to where the new path is found + if self._bases[e[0]][i] % arrow in self._bases[e[1]]: + j = self._bases[e[1]].index(self._bases[e[0]][i] % e) + maps[e][i, j] = self._base_ring.one() + + # Create the spaces and then the representation + spaces = dict((v, len(self._bases[v])) for v in Q) + super(QuiverRep_with_dual_path_basis, self).__init__(k, P, spaces, maps) +