Operational MILP Warm Starts

The operational MILP now accepts heuristic schedules as warm starts via fhops solve-mip-operational --incumbent seed.csv. This page documents how to generate those CSVs, what the CLI/API derive from them, and—critically—why the feature is still experimental for medium/large ladders.

Workflow

  1. Generate a candidate schedule. Use any heuristic command that emits assignments with the canonical column schema:

    fhops solve-heur examples/med42/scenario.yaml \
      --iters 0 \
      --out tmp/med42_greedy_incumbent.csv
    

    The CSV must include machine_id, block_id, day, and shift_id. If you pass the optional assigned or production columns they are honoured when seeding Pyomo variables.

  2. Feed the incumbent to the MILP. Any solve-mip-operational invocation can reuse the schedule:

    fhops solve-mip-operational examples/med42/scenario.yaml \
      --solver gurobi \
      --solver-option Threads=36 \
      --solver-option TimeLimit=120 \
      --incumbent tmp/med42_greedy_incumbent.csv \
      --out tmp/med42_mip_seeded.csv
    

    The CLI rebuilds the fhops.optimization.operational_problem.OperationalProblem context, derives the implied transitions, activation binaries, per-role inventories, landing surplus, and leftovers, and then sets Pyomo’s warmstart=True flag before launching the solver.

  3. Inspect the solver log. Successful warm starts show the candidate objective up-front. When the log contains User MIP start did not produce a new incumbent solution the solver ignored the seed (usually because it can find a better incumbent through its own heuristics).

Current limitations

  • The plumbing works end-to-end—tiny7/small21 reuse the incumbent immediately—but med42 and large84 still reject greedy or short SA seeds. Those incumbents complete all blocks in ≈23–47 days, while the MILP needs high-quality assignments that respect every loader/landing constraint; the solver therefore finds its own incumbent faster than it can repair the provided schedule.

  • Gurobi and HiGHS require every binary implied by the incumbent (assignment, transition, mobilisation activation, loader buffer) to be populated. The CLI handles this automatically, but if you call fhops.model.milp.driver.solve_operational_milp() directly you must pass the OperationalProblem context so the helper can rebuild sequencing state.

  • Warm starts are best-effort. Providing an incumbent is always safe, yet you should not expect runtime improvements unless the seed is near-feasible for the operational MILP. Until we develop stronger heuristics (e.g., 60 s SA runs with repairs or rolling-horizon MILPs), treat --incumbent as a diagnostic tool rather than a guaranteed accelerator.

Practical guidance

  • Capture solver logs with --solver-option LogFile=med42.log when experimenting so you can confirm whether the incumbent was accepted.

  • Budget heuristics so they can produce a schedule that finishes close to the horizon (e.g., SA with --iters 2000 and --watch set to 60 seconds). Seeds that leave large staged volume or violate sequencing will be discarded.

  • Fall back to solver-based heuristics (pure Gurobi/HiGHS) if the warm start keeps getting rejected—the solver is often faster at generating its own incumbent once it hits the strong root relaxation.

Future work

Warm starts become truly useful once we can:

  • Generate med42-quality incumbents that satisfy loader/landing balance (potentially by repairing SA outputs with the SequencingTracker).

  • Lock in early-week decisions via rolling-horizon MILPs so the incumbent only needs to cover a subset of shifts at a time.

  • Expose benchmark automation that measures “seeded vs unseeded” runtime/gap curves in CI.

Until then, the published CLI/API docs intentionally describe the feature as operational-but-not-yet-practically-useful so users know what to expect.