Skip to content

Commit 25c01c4

Browse files
Transurgeonclaude
andcommitted
Address PR review comments on nlp_solving_chain extraction
- Add circular import comment in problem.py explaining the deferred import - Move NLP_SOLVER_VARIANTS from nlp_solving_chain.py to defines.py - Set BOUNDED_VARIABLES = True on NLPsolver base class and use it in _build_nlp_chain (matching the conic solver pattern in solving_chain.py) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 423f4d8 commit 25c01c4

5 files changed

Lines changed: 30 additions & 21 deletions

File tree

cvxpy/problems/problem.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,8 @@ def _solve(self,
10741074
return self.value
10751075

10761076
if nlp and self.is_dnlp():
1077+
# Deferred import to avoid circular import:
1078+
# nlp_solving_chain → dnlp2smooth → cvxpy → problem
10771079
from cvxpy.reductions.solvers.nlp_solving_chain import solve_nlp
10781080
return solve_nlp(self, solver, warm_start, verbose, **kwargs)
10791081
elif nlp and not self.is_dnlp():

cvxpy/reductions/solvers/defines.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
# NLP interfaces
5050
from cvxpy.reductions.solvers.nlp_solvers.copt_nlpif import COPT as COPT_nlp
5151
from cvxpy.reductions.solvers.nlp_solvers.ipopt_nlpif import IPOPT as IPOPT_nlp
52+
from cvxpy.reductions.solvers.nlp_solvers.knitro_nlpif import KNITRO as KNITRO_nlp
5253
from cvxpy.reductions.solvers.nlp_solvers.uno_nlpif import UNO as UNO_nlp
5354

5455
# QP interfaces
@@ -83,14 +84,23 @@
8384
]}
8485

8586
SOLVER_MAP_NLP = {inst.name(): inst for inst in [
86-
IPOPT_nlp(), UNO_nlp(), COPT_nlp(),
87+
IPOPT_nlp(), KNITRO_nlp(), UNO_nlp(), COPT_nlp(),
8788
]}
8889

8990
# Preference-ordered solver name lists, derived from the maps above.
9091
CONIC_SOLVERS = list(SOLVER_MAP_CONIC)
9192
QP_SOLVERS = list(SOLVER_MAP_QP)
9293
NLP_SOLVERS = list(SOLVER_MAP_NLP)
9394

95+
# Solver variants: maps variant name → (base solver name, extra kwargs).
96+
NLP_SOLVER_VARIANTS = {
97+
"knitro_ipm": ("KNITRO", {"algorithm": 1}),
98+
"knitro_sqp": ("KNITRO", {"algorithm": 4}),
99+
"knitro_alm": ("KNITRO", {"algorithm": 6}),
100+
"uno_ipm": ("UNO", {"preset": "ipopt", "linear_solver": "MUMPS"}),
101+
"uno_sqp": ("UNO", {"preset": "filtersqp"}),
102+
}
103+
94104
# Mixed-integer solver lists, derived from solver class attributes.
95105
MI_SOLVERS = [
96106
name for name, slv in SOLVER_MAP_CONIC.items() if slv.MIP_CAPABLE

cvxpy/reductions/solvers/nlp_solvers/knitro_nlpif.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ class KNITRO(NLPsolver):
2727
NLP interface for the KNITRO solver
2828
"""
2929

30-
BOUNDED_VARIABLES = True
3130
# Keys:
3231
CONTEXT_KEY = "context"
3332
X_INIT_KEY = "x_init"

cvxpy/reductions/solvers/nlp_solvers/nlp_solver.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class NLPsolver(Solver):
3636
"""
3737
REQUIRES_CONSTR = False
3838
MIP_CAPABLE = False
39+
BOUNDED_VARIABLES = True
3940

4041
def accepts(self, problem):
4142
"""

cvxpy/reductions/solvers/nlp_solving_chain.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,52 +20,49 @@
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.solvers.defines import INSTALLED_SOLVERS, SOLVER_MAP_NLP
23+
from cvxpy.reductions.solvers.defines import INSTALLED_SOLVERS, NLP_SOLVER_VARIANTS, SOLVER_MAP_NLP
2424
from cvxpy.reductions.solvers.solving_chain import SolvingChain
2525

26-
# Solver variants: maps variant name → (base solver name, extra kwargs).
27-
NLP_SOLVER_VARIANTS = {
28-
"knitro_ipm": ("KNITRO", {"algorithm": 1}),
29-
"knitro_sqp": ("KNITRO", {"algorithm": 4}),
30-
"knitro_alm": ("KNITRO", {"algorithm": 6}),
31-
"uno_ipm": ("UNO", {"preset": "ipopt", "linear_solver": "MUMPS"}),
32-
"uno_sqp": ("UNO", {"preset": "filtersqp"}),
33-
}
34-
3526

3627
def _build_nlp_chain(problem, solver, kwargs):
3728
"""Build the NLP reduction chain and return (SolvingChain, kwargs).
3829
3930
Solver selection may mutate kwargs (e.g., Knitro algorithm, Uno preset).
4031
"""
41-
if type(problem.objective) == Maximize:
42-
reductions = [FlipObjective()]
43-
else:
44-
reductions = []
45-
reductions = reductions + [CvxAttr2Constr(reduce_bounds=False), Dnlp2Smooth()]
46-
32+
# Resolve the solver instance.
4733
if solver is None:
4834
# Pick first installed NLP solver in preference order.
4935
for name, inst in SOLVER_MAP_NLP.items():
5036
if name in INSTALLED_SOLVERS:
51-
reductions.append(inst)
37+
solver_instance = inst
5238
break
5339
else:
5440
raise error.SolverError(
5541
"No NLP solver is installed. Install one of: %s"
5642
% ", ".join(SOLVER_MAP_NLP)
5743
)
5844
elif solver in SOLVER_MAP_NLP:
59-
reductions.append(SOLVER_MAP_NLP[solver])
45+
solver_instance = SOLVER_MAP_NLP[solver]
6046
elif solver.lower() in NLP_SOLVER_VARIANTS:
6147
base_name, variant_kwargs = NLP_SOLVER_VARIANTS[solver.lower()]
6248
kwargs.update(variant_kwargs)
63-
reductions.append(SOLVER_MAP_NLP[base_name])
49+
solver_instance = SOLVER_MAP_NLP[base_name]
6450
else:
6551
raise error.SolverError(
6652
"Solver %s is not supported for NLP problems." % solver
6753
)
6854

55+
# Build the reduction chain.
56+
if type(problem.objective) == Maximize:
57+
reductions = [FlipObjective()]
58+
else:
59+
reductions = []
60+
reductions += [
61+
CvxAttr2Constr(reduce_bounds=not solver_instance.BOUNDED_VARIABLES),
62+
Dnlp2Smooth(),
63+
solver_instance,
64+
]
65+
6966
return SolvingChain(reductions=reductions), kwargs
7067

7168

0 commit comments

Comments
 (0)