Tactical–Operational Scenario Contract

Phase 6 adds a TOPM-inspired aggregate planning layer beside the existing day/shift operational scheduler. The tactical–operational contract is intentionally separate from schema 1.0.0 so current operational scenario bundles remain unchanged.

Canonical formulation

The full equation set and equation-to-code mapping are maintained from a shared source module used by Sphinx and manuscript/thesis assets:

FHOPS’ TOPM-inspired tactical–operational solver is formulated on an aggregate period grid and chooses harvest areas by planning unit, harvest system, and period while carrying products through transport, facility consumption, inventory, outside purchases, and optional infrastructure/fleet modules. The equations below mirror the implemented Pyomo model in fhops.model.milp.tactical_operational.build_tactical_operational_model and the validated contract in fhops.planning.tactical_operational.models.TacticalOperationalScenario.

Problem statement. Given planning units, period calendars, product yields, harvest-system options, facility demand envelopes, transport arcs, external supply, and optional road/silviculture/fleet modules, choose harvest quantities and downstream flows that satisfy demand and feasibility constraints at minimum discounted delivered cost, or maximize discounted profit/NPV when product values are supplied.

Sets and indices.

  • \(b \in \mathcal{B}\): planning units/blocks.

  • \(o \in \mathcal{O}\): eligible block \(\times\) system \(\times\) period harvest options.

  • \(t \in \mathcal{T}\): ordered tactical–operational periods.

  • \(p \in \mathcal{P}\): products/species-grade classes.

  • \(a \in \mathcal{A}\): product transport arcs.

  • \(u \in \mathcal{U}\): external supply options, indexed by source/destination/product/period.

  • \((f,p,t) \in \mathcal{F}\): facility/product/period balance keys.

  • \(r \in \mathcal{R}\): optional road projects.

  • \(q \in \mathcal{Q}\): optional silviculture transitions.

  • \(e \in \mathcal{E}\): optional fleet acquisition options.

Parameters.

  • \(A_b\): operable area of planning unit \(b\) (ha).

  • \(L_o,U_o\): minimum/maximum active area for option \(o\) (ha).

  • \(Y_{b,p}\): product yield for block \(b\) and product \(p\) (m\(^3\)/ha).

  • \(K_o\): optional total production capacity for option \(o\) (m\(^3\)/period).

  • \(K^{fleet}_{s,t}\): base fleet capacity for system \(s\) in period \(t\) (m\(^3\)).

  • \(F_o,c_o\): fixed cost and variable cost per m\(^3\) for option \(o\).

  • \(c_a,c_u\): transport cost per m\(^3\) on arc \(a\) and delivered purchase cost per m\(^3\) for supply \(u\).

  • \(D^{min}_{f,p,t},D^{target}_{f,p,t},D^{max}_{f,p,t}\): facility demand envelope (m\(^3\)).

  • \(v_{f,p,t}\): optional delivered product value per m\(^3\) for profit objectives.

  • \(d_t\): discount factor for period \(t\).

  • \(I^0_{f,p}\): opening facility inventory (m\(^3\)).

  • \(C_a,C_r\): transport arc and active-road capacity (m\(^3\)/period).

  • \(B_r,M_r\): road build cost and maintenance cost per active period.

  • \(c_q\): silviculture cost per ha for transition \(q\).

  • \(N^{max}_e,K_e,C^{fleet}_e\): maximum units, added capacity per period, and purchase cost for fleet option \(e\).

Decision variables.

  • \(H_o \ge 0\): area harvested under option \(o\) (ha).

  • \(Z_o \in \{0,1\}\): activation indicator for option \(o\).

  • \(V_{o,p} \ge 0\): product volume produced by option \(o\) (m\(^3\)).

  • \(F_a \ge 0\): product flow on transport arc \(a\) (m\(^3\)).

  • \(P_u \ge 0\): external purchase quantity for supply option \(u\) (m\(^3\)).

  • \(C_{f,p,t} \ge 0\): facility consumption/demand fulfillment (m\(^3\)).

  • \(I_{f,p,t} \ge 0\): closing facility inventory (m\(^3\)).

  • \(R^B_{r,t},R^A_{r,t} \in \{0,1\}\): road build and available indicators when roads are enabled.

  • \(Q_{q,t} \ge 0\): silviculture activity area scheduled for transition \(q\) in period \(t\).

  • \(N_e \in \mathbb{Z}_{\ge 0}\): units purchased for fleet option \(e\).

