Skip to content

Extend the barrier solver for Second-Order Cone Programming (SOCP)#1290

Merged
rapids-bot[bot] merged 62 commits into
NVIDIA:release/26.06from
chris-maes:socp-barrier-solver-2
May 30, 2026
Merged

Extend the barrier solver for Second-Order Cone Programming (SOCP)#1290
rapids-bot[bot] merged 62 commits into
NVIDIA:release/26.06from
chris-maes:socp-barrier-solver-2

Conversation

@chris-maes
Copy link
Copy Markdown
Contributor

@chris-maes chris-maes commented May 22, 2026

Extend the barrier solver to handle second-order cone programming (SOCP) specified via quadratic constraints (x^T Q x + a^T x <= rhs).

Algorithmic changes:

  • Implement Nesterov-Todd scaling for second-order cone (SOC) blocks with Mehrotra predictor-corrector steps including cone corrector terms.
  • Extend the augmented KKT system with dense SOC Hessian blocks and cone-aware step-length computation.
  • Add Ruiz equilibration for improved conditioning of SOCP/QP problems.
  • Convert quadratic constraints to canonical SOC form, supporting standard, rotated, and affine cone variants with automatic variable aliasing and bound splitting.

Public API:

  • Python Modeling API: Extend Variable, Quadratic Expression, and Constraint to support quadratic constraints.
  • Python Data Model API: Add the functions add_quadratic_constraint, get_quadratic_constraints and clear_quadratic_constraints.

Presolve:

  • Preserve cone variable structure during presolve (skip cone block for bound tightening, variable/constraint removal).
  • Use CSR row access for dual correction of bounded free variables in post-solve (replaces column scan).

Code quality and refactoring:

  • Introduce coo_entries_t to replace std::tuple-based COO storage in parsers.
  • Simplify triples_to_csr_flat and build_symmetric_q_coo.

Tests:

  • Add unit tests for SOC kernels (NT scaling, step length, Hessian).
  • Add integration tests for SOCP problems via the barrier solver.
  • Add C API tests for quadratic constraints.

This PR extends the 2x2 augmented system formulation of the barrier
solver to solve SOCP problems. It supports both explicit cone
variables as well as the cone rows in the Ax=b constraint. It uses
Nesterov-Todd scaling to make the conic diagonal blocks symmetric.

Co-authored-by: Yan Zaretskiy <yan@fastmail.com>
Co-authored-by: Yan Zaretskiy <yzaretskiy@nvidia.com>
Co-authored-by: Yuwen Chen <yuwchen@nvidia.com>
@chris-maes chris-maes requested review from a team as code owners May 22, 2026 21:54
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented May 22, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@chris-maes chris-maes added non-breaking Introduces a non-breaking change improvement Improves an existing functionality P0 feature request New feature or request and removed improvement Improves an existing functionality labels May 22, 2026
@chris-maes chris-maes added this to the 26.06 milestone May 22, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 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
📝 Walkthrough

Walkthrough

This PR implements comprehensive second-order cone programming (SOCP) and quadratic-constraint support across the CUOPT stack. Changes migrate quadratic matrices from CSR to COO format, extend presolve to handle cone variables separately, add two-dimensional scaling with Ruiz equilibration, integrate barrier solver validation, and provide complete Python bindings for quadratic constraint modeling and I/O.

Changes

SOCP and Quadratic Constraint Implementation

