Skip to content

API Reference

Auto-generated from source code docstrings.

  • CoreEngine, RunResults, Scenario, Action, Observation, RoundResult
  • BrainsBrain ABC, RL brains, rule-based strategies
  • Metrics — cooperation rate, Nash distance, social welfare, entropy, Gini, regret, reciprocity, adaptation speed
  • IO — config loading, Pydantic schemas, Parquet reader/writer

Top-level API

policy_arena

PolicyArena — cross-paradigm agent simulation engine.

Run game-theoretic simulations with rule-based, reinforcement learning, and LLM-powered agents.

Quick start::

import policy_arena as pa

# Run from a YAML config
results = pa.run("scenarios/pd_rl_vs_rulebased.yaml")

# Access results
print(results.model_metrics.tail())
print(results.agent_metrics.tail())

run(config, *, seed=None, rounds=None)

Run a simulation and return results.

Parameters:

Name Type Description Default
config str | Path | ScenarioConfig

Path to a YAML config file, or a ScenarioConfig object.

required
seed int | None

Override the random seed from the config.

None
rounds int | None

Override the number of rounds from the config.

None

Returns:

Type Description
RunResults

Contains model_metrics and agent_metrics DataFrames.

Source code in src/policy_arena/__init__.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
def run(
    config: str | Path | ScenarioConfig,
    *,
    seed: int | None = None,
    rounds: int | None = None,
) -> RunResults:
    """Run a simulation and return results.

    Parameters
    ----------
    config
        Path to a YAML config file, or a ``ScenarioConfig`` object.
    seed
        Override the random seed from the config.
    rounds
        Override the number of rounds from the config.

    Returns
    -------
    RunResults
        Contains ``model_metrics`` and ``agent_metrics`` DataFrames.
    """
    from policy_arena.io.config_loader import build_scenario
    from policy_arena.io.config_loader import load_config as _load
    from policy_arena.io.schemas import ScenarioConfig as _ScenarioConfig

    if isinstance(config, str | Path):
        cfg = _load(config)
    elif isinstance(config, _ScenarioConfig):
        cfg = config
    else:
        raise TypeError(
            f"config must be a path or ScenarioConfig, got {type(config).__name__}"
        )

    updates: dict = {}
    if seed is not None:
        updates["seed"] = seed
    if rounds is not None:
        updates["rounds"] = rounds
    if updates:
        cfg = cfg.model_copy(update=updates)

    scenario = build_scenario(cfg)
    return Engine().run(scenario)

list_games()

Return sorted list of available game IDs.

Source code in src/policy_arena/__init__.py
94
95
96
def list_games() -> list[str]:
    """Return sorted list of available game IDs."""
    return sorted(get_registry().keys())

list_scenarios()

Return sorted list of built-in scenario names.

Source code in src/policy_arena/__init__.py
57
58
59
60
def list_scenarios() -> list[str]:
    """Return sorted list of built-in scenario names."""
    scenarios_dir = Path(__file__).parent / "scenarios"
    return sorted(p.stem for p in scenarios_dir.glob("*.yaml"))

get_scenario_path(name)

Return the path to a built-in scenario YAML file.

Parameters:

Name Type Description Default
name str

Scenario name (without .yaml extension). Use list_scenarios() to see available names.

required

Raises:

Type Description
FileNotFoundError

If no built-in scenario matches the name.

Source code in src/policy_arena/__init__.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def get_scenario_path(name: str) -> Path:
    """Return the path to a built-in scenario YAML file.

    Parameters
    ----------
    name
        Scenario name (without .yaml extension).
        Use ``list_scenarios()`` to see available names.

    Raises
    ------
    FileNotFoundError
        If no built-in scenario matches the name.
    """
    scenarios_dir = Path(__file__).parent / "scenarios"
    path = scenarios_dir / f"{name}.yaml"
    if not path.exists():
        available = list_scenarios()
        raise FileNotFoundError(
            f"No built-in scenario '{name}'. Available: {available}"
        )
    return path

load_config(path)

Load and validate a YAML scenario config file.

Source code in src/policy_arena/__init__.py
87
88
89
90
91
def load_config(path: str | Path) -> ScenarioConfig:
    """Load and validate a YAML scenario config file."""
    from policy_arena.io.config_loader import load_config as _load

    return _load(path)

get_registry()

Return the global game registry, discovering games on first call.

Source code in src/policy_arena/registration.py
83
84
85
86
87
88
89
90
def get_registry() -> GameRegistry:
    """Return the global game registry, discovering games on first call."""
    global _registry
    if _registry is None:
        _registry = GameRegistry()
        _discover_builtin_games(_registry)
        _discover_entrypoint_games(_registry)
    return _registry

configure_logging(level='INFO')

Configure PolicyArena logging with a console handler.

Parameters:

Name Type Description Default
level str | int

Logging level name (e.g. "DEBUG", "INFO") or numeric level.

'INFO'
Source code in src/policy_arena/_logging.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def configure_logging(level: str | int = "INFO") -> None:
    """Configure PolicyArena logging with a console handler.

    Parameters
    ----------
    level
        Logging level name (e.g. ``"DEBUG"``, ``"INFO"``) or numeric level.
    """
    if isinstance(level, str):
        level = getattr(logging, level.upper(), logging.INFO)

    logger.setLevel(level)

    # Avoid adding duplicate handlers on repeated calls
    if not any(
        isinstance(h, logging.StreamHandler) and h.stream.name == "<stderr>"  # type: ignore[union-attr]
        for h in logger.handlers
        if not isinstance(h, logging.NullHandler)
    ):
        handler = logging.StreamHandler()
        handler.setLevel(level)
        formatter = logging.Formatter(
            "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
            datefmt="%H:%M:%S",
        )
        handler.setFormatter(formatter)
        logger.addHandler(handler)
    else:
        # Update existing handler level
        for h in logger.handlers:
            if isinstance(h, logging.StreamHandler) and not isinstance(
                h, logging.NullHandler
            ):
                h.setLevel(level)