Harvest quantity modes.

All modes enforce the physical upper bound:

\[H_o \le U_o Z_o \qquad \forall o\in\mathcal{O}.\]

Semi-continuous mode (the default) additionally enforces option-specific minimum active area:

\[L_o Z_o \le H_o \qquad \forall o\in\mathcal{O}.\]

Continuous mode omits that lower bound. Whole-block mode forces full operable area when active:

\[H_o = A_{b(o)} Z_o \qquad \forall o\in\mathcal{O}.\]

Core constraints.

Product conversion and option productivity:

\[V_{o,p}=Y_{b(o),p}H_o \qquad \forall o,p,\]
\[\sum_p Y_{b(o),p}H_o \le K_o Z_o \qquad \forall o \text{ with } K_o \text{ declared}.\]

Block area balance:

\[\sum_{o:b(o)=b} H_o \le A_b \qquad \forall b\in\mathcal{B}.\]

Fleet capacity with optional acquisition:

\[\sum_{o:s(o)=s,t(o)=t}\sum_p Y_{b(o),p}H_o \le K^{fleet}_{s,t} + \sum_{e:s(e)=s,\ \tau(e)\le t<\tau(e)+L_e} K_e N_e \qquad \forall s,t,\]

where \(\tau(e)\) is the purchase period sequence and \(L_e\) the economic life in periods. Purchases are bounded by \(0\le N_e\le N^{max}_e\).

Flow supply and arc capacity:

\[\sum_{a\in out(b,p,t)}F_a \le \sum_{o:b(o)=b,t(o)=t}V_{o,p} \qquad \forall b,p,t,\]
\[F_a \le C_a \qquad \forall a\in\mathcal{A}\text{ with declared capacity}.\]

External purchase bounds:

\[P^{min}_u \le P_u \le P^{max}_u \qquad \forall u\in\mathcal{U}.\]

Facility consumption bounds and target mode:

\[C_{f,p,t} \ge D^{min}_{f,p,t},\qquad C_{f,p,t} \le D^{max}_{f,p,t}\text{ when declared},\]
\[C_{f,p,t}=D^{target}_{f,p,t}\text{ when target basis is selected and a target exists}.\]

Facility inventory balance:

\[I_{f,p,t}=I_{f,p,t-1}+\sum_{a\in in(f,p,t)}F_a+\sum_{u\in in(f,p,t)}P_u-C_{f,p,t} \qquad \forall (f,p,t),\]

with \(I_{f,p,0}=I^0_{f,p}\).

Optional road module.

Road build timing, one-time build, availability, dependencies, block access, and active capacity are represented as:

\[R^A_{r,t}=\sum_{t'\le t}R^B_{r,t'},\qquad \sum_t R^B_{r,t}\le 1,\]
\[R^A_{r,t}\le R^A_{\rho(r),t}\quad\text{for dependency }\rho,\]
\[Z_o \le \sum_{r\in access(b(o))}R^A_{r,t(o)},\]
\[\sum_{o:t(o)=t,\ access(b(o))\ne\varnothing}\sum_pY_{b(o),p}H_o \le \sum_r C_rR^A_{r,t}.\]

Road costs enter the objective as \(\sum_{r,t}d_t(B_rR^B_{r,t}+M_rR^A_{r,t})\).

Optional silviculture module.

For each required transition \(q\) triggered by block/system harvest:

\[\sum_{t\ge earliest(q)}Q_{q,t}=\sum_{o:b(o)=b(q),s(o)=s(q)}H_o.\]

Transition area carries discounted cost \(\sum_{q,t}d_t c_q Q_{q,t}\).

Objective profiles.

Default minimum discounted delivered cost:

\[\min\; \sum_o d_{t(o)}(F_oZ_o+c_o\sum_pY_{b(o),p}H_o) +\sum_a d_{t(a)}c_aF_a +\sum_u d_{t(u)}c_uP_u +\text{road, silviculture, and fleet costs}.\]

When demand rows include value per m\(^3\), max_discounted_profit maximizes

\[\sum_{f,p,t}d_tv_{f,p,t}C_{f,p,t}-\text{Cost}.\]

max_npv additionally adds declared final-period terminal inventory value:

\[\sum_{f,p}d_{t_{final}}v^{terminal}_{f,p}I_{f,p,t_{final}}.\]

Implementation mapping (equation blocks to code).

Equation/constraint block

