From fe99d1f783940086fc7268209073306f020b8d16 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Mon, 3 Feb 2025 15:50:09 +0100 Subject: [PATCH 01/22] Rename ConvergenceExperimentSettings to ConvergenceBenchmarkSettings --- benchmarks/definition/__init__.py | 4 ++-- benchmarks/definition/config.py | 2 +- benchmarks/domains/synthetic_2C1D_1C.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/benchmarks/definition/__init__.py b/benchmarks/definition/__init__.py index 970a3dacbd..e715f86394 100644 --- a/benchmarks/definition/__init__.py +++ b/benchmarks/definition/__init__.py @@ -3,11 +3,11 @@ from benchmarks.definition.config import ( Benchmark, BenchmarkSettings, - ConvergenceExperimentSettings, + ConvergenceBenchmarkSettings, ) __all__ = [ - "ConvergenceExperimentSettings", + "ConvergenceBenchmarkSettings", "Benchmark", "BenchmarkSettings", ] diff --git a/benchmarks/definition/config.py b/benchmarks/definition/config.py index 211c0c48ac..0d4e7ea5f0 100644 --- a/benchmarks/definition/config.py +++ b/benchmarks/definition/config.py @@ -28,7 +28,7 @@ class BenchmarkSettings(ABC, BenchmarkSerialization): @define(frozen=True) -class ConvergenceExperimentSettings(BenchmarkSettings): +class ConvergenceBenchmarkSettings(BenchmarkSettings): """Benchmark configuration for recommender convergence analyses.""" batch_size: int = field(validator=instance_of(int)) diff --git a/benchmarks/domains/synthetic_2C1D_1C.py b/benchmarks/domains/synthetic_2C1D_1C.py index 94c475ac4c..51e227fa45 100644 --- a/benchmarks/domains/synthetic_2C1D_1C.py +++ b/benchmarks/domains/synthetic_2C1D_1C.py @@ -17,7 +17,7 @@ from baybe.targets import NumericalTarget from benchmarks.definition import ( Benchmark, - ConvergenceExperimentSettings, + ConvergenceBenchmarkSettings, ) if TYPE_CHECKING: @@ -49,7 +49,7 @@ def lookup(df: pd.DataFrame, /) -> pd.DataFrame: ) -def synthetic_2C1D_1C(settings: ConvergenceExperimentSettings) -> DataFrame: +def synthetic_2C1D_1C(settings: ConvergenceBenchmarkSettings) -> DataFrame: """Hybrid synthetic test function. Inputs: @@ -95,7 +95,7 @@ def synthetic_2C1D_1C(settings: ConvergenceExperimentSettings) -> DataFrame: ) -benchmark_config = ConvergenceExperimentSettings( +benchmark_config = ConvergenceBenchmarkSettings( batch_size=5, n_doe_iterations=30, n_mc_iterations=50, From 4f4abcd663d210413e40ee9bdf026428cf2e16ed Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Mon, 3 Feb 2025 15:53:31 +0100 Subject: [PATCH 02/22] Extract convergence-related attributes into subclass --- benchmarks/__init__.py | 12 ++++++++++-- benchmarks/definition/__init__.py | 4 +++- benchmarks/definition/config.py | 17 +++++++++++------ benchmarks/domains/synthetic_2C1D_1C.py | 6 +++--- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/benchmarks/__init__.py b/benchmarks/__init__.py index fb631ee0c9..d1221e3ec1 100644 --- a/benchmarks/__init__.py +++ b/benchmarks/__init__.py @@ -1,9 +1,17 @@ """Benchmarking module for performance tracking.""" -from benchmarks.definition import Benchmark +from benchmarks.definition import ( + Benchmark, + BenchmarkSettings, + ConvergenceBenchmark, + ConvergenceBenchmarkSettings, +) from benchmarks.result import Result __all__ = [ - "Result", "Benchmark", + "BenchmarkSettings", + "ConvergenceBenchmark", + "ConvergenceBenchmarkSettings", + "Result", ] diff --git a/benchmarks/definition/__init__.py b/benchmarks/definition/__init__.py index e715f86394..dff981ec36 100644 --- a/benchmarks/definition/__init__.py +++ b/benchmarks/definition/__init__.py @@ -3,11 +3,13 @@ from benchmarks.definition.config import ( Benchmark, BenchmarkSettings, + ConvergenceBenchmark, ConvergenceBenchmarkSettings, ) __all__ = [ - "ConvergenceBenchmarkSettings", "Benchmark", "BenchmarkSettings", + "ConvergenceBenchmark", + "ConvergenceBenchmarkSettings", ] diff --git a/benchmarks/definition/config.py b/benchmarks/definition/config.py index 0d4e7ea5f0..15c493d8bd 100644 --- a/benchmarks/definition/config.py +++ b/benchmarks/definition/config.py @@ -54,12 +54,6 @@ class Benchmark(Generic[BenchmarkSettingsType], BenchmarkSerialization): name: str = field(init=False) """The name of the benchmark.""" - best_possible_result: float | None = field(default=None) - """The best possible result which can be achieved in the optimization process.""" - - optimal_function_inputs: list[dict[str, Any]] | None = field(default=None) - """An input that creates the best_possible_result.""" - @property def description(self) -> str: """The description of the benchmark function.""" @@ -91,6 +85,17 @@ def __call__(self) -> Result: return Result(self.name, result, metadata) +@define(frozen=True) +class ConvergenceBenchmark(Benchmark): + """A class for defining convergence benchmarks.""" + + best_possible_result: float | None = field(default=None) + """The best possible result which can be achieved in the optimization process.""" + + optimal_function_inputs: list[dict[str, Any]] | None = field(default=None) + """An input that creates the best_possible_result.""" + + # Register un-/structure hooks converter.register_unstructure_hook( Benchmark, diff --git a/benchmarks/domains/synthetic_2C1D_1C.py b/benchmarks/domains/synthetic_2C1D_1C.py index 51e227fa45..732e3b59ae 100644 --- a/benchmarks/domains/synthetic_2C1D_1C.py +++ b/benchmarks/domains/synthetic_2C1D_1C.py @@ -15,8 +15,8 @@ from baybe.searchspace import SearchSpace from baybe.simulation import simulate_scenarios from baybe.targets import NumericalTarget -from benchmarks.definition import ( - Benchmark, +from benchmarks.definition.config import ( + ConvergenceBenchmark, ConvergenceBenchmarkSettings, ) @@ -101,7 +101,7 @@ def synthetic_2C1D_1C(settings: ConvergenceBenchmarkSettings) -> DataFrame: n_mc_iterations=50, ) -synthetic_2C1D_1C_benchmark = Benchmark( +synthetic_2C1D_1C_benchmark = ConvergenceBenchmark( function=synthetic_2C1D_1C, best_possible_result=4.09685, settings=benchmark_config, From bba2c77037466de590e1272f617bfd46a9d9faae Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Mon, 3 Feb 2025 17:31:18 +0100 Subject: [PATCH 03/22] Move convergence benchmark functionality to separate module --- benchmarks/definition/__init__.py | 4 ++- benchmarks/definition/config.py | 29 ++-------------------- benchmarks/definition/convergence.py | 33 +++++++++++++++++++++++++ benchmarks/domains/synthetic_2C1D_1C.py | 2 +- 4 files changed, 39 insertions(+), 29 deletions(-) create mode 100644 benchmarks/definition/convergence.py diff --git a/benchmarks/definition/__init__.py b/benchmarks/definition/__init__.py index dff981ec36..bcd01a6048 100644 --- a/benchmarks/definition/__init__.py +++ b/benchmarks/definition/__init__.py @@ -1,8 +1,10 @@ -"""Benchmark task definitions.""" +"""Benchmark definitions.""" from benchmarks.definition.config import ( Benchmark, BenchmarkSettings, +) +from benchmarks.definition.convergence import ( ConvergenceBenchmark, ConvergenceBenchmarkSettings, ) diff --git a/benchmarks/definition/config.py b/benchmarks/definition/config.py index 15c493d8bd..099424817d 100644 --- a/benchmarks/definition/config.py +++ b/benchmarks/definition/config.py @@ -1,10 +1,10 @@ -"""Benchmark configurations.""" +"""Basic benchmark configuration.""" import time from abc import ABC from collections.abc import Callable from datetime import datetime, timedelta, timezone -from typing import Any, Generic, TypeVar +from typing import Generic, TypeVar from attrs import define, field from attrs.validators import instance_of @@ -27,20 +27,6 @@ class BenchmarkSettings(ABC, BenchmarkSerialization): BenchmarkSettingsType = TypeVar("BenchmarkSettingsType", bound=BenchmarkSettings) -@define(frozen=True) -class ConvergenceBenchmarkSettings(BenchmarkSettings): - """Benchmark configuration for recommender convergence analyses.""" - - batch_size: int = field(validator=instance_of(int)) - """The recommendation batch size.""" - - n_doe_iterations: int = field(validator=instance_of(int)) - """The number of Design of Experiment iterations.""" - - n_mc_iterations: int = field(validator=instance_of(int)) - """The number of Monte Carlo iterations.""" - - @define(frozen=True) class Benchmark(Generic[BenchmarkSettingsType], BenchmarkSerialization): """The base class for a benchmark executable.""" @@ -85,17 +71,6 @@ def __call__(self) -> Result: return Result(self.name, result, metadata) -@define(frozen=True) -class ConvergenceBenchmark(Benchmark): - """A class for defining convergence benchmarks.""" - - best_possible_result: float | None = field(default=None) - """The best possible result which can be achieved in the optimization process.""" - - optimal_function_inputs: list[dict[str, Any]] | None = field(default=None) - """An input that creates the best_possible_result.""" - - # Register un-/structure hooks converter.register_unstructure_hook( Benchmark, diff --git a/benchmarks/definition/convergence.py b/benchmarks/definition/convergence.py new file mode 100644 index 0000000000..7bf07ab0dd --- /dev/null +++ b/benchmarks/definition/convergence.py @@ -0,0 +1,33 @@ +"""Convergence benchmark configuration.""" + +from typing import Any + +from attrs import define, field +from attrs.validators import instance_of + +from benchmarks.definition.config import Benchmark, BenchmarkSettings + + +@define(frozen=True) +class ConvergenceBenchmarkSettings(BenchmarkSettings): + """Benchmark configuration for recommender convergence analyses.""" + + batch_size: int = field(validator=instance_of(int)) + """The recommendation batch size.""" + + n_doe_iterations: int = field(validator=instance_of(int)) + """The number of Design of Experiment iterations.""" + + n_mc_iterations: int = field(validator=instance_of(int)) + """The number of Monte Carlo iterations.""" + + +@define(frozen=True) +class ConvergenceBenchmark(Benchmark): + """A class for defining convergence benchmarks.""" + + best_possible_result: float | None = field(default=None) + """The best possible result which can be achieved in the optimization process.""" + + optimal_function_inputs: list[dict[str, Any]] | None = field(default=None) + """An input that creates the best_possible_result.""" diff --git a/benchmarks/domains/synthetic_2C1D_1C.py b/benchmarks/domains/synthetic_2C1D_1C.py index 732e3b59ae..62dbca3952 100644 --- a/benchmarks/domains/synthetic_2C1D_1C.py +++ b/benchmarks/domains/synthetic_2C1D_1C.py @@ -15,7 +15,7 @@ from baybe.searchspace import SearchSpace from baybe.simulation import simulate_scenarios from baybe.targets import NumericalTarget -from benchmarks.definition.config import ( +from benchmarks.definition.convergence import ( ConvergenceBenchmark, ConvergenceBenchmarkSettings, ) From 3618972e3efc48d62edcf78b66e6e029e88758a0 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Wed, 5 Feb 2025 14:17:40 +0100 Subject: [PATCH 04/22] Instantiate settings type --- benchmarks/definition/convergence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/definition/convergence.py b/benchmarks/definition/convergence.py index 7bf07ab0dd..77bd973c6d 100644 --- a/benchmarks/definition/convergence.py +++ b/benchmarks/definition/convergence.py @@ -23,7 +23,7 @@ class ConvergenceBenchmarkSettings(BenchmarkSettings): @define(frozen=True) -class ConvergenceBenchmark(Benchmark): +class ConvergenceBenchmark(Benchmark[ConvergenceBenchmarkSettings]): """A class for defining convergence benchmarks.""" best_possible_result: float | None = field(default=None) From 41a1163c358fc933de4be1b8e056d9b1d735d048 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Wed, 5 Feb 2025 14:19:21 +0100 Subject: [PATCH 05/22] Make settings classes kw-only --- benchmarks/definition/config.py | 4 ++-- benchmarks/definition/convergence.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmarks/definition/config.py b/benchmarks/definition/config.py index 099424817d..364c5c8e50 100644 --- a/benchmarks/definition/config.py +++ b/benchmarks/definition/config.py @@ -16,11 +16,11 @@ from benchmarks.serialization import BenchmarkSerialization, converter -@define(frozen=True) +@define(frozen=True, kw_only=True) class BenchmarkSettings(ABC, BenchmarkSerialization): """Benchmark configuration for recommender analyses.""" - random_seed: int = field(validator=instance_of(int), kw_only=True, default=1337) + random_seed: int = field(validator=instance_of(int), default=1337) """The random seed for reproducibility.""" diff --git a/benchmarks/definition/convergence.py b/benchmarks/definition/convergence.py index 77bd973c6d..6d479e3774 100644 --- a/benchmarks/definition/convergence.py +++ b/benchmarks/definition/convergence.py @@ -8,7 +8,7 @@ from benchmarks.definition.config import Benchmark, BenchmarkSettings -@define(frozen=True) +@define(frozen=True, kw_only=True) class ConvergenceBenchmarkSettings(BenchmarkSettings): """Benchmark configuration for recommender convergence analyses.""" From 7fc50cc6288e780ae74a512083438a85905d4e94 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Wed, 5 Feb 2025 14:29:49 +0100 Subject: [PATCH 06/22] Rename config module to base --- benchmarks/definition/__init__.py | 2 +- benchmarks/definition/{config.py => base.py} | 0 benchmarks/definition/convergence.py | 2 +- benchmarks/domains/__init__.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename benchmarks/definition/{config.py => base.py} (100%) diff --git a/benchmarks/definition/__init__.py b/benchmarks/definition/__init__.py index bcd01a6048..c5895d8ecd 100644 --- a/benchmarks/definition/__init__.py +++ b/benchmarks/definition/__init__.py @@ -1,6 +1,6 @@ """Benchmark definitions.""" -from benchmarks.definition.config import ( +from benchmarks.definition.base import ( Benchmark, BenchmarkSettings, ) diff --git a/benchmarks/definition/config.py b/benchmarks/definition/base.py similarity index 100% rename from benchmarks/definition/config.py rename to benchmarks/definition/base.py diff --git a/benchmarks/definition/convergence.py b/benchmarks/definition/convergence.py index 6d479e3774..488c1f233a 100644 --- a/benchmarks/definition/convergence.py +++ b/benchmarks/definition/convergence.py @@ -5,7 +5,7 @@ from attrs import define, field from attrs.validators import instance_of -from benchmarks.definition.config import Benchmark, BenchmarkSettings +from benchmarks.definition.base import Benchmark, BenchmarkSettings @define(frozen=True, kw_only=True) diff --git a/benchmarks/domains/__init__.py b/benchmarks/domains/__init__.py index 4a0e956a83..cf0d577824 100644 --- a/benchmarks/domains/__init__.py +++ b/benchmarks/domains/__init__.py @@ -1,6 +1,6 @@ """Benchmark domains.""" -from benchmarks.definition.config import Benchmark +from benchmarks.definition.base import Benchmark from benchmarks.domains.synthetic_2C1D_1C import synthetic_2C1D_1C_benchmark BENCHMARKS: list[Benchmark] = [ From f552a9408889524b014f103e5f6ea5254fa81792 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Wed, 5 Feb 2025 21:29:54 +0100 Subject: [PATCH 07/22] Reorder benchmark attributes --- benchmarks/definition/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmarks/definition/base.py b/benchmarks/definition/base.py index 364c5c8e50..5993c1ad5e 100644 --- a/benchmarks/definition/base.py +++ b/benchmarks/definition/base.py @@ -31,12 +31,12 @@ class BenchmarkSettings(ABC, BenchmarkSerialization): class Benchmark(Generic[BenchmarkSettingsType], BenchmarkSerialization): """The base class for a benchmark executable.""" - settings: BenchmarkSettingsType = field() - """The benchmark configuration.""" - function: Callable[[BenchmarkSettingsType], DataFrame] = field() """The callable which contains the benchmarking logic.""" + settings: BenchmarkSettingsType = field() + """The benchmark configuration.""" + name: str = field(init=False) """The name of the benchmark.""" From d6c7ef05c6bb2a179cacd53321379167117a9272 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Wed, 5 Feb 2025 21:36:20 +0100 Subject: [PATCH 08/22] Ensure that the benchmark function has a docstring --- benchmarks/definition/base.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/benchmarks/definition/base.py b/benchmarks/definition/base.py index 5993c1ad5e..91b0229035 100644 --- a/benchmarks/definition/base.py +++ b/benchmarks/definition/base.py @@ -40,12 +40,16 @@ class Benchmark(Generic[BenchmarkSettingsType], BenchmarkSerialization): name: str = field(init=False) """The name of the benchmark.""" + @function.validator + def _validate_function(self, _, function) -> None: + if function.__doc__ is None: + raise ValueError("The benchmark function must have a docstring.") + @property def description(self) -> str: """The description of the benchmark function.""" - if self.function.__doc__ is not None: - return self.function.__doc__ - return "No description available." + assert self.function.__doc__ is not None + return self.function.__doc__ @name.default def _default_name(self): From 64e1a17e45d1779a3f214ce2d66dcb8ca3d59437 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Wed, 5 Feb 2025 21:37:11 +0100 Subject: [PATCH 09/22] Drop unused name attribute --- benchmarks/definition/base.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/benchmarks/definition/base.py b/benchmarks/definition/base.py index 91b0229035..7b6bd6bad5 100644 --- a/benchmarks/definition/base.py +++ b/benchmarks/definition/base.py @@ -37,9 +37,6 @@ class Benchmark(Generic[BenchmarkSettingsType], BenchmarkSerialization): settings: BenchmarkSettingsType = field() """The benchmark configuration.""" - name: str = field(init=False) - """The name of the benchmark.""" - @function.validator def _validate_function(self, _, function) -> None: if function.__doc__ is None: @@ -51,11 +48,6 @@ def description(self) -> str: assert self.function.__doc__ is not None return self.function.__doc__ - @name.default - def _default_name(self): - """Return the name of the benchmark function.""" - return self.function.__name__ - def __call__(self) -> Result: """Execute the benchmark and return the result.""" start_datetime = datetime.now(timezone.utc) From 8c4cb6d6c2eff22bcec9b5afde6c5f87d2168d2c Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Wed, 5 Feb 2025 21:44:49 +0100 Subject: [PATCH 10/22] Switch to modern cattrs hook registration mechanism --- benchmarks/definition/base.py | 15 +++++---------- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/benchmarks/definition/base.py b/benchmarks/definition/base.py index 7b6bd6bad5..2e3046eee0 100644 --- a/benchmarks/definition/base.py +++ b/benchmarks/definition/base.py @@ -67,13 +67,8 @@ def __call__(self) -> Result: return Result(self.name, result, metadata) -# Register un-/structure hooks -converter.register_unstructure_hook( - Benchmark, - lambda o: dict( - {"description": o.description}, - **make_dict_unstructure_fn(Benchmark, converter, function=override(omit=True))( - o - ), - ), -) +@converter.register_unstructure_hook +def unstructure_benchmark(benchmark: Benchmark) -> dict: + """Unstructure a benchmark instance.""" + fn = make_dict_unstructure_fn(Benchmark, converter, function=override(omit=True)) + return {"description": benchmark.description, **fn(benchmark)} diff --git a/pyproject.toml b/pyproject.toml index 3bf1c36ace..352d0629fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dynamic = ['version'] dependencies = [ "attrs>=24.1.0", "botorch>=0.9.3,<1", - "cattrs>=23.2.0", + "cattrs>=24.1.0", "exceptiongroup", "funcy>=1.17,<2", "gpytorch>=1.9.1,<2", From 9afcb1b8c88f7df8d55b4d9c4be8f3a6731ea961 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Wed, 5 Feb 2025 21:47:23 +0100 Subject: [PATCH 11/22] Update lockfile --- .lockfiles/py310-dev.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.lockfiles/py310-dev.lock b/.lockfiles/py310-dev.lock index d0da179b99..7c37147154 100644 --- a/.lockfiles/py310-dev.lock +++ b/.lockfiles/py310-dev.lock @@ -8,7 +8,7 @@ anyio==4.4.0 # via # httpx # jupyter-server -appnope==0.1.4 ; platform_system == 'Darwin' +appnope==0.1.4 ; sys_platform == 'darwin' # via ipykernel argon2-cffi==23.1.0 # via jupyter-server @@ -61,7 +61,7 @@ cachetools==5.4.0 # via # streamlit # tox -cattrs==23.2.3 +cattrs==24.1.2 # via baybe (pyproject.toml) certifi==2024.7.4 # via @@ -240,7 +240,7 @@ importlib-metadata==7.1.0 # opentelemetry-api iniconfig==2.0.0 # via pytest -intel-openmp==2021.4.0 ; platform_system == 'Windows' +intel-openmp==2021.4.0 ; sys_platform == 'win32' # via mkl interface-meta==1.3.0 # via formulaic @@ -393,7 +393,7 @@ mdurl==0.1.2 # via markdown-it-py mistune==3.0.2 # via nbconvert -mkl==2021.4.0 ; platform_system == 'Windows' +mkl==2021.4.0 ; sys_platform == 'win32' # via torch mmh3==5.0.1 # via e3fp @@ -487,36 +487,36 @@ numpy==1.26.4 # types-seaborn # xarray # xyzpy -nvidia-cublas-cu12==12.1.3.1 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-cublas-cu12==12.1.3.1 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via # nvidia-cudnn-cu12 # nvidia-cusolver-cu12 # torch -nvidia-cuda-cupti-cu12==12.1.105 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-cuda-cupti-cu12==12.1.105 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch -nvidia-cuda-nvrtc-cu12==12.1.105 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-cuda-nvrtc-cu12==12.1.105 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch -nvidia-cuda-runtime-cu12==12.1.105 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-cuda-runtime-cu12==12.1.105 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch -nvidia-cudnn-cu12==8.9.2.26 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-cudnn-cu12==8.9.2.26 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch -nvidia-cufft-cu12==11.0.2.54 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-cufft-cu12==11.0.2.54 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch -nvidia-curand-cu12==10.3.2.106 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-curand-cu12==10.3.2.106 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch -nvidia-cusolver-cu12==11.4.5.107 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-cusolver-cu12==11.4.5.107 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch -nvidia-cusparse-cu12==12.1.0.106 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-cusparse-cu12==12.1.0.106 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via # nvidia-cusolver-cu12 # torch -nvidia-nccl-cu12==2.20.5 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-nccl-cu12==2.20.5 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch -nvidia-nvjitlink-cu12==12.5.82 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-nvjitlink-cu12==12.5.82 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via # nvidia-cusolver-cu12 # nvidia-cusparse-cu12 -nvidia-nvtx-cu12==12.1.105 ; platform_machine == 'x86_64' and platform_system == 'Linux' +nvidia-nvtx-cu12==12.1.105 ; platform_machine == 'x86_64' and sys_platform == 'linux' # via torch onnx==1.16.1 # via @@ -922,7 +922,7 @@ sympy==1.13.1 # via # onnxruntime # torch -tbb==2021.13.0 ; platform_system == 'Windows' +tbb==2021.13.0 ; sys_platform == 'win32' # via mkl tenacity==8.5.0 # via @@ -1007,7 +1007,7 @@ traitlets==5.14.3 # nbclient # nbconvert # nbformat -triton==2.3.1 ; python_full_version < '3.12' and platform_machine == 'x86_64' and platform_system == 'Linux' +triton==2.3.1 ; python_full_version < '3.12' and platform_machine == 'x86_64' and sys_platform == 'linux' # via torch typeguard==2.13.3 # via @@ -1050,7 +1050,7 @@ virtualenv==20.26.3 # via # pre-commit # tox -watchdog==4.0.1 ; platform_system != 'Darwin' +watchdog==4.0.1 ; sys_platform != 'darwin' # via streamlit wcwidth==0.2.13 # via prompt-toolkit From 689ec000922e8db2a13a2b708b6bd0023168e8d6 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Wed, 5 Feb 2025 22:03:14 +0100 Subject: [PATCH 12/22] Refine some docstrings --- benchmarks/definition/base.py | 8 ++++---- benchmarks/definition/convergence.py | 4 ++-- benchmarks/result/metadata.py | 2 +- benchmarks/result/result.py | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/benchmarks/definition/base.py b/benchmarks/definition/base.py index 2e3046eee0..5e9672dd40 100644 --- a/benchmarks/definition/base.py +++ b/benchmarks/definition/base.py @@ -18,10 +18,10 @@ @define(frozen=True, kw_only=True) class BenchmarkSettings(ABC, BenchmarkSerialization): - """Benchmark configuration for recommender analyses.""" + """The basic benchmark configuration.""" random_seed: int = field(validator=instance_of(int), default=1337) - """The random seed for reproducibility.""" + """The used random seed.""" BenchmarkSettingsType = TypeVar("BenchmarkSettingsType", bound=BenchmarkSettings) @@ -29,10 +29,10 @@ class BenchmarkSettings(ABC, BenchmarkSerialization): @define(frozen=True) class Benchmark(Generic[BenchmarkSettingsType], BenchmarkSerialization): - """The base class for a benchmark executable.""" + """The base class for all benchmark definitions.""" function: Callable[[BenchmarkSettingsType], DataFrame] = field() - """The callable which contains the benchmarking logic.""" + """The callable containing the benchmarking logic.""" settings: BenchmarkSettingsType = field() """The benchmark configuration.""" diff --git a/benchmarks/definition/convergence.py b/benchmarks/definition/convergence.py index 488c1f233a..0881e817e2 100644 --- a/benchmarks/definition/convergence.py +++ b/benchmarks/definition/convergence.py @@ -27,7 +27,7 @@ class ConvergenceBenchmark(Benchmark[ConvergenceBenchmarkSettings]): """A class for defining convergence benchmarks.""" best_possible_result: float | None = field(default=None) - """The best possible result which can be achieved in the optimization process.""" + """The best possible result that can be achieved in the optimization process.""" optimal_function_inputs: list[dict[str, Any]] | None = field(default=None) - """An input that creates the best_possible_result.""" + """The optimal inputs to the system being optimized.""" diff --git a/benchmarks/result/metadata.py b/benchmarks/result/metadata.py index 75f368e92a..d2d062836f 100644 --- a/benchmarks/result/metadata.py +++ b/benchmarks/result/metadata.py @@ -27,7 +27,7 @@ class ResultMetadata(BenchmarkSerialization): """The latest BayBE tag reachable in the ancestor commit history.""" branch: str = field(validator=instance_of(str), init=False) - """The branch currently checked out.""" + """The branch checked out during benchmark execution.""" @branch.default def _default_branch(self) -> str: diff --git a/benchmarks/result/result.py b/benchmarks/result/result.py index 69a9b9104a..173b71d010 100644 --- a/benchmarks/result/result.py +++ b/benchmarks/result/result.py @@ -16,7 +16,7 @@ @define(frozen=True) class Result(BenchmarkSerialization): - """A single result of the benchmarking.""" + """A single benchmarking result.""" benchmark_identifier: str = field(validator=instance_of(str)) """The identifier of the benchmark that produced the result.""" From c8fd73123506a25d370dd543bd23a4dcfa32f0f7 Mon Sep 17 00:00:00 2001 From: Fabian Liebig Date: Thu, 6 Feb 2025 09:47:43 +0100 Subject: [PATCH 13/22] Update branch attribute to allow None and filter out None components in path construction --- benchmarks/persistence/persistence.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/benchmarks/persistence/persistence.py b/benchmarks/persistence/persistence.py index 83dfce13d6..6f7b4a28c1 100644 --- a/benchmarks/persistence/persistence.py +++ b/benchmarks/persistence/persistence.py @@ -48,7 +48,7 @@ class PathConstructor: benchmark_name: str = field(validator=instance_of(str)) """The name of the benchmark for which the path should be constructed.""" - branch: str = field(validator=instance_of(str)) + branch: str | None = field(validator=instance_of((str, type(None)))) """The branch checked out at benchmark execution time.""" latest_baybe_tag: str = field(validator=instance_of(str)) @@ -117,7 +117,9 @@ def get_path(self, strategy: PathStrategy) -> Path: ] sanitized_components = [ - self._sanitize_string(component) for component in components + self._sanitize_string(component) + for component in components + if component is not None ] path = separator.join(sanitized_components) + separator + "result.json" From 828c8043361678f140af54a7da2fe1cc0bb9317b Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Thu, 6 Feb 2025 10:26:10 +0100 Subject: [PATCH 14/22] Simplify validator using built-in optional decorator --- benchmarks/persistence/persistence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/persistence/persistence.py b/benchmarks/persistence/persistence.py index 6f7b4a28c1..3ee90cc7ae 100644 --- a/benchmarks/persistence/persistence.py +++ b/benchmarks/persistence/persistence.py @@ -12,7 +12,7 @@ import boto3 import boto3.session from attr import define, field -from attrs.validators import instance_of +from attrs.validators import instance_of, optional from boto3.session import Session from typing_extensions import override @@ -48,7 +48,7 @@ class PathConstructor: benchmark_name: str = field(validator=instance_of(str)) """The name of the benchmark for which the path should be constructed.""" - branch: str | None = field(validator=instance_of((str, type(None)))) + branch: str | None = field(validator=optional(instance_of(str))) """The branch checked out at benchmark execution time.""" latest_baybe_tag: str = field(validator=instance_of(str)) From acb82b1f6755383647938d94beee802090c25ea9 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Thu, 6 Feb 2025 09:26:39 +0100 Subject: [PATCH 15/22] Fix branch attribute for detached head cases --- benchmarks/result/metadata.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/benchmarks/result/metadata.py b/benchmarks/result/metadata.py index d2d062836f..10eb53d39a 100644 --- a/benchmarks/result/metadata.py +++ b/benchmarks/result/metadata.py @@ -30,11 +30,14 @@ class ResultMetadata(BenchmarkSerialization): """The branch checked out during benchmark execution.""" @branch.default - def _default_branch(self) -> str: + def _default_branch(self) -> str | None: """Set the current checkout branch.""" repo = git.Repo(search_parent_directories=True) - current_branch = repo.active_branch.name - return current_branch + try: + current_branch = repo.active_branch.name + return current_branch + except TypeError: + return None @commit_hash.default def _default_commit_hash(self) -> str: From 6c0eaff020297f0a4154f0fe1ed24c02cdb7d7f3 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Thu, 6 Feb 2025 09:49:34 +0100 Subject: [PATCH 16/22] Refactor ConvergenceBenchmark attributes * Drop optimal_function_inputs because it's not needed * Rename and refactor best_possible_result so that it contains the optima for all targets individually --- benchmarks/definition/convergence.py | 18 ++++++++++++------ benchmarks/domains/synthetic_2C1D_1C.py | 6 +----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/benchmarks/definition/convergence.py b/benchmarks/definition/convergence.py index 0881e817e2..c6abb5b458 100644 --- a/benchmarks/definition/convergence.py +++ b/benchmarks/definition/convergence.py @@ -3,7 +3,7 @@ from typing import Any from attrs import define, field -from attrs.validators import instance_of +from attrs.validators import deep_mapping, instance_of, optional from benchmarks.definition.base import Benchmark, BenchmarkSettings @@ -26,8 +26,14 @@ class ConvergenceBenchmarkSettings(BenchmarkSettings): class ConvergenceBenchmark(Benchmark[ConvergenceBenchmarkSettings]): """A class for defining convergence benchmarks.""" - best_possible_result: float | None = field(default=None) - """The best possible result that can be achieved in the optimization process.""" - - optimal_function_inputs: list[dict[str, Any]] | None = field(default=None) - """The optimal inputs to the system being optimized.""" + optimal_target_values: dict[str, Any] | None = field( + default=None, + validator=optional( + deep_mapping( + key_validator=instance_of(str), + mapping_validator=instance_of(dict), + value_validator=lambda *_: None, + ) + ), + ) + """The optimal values that can be achieved for the targets **individually**.""" diff --git a/benchmarks/domains/synthetic_2C1D_1C.py b/benchmarks/domains/synthetic_2C1D_1C.py index 62dbca3952..d5cb92e926 100644 --- a/benchmarks/domains/synthetic_2C1D_1C.py +++ b/benchmarks/domains/synthetic_2C1D_1C.py @@ -103,12 +103,8 @@ def synthetic_2C1D_1C(settings: ConvergenceBenchmarkSettings) -> DataFrame: synthetic_2C1D_1C_benchmark = ConvergenceBenchmark( function=synthetic_2C1D_1C, - best_possible_result=4.09685, + optimal_target_values={"target": 4.09685}, settings=benchmark_config, - optimal_function_inputs=[ - {"x": 1.610, "y": 1.571, "z": 3}, - {"x": 1.610, "y": -4.712, "z": 3}, - ], ) From 29fd0d7db662c2ec079b0290e2a06aba49ae7521 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Thu, 6 Feb 2025 10:08:56 +0100 Subject: [PATCH 17/22] Correctly unstructure Benchmark subclasses --- benchmarks/definition/base.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/benchmarks/definition/base.py b/benchmarks/definition/base.py index 5e9672dd40..316760b0b0 100644 --- a/benchmarks/definition/base.py +++ b/benchmarks/definition/base.py @@ -8,7 +8,8 @@ from attrs import define, field from attrs.validators import instance_of -from cattr.gen import make_dict_unstructure_fn, override +from cattrs import override +from cattrs.gen import make_dict_unstructure_fn from pandas import DataFrame from baybe.utils.random import temporary_seed @@ -70,5 +71,7 @@ def __call__(self) -> Result: @converter.register_unstructure_hook def unstructure_benchmark(benchmark: Benchmark) -> dict: """Unstructure a benchmark instance.""" - fn = make_dict_unstructure_fn(Benchmark, converter, function=override(omit=True)) + fn = make_dict_unstructure_fn( + type(benchmark), converter, function=override(omit=True) + ) return {"description": benchmark.description, **fn(benchmark)} From cf88fe26dc2dd87a915c42d6b81c680be39ac080 Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Thu, 6 Feb 2025 10:23:36 +0100 Subject: [PATCH 18/22] Bring back benchmark name as property Should not have been removed since used as identifier for Result, but implementation as property makes more sense. --- benchmarks/definition/base.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/benchmarks/definition/base.py b/benchmarks/definition/base.py index 316760b0b0..d1f6858684 100644 --- a/benchmarks/definition/base.py +++ b/benchmarks/definition/base.py @@ -43,6 +43,11 @@ def _validate_function(self, _, function) -> None: if function.__doc__ is None: raise ValueError("The benchmark function must have a docstring.") + @property + def name(self) -> str: + """The name of the benchmark function.""" + return self.function.__name__ + @property def description(self) -> str: """The description of the benchmark function.""" @@ -74,4 +79,8 @@ def unstructure_benchmark(benchmark: Benchmark) -> dict: fn = make_dict_unstructure_fn( type(benchmark), converter, function=override(omit=True) ) - return {"description": benchmark.description, **fn(benchmark)} + return { + "name": benchmark.name, + "description": benchmark.description, + **fn(benchmark), + } From 9cc2dcd603b82b5ee3f45332bccdd3c607f53885 Mon Sep 17 00:00:00 2001 From: Fabian Liebig Date: Thu, 6 Feb 2025 14:25:57 +0100 Subject: [PATCH 19/22] Update branch attribute to handle detached head state by setting it to 'branchless' --- benchmarks/persistence/persistence.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/benchmarks/persistence/persistence.py b/benchmarks/persistence/persistence.py index 3ee90cc7ae..d8a38c8d9c 100644 --- a/benchmarks/persistence/persistence.py +++ b/benchmarks/persistence/persistence.py @@ -12,7 +12,7 @@ import boto3 import boto3.session from attr import define, field -from attrs.validators import instance_of, optional +from attrs.validators import instance_of from boto3.session import Session from typing_extensions import override @@ -48,8 +48,12 @@ class PathConstructor: benchmark_name: str = field(validator=instance_of(str)) """The name of the benchmark for which the path should be constructed.""" - branch: str | None = field(validator=optional(instance_of(str))) - """The branch checked out at benchmark execution time.""" + branch: str = field( + validator=instance_of(str), + converter=lambda x: "branchless" if x is None else x, + ) + """The branch checked out at benchmark execution time. + In case of detached head state the branch is set to 'branchless'.""" latest_baybe_tag: str = field(validator=instance_of(str)) """The latest BayBE version tag existing at benchmark execution time.""" @@ -108,6 +112,7 @@ def get_path(self, strategy: PathStrategy) -> Path: separator = "/" if strategy is PathStrategy.HIERARCHICAL else "_" file_usable_date = self.execution_date_time.strftime("%Y-%m-%d") + components = [ self.benchmark_name, self.branch, @@ -117,9 +122,7 @@ def get_path(self, strategy: PathStrategy) -> Path: ] sanitized_components = [ - self._sanitize_string(component) - for component in components - if component is not None + self._sanitize_string(component) for component in components ] path = separator.join(sanitized_components) + separator + "result.json" From 5b3f8c609ebe8c5ffe7a30fc7e3953dfe526acda Mon Sep 17 00:00:00 2001 From: Fabian Liebig Date: Fri, 7 Feb 2025 07:46:42 +0100 Subject: [PATCH 20/22] Update branch converter to use '-branchless-' for None values for reasonable sorting of file names and folders --- benchmarks/persistence/persistence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/persistence/persistence.py b/benchmarks/persistence/persistence.py index d8a38c8d9c..566803971b 100644 --- a/benchmarks/persistence/persistence.py +++ b/benchmarks/persistence/persistence.py @@ -50,7 +50,7 @@ class PathConstructor: branch: str = field( validator=instance_of(str), - converter=lambda x: "branchless" if x is None else x, + converter=lambda x: "-branchless-" if x is None else x, ) """The branch checked out at benchmark execution time. In case of detached head state the branch is set to 'branchless'.""" From 2dfc81738277ce1fb9bb0e431d8d88d8766796de Mon Sep 17 00:00:00 2001 From: Fabian Liebig Date: Fri, 7 Feb 2025 07:57:21 +0100 Subject: [PATCH 21/22] Update ResultMetadata branch attribute to allow None values for detached head state --- benchmarks/result/metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/result/metadata.py b/benchmarks/result/metadata.py index 10eb53d39a..e777f89f05 100644 --- a/benchmarks/result/metadata.py +++ b/benchmarks/result/metadata.py @@ -4,7 +4,7 @@ import git from attrs import define, field -from attrs.validators import instance_of +from attrs.validators import instance_of, optional from cattrs.gen import make_dict_unstructure_fn from benchmarks.serialization import BenchmarkSerialization, converter @@ -26,7 +26,7 @@ class ResultMetadata(BenchmarkSerialization): latest_baybe_tag: str = field(validator=instance_of(str), init=False) """The latest BayBE tag reachable in the ancestor commit history.""" - branch: str = field(validator=instance_of(str), init=False) + branch: str | None = field(validator=optional(instance_of(str)), init=False) """The branch checked out during benchmark execution.""" @branch.default From 2322b2a773d4164b0260e290ed46ee2b876eb8bf Mon Sep 17 00:00:00 2001 From: AdrianSosic Date: Fri, 7 Feb 2025 08:02:09 +0100 Subject: [PATCH 22/22] Simplify converter expression --- benchmarks/persistence/persistence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/persistence/persistence.py b/benchmarks/persistence/persistence.py index 566803971b..01ff02d625 100644 --- a/benchmarks/persistence/persistence.py +++ b/benchmarks/persistence/persistence.py @@ -49,11 +49,11 @@ class PathConstructor: """The name of the benchmark for which the path should be constructed.""" branch: str = field( + converter=lambda x: x or "-branchless-", validator=instance_of(str), - converter=lambda x: "-branchless-" if x is None else x, ) """The branch checked out at benchmark execution time. - In case of detached head state the branch is set to 'branchless'.""" + In case of detached head state the branch is set to '-branchless-'.""" latest_baybe_tag: str = field(validator=instance_of(str)) """The latest BayBE version tag existing at benchmark execution time."""