Клеточные автоматы с использованием класса python

Я создал класс, который инициирует и обновляет данные CA, и я создал функцию «Simulate», которая обновляет ячейки на основе правила, согласно которому огонь распространяется по деревьям и оставляет пустые места. Пустые места заменяются деревьями с заданной вероятностью.

Существует проблема, когда кажется, что моя функция применяет правило к держателю данных текущего времени, а не к держателю данных предыдущего времени. Я установил prevstate = self.state в качестве временного держателя данных для предыдущей итерации, но, выполняя небольшие тесты, я обнаружил, что он дает те же результаты, как если бы я вообще не включал эту строку. Что я делаю не так?

import numpy as np
import random
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, colorConverter
from matplotlib.animation import FuncAnimation

#dimentions:
x = 10
y = 10

lighting = 0  #set to 0 for testing
grow = 0.3


#parameter values
empty = 0
tree = 1
fire = 2

random.seed(1)

#CA Rule definition
def update(mat, i, j, lighting, grow, prob):
    if mat[i, j] == empty:
        if prob < grow:
            return tree
        else:
            return empty
    elif mat[i, j] == tree:
        if max(mat[i-1, j], mat[i+1, j], mat[i, j-1], mat[i, j+1]) == fire:
            return fire
        elif prob < lighting:
            return fire
        else:
            return tree
    else:
        return empty


########## Data Holder
class Simulation:
    def __init__(self):
        self.frame = 0
        #self.state = np.random.randint(2, size=(x, y)) commented out for testing
        self.state = np.ones((x, y))
        self.state[5, 5] = 2  #initial fire started at this location for testing

    def updateS(self):
        prevstate = self.state    #line of code i think should be passing previous iteration through rule

        for i in range(1, y-1):
            for j in range(1, x-1):
                prob = random.random()
                self.state[i, j] = update(prevstate, i, j, lighting, grow, prob)

    def step(self):
        self.updateS()
        self.frame += 1


simulation = Simulation()
figure = plt.figure()

ca_plot = plt.imshow(simulation.state, cmap='seismic', interpolation='bilinear', vmin=empty, vmax=fire)
plt.colorbar(ca_plot)
transparent = colorConverter.to_rgba('black', alpha=0)
#wall_colormap = LinearSegmentedColormap.from_list('my_colormap', [transparent, 'green'], 2)


def animation_func(i):
    simulation.step()
    ca_plot.set_data(simulation.state)
    return ca_plot

animation = FuncAnimation(figure, animation_func, interval=1000)
mng = plt.get_current_fig_manager()
mng.window.showMaximized()
plt.show()

Приветствуются любые комментарии о лучших способах реализации ЦС!


person ushham    schedule 13.04.2020    source источник


Ответы (2)


Присваивания Python — это указатели... Поэтому, когда вы обновляете self.state, то prevstate также обновляется.

Я ожидаю, если вы установите:

prevstate = copy.copy(self.state)

Это должно решить вашу проблему.

Копировать документы

person jabberwocky    schedule 13.04.2020
comment
Большое спасибо! Просто чтобы уточнить, моя проблема заключалась в том, что, поскольку prevstate указывал на self.state, он обновлялся и всегда был идентичен self.state на протяжении всей итерации? - person ushham; 13.04.2020
comment
Без проблем. Подумайте об этом больше, как. Есть один массив. Строка: prevstate = self.state устанавливает обе ваши переменные так, чтобы они указывали на один и тот же массив. Но все же один массив. Copy.copy() делает 2 массива присвоенными 2 переменным. - person jabberwocky; 13.04.2020

Как правильно отмечает бармаглот, ваша проблема заключается в том, что строка prevstate = self.state делает prevstate новой ссылкой на тот же массив numpy, что и self.state, так что изменение содержимого одного также изменяет другое.

Однако вместо того, чтобы копировать массив на каждой итерации, чуть более эффективным решением было бы предварительно выделить два массива и поменять их местами, например так:

class Simulation:
    def __init__(self):
        self.frame = 0
        self.state = np.ones((x, y))
        self.state[5, 5] = 2
        self.prevstate = np.ones((x, y))  # <-- add this line

    def updateS(self):
        self.state, self.prevstate = self.prevstate, self.state  # <-- swap the buffers

        for i in range(1, y-1):
            for j in range(1, x-1):
                prob = random.random()
                self.state[i, j] = update(self.prevstate, i, j, lighting, grow, prob)

Я говорю «слегка», потому что все, что вы действительно сохраняете, — это копия пустого массива и некоторая работа для сборщика мусора. Однако, если вы достаточно оптимизируете свой внутренний цикл обновления состояния — может быть, например. реализация правила CA с использованием numba — относительная стоимость дополнительной копии массива станет значительной. В любом случае, у этого метода «двойной буферизации» нет реальных недостатков, так что это хорошая привычка.

person Ilmari Karonen    schedule 14.04.2020
comment
Спасибо, и отличный совет о numba. С вашими правками он работает намного быстрее! Не могли бы вы объяснить логику синтаксиса строки, которая меняет местами буферы? Я не понимаю 1. как используются запятые или как установка self.prevstate = self.prevstate обновляет любой массив? - person ushham; 15.04.2020
comment
Это обычная идиома для замены переменных в Python с использованием распаковка кортежа. Запятые имеют более высокий приоритет, чем оператор присваивания, поэтому он анализируется как (state, prevstate) = (prevstate, state). (Обратите внимание, что это не работает для фрагментов пустого массива.) - person Ilmari Karonen; 15.04.2020