diff --git a/baybe/campaign.py b/baybe/campaign.py index e7e93839f..233d62312 100644 --- a/baybe/campaign.py +++ b/baybe/campaign.py @@ -29,6 +29,7 @@ telemetry_record_value, ) from baybe.utils.boolean import eq_dataframe +from baybe.utils.plotting import to_string @define @@ -84,19 +85,14 @@ class Campaign(SerialMixin): """The cached recommendations.""" def __str__(self) -> str: - start_bold = "\033[1m" - end_bold = "\033[0m" - - # Get str representation of campaign fields - fields_to_print = [self.searchspace, self.objective, self.recommender] - fields_str = "\n\n".join(str(x) for x in fields_to_print) - - # Put all relevant attributes of the campaign in one string - campaign_str = f"""{start_bold}Campaign{end_bold} - \n{start_bold}Meta Data{end_bold}\nBatches Done: {self.n_batches_done} - \rFits Done: {self.n_fits_done}\n\n{fields_str}\n""" - - return campaign_str.replace("\n", "\n ").replace("\r", "\r ") + metadata_fields = [ + to_string("Batches done", self.n_batches_done, single_line=True), + to_string("Fits done", self.n_fits_done, single_line=True), + ] + metadata = to_string("Meta Data", *metadata_fields) + fields = [metadata, self.searchspace, self.objective, self.recommender] + + return to_string(self.__class__.__name__, *fields) @property def measurements(self) -> pd.DataFrame: diff --git a/baybe/objectives/desirability.py b/baybe/objectives/desirability.py index 1346a6f4e..5aac1103f 100644 --- a/baybe/objectives/desirability.py +++ b/baybe/objectives/desirability.py @@ -16,7 +16,9 @@ from baybe.targets.base import Target from baybe.targets.numerical import NumericalTarget from baybe.utils.basic import to_tuple +from baybe.utils.dataframe import pretty_print_df from baybe.utils.numerical import geom_mean +from baybe.utils.plotting import to_string from baybe.utils.validation import finite_float @@ -122,19 +124,17 @@ def _normalized_weights(self) -> np.ndarray: return np.asarray(self.weights) / np.sum(self.weights) def __str__(self) -> str: - start_bold = "\033[1m" - end_bold = "\033[0m" - targets_list = [target.summary() for target in self.targets] targets_df = pd.DataFrame(targets_list) targets_df["Weight"] = self.weights - objective_str = f"""{start_bold}Objective{end_bold} - \n{start_bold}Type: {end_bold}{self.__class__.__name__} - \n{start_bold}Targets {end_bold}\n{targets_df} - \n{start_bold}Scalarizer: {end_bold}{self.scalarizer.name}""" + fields = [ + to_string("Type", self.__class__.__name__, single_line=True), + to_string("Targets", pretty_print_df(targets_df)), + to_string("Scalarizer", self.scalarizer.name, single_line=True), + ] - return objective_str.replace("\n", "\n ") + return to_string("Objective", *fields) def transform(self, data: pd.DataFrame) -> pd.DataFrame: # noqa: D102 # See base class. diff --git a/baybe/objectives/single.py b/baybe/objectives/single.py index 2e91f2343..59f322279 100644 --- a/baybe/objectives/single.py +++ b/baybe/objectives/single.py @@ -6,6 +6,8 @@ from baybe.objectives.base import Objective from baybe.targets.base import Target +from baybe.utils.dataframe import pretty_print_df +from baybe.utils.plotting import to_string @define(frozen=True, slots=False) @@ -16,17 +18,15 @@ class SingleTargetObjective(Objective): """The single target considered by the objective.""" def __str__(self) -> str: - start_bold = "\033[1m" - end_bold = "\033[0m" - targets_list = [target.summary() for target in self.targets] targets_df = pd.DataFrame(targets_list) - objective_str = f"""{start_bold}Objective{end_bold} - \n{start_bold}Type: {end_bold}{self.__class__.__name__} - \n{start_bold}Targets {end_bold}\n{targets_df}""" + fields = [ + to_string("Type", self.__class__.__name__, single_line=True), + to_string("Targets", pretty_print_df(targets_df)), + ] - return objective_str.replace("\n", "\n ") + return to_string("Objective", *fields) @property def targets(self) -> tuple[Target, ...]: # noqa: D102 diff --git a/baybe/parameters/numerical.py b/baybe/parameters/numerical.py index 5c6c34117..950821c98 100644 --- a/baybe/parameters/numerical.py +++ b/baybe/parameters/numerical.py @@ -18,7 +18,7 @@ @define(frozen=True, slots=False) class NumericalDiscreteParameter(DiscreteParameter): - """Parameter class for discrete numerical parameters (a.k.a. setpoints).""" + """Class for discrete numerical parameters (a.k.a. setpoints).""" # class variables is_numerical: ClassVar[bool] = True @@ -100,7 +100,7 @@ def is_in_range(self, item: float) -> bool: # noqa: D102 @define(frozen=True, slots=False) class NumericalContinuousParameter(ContinuousParameter): - """Parameter class for continuous numerical parameters.""" + """Class for continuous numerical parameters.""" # class variables is_numerical: ClassVar[bool] = True diff --git a/baybe/recommenders/meta/sequential.py b/baybe/recommenders/meta/sequential.py index d7d48e5e0..6c38596ff 100644 --- a/baybe/recommenders/meta/sequential.py +++ b/baybe/recommenders/meta/sequential.py @@ -22,6 +22,7 @@ block_serialization_hook, converter, ) +from baybe.utils.plotting import to_string @define @@ -64,6 +65,14 @@ def select_recommender( # noqa: D102 else self.initial_recommender ) + def __str__(self) -> str: + fields = [ + to_string("Initial recommender", self.initial_recommender), + to_string("Recommender", self.recommender), + to_string("Switch after", self.switch_after, single_line=True), + ] + return to_string(self.__class__.__name__, *fields) + @define class SequentialMetaRecommender(MetaRecommender): @@ -164,6 +173,13 @@ def select_recommender( # noqa: D102 return recommender + def __str__(self) -> str: + fields = [ + to_string("Recommenders", self.recommenders), + to_string("Mode", self.mode, single_line=True), + ] + return to_string(self.__class__.__name__, *fields) + @define class StreamingSequentialMetaRecommender(MetaRecommender): @@ -242,6 +258,12 @@ def select_recommender( # noqa: D102 return self._last_recommender # type: ignore[return-value] + def __str__(self) -> str: + fields = [ + to_string("Recommenders", self.recommenders), + ] + return to_string(self.__class__.__name__, *fields) + # The recommender iterable cannot be serialized converter.register_unstructure_hook( diff --git a/baybe/recommenders/pure/bayesian/botorch.py b/baybe/recommenders/pure/bayesian/botorch.py index d5fc36420..2ec317dce 100644 --- a/baybe/recommenders/pure/bayesian/botorch.py +++ b/baybe/recommenders/pure/bayesian/botorch.py @@ -17,6 +17,7 @@ SubspaceDiscrete, ) from baybe.utils.dataframe import to_tensor +from baybe.utils.plotting import to_string from baybe.utils.sampling_algorithms import ( DiscreteSamplingMethod, sample_numerical_df, @@ -295,3 +296,20 @@ def _recommend_hybrid( rec_exp = pd.concat([rec_disc_exp, rec_cont_exp], axis=1) return rec_exp + + def __str__(self) -> str: + fields = [ + to_string("Surrogate", self.surrogate_model), + to_string( + "Acquisition function", self.acquisition_function, single_line=True + ), + to_string("Compatibility", self.compatibility, single_line=True), + to_string( + "Sequential continuous", self.sequential_continuous, single_line=True + ), + to_string("Hybrid sampler", self.hybrid_sampler, single_line=True), + to_string( + "Sampling percentage", self.sampling_percentage, single_line=True + ), + ] + return to_string(self.__class__.__name__, *fields) diff --git a/baybe/recommenders/pure/nonpredictive/clustering.py b/baybe/recommenders/pure/nonpredictive/clustering.py index 425d9202b..70d228496 100644 --- a/baybe/recommenders/pure/nonpredictive/clustering.py +++ b/baybe/recommenders/pure/nonpredictive/clustering.py @@ -13,6 +13,7 @@ from baybe.recommenders.pure.nonpredictive.base import NonPredictiveRecommender from baybe.searchspace import SearchSpaceType, SubspaceDiscrete +from baybe.utils.plotting import to_string @define @@ -125,6 +126,18 @@ def _recommend_discrete( # Convert positional indices into DataFrame indices and return result return candidates_comp.index[selection] + def __str__(self) -> str: + fields = [ + to_string("Compatibility", self.compatibility, single_line=True), + to_string( + "Name of clustering parameter", + self.model_cluster_num_parameter_name, + single_line=True, + ), + to_string("Model parameters", self.model_params, single_line=True), + ] + return to_string(self.__class__.__name__, *fields) + @define class PAMClusteringRecommender(SKLearnClusteringRecommender): diff --git a/baybe/recommenders/pure/nonpredictive/sampling.py b/baybe/recommenders/pure/nonpredictive/sampling.py index dd8a1a73f..3811e65f1 100644 --- a/baybe/recommenders/pure/nonpredictive/sampling.py +++ b/baybe/recommenders/pure/nonpredictive/sampling.py @@ -8,6 +8,7 @@ from baybe.recommenders.pure.nonpredictive.base import NonPredictiveRecommender from baybe.searchspace import SearchSpace, SearchSpaceType, SubspaceDiscrete +from baybe.utils.plotting import to_string from baybe.utils.sampling_algorithms import farthest_point_sampling @@ -45,6 +46,10 @@ def _recommend_hybrid( cont_random.index = disc_random.index return pd.concat([disc_random, cont_random], axis=1) + def __str__(self) -> str: + fields = [to_string("Compatibility", self.compatibility, single_line=True)] + return to_string(self.__class__.__name__, *fields) + class FPSRecommender(NonPredictiveRecommender): """An initial recommender that selects candidates via Farthest Point Sampling.""" @@ -70,3 +75,7 @@ def _recommend_discrete( candidates_scaled = np.ascontiguousarray(scaler.transform(candidates_comp)) ilocs = farthest_point_sampling(candidates_scaled, batch_size) return candidates_comp.index[ilocs] + + def __str__(self) -> str: + fields = [to_string("Compatibility", self.compatibility, single_line=True)] + return to_string(self.__class__.__name__, *fields) diff --git a/baybe/searchspace/continuous.py b/baybe/searchspace/continuous.py index 7b898810a..5e4438397 100644 --- a/baybe/searchspace/continuous.py +++ b/baybe/searchspace/continuous.py @@ -30,6 +30,7 @@ from baybe.serialization import SerialMixin, converter, select_constructor_hook from baybe.utils.basic import to_tuple from baybe.utils.dataframe import pretty_print_df +from baybe.utils.plotting import to_string if TYPE_CHECKING: from baybe.searchspace.core import SearchSpace @@ -71,9 +72,6 @@ def __str__(self) -> str: if self.is_empty: return "" - start_bold = "\033[1m" - end_bold = "\033[0m" - # Convert the lists to dataFrames to be able to use pretty_printing param_list = [param.summary() for param in self.parameters] eq_constraints_list = [constr.summary() for constr in self.constraints_lin_eq] @@ -84,21 +82,18 @@ def __str__(self) -> str: constr.summary() for constr in self.constraints_nonlin ] param_df = pd.DataFrame(param_list) - lin_eq_constr_df = pd.DataFrame(eq_constraints_list) - lin_ineq_constr_df = pd.DataFrame(ineq_constraints_list) - nonlinear_constr_df = pd.DataFrame(nonlin_constraints_list) - - # Put all attributes of the continuous class in one string - continuous_str = f"""{start_bold}Continuous Search Space{end_bold} - \n{start_bold}Continuous Parameters{end_bold}\n{pretty_print_df(param_df)} - \n{start_bold}List of Linear Equality Constraints{end_bold} - \r{pretty_print_df(lin_eq_constr_df)} - \n{start_bold}List of Linear Inequality Constraints{end_bold} - \r{pretty_print_df(lin_ineq_constr_df)} - \n{start_bold}List of Nonlinear Constraints{end_bold} - \r{pretty_print_df(nonlinear_constr_df)}""" - - return continuous_str.replace("\n", "\n ").replace("\r", "\r ") + lin_eq_df = pd.DataFrame(eq_constraints_list) + lin_ineq_df = pd.DataFrame(ineq_constraints_list) + nonlinear_df = pd.DataFrame(nonlin_constraints_list) + + fields = [ + to_string("Continuous Parameters", pretty_print_df(param_df)), + to_string("Linear Equality Constraints", pretty_print_df(lin_eq_df)), + to_string("Linear Inequality Constraints", pretty_print_df(lin_ineq_df)), + to_string("Non-linear Constraints", pretty_print_df(nonlinear_df)), + ] + + return to_string(self.__class__.__name__, *fields) @property def constraints_cardinality(self) -> tuple[ContinuousCardinalityConstraint, ...]: diff --git a/baybe/searchspace/core.py b/baybe/searchspace/core.py index 23af0188f..55c0b6f4c 100644 --- a/baybe/searchspace/core.py +++ b/baybe/searchspace/core.py @@ -25,6 +25,7 @@ from baybe.searchspace.validation import validate_parameters from baybe.serialization import SerialMixin, converter, select_constructor_hook from baybe.telemetry import TELEM_LABELS, telemetry_record_value +from baybe.utils.plotting import to_string class SearchSpaceType(Enum): @@ -66,19 +67,14 @@ class SearchSpace(SerialMixin): """The (potentially empty) continuous subspace of the overall search space.""" def __str__(self) -> str: - start_bold = "\033[1m" - end_bold = "\033[0m" - head_str = f"""{start_bold}Search Space{end_bold} - \n{start_bold}Search Space Type: {end_bold}{self.type.name}""" - - # Check the sub space size to avoid adding unwanted break lines - # if the sub space is empty - discrete_str = f"\n\n{self.discrete}" if not self.discrete.is_empty else "" - continuous_str = ( - f"\n\n{self.continuous}" if not self.continuous.is_empty else "" - ) - searchspace_str = f"{head_str}{discrete_str}{continuous_str}" - return searchspace_str.replace("\n", "\n ").replace("\r", "\r ") + fields = [ + to_string("Search Space Type", self.type.name, single_line=True), + ] + if not self.discrete.is_empty: + fields.append(str(self.discrete)) + if not self.continuous.is_empty: + fields.append(str(self.continuous)) + return to_string(self.__class__.__name__, *fields) def __attrs_post_init__(self): """Perform validation and record telemetry values.""" diff --git a/baybe/searchspace/discrete.py b/baybe/searchspace/discrete.py index 41797702e..27bf049a5 100644 --- a/baybe/searchspace/discrete.py +++ b/baybe/searchspace/discrete.py @@ -39,6 +39,7 @@ ) from baybe.utils.memory import bytes_to_human_readable from baybe.utils.numerical import DTypeFloatNumpy +from baybe.utils.plotting import to_string if TYPE_CHECKING: import polars as pl @@ -120,9 +121,6 @@ def __str__(self) -> str: if self.is_empty: return "" - start_bold = "\033[1m" - end_bold = "\033[0m" - # Convert the lists to dataFrames to be able to use pretty_printing param_list = [param.summary() for param in self.parameters] constraints_list = [constr.summary() for constr in self.constraints] @@ -135,19 +133,31 @@ def __str__(self) -> str: dont_recommend_count = len(self.metadata[self.metadata[_METADATA_COLUMNS[2]]]) metadata_count = len(self.metadata) - # Put all attributes of the discrete class in one string. - discrete_str = f"""{start_bold}Discrete Search Space{end_bold} - \n{start_bold}Discrete Parameters{end_bold}\n{pretty_print_df(param_df)} - \n{start_bold}Experimental Representation{end_bold} - \r{pretty_print_df(self.exp_rep)}\n\n{start_bold}Metadata:{end_bold} - \r{_METADATA_COLUMNS[0]}: {was_recommended_count}/{metadata_count} - \r{_METADATA_COLUMNS[1]}: {was_measured_count}/{metadata_count} - \r{_METADATA_COLUMNS[2]}: {dont_recommend_count}/{metadata_count} - \n{start_bold}Constraints{end_bold}\n{pretty_print_df(constraints_df)} - \n{start_bold}Computational Representation{end_bold} - \r{pretty_print_df(self.comp_rep)}""" - - return discrete_str.replace("\n", "\n ").replace("\r", "\r ") + metadata_fields = [ + to_string( + f"{_METADATA_COLUMNS[0]}", + f"{was_recommended_count}/{metadata_count}", + single_line=True, + ), + to_string( + f"{_METADATA_COLUMNS[1]}", + f"{was_measured_count}/{metadata_count}", + single_line=True, + ), + to_string( + f"{_METADATA_COLUMNS[2]}", + f"{dont_recommend_count}/{metadata_count}", + single_line=True, + ), + ] + fields = [ + to_string("Discrete Parameters", pretty_print_df(param_df)), + to_string("Experimental Representation", pretty_print_df(self.exp_rep)), + to_string("Meta Data", *metadata_fields), + to_string("Constraints", pretty_print_df(constraints_df)), + to_string("Computational Representation", pretty_print_df(self.comp_rep)), + ] + return to_string(self.__class__.__name__, *fields) @exp_rep.validator def _validate_exp_rep( # noqa: DOC101, DOC103 diff --git a/baybe/surrogates/bandit.py b/baybe/surrogates/bandit.py index a90e64b2b..cf8c47291 100644 --- a/baybe/surrogates/bandit.py +++ b/baybe/surrogates/bandit.py @@ -13,6 +13,7 @@ from baybe.searchspace.core import SearchSpace from baybe.surrogates.base import Surrogate from baybe.targets.binary import _FAILURE_VALUE_COMP, _SUCCESS_VALUE_COMP +from baybe.utils.plotting import to_string from baybe.utils.random import temporary_seed if TYPE_CHECKING: @@ -160,3 +161,7 @@ def _fit(self, train_x: Tensor, train_y: Tensor, _: Any = None) -> None: wins = (train_x * (train_y == float(_SUCCESS_VALUE_COMP))).sum(dim=0) losses = (train_x * (train_y == float(_FAILURE_VALUE_COMP))).sum(dim=0) self._win_lose_counts = torch.vstack([wins, losses]).to(torch.int) + + def __str__(self) -> str: + fields = [to_string("Prior", self.prior, single_line=True)] + return to_string(super().__str__(), *fields) diff --git a/baybe/surrogates/base.py b/baybe/surrogates/base.py index 680333185..6f891faba 100644 --- a/baybe/surrogates/base.py +++ b/baybe/surrogates/base.py @@ -28,6 +28,7 @@ ) from baybe.serialization.mixin import SerialMixin from baybe.utils.dataframe import to_tensor +from baybe.utils.plotting import to_string from baybe.utils.scaling import ColumnTransformer if TYPE_CHECKING: @@ -312,6 +313,16 @@ def fit( def _fit(self, train_x: Tensor, train_y: Tensor) -> None: """Perform the actual fitting logic.""" + def __str__(self) -> str: + fields = [ + to_string( + "Supports Transfer Learning", + self.supports_transfer_learning, + single_line=True, + ), + ] + return to_string(self.__class__.__name__, *fields) + @define class IndependentGaussianSurrogate(Surrogate, ABC): diff --git a/baybe/surrogates/custom.py b/baybe/surrogates/custom.py index c3054c3d9..0153d3ad5 100644 --- a/baybe/surrogates/custom.py +++ b/baybe/surrogates/custom.py @@ -27,6 +27,7 @@ from baybe.surrogates.base import IndependentGaussianSurrogate from baybe.surrogates.utils import batchify_mean_var_prediction from baybe.utils.numerical import DTypeFloatONNX +from baybe.utils.plotting import to_string if TYPE_CHECKING: import onnxruntime as ort @@ -137,3 +138,7 @@ def validate_compatibility(cls, searchspace: SearchSpace) -> None: f"a one-dimensional computational representation or " f"{CustomDiscreteParameter.__name__}." ) + + def __str__(self) -> str: + fields = [to_string("ONNX input name", self.onnx_input_name, single_line=True)] + return to_string(super().__str__(), *fields) diff --git a/baybe/surrogates/gaussian_process/core.py b/baybe/surrogates/gaussian_process/core.py index 2778e85a7..89a759090 100644 --- a/baybe/surrogates/gaussian_process/core.py +++ b/baybe/surrogates/gaussian_process/core.py @@ -22,6 +22,7 @@ DefaultKernelFactory, _default_noise_factory, ) +from baybe.utils.plotting import to_string if TYPE_CHECKING: from botorch.models.model import Model @@ -211,3 +212,9 @@ def _fit(self, train_x: Tensor, train_y: Tensor) -> None: botorch.optim.fit.fit_gpytorch_mll_torch(mll, step_limit=200) else: botorch.fit.fit_gpytorch_mll(mll) + + def __str__(self) -> str: + fields = [ + to_string("Kernel factory", self.kernel_factory, single_line=True), + ] + return to_string(super().__str__(), *fields) diff --git a/baybe/surrogates/linear.py b/baybe/surrogates/linear.py index b0d5f9c36..a4196f04c 100644 --- a/baybe/surrogates/linear.py +++ b/baybe/surrogates/linear.py @@ -10,6 +10,7 @@ from baybe.surrogates.base import IndependentGaussianSurrogate from baybe.surrogates.utils import batchify_mean_var_prediction, catch_constant_targets from baybe.surrogates.validation import get_model_params_validator +from baybe.utils.plotting import to_string if TYPE_CHECKING: from torch import Tensor @@ -58,3 +59,7 @@ def _fit(self, train_x: Tensor, train_y: Tensor) -> None: # See base class. self._model = ARDRegression(**(self.model_params)) self._model.fit(train_x, train_y.ravel()) + + def __str__(self) -> str: + fields = [to_string("Model Params", self.model_params, single_line=True)] + return to_string(super().__str__(), *fields) diff --git a/baybe/surrogates/ngboost.py b/baybe/surrogates/ngboost.py index 7f4a7a1a3..6a79fb345 100644 --- a/baybe/surrogates/ngboost.py +++ b/baybe/surrogates/ngboost.py @@ -11,6 +11,7 @@ from baybe.surrogates.base import IndependentGaussianSurrogate from baybe.surrogates.utils import batchify_mean_var_prediction, catch_constant_targets from baybe.surrogates.validation import get_model_params_validator +from baybe.utils.plotting import to_string if TYPE_CHECKING: from botorch.models.transforms.input import InputTransform @@ -82,3 +83,7 @@ def _estimate_moments( def _fit(self, train_x: Tensor, train_y: Tensor) -> None: # See base class. self._model = NGBRegressor(**(self.model_params)).fit(train_x, train_y.ravel()) + + def __str__(self) -> str: + fields = [to_string("Model Params", self.model_params, single_line=True)] + return to_string(super().__str__(), *fields) diff --git a/baybe/surrogates/random_forest.py b/baybe/surrogates/random_forest.py index 2db112b99..570feecbc 100644 --- a/baybe/surrogates/random_forest.py +++ b/baybe/surrogates/random_forest.py @@ -13,6 +13,7 @@ from baybe.surrogates.base import Surrogate from baybe.surrogates.utils import batchify_ensemble_predictor, catch_constant_targets from baybe.surrogates.validation import get_model_params_validator +from baybe.utils.plotting import to_string if TYPE_CHECKING: from botorch.models.ensemble import EnsemblePosterior @@ -104,3 +105,7 @@ def _fit(self, train_x: Tensor, train_y: Tensor) -> None: # See base class. self._model = RandomForestRegressor(**(self.model_params)) self._model.fit(train_x.numpy(), train_y.numpy().ravel()) + + def __str__(self) -> str: + fields = [to_string("Model Params", self.model_params, single_line=True)] + return to_string(super().__str__(), *fields) diff --git a/baybe/utils/dataframe.py b/baybe/utils/dataframe.py index 7eec8c38d..80d3c1c4a 100644 --- a/baybe/utils/dataframe.py +++ b/baybe/utils/dataframe.py @@ -449,7 +449,13 @@ def fuzzy_row_match( return pd.Index(inds_matched) -def pretty_print_df(df: pd.DataFrame, max_rows: int = 6, max_columns: int = 4) -> str: +def pretty_print_df( + df: pd.DataFrame, + max_rows: int = 6, + max_columns: int = 4, + max_colwidth: int = 16, + precision: int = 3, +) -> str: """Convert a dataframe into a pretty/readable format. This function returns a customized str representation of the dataframe. @@ -459,6 +465,8 @@ def pretty_print_df(df: pd.DataFrame, max_rows: int = 6, max_columns: int = 4) - df: The dataframe to be printed. max_rows: Maximum number of rows to display. max_columns: Maximum number of columns to display. + max_colwidth: Maximum width of an individual column. + precision: Number of digits to which numbers should be rounded. Returns: The values to be printed as a str table. @@ -469,8 +477,19 @@ def pretty_print_df(df: pd.DataFrame, max_rows: int = 6, max_columns: int = 4) - max_rows, "display.max_columns", max_columns, + "display.max_colwidth", + max_colwidth, + "display.precision", + precision, "expand_frame_repr", False, ): - str_df = str(df) + # Pandas does not truncate the names of columns with long names, which makes + # computational representations barely readable in some of the examples. Hence, + # we truncate them manually. For details, see + # https://stackoverflow.com/questions/64976267/pandas-truncate-column-names) + str_df = df.rename( + columns=lambda x: x[:max_colwidth], + ) + str_df = str(str_df) return str_df diff --git a/baybe/utils/plotting.py b/baybe/utils/plotting.py index 9a8697665..a73ea6b50 100644 --- a/baybe/utils/plotting.py +++ b/baybe/utils/plotting.py @@ -140,3 +140,37 @@ def create_example_plots( else: warnings.warn("Plots could not be saved.") plt.close() + + +def indent(text: str, amount: int = 3, ch: str = " ") -> str: + """Indent a given text by a certain amount.""" + padding = amount * ch + return "".join(padding + line for line in text.splitlines(keepends=True)) + + +def to_string(header: str, *fields: Any, single_line: bool = False) -> str: + """Create a nested string representation. + + Args: + header: The header, typically the name of a class. + *fields: Fields to be printed with an indentation. + single_line: If ``True``, print the representation on a single line. + Only applicable when given a single field. + + Raises: + ValueError: If ``single_line`` is ``True`` but ``fields`` contains more than one + element. + + Returns: + The string representation with indented fields. + """ + if single_line: + if len(fields) > 1: + raise ValueError( + "``single_line`` is only applicable when given a single field." + ) + # Since single line headers look ugly without a ":", we add it manually + header = header if header.endswith(":") else header + ":" + return f"{header} {str(fields[0])}" + + return "\n".join([header] + [indent(str(f)) for f in fields]) diff --git a/examples/Basics/recommenders.py b/examples/Basics/recommenders.py index 3d541a1ad..4759041df 100644 --- a/examples/Basics/recommenders.py +++ b/examples/Basics/recommenders.py @@ -45,6 +45,7 @@ # Per default the initial recommender chosen is a random recommender. + INITIAL_RECOMMENDER = RandomRecommender() ### Available surrogate models @@ -52,8 +53,10 @@ # This model uses available data to model the objective function as well as the uncertainty. # The surrogate model is then used by the acquisition function to make recommendations. -# The following are the available basic surrogates -print(get_subclasses(Surrogate)) +# The following are the available basic surrogates: + +for subclass in get_subclasses(Surrogate): + print(subclass) # Per default a Gaussian Process is used # You can change the used kernel by using the optional `kernel` keyword. diff --git a/examples/Constraints_Continuous/hybrid_space.py b/examples/Constraints_Continuous/hybrid_space.py index 7cde1128c..ea6d26c3a 100644 --- a/examples/Constraints_Continuous/hybrid_space.py +++ b/examples/Constraints_Continuous/hybrid_space.py @@ -74,8 +74,8 @@ ] # We model the following constraints: -# `1.0*x_1 + 1.0*x_2 = 1.0` -# `1.0*x_3 - 1.0*x_4 = 2.0` +# - $1.0*x_1 + 1.0*x_2 = 1.0$ +# - $1.0*x_3 - 1.0*x_4 = 2.0$ constraints = [ DiscreteSumConstraint( @@ -118,7 +118,7 @@ measurements = campaign.measurements TOLERANCE = 0.01 -# `1.0*x_1 + 1.0*x_2 = 1.0` +# $1.0*x_1 + 1.0*x_2 = 1.0$ print( "1.0*x_1 + 1.0*x_2 = 1.0 satisfied in all recommendations? ", @@ -127,7 +127,7 @@ ), ) -# `1.0*x_3 - 1.0*x_4 = 2.0` +# $1.0*x_3 - 1.0*x_4 = 2.0$ print( "1.0*x_3 - 1.0*x_4 = 2.0 satisfied in all recommendations? ", diff --git a/examples/Constraints_Continuous/linear_constraints.py b/examples/Constraints_Continuous/linear_constraints.py index def3bc6d9..74934276c 100644 --- a/examples/Constraints_Continuous/linear_constraints.py +++ b/examples/Constraints_Continuous/linear_constraints.py @@ -58,10 +58,10 @@ ] # We model the following constraints: -# `1.0*x_1 + 1.0*x_2 = 1.0` -# `1.0*x_3 - 1.0*x_4 = 2.0` -# `1.0*x_1 + 1.0*x_3 >= 1.0` -# `2.0*x_2 + 3.0*x_4 <= 1.0` which is equivalent to `-2.0*x_2 - 3.0*x_4 >= -1.0` +# - $1.0*x_1 + 1.0*x_2 = 1.0$ +# - $1.0*x_3 - 1.0*x_4 = 2.0$ +# - $1.0*x_1 + 1.0*x_3 >= 1.0$ +# - $2.0*x_2 + 3.0*x_4 <= 1.0$ which is equivalent to $-2.0*x_2 - 3.0*x_4 >= -1.0$ constraints = [ ContinuousLinearEqualityConstraint( @@ -112,7 +112,7 @@ measurements = campaign.measurements TOLERANCE = 0.01 -# `1.0*x_1 + 1.0*x_2 = 1.0` +# $1.0*x_1 + 1.0*x_2 = 1.0$ print( "1.0*x_1 + 1.0*x_2 = 1.0 satisfied in all recommendations? ", @@ -121,7 +121,7 @@ ), ) -# `1.0*x_3 - 1.0*x_4 = 2.0` +# $1.0*x_3 - 1.0*x_4 = 2.0$ print( "1.0*x_3 - 1.0*x_4 = 2.0 satisfied in all recommendations? ", @@ -130,14 +130,14 @@ ), ) -# `1.0*x_1 + 1.0*x_3 >= 1.0` +# $1.0*x_1 + 1.0*x_3 >= 1.0$ print( "1.0*x_1 + 1.0*x_3 >= 1.0 satisfied in all recommendations? ", (1.0 * measurements["x_1"] + 1.0 * measurements["x_3"]).ge(1.0 - TOLERANCE).all(), ) -# `2.0*x_2 + 3.0*x_4 <= 1.0` +# $2.0*x_2 + 3.0*x_4 <= 1.0$ print( "2.0*x_2 + 3.0*x_4 <= 1.0 satisfied in all recommendations? ", diff --git a/examples/Searchspaces/continuous_space_botorch_function.py b/examples/Searchspaces/continuous_space_botorch_function.py index 46b7f48db..aa7bc7246 100644 --- a/examples/Searchspaces/continuous_space_botorch_function.py +++ b/examples/Searchspaces/continuous_space_botorch_function.py @@ -49,8 +49,8 @@ ### Creating the searchspace and the objective -# Since the searchspace is continuous test, we construct `NumericalContinuousParameter`s -# We use that data of the test function to deduce bounds and number of parameters. +# Since the searchspace is continuous, we use `NumericalContinuousParameter`s. +# We use the data of the test function to deduce bounds and number of parameters. parameters = [ NumericalContinuousParameter( diff --git a/examples/Serialization/basic_serialization.py b/examples/Serialization/basic_serialization.py index 72f8caeb1..4f4aadac5 100644 --- a/examples/Serialization/basic_serialization.py +++ b/examples/Serialization/basic_serialization.py @@ -76,6 +76,7 @@ print(campaign_recreate, end="\n" * 3) # Verify that both objects are equal. + assert campaign == campaign_recreate print("Passed basic assertion check!") diff --git a/examples/Serialization/create_from_config.py b/examples/Serialization/create_from_config.py index f11b336ea..9ca83a117 100644 --- a/examples/Serialization/create_from_config.py +++ b/examples/Serialization/create_from_config.py @@ -59,14 +59,13 @@ "constraints": [] }, "objective": { - "mode": "SINGLE", - "targets": [ + "type": "SingleTargetObjective", + "target": { "type": "NumericalTarget", "name": "Yield", "mode": "MAX" } - ] }, "recommender": { "type": "TwoPhaseMetaRecommender", @@ -96,5 +95,6 @@ campaign = Campaign.from_config(CONFIG) # We now perform a recommendation as usual and print it. + recommendation = campaign.recommend(batch_size=3) print(recommendation) diff --git a/examples/Serialization/validate_config.py b/examples/Serialization/validate_config.py index 52e8bd311..c6aa37252 100644 --- a/examples/Serialization/validate_config.py +++ b/examples/Serialization/validate_config.py @@ -58,14 +58,13 @@ "constraints": [] }, "objective": { - "mode": "SINGLE", - "targets": [ + "type": "SingleTargetObjective", + "target": { "type": "NumericalTarget", "name": "Yield", "mode": "MAX" } - ] }, "recommender": { "type": "TwoPhaseMetaRecommender", @@ -129,14 +128,13 @@ "constraints": [] }, "objective": { - "mode": "SINGLE", - "targets": [ + "type": "SingleTargetObjective", + "target": { "type": "NumericalTarget", "name": "Yield", "mode": "MAX" } - ] }, "recommender": { "type": "TwoPhaseMetaRecommender",