evolutionary-game-dynamics 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ankur-tutlani
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,408 @@
1
+ Metadata-Version: 2.4
2
+ Name: evolutionary-game-dynamics
3
+ Version: 0.1.0
4
+ Summary: A Python library for simulating two-player strategic interactions with memory-based decision-making, exogenous shocks, and recovery dynamics.
5
+ Author-email: Ankur Tutlani <ankur.tutlani@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2026 ankur-tutlani
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/ankur-tutlani/evolutionary-game-dynamics
29
+ Project-URL: Documentation, https://github.com/ankur-tutlani/evolutionary-game-dynamics#readme
30
+ Project-URL: Repository, https://github.com/ankur-tutlani/evolutionary-game-dynamics.git
31
+ Project-URL: Issues, https://github.com/ankur-tutlani/evolutionary-game-dynamics/issues
32
+ Keywords: game-theory,evolutionary-dynamics,agent-based-simulation,monte-carlo,agent-based-modeling
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Science/Research
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: Topic :: Scientific/Engineering
37
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
38
+ Classifier: License :: OSI Approved :: MIT License
39
+ Classifier: Programming Language :: Python :: 3
40
+ Classifier: Programming Language :: Python :: 3.10
41
+ Classifier: Programming Language :: Python :: 3.11
42
+ Classifier: Programming Language :: Python :: 3.12
43
+ Requires-Python: >=3.11
44
+ Description-Content-Type: text/markdown
45
+ License-File: LICENSE
46
+ Requires-Dist: numpy>=2.0
47
+ Requires-Dist: pandas>=2.0
48
+ Requires-Dist: matplotlib>=3.5
49
+ Requires-Dist: seaborn>=0.12
50
+ Requires-Dist: openpyxl>=3.0
51
+ Provides-Extra: dev
52
+ Requires-Dist: pytest>=7.0; extra == "dev"
53
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
54
+ Dynamic: license-file
55
+
56
+ # Game Theory Simulation Library
57
+
58
+ ## Overview
59
+
60
+ This Python library simulates two-player strategic interactions with memory-based decision-making, exogenous shocks, and recovery dynamics. It is designed for research on **norm formation, stability, and recovery** following temporary disruptions to the payoff environment.
61
+
62
+ The project enables researchers to conduct large-scale Monte Carlo simulations across parameter spaces, analyze how agents' behavioral patterns respond to shocks, and measure recovery times to equilibrium. It is particularly useful for studying the robustness of social conventions and norms in dynamic game-theoretic settings.
63
+
64
+ ## Key Features
65
+
66
+ - **Two-Player Game Engine**: Configurable N×M games with arbitrary payoff matrices for both players
67
+ - **Memory-Based Behavior**: Agents remember past joint actions and base decisions on that history
68
+ - **Multiple Response Rules**:
69
+ - Exhaustive best responses (condition on distinct actions observed in history)
70
+ - Expected payoff maximization (empirical frequency-based expectations)
71
+ - Epsilon-greedy stochastic actions for bounded rationality
72
+ - **Shock Analysis**:
73
+ - Temporary payoff matrix changes to represent exogenous disruptions
74
+ - Track pre-shock, post-shock, and recovery-phase frequencies
75
+ - Measure time-to-recovery for each action pair
76
+ - **Initial State Modes**:
77
+ - Random initial histories
78
+ - Balanced distributions
79
+ - Canonical (exhaustive enumeration) distributions
80
+ - **Comprehensive Analysis & Visualization**:
81
+ - Pre/post-shock frequency comparisons
82
+ - Recovery time distributions
83
+ - Heatmaps across memory length and noise parameters
84
+ - Recovery rate statistics
85
+ - Individual trajectory plotting
86
+ - **Monte Carlo Sensitivity Analysis**: Systematic parameter sweeps with parallel Monte Carlo runs
87
+
88
+ ## Installation
89
+
90
+ ### Prerequisites
91
+ - Python 3.11+
92
+ - Conda (recommended) or pip
93
+
94
+ ### Using Conda (Recommended)
95
+
96
+ ```bash
97
+ conda env create -f environment.yml
98
+ conda activate game-sim-env
99
+ ```
100
+
101
+ ### Using pip
102
+
103
+ If you prefer pip, install the required packages:
104
+
105
+ ```bash
106
+ pip install numpy pandas matplotlib seaborn openpyxl
107
+ ```
108
+
109
+ ## Project Structure
110
+
111
+ ```
112
+ .
113
+ ├── README.md # This file
114
+ ├── environment.yml # Conda environment specification
115
+
116
+ ├── simulation.py # Core simulation engine
117
+ │ ├── simulate_two_player_game() # Basic game simulation
118
+ │ ├── simulate_with_shock() # Game simulation with payoff shocks
119
+ │ ├── run_sensitivity_with_shock() # Monte Carlo sensitivity analysis
120
+ │ └── compute_*_frequencies() # Recovery time calculations
121
+
122
+ ├── response_functions.py # Decision-making strategies
123
+ │ ├── exhaustive_epsilon_policy_*() # Exhaustive best response policies
124
+ │ ├── expected_payoff_policy_*() # Expected payoff maximization policies
125
+ │ └── unified_response() # Policy selector wrapper
126
+
127
+ ├── analysis.py # Result aggregation & reporting
128
+ │ ├── build_table_norm_epsilon_memory() # Master results table
129
+ │ ├── build_table_norm_shock_pair() # Shock outcome analysis
130
+ │ └── build_table_norm_initial_state_bin()# Initial state sensitivity
131
+
132
+ ├── plotting.py # Visualization functions
133
+ │ ├── plot_pre_post_frequency_overall() # Summary bar chart
134
+ │ ├── plot_recovery_*_allpairs() # Recovery vs parameters
135
+ │ ├── plot_pre_shock_heatmap_grid() # Heatmaps (2×2 subplots)
136
+ │ ├── plot_individual_run() # Trajectory visualization
137
+ │ ├── heatmap_standard_deviation_grid() # Variability analysis
138
+ │ └── generate_all_outputs() # Full report generation
139
+
140
+ ├── utils.py # Utility functions
141
+ │ ├── get_distinct_actions_from_history() # Extract observation set
142
+ │ ├── best_responses_*() # Best response calculation
143
+ │ ├── epsilon_greedy_choice() # Stochastic action selection
144
+ │ ├── generate_initial_history() # History initialization
145
+ │ ├── generate_all_distributions() # Canonical distribution enumeration
146
+ │ └── compute_initial_distribution() # Distribution parsing
147
+
148
+ └── examples.py # Example usage & parameter templates
149
+ ```
150
+
151
+ ## Usage
152
+
153
+ ### Basic Example: Single Simulation
154
+
155
+ ```python
156
+ from simulation import simulate_two_player_game
157
+
158
+ # Define a 2×2 game (Prisoner's Dilemma)
159
+ row_player_payoffs = [2, 0, 0, 1] # (C,C), (C,D), (D,C), (D,D)
160
+ col_player_payoffs = [1, 0, 0, 2]
161
+
162
+ # Run one trajectory
163
+ traj_df, freq_df = simulate_two_player_game(
164
+ num_rows=2,
165
+ num_cols=2,
166
+ memory_length=3,
167
+ timeperiod=100,
168
+ row_player_payoffs=row_player_payoffs,
169
+ column_player_payoffs=col_player_payoffs,
170
+ epsilon_row=0.05,
171
+ epsilon_col=0.05,
172
+ response_rule='exhaustive',
173
+ random_seed=42
174
+ )
175
+
176
+ print(traj_df.head()) # Time series of actions
177
+ print(freq_df) # Frequency of each joint action
178
+ ```
179
+
180
+ ### Shock Simulation
181
+
182
+ ```python
183
+ from simulation import simulate_with_shock
184
+
185
+ # Same setup, but with a temporary payoff shock
186
+ shock_payoffs_row = [2, 1, 1, 1]
187
+ shock_payoffs_col = [1, 1, 1, 2]
188
+
189
+ traj_df, freq_df = simulate_with_shock(
190
+ num_rows=2,
191
+ num_cols=2,
192
+ memory_length=3,
193
+ timeperiod=100,
194
+ row_player_payoffs=row_player_payoffs,
195
+ column_player_payoffs=col_player_payoffs,
196
+ shock_payoff_row=shock_payoffs_row,
197
+ shock_payoff_col=shock_payoffs_col,
198
+ epsilon_row=0.05,
199
+ epsilon_col=0.05,
200
+ shock_time=48, # Shock starts at t=48
201
+ shock_duration=4, # Lasts 4 periods
202
+ response_rule='exhaustive',
203
+ random_seed=42
204
+ )
205
+ ```
206
+
207
+ ### Full Monte Carlo Sensitivity Analysis
208
+
209
+ ```python
210
+ from simulation import run_sensitivity, generate_all_outputs
211
+ from utils import generate_all_distributions
212
+ import pandas as pd
213
+
214
+ # Parameters
215
+ num_rows, num_cols = 2, 2
216
+ row_payoffs = [2, 0, 0, 1]
217
+ col_payoffs = [1, 0, 0, 2]
218
+ shock_payoffs_row = [2, 1, 1, 1]
219
+ shock_payoffs_col = [1, 1, 1, 2]
220
+
221
+ # Sensitivity ranges
222
+ memory_lengths = [1, 2, 3, 4, 5]
223
+ epsilons = [0, 0.01, 0.05, 0.1, 0.15]
224
+ timeperiods = [100]
225
+ shock_times = [48]
226
+ shock_durations = [4]
227
+
228
+ # Precompute canonical distributions
229
+ canonical_sets = {}
230
+ for mem in memory_lengths:
231
+ canonical_sets[mem] = generate_all_distributions(num_rows, num_cols, mem)
232
+
233
+ # Run main analysis
234
+ results_df = run_sensitivity(
235
+ initial_state_mode='canonical', # or 'random', 'balanced'
236
+ num_rows=num_rows,
237
+ num_cols=num_cols,
238
+ row_player_payoffs=row_payoffs,
239
+ column_player_payoffs=col_payoffs,
240
+ shock_payoff_row=shock_payoffs_row,
241
+ shock_payoff_col=shock_payoffs_col,
242
+ memory_lengths=memory_lengths,
243
+ epsilons=epsilons,
244
+ timeperiods=timeperiods,
245
+ shock_times=shock_times,
246
+ shock_durations=shock_durations,
247
+ canonical_sets=canonical_sets,
248
+ n_runs_per_setting=10, # Monte Carlo runs per setting
249
+ export_dir='outputs/analysis',
250
+ iter_name='test_run_1',
251
+ random_seed=123,
252
+ response_rule='exhaustive'
253
+ )
254
+
255
+ # Generate all outputs (tables + visualizations)
256
+ outputs = generate_all_outputs(
257
+ results_df,
258
+ output_dir='outputs/figures'
259
+ )
260
+
261
+ # Access generated tables
262
+ table1 = outputs['table1'] # Master results table
263
+ table2 = outputs['table2'] # Initial state binned analysis
264
+ table3 = outputs['table3'] # Shock pair comparison
265
+ ```
266
+
267
+ ### Customizing Decision Rules
268
+
269
+ **Exhaustive Best Response Rule** (Default)
270
+ ```python
271
+ # Agents choose an action from the set of best responses to any distinct
272
+ # opponent action observed in history, plus epsilon-greedy exploration
273
+ response_rule = 'exhaustive'
274
+ ```
275
+
276
+ **Expected Payoff Rule**
277
+ ```python
278
+ # Agents estimate empirical frequency distribution of opponent actions,
279
+ # compute expected payoffs for each own action, then pick best responses
280
+ # plus epsilon-greedy exploration
281
+ response_rule = 'expected'
282
+ ```
283
+
284
+ ### Output Files
285
+
286
+ The library generates two types of outputs:
287
+
288
+ **1. Data Files** (`export_dir`)
289
+ - `monte_carlo_results_[iter_name].xlsx` — Main results table (one row per setting, run, and pair)
290
+ - `traj_mem*.xlsx` — Individual trajectory files for each run
291
+
292
+ **2. Analysis Files** (`output_dir`)
293
+ - `table_norm_epsilon_memory.xlsx` — Summary statistics by memory length and epsilon
294
+ - `table_norm_initial_state_bin.xlsx` — Recovery by initial state distribution
295
+ - `table_norm_shock_pair.xlsx` — Results by shock joint action
296
+ - PNG visualizations — Heatmaps and line plots
297
+
298
+ ## Configuration Parameters
299
+
300
+ ### Game Setup
301
+ | Parameter | Type | Description |
302
+ |-----------|------|-------------|
303
+ | `num_rows`, `num_cols` | int | Game dimensions (rows = row player actions, cols = column player actions) |
304
+ | `row_player_payoffs` | list | Payoff vector for row player (length = rows × cols) |
305
+ | `column_player_payoffs` | list | Payoff vector for column player (length = rows × cols) |
306
+ | `shock_payoff_row` | list | Payoff vector during shock period for row player |
307
+ | `shock_payoff_col` | list | Payoff vector during shock period for column player |
308
+
309
+ ### Behavioral Parameters
310
+ | Parameter | Type | Default | Description |
311
+ |-----------|------|---------|-------------|
312
+ | `memory_length` | int | — | Number of past joint actions agents remember |
313
+ | `epsilon_row`, `epsilon_col` | float | 0.1 | Probability of random action (per player) |
314
+ | `response_rule` | str | 'exhaustive' | Decision rule: 'exhaustive' or 'expected' |
315
+ | `initial_state_mode` | str | — | How to initialize: 'random', 'balanced', or 'canonical' |
316
+
317
+ ### Simulation Parameters
318
+ | Parameter | Type | Description |
319
+ |-----------|------|-------------|
320
+ | `timeperiod` | int | Number of periods to simulate |
321
+ | `shock_time` | int | Period when shock begins (or None for no shock) |
322
+ | `shock_duration` | int | Number of periods shock lasts |
323
+ | `n_runs_per_setting` | int | Monte Carlo replications per parameter combination |
324
+ | `random_seed` | int | Seed for reproducibility |
325
+
326
+ ## Key Functions Reference
327
+
328
+ ### Core Simulation
329
+ - `simulate_two_player_game()` — Run a single game trajectory
330
+ - `simulate_with_shock()` — Single trajectory with payoff shock
331
+ - `run_sensitivity()` — Full Monte Carlo analysis across parameter grid
332
+
333
+ ### Analysis
334
+ - `build_table_norm_epsilon_memory()` — Aggregate by memory and epsilon
335
+ - `build_table_norm_shock_pair()` — Compare shock outcomes
336
+ - `build_table_norm_initial_state_bin()` — Bin by initial state and compare
337
+
338
+ ### Visualization
339
+ - `generate_all_outputs()` — Produces all tables and plots automatically
340
+ - `plot_recovery_heatmap_grid()` — 2×2 grid of recovery time heatmaps
341
+ - `plot_pre_shock_heatmap_grid()` — 2×2 grid of pre-shock frequency heatmaps
342
+ - `plot_individual_run()` — Trajectory visualization with shock window
343
+
344
+ ## Theory Background
345
+
346
+ This library implements a **memory-based best-response model** of strategic interaction:
347
+
348
+ 1. **History**: Each agent maintains a list of recent joint actions (payoff pairs played)
349
+ 2. **Play**: At each time step:
350
+ - Extract distinct opponent actions from history
351
+ - Compute best responses (or expected payoffs)
352
+ - Apply epsilon-greedy action selection
353
+ 3. **Update**: Add the joint action to history (remove oldest if memory exceeded)
354
+ 4. **Shock**: Optionally change payoff matrices temporarily
355
+ 5. **Recovery**: Track time until behavior returns to pre-shock norm
356
+
357
+ **Key metric**: *Recovery time* = periods until a norm's frequency returns to pre-shock level.
358
+
359
+ ## Dependencies
360
+
361
+ - `numpy` — Numerical computing
362
+ - `pandas` — Data manipulation and analysis
363
+ - `matplotlib` — Plotting library
364
+ - `seaborn` — Statistical visualization
365
+ - `openpyxl` — Excel file I/O
366
+
367
+ All included in `environment.yml`.
368
+
369
+ ## Example Workflow
370
+
371
+ See [examples.py](examples.py) for a complete working example showing:
372
+ 1. Parameter setup
373
+ 2. Canonical distribution precomputation
374
+ 3. Sensitivity analysis execution
375
+ 4. Output generation
376
+ 5. Result visualization
377
+
378
+ To run the example:
379
+ ```bash
380
+ python examples.py
381
+ ```
382
+
383
+ ## Output Interpretation
384
+
385
+ ### Pre/Post Shock Frequencies
386
+ Shows how frequently each joint action is played before / after the shock. Used to identify which norms are disrupted and which persist.
387
+
388
+ ### Recovery Time
389
+ Number of periods until a norm's frequency returns to at least its pre-shock level. Longer recovery = less resilient norms.
390
+
391
+ ### Heatmaps
392
+ Visualize how recovery time (or pre-shock frequency) varies across memory length and epsilon parameters. Identify which parameter combinations produce robust vs. fragile norms.
393
+
394
+ ## Notes for Users
395
+
396
+ - **Random Seed**: Set `random_seed` for reproducibility
397
+ - **Memory Length**: Larger memory captures more history but increases state space
398
+ - **Epsilon**: Higher epsilon = more randomness = less stable norms
399
+ - **Initial State**: 'canonical' requires precomputation but covers the full distribution space
400
+ - **Export Path**: Create parent directories in advance if needed; the library will handle subdirectory creation
401
+
402
+ ## License
403
+
404
+ MIT License
405
+
406
+ ## Citation
407
+
408
+ If you use this library in your research, please cite it appropriately. Citation format to be added upon publication.