Skip to content

Commit c9cf42e

Browse files
Transurgeonclaude
andcommitted
Sync DNLP fork with CVXPY upstream master
Absorbs 12 upstream commits: type hints modernization, SvecPSD cone, power refactoring, mul_bounds NaN fix, and various bugfixes. All 8 merge conflicts resolved in favor of DNLP (parameter support, chain rule canonicalizers, negative power exponents). Lint fixes applied. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 50def87 commit c9cf42e

247 files changed

Lines changed: 1818 additions & 1618 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.git-blame-ignore-revs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Removes white space from blank lines (#3264)
2+
bebc9e0b17decfcdeebb37e6d72a6333bf42f7bb
3+
4+
# Modernize type hints and add pyright baseline (#3173)
5+
99b21e3ea481d5a47c125bfde23c032462ce82d7
6+
7+
# Add trailing whitespace and missing newline ruff rules (#3177)
8+
8f839a4be777a569dd49d94d92cb975fdc0b9198

.github/workflows/gcsopt.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88
tags:
99
- '*'
1010
jobs:
11-
test_backends:
11+
test_gcsopt:
1212
runs-on: ubuntu-latest
1313
steps:
1414
- uses: actions/setup-python@v6

.github/workflows/pyright.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: pyright
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- master
8+
9+
jobs:
10+
pyright:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v5
14+
15+
- name: Install uv
16+
uses: astral-sh/setup-uv@v7
17+
with:
18+
python-version: "3.11"
19+
enable-cache: true
20+
21+
- name: Install dependencies
22+
run: uv sync --no-install-project
23+
24+
- name: Run pyright
25+
run: uvx pyright

.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@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v2.16.4
54+
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v2.16.4
5555
with:
5656
sarif_file: results.sarif

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ repos:
2424
rev: v0.23
2525
hooks:
2626
- id: validate-pyproject
27+

cvxpy/atoms/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@
155155
sigma_max,
156156
tr_inv,
157157
quantum_rel_entr,
158+
von_neumann_entr,
158159
]
159160

160161
NONPOS_ATOMS = [

cvxpy/atoms/affine/add_expr.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
limitations under the License.
1515
"""
1616
import operator as op
17+
from collections.abc import Iterable
1718
from functools import reduce
18-
from typing import Any, Iterable, List, Tuple
19+
from typing import Any
1920

2021
import numpy as np
2122

@@ -38,12 +39,12 @@ def __init__(self, arg_groups: Iterable[Expression]) -> None:
3839
for group in arg_groups:
3940
self.args += self.expand_args(group)
4041

41-
def shape_from_args(self) -> Tuple[int, ...]:
42+
def shape_from_args(self) -> tuple[int, ...]:
4243
"""Returns the (row, col) shape of the expression.
4344
"""
4445
return u.shape.sum_shapes([arg.shape for arg in self.args])
4546

46-
def expand_args(self, expr: Expression) -> List[Expression]:
47+
def expand_args(self, expr: Expression) -> list[Expression]:
4748
"""Helper function to extract the arguments from an AddExpression.
4849
"""
4950
if isinstance(expr, AddExpression):
@@ -56,13 +57,13 @@ def name(self) -> str:
5657
for i in range(1, len(self.args)):
5758
result += " + " + str(self.args[i])
5859
return result
59-
60+
6061
def format_labeled(self):
6162
"""Format addition with labels where available."""
6263
# Check for own label first
6364
if self._label is not None:
6465
return self._label
65-
66+
6667
# Build from sub-expressions using their labels
6768
result = self.args[0].format_labeled()
6869
for i in range(1, len(self.args)):
@@ -82,7 +83,7 @@ def is_atom_log_log_concave(self) -> bool:
8283
"""
8384
return False
8485

85-
def bounds_from_args(self) -> Tuple[np.ndarray, np.ndarray]:
86+
def bounds_from_args(self) -> tuple[np.ndarray, np.ndarray]:
8687
"""Returns bounds for addition based on argument bounds."""
8788
# Start with first argument's bounds
8889
lb, ub = self.args[0].get_bounds()
@@ -133,8 +134,8 @@ def copy(self, args=None, id_objects=None) -> 'AddExpression':
133134
return copy
134135

135136
def graph_implementation(
136-
self, arg_objs, shape: Tuple[int, ...], data=None
137-
) -> Tuple[lo.LinOp, List[Constraint]]:
137+
self, arg_objs, shape: tuple[int, ...], data=None
138+
) -> tuple[lo.LinOp, list[Constraint]]:
138139
"""Sum the linear expressions.
139140
140141
Parameters

cvxpy/atoms/affine/affine_atom.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
"""
16-
from typing import Any, List, Tuple
16+
from typing import Any
1717

