"""Validation functionality for constraints."""fromcollections.abcimportCollectionfromitertoolsimportcombinationsfrombaybe.constraints.baseimportConstraintfrombaybe.constraints.continuousimportContinuousCardinalityConstraintfrombaybe.constraints.discreteimport(DiscreteDependenciesConstraint,)frombaybe.parametersimportNumericalContinuousParameterfrombaybe.parameters.baseimportParametertry:# For python < 3.11, use the exceptiongroup backportExceptionGroupexceptNameError:fromexceptiongroupimportExceptionGroup
[docs]defvalidate_constraints(# noqa: DOC101, DOC103constraints: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 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. """ifsum(isinstance(itm,DiscreteDependenciesConstraint)foritminconstraints)>1:raiseValueError(f"There is only one {DiscreteDependenciesConstraint.__name__} allowed. "f"Please specify all dependencies in one single constraint.")validate_cardinality_constraints_are_nonoverlapping([conforconinconstraintsifisinstance(con,ContinuousCardinalityConstraint)])param_names_all=[p.nameforpinparameters]param_names_discrete=[p.nameforpinparametersifp.is_discrete]param_names_continuous=[p.nameforpinparametersifp.is_continuous]param_names_non_numerical=[p.nameforpinparametersifnotp.is_numerical]params_continuous:list[NumericalContinuousParameter]=[pforpinparametersifisinstance(p,NumericalContinuousParameter)]forconstraintinconstraints:ifnotall(pinparam_names_allforpinconstraint.parameters):raiseValueError(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: {constraint.parameters}")ifconstraint.is_continuousandany(pinparam_names_discreteforpinconstraint.parameters):raiseValueError(f"You are trying to initialize a continuous constraint over a "f"parameter that is discrete. Parameter list of the affected "f"constraint: {constraint.parameters}")ifconstraint.is_discreteandany(pinparam_names_continuousforpinconstraint.parameters):raiseValueError(f"You are trying to initialize a discrete constraint over a parameter "f"that is continuous. Parameter list of the affected constraint: "f"{constraint.parameters}")ifconstraint.numerical_onlyandany(pinparam_names_non_numericalforpinconstraint.parameters):raiseValueError(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}.")ifisinstance(constraint,ContinuousCardinalityConstraint):validate_cardinality_constraint_parameter_bounds(constraint,params_continuous)
[docs]defvalidate_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. """forc1,c2incombinations(constraints,2):if(s1:=set(c1.parameters)).intersection(s2:=set(c2.parameters)):raiseValueError(f"Constraints of type `{ContinuousCardinalityConstraint.__name__}` "f"cannot share the same parameters. Found the following overlapping "f"parameter sets: {s1}, {s2}.")
[docs]defvalidate_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=[]fornameinconstraint.parameters:try:parameter=next(pforpinparametersifp.name==name)exceptStopIterationasex:raiseValueError(f"The parameter '{name}' referenced by the constraint is not contained "f"in the given collection of parameters.")fromexifnotparameter.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."))ifexceptions:iflen(exceptions)==1:raiseexceptions[0]raiseExceptionGroup("Invalid parameter bounds",exceptions)