Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
b758b9f
fix timer issues
akifcorduk Feb 11, 2026
0392a62
disable jobserver flag when not actually using jobserver
aliceb-nv Feb 11, 2026
ee54477
disable jobserver unless explicitely requested
aliceb-nv Feb 11, 2026
f876fc0
better workaround fix build
aliceb-nv Feb 11, 2026
4bbf743
scaling test
akifcorduk Feb 16, 2026
1c02baa
Merge branch 'main' of github.com:NVIDIA/cuopt into fix_timer
akifcorduk Feb 16, 2026
7de08d2
add timers to right_looking_lu and refactoring the basis
akifcorduk Feb 17, 2026
942de9c
remove timers from cuts
akifcorduk Feb 17, 2026
0b944d1
convert lambda to function and remove unnecessary checks
akifcorduk Feb 17, 2026
71b7f2f
fix thrust changes
akifcorduk Feb 17, 2026
82b2d64
handle review comments
akifcorduk Feb 18, 2026
0c81173
handle review comments
akifcorduk Feb 18, 2026
8f93926
revert scaling
akifcorduk Feb 18, 2026
80e2650
initial row scaling for mip
akifcorduk Feb 18, 2026
f021ba3
fix pdlp issues and finalize lp scaling
akifcorduk Feb 19, 2026
609c578
move timer with inout parameters
akifcorduk Feb 19, 2026
3d32acd
fix merge conflicts
akifcorduk Feb 19, 2026
0c54ecf
fix merge conflicts
akifcorduk Feb 19, 2026
fc414e7
revert cmake comment
akifcorduk Feb 19, 2026
41f5c3e
Merge branch 'fix_timer' into scaling_test
akifcorduk Feb 19, 2026
aa0b795
root node scaling
akifcorduk Feb 20, 2026
1c03794
sscaling off
akifcorduk Feb 20, 2026
74ec555
Merge branch 'main' of github.com:NVIDIA/cuopt into scaling_test
akifcorduk Feb 20, 2026
e849c70
correct mip gap computation
akifcorduk Feb 20, 2026
ea09141
wip
akifcorduk Feb 23, 2026
adfbf7d
with scaling
akifcorduk Feb 23, 2026
92b165e
make pdlp/barrier scaling default
akifcorduk Feb 23, 2026
2b080cc
fix compule error
akifcorduk Feb 23, 2026
7a61d13
skip mip scaling
akifcorduk Feb 23, 2026
0ee8344
without skipping big M
akifcorduk Feb 24, 2026
2a500f4
mip scaling with skipping big M
akifcorduk Feb 24, 2026
d0c5a42
fix thrust build + more timer checks
aliceb-nv Feb 24, 2026
d559b34
Merge branch 'main' into fix-thrust-build
aliceb-nv Feb 24, 2026
5b5909e
Merge commit 'refs/pull/902/head' of github.com:NVIDIA/cuopt into sca…
akifcorduk Feb 24, 2026
84f8fb3
fix headers
akifcorduk Feb 24, 2026
67240f5
review comment
aliceb-nv Feb 24, 2026
a15424f
fix thrust solve
aliceb-nv Feb 24, 2026
6772270
Merge commit 'refs/pull/902/head' of github.com:NVIDIA/cuopt into sca…
akifcorduk Feb 24, 2026
4a1c672
remove nvtx
akifcorduk Feb 24, 2026
7045ae7
fix init issues
akifcorduk Feb 25, 2026
80ac99d
don't skip big m
akifcorduk Feb 25, 2026
8eaebba
do scaling beforehand
akifcorduk Feb 25, 2026
6b2a631
without big M
akifcorduk Feb 25, 2026
cfcf0ab
try with mip scaling
akifcorduk Feb 27, 2026
1db603c
with assertS
akifcorduk Feb 27, 2026
0f39c06
try without any lp scaling
akifcorduk Feb 27, 2026
9e2d2d1
Merge branch 'main' of github.com:NVIDIA/cuopt into scaling_test
akifcorduk Feb 27, 2026
4ecf8f9
Merge branch 'main' of github.com:NVIDIA/cuopt into scaling_test
akifcorduk Mar 4, 2026
ca5b07e
scaling before presolve
akifcorduk Mar 4, 2026
2f58450
fix objective issues and pdlp solver mode
akifcorduk Mar 4, 2026
071ec5d
with stable 3
akifcorduk Mar 4, 2026
a1030a7
fix issues solver mode 2
akifcorduk Mar 4, 2026
5811ebb
without scaling
akifcorduk Mar 5, 2026
8f8fb84
lp only relative tolerance
akifcorduk Mar 5, 2026
7ec1ef7
lp with abs tolerance
akifcorduk Mar 5, 2026
aaa6ef9
only relative tolerance
akifcorduk Mar 5, 2026
68f6ac3
per constraint residual with relative tolerance
akifcorduk Mar 6, 2026
f536037
per constraint, absolute tolerance
akifcorduk Mar 6, 2026
77c4d7a
only relative tolerance with submip scaling
akifcorduk Mar 6, 2026
89bd164
with absolute tolerance of 1e-6 and per constraint
akifcorduk Mar 6, 2026
2d54c28
mip scaling on
akifcorduk Mar 6, 2026
95e14aa
gcd scaling and less agressive
akifcorduk Mar 6, 2026
d52074e
fix scaling changes
akifcorduk Mar 7, 2026
4915897
test fixes
akifcorduk Mar 8, 2026
701fd70
Merge branch 'main' of github.com:NVIDIA/cuopt into scaling_test
akifcorduk Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 212 additions & 0 deletions benchmarks/linear_programming/cuopt/parse_scaling_logs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Parse MIP scaling benchmark logs into CSV.

