# Utilities BayBE comes with a set of useful functions that can make your life easier in certain scenarios. ## Search Space Memory Estimation In search spaces that have discrete parts, the memory needed to store the respective data can become excessively large as the number of points grows with the amount of possible combinations arising form all discrete parameter values. The [`SearchSpace.estimate_product_space_size`](baybe.searchspace.core.SearchSpace.estimate_product_space_size) and [`SubspaceDiscrete.estimate_product_space_size`](baybe.searchspace.discrete.SubspaceDiscrete.estimate_product_space_size) utilities allow estimating the memory needed to represent the discrete subspace. They return a [`MemorySize`](baybe.searchspace.discrete.MemorySize) object that contains some relevant estimates: ```python import numpy as np from baybe.parameters import NumericalDiscreteParameter from baybe.searchspace import SearchSpace # This creates 10 parameters with 20 values each. # The resulting space would have 20^10 entries, requiring around 745 TB of memory for # both experimental and computational representation of the search space. parameters = [ NumericalDiscreteParameter(name=f"p{k+1}", values=np.linspace(0, 100, 20)) for k in range(10) ] # Estimate the required memory for such a space mem_estimate = SearchSpace.estimate_product_space_size(parameters) # Print quantities of interest print("Experimental Representation") print(f"Estimated size: {mem_estimate.exp_rep_human_readable}") print(f"Estimated size in Bytes: {mem_estimate.exp_rep_bytes}") print(f"Expected data frame shape: {mem_estimate.exp_rep_shape}") print("Computational Representation") print(f"Estimated size: {mem_estimate.comp_rep_human_readable}") print(f"Estimated size in Bytes: {mem_estimate.comp_rep_bytes}") print(f"Expected data frame shape: {mem_estimate.comp_rep_shape}") ``` ```{admonition} Estimation with Constraints :class: warning {meth}`~baybe.searchspace.core.SearchSpace.estimate_product_space_size` currently does not include the influence of potential constraints in your search space as it is generally very hard to incorporate the effect of arbitrary constraints without actually building the entire space. Hence, you should always **treat the number you get as upper bound** of required memory. This can still be useful – for instance if your estimate already is several Exabytes, it is unlikely that most computers would be able to handle the result even if there are constraints present. ``` ```{admonition} Memory During Optimization :class: warning {meth}`~baybe.searchspace.core.SearchSpace.estimate_product_space_size` only estimates the memory required to handle the search space. **It does not estimate the memory required during optimization**, which can be of a similar magnitude, but generally depends on additional factors. ``` ```{admonition} Influence of Continuous Parameters :class: info Continuous parameters do not influence the size of the discrete search space part as they do not contribute to the combinatorial configurations like discrete parameters. Hence, they are ignored by the utility. ``` ```{admonition} Efficient Search Space Creation :class: tip If you run into issues creating large search spaces, as for instance in mixture use cases, you should consider resorting to more specialized ways of creation by invoking alternative search space constructors like {meth}`~baybe.searchspace.discrete.SubspaceDiscrete.from_dataframe` or {meth}`~baybe.searchspace.discrete.SubspaceDiscrete.from_simplex`. Instead of creating a product space first and then filtering it down according to constraints, they offer a more direct and thus efficient path to the desired result, typically requiring substantially less memory. For example, {meth}`~baybe.searchspace.discrete.SubspaceDiscrete.from_simplex` includes the mixture constraint already *during* the product creation. In addition, BayBE can also be installed with its optional `polars` dependency (`pip install baybe[polars]`) that activates efficient machinery for constraint handling. ``` ## Reproducibility In some scenarios, for instance when testing your code setup, it can be useful to fix the random seeds for all relevant engines to generate reproducible results. BayBE offers the [`set_random_seed`](baybe.utils.random.set_random_seed) utility for this purpose: ```python from baybe.utils.random import set_random_seed # Set the global random seed for all relevant engines set_random_seed(1337) # Assuming we have a prepared campaign campaign.recommend(5) ``` Setting the global random seed can be undesirable if there are other packages in your setup that might unintentionally be influenced by this. For this, BayBE offers [`temporary_seed`](baybe.utils.random.temporary_seed): ```python from baybe.utils.random import temporary_seed # Set the random seed for all relevant engines temporarily within the context with temporary_seed(1337): campaign.recommend(5) ``` ## Adding Fake Target Measurements and Parameter Noise When creating test scripts, it is often useful to try the recommendation loop for a few iterations. However, this requires some arbitrary target measurements to be set. Instead of coming up with a custom logic every time, you can use the [`add_fake_measurements`](baybe.utils.dataframe.add_fake_measurements) utility to add fake target measurements and the [`add_parameter_noise`](baybe.utils.dataframe.add_parameter_noise) utility to add artificial parameter noise: ```python from baybe.utils.dataframe import add_fake_measurements, add_parameter_noise # Get recommendations recommendations = campaign.recommend(5) # Add fake target measurements and artificial parameter noise to the recommendations. # The utilities modify the dataframes inplace. measurements = recommendations.copy() add_fake_measurements(measurements, campaign.targets) add_parameter_noise(measurements, campaign.parameters) # Now continue the loop, e.g. by adding the measurements... ```