Skip to content

Commit 8e16fa2

Browse files
committed
Restore upstream README.md and CLAUDE.md
For the cvxpy/cvxpy upstream sync PR; keeps cvxpy's own docs intact instead of replacing them with the DNLP fork's versions.
1 parent d159e42 commit 8e16fa2

2 files changed

Lines changed: 302 additions & 137 deletions

File tree

CLAUDE.md

Lines changed: 160 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,29 @@
1-
# CLAUDE.md
1+
# CVXPY Development Guide
22

3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4-
5-
This is a **fork of CVXPY** that adds **Disciplined Nonlinear Programming (DNLP)** support, enabling non-convex optimization with automatic differentiation via SparseDiffPy.
6-
7-
## Commands
3+
## Quick Reference
84

5+
### Commands
96
```bash
107
# Install in development mode
118
pip install -e .
129

1310
# Install pre-commit hooks (required)
1411
pip install pre-commit && pre-commit install
1512

16-
# Lint
17-
ruff check cvxpy
18-
1913
# Run all tests
2014
pytest cvxpy/tests/
2115

22-
# Run NLP-specific tests
23-
pytest cvxpy/tests/nlp_tests/
24-
25-
# Run a specific test
16+
# Run specific test
2617
pytest cvxpy/tests/test_atoms.py::TestAtoms::test_norm_inf
2718
```
2819

2920
## Code Style
3021

3122
- **Line length**: 100 characters
32-
- **Linter**: `ruff`
3323
- **IMPORTANT: IMPORTS AT THE TOP** of files - circular imports are the only exception
34-
- **IMPORTANT:** Add Apache 2.0 license header to all new files:
24+
- **IMPORTANT:** Add Apache 2.0 license header to all new files
3525

26+
### License Header
3627
```python
3728
"""
3829
Copyright, the CVXPY authors
@@ -51,110 +42,205 @@ limitations under the License.
5142
"""
5243
```
5344

45+
## Project Structure
46+
47+
```
48+
cvxpy/
49+
├── atoms/ # Mathematical functions (exp, log, norm, etc.)
50+
│ ├── affine/ # Linear/affine ops (reshape, sum, trace, index)
51+
│ └── elementwise/ # Element-wise ops (exp, log, abs, sqrt)
52+
├── constraints/ # Constraint types (Zero, NonNeg, SOC, PSD)
53+
├── expressions/ # Variable, Parameter, Constant, Expression
54+
├── problems/ # Problem class and Minimize/Maximize
55+
├── reductions/ # Problem transformations
56+
│ ├── dcp2cone/ # DCP → conic canonicalizers
57+
│ ├── dgp2dcp/ # DGP → DCP transforms
58+
│ └── solvers/ # Solver interfaces
59+
│ ├── conic_solvers/
60+
│ └── qp_solvers/
61+
├── lin_ops/ # Linear operator representation
62+
│ └── backends/ # Canonicalization backends
63+
├── utilities/ # Helpers, performance utils
64+
└── tests/ # Unit tests (use pytest to run)
65+
```
66+
5467
## Architecture
5568

5669
### Expression Hierarchy
5770
```
5871
Expression (base)
59-
├── Leaf (terminal nodes: Variable, Parameter, Constant)
60-
└── Atom (function applications: AffineAtom, Elementwise, AxisAtom)
72+
├── Leaf (terminal nodes)
73+
│ ├── Variable
74+
│ ├── Parameter
75+
│ └── Constant
76+
└── Atom (function applications)
77+
├── AffineAtom
78+
├── Elementwise
79+
└── AxisAtom
6180
```
6281

63-
### Reduction Chains
64-
65-
**Standard (DCP) chain:**
82+
### Reduction Chain
83+
Problems are transformed through a chain of reductions:
6684
```
6785
Problem → [Dgp2Dcp] → [FlipObjective] → Dcp2Cone → CvxAttr2Constr → ConeMatrixStuffing → Solver
6886
```
6987