Layer / File(s) Summary
Quadratic constraint COO format and data model
cpp/include/cuopt/linear_programming/io/data_model_view.hpp, cpp/include/cuopt/linear_programming/io/mps_data_model.hpp, cpp/include/cuopt/linear_programming/optimization_problem_interface.hpp, cpp/src/dual_simplex/user_problem.hpp, cpp/src/dual_simplex/presolve.hpp
Core data structures switch from CSR (quadratic_indices/quadratic_offsets) to COO (quadratic_row_indices/quadratic_col_indices). user_problem_t and lp_problem_t now track cone layout via cone_var_start and second_order_cone_dims; presolve_info_t tracks direct_free_variables instead of free_variable_indices.
Cone-aware presolve and variable elimination
cpp/src/dual_simplex/presolve.cpp, cpp/src/dual_simplex/presolve.hpp
Presolve distinguishes linear columns from cone/SOCP variables; restricts empty-column removal, bound transformations, and free-variable expansion to the linear block; adds SOC-aware slack insertion for <= constraints; propagates cone metadata through all transformations; introduces direct free-variable handling for barrier presolve.
Two-dimensional scaling with Ruiz equilibration and dual unscaling
cpp/src/dual_simplex/scaling.cpp, cpp/src/dual_simplex/scaling.hpp, cpp/src/dual_simplex/vector_math.cuh
Replaces column-only scaling with two-dimensional approach including Ruiz iterative equilibration for SOCP/QP; scales cone variable blocks uniformly; unscales dual variables (y) alongside primal; updates norm operations to accept RAFT spans instead of concrete containers.
Barrier solver cone validation and search direction perturbation
cpp/src/barrier/barrier.hpp, cpp/src/dual_simplex/solve.cpp
Adds validate_barrier_cone_layout() to enforce cone dimensions ≥ 2 and [linear | cone] column layout; extends gpu_compute_search_direction with dual_perturb and primal_perturb parameters; integrates unscaled dual variables throughout barrier solve; recomputes objective from user-scaled values.
Parser and writer updates for COO format
cpp/src/io/lp_parser.cpp, cpp/src/io/mps_parser.cpp, cpp/src/io/mps_data_model.cpp, cpp/src/io/mps_writer.cpp
Parsers construct quadratic constraints using COO representation with stable sorting by (row, col); MPS writer iterates via explicit row/col indices; append_quadratic_constraint validates constraint type L/G; variable types default to continuous when unspecified.
PDLP quadratic feature detection and barrier routing
cpp/src/pdlp/translate.hpp, cpp/src/pdlp/solve.cu
Adds early detection of quadratic objectives/constraints; forces barrier method and disables presolve for quadratic problems; validates quadratic objective sense; enables barrier presolve by default; conditionally applies SOC conversion during translation to dual_simplex user_problem.
Python Cython bindings for quadratic constraints
python/cuopt/cuopt/linear_programming/data_model/data_model.pxd, python/cuopt/cuopt/linear_programming/io/parser.pxd, python/cuopt/cuopt/linear_programming/io/parser_wrapper.pyx
Adds Cython extern bindings for mps_data_model_t and quadratic_constraint_t structures; exposes set_quadratic_constraints on data_model_view_t; provides get_quadratic_constraints accessor for marshaling C++ parsed constraints to Python; extracts quadratic components during data model unmarshaling.
Python DataModel quadratic constraint management
python/cuopt/cuopt/linear_programming/data_model/data_model.py, python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pxd, python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx
Extends DataModel with add_quadratic_constraint/clear_quadratic_constraints/get_quadratic_constraints methods; validates sense (L/G only, rejects E); transfers C++ quadratic constraints to Python during unmarshal; defaults variable types to continuous; coerces non-NumPy inputs via np.asarray.
Python problem modeling for quadratic expressions and constraints
python/cuopt/cuopt/linear_programming/problem.py
Variable/LinearExpression/QuadraticExpression support quadratic constraint creation via <=/>= comparisons; implements negation and comparison operators; adds _quadratic_expression_to_qcmatrix helper for QCMATRIX export with duplicate merging; Problem separates linear/quadratic constraint handling, prevents quadratic constraint modification, returns solver solution object.
Comprehensive test coverage for SOCP and quadratic constraints
cpp/tests/dual_simplex/CMakeLists.txt, cpp/tests/dual_simplex/unit_tests/solve_barrier.cu, cpp/tests/linear_programming/parser_test.cpp, python/cuopt/cuopt/tests/linear_programming/test_python_API.py
Adds numerous barrier solver tests for SOC metadata propagation, cone layout validation, free-variable uncrushing, end-to-end SOCP solves, QP+SOC problems; updates parser tests for COO representation; adds Python API test for duplicate term merging.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes


Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.84% 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
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.
Title check ✅ Passed The PR title 'Extend the barrier solver for Second-Order Cone Programming (SOCP)' directly and clearly summarizes the main objective of this comprehensive changeset, which adds SOCP support to the barrier solver.
Description check ✅ Passed The pull request description clearly explains the extension of the barrier solver to handle second-order cone programming via quadratic constraints, including algorithmic changes, public API updates, presolve modifications, and code quality improvements.

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

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

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: 7

Caution

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

⚠️ Outside diff range comments (2)
cpp/src/barrier/dense_vector.hpp (1)

11-14: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add <limits> to keep cpp/src/barrier/dense_vector.hpp self-contained.
dense_vector.hpp uses std::numeric_limits (line 195) but does not include <limits>, so it relies on transitive includes (IWYU fragile).

🔧 Proposed fix
 `#include` <cmath>
 `#include` <cstdio>
+#include <limits>
 `#include` <vector>
