From 7e4bd4d2ff6b48d1baf75d089bcd19cd7e7b58f4 Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 26 Nov 2023 18:38:32 -0500 Subject: [PATCH] Sugarscape {G1,{M,T}}: Add tests --- examples/sugarscape_g1mt/Readme.md | 3 +- .../sugarscape_g1mt/sugarscape_g1mt/model.py | 7 ++ examples/sugarscape_g1mt/tests.py | 72 +++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 examples/sugarscape_g1mt/tests.py diff --git a/examples/sugarscape_g1mt/Readme.md b/examples/sugarscape_g1mt/Readme.md index 67ddfd9b..7fbced07 100644 --- a/examples/sugarscape_g1mt/Readme.md +++ b/examples/sugarscape_g1mt/Readme.md @@ -3,7 +3,7 @@ ## Summary This is Epstein & Axtell's Sugarscape model with Traders, a detailed description is in Chapter four of -*Growing Artificial Societies: Social Science from the Bottom Up.* (1996) +*Growing Artificial Societies: Social Science from the Bottom Up.* (1996) The model shows an emergent price equilibrium can happen via a decentralized dynamics. This code generally matches the code in the Complexity Explorer Tutorial, but in `.py` instead of `.ipynb` format. @@ -83,6 +83,7 @@ Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and p * ``server.py``: Sets up and launches and interactive visualization server. * ``run.py``: Runs Server, Single Run or Batch Run with data collection and basic analysis. * `app.py`: Runs a visualization server via Solara (`solara run app.py`). +* `tests.py`: Has tests to ensure that the model reproduces the results in shown in Growing Artificial Societies. ## Additional Resources diff --git a/examples/sugarscape_g1mt/sugarscape_g1mt/model.py b/examples/sugarscape_g1mt/sugarscape_g1mt/model.py index 83eaaafa..dd27da23 100644 --- a/examples/sugarscape_g1mt/sugarscape_g1mt/model.py +++ b/examples/sugarscape_g1mt/sugarscape_g1mt/model.py @@ -49,6 +49,7 @@ def __init__( metabolism_max=5, vision_min=1, vision_max=5, + enable_trade=True, ): # Initiate width and heigh of sugarscape self.width = width @@ -61,6 +62,7 @@ def __init__( self.metabolism_max = metabolism_max self.vision_min = vision_min self.vision_max = vision_max + self.enable_trade = enable_trade self.running = True # initiate activation schedule @@ -175,6 +177,11 @@ def step(self): agent.eat() agent.maybe_die() + if not self.enable_trade: + # If trade is not enabled, return early + self.datacollector.collect(self) + return + trader_shuffle = self.randomize_traders() for agent in trader_shuffle: diff --git a/examples/sugarscape_g1mt/tests.py b/examples/sugarscape_g1mt/tests.py new file mode 100644 index 00000000..bcfcf739 --- /dev/null +++ b/examples/sugarscape_g1mt/tests.py @@ -0,0 +1,72 @@ +import random + +import numpy as np +from scipy import stats +from sugarscape_g1mt.model import SugarscapeG1mt, flatten +from sugarscape_g1mt.trader_agents import Trader + +random.seed(1) + + +def check_slope(y, increasing): + x = range(len(y)) + slope, intercept, _, p_value, _ = stats.linregress(x, y) + result = (slope > 0) if increasing else (slope < 0) + # p_value for significance. + assert result and p_value < 0.05, (slope, p_value) + + +def test_decreasing_price_variance(): + # The variance of the average trade price should decrease over time (figure IV-3) + # See Growing Artificial Societies p. 109. + model = SugarscapeG1mt() + model.datacollector._new_model_reporter( + "price_variance", + lambda m: np.var( + flatten([a.prices for a in m.schedule.agents_by_type[Trader].values()]) + ), + ) + model.run_model(step_count=50) + + df_model = model.datacollector.get_model_vars_dataframe() + + check_slope(df_model.price_variance, increasing=False) + + +def test_carrying_capacity(): + def calculate_carrying_capacities(enable_trade): + carrying_capacities = [] + visions = range(1, 10) + for vision_max in visions: + model = SugarscapeG1mt(vision_max=vision_max, enable_trade=enable_trade) + model.run_model(step_count=50) + carrying_capacities.append(len(model.schedule.agents_by_type[Trader])) + return carrying_capacities + + # Carrying capacity should increase over mean vision (figure IV-6). + # See Growing Artificial Societies p. 112. + carrying_capacities_with_trade = calculate_carrying_capacities(True) + check_slope( + carrying_capacities_with_trade, + increasing=True, + ) + # Carrying capacity should be higher when trade is enabled (figure IV-6). + carrying_capacities_no_trade = calculate_carrying_capacities(False) + check_slope( + carrying_capacities_no_trade, + increasing=True, + ) + + t_statistic, p_value = stats.ttest_rel( + carrying_capacities_with_trade, carrying_capacities_no_trade + ) + # t_statistic > 0 means carrying_capacities_with_trade has larger values + # than carrying_capacities_no_trade. + # p_value for significance. + assert t_statistic > 0 and p_value < 0.05 + + +# TODO: +# 1. Reproduce figure IV-12 that the log of average price should decrease over average agent age +# 2. Reproduce figure IV-13 that the gini coefficient on trade should decrease over mean vision, and should be higher with trade +# 3. a stricter test would be to ensure the amount of variance of the trade price matches figure IV-3