Source code for flopt.solution

import math
import itertools

import numpy as np

from flopt.variable import VarElement
from flopt.container import FloptNdarray
from flopt.constants import VariableType


to_value_ufunc = np.frompyfunc(lambda x: x.value(), 1, 1)


[docs]class Solution(FloptNdarray): """ Solution Class Parameters ---------- variables : list of VarElement family variables which has no duplicate Attributes ---------- name : str name of solution type : str _variables : list of varElement variable array _var_dict : dict key is name of variable, key is variable Examples ---------- Create a Solution which has Integer, Continuous and Binary Variables >>> a = Variable(name="a", lowBound=0, upBound=1, cat="Integer") >>> b = Variable(name="b", lowBound=1, upBound=2, cat="Continuous") >>> c = Variable(name="c", cat="Binary") >>> sol = Solution([a, b, c]) Four arithmetic operations are supported between Solutions or between a Solution and a constant. >>> sol1 = Solution([a, b]) >>> sol2 = Solution([a, c]) >>> sol_plus = sol1 + sol2 # (Solution + Solution or Solution + list) >>> sol_minus = sol1 - sol2 # (Solution - Solution or Solution - list) >>> sol_product = sol1 * 2 # (Solution * constant) >>> sol_divide = sol1 / 2 # (Solution / constant) """ def __new__(cls, variables, sort=True, *args, **kwargs): if isinstance(variables, (tuple, set)): variables = list(variables) if sort: variables = sorted(variables, key=lambda var: var.name) obj = np.asarray(variables, dtype=object).view(cls) obj._variables = variables obj._var_dict = None return obj def __array_finalize__(self, obj): self._variables = getattr(obj, "_variables", None) self._var_dict = getattr(obj, "_var_dict", None)
[docs] def toDict(self): """ Returns ------- dict: key is name of variable, value is VarElement family or Expression or Const """ if self._var_dict is None: self._var_dict = {var.name: var for var in self._variables} return self._var_dict
[docs] def value(self, solution=None): """ Parameters ---------- solution: None or Solution Returns ------- list values of the variables in the Solution """ if solution is not None: return np.array([solution.toDict()[var.name] for var in solution]) return to_value_ufunc(self._variables)
[docs] def setValue(self, name, value): """ Parameters ---------- name: str value: int or float """ self.toDict()[name].setValue(value)
[docs] def setValuesFromArray(self, array): """ Parameters ---------- array: iterator array of variable values """ assert len(self._variables) == len(array) for var, value in zip(self._variables, array): var.setValue(value)
[docs] def getVariables(self): """ Returns ------- list Variable instances which belong to the Solution """ return FloptNdarray(self._variables)
[docs] def clone(self): """ Returns ------- Solution Copy of the Solution (call by value) """ return +self
[docs] def copy(self, other): """ Copy the values of a Solution to itself (call by value) """ for var, ovar in zip(self._variables, other._variables): var.setValue(ovar.value())
[docs] def setRandom(self): """ Set the solution values uniformly random """ for var in self._variables: var.setRandom() return self
[docs] def feasible(self): """ Returns ------- bool Whether the solution is feasible or not """ return all(var.feasible() for var in self._variables)
[docs] def clip(self): """ Guarantee feasibility of the solution """ for var in self._variables: var.clip()
[docs] def squaredNorm(self): """ Returns ------- float Squared 2-norm of the solution as a vector in Euclid space """ value = self.value() return (value * value).sum()
[docs] def norm(self): """ Returns ------- float 2-norm of the solution as a vector in Euclid space """ return math.sqrt(self.squaredNorm())
[docs] def dot(self, other): """ Returns ------- float Inner product between the Solution and another Solution """ inner = sum( var1.value() * var2.value() for var1, var2 in zip(self._variables, other._variables) ) return inner
def __pos__(self): variables = [var.clone() for var in self._variables] return Solution(variables) def __neg__(self): variables = [var.clone() for var in self._variables] for var in variables: var.setValue(-var.value()) return Solution(variables) def __add__(self, other): """ Parameters ---------- other: Solution or np.ndarray or list or float Returns ------- solution: new instance of self + other """ solution = self.clone() solution += other return solution def __iadd__(self, other): """ Parameters ---------- other: Solution or np.ndarray or list or float Returns ------- solution: self added by other """ if isinstance(other, Solution): for var1, var2 in zip(self._variables, other._variables): var1.setValue(var1.value() + var2.value()) elif isinstance(other, (list, np.ndarray)): for var, v in zip(self._variables, other): var.setValue(var.value() + v) elif isinstance(other, (int, float)): for var in self._variables: var.setValue(var.value() + other) else: raise NotImplementedError return self def __radd__(self, other): return self + other def __sub__(self, other): """ Parameters ---------- other: Solution or np.ndarray or list or float Returns ------- solution: new instance of self - other """ solution = self.clone() solution -= other return solution def __isub__(self, other): """ Parameters ---------- other: Solution or np.ndarray or list or float Returns ------- solution: self substracted by other """ if isinstance(other, Solution): for var1, var2 in zip(self._variables, other._variables): var1.setValue(var1.value() - var2.value()) elif isinstance(other, (list, np.ndarray)): for var, v in zip(self._variables, other): var.setValue(var.value() - v) elif isinstance(other, (int, float)): for var in self._variables: var.setValue(var.value() - other) else: raise NotImplementedError return self def __rsub__(self, other): return -self + other def __mul__(self, other): """ Parameters ---------- other: Solution or np.ndarray or list or float Returns ------- solution: new instance of self * other """ solution = self.clone() solution *= other return solution def __imul__(self, other): """ Parameters ---------- other: Solution or np.ndarray or list or float Returns ------- solution: self multiplied by other """ if isinstance(other, Solution): for var1, var2 in zip(self._variables, other._variables): var1.setValue(var1.value() * var2.value()) elif isinstance(other, (list, np.ndarray)): for var, v in zip(self._variables, other): var.setValue(var.value() * v) elif isinstance(other, (int, float)): for var in self._variables: var.setValue(var.value() * other) else: raise NotImplementedError return self def __rmul__(self, other): return self * other def __truediv__(self, other): """ Parameters ---------- other: Solution or np.ndarray or list or float Returns ------- solution: new instance of self / other """ solution = self.clone() solution /= other return solution def __itruediv__(self, other): """ Parameters ---------- other: Solution or np.ndarray or list or float Returns ------- solution: self divided by other """ if isinstance(other, list): other = [1.0 / v for v in other] if isinstance(other, (int, float, np.ndarray)): other = 1.0 / other else: NotImplementedError return self.__imul__(other) def __abs__(self): solution = self.clone() for var in solution._variables: var.setValue(abs(var.value())) return solution def __hash__(self): return hash((id(self), tuple(self._variables))) def __len__(self): return len(self._variables) def __iter__(self): return iter(self._variables) # def __getitem__(self, k): # return self._variables[k] def __str__(self): return self.__repr__() def __repr__(self): return f"Solution([{', '.join([var.name for var in self._variables])}])"