Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/combinat/cluster_algebra_quiver/mutation_class.py
8818 views
1
r"""
2
mutation_class
3
4
This file contains helper functions for compute the mutation class of a cluster algebra or quiver.
5
6
For the compendium on the cluster algebra and quiver package see
7
8
http://arxiv.org/abs/1102.4844
9
10
AUTHORS:
11
12
- Gregg Musiker
13
- Christian Stump
14
"""
15
16
#*****************************************************************************
17
# Copyright (C) 2011 Gregg Musiker <[email protected]>
18
# Christian Stump <[email protected]>
19
#
20
# Distributed under the terms of the GNU General Public License (GPL)
21
# http://www.gnu.org/licenses/
22
#*****************************************************************************
23
import time
24
from sage.groups.perm_gps.partn_ref.refinement_graphs import *
25
from sage.graphs.generic_graph import graph_isom_equivalent_non_edge_labeled_graph
26
from copy import copy
27
from sage.rings.all import ZZ, infinity
28
from sage.graphs.all import Graph, DiGraph
29
from sage.matrix.all import matrix
30
from sage.combinat.cluster_algebra_quiver.quiver_mutation_type import _edge_list_to_matrix
31
32
def _principal_part( mat ):
33
"""
34
Returns the principal part of a matrix.
35
36
INPUT:
37
38
- ``mat`` - a matrix with at least as many rows as columns
39
40
OUTPUT:
41
42
The top square part of the matrix ``mat``.
43
44
EXAMPLES::
45
46
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _principal_part
47
sage: M = Matrix([[1,2],[3,4],[5,6]]); M
48
[1 2]
49
[3 4]
50
[5 6]
51
sage: _principal_part(M)
52
[1 2]
53
[3 4]
54
"""
55
n, m = mat.ncols(), mat.nrows()-mat.ncols()
56
if m < 0:
57
raise ValueError('The input matrix has more columns than rows.')
58
elif m == 0:
59
return mat
60
else:
61
return mat.submatrix(0,0,n,n)
62
63
def _digraph_mutate( dg, k, n, m ):
64
"""
65
Returns a digraph obtained from dg with n+m vertices by mutating at vertex k.
66
67
INPUT:
68
69
- ``dg`` -- a digraph with integral edge labels with ``n+m`` vertices
70
- ``k`` -- the vertex at which ``dg`` is mutated
71
72
EXAMPLES::
73
74
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_mutate
75
sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
76
sage: dg = ClusterQuiver(['A',4]).digraph()
77
sage: dg.edges()
78
[(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -1))]
79
sage: _digraph_mutate(dg,2,4,0).edges()
80
[(0, 1, (1, -1)), (1, 2, (1, -1)), (3, 2, (1, -1))]
81
"""
82
edges = dict( ((v1,v2),label) for v1,v2,label in dg._backend.iterator_in_edges(dg,True) )
83
in_edges = [ (v1,v2,edges[(v1,v2)]) for (v1,v2) in edges if v2 == k ]
84
out_edges = [ (v1,v2,edges[(v1,v2)]) for (v1,v2) in edges if v1 == k ]
85
in_edges_new = [ (v2,v1,(-label[1],-label[0])) for (v1,v2,label) in in_edges ]
86
out_edges_new = [ (v2,v1,(-label[1],-label[0])) for (v1,v2,label) in out_edges ]
87
diag_edges_new = []
88
diag_edges_del = []
89
90
for (v1,v2,label1) in in_edges:
91
for (w1,w2,label2) in out_edges:
92
l11,l12 = label1
93
l21,l22 = label2
94
if (v1,w2) in edges:
95
diag_edges_del.append( (v1,w2,edges[(v1,w2)]) )
96
a,b = edges[(v1,w2)]
97
a,b = a+l11*l21, b-l12*l22
98
diag_edges_new.append( (v1,w2,(a,b)) )
99
elif (w2,v1) in edges:
100
diag_edges_del.append( (w2,v1,edges[(w2,v1)]) )
101
a,b = edges[(w2,v1)]
102
a,b = b+l11*l21, a-l12*l22
103
if a<0:
104
diag_edges_new.append( (w2,v1,(b,a)) )
105
elif a>0:
106
diag_edges_new.append( (v1,w2,(a,b)) )
107
else:
108
a,b = l11*l21,-l12*l22
109
diag_edges_new.append( (v1,w2,(a,b)) )
110
111
del_edges = in_edges + out_edges + diag_edges_del
112
new_edges = in_edges_new + out_edges_new + diag_edges_new
113
new_edges += [ (v1,v2,edges[(v1,v2)]) for (v1,v2) in edges if not (v1,v2,edges[(v1,v2)]) in del_edges ]
114
115
dg_new = DiGraph()
116
for v1,v2,label in new_edges:
117
dg_new._backend.add_edge(v1,v2,label,True)
118
if dg_new.order() < n+m:
119
dg_new_vertices = [ v for v in dg_new ]
120
for i in [ v for v in dg if v not in dg_new_vertices ]:
121
dg_new.add_vertex(i)
122
123
return dg_new
124
125
def _matrix_to_digraph( M ):
126
"""
127
Returns the digraph obtained from the matrix ``M``.
128
In order to generate a quiver, we assume that ``M`` is skew-symmetrizable.
129
130
EXAMPLES::
131
132
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _matrix_to_digraph
133
sage: _matrix_to_digraph(matrix(3,[0,1,0,-1,0,-1,0,1,0]))
134
Digraph on 3 vertices
135
"""
136
n = M.ncols()
137
138
dg = DiGraph(sparse=True)
139
for i,j in M.nonzero_positions():
140
if i >= n: a,b = M[i,j],-M[i,j]
141
else: a,b = M[i,j],M[j,i]
142
if a > 0:
143
dg._backend.add_edge(i,j,(a,b),True)
144
elif i >= n:
145
dg._backend.add_edge(j,i,(-a,-b),True)
146
if dg.order() < M.nrows():
147
for i in [ index for index in xrange(M.nrows()) if index not in dg ]:
148
dg.add_vertex(i)
149
return dg
150
151
def _dg_canonical_form( dg, n, m ):
152
"""
153
Turns the digraph ``dg`` into its canonical form, and returns the corresponding isomorphism, and the vertex orbits of the automorphism group.
154
155
EXAMPLES::
156
157
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _dg_canonical_form
158
sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
159
sage: dg = ClusterQuiver(['B',4]).digraph(); dg.edges()
160
[(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -2))]
161
sage: _dg_canonical_form(dg,4,0); dg.edges()
162
({0: 0, 1: 3, 2: 1, 3: 2}, [[0], [3], [1], [2]])
163
[(0, 3, (1, -1)), (1, 2, (1, -2)), (1, 3, (1, -1))]
164
"""
165
vertices = [ v for v in dg ]
166
if m > 0:
167
partition = [ vertices[:n], vertices[n:] ]
168
else:
169
partition = [ vertices ]
170
partition_add, edges = _graph_without_edge_labels(dg,vertices)
171
partition += partition_add
172
automorphism_group, obsolete, iso = search_tree(dg, partition=partition, lab=True, dig=True, certify=True)
173
orbits = get_orbits( automorphism_group, n+m )
174
orbits = [ [ iso[i] for i in orbit] for orbit in orbits ]
175
for v in iso.keys():
176
if v >= n+m:
177
del iso[v]
178
v1,v2,label1 = dg._backend.iterator_in_edges([v],True).next()
179
w1,w2,label2 = dg._backend.iterator_out_edges([v],True).next()
180
dg._backend.del_edge(v1,v2,label1,True)
181
dg._backend.del_edge(w1,w2,label2,True)
182
dg._backend.del_vertex(v)
183
add_index = True
184
index = 0
185
while add_index:
186
l = partition_add[index]
187
if v in l:
188
dg._backend.add_edge(v1,w2,edges[index],True)
189
add_index = False
190
index += 1
191
dg._backend.relabel( iso, True )
192
return iso, orbits
193
194
def _mutation_class_iter( dg, n, m, depth=infinity, return_dig6=False, show_depth=False, up_to_equivalence=True, sink_source=False ):
195
"""
196
Returns an iterator for mutation class of dg with respect to several parameters.
197
198
INPUT:
199
200
- ``dg`` -- a digraph with n+m vertices
201
- ``depth`` -- a positive integer or infinity specifying (roughly) how many steps away from the initial seed to mutate
202
- ``return_dig6`` -- indicates whether to convert digraph data to dig6 string data
203
- ``show_depth`` -- if True, indicates that a running count of the depth is to be displayed
204
- ``up_to_equivalence`` -- if True, only one digraph for each graph-isomorphism class is recorded
205
- ``sink_source`` -- if True, only mutations at sinks or sources are applied
206
207
EXAMPLES::
208
209
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _mutation_class_iter
210
sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
211
sage: dg = ClusterQuiver(['A',[1,2],1]).digraph()
212
sage: itt = _mutation_class_iter(dg, 3,0)
213
sage: itt.next()[0].edges()
214
[(0, 1, (1, -1)), (0, 2, (1, -1)), (1, 2, (1, -1))]
215
sage: itt.next()[0].edges()
216
[(0, 2, (1, -1)), (1, 0, (2, -2)), (2, 1, (1, -1))]
217
"""
218
timer = time.time()
219
depth_counter = 0
220
if up_to_equivalence:
221
iso, orbits = _dg_canonical_form( dg, n, m )
222
iso_inv = dict( (iso[a],a) for a in iso )
223
224
dig6 = _digraph_to_dig6( dg, hashable=True )
225
dig6s = {}
226
if up_to_equivalence:
227
orbits = [ orbit[0] for orbit in orbits ]
228
dig6s[dig6] = [ orbits, [], iso_inv ]
229
else:
230
dig6s[dig6] = [ range(n), [] ]
231
if return_dig6:
232
yield (dig6, [])
233
else:
234
yield (dg, [])
235
236
gets_bigger = True
237
if show_depth:
238
timer2 = time.time()
239
dc = str(depth_counter)
240
dc += ' ' * (5-len(dc))
241
nr = str(len(dig6s))
242
nr += ' ' * (10-len(nr))
243
print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer)
244
245
while gets_bigger and depth_counter < depth:
246
gets_bigger = False
247
keys = dig6s.keys()
248
for key in keys:
249
mutation_indices = [ i for i in dig6s[key][0] if i < n ]
250
if mutation_indices:
251
dg = _dig6_to_digraph( key )
252
while mutation_indices:
253
i = mutation_indices.pop()
254
if not sink_source or _dg_is_sink_source( dg, i ):
255
dg_new = _digraph_mutate( dg, i, n, m )
256
if up_to_equivalence:
257
iso, orbits = _dg_canonical_form( dg_new, n, m )
258
i_new = iso[i]
259
iso_inv = dict( (iso[a],a) for a in iso )
260
else:
261
i_new = i
262
dig6_new = _digraph_to_dig6( dg_new, hashable=True )
263
if dig6_new in dig6s:
264
if i_new in dig6s[dig6_new][0]:
265
dig6s[dig6_new][0].remove(i_new)
266
else:
267
gets_bigger = True
268
if up_to_equivalence:
269
orbits = [ orbit[0] for orbit in orbits if i_new not in orbit ]
270
iso_history = dict( (a, dig6s[key][2][iso_inv[a]]) for a in iso )
271
i_history = iso_history[i_new]
272
history = dig6s[key][1] + [i_history]
273
dig6s[dig6_new] = [orbits,history,iso_history]
274
else:
275
orbits = range(n)
276
del orbits[i_new]
277
history = dig6s[key][1] + [i_new]
278
dig6s[dig6_new] = [orbits,history]
279
if return_dig6:
280
yield (dig6_new,history)
281
else:
282
yield (dg_new,history)
283
depth_counter += 1
284
if show_depth and gets_bigger:
285
timer2 = time.time()
286
dc = str(depth_counter)
287
dc += ' ' * (5-len(dc))
288
nr = str(len(dig6s))
289
nr += ' ' * (10-len(nr))
290
print "Depth: %s found: %s Time: %.2f s"%(dc,nr,timer2-timer)
291
292
def _digraph_to_dig6( dg, hashable=False ):
293
"""
294
Returns the dig6 and edge data of the digraph dg.
295
296
INPUT:
297
298
- ``dg`` -- a digraph
299
- ``hashable`` -- (Boolean; optional; default:False) if ``True``, the edge labels are turned into a dict.
300
301
EXAMPLES::
302
303
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_to_dig6
304
sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
305
sage: dg = ClusterQuiver(['A',4]).digraph()
306
sage: _digraph_to_dig6(dg)
307
('COD?', {})
308
"""
309
dig6 = dg.dig6_string()
310
D = {}
311
for E in dg._backend.iterator_in_edges(dg,True):
312
if E[2] != (1,-1):
313
D[ (E[0],E[1]) ] = E[2]
314
if hashable:
315
D = tuple( sorted( D.items() ) )
316
return (dig6,D)
317
318
def _dig6_to_digraph( dig6 ):
319
"""
320
Returns the digraph obtained from the dig6 and edge data.
321
322
INPUT:
323
324
- ``dig6`` -- a pair ``(dig6, edges)`` where ``dig6`` is a string encoding a digraph and ``edges`` is a dict or tuple encoding edges
325
326
EXAMPLES::
327
328
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_to_dig6
329
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _dig6_to_digraph
330
sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
331
sage: dg = ClusterQuiver(['A',4]).digraph()
332
sage: data = _digraph_to_dig6(dg)
333
sage: _dig6_to_digraph(data)
334
Digraph on 4 vertices
335
sage: _dig6_to_digraph(data).edges()
336
[(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -1))]
337
"""
338
dig6, edges = dig6
339
dg = DiGraph( dig6 )
340
if not type(edges) == dict:
341
edges = dict( edges )
342
for edge in dg._backend.iterator_in_edges(dg,False):
343
if edge in edges:
344
dg.set_edge_label( edge[0],edge[1],edges[edge] )
345
else:
346
dg.set_edge_label( edge[0],edge[1], (1,-1) )
347
return dg
348
349
def _dig6_to_matrix( dig6 ):
350
"""
351
Returns the matrix obtained from the dig6 and edge data.
352
353
INPUT:
354
355
- ``dig6`` -- a pair ``(dig6, edges)`` where ``dig6`` is a string encoding a digraph and ``edges`` is a dict or tuple encoding edges
356
357
EXAMPLES::
358
359
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _digraph_to_dig6, _dig6_to_matrix
360
sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
361
sage: dg = ClusterQuiver(['A',4]).digraph()
362
sage: data = _digraph_to_dig6(dg)
363
sage: _dig6_to_matrix(data)
364
[ 0 1 0 0]
365
[-1 0 -1 0]
366
[ 0 1 0 1]
367
[ 0 0 -1 0]
368
"""
369
dg = _dig6_to_digraph( dig6 )
370
return _edge_list_to_matrix( dg.edges(), dg.order(), 0 )
371
372
def _dg_is_sink_source( dg, v ):
373
"""
374
Returns True iff the digraph dg has a sink or a source at vertex v.
375
376
INPUT:
377
378
- ``dg`` -- a digraph
379
- ``v`` -- a vertex of dg
380
381
EXAMPLES::
382
383
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _dg_is_sink_source
384
sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
385
sage: dg = ClusterQuiver(['A',[1,2],1]).digraph()
386
sage: _dg_is_sink_source(dg, 0 )
387
True
388
sage: _dg_is_sink_source(dg, 1 )
389
True
390
sage: _dg_is_sink_source(dg, 2 )
391
False
392
"""
393
in_edges = [ edge for edge in dg._backend.iterator_in_edges([v],True) ]
394
out_edges = [ edge for edge in dg._backend.iterator_out_edges([v],True) ]
395
return not ( in_edges and out_edges )
396
397
def _graph_without_edge_labels(dg,vertices):
398
"""
399
Replaces edge labels in dg other than ``(1,-1)`` by this edge label, and returns the corresponding partition of the edges.
400
401
EXAMPLES::
402
403
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _graph_without_edge_labels
404
sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
405
sage: dg = ClusterQuiver(['B',4]).digraph(); dg.edges()
406
[(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 3, (1, -2))]
407
sage: _graph_without_edge_labels(dg,range(3)); dg.edges()
408
([[5]], [(1, -2)])
409
[(0, 1, (1, -1)), (2, 1, (1, -1)), (2, 5, (1, -1)), (5, 3, (1, -1))]
410
"""
411
edges = [ edge for edge in dg._backend.iterator_in_edges(dg,True) ]
412
edge_labels = sorted([ label for v1,v2,label in edges if not label == (1,-1)])
413
i = 1
414
while i < len(edge_labels):
415
if edge_labels[i] == edge_labels[i-1]:
416
edge_labels.pop(i)
417
else:
418
i += 1
419
edge_partition = [[] for _ in xrange(len(edge_labels))]
420
i = 0
421
new_vertices = []
422
for u,v,l in edges:
423
while i in vertices or i in new_vertices:
424
i += 1
425
new_vertices.append(i)
426
if not l == (1,-1):
427
index = edge_labels.index(l)
428
edge_partition[index].append(i)
429
dg._backend.add_edge(u,i,(1,-1),True)
430
dg._backend.add_edge(i,v,(1,-1),True)
431
dg._backend.del_edge(u,v,l,True)
432
return [a for a in edge_partition if a != []], edge_labels
433
434
def _has_two_cycles( dg ):
435
"""
436
Returns True if the input digraph has a 2-cycle and False otherwise.
437
438
EXAMPLES::
439
440
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _has_two_cycles
441
sage: _has_two_cycles( DiGraph([[0,1],[1,0]]))
442
True
443
sage: from sage.combinat.cluster_algebra_quiver.quiver import ClusterQuiver
444
sage: _has_two_cycles( ClusterQuiver(['A',3]).digraph() )
445
False
446
"""
447
edge_set = dg.edges(labels=False)
448
for (v,w) in edge_set:
449
if (w,v) in edge_set:
450
return True
451
return False
452
453
def _is_valid_digraph_edge_set( edges, frozen=0 ):
454
"""
455
Returns 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.
456
457
INPUT:
458
459
- ``frozen`` -- (integer; default:0) The number of frozen vertices.
460
461
EXAMPLES::
462
463
sage: from sage.combinat.cluster_algebra_quiver.mutation_class import _is_valid_digraph_edge_set
464
sage: _is_valid_digraph_edge_set( [[0,1,'a'],[2,3,(1,-1)]] )
465
The given digraph has edge labels which are not integral or integral 2-tuples.
466
False
467
sage: _is_valid_digraph_edge_set( [[0,1],[2,3,(1,-1)]] )
468
True
469
sage: _is_valid_digraph_edge_set( [[0,1,'a'],[2,3,(1,-1)],[3,2,(1,-1)]] )
470
The given digraph or edge list contains oriented 2-cycles.
471
False
472
"""
473
try:
474
dg = DiGraph()
475
dg.allow_multiple_edges(True)
476
dg.add_edges( edges )
477
478
# checks if the digraph contains loops
479
if dg.has_loops():
480
print "The given digraph or edge list contains loops."
481
return False
482
483
# checks if the digraph contains oriented 2-cycles
484
if _has_two_cycles( dg ):
485
print "The given digraph or edge list contains oriented 2-cycles."
486
return False
487
488
# checks if all edge labels are 'None', positive integers or tuples of positive integers
489
if 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() ):
490
print "The given digraph has edge labels which are not integral or integral 2-tuples."
491
return False
492
493
# checks if all edge labels for multiple edges are 'None' or positive integers
494
if dg.has_multiple_edges():
495
for e in set( dg.multiple_edges(labels=False) ):
496
if not all( i == None or ( i in ZZ and i > 0 ) for i in dg.edge_label( e[0], e[1] ) ):
497
print "The given digraph or edge list contains multiple edges with non-integral labels."
498
return False
499
500
n = dg.order() - frozen
501
if n < 0:
502
print "The number of frozen variables is larger than the number of vertices."
503
return False
504
505
if [ e for e in dg.edges(labels=False) if e[0] >= n] != []:
506
print "The given digraph or edge list contains edges within the frozen vertices."
507
return False
508
509
return True
510
except Exception:
511
print "Could not even build a digraph from the input data."
512
return False
513
514