Skip to content

Fix three bugs before patch release - #130

Merged
SteveDiamond merged 3 commits into
masterfrom
fix/patch-release-bugs
Jun 5, 2026
Merged

SteveDiamond merged 3 commits into
masterfrom
fix/patch-release-bugs

Conversation

@SteveDiamond

Copy link
Copy Markdown
Member

Three bugs found while auditing the codebase ahead of the patch release. Each is covered by a new regression test.

🔴 1. prob.value had the wrong sign for maximization problems

The dccp() return value was correctly de-negated for Maximize, but the value written to prob.value was the raw (negated) subproblem value. So a solved maximization problem returned the right number while prob.value held its negation.

Maximize(norm(x)) s.t. 0<=x<=1   # optimum sqrt(2) ≈ 1.414
  before:  return 1.414   prob.value -1.414  ❌
  after:   return 1.414   prob.value  1.414  ✅

Fixed in both the single-init (_solve) and multi-init (solve_multi_init) paths by setting prob.value from the original objective after writing the variables back, so the sign matches the problem sense.

🔴 2. Infinite loop in constraint convexification damping

The constraint-convexification damping loop was unbounded (while c_conv is None:), unlike the objective path which is bounded by max_iter_damp. When damping could not recover a valid linearization point (e.g. a variable lands outside a function's domain such as sqrt/log and _apply_damping is a no-op for it), the solve hung forever. Now bounded by max_iter_damp and raises NonDCCPError, mirroring the objective path. Extracted into _convexify_constr_with_damping.

🟡 3. seed ignored for multi-restart runs

_solve_multi_sequential / _solve_multi_parallel called initialize(..., random=True) without passing the configured seed, so seed= did not make multi-restart (k_ccp > 1) runs reproducible. Restarts now derive a distinct, reproducible per-restart seed via _restart_seed.

Testing

  • uv run pytest: 85 passed, 1 skipped (was 63; added 4 regression tests)
  • uv run ruff check: clean
  • uv run pyright: 0 errors

🤖 Generated with Claude Code

1. prob.value had the wrong sign for maximization problems. The function
   return value was de-negated, but the value written to prob.value was the
   raw (negated) subproblem value. Now set prob.value from the original
   objective so the sign matches the problem sense, in both the single-init
   and multi-init paths.

2. The constraint-convexification damping loop was unbounded and could hang
   forever when damping could not recover a valid linearization point (e.g. a
   variable outside a function's domain). It is now bounded by max_iter_damp
   and raises NonDCCPError, mirroring the objective path. Extracted into
   _convexify_constr_with_damping.

3. Multi-restart initializations (k_ccp > 1) ignored the configured seed, so
   seed= did not make restart runs reproducible. Restarts now derive a
   distinct, reproducible seed per restart via _restart_seed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SteveDiamond

Copy link
Copy Markdown
Member Author

@d-002 these are other bugs that I will fix in the v1.1.1 patch release. If you have anything else you would like fixed besides the bug you already identified, post here.

prob.value is typed as a union that doesn't support > / float(), so coerce
via float(...) with a type: ignore (matching the existing test pattern)
before comparing. CI runs pyright over tests too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@d-002

d-002 commented Jun 3, 2026 •

Copy link
Copy Markdown
Contributor

Hi,
Thanks for the mention, I did notice another warning (RuntimeWarning: Reading from a sparse CVXPY expression via ".value" is discouraged. Use ".value_sparse" instead) when using the latest commit as a package.
I haven't had time to investigate, but maybe this could be worth fixing as well?

Edit: after a discussion with Gemini, it appears this warning could be suppressed, as it means CVXPY converted a sparse array into a dense one in a specific case where it could lead to loss of performance.
However, the DCCP codebase mainly uses dense numpy arrays so the warning is not relevant.

@SteveDiamond

Copy link
Copy Markdown
Member Author

Thanks! The warning turned out to be only fixable on the cvxpy side, and it's also spurious (just checking if something is None). But I'll create a cvxpy issue about it. For the moment I suppressed the warnings.

DCCP creates sparse-pattern gradient parameters for efficiency and sets
them via .value_sparse, but cvxpy's solve loop still reads .value on those
leaves, emitting a "Reading from a sparse CVXPY expression via `.value` is
discouraged" RuntimeWarning on every solve. Scope a filter for that one
message around the public dccp() entry point and around _solve_one_init
(re-applied there since the parallel path runs in a worker process that
does not inherit the parent's filters). Add regression tests that reset the
warning filters to verify DCCP's own suppression, not the pytest config.

Tracked upstream: cvxpy/cvxpy#3367.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@SteveDiamond
SteveDiamond requested a review from PTNobel June 5, 2026 23:07
@SteveDiamond
SteveDiamond merged commit 108c3bc into master Jun 5, 2026
19 checks passed
@SteveDiamond
SteveDiamond deleted the fix/patch-release-bugs branch June 5, 2026 23:55
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.

3 participants