Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6f88f67
Don't add extra slacks (artifical variables) for ranged rows
chris-maes Jun 11, 2025
94307c9
Merge branch 'branch-25.05' into less_artificals
rgsl888prabhu Jul 17, 2025
c0f5886
Add callbacks to send root solution from original problem
hlinsen Nov 3, 2025
3cb93e7
Fix reduced costs and crossover call
hlinsen Nov 5, 2025
3eb950b
Pass correct vstatus vector
hlinsen Nov 5, 2025
4a1cf94
Debugging dual inf norm assertion
hlinsen Nov 5, 2025
3229b0c
Implement conversion of ge contraints to le
hlinsen Nov 5, 2025
49c97ba
Lower tolerance and run dual phase 2 validation
hlinsen Nov 6, 2025
d333846
Wip save
hlinsen Nov 7, 2025
0351a83
Pull changes from chris less artificial PR.
hlinsen Nov 7, 2025
00a89fe
Remove prints
hlinsen Nov 8, 2025
ed71447
Merge branch 'main' of github.com:NVIDIA/cuopt into concurrent-root-s…
hlinsen Nov 8, 2025
bfe9be9
Remove prints
hlinsen Nov 8, 2025
2a9c994
Debugging successive calls with vstatus
hlinsen Nov 11, 2025
32c6edb
Fix precision issues
hlinsen Nov 12, 2025
4764d51
Save debugging state
hlinsen Nov 12, 2025
b6aad5d
Remove unecessary original problem argument
hlinsen Nov 12, 2025
1c0d139
Call concurrent mode from mip root solve
hlinsen Nov 12, 2025
a198738
Use atomic flag to send root solution
hlinsen Nov 13, 2025
031b7de
Use atomic flag to send root solution
hlinsen Nov 13, 2025
55de62d
Fix merge conflicts
hlinsen Nov 13, 2025
63d8b87
Merge branch 'concurrent-root-solve' of github.com:hlinsen/cuopt into…
hlinsen Nov 13, 2025
68361a5
Disable print
hlinsen Nov 13, 2025
dcca90b
Set root solution in rins
hlinsen Nov 13, 2025
4a10885
Only use concurrent root solve for main bb thread
hlinsen Nov 13, 2025
32d984a
Merge branch 'main' of github.com:NVIDIA/cuopt into concurrent-root-s…
hlinsen Nov 14, 2025
94182cc
Merge branch 'main' of github.com:NVIDIA/cuopt into concurrent-root-s…
hlinsen Nov 14, 2025
bc002c4
Merge branch 'concurrent-root-solve' of github.com:hlinsen/cuopt into…
hlinsen Nov 14, 2025
26ccae5
Fix asserts
hlinsen Nov 14, 2025
093cbcb
Disable rins logs
hlinsen Nov 14, 2025
ca7c9d3
Recompute dual solution after primal push
hlinsen Nov 17, 2025
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
128 changes: 104 additions & 24 deletions cpp/src/dual_simplex/branch_and_bound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <omp.h>
#include <algorithm>
#include <dual_simplex/branch_and_bound.hpp>
#include <dual_simplex/crossover.hpp>
#include <dual_simplex/initial_basis.hpp>
Comment on lines 8 to 12
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix duplicated NUMERICAL_ISSUES condition and unreachable “Unbounded” branch.

In the main-thread root path, the crossover status handling has a clear copy‑paste bug:

if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) {
  settings_.log.printf("MIP Infeasible\n");
  return mip_status_t::INFEASIBLE;
}
if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) {
  settings_.log.printf("MIP Unbounded\n");
  ...
  return mip_status_t::UNBOUNDED;
}

The second if is unreachable and the intended status for the “MIP Unbounded” path is almost certainly something else (e.g., a distinct crossover enum for unboundedness or a feasibility variant). As written, you will never hit the “MIP Unbounded” path, and all NUMERICAL_ISSUES are treated as infeasible.

