Skip to content

MIP log cleanup#1402

Open
nguidotti wants to merge 27 commits into
NVIDIA:mainfrom
nguidotti:last-log-cleanup
Open

MIP log cleanup#1402
nguidotti wants to merge 27 commits into
NVIDIA:mainfrom
nguidotti:last-log-cleanup

Conversation

@nguidotti

@nguidotti nguidotti commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

This PR cleans the logs for the MIP solver. More specifically,

  • De-duplicate how each row of B&B table is constructed using std::format
  • Extend support for std::format to the logger_t
  • Add spacing between different sections of the solver (papilo, cuopt presolve and symmetry detection)
  • Removed duplicated information (problem dimensions, B&B info)
  • Replace last log line with a summary of the solve

Examples

feasible.log
infeasible.log
optimal.log
timeout.log

Checklist

  • I am familiar with the Contributing Guidelines.
  • Testing
    • New or existing tests cover these changes
    • Added tests
    • Created an issue to follow-up
    • NA
  • Documentation
    • The documentation is up to date with these changes
    • Added new documentation
    • NA

nguidotti added 18 commits June 1, 2026 11:05
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…an method.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…ogs to use the std::format variant.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
…ly with restarts.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
@nguidotti nguidotti added this to the 26.08 milestone Jun 8, 2026
@nguidotti nguidotti self-assigned this Jun 8, 2026
@nguidotti nguidotti requested a review from a team as a code owner June 8, 2026 15:43
@nguidotti nguidotti added the non-breaking Introduces a non-breaking change label Jun 8, 2026
@nguidotti nguidotti requested a review from yuwenchen95 June 8, 2026 15:43
@nguidotti nguidotti added the improvement Improves an existing functionality label Jun 8, 2026
@nguidotti nguidotti requested a review from aliceb-nv June 8, 2026 15:43
@nguidotti nguidotti added the mip label Jun 8, 2026
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a B&B timing field to solver stats, refactors branch-and-bound progress/table reporting with unified gap formatting and header helper, introduces std::format-based logger helpers, makes solution-summary logging multi-line and unconditional, and applies minor logging message/level tweaks across presolve/cuts/diversity code.

Changes

MIP Solver Reporting and Diagnostics

Layer / File(s) Summary
B&B progress table & gap formatting
cpp/src/branch_and_bound/branch_and_bound.cpp, cpp/src/branch_and_bound/branch_and_bound.hpp
Adds column-width constants, to_percentage(f_t), and print_table_header(); unifies heuristic/general reporting to use user_relative_gap(...) + to_percentage(...); replaces ad-hoc headers and updates some printf→debug_format/print_format calls.
Logger formatting utilities
cpp/src/dual_simplex/logger.hpp
Adds print_format and debug_format template helpers using std::format, with newline trimming behavior and routing to console and/or active log file.
Solver stats: bnb_time field
cpp/include/cuopt/linear_programming/mip/solver_stats.hpp
Adds bnb_time member to solver_stats_t, initializes it to 0., and includes it in the copy-assignment operator.
Solution summary emission
cpp/src/mip_heuristics/solver_solution.cu, cpp/src/mip_heuristics/solve.cu
Rewrites log_detailed_summary() to build a multi-line formatted report (objective, dual, abs/rel gaps, constraint-violation placeholders) and logs total solve time; log_detailed_summary() is invoked unconditionally from the solver helper.
Misc: presolve, cuts, diversity, solver tweaks
cpp/src/branch_and_bound/symmetry.hpp, cpp/src/cuts/cuts.cpp, cpp/src/mip_heuristics/diversity/diversity_manager.cu, cpp/src/mip_heuristics/presolve/third_party_presolve.cpp, cpp/src/mip_heuristics/solver.cu
Small logging message and log-level adjustments: symmetry start log, "Computing variable bounds..." debug log and printf→debug changes, presolve start wording, Papilo presolve log level/message change, and a minor leading-newline formatting change in a probing log.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • Bubullzz
  • Iroy30
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main focus of the changeset: cleaning up and restructuring MIP solver logs.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly explains the purpose (cleaning MIP solver logs) and lists specific changes, such as de-duplication using std::format, extending format support to logger_t, adding spacing between solver sections, and replacing the final log line with a summary.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
cpp/src/mip_heuristics/solver.cu (1)

