From 74cf06866cdcb6617fee6e02733fb5a6a3aa691b Mon Sep 17 00:00:00 2001 From: Martin Fitzner Date: Tue, 3 Sep 2024 18:03:17 +0200 Subject: [PATCH] Make pending experiment candidate exclusion configurable --- CHANGELOG.md | 3 +++ baybe/recommenders/naive.py | 9 --------- baybe/recommenders/pure/base.py | 13 +++++++++++-- baybe/recommenders/pure/nonpredictive/base.py | 11 +++++++---- tests/test_pending_experiments.py | 4 +++- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1495ccd9..39379b862 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Discrete search space Cartesian product can be created lazily via Polars - `recommend` now accepts the `pending_experiments` argument, informing the algorithm about points that were already selected for evaluation +- Pure recommenders now have the `allow_recommending_pending_experiments` flag, + controlling whether pending experiments are excluded from candidates in purely + discrete search spaces ### Changed - Passing an `Objective` to `Campaign` is now optional diff --git a/baybe/recommenders/naive.py b/baybe/recommenders/naive.py index f471b17cd..d8a20a953 100644 --- a/baybe/recommenders/naive.py +++ b/baybe/recommenders/naive.py @@ -6,7 +6,6 @@ import pandas as pd from attrs import define, evolve, field, fields -from baybe.exceptions import UnusedObjectWarning from baybe.objectives.base import Objective from baybe.recommenders.pure.base import PureRecommender from baybe.recommenders.pure.bayesian.base import BayesianRecommender @@ -114,14 +113,6 @@ def recommend( # noqa: D102 ) # We are in a hybrid setting now - if pending_experiments is not None: - raise UnusedObjectWarning( - f"Pending experiments were provided but the selected recommender " - f"'{self.__class__.__name__}' with discrete " - f"sub-recommender '{self.disc_recommender.__class__.__name__}' only " - f"utilizes this information for purely discrete spaces." - ) - # We will attach continuous parts to discrete parts and the other way round. # To make things simple, we sample a single point in the continuous space which # will then be attached to every discrete point when the acquisition function diff --git a/baybe/recommenders/pure/base.py b/baybe/recommenders/pure/base.py index 6c5021d90..183c7cda5 100644 --- a/baybe/recommenders/pure/base.py +++ b/baybe/recommenders/pure/base.py @@ -32,6 +32,11 @@ class PureRecommender(ABC, RecommenderProtocol): """Allow to make recommendations that were measured previously. This only has an influence in discrete search spaces.""" + allow_recommending_pending_experiments: bool = field(default=False, kw_only=True) + """Allow `pending_experiments` to be part of the recommendations. If set to `False`, + the corresponding points will be removed from the candidates. This only influences + recommendations for purely discrete search spaces.""" + def recommend( # noqa: D102 self, batch_size: int, @@ -182,13 +187,17 @@ def _recommend_with_discrete_parts( # Get discrete candidates # Repeated recommendations are always allowed for hybrid spaces - # Pending experiments are excluded for discrete spaces + # Pending experiments are excluded for discrete spaces unless configured + # differently. + dont_exclude_pending = ( + is_hybrid_space or self.allow_recommending_pending_experiments + ) _, candidates_comp = searchspace.discrete.get_candidates( allow_repeated_recommendations=is_hybrid_space or self.allow_repeated_recommendations, allow_recommending_already_measured=is_hybrid_space or self.allow_recommending_already_measured, - exclude=None if is_hybrid_space else pending_experiments, + exclude=None if dont_exclude_pending else pending_experiments, ) # Check if enough candidates are left diff --git a/baybe/recommenders/pure/nonpredictive/base.py b/baybe/recommenders/pure/nonpredictive/base.py index e061005ee..ec84c501b 100644 --- a/baybe/recommenders/pure/nonpredictive/base.py +++ b/baybe/recommenders/pure/nonpredictive/base.py @@ -4,6 +4,7 @@ from abc import ABC import pandas as pd +from attr import fields from attrs import define from baybe.exceptions import UnusedObjectWarning @@ -40,14 +41,16 @@ def recommend( # noqa: D102 f"consider any objectives, meaning that the argument is ignored.", UnusedObjectWarning, ) - if ( - pending_experiments is not None - and searchspace.type is not SearchSpaceType.DISCRETE + if (pending_experiments is not None) and ( + self.allow_recommending_pending_experiments + or searchspace.type is not SearchSpaceType.DISCRETE ): raise UnusedObjectWarning( f"Pending experiments were provided but the selected recommender " f"'{self.__class__.__name__}' only utilizes this information for " - f"purely discrete spaces." + f"purely discrete spaces and " + f"{fields(self.__class__).allow_recommending_pending_experiments.name}" + f"=False." ) return super().recommend( batch_size=batch_size, diff --git a/tests/test_pending_experiments.py b/tests/test_pending_experiments.py index 9d4ae49c1..234848ef0 100644 --- a/tests/test_pending_experiments.py +++ b/tests/test_pending_experiments.py @@ -28,7 +28,9 @@ # Repeated recommendations explicitly need to be allowed or the potential overlap will # be avoided trivially _flags = dict( - allow_repeated_recommendations=True, allow_recommending_already_measured=True + allow_repeated_recommendations=True, + allow_recommending_already_measured=True, + allow_recommending_pending_experiments=False, )