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.
Files changed (84) hide show
  1. bencher/__init__.py +20 -2
  2. bencher/bench_cfg.py +262 -54
  3. bencher/bench_report.py +2 -2
  4. bencher/bench_runner.py +96 -10
  5. bencher/bencher.py +421 -89
  6. bencher/class_enum.py +70 -7
  7. bencher/example/example_dataframe.py +2 -2
  8. bencher/example/example_levels.py +17 -173
  9. bencher/example/example_pareto.py +107 -31
  10. bencher/example/example_rerun2.py +1 -1
  11. bencher/example/example_simple_bool.py +2 -2
  12. bencher/example/example_simple_float2d.py +6 -1
  13. bencher/example/example_video.py +2 -0
  14. bencher/example/experimental/example_hvplot_explorer.py +2 -2
  15. bencher/example/inputs_0D/example_0_in_1_out.py +25 -15
  16. bencher/example/inputs_0D/example_0_in_2_out.py +12 -3
  17. bencher/example/inputs_0_float/example_0_cat_in_2_out.py +88 -0
  18. bencher/example/inputs_0_float/example_1_cat_in_2_out.py +98 -0
  19. bencher/example/inputs_0_float/example_2_cat_in_2_out.py +107 -0
  20. bencher/example/inputs_0_float/example_3_cat_in_2_out.py +111 -0
  21. bencher/example/inputs_1D/example1d_common.py +48 -12
  22. bencher/example/inputs_1D/example_0_float_1_cat.py +33 -0
  23. bencher/example/inputs_1D/example_1_cat_in_2_out_repeats.py +68 -0
  24. bencher/example/inputs_1D/example_1_float_2_cat_repeats.py +3 -0
  25. bencher/example/inputs_1D/example_1_int_in_1_out.py +98 -0
  26. bencher/example/inputs_1D/example_1_int_in_2_out.py +101 -0
  27. bencher/example/inputs_1D/example_1_int_in_2_out_repeats.py +99 -0
  28. bencher/example/inputs_1_float/example_1_float_0_cat_in_2_out.py +117 -0
  29. bencher/example/inputs_1_float/example_1_float_1_cat_in_2_out.py +124 -0
  30. bencher/example/inputs_1_float/example_1_float_2_cat_in_2_out.py +132 -0
  31. bencher/example/inputs_1_float/example_1_float_3_cat_in_2_out.py +140 -0
  32. bencher/example/inputs_2D/example_2_cat_in_4_out_repeats.py +104 -0
  33. bencher/example/inputs_2_float/example_2_float_0_cat_in_2_out.py +98 -0
  34. bencher/example/inputs_2_float/example_2_float_1_cat_in_2_out.py +112 -0
  35. bencher/example/inputs_2_float/example_2_float_2_cat_in_2_out.py +122 -0
  36. bencher/example/inputs_2_float/example_2_float_3_cat_in_2_out.py +138 -0
  37. bencher/example/inputs_3_float/example_3_float_0_cat_in_2_out.py +111 -0
  38. bencher/example/inputs_3_float/example_3_float_1_cat_in_2_out.py +117 -0
  39. bencher/example/inputs_3_float/example_3_float_2_cat_in_2_out.py +124 -0
  40. bencher/example/inputs_3_float/example_3_float_3_cat_in_2_out.py +129 -0
  41. bencher/example/meta/generate_examples.py +118 -7
  42. bencher/example/meta/generate_meta.py +88 -40
  43. bencher/job.py +174 -9
  44. bencher/plotting/plot_filter.py +52 -17
  45. bencher/results/bench_result.py +117 -25
  46. bencher/results/bench_result_base.py +117 -8
  47. bencher/results/dataset_result.py +6 -200
  48. bencher/results/explorer_result.py +23 -0
  49. bencher/results/{hvplot_result.py → histogram_result.py} +3 -18
  50. bencher/results/holoview_results/__init__.py +0 -0
  51. bencher/results/holoview_results/bar_result.py +79 -0
  52. bencher/results/holoview_results/curve_result.py +110 -0
  53. bencher/results/holoview_results/distribution_result/__init__.py +0 -0
  54. bencher/results/holoview_results/distribution_result/box_whisker_result.py +73 -0
  55. bencher/results/holoview_results/distribution_result/distribution_result.py +109 -0
  56. bencher/results/holoview_results/distribution_result/scatter_jitter_result.py +92 -0
  57. bencher/results/holoview_results/distribution_result/violin_result.py +70 -0
  58. bencher/results/holoview_results/heatmap_result.py +319 -0
  59. bencher/results/holoview_results/holoview_result.py +346 -0
  60. bencher/results/holoview_results/line_result.py +240 -0
  61. bencher/results/holoview_results/scatter_result.py +107 -0
  62. bencher/results/holoview_results/surface_result.py +158 -0
  63. bencher/results/holoview_results/table_result.py +14 -0
  64. bencher/results/holoview_results/tabulator_result.py +20 -0
  65. bencher/results/optuna_result.py +30 -115
  66. bencher/results/video_controls.py +38 -0
  67. bencher/results/video_result.py +39 -36
  68. bencher/results/video_summary.py +2 -2
  69. bencher/results/{plotly_result.py → volume_result.py} +29 -8
  70. bencher/utils.py +175 -26
  71. bencher/variables/inputs.py +122 -15
  72. bencher/video_writer.py +2 -1
  73. bencher/worker_job.py +31 -3
  74. {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/METADATA +24 -24
  75. holobench-1.43.0.dist-info/RECORD +147 -0
  76. bencher/example/example_levels2.py +0 -37
  77. bencher/example/inputs_1D/example_1_in_1_out.py +0 -62
  78. bencher/example/inputs_1D/example_1_in_2_out.py +0 -63
  79. bencher/example/inputs_1D/example_1_in_2_out_repeats.py +0 -61
  80. bencher/results/holoview_result.py +0 -796
  81. bencher/results/panel_result.py +0 -41
  82. holobench-1.41.0.dist-info/RECORD +0 -114
  83. {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/WHEEL +0 -0
  84. {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 ClassEnum is a pattern to make it easier to create factory a factory method that converts from an enum to a corresponding class. Subclasses should implement to_class(enum_instance:EnumType) which takes an enum returns the corresponding instance of that class."""
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 the class referred to by this enum
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
- """Subclasses should overrides this method to take an enum returns the corresponding instance of that class."""
28
- raise NotImplementedError()
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
- res = bench.plot_sweep(input_vars=["value"], const_vars=dict(repeats_x=4))
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.report.append(res.to_dataset1(container=hv.Curve))
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.utils import int_to_col
3
-
4
- import math
5
- import holoviews as hv
6
- from typing import Any, List
7
- import panel as pn
8
- from holoviews import opts
9
-
10
-
11
- class LevelsExample(bch.ParametrizedSweep):
12
- xval = bch.FloatSweep(bounds=[0, 3.14])
13
- yval = bch.FloatSweep(bounds=[0, 3.14])
14
- level = bch.IntSweep(default=1, bounds=[1, 6])
15
-
16
- output = bch.ResultVar(units="v")
17
- hmap = bch.ResultHmap()
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
- run_levels().report.show()
23
+ example_levels().report.show()
@@ -1,51 +1,127 @@
1
- # pylint: disable=duplicate-code
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
- # All the examples will be using the data structures and benchmark function defined in this file
6
- from bencher.example.benchmark_data import ExampleBenchCfgIn, ExampleBenchCfgOut, bench_function
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
- """Example of how to calculate the pareto front of a parameter sweep
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): configuration of how to perform the param sweep
93
+ run_cfg (BenchRunCfg): Configuration for the benchmark run
94
+ report (BenchReport): Report object to store results
14
95
 
15
96
  Returns:
16
- Bench: results of the parameter sweep
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
- bench = bch.Bench(
21
- "Multi-objective optimisation",
22
- bench_function,
23
- ExampleBenchCfgIn,
24
- run_cfg=run_cfg,
25
- report=report,
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
- res = bench.plot_sweep(
29
- title="Pareto Optimisation with Optuna",
30
- description="This example shows how to plot the pareto front of the tradeoff between multiple criteria. When multiple result variable are defined, and use_optuna=True a pareto plot and the relative importance of each input variable on the output criteria is plotted. A summary of the points on the pareto front is printed as well. You can use the pareto plot to decide the how to trade off one objective for another. Pareto plots are supported for 2D and 3D. If you have more than 3 result variables the first 3 are selected for the pareto plot. Plotting 4D surfaces is left as an exercise to the reader",
31
- input_vars=[
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
- bench.report.append(res.to_optuna_plots())
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
- run_cfg_ex = bch.BenchRunCfg()
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/dyson-ai/bencher.git",
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
- res = bench.plot_sweep(
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.report.append(res.to_bar())
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
 
@@ -119,6 +119,8 @@ def example_video_tap(
119
119
  )
120
120
  )
121
121
 
122
+ bench.worker_class_instance.get_results_only()
123
+
122
124
  return bench
123
125
 
124
126
 
@@ -7,7 +7,7 @@ bench = bch.Bench("Bencher_Example_Simple", bench_function, ExampleBenchCfgIn)
7
7
 
8
8
 
9
9
  if __name__ == "__main__":
10
- bench_out = bench.plot_sweep(
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.report.append(bench_out.to_explorer())
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 dimensions. It samples from a gaussian distribution"""
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 uniform distribution
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 1 dimensional float variable and plot the result of passing that parameter sweep to the benchmarking function"""
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.report.append(bench.get_result().to_table())
41
+ bench.add(bch.TableResult)
33
42
  return bench
34
43
 
35
44
 
36
45
  if __name__ == "__main__":
37
- run_config = bch.BenchRunCfg(repeats=100)
38
- reprt = bch.BenchReport()
39
- # example_0_in_1_out(run_cfg, report).report.show()
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, report)
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
- example_0_in_1_out(run_config, reprt)
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, reprt)
61
+ # run_config.auto_plot = True
62
+ # example_0_in_1_out(run_config, report_obj)
53
63
 
54
- reprt.show()
64
+ # report_obj.show()