Problem constraints#
The Problem class provides a check_constraints method to validate input parameters.
So that it works, problems have to declare the constraints for their parameters in their Conditions class member (which itself is a dataclass).
Constraints can have the following categories:
- engibench.constraint.THEORY = <Category.Theory: 1>#
The constraint is not known to cause a runtime error, values outside of the constraint domain are unphysical and might lead to unphysical domains.
- engibench.constraint.IMPL = <Category.Implementation: 2>#
Violating the constraint, will cause runtime errors / undefined behavior due to the implementation.
A constraint can have more than one category. The | operator can be used to combine categories.
On top of categories, constraints have a criticality level (Error by default)
- class engibench.constraint.Criticality(value)[source]#
Criticality of a constraint violation.
- Error = 1#
- Warning = 2#
There are 2 ways to declare a constraint:
Simple constraint, only constraining a single parameter#
Use typing.Annotated,
where the annotation is one or multiple Constraint objects.
Predefined constraints are:
- constraint.bounded(*, upper: T | None = None) Constraint#
Create a constraint which checks that the specified parameter is contained in an interval [lower, upper].
- constraint.less_than() Constraint#
Create a constraint which checks that the specified parameter is less than upper.
- constraint.greater_than() Constraint#
Create a constraint which checks that the specified parameter is greater than lower.
Example:
@dataclass
class Conditions:
"""Conditions."""
volfrac: Annotated[
float,
bounded(lower=0.0, upper=1.0).category(THEORY),
bounded(lower=0.1, upper=0.9).warning().category(IMPL),
] = 0.35
Here, we declare a THEORY/Error constraint and a IMPL/Warning constraint for the volfrac parameter.
Constraint which also may affect more than one parameter#
Add a static method, decorated with the @constraint decorator.
Example:
@dataclass
class Config(Conditions):
"""Structured representation of conditions."""
rmin: float = 2.0
nelx: int = 100
nely: int = 50
@constraint
@staticmethod
def rmin_bound(rmin: float, nelx: int, nely: int) -> None:
"""Constraint for rmin ∈ (0.0, max{ nelx, nely }]."""
assert 0 < rmin <= max(nelx, nely), f"Params.rmin: {rmin} ∉ (0, max(nelx, nely)]"
This declares a constraint for the 3 parameters (rmin, nelx, nely) with custom logic. This constraint does not have any category.
If we would want to add a category, @constraint could be replaced by @constraint(category=ERROR) for example.
Example: Beams2D#
Here, we provide a concrete example for the Beams2D problem that illustrates when to use certain constraints over others.
The case for a
THEORY/Errorconstraint: We know that for a topology optimization problem like Beams2D, the volume fractionvolfracmust be defined between 0 and 1 because the design space must be somewhere between empty void (0) and completely filled with solid material (1). Therefore, this must be our theoretical bound and we declarebounded(lower=0.0, upper=1.0).category(THEORY)as an argument when definingvolfrac. Violating this constraint will throw an error, because it is physically impossible to have a volume fraction outside of this range.The case for a
IMPL/Warningconstraint: Through experimentation, we have found that values ofvolfracbelow 0.1 lead to solver instability and high compliance values, as there is not enough material available for the optimizer to provide adequate support against the applied force. We have also found that values above 0.9 do not provide any structurally meaningful solutions, since the design space can be almost completely filled with solid material. Therefore, we recommend users to stay within these practical implementation bounds, declaringbounded(lower=0.1, upper=0.9).warning().category(IMPL)as another argument ofvolfrac. Violating this constraint will produce a warning summarizing the above explanation, but allow the user to continue.
Writing custom constraints#
Using custom logic using the @constraint form is straight forward - as shown in the above example with the rmin_bound constraint.
But also custom logic inside Annotated[] is supported. Every Constraint
objects object inside Annotated[] is considered a constraint.
The simplest case is an unparametrized constraint:
from engibench.constraint import Constraint
non_zero = Constraint(lambda value: value != 0)
Parameterizable constraints are defined as functions returning Constraint objects:
from engibench.constraint import Constraint
def not_equal_to(value: int, /) -> Constraint:
"""Create a constraint which checks that the specified parameter is not equal to `value`."""
def check(actual_value: int) -> None:
assert actual_value != value, f"value == {value}"
return Constraint(check)
API#
- engibench.constraint.constraint(check: Callable[[...], None], /) Constraint[source]#
- engibench.constraint.constraint(*, categories: Category = UNCATEGORIZED, criticality: Criticality = Criticality.Error) Callable[[Callable[[...], None]], Constraint]
Decorator for check callbacks to convert the callback to a
Constraint.
- class engibench.constraint.Constraint(check: ~collections.abc.Callable[[...], None], categories: ~engibench.constraint.Category = <Category: 0>, criticality: ~engibench.constraint.Criticality = Criticality.Error)[source]#
Constraint for parameters passed to e.g.
engibench.core.Problem.optimize().- categories: Category = 0#
Categories of the constraint.
- category(category: Category) Constraint[source]#
Return a copy of the constraint which has the specified category added.
- check: Callable[[...], None]#
Check callback raising an AssertError if the constraint is violated.
- check_dict(parameter_args: dict[str, Any]) Violation | None[source]#
Check for a violation of the given constraint for the given parameters.
- check_value(value: Any) Violation | None[source]#
Check for a violation for the given single positional value.
- criticality: Criticality = 1#
Criticality of a violation of the constraint.
- warning() Constraint[source]#
Return a copy of the constraint with the criticality level set to “warning”.