Skip to content

[BUG] std::terminate (process abort) on an infeasible MILP in opportunistic (default) mode #1396

Description

@cafzal

Describe the bug

Solving an infeasible MILP with default settings aborts the process via std::terminate ("terminate called without an active exception", SIGABRT) instead of returning an Infeasible status. The abort is specific to the concurrent LP root-relaxation path, which is active in opportunistic determinism mode — the default. Setting mip_determinism_mode = 1 (deterministic) returns Infeasible cleanly on the identical model. In a hosted kernel (Jupyter/Colab) the abort kills and restarts the kernel; being a C++ abort it is uncatchable from Python.

Distinct from but likely related to #1021 (also concurrent-mode root-relaxation infeasibility — that one a false infeasible + hang; this one a genuine infeasible that crashes instead of reporting it).

Steps/Code to reproduce

Minimal self-contained A/B (cuOpt + numpy only): one infeasible 120-variable MILP solved twice, toggling only mip_determinism_mode, each variant in its own subprocess so the abort is observable without killing the parent. Output on cuOpt 26.4.0 / NVIDIA L4 / CUDA 12.9:

deterministic  -> exit 0,          STATUS Infeasible
opportunistic  -> exit -6 SIGABRT, terminate called without an active exception
repro script
import subprocess, sys, textwrap

WORKER = textwrap.dedent('''
    import sys
    import numpy as np
    from cuopt.linear_programming.problem import Problem, INTEGER, MINIMIZE
    from cuopt.linear_programming import SolverSettings
    from cuopt.linear_programming.solver.solver_parameters import (
        CUOPT_MIP_DETERMINISM_MODE, CUOPT_TIME_LIMIT)

    mode = sys.argv[1]
    rng = np.random.default_rng(0)
    n = 120
    a = rng.uniform(1.0, 10.0, n)
    b = a + rng.uniform(-0.5, 0.5, n)            # correlated second weight vector
    need_a = 0.80 * a.sum()                       # capture >=80% of a-value ...
    cap_b  = 0.30 * b.sum()                       # ... using <=30% of b-value -> infeasible (a~b)

    p = Problem("infeasible_milp")
    x = [p.addVariable(lb=0.0, ub=1.0, vtype=INTEGER, name=f"x{i}") for i in range(n)]
    p.setObjective(sum(x), sense=MINIMIZE)
    p.addConstraint(sum(float(a[i]) * x[i] for i in range(n)) >= float(need_a), name="need_a")
    p.addConstraint(sum(float(b[i]) * x[i] for i in range(n)) <= float(cap_b),  name="cap_b")

    s = SolverSettings()
    s.set_parameter(CUOPT_TIME_LIMIT, 15.0)
    if mode == "deterministic":
        s.set_parameter(CUOPT_MIP_DETERMINISM_MODE, 1)
    p.solve(s)
    print("STATUS=" + str(getattr(p.Status, "name", p.Status)))
''')

for mode in ["deterministic", "opportunistic"]:
    r = subprocess.run([sys.executable, "-c", WORKER, mode], capture_output=True, text=True)
    out = r.stdout.strip() or (r.stderr.strip().splitlines() or [""])[-1]
    print(f"{mode:>13} -> exit {r.returncode:<4} | {out}")

Expected behavior

Return Infeasible as a status (as the sequential / deterministic path already does), not abort the process.

Environment details

  • Environment location: Cloud (Google Colab)
  • OS: Linux
  • Hardware: NVIDIA L4, CUDA 12.9
  • Method of cuOpt install: pip (cuopt-cu12)
  • cuOpt Version: 26.4.0 (git hash d9b7c96a)

Additional context

Workaround (confirmed by the A/B above): mip_determinism_mode = 1.

Hypothesis from a read of main (for triage — not verified at the line level): opportunistic mode enables the concurrent root solve (mip_heuristics/solver.cudeterminism_mode == OPPORTUNISTIC -> set_concurrent_lp_root_solve(true)). In branch_and_bound.cpp (solve_root_relaxation) the dual-simplex worker is spawned as an OpenMP task whose explicit join (set_root_concurrent_halt + #pragma omp taskwait) sits on the crossover-OPTIMAL branch; an infeasible model produces no crossover solution, so that branch appears to be skipped. "terminate ... without an active exception" is the signature of a worker torn down while still joinable rather than a propagating C++ exception.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions