Solver options¶
Everything is controlled by keyword arguments of minimize. The defaults are
the configuration used for the published results; most problems need none of
them changed beyond tolerances and a time limit.
r = minimize(f, lo, hi, ineq=g, eq=h, integer=None,
abs_tol=1e-6, rel_tol=1e-6, time_limit=60) # every other option at its default
Problem¶
| argument | default | meaning |
|---|---|---|
f |
objective, a JAX-traceable function of a 1-D array returning a scalar | |
lo, hi |
variable bounds; may be infinite (see Modelling) | |
ineq |
None |
g(x) <= 0, a scalar or 1-D array |
eq |
None |
h(x) == 0, a scalar or 1-D array |
integer |
None |
integer variables, as indices or a boolean mask |
Termination and tolerances¶
| argument | default | meaning |
|---|---|---|
abs_tol, rel_tol |
1e-6, 1e-6 |
stop with "optimal" when fun - lower_bound <= max(abs_tol, rel_tol * abs(fun)) |
feas_tol |
1e-6 |
a point is feasible when ineq <= feas_tol and abs(eq) <= feas_tol |
max_iter |
5000 |
iterations of the branch-and-bound loop (status "max_iter") |
time_limit |
inf |
seconds, counted from the call, including tracing and compilation (status "time_limit"). If the kernels are not ready in time the result has lower_bound=-inf and message says so. |
verbose |
False |
print progress every 25 iterations |
Branching¶
Each iteration selects the boxes with the lowest bounds and splits them. Two arguments decide how.
branch_var: which variable to split¶
| value | rule |
|---|---|
"auto" (default) |
"pseudocost" when there are no integer variables, "width" when there are |
"width" |
the variable with the largest relative width among the variables that appear nonlinearly. On the lifted quadratic LP, relative width times the McCormick gap of the variable's products at the relaxation point. |
"pseudocost" |
the product score of the per-variable bound gains observed so far; on the lifted quadratic LP, times a continuous variable's "width" score; with integer variables, chosen among the integers fractional at the relaxation point when there are any |
"reliability" |
like "pseudocost", but variables with fewer than 2 observations are strong-branched (up to 4 of them per box), within the batch budget |
"strong" |
every selected parent is split on its best-scored variable (pseudocost product score, width before any pseudocost is known), and the padding slots of the kernel calls the iteration makes anyway strong-branch further candidates. Only variables at least 1/10 as wide as the widest are candidates. Each parent is then split on its 2 best candidates at once (those scoring at least half the best) into up to 4 orthants, each bounded by the largest of the tightened halves. |
Only variables that appear nonlinearly, read off the jaxpr (sampled Hessians when the dependency tracing cannot follow an equation), are branched on in a continuous problem. Integer variables are split at integer values.
In a re-run of the branching comparison on current main (2026-10-20, after
PRs #76 and #81, warm, on a 4-core cloud VM), on MINLPLib start50 at 60 s
"pseudocost" solves 40 instances against 38 for "width" and "strong"; on
int50 "width" and "pseudocost" each solve 43 (not the same ones:
pseudocost gains ex1252a and tln4 and loses hadamard_5 and
smallinvDAXr5b150-165) and "strong" 41. Hence the "auto" default. On the 21
built-in problems plus univariate_sum_10/20, nn_tanh_5x8x2 and qcqp_10_5 (all
certified by every rule), node counts relative to "width" as geometric means
are 1.00 for "pseudocost" and 0.83 for "strong", which needs fewer nodes on
15 of the 25. The largest gains for "strong" are on hartmann3 (157 → 42
nodes), hartmann6 (1099 → 634) and univariate_sum_20 (495 → 191). On problems
with 2 to 6 variables the choice of variable matters little. "strong" can
cost more than it saves on deep trees: nn_tanh_5x8x2 takes 575k nodes and
114 s against 251k and 40 s with "width", and over the 25 problems its total
warm time is 127 s against 57 s. The data are in
benchmarks/results/branch_*. "reliability" can be
several times slower in wall time on integer problems, because each
strong-branching trial is a bounded box. Result.evaluations counts every box
bounded, including strong-branching trials, so compare evaluations as well
as nodes when trying these rules.
branch_point: where to split it¶
| value | rule |
|---|---|
"mid" (default) |
bisect |
"relax" |
split at the relaxation minimizer, pulled toward the midpoint and kept away from the faces |
Bound tightening¶
| argument | default | meaning |
|---|---|---|
fbbt |
True |
forward-backward interval propagation over the jaxpr before every node is bounded |
fbbt_rounds |
5 |
FBBT sweeps per node |
dbbt |
True |
duality-based range reduction from each node's bound certificate f >= lb + Σ r_j (x_j - lo_j) |
obbt |
"root" |
LP-based tightening of every variable: "root", "tree" (every node) or False |
obbt_rounds |
2 |
OBBT rounds |
Relaxations and node bounds¶
| argument | default | meaning |
|---|---|---|
univariate |
True |
replace the relaxation of every smooth sub-expression of a single variable by a grid-hull envelope (nearly exact) |
alphabb |
True |
whole-function αBB with scaled-Gershgorin α from an interval Hessian for smooth functions; "eigen" adds the eigenvalue relaxation of exact quadratics |
multivariate |
False |
relax products by the multivariate McCormick rule of Tsoukalas & Mitsos 2014, tighter where a factor's interval straddles zero |
lifted |
False |
bound nodes with the extended-space LP (one column per nonlinear jaxpr node) instead of the reduced-space cuts; opt-in and slower |
quadratic |
True |
a constrained problem whose functions are all quadratic is bounded, and OBBT-tightened, over a lifted sparse LP with one McCormick-linked column per product |
crown |
False |
add the CROWN backward linear bound of f to every node bound: False, True, or "auto" (on when every primitive of f has a CROWN rule and f has a saturating activation or ReLU). Useful for neural-network surrogates. |
n_fw |
20 |
Frank–Wolfe steps on the convex relaxation, which give the affine cuts |
n_kelley |
4 |
Kelley points added to the cut LP of a constrained problem |
cut_pool |
128 |
rows of a pool of tangent cuts of the relaxation, taken on the root box and so valid at every node, added to the node LPs of a constrained problem (reduced-space bounder only); 0 turns it off |
safe |
False |
round every quantity that feeds a lower bound outward by a few ulps (about 1.7× slower); see Correctness |
Upper bounds (local solves)¶
| argument | default | meaning |
|---|---|---|
local_solve |
True |
run local NLP solves for incumbents |
local_solver |
"auto" |
"pounce", "optax" (pure JAX), "scipy" (L-BFGS-B / SLSQP), or "auto" (pounce if installed) |
n_starts |
1 |
scales the number of local starts drawn from the relaxation minimizers of the most promising boxes |
Once there is an incumbent, local searches and integer dives that fail to improve it are run less often.
Batching and devices¶
| argument | default | meaning |
|---|---|---|
batch |
256 |
most boxes bounded per iteration |
batch_sizes |
(16,) |
sizes of the kernel calls; a batch is padded to the smallest size that holds it or cut into calls of the largest. Each size is traced and compiled once, so the default is a single small size. |
devices |
None |
JAX devices to split the largest calls across; e.g. devices=jax.devices()[:1] disables splitting |
On CPU, expose several cores as devices by setting
XLA_FLAGS=--xla_force_host_platform_device_count=N before JAX is imported.
The bounding kernels compile in background threads and are cached on disk
across processes, under ~/.cache/jaxglobal by default. JAXGLOBAL_CACHE_DIR
moves the cache and JAXGLOBAL_CACHE=0 turns it off.
The result¶
minimize returns a Result:
| field | meaning |
|---|---|
x |
best point found |
fun |
f(x), an upper bound on the global minimum |
lower_bound |
proven lower bound on the global minimum |
gap |
fun - lower_bound |
status |
"optimal", "infeasible", "max_iter", "time_limit", "unbounded_domain", "unverified" or "unproven" |
message |
details for a non-optimal status |
converged |
whether the gap closed |
nodes, iterations, evaluations |
search-tree nodes, B&B iterations, boxes bounded (including strong-branching trials) |
time |
wall-clock seconds |
history |
per-iteration progress |
max_violation, verified |
the incumbent re-checked outside jit in float64: box, ineq, eq, integrality and f(x) == fun. A failed check turns "optimal" into "unverified". An incumbent repaired to strict feasibility (message says so) whose gap then exceeds 100x the tolerance turns it into "unproven". |
See the API reference for the full docstring.