Pyomo component / helper

Data provenance

Harvest upper bound

model.harvest_upper

HarvestSystemOption.max_area_ha and block operable area

Semi-continuous minimum cut

model.harvest_lower

HarvestSystemOption.min_area_ha

Whole-block mode

model.whole_block

PlanningUnit.operable_area_ha

Productivity cap

model.productivity_cap

HarvestSystemOption.productivity_m3_per_period

Product conversion

model.product_conversion

PlanningUnit.product_yields_m3_per_ha

Block area balance

model.block_area

PlanningUnit.operable_area_ha

Fleet capacity and acquisition

model.fleet_capacity, model.fleet_units, model.fleet_option_upper

FleetCapacity and FleetOption

Flow supply

model.flow_supply

TransportArc and product_volume

Arc capacity

model.arc_capacity

TransportArc.capacity_m3

Purchase bounds

model.purchase_lower, model.purchase_upper

ExternalSupply

Consumption bounds/targets

model.consumption_lower, model.consumption_target, model.consumption_upper

FacilityDemand

Inventory balance

model.inventory_balance

InitialInventory, flows, purchases, consumption

Road build/availability/dependencies/access/capacity

model.road_build, model.road_available, model.road_build_timing, model.road_availability, model.road_build_once, model.road_dependencies, model.road_access, model.road_capacity

RoadProject, RoadDependency, BlockRoadAccess

Silviculture fulfillment

model.silviculture_area, model.silviculture_fulfillment

SilvicultureTransition

Objective profiles

model.objective

Economics.objective_profile, costs, values, discount factors

Bundle replay and telemetry

tactical_bundle_to_dict(...), tactical_bundle_from_dict(...), solve_tactical_operational_milp(...)

fhops.model.milp.tactical_operational

This formulation is the canonical mathematical reference for the FHOPS tactical–operational MILP. Generated TeX/RST outputs are derived artifacts and should not be edited directly.

Use the tactical contract for 1–5 year plans that need:

  • planning periods (four-week periods, months, seasons, or custom tables);

  • block × harvest-system × period alternatives;

  • partial, semi-continuous, or whole-block harvest quantities;

  • product-specific yields;

  • facilities, demand envelopes, transport arcs, and opening inventories;

  • outside purchases and discounted economics; and

  • later road, silviculture, and fleet-investment modules.

Loading and validation

The Python loader accepts either inline YAML sections or a data: section that points each long-form table at a CSV file:

from fhops.planning import load_tactical_operational_scenario

scenario = load_tactical_operational_scenario(
    "tests/fixtures/tactical_operational/topm-mini/specification.yaml"
)
print(scenario.dimension_summary())

The CLI validates the same contract and reports model dimensions before any solver model is built:

fhops validate tactical-operational tests/fixtures/tactical_operational/topm-mini/specification.yaml

The legacy operational form remains supported:

fhops validate examples/tiny7/scenario.yaml

Period templates

fhops.planning.tactical_operational.time provides two starter templates:

  • four_week_periods(year) — up to thirteen 28-day periods with optional effective annual discounting;

  • seasonal_periods(year) — winter/spring/summer/fall periods with season tags.

Both produce validated PlanningPeriod objects. Custom period tables can define explicit parent_period_id values for roll-up reporting.

Core MILP formulation

The Phase 6 core model chooses a harvest area H[o] >= 0 and activation Z[o] ∈ {0,1} for each eligible block × system × period option o. Product volumes are derived from area using unit-specific yields:

\[V[o,p] = Y[b(o),p] H[o]\]

Semi-continuous mode enforces option-specific minimum and maximum active areas without arbitrary big-M constants:

\[L_o Z_o \le H_o \le U_o Z_o\]

Continuous mode drops the lower bound; whole-block mode uses H_o = operable_area[b(o)] Z_o. Block area, option productivity, fleet capacity, and facility-demand targets complete the harvest core.

Product flows, purchases, consumption, and facility inventories are coupled to that harvest core. For transport arc a, external supply u, facility/product/period (m,p,t), and previous period t-1:

\[\sum_{a \in out(b,p,t)} F_a \le \sum_{o:b(o)=b,t(o)=t} V[o,p]\]
\[I[m,p,t] = I[m,p,t-1] + \sum_{a \in in(m,p,t)} F_a + P_u - C[m,p,t]\]

Consumption is bounded by the facility demand envelope (or fixed at the target when --demand-basis target). Transport arc capacities and external purchase bounds apply directly. The default objective is discounted harvest + transport + purchase cost:

