1# ___________________________________________________________________________ 2# 3# Pyomo: Python Optimization Modeling Objects 4# Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC 5# Under the terms of Contract DE-NA0003525 with National Technology and 6# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain 7# rights in this software. 8# This software is distributed under the 3-clause BSD License. 9# ___________________________________________________________________________ 10 11"""Utility functions and classes for the MindtPy solver.""" 12from __future__ import division 13import logging 14from pyomo.common.collections import ComponentMap, Bunch 15from pyomo.core import (Block, Constraint, 16 Objective, Reals, Suffix, Var, minimize, RangeSet, ConstraintList, TransformationFactory) 17from pyomo.core.expr import differentiate 18from pyomo.core.expr import current as EXPR 19from pyomo.opt import SolverFactory, SolverResults 20from pyomo.solvers.plugins.solvers.persistent_solver import PersistentSolver 21from pyomo.contrib.gdpopt.util import get_main_elapsed_time, time_code 22from pyomo.core.expr.calculus.derivatives import differentiate 23from pyomo.common.dependencies import attempt_import 24from pyomo.contrib.fbbt.fbbt import fbbt 25from pyomo.solvers.plugins.solvers.gurobi_persistent import GurobiPersistent 26 27pyomo_nlp = attempt_import('pyomo.contrib.pynumero.interfaces.pyomo_nlp')[0] 28numpy = attempt_import('numpy')[0] 29logger = logging.getLogger('pyomo.contrib') 30 31 32class MindtPySolveData(object): 33 """Data container to hold solve-instance data. 34 Key attributes: 35 - original_model: the original model that the user gave us to solve 36 - working_model: the original model after preprocessing 37 """ 38 pass 39 40 41def model_is_valid(solve_data, config): 42 """ 43 Determines whether the model is solveable by MindtPy. 44 45 This function returns True if the given model is solveable by MindtPy (and performs some preprocessing such 46 as moving the objective to the constraints). 47 48 Parameters 49 ---------- 50 solve_data: MindtPy Data Container 51 data container that holds solve-instance data 52 config: MindtPy configurations 53 contains the specific configurations for the algorithm 54 55 Returns 56 ------- 57 Boolean value (True if model is solveable in MindtPy else False) 58 """ 59 m = solve_data.working_model 60 MindtPy = m.MindtPy_utils 61 62 # Handle LP/NLP being passed to the solver 63 prob = solve_data.results.problem 64 if len(MindtPy.discrete_variable_list) == 0: 65 config.logger.info('Problem has no discrete decisions.') 66 obj = next(m.component_data_objects(ctype=Objective, active=True)) 67 if (any(c.body.polynomial_degree() not in {1, 0} for c in MindtPy.constraint_list) or 68 obj.expr.polynomial_degree() not in {1, 0}): 69 config.logger.info( 70 'Your model is a NLP (nonlinear program). ' 71 'Using NLP solver %s to solve.' % config.nlp_solver) 72 nlpopt = SolverFactory(config.nlp_solver) 73 set_solver_options(nlpopt, solve_data, config, solver_type='nlp') 74 nlpopt.solve(solve_data.original_model, 75 tee=config.nlp_solver_tee, **config.nlp_solver_args) 76 return False 77 else: 78 config.logger.info( 79 'Your model is an LP (linear program). ' 80 'Using LP solver %s to solve.' % config.mip_solver) 81 mainopt = SolverFactory(config.mip_solver) 82 if isinstance(mainopt, PersistentSolver): 83 mainopt.set_instance(solve_data.original_model) 84 set_solver_options(mainopt, solve_data, 85 config, solver_type='mip') 86 mainopt.solve(solve_data.original_model, 87 tee=config.mip_solver_tee, **config.mip_solver_args) 88 return False 89 90 if not hasattr(m, 'dual') and config.calculate_dual: # Set up dual value reporting 91 m.dual = Suffix(direction=Suffix.IMPORT) 92 93 # TODO if any continuous variables are multiplied with binary ones, 94 # need to do some kind of transformation (Glover?) or throw an error message 95 return True 96 97 98def calc_jacobians(solve_data, config): 99 """ 100 Generates a map of jacobians for the variables in the model 101 102 This function generates a map of jacobians corresponding to the variables in the model and adds this 103 ComponentMap to solve_data 104 105 Parameters 106 ---------- 107 solve_data: MindtPy Data Container 108 data container that holds solve-instance data 109 config: MindtPy configurations 110 contains the specific configurations for the algorithm 111 """ 112 # Map nonlinear_constraint --> Map( 113 # variable --> jacobian of constraint wrt. variable) 114 solve_data.jacobians = ComponentMap() 115 if config.differentiate_mode == 'reverse_symbolic': 116 mode = differentiate.Modes.reverse_symbolic 117 elif config.differentiate_mode == 'sympy': 118 mode = differentiate.Modes.sympy 119 for c in solve_data.mip.MindtPy_utils.nonlinear_constraint_list: 120 vars_in_constr = list(EXPR.identify_variables(c.body)) 121 jac_list = differentiate( 122 c.body, wrt_list=vars_in_constr, mode=mode) 123 solve_data.jacobians[c] = ComponentMap( 124 (var, jac_wrt_var) 125 for var, jac_wrt_var in zip(vars_in_constr, jac_list)) 126 127 128def add_feas_slacks(m, config): 129 """ 130 Adds feasibility slack variables according to config.feasibility_norm (given an infeasible problem) 131 132 Parameters 133 ---------- 134 m: model 135 Pyomo model 136 config: ConfigBlock 137 contains the specific configurations for the algorithm 138 """ 139 MindtPy = m.MindtPy_utils 140 # generate new constraints 141 for i, constr in enumerate(MindtPy.nonlinear_constraint_list, 1): 142 if constr.has_ub(): 143 if config.feasibility_norm in {'L1', 'L2'}: 144 MindtPy.feas_opt.feas_constraints.add( 145 constr.body - constr.upper 146 <= MindtPy.feas_opt.slack_var[i]) 147 else: 148 MindtPy.feas_opt.feas_constraints.add( 149 constr.body - constr.upper 150 <= MindtPy.feas_opt.slack_var) 151 if constr.has_lb(): 152 if config.feasibility_norm in {'L1', 'L2'}: 153 MindtPy.feas_opt.feas_constraints.add( 154 constr.body - constr.lower 155 >= -MindtPy.feas_opt.slack_var[i]) 156 else: 157 MindtPy.feas_opt.feas_constraints.add( 158 constr.body - constr.lower 159 >= -MindtPy.feas_opt.slack_var) 160 161 162def var_bound_add(solve_data, config): 163 """ 164 This function will add bounds for variables in nonlinear constraints if they are not bounded. (This is to avoid 165 an unbounded main problem in the LP/NLP algorithm.) Thus, the model will be updated to include bounds for the 166 unbounded variables in nonlinear constraints. 167 168 Parameters 169 ---------- 170 solve_data: MindtPy Data Container 171 data container that holds solve-instance data 172 config: ConfigBlock 173 contains the specific configurations for the algorithm 174 175 """ 176 m = solve_data.working_model 177 MindtPy = m.MindtPy_utils 178 for c in MindtPy.nonlinear_constraint_list: 179 for var in EXPR.identify_variables(c.body): 180 if var.has_lb() and var.has_ub(): 181 continue 182 elif not var.has_lb(): 183 if var.is_integer(): 184 var.setlb(-config.integer_var_bound - 1) 185 else: 186 var.setlb(-config.continuous_var_bound - 1) 187 elif not var.has_ub(): 188 if var.is_integer(): 189 var.setub(config.integer_var_bound) 190 else: 191 var.setub(config.continuous_var_bound) 192 193 194def generate_norm2sq_objective_function(model, setpoint_model, discrete_only=False): 195 """ 196 This function generates objective (FP-NLP subproblem) for minimum euclidean distance to setpoint_model 197 L2 distance of (x,y) = \sqrt{\sum_i (x_i - y_i)^2} 198 199 Parameters 200 ---------- 201 model: Pyomo model 202 the model that needs new objective function 203 setpoint_model: Pyomo model 204 the model that provides the base point for us to calculate the distance 205 discrete_only: Bool 206 only optimize on distance between the discrete variables 207 """ 208 # skip objective_value variable and slack_var variables 209 var_filter = (lambda v: v[1].is_integer()) if discrete_only \ 210 else (lambda v: v[1].name != 'MindtPy_utils.objective_value' and 211 'MindtPy_utils.feas_opt.slack_var' not in v[1].name) 212 213 model_vars, setpoint_vars = zip(*filter(var_filter, 214 zip(model.component_data_objects(Var), 215 setpoint_model.component_data_objects(Var)))) 216 assert len(model_vars) == len( 217 setpoint_vars), 'Trying to generate Squared Norm2 objective function for models with different number of variables' 218 219 return Objective(expr=( 220 sum([(model_var - setpoint_var.value)**2 221 for (model_var, setpoint_var) in 222 zip(model_vars, setpoint_vars)]))) 223 224 225def generate_norm1_objective_function(model, setpoint_model, discrete_only=False): 226 """ 227 This function generates objective (PF-OA main problem) for minimum Norm1 distance to setpoint_model 228 Norm1 distance of (x,y) = \sum_i |x_i - y_i| 229 230 Parameters 231 ---------- 232 model: Pyomo model 233 the model that needs new objective function 234 setpoint_model: Pyomo model 235 the model that provides the base point for us to calculate the distance 236 discrete_only: Bool 237 only optimize on distance between the discrete variables 238 """ 239 # skip objective_value variable and slack_var variables 240 var_filter = (lambda v: v.is_integer()) if discrete_only \ 241 else (lambda v: v.name != 'MindtPy_utils.objective_value' and 242 'MindtPy_utils.feas_opt.slack_var' not in v.name) 243 model_vars = list(filter(var_filter, model.component_data_objects(Var))) 244 setpoint_vars = list( 245 filter(var_filter, setpoint_model.component_data_objects(Var))) 246 assert len(model_vars) == len( 247 setpoint_vars), 'Trying to generate Norm1 objective function for models with different number of variables' 248 model.MindtPy_utils.del_component('L1_obj') 249 obj_blk = model.MindtPy_utils.L1_obj = Block() 250 obj_blk.L1_obj_idx = RangeSet(len(model_vars)) 251 obj_blk.L1_obj_var = Var( 252 obj_blk.L1_obj_idx, domain=Reals, bounds=(0, None)) 253 obj_blk.abs_reform = ConstraintList() 254 for idx, v_model, v_setpoint in zip(obj_blk.L1_obj_idx, model_vars, 255 setpoint_vars): 256 obj_blk.abs_reform.add( 257 expr=v_model - v_setpoint.value >= -obj_blk.L1_obj_var[idx]) 258 obj_blk.abs_reform.add( 259 expr=v_model - v_setpoint.value <= obj_blk.L1_obj_var[idx]) 260 261 return Objective(expr=sum(obj_blk.L1_obj_var[idx] for idx in obj_blk.L1_obj_idx)) 262 263 264def generate_norm_inf_objective_function(model, setpoint_model, discrete_only=False): 265 """ 266 This function generates objective (PF-OA main problem) for minimum Norm Infinity distance to setpoint_model 267 Norm-Infinity distance of (x,y) = \max_i |x_i - y_i| 268 269 Parameters 270 ---------- 271 model: Pyomo model 272 the model that needs new objective function 273 setpoint_model: Pyomo model 274 the model that provides the base point for us to calculate the distance 275 discrete_only: Bool 276 only optimize on distance between the discrete variables 277 """ 278 # skip objective_value variable and slack_var variables 279 var_filter = (lambda v: v.is_integer()) if discrete_only \ 280 else (lambda v: v.name != 'MindtPy_utils.objective_value' and 281 'MindtPy_utils.feas_opt.slack_var' not in v.name) 282 model_vars = list(filter(var_filter, model.component_data_objects(Var))) 283 setpoint_vars = list( 284 filter(var_filter, setpoint_model.component_data_objects(Var))) 285 assert len(model_vars) == len( 286 setpoint_vars), 'Trying to generate Norm Infinity objective function for models with different number of variables' 287 model.MindtPy_utils.del_component('L_infinity_obj') 288 obj_blk = model.MindtPy_utils.L_infinity_obj = Block() 289 obj_blk.L_infinity_obj_var = Var(domain=Reals, bounds=(0, None)) 290 obj_blk.abs_reform = ConstraintList() 291 for v_model, v_setpoint in zip(model_vars, 292 setpoint_vars): 293 obj_blk.abs_reform.add( 294 expr=v_model - v_setpoint.value >= -obj_blk.L_infinity_obj_var) 295 obj_blk.abs_reform.add( 296 expr=v_model - v_setpoint.value <= obj_blk.L_infinity_obj_var) 297 298 return Objective(expr=obj_blk.L_infinity_obj_var) 299 300 301def generate_lag_objective_function(model, setpoint_model, config, solve_data, discrete_only=False): 302 """The function generate taylor extension of the Lagrangean function. 303 304 Args: 305 model ([type]): [description] 306 setpoint_model ([type]): [description] 307 discrete_only (bool, optional): [description]. Defaults to False. 308 """ 309 temp_model = setpoint_model.clone() 310 for var in temp_model.MindtPy_utils.variable_list: 311 if var.is_integer(): 312 var.unfix() 313 # objective_list[0] is the original objective function, not in MindtPy_utils block 314 temp_model.MindtPy_utils.objective_list[0].activate() 315 temp_model.MindtPy_utils.deactivate() 316 TransformationFactory('core.relax_integer_vars').apply_to(temp_model) 317 # Note: PyNumero does not support discrete variables 318 # So PyomoNLP should operate on setpoint_model 319 320 # Implementation 1 321 # First calculate Jacobian and Hessian without assigning variable and constraint sequence, then use get_primal_indices to get the indices. 322 with time_code(solve_data.timing, 'PyomoNLP'): 323 nlp = pyomo_nlp.PyomoNLP(temp_model) 324 lam = [-temp_model.dual[constr] if abs(temp_model.dual[constr]) > config.zero_tolerance else 0 325 for constr in nlp.get_pyomo_constraints()] 326 nlp.set_duals(lam) 327 obj_grad = nlp.evaluate_grad_objective().reshape(-1, 1) 328 jac = nlp.evaluate_jacobian().toarray() 329 jac_lag = obj_grad + jac.transpose().dot(numpy.array(lam).reshape(-1, 1)) 330 jac_lag[abs(jac_lag) < config.zero_tolerance] = 0 331 # jac_lag of continuous variables should be zero 332 for var in temp_model.MindtPy_utils.continuous_variable_list[:-1]: 333 jac_lag[nlp.get_primal_indices([var])[0]] = 0 334 nlp_var = set([i.name for i in nlp.get_pyomo_variables()]) 335 first_order_term = sum(float(jac_lag[nlp.get_primal_indices([temp_var])[0]]) * (var - temp_var.value) for var, 336 temp_var in zip(model.MindtPy_utils.variable_list[:-1], temp_model.MindtPy_utils.variable_list[:-1]) if temp_var.name in nlp_var) 337 338 if config.add_regularization == 'grad_lag': 339 return Objective(expr=first_order_term, sense=minimize) 340 elif config.add_regularization in {'hess_lag', 'hess_only_lag'}: 341 # Implementation 1 342 hess_lag = nlp.evaluate_hessian_lag().toarray() 343 hess_lag[abs(hess_lag) < config.zero_tolerance] = 0 344 second_order_term = 0.5 * sum((var_i - temp_var_i.value) * float(hess_lag[nlp.get_primal_indices([temp_var_i])[0]][nlp.get_primal_indices([temp_var_j])[0]]) * (var_j - temp_var_j.value) 345 for var_i, temp_var_i in zip(model.MindtPy_utils.variable_list[:-1], temp_model.MindtPy_utils.variable_list[:-1]) 346 for var_j, temp_var_j in zip(model.MindtPy_utils.variable_list[:-1], temp_model.MindtPy_utils.variable_list[:-1]) 347 if (temp_var_i.name in nlp_var and temp_var_j.name in nlp_var)) 348 if config.add_regularization == 'hess_lag': 349 return Objective(expr=first_order_term + second_order_term, sense=minimize) 350 elif config.add_regularization == 'hess_only_lag': 351 return Objective(expr=second_order_term, sense=minimize) 352 elif config.add_regularization == 'sqp_lag': 353 var_filter = (lambda v: v[1].is_integer()) if discrete_only \ 354 else (lambda v: v[1].name != 'MindtPy_utils.objective_value' and 355 'MindtPy_utils.feas_opt.slack_var' not in v[1].name) 356 357 model_vars, setpoint_vars = zip(*filter(var_filter, 358 zip(model.component_data_objects(Var), 359 setpoint_model.component_data_objects(Var)))) 360 assert len(model_vars) == len( 361 setpoint_vars), 'Trying to generate Squared Norm2 objective function for models with different number of variables' 362 if config.sqp_lag_scaling_coef is None: 363 rho = 1 364 elif config.sqp_lag_scaling_coef == 'fixed': 365 r = 1 366 rho = numpy.linalg.norm(jac_lag/(2*r)) 367 elif config.sqp_lag_scaling_coef == 'variable_dependent': 368 r = numpy.sqrt( 369 len(temp_model.MindtPy_utils.discrete_variable_list)) 370 rho = numpy.linalg.norm(jac_lag/(2*r)) 371 372 return Objective(expr=first_order_term + rho*sum([(model_var - setpoint_var.value)**2 for (model_var, setpoint_var) in zip(model_vars, setpoint_vars)])) 373 374 375def generate_norm1_norm_constraint(model, setpoint_model, config, discrete_only=True): 376 """ 377 This function generates constraint (PF-OA main problem) for minimum Norm1 distance to setpoint_model 378 Norm constraint is used to guarantees the monotonicity of the norm objective value sequence of all iterations 379 Norm1 distance of (x,y) = \sum_i |x_i - y_i| 380 Ref: Paper 'A storm of feasibility pumps for nonconvex MINLP' Eq. (16) 381 382 Parameters 383 ---------- 384 model: Pyomo model 385 the model that needs new objective function 386 setpoint_model: Pyomo model 387 the model that provides the base point for us to calculate the distance 388 discrete_only: Bool 389 only optimize on distance between the discrete variables 390 """ 391 392 var_filter = (lambda v: v.is_integer()) if discrete_only \ 393 else (lambda v: True) 394 model_vars = list(filter(var_filter, model.component_data_objects(Var))) 395 setpoint_vars = list( 396 filter(var_filter, setpoint_model.component_data_objects(Var))) 397 assert len(model_vars) == len( 398 setpoint_vars), 'Trying to generate Norm1 norm constraint for models with different number of variables' 399 norm_constraint_blk = model.MindtPy_utils.L1_norm_constraint = Block() 400 norm_constraint_blk.L1_slack_idx = RangeSet(len(model_vars)) 401 norm_constraint_blk.L1_slack_var = Var( 402 norm_constraint_blk.L1_slack_idx, domain=Reals, bounds=(0, None)) 403 norm_constraint_blk.abs_reform = ConstraintList() 404 for idx, v_model, v_setpoint in zip(norm_constraint_blk.L1_slack_idx, model_vars, 405 setpoint_vars): 406 norm_constraint_blk.abs_reform.add( 407 expr=v_model - v_setpoint.value >= -norm_constraint_blk.L1_slack_var[idx]) 408 norm_constraint_blk.abs_reform.add( 409 expr=v_model - v_setpoint.value <= norm_constraint_blk.L1_slack_var[idx]) 410 rhs = config.fp_norm_constraint_coef * \ 411 sum(abs(v_model.value-v_setpoint.value) 412 for v_model, v_setpoint in zip(model_vars, setpoint_vars)) 413 norm_constraint_blk.sum_slack = Constraint( 414 expr=sum(norm_constraint_blk.L1_slack_var[idx] for idx in norm_constraint_blk.L1_slack_idx) <= rhs) 415 416 417def set_solver_options(opt, solve_data, config, solver_type, regularization=False): 418 """ set options for MIP/NLP solvers 419 420 Args: 421 opt : SolverFactory 422 the solver 423 solve_data: MindtPy Data Container 424 data container that holds solve-instance data 425 config: ConfigBlock 426 contains the specific configurations for the algorithm 427 solver_type: String 428 The type of the solver, i.e. mip or nlp 429 regularization (bool, optional): Boolean. 430 Defaults to False. 431 """ 432 # TODO: integrate nlp_args here 433 # nlp_args = dict(config.nlp_solver_args) 434 elapsed = get_main_elapsed_time(solve_data.timing) 435 remaining = int(max(config.time_limit - elapsed, 1)) 436 if solver_type == 'mip': 437 if regularization: 438 solver_name = config.mip_regularization_solver 439 if config.regularization_mip_threads > 0: 440 opt.options['threads'] = config.regularization_mip_threads 441 else: 442 solver_name = config.mip_solver 443 if config.threads > 0: 444 opt.options['threads'] = config.threads 445 elif solver_type == 'nlp': 446 solver_name = config.nlp_solver 447 # TODO: opt.name doesn't work for GAMS 448 if solver_name in {'cplex', 'gurobi', 'gurobi_persistent'}: 449 opt.options['timelimit'] = remaining 450 opt.options['mipgap'] = config.mip_solver_mipgap 451 if solver_name == 'gurobi_persistent' and config.single_tree: 452 # PreCrush: Controls presolve reductions that affect user cuts 453 # You should consider setting this parameter to 1 if you are using callbacks to add your own cuts. 454 opt.set_gurobi_param('PreCrush', 1) 455 opt.set_gurobi_param('LazyConstraints', 1) 456 if regularization == True: 457 if solver_name == 'cplex': 458 if config.solution_limit is not None: 459 opt.options['mip limits solutions'] = config.solution_limit 460 opt.options['mip strategy presolvenode'] = 3 461 # TODO: need to discuss if this option should be added. 462 if config.add_regularization in {'hess_lag', 'hess_only_lag'}: 463 opt.options['optimalitytarget'] = 3 464 elif solver_name == 'gurobi': 465 if config.solution_limit is not None: 466 opt.options['SolutionLimit'] = config.solution_limit 467 opt.options['Presolve'] = 2 468 elif solver_name == 'cplex_persistent': 469 opt.options['timelimit'] = remaining 470 opt._solver_model.parameters.mip.tolerances.mipgap.set( 471 config.mip_solver_mipgap) 472 if regularization is True: 473 if config.solution_limit is not None: 474 opt._solver_model.parameters.mip.limits.solutions.set( 475 config.solution_limit) 476 opt._solver_model.parameters.mip.strategy.presolvenode.set(3) 477 if config.add_regularization in {'hess_lag', 'hess_only_lag'}: 478 opt._solver_model.parameters.optimalitytarget.set(3) 479 elif solver_name == 'glpk': 480 opt.options['tmlim'] = remaining 481 # TODO: mipgap does not work for glpk yet 482 # opt.options['mipgap'] = config.mip_solver_mipgap 483 elif solver_name == 'baron': 484 opt.options['MaxTime'] = remaining 485 opt.options['AbsConFeasTol'] = config.zero_tolerance 486 elif solver_name == 'ipopt': 487 opt.options['max_cpu_time'] = remaining 488 opt.options['constr_viol_tol'] = config.zero_tolerance 489 elif solver_name == 'gams': 490 if solver_type == 'mip': 491 opt.options['add_options'] = ['option optcr=%s;' % config.mip_solver_mipgap, 492 'option reslim=%s;' % remaining] 493 elif solver_type == 'nlp': 494 opt.options['add_options'] = ['option reslim=%s;' % remaining] 495 if config.nlp_solver_args.__contains__('solver'): 496 if config.nlp_solver_args['solver'] in {'ipopt', 'ipopth', 'msnlp', 'conopt', 'baron'}: 497 if config.nlp_solver_args['solver'] == 'ipopt': 498 opt.options['add_options'].append( 499 '$onecho > ipopt.opt') 500 opt.options['add_options'].append( 501 'constr_viol_tol ' + str(config.zero_tolerance)) 502 elif config.nlp_solver_args['solver'] == 'ipopth': 503 opt.options['add_options'].append( 504 '$onecho > ipopth.opt') 505 opt.options['add_options'].append( 506 'constr_viol_tol ' + str(config.zero_tolerance)) 507 # TODO: Ipopt warmstart option 508 # opt.options['add_options'].append('warm_start_init_point yes\n' 509 # 'warm_start_bound_push 1e-9\n' 510 # 'warm_start_bound_frac 1e-9\n' 511 # 'warm_start_slack_bound_frac 1e-9\n' 512 # 'warm_start_slack_bound_push 1e-9\n' 513 # 'warm_start_mult_bound_push 1e-9\n') 514 elif config.nlp_solver_args['solver'] == 'conopt': 515 opt.options['add_options'].append( 516 '$onecho > conopt.opt') 517 opt.options['add_options'].append( 518 'RTNWMA ' + str(config.zero_tolerance)) 519 elif config.nlp_solver_args['solver'] == 'msnlp': 520 opt.options['add_options'].append( 521 '$onecho > msnlp.opt') 522 opt.options['add_options'].append( 523 'feasibility_tolerance ' + str(config.zero_tolerance)) 524 elif config.nlp_solver_args['solver'] == 'baron': 525 opt.options['add_options'].append( 526 '$onecho > baron.opt') 527 opt.options['add_options'].append( 528 'AbsConFeasTol ' + str(config.zero_tolerance)) 529 opt.options['add_options'].append('$offecho') 530 opt.options['add_options'].append('GAMS_MODEL.optfile=1') 531 532 533def get_integer_solution(model, string_zero=False): 534 """ obtain the value of integer variables from the provided model. 535 536 Args: 537 model: Pyomo model 538 the model to extract value of integer variables 539 string_zero: Boolean 540 whether to store zero as string 541 """ 542 temp = [] 543 for var in model.component_data_objects(ctype=Var): 544 if var.is_integer(): 545 if string_zero: 546 if var.value == 0: 547 # In cplex, negative zero is different from zero, so we use string to denote this(Only in singletree) 548 temp.append(str(var.value)) 549 else: 550 temp.append(int(round(var.value))) 551 else: 552 temp.append(int(round(var.value))) 553 return tuple(temp) 554 555 556def setup_solve_data(model, config): 557 solve_data = MindtPySolveData() 558 solve_data.results = SolverResults() 559 solve_data.timing = Bunch() 560 solve_data.curr_int_sol = [] 561 solve_data.should_terminate = False 562 solve_data.integer_list = [] 563 564 # if the objective function is a constant, dual bound constraint is not added. 565 obj = next(model.component_data_objects(ctype=Objective, active=True)) 566 if obj.expr.polynomial_degree() == 0: 567 config.use_dual_bound = False 568 569 if config.use_fbbt: 570 fbbt(model) 571 # TODO: logging_level is not logging.INFO here 572 config.logger.info( 573 'Use the fbbt to tighten the bounds of variables') 574 575 solve_data.original_model = model 576 solve_data.working_model = model.clone() 577 578 # Set up iteration counters 579 solve_data.nlp_iter = 0 580 solve_data.mip_iter = 0 581 solve_data.mip_subiter = 0 582 solve_data.nlp_infeasible_counter = 0 583 if config.init_strategy == 'FP': 584 solve_data.fp_iter = 1 585 586 # set up bounds 587 solve_data.LB = float('-inf') 588 solve_data.UB = float('inf') 589 solve_data.LB_progress = [solve_data.LB] 590 solve_data.UB_progress = [solve_data.UB] 591 if config.single_tree and (config.add_no_good_cuts or config.use_tabu_list): 592 solve_data.stored_bound = {} 593 if config.strategy == 'GOA' and (config.add_no_good_cuts or config.use_tabu_list): 594 solve_data.num_no_good_cuts_added = {} 595 596 # Flag indicating whether the solution improved in the past 597 # iteration or not 598 solve_data.solution_improved = False 599 solve_data.bound_improved = False 600 601 if config.nlp_solver == 'ipopt': 602 if not hasattr(solve_data.working_model, 'ipopt_zL_out'): 603 solve_data.working_model.ipopt_zL_out = Suffix( 604 direction=Suffix.IMPORT) 605 if not hasattr(solve_data.working_model, 'ipopt_zU_out'): 606 solve_data.working_model.ipopt_zU_out = Suffix( 607 direction=Suffix.IMPORT) 608 609 return solve_data 610 611 612class GurobiPersistent4MindtPy(GurobiPersistent): 613 614 def _intermediate_callback(self): 615 def f(gurobi_model, where): 616 self._callback_func(self._pyomo_model, self, 617 where, self.solve_data, self.config) 618 return f 619