Skip to content

Commit b15d1f7

Browse files
authored
Merge branch 'master' into cvxpy-sync-1.9
2 parents 8e16fa2 + 386e471 commit b15d1f7

43 files changed

Lines changed: 1131 additions & 398 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: test_cvxpygen
1+
name: test_extensions
22

33
on:
44
pull_request:
@@ -21,14 +21,26 @@ jobs:
2121
run: |
2222
sudo apt update
2323
sudo apt install libeigen3-dev
24-
- name: Install cvxpy and CVXPYgen
24+
- name: Install CVXPY
2525
run: |
26-
uv venv
26+
uv venv --seed
2727
uv pip install .
28+
- name: Install CVXPYlayers
29+
run: |
30+
git clone https://github.com/cvxpy/cvxpylayers.git
31+
cd cvxpylayers
32+
uv pip install ".[torch, jax]"
33+
uv pip install "mlx[cpu]" "mpax"
34+
- name: Install CVXPYgen
35+
run: |
2836
git clone --recurse-submodules https://github.com/cvxgrp/cvxpygen.git
2937
cd cvxpygen
3038
uv pip install .[dev]
31-
uv pip install cvxpylayers==0.1.9 "torch>=2.3" jax==0.5.3
39+
- name: Run CVXPYlayers tests
40+
run: |
41+
uv run --no-sync pytest cvxpylayers/tests \
42+
--ignore=cvxpylayers/tests/test_cuclarabel.py
3243
- name: Run CVXPYgen tests
44+
if: success() || failure()
3345
run: |
3446
uv run --no-sync pytest cvxpygen/tests

.github/workflows/scorecards.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ jobs:
5151
# Upload the results to GitHub's code scanning dashboard (optional).
5252
# Commenting out will disable upload of results to your repo's Code Scanning dashboard
5353
- name: "Upload to code-scanning"
54-
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
54+
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
5555
with:
5656
sarif_file: results.sarif

.github/workflows/test_backends.yml

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,28 @@ on:
1111
jobs:
1212
test_backends:
1313
runs-on: ubuntu-latest
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
backend: [SCIPY, COO]
1418
steps:
15-
- uses: actions/setup-python@v6
19+
- uses: actions/checkout@v5
20+
21+
- name: Install uv
22+
uses: astral-sh/setup-uv@v7
1623
with:
1724
python-version: "3.12"
18-
- uses: actions/checkout@v5
25+
enable-cache: true
26+
1927
- name: Install cvxpy dependencies
2028
run: |
21-
pip install -e .
22-
pip install pytest hypothesis
23-
- name: Run tests with SCIPY backend
24-
run : |
25-
export CVXPY_DEFAULT_CANON_BACKEND="SCIPY"
26-
python -c "from cvxpy.cvxcore.python.canonInterface import get_default_canon_backend; print(get_default_canon_backend())"
27-
python -c "from cvxpy.cvxcore.python.canonInterface import get_default_canon_backend; assert get_default_canon_backend() == 'SCIPY'"
28-
pytest
29-
- name: Run tests with COO backend
30-
run : |
31-
export CVXPY_DEFAULT_CANON_BACKEND="COO"
32-
python -c "from cvxpy.cvxcore.python.canonInterface import get_default_canon_backend; print(get_default_canon_backend())"
33-
python -c "from cvxpy.cvxcore.python.canonInterface import get_default_canon_backend; assert get_default_canon_backend() == 'COO'"
34-
pytest
29+
uv venv
30+
uv pip install -e ".[testing]"
31+
32+
- name: Run tests with ${{ matrix.backend }} backend
33+
env:
34+
CVXPY_DEFAULT_CANON_BACKEND: ${{ matrix.backend }}
35+
run: |
36+
uv run python -c "from cvxpy.cvxcore.python.canonInterface import get_default_canon_backend; print(get_default_canon_backend())"
37+
uv run python -c "from cvxpy.cvxcore.python.canonInterface import get_default_canon_backend; assert get_default_canon_backend() == '${{ matrix.backend }}'"
38+
uv run pytest
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: test_derivative
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- master
8+
9+
jobs:
10+
test_derivative:
11+
runs-on: ubuntu-22.04
12+
defaults:
13+
run:
14+
shell: bash -l {0}
15+
steps:
16+
- uses: actions/checkout@v5
17+
- uses: astral-sh/setup-uv@v7
18+
with:
19+
python-version: "3.12"
20+
enable-cache: true
21+
- name: Install cvxpy and diffcp
22+
run: |
23+
uv sync --dev
24+
uv pip install pytest diffcp
25+
- name: Run test_derivative
26+
run: uv run pytest -rs cvxpy/tests/test_derivative.py

