def Pyomo2FuncDesigner(instance): if not FD_available: return None ipoint = {} vars = {} sense = None nobj = 0 smap = SymbolMap() _f_name = [] _f = [] _c = [] for con in instance.component_data_objects(Constraint, active=True): body = Pyomo2FD_expression(con.body, ipoint, vars, smap) if not con.lower is None: lower = Pyomo2FD_expression(con.lower, ipoint, vars, smap) _c.append( body > lower ) if not con.upper is None: upper = Pyomo2FD_expression(con.upper, ipoint, vars, smap) _c.append( body < upper ) for var in instance.component_data_objects(Var, active=True): body = Pyomo2FD_expression(var, ipoint, vars, smap) if not var.lb is None: lower = Pyomo2FD_expression(var.lb, ipoint, vars, smap) _c.append( body > lower ) if not var.ub is None: upper = Pyomo2FD_expression(var.ub, ipoint, vars, smap) _c.append( body < upper ) for obj in instance.component_data_objects(Objective, active=True): nobj += 1 if obj.is_minimizing(): _f.append( Pyomo2FD_expression(obj.expr, ipoint, vars, smap) ) else: _f.append( - Pyomo2FD_expression(obj.expr, ipoint, vars, smap) ) _f_name.append(obj.name) smap.getSymbol(obj, lambda objective: objective.name) # TODO - use 0.0 for default values??? # TODO - create results map S = FuncDesigner.oosystem() S._symbol_map = smap S.f = _f[0] S._f_name = _f_name S.constraints.update(_c) S.initial_point = ipoint S.sense = sense return S
def Pyomo2FuncDesigner(instance): if not FD_available: return None ipoint = {} vars = {} sense = None nobj = 0 smap = SymbolMap() _f_name = [] _f = [] _c = [] for con in instance.component_data_objects(Constraint, active=True): body = Pyomo2FD_expression(con.body, ipoint, vars, smap) if not con.lower is None: lower = Pyomo2FD_expression(con.lower, ipoint, vars, smap) _c.append( body > lower ) if not con.upper is None: upper = Pyomo2FD_expression(con.upper, ipoint, vars, smap) _c.append( body < upper ) for var in instance.component_data_objects(Var, active=True): body = Pyomo2FD_expression(var, ipoint, vars, smap) if not var.lb is None: lower = Pyomo2FD_expression(var.lb, ipoint, vars, smap) _c.append( body > lower ) if not var.ub is None: upper = Pyomo2FD_expression(var.ub, ipoint, vars, smap) _c.append( body < upper ) for obj in instance.component_data_objects(Objective, active=True): nobj += 1 if obj.is_minimizing(): _f.append( Pyomo2FD_expression(obj.expr, ipoint, vars, smap) ) else: _f.append( - Pyomo2FD_expression(obj.expr, ipoint, vars, smap) ) _f_name.append( obj.cname(True) ) smap.getSymbol(obj, lambda objective: objective.cname(True)) # TODO - use 0.0 for default values??? # TODO - create results map S = FuncDesigner.oosystem() S._symbol_map = smap S.f = _f[0] S._f_name = _f_name S.constraints.update(_c) S.initial_point = ipoint S.sense = sense return S
def __call__(self, model, output_filename, solver_capability, io_options): # Make sure not to modify the user's dictionary, they may be # reusing it outside of this call io_options = dict(io_options) # NOTE: io_options is a simple dictionary of keyword-value # pairs specific to this writer. symbolic_solver_labels = \ io_options.pop("symbolic_solver_labels", False) labeler = io_options.pop("labeler", None) # How much effort do we want to put into ensuring the # LP file is written deterministically for a Pyomo model: # 0 : None # 1 : sort keys of indexed components (default) # 2 : sort keys AND sort names (over declaration order) file_determinism = io_options.pop("file_determinism", 1) sorter = SortComponents.unsorted if file_determinism >= 1: sorter = sorter | SortComponents.indices if file_determinism >= 2: sorter = sorter | SortComponents.alphabetical output_fixed_variable_bounds = \ io_options.pop("output_fixed_variable_bounds", False) # Skip writing constraints whose body section is fixed (i.e., # no variables) skip_trivial_constraints = \ io_options.pop("skip_trivial_constraints", False) # Note: Baron does not allow specification of runtime # option outside of this file, so we add support # for them here solver_options = io_options.pop("solver_options", {}) if len(io_options): raise ValueError( "ProblemWriter_baron_writer passed unrecognized io_options:\n\t" + "\n\t".join("%s = %s" % (k, v) for k, v in iteritems(io_options))) if symbolic_solver_labels and (labeler is not None): raise ValueError("Baron problem writer: Using both the " "'symbolic_solver_labels' and 'labeler' " "I/O options is forbidden") # Make sure there are no strange ActiveComponents. The expression # walker will handle strange things in constraints later. model_ctypes = model.collect_ctypes(active=True) invalids = set() for t in (model_ctypes - valid_active_ctypes_minlp): if issubclass(t, ActiveComponent): invalids.add(t) if len(invalids): invalids = [t.__name__ for t in invalids] raise RuntimeError( "Unallowable active component(s) %s.\nThe BARON writer cannot " "export models with this component type." % ", ".join(invalids)) if output_filename is None: output_filename = model.name + ".bar" output_file = open(output_filename, "w") # Process the options. Rely on baron to catch # and reset bad option values output_file.write("OPTIONS {\n") summary_found = False if len(solver_options): for key, val in iteritems(solver_options): if (key.lower() == 'summary'): summary_found = True if key.endswith("Name"): output_file.write(key + ": \"" + str(val) + "\";\n") else: output_file.write(key + ": " + str(val) + ";\n") if not summary_found: # The 'summary option is defaulted to 0, so that no # summary file is generated in the directory where the # user calls baron. Check if a user explicitly asked for # a summary file. output_file.write("Summary: 0;\n") output_file.write("}\n\n") if symbolic_solver_labels: # Note that the Var and Constraint labelers must use the # same labeler, so that we can correctly detect name # collisions (which can arise when we truncate the labels to # the max allowable length. BARON requires all identifiers # to start with a letter. We will (randomly) choose "s_" # (for 'shortened') v_labeler = c_labeler = ShortNameLabeler(15, prefix='s_', suffix='_', caseInsensitive=True, legalRegex='^[a-zA-Z]') elif labeler is None: v_labeler = NumericLabeler('x') c_labeler = NumericLabeler('c') else: v_labeler = c_labeler = labeler symbol_map = SymbolMap() symbol_map.default_labeler = v_labeler #sm_bySymbol = symbol_map.bySymbol # Cache the list of model blocks so we don't have to call # model.block_data_objects() many many times, which is slow # for indexed blocks all_blocks_list = list( model.block_data_objects(active=True, sort=sorter, descend_into=True)) active_components_data_var = {} #for block in all_blocks_list: # tmp = active_components_data_var[id(block)] = \ # list(obj for obj in block.component_data_objects(Var, # sort=sorter, # descend_into=False)) # create_symbols_func(symbol_map, tmp, labeler) # GAH: Not sure this is necessary, and also it would break for # non-mutable indexed params so I am commenting out for now. #for param_data in active_components_data(block, Param, sort=sorter): #instead of checking if param_data._mutable: #if not param_data.is_constant(): # create_symbol_func(symbol_map, param_data, labeler) #symbol_map_variable_ids = set(symbol_map.byObject.keys()) #object_symbol_dictionary = symbol_map.byObject # # Go through the objectives and constraints and generate # the output so that we can obtain the set of referenced # variables. # equation_section_stream = StringIO() referenced_variable_ids, branching_priorities_suffixes = \ self._write_equations_section( model, equation_section_stream, all_blocks_list, active_components_data_var, symbol_map, c_labeler, output_fixed_variable_bounds, skip_trivial_constraints, sorter) # # BINARY_VARIABLES, INTEGER_VARIABLES, POSITIVE_VARIABLES, VARIABLES # BinVars = [] IntVars = [] PosVars = [] Vars = [] for vid in referenced_variable_ids: name = symbol_map.byObject[vid] var_data = symbol_map.bySymbol[name]() if var_data.is_continuous(): if var_data.has_lb() and (value(var_data.lb) >= 0): TypeList = PosVars else: TypeList = Vars elif var_data.is_binary(): TypeList = BinVars elif var_data.is_integer(): TypeList = IntVars else: assert False TypeList.append(name) if len(BinVars) > 0: BinVars.sort() output_file.write('BINARY_VARIABLES ') output_file.write(", ".join(BinVars)) output_file.write(';\n\n') if len(IntVars) > 0: IntVars.sort() output_file.write('INTEGER_VARIABLES ') output_file.write(", ".join(IntVars)) output_file.write(';\n\n') PosVars.append('ONE_VAR_CONST__') PosVars.sort() output_file.write('POSITIVE_VARIABLES ') output_file.write(", ".join(PosVars)) output_file.write(';\n\n') if len(Vars) > 0: Vars.sort() output_file.write('VARIABLES ') output_file.write(", ".join(Vars)) output_file.write(';\n\n') # # LOWER_BOUNDS # lbounds = {} for vid in referenced_variable_ids: name = symbol_map.byObject[vid] var_data = symbol_map.bySymbol[name]() if var_data.fixed: if output_fixed_variable_bounds: var_data_lb = ftoa(var_data.value) else: var_data_lb = None else: var_data_lb = None if var_data.has_lb(): var_data_lb = ftoa(var_data.lb) if var_data_lb is not None: name_to_output = symbol_map.getSymbol(var_data) lbounds[name_to_output] = '%s: %s;\n' % (name_to_output, var_data_lb) if len(lbounds) > 0: output_file.write("LOWER_BOUNDS{\n") output_file.write("".join(lbounds[key] for key in sorted(lbounds.keys()))) output_file.write("}\n\n") lbounds = None # # UPPER_BOUNDS # ubounds = {} for vid in referenced_variable_ids: name = symbol_map.byObject[vid] var_data = symbol_map.bySymbol[name]() if var_data.fixed: if output_fixed_variable_bounds: var_data_ub = ftoa(var_data.value) else: var_data_ub = None else: var_data_ub = None if var_data.has_ub(): var_data_ub = ftoa(var_data.ub) if var_data_ub is not None: name_to_output = symbol_map.getSymbol(var_data) ubounds[name_to_output] = '%s: %s;\n' % (name_to_output, var_data_ub) if len(ubounds) > 0: output_file.write("UPPER_BOUNDS{\n") output_file.write("".join(ubounds[key] for key in sorted(ubounds.keys()))) output_file.write("}\n\n") ubounds = None # # BRANCHING_PRIORITIES # # Specifying priorities requires that the pyomo model has established an # EXTERNAL, float suffix called 'branching_priorities' on the model # object, indexed by the relevant variable BranchingPriorityHeader = False for suffix in branching_priorities_suffixes: for var_data, priority in iteritems(suffix): if id(var_data) not in referenced_variable_ids: continue if priority is not None: if not BranchingPriorityHeader: output_file.write('BRANCHING_PRIORITIES{\n') BranchingPriorityHeader = True name_to_output = symbol_map.getSymbol(var_data) output_file.write(name_to_output + ': ' + str(priority) + ';\n') if BranchingPriorityHeader: output_file.write("}\n\n") # # Now write the objective and equations section # output_file.write(equation_section_stream.getvalue()) # # STARTING_POINT # output_file.write('STARTING_POINT{\nONE_VAR_CONST__: 1;\n') tmp = {} for vid in referenced_variable_ids: name = symbol_map.byObject[vid] var_data = symbol_map.bySymbol[name]() starting_point = var_data.value if starting_point is not None: var_name = symbol_map.getSymbol(var_data) tmp[var_name] = "%s: %s;\n" % (var_name, ftoa(starting_point)) output_file.write("".join(tmp[key] for key in sorted(tmp.keys()))) output_file.write('}\n\n') output_file.close() return output_filename, symbol_map
class CPLEXPersistent(CPLEXDirect, PersistentSolver): """The CPLEX LP/MIP solver """ pyomo.util.plugin.alias('_cplex_persistent', doc='Persistent Python interface to the CPLEX LP/MIP solver') def __init__(self, **kwds): # # Call base class constructor # kwds['type'] = 'cplexpersistent' CPLEXDirect.__init__(self, **kwds) # maps pyomo var data labels to the corresponding CPLEX variable id. self._cplex_variable_ids = {} self._cplex_variable_names = None # # updates all variable bounds in the compiled model - handles # fixed variables and related issues. re-does everything from # scratch by default, ignoring whatever was specified # previously. if the value associated with the keyword # vars_to_update is a non-empty list (assumed to be variable name # / index pairs), then only the bounds for those variables are # updated. this function assumes that the variables themselves # already exist in the compiled model. # def compile_variable_bounds(self, pyomo_instance, vars_to_update): from pyomo.core.base import Var if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin " "cannot compile variable bounds - no " "instance is presently compiled") # the bound update entries should be name-value pairs new_lower_bounds = [] new_upper_bounds = [] # operates through side effects on the above lists! def update_bounds_lists(var_name): var_lb = None var_ub = None if var_data.fixed and self._output_fixed_variable_bounds: var_lb = var_ub = var_data.value elif var_data.fixed: # if we've been directed to not deal with fixed # variables, then skip - they should have been # compiled out of any description of the constraints return else: if not var_data.has_lb(): var_lb = -CPLEXDirect._cplex_module.infinity else: var_lb = value(var_data.lb) if not var_data.has_ub(): var_ub = CPLEXDirect._cplex_module.infinity else: var_ub= value(var_data.ub) var_cplex_id = self._cplex_variable_ids[var_name] new_lower_bounds.append((var_cplex_id, var_lb)) new_upper_bounds.append((var_cplex_id, var_ub)) if len(vars_to_update) == 0: for var_data in pyomo_instance.component_data_objects(Var, active=True): var_name = self._symbol_map.getSymbol(var_data, self._labeler) update_bounds_lists(var_name) else: for var_name, var_index in vars_to_update: var = pyomo_instance.find_component(var_name) # TBD - do some error checking! var_data = var[var_index] var_name = self._symbol_map.getSymbol(var_data, self._labeler) update_bounds_lists(var_name) self._active_cplex_instance.variables.set_lower_bounds(new_lower_bounds) self._active_cplex_instance.variables.set_upper_bounds(new_upper_bounds) # # method to compile objective of the input pyomo instance. # TBD: # it may be smarter just to track the associated pyomo instance, # and re-compile it automatically from a cached local attribute. # this would ensure consistency, among other things! # def compile_objective(self, pyomo_instance): from pyomo.core.base import Objective from pyomo.repn import canonical_is_constant, LinearCanonicalRepn, canonical_degree if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin " "cannot compile objective - no " "instance is presently compiled") cplex_instance = self._active_cplex_instance self._has_quadratic_objective = False cntr = 0 for block in pyomo_instance.block_data_objects(active=True): gen_obj_canonical_repn = \ getattr(block, "_gen_obj_canonical_repn", True) # Get/Create the ComponentMap for the repn if not hasattr(block,'_canonical_repn'): block._canonical_repn = ComponentMap() block_canonical_repn = block._canonical_repn for obj_data in block.component_data_objects(Objective, active=True, descend_into=False): cntr += 1 if cntr > 1: raise ValueError( "Multiple active objectives found on Pyomo instance '%s'. " "Solver '%s' will only handle a single active objective" \ % (pyomo_instance.name, self.type)) if obj_data.is_minimizing(): cplex_instance.objective.set_sense( cplex_instance.objective.sense.minimize) else: cplex_instance.objective.set_sense( cplex_instance.objective.sense.maximize) cplex_instance.objective.set_name( self._symbol_map.getSymbol(obj_data, self._labeler)) if gen_obj_canonical_repn: obj_repn = generate_canonical_repn(obj_data.expr) block_canonical_repn[obj_data] = obj_repn else: obj_repn = block_canonical_repn[obj_data] if (isinstance(obj_repn, LinearCanonicalRepn) and \ ((obj_repn.linear == None) or \ (len(obj_repn.linear) == 0))) or \ canonical_is_constant(obj_repn): print("Warning: Constant objective detected, replacing " "with a placeholder to prevent solver failure.") offset = obj_repn.constant if offset is None: offset = 0.0 objective_expression = [("ONE_VAR_CONSTANT",offset)] cplex_instance.objective.set_linear(objective_expression) else: if isinstance(obj_repn, LinearCanonicalRepn): objective_expression, offset = \ self._encode_constraint_body_linear_specialized( obj_repn, self._labeler, use_variable_names=False, cplex_variable_name_index_map=self._cplex_variable_ids, as_pairs=True) if offset != 0.0: objective_expression.append((self._cplex_variable_ids["ONE_VAR_CONSTANT"],offset)) cplex_instance.objective.set_linear(objective_expression) else: #Linear terms if 1 in obj_repn: objective_expression, offset = \ self._encode_constraint_body_linear( obj_repn, self._labeler, as_pairs=True) if offset != 0.0: objective_expression.append(("ONE_VAR_CONSTANT",offset)) cplex_instance.objective.set_linear(objective_expression) #Quadratic terms if 2 in obj_repn: self._has_quadratic_objective = True objective_expression = \ self._encode_constraint_body_quadratic(obj_repn, self._labeler, as_triples=True, is_obj=2.0) cplex_instance.objective.\ set_quadratic_coefficients(objective_expression) degree = canonical_degree(obj_repn) if (degree is None) or (degree > 2): raise ValueError( "CPLEXPersistent plugin does not support general nonlinear " "objective expressions (only linear or quadratic).\n" "Objective: %s" % (obj_data.name)) # # method to populate the CPLEX problem instance (interface) from # the supplied Pyomo problem instance. # def compile_instance(self, pyomo_instance, symbolic_solver_labels=False, output_fixed_variable_bounds=False, skip_trivial_constraints=False): from pyomo.core.base import Var, Constraint, SOSConstraint from pyomo.repn import canonical_is_constant, LinearCanonicalRepn, canonical_degree self._symbolic_solver_labels = symbolic_solver_labels self._output_fixed_variable_bounds = output_fixed_variable_bounds self._skip_trivial_constraints = skip_trivial_constraints self._has_quadratic_constraints = False self._has_quadratic_objective = False self._active_cplex_instance = CPLEXDirect._cplex_module.Cplex() if self._symbolic_solver_labels: labeler = self._labeler = TextLabeler() else: labeler = self._labeler = NumericLabeler('x') self._symbol_map = SymbolMap() self._instance = pyomo_instance if isinstance(pyomo_instance, IBlockStorage): # BIG HACK if not hasattr(pyomo_instance, "._symbol_maps"): setattr(pyomo_instance, "._symbol_maps", {}) getattr(pyomo_instance, "._symbol_maps")[id(self._symbol_map)] = \ self._symbol_map else: pyomo_instance.solutions.add_symbol_map(self._symbol_map) self._smap_id = id(self._symbol_map) # we use this when iterating over the constraints because it # will have a much smaller hash table, we also use this for # the warm start code after it is cleaned to only contain # variables referenced in the constraints self._variable_symbol_map = SymbolMap() # cplex wants the caller to set the problem type, which is (for # current purposes) strictly based on variable type counts. self._num_binary_variables = 0 self._num_integer_variables = 0 self._num_continuous_variables = 0 self._used_sos_constraints = False ############################################# # populate the variables in the cplex model # ############################################# var_names = [] var_lbs = [] var_ubs = [] var_types = [] self._referenced_variable_ids.clear() # maps pyomo var data labels to the corresponding CPLEX variable id. self._cplex_variable_ids.clear() # cached in the loop below - used to update the symbol map # immediately following loop termination. var_label_pairs = [] for var_data in pyomo_instance.component_data_objects(Var, active=True): if var_data.fixed and not self._output_fixed_variable_bounds: # if a variable is fixed, and we're preprocessing # fixed variables (as in not outputting them), there # is no need to add them to the compiled model. continue var_name = self._symbol_map.getSymbol(var_data, labeler) var_names.append(var_name) var_label_pairs.append((var_data, var_name)) self._cplex_variable_ids[var_name] = len(self._cplex_variable_ids) if not var_data.has_lb(): var_lbs.append(-CPLEXDirect._cplex_module.infinity) else: var_lbs.append(value(var_data.lb)) if not var_data.has_ub(): var_ubs.append(CPLEXDirect._cplex_module.infinity) else: var_ubs.append(value(var_data.ub)) if var_data.is_integer(): var_types.append(self._active_cplex_instance.variables.type.integer) self._num_integer_variables += 1 elif var_data.is_binary(): var_types.append(self._active_cplex_instance.variables.type.binary) self._num_binary_variables += 1 elif var_data.is_continuous(): var_types.append(self._active_cplex_instance.variables.type.continuous) self._num_continuous_variables += 1 else: raise TypeError("Invalid domain type for variable with name '%s'. " "Variable is not continuous, integer, or binary.") self._active_cplex_instance.variables.add(names=var_names, lb=var_lbs, ub=var_ubs, types=var_types) self._active_cplex_instance.variables.add(lb=[1], ub=[1], names=["ONE_VAR_CONSTANT"]) self._cplex_variable_ids["ONE_VAR_CONSTANT"] = len(self._cplex_variable_ids) self._variable_symbol_map.addSymbols(var_label_pairs) self._cplex_variable_names = self._active_cplex_instance.variables.get_names() ######################################################## # populate the standard constraints in the cplex model # ######################################################## expressions = [] senses = [] rhss = [] range_values = [] names = [] qexpressions = [] qlinears = [] qsenses = [] qrhss = [] qnames = [] for block in pyomo_instance.block_data_objects(active=True): gen_con_canonical_repn = \ getattr(block, "_gen_con_canonical_repn", True) # Get/Create the ComponentMap for the repn if not hasattr(block,'_canonical_repn'): block._canonical_repn = ComponentMap() block_canonical_repn = block._canonical_repn for con in block.component_data_objects(Constraint, active=True, descend_into=False): if (not con.has_lb()) and \ (not con.has_ub()): assert not con.equality continue # not binding at all, don't bother con_repn = None if con._linear_canonical_form: con_repn = con.canonical_form() elif isinstance(con, LinearCanonicalRepn): con_repn = con else: if gen_con_canonical_repn: con_repn = generate_canonical_repn(con.body) block_canonical_repn[con] = con_repn else: con_repn = block_canonical_repn[con] # There are conditions, e.g., when fixing variables, under which # a constraint block might be empty. Ignore these, for both # practical reasons and the fact that the CPLEX LP format # requires a variable in the constraint body. It is also # possible that the body of the constraint consists of only a # constant, in which case the "variable" of if isinstance(con_repn, LinearCanonicalRepn): if self._skip_trivial_constraints and \ ((con_repn.linear is None) or \ (len(con_repn.linear) == 0)): continue else: # we shouldn't come across a constant canonical repn # that is not LinearCanonicalRepn assert not canonical_is_constant(con_repn) name = self._symbol_map.getSymbol(con, labeler) expr = None qexpr = None quadratic = False if isinstance(con_repn, LinearCanonicalRepn): expr, offset = \ self._encode_constraint_body_linear_specialized(con_repn, labeler, use_variable_names=False, cplex_variable_name_index_map=self._cplex_variable_ids) else: degree = canonical_degree(con_repn) if degree == 2: quadratic = True elif (degree != 0) or (degree != 1): raise ValueError( "CPLEXPersistent plugin does not support general nonlinear " "constraint expression (only linear or quadratic).\n" "Constraint: %s" % (con.name)) expr, offset = self._encode_constraint_body_linear(con_repn, labeler) if quadratic: if expr is None: expr = CPLEXDirect._cplex_module.SparsePair(ind=[0],val=[0.0]) self._has_quadratic_constraints = True qexpr = self._encode_constraint_body_quadratic(con_repn,labeler) qnames.append(name) if con.equality: # equality constraint. qsenses.append('E') qrhss.append(self._get_bound(con.lower) - offset) elif con.has_lb() and con.has_ub(): raise RuntimeError( "The CPLEXDirect plugin can not translate range " "constraints containing quadratic expressions.") elif con.has_lb(): assert not con.has_ub() qsenses.append('G') qrhss.append(self._get_bound(con.lower) - offset) else: assert con.has_ub() qsenses.append('L') qrhss.append(self._get_bound(con.upper) - offset) qlinears.append(expr) qexpressions.append(qexpr) else: names.append(name) expressions.append(expr) if con.equality: # equality constraint. senses.append('E') rhss.append(self._get_bound(con.lower) - offset) range_values.append(0.0) elif con.has_lb() and con.has_ub(): # ranged constraint. senses.append('R') lower_bound = self._get_bound(con.lower) - offset upper_bound = self._get_bound(con.upper) - offset rhss.append(lower_bound) range_values.append(upper_bound - lower_bound) elif con.has_lb(): senses.append('G') rhss.append(self._get_bound(con.lower) - offset) range_values.append(0.0) else: assert con.has_ub() senses.append('L') rhss.append(self._get_bound(con.upper) - offset) range_values.append(0.0) ################################################### # populate the SOS constraints in the cplex model # ################################################### # SOS constraints - largely taken from cpxlp.py so updates there, # should be applied here # TODO: Allow users to specify the variables coefficients for custom # branching/set orders - refer to cpxlp.py sosn = self._capabilities.sosn sos1 = self._capabilities.sos1 sos2 = self._capabilities.sos2 modelSOS = ModelSOS() for soscondata in pyomo_instance.component_data_objects(SOSConstraint, active=True): level = soscondata.level if (level == 1 and not sos1) or \ (level == 2 and not sos2) or \ (level > 2 and not sosn): raise Exception("Solver does not support SOS level %s constraints" % (level,)) modelSOS.count_constraint(self._symbol_map, labeler, self._variable_symbol_map, soscondata) if modelSOS.sosType: for key in modelSOS.sosType: self._active_cplex_instance.SOS.add(type = modelSOS.sosType[key], name = modelSOS.sosName[key], SOS = [modelSOS.varnames[key], modelSOS.weights[key]]) self._referenced_variable_ids.update(modelSOS.varids[key]) self._used_sos_constraints = True self._active_cplex_instance.linear_constraints.add( lin_expr=expressions, senses=senses, rhs=rhss, range_values=range_values, names=names) for index in xrange(len(qexpressions)): self._active_cplex_instance.quadratic_constraints.add( lin_expr=qlinears[index], quad_expr=qexpressions[index], sense=qsenses[index], rhs=qrhss[index], name=qnames[index]) ############################################# # populate the objective in the cplex model # ############################################# self.compile_objective(pyomo_instance) # # simple method to query whether a Pyomo instance has already been # compiled. # def instance_compiled(self): return self._active_cplex_instance is not None # # Override base class method to check for compiled instance # def _warm_start(self, instance): if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin " "cannot warm start - no instance is " "presently compiled") # clear any existing warm starts. self._active_cplex_instance.MIP_starts.delete() # the iteration order is identical to that used in generating # the cplex instance, so all should be well. variable_ids = [] variable_values = [] # IMPT: the var_data returned is a weak ref! for label, var_data in iteritems(self._variable_symbol_map.bySymbol): cplex_id = self._cplex_variable_ids[label] if var_data().fixed and not self._output_fixed_variable_bounds: continue elif var_data().value is not None: variable_ids.append(cplex_id) variable_values.append(var_data().value) if len(variable_ids): self._active_cplex_instance.MIP_starts.add( [variable_ids, variable_values], self._active_cplex_instance.MIP_starts.effort_level.auto) # # Override base class method to check for compiled instance # def _populate_cplex_instance(self, model): assert model == self._instance def _presolve(self, *args, **kwds): if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin" " cannot presolve - no instance is " "presently compiled") # These must be passed in to the compile_instance method, # but assert that any values here match those already supplied if 'symbolic_solver_labels' in kwds: assert self._symbolic_solver_labels == \ kwds['symbolic_solver_labels'] if 'output_fixed_variable_bounds' in kwds: assert self._output_fixed_variable_bounds == \ kwds['output_fixed_variable_bounds'] if 'skip_trivial_constraints' in kwds: assert self._skip_trivial_constraints == \ kwds["skip_trivial_constraints"] if isinstance(self._instance, IBlockStorage): # BIG HACK if not hasattr(self._instance, "._symbol_maps"): setattr(self._instance, "._symbol_maps", {}) getattr(self._instance, "._symbol_maps")[id(self._symbol_map)] = \ self._symbol_map else: if self._smap_id not in self._instance.solutions.symbol_map: self._instance.solutions.add_symbol_map(self._symbol_map) ################################################ # populate the problem type in the cplex model # ################################################ # This gets rid of the annoying "Freeing MIP data." message. def _filter_freeing_mip_data(val): if val.strip() == 'Freeing MIP data.': return "" return val self._active_cplex_instance.set_warning_stream(sys.stderr, fn=_filter_freeing_mip_data) if (self._has_quadratic_objective is True) or \ (self._has_quadratic_constraints is True): if (self._num_integer_variables > 0) or \ (self._num_binary_variables > 0) or \ (self._used_sos_constraints): if self._has_quadratic_constraints is True: self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.MIQCP) else: self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.MIQP) else: if self._has_quadratic_constraints is True: self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.QCP) else: self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.QP) elif (self._num_integer_variables > 0) or \ (self._num_binary_variables > 0) or \ (self._used_sos_constraints): self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.MILP) else: self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.LP) # restore the warning stream without our filter function self._active_cplex_instance.set_warning_stream(sys.stderr) CPLEXDirect._presolve(self, *args, **kwds) # like other solver plugins, persistent solver plugins can # take an instance as an input argument. the only context in # which this instance is used, however, is for warm-starting. if len(args) > 2: raise ValueError("The CPLEXPersistent plugin method " "'_presolve' can be supplied at most " "one problem instance - %s were " "supplied" % len(args)) # Re-add the symbol map id if it was cleared # after a previous solution load if id(self._symbol_map) not in args[0].solutions.symbol_map: args[0].solutions.add_symbol_map(self._symbol_map) self._smap_id = id(self._symbol_map) # # invoke the solver on the currently compiled instance!!! # def _apply_solver(self): if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin cannot " "apply solver - no instance is presently compiled") # NOTE: # CPLEX maintains the pool of feasible solutions from the # prior solve as the set of mip starts for the next solve. # and evaluating multiple mip starts (and there can be many) # is expensive. so if the warm_start method is not invoked, # there will potentially be a lot of time wasted. return CPLEXDirect._apply_solver(self) def _postsolve(self): if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin " "cannot postsolve - no instance is " "presently compiled") active_cplex_instance = self._active_cplex_instance variable_symbol_map = self._variable_symbol_map instance = self._instance ret = CPLEXDirect._postsolve(self) # # These get reset to None by the base class method # self._active_cplex_instance = active_cplex_instance self._variable_symbol_map = variable_symbol_map self._instance = instance return ret
class NLWriter(PersistentBase): def __init__(self): super(NLWriter, self).__init__() self._config = WriterConfig() self._writer = None self._symbol_map = SymbolMap() self._var_labeler = None self._con_labeler = None self._param_labeler = None self._pyomo_var_to_solver_var_map = dict() self._pyomo_con_to_solver_con_map = dict() self._solver_var_to_pyomo_var_map = dict() self._solver_con_to_pyomo_con_map = dict() self._pyomo_param_to_solver_param_map = dict() self._walker = PyomoToCModelWalker( self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map) @property def config(self): return self._config @config.setter def config(self, val: WriterConfig): self._config = val @property def symbol_map(self): return self._symbol_map def set_instance(self, model): saved_config = self.config saved_update_config = self.update_config self.__init__() self.config = saved_config self.update_config = saved_update_config self._model = model if self.config.symbolic_solver_labels: self._var_labeler = TextLabeler() self._con_labeler = TextLabeler() self._param_labeler = TextLabeler() else: self._var_labeler = NumericLabeler('x') self._con_labeler = NumericLabeler('c') self._param_labeler = NumericLabeler('p') self._writer = cmodel.NLWriter() self.add_block(model) if self._objective is None: self.set_objective(None) def _add_variables(self, variables: List[_GeneralVarData]): cvars = cmodel.create_vars(len(variables)) for ndx, v in enumerate(variables): cv = cvars[ndx] cv.name = self._symbol_map.getSymbol(v, self._var_labeler) if not v.is_continuous(): raise NotImplementedError( 'NLWriter currently only supports continuous variables') lb = value(v.lb) ub = value(v.ub) if lb is not None: cv.lb = lb if ub is not None: cv.ub = ub if v.value is not None: cv.value = v.value if v.is_fixed(): cv.fixed = True self._pyomo_var_to_solver_var_map[id(v)] = cv self._solver_var_to_pyomo_var_map[cv] = v def _add_params(self, params: List[_ParamData]): cparams = cmodel.create_params(len(params)) for ndx, p in enumerate(params): cp = cparams[ndx] cp.name = self._symbol_map.getSymbol(p, self._param_labeler) cp.value = p.value self._pyomo_param_to_solver_param_map[id(p)] = cp def _add_constraints(self, cons: List[_GeneralConstraintData]): for c in cons: cname = self._symbol_map.getSymbol(c, self._con_labeler) repn = generate_standard_repn(c.body, compute_values=False, quadratic=False) const = self._walker.dfs_postorder_stack(repn.constant) lin_vars = [ self._pyomo_var_to_solver_var_map[id(i)] for i in repn.linear_vars ] lin_coef = [ self._walker.dfs_postorder_stack(i) for i in repn.linear_coefs ] if repn.nonlinear_expr is None: nonlin = self._walker.dfs_postorder_stack(0) else: nonlin = self._walker.dfs_postorder_stack(repn.nonlinear_expr) cc = cmodel.NLConstraint(const, lin_coef, lin_vars, nonlin) lb = c.lower ub = c.upper if lb is not None: cc.lb = self._walker.dfs_postorder_stack(lb) if ub is not None: cc.ub = self._walker.dfs_postorder_stack(ub) self._writer.add_constraint(cc) self._pyomo_con_to_solver_con_map[c] = cc self._solver_con_to_pyomo_con_map[cc] = c def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'NL writer does not support SOS constraints') def _remove_constraints(self, cons: List[_GeneralConstraintData]): for c in cons: cc = self._pyomo_con_to_solver_con_map.pop(c) self._writer.remove_constraint(cc) self._symbol_map.removeSymbol(c) self._con_labeler.remove_obj(c) del self._solver_con_to_pyomo_con_map[cc] def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'NL writer does not support SOS constraints') def _remove_variables(self, variables: List[_GeneralVarData]): for v in variables: cvar = self._pyomo_var_to_solver_var_map.pop(id(v)) del self._solver_var_to_pyomo_var_map[cvar] self._symbol_map.removeSymbol(v) self._var_labeler.remove_obj(v) def _remove_params(self, params: List[_ParamData]): for p in params: del self._pyomo_param_to_solver_param_map[id(p)] self._symbol_map.removeSymbol(p) self._param_labeler.remove_obj(p) def _update_variables(self, variables: List[_GeneralVarData]): for v in variables: cv = self._pyomo_var_to_solver_var_map[id(v)] if not v.is_continuous(): raise NotImplementedError( 'NLWriter currently only supports continuous variables') lb = value(v.lb) ub = value(v.ub) if lb is None: cv.lb = -cmodel.inf else: cv.lb = lb if ub is None: cv.ub = cmodel.inf else: cv.ub = ub if v.value is not None: cv.value = v.value if v.is_fixed(): cv.fixed = True else: cv.fixed = False def update_params(self): for p_id, p in self._params.items(): cp = self._pyomo_param_to_solver_param_map[p_id] cp.value = p.value def _set_objective(self, obj: _GeneralObjectiveData): if obj is None: const = cmodel.Constant(0) lin_vars = list() lin_coef = list() nonlin = cmodel.Constant(0) sense = 0 else: repn = generate_standard_repn(obj.expr, compute_values=False, quadratic=False) const = self._walker.dfs_postorder_stack(repn.constant) lin_vars = [ self._pyomo_var_to_solver_var_map[id(i)] for i in repn.linear_vars ] lin_coef = [ self._walker.dfs_postorder_stack(i) for i in repn.linear_coefs ] if repn.nonlinear_expr is None: nonlin = cmodel.Constant(0) else: nonlin = self._walker.dfs_postorder_stack(repn.nonlinear_expr) if obj.sense is minimize: sense = 0 else: sense = 1 cobj = cmodel.NLObjective(const, lin_coef, lin_vars, nonlin) cobj.sense = sense self._writer.objective = cobj def write(self, model: _BlockData, filename: str, timer: HierarchicalTimer = None): if timer is None: timer = HierarchicalTimer() if model is not self._model: timer.start('set_instance') self.set_instance(model) timer.stop('set_instance') else: timer.start('update') self.update(timer=timer) for cv, v in self._solver_var_to_pyomo_var_map.items(): if v.value is not None: cv.value = v.value timer.stop('update') timer.start('write file') self._writer.write(filename) timer.stop('write file') def get_ordered_vars(self): return [ self._solver_var_to_pyomo_var_map[i] for i in self._writer.get_solve_vars() ] def get_ordered_cons(self): return [ self._solver_con_to_pyomo_con_map[i] for i in self._writer.get_solve_cons() ] def get_active_objective(self): return self._objective
class LPWriter(PersistentBase): def __init__(self): super(LPWriter, self).__init__() self._config = WriterConfig() self._writer = None self._symbol_map = SymbolMap() self._var_labeler = None self._con_labeler = None self._param_labeler = None self._obj_labeler = None self._pyomo_var_to_solver_var_map = dict() self._pyomo_con_to_solver_con_map = dict() self._solver_var_to_pyomo_var_map = dict() self._solver_con_to_pyomo_con_map = dict() self._pyomo_param_to_solver_param_map = dict() self._walker = PyomoToCModelWalker( self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map) @property def config(self): return self._config @config.setter def config(self, val: WriterConfig): self._config = val def set_instance(self, model): saved_config = self.config saved_update_config = self.update_config self.__init__() self.config = saved_config self.update_config = saved_update_config self._model = model if self.config.symbolic_solver_labels: self._var_labeler = TextLabeler() self._con_labeler = TextLabeler() self._param_labeler = TextLabeler() self._obj_labeler = TextLabeler() else: self._var_labeler = NumericLabeler('x') self._con_labeler = NumericLabeler('c') self._param_labeler = NumericLabeler('p') self._obj_labeler = NumericLabeler('obj') self._writer = cmodel.LPWriter() self.add_block(model) if self._objective is None: self.set_objective(None) def _add_variables(self, variables: List[_GeneralVarData]): cvars = cmodel.create_vars(len(variables)) for ndx, v in enumerate(variables): cv = cvars[ndx] cv.name = self._symbol_map.getSymbol(v, self._var_labeler) if v.is_binary(): cv.domain = 'binary' elif v.is_integer(): cv.domain = 'integer' else: assert v.is_continuous( ), 'LP writer only supports continuous, binary, and integer variables' cv.domain = 'continuous' _, lb, ub, v_is_fixed, v_domain, v_value = self._vars[id(v)] if lb is not None: cv.lb = lb if ub is not None: cv.ub = ub if v_value is not None: cv.value = v_value if v_is_fixed: cv.fixed = True self._pyomo_var_to_solver_var_map[id(v)] = cv self._solver_var_to_pyomo_var_map[cv] = v def _add_params(self, params: List[_ParamData]): cparams = cmodel.create_params(len(params)) for ndx, p in enumerate(params): cp = cparams[ndx] cp.name = self._symbol_map.getSymbol(p, self._param_labeler) cp.value = p.value self._pyomo_param_to_solver_param_map[id(p)] = cp def _add_constraints(self, cons: List[_GeneralConstraintData]): cmodel.process_lp_constraints(cons, self) def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'LP writer does not yet support SOS constraints') def _remove_constraints(self, cons: List[_GeneralConstraintData]): for c in cons: cc = self._pyomo_con_to_solver_con_map.pop(c) self._writer.remove_constraint(cc) self._symbol_map.removeSymbol(c) self._con_labeler.remove_obj(c) del self._solver_con_to_pyomo_con_map[cc] def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'LP writer does not yet support SOS constraints') def _remove_variables(self, variables: List[_GeneralVarData]): for v in variables: cvar = self._pyomo_var_to_solver_var_map.pop(id(v)) del self._solver_var_to_pyomo_var_map[cvar] self._symbol_map.removeSymbol(v) self._var_labeler.remove_obj(v) def _remove_params(self, params: List[_ParamData]): for p in params: del self._pyomo_param_to_solver_param_map[id(p)] self._symbol_map.removeSymbol(p) self._param_labeler.remove_obj(p) def update_variables(self, variables: List[_GeneralVarData]): for v in variables: cv = self._pyomo_var_to_solver_var_map[id(v)] if v.is_binary(): cv.domain = 'binary' elif v.is_integer(): cv.domain = 'integer' else: assert v.is_continuous( ), 'LP writer only supports continuous, binary, and integer variables' cv.domain = 'continuous' lb = value(v.lb) ub = value(v.ub) if lb is None: cv.lb = -cmodel.inf else: cv.lb = lb if ub is None: cv.ub = cmodel.inf else: cv.ub = ub if v.value is not None: cv.value = v.value if v.is_fixed(): cv.fixed = True else: cv.fixed = False def update_params(self): for p_id, p in self._params.items(): cp = self._pyomo_param_to_solver_param_map[p_id] cp.value = p.value def _set_objective(self, obj: _GeneralObjectiveData): if obj is None: const = cmodel.Constant(0) lin_coef = list() lin_vars = list() quad_coef = list() quad_vars_1 = list() quad_vars_2 = list() sense = 0 else: repn = generate_standard_repn(obj.expr, compute_values=False, quadratic=True) const = self._walker.dfs_postorder_stack(repn.constant) lin_coef = [ self._walker.dfs_postorder_stack(i) for i in repn.linear_coefs ] lin_vars = [ self._pyomo_var_to_solver_var_map[id(i)] for i in repn.linear_vars ] quad_coef = [ self._walker.dfs_postorder_stack(i) for i in repn.quadratic_coefs ] quad_vars_1 = [ self._pyomo_var_to_solver_var_map[id(i[0])] for i in repn.quadratic_vars ] quad_vars_2 = [ self._pyomo_var_to_solver_var_map[id(i[1])] for i in repn.quadratic_vars ] if obj.sense is minimize: sense = 0 else: sense = 1 cobj = cmodel.LPObjective(const, lin_coef, lin_vars, quad_coef, quad_vars_1, quad_vars_2) cobj.sense = sense if obj is None: cname = 'objective' else: cname = self._symbol_map.getSymbol(obj, self._obj_labeler) cobj.name = cname self._writer.objective = cobj def write(self, model: _BlockData, filename: str, timer: HierarchicalTimer = None): if timer is None: timer = HierarchicalTimer() if model is not self._model: timer.start('set_instance') self.set_instance(model) timer.stop('set_instance') else: timer.start('update') self.update(timer=timer) timer.stop('update') timer.start('write file') self._writer.write(filename) timer.stop('write file') def get_vars(self): return [ self._solver_var_to_pyomo_var_map[i] for i in self._writer.get_solve_vars() ] def get_ordered_cons(self): return [ self._solver_con_to_pyomo_con_map[i] for i in self._writer.get_solve_cons() ] def get_active_objective(self): return self._objective @property def symbol_map(self): return self._symbol_map
class NLWriter(PersistentBase): def __init__(self): super(NLWriter, self).__init__() self._config = WriterConfig() self._writer = None self._symbol_map = SymbolMap() self._var_labeler = None self._con_labeler = None self._param_labeler = None self._pyomo_var_to_solver_var_map = dict() self._pyomo_con_to_solver_con_map = dict() self._solver_var_to_pyomo_var_map = dict() self._solver_con_to_pyomo_con_map = dict() self._pyomo_param_to_solver_param_map = dict() self._expr_types = None @property def config(self): return self._config @config.setter def config(self, val: WriterConfig): self._config = val @property def symbol_map(self): return self._symbol_map def set_instance(self, model): saved_config = self.config saved_update_config = self.update_config self.__init__() self.config = saved_config self.update_config = saved_update_config self._model = model self._expr_types = cmodel.PyomoExprTypes() if self.config.symbolic_solver_labels: self._var_labeler = TextLabeler() self._con_labeler = TextLabeler() self._param_labeler = TextLabeler() else: self._var_labeler = NumericLabeler('x') self._con_labeler = NumericLabeler('c') self._param_labeler = NumericLabeler('p') self._writer = cmodel.NLWriter() self.add_block(model) if self._objective is None: self.set_objective(None) self._set_pyomo_amplfunc_env() def _add_variables(self, variables: List[_GeneralVarData]): cmodel.process_pyomo_vars(self._expr_types, variables, self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map, self._vars, self._solver_var_to_pyomo_var_map, False, None, None, False) def _add_params(self, params: List[_ParamData]): cparams = cmodel.create_params(len(params)) for ndx, p in enumerate(params): cp = cparams[ndx] cp.name = self._symbol_map.getSymbol(p, self._param_labeler) cp.value = p.value self._pyomo_param_to_solver_param_map[id(p)] = cp def _add_constraints(self, cons: List[_GeneralConstraintData]): cmodel.process_nl_constraints(self._writer, self._expr_types, cons, self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map, self._active_constraints, self._pyomo_con_to_solver_con_map, self._solver_con_to_pyomo_con_map) def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'NL writer does not support SOS constraints') def _remove_constraints(self, cons: List[_GeneralConstraintData]): for c in cons: cc = self._pyomo_con_to_solver_con_map.pop(c) self._writer.remove_constraint(cc) self._con_labeler.remove_obj(c) del self._solver_con_to_pyomo_con_map[cc] def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'NL writer does not support SOS constraints') def _remove_variables(self, variables: List[_GeneralVarData]): for v in variables: cvar = self._pyomo_var_to_solver_var_map.pop(id(v)) del self._solver_var_to_pyomo_var_map[cvar] # self._symbol_map.removeSymbol(v) self._var_labeler.remove_obj(v) def _remove_params(self, params: List[_ParamData]): for p in params: del self._pyomo_param_to_solver_param_map[id(p)] self._symbol_map.removeSymbol(p) self._param_labeler.remove_obj(p) def _update_variables(self, variables: List[_GeneralVarData]): cmodel.process_pyomo_vars(self._expr_types, variables, self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map, self._vars, self._solver_var_to_pyomo_var_map, False, None, None, True) def update_params(self): for p_id, p in self._params.items(): cp = self._pyomo_param_to_solver_param_map[p_id] cp.value = p.value def _set_objective(self, obj: _GeneralObjectiveData): if obj is None: const = cmodel.Constant(0) lin_vars = list() lin_coef = list() nonlin = cmodel.Constant(0) sense = 0 else: pyomo_expr_types = cmodel.PyomoExprTypes() repn = generate_standard_repn(obj.expr, compute_values=False, quadratic=False) const = cmodel.appsi_expr_from_pyomo_expr( repn.constant, self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map, pyomo_expr_types) lin_vars = [ self._pyomo_var_to_solver_var_map[id(i)] for i in repn.linear_vars ] lin_coef = [ cmodel.appsi_expr_from_pyomo_expr( i, self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map, pyomo_expr_types) for i in repn.linear_coefs ] if repn.nonlinear_expr is None: nonlin = cmodel.appsi_expr_from_pyomo_expr( 0, self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map, pyomo_expr_types) else: nonlin = cmodel.appsi_expr_from_pyomo_expr( repn.nonlinear_expr, self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map, pyomo_expr_types) if obj.sense is minimize: sense = 0 else: sense = 1 cobj = cmodel.NLObjective(const, lin_coef, lin_vars, nonlin) cobj.sense = sense self._writer.objective = cobj def write(self, model: _BlockData, filename: str, timer: HierarchicalTimer = None): if timer is None: timer = HierarchicalTimer() if model is not self._model: timer.start('set_instance') self.set_instance(model) timer.stop('set_instance') else: timer.start('update') self.update(timer=timer) for cv, v in self._solver_var_to_pyomo_var_map.items(): if v.value is not None: cv.value = v.value timer.stop('update') timer.start('write file') self._writer.write(filename) timer.stop('write file') def update(self, timer: HierarchicalTimer = None): super(NLWriter, self).update(timer=timer) self._set_pyomo_amplfunc_env() def get_ordered_vars(self): return [ self._solver_var_to_pyomo_var_map[i] for i in self._writer.get_solve_vars() ] def get_ordered_cons(self): return [ self._solver_con_to_pyomo_con_map[i] for i in self._writer.get_solve_cons() ] def get_active_objective(self): return self._objective def _set_pyomo_amplfunc_env(self): if self._external_functions: external_Libs = OrderedSet() for con, ext_funcs in self._external_functions.items(): external_Libs.update([i._fcn._library for i in ext_funcs]) set_pyomo_amplfunc_env(external_Libs) elif "PYOMO_AMPLFUNC" in os.environ: del os.environ["PYOMO_AMPLFUNC"]
def solve(self, model: _BlockData, tee: bool = False, load_solutions: bool = True, logfile: Optional[str] = None, solnfile: Optional[str] = None, timelimit: Optional[float] = None, report_timing: bool = False, solver_io: Optional[str] = None, suffixes: Optional[Sequence] = None, options: Optional[Dict] = None, keepfiles: bool = False, symbolic_solver_labels: bool = False): original_config = self.config self.config = self.config() self.config.stream_solver = tee self.config.load_solution = load_solutions self.config.symbolic_solver_labels = symbolic_solver_labels self.config.time_limit = timelimit self.config.report_timing = report_timing if solver_io is not None: raise NotImplementedError('Still working on this') if suffixes is not None: raise NotImplementedError('Still working on this') if logfile is not None: raise NotImplementedError('Still working on this') if 'keepfiles' in self.config: self.config.keepfiles = keepfiles if solnfile is not None: if 'filename' in self.config: filename = os.path.splitext(solnfile)[0] self.config.filename = filename original_options = self.options if options is not None: self.options = options results: Results = super(LegacySolverInterface, self).solve(model) legacy_results = LegacySolverResults() legacy_soln = LegacySolution() legacy_results.solver.status = legacy_solver_status_map[results.termination_condition] legacy_results.solver.termination_condition = legacy_termination_condition_map[results.termination_condition] legacy_soln.status = legacy_solution_status_map[results.termination_condition] legacy_results.solver.termination_message = str(results.termination_condition) obj = get_objective(model) legacy_results.problem.sense = obj.sense if obj.sense == minimize: legacy_results.problem.lower_bound = results.best_objective_bound legacy_results.problem.upper_bound = results.best_feasible_objective else: legacy_results.problem.upper_bound = results.best_objective_bound legacy_results.problem.lower_bound = results.best_feasible_objective if results.best_feasible_objective is not None and results.best_objective_bound is not None: legacy_soln.gap = abs(results.best_feasible_objective - results.best_objective_bound) else: legacy_soln.gap = None symbol_map = SymbolMap() symbol_map.byObject = dict(self.symbol_map.byObject) symbol_map.bySymbol = {symb: weakref.ref(obj()) for symb, obj in self.symbol_map.bySymbol.items()} symbol_map.aliases = {symb: weakref.ref(obj()) for symb, obj in self.symbol_map.aliases.items()} symbol_map.default_labeler = self.symbol_map.default_labeler model.solutions.add_symbol_map(symbol_map) legacy_results._smap_id = id(symbol_map) delete_legacy_soln = True if load_solutions: if hasattr(model, 'dual') and model.dual.import_enabled(): for c, val in results.solution_loader.get_duals().items(): model.dual[c] = val if hasattr(model, 'slack') and model.slack.import_enabled(): for c, val in results.solution_loader.get_slacks().items(): model.slack[c] = val if hasattr(model, 'rc') and model.rc.import_enabled(): for v, val in results.solution_loader.get_reduced_costs().items(): model.rc[v] = val elif results.best_feasible_objective is not None: delete_legacy_soln = False for v, val in results.solution_loader.get_primals().items(): legacy_soln.variable[symbol_map.getSymbol(v)] = {'Value': val} if hasattr(model, 'dual') and model.dual.import_enabled(): for c, val in results.solution_loader.get_duals().items(): legacy_soln.constraint[symbol_map.getSymbol(c)] = {'Dual': val} if hasattr(model, 'slack') and model.slack.import_enabled(): for c, val in results.solution_loader.get_slacks().items(): symbol = symbol_map.getSymbol(c) if symbol in legacy_soln.constraint: legacy_soln.constraint[symbol]['Slack'] = val if hasattr(model, 'rc') and model.rc.import_enabled(): for v, val in results.solution_loader.get_reduced_costs().items(): legacy_soln.variable['Rc'] = val legacy_results.solution.insert(legacy_soln) if delete_legacy_soln: legacy_results.solution.delete(0) self.config = original_config self.options = original_options return legacy_results
class CPLEXPersistent(CPLEXDirect, PersistentSolver): """The CPLEX LP/MIP solver """ pyomo.util.plugin.alias('_cplex_persistent', doc='Persistent Python interface to the CPLEX LP/MIP solver') def __init__(self, **kwds): # # Call base class constructor # kwds['type'] = 'cplexpersistent' CPLEXDirect.__init__(self, **kwds) # maps pyomo var data labels to the corresponding CPLEX variable id. self._cplex_variable_ids = {} self._cplex_variable_names = None # # updates all variable bounds in the compiled model - handles # fixed variables and related issues. re-does everything from # scratch by default, ignoring whatever was specified # previously. if the value associated with the keyword # vars_to_update is a non-empty list (assumed to be variable name # / index pairs), then only the bounds for those variables are # updated. this function assumes that the variables themselves # already exist in the compiled model. # def compile_variable_bounds(self, pyomo_instance, vars_to_update): from pyomo.core.base import Var if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin " "cannot compile variable bounds - no " "instance is presently compiled") # the bound update entries should be name-value pairs new_lower_bounds = [] new_upper_bounds = [] # operates through side effects on the above lists! def update_bounds_lists(var_name): var_lb = None var_ub = None if var_data.fixed and self._output_fixed_variable_bounds: var_lb = var_ub = var_data.value elif var_data.fixed: # if we've been directed to not deal with fixed # variables, then skip - they should have been # compiled out of any description of the constraints return else: if var_data.lb is None: var_lb = -cplex.infinity else: var_lb = value(var_data.lb) if var_data.ub is None: var_ub = cplex.infinity else: var_ub= value(var_data.ub) var_cplex_id = self._cplex_variable_ids[var_name] new_lower_bounds.append((var_cplex_id, var_lb)) new_upper_bounds.append((var_cplex_id, var_ub)) if len(vars_to_update) == 0: for var_data in pyomo_instance.component_data_objects(Var, active=True): var_name = self._symbol_map.getSymbol(var_data, self._labeler) update_bounds_lists(var_name) else: for var_name, var_index in vars_to_update: var = pyomo_instance.find_component(var_name) # TBD - do some error checking! var_data = var[var_index] var_name = self._symbol_map.getSymbol(var_data, self._labeler) update_bounds_lists(var_name) self._active_cplex_instance.variables.set_lower_bounds(new_lower_bounds) self._active_cplex_instance.variables.set_upper_bounds(new_upper_bounds) # # method to compile objective of the input pyomo instance. # TBD: # it may be smarter just to track the associated pyomo instance, # and re-compile it automatically from a cached local attribute. # this would ensure consistency, among other things! # def compile_objective(self, pyomo_instance): from pyomo.core.base import Objective from pyomo.repn import canonical_is_constant, LinearCanonicalRepn, canonical_degree if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin " "cannot compile objective - no " "instance is presently compiled") cplex_instance = self._active_cplex_instance cntr = 0 for block in pyomo_instance.block_data_objects(active=True): gen_obj_canonical_repn = \ getattr(block, "_gen_obj_canonical_repn", True) # Get/Create the ComponentMap for the repn if not hasattr(block,'_canonical_repn'): block._canonical_repn = ComponentMap() block_canonical_repn = block._canonical_repn for obj_data in block.component_data_objects(Objective, active=True, descend_into=False): cntr += 1 if cntr > 1: raise ValueError( "Multiple active objectives found on Pyomo instance '%s'. " "Solver '%s' will only handle a single active objective" \ % (pyomo_instance.cname(True), self.type)) if obj_data.is_minimizing(): cplex_instance.objective.set_sense( cplex_instance.objective.sense.minimize) else: cplex_instance.objective.set_sense( cplex_instance.objective.sense.maximize) cplex_instance.objective.set_name( self._symbol_map.getSymbol(obj_data, self._labeler)) if gen_obj_canonical_repn: obj_repn = generate_canonical_repn(obj_data.expr) block_canonical_repn[obj_data] = obj_repn else: obj_repn = block_canonical_repn[obj_data] if (isinstance(obj_repn, LinearCanonicalRepn) and \ (obj_repn.linear == None)) or \ canonical_is_constant(obj_repn): print("Warning: Constant objective detected, replacing " "with a placeholder to prevent solver failure.") offset = obj_repn.constant if offset is None: offset = 0.0 objective_expression = [("ONE_VAR_CONSTANT",offset)] cplex_instance.objective.set_linear(objective_expression) else: if isinstance(obj_repn, LinearCanonicalRepn): objective_expression, offset = \ self._encode_constraint_body_linear_specialized( obj_repn, self._labeler, use_variable_names=False, cplex_variable_name_index_map=self._cplex_variable_ids, as_pairs=True) if offset != 0.0: objective_expression.append((self._cplex_variable_ids["ONE_VAR_CONSTANT"],offset)) cplex_instance.objective.set_linear(objective_expression) else: #Linear terms if 1 in obj_repn: objective_expression, offset = \ self._encode_constraint_body_linear( obj_repn, self._labeler, as_pairs=True) if offset != 0.0: objective_expression.append(("ONE_VAR_CONSTANT",offset)) cplex_instance.objective.set_linear(objective_expression) #Quadratic terms if 2 in obj_repn: self._has_quadratic_objective = True objective_expression = \ self._encode_constraint_body_quadratic(obj_repn, self._labeler, as_triples=True, is_obj=2.0) cplex_instance.objective.\ set_quadratic_coefficients(objective_expression) degree = canonical_degree(obj_repn) if (degree is None) or (degree > 2): raise ValueError( "CPLEXPersistent plugin does not support general nonlinear " "objective expressions (only linear or quadratic).\n" "Objective: %s" % (obj_data.cname(True))) # # method to populate the CPLEX problem instance (interface) from # the supplied Pyomo problem instance. # def compile_instance(self, pyomo_instance, symbolic_solver_labels=False, output_fixed_variable_bounds=False, skip_trivial_constraints=False): from pyomo.core.base import Var, Constraint, SOSConstraint from pyomo.repn import canonical_is_constant, LinearCanonicalRepn, canonical_degree self._symbolic_solver_labels = symbolic_solver_labels self._output_fixed_variable_bounds = output_fixed_variable_bounds self._skip_trivial_constraints = skip_trivial_constraints self._has_quadratic_constraints = False self._has_quadratic_objective = False used_sos_constraints = False self._active_cplex_instance = cplex.Cplex() if self._symbolic_solver_labels: labeler = self._labeler = TextLabeler() else: labeler = self._labeler = NumericLabeler('x') self._symbol_map = SymbolMap() self._instance = pyomo_instance pyomo_instance.solutions.add_symbol_map(self._symbol_map) self._smap_id = id(self._symbol_map) # we use this when iterating over the constraints because it # will have a much smaller hash table, we also use this for # the warm start code after it is cleaned to only contain # variables referenced in the constraints self._variable_symbol_map = SymbolMap() # cplex wants the caller to set the problem type, which is (for # current purposes) strictly based on variable type counts. num_binary_variables = 0 num_integer_variables = 0 num_continuous_variables = 0 ############################################# # populate the variables in the cplex model # ############################################# var_names = [] var_lbs = [] var_ubs = [] var_types = [] self._referenced_variable_ids.clear() # maps pyomo var data labels to the corresponding CPLEX variable id. self._cplex_variable_ids.clear() # cached in the loop below - used to update the symbol map # immediately following loop termination. var_label_pairs = [] for var_data in pyomo_instance.component_data_objects(Var, active=True): if var_data.fixed and not self._output_fixed_variable_bounds: # if a variable is fixed, and we're preprocessing # fixed variables (as in not outputting them), there # is no need to add them to the compiled model. continue var_name = self._symbol_map.getSymbol(var_data, labeler) var_names.append(var_name) var_label_pairs.append((var_data, var_name)) self._cplex_variable_ids[var_name] = len(self._cplex_variable_ids) if (var_data.lb is None) or (var_data.lb == -infinity): var_lbs.append(-cplex.infinity) else: var_lbs.append(value(var_data.lb)) if (var_data.ub is None) or (var_data.ub == infinity): var_ubs.append(cplex.infinity) else: var_ubs.append(value(var_data.ub)) if var_data.is_integer(): var_types.append(self._active_cplex_instance.variables.type.integer) num_integer_variables += 1 elif var_data.is_binary(): var_types.append(self._active_cplex_instance.variables.type.binary) num_binary_variables += 1 elif var_data.is_continuous(): var_types.append(self._active_cplex_instance.variables.type.continuous) num_continuous_variables += 1 else: raise TypeError("Invalid domain type for variable with name '%s'. " "Variable is not continuous, integer, or binary.") self._active_cplex_instance.variables.add(names=var_names, lb=var_lbs, ub=var_ubs, types=var_types) self._active_cplex_instance.variables.add(lb=[1], ub=[1], names=["ONE_VAR_CONSTANT"]) self._cplex_variable_ids["ONE_VAR_CONSTANT"] = len(self._cplex_variable_ids) self._variable_symbol_map.addSymbols(var_label_pairs) self._cplex_variable_names = self._active_cplex_instance.variables.get_names() ######################################################## # populate the standard constraints in the cplex model # ######################################################## expressions = [] senses = [] rhss = [] range_values = [] names = [] qexpressions = [] qlinears = [] qsenses = [] qrhss = [] qnames = [] for block in pyomo_instance.block_data_objects(active=True): gen_con_canonical_repn = \ getattr(block, "_gen_con_canonical_repn", True) # Get/Create the ComponentMap for the repn if not hasattr(block,'_canonical_repn'): block._canonical_repn = ComponentMap() block_canonical_repn = block._canonical_repn for con in block.component_data_objects(Constraint, active=True, descend_into=False): if (con.lower is None) and \ (con.upper is None): continue # not binding at all, don't bother con_repn = None if isinstance(con, LinearCanonicalRepn): con_repn = con else: if gen_con_canonical_repn: con_repn = generate_canonical_repn(con.body) block_canonical_repn[con] = con_repn else: con_repn = block_canonical_repn[con] # There are conditions, e.g., when fixing variables, under which # a constraint block might be empty. Ignore these, for both # practical reasons and the fact that the CPLEX LP format # requires a variable in the constraint body. It is also # possible that the body of the constraint consists of only a # constant, in which case the "variable" of if isinstance(con_repn, LinearCanonicalRepn): if (con_repn.linear is None) and \ self._skip_trivial_constraints: continue else: # we shouldn't come across a constant canonical repn # that is not LinearCanonicalRepn assert not canonical_is_constant(con_repn) name = self._symbol_map.getSymbol(con, labeler) expr = None qexpr = None quadratic = False if isinstance(con_repn, LinearCanonicalRepn): expr, offset = \ self._encode_constraint_body_linear_specialized(con_repn, labeler, use_variable_names=False, cplex_variable_name_index_map=self._cplex_variable_ids) else: degree = canonical_degree(con_repn) if degree == 2: quadratic = True elif (degree != 0) or (degree != 1): raise ValueError( "CPLEXPersistent plugin does not support general nonlinear " "constraint expression (only linear or quadratic).\n" "Constraint: %s" % (con.cname(True))) expr, offset = self._encode_constraint_body_linear(con_repn, labeler) if quadratic: if expr is None: expr = cplex.SparsePair(ind=[0],val=[0.0]) self._has_quadratic_constraints = True qexpr = self._encode_constraint_body_quadratic(con_repn,labeler) qnames.append(name) if con.equality: # equality constraint. qsenses.append('E') qrhss.append(self._get_bound(con.lower) - offset) elif (con.lower is not None) and (con.upper is not None): raise RuntimeError( "The CPLEXDirect plugin can not translate range " "constraints containing quadratic expressions.") elif con.lower is not None: assert con.upper is None qsenses.append('G') qrhss.append(self._get_bound(con.lower) - offset) else: qsenses.append('L') qrhss.append(self._get_bound(con.upper) - offset) qlinears.append(expr) qexpressions.append(qexpr) else: names.append(name) expressions.append(expr) if con.equality: # equality constraint. senses.append('E') rhss.append(self._get_bound(con.lower) - offset) range_values.append(0.0) elif (con.lower is not None) and (con.upper is not None): # ranged constraint. senses.append('R') lower_bound = self._get_bound(con.lower) - offset upper_bound = self._get_bound(con.upper) - offset rhss.append(lower_bound) range_values.append(upper_bound - lower_bound) elif con.lower is not None: senses.append('G') rhss.append(self._get_bound(con.lower) - offset) range_values.append(0.0) else: senses.append('L') rhss.append(self._get_bound(con.upper) - offset) range_values.append(0.0) ################################################### # populate the SOS constraints in the cplex model # ################################################### # SOS constraints - largely taken from cpxlp.py so updates there, # should be applied here # TODO: Allow users to specify the variables coefficients for custom # branching/set orders - refer to cpxlp.py sosn = self._capabilities.sosn sos1 = self._capabilities.sos1 sos2 = self._capabilities.sos2 modelSOS = ModelSOS() for soscondata in pyomo_instance.component_data_objects(SOSConstraint, active=True): level = soscondata.level if (level == 1 and not sos1) or \ (level == 2 and not sos2) or \ (level > 2 and not sosn): raise Exception("Solver does not support SOS level %s constraints" % (level,)) modelSOS.count_constraint(self._symbol_map, labeler, self._variable_symbol_map, soscondata) if modelSOS.sosType: for key in modelSOS.sosType: self._active_cplex_instance.SOS.add(type = modelSOS.sosType[key], name = modelSOS.sosName[key], SOS = [modelSOS.varnames[key], modelSOS.weights[key]]) self._referenced_variable_ids.update(modelSOS.varids[key]) used_sos_constraints = True self._active_cplex_instance.linear_constraints.add( lin_expr=expressions, senses=senses, rhs=rhss, range_values=range_values, names=names) for index in xrange(len(qexpressions)): self._active_cplex_instance.quadratic_constraints.add( lin_expr=qlinears[index], quad_expr=qexpressions[index], sense=qsenses[index], rhs=qrhss[index], name=qnames[index]) ############################################# # populate the objective in the cplex model # ############################################# self.compile_objective(pyomo_instance) ################################################ # populate the problem type in the cplex model # ################################################ # This gets rid of the annoying "Freeing MIP data." message. def _filter_freeing_mip_data(val): if val.strip() == 'Freeing MIP data.': return "" return val self._active_cplex_instance.set_warning_stream(sys.stderr, fn=_filter_freeing_mip_data) if (self._has_quadratic_objective is True) or \ (self._has_quadratic_constraints is True): if (num_integer_variables > 0) or \ (num_binary_variables > 0) or \ (used_sos_constraints): if self._has_quadratic_constraints is True: self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.MIQCP) else: self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.MIQP) else: if self._has_quadratic_constraints is True: self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.QCP) else: self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.QP) elif (num_integer_variables > 0) or \ (num_binary_variables > 0) or \ (used_sos_constraints): self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.MILP) else: self._active_cplex_instance.set_problem_type( self._active_cplex_instance.problem_type.LP) # restore the warning stream without our filter function self._active_cplex_instance.set_warning_stream(sys.stderr) # # simple method to query whether a Pyomo instance has already been # compiled. # def instance_compiled(self): return self._active_cplex_instance is not None # # Override base class method to check for compiled instance # def _warm_start(self, instance): if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin " "cannot warm start - no instance is " "presently compiled") # clear any existing warm starts. self._active_cplex_instance.MIP_starts.delete() # the iteration order is identical to that used in generating # the cplex instance, so all should be well. variable_ids = [] variable_values = [] # IMPT: the var_data returned is a weak ref! for label, var_data in iteritems(self._variable_symbol_map.bySymbol): cplex_id = self._cplex_variable_ids[label] if var_data().fixed and not self._output_fixed_variable_bounds: continue elif var_data().value is not None: variable_ids.append(cplex_id) variable_values.append(var_data().value) if len(variable_ids): self._active_cplex_instance.MIP_starts.add( [variable_ids, variable_values], self._active_cplex_instance.MIP_starts.effort_level.auto) # # Override base class method to check for compiled instance # def _populate_cplex_instance(self, model): assert model == self._instance def _presolve(self, *args, **kwds): if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin" " cannot presolve - no instance is " "presently compiled") # These must be passed in to the compile_instance method, # but assert that any values here match those already supplied if 'symbolic_solver_labels' in kwds: assert self._symbolic_solver_labels == \ kwds['symbolic_solver_labels'] if 'output_fixed_variable_bounds' in kwds: assert self._output_fixed_variable_bounds == \ kwds['output_fixed_variable_bounds'] if 'skip_trivial_constraints' in kwds: assert self._skip_trivial_constraints == \ kwds["skip_trivial_constraints"] if self._smap_id not in self._instance.solutions.symbol_map: self._instance.solutions.add_symbol_map(self._symbol_map) CPLEXDirect._presolve(self, *args, **kwds) # like other solver plugins, persistent solver plugins can # take an instance as an input argument. the only context in # which this instance is used, however, is for warm-starting. if len(args) > 2: raise ValueError("The CPLEXPersistent plugin method " "'_presolve' can be supplied at most " "one problem instance - %s were " "supplied" % len(args)) # Re-add the symbol map id if it was cleared # after a previous solution load if id(self._symbol_map) not in args[0].solutions.symbol_map: args[0].solutions.add_symbol_map(self._symbol_map) self._smap_id = id(self._symbol_map) # # invoke the solver on the currently compiled instance!!! # def _apply_solver(self): if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin cannot " "apply solver - no instance is presently compiled") # NOTE: # CPLEX maintains the pool of feasible solutions from the # prior solve as the set of mip starts for the next solve. # and evaluating multiple mip starts (and there can be many) # is expensive. so if the warm_start method is not invoked, # there will potentially be a lot of time wasted. return CPLEXDirect._apply_solver(self) def _postsolve(self): if self._active_cplex_instance is None: raise RuntimeError("***The CPLEXPersistent solver plugin " "cannot postsolve - no instance is " "presently compiled") active_cplex_instance = self._active_cplex_instance variable_symbol_map = self._variable_symbol_map instance = self._instance ret = CPLEXDirect._postsolve(self) # # These get reset to None by the base class method # self._active_cplex_instance = active_cplex_instance self._variable_symbol_map = variable_symbol_map self._instance = instance return ret
class IntervalTightener(PersistentBase): def __init__(self): super(IntervalTightener, self).__init__() self._config = IntervalConfig() self._cmodel = None self._var_map = dict() self._con_map = dict() self._param_map = dict() self._rvar_map = dict() self._rcon_map = dict() self._pyomo_expr_types = cmodel.PyomoExprTypes() self._symbolic_solver_labels: bool = False self._symbol_map = SymbolMap() self._var_labeler = None self._con_labeler = None self._param_labeler = None self._obj_labeler = None self._objective = None @property def config(self): return self._config @config.setter def config(self, val: IntervalConfig): self._config = val def set_instance(self, model, symbolic_solver_labels: Optional[bool] = None): saved_config = self.config saved_update_config = self.update_config self.__init__() self.config = saved_config self.update_config = saved_update_config self._expr_types = cmodel.PyomoExprTypes() if symbolic_solver_labels is not None: self._symbolic_solver_labels = symbolic_solver_labels if self._symbolic_solver_labels: self._var_labeler = TextLabeler() self._con_labeler = TextLabeler() self._param_labeler = TextLabeler() self._obj_labeler = TextLabeler() self._model = model self._cmodel = cmodel.FBBTModel() self.add_block(model) if self._objective is None: self.set_objective(None) def _add_variables(self, variables: List[_GeneralVarData]): if self._symbolic_solver_labels: set_name = True symbol_map = self._symbol_map labeler = self._var_labeler else: set_name = False symbol_map = None labeler = None cmodel.process_pyomo_vars(self._pyomo_expr_types, variables, self._var_map, self._param_map, self._vars, self._rvar_map, set_name, symbol_map, labeler, False) def _add_params(self, params: List[_ParamData]): cparams = cmodel.create_params(len(params)) for ndx, p in enumerate(params): cp = cparams[ndx] cp.value = p.value self._param_map[id(p)] = cp if self._symbolic_solver_labels: for ndx, p in enumerate(params): cp = cparams[ndx] cp.name = self._symbol_map.getSymbol(p, self._param_labeler) def _add_constraints(self, cons: List[_GeneralConstraintData]): cmodel.process_fbbt_constraints(self._cmodel, self._pyomo_expr_types, cons, self._var_map, self._param_map, self._active_constraints, self._con_map, self._rcon_map) if self._symbolic_solver_labels: for c, cc in self._con_map.items(): cc.name = self._symbol_map.getSymbol(c, self._con_labeler) def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'IntervalTightener does not support SOS constraints') def _remove_constraints(self, cons: List[_GeneralConstraintData]): if self._symbolic_solver_labels: for c in cons: self._symbol_map.removeSymbol(c) for c in cons: cc = self._con_map.pop(c) self._cmodel.remove_constraint(cc) del self._rcon_map[cc] def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError( 'IntervalTightener does not support SOS constraints') def _remove_variables(self, variables: List[_GeneralVarData]): if self._symbolic_solver_labels: for v in variables: self._symbol_map.removeSymbol(v) for v in variables: cvar = self._var_map.pop(id(v)) del self._rvar_map[cvar] def _remove_params(self, params: List[_ParamData]): if self._symbolic_solver_labels: for p in params: self._symbol_map.removeSymbol(p) for p in params: del self._param_map[id(p)] def _update_variables(self, variables: List[_GeneralVarData]): cmodel.process_pyomo_vars(self._pyomo_expr_types, variables, self._var_map, self._param_map, self._vars, self._rvar_map, False, None, None, True) def update_params(self): for p_id, p in self._params.items(): cp = self._param_map[p_id] cp.value = p.value def set_objective(self, obj: _GeneralObjectiveData): if self._symbolic_solver_labels: if self._objective is not None: self._symbol_map.removeSymbol(self._objective) super().set_objective(obj) def _set_objective(self, obj: _GeneralObjectiveData): if obj is None: ce = cmodel.Constant(0) sense = 0 else: ce = cmodel.appsi_expr_from_pyomo_expr(obj.expr, self._var_map, self._param_map, self._pyomo_expr_types) if obj.sense is minimize: sense = 0 else: sense = 1 cobj = cmodel.FBBTObjective(ce) cobj.sense = sense self._cmodel.objective = cobj self._objective = obj if self._symbolic_solver_labels and obj is not None: cobj.name = self._symbol_map.getSymbol(obj, self._obj_labeler) def _update_pyomo_var_bounds(self): for cv, v in self._rvar_map.items(): cv_lb = cv.get_lb() cv_ub = cv.get_ub() if -cmodel.inf < cv_lb: v.setlb(cv_lb) v_id = id(v) _v, _lb, _ub, _fixed, _domain, _value = self._vars[v_id] self._vars[v_id] = (_v, cv_lb, _ub, _fixed, _domain, _value) if cv_ub < cmodel.inf: v.setub(cv_ub) v_id = id(v) _v, _lb, _ub, _fixed, _domain, _value = self._vars[v_id] self._vars[v_id] = (_v, _lb, cv_ub, _fixed, _domain, _value) def _deactivate_satisfied_cons(self): cons_to_deactivate = list() if self.config.deactivate_satisfied_constraints: for c, cc in self._con_map.items(): if not cc.active: cons_to_deactivate.append(c) self.remove_constraints(cons_to_deactivate) for c in cons_to_deactivate: c.deactivate() def perform_fbbt(self, model: _BlockData, symbolic_solver_labels: Optional[bool] = None): if model is not self._model: self.set_instance(model, symbolic_solver_labels=symbolic_solver_labels) else: if symbolic_solver_labels is not None and symbolic_solver_labels != self._symbolic_solver_labels: raise RuntimeError( 'symbolic_solver_labels can only be changed through the set_instance method. ' 'Please either use set_instance or create a new instance of IntervalTightener.' ) self.update() try: n_iter = self._cmodel.perform_fbbt( self.config.feasibility_tol, self.config.integer_tol, self.config.improvement_tol, self.config.max_iter, self.config.deactivate_satisfied_constraints) finally: # we want to make sure the pyomo model and cmodel stay in sync # even if an exception is raised and caught self._update_pyomo_var_bounds() self._deactivate_satisfied_cons() return n_iter def perform_fbbt_with_seed(self, model: _BlockData, seed_var: _GeneralVarData): if model is not self._model: self.set_instance(model) else: self.update() try: n_iter = self._cmodel.perform_fbbt_with_seed( self._var_map[id(seed_var)], self.config.feasibility_tol, self.config.integer_tol, self.config.improvement_tol, self.config.max_iter, self.config.deactivate_satisfied_constraints) finally: # we want to make sure the pyomo model and cmodel stay in sync # even if an exception is raised and caught self._update_pyomo_var_bounds() self._deactivate_satisfied_cons() return n_iter
class LPWriter(PersistentBase): def __init__(self): super(LPWriter, self).__init__() self._config = WriterConfig() self._writer = None self._symbol_map = SymbolMap() self._var_labeler = None self._con_labeler = None self._param_labeler = None self._obj_labeler = None self._pyomo_var_to_solver_var_map = dict() self._pyomo_con_to_solver_con_map = dict() self._solver_var_to_pyomo_var_map = dict() self._solver_con_to_pyomo_con_map = dict() self._pyomo_param_to_solver_param_map = dict() self._expr_types = None @property def config(self): return self._config @config.setter def config(self, val: WriterConfig): self._config = val def set_instance(self, model): saved_config = self.config saved_update_config = self.update_config self.__init__() self.config = saved_config self.update_config = saved_update_config self._model = model self._expr_types = cmodel.PyomoExprTypes() if self.config.symbolic_solver_labels: self._var_labeler = TextLabeler() self._con_labeler = TextLabeler() self._param_labeler = TextLabeler() self._obj_labeler = TextLabeler() else: self._var_labeler = NumericLabeler('x') self._con_labeler = NumericLabeler('c') self._param_labeler = NumericLabeler('p') self._obj_labeler = NumericLabeler('obj') self._writer = cmodel.LPWriter() self.add_block(model) if self._objective is None: self.set_objective(None) def _add_variables(self, variables: List[_GeneralVarData]): cmodel.process_pyomo_vars(self._expr_types, variables, self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map, self._vars, self._solver_var_to_pyomo_var_map, True, self._symbol_map, self._var_labeler, False) def _add_params(self, params: List[_ParamData]): cparams = cmodel.create_params(len(params)) for ndx, p in enumerate(params): cp = cparams[ndx] cp.name = self._symbol_map.getSymbol(p, self._param_labeler) cp.value = p.value self._pyomo_param_to_solver_param_map[id(p)] = cp def _add_constraints(self, cons: List[_GeneralConstraintData]): cmodel.process_lp_constraints(cons, self) def _add_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('LP writer does not yet support SOS constraints') def _remove_constraints(self, cons: List[_GeneralConstraintData]): for c in cons: cc = self._pyomo_con_to_solver_con_map.pop(c) self._writer.remove_constraint(cc) self._symbol_map.removeSymbol(c) self._con_labeler.remove_obj(c) del self._solver_con_to_pyomo_con_map[cc] def _remove_sos_constraints(self, cons: List[_SOSConstraintData]): if len(cons) != 0: raise NotImplementedError('LP writer does not yet support SOS constraints') def _remove_variables(self, variables: List[_GeneralVarData]): for v in variables: cvar = self._pyomo_var_to_solver_var_map.pop(id(v)) del self._solver_var_to_pyomo_var_map[cvar] self._symbol_map.removeSymbol(v) self._var_labeler.remove_obj(v) def _remove_params(self, params: List[_ParamData]): for p in params: del self._pyomo_param_to_solver_param_map[id(p)] self._symbol_map.removeSymbol(p) self._param_labeler.remove_obj(p) def _update_variables(self, variables: List[_GeneralVarData]): cmodel.process_pyomo_vars(self._expr_types, variables, self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map, self._vars, self._solver_var_to_pyomo_var_map, False, None, None, True) def update_params(self): for p_id, p in self._params.items(): cp = self._pyomo_param_to_solver_param_map[p_id] cp.value = p.value def _set_objective(self, obj: _GeneralObjectiveData): cobj = cmodel.process_lp_objective(self._expr_types, obj, self._pyomo_var_to_solver_var_map, self._pyomo_param_to_solver_param_map) if obj is None: sense = 0 cname = 'objective' else: cname = self._symbol_map.getSymbol(obj, self._obj_labeler) if obj.sense is minimize: sense = 0 else: sense = 1 cobj.sense = sense cobj.name = cname self._writer.objective = cobj def write(self, model: _BlockData, filename: str, timer: HierarchicalTimer = None): if timer is None: timer = HierarchicalTimer() if model is not self._model: timer.start('set_instance') self.set_instance(model) timer.stop('set_instance') else: timer.start('update') self.update(timer=timer) timer.stop('update') timer.start('write file') self._writer.write(filename) timer.stop('write file') def get_vars(self): return [self._solver_var_to_pyomo_var_map[i] for i in self._writer.get_solve_vars()] def get_ordered_cons(self): return [self._solver_con_to_pyomo_con_map[i] for i in self._writer.get_solve_cons()] def get_active_objective(self): return self._objective @property def symbol_map(self): return self._symbol_map
def __call__(self, model, output_filename, solver_capability, io_options): # Make sure not to modify the user's dictionary, they may be # reusing it outside of this call io_options = dict(io_options) # NOTE: io_options is a simple dictionary of keyword-value # pairs specific to this writer. symbolic_solver_labels = \ io_options.pop("symbolic_solver_labels", False) labeler = io_options.pop("labeler", None) # How much effort do we want to put into ensuring the # LP file is written deterministically for a Pyomo model: # 0 : None # 1 : sort keys of indexed components (default) # 2 : sort keys AND sort names (over declaration order) file_determinism = io_options.pop("file_determinism", 1) sorter = SortComponents.unsorted if file_determinism >= 1: sorter = sorter | SortComponents.indices if file_determinism >= 2: sorter = sorter | SortComponents.alphabetical output_fixed_variable_bounds = \ io_options.pop("output_fixed_variable_bounds", False) # Skip writing constraints whose body section is fixed (i.e., # no variables) skip_trivial_constraints = \ io_options.pop("skip_trivial_constraints", False) # Note: Baron does not allow specification of runtime # option outside of this file, so we add support # for them here solver_options = io_options.pop("solver_options", {}) if len(io_options): raise ValueError( "ProblemWriter_baron_writer passed unrecognized io_options:\n\t" + "\n\t".join("%s = %s" % (k,v) for k,v in iteritems(io_options))) if symbolic_solver_labels and (labeler is not None): raise ValueError("Baron problem writer: Using both the " "'symbolic_solver_labels' and 'labeler' " "I/O options is forbidden") # Make sure there are no strange ActiveComponents. The expression # walker will handle strange things in constraints later. model_ctypes = model.collect_ctypes(active=True) invalids = set() for t in (model_ctypes - valid_active_ctypes_minlp): if issubclass(t, ActiveComponent): invalids.add(t) if len(invalids): invalids = [t.__name__ for t in invalids] raise RuntimeError( "Unallowable active component(s) %s.\nThe BARON writer cannot " "export models with this component type." % ", ".join(invalids)) if output_filename is None: output_filename = model.name + ".bar" output_file=open(output_filename, "w") # Process the options. Rely on baron to catch # and reset bad option values output_file.write("OPTIONS {\n") summary_found = False if len(solver_options): for key, val in iteritems(solver_options): if (key.lower() == 'summary'): summary_found = True if key.endswith("Name"): output_file.write(key+": \""+str(val)+"\";\n") else: output_file.write(key+": "+str(val)+";\n") if not summary_found: # The 'summary option is defaulted to 0, so that no # summary file is generated in the directory where the # user calls baron. Check if a user explicitly asked for # a summary file. output_file.write("Summary: 0;\n") output_file.write("}\n\n") if symbolic_solver_labels: v_labeler = AlphaNumericTextLabeler() c_labeler = AlphaNumericTextLabeler() elif labeler is None: v_labeler = NumericLabeler('x') c_labeler = NumericLabeler('c') symbol_map = SymbolMap() symbol_map.default_labeler = v_labeler #sm_bySymbol = symbol_map.bySymbol # Cache the list of model blocks so we don't have to call # model.block_data_objects() many many times, which is slow # for indexed blocks all_blocks_list = list(model.block_data_objects(active=True, sort=sorter, descend_into=True)) active_components_data_var = {} #for block in all_blocks_list: # tmp = active_components_data_var[id(block)] = \ # list(obj for obj in block.component_data_objects(Var, # sort=sorter, # descend_into=False)) # create_symbols_func(symbol_map, tmp, labeler) # GAH: Not sure this is necessary, and also it would break for # non-mutable indexed params so I am commenting out for now. #for param_data in active_components_data(block, Param, sort=sorter): #instead of checking if param_data._mutable: #if not param_data.is_constant(): # create_symbol_func(symbol_map, param_data, labeler) #symbol_map_variable_ids = set(symbol_map.byObject.keys()) #object_symbol_dictionary = symbol_map.byObject # # Go through the objectives and constraints and generate # the output so that we can obtain the set of referenced # variables. # equation_section_stream = StringIO() referenced_variable_ids, branching_priorities_suffixes = \ self._write_equations_section( model, equation_section_stream, all_blocks_list, active_components_data_var, symbol_map, c_labeler, output_fixed_variable_bounds, skip_trivial_constraints, sorter) # # BINARY_VARIABLES, INTEGER_VARIABLES, POSITIVE_VARIABLES, VARIABLES # BinVars = [] IntVars = [] PosVars = [] Vars = [] for vid in referenced_variable_ids: name = symbol_map.byObject[vid] var_data = symbol_map.bySymbol[name]() if var_data.is_continuous(): if var_data.has_lb() and \ (self._get_bound(var_data.lb) >= 0): TypeList = PosVars else: TypeList = Vars elif var_data.is_binary(): TypeList = BinVars elif var_data.is_integer(): TypeList = IntVars else: assert False TypeList.append(name) if len(BinVars) > 0: BinVars.sort() output_file.write('BINARY_VARIABLES ') output_file.write(", ".join(BinVars)) output_file.write(';\n\n') if len(IntVars) > 0: IntVars.sort() output_file.write('INTEGER_VARIABLES ') output_file.write(", ".join(IntVars)) output_file.write(';\n\n') PosVars.append('ONE_VAR_CONST__') PosVars.sort() output_file.write('POSITIVE_VARIABLES ') output_file.write(", ".join(PosVars)) output_file.write(';\n\n') if len(Vars) > 0: Vars.sort() output_file.write('VARIABLES ') output_file.write(", ".join(Vars)) output_file.write(';\n\n') # # LOWER_BOUNDS # lbounds = {} for vid in referenced_variable_ids: name = symbol_map.byObject[vid] var_data = symbol_map.bySymbol[name]() if var_data.fixed: if output_fixed_variable_bounds: var_data_lb = var_data.value else: var_data_lb = None else: var_data_lb = None if var_data.has_lb(): var_data_lb = self._get_bound(var_data.lb) if var_data_lb is not None: name_to_output = symbol_map.getSymbol(var_data) lb_string_template = '%s: %'+self._precision_string+';\n' lbounds[name_to_output] = lb_string_template % (name_to_output, var_data_lb) if len(lbounds) > 0: output_file.write("LOWER_BOUNDS{\n") output_file.write("".join( lbounds[key] for key in sorted(lbounds.keys()) ) ) output_file.write("}\n\n") lbounds = None # # UPPER_BOUNDS # ubounds = {} for vid in referenced_variable_ids: name = symbol_map.byObject[vid] var_data = symbol_map.bySymbol[name]() if var_data.fixed: if output_fixed_variable_bounds: var_data_ub = var_data.value else: var_data_ub = None else: var_data_ub = None if var_data.has_ub(): var_data_ub = self._get_bound(var_data.ub) if var_data_ub is not None: name_to_output = symbol_map.getSymbol(var_data) ub_string_template = '%s: %'+self._precision_string+';\n' ubounds[name_to_output] = ub_string_template % (name_to_output, var_data_ub) if len(ubounds) > 0: output_file.write("UPPER_BOUNDS{\n") output_file.write("".join( ubounds[key] for key in sorted(ubounds.keys()) ) ) output_file.write("}\n\n") ubounds = None # # BRANCHING_PRIORITIES # # Specifying priorities requires that the pyomo model has established an # EXTERNAL, float suffix called 'branching_priorities' on the model # object, indexed by the relevant variable BranchingPriorityHeader = False for suffix in branching_priorities_suffixes: for var_data, priority in iteritems(suffix): if id(var_data) not in referenced_variable_ids: continue if priority is not None: if not BranchingPriorityHeader: output_file.write('BRANCHING_PRIORITIES{\n') BranchingPriorityHeader = True name_to_output = symbol_map.getSymbol(var_data) output_file.write(name_to_output+': '+str(priority)+';\n') if BranchingPriorityHeader: output_file.write("}\n\n") # # Now write the objective and equations section # output_file.write(equation_section_stream.getvalue()) # # STARTING_POINT # output_file.write('STARTING_POINT{\nONE_VAR_CONST__: 1;\n') tmp = {} string_template = '%s: %'+self._precision_string+';\n' for vid in referenced_variable_ids: name = symbol_map.byObject[vid] var_data = symbol_map.bySymbol[name]() starting_point = var_data.value if starting_point is not None: var_name = symbol_map.getSymbol(var_data) tmp[var_name] = string_template % (var_name, starting_point) output_file.write("".join( tmp[key] for key in sorted(tmp.keys()) )) output_file.write('}\n\n') output_file.close() return output_filename, symbol_map