Source code for bamengine

"""
BAM Engine - Bottom-Up Adaptive Macroeconomics Simulation Framework
====================================================================

BAM Engine is a Python implementation of the BAM (Bottom-Up Adaptive
Macroeconomics) model from Delli Gatti et al. (2011). It provides a
high-performance, vectorized agent-based macroeconomic simulation framework
for studying complex economic dynamics.

Quick Start
-----------
Basic simulation with default configuration:

>>> import bamengine as bam
>>> sim = bam.Simulation.init(seed=42)
>>> sim.run(n_periods=100)
>>> unemployment = np.mean(~sim.wrk.employed)
>>> print(f"Final unemployment: {unemployment:.2%}")

Custom configuration via kwargs:

>>> sim = bam.Simulation.init(n_firms=200, n_households=1000, n_banks=20, seed=42)
>>> sim.run(n_periods=100)

Custom configuration via YAML file:

>>> sim = bam.Simulation.init(config="my_config.yml", seed=42)
>>> sim.run(n_periods=100)

Key Concepts
------------
**Agents and Roles**
  Agents have multiple roles (components). Firms are Producer + Employer + Borrower.
  Households are Worker + Consumer. Banks are Lender.

**Event Pipeline**
  Each period executes 37 events in fixed order: Planning → Labor Market →
  Credit Market → Production → Goods Market → Revenue → Bankruptcy → Entry.

**Vectorized Operations**
  All agent state stored in NumPy arrays for performance. Population-level
  operations execute in parallel.

**Deterministic RNG**
  Fixed seed ensures reproducible simulations for scientific research.

Public API
----------
**Core Classes**

Simulation
    Main simulation facade for running BAM simulations.
Role
    Base class for defining custom agent components.
Event
    Base class for defining custom economic events.
Relationship
    Base class for defining agent-to-agent relationships.
Economy
    Container for economy-wide state (prices, wages, unemployment).

**Decorators**

role
    Decorator for defining custom role classes (simplified syntax).
event
    Decorator for defining custom event classes (simplified syntax).
relationship
    Decorator for defining custom relationship classes.

**Type Aliases**

Float, Int, Bool, AgentId
    User-friendly type aliases for defining custom roles without NumPy knowledge.
Rng
    Type alias for numpy.random.Generator (random number generator).

**Utility Modules**

ops
    NumPy-free operations for writing custom events (add, multiply, divide, etc.).
logging
    Custom logging with TRACE level and per-event log configuration.

**Registry Functions**

get_role, get_event, get_relationship
    Retrieve registered roles/events/relationships by name.
list_roles, list_events, list_relationships
    List all registered roles/events/relationships.

Examples
--------
Access time-series data after simulation:

>>> sim = bam.Simulation.init(seed=42)
>>> sim.run(n_periods=100)
>>> import matplotlib.pyplot as plt
>>> plt.plot(sim.ec.inflation_history)
>>> plt.title("Inflation Over Time")

Define a custom role:

>>> @bam.role
... class Inventory:
...     goods_on_hand: bam.Float
...     reorder_point: bam.Float
...     supplier_id: bam.AgentId

Define a custom event:

>>> @bam.event
... class CustomPricing:
...     def execute(self, sim):
...         prod = sim.get_role("Producer")
...         bam.ops.multiply(prod.price, 1.1, out=prod.price)

Step through simulation manually:

>>> sim = bam.Simulation.init(seed=42)
>>> for period in range(10):
...     sim.step()
...     if period % 5 == 0:
...         unemp = np.mean(~sim.wrk.employed)
...         print(f"Period {period}: Unemployment = {unemp:.2%}")

Module Organization
-------------------
**Public API** (stable, documented, recommended for users):
  - `bamengine.Simulation` : Main simulation class
  - `bamengine.role`, `bamengine.event`, `bamengine.relationship` : Decorators
  - `bamengine.ops` : NumPy-free operations
  - `bamengine.logging` : Custom logging
  - `bamengine.typing` : Type system definitions

**Internal Modules** (implementation details, subject to change):
  - `bamengine.simulation` : Simulation implementation
  - `bamengine.core` : ECS infrastructure (registry, pipeline)
  - `bamengine.roles` : Built-in role implementations (Producer, Worker, etc.)
  - `bamengine.events` : Built-in event implementations (37 events)
  - `bamengine.relationships` : Built-in relationships (LoanBook)
  - `bamengine.economy` : Economy-wide state, scalars and time-series
  - `bamengine.config` : Configuration and validation
  - `bamengine.utils` : Internal utilities

References
----------
Delli Gatti, D., Desiderio, S., Gaffeo, E., Cirillo, P., & Gallegati, M. (2011).
The BAM model at work. In Macroeconomics from the Bottom-up (New Economic Windows).
Springer Milano. https://doi.org/10.1007/978-88-470-1971-3

See Also
--------
Configuration files in ``bamengine/config/``:

- ``defaults.yml`` : Default configuration parameters
- ``default_pipeline.yml`` : Default event execution order

Notes
-----
- Time scale: 1 period = 1 quarter (4 periods = 1 year)
- All simulations are deterministic when seed is specified
- Configuration precedence: config/defaults.yml → user config → kwargs
- Pipeline events execute in explicit order (no automatic dependency resolution)
"""

from __future__ import annotations

__version__: str = "0.9.1"

# ============================================================================
# Standard library imports
# ============================================================================
from typing import Any, TypeAlias

import numpy as np

# ============================================================================
# Type system for user extensions (must be before Simulation import)
# ============================================================================
from .typing import Agent as AgentId
from .typing import Bool, Float, Int

# Type alias for RNG (must be before Simulation import)
Rng: TypeAlias = np.random.Generator

# ============================================================================
# User-facing utilities (must be before Simulation import)
# ============================================================================
from . import logging, ops  # noqa: E402 (circular‑safe)


[docs] def make_rng(seed: int | None = None) -> Rng: """Create a new random number generator. This is the recommended way to create RNGs for use with BAM Engine. Under the hood, this uses NumPy's `default_rng`, which provides the modern Generator API with better statistical properties than the legacy RandomState. Parameters ---------- seed : int | None Seed for reproducibility. If `None`, uses a random seed. Returns ------- Rng A NumPy random number generator (np.random.Generator). Examples -------- >>> import bamengine as bam >>> rng = bam.make_rng(42) # Reproducible >>> rng.normal(0, 1, size=10) # Use standard NumPy methods >>> rng2 = bam.make_rng() # Random seed See Also -------- numpy.random.default_rng : The underlying NumPy function """ return np.random.default_rng(seed)
# ============================================================================ # ECS extensibility components # ============================================================================ from .core import ( # noqa: E402 (circular‑safe) Agent, AgentType, Event, Relationship, Role, event, get_event, get_relationship, get_role, list_events, list_relationships, list_roles, relationship, role, ) from .economy import Economy # noqa: E402 (circular‑safe) from .extension import Extension # noqa: E402 (circular‑safe) from .results import SimulationResults # noqa: E402 (circular‑safe) from .simulation import Simulation # noqa: E402 (circular‑safe) # ============================================================================ # Collect configurations for data collection # ============================================================================ BASELINE_COLLECT: dict[str, Any] = { "Producer": ["production", "labor_productivity"], "Worker": ["wage", "employed"], "Employer": ["n_vacancies"], "capture_timing": { "Worker.wage": "workers_receive_wage", "Worker.employed": "firms_run_production", "Producer.production": "firms_run_production", "Employer.n_vacancies": "firms_decide_vacancies", }, } # ============================================================================ # Public API exports # ============================================================================ __all__ = [ "Simulation", "SimulationResults", "Extension", "__version__", # Core ECS components "Agent", "AgentType", "Role", "Economy", "Event", "Relationship", "event", "role", "get_event", "get_role", "get_relationship", "list_events", "list_roles", "list_relationships", "relationship", # Type system "Float", "Int", "Bool", "AgentId", "Rng", # Collect configurations "BASELINE_COLLECT", # Utilities "make_rng", "ops", "logging", ]