\[\min \sum_o d_{t(o)} (F_o Z_o + c_o Y^{total}_{b(o)} H_o) + \sum_a d_{t(a)} c_a F_a + \sum_u d_{t(u)} c_u P_u\]

Two value-oriented profiles are also available when demand rows carry value_per_m3: max_discounted_profit maximizes delivered product value minus cost, and max_npv adds the final-period value of declared terminal inventory. Override the scenario value with --objective-profile.

Optional infrastructure modules

The contract now includes optional long-form tables for:

  • road projects, dependencies, and block access (roads, road_dependencies, block_road_access);

  • silviculture follow-up transitions by block/system (silviculture_transitions); and

  • fleet acquisition options with economic-life capacity (fleet_options).

These modules are disabled by default so the harvest/product-flow core remains reproducible. Enable them independently from the CLI:

fhops plan tactical-operational \
  tests/fixtures/tactical_operational/topm-mini/specification.yaml \
  --enable-roads \
  --out-roads-csv tmp/topm-mini-roads.csv

Road activation uses build/available binaries, cumulative timing, prerequisite links, access gates, and active-road capacity. Silviculture transitions schedule required follow-up area in eligible periods and add discounted per-hectare costs. Fleet acquisition variables add capacity during their economic life and charge the purchase-period discounted cost.

The code mapping is intentionally direct:

  • area / harvest_active: fhops.model.milp.tactical_operational.model.area and model.harvest_active.

  • Product conversion: model.product_conversion.

  • Area/productivity/fleet limits: model.block_area, model.productivity_cap, and model.fleet_capacity.

  • Flow supply and arc capacity: model.flow_supply and model.arc_capacity.

  • Purchases, consumption, and inventory: model.purchase_lower/model.purchase_upper, model.consumption_*, and model.inventory_balance.

  • Roads: model.road_build, model.road_available, model.road_access, and model.road_capacity.

  • Silviculture: model.silviculture_area and model.silviculture_fulfillment.

  • Fleet investment: model.fleet_units and the capacity augmentation inside model.fleet_capacity.

  • Objective assembly: model.objective.

  • Bundle replay: fhops.model.milp.tactical_operational.tactical_bundle_to_dict() and tactical_bundle_from_dict().

Solve the fixture from the CLI:

fhops plan tactical-operational \
  tests/fixtures/tactical_operational/topm-mini/specification.yaml \
  --harvest-mode semi_continuous \
  --demand-basis target \
  --solver highs \
  --out-json tmp/topm-mini.json \
  --out-harvest-csv tmp/topm-mini-harvest.csv \
  --out-production-csv tmp/topm-mini-production.csv

Scenario overlays, diffs, and batch reports

Phase 6 scenarios support sparse overlays so sensitivity cases can inherit a base contract without copying every table. Overlay rows are matched by stable identifiers (for example block_id, option_id, arc_id, or composite facility/product/period keys). An overlay can update nested fields, append rows, and remove rows through _remove.

overlay_id: lower-sawlog-demand
description: Reduce Y1-P1 sawlog target from 780 to 700 m3.
facility_demand:
  - facility_id: mill_saw
    product_id: sawlog
    period_id: Y1-P1
    target_m3: 700.0

Apply and inspect the overlay:

fhops scenario overlay base.yaml lower-demand.yaml --out scenario-lower.yaml
fhops scenario diff base.yaml scenario-lower.yaml --out tmp/scenario-diff.csv

A batch manifest can solve base and overlaid cases in one command:

cases:
  - case_id: base
    scenario: base.yaml
  - case_id: lower-demand
    scenario: base.yaml
    overlay: lower-demand.yaml
    enable_roads: true
fhops scenario batch batch.yaml --out-dir tmp/tactical-batch
fhops report tactical tmp/tactical-batch/base/result.json --out-dir tmp/tactical-report

Each batch case writes normalized decision tables and a Markdown objective summary; the batch root contains comparison.csv and comparison.md.

Tactical-to-operational handoff

Use fhops.planning.tactical_operational.integration to convert a tactical solve into an operational business window. The handoff contract is explicit:

  • selected harvest decisions become TacticalCommitment records;

  • a rolling state tracks remaining block area/product volume, facility inventory, active roads, fleet units, and cumulative objective components;

  • compile_business_window_scenario filters an operational scenario to committed blocks, maps tactical block IDs to operational IDs, assigns the selected harvest system, and clamps the window;

  • write_operational_scenario_bundle emits a loadable YAML/CSV operational bundle; and

  • apply_operational_realization rolls realized production back into aggregate state.

