2020from cvxpy .reductions .cvx_attr2constr import CvxAttr2Constr
2121from cvxpy .reductions .dnlp2smooth .dnlp2smooth import Dnlp2Smooth
2222from cvxpy .reductions .flip_objective import FlipObjective
23+ from cvxpy .reductions .inverse_data import InverseData
2324from cvxpy .reductions .solvers .defines import INSTALLED_SOLVERS , NLP_SOLVER_VARIANTS , SOLVER_MAP_NLP
2425from cvxpy .reductions .solvers .nlp_solvers .diff_engine .converters import build_theta
2526from 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+
157181def 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