Releases: irena-flextool/flextool
Release list
v4.0.0b23 — commercial solvers handle spaces; opt-in Benders cut compaction
Pre-release. No schema change (v63); dependency floors unchanged (polar-high>=3.4.0).
Solvers — commercial CLIs (Gurobi / CPLEX / Xpress / COPT)
- Commercial solvers now handle entity names containing spaces (e.g.
Battery Farm), matching the whitespace-agnostic in-process HiGHS path. Fixes both theparent failed to read MPS backcrash and the subsequent silent-wrong-answer: the MPS is written with generic, whitespace-safe names (emit_names=False) and the solution mapped back by index onto the real names held in memory. No parent-sidereadModel. - CPLEX/Xpress/COPT
.solparsing fixes (COPT objective; Xpress switched to MPS-like SLX + PrtSol), plus real-solve tests for all four solvers.
Decomposition (Benders) — opt-in, OFF by default
- Periodic master cut compaction (
FLEXTOOL_BENDERS_CUT_COMPACT_AT, default OFF) withslack/dominancepolicy — experimental. - Capability guard: needs
polar-high>=3.5.0; if set on an older polar-high, compaction disables with a clear warning instead of crashing.
🤖 Generated with Claude Code
v4.0.0b22
Release 4.0.0b22 (1.7.2026) — benders_in_out_weight solve parameter (schema v63)
Database migration v62 → v63 (automatic on load). Dependency floors
unchanged (polar-high>=3.4.0, polars>=1.40, highspy<=1.14.0). Additive and
OFF by default — the converged solution is unchanged on every model.
Decomposition (Benders)
- The in-out separation weight is now a per-solve database parameter,
solve.benders_in_out_weight(previously only the machine-local environment
variableFLEXTOOL_BENDERS_IN_OUT_WEIGHTfrom b21). It is the weightλin the
in-out separation pointf_sep = λ·centre + (1-λ)·f_out:0.0(the default)
= off (exact Benders, byte-identical to before); values in(0, 1)turn the
stabilisation on, larger = more. Only used whendecomposition = 'benders'. The
environment variable still works and, when set to a valid value, overrides
the database parameter (machine-local wins), mirroring the worker-count knob;
an out-of-range or malformed environment value is ignored with a warning and the
database value is used. The v63 migration adds the parameter to thesolve
class (default0.0,solve_advancedgroup) — no authored data changes, and a
database with the parameter unset behaves exactly as before.
v4.0.0b21
Release 4.0.0b21 (1.7.2026) — Benders in-out stabilization (degeneracy tail-off)
No database migration (schema stays v62). Dependency floor raised to
polar-high>=3.4.0 (polars>=1.40, highspy<=1.14.0 unchanged). Opt-in and
OFF by default — the converged solution is unchanged on every model.
Decomposition (Benders)
- Optional in-out separation to speed up a slowly-converging (tailing-off)
spatial-Benders solve. When storage (or any inter-temporal coupling) makes
timesteps fungible, the region recourse is flat in the per-timestep coupling
flow and the cut slopes become basis-dependent; the master then wanders among
cost-equivalent flow schedules and the lower bound closes very slowly even
though the best feasible solution is near-optimal early. In-out separation
(Ben-Ameur & Neto 2007) generates each cut at an interior point
f_sep = λ·centre + (1-λ)·f_outinstead of the extreme master vertex — better-
centred cuts, no wandering, faster bound closure, at zero extra subproblem
solves (the region is solved once per iteration either way, just at a better
point). Enabled per solve via the environment variable
FLEXTOOL_BENDERS_IN_OUT_WEIGHT(the weightλ):0.0(the default) is OFF
and byte-identical to before;λ ∈ (0, 1)turns it on, larger = more
stabilisation. On a hydrogen-trade tail-off benchmark,λ ≈ 0.3–0.7reached the
practical optimality gaps about 30% faster and closed the bound tighter than the
un-stabilised run (which plateaued above the gap it could otherwise reach). The
stabilisation math is the domain-freepolar_high.decomposition.InOutStabilizer
(3.4.0); FlexTool drives one instance per node group. Correctness is preserved
regardless ofλ: a cut generated at any point is a valid supporting hyperplane
(valid lower bound), the interior point is clamped to the chosen capacity (valid
upper bound), and the moment a region's cut fails to separate the master vertex
the method falls back to an exact-Benders step for that region — so the optimum
is unchanged and convergence is guaranteed. A future release will promote the
knob to asolvedatabase parameter once a default is settled.
v4.0.0b20
Release 4.0.0b20 (1.7.2026) — Benders stall guard (fail fast with a diagnostic)
No database migration (schema stays v62). Dependency floor raised to
polar-high>=3.3.0 (polars>=1.40, highspy<=1.14.0 unchanged) for the new
generic tail-off detector. Detection only — no converged solution changes.
Decomposition (Benders)
- A stalled spatial-Benders solve now fails fast with a plain-English
diagnostic instead of silently exhausting the iteration cap. When one node
group is near-infeasible on its own (it can only meet its demand via the
boundary/slack penalty), the master keeps proposing coupling flows that force
the penalty regime, the best feasible cost freezes far above any sane value,
and the run would otherwise burn all its iterations returning that garbage. The
solver now detects this and aborts with a three-section message that names the
worst-offender node group and recommends the fix (give that node group's
import/boundary nodes a moderate import price rather than an extreme penalty —
an over-large penalty is what inflates the recourse and freezes the bound). The
detector is a domain-free tail-off monitor in polar-high 3.3.0
(decomposition.StallMonitor); FlexTool supplies the reference scale (the sum
of the node groups' stand-alone costs) and the node-group diagnosis. A run is
flagged stalled only when the best feasible cost is frozen for a window of
iterations AND still far above that reference AND the gap is far from the
tolerance — a conjunction that never trips a slow-but-genuinely-converging
solve (validated against solves that converge in 8–28 iterations). The window
defaults to 8 and is overridable withFLEXTOOL_BENDERS_MAX_STALL.
v4.0.0b19
Release 4.0.0b19 (1.7.2026) — retiring-unit existing map read + Benders cut-tolerance fix
No database migration (schema stays v62) and dependency floors unchanged
(polar-high>=3.2.0, polars>=1.40, highspy<=1.14.0). Two independent
fixes: one corrects an output/dispatch parameter for retiring units; the other
removes a spurious hard-failure in the spatial-Benders decomposition solver.
Outputs / dispatch
- Read a retiring unit's
existingcapacity by the per-period MAX, not the
last row._entity_unitsize_lfgated theexisting-map cascade input on
"period" in columns, but Spine names a Map's index column with its
silent-default"x", so the gate never matched and every period row fell
through to aunique(keep="last")that kept the last period's value. For a
unit whoseexistingmap decays to0at expiry, that collapsed the cascade
input to0, defaultedunitsizeto1000, and turned
existing_count = existing/unitsizeinto a spurious fraction — capping
continuous online at that fraction and making integer online impossible to
commit. The reader now takes the per-entity MAX over whatever period rows
exist, index-name-agnostically (CLAUDE.md Invariant #2). Byte-identical for
scalarexisting(a single-row group-by is a no-op) and for every existing
test fixture; corrects the reported online/count for retiring-unit models.
Adds regression tests in the silent-default-index coverage file and a dev-doc
audit of the readers that live outside the_param_shapesresolver.
Decomposition (Benders)
- The spatial-Benders cut self-check no longer hard-fails on solver
round-off. After each master solve,_check_cuts_satisfiedasserts every
just-appended optimality cut is honoured at the new master point. That check
re-derives a row already present in the master LP, so it can only differ from
the solved value by the solver's feasibility tolerance — which HiGHS measures
on its internally-scaled matrix, making the unscaled slack scale with the
cut row's coefficient magnitude, not its right-hand side. On early iterations
a node group whose recourse cost overshoots produces large reduced-cost
slopes, socost_randΣ slope·f̄nearly cancel: the rhs collapses toO(1)
while the coefficients stay atO(1e6). The old tolerance keyed off the
cancelled rhs and demanded a precision the ill-conditioned row cannot deliver,
aborting an otherwise-converging solve (observed on a 7-node-group hydrogen-
trade model: a7e-4slack on a2.66e6-scale row —2.7e-10relative). The
tolerance is now keyed off the row magnitude, matching the fail-safe design of
the sibling flow-clamp / lower-bound / sandwich guards: numerical noise is
absorbed, a moderate gap is warned, and only a gross violation (a genuinely
un-appended cut, whose recourse estimate sits near its large-negative floor)
still hard-fails — now with the same three-section plain-English diagnostic as
the other guards. The affected model converges to the monolith objective
within tolerance.
v4.0.0b18 — fix zeroed existing capacity (scalar+Map entity classes)
Release 4.0.0b18 (30.6.2026) — fix zeroed existing capacity for scalar+Map entity classes
No database migration (schema stays v62) and dependency floors unchanged
(polar-high>=3.2.0, polars>=1.40, highspy<=1.14.0). Output-only fix — the
converged solution (dispatch, investments, optimal objective) is unchanged.
Outputs
- Fix zeroed
existingcapacity for a unit whose constant value shares an
entity class with a period-Map sibling. When one unit carries a scalar
(constant)existingcapacity while another unit of the same class carries a
period-indexed Map, the Spine reader returns a single frame with one shared
index column (namedxby Spine's silent default) in which the constant
unit's row has a null index. The per-entity resolver classified rows by the
presence of that column rather than per-row, so the null-index scalar became
an explicit(entity, period=null)row that never joined the period grid and
was silently filled with0. Downstream this zeroed the output-facing
existing/total capacity for such units — surfacing most visibly as negative
VRE curtailment (potential − flow, withpotentialcomputed from a0
capacity), and as0in the capacity, capacity-factor, and per-entity
pre-existing fixed-cost reports. The resolver now detects scalars per row (a
null index broadcasts the constant across the period universe), in both mirror
implementations. The LP itself was never affected — its flow bound reads a
separate, scalar-correct existing-count parameter, and existing fixed cost
enters the objective only as an opt-in constant term — so this is purely a
reporting fix; re-running output processing on an existing solve produces
correct reports without re-solving.
v4.0.0b17 — autoscale centres the objective
No database migration (schema stays v62). Dependency floor raised to polar-high>=3.2.0. The converged solution is unchanged on every model.
The "Problem has some excessively small costs" warning is fixed at the source
FlexTool multiplies the whole objective by the legacy scale_the_objective = 1e-6, dragging every cost coefficient ~6 decades down — a typical operational-cost band lands around [5e-5, 6e-1], below HiGHS' 1e-4 floor. polar-high 3.2.0's autoscale Layer 3 now geometrically centres the cost band over HiGHS' comfort zone via a power-of-two user_objective_scale: the band straddles 1.0, the warning clears, and the cheapest costs gain several decades of headroom above the dual-feasibility tolerance — which helps duals (node/reserve prices) converge on degenerate dispatch LPs.
The exponent is a power of two and HiGHS unscales the objective and duals on output, so the reported solution is byte-for-byte unchanged — only the magnitudes the simplex pivots on. Bands already in HiGHS' zone are left untouched.
Housekeeping
- Genericized a handful of test fixtures and source comments; no behavioural change.
v4.0.0b16 — Benders fail-safe numerical guards + clearer failure diagnostics
Release 4.0.0b16 (30.6.2026) — Benders fail-safe numerical guards + clearer failure diagnostics
No database migration (schema stays v62) and dependency floors unchanged
(polar-high>=3.1.0, polars>=1.40, highspy<=1.14.0). Robustness fixes to the
Benders regional-decomposition driver plus a docs note; a model that does not use
regional decomposition is unaffected, and the converged solution is unchanged.
Benders regional decomposition
- Clamp the master coupling flow to capacity instead of tuning the tolerance.
The post-master-solvef <= capself-check kept tripping on benign solver
slack: HiGHS enforces feasibility on the internally-scaled master, so the
unscaled slack exceeds both the nominal tolerance and the reported
max_primal_infeasibility, and it grows as cuts accumulate (~3e-6at iter 4,
~1.2e-5at iter 8) — a fixed tolerance chases a moving target (this supersedes
the b14 tolerance-from-feasibility approach). Instead, the upper bound is now
evaluated at the strictly capacity-feasible point(C, min(f, cap)): clamping
flow down can only raise region recourse cost, so it stays a valid whole-problem
UB and the optimality cut stays valid (a subgradient inequality holds at any
linearization point). The clamped flow is used consistently downstream (region
pin, next cuts, incumbent); only a gross overshoot (>1e-2relative, or1e3×
the solver's reported infeasibility) still hard-fails as a genuine read/stale
bug. Clamp magnitude logged at DEBUG. - Fail-safe LB guards. The LB-monotonicity (
1e-6) andLB <= best_UB
sandwich (1e-9) self-checks no longer hard-crash the whole run on benign
numerical noise (discarding a good feasible incumbent). Both now separate noise
from a genuine invalid-bound bug via a unified gross band
max(tol, 1e-3): a small LB dip pins LB back to the previous valid bound and
continues; a small LB/UB crossing is treated as converged (stop on the
incumbent); a gross dip or overshoot still hard-fails (stale basis / corrupted
cut / invalid-bound pathology). - Plain-English failure diagnostics. Every hard-failure site (master /
node-group not optimal, LB dip,LB > UB, coupling overshoot) now emits a
one-line summary plus "What this means" and "How to avoid it", using only
FlexTool class vocabulary (node, group, connection, flow) — no model-instance
terms — pinned by a static AST regression test.
Documentation
- Recommend setting an explicit
Mapindex_name(periodortime) for
parameters that accept both a period map and a time series (e.g. flowGroup
min_instant_flow/max_instant_flow), so the value-domain fallback never has
to guess; cross-linked from the flowGroup flow-limit reference.
v4.0.0b15 — flowGroup instant-flow obligation fix
Fixes a flowGroup min_instant_flow / max_instant_flow limit being silently ignored — or crashing at load — depending on how the limit was authored. No database migration (schema stays v62) and dependency floors are unchanged (polar-high>=3.1.0, polars>=1.40, highspy<=1.14.0). Models that set no instant-flow limit on a flowGroup are unaffected.
flowGroup flow limits
- Emit the instant-flow constraint for every authored shape. The
min_instant_flow/max_instant_flowobligation's RHS cap was resolved correctly for all shapes (constant, period map, time map, period+time — including Spine's silent-default"x"Mapindex_name), but the constraint's(g, d, t)support was built by a separate raw-source projection that detected the index axis by column name. That projection returned an empty support for an"x"-labelled period map, a constant, or a time map — so the>=/<=constraint was never emitted and the limit was silently ignored — and it crashed (ColumnNotFoundError: "t") on a pure period map. The support is now derived directly from the resolved cap and broadcast against the active(d, t)grid, exactly as the cumulative-flow limits already are, so every authoring shape binds correctly. A regression test covers each cap shape plus the reported field case (an"x"-indexed period map with a zero later period under a single-period solve).
v4.0.0b14 — Benders master coupling self-check tolerance fix
Release 4.0.0b14 (29.6.2026) — Benders master coupling self-check tolerance fix
Raises the solver-backend floor to polar-high>=3.1.0 (the Benders master
coupling self-check now reads Solution.max_primal_infeasibility, added in
3.1.0). No database migration (schema stays v62); other floors unchanged
(polars>=1.40, highspy<=1.14.0). A model that does not use regional
(Benders) decomposition is unaffected.
Benders regional decomposition
- Size the master coupling self-check from the solver's achieved feasibility.
The post-master-solve self-check that the chosen flow is supported by the
chosen capacity (f <= cap) used a hard-coded1e-6absolute tolerance. For a
unitsize-normalised coupling rowC - f >= 0with small capacity, HiGHS
enforces primal feasibility on the internally-scaled row (default1e-7),
which maps to a larger unscaled slack onf <= cap— a normal solver
artifact, not an invalid bound. On a full-scale model this tripped at iteration
4 (slack ~3e-6> tol1e-6) and aborted an otherwise-valid solve. The
tolerance is now derived from the master solve's own
Solution.max_primal_infeasibility(with a small relative floor), and the
error message reports the actual slack vs. tolerance so a genuine,
orders-of-magnitude coupling violation still surfaces loudly.