🤖 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/barrier/dense_vector.hpp` around lines 11 - 14, The header
dense_vector.hpp is using std::numeric_limits (referenced around the usage at
line ~195) but doesn't include <limits>, causing a fragile IWYU dependency; fix
by adding an `#include` <limits> at the top of cpp/src/barrier/dense_vector.hpp so
the header is self-contained and std::numeric_limits is available to
functions/classes in that file (e.g., where numeric_limits is used).
cpp/src/dual_simplex/simplex_solver_settings.hpp (1)

49-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep barrier_presolve opt-in at the barrier call site.

Changing the shared default to true affects more than barrier solves. run_dual_simplex() in cpp/src/pdlp/solve.cu still default-constructs simplex_solver_settings_t and does not override this flag before calling into presolve, so dual-simplex now inherits barrier-specific presolve behavior too. If the intent is only to preserve the barrier path after removing the local barrier_settings copy, set barrier_presolve = true there instead of flipping the global default.

Also applies to: 166-166

🤖 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/simplex_solver_settings.hpp` around lines 49 - 83, The
constructor for simplex_solver_settings_t currently sets barrier_presolve =
true, which unintentionally enables barrier-specific presolve for dual-simplex
callers that default-construct simplex_solver_settings_t (e.g., run_dual_simplex
in pdlp/solve.cu); revert the global default by setting barrier_presolve back to
false in simplex_solver_settings_t, and instead explicitly enable
barrier_presolve = true only where the barrier path is invoked (restore the
previous behavior by setting barrier_presolve on the barrier_settings copy or
before calling the barrier routine in the barrier-specific call site such as the
code that previously owned barrier_settings).
🧹 Nitpick comments (2)
cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp (1)

283-285: ⚡ Quick win

Document the new barrier tolerance semantics inline.

These three new numerical controls should include brief field comments describing which residual each one governs and intended value range/behavior.

As per coding guidelines "Flag missing documentation for numerical tolerances and algorithm parameters."

🤖 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/include/cuopt/linear_programming/pdlp/solver_settings.hpp` around lines
283 - 285, Add inline field comments for barrier_relative_feasibility_tolerance,
barrier_relative_optimality_tolerance, and
barrier_relative_complementarity_tolerance that state which residual each
parameter controls (feasibility residual, optimality/residual gradient, and
complementarity gap respectively), include suggested value ranges (e.g. typical
defaults ~1e-8 and acceptable range like 1e-12..1e-4) and note behavior (smaller
values tighten termination, larger values relax it); place these brief comments
immediately above or to the right of the three fields in solver_settings.hpp so
the semantics are clear to readers and follow the project's documentation
guideline for numerical tolerances.
cpp/src/dual_simplex/scaling.cpp (1)

57-173: ⚡ Quick win

Document the new Ruiz heuristics as solver parameters.

100.0, 10, 0.1, and the 1e20 bound sentinel now directly affect scaling behavior, but they are still anonymous literals inside the solver core. Please at least lift them into named constants with rationale, or preferably settings, so regressions are traceable and tuning is deliberate. As per coding guidelines, "Flag missing documentation for numerical tolerances and algorithm parameters".

🤖 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/scaling.cpp` around lines 57 - 173, The Ruiz
equilibration uses hard-coded literals (100.0 row norm ratio, 10 max iterations,
0.1 deviation tolerance, and 1e20 bound sentinel) — lift them into named
parameters (preferably on the existing settings object) and replace the
anonymous literals in functions/blocks around Ruiz equilibration (check
row_norm_ratio comparison, max_ruiz_iterations, the max_deviation break
threshold, and the bound checks using 1e20), then update the log message ("Ruiz
equilibration: coefficient range ...") to include the effective settings values;
name suggestions: settings.ruiz_row_ratio_threshold,
settings.ruiz_max_iterations, settings.ruiz_deviation_tolerance,
settings.bound_sentinel (or constants with those names if not exposed) so future
tuning and documentation can reference them.
🤖 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/presolve.cpp`:
- Around line 939-940: The code caches linear_cols = linear_var_count(problem)
before presolve but then calls remove_empty_cols which can shrink the linear
block, so later uses of linear_cols (e.g., in the logic around cone_var_start,
second_order_cone_dims and accesses to
problem.lower/problem.upper/problem.objective) can be out-of-date and index past
vectors; recompute linear_cols immediately after any column-removal/presolve
passes (specifically after remove_empty_cols) and use the refreshed linear_cols
when deciding free-variable handling and when computing cone_var_start/iterating
second_order_cone_dims to ensure all indexing into
problem.lower/problem.upper/problem.objective is within bounds.

In `@cpp/src/dual_simplex/scaling.cpp`:
- Around line 36-63: The Ruiz scaling branch for SOCP/QP runs before honoring
settings.scale_columns, so callers cannot disable column scaling for
quadratic/conic models; update the logic in scaling.cpp to check
settings.scale_columns before entering the Ruiz equilibration branch (or add
settings.scale_columns to the condition that gates Ruiz), ensuring the existing
guard (settings.scale_columns) is evaluated prior to the block that constructs
Arow_check and computes row_norm_ratio (refer to the Ruiz equilibration code and
the settings.scale_columns flag), and apply the same fix for the similar branch
around the later block referenced in the comment (the section around the lines
noted as 177-180).

In `@cpp/src/dual_simplex/solve.cpp`:
- Around line 64-97: The function validate_barrier_cone_layout uses
problem.cone_var_start as an index into problem.lower/problem.upper without
validating it; add an explicit check near the top of
validate_barrier_cone_layout (before computing cone_end or iterating bounds)
that cone_var_start is within [0, problem.num_cols] (and that cone_var_start <=
problem.num_cols) and if not call settings.log.printf with a clear error (e.g.
"Error: invalid cone_var_start") and return false so out-of-range indexing on
problem.lower/problem.upper is prevented.

In `@cpp/src/io/mps_data_model.cpp`:
- Around line 163-187: Check and reject out-of-range or negative index values
immediately after the existing length/null checks: iterate the span
linear_indices (for linear_values.size()) and ensure each index is within [0,
num_variables) (replace num_variables with the actual variable/column count
symbol used in this file), calling mps_parser_expects on the first offending
value; similarly iterate quadratic_row_indices and quadratic_col_indices for
q_nnz entries and validate each is in [0, num_variables) (or appropriate row/col
bounds if different), rejecting any negative or too-large index with a clear
ValidationError message that includes the offending index and its position.

In `@cpp/src/io/mps_writer.cpp`:
- Around line 505-517: The loop uses nnz from qc.quadratic_values.size() but
indexes qc.quadratic_row_indices and qc.quadratic_col_indices without
validation; add input validation before the loop (e.g., in the function that
writes QCMATRIX) to ensure qc.quadratic_row_indices.size() and
qc.quadratic_col_indices.size() are at least nnz and handle the mismatch by
returning an error/throwing or logging and aborting export. Specifically, check
sizes of quadratic_values, quadratic_row_indices, and quadratic_col_indices
(symbols: nnz, qc.quadratic_values, qc.quadratic_row_indices,
qc.quadratic_col_indices) and fail fast if any are shorter than nnz so the
for-loop that reads indices cannot read out-of-bounds.

In `@python/cuopt/cuopt/linear_programming/data_model/data_model.py`:
- Around line 293-375: The new public DataModel methods
get_quadratic_constraints, clear_quadratic_constraints, and
add_quadratic_constraint lack type hints and complete docstring sections; update
their signatures to include precise type annotations (e.g., return types:
get_quadratic_constraints -> List[Dict[str, Any]] or appropriate TypedDict,
clear_quadratic_constraints -> None, add_quadratic_constraint parameter types
like constraint_row_index: int, constraint_row_name: str = "", linear_values:
Optional[Sequence[float]] = None, linear_indices: Optional[Sequence[int]] =
None, rhs_value: float = 0.0, quadratic_values: Optional[Sequence[float]] =
None, quadratic_row_indices: Optional[Sequence[int]] = None,
quadratic_col_indices: Optional[Sequence[int]] = None, sense: Union[str, Enum] =
"L" and return type -> None) and expand each docstring to include Parameters,
Returns, and Raises sections consistent with project style (document possible
ValueError raises in add_quadratic_constraint and any exceptions raised by
`@catch_cuopt_exception`). Ensure imports for typing (List, Dict, Any, Optional,
Sequence, Union) are added if missing and preserve existing logic in the
function bodies.

In `@python/cuopt/cuopt/linear_programming/problem.py`:
- Around line 1983-1988: The public method getQuadraticConstraints lacks a
return type hint and a documented Returns section; add a type annotation such as
-> List[Constraint] (or -> List["Constraint"] if Constraint is not yet defined)
and import List from typing at the top of the module, then expand the docstring
to include a Returns section describing that it returns a list of constraint
objects representing quadratic (QCMATRIX) constraints (e.g., "Returns:
List[Constraint]: All quadratic constraints in the problem."). Keep the
implementation unchanged: it should still return [c for c in self.constrs if
c.is_quadratic].

---

Outside diff comments:
In `@cpp/src/barrier/dense_vector.hpp`:
- Around line 11-14: The header dense_vector.hpp is using std::numeric_limits
(referenced around the usage at line ~195) but doesn't include <limits>, causing
a fragile IWYU dependency; fix by adding an `#include` <limits> at the top of
cpp/src/barrier/dense_vector.hpp so the header is self-contained and
std::numeric_limits is available to functions/classes in that file (e.g., where
numeric_limits is used).

In `@cpp/src/dual_simplex/simplex_solver_settings.hpp`:
- Around line 49-83: The constructor for simplex_solver_settings_t currently
sets barrier_presolve = true, which unintentionally enables barrier-specific
presolve for dual-simplex callers that default-construct
simplex_solver_settings_t (e.g., run_dual_simplex in pdlp/solve.cu); revert the
global default by setting barrier_presolve back to false in
simplex_solver_settings_t, and instead explicitly enable barrier_presolve = true
only where the barrier path is invoked (restore the previous behavior by setting
barrier_presolve on the barrier_settings copy or before calling the barrier
routine in the barrier-specific call site such as the code that previously owned
barrier_settings).

---

Nitpick comments:
In `@cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp`:
- Around line 283-285: Add inline field comments for
barrier_relative_feasibility_tolerance, barrier_relative_optimality_tolerance,
and barrier_relative_complementarity_tolerance that state which residual each
parameter controls (feasibility residual, optimality/residual gradient, and
complementarity gap respectively), include suggested value ranges (e.g. typical
defaults ~1e-8 and acceptable range like 1e-12..1e-4) and note behavior (smaller
values tighten termination, larger values relax it); place these brief comments
immediately above or to the right of the three fields in solver_settings.hpp so
the semantics are clear to readers and follow the project's documentation
guideline for numerical tolerances.

In `@cpp/src/dual_simplex/scaling.cpp`:
- Around line 57-173: The Ruiz equilibration uses hard-coded literals (100.0 row
norm ratio, 10 max iterations, 0.1 deviation tolerance, and 1e20 bound sentinel)
— lift them into named parameters (preferably on the existing settings object)
and replace the anonymous literals in functions/blocks around Ruiz equilibration
(check row_norm_ratio comparison, max_ruiz_iterations, the max_deviation break
threshold, and the bound checks using 1e20), then update the log message ("Ruiz
equilibration: coefficient range ...") to include the effective settings values;
name suggestions: settings.ruiz_row_ratio_threshold,
settings.ruiz_max_iterations, settings.ruiz_deviation_tolerance,
settings.bound_sentinel (or constants with those names if not exposed) so future
tuning and documentation can reference them.
🪄 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: adbdcc6b-fcfc-43b0-b6b8-9cb53032b5d9

📥 Commits

Reviewing files that changed from the base of the PR and between f3dd013 and c721556.

📒 Files selected for processing (37)
  • cpp/include/cuopt/linear_programming/constants.h
  • cpp/include/cuopt/linear_programming/io/data_model_view.hpp
  • cpp/include/cuopt/linear_programming/io/mps_data_model.hpp
  • cpp/include/cuopt/linear_programming/optimization_problem_interface.hpp
  • cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp
  • cpp/src/barrier/barrier.cu
  • cpp/src/barrier/barrier.hpp
  • cpp/src/barrier/dense_vector.hpp
  • cpp/src/dual_simplex/presolve.cpp
  • cpp/src/dual_simplex/presolve.hpp
  • cpp/src/dual_simplex/scaling.cpp
  • cpp/src/dual_simplex/scaling.hpp
  • cpp/src/dual_simplex/simplex_solver_settings.hpp
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/dual_simplex/user_problem.hpp
  • cpp/src/dual_simplex/vector_math.cuh
  • cpp/src/io/lp_parser.cpp
  • cpp/src/io/mps_data_model.cpp
  • cpp/src/io/mps_parser.cpp
  • cpp/src/io/mps_writer.cpp
  • cpp/src/math_optimization/solver_settings.cu
  • cpp/src/pdlp/solve.cu
  • cpp/src/pdlp/termination_strategy/infeasibility_information.cu
  • cpp/src/pdlp/translate.hpp
  • cpp/tests/dual_simplex/CMakeLists.txt
  • cpp/tests/dual_simplex/unit_tests/solve_barrier.cu
  • cpp/tests/linear_programming/parser_test.cpp
  • cpp/tests/qp/unit_tests/no_constraints.cu
  • cpp/tests/qp/unit_tests/two_variable_test.cu
  • python/cuopt/cuopt/linear_programming/data_model/data_model.pxd
  • python/cuopt/cuopt/linear_programming/data_model/data_model.py
  • python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pxd
  • python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyx
  • python/cuopt/cuopt/linear_programming/io/parser.pxd
  • python/cuopt/cuopt/linear_programming/io/parser_wrapper.pyx
  • python/cuopt/cuopt/linear_programming/problem.py
  • python/cuopt/cuopt/tests/linear_programming/test_python_API.py
💤 Files with no reviewable changes (1)
  • cpp/src/pdlp/termination_strategy/infeasibility_information.cu

Comment thread cpp/src/dual_simplex/presolve.cpp Outdated
Comment thread cpp/src/dual_simplex/scaling.cpp
Comment thread cpp/src/dual_simplex/solve.cpp Outdated
Comment on lines +64 to +97
template <typename i_t, typename f_t>
bool validate_barrier_cone_layout(const lp_problem_t<i_t, f_t>& problem,
const simplex_solver_settings_t<i_t, f_t>& settings)
{
if (problem.second_order_cone_dims.empty()) { return true; }

i_t cone_end = problem.cone_var_start;
for (auto q_k : problem.second_order_cone_dims) {
if (q_k <= 1) {
settings.log.printf(
"Error: second-order cone dimensions must be at least 2; use linear variables instead of "
"Q^1\n");
return false;
}
cone_end += q_k;
}

if (cone_end != problem.num_cols) {
settings.log.printf("Error: conic variables must form a trailing block [linear | cone]\n");
return false;
}

for (i_t j = problem.cone_var_start; j < cone_end; ++j) {
if (problem.lower[j] != 0.0 && problem.lower[j] > -inf) {
settings.log.printf("Error: explicit lower bound on conic variable %d is not supported\n", j);
return false;
}
if (problem.upper[j] < inf) {
settings.log.printf("Error: explicit upper bound on conic variable %d is not supported\n", j);
return false;
}
}

return true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate cone_var_start before using it as an index.

This helper checks cone sizes and the trailing-block invariant, but it never verifies that cone_var_start itself is in [0, num_cols] before using it to drive the bounds loop. A malformed converted problem can still walk lower/upper out of range here instead of failing cleanly.

Suggested fix
 bool validate_barrier_cone_layout(const lp_problem_t<i_t, f_t>& problem,
                                   const simplex_solver_settings_t<i_t, f_t>& settings)
 {
   if (problem.second_order_cone_dims.empty()) { return true; }
+
+  if (problem.cone_var_start < 0 || problem.cone_var_start > problem.num_cols) {
+    settings.log.printf("Error: cone_var_start must be in [0, num_cols]\n");
+    return false;
+  }
 
   i_t cone_end = problem.cone_var_start;
As per coding guidelines, "Flag missing input validation at library and server boundaries".
🤖 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/solve.cpp` around lines 64 - 97, The function
validate_barrier_cone_layout uses problem.cone_var_start as an index into
problem.lower/problem.upper without validating it; add an explicit check near
the top of validate_barrier_cone_layout (before computing cone_end or iterating
bounds) that cone_var_start is within [0, problem.num_cols] (and that
cone_var_start <= problem.num_cols) and if not call settings.log.printf with a
clear error (e.g. "Error: invalid cone_var_start") and return false so
out-of-range indexing on problem.lower/problem.upper is prevented.

Comment thread cpp/src/io/mps_data_model.cpp
Comment thread cpp/src/io/mps_writer.cpp Outdated
Comment thread python/cuopt/cuopt/linear_programming/data_model/data_model.py
Comment thread python/cuopt/cuopt/linear_programming/problem.py
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 (2)
cpp/src/pdlp/solve.cu (1)

1796-1827: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move the quadratic-objective sense check before the early solve_qp() return.

solve_lp() returns to solve_qp() before this new minimization-only guard runs, so a maximization QP still goes down the barrier path instead of being rejected. Put the sense check before the early return, or mirror it inside solve_qp().

🛠️ Minimal fix
-  if (op_problem.has_quadratic_objective()) {
-    return solve_qp(op_problem, settings_const, problem_checking);
-  }
+  if (op_problem.has_quadratic_objective() && op_problem.get_sense()) {
+    CUOPT_LOG_ERROR("Quadratic problems must be minimized");
+    return optimization_problem_solution_t<i_t, f_t>(
+      pdlp_termination_status_t::NumericalError, op_problem.get_handle_ptr()->get_stream());
+  }
+  if (op_problem.has_quadratic_objective()) {
+    return solve_qp(op_problem, settings_const, problem_checking);
+  }

As per coding guidelines, flag missing input validation at library and server boundaries.

🤖 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/pdlp/solve.cu` around lines 1796 - 1827, The minimization-only check
for quadratic objectives is executed after the early return to solve_qp(), so
maximization QPs can bypass the guard; move the sense check for quadratic
objectives (the op_problem.has_quadratic_objective() && op_problem.get_sense()
branch that logs "Quadratic problems must be minimized") so it runs before the
conditional that returns solve_qp(op_problem, settings_const, problem_checking),
or alternatively add the same sense validation inside solve_qp(); update the
relevant logic in solve_lp()/solve_qp() to ensure any quadratic-objective
problem is rejected with CUOPT_LOG_ERROR and returns
pdlp_termination_status_t::NumericalError for non-minimization cases.
cpp/src/dual_simplex/presolve.cpp (1)

289-393: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Remap Q with the post-slack column order.

In the cone-aware <= conversion, slacks are inserted before the cone block, but problem.Q is still populated later from user_problem.Q_*. That means the remap block here never runs, and the later load keeps quadratic row/column indices in the pre-insertion layout. Any QP/SOCP instance with L rows will attach quadratic terms to the wrong columns.

Either build problem.Q before convert_less_than_to_equal() so this remap executes, or rebuild Q from old_to_new after the slack insertion finalizes the new column order.

As per coding guidelines, this PR should prioritize correctness in problem transformations and avoid index-mapping mistakes across presolve/scaling/uncrush.

Also applies to: 915-933

🤖 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/presolve.cpp` around lines 289 - 393, The remapping of
quadratic matrix problem.Q is happening before the final column order
(post-slack insertion) is guaranteed, so Q indices remain in the pre-insertion
layout; fix by rebuilding/remapping problem.Q after slacks are inserted using
the old_to_new mapping (use old_Q and old_to_new to populate problem.Q.j and
problem.Q.x) or by ensuring problem.Q is constructed before calling
convert_less_than_to_equal(); specifically locate the slack-insertion block that
produces expanded_A, new_slacks, old_to_new and then, after finalizing
num_cols/new_cone_start and expanded_A assignment, reconstruct problem.Q from
old_Q using old_to_new (update problem.Q.row_start, problem.Q.j, problem.Q.x,
problem.Q.m/n/nz_max) so quadratic row/column indices match the post-slack
column order.
🧹 Nitpick comments (1)
cpp/tests/dual_simplex/unit_tests/solve_barrier.cu (1)

997-1030: 💤 Low value

Consider removing debug printf statement.

Line 1024 contains a printf debug statement that may have been left from development. Consider removing it for cleaner test output.

-  printf("x %e y %e z %e\n", h_x[0], h_y[0], h_z[0]);
🤖 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/tests/dual_simplex/unit_tests/solve_barrier.cu` around lines 997 - 1030,
The test barrier::min_x_squared_free_variable_dual_correction contains a
leftover debug printf("x %e y %e z %e\n", h_x[0], h_y[0], h_z[0]) — remove this
printf call from the TEST(min_x_squared_free_variable_dual_correction) body so
the unit test output stays clean; keep the EXPECT_NEAR assertions and other code
unchanged.
🤖 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/dual_simplex/presolve.cpp`:
- Around line 289-393: The remapping of quadratic matrix problem.Q is happening
before the final column order (post-slack insertion) is guaranteed, so Q indices
remain in the pre-insertion layout; fix by rebuilding/remapping problem.Q after
slacks are inserted using the old_to_new mapping (use old_Q and old_to_new to
populate problem.Q.j and problem.Q.x) or by ensuring problem.Q is constructed
before calling convert_less_than_to_equal(); specifically locate the
slack-insertion block that produces expanded_A, new_slacks, old_to_new and then,
after finalizing num_cols/new_cone_start and expanded_A assignment, reconstruct
problem.Q from old_Q using old_to_new (update problem.Q.row_start, problem.Q.j,
problem.Q.x, problem.Q.m/n/nz_max) so quadratic row/column indices match the
post-slack column order.

In `@cpp/src/pdlp/solve.cu`:
- Around line 1796-1827: The minimization-only check for quadratic objectives is
executed after the early return to solve_qp(), so maximization QPs can bypass
the guard; move the sense check for quadratic objectives (the
op_problem.has_quadratic_objective() && op_problem.get_sense() branch that logs
"Quadratic problems must be minimized") so it runs before the conditional that
returns solve_qp(op_problem, settings_const, problem_checking), or alternatively
add the same sense validation inside solve_qp(); update the relevant logic in
solve_lp()/solve_qp() to ensure any quadratic-objective problem is rejected with
CUOPT_LOG_ERROR and returns pdlp_termination_status_t::NumericalError for
non-minimization cases.

---

Nitpick comments:
In `@cpp/tests/dual_simplex/unit_tests/solve_barrier.cu`:
- Around line 997-1030: The test
barrier::min_x_squared_free_variable_dual_correction contains a leftover debug
printf("x %e y %e z %e\n", h_x[0], h_y[0], h_z[0]) — remove this printf call
from the TEST(min_x_squared_free_variable_dual_correction) body so the unit test
output stays clean; keep the EXPECT_NEAR assertions and other code unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d5a99db0-4541-4bb7-afd6-bba771f2a3f8

📥 Commits

Reviewing files that changed from the base of the PR and between 911cb04 and 3803961.

📒 Files selected for processing (10)
  • cpp/src/barrier/barrier.cu
  • cpp/src/dual_simplex/presolve.cpp
  • cpp/src/dual_simplex/presolve.hpp
  • cpp/src/dual_simplex/scaling.cpp
  • cpp/src/dual_simplex/solve.cpp
  • cpp/src/pdlp/solve.cu
  • cpp/tests/dual_simplex/unit_tests/solve_barrier.cu
  • python/cuopt/cuopt/linear_programming/data_model/data_model.py
  • python/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pxd
  • python/cuopt/cuopt/linear_programming/problem.py
💤 Files with no reviewable changes (1)
  • python/cuopt/cuopt/linear_programming/data_model/data_model.py

Comment thread cpp/src/barrier/barrier.cu Outdated
@yuwenchen95
Copy link
Copy Markdown
Contributor

/ok to test 3fc42b7

@yuwenchen95
Copy link
Copy Markdown
Contributor

/ok to test 988ece4

Comment thread cpp/src/barrier/translate_soc.hpp Outdated
Comment thread cpp/src/barrier/translate_soc.hpp Outdated
Comment thread cpp/src/barrier/translate_soc.hpp Outdated
@chris-maes chris-maes changed the title Extend the barrier solver to SOCP problems Extend the barrier solver to Second-Order Cone Programming (SOCP) May 29, 2026
@chris-maes chris-maes changed the title Extend the barrier solver to Second-Order Cone Programming (SOCP) Extend the barrier solver for Second-Order Cone Programming (SOCP) May 29, 2026
@yuwenchen95
Copy link
Copy Markdown
Contributor

/ok to test b264a17

@yuwenchen95 yuwenchen95 force-pushed the socp-barrier-solver-2 branch from b264a17 to 1362c5b Compare May 29, 2026 19:33
@yuwenchen95
Copy link
Copy Markdown
Contributor

/ok to test 1362c5b

Copy link
Copy Markdown
Contributor

@rg20 rg20 left a comment

Choose a reason for hiding this comment

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

Most of my comments relate to cosmetic changes.

Comment thread cpp/include/cuopt/linear_programming/cpu_optimization_problem.hpp
Comment thread cpp/src/io/mps_data_model.cpp Outdated

std::vector<size_t> perm(q_nnz);
std::iota(perm.begin(), perm.end(), size_t{0});
std::stable_sort(perm.begin(), perm.end(), [&](size_t a, size_t b) {
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.

Why do you need stable sort? Are there duplicates? If so they have to be handled as well.

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.

@yuwenchen95 can you answer this when you have a chance?

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.

I think sort is enough since we would throw an error in translation step if duplicate items appear. Corrected it in the new commit.

Comment thread cpp/src/pdlp/solve.cu
Comment thread cpp/src/dual_simplex/user_problem.hpp Outdated
Comment thread cpp/src/dual_simplex/user_problem.hpp Outdated
Comment thread cpp/src/barrier/barrier.cu Outdated
Comment thread cpp/src/barrier/barrier.cu Outdated
Comment thread cpp/src/barrier/barrier.cu
Comment thread cpp/src/barrier/barrier.cu
Comment thread cpp/src/barrier/barrier.cu
@chris-maes
Copy link
Copy Markdown
Contributor Author

/ok to test 475aa20

@chris-maes
Copy link
Copy Markdown
Contributor Author

/ok to test aaefe54

@chris-maes
Copy link
Copy Markdown
Contributor Author

/ok to test f1711a7

…ions back from the expanded SOC layout, less duplicated direct-free-variable logic in the barrier kernel host code, removal of an redundant presolve override, and a minor parser sort simplification. No intended behavior change except callers of solve_linear_program_with_barrier no longer get presolve forced on unless they set it.
@yuwenchen95
Copy link
Copy Markdown
Contributor

/ok to test 2736181

@chris-maes
Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot rapids-bot Bot merged commit be1b208 into NVIDIA:release/26.06 May 30, 2026
185 of 192 checks passed
@chris-maes chris-maes deleted the socp-barrier-solver-2 branch May 30, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request New feature or request non-breaking Introduces a non-breaking change P0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants