Skip to content

Fix factor-2 error in Hermitian PSD constraint dual recovery#3388

Open
PTNobel wants to merge 1 commit into
masterfrom
ptn/fix-hermitian-psd-dual
Open

Fix factor-2 error in Hermitian PSD constraint dual recovery#3388
PTNobel wants to merge 1 commit into
masterfrom
ptn/fix-hermitian-psd-dual

Conversation

@PTNobel

@PTNobel PTNobel commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Description

A Hermitian PSD constraint X >> 0 is canonicalized to the real
dilation [[Re(X), -Im(X)], [Im(X), Re(X)]] >> 0. The dilation doubles
the inner product: <Y_big, X_big> = 2Re(<Y, X>) for Y recovered from
the blocks of Y_big. Complex2Real.invert() recovered the complex dual
as dual[:n,:n] + 1j
dual[n:,:n] without compensating, so Hermitian PSD
duals were half their correct value: for
minimize Re(trace(C @ X)) s.t. X >> A the KKT conditions give Y == C,
but con.dual_value returned C/2 and violated strong duality
(Re(<Y, A>) was half the optimal value). The real symmetric analog
already returned the correct dual.

Multiply the recovered dual by 2. Other complex dual recoveries in
complex2real.py (Equality/Zero and purely imaginary constraints) split
into separate real constraints rather than a dilation, so they have no
such factor and are unchanged. No existing tests encoded the halved
value.

The regression test checks the dual equals C and that strong duality
holds; the fix was also verified against finite-difference sensitivity
to Hermitian perturbations of A.

Found by an automated bug-hunting audit of master; each finding was reproduced against an independent oracle before fixing.

Type of change

  • New feature (backwards compatible)
  • New feature (breaking API changes)
  • Bug fix
  • Other (Documentation, CI, ...)

Contribution checklist

  • Add our license to new files.
  • Check that your code adheres to our coding style.
  • Write unittests.
  • Run the unittests and check that they’re passing.
  • Run the benchmarks to make sure your change doesn’t introduce a regression.

A Hermitian PSD constraint X >> 0 is canonicalized to the real
dilation [[Re(X), -Im(X)], [Im(X), Re(X)]] >> 0. The dilation doubles
the inner product: <Y_big, X_big> = 2*Re(<Y, X>) for Y recovered from
the blocks of Y_big. Complex2Real.invert() recovered the complex dual
as dual[:n,:n] + 1j*dual[n:,:n] without compensating, so Hermitian PSD
duals were half their correct value: for
minimize Re(trace(C @ X)) s.t. X >> A the KKT conditions give Y == C,
but con.dual_value returned C/2 and violated strong duality
(Re(<Y, A>) was half the optimal value). The real symmetric analog
already returned the correct dual.

Multiply the recovered dual by 2. Other complex dual recoveries in
complex2real.py (Equality/Zero and purely imaginary constraints) split
into separate real constraints rather than a dilation, so they have no
such factor and are unchanged. No existing tests encoded the halved
value.

The regression test checks the dual equals C and that strong duality
holds; the fix was also verified against finite-difference sensitivity
to Hermitian perturbations of A.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM

@github-actions

Copy link
Copy Markdown

Benchmarks that have stayed the same:

   before           after         ratio
 [1a5c7eb0]       [7f1ebbdc]
      1.31±0s          1.34±0s     1.02  gini_portfolio.Murray.time_compile_problem
      21.4±0s          21.5±0s     1.01  sdp_segfault_1132_benchmark.SDPSegfault1132Benchmark.time_compile_problem
      152±0ms          153±0ms     1.01  high_dim_convex_plasticity.ConvexPlasticity.time_compile_problem
      2.47±0s          2.49±0s     1.01  matrix_stuffing.SmallMatrixStuffing.time_compile_problem
      745±0ms          750±0ms     1.01  simple_LP_benchmarks.SimpleFullyParametrizedLPBenchmark.time_compile_problem
      2.62±0s          2.63±0s     1.01  simple_QP_benchmarks.ParametrizedQPBenchmark.time_compile_problem
      1.43±0s          1.43±0s     1.00  gini_portfolio.Yitzhaki.time_compile_problem
      3.49±0s          3.48±0s     1.00  matrix_stuffing.ConeMatrixStuffingBench.time_compile_problem
      10.1±0s          10.1±0s     1.00  simple_LP_benchmarks.SimpleLPBenchmark.time_compile_problem
      4.97±0s          4.95±0s     1.00  gini_portfolio.Cajas.time_compile_problem
      1.96±0s          1.95±0s     1.00  simple_QP_benchmarks.LeastSquares.time_compile_problem
      3.89±0s          3.87±0s     0.99  matrix_stuffing.ParamSmallMatrixStuffing.time_compile_problem
      1.85±0s          1.84±0s     0.99  finance.FactorCovarianceModel.time_compile_problem
      4.56±0s          4.53±0s     0.99  svm_l1_regularization.SVMWithL1Regularization.time_compile_problem
      1.48±0s          1.47±0s     0.99  matrix_stuffing.ParamConeMatrixStuffing.time_compile_problem
      4.00±0s          3.96±0s     0.99  huber_regression.HuberRegression.time_compile_problem
      1.95±0s          1.91±0s     0.98  semidefinite_programming.SemidefiniteProgramming.time_compile_problem
      3.90±0s          3.82±0s     0.98  simple_QP_benchmarks.UnconstrainedQP.time_compile_problem
      5.67±0s          5.56±0s     0.98  optimal_advertising.OptimalAdvertising.time_compile_problem
      1.59±0s          1.55±0s     0.98  tv_inpainting.TvInpainting.time_compile_problem
      4.41±0s          4.31±0s     0.98  simple_QP_benchmarks.SimpleQPBenchmark.time_compile_problem
      13.3±0s          12.9±0s     0.97  finance.CVaRBenchmark.time_compile_problem
      1.93±0s          1.86±0s     0.97  simple_LP_benchmarks.SimpleScalarParametrizedLPBenchmark.time_compile_problem
      2.56±0s          2.43±0s     0.95  slow_pruning_1668_benchmark.SlowPruningBenchmark.time_compile_problem
      2.82±0s          2.60±0s     0.92  quantum_hilbert_matrix.QuantumHilbertMatrix.time_compile_problem

@PTNobel PTNobel marked this pull request as draft June 10, 2026 06:26
@PTNobel PTNobel marked this pull request as ready for review June 10, 2026 21:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant