Skip to content

B&B logs deduplication#1352

Open
nguidotti wants to merge 16 commits into
NVIDIA:mainfrom
nguidotti:tree-progress
Open

B&B logs deduplication#1352
nguidotti wants to merge 16 commits into
NVIDIA:mainfrom
nguidotti:tree-progress

Conversation

@nguidotti
Copy link
Copy Markdown
Contributor

@nguidotti nguidotti commented Jun 1, 2026

Changes

  • De-duplicate methods for reporting the progress of the MIP solver. Uses std::format to be easier to see the spacing for each column.
  • Added support for the std::format syntax to the logger_t

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 7 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>
@nguidotti nguidotti added this to the 26.08 milestone Jun 1, 2026
@nguidotti nguidotti self-assigned this Jun 1, 2026
@nguidotti nguidotti requested a review from a team as a code owner June 1, 2026 09:23
@nguidotti nguidotti added the non-breaking Introduces a non-breaking change label Jun 1, 2026
@nguidotti nguidotti requested review from kaatish and yuwenchen95 June 1, 2026 09:23
@nguidotti nguidotti added improvement Improves an existing functionality P1 mip labels Jun 1, 2026
@nguidotti nguidotti requested review from aliceb-nv and chris-maes and removed request for kaatish and yuwenchen95 June 1, 2026 09:23
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5656e437-6c37-4d73-a145-7e6efa100363

📥 Commits

Reviewing files that changed from the base of the PR and between 5eb9730 and 739aab2.

📒 Files selected for processing (1)
  • cpp/src/branch_and_bound/branch_and_bound.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • cpp/src/branch_and_bound/branch_and_bound.cpp

📝 Walkthrough

Walkthrough

Refactors branch-and-bound reporting to centralize table headers and gap-to-percentage formatting, adds C++20 std::format logging helpers to logger_t, and updates deterministic sync and cut-pass logging to use the new formatting and header emission.

Changes

Logging and branch-and-bound reporting refactor

Layer / File(s) Summary
Logger: C++20 std::format support
cpp/src/dual_simplex/logger.hpp
Adds <format> and <utility> headers; implements print_format and debug_format template methods that format via std::format and conditionally emit to console (via CUOPT_LOG_INFO/CUOPT_LOG_TRACE or std::printf) and to a file via std::fprintf.
Branch-and-bound reporting: table header, gap helper, and std::format output
cpp/src/branch_and_bound/branch_and_bound.hpp, cpp/src/branch_and_bound/branch_and_bound.cpp
Introduces to_percentage helper for relative-gap → percentage text; declares/implements print_table_header() to centralize progress-table header (adds "Work" column in deterministic mode); refactors report_heuristic() and report() to compute user objective/lower bound, build user_relative_gap, format with to_percentage, and emit rows via std::format; updates cut-pass and main B&B initialization to call print_table_header(); updates deterministic sync logging to use the new user_gap_text.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.17% 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 'B&B logs deduplication' directly relates to the main objective of the PR, which is to de-duplicate methods for reporting MIP solver progress, as confirmed by the raw summary and PR description.
Description check ✅ Passed The description accurately describes the key changes: de-duplication of reporting methods using std::format and addition of std::format support to logger_t, which matches the modifications shown in the raw summary.
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.

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

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Infer (1.2.0)
cpp/src/branch_and_bound/branch_and_bound.cpp

cpp/src/branch_and_bound/branch_and_bound.cpp:8:10: fatal error: 'branch_and_bound/branch_and_bound.hpp' file not found
8 | #include <branch_and_bound/branch_and_bound.hpp>
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/739aab2b1410f7f5f2b9585733ec6ea41ed1be08-df26eec8343bec84/tmp/clang_command_.tmp.b9f008.txt
++Contents of '/tmp/coderabbit-infer/739aab2b1410f7f5f2b9585733ec6ea41ed1be08-df26eec8343bec84/tmp/clang_command_.tmp.b9f008.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExporter" "MAX_STRING_SIZE=6553

... [truncated 1187 characters] ...

nclude"
"-internal-isystem" "/usr/local/include" "-internal-isystem"
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
"-internal-externc-isystem" "/usr/include/x86_64-linux-gnu"
"-internal-externc-isystem" "/include" "-internal-externc-isystem"
"/usr/include" "-Wno-ignored-optimization-argument" "-Wno-everything"
"-fdeprecated-macro" "-ferror-limit" "19" "-fgnuc-version=4.2.1"
"-fskip-odr-check-in-gmf" "-fcxx-exceptions" "-fexceptions"
"-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o"
"/tmp/coderabbit-infer/df26eec8343bec84/file.o" "-x" "c++"
"cpp/src/branch_and_bound/branch_and_bound.cpp" "-O0" "-fno-builtin"
"-include"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../lib/clang_wrappers/global_defines.h"
"-Wno-everything"


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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/branch_and_bound/mip_node.hpp (1)

223-227: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix printf format for uint64_t node_id in B&B debug logging
cpp/src/branch_and_bound/mip_node.hpp uses %ld for current_node->node_id (type uint64_t) in traverse_children(), which is non-portable and can produce undefined behavior.

Proposed fix
+#include <cinttypes>
...
-        printf("Node %ld with no children at depth %d lower bound %e. status %d\n",
+        printf("Node %" PRIu64 " with no children at depth %d lower bound %e. status %d\n",
                current_node->node_id,
                current_node->depth,
                current_node->lower_bound,
                current_node->status);
🤖 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/mip_node.hpp` around lines 223 - 227, The printf in
traverse_children uses %ld for current_node->node_id (type uint64_t) which is
non-portable; update the format to use the correct uint64_t specifier (e.g.,
include <inttypes.h> and use "%" PRIu64 with current_node->node_id) so the
node_id prints portably, or alternatively cast to (unsigned long long) and use
"%llu"; adjust the printf call in traverse_children accordingly.
🤖 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/branch_and_bound/search_tree.hpp`:
- Around line 65-70: The issue is that num_nodes (an omp_atomic_t<i_t>) can
overflow when i_t is a 32-bit int while node IDs are stored as uint64_t; update
the counter to use a 64-bit atomic and stop deriving IDs from a narrower type.
Change num_nodes to an atomic type sized to 64 bits (e.g.,
omp_atomic_t<uint64_t> or std::atomic<uint64_t>), ensure branch() calls use
num_nodes.fetch_add(2) returning a uint64_t, and assign the two child IDs
deterministically (use the returned base id and base id+1 rather than
pre-incrementing ++id) in the mip_node_t constructor calls; apply the same
change to the other occurrence around the second mentioned location (the code
near the 156-157 reference).
- Around line 8-12: The header
cuopt::linear_programming::dual_simplex::search_tree.hpp must be made
self-contained: add explicit standard headers for any std symbols it uses (e.g.,
<vector>, <memory>, <optional>, <string>, <mutex>, <cstdint> or others actually
referenced) rather than relying on transitive includes from mip_node.hpp; update
search_tree.hpp to `#include` those standard headers at top and keep the existing
`#include` <branch_and_bound/mip_node.hpp>, so all symbols used in the
declarations inside the namespace are defined by the header itself.

In `@cpp/src/dual_simplex/logger.hpp`:
- Around line 95-96: The code mutates the shared string msg by replacing a
trailing '\n' with '\0' before calling CUOPT_LOG_INFO, which removes the newline
for subsequent file logging; instead, avoid mutating msg in logger.hpp by
creating a temporary view or a separate string (e.g., msg_no_nl) for the
console/formatting path and leave msg intact for file output; update all
occurrences that replace msg.back() (the instances around CUOPT_LOG_INFO and the
corresponding lines mentioned) to use the temporary non-mutated value when
formatting console output so file logging still receives the original msg with
its newline.
- Around line 19-20: logger.hpp uses std::forward (used in the variadic
forwarding calls around where the logging helper templates invoke
std::forward<Args&&>(args)) but doesn't include <utility>, relying on transitive
includes; fix this by adding `#include` <utility> to the top of
cpp/src/dual_simplex/logger.hpp (alongside the existing `#include` <format>) so
the file is self-contained and IWYU-compliant.

---

Outside diff comments:
In `@cpp/src/branch_and_bound/mip_node.hpp`:
- Around line 223-227: The printf in traverse_children uses %ld for
current_node->node_id (type uint64_t) which is non-portable; update the format
to use the correct uint64_t specifier (e.g., include <inttypes.h> and use "%"
PRIu64 with current_node->node_id) so the node_id prints portably, or
alternatively cast to (unsigned long long) and use "%llu"; adjust the printf
call in traverse_children accordingly.
🪄 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: 9a934bf0-2a56-424f-8d93-17eb639ab669

📥 Commits

Reviewing files that changed from the base of the PR and between ffbe51a and 6d7f0f5.

📒 Files selected for processing (5)
  • cpp/src/branch_and_bound/branch_and_bound.cpp
  • cpp/src/branch_and_bound/branch_and_bound.hpp
  • cpp/src/branch_and_bound/mip_node.hpp
  • cpp/src/branch_and_bound/search_tree.hpp
  • cpp/src/dual_simplex/logger.hpp

Comment thread cpp/src/branch_and_bound/search_tree.hpp Outdated
Comment thread cpp/src/branch_and_bound/search_tree.hpp Outdated
Comment thread cpp/src/dual_simplex/logger.hpp
Comment thread cpp/src/dual_simplex/logger.hpp Outdated
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
@chris-maes
Copy link
Copy Markdown
Contributor

Let's chat before merging

@nguidotti
Copy link
Copy Markdown
Contributor Author

Let's chat before merging

Sure

nguidotti added 2 commits June 1, 2026 19:40
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <format>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is a very heavy heade in terms of compilation times in most c++ implementations. I'd be in favor of keeping it out of common headers and only use it in the TUs where it is required

Copy link
Copy Markdown
Contributor Author

@nguidotti nguidotti Jun 3, 2026

Choose a reason for hiding this comment

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

I did not notice a significant increase in compilation time by including or using the std::format. Maybe if it is used everywhere

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.

BTW, we can skip the compile-time checks of std::format with std::vformat. Although I would say the former is better.

Comment thread cpp/src/branch_and_bound/search_tree.hpp Outdated
nguidotti added 2 commits June 8, 2026 10:01
…ly with restarts.

Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
@nguidotti nguidotti changed the title Track B&B search progress + B&B logs deduplication B&B logs deduplication Jun 8, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/branch_and_bound/search_tree.hpp (1)

39-40: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

progress will stop reflecting deep-tree work accurately.

progress += std::ldexp(f_t(1), -node_ptr->depth) accumulates tree weight in the solver scalar type. Once depth exceeds the mantissa range, contributions are rounded away; for even deeper nodes they underflow to zero. Since this PR turns tree weight into the B&B progress signal, deep subtrees can stop contributing and the reported progress becomes systematically wrong. Store this metric in a dedicated higher-precision or exact representation instead of f_t.

As per coding guidelines: "Prevent numerical instability (overflow, underflow, precision loss) producing wrong results".

Also applies to: 159-159

🤖 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/search_tree.hpp` around lines 39 - 40, The progress
accumulation uses the solver scalar f_t and loses contributions for deep
node_ptr->depth because of mantissa/underflow; change the representation of
progress to a higher-precision or exact type (e.g., long double or a
boost::multiprecision binary/decimal type) and perform the std::ldexp and
addition in that type instead of f_t so deep-tree weights do not round to zero;
update the declaration/initialization of progress and any uses
(search_tree::progress, the accumulation sites at the shown ++num_final_nodes /
progress += std::ldexp(f_t(1), -node_ptr->depth) and the analogous line at 159)
to use the new higher-precision type while converting to f_t only when strictly
needed for downstream solver calculations.

Source: Coding guidelines

🧹 Nitpick comments (1)
cpp/src/branch_and_bound/search_tree.hpp (1)

27-27: ⚡ Quick win

Make this converting constructor explicit.

This currently allows implicit mip_node_t<i_t, f_t>search_tree_t<i_t, f_t> conversions.

Suggested change
-  search_tree_t(mip_node_t<i_t, f_t>&& node) : search_tree_t() { root = std::move(node); }
+  explicit search_tree_t(mip_node_t<i_t, f_t>&& node) : search_tree_t()
+  {
+    root = std::move(node);
+  }

As per coding guidelines: "Use explicit on single-argument constructors in C++ classes".

🤖 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/search_tree.hpp` at line 27, The single-argument
converting constructor search_tree_t(mip_node_t<i_t, f_t>&& node) should be made
explicit to prevent unintended implicit conversions from mip_node_t<i_t, f_t> to
search_tree_t<i_t, f_t>; update the constructor signature for search_tree_t to
add the explicit keyword (i.e., change the constructor definition for
search_tree_t that takes mip_node_t<i_t, f_t>&& to be explicit) while leaving
the implementation otherwise unchanged.

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.

Outside diff comments:
In `@cpp/src/branch_and_bound/search_tree.hpp`:
- Around line 39-40: The progress accumulation uses the solver scalar f_t and
loses contributions for deep node_ptr->depth because of mantissa/underflow;
change the representation of progress to a higher-precision or exact type (e.g.,
long double or a boost::multiprecision binary/decimal type) and perform the
std::ldexp and addition in that type instead of f_t so deep-tree weights do not
round to zero; update the declaration/initialization of progress and any uses
(search_tree::progress, the accumulation sites at the shown ++num_final_nodes /
progress += std::ldexp(f_t(1), -node_ptr->depth) and the analogous line at 159)
to use the new higher-precision type while converting to f_t only when strictly
needed for downstream solver calculations.

---

Nitpick comments:
In `@cpp/src/branch_and_bound/search_tree.hpp`:
- Line 27: The single-argument converting constructor
search_tree_t(mip_node_t<i_t, f_t>&& node) should be made explicit to prevent
unintended implicit conversions from mip_node_t<i_t, f_t> to search_tree_t<i_t,
f_t>; update the constructor signature for search_tree_t to add the explicit
keyword (i.e., change the constructor definition for search_tree_t that takes
mip_node_t<i_t, f_t>&& to be explicit) while leaving the implementation
otherwise unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 43e2811d-3b3b-4e2b-8167-5c6de6a416e9

📥 Commits

Reviewing files that changed from the base of the PR and between da1015f and e47c61f.

📒 Files selected for processing (2)
  • cpp/src/branch_and_bound/branch_and_bound.cpp
  • cpp/src/branch_and_bound/search_tree.hpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • cpp/src/branch_and_bound/branch_and_bound.cpp

nguidotti added 2 commits June 8, 2026 10:09
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
Signed-off-by: Nicolas L. Guidotti <nguidotti@nvidia.com>
@nguidotti nguidotti requested a review from aliceb-nv June 8, 2026 08:35
Copy link
Copy Markdown
Contributor

@aliceb-nv aliceb-nv left a comment

Choose a reason for hiding this comment

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

Thank you! Just a minor nitpick.

Comment on lines +348 to +360
std::format("H {:>12} {:>12} {:^+19.6e} {:^+15.6e} {:>8} {:>7} {:^11} {:^11}",
"", // nodes explored
"", // nodes unexplored
user_obj,
user_lower,
"", // integer infeasible
"", // depth
"", // iter/node
user_gap_text);

if (settings_.deterministic) { log_line += std::format("{:^8}", ""); }
log_line += std::format(" {:>8.2f}", toc(exploration_stats_.start_time));
settings_.log.printf("%s\n", log_line.c_str());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The exact field widths (12, 12, 19, 15, 8, 7, 11 ,11) remain duplicated across calls. Maybe we could have them be macro constants to keep a single source of truth

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 could have a class dedicated to printing the table (since it also used in the other parts of the code) where each column has its own formatting rules (alignment, width, sign, etc.). This seems a little bit overkill, though hahaha

Copy link
Copy Markdown
Contributor

@aliceb-nv aliceb-nv Jun 8, 2026

Choose a reason for hiding this comment

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

I was thinking of something like


#define NODES_EXPL_WIDTH 12
#define NODES_UNEX_WIDTH 12
#define OBJ_WIDTH 19
/*  ... */

#define SECTION_HEADER_FORMAT "{:>" #NODES_EXPL_WIDTH "} {:>" #NODES_UNEX_WIDTH "} {:^+" #OBJ_WIDTH "} /* .... */
#define SECTION_ENTRY_FORMAT  "{:>" #NODES_EXPL_WIDTH "} {:>" #NODES_UNEX_WIDTH "} {:^+" #OBJ_WIDTH ".6e} /* .... */

// ....

std::format("H " SECTION_ENTRY_FORMAT,
                   values....);

Might be clearer in the actual business code, but it's kind of a matter of taste :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Or, as Codex kindly just pointed out to me, do this!

// The {} inside the format specifier tells std::format to look at the arguments for the width
std::format("H {:>{}} {:>{}} {:^+{}.6e}",
            nodes_expl_val, NODES_EXPL_WIDTH,
            nodes_unex_val, NODES_UNEX_WIDTH,
            obj_val,        OBJ_WIDTH);

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 P1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants