Source code for iwopy.core.constraint
import numpy as np
from iwopy.utils import new_instance
from .function import OptFunction
[docs]
class Constraint(OptFunction):
    """
    Abstract base class for optimization
    constraints.
    Attributes
    ----------
    tol: float
        The tolerance for constraint violations
    :group: core
    """
[docs]
    def __init__(self, *args, tol=1e-5, **kwargs):
        """
        Constructor
        Parameters
        ----------
        tol: float
            The tolerance for constraint violations
        """
        super().__init__(*args, **kwargs)
        self.tol = tol 
[docs]
    def get_bounds(self):
        """
        Returns the bounds for all components.
        Non-existing bounds are expressed by np.inf.
        Returns
        -------
        min: np.array
            The lower bounds, shape: (n_components,)
        max: np.array
            The upper bounds, shape: (n_components,)
        """
        return (
            np.full(self.n_components(), -np.inf, dtype=np.float64),
            np.zeros(self.n_components(), dtype=np.float64),
        ) 
[docs]
    def check_individual(self, constraint_values, verbosity=0):
        """
        Check if the constraints are fullfilled for the
        given individual.
        Parameters
        ----------
        constraint_values: np.array
            The constraint values, shape: (n_components,)
        verbosity: int
            The verbosity level, 0 = silent
        Returns
        values: np.array
        -------
            The boolean result, shape: (n_components,)
        """
        vals = constraint_values
        mi, ma = self.get_bounds()
        out = (vals + self.tol >= mi) & (vals - self.tol <= ma)
        if verbosity:
            print(f"Constraint '{self.name}': tol = {self.tol}")
            cnames = self.component_names
            for ci in range(self.n_components()):
                val = f"{cnames[ci]} = {vals[ci]:.3e}"
                suc = "OK" if out[ci] else "FAILED"
                print(f"  Constraint {val:<30} {suc}")
        return out 
[docs]
    def check_population(self, constraint_values, verbosity=0):
        """
        Check if the constraints are fullfilled for the
        given population.
        Parameters
        ----------
        constraint_values: np.array
            The constraint values, shape: (n_pop, n_components,)
        verbosity: int
            The verbosity level, 0 = silent
        Returns
        -------
        values: np.array
            The boolean result, shape: (n_pop, n_components)
        """
        vals = constraint_values
        mi, ma = self.get_bounds()
        mi = np.array(mi, dtype=np.float64)
        ma = np.array(ma, dtype=np.float64)
        out = (vals + self.tol >= mi[None, :]) & (vals - self.tol <= ma[None, :])
        if verbosity:
            print(f"Constraint '{self.name}': tol = {self.tol}")
            cnames = self.component_names
            for ci in range(self.n_components()):
                suc = "OK" if np.all(out[ci]) else "FAILED"
                print(f"  Constraint {cnames[ci]:<20} {suc}")
        return out 
[docs]
    @classmethod
    def new(cls, constraint_type, *args, **kwargs):
        """
        Run-time constraint factory.
        Parameters
        ----------
        constraint_type: str
            The selected derived class name
        args: tuple, optional
            Additional parameters for constructor
        kwargs: dict, optional
            Additional parameters for constructor
        """
        return new_instance(cls, constraint_type, *args, **kwargs)