Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/sandpiles/sandpile.py
8817 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.superseded 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
14
from sage.combinat.set_partition import SetPartitions
15
from sage.homology.simplicial_complex import SimplicialComplex
16
from sage.plot.colors import rainbow
17
from sage.env import SAGE_LOCAL
18
19
r"""
20
To calculate linear systems associated with divisors, 4ti2 must be installed.
21
One way to do this is to run sage -i to install glpk, then 4ti2. See
22
http://sagemath.org/download-packages.html to get the exact names of these
23
packages. An alternative is to install 4ti2 separately, then point the
24
following variable to the correct path.
25
"""
26
27
path_to_zsolve = os.path.join(SAGE_LOCAL,'bin','zsolve')
28
29
r"""
30
Sage Sandpiles
31
32
Functions and classes for mathematical sandpiles.
33
34
Version: 2.3
35
36
AUTHOR:
37
-- Marshall Hampton (2010-1-10) modified for inclusion as a module
38
within Sage library.
39
40
-- David Perkinson (2010-12-14) added show3d(), fixed bug in resolution(),
41
replaced elementary_divisors() with invariant_factors(), added show() for
42
SandpileConfig and SandpileDivisor.
43
44
-- David Perkinson (2010-9-18): removed is_undirected, added show(), added
45
verbose arguments to several functions to display SandpileConfigs and divisors as
46
lists of integers
47
48
-- David Perkinson (2010-12-19): created separate SandpileConfig, SandpileDivisor, and
49
Sandpile classes
50
51
-- David Perkinson (2009-07-15): switched to using config_to_list instead
52
of .values(), thus fixing a few bugs when not using integer labels for
53
vertices.
54
55
-- David Perkinson (2009): many undocumented improvements
56
57
-- David Perkinson (2008-12-27): initial version
58
59
EXAMPLES::
60
61
A weighted directed graph given as a Python dictionary::
62
63
sage: from sage.sandpiles import *
64
sage: g = {0: {}, \
65
1: {0: 1, 2: 1, 3: 1}, \
66
2: {1: 1, 3: 1, 4: 1}, \
67
3: {1: 1, 2: 1, 4: 1}, \
68
4: {2: 1, 3: 1}}
69
70
The associated sandpile with 0 chosen as the sink::
71
72
sage: S = Sandpile(g,0)
73
74
A picture of the graph::
75
76
sage: S.show()
77
78
The relevant Laplacian matrices::
79
80
sage: S.laplacian()
81
[ 0 0 0 0 0]
82
[-1 3 -1 -1 0]
83
[ 0 -1 3 -1 -1]
84
[ 0 -1 -1 3 -1]
85
[ 0 0 -1 -1 2]
86
sage: S.reduced_laplacian()
87
[ 3 -1 -1 0]
88
[-1 3 -1 -1]
89
[-1 -1 3 -1]
90
[ 0 -1 -1 2]
91
92
The number of elements of the sandpile group for S::
93
94
sage: S.group_order()
95
8
96
97
The structure of the sandpile group::
98
99
sage: S.invariant_factors()
100
[1, 1, 1, 8]
101
102
The elements of the sandpile group for S::
103
104
sage: S.recurrents()
105
[{1: 2, 2: 2, 3: 2, 4: 1},
106
{1: 2, 2: 2, 3: 2, 4: 0},
107
{1: 2, 2: 1, 3: 2, 4: 0},
108
{1: 2, 2: 2, 3: 0, 4: 1},
109
{1: 2, 2: 0, 3: 2, 4: 1},
110
{1: 2, 2: 2, 3: 1, 4: 0},
111
{1: 2, 2: 1, 3: 2, 4: 1},
112
{1: 2, 2: 2, 3: 1, 4: 1}]
113
114
The maximal stable element (2 grains of sand on vertices 1, 2, and 3, and 1
115
grain of sand on vertex 4::
116
117
sage: S.max_stable()
118
{1: 2, 2: 2, 3: 2, 4: 1}
119
sage: S.max_stable().values()
120
[2, 2, 2, 1]
121
122
The identity of the sandpile group for S::
123
124
sage: S.identity()
125
{1: 2, 2: 2, 3: 2, 4: 0}
126
127
Some group operations::
128
129
sage: m = S.max_stable()
130
sage: i = S.identity()
131
sage: m.values()
132
[2, 2, 2, 1]
133
sage: i.values()
134
[2, 2, 2, 0]
135
sage: m+i # coordinate-wise sum
136
{1: 4, 2: 4, 3: 4, 4: 1}
137
sage: m - i
138
{1: 0, 2: 0, 3: 0, 4: 1}
139
sage: m & i # add, then stabilize
140
{1: 2, 2: 2, 3: 2, 4: 1}
141
sage: e = m + m
142
sage: e
143
{1: 4, 2: 4, 3: 4, 4: 2}
144
sage: ~e # stabilize
145
{1: 2, 2: 2, 3: 2, 4: 0}
146
sage: a = -m
147
sage: a & m
148
{1: 0, 2: 0, 3: 0, 4: 0}
149
sage: a * m # add, then find the equivalent recurrent
150
{1: 2, 2: 2, 3: 2, 4: 0}
151
sage: a^3 # a*a*a
152
{1: 2, 2: 2, 3: 2, 4: 1}
153
sage: a^(-1) == m
154
True
155
sage: a < m # every coordinate of a is < that of m
156
True
157
158
Firing an unstable vertex returns resulting configuration::
159
160
sage: c = S.max_stable() + S.identity()
161
sage: c.fire_vertex(1)
162
{1: 1, 2: 5, 3: 5, 4: 1}
163
sage: c
164
{1: 4, 2: 4, 3: 4, 4: 1}
165
166
Fire all unstable vertices::
167
168
sage: c.unstable()
169
[1, 2, 3]
170
sage: c.fire_unstable()
171
{1: 3, 2: 3, 3: 3, 4: 3}
172
173
Stabilize c, returning the resulting configuration and the firing
174
vector::
175
176
sage: c.stabilize(True)
177
[{1: 2, 2: 2, 3: 2, 4: 1}, {1: 6, 2: 8, 3: 8, 4: 8}]
178
sage: c
179
{1: 4, 2: 4, 3: 4, 4: 1}
180
sage: S.max_stable() & S.identity() == c.stabilize()
181
True
182
183
The number of superstable configurations of each degree::
184
185
sage: S.hilbert_function()
186
[1, 4, 8]
187
sage: S.postulation()
188
2
189
190
the saturated, homogeneous sandpile ideal
191
192
sage: S.ideal()
193
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
194
195
its minimal free resolution
196
197
sage: S.resolution()
198
'R^1 <-- R^7 <-- R^15 <-- R^13 <-- R^4'
199
200
and its Betti numbers::
201
202
sage: S.betti()
203
0 1 2 3 4
204
------------------------------------
205
0: 1 1 - - -
206
1: - 2 2 - -
207
2: - 4 13 13 4
208
------------------------------------
209
total: 1 7 15 13 4
210
211
Distribution of avalanche sizes::
212
213
sage: S = grid_sandpile(10,10)
214
sage: m = S.max_stable()
215
sage: a = []
216
sage: for i in range(1000):
217
... m = m.add_random()
218
... m, f = m.stabilize(True)
219
... a.append(sum(f.values()))
220
...
221
sage: p = list_plot([[log(i+1),log(a.count(i))] for i in [0..max(a)] if a.count(i)])
222
sage: p.axes_labels(['log(N)','log(D(N))'])
223
sage: t = text("Distribution of avalanche sizes", (2,2), rgbcolor=(1,0,0))
224
sage: show(p+t,axes_labels=['log(N)','log(D(N))'])
225
"""
226
#*****************************************************************************
227
# Copyright (C) 2011 David Perkinson <[email protected]>
228
#
229
# Distributed under the terms of the GNU General Public License (GPL)
230
# http://www.gnu.org/licenses/
231
#*****************************************************************************
232
233
class Sandpile(DiGraph):
234
"""
235
Class for Dhar's abelian sandpile model.
236
"""
237
238
def __init__(self, g, sink):
239
r"""
240
Create a sandpile.
241
242
INPUT:
243
244
- ``g`` - dict for directed multgraph (see NOTES) edges weighted by
245
nonnegative integers
246
247
- ``sink`` - A sink vertex. Any outgoing edges from the designated
248
sink are ignored for the purposes of stabilization. It is assumed
249
that every vertex has a directed path into the sink.
250
251
OUTPUT:
252
253
Sandpile
254
255
EXAMPLES:
256
257
Here ``g`` represents a square with directed, multiple edges with three
258
vertices, ``a``, ``b``, ``c``, and ``d``. The vertex ``a`` has
259
outgoing edges to itself (weight 2), to vertex ``b`` (weight 1), and
260
vertex ``c`` (weight 3), for example.
261
262
::
263
264
sage: g = {'a': {'a':2, 'b':1, 'c':3}, 'b': {'a':1, 'd':1},\
265
'c': {'a':1,'d': 1}, 'd': {'b':1, 'c':1}}
266
sage: G = Sandpile(g,'d')
267
268
Here is a square with unweighted edges. In this example, the graph is
269
also undirected.
270
271
::
272
273
sage: g = {0:[1,2], 1:[0,3], 2:[0,3], 3:[1,2]}
274
sage: G = Sandpile(g,3)
275
276
NOTES::
277
278
Loops are allowed. There are two admissible formats, both of which are
279
dictionaries whose keys are the vertex names. In one format, the
280
values are dictionaries with keys the names of vertices which are the
281
tails of outgoing edges and with values the weights of the edges. In
282
the other format, the values are lists of names of vertices which are
283
the tails of the outgoing edges. All weights are then automatically
284
assigned the value 1.
285
286
TESTS::
287
288
sage: S = complete_sandpile(4)
289
sage: TestSuite(S).run()
290
"""
291
# preprocess a graph, if necessary
292
if type(g) == dict and type(g.values()[0]) == dict:
293
pass # this is the default format
294
elif type(g) == dict and type(g.values()[0]) == list:
295
processed_g = {}
296
for k in g.keys():
297
temp = {}
298
for vertex in g[k]:
299
temp[vertex] = 1
300
processed_g[k] = temp
301
g = processed_g
302
elif type(g) == Graph:
303
processed_g = {}
304
for v in g.vertices():
305
edges = {}
306
for n in g.neighbors(v):
307
if type(g.edge_label(v,n)) == type(1) and g.edge_label(v,n) >=0:
308
edges[n] = g.edge_label(v,n)
309
else:
310
edges[n] = 1
311
processed_g[v] = edges
312
g = processed_g
313
elif type(g) == DiGraph:
314
processed_g = {}
315
for v in g.vertices():
316
edges = {}
317
for n in g.neighbors_out(v):
318
if (type(g.edge_label(v,n)) == type(1)
319
and g.edge_label(v,n)>=0):
320
edges[n] = g.edge_label(v,n)
321
else:
322
edges[n] = 1
323
processed_g[v] = edges
324
g = processed_g
325
else:
326
raise SyntaxError, g
327
328
# create digraph and initialize some variables
329
DiGraph.__init__(self,g,weighted=True)
330
self._dict = deepcopy(g)
331
self._sink = sink # key for sink
332
self._sink_ind = self.vertices().index(sink)
333
self._nonsink_vertices = deepcopy(self.vertices())
334
del self._nonsink_vertices[self._sink_ind]
335
# compute laplacians:
336
self._laplacian = self.laplacian_matrix(indegree=False)
337
temp = range(self.num_verts())
338
del temp[self._sink_ind]
339
self._reduced_laplacian = self._laplacian[temp,temp]
340
341
def __getattr__(self, name):
342
"""
343
Set certain variables only when called.
344
345
INPUT:
346
347
``name`` - name of an internal method
348
349
OUTPUT:
350
351
None.
352
353
EXAMPLES::
354
355
sage: S = complete_sandpile(5)
356
sage: S.__getattr__('_max_stable')
357
{1: 3, 2: 3, 3: 3, 4: 3}
358
"""
359
if not self.__dict__.has_key(name):
360
if name == '_max_stable':
361
self._set_max_stable()
362
return deepcopy(self.__dict__[name])
363
if name == '_max_stable_div':
364
self._set_max_stable_div()
365
return deepcopy(self.__dict__[name])
366
elif name == '_out_degrees':
367
self._set_out_degrees()
368
return deepcopy(self.__dict__[name])
369
elif name == '_in_degrees':
370
self._set_in_degrees()
371
return deepcopy(self.__dict__[name])
372
elif name == '_burning_config' or name == '_burning_script':
373
self._set_burning_config()
374
return deepcopy(self.__dict__[name])
375
elif name == '_identity':
376
self._set_identity()
377
return deepcopy(self.__dict__[name])
378
elif name == '_recurrents':
379
self._set_recurrents()
380
return deepcopy(self.__dict__[name])
381
elif name == '_min_recurrents':
382
self._set_min_recurrents()
383
return deepcopy(self.__dict__[name])
384
elif name == '_superstables':
385
self._set_superstables()
386
return deepcopy(self.__dict__[name])
387
elif name == '_group_order':
388
self.__dict__[name] = det(self._reduced_laplacian.dense_matrix())
389
return self.__dict__[name]
390
elif name == '_invariant_factors':
391
self._set_invariant_factors()
392
return deepcopy(self.__dict__[name])
393
elif name == '_betti_complexes':
394
self._set_betti_complexes()
395
return deepcopy(self.__dict__[name])
396
elif (name == '_postulation' or name == '_h_vector'
397
or name == '_hilbert_function'):
398
self._set_hilbert_function()
399
return deepcopy(self.__dict__[name])
400
elif (name == '_ring' or name == '_unsaturated_ideal'):
401
self._set_ring()
402
return self.__dict__[name]
403
elif name == '_ideal':
404
self._set_ideal()
405
return self.__dict__[name]
406
elif (name == '_resolution' or name == '_betti' or name ==
407
'_singular_resolution'):
408
self._set_resolution()
409
return self.__dict__[name]
410
elif name == '_groebner':
411
self._set_groebner()
412
return self.__dict__[name]
413
elif name == '_points':
414
self._set_points()
415
return self.__dict__[name]
416
else:
417
raise AttributeError, name
418
419
def version(self):
420
r"""
421
The version number of Sage Sandpiles
422
423
INPUT:
424
425
None
426
427
OUTPUT:
428
429
string
430
431
432
EXAMPLES::
433
434
sage: S = sandlib('generic')
435
sage: S.version()
436
Sage Sandpiles Version 2.3
437
"""
438
print 'Sage Sandpiles Version 2.3'
439
440
def show(self,**kwds):
441
r"""
442
Draws the graph.
443
444
INPUT:
445
446
``kwds`` - arguments passed to the show method for Graph or DiGraph
447
448
OUTPUT:
449
450
None
451
452
EXAMPLES::
453
454
sage: S = sandlib('generic')
455
sage: S.show()
456
sage: S.show(graph_border=True, edge_labels=True)
457
"""
458
459
if self.is_undirected():
460
Graph(self).show(**kwds)
461
else:
462
DiGraph(self).show(**kwds)
463
464
def show3d(self,**kwds):
465
r"""
466
Draws the graph.
467
468
INPUT:
469
470
``kwds`` - arguments passed to the show method for Graph or DiGraph
471
472
OUTPUT:
473
474
None
475
476
EXAMPLES::
477
478
sage: S = sandlib('generic')
479
sage: S.show3d()
480
"""
481
482
if self.is_undirected():
483
Graph(self).show3d(**kwds)
484
else:
485
DiGraph(self).show3d(**kwds)
486
487
def dict(self):
488
r"""
489
A dictionary of dictionaries representing a directed graph.
490
491
INPUT:
492
493
None
494
495
OUTPUT:
496
497
dict
498
499
500
EXAMPLES::
501
502
sage: G = sandlib('generic')
503
sage: G.dict()
504
{0: {},
505
1: {0: 1, 3: 1, 4: 1},
506
2: {0: 1, 3: 1, 5: 1},
507
3: {2: 1, 5: 1},
508
4: {1: 1, 3: 1},
509
5: {2: 1, 3: 1}}
510
sage: G.sink()
511
0
512
"""
513
return deepcopy(self._dict)
514
515
def sink(self):
516
r"""
517
The identifier for the sink vertex.
518
519
INPUT:
520
521
None
522
523
OUTPUT:
524
525
Object (name for the sink vertex)
526
527
EXAMPLES::
528
529
sage: G = sandlib('generic')
530
sage: G.sink()
531
0
532
sage: H = grid_sandpile(2,2)
533
sage: H.sink()
534
'sink'
535
sage: type(H.sink())
536
<type 'str'>
537
"""
538
return self._sink
539
540
def laplacian(self):
541
r"""
542
The Laplacian matrix of the graph.
543
544
INPUT:
545
546
None
547
548
OUTPUT:
549
550
matrix
551
552
553
EXAMPLES::
554
555
sage: G = sandlib('generic')
556
sage: G.laplacian()
557
[ 0 0 0 0 0 0]
558
[-1 3 0 -1 -1 0]
559
[-1 0 3 -1 0 -1]
560
[ 0 0 -1 2 0 -1]
561
[ 0 -1 0 -1 2 0]
562
[ 0 0 -1 -1 0 2]
563
564
NOTES::
565
566
The function ``laplacian_matrix`` should be avoided. It returns the
567
indegree version of the laplacian.
568
"""
569
return deepcopy(self._laplacian)
570
571
def reduced_laplacian(self):
572
r"""
573
The reduced Laplacian matrix of the graph.
574
575
INPUT:
576
577
None
578
579
OUTPUT:
580
581
matrix
582
583
584
EXAMPLES::
585
586
sage: G = sandlib('generic')
587
sage: G.laplacian()
588
[ 0 0 0 0 0 0]
589
[-1 3 0 -1 -1 0]
590
[-1 0 3 -1 0 -1]
591
[ 0 0 -1 2 0 -1]
592
[ 0 -1 0 -1 2 0]
593
[ 0 0 -1 -1 0 2]
594
sage: G.reduced_laplacian()
595
[ 3 0 -1 -1 0]
596
[ 0 3 -1 0 -1]
597
[ 0 -1 2 0 -1]
598
[-1 0 -1 2 0]
599
[ 0 -1 -1 0 2]
600
601
NOTES:
602
603
This is the Laplacian matrix with the row and column indexed by the
604
sink vertex removed.
605
"""
606
return deepcopy(self._reduced_laplacian)
607
608
def group_order(self):
609
r"""
610
The size of the sandpile group.
611
612
INPUT:
613
614
None
615
616
OUTPUT:
617
618
int
619
620
EXAMPLES::
621
622
sage: S = sandlib('generic')
623
sage: S.group_order()
624
15
625
"""
626
return self._group_order
627
628
def _set_max_stable(self):
629
r"""
630
Initialize the variable holding the maximal stable configuration.
631
632
INPUT:
633
634
None
635
636
OUTPUT:
637
638
NONE
639
640
EXAMPLES::
641
642
sage: S = sandlib('generic')
643
sage: S._set_max_stable()
644
"""
645
m = {}
646
for v in self._nonsink_vertices:
647
m[v] = self.out_degree(v)-1
648
self._max_stable = SandpileConfig(self,m)
649
650
def max_stable(self):
651
r"""
652
The maximal stable configuration.
653
654
INPUT:
655
656
None
657
658
OUTPUT:
659
660
SandpileConfig (the maximal stable configuration)
661
662
663
EXAMPLES::
664
665
sage: S = sandlib('generic')
666
sage: S.max_stable()
667
{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}
668
"""
669
return deepcopy(self._max_stable)
670
671
def _set_max_stable_div(self):
672
r"""
673
Initialize the variable holding the maximal stable divisor.
674
675
INPUT:
676
677
None
678
679
OUTPUT:
680
681
NONE
682
683
EXAMPLES::
684
685
sage: S = sandlib('generic')
686
sage: S._set_max_stable_div()
687
"""
688
m = {}
689
for v in self.vertices():
690
m[v] = self.out_degree(v)-1
691
self._max_stable_div = SandpileDivisor(self,m)
692
693
def max_stable_div(self):
694
r"""
695
The maximal stable divisor.
696
697
INPUT:
698
699
SandpileDivisor
700
701
OUTPUT:
702
703
SandpileDivisor (the maximal stable divisor)
704
705
706
EXAMPLES::
707
708
sage: S = sandlib('generic')
709
sage: S.max_stable_div()
710
{0: -1, 1: 2, 2: 2, 3: 1, 4: 1, 5: 1}
711
sage: S.out_degree()
712
{0: 0, 1: 3, 2: 3, 3: 2, 4: 2, 5: 2}
713
"""
714
return deepcopy(self._max_stable_div)
715
716
def _set_out_degrees(self):
717
r"""
718
Initialize the variable holding the out-degrees.
719
720
INPUT:
721
722
None
723
724
OUTPUT:
725
726
NONE
727
728
EXAMPLES::
729
730
sage: S = sandlib('generic')
731
sage: S._set_out_degrees()
732
"""
733
self._out_degrees = dict(self.zero_div())
734
for v in self.vertices():
735
self._out_degrees[v] = 0
736
for e in self.edges_incident(v):
737
self._out_degrees[v] += e[2]
738
739
def out_degree(self, v=None):
740
r"""
741
The out-degree of a vertex or a list of all out-degrees.
742
743
INPUT:
744
745
``v`` (optional) - vertex name
746
747
OUTPUT:
748
749
integer or dict
750
751
EXAMPLES::
752
753
sage: S = sandlib('generic')
754
sage: S.out_degree(2)
755
3
756
sage: S.out_degree()
757
{0: 0, 1: 3, 2: 3, 3: 2, 4: 2, 5: 2}
758
"""
759
if not v is None:
760
return self._out_degrees[v]
761
else:
762
return self._out_degrees
763
764
def _set_in_degrees(self):
765
"""
766
Initialize the variable holding the in-degrees.
767
768
INPUT:
769
770
None
771
772
OUTPUT:
773
774
NONE
775
776
EXAMPLES::
777
778
sage: S = sandlib('generic')
779
sage: S._set_in_degrees()
780
"""
781
self._in_degrees = dict(self.zero_div())
782
for e in self.edges():
783
self._in_degrees[e[1]] += e[2]
784
785
def in_degree(self, v=None):
786
r"""
787
The in-degree of a vertex or a list of all in-degrees.
788
789
INPUT:
790
791
``v`` - vertex name or None
792
793
OUTPUT:
794
795
integer or dict
796
797
EXAMPLES::
798
799
sage: S = sandlib('generic')
800
sage: S.in_degree(2)
801
2
802
sage: S.in_degree()
803
{0: 2, 1: 1, 2: 2, 3: 4, 4: 1, 5: 2}
804
"""
805
if not v is None:
806
return self._in_degrees[v]
807
else:
808
return self._in_degrees
809
810
def _set_burning_config(self):
811
r"""
812
Calculate the minimal burning configuration and its corresponding
813
script.
814
815
EXAMPLES::
816
817
sage: g = {0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1}, \
818
3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}}
819
sage: S = Sandpile(g,0)
820
sage: S._set_burning_config()
821
"""
822
# TODO: Cythonize!
823
d = self._reduced_laplacian.nrows()
824
burn = sum(self._reduced_laplacian)
825
script=[1]*d # d 1s
826
done = False
827
while not done:
828
bad = -1
829
for i in range(d):
830
if burn[i] < 0:
831
bad = i
832
break
833
if bad == -1:
834
done = True
835
else:
836
burn += self._reduced_laplacian[bad]
837
script[bad]+=1
838
b = iter(burn)
839
s = iter(script)
840
bc = {} # burning config
841
bs = {} # burning script
842
for v in self._nonsink_vertices:
843
bc[v] = b.next()
844
bs[v] = s.next()
845
self._burning_config = SandpileConfig(self,bc)
846
self._burning_script = SandpileConfig(self,bs)
847
848
def burning_config(self):
849
r"""
850
A minimal burning configuration.
851
852
INPUT:
853
854
None
855
856
OUTPUT:
857
858
dict (configuration)
859
860
EXAMPLES::
861
862
sage: g = {0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1}, \
863
3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}}
864
sage: S = Sandpile(g,0)
865
sage: S.burning_config()
866
{1: 2, 2: 0, 3: 1, 4: 1, 5: 0}
867
sage: S.burning_config().values()
868
[2, 0, 1, 1, 0]
869
sage: S.burning_script()
870
{1: 1, 2: 3, 3: 5, 4: 1, 5: 4}
871
sage: script = S.burning_script().values()
872
sage: script
873
[1, 3, 5, 1, 4]
874
sage: matrix(script)*S.reduced_laplacian()
875
[2 0 1 1 0]
876
877
NOTES:
878
879
The burning configuration and script are computed using a modified
880
version of Speer's script algorithm. This is a generalization to
881
directed multigraphs of Dhar's burning algorithm.
882
883
A *burning configuration* is a nonnegative integer-linear
884
combination of the rows of the reduced Laplacian matrix having
885
nonnegative entries and such that every vertex has a path from some
886
vertex in its support. The corresponding *burning script* gives
887
the integer-linear combination needed to obtain the burning
888
configuration. So if `b` is the burning configuration, `\sigma` is its
889
script, and `\tilde{L}` is the reduced Laplacian, then `\sigma\cdot
890
\tilde{L} = b`. The *minimal burning configuration* is the one
891
with the minimal script (its components are no larger than the
892
components of any other script
893
for a burning configuration).
894
895
The following are equivalent for a configuration `c` with burning
896
configuration `b` having script `\sigma`:
897
898
- `c` is recurrent;
899
- `c+b` stabilizes to `c`;
900
- the firing vector for the stabilization of `c+b` is `\sigma`.
901
"""
902
return deepcopy(self._burning_config)
903
904
def burning_script(self):
905
r"""
906
A script for the minimal burning configuration.
907
908
INPUT:
909
910
None
911
912
OUTPUT:
913
914
dict
915
916
EXAMPLES::
917
918
sage: g = {0:{},1:{0:1,3:1,4:1},2:{0:1,3:1,5:1},\
919
3:{2:1,5:1},4:{1:1,3:1},5:{2:1,3:1}}
920
sage: S = Sandpile(g,0)
921
sage: S.burning_config()
922
{1: 2, 2: 0, 3: 1, 4: 1, 5: 0}
923
sage: S.burning_config().values()
924
[2, 0, 1, 1, 0]
925
sage: S.burning_script()
926
{1: 1, 2: 3, 3: 5, 4: 1, 5: 4}
927
sage: script = S.burning_script().values()
928
sage: script
929
[1, 3, 5, 1, 4]
930
sage: matrix(script)*S.reduced_laplacian()
931
[2 0 1 1 0]
932
933
NOTES:
934
935
The burning configuration and script are computed using a modified
936
version of Speer's script algorithm. This is a generalization to
937
directed multigraphs of Dhar's burning algorithm.
938
939
A *burning configuration* is a nonnegative integer-linear
940
combination of the rows of the reduced Laplacian matrix having
941
nonnegative entries and such that every vertex has a path from some
942
vertex in its support. The corresponding *burning script* gives the
943
integer-linear combination needed to obtain the burning configuration.
944
So if `b` is the burning configuration, `s` is its script, and
945
`L_{\mathrm{red}}` is the reduced Laplacian, then `s\cdot
946
L_{\mathrm{red}}= b`. The *minimal burning configuration* is the one
947
with the minimal script (its components are no larger than the
948
components of any other script
949
for a burning configuration).
950
951
The following are equivalent for a configuration `c` with burning
952
configuration `b` having script `s`:
953
954
- `c` is recurrent;
955
- `c+b` stabilizes to `c`;
956
- the firing vector for the stabilization of `c+b` is `s`.
957
"""
958
return deepcopy(self._burning_script)
959
960
def nonsink_vertices(self):
961
r"""
962
The names of the nonsink vertices.
963
964
INPUT:
965
966
None
967
968
OUTPUT:
969
970
None
971
972
EXAMPLES::
973
974
sage: S = sandlib('generic')
975
sage: S.nonsink_vertices()
976
[1, 2, 3, 4, 5]
977
"""
978
return self._nonsink_vertices
979
980
def all_k_config(self,k):
981
r"""
982
The configuration with all values set to k.
983
984
INPUT:
985
986
``k`` - integer
987
988
OUTPUT:
989
990
SandpileConfig
991
992
EXAMPLES::
993
994
sage: S = sandlib('generic')
995
sage: S.all_k_config(7)
996
{1: 7, 2: 7, 3: 7, 4: 7, 5: 7}
997
"""
998
return SandpileConfig(self,[k]*(self.num_verts()-1))
999
1000
def zero_config(self):
1001
r"""
1002
The all-zero configuration.
1003
1004
INPUT:
1005
1006
None
1007
1008
OUTPUT:
1009
1010
SandpileConfig
1011
1012
EXAMPLES::
1013
1014
sage: S = sandlib('generic')
1015
sage: S.zero_config()
1016
{1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
1017
"""
1018
return self.all_k_config(0)
1019
1020
# TODO: cythonize stabilization!
1021
# The following would presumably be moved to the SandpileConfig class
1022
#def new_stabilize(self, config):
1023
# r"""
1024
# Stabilize \code{config}, returning \code{[out_config, firing_vector]},
1025
# where \code{out_config} is the modified configuration.
1026
# """
1027
# c, f = cython_stabilize(config, self.reduced_laplacian(),
1028
# self.out_degree(), self.nonsink_vertices())
1029
# self._config = c
1030
# return [c, f]
1031
1032
def _set_identity(self):
1033
r"""
1034
Computes ``_identity``, the variable holding the identity configuration
1035
of the sandpile group, when ``identity()`` is first called by a user.
1036
1037
INPUT:
1038
1039
None
1040
1041
OUTPUT:
1042
1043
None
1044
1045
EXAMPLES::
1046
1047
sage: S = sandlib('generic')
1048
sage: S._set_identity()
1049
"""
1050
m = self._max_stable
1051
self._identity = (m&m).dualize()&m
1052
1053
def identity(self):
1054
r"""
1055
The identity configuration.
1056
1057
INPUT:
1058
1059
None
1060
1061
OUTPUT:
1062
1063
dict (the identity configuration)
1064
1065
EXAMPLES::
1066
1067
sage: S = sandlib('generic')
1068
sage: e = S.identity()
1069
sage: x = e & S.max_stable() # stable addition
1070
sage: x
1071
{1: 2, 2: 2, 3: 1, 4: 1, 5: 1}
1072
sage: x == S.max_stable()
1073
True
1074
"""
1075
return deepcopy(self._identity)
1076
1077
def _set_recurrents(self):
1078
"""
1079
Computes ``_recurrents``, the variable holding the list of recurrent
1080
configurations, when ``recurrents()`` is first called by a user.
1081
1082
INPUT:
1083
1084
None
1085
1086
OUTPUT:
1087
1088
None
1089
1090
EXAMPLES::
1091
1092
sage: S = sandlib('generic')
1093
sage: S._set_recurrents()
1094
"""
1095
self._recurrents = []
1096
active = [self._max_stable]
1097
while active != []:
1098
c = active.pop()
1099
self._recurrents.append(c)
1100
for v in self._nonsink_vertices:
1101
cnext = deepcopy(c)
1102
cnext[v] += 1
1103
cnext = ~cnext
1104
if (cnext not in active) and (cnext not in self._recurrents):
1105
active.append(cnext)
1106
1107
def recurrents(self, verbose=True):
1108
r"""
1109
The list of recurrent configurations. If ``verbose`` is
1110
``False``, the configurations are converted to lists of
1111
integers.
1112
1113
INPUT:
1114
1115
``verbose`` (optional) - boolean
1116
1117
OUTPUT:
1118
1119
list (of recurrent configurations)
1120
1121
1122
EXAMPLES::
1123
1124
sage: S = sandlib('generic')
1125
sage: S.recurrents()
1126
[{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}]
1127
sage: S.recurrents(verbose=False)
1128
[[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]]
1129
"""
1130
if verbose:
1131
return deepcopy(self._recurrents)
1132
else:
1133
return [r.values() for r in self._recurrents]
1134
1135
def _set_superstables(self):
1136
r"""
1137
Computes ``_superstables``, the variable holding the list of superstable
1138
configurations, when ``superstables()`` is first called by a user.
1139
1140
INPUT:
1141
1142
None
1143
1144
OUTPUT:
1145
1146
None
1147
1148
EXAMPLES::
1149
1150
sage: S = sandlib('generic')
1151
sage: S._set_superstables()
1152
"""
1153
self._superstables = [c.dualize() for c in self._recurrents]
1154
1155
def superstables(self, verbose=True):
1156
r"""
1157
The list of superstable configurations as dictionaries if
1158
``verbose`` is ``True``, otherwise as lists of integers. The
1159
superstables are also known as `G`-parking functions.
1160
1161
INPUT:
1162
1163
``verbose`` (optional) - boolean
1164
1165
OUTPUT:
1166
1167
list (of superstable elements)
1168
1169
1170
EXAMPLES::
1171
1172
sage: S = sandlib('generic')
1173
sage: S.superstables()
1174
[{1: 0, 2: 0, 3: 0, 4: 0, 5: 0},
1175
{1: 0, 2: 0, 3: 1, 4: 0, 5: 0},
1176
{1: 2, 2: 0, 3: 0, 4: 0, 5: 1},
1177
{1: 2, 2: 0, 3: 0, 4: 0, 5: 0},
1178
{1: 1, 2: 0, 3: 0, 4: 0, 5: 0},
1179
{1: 1, 2: 0, 3: 1, 4: 0, 5: 0},
1180
{1: 0, 2: 0, 3: 0, 4: 1, 5: 0},
1181
{1: 0, 2: 0, 3: 1, 4: 1, 5: 0},
1182
{1: 0, 2: 0, 3: 0, 4: 1, 5: 1},
1183
{1: 1, 2: 0, 3: 0, 4: 0, 5: 1},
1184
{1: 1, 2: 0, 3: 0, 4: 1, 5: 1},
1185
{1: 1, 2: 0, 3: 0, 4: 1, 5: 0},
1186
{1: 2, 2: 0, 3: 1, 4: 0, 5: 0},
1187
{1: 0, 2: 0, 3: 0, 4: 0, 5: 1},
1188
{1: 1, 2: 0, 3: 1, 4: 1, 5: 0}]
1189
sage: S.superstables(False)
1190
[[0, 0, 0, 0, 0],
1191
[0, 0, 1, 0, 0],
1192
[2, 0, 0, 0, 1],
1193
[2, 0, 0, 0, 0],
1194
[1, 0, 0, 0, 0],
1195
[1, 0, 1, 0, 0],
1196
[0, 0, 0, 1, 0],
1197
[0, 0, 1, 1, 0],
1198
[0, 0, 0, 1, 1],
1199
[1, 0, 0, 0, 1],
1200
[1, 0, 0, 1, 1],
1201
[1, 0, 0, 1, 0],
1202
[2, 0, 1, 0, 0],
1203
[0, 0, 0, 0, 1],
1204
[1, 0, 1, 1, 0]]
1205
"""
1206
if verbose:
1207
return deepcopy(self._superstables)
1208
else:
1209
verts = self.nonsink_vertices()
1210
return [s.values() for s in self._superstables]
1211
1212
def is_undirected(self):
1213
r"""
1214
``True`` if ``(u,v)`` is and edge if and only if ``(v,u)`` is an
1215
edges, each edge with the same weight.
1216
1217
INPUT:
1218
1219
None
1220
1221
OUTPUT:
1222
1223
boolean
1224
1225
EXAMPLES::
1226
1227
sage: complete_sandpile(4).is_undirected()
1228
True
1229
sage: sandlib('gor').is_undirected()
1230
False
1231
"""
1232
return self.laplacian().is_symmetric()
1233
1234
def _set_min_recurrents(self):
1235
r"""
1236
Computes the minimal recurrent elements. If the underlying graph is
1237
undirected, these are the recurrent elements of least degree.
1238
1239
INPUT:
1240
1241
None
1242
1243
OUTPUT:
1244
1245
None
1246
1247
EXAMPLES::
1248
1249
sage: complete_sandpile(4)._set_min_recurrents()
1250
"""
1251
if self.is_undirected():
1252
m = min([r.deg() for r in self.recurrents()])
1253
rec = [r for r in self.recurrents() if r.deg()==m]
1254
else:
1255
rec = self.recurrents()
1256
for r in self.recurrents():
1257
if exists(rec, lambda x: r>x)[0]:
1258
rec.remove(r)
1259
self._min_recurrents = rec
1260
1261
def min_recurrents(self, verbose=True):
1262
r"""
1263
The minimal recurrent elements. If the underlying graph is
1264
undirected, these are the recurrent elements of least degree. If
1265
``verbose is ``False``, the configurations are converted to lists of
1266
integers.
1267
1268
INPUT:
1269
1270
``verbose`` (optional) - boolean
1271
1272
OUTPUT:
1273
1274
list of SandpileConfig
1275
1276
EXAMPLES::
1277
1278
sage: S=sandlib('riemann-roch2')
1279
sage: S.min_recurrents()
1280
[{1: 0, 2: 0, 3: 1}, {1: 1, 2: 1, 3: 0}]
1281
sage: S.min_recurrents(False)
1282
[[0, 0, 1], [1, 1, 0]]
1283
sage: S.recurrents(False)
1284
[[1, 1, 2],
1285
[0, 1, 1],
1286
[0, 1, 2],
1287
[1, 0, 1],
1288
[1, 0, 2],
1289
[0, 0, 2],
1290
[1, 1, 1],
1291
[0, 0, 1],
1292
[1, 1, 0]]
1293
sage: [i.deg() for i in S.recurrents()]
1294
[4, 2, 3, 2, 3, 2, 3, 1, 2]
1295
"""
1296
if verbose:
1297
return deepcopy(self._min_recurrents)
1298
else:
1299
return [r.values() for r in self._min_recurrents]
1300
1301
def max_superstables(self, verbose=True):
1302
r"""
1303
The maximal superstable configurations. If the underlying graph is
1304
undirected, these are the superstables of highest degree. If
1305
``verbose`` is ``False``, the configurations are converted to lists of
1306
integers.
1307
1308
INPUT:
1309
1310
``verbose`` (optional) - boolean
1311
1312
OUTPUT:
1313
1314
list (of maximal superstables)
1315
1316
EXAMPLES:
1317
1318
sage: S=sandlib('riemann-roch2')
1319
sage: S.max_superstables()
1320
[{1: 1, 2: 1, 3: 1}, {1: 0, 2: 0, 3: 2}]
1321
sage: S.superstables(False)
1322
[[0, 0, 0],
1323
[1, 0, 1],
1324
[1, 0, 0],
1325
[0, 1, 1],
1326
[0, 1, 0],
1327
[1, 1, 0],
1328
[0, 0, 1],
1329
[1, 1, 1],
1330
[0, 0, 2]]
1331
sage: S.h_vector()
1332
[1, 3, 4, 1]
1333
"""
1334
result = [r.dualize() for r in self.min_recurrents()]
1335
if verbose:
1336
return result
1337
else:
1338
return [r.values() for r in result]
1339
1340
1341
def nonspecial_divisors(self, verbose=True):
1342
r"""
1343
The nonspecial divisors: those divisors of degree ``g-1`` with
1344
empty linear system. The term is only defined for undirected graphs.
1345
Here, ``g = |E| - |V| + 1`` is the genus of the graph. If ``verbose``
1346
is ``False``, the divisors are converted to lists of integers.
1347
1348
INPUT:
1349
1350
``verbose`` (optional) - boolean
1351
1352
OUTPUT:
1353
1354
list (of divisors)
1355
1356
EXAMPLES::
1357
1358
sage: S = complete_sandpile(4)
1359
sage: ns = S.nonspecial_divisors() # optional - 4ti2
1360
sage: D = ns[0] # optional - 4ti2
1361
sage: D.values() # optional - 4ti2
1362
[-1, 1, 0, 2]
1363
sage: D.deg() # optional - 4ti2
1364
2
1365
sage: [i.effective_div() for i in ns] # optional - 4ti2
1366
[[], [], [], [], [], []]
1367
"""
1368
if self.is_undirected():
1369
result = []
1370
for s in self.max_superstables():
1371
D = dict(s)
1372
D[self._sink] = -1
1373
D = SandpileDivisor(self, D)
1374
result.append(D)
1375
if verbose:
1376
return result
1377
else:
1378
return [r.values() for r in result]
1379
else:
1380
raise UserWarning, "The underlying graph must be undirected."
1381
1382
def canonical_divisor(self):
1383
r"""
1384
The canonical divisor: the divisor ``deg(v)-2`` grains of sand
1385
on each vertex. Only for undirected graphs.
1386
1387
INPUT:
1388
1389
None
1390
1391
OUTPUT:
1392
1393
SandpileDivisor
1394
1395
EXAMPLES::
1396
1397
sage: S = complete_sandpile(4)
1398
sage: S.canonical_divisor()
1399
{0: 1, 1: 1, 2: 1, 3: 1}
1400
"""
1401
if self.is_undirected():
1402
return SandpileDivisor(self,[self.out_degree(v)-2 for v in self.vertices()])
1403
else:
1404
raise UserWarning, "Only for undirected graphs."
1405
1406
def _set_invariant_factors(self):
1407
r"""
1408
Computes the variable holding the elementary divisors of the sandpile
1409
group when ``invariant_factors()`` is first called by the user.
1410
1411
INPUT:
1412
1413
None
1414
1415
OUTPUT:
1416
1417
None
1418
1419
EXAMPLES::
1420
1421
sage: S = sandlib('generic')
1422
sage: S._set_invariant_factors()
1423
"""
1424
# Sage seems to have issues with computing the Smith normal form and
1425
# elementary divisors of a sparse matrix, so we have to convert:
1426
A = self.reduced_laplacian().dense_matrix()
1427
self._invariant_factors = A.elementary_divisors()
1428
1429
def invariant_factors(self):
1430
r"""
1431
The invariant factors of the sandpile group (a finite
1432
abelian group).
1433
1434
INPUT:
1435
1436
None
1437
1438
OUTPUT:
1439
1440
list of integers
1441
1442
EXAMPLES::
1443
1444
sage: S = sandlib('generic')
1445
sage: S.invariant_factors()
1446
[1, 1, 1, 1, 15]
1447
"""
1448
return deepcopy(self._invariant_factors)
1449
1450
elementary_divisors = deprecated_function_alias(10618, invariant_factors)
1451
1452
def _set_hilbert_function(self):
1453
"""
1454
Computes the variables holding the Hilbert function of the homogeneous
1455
homogeneous sandpile ideal, the first differences of the Hilbert
1456
function, and the postulation number for the zero-set of the sandpile
1457
ideal when any one of these is called by the user.
1458
1459
INPUT:
1460
1461
None
1462
1463
OUTPUT:
1464
1465
None
1466
1467
EXAMPLES::
1468
1469
sage: S = sandlib('generic')
1470
sage: S._set_hilbert_function()
1471
"""
1472
v = [i.deg() for i in self._superstables]
1473
self._postulation = max(v)
1474
self._h_vector = [v.count(i) for i in range(self._postulation+1)]
1475
self._hilbert_function = [1]
1476
for i in range(self._postulation):
1477
self._hilbert_function.append(self._hilbert_function[i]
1478
+self._h_vector[i+1])
1479
1480
def h_vector(self):
1481
r"""
1482
The first differences of the Hilbert function of the homogeneous
1483
sandpile ideal. It lists the number of superstable configurations in
1484
each degree.
1485
1486
INPUT:
1487
1488
None
1489
1490
OUTPUT:
1491
1492
list of nonnegative integers
1493
1494
1495
EXAMPLES::
1496
1497
sage: S = sandlib('generic')
1498
sage: S.hilbert_function()
1499
[1, 5, 11, 15]
1500
sage: S.h_vector()
1501
[1, 4, 6, 4]
1502
"""
1503
return deepcopy(self._h_vector)
1504
1505
def hilbert_function(self):
1506
r"""
1507
The Hilbert function of the homogeneous sandpile ideal.
1508
1509
INPUT:
1510
1511
None
1512
1513
OUTPUT:
1514
1515
list of nonnegative integers
1516
1517
EXAMPLES::
1518
1519
sage: S = sandlib('generic')
1520
sage: S.hilbert_function()
1521
[1, 5, 11, 15]
1522
"""
1523
return deepcopy(self._hilbert_function)
1524
1525
def postulation(self):
1526
r"""
1527
The postulation number of the sandpile ideal. This is the
1528
largest weight of a superstable configuration of the graph.
1529
1530
INPUT:
1531
1532
None
1533
1534
OUTPUT:
1535
1536
nonnegative integer
1537
1538
EXAMPLES::
1539
1540
sage: S = sandlib('generic')
1541
sage: S.postulation()
1542
3
1543
"""
1544
return self._postulation
1545
1546
def reorder_vertices(self):
1547
r"""
1548
Create a copy of the sandpile but with the vertices ordered according
1549
to their distance from the sink, from greatest to least.
1550
1551
INPUT:
1552
1553
None
1554
1555
OUTPUT:
1556
1557
Sandpile
1558
1559
EXAMPLES::
1560
sage: S = sandlib('kite')
1561
sage: S.dict()
1562
{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}}
1563
sage: T = S.reorder_vertices()
1564
sage: T.dict()
1565
{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: {}}
1566
"""
1567
1568
# first order the vertices according to their distance from the sink
1569
verts = self.vertices()
1570
verts = sorted(verts, self._compare_vertices)
1571
verts.reverse()
1572
perm = {}
1573
for i in range(len(verts)):
1574
perm[verts[i]]=i
1575
old = self.dict()
1576
new = {}
1577
for i in old:
1578
entry = {}
1579
for j in old[i]:
1580
entry[perm[j]]=old[i][j]
1581
new[perm[i]] = entry
1582
return Sandpile(new,len(verts)-1)
1583
1584
1585
#################### Functions for divisors #####################
1586
1587
def all_k_div(self,k):
1588
r"""
1589
The divisor with all values set to k.
1590
1591
INPUT:
1592
1593
``k`` - integer
1594
1595
OUTPUT:
1596
1597
SandpileDivisor
1598
1599
EXAMPLES::
1600
1601
sage: S = sandlib('generic')
1602
sage: S.all_k_div(7)
1603
{0: 7, 1: 7, 2: 7, 3: 7, 4: 7, 5: 7}
1604
"""
1605
return SandpileDivisor(self,[k]*self.num_verts())
1606
1607
def zero_div(self):
1608
r"""
1609
The all-zero divisor.
1610
1611
INPUT:
1612
1613
None
1614
1615
OUTPUT:
1616
1617
SandpileDivisor
1618
1619
EXAMPLES::
1620
1621
sage: S = sandlib('generic')
1622
sage: S.zero_div()
1623
{0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
1624
"""
1625
return self.all_k_div(0)
1626
1627
# FIX: save this in the __dict__
1628
def _set_betti_complexes(self):
1629
r"""
1630
Compute the value return by the ``betti_complexes`` method.
1631
1632
INPUT:
1633
1634
None
1635
1636
OUTPUT:
1637
1638
None
1639
1640
EXAMPLES::
1641
1642
sage: S = Sandpile({0:{},1:{0: 1, 2: 1, 3: 4},2:{3: 5},3:{1: 1, 2: 1}},0)
1643
sage: S._set_betti_complexes() # optional - 4ti2
1644
"""
1645
results = []
1646
verts = self.vertices()
1647
r = self.recurrents()
1648
for D in r:
1649
d = D.deg()
1650
# change D to a dict since SandpileConfig will not allow adding a key
1651
D = dict(D)
1652
D[self.sink()] = -d
1653
D = SandpileDivisor(self,D)
1654
test = True
1655
while test:
1656
D[self.sink()] += 1
1657
complex = D.Dcomplex()
1658
if sum(complex.betti().values()) > 1: # change from 0 to 1
1659
results.append([deepcopy(D), complex])
1660
if len(complex.maximal_faces()) == 1 and list(complex.maximal_faces()[0]) == verts:
1661
test = False
1662
self._betti_complexes = results
1663
1664
def betti_complexes(self):
1665
r"""
1666
A list of all the divisors with nonempty linear systems whose
1667
corresponding simplicial complexes have nonzero homology in some
1668
dimension. Each such divisors is returned with its corresponding
1669
simplicial complex.
1670
1671
INPUT:
1672
1673
None
1674
1675
OUTPUT:
1676
1677
list (of pairs [divisors, corresponding simplicial complex])
1678
1679
1680
EXAMPLES::
1681
1682
sage: S = Sandpile({0:{},1:{0: 1, 2: 1, 3: 4},2:{3: 5},3:{1: 1, 2: 1}},0)
1683
sage: p = S.betti_complexes() # optional - 4ti2
1684
sage: p[0] # optional - 4ti2
1685
[{0: -8, 1: 5, 2: 4, 3: 1}, Simplicial complex with vertex set (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, 0, 0], [0, -1, -1]], '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+' -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[1:-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[1:-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, 0, 0, 0, -1], [0, 0, -1, -1, 0, -2], [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: 0, 1: 0, 2: 1, 3: 1, 4: 0, 5: 0}, {0: 1, 1: 0, 2: 0, 3: 1, 4: 0, 5: 0}, {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 2}]
4206
sage: D.effective_div(False) # optional - 4ti2
4207
[[0, 0, 1, 1, 0, 0], [1, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 2]]
4208
"""
4209
if verbose:
4210
return deepcopy(self._effective_div)
4211
else:
4212
return [E.values() for E in self._effective_div]
4213
4214
def _set_r_of_D(self, verbose=False):
4215
r"""
4216
Computes ``r(D)`` and an effective divisor ``F`` such
4217
that ``|D - F|`` is empty.
4218
4219
INPUT:
4220
4221
verbose (optional) - boolean
4222
4223
OUTPUT:
4224
4225
None
4226
4227
EXAMPLES::
4228
4229
sage: S = sandlib('generic')
4230
sage: D = SandpileDivisor(S, [0,0,0,0,0,4]) # optional - 4ti2
4231
sage: D._set_r_of_D() # optional - 4ti2
4232
"""
4233
eff = self.effective_div()
4234
n = self._sandpile.num_verts()
4235
r = -1
4236
if eff == []:
4237
self._r_of_D = (r, self)
4238
return
4239
else:
4240
d = vector(self.values())
4241
# standard basis vectors
4242
e = []
4243
for i in range(n):
4244
v = vector([0]*n)
4245
v[i] += 1
4246
e.append(v)
4247
level = [vector([0]*n)]
4248
while True:
4249
r += 1
4250
if verbose:
4251
print r
4252
new_level = []
4253
for v in level:
4254
for i in range(n):
4255
w = v + e[i]
4256
if w not in new_level:
4257
new_level.append(w)
4258
C = d - w
4259
C = SandpileDivisor(self._sandpile,list(C))
4260
eff = C.effective_div()
4261
if eff == []:
4262
self._r_of_D = (r, SandpileDivisor(self._sandpile,list(w)))
4263
return
4264
level = new_level
4265
4266
def r_of_D(self, verbose=False):
4267
r"""
4268
Returns ``r(D)`` and, if ``verbose`` is ``True``, an effective divisor
4269
``F`` such that ``|D - F|`` is empty.
4270
4271
INPUT:
4272
4273
``verbose`` (optional) - boolean
4274
4275
OUTPUT:
4276
4277
integer ``r(D)`` or tuple (integer ``r(D)``, divisor ``F``)
4278
4279
EXAMPLES::
4280
4281
sage: S = sandlib('generic')
4282
sage: D = SandpileDivisor(S, [0,0,0,0,0,4]) # optional - 4ti2
4283
sage: E = D.r_of_D(True) # optional - 4ti2
4284
sage: E # optional - 4ti2
4285
(1, {0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 0})
4286
sage: F = E[1] # optional - 4ti2
4287
sage: (D - F).values() # optional - 4ti2
4288
[0, -1, 0, -1, 0, 4]
4289
sage: (D - F).effective_div() # optional - 4ti2
4290
[]
4291
sage: SandpileDivisor(S, [0,0,0,0,0,-4]).r_of_D(True) # optional - 4ti2
4292
(-1, {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: -4})
4293
"""
4294
if verbose:
4295
return self._r_of_D
4296
else:
4297
return self._r_of_D[0]
4298
4299
def support(self):
4300
r"""
4301
List of keys of the nonzero values of the divisor.
4302
4303
INPUT:
4304
4305
None
4306
4307
OUTPUT:
4308
4309
list - support of the divisor
4310
4311
4312
EXAMPLES::
4313
4314
sage: S = sandlib('generic')
4315
sage: c = S.identity()
4316
sage: c.values()
4317
[2, 2, 1, 1, 0]
4318
sage: c.support()
4319
[1, 2, 3, 4]
4320
sage: S.vertices()
4321
[0, 1, 2, 3, 4, 5]
4322
"""
4323
return [i for i in self.keys() if self[i] !=0]
4324
4325
def _set_Dcomplex(self):
4326
r"""
4327
Computes the simplicial complex determined by the supports of the
4328
linearly equivalent effective divisors.
4329
4330
INPUT:
4331
4332
None
4333
4334
OUTPUT:
4335
4336
None
4337
4338
EXAMPLES::
4339
4340
sage: S = sandlib('generic')
4341
sage: D = SandpileDivisor(S, [0,1,2,0,0,1]) # optional - 4ti2
4342
sage: D._set_Dcomplex() # optional - 4ti2
4343
"""
4344
simp = []
4345
for E in self.effective_div():
4346
supp_E = E.support()
4347
test = True
4348
for s in simp:
4349
if set(supp_E).issubset(set(s)):
4350
test = False
4351
break
4352
if test:
4353
simp.append(supp_E)
4354
result = []
4355
simp.reverse()
4356
while simp != []:
4357
supp = simp.pop()
4358
test = True
4359
for s in simp:
4360
if set(supp).issubset(set(s)):
4361
test = False
4362
break
4363
if test:
4364
result.append(supp)
4365
self._Dcomplex = SimplicialComplex(result)
4366
4367
def Dcomplex(self):
4368
r"""
4369
The simplicial complex determined by the supports of the
4370
linearly equivalent effective divisors.
4371
4372
INPUT:
4373
4374
None
4375
4376
OUTPUT:
4377
4378
simplicial complex
4379
4380
EXAMPLES::
4381
4382
sage: S = sandlib('generic')
4383
sage: p = SandpileDivisor(S, [0,1,2,0,0,1]).Dcomplex() # optional - 4ti2
4384
sage: p.homology() # optional - 4ti2
4385
{0: 0, 1: Z x Z, 2: 0, 3: 0}
4386
sage: p.f_vector() # optional - 4ti2
4387
[1, 6, 15, 9, 1]
4388
sage: p.betti() # optional - 4ti2
4389
{0: 1, 1: 2, 2: 0, 3: 0}
4390
"""
4391
return self._Dcomplex
4392
4393
def betti(self):
4394
r"""
4395
The Betti numbers for the simplicial complex associated with
4396
the divisor.
4397
4398
INPUT:
4399
4400
None
4401
4402
OUTPUT:
4403
4404
dictionary of integers
4405
4406
EXAMPLES::
4407
4408
sage: S = Sandpile(graphs.CycleGraph(3), 0)
4409
sage: D = SandpileDivisor(S, [2,0,1])
4410
sage: D.betti() # optional - 4ti2
4411
{0: 1, 1: 1}
4412
"""
4413
return self.Dcomplex().betti()
4414
4415
def add_random(self):
4416
r"""
4417
Add one grain of sand to a random vertex.
4418
4419
INPUT:
4420
4421
None
4422
4423
OUTPUT:
4424
4425
SandpileDivisor
4426
4427
EXAMPLES::
4428
4429
sage: S = sandlib('generic')
4430
sage: S.zero_div().add_random() #random
4431
{0: 0, 1: 0, 2: 0, 3: 1, 4: 0, 5: 0}
4432
"""
4433
D = dict(self)
4434
C = CombinatorialClass()
4435
C.list = lambda: self.keys()
4436
D[C.random_element()] += 1
4437
return SandpileDivisor(self._sandpile,D)
4438
4439
def is_symmetric(self, orbits):
4440
r"""
4441
This function checks if the values of the divisor are constant
4442
over the vertices in each sublist of ``orbits``.
4443
4444
INPUT:
4445
4446
- ``orbits`` - list of lists of vertices
4447
4448
OUTPUT:
4449
4450
boolean
4451
4452
EXAMPLES::
4453
4454
sage: S = sandlib('kite')
4455
sage: S.dict()
4456
{0: {},
4457
1: {0: 1, 2: 1, 3: 1},
4458
2: {1: 1, 3: 1, 4: 1},
4459
3: {1: 1, 2: 1, 4: 1},
4460
4: {2: 1, 3: 1}}
4461
sage: D = SandpileDivisor(S, [2,1, 2, 2, 3])
4462
sage: D.is_symmetric([[0,2,3]])
4463
True
4464
"""
4465
for x in orbits:
4466
if len(set([self[v] for v in x])) > 1:
4467
return False
4468
return True
4469
4470
def _set_life(self):
4471
r"""
4472
Will the sequence of divisors ``D_i`` where ``D_{i+1}`` is obtained from
4473
``D_i`` by firing all unstable vertices of ``D_i`` stabilize? If so,
4474
save the resulting cycle, otherwise save ``[]``.
4475
4476
INPUT:
4477
4478
None
4479
4480
OUTPUT:
4481
4482
None
4483
4484
EXAMPLES::
4485
4486
sage: S = complete_sandpile(4)
4487
sage: D = SandpileDivisor(S, {0: 4, 1: 3, 2: 3, 3: 2})
4488
sage: D._set_life()
4489
"""
4490
oldD = deepcopy(self)
4491
result = [oldD]
4492
while True:
4493
if oldD.unstable()==[]:
4494
self._life = []
4495
return
4496
newD = oldD.fire_unstable()
4497
if newD not in result:
4498
result.append(newD)
4499
oldD = deepcopy(newD)
4500
else:
4501
self._life = result[result.index(newD):]
4502
return
4503
4504
def is_alive(self, cycle=False):
4505
r"""
4506
Will the divisor stabilize under repeated firings of all unstable
4507
vertices? Optionally returns the resulting cycle.
4508
4509
INPUT:
4510
4511
``cycle`` (optional) - boolean
4512
4513
OUTPUT:
4514
4515
boolean or optionally, a list of SandpileDivisors
4516
4517
EXAMPLES::
4518
4519
sage: S = complete_sandpile(4)
4520
sage: D = SandpileDivisor(S, {0: 4, 1: 3, 2: 3, 3: 2})
4521
sage: D.is_alive()
4522
True
4523
sage: D.is_alive(True)
4524
[{0: 4, 1: 3, 2: 3, 3: 2}, {0: 3, 1: 2, 2: 2, 3: 5}, {0: 1, 1: 4, 2: 4, 3: 3}]
4525
"""
4526
if cycle:
4527
return self._life
4528
else:
4529
return self._life != []
4530
4531
def show(self,heights=True,directed=None,**kwds):
4532
r"""
4533
Show the divisor.
4534
4535
INPUT:
4536
4537
- ``heights`` - whether to label each vertex with the amount of sand
4538
- ``kwds`` - arguments passed to the show method for Graph
4539
- ``directed`` - whether to draw directed edges
4540
4541
OUTPUT:
4542
4543
None
4544
4545
EXAMPLES::
4546
4547
sage: S = sandlib('genus2')
4548
sage: D = SandpileDivisor(S,[1,-2,0,2])
4549
sage: D.show(graph_border=True,vertex_size=700,directed=False)
4550
"""
4551
if directed==True:
4552
T = DiGraph(self.sandpile())
4553
elif directed==False:
4554
T = Graph(self.sandpile())
4555
elif self.sandpile().is_directed():
4556
T = DiGraph(self.sandpile())
4557
else:
4558
T = Graph(self.sandpile())
4559
4560
max_height = max(self.sandpile().out_degree_sequence())
4561
if heights:
4562
a = {}
4563
for i in T.vertices():
4564
a[i] = str(i)+":"+str(T[i])
4565
T.relabel(a)
4566
T.show(**kwds)
4567
4568
#######################################
4569
######### Some test graphs ############
4570
#######################################
4571
4572
def sandlib(selector=None):
4573
r"""
4574
Returns the sandpile identified by ``selector``. If no argument is
4575
given, a description of the sandpiles in the sandlib is printed.
4576
4577
INPUT:
4578
4579
``selector`` - identifier or None
4580
4581
OUTPUT:
4582
4583
sandpile or description
4584
4585
EXAMPLES::
4586
4587
sage: sandlib()
4588
<BLANKLINE>
4589
Sandpiles in the sandlib:
4590
kite : generic undirected graphs with 5 vertices
4591
generic : generic digraph with 6 vertices
4592
genus2 : Undirected graph of genus 2
4593
ci1 : complete intersection, non-DAG but equivalent to a DAG
4594
riemann-roch1 : directed graph with postulation 9 and 3 maximal weight superstables
4595
riemann-roch2 : directed graph with a superstable not majorized by a maximal superstable
4596
gor : Gorenstein but not a complete intersection
4597
4598
4599
sage: S = sandlib('gor')
4600
sage: S.resolution()
4601
'R^1 <-- R^5 <-- R^5 <-- R^1'
4602
"""
4603
# The convention is for the sink to be zero.
4604
sandpiles = {
4605
'generic':{
4606
'description':'generic digraph with 6 vertices',
4607
'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}}
4608
},
4609
'kite':{
4610
'description':'generic undirected graphs with 5 vertices',
4611
'graph':{0:{}, 1:{0:1,2:1,3:1}, 2:{1:1,3:1,4:1}, 3:{1:1,2:1,4:1},
4612
4:{2:1,3:1}}
4613
},
4614
'riemann-roch1':{
4615
'description':'directed graph with postulation 9 and 3 maximal weight superstables',
4616
'graph':{0: {1: 3, 3: 1},
4617
1: {0: 2, 2: 2, 3: 2},
4618
2: {0: 1, 1: 1},
4619
3: {0: 3, 1: 1, 2: 1}
4620
}
4621
},
4622
'riemann-roch2':{
4623
'description':'directed graph with a superstable not majorized by a maximal superstable',
4624
'graph':{
4625
0: {},
4626
1: {0: 1, 2: 1},
4627
2: {0: 1, 3: 1},
4628
3: {0: 1, 1: 1, 2: 1}
4629
}
4630
},
4631
'gor':{
4632
'description':'Gorenstein but not a complete intersection',
4633
'graph':{
4634
0: {},
4635
1: {0:1, 2: 1, 3: 4},
4636
2: {3: 5},
4637
3: {1: 1, 2: 1}
4638
}
4639
},
4640
'ci1':{
4641
'description':'complete intersection, non-DAG but equivalent to a DAG',
4642
'graph':{0:{}, 1: {2: 2}, 2: {0: 4, 1: 1}}
4643
},
4644
'genus2':{
4645
'description':'Undirected graph of genus 2',
4646
'graph':{
4647
0:[1,2],
4648
1:[0,2,3],
4649
2:[0,1,3],
4650
3:[1,2]
4651
}
4652
},
4653
}
4654
if selector==None:
4655
print
4656
print ' Sandpiles in the sandlib:'
4657
for i in sandpiles:
4658
print ' ', i, ':', sandpiles[i]['description']
4659
print
4660
elif selector not in sandpiles.keys():
4661
print selector, 'is not in the sandlib.'
4662
else:
4663
return Sandpile(sandpiles[selector]['graph'], 0)
4664
4665
#################################################
4666
########## Some useful functions ################
4667
#################################################
4668
4669
def complete_sandpile(n):
4670
r"""
4671
The sandpile on the complete graph with n vertices.
4672
4673
INPUT:
4674
4675
``n`` - positive integer
4676
4677
OUTPUT:
4678
4679
Sandpile
4680
4681
EXAMPLES::
4682
4683
sage: K = complete_sandpile(5)
4684
sage: K.betti(verbose=False)
4685
[1, 15, 50, 60, 24]
4686
"""
4687
return Sandpile(graphs.CompleteGraph(n), 0)
4688
4689
def grid_sandpile(m,n):
4690
"""
4691
The mxn grid sandpile. Each nonsink vertex has degree 4.
4692
4693
INPUT:
4694
``m``, ``n`` - positive integers
4695
4696
OUTPUT:
4697
Sandpile with sink named ``sink``.
4698
4699
EXAMPLES::
4700
4701
sage: G = grid_sandpile(3,4)
4702
sage: G.dict()
4703
{(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': {}}
4704
sage: G.group_order()
4705
4140081
4706
sage: G.invariant_factors()
4707
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1380027]
4708
"""
4709
g = {}
4710
# corners first
4711
g[(1,1)] = {(1,2):1, (2,1):1, 'sink':2}
4712
g[(m,1)] = {(m-1,1):1, (m,2):1, 'sink':2}
4713
g[(1,n)] = {(1,n-1):1, (2,n):1, 'sink':2}
4714
g[(m,n)] = {(m-1,n):1, (m,n-1):1, 'sink':2}
4715
# top edge
4716
for col in range(2,n):
4717
g[(1,col)] = {(1,col-1):1, (1,col+1):1, (2,col):1, 'sink':1}
4718
# left edge
4719
for row in range (2,m):
4720
g[(row,1)] = {(row-1,1):1, (row+1,1):1, (row,2):1, 'sink':1}
4721
# right edge
4722
for row in range (2,m):
4723
g[(row,n)] = {(row-1,n):1, (row+1,n):1, (row,n-1):1, 'sink':1}
4724
# bottom edge
4725
for col in range(2,n):
4726
g[(m,col)] = {(m,col-1):1, (m,col+1):1, (m-1,col):1, 'sink':1}
4727
# inner vertices
4728
for row in range(2,m):
4729
for col in range(2,n):
4730
g[(row,col)] ={(row-1,col):1, (row+1,col):1, (row,col-1):1, (row,col+1):1}
4731
# the sink vertex
4732
g['sink'] = {}
4733
return Sandpile(g, 'sink')
4734
4735
def triangle_sandpile(n):
4736
r"""
4737
A triangular sandpile. Each nonsink vertex has out-degree six. The
4738
vertices on the boundary of the triangle are connected to the sink.
4739
4740
INPUT:
4741
4742
``n`` - int
4743
4744
OUTPUT:
4745
4746
Sandpile
4747
4748
EXAMPLES::
4749
4750
sage: T = triangle_sandpile(5)
4751
sage: T.group_order()
4752
135418115000
4753
"""
4754
T = {'sink':{}}
4755
for i in range(n):
4756
for j in range(n-i):
4757
T[(i,j)] = {}
4758
if i<n-j-1:
4759
T[(i,j)][(i+1,j)] = 1
4760
T[(i,j)][(i,j+1)] = 1
4761
if i>0:
4762
T[(i,j)][(i-1,j+1)] = 1
4763
T[(i,j)][(i-1,j)] = 1
4764
if j>0:
4765
T[(i,j)][(i,j-1)] = 1
4766
T[(i,j)][(i+1,j-1)] = 1
4767
d = len(T[(i,j)])
4768
if d<6:
4769
T[(i,j)]['sink'] = 6-d
4770
T = Sandpile(T,'sink')
4771
pos = {}
4772
for x in T.nonsink_vertices():
4773
coords = list(x)
4774
coords[0]+=QQ(1)/2*coords[1]
4775
pos[x] = coords
4776
pos['sink'] = (-1,-1)
4777
T.set_pos(pos)
4778
return T
4779
4780
def aztec_sandpile(n):
4781
r"""
4782
The aztec diamond graph.
4783
4784
INPUT:
4785
4786
``n`` - integer
4787
4788
OUTPUT:
4789
4790
dictionary for the aztec diamond graph
4791
4792
EXAMPLES::
4793
4794
sage: aztec_sandpile(2)
4795
{'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}}
4796
sage: Sandpile(aztec_sandpile(2),'sink').group_order()
4797
4542720
4798
4799
NOTES:
4800
4801
This is the aztec diamond graph with a sink vertex added. Boundary
4802
vertices have edges to the sink so that each vertex has degree 4.
4803
"""
4804
aztec_sandpile = {}
4805
half = QQ(1)/2
4806
for i in srange(n):
4807
for j in srange(n-i):
4808
aztec_sandpile[(half+i,half+j)] = {}
4809
aztec_sandpile[(-half-i,half+j)] = {}
4810
aztec_sandpile[(half+i,-half-j)] = {}
4811
aztec_sandpile[(-half-i,-half-j)] = {}
4812
non_sinks = aztec_sandpile.keys()
4813
aztec_sandpile['sink'] = {}
4814
for vert in non_sinks:
4815
weight = abs(vert[0]) + abs(vert[1])
4816
x = vert[0]
4817
y = vert[1]
4818
if weight < n:
4819
aztec_sandpile[vert] = {(x+1,y):1, (x,y+1):1, (x-1,y):1, (x,y-1):1}
4820
else:
4821
if (x+1,y) in aztec_sandpile.keys():
4822
aztec_sandpile[vert][(x+1,y)] = 1
4823
if (x,y+1) in aztec_sandpile.keys():
4824
aztec_sandpile[vert][(x,y+1)] = 1
4825
if (x-1,y) in aztec_sandpile.keys():
4826
aztec_sandpile[vert][(x-1,y)] = 1
4827
if (x,y-1) in aztec_sandpile.keys():
4828
aztec_sandpile[vert][(x,y-1)] = 1
4829
if len(aztec_sandpile[vert]) < 4:
4830
out_degree = 4 - len(aztec_sandpile[vert])
4831
aztec_sandpile[vert]['sink'] = out_degree
4832
aztec_sandpile['sink'][vert] = out_degree
4833
return aztec_sandpile
4834
4835
def random_digraph(num_verts, p=0.5, directed=True, weight_max=1):
4836
"""
4837
A random weighted digraph with a directed spanning tree rooted at `0`. If
4838
``directed = False``, the only difference is that if `(i,j,w)` is an edge with
4839
tail `i`, head `j`, and weight `w`, then `(j,i,w)` appears also. The result
4840
is returned as a Sage digraph.
4841
4842
INPUT:
4843
4844
- ``num_verts`` - number of vertices
4845
- ``p`` - probability edges occur
4846
- ``directed`` - True if directed
4847
- ``weight_max`` - integer maximum for random weights
4848
4849
OUTPUT:
4850
4851
random graph
4852
4853
EXAMPLES::
4854
4855
sage: g = random_digraph(6,0.2,True,3)
4856
sage: S = Sandpile(g,0)
4857
sage: S.show(edge_labels = True)
4858
4859
TESTS:
4860
4861
Check that we can construct a random digraph with the
4862
default arguments (:trac:`12181`)::
4863
4864
sage: random_digraph(5)
4865
Digraph on 5 vertices
4866
"""
4867
4868
a = digraphs.RandomDirectedGN(num_verts)
4869
b = graphs.RandomGNP(num_verts,p)
4870
a.add_edges(b.edges())
4871
if directed:
4872
c = graphs.RandomGNP(num_verts,p)
4873
# reverse the edges of c and add them in
4874
a.add_edges([(j,i,None) for i,j,k in c.edges()])
4875
else:
4876
a.add_edges([(j,i,None) for i,j,k in a.edges()])
4877
a.add_edges([(j,i,None) for i,j,k in b.edges()])
4878
# now handle the weights
4879
for i,j,k in a.edge_iterator():
4880
a.set_edge_label(i,j,ZZ.random_element(weight_max)+1)
4881
return a
4882
4883
def random_DAG(num_verts, p=0.5, weight_max=1):
4884
r"""
4885
A random directed acyclic graph with ``num_verts`` vertices.
4886
The method starts with the sink vertex and adds vertices one at a time.
4887
Each vertex is connected only to only previously defined vertices, and the
4888
probability of each possible connection is given by the argument ``p``.
4889
The weight of an edge is a random integer between ``1`` and
4890
``weight_max``.
4891
4892
INPUT:
4893
4894
- ``num_verts`` - positive integer
4895
- ``p`` - real number such that `0 < p \leq 1`
4896
- ``weight_max`` - positive integer
4897
4898
OUTPUT:
4899
4900
a dictionary, encoding the edges of a directed acyclic graph with sink `0`
4901
4902
EXAMPLES::
4903
4904
sage: d = DiGraph(random_DAG(5, .5));d
4905
Digraph on 5 vertices
4906
4907
TESTS:
4908
4909
Check that we can construct a random DAG with the
4910
default arguments (:trac:`12181`)::
4911
4912
sage: g = random_DAG(5);DiGraph(g)
4913
Digraph on 5 vertices
4914
4915
Check that bad inputs are rejected::
4916
sage: g = random_DAG(5,1.1)
4917
Traceback (most recent call last):
4918
...
4919
ValueError: The parameter p must satisfy 0 < p <= 1.
4920
sage: g = random_DAG(5,0.1,-1)
4921
Traceback (most recent call last):
4922
...
4923
ValueError: The parameter weight_max must be positive.
4924
"""
4925
if not(0 < p and p <= 1):
4926
raise ValueError("The parameter p must satisfy 0 < p <= 1.")
4927
weight_max=ZZ(weight_max)
4928
if not(0 < weight_max):
4929
raise ValueError("The parameter weight_max must be positive.")
4930
g = {0:{}}
4931
for i in range(1,num_verts):
4932
out_edges = {}
4933
while out_edges == {}:
4934
for j in range(i):
4935
if p > random():
4936
out_edges[j] = randint(1,weight_max)
4937
g[i] = out_edges
4938
return g
4939
4940
def random_tree(n,d):
4941
r"""
4942
A random undirected tree with ``n`` nodes, no node having
4943
degree higher than ``d``.
4944
4945
INPUT:
4946
4947
``n``, ``d`` - integers
4948
4949
OUTPUT:
4950
4951
Graph
4952
4953
EXAMPLES::
4954
4955
sage: T = random_tree(15,3)
4956
sage: T.show()
4957
sage: S = Sandpile(T,0)
4958
sage: U = S.reorder_vertices()
4959
sage: U.show()
4960
"""
4961
g = Graph()
4962
# active vertices
4963
active = [0]
4964
g.add_vertex(0)
4965
next_vertex = 1
4966
while g.num_verts()<n:
4967
node = randint(0,g.num_verts()-1)
4968
if g.degree(node)>d:
4969
active.remove(node)
4970
break
4971
r = randint(0,d)
4972
if r>0:
4973
for i in range(r):
4974
g.add_vertex(next_vertex)
4975
g.add_edge((node,next_vertex))
4976
active.append(next_vertex)
4977
next_vertex+=1
4978
return g
4979
4980
def glue_graphs(g,h,glue_g,glue_h):
4981
r"""
4982
Glue two graphs together.
4983
4984
INPUT:
4985
4986
- ``g``, ``h`` - dictionaries for directed multigraphs
4987
- ``glue_h``, ``glue_g`` - dictionaries for a vertex
4988
4989
OUTPUT:
4990
4991
dictionary for a directed multigraph
4992
4993
4994
EXAMPLES::
4995
4996
sage: x = {0: {}, 1: {0: 1}, 2: {0: 1, 1: 1}, 3: {0: 1, 1: 1, 2: 1}}
4997
sage: y = {0: {}, 1: {0: 2}, 2: {1: 2}, 3: {0: 1, 2: 1}}
4998
sage: glue_x = {1: 1, 3: 2}
4999
sage: glue_y = {0: 1, 1: 2, 3: 1}
5000
sage: z = glue_graphs(x,y,glue_x,glue_y)
5001
sage: z
5002
{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}}
5003
sage: S = Sandpile(z,0)
5004
sage: S.h_vector()
5005
[1, 6, 17, 31, 41, 41, 31, 17, 6, 1]
5006
sage: S.resolution()
5007
'R^1 <-- R^7 <-- R^21 <-- R^35 <-- R^35 <-- R^21 <-- R^7 <-- R^1'
5008
5009
NOTES:
5010
5011
This method makes a dictionary for a graph by combining those for
5012
``g`` and ``h``. The sink of ``g`` is replaced by a vertex that
5013
is connected to the vertices of ``g`` as specified by ``glue_g``
5014
the vertices of ``h`` as specified in ``glue_h``. The sink of the glued
5015
graph is `0`.
5016
5017
Both ``glue_g`` and ``glue_h`` are dictionaries with entries of the form
5018
``v:w`` where ``v`` is the vertex to be connected to and ``w`` is the weight
5019
of the connecting edge.
5020
"""
5021
# first find the sinks of g and h
5022
for i in g:
5023
if g[i] == {}:
5024
g_sink = i
5025
break
5026
for i in h:
5027
if h[i] == {}:
5028
h_sink = i
5029
break
5030
k = {0: {}} # the new graph dictionary, starting with the sink
5031
for i in g:
5032
if i != g_sink:
5033
new_edges = {}
5034
for j in g[i]:
5035
new_edges['x'+str(j)] = g[i][j]
5036
k['x'+str(i)] = new_edges
5037
for i in h:
5038
if i != h_sink:
5039
new_edges = {}
5040
for j in h[i]:
5041
if j == h_sink:
5042
new_edges[0] = h[i][j]
5043
else:
5044
new_edges['y'+str(j)] = h[i][j]
5045
k['y'+str(i)] = new_edges
5046
# now handle the glue vertex (old g sink)
5047
new_edges = {}
5048
for i in glue_g:
5049
new_edges['x'+str(i)] = glue_g[i]
5050
for i in glue_h:
5051
if i == h_sink:
5052
new_edges[0] = glue_h[i]
5053
else:
5054
new_edges['y'+str(i)] = glue_h[i]
5055
k['x'+str(g_sink)] = new_edges
5056
return k
5057
5058
def firing_graph(S,eff):
5059
r"""
5060
Creates a digraph with divisors as vertices and edges between two
5061
divisors ``D`` and ``E`` if firing a single vertex in ``D`` gives
5062
``E``.
5063
5064
INPUT:
5065
5066
``S`` - sandpile
5067
``eff`` - list of divisors
5068
5069
OUTPUT:
5070
5071
DiGraph
5072
5073
EXAMPLES::
5074
5075
sage: S = Sandpile(graphs.CycleGraph(6),0)
5076
sage: D = SandpileDivisor(S, [1,1,1,1,2,0])
5077
sage: eff = D.effective_div() # optional - 4ti2
5078
sage: firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) # optional - 4ti2
5079
"""
5080
g = DiGraph()
5081
g.add_vertices(range(len(eff)))
5082
for i in g.vertices():
5083
for v in eff[i]:
5084
if eff[i][v]>=S.out_degree(v):
5085
new_div = deepcopy(eff[i])
5086
new_div[v] -= S.out_degree(v)
5087
for oe in S.outgoing_edges(v):
5088
new_div[oe[1]]+=oe[2]
5089
if new_div in eff:
5090
g.add_edge((i,eff.index(new_div)))
5091
return g
5092
5093
def parallel_firing_graph(S, eff):
5094
r"""
5095
Creates a digraph with divisors as vertices and edges between two
5096
divisors ``D`` and ``E`` if firing all unstable vertices in ``D`` gives
5097
``E``.
5098
5099
INPUT:
5100
5101
``S`` - Sandpile
5102
``eff`` - list of divisors
5103
5104
OUTPUT:
5105
5106
DiGraph
5107
5108
EXAMPLES::
5109
5110
sage: S = Sandpile(graphs.CycleGraph(6),0)
5111
sage: D = SandpileDivisor(S, [1,1,1,1,2,0])
5112
sage: eff = D.effective_div() # optional - 4ti2
5113
sage: parallel_firing_graph(S,eff).show3d(edge_size=.005,vertex_size=0.01) # optional - 4ti2
5114
"""
5115
g = DiGraph()
5116
g.add_vertices(range(len(eff)))
5117
for i in g.vertices():
5118
new_edge = False
5119
new_div = deepcopy(eff[i])
5120
for v in eff[i]:
5121
if eff[i][v]>=S.out_degree(v):
5122
new_edge = True
5123
new_div[v] -= S.out_degree(v)
5124
for oe in S.outgoing_edges(v):
5125
new_div[oe[1]]+=oe[2]
5126
if new_edge and (new_div in eff):
5127
g.add_edge((i,eff.index(new_div)))
5128
return g
5129
5130
def admissible_partitions(S, k):
5131
r"""
5132
The partitions of the vertices of ``S`` into ``k`` parts,
5133
each of which is connected.
5134
5135
INPUT:
5136
5137
``S`` - Sandpile
5138
``k`` - integer
5139
5140
OUTPUT:
5141
5142
list of partitions
5143
5144
EXAMPLES::
5145
5146
sage: S = Sandpile(graphs.CycleGraph(4), 0)
5147
sage: P = [admissible_partitions(S, i) for i in [2,3,4]]
5148
sage: P
5149
[[{{0}, {1, 2, 3}},
5150
{{0, 2, 3}, {1}},
5151
{{0, 1, 3}, {2}},
5152
{{0, 1, 2}, {3}},
5153
{{0, 1}, {2, 3}},
5154
{{0, 3}, {1, 2}}],
5155
[{{0}, {1}, {2, 3}},
5156
{{0}, {1, 2}, {3}},
5157
{{0, 3}, {1}, {2}},
5158
{{0, 1}, {2}, {3}}],
5159
[{{0}, {1}, {2}, {3}}]]
5160
sage: for p in P:
5161
... sum([partition_sandpile(S, i).betti(verbose=False)[-1] for i in p])
5162
6
5163
8
5164
3
5165
sage: S.betti()
5166
0 1 2 3
5167
------------------------------
5168
0: 1 - - -
5169
1: - 6 8 3
5170
------------------------------
5171
total: 1 6 8 3
5172
"""
5173
v = S.vertices()
5174
if S.is_directed():
5175
G = DiGraph(S)
5176
else:
5177
G = Graph(S)
5178
result = []
5179
for p in SetPartitions(v, k):
5180
if forall(p, lambda x : G.subgraph(list(x)).is_connected())[0]:
5181
result.append(p)
5182
return result
5183
5184
def partition_sandpile(S,p):
5185
r"""
5186
Each set of vertices in ``p`` is regarded as a single vertex, with and edge
5187
between ``A`` and ``B`` if some element of ``A`` is connected by an edge
5188
to some element of ``B`` in ``S``.
5189
5190
INPUT:
5191
5192
``S`` - Sandpile
5193
``p`` - partition of the vertices of ``S``
5194
5195
OUTPUT:
5196
5197
Sandpile
5198
5199
EXAMPLES::
5200
5201
sage: S = Sandpile(graphs.CycleGraph(4), 0)
5202
sage: P = [admissible_partitions(S, i) for i in [2,3,4]]
5203
sage: for p in P:
5204
... sum([partition_sandpile(S, i).betti(verbose=False)[-1] for i in p])
5205
6
5206
8
5207
3
5208
sage: S.betti()
5209
0 1 2 3
5210
------------------------------
5211
0: 1 - - -
5212
1: - 6 8 3
5213
------------------------------
5214
total: 1 6 8 3
5215
"""
5216
from sage.combinat.combination import Combinations
5217
g = Graph()
5218
g.add_vertices([tuple(i) for i in p])
5219
for u,v in Combinations(g.vertices(), 2):
5220
for i in u:
5221
for j in v:
5222
if (i,j,1) in S.edges():
5223
g.add_edge((u, v))
5224
break
5225
for i in g.vertices():
5226
if S.sink() in i:
5227
return Sandpile(g,i)
5228
5229
def firing_vector(S,D,E):
5230
r"""
5231
If ``D`` and ``E`` are linearly equivalent divisors, find the firing vector
5232
taking ``D`` to ``E``.
5233
5234
INPUT:
5235
5236
- ``S`` -Sandpile
5237
``D``, ``E`` - tuples (representing linearly equivalent divisors)
5238
5239
OUTPUT:
5240
5241
tuple (representing a firing vector from ``D`` to ``E``)
5242
5243
EXAMPLES::
5244
5245
sage: S = complete_sandpile(4)
5246
sage: D = SandpileDivisor(S, {0: 0, 1: 0, 2: 8, 3: 0})
5247
sage: E = SandpileDivisor(S, {0: 2, 1: 2, 2: 2, 3: 2})
5248
sage: v = firing_vector(S, D, E)
5249
sage: v
5250
(0, 0, 2, 0)
5251
5252
The divisors must be linearly equivalent::
5253
5254
sage: vector(D.values()) - S.laplacian()*vector(v) == vector(E.values())
5255
True
5256
sage: firing_vector(S, D, S.zero_div())
5257
Error. Are the divisors linearly equivalent?
5258
"""
5259
try:
5260
v = vector(D.values())
5261
w = vector(E.values())
5262
return tuple(S.laplacian().solve_left(v-w))
5263
except ValueError:
5264
print "Error. Are the divisors linearly equivalent?"
5265
return
5266
5267
def min_cycles(G,v):
5268
r"""
5269
Minimal length cycles in the digraph ``G`` starting at vertex ``v``.
5270
5271
INPUT:
5272
5273
``G`` - DiGraph
5274
``v`` - vertex of ``G``
5275
5276
OUTPUT:
5277
5278
list of lists of vertices
5279
5280
EXAMPLES::
5281
5282
sage: T = sandlib('gor')
5283
sage: [min_cycles(T, i) for i in T.vertices()]
5284
[[], [[1, 3]], [[2, 3, 1], [2, 3]], [[3, 1], [3, 2]]]
5285
"""
5286
pr = G.neighbors_in(v)
5287
sp = G.shortest_paths(v)
5288
return [sp[i] for i in pr if i in sp.keys()]
5289
5290
def wilmes_algorithm(M):
5291
r"""
5292
Computes an integer matrix ``L`` with the same integer row span as ``M``
5293
and such that ``L`` is the reduced laplacian of a directed multigraph.
5294
5295
INPUT:
5296
5297
``M`` - square integer matrix of full rank
5298
5299
OUTPUT:
5300
5301
``L`` - integer matrix
5302
5303
EXAMPLES::
5304
5305
sage: P = matrix([[2,3,-7,-3],[5,2,-5,5],[8,2,5,4],[-5,-9,6,6]])
5306
sage: wilmes_algorithm(P)
5307
[ 1642 -13 -1627 -1]
5308
[ -1 1980 -1582 -397]
5309
[ 0 -1 1650 -1649]
5310
[ 0 0 -1658 1658]
5311
5312
NOTES:
5313
5314
The algorithm is due to John Wilmes.
5315
"""
5316
# find the gcd of the row-sums, and perform the corresponding row
5317
# operations on M
5318
if M.matrix_over_field().is_invertible():
5319
L = deepcopy(M)
5320
L = matrix(ZZ,L)
5321
U = matrix(ZZ,[sum(i) for i in L]).smith_form()[2].transpose()
5322
L = U*M
5323
for k in range(1,M.nrows()-1):
5324
smith = matrix(ZZ,[i[k-1] for i in L[k:]]).smith_form()[2].transpose()
5325
U = identity_matrix(ZZ,k).block_sum(smith)
5326
L = U*L
5327
L[k] = -L[k]
5328
if L[-1][-2]>0:
5329
L[-1] = -L[-1]
5330
for k in range(M.nrows()-2,-1,-1):
5331
for i in range(k+2,M.nrows()):
5332
while L[k][i-1]>0:
5333
L[k] = L[k] + L[i]
5334
v = -L[k+1]
5335
for i in range(k+2,M.nrows()):
5336
v = abs(L[i,i-1])*v + v[i-1]*L[i]
5337
while L[k,k]<=0 or L[k,-1]>0:
5338
L[k] = L[k] + v
5339
return L
5340
else:
5341
raise UserWarning, 'matrix not of full rank'
5342
5343
######### Notes ################
5344
"""
5345
* pickling sandpiles?
5346
* Does 'sat' return a minimal generating set
5347
"""
5348
5349
5350