{"id":49578508,"url":"https://github.com/ankur-tutlani/evolutionary-game-dynamics","last_synced_at":"2026-05-03T18:07:16.939Z","repository":{"id":354220160,"uuid":"1163688118","full_name":"ankur-tutlani/evolutionary-game-dynamics","owner":"ankur-tutlani","description":"A Python library for simulating two‑player strategic interactions with memory, shocks, and recovery dynamics. It supports large‑scale Monte Carlo experiments to study how behavioral norms form, adapt, and regain stability after temporary disruptions.","archived":false,"fork":false,"pushed_at":"2026-02-22T15:16:55.000Z","size":22,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T18:13:40.189Z","etag":null,"topics":["agent-based-modeling","agent-based-simulation","evolutionary-algorithms","evolutionary-dynamics","evolutionary-game-simulations","evolutionary-game-theory","gametheory","monte-carlo-simulation","normal-form-games","norms","social-norms"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ankur-tutlani.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-22T01:40:52.000Z","updated_at":"2026-02-22T15:24:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ankur-tutlani/evolutionary-game-dynamics","commit_stats":null,"previous_names":["ankur-tutlani/evolutionary-game-dynamics"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ankur-tutlani/evolutionary-game-dynamics","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankur-tutlani%2Fevolutionary-game-dynamics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankur-tutlani%2Fevolutionary-game-dynamics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankur-tutlani%2Fevolutionary-game-dynamics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankur-tutlani%2Fevolutionary-game-dynamics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ankur-tutlani","download_url":"https://codeload.github.com/ankur-tutlani/evolutionary-game-dynamics/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankur-tutlani%2Fevolutionary-game-dynamics/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32579120,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-03T06:36:36.687Z","status":"ssl_error","status_checked_at":"2026-05-03T06:36:09.306Z","response_time":103,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-based-modeling","agent-based-simulation","evolutionary-algorithms","evolutionary-dynamics","evolutionary-game-simulations","evolutionary-game-theory","gametheory","monte-carlo-simulation","normal-form-games","norms","social-norms"],"created_at":"2026-05-03T18:07:16.314Z","updated_at":"2026-05-03T18:07:16.932Z","avatar_url":"https://github.com/ankur-tutlani.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Game Theory Simulation Library\n\n## Overview\n\nThis 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.\n\nThe 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.\n\n## Key Features\n\n- **Two-Player Game Engine**: Configurable N×M games with arbitrary payoff matrices for both players\n- **Memory-Based Behavior**: Agents remember past joint actions and base decisions on that history\n- **Multiple Response Rules**: \n  - Exhaustive best responses (condition on distinct actions observed in history)\n  - Expected payoff maximization (empirical frequency-based expectations)\n  - Epsilon-greedy stochastic actions for bounded rationality\n- **Shock Analysis**: \n  - Temporary payoff matrix changes to represent exogenous disruptions\n  - Track pre-shock, post-shock, and recovery-phase frequencies\n  - Measure time-to-recovery for each action pair\n- **Initial State Modes**: \n  - Random initial histories\n  - Balanced distributions\n  - Canonical (exhaustive enumeration) distributions\n- **Comprehensive Analysis \u0026 Visualization**:\n  - Pre/post-shock frequency comparisons\n  - Recovery time distributions\n  - Heatmaps across memory length and noise parameters\n  - Recovery rate statistics\n  - Individual trajectory plotting\n- **Monte Carlo Sensitivity Analysis**: Systematic parameter sweeps with parallel Monte Carlo runs\n\n## Installation\n\n### Prerequisites\n- Python 3.11+\n- Conda (recommended) or pip\n\n### Using Conda (Recommended)\n\n```bash\nconda env create -f environment.yml\nconda activate game-sim-env\n```\n\n### Using pip\n\nIf you prefer pip, install the required packages:\n\n```bash\npip install numpy pandas matplotlib seaborn openpyxl\n```\n\n## Project Structure\n\n```\n.\n├── README.md                      # This file\n├── environment.yml                # Conda environment specification\n│\n├── simulation.py                  # Core simulation engine\n│   ├── simulate_two_player_game()          # Basic game simulation\n│   ├── simulate_with_shock()               # Game simulation with payoff shocks\n│   ├── run_sensitivity_with_shock()        # Monte Carlo sensitivity analysis\n│   └── compute_*_frequencies()             # Recovery time calculations\n│\n├── response_functions.py          # Decision-making strategies\n│   ├── exhaustive_epsilon_policy_*()       # Exhaustive best response policies\n│   ├── expected_payoff_policy_*()          # Expected payoff maximization policies\n│   └── unified_response()                  # Policy selector wrapper\n│\n├── analysis.py                    # Result aggregation \u0026 reporting\n│   ├── build_table_norm_epsilon_memory()   # Master results table\n│   ├── build_table_norm_shock_pair()       # Shock outcome analysis\n│   └── build_table_norm_initial_state_bin()# Initial state sensitivity\n│\n├── plotting.py                    # Visualization functions\n│   ├── plot_pre_post_frequency_overall()   # Summary bar chart\n│   ├── plot_recovery_*_allpairs()          # Recovery vs parameters\n│   ├── plot_pre_shock_heatmap_grid()       # Heatmaps (2×2 subplots)\n│   ├── plot_individual_run()               # Trajectory visualization\n│   ├── heatmap_standard_deviation_grid()   # Variability analysis\n│   └── generate_all_outputs()              # Full report generation\n│\n├── utils.py                       # Utility functions\n│   ├── get_distinct_actions_from_history() # Extract observation set\n│   ├── best_responses_*()                  # Best response calculation\n│   ├── epsilon_greedy_choice()             # Stochastic action selection\n│   ├── generate_initial_history()          # History initialization\n│   ├── generate_all_distributions()        # Canonical distribution enumeration\n│   └── compute_initial_distribution()      # Distribution parsing\n│\n└── examples.py                    # Example usage \u0026 parameter templates\n```\n\n## Usage\n\n### Basic Example: Single Simulation\n\n```python\nfrom simulation import simulate_two_player_game\n\n# Define a 2×2 game (Prisoner's Dilemma)\nrow_player_payoffs = [2, 0, 0, 1]    # (C,C), (C,D), (D,C), (D,D)\ncol_player_payoffs = [1, 0, 0, 2]\n\n# Run one trajectory\ntraj_df, freq_df = simulate_two_player_game(\n    num_rows=2,\n    num_cols=2,\n    memory_length=3,\n    timeperiod=100,\n    row_player_payoffs=row_player_payoffs,\n    column_player_payoffs=col_player_payoffs,\n    epsilon_row=0.05,\n    epsilon_col=0.05,\n    response_rule='exhaustive',\n    random_seed=42\n)\n\nprint(traj_df.head())        # Time series of actions\nprint(freq_df)              # Frequency of each joint action\n```\n\n### Shock Simulation\n\n```python\nfrom simulation import simulate_with_shock\n\n# Same setup, but with a temporary payoff shock\nshock_payoffs_row = [2, 1, 1, 1]\nshock_payoffs_col = [1, 1, 1, 2]\n\ntraj_df, freq_df = simulate_with_shock(\n    num_rows=2,\n    num_cols=2,\n    memory_length=3,\n    timeperiod=100,\n    row_player_payoffs=row_player_payoffs,\n    column_player_payoffs=col_player_payoffs,\n    shock_payoff_row=shock_payoffs_row,\n    shock_payoff_col=shock_payoffs_col,\n    epsilon_row=0.05,\n    epsilon_col=0.05,\n    shock_time=48,              # Shock starts at t=48\n    shock_duration=4,           # Lasts 4 periods\n    response_rule='exhaustive',\n    random_seed=42\n)\n```\n\n### Full Monte Carlo Sensitivity Analysis\n\n```python\nfrom simulation import run_sensitivity, generate_all_outputs\nfrom utils import generate_all_distributions\nimport pandas as pd\n\n# Parameters\nnum_rows, num_cols = 2, 2\nrow_payoffs = [2, 0, 0, 1]\ncol_payoffs = [1, 0, 0, 2]\nshock_payoffs_row = [2, 1, 1, 1]\nshock_payoffs_col = [1, 1, 1, 2]\n\n# Sensitivity ranges\nmemory_lengths = [1, 2, 3, 4, 5]\nepsilons = [0, 0.01, 0.05, 0.1, 0.15]\ntimeperiods = [100]\nshock_times = [48]\nshock_durations = [4]\n\n# Precompute canonical distributions\ncanonical_sets = {}\nfor mem in memory_lengths:\n    canonical_sets[mem] = generate_all_distributions(num_rows, num_cols, mem)\n\n# Run main analysis\nresults_df = run_sensitivity(\n    initial_state_mode='canonical',  # or 'random', 'balanced'\n    num_rows=num_rows,\n    num_cols=num_cols,\n    row_player_payoffs=row_payoffs,\n    column_player_payoffs=col_payoffs,\n    shock_payoff_row=shock_payoffs_row,\n    shock_payoff_col=shock_payoffs_col,\n    memory_lengths=memory_lengths,\n    epsilons=epsilons,\n    timeperiods=timeperiods,\n    shock_times=shock_times,\n    shock_durations=shock_durations,\n    canonical_sets=canonical_sets,\n    n_runs_per_setting=10,          # Monte Carlo runs per setting\n    export_dir='outputs/analysis',\n    iter_name='test_run_1',\n    random_seed=123,\n    response_rule='exhaustive'\n)\n\n# Generate all outputs (tables + visualizations)\noutputs = generate_all_outputs(\n    results_df, \n    output_dir='outputs/figures'\n)\n\n# Access generated tables\ntable1 = outputs['table1']  # Master results table\ntable2 = outputs['table2']  # Initial state binned analysis\ntable3 = outputs['table3']  # Shock pair comparison\n```\n\n### Customizing Decision Rules\n\n**Exhaustive Best Response Rule** (Default)\n```python\n# Agents choose an action from the set of best responses to any distinct \n# opponent action observed in history, plus epsilon-greedy exploration\nresponse_rule = 'exhaustive'\n```\n\n**Expected Payoff Rule**\n```python\n# Agents estimate empirical frequency distribution of opponent actions,\n# compute expected payoffs for each own action, then pick best responses\n# plus epsilon-greedy exploration\nresponse_rule = 'expected'\n```\n\n### Output Files\n\nThe library generates two types of outputs:\n\n**1. Data Files** (`export_dir`)\n- `monte_carlo_results_[iter_name].xlsx` — Main results table (one row per setting, run, and pair)\n- `traj_mem*.xlsx` — Individual trajectory files for each run\n\n**2. Analysis Files** (`output_dir`)\n- `table_norm_epsilon_memory.xlsx` — Summary statistics by memory length and epsilon\n- `table_norm_initial_state_bin.xlsx` — Recovery by initial state distribution\n- `table_norm_shock_pair.xlsx` — Results by shock joint action\n- PNG visualizations — Heatmaps and line plots\n\n## Configuration Parameters\n\n### Game Setup\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `num_rows`, `num_cols` | int | Game dimensions (rows = row player actions, cols = column player actions) |\n| `row_player_payoffs` | list | Payoff vector for row player (length = rows × cols) |\n| `column_player_payoffs` | list | Payoff vector for column player (length = rows × cols) |\n| `shock_payoff_row` | list | Payoff vector during shock period for row player |\n| `shock_payoff_col` | list | Payoff vector during shock period for column player |\n\n### Behavioral Parameters\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `memory_length` | int | — | Number of past joint actions agents remember |\n| `epsilon_row`, `epsilon_col` | float | 0.1 | Probability of random action (per player) |\n| `response_rule` | str | 'exhaustive' | Decision rule: 'exhaustive' or 'expected' |\n| `initial_state_mode` | str | — | How to initialize: 'random', 'balanced', or 'canonical' |\n\n### Simulation Parameters\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `timeperiod` | int | Number of periods to simulate |\n| `shock_time` | int | Period when shock begins (or None for no shock) |\n| `shock_duration` | int | Number of periods shock lasts |\n| `n_runs_per_setting` | int | Monte Carlo replications per parameter combination |\n| `random_seed` | int | Seed for reproducibility |\n\n## Key Functions Reference\n\n### Core Simulation\n- `simulate_two_player_game()` — Run a single game trajectory\n- `simulate_with_shock()` — Single trajectory with payoff shock\n- `run_sensitivity()` — Full Monte Carlo analysis across parameter grid\n\n### Analysis\n- `build_table_norm_epsilon_memory()` — Aggregate by memory and epsilon\n- `build_table_norm_shock_pair()` — Compare shock outcomes\n- `build_table_norm_initial_state_bin()` — Bin by initial state and compare\n\n### Visualization\n- `generate_all_outputs()` — Produces all tables and plots automatically\n- `plot_recovery_heatmap_grid()` — 2×2 grid of recovery time heatmaps\n- `plot_pre_shock_heatmap_grid()` — 2×2 grid of pre-shock frequency heatmaps\n- `plot_individual_run()` — Trajectory visualization with shock window\n\n## Theory Background\n\nThis library implements a **memory-based best-response model** of strategic interaction:\n\n1. **History**: Each agent maintains a list of recent joint actions (payoff pairs played)\n2. **Play**: At each time step:\n   - Extract distinct opponent actions from history\n   - Compute best responses (or expected payoffs)\n   - Apply epsilon-greedy action selection\n3. **Update**: Add the joint action to history (remove oldest if memory exceeded)\n4. **Shock**: Optionally change payoff matrices temporarily\n5. **Recovery**: Track time until behavior returns to pre-shock norm\n\n**Key metric**: *Recovery time* = periods until a norm's frequency returns to pre-shock level.\n\n## Dependencies\n\n- `numpy` — Numerical computing\n- `pandas` — Data manipulation and analysis\n- `matplotlib` — Plotting library\n- `seaborn` — Statistical visualization\n- `openpyxl` — Excel file I/O\n\nAll included in `environment.yml`.\n\n## Example Workflow\n\nSee [examples.py](examples.py) for a complete working example showing:\n1. Parameter setup\n2. Canonical distribution precomputation\n3. Sensitivity analysis execution\n4. Output generation\n5. Result visualization\n\nTo run the example:\n```bash\npython examples.py\n```\n\n## Output Interpretation\n\n### Pre/Post Shock Frequencies\nShows how frequently each joint action is played before / after the shock. Used to identify which norms are disrupted and which persist.\n\n### Recovery Time\nNumber of periods until a norm's frequency returns to at least its pre-shock level. Longer recovery = less resilient norms.\n\n### Heatmaps\nVisualize how recovery time (or pre-shock frequency) varies across memory length and epsilon parameters. Identify which parameter combinations produce robust vs. fragile norms.\n\n## Notes for Users\n\n- **Random Seed**: Set `random_seed` for reproducibility\n- **Memory Length**: Larger memory captures more history but increases state space\n- **Epsilon**: Higher epsilon = more randomness = less stable norms\n- **Initial State**: 'canonical' requires precomputation but covers the full distribution space\n- **Export Path**: Create parent directories in advance if needed; the library will handle subdirectory creation\n\n## License\n\nMIT License\n\n## Citation\n\nIf you use this library in your research, please cite it appropriately. Citation format to be added upon publication.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fankur-tutlani%2Fevolutionary-game-dynamics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fankur-tutlani%2Fevolutionary-game-dynamics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fankur-tutlani%2Fevolutionary-game-dynamics/lists"}