Ejemplo n.º 1
0
 def __rmatmul__(self, other):
     """
     :param other: a constant Expression or nd-array which left-multiplies "self".
     :return: other @ self
     """
     if other.ndim > 2 or self.ndim > 2:  # pragma: no cover
         msg = '\n \t Matmul implementation uses "dot", '
         msg += 'which behaves differently for higher dimension arrays.\n'
         raise RuntimeError(msg)
     if isinstance(other, sp.spmatrix):
         other = other.toarray()
     (A, x, B) = self.factor()
     if isinstance(other, Expression):
         if not other.is_constant():  # pragma: no cover
             raise RuntimeError(
                 'Can only multiply by constant Expressions.')
         else:
             _, _, other = other.factor()
     if other.ndim == 2:
         other_times_A = np.tensordot(other, A, axes=1)
     else:
         other_times_A = np.tensordot(other.reshape((1, -1)), A, axes=1)
         other_times_A = np.squeeze(other_times_A, axis=0)
     other_times_A_x = Expression._disjoint_dot(other_times_A, x)
     res = other_times_A_x
     other_times_B = np.tensordot(other, B, axes=1)
     for tup in array_index_iterator(other_times_A_x.shape):
         res[tup].offset = other_times_B[tup]
     return res
Ejemplo n.º 2
0
    def factor(self):
        """
        Returns a tuple ``(A, x, B)``.

        ``A`` is a tensor of one order higher than the current Expression object, i.e.
        ``A.ndim == self.ndim + 1``. The dimensions of ``A`` and ``self`` agree up until
        ``self.ndim``, i.e. ``A.shape[:-1] == self.shape``.

        ``x`` is a list of ScalarAtom objects, with ``len(x) == A.shape[-1]``.

        ``B`` is a numpy array of the same shape as ``self``.

        The purpose of this function is to enable faster matrix multiplications of Expression
        objects. The idea is that if you tensor-contract ``A`` along its final dimension according
        to ``x``, and then add ``B``, you recover this Expression.
        """
        x = list(set(a for se in self.flat for a in se.atoms_to_coeffs))
        x.sort(key=lambda a: a.id)
        # Sorting by ScalarAtom id makes this method deterministic
        # when all ScalarAtoms in this Expression are of the same type.
        # That's useful for, e.g. affine Expressions, which
        # we need to test for symbolic equivalence.
        atoms_to_pos = {a: i for (i, a) in enumerate(x)}
        A = np.zeros(self.shape + (len(x), ))
        B = np.zeros(self.shape)
        for tup in array_index_iterator(self.shape):
            se = self[tup]
            for a, c in se.atoms_to_coeffs.items():
                A[tup + (atoms_to_pos[a], )] = c
            B[tup] = se.offset
        return A, x, B
Ejemplo n.º 3
0
 def value(self, value):
     if isinstance(value, __REAL_TYPES__):
         value = np.array(value)
     if value.shape != self.shape:  # pragma: no cover
         raise RuntimeError('Dimension mismatch.')
     for tup in array_index_iterator(self.shape):
         sv = list(self[tup].atoms_to_coeffs)[0]
         sv._value = value[tup]
     pass
Ejemplo n.º 4
0
 def value(self):
     """
     An ndarray containing numeric entries, of shape equal to ``self.shape``.
     This is the result of propagating the value of ScalarVariable objects
     through the symbolic operations tracked by this Expression.
     """
     val = np.zeros(shape=self.shape)
     for tup in array_index_iterator(self.shape):
         val[tup] = self[tup].value
     return val
Ejemplo n.º 5
0
 def is_concave(self):
     """
     Return an ndarray of booleans. For a fixed component index, the value
     of the returned array indicates if that component of the current Expression
     is a concave function of Variables within its scope.
     """
     res = np.empty(shape=self.shape, dtype=bool)
     for tup in array_index_iterator(self.shape):
         res[tup] = self[tup].is_concave()
     return res
Ejemplo n.º 6
0
 def _disjoint_dot(array, list_of_atoms):
     # This is still MUCH SLOWER than adding numbers together.
     if len(list_of_atoms) != array.shape[-1]:  # pragma: no cover
         raise RuntimeError('Incompatible dimensions to disjoint_dot.')
     expr = np.empty(shape=array.shape[:-1], dtype=object)
     for tup in array_index_iterator(expr.shape):
         dict_items = []
         for i, a in enumerate(list_of_atoms):
             dict_items.append((a, array[tup + (i, )]))
         d = dict(dict_items)
         expr[tup] = ScalarExpression(d, 0, verify=False)
     return expr.view(Expression)
Ejemplo n.º 7
0
 def scalar_variable_ids(self):
     """
     Each component of this Variable object (i.e. each "scalar variable") contains
     an index which uniquely identifies it in all models where this Variable appears.
     Return the list of these indices.
     """
     if self.is_proper():
         return self._scalar_variable_ids
     else:
         return [
             self[tup].scalar_variables()[0].id
             for tup in array_index_iterator(self.shape)
         ]
Ejemplo n.º 8
0
 def __unstructured_populate__(obj):
     if obj.shape == ():
         v = ScalarVariable(parent=obj, index=tuple())
         np.ndarray.__setitem__(obj, tuple(),
                                ScalarExpression({v: 1}, 0, verify=False))
         obj._scalar_variable_ids.append(v.id)
     else:
         for tup in array_index_iterator(obj.shape):
             v = ScalarVariable(parent=obj, index=tup)
             obj._scalar_variable_ids.append(v.id)
             np.ndarray.__setitem__(
                 obj, tup, ScalarExpression({v: 1}, 0, verify=False))
     pass
Ejemplo n.º 9
0
def abs(x, eval_only=False):
    """
    Return a coniclifts Expression representing |x| componentwise.

    :param x: a coniclifts Expression.

    :param eval_only: bool. True if the returned Expression will not be used
    in an optimization problem.
    """
    if not isinstance(x, Expression):
        x = Expression(x)
    expr = np.empty(shape=x.shape, dtype=object)
    for tup in array_index_iterator(expr.shape):
        expr[tup] = ScalarExpression({Abs(x[tup], eval_only): 1}, 0, verify=False)
    return expr.view(Expression)
Ejemplo n.º 10
0
def relent(x, y, elementwise=False):
    if not isinstance(x, Expression):
        x = Expression(x)
    if not isinstance(y, Expression):
        y = Expression(y)
    if x.size != y.size:
        raise RuntimeError('Incompatible arguments to relent.')
    if elementwise:
        expr = np.empty(shape=x.shape, dtype=object)
        for tup in array_index_iterator(expr.shape):
            expr[tup] = ScalarExpression({RelEnt(x[tup], y[tup]): 1}, 0)
        return expr.view(Expression)
    else:
        x = x.ravel()
        y = y.ravel()
        d = dict((RelEnt(x[i], y[i]), 1) for i in range(x.size))
        return ScalarExpression(d, 0).as_expr()
Ejemplo n.º 11
0
 def __unstructured_populate__(obj):
     if obj.shape == ():
         v = ScalarVariable(parent=obj, index=tuple())
         d = defaultdict(int)
         d[v] = 1
         se = ScalarExpression(d, 0, verify=False, copy=False)
         np.ndarray.__setitem__(obj, tuple(), se)
         obj._scalar_variable_ids.append(v.id)
     else:
         for tup in array_index_iterator(obj.shape):
             v = ScalarVariable(parent=obj, index=tup)
             obj._scalar_variable_ids.append(v.id)
             d = defaultdict(int)
             d[v] = 1
             se = ScalarExpression(d, 0, verify=False, copy=False)
             np.ndarray.__setitem__(obj, tup, se)
     pass
Ejemplo n.º 12
0
 def __symmetric_populate__(obj):
     if obj.ndim != 2 or obj.shape[0] != obj.shape[1]:  # pragma: no cover
         raise RuntimeError('Symmetric variables must be 2d, and square.')
     temp_id_array = np.zeros(shape=obj.shape, dtype=int)
     for i in range(obj.shape[0]):
         v = ScalarVariable(parent=obj, index=(i, i))
         np.ndarray.__setitem__(obj, (i, i),
                                ScalarExpression({v: 1}, 0, verify=False))
         temp_id_array[i, i] = v.id
         for j in range(i + 1, obj.shape[1]):
             v = ScalarVariable(parent=obj, index=(i, j))
             np.ndarray.__setitem__(
                 obj, (i, j), ScalarExpression({v: 1}, 0, verify=False))
             np.ndarray.__setitem__(
                 obj, (j, i), ScalarExpression({v: 1}, 0, verify=False))
             temp_id_array[i, j] = v.id
             temp_id_array[j, i] = v.id
     for tup in array_index_iterator(obj.shape):
         obj._scalar_variable_ids.append(temp_id_array[tup])
     pass
Ejemplo n.º 13
0
def make_variable_map(variables, var_indices):
    """
    :param variables: a list of Variable objects with is_proper=True. These variables
    appear in some vectorized conic system represented by {x : A @ x + b \in K}.

    :param var_indices: a list of 1darrays. The i^th 1darray in this list contains
    the locations of the i^th Variable's entries with respect to the vectorized
    conic system {x : A @ x + b \in K}.

    :return: a dictionary mapping Variable names to ndarrays of indices. These ndarrays
    of indices allow the user to access a Variable's value from the vectorized conic system
    in a very convenient way.

    For example, if "my_var" is the name of a Variable with shape (10, 2, 1, 4), and "x" is
    feasible for the conic system {x : A @ x + b \in K}, then a feasible value for "my_var"
    is the 10-by-2-by-1-by-4 array given by x[variable_map['my_var']].

    NOTES:

        This function assumes that every entry of a Variable object is an unadorned ScalarVariable.
        As a result, skew-symmetric Variables or Variables with sparsity patterns are not supported
        by this very important function. Symmetric matrices are supported by this function.

        If some but not-all components of a Variable "my_var" participate in a vectorized conic
        system {x : A @ x + b \in K }, then var_indices is presumed to map these ScalarVariables
        to the number -1. The number -1 will then appear as a value in variable_map. When values
        are set for ScalarVariable objects, the system {x : A @ x + b \in K} is extended to
        { [x,0] : A @ x + b \in K}. In this way, ScalarVariables which do not participate in a
        given optimization problem are assigned the value 0.

    """
    variable_map = dict()
    for i, v in enumerate(variables):
        temp = np.zeros(v.shape)
        j = 0
        for tup in util.array_index_iterator(v.shape):
            temp[tup] = var_indices[i][j]
            j += 1
        variable_map[v.name] = np.array(temp, dtype=int)
    return variable_map
Ejemplo n.º 14
0
 def __new__(cls, obj):
     attempt = np.array(obj, dtype=object, copy=False, subok=True)
     for tup in array_index_iterator(attempt.shape):
         # noinspection PyTypeChecker,PyCallByClass
         Expression.__setitem__(attempt, tup, attempt[tup])
     return attempt.view(Expression)