Source code for qics.cones.symmetric.nonnegorthant

# Copyright (c) 2024, Kerry He, James Saunderson, and Hamza Fawzi

# This Python package QICS is licensed under the MIT license; see LICENSE.md
# file in the root directory or at https://github.com/kerry-he/qics

import numpy as np
import scipy as sp

from qics.cones.base import SymCone


[docs] class NonNegOrthant(SymCone): r"""A class representing a nonnegative orthant .. math:: \mathbb{R}^n_+ = \{ x \in \mathbb{R}^n : x \geq 0 \}. Parameters ---------- n : :obj:`int` Dimension of the cone. """ def __init__(self, n): self.n = n self.nu = n # Barrier parameter self.dim = [n] self.type = ["r"] self.congr_aux_updated = False return def get_init_point(self, out): self.set_point([np.ones((self.n, 1))], [np.ones((self.n, 1))]) out[0][:] = self.x return out def set_point(self, primal, dual=None, a=True): self.x = primal[0] * a self.z = dual[0] * a if (dual is not None) else None def set_dual(self, dual, a=True): self.z = dual[0] * a def get_feas(self): if np.any(np.less_equal(self.x, 0)): return False if self.z is None and np.any(np.less_equal(self.z, 0)): return False return True def get_dual_feas(self): return np.all(np.greater(self.z, 0)) def get_val(self): return -np.sum(np.log(self.x)) def grad_ip(self, out): out[0][:] = -np.reciprocal(self.x) return out def hess_prod_ip(self, out, H): out[0][:] = H[0] / (self.x**2) return out def hess_congr(self, A): return self.base_congr(A, np.reciprocal(self.x)) def invhess_prod_ip(self, out, H): out[0][:] = H[0] * (self.x**2) return out def invhess_congr(self, A): return self.base_congr(A, self.x) def base_congr(self, A, x): if sp.sparse.issparse(A): if not hasattr(self, "Ax"): self.Ax = A.copy() self.Ax.data = A.data * np.take(x, A.col) return self.Ax @ self.Ax.T else: Ax = x * A.T return Ax.T @ Ax def third_dir_deriv_axpy(self, out, H, a=True): out[0] -= 2 * a * H[0] * H[0] / (self.x * self.x * self.x) return out def prox(self): return np.linalg.norm(self.x * self.z - 1, np.inf) # ======================================================================== # Functions specific to symmetric cones for NT scaling # ======================================================================== # We want the NT scaling point w and scaled variable lambda such that # H(w) s = z <==> lambda := W^-T s = W z # where H(w) = W^T W. For the nonnegative orthant, we have # w = s.^1/2 ./ z.^1/2 # lambda = z.^1/2 .* s.^1/2 # Also, we have the linear transformations given by # H(w) ds = ds .* s ./ z # W^-T ds = ds .* z.^1/2 ./ s.^1/2 # W dz = dz .* s.^1/2 ./ z.^1/2 # See: [Section 4.1]https://www.seas.ucla.edu/~vandenbe/publications/coneprog.pdf def nt_prod_ip(self, out, H): out[0][:] = H[0] * self.z / self.x return out def nt_congr(self, A): return self.base_congr(A, np.sqrt(self.z / self.x)) def invnt_prod_ip(self, out, H): out[0][:] = H[0] * self.x / self.z return out def invnt_congr(self, A): return self.base_congr(A, np.sqrt(self.x / self.z)) def comb_dir(self, out, ds, dz, sigma_mu): # Compute the residual for rs where rs is given as the lhs of # Lambda o (W dz + W^-T ds) = -Lambda o Lambda - (W^-T ds_a) o (W dz_a) # + sigma * mu * 1 # which is rearranged into the form H ds + dz = rs, i.e., # rs := W^-1 [ Lambda \ (-Lambda o Lambda - (W^-T ds_a) o (W dz_a) + sigma*mu 1) ] # See: [Section 5.4]https://www.seas.ucla.edu/~vandenbe/publications/coneprog.pdf out[0][:] = (sigma_mu - ds[0] * dz[0]) / self.x - self.z def step_to_boundary(self, ds, dz): # Compute the maximum step alpha in [0, 1] we can take such that # s + alpha ds >= 0 # z + alpha dz >= 0 # See: [Section 8.3]https://www.seas.ucla.edu/~vandenbe/publications/coneprog.pdf # Compute rho := ds / s and sig := dz / z min_rho = np.min(ds / self.x) min_sig = np.min(dz / self.z) # Maximum step is given by # alpha := 1 / max(0, -min(rho), -min(sig)) # Clamp this step between 0 and 1 if min_rho >= 0 and min_sig >= 0: return 1.0 else: return 1.0 / max(-min_rho, -min_sig)