Extend the barrier solver to SOCP problems#1290
Conversation
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>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis 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. ChangesSOCP and Quadratic Constraint Implementation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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 winAdd
<limits>to keepcpp/src/barrier/dense_vector.hppself-contained.
dense_vector.hppusesstd::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 winKeep
barrier_presolveopt-in at the barrier call site.Changing the shared default to
trueaffects more than barrier solves.run_dual_simplex()incpp/src/pdlp/solve.custill default-constructssimplex_solver_settings_tand 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 localbarrier_settingscopy, setbarrier_presolve = truethere 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 winDocument 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 winDocument the new Ruiz heuristics as solver parameters.
100.0,10,0.1, and the1e20bound 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
📒 Files selected for processing (37)
cpp/include/cuopt/linear_programming/constants.hcpp/include/cuopt/linear_programming/io/data_model_view.hppcpp/include/cuopt/linear_programming/io/mps_data_model.hppcpp/include/cuopt/linear_programming/optimization_problem_interface.hppcpp/include/cuopt/linear_programming/pdlp/solver_settings.hppcpp/src/barrier/barrier.cucpp/src/barrier/barrier.hppcpp/src/barrier/dense_vector.hppcpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/presolve.hppcpp/src/dual_simplex/scaling.cppcpp/src/dual_simplex/scaling.hppcpp/src/dual_simplex/simplex_solver_settings.hppcpp/src/dual_simplex/solve.cppcpp/src/dual_simplex/user_problem.hppcpp/src/dual_simplex/vector_math.cuhcpp/src/io/lp_parser.cppcpp/src/io/mps_data_model.cppcpp/src/io/mps_parser.cppcpp/src/io/mps_writer.cppcpp/src/math_optimization/solver_settings.cucpp/src/pdlp/solve.cucpp/src/pdlp/termination_strategy/infeasibility_information.cucpp/src/pdlp/translate.hppcpp/tests/dual_simplex/CMakeLists.txtcpp/tests/dual_simplex/unit_tests/solve_barrier.cucpp/tests/linear_programming/parser_test.cppcpp/tests/qp/unit_tests/no_constraints.cucpp/tests/qp/unit_tests/two_variable_test.cupython/cuopt/cuopt/linear_programming/data_model/data_model.pxdpython/cuopt/cuopt/linear_programming/data_model/data_model.pypython/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pxdpython/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pyxpython/cuopt/cuopt/linear_programming/io/parser.pxdpython/cuopt/cuopt/linear_programming/io/parser_wrapper.pyxpython/cuopt/cuopt/linear_programming/problem.pypython/cuopt/cuopt/tests/linear_programming/test_python_API.py
💤 Files with no reviewable changes (1)
- cpp/src/pdlp/termination_strategy/infeasibility_information.cu
| const i_t linear_cols = linear_var_count(problem); | ||
| const bool has_cones = !problem.second_order_cone_dims.empty(); |
There was a problem hiding this comment.
Recompute linear_cols after column-removal passes.
Line 939 caches linear_cols before presolve mutations. After Line 1314 (remove_empty_cols), the linear block can shrink, but Lines 1319-1334 still use the stale bound, which can index past problem.lower/upper/objective and mis-handle free-variable logic.
🛠️ Minimal fix
- const i_t linear_cols = linear_var_count(problem);
+ i_t linear_cols = linear_var_count(problem);
...
if (num_empty_cols > 0) {
settings.log.printf("Presolve attempt to remove %d empty cols\n", num_empty_cols);
remove_empty_cols(problem, num_empty_cols, presolve_info);
+ linear_cols = linear_var_count(problem);
}As per coding guidelines, this violates the review focus on "correctness in problem transformations ... mismatched index-space usage when adding cone_var_start/second_order_cone_dims."
Also applies to: 1313-1334
🤖 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 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.
| if (!unscaled.second_order_cone_dims.empty() || unscaled.Q.n > 0) { | ||
| col_scale.assign(n, 1.0); | ||
|
|
||
| // Decide whether Ruiz scaling is needed by checking row-norm imbalance. | ||
| // If max_row_norm / min_row_norm is small, the matrix is already well-conditioned | ||
| // and scaling can hurt (e.g. by amplifying tiny noise coefficients). | ||
| csr_matrix_t<i_t, f_t> Arow_check(0, 0, 0); | ||
| scaled.A.to_compressed_row(Arow_check); | ||
| f_t max_row_norm = 0; | ||
| f_t min_row_norm = std::numeric_limits<f_t>::max(); | ||
| for (i_t i = 0; i < m; ++i) { | ||
| f_t row_norm = 0; | ||
| for (i_t p = Arow_check.row_start[i]; p < Arow_check.row_start[i + 1]; ++p) { | ||
| f_t a = std::abs(Arow_check.x[p]); | ||
| if (a > row_norm) row_norm = a; | ||
| } | ||
| if (row_norm > 0) { | ||
| max_row_norm = std::max(max_row_norm, row_norm); | ||
| min_row_norm = std::min(min_row_norm, row_norm); | ||
| } | ||
| } | ||
| f_t row_norm_ratio = (min_row_norm > 0) ? max_row_norm / min_row_norm : 1.0; | ||
|
|
||
| if (row_norm_ratio < 100.0) { | ||
| settings.log.printf("Skipping Ruiz equilibration (row norm ratio %.1f < 100)\n", | ||
| row_norm_ratio); | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
Honor scale_columns for SOCP/QP too.
This new Ruiz branch runs before the existing !settings.scale_columns guard, so callers can no longer disable scaling for quadratic/conic models. If these models must always use Ruiz, that needs a separate setting; otherwise move the guard ahead of this branch.
Suggested fix
row_scale.assign(m, 1.0);
+
+ if (!settings.scale_columns) {
+ settings.log.printf("Skipping column scaling\n");
+ col_scale.assign(n, 1.0);
+ return 0;
+ }
// =========================================================================
// Ruiz equilibration for SOCP (and QP) problems
// =========================================================================
- if (!unscaled.second_order_cone_dims.empty() || unscaled.Q.n > 0) {
+ if (!unscaled.second_order_cone_dims.empty() || unscaled.Q.n > 0) {
col_scale.assign(n, 1.0);
@@
- if (!settings.scale_columns) {
- settings.log.printf("Skipping column scaling\n");
- col_scale.resize(n, 1.0);
- return 0;
- }
-
col_scale.resize(n);Also applies to: 177-180
🤖 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 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).
| 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; |
There was a problem hiding this comment.
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;🤖 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.
| mps_parser_expects(linear_values.size() == linear_indices.size(), | ||
| error_type_t::ValidationError, | ||
| "linear_values and linear_indices must have the same nnz count"); | ||
|
|
||
| mps_parser_expects( | ||
| !quadratic_offsets.empty(), error_type_t::ValidationError, "quadratic_offsets cannot be empty"); | ||
| const size_t q_nnz = quadratic_values.size(); | ||
| mps_parser_expects(q_nnz == quadratic_row_indices.size(), | ||
| error_type_t::ValidationError, | ||
| "quadratic_values and quadratic_row_indices must have the same length"); | ||
| mps_parser_expects(q_nnz == quadratic_col_indices.size(), | ||
| error_type_t::ValidationError, | ||
| "quadratic_values and quadratic_col_indices must have the same length"); | ||
|
|
||
| if (!linear_values.empty()) { | ||
| mps_parser_expects(linear_values.data() != nullptr && linear_indices.data() != nullptr, | ||
| error_type_t::ValidationError, | ||
| "linear_values and linear_indices cannot be null when non-empty"); | ||
| } | ||
|
|
||
| if (q_nnz > 0) { | ||
| mps_parser_expects(quadratic_values.data() != nullptr && | ||
| quadratic_row_indices.data() != nullptr && | ||
| quadratic_col_indices.data() != nullptr, | ||
| error_type_t::ValidationError, | ||
| "Q COO spans cannot be null when nnz > 0"); | ||
| } |
There was a problem hiding this comment.
Validate index values, not just COO array lengths.
This accepts malformed linear_indices / quadratic_*_indices as long as the spans are the same length, so negative or too-large ids can still flow into the model and fail later in writers or solver setup. Please reject invalid indices here while the error can still be reported as a user input problem.
Suggested guard
mps_parser_expects(linear_values.size() == linear_indices.size(),
error_type_t::ValidationError,
"linear_values and linear_indices must have the same nnz count");
+ mps_parser_expects(std::all_of(linear_indices.begin(), linear_indices.end(), [](i_t idx) {
+ return idx >= 0;
+ }),
+ error_type_t::ValidationError,
+ "linear_indices must be non-negative");
const size_t q_nnz = quadratic_values.size();
mps_parser_expects(q_nnz == quadratic_row_indices.size(),
error_type_t::ValidationError,
"quadratic_values and quadratic_row_indices must have the same length");
mps_parser_expects(q_nnz == quadratic_col_indices.size(),
error_type_t::ValidationError,
"quadratic_values and quadratic_col_indices must have the same length");
+ mps_parser_expects(
+ std::all_of(quadratic_row_indices.begin(), quadratic_row_indices.end(), [](i_t idx) {
+ return idx >= 0;
+ }) &&
+ std::all_of(quadratic_col_indices.begin(), quadratic_col_indices.end(), [](i_t idx) {
+ return idx >= 0;
+ }),
+ error_type_t::ValidationError,
+ "Q COO indices must be non-negative");
+ if (n_vars_ > 0) {
+ mps_parser_expects(
+ std::all_of(linear_indices.begin(), linear_indices.end(), [&](i_t idx) {
+ return idx < n_vars_;
+ }) &&
+ std::all_of(quadratic_row_indices.begin(), quadratic_row_indices.end(), [&](i_t idx) {
+ return idx < n_vars_;
+ }) &&
+ std::all_of(quadratic_col_indices.begin(), quadratic_col_indices.end(), [&](i_t idx) {
+ return idx < n_vars_;
+ }),
+ error_type_t::ValidationError,
+ "constraint indices must be smaller than the number of variables");
+ }🤖 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/io/mps_data_model.cpp` around lines 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.
| const i_t nnz = static_cast<i_t>(qc.quadratic_values.size()); | ||
| for (i_t p = 0; p < nnz; ++p) { | ||
| const i_t i = qc.quadratic_row_indices[static_cast<size_t>(p)]; | ||
| const i_t j = qc.quadratic_col_indices[static_cast<size_t>(p)]; | ||
| f_t v = qc.quadratic_values[static_cast<size_t>(p)]; | ||
| std::string row_var_name = static_cast<size_t>(i) < problem_.get_variable_names().size() | ||
| ? problem_.get_variable_names()[i] | ||
| ? problem_.get_variable_names()[static_cast<size_t>(i)] | ||
| : "C" + std::to_string(i); | ||
| for (i_t p = qc.quadratic_offsets[i]; p < qc.quadratic_offsets[i + 1]; ++p) { | ||
| i_t j = qc.quadratic_indices[p]; | ||
| f_t v = qc.quadratic_values[p]; | ||
| std::string col_var_name = static_cast<size_t>(j) < problem_.get_variable_names().size() | ||
| ? problem_.get_variable_names()[j] | ||
| : "C" + std::to_string(j); | ||
| if (v != f_t(0)) { | ||
| mps_file << " " << row_var_name << " " << col_var_name << " " << v << "\n"; | ||
| } | ||
| std::string col_var_name = static_cast<size_t>(j) < problem_.get_variable_names().size() | ||
| ? problem_.get_variable_names()[static_cast<size_t>(j)] | ||
| : "C" + std::to_string(j); | ||
| if (v != f_t(0)) { | ||
| mps_file << " " << row_var_name << " " << col_var_name << " " << v << "\n"; |
There was a problem hiding this comment.
Guard the QCMATRIX COO arrays before indexing them.
nnz is taken from quadratic_values.size(), but the loop indexes quadratic_row_indices[p] and quadratic_col_indices[p] unconditionally. If a caller built an inconsistent quadratic-constraint bundle, export will read past the shorter vector instead of failing cleanly.
Suggested guard
if (problem_.has_quadratic_constraints()) {
for (const auto& qc : problem_.get_quadratic_constraints()) {
mps_file << "QCMATRIX " << qc.constraint_row_name << "\n";
+ mps_parser_expects(qc.quadratic_row_indices.size() == qc.quadratic_values.size() &&
+ qc.quadratic_col_indices.size() == qc.quadratic_values.size(),
+ error_type_t::ValidationError,
+ "Quadratic constraint '%s' has inconsistent COO array lengths",
+ qc.constraint_row_name.c_str());
const i_t nnz = static_cast<i_t>(qc.quadratic_values.size());
for (i_t p = 0; p < nnz; ++p) {
const i_t i = qc.quadratic_row_indices[static_cast<size_t>(p)];
const i_t j = qc.quadratic_col_indices[static_cast<size_t>(p)];🤖 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/io/mps_writer.cpp` around lines 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.
| def get_quadratic_constraints(self): | ||
| """ | ||
| Return quadratic (QCMATRIX) constraints appended to this model. | ||
|
|
||
| Each entry is a dict with keys including ``constraint_row_index``, | ||
| ``constraint_row_name``, ``constraint_row_type``, COO arrays, and ``rhs_value``. | ||
| """ | ||
| return self.quadratic_constraints | ||
|
|
||
| @catch_cuopt_exception | ||
| def clear_quadratic_constraints(self): | ||
| """ | ||
| Remove all quadratic (QCMATRIX) constraints from the model. | ||
|
|
||
| Quadratic constraints are converted to second-order cone form internally | ||
| and solved with the barrier method. | ||
| """ | ||
| super().clear_quadratic_constraints() | ||
|
|
||
| @catch_cuopt_exception | ||
| def add_quadratic_constraint( | ||
| self, | ||
| constraint_row_index, | ||
| constraint_row_name="", | ||
| linear_values=None, | ||
| linear_indices=None, | ||
| rhs_value=0.0, | ||
| quadratic_values=None, | ||
| quadratic_row_indices=None, | ||
| quadratic_col_indices=None, | ||
| sense="L", | ||
| ): | ||
| """ | ||
| Add one quadratic constraint in MPS QCMATRIX form. | ||
|
|
||
| Each constraint has a linear part (optional) and a quadratic part in COO | ||
| format. Call multiple times to add several QCMATRIX rows; each call must | ||
| use a distinct ``constraint_row_index``. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| constraint_row_index : int | ||
| ROWS declaration index for this quadratic row (among all rows). | ||
| constraint_row_name : str, optional | ||
| Optional row name (for MPS export). | ||
| linear_values, linear_indices : array-like, optional | ||
| Sparse linear coefficients on the same variable index space. | ||
| rhs_value : float, optional | ||
| Right-hand side of the quadratic row. | ||
| quadratic_values, quadratic_row_indices, quadratic_col_indices : array-like | ||
| COO triplets for the quadratic matrix Q in | ||
| ``linear^T x + x^T Q x {sense} rhs_value``. | ||
| sense : str, optional | ||
| MPS row type: ``'L'`` (default, ``<=``) or ``'G'`` (``>=``), same values as | ||
| :meth:`set_row_types`. Rows are stored with the given sense; ``'G'`` is converted to | ||
| ``'L'`` when the barrier solver builds second-order cones from QCMATRIX data. | ||
| Equality (``'E'``) is not supported. | ||
|
|
||
| Notes | ||
| ----- | ||
| When any quadratic constraint is present, cuOpt selects the barrier | ||
| solver and converts QCMATRIX rows to second-order cones. | ||
| """ | ||
| if hasattr(sense, "value"): | ||
| sense = sense.value | ||
| if sense == "E": | ||
| raise ValueError("Equality constraints are not supported.") | ||
| if sense not in ("L", "G"): | ||
| raise ValueError( | ||
| f"Invalid sense {sense!r}; use 'L' or 'G' like set_row_types " | ||
| "(equality 'E' is not supported)." | ||
| ) | ||
| super().add_quadratic_constraint( | ||
| constraint_row_index, | ||
| constraint_row_name, | ||
| linear_values, | ||
| linear_indices, | ||
| rhs_value, | ||
| quadratic_values, | ||
| quadratic_row_indices, | ||
| quadratic_col_indices, | ||
| constraint_row_type=sense, | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Add type hints and complete docstring sections on new public DataModel APIs.
get_quadratic_constraints, clear_quadratic_constraints, and add_quadratic_constraint are new public APIs but currently omit type hints, and docstrings are missing consistent Returns/Raises sections.
Proposed update
- def get_quadratic_constraints(self):
+ def get_quadratic_constraints(self) -> list[dict]:
"""
Return quadratic (QCMATRIX) constraints appended to this model.
@@
+ Returns
+ -------
+ list[dict]
+ Stored quadratic-constraint records.
"""
return self.quadratic_constraints
@@
- def clear_quadratic_constraints(self):
+ def clear_quadratic_constraints(self) -> None:
"""
Remove all quadratic (QCMATRIX) constraints from the model.
@@
+ Returns
+ -------
+ None
"""
super().clear_quadratic_constraints()
@@
- def add_quadratic_constraint(
+ def add_quadratic_constraint(
self,
- constraint_row_index,
- constraint_row_name="",
- linear_values=None,
- linear_indices=None,
- rhs_value=0.0,
- quadratic_values=None,
- quadratic_row_indices=None,
- quadratic_col_indices=None,
- sense="L",
- ):
+ constraint_row_index: int,
+ constraint_row_name: str = "",
+ linear_values=None,
+ linear_indices=None,
+ rhs_value: float = 0.0,
+ quadratic_values=None,
+ quadratic_row_indices=None,
+ quadratic_col_indices=None,
+ sense: str = "L",
+ ) -> None:
@@
+ Returns
+ -------
+ None
+
+ Raises
+ ------
+ ValueError
+ If `sense` is unsupported or equality is requested.
"""As per coding guidelines, **/*.py: “Add type hints on all new public API functions and classes in Python” and “Include docstring content (params, returns, raises) on all new public Python APIs”.
🤖 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 `@python/cuopt/cuopt/linear_programming/data_model/data_model.py` around lines
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.
| def getQuadraticConstraints(self): | ||
| """ | ||
| Returns all quadratic (QCMATRIX) constraints in the problem. | ||
| """ | ||
| return [c for c in self.constrs if c.is_quadratic] | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Type and document Problem.getQuadraticConstraints as a public API.
This newly added public method should include a return type hint and explicit Returns docstring section.
Proposed update
- def getQuadraticConstraints(self):
+ def getQuadraticConstraints(self) -> list[Constraint]:
"""
Returns all quadratic (QCMATRIX) constraints in the problem.
+
+ Returns
+ -------
+ list[Constraint]
+ Quadratic constraints currently attached to the problem.
"""
return [c for c in self.constrs if c.is_quadratic]As per coding guidelines, **/*.py: “Add type hints on all new public API functions and classes in Python” and “Include docstring content (params, returns, raises) on all new public Python APIs”.
🤖 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 `@python/cuopt/cuopt/linear_programming/problem.py` around lines 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].
There was a problem hiding this comment.
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 winMove the quadratic-objective sense check before the early
solve_qp()return.
solve_lp()returns tosolve_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 insidesolve_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 liftRemap
Qwith the post-slack column order.In the cone-aware
<=conversion, slacks are inserted before the cone block, butproblem.Qis still populated later fromuser_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 withLrows will attach quadratic terms to the wrong columns.Either build
problem.Qbeforeconvert_less_than_to_equal()so this remap executes, or rebuildQfromold_to_newafter 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 valueConsider removing debug printf statement.
Line 1024 contains a
printfdebug 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
📒 Files selected for processing (10)
cpp/src/barrier/barrier.cucpp/src/dual_simplex/presolve.cppcpp/src/dual_simplex/presolve.hppcpp/src/dual_simplex/scaling.cppcpp/src/dual_simplex/solve.cppcpp/src/pdlp/solve.cucpp/tests/dual_simplex/unit_tests/solve_barrier.cupython/cuopt/cuopt/linear_programming/data_model/data_model.pypython/cuopt/cuopt/linear_programming/data_model/data_model_wrapper.pxdpython/cuopt/cuopt/linear_programming/problem.py
💤 Files with no reviewable changes (1)
- python/cuopt/cuopt/linear_programming/data_model/data_model.py
…aints_to_second_order_cones
| const f_t mu_denom = data.complementarity_degree(data.x.size(), data.n_upper_bounds); | ||
| mu_aff = complementarity_aff_sum / mu_denom; | ||
| sigma = std::max(0.0, std::min(1.0, std::pow(mu_aff / mu, 3.0))); | ||
| new_mu = sigma * mu; |
There was a problem hiding this comment.
@yuwenchen95 This changes new_mu from sigma * mu_aff to sigma * mu. Is that intentional. I'd have to double check but I'm pretty sure the former was correct.
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.
Description
Issue
Checklist