Please correct the second condition to the appropriate status (and/or use else if) so that infeasibility vs. unboundedness is distinguished as intended, and add a brief comment documenting the mapping between crossover_status_t and MIP status here.

Also applies to: 1061-1095, 1111-1169

🧰 Tools
🪛 Clang (14.0.6)

[error] 8-8: 'omp.h' file not found

(clang-diagnostic-error)

#include <dual_simplex/logger.hpp>
#include <dual_simplex/mip_node.hpp>
Expand Down Expand Up @@ -1059,34 +1060,113 @@ mip_status_t branch_and_bound_t<i_t, f_t>::solve(mip_solution_t<i_t, f_t>& solut

root_relax_soln_.resize(original_lp_.num_rows, original_lp_.num_cols);

settings_.log.printf("Solving LP root relaxation\n");
settings_.log.printf("Waiting for PDLP root relaxation\n");

simplex_solver_settings_t lp_settings = settings_;
lp_settings.inside_mip = 1;
lp_status_t root_status = solve_linear_program_advanced(
original_lp_, stats_.start_time, lp_settings, root_relax_soln_, root_vstatus_, edge_norms_);
stats_.total_lp_iters = root_relax_soln_.iterations;
stats_.total_lp_solve_time = toc(stats_.start_time);
if (root_status == lp_status_t::INFEASIBLE) {
settings_.log.printf("MIP Infeasible\n");
// FIXME: rarely dual simplex detects infeasible whereas it is feasible.
// to add a small safety net, check if there is a primal solution already.
// Uncomment this if the issue with cost266-UUE is resolved
// if (settings.heuristic_preemption_callback != nullptr) {
// settings.heuristic_preemption_callback();
// }
return mip_status_t::INFEASIBLE;
}
if (root_status == lp_status_t::UNBOUNDED) {
settings_.log.printf("MIP Unbounded\n");
if (settings_.heuristic_preemption_callback != nullptr) {
settings_.heuristic_preemption_callback();
if (!is_main_thread()) {
lp_status_t root_status = solve_linear_program_advanced(
original_lp_, stats_.start_time, lp_settings, root_relax_soln_, root_vstatus_, edge_norms_);
stats_.total_lp_iters = root_relax_soln_.iterations;
stats_.total_lp_solve_time = toc(stats_.start_time);

Comment on lines 1060 to +1072
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Avoid unbounded busy-spin on root_relaxation_solution_set_ and clarify the callback contract.

The main-thread path now does:

settings_.log.printf("Waiting for PDLP root relaxation\n");
// ...
while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) {
  continue;
}

This pure spin loop will:

  • Burn a CPU core while PDLP is running, and
  • Deadlock indefinitely if the root-relaxation callback is never invoked (e.g., error path, early exit, wiring issue), with no time-limit or cancellation check.

For robustness, consider at least:

  • Adding a time-limit/interrupt check in the loop, and
  • Using std::this_thread::sleep_for or a condition variable/atomic wait to avoid a hot spin.

Also, make the contract explicit (in the header or nearby comment) that all PDLP paths must eventually call the setter that flips root_relaxation_solution_set_ (with memory_order_release) even on failure/time-limit, so B&B cannot hang forever waiting for a solution that never arrives.

To validate the wiring, please grep for all writes to root_relaxation_solution_set_ and ensure they are on the success and failure/time-limit paths of the PDLP root solve.

Also applies to: 1111-1136


🏁 Script executed:

#!/bin/bash
rg -n "root_relaxation_solution_set_" cpp

Length of output: 347


I need to see more context to verify the reviewer's concerns about the deadlock risk. Let me examine the spin loop and callback invocation sites.


---

🏁 Script executed:

```shell
#!/bin/bash
# Show the spin loop context (lines 1110-1120)
sed -n '1110,1120p' cpp/src/dual_simplex/branch_and_bound.cpp

Length of output: 520


🏁 Script executed:

#!/bin/bash
# Show the callback setter context (lines 135-145)
sed -n '135,145p' cpp/src/dual_simplex/branch_and_bound.hpp

Length of output: 449


🏁 Script executed:

#!/bin/bash
# Search for PDLP solver invocation to understand when callback is called
rg -n "pdlp|PDLP" cpp/src/dual_simplex/branch_and_bound.cpp -A 2 -B 2 | head -100

Length of output: 214


🏁 Script executed:

#!/bin/bash
# Look for error handling or time-limit paths that might involve root_relaxation
rg -n "time.limit|error|timeout|fail" cpp/src/dual_simplex/branch_and_bound.cpp -C 2 | head -80

Length of output: 3311


🏁 Script executed:

#!/bin/bash
# Check if there's a destructor or cleanup code
rg -n "~|destructor" cpp/src/dual_simplex/branch_and_bound.hpp

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check for any thread join or wait logic in main thread
rg -n "join|detach|thread" cpp/src/dual_simplex/branch_and_bound.cpp -C 2 | head -100

Length of output: 4639


🏁 Script executed:

#!/bin/bash
# Look at the section mentioned in "Also applies to: 1111-1136"
sed -n '1111,1136p' cpp/src/dual_simplex/branch_and_bound.cpp

Length of output: 1206


🏁 Script executed:

#!/bin/bash
# Search for where the callback/lambda capturing root_relaxation_solution_set_ is registered
rg -n "root_relaxation_solution_set_|set_root_relax" cpp/src/dual_simplex/branch_and_bound.cpp -B 5 -A 5 | head -150

Length of output: 592


I need to find where the callback method is defined and all sites that invoke it.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find the method containing the setter at line 137
sed -n '120,150p' cpp/src/dual_simplex/branch_and_bound.hpp

Length of output: 1431


🏁 Script executed:

#!/bin/bash
# Search for all invocations of the root relaxation setter callback
rg -n "set_root_relax|set_root_relax_soln" cpp/src/dual_simplex/ -B 3 -A 1

Length of output: 491


🏁 Script executed:

#!/bin/bash
# Search for PDLP configuration, callback setup, or lambda capture
rg -n "pdlp_solver|pdlp_settings|AddCallback|callback|set_root" cpp/src/dual_simplex/branch_and_bound.cpp -B 2 -A 2 | head -150

Length of output: 3680


🏁 Script executed:

#!/bin/bash
# Check if there's error handling in the non-main thread path (lines 1067-1110)
sed -n '1067,1110p' cpp/src/dual_simplex/branch_and_bound.cpp

Length of output: 1969


🏁 Script executed:

#!/bin/bash
# Search for where PDLP solver is invoked and where callbacks might be registered
rg -n "solve_linear_program_advanced|pdlp" cpp/src/dual_simplex/ --type=hpp --type=cpp -B 5 -A 5 | head -200

Length of output: 27


I need to find where set_root_relaxation_solution() is actually invoked. The earlier search didn't find call sites, which is concerning.


---

🏁 Script executed:

```shell
#!/bin/bash
# Search for call sites of set_root_relaxation_solution
rg -n "set_root_relaxation_solution" cpp/src/dual_simplex/ -A 1 -B 1

Length of output: 312


🏁 Script executed:

#!/bin/bash
# Correct syntax and search for PDLP solver setup
rg -n "solve_linear_program_advanced" cpp/src/dual_simplex/

Length of output: 727


🏁 Script executed:

#!/bin/bash
# Look for callback mechanism or PDLP configuration
rg -n "callback|Callback" cpp/src/dual_simplex/branch_and_bound.hpp | head -30

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Check main thread detection and thread spawning
sed -n '1030,1080p' cpp/src/dual_simplex/branch_and_bound.cpp

Length of output: 1993


🏁 Script executed:

#!/bin/bash
# Search for where PDLP might be set up or where branch_and_bound is used as callback
rg -n "branch_and_bound" cpp/src/dual_simplex/ --type cpp -l

Length of output: 114


🏁 Script executed:

#!/bin/bash
# Look for files that might setup PDLP with callback
find cpp/src -name "*.cpp" -o -name "*.h" | xargs grep -l "set_root_relaxation_solution" 2>/dev/null

Length of output: 0


The setter is never called within the B&B code itself—it must be invoked from external PDLP worker. Let me trace the callback mechanism.


---

🏁 Script executed:

```shell
#!/bin/bash
# Examine solve_linear_program_advanced definition
sed -n '103,180p' cpp/src/dual_simplex/solve.cpp

Length of output: 3592


🏁 Script executed:

#!/bin/bash
# Search for branch_and_bound usage in solve.cpp
rg -n "branch_and_bound|set_root_relaxation" cpp/src/dual_simplex/solve.cpp -B 3 -A 3

Length of output: 949


🏁 Script executed:

#!/bin/bash
# Look for PDLP or callback context setup
rg -n "pdlp|PDLP|context|Context|callback" cpp/src/dual_simplex/solve.cpp | head -50

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if branch_and_bound is passed to solve functions or if there's threading
rg -n "thread|Thread|PDLP|pdlp|worker" cpp/src/dual_simplex/branch_and_bound.cpp | head -40

Length of output: 1930


🏁 Script executed:

#!/bin/bash
# Look for any MIP root solve entry point that might interface with external PDLP
rg -n "root.*relax|mip_status_t.*solve|solve.*mip" cpp/src/dual_simplex/branch_and_bound.cpp | head -30

Length of output: 2162


Based on my verification, the reviewer's concern is confirmed as a legitimate major issue. Here's what I found:

Key findings:

  1. set_root_relaxation_solution() (the setter) is never called from within B&B code—it must be invoked by external PDLP worker thread
  2. The main thread path (line 1112) enters a pure spin loop with no timeout, no sleep, and no condition variable
  3. If the external PDLP never calls the setter (due to error, timeout, or wiring failure), the main thread deadlocks indefinitely
  4. The non-main thread path (line 1068) directly solves the LP but doesn't set the flag on error paths before returning
  5. Comment at line 1111 references "diversity manager" suggesting external integration, confirming the setter depends on external code

Avoid unbounded busy-spin on root_relaxation_solution_set_ and guarantee callback contract.

The main-thread path (line 1112) enters a pure spin loop with no safeguards:

while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) {
  continue;
}

This will deadlock indefinitely if the external PDLP worker never calls set_root_relaxation_solution() (e.g., error path, timeout, or missing callback wiring), since there is no time-limit check or cancellation mechanism.

Required fixes:

  1. Add a time-limit check in the spin loop (or use std::condition_variable or std::atomic::wait for better efficiency)
  2. Document the callback contract: ensure all PDLP exit paths (success, error, time-limit) invoke the setter with memory_order_release
  3. Add a safety net (e.g., break after timeout) to prevent indefinite hangs

Also verify that all external PDLP error handlers guarantee invoking the setter before returning control to B&B.

🤖 Prompt for AI Agents
In cpp/src/dual_simplex/branch_and_bound.cpp around lines 1061-1072 and the spin
at ~1112, the main-thread busy-wait on root_relaxation_solution_set_ can
deadlock if the external PDLP never calls set_root_relaxation_solution();
replace the unbounded spin with a safe wait (prefer std::condition_variable or
std::atomic::wait with a timeout) and add a timeout/failure path that breaks the
wait, logs the timeout, and returns an error instead of spinning forever; also
ensure every PDLP exit path (success and all error/timeout handlers) calls
set_root_relaxation_solution() with memory_order_release (or signals the
condition_variable) so the contract is always fulfilled, and add unit/inline
comments documenting the requirement.

if (root_status == lp_status_t::INFEASIBLE) {
settings_.log.printf("MIP Infeasible\n");
// FIXME: rarely dual simplex detects infeasible whereas it is feasible.
// to add a small safety net, check if there is a primal solution already.
// Uncomment this if the issue with cost266-UUE is resolved
// if (settings.heuristic_preemption_callback != nullptr) {
// settings.heuristic_preemption_callback();
// }
return mip_status_t::INFEASIBLE;
}
if (root_status == lp_status_t::UNBOUNDED) {
settings_.log.printf("MIP Unbounded\n");
if (settings_.heuristic_preemption_callback != nullptr) {
settings_.heuristic_preemption_callback();
}
return mip_status_t::UNBOUNDED;
}
return mip_status_t::UNBOUNDED;
}

if (root_status == lp_status_t::TIME_LIMIT) {
status_ = mip_exploration_status_t::TIME_LIMIT;
return set_final_solution(solution, -inf);
if (root_status == lp_status_t::TIME_LIMIT) {
status_ = mip_exploration_status_t::TIME_LIMIT;
return set_final_solution(solution, -inf);
}
} else {
i_t validation_iters = 0;
// edge_norms_.clear();
// settings_.set_log(true);
// dual::status_t lp_status = dual_phase2(2,
// 0,
// stats_.start_time,
// original_lp_,
// settings_,
// root_vstatus_,
// root_relax_soln_,
// validation_iters,
// edge_norms_);
// settings_.log.printf("Validation iterations root relaxation: %d\n", validation_iters);
// exit(0);

// Wait for the root relaxation solution to be sent by the diversity manager
while (!root_relaxation_solution_set_.load(std::memory_order_acquire)) {
continue;
}
stats_.total_lp_iters = root_relax_soln_.iterations;
stats_.total_lp_solve_time = toc(stats_.start_time);
// Crush the root relaxation solution on converted user problem
std::vector<f_t> crushed_root_x;
crush_primal_solution(
original_problem_, original_lp_, root_relax_soln_.x, new_slacks_, crushed_root_x);
std::vector<f_t> crushed_root_y;
std::vector<f_t> crushed_root_z;

f_t dual_res_inf = crush_dual_solution(original_problem_,
original_lp_,
new_slacks_,
root_relax_soln_.y,
root_relax_soln_.z,
crushed_root_y,
crushed_root_z);
settings_.log.printf("Dual residual inf: %e\n", dual_res_inf);

root_relax_soln_.x = crushed_root_x;
root_relax_soln_.y = crushed_root_y;
root_relax_soln_.z = crushed_root_z;

// Call crossover on the crushed solution
lp_solution_t<i_t, f_t> crossover_solution(original_lp_.num_rows, original_lp_.num_cols);
crossover_status_t crossover_status = crossover(original_lp_,
settings_,
root_relax_soln_,
stats_.start_time,
crossover_solution,
root_vstatus_);
settings_.log.printf("Crossover status: %d\n", crossover_status);

if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) {
settings_.log.printf("MIP Infeasible\n");
// FIXME: rarely dual simplex detects infeasible whereas it is feasible.
// to add a small safety net, check if there is a primal solution already.
// Uncomment this if the issue with cost266-UUE is resolved
// if (settings.heuristic_preemption_callback != nullptr) {
// settings.heuristic_preemption_callback();
// }
return mip_status_t::INFEASIBLE;
}
if (crossover_status == crossover_status_t::NUMERICAL_ISSUES) {
settings_.log.printf("MIP Unbounded\n");
if (settings_.heuristic_preemption_callback != nullptr) {
settings_.heuristic_preemption_callback();
}
return mip_status_t::UNBOUNDED;
}

if (crossover_status == crossover_status_t::TIME_LIMIT ||
crossover_status == crossover_status_t::CONCURRENT_LIMIT) {
status_ = mip_exploration_status_t::TIME_LIMIT;
return set_final_solution(solution, -inf);
}
}

assert(root_vstatus_.size() == original_lp_.num_cols);
Expand Down
24 changes: 24 additions & 0 deletions cpp/src/dual_simplex/branch_and_bound.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <dual_simplex/simplex_solver_settings.hpp>
#include <dual_simplex/solution.hpp>
#include <dual_simplex/types.hpp>
#include <utilities/macros.cuh>
#include <utilities/omp_helpers.hpp>

#include <omp.h>
Expand Down Expand Up @@ -118,9 +119,29 @@ class branch_and_bound_t {
// Set an initial guess based on the user_problem. This should be called before solve.
void set_initial_guess(const std::vector<f_t>& user_guess) { guess_ = user_guess; }

// Set the root solution found by PDLP
void set_root_relaxation_solution(const std::vector<f_t>& primal,
const std::vector<f_t>& dual,
const std::vector<f_t>& reduced_costs,
f_t objective,
f_t user_objective,
i_t iterations)
{
root_relax_soln_.x = primal;
root_relax_soln_.y = dual;
root_relax_soln_.z = reduced_costs;
root_objective_ = objective;
root_relax_soln_.objective = objective;
root_relax_soln_.user_objective = user_objective;
root_relax_soln_.iterations = iterations;
root_relaxation_solution_set_.store(true, std::memory_order_release);
}

// Set a solution based on the user problem during the course of the solve
void set_new_solution(const std::vector<f_t>& solution);

void set_main_thread(bool main_thread) { main_thread_ = main_thread; }

// Repair a low-quality solution from the heuristics.
bool repair_solution(const std::vector<f_t>& leaf_edge_norms,
const std::vector<f_t>& potential_solution,
Expand All @@ -130,6 +151,7 @@ class branch_and_bound_t {
f_t get_upper_bound();
f_t get_lower_bound();
i_t get_heap_size();
bool is_main_thread() const { return main_thread_; }

// The main entry routine. Returns the solver status and populates solution with the incumbent.
mip_status_t solve(mip_solution_t<i_t, f_t>& solution);
Expand Down Expand Up @@ -180,6 +202,8 @@ class branch_and_bound_t {
f_t root_objective_;
lp_solution_t<i_t, f_t> root_relax_soln_;
std::vector<f_t> edge_norms_;
std::atomic<bool> root_relaxation_solution_set_{false};
bool main_thread_{false};

// Pseudocosts
pseudo_costs_t<i_t, f_t> pc_;
Expand Down
1 change: 1 addition & 0 deletions cpp/src/dual_simplex/crossover.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,7 @@ crossover_status_t crossover(const lp_problem_t<i_t, f_t>& lp,
lp, settings, start_time, solution, ft, basic_list, nonbasic_list, superbasic_list, vstatus);
if (primal_push_status < 0) { return return_to_status(primal_push_status); }
print_crossover_info(lp, settings, vstatus, solution, "Primal push complete");
compute_dual_solution_from_basis(lp, ft, basic_list, nonbasic_list, solution.y, solution.z);
} else {
settings.log.printf("No primal push needed. No superbasic variables\n");
}
Expand Down
29 changes: 22 additions & 7 deletions cpp/src/dual_simplex/phase2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1910,6 +1910,7 @@ void set_primal_variables_on_bounds(const lp_problem_t<i_t, f_t>& lp,
std::vector<f_t>& x)
{
const i_t n = lp.num_cols;
settings.log.printf("tolerance %e\n", settings.fixed_tol);
for (i_t j = 0; j < n; ++j) {
// We set z_j = 0 for basic variables
// But we explicitally skip setting basic variables here
Expand All @@ -1927,11 +1928,19 @@ void set_primal_variables_on_bounds(const lp_problem_t<i_t, f_t>& lp,
}
x[j] = lp.lower[j];
vstatus[j] = variable_status_t::NONBASIC_FIXED;
} else if (z[j] == 0 && lp.lower[j] > -inf && vstatus[j] == variable_status_t::NONBASIC_LOWER) {
// } else if (z[j] == 0 && lp.lower[j] > -inf && vstatus[j] ==
// variable_status_t::NONBASIC_LOWER) {
// x[j] = lp.lower[j];
// } else if (z[j] == 0 && lp.upper[j] < inf && vstatus[j] ==
// variable_status_t::NONBASIC_UPPER) {
// x[j] = lp.upper[j];
} else if (std::abs(z[j]) <= fixed_tolerance && lp.lower[j] > -inf &&
vstatus[j] == variable_status_t::NONBASIC_LOWER) {
x[j] = lp.lower[j];
} else if (z[j] == 0 && lp.upper[j] < inf && vstatus[j] == variable_status_t::NONBASIC_UPPER) {
} else if (std::abs(z[j]) <= fixed_tolerance && lp.upper[j] < inf &&
vstatus[j] == variable_status_t::NONBASIC_UPPER) {
x[j] = lp.upper[j];
} else if (z[j] >= 0 && lp.lower[j] > -inf) {
} else if (z[j] > fixed_tolerance && lp.lower[j] > -inf) {
if (vstatus[j] != variable_status_t::NONBASIC_LOWER) {
settings.log.debug(
"Setting nonbasic lower variable (zj %e) %d to %e (current %e). vstatus %d\n",
Expand All @@ -1943,7 +1952,7 @@ void set_primal_variables_on_bounds(const lp_problem_t<i_t, f_t>& lp,
}
x[j] = lp.lower[j];
vstatus[j] = variable_status_t::NONBASIC_LOWER;
} else if (z[j] <= 0 && lp.upper[j] < inf) {
} else if (z[j] < fixed_tolerance && lp.upper[j] < inf) {
if (vstatus[j] != variable_status_t::NONBASIC_UPPER) {
settings.log.debug(
"Setting nonbasic upper variable (zj %e) %d to %e (current %e). vstatus %d\n",
Expand Down Expand Up @@ -1984,7 +1993,7 @@ void set_primal_variables_on_bounds(const lp_problem_t<i_t, f_t>& lp,
"Setting free variable %d to %e. vstatus %d\n", j, 0, static_cast<int>(vstatus[j]));
}
vstatus[j] = variable_status_t::NONBASIC_FREE;
settings.log.printf("Setting free variable %d as nonbasic at 0\n", j);
settings.log.debug("Setting free variable %d as nonbasic at 0\n", j);
} else {
assert(1 == 0);
}
Expand Down Expand Up @@ -2192,6 +2201,7 @@ dual::status_t dual_phase2(i_t phase,
iter,
delta_y_steepest_edge);
}
#define PRINT_VSTATUS_CHANGES

template <typename i_t, typename f_t>
dual::status_t dual_phase2_with_advanced_basis(i_t phase,
Expand Down Expand Up @@ -2255,14 +2265,19 @@ dual::status_t dual_phase2_with_advanced_basis(i_t phase,
// Solve B'*y = cB
ft.b_transpose_solve(c_basic, y);
if (toc(start_time) > settings.time_limit) { return dual::status_t::TIME_LIMIT; }
constexpr bool print_norms = false;
constexpr bool print_norms = true;
if constexpr (print_norms) {
settings.log.printf(
"|| y || %e || cB || %e\n", vector_norm_inf<i_t, f_t>(y), vector_norm_inf<i_t, f_t>(c_basic));
}

phase2::compute_reduced_costs(objective, lp.A, y, basic_list, nonbasic_list, z);
if constexpr (print_norms) { settings.log.printf("|| z || %e\n", vector_norm_inf<i_t, f_t>(z)); }
if constexpr (print_norms) {
settings.log.printf("|| z || %e\n", vector_norm_inf<i_t, f_t>(z));
// for (i_t j = 0; j < n; ++j) {
// settings.log.printf("z[%d] = %e\n", j, z[j]);
// }
}

#ifdef COMPUTE_DUAL_RESIDUAL
std::vector<f_t> dual_res1;
Expand Down
Loading