def _build_equality_set(model): """Construct an equality set map. Maps all variables to the set of variables that are linked to them by equality. Mapping takes place using id(). That is, if you have x = y, then you would have id(x) -> ComponentSet([x, y]) and id(y) -> ComponentSet([x, y]) in the mapping. """ # Map of variables to their equality set (ComponentSet) eq_var_map = ComponentMap() # Loop through all the active constraints in the model for constraint in model.component_data_objects( ctype=Constraint, active=True, descend_into=True): eq_linked_vars = _get_equality_linked_variables(constraint) if not eq_linked_vars: continue # if we get an empty tuple, skip to next constraint. v1, v2 = eq_linked_vars set1 = eq_var_map.get(v1, ComponentSet((v1, v2))) set2 = eq_var_map.get(v2, (v2,)) # if set1 and set2 are equivalent, skip to next constraint. if set1 is set2: continue # add all elements of set2 to set 1 set1.update(set2) # Update all elements to point to set 1 for v in set1: eq_var_map[v] = set1 return eq_var_map
def __init__(self, **kwds): # Suffix type information self._direction = None self._datatype = None self._rule = None # The suffix direction direction = kwds.pop('direction', Suffix.LOCAL) # The suffix datatype datatype = kwds.pop('datatype', Suffix.FLOAT) # The suffix construction rule # TODO: deprecate the use of 'rule' self._rule = kwds.pop('rule', None) self._rule = kwds.pop('initialize', self._rule) # Check that keyword values make sense (these function have # internal error checking). self.set_direction(direction) self.set_datatype(datatype) # Initialize base classes kwds.setdefault('ctype', Suffix) ActiveComponent.__init__(self, **kwds) ComponentMap.__init__(self) if self._rule is None: self.construct()
def __init__(self, expression, improved_var_bounds=None): super(MCPP_visitor, self).__init__() self.mcpp = _MCPP_lib() so_file_version = self.mcpp.get_version() if six.PY3: so_file_version = so_file_version.decode("utf-8") if not so_file_version == __version__: raise MCPP_Error( "Shared object file version %s is out of date with MC++ interface version %s. " "Please rebuild the library." % (so_file_version, __version__)) self.missing_value_warnings = [] self.expr = expression vars = list(identify_variables(expression, include_fixed=False)) self.num_vars = len(vars) # Map expression variables to MC variables self.known_vars = ComponentMap() # Map expression variables to their index self.var_to_idx = ComponentMap() # Pre-register all variables inf = float('inf') for i, var in enumerate(vars): self.var_to_idx[var] = i # check if improved variable bound is provided if improved_var_bounds is not None: lb, ub = improved_var_bounds.get(var, (-inf, inf)) else: lb, ub = -inf, inf self.known_vars[var] = self.register_var(var, lb, ub) self.refs = None
def __setstate__(self, state): """ This method must be defined for deepcopy/pickling because this class relies on component ids. """ ActiveComponent.__setstate__(self, state) ComponentMap.__setstate__(self, state)
def _bilinear_expressions(model): # TODO for now, we look for only expressions where the bilinearities are # exposed on the root level SumExpression, and thus accessible via # generate_standard_repn. This will not detect exp(x*y). We require a # factorization transformation to be applied beforehand in order to pick # these constraints up. pass # Bilinear map will be stored in the format: # x --> (y --> [constr1, constr2, ...], z --> [constr2, constr3]) bilinear_map = ComponentMap() for constr in model.component_data_objects( Constraint, active=True, descend_into=(Block, Disjunct)): if constr.body.polynomial_degree() in (1, 0): continue # Skip trivial and linear constraints repn = generate_standard_repn(constr.body) for pair in repn.quadratic_vars: v1, v2 = pair v1_pairs = bilinear_map.get(v1, ComponentMap()) if v2 in v1_pairs: # bilinear term has been found before. Simply add constraint to # the set associated with the bilinear term. v1_pairs[v2].add(constr) else: # We encounter the bilinear term for the first time. bilinear_map[v1] = v1_pairs bilinear_map[v2] = bilinear_map.get(v2, ComponentMap()) constraints_with_bilinear_pair = ComponentSet([constr]) bilinear_map[v1][v2] = constraints_with_bilinear_pair bilinear_map[v2][v1] = constraints_with_bilinear_pair return bilinear_map
def _set_instance(self, model, kwds={}): self._range_constraints = set() DirectOrPersistentSolver._set_instance(self, model, kwds) self._pyomo_con_to_solver_con_map = dict() self._solver_con_to_pyomo_con_map = ComponentMap() self._pyomo_var_to_solver_var_map = ComponentMap() self._solver_var_to_pyomo_var_map = ComponentMap() try: if model.name is not None: self._solver_model = self._gurobipy.Model(model.name) else: self._solver_model = self._gurobipy.Model() except Exception: e = sys.exc_info()[1] msg = ( 'Unable to create Gurobi model. Have you installed the Python bindings for Gurboi?\n\n\t' + 'Error message: {0}'.format(e)) raise Exception(msg) self._add_block(model) for var, n_ref in self._referenced_variables.items(): if n_ref != 0: if var.fixed: if not self._output_fixed_variable_bounds: raise ValueError( "Encountered a fixed variable (%s) inside an active objective " "or constraint expression on model %s, which is usually indicative of " "a preprocessing error. Use the IO-option 'output_fixed_variable_bounds=True' " "to suppress this error and fix the variable by overwriting its bounds in " "the Gurobi instance." % ( var.name, self._pyomo_model.name, ))
def _apply_to(self, instance, **kwds): config = self.CONFIG(kwds) if config.reversible: if any( hasattr(instance, map_name) for map_name in [ '_tmp_var_bound_strip_lb', '_tmp_var_bound_strip_ub', '_tmp_var_bound_strip_domain' ]): raise RuntimeError( 'Variable stripping reversion component maps already ' 'exist. Did you already apply a temporary transformation ' 'without a subsequent reversion?') # Component maps to store data for reversion. instance._tmp_var_bound_strip_lb = ComponentMap() instance._tmp_var_bound_strip_ub = ComponentMap() instance._tmp_var_bound_strip_domain = ComponentMap() for var in instance.component_data_objects(ctype=Var): if config.strip_domains and not var.domain == Reals: if config.reversible: instance._tmp_var_bound_strip_domain[var] = var.domain var.domain = Reals if var.has_lb(): if config.reversible: instance._tmp_var_bound_strip_lb[var] = var.lb var.setlb(None) if var.has_ub(): if config.reversible: instance._tmp_var_bound_strip_ub[var] = var.ub var.setub(None)
def test_clear(self): cmap = ComponentMap() self.assertEqual(len(cmap), 0) cmap.update(self._components) self.assertEqual(len(cmap), len(self._components)) cmap.clear() self.assertEqual(len(cmap), 0)
def _build_equality_set(model): """Construct an equality set map. Maps all variables to the set of variables that are linked to them by equality. Mapping takes place using id(). That is, if you have x = y, then you would have id(x) -> ComponentSet([x, y]) and id(y) -> ComponentSet([x, y]) in the mapping. """ # Map of variables to their equality set (ComponentSet) eq_var_map = ComponentMap() # Loop through all the active constraints in the model for constraint in model.component_data_objects(ctype=Constraint, active=True, descend_into=True): eq_linked_vars = _get_equality_linked_variables(constraint) if not eq_linked_vars: continue # if we get an empty tuple, skip to next constraint. v1, v2 = eq_linked_vars set1 = eq_var_map.get(v1, ComponentSet((v1, v2))) set2 = eq_var_map.get(v2, (v2, )) # if set1 and set2 are equivalent, skip to next constraint. if set1 is set2: continue # add all elements of set2 to set 1 set1.update(set2) # Update all elements to point to set 1 for v in set1: eq_var_map[v] = set1 return eq_var_map
def _set_instance(self, model, kwds={}): if not isinstance(model, (Model, IBlockStorage)): msg = "The problem instance supplied to the {0} plugin " \ "'_presolve' method must be of type 'Model'".format(type(self)) raise ValueError(msg) self._pyomo_model = model self._symbolic_solver_labels = kwds.pop('symbolic_solver_labels', self._symbolic_solver_labels) self._skip_trivial_constraints = kwds.pop( 'skip_trivial_constraints', self._skip_trivial_constraints) self._output_fixed_variable_bounds = kwds.pop( 'output_fixed_variable_bounds', self._output_fixed_variable_bounds) self._pyomo_var_to_solver_var_map = ComponentMap() self._pyomo_con_to_solver_con_map = ComponentMap() self._vars_referenced_by_con = ComponentMap() self._vars_referenced_by_obj = ComponentSet() self._referenced_variables = ComponentMap() self._objective_label = None self._objective = None self._symbol_map = SymbolMap() if self._symbolic_solver_labels: self._labeler = TextLabeler() else: self._labeler = NumericLabeler('x')
def _get_constraint_map_dict(self, transBlock): if not hasattr(transBlock, "_constraintMap"): transBlock._constraintMap = { 'srcConstraints': ComponentMap(), 'transformedConstraints': ComponentMap() } return transBlock._constraintMap
def test_update(self): cmap = ComponentMap() self.assertEqual(len(cmap), 0) cmap.update(self._components) self.assertEqual(len(cmap), len(self._components)) for c, val in self._components: self.assertEqual(cmap[c], val)
def _build_equality_set(m): """Construct an equality set map. Maps all variables to the set of variables that are linked to them by equality. Mapping takes place using id(). That is, if you have x = y, then you would have id(x) -> ComponentSet([x, y]) and id(y) -> ComponentSet([x, y]) in the mapping. """ #: dict: map of var UID to the set of all equality-linked var UIDs eq_var_map = ComponentMap() relevant_vars = ComponentSet() for constr in m.component_data_objects(ctype=Constraint, active=True, descend_into=True): # Check to make sure the constraint is of form v1 - v2 == 0 if (value(constr.lower) == 0 and value(constr.upper) == 0 and constr.body.polynomial_degree() == 1): repn = generate_canonical_repn(constr.body) # only take the variables with nonzero coefficients vars_ = [v for i, v in enumerate(repn.variables) if repn.linear[i]] if (len(vars_) == 2 and sorted(l for l in repn.linear if l) == [-1, 1]): # this is an a == b constraint. v1 = vars_[0] v2 = vars_[1] set1 = eq_var_map.get(v1, ComponentSet([v1])) set2 = eq_var_map.get(v2, ComponentSet([v2])) relevant_vars.update([v1, v2]) set1.update(set2) # set1 is now the union for v in set1: eq_var_map[v] = set1 return eq_var_map, relevant_vars
def _build_equality_set(m): """Construct an equality set map. Maps all variables to the set of variables that are linked to them by equality. Mapping takes place using id(). That is, if you have x = y, then you would have id(x) -> ComponentSet([x, y]) and id(y) -> ComponentSet([x, y]) in the mapping. """ #: dict: map of var UID to the set of all equality-linked var UIDs eq_var_map = ComponentMap() relevant_vars = ComponentSet() for constr in m.component_data_objects(ctype=Constraint, active=True, descend_into=True): # Check to make sure the constraint is of form v1 - v2 == 0 if (value(constr.lower) == 0 and value(constr.upper) == 0 and constr.body.polynomial_degree() == 1): repn = generate_standard_repn(constr.body) # only take the variables with nonzero coefficients vars_ = [v for i, v in enumerate(repn.linear_vars) if repn.linear_coefs[i]] if (len(vars_) == 2 and sorted(l for l in repn.linear_coefs if l) == [-1, 1]): # this is an a == b constraint. v1 = vars_[0] v2 = vars_[1] set1 = eq_var_map.get(v1, ComponentSet([v1])) set2 = eq_var_map.get(v2, ComponentSet([v2])) relevant_vars.update([v1, v2]) set1.update(set2) # set1 is now the union for v in set1: eq_var_map[v] = set1 return eq_var_map, relevant_vars
def reverse_sd(expr): """ First order reverse ad Parameters ---------- expr: pyomo.core.expr.numeric_expr.ExpressionBase expression to differentiate Returns ------- pyomo.core.kernel.component_map.ComponentMap component_map mapping variables to derivatives with respect to the corresponding variable """ val_dict = ComponentMap() der_dict = ComponentMap() visitorA = _ReverseSDVisitorLeafToRoot(val_dict, der_dict) visitorA.dfs_postorder_stack(expr) der_dict[expr] = 1 visitorB = _ReverseSDVisitorRootToLeaf(val_dict, der_dict) visitorB.dfs_postorder_stack(expr) return der_dict
def test_items(self): cmap = ComponentMap(self._components) for x in cmap.items(): self.assertEqual(type(x), tuple) self.assertEqual(len(x), 2) self.assertEqual( sorted(cmap.items(), key=lambda _x: (id(_x[0]), _x[1])), sorted(self._components, key=lambda _x: (id(_x[0]), _x[1])))
def test_len(self): cmap = ComponentMap() self.assertEqual(len(cmap), 0) cmap.update(self._components) self.assertEqual(len(cmap), len(self._components)) cmap = ComponentMap(self._components) self.assertEqual(len(cmap), len(self._components)) self.assertTrue(len(self._components) > 0)
def __init__(self, component): BaseRelaxationData.__init__(self, component) self._partitions = ComponentMap() """ComponentMap: var: list of float""" self._saved_partitions = [] """list of CompnentMap"""
def test_iter(self): cmap = ComponentMap() self.assertEqual(list(iter(cmap)), []) cmap.update(self._components) ids_seen = set() for c in cmap: ids_seen.add(id(c)) self.assertEqual(ids_seen, set(id(c) for c, val in self._components))
def __init__(self, **kwds): kwds['type'] = 'gurobi_direct' DirectSolver.__init__(self, **kwds) self._pyomo_var_to_solver_var_map = ComponentMap() self._solver_var_to_pyomo_var_map = ComponentMap() self._pyomo_con_to_solver_con_map = dict() self._solver_con_to_pyomo_con_map = ComponentMap() self._init()
def test_product_with_itself(self, visitor, f, cvx_f, bounds_f, expected): convexity = ComponentMap() convexity[f] = cvx_f bounds = ComponentMap() bounds[f] = bounds_f expr = f * f matched, result = visitor.visit_expression(expr, convexity, None, bounds) assert matched assert result == expected
def _result_with_mono_bounds(self, visitor, g, mono_g, bounds_g): mono = ComponentMap() mono[g] = mono_g bounds = ComponentMap() bounds[g] = bounds_g expr = pe.cos(g) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched return result
def test_items(self): cmap = ComponentMap(self._components) for x in cmap.items(): self.assertEqual(type(x), tuple) self.assertEqual(len(x), 2) self.assertEqual(sorted(cmap.items(), key=lambda _x: (id(_x[0]), _x[1])), sorted(self._components, key=lambda _x: (id(_x[0]), _x[1])))
def test_iter(self): cmap = ComponentMap() self.assertEqual(list(iter(cmap)), []) cmap.update(self._components) ids_seen = set() for c in cmap: ids_seen.add(id(c)) self.assertEqual(ids_seen, set(id(c) for c,val in self._components))
def test_abs(visitor, g, mono_g, bounds_g, expected): bounds = ComponentMap() bounds[g] = bounds_g mono = ComponentMap() mono[g] = mono_g expr = abs(g) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched assert result == expected
def test_reciprocal(visitor, g, mono_g, bound_g, expected): bounds = ComponentMap() bounds[g] = bound_g mono = ComponentMap() mono[g] = mono_g expr = ReciprocalExpression([g]) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched assert result == expected
def test_nonincreasing_function(visitor, g, func_name, mono_g, bounds_g): mono = ComponentMap() mono[g] = mono_g bounds = ComponentMap() bounds[g] = bounds_g func = getattr(pe, func_name) expr = func(g) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched assert result == mono_g.negate()
def test_negation(visitor, g, mono_g, bounds_g): mono = ComponentMap() mono[g] = mono_g bounds = ComponentMap() bounds[g] = bounds_g expr = -g assume(isinstance(expr, NegationExpression)) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched assert result == mono_g.negate()
def __init__(self): self.pyomo2sympy = ComponentMap() self.parent_symbol = ComponentMap() self.sympy2pyomo = {} self.sympy2latex = {} self.used = {} self.i_var = 0 self.i_expr = 0 self.i_func = 0 self.i = 0
def generate_names(self, active=None, descend_into=True, convert=str, prefix=""): """ Generate a container of fully qualified names (up to this container) for objects stored under this container. Args: active (:const:`True`/:const:`None`): Set to :const:`True` to indicate that only active components should be included. The default value of :const:`None` indicates that all components (including those that have been deactivated) should be included. *Note*: This flag is ignored for any objects that do not have an active flag. descend_into (bool): Indicates whether or not to include subcomponents of any container objects that are not components. Default is :const:`True`. convert (function): A function that converts a storage key into a string representation. Default is str. prefix (str): A string to prefix names with. Returns: A component map that behaves as a dictionary mapping component objects to names. """ assert active in (None, True) from pyomo.core.kernel.component_map import ComponentMap names = ComponentMap() # if not active, then no children can be active if (active is not None) and \ not getattr(self, _active_flag_name, True): return names name_template = (prefix + self._child_storage_delimiter_string + self._child_storage_entry_string) for child in self.children(): if (active is None) or \ getattr(child, _active_flag_name, True): names[child] = (name_template % convert(child.storage_key)) if descend_into and child._is_container and \ (not child._is_component): names.update( child.generate_names(active=active, descend_into=True, convert=convert, prefix=names[child])) return names
def _result_with_base_expo(self, visitor, base, mono_base, bounds_base, expo): mono = ComponentMap() mono[base] = mono_base mono[expo] = M.Constant bounds = ComponentMap() bounds[base] = bounds_base bounds[expo] = I(expo, expo) expr = PowExpression([base, expo]) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched return result
def test_pow(visitor, base, expo, mono_base, bounds_base, mono_expo, bounds_expo): mono = ComponentMap() mono[base] = mono_base mono[expo] = mono_expo bounds = ComponentMap() bounds[base] = bounds_base bounds[expo] = bounds_expo expr = PowExpression([base, expo]) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched assert result == M.Unknown
def __init__(self, **kwds): if 'type' not in kwds: kwds['type'] = 'gurobi_direct' super(GurobiDirect, self).__init__(**kwds) self._pyomo_var_to_solver_var_map = ComponentMap() self._solver_var_to_pyomo_var_map = ComponentMap() self._pyomo_con_to_solver_con_map = dict() self._solver_con_to_pyomo_con_map = ComponentMap() self._needs_updated = True # flag that indicates if solver_model.update() needs called before getting variable and constraint attributes self._callback = None self._callback_func = None self._name = None try: import gurobipy self._gurobipy = gurobipy self._python_api_exists = True self._version = self._gurobipy.gurobi.version() self._name = "Gurobi %s.%s%s" % self._version while len(self._version) < 4: self._version += (0, ) self._version = self._version[:4] self._version_major = self._version[0] except ImportError: self._python_api_exists = False except Exception as e: # other forms of exceptions can be thrown by the gurobi python # import. for example, a gurobipy.GurobiError exception is thrown # if all tokens for Gurobi are already in use. assuming, of # course, the license is a token license. unfortunately, you can't # import without a license, which means we can't test for the # exception above! print("Import of gurobipy failed - gurobi message=" + str(e) + "\n") self._python_api_exists = False self._range_constraints = set() self._max_obj_degree = 2 self._max_constraint_degree = 2 # Note: Undefined capabilites default to None self._capabilities.linear = True self._capabilities.quadratic_objective = True self._capabilities.quadratic_constraint = True self._capabilities.integer = True self._capabilities.sos1 = True self._capabilities.sos2 = True # fix for compatibility with pre-5.0 Gurobi if self._python_api_exists and \ (self._version_major < 5): self._max_constraint_degree = 1 self._capabilities.quadratic_constraint = False
def _rule_result(self, visitor, g, cvx, mono_g, bounds_g, func): convexity = ComponentMap() convexity[g] = cvx mono = ComponentMap() mono[g] = mono_g bounds = ComponentMap() bounds[g] = bounds_g expr = func(g) matched, result = visitor.visit_expression(expr, convexity, mono, bounds) assert matched return result
def test_visitor_tightens_new_bounds(visitor, expr): bounds = ComponentMap() assert bounds.get(expr, None) is None assert visitor.handle_result(expr, Interval(0, 2), bounds) assert bounds[expr] == Interval(0, 2) assert not visitor.handle_result(expr, Interval(0, 2), bounds) assert bounds[expr] == Interval(0, 2) assert visitor.handle_result(expr, Interval(0, 1), bounds) assert bounds[expr] == Interval(0, 1)
def calc_jacobians(solve_data, config): """Generate a map of jacobians.""" # Map nonlinear_constraint --> Map( # variable --> jacobian of constraint wrt. variable) solve_data.jacobians = ComponentMap() for c in solve_data.mip.MindtPy_utils.constraint_list: if c.body.polynomial_degree() in (1, 0): continue # skip linear constraints vars_in_constr = list(EXPR.identify_variables(c.body)) jac_list = differentiate(c.body, wrt_list=vars_in_constr) solve_data.jacobians[c] = ComponentMap( (var, jac_wrt_var) for var, jac_wrt_var in zip(vars_in_constr, jac_list))
def _result_with_base_expo(self, visitor, base, expo, mono_expo, bounds_expo): rule = PowerRule() mono = ComponentMap() mono[base] = M.Constant mono[expo] = mono_expo bounds = ComponentMap() bounds[base] = I(base, base) bounds[expo] = bounds_expo expr = base ** expo assume(isinstance(expr, PowExpression)) matched, result = visitor.visit_expression(expr, mono, bounds) assert matched return result
def test_getsetdelitem(self): cmap = ComponentMap() for c, val in self._components: self.assertTrue(c not in cmap) for c, val in self._components: cmap[c] = val self.assertEqual(cmap[c], val) self.assertEqual(cmap.get(c), val) del cmap[c] with self.assertRaises(KeyError): cmap[c] with self.assertRaises(KeyError): del cmap[c] self.assertEqual(cmap.get(c), None)
def determine_valid_values(block, discr_var_to_constrs_map, config): """Calculate valid values for each effectively discrete variable. We need the set of possible values for the effectively discrete variable in order to do the reformulations. Right now, we select a naive approach where we look for variables in the discreteness-inducing constraints. We then adjust their values and see if things are stil feasible. Based on their coefficient values, we can infer a set of allowable values for the effectively discrete variable. Args: block: The model or a disjunct on the model. """ possible_values = ComponentMap() for eff_discr_var, constrs in discr_var_to_constrs_map.items(): # get the superset of possible values by looking through the # constraints for constr in constrs: repn = generate_standard_repn(constr.body) var_coef = sum(coef for i, coef in enumerate(repn.linear_coefs) if repn.linear_vars[i] is eff_discr_var) const = -(repn.constant - constr.upper) / var_coef possible_vals = set((const,)) for i, var in enumerate(repn.linear_vars): if var is eff_discr_var: continue coef = -repn.linear_coefs[i] / var_coef if var.is_binary(): var_values = (0, coef) elif var.is_integer(): var_values = [v * coef for v in range(var.lb, var.ub + 1)] else: raise ValueError( '%s has unacceptable variable domain: %s' % (var.name, var.domain)) possible_vals = set( (v1 + v2 for v1 in possible_vals for v2 in var_values)) old_possible_vals = possible_values.get(eff_discr_var, None) if old_possible_vals is not None: possible_values[eff_discr_var] = old_possible_vals & possible_vals else: possible_values[eff_discr_var] = possible_vals possible_values = prune_possible_values(block, possible_values, config) return possible_values
def test_setdefault(self): cmap = ComponentMap() for c,_ in self._components: with self.assertRaises(KeyError): cmap[c] self.assertTrue(c not in cmap) cmap.setdefault(c, []).append(1) self.assertEqual(cmap[c], [1]) del cmap[c] with self.assertRaises(KeyError): cmap[c] self.assertTrue(c not in cmap) cmap[c] = [] cmap.setdefault(c, []).append(1) self.assertEqual(cmap[c], [1])
def _set_instance(self, model, kwds={}): self._range_constraints = set() DirectOrPersistentSolver._set_instance(self, model, kwds) self._pyomo_con_to_solver_con_map = dict() self._solver_con_to_pyomo_con_map = ComponentMap() self._pyomo_var_to_solver_var_map = ComponentMap() self._solver_var_to_pyomo_var_map = ComponentMap() try: if model.name is not None: self._solver_model = self._gurobipy.Model(model.name) else: self._solver_model = self._gurobipy.Model() except Exception: e = sys.exc_info()[1] msg = ("Unable to create Gurobi model. " "Have you installed the Python " "bindings for Gurboi?\n\n\t"+ "Error message: {0}".format(e)) raise Exception(msg) self._add_block(model) for var, n_ref in self._referenced_variables.items(): if n_ref != 0: if var.fixed: if not self._output_fixed_variable_bounds: raise ValueError( "Encountered a fixed variable (%s) inside " "an active objective or constraint " "expression on model %s, which is usually " "indicative of a preprocessing error. Use " "the IO-option 'output_fixed_variable_bounds=True' " "to suppress this error and fix the variable " "by overwriting its bounds in the Gurobi instance." % (var.name, self._pyomo_model.name,))
def test_eq(self): cmap1 = ComponentMap() self.assertNotEqual(cmap1, set()) self.assertFalse(cmap1 == set()) self.assertNotEqual(cmap1, list()) self.assertFalse(cmap1 == list()) self.assertNotEqual(cmap1, tuple()) self.assertFalse(cmap1 == tuple()) self.assertEqual(cmap1, dict()) self.assertTrue(cmap1 == dict()) cmap1.update(self._components) self.assertNotEqual(cmap1, set()) self.assertFalse(cmap1 == set()) self.assertNotEqual(cmap1, list()) self.assertFalse(cmap1 == list()) self.assertNotEqual(cmap1, tuple()) self.assertFalse(cmap1 == tuple()) self.assertNotEqual(cmap1, dict()) self.assertFalse(cmap1 == dict()) self.assertTrue(cmap1 == cmap1) self.assertEqual(cmap1, cmap1) cmap2 = ComponentMap(self._components) self.assertTrue(cmap2 == cmap1) self.assertFalse(cmap2 != cmap1) self.assertEqual(cmap2, cmap1) self.assertTrue(cmap1 == cmap2) self.assertFalse(cmap1 != cmap2) self.assertEqual(cmap1, cmap2) del cmap2[self._components[0][0]] self.assertFalse(cmap2 == cmap1) self.assertTrue(cmap2 != cmap1) self.assertNotEqual(cmap2, cmap1) self.assertFalse(cmap1 == cmap2) self.assertTrue(cmap1 != cmap2) self.assertNotEqual(cmap1, cmap2)
def detect_effectively_discrete_vars(block, equality_tolerance): """Detect effectively discrete variables. These continuous variables are the sum of discrete variables. """ # Map of effectively_discrete var --> inducing constraints effectively_discrete = ComponentMap() for constr in block.component_data_objects(Constraint, active=True): if constr.lower is None or constr.upper is None: continue # skip inequality constraints if fabs(value(constr.lower) - value(constr.upper) ) > equality_tolerance: continue # not equality constriant. Skip. if constr.body.polynomial_degree() not in (1, 0): continue # skip nonlinear expressions repn = generate_standard_repn(constr.body) if len(repn.linear_vars) < 2: # TODO should this be < 2 or < 1? # TODO we should make sure that trivial equality relations are # preprocessed before this, or we will end up reformulating # expressions that we do not need to here. continue non_discrete_vars = list(v for v in repn.linear_vars if v.is_continuous()) if len(non_discrete_vars) == 1: # We know that this is an effectively discrete continuous # variable. Add it to our identified variable list. var = non_discrete_vars[0] inducing_constraints = effectively_discrete.get(var, []) inducing_constraints.append(constr) effectively_discrete[var] = inducing_constraints # TODO we should eventually also look at cases where all other # non_discrete_vars are effectively_discrete_vars return effectively_discrete
def __init__(self, component=None): # # These lines represent in-lining of the # following constructors: # - ComponentData # - NumericValue self._component = weakref_ref(component) if (component is not None) \ else None self.vars = {} self._arcs = [] self._sources = [] self._dests = [] self._rules = {} self._splitfracs = ComponentMap()
def fbbt(comp, deactivate_satisfied_constraints=False, integer_tol=1e-5, infeasible_tol=1e-8): """ Perform FBBT on a constraint, block, or model. For more control, use fbbt_con and fbbt_block. For detailed documentation, see the docstrings for fbbt_con and fbbt_block. Parameters ---------- comp: pyomo.core.base.constraint.Constraint or pyomo.core.base.block.Block or pyomo.core.base.PyomoModel.ConcreteModel deactivate_satisfied_constraints: bool If deactivate_satisfied_constraints is True and a constraint is always satisfied, then the constranit will be deactivated integer_tol: float If the lower bound computed on a binary variable is less than or equal to integer_tol, then the lower bound is left at 0. Otherwise, the lower bound is increased to 1. If the upper bound computed on a binary variable is greater than or equal to 1-integer_tol, then the upper bound is left at 1. Otherwise the upper bound is decreased to 0. infeasible_tol: float If the bounds computed on the body of a constraint violate the bounds of the constraint by more than infeasible_tol, then the constraint is considered infeasible and an exception is raised. Returns ------- new_var_bounds: ComponentMap A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed from FBBT. """ new_var_bounds = ComponentMap() if comp.type() == Constraint: if comp.is_indexed(): for _c in comp.values(): _new_var_bounds = fbbt_con(comp, deactivate_satisfied_constraints=deactivate_satisfied_constraints, integer_tol=integer_tol, infeasible_tol=infeasible_tol) new_var_bounds.update(_new_var_bounds) else: _new_var_bounds = fbbt_con(comp, deactivate_satisfied_constraints=deactivate_satisfied_constraints, integer_tol=integer_tol, infeasible_tol=infeasible_tol) new_var_bounds.update(_new_var_bounds) elif comp.type() == Block: _new_var_bounds = fbbt_block(comp, deactivate_satisfied_constraints=deactivate_satisfied_constraints, integer_tol=integer_tol, infeasible_tol=infeasible_tol) new_var_bounds.update(_new_var_bounds) else: raise FBBTException('Cannot perform FBBT on objects of type {0}'.format(type(comp))) return new_var_bounds
class GurobiDirect(DirectSolver): def __init__(self, **kwds): kwds['type'] = 'gurobi_direct' DirectSolver.__init__(self, **kwds) self._pyomo_var_to_solver_var_map = ComponentMap() self._solver_var_to_pyomo_var_map = ComponentMap() self._pyomo_con_to_solver_con_map = dict() self._solver_con_to_pyomo_con_map = ComponentMap() self._init() def _init(self): self._name = None try: import gurobipy self._gurobipy = gurobipy self._python_api_exists = True self._version = self._gurobipy.gurobi.version() self._name = "Gurobi %s.%s%s" % self._version while len(self._version) < 4: self._version += (0,) self._version = self._version[:4] self._version_major = self._version[0] except ImportError: self._python_api_exists = False except Exception as e: # other forms of exceptions can be thrown by the gurobi python # import. for example, a gurobipy.GurobiError exception is thrown # if all tokens for Gurobi are already in use. assuming, of # course, the license is a token license. unfortunately, you can't # import without a license, which means we can't test for the # exception above! print("Import of gurobipy failed - gurobi message=" + str(e) + "\n") self._python_api_exists = False self._range_constraints = set() self._max_obj_degree = 2 self._max_constraint_degree = 2 # Note: Undefined capabilites default to None self._capabilities.linear = True self._capabilities.quadratic_objective = True self._capabilities.quadratic_constraint = True self._capabilities.integer = True self._capabilities.sos1 = True self._capabilities.sos2 = True # fix for compatibility with pre-5.0 Gurobi if self._python_api_exists and \ (self._version_major < 5): self._max_constraint_degree = 1 self._capabilities.quadratic_constraint = False def _apply_solver(self): if not self._save_results: for block in self._pyomo_model.block_data_objects(descend_into=True, active=True): for var in block.component_data_objects(ctype=pyomo.core.base.var.Var, descend_into=False, active=True, sort=False): var.stale = True if self._tee: self._solver_model.setParam('OutputFlag', 1) else: self._solver_model.setParam('OutputFlag', 0) self._solver_model.setParam('LogFile', self._log_file) if self._keepfiles: print("Solver log file: "+self._log_file) # Options accepted by gurobi (case insensitive): # ['Cutoff', 'IterationLimit', 'NodeLimit', 'SolutionLimit', 'TimeLimit', # 'FeasibilityTol', 'IntFeasTol', 'MarkowitzTol', 'MIPGap', 'MIPGapAbs', # 'OptimalityTol', 'PSDTol', 'Method', 'PerturbValue', 'ObjScale', 'ScaleFlag', # 'SimplexPricing', 'Quad', 'NormAdjust', 'BarIterLimit', 'BarConvTol', # 'BarCorrectors', 'BarOrder', 'Crossover', 'CrossoverBasis', 'BranchDir', # 'Heuristics', 'MinRelNodes', 'MIPFocus', 'NodefileStart', 'NodefileDir', # 'NodeMethod', 'PumpPasses', 'RINS', 'SolutionNumber', 'SubMIPNodes', 'Symmetry', # 'VarBranch', 'Cuts', 'CutPasses', 'CliqueCuts', 'CoverCuts', 'CutAggPasses', # 'FlowCoverCuts', 'FlowPathCuts', 'GomoryPasses', 'GUBCoverCuts', 'ImpliedCuts', # 'MIPSepCuts', 'MIRCuts', 'NetworkCuts', 'SubMIPCuts', 'ZeroHalfCuts', 'ModKCuts', # 'Aggregate', 'AggFill', 'PreDual', 'DisplayInterval', 'IISMethod', 'InfUnbdInfo', # 'LogFile', 'PreCrush', 'PreDepRow', 'PreMIQPMethod', 'PrePasses', 'Presolve', # 'ResultFile', 'ImproveStartTime', 'ImproveStartGap', 'Threads', 'Dummy', 'OutputFlag'] for key, option in self.options.items(): # When options come from the pyomo command, all # values are string types, so we try to cast # them to a numeric value in the event that # setting the parameter fails. try: self._solver_model.setParam(key, option) except TypeError: # we place the exception handling for # checking the cast of option to a float in # another function so that we can simply # call raise here instead of except # TypeError as e / raise e, because the # latter does not preserve the Gurobi stack # trace if not _is_numeric(option): raise self._solver_model.setParam(key, float(option)) if self._version_major >= 5: for suffix in self._suffixes: if re.match(suffix, "dual"): self._solver_model.setParam(self._gurobipy.GRB.Param.QCPDual, 1) self._solver_model.optimize() self._solver_model.setParam('LogFile', 'default') # FIXME: can we get a return code indicating if Gurobi had a significant failure? return Bunch(rc=None, log=None) def _get_expr_from_pyomo_repn(self, repn, max_degree=2): referenced_vars = ComponentSet() degree = repn.polynomial_degree() if (degree is None) or (degree > max_degree): raise DegreeError('GurobiDirect does not support expressions of degree {0}.'.format(degree)) if len(repn.linear_vars) > 0: referenced_vars.update(repn.linear_vars) new_expr = self._gurobipy.LinExpr(repn.linear_coefs, [self._pyomo_var_to_solver_var_map[i] for i in repn.linear_vars]) else: new_expr = 0.0 for i,v in enumerate(repn.quadratic_vars): x,y = v new_expr += repn.quadratic_coefs[i] * self._pyomo_var_to_solver_var_map[x] * self._pyomo_var_to_solver_var_map[y] referenced_vars.add(x) referenced_vars.add(y) new_expr += repn.constant return new_expr, referenced_vars def _get_expr_from_pyomo_expr(self, expr, max_degree=2): if max_degree == 2: repn = generate_standard_repn(expr, quadratic=True) else: repn = generate_standard_repn(expr, quadratic=False) try: gurobi_expr, referenced_vars = self._get_expr_from_pyomo_repn(repn, max_degree) except DegreeError as e: msg = e.args[0] msg += '\nexpr: {0}'.format(expr) raise DegreeError(msg) return gurobi_expr, referenced_vars def _add_var(self, var): varname = self._symbol_map.getSymbol(var, self._labeler) vtype = self._gurobi_vtype_from_var(var) if var.has_lb(): lb = value(var.lb) else: lb = -self._gurobipy.GRB.INFINITY if var.has_ub(): ub = value(var.ub) else: ub = self._gurobipy.GRB.INFINITY gurobipy_var = self._solver_model.addVar(lb=lb, ub=ub, vtype=vtype, name=varname) self._pyomo_var_to_solver_var_map[var] = gurobipy_var self._solver_var_to_pyomo_var_map[gurobipy_var] = var self._referenced_variables[var] = 0 if var.is_fixed(): gurobipy_var.setAttr('lb', var.value) gurobipy_var.setAttr('ub', var.value) def _set_instance(self, model, kwds={}): self._range_constraints = set() DirectOrPersistentSolver._set_instance(self, model, kwds) self._pyomo_con_to_solver_con_map = dict() self._solver_con_to_pyomo_con_map = ComponentMap() self._pyomo_var_to_solver_var_map = ComponentMap() self._solver_var_to_pyomo_var_map = ComponentMap() try: if model.name is not None: self._solver_model = self._gurobipy.Model(model.name) else: self._solver_model = self._gurobipy.Model() except Exception: e = sys.exc_info()[1] msg = ("Unable to create Gurobi model. " "Have you installed the Python " "bindings for Gurboi?\n\n\t"+ "Error message: {0}".format(e)) raise Exception(msg) self._add_block(model) for var, n_ref in self._referenced_variables.items(): if n_ref != 0: if var.fixed: if not self._output_fixed_variable_bounds: raise ValueError( "Encountered a fixed variable (%s) inside " "an active objective or constraint " "expression on model %s, which is usually " "indicative of a preprocessing error. Use " "the IO-option 'output_fixed_variable_bounds=True' " "to suppress this error and fix the variable " "by overwriting its bounds in the Gurobi instance." % (var.name, self._pyomo_model.name,)) def _add_block(self, block): DirectOrPersistentSolver._add_block(self, block) self._solver_model.update() def _add_constraint(self, con): if not con.active: return None if is_fixed(con.body): if self._skip_trivial_constraints: return None conname = self._symbol_map.getSymbol(con, self._labeler) if con._linear_canonical_form: gurobi_expr, referenced_vars = self._get_expr_from_pyomo_repn( con.canonical_form(), self._max_constraint_degree) #elif isinstance(con, LinearCanonicalRepn): # gurobi_expr, referenced_vars = self._get_expr_from_pyomo_repn( # con, # self._max_constraint_degree) else: gurobi_expr, referenced_vars = self._get_expr_from_pyomo_expr( con.body, self._max_constraint_degree) if con.has_lb(): if not is_fixed(con.lower): raise ValueError("Lower bound of constraint {0} " "is not constant.".format(con)) if con.has_ub(): if not is_fixed(con.upper): raise ValueError("Upper bound of constraint {0} " "is not constant.".format(con)) if con.equality: gurobipy_con = self._solver_model.addConstr(lhs=gurobi_expr, sense=self._gurobipy.GRB.EQUAL, rhs=value(con.lower), name=conname) elif con.has_lb() and con.has_ub(): gurobipy_con = self._solver_model.addRange(gurobi_expr, value(con.lower), value(con.upper), name=conname) self._range_constraints.add(con) elif con.has_lb(): gurobipy_con = self._solver_model.addConstr(lhs=gurobi_expr, sense=self._gurobipy.GRB.GREATER_EQUAL, rhs=value(con.lower), name=conname) elif con.has_ub(): gurobipy_con = self._solver_model.addConstr(lhs=gurobi_expr, sense=self._gurobipy.GRB.LESS_EQUAL, rhs=value(con.upper), name=conname) else: raise ValueError("Constraint does not have a lower " "or an upper bound: {0} \n".format(con)) for var in referenced_vars: self._referenced_variables[var] += 1 self._vars_referenced_by_con[con] = referenced_vars self._pyomo_con_to_solver_con_map[con] = gurobipy_con self._solver_con_to_pyomo_con_map[gurobipy_con] = con def _add_sos_constraint(self, con): if not con.active: return None conname = self._symbol_map.getSymbol(con, self._labeler) level = con.level if level == 1: sos_type = self._gurobipy.GRB.SOS_TYPE1 elif level == 2: sos_type = self._gurobipy.GRB.SOS_TYPE2 else: raise ValueError("Solver does not support SOS " "level {0} constraints".format(level)) gurobi_vars = [] weights = [] self._vars_referenced_by_con[con] = ComponentSet() if hasattr(con, 'get_items'): # aml sos constraint sos_items = list(con.get_items()) else: # kernel sos constraint sos_items = list(con.items()) for v, w in sos_items: self._vars_referenced_by_con[con].add(v) gurobi_vars.append(self._pyomo_var_to_solver_var_map[v]) self._referenced_variables[v] += 1 weights.append(w) gurobipy_con = self._solver_model.addSOS(sos_type, gurobi_vars, weights) self._pyomo_con_to_solver_con_map[con] = gurobipy_con self._solver_con_to_pyomo_con_map[gurobipy_con] = con def _gurobi_vtype_from_var(self, var): """ This function takes a pyomo variable and returns the appropriate gurobi variable type :param var: pyomo.core.base.var.Var :return: gurobipy.GRB.CONTINUOUS or gurobipy.GRB.BINARY or gurobipy.GRB.INTEGER """ if var.is_binary(): vtype = self._gurobipy.GRB.BINARY elif var.is_integer(): vtype = self._gurobipy.GRB.INTEGER elif var.is_continuous(): vtype = self._gurobipy.GRB.CONTINUOUS else: raise ValueError('Variable domain type is not recognized for {0}'.format(var.domain)) return vtype def _set_objective(self, obj): if self._objective is not None: for var in self._vars_referenced_by_obj: self._referenced_variables[var] -= 1 self._vars_referenced_by_obj = ComponentSet() self._objective = None if obj.active is False: raise ValueError('Cannot add inactive objective to solver.') if obj.sense == minimize: sense = self._gurobipy.GRB.MINIMIZE elif obj.sense == maximize: sense = self._gurobipy.GRB.MAXIMIZE else: raise ValueError('Objective sense is not recognized: {0}'.format(obj.sense)) gurobi_expr, referenced_vars = self._get_expr_from_pyomo_expr(obj.expr, self._max_obj_degree) for var in referenced_vars: self._referenced_variables[var] += 1 self._solver_model.setObjective(gurobi_expr, sense=sense) self._objective = obj self._vars_referenced_by_obj = referenced_vars def _postsolve(self): # the only suffixes that we extract from GUROBI are # constraint duals, constraint slacks, and variable # reduced-costs. scan through the solver suffix list # and throw an exception if the user has specified # any others. extract_duals = False extract_slacks = False extract_reduced_costs = False for suffix in self._suffixes: flag = False if re.match(suffix, "dual"): extract_duals = True flag = True if re.match(suffix, "slack"): extract_slacks = True flag = True if re.match(suffix, "rc"): extract_reduced_costs = True flag = True if not flag: raise RuntimeError("***The gurobi_direct solver plugin cannot extract solution suffix="+suffix) gprob = self._solver_model grb = self._gurobipy.GRB status = gprob.Status if gprob.getAttr(self._gurobipy.GRB.Attr.IsMIP): if extract_reduced_costs: logger.warning("Cannot get reduced costs for MIP.") if extract_duals: logger.warning("Cannot get duals for MIP.") extract_reduced_costs = False extract_duals = False self.results = SolverResults() soln = Solution() self.results.solver.name = self._name self.results.solver.wallclock_time = gprob.Runtime if status == grb.LOADED: # problem is loaded, but no solution self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Model is loaded, but no solution information is available." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.unknown elif status == grb.OPTIMAL: # optimal self.results.solver.status = SolverStatus.ok self.results.solver.termination_message = "Model was solved to optimality (subject to tolerances), " \ "and an optimal solution is available." self.results.solver.termination_condition = TerminationCondition.optimal soln.status = SolutionStatus.optimal elif status == grb.INFEASIBLE: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Model was proven to be infeasible" self.results.solver.termination_condition = TerminationCondition.infeasible soln.status = SolutionStatus.infeasible elif status == grb.INF_OR_UNBD: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Problem proven to be infeasible or unbounded." self.results.solver.termination_condition = TerminationCondition.infeasibleOrUnbounded soln.status = SolutionStatus.unsure elif status == grb.UNBOUNDED: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Model was proven to be unbounded." self.results.solver.termination_condition = TerminationCondition.unbounded soln.status = SolutionStatus.unbounded elif status == grb.CUTOFF: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimal objective for model was proven to be worse than the " \ "value specified in the Cutoff parameter. No solution " \ "information is available." self.results.solver.termination_condition = TerminationCondition.minFunctionValue soln.status = SolutionStatus.unknown elif status == grb.ITERATION_LIMIT: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization terminated because the total number of simplex " \ "iterations performed exceeded the value specified in the " \ "IterationLimit parameter." self.results.solver.termination_condition = TerminationCondition.maxIterations soln.status = SolutionStatus.stoppedByLimit elif status == grb.NODE_LIMIT: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization terminated because the total number of " \ "branch-and-cut nodes explored exceeded the value specified " \ "in the NodeLimit parameter" self.results.solver.termination_condition = TerminationCondition.maxEvaluations soln.status = SolutionStatus.stoppedByLimit elif status == grb.TIME_LIMIT: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization terminated because the time expended exceeded " \ "the value specified in the TimeLimit parameter." self.results.solver.termination_condition = TerminationCondition.maxTimeLimit soln.status = SolutionStatus.stoppedByLimit elif status == grb.SOLUTION_LIMIT: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization terminated because the number of solutions found " \ "reached the value specified in the SolutionLimit parameter." self.results.solver.termination_condition = TerminationCondition.unknown soln.status = SolutionStatus.stoppedByLimit elif status == grb.INTERRUPTED: self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "Optimization was terminated by the user." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error elif status == grb.NUMERIC: self.results.solver.status = SolverStatus.error self.results.solver.termination_message = "Optimization was terminated due to unrecoverable numerical " \ "difficulties." self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error elif status == grb.SUBOPTIMAL: self.results.solver.status = SolverStatus.warning self.results.solver.termination_message = "Unable to satisfy optimality tolerances; a sub-optimal " \ "solution is available." self.results.solver.termination_condition = TerminationCondition.other soln.status = SolutionStatus.feasible # note that USER_OBJ_LIMIT was added in Gurobi 7.0, so it may not be present elif (status is not None) and \ (status == getattr(grb,'USER_OBJ_LIMIT',None)): self.results.solver.status = SolverStatus.aborted self.results.solver.termination_message = "User specified an objective limit " \ "(a bound on either the best objective " \ "or the best bound), and that limit has " \ "been reached. Solution is available." self.results.solver.termination_condition = TerminationCondition.other soln.status = SolutionStatus.stoppedByLimit else: self.results.solver.status = SolverStatus.error self.results.solver.termination_message = \ ("Unhandled Gurobi solve status " "("+str(status)+")") self.results.solver.termination_condition = TerminationCondition.error soln.status = SolutionStatus.error self.results.problem.name = gprob.ModelName if gprob.ModelSense == 1: self.results.problem.sense = minimize elif gprob.ModelSense == -1: self.results.problem.sense = maximize else: raise RuntimeError('Unrecognized gurobi objective sense: {0}'.format(gprob.ModelSense)) self.results.problem.upper_bound = None self.results.problem.lower_bound = None if (gprob.NumBinVars + gprob.NumIntVars) == 0: try: self.results.problem.upper_bound = gprob.ObjVal self.results.problem.lower_bound = gprob.ObjVal except (self._gurobipy.GurobiError, AttributeError): pass elif gprob.ModelSense == 1: # minimizing try: self.results.problem.upper_bound = gprob.ObjVal except (self._gurobipy.GurobiError, AttributeError): pass try: self.results.problem.lower_bound = gprob.ObjBound except (self._gurobipy.GurobiError, AttributeError): pass elif gprob.ModelSense == -1: # maximizing try: self.results.problem.upper_bound = gprob.ObjBound except (self._gurobipy.GurobiError, AttributeError): pass try: self.results.problem.lower_bound = gprob.ObjVal except (self._gurobipy.GurobiError, AttributeError): pass else: raise RuntimeError('Unrecognized gurobi objective sense: {0}'.format(gprob.ModelSense)) try: soln.gap = self.results.problem.upper_bound - self.results.problem.lower_bound except TypeError: soln.gap = None self.results.problem.number_of_constraints = gprob.NumConstrs + gprob.NumQConstrs + gprob.NumSOS self.results.problem.number_of_nonzeros = gprob.NumNZs self.results.problem.number_of_variables = gprob.NumVars self.results.problem.number_of_binary_variables = gprob.NumBinVars self.results.problem.number_of_integer_variables = gprob.NumIntVars self.results.problem.number_of_continuous_variables = gprob.NumVars - gprob.NumIntVars - gprob.NumBinVars self.results.problem.number_of_objectives = 1 self.results.problem.number_of_solutions = gprob.SolCount # if a solve was stopped by a limit, we still need to check to # see if there is a solution available - this may not always # be the case, both in LP and MIP contexts. if self._save_results: """ This code in this if statement is only needed for backwards compatability. It is more efficient to set _save_results to False and use load_vars, load_duals, etc. """ if gprob.SolCount > 0: soln_variables = soln.variable soln_constraints = soln.constraint gurobi_vars = self._solver_model.getVars() gurobi_vars = list(set(gurobi_vars).intersection(set(self._pyomo_var_to_solver_var_map.values()))) var_vals = self._solver_model.getAttr("X", gurobi_vars) names = self._solver_model.getAttr("VarName", gurobi_vars) for gurobi_var, val, name in zip(gurobi_vars, var_vals, names): pyomo_var = self._solver_var_to_pyomo_var_map[gurobi_var] if self._referenced_variables[pyomo_var] > 0: pyomo_var.stale = False soln_variables[name] = {"Value": val} if extract_reduced_costs: vals = self._solver_model.getAttr("Rc", gurobi_vars) for gurobi_var, val, name in zip(gurobi_vars, vals, names): pyomo_var = self._solver_var_to_pyomo_var_map[gurobi_var] if self._referenced_variables[pyomo_var] > 0: soln_variables[name]["Rc"] = val if extract_duals or extract_slacks: gurobi_cons = self._solver_model.getConstrs() con_names = self._solver_model.getAttr("ConstrName", gurobi_cons) for name in con_names: soln_constraints[name] = {} if self._version_major >= 5: gurobi_q_cons = self._solver_model.getQConstrs() q_con_names = self._solver_model.getAttr("QCName", gurobi_q_cons) for name in q_con_names: soln_constraints[name] = {} if extract_duals: vals = self._solver_model.getAttr("Pi", gurobi_cons) for val, name in zip(vals, con_names): soln_constraints[name]["Dual"] = val if self._version_major >= 5: q_vals = self._solver_model.getAttr("QCPi", gurobi_q_cons) for val, name in zip(q_vals, q_con_names): soln_constraints[name]["Dual"] = val if extract_slacks: gurobi_range_con_vars = set(self._solver_model.getVars()) - set(self._pyomo_var_to_solver_var_map.values()) vals = self._solver_model.getAttr("Slack", gurobi_cons) for gurobi_con, val, name in zip(gurobi_cons, vals, con_names): pyomo_con = self._solver_con_to_pyomo_con_map[gurobi_con] if pyomo_con in self._range_constraints: lin_expr = self._solver_model.getRow(gurobi_con) for i in reversed(range(lin_expr.size())): v = lin_expr.getVar(i) if v in gurobi_range_con_vars: Us_ = v.X Ls_ = v.UB - v.X if Us_ > Ls_: soln_constraints[name]["Slack"] = Us_ else: soln_constraints[name]["Slack"] = -Ls_ break else: soln_constraints[name]["Slack"] = val if self._version_major >= 5: q_vals = self._solver_model.getAttr("QCSlack", gurobi_q_cons) for val, name in zip(q_vals, q_con_names): soln_constraints[name]["Slack"] = val elif self._load_solutions: if gprob.SolCount > 0: self._load_vars() if extract_reduced_costs: self._load_rc() if extract_duals: self._load_duals() if extract_slacks: self._load_slacks() self.results.solution.insert(soln) # finally, clean any temporary files registered with the temp file # manager, created populated *directly* by this plugin. TempfileManager.pop(remove=not self._keepfiles) return DirectOrPersistentSolver._postsolve(self) def warm_start_capable(self): return True def _warm_start(self): for pyomo_var, gurobipy_var in self._pyomo_var_to_solver_var_map.items(): if pyomo_var.value is not None: gurobipy_var.setAttr(self._gurobipy.GRB.Attr.Start, value(pyomo_var)) def _load_vars(self, vars_to_load=None): var_map = self._pyomo_var_to_solver_var_map ref_vars = self._referenced_variables if vars_to_load is None: vars_to_load = var_map.keys() gurobi_vars_to_load = [var_map[pyomo_var] for pyomo_var in vars_to_load] vals = self._solver_model.getAttr("X", gurobi_vars_to_load) for var, val in zip(vars_to_load, vals): if ref_vars[var] > 0: var.stale = False var.value = val def _load_rc(self, vars_to_load=None): if not hasattr(self._pyomo_model, 'rc'): self._pyomo_model.rc = Suffix(direction=Suffix.IMPORT) var_map = self._pyomo_var_to_solver_var_map ref_vars = self._referenced_variables rc = self._pyomo_model.rc if vars_to_load is None: vars_to_load = var_map.keys() gurobi_vars_to_load = [var_map[pyomo_var] for pyomo_var in vars_to_load] vals = self._solver_model.getAttr("Rc", gurobi_vars_to_load) for var, val in zip(vars_to_load, vals): if ref_vars[var] > 0: rc[var] = val def _load_duals(self, cons_to_load=None): if not hasattr(self._pyomo_model, 'dual'): self._pyomo_model.dual = Suffix(direction=Suffix.IMPORT) con_map = self._pyomo_con_to_solver_con_map reverse_con_map = self._solver_con_to_pyomo_con_map dual = self._pyomo_model.dual if cons_to_load is None: linear_cons_to_load = self._solver_model.getConstrs() if self._version_major >= 5: quadratic_cons_to_load = self._solver_model.getQConstrs() else: gurobi_cons_to_load = set([con_map[pyomo_con] for pyomo_con in cons_to_load]) linear_cons_to_load = gurobi_cons_to_load.intersection(set(self._solver_model.getConstrs())) if self._version_major >= 5: quadratic_cons_to_load = gurobi_cons_to_load.intersection(set(self._solver_model.getQConstrs())) linear_vals = self._solver_model.getAttr("Pi", linear_cons_to_load) if self._version_major >= 5: quadratic_vals = self._solver_model.getAttr("QCPi", quadratic_cons_to_load) for gurobi_con, val in zip(linear_cons_to_load, linear_vals): pyomo_con = reverse_con_map[gurobi_con] dual[pyomo_con] = val if self._version_major >= 5: for gurobi_con, val in zip(quadratic_cons_to_load, quadratic_vals): pyomo_con = reverse_con_map[gurobi_con] dual[pyomo_con] = val def _load_slacks(self, cons_to_load=None): if not hasattr(self._pyomo_model, 'slack'): self._pyomo_model.slack = Suffix(direction=Suffix.IMPORT) con_map = self._pyomo_con_to_solver_con_map reverse_con_map = self._solver_con_to_pyomo_con_map slack = self._pyomo_model.slack gurobi_range_con_vars = set(self._solver_model.getVars()) - set(self._pyomo_var_to_solver_var_map.values()) if cons_to_load is None: linear_cons_to_load = self._solver_model.getConstrs() if self._version_major >= 5: quadratic_cons_to_load = self._solver_model.getQConstrs() else: gurobi_cons_to_load = set([con_map[pyomo_con] for pyomo_con in cons_to_load]) linear_cons_to_load = gurobi_cons_to_load.intersection(set(self._solver_model.getConstrs())) if self._version_major >= 5: quadratic_cons_to_load = gurobi_cons_to_load.intersection(set(self._solver_model.getQConstrs())) linear_vals = self._solver_model.getAttr("Slack", linear_cons_to_load) if self._version_major >= 5: quadratic_vals = self._solver_model.getAttr("QCSlack", quadratic_cons_to_load) for gurobi_con, val in zip(linear_cons_to_load, linear_vals): pyomo_con = reverse_con_map[gurobi_con] if pyomo_con in self._range_constraints: lin_expr = self._solver_model.getRow(gurobi_con) for i in reversed(range(lin_expr.size())): v = lin_expr.getVar(i) if v in gurobi_range_con_vars: Us_ = v.X Ls_ = v.UB - v.X if Us_ > Ls_: slack[pyomo_con] = Us_ else: slack[pyomo_con] = -Ls_ break else: slack[pyomo_con] = val if self._version_major >= 5: for gurobi_con, val in zip(quadratic_cons_to_load, quadratic_vals): pyomo_con = reverse_con_map[gurobi_con] slack[pyomo_con] = val def load_duals(self, cons_to_load=None): """ Load the duals into the 'dual' suffix. The 'dual' suffix must live on the parent model. Parameters ---------- cons_to_load: list of Constraint """ self._load_duals(cons_to_load) def load_rc(self, vars_to_load): """ Load the reduced costs into the 'rc' suffix. The 'rc' suffix must live on the parent model. Parameters ---------- vars_to_load: list of Var """ self._load_rc(vars_to_load) def load_slacks(self, cons_to_load=None): """ Load the values of the slack variables into the 'slack' suffix. The 'slack' suffix must live on the parent model. Parameters ---------- cons_to_load: list of Constraint """ self._load_slacks(cons_to_load)
class _PortData(ComponentData): """ This class defines the data for a single Port Attributes ---------- vars:`dict` A dictionary mapping added names to variables """ __slots__ = ('vars', '_arcs', '_sources', '_dests', '_rules', '_splitfracs') def __init__(self, component=None): # # These lines represent in-lining of the # following constructors: # - ComponentData # - NumericValue self._component = weakref_ref(component) if (component is not None) \ else None self.vars = {} self._arcs = [] self._sources = [] self._dests = [] self._rules = {} self._splitfracs = ComponentMap() def __getstate__(self): state = super(_PortData, self).__getstate__() for i in _PortData.__slots__: state[i] = getattr(self, i) return state # Note: None of the slots on this class need to be edited, so we # don't need to implement a specialized __setstate__ method, and # can quietly rely on the super() class's implementation. def __getattr__(self, name): """Returns `self.vars[name]` if it exists""" if name in self.vars: return self.vars[name] # Since the base classes don't support getattr, we can just # throw the "normal" AttributeError raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) def arcs(self, active=None): """A list of Arcs in which this Port is a member""" return self._collect_ports(active, self._arcs) def sources(self, active=None): """A list of Arcs in which this Port is a destination""" return self._collect_ports(active, self._sources) def dests(self, active=None): """A list of Arcs in which this Port is a source""" return self._collect_ports(active, self._dests) def _collect_ports(self, active, port_list): # need to call the weakrefs if active is None: return [_a() for _a in port_list] tmp = [] for _a in port_list: a = _a() if a.active == active: tmp.append(a) return tmp def set_value(self, value): """Cannot specify the value of a port""" raise ValueError("Cannot specify the value of a port: '%s'" % self.name) def polynomial_degree(self): """Returns the maximum polynomial degree of all port members""" ans = 0 for v in self.iter_vars(): tmp = v.polynomial_degree() if tmp is None: return None ans = max(ans, tmp) return ans def is_fixed(self): """Return True if all vars/expressions in the Port are fixed""" return all(v.is_fixed() for v in self.iter_vars()) def is_potentially_variable(self): """Return True as ports may (should!) contain variables""" return True def is_binary(self): """Return True if all variables in the Port are binary""" return len(self) and all( v.is_binary() for v in self.iter_vars(expr_vars=True)) def is_integer(self): """Return True if all variables in the Port are integer""" return len(self) and all( v.is_integer() for v in self.iter_vars(expr_vars=True)) def is_continuous(self): """Return True if all variables in the Port are continuous""" return len(self) and all( v.is_continuous() for v in self.iter_vars(expr_vars=True)) def add(self, var, name=None, rule=None, **kwds): """ Add `var` to this Port, casting it to a Pyomo numeric if necessary Arguments --------- var A variable or some `NumericValue` like an expression name: `str` Name to associate with this member of the Port rule: `function` Function implementing the desired expansion procedure for this member. `Port.Equality` by default, other options include `Port.Extensive`. Customs are allowed. kwds Keyword arguments that will be passed to rule """ if var is not None: try: # indexed components are ok, but as_numeric will error on them # make sure they have this attribute var.is_indexed() except AttributeError: var = as_numeric(var) if name is None: name = var.local_name if name in self.vars and self.vars[name] is not None: # don't throw warning if replacing an implicit (None) var logger.warning("Implicitly replacing variable '%s' in Port '%s'.\n" "To avoid this warning, use Port.remove() first." % (name, self.name)) self.vars[name] = var if rule is None: rule = Port.Equality if rule is Port.Extensive: # avoid name collisions if (name.endswith("_split") or name.endswith("_equality") or name == "splitfrac"): raise ValueError( "Extensive variable '%s' on Port '%s' may not end " "with '_split' or '_equality'" % (name, self.name)) self._rules[name] = (rule, kwds) def remove(self, name): """Remove this member from the port""" if name not in self.vars: raise ValueError("Cannot remove member '%s' not in Port '%s'" % (name, self.name)) self.vars.pop(name) self._rules.pop(name) def rule_for(self, name): """Return the rule associated with the given port member""" return self._rules[name][0] def is_equality(self, name): """Return True if the rule for this port member is Port.Equality""" return self.rule_for(name) is Port.Equality def is_extensive(self, name): """Return True if the rule for this port member is Port.Extensive""" return self.rule_for(name) is Port.Extensive def fix(self): """ Fix all variables in the port at their current values. For expressions, fix every variable in the expression. """ for v in self.iter_vars(expr_vars=True, fixed=False): v.fix() def unfix(self): """ Unfix all variables in the port. For expressions, unfix every variable in the expression. """ for v in self.iter_vars(expr_vars=True, fixed=True): v.unfix() free = unfix def iter_vars(self, expr_vars=False, fixed=None, names=False): """ Iterate through every member of the port, going through the indices of indexed members. Arguments --------- expr_vars: `bool` If True, call `identify_variables` on expression type members fixed: `bool` Only include variables/expressions with this type of fixed names: `bool` If True, yield (name, var/expr) pairs """ for name, mem in iteritems(self.vars): if not mem.is_indexed(): itr = (mem,) else: itr = itervalues(mem) for v in itr: if fixed is not None and v.is_fixed() != fixed: continue if expr_vars and v.is_expression_type(): for var in identify_variables(v): if fixed is not None and var.is_fixed() != fixed: continue if names: yield name, var else: yield var else: if names: yield name, v else: yield v def set_split_fraction(self, arc, val, fix=True): """ Set the split fraction value to be used for an arc during arc expansion when using `Port.Extensive`. """ if arc not in self.dests(): raise ValueError("Port '%s' is not a source of Arc '%s', cannot " "set split fraction" % (self.name, arc.name)) self._splitfracs[arc] = (val, fix) def get_split_fraction(self, arc): """ Returns a tuple (val, fix) for the split fraction of this arc that was set via `set_split_fraction` if it exists, and otherwise None. """ res = self._splitfracs.get(arc, None) if res is None: return None else: return res
def test_keys(self): cmap = ComponentMap(self._components) self.assertEqual(sorted(cmap.keys(), key=id), sorted(list(c for c,val in self._components), key=id))
def fbbt_block(m, tol=1e-4, deactivate_satisfied_constraints=False, integer_tol=1e-5, infeasible_tol=1e-8): """ Feasibility based bounds tightening (FBBT) for a block or model. This loops through all of the constraints in the block and performs FBBT on each constraint (see the docstring for fbbt_con()). Through this processes, any variables whose bounds improve by more than tol are collected, and FBBT is performed again on all constraints involving those variables. This process is continued until no variable bounds are improved by more than tol. Parameters ---------- m: pyomo.core.base.block.Block or pyomo.core.base.PyomoModel.ConcreteModel tol: float deactivate_satisfied_constraints: bool If deactivate_satisfied_constraints is True and a constraint is always satisfied, then the constranit will be deactivated integer_tol: float If the lower bound computed on a binary variable is less than or equal to integer_tol, then the lower bound is left at 0. Otherwise, the lower bound is increased to 1. If the upper bound computed on a binary variable is greater than or equal to 1-integer_tol, then the upper bound is left at 1. Otherwise the upper bound is decreased to 0. infeasible_tol: float If the bounds computed on the body of a constraint violate the bounds of the constraint by more than infeasible_tol, then the constraint is considered infeasible and an exception is raised. Returns ------- new_var_bounds: ComponentMap A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed from FBBT. """ new_var_bounds = ComponentMap() var_to_con_map = ComponentMap() var_lbs = ComponentMap() var_ubs = ComponentMap() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=True, sort=True): for v in identify_variables(c.body): if v not in var_to_con_map: var_to_con_map[v] = list() if v.lb is None: var_lbs[v] = -math.inf else: var_lbs[v] = value(v.lb) if v.ub is None: var_ubs[v] = math.inf else: var_ubs[v] = value(v.ub) var_to_con_map[v].append(c) for _v in m.component_data_objects(ctype=Var, active=True, descend_into=True, sort=True): if _v.is_fixed(): _v.setlb(_v.value) _v.setub(_v.value) new_var_bounds[_v] = (_v.value, _v.value) improved_vars = ComponentSet() for c in m.component_data_objects(ctype=Constraint, active=True, descend_into=True, sort=True): _new_var_bounds = fbbt_con(c, deactivate_satisfied_constraints=deactivate_satisfied_constraints, integer_tol=integer_tol, infeasible_tol=infeasible_tol) new_var_bounds.update(_new_var_bounds) for v, bnds in _new_var_bounds.items(): vlb, vub = bnds if vlb is not None: if vlb > var_lbs[v] + tol: improved_vars.add(v) var_lbs[v] = vlb if vub is not None: if vub < var_ubs[v] - tol: improved_vars.add(v) var_ubs[v] = vub while len(improved_vars) > 0: v = improved_vars.pop() for c in var_to_con_map[v]: _new_var_bounds = fbbt_con(c, deactivate_satisfied_constraints=deactivate_satisfied_constraints, integer_tol=integer_tol, infeasible_tol=infeasible_tol) new_var_bounds.update(_new_var_bounds) for _v, bnds in _new_var_bounds.items(): _vlb, _vub = bnds if _vlb is not None: if _vlb > var_lbs[_v] + tol: improved_vars.add(_v) var_lbs[_v] = _vlb if _vub is not None: if _vub < var_ubs[_v] - tol: improved_vars.add(_v) var_ubs[_v] = _vub return new_var_bounds
def fbbt_con(con, deactivate_satisfied_constraints=False, integer_tol=1e-5, infeasible_tol=1e-8): """ Feasibility based bounds tightening for a constraint. This function attempts to improve the bounds of each variable in the constraint based on the bounds of the constraint and the bounds of the other variables in the constraint. For example: >>> import pyomo.environ as pe >>> from pyomo.contrib.fbbt.fbbt import fbbt >>> m = pe.ConcreteModel() >>> m.x = pe.Var(bounds=(-1,1)) >>> m.y = pe.Var(bounds=(-2,2)) >>> m.z = pe.Var() >>> m.c = pe.Constraint(expr=m.x*m.y + m.z == 1) >>> fbbt(m.c) >>> print(m.z.lb, m.z.ub) -1.0 3.0 Parameters ---------- con: pyomo.core.base.constraint.Constraint constraint on which to perform fbbt deactivate_satisfied_constraints: bool If deactivate_satisfied_constraints is True and the constraint is always satisfied, then the constranit will be deactivated integer_tol: float If the lower bound computed on a binary variable is less than or equal to integer_tol, then the lower bound is left at 0. Otherwise, the lower bound is increased to 1. If the upper bound computed on a binary variable is greater than or equal to 1-integer_tol, then the upper bound is left at 1. Otherwise the upper bound is decreased to 0. infeasible_tol: float If the bounds computed on the body of a constraint violate the bounds of the constraint by more than infeasible_tol, then the constraint is considered infeasible and an exception is raised. Returns ------- new_var_bounds: ComponentMap A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed from FBBT. """ if not con.active: return ComponentMap() bnds_dict = ComponentMap() # a dictionary to store the bounds of every node in the tree # a walker to propagate bounds from the variables to the root visitorA = _FBBTVisitorLeafToRoot(bnds_dict) visitorA.dfs_postorder_stack(con.body) # Now we need to replace the bounds in bnds_dict for the root # node with the bounds on the constraint (if those bounds are # better). _lb = value(con.lower) _ub = value(con.upper) if _lb is None: _lb = -math.inf if _ub is None: _ub = math.inf lb, ub = bnds_dict[con.body] # check if the constraint is infeasible if lb > _ub + infeasible_tol or ub < _lb - infeasible_tol: raise InfeasibleConstraintException('Detected an infeasible constraint during FBBT: {0}'.format(str(con))) # check if the constraint is always satisfied if deactivate_satisfied_constraints: if lb >= _lb and ub <= _ub: con.deactivate() if _lb > lb: lb = _lb if _ub < ub: ub = _ub bnds_dict[con.body] = (lb, ub) # Now, propagate bounds back from the root to the variables visitorB = _FBBTVisitorRootToLeaf(bnds_dict, integer_tol=integer_tol) visitorB.dfs_postorder_stack(con.body) new_var_bounds = ComponentMap() for _node, _bnds in bnds_dict.items(): if _node.__class__ in nonpyomo_leaf_types: continue if _node.is_variable_type(): lb, ub = bnds_dict[_node] if lb == -math.inf: lb = None if ub == math.inf: ub = None new_var_bounds[_node] = (lb, ub) return new_var_bounds
def test_str(self): cmap = ComponentMap() self.assertEqual(str(cmap), "ComponentMap({})") cmap.update(self._components) str(cmap)
def test_values(self): cmap = ComponentMap(self._components) self.assertEqual(sorted(cmap.values()), sorted(list(val for c,val in self._components)))