493-496: ⚡ Quick win

Use steady_clock for elapsed B&B timing.

std::chrono::system_clock can jump if wall time changes, which can produce inaccurate bnb_time. Use std::chrono::steady_clock for duration measurement.

Suggested change
-        auto t0                               = std::chrono::system_clock::now();
+        auto t0                               = std::chrono::steady_clock::now();
         branch_and_bound_status               = branch_and_bound->solve(branch_and_bound_solution);
-        std::chrono::duration<double> elapsed = std::chrono::system_clock::now() - t0;
+        std::chrono::duration<double> elapsed = std::chrono::steady_clock::now() - t0;
         context.stats.bnb_time                = elapsed.count();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/mip_heuristics/solver.cu` around lines 493 - 496, The timing uses
std::chrono::system_clock which can jump; change it to use
std::chrono::steady_clock for measuring branch-and-bound elapsed time: replace
the t0 capture and subsequent duration calculation around
branch_and_bound->solve(branch_and_bound_solution) so that t0 is set with
std::chrono::steady_clock::now() and the duration is computed from
std::chrono::steady_clock::now() - t0, then store elapsed.count() into
context.stats.bnb_time (leaving branch_and_bound_status and
branch_and_bound->solve unchanged).
cpp/src/branch_and_bound/branch_and_bound.cpp (1)

37-43: ⚡ Quick win

Add the direct <format> include for std::format usage.

This file now relies on std::format in multiple paths, but it does not include <format> directly. Please add it to avoid transitive-include fragility.

Suggested diff
 `#include` <algorithm>
 `#include` <cmath>
 `#include` <cstdio>
 `#include` <cstdlib>
+#include <format>
 `#include` <limits>
 `#include` <string>
 `#include` <vector>

As per coding guidelines, “Apply Include What You Use (IWYU) principle in C++ headers.”

Also applies to: 211-217, 335-357

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/branch_and_bound/branch_and_bound.cpp` around lines 37 - 43, Summary:
The file uses std::format but doesn't directly include <format>, violating IWYU;
add the direct include. Update branch_and_bound.cpp by inserting `#include`
<format> alongside the other headers near the top so std::format calls compile
reliably; also scan the locations mentioned (lines around the blocks that use
std::format—the contexts referenced as 211-217 and 335-357) and add or ensure a
<format> include there or at the top of the translation unit if those uses are
in the same file, guaranteeing all std::format uses (e.g., in functions that
call std::format) have the direct <format> include.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/src/dual_simplex/logger.hpp`:
- Around line 19-20: The header cpp/src/dual_simplex/logger.hpp uses
std::string_view (referenced by the CUOPT_LOG_ACTIVE_LEVEL macro and related
logging functions) but doesn't include <string_view>; add `#include` <string_view>
alongside the other includes at the top of logger.hpp so the header is
self-contained and no longer relies on transitive includes.
- Around line 96-97: The logger misuses std::string_view (calling c_str()) and
creates a view into a temporary, and print_format ignores the trimmed view; fix
by producing an owning trimmed std::string when modification is needed and use
its c_str() for logging, and update both debug_format and print_format to use
that trimmed std::string (or, if you prefer non-owning, use msg_view.data() with
a length-based logging API) so no dangling pointer or ignored trimming occurs;
change references in logger.hpp in the debug_format and print_format
implementations to use the corrected trimmed std::string (or data()+length
approach) instead of msg_view.c_str() or msg.c_str().

In `@cpp/src/mip_heuristics/solver_solution.cu`:
- Around line 252-256: The current branch for !has_solution sets obj, gap, and
dual_bound to infinity which overwrites a valid solver bound; modify the block
so that when has_solution is false you still set obj and gap to
std::numeric_limits<f_t>::infinity() but do not overwrite dual_bound — either
leave dual_bound as obtained from stats_.get_solution_bound() or explicitly set
dual_bound = stats_.get_solution_bound(); locate the logic around has_solution,
obj, gap, dual_bound in solver_solution.cu and update accordingly.

