diff --git a/CHANGELOG.md b/CHANGELOG.md index 96fbfbab76..a21b5e7172 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/baybe/campaign.py b/baybe/campaign.py index bc5ff82b3f..5ccf45356c 100644 --- a/baybe/campaign.py +++ b/baybe/campaign.py @@ -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, @@ -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] ), ) else: diff --git a/baybe/recommenders/naive.py b/baybe/recommenders/naive.py index 4887e4f0ec..04800c7db2 100644 --- a/baybe/recommenders/naive.py +++ b/baybe/recommenders/naive.py @@ -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 @@ -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, ) diff --git a/baybe/recommenders/pure/base.py b/baybe/recommenders/pure/base.py index 7eb11f0f9a..493a086710 100644 --- a/baybe/recommenders/pure/base.py +++ b/baybe/recommenders/pure/base.py @@ -133,16 +133,11 @@ def recommend( subspace_continuous=searchspace.continuous, batch_size=batch_size ) else: - return self._recommend_with_discrete_parts( - searchspace, - batch_size, - pending_experiments=pending_experiments, - ) + return self._recommend_with_discrete_parts(searchspace, batch_size) def _recommend_discrete( self, subspace_discrete: SubspaceDiscrete, - candidates_exp: pd.DataFrame, batch_size: int, ) -> pd.DataFrame: """Generate recommendations from a discrete search space. @@ -150,8 +145,6 @@ 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. Raises: @@ -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: @@ -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: @@ -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. @@ -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: @@ -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. @@ -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. @@ -281,14 +267,11 @@ def _recommend_with_discrete_parts( f"{constraint_types}." ) - # Get discrete candidates - candidates_exp, _ = searchspace.discrete.get_candidates() - # 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." @@ -296,11 +279,9 @@ def _recommend_with_discrete_parts( # 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 diff --git a/baybe/recommenders/pure/bayesian/botorch/core.py b/baybe/recommenders/pure/bayesian/botorch/core.py index d2bd8489b4..b420e3b88b 100644 --- a/baybe/recommenders/pure/bayesian/botorch/core.py +++ b/baybe/recommenders/pure/bayesian/botorch/core.py @@ -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. @@ -167,8 +166,6 @@ 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: @@ -176,12 +173,8 @@ def _recommend_discrete( 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( @@ -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. @@ -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, diff --git a/baybe/recommenders/pure/bayesian/botorch/discrete.py b/baybe/recommenders/pure/bayesian/botorch/discrete.py index 086ccc24aa..24551a3fee 100644 --- a/baybe/recommenders/pure/bayesian/botorch/discrete.py +++ b/baybe/recommenders/pure/bayesian/botorch/discrete.py @@ -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 @@ -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. @@ -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: @@ -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) @@ -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. @@ -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: @@ -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 @@ -140,4 +137,4 @@ def recommend_discrete_without_subsets( )["index"] ) - return candidates_exp.loc[idxs] + return subspace_discrete.exp_rep.loc[idxs] diff --git a/baybe/recommenders/pure/bayesian/botorch/hybrid.py b/baybe/recommenders/pure/bayesian/botorch/hybrid.py index 0b017a92dd..0c81339b25 100644 --- a/baybe/recommenders/pure/bayesian/botorch/hybrid.py +++ b/baybe/recommenders/pure/bayesian/botorch/hybrid.py @@ -9,6 +9,7 @@ import numpy as np import pandas as pd +from attrs import evolve from baybe.constraints.utils import is_cardinality_fulfilled from baybe.exceptions import ( @@ -30,7 +31,6 @@ def recommend_hybrid_without_subsets( recommender: BotorchRecommender, searchspace: SearchSpace, - candidates_exp: pd.DataFrame, batch_size: int, ) -> pd.DataFrame: """Recommend points using the ``optimize_acqf_mixed`` function of BoTorch. @@ -49,8 +49,6 @@ def recommend_hybrid_without_subsets( Args: recommender: The recommender instance. 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. Raises: @@ -83,7 +81,8 @@ def recommend_hybrid_without_subsets( from botorch.optim import optimize_acqf_mixed # Transform discrete candidates - candidates_comp = searchspace.discrete.transform(candidates_exp) + # (Create a shallow copy to avoid in-place modifications of the original dataframe) + candidates_comp = searchspace.discrete.comp_rep.copy(deep=False) # Calculate the number of samples from the given percentage n_candidates = math.ceil( @@ -164,7 +163,6 @@ def recommend_hybrid_without_subsets( def recommend_hybrid_with_subsets( recommender: BotorchRecommender, searchspace: SearchSpace, - candidates_exp: pd.DataFrame, batch_size: int, ) -> pd.DataFrame: """Recommend from a hybrid space with subset constraints. @@ -177,15 +175,11 @@ def recommend_hybrid_with_subsets( Args: recommender: The recommender instance. 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. """ - from attrs import evolve - subspace_c = searchspace.continuous # Get combined configurations, capped at max_n_subsets @@ -194,11 +188,9 @@ def recommend_hybrid_with_subsets( # discrete candidate by varying continuous parameters. combined_masks: Iterable[tuple[np.ndarray, frozenset[str]]] if searchspace.n_subsets <= recommender.max_n_subsets: - combined_masks = searchspace.subsets(candidates_exp) + combined_masks = searchspace.subsets() else: - combined_masks = searchspace.sample_subsets( - candidates_exp, recommender.max_n_subsets - ) + combined_masks = searchspace.sample_subsets(recommender.max_n_subsets) def make_callable( d_mask: np.ndarray, @@ -207,18 +199,21 @@ def make_callable( def optimize() -> tuple[pd.DataFrame, Tensor]: import torch - subset = candidates_exp.loc[d_mask] - - if c_inactive_params: - mod_cont = subspace_c._enforce_cardinality_constraints( - c_inactive_params - ) - else: - mod_cont = subspace_c - mod_searchspace = evolve(searchspace, continuous=mod_cont) + mod_disc = evolve( + searchspace.discrete, + exp_rep=searchspace.discrete.exp_rep.loc[d_mask], + ) + mod_cont = ( + subspace_c._enforce_cardinality_constraints(c_inactive_params) + if c_inactive_params + else subspace_c + ) + mod_searchspace = evolve( + searchspace, discrete=mod_disc, continuous=mod_cont + ) rec = recommend_hybrid_without_subsets( - recommender, mod_searchspace, subset, batch_size + recommender, mod_searchspace, batch_size ) comp = mod_searchspace.transform(rec) diff --git a/baybe/recommenders/pure/nonpredictive/clustering.py b/baybe/recommenders/pure/nonpredictive/clustering.py index 8225e2af0f..5a23c44afd 100644 --- a/baybe/recommenders/pure/nonpredictive/clustering.py +++ b/baybe/recommenders/pure/nonpredictive/clustering.py @@ -101,7 +101,6 @@ def _make_selection_custom( def _recommend_discrete( self, subspace_discrete: SubspaceDiscrete, - candidates_exp: pd.DataFrame, batch_size: int, ) -> pd.DataFrame: # Fit scaler on entire search space @@ -112,8 +111,9 @@ def _recommend_discrete( scaler.fit(subspace_discrete.comp_rep) # Scale candidates - candidates_comp = subspace_discrete.transform(candidates_exp) - candidates_scaled = np.ascontiguousarray(scaler.transform(candidates_comp)) + candidates_scaled = np.ascontiguousarray( + scaler.transform(subspace_discrete.comp_rep) + ) # Set model parameters and perform fit model = self._get_model_cls()( @@ -129,7 +129,7 @@ def _recommend_discrete( selection = self._make_selection_default(model, candidates_scaled) # Select rows by positional indices and return the corresponding subset - return candidates_exp.iloc[selection] + return subspace_discrete.exp_rep.iloc[selection] @override def __str__(self) -> str: diff --git a/baybe/recommenders/pure/nonpredictive/sampling.py b/baybe/recommenders/pure/nonpredictive/sampling.py index 7320b9bdf0..534fa1922c 100644 --- a/baybe/recommenders/pure/nonpredictive/sampling.py +++ b/baybe/recommenders/pure/nonpredictive/sampling.py @@ -31,7 +31,6 @@ class RandomRecommender(NonPredictiveRecommender): def _recommend_hybrid( self, searchspace: SearchSpace, - candidates_exp: pd.DataFrame, batch_size: int, ) -> pd.DataFrame: is_hybrid = searchspace.type is SearchSpaceType.HYBRID @@ -42,10 +41,11 @@ def _recommend_hybrid( if searchspace.type is SearchSpaceType.CONTINUOUS: return cont_random + candidates_exp = searchspace.discrete.exp_rep + # Restrict to a random subset if subset-generating constraints are present if searchspace.discrete.n_subsets > 0: masks = searchspace.discrete.sample_subset_masks( - candidates_exp, n=1, min_candidates=None if is_hybrid else batch_size, ) @@ -146,7 +146,6 @@ def _validate_random_tie_break(self, _, value): def _recommend_discrete( self, subspace_discrete: SubspaceDiscrete, - candidates_exp: pd.DataFrame, batch_size: int, ) -> pd.DataFrame: # Fit scaler on entire search space @@ -157,7 +156,7 @@ def _recommend_discrete( scaler.fit(subspace_discrete.comp_rep) # Scale and sample - candidates_comp = subspace_discrete.transform(candidates_exp) + candidates_comp = subspace_discrete.comp_rep candidates_scaled = np.ascontiguousarray(scaler.transform(candidates_comp)) if active_settings.use_fpsample: @@ -174,7 +173,7 @@ def _recommend_discrete( initialization=self.initialization.value, random_tie_break=self.random_tie_break, ) - return candidates_exp.iloc[ilocs] + return subspace_discrete.exp_rep.iloc[ilocs] @override def __str__(self) -> str: diff --git a/baybe/searchspace/_filtered.py b/baybe/searchspace/_filtered.py deleted file mode 100644 index f242e1c3bb..0000000000 --- a/baybe/searchspace/_filtered.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Search spaces with metadata.""" - -import numpy as np -import numpy.typing as npt -import pandas as pd -from attrs import asdict, cmp_using, define, field -from attrs.validators import instance_of -from typing_extensions import Self, override - -from baybe.searchspace import SubspaceDiscrete - - -@define -class FilteredSubspaceDiscrete(SubspaceDiscrete): - """A filtered search space representing a reduced candidate set.""" - - mask_keep: npt.NDArray[np.bool_] = field( - validator=instance_of(np.ndarray), - kw_only=True, - eq=cmp_using(eq=np.array_equal), - ) - """The filtering mask. ``True`` marks elements to be kept.""" - - @mask_keep.validator - def _validate_mask_keep(self, _, value) -> None: - if not len(value) == len(self.exp_rep): - raise ValueError("Filter mask must match the size of the space.") - if not value.dtype == bool: - raise ValueError("Filter mask must only contain Boolean values.") - - @classmethod - def from_subspace( - cls, subspace: SubspaceDiscrete, mask_keep: npt.NDArray[np.bool_] - ) -> Self: - """Filter an existing subspace.""" - kwargs = asdict(subspace, filter=lambda attr, _: attr.init, recurse=False) - - # >>>>> Deprecation - kwargs.pop("_empty_encoding", None) - kwargs.pop("_constraints", None) - kwargs.pop("_comp_rep", None) - # <<<<< Deprecation - - return cls(**kwargs, mask_keep=mask_keep) - - @override - def get_candidates(self) -> tuple[pd.DataFrame, pd.DataFrame]: - return self.exp_rep.loc[self.mask_keep], self.comp_rep.loc[self.mask_keep] diff --git a/baybe/searchspace/core.py b/baybe/searchspace/core.py index 06d9dbf274..2ec217da23 100644 --- a/baybe/searchspace/core.py +++ b/baybe/searchspace/core.py @@ -18,7 +18,7 @@ from baybe.constraints.base import Constraint from baybe.exceptions import InfeasibilityError from baybe.parameters import TaskParameter -from baybe.parameters.base import Parameter +from baybe.parameters.base import ContinuousParameter, DiscreteParameter, Parameter from baybe.searchspace.continuous import SubspaceContinuous from baybe.searchspace.discrete import ( MemorySize, @@ -293,7 +293,6 @@ def n_subsets(self) -> int: def subsets( self, - candidates_exp: pd.DataFrame, min_discrete_candidates: int | None = None, ) -> Iterator[tuple[npt.NDArray[np.bool_], frozenset[str]]]: r"""Get an iterator over all combined subset configurations. @@ -302,7 +301,6 @@ def subsets( configurations. Args: - candidates_exp: The experimental representation of discrete candidates. min_discrete_candidates: If provided, discrete Subsets with fewer matching candidates are skipped. @@ -310,15 +308,12 @@ def subsets( A discrete mask and continuous inactive parameters pair. """ yield from product( - self.discrete.subset_masks( - candidates_exp, min_candidates=min_discrete_candidates - ), + self.discrete.subset_masks(min_candidates=min_discrete_candidates), self.continuous.inactive_parameter_combinations(), ) def sample_subsets( self, - candidates_exp: pd.DataFrame, n: int, min_discrete_candidates: int | None = None, *, @@ -331,7 +326,6 @@ def sample_subsets( Duplicate pairs are skipped. Args: - candidates_exp: The experimental representation of discrete candidates. n: Number of unique configurations to sample. min_discrete_candidates: If provided, discrete Subsets with fewer matching candidates are excluded. @@ -346,7 +340,6 @@ def sample_subsets( A list of ``(discrete_mask, continuous_inactive_params)`` tuples. """ d_iter = self.discrete.subset_masks( - candidates_exp, min_candidates=min_discrete_candidates, mode="replace", ) @@ -539,26 +532,21 @@ def _drop_parameters(self, names: Collection[str], /) -> _ReducedSearchSpace: ) remaining = [p for p in self.parameters if p.name not in names_set] - disc_params = [p for p in remaining if p.is_discrete] - cont_params = [p for p in remaining if p.is_continuous] + disc_params = [p for p in remaining if isinstance(p, DiscreteParameter)] + cont_params = [p for p in remaining if isinstance(p, ContinuousParameter)] # Explicit comp_rep needed because transform() drops columns for empty inputs. discrete = ( SubspaceDiscrete( parameters=disc_params, exp_rep=pd.DataFrame(columns=[p.name for p in disc_params]), - comp_rep=pd.DataFrame( - columns=[c for p in disc_params for c in p.comp_rep_columns] - ), ) if disc_params else SubspaceDiscrete.empty() ) continuous = ( - SubspaceContinuous( - parameters=cont_params, - ) + SubspaceContinuous(parameters=cont_params) if cont_params else SubspaceContinuous.empty() ) diff --git a/baybe/searchspace/discrete.py b/baybe/searchspace/discrete.py index 9f5270c98c..c0a21df74a 100644 --- a/baybe/searchspace/discrete.py +++ b/baybe/searchspace/discrete.py @@ -712,7 +712,6 @@ def n_subsets(self) -> int: def subset_masks( self, - candidates_exp: pd.DataFrame, min_candidates: int | None = None, mode: Literal["sequential", "shuffled", "replace"] = "shuffled", ) -> Iterator[npt.NDArray[np.bool_]]: @@ -723,7 +722,6 @@ def subset_masks( combined masks. Args: - candidates_exp: The experimental representation of candidate points. min_candidates: If provided, combined masks selecting fewer rows are silently skipped. mode: The iteration strategy. @@ -744,10 +742,10 @@ def subset_masks( per_constraint: list[list[npt.NDArray[np.bool_]]] if not self.batch_constraints: - per_constraint = [[np.ones(len(candidates_exp), dtype=bool)]] + per_constraint = [[np.ones(len(self.exp_rep), dtype=bool)]] else: per_constraint = [ - c.subset_masks(candidates_exp) for c in self.batch_constraints + c.subset_masks(self.exp_rep) for c in self.batch_constraints ] total = prod(len(masks) for masks in per_constraint) @@ -779,14 +777,12 @@ def subset_masks( def sample_subset_masks( self, - candidates_exp: pd.DataFrame, n: int, min_candidates: int | None = None, ) -> list[npt.NDArray[np.bool_]]: """Sample subset masks (without replacement). Args: - candidates_exp: The experimental representation of candidate points. n: Number of masks to sample. min_candidates: If provided, Subsets with fewer matching candidates are skipped. @@ -796,7 +792,7 @@ def sample_subset_masks( """ return list( islice( - self.subset_masks(candidates_exp, min_candidates), + self.subset_masks(min_candidates), n, ) ) diff --git a/tests/constraints/test_batch_constraint.py b/tests/constraints/test_batch_constraint.py index a3c4bf0c23..0252a8a203 100644 --- a/tests/constraints/test_batch_constraint.py +++ b/tests/constraints/test_batch_constraint.py @@ -150,9 +150,5 @@ def test_subset_masks_min_candidates(min_candidates, expected_count, constraint) if constraint is not None: constraints.append(constraint) searchspace = SearchSpace.from_product(_params, constraints) - masks = list( - searchspace.discrete.subset_masks( - searchspace.discrete.exp_rep, min_candidates=min_candidates - ) - ) + masks = list(searchspace.discrete.subset_masks(min_candidates=min_candidates)) assert len(masks) == expected_count