Skip to content

Commit 530bb79

Browse files
Transurgeonclaude
andcommitted
Add diff_engine integration layer for CVXPY-to-C expression conversion
- Create cvxpy/reductions/solvers/nlp_solvers/diff_engine/ package - Move C_problem wrapper and ATOM_CONVERTERS from dnlp-diff-engine - converters.py: Expression tree conversion from CVXPY atoms to C nodes - c_problem.py: C_problem class wrapping the C problem struct - Update nlp_solver.py to import from new location This separates CVXPY-specific glue code from the pure C autodiff library, eliminating circular dependencies and preparing dnlp-diff-engine for standalone PyPI distribution. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent bd696aa commit 530bb79

File tree

4 files changed

+460
-2
lines changed

4 files changed

+460
-2
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""
2+
Copyright 2025, the CVXPY developers
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
17+
"""
18+
CVXPY integration layer for the DNLP diff engine.
19+
20+
This module converts CVXPY expressions to C expression trees
21+
for automatic differentiation.
22+
"""
23+
24+
from cvxpy.reductions.solvers.nlp_solvers.diff_engine.c_problem import C_problem
25+
from cvxpy.reductions.solvers.nlp_solvers.diff_engine.converters import (
26+
ATOM_CONVERTERS,
27+
build_variable_dict,
28+
convert_expr,
29+
convert_expressions,
30+
)
31+
32+
__all__ = [
33+
"C_problem",
34+
"ATOM_CONVERTERS",
35+
"build_variable_dict",
36+
"convert_expr",
37+
"convert_expressions",
38+
]
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""
2+
Copyright 2025, the CVXPY developers
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
17+
"""
18+
Wrapper around C problem struct for CVXPY problems.
19+
"""
20+
21+
import numpy as np
22+
from scipy import sparse
23+
24+
import cvxpy as cp
25+
26+
# Import the low-level C bindings
27+
try:
28+
import dnlp_diff_engine as _diffengine
29+
except ImportError as e:
30+
raise ImportError(
31+
"dnlp-diff-engine is required for NLP solving. "
32+
"Install with: pip install dnlp-diff-engine"
33+
) from e
34+
35+
from cvxpy.reductions.solvers.nlp_solvers.diff_engine.converters import (
36+
build_variable_dict,
37+
convert_expr,
38+
)
39+
40+
41+
class C_problem:
42+
"""Wrapper around C problem struct for CVXPY problems."""
43+
44+
def __init__(self, cvxpy_problem: cp.Problem):
45+
var_dict, n_vars = build_variable_dict(cvxpy_problem.variables())
46+
c_obj = convert_expr(cvxpy_problem.objective.expr, var_dict, n_vars)
47+
c_constraints = [convert_expr(c.expr, var_dict, n_vars) for c in cvxpy_problem.constraints]
48+
self._capsule = _diffengine.make_problem(c_obj, c_constraints)
49+
self._allocated = False
50+
51+
def init_derivatives(self):
52+
"""Initialize derivative structures. Must be called before forward/gradient/jacobian."""
53+
_diffengine.problem_init_derivatives(self._capsule)
54+
self._allocated = True
55+
56+
def objective_forward(self, u: np.ndarray) -> float:
57+
"""Evaluate objective. Returns obj_value float."""
58+
return _diffengine.problem_objective_forward(self._capsule, u)
59+
60+
def constraint_forward(self, u: np.ndarray) -> np.ndarray:
61+
"""Evaluate constraints only. Returns constraint_values array."""
62+
return _diffengine.problem_constraint_forward(self._capsule, u)
63+
64+
def gradient(self) -> np.ndarray:
65+
"""Compute gradient of objective. Call objective_forward first. Returns gradient array."""
66+
return _diffengine.problem_gradient(self._capsule)
67+
68+
def jacobian(self) -> sparse.csr_matrix:
69+
"""Compute constraint Jacobian. Call constraint_forward first."""
70+
data, indices, indptr, shape = _diffengine.problem_jacobian(self._capsule)
71+
return sparse.csr_matrix((data, indices, indptr), shape=shape)
72+
73+
def hessian(self, obj_factor: float, lagrange: np.ndarray) -> sparse.csr_matrix:
74+
"""Compute Lagrangian Hessian.
75+
76+
Computes: obj_factor * H_obj + sum(lagrange_i * H_constraint_i)
77+
78+
Call objective_forward and constraint_forward before this.
79+
80+
Args:
81+
obj_factor: Weight for objective Hessian
82+
lagrange: Array of Lagrange multipliers (length = total_constraint_size)
83+
84+
Returns:
85+
scipy CSR matrix of shape (n_vars, n_vars)
86+
"""
87+
data, indices, indptr, shape = _diffengine.problem_hessian(
88+
self._capsule, obj_factor, lagrange
89+
)
90+
return sparse.csr_matrix((data, indices, indptr), shape=shape)

0 commit comments

Comments
 (0)