1818
import scipy.sparse as sp
1919

@@ -30,7 +30,7 @@ class AffAtom(Atom):
3030
""" Abstract base class for affine atoms. """
3131
_allow_complex = True
3232

33-
def sign_from_args(self) -> Tuple[bool, bool]:
33+
def sign_from_args(self) -> tuple[bool, bool]:
3434
"""By default, the sign is the most general of all the argument signs.
3535
"""
3636
return u.sign.sum_signs([arg for arg in self.args])
@@ -112,7 +112,7 @@ def is_nsd(self) -> bool:
112112
return False
113113
return True
114114

115-
def _grad(self, values) -> List[Any]:
115+
def _grad(self, values) -> list[Any]:
116116
"""Gives the (sub/super)gradient of the atom w.r.t. each argument.
117117
118118
Matrix expressions are vectorized, so the gradient is a matrix.

cvxpy/atoms/affine/binary_operators.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
import operator as op
1818
from functools import reduce
19-
from typing import List, Tuple
2019

2120
import numpy as np
2221
import scipy.sparse as sp
@@ -64,13 +63,13 @@ def name(self):
6463
else:
6564
pretty_args.append(a.name())
6665
return pretty_args[0] + ' ' + self.OP_NAME + ' ' + pretty_args[1]
67-
66+
6867
def format_labeled(self):
6968
"""Format binary operation with labels where available."""
7069
# Check for own label first
7170
if self._label is not None:
7271
return self._label
73-
72+
7473
# Build from sub-expressions using their labels
7574
pretty_args = []
7675
for i, a in enumerate(self.args):
@@ -90,7 +89,7 @@ def numeric(self, values):
9089
"""
9190
return reduce(self.OP_FUNC, values)
9291

93-
def sign_from_args(self) -> Tuple[bool, bool]:
92+
def sign_from_args(self) -> tuple[bool, bool]:
9493
"""Default to rules for times.
9594
"""
9695
return u.sign.mul_sign(self.args[0], self.args[1])
@@ -107,7 +106,7 @@ def is_complex(self) -> bool:
107106
return (self.args[0].is_complex() or self.args[1].is_complex()) and \
108107
not (self.args[0].is_imag() and self.args[1].is_imag())
109108

110-
def bounds_from_args(self) -> Tuple[np.ndarray, np.ndarray]:
109+
def bounds_from_args(self) -> tuple[np.ndarray, np.ndarray]:
111110
"""Returns bounds for elementwise multiplication based on argument bounds."""
112111
lb1, ub1 = self.args[0].get_bounds()
113112
lb2, ub2 = self.args[1].get_bounds()
@@ -192,7 +191,7 @@ def numeric(self, values):
192191
else:
193192
return values[0] @ values[1]
194193

195-
def shape_from_args(self) -> Tuple[int, ...]:
194+
def shape_from_args(self) -> tuple[int, ...]:
196195
"""Returns the (row, col) shape of the expression.
197196
"""
198197
return u.shape.mul_shapes(self.args[0].shape, self.args[1].shape)
@@ -235,7 +234,7 @@ def is_atom_log_log_concave(self) -> bool:
235234
"""
236235
return False
237236

238-
def bounds_from_args(self) -> Tuple[np.ndarray, np.ndarray]:
237+
def bounds_from_args(self) -> tuple[np.ndarray, np.ndarray]:
239238
"""Returns bounds for matrix multiplication based on argument bounds."""
240239
lb1, ub1 = self.args[0].get_bounds()
241240
lb2, ub2 = self.args[1].get_bounds()
@@ -312,8 +311,8 @@ def _grad(self, values):
312311
return [DX, DY]
313312

314313
def graph_implementation(
315-
self, arg_objs, shape: Tuple[int, ...], data=None
316-
) -> Tuple[lo.LinOp, List[Constraint]]:
314+
self, arg_objs, shape: tuple[int, ...], data=None
315+
) -> tuple[lo.LinOp, list[Constraint]]:
317316
"""Multiply the linear expressions.
318317
319318
Parameters
@@ -359,7 +358,7 @@ def is_atom_log_log_concave(self) -> bool:
359358
"""Is the atom log-log concave?"""
360359
return True
361360

