Skip to content

[FEATURE REQUEST]: drop single-point bounds on domain objectives, because they lead to unintelligible errors downstream #690

@rosonaeldred

Description

@rosonaeldred

Motivation

Downstream, sometimes have had a user set bounds on a ContinuousOutput feature. If the bounds do not have identical left and righthand sides, then they are generally ignored.

ParEGO/botorch errors out in searching when the bounds reduce the space to have nothing in it.
This will happen due to human error if the bounds get set to constant, e.g.

bounds=(0.0, 0.0)

Error:

RuntimeError, probability tensor contains either inf, nan or element < 0'

Example of automatic assignment of bounds on Outputs to (0,1)

Notice there's no assigned bounds on the Continuous outptu:

domain = Domain.from_lists(
    [
        ContinuousInput(key="component_1", bounds=(0, 10)),
        ContinuousInput(key="component_2", bounds=(0, 10)),
        ContinuousInput(key="component_3", bounds=(0, 10)),
    ],
    [
        ContinuousOutput(key="viscosity", objective=MinimizeObjective()),
        ContinuousOutput(key="hardness", objective=MaximizeObjective()),
    ],
)
s = domain.model_dump_json(indent=2)

#| output: asis
print(f"```json\n{s}\n```")

Dumping to json, you can see they were assigned to bounds=(0,1)
Relevant part of the json:

  "outputs": {
    "type": "Outputs",
    "features": [
      {
        "type": "ContinuousOutput",
        "key": "viscosity",
        "unit": null,
        "objective": {
          "type": "MinimizeObjective",
          "w": 1.0,
          "bounds": [
            0.0,
            1.0
          ]
       }
]

Now assume someone provides a json domain definition with

     {
        "type": "ContinuousOutput",
        "key": "viscosity",
        "unit": null,
        "objective": {
          "type": "MinimizeObjective",
          "w": 1.0,
          "bounds": [
            0.0,
            0.0
          ]
       }
]

Where are bounds set anyway?

MaximizeObjective and MinimizeObjective inherit from IdentityObjective, which explicitly sets Bounds =(0,1)

class IdentityObjective(Objective):
"""An objective returning the identity as reward.
The return can be scaled, when a lower and upper bound are provided.
Attributes:
w (float): float between zero and one for weighting the objective
bounds (Tuple[float], optional): Bound for normalizing the objective between zero and one. Defaults to (0,1).
"""
type: Literal["IdentityObjective"] = "IdentityObjective" # type: ignore
w: TWeight = 1
bounds: Bounds = [0, 1]

Describe the solution you'd like to see implemented in BoFire.

I think if the validator had a more strict requirement than monotonically increasing, that should do it.

Bounds = Annotated[
Union[List[float], Tuple[float, float]],
Field(min_length=2, max_length=2),
AfterValidator(validate_monotonically_increasing),
]

STRONGER Solution: drop the bounds on objectives entirely.

[Obviously, will not fix all problems about search spaces being empty, but will catch some problems.]

Describe any alternatives you've considered to the above solution.

Minimally, check the domain for inconsistencies like this and then raise a verbose error.

Is this related to an existing issue in BoFire or another repository? If so please include links to those issues here.

No response

Pull Request

None

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions