Source code for secml.optim.constraints.c_constraint_box

"""
.. module:: CConstraintBox
   :synopsis: Box constraint.

.. moduleauthor:: Battista Biggio <battista.biggio@unica.it>
.. moduleauthor:: Marco Melis <marco.melis@unica.it>

"""
import numpy as np
from secml.optim.constraints import CConstraint
from secml.array import CArray
from secml.core.constants import inf


[docs]class CConstraintBox(CConstraint): """Class that defines a box constraint. Parameters ---------- lb, ub : scalar or CArray or None, optional Bounds of the constraints. If scalar, the same bound will be applied to all features. If CArray, should contain a bound for each feature. If None, a +/- inf ub/lb bound will be used for all features. Attributes ---------- class_type : 'box' """ __class_type = 'box' def __init__(self, lb=None, ub=None): # Lower bound lb = -inf if lb is None else lb self._lb = lb.ravel() if isinstance(lb, CArray) else lb # Upper bound ub = inf if ub is None else ub self._ub = ub.ravel() if isinstance(ub, CArray) else ub self._validate_bounds() # Check if bounds have been correctly defined @property def lb(self): """Lower bound.""" return self._lb @property def ub(self): """Upper bound.""" return self._ub def _validate_bounds(self): """Check that bounds are valid. Must: - be lb <= ub - have same size if both CArray. """ lb_array = CArray(self.lb) ub_array = CArray(self.ub) if isinstance(self.lb, CArray) and isinstance(self.ub, CArray): if lb_array.size != ub_array.size: raise ValueError("`ub` and `lb` must have the same size if " "both `CArray`. Currently {:} and {:}" "".format(ub_array.size, lb_array.size)) if (lb_array > ub_array).any(): raise ValueError("`lb` must be lower or equal than `ub`") def _check_inf(self): """Return True if any of the bounds are or contain inf. Returns ------- bool """ # Convert both bounds to CArray for simplicity if CArray(self.ub).is_inf().any() or CArray(self.lb).is_inf().any(): return True return False @property def center(self): """Center of the constraint.""" if self._check_inf() is True: raise ValueError("cannot compute `center` as at least one value " "in the bounds is +/- `inf`") return CArray(0.5 * (self.ub + self.lb)).ravel() @property def radius(self): """Radius of the constraint.""" if self._check_inf() is True: raise ValueError("cannot compute `radius` as at least one value " "in the bounds is +/- `inf`") return CArray(0.5 * (self.ub - self.lb)).ravel()
[docs] def set_center_radius(self, c, r): """Set constraint bounds in terms of center and radius. This method will transform the input center/radius as follows: lb = center - radius ub = center + radius Parameters ---------- c : scalar Constraint center. r : scalar Constraint radius. """ self._lb = c - r self._ub = c + r self._validate_bounds() # Check if bounds have been correctly defined
[docs] def is_active(self, x, tol=1e-4): """Returns True if constraint is active. A constraint is active if c(x) = 0. By default we assume constraints of the form c(x) <= 0. Parameters ---------- x : CArray Input sample. tol : float, optional Tolerance to use for comparing c(x) against 0. Default 1e-4. Returns ------- bool True if constraint is active, False otherwise. """ # If at least one value in the bounds is +/- inf, # the constraint is never active if self._check_inf() is True: return False return super(CConstraintBox, self).is_active(x, tol=tol)
[docs] def is_violated(self, x): """Returns the violated status of the constraint for the sample x. We assume the constraint violated if c(x) <= 0. Parameters ---------- x : CArray Input sample. Returns ------- bool True if constraint is violated, False otherwise. """ if not x.is_vector_like: raise ValueError("only a vector-like array is accepted") return (x < self.lb).logical_or(x > self.ub).any()
def _constraint(self, x): """Returns the value of the constraint for the sample x. The constraint value y is given by: y = max(abs(x - center) - radius) Parameters ---------- x : CArray Input array. Returns ------- float Value of the constraint. """ # if x is sparse, and center and radius are not (sparse) vectors if x.issparse and self.center.size != x.size and \ self.radius.size != x.size: return self._constraint_sparse(x) return float((abs(x - self.center) - self.radius).max()) def _constraint_sparse(self, x): """Returns the value of the constraint for the sample x. This implementation for sparse arrays only allows a scalar value for center and radius. Parameters ---------- x : CArray Input array. Returns ------- float Value of the constraint. """ if self.center.size > 1 and self.radius.size > 1: raise ValueError("Box center and radius are not scalar values.") m0 = (abs(0 - self.center) - self.radius).max() if x.nnz == 0: return float(m0) # computes constraint values (l-inf dist. to center) for nonzero values z = abs(CArray(x.nnz_data).todense() - self.center) - self.radius m = z.max() # if there are no zeros in x... (it may be effectively "dense") if x.nnz == x.size: # return current maximum value return float(m) # otherwise evaluate also the l-inf dist. of 0 elements to the center, # and also consider that in the max computation return float(max(m, m0)) def _projection(self, x): """Project x onto feasible domain / within the given constraint. Parameters ---------- x : CArray Input sample. Returns ------- CArray Projected x onto feasible domain if constraint is violated. """ # If bound is float, ensure x is float if np.issubdtype(CArray(self.ub).dtype, np.floating) or \ np.issubdtype(CArray(self.ub).dtype, np.floating): x = x.astype(float) if isinstance(self.ub, CArray): x[x > self.ub] = self.ub[x > self.ub] else: # Same ub for all the features x[x > self.ub] = self.ub if isinstance(self.lb, CArray): x[x < self.lb] = self.lb[x < self.lb] else: # Same lb for all the features x[x < self.lb] = self.lb return x