def test_pyomo_external_model(self): m = pyo.ConcreteModel() m.Pin = pyo.Var(initialize=100, bounds=(0,None)) m.c1 = pyo.Var(initialize=1.0, bounds=(0,None)) m.c2 = pyo.Var(initialize=1.0, bounds=(0,None)) m.F = pyo.Var(initialize=10, bounds=(0,None)) m.P1 = pyo.Var() m.P2 = pyo.Var() m.F_con = pyo.Constraint(expr = m.F == 10) m.Pin_con = pyo.Constraint(expr = m.Pin == 100) # simple parameter estimation test m.obj = pyo.Objective(expr= (m.P1 - 90)**2 + (m.P2 - 40)**2) cyipopt_problem = \ PyomoExternalCyIpoptProblem(m, PressureDropModel(), [m.Pin, m.c1, m.c2, m.F], [m.P1, m.P2] ) # check that the dummy variable is initialized expected_dummy_var_value = pyo.value(m.Pin) + pyo.value(m.c1) + pyo.value(m.c2) + pyo.value(m.F) \ + 0 + 0 # + pyo.value(m.P1) + pyo.value(m.P2) # not initialized - therefore should use zero self.assertAlmostEqual(pyo.value(m._dummy_variable_CyIpoptPyomoExNLP), expected_dummy_var_value) # solve the problem solver = CyIpoptSolver(cyipopt_problem, {'hessian_approximation':'limited-memory'}) x, info = solver.solve(tee=False) cyipopt_problem.load_x_into_pyomo(x) self.assertAlmostEqual(pyo.value(m.c1), 0.1, places=5) self.assertAlmostEqual(pyo.value(m.c2), 0.5, places=5)
def test_options(self): model = create_model1() nlp = PyomoNLP(model) solver = CyIpoptSolver(CyIpoptNLP(nlp), options={'max_iter': 1}) x, info = solver.solve(tee=False) nlp.set_primals(x) self.assertAlmostEqual(nlp.evaluate_objective(), -5.0879028e+02, places=5)
def test_composite_nlp(self): G = np.array([[6, 2, 1], [2, 5, 2], [1, 2, 4]]) A = np.array([[1, 0, 1], [0, 1, 1]]) b = np.array([3, 0]) c = np.array([-8, -3, -3]) scenarios = dict() coupling_vars = dict() n_scenarios = 2 np.random.seed(seed=985739465) bs = [b, b + 0.001] for i in range(n_scenarios): instance = create_model3(G, A, bs[i], c) nlp = PyomoNLP(instance) scenario_name = "s{}".format(i) scenarios[scenario_name] = nlp coupling_vars[scenario_name] = [nlp.variable_idx(instance.x[0])] nlp = TwoStageStochasticNLP(scenarios, coupling_vars) solver = CyIpoptSolver(nlp) x, info = solver.solve(tee=False) x_sol = np.array([ 2.00003846, -0.99996154, 0.99996154, 2.00003846, -0.99996154, 1.00096154, 2.00003846 ]) self.assertTrue(np.allclose(x, x_sol, rtol=1e-4)) self.assertAlmostEqual(nlp.objective(x), -6.99899, 3)
def test_model1_with_scaling(self): m = create_model1() m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) m.scaling_factor[m.o] = 1e-6 # scale the objective m.scaling_factor[m.c] = 2.0 # scale the equality constraint m.scaling_factor[m.d] = 3.0 # scale the inequality constraint m.scaling_factor[m.x[1]] = 4.0 # scale one of the x variables cynlp = CyIpoptNLP(PyomoNLP(m)) options={'nlp_scaling_method': 'user-scaling', 'output_file': '_cyipopt-scaling.log', 'file_print_level':10, 'max_iter': 0} solver = CyIpoptSolver(cynlp, options=options) x, info = solver.solve() with open('_cyipopt-scaling.log', 'r') as fd: solver_trace = fd.read() os.remove('_cyipopt-scaling.log') # check for the following strings in the log and then delete the log self.assertIn('nlp_scaling_method = user-scaling', solver_trace) self.assertIn('output_file = _cyipopt-scaling.log', solver_trace) self.assertIn('objective scaling factor = 1e-06', solver_trace) self.assertIn('x scaling provided', solver_trace) self.assertIn('c scaling provided', solver_trace) self.assertIn('d scaling provided', solver_trace) self.assertIn('DenseVector "x scaling vector" with 3 elements:', solver_trace) self.assertIn('x scaling vector[ 1]= 1.0000000000000000e+00', solver_trace) self.assertIn('x scaling vector[ 2]= 1.0000000000000000e+00', solver_trace) self.assertIn('x scaling vector[ 3]= 4.0000000000000000e+00', solver_trace) self.assertIn('DenseVector "c scaling vector" with 1 elements:', solver_trace) self.assertIn('c scaling vector[ 1]= 2.0000000000000000e+00', solver_trace) self.assertIn('DenseVector "d scaling vector" with 1 elements:', solver_trace) self.assertIn('d scaling vector[ 1]= 3.0000000000000000e+00', solver_trace)
def test_model2(self): model = create_model2() nlp = PyomoNLP(model) solver = CyIpoptSolver(nlp) x, info = solver.solve(tee=False) x_sol = np.array([3.0, 1.99997807]) y_sol = np.array([0.00017543]) self.assertTrue(np.allclose(x, x_sol, rtol=1e-4)) self.assertAlmostEqual(nlp.objective(x), -31.000000057167462, 3) self.assertTrue(np.allclose(info['mult_g'], y_sol, rtol=1e-4))
def test_model1(self): model = create_model1() nlp = PyomoNLP(model) solver = CyIpoptSolver(nlp) x, info = solver.solve(tee=False) x_sol = np.array([3.85958688, 4.67936007, 3.10358931]) y_sol = np.array([-1.0, 53.90357665]) self.assertTrue(np.allclose(x, x_sol, rtol=1e-4)) self.assertAlmostEqual(nlp.objective(x), -428.6362455416348) self.assertTrue(np.allclose(info['mult_g'], y_sol, rtol=1e-4))
def test_cyipopt_callback(self): # Use a callback to check that the reported infeasibility is # due to the scaled equality constraints. # Note that the scaled infeasibility is not what we see if we # call solve with tee=True, as by default the displayed infeasibility # is unscaled. Luckily, we can still access the scaled infeasibility # with a callback. m = self.make_model() scaling_factors = [1e-4, 1e4] m.epm.set_equality_constraint_scaling_factors(scaling_factors) nlp = PyomoNLPWithGreyBoxBlocks(m) def callback( local_nlp, alg_mod, iter_count, obj_value, inf_pr, inf_du, mu, d_norm, regularization_size, alpha_du, alpha_pr, ls_trials, ): primals = tuple(local_nlp.get_primals()) # I happen to know the order of the primals here u, v, x, y = primals # Calculate the scaled residuals I expect con_3_resid = scaling_factors[0] * abs( self.con_3_body(x, y, u, v) - self.con_3_rhs()) con_4_resid = scaling_factors[1] * abs( self.con_4_body(x, y, u, v) - self.con_4_rhs()) pred_inf_pr = max(con_3_resid, con_4_resid) # Make sure Ipopt is using the scaled constraints internally self.assertAlmostEqual(inf_pr, pred_inf_pr) cyipopt_nlp = CyIpoptNLP( nlp, intermediate_callback=callback, ) x0 = nlp.get_primals() cyipopt = CyIpoptSolver( cyipopt_nlp, options={ "max_iter": 0, "nlp_scaling_method": "user-scaling", }, ) cyipopt.solve(x0=x0)
def test_model2(self): model = create_model2() nlp = PyomoNLP(model) solver = CyIpoptSolver(CyIpoptNLP(nlp)) x, info = solver.solve(tee=False) x_sol = np.array([3.0, 1.99997807]) y_sol = np.array([0.00017543]) self.assertTrue(np.allclose(x, x_sol, rtol=1e-4)) nlp.set_primals(x) nlp.set_duals(y_sol) self.assertAlmostEqual(nlp.evaluate_objective(), -31.000000057167462, places=5) self.assertTrue(np.allclose(info['mult_g'], y_sol, rtol=1e-4))
def test_model3(self): G = np.array([[6, 2, 1], [2, 5, 2], [1, 2, 4]]) A = np.array([[1, 0, 1], [0, 1, 1]]) b = np.array([3, 0]) c = np.array([-8, -3, -3]) model = create_model3(G, A, b, c) nlp = PyomoNLP(model) solver = CyIpoptSolver(nlp) x, info = solver.solve(tee=False) x_sol = np.array([2.0, -1.0, 1.0]) y_sol = np.array([-3., 2.]) self.assertTrue(np.allclose(x, x_sol, rtol=1e-4)) self.assertAlmostEqual(nlp.objective(x), -3.5, 3) self.assertTrue(np.allclose(info['mult_g'], y_sol, rtol=1e-4))
def test_pyomo_external_model_ndarray_scaling(self): m = pyo.ConcreteModel() m.Pin = pyo.Var(initialize=100, bounds=(0, None)) m.c1 = pyo.Var(initialize=1.0, bounds=(0, None)) m.c2 = pyo.Var(initialize=1.0, bounds=(0, None)) m.F = pyo.Var(initialize=10, bounds=(0, None)) m.P1 = pyo.Var() m.P2 = pyo.Var() m.F_con = pyo.Constraint(expr=m.F == 10) m.Pin_con = pyo.Constraint(expr=m.Pin == 100) # simple parameter estimation test m.obj = pyo.Objective(expr=(m.P1 - 90)**2 + (m.P2 - 40)**2) # set scaling parameters for the pyomo variables and constraints m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT) m.scaling_factor[m.obj] = 0.1 # scale the objective m.scaling_factor[m.Pin] = 2.0 # scale the variable m.scaling_factor[m.c1] = 3.0 # scale the variable m.scaling_factor[m.c2] = 4.0 # scale the variable m.scaling_factor[m.F] = 5.0 # scale the variable m.scaling_factor[m.P1] = 6.0 # scale the variable m.scaling_factor[m.P2] = 7.0 # scale the variable m.scaling_factor[m.F_con] = 8.0 # scale the pyomo constraint m.scaling_factor[m.Pin_con] = 9.0 # scale the pyomo constraint # test that this all works with ndarray input as well cyipopt_problem = \ PyomoExternalCyIpoptProblem(pyomo_model=m, ex_input_output_model=PressureDropModel(), inputs=[m.Pin, m.c1, m.c2, m.F], outputs=[m.P1, m.P2], outputs_eqn_scaling=np.asarray([10.0, 11.0], dtype=np.float64) ) # solve the problem options = { 'hessian_approximation': 'limited-memory', 'nlp_scaling_method': 'user-scaling', 'output_file': '_cyipopt-pyomo-ext-scaling-ndarray.log', 'file_print_level': 10, 'max_iter': 0 } solver = CyIpoptSolver(cyipopt_problem, options=options) x, info = solver.solve(tee=False) with open('_cyipopt-pyomo-ext-scaling-ndarray.log', 'r') as fd: solver_trace = fd.read() os.remove('_cyipopt-pyomo-ext-scaling-ndarray.log') self.assertIn('nlp_scaling_method = user-scaling', solver_trace) self.assertIn('output_file = _cyipopt-pyomo-ext-scaling-ndarray.log', solver_trace) self.assertIn('objective scaling factor = 0.1', solver_trace) self.assertIn('x scaling provided', solver_trace) self.assertIn('c scaling provided', solver_trace) self.assertIn('d scaling provided', solver_trace) self.assertIn('DenseVector "x scaling vector" with 7 elements:', solver_trace) self.assertIn('x scaling vector[ 1]= 6.0000000000000000e+00', solver_trace) self.assertIn('x scaling vector[ 2]= 7.0000000000000000e+00', solver_trace) self.assertIn('x scaling vector[ 3]= 2.0000000000000000e+00', solver_trace) self.assertIn('x scaling vector[ 4]= 3.0000000000000000e+00', solver_trace) self.assertIn('x scaling vector[ 5]= 4.0000000000000000e+00', solver_trace) self.assertIn('x scaling vector[ 6]= 5.0000000000000000e+00', solver_trace) self.assertIn('x scaling vector[ 7]= 1.0000000000000000e+00', solver_trace) self.assertIn('DenseVector "c scaling vector" with 5 elements:', solver_trace) self.assertIn('c scaling vector[ 1]= 8.0000000000000000e+00', solver_trace) self.assertIn('c scaling vector[ 2]= 9.0000000000000000e+00', solver_trace) self.assertIn('c scaling vector[ 3]= 1.0000000000000000e+00', solver_trace) self.assertIn('c scaling vector[ 4]= 1.0000000000000000e+01', solver_trace) self.assertIn('c scaling vector[ 5]= 1.1000000000000000e+01', solver_trace)
def __init__( self, input_vars, external_vars, residual_cons, external_cons, use_cyipopt=None, solver=None, ): """ Arguments: ---------- input_vars: list List of variables sent to this system by the outer solver external_vars: list List of variables that are solved for internally by this system residual_cons: list List of equality constraints whose residuals are exposed to the outer solver external_cons: list List of equality constraints used to solve for the external variables use_cyipopt: bool Whether to use CyIpopt to solve strongly connected components of the implicit function that have dimension greater than one. solver: Pyomo solver object Used to solve strongly connected components of the implicit function that have dimension greater than one. Only used if use_cyipopt is False. """ if use_cyipopt is None: use_cyipopt = cyipopt_available if use_cyipopt and not cyipopt_available: raise RuntimeError( "Constructing an ExternalPyomoModel with CyIpopt unavailable. " "Please set the use_cyipopt argument to False.") if solver is not None and use_cyipopt: raise RuntimeError( "Constructing an ExternalPyomoModel with a solver specified " "and use_cyipopt set to True. Please set use_cyipopt to False " "to use the desired solver.") elif solver is None and not use_cyipopt: solver = SolverFactory("ipopt") # If use_cyipopt is True, this solver is None and will not be used. self._solver = solver self._use_cyipopt = use_cyipopt # We only need this block to construct the NLP, which wouldn't # be necessary if we could compute Hessians of Pyomo constraints. self._block = create_subsystem_block( residual_cons + external_cons, input_vars + external_vars, ) self._block._obj = Objective(expr=0.0) self._nlp = PyomoNLP(self._block) self._scc_list = list( generate_strongly_connected_components(external_cons, variables=external_vars)) if use_cyipopt: # Using CyIpopt allows us to solve inner problems without # costly rewriting of the nl file. It requires quite a bit # of preprocessing, however, to construct the ProjectedNLP # for each block of the decomposition. # Get "vector-valued" SCCs, those of dimension > 0. # We will solve these with a direct IPOPT interface, which requires # some preprocessing. self._vector_scc_list = [(scc, inputs) for scc, inputs in self._scc_list if len(scc.vars) > 1] # Need a dummy objective to create an NLP for scc, inputs in self._vector_scc_list: scc._obj = Objective(expr=0.0) # I need scaling_factor so Pyomo NLPs I create from these blocks # don't break when ProjectedNLP calls get_primals_scaling scc.scaling_factor = Suffix(direction=Suffix.EXPORT) # HACK: scaling_factor just needs to be nonempty. scc.scaling_factor[scc._obj] = 1.0 # These are the "original NLPs" that will be projected self._vector_scc_nlps = [ PyomoNLP(scc) for scc, inputs in self._vector_scc_list ] self._vector_scc_var_names = [[ var.name for var in scc.vars.values() ] for scc, inputs in self._vector_scc_list] self._vector_proj_nlps = [ ProjectedNLP(nlp, names) for nlp, names in zip( self._vector_scc_nlps, self._vector_scc_var_names) ] # We will solve the ProjectedNLPs rather than the original NLPs self._cyipopt_nlps = [ CyIpoptNLP(nlp) for nlp in self._vector_proj_nlps ] self._cyipopt_solvers = [ CyIpoptSolver(nlp) for nlp in self._cyipopt_nlps ] self._vector_scc_input_coords = [ nlp.get_primal_indices(inputs) for nlp, (scc, inputs) in zip( self._vector_scc_nlps, self._vector_scc_list) ] assert len(external_vars) == len(external_cons) self.input_vars = input_vars self.external_vars = external_vars self.residual_cons = residual_cons self.external_cons = external_cons self.residual_con_multipliers = [None for _ in residual_cons] self.residual_scaling_factors = None