Skip to content

Add generality-oriented BO features - #902

Open
StefanPSchmid wants to merge 12 commits into
dev/recommenderfrom
dev/generality-bo
Open

Add generality-oriented BO features#902
StefanPSchmid wants to merge 12 commits into
dev/recommenderfrom
dev/generality-bo

Conversation

@StefanPSchmid

Copy link
Copy Markdown
Collaborator

Generality-BO integration into BayBE

This PR creates a first draft for generality-oriented BO (done in this paper https://arxiv.org/abs/2502.18966) within BayBE.

Main features/changes:

  • Introduce GeneralityParameter, which triggers generality-oriented BO (inspired by TaskParameter)
  • Add recommend_generality on the Recommender level
  • Introduce GeneralityModel and GeneralityPosterior, which are models that perform the Aggregation described in the paper, over which the optimizer can then run


__all__ = [
"AggregationFunction",
"MeanAggregation",

@StefanPSchmid StefanPSchmid Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the three relevant aggregation functions implemented in the paper;

Idea in those:

  • MeanAggregation: Optimize the average across all contexts
  • SigmoidAggregation: Optimize the number of contexts above a threshold
  • MinAggregation: Optimize the worst-case across all contexts

Not sure exactly how this is influenced by minimization (negation), and how to optimally bake this into the code (esp. Sigmoid and Min), but I tried accounting for that in the GeneralityModel



@define(frozen=True, slots=False)
class GeneralityParameter(_DiscreteLabelLikeParameter):

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GeneralityParameter, that should be used to trigger generality-oriented optimization within the recommenders; inspiration taken from the TaskParameter

Idea is to have a context (over which you want to be general), that can be encoded (not just OHE/INT, but any discrete parameter like Substance encoding)

numerical_measurements_must_be_within_tolerance=False,
)

if searchspace._generality_parameter is not None:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Routing to generality-oriented optimization within the recommender

Comment thread baybe/searchspace/core.py
assert len(params) == 1
return params[0]

def _split_by_generality(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this can be combined with some other funcitonality in the searchspace, but essentially this removes the generalityparameter from the searchspace, since in the first step we want to optimize over all parameters except the generality one

samples = self.rsample(torch.Size([self._n_variance_samples]))
return samples.var(dim=0)

def _aggregate(self, samples: Tensor) -> Tensor:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perform the aggregation step (i.e. applying the generality metric), and then forward over it ... r is the number of contexts, m is the number of objectives

from baybe.aggregation.base import AggregationFunction


class _GeneralityPosterior(Posterior):

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posterior with many properties from the joint posterior (over design-space and context parameter), but that also performs the aggregation

)

def _expand(self, X: Tensor) -> Tensor:
"""Pair each candidate with all context values for the base GP.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done to assess generality

_register_generality_sampler()


class _GeneralityModel(Model):

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

surrogate model for generality, that is based on the joint model but holds the GeneralityPosterior

Comment thread tests/test_generality.py

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude generated since I am not experienced with tests and I assume the design will still change a bit; tried to keep it as close to the tests with MagicMock as I could

objective=IdentityMCObjective(),
)

if not objective.is_multi_output:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since generality-oriented BO is in a partial monitoring scenario, the "best_f" needed for some of the acquisition functions is not clear. What we do in the paper is to take the SurrogateModel and find the best predicted generality (PosteriorMean) and take that as the best_f

This is not possible for multi-objective, since multiple points on the generality-pareto-front could be "best", and there is no clear way of defining what best_f could be

)
acqf_kwargs["best_f"] = best_f_scores.item()

user_attrs, _ = match_attributes(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should give the X-acquisition function of choice the required arguments

**{k: v for k, v in acqf_kwargs.items() if k in sig}
)

x_points, _ = recommender.optimizer(batch_size, botorch_acqf, x_subspace)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The optimization approach follows the "Sequential one-step lookahead" approach outlined in the paper, with a few differences. This approach was chosen because it performed well in our benchmarks, while being very fast (and Martin at one point mentioned to me that speed is important)

The workflow, with highlighted differences:

  • While the CurryBO implementation does batching by conditioning on points, here we first get a batch of X-points (which should all be distinct) at once, from the optimizer. Optimization is performed on the acquisition function on the GeneralityPosterior, which did the Aggregation over the different contexts already.

  • For each batch point, the recommender then fixes the search space, and picks a context (w) to optimize, by maximizing the standard deviation (picking the most uncertain / informative one). If I understood the code correctly, for discrete searchspaces, the (x,w) combinations that have already been measured should have been filtered out (if that flag is set to True) ... and for continuous/hybrid x-spaces, restricting (x,w) combinations makes no sense anyways

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file implements the generality-oriented optimization logic; some nomenclature:

x is the "design space", i.e. the space you want to find your maximum in (which is also the search space, with the GeneralityParameter removed).
w, or context, is the variable over which you want to be general (and over which you aggregate).

Happy to discuss about nomenclature / better naming

@@ -0,0 +1,138 @@
"""Generality recommendation logic for BayesianRecommender (CurryBO algorithm)."""

from __future__ import annotations

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few points I would also like to make you aware of / discuss:

  • How constraints could / should be handled. Especially for x,w combinations, currently not enabled. For the respective X space, constraints should work as implemented by the recommender, although the ones with subsets might need to be revisited (but as I understood from our previous discussions, that should get integrated into the optimizer?)

  • Due to the partial monitoring / generality-scenario, the best measurement is not automatically the optimum. In CurryBO, we thus not only predict the next point, but also the current optimum, based on the PosteriorMean of the generality function. I have no idea where such an additional part of the recommendation could be integrated, happy to hear your ideas! Thanks!

@StefanPSchmid
StefanPSchmid marked this pull request as ready for review August 21, 2026 08:57
Copilot AI lite review requested due to automatic review settings August 21, 2026 08:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a first integration of generality-oriented Bayesian optimization into BayBE by introducing a dedicated “context” parameter, context aggregation utilities, and a BoTorch model/posterior wrapper to optimize over designs while aggregating across contexts.

Changes:

  • Introduces GeneralityParameter and search space validation/splitting logic for separating design vs. generality context.
  • Adds a generality-aware recommendation path (recommend_generality) in the pure recommender stack.
  • Adds aggregation primitives and a BoTorch _GeneralityModel / _GeneralityPosterior, plus an initial test suite.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/test_generality.py Adds unit/wiring tests for aggregation, search space splitting/fixing, model expansion, posterior aggregation, and recommender wiring.
baybe/aggregation/init.py Exposes aggregation API for generality-oriented optimization.
baybe/aggregation/base.py Defines the AggregationFunction base class for context aggregation.
baybe/aggregation/aggregations.py Implements concrete aggregation strategies (mean/min/sigmoid).
baybe/parameters/categorical.py Adds GeneralityParameter (context parameter wrapper) and aggregation configuration.
baybe/parameters/enum.py Adds a new _ParameterKind.GENERALITY flag.
baybe/parameters/init.py Re-exports GeneralityParameter from baybe.parameters.
baybe/searchspace/validation.py Adds validation rules for GeneralityParameter count and Task/Generality incompatibility.
baybe/searchspace/core.py Adds _generality_parameter access and _split_by_generality() helper for context-aware optimization.
baybe/surrogates/generality.py Introduces _GeneralityModel and _GeneralityPosterior wrappers for context-aggregated posteriors.
baybe/recommenders/pure/bayesian/generality.py Implements recommend_generality() (two-step design-then-context selection).
baybe/recommenders/pure/bayesian/core.py Wires Bayesian recommender to dispatch into the generality recommendation path.
baybe/recommenders/pure/base.py Adds top-level dispatch for search spaces containing a generality parameter.
Suppressed comments (1)

baybe/parameters/enum.py:38

  • _ParameterKind.from_parameter() doesn’t map GeneralityParameter to _ParameterKind.GENERALITY, so GeneralityParameter instances will still be classified as REGULAR via Parameter._kind. This makes the newly added GENERALITY flag effectively unused and can break any compatibility logic relying on _kind.
    @staticmethod
    def from_parameter(parameter: Parameter) -> _ParameterKind:
        """Determine the kind of a parameter from its type."""
        from baybe.parameters.categorical import TaskParameter

        if isinstance(parameter, TaskParameter):
            return _ParameterKind.TASK
        return _ParameterKind.REGULAR

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 40 to 41
NotImplementedError: If more than one
:class:`baybe.parameters.categorical.TaskParameter` is requested.
Comment thread baybe/searchspace/core.py
Comment on lines +429 to +432
sample_row = self.discrete.exp_rep.head(1)
full_comp = self.transform(sample_row, allow_extra=True)
full_columns = list(full_comp.columns)

Comment on lines +93 to +118
@define(frozen=True, slots=False)
class GeneralityParameter(_DiscreteLabelLikeParameter):
"""Parameter marking a discrete parameter as the context dimension."""

context: DiscreteParameter = field()
"""Parameter for the contexts."""

aggregation: AggregationFunction = field(factory=MeanAggregation)
"""Aggregation mode over contexts."""

@property
def encoding(self):
"""The encoding of the context parameter."""
return self.context.encoding

@override
@property
def values(self) -> tuple:
"""The values of the context parameter."""
return self.context.values

@override
@cached_property
def comp_df(self) -> pd.DataFrame:
"""The comp_df of the context parameter."""
return self.context.comp_df
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants