In a nutshell
While trying to wrap the latest cuOpt release (v26.02), I encountered an issue with the test_air05 test.
This test passes with cuopt 25.12, but errors with v26.02 (see details below).
[EDIT]: this is likely coming from an upstream bug being triggered in this specific instance. Upstream issue opened.
Root cause
cuOpt --> MOI status code conversion
I tracked the issue to the MOI-level retrieval of the TerminationStatusCode here:
|
function MOI.get(model::Optimizer, ::MOI.TerminationStatus) |
|
return _TerminationStatusMap[model.termination_status][1] |
|
end |
and more specifically here:
|
CUOPT_TERIMINATION_STATUS_FEASIBLE_FOUND => |
|
(MOI.OTHER_LIMIT, "cuOptModelStatusFeasible"), |
Note that:
- cuOpt's return status
FEASIBLE_FOUND gets mapped to MOI.OtherLimit
- (see solver log below) cuOpt returns a lower bound (
2.6383013888888891e+04) which is higher than the optimal value (2.6373999999999985e+04)
cuOpt termination status
I also noted the following difference at the cuOpt level:
- in v25.12, after the call to
MOI.optimize!(model), model.termination_status evaluates to 1
- in v26.02, after the call to
MOI.optimize!(model), model.termination_status evaluates to 8
Steps to reproduce (v26.02)
- Make sure you have cuOpt v26.02 installed
- Run
test_air05 test from the test suite
- You should obtain a log and stracktrace similar to the ones reproduced below:
cuOpt log (note that I activated logging by setting log_to_console to `true)
Setting parameter time_limit to 6.000000e+01
Setting parameter log_to_console to true
cuOpt version: 26.2.0, git hash: f73da24d, host arch: aarch64, device archs: 75-real,80-real,86-real,90a-real,100f-real,120a-real,120
CPU: Unknown, threads (physical/logical): 1/20, RAM: 105.63 GiB
CUDA 13.1, device: NVIDIA GB10 (ID 0), VRAM: 119.70 GiB
CUDA device UUID: 43bce240-d7d6-65e5-ca7a-e351bab5de7a
Solving a problem with 426 constraints, 7195 variables (7195 integers), and 52121 nonzeros
Problem scaling:
Objective coefficents range: [4e+01, 3e+03]
Constraint matrix coefficients range: [1e+00, 1e+00]
Constraint rhs / bounds range: [0e+00, 1e+00]
Variable bounds range: [0e+00, 1e+00]
Original problem: 426 constraints, 7195 variables, 52121 nonzeros
Calling Papilo presolver (git hash 741a2b9c)
Presolve status: reduced the problem
Presolve removed: 90 constraints, 1116 variables, 16171 nonzeros
Presolved problem: 336 constraints, 6079 variables, 35950 nonzeros
Objective function is integral
Papilo presolve time: 0.42
Objective offset 9167.000000 scaling_factor 1.000000
Model fingerprint: 0x3257a27
Running presolve!
Unused variables detected, eliminating them! Unused var count 4
After cuOpt presolve: 335 constraints, 6075 variables, objective offset 9167.000000.
cuOpt presolve time: 4.49
Solving LP root relaxation in concurrent mode
Skipping column scaling
Dual Simplex Phase 1
Dual feasible solution found.
Dual Simplex Phase 2
Iter Objective Num Inf. Sum Inf. Perturb Time
0 -8.1936000000000000e+04 277 2.31020000e+04 0.00e+00 5.26
1 -8.0926000000000000e+04 266 1.48790000e+04 0.00e+00 5.26
1000 -5.7784953825729302e+04 127 1.13852044e+02 0.00e+00 5.33
Root relaxation solution found in 1194 iterations and 0.10s by Dual Simplex
Root relaxation objective +2.58776093e+04
| Explored | Unexplored | Objective | Bound | IntInf | Depth | Iter/Node | Gap | Time |
0 0 +inf +2.588508e+04 228 0 1.2e+03 - 5.58
Gomory cuts : 1
MIR cuts : 0
Knapsack cuts : 0
Strong CG cuts : 0
Cut pool size : 211
Size with cuts : 336 constraints, 6411 variables, 1250070204 nonzeros
Strong branching using 19 threads and 228 fractional variables
H +4.551700e+04 +2.588508e+04 43.1% 5.93
Strong branching completed in 0.40s
Exploring the B&B tree using 19 threads
| Explored | Unexplored | Objective | Bound | IntInf | Depth | Iter/Node | Gap | Time |
H +4.354400e+04 +2.591578e+04 40.5% 7.76
B 31 27 +2.645600e+04 +2.591578e+04 0 19 1.3e+02 2.0% 7.76
D 49 43 +2.643900e+04 +2.591578e+04 0 21 1.2e+02 2.0% 7.86
D 166 108 +2.641400e+04 +2.604586e+04 0 33 9.8e+01 1.4% 8.14
B 226 134 +2.640200e+04 +2.612955e+04 0 15 9.4e+01 1.0% 8.37
D 322 140 +2.637500e+04 +2.622476e+04 0 15 9.5e+01 0.6% 8.60
B 380 120 +2.637400e+04 +2.628093e+04 0 18 9.2e+01 0.4% 8.71
510 27 +2.637400e+04 +2.638301e+04 166 8 7.8e+01 0.0% 8.86
Explored 510 nodes in 8.86s.
Absolute Gap -9.013889e+00 Objective 2.6373999999999985e+04 Lower Bound 2.6383013888888891e+04
Optimal solution found.
Solution objective: 26374.000000 , relative_mip_gap 0.000342 solution_bound 26383.013889 presolve_time 4.913286 total_solve_time 9.023263 max constraint violation 0.000000 max int violation 0.000000 max var bounds violation 0.000000 nodes 510 simplex_iterations 39853
test failure stack trace:
test_air05: Test Failed at ~/github/cuOpt.jl/test/MOI_wrapper.jl:84
Expression: MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL
Evaluated: MathOptInterface.OTHER_LIMIT == MathOptInterface.OPTIMAL
Stacktrace:
[1] macro expansion
@ ~/.julia/juliaup/julia-1.12.4+0.aarch64.linux.gnu/share/julia/stdlib/v1.12/Test/src/Test.jl:680 [inlined]
[2] test_air05()
@ Main.TestMOIWrapper ~/github/cuOpt.jl/test/MOI_wrapper.jl:84
[3] macro expansion
@ ~/github/cuOpt.jl/test/MOI_wrapper.jl:27 [inlined]
[4] macro expansion
@ ~/.julia/juliaup/julia-1.12.4+0.aarch64.linux.gnu/share/julia/stdlib/v1.12/Test/src/Test.jl:1776 [inlined]
[5] runtests()
@ Main.TestMOIWrapper ~/github/cuOpt.jl/test/MOI_wrapper.jl:27
More specifically, model.termination_status evaluates to 8, which yields an MOI.OTHER_LIMIT termination status and the test fails.
Additional logs (v25.12)
For completeness, log from the same problem, using cuOpt 25.12
Setting parameter time_limit to 6.000000e+01
Setting parameter log_to_console to true
cuOpt version: 25.12.0, git hash: d97ff6b, host arch: aarch64, device archs: 75-real,80-real,86-real,90a-real,100f-real,120a-real,120
CPU: Unknown, threads (physical/logical): 1/20, RAM: 104.57 GiB
CUDA 13.0, device: NVIDIA GB10 (ID 0), VRAM: 119.70 GiB
CUDA device UUID: 43bce240-d7d6-65e5-ca7a-e351bab5de7a
Solving a problem with 426 constraints, 7195 variables (7195 integers), and 52121 nonzeros
Problem scaling:
Objective coefficents range: [4e+01, 3e+03]
Constraint matrix coefficients range: [1e+00, 1e+00]
Constraint rhs / bounds range: [0e+00, 1e+00]
Variable bounds range: [0e+00, 1e+00]
Original problem: 426 constraints, 7195 variables, 52121 nonzeros
Calling Papilo presolver
Presolve status: reduced the problem
Presolve removed: 90 constraints, 1116 variables, 16171 nonzeros
Presolved problem: 336 constraints, 6079 variables, 35950 nonzeros
Objective function is integral
Papilo presolve time: 0.426261
Objective offset 9167.000000 scaling_factor 1.000000
Running presolve!
Unused variables detected, eliminating them! Unused var count 2
After trivial presolve: 335 constraints, 6077 variables, objective offset 9167.000000.
Using 19 CPU threads for B&B
Solving LP root relaxation
Scaling matrix. Maximum column norm 1.000000e+00, minimum column norm 8.965530e-02
Dual Simplex Phase 1
Dual feasible solution found.
Dual Simplex Phase 2
Iter Objective Num Inf. Sum Inf. Perturb Time
1 -8.1100000000000000e+04 272 1.84122651e+02 0.00e+00 0.01
Root relaxation solution found in 990 iterations and 0.10s
Root relaxation objective +2.58776093e+04
Strong branching using 19 threads and 222 fractional variables
Exploring the B&B tree using 19 threads (best-first = 4, diving = 15)
| Explored | Unexplored | Objective | Bound | Depth | Iter/Node | Gap | Time |
D 92 94 +2.637400e+04 +2.615126e+04 2 1.6e+02 0.8% 0.79
B 347 157 +2.637400e+04 +2.629885e+04 16 8.9e+01 0.3% 1.32
Explored 532 nodes in 1.52s.
Absolute Gap 2.634867e+00 Objective 2.6373999999999989e+04 Lower Bound 2.6371365133232783e+04
Optimal solution found within relative MIP gap tolerance (1.0e-04)
Post-solve status: succeeded
Solution objective: 26374.000000 , relative_mip_gap 0.000100 solution_bound 26371.365133 presolve_time 0.698856 total_solve_time 3.687329 max constraint violation 0.000000 max int violation 0.000000 max var bounds violation 0.000000 nodes 532 simplex_iterations 37434
In that version, model.termination_status is 1, which yields an MOI.OPTIMAL termination status and the test passes.
In a nutshell
While trying to wrap the latest cuOpt release (v26.02), I encountered an issue with the
test_air05test.This test passes with cuopt 25.12, but errors with v26.02 (see details below).
[EDIT]: this is likely coming from an upstream bug being triggered in this specific instance. Upstream issue opened.
Root cause
cuOpt --> MOI status code conversion
I tracked the issue to the MOI-level retrieval of the
TerminationStatusCodehere:cuOpt.jl/src/MOI_wrapper.jl
Lines 288 to 290 in aa06a3a
and more specifically here:
cuOpt.jl/src/MOI_wrapper.jl
Lines 284 to 285 in aa06a3a
Note that:
FEASIBLE_FOUNDgets mapped toMOI.OtherLimit2.6383013888888891e+04) which is higher than the optimal value (2.6373999999999985e+04)cuOpt termination status
I also noted the following difference at the cuOpt level:
MOI.optimize!(model),model.termination_statusevaluates to1MOI.optimize!(model),model.termination_statusevaluates to8Steps to reproduce (v26.02)
test_air05test from the test suitecuOpt log (note that I activated logging by setting
log_to_consoleto `true)test failure stack trace:
More specifically,
model.termination_statusevaluates to8, which yields anMOI.OTHER_LIMITtermination status and the test fails.Additional logs (v25.12)
For completeness, log from the same problem, using cuOpt 25.12
In that version,
model.termination_statusis1, which yields anMOI.OPTIMALtermination status and the test passes.