Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e5fb94e
Add parameter support to DNLP diff engine
Transurgeon Mar 27, 2026
50447d1
Remove unused converters and simplify NLP parameter cache
Transurgeon Mar 27, 2026
9b8bd33
Simplify parameter plumbing: replace InverseData with param_id_map
Transurgeon Mar 27, 2026
d0f8b9b
Remove test_nlp_parameters.py for now
Transurgeon Mar 27, 2026
b6b81cd
Clean up converters: add ConvertContext, uniform converter signatures
Transurgeon Mar 27, 2026
cb029d0
Keep original (expr, children) converter signatures
Transurgeon Mar 27, 2026
b2d88c1
Restore original converter signatures, minimal parameter changes
Transurgeon Mar 27, 2026
dcf1de0
Move parameter update into solve_via_data, use _solver_cache['NLP']
Transurgeon Mar 27, 2026
475b2d1
Clean up parameter update: inline into Oracles, add to all NLP solvers
Transurgeon Mar 27, 2026
1dabb25
adds tests for parameters
Transurgeon Mar 27, 2026
ac8781e
Add parameter tests, initial value init, update_params in all solvers
Transurgeon Mar 27, 2026
ba39a65
Always pass param_node to matmul helpers (required by new SparseDiffPy)
Transurgeon Mar 27, 2026
22851ec
Add diffengine as DCP canonicalization backend with parameter support
Transurgeon Mar 28, 2026
6ea8046
fix ordering for matrices for diffengine cone
Transurgeon Mar 29, 2026
22bb5e6
Replace ConvertContext with flat functions for converter state
Transurgeon Mar 29, 2026
918b1cc
Update CLAUDE.md with DNLP architecture documentation
Transurgeon Mar 29, 2026
97b08bb
Split converters.py into helpers.py, registry.py, and converters.py
Transurgeon Mar 29, 2026
cde8bc0
Fix lint and remove section comment banners
Transurgeon Mar 29, 2026
1ef159e
chnages from code review with dance
Transurgeon Mar 30, 2026
772edf9
add test for right elementwise multiply, which forces an argument ord…
Transurgeon Mar 30, 2026
463576a
minor edits to parameters
dance858 Mar 30, 2026
46dfd46
adding test for parametrized least-squares vs. clarabel
Transurgeon Apr 1, 2026
9652584
Merge branch 'master' into feat/dnlp-parameter-support
Transurgeon Apr 6, 2026
c47b3fe
make three more legit parameter tests
Transurgeon Apr 6, 2026
4de6775
Merge branch 'master' into feat/diffengine-dcp-backend
Transurgeon Apr 7, 2026
712f3da
Merge feat/dnlp-parameter-support and update DCP backend to new API
Transurgeon Apr 8, 2026
39cb925
remove generated tests
Transurgeon Apr 8, 2026
c616d0e
refactor: extract shared build_capsule helper and clean up DCP backend
Transurgeon Apr 8, 2026
a865699
clean up: remove NonPos handling, rename backend constant, drop lazy …
Transurgeon Apr 8, 2026
c539c57
simplify DiffengineConeProgram: derive fields from inverse_data
Transurgeon Apr 8, 2026
9707aa1
simplify: remove unnecessary else branch in parameter init
Transurgeon Apr 8, 2026
595c7d7
clean up diffengine_cone_program: imports, aliases, unused vars
Transurgeon Apr 8, 2026
fb95d13
simplify apply_parameters: remove x0 caching, streamline P return
Transurgeon Apr 8, 2026
08b741c
move build_diffengine_cone_program into from_problem classmethod
Transurgeon Apr 8, 2026
1246a1e
slim down DiffengineConeProgram: remove unnecessary properties
Transurgeon Apr 8, 2026
a094014
make stored matrices and inverse_data public attributes
Transurgeon Apr 8, 2026
e021de7
Merge branch 'master' into feat/diffengine-dcp-backend
Transurgeon Apr 11, 2026
d332b9a
cleanup the docstrings a bit
Transurgeon Apr 11, 2026
2b53a37
revert changes to nlp path and add custom path in diffengine-cone-prog
Transurgeon Apr 11, 2026
2dffe26
adds div and other atoms to registry
Transurgeon Apr 12, 2026
a2ae719
adds converters for new atoms
Transurgeon Apr 13, 2026
33723f8
some fixes, like returning csc, and correct hessian
Transurgeon Apr 15, 2026
944eb91
add skew_symmetric_wrap and symmetric_wrap to ATOM_CONVERTERS
Transurgeon Apr 15, 2026
b8d716a
fix diff engine converter bugs: 1D matmul dims, reshape, transpose, q…
Transurgeon Apr 22, 2026
9a7cc43
Merge branch 'master' into feat/diffengine-dcp-backend
Transurgeon Apr 29, 2026
6919754
Remove local sparsediffpy source override from pyproject.toml
Transurgeon Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions cvxpy/cvxcore/python/canonInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,13 @@ def get_problem_matrix(linOps,
default_canon_backend = get_default_canon_backend()
canon_backend = default_canon_backend if not canon_backend else canon_backend

# DIFFENGINE is a reduction-layer replacement for ConeMatrixStuffing and
# has no lin_ops backend of its own. When code paths reach this matrix
# builder directly (e.g. tests that bypass the stuffing reduction), fall
# through to the CPP backend so the lin_ops pipeline still works.
if canon_backend == s.DIFFENGINE_BACKEND:
canon_backend = s.CPP_CANON_BACKEND

if canon_backend == s.CPP_CANON_BACKEND:
from cvxpy.cvxcore.python.cppbackend import build_matrix
return build_matrix(id_to_col, param_to_size, param_to_col, var_length, constr_length, linOps)
Expand Down
20 changes: 16 additions & 4 deletions cvxpy/reductions/dcp2cone/canonicalizers/perspective_canon.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,22 @@ def perspective_canon(expr, args, solver_context: SolverInfo | None = None):
prob_canon = chain.apply(aux_prob)[0] # grab problem instance
# get cone representation of c, A, and b for some problem.

q = prob_canon.q.toarray().flatten()[:-1]
d = prob_canon.q.toarray().flatten()[-1]
Ab = prob_canon.A.toarray().reshape((-1, len(q) + 1), order="F")
A, b = Ab[:, :-1], Ab[:, -1]
# Extract cone representation q, d, A, b.
# ParamConeProg stores q as sparse (n+1, n_params+1) tensor with d
# embedded in the last row, and A as a flattened tensor with b embedded.
# DiffengineConeProgram stores q, d, A, b as separate concrete arrays.
if hasattr(prob_canon, 'd') and not hasattr(prob_canon.q, 'toarray'):
# DiffengineConeProgram: concrete matrices, d stored separately.
q = prob_canon.q
d = prob_canon.d
A = prob_canon.A.toarray() if hasattr(prob_canon.A, 'toarray') else prob_canon.A
b = prob_canon.b
else:
# ParamConeProg: sparse tensor with embedded offsets.
q = prob_canon.q.toarray().flatten()[:-1]
d = prob_canon.q.toarray().flatten()[-1]
Ab = prob_canon.A.toarray().reshape((-1, len(q) + 1), order="F")
A, b = Ab[:, :-1], Ab[:, -1]

# given f in epigraph form, aka epi f = \{(x,t) | f(x) \leq t\}
# = \{(x,t) | Fx +tg + e \in K} for K a cone, the epigraph of the
Expand Down
153 changes: 113 additions & 40 deletions cvxpy/reductions/dcp2cone/cone_matrix_stuffing.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,53 @@ def split_solution(self, sltn, active_vars=None):
value, var.shape, order='F')
return sltn_dict

def apply_restruct_mat(self, restruct_mat, restruct_mat_op):
"""Apply restructuring matrix to parametric tensor A.

Parameters
----------
restruct_mat : list
List of sparse matrices or linear operators (unused for this path).
restruct_mat_op : LinearOperator
Block diagonal linear operator for restructuring.

Returns
-------
ParamConeProg
New program with restructured A tensor.
"""
if restruct_mat_op is not None:
unspecified, _ = np.divmod(self.A.shape[0] * self.A.shape[1],
restruct_mat_op.shape[1], dtype=np.int64)
reshaped_A = self.A.reshape(restruct_mat_op.shape[1],
unspecified, order='F').tocsr()
restructured_A = restruct_mat_op(reshaped_A).tocoo()
# Because of a bug in scipy versions < 1.20, `reshape`
# can overflow if indices are int32s.
restructured_A.row = restructured_A.row.astype(np.int64)
restructured_A.col = restructured_A.col.astype(np.int64)
restructured_A = restructured_A.reshape(
np.int64(restruct_mat_op.shape[0]) * (np.int64(self.x.size) + 1),
self.A.shape[1], order='F')
else:
restructured_A = self.A
return ParamConeProg(
self.q,
self.x,
restructured_A,
self.variables,
self.var_id_to_col,
self.constraints,
self.parameters,
self.param_id_to_col,
P=self.P,
formatted=True,
lower_bounds=self.lower_bounds,
upper_bounds=self.upper_bounds,
lb_tensor=self.lb_tensor,
ub_tensor=self.ub_tensor,
)

def split_adjoint(self, del_vars=None):
"""Adjoint of split_solution.
"""
Expand All @@ -343,6 +390,65 @@ def split_adjoint(self, del_vars=None):
return var_vec


def lower_and_order_constraints(constraints):
"""Lower equality/inequality constraints and reorder by cone type.

Converts Equality -> Zero, Inequality -> NonNeg, and normalizes
SOC/PowCone/ExpCone axes. Returns constraints ordered as:
Zero, NonNeg, SOC, PSD, ExpCone, PowCone3D, PowConeND.

Parameters
----------
constraints : list
The problem constraints to lower and reorder.

Returns
-------
ordered_cons : list
The lowered and reordered constraints.
cons_id_map : dict
Mapping from constraint id to constraint id (identity map).
"""
cons = []
for con in constraints:
if isinstance(con, Equality):
con = lower_equality(con)
elif isinstance(con, Inequality):
con = lower_ineq_to_nonneg(con)
elif isinstance(con, SOC) and con.axis == 1:
con = SOC(con.args[0], con.args[1].T, axis=0,
constr_id=con.constr_id)
elif isinstance(con, PowCone3D) and con.args[0].ndim > 1:
x, y, z = con.args
alpha = con.alpha
con = PowCone3D(x.flatten(order='F'),
y.flatten(order='F'),
z.flatten(order='F'),
alpha.flatten(order='F'),
constr_id=con.constr_id)
elif isinstance(con, PowConeND) and con.axis == 1:
alpha = con.alpha.T
W = con.W.T
con = PowConeND(W, con.z.flatten(order='F'),
alpha,
axis=0,
constr_id=con.constr_id)
elif isinstance(con, ExpCone) and con.args[0].ndim > 1:
x, y, z = con.args
con = ExpCone(x.flatten(order='F'), y.flatten(order='F'),
z.flatten(order='F'),
constr_id=con.constr_id)
cons.append(con)

constr_map = group_constraints(cons)
ordered_cons = constr_map[Zero] + constr_map[NonNeg] + \
constr_map[SOC] + constr_map.get(PSD, []) + \
constr_map.get(SvecPSD, []) + constr_map[ExpCone] + \
constr_map[PowCone3D] + constr_map[PowConeND]
cons_id_map = {con.id: con.id for con in ordered_cons}
return ordered_cons, cons_id_map


class ConeMatrixStuffing(MatrixStuffing):
"""Construct matrices for linear cone problems.

Expand Down Expand Up @@ -384,52 +490,19 @@ def stuffed_objective(self, problem, extractor):

def apply(self, problem):
inverse_data = InverseData(problem)
# Lower equality and inequality to Zero and NonNeg.
cons = []
for con in problem.constraints:
if isinstance(con, Equality):
con = lower_equality(con)
elif isinstance(con, Inequality):
con = lower_ineq_to_nonneg(con)
elif isinstance(con, SOC) and con.axis == 1:
con = SOC(con.args[0], con.args[1].T, axis=0,
constr_id=con.constr_id)
elif isinstance(con, PowCone3D) and con.args[0].ndim > 1:
x, y, z = con.args
alpha = con.alpha
con = PowCone3D(x.flatten(order='F'),
y.flatten(order='F'),
z.flatten(order='F'),
alpha.flatten(order='F'),
constr_id=con.constr_id)
elif isinstance(con, PowConeND) and con.axis == 1:
alpha = con.alpha.T
W = con.W.T
con = PowConeND(W, con.z.flatten(order='F'),
alpha,
axis=0,
constr_id=con.constr_id)
elif isinstance(con, ExpCone) and con.args[0].ndim > 1:
x, y, z = con.args
con = ExpCone(x.flatten(order='F'), y.flatten(order='F'), z.flatten(order='F'),
constr_id=con.constr_id)
cons.append(con)
# Lower equality and inequality constraints and reorder by cone type.
ordered_cons, cons_id_map = lower_and_order_constraints(problem.constraints)
inverse_data.cons_id_map = cons_id_map
inverse_data.constraints = ordered_cons

# Need to check that intended canonicalization backend still works.
lowered_con_problem = problem.copy([problem.objective, cons])
lowered_con_problem = problem.copy(
[problem.objective, ordered_cons])
canon_backend = get_canon_backend(lowered_con_problem, self.canon_backend)
# Form the constraints
extractor = CoeffExtractor(inverse_data, canon_backend)
params_to_P, params_to_c, flattened_variable = self.stuffed_objective(
problem, extractor)
# Reorder constraints to Zero, NonNeg, SOC, PSD, EXP, PowCone3D, PowConeND
constr_map = group_constraints(cons)
ordered_cons = constr_map[Zero] + constr_map[NonNeg] + \
constr_map[SOC] + constr_map.get(PSD, []) + \
constr_map.get(SvecPSD, []) + constr_map[ExpCone] + \
constr_map[PowCone3D] + constr_map[PowConeND]
inverse_data.cons_id_map = {con.id: con.id for con in ordered_cons}

inverse_data.constraints = ordered_cons
# Batch expressions together, then split apart.
expr_list = [arg for c in ordered_cons for arg in c.args]
params_to_problem_data = extractor.affine(expr_list)
Expand Down
Loading
Loading