Fix the bug detecting nonconvex quadratic constraints in the general …#1438
Fix the bug detecting nonconvex quadratic constraints in the general …#1438yuwenchen95 wants to merge 1 commit into
Conversation
…path Signed-off-by: yuwenchen95 <yuwchen@nvidia.com>
cedb27f to
07f7b3f
Compare
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughFixes a silent constraint-drop bug: ChangesCross-only indefinite QCQP detection and rejection
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 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: 1
🤖 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/right_looking_lu.cpp`:
- Around line 1686-1687: Add a new method `has_remaining_numerically_nonzero` to
the `symmetric_trailing_matrix_t` class that checks whether the trailing matrix
has any numerically nonzero elements by iterating through diagonal elements and
off-diagonal elements, comparing them against a numeric tolerance parameter.
Then, refine the indefinite return condition around line 1726 (in the 1723-1727
range) so that it returns indefinite not only when `pivots == 0`, but also when
the trailing matrix is still numerically nonzero after partial elimination,
ensuring mixed cases with one early pivot followed by remaining cross-only
indefinite blocks are properly handled.
🪄 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: 8a87302e-97ad-4c39-a947-ab3d6cfcd39e
📒 Files selected for processing (5)
cpp/src/barrier/translate_soc.hppcpp/src/dual_simplex/right_looking_lu.cppcpp/src/dual_simplex/right_looking_lu.hppcpp/tests/dual_simplex/unit_tests/right_looking_ldlt.cppcpp/tests/socp/general_quadratic_test.cu
| const i_t n = A.n; | ||
| const i_t input_nnz = A.nnz(); |
There was a problem hiding this comment.
Handle no-pivot stalls after partial elimination as indefinite when the trailing matrix is still nonzero.
At Line 1726, the indefinite return is gated by pivots == 0, so mixed cases can still slip through (e.g., one valid early pivot, then a remaining cross-only indefinite block). That returns a positive rank and can still let non-convex QCQP pass conversion.
🛠️ Suggested fix
- const i_t n = A.n;
- const i_t input_nnz = A.nnz();
+ const i_t n = A.n;
...
- if (pivot_p == -1) {
- // No acceptable diagonal pivot. Zero matrix is rank 0; nonzero cross-only indefinite
- // matrices (e.g. [[0, a]; [a, 0]]) also stall here and must not be treated as PSD.
- if (pivots == 0 && input_nnz > 0) { return INDEFINITE_MATRIX_RETURN; }
- break;
- }
+ if (pivot_p == -1) {
+ // If any significant entry remains in the trailing matrix, matrix is not PSD.
+ if (trailing_matrix.has_remaining_numerically_nonzero(pivot_tol)) {
+ return INDEFINITE_MATRIX_RETURN;
+ }
+ break; // true rank-deficient zero remainder
+ }// Add to symmetric_trailing_matrix_t
bool has_remaining_numerically_nonzero(f_t nz_tol) const
{
for (i_t j = 0; j < n_; ++j) {
if (std::abs(diag_[j]) >= nz_tol) { return true; }
for (i_t p = col_start_[j]; p < col_end_[j]; ++p) {
if (std::abs(c_x_[p]) >= nz_tol) { return true; }
}
}
return false;
}As per coding guidelines, solver reviews should catch degenerate/numerical transformation-path correctness gaps.
Also applies to: 1723-1727
🤖 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/right_looking_lu.cpp` around lines 1686 - 1687, Add a
new method `has_remaining_numerically_nonzero` to the
`symmetric_trailing_matrix_t` class that checks whether the trailing matrix has
any numerically nonzero elements by iterating through diagonal elements and
off-diagonal elements, comparing them against a numeric tolerance parameter.
Then, refine the indefinite return condition around line 1726 (in the 1723-1727
range) so that it returns indefinite not only when `pivots == 0`, but also when
the trailing matrix is still numerically nonzero after partial elimination,
ensuring mixed cases with one early pivot followed by remaining cross-only
indefinite blocks are properly handled.
Source: Coding guidelines
|
|
||
| // Test: cross-only indefinite Q (issue #1434). H = [[0, 2]; [2, 0]] has zero diagonals so LDLT | ||
| // returns rank 0 without a negative pivot; must still be rejected as non-convex. | ||
| TEST(general_quadratic, rejects_cross_only_indefinite) |
There was a problem hiding this comment.
Instead of testing an internal converter, what about the end-to-end behavior on a non-convex instance? What status does the solver return?
There was a problem hiding this comment.
I'm following the style of previous rejects_non_convex test, just to check if we can capture the error correctly.
There was a problem hiding this comment.
Here is the full info after the change:
(cuopt_dev)~:/cuopt_workspace/cuopt$ ./cpp/build/cuopt_cli ./datasets/qcqp/issue_1434_nonconvex.lp
Setting CUDA_MODULE_LOADING to EAGER
Reading file issue_1434_nonconvex.lp
Read file issue_1434_nonconvex.lp in 0.01 seconds
cuOpt version: 26.8.0, git hash: 07f7b3f7, host arch: x86_64, device archs: 90a-real
CPU: AMD EPYC 7413 24-Core Processor, threads (physical/logical): 24/48, RAM: 211.39 GiB
CUDA 12.9, device: NVIDIA H200 (ID 0), VRAM: 139.80 GiB
CUDA device UUID: 9f8075aa-e02a-0953-1955-32f638fb2993
Problem has 1 quadratic constraints. Converting to second-order cones and solving with barrier.
Error in solve_qcqp: {"CUOPT_ERROR_TYPE": "ValidationError", "msg": "Quadratic constraint 'q0' is non-convex (Q matrix is indefinite)"}
Description
Cross-only indefinite quadratic constraints (e.g.
2·x₀·x₁ ≤ 0.5withH = [[0,2],[2,0]]) previously passed convexity checks on the general SOC conversion path. Diagonal LDLT returnedrank = 0withoutINDEFINITE_MATRIX_RETURN, and a degenerater = 0SOC lift silently dropped the quadratic term, allowing wrong solutions to be reported as Optimal (e.g. obj=2 at (1,1) instead of rejecting the problem).Fixes:
right_looking_ldlt: returnINDEFINITE_MATRIX_RETURNwhen factorization stalls atrank = 0on a nonzero matrix.translate_soc.hpp(general path):cuopt_expects(rank >= 1, …)before building the SOC lift.Adds LDLT and SOC-conversion unit tests for the cross-only indefinite case.
Issue
closes #1434
Checklist
Test plan
./build.sh libcuoptwith conda env active, then run built/installedcuopt_cliagainst$CONDA_PREFIX/lib/libcuopt.so./cpp/build/tests/dual_simplex/DUAL_SIMPLEX_TEST --gtest_filter='right_looking_ldlt.indefinite_cross_only_2x2'./cpp/build/tests/socp/SOCP_TEST --gtest_filter='general_quadratic.rejects_cross_only_indefinite'./cpp/build/tests/socp/SOCP_TEST --gtest_filter='general_quadratic.rejects_non_convex'datasets/qcqp/issue_1434_nonconvex.lp→ValidationError(not Optimal obj=2)