def make_sig_lagrangian(f, gts, eqs, p, q): """ Given a problem .. math:: \\begin{align*} \min\{ f(x) :~& g(x) \geq 0 \\text{ for } g \\in \\mathtt{gts}, \\\\ & g(x) = 0 \\text{ for } g \\in \\mathtt{eqs}, \\\\ & \\text{and } x \\in X \} \\end{align*} construct the q-fold constraints ``q-gts`` and ``q-eqs``, by taking all products of ``<= q`` elements from ``gts`` and ``eqs`` respectively. Then form the Lagrangian .. math:: L = f - \\gamma - \sum_{g \, \\in \, \\mathtt{q-gts}} s_g \cdot g - \sum_{g \, \\in \, \\mathtt{q-eqs}} z_g \cdot g where :math:`\\gamma` is a coniclifts Variable of dimension 1, and the coefficients on Signomials :math:`s_g` and :math:`z_g` are coniclifts Variables of a dimension determined by ``p``. Parameters ---------- f : Signomial The objective in a desired minimization problem. gts : list of Signomials For every ``g in gts``, there is a desired constraint that variables ``x`` satisfy ``g(x) >= 0``. eqs : list of Signomials For every ``g in eqs``, there is a desired constraint that variables ``x`` satisfy ``g(x) == 0``. p : int Controls the complexity of ``s_g`` and ``z_g``. q : int The number of folds of constraints ``gts`` and ``eqs``. Returns ------- L : Signomial ``L.c`` is an affine expression of coniclifts Variables. ineq_dual_sigs : a list of pairs of Signomial objects. If the pair ``(s_g, g)`` is in this list, then ``s_g`` is a generalized Lagrange multiplier to the constraint ``g(x) >= 0``. eq_dual_sigs : a list of pairs of Signomial objects. If the pair ``(z_g, g)`` is in this list, then ``z_g`` is a generalized Lagrange multiplier to the constraint ``g(x) == 0``. gamma : coniclifts.Variable. In primal-form SAGE relaxations, we want to maximize ``gamma``. In dual form SAGE relaxations, ``gamma`` induces an equality constraint. Notes ----- The Lagrange multipliers ``s_g`` and ``z_g`` share a common matrix of exponent vectors, which we call ``alpha_hat``. When ``p = 0``, ``alpha_hat`` consists of a single row, of all zeros. In this case, ``s_g`` and ``z_g`` are constant Signomials, and the coefficient vectors ``s_g.c`` and ``z_g.c`` are effectively scalars. When ``p > 0``, the rows of ``alpha_hat`` are set to all ``p``-wise sums of exponent vectors appearing in either ``f``, or some ``g in gts``, or some ``g in eqs``. """ folded_gt = con_gen.up_to_q_fold_cons(gts, q) gamma = cl.Variable(name='gamma') L = f - gamma alpha_E_p = hierarchy_e_k([L] + list(gts) + list(eqs), k=p) ineq_dual_sigs = [] summands = [L] for g in folded_gt: s_g_coeff = cl.Variable(name='s_' + str(g), shape=(alpha_E_p.shape[0], )) s_g = Signomial(alpha_E_p, s_g_coeff) summands.append(-g * s_g) ineq_dual_sigs.append((s_g, g)) eq_dual_sigs = [] folded_eq = con_gen.up_to_q_fold_cons(eqs, q) for g in folded_eq: z_g_coeff = cl.Variable(name='z_' + str(g), shape=(alpha_E_p.shape[0], )) z_g = Signomial(alpha_E_p, z_g_coeff) summands.append(-g * z_g) eq_dual_sigs.append((z_g, g)) L = Signomial.sum(summands) return L, ineq_dual_sigs, eq_dual_sigs, gamma
def __call__(self, x): """ Parameters ---------- x : ndarray This vector can contain real numeric types and Polynomial objects. Returns ------- val : float or Polynomial If ``x`` is purely numeric, then ``val`` is a float. If ``x`` contains Polynomial objects, then ``val`` is a Polynomial object. Examples -------- Evaluating a Polynomial at a numeric point. :: p = Polynomial.from_dict({(1,2,3): -1}) x = np.array([3,2,1]) print(p(x)) # (3 ** 1) * (2 ** 2) * (1 ** 3) * (-1) == -12 Evaluating a Polynomial on another polynomial. :: p = Polynomial.from_dict({(2,): 1}) # represents lambda x: x ** 2 z = Polynomial.from_dict({(1,): 2, (0,): -1}) # represents lambda x: 2*x - 1 w = p(z) # represents lambda x: (2*x - 1) ** 2 print(w(0.5)) # equals zero print(w(1)) # equals one print(w(0)) # equals one Notes ----- If ``x`` contains Polynomial entries, then those Polynomials must all be over the same number of variables. However, those Polynomials need not have the same number of variables as the current polynomial. """ if not hasattr(x, 'shape'): x = np.array([x]) if x.ndim == 0: x = x.ravel() if not x.shape[0] == self.n: msg = 'Domain is R^' + str(self._n) + 'but x is in R^' + str( x.shape[0]) raise ValueError(msg) xi_func = None if x.dtype == 'object': func_xis = [xi for xi in x.flat if isinstance(xi, Signomial)] ns = np.array([xi.n for xi in func_xis]) if ns.size == 0: x = x.astype(np.float) elif np.any(ns != ns[0]): msg = 'The input vector cannot contain functions over different variables.' raise ValueError(msg) else: xi_func = func_xis[0] if x.ndim == 1: x = x.ravel() temp1 = np.power(x, self.alpha) temp2 = np.prod(temp1, axis=1) temp3 = self.c * temp2 if temp3.dtype != 'object': val = np.sum(temp3) return val else: if isinstance(xi_func, Polynomial): temp3 = [xi_func.upcast_to_polynomial(ti) for ti in temp3] else: temp3 = [xi_func.upcast_to_signomial(ti) for ti in temp3] val = Signomial.sum(temp3) return val elif x.ndim == 2: vals = [self.__call__(xi) for xi in x.T] val = np.array(vals) return val else: raise ValueError('Can only evaluate on x with dimension <= 2.')