|
39 | 39 | from cvxpy.problems.objective import Maximize, Minimize |
40 | 40 | from cvxpy.reductions import InverseData |
41 | 41 | from cvxpy.reductions.chain import Chain |
42 | | -from cvxpy.reductions.cvx_attr2constr import CvxAttr2Constr |
43 | | -from cvxpy.reductions.dnlp2smooth.dnlp2smooth import Dnlp2Smooth |
44 | 42 | from cvxpy.reductions.dqcp2dcp import dqcp2dcp |
45 | 43 | from cvxpy.reductions.eval_params import EvalParams |
46 | 44 | from cvxpy.reductions.flip_objective import FlipObjective |
47 | 45 | from cvxpy.reductions.solution import INF_OR_UNB_MESSAGE |
48 | 46 | from cvxpy.reductions.solvers import bisection |
49 | 47 | from cvxpy.reductions.solvers import defines as slv_def |
50 | | -from cvxpy.reductions.solvers.nlp_solvers.copt_nlpif import COPT as COPT_nlp |
51 | | -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 |
53 | | -from cvxpy.reductions.solvers.nlp_solvers.uno_nlpif import UNO as UNO_nlp |
54 | 48 | from cvxpy.reductions.solvers.solver_inverse_data import SolverInverseData |
55 | 49 | from cvxpy.reductions.solvers.solving_chain import ( |
56 | 50 | SolvingChain, |
@@ -1080,89 +1074,10 @@ def _solve(self, |
1080 | 1074 | return self.value |
1081 | 1075 |
|
1082 | 1076 | if nlp and self.is_dnlp(): |
1083 | | - if type(self.objective) == Maximize: |
1084 | | - reductions = [FlipObjective()] |
1085 | | - else: |
1086 | | - reductions = [] |
1087 | | - reductions = reductions + [CvxAttr2Constr(reduce_bounds=False), Dnlp2Smooth()] |
1088 | | - # instantiate based on user provided solver |
1089 | | - # (default to Ipopt) |
1090 | | - if solver is s.IPOPT or solver is None: |
1091 | | - nlp_reductions = reductions + [IPOPT_nlp()] |
1092 | | - elif "knitro" in solver.lower(): |
1093 | | - if solver == "knitro_ipm": |
1094 | | - kwargs["algorithm"] = 1 |
1095 | | - elif solver == "knitro_sqp": |
1096 | | - kwargs["algorithm"] = 4 |
1097 | | - elif solver == "knitro_alm": |
1098 | | - kwargs["algorithm"] = 6 |
1099 | | - nlp_reductions = reductions + [KNITRO_nlp()] |
1100 | | - elif solver is s.COPT: |
1101 | | - nlp_reductions = reductions + [COPT_nlp()] |
1102 | | - elif "uno" in solver.lower(): |
1103 | | - if solver.lower() == "uno_ipm": |
1104 | | - # Interior-point method (requires MUMPS linear solver) |
1105 | | - kwargs["preset"] = "ipopt" |
1106 | | - kwargs["linear_solver"] = "MUMPS" |
1107 | | - elif solver.lower() == "uno_sqp": |
1108 | | - # SQP method (default) |
1109 | | - kwargs["preset"] = "filtersqp" |
1110 | | - nlp_reductions = reductions + [UNO_nlp()] |
1111 | | - else: |
1112 | | - raise error.SolverError( |
1113 | | - "Solver %s is not supported for NLP problems." % solver |
1114 | | - ) |
1115 | | - # canonicalize disciplined nlp problems to smooth form |
1116 | | - nlp_chain = SolvingChain(reductions=nlp_reductions) |
1117 | | - best_of = kwargs.pop("best_of", 1) |
1118 | | - |
1119 | | - # standard solve |
1120 | | - if best_of == 1: |
1121 | | - self.set_NLP_initial_point() |
1122 | | - canon_problem, inverse_data = nlp_chain.apply(problem=self) |
1123 | | - solution = nlp_chain.solver.solve_via_data(canon_problem, warm_start, |
1124 | | - verbose, solver_opts=kwargs) |
1125 | | - self.unpack_results(solution, nlp_chain, inverse_data) |
1126 | | - return self.value |
1127 | | - # best-of-N solve |
1128 | | - else: |
1129 | | - if (not isinstance(best_of, int)) or best_of < 1: |
1130 | | - raise ValueError("best_of must be a positive integer.") |
1131 | | - |
1132 | | - best_obj, best_solution = float("inf"), None |
1133 | | - all_objs = np.zeros(shape=(best_of,)) |
1134 | | - |
1135 | | - for run in range(best_of): |
1136 | | - print("Starting NLP solve %d of %d" % (run + 1, best_of)) |
1137 | | - self.set_random_NLP_initial_point(run) |
1138 | | - canon_problem, inverse_data = nlp_chain.apply(problem=self) |
1139 | | - solution = nlp_chain.solver.solve_via_data(canon_problem, warm_start, |
1140 | | - verbose, solver_opts=kwargs) |
1141 | | - |
1142 | | - # This gives the objective value of the C problem |
1143 | | - # which can be slightly different from the original NLP |
1144 | | - # so we use the below approach with unpacking. Preferably |
1145 | | - # we would have a way to do this without unpacking. |
1146 | | - #obj_value = canon_problem['objective'](solution['x']) |
1147 | | - |
1148 | | - # set cvxpy variable |
1149 | | - self.unpack_results(solution, nlp_chain, inverse_data) |
1150 | | - obj_value = self.objective.value |
1151 | | - |
1152 | | - all_objs[run] = obj_value |
1153 | | - if obj_value < best_obj: |
1154 | | - best_obj = obj_value |
1155 | | - print("best_obj: ", best_obj) |
1156 | | - best_solution = solution |
1157 | | - |
1158 | | - # unpack best solution |
1159 | | - if type(self.objective) == Maximize: |
1160 | | - all_objs = -all_objs |
1161 | | - |
1162 | | - # propagate all objective values to the user |
1163 | | - best_solution['all_objs_from_best_of'] = all_objs |
1164 | | - self.unpack_results(best_solution, nlp_chain, inverse_data) |
1165 | | - return self.value |
| 1077 | + # Deferred import to avoid circular import: |
| 1078 | + # nlp_solving_chain → dnlp2smooth → cvxpy → problem |
| 1079 | + from cvxpy.reductions.solvers.nlp_solving_chain import solve_nlp |
| 1080 | + return solve_nlp(self, solver, warm_start, verbose, **kwargs) |
1166 | 1081 | elif nlp and not self.is_dnlp(): |
1167 | 1082 | raise error.DNLPError("The problem you specified is not DNLP.") |
1168 | 1083 |
|
@@ -1517,103 +1432,7 @@ def unpack_results(self, solution, chain: SolvingChain, inverse_data) -> None: |
1517 | 1432 | self._solver_stats = SolverStats.from_dict(self._solution.attr, |
1518 | 1433 | chain.solver.name()) |
1519 | 1434 |
|
1520 | | - |
1521 | | - def set_NLP_initial_point(self) -> dict: |
1522 | | - """ Constructs an initial point for the optimization problem. If no |
1523 | | - initial value is specified, look at the bounds. If both lb and ub are |
1524 | | - specified, we initialize the variables to be their midpoints. If only |
1525 | | - one of them is specified, we initialize the variable one unit from |
1526 | | - the bound. If none of them is specified, we initialize it to zero. |
1527 | | - """ |
1528 | | - for var in self.variables(): |
1529 | | - if var.value is not None: |
1530 | | - continue |
1531 | | - |
1532 | | - bounds = var.bounds |
1533 | | - is_nonneg = var.is_nonneg() |
1534 | | - is_nonpos = var.is_nonpos() |
1535 | | - |
1536 | | - if bounds is None: |
1537 | | - if is_nonneg: |
1538 | | - x0 = np.ones(var.shape) |
1539 | | - elif is_nonpos: |
1540 | | - x0 = -np.ones(var.shape) |
1541 | | - else: |
1542 | | - x0 = np.zeros(var.shape) |
1543 | | - |
1544 | | - var.save_value(x0) |
1545 | | - else: |
1546 | | - lb, ub = bounds |
1547 | | - |
1548 | | - if is_nonneg: |
1549 | | - lb = np.maximum(lb, 0) |
1550 | | - elif is_nonpos: |
1551 | | - ub = np.maximum(ub, 0) |
1552 | | - |
1553 | | - lb_finite = np.isfinite(lb) |
1554 | | - ub_finite = np.isfinite(ub) |
1555 | | - # Replace infs with zero for arithmetic |
1556 | | - lb0 = np.where(lb_finite, lb, 0.0) |
1557 | | - ub0 = np.where(ub_finite, ub, 0.0) |
1558 | | - # Midpoint if both finite, one from bound if only one finite, zero if none |
1559 | | - init = (lb_finite * ub_finite * 0.5 * (lb0 + ub0) + |
1560 | | - lb_finite * (~ub_finite) * (lb0 + 1.0) + |
1561 | | - (~lb_finite) * ub_finite * (ub0 - 1.0)) |
1562 | | - # Broadcast to variable shape (handles scalar bounds) |
1563 | | - init = np.broadcast_to(init, var.shape).copy() |
1564 | | - var.save_value(init) |
1565 | | - |
1566 | | - |
1567 | | - def set_random_NLP_initial_point(self, run) -> dict: |
1568 | | - """ Generates a random initial point for DNLP problems. |
1569 | | - A variable is initialized randomly in the following cases: |
1570 | | - 1. 'sample_bounds' is set for that variable. |
1571 | | - 2. the initial value specified by the user is None, |
1572 | | - 'sample_bounds' is not set for that variable, but the |
1573 | | - variable has both finite lower and upper bounds. |
1574 | | - """ |
1575 | 1435 |
|
1576 | | - # store user-specified initial values for variables that do |
1577 | | - # not have sample bounds assigned |
1578 | | - if run == 0: |
1579 | | - self._user_initials = {} |
1580 | | - for var in self.variables(): |
1581 | | - if var.sample_bounds is not None: |
1582 | | - self._user_initials[var.id] = None |
1583 | | - else: |
1584 | | - self._user_initials[var.id] = var.value |
1585 | | - |
1586 | | - for var in self.variables(): |
1587 | | - |
1588 | | - # skip variables with user-specified initial value |
1589 | | - # (note that any variable with sample bounds set will have |
1590 | | - # _user_initials[var.id] == None) |
1591 | | - if self._user_initials[var.id] is not None: |
1592 | | - # reset to user-specified initial value from last solve |
1593 | | - var.value = self._user_initials[var.id] |
1594 | | - continue |
1595 | | - else: |
1596 | | - # reset to None from last solve |
1597 | | - var.value = None |
1598 | | - |
1599 | | - # set sample_bounds to variable bounds if sample_bounds is None |
1600 | | - # and variable bounds (possibly infinite) are set |
1601 | | - if var.sample_bounds is None and var.bounds is not None: |
1602 | | - var.sample_bounds = var.bounds |
1603 | | - |
1604 | | - # sample initial value if sample_bounds is set |
1605 | | - if var.sample_bounds is not None: |
1606 | | - low, high = var.sample_bounds |
1607 | | - if not np.all(np.isfinite(low)) or not np.all(np.isfinite(high)): |
1608 | | - raise ValueError( |
1609 | | - "Variable %s has non-finite sample_bounds %s. Cannot generate" |
1610 | | - " random initial point. Either add sample bounds or set the value. " |
1611 | | - % (var.name(), var.sample_bounds) |
1612 | | - ) |
1613 | | - |
1614 | | - initial_val = np.random.uniform(low=low, high=high, size=var.shape) |
1615 | | - var.save_value(initial_val) |
1616 | | - |
1617 | 1436 | def __str__(self) -> str: |
1618 | 1437 | if len(self.constraints) == 0: |
1619 | 1438 | return str(self.objective) |
|
0 commit comments