Skip to content

Commit 33b6500

Browse files
authored
Merge pull request #6 from codegithubka/storm
Working CA, PP, and visualizations
2 parents 047e457 + 698bbc5 commit 33b6500

7 files changed

Lines changed: 792 additions & 305 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
__pycache__/
2+
.pytest_cache/
23
.vscode/
34
results/
45
# Virtual environment

docs/prompts.md

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,8 +545,135 @@ In the run function, check whether the entries in the cell_params arrays align w
545545
### output
546546
Made large changes to multiple functions. See git commit for details.
547547

548-
---
548+
### changes
549+
Added color bars to the evolving parameters plots.
550+
551+
## CA and PP tests
552+
Now that the MVP functionality is fulfilled, we need to test it. For this, we create numerous tests for both the CA class and the PP class, testing if incorrect arguments raise the correct errors, if any functions raise unwanted errors, but mostly if the update functions work as intended.
553+
554+
### prompt
555+
Add tests for the CA and PP class to the test_ca.py file. Check whether incorrect arguments do not raise errors and if any functions do raise errors. Also, test whether run picks up on cell_params array that do not align by grid. For this, manually change some cell_params array entries before running the simulation. Check whether all errors are informative, in the sense that a user would know how to avoid it next time.
556+
557+
Lastly, check whether trivial simulations result in the correct outcome. For example, if both the predator and prey death rates are 1 and the birth rates are below 1, then the grid should go extinct. Also, if the birth rate of the prey and predators are 1 and the death rate of the predators to be 0 we expect the prey to go extinct an the predators to occupy a large number of cells.
558+
559+
Create any additional tests if they are unique, meaning it is not tested already, and well-defined, meaning we know the intended behavior and we know how to compare it to the true behavior.
560+
561+
### output
562+
def test_initialization_and_grid_filling():
563+
rows, cols = 10, 10
564+
densities = (0.2, 0.1)
565+
ca = CA(rows, cols, densities, neighborhood="moore", params={}, cell_params={}, seed=42)
566+
assert ca.grid.shape == (rows, cols)
567+
assert ca.n_species == len(densities)
568+
total_cells = rows * cols
569+
# expected counts use the same rounding as CA.__init__
570+
expected_counts = [int(round(total_cells * d)) for d in densities]
571+
# verify actual counts equal expected
572+
for i, exp in enumerate(expected_counts, start=1):
573+
assert int(np.count_nonzero(ca.grid == i)) == exp
574+
575+
576+
def test_invalid_parameters_raise():
577+
# invalid rows/cols
578+
with pytest.raises(AssertionError):
579+
CA(0, 5, (0.1,), "moore", {}, {}, seed=1)
580+
with pytest.raises(AssertionError):
581+
CA(5, -1, (0.1,), "moore", {}, {}, seed=1)
582+
# densities must be non-empty tuple
583+
with pytest.raises(AssertionError):
584+
CA(5, 5, (), "moore", {}, {}, seed=1)
585+
# densities sum > 1
586+
with pytest.raises(AssertionError):
587+
CA(5, 5, (0.8, 0.8), "moore", {}, {}, seed=1)
588+
# invalid neighborhood
589+
with pytest.raises(AssertionError):
590+
CA(5, 5, (0.1,), "invalid", {}, {}, seed=1)
591+
592+
# PP: params must be a dict or None
593+
with pytest.raises(TypeError):
594+
PP(rows=5, cols=5, densities=(0.2, 0.1), neighborhood="moore", params="bad", cell_params=None, seed=1)
595+
596+
597+
def test_neighborhood_counting():
598+
# set up a small grid with a single prey in the center and check neighbor counts
599+
ca = CA(3, 3, (0.0,), neighborhood="moore", params={}, cell_params={}, seed=1)
600+
ca.grid[:] = 0
601+
ca.grid[1, 1] = 1
602+
counts = ca.count_neighbors()
603+
# counts is a tuple with one array (state 1)
604+
neigh = counts[0]
605+
# all 8 neighbors of center should have count 1
606+
expected_positions = [(0, 0), (0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1), (2, 2)]
607+
for r in range(3):
608+
for c in range(3):
609+
if (r, c) in expected_positions:
610+
assert neigh[r, c] == 1
611+
else:
612+
# center has 0 neighbors of same state
613+
assert neigh[r, c] == 0
614+
615+
616+
def test_run_detects_cell_params_shape_and_nonnan_mismatch():
617+
# create a PP and enable evolution for a parameter
618+
pp = PP(rows=5, cols=5, densities=(0.2, 0.1), neighborhood="moore", params=None, cell_params=None, seed=2)
619+
pp.evolve("prey_death", sd=0.01, min=0.0, max=1.0)
620+
621+
# wrong shape should raise informative ValueError during run()
622+
pp.cell_params["prey_death"] = np.zeros((1, 1))
623+
with pytest.raises(ValueError) as excinfo:
624+
pp.run(1)
625+
assert "shape equal to grid" in str(excinfo.value)
626+
627+
# now create a same-shaped array but with non-NaN positions that don't match prey positions
628+
arr = np.zeros(pp.grid.shape, dtype=float) # filled with non-NaN everywhere
629+
pp.cell_params["prey_death"] = arr
630+
with pytest.raises(ValueError) as excinfo2:
631+
pp.run(1)
632+
assert "non-NaN entries must match positions" in str(excinfo2.value)
633+
634+
635+
def test_extinction_when_death_one():
636+
# when both death rates are 1 all individuals should die in one step
637+
params = {
638+
"prey_death": 1.0,
639+
"predator_death": 1.0,
640+
"prey_birth": 0.0,
641+
"predator_birth": 0.0,
642+
}
643+
pp = PP(rows=10, cols=10, densities=(0.2, 0.1), neighborhood="moore", params=params, cell_params=None, seed=3)
644+
pp.run(1)
645+
# no prey or predators should remain
646+
assert np.count_nonzero(pp.grid != 0) == 0
647+
648+
649+
def test_predators_dominate_with_high_birth_and_zero_predator_death():
650+
params = {
651+
"prey_death": 0.0,
652+
"predator_death": 0.0,
653+
"prey_birth": 1.0,
654+
"predator_birth": 1.0,
655+
}
656+
pp = PP(rows=10, cols=10, densities=(0.1, 0.05), neighborhood="moore", params=params, cell_params=None, seed=4)
657+
# run longer to allow predators to consume prey; expect prey extinction
658+
pp.run(200)
659+
after_prey = int(np.count_nonzero(pp.grid == 1))
660+
after_pred = int(np.count_nonzero(pp.grid == 2))
661+
# after sufficient time, prey should go extinct and predators remain
662+
assert after_prey == 0
663+
assert after_pred > 0
664+
665+
### changes
666+
Revealed and fixed error in the PP class' update functions where no parameter key was passed to the _process_reproduction function, resulting in an error.
667+
668+
## More visualizations
669+
Now that we can run simulations, we need to understand what is happening. For this, we first need graphs detailing the population counts as well as the min, mean, and max values of each evolving parameter. Additionally, we need to add functionality that stops mutation after a certain amount of steps, after which we can see which parameter values survive and which go extinct.
670+
671+
### prompt
672+
Add graphs underneath the imshow plots to show the simulation state over time. For the states grid, show the population count of the prey and predator over time. For the evolving parameters, show the min, mean, and max value of that parameter over time. Only measure these values when the figure is updated, to make sure it only adds overhead every interval iterations.
673+
674+
Also create a separate plot left of the states grid plot that shows the distribution of prey neighbors for each prey. I want a histogram showing the amount of prey with each possible prey neighbor count (for moore this is 8). Below that, add a graph showing the 25%, the mean, and the 75% value for the neighbor count.
549675

676+
Lastly, add functionality to stop evolution after a certain time-step. This should be an optional argument to the run function. Also add a function to create snapshots of the histogram, states grid, and cell parameters grids. As these are snapshots, the graphs below these plots should not be included. Add another argument to the run function, which is a list of the iterations to create snapshots at. Save these snapshots to the results folder, where each run should have its own folder with snapshots. Make sure the snapshot file names include the iteration.
550677

551678
### Mean Field class
552679

0 commit comments

Comments
 (0)