Skip to content

LP Planner

Chrison Simtian edited this page May 17, 2026 · 1 revision

LP Planner

The core planning question — given the inputs I have and the outputs I need, what's the cheapest set of recipes that satisfies them? — is solved as a linear program using Google OR-Tools (GLOP solver).

Where it lives

Layer File Role
Domain src/ERP/Domain/ProductionPlan.cs Output type: Targets, Available, Steps, RawInputsConsumed, MissingInputs, ExtractorAllocations, Warnings, FluidPipes, Sensitivity, GeneratorAllocations
Application src/ERP/Application/IRecipePlanner.cs Port
Application src/ERP/Application/RecursiveRecipePlanner.cs Heuristic fallback (kept for parity tests)
Infrastructure src/ERP/Infrastructure/OrToolsRecipePlanner.cs LP implementation (native OR-Tools dep)
Infrastructure src/ERP/Infrastructure/PlannerOptions.cs Tuneables: penalties, tie-breakers, planner selection

Native OR-Tools stays in Infrastructure; Application talks to the port only.

Decision variables

Variable Meaning
recipe[r] Production rate (per-minute, fractional) for recipe r
item[i] Net supply of item i (raw inputs + recipe outputs − recipe inputs)
raw[i] How much of raw input i is drawn from Available
short[i] Shortfall on item i — non-zero only when supply can't meet demand
node[n] Extractor allocation per resource node (#92)
generator[g, fuel] Buildings count + fuel draw per generator/fuel pair (#137)

The variables are continuous — the LP relaxation gives fractional building counts which we surface alongside whole-integer rates. That's deliberate; an ILP would block on solver licensing and is overkill for steady-state planning.

Constraints

  • Supply ≥ demand per item — sum of all contributions (recipe outputs + raw inputs + shortfall) is at least the demand (recipe inputs + targets).
  • Power supply ≥ recipe draw — only when a power target is set; generators contribute, recipes draw.
  • Node capacitynode[n] is bounded by the node's max extraction rate derived from purity + extractor tier.

Objective

Minimise (in this priority order):

  1. Shortfall on produced items (huge penalty, ShortfallPenaltyForProduced = 1e9)
  2. Shortfall on raw inputs (smaller penalty, ShortfallPenaltyForRaw = 1e6)
  3. Power demand (base draw + miner draw + generator building cost)
  4. Raw draw (RawDrawTieBreaker = 1e-3 — above GLOP optimality tolerance, below recipe power)

The two-tier shortfall penalty exists so the LP attributes blame correctly — without it, the solver would happily mark a target item short rather than its upstream raw input, hiding the real bottleneck.

What we extract post-solve

  • Steps — every active recipe with rate, building count, machine type.
  • RawInputsConsumed — final per-raw draw.
  • MissingInputs (InfeasibleItem) — anything still short after the LP found its best objective; surfaces the real deficit.
  • ExtractorAllocations — which nodes were used at what rate (#92).
  • FluidPipes — per-item pipe throughput requirements + recommended tier (#90).
  • Sensitivity (LpSensitivity) — shadow prices on supply constraints and reduced costs on production recipes (#129). Surfaces "what's pinning the plan" without the user having to re-solve manually.
  • GeneratorAllocations — per-generator building counts and fuel draw for the chosen power target (#137).

Generator-aware planning (v0.3+)

Pass PowerTargetMw on the PlanProductionQuery. The LP chooses freely between generator kinds (Coal, Fuel, Nuclear, …) and fuels (e.g. coal vs compacted coal) — there's no preference; the objective picks the lowest-cost mix.

If the available raw inputs can't sustain the requested power, the LP returns the closest feasible solution and the shortfall surfaces as a MissingInput rather than infeasibility.

Sensitivity analysis (v0.3)

Sensitivity.SupplyConstraints[i].ShadowPrice tells you how much the objective would improve per additional unit of item i. Reduced costs on recipes show how expensive an inactive recipe would have to become before it gets brought into the plan.

The view layer surfaces these in the planner page under the Sensitivity tab, so users can answer "would adding one more iron ore patch help?" without re-running the LP.

Tests

  • test/ERP/Infrastructure.Tests/OrToolsRecipePlannerTests.cs — golden-path
    • edge cases (no raw, contradictory targets, missing recipes, …).
  • test/ERP/Application.Tests/RecursiveRecipePlannerTests.cs — heuristic baseline; kept so we can spot regressions when changing penalties.

Clone this wiki locally