---

Nitpick comments:
In `@cpp/src/branch_and_bound/branch_and_bound.cpp`:
- Around line 37-43: Summary: The file uses std::format but doesn't directly
include <format>, violating IWYU; add the direct include. Update
branch_and_bound.cpp by inserting `#include` <format> alongside the other headers
near the top so std::format calls compile reliably; also scan the locations
mentioned (lines around the blocks that use std::format—the contexts referenced
as 211-217 and 335-357) and add or ensure a <format> include there or at the top
of the translation unit if those uses are in the same file, guaranteeing all
std::format uses (e.g., in functions that call std::format) have the direct
<format> include.

In `@cpp/src/mip_heuristics/solver.cu`:
- Around line 493-496: The timing uses std::chrono::system_clock which can jump;
change it to use std::chrono::steady_clock for measuring branch-and-bound
elapsed time: replace the t0 capture and subsequent duration calculation around
branch_and_bound->solve(branch_and_bound_solution) so that t0 is set with
std::chrono::steady_clock::now() and the duration is computed from
std::chrono::steady_clock::now() - t0, then store elapsed.count() into
context.stats.bnb_time (leaving branch_and_bound_status and
branch_and_bound->solve unchanged).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4c889dc2-982e-450d-8474-f1199d65f7fb

📥 Commits

Reviewing files that changed from the base of the PR and between 2384454 and ec577e6.

📒 Files selected for processing (7)
  • cpp/include/cuopt/linear_programming/mip/solver_stats.hpp
  • cpp/src/branch_and_bound/branch_and_bound.cpp
  • cpp/src/branch_and_bound/branch_and_bound.hpp
  • cpp/src/dual_simplex/logger.hpp
  • cpp/src/mip_heuristics/solve.cu
  • cpp/src/mip_heuristics/solver.cu
  • cpp/src/mip_heuristics/solver_solution.cu

