PAWS3 (pronounced "paws") is a modular Python framework for rolling-horizon simulated replanning of forest management under principal–agent dynamics, with an optional bilevel model coupling (government "principal" upper level; industrial "agent" lower level). It is designed to align with the FHOPS package structure and CLI conventions to minimize onboarding cost across projects.
- Backbone: Pyomo
- Solvers: HiGHS (LP/MIP) via
highspyorappsi(exec fallback supported) - Patterns: strategy/plug‑in behaviors for principal/agent variants; clean separation of data, model, and simulation orchestration
- Variants supported: myopic upper-level, anticipative upper-level with explicit agent model, bilevel reformulations, rolling-horizon windowing and re-optimization
# install (editable)
pip install -e .
# initialize a new project space with example config & data
paws3 init examples/minimal
# run a rolling-horizon simulation (myopic principal; dummy agent emulator)
paws3 run-sim --config examples/configs/minimal.yaml
# run bilevel (stub lower-level; for scaffolding validation)
paws3 solve-bilevel --config examples/configs/bilevel_stub.yamlpaws3/core/– config, data models (Pydantic), simulator orchestration, loggingpaws3/models/– Pyomo builders for principal/agent/bilevel and solver adapterspaws3/io/– loaders/savers; CSV/Parquet and YAML config readerspaws3/plugins/– registry for behavior variants (principal policies, agent emulators, pricing rules)paws3/cli/– Typer CLI entry points mirroring FHOPS style (*-cmdsynonyms provided)
- Fast iteration: You can start from tiny CSVs (strata, yields, demand) and get a working simulation loop without standing up a database or a giant codebase.
- Explicit structure: The rolling‑horizon loop is deliberately simple and readable; the UL/LL interface is narrow and typed so you can swap in different models.
- Research‑friendly: Add constraints/objectives, try even‑flow vs. revenue maximization, prototype decomposition/stubbed LLs, and record commits each period.
CSV data ──► ProblemData ──► RollingHorizonSimulator
╰──► Principal (UL) model ──► first‑period commitments
╰───────────────► Agent / Lower‑Level (LL) behavior (stub or FHOPS)
-
Data layer (
io/)csv_loader.pyreads small CSVs into aProblemDataobject:strata.csv:stratum_id, area, species, ageyields.csv:stratum_id, age_bin, yield_m3_per_hademand.csv:period, species, min_vol, max_vol
_expand_absolute_yields(...)converts age‑bin yields into absolute‑time yields for all simulated periods (e.g., “10y” bins → t=0..T).
-
Core (
core/)config.pydefines typedPawsConfig(horizon, solver, policies, data path).sim.pyhasRollingHorizonSimulatorwhich:- Loads
ProblemData - Loops periods
t = start .. end - Builds/solves the principal model on the current window
[t, t+H-1] - Extracts first‑period commitments (UL→LL)
- Invokes agent behavior (LL) to emulate consumption/market response
- Advances to next period and repeats
- Loads
-
Models (
models/)principal.pycontains the upper‑level Pyomo model (clean LP by default).bilevel.pymanages the UL/LL handoff. Today a stub LL is provided; you can drop in a full FHOPS or decomposition later.
Default decisions/logic (simple and readable; change as you like):
- Decision variable:
X[s,t]= harvested area (ha) in stratum s during absolute period t (within the rolling window). - Volume expression:
vol[t] = Σ_s X[s,t] · yield[s,t](m³/ha × ha). - Area conservation:
Σ_t X[s,t] ≤ area[s](can’t harvest more than stand area over the window). - Physical cap (safety):
vol[t] ≤ Σ_s area[s] · yield[s,t]. - Demand bounds (optional): from
demand.csvif periods fall in the window. - Even‑flow policy (optional): enforce minimum flow on the first step only (
vol[t0] ≥ min_vol) to avoid infeasibility later. - Objective (default): maximize total volume over the window:
max Σ_t vol[t].- Swap in even‑flow (L1 deviations) or revenue objectives easily.
First‑period commitments. After solving, the simulator extracts per‑stratum volume at t0 and publishes a compact mapping like:
{ (stratum_id, "sawlog") : committed_volume_m3, ... }
If per‑stratum is zero but the aggregate is positive, it falls back to {("ALL", "sawlog"): vol_t0}.
- The LL currently defaults to a stub that simply acknowledges principal commitments.
- A pluggable
agent_behaviorhook allows emulating consumption, deliveries, and pricing rules (e.g., profit-max or contractual take‑or‑pay). - You can replace the stub with a full FHOPS or other detailed mill/contract model;
bilevel.pykeeps the interface narrow.
- Windowing. At absolute time
t, solve the UL on[t, t+H-1](H =horizon_periods). - Commit. Extract and record only t’s decision (first‑period commitment).
- React. Run the LL behavior to emulate what actually ships/consumes at time
t. - Advance. Move to
t+replanning_step, rebuild, and repeat untilend_period.
This mirrors real planning where only today’s decision is binding; future periods are re‑optimized as new information arrives.
- CSV data: put under
examples/minimal/data(or your own path). - Config YAML: choose horizon, policies, solver, and paths. Example:
horizon:
period_length: "10y" # or 7, "6mo", "52w", "7d"
horizon_periods: 10
replanning_step: 1
start_period: 0
end_period: 24
principal_policy:
name: even_flow
params:
min_vol: 5000
agent_behavior:
name: profit_max_flow_stub
params: {}
bilevel:
enabled: true
reformulation: decomposition
solver:
driver: appsi
time_limit: 30 # seconds
run:
random_seed: 42
log_level: INFO
out_dir: examples/minimal/runs
data_path: examples/minimal/data- Console logs per period/window (UL cap, chosen flow, commitments, LL response).
- Structured results (planned) available from the simulator (e.g., for tests or notebooks).
- You can add CSV/Parquet writers or plots in the run loop if needed.
- Objectives/constraints: Edit
models/principal.pyto add even‑flow (L1/L2), revenue terms, carbon constraints, adjacency, road/opening costs, etc. - Lower‑level detail: Replace the stub with an MILP for contracts, mill capacities, grades, or multi‑product flows.
- Data: Add more columns/tables; expand
ProblemDataand the CSV reader. - Decomposition: Keep the UL/LL interface stable while experimenting with different bilevel reformulations.
- LL is a stub by default; market clearing and contracts are illustrative.
- Yields are assumed per‑period (m³/ha) and expanded from age‑bins to absolute time with a simple clip‑to‑last‑bin rule.
- The demo uses one product label (
"sawlog") for commitments; generalize as needed. - No stochasticity or Bayesian updates yet (but you can inject them in the loop).
# From repo root
paws3 solve-bilevel --config examples/configs/bilevel_stub.yaml -vYou should see a rolling sequence of windows, first‑period commitments, and (stub) agent responses.
- Stratum: a stand/age class with an area, species, and starting age.
- Yield: m³/ha for a stand at a given age bin (expanded to absolute time).
- Commitment: the UL’s binding volume decision at the current period.
- Agent: the LL process consuming or allocating the committed volume.
- Window: the local planning horizon used at each simulation step.
This codebase is intentionally small and evolving. Expect rough edges in logging and config until the interfaces stabilize. Contributions and issue reports are welcome.
MIT