cvxpy/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
suppfunc as suppfunc,
6565
)
6666
from cvxpy import logic as logic
67+
from cvxpy import nlp as nlp
6768
from cvxpy.reductions.solvers.defines import installed_solvers as installed_solvers
6869
from cvxpy.settings import (
6970
CBC as CBC,

cvxpy/atoms/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,8 @@
7272
from cvxpy.atoms.elementwise.sqrt import sqrt
7373
from cvxpy.atoms.elementwise.square import square
7474
from cvxpy.atoms.elementwise.xexp import xexp
75-
from cvxpy.atoms.elementwise.trig import sin, cos, tan
76-
from cvxpy.atoms.elementwise.hyperbolic import sinh, asinh, tanh, atanh
77-
from cvxpy.atoms.elementwise.normcdf import normcdf
75+
# NLP atoms (require nlp=True solver) are accessible via cp.nlp namespace only.
76+
# e.g. cp.nlp.sin, cp.nlp.cos — not at the top-level cp namespace.
7877
from cvxpy.atoms.eye_minus_inv import eye_minus_inv, resolvent
7978
from cvxpy.atoms.gen_lambda_max import gen_lambda_max
8079
from cvxpy.atoms.geo_mean import GeoMean, GeoMeanApprox, geo_mean

cvxpy/atoms/quad_form.py

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717

1818
import numpy as np
1919
import scipy.sparse as sp
20-
from scipy import linalg as LA
2120

2221
from cvxpy.atoms.affine.wraps import psd_wrap
2322
from cvxpy.atoms.atom import Atom
2423
from cvxpy.expressions.constants.parameter import is_param_affine, is_param_free
2524
from cvxpy.expressions.expression import Expression
2625
from cvxpy.interface.matrix_utilities import is_sparse
2726
from cvxpy.utilities import scopes
28-
from cvxpy.utilities.linalg import sparse_cholesky
27+
from cvxpy.utilities.linalg import dense_ldl_decomp, sparse_cholesky
2928
from cvxpy.utilities.warn import warn
3029

3130

@@ -264,27 +263,7 @@ def decomp_quad(P, cond=None, rcond=None, lower=True, check_finite: bool = True)
264263
return 1.0, np.empty((0, 0)), L[p, :]
265264
except ValueError:
266265
P = P.toarray() # make dense (needs to happen for ldl).
267-
lu, d, _perm = LA.ldl(P, lower=lower, check_finite=check_finite)
268-
269-
# Extract effective diagonal values from D, handling any 2x2 blocks.
270-
# For PSD/NSD matrices D is diagonal (no 2x2 blocks). For indefinite
271-
# matrices, Bunch-Kaufman pivoting may introduce 2x2 blocks which we
272-
# resolve by batching all 2x2 blocks into a single eigh call.
273-
sub_diag = np.diag(d, -1)
274-
block_starts = np.nonzero(sub_diag)[0]
275-
diag_vals = np.real(np.diag(d)).copy()
276-
if len(block_starts) > 0:
277-
bs = block_starts
278-
idx = bs[:, None] + np.arange(2)[None, :] # (k, 2)
279-
blocks = d[idx[:, :, None], idx[:, None, :]] # (k, 2, 2)
280-
eigvals, eigvecs = np.linalg.eigh(blocks) # (k, 2), (k, 2, 2)
281-
diag_vals[bs] = eigvals[:, 0]
282-
diag_vals[bs + 1] = eigvals[:, 1]
283-
# Apply eigenvector rotations to lu columns
284-
lu_i = lu[:, bs].copy()
285-
lu_ip1 = lu[:, bs + 1].copy()
286-
lu[:, bs] = lu_i * eigvecs[:, 0, 0] + lu_ip1 * eigvecs[:, 1, 0]
287-
lu[:, bs + 1] = lu_i * eigvecs[:, 0, 1] + lu_ip1 * eigvecs[:, 1, 1]
266+
diag_vals, lu = dense_ldl_decomp(P, lower=lower, check_finite=check_finite)
288267

289268
if rcond is not None:
290269
cond = rcond

cvxpy/expressions/leaf.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ def __init__(
167167
"Sparsity and diag attributes force zeros, which contradicts "
168168
"strict positivity/negativity."
169169
)
170-
self._leaf_of_provenance = None
171170
self.args = []
172171
self.bounds = self._ensure_valid_bounds(bounds)
173172
self.attributes['bounds'] = self.bounds
@@ -709,18 +708,6 @@ def is_dpp(self, context: str = 'dcp') -> bool:
709708
def atoms(self) -> list[Atom]:
710709
return []
711710

712-
def attributes_were_lowered(self) -> bool:
713-
"""True iff this leaf was generated when lowering a leaf with attributes."""
714-
return self._leaf_of_provenance is not None
715-
716-
def set_leaf_of_provenance(self, leaf: Leaf) -> None:
717-
assert leaf.attributes
718-
self._leaf_of_provenance = leaf
719-
720-
def leaf_of_provenance(self) -> Leaf | None:
721-
"""Returns a leaf with attributes from which this leaf was generated."""
722-
return self._leaf_of_provenance
723-
724711
@property
725712
def _has_dim_reducing_attr(self) -> bool:
726713
return (self.sparse_idx is not None or self.attributes['diag'] or

cvxpy/expressions/variable.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,6 @@ def canonicalize(self) -> tuple[Expression, list[Constraint]]:
126126
obj = lu.create_var(self.shape, self.id)
127127
return (obj, [])
128128

129-
def set_variable_of_provenance(self, variable: Variable) -> None:
130-
"""Deprecated: use set_leaf_of_provenance instead."""
131-
self.set_leaf_of_provenance(variable)
132-
133-
def variable_of_provenance(self) -> Variable | None:
134-
"""Deprecated: use leaf_of_provenance instead."""
135-
return self.leaf_of_provenance()
136-
137129
def __repr__(self) -> str:
138130
"""String to recreate the variable."""
139131
attr_str = self._get_attr_str()

cvxpy/nlp/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""
2+
cvxpy.nlp — Namespace for NLP (nonlinear programming) atoms.
3+
4+
These atoms require a solver that supports nlp=True (e.g. IPOPT, UNO).
5+
6+
Example usage:
7+
import cvxpy as cp
8+
x = cp.Variable()
9+
prob = cp.Problem(cp.Minimize(cp.nlp.sin(x)), [x >= 0])
10+
prob.solve(nlp=True)
11+
"""
12+
13+
from cvxpy.atoms.elementwise.trig import sin, cos, tan
14+
from cvxpy.atoms.elementwise.hyperbolic import sinh, tanh, asinh, atanh
15+
16+
__all__ = [
17+
"sin", "cos", "tan",
18+
"sinh", "tanh", "asinh", "atanh",
19+
]

0 commit comments

Comments
 (0)