Source code for baybe.constraints.validation
"""Validation functionality for constraints."""
from collections.abc import Collection
from itertools import combinations
from exceptiongroup import ExceptionGroup
from baybe.constraints.base import Constraint
from baybe.constraints.continuous import ContinuousCardinalityConstraint
from baybe.constraints.discrete import (
DiscreteBatchConstraint,
DiscreteDependenciesConstraint,
)
from baybe.parameters import NumericalContinuousParameter
from baybe.parameters.base import Parameter
[docs]
def validate_constraints( # noqa: DOC101, DOC103
constraints: Collection[Constraint], parameters: Collection[Parameter]
) -> None:
"""Assert that a given collection of constraints is valid.
Raises:
ValueError: If there is more than one
:class:`baybe.constraints.discrete.DiscreteDependenciesConstraint` declared.
ValueError: If any two continuous cardinality constraints have an overlapping
parameter set.
ValueError: If multiple batch constraints reference the same parameter.
ValueError: If any constraint contains an invalid parameter name.
ValueError: If any continuous constraint includes a discrete parameter.
ValueError: If any discrete constraint includes a continuous parameter.
ValueError: If any discrete constraint that is valid only for numerical
discrete parameters includes non-numerical discrete parameters.
ValueError: If any parameter affected by a cardinality constraint does
not include zero.
"""
if sum(isinstance(itm, DiscreteDependenciesConstraint) for itm in constraints) > 1:
raise ValueError(
f"There is only one {DiscreteDependenciesConstraint.__name__} allowed. "
f"Please specify all dependencies in one single constraint."
)
validate_cardinality_constraints_are_nonoverlapping(
[con for con in constraints if isinstance(con, ContinuousCardinalityConstraint)]
)
batch_param_names = [
c.parameters[0] for c in constraints if isinstance(c, DiscreteBatchConstraint)
]
if duplicates := {n for n in batch_param_names if batch_param_names.count(n) > 1}:
raise ValueError(
f"Multiple '{DiscreteBatchConstraint.__name__}' instances reference "
f"the same parameter(s): {duplicates}. Each parameter can have at "
f"most one batch constraint."
)
param_names_all = [p.name for p in parameters]
param_names_discrete = [p.name for p in parameters if p.is_discrete]
param_names_continuous = [p.name for p in parameters if p.is_continuous]
param_names_non_numerical = [p.name for p in parameters if not p.is_numerical]
params_continuous: list[NumericalContinuousParameter] = [
p for p in parameters if isinstance(p, NumericalContinuousParameter)
]
for constraint in constraints:
if not all(p in param_names_all for p in constraint._required_parameters):
raise ValueError(
f"You are trying to create a constraint with at least one parameter "
f"name that does not exist in the list of defined parameters. "
f"Parameter list of the affected constraint: "
f"{constraint._required_parameters}"
)
if constraint.is_continuous and any(
p in param_names_discrete for p in constraint._required_parameters
):
raise ValueError(
f"You are trying to initialize a continuous constraint over a "
f"parameter that is discrete. Parameter list of the affected "
f"constraint: {constraint._required_parameters}"
)
if constraint.is_discrete and any(
p in param_names_continuous for p in constraint._required_parameters
):
raise ValueError(
f"You are trying to initialize a discrete constraint over a parameter "
f"that is continuous. Parameter list of the affected constraint: "
f"{constraint._required_parameters}"
)
if constraint.numerical_only and any(
p in param_names_non_numerical for p in constraint.parameters
):
raise ValueError(
f"You are trying to initialize a constraint of type "
f"'{constraint.__class__.__name__}', which is valid only for numerical "
f"discrete parameters, over a non-numerical parameter. "
f"Parameter list of the affected constraint: {constraint.parameters}."
)
if isinstance(constraint, ContinuousCardinalityConstraint):
validate_cardinality_constraint_parameter_bounds(
constraint, params_continuous
)
[docs]
def validate_cardinality_constraints_are_nonoverlapping(
constraints: Collection[ContinuousCardinalityConstraint],
) -> None:
"""Validate that cardinality constraints are non-overlapping.
Args:
constraints: A collection of continuous cardinality constraints.
Raises:
ValueError: If any two continuous cardinality constraints have an overlapping
parameter set.
"""
for c1, c2 in combinations(constraints, 2):
if (s1 := set(c1.parameters)).intersection(s2 := set(c2.parameters)):
raise ValueError(
f"Constraints of type `{ContinuousCardinalityConstraint.__name__}` "
f"cannot share the same parameters. Found the following overlapping "
f"parameter sets: {s1}, {s2}."
)
[docs]
def validate_cardinality_constraint_parameter_bounds(
constraint: ContinuousCardinalityConstraint,
parameters: Collection[NumericalContinuousParameter],
) -> None:
"""Validate that all parameters of a continuous cardinality constraint include zero.
Args:
constraint: A continuous cardinality constraint.
parameters: A collection of parameters, including those affected by the
constraint.
Raises:
ValueError: If one of the affected parameters does not include zero.
ExceptionGroup: If several of the affected parameters do not include zero.
"""
exceptions = []
for name in constraint.parameters:
try:
parameter = next(p for p in parameters if p.name == name)
except StopIteration as ex:
raise ValueError(
f"The parameter '{name}' referenced by the constraint is not contained "
f"in the given collection of parameters."
) from ex
if not parameter.is_in_range(0.0):
exceptions.append(
ValueError(
f"The bounds of all parameters affected by a constraint of type "
f"'{ContinuousCardinalityConstraint.__name__}' must include zero, "
f"but the bounds of parameter '{name}' are "
f"{parameter.bounds.to_tuple()}, which may indicate unintended "
f"settings in your parameter definition. "
f"A parameter whose value range excludes zero trivially "
f"increases the cardinality of the resulting configuration by one. "
f"Therefore, if your parameter definitions are all correct, "
f"consider excluding the parameter from the constraint and "
f"reducing the cardinality limits by one accordingly."
)
)
if exceptions:
if len(exceptions) == 1:
raise exceptions[0]
raise ExceptionGroup("Invalid parameter bounds", exceptions)