Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/sandpiles/sandpile.py
4097 views
1
from string import join
2
import os
3
from sage.symbolic.all import I, pi
4
from sage.functions.log import exp
5
from sage.graphs.all import DiGraph, Graph, graphs, digraphs
6
from copy import deepcopy
7
from sage.rings.all import PolynomialRing, QQ, ZZ, lcm
8
from sage.misc.all import prod, det, forall, tmp_filename, random, randint, exists, denominator, srange
9
from sage.misc.misc import deprecated_function_alias
10
from sage.modules.free_module_element import vector
11
from sage.matrix.constructor import matrix, identity_matrix
12
from sage.interfaces.singular import singular
13
from sage.combinat.combinat import CombinatorialClass, combinations
14
from sage.combinat.set_partition import SetPartitions
15
from sage.homology.simplicial_complex import SimplicialComplex
16
from sage.plot.colors import rainbow
17
18
r"""
19
To calculate linear systems associated with divisors, 4ti2 must be installed.
20
One way to do this is to run sage -i to install glpk, then 4ti2. See
21
http://sagemath.org/download-packages.html to get the exact names of these
22
packages. An alternative is to install 4ti2 separately, then point the
23
following variable to the correct path.
24
"""
25
SAGE_ROOT = os.environ['SAGE_ROOT']
26
path_to_zsolve = SAGE_ROOT+'/local/bin/'
27
28
r"""
29
Sage Sandpiles
30
31
Functions and classes for mathematical sandpiles.
32
33
Version: 2.3
34
35
AUTHOR:
36
-- Marshall Hampton (2010-1-10) modified for inclusion as a module
37
within Sage library.
38
39
-- David Perkinson (2010-12-14) added show3d(), fixed bug in resolution(),
40
replaced elementary_divisors() with invariant_factors(), added show() for
41
SandpileConfig and SandpileDivisor.
42
43
-- David Perkinson (2010-9-18): removed is_undirected, added show(), added
44
verbose arguments to several functions to display SandpileConfigs and divisors as
45
lists of integers
46
47
-- David Perkinson (2010-12-19): created separate SandpileConfig, SandpileDivisor, and
48
Sandpile classes
49
50
-- David Perkinson (2009-07-15): switched to using config_to_list instead
51
of .values(), thus fixing a few bugs when not using integer labels for
52
vertices.
53
54
-- David Perkinson (2009): many undocumented improvements
55
56
-- David Perkinson (2008-12-27): initial version
57
58
EXAMPLES::
59
60
A weighted directed graph given as a Python dictionary:
61
62
sage: from sage.sandpiles import *
63
sage: g = {0: {}, \
64
1: {0: 1, 2: 1, 3: 1}, \
65
2: {1: 1, 3: 1, 4: 1}, \
66
3: {1: 1, 2: 1, 4: 1}, \
67
4: {2: 1, 3: 1}}
68
69
The associated sandpile with 0 chosen as the sink::
70
71
sage: S = Sandpile(g,0)
72
73
A picture of the graph::
74
75
sage: S.show()
76
77
The relevant Laplacian matrices::
78
79
sage: S.laplacian()
80
[ 0 0 0 0 0]
81
[-1 3 -1 -1 0]
82
[ 0 -1 3 -1 -1]
83
[ 0 -1 -1 3 -1]
84
[ 0 0 -1 -1 2]
85
sage: S.reduced_laplacian()
86
[ 3 -1 -1 0]
87
[-1 3 -1 -1]
88
[-1 -1 3 -1]
89
[ 0 -1 -1 2]
90
91
The number of elements of the sandpile group for S::
92
93
sage: S.group_order()
94
8
95
96
The structure of the sandpile group::
97
98
sage: S.invariant_factors()
99
[1, 1, 1, 8]
100
101
The elements of the sandpile group for S::
102
103
sage: S.recurrents()
104
[{1: 2, 2: 2, 3: 2, 4: 1},
105
{1: 2, 2: 2, 3: 2, 4: 0},
106
{1: 2, 2: 1, 3: 2, 4: 0},
107
{1: 2, 2: 2, 3: 0, 4: 1},
108
{1: 2, 2: 0, 3: 2, 4: 1},
109
{1: 2, 2: 2, 3: 1, 4: 0},
110
{1: 2, 2: 1, 3: 2, 4: 1},
111
{1: 2, 2: 2, 3: 1, 4: 1}]
112
113
The maximal stable element (2 grains of sand on vertices 1, 2, and 3, and 1
114
grain of sand on vertex 4::
115
116
sage: S.max_stable()
117
{1: 2, 2: 2, 3: 2, 4: 1}
118
sage: S.max_stable().values()
119
[2, 2, 2, 1]
120
121
The identity of the sandpile group for S::
122
123
sage: S.identity()
124
{1: 2, 2: 2, 3: 2, 4: 0}
125
126
Some group operations::
127
128
sage: m = S.max_stable()
129
sage: i = S.identity()
130
sage: m.values()
131
[2, 2, 2, 1]
132
sage: i.values()
133
[2, 2, 2, 0]
134
sage: m+i # coordinate-wise sum
135
{1: 4, 2: 4, 3: 4, 4: 1}
136
sage: m - i
137
{1: 0, 2: 0, 3: 0, 4: 1}
138
sage: m & i # add, then stabilize
139
{1: 2, 2: 2, 3: 2, 4: 1}
140
sage: e = m + m
141
sage: e
142
{1: 4, 2: 4, 3: 4, 4: 2}
143
sage: ~e # stabilize
144
{1: 2, 2: 2, 3: 2, 4: 0}
145
sage: a = -m
146
sage: a & m
147
{1: 0, 2: 0, 3: 0, 4: 0}
148
sage: a * m # add, then find the equivalent recurrent
149
{1: 2, 2: 2, 3: 2, 4: 0}
150
sage: a^3 # a*a*a
151
{1: 2, 2: 2, 3: 2, 4: 1}
152
sage: a^(-1) == m
153
True
154
sage: a < m # every coordinate of a is < that of m
155
True
156
157
Firing an unstable vertex returns resulting configuration::
158
159
sage: c = S.max_stable() + S.identity()
160
sage: c.fire_vertex(1)
161
{1: 1, 2: 5, 3: 5, 4: 1}
162
sage: c
163
{1: 4, 2: 4, 3: 4, 4: 1}
164
165
Fire all unstable vertices::
166
167
sage: c.unstable()
168
[1, 2, 3]
169
sage: c.fire_unstable()
170
{1: 3, 2: 3, 3: 3, 4: 3}
171
172
Stabilize c, returning the resulting configuration and the firing
173
vector::
174
175
sage: c.stabilize(True)
176
[{1: 2, 2: 2, 3: 2, 4: 1}, {1: 6, 2: 8, 3: 8, 4: 8}]
177
sage: c
178
{1: 4, 2: 4, 3: 4, 4: 1}
179
sage: S.max_stable() & S.identity() == c.stabilize()
180
True
181
182
The number of superstable configurations of each degree::
183
184
sage: S.hilbert_function()
185
[1, 4, 8]
186
sage: S.postulation()
187
2
188
189
the saturated, homogeneous sandpile ideal
190
191
sage: S.ideal()
192
Ideal (x1 - x0, x3*x2 - x0^2, x4^2 - x0^2, x2^3 - x4*x3*x0, x4*x2^2 - x3^2*x0, x3^3 - x4*x2*x0, x4*x3^2 - x2^2*x0) of Multivariate Polynomial Ring in x4, x3, x2, x1, x0 over Rational Field
193
194
its minimal free resolution
195
196
sage: S.resolution()
197
'R^1 <-- R^7 <-- R^15 <-- R^13 <-- R^4'
198
199
and its Betti numbers::
200
201
sage: S.betti()
202
0 1 2 3 4
203
------------------------------------
204
0: 1 1 - - -
205
1: - 2 2 - -
206
2: - 4 13 13 4
207
------------------------------------
208
total: 1 7 15 13 4
209
210
Distribution of avalanche sizes::
211
212
sage: S = grid_sandpile(10,10)
213
sage: m = S.max_stable()
214
sage: a = []
215
sage: for i in range(1000):
216
... m = m.add_random()
217
... m, f = m.stabilize(True)
218
... a.append(sum(f.values()))
219
...
220
sage: p = list_plot([[log(i+1),log(a.count(i))] for i in [0..max(a)] if a.count(i)])
221
sage: p.axes_labels(['log(N)','log(D(N))'])
222
sage: t = text("Distribution of avalanche sizes", (2,2), rgbcolor=(1,0,0))
223
sage: show(p+t,axes_labels=['log(N)','log(D(N))'])
224
"""
225
#*****************************************************************************
226
# Copyright (C) 2011 David Perkinson <[email protected]>
227
#
228
# Distributed under the terms of the GNU General Public License (GPL)
229
# http://www.gnu.org/licenses/
230
#*****************************************************************************
231
232
class Sandpile(DiGraph):
233
"""
234
Class for Dhar's abelian sandpile model.
235
"""
236
237
def __init__(self, g, sink):
238
r"""
239
Create a sandpile.
240
241
INPUT:
242
243
- ``g`` - dict for directed multgraph (see NOTES) edges weighted by
244
nonnegative integers
245
246
- ``sink`` - A sink vertex. Any outgoing edges from the designated
247
sink are ignored for the purposes of stabilization. It is assumed
248
that every vertex has a directed path into the sink.
249
250
OUTPUT:
251
252
Sandpile
253
254
EXAMPLES:
255
256
Here ``g`` represents a square with directed, multiple edges with three
257
vertices, ``a``, ``b``, ``c``, and ``d``. The vertex ``a`` has
258
outgoing edges to itself (weight 2), to vertex ``b`` (weight 1), and
259
vertex ``c`` (weight 3), for example.
260
261
::
262
263
sage: g = {'a': {'a':2, 'b':1, 'c':3}, 'b': {'a':1, 'd':1},\
264
'c': {'a':1,'d': 1}, 'd': {'b':1, 'c':1}}
265
sage: G = Sandpile(g,'d')
266
267
Here is a square with unweighted edges. In this example, the graph is
268
also undirected.
269
270
::
271
272
sage: g = {0:[1,2], 1:[0,3], 2:[0,3], 3:[1,2]}
273
sage: G = Sandpile(g,3)
274
275
NOTES::
276
277
Loops are allowed. There are two admissible formats, both of which are
278
dictionaries whose keys are the vertex names. In one format, the
279
values are dictionaries with keys the names of vertices which are the
280
tails of outgoing edges and with values the weights of the edges. In
281
the other format, the values are lists of names of vertices which are
282
the tails of the outgoing edges. All weights are then automatically
283
assigned the value 1.
284
285
TESTS::
286
287
sage: S = complete_sandpile(4)
288
sage: TestSuite(S).run()
289
"""
290
# preprocess a graph, if necessary
291
if type(g) == dict and type(g.values()[0]) == dict:
292
pass # this is the default format
293
elif type(g) == dict and type(g.values()[0]) == list:
294
processed_g = {}
295
for k in g.keys():
296
temp = {}
297
for vertex in g[k]:
298
temp[vertex] = 1
299
processed_g[k] = temp
300
g = processed_g
301
elif type(g) == Graph:
302
processed_g = {}
303
for v in g.vertices():
304
edges = {}
305
for n in g.neighbors(v):
306
if type(g.edge_label(v,n)) == type(1) and g.edge_label(v,n) >=0:
307
edges[n] = g.edge_label(v,n)
308
else:
309
edges[n] = 1
310
processed_g[v] = edges
311
g = processed_g
312
elif type(g) == DiGraph:
313
processed_g = {}
314
for v in g.vertices():
315
edges = {}
316
for n in g.neighbors_out(v):
317
if (type(g.edge_label(v,n)) == type(1)
318
and g.edge_label(v,n)>=0):
319
edges[n] = g.edge_label(v,n)
320
else:
321
edges[n] = 1
322
processed_g[v] = edges
323
g = processed_g
324
else:
325
raise SyntaxError, g
326
327
# create digraph and initialize some variables
328
DiGraph.__init__(self,g,weighted=True)
329
self._dict = deepcopy(g)
330
self._sink = sink # key for sink
331
self._sink_ind = self.vertices().index(sink)
332
self._nonsink_vertices = deepcopy(self.vertices())
333
del self._nonsink_vertices[self._sink_ind]
334
# compute laplacians:
335
self._laplacian = self.laplacian_matrix(indegree=False)
336
temp = range(self.num_verts())
337
del temp[self._sink_ind]
338
self._reduced_laplacian = self._laplacian[temp,temp]
339
340
def __getattr__(self, name):
341
"""
342
Set certain variables only when called.
343
344
INPUT:
345
346
``name`` - name of an internal method
347
348
OUTPUT:
349
350
None.
351
352
EXAMPLES::
353
354
sage: S = complete_sandpile(5)
355
sage: S.__getattr__('_max_stable')
356
{1: 3, 2: 3, 3: 3, 4: 3}
357
"""
358
if not self.__dict__.has_key(name):
359
if name == '_max_stable':
360
self._set_max_stable()
361
return deepcopy(self.__dict__[name])
362
if name == '_max_stable_div':
363
self._set_max_stable_div()
364
return deepcopy(self.__dict__[name])
365
elif name == '_out_degrees':
366
self._set_out_degrees()
367
return deepcopy(self.__dict__[name])
368
elif name == '_in_degrees':
369
self._set_in_degrees()
370
return deepcopy(self.__dict__[name])
371
elif name == '_burning_config' or name == '_burning_script':
372
self._set_burning_config()
373
return deepcopy(self.__dict__[name])
374
elif name == '_identity':
375
self._set_identity()
376
return deepcopy(self.__dict__[name])
377
elif name == '_recurrents':
378
self._set_recurrents()
379
return deepcopy(self.__dict__[name])
380
elif name == '_min_recurrents':
381
self._set_min_recurrents()
382
return deepcopy(self.__dict__[name])
383
elif name == '_superstables':
384
self._set_superstables()
385
return deepcopy(self.__dict__[name])
386
elif name == '_group_order':
387
self.__dict__[name] = det(self._reduced_laplacian.dense_matrix())
388
return self.__dict__[name]
389
elif name == '_invariant_factors':
390
self._set_invariant_factors()
391
return deepcopy(self.__dict__[name])
392
elif name == '_betti_complexes':
393
self._set_betti_complexes()
394
return deepcopy(self.__dict__[name])
395
elif (name == '_postulation' or name == '_h_vector'
396
or name == '_hilbert_function'):
397
self._set_hilbert_function()
398
return deepcopy(self.__dict__[name])
399
elif (name == '_ring' or name == '_unsaturated_ideal'):
400
self._set_ring()
401
return self.__dict__[name]
402
elif name == '_ideal':
403
self._set_ideal()
404
return self.__dict__[name]
405
elif (name == '_resolution' or name == '_betti' or name ==
406
'_singular_resolution'):
407
self._set_resolution()
408
return self.__dict__[name]
409
elif name == '_groebner':
410
self._set_groebner()
411
return self.__dict__[name]
412
elif name == '_points':
413
self._set_points()
414
return self.__dict__[name]
415
else:
416
raise AttributeError, name
417
418
def version(self):
419
r"""
420
The version number of Sage Sandpiles
421
422
INPUT:
423
424
None
425
426
OUTPUT:
427
428
string
429
430
431
EXAMPLES::
432
433
sage: S = sandlib('generic')
434
sage: S.version()
435
Sage Sandpiles Version 2.3
436
"""
437
print 'Sage Sandpiles Version 2.3'
438
439
def show(self,**kwds):
440
r"""
441
Draws the graph.
442
443
INPUT:
444
445
``kwds`` - arguments passed to the show method for Graph or DiGraph
446
447
OUTPUT:
448
449
None
450
451
EXAMPLES::
452
453
sage: S = sandlib('generic')
454
sage: S.show()
455
sage: S.show(graph_border=True, edge_labels=True)
456
"""
457
458
if self.is_undirected():
459
Graph(self).show(**kwds)
460
else:
461
DiGraph(self).show(**kwds)
462
463
def show3d(self,**kwds):
464
r"""
465
Draws the graph.
466
467
INPUT:
468
469
``kwds`` - arguments passed to the show method for Graph or DiGraph
470
471
OUTPUT:
472
473
None
474
475
EXAMPLES::
476
477
sage: S = sandlib('generic')
478
sage: S.show3d()
479
"""
480
481
if self.is_undirected():
482
Graph(self).show3d(**kwds)
483
else:
484
DiGraph(self).show3d(**kwds)
485
486
def dict(self):
487
r"""
488
A dictionary of dictionaries representing a directed graph.
489
490
INPUT:
491
492
None
493
494
OUTPUT:
495
496
dict
497
498
499
EXAMPLES::
500
501
sage: G = sandlib('generic')
502
sage: G.dict()
503
{0: {},
504
1: {0: 1, 3: 1, 4: 1},
505
2: {0: 1, 3: 1, 5: 1},
506
3: {2: 1, 5: 1},
507
4: {1: 1, 3: 1},
508
5: {2: 1, 3: 1}}
509
sage: G.sink()
510
0
511
"""
512
return deepcopy(self._dict)
513
514
def sink(self):
515
r"""
516
The identifier for the sink vertex.
517
518
INPUT:
519
520
None
521
522
OUTPUT:
523
524
Object (name for the sink vertex)
525
526
EXAMPLES::
527
528
sage: G = sandlib('generic')
529
sage: G.sink()
530
0
531
sage: H = grid_sandpile(2,2)
532
sage: H.sink()
533
'sink'
534
sage: type(H.sink())
535
<type 'str'>
536
"""
537
return self._sink
538
539
def laplacian(self):
540
r"""
541
The Laplacian matrix of the graph.
542
543
INPUT:
544
545
None
546
547
OUTPUT:
548
549
matrix
550
551
552
EXAMPLES::
553
554
sage: G = sandlib('generic')
555
sage: G.laplacian()
556
[ 0 0 0 0 0 0]
557
[-1 3 0 -1 -1 0]
558
[-1 0 3 -1 0 -1]
559
[ 0 0 -1 2 0 -1]
560
[ 0 -1 0 -1 2 0]
561
[ 0 0 -1 -1 0 2]
562
563
NOTES::
564
565
The function ``laplacian_matrix`` should be avoided. It returns the
566
indegree version of the laplacian.
567
"""
568
return deepcopy(self._laplacian)
569
570
def reduced_laplacian(self):
571
r"""
572
The reduced Laplacian matrix of the graph.
573
574
INPUT:
575
576
None
577
578
OUTPUT:
579
580
matrix
581
582
583
EXAMPLES::
584
585
sage: G = sandlib('generic')
586
sage: G.laplacian()
587
[ 0 0 0 0 0 0]
588
[-1 3 0 -1 -1 0]
589
[-1 0 3 -1 0 -1]
590
[ 0 0 -1 2 0 -1]
591
[ 0 -1 0 -1 2 0]
592
[ 0 0 -1 -1 0 2]
593
sage: G.reduced_laplacian()
594
[ 3 0 -1 -1 0]
595
[ 0 3 -1 0 -1]
596
[ 0 -1 2 0 -1]
597
[-1 0 -1 2 0]
598
[ 0 -1 -1 0 2]
599
600
NOTES:
601
602
This is the Laplacian matrix with the row and column indexed by the
603
sink vertex removed.
604
"""
605
return deepcopy(self._reduced_laplacian)
606
607
def group_order(self):
608
r"""
609
The size of the sandpile group.
610
611
INPUT:
612
613
None
614
615
OUTPUT:
616
617
int
618
619
EXAMPLES::
620
621
sage: S = sandlib('generic')
622
sage: S.group_order()
623
15
624
"""
625
return self._group_order
626
627
def _set_max_stable(self):
628
r"""
629
Initialize the variable holding the maximal stable configuration.
630
631
INPUT:
632
633
None
634
635
OUTPUT:
636
637
NONE
638
639
EXAMPLES::
640
641
sage: S = sandlib('generic')
642
sage: S._set_max_stable()
643
"""
644
m = {}
645
for v in self._nonsink_vertices:
646
m[v] = self.out_degree(v)-1
647
self._max_stable = SandpileConfig(self,m)
648
649
def max_stable(self):
650
r"""
651
The maximal stable configuration.
652
653
INPUT:
654
655
None
656
657
OUTPUT:
658
659
SandpileConfig (the maximal stable configuration)
660
661
662
EXAMPLES::
663
664
sage: S = sandlib('generic')
665
sage: S.max_stable()
666
{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}
667
"""
668
return deepcopy(self._max_stable)
669
670
def _set_max_stable_div(self):
671
r"""
672
Initialize the variable holding the maximal stable divisor.
673
674
INPUT:
675
676
None
677
678
OUTPUT:
679
680
NONE
681
682
EXAMPLES::
683
684
sage: S = sandlib('generic')
685
sage: S._set_max_stable_div()
686
"""
687
m = {}
688
for v in self.vertices():
689
m[v] = self.out_degree(v)-1
690
self._max_stable_div = SandpileDivisor(self,m)
691
692
def max_stable_div(self):
693
r"""
694
The maximal stable divisor.
695
696
INPUT:
697
698
SandpileDivisor
699
700
OUTPUT:
701
702
SandpileDivisor (the maximal stable divisor)
703
704
705
EXAMPLES::
706
707
sage: S = sandlib('generic')
708
sage: S.max_stable_div()
709
{0: -1, 1: 2, 2: 2, 3: 1, 4: 1, 5: 1}
710
sage: S.out_degree()
711
{0: 0, 1: 3, 2: 3, 3: 2, 4: 2, 5: 2}
712
"""
713
return deepcopy(self._max_stable_div)
714
715
def _set_out_degrees(self):
716
r"""
717
Initialize the variable holding the out-degrees.
718
719
INPUT:
720
721
None
722
723
OUTPUT:
724
725
NONE
726
727
EXAMPLES::
728
729
sage: S = sandlib('generic')
730
sage: S._set_out_degrees()
731
"""
732
self._out_degrees = dict(self.zero_div())
733
for v in self.vertices():
734
self._out_degrees[v] = 0
735
for e in self.edges_incident(v):
736
self._out_degrees[v] += e[2]
737
738
def out_degree(self, v=None):
739
r"""
740
The out-degree of a vertex or a list of all out-degrees.
741
742
INPUT:
743
744
``v`` (optional) - vertex name
745
746
OUTPUT:
747
748
integer or dict
749
750
EXAMPLES::
751
752
sage: S = sandlib('generic')
753
sage: S.out_degree(2)
754
3
755
sage: S.out_degree()
756
{0: 0, 1: 3, 2: 3, 3: 2, 4: 2, 5: 2}
757
"""
758
if not v is None:
759
return self._out_degrees[v]
760
else:
761
return self._out_degrees
762
763
def _set_in_degrees(self):
764
"""
765
Initialize the variable holding the in-degrees.
766
767
INPUT:
768
769
None
770
771
OUTPUT:
772
773
NONE
774
775
EXAMPLES::
776
777
sage: S = sandlib('generic')
778
sage: S._set_in_degrees()
779
"""
780
self._in_degrees = dict(self.zero_div())
781
for e in self.edges():
782
self._in_degrees[e[1]] += e[2]
783
784
def in_degree(self, v=None):
785
r"""
786
The in-degree of a vertex or a list of all in-degrees.
787
788
INPUT:
789
790
``v`` - vertex name or None
791
792
OUTPUT:
793
794
integer or dict
795
796
EXAMPLES::
797
798
sage: S = sandlib('generic')
799
sage: S.in_degree(2)
800
2
801
sage: S.in_degree()
802
{0: 2, 1: 1, 2: 2, 3: 4, 4: 1, 5: 2}
803
"""
804
if not v is None:
805
return self._in_degrees[v]
806
else:
807
return self._in_degrees
808
809
def _set_burning_config(self):
810
r"""
811
Calculate the minimal burning configuration and its corresponding
812
script.
813
814
EXAMPLES::
815
816
sage: g = {0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1}, \
817
3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}}
818
sage: S = Sandpile(g,0)
819
sage: S._set_burning_config()
820
"""
821
# TODO: Cythonize!
822
d = self._reduced_laplacian.nrows()
823
burn = sum(self._reduced_laplacian)
824
script=[1]*d # d 1s
825
done = False
826
while not done:
827
bad = -1
828
for i in range(d):
829
if burn[i] < 0:
830
bad = i
831
break
832
if bad == -1:
833
done = True
834
else:
835
burn += self._reduced_laplacian[bad]
836
script[bad]+=1
837
b = iter(burn)
838
s = iter(script)
839
bc = {} # burning config
840
bs = {} # burning script
841
for v in self._nonsink_vertices:
842
bc[v] = b.next()
843
bs[v] = s.next()
844
self._burning_config = SandpileConfig(self,bc)
845
self._burning_script = SandpileConfig(self,bs)
846
847
def burning_config(self):
848
r"""
849
A minimal burning configuration.
850
851
INPUT:
852
853
None
854
855
OUTPUT:
856
857
dict (configuration)
858
859
EXAMPLES::
860
861
sage: g = {0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1}, \
862
3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}}
863
sage: S = Sandpile(g,0)
864
sage: S.burning_config()
865
{1: 2, 2: 0, 3: 1, 4: 1, 5: 0}
866
sage: S.burning_config().values()
867
[2, 0, 1, 1, 0]
868
sage: S.burning_script()
869
{1: 1, 2: 3, 3: 5, 4: 1, 5: 4}
870
sage: script = S.burning_script().values()
871
sage: script
872
[1, 3, 5, 1, 4]
873
sage: matrix(script)*S.reduced_laplacian()
874
[2 0 1 1 0]
875
876
NOTES:
877
878
The burning configuration and script are computed using a modified
879
version of Speer's script algorithm. This is a generalization to
880
directed multigraphs of Dhar's burning algorithm.
881
882
A *burning configuration* is a nonnegative integer-linear
883
combination of the rows of the reduced Laplacian matrix having
884
nonnegative entries and such that every vertex has a path from some
885
vertex in its support. The corresponding *burning script* gives
886
the integer-linear combination needed to obtain the burning
887
configuration. So if `b` is the burning configuration, `\sigma` is its
888
script, and `\tilde{L}` is the reduced Laplacian, then `\sigma\cdot
889
\tilde{L} = b`. The *minimal burning configuration* is the one
890
with the minimal script (its components are no larger than the
891
components of any other script
892
for a burning configuration).
893
894
The following are equivalent for a configuration `c` with burning
895
configuration `b` having script `\sigma`:
896
897
- `c` is recurrent;
898
- `c+b` stabilizes to `c`;
899
- the firing vector for the stabilization of `c+b` is `\sigma`.
900
"""
901
return deepcopy(self._burning_config)
902
903
def burning_script(self):
904
r"""
905
A script for the minimal burning configuration.
906
907
INPUT:
908
909
None
910
911
OUTPUT:
912
913
dict
914
915
EXAMPLES::
916
917
sage: g = {0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1},\
918
3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}}
919
sage: S = Sandpile(g,0)
920
sage: S.burning_config()
921
{1: 2, 2: 0, 3: 1, 4: 1, 5: 0}
922
sage: S.burning_config().values()
923
[2, 0, 1, 1, 0]
924
sage: S.burning_script()
925
{1: 1, 2: 3, 3: 5, 4: 1, 5: 4}
926
sage: script = S.burning_script().values()
927
sage: script
928
[1, 3, 5, 1, 4]
929
sage: matrix(script)*S.reduced_laplacian()
930
[2 0 1 1 0]
931
932
NOTES:
933
934
The burning configuration and script are computed using a modified
935
version of Speer's script algorithm. This is a generalization to
936
directed multigraphs of Dhar's burning algorithm.
937
938
A *burning configuration* is a nonnegative integer-linear
939
combination of the rows of the reduced Laplacian matrix having
940
nonnegative entries and such that every vertex has a path from some
941
vertex in its support. The corresponding *burning script* gives the
942
integer-linear combination needed to obtain the burning configuration.
943
So if `b` is the burning configuration, `s` is its script, and
944
`L_{\mathrm{red}}` is the reduced Laplacian, then `s\cdot
945
L_{\mathrm{red}}= b`. The *minimal burning configuration* is the one
946
with the minimal script (its components are no larger than the
947
components of any other script
948
for a burning configuration).
949
950
The following are equivalent for a configuration `c` with burning
951
configuration `b` having script `s`:
952
953
- `c` is recurrent;
954
- `c+b` stabilizes to `c`;
955
- the firing vector for the stabilization of `c+b` is `s`.
956
"""
957
return deepcopy(self._burning_script)
958
959
def nonsink_vertices(self):
960
r"""
961
The names of the nonsink vertices.
962
963
INPUT:
964
965
None
966
967
OUTPUT:
968
969
None
970
971
EXAMPLES::
972
973
sage: S = sandlib('generic')
974
sage: S.nonsink_vertices()
975
[1, 2, 3, 4, 5]
976
"""
977
return self._nonsink_vertices
978
979
def all_k_config(self,k):
980
r"""
981
The configuration with all values set to k.
982
983
INPUT:
984
985
``k`` - integer
986
987
OUTPUT:
988
989
SandpileConfig
990
991
EXAMPLES::
992
993
sage: S = sandlib('generic')
994
sage: S.all_k_config(7)
995
{1: 7, 2: 7, 3: 7, 4: 7, 5: 7}
996
"""
997
return SandpileConfig(self,[k]*(self.num_verts()-1))
998
999
def zero_config(self):
1000
r"""
1001
The all-zero configuration.
1002
1003
INPUT:
1004
1005
None
1006
1007
OUTPUT:
1008
1009
SandpileConfig
1010
1011
EXAMPLES::
1012
1013
sage: S = sandlib('generic')
1014
sage: S.zero_config()
1015
{1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
1016
"""
1017
return self.all_k_config(0)
1018
1019
# TODO: cythonize stabilization!
1020
# The following would presumably be moved to the SandpileConfig class
1021
#def new_stabilize(self, config):
1022
# r"""
1023
# Stabilize \code{config}, returning \code{[out_config, firing_vector]},
1024
# where \code{out_config} is the modified configuration.
1025
# """
1026
# c, f = cython_stabilize(config, self.reduced_laplacian(),
1027
# self.out_degree(), self.nonsink_vertices())
1028
# self._config = c
1029
# return [c, f]
1030
1031
def _set_identity(self):
1032
r"""
1033
Computes ``_identity``, the variable holding the identity configuration
1034
of the sandpile group, when ``identity()`` is first called by a user.
1035
1036
INPUT:
1037
1038
None
1039
1040
OUTPUT:
1041
1042
None
1043
1044
EXAMPLES::
1045
1046
sage: S = sandlib('generic')
1047
sage: S._set_identity()
1048
"""
1049
m = self._max_stable
1050
self._identity = (m&m).dualize()&m
1051
1052
def identity(self):
1053
r"""
1054
The identity configuration.
1055
1056
INPUT:
1057
1058
None
1059
1060
OUTPUT:
1061
1062
dict (the identity configuration)
1063
1064
EXAMPLES::
1065
1066
sage: S = sandlib('generic')
1067
sage: e = S.identity()
1068
sage: x = e & S.max_stable() # stable addition
1069
sage: x
1070
{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}
1071
sage: x == S.max_stable()
1072
True
1073
"""
1074
return deepcopy(self._identity)
1075
1076
def _set_recurrents(self):
1077
"""
1078
Computes ``_recurrents``, the variable holding the list of recurrent
1079
configurations, when ``recurrents()`` is first called by a user.
1080
1081
INPUT:
1082
1083
None
1084
1085
OUTPUT:
1086
1087
None
1088
1089
EXAMPLES::
1090
1091
sage: S = sandlib('generic')
1092
sage: S._set_recurrents()
1093
"""
1094
self._recurrents = []
1095
active = [self._max_stable]
1096
while active != []:
1097
c = active.pop()
1098
self._recurrents.append(c)
1099
for v in self._nonsink_vertices:
1100
cnext = deepcopy(c)
1101
cnext[v] += 1
1102
cnext = ~cnext
1103
if (cnext not in active) and (cnext not in self._recurrents):
1104
active.append(cnext)
1105
1106
def recurrents(self, verbose=True):
1107
r"""
1108
The list of recurrent configurations. If ``verbose`` is
1109
``False``, the configurations are converted to lists of
1110
integers.
1111
1112
INPUT:
1113
1114
``verbose`` (optional) - boolean
1115
1116
OUTPUT:
1117
1118
list (of recurrent configurations)
1119
1120
1121
EXAMPLES::
1122
1123
sage: S = sandlib('generic')
1124
sage: S.recurrents()
1125
[{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 2, 2: 2, 3: 0, 4: 1, 5: 1}, {1: 0, 2: 2, 3: 1, 4: 1, 5: 0}, {1: 0, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 1, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 1, 2: 2, 3: 0, 4: 1, 5: 1}, {1: 2, 2: 2, 3: 1, 4: 0, 5: 1}, {1: 2, 2: 2, 3: 0, 4: 0, 5: 1}, {1: 2, 2: 2, 3: 1, 4: 0, 5: 0}, {1: 1, 2: 2, 3: 1, 4: 1, 5: 0}, {1: 1, 2: 2, 3: 1, 4: 0, 5: 0}, {1: 1, 2: 2, 3: 1, 4: 0, 5: 1}, {1: 0, 2: 2, 3: 0, 4: 1, 5: 1}, {1: 2, 2: 2, 3: 1, 4: 1, 5: 0}, {1: 1, 2: 2, 3: 0, 4: 0, 5: 1}]
1126
sage: S.recurrents(verbose=False)
1127
[[2, 2, 1, 1, 1], [2, 2, 0, 1, 1], [0, 2, 1, 1, 0], [0, 2, 1, 1, 1], [1, 2, 1, 1, 1], [1, 2, 0, 1, 1], [2, 2, 1, 0, 1], [2, 2, 0, 0, 1], [2, 2, 1, 0, 0], [1, 2, 1, 1, 0], [1, 2, 1, 0, 0], [1, 2, 1, 0, 1], [0, 2, 0, 1, 1], [2, 2, 1, 1, 0], [1, 2, 0, 0, 1]]
1128
"""
1129
if verbose:
1130
return deepcopy(self._recurrents)
1131
else:
1132
return [r.values() for r in self._recurrents]
1133
1134
def _set_superstables(self):
1135
r"""
1136
Computes ``_superstables``, the variable holding the list of superstable
1137
configurations, when ``superstables()`` is first called by a user.
1138
1139
INPUT:
1140
1141
None
1142
1143
OUTPUT:
1144
1145
None
1146
1147
EXAMPLES::
1148
1149
sage: S = sandlib('generic')
1150
sage: S._set_superstables()
1151
"""
1152
self._superstables = [c.dualize() for c in self._recurrents]
1153
1154
def superstables(self, verbose=True):
1155
r"""
1156
The list of superstable configurations as dictionaries if
1157
``verbose`` is ``True``, otherwise as lists of integers. The
1158
superstables are also known as `G`-parking functions.
1159
1160
INPUT:
1161
1162
``verbose`` (optional) - boolean
1163
1164
OUTPUT:
1165
1166
list (of superstable elements)
1167
1168
1169
EXAMPLES::
1170
1171
sage: S = sandlib('generic')
1172
sage: S.superstables()
1173
[{1: 0, 2: 0, 3: 0, 4: 0, 5: 0},
1174
{1: 0, 2: 0, 3: 1, 4: 0, 5: 0},
1175
{1: 2, 2: 0, 3: 0, 4: 0, 5: 1},
1176
{1: 2, 2: 0, 3: 0, 4: 0, 5: 0},
1177
{1: 1, 2: 0, 3: 0, 4: 0, 5: 0},
1178
{1: 1, 2: 0, 3: 1, 4: 0, 5: 0},
1179
{1: 0, 2: 0, 3: 0, 4: 1, 5: 0},
1180
{1: 0, 2: 0, 3: 1, 4: 1, 5: 0},
1181
{1: 0, 2: 0, 3: 0, 4: 1, 5: 1},
1182
{1: 1, 2: 0, 3: 0, 4: 0, 5: 1},
1183
{1: 1, 2: 0, 3: 0, 4: 1, 5: 1},
1184
{1: 1, 2: 0, 3: 0, 4: 1, 5: 0},
1185
{1: 2, 2: 0, 3: 1, 4: 0, 5: 0},
1186
{1: 0, 2: 0, 3: 0, 4: 0, 5: 1},
1187
{1: 1, 2: 0, 3: 1, 4: 1, 5: 0}]
1188
sage: S.superstables(False)
1189
[[0, 0, 0, 0, 0],
1190
[0, 0, 1, 0, 0],
1191
[2, 0, 0, 0, 1],
1192
[2, 0, 0, 0, 0],
1193
[1, 0, 0, 0, 0],
1194
[1, 0, 1, 0, 0],
1195
[0, 0, 0, 1, 0],
1196
[0, 0, 1, 1, 0],
1197
[0, 0, 0, 1, 1],
1198
[1, 0, 0, 0, 1],
1199
[1, 0, 0, 1, 1],
1200
[1, 0, 0, 1, 0],
1201
[2, 0, 1, 0, 0],
1202
[0, 0, 0, 0, 1],
1203
[1, 0, 1, 1, 0]]
1204
"""
1205
if verbose:
1206
return deepcopy(self._superstables)
1207
else:
1208
verts = self.nonsink_vertices()
1209
return [s.values() for s in self._superstables]
1210
1211
def is_undirected(self):
1212
r"""
1213
``True`` if ``(u,v)`` is and edge if and only if ``(v,u)`` is an
1214
edges, each edge with the same weight.
1215
1216
INPUT:
1217
1218
None
1219
1220
OUTPUT:
1221
1222
boolean
1223
1224
EXAMPLES::
1225
1226
sage: complete_sandpile(4).is_undirected()
1227
True
1228
sage: sandlib('gor').is_undirected()
1229
False
1230
"""
1231
return self.laplacian().is_symmetric()
1232
1233
def _set_min_recurrents(self):
1234
r"""
1235
Computes the minimal recurrent elements. If the underlying graph is
1236
undirected, these are the recurrent elements of least degree.
1237
1238
INPUT:
1239
1240
None
1241
1242
OUTPUT:
1243
1244
None
1245
1246
EXAMPLES::
1247
1248
sage: complete_sandpile(4)._set_min_recurrents()
1249
"""
1250
if self.is_undirected():
1251
m = min([r.deg() for r in self.recurrents()])
1252
rec = [r for r in self.recurrents() if r.deg()==m]
1253
else:
1254
rec = self.recurrents()
1255
for r in self.recurrents():
1256
if exists(rec, lambda x: r>x)[0]:
1257
rec.remove(r)
1258
self._min_recurrents = rec
1259
1260
def min_recurrents(self, verbose=True):
1261
r"""
1262
The minimal recurrent elements. If the underlying graph is
1263
undirected, these are the recurrent elements of least degree. If
1264
``verbose is ``False``, the configurations are converted to lists of
1265
integers.
1266
1267
INPUT:
1268
1269
``verbose`` (optional) - boolean
1270
1271
OUTPUT:
1272
1273
list of SandpileConfig
1274
1275
EXAMPLES::
1276
1277
sage: S=sandlib('riemann-roch2')
1278
sage: S.min_recurrents()
1279
[{1: 0, 2: 0, 3: 1}, {1: 1, 2: 1, 3: 0}]
1280
sage: S.min_recurrents(False)
1281
[[0, 0, 1], [1, 1, 0]]
1282
sage: S.recurrents(False)
1283
[[1, 1, 2],
1284
[0, 1, 1],
1285
[0, 1, 2],
1286
[1, 0, 1],
1287
[1, 0, 2],
1288
[0, 0, 2],
1289
[1, 1, 1],
1290
[0, 0, 1],
1291
[1, 1, 0]]
1292
sage: [i.deg() for i in S.recurrents()]
1293
[4, 2, 3, 2, 3, 2, 3, 1, 2]
1294
"""
1295
if verbose:
1296
return deepcopy(self._min_recurrents)
1297
else:
1298
return [r.values() for r in self._min_recurrents]
1299
1300
def max_superstables(self, verbose=True):
1301
r"""
1302
The maximal superstable configurations. If the underlying graph is
1303
undirected, these are the superstables of highest degree. If
1304
``verbose`` is ``False``, the configurations are converted to lists of
1305
integers.
1306
1307
INPUT:
1308
1309
``verbose`` (optional) - boolean
1310
1311
OUTPUT:
1312
1313
list (of maximal superstables)
1314
1315
EXAMPLES:
1316
1317
sage: S=sandlib('riemann-roch2')
1318
sage: S.max_superstables()
1319
[{1: 1, 2: 1, 3: 1}, {1: 0, 2: 0, 3: 2}]
1320
sage: S.superstables(False)
1321
[[0, 0, 0],
1322
[1, 0, 1],
1323
[1, 0, 0],
1324
[0, 1, 1],
1325
[0, 1, 0],
1326
[1, 1, 0],
1327
[0, 0, 1],
1328
[1, 1, 1],
1329
[0, 0, 2]]
1330
sage: S.h_vector()
1331
[1, 3, 4, 1]
1332
"""
1333
result = [r.dualize() for r in self.min_recurrents()]
1334
if verbose:
1335
return result
1336
else:
1337
return [r.values() for r in result]
1338
1339
1340
def nonspecial_divisors(self, verbose=True):
1341
r"""
1342
The nonspecial divisors: those divisors of degree ``g-1`` with
1343
empty linear system. The term is only defined for undirected graphs.
1344
Here, ``g = |E| - |V| + 1`` is the genus of the graph. If ``verbose``
1345
is ``False``, the divisors are converted to lists of integers.
1346
1347
INPUT:
1348
1349
``verbose`` (optional) - boolean
1350
1351
OUTPUT:
1352
1353
list (of divisors)
1354
1355
EXAMPLES::
1356
1357
sage: S = complete_sandpile(4)
1358
sage: ns = S.nonspecial_divisors() # optional - 4ti2
1359
sage: D = ns[0] # optional - 4ti2
1360
sage: D.values() # optional - 4ti2
1361
[-1, 1, 0, 2]
1362
sage: D.deg() # optional - 4ti2
1363
2
1364
sage: [i.effective_div() for i in ns] # optional - 4ti2
1365
[[], [], [], [], [], []]
1366
"""
1367
if self.is_undirected():
1368
result = []
1369
for s in self.max_superstables():
1370
D = dict(s)
1371
D[self._sink] = -1
1372
D = SandpileDivisor(self, D)
1373
result.append(D)
1374
if verbose:
1375
return result
1376
else:
1377
return [r.values() for r in result]
1378
else:
1379
raise UserWarning, "The underlying graph must be undirected."
1380
1381
def canonical_divisor(self):
1382
r"""
1383
The canonical divisor: the divisor ``deg(v)-2`` grains of sand
1384
on each vertex. Only for undirected graphs.
1385
1386
INPUT:
1387
1388
None
1389
1390
OUTPUT:
1391
1392
SandpileDivisor
1393
1394
EXAMPLES::
1395
1396
sage: S = complete_sandpile(4)
1397
sage: S.canonical_divisor()
1398
{0: 1, 1: 1, 2: 1, 3: 1}
1399
"""
1400
if self.is_undirected():
1401
return SandpileDivisor(self,[self.out_degree(v)-2 for v in self.vertices()])
1402
else:
1403
raise UserWarning, "Only for undirected graphs."
1404
1405
def _set_invariant_factors(self):
1406
r"""
1407
Computes the variable holding the elementary divisors of the sandpile
1408
group when ``invariant_factors()`` is first called by the user.
1409
1410
INPUT:
1411
1412
None
1413
1414
OUTPUT:
1415
1416
None
1417
1418
EXAMPLES::
1419
1420
sage: S = sandlib('generic')
1421
sage: S._set_invariant_factors()
1422
"""
1423
# Sage seems to have issues with computing the Smith normal form and
1424
# elementary divisors of a sparse matrix, so we have to convert:
1425
A = self.reduced_laplacian().dense_matrix()
1426
self._invariant_factors = A.elementary_divisors()
1427
1428
def invariant_factors(self):
1429
r"""
1430
The invariant factors of the sandpile group (a finite
1431
abelian group).
1432
1433
INPUT:
1434
1435
None
1436
1437
OUTPUT:
1438
1439
list of integers
1440
1441
EXAMPLES::
1442
1443
sage: S = sandlib('generic')
1444
sage: S.invariant_factors()
1445
[1, 1, 1, 1, 15]
1446
"""
1447
return deepcopy(self._invariant_factors)
1448
1449
elementary_divisors = deprecated_function_alias(invariant_factors, 'Sage Sandpile Version 2.2')
1450
1451
def _set_hilbert_function(self):
1452
"""
1453
Computes the variables holding the Hilbert function of the homogeneous
1454
homogeneous sandpile ideal, the first differences of the Hilbert
1455
function, and the postulation number for the zero-set of the sandpile
1456
ideal when any one of these is called by the user.
1457
1458
INPUT:
1459
1460
None
1461
1462
OUTPUT:
1463
1464
None
1465
1466
EXAMPLES::
1467
1468
sage: S = sandlib('generic')
1469
sage: S._set_hilbert_function()
1470
"""
1471
v = [i.deg() for i in self._superstables]
1472
self._postulation = max(v)
1473
self._h_vector = [v.count(i) for i in range(self._postulation+1)]
1474
self._hilbert_function = [1]
1475
for i in range(self._postulation):
1476
self._hilbert_function.append(self._hilbert_function[i]
1477
+self._h_vector[i+1])
1478
1479
def h_vector(self):
1480
r"""
1481
The first differences of the Hilbert function of the homogeneous
1482
sandpile ideal. It lists the number of superstable configurations in
1483
each degree.
1484
1485
INPUT:
1486
1487
None
1488
1489
OUTPUT:
1490
1491
list of nonnegative integers
1492
1493
1494
EXAMPLES::
1495
1496
sage: S = sandlib('generic')
1497
sage: S.hilbert_function()
1498
[1, 5, 11, 15]
1499
sage: S.h_vector()
1500
[1, 4, 6, 4]
1501
"""
1502
return deepcopy(self._h_vector)
1503
1504
def hilbert_function(self):
1505
r"""
1506
The Hilbert function of the homogeneous sandpile ideal.
1507
1508
INPUT:
1509
1510
None
1511
1512
OUTPUT:
1513
1514
list of nonnegative integers
1515
1516
EXAMPLES::
1517
1518
sage: S = sandlib('generic')
1519
sage: S.hilbert_function()
1520
[1, 5, 11, 15]
1521
"""
1522
return deepcopy(self._hilbert_function)
1523
1524
def postulation(self):
1525
r"""
1526
The postulation number of the sandpile ideal. This is the
1527
largest weight of a superstable configuration of the graph.
1528
1529
INPUT:
1530
1531
None
1532
1533
OUTPUT:
1534
1535
nonnegative integer
1536
1537
EXAMPLES::
1538
1539
sage: S = sandlib('generic')
1540
sage: S.postulation()
1541
3
1542
"""
1543
return self._postulation
1544
1545
def reorder_vertices(self):
1546
r"""
1547
Create a copy of the sandpile but with the vertices ordered according
1548
to their distance from the sink, from greatest to least.
1549
1550
INPUT:
1551
1552
None
1553
1554
OUTPUT:
1555
1556
Sandpile
1557
1558
EXAMPLES::
1559
sage: S = sandlib('kite')
1560
sage: S.dict()
1561
{0: {}, 1: {0: 1, 2: 1, 3: 1}, 2: {1: 1, 3: 1, 4: 1}, 3: {1: 1, 2: 1, 4: 1}, 4: {2: 1, 3: 1}}
1562
sage: T = S.reorder_vertices()
1563
sage: T.dict()
1564
{0: {1: 1, 2: 1}, 1: {0: 1, 2: 1, 3: 1}, 2: {0: 1, 1: 1, 3: 1}, 3: {1: 1, 2: 1, 4: 1}, 4: {}}
1565
"""
1566
1567
# first order the vertices according to their distance from the sink
1568
verts = self.vertices()
1569
verts = sorted(verts, self._compare_vertices)
1570
verts.reverse()
1571
perm = {}
1572
for i in range(len(verts)):
1573
perm[verts[i]]=i
1574
old = self.dict()
1575
new = {}
1576
for i in old:
1577
entry = {}
1578
for j in old[i]:
1579
entry[perm[j]]=old[i][j]
1580
new[perm[i]] = entry
1581
return Sandpile(new,len(verts)-1)
1582
1583
1584
#################### Functions for divisors #####################
1585
1586
def all_k_div(self,k):
1587
r"""
1588
The divisor with all values set to k.
1589
1590
INPUT:
1591
1592
``k`` - integer
1593
1594
OUTPUT:
1595
1596
SandpileDivisor
1597
1598
EXAMPLES::
1599
1600
sage: S = sandlib('generic')
1601
sage: S.all_k_div(7)
1602
{0: 7, 1: 7, 2: 7, 3: 7, 4: 7, 5: 7}
1603
"""
1604
return SandpileDivisor(self,[k]*self.num_verts())
1605
1606
def zero_div(self):
1607
r"""
1608
The all-zero divisor.
1609
1610
INPUT:
1611
1612
None
1613
1614
OUTPUT:
1615
1616
SandpileDivisor
1617
1618
EXAMPLES::
1619
1620
sage: S = sandlib('generic')
1621
sage: S.zero_div()
1622
{0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
1623
"""
1624
return self.all_k_div(0)
1625
1626
# FIX: save this in the __dict__
1627
def _set_betti_complexes(self):
1628
r"""
1629
Compute the value return by the ``betti_complexes`` method.
1630
1631
INPUT:
1632
1633
None
1634
1635
OUTPUT:
1636
1637
None
1638
1639
EXAMPLES::
1640
1641
sage: S = Sandpile({0:{},1:{0: 1, 2: 1, 3: 4},2:{3: 5},3:{1: 1, 2: 1}},0)
1642
sage: S._set_betti_complexes() # optional - 4ti2
1643
"""
1644
results = []
1645
verts = self.vertices()
1646
r = self.recurrents()
1647
for D in r:
1648
d = D.deg()
1649
# change D to a dict since SandpileConfig will not allow adding a key
1650
D = dict(D)
1651
D[self.sink()] = -d
1652
D = SandpileDivisor(self,D)
1653
test = True
1654
while test:
1655
D[self.sink()] += 1
1656
complex = D.Dcomplex()
1657
if sum(complex.betti().values()) > 1: # change from 0 to 1
1658
results.append([deepcopy(D), complex])
1659
if len(complex.maximal_faces()) == 1 and list(complex.maximal_faces()[0]) == verts:
1660
test = False
1661
self._betti_complexes = results
1662
1663
def betti_complexes(self):
1664
r"""
1665
A list of all the divisors with nonempty linear systems whose
1666
corresponding simplicial complexes have nonzero homology in some
1667
dimension. Each such divisors is returned with its corresponding
1668
simplicial complex.
1669
1670
INPUT:
1671
1672
None
1673
1674
OUTPUT:
1675
1676
list (of pairs [divisors, corresponding simplicial complex])
1677
1678
1679
EXAMPLES::
1680
1681
sage: S = Sandpile({0:{},1:{0: 1, 2: 1, 3: 4},2:{3: 5},3:{1: 1, 2: 1}},0)
1682
sage: p = S.betti_complexes() # optional - 4ti2
1683
sage: p[0] # optional - 4ti2
1684
[{0: -8, 1: 5, 2: 4, 3: 1},
1685
Simplicial complex with vertex set (0, 1, 2, 3) and facets {(1, 2), (3,)}]
1686
sage: S.resolution() # optional - 4ti2
1687
'R^1 <-- R^5 <-- R^5 <-- R^1'
1688
sage: S.betti()
1689
0 1 2 3
1690
------------------------------
1691
0: 1 - - -
1692
1: - 5 5 -
1693
2: - - - 1
1694
------------------------------
1695
total: 1 5 5 1
1696
sage: len(p) # optional - 4ti2
1697
11
1698
sage: p[0][1].homology() # optional - 4ti2
1699
{0: Z, 1: 0}
1700
sage: p[-1][1].homology() # optional - 4ti2
1701
{0: 0, 1: 0, 2: Z}
1702
"""
1703
return deepcopy(self._betti_complexes)
1704
1705
#######################################
1706
######### Algebraic Geometry ##########
1707
#######################################
1708
1709
def _compare_vertices(self, v, w):
1710
r"""
1711
Compare vertices based on their distance from the sink.
1712
1713
INPUT:
1714
1715
``v``, ``w`` - vertices
1716
1717
OUTPUT:
1718
1719
integer
1720
1721
EXAMPLES::
1722
1723
sage: S = sandlib('generic')
1724
sage: S.vertices()
1725
[0, 1, 2, 3, 4, 5]
1726
sage: S.distance(3,S.sink())
1727
2
1728
sage: S.distance(1,S.sink())
1729
1
1730
sage: S._compare_vertices(1,3)
1731
-1
1732
"""
1733
return self.distance(v, self._sink) - self.distance(w, self._sink)
1734
1735
def _set_ring(self):
1736
r"""
1737
Set up polynomial ring for the sandpile.
1738
1739
INPUT:
1740
1741
None
1742
1743
OUTPUT:
1744
1745
None
1746
1747
EXAMPLES::
1748
1749
sage: S = sandlib('generic')
1750
sage: S._set_ring()
1751
"""
1752
# first order the vertices according to their distance from the sink
1753
verts = self.vertices()
1754
verts = sorted(verts, self._compare_vertices)
1755
verts.reverse()
1756
1757
# variable i refers to the i-th vertex in self.vertices()
1758
names = [self.vertices().index(v) for v in verts]
1759
1760
vars = ''
1761
for i in names:
1762
vars += 'x' + str(i) + ','
1763
vars = vars[:-1]
1764
# create the ring
1765
self._ring = PolynomialRing(QQ, vars)
1766
# create the ideal
1767
gens = []
1768
for i in self.nonsink_vertices():
1769
new_gen = 'x' + str(self.vertices().index(i))
1770
new_gen += '^' + str(self.out_degree(i))
1771
new_gen += '-'
1772
for j in self._dict[i]:
1773
new_gen += 'x' + str(self.vertices().index(j))
1774
new_gen += '^' + str(self._dict[i][j]) + '*'
1775
new_gen = new_gen[:-1]
1776
gens.append(new_gen)
1777
self._unsaturated_ideal = self._ring.ideal(gens)
1778
1779
def _set_ideal(self):
1780
r"""
1781
Create the saturated lattice ideal for the sandpile.
1782
1783
INPUT:
1784
1785
None
1786
1787
OUTPUT:
1788
1789
None
1790
1791
EXAMPLES::
1792
1793
sage: S = sandlib('generic')
1794
sage: S._set_ideal()
1795
"""
1796
R = self.ring()
1797
I = self._unsaturated_ideal._singular_()
1798
self._ideal = R.ideal(I.sat(prod(R.gens())._singular_())[1])
1799
1800
def unsaturated_ideal(self):
1801
r"""
1802
The unsaturated, homogeneous sandpile ideal.
1803
1804
INPUT:
1805
1806
None
1807
1808
OUTPUT:
1809
1810
ideal
1811
1812
EXAMPLES::
1813
1814
sage: S = sandlib('generic')
1815
sage: S.unsaturated_ideal().gens()
1816
[x1^3 - x4*x3*x0, x2^3 - x5*x3*x0, x3^2 - x5*x2, x4^2 - x3*x1, x5^2 - x3*x2]
1817
sage: S.ideal().gens()
1818
[x2 - x0, x3^2 - x5*x0, x5*x3 - x0^2, x4^2 - x3*x1, x5^2 - x3*x0, x1^3 - x4*x3*x0, x4*x1^2 - x5*x0^2]
1819
"""
1820
return self._unsaturated_ideal
1821
1822
def ideal(self, gens=False):
1823
r"""
1824
The saturated, homogeneous sandpile ideal (or its generators if
1825
``gens=True``).
1826
1827
INPUT:
1828
1829
``verbose`` (optional) - boolean
1830
1831
OUTPUT:
1832
1833
ideal or, optionally, the generators of an ideal
1834
1835
EXAMPLES::
1836
1837
sage: S = sandlib('generic')
1838
sage: S.ideal()
1839
Ideal (x2 - x0, x3^2 - x5*x0, x5*x3 - x0^2, x4^2 - x3*x1, x5^2 - x3*x0, x1^3 - x4*x3*x0, x4*x1^2 - x5*x0^2) of Multivariate Polynomial Ring in x5, x4, x3, x2, x1, x0 over Rational Field
1840
sage: S.ideal(True)
1841
[x2 - x0, x3^2 - x5*x0, x5*x3 - x0^2, x4^2 - x3*x1, x5^2 - x3*x0, x1^3 - x4*x3*x0, x4*x1^2 - x5*x0^2]
1842
sage: S.ideal().gens() # another way to get the generators
1843
[x2 - x0, x3^2 - x5*x0, x5*x3 - x0^2, x4^2 - x3*x1, x5^2 - x3*x0, x1^3 - x4*x3*x0, x4*x1^2 - x5*x0^2]
1844
"""
1845
if gens:
1846
return self._ideal.gens()
1847
else:
1848
return self._ideal
1849
1850
def ring(self):
1851
r"""
1852
The ring containing the homogeneous sandpile ideal.
1853
1854
INPUT:
1855
1856
None
1857
1858
OUTPUT:
1859
1860
ring
1861
1862
EXAMPLES::
1863
1864
sage: S = sandlib('generic')
1865
sage: S.ring()
1866
Multivariate Polynomial Ring in x5, x4, x3, x2, x1, x0 over Rational Field
1867
sage: S.ring().gens()
1868
(x5, x4, x3, x2, x1, x0)
1869
1870
NOTES:
1871
1872
The indeterminate `xi` corresponds to the `i`-th vertex as listed my
1873
the method ``vertices``. The term-ordering is degrevlex with
1874
indeterminates ordered according to their distance from the sink (larger
1875
indeterminates are further from the sink).
1876
"""
1877
return self._ring
1878
1879
def _set_resolution(self):
1880
r"""
1881
Compute the free resolution of the homogeneous sandpile ideal.
1882
1883
INPUT:
1884
1885
None
1886
1887
OUTPUT:
1888
1889
None
1890
1891
EXAMPLES::
1892
1893
sage: S = sandlib('generic')
1894
sage: S._set_resolution()
1895
"""
1896
# get the resolution in singular form
1897
res = self.ideal()._singular_().mres(0)
1898
# compute the betti numbers
1899
#self._betti = [1] + [len(res[i])
1900
# for i in range(1,len(res)-2)]
1901
self._betti = [1] + [len(x) for x in res]
1902
# convert the resolution to a list of Sage poly matrices
1903
result = []
1904
zero = self._ring.gens()[0]*0
1905
for i in range(1,len(res)+1):
1906
syz_mat = []
1907
new = [res[i][j] for j in range(1,res[i].size()+1)]
1908
for j in range(self._betti[i]):
1909
row = new[j].transpose().sage_matrix(self._ring)
1910
row = [r for r in row[0]]
1911
if len(row)<self._betti[i-1]:
1912
row += [zero]*(self._betti[i-1]-len(row))
1913
syz_mat.append(row)
1914
syz_mat = matrix(self._ring, syz_mat).transpose()
1915
result.append(syz_mat)
1916
self._resolution = result
1917
self._singular_resolution = res
1918
1919
def resolution(self, verbose=False):
1920
r"""
1921
This function computes a minimal free resolution of the homogeneous
1922
sandpile ideal. If ``verbose`` is ``True``, then all of the mappings
1923
are returned. Otherwise, the resolution is summarized.
1924
1925
INPUT:
1926
1927
``verbose`` (optional) - boolean
1928
1929
OUTPUT:
1930
1931
free resolution of the sandpile ideal
1932
1933
EXAMPLES::
1934
1935
sage: S = sandlib('gor')
1936
sage: S.resolution()
1937
'R^1 <-- R^5 <-- R^5 <-- R^1'
1938
sage: S.resolution(True)
1939
[
1940
[ x1^2 - x3*x0 x3*x1 - x2*x0 x3^2 - x2*x1 x2*x3 - x0^2 x2^2 - x1*x0],
1941
<BLANKLINE>
1942
[ x3 x2 0 x0 0] [ x2^2 - x1*x0]
1943
[-x1 -x3 x2 0 -x0] [-x2*x3 + x0^2]
1944
[ x0 x1 0 x2 0] [-x3^2 + x2*x1]
1945
[ 0 0 -x1 -x3 x2] [x3*x1 - x2*x0]
1946
[ 0 0 x0 x1 -x3], [ x1^2 - x3*x0]
1947
]
1948
sage: r = S.resolution(True)
1949
sage: r[0]*r[1]
1950
[0 0 0 0 0]
1951
sage: r[1]*r[2]
1952
[0]
1953
[0]
1954
[0]
1955
[0]
1956
[0]
1957
"""
1958
if verbose:
1959
return self._resolution
1960
else:
1961
r = ['R^'+str(i) for i in self._betti]
1962
return join(r,' <-- ')
1963
1964
def _set_groebner(self):
1965
r"""
1966
Computes a Groebner basis for the homogeneous sandpile ideal with
1967
respect to the standard sandpile ordering (see ``ring``).
1968
1969
INPUT:
1970
1971
None
1972
1973
OUTPUT:
1974
1975
None
1976
1977
EXAMPLES::
1978
1979
sage: S = sandlib('generic')
1980
sage: S._set_groebner()
1981
"""
1982
self._groebner = self._ideal.groebner_basis()
1983
1984
def groebner(self):
1985
r"""
1986
A Groebner basis for the homogeneous sandpile ideal with
1987
respect to the standard sandpile ordering (see ``ring``).
1988
1989
INPUT:
1990
1991
None
1992
1993
OUTPUT:
1994
1995
Groebner basis
1996
1997
EXAMPLES::
1998
1999
sage: S = sandlib('generic')
2000
sage: S.groebner()
2001
[x4*x1^2 - x5*x0^2, x1^3 - x4*x3*x0, x5^2 - x3*x0, x4^2 - x3*x1, x5*x3 - x0^2, x3^2 - x5*x0, x2 - x0]
2002
"""
2003
return self._groebner
2004
2005
def betti(self, verbose=True):
2006
r"""
2007
Computes the Betti table for the homogeneous sandpile ideal. If
2008
``verbose`` is ``True``, it prints the standard Betti table, otherwise,
2009
it returns a less formated table.
2010
2011
INPUT:
2012
2013
``verbose`` (optional) - boolean
2014
2015
OUTPUT:
2016
2017
Betti numbers for the sandpile
2018
2019
2020
EXAMPLES::
2021
2022
sage: S = sandlib('generic')
2023
sage: S.betti()
2024
0 1 2 3 4 5
2025
------------------------------------------
2026
0: 1 1 - - - -
2027
1: - 4 6 2 - -
2028
2: - 2 7 7 2 -
2029
3: - - 6 16 14 4
2030
------------------------------------------
2031
total: 1 7 19 25 16 4
2032
sage: S.betti(False)
2033
[1, 7, 19, 25, 16, 4]
2034
"""
2035
if verbose:
2036
print singular.eval('print(betti(%s),"betti")'%self._singular_resolution.name())
2037
else:
2038
return self._betti
2039
2040
def solve(self):
2041
r"""
2042
Approximations of the complex affine zeros of the sandpile
2043
ideal.
2044
2045
INPUT:
2046
2047
None
2048
2049
OUTPUT:
2050
2051
list of complex numbers
2052
2053
EXAMPLES::
2054
2055
sage: S = Sandpile({0: {}, 1: {2: 2}, 2: {0: 4, 1: 1}}, 0)
2056
sage: S.solve()
2057
[[-0.707107 + 0.707107*I, 0.707107 - 0.707107*I], [-0.707107 - 0.707107*I, 0.707107 + 0.707107*I], [-I, -I], [I, I], [0.707107 + 0.707107*I, -0.707107 - 0.707107*I], [0.707107 - 0.707107*I, -0.707107 + 0.707107*I], [1, 1], [-1, -1]]
2058
sage: len(_)
2059
8
2060
sage: S.group_order()
2061
8
2062
2063
NOTES:
2064
2065
The solutions form a multiplicative group isomorphic to the sandpile
2066
group. Generators for this group are given exactly by ``points()``.
2067
"""
2068
singular.setring(self._ring._singular_())
2069
v = [singular.var(i) for i in range(1,singular.nvars(self._ring))]
2070
vars = '('
2071
for i in v:
2072
vars += str(i)
2073
vars += ','
2074
vars = vars[:-1] # to get rid of the final ,
2075
vars += ')'
2076
L = singular.subst(self._ideal,
2077
singular.var(singular.nvars(self._ring)),1)
2078
R = singular.ring(0,vars,'lp')
2079
K = singular.fetch(self._ring,L)
2080
K = singular.groebner(K)
2081
singular.LIB('solve.lib')
2082
M = K.solve(5,1)
2083
singular.setring(M)
2084
sol= singular('SOL').sage_structured_str_list()
2085
sol = sol[0][0]
2086
sol = [map(eval,[j.replace('i','I') for j in k]) for k in sol]
2087
return sol
2088
2089
def _set_points(self):
2090
r"""
2091
Generators for the multiplicative group of zeros of the sandpile
2092
ideal.
2093
2094
INPUT:
2095
2096
None
2097
2098
OUTPUT:
2099
2100
None
2101
2102
EXAMPLES::
2103
sage: S = sandlib('generic')
2104
sage: S._set_points()
2105
"""
2106
L = self._reduced_laplacian.transpose().dense_matrix()
2107
n = self.num_verts()-1;
2108
D, U, V = L.smith_form()
2109
self._points = []
2110
one = [1]*n
2111
for k in range(n):
2112
x = [exp(2*pi*I*U[k,t]/D[k,k]) for t in range(n)]
2113
if x not in self._points and x != one:
2114
self._points.append(x)
2115
2116
def points(self):
2117
r"""
2118
Generators for the multiplicative group of zeros of the sandpile
2119
ideal.
2120
2121
INPUT:
2122
2123
None
2124
2125
OUTPUT:
2126
2127
list of complex numbers
2128
2129
EXAMPLES:
2130
2131
The sandpile group in this example is cyclic, and hence there is a
2132
single generator for the group of solutions.
2133
2134
::
2135
2136
sage: S = sandlib('generic')
2137
sage: S.points()
2138
[[e^(4/5*I*pi), 1, e^(2/3*I*pi), e^(-34/15*I*pi), e^(-2/3*I*pi)]]
2139
"""
2140
return self._points
2141
2142
# FIX: use the is_symmetric functions for configurations.
2143
def symmetric_recurrents(self, orbits):
2144
r"""
2145
The list of symmetric recurrent configurations.
2146
2147
INPUT:
2148
2149
``orbits`` - list of lists partitioning the vertices
2150
2151
OUTPUT:
2152
2153
list of recurrent configurations
2154
2155
EXAMPLES::
2156
2157
sage: S = sandlib('kite')
2158
sage: S.dict()
2159
{0: {},
2160
1: {0: 1, 2: 1, 3: 1},
2161
2: {1: 1, 3: 1, 4: 1},
2162
3: {1: 1, 2: 1, 4: 1},
2163
4: {2: 1, 3: 1}}
2164
sage: S.symmetric_recurrents([[1],[2,3],[4]])
2165
[{1: 2, 2: 2, 3: 2, 4: 1}, {1: 2, 2: 2, 3: 2, 4: 0}]
2166
sage: S.recurrents()
2167
[{1: 2, 2: 2, 3: 2, 4: 1},
2168
{1: 2, 2: 2, 3: 2, 4: 0},
2169
{1: 2, 2: 1, 3: 2, 4: 0},
2170
{1: 2, 2: 2, 3: 0, 4: 1},
2171
{1: 2, 2: 0, 3: 2, 4: 1},
2172
{1: 2, 2: 2, 3: 1, 4: 0},
2173
{1: 2, 2: 1, 3: 2, 4: 1},
2174
{1: 2, 2: 2, 3: 1, 4: 1}]
2175
2176
NOTES:
2177
2178
The user is responsible for ensuring that the list of orbits comes from
2179
a group of symmetries of the underlying graph.
2180
"""
2181
sym_recurrents = []
2182
active = [self._max_stable]
2183
while active != []:
2184
c = active.pop()
2185
sym_recurrents.append(c)
2186
for orb in orbits:
2187
cnext = deepcopy(c)
2188
for v in orb:
2189
cnext[v] += 1
2190
cnext = cnext.stabilize()
2191
if (cnext not in active) and (cnext not in sym_recurrents):
2192
active.append(cnext)
2193
return deepcopy(sym_recurrents)
2194
2195
#######################################
2196
########### SandpileConfig Class ##############
2197
#######################################
2198
class SandpileConfig(dict):
2199
r"""
2200
Class for configurations on a sandpile.
2201
"""
2202
2203
def __init__(self, S, c):
2204
r"""
2205
Create a configuration on a Sandpile.
2206
2207
INPUT:
2208
2209
- ``S`` - Sandpile
2210
- ``c`` - dict or list representing a configuration
2211
2212
OUTPUT:
2213
2214
SandpileConfig
2215
2216
EXAMPLES::
2217
2218
sage: S = sandlib('generic')
2219
sage: C = SandpileConfig(S,[1,2,3,3,2])
2220
sage: C.order()
2221
3
2222
sage: ~(C+C+C)==S.identity()
2223
True
2224
"""
2225
if len(c)==S.num_verts()-1:
2226
if type(c)==dict or type(c)==SandpileConfig:
2227
dict.__init__(self,c)
2228
elif type(c)==list:
2229
c.reverse()
2230
config = {}
2231
for v in S.vertices():
2232
if v!=S.sink():
2233
config[v] = c.pop()
2234
dict.__init__(self,config)
2235
else:
2236
raise SyntaxError, c
2237
2238
self._sandpile = S
2239
self._vertices = S.nonsink_vertices()
2240
2241
def __deepcopy__(self, memo):
2242
r"""
2243
Overrides the deepcopy method for dict.
2244
2245
INPUT:
2246
2247
None
2248
2249
OUTPUT:
2250
2251
None
2252
2253
EXAMPLES::
2254
2255
sage: S = sandlib('generic')
2256
sage: c = SandpileConfig(S, [1,2,3,4,5])
2257
sage: d = deepcopy(c)
2258
sage: d[1] += 10
2259
sage: c
2260
{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}
2261
sage: d
2262
{1: 11, 2: 2, 3: 3, 4: 4, 5: 5}
2263
"""
2264
c = SandpileConfig(self._sandpile, dict(self))
2265
c.__dict__.update(self.__dict__)
2266
return c
2267
2268
def __setitem__(self, key, item):
2269
r"""
2270
Overrides the setitem method for dict.
2271
2272
INPUT:
2273
2274
- ``key``, ``item`` - objects
2275
2276
OUTPUT:
2277
2278
None
2279
2280
EXAMPLES::
2281
2282
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2283
sage: c = SandpileConfig(S, [4,1])
2284
sage: c.equivalent_recurrent()
2285
{1: 1, 2: 1}
2286
sage: c.__dict__
2287
{'_sandpile': Digraph on 3 vertices, '_equivalent_recurrent': [{1: 1, 2: 1}, {1: 2, 2: 1}], '_vertices': [1, 2]}
2288
2289
NOTES:
2290
2291
In the example, above, changing the value of ``c`` at some vertex makes
2292
a call to setitem, which resets some of the stored variables for ``c``.
2293
"""
2294
if key in self.keys():
2295
dict.__setitem__(self,key,item)
2296
S = self._sandpile
2297
V = self._vertices
2298
self.__dict__ = {'_sandpile':S, '_vertices': V}
2299
else:
2300
pass
2301
2302
def __getattr__(self, name):
2303
"""
2304
Set certain variables only when called.
2305
2306
INPUT:
2307
2308
``name`` - name of an internal method
2309
2310
OUTPUT:
2311
2312
None.
2313
2314
EXAMPLES::
2315
2316
sage: S = complete_sandpile(4)
2317
sage: C = SandpileConfig(S,[1,1,1])
2318
sage: C.__getattr__('_deg')
2319
3
2320
"""
2321
if not self.__dict__.has_key(name):
2322
if name=='_deg':
2323
self._set_deg()
2324
return self.__dict__[name]
2325
if name=='_stabilize':
2326
self._set_stabilize()
2327
return self.__dict__[name]
2328
if name=='_equivalent_recurrent':
2329
self._set_equivalent_recurrent()
2330
return self.__dict__[name]
2331
if name=='_is_recurrent':
2332
self._set_is_recurrent()
2333
return self.__dict__[name]
2334
if name=='_equivalent_superstable':
2335
self._set_equivalent_superstable()
2336
return self.__dict__[name]
2337
if name=='_is_superstable':
2338
self._set_is_superstable()
2339
return self.__dict__[name]
2340
else:
2341
raise AttributeError, name
2342
2343
def _set_deg(self):
2344
r"""
2345
Compute and store the degree of the configuration.
2346
2347
INPUT:
2348
2349
None
2350
2351
OUTPUT:
2352
2353
integer
2354
2355
EXAMPLES::
2356
2357
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2358
sage: c = SandpileConfig(S, [1,2])
2359
sage: c._set_deg()
2360
"""
2361
self._deg = sum(self.values())
2362
2363
def deg(self):
2364
r"""
2365
The degree of the configuration.
2366
2367
INPUT:
2368
2369
None
2370
2371
OUTPUT:
2372
2373
integer
2374
2375
EXAMPLES::
2376
2377
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2378
sage: c = SandpileConfig(S, [1,2])
2379
sage: c.deg()
2380
3
2381
"""
2382
return self._deg
2383
2384
def __add__(self,other):
2385
r"""
2386
Addition of configurations.
2387
2388
INPUT:
2389
2390
``other`` - SandpileConfig
2391
2392
OUTPUT:
2393
2394
sum of ``self`` and ``other``
2395
2396
EXAMPLES::
2397
2398
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2399
sage: c = SandpileConfig(S, [1,2])
2400
sage: d = SandpileConfig(S, [3,2])
2401
sage: c + d
2402
{1: 4, 2: 4}
2403
"""
2404
result = deepcopy(self)
2405
for v in self:
2406
result[v] += other[v]
2407
return result
2408
2409
def __sub__(self,other):
2410
r"""
2411
Subtraction of configurations.
2412
2413
INPUT:
2414
2415
``other`` - SandpileConfig
2416
2417
OUTPUT:
2418
2419
sum of ``self`` and ``other``
2420
2421
EXAMPLES::
2422
2423
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2424
sage: c = SandpileConfig(S, [1,2])
2425
sage: d = SandpileConfig(S, [3,2])
2426
sage: c - d
2427
{1: -2, 2: 0}
2428
"""
2429
sum = deepcopy(self)
2430
for v in self:
2431
sum[v] -= other[v]
2432
return sum
2433
2434
def __rsub__(self,other):
2435
r"""
2436
Right-side subtraction of configurations.
2437
2438
INPUT:
2439
2440
``other`` - SandpileConfig
2441
2442
OUTPUT:
2443
2444
sum of ``self`` and ``other``
2445
2446
EXAMPLES::
2447
2448
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2449
sage: c = SandpileConfig(S, [1,2])
2450
sage: d = {1: 3, 2: 2}
2451
sage: d - c
2452
{1: 2, 2: 0}
2453
2454
TESTS::
2455
2456
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2457
sage: c = SandpileConfig(S, [1,2])
2458
sage: d = {1: 3, 2: 2}
2459
sage: c.__rsub__(d)
2460
{1: 2, 2: 0}
2461
"""
2462
sum = deepcopy(other)
2463
for v in self:
2464
sum[v] -= self[v]
2465
return sum
2466
2467
def __neg__(self):
2468
r"""
2469
The additive inverse of the configuration.
2470
2471
INPUT:
2472
2473
None
2474
2475
OUTPUT:
2476
2477
SandpileConfig
2478
2479
EXAMPLES::
2480
2481
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2482
sage: c = SandpileConfig(S, [1,2])
2483
sage: -c
2484
{1: -1, 2: -2}
2485
"""
2486
return SandpileConfig(self._sandpile, [-self[v] for v in self._vertices])
2487
2488
# recurrent addition
2489
def __mul__(self, other):
2490
r"""
2491
The recurrent element equivalent to the sum.
2492
2493
INPUT:
2494
2495
``other`` - SandpileConfig
2496
2497
OUTPUT:
2498
2499
SandpileConfig
2500
2501
EXAMPLES::
2502
2503
sage: S = Sandpile(graphs.CycleGraph(4), 0)
2504
sage: c = SandpileConfig(S, [1,0,0])
2505
sage: c + c # ordinary addition
2506
{1: 2, 2: 0, 3: 0}
2507
sage: c & c # add and stabilize
2508
{1: 0, 2: 1, 3: 0}
2509
sage: c*c # add and find equivalent recurrent
2510
{1: 1, 2: 1, 3: 1}
2511
sage: (c*c).is_recurrent()
2512
True
2513
sage: c*(-c) == S.identity()
2514
True
2515
"""
2516
return (self+other).equivalent_recurrent()
2517
2518
def __le__(self, other):
2519
r"""
2520
``True`` if every component of ``self`` is at most that of
2521
``other``.
2522
2523
INPUT:
2524
2525
``other`` - SandpileConfig
2526
2527
OUTPUT:
2528
2529
boolean
2530
2531
EXAMPLES::
2532
2533
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2534
sage: c = SandpileConfig(S, [1,2])
2535
sage: d = SandpileConfig(S, [2,3])
2536
sage: e = SandpileConfig(S, [2,0])
2537
sage: c <= c
2538
True
2539
sage: c <= d
2540
True
2541
sage: d <= c
2542
False
2543
sage: c <= e
2544
False
2545
sage: e <= c
2546
False
2547
"""
2548
return forall(self._vertices, lambda v: self[v]<=other[v])[0]
2549
2550
def __lt__(self,other):
2551
r"""
2552
``True`` if every component of ``self`` is at most that
2553
of ``other`` and the two configurations are not equal.
2554
2555
INPUT:
2556
2557
``other`` - SandpileConfig
2558
2559
OUTPUT:
2560
2561
boolean
2562
2563
EXAMPLES::
2564
2565
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2566
sage: c = SandpileConfig(S, [1,2])
2567
sage: d = SandpileConfig(S, [2,3])
2568
sage: c < c
2569
False
2570
sage: c < d
2571
True
2572
sage: d < c
2573
False
2574
"""
2575
return self<=other and self!=other
2576
2577
def __ge__(self, other):
2578
r"""
2579
``True`` if every component of ``self`` is at least that of
2580
``other``.
2581
2582
INPUT:
2583
2584
``other`` - SandpileConfig
2585
2586
OUTPUT:
2587
2588
boolean
2589
2590
EXAMPLES::
2591
2592
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2593
sage: c = SandpileConfig(S, [1,2])
2594
sage: d = SandpileConfig(S, [2,3])
2595
sage: e = SandpileConfig(S, [2,0])
2596
sage: c >= c
2597
True
2598
sage: d >= c
2599
True
2600
sage: c >= d
2601
False
2602
sage: e >= c
2603
False
2604
sage: c >= e
2605
False
2606
"""
2607
return forall(self._vertices, lambda v: self[v]>=other[v])[0]
2608
2609
def __gt__(self,other):
2610
r"""
2611
``True`` if every component of ``self`` is at least that
2612
of ``other`` and the two configurations are not equal.
2613
2614
INPUT:
2615
2616
``other`` - SandpileConfig
2617
2618
OUTPUT:
2619
2620
boolean
2621
2622
EXAMPLES::
2623
2624
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2625
sage: c = SandpileConfig(S, [1,2])
2626
sage: d = SandpileConfig(S, [1,3])
2627
sage: c > c
2628
False
2629
sage: d > c
2630
True
2631
sage: c > d
2632
False
2633
"""
2634
return self>=other and self!=other
2635
2636
# recurrent power
2637
def __pow__(self, k):
2638
r"""
2639
The recurrent element equivalent to the sum of the
2640
configuration with itself ``k`` times. If ``k`` is negative, do the
2641
same for the negation of the configuration. If ``k`` is zero, return
2642
the identity of the sandpile group.
2643
2644
INPUT:
2645
2646
``k`` - SandpileConfig
2647
2648
OUTPUT:
2649
2650
SandpileConfig
2651
2652
EXAMPLES::
2653
2654
sage: S = Sandpile(graphs.CycleGraph(4), 0)
2655
sage: c = SandpileConfig(S, [1,0,0])
2656
sage: c^3
2657
{1: 1, 2: 1, 3: 0}
2658
sage: (c + c + c) == c^3
2659
False
2660
sage: (c + c + c).equivalent_recurrent() == c^3
2661
True
2662
sage: c^(-1)
2663
{1: 1, 2: 1, 3: 0}
2664
sage: c^0 == S.identity()
2665
True
2666
"""
2667
result = self._sandpile.zero_config()
2668
if k == 0:
2669
return self._sandpile.identity()
2670
else:
2671
if k<0:
2672
k = -k
2673
for i in range(k):
2674
result -= self
2675
else:
2676
for i in range(k):
2677
result += self
2678
return result.equivalent_recurrent()
2679
2680
# stable addition
2681
def __and__(self, other):
2682
r"""
2683
The stabilization of the sum.
2684
2685
INPUT:
2686
2687
``other`` - SandpileConfig
2688
2689
OUTPUT:
2690
2691
SandpileConfig
2692
2693
EXAMPLES::
2694
2695
sage: S = Sandpile(graphs.CycleGraph(4), 0)
2696
sage: c = SandpileConfig(S, [1,0,0])
2697
sage: c + c # ordinary addition
2698
{1: 2, 2: 0, 3: 0}
2699
sage: c & c # add and stabilize
2700
{1: 0, 2: 1, 3: 0}
2701
sage: c*c # add and find equivalent recurrent
2702
{1: 1, 2: 1, 3: 1}
2703
sage: ~(c + c) == c & c
2704
True
2705
"""
2706
return ~(self+other)
2707
2708
def sandpile(self):
2709
r"""
2710
The configuration's underlying sandpile.
2711
2712
INPUT:
2713
2714
None
2715
2716
OUTPUT:
2717
2718
Sandpile
2719
2720
EXAMPLES::
2721
2722
sage: S = sandlib('genus2')
2723
sage: c = S.identity()
2724
sage: c.sandpile()
2725
Digraph on 4 vertices
2726
sage: c.sandpile() == S
2727
True
2728
"""
2729
return self._sandpile
2730
2731
def values(self):
2732
r"""
2733
The values of the configuration as a list, sorted in the order
2734
of the vertices.
2735
2736
INPUT:
2737
2738
None
2739
2740
OUTPUT:
2741
2742
list of integers
2743
2744
boolean
2745
2746
EXAMPLES::
2747
2748
sage: S = Sandpile({'a':[1,'b'], 'b':[1,'a'], 1:['a']},'a')
2749
sage: c = SandpileConfig(S, {'b':1, 1:2})
2750
sage: c
2751
{1: 2, 'b': 1}
2752
sage: c.values()
2753
[2, 1]
2754
sage: S.nonsink_vertices()
2755
[1, 'b']
2756
"""
2757
return [self[v] for v in self._vertices]
2758
2759
def dualize(self):
2760
r"""
2761
The difference between the maximal stable configuration and the
2762
configuration.
2763
2764
INPUT:
2765
2766
None
2767
2768
OUTPUT:
2769
2770
SandpileConfig
2771
2772
EXAMPLES::
2773
2774
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2775
sage: c = SandpileConfig(S, [1,2])
2776
sage: S.max_stable()
2777
{1: 1, 2: 1}
2778
sage: c.dualize()
2779
{1: 0, 2: -1}
2780
sage: S.max_stable() - c == c.dualize()
2781
True
2782
"""
2783
return self._sandpile.max_stable()-self
2784
2785
def fire_vertex(self, v):
2786
r"""
2787
Fire the vertex ``v``.
2788
2789
INPUT:
2790
2791
``v`` - vertex
2792
2793
OUTPUT:
2794
2795
SandpileConfig
2796
2797
EXAMPLES::
2798
2799
sage: S = Sandpile(graphs.CycleGraph(3), 0)
2800
sage: c = SandpileConfig(S, [1,2])
2801
sage: c.fire_vertex(2)
2802
{1: 2, 2: 0}
2803
"""
2804
c = dict(self)
2805
c[v] -= self._sandpile.out_degree(v)
2806
for e in self._sandpile.outgoing_edges(v):
2807
if e[1]!=self._sandpile.sink():
2808
c[e[1]]+=e[2]
2809
return SandpileConfig(self._sandpile,c)
2810
2811
def fire_script(self, sigma):
2812
r"""
2813
Fire the script ``sigma``, i.e., fire each vertex the indicated number
2814
of times.
2815
2816
INPUT:
2817
2818
``sigma`` - SandpileConfig or (list or dict representing a SandpileConfig)
2819
2820
OUTPUT:
2821
2822
SandpileConfig
2823
2824
EXAMPLES::
2825
2826
sage: S = Sandpile(graphs.CycleGraph(4), 0)
2827
sage: c = SandpileConfig(S, [1,2,3])
2828
sage: c.unstable()
2829
[2, 3]
2830
sage: c.fire_script(SandpileConfig(S,[0,1,1]))
2831
{1: 2, 2: 1, 3: 2}
2832
sage: c.fire_script(SandpileConfig(S,[2,0,0])) == c.fire_vertex(1).fire_vertex(1)
2833
True
2834
"""
2835
c = dict(self)
2836
if type(sigma)!=SandpileConfig:
2837
sigma = SandpileConfig(self._sandpile, sigma)
2838
sigma = sigma.values()
2839
for i in range(len(sigma)):
2840
v = self._vertices[i]
2841
c[v] -= sigma[i]*self._sandpile.out_degree(v)
2842
for e in self._sandpile.outgoing_edges(v):
2843
if e[1]!=self._sandpile.sink():
2844
c[e[1]]+=sigma[i]*e[2]
2845
return SandpileConfig(self._sandpile, c)
2846
2847
def unstable(self):
2848
r"""
2849
List of the unstable vertices.
2850
2851
INPUT:
2852
2853
None
2854
2855
OUTPUT:
2856
2857
list of vertices
2858
2859
EXAMPLES::
2860
2861
sage: S = Sandpile(graphs.CycleGraph(4), 0)
2862
sage: c = SandpileConfig(S, [1,2,3])
2863
sage: c.unstable()
2864
[2, 3]
2865
"""
2866
return [v for v in self._vertices if
2867
self[v]>=self._sandpile.out_degree(v)]
2868
2869
def fire_unstable(self):
2870
r"""
2871
Fire all unstable vertices.
2872
2873
INPUT:
2874
2875
None
2876
2877
OUTPUT:
2878
2879
SandpileConfig
2880
2881
EXAMPLES::
2882
2883
sage: S = Sandpile(graphs.CycleGraph(4), 0)
2884
sage: c = SandpileConfig(S, [1,2,3])
2885
sage: c.fire_unstable()
2886
{1: 2, 2: 1, 3: 2}
2887
"""
2888
c = dict(self)
2889
for v in self.unstable():
2890
c[v] -= self._sandpile.out_degree(v)
2891
for e in self._sandpile.outgoing_edges(v):
2892
if e[1]!=self._sandpile.sink():
2893
c[e[1]]+=e[2]
2894
return SandpileConfig(self._sandpile,c)
2895
2896
# FIX: make this faster by not converting to SandpileConfig?
2897
def _set_stabilize(self):
2898
r"""
2899
Computes the stabilized configuration and its firing vector.
2900
2901
INPUT:
2902
2903
None
2904
2905
OUTPUT:
2906
2907
None
2908
2909
EXAMPLES::
2910
2911
sage: S = sandlib('generic')
2912
sage: c = S.max_stable() + S.identity()
2913
sage: c._set_stabilize()
2914
"""
2915
c = deepcopy(self)
2916
firing_vector = self._sandpile.zero_config()
2917
unstable = c.unstable()
2918
while unstable:
2919
for v in unstable:
2920
firing_vector[v] += 1
2921
c = c.fire_unstable()
2922
unstable = c.unstable()
2923
self._stabilize = [c, firing_vector]
2924
2925
def stabilize(self, with_firing_vector=False):
2926
r"""
2927
The stabilized configuration. Optionally returns the
2928
corresponding firing vector.
2929
2930
INPUT: s
2931
2932
``with_firing_vector`` (optional) - boolean
2933
2934
OUTPUT:
2935
2936
``SandpileConfig`` or ``[SandpileConfig, firing_vector]``
2937
2938
EXAMPLES::
2939
2940
sage: S = sandlib('generic')
2941
sage: c = S.max_stable() + S.identity()
2942
sage: c.stabilize(True)
2943
[{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}, {1: 1, 2: 5, 3: 7, 4: 1, 5: 6}]
2944
sage: S.max_stable() & S.identity()
2945
{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}
2946
sage: S.max_stable() & S.identity() == c.stabilize()
2947
True
2948
sage: ~c
2949
{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}
2950
"""
2951
if with_firing_vector:
2952
return self._stabilize
2953
else:
2954
return self._stabilize[0]
2955
2956
def __invert__(self):
2957
r"""
2958
The stabilized configuration.
2959
2960
INPUT:
2961
2962
None
2963
2964
OUTPUT:
2965
2966
``SandpileConfig``
2967
2968
Returns the stabilized configuration.
2969
EXAMPLES::
2970
2971
sage: S = sandlib('generic')
2972
sage: c = S.max_stable() + S.identity()
2973
sage: ~c
2974
{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}
2975
sage: ~c == c.stabilize()
2976
True
2977
"""
2978
return self._stabilize[0]
2979
2980
def support(self):
2981
r"""
2982
The input is a dictionary of integers. The output is a list of keys
2983
of nonzero values of the dictionary.
2984
2985
INPUT:
2986
2987
None
2988
2989
OUTPUT:
2990
2991
list - support of the config
2992
2993
EXAMPLES::
2994
2995
sage: S = sandlib('generic')
2996
sage: c = S.identity()
2997
sage: c.values()
2998
[2, 2, 1, 1, 0]
2999
sage: c.support()
3000
[1, 2, 3, 4]
3001
sage: S.vertices()
3002
[0, 1, 2, 3, 4, 5]
3003
"""
3004
return [i for i in self.keys() if self[i] !=0]
3005
3006
3007
def add_random(self):
3008
r"""
3009
Add one grain of sand to a random nonsink vertex.
3010
3011
INPUT:
3012
3013
None
3014
3015
OUTPUT:
3016
3017
SandpileConfig
3018
3019
EXAMPLES:
3020
3021
We compute the 'sizes' of the avalanches caused by adding random grains
3022
of sand to the maximal stable configuration on a grid graph. The
3023
function ``stabilize()`` returns the firing vector of the
3024
stabilization, a dictionary whose values say how many times each vertex
3025
fires in the stabilization.
3026
3027
::
3028
3029
sage: S = grid_sandpile(10,10)
3030
sage: m = S.max_stable()
3031
sage: a = []
3032
sage: for i in range(1000):
3033
... m = m.add_random()
3034
... m, f = m.stabilize(True)
3035
... a.append(sum(f.values()))
3036
...
3037
sage: p = list_plot([[log(i+1),log(a.count(i))] for i in [0..max(a)] if a.count(i)])
3038
sage: p.axes_labels(['log(N)','log(D(N))'])
3039
sage: t = text("Distribution of avalanche sizes", (2,2), rgbcolor=(1,0,0))
3040
sage: show(p+t,axes_labels=['log(N)','log(D(N))'])
3041
"""
3042
config = dict(self)
3043
C = CombinatorialClass()
3044
C.list = lambda: self.keys()
3045
config[C.random_element()] += 1
3046
return SandpileConfig(self._sandpile,config)
3047
3048
def order(self):
3049
r"""
3050
The order of the recurrent element equivalent to ``config``.
3051
3052
INPUT:
3053
3054
``config`` - configuration
3055
3056
OUTPUT:
3057
3058
integer
3059
3060
EXAMPLES::
3061
3062
sage: S = sandlib('generic')
3063
sage: [r.order() for r in S.recurrents()]
3064
[3, 3, 5, 15, 15, 15, 5, 15, 15, 5, 15, 5, 15, 1, 15]
3065
"""
3066
v = vector(self.values())
3067
w = v*self._sandpile.reduced_laplacian().dense_matrix()**(-1)
3068
return lcm([denominator(i) for i in w])
3069
3070
def is_stable(self):
3071
r"""
3072
``True`` if stable.
3073
3074
INPUT:
3075
3076
None
3077
3078
OUTPUT:
3079
3080
boolean
3081
3082
EXAMPLES::
3083
3084
sage: S = sandlib('generic')
3085
sage: S.max_stable().is_stable()
3086
True
3087
sage: (S.max_stable() + S.max_stable()).is_stable()
3088
False
3089
sage: (S.max_stable() & S.max_stable()).is_stable()
3090
True
3091
"""
3092
for v in self._vertices:
3093
if self[v] >= self._sandpile.out_degree(v):
3094
return False
3095
return True
3096
3097
def _set_equivalent_recurrent(self):
3098
r"""
3099
Sets the equivalent recurrent configuration and the corresponding
3100
firing vector.
3101
3102
INPUT:
3103
3104
None
3105
3106
OUTPUT:
3107
3108
None
3109
3110
EXAMPLES::
3111
3112
sage: S = sandlib('generic')
3113
sage: a = -S.max_stable()
3114
sage: a._set_equivalent_recurrent()
3115
"""
3116
old = self
3117
# revise next line with c._sandpile.zero_config()
3118
firing_vector = self._sandpile.zero_config()
3119
done = False
3120
bs = self._sandpile.burning_script()
3121
bc = self._sandpile.burning_config()
3122
while not done:
3123
firing_vector = firing_vector - bs
3124
new, new_fire = (old + bc).stabilize(True)
3125
firing_vector = firing_vector + new_fire
3126
if new == old:
3127
done = True
3128
else:
3129
old = new
3130
self._equivalent_recurrent = [new, firing_vector]
3131
3132
def equivalent_recurrent(self, with_firing_vector=False):
3133
r"""
3134
The recurrent configuration equivalent to the given
3135
configuration and optionally returns the corresponding firing vector.
3136
3137
INPUT:
3138
3139
``with_firing_vector`` (optional) - boolean
3140
3141
OUTPUT:
3142
3143
``SandpileConfig`` or ``[SandpileConfig, firing_vector]``
3144
3145
3146
EXAMPLES::
3147
3148
sage: S = sandlib('generic')
3149
sage: c = SandpileConfig(S, [0,0,0,0,0])
3150
sage: c.equivalent_recurrent() == S.identity()
3151
True
3152
sage: x = c.equivalent_recurrent(True)
3153
sage: r = vector([x[0][v] for v in S.nonsink_vertices()])
3154
sage: f = vector([x[1][v] for v in S.nonsink_vertices()])
3155
sage: cv = vector(c.values())
3156
sage: r == cv - f*S.reduced_laplacian()
3157
True
3158
3159
NOTES:
3160
3161
Let `L` be the reduced laplacian, `c` the initial configuration, `r` the
3162
returned configuration, and `f` the firing vector. Then `r = c - f\cdot
3163
L`.
3164
"""
3165
if with_firing_vector:
3166
return self._equivalent_recurrent
3167
else:
3168
return self._equivalent_recurrent[0]
3169
3170
def _set_is_recurrent(self):
3171
r"""
3172
Computes and stores whether the configuration is recurrent.
3173
3174
INPUT:
3175
3176
None
3177
3178
OUTPUT:
3179
3180
None
3181
3182
EXAMPLES::
3183
3184
sage: S = sandlib('generic')
3185
sage: S.max_stable()._set_is_recurrent()
3186
"""
3187
if '_recurrents' in self._sandpile.__dict__:
3188
self._is_recurrent = (self in self._sandpile._recurrents)
3189
elif self.__dict__.has_key('_equivalent_recurrent'):
3190
self._is_recurrent = (self._equivalent_recurrent == self)
3191
else:
3192
# add the burning configuration to config
3193
# change once sandpile._burning_config is a SandpileConfig
3194
b = SandpileConfig(self._sandpile,self._sandpile._burning_config)
3195
c = ~(self + b)
3196
self._is_recurrent = (c == self)
3197
3198
def is_recurrent(self):
3199
r"""
3200
``True`` if the configuration is recurrent.
3201
3202
INPUT:
3203
3204
None
3205
3206
OUTPUT:
3207
3208
boolean
3209
3210
EXAMPLES::
3211
3212
sage: S = sandlib('generic')
3213
sage: S.identity().is_recurrent()
3214
True
3215
sage: S.zero_config().is_recurrent()
3216
False
3217
"""
3218
return self._is_recurrent
3219
3220
def _set_equivalent_superstable(self):
3221
r"""
3222
Sets the superstable configuration equivalent to the given
3223
configuration and its corresponding firing vector.
3224
3225
INPUT:
3226
3227
None
3228
3229
OUTPUT:
3230
3231
``[configuration, firing_vector]``
3232
3233
3234
EXAMPLES::
3235
3236
sage: S = sandlib('generic')
3237
sage: m = S.max_stable()
3238
sage: m._set_equivalent_superstable()
3239
"""
3240
r, fv = self.dualize().equivalent_recurrent(with_firing_vector=True)
3241
self._equivalent_superstable = [r.dualize(), -fv]
3242
3243
def equivalent_superstable(self, with_firing_vector=False):
3244
r"""
3245
The equivalent superstable configuration. Optionally
3246
returns the corresponding firing vector.
3247
3248
INPUT:
3249
3250
``with_firing_vector`` (optional) - boolean
3251
3252
OUTPUT:
3253
3254
``SandpileConfig`` or ``[SandpileConfig, firing_vector]``
3255
3256
3257
EXAMPLES::
3258
3259
sage: S = sandlib('generic')
3260
sage: m = S.max_stable()
3261
sage: m.equivalent_superstable().is_superstable()
3262
True
3263
sage: x = m.equivalent_superstable(True)
3264
sage: s = vector(x[0].values())
3265
sage: f = vector(x[1].values())
3266
sage: mv = vector(m.values())
3267
sage: s == mv - f*S.reduced_laplacian()
3268
True
3269
3270
NOTES:
3271
3272
Let `L` be the reduced laplacian, `c` the initial configuration, `s` the
3273
returned configuration, and `f` the firing vector. Then `s = c - f\cdot
3274
L`.
3275
"""
3276
if with_firing_vector:
3277
return self._equivalent_superstable
3278
else:
3279
return self._equivalent_superstable[0]
3280
3281
def _set_is_superstable(self):
3282
r"""
3283
Computes and stores whether ``config`` is superstable.
3284
3285
INPUT:
3286
3287
None
3288
3289
OUTPUT:
3290
3291
boolean
3292
3293
EXAMPLES::
3294
3295
sage: S = sandlib('generic')
3296
sage: S.zero_config()._set_is_superstable()
3297
"""
3298
if '_superstables' in self._sandpile.__dict__:
3299
self._is_superstable = (self in self._sandpile._superstables)
3300
elif self.__dict__.has_key('_equivalent_superstable'):
3301
self._is_superstable = (self._equivalent_superstable[0] == self)
3302
else:
3303
self._is_superstable = self.dualize().is_recurrent()
3304
3305
def is_superstable(self):
3306
r"""
3307
``True`` if ``config`` is superstable, i.e., whether its dual is
3308
recurrent.
3309
3310
INPUT:
3311
3312
None
3313
3314
OUTPUT:
3315
3316
boolean
3317
3318
EXAMPLES::
3319
3320
sage: S = sandlib('generic')
3321
sage: S.zero_config().is_superstable()
3322
True
3323
"""
3324
return self._is_superstable
3325
3326
def is_symmetric(self, orbits):
3327
r"""
3328
This function checks if the values of the configuration are constant
3329
over the vertices in each sublist of ``orbits``.
3330
3331
INPUT:
3332
3333
``orbits`` - list of lists of vertices
3334
3335
OUTPUT:
3336
3337
boolean
3338
3339
EXAMPLES::
3340
3341
sage: S = sandlib('kite')
3342
sage: S.dict()
3343
{0: {},
3344
1: {0: 1, 2: 1, 3: 1},
3345
2: {1: 1, 3: 1, 4: 1},
3346
3: {1: 1, 2: 1, 4: 1},
3347
4: {2: 1, 3: 1}}
3348
sage: c = SandpileConfig(S, [1, 2, 2, 3])
3349
sage: c.is_symmetric([[2,3]])
3350
True
3351
"""
3352
for x in orbits:
3353
if len(set([self[v] for v in x])) > 1:
3354
return False
3355
return True
3356
3357
def show(self,sink=True,colors=True,heights=False,directed=None,**kwds):
3358
r"""
3359
Show the configuration.
3360
3361
INPUT:
3362
3363
- ``sink`` - whether to show the sink
3364
- ``colors`` - whether to color-code the amount of sand on each vertex
3365
- ``heights`` - whether to label each vertex with the amount of sand
3366
- ``kwds`` - arguments passed to the show method for Graph
3367
- ``directed`` - whether to draw directed edges
3368
3369
OUTPUT:
3370
3371
None
3372
3373
EXAMPLES::
3374
3375
sage: S=sandlib('genus2')
3376
sage: c=S.identity()
3377
sage: S=sandlib('genus2')
3378
sage: c=S.identity()
3379
sage: c.show()
3380
sage: c.show(directed=False)
3381
sage: c.show(sink=False,colors=False,heights=True)
3382
"""
3383
if directed==True:
3384
T = DiGraph(self.sandpile())
3385
elif directed==False:
3386
T = Graph(self.sandpile())
3387
elif self.sandpile().is_directed():
3388
T = DiGraph(self.sandpile())
3389
else:
3390
T = Graph(self.sandpile())
3391
3392
max_height = max(self.sandpile().out_degree_sequence())
3393
if not sink:
3394
T.delete_vertex(self.sandpile().sink())
3395
if heights:
3396
a = {}
3397
for i in T.vertices():
3398
if i==self.sandpile().sink():
3399
a[i] = str(i)
3400
else:
3401
a[i] = str(i)+":"+str(self[i])
3402
T.relabel(a)
3403
if colors:
3404
vc = {} # vertex colors
3405
r = rainbow(max_height) # colors
3406
for i in range(max_height):
3407
vc[r[i]] = []
3408
for i in self.sandpile().nonsink_vertices():
3409
if heights:
3410
vc[r[self[i]]].append(a[i])
3411
else:
3412
vc[r[self[i]]].append(i)
3413
T.show(vertex_colors=vc,**kwds)
3414
else:
3415
T.show(**kwds)
3416
3417
#######################################
3418
########### SandpileDivisor Class #############
3419
#######################################
3420
3421
class SandpileDivisor(dict):
3422
r"""
3423
Class for divisors on a sandpile.
3424
"""
3425
3426
def __init__(self, S, D):
3427
r"""
3428
Create a divisor on a Sandpile.
3429
3430
INPUT:
3431
3432
- ``S`` - Sandpile
3433
- ``D`` - dict or list representing a divisor
3434
3435
OUTPUT:
3436
3437
SandpileDivisor
3438
3439
EXAMPLES::
3440
3441
sage: S = sandlib('generic')
3442
sage: D = SandpileDivisor(S,[0,1,0,1,1,3])
3443
sage: D.support()
3444
[1, 3, 4, 5]
3445
3446
"""
3447
if len(D)==S.num_verts():
3448
if type(D) in [dict, SandpileDivisor, SandpileConfig]:
3449
dict.__init__(self,dict(D))
3450
elif type(D)==list:
3451
div = {}
3452
for i in range(S.num_verts()):
3453
div[S.vertices()[i]] = D[i]
3454
dict.__init__(self,div)
3455
else:
3456
raise SyntaxError, D
3457
3458
self._sandpile = S
3459
self._vertices = S.vertices()
3460
3461
def __deepcopy__(self, memo):
3462
r"""
3463
Overrides the deepcopy method for dict.
3464
3465
INPUT:
3466
3467
None
3468
3469
OUTPUT:
3470
3471
None
3472
3473
EXAMPLES::
3474
3475
sage: S = sandlib('generic')
3476
sage: D = SandpileDivisor(S, [1,2,3,4,5,6])
3477
sage: E = deepcopy(D)
3478
sage: E[0] += 10
3479
sage: D
3480
{0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6}
3481
sage: E
3482
{0: 11, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6}
3483
"""
3484
D = SandpileDivisor(self._sandpile, dict(self))
3485
D.__dict__.update(self.__dict__)
3486
return D
3487
3488
def __setitem__(self, key, item):
3489
r"""
3490
Overrides the setitem method for dict.
3491
3492
INPUT:
3493
3494
- ``key``, ``item`` - objects
3495
3496
OUTPUT:
3497
3498
None
3499
3500
EXAMPLES::
3501
3502
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3503
sage: D = SandpileDivisor(S, [0,1,1])
3504
sage: eff = D.effective_div() # optional - 4ti2
3505
sage: D.__dict__ # optional - 4ti2
3506
{'_sandpile': Digraph on 3 vertices, '_effective_div': [{0: 2, 1: 0, 2: 0}, {0: 0, 1: 1, 2: 1}], '_linear_system': {'inhomog': [[1, 0, 0], [0, -1, -1], [0, 0, 0]], 'num_inhomog': 3, 'num_homog': 2, 'homog': [[-1, -1, -1], [1, 1, 1]]}, '_vertices': [0, 1, 2]}
3507
sage: D[0] += 1 # optional - 4ti2
3508
sage: D.__dict__ # optional - 4ti2
3509
{'_sandpile': Digraph on 3 vertices, '_vertices': [0, 1, 2]}
3510
3511
NOTES:
3512
3513
In the example, above, changing the value of ``D`` at some vertex makes
3514
a call to setitem, which resets some of the stored variables for ``D``.
3515
"""
3516
if key in self.keys():
3517
dict.__setitem__(self,key,item)
3518
S = self._sandpile
3519
V = self._vertices
3520
self.__dict__ = {'_sandpile':S, '_vertices': V}
3521
else:
3522
pass
3523
3524
def __getattr__(self, name):
3525
"""
3526
Set certain variables only when called.
3527
3528
INPUT:
3529
3530
``name`` - name of an internal method
3531
3532
OUTPUT:
3533
3534
None.
3535
3536
EXAMPLES::
3537
3538
sage: S = sandlib('generic')
3539
sage: D = SandpileDivisor(S,[0,1,0,1,1,3])
3540
sage: D.__getattr__('_deg')
3541
6
3542
"""
3543
if not self.__dict__.has_key(name):
3544
if name=='_deg':
3545
self._set_deg()
3546
return self.__dict__[name]
3547
if name=='_linear_system':
3548
self._set_linear_system()
3549
return self.__dict__[name]
3550
if name=='_effective_div':
3551
self._set_effective_div()
3552
return self.__dict__[name]
3553
if name=='_r_of_D':
3554
self._set_r_of_D()
3555
return self.__dict__[name]
3556
if name=='_Dcomplex':
3557
self._set_Dcomplex()
3558
return self.__dict__[name]
3559
if name=='_life':
3560
self._set_life()
3561
return self.__dict__[name]
3562
else:
3563
raise AttributeError, name
3564
3565
def _set_deg(self):
3566
r"""
3567
Compute and store the degree of the divisor.
3568
3569
INPUT:
3570
3571
None
3572
3573
OUTPUT:
3574
3575
integer
3576
3577
EXAMPLES::
3578
3579
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3580
sage: D = SandpileDivisor(S, [1,2,3])
3581
sage: D._set_deg()
3582
"""
3583
self._deg = sum(self.values())
3584
3585
def deg(self):
3586
r"""
3587
The degree of the divisor.
3588
3589
INPUT:
3590
3591
None
3592
3593
OUTPUT:
3594
3595
integer
3596
3597
EXAMPLES::
3598
3599
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3600
sage: D = SandpileDivisor(S, [1,2,3])
3601
sage: D.deg()
3602
6
3603
"""
3604
return self._deg
3605
3606
def __add__(self, other):
3607
r"""
3608
Addition of divisors.
3609
3610
INPUT:
3611
3612
``other`` - SandpileDivisor
3613
3614
OUTPUT:
3615
3616
sum of ``self`` and ``other``
3617
3618
EXAMPLES::
3619
3620
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3621
sage: D = SandpileDivisor(S, [1,2,3])
3622
sage: E = SandpileDivisor(S, [3,2,1])
3623
sage: D + E
3624
{0: 4, 1: 4, 2: 4}
3625
"""
3626
sum = deepcopy(self)
3627
for v in self:
3628
sum[v] += other[v]
3629
return sum
3630
3631
def __radd__(self,other):
3632
r"""
3633
Right-side addition of divisors.
3634
3635
INPUT:
3636
3637
``other`` - SandpileDivisor
3638
3639
OUTPUT:
3640
3641
sum of ``self`` and ``other``
3642
3643
EXAMPLES::
3644
3645
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3646
sage: D = SandpileDivisor(S, [1,2,3])
3647
sage: E = SandpileDivisor(S, [3,2,1])
3648
sage: D.__radd__(E)
3649
{0: 4, 1: 4, 2: 4}
3650
"""
3651
sum = deepcopy(other)
3652
for v in self:
3653
sum[v] += self[v]
3654
return sum
3655
3656
def __sub__(self, other):
3657
r"""
3658
Subtraction of divisors.
3659
3660
INPUT:
3661
3662
``other`` - SandpileDivisor
3663
3664
OUTPUT:
3665
3666
Difference of ``self`` and ``other``
3667
3668
EXAMPLES::
3669
3670
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3671
sage: D = SandpileDivisor(S, [1,2,3])
3672
sage: E = SandpileDivisor(S, [3,2,1])
3673
sage: D - E
3674
{0: -2, 1: 0, 2: 2}
3675
"""
3676
sum = deepcopy(self)
3677
for v in self:
3678
sum[v] -= other[v]
3679
return sum
3680
3681
def __rsub__(self, other):
3682
r"""
3683
Right-side subtraction of divisors.
3684
INPUT:
3685
3686
``other`` - SandpileDivisor
3687
3688
OUTPUT:
3689
3690
Difference of ``self`` and ``other``
3691
3692
EXAMPLES::
3693
3694
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3695
sage: D = SandpileDivisor(S, [1,2,3])
3696
sage: E = {0: 3, 1: 2, 2: 1}
3697
sage: D.__rsub__(E)
3698
{0: 2, 1: 0, 2: -2}
3699
sage: E - D
3700
{0: 2, 1: 0, 2: -2}
3701
"""
3702
sum = deepcopy(other)
3703
for v in self:
3704
sum[v] -= self[v]
3705
return sum
3706
3707
def __neg__(self):
3708
r"""
3709
The additive inverse of the divisor.
3710
3711
INPUT:
3712
3713
None
3714
3715
OUTPUT:
3716
3717
SandpileDivisor
3718
3719
EXAMPLES::
3720
3721
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3722
sage: D = SandpileDivisor(S, [1,2,3])
3723
sage: -D
3724
{0: -1, 1: -2, 2: -3}
3725
"""
3726
return SandpileDivisor(self._sandpile, [-self[v] for v in self._vertices])
3727
3728
def __le__(self, other):
3729
r"""
3730
``True`` if every component of ``self`` is at most that of
3731
``other``.
3732
3733
INPUT:
3734
3735
``other`` - SandpileDivisor
3736
3737
OUTPUT:
3738
3739
boolean
3740
3741
EXAMPLES::
3742
3743
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3744
sage: D = SandpileDivisor(S, [1,2,3])
3745
sage: E = SandpileDivisor(S, [2,3,4])
3746
sage: F = SandpileDivisor(S, [2,0,4])
3747
sage: D <= D
3748
True
3749
sage: D <= E
3750
True
3751
sage: E <= D
3752
False
3753
sage: D <= F
3754
False
3755
sage: F <= D
3756
False
3757
"""
3758
return forall(self._vertices, lambda v: self[v]<=other[v])[0]
3759
3760
def __lt__(self,other):
3761
r"""
3762
``True`` if every component of ``self`` is at most that
3763
of ``other`` and the two divisors are not equal.
3764
3765
INPUT:
3766
3767
``other`` - SandpileDivisor
3768
3769
OUTPUT:
3770
3771
boolean
3772
3773
EXAMPLES::
3774
3775
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3776
sage: D = SandpileDivisor(S, [1,2,3])
3777
sage: E = SandpileDivisor(S, [2,3,4])
3778
sage: D < D
3779
False
3780
sage: D < E
3781
True
3782
sage: E < D
3783
False
3784
"""
3785
return self<=other and self!=other
3786
3787
def __ge__(self, other):
3788
r"""
3789
``True`` if every component of ``self`` is at least that of
3790
``other``.
3791
3792
INPUT:
3793
3794
``other`` - SandpileDivisor
3795
3796
OUTPUT:
3797
3798
boolean
3799
3800
EXAMPLES::
3801
3802
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3803
sage: D = SandpileDivisor(S, [1,2,3])
3804
sage: E = SandpileDivisor(S, [2,3,4])
3805
sage: F = SandpileDivisor(S, [2,0,4])
3806
sage: D >= D
3807
True
3808
sage: E >= D
3809
True
3810
sage: D >= E
3811
False
3812
sage: F >= D
3813
False
3814
sage: D >= F
3815
False
3816
"""
3817
return forall(self._vertices, lambda v: self[v]>=other[v])[0]
3818
3819
def __gt__(self,other):
3820
r"""
3821
``True`` if every component of ``self`` is at least that
3822
of ``other`` and the two divisors are not equal.
3823
3824
INPUT:
3825
3826
``other`` - SandpileDivisor
3827
3828
OUTPUT:
3829
3830
boolean
3831
3832
EXAMPLES::
3833
3834
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3835
sage: D = SandpileDivisor(S, [1,2,3])
3836
sage: E = SandpileDivisor(S, [1,3,4])
3837
sage: D > D
3838
False
3839
sage: E > D
3840
True
3841
sage: D > E
3842
False
3843
"""
3844
return self>=other and self!=other
3845
3846
def sandpile(self):
3847
r"""
3848
The divisor's underlying sandpile.
3849
3850
INPUT:
3851
3852
None
3853
3854
OUTPUT:
3855
3856
Sandpile
3857
3858
EXAMPLES::
3859
3860
sage: S = sandlib('genus2')
3861
sage: D = SandpileDivisor(S,[1,-2,0,3])
3862
sage: D.sandpile()
3863
Digraph on 4 vertices
3864
sage: D.sandpile() == S
3865
True
3866
"""
3867
return self._sandpile
3868
3869
def values(self):
3870
r"""
3871
The values of the divisor as a list, sorted in the order of the
3872
vertices.
3873
3874
INPUT:
3875
3876
None
3877
3878
OUTPUT:
3879
3880
list of integers
3881
3882
boolean
3883
3884
EXAMPLES::
3885
3886
sage: S = Sandpile({'a':[1,'b'], 'b':[1,'a'], 1:['a']},'a')
3887
sage: D = SandpileDivisor(S, {'a':0, 'b':1, 1:2})
3888
sage: D
3889
{'a': 0, 1: 2, 'b': 1}
3890
sage: D.values()
3891
[2, 0, 1]
3892
sage: S.vertices()
3893
[1, 'a', 'b']
3894
"""
3895
return [self[v] for v in self._vertices]
3896
3897
def dualize(self):
3898
r"""
3899
The difference between the maximal stable divisor and the
3900
divisor.
3901
3902
INPUT:
3903
3904
None
3905
3906
OUTPUT:
3907
3908
SandpileDivisor
3909
3910
EXAMPLES::
3911
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3912
sage: D = SandpileDivisor(S, [1,2,3])
3913
sage: D.dualize()
3914
{0: 0, 1: -1, 2: -2}
3915
sage: S.max_stable_div() - D == D.dualize()
3916
True
3917
"""
3918
return self._sandpile.max_stable_div() - self
3919
3920
def fire_vertex(self,v):
3921
r"""
3922
Fire the vertex ``v``.
3923
3924
INPUT:
3925
3926
``v`` - vertex
3927
3928
OUTPUT:
3929
3930
SandpileDivisor
3931
3932
EXAMPLES::
3933
3934
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3935
sage: D = SandpileDivisor(S, [1,2,3])
3936
sage: D.fire_vertex(1)
3937
{0: 2, 1: 0, 2: 4}
3938
"""
3939
D = dict(self)
3940
D[v] -= self._sandpile.out_degree(v)
3941
for e in self._sandpile.outgoing_edges(v):
3942
D[e[1]]+=e[2]
3943
return SandpileDivisor(self._sandpile,D)
3944
3945
def fire_script(self, sigma):
3946
r"""
3947
Fire the script ``sigma``, i.e., fire each vertex the indicated number
3948
of times.
3949
3950
INPUT:
3951
3952
``sigma`` - SandpileDivisor or (list or dict representing a SandpileDivisor)
3953
3954
OUTPUT:
3955
3956
SandpileDivisor
3957
3958
EXAMPLES::
3959
3960
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3961
sage: D = SandpileDivisor(S, [1,2,3])
3962
sage: D.unstable()
3963
[1, 2]
3964
sage: D.fire_script([0,1,1])
3965
{0: 3, 1: 1, 2: 2}
3966
sage: D.fire_script(SandpileDivisor(S,[2,0,0])) == D.fire_vertex(0).fire_vertex(0)
3967
True
3968
"""
3969
D = dict(self)
3970
if type(sigma)!=SandpileDivisor:
3971
sigma = SandpileDivisor(self._sandpile, sigma)
3972
sigma = sigma.values()
3973
for i in range(len(sigma)):
3974
v = self._vertices[i]
3975
D[v] -= sigma[i]*self._sandpile.out_degree(v)
3976
for e in self._sandpile.outgoing_edges(v):
3977
D[e[1]]+=sigma[i]*e[2]
3978
return SandpileDivisor(self._sandpile, D)
3979
3980
def unstable(self):
3981
r"""
3982
List of the unstable vertices.
3983
3984
INPUT:
3985
3986
None
3987
3988
OUTPUT:
3989
3990
list of vertices
3991
3992
EXAMPLES::
3993
3994
sage: S = Sandpile(graphs.CycleGraph(3), 0)
3995
sage: D = SandpileDivisor(S, [1,2,3])
3996
sage: D.unstable()
3997
[1, 2]
3998
"""
3999
return [v for v in self._vertices if
4000
self[v]>=self._sandpile.out_degree(v)]
4001
4002
def fire_unstable(self):
4003
r"""
4004
Fire all unstable vertices.
4005
4006
INPUT:
4007
4008
None
4009
4010
OUTPUT:
4011
4012
SandpileDivisor
4013
4014
EXAMPLES::
4015
4016
sage: S = Sandpile(graphs.CycleGraph(3), 0)
4017
sage: D = SandpileDivisor(S, [1,2,3])
4018
sage: D.fire_unstable()
4019
{0: 3, 1: 1, 2: 2}
4020
"""
4021
D = dict(self)
4022
for v in self.unstable():
4023
D[v] -= self._sandpile.out_degree(v)
4024
for e in self._sandpile.outgoing_edges(v):
4025
D[e[1]]+=e[2]
4026
return SandpileDivisor(self._sandpile,D)
4027
4028
def _set_linear_system(self):
4029
r"""
4030
Computes and stores the complete linear system of a divisor.
4031
4032
INPUT: None
4033
4034
OUTPUT:
4035
4036
dict - ``{num_homog: int, homog:list, num_inhomog:int, inhomog:list}``
4037
4038
EXAMPLES::
4039
4040
sage: S = Sandpile(graphs.CycleGraph(3), 0)
4041
sage: D = SandpileDivisor(S, [0,1,1])
4042
sage: D._set_linear_system() # optional - 4ti2
4043
4044
WARNING:
4045
4046
This method requires 4ti2.
4047
"""
4048
# import os
4049
4050
L = self._sandpile._laplacian.transpose()
4051
n = self._sandpile.num_verts()
4052
4053
# temporary file names
4054
lin_sys = tmp_filename()
4055
lin_sys_mat = lin_sys + '.mat'
4056
lin_sys_rel = lin_sys + '.rel'
4057
lin_sys_rhs = lin_sys + '.rhs'
4058
lin_sys_sign= lin_sys + '.sign'
4059
lin_sys_zhom= lin_sys + '.zhom'
4060
lin_sys_zinhom= lin_sys + '.zinhom'
4061
lin_sys_log = lin_sys + '.log'
4062
4063
mat_file = open(lin_sys_mat,'w')
4064
mat_file.write(str(n)+' ')
4065
mat_file.write(str(n)+'\n')
4066
for r in L:
4067
mat_file.write(join(map(str,r)))
4068
mat_file.write('\n')
4069
mat_file.close()
4070
# relations file
4071
rel_file = open(lin_sys_rel,'w')
4072
rel_file.write('1 ')
4073
rel_file.write(str(n)+'\n')
4074
rel_file.write(join(['>']*n))
4075
rel_file.write('\n')
4076
rel_file.close()
4077
# right-hand side file
4078
rhs_file = open(lin_sys_rhs,'w')
4079
rhs_file.write('1 ')
4080
rhs_file.write(str(n)+'\n')
4081
rhs_file.write(join([str(-i) for i in self.values()]))
4082
rhs_file.write('\n')
4083
rhs_file.close()
4084
# sign file
4085
sign_file = open(lin_sys_sign,'w')
4086
sign_file.write('1 ')
4087
sign_file.write(str(n)+'\n')
4088
"""
4089
Conjecture: taking only 1s just below is OK, i.e., looking for solutions
4090
with nonnegative entries. The Laplacian has kernel of dimension 1,
4091
generated by a nonnegative vector. I would like to say that translating
4092
by this vector, we transform any solution into a nonnegative solution.
4093
What if the vector in the kernel does not have full support though?
4094
"""
4095
sign_file.write(join(['2']*n)) # so maybe a 1 could go here
4096
sign_file.write('\n')
4097
sign_file.close()
4098
# compute
4099
try:
4100
os.system(path_to_zsolve+'zsolve -q ' + lin_sys + ' > ' + lin_sys_log)
4101
# process the results
4102
zhom_file = open(lin_sys_zhom,'r')
4103
except IOError:
4104
print """
4105
**********************************
4106
*** This method requires 4ti2. ***
4107
**********************************
4108
4109
Read the beginning of sandpile.sage or see the Sage Sandpiles
4110
documentation for installation instructions.
4111
"""
4112
return
4113
## first, the cone generators (the homogeneous points)
4114
a = zhom_file.read()
4115
zhom_file.close()
4116
a = a.split('\n')
4117
# a starts with two numbers. We are interested in the first one
4118
num_homog = int(a[0].split()[0])
4119
homog = [map(int,i.split()) for i in a[2:-1]]
4120
## second, the inhomogeneous points
4121
zinhom_file = open(lin_sys_zinhom,'r')
4122
b = zinhom_file.read()
4123
zinhom_file.close()
4124
b = b.split('\n')
4125
num_inhomog = int(b[0].split()[0])
4126
inhomog = [map(int,i.split()) for i in b[2:-1]]
4127
self._linear_system = {'num_homog':num_homog, 'homog':homog,
4128
'num_inhomog':num_inhomog, 'inhomog':inhomog}
4129
4130
def linear_system(self):
4131
r"""
4132
The complete linear system of a divisor.
4133
4134
INPUT: None
4135
4136
OUTPUT:
4137
4138
dict - ``{num_homog: int, homog:list, num_inhomog:int, inhomog:list}``
4139
4140
EXAMPLES::
4141
4142
sage: S = sandlib('generic')
4143
sage: D = SandpileDivisor(S, [0,0,0,0,0,2])
4144
sage: D.linear_system() # optional - 4ti2
4145
{'inhomog': [[0, 0, -1, -1, 0, -2], [0, 0, 0, 0, 0, -1], [0, 0, 0, 0, 0, 0]], 'num_inhomog': 3, 'num_homog': 2, 'homog': [[1, 0, 0, 0, 0, 0], [-1, 0, 0, 0, 0, 0]]}
4146
4147
NOTES:
4148
4149
If `L` is the Laplacian, an arbitrary `v` such that `v\cdot L\geq -D`
4150
has the form `v = w + t` where `w` is in ``inhomg`` and `t` is in the
4151
integer span of ``homog`` in the output of ``linear_system(D)``.
4152
4153
WARNING:
4154
4155
This method requires 4ti2.
4156
"""
4157
return self._linear_system
4158
4159
def _set_effective_div(self):
4160
r"""
4161
Computes all of the linearly equivalent effective divisors linearly.
4162
4163
INPUT:
4164
4165
None
4166
4167
OUTPUT:
4168
4169
None
4170
4171
EXAMPLES::
4172
4173
sage: S = sandlib('generic')
4174
sage: D = SandpileDivisor(S, [0,0,0,0,0,2]) # optional - 4ti2
4175
sage: D._set_effective_div() # optional - 4ti2
4176
"""
4177
result = []
4178
r = self.linear_system()
4179
d = vector(self.values())
4180
for x in r['inhomog']:
4181
c = vector(x)*self._sandpile._laplacian + d
4182
c = SandpileDivisor(self._sandpile,list(c))
4183
if c not in result:
4184
result.append(c)
4185
self._effective_div = result
4186
4187
def effective_div(self, verbose=True):
4188
r"""
4189
All linearly equivalent effective divisors. If ``verbose``
4190
is ``False``, the divisors are converted to lists of integers.
4191
4192
INPUT:
4193
4194
``verbose`` (optional) - boolean
4195
4196
OUTPUT:
4197
4198
list (of divisors)
4199
4200
EXAMPLES::
4201
4202
sage: S = sandlib('generic')
4203
sage: D = SandpileDivisor(S, [0,0,0,0,0,2]) # optional - 4ti2
4204
sage: D.effective_div() # optional - 4ti2
4205
[{0: 1, 1: 0, 2: 0, 3: 1, 4: 0, 5: 0},
4206
{0: 0, 1: 0, 2: 1, 3: 1, 4: 0, 5: 0},
4207
{0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 2}]
4208
sage: D.effective_div(False) # optional - 4ti2
4209
[[1, 0, 0, 1, 0, 0], [0, 0, 1, 1, 0, 0], [0, 0, 0, 0, 0, 2]]
4210
"""
4211
if verbose:
4212
return deepcopy(self._effective_div)
4213
else:
4214
return [E.values() for E in self._effective_div]
4215
4216
def _set_r_of_D(self, verbose=False):
4217
r"""
4218
Computes ``r(D)`` and an effective divisor ``F`` such
4219
that ``|D - F|`` is empty.
4220
4221
INPUT:
4222
4223
verbose (optional) - boolean
4224
4225
OUTPUT:
4226
4227
None
4228
4229
EXAMPLES::
4230
4231
sage: S = sandlib('generic')
4232
sage: D = SandpileDivisor(S, [0,0,0,0,0,4]) # optional - 4ti2
4233
sage: D._set_r_of_D() # optional - 4ti2
4234
"""
4235
eff = self.effective_div()
4236
n = self._sandpile.num_verts()
4237
r = -1
4238
if eff == []:
4239
self._r_of_D = (r, self)
4240
return
4241
else:
4242
d = vector(self.values())
4243
# standard basis vectors
4244
e = []
4245
for i in range(n):
4246
v = vector([0]*n)
4247
v[i] += 1
4248
e.append(v)
4249
level = [vector([0]*n)]
4250
while True:
4251
r += 1
4252
if verbose:
4253
print r
4254
new_level = []
4255
for v in level:
4256
for i in range(n):
4257
w = v + e[i]
4258
if w not in new_level:
4259
new_level.append(w)
4260
C = d - w
4261
C = SandpileDivisor(self._sandpile,list(C))
4262
eff = C.effective_div()
4263
if eff == []:
4264
self._r_of_D = (r, SandpileDivisor(self._sandpile,list(w)))
4265
return
4266
level = new_level
4267
4268
def r_of_D(self, verbose=False):
4269
r"""
4270
Returns ``r(D)`` and, if ``verbose`` is ``True``, an effective divisor
4271
``F`` such that ``|D - F|`` is empty.
4272
4273
INPUT:
4274
4275
``verbose`` (optional) - boolean
4276
4277
OUTPUT:
4278
4279
integer ``r(D)`` or tuple (integer ``r(D)``, divisor ``F``)
4280
4281
EXAMPLES::
4282
4283
sage: S = sandlib('generic')
4284
sage: D = SandpileDivisor(S, [0,0,0,0,0,4]) # optional - 4ti2
4285
sage: E = D.r_of_D(True) # optional - 4ti2
4286
sage: E # optional - 4ti2
4287
(1, {0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 0})
4288
sage: F = E[1] # optional - 4ti2
4289
sage: (D - F).values() # optional - 4ti2
4290
[0, -1, 0, -1, 0, 4]
4291
sage: (D - F).effective_div() # optional - 4ti2
4292
[]
4293
sage: SandpileDivisor(S, [0,0,0,0,0,-4]).r_of_D(True) # optional - 4ti2
4294
(-1, {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: -4})
4295
"""
4296
if verbose:
4297
return self._r_of_D
4298
else:
4299
return self._r_of_D[0]
4300
4301
def support(self):
4302
r"""
4303
List of keys of the nonzero values of the divisor.
4304
4305
INPUT:
4306
4307
None
4308
4309
OUTPUT:
4310
4311
list - support of the divisor
4312
4313
4314
EXAMPLES::
4315
4316
sage: S = sandlib('generic')
4317
sage: c = S.identity()
4318
sage: c.values()
4319
[2, 2, 1, 1, 0]
4320
sage: c.support()
4321
[1, 2, 3, 4]
4322
sage: S.vertices()
4323
[0, 1, 2, 3, 4, 5]
4324
"""
4325
return [i for i in self.keys() if self[i] !=0]
4326
4327
def _set_Dcomplex(self):
4328
r"""
4329
Computes the simplicial complex determined by the supports of the
4330
linearly equivalent effective divisors.
4331
4332
INPUT:
4333
4334
None
4335
4336
OUTPUT:
4337
4338
None
4339
4340
EXAMPLES::
4341
4342
sage: S = sandlib('generic')
4343
sage: D = SandpileDivisor(S, [0,1,2,0,0,1]) # optional - 4ti2
4344
sage: D._set_Dcomplex() # optional - 4ti2
4345
"""
4346
simp = []
4347
for E in self.effective_div():
4348
supp_E = E.support()
4349
test = True
4350
for s in simp:
4351
if set(supp_E).issubset(set(s)):
4352
test = False
4353
break
4354
if test:
4355
simp.append(supp_E)
4356
result = []
4357
simp.reverse()
4358
while simp != []:
4359
supp = simp.pop()
4360
test = True
4361
for s in simp:
4362
if set(supp).issubset(set(s)):
4363
test = False
4364
break
4365
if test:
4366
result.append(supp)
4367
self._Dcomplex = SimplicialComplex(self._sandpile.vertices(),result)
4368
4369
def Dcomplex(self):
4370
r"""
4371
The simplicial complex determined by the supports of the
4372
linearly equivalent effective divisors.
4373
4374
INPUT:
4375
4376
None
4377
4378
OUTPUT:
4379
4380
simplicial complex
4381
4382
EXAMPLES::
4383
4384
sage: S = sandlib('generic')
4385
sage: p = SandpileDivisor(S, [0,1,2,0,0,1]).Dcomplex() # optional - 4ti2
4386
sage: p.homology() # optional - 4ti2
4387
{0: 0, 1: Z x Z, 2: 0, 3: 0}
4388
sage: p.f_vector() # optional - 4ti2
4389
[1, 6, 15, 9, 1]
4390
sage: p.betti() # optional - 4ti2
4391
{0: 1, 1: 2, 2: 0, 3: 0}
4392
"""
4393
return self._Dcomplex
4394
4395
def betti(self):
4396
r"""
4397
The Betti numbers for the simplicial complex associated with
4398
the divisor.
4399
4400
INPUT:
4401
4402
None
4403
4404
OUTPUT:
4405
4406
dictionary of integers
4407
4408
EXAMPLES::
4409
4410
sage: S = Sandpile(graphs.CycleGraph(3), 0)
4411
sage: D = SandpileDivisor(S, [2,0,1])
4412
sage: D.betti() # optional - 4ti2
4413
{0: 1, 1: 1}
4414
"""
4415
return self.Dcomplex().betti()
4416
4417
def add_random(self):
4418
r"""
4419
Add one grain of sand to a random vertex.
4420
4421
INPUT:
4422
4423
None
4424
4425
OUTPUT:
4426
4427
SandpileDivisor
4428
4429
EXAMPLES::
4430
4431
sage: S = sandlib('generic')
4432
sage: S.zero_div().add_random() #random
4433
{0: 0, 1: 0, 2: 0, 3: 1, 4: 0, 5: 0}
4434
"""
4435
D = dict(self)
4436
C = CombinatorialClass()
4437
C.list = lambda: self.keys()
4438
D[C.random_element()] += 1
4439
return SandpileDivisor(self._sandpile,D)
4440
4441
def is_symmetric(self, orbits):
4442
r"""
4443
This function checks if the values of the divisor are constant
4444
over the vertices in each sublist of ``orbits``.
4445
4446
INPUT:
4447
4448
- ``orbits`` - list of lists of vertices
4449
4450
OUTPUT:
4451
4452
boolean
4453
4454
EXAMPLES::
4455
4456
sage: S = sandlib('kite')
4457
sage: S.dict()
4458
{0: {},
4459
1: {0: 1, 2: 1, 3: 1},
4460
2: {1: 1, 3: 1, 4: 1},
4461
3: {1: 1, 2: 1, 4: 1},
4462
4: {2: 1, 3: 1}}
4463
sage: D = SandpileDivisor(S, [2,1, 2, 2, 3])
4464
sage: D.is_symmetric([[0,2,3]])
4465
True
4466
"""
4467
for x in orbits:
4468
if len(set([self[v] for v in x])) > 1:
4469
return False
4470
return True
4471
4472
def _set_life(self):
4473
r"""
4474
Will the sequence of divisors ``D_i`` where ``D_{i+1}`` is obtained from
4475
``D_i`` by firing all unstable vertices of ``D_i`` stabilize? If so,
4476
save the resulting cycle, otherwise save ``[]``.
4477
4478
INPUT:
4479
4480
None
4481
4482
OUTPUT:
4483
4484
None
4485
4486
EXAMPLES::
4487
4488
sage: S = complete_sandpile(4)
4489
sage: D = SandpileDivisor(S, {0: 4, 1: 3, 2: 3, 3: 2})
4490
sage: D._set_life()
4491
"""
4492
oldD = deepcopy(self)
4493
result = [oldD]
4494
while True:
4495
if oldD.unstable()==[]:
4496
self._life = []
4497
return
4498
newD = oldD.fire_unstable()
4499
if newD not in result:
4500
result.append(newD)
4501
oldD = deepcopy(newD)
4502
else:
4503
self._life = result[result.index(newD):]
4504
return
4505
4506
def is_alive(self, cycle=False):
4507
r"""
4508
Will the divisor stabilize under repeated firings of all unstable
4509
vertices? Optionally returns the resulting cycle.
4510
4511
INPUT:
4512
4513
``cycle`` (optional) - boolean
4514
4515
OUTPUT:
4516
4517
boolean or optionally, a list of SandpileDivisors
4518
4519
EXAMPLES::
4520
4521
sage: S = complete_sandpile(4)
4522
sage: D = SandpileDivisor(S, {0: 4, 1: 3, 2: 3, 3: 2})
4523
sage: D.is_alive()
4524
True
4525
sage: D.is_alive(True)
4526
[{0: 4, 1: 3, 2: 3, 3: 2}, {0: 3, 1: 2, 2: 2, 3: 5}, {0: 1, 1: 4, 2: 4, 3: 3}]
4527
"""
4528
if cycle:
4529
return self._life
4530
else:
4531
return self._life != []
4532
4533
def show(self,heights=True,directed=None,**kwds):
4534
r"""
4535
Show the divisor.
4536
4537
INPUT:
4538
4539
- ``heights`` - whether to label each vertex with the amount of sand
4540
- ``kwds`` - arguments passed to the show method for Graph
4541
- ``directed`` - whether to draw directed edges
4542
4543
OUTPUT:
4544
4545
None
4546
4547
EXAMPLES::
4548
4549
sage: S = sandlib('genus2')
4550
sage: D = SandpileDivisor(S,[1,-2,0,2])
4551
sage: D.show(graph_border=True,vertex_size=700,directed=False)
4552
"""
4553
if directed==True:
4554
T = DiGraph(self.sandpile())
4555
elif directed==False:
4556
T = Graph(self.sandpile())
4557
elif self.sandpile().is_directed():
4558
T = DiGraph(self.sandpile())
4559
else:
4560
T = Graph(self.sandpile())
4561
4562
max_height = max(self.sandpile().out_degree_sequence())
4563
if heights:
4564
a = {}
4565
for i in T.vertices():
4566
a[i] = str(i)+":"+str(T[i])
4567
T.relabel(a)
4568
T.show(**kwds)
4569
4570
#######################################
4571
######### Some test graphs ############
4572
#######################################
4573
4574
def sandlib(selector=None):
4575
r"""
4576
Returns the sandpile identified by ``selector``. If no argument is
4577
given, a description of the sandpiles in the sandlib is printed.
4578
4579
INPUT:
4580
4581
``selector`` - identifier or None
4582
4583
OUTPUT:
4584
4585
sandpile or description
4586
4587
EXAMPLES::
4588
4589
sage: sandlib()
4590
<BLANKLINE>
4591
Sandpiles in the sandlib:
4592
kite : generic undirected graphs with 5 vertices
4593
generic : generic digraph with 6 vertices
4594
genus2 : Undirected graph of genus 2
4595
ci1 : complete intersection, non-DAG but equivalent to a DAG
4596
riemann-roch1 : directed graph with postulation 9 and 3 maximal weight superstables
4597
riemann-roch2 : directed graph with a superstable not majorized by a maximal superstable
4598
gor : Gorenstein but not a complete intersection
4599
4600
4601
sage: S = sandlib('gor')
4602
sage: S.resolution()
4603
'R^1 <-- R^5 <-- R^5 <-- R^1'
4604
"""
4605
# The convention is for the sink to be zero.
4606
sandpiles = {
4607
'generic':{
4608
'description':'generic digraph with 6 vertices',
4609
'graph':{0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1},3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}}
4610
},
4611
'kite':{
4612
'description':'generic undirected graphs with 5 vertices',
4613
'graph':{0:{}, 1:{0:1,2:1,3:1}, 2:{1:1,3:1,4:1}, 3:{1:1,2:1,4:1},
4614
4:{2:1,3:1}}
4615
},
4616
'riemann-roch1':{
4617
'description':'directed graph with postulation 9 and 3 maximal weight superstables',
4618
'graph':{0: {1: 3, 3: 1},
4619
1: {0: 2, 2: 2, 3: 2},
4620
2: {0: 1, 1: 1},
4621
3: {0: 3, 1: 1, 2: 1}
4622
}
4623
},
4624
'riemann-roch2':{
4625
'description':'directed graph with a superstable not majorized by a maximal superstable',
4626
'graph':{
4627
0: {},
4628
1: {0: 1, 2: 1},
4629
2: {0: 1, 3: 1},
4630
3: {0: 1, 1: 1, 2: 1}
4631
}
4632
},
4633
'gor':{
4634
'description':'Gorenstein but not a complete intersection',
4635
'graph':{
4636
0: {},
4637
1: {0:1, 2: 1, 3: 4},
4638
2: {3: 5},
4639
3: {1: 1, 2: 1}
4640
}
4641
},
4642
'ci1':{
4643
'description':'complete intersection, non-DAG but equivalent to a DAG',
4644
'graph':{0:{}, 1: {2: 2}, 2: {0: 4, 1: 1}}
4645
},
4646
'genus2':{
4647
'description':'Undirected graph of genus 2',
4648
'graph':{
4649
0:[1,2],
4650
1:[0,2,3],
4651
2:[0,1,3],
4652
3:[1,2]
4653
}
4654
},
4655
}
4656
if selector==None:
4657
print
4658
print ' Sandpiles in the sandlib:'
4659
for i in sandpiles:
4660
print ' ', i, ':', sandpiles[i]['description']
4661
print
4662
elif selector not in sandpiles.keys():
4663
print selector, 'is not in the sandlib.'
4664
else:
4665
return Sandpile(sandpiles[selector]['graph'], 0)
4666
4667
#################################################
4668
########## Some useful functions ################
4669
#################################################
4670
4671
def complete_sandpile(n):
4672
r"""
4673
The sandpile on the complete graph with n vertices.
4674
4675
INPUT:
4676
4677
``n`` - positive integer
4678
4679
OUTPUT:
4680
4681
Sandpile
4682
4683
EXAMPLES::
4684
4685
sage: K = complete_sandpile(5)
4686
sage: K.betti(verbose=False)
4687
[1, 15, 50, 60, 24]
4688
"""
4689
return Sandpile(graphs.CompleteGraph(n), 0)
4690
4691
def grid_sandpile(m,n):
4692
"""
4693
The mxn grid sandpile. Each nonsink vertex has degree 4.
4694
4695
INPUT:
4696
``m``, ``n`` - positive integers
4697
4698
OUTPUT:
4699
Sandpile with sink named ``sink``.
4700
4701
EXAMPLES::
4702
4703
sage: G = grid_sandpile(3,4)
4704
sage: G.dict()
4705
{(1, 2): {(1, 1): 1, (1, 3): 1, 'sink': 1, (2, 2): 1}, (3, 2): {(3, 3): 1, (3, 1): 1, 'sink': 1, (2, 2): 1}, (1, 3): {(1, 2): 1, (2, 3): 1, 'sink': 1, (1, 4): 1}, (3, 3): {(2, 3): 1, (3, 2): 1, (3, 4): 1, 'sink': 1}, (3, 1): {(3, 2): 1, 'sink': 2, (2, 1): 1}, (1, 4): {(1, 3): 1, (2, 4): 1, 'sink': 2}, (2, 4): {(2, 3): 1, (3, 4): 1, 'sink': 1, (1, 4): 1}, (2, 3): {(3, 3): 1, (1, 3): 1, (2, 4): 1, (2, 2): 1}, (2, 1): {(1, 1): 1, (3, 1): 1, 'sink': 1, (2, 2): 1}, (2, 2): {(1, 2): 1, (3, 2): 1, (2, 3): 1, (2, 1): 1}, (3, 4): {(2, 4): 1, (3, 3): 1, 'sink': 2}, (1, 1): {(1, 2): 1, 'sink': 2, (2, 1): 1}, 'sink': {}}
4706
sage: G.group_order()
4707
4140081
4708
sage: G.invariant_factors()
4709
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1380027]
4710
"""
4711
g = {}
4712
# corners first
4713
g[(1,1)] = {(1,2):1, (2,1):1, 'sink':2}
4714
g[(m,1)] = {(m-1,1):1, (m,2):1, 'sink':2}
4715
g[(1,n)] = {(1,n-1):1, (2,n):1, 'sink':2}
4716
g[(m,n)] = {(m-1,n):1, (m,n-1):1, 'sink':2}
4717
# top edge
4718
for col in range(2,n):
4719
g[(1,col)] = {(1,col-1):1, (1,col+1):1, (2,col):1, 'sink':1}
4720
# left edge
4721
for row in range (2,m):
4722
g[(row,1)] = {(row-1,1):1, (row+1,1):1, (row,2):1, 'sink':1}
4723
# right edge
4724
for row in range (2,m):
4725
g[(row,n)] = {(row-1,n):1, (row+1,n):1, (row,n-1):1, 'sink':1}
4726
# bottom edge
4727
for col in range(2,n):
4728
g[(m,col)] = {(m,col-1):1, (m,col+1):1, (m-1,col):1, 'sink':1}
4729
# inner vertices
4730
for row in range(2,m):
4731
for col in range(2,n):
4732
g[(row,col)] ={(row-1,col):1, (row+1,col):1, (row,col-1):1, (row,col+1):1}
4733
# the sink vertex
4734
g['sink'] = {}
4735
return Sandpile(g, 'sink')
4736
4737
def triangle_sandpile(n):
4738
r"""
4739
A triangular sandpile. Each nonsink vertex has out-degree six. The
4740
vertices on the boundary of the triangle are connected to the sink.
4741
4742
INPUT:
4743
4744
``n`` - int
4745
4746
OUTPUT:
4747
4748
Sandpile
4749
4750
EXAMPLES::
4751
4752
sage: T = triangle_sandpile(5)
4753
sage: T.group_order()
4754
135418115000
4755
"""
4756
T = {'sink':{}}
4757
for i in range(n):
4758
for j in range(n-i):
4759
T[(i,j)] = {}
4760
if i<n-j-1:
4761
T[(i,j)][(i+1,j)] = 1
4762
T[(i,j)][(i,j+1)] = 1
4763
if i>0:
4764
T[(i,j)][(i-1,j+1)] = 1
4765
T[(i,j)][(i-1,j)] = 1
4766
if j>0:
4767
T[(i,j)][(i,j-1)] = 1
4768
T[(i,j)][(i+1,j-1)] = 1
4769
d = len(T[(i,j)])
4770
if d<6:
4771
T[(i,j)]['sink'] = 6-d
4772
T = Sandpile(T,'sink')
4773
pos = {}
4774
for x in T.nonsink_vertices():
4775
coords = list(x)
4776
coords[0]+=1/2*coords[1]
4777
pos[x] = coords
4778
pos['sink'] = (-1,-1)
4779
T.set_pos(pos)
4780
return T
4781
4782
def aztec_sandpile(n):
4783
r"""
4784
The aztec diamond graph.
4785
4786
INPUT:
4787
4788
``n`` - integer
4789
4790
OUTPUT:
4791
4792
dictionary for the aztec diamond graph
4793
4794
EXAMPLES::
4795
4796
sage: aztec_sandpile(2)
4797
{'sink': {(3/2, 1/2): 2, (-1/2, -3/2): 2, (-3/2, 1/2): 2, (1/2, 3/2): 2, (1/2, -3/2): 2, (-3/2, -1/2): 2, (-1/2, 3/2): 2, (3/2, -1/2): 2}, (1/2, 3/2): {(-1/2, 3/2): 1, (1/2, 1/2): 1, 'sink': 2}, (1/2, 1/2): {(1/2, -1/2): 1, (3/2, 1/2): 1, (1/2, 3/2): 1, (-1/2, 1/2): 1}, (-3/2, 1/2): {(-3/2, -1/2): 1, 'sink': 2, (-1/2, 1/2): 1}, (-1/2, -1/2): {(-3/2, -1/2): 1, (1/2, -1/2): 1, (-1/2, -3/2): 1, (-1/2, 1/2): 1}, (-1/2, 1/2): {(-3/2, 1/2): 1, (-1/2, -1/2): 1, (-1/2, 3/2): 1, (1/2, 1/2): 1}, (-3/2, -1/2): {(-3/2, 1/2): 1, (-1/2, -1/2): 1, 'sink': 2}, (3/2, 1/2): {(1/2, 1/2): 1, (3/2, -1/2): 1, 'sink': 2}, (-1/2, 3/2): {(1/2, 3/2): 1, 'sink': 2, (-1/2, 1/2): 1}, (1/2, -3/2): {(1/2, -1/2): 1, (-1/2, -3/2): 1, 'sink': 2}, (3/2, -1/2): {(3/2, 1/2): 1, (1/2, -1/2): 1, 'sink': 2}, (1/2, -1/2): {(1/2, -3/2): 1, (-1/2, -1/2): 1, (1/2, 1/2): 1, (3/2, -1/2): 1}, (-1/2, -3/2): {(-1/2, -1/2): 1, 'sink': 2, (1/2, -3/2): 1}}
4798
sage: Sandpile(aztec_sandpile(2),'sink').group_order()
4799
4542720
4800
4801
NOTES:
4802
4803
This is the aztec diamond graph with a sink vertex added. Boundary
4804
vertices have edges to the sink so that each vertex has degree 4.
4805
"""
4806
aztec_sandpile = {}
4807
half = QQ(1)/2
4808
for i in srange(n):
4809
for j in srange(n-i):
4810
aztec_sandpile[(half+i,half+j)] = {}
4811
aztec_sandpile[(-half-i,half+j)] = {}
4812
aztec_sandpile[(half+i,-half-j)] = {}
4813
aztec_sandpile[(-half-i,-half-j)] = {}
4814
non_sinks = aztec_sandpile.keys()
4815
aztec_sandpile['sink'] = {}
4816
for vert in non_sinks:
4817
weight = abs(vert[0]) + abs(vert[1])
4818
x = vert[0]
4819
y = vert[1]
4820
if weight < n:
4821
aztec_sandpile[vert] = {(x+1,y):1, (x,y+1):1, (x-1,y):1, (x,y-1):1}
4822
else:
4823
if (x+1,y) in aztec_sandpile.keys():
4824
aztec_sandpile[vert][(x+1,y)] = 1
4825
if (x,y+1) in aztec_sandpile.keys():
4826
aztec_sandpile[vert][(x,y+1)] = 1
4827
if (x-1,y) in aztec_sandpile.keys():
4828
aztec_sandpile[vert][(x-1,y)] = 1
4829
if (x,y-1) in aztec_sandpile.keys():
4830
aztec_sandpile[vert][(x,y-1)] = 1
4831
if len(aztec_sandpile[vert]) < 4:
4832
out_degree = 4 - len(aztec_sandpile[vert])
4833
aztec_sandpile[vert]['sink'] = out_degree
4834
aztec_sandpile['sink'][vert] = out_degree
4835
return aztec_sandpile
4836
4837
def random_digraph(num_verts, p=1/2, directed=True, weight_max=1):
4838
"""
4839
A random weighted digraph with a directed spanning tree rooted at `0`. If
4840
``directed = False``, the only difference is that if `(i,j,w)` is an edge with
4841
tail `i`, head `j`, and weight `w`, then `(j,i,w)` appears also. The result
4842
is returned as a Sage digraph.
4843
4844
INPUT:
4845
4846
- ``num_verts`` - number of vertices
4847
- ``p`` - probability edges occur
4848
- ``directed`` - True if directed
4849
- ``weight_max`` - integer maximum for random weights
4850
4851
OUTPUT:
4852
4853
random graph
4854
4855
EXAMPLES::
4856
4857
sage: g = random_digraph(6,0.2,True,3)
4858
sage: S = Sandpile(g,0)
4859
sage: S.show(edge_labels = True)
4860
"""
4861
4862
a = digraphs.RandomDirectedGN(num_verts)
4863
b = graphs.RandomGNP(num_verts,p)
4864
a.add_edges(b.edges())
4865
if directed:
4866
c = graphs.RandomGNP(num_verts,p)
4867
# reverse the edges of c and add them in
4868
a.add_edges([(j,i,None) for i,j,k in c.edges()])
4869
else:
4870
a.add_edges([(j,i,None) for i,j,k in a.edges()])
4871
a.add_edges([(j,i,None) for i,j,k in b.edges()])
4872
# now handle the weights
4873
for i,j,k in a.edge_iterator():
4874
a.set_edge_label(i,j,ZZ.random_element(weight_max)+1)
4875
return a
4876
4877
def random_DAG(num_verts, p=1/2, weight_max=1):
4878
r"""
4879
A random directed acyclic graph with ``num_verts`` vertices.
4880
The method starts with the sink vertex and adds vertices one at a time.
4881
Each vertex is connected only to only previously defined vertices, and the
4882
probability of each possible connection is given by the argument ``p``.
4883
The weight of an edge is a random integer between ``1`` and
4884
``weight_max``.
4885
4886
INPUT:
4887
4888
- ``num_verts`` - positive integer
4889
- ``p`` - number between `0` and `1`
4890
- ``weight_max`` -- integer greater than `0`
4891
4892
OUTPUT:
4893
4894
directed acyclic graph with sink `0`
4895
4896
EXAMPLES::
4897
4898
sage: S = random_DAG(5, 0.3)
4899
"""
4900
g = {0:{}}
4901
for i in range(1,num_verts+1):
4902
out_edges = {}
4903
while out_edges == {}:
4904
for j in range(i):
4905
if p > random():
4906
out_edges[j] = randint(1,weight_max)
4907
g[i] = out_edges
4908
return g
4909
4910
def random_tree(n,d):
4911
r"""
4912
A random undirected tree with ``n`` nodes, no node having
4913
degree higher than ``d``.
4914
4915
INPUT:
4916
4917
``n``, ``d`` - integers
4918
4919
OUTPUT:
4920
4921
Graph
4922
4923
EXAMPLES::
4924
4925
sage: T = random_tree(15,3)
4926
sage: T.show()
4927
sage: S = Sandpile(T,0)
4928
sage: U = S.reorder_vertices()
4929
sage: U.show()
4930
"""
4931
g = Graph()
4932
# active vertices
4933
active = [0]
4934
g.add_vertex(0)
4935
next_vertex = 1
4936
while g.num_verts()<n:
4937
node = randint(0,g.num_verts()-1)
4938
if g.degree(node)>d:
4939
active.remove(node)
4940
break
4941
r = randint(0,d)
4942
if r>0:
4943
for i in range(r):
4944
g.add_vertex(next_vertex)
4945
g.add_edge((node,next_vertex))
4946
active.append(next_vertex)
4947
next_vertex+=1
4948
return g
4949
4950
def glue_graphs(g,h,glue_g,glue_h):
4951
r"""
4952
Glue two graphs together.
4953
4954
INPUT:
4955
4956
- ``g``, ``h`` - dictionaries for directed multigraphs
4957
- ``glue_h``, ``glue_g`` - dictionaries for a vertex
4958
4959
OUTPUT:
4960
4961
dictionary for a directed multigraph
4962
4963
4964
EXAMPLES::
4965
4966
sage: x = {0: {}, 1: {0: 1}, 2: {0: 1, 1: 1}, 3: {0: 1, 1: 1, 2: 1}}
4967
sage: y = {0: {}, 1: {0: 2}, 2: {1: 2}, 3: {0: 1, 2: 1}}
4968
sage: glue_x = {1: 1, 3: 2}
4969
sage: glue_y = {0: 1, 1: 2, 3: 1}
4970
sage: z = glue_graphs(x,y,glue_x,glue_y)
4971
sage: z
4972
{0: {}, 'y2': {'y1': 2}, 'y1': {0: 2}, 'x2': {'x0': 1, 'x1': 1}, 'x3': {'x2': 1, 'x0': 1, 'x1': 1}, 'y3': {0: 1, 'y2': 1}, 'x1': {'x0': 1}, 'x0': {0: 1, 'x3': 2, 'y3': 1, 'x1': 1, 'y1': 2}}
4973
sage: S = Sandpile(z,0)
4974
sage: S.h_vector()
4975
[1, 6, 17, 31, 41, 41, 31, 17, 6, 1]
4976
sage: S.resolution()
4977
'R^1 <-- R^7 <-- R^21 <-- R^35 <-- R^35 <-- R^21 <-- R^7 <-- R^1'
4978
4979
NOTES:
4980
4981
This method makes a dictionary for a graph by combining those for
4982
``g`` and ``h``. The sink of ``g`` is replaced by a vertex that
4983
is connected to the vertices of ``g`` as specified by ``glue_g``
4984
the vertices of ``h`` as specified in ``glue_h``. The sink of the glued
4985
graph is `0`.
4986
4987
Both ``glue_g`` and ``glue_h`` are dictionaries with entries of the form
4988
``v:w`` where ``v`` is the vertex to be connected to and ``w`` is the weight
4989
of the connecting edge.
4990
"""
4991
# first find the sinks of g and h
4992
for i in g:
4993
if g[i] == {}:
4994
g_sink = i
4995
break
4996
for i in h:
4997
if h[i] == {}:
4998
h_sink = i
4999
break
5000
k = {0: {}} # the new graph dictionary, starting with the sink
5001
for i in g:
5002
if i != g_sink:
5003
new_edges = {}
5004
for j in g[i]:
5005
new_edges['x'+str(j)] = g[i][j]
5006
k['x'+str(i)] = new_edges
5007
for i in h:
5008
if i != h_sink:
5009
new_edges = {}
5010
for j in h[i]:
5011
if j == h_sink:
5012
new_edges[0] = h[i][j]
5013
else:
5014
new_edges['y'+str(j)] = h[i][j]
5015
k['y'+str(i)] = new_edges
5016
# now handle the glue vertex (old g sink)
5017
new_edges = {}
5018
for i in glue_g:
5019
new_edges['x'+str(i)] = glue_g[i]
5020
for i in glue_h:
5021
if i == h_sink:
5022
new_edges[0] = glue_h[i]
5023
else:
5024
new_edges['y'+str(i)] = glue_h[i]
5025
k['x'+str(g_sink)] = new_edges
5026
return k
5027
5028
def firing_graph(S,eff):
5029
r"""
5030
Creates a digraph with divisors as vertices and edges between two
5031
divisors ``D`` and ``E`` if firing a single vertex in ``D`` gives
5032
``E``.
5033
5034
INPUT:
5035
5036
``S`` - sandpile
5037
``eff`` - list of divisors
5038
5039
OUTPUT:
5040
5041
DiGraph
5042
5043
EXAMPLES::
5044
5045
sage: S = Sandpile(graphs.CycleGraph(6),0)
5046
sage: D = SandpileDivisor(S, [1,1,1,1,2,0])
5047
sage: eff = D.effective_div() # optional - 4ti2
5048
sage: firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) # optional - 4ti2
5049
"""
5050
g = DiGraph()
5051
g.add_vertices(range(len(eff)))
5052
for i in g.vertices():
5053
for v in eff[i]:
5054
if eff[i][v]>=S.out_degree(v):
5055
new_div = deepcopy(eff[i])
5056
new_div[v] -= S.out_degree(v)
5057
for oe in S.outgoing_edges(v):
5058
new_div[oe[1]]+=oe[2]
5059
if new_div in eff:
5060
g.add_edge((i,eff.index(new_div)))
5061
return g
5062
5063
def parallel_firing_graph(S, eff):
5064
r"""
5065
Creates a digraph with divisors as vertices and edges between two
5066
divisors ``D`` and ``E`` if firing all unstable vertices in ``D`` gives
5067
``E``.
5068
5069
INPUT:
5070
5071
``S`` - Sandpile
5072
``eff`` - list of divisors
5073
5074
OUTPUT:
5075
5076
DiGraph
5077
5078
EXAMPLES::
5079
5080
sage: S = Sandpile(graphs.CycleGraph(6),0)
5081
sage: D = SandpileDivisor(S, [1,1,1,1,2,0])
5082
sage: eff = D.effective_div() # optional - 4ti2
5083
sage: parallel_firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) # optional - 4ti2
5084
"""
5085
g = DiGraph()
5086
g.add_vertices(range(len(eff)))
5087
for i in g.vertices():
5088
new_edge = False
5089
new_div = deepcopy(eff[i])
5090
for v in eff[i]:
5091
if eff[i][v]>=S.out_degree(v):
5092
new_edge = True
5093
new_div[v] -= S.out_degree(v)
5094
for oe in S.outgoing_edges(v):
5095
new_div[oe[1]]+=oe[2]
5096
if new_edge and (new_div in eff):
5097
g.add_edge((i,eff.index(new_div)))
5098
return g
5099
5100
def admissible_partitions(S, k):
5101
r"""
5102
The partitions of the vertices of ``S`` into ``k`` parts,
5103
each of which is connected.
5104
5105
INPUT:
5106
5107
``S`` - Sandpile
5108
``k`` - integer
5109
5110
OUTPUT:
5111
5112
list of partitions
5113
5114
EXAMPLES::
5115
5116
sage: S = Sandpile(graphs.CycleGraph(4), 0)
5117
sage: P = [admissible_partitions(S, i) for i in [2,3,4]]
5118
sage: P
5119
[[{{1, 2, 3}, {0}},
5120
{{0, 2, 3}, {1}},
5121
{{2}, {0, 1, 3}},
5122
{{0, 1, 2}, {3}},
5123
{{2, 3}, {0, 1}},
5124
{{1, 2}, {0, 3}}],
5125
[{{2, 3}, {0}, {1}},
5126
{{1, 2}, {3}, {0}},
5127
{{2}, {0, 3}, {1}},
5128
{{2}, {3}, {0, 1}}],
5129
[{{2}, {3}, {0}, {1}}]]
5130
sage: for p in P:
5131
... sum([partition_sandpile(S, i).betti(verbose=False)[-1] for i in p])
5132
6
5133
8
5134
3
5135
sage: S.betti()
5136
0 1 2 3
5137
------------------------------
5138
0: 1 - - -
5139
1: - 6 8 3
5140
------------------------------
5141
total: 1 6 8 3
5142
"""
5143
v = S.vertices()
5144
if S.is_directed():
5145
G = DiGraph(S)
5146
else:
5147
G = Graph(S)
5148
result = []
5149
for p in SetPartitions(v, k):
5150
if forall(p, lambda x : G.subgraph(list(x)).is_connected())[0]:
5151
result.append(p)
5152
return result
5153
5154
def partition_sandpile(S,p):
5155
r"""
5156
Each set of vertices in ``p`` is regarded as a single vertex, with and edge
5157
between ``A`` and ``B`` if some element of ``A`` is connected by an edge
5158
to some element of ``B`` in ``S``.
5159
5160
INPUT:
5161
5162
``S`` - Sandpile
5163
``p`` - partition of the vertices of ``S``
5164
5165
OUTPUT:
5166
5167
Sandpile
5168
5169
EXAMPLES::
5170
5171
sage: S = Sandpile(graphs.CycleGraph(4), 0)
5172
sage: P = [admissible_partitions(S, i) for i in [2,3,4]]
5173
sage: for p in P:
5174
... sum([partition_sandpile(S, i).betti(verbose=False)[-1] for i in p])
5175
6
5176
8
5177
3
5178
sage: S.betti()
5179
0 1 2 3
5180
------------------------------
5181
0: 1 - - -
5182
1: - 6 8 3
5183
------------------------------
5184
total: 1 6 8 3
5185
"""
5186
g = Graph()
5187
g.add_vertices([tuple(i) for i in p])
5188
for u,v in combinations(range(len(g.vertices())),2):
5189
for i in g.vertices()[u]:
5190
for j in g.vertices()[v]:
5191
if (i,j,1) in S.edges():
5192
g.add_edge((g.vertices()[u],g.vertices()[v]))
5193
break
5194
for i in g.vertices():
5195
if S.sink() in i:
5196
return Sandpile(g,i)
5197
5198
def firing_vector(S,D,E):
5199
r"""
5200
If ``D`` and ``E`` are linearly equivalent divisors, find the firing vector
5201
taking ``D`` to ``E``.
5202
5203
INPUT:
5204
5205
- ``S`` -Sandpile
5206
``D``, ``E`` - tuples (representing linearly equivalent divisors)
5207
5208
OUTPUT:
5209
5210
tuple (representing a firing vector from ``D`` to ``E``)
5211
5212
EXAMPLES::
5213
5214
sage: S = complete_sandpile(4)
5215
sage: D = SandpileDivisor(S, {0: 0, 1: 0, 2: 8, 3: 0})
5216
sage: E = SandpileDivisor(S, {0: 2, 1: 2, 2: 2, 3: 2})
5217
sage: v = firing_vector(S, D, E)
5218
sage: v
5219
(0, 0, 2, 0)
5220
5221
The divisors must be linearly equivalent::
5222
5223
sage: vector(D.values()) - S.laplacian()*vector(v) == vector(E.values())
5224
True
5225
sage: firing_vector(S, D, S.zero_div())
5226
Error. Are the divisors linearly equivalent?
5227
"""
5228
try:
5229
v = vector(D.values())
5230
w = vector(E.values())
5231
return tuple(S.laplacian().solve_left(v-w))
5232
except ValueError:
5233
print "Error. Are the divisors linearly equivalent?"
5234
return
5235
5236
def min_cycles(G,v):
5237
r"""
5238
Minimal length cycles in the digraph ``G`` starting at vertex ``v``.
5239
5240
INPUT:
5241
5242
``G`` - DiGraph
5243
``v`` - vertex of ``G``
5244
5245
OUTPUT:
5246
5247
list of lists of vertices
5248
5249
EXAMPLES::
5250
5251
sage: T = sandlib('gor')
5252
sage: [min_cycles(T, i) for i in T.vertices()]
5253
[[], [[1, 3]], [[2, 3, 1], [2, 3]], [[3, 1], [3, 2]]]
5254
"""
5255
pr = G.neighbors_in(v)
5256
sp = G.shortest_paths(v)
5257
return [sp[i] for i in pr if i in sp.keys()]
5258
5259
def wilmes_algorithm(M):
5260
r"""
5261
Computes an integer matrix ``L`` with the same integer row span as ``M``
5262
and such that ``L`` is the reduced laplacian of a directed multigraph.
5263
5264
INPUT:
5265
5266
``M`` - square integer matrix of full rank
5267
5268
OUTPUT:
5269
5270
``L`` - integer matrix
5271
5272
EXAMPLES::
5273
5274
sage: P = matrix([[2,3,-7,-3],[5,2,-5,5],[8,2,5,4],[-5,-9,6,6]])
5275
sage: wilmes_algorithm(P)
5276
[ 1642 -13 -1627 -1]
5277
[ -1 1980 -1582 -397]
5278
[ 0 -1 1650 -1649]
5279
[ 0 0 -1658 1658]
5280
5281
NOTES:
5282
5283
The algorithm is due to John Wilmes.
5284
"""
5285
# find the gcd of the row-sums, and perform the corresponding row
5286
# operations on M
5287
if M.matrix_over_field().is_invertible():
5288
L = deepcopy(M)
5289
L = matrix(ZZ,L)
5290
U = matrix(ZZ,[sum(i) for i in L]).smith_form()[2].transpose()
5291
L = U*M
5292
for k in range(1,M.nrows()-1):
5293
smith = matrix(ZZ,[i[k-1] for i in L[k:]]).smith_form()[2].transpose()
5294
U = identity_matrix(ZZ,k).block_sum(smith)
5295
L = U*L
5296
L[k] = -L[k]
5297
if L[-1][-2]>0:
5298
L[-1] = -L[-1]
5299
for k in range(M.nrows()-2,-1,-1):
5300
for i in range(k+2,M.nrows()):
5301
while L[k][i-1]>0:
5302
L[k] = L[k] + L[i]
5303
v = -L[k+1]
5304
for i in range(k+2,M.nrows()):
5305
v = abs(L[i,i-1])*v + v[i-1]*L[i]
5306
while L[k,k]<=0 or L[k,-1]>0:
5307
L[k] = L[k] + v
5308
return L
5309
else:
5310
raise UserWarning, 'matrix not of full rank'
5311
5312
######### Notes ################
5313
"""
5314
* pickling sandpiles?
5315
* Does 'sat' return a minimal generating set
5316
"""
5317
5318
5319