362-
def bounds_from_args(self) -> Tuple[np.ndarray, np.ndarray]:
361+
def bounds_from_args(self) -> tuple[np.ndarray, np.ndarray]:
363362
"""Returns bounds for elementwise multiplication based on argument bounds.
364363
365364
Overrides MulExpression to use elementwise multiplication bounds
@@ -397,7 +396,7 @@ def validate_arguments(self):
397396
np.empty(self.args[1].shape, dtype=np.dtype([]))
398397
)
399398

400-
def shape_from_args(self) -> Tuple[int, ...]:
399+
def shape_from_args(self) -> tuple[int, ...]:
401400
"""Call np.broadcast on multiply arguments."""
402401
return np.broadcast_shapes(self.args[0].shape, self.args[1].shape)
403402

@@ -448,8 +447,8 @@ def _grad(self, values):
448447
return [DX, DY]
449448

450449
def graph_implementation(
451-
self, arg_objs, shape: Tuple[int, ...], data=None
452-
) -> Tuple[lo.LinOp, List[Constraint]]:
450+
self, arg_objs, shape: tuple[int, ...], data=None
451+
) -> tuple[lo.LinOp, list[Constraint]]:
453452
"""Multiply the expressions elementwise.
454453
455454
Parameters
@@ -509,7 +508,7 @@ def has_quadratic_term(self) -> bool:
509508
def is_qpwa(self) -> bool:
510509
return self.args[0].is_qpwa() and self.args[1].is_constant()
511510

512-
def shape_from_args(self) -> Tuple[int, ...]:
511+
def shape_from_args(self) -> tuple[int, ...]:
513512
"""Returns the (row, col) shape of the expression.
514513
"""
515514
return self.args[0].shape
@@ -539,7 +538,7 @@ def is_atom_quasiconvex(self) -> bool:
539538
def is_atom_quasiconcave(self) -> bool:
540539
return self.is_atom_quasiconvex()
541540

542-
def bounds_from_args(self) -> Tuple[np.ndarray, np.ndarray]:
541+
def bounds_from_args(self) -> tuple[np.ndarray, np.ndarray]:
543542
"""Returns bounds for division based on argument bounds."""
544543
lb1, ub1 = self.args[0].get_bounds()
545544
lb2, ub2 = self.args[1].get_bounds()
@@ -562,8 +561,8 @@ def is_decr(self, idx) -> bool:
562561
return self.args[0].is_nonneg()
563562

564563
def graph_implementation(
565-
self, arg_objs, shape: Tuple[int, ...], data=None
566-
) -> Tuple[lo.LinOp, List[Constraint]]:
564+
self, arg_objs, shape: tuple[int, ...], data=None
565+
) -> tuple[lo.LinOp, list[Constraint]]:
567566
"""Multiply the linear expressions.
568567
569568
Parameters
@@ -646,7 +645,7 @@ def outer(x, y):
646645
y = Expression.cast_to_const(y)
647646
if y.ndim > 1:
648647
raise ValueError("y must be a 1-d array.")
649-
648+
650649
x = reshape(x, (x.size, 1), order='F')
651650
y = reshape(y, (1, y.size), order='F')
652651
return x @ y

cvxpy/atoms/affine/broadcast_to.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
limitations under the License.
1515
"""
1616

17-
from typing import List, Optional, Tuple
1817

1918
import numpy as np
2019

@@ -44,7 +43,7 @@ def is_atom_log_log_concave(self) -> bool:
4443
def numeric(self, values):
4544
return np.broadcast_to(values[0], shape=self.broadcast_shape)
4645

47-
def get_data(self) -> List[Optional[int]]:
46+
def get_data(self) -> list[int | None]:
4847
return [self.broadcast_shape]
4948

5049
def validate_arguments(self) -> None:
@@ -53,15 +52,15 @@ def validate_arguments(self) -> None:
5352
shape=self.broadcast_shape
5453
)
5554

56-
def shape_from_args(self) -> Tuple[int, ...]:
55+
def shape_from_args(self) -> tuple[int, ...]:
5756
return self.broadcast_shape
5857

5958
def graph_implementation(
6059
self,
6160
arg_objs,
62-
shape: Tuple[int, ...],
61+
shape: tuple[int, ...],
6362
data=None,
64-
) -> Tuple[lo.LinOp, List[Constraint]]:
63+
) -> tuple[lo.LinOp, list[Constraint]]:
6564
"""Broadcast an expression to a given shape.
6665
6766
Parameters

0 commit comments

Comments
 (0)