Source code for baybe.objectives.single

"""Functionality for single-target objectives."""

from __future__ import annotations

import gc
from typing import TYPE_CHECKING, ClassVar

import pandas as pd
from attrs import define, field
from attrs.validators import instance_of
from typing_extensions import override

from baybe.exceptions import NonGaussianityError
from baybe.objectives.base import Objective
from baybe.targets.base import Target
from baybe.targets.numerical import NumericalTarget
from baybe.transformations.basic import AffineTransformation, IdentityTransformation
from baybe.utils.conversion import to_string
from baybe.utils.dataframe import pretty_print_df

if TYPE_CHECKING:
    from botorch.acquisition.objective import (
        MCAcquisitionObjective,
        ScalarizedPosteriorTransform,
    )


[docs] @define(frozen=True, slots=False) class SingleTargetObjective(Objective): """An objective focusing on a single target.""" is_multi_output: ClassVar[bool] = False # See base class. _target: Target = field(validator=instance_of(Target), alias="target") """The single target considered by the objective.""" @override def __str__(self) -> str: targets_list = [target.summary() for target in self.targets] targets_df = pd.DataFrame(targets_list) fields = [ to_string("Type", self.__class__.__name__, single_line=True), to_string("Targets", pretty_print_df(targets_df)), ] return to_string("Objective", *fields) @override @property def targets(self) -> tuple[Target, ...]: return (self._target,) @override @property def output_names(self) -> tuple[str, ...]: return (self._target.name,) @override @property def supports_partial_measurements(self) -> bool: return False
[docs] @override def to_botorch(self) -> MCAcquisitionObjective: from botorch.acquisition.objective import IdentityMCObjective from baybe.objectives.botorch import ChainedMCObjective if isinstance(self._target, NumericalTarget): return ChainedMCObjective(super().to_botorch(), IdentityMCObjective()) return IdentityMCObjective()
[docs] @override def to_botorch_posterior_transform(self) -> ScalarizedPosteriorTransform: if not ( isinstance((t := self._target), NumericalTarget) and isinstance( (tr := t.transformation), (IdentityTransformation, AffineTransformation) ) ): raise NonGaussianityError( f"Converting an objective of type '{type(self).__name__}' is only " f"possible when the transformation result is Gaussian, that is, " f"when the target is of type '{NumericalTarget.__name__}' and the " f"assigned transformation is affine." ) return (tr.negate() if t.minimize else tr).to_botorch_posterior_transform()
# Collect leftover original slotted classes processed by `attrs.define` gc.collect()