Skip to content

Commit 50447d1

Browse files
Transurgeonclaude
andcommitted
Remove unused converters and simplify NLP parameter cache
Remove diag_mat, upper_tri, vstack, and kron converters and their tests. Simplify solve_nlp parameter caching to mirror the best_of pattern: always re-run the full reduction chain, cache only the solver_cache (Oracles) on problem._nlp_cache between solve() calls. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e5fb94e commit 50447d1

2 files changed

Lines changed: 28 additions & 118 deletions

File tree

cvxpy/reductions/solvers/nlp_solvers/diff_engine/converters.py

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -346,53 +346,6 @@ def _convert_diag_vec(expr, children):
346346
raise NotImplementedError("diag_vec with k != 0 not supported in diff engine")
347347
return _diffengine.make_diag_vec(children[0])
348348

349-
def _convert_diag_mat(expr, children):
350-
"""Convert diag_mat: extract diagonal from square matrix."""
351-
if expr.k != 0:
352-
raise NotImplementedError("diag_mat with k != 0 not supported in diff engine")
353-
result = _diffengine.make_diag_mat(children[0])
354-
# C returns (n, 1) but CVXPY shape (n,) normalizes to (1, n)
355-
d1, d2 = normalize_shape(expr.shape)
356-
return _diffengine.make_reshape(result, d1, d2)
357-
358-
def _convert_upper_tri(_expr, children):
359-
"""Convert upper_tri: extract strict upper triangular elements."""
360-
return _diffengine.make_upper_tri(children[0])
361-
362-
def _convert_vstack(_expr, children):
363-
"""Convert vertical stack of expressions."""
364-
return _diffengine.make_vstack(children)
365-
366-
def _convert_kron(expr, children):
367-
"""Convert Kronecker product kron(C, X) or kron(X, C).
368-
369-
Only kron(C, X) with constant left argument is supported natively.
370-
"""
371-
left_arg, right_arg = expr.args
372-
373-
if left_arg.is_constant():
374-
# kron(C, X) -- use native make_kron_left
375-
C = left_arg.value
376-
if sparse.issparse(C):
377-
if not isinstance(C, sparse.csr_matrix):
378-
C = sparse.csr_matrix(C)
379-
else:
380-
C = sparse.csr_matrix(C)
381-
m, n = C.shape
382-
p, q = right_arg.shape
383-
return _diffengine.make_kron_left(
384-
children[1],
385-
C.data.astype(np.float64, copy=False),
386-
C.indices.astype(np.int32, copy=False),
387-
C.indptr.astype(np.int32, copy=False),
388-
m, n, p, q,
389-
)
390-
else:
391-
raise NotImplementedError(
392-
"kron(X, C) with variable left argument is not supported in the diff engine. "
393-
"Only kron(C, X) with constant left argument is currently supported."
394-
)
395-
396349
# Mapping from CVXPY atom names to C diff engine functions
397350
# Converters receive (expr, children) where expr is the CVXPY expression
398351
ATOM_CONVERTERS = {
@@ -443,13 +396,6 @@ def _convert_kron(expr, children):
443396
"Trace": _convert_trace,
444397
# Diagonal
445398
"diag_vec": _convert_diag_vec,
446-
"diag_mat": _convert_diag_mat,
447-
# Upper triangular
448-
"upper_tri": _convert_upper_tri,
449-
# Vertical stack
450-
"Vstack": _convert_vstack,
451-
# Kronecker product
452-
"kron": _convert_kron,
453399
}
454400

455401

cvxpy/reductions/solvers/nlp_solving_chain.py

Lines changed: 28 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from cvxpy.reductions.cvx_attr2constr import CvxAttr2Constr
2121
from cvxpy.reductions.dnlp2smooth.dnlp2smooth import Dnlp2Smooth
2222
from cvxpy.reductions.flip_objective import FlipObjective
23+
from cvxpy.reductions.inverse_data import InverseData
2324
from cvxpy.reductions.solvers.defines import INSTALLED_SOLVERS, NLP_SOLVER_VARIANTS, SOLVER_MAP_NLP
2425
from cvxpy.reductions.solvers.nlp_solvers.diff_engine.converters import build_theta
2526
from cvxpy.reductions.solvers.solving_chain import SolvingChain
@@ -154,6 +155,29 @@ def _set_random_nlp_initial_point(problem, run, user_initials):
154155
var.save_value(initial_val)
155156

156157

158+
def _get_nlp_solver_cache(problem, solver, canon_problem):
159+
"""Return a solver_cache dict for Oracles reuse, or None.
160+
161+
When the problem has parameters and a cached solver_cache exists from a
162+
previous solve, update the C DAG parameter values and return it.
163+
Otherwise return an empty dict (Oracles will be created by solve_via_data)
164+
or None (no parameters).
165+
"""
166+
if not problem.parameters():
167+
return None
168+
169+
nlp_cache = getattr(problem, '_nlp_cache', None)
170+
if nlp_cache is not None and nlp_cache.get('solver') == solver:
171+
solver_cache = nlp_cache['solver_cache']
172+
canon_cvxpy = canon_problem["_bounds"].new_problem
173+
param_inv = InverseData(canon_cvxpy)
174+
theta = build_theta(list(canon_cvxpy.parameters()), param_inv)
175+
solver_cache['oracles'].update_params(theta)
176+
return solver_cache
177+
178+
return {}
179+
180+
157181
def solve_nlp(problem, solver, warm_start, verbose, **kwargs):
158182
"""Solve an NLP problem using the DNLP reduction chain.
159183
@@ -176,80 +200,20 @@ def solve_nlp(problem, solver, warm_start, verbose, **kwargs):
176200
The optimal problem value.
177201
"""
178202
nlp_chain, kwargs = _build_nlp_chain(problem, solver, kwargs)
179-
has_params = len(problem.parameters()) > 0
180203

181-
# Standard single solve
182204
if "best_of" not in kwargs:
183-
# Check for NLP parameter cache (analogous to DPP fast path)
184-
nlp_cache = getattr(problem, '_nlp_cache', None)
185-
186-
# Invalidate cache if solver changed
187-
if nlp_cache is not None and nlp_cache.get('solver') != solver:
188-
nlp_cache = None
189-
problem._nlp_cache = None
190-
191-
if nlp_cache is not None and has_params:
192-
# === FAST PATH: reuse cached C DAG, update parameters only ===
193-
# 1. Update transformed param values in reductions
194-
# (CvxAttr2Constr updates reduced_param.value from original)
195-
for reduction in nlp_cache['chain'].reductions:
196-
reduction.update_parameters(problem)
197-
198-
# 2. Build theta from canonicalized params, push to C DAG
199-
canon_params = nlp_cache['canon_params']
200-
param_inverse_data = nlp_cache['param_inverse_data']
201-
theta = build_theta(canon_params, param_inverse_data)
202-
nlp_cache['solver_cache']['oracles'].update_params(theta)
203-
204-
# 3. Refresh initial point, reuse cached static bounds
205-
_set_nlp_initial_point(problem)
206-
data = dict(nlp_cache['data'])
207-
data["x0"] = nlp_cache['data']["_bounds"].construct_initial_point()
208-
209-
# 4. Solve with cached oracles
210-
solution = nlp_chain.solver.solve_via_data(
211-
data, warm_start, verbose, solver_opts=kwargs,
212-
solver_cache=nlp_cache['solver_cache'])
213-
problem.unpack_results(
214-
solution, nlp_chain, nlp_cache['inverse_data'])
215-
return problem.value
216-
217-
# === SLOW PATH (first solve or no parameters) ===
218205
_set_nlp_initial_point(problem)
219206
canon_problem, inverse_data = nlp_chain.apply(problem=problem)
220207

221-
# Create solver_cache with param-aware Oracles
222-
solver_cache = {}
223-
if has_params:
224-
from cvxpy.reductions.inverse_data import InverseData as ID
225-
from cvxpy.reductions.solvers.nlp_solvers.nlp_solver import Oracles
226-
227-
canon_cvxpy_problem = canon_problem["_bounds"].new_problem
228-
param_inverse_data = ID(canon_cvxpy_problem)
229-
230-
hessian_approx = kwargs.get('hessian_approximation', 'exact')
231-
use_hessian = (hessian_approx == 'exact')
232-
233-
oracles = Oracles(canon_cvxpy_problem, verbose=verbose,
234-
use_hessian=use_hessian,
235-
inverse_data=param_inverse_data)
236-
solver_cache['oracles'] = oracles
208+
solver_cache = _get_nlp_solver_cache(problem, solver, canon_problem)
237209

238210
solution = nlp_chain.solver.solve_via_data(
239211
canon_problem, warm_start, verbose, solver_opts=kwargs,
240-
solver_cache=solver_cache if has_params else None)
212+
solver_cache=solver_cache)
241213

242-
# Cache for subsequent solves with different parameter values
243-
if has_params:
214+
if solver_cache is not None:
244215
problem._nlp_cache = {
245-
'solver': solver,
246-
'chain': nlp_chain,
247-
'solver_cache': solver_cache,
248-
'param_inverse_data': param_inverse_data,
249-
'canon_params': list(canon_cvxpy_problem.parameters()),
250-
'inverse_data': inverse_data,
251-
'data': canon_problem,
252-
}
216+
'solver': solver, 'solver_cache': solver_cache}
253217

254218
problem.unpack_results(solution, nlp_chain, inverse_data)
255219
return problem.value

0 commit comments

Comments
 (0)