Comment thread cpp/src/dual_simplex/logger.hpp
Comment thread cpp/src/dual_simplex/logger.hpp Outdated
Comment thread cpp/src/mip_heuristics/solver_solution.cu Outdated
@nguidotti nguidotti changed the title MIP solver summary MIP log cleanup Jun 9, 2026
@nguidotti nguidotti mentioned this pull request Jun 9, 2026
8 tasks
nguidotti added 2 commits June 9, 2026 15:06
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
papilo_problem.getNRows(),
papilo_problem.getNCols(),
papilo_problem.getConstraintMatrix().getNnz());
CUOPT_LOG_DEBUG("Original problem: %d constraints, %d variables, %d nonzeros",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already log the dimensions of the problems in another place. This is duplicated info

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
cpp/src/dual_simplex/logger.hpp (1)

97-100: ⚡ Quick win

Fix inconsistent newline check and consider more efficient trimming.

Lines 97–98 (and 154–155 in debug_format) check msg.ends_with("\n") but then modify msg_no_newline. This inconsistency is confusing and could cause bugs if the code is later refactored. Additionally, copying the entire string just to remove one character is inefficient.

♻️ Suggested improvement

Use string_view to avoid the copy, or at least check the same variable you modify:

 `#ifdef` CUOPT_LOG_ACTIVE_LEVEL
-        std::string msg_no_newline = msg;
-        if (msg_no_newline.size() > 0 && msg.ends_with("\n")) { msg_no_newline.pop_back(); }
-        
-        CUOPT_LOG_INFO("%s%s", log_prefix.c_str(), msg_no_newline.c_str());
+        std::string_view msg_view{msg};
+        if (!msg_view.empty() && msg_view.back() == '\n') { msg_view.remove_suffix(1); }
+        CUOPT_LOG_INFO("%s%.*s", log_prefix.c_str(), static_cast<int>(msg_view.size()), msg_view.data());

Apply the analogous change in debug_format (lines 154–156), replacing CUOPT_LOG_INFO with CUOPT_LOG_TRACE.

Also applies to: 154-156

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/dual_simplex/logger.hpp` around lines 97 - 100, The newline-trim is
inconsistent and inefficient: the code checks msg.ends_with("\n") but pops from
msg_no_newline and copies the whole string; change both occurrences (in the
function that logs with CUOPT_LOG_INFO and in debug_format which should use
CUOPT_LOG_TRACE) to operate on a std::string_view (or at minimum check and pop
from the same variable) so you avoid the full copy — e.g., create a
std::string_view sv = msg; if (sv.size() && sv.back() == '\n')
sv.remove_suffix(1); then pass sv.data() (or format from the view) to
CUOPT_LOG_INFO and use CUOPT_LOG_TRACE in the debug_format variant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/src/dual_simplex/logger.hpp`:
- Line 94: Replace the non-idiomatic perfect-forwarding usage
std::forward<Args&&>(args)... with std::forward<Args>(args)... in both places
where the formatted message is constructed: the std::format call that
initializes msg and the debug_format function; ensure you update the template
forwarding in those call sites (refer to the msg construction and debug_format)
to use std::forward<Args>(args)... so forwarding follows standard C++
conventions.

---

Nitpick comments:
In `@cpp/src/dual_simplex/logger.hpp`:
- Around line 97-100: The newline-trim is inconsistent and inefficient: the code
checks msg.ends_with("\n") but pops from msg_no_newline and copies the whole
string; change both occurrences (in the function that logs with CUOPT_LOG_INFO
and in debug_format which should use CUOPT_LOG_TRACE) to operate on a
std::string_view (or at minimum check and pop from the same variable) so you
avoid the full copy — e.g., create a std::string_view sv = msg; if (sv.size() &&
sv.back() == '\n') sv.remove_suffix(1); then pass sv.data() (or format from the
view) to CUOPT_LOG_INFO and use CUOPT_LOG_TRACE in the debug_format variant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8d7a3265-92d5-4334-8cb3-00dcf9c3dc2e

📥 Commits

Reviewing files that changed from the base of the PR and between 4b3a6b6 and e7523fd.

📒 Files selected for processing (1)
  • cpp/src/dual_simplex/logger.hpp

Comment thread cpp/src/dual_simplex/logger.hpp Outdated
nguidotti added 5 commits June 9, 2026 16:00
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
@nguidotti nguidotti requested a review from a team as a code owner June 10, 2026 12:56
@nguidotti nguidotti requested a review from tmckayus June 10, 2026 12:56
@mlubin

mlubin commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Is there an easy way to show a side-by-side of the key differences before and after to facilitate review?

@nguidotti

nguidotti commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

Is there an easy way to show a side-by-side of the key differences before and after to facilitate review?

I can add diff of each log

@nguidotti

nguidotti commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

"old" logs (from the main branch):
feasible_old.log
infeasible_old.log
optimal_old.log
timeout_old.log

Side-by-side:
feasible_diff.log
infeasible_diff.log
optimal_diff.log
timeout_diff.log

edit: the spacing in the diff version make it harder to see. I think the best way is to open them side-by-side in your text editor

The MIP log cleanup (PR NVIDIA#1402) renamed `log_detailed_summary()` output
from "Solution objective: ..." to "Best objective ...", but test_cli.sh
was not updated to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
@nguidotti nguidotti requested review from chris-maes and mlubin and removed request for yuwenchen95 June 11, 2026 08:05
@mlubin

mlubin commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

logdiff.html

@mlubin

mlubin commented Jun 11, 2026

Copy link
Copy Markdown
Contributor
  • The optimal case has a new extra blank line at the end.
  • "The problem is integer infeasible." this is a bit jargon-y. Maybe "No feasible solution exists."?
  • The timeout case has 4 blank lines between the iteration logs and the summary at the end. All other cases have 2.
  • Should we consider using %g formatting for the summary to be a bit more human friendly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improves an existing functionality mip non-breaking Introduces a non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants