Skip to content

Improved heuristic formulation of non-convex health burden; avoid MIP#24

Merged
koen-vg merged 5 commits into
mainfrom
health-relax-and-fix
Jul 6, 2026
Merged

Improved heuristic formulation of non-convex health burden; avoid MIP#24
koen-vg merged 5 commits into
mainfrom
health-relax-and-fix

Conversation

@koen-vg

@koen-vg koen-vg commented Jul 6, 2026

Copy link
Copy Markdown
Member

The binary-variable-based piece-wise linear approximation of non-convex constraints that makes the health formulation work causes the Highs solver to struggle with default, health-enabled GLADE models. This PR introduces a heuristic approach which works very well in practice: solve the relaxation of the MIP first, then fix binary variables to the induced linear segment and resolve; a "relax-and-fit" approach. It turns out that this works well in most cases, cuts down on solving time by Gurobi by ~30%, and makes a Highs solve feasible in under 5 minutes.

koen-vg added 5 commits July 3, 2026 16:33
The health module's non-convex dose-response curves previously required
SOS1 segment indicators, making health-enabled solves MIPs. HiGHS cannot
solve these at full resolution: its MIP path is limited to a serial dual
simplex for the root relaxation, which does not finish the root LP within
10x the time Gurobi needs for the entire solve, and it has no option to
use its interior-point solver inside the MIP.

Add health.segment_formulation: relax_and_fix, a two-pass LP scheme that
adds no integer structure at all:

1. Solve the model without segment indicators (a valid relaxation; the
   delta fill-up formulation is already exact for convex curves).
2. Pin each non-convex (cluster, risk) pair's delta bounds to the fill-up
   pattern of the segment containing its relaxed intake
   (health.fix_nonconvex_segments).
3. Re-solve warm-started from the pass-1 basis; the relative difference
   between the two objectives is a certified optimality gap, checked
   against health.relax_and_fix_max_gap.

On the full-resolution central health scenario with HiGHS ipm+crossover
(solving.options_highs: {solver: ipm, run_crossover: on}), the scheme
solves in ~5 minutes end to end with a certified gap of 6.5e-05 (target
1e-03) and a repaired objective slightly better than the Gurobi MIP
incumbent at the same tolerance. The repair pass takes ~8 s. The default
sos1 formulation is unchanged, remains exact, and stays the right choice
with Gurobi.
The repair pass inherits the configured Method=2 (barrier), which ignores
the pass-1 basis and re-solves from scratch. With Method=1 the dual
simplex hot-starts from the written basis and repairs the delta-bound
changes in a few seconds. Measured on the central health scenario:
relax-and-fix under Gurobi drops to ~40s total vs ~55s for the exact sos1
MIP, with certified gaps of 4e-04 (value_per_yll=50k) and 0 (200k).
Measured on the full-resolution central scenario (value_per_yll 50k/200k),
relax-and-fix beats the exact sos1 MIP under Gurobi on every axis: ~30%
faster (42s vs 61s), 37% lower peak memory (3.0 GB vs 4.8 GB, helped by
freeing the pass-1 solver model before the repair build), objectives that
certify tighter than the 0.1% MIP gap actually achieves, and identical
results across Gurobi and HiGHS to seven significant digits. Higher
values per YLL tighten the certified gap (exactly 0 at 200k USD/YLL)
because intakes get pushed to breakpoint corners where the relaxation is
exact. sos1 remains available for exact MIP solves.
Full-resolution solves now peak at ~4.7 GB under HiGHS (ipm+crossover)
and ~3.1 GB under Gurobi with the relax-and-fix health formulation
(measured on central scen-central), so the previous 8000 MB default was
oversized. Retries still escalate the allocation by 1.3x per attempt.

Claude-Session: https://claude.ai/code/session_014hiDv7bnKQnhu5B7hnVfhe
A certified gap above health.relax_and_fix_max_gap previously aborted
the solve, forcing a config change and rerun even when only a few
scenarios were affected. Since re-fixing segments can only improve the
incumbent but never the relaxed bound, an uncertifiable gap structurally
requires the MIP: run_relax_and_fix now re-fixes from the repaired
intakes for up to three rounds (boundary-pressing intakes select the
neighbouring segment) and then restores the exact SOS1 indicators on the
same model, seeded with the repaired solution as MIP start, instead of
erroring.

Validated by forcing the fallback on the full-resolution central config
(max_gap 1e-9): Gurobi accepts the seeded incumbent and certifies 0.078%
in ~35s, confirming the heuristic solution was already optimal to solver
tolerance. Unit tests cover both the fallback chain (via a synthetic
S-shaped curve whose relaxation provably undercuts) and the certified
fast path.

Claude-Session: https://claude.ai/code/session_014hiDv7bnKQnhu5B7hnVfhe
@koen-vg koen-vg merged commit a84acb5 into main Jul 6, 2026
4 checks passed
@koen-vg koen-vg deleted the health-relax-and-fix branch July 6, 2026 16:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant