Skip to content

Search space

black_it.search_space.SearchSpace

A class that contains information on the search grid of explorable parameters.

Source code in black_it/search_space.py
class SearchSpace:  # pylint: disable=too-few-public-methods
    """A class that contains information on the search grid of explorable parameters."""

    def __init__(
        self,
        parameters_bounds: Union[NDArray[np.float64], List[List[float]]],
        parameters_precision: Union[NDArray[np.float64], List[float]],
        verbose: bool,
    ):
        """
        Initialize the SearchSpace object.

        The values of parameters_bounds and parameters_precision parameters have
        to satisfy the following constraints, otherwise an exception (subclass
        of SearchSpaceError) is raised:

        - parameters_bounds must be a two-elements array/list (or
          BoundsNotOfSizeTwoError is raised)
        - the two sub-arrays/lists of parameter_bounds must have the same length (or
          BoundsOfDifferentLengthError is raised)
        - parameters_precision array/list must have the same number of elements than
          the two sub-arrays/lists in parameters_bounds (or BadPrecisionLengthError is
          raised)
        - lower bounds and upper bounds cannot have the same value (or
          SameLowerAndUpperBoundError is raised)
        - every lower bound must be lower than the corresponding upper bound (or
          LowerBoundGreaterThanUpperBoundError)
        - 0 is an invalid value for a precision (or PrecisionZeroError is
          raised)
        - any given parameter precision has to be strictly lower than the
          allowed parameter span (or PrecisionGreaterThanBoundsRangeError is
          raised)

        Args:
            parameters_bounds: lower and upper bounds of the parameters.
            parameters_precision: resolution of the grid of parameters.
            verbose: whether to print or not the information on the search space.
        """
        SearchSpace._check_bounds(parameters_bounds, parameters_precision)

        # The bounds we were given are well formed. Save them.
        self._parameters_bounds = np.array(parameters_bounds)
        self._parameters_precision = np.array(parameters_precision)

        # Initialize search grid
        self._param_grid: List[NDArray[np.float64]] = []
        self._space_size = 1
        for i in range(self.dims):
            new_col = np.arange(
                parameters_bounds[0][i],
                parameters_bounds[1][i] + 0.0000001,
                parameters_precision[i],
                dtype=np.float64,
            )
            self._param_grid.append(new_col)
            self._space_size *= len(new_col)

        if verbose:
            print("\n***")
            print(f"Number of free params:       {self.dims}.")
            print(f"Explorable param space size: {self.space_size}.")
            print("***\n")

    @staticmethod
    def _check_bounds(
        parameters_bounds: Union[NDArray[np.float64], List[List[float]]],
        parameters_precision: Union[NDArray[np.float64], List[float]],
    ) -> None:
        """
        Ensure parameter_bounds and parameter_precision have acceptable values.

        This is an helper function for SearchSpace.__init__().

        Args:
            parameters_bounds: lower and upper bounds of the parameters
            parameters_precision: resolution of the grid of parameters
        """
        # ensure parameters_bounds is a two-elements array
        if len(parameters_bounds) != 2:
            raise BoundsNotOfSizeTwoError(len(parameters_bounds))

        # ensure the two sub-arrays of parameter_bounds (which are the min and
        # max bounds for each parameter) have the same length
        if len(parameters_bounds[0]) != len(parameters_bounds[1]):
            raise BoundsOfDifferentLengthError(
                len(parameters_bounds[0]),
                len(parameters_bounds[1]),
            )

        # ensure parameters_precision array has as many elements as either one
        # of parameters_bounds sub-array
        if len(parameters_precision) != len(parameters_bounds[0]):
            raise BadPrecisionLengthError(
                len(parameters_precision),
                len(parameters_bounds[0]),
            )

        for i, (lower_bound, upper_bound, precision) in enumerate(
            zip(parameters_bounds[0], parameters_bounds[1], parameters_precision)
        ):
            # ensure lower bounds and upper bounds do not have the same value
            if lower_bound == upper_bound:
                raise SameLowerAndUpperBoundError(i, lower_bound)

            # ensure each lower bound is lower than the corresponding upper one
            if lower_bound > upper_bound:
                raise LowerBoundGreaterThanUpperBoundError(i, lower_bound, upper_bound)

            # a precision of 0 is not meaningful
            if precision == 0:
                raise PrecisionZeroError(i)

            # any given parameter precision has to be strictly lower than the
            # allowed parameter span
            if precision > (upper_bound - lower_bound):
                raise PrecisionGreaterThanBoundsRangeError(
                    i, lower_bound, upper_bound, precision
                )

    @property
    def param_grid(self) -> List[NDArray[np.float64]]:
        """Discretized parameter space containing all possible candidates for calibration."""
        return self._param_grid

    @property
    def parameters_bounds(self) -> NDArray[np.float64]:
        """Two dimensional array containing lower and upper bounds for each parameter."""
        return self._parameters_bounds

    @property
    def parameters_precision(self) -> NDArray[np.float64]:
        """One dimensional array containing the precisions for each parameter."""
        return self._parameters_precision

    @property
    def dims(self) -> int:
        """Return the number of model parameters configured for calibration."""
        return len(self._parameters_precision)

    @property
    def space_size(self) -> int:
        """Cardinality of the potential parameter space to be searched."""
        return self._space_size

dims: int property readonly

Return the number of model parameters configured for calibration.

param_grid: List[numpy.ndarray[Any, numpy.dtype[numpy.float64]]] property readonly

Discretized parameter space containing all possible candidates for calibration.

parameters_bounds: ndarray property readonly

Two dimensional array containing lower and upper bounds for each parameter.

parameters_precision: ndarray property readonly

One dimensional array containing the precisions for each parameter.

space_size: int property readonly

Cardinality of the potential parameter space to be searched.

__init__(self, parameters_bounds, parameters_precision, verbose) special

Initialize the SearchSpace object.

The values of parameters_bounds and parameters_precision parameters have to satisfy the following constraints, otherwise an exception (subclass of SearchSpaceError) is raised:

  • parameters_bounds must be a two-elements array/list (or BoundsNotOfSizeTwoError is raised)
  • the two sub-arrays/lists of parameter_bounds must have the same length (or BoundsOfDifferentLengthError is raised)
  • parameters_precision array/list must have the same number of elements than the two sub-arrays/lists in parameters_bounds (or BadPrecisionLengthError is raised)
  • lower bounds and upper bounds cannot have the same value (or SameLowerAndUpperBoundError is raised)
  • every lower bound must be lower than the corresponding upper bound (or LowerBoundGreaterThanUpperBoundError)
  • 0 is an invalid value for a precision (or PrecisionZeroError is raised)
  • any given parameter precision has to be strictly lower than the allowed parameter span (or PrecisionGreaterThanBoundsRangeError is raised)

Parameters:

Name Type Description Default
parameters_bounds Union[numpy.ndarray[Any, numpy.dtype[numpy.float64]], List[List[float]]]

lower and upper bounds of the parameters.

required
parameters_precision Union[numpy.ndarray[Any, numpy.dtype[numpy.float64]], List[float]]

resolution of the grid of parameters.

required
verbose bool

whether to print or not the information on the search space.

required
Source code in black_it/search_space.py
def __init__(
    self,
    parameters_bounds: Union[NDArray[np.float64], List[List[float]]],
    parameters_precision: Union[NDArray[np.float64], List[float]],
    verbose: bool,
):
    """
    Initialize the SearchSpace object.

    The values of parameters_bounds and parameters_precision parameters have
    to satisfy the following constraints, otherwise an exception (subclass
    of SearchSpaceError) is raised:

    - parameters_bounds must be a two-elements array/list (or
      BoundsNotOfSizeTwoError is raised)
    - the two sub-arrays/lists of parameter_bounds must have the same length (or
      BoundsOfDifferentLengthError is raised)
    - parameters_precision array/list must have the same number of elements than
      the two sub-arrays/lists in parameters_bounds (or BadPrecisionLengthError is
      raised)
    - lower bounds and upper bounds cannot have the same value (or
      SameLowerAndUpperBoundError is raised)
    - every lower bound must be lower than the corresponding upper bound (or
      LowerBoundGreaterThanUpperBoundError)
    - 0 is an invalid value for a precision (or PrecisionZeroError is
      raised)
    - any given parameter precision has to be strictly lower than the
      allowed parameter span (or PrecisionGreaterThanBoundsRangeError is
      raised)

    Args:
        parameters_bounds: lower and upper bounds of the parameters.
        parameters_precision: resolution of the grid of parameters.
        verbose: whether to print or not the information on the search space.
    """
    SearchSpace._check_bounds(parameters_bounds, parameters_precision)

    # The bounds we were given are well formed. Save them.
    self._parameters_bounds = np.array(parameters_bounds)
    self._parameters_precision = np.array(parameters_precision)

    # Initialize search grid
    self._param_grid: List[NDArray[np.float64]] = []
    self._space_size = 1
    for i in range(self.dims):
        new_col = np.arange(
            parameters_bounds[0][i],
            parameters_bounds[1][i] + 0.0000001,
            parameters_precision[i],
            dtype=np.float64,
        )
        self._param_grid.append(new_col)
        self._space_size *= len(new_col)

    if verbose:
        print("\n***")
        print(f"Number of free params:       {self.dims}.")
        print(f"Explorable param space size: {self.space_size}.")
        print("***\n")

black_it.search_space.SearchSpaceError (ValueError)

Base class for the exceptions raised by SearchSpace when its construction fails.

If you need to distinguish a specific error, please do not rely on parsing the error message, because it is considered unstable: catch instead the specific exception type you want to handle, and use the additional fields each subtype exposes (for example: BadPrecisionLengthError.bounds_length)

Source code in black_it/search_space.py
class SearchSpaceError(ValueError):
    """Base class for the exceptions raised by SearchSpace when its construction fails.

    If you need to distinguish a specific error, please do not rely on parsing
    the error message, because it is considered unstable: catch instead the
    specific exception type you want to handle, and use the additional fields
    each subtype exposes (for example: BadPrecisionLengthError.bounds_length)
    """

black_it.search_space.BadPrecisionLengthError (SearchSpaceError)

Raised when the parameters_precision array has a different length than the bounds'.

Attributes:

Name Type Description
precisions_length int

number of elements of the precision subarray

bounds_length int

number of elements of the two bounds subbarrays

Source code in black_it/search_space.py
class BadPrecisionLengthError(SearchSpaceError):
    """Raised when the parameters_precision array has a different length than the bounds'.

    Attributes:
        precisions_length (int): number of elements of the precision subarray
        bounds_length (int): number of elements of the two bounds subbarrays
    """

    def __init__(  # noqa: D107
        self, precisions_length: int, bounds_length: int
    ) -> None:
        super().__init__(
            f"parameters_precision array has {precisions_length} elements. Its "
            f"length, instead, has to be {bounds_length}, the same as the bounds'."
        )
        self.precisions_length = precisions_length
        self.bounds_length = bounds_length

black_it.search_space.BoundsNotOfSizeTwoError (SearchSpaceError)

Raised when bounds are not a two-dimensional array.

Attributes:

Name Type Description
count_bounds_subarrays int

wrong number of subarrays the parameter_bounds array was made of. It will be different than 2.

Source code in black_it/search_space.py
class BoundsNotOfSizeTwoError(SearchSpaceError):
    """Raised when bounds are not a two-dimensional array.

    Attributes:
        count_bounds_subarrays (int): wrong number of subarrays the
            parameter_bounds array was made of. It will be different than 2.
    """

    def __init__(self, count_bounds_subarrays: int) -> None:  # noqa: D107
        super().__init__(
            f"parameters_bounds must be a two dimensional array. This one has "
            f"size {count_bounds_subarrays}."
        )
        self.count_bounds_subarrays = count_bounds_subarrays

black_it.search_space.BoundsOfDifferentLengthError (SearchSpaceError)

Raised when the lower and upper bounds do not have the same number of elements.

Attributes:

Name Type Description
lower_bounds_length int

number of elements in the subarray 0. It will be different than upper_bounds_length

upper_bounds_length int

number of elements in the subarray 1. It will be different than lower_bounds_length

Source code in black_it/search_space.py
class BoundsOfDifferentLengthError(SearchSpaceError):
    """Raised when the lower and upper bounds do not have the same number of elements.

    Attributes:
        lower_bounds_length (int): number of elements in the subarray 0. It will
            be different than upper_bounds_length
        upper_bounds_length (int): number of elements in the subarray 1. It will
            be different than lower_bounds_length
    """

    def __init__(  # noqa: D107
        self, lower_bounds_length: int, upper_bounds_length: int
    ) -> None:
        super().__init__(
            f"parameters_bounds subarrays must be of the same length. Lower "
            f"bounds length: {lower_bounds_length}, upper bounds length: "
            f"{upper_bounds_length}."
        )
        self.lower_bounds_length = lower_bounds_length
        self.upper_bounds_length = upper_bounds_length

black_it.search_space.LowerBoundGreaterThanUpperBoundError (SearchSpaceError)

Raised when the lower bound of a parameter is greater than its upper bound.

Attributes:

Name Type Description
param_index int

0-based index of the parameter presenting the error

lower_bound float

lower bound. It will be higher than upper bound

upper_bound float

upper bound. It will be lower than lower bound

Source code in black_it/search_space.py
class LowerBoundGreaterThanUpperBoundError(SearchSpaceError):
    """Raised when the lower bound of a parameter is greater than its upper bound.

    Attributes:
        param_index (int): 0-based index of the parameter presenting the error
        lower_bound (float): lower bound. It will be higher than upper bound
        upper_bound (float): upper bound. It will be lower than lower bound
    """

    def __init__(  # noqa: D107
        self, param_index: int, lower_bound: float, upper_bound: float
    ) -> None:
        super().__init__(
            f"Parameter {param_index}'s lower bound ({lower_bound}) must be "
            f"lower than its upper bound ({upper_bound})."
        )
        self.param_index = param_index
        self.lower_bound = lower_bound
        self.upper_bound = upper_bound

black_it.search_space.PrecisionZeroError (SearchSpaceError)

Raised when a parameter precision is set to 0.

Attributes:

Name Type Description
param_index int

0-based index of the parameter presenting the error

Source code in black_it/search_space.py
class PrecisionZeroError(SearchSpaceError):
    """Raised when a parameter precision is set to 0.

    Attributes:
        param_index (int): 0-based index of the parameter presenting the error
    """

    def __init__(self, param_index: int) -> None:  # noqa: D107
        super().__init__(f"Parameter {param_index}'s precision cannot be zero.")
        self.param_index = param_index

black_it.search_space.SameLowerAndUpperBoundError (SearchSpaceError)

Raised when the lower and the upper bound of a parameter have the same value.

Attributes:

Name Type Description
param_index int

0-based index of the parameter presenting the error

bound_value float

common value of the parameter bound

Source code in black_it/search_space.py
class SameLowerAndUpperBoundError(SearchSpaceError):
    """Raised when the lower and the upper bound of a parameter have the same value.

    Attributes:
        param_index (int): 0-based index of the parameter presenting the error
        bound_value (float): common value of the parameter bound
    """

    def __init__(self, param_index: int, bound_value: float) -> None:  # noqa: D107
        super().__init__(
            f"Parameter {param_index}'s lower and upper bounds have the same "
            f"value ({bound_value}). This calibrator cannot handle that. "
            f"Please redefine externally your model in order to hardcode "
            f"parameter {param_index} to {bound_value}."
        )
        self.param_index = param_index
        self.bound_value = bound_value