%load_ext autoreload
%autoreload 2
from pylattica.discrete import PhaseSet
from pylattica.structures.square_grid import DiscreteGridSetup
from pylattica.visualization import SquareGridArtist2D, DiscreteCellArtist
Square Grid Simulations¶
Many of the most common Cellular Automaton simulations take place on a grid of square cells. pylattica
has a number of classes that support this use case. In this demonstration, we will consider a simple simulation in which cells can take on one of the following states, each labelled by a letter:
phases = PhaseSet(["A", "B", "C", "D", "E"])
To set up a simulation that involves discrete cell state values (i.e. "A", "B", "C", "D", or "E" in our case), and takes place on a square grid, we use the DiscreteGridSetup
class. This class provides support for initializing a number of common scenarios that you might want to use as simulation starting points. To create the setup object, just provide the set of all possible phases that can occur during your simulation.
setup = DiscreteGridSetup(phases)
Random Noise¶
This setup mode just provides a totally random distribution of the phases you specify.
simulation_side_length = 40
phases_to_include = ["B", "C", "D"]
simulation = setup.setup_noise(60, phases_to_include)
cell_artist = DiscreteCellArtist.from_discrete_state(simulation.state)
step_artist = SquareGridArtist2D(simulation.structure, cell_artist=cell_artist)
step_artist.jupyter_show(simulation.state, cell_size=5)
Interface¶
This method sets up an interface between two phases
simulation_side_length = 60
left_phase = "A"
right_phase = "B"
simulation = setup.setup_interface(simulation_side_length, left_phase, right_phase)
cell_artist = DiscreteCellArtist.from_discrete_state(simulation.state)
step_artist = SquareGridArtist2D(simulation.structure, cell_artist=cell_artist)
step_artist.jupyter_show(simulation.state, cell_size=5)
Random sites¶
Chooses cells separated by a buffer and assigns phases with the desired ratio to them.
simulation_side_length = 60
total_num_sites = 100
background_phase = "A"
site_phases = ["B", "C"]
site_ratios = [1, 2] # There should be 2 C sites for every 1 B site
buffer = 2 # Each site should be at least 2 cells away from any other
simulation = setup.setup_random_sites(
simulation_side_length,
background_spec=background_phase,
num_sites_desired=total_num_sites,
nuc_species=site_phases,
nuc_ratios=site_ratios,
buffer=buffer
)
cell_artist = DiscreteCellArtist.from_discrete_state(simulation.state)
step_artist = SquareGridArtist2D(simulation.structure, cell_artist=cell_artist)
step_artist.jupyter_show(simulation.state, cell_size=5)
100%|███████████████████████████████████| 3600/3600 [00:00<00:00, 5413.62it/s]
Single Central Particle¶
Assigns the central region of the specified radius to one phase, and everything else to another.
simulation_side_length = 60
particle_radius = 10
background_phase = "A"
particle_phase = "B"
simulation = setup.setup_particle(
simulation_side_length,
radius = particle_radius,
bulk_phase = background_phase,
particle_phase = particle_phase
)
cell_artist = DiscreteCellArtist.from_discrete_state(simulation.state)
step_artist = SquareGridArtist2D(simulation.structure, cell_artist=cell_artist)
step_artist.jupyter_show(simulation.state, cell_size=5)
Organically grown grains¶
You can also setup organically grown grains using the included GrowthModel
. In this model, we establish random sites, and then grow particles outward from them in the shape of the specified neighborhood. Here's how it looks with a few different neighborhoods:
Moore¶
from pylattica.structures.square_grid.growth_setup import GrowthSetup
from pylattica.structures.square_grid.neighborhoods import MooreNbHoodBuilder
simulation_side_length = 60
total_num_sites = 20
background_phase = "A"
site_phases = ["B", "C", "D", "E"]
buffer = 2 # Each site should be at least 2 cells away from any other
growth_setup = GrowthSetup(phases)
simulation = growth_setup.grow(
simulation_side_length,
background_spec=background_phase,
num_sites_desired=total_num_sites,
nuc_species=site_phases,
buffer=buffer,
nb_builder=MooreNbHoodBuilder(1)
)
cell_artist = DiscreteCellArtist.from_discrete_state(simulation.state)
step_artist = SquareGridArtist2D(simulation.structure, cell_artist=cell_artist)
step_artist.jupyter_show(simulation.state, cell_size=5)
100%|███████████████████████████████████| 3600/3600 [00:00<00:00, 5371.84it/s] 100%|██████████████████████████████████| 3600/3600 [00:00<00:00, 14857.60it/s] 100%|████████████████████████████████████████| 60/60 [00:00<00:00, 288.28it/s]
Von Neumann¶
from pylattica.structures.square_grid.neighborhoods import VonNeumannNbHood2DBuilder
simulation = growth_setup.grow(
simulation_side_length,
background_spec=background_phase,
num_sites_desired=total_num_sites,
nuc_species=site_phases,
buffer=buffer,
nb_builder=VonNeumannNbHood2DBuilder()
)
cell_artist = DiscreteCellArtist.from_discrete_state(simulation.state)
step_artist = SquareGridArtist2D(simulation.structure, cell_artist=cell_artist)
step_artist.jupyter_show(simulation.state, cell_size=5)
100%|███████████████████████████████████| 3600/3600 [00:00<00:00, 4911.42it/s] 100%|██████████████████████████████████| 3600/3600 [00:00<00:00, 26419.10it/s] 100%|████████████████████████████████████████| 60/60 [00:00<00:00, 280.44it/s]
Circular¶
from pylattica.core.neighborhood_builders import DistanceNeighborhoodBuilder
simulation = growth_setup.grow(
simulation_side_length,
background_spec=background_phase,
num_sites_desired=total_num_sites,
nuc_species=site_phases,
buffer=buffer,
nb_builder=DistanceNeighborhoodBuilder(5)
)
cell_artist = DiscreteCellArtist.from_discrete_state(simulation.state)
step_artist = SquareGridArtist2D(simulation.structure, cell_artist=cell_artist)
step_artist.jupyter_show(simulation.state, cell_size=5)
100%|███████████████████████████████████| 3600/3600 [00:00<00:00, 4786.48it/s] 100%|█████████████████████████████████████| 3600/3600 [02:03<00:00, 29.27it/s] 100%|████████████████████████████████████████| 60/60 [00:00<00:00, 242.33it/s]
Structure and State (or the Simulation
object)¶
The output of the setup methods defined above is an instance of Simulation
. This object has two attributes: .structure
and .state
. The structure refers to the lattice of sites that underpins the simulation. In the case of these states we just set up, that lattice is a 2D square grid. In general, it can be any type of lattice. This lattice is called the "structure" of the simulation state. It is subject to periodic boundary conditions, meaning that a cell on the edge of the structure is a neighbor to the cell on the opposite edge of the grid.
You can access the structure via the structure
attribute, and view it's sites. We'll use a small interface simulation for this example.
simulation_side_length = 4
left_phase = "A"
right_phase = "B"
simulation = setup.setup_interface(simulation_side_length, left_phase, right_phase)
Here's how you see all the sites in the structure:
simulation.structure.sites()
[{'_site_class': '_A', '_location': array([0., 0.]), '_site_id': 0}, {'_site_class': '_A', '_location': array([1., 0.]), '_site_id': 1}, {'_site_class': '_A', '_location': array([2., 0.]), '_site_id': 2}, {'_site_class': '_A', '_location': array([3., 0.]), '_site_id': 3}, {'_site_class': '_A', '_location': array([0., 1.]), '_site_id': 4}, {'_site_class': '_A', '_location': array([1., 1.]), '_site_id': 5}, {'_site_class': '_A', '_location': array([2., 1.]), '_site_id': 6}, {'_site_class': '_A', '_location': array([3., 1.]), '_site_id': 7}, {'_site_class': '_A', '_location': array([0., 2.]), '_site_id': 8}, {'_site_class': '_A', '_location': array([1., 2.]), '_site_id': 9}, {'_site_class': '_A', '_location': array([2., 2.]), '_site_id': 10}, {'_site_class': '_A', '_location': array([3., 2.]), '_site_id': 11}, {'_site_class': '_A', '_location': array([0., 3.]), '_site_id': 12}, {'_site_class': '_A', '_location': array([1., 3.]), '_site_id': 13}, {'_site_class': '_A', '_location': array([2., 3.]), '_site_id': 14}, {'_site_class': '_A', '_location': array([3., 3.]), '_site_id': 15}]
See how the locations are represented by two coordinates? They lie on a square grid.
Every site in this structure has a _site_class
of "_A". This is not significant. In other, more complex simulations, this attribute can be used to distinguish types of sites that exist in the structure. For instance, in materials science, we refer to lattice sites by their coordination structure (e.g. tetrahedral, octahedral).
Simulation State Dictionary¶
You may have noticed that the sites in our structure also have IDs. These IDs are referred to by the SimulationState
. This object is essentially a dictionary that maps site IDs to the state of that site. A site state can be any arbitrary python dictionary, but in our case each site is just assigned an occupancy value. Let's make some sense of this.
The first site we have is (0,0). It's ID is 0 as well.
site = simulation.structure.sites()[0]
print(f'Site {site["_site_id"]} is at {site["_location"]}')
Site 0 is at [0. 0.]
Now let's see what it's state is:
simulation.state.get_site_state(0)
{'_site_id': 0, 'DISCRETE_OCCUPANCY': 'A'}
The DISCRETE_OCCUPANCY
key indicates the "phase" that occupies that cell. Let's see if that lines up with our visualization. Remember, this site is at (0,0), or the bottom left of the simulation state.
cell_artist = DiscreteCellArtist.from_discrete_state(simulation.state)
step_artist = SquareGridArtist2D(simulation.structure, cell_artist=cell_artist)
step_artist.jupyter_show(simulation.state, cell_size=30)
Sure enough, that bottom left cell is an 'A' cell!
Every site in this structure has a _site_class
of "_A". This is not significant. In other, more complex simulations, this attribute can be used to distinguish types of sites that exist in the structure. For instance, in materials science, we refer to lattice sites by their coordination structure (e.g. tetrahedral, octahedral).