Reads log output (from stdin or file arguments) produced by the cuOpt MIP
solver with the MIP_SCALING_METRICS / MIP_SCALING_SUMMARY / MIP_OBJ_SCALING /
MIP_GAP_METRICS log lines and the standard solver output.

Usage:
python parse_scaling_logs.py < benchmark_output.log > results.csv
python parse_scaling_logs.py log1.log log2.log > results.csv
"""

import csv
import re
import sys
from collections import defaultdict


def _float_or_na(s):
try:
return float(s)
except (ValueError, TypeError):
return ""


def _int_or_na(s):
try:
return int(s)
except (ValueError, TypeError):
return ""


INSTANCE_RE = re.compile(
r"(?:Reading|Solving|instance[=:]\s*)(\S+?)(?:\.mps)?(?:\s|$)", re.I
)
FEASIBLE_RE = re.compile(
r"Found new best solution.*objective[=:\s]+([\d.eE+\-]+)", re.I
)
OPTIMAL_RE = re.compile(r"Optimal solution found", re.I)
OBJ_RE = re.compile(r"Objective\s+([\d.eE+\-]+)", re.I)
REL_GAP_RE = re.compile(r"relative_mip_gap\s+([\d.eE+\-]+)", re.I)
SIMPLEX_RE = re.compile(r"simplex_iterations\s+([\d,]+)", re.I)
NODES_RE = re.compile(r"Explored\s+(\d+)\s+nodes\s+in\s+([\d.]+)s", re.I)
INFEASIBLE_RE = re.compile(r"Integer infeasible|Infeasible", re.I)

SCALING_METRICS_RE = re.compile(
r"MIP_SCALING_METRICS\s+iteration=(\d+)\s+log2_spread=([\d.eE+\-]+)\s+"
r"target_norm=([\d.eE+\-]+)\s+scaled_rows=(\d+)\s+valid_rows=(\d+)"
)
SCALING_SUMMARY_RE = re.compile(
r"MIP_SCALING_SUMMARY\s+rows=(\d+)\s+bigm_rows=(\d+)\s+final_spread=([\d.eE+\-inf]+)"
)
OBJ_SCALING_RE = re.compile(
r"MIP_OBJ_SCALING\s+(applied|skipped).*?(?:scale=([\d.eE+\-]+))?"
r".*?(?:new_scaling_factor=([\d.eE+\-]+))?"
)
GAP_METRICS_RE = re.compile(
r"MIP_GAP_METRICS\s+abs_gap_user=([\d.eE+\-]+)\s+rel_gap=([\d.eE+\-]+)\s+"
r"obj_user=([\d.eE+\-]+)\s+bound_user=([\d.eE+\-]+)\s+obj_scale=([\d.eE+\-]+)"
)
SCALING_SKIPPED_RE = re.compile(r"MIP row scaling skipped", re.I)
SOL_FOUND_RE = re.compile(r"sol_found=(\d+).*?obj_val=([\d.eE+\-inf]+)", re.I)
RUN_MPS_RE = re.compile(r"run_mps\s+(\S+)", re.I)


def parse_logs(lines):
records = []
cur = defaultdict(lambda: "")
instance_name = ""

def flush():
nonlocal instance_name
if instance_name:
cur["instance"] = instance_name
records.append(dict(cur))
cur.clear()
instance_name = ""

for line in lines:
line = line.rstrip("\n")

m = RUN_MPS_RE.search(line)
if m:
flush()
instance_name = m.group(1).replace(".mps", "")
continue

m = INSTANCE_RE.search(line)
if m and not instance_name:
instance_name = m.group(1)

m = SCALING_SKIPPED_RE.search(line)
if m:
cur["scaling_applied"] = "no"

m = SCALING_METRICS_RE.search(line)
if m:
cur["scaling_applied"] = "yes"
cur["scaling_last_iteration"] = m.group(1)
cur["scaling_last_spread"] = m.group(2)
cur["scaling_target_norm"] = m.group(3)

m = SCALING_SUMMARY_RE.search(line)
if m:
cur["rows"] = m.group(1)
cur["bigm_rows"] = m.group(2)
cur["final_spread"] = m.group(3)

m = OBJ_SCALING_RE.search(line)
if m:
cur["obj_scaling_status"] = m.group(1)
if m.group(2):
cur["obj_scaling_factor"] = m.group(2)
if m.group(3):
cur["obj_new_scaling_factor"] = m.group(3)

m = FEASIBLE_RE.search(line)
if m:
cur["feasible"] = 1
cur["objective"] = m.group(1)

m = OPTIMAL_RE.search(line)
if m:
cur["optimal"] = 1

m = NODES_RE.search(line)
if m:
cur["nodes_explored"] = m.group(1)
cur["solve_time_s"] = m.group(2)

m = SIMPLEX_RE.search(line)
if m:
cur["simplex_iters"] = m.group(1).replace(",", "")

m = GAP_METRICS_RE.search(line)
if m:
cur["abs_gap_user"] = m.group(1)
cur["rel_gap"] = m.group(2)
cur["obj_user"] = m.group(3)
cur["bound_user"] = m.group(4)
cur["obj_scale"] = m.group(5)

m = SOL_FOUND_RE.search(line)
if m:
cur["feasible"] = int(m.group(1))
cur["objective"] = m.group(2)

m = INFEASIBLE_RE.search(line)
if m:
cur["feasible"] = 0

flush()
return records


COLUMNS = [
"instance",
"feasible",
"optimal",
"objective",
"rel_gap",
"abs_gap_user",
"obj_user",
"bound_user",
"solve_time_s",
"simplex_iters",
"nodes_explored",
"scaling_applied",
"bigm_rows",
"rows",
"final_spread",
"scaling_last_iteration",
"scaling_target_norm",
"obj_scaling_status",
"obj_scaling_factor",
"obj_new_scaling_factor",
"obj_scale",
]


def main():
if len(sys.argv) > 1:
lines = []
for path in sys.argv[1:]:
with open(path) as f:
lines.extend(f.readlines())
else:
lines = sys.stdin.readlines()

records = parse_logs(lines)

writer = csv.DictWriter(
sys.stdout, fieldnames=COLUMNS, extrasaction="ignore"
)
writer.writeheader()
for rec in records:
writer.writerow(rec)

n_feasible = sum(1 for r in records if r.get("feasible") == 1)
n_optimal = sum(1 for r in records if r.get("optimal") == 1)
n_total = len(records)
print(
f"# Summary: {n_total} instances, {n_feasible} feasible, {n_optimal} optimal",
file=sys.stderr,
)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions benchmarks/linear_programming/cuopt/run_mip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ int run_single_file(std::string file_path,
settings.tolerances.absolute_tolerance = 1e-6;
settings.presolver = cuopt::linear_programming::presolver_t::Default;
settings.reliability_branching = reliability_branching;
settings.mip_scaling = true;
settings.seed = 42;
cuopt::linear_programming::benchmark_info_t benchmark_info;
settings.benchmark_info_ptr = &benchmark_info;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class mip_solver_settings_t {

/** Initial primal solutions */
std::vector<std::shared_ptr<rmm::device_uvector<f_t>>> initial_solutions;
bool mip_scaling = false;
bool mip_scaling = true;
presolver_t presolver{presolver_t::Default};
/**
* @brief Determinism mode for MIP solver.
Expand Down
29 changes: 22 additions & 7 deletions cpp/src/branch_and_bound/branch_and_bound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -695,9 +695,9 @@ void branch_and_bound_t<i_t, f_t>::set_final_solution(mip_solution_t<i_t, f_t>&
settings_.heuristic_preemption_callback();
}

f_t gap = upper_bound_ - lower_bound;
f_t obj = compute_user_objective(original_lp_, upper_bound_.load());
f_t user_bound = compute_user_objective(original_lp_, lower_bound);
f_t gap = std::abs(obj - user_bound);
f_t gap_rel = user_relative_gap(original_lp_, upper_bound_.load(), lower_bound);
bool is_maximization = original_lp_.obj_scale < 0.0;

Expand All @@ -709,6 +709,13 @@ void branch_and_bound_t<i_t, f_t>::set_final_solution(mip_solution_t<i_t, f_t>&
obj,
is_maximization ? "Upper" : "Lower",
user_bound);
settings_.log.printf(
"MIP_GAP_METRICS abs_gap_user=%e rel_gap=%e obj_user=%.16e bound_user=%.16e obj_scale=%e\n",
gap,
gap_rel,
obj,
user_bound,
original_lp_.obj_scale);

if (gap <= settings_.absolute_mip_gap_tol || gap_rel <= settings_.relative_mip_gap_tol) {
solver_status_ = mip_status_t::OPTIMAL;
Expand Down Expand Up @@ -1155,7 +1162,10 @@ std::pair<node_status_t, rounding_direction_t> branch_and_bound_t<i_t, f_t>::upd
Policy& policy)
{
constexpr f_t inf = std::numeric_limits<f_t>::infinity();
const f_t abs_fathom_tol = settings_.absolute_mip_gap_tol / 10;
const f_t obj_scale_mag = std::abs(original_lp_.obj_scale);
const f_t abs_fathom_tol = obj_scale_mag > f_t(0)
? settings_.absolute_mip_gap_tol / (obj_scale_mag * f_t(10))
: settings_.absolute_mip_gap_tol / f_t(10);
lp_problem_t<i_t, f_t>& leaf_problem = worker->leaf_problem;
lp_solution_t<i_t, f_t>& leaf_solution = worker->leaf_solution;
const f_t upper_bound = policy.upper_bound();
Expand Down Expand Up @@ -1606,7 +1616,8 @@ void branch_and_bound_t<i_t, f_t>::run_scheduler()
#endif

f_t lower_bound = get_lower_bound();
f_t abs_gap = upper_bound_ - lower_bound;
f_t abs_gap = std::abs(compute_user_objective(original_lp_, upper_bound_.load()) -
compute_user_objective(original_lp_, lower_bound));
f_t rel_gap = user_relative_gap(original_lp_, upper_bound_.load(), lower_bound);
i_t last_node_depth = 0;
i_t last_int_infeas = 0;
Expand All @@ -1616,7 +1627,8 @@ void branch_and_bound_t<i_t, f_t>::run_scheduler()
(active_workers_per_strategy_[0] > 0 || node_queue_.best_first_queue_size() > 0)) {
bool launched_any_task = false;
lower_bound = get_lower_bound();
abs_gap = upper_bound_ - lower_bound;
abs_gap = std::abs(compute_user_objective(original_lp_, upper_bound_.load()) -
compute_user_objective(original_lp_, lower_bound));
rel_gap = user_relative_gap(original_lp_, upper_bound_.load(), lower_bound);

repair_heuristic_solutions();
Expand Down Expand Up @@ -1731,14 +1743,16 @@ void branch_and_bound_t<i_t, f_t>::single_threaded_solve()
branch_and_bound_worker_t<i_t, f_t> worker(0, original_lp_, Arow_, var_types_, settings_);

f_t lower_bound = get_lower_bound();
f_t abs_gap = upper_bound_ - lower_bound;
f_t abs_gap = std::abs(compute_user_objective(original_lp_, upper_bound_.load()) -
compute_user_objective(original_lp_, lower_bound));
f_t rel_gap = user_relative_gap(original_lp_, upper_bound_.load(), lower_bound);

while (solver_status_ == mip_status_t::UNSET && abs_gap > settings_.absolute_mip_gap_tol &&
rel_gap > settings_.relative_mip_gap_tol && node_queue_.best_first_queue_size() > 0) {
bool launched_any_task = false;
lower_bound = get_lower_bound();
abs_gap = upper_bound_ - lower_bound;
abs_gap = std::abs(compute_user_objective(original_lp_, upper_bound_.load()) -
compute_user_objective(original_lp_, lower_bound));
rel_gap = user_relative_gap(original_lp_, upper_bound_.load(), lower_bound);

repair_heuristic_solutions();
Expand Down Expand Up @@ -2317,7 +2331,8 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut
report(' ', obj, root_objective_, 0, num_fractional);

f_t rel_gap = user_relative_gap(original_lp_, upper_bound_.load(), root_objective_);
f_t abs_gap = upper_bound_.load() - root_objective_;
f_t abs_gap = std::abs(compute_user_objective(original_lp_, upper_bound_.load()) -
compute_user_objective(original_lp_, root_objective_));
if (rel_gap < settings_.relative_mip_gap_tol || abs_gap < settings_.absolute_mip_gap_tol) {
set_solution_at_root(solution, cut_info);
set_final_solution(solution, root_objective_);
Expand Down
2 changes: 1 addition & 1 deletion cpp/src/dual_simplex/user_problem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ struct user_problem_t {
std::vector<std::string> row_names;
std::vector<std::string> col_names;
f_t obj_constant;
f_t obj_scale; // 1.0 for min, -1.0 for max
f_t obj_scale; // positive for min, netagive for max
bool objective_is_integral{false};
std::vector<variable_type_t> var_types;
std::vector<i_t> Q_offsets;
Expand Down
1 change: 1 addition & 0 deletions cpp/src/mip_heuristics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ set(MIP_LP_NECESSARY_FILES

# Files that are MIP-specific and not needed for pure LP
set(MIP_NON_LP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/mip_scaling_strategy.cu
${CMAKE_CURRENT_SOURCE_DIR}/solve.cu
${CMAKE_CURRENT_SOURCE_DIR}/solver.cu
${CMAKE_CURRENT_SOURCE_DIR}/diversity/assignment_hash_map.cu
Expand Down
32 changes: 18 additions & 14 deletions cpp/src/mip_heuristics/diversity/diversity_manager.cu
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ bool diversity_manager_t<i_t, f_t>::run_presolve(f_t time_limit, timer_t global_
raft::common::nvtx::range fun_scope("run_presolve");
CUOPT_LOG_INFO("Running presolve!");
timer_t presolve_timer(time_limit);

auto term_crit = ls.constraint_prop.bounds_update.solve(*problem_ptr);
if (ls.constraint_prop.bounds_update.infeas_constraints_count > 0) {
stats.presolve_time = timer.elapsed_time();
Expand Down Expand Up @@ -412,23 +413,26 @@ solution_t<i_t, f_t> diversity_manager_t<i_t, f_t>::run_solver()
} else if (!fj_only_run) {
convert_greater_to_less(*problem_ptr);

f_t tolerance_divisor =
problem_ptr->tolerances.absolute_tolerance / problem_ptr->tolerances.relative_tolerance;
if (tolerance_divisor == 0) { tolerance_divisor = 1; }
f_t absolute_tolerance = context.settings.tolerances.absolute_tolerance;
// f_t tolerance_divisor = 100.;

pdlp_solver_settings_t<i_t, f_t> pdlp_settings{};
pdlp_settings.tolerances.relative_primal_tolerance = absolute_tolerance / tolerance_divisor;
pdlp_settings.tolerances.relative_dual_tolerance = absolute_tolerance / tolerance_divisor;
pdlp_settings.time_limit = lp_time_limit;
pdlp_settings.first_primal_feasible = false;
pdlp_settings.concurrent_halt = &global_concurrent_halt;
pdlp_settings.method = method_t::Concurrent;
pdlp_settings.inside_mip = true;
pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2;
pdlp_settings.num_gpus = context.settings.num_gpus;
pdlp_settings.presolver = presolver_t::None;

pdlp_settings.tolerances.absolute_dual_tolerance = absolute_tolerance;
pdlp_settings.tolerances.relative_dual_tolerance =
context.settings.tolerances.relative_tolerance;
pdlp_settings.tolerances.absolute_primal_tolerance = absolute_tolerance;
pdlp_settings.tolerances.relative_primal_tolerance =
context.settings.tolerances.relative_tolerance;
pdlp_settings.time_limit = lp_time_limit;
pdlp_settings.first_primal_feasible = false;
pdlp_settings.concurrent_halt = &global_concurrent_halt;
pdlp_settings.method = method_t::Concurrent;
pdlp_settings.inside_mip = true;
pdlp_settings.pdlp_solver_mode = pdlp_solver_mode_t::Stable2;
pdlp_settings.num_gpus = context.settings.num_gpus;
pdlp_settings.presolver = presolver_t::None;
pdlp_settings.per_constraint_residual = true;
set_pdlp_solver_mode(pdlp_settings);
timer_t lp_timer(lp_time_limit);
auto lp_result = solve_lp_with_method<i_t, f_t>(*problem_ptr, pdlp_settings, lp_timer);

Expand Down
3 changes: 1 addition & 2 deletions cpp/src/mip_heuristics/diversity/lns/rins.cu
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ void rins_t<i_t, f_t>::run_rins()

std::vector<std::vector<f_t>> rins_solution_queue;

mip_solver_context_t<i_t, f_t> fj_context(
&rins_handle, &fixed_problem, context.settings, context.scaling);
mip_solver_context_t<i_t, f_t> fj_context(&rins_handle, &fixed_problem, context.settings);
fj_t<i_t, f_t> fj(fj_context);
solution_t<i_t, f_t> fj_solution(fixed_problem);
fj_solution.copy_new_assignment(cuopt::host_copy(fixed_assignment, rins_handle.get_stream()));
Expand Down
Loading
Loading