Source code for pyproximal.projection.Box
import numpy as np
from scipy.optimize import bisect
[docs]class BoxProj():
r"""Box orthogonal projection.
Parameters
----------
lower : :obj:`float` or :obj:`np.ndarray`, optional
Lower bound
upper : :obj:`float` or :obj:`np.ndarray`, optional
Upper bound
Notes
-----
Given a Box set defined as:
.. math::
\operatorname{Box}_{[l, u]} = \{ x: l \leq x\leq u \}
its orthogonal projection is:
.. math::
P_{\operatorname{Box}_{[l, u]}} (x_i) = min\{ max \{x_i, l_i\}, u_i \} =
\begin{cases}
l_i, & x_i < l_i\\
x_i,& l_i \leq x_i \leq u_i \\
u_i, & x_i > u_i\\
\end{cases} \quad \forall i
Note that this is the proximal operator of the corresponding
indicator function :math:`\mathcal{I}_{\operatorname{Box}_{[l, u]}}`.
"""
def __init__(self, lower=-np.inf, upper=np.inf):
self.lower = lower
self.upper = upper
def __call__(self, x):
x = np.minimum(np.maximum(x, self.lower), self.upper)
return x
[docs]class HyperPlaneBoxProj():
r"""Orthogonal projection of the intersection between a Hyperplane and a
Box.
Parameters
----------
coeffs : :obj:`np.ndarray`
Vector of coefficients used in the definition of the hyperplane
scalar : :obj:`float`
Scalar used in the definition of the hyperplane
lower : :obj:`float` or :obj:`np.ndarray`, optional
Lower bound of Box
upper : :obj:`float` or :obj:`np.ndarray`, optional
Upper bound of Box
maxiter : :obj:`int`, optional
Maximum number of iterations used by :func:`scipy.optimize.bisect`
xtol : :obj:`float`, optional
Absolute tolerance of :func:`scipy.optimize.bisect`
Notes
-----
Given the definition of an Hyperplane:
.. math::
H_{c,b} = \{ \mathbf{x}: \mathbf{c}^T \mathbf{x} = b\}
that of a Box (see :class:`pyproximal.projection.Box.BoxProj`), the
intersection between the two can be written as:
.. math::
C = Box_{[l, u]} \cap H_{c,b} =
\{ \mathbf{x}: \mathbf{c}^T \mathbf{x} = b , \; l \leq x_i \leq u \}
The orthogonal projection of such intersection is given by:
.. math::
P_C = P_{Box_{[l, u]}} (\mathbf{x} - \mu^* \mathbf{c})
where :math:`\mu` is obtained by solving the following equation by
bisection
.. math::
f(\mu) = \mathbf{c}^T P_{Box_{[l, u]}} (\mathbf{x} -
\mu \mathbf{c}) - b
"""
def __init__(self, coeffs, scalar, lower=-np.inf, upper=np.inf,
maxiter=100, xtol=1e-5):
self.coeffs = coeffs.ravel()
self.scalar = scalar
self.lower = lower
self.upper = upper
self.maxiter = maxiter
self.xtol = xtol
self.box = BoxProj(lower, upper)
def __call__(self, x):
"""Apply HyperPlaneBoxProj projection
Parameters
----------
x : :obj:`np.ndarray`
Vector
"""
def fun(mu, x):
return np.dot(self.coeffs, self.box(x - mu * self.coeffs)) - \
self.scalar
xshape = x.shape
x = x.ravel()
# identify brackets for bisect ensuring that the evaluated fun
# has different sign
bisect_lower = -1
while fun(bisect_lower, x) < 0:
bisect_lower *= 2
bisect_upper = 1
while fun(bisect_upper, x) > 0:
bisect_upper *= 2
# find optimal mu
mu = bisect(lambda mu: fun(mu, x), bisect_lower, bisect_upper,
maxiter=self.maxiter, xtol=self.xtol)
# compute projection
y = self.box(x - mu * self.coeffs)
return y.reshape(xshape)