Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
as public utilities
- `is_constrained` property removed from `SubspaceDiscrete`, `SubspaceContinuous`,
and `SearchSpace`
- `candidates_exp` argument removed from `SubspaceDiscrete.subset_masks`,
`SubspaceDiscrete.sample_subset_masks`, `SearchSpace.subsets`, and
`SearchSpace.sample_subsets`

### Added
- `narwhals` as a hard dependency
Expand All @@ -33,6 +36,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `SubspaceDiscrete.from_product` and `SubspaceDiscrete.from_simplex` now split
their `constraints` argument into filtering constraints (applied during construction)
and batch constraints (stored in `batch_constraints`)
- Internal search space and recommender logic simplified by reducing indirection and
argument passing between methods

### Fixed
- Deserialization with constructor selection now correctly respects converter settings
Expand Down
5 changes: 2 additions & 3 deletions baybe/campaign.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
from baybe.recommenders.meta.sequential import TwoPhaseMetaRecommender
from baybe.recommenders.pure.bayesian.base import BayesianRecommender
from baybe.recommenders.pure.nonpredictive.base import NonPredictiveRecommender
from baybe.searchspace._filtered import FilteredSubspaceDiscrete
from baybe.searchspace.core import (
SearchSpace,
SearchSpaceType,
Expand Down Expand Up @@ -613,8 +612,8 @@ def recommend(
)
searchspace = evolve(
self.searchspace,
discrete=FilteredSubspaceDiscrete.from_subspace(
self.searchspace.discrete, ~mask_todrop.to_numpy()
discrete=evolve(
self.searchspace.discrete, exp_rep=exp_rep.loc[~mask_todrop]
),
)
Comment thread
myrazma marked this conversation as resolved.
else:
Expand Down
4 changes: 0 additions & 4 deletions baybe/recommenders/naive.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,6 @@ def recommend(
cont_part = searchspace.continuous.sample_uniform(1)
cont_part_tensor = to_tensor(cont_part).unsqueeze(-2)

# Get discrete candidates
candidates_exp, _ = searchspace.discrete.get_candidates()

# We now check whether the discrete recommender is bayesian.
if isinstance(self.disc_recommender, BayesianRecommender):
# Get access to the recommenders acquisition function
Expand All @@ -115,7 +112,6 @@ def recommend(
# Call the private function of the discrete recommender and get the candidates
disc_rec = self.disc_recommender._recommend_discrete(
subspace_discrete=searchspace.discrete,
candidates_exp=candidates_exp,
batch_size=batch_size,
)

Expand Down
27 changes: 4 additions & 23 deletions baybe/recommenders/pure/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,25 +133,18 @@ def recommend(
subspace_continuous=searchspace.continuous, batch_size=batch_size
Comment thread
fabianliebig marked this conversation as resolved.
)
else:
return self._recommend_with_discrete_parts(
searchspace,
batch_size,
pending_experiments=pending_experiments,
)
return self._recommend_with_discrete_parts(searchspace, batch_size)
Comment thread
fabianliebig marked this conversation as resolved.

def _recommend_discrete(
self,
subspace_discrete: SubspaceDiscrete,
candidates_exp: pd.DataFrame,
batch_size: int,
) -> pd.DataFrame:
"""Generate recommendations from a discrete search space.

Args:
subspace_discrete: The discrete subspace from which to generate
recommendations.
candidates_exp: The experimental representation of all discrete candidate
points to be considered.
batch_size: The size of the recommendation batch.

Raises:
Expand All @@ -166,7 +159,6 @@ def _recommend_discrete(
try:
return self._recommend_hybrid(
searchspace=SearchSpace(discrete=subspace_discrete),
candidates_exp=candidates_exp,
batch_size=batch_size,
)
except NotImplementedError as exc:
Expand Down Expand Up @@ -202,7 +194,6 @@ def _recommend_continuous(
try:
return self._recommend_hybrid(
searchspace=SearchSpace(continuous=subspace_continuous),
candidates_exp=pd.DataFrame(),
batch_size=batch_size,
)
except NotImplementedError as exc:
Expand All @@ -218,7 +209,6 @@ def _recommend_continuous(
def _recommend_hybrid(
self,
searchspace: SearchSpace,
candidates_exp: pd.DataFrame,
batch_size: int,
) -> pd.DataFrame:
"""Generate recommendations from a hybrid search space.
Expand All @@ -230,8 +220,6 @@ def _recommend_hybrid(
Args:
searchspace: The hybrid search space from which to generate
recommendations.
candidates_exp: The experimental representation of all discrete candidate
points to be considered.
batch_size: The size of the recommendation batch.

Raises:
Expand All @@ -246,7 +234,6 @@ def _recommend_with_discrete_parts(
self,
searchspace: SearchSpace,
batch_size: int,
pending_experiments: pd.DataFrame | None,
) -> pd.DataFrame:
"""Obtain recommendations in search spaces with a discrete part.

Expand All @@ -256,7 +243,6 @@ def _recommend_with_discrete_parts(
Args:
searchspace: The search space from which to generate recommendations.
batch_size: The size of the recommendation batch.
pending_experiments: Pending experiments in experimental representation.

Returns:
A dataframe containing the recommendations as individual rows.
Expand All @@ -281,26 +267,21 @@ def _recommend_with_discrete_parts(
f"{constraint_types}."
)

# Get discrete candidates
candidates_exp, _ = searchspace.discrete.get_candidates()
Comment thread
AdrianSosic marked this conversation as resolved.

# TODO: Introduce new flag to recommend batches larger than the search space

# Check if enough candidates are left
# TODO [15917]: This check is not perfectly correct.
if (not is_hybrid_space) and (len(candidates_exp) < batch_size):
if (not is_hybrid_space) and (len(searchspace.discrete.exp_rep) < batch_size):
raise NotEnoughPointsLeftError(
f"Using the current settings, there are fewer than {batch_size} "
f"possible data points left to recommend."
)

# Get recommendations
if is_hybrid_space:
rec = self._recommend_hybrid(searchspace, candidates_exp, batch_size)
rec = self._recommend_hybrid(searchspace, batch_size)
else:
rec = self._recommend_discrete(
searchspace.discrete, candidates_exp, batch_size
)
rec = self._recommend_discrete(searchspace.discrete, batch_size)

# Return recommendations
return rec
Expand Down
22 changes: 4 additions & 18 deletions baybe/recommenders/pure/bayesian/botorch/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ def __str__(self) -> str:
def _recommend_discrete(
self,
subspace_discrete: SubspaceDiscrete,
candidates_exp: pd.DataFrame,
batch_size: int,
) -> pd.DataFrame:
"""Generate recommendations from a discrete search space.
Expand All @@ -167,21 +166,15 @@ def _recommend_discrete(
Args:
subspace_discrete: The discrete subspace from which to generate
recommendations.
candidates_exp: The experimental representation of all discrete candidate
points to be considered.
batch_size: The size of the recommendation batch.

Returns:
A dataframe containing the recommendations as a subset of rows from the
provided experimental representation.
"""
if subspace_discrete.n_subsets > 0:
return recommend_discrete_with_subsets(
self, subspace_discrete, candidates_exp, batch_size
)
return recommend_discrete_without_subsets(
self, subspace_discrete, candidates_exp, batch_size
)
return recommend_discrete_with_subsets(self, subspace_discrete, batch_size)
return recommend_discrete_without_subsets(self, subspace_discrete, batch_size)

@override
def _recommend_continuous(
Expand Down Expand Up @@ -221,7 +214,6 @@ def _recommend_continuous(
def _recommend_hybrid(
self,
searchspace: SearchSpace,
candidates_exp: pd.DataFrame,
batch_size: int,
) -> pd.DataFrame:
"""Generate recommendations from a hybrid search space.
Expand All @@ -231,20 +223,14 @@ def _recommend_hybrid(

Args:
searchspace: The search space in which the recommendations should be made.
candidates_exp: The experimental representation of the candidates
of the discrete subspace.
batch_size: The size of the calculated batch.

Returns:
The recommended points.
"""
if searchspace.n_subsets > 0:
return recommend_hybrid_with_subsets(
self, searchspace, candidates_exp, batch_size
)
return recommend_hybrid_without_subsets(
self, searchspace, candidates_exp, batch_size
)
return recommend_hybrid_with_subsets(self, searchspace, batch_size)
return recommend_hybrid_without_subsets(self, searchspace, batch_size)

def _optimize_over_subsets(
self,
Expand Down
27 changes: 12 additions & 15 deletions baybe/recommenders/pure/bayesian/botorch/discrete.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import numpy as np
import numpy.typing as npt
import pandas as pd
from attrs import evolve

from baybe.searchspace import SubspaceDiscrete
from baybe.utils.dataframe import to_tensor
Expand All @@ -21,7 +22,6 @@
def recommend_discrete_with_subsets(
recommender: BotorchRecommender,
subspace_discrete: SubspaceDiscrete,
candidates_exp: pd.DataFrame,
batch_size: int,
) -> pd.DataFrame:
"""Recommend from a discrete space with subset-generating constraints.
Expand All @@ -35,7 +35,6 @@ def recommend_discrete_with_subsets(
recommender: The recommender instance.
subspace_discrete: The discrete subspace from which to generate
recommendations.
candidates_exp: The experimental representation of candidates.
batch_size: The size of the recommendation batch.

Returns:
Expand All @@ -46,22 +45,23 @@ def recommend_discrete_with_subsets(

masks: Iterable[npt.NDArray[np.bool_]]
if subspace_discrete.n_subsets <= recommender.max_n_subsets:
masks = subspace_discrete.subset_masks(
candidates_exp, min_candidates=batch_size
)
masks = subspace_discrete.subset_masks(min_candidates=batch_size)
else:
masks = subspace_discrete.sample_subset_masks(
candidates_exp, recommender.max_n_subsets, min_candidates=batch_size
recommender.max_n_subsets,
min_candidates=batch_size,
)

def make_callable(
mask: np.ndarray,
) -> Callable[[], tuple[pd.DataFrame, Tensor]]:
def optimize() -> tuple[pd.DataFrame, Tensor]:
subset = candidates_exp.loc[mask]
subset_subspace = evolve(
subspace_discrete, exp_rep=subspace_discrete.exp_rep.loc[mask]
)

rec = recommend_discrete_without_subsets(
recommender, subspace_discrete, subset, batch_size
recommender, subset_subspace, batch_size
)

comp = subspace_discrete.transform(rec)
Expand All @@ -79,7 +79,6 @@ def optimize() -> tuple[pd.DataFrame, Tensor]:
def recommend_discrete_without_subsets(
recommender: BotorchRecommender,
subspace_discrete: SubspaceDiscrete,
candidates_exp: pd.DataFrame,
batch_size: int,
) -> pd.DataFrame:
"""Generate recommendations from a discrete search space.
Expand All @@ -88,8 +87,6 @@ def recommend_discrete_without_subsets(
recommender: The recommender instance.
subspace_discrete: The discrete subspace from which to generate
recommendations.
candidates_exp: The experimental representation of all discrete candidate
points to be considered.
batch_size: The size of the recommendation batch.

Raises:
Expand Down Expand Up @@ -120,13 +117,13 @@ def recommend_discrete_without_subsets(

from botorch.optim import optimize_acqf_discrete

# determine the next set of points to be tested
candidates_comp = subspace_discrete.transform(candidates_exp)
# Determine the next set of points to be tested
candidates_comp = subspace_discrete.comp_rep
points, _ = optimize_acqf_discrete(
recommender._botorch_acqf, batch_size, to_tensor(candidates_comp)
)

# retrieve the rows from the input dataframe corresponding to the selected points
# Retrieve the rows from the subspace corresponding to the selected points
# IMPROVE: The merging procedure is conceptually similar to what
# `SearchSpace._match_measurement_with_searchspace_indices` does, though using
# a simpler matching logic. When refactoring the SearchSpace class to
Expand All @@ -140,4 +137,4 @@ def recommend_discrete_without_subsets(
)["index"]
)

return candidates_exp.loc[idxs]
return subspace_discrete.exp_rep.loc[idxs]
Loading
Loading