class ISOS(ICategorizedObject): """ The interface for Special Ordered Sets. """ __slots__ = () # # Implementations can choose to define these # properties as using __slots__, __dict__, or # by overriding the @property method # variables = _abstract_readwrite_property(doc="The sos variables") weights = _abstract_readwrite_property(doc="The sos variables") level = _abstract_readwrite_property(doc="The sos level (e.g., 1,2,...)") # # Interface # def items(self): """Iterator over the sos variables and weights as tuples""" return zip(self.variables, self.weights) def __contains__(self, v): """Check if the sos contains the variable v""" for x in self.variables: if id(x) == id(v): return True def __len__(self): """The number of members in the set""" return len(self.variables)
class IExpression(ICategorizedObject, IIdentityExpression): """ The interface for mutable expressions. """ __slots__ = () # # Implementations can choose to define these # properties as using __slots__, __dict__, or # by overriding the @property method # expr = _abstract_readwrite_property(doc="The stored expression") # # Override some of the NumericValue methods implemented # by the base class # def is_constant(self): """A boolean indicating whether this expression is constant.""" return False def is_potentially_variable(self): """A boolean indicating whether this expression can reference variables.""" return True def clone(self): """Return a clone of this expression (no-op).""" return self
class IParameter(ICategorizedObject, NumericValue): """ The interface for mutable parameters. """ __slots__ = () # # Implementations can choose to define these # properties as using __slots__, __dict__, or # by overriding the @property method # value = _abstract_readwrite_property(doc="The value of the parameter") # # Implement the NumericValue abstract methods # def __call__(self, exception=True): """Compute the value of this parameter.""" return self.value def is_constant(self): """A boolean indicating that this parameter is constant.""" return False def is_parameter_type(self): """A boolean indicating that this is a parameter object.""" # # The semantics of ParamData and parameter are different. # By default, ParamData are immutable. Hence, we treat the # parameter objects as non-parameter data ... for now. # return False def is_variable_type(self): """A boolean indicating that this is a variable object.""" return False def is_fixed(self): """A boolean indicating that this parameter is fixed.""" return True def is_potentially_variable(self): """Returns :const:`False` because this object can never reference variables.""" return False def polynomial_degree(self): """Always return zero because we always validate that the stored expression can never reference variables.""" return 0
class IObjective(IExpression): """ The interface for optimization objectives. """ __slots__ = () # # Implementations can choose to define these # properties as using __slots__, __dict__, or # by overriding the @property method # sense = _abstract_readwrite_property( doc=("The optimization direction for the " "objective (minimize or maximize)")) # # Interface # def is_minimizing(self): return self.sense == minimize
class IVariable(ICategorizedObject, NumericValue): """The interface for decision variables""" __slots__ = () _valid_domain_types = (RealSet, IntegerSet) # # Implementations can choose to define these # properties as using __slots__, __dict__, or # by overriding the @property method # domain_type = _abstract_readwrite_property( doc=("The domain type of the variable " "(:class:`RealSet` or :class:`IntegerSet`)")) lb = _abstract_readwrite_property(doc="The lower bound of the variable") ub = _abstract_readwrite_property(doc="The upper bound of the variable") value = _abstract_readwrite_property(doc="The value of the variable") fixed = _abstract_readwrite_property( doc="The fixed status of the variable") stale = _abstract_readwrite_property( doc="The stale status of the variable") # # Interface # @property def bounds(self): """Get/Set the bounds as a tuple (lb, ub).""" return (self.lb, self.ub) @bounds.setter def bounds(self, bounds_tuple): self.lb, self.ub = bounds_tuple def fix(self, value=NoArgumentGiven): """ Fix the variable. Sets the fixed indicator to :const:`True`. An optional value argument will update the variable's value before fixing. """ if value is not NoArgumentGiven: self.value = value self.fixed = True def unfix(self): """Free the variable. Sets the fixed indicator to :const:`False`.""" self.fixed = False free = unfix def has_lb(self): """Returns :const:`False` when the lower bound is :const:`None` or negative infinity""" lb = self.lb return (lb is not None) and \ (value(lb) != _neg_inf) def has_ub(self): """Returns :const:`False` when the upper bound is :const:`None` or positive infinity""" ub = self.ub return (ub is not None) and \ (value(ub) != _pos_inf) @property def lslack(self): """Lower slack (value - lb). Returns :const:`None` if the variable value is :const:`None`.""" val = self.value if val is None: return None lb = self.lb if lb is None: lb = _neg_inf else: lb = value(lb) return val - lb @property def uslack(self): """Upper slack (ub - value). Returns :const:`None` if the variable value is :const:`None`.""" val = self.value if val is None: return None ub = self.ub if ub is None: ub = _pos_inf else: ub = value(ub) return ub - val @property def slack(self): """min(lslack, uslack). Returns :const:`None` if the variable value is :const:`None`.""" # this method is written so that constraint # types that build the body expression on the # fly do not have to here val = self.value if val is None: return None return min(self.lslack, self.uslack) # # Convenience methods mainly used by the solver # interfaces # def is_continuous(self): """Returns :const:`True` when the domain type is :class:`RealSet`.""" return self.domain_type.get_interval()[2] == 0 # this could be expanded to include semi-continuous # where as is_integer would not def is_discrete(self): """Returns :const:`True` when the domain type is :class:`IntegerSet`.""" return self.domain_type.get_interval()[2] not in (0, None) def is_integer(self): """Returns :const:`True` when the domain type is :class:`IntegerSet`.""" return self.domain_type.get_interval()[2] == 1 def is_binary(self): """Returns :const:`True` when the domain type is :class:`IntegerSet` and the bounds are within [0,1].""" return self.domain_type.get_interval()[2] == 1 \ and (value(self.lb), value(self.ub)) in {(0,1), (0,0), (1,1)} # TODO? # def is_semicontinuous(self): # """Returns :const:`True` when the domain class is # SemiContinuous.""" # return issubclass(self.domain_type, SemiRealSet) # def is_semiinteger(self): # """Returns :const:`True` when the domain class is # SemiInteger.""" # return issubclass(self.domain_type, SemiIntegerSet) # # Implement the NumericValue abstract methods # def is_fixed(self): """Returns :const:`True` if this variable is fixed, otherwise returns :const:`False`.""" return self.fixed def is_constant(self): """Returns :const:`False` because this is not a constant in an expression.""" return False def is_parameter_type(self): """Returns :const:`False` because this is not a parameter object.""" return False def is_variable_type(self): """Returns :const:`True` because this is a variable object.""" return True def is_potentially_variable(self): """Returns :const:`True` because this is a variable.""" return True def polynomial_degree(self): """Return the polynomial degree of this expression""" # If the variable is fixed, it represents a constant; # otherwise, it has degree 1. if self.fixed: return 0 return 1 def __call__(self, exception=True): """Return the value of this variable.""" if exception and (self.value is None): raise ValueError("value is None") return self.value