-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Hawk/dove batch run code for data analysis (#60)
* Suppress convergence print statement for batch runs * Customize risk category string output for batch run data collection * Custom batch runner for hawk/dove multi risk sim * Configure hawk/dove multi batch run to be installed as a script * Step argument needs to be an integer also * Revise multiprocessing based on slurm cpu efficiency report * Correct basic hawk/dove readme for scope * Use configured neighborhood sizes; correct typo in help string * Add file prefix option for generated data files * Turn random play odds completely off by default * Add readme for hawk/dove multi risk game; document batch running * Add sample batch run slurm script * Update simulatingrisk/hawkdove/model.py Co-authored-by: Laure Thompson <[email protected]> * Update simulatingrisk/hawkdovemulti/README.md Co-authored-by: Laure Thompson <[email protected]> * Update simulatingrisk/hawkdovemulti/batch_run.py Co-authored-by: Laure Thompson <[email protected]> * Update simulatingrisk/hawkdovemulti/simrisk_batch.slurm Co-authored-by: Laure Thompson <[email protected]> * Update simulatingrisk/hawkdovemulti/batch_run.py Co-authored-by: Laure Thompson <[email protected]> * Cleanup items flagged by @laurejt in code review * Set file prefix default in argparse for consistency * Document step offset between scheduler and data collection * Better documentation for sample slurm batch script --------- Co-authored-by: Laure Thompson <[email protected]>
- Loading branch information
Showing
8 changed files
with
320 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Hawk-Dove with multiple risk attitudes | ||
|
||
This is a variation of the [Hawk/Dove game with risk attitudes](../hawkdove/). | ||
This version adds multiple risk attitudes, with options for updating | ||
risk attitudes periodically based on comparing success of neighboring agents. | ||
|
||
The basic mechanics of the game are the same. This model adds options | ||
for agent risk adjustment (none, adopt, average) and period of risk | ||
adjustment (by default, every ten rounds). The payoff used to compare | ||
agents when adjusting risk attitudes can either be recent (since the | ||
last adjustment round) or total points for the whole game. The | ||
adjustment neighborhood, or which neighboring agents are considered | ||
when adjusting risk attitudes, can be configured to 4, 8, or 24. | ||
|
||
Initial risk attitudes are set by the model. Risk distribution can | ||
be configured to use a normal distribution, uniform (random), bimodal, | ||
skewed left, or skewed right. | ||
|
||
Like the base hawk/dove risk attitude game, there is also a | ||
configuration to add some chance of agents playing hawk/dove randomly | ||
instead of choosing based on the rules of the game. | ||
|
||
## Batch running | ||
|
||
This module includes a custom batch run script to run the simulation and | ||
collect data across a large combination of parameters and generate data | ||
files with collected model and agent data. | ||
|
||
To run the script locally from the root project directory: | ||
```sh | ||
simulatingrisk/hawkdovemulti/batch_run.py | ||
``` | ||
Use `-h` or `--help` to see options. | ||
|
||
If this project has been installed with pip or similar, the script is | ||
available as `simrisk-hawkdovemulti-batchrun`. | ||
|
||
To run the batch run script on an HPC cluster: | ||
|
||
- Create a conda environment and install dependencies and this project. | ||
(Major mesa dependencies available with conda are installed first as | ||
conda packages) | ||
|
||
```sh | ||
module load anaconda3/2023.9 | ||
conda create --name simrisk pandas networkx matplotlib numpy tqdm click | ||
conda activate simrisk | ||
pip install git+https://github.com/Princeton-CDH/simulating-risk.git@hawkdove-batchrun | ||
``` | ||
For convenience, an example [slurm batch script](simrisk_batch.slurm) is | ||
included for running the batch run script (some portions are | ||
specific to Princeton's Research Computing HPC environment.) | ||
|
||
- Customize the slurm batch script as desired, copy it to the cluster, and submit | ||
the job: `sbatch simrisk_batch.slurm` | ||
|
||
By default, the batch run script will use all available processors, and will | ||
create model and agent data files under a `data/hawkdovemulti/` directory | ||
relative to the working directory where the script is called. | ||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
#!/usr/bin/env python | ||
|
||
import argparse | ||
import csv | ||
from datetime import datetime | ||
import multiprocessing | ||
import os | ||
|
||
from tqdm.auto import tqdm | ||
|
||
from mesa.batchrunner import _make_model_kwargs, _collect_data | ||
|
||
from simulatingrisk.hawkdovemulti.model import HawkDoveMultipleRiskModel | ||
|
||
|
||
neighborhood_sizes = list(HawkDoveMultipleRiskModel.neighborhood_sizes) | ||
|
||
# combination of parameters we want to run | ||
params = { | ||
"grid_size": [10, 25, 50], # 100], | ||
"risk_adjustment": ["adopt", "average"], | ||
"play_neighborhood": neighborhood_sizes, | ||
"observed_neighborhood": neighborhood_sizes, | ||
"adjust_neighborhood": neighborhood_sizes, | ||
"hawk_odds": [0.5, 0.25, 0.75], | ||
"adjust_every": [2, 10, 20], | ||
"risk_distribution": HawkDoveMultipleRiskModel.risk_distribution_options, | ||
"adjust_payoff": HawkDoveMultipleRiskModel.supported_adjust_payoffs, | ||
# random? | ||
} | ||
|
||
|
||
# method for multiproc running model with a set of params | ||
def run_hawkdovemulti_model(args): | ||
run_id, iteration, params, max_steps = args | ||
# simplified model runner adapted from mesa batch run code | ||
|
||
model = HawkDoveMultipleRiskModel(**params) | ||
while model.running and model.schedule.steps <= max_steps: | ||
model.step() | ||
|
||
# collect data for the last step | ||
# (scheduler is 1-based index but data collection is 0-based) | ||
step = model.schedule.steps - 1 | ||
|
||
model_data, all_agents_data = _collect_data(model, step) | ||
|
||
# combine run id, step, and params, with collected model data | ||
run_data = {"RunId": run_id, "iteration": iteration, "Step": step} | ||
run_data.update(params) | ||
run_data.update(model_data) | ||
|
||
agent_data = [ | ||
{ | ||
"RunId": run_id, | ||
"iteration": iteration, | ||
"Step": step, | ||
**agent_data, | ||
} | ||
for agent_data in all_agents_data | ||
] | ||
|
||
return run_data, agent_data | ||
|
||
|
||
def batch_run( | ||
params, iterations, number_processes, max_steps, progressbar, file_prefix | ||
): | ||
param_combinations = _make_model_kwargs(params) | ||
total_param_combinations = len(param_combinations) | ||
total_runs = total_param_combinations * iterations | ||
print( | ||
f"{total_param_combinations} parameter combinations, " | ||
+ f"{iterations} iteration{'s' if iterations != 1 else ''}, " | ||
+ f"{total_runs} total runs" | ||
) | ||
|
||
# create a list of all the parameters to run, with run id and iteration | ||
runs_list = [] | ||
run_id = 0 | ||
for params in param_combinations: | ||
for iteration in range(iterations): | ||
runs_list.append((run_id, iteration, params, max_steps)) | ||
run_id += 1 | ||
|
||
# collect data in a directory for this model | ||
data_dir = os.path.join("data", "hawkdovemulti") | ||
os.makedirs(data_dir, exist_ok=True) | ||
datestr = datetime.today().isoformat().replace(".", "_").replace(":", "") | ||
model_output_filename = os.path.join(data_dir, f"{file_prefix}{datestr}_model.csv") | ||
agent_output_filename = os.path.join(data_dir, f"{file_prefix}{datestr}_agent.csv") | ||
print( | ||
f"Saving data collection results to:\n {model_output_filename}" | ||
+ f"\n {agent_output_filename}" | ||
) | ||
# open output files so data can be written as it is generated | ||
with open(model_output_filename, "w", newline="") as model_output_file, open( | ||
agent_output_filename, "w", newline="" | ||
) as agent_output_file: | ||
model_dict_writer = None | ||
agent_dict_writer = None | ||
|
||
# adapted from mesa batch run code | ||
with tqdm(total=total_runs, disable=not progressbar) as pbar: | ||
with multiprocessing.Pool(number_processes) as pool: | ||
for model_data, agent_data in pool.imap_unordered( | ||
run_hawkdovemulti_model, runs_list | ||
): | ||
# initialize dictwriter and start csv after the first batch | ||
if model_dict_writer is None: | ||
# get field names from first entry | ||
model_dict_writer = csv.DictWriter( | ||
model_output_file, model_data.keys() | ||
) | ||
model_dict_writer.writeheader() | ||
|
||
model_dict_writer.writerow(model_data) | ||
|
||
if agent_dict_writer is None: | ||
# get field names from first entry | ||
agent_dict_writer = csv.DictWriter( | ||
agent_output_file, agent_data[0].keys() | ||
) | ||
agent_dict_writer.writeheader() | ||
|
||
agent_dict_writer.writerows(agent_data) | ||
|
||
pbar.update() | ||
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser( | ||
prog="hawk/dove batch_run", | ||
description="Batch run for hawk/dove multiple risk attitude simulation.", | ||
epilog="""Data files will be created in data/hawkdovemulti/ | ||
relative to current path.""", | ||
) | ||
parser.add_argument( | ||
"-i", | ||
"--iterations", | ||
type=int, | ||
help="Number of iterations to run for each set of parameters " | ||
+ "(default: %(default)s)", | ||
default=100, | ||
) | ||
parser.add_argument( | ||
"-m", | ||
"--max-steps", | ||
help="Maximum steps to run simulations if they have not already " | ||
+ "converged (default: %(default)s)", | ||
default=125, # typically converges quickly, around step 60 without randomness | ||
type=int, | ||
) | ||
parser.add_argument( | ||
"-p", | ||
"--processes", | ||
type=int, | ||
help="Number of processes to use (default: all available CPUs)", | ||
default=None, | ||
) | ||
parser.add_argument( | ||
"--progress", | ||
help="Display progress bar (default: %(default)s)", | ||
action=argparse.BooleanOptionalAction, | ||
default=True, | ||
) | ||
parser.add_argument( | ||
"--file-prefix", | ||
help="Prefix for data filenames (no prefix by default)", | ||
default="", | ||
) | ||
# may want to add an option to configure output dir | ||
|
||
args = parser.parse_args() | ||
batch_run( | ||
params, | ||
args.iterations, | ||
args.processes, | ||
args.max_steps, | ||
args.progress, | ||
args.file_prefix, | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
#!/bin/bash | ||
#SBATCH --job-name=simrisk # job short name | ||
#SBATCH --nodes=1 # node count | ||
#SBATCH --ntasks=1 # total number of tasks across all nodes | ||
#SBATCH --cpus-per-task=20 # cpu-cores per task | ||
#SBATCH --mem-per-cpu=525M # memory per cpu-core | ||
#SBATCH --time=02:00:00 # total run time limit (HH:MM:SS) | ||
#SBATCH --mail-type=begin # send email when job begins | ||
#SBATCH --mail-type=end # send email when job ends | ||
#SBATCH --mail-type=fail # send email if job fails | ||
#SBATCH --mail-user=EMAIL | ||
|
||
# Template for batch running hawkdovemulti simulation with slurm. | ||
# Assumes a conda environment named simrisk is set up with required dependencies. | ||
# | ||
# Update before using: | ||
# - EMAIL for slurm notification | ||
# - customize path for working directory (set username if using Princeton HPC) | ||
# (and make sure the directory exists) | ||
# - add an SBATCH array directive if desired | ||
# - customize the batch run command as appropriate | ||
# - configure the time appropriately for the batch run | ||
|
||
module purge | ||
module load anaconda3/2023.9 | ||
conda activate simrisk | ||
|
||
# change working directory for data output | ||
cd /scratch/network/<USER>/simrisk | ||
|
||
# test run: one iteration, max of 200 steps, no progress bar | ||
# (completed in ~18 minutes on 20 CPUs) | ||
#simrisk-hawkdovemulti-batchrun --iterations 1 --max-step 200 --no-progress | ||
|
||
# longer run: 10 iterations, max of 200 steps, no progress bar | ||
#simrisk-hawkdovemulti-batchrun --iterations 10 --max-step 200 --no-progress | ||
|
||
# To generate data for a larger total number of iterations, | ||
# run the script as a job array. | ||
# e.g. for 100 iterations, run with --iterations 10 and 10 tasks with #SBATCH --array=0-9 | ||
# and add a file prefix option to generate separate files that can be grouped | ||
simrisk-hawkdovemulti-batchrun --iterations 10 --max-step 125 --no-progress --file-prefix "job${SLURM_ARRAY_JOB_ID}_task${SLURM_ARRAY_TASK_ID}_" | ||
|
||
|
Oops, something went wrong.