Task: Abelian sandpile model_Identity


C++: #include <algorithm>
#include <array>
#include <cassert>
#include <initializer_list>
#include <iostream>

constexpr size_t sp_rows = 3;
constexpr size_t sp_columns = 3;
constexpr size_t sp_cells = sp_rows * sp_columns;
constexpr int sp_limit = 4;

class abelian_sandpile {
    friend std::ostream& operator<<(std::ostream&, const abelian_sandpile&);

public:
    abelian_sandpile();
    explicit abelian_sandpile(std::initializer_list<int> init);
    void stabilize();
    bool is_stable() const;
    void topple();
    abelian_sandpile& operator+=(const abelian_sandpile&);
    bool operator==(const abelian_sandpile&);

private:
    int& cell_value(size_t row, size_t column) {
        return cells_[cell_index(row, column)];
    }
    static size_t cell_index(size_t row, size_t column) {
        return row * sp_columns + column;
    }
    static size_t row_index(size_t cell_index) {
        return cell_index/sp_columns;
    }
    static size_t column_index(size_t cell_index) {
        return cell_index % sp_columns;
    }

    std::array<int, sp_cells> cells_;
};

abelian_sandpile::abelian_sandpile() {
    cells_.fill(0);
}

abelian_sandpile::abelian_sandpile(std::initializer_list<int> init) {
    assert(init.size() == sp_cells);
    std::copy(init.begin(), init.end(), cells_.begin());
}

abelian_sandpile& abelian_sandpile::operator+=(const abelian_sandpile& other) {
    for (size_t i = 0; i < sp_cells; ++i)
        cells_[i] += other.cells_[i];
    stabilize();
    return *this;
}

bool abelian_sandpile::operator==(const abelian_sandpile& other) {
    return cells_ == other.cells_;
}

bool abelian_sandpile::is_stable() const {
    return std::none_of(cells_.begin(), cells_.end(),
                        [](int a) { return a >= sp_limit; });
}

void abelian_sandpile::topple() {
    for (size_t i = 0; i < sp_cells; ++i) {
        if (cells_[i] >= sp_limit) {
            cells_[i] -= sp_limit;
            size_t row = row_index(i);
            size_t column = column_index(i);
            if (row > 0)
                ++cell_value(row - 1, column);
            if (row + 1 < sp_rows)
                ++cell_value(row + 1, column);
            if (column > 0)
                ++cell_value(row, column - 1);
            if (column + 1 < sp_columns)
                ++cell_value(row, column + 1);
            break;
        }
    }
}

void abelian_sandpile::stabilize() {
    while (!is_stable())
        topple();
}

abelian_sandpile operator+(const abelian_sandpile& a, const abelian_sandpile& b) {
    abelian_sandpile c(a);
    c += b;
    return c;
}

std::ostream& operator<<(std::ostream& out, const abelian_sandpile& as) {
    for (size_t i = 0; i < sp_cells; ++i) {
        if (i > 0)
            out << (as.column_index(i) == 0 ? '\n' : ' ');
        out << as.cells_[i];
    }
    return out << '\n';
}

int main() {
    std::cout << std::boolalpha;

    std::cout << "Avalanche:\n";
    abelian_sandpile sp{4,3,3, 3,1,2, 0,2,3};
    while (!sp.is_stable()) {
        std::cout << sp << "stable? " << sp.is_stable() << "\n\n";
        sp.topple();
    }
    std::cout << sp << "stable? " << sp.is_stable() << "\n\n";

    std::cout << "Commutativity:\n";
    abelian_sandpile s1{1,2,0, 2,1,1, 0,1,3};
    abelian_sandpile s2{2,1,3, 1,0,1, 0,1,0};
    abelian_sandpile sum1(s1 + s2);
    abelian_sandpile sum2(s2 + s1);
    std::cout << "s1 + s2 equals s2 + s1? " << (sum1 == sum2) << "\n\n";
    std::cout << "s1 + s2 = \n" << sum1;
    std::cout << "\ns2 + s1 = \n" << sum2;
    std::cout << '\n';

    std::cout << "Identity:\n";
    abelian_sandpile s3{3,3,3, 3,3,3, 3,3,3};
    abelian_sandpile s3_id{2,1,2, 1,0,1, 2,1,2};
    abelian_sandpile sum3(s3 + s3_id);
    abelian_sandpile sum4(s3_id + s3_id);
    std::cout << "s3 + s3_id equals s3? " << (sum3 == s3) << '\n';
    std::cout << "s3_id + s3_id equals s3_id? " << (sum4 == s3_id) << "\n\n";
    std::cout << "s3 + s3_id = \n" << sum3;
    std::cout << "\ns3_id + s3_id = \n" << sum4;

    return 0;
}






Python: 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
        
        g = self.grid
        for row_col in self._border.intersection(g.keys()):
            del g[row_col]
        return self
    
    __pos__ = 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__}()'
        

unstable = Sandpile()
s1 = Sandpile()
s2 = Sandpile()
s3 = Sandpile("3 3 3  3 3 3  3 3 3")
s3_id = Sandpile("2 1 2  1 0 1  2 1 2")