Skip to content

Commit

Permalink
Rework signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
Scienfitz committed Jul 23, 2024
1 parent f3e4f89 commit 74101aa
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 89 deletions.
21 changes: 9 additions & 12 deletions baybe/recommenders/naive.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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
Expand Down Expand Up @@ -113,6 +114,13 @@ def recommend( # noqa: D102
)

# We are in a hybrid setting now
if pending_measurements is not None:
raise UnusedObjectWarning(
f"A non-empty set of pending measurements was provided, but the "
f"selected recommender {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
Expand All @@ -123,21 +131,11 @@ def recommend( # noqa: D102

# Get discrete candidates. The metadata flags are ignored since the search space
# is hybrid
# TODO Slight BOILERPLATE CODE, see recommender.py, ll. 47+
_, candidates_comp = searchspace.discrete.get_candidates(
allow_repeated_recommendations=True,
allow_recommending_already_measured=True,
)

# Transform pending measurements
if pending_measurements is None:
pending_comp_discrete = None
pending_comp_continuous = None
else:
pending_comp = searchspace.transform(pending_measurements)
pending_comp_discrete = pending_comp[candidates_comp.columns]
pending_comp_continuous = pending_comp[searchspace.continuous.param_names]

# We now check whether the discrete recommender is bayesian.
if isinstance(self.disc_recommender, BayesianRecommender):
# Get access to the recommenders acquisition function
Expand All @@ -160,7 +158,6 @@ def recommend( # noqa: D102
subspace_discrete=searchspace.discrete,
candidates_comp=candidates_comp,
batch_size=batch_size,
pending_comp=pending_comp_discrete,
)

# Get one random discrete point that will be attached when evaluating the
Expand All @@ -183,7 +180,7 @@ def recommend( # noqa: D102

# Call the private function of the continuous recommender
rec_cont = self.cont_recommender._recommend_continuous(
searchspace.continuous, batch_size, pending_comp_continuous
searchspace.continuous, batch_size
)

# Glue the solutions together and return them
Expand Down
37 changes: 6 additions & 31 deletions baybe/recommenders/pure/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,8 @@ def recommend( # noqa: D102
) -> pd.DataFrame:
# See base class
if searchspace.type is SearchSpaceType.CONTINUOUS:
# Transform pending measurements
pending_comp = (
None
if pending_measurements is None
else searchspace.transform(pending_measurements)
)

return self._recommend_continuous(
subspace_continuous=searchspace.continuous,
batch_size=batch_size,
pending_comp=pending_comp,
subspace_continuous=searchspace.continuous, batch_size=batch_size
)
else:
return self._recommend_with_discrete_parts(
Expand All @@ -66,7 +57,6 @@ def _recommend_discrete(
subspace_discrete: SubspaceDiscrete,
candidates_comp: pd.DataFrame,
batch_size: int,
pending_comp: pd.DataFrame | None,
) -> pd.Index:
"""Generate recommendations from a discrete search space.
Expand All @@ -76,7 +66,6 @@ def _recommend_discrete(
candidates_comp: The computational representation of all discrete candidate
points to be considered.
batch_size: The size of the recommendation batch.
pending_comp: Computational representation of pending measurements.
Raises:
NotImplementedError: If the function is not implemented by the child class.
Expand All @@ -92,7 +81,6 @@ def _recommend_discrete(
searchspace=SearchSpace(discrete=subspace_discrete),
candidates_comp=candidates_comp,
batch_size=batch_size,
pending_comp=pending_comp,
).index
except NotImplementedError as exc:
raise NotImplementedError(
Expand All @@ -108,15 +96,13 @@ def _recommend_continuous(
self,
subspace_continuous: SubspaceContinuous,
batch_size: int,
pending_comp: pd.DataFrame | None = None,
) -> pd.DataFrame:
"""Generate recommendations from a continuous search space.
Args:
subspace_continuous: The continuous subspace from which to generate
recommendations.
batch_size: The size of the recommendation batch.
pending_comp: Pending points in computational representation.
Raises:
NotImplementedError: If the function is not implemented by the child class.
Expand All @@ -131,7 +117,6 @@ def _recommend_continuous(
searchspace=SearchSpace(continuous=subspace_continuous),
candidates_comp=pd.DataFrame(),
batch_size=batch_size,
pending_comp=pending_comp,
)
except NotImplementedError as exc:
raise NotImplementedError(
Expand All @@ -148,7 +133,6 @@ def _recommend_hybrid(
searchspace: SearchSpace,
candidates_comp: pd.DataFrame,
batch_size: int,
pending_comp: pd.DataFrame | None,
) -> pd.DataFrame:
"""Generate recommendations from a hybrid search space.
Expand All @@ -162,8 +146,6 @@ def _recommend_hybrid(
candidates_comp: The computational representation of all discrete candidate
points to be considered.
batch_size: The size of the recommendation batch.
pending_comp: Pending points in computational representation.
Raises:
NotImplementedError: If the function is not implemented by the child class.
Expand All @@ -177,7 +159,7 @@ def _recommend_with_discrete_parts(
self,
searchspace: SearchSpace,
batch_size: int,
pending_measurements: pd.DataFrame | None = None,
pending_measurements: pd.DataFrame | None,
) -> pd.DataFrame:
"""Obtain recommendations in search spaces with a discrete part.
Expand All @@ -200,18 +182,13 @@ def _recommend_with_discrete_parts(

# Get discrete candidates
# Repeated recommendations are always allowed for hybrid spaces
# Pending points are excluded for discrete spaces
_, 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,
)

# Transform pending measurements
pending_comp = (
None
if pending_measurements is None
else searchspace.transform(pending_measurements)
exclude=None if is_hybrid_space else pending_measurements,
)

# Check if enough candidates are left
Expand All @@ -228,13 +205,11 @@ def _recommend_with_discrete_parts(

# Get recommendations
if is_hybrid_space:
rec = self._recommend_hybrid(
searchspace, candidates_comp, batch_size, pending_comp
)
rec = self._recommend_hybrid(searchspace, candidates_comp, batch_size)
idxs = rec.index
else:
idxs = self._recommend_discrete(
searchspace.discrete, candidates_comp, batch_size, pending_comp
searchspace.discrete, candidates_comp, batch_size
)
rec = searchspace.discrete.exp_rep.loc[idxs, :]

Expand Down
6 changes: 0 additions & 6 deletions baybe/recommenders/pure/bayesian/botorch.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ def _recommend_discrete(
subspace_discrete: SubspaceDiscrete,
candidates_comp: pd.DataFrame,
batch_size: int,
pending_comp: pd.DataFrame | None = None,
) -> pd.Index:
"""Generate recommendations from a discrete search space.
Expand All @@ -87,7 +86,6 @@ def _recommend_discrete(
candidates_comp: The computational representation of all discrete candidate
points to be considered.
batch_size: The size of the recommendation batch.
pending_comp: Computational representation of pending measurements.
Raises:
IncompatibleAcquisitionFunctionError: If a non-Monte Carlo acquisition
Expand Down Expand Up @@ -131,15 +129,13 @@ def _recommend_continuous(
self,
subspace_continuous: SubspaceContinuous,
batch_size: int,
pending_comp: pd.DataFrame | None = None,
) -> pd.DataFrame:
"""Generate recommendations from a continuous search space.
Args:
subspace_continuous: The continuous subspace from which to generate
recommendations.
batch_size: The size of the recommendation batch.
pending_comp: Computational representation of pending measurements.
Raises:
IncompatibleAcquisitionFunctionError: If a non-Monte Carlo acquisition
Expand Down Expand Up @@ -186,7 +182,6 @@ def _recommend_hybrid(
searchspace: SearchSpace,
candidates_comp: pd.DataFrame,
batch_size: int,
pending_comp: pd.DataFrame | None = None,
) -> pd.DataFrame:
"""Recommend points using the ``optimize_acqf_mixed`` function of BoTorch.
Expand All @@ -202,7 +197,6 @@ def _recommend_hybrid(
candidates_comp: The computational representation of the candidates
of the discrete subspace.
batch_size: The size of the calculated batch.
pending_comp: Computational representation of pending measurements.
Raises:
IncompatibleAcquisitionFunctionError: If a non-Monte Carlo acquisition
Expand Down
12 changes: 11 additions & 1 deletion baybe/recommenders/pure/nonpredictive/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from baybe.exceptions import UnusedObjectWarning
from baybe.objectives.base import Objective
from baybe.recommenders.pure.base import PureRecommender
from baybe.searchspace.core import SearchSpace
from baybe.searchspace.core import SearchSpace, SearchSpaceType


@define
Expand Down Expand Up @@ -40,9 +40,19 @@ def recommend( # noqa: D102
f"consider any objectives, meaning that the argument is ignored.",
UnusedObjectWarning,
)
if (
pending_measurements is not None
and searchspace.type is not SearchSpaceType.DISCRETE
):
raise UnusedObjectWarning(
f"A non-empty set of pending measurements was provided, but the "
f"selected recommender {self.__class__.__name__} only utilizes this "
f"information for purely discrete spaces."
)
return super().recommend(
batch_size=batch_size,
searchspace=searchspace,
objective=objective,
measurements=measurements,
pending_measurements=pending_measurements,
)
9 changes: 0 additions & 9 deletions baybe/recommenders/pure/nonpredictive/clustering.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@ def _recommend_discrete(
subspace_discrete: SubspaceDiscrete,
candidates_comp: pd.DataFrame,
batch_size: int,
pending_comp: pd.DataFrame | None = None,
) -> pd.Index:
# See base class.

Expand All @@ -107,14 +106,6 @@ def _recommend_discrete(
scaler = StandardScaler()
scaler.fit(subspace_discrete.comp_rep)

# Ignore exact pending point matches in the candidates
if pending_comp is not None:
candidates_comp = (
candidates_comp.merge(pending_comp, indicator=True, how="outer")
.query('_merge == "left_only"')
.drop(columns=["_merge"])
)

# Scale candidates
candidates_scaled = np.ascontiguousarray(scaler.transform(candidates_comp))

Expand Down
21 changes: 0 additions & 21 deletions baybe/recommenders/pure/nonpredictive/sampling.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
"""Recommenders based on sampling."""

import warnings
from typing import ClassVar

import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler

from baybe.exceptions import UnusedObjectWarning
from baybe.recommenders.pure.nonpredictive.base import NonPredictiveRecommender
from baybe.searchspace import SearchSpace, SearchSpaceType, SubspaceDiscrete
from baybe.utils.sampling_algorithms import farthest_point_sampling
Expand All @@ -25,18 +23,8 @@ def _recommend_hybrid(
searchspace: SearchSpace,
candidates_comp: pd.DataFrame,
batch_size: int,
pending_comp: pd.DataFrame | None = None,
) -> pd.DataFrame:
# See base class.

if (pending_comp is not None) and (len(pending_comp) != 0):
warnings.warn(
f"'{self.recommend.__name__}' was called with a non-empty "
f"set of pending measurements but '{self.__class__.__name__}' does not "
f"utilize this information, meaning that the argument is ignored.",
UnusedObjectWarning,
)

if searchspace.type == SearchSpaceType.DISCRETE:
return candidates_comp.sample(batch_size)

Expand Down Expand Up @@ -69,7 +57,6 @@ def _recommend_discrete(
subspace_discrete: SubspaceDiscrete,
candidates_comp: pd.DataFrame,
batch_size: int,
pending_comp: pd.DataFrame | None = None,
) -> pd.Index:
# See base class.

Expand All @@ -78,14 +65,6 @@ def _recommend_discrete(
scaler = StandardScaler()
scaler.fit(subspace_discrete.comp_rep)

# Ignore exact pending point matches in the candidates
if pending_comp is not None:
candidates_comp = (
candidates_comp.merge(pending_comp, indicator=True, how="outer")
.query('_merge == "left_only"')
.drop(columns=["_merge"])
)

# Scale and sample
candidates_scaled = np.ascontiguousarray(scaler.transform(candidates_comp))
ilocs = farthest_point_sampling(candidates_scaled, batch_size)
Expand Down
9 changes: 9 additions & 0 deletions baybe/searchspace/discrete.py
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,7 @@ def get_candidates(
self,
allow_repeated_recommendations: bool = False,
allow_recommending_already_measured: bool = False,
exclude: pd.DataFrame | None = None,
) -> tuple[pd.DataFrame, pd.DataFrame]:
"""Return the set of candidate parameter settings that can be tested.
Expand All @@ -652,6 +653,8 @@ def get_candidates(
allow_recommending_already_measured: If ``True``, parameters settings for
which there are already target values available are still considered as
valid candidates.
exclude: Points in experimental representation that should be excluded as
candidates.
Returns:
The candidate parameter settings both in experimental and computational
Expand All @@ -664,6 +667,12 @@ def get_candidates(
if not allow_recommending_already_measured:
mask_todrop |= self.metadata["was_measured"]

# Remove additional excludes
if exclude is not None:
mask_todrop |= pd.merge(self.exp_rep, exclude, indicator=True, how="left")[
"_merge"
].eq("both")

return self.exp_rep.loc[~mask_todrop], self.comp_rep.loc[~mask_todrop]

def transform(
Expand Down
Loading

0 comments on commit 74101aa

Please sign in to comment.