def poly_constrained_dual(f, gts, eqs, p=0, q=1, ell=0, X=None, slacks=False): """ Construct the dual SAGE-(p, q, ell) relaxation for the polynomial optimization problem inf{ f(x) : g(x) >= 0 for g in gts, g(x) == 0 for g in eqs, and x in X } where :math:`X = R^{\\texttt{f.n}}` by default. """ lagrangian, ineq_lag_mults, eq_lag_mults, _ = make_poly_lagrangian(f, gts, eqs, p=p, q=q) metadata = {'lagrangian': lagrangian, 'f': f, 'gts': gts, 'eqs': eqs, 'X': X} if ell > 0: alpha_E_1 = hierarchy_e_k([f, f.upcast_to_polynomial(1)] + gts + eqs, k=1) modulator = Polynomial(2 * alpha_E_1, np.ones(alpha_E_1.shape[0])) ** ell lagrangian = lagrangian * modulator f = f * modulator else: modulator = f.upcast_to_polynomial(1) metadata['modulator'] = modulator # In primal form, the Lagrangian is constrained to be a SAGE polynomial. # Introduce a dual variable "v" for this constraint. v = cl.Variable(shape=(lagrangian.m, 1), name='v') metadata['v_poly'] = v constraints = relative_dual_sage_poly_cone(lagrangian, v, 'Lagrangian', log_AbK=X) for s_g, g in ineq_lag_mults: # These generalized Lagrange multipliers "s_g" are SAGE polynomials. # For each such multiplier, introduce an appropriate dual variable "v_g", along # with constraints over that dual variable. g_m = g * modulator c_g = sym_corr.moment_reduction_array(s_g, g_m, lagrangian) name_base = 'v_' + str(g) if slacks: v_g = cl.Variable(name=name_base, shape=(s_g.m, 1)) con = c_g @ v == v_g con.name += str(g) + ' >= 0' constraints.append(con) else: v_g = c_g @ v constraints += relative_dual_sage_poly_cone(s_g, v_g, name_base=(name_base + ' domain'), log_AbK=X) for z_g, g in eq_lag_mults: # These generalized Lagrange multipliers "z_g" are arbitrary polynomials. # They dualize to homogeneous equality constraints. g_m = g * modulator c_g = sym_corr.moment_reduction_array(z_g, g_m, lagrangian) con = c_g @ v == 0 con.name += str(g) + ' == 0' constraints.append(con) # Equality constraint (for the Lagrangian to be bounded). a = sym_corr.relative_coeff_vector(modulator, lagrangian.alpha) constraints.append(a.T @ v == 1) # Define the dual objective function. obj_vec = sym_corr.relative_coeff_vector(f, lagrangian.alpha) obj = obj_vec.T @ v # Return the coniclifts Problem. prob = cl.Problem(cl.MIN, obj, constraints) prob.metadata = metadata cl.clear_variable_indices() return prob
def sig_constrained_dual(f, gts, eqs, p=0, q=1, ell=0, X=None, slacks=False): """ Construct the SAGE-(p, q, ell) dual problem for the signomial program min{ f(x) : g(x) >= 0 for g in gts, g(x) == 0 for g in eqs, and x in X } where X = :math:`R^{\\texttt{f.n}}` by default. """ lagrangian, ineq_lag_mults, eq_lag_mults, _ = make_sig_lagrangian(f, gts, eqs, p=p, q=q) metadata = {'lagrangian': lagrangian, 'f': f, 'gts': gts, 'eqs': eqs, 'level': (p, q, ell), 'X': X} if ell > 0: alpha_E_1 = hierarchy_e_k([f, f.upcast_to_signomial(1)] + list(gts) + list(eqs), k=1) modulator = Signomial(alpha_E_1, np.ones(alpha_E_1.shape[0])) ** ell lagrangian = lagrangian * modulator f = f * modulator else: modulator = f.upcast_to_signomial(1) metadata['modulator'] = modulator # In primal form, the Lagrangian is constrained to be a SAGE signomial. # Introduce a dual variable "v" for this constraint. v = cl.Variable(shape=(lagrangian.m, 1), name='v') con = relative_dual_sage_cone(lagrangian, v, name='Lagrangian SAGE dual constraint', X=X) constraints = [con] expcovers = None for i, (s_h, h) in enumerate(ineq_lag_mults): # These generalized Lagrange multipliers "s_h" are SAGE signomials. # For each such multiplier, introduce an appropriate dual variable "v_h", along # with constraints over that dual variable. h_m = h * modulator c_h = sym_corr.moment_reduction_array(s_h, h_m, lagrangian) if slacks: v_h = cl.Variable(name='v_' + str(h), shape=(s_h.m, 1)) constraints.append(c_h @ v == v_h) else: v_h = c_h @ v con_name = 'SAGE dual for signomial inequality # ' + str(i) con = relative_dual_sage_cone(s_h, v_h, name=con_name, X=X, expcovers=expcovers) expcovers = con.ech.expcovers # only * really * needed in first iteration, but keeps code flat. constraints.append(con) for s_h, h in eq_lag_mults: # These generalized Lagrange multipliers "s_h" are arbitrary signomials. # They dualize to homogeneous equality constraints. h = h * modulator c_h = sym_corr.moment_reduction_array(s_h, h, lagrangian) constraints.append(c_h @ v == 0) # Equality constraint (for the Lagrangian to be bounded). a = sym_corr.relative_coeff_vector(modulator, lagrangian.alpha) constraints.append(a.T @ v == 1) # Define the dual objective function. obj_vec = sym_corr.relative_coeff_vector(f, lagrangian.alpha) obj = obj_vec.T @ v # Return the coniclifts Problem. prob = cl.Problem(cl.MIN, obj, constraints) prob.metadata = metadata cl.clear_variable_indices() return prob
def sig_solrec(prob, ineq_tol=1e-8, eq_tol=1e-6, skip_ls=False): """ Recover a list of candidate solutions from a dual SAGE relaxation. Solutions are guaranteed to be feasible up to specified tolerances, but not necessarily optimal. Parameters ---------- prob : coniclifts.Problem A dual-form SAGE relaxation. ineq_tol : float The amount by which recovered solutions can violate inequality constraints. eq_tol : float The amount by which recovered solutions can violate equality constraints. skip_ls : bool Whether or not to skip constrained least-squares solution recovery. Returns ------- sols : list of ndarrays A list of feasible solutions, sorted in increasing order of objective function value. It is possible that this list is empty, in which case no feasible solutions were recovered. """ con = prob.constraints[0] if not con.name == 'Lagrangian SAGE dual constraint': # pragma: no cover raise RuntimeError('Unexpected first constraint in dual SAGE relaxation.') metadata = prob.metadata f = metadata['f'] # Recover any constraints present in "prob" lag_gts, lag_eqs = [], [] if 'gts' in metadata: # only happens in "constrained_sage_dual". lag_gts = metadata['gts'] lag_eqs = metadata['eqs'] lagrangian = _make_dummy_lagrangian(f, lag_gts, lag_eqs) if con.X is None: X_gts, X_eqs = [], [] else: X_gts, X_eqs = con.X.gts, con.X.eqs gts = lag_gts + X_gts eqs = lag_eqs + X_eqs # Search for solutions which meet the feasibility criteria v = con.v.value v[v < 0] = 0 if np.any(np.isnan(v)): return None alpha = con.alpha dummy_modulated_lagrangian = Signomial(alpha, np.ones(shape=(alpha.shape[0],))) alpha_reduced = lagrangian.alpha modulator = metadata['modulator'] M = moment_reduction_array(lagrangian, modulator, dummy_modulated_lagrangian) if skip_ls: sols0 = [] else: sols0 = _least_squares_solution_recovery(alpha_reduced, con, v, M, gts, eqs, ineq_tol, eq_tol) sols1 = _dual_age_cone_solution_recovery(con, v, M, gts, eqs, ineq_tol, eq_tol) sols = sols0 + sols1 sols.sort(key=lambda mu: f(mu)) return sols
def poly_solrec(prob, ineq_tol=1e-8, eq_tol=1e-6, skip_ls=False, **kwargs): """ Recover a list of candidate solutions from a dual SAGE relaxation. Solutions are guaranteed to be feasible up to specified tolerances, but not necessarily optimal. Parameters ---------- prob : coniclifts.Problem A dual-form SAGE relaxation, from ``poly_constrained_relaxation``. ineq_tol : float The amount by which recovered solutions can violate inequality constraints. eq_tol : float The amount by which recovered solutions can violate equality constraints. skip_ls : bool Whether or not to skip least-squares solution recovery. Returns ------- sols : list of ndarrays A list of feasible solutions, sorted in increasing order of objective function value. It is possible that this list is empty, in which case no feasible solutions were recovered. Notes ----- This function accepts the following keyword arguments: zero_tol : float Used in magnitude recovery. If a component of the Lagrangian's moment vector is smaller than this (in absolute value), pretend it's zero in the least-squares step. Defaults to 1e-20. heuristic_signs : bool Used in sign recovery. If True, then attempts to infer variable signs from the Lagrangian's moment vector even when a completely consistent set of signs does not exist. Defaults to True. all_signs : bool Used in sign recovery. If True, then consider returning solutions which differ only by sign. Defaults to True. This function is implemented only for poly_constrained_relaxation (not poly_relaxation). """ zero_tol = kwargs['zero_tol'] if 'zero_tol' in kwargs else 1e-20 heuristic = kwargs[ 'heuristic_signs'] if 'heuristic_signs' in kwargs else True all_signs = kwargs['all_signs'] if 'all_signs' in kwargs else True metadata = prob.metadata f = metadata['f'] lag_gts = metadata['gts'] lag_eqs = metadata['eqs'] lagrangian = _make_dummy_lagrangian(f, lag_gts, lag_eqs) con = prob.constraints[0] alpha = con.alpha dummy_modulated_lagrangian = Polynomial( alpha, np.ones(shape=(alpha.shape[0], ))) # coefficients dont matter modulator = metadata['modulator'] v = metadata['v_poly'].value # possible that v_sig and v are the same if np.any(np.isnan(v)): return [] M = moment_reduction_array(lagrangian, modulator, dummy_modulated_lagrangian) v_reduced = M @ v alpha_reduced = lagrangian.alpha mags = variable_magnitudes(con, alpha_reduced, v_reduced, zero_tol, skip_ls) signs = variable_sign_patterns(alpha_reduced, v_reduced, heuristic, all_signs) # Now we need to build the candidate solutions, and check them for feasibility. if con.X is not None: gts = lag_gts + [g for g in con.X.gts] eqs = lag_eqs + [g for g in con.X.eqs] else: gts = lag_gts eqs = lag_eqs solutions = [] for mag in mags: for sign in signs: x = mag * sign # elementwise if is_feasible(x, gts, eqs, ineq_tol, eq_tol): solutions.append(x) solutions.sort(key=lambda xi: f(xi)) return solutions