CLI example using topm-mini commitments against the operational tiny7 bundle:

fhops plan tactical-operational \
  tests/fixtures/tactical_operational/topm-mini/specification.yaml \
  --out-json tmp/topm-mini-result.json

fhops plan compile-tactical \
  tmp/topm-mini-result.json \
  examples/tiny7/scenario.yaml \
  --block-map B1=B01 \
  --block-map B2=B02 \
  --horizon-days 7 \
  --out-dir tmp/topm-mini-operational

The output directory contains a normal operational scenario.yaml + CSV bundle that can be validated and solved with the existing FHOPS commands.

Scale benchmarks and uncertainty envelopes

Use fhops synth tactical to generate deterministic TOPM-shaped scale scenarios without copying restricted TOPM/OperMAX data. The generator creates a rectangular block × system × period eligibility grid with product yields, fleet capacity, facility demand, transport arcs, and optional external supply:

fhops synth tactical \
  --out tmp/topm-scale.yaml \
  --blocks 100 \
  --years 1 \
  --periods-per-year 4 \
  --products 2 \
  --facilities 2 \
  --systems 2 \
  --seed 44

Benchmark one or more generated sizes with:

fhops scenario benchmark \
  --blocks 25 \
  --blocks 100 \
  --periods-per-year 4 \
  --time-limit 60 \
  --out-dir tmp/topm-scale-benchmark

The benchmark exports CSV, JSON, and Markdown summaries containing scenario dimensions, Pyomo variable/constraint counts, build time, solve time, objective, solver status, and termination condition. Decomposition methods (Benders, Dantzig–Wolfe, fix-and-optimize) should only be adopted after these measurements identify a real bottleneck. The committed smoke benchmark in docs/assets/tactical/tactical_scale_benchmark.md currently shows HiGHS solving generated 25-block and 100-block cases in under one second each (1,018/1,261 and 4,018/4,936 variables/constraints, respectively). The full-scale artifact at docs/assets/tactical/full_scale/tactical_scale_benchmark.md covers 500 blocks × 5 years × 4 periods/year: 20,000 harvest options, 100,082 variables, 120,664 constraints, and an optimal HiGHS result in 40.8 s with about 860 MiB peak memory.

For uncertainty screening, use scenario overlays plus fhops scenario batch to compare demand, productivity, road-cost, or purchase-price cases before attempting robust or stochastic MILP variants.

Practitioner-scale validation case

Phase 7 adds a redistributable synthetic practitioner case shaped like a small mixed-terrain tenure: 24 blocks, 8 periods, 3 products, 2 mills, 3 harvest systems, roads/dependencies, silviculture follow-up, and an optional fleet expansion. Generate or inspect it with:

fhops synth tactical-practitioner --out tmp/practitioner.yaml
fhops validate tactical-operational tmp/practitioner.yaml
fhops plan tactical-operational tmp/practitioner.yaml \
  --enable-roads --enable-silviculture --enable-fleet-investment \
  --out-json tmp/practitioner-result.json

The checked-in fixture lives at tests/fixtures/tactical_operational/practitioner-case/scenario.yaml and regression tests verify schema dimensions, minimum-cut feasibility, road/silviculture/fleet module behavior, and every facility/product/period inventory balance.

Guided tactical planner notebook

The onboarding series now includes examples/05_fhops_tactical_operational.ipynb, an executable walkthrough covering:

  • practitioner-case generation and contract validation;

  • aggregate MILP solve with roads/silviculture/fleet modules;

  • objective decomposition and normalized decision tables;

  • facility inventory balance audits;

  • sparse overlays and scenario diffs;

  • report exports; and

  • tactical→operational compilation into an operational scenario bundle.

Run it with:

python scripts/run_example_notebooks.py --notebook 05

topm-mini acceptance fixture

tests/fixtures/tactical_operational/topm-mini/specification.yaml is the copyright-safe executable specification for the expansion. It contains hand-calculated checks for:

  • continuous vs. semi-continuous vs. whole-block harvest activation;

  • a two-option economic dispatch case;

  • product-specific yield conversion; and

  • facility inventory balances.

See notes/topm_mini_specification.md for the current expected values and implementation notes.