Path: blob/master/src/sage/combinat/cluster_algebra_quiver/mutation_class.py
8818 views
r"""1mutation_class23This file contains helper functions for compute the mutation class of a cluster algebra or quiver.45For the compendium on the cluster algebra and quiver package see67http://arxiv.org/abs/1102.484489AUTHORS:1011- Gregg Musiker12- Christian Stump13"""1415#*****************************************************************************16# Copyright (C) 2011 Gregg Musiker <[email protected]>17# Christian Stump <[email protected]>18#19# Distributed under the terms of the GNU General Public License (GPL)20# http://www.gnu.org/licenses/21#*****************************************************************************22import time23from sage.groups.perm_gps.partn_ref.refinement_graphs import *24from sage.graphs.generic_graph import graph_isom_equivalent_non_edge_labeled_graph25from copy import copy26from sage.rings.all import ZZ, infinity27from sage.graphs.all import Graph, DiGraph28from sage.matrix.all import matrix29from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import _edge_list_to_matrix3031def _principal_part( mat ):32"""33Returns the principal part of a matrix.3435INPUT:3637- ``mat`` - a matrix with at least as many rows as columns3839OUTPUT:4041The top square part of the matrix ``mat``.4243EXAMPLES::4445sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _principal_part46sage: M = Matrix([[1,2],[3,4],[5,6]]); M47[1 2]48[3 4]49[5 6]50sage: _principal_part(M)51[1 2]52[3 4]53"""54n, m = mat.ncols(), mat.nrows()-mat.ncols()55if m < 0:56raise ValueError('The input matrix has more columns than rows.')57elif m == 0:58return mat59else:60return mat.submatrix(0,0,n,n)6162def _digraph_mutate( dg, k, n, m ):63"""64Returns a digraph obtained from dg with n+m vertices by mutating at vertex k.6566INPUT:6768- ``dg`` -- a digraph with integral edge labels with ``n+m`` vertices69- ``k`` -- the vertex at which ``dg`` is mutated7071EXAMPLES::7273sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_mutate74sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver75sage: dg = ClusterQuiver(['A',4]).digraph()76sage: dg.edges()77[(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -1))]78sage: _digraph_mutate(dg,2,4,0).edges()79[(0, 1, (1, -1)), (1, 2, (1, -1)), (3, 2, (1, -1))]80"""81edges = dict( ((v1,v2),label) for v1,v2,label in dg._backend.iterator_in_edges(dg,True) )82in_edges = [ (v1,v2,edges[(v1,v2)]) for (v1,v2) in edges if v2 == k ]83out_edges = [ (v1,v2,edges[(v1,v2)]) for (v1,v2) in edges if v1 == k ]84in_edges_new = [ (v2,v1,(-label[1],-label[0])) for (v1,v2,label) in in_edges ]85out_edges_new = [ (v2,v1,(-label[1],-label[0])) for (v1,v2,label) in out_edges ]86diag_edges_new = []87diag_edges_del = []8889for (v1,v2,label1) in in_edges:90for (w1,w2,label2) in out_edges:91l11,l12 = label192l21,l22 = label293if (v1,w2) in edges:94diag_edges_del.append( (v1,w2,edges[(v1,w2)]) )95a,b = edges[(v1,w2)]96a,b = a+l11*l21, b-l12*l2297diag_edges_new.append( (v1,w2,(a,b)) )98elif (w2,v1) in edges:99diag_edges_del.append( (w2,v1,edges[(w2,v1)]) )100a,b = edges[(w2,v1)]101a,b = b+l11*l21, a-l12*l22102if a<0:103diag_edges_new.append( (w2,v1,(b,a)) )104elif a>0:105diag_edges_new.append( (v1,w2,(a,b)) )106else:107a,b = l11*l21,-l12*l22108diag_edges_new.append( (v1,w2,(a,b)) )109110del_edges = in_edges + out_edges + diag_edges_del111new_edges = in_edges_new + out_edges_new + diag_edges_new112new_edges += [ (v1,v2,edges[(v1,v2)]) for (v1,v2) in edges if not (v1,v2,edges[(v1,v2)]) in del_edges ]113114dg_new = DiGraph()115for v1,v2,label in new_edges:116dg_new._backend.add_edge(v1,v2,label,True)117if dg_new.order() < n+m:118dg_new_vertices = [ v for v in dg_new ]119for i in [ v for v in dg if v not in dg_new_vertices ]:120dg_new.add_vertex(i)121122return dg_new123124def _matrix_to_digraph( M ):125"""126Returns the digraph obtained from the matrix ``M``.127In order to generate a quiver, we assume that ``M`` is skew-symmetrizable.128129EXAMPLES::130131sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _matrix_to_digraph132sage: _matrix_to_digraph(matrix(3,[0,1,0,-1,0,-1,0,1,0]))133Digraph on 3 vertices134"""135n = M.ncols()136137dg = DiGraph(sparse=True)138for i,j in M.nonzero_positions():139if i >= n: a,b = M[i,j],-M[i,j]140else: a,b = M[i,j],M[j,i]141if a > 0:142dg._backend.add_edge(i,j,(a,b),True)143elif i >= n:144dg._backend.add_edge(j,i,(-a,-b),True)145if dg.order() < M.nrows():146for i in [ index for index in xrange(M.nrows()) if index not in dg ]:147dg.add_vertex(i)148return dg149150def _dg_canonical_form( dg, n, m ):151"""152Turns the digraph ``dg`` into its canonical form, and returns the corresponding isomorphism, and the vertex orbits of the automorphism group.153154EXAMPLES::155156sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _dg_canonical_form157sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver158sage: dg = ClusterQuiver(['B',4]).digraph(); dg.edges()159[(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -2))]160sage: _dg_canonical_form(dg,4,0); dg.edges()161({0: 0, 1: 3, 2: 1, 3: 2}, [[0], [3], [1], [2]])162[(0, 3, (1, -1)), (1, 2, (1, -2)), (1, 3, (1, -1))]163"""164vertices = [ v for v in dg ]165if m > 0:166partition = [ vertices[:n], vertices[n:] ]167else:168partition = [ vertices ]169partition_add, edges = _graph_without_edge_labels(dg,vertices)170partition += partition_add171automorphism_group, obsolete, iso = search_tree(dg, partition=partition, lab=True, dig=True, certify=True)172orbits = get_orbits( automorphism_group, n+m )173orbits = [ [ iso[i] for i in orbit] for orbit in orbits ]174for v in iso.keys():175if v >= n+m:176del iso[v]177v1,v2,label1 = dg._backend.iterator_in_edges([v],True).next()178w1,w2,label2 = dg._backend.iterator_out_edges([v],True).next()179dg._backend.del_edge(v1,v2,label1,True)180dg._backend.del_edge(w1,w2,label2,True)181dg._backend.del_vertex(v)182add_index = True183index = 0184while add_index:185l = partition_add[index]186if v in l:187dg._backend.add_edge(v1,w2,edges[index],True)188add_index = False189index += 1190dg._backend.relabel( iso, True )191return iso, orbits192193def _mutation_class_iter( dg, n, m, depth=infinity, return_dig6=False, show_depth=False, up_to_equivalence=True, sink_source=False ):194"""195Returns an iterator for mutation class of dg with respect to several parameters.196197INPUT:198199- ``dg`` -- a digraph with n+m vertices200- ``depth`` -- a positive integer or infinity specifying (roughly) how many steps away from the initial seed to mutate201- ``return_dig6`` -- indicates whether to convert digraph data to dig6 string data202- ``show_depth`` -- if True, indicates that a running count of the depth is to be displayed203- ``up_to_equivalence`` -- if True, only one digraph for each graph-isomorphism class is recorded204- ``sink_source`` -- if True, only mutations at sinks or sources are applied205206EXAMPLES::207208sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _mutation_class_iter209sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver210sage: dg = ClusterQuiver(['A',[1,2],1]).digraph()211sage: itt = _mutation_class_iter(dg, 3,0)212sage: itt.next()[0].edges()213[(0, 1, (1, -1)), (0, 2, (1, -1)), (1, 2, (1, -1))]214sage: itt.next()[0].edges()215[(0, 2, (1, -1)), (1, 0, (2, -2)), (2, 1, (1, -1))]216"""217timer = time.time()218depth_counter = 0219if up_to_equivalence:220iso, orbits = _dg_canonical_form( dg, n, m )221iso_inv = dict( (iso[a],a) for a in iso )222223dig6 = _digraph_to_dig6( dg, hashable=True )224dig6s = {}225if up_to_equivalence:226orbits = [ orbit[0] for orbit in orbits ]227dig6s[dig6] = [ orbits, [], iso_inv ]228else:229dig6s[dig6] = [ range(n), [] ]230if return_dig6:231yield (dig6, [])232else:233yield (dg, [])234235gets_bigger = True236if show_depth:237timer2 = time.time()238dc = str(depth_counter)239dc += ' ' * (5-len(dc))240nr = str(len(dig6s))241nr += ' ' * (10-len(nr))242print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer)243244while gets_bigger and depth_counter < depth:245gets_bigger = False246keys = dig6s.keys()247for key in keys:248mutation_indices = [ i for i in dig6s[key][0] if i < n ]249if mutation_indices:250dg = _dig6_to_digraph( key )251while mutation_indices:252i = mutation_indices.pop()253if not sink_source or _dg_is_sink_source( dg, i ):254dg_new = _digraph_mutate( dg, i, n, m )255if up_to_equivalence:256iso, orbits = _dg_canonical_form( dg_new, n, m )257i_new = iso[i]258iso_inv = dict( (iso[a],a) for a in iso )259else:260i_new = i261dig6_new = _digraph_to_dig6( dg_new, hashable=True )262if dig6_new in dig6s:263if i_new in dig6s[dig6_new][0]:264dig6s[dig6_new][0].remove(i_new)265else:266gets_bigger = True267if up_to_equivalence:268orbits = [ orbit[0] for orbit in orbits if i_new not in orbit ]269iso_history = dict( (a, dig6s[key][2][iso_inv[a]]) for a in iso )270i_history = iso_history[i_new]271history = dig6s[key][1] + [i_history]272dig6s[dig6_new] = [orbits,history,iso_history]273else:274orbits = range(n)275del orbits[i_new]276history = dig6s[key][1] + [i_new]277dig6s[dig6_new] = [orbits,history]278if return_dig6:279yield (dig6_new,history)280else:281yield (dg_new,history)282depth_counter += 1283if show_depth and gets_bigger:284timer2 = time.time()285dc = str(depth_counter)286dc += ' ' * (5-len(dc))287nr = str(len(dig6s))288nr += ' ' * (10-len(nr))289print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer)290291def _digraph_to_dig6( dg, hashable=False ):292"""293Returns the dig6 and edge data of the digraph dg.294295INPUT:296297- ``dg`` -- a digraph298- ``hashable`` -- (Boolean; optional; default:False) if ``True``, the edge labels are turned into a dict.299300EXAMPLES::301302sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_to_dig6303sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver304sage: dg = ClusterQuiver(['A',4]).digraph()305sage: _digraph_to_dig6(dg)306('COD?', {})307"""308dig6 = dg.dig6_string()309D = {}310for E in dg._backend.iterator_in_edges(dg,True):311if E[2] != (1,-1):312D[ (E[0],E[1]) ] = E[2]313if hashable:314D = tuple( sorted( D.items() ) )315return (dig6,D)316317def _dig6_to_digraph( dig6 ):318"""319Returns the digraph obtained from the dig6 and edge data.320321INPUT:322323- ``dig6`` -- a pair ``(dig6, edges)`` where ``dig6`` is a string encoding a digraph and ``edges`` is a dict or tuple encoding edges324325EXAMPLES::326327sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_to_dig6328sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _dig6_to_digraph329sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver330sage: dg = ClusterQuiver(['A',4]).digraph()331sage: data = _digraph_to_dig6(dg)332sage: _dig6_to_digraph(data)333Digraph on 4 vertices334sage: _dig6_to_digraph(data).edges()335[(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -1))]336"""337dig6, edges = dig6338dg = DiGraph( dig6 )339if not type(edges) == dict:340edges = dict( edges )341for edge in dg._backend.iterator_in_edges(dg,False):342if edge in edges:343dg.set_edge_label( edge[0],edge[1],edges[edge] )344else:345dg.set_edge_label( edge[0],edge[1], (1,-1) )346return dg347348def _dig6_to_matrix( dig6 ):349"""350Returns the matrix obtained from the dig6 and edge data.351352INPUT:353354- ``dig6`` -- a pair ``(dig6, edges)`` where ``dig6`` is a string encoding a digraph and ``edges`` is a dict or tuple encoding edges355356EXAMPLES::357358sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_to_dig6, _dig6_to_matrix359sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver360sage: dg = ClusterQuiver(['A',4]).digraph()361sage: data = _digraph_to_dig6(dg)362sage: _dig6_to_matrix(data)363[ 0 1 0 0]364[-1 0 -1 0]365[ 0 1 0 1]366[ 0 0 -1 0]367"""368dg = _dig6_to_digraph( dig6 )369return _edge_list_to_matrix( dg.edges(), dg.order(), 0 )370371def _dg_is_sink_source( dg, v ):372"""373Returns True iff the digraph dg has a sink or a source at vertex v.374375INPUT:376377- ``dg`` -- a digraph378- ``v`` -- a vertex of dg379380EXAMPLES::381382sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _dg_is_sink_source383sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver384sage: dg = ClusterQuiver(['A',[1,2],1]).digraph()385sage: _dg_is_sink_source(dg, 0 )386True387sage: _dg_is_sink_source(dg, 1 )388True389sage: _dg_is_sink_source(dg, 2 )390False391"""392in_edges = [ edge for edge in dg._backend.iterator_in_edges([v],True) ]393out_edges = [ edge for edge in dg._backend.iterator_out_edges([v],True) ]394return not ( in_edges and out_edges )395396def _graph_without_edge_labels(dg,vertices):397"""398Replaces edge labels in dg other than ``(1,-1)`` by this edge label, and returns the corresponding partition of the edges.399400EXAMPLES::401402sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _graph_without_edge_labels403sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver404sage: dg = ClusterQuiver(['B',4]).digraph(); dg.edges()405[(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -2))]406sage: _graph_without_edge_labels(dg,range(3)); dg.edges()407([[5]], [(1, -2)])408[(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 5, (1, -1)), (5, 3, (1, -1))]409"""410edges = [ edge for edge in dg._backend.iterator_in_edges(dg,True) ]411edge_labels = sorted([ label for v1,v2,label in edges if not label == (1,-1)])412i = 1413while i < len(edge_labels):414if edge_labels[i] == edge_labels[i-1]:415edge_labels.pop(i)416else:417i += 1418edge_partition = [[] for _ in xrange(len(edge_labels))]419i = 0420new_vertices = []421for u,v,l in edges:422while i in vertices or i in new_vertices:423i += 1424new_vertices.append(i)425if not l == (1,-1):426index = edge_labels.index(l)427edge_partition[index].append(i)428dg._backend.add_edge(u,i,(1,-1),True)429dg._backend.add_edge(i,v,(1,-1),True)430dg._backend.del_edge(u,v,l,True)431return [a for a in edge_partition if a != []], edge_labels432433def _has_two_cycles( dg ):434"""435Returns True if the input digraph has a 2-cycle and False otherwise.436437EXAMPLES::438439sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _has_two_cycles440sage: _has_two_cycles( DiGraph([[0,1],[1,0]]))441True442sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver443sage: _has_two_cycles( ClusterQuiver(['A',3]).digraph() )444False445"""446edge_set = dg.edges(labels=False)447for (v,w) in edge_set:448if (w,v) in edge_set:449return True450return False451452def _is_valid_digraph_edge_set( edges, frozen=0 ):453"""454Returns True if the input data is the edge set of a digraph for a quiver (no loops, no 2-cycles, edge-labels of the specified format), and returns False otherwise.455456INPUT:457458- ``frozen`` -- (integer; default:0) The number of frozen vertices.459460EXAMPLES::461462sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _is_valid_digraph_edge_set463sage: _is_valid_digraph_edge_set( [[0,1,'a'],[2,3,(1,-1)]] )464The given digraph has edge labels which are not integral or integral 2-tuples.465False466sage: _is_valid_digraph_edge_set( [[0,1],[2,3,(1,-1)]] )467True468sage: _is_valid_digraph_edge_set( [[0,1,'a'],[2,3,(1,-1)],[3,2,(1,-1)]] )469The given digraph or edge list contains oriented 2-cycles.470False471"""472try:473dg = DiGraph()474dg.allow_multiple_edges(True)475dg.add_edges( edges )476477# checks if the digraph contains loops478if dg.has_loops():479print "The given digraph or edge list contains loops."480return False481482# checks if the digraph contains oriented 2-cycles483if _has_two_cycles( dg ):484print "The given digraph or edge list contains oriented 2-cycles."485return False486487# checks if all edge labels are 'None', positive integers or tuples of positive integers488if not all( i == None or ( i in ZZ and i > 0 ) or ( type(i) == tuple and len(i) == 2 and i[0] in ZZ and i[1] in ZZ ) for i in dg.edge_labels() ):489print "The given digraph has edge labels which are not integral or integral 2-tuples."490return False491492# checks if all edge labels for multiple edges are 'None' or positive integers493if dg.has_multiple_edges():494for e in set( dg.multiple_edges(labels=False) ):495if not all( i == None or ( i in ZZ and i > 0 ) for i in dg.edge_label( e[0], e[1] ) ):496print "The given digraph or edge list contains multiple edges with non-integral labels."497return False498499n = dg.order() - frozen500if n < 0:501print "The number of frozen variables is larger than the number of vertices."502return False503504if [ e for e in dg.edges(labels=False) if e[0] >= n] != []:505print "The given digraph or edge list contains edges within the frozen vertices."506return False507508return True509except Exception:510print "Could not even build a digraph from the input data."511return False512513514