def _generate_model(self): self.model = None self.model = ConcreteModel() model = self.model model._name = self.description n = 7 m = 7 model.N = RangeSet(1, n) model.M = RangeSet(1, m) model.c = Param(model.N, rule=c_rule) model.b = Param(model.M, rule=b_rule) model.A = Param(model.M, model.N, rule=A_rule) model.x = Var(model.N, within=NonNegativeReals) model.y = Var(model.M, within=NonNegativeReals) model.cost = Objective(expr=sum_product(model.c, model.x)) model.primalcon = Constraint(model.M, rule=primalcon_rule) #model.dual = Suffix(direction=Suffix.IMPORT) #model.rc = Suffix(direction=Suffix.IMPORT) model.slack = Suffix(direction=Suffix.IMPORT) model.urc = Suffix(direction=Suffix.IMPORT) model.lrc = Suffix(direction=Suffix.IMPORT)
def setup_solve_data(model, config): solve_data = MindtPySolveData() solve_data.results = SolverResults() solve_data.timing = Bunch() solve_data.curr_int_sol = [] solve_data.should_terminate = False solve_data.integer_list = [] # if the objective function is a constant, dual bound constraint is not added. obj = next(model.component_data_objects(ctype=Objective, active=True)) if obj.expr.polynomial_degree() == 0: config.use_dual_bound = False if config.use_fbbt: fbbt(model) # TODO: logging_level is not logging.INFO here config.logger.info('Use the fbbt to tighten the bounds of variables') solve_data.original_model = model solve_data.working_model = model.clone() # Set up iteration counters solve_data.nlp_iter = 0 solve_data.mip_iter = 0 solve_data.mip_subiter = 0 solve_data.nlp_infeasible_counter = 0 if config.init_strategy == 'FP': solve_data.fp_iter = 1 # set up bounds solve_data.LB = float('-inf') solve_data.UB = float('inf') solve_data.LB_progress = [solve_data.LB] solve_data.UB_progress = [solve_data.UB] if config.single_tree and (config.add_no_good_cuts or config.use_tabu_list): solve_data.stored_bound = {} if config.strategy == 'GOA' and (config.add_no_good_cuts or config.use_tabu_list): solve_data.num_no_good_cuts_added = {} # Flag indicating whether the solution improved in the past # iteration or not solve_data.solution_improved = False solve_data.bound_improved = False if config.nlp_solver == 'ipopt': if not hasattr(solve_data.working_model, 'ipopt_zL_out'): solve_data.working_model.ipopt_zL_out = Suffix( direction=Suffix.IMPORT) if not hasattr(solve_data.working_model, 'ipopt_zU_out'): solve_data.working_model.ipopt_zU_out = Suffix( direction=Suffix.IMPORT) return solve_data
def _add_dual_suffix(self, rHull): # rHull is our model and we aren't giving it back (unless in the future # we we add a callback to do basic steps to it...), so we just check if # dual is there. If it's a Suffix, we'll borrow it. If it's something # else we'll rename it and add the Sufix. dual = rHull.component("dual") if dual is None: rHull.dual = Suffix(direction=Suffix.IMPORT) else: if dual.ctype is Suffix: return rHull.del_component(dual) rHull.dual = Suffix(direction=Suffix.IMPORT) rHull.add_component(unique_component_name(rHull, "dual"), dual)
def _write_bundle_NL(worker, bundle, output_directory, linking_suffix_name, objective_suffix_name, symbolic_solver_labels): assert os.path.exists(output_directory) bundle_instance = worker._bundle_binding_instance_map[bundle.name] assert not hasattr(bundle_instance, ".tmpblock") tmpblock = Block(concrete=True) bundle_instance.add_component(".tmpblock", tmpblock) # # linking variable suffix # tmpblock.add_component(linking_suffix_name, Suffix(direction=Suffix.EXPORT)) linking_suffix = getattr(tmpblock, linking_suffix_name) # Loop over all nodes for the bundle except the leaf nodes, # which have no blended variables scenario_tree = worker.scenario_tree for stage in bundle.scenario_tree.stages[:-1]: for _node in stage.nodes: # get the node of off the real scenario tree # as this has the linked variable information node = scenario_tree.get_node(_node.name) master_variable = bundle_instance.find_component( "MASTER_BLEND_VAR_"+str(node.name)) for variable_id in node._standard_variable_ids: linking_suffix[master_variable[variable_id]] = variable_id # # objective weight suffix # tmpblock.add_component(objective_suffix_name, Suffix(direction=Suffix.EXPORT)) getattr(tmpblock, objective_suffix_name)[bundle_instance] = \ bundle._probability output_filename = os.path.join(output_directory, str(bundle.name)+".nl") bundle_instance.write( output_filename, io_options={'symbolic_solver_labels': symbolic_solver_labels}) bundle_instance.del_component(tmpblock)
def GDPopt_initialize_master(solve_data, config): """Initialize the decomposition algorithm. This includes generating the initial cuts require to build the master problem. """ config.logger.info("---Starting GDPopt initialization---") m = solve_data.working_model if not hasattr(m, 'dual'): # Set up dual value reporting m.dual = Suffix(direction=Suffix.IMPORT) m.dual.activate() # Set up the linear GDP model solve_data.linear_GDP = m.clone() # deactivate nonlinear constraints for c in solve_data.linear_GDP.GDPopt_utils.\ working_nonlinear_constraints: c.deactivate() # Initialization strategies init_strategy = valid_init_strategies.get(config.init_strategy, None) if init_strategy is not None: init_strategy(solve_data, config) else: raise ValueError( 'Unknown initialization strategy: %s. ' 'Valid strategies include: %s' % (config.init_strategy, ", ".join( k for (k, v) in valid_init_strategies.items() if v is not None)))
def test_EXPORT_suffixes_with_SOSConstraint_duplicateref(self): model = ConcreteModel() model.ref = Suffix(direction=Suffix.EXPORT,datatype=Suffix.INT) model.y = Var([1,2,3]) model.obj = Objective(expr=sum_product(model.y)) # The NL writer will convert this constraint to ref and sosno # suffixes on model.y model.sos_con = SOSConstraint(var=model.y, index=[1,2,3], sos=1) for i,val in zip([1,2,3],[11,12,13]): model.ref.set_value(model.y[i],val) try: model.write(filename=join(currdir,"junk.nl"), format=ProblemFormat.nl, io_options={"symbolic_solver_labels" : False}) except RuntimeError: pass else: os.remove(join(currdir,"junk.nl")) self.fail("The NL writer should have thrown an exception "\ "when overlap of SOSConstraint generated suffixes "\ "and user declared suffixes occurs.") try: os.remove(join(currdir,"junk.nl")) except: pass
def GDPopt_initialize_master(solve_data, config): """Initialize the decomposition algorithm. This includes generating the initial cuts require to build the master problem. """ config.logger.info("---Starting GDPopt initialization---") m = solve_data.working_model if not hasattr(m, 'dual'): # Set up dual value reporting m.dual = Suffix(direction=Suffix.IMPORT) m.dual.activate() # Set up the linear GDP model solve_data.linear_GDP = m.clone() # deactivate nonlinear constraints for c in solve_data.linear_GDP.component_data_objects( Constraint, active=True, descend_into=(Block, Disjunct)): if c.body.polynomial_degree() not in (1, 0): c.deactivate() # Initialization strategies, defined at bottom init_strategy_fctn = valid_init_strategies.get(config.init_strategy, None) if init_strategy_fctn is not None: init_strategy_fctn(solve_data, config) else: raise ValueError( 'Unknown initialization strategy: %s. ' 'Valid strategies include: %s' % (config.init_strategy, ", ".join( k for (k, v) in valid_init_strategies.items() if v is not None)))
def model_is_valid(solve_data, config): """Validate that the model is solveable by MindtPy. Also preforms some preprocessing such as moving the objective to the constraints. """ m = solve_data.working_model MindtPy = m.MindtPy_utils # Handle LP/NLP being passed to the solver prob = solve_data.results.problem if (prob.number_of_binary_variables == 0 and prob.number_of_integer_variables == 0 and prob.number_of_disjunctions == 0): config.logger.info('Problem has no discrete decisions.') if len(MindtPy.working_nonlinear_constraints) > 0: config.logger.info("Your model is an NLP (nonlinear program). " "Using NLP solver %s to solve." % config.nlp) SolverFactory(config.nlp).solve(solve_data.original_model, **config.nlp_options) return False else: config.logger.info("Your model is an LP (linear program). " "Using LP solver %s to solve." % config.mip) SolverFactory(config.mip).solve(solve_data.original_model, **config.mip_options) return False if not hasattr(m, 'dual'): # Set up dual value reporting m.dual = Suffix(direction=Suffix.IMPORT) # TODO if any continuous variables are multipled with binary ones, need # to do some kind of transformation (Glover?) or throw an error message return True
def test_EXPORT_suffixes_no_datatype(self): model = ConcreteModel() model.sosno = Suffix(direction=Suffix.EXPORT,datatype=None) model.y = Var([1,2,3]) model.obj = Objective(expr=sum_product(model.y)) # The NL writer will convert this constraint to ref and sosno # suffixes on model.y model.sos_con = SOSConstraint(var=model.y, index=[1,2,3], sos=1) for i in [1,2,3]: model.sosno.set_value(model.y[i],-1) try: model.write(filename=join(currdir,"junk.nl"), format=ProblemFormat.nl, io_options={"symbolic_solver_labels" : False}) except RuntimeError: pass else: os.remove(join(currdir,"junk.nl")) self.fail("The NL writer should have thrown an exception "\ "when using an EXPORT suffix with datatype=None") try: os.remove(join(currdir,"junk.nl")) except: pass
def model_is_valid(solve_data, config): """ Determines whether the model is solveable by MindtPy. This function returns True if the given model is solveable by MindtPy (and performs some preprocessing such as moving the objective to the constraints). Parameters ---------- solve_data: MindtPy Data Container data container that holds solve-instance data config: MindtPy configurations contains the specific configurations for the algorithm Returns ------- Boolean value (True if model is solveable in MindtPy else False) """ m = solve_data.working_model MindtPy = m.MindtPy_utils # Handle LP/NLP being passed to the solver prob = solve_data.results.problem if (prob.number_of_binary_variables == 0 and prob.number_of_integer_variables == 0 and prob.number_of_disjunctions == 0): config.logger.info('Problem has no discrete decisions.') obj = next(m.component_data_objects(ctype=Objective, active=True)) if (any(c.body.polynomial_degree() not in (1, 0) for c in MindtPy.constraint_list) or obj.expr.polynomial_degree() not in (1, 0)): config.logger.info("Your model is an NLP (nonlinear program). " "Using NLP solver %s to solve." % config.nlp_solver) SolverFactory(config.nlp_solver).solve(solve_data.original_model, tee=config.solver_tee, **config.nlp_solver_args) return False else: config.logger.info("Your model is an LP (linear program). " "Using LP solver %s to solve." % config.mip_solver) mipopt = SolverFactory(config.mip_solver) if isinstance(mipopt, PersistentSolver): mipopt.set_instance(solve_data.original_model) if config.threads > 0: masteropt.options["threads"] = config.threads mipopt.solve(solve_data.original_model, tee=config.solver_tee, **config.mip_solver_args) return False if not hasattr(m, 'dual') and config.use_dual: # Set up dual value reporting m.dual = Suffix(direction=Suffix.IMPORT) # TODO if any continuous variables are multiplied with binary ones, # need to do some kind of transformation (Glover?) or throw an error message return True
def generate_model(self, import_suffixes=[]): """ Generate the model """ self._generate_model() # Add suffixes self.test_suffixes = [] if self.disable_suffix_tests else \ import_suffixes for suffix in self.test_suffixes: setattr(self.model, suffix, Suffix(direction=Suffix.IMPORT))
def model_is_valid(solve_data, config): """Determines whether the model is solvable by MindtPy. Parameters ---------- solve_data : MindtPySolveData Data container that holds solve-instance data. config : ConfigBlock The specific configurations for MindtPy. Returns ------- bool True if model is solvable in MindtPy, False otherwise. """ m = solve_data.working_model MindtPy = m.MindtPy_utils # Handle LP/NLP being passed to the solver prob = solve_data.results.problem if len(MindtPy.discrete_variable_list) == 0: config.logger.info('Problem has no discrete decisions.') obj = next(m.component_data_objects(ctype=Objective, active=True)) if (any(c.body.polynomial_degree() not in solve_data.mip_constraint_polynomial_degree for c in MindtPy.constraint_list) or obj.expr.polynomial_degree() not in solve_data.mip_objective_polynomial_degree): config.logger.info('Your model is a NLP (nonlinear program). ' 'Using NLP solver %s to solve.' % config.nlp_solver) nlpopt = SolverFactory(config.nlp_solver) set_solver_options(nlpopt, solve_data, config, solver_type='nlp') nlpopt.solve(solve_data.original_model, tee=config.nlp_solver_tee, **config.nlp_solver_args) return False else: config.logger.info('Your model is an LP (linear program). ' 'Using LP solver %s to solve.' % config.mip_solver) mainopt = SolverFactory(config.mip_solver) if isinstance(mainopt, PersistentSolver): mainopt.set_instance(solve_data.original_model) set_solver_options(mainopt, solve_data, config, solver_type='mip') mainopt.solve(solve_data.original_model, tee=config.mip_solver_tee, **config.mip_solver_args) return False if not hasattr( m, 'dual') and config.calculate_dual: # Set up dual value reporting m.dual = Suffix(direction=Suffix.IMPORT) # TODO if any continuous variables are multiplied with binary ones, # need to do some kind of transformation (Glover?) or throw an error message return True
def _write_bundle_NL(worker, bundle, output_directory, linking_suffix_name, objective_suffix_name): from pyomo.repn.plugins.ampl import ProblemWriter_nl assert os.path.exists(output_directory) bundle_instance = worker._bundle_binding_instance_map[bundle.name] # # linking variable suffix # bundle_instance.del_component(linking_suffix_name) bundle_instance.add_component(linking_suffix_name, Suffix(direction=Suffix.EXPORT)) linking_suffix = getattr(bundle_instance, linking_suffix_name) # Loop over all nodes for the bundle except the leaf nodes, # which have no blended variables scenario_tree = worker.scenario_tree for stage in bundle.scenario_tree.stages[:-1]: for _node in stage.nodes: # get the node of off the real scenario tree # as this has the linked variable information node = scenario_tree.get_node(_node.name) master_variable = bundle_instance.find_component( "MASTER_BLEND_VAR_" + str(node.name)) for variable_id in node._standard_variable_ids: linking_suffix[master_variable[variable_id]] = variable_id # # objective weight suffix # bundle_instance.del_component(objective_suffix_name) bundle_instance.add_component(objective_suffix_name, Suffix(direction=Suffix.EXPORT)) getattr(bundle_instance, objective_suffix_name)[bundle_instance] = \ bundle._probability output_filename = os.path.join(output_directory, str(bundle.name) + ".nl") with ProblemWriter_nl() as nl_writer: nl_writer(bundle_instance, output_filename, lambda x: True, {}) bundle_instance.del_component(linking_suffix_name) bundle_instance.del_component(objective_suffix_name)
def _write_scenario_NL(worker, scenario, output_directory, linking_suffix_name, objective_suffix_name, symbolic_solver_labels): assert os.path.exists(output_directory) instance = scenario._instance assert not hasattr(instance, ".tmpblock") tmpblock = Block(concrete=True) instance.add_component(".tmpblock", tmpblock) # # linking variable suffix # bySymbol = instance._ScenarioTreeSymbolMap.bySymbol tmpblock.add_component(linking_suffix_name, Suffix(direction=Suffix.EXPORT)) linking_suffix = getattr(tmpblock, linking_suffix_name) # Loop over all nodes for the scenario except the leaf node, # which has no blended variables for node in scenario._node_list[:-1]: for variable_id in node._standard_variable_ids: linking_suffix[bySymbol[variable_id]] = variable_id # # objective weight suffix # tmpblock.add_component(objective_suffix_name, Suffix(direction=Suffix.EXPORT)) getattr(tmpblock, objective_suffix_name)[instance] = \ scenario._probability output_filename = os.path.join(output_directory, str(scenario.name)+".nl") instance.write( output_filename, io_options={'symbolic_solver_labels': symbolic_solver_labels}) instance.del_component(tmpblock)
def get_mock_model_with_priorities(self): m = ConcreteModel() m.x = Var(domain=Integers) m.s = RangeSet(10) m.y = Var(m.s, domain=Integers) m.o = Objective(expr=m.x + sum(m.y), sense=minimize) m.c = Constraint(expr=m.x >= 1) m.c2 = Constraint(expr=quicksum(m.y[i] for i in m.s) >= 10) m.priority = Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT) m.direction = Suffix(direction=Suffix.EXPORT, datatype=Suffix.INT) m.priority.set_value(m.x, 1) # Ensure tests work for both options of `expand` m.priority.set_value(m.y, 2, expand=False) m.direction.set_value(m.y, BranchDirection.down, expand=True) m.direction.set_value(m.y[10], BranchDirection.up) return m
def generate_model(self, import_suffixes=[]): """ Generate the model """ self._generate_model() # Add suffixes self.test_suffixes = [] if self.disable_suffix_tests else \ import_suffixes if isinstance(self.model, IBlockStorage): for suffix in self.test_suffixes: setattr(self.model, suffix, pmo.suffix(direction=pmo.suffix.IMPORT)) else: for suffix in self.test_suffixes: setattr(self.model, suffix, Suffix(direction=Suffix.IMPORT))
def test_EXPORT_suffixes_float(self): model = ConcreteModel() model.junk = Suffix(direction=Suffix.EXPORT, datatype=Suffix.FLOAT) model.junk_inactive = Suffix(direction=Suffix.EXPORT, datatype=Suffix.FLOAT) model.x = Var() model.junk.set_value(model.x, 1) model.junk_inactive.set_value(model.x, 1) model.y = Var([1, 2], dense=True) model.junk.set_value(model.y, 2) model.junk_inactive.set_value(model.y, 2) model.obj = Objective(expr=model.x + sum_product(model.y)) model.junk.set_value(model.obj, 3) model.junk_inactive.set_value(model.obj, 3) model.conx = Constraint(expr=model.x >= 1) model.junk.set_value(model.conx, 4) model.junk_inactive.set_value(model.conx, 4) model.cony = Constraint([1, 2], rule=lambda model, i: model.y[i] >= 1) model.junk.set_value(model.cony, 5) model.junk_inactive.set_value(model.cony, 5) model.junk.set_value(model, 6) model.junk_inactive.set_value(model, 6) # This one should NOT end up in the NL file model.junk_inactive.deactivate() model.write(filename=join(currdir, "EXPORT_suffixes.test.nl"), format=ProblemFormat.nl, io_options={"symbolic_solver_labels": False}) _test, _base = join(currdir, "EXPORT_suffixes.test.nl"), join( currdir, "EXPORT_suffixes_float.baseline.nl") self.assertTrue(cmp(_test, _base), msg="Files %s and %s differ" % (_test, _base))
def _write_scenario_NL(worker, scenario, output_directory, linking_suffix_name, objective_suffix_name): from pyomo.repn.plugins.ampl import ProblemWriter_nl assert os.path.exists(output_directory) instance = scenario._instance # # linking variable suffix # bySymbol = instance._ScenarioTreeSymbolMap.bySymbol instance.del_component(linking_suffix_name) instance.add_component(linking_suffix_name, Suffix(direction=Suffix.EXPORT)) linking_suffix = getattr(instance, linking_suffix_name) # Loop over all nodes for the scenario except the leaf node, # which has no blended variables for node in scenario._node_list[:-1]: for variable_id in node._standard_variable_ids: linking_suffix[bySymbol[variable_id]] = variable_id # # objective weight suffix # instance.del_component(objective_suffix_name) instance.add_component(objective_suffix_name, Suffix(direction=Suffix.EXPORT)) getattr(instance, objective_suffix_name)[instance] = \ scenario._probability output_filename = os.path.join(output_directory, str(scenario.name) + ".nl") with ProblemWriter_nl() as nl_writer: nl_writer(instance, output_filename, lambda x: True, {}) instance.del_component(linking_suffix_name) instance.del_component(objective_suffix_name)
def _create_model(self): model = ConcreteModel() model.dual = Suffix(direction=Suffix.IMPORT,default=0.0) for stage in self._ph._scenario_tree._stages[:-1]: # all blended stages for tree_node in stage._tree_nodes: setattr(model,tree_node._name,Block()) block = getattr(model,tree_node._name) block.var_to_id = {} block.id_to_var = [] for cntr, (var,index) in enumerate((name,idx) for name, indices in iteritems(tree_node._variable_indices) for idx in indices): block.var_to_id[var,index] = cntr block.id_to_var.append((var,index)) block.var_index = RangeSet(0,len(block.id_to_var)-1) self._model = model
def _add_local_var_suffix(self, disjunct): # If the Suffix is there, we will borrow it. If not, we make it. If it's # something else, we complain. localSuffix = disjunct.component("LocalVars") if localSuffix is None: disjunct.LocalVars = Suffix(direction=Suffix.LOCAL) else: if localSuffix.ctype is Suffix: return raise GDP_Error( "A component called 'LocalVars' is declared on " "Disjunct %s, but it is of type %s, not Suffix." % (disjunct.getname(fully_qualified=True, name_buffer=NAME_BUFFER), localSuffix.ctype))
def model_is_valid(solve_data, config): """Validate that the model is solveable by MindtPy. Also preforms some preprocessing such as moving the objective to the constraints. """ m = solve_data.working_model MindtPy = m.MindtPy_utils # Check for any integer variables if any(True for v in m.component_data_objects(ctype=Var, descend_into=True) if v.is_integer() and not v.fixed): raise ValueError('Model contains unfixed integer variables. ' 'MindtPy does not currently support solution of ' 'such problems.') # TODO add in the reformulation using base 2 # Handle LP/NLP being passed to the solver prob = solve_data.results.problem if (prob.number_of_binary_variables == 0 and prob.number_of_integer_variables == 0 and prob.number_of_disjunctions == 0): config.logger.info('Problem has no discrete decisions.') if len(MindtPy.working_nonlinear_constraints) > 0: config.logger.info("Your model is an NLP (nonlinear program). " "Using NLP solver %s to solve." % config.nlp) SolverFactory(config.nlp).solve(solve_data.original_model, **config.nlp_options) return False else: config.logger.info("Your model is an LP (linear program). " "Using LP solver %s to solve." % config.mip) SolverFactory(config.mip).solve(solve_data.original_model, **config.mip_options) return False if not hasattr(m, 'dual'): # Set up dual value reporting m.dual = Suffix(direction=Suffix.IMPORT) # TODO if any continuous variables are multipled with binary ones, need # to do some kind of transformation (Glover?) or throw an error message return True
def model_is_valid(solve_data, config): """Validate that the model is solveable by MindtPy. Also preforms some preprocessing such as moving the objective to the constraints. """ m = solve_data.working_model MindtPy = m.MindtPy_utils # Handle LP/NLP being passed to the solver prob = solve_data.results.problem if (prob.number_of_binary_variables == 0 and prob.number_of_integer_variables == 0 and prob.number_of_disjunctions == 0): config.logger.info('Problem has no discrete decisions.') obj = next(m.component_data_objects(ctype=Objective, active=True)) if (any(c.body.polynomial_degree() not in (1, 0) for c in MindtPy.constraint_list) or obj.expr.polynomial_degree() not in (1, 0)): config.logger.info("Your model is an NLP (nonlinear program). " "Using NLP solver %s to solve." % config.nlp_solver) SolverFactory(config.nlp_solver).solve(solve_data.original_model, **config.nlp_solver_args) return False else: config.logger.info("Your model is an LP (linear program). " "Using LP solver %s to solve." % config.mip_solver) mipopt = SolverFactory(config.mip_solver) if isinstance(mipopt, PersistentSolver): mipopt.set_instance(solve_data.original_model) mipopt.solve(solve_data.original_model, **config.mip_solver_args) return False if not hasattr(m, 'dual'): # Set up dual value reporting m.dual = Suffix(direction=Suffix.IMPORT) # TODO if any continuous variables are multipled with binary ones, need # to do some kind of transformation (Glover?) or throw an error message return True
def post_ph_initialization(self, ph): print("Called after PH initialization!") print("Writing out PySP files for input to Schur IP") output_directory_name = "schurip" os.system("rm -rf " + output_directory_name) os.mkdir(output_directory_name) nl_writer = WriterFactory('nl') root_node = ph._scenario_tree.findRootNode() scenario_number = 1 for instance_name, instance in iteritems(ph._instances): # even though they are identical, SchurIP wants a .lqm file per scenario. # so tag the suffix data on a per-instance basis. instance.lqm = Suffix(direction=Suffix.LOCAL) for variable_name, variable_indices in iteritems( root_node._variable_indices): variable = getattr(instance, variable_name) for index in variable_indices: var_value = variable[index] instance.lqm.set_value(var_value, 1) scenario_output_filename = output_directory_name + os.sep + "Scenario" + str( scenario_number) + ".nl" result = nl_writer(instance, scenario_output_filename, lambda x: True, ph._symbolic_solver_labels) scenario_number += 1 print("NL files for PySP instance written to output directory: " + output_directory_name) sys.exit(0)
def solve(self, model, **kwds): config = self.CONFIG(kwds.pop('options', {})) config.set_value(kwds) return SolverFactory('gdpopt').solve( model, strategy='LBB', minlp_solver=config.solver, minlp_solver_args=config.solver_args, tee=config.tee, check_sat=config.check_sat, logger=config.logger, time_limit=config.time_limit) # Validate model to be used with gdpbb self.validate_model(model) # Set solver as an MINLP solve_data = GDPbbSolveData() solve_data.timing = Container() solve_data.original_model = model solve_data.results = SolverResults() old_logger_level = config.logger.getEffectiveLevel() with time_code(solve_data.timing, 'total', is_main_timer=True), \ restore_logger_level(config.logger), \ create_utility_block(model, 'GDPbb_utils', solve_data): if config.tee and old_logger_level > logging.INFO: # If the logger does not already include INFO, include it. config.logger.setLevel(logging.INFO) config.logger.info( "Starting GDPbb version %s using %s as subsolver" % (".".join(map(str, self.version())), config.solver)) # Setup results solve_data.results.solver.name = 'GDPbb - %s' % (str( config.solver)) setup_results_object(solve_data, config) # clone original model for root node of branch and bound root = solve_data.working_model = solve_data.original_model.clone() # get objective sense process_objective(solve_data, config) objectives = solve_data.original_model.component_data_objects( Objective, active=True) obj = next(objectives, None) solve_data.results.problem.sense = obj.sense # set up lists to keep track of which disjunctions have been covered. # this list keeps track of the relaxed disjunctions root.GDPbb_utils.unenforced_disjunctions = list( disjunction for disjunction in root.GDPbb_utils.disjunction_list if disjunction.active) root.GDPbb_utils.deactivated_constraints = ComponentSet([ constr for disjunction in root.GDPbb_utils.unenforced_disjunctions for disjunct in disjunction.disjuncts for constr in disjunct.component_data_objects(ctype=Constraint, active=True) if constr.body.polynomial_degree() not in (1, 0) ]) # Deactivate nonlinear constraints in unenforced disjunctions for constr in root.GDPbb_utils.deactivated_constraints: constr.deactivate() # Add the BigM suffix if it does not already exist. Used later during nonlinear constraint activation. if not hasattr(root, 'BigM'): root.BigM = Suffix() # Pre-screen that none of the disjunctions are already predetermined due to the disjuncts being fixed # to True/False values. # TODO this should also be done within the loop, but we aren't handling it right now. # Should affect efficiency, but not correctness. root.GDPbb_utils.disjuncts_fixed_True = ComponentSet() # Only find top-level (non-nested) disjunctions for disjunction in root.component_data_objects(Disjunction, active=True): fixed_true_disjuncts = [ disjunct for disjunct in disjunction.disjuncts if disjunct.indicator_var.fixed and disjunct.indicator_var.value == 1 ] fixed_false_disjuncts = [ disjunct for disjunct in disjunction.disjuncts if disjunct.indicator_var.fixed and disjunct.indicator_var.value == 0 ] for disjunct in fixed_false_disjuncts: disjunct.deactivate() if len(fixed_false_disjuncts) == len( disjunction.disjuncts) - 1: # all but one disjunct in the disjunction is fixed to False. Remaining one must be true. if not fixed_true_disjuncts: fixed_true_disjuncts = [ disjunct for disjunct in disjunction.disjuncts if disjunct not in fixed_false_disjuncts ] # Reactivate the fixed-true disjuncts for disjunct in fixed_true_disjuncts: newly_activated = ComponentSet() for constr in disjunct.component_data_objects(Constraint): if constr in root.GDPbb_utils.deactivated_constraints: newly_activated.add(constr) constr.activate() # Set the big M value for the constraint root.BigM[constr] = 1 # Note: we use a default big M value of 1 # because all non-selected disjuncts should be deactivated. # Therefore, none of the big M transformed nonlinear constraints will need to be relaxed. # The default M value should therefore be irrelevant. root.GDPbb_utils.deactivated_constraints -= newly_activated root.GDPbb_utils.disjuncts_fixed_True.add(disjunct) if fixed_true_disjuncts: assert disjunction.xor, "GDPbb only handles disjunctions in which one term can be selected. " \ "%s violates this assumption." % (disjunction.name, ) root.GDPbb_utils.unenforced_disjunctions.remove( disjunction) # Check satisfiability if config.check_sat and satisfiable(root, config.logger) is False: # Problem is not satisfiable. Problem is infeasible. obj_value = obj_sign * float('inf') else: # solve the root node config.logger.info("Solving the root node.") obj_value, result, var_values = self.subproblem_solve( root, config) if obj_sign * obj_value == float('inf'): config.logger.info( "Model was found to be infeasible at the root node. Elapsed %.2f seconds." % get_main_elapsed_time(solve_data.timing)) if solve_data.results.problem.sense == minimize: solve_data.results.problem.lower_bound = float('inf') solve_data.results.problem.upper_bound = None else: solve_data.results.problem.lower_bound = None solve_data.results.problem.upper_bound = float('-inf') solve_data.results.solver.timing = solve_data.timing solve_data.results.solver.iterations = 0 solve_data.results.solver.termination_condition = tc.infeasible return solve_data.results # initialize minheap for Branch and Bound algorithm # Heap structure: (ordering tuple, model) # Ordering tuple: (objective value, disjunctions_left, -total_nodes_counter) # - select solutions with lower objective value, # then fewer disjunctions left to explore (depth first), # then more recently encountered (tiebreaker) heap = [] total_nodes_counter = 0 disjunctions_left = len(root.GDPbb_utils.unenforced_disjunctions) heapq.heappush(heap, ((obj_sign * obj_value, disjunctions_left, -total_nodes_counter), root, result, var_values)) # loop to branch through the tree while len(heap) > 0: # pop best model off of heap sort_tuple, incumbent_model, incumbent_results, incumbent_var_values = heapq.heappop( heap) incumbent_obj_value, disjunctions_left, _ = sort_tuple config.logger.info( "Exploring node with LB %.10g and %s inactive disjunctions." % (incumbent_obj_value, disjunctions_left)) # if all the originally active disjunctions are active, solve and # return solution if disjunctions_left == 0: config.logger.info("Model solved.") # Model is solved. Copy over solution values. original_model = solve_data.original_model for orig_var, val in zip( original_model.GDPbb_utils.variable_list, incumbent_var_values): orig_var.value = val solve_data.results.problem.lower_bound = incumbent_results.problem.lower_bound solve_data.results.problem.upper_bound = incumbent_results.problem.upper_bound solve_data.results.solver.timing = solve_data.timing solve_data.results.solver.iterations = total_nodes_counter solve_data.results.solver.termination_condition = incumbent_results.solver.termination_condition return solve_data.results # Pick the next disjunction to branch on next_disjunction = incumbent_model.GDPbb_utils.unenforced_disjunctions[ 0] config.logger.info("Branching on disjunction %s" % next_disjunction.name) assert next_disjunction.xor, "GDPbb only handles disjunctions in which one term can be selected. " \ "%s violates this assumption." % (next_disjunction.name, ) new_nodes_counter = 0 for i, disjunct in enumerate(next_disjunction.disjuncts): # Create one branch for each of the disjuncts on the disjunction if any(disj.indicator_var.fixed and disj.indicator_var.value == 1 for disj in next_disjunction.disjuncts if disj is not disjunct): # If any other disjunct is fixed to 1 and an xor relationship applies, # then this disjunct cannot be activated. continue # Check time limit if get_main_elapsed_time( solve_data.timing) >= config.time_limit: if solve_data.results.problem.sense == minimize: solve_data.results.problem.lower_bound = incumbent_obj_value solve_data.results.problem.upper_bound = float( 'inf') else: solve_data.results.problem.lower_bound = float( '-inf') solve_data.results.problem.upper_bound = incumbent_obj_value config.logger.info('GDPopt unable to converge bounds ' 'before time limit of {} seconds. ' 'Elapsed: {} seconds'.format( config.time_limit, get_main_elapsed_time( solve_data.timing))) config.logger.info( 'Final bound values: LB: {} UB: {}'.format( solve_data.results.problem.lower_bound, solve_data.results.problem.upper_bound)) solve_data.results.solver.timing = solve_data.timing solve_data.results.solver.iterations = total_nodes_counter solve_data.results.solver.termination_condition = tc.maxTimeLimit return solve_data.results # Branch on the disjunct child = incumbent_model.clone() # TODO I am leaving the old branching system in place, but there should be # something better, ideally that deals with nested disjunctions as well. disjunction_to_branch = child.GDPbb_utils.unenforced_disjunctions.pop( 0) child_disjunct = disjunction_to_branch.disjuncts[i] child_disjunct.indicator_var.fix(1) # Deactivate (and fix to 0) other disjuncts on the disjunction for disj in disjunction_to_branch.disjuncts: if disj is not child_disjunct: disj.deactivate() # Activate nonlinear constraints on the newly fixed child disjunct newly_activated = ComponentSet() for constr in child_disjunct.component_data_objects( Constraint): if constr in child.GDPbb_utils.deactivated_constraints: newly_activated.add(constr) constr.activate() # Set the big M value for the constraint child.BigM[constr] = 1 # Note: we use a default big M value of 1 # because all non-selected disjuncts should be deactivated. # Therefore, none of the big M transformed nonlinear constraints will need to be relaxed. # The default M value should therefore be irrelevant. child.GDPbb_utils.deactivated_constraints -= newly_activated child.GDPbb_utils.disjuncts_fixed_True.add(child_disjunct) if disjunct in incumbent_model.GDPbb_utils.disjuncts_fixed_True: # If the disjunct was already branched to True from a parent disjunct branching, just pass # through the incumbent value without resolving. The solution should be the same as the parent. total_nodes_counter += 1 ordering_tuple = (obj_sign * incumbent_obj_value, disjunctions_left - 1, -total_nodes_counter) heapq.heappush(heap, (ordering_tuple, child, result, incumbent_var_values)) new_nodes_counter += 1 continue if config.check_sat and satisfiable( child, config.logger) is False: # Problem is not satisfiable. Skip this disjunct. continue obj_value, result, var_values = self.subproblem_solve( child, config) total_nodes_counter += 1 ordering_tuple = (obj_sign * obj_value, disjunctions_left - 1, -total_nodes_counter) heapq.heappush(heap, (ordering_tuple, child, result, var_values)) new_nodes_counter += 1 config.logger.info( "Added %s new nodes with %s relaxed disjunctions to the heap. Size now %s." % (new_nodes_counter, disjunctions_left - 1, len(heap)))
def solve(self, model, **kwds): """Solve the model. Warning: this solver is still in beta. Keyword arguments subject to change. Undocumented keyword arguments definitely subject to change. Warning: at this point in time, if you try to use PSC or GBD with anything other than IPOPT as the NLP solver, bad things will happen. This is because the suffixes are not in place to extract dual values from the variable bounds for any other solver. TODO: fix needed with the GBD implementation. Args: model (Block): a Pyomo model or block to be solved """ config = self.CONFIG(kwds.pop('options', {})) config.set_value(kwds) # configuration confirmation if config.single_tree: config.iteration_limit = 1 config.add_slack = False config.add_nogood_cuts = False config.mip_solver = 'cplex_persistent' config.logger.info( "Single tree implementation is activated. The defalt MIP solver is 'cplex_persistent'" ) # if the slacks fix to zero, just don't add them if config.max_slack == 0.0: config.add_slack = False if config.strategy == "GOA": config.add_nogood_cuts = True config.add_slack = True config.use_mcpp = True config.integer_to_binary = True config.use_dual = False config.use_fbbt = True if config.nlp_solver == "baron": config.use_dual = False # if ecp tolerance is not provided use bound tolerance if config.ecp_tolerance is None: config.ecp_tolerance = config.bound_tolerance # if the objective function is a constant, dual bound constraint is not added. obj = next(model.component_data_objects(ctype=Objective, active=True)) if obj.expr.polynomial_degree() == 0: config.use_dual_bound = False solve_data = MindtPySolveData() solve_data.results = SolverResults() solve_data.timing = Container() solve_data.curr_int_sol = [] solve_data.prev_int_sol = [] if config.use_fbbt: fbbt(model) config.logger.info( "Use the fbbt to tighten the bounds of variables") solve_data.original_model = model solve_data.working_model = model.clone() if config.integer_to_binary: TransformationFactory('contrib.integer_to_binary'). \ apply_to(solve_data.working_model) new_logging_level = logging.INFO if config.tee else None with time_code(solve_data.timing, 'total', is_main_timer=True), \ lower_logger_level_to(config.logger, new_logging_level), \ create_utility_block(solve_data.working_model, 'MindtPy_utils', solve_data): config.logger.info("---Starting MindtPy---") MindtPy = solve_data.working_model.MindtPy_utils setup_results_object(solve_data, config) process_objective(solve_data, config, use_mcpp=config.use_mcpp) # Save model initial values. solve_data.initial_var_values = list( v.value for v in MindtPy.variable_list) # Store the initial model state as the best solution found. If we # find no better solution, then we will restore from this copy. solve_data.best_solution_found = None solve_data.best_solution_found_time = None # Record solver name solve_data.results.solver.name = 'MindtPy' + str(config.strategy) # Validate the model to ensure that MindtPy is able to solve it. if not model_is_valid(solve_data, config): return # Create a model block in which to store the generated feasibility # slack constraints. Do not leave the constraints on by default. feas = MindtPy.MindtPy_feas = Block() feas.deactivate() feas.feas_constraints = ConstraintList( doc='Feasibility Problem Constraints') # Create a model block in which to store the generated linear # constraints. Do not leave the constraints on by default. lin = MindtPy.MindtPy_linear_cuts = Block() lin.deactivate() # Integer cuts exclude particular discrete decisions lin.integer_cuts = ConstraintList(doc='integer cuts') # Feasible integer cuts exclude discrete realizations that have # been explored via an NLP subproblem. Depending on model # characteristics, the user may wish to revisit NLP subproblems # (with a different initialization, for example). Therefore, these # cuts are not enabled by default. # # Note: these cuts will only exclude integer realizations that are # not already in the primary integer_cuts ConstraintList. lin.feasible_integer_cuts = ConstraintList( doc='explored integer cuts') lin.feasible_integer_cuts.deactivate() # Set up iteration counters solve_data.nlp_iter = 0 solve_data.mip_iter = 0 solve_data.mip_subiter = 0 # set up bounds solve_data.LB = float('-inf') solve_data.UB = float('inf') solve_data.LB_progress = [solve_data.LB] solve_data.UB_progress = [solve_data.UB] if config.single_tree and config.add_nogood_cuts: solve_data.stored_bound = {} if config.strategy == 'GOA' and config.add_nogood_cuts: solve_data.num_no_good_cuts_added = {} # Set of NLP iterations for which cuts were generated lin.nlp_iters = Set(dimen=1) # Set of MIP iterations for which cuts were generated in ECP lin.mip_iters = Set(dimen=1) if config.feasibility_norm == 'L1' or config.feasibility_norm == 'L2': feas.nl_constraint_set = Set( initialize=[ i for i, constr in enumerate(MindtPy.constraint_list, 1) if constr.body.polynomial_degree() not in (1, 0) ], doc="Integer index set over the nonlinear constraints." "The set corresponds to the index of nonlinear constraint in constraint_set" ) # Create slack variables for feasibility problem feas.slack_var = Var(feas.nl_constraint_set, domain=NonNegativeReals, initialize=1) else: feas.slack_var = Var(domain=NonNegativeReals, initialize=1) # Create slack variables for OA cuts if config.add_slack: lin.slack_vars = VarList(bounds=(0, config.max_slack), initialize=0, domain=NonNegativeReals) # Flag indicating whether the solution improved in the past # iteration or not solve_data.solution_improved = False if config.nlp_solver == 'ipopt': if not hasattr(solve_data.working_model, 'ipopt_zL_out'): solve_data.working_model.ipopt_zL_out = Suffix( direction=Suffix.IMPORT) if not hasattr(solve_data.working_model, 'ipopt_zU_out'): solve_data.working_model.ipopt_zU_out = Suffix( direction=Suffix.IMPORT) # Initialize the master problem with time_code(solve_data.timing, 'initialization'): MindtPy_initialize_master(solve_data, config) # Algorithm main loop with time_code(solve_data.timing, 'main loop'): MindtPy_iteration_loop(solve_data, config) if solve_data.best_solution_found is not None: # Update values in original model copy_var_list_values(from_list=solve_data.best_solution_found. MindtPy_utils.variable_list, to_list=MindtPy.variable_list, config=config) # MindtPy.objective_value.set_value( # value(solve_data.working_objective_expr, exception=False)) copy_var_list_values( MindtPy.variable_list, solve_data.original_model.component_data_objects(Var), config) solve_data.results.problem.lower_bound = solve_data.LB solve_data.results.problem.upper_bound = solve_data.UB solve_data.results.solver.timing = solve_data.timing solve_data.results.solver.user_time = solve_data.timing.total solve_data.results.solver.wallclock_time = solve_data.timing.total solve_data.results.solver.iterations = solve_data.mip_iter solve_data.results.solver.best_solution_found_time = solve_data.best_solution_found_time if config.single_tree: solve_data.results.solver.num_nodes = solve_data.nlp_iter - \ (1 if config.init_strategy == 'rNLP' else 0) return solve_data.results
def writer_test(self): # Instantiate the model class model_class = modelClass() save_filename = join( thisDir, (model_class.descrStr() + "." + test_name + ".results")) # cleanup possibly existing old test files try: os.remove(save_filename) except OSError: pass # Make sure we start from a new solver plugin # each time. We don't want them to maintain # some state that carries over between tests opt, io_options = test_case.initialize() if test_case.io == 'nl': self.assertEqual(opt.problem_format(), ProblemFormat.nl) elif test_case.io == 'lp': self.assertEqual(opt.problem_format(), ProblemFormat.cpxlp) elif test_case.io == 'mps': self.assertEqual(opt.problem_format(), ProblemFormat.mps) elif test_case.io == 'python': self.assertEqual(opt.problem_format(), None) # check that the solver plugin is at least as capable as the # test_case advertises, otherwise the plugin capabilities need # to be change or the test case should be removed if not all(opt.has_capability(tag) for tag in test_case.capabilities): self.fail("Actual plugin capabilities are less than " "that of the of test case for the plugin: " + test_case.name + ' (' + test_case.io + ')') # Create the model instance and send to the solver model_class.generateModel() model_class.warmstartModel() model = model_class.model self.assertTrue(model is not None) test_suffixes = [] if model_class.disableSuffixTests() else \ test_case.import_suffixes for suffix in test_suffixes: setattr(model, suffix, Suffix(direction=Suffix.IMPORT)) if isinstance(opt, PersistentSolver): opt.compile_instance(model, symbolic_solver_labels=symbolic_labels) # solve if opt.warm_start_capable(): results = opt.solve(model, symbolic_solver_labels=symbolic_labels, warmstart=True, load_solutions=False, **io_options) else: results = opt.solve(model, symbolic_solver_labels=symbolic_labels, load_solutions=False, **io_options) model_class.postSolveTestValidation(self, results) model.solutions.load_from( results, default_variable_value=opt.default_variable_value()) model_class.saveCurrentSolution(save_filename, suffixes=test_suffixes) # There are certain cases where the latest solver version has # a bug so this should not cause a pyomo test to fail is_expected_failure, failure_msg = \ check_expected_failures(test_case, modelClass) # validate the solution returned by the solver rc = model_class.validateCurrentSolution(suffixes=test_suffixes) if is_expected_failure: if rc[0] is True: warnings.warn("\nTest model '%s' was marked as an expected " "failure but no failure occured. The " "reason given for the expected failure " "is:\n\n****\n%s\n****\n\n" "Please remove this case as an expected " "failure if the above issue has been " "corrected in the latest version of the " "solver." % (model_class.descrStr(), failure_msg)) if _cleanup_expected_failures: os.remove(save_filename) if not rc[0]: try: model.solutions.store_to(results) except ValueError: pass self.fail("Solution mismatch for plugin " + test_case.name + ' ' + str(opt.version()) + ', ' + test_case.io + " interface and problem type " + model_class.descrStr() + "\n" + rc[1] + "\n" + (str(results.Solution(0)) if len(results.solution ) else "No Solution")) # cleanup if the test passed try: os.remove(save_filename) except OSError: pass
def solve(self, model, **kwds): """Solve the model. Warning: this solver is still in beta. Keyword arguments subject to change. Undocumented keyword arguments definitely subject to change. Warning: at this point in time, if you try to use PSC or GBD with anything other than IPOPT as the NLP solver, bad things will happen. This is because the suffixes are not in place to extract dual values from the variable bounds for any other solver. TODO: fix needed with the GBD implementation. Args: model (Block): a Pyomo model or block to be solved """ config = self.CONFIG(kwds.pop('options', {})) config.set_value(kwds) solve_data = MindtPySolveData() solve_data.results = SolverResults() solve_data.timing = Container() solve_data.original_model = model solve_data.working_model = model.clone() if config.integer_to_binary: TransformationFactory('contrib.integer_to_binary'). \ apply_to(solve_data.working_model) new_logging_level = logging.INFO if config.tee else None with time_code(solve_data.timing, 'total', is_main_timer=True), \ lower_logger_level_to(config.logger, new_logging_level), \ create_utility_block(solve_data.working_model, 'MindtPy_utils', solve_data): config.logger.info("---Starting MindtPy---") MindtPy = solve_data.working_model.MindtPy_utils setup_results_object(solve_data, config) process_objective(solve_data, config) # Save model initial values. solve_data.initial_var_values = list( v.value for v in MindtPy.variable_list) # Store the initial model state as the best solution found. If we # find no better solution, then we will restore from this copy. solve_data.best_solution_found = None # Record solver name solve_data.results.solver.name = 'MindtPy' + str(config.strategy) # Validate the model to ensure that MindtPy is able to solve it. if not model_is_valid(solve_data, config): return # Create a model block in which to store the generated feasibility # slack constraints. Do not leave the constraints on by default. feas = MindtPy.MindtPy_feas = Block() feas.deactivate() feas.feas_constraints = ConstraintList( doc='Feasibility Problem Constraints') # Create a model block in which to store the generated linear # constraints. Do not leave the constraints on by default. lin = MindtPy.MindtPy_linear_cuts = Block() lin.deactivate() # Integer cuts exclude particular discrete decisions lin.integer_cuts = ConstraintList(doc='integer cuts') # Feasible integer cuts exclude discrete realizations that have # been explored via an NLP subproblem. Depending on model # characteristics, the user may wish to revisit NLP subproblems # (with a different initialization, for example). Therefore, these # cuts are not enabled by default. # # Note: these cuts will only exclude integer realizations that are # not already in the primary integer_cuts ConstraintList. lin.feasible_integer_cuts = ConstraintList( doc='explored integer cuts') lin.feasible_integer_cuts.deactivate() # Set up iteration counters solve_data.nlp_iter = 0 solve_data.mip_iter = 0 solve_data.mip_subiter = 0 # set up bounds solve_data.LB = float('-inf') solve_data.UB = float('inf') solve_data.LB_progress = [solve_data.LB] solve_data.UB_progress = [solve_data.UB] # Set of NLP iterations for which cuts were generated lin.nlp_iters = Set(dimen=1) # Set of MIP iterations for which cuts were generated in ECP lin.mip_iters = Set(dimen=1) nonlinear_constraints = [ c for c in MindtPy.constraint_list if c.body.polynomial_degree() not in (1, 0) ] lin.nl_constraint_set = RangeSet( len(nonlinear_constraints), doc="Integer index set over the nonlinear constraints") feas.constraint_set = RangeSet( len(MindtPy.constraint_list), doc="integer index set over the constraints") # # Mapping Constraint -> integer index # MindtPy.feas_map = {} # # Mapping integer index -> Constraint # MindtPy.feas_inverse_map = {} # # Generate the two maps. These maps may be helpful for later # # interpreting indices on the slack variables or generated cuts. # for c, n in zip(MindtPy.constraint_list, feas.constraint_set): # MindtPy.feas_map[c] = n # MindtPy.feas_inverse_map[n] = c # Create slack variables for OA cuts lin.slack_vars = VarList(bounds=(0, config.max_slack), initialize=0, domain=NonNegativeReals) # Create slack variables for feasibility problem feas.slack_var = Var(feas.constraint_set, domain=NonNegativeReals, initialize=1) # Flag indicating whether the solution improved in the past # iteration or not solve_data.solution_improved = False if not hasattr(solve_data.working_model, 'ipopt_zL_out'): solve_data.working_model.ipopt_zL_out = Suffix( direction=Suffix.IMPORT) if not hasattr(solve_data.working_model, 'ipopt_zU_out'): solve_data.working_model.ipopt_zU_out = Suffix( direction=Suffix.IMPORT) # Initialize the master problem with time_code(solve_data.timing, 'initialization'): MindtPy_initialize_master(solve_data, config) # Algorithm main loop with time_code(solve_data.timing, 'main loop'): MindtPy_iteration_loop(solve_data, config) if solve_data.best_solution_found is not None: # Update values in original model copy_var_list_values(from_list=solve_data.best_solution_found. MindtPy_utils.variable_list, to_list=MindtPy.variable_list, config=config) # MindtPy.objective_value.set_value( # value(solve_data.working_objective_expr, exception=False)) copy_var_list_values( MindtPy.variable_list, solve_data.original_model.component_data_objects(Var), config) solve_data.results.problem.lower_bound = solve_data.LB solve_data.results.problem.upper_bound = solve_data.UB solve_data.results.solver.timing = solve_data.timing solve_data.results.solver.user_time = solve_data.timing.total solve_data.results.solver.wallclock_time = solve_data.timing.total solve_data.results.solver.iterations = solve_data.mip_iter return solve_data.results
def _transform_disjunct(self, obj, transBlock, varSet, localVars): # deactivated should only come from the user if not obj.active: if obj.indicator_var.is_fixed(): if value(obj.indicator_var) == 0: # The user cleanly deactivated the disjunct: there # is nothing for us to do here. return else: raise GDP_Error( "The disjunct '%s' is deactivated, but the " "indicator_var is fixed to %s. This makes no sense." % (obj.name, value(obj.indicator_var))) if obj._transformation_block is None: raise GDP_Error( "The disjunct '%s' is deactivated, but the " "indicator_var is not fixed and the disjunct does not " "appear to have been relaxed. This makes no sense. " "(If the intent is to deactivate the disjunct, fix its " "indicator_var to 0.)" % (obj.name, )) if obj._transformation_block is not None: # we've transformed it, which means this is the second time it's # appearing in a Disjunction raise GDP_Error( "The disjunct '%s' has been transformed, but a disjunction " "it appears in has not. Putting the same disjunct in " "multiple disjunctions is not supported." % obj.name) # create a relaxation block for this disjunct relaxedDisjuncts = transBlock.relaxedDisjuncts relaxationBlock = relaxedDisjuncts[len(relaxedDisjuncts)] relaxationBlock.localVarReferences = Block() # Put the disaggregated variables all on their own block so that we can # isolate the name collisions and still have complete control over the # names on this block. (This is for peace of mind now, but will matter # in the future for adding the binaries corresponding to Boolean # indicator vars.) relaxationBlock.disaggregatedVars = Block() # add the map that will link back and forth between transformed # constraints and their originals. relaxationBlock._constraintMap = { 'srcConstraints': ComponentMap(), 'transformedConstraints': ComponentMap() } # Map between disaggregated variables for this disjunct and their # originals relaxationBlock._disaggregatedVarMap = { 'srcVar': ComponentMap(), 'disaggregatedVar': ComponentMap(), } # Map between disaggregated variables and their lb*indicator <= var <= # ub*indicator constraints relaxationBlock._bigMConstraintMap = ComponentMap() # add mappings to source disjunct (so we'll know we've relaxed) obj._transformation_block = weakref_ref(relaxationBlock) relaxationBlock._srcDisjunct = weakref_ref(obj) # add Suffix to the relaxation block that disaggregated variables are # local (in case this is nested in another Disjunct) local_var_set = None parent_disjunct = obj.parent_block() while parent_disjunct is not None: if parent_disjunct.ctype is Disjunct: break parent_disjunct = parent_disjunct.parent_block() if parent_disjunct is not None: localVarSuffix = relaxationBlock.LocalVars = Suffix( direction=Suffix.LOCAL) local_var_set = localVarSuffix[parent_disjunct] = ComponentSet() # add the disaggregated variables and their bigm constraints # to the relaxationBlock for var in varSet: lb = var.lb ub = var.ub if lb is None or ub is None: raise GDP_Error("Variables that appear in disjuncts must be " "bounded in order to use the hull " "transformation! Missing bound for %s." % (var.name)) disaggregatedVar = Var(within=Reals, bounds=(min(0, lb), max(0, ub)), initialize=var.value) # naming conflicts are possible here since this is a bunch # of variables from different blocks coming together, so we # get a unique name disaggregatedVarName = unique_component_name( relaxationBlock.disaggregatedVars, var.getname(fully_qualified=False, name_buffer=NAME_BUFFER), ) relaxationBlock.disaggregatedVars.add_component( disaggregatedVarName, disaggregatedVar) # mark this as local because we won't re-disaggregate if this is a # nested disjunction if local_var_set is not None: local_var_set.add(disaggregatedVar) # store the mappings from variables to their disaggregated selves on # the transformation block. relaxationBlock._disaggregatedVarMap['disaggregatedVar'][ var] = disaggregatedVar relaxationBlock._disaggregatedVarMap['srcVar'][ disaggregatedVar] = var bigmConstraint = Constraint(transBlock.lbub) relaxationBlock.add_component(disaggregatedVarName + "_bounds", bigmConstraint) if lb: bigmConstraint.add('lb', obj.indicator_var * lb <= disaggregatedVar) if ub: bigmConstraint.add('ub', disaggregatedVar <= obj.indicator_var * ub) relaxationBlock._bigMConstraintMap[ disaggregatedVar] = bigmConstraint for var in localVars: lb = var.lb ub = var.ub if lb is None or ub is None: raise GDP_Error("Variables that appear in disjuncts must be " "bounded in order to use the hull " "transformation! Missing bound for %s." % (var.name)) if value(lb) > 0: var.setlb(0) if value(ub) < 0: var.setub(0) # map it to itself relaxationBlock._disaggregatedVarMap['disaggregatedVar'][var] = var relaxationBlock._disaggregatedVarMap['srcVar'][var] = var # naming conflicts are possible here since this is a bunch # of variables from different blocks coming together, so we # get a unique name conName = unique_component_name( relaxationBlock, var.getname(fully_qualified=False, name_buffer=NAME_BUFFER) + \ "_bounds") bigmConstraint = Constraint(transBlock.lbub) relaxationBlock.add_component(conName, bigmConstraint) if lb: bigmConstraint.add('lb', obj.indicator_var * lb <= var) if ub: bigmConstraint.add('ub', var <= obj.indicator_var * ub) relaxationBlock._bigMConstraintMap[var] = bigmConstraint var_substitute_map = dict((id(v), newV) for v, newV in iteritems( relaxationBlock._disaggregatedVarMap['disaggregatedVar'])) zero_substitute_map = dict((id(v), ZeroConstant) for v, newV in \ iteritems( relaxationBlock._disaggregatedVarMap[ 'disaggregatedVar'])) zero_substitute_map.update((id(v), ZeroConstant) for v in localVars) # Transform each component within this disjunct self._transform_block_components(obj, obj, var_substitute_map, zero_substitute_map) # deactivate disjunct so writers can be happy obj._deactivate_without_fixing_indicator()
def apply_optimizer(data, instance=None): """ Perform optimization with a concrete instance Required: instance: Problem instance. Returned: results: Optimization results. opt: Optimizer object. """ # if not data.options.runtime.logging == 'quiet': sys.stdout.write('[%8.2f] Applying solver\n' % (time.time() - start_time)) sys.stdout.flush() # # # Create Solver and Perform Optimization # solver = data.options.solvers[0].solver_name if solver is None: raise ValueError("Problem constructing solver: no solver specified") if len(data.options.solvers[0].suffixes) > 0: for suffix_name in data.options.solvers[0].suffixes: if suffix_name[0] in ['"', "'"]: suffix_name = suffix_name[1:-1] # Don't redeclare the suffix if it already exists suffix = getattr(instance, suffix_name, None) if suffix is None: setattr(instance, suffix_name, Suffix(direction=Suffix.IMPORT)) else: raise ValueError("Problem declaring solver suffix %s. A component "\ "with that name already exists on model %s." % (suffix_name, instance.name)) if getattr(data.options.solvers[0].options, 'timelimit', 0) == 0: data.options.solvers[0].options.timelimit = None # # Default results # results = None # # Figure out the type of solver manager # solver_mngr_name = None if data.options.solvers[0].manager is None: solver_mngr_name = 'serial' elif not data.options.solvers[0].manager in SolverManagerFactory: raise ValueError("Unknown solver manager %s" % data.options.solvers[0].manager) else: solver_mngr_name = data.options.solvers[0].manager # # Create the solver manager # solver_mngr_kwds = {} with SolverManagerFactory(solver_mngr_name, **solver_mngr_kwds) as solver_mngr: if solver_mngr is None: msg = "Problem constructing solver manager '%s'" raise ValueError(msg % str(data.options.solvers[0].manager)) # # Setup keywords for the solve # keywords = {} if (data.options.runtime.keep_files or \ data.options.postsolve.print_logfile): keywords['keepfiles'] = True if data.options.model.symbolic_solver_labels: keywords['symbolic_solver_labels'] = True if data.options.model.file_determinism != 1: keywords['file_determinism'] = data.options.model.file_determinism keywords['tee'] = data.options.runtime.stream_output keywords['timelimit'] = getattr(data.options.solvers[0].options, 'timelimit', 0) keywords['report_timing'] = data.options.runtime.report_timing # FIXME: solver_io and executable are not being used # in the case of a non-serial solver manager # # Call the solver # if solver_mngr_name == 'serial': # # If we're running locally, then we create the # optimizer and pass it into the solver manager. # sf_kwds = {} sf_kwds['solver_io'] = data.options.solvers[0].io_format if data.options.solvers[0].solver_executable is not None: sf_kwds['executable'] = data.options.solvers[ 0].solver_executable with SolverFactory(solver, **sf_kwds) as opt: if opt is None: raise ValueError("Problem constructing solver `%s`" % str(solver)) for name in registered_callback: opt.set_callback(name, registered_callback[name]) if len(data.options.solvers[0].options) > 0: opt.set_options(data.options.solvers[0].options) #opt.set_options(" ".join("%s=%s" % (key, value) # for key, value in data.options.solvers[0].options.iteritems() # if not key == 'timelimit')) if not data.options.solvers[0].options_string is None: opt.set_options(data.options.solvers[0].options_string) # # Use the solver manager to call the optimizer # results = solver_mngr.solve(instance, opt=opt, **keywords) else: # # Get the solver option arguments # if len( data.options.solvers[0].options ) > 0 and not data.options.solvers[0].options_string is None: # If both 'options' and 'options_string' were specified, then create a # single options string that is passed to the solver. ostring = " ".join("%s=%s" % (key, value) for key, value in data.options.solvers[0].options.iteritems() if not value is None) keywords['options'] = ostring + ' ' + data.options.solvers[ 0].options_string elif len(data.options.solvers[0].options) > 0: keywords['options'] = data.options.solvers[0].options else: keywords['options'] = data.options.solvers[0].options_string # # If we're running remotely, then we pass the optimizer name to the solver # manager. # results = solver_mngr.solve(instance, opt=solver, **keywords) if data.options.runtime.profile_memory >= 1 and pympler_available: global memory_data mem_used = pympler.muppy.get_size(pympler.muppy.get_objects()) if mem_used > data.local.max_memory: data.local.max_memory = mem_used print(" Total memory = %d bytes following optimization" % mem_used) return Bunch(results=results, opt=solver, local=data.local)
def _perform_branch_and_bound(solve_data): solve_data.explored_nodes = 0 root_node = solve_data.working_model root_util_blk = root_node.GDPopt_utils config = solve_data.config # Map unfixed disjunct -> list of deactivated constraints root_util_blk.disjunct_to_nonlinear_constraints = ComponentMap() # Map relaxed disjunctions -> list of unfixed disjuncts root_util_blk.disjunction_to_unfixed_disjuncts = ComponentMap() # Preprocess the active disjunctions for disjunction in root_util_blk.disjunction_list: assert disjunction.active disjuncts_fixed_True = [] disjuncts_fixed_False = [] unfixed_disjuncts = [] # categorize the disjuncts in the disjunction for disjunct in disjunction.disjuncts: if disjunct.indicator_var.fixed: if disjunct.indicator_var.value == 1: disjuncts_fixed_True.append(disjunct) elif disjunct.indicator_var.value == 0: disjuncts_fixed_False.append(disjunct) else: pass # raise error for fractional value? else: unfixed_disjuncts.append(disjunct) # update disjunct lists for predetermined disjunctions if len(disjuncts_fixed_False) == len(disjunction.disjuncts) - 1: # all but one disjunct in the disjunction is fixed to False. # Remaining one must be true. If not already fixed to True, do so. if not disjuncts_fixed_True: disjuncts_fixed_True = unfixed_disjuncts unfixed_disjuncts = [] disjuncts_fixed_True[0].indicator_var.fix(1) elif disjuncts_fixed_True and disjunction.xor: assert len( disjuncts_fixed_True ) == 1, "XOR (only one True) violated: %s" % disjunction.name disjuncts_fixed_False.extend(unfixed_disjuncts) unfixed_disjuncts = [] # Make sure disjuncts fixed to False are properly deactivated. for disjunct in disjuncts_fixed_False: disjunct.deactivate() # Deactivate nonlinear constraints in unfixed disjuncts for disjunct in unfixed_disjuncts: nonlinear_constraints_in_disjunct = [ constr for constr in disjunct.component_data_objects(Constraint, active=True) if constr.body.polynomial_degree() not in _linear_degrees ] for constraint in nonlinear_constraints_in_disjunct: constraint.deactivate() if nonlinear_constraints_in_disjunct: # TODO might be worthwhile to log number of nonlinear constraints in each disjunction # for later branching purposes root_util_blk.disjunct_to_nonlinear_constraints[ disjunct] = nonlinear_constraints_in_disjunct root_util_blk.disjunction_to_unfixed_disjuncts[ disjunction] = unfixed_disjuncts pass # Add the BigM suffix if it does not already exist. Used later during nonlinear constraint activation. # TODO is this still necessary? if not hasattr(root_node, 'BigM'): root_node.BigM = Suffix() # Set up the priority queue queue = solve_data.bb_queue = [] solve_data.created_nodes = 0 unbranched_disjunction_indices = [ i for i, disjunction in enumerate(root_util_blk.disjunction_list) if disjunction in root_util_blk.disjunction_to_unfixed_disjuncts ] sort_tuple = BBNodeData( obj_lb=float('-inf'), obj_ub=float('inf'), is_screened=False, is_evaluated=False, num_unbranched_disjunctions=len(unbranched_disjunction_indices), node_count=0, unbranched_disjunction_indices=unbranched_disjunction_indices, ) heappush(queue, (sort_tuple, root_node)) # Do the branch and bound while len(queue) > 0: # visit the top node on the heap # from pprint import pprint # pprint([( # x[0].node_count, x[0].obj_lb, x[0].obj_ub, x[0].num_unbranched_disjunctions # ) for x in sorted(queue)]) node_data, node_model = heappop(queue) config.logger.info("Nodes: %s LB %.10g Unbranched %s" % (solve_data.explored_nodes, node_data.obj_lb, node_data.num_unbranched_disjunctions)) # Check time limit elapsed = get_main_elapsed_time(solve_data.timing) if elapsed >= config.time_limit: config.logger.info('GDPopt-LBB unable to converge bounds ' 'before time limit of {} seconds. ' 'Elapsed: {} seconds'.format( config.time_limit, elapsed)) no_feasible_soln = float('inf') solve_data.LB = node_data.obj_lb if solve_data.objective_sense == minimize else -no_feasible_soln solve_data.UB = no_feasible_soln if solve_data.objective_sense == minimize else -node_data.obj_lb config.logger.info('Final bound values: LB: {} UB: {}'.format( solve_data.LB, solve_data.UB)) solve_data.results.solver.termination_condition = tc.maxTimeLimit return True # Handle current node if not node_data.is_screened: # Node has not been evaluated. solve_data.explored_nodes += 1 new_node_data = _prescreen_node(node_data, node_model, solve_data) heappush( queue, (new_node_data, node_model)) # replace with updated node data elif node_data.obj_lb < node_data.obj_ub - config.bound_tolerance and not node_data.is_evaluated: # Node has not been fully evaluated. # Note: infeasible and unbounded nodes will skip this condition, because of strict inequality new_node_data = _evaluate_node(node_data, node_model, solve_data) heappush( queue, (new_node_data, node_model)) # replace with updated node data elif node_data.num_unbranched_disjunctions == 0 or node_data.obj_lb == float( 'inf'): # We have reached a leaf node, or the best available node is infeasible. original_model = solve_data.original_model copy_var_list_values( from_list=node_model.GDPopt_utils.variable_list, to_list=original_model.GDPopt_utils.variable_list, config=config, ) solve_data.LB = node_data.obj_lb if solve_data.objective_sense == minimize else -node_data.obj_ub solve_data.UB = node_data.obj_ub if solve_data.objective_sense == minimize else -node_data.obj_lb solve_data.master_iteration = solve_data.explored_nodes if node_data.obj_lb == float('inf'): solve_data.results.solver.termination_condition = tc.infeasible elif node_data.obj_ub == float('-inf'): solve_data.results.solver.termination_condition = tc.unbounded else: solve_data.results.solver.termination_condition = tc.optimal return else: _branch_on_node(node_data, node_model, solve_data)