70-
**NLP chain** (when `nlp=True`):
71-
```
72-
Problem → [FlipObjective] → CvxAttr2Constr → Dnlp2Smooth → NLPSolver (IPOPT/KNITRO/UNO/COPT)
73-
```
88+
**Key reductions:**
89+
- `Dgp2Dcp` - Converts DGP to DCP (if `gp=True`)
90+
- `FlipObjective` - Converts Maximize to Minimize (negates objective)
91+
- `Dcp2Cone` - Canonicalizes atoms to conic constraints (calls canonicalizers)
92+
- `CvxAttr2Constr` - Converts variable attributes (e.g., `nonneg=True`) to constraints
93+
- `ConeMatrixStuffing` - Extracts A, b, c matrices for solver
7494

75-
Each reduction implements `accepts()`, `apply()`, and `invert()`.
76-
See `cvxpy/reductions/solvers/solving_chain.py` for DCP chain construction.
77-
See `cvxpy/reductions/solvers/nlp_solving_chain.py` for NLP chain construction.
95+
Each reduction implements:
96+
- `accepts(problem) → bool` - Can handle this problem?
97+
- `apply(problem) → (new_problem, inverse_data)` - Transform
98+
- `invert(solution, inverse_data) → solution` - Map solution back
99+
100+
See `cvxpy/reductions/solvers/solving_chain.py` for chain construction.
78101

79102
### DCP Rules
80-
Atoms define curvature via `is_atom_convex()`, `is_atom_concave()`, `is_incr(idx)`, `is_decr(idx)`.
103+
Atoms define curvature via:
104+
- `is_atom_convex()` / `is_atom_concave()` - Intrinsic curvature
105+
- `is_incr(idx)` / `is_decr(idx)` - Monotonicity per argument
106+
107+
### DGP (Disciplined Geometric Programming)
108+
DGP problems use log-log curvature instead of standard curvature. Transformed to DCP via `dgp2dcp` reduction.
109+
110+
### DQCP (Disciplined Quasiconvex Programming)
111+
DQCP extends DCP to quasiconvex functions. Solved via bisection on a parameter. Transformed via `dqcp2dcp` reduction.
81112

82113
### DPP (Disciplined Parametrized Programming)
83-
Parameters are treated as affine (not constant) for curvature analysis. `param * param` is NOT DPP; `param * variable` is DPP. Check with `problem.is_dpp()`. See `cvxpy/utilities/scopes.py`.
114+
DPP enables efficient re-solving when only `Parameter` values change. CVXPY caches the canonicalization and reuses it.
84115

85-
## DNLP Architecture
116+
**How it works**: Parameters are treated as affine (not constant) for curvature analysis. This means:
117+
- `param * param` → NOT DPP (quadratic in params)
118+
- `param * variable` → DPP (affine in params, params only in one factor)
119+
- `cp.norm(param)` in constraint → NOT DPP (nonlinear in params)
86120

87-
### Core flow (`nlp_solving_chain.py`)
88-
`solve_nlp()` orchestrates DNLP solving:
89-
1. Builds chain: [FlipObjective] → CvxAttr2Constr → Dnlp2Smooth → NLPSolver
90-
2. Sets initial points (from variable values or bounds-based sampling)
91-
3. Supports best-of-N solving with random restarts via `sample_bounds`
92-
4. Caches solver state in `_solver_cache['NLP']` for parametric re-solving
121+
Check with `problem.is_dpp()`. See `cvxpy/utilities/scopes.py` for implementation.
93122

94-
### DNLP expression properties
95-
- `is_dnlp()` on Problem: validates all constraints/objective satisfy DNLP rules
96-
- `is_smooth()`: expression is both linearizable_convex and linearizable_concave
97-
- `is_linearizable_convex()` / `is_linearizable_concave()`: DNLP composition rules
98-
- `is_atom_smooth()`: atom-level smoothness (e.g., log, exp, sin)
123+
## Implementing New Atoms
99124

100-
### Smooth canonicalization (`cvxpy/reductions/dnlp2smooth/`)
101-
`Dnlp2Smooth` converts DNLP expressions to smooth forms. Canonicalizers in `dnlp2smooth/canonicalizers/` handle atoms like log, exp, sin, power, geo_mean, etc. Registered in `SMOOTH_CANON_METHODS` dict.
125+
### 1. Create Atom Class
126+
Location: `cvxpy/atoms/` or `cvxpy/atoms/elementwise/`
102127

103-
### Diff engine (`cvxpy/reductions/solvers/nlp_solvers/diff_engine/`)
104-
- `c_problem.py`: Wraps SparseDiffPy C library for AD (objective, gradient, Jacobian, Hessian)
105-
- `converters.py`: Entry point — `convert_expr(expr, var_dict, n_vars, param_dict=None)` recursively converts CVXPY expressions to C diff engine nodes
106-
- `registry.py`: `ATOM_CONVERTERS` dict mapping atom names to converter functions (40+ atoms)
107-
- `helpers.py`: Shared utilities (`build_var_dict`, `build_param_dict`, matmul helpers, `normalize_shape`)
128+
```python
129+
from typing import Tuple
130+
from cvxpy.atoms.atom import Atom
108131

109-
### Solver interfaces (`cvxpy/reductions/solvers/nlp_solvers/`)
110-
- `nlp_solver.py`: Base `NLPsolver` class with `Bounds` (constraint extraction) and `Oracles` (diff engine wrapper)
111-
- Solver implementations: `ipopt_nlpif.py`, `knitro_nlpif.py`, `uno_nlpif.py`, `copt_nlpif.py`
112-
- Parameter updates flow through `Oracles.update_params()``C_problem.update_params()`
132+
class my_atom(Atom):
133+
def __init__(self, x) -> None:
134+
super().__init__(x)
113135

114-
### Parameter support
115-
Parameters are passed as param nodes to the diff engine at construction. On re-solve, `update_params()` updates values without rebuilding the expression graph. The solver cache (`_solver_cache['NLP']`) stores `Oracles` between solves.
136+
def shape_from_args(self) -> Tuple[int, ...]:
137+
return self.args[0].shape
116138

117-
## Implementing New Atoms
139+
def sign_from_args(self) -> Tuple[bool, bool]:
140+
return (False, False) # (is_nonneg, is_nonpos)
118141

119-
### 1. Create atom class in `cvxpy/atoms/` or `cvxpy/atoms/elementwise/`
120-
Implement: `shape_from_args()`, `sign_from_args()`, `is_atom_convex()`, `is_atom_concave()`, `is_incr(idx)`, `is_decr(idx)`, `numeric(values)`.
142+
def is_atom_convex(self) -> bool:
143+
return True
121144

122-
### 2. Create DCP canonicalizer in `cvxpy/reductions/dcp2cone/canonicalizers/`
123-
Register in `canonicalizers/__init__.py``CANON_METHODS[my_atom] = my_atom_canon`.
145+
def is_atom_concave(self) -> bool:
146+
return False
124147

125-
### 3. Create smooth canonicalizer in `cvxpy/reductions/dnlp2smooth/canonicalizers/` (if atom is smooth)
126-
Register in `SMOOTH_CANON_METHODS`.
148+
def is_incr(self, idx: int) -> bool:
149+
return True
127150

128-
### 4. Add converter in `cvxpy/reductions/solvers/nlp_solvers/diff_engine/registry.py``ATOM_CONVERTERS` (for NLP support)
151+
def is_decr(self, idx: int) -> bool:
152+
return False
129153

130-
### 5. Export in `cvxpy/atoms/__init__.py`
154+
def numeric(self, values):
155+
return np.my_function(values[0])
156+
```
157+
158+
### 2. Create Canonicalizer
159+
Location: `cvxpy/reductions/dcp2cone/canonicalizers/`
160+
161+
```python
162+
from cvxpy.expressions.variable import Variable
163+
from cvxpy.utilities.solver_context import SolverInfo
164+
165+
def my_atom_canon(expr, args, solver_context: SolverInfo | None = None):
166+
x = args[0]
167+
t = Variable(expr.shape)
168+
# For CONVEX atoms: use t >= f(x)
169+
# When minimizing, optimizer pushes t down to equality: t = f(x)
170+
# For CONCAVE atoms: use t <= f(x)
171+
# When maximizing, optimizer pushes t up to equality: t = f(x)
172+
constraints = [t >= x] # Example for convex atom
173+
return t, constraints
174+
```
175+
176+
### 3. Register
177+
In `cvxpy/reductions/dcp2cone/canonicalizers/__init__.py`:
178+
```python
179+
from cvxpy.atoms import my_atom
180+
CANON_METHODS[my_atom] = my_atom_canon
181+
```
182+
183+
### 4. Export
184+
In `cvxpy/atoms/__init__.py`:
185+
```python
186+
from cvxpy.atoms.my_atom import my_atom
187+
```
131188

132189
## Testing
133190

134-
- Use `solver=cp.CLARABEL` for DCP tests that call `problem.solve()`
135-
- NLP tests go in `cvxpy/tests/nlp_tests/`
136-
- Test class inherits from `cvxpy.tests.base_test.BaseTest`
137-
- Assertion helpers: `self.assertItemsAlmostEqual(a, b, places=5)`, `self.assertAlmostEqual(a, b, places=5)`
138-
- Test with `Parameter` objects for DPP compliance
191+
Tests should be **comprehensive but concise and focused**. Cover edge cases without unnecessary verbosity.
192+
193+
**IMPORTANT:** Use `solver=cp.CLARABEL` for tests that call `problem.solve()` - it's the default open-source solver.
194+
195+
### Base Test Pattern
196+
```python
197+
from cvxpy.tests.base_test import BaseTest
198+
import cvxpy as cp
199+
import numpy as np
200+
201+
class TestMyFeature(BaseTest):
202+
def test_basic(self) -> None:
203+
x = cp.Variable(2)
204+
atom = cp.my_atom(x)
205+
206+
# Test DCP
207+
self.assertTrue(atom.is_convex())
208+
209+
# Test numeric
210+
x.value = np.array([1.0, 2.0])
211+
self.assertItemsAlmostEqual(atom.value, expected)
212+
213+
def test_solve(self) -> None:
214+
x = cp.Variable(2)
215+
prob = cp.Problem(cp.Minimize(cp.sum(x)), [x >= 1])
216+
prob.solve(solver=cp.CLARABEL)
217+
self.assertEqual(prob.status, cp.OPTIMAL)
218+
```
219+
220+
### Assertion Helpers
221+
- `self.assertItemsAlmostEqual(a, b, places=5)` - Compare arrays
222+
- `self.assertAlmostEqual(a, b, places=5)` - Compare scalars
139223

140224
## Canon Backend Architecture
141225

142-
Backends handle matrix construction during `ConeMatrixStuffing`. Located in `cvxpy/lin_ops/backends/`.
143-
- `CPP` (default) - C++ implementation, fastest for large expression trees
144-
- `SCIPY` - Pure Python with SciPy sparse matrices
226+
Backends are critical to performance. They handle matrix construction during `ConeMatrixStuffing`. Located in `cvxpy/lin_ops/backends/`.
227+
228+
**Backends:**
229+
- `CPP` (default) - C++ implementation, fastest for problems with large expression trees
230+
- `SCIPY` - Pure Python with SciPy sparse matrices, good for large problems
145231
- `COO` - 3D COO tensor, better for large DPP problems with many parameters
146232

147233
Select via `CVXPY_DEFAULT_CANON_BACKEND=CPP` (default), `SCIPY`, or `COO`.
148234

149235
## Pull Requests
150236

151-
Always use the PR template in `.github/`. **Never check an item in the Contribution Checklist that has not actually been done.**
237+
Always use the PR template in `.github/` when opening PRs. Fill out all sections. **Never check an item in the Contribution Checklist that has not actually been done.**
152238

153239
## Common Mistakes to Avoid
154240

155-
1. **Forgetting to register canonicalizers** in `canonicalizers/__init__.py` (both DCP and smooth)
241+
1. **Forgetting to register canonicalizers** in `canonicalizers/__init__.py`
156242
2. **Forgetting to export atoms** in `cvxpy/atoms/__init__.py`
157243
3. Missing `is_incr`/`is_decr` methods in atoms (breaks DCP analysis)
158244
4. Not testing with `Parameter` objects (DPP compliance)
159245
5. Missing license headers on new files
160-
6. Not adding a diff engine converter for new atoms (breaks NLP support)
246+
6. **Forgetting to update documentation** - new features need docs at [cvxpy.org](https://www.cvxpy.org/) (see `doc/` folder)

0 commit comments

Comments
 (0)