How to resolve the algorithm Abelian sandpile model/Identity step by step in the Python programming language
How to resolve the algorithm Abelian sandpile model/Identity step by step in the Python programming language
Table of Contents
Problem Statement
Our sandpiles are based on a 3 by 3 rectangular grid giving nine areas that contain a number from 0 to 3 inclusive. (The numbers are said to represent grains of sand in each area of the sandpile). E.g. s1 = and s2 = Addition on sandpiles is done by adding numbers in corresponding grid areas, so for the above: If the addition would result in more than 3 "grains of sand" in any area then those areas cause the whole sandpile to become "unstable" and the sandpile areas are "toppled" in an "avalanche" until the "stable" result is obtained. Any unstable area (with a number >= 4), is "toppled" by loosing one grain of sand to each of its four horizontal or vertical neighbours. Grains are lost at the edge of the grid, but otherwise increase the number in neighbouring cells by one, whilst decreasing the count in the toppled cell by four in each toppling. A toppling may give an adjacent area more than four grains of sand leading to a chain of topplings called an "avalanche". E.g. The final result is the stable sandpile on the right. Note: The order in which cells are toppled does not affect the final result.
Show confirming output here, with your examples.
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Abelian sandpile model/Identity step by step in the Python programming language
Implementing Stable Sandpiles
Overview
In this Python implementation, a sandpile is represented as a 2D grid, where each cell contains a non-negative integer representing the number of sand grains at that location.
Class Definition
The Sandpile
class is defined to represent a sandpile:
__init__
: Initializes the sandpile from a grid of integers._border
: Defines a set of grid coordinates corresponding to border locations (not considered part of the sandpile)._cell_coords
: Defines a list of grid coordinates corresponding to the non-border cells within the sandpile.topple
: Topples the sandpile if any cell contains four or more grains, redistributing the grains to neighboring cells (left, right, up, down).stabilise
: Repeatedly topples the sandpile until it reaches a stable state, where no cell contains four or more grains.__pos__
(alias forstabilise
): Provides a convenient shortcut to stabilize the sandpile.__eq__
: Compares two sandpiles for equality based on the values in their grids.__add__
: Adds two sandpiles by summing the grain counts at each cell.__str__
: Produces a string representation of the sandpile.__repr__
: Provides a string representation of the sandpile for debugging purposes.
Testing and Demonstration
In the provided code, several sandpiles are created and compared:
unstable
: An unstable sandpile with grains exceeding the toppling threshold.s1
,s2
,s3
: Various stable sandpiles.s3_id
: The identity sandpile, which remains unchanged after any addition.
A series of operations and tests are performed:
- A cascade series is generated for the unstable sandpile, showing the sequence of states until it stabilizes.
- Sandpile addition is demonstrated, with tests that show the commutative property and the identity property of the addition operation.
Additional Functions
addSand
: A function that adds two sandpiles, returning a new stabilized sandpile.cascadeSeries
: Generates a series of sandpile states from an initial state, toppling until stability.convergence
: Finds the point of convergence in a sequence where a condition becomes true for two successive elements.nextState
: Applies toppling rules to a single sandpile cell, determining the next state based on the cell's grain count and neighboring cells.indexNeighbours
: Retrieves the indices of vertically and horizontally neighboring cells for a given index in the flattened sandpile representation.
Visualizing Sandpiles
showSandPiles
: Generates a multi-line representation of sandpiles, optionally delimiting them with operators or indents.
Generic Utility Functions
chunksOf
: Divides a list into sublists of a specified length.concat
: Concatenates a list of lists into a single list.findIndex
: Finds the first index of an element that matches a given condition.iterate
: Generates an infinite list of repeated applications of a function.maybe
: Provides a default value if a given value isNone
, otherwise applies a function to it.
Source code in the python programming language
from itertools import product
from collections import defaultdict
class Sandpile():
def __init__(self, gridtext):
array = [int(x) for x in gridtext.strip().split()]
self.grid = defaultdict(int,
{(i //3, i % 3): x
for i, x in enumerate(array)})
_border = set((r, c)
for r, c in product(range(-1, 4), repeat=2)
if not 0 <= r <= 2 or not 0 <= c <= 2
)
_cell_coords = list(product(range(3), repeat=2))
def topple(self):
g = self.grid
for r, c in self._cell_coords:
if g[(r, c)] >= 4:
g[(r - 1, c)] += 1
g[(r + 1, c)] += 1
g[(r, c - 1)] += 1
g[(r, c + 1)] += 1
g[(r, c)] -= 4
return True
return False
def stabilise(self):
while self.topple():
pass
# Remove extraneous grid border
g = self.grid
for row_col in self._border.intersection(g.keys()):
del g[row_col]
return self
__pos__ = stabilise # +s == s.stabilise()
def __eq__(self, other):
g = self.grid
return all(g[row_col] == other.grid[row_col]
for row_col in self._cell_coords)
def __add__(self, other):
g = self.grid
ans = Sandpile("")
for row_col in self._cell_coords:
ans.grid[row_col] = g[row_col] + other.grid[row_col]
return ans.stabilise()
def __str__(self):
g, txt = self.grid, []
for row in range(3):
txt.append(' '.join(str(g[(row, col)])
for col in range(3)))
return '\n'.join(txt)
def __repr__(self):
return f'{self.__class__.__name__}(""""\n{self.__str__()}""")'
unstable = Sandpile("""
4 3 3
3 1 2
0 2 3""")
s1 = Sandpile("""
1 2 0
2 1 1
0 1 3
""")
s2 = Sandpile("""
2 1 3
1 0 1
0 1 0
""")
s3 = Sandpile("3 3 3 3 3 3 3 3 3")
s3_id = Sandpile("2 1 2 1 0 1 2 1 2")
'''Abelian Sandpile – Identity'''
from operator import add, eq
# -------------------------- TEST --------------------------
# main :: IO ()
def main():
'''Tests of cascades and additions'''
s0 = [[4, 3, 3], [3, 1, 2], [0, 2, 3]]
s1 = [[1, 2, 0], [2, 1, 1], [0, 1, 3]]
s2 = [[2, 1, 3], [1, 0, 1], [0, 1, 0]]
s3 = [[3, 3, 3], [3, 3, 3], [3, 3, 3]]
s3_id = [[2, 1, 2], [1, 0, 1], [2, 1, 2]]
series = list(cascadeSeries(s0))
for expr in [
'Cascade:',
showSandPiles(
[(' ', series[0])] + [
(':', xs) for xs in series[1:]
]
),
'',
f's1 + s2 == s2 + s1 -> {addSand(s1)(s2) == addSand(s2)(s1)}',
showSandPiles([
(' ', s1),
('+', s2),
('=', addSand(s1)(s2))
]),
'',
showSandPiles([
(' ', s2),
('+', s1),
('=', addSand(s2)(s1))
]),
'',
f's3 + s3_id == s3 -> {addSand(s3)(s3_id) == s3}',
showSandPiles([
(' ', s3),
('+', s3_id),
('=', addSand(s3)(s3_id))
]),
'',
f's3_id + s3_id == s3_id -> {addSand(s3_id)(s3_id) == s3_id}',
showSandPiles([
(' ', s3_id),
('+', s3_id),
('=', addSand(s3_id)(s3_id))
]),
]:
print(expr)
# ----------------------- SANDPILES ------------------------
# addSand :: [[Int]] -> [[Int]] -> [[Int]]
def addSand(xs):
'''The stabilised sum of two sandpiles.
'''
def go(ys):
return cascadeSeries(
chunksOf(len(xs))(
map(
add,
concat(xs),
concat(ys)
)
)
)[-1]
return go
# cascadeSeries :: [[Int]] -> [[[Int]]]
def cascadeSeries(rows):
'''The sequence of states from a given
sand pile to a stable condition.
'''
xs = list(rows)
w = len(xs)
return [
list(chunksOf(w)(x)) for x
in convergence(eq)(
iterate(nextState(w))(
concat(xs)
)
)
]
# convergence :: (a -> a -> Bool) -> [a] -> [a]
def convergence(p):
'''All items of xs to the point where the binary
p returns True over two successive values.
'''
def go(xs):
def conv(prev, ys):
y = next(ys)
return [prev] + (
[] if p(prev, y) else conv(y, ys)
)
return conv(next(xs), xs)
return go
# nextState Int -> Int -> [Int] -> [Int]
def nextState(w):
'''The next state of a (potentially unstable)
flattened sand-pile matrix of row length w.
'''
def go(xs):
def tumble(i):
neighbours = indexNeighbours(w)(i)
return [
1 + k if j in neighbours else (
k - (1 + w) if j == i else k
) for (j, k) in enumerate(xs)
]
return maybe(xs)(tumble)(
findIndex(lambda x: w < x)(xs)
)
return go
# indexNeighbours :: Int -> Int -> [Int]
def indexNeighbours(w):
'''Indices vertically and horizontally adjoining the
given index in a flattened matrix of dimension w.
'''
def go(i):
lastCol = w - 1
iSqr = (w * w)
col = i % w
return [
j for j in [i - w, i + w]
if -1 < j < iSqr
] + ([i - 1] if 0 != col else []) + (
[1 + i] if lastCol != col else []
)
return go
# ------------------------ DISPLAY -------------------------
# showSandPiles :: [(String, [[Int]])] -> String
def showSandPiles(pairs):
'''Indented multi-line representation
of a sequence of matrices, delimited
by preceding operators or indents.
'''
return '\n'.join([
' '.join([' '.join(map(str, seq)) for seq in tpl])
for tpl in zip(*[
zip(
*[list(str(pfx).center(len(rows)))]
+ list(zip(*rows))
)
for (pfx, rows) in pairs
])
])
# ------------------------ GENERIC -------------------------
# chunksOf :: Int -> [a] -> [[a]]
def chunksOf(n):
'''A series of lists of length n, subdividing the
contents of xs. Where the length of xs is not evenly
divible, the final list will be shorter than n.
'''
def go(xs):
ys = list(xs)
return (
ys[i:n + i] for i in range(0, len(ys), n)
) if 0 < n else None
return go
# concat :: [[a]] -> [a]
def concat(xs):
'''The concatenation of all
elements in a list.
'''
return [x for lst in xs for x in lst]
# findIndex :: (a -> Bool) -> [a] -> Maybe Int
def findIndex(p):
'''Just the first index at which an
element in xs matches p,
or Nothing if no elements match.
'''
def go(xs):
return next(
(i for (i, x) in enumerate(xs) if p(x)),
None
)
return go
# iterate :: (a -> a) -> a -> Gen [a]
def iterate(f):
'''An infinite list of repeated
applications of f to x.
'''
def go(x):
v = x
while True:
yield v
v = f(v)
return go
# maybe :: b -> (a -> b) -> Maybe a -> b
def maybe(v):
'''Either the default value v, if x is None,
or the application of f to x.
'''
def go(f):
def g(x):
return v if None is x else f(x)
return g
return go
# MAIN ---
if __name__ == '__main__':
main()
You may also check:How to resolve the algorithm Equal prime and composite sums step by step in the Julia programming language
You may also check:How to resolve the algorithm 100 doors step by step in the CLU programming language
You may also check:How to resolve the algorithm Last letter-first letter step by step in the BBC BASIC programming language
You may also check:How to resolve the algorithm Mandelbrot set step by step in the Modula-3 programming language
You may also check:How to resolve the algorithm Function definition step by step in the Aime programming language