holobench 1.41.0__py3-none-any.whl → 1.43.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bencher/__init__.py +20 -2
- bencher/bench_cfg.py +262 -54
- bencher/bench_report.py +2 -2
- bencher/bench_runner.py +96 -10
- bencher/bencher.py +421 -89
- bencher/class_enum.py +70 -7
- bencher/example/example_dataframe.py +2 -2
- bencher/example/example_levels.py +17 -173
- bencher/example/example_pareto.py +107 -31
- bencher/example/example_rerun2.py +1 -1
- bencher/example/example_simple_bool.py +2 -2
- bencher/example/example_simple_float2d.py +6 -1
- bencher/example/example_video.py +2 -0
- bencher/example/experimental/example_hvplot_explorer.py +2 -2
- bencher/example/inputs_0D/example_0_in_1_out.py +25 -15
- bencher/example/inputs_0D/example_0_in_2_out.py +12 -3
- bencher/example/inputs_0_float/example_0_cat_in_2_out.py +88 -0
- bencher/example/inputs_0_float/example_1_cat_in_2_out.py +98 -0
- bencher/example/inputs_0_float/example_2_cat_in_2_out.py +107 -0
- bencher/example/inputs_0_float/example_3_cat_in_2_out.py +111 -0
- bencher/example/inputs_1D/example1d_common.py +48 -12
- bencher/example/inputs_1D/example_0_float_1_cat.py +33 -0
- bencher/example/inputs_1D/example_1_cat_in_2_out_repeats.py +68 -0
- bencher/example/inputs_1D/example_1_float_2_cat_repeats.py +3 -0
- bencher/example/inputs_1D/example_1_int_in_1_out.py +98 -0
- bencher/example/inputs_1D/example_1_int_in_2_out.py +101 -0
- bencher/example/inputs_1D/example_1_int_in_2_out_repeats.py +99 -0
- bencher/example/inputs_1_float/example_1_float_0_cat_in_2_out.py +117 -0
- bencher/example/inputs_1_float/example_1_float_1_cat_in_2_out.py +124 -0
- bencher/example/inputs_1_float/example_1_float_2_cat_in_2_out.py +132 -0
- bencher/example/inputs_1_float/example_1_float_3_cat_in_2_out.py +140 -0
- bencher/example/inputs_2D/example_2_cat_in_4_out_repeats.py +104 -0
- bencher/example/inputs_2_float/example_2_float_0_cat_in_2_out.py +98 -0
- bencher/example/inputs_2_float/example_2_float_1_cat_in_2_out.py +112 -0
- bencher/example/inputs_2_float/example_2_float_2_cat_in_2_out.py +122 -0
- bencher/example/inputs_2_float/example_2_float_3_cat_in_2_out.py +138 -0
- bencher/example/inputs_3_float/example_3_float_0_cat_in_2_out.py +111 -0
- bencher/example/inputs_3_float/example_3_float_1_cat_in_2_out.py +117 -0
- bencher/example/inputs_3_float/example_3_float_2_cat_in_2_out.py +124 -0
- bencher/example/inputs_3_float/example_3_float_3_cat_in_2_out.py +129 -0
- bencher/example/meta/generate_examples.py +118 -7
- bencher/example/meta/generate_meta.py +88 -40
- bencher/job.py +174 -9
- bencher/plotting/plot_filter.py +52 -17
- bencher/results/bench_result.py +117 -25
- bencher/results/bench_result_base.py +117 -8
- bencher/results/dataset_result.py +6 -200
- bencher/results/explorer_result.py +23 -0
- bencher/results/{hvplot_result.py → histogram_result.py} +3 -18
- bencher/results/holoview_results/__init__.py +0 -0
- bencher/results/holoview_results/bar_result.py +79 -0
- bencher/results/holoview_results/curve_result.py +110 -0
- bencher/results/holoview_results/distribution_result/__init__.py +0 -0
- bencher/results/holoview_results/distribution_result/box_whisker_result.py +73 -0
- bencher/results/holoview_results/distribution_result/distribution_result.py +109 -0
- bencher/results/holoview_results/distribution_result/scatter_jitter_result.py +92 -0
- bencher/results/holoview_results/distribution_result/violin_result.py +70 -0
- bencher/results/holoview_results/heatmap_result.py +319 -0
- bencher/results/holoview_results/holoview_result.py +346 -0
- bencher/results/holoview_results/line_result.py +240 -0
- bencher/results/holoview_results/scatter_result.py +107 -0
- bencher/results/holoview_results/surface_result.py +158 -0
- bencher/results/holoview_results/table_result.py +14 -0
- bencher/results/holoview_results/tabulator_result.py +20 -0
- bencher/results/optuna_result.py +30 -115
- bencher/results/video_controls.py +38 -0
- bencher/results/video_result.py +39 -36
- bencher/results/video_summary.py +2 -2
- bencher/results/{plotly_result.py → volume_result.py} +29 -8
- bencher/utils.py +175 -26
- bencher/variables/inputs.py +122 -15
- bencher/video_writer.py +2 -1
- bencher/worker_job.py +31 -3
- {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/METADATA +24 -24
- holobench-1.43.0.dist-info/RECORD +147 -0
- bencher/example/example_levels2.py +0 -37
- bencher/example/inputs_1D/example_1_in_1_out.py +0 -62
- bencher/example/inputs_1D/example_1_in_2_out.py +0 -63
- bencher/example/inputs_1D/example_1_in_2_out_repeats.py +0 -61
- bencher/results/holoview_result.py +0 -796
- bencher/results/panel_result.py +0 -41
- holobench-1.41.0.dist-info/RECORD +0 -114
- {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/WHEEL +0 -0
- {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/licenses/LICENSE +0 -0
bencher/class_enum.py
CHANGED
@@ -1,52 +1,115 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from strenum import StrEnum
|
3
|
-
from typing import Any
|
3
|
+
from typing import Any, TypeVar
|
4
4
|
import importlib
|
5
5
|
from abc import abstractmethod
|
6
6
|
from dataclasses import dataclass
|
7
7
|
from enum import auto
|
8
8
|
|
9
|
+
T = TypeVar("T") # Generic type for return value of to_class
|
10
|
+
|
9
11
|
|
10
12
|
class ClassEnum(StrEnum):
|
11
|
-
"""A
|
13
|
+
"""A string-based enum class that maps enum values to corresponding class instances.
|
14
|
+
|
15
|
+
ClassEnum is a pattern to make it easier to create a factory method that converts
|
16
|
+
from an enum value to a corresponding class instance. Subclasses should implement
|
17
|
+
the to_class() method which takes an enum value and returns an instance of the
|
18
|
+
corresponding class.
|
19
|
+
|
20
|
+
This pattern is useful for configuration-driven class instantiation, allowing
|
21
|
+
classes to be selected via string configuration values that match enum names.
|
22
|
+
"""
|
12
23
|
|
13
24
|
@classmethod
|
14
25
|
def to_class_generic(cls, module_import: str, class_name: str) -> Any:
|
15
|
-
"""Create an instance of
|
26
|
+
"""Create an instance of a class from its module path and class name.
|
27
|
+
|
28
|
+
This utility method dynamically imports a module and instantiates a class from it.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
module_import (str): The module path to import (e.g., "bencher.class_enum")
|
32
|
+
class_name (str): The name of the class to instantiate
|
16
33
|
|
17
34
|
Returns:
|
18
|
-
Any: instance of the class
|
35
|
+
Any: A new instance of the specified class
|
19
36
|
"""
|
20
|
-
|
21
37
|
class_def = getattr(importlib.import_module(module_import), class_name)
|
22
38
|
return class_def()
|
23
39
|
|
24
40
|
@classmethod
|
25
41
|
@abstractmethod
|
26
42
|
def to_class(cls, enum_val: ClassEnum) -> Any:
|
27
|
-
"""
|
28
|
-
|
43
|
+
"""Convert an enum value to its corresponding class instance.
|
44
|
+
|
45
|
+
Subclasses must override this method to implement the mapping from
|
46
|
+
enum values to class instances.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
enum_val (ClassEnum): The enum value to convert to a class instance
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
Any: An instance of the class corresponding to the enum value
|
53
|
+
|
54
|
+
Raises:
|
55
|
+
NotImplementedError: If this method is not overridden by a subclass
|
56
|
+
"""
|
57
|
+
raise NotImplementedError("Subclasses must implement to_class()")
|
29
58
|
|
30
59
|
|
31
60
|
@dataclass
|
32
61
|
class BaseClass:
|
62
|
+
"""Base class for the ClassEnum example.
|
63
|
+
|
64
|
+
A simple dataclass that serves as the base class for the ClassEnum example classes.
|
65
|
+
|
66
|
+
Attributes:
|
67
|
+
baseclassname (str): A name for the base class
|
68
|
+
"""
|
69
|
+
|
33
70
|
baseclassname: str = "class0"
|
34
71
|
|
35
72
|
|
36
73
|
@dataclass
|
37
74
|
class Class1(BaseClass):
|
75
|
+
"""Example subclass 1 for the ClassEnum demonstration.
|
76
|
+
|
77
|
+
Attributes:
|
78
|
+
classname (str): A name for this class
|
79
|
+
"""
|
80
|
+
|
38
81
|
classname: str = "class1"
|
39
82
|
|
40
83
|
|
41
84
|
@dataclass
|
42
85
|
class Class2(BaseClass):
|
86
|
+
"""Example subclass 2 for the ClassEnum demonstration.
|
87
|
+
|
88
|
+
Attributes:
|
89
|
+
classname (str): A name for this class
|
90
|
+
"""
|
91
|
+
|
43
92
|
classname: str = "class2"
|
44
93
|
|
45
94
|
|
46
95
|
class ExampleEnum(ClassEnum):
|
96
|
+
"""An example implementation of ClassEnum.
|
97
|
+
|
98
|
+
This enum demonstrates how to use ClassEnum to map enum values to class instances.
|
99
|
+
Each enum value corresponds to a class name that can be instantiated.
|
100
|
+
"""
|
101
|
+
|
47
102
|
Class1 = auto()
|
48
103
|
Class2 = auto()
|
49
104
|
|
50
105
|
@classmethod
|
51
106
|
def to_class(cls, enum_val: ExampleEnum) -> BaseClass:
|
107
|
+
"""Convert an ExampleEnum value to its corresponding class instance.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
enum_val (ExampleEnum): The enum value to convert
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
BaseClass: An instance of either Class1 or Class2, depending on the enum value
|
114
|
+
"""
|
52
115
|
return cls.to_class_generic("bencher.class_enum", enum_val)
|
@@ -25,7 +25,7 @@ class ExampleMergeDataset(bch.ParametrizedSweep):
|
|
25
25
|
|
26
26
|
def example_dataset(run_cfg: bch.BenchRunCfg = None, report: bch.BenchReport = None):
|
27
27
|
bench = ExampleMergeDataset().to_bench(run_cfg, report)
|
28
|
-
|
28
|
+
bench.plot_sweep(input_vars=["value"], const_vars=dict(repeats_x=4))
|
29
29
|
# bench.report.append(res.to_panes(target_dimension=1))
|
30
30
|
# bench.report.append(res.to_panes(target_dimension=2))
|
31
31
|
# bench.reprt.append(res.to_video_grid
|
@@ -39,7 +39,7 @@ def example_dataset(run_cfg: bch.BenchRunCfg = None, report: bch.BenchReport = N
|
|
39
39
|
# )
|
40
40
|
# bench.report.append(res.to_panes(container=hv.Bars,target_dimension=1))
|
41
41
|
# bench.report.append(res.to_panes(container=hv.Curve))
|
42
|
-
bench.
|
42
|
+
bench.add(bch.DataSetResult, container=hv.Curve)
|
43
43
|
return bench
|
44
44
|
|
45
45
|
|
@@ -1,179 +1,23 @@
|
|
1
1
|
import bencher as bch
|
2
|
-
from bencher.
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
def __call__(self, **kwargs: Any) -> Any:
|
20
|
-
self.update_params_from_kwargs(**kwargs)
|
21
|
-
self.output = math.sin(self.xval) + math.cos(self.yval)
|
22
|
-
self.hmap = hv.Points((self.xval, self.yval)).opts(
|
23
|
-
marker="o", size=110 - self.level * 20, color=int_to_col(self.level - 1)
|
24
|
-
)
|
25
|
-
|
26
|
-
return self.get_results_values_as_dict()
|
27
|
-
|
28
|
-
|
29
|
-
class RunWithLevel(bch.ParametrizedSweep):
|
30
|
-
level = bch.IntSweep(default=1, bounds=[1, 8])
|
31
|
-
dimensions = bch.IntSweep(default=1, bounds=[1, 2])
|
32
|
-
|
33
|
-
level_samples = bch.ResultVar()
|
34
|
-
|
35
|
-
def __call__(self, **kwargs) -> dict():
|
36
|
-
self.update_params_from_kwargs(**kwargs)
|
37
|
-
|
38
|
-
self.level_samples = int(
|
39
|
-
pow(
|
40
|
-
len(bch.FloatSweep(bounds=[0, 1]).with_level(self.level).values()),
|
41
|
-
self.dimensions,
|
42
|
-
)
|
43
|
-
)
|
44
|
-
return self.get_results_values_as_dict()
|
45
|
-
|
46
|
-
|
47
|
-
def run_with_dim(bench: bch.Bench, dims: List[bch.SweepBase]) -> List[bch.BenchResult]:
|
48
|
-
results = []
|
49
|
-
for level in range(1, 6):
|
50
|
-
print(level)
|
51
|
-
res = bench.plot_sweep(
|
52
|
-
f"Level:{level}",
|
53
|
-
input_vars=dims,
|
54
|
-
const_vars=LevelsExample.get_input_defaults(
|
55
|
-
[LevelsExample.param.level.with_const(level)]
|
56
|
-
),
|
57
|
-
result_vars=[LevelsExample.param.output, LevelsExample.param.hmap],
|
58
|
-
run_cfg=bch.BenchRunCfg(level=level, auto_plot=False),
|
59
|
-
)
|
60
|
-
|
61
|
-
results.append(res)
|
62
|
-
return results
|
63
|
-
|
64
|
-
|
65
|
-
def run_levels_1D(bench: bch.Bench) -> bch.Bench:
|
66
|
-
results = run_with_dim(bench, [LevelsExample.param.xval])
|
67
|
-
bench.report.append_title("Using Levels to define sample density")
|
68
|
-
|
69
|
-
bench1 = bch.Bench("levels", RunWithLevel(), run_cfg=bch.BenchRunCfg(auto_plot=False))
|
70
|
-
res1 = bench1.plot_sweep("Levels", input_vars=[RunWithLevel.param.level])
|
71
|
-
|
72
|
-
bench.report.append_markdown(
|
73
|
-
"Sample levels let you perform parameter sweeps without having to decide how many samples to take when defining the class. If you perform a sweep at level 2, then all the points are reused when sampling at level 3. The higher levels reuse the points from lower levels to avoid having to recompute potentially expensive samples. The other advantage is that it enables a workflow where you can quickly see the results of the sweep at a low resolution to sense check the code, and then run it at a high level to get the fidelity you want. When calling a sweep at a high level, you can publish the intermediate lower level results as the computiation continues so that you can track the progress of the computation and end the sweep early when you have sufficient resolution",
|
74
|
-
width=600,
|
75
|
-
)
|
76
|
-
row = pn.Row()
|
77
|
-
row.append(res1.to_table())
|
78
|
-
# row.append(res1.to_curve().opts(shared_axes=False))
|
79
|
-
row.append(res1.to_curve())
|
80
|
-
|
81
|
-
bench.report.append(row)
|
82
|
-
|
83
|
-
bench.report.append_markdown(
|
84
|
-
"Level 1 returns a single point at the lower bound of the parameter. Level 2 uses the upper and lower bounds of the parameter. All subsequent levels are created by adding a sample between each previously calculated sample to ensure that all previous values can be reused while retaining an equal sample spacing. The following plots show the sample points as circles and the corresponding plot of a sin function sampled at that level.",
|
85
|
-
width=600,
|
2
|
+
from bencher.example.meta.example_meta import BenchMeta
|
3
|
+
|
4
|
+
|
5
|
+
def example_levels(run_cfg: bch.BenchRunCfg = None, report: bch.BenchReport = None) -> bch.Bench:
|
6
|
+
bench = BenchMeta().to_bench(run_cfg, report)
|
7
|
+
|
8
|
+
bench.plot_sweep(
|
9
|
+
title="Using Levels to define sample density",
|
10
|
+
description="Sample levels let you perform parameter sweeps without having to decide how many samples to take when defining the class. If you perform a sweep at level 2, then all the points are reused when sampling at level 3. The higher levels reuse the points from lower levels to avoid having to recompute potentially expensive samples. The other advantage is that it enables a workflow where you can quickly see the results of the sweep at a low resolution to sense check the code, and then run it at a high level to get the fidelity you want. When calling a sweep at a high level, you can publish the intermediate lower level results as the computiation continues so that you can track the progress of the computation and end the sweep early when you have sufficient resolution",
|
11
|
+
input_vars=[
|
12
|
+
bch.p("float_vars", [1, 2]),
|
13
|
+
bch.p("level", [2, 3, 4, 5]),
|
14
|
+
],
|
15
|
+
const_vars=[
|
16
|
+
BenchMeta.param.categorical_vars.with_const(0),
|
17
|
+
],
|
86
18
|
)
|
87
|
-
|
88
|
-
combined_pts = hv.Overlay()
|
89
|
-
combined_curve = hv.Overlay()
|
90
|
-
for it, r in enumerate(results):
|
91
|
-
lvl = it + 1
|
92
|
-
row = pn.Row()
|
93
|
-
pts = r.to_holomap().overlay().opts(title=f"Sample Points for level: {lvl}", height=300)
|
94
|
-
ds = r.to_hv_dataset()
|
95
|
-
crv = r.to_curve_ds(ds.data).opts(shared_axes=False, height=300) * r.to_hv_dataset(
|
96
|
-
bch.ReduceType.NONE
|
97
|
-
).to(hv.Scatter).opts(
|
98
|
-
title=f"Function Values for level: {lvl}", size=5, height=300, shared_axes=False
|
99
|
-
)
|
100
|
-
|
101
|
-
combined_pts *= pts
|
102
|
-
combined_curve *= crv
|
103
|
-
row.append(pts)
|
104
|
-
row.append(crv)
|
105
|
-
bench.report.append_markdown(f"## {r.bench_cfg.title}")
|
106
|
-
bench.report.append(row)
|
107
|
-
|
108
|
-
bench.report.append_markdown(
|
109
|
-
"This plot overlays the previous plots into a single image. It shows how each level overlaps the previous level"
|
110
|
-
)
|
111
|
-
|
112
|
-
bench.report.append(pn.Row(combined_pts, combined_curve))
|
113
|
-
return bench
|
114
|
-
|
115
|
-
|
116
|
-
def run_levels_2D(bench: bch.Bench) -> bch.Bench:
|
117
|
-
results = run_with_dim(bench, [LevelsExample.param.xval, LevelsExample.param.yval])
|
118
|
-
bench.report.append_markdown("# Using Levels to define 2D sample density", "Levels 2D")
|
119
|
-
|
120
|
-
bench1 = bch.Bench("lol", RunWithLevel(), run_cfg=bch.BenchRunCfg(auto_plot=False))
|
121
|
-
res1 = bench1.plot_sweep(
|
122
|
-
"Levels",
|
123
|
-
input_vars=[RunWithLevel.param.level],
|
124
|
-
const_vars=[RunWithLevel.param.dimensions.with_const(2)],
|
125
|
-
)
|
126
|
-
row = pn.Row()
|
127
|
-
row.append(res1.to_table())
|
128
|
-
# row.append(res1.to_curve().opts(shared_axes=False))
|
129
|
-
row.append(res1.to_curve())
|
130
|
-
|
131
|
-
bench.report.append(row)
|
132
|
-
|
133
|
-
for it, r in enumerate(results):
|
134
|
-
lvl = it + 1
|
135
|
-
row = pn.Row()
|
136
|
-
bench.report.append_markdown(f"## {r.bench_cfg.title}")
|
137
|
-
row.append(
|
138
|
-
r.to_holomap()
|
139
|
-
.overlay()
|
140
|
-
.opts(title=f"Sample Points for level: {lvl}", shared_axes=False)
|
141
|
-
)
|
142
|
-
row.append(
|
143
|
-
r.to_heatmap_single(r.bench_cfg.result_vars[0], bch.ReduceType.NONE).opts(
|
144
|
-
title=f"Function Value Heatmap for level: {lvl}", shared_axes=False
|
145
|
-
)
|
146
|
-
)
|
147
|
-
bench.report.append(row)
|
148
|
-
|
149
|
-
bench.report.append_markdown(
|
150
|
-
"This plot overlays the previous plots into a single image. It shows how each level overlaps the previous level"
|
151
|
-
)
|
152
|
-
overlay = hv.Overlay()
|
153
|
-
for lvl, r in enumerate(results):
|
154
|
-
overlay *= (
|
155
|
-
r.to_holomap()
|
156
|
-
.overlay()
|
157
|
-
.opts(width=1000, height=1000, show_legend=False, shared_axes=False)
|
158
|
-
)
|
159
|
-
|
160
|
-
bench.report.append(overlay)
|
161
|
-
return bench
|
162
|
-
|
163
|
-
|
164
|
-
def run_levels(run_cfg: bch.BenchRunCfg = None, report: bch.BenchReport = None) -> bch.Bench:
|
165
|
-
hv.extension("bokeh")
|
166
|
-
opts.defaults(
|
167
|
-
opts.Curve(show_legend=False),
|
168
|
-
opts.Points(show_legend=False),
|
169
|
-
)
|
170
|
-
|
171
|
-
bench = bch.Bench("Levels", LevelsExample(), run_cfg=run_cfg, report=report)
|
172
|
-
bench = run_levels_1D(bench)
|
173
|
-
bench = run_levels_2D(bench)
|
174
|
-
|
175
19
|
return bench
|
176
20
|
|
177
21
|
|
178
22
|
if __name__ == "__main__":
|
179
|
-
|
23
|
+
example_levels().report.show()
|
@@ -1,51 +1,127 @@
|
|
1
|
-
|
1
|
+
"""
|
2
|
+
Advanced Pareto Optimization Example with Randomness
|
3
|
+
|
4
|
+
This example demonstrates multi-objective optimization using Optuna with the Bencher framework.
|
5
|
+
It shows how to:
|
6
|
+
1. Define a problem with multiple competing objectives
|
7
|
+
2. Use Optuna's multi-objective optimization capabilities
|
8
|
+
3. Visualize and analyze the Pareto front
|
9
|
+
4. Compare different optimization approaches
|
10
|
+
5. Demonstrate the effect of randomness on Pareto optimization
|
11
|
+
"""
|
2
12
|
|
3
13
|
import bencher as bch
|
14
|
+
import numpy as np
|
15
|
+
|
16
|
+
np.random.seed(0)
|
17
|
+
|
18
|
+
|
19
|
+
class EngineeringDesignProblem(bch.ParametrizedSweep):
|
20
|
+
"""
|
21
|
+
A simplified engineering design problem with two competing objectives.
|
22
|
+
|
23
|
+
This example simulates a common engineering trade-off problem:
|
24
|
+
- Performance vs Cost
|
25
|
+
|
26
|
+
This is a classic multi-objective optimization scenario.
|
27
|
+
The problem includes controlled randomness to simulate real-world variability
|
28
|
+
in manufacturing processes and materials.
|
29
|
+
"""
|
30
|
+
|
31
|
+
# Input design parameters - reduced to just 2
|
32
|
+
material_quality = bch.FloatSweep(
|
33
|
+
default=0.5, bounds=[0.1, 1.0], doc="Quality of the material (dimensionless)", samples=20
|
34
|
+
)
|
35
|
+
thickness = bch.FloatSweep(
|
36
|
+
default=0.05, bounds=[0.01, 0.2], doc="Component thickness (m)", samples=20
|
37
|
+
)
|
38
|
+
|
39
|
+
# Result variables - reduced to just 2 objectives to be optimized
|
40
|
+
performance = bch.ResultVar("score", bch.OptDir.maximize, doc="Performance metric (maximize)")
|
41
|
+
cost = bch.ResultVar("$", bch.OptDir.minimize, doc="Manufacturing cost (minimize)")
|
42
|
+
|
43
|
+
def __call__(self, **kwargs) -> dict:
|
44
|
+
"""
|
45
|
+
Calculate the multi-objective outcomes based on input parameters.
|
4
46
|
|
5
|
-
|
6
|
-
|
47
|
+
This simulates an engineering design problem where various objectives
|
48
|
+
compete with each other:
|
49
|
+
- Higher quality materials improve performance but increase cost
|
50
|
+
- Thicker material improves performance but increases cost
|
51
|
+
|
52
|
+
Includes inherent randomness to simulate:
|
53
|
+
- Manufacturing variability
|
54
|
+
- Material property variations
|
55
|
+
- Measurement uncertainty
|
56
|
+
"""
|
57
|
+
self.update_params_from_kwargs(**kwargs)
|
58
|
+
|
59
|
+
# Base performance calculation
|
60
|
+
base_performance = self.material_quality * 80 + self.thickness * 50
|
61
|
+
|
62
|
+
# Add significant randomness (standard deviation = 10% of the base value)
|
63
|
+
# This will create noticeably different results on each run
|
64
|
+
performance_variability = 0.15 * base_performance
|
65
|
+
self.performance = base_performance + np.random.normal(0, performance_variability)
|
66
|
+
|
67
|
+
# Introduce a 30% chance of failure (e.g., due to manufacturing defects)
|
68
|
+
if np.random.rand() < 0.3:
|
69
|
+
self.performance = np.nan
|
70
|
+
|
71
|
+
# Base cost calculation
|
72
|
+
base_cost = self.material_quality * 100 + 10 / (self.thickness + 0.01)
|
73
|
+
|
74
|
+
# Add randomness to cost (standard deviation = 8% of the base value)
|
75
|
+
# Manufacturing costs can vary significantly in real-world scenarios
|
76
|
+
cost_variability = 0.12 * base_cost
|
77
|
+
self.cost = base_cost + np.random.normal(0, cost_variability)
|
78
|
+
|
79
|
+
return self.get_results_values_as_dict()
|
7
80
|
|
8
81
|
|
9
82
|
def example_pareto(run_cfg: bch.BenchRunCfg = None, report: bch.BenchReport = None) -> bch.Bench:
|
10
|
-
"""
|
83
|
+
"""
|
84
|
+
Advanced example of multi-objective Pareto optimization using Optuna.
|
85
|
+
|
86
|
+
This function demonstrates:
|
87
|
+
1. Grid search approach to visualize the entire parameter space
|
88
|
+
2. True multi-objective optimization with Optuna
|
89
|
+
3. Analysis of the Pareto front
|
90
|
+
4. Effect of randomness on the Pareto front
|
11
91
|
|
12
92
|
Args:
|
13
|
-
run_cfg (BenchRunCfg):
|
93
|
+
run_cfg (BenchRunCfg): Configuration for the benchmark run
|
94
|
+
report (BenchReport): Report object to store results
|
14
95
|
|
15
96
|
Returns:
|
16
|
-
Bench:
|
97
|
+
Bench: Benchmark object with results
|
17
98
|
"""
|
99
|
+
if run_cfg is None:
|
100
|
+
run_cfg = bch.BenchRunCfg()
|
101
|
+
run_cfg.repeats = 5 # Multiple repeats to demonstrate randomness effects
|
102
|
+
run_cfg.level = 4
|
103
|
+
|
104
|
+
# Set up Optuna for multi-objective optimization
|
18
105
|
run_cfg.use_optuna = True
|
19
106
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
)
|
107
|
+
# Important: Set multiple repeats to demonstrate the effect of randomness
|
108
|
+
# The framework will automatically calculate and plot both individual runs and averages
|
109
|
+
run_cfg.repeats = 5
|
110
|
+
|
111
|
+
# Create problem definition and benchmark
|
112
|
+
bench = EngineeringDesignProblem().to_bench(run_cfg, report)
|
27
113
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
ExampleBenchCfgIn.param.theta,
|
33
|
-
ExampleBenchCfgIn.param.offset,
|
34
|
-
],
|
35
|
-
result_vars=[ExampleBenchCfgOut.param.out_sin, ExampleBenchCfgOut.param.out_cos],
|
36
|
-
const_vars=ExampleBenchCfgIn.get_input_defaults(
|
37
|
-
[ExampleBenchCfgIn.param.noisy.with_const(True)]
|
38
|
-
),
|
39
|
-
post_description="""# Post Description
|
40
|
-
This is a slightly unusual way of doing pareto optimisation as we are not using a typical multi-objective optimisation algorithm [TODO, add example]. Instead we are performing a grid search and looking at the resulting pareto plot. The reason for doing a grid search instead of standard pareto optimisation is that we can produce more isolated plots of how an input affects an output which can help understanding of the parameter space. Future examples will show how to use grid search to bootstrap further optimisation with a multi objective optimiser""",
|
114
|
+
# Perform grid search on our two input variables
|
115
|
+
grid_result = bench.plot_sweep(
|
116
|
+
title="Parameter Space Exploration with Variability",
|
117
|
+
description="Exploring how material quality and thickness affect performance and cost with inherent randomness",
|
41
118
|
)
|
42
119
|
|
43
|
-
|
120
|
+
# Add Optuna-specific visualizations
|
121
|
+
bench.report.append(grid_result.to_optuna_plots())
|
122
|
+
|
44
123
|
return bench
|
45
124
|
|
46
125
|
|
47
126
|
if __name__ == "__main__":
|
48
|
-
|
49
|
-
run_cfg_ex.repeats = 2
|
50
|
-
run_cfg_ex.level = 2
|
51
|
-
example_pareto(run_cfg_ex).report.show()
|
127
|
+
example_pareto().report.show()
|
@@ -23,7 +23,7 @@ else:
|
|
23
23
|
# publish data to a github branch
|
24
24
|
bch.publish_and_view_rrd(
|
25
25
|
file_path,
|
26
|
-
remote="https://github.com/
|
26
|
+
remote="https://github.com/blooop/bencher.git",
|
27
27
|
branch_name="test_rrd",
|
28
28
|
content_callback=bch.github_content,
|
29
29
|
).show()
|
@@ -16,14 +16,14 @@ def example_1D_bool(run_cfg: bch.BenchRunCfg) -> bch.Bench:
|
|
16
16
|
)
|
17
17
|
|
18
18
|
# here we sample the input variable theta and plot the value of output1. The (noisy) function is sampled 20 times so you can see the distribution
|
19
|
-
|
19
|
+
bench.plot_sweep(
|
20
20
|
title="Example 1D Bool",
|
21
21
|
input_vars=[ExampleBenchCfgIn.param.noisy],
|
22
22
|
result_vars=[ExampleBenchCfgOut.param.out_sin],
|
23
23
|
description=example_1D_bool.__doc__,
|
24
24
|
run_cfg=run_cfg,
|
25
25
|
)
|
26
|
-
bench.
|
26
|
+
bench.add(bch.BarResult)
|
27
27
|
|
28
28
|
return bench
|
29
29
|
|
@@ -21,7 +21,12 @@ def example_2D_float(run_cfg: bch.BenchRunCfg = None, report: bch.BenchReport =
|
|
21
21
|
"""This example shows how to sample a 1 dimensional float variable and plot the result of passing that parameter sweep to the benchmarking function"""
|
22
22
|
|
23
23
|
bench = SimpleFloat().to_bench(run_cfg, report)
|
24
|
-
bench.plot_sweep()
|
24
|
+
res = bench.plot_sweep()
|
25
|
+
|
26
|
+
bench.add(bch.CurveResult)
|
27
|
+
bench.report.append(res.to(bch.CurveResult))
|
28
|
+
bench.report.append(res.to(bch.HeatmapResult))
|
29
|
+
bench.add(bch.BarResult)
|
25
30
|
return bench
|
26
31
|
|
27
32
|
|
bencher/example/example_video.py
CHANGED
@@ -7,7 +7,7 @@ bench = bch.Bench("Bencher_Example_Simple", bench_function, ExampleBenchCfgIn)
|
|
7
7
|
|
8
8
|
|
9
9
|
if __name__ == "__main__":
|
10
|
-
|
10
|
+
res = bench.plot_sweep(
|
11
11
|
input_vars=[ExampleBenchCfgIn.param.theta, ExampleBenchCfgIn.param.offset],
|
12
12
|
result_vars=[ExampleBenchCfgOut.param.out_sin],
|
13
13
|
title="Float 1D Example",
|
@@ -34,5 +34,5 @@ if __name__ == "__main__":
|
|
34
34
|
),
|
35
35
|
)
|
36
36
|
|
37
|
-
bench.
|
37
|
+
bench.add(bch.ExplorerResult)
|
38
38
|
bench.report.show()
|
@@ -5,13 +5,13 @@ import random
|
|
5
5
|
|
6
6
|
|
7
7
|
class SimpleFloat0D(bch.ParametrizedSweep):
|
8
|
-
"""This class has 0 input dimensions and 1 output
|
8
|
+
"""This class has 0 input dimensions and 1 output dimension. It samples from a Gaussian distribution"""
|
9
9
|
|
10
10
|
# This defines a variable that we want to plot
|
11
11
|
output = bch.ResultVar(units="ul", doc="a sample from a gaussian distribution")
|
12
12
|
|
13
13
|
def __call__(self, **kwargs) -> dict:
|
14
|
-
"""Generate a sample from a
|
14
|
+
"""Generate a sample from a Gaussian distribution
|
15
15
|
|
16
16
|
Returns:
|
17
17
|
dict: a dictionary with all the result variables in the ParametrisedSweep class as named key value pairs.
|
@@ -24,31 +24,41 @@ class SimpleFloat0D(bch.ParametrizedSweep):
|
|
24
24
|
def example_0_in_1_out(
|
25
25
|
run_cfg: bch.BenchRunCfg = None, report: bch.BenchReport = None
|
26
26
|
) -> bch.Bench:
|
27
|
-
"""This example shows how to sample a
|
27
|
+
"""This example shows how to sample a 0-dimensional variable (no input parameters)
|
28
|
+
and plot the result of that sampling operation.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
run_cfg: Configuration for the benchmark run
|
32
|
+
report: Report to append the results to
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
bch.Bench: The benchmark object
|
36
|
+
"""
|
28
37
|
|
29
38
|
bench = SimpleFloat0D().to_bench(run_cfg, report)
|
30
39
|
bench.plot_sweep()
|
31
40
|
|
32
|
-
bench.
|
41
|
+
bench.add(bch.TableResult)
|
33
42
|
return bench
|
34
43
|
|
35
44
|
|
36
45
|
if __name__ == "__main__":
|
37
|
-
|
38
|
-
|
39
|
-
#
|
46
|
+
example_0_in_1_out().report.show()
|
47
|
+
# run_config = bch.BenchRunCfg(repeats=100)
|
48
|
+
# report_obj = bch.BenchReport()
|
49
|
+
# example_0_in_1_out(run_config, report_obj).report.show()
|
40
50
|
|
41
51
|
# run_cfg.over_time = True
|
42
52
|
# run_cfg.cache_samples = True
|
43
53
|
# for i in range(4):
|
44
|
-
# example_0_in_1_out(run_cfg,
|
54
|
+
# example_0_in_1_out(run_cfg, report_obj)
|
45
55
|
|
46
|
-
run_config.over_time = True
|
47
|
-
run_config.auto_plot = False
|
48
|
-
for _ in range(4):
|
49
|
-
|
56
|
+
# run_config.over_time = True
|
57
|
+
# run_config.auto_plot = False
|
58
|
+
# for _ in range(4):
|
59
|
+
# example_0_in_1_out(run_config, report_obj)
|
50
60
|
|
51
|
-
run_config.auto_plot = True
|
52
|
-
example_0_in_1_out(run_config,
|
61
|
+
# run_config.auto_plot = True
|
62
|
+
# example_0_in_1_out(run_config, report_obj)
|
53
63
|
|
54
|
-
|
64
|
+
# report_obj.show()
|