Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add built-in function for local sensitivity analysis #1908

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions mesa/batchrunner.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import inspect
import itertools
from functools import partial
from multiprocessing import Pool
Expand Down Expand Up @@ -198,3 +199,114 @@
agent_dict.update(zip(dc.agent_reporters, data[2:]))
all_agents_data.append(agent_dict)
return model_data, all_agents_data


def local_sensitivity_analysis(
model_cls: Type[Model],
common_range: List[float] = [0.8, 1.25],
specific_ranges: Optional[Dict[str, List[float]]] = None,
ignore_parameters: Optional[List[str]] = None,
relative_range: bool = True,
iterations: int = 10,
**kwargs
) -> List[Dict[str, Any]]:
"""
Performs a sensitivity analysis on a given Mesa agent-based model class,
exploring how changes in model parameters affect the model's behavior.
This function allows for both relative and absolute variations in parameter values.

Parameters
----------
model_cls : Type[Model]
The Mesa model class to be analyzed. Must be a subclass of mesa.Model.

common_range : List[float], default=[0.8, 1.25]
A list of multipliers (for relative range) or values (for absolute range)
to be applied to each numeric parameter, unless overridden by specific_ranges.

specific_ranges : Optional[Dict[str, List[float]]], default=None
A dictionary mapping parameter names to lists of multipliers or values.
These specific ranges override the common_range for the respective parameters.

ignore_parameters : Optional[List[str]], default=None
A list of parameter names to be excluded from the sensitivity analysis.

relative_range : bool, default=True
Determines whether the values in common_range and specific_ranges are
treated as relative multipliers (True) or absolute values (False).
Relative multipliers are multiplied by the default parameter value,
while absolute values are used as-is.

iterations : int, default=10
The number of iterations to run for each parameter value. Is passed directly
to the Mesa batch_run function.

**kwargs
Additional keyword arguments to be passed directly to the Mesa batch_run function.
These can include 'iterations', 'max_steps', 'data_collection_period', etc.

Returns
-------
List[Dict[str, Any]]
A list of dictionaries, each representing the results of one model run.
Each dictionary includes the parameter values used for that run and the
data collected during the simulation.

Notes
-----
- Only numeric parameters (integers and floats) are varied. Non-numeric parameters
are ignored and set to their default values.
- All input arguments of the Model should have default values.
- For integer parameters, resulting values from relative variations are rounded
to the nearest integer, and duplicate values are eliminated.
- The function includes a default run (using the model's baseline parameters)
for comparison with varied parameter runs.
"""
# Check if all parameters of the model have default values
model_parameters = inspect.signature(model_cls.__init__).parameters

Check warning on line 266 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L266

Added line #L266 was not covered by tests
for name, param in model_parameters.items():
if param.default is inspect.Parameter.empty and name != 'self':
raise ValueError(f"All model parameters must have default values. "

Check warning on line 269 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L269

Added line #L269 was not covered by tests
f"The parameter '{name}' does not have a default value.")

# Retrieve default parameter values from the model's constructor and check for missing defaults
base_params = {}

Check warning on line 273 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L273

Added line #L273 was not covered by tests
for k, v in inspect.signature(model_cls.__init__).parameters.items():
if k == 'self' or k in (ignore_parameters or []):
continue

Check warning on line 276 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L276

Added line #L276 was not covered by tests
if v.default is inspect.Parameter.empty:
raise ValueError(f"Input parameter '{k}' of {model_cls.__name__} does not have a default value, which is required for this local sensitivity analysis function.")
base_params[k] = v.default

Check warning on line 279 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L278-L279

Added lines #L278 - L279 were not covered by tests

# Filter out non-numeric parameters
numeric_params = {k: v for k, v in base_params.items() if isinstance(v, (int, float))}

Check warning on line 282 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L282

Added line #L282 was not covered by tests

# Generate parameter variations
parameter_variations = {'default': base_params} # Special entry for default run

Check warning on line 285 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L285

Added line #L285 was not covered by tests
for param, default_value in numeric_params.items():
# Apply specific range if available, otherwise use common range
ranges = specific_ranges.get(param, common_range)

Check warning on line 288 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L288

Added line #L288 was not covered by tests

if relative_range:
# Relative range: multiply the default value by each value in the range
varied_values = {default_value * multiplier for multiplier in ranges}

Check warning on line 292 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L292

Added line #L292 was not covered by tests
else:
# Absolute range: use the values directly
varied_values = set(ranges)

Check warning on line 295 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L295

Added line #L295 was not covered by tests

# Round integers and remove duplicates
if isinstance(default_value, int):
varied_values = {round(val) for val in varied_values}

Check warning on line 299 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L299

Added line #L299 was not covered by tests

parameter_variations[param] = sorted(varied_values)

Check warning on line 301 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L301

Added line #L301 was not covered by tests

# Prepare parameters for batch_run
parameters = {param: values for param, values in parameter_variations.items()}

Check warning on line 304 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L304

Added line #L304 was not covered by tests

# Execute batch run using the Mesa batch_run function
return batch_run(

Check warning on line 307 in mesa/batchrunner.py

View check run for this annotation

Codecov / codecov/patch

mesa/batchrunner.py#L307

Added line #L307 was not covered by tests
model_cls=model_cls,
parameters=parameters,
iterations=iterations,
**kwargs
)