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/bencher.py CHANGED
@@ -2,7 +2,8 @@ import logging
2
2
  from datetime import datetime
3
3
  from itertools import product, combinations
4
4
 
5
- from typing import Callable, List, Optional
5
+ from param import Parameter
6
+ from typing import Callable, List, Optional, Tuple, Any
6
7
  from copy import deepcopy
7
8
  import numpy as np
8
9
  import param
@@ -46,8 +47,22 @@ for handler in logging.root.handlers:
46
47
  handler.setFormatter(formatter)
47
48
 
48
49
 
49
- def set_xarray_multidim(data_array: xr.DataArray, index_tuple, value: float) -> xr.DataArray:
50
- # """This is terrible, I need to do this in a better way, but [] does not like *args syntax and the () version of the set function doesn't either"""
50
+ def set_xarray_multidim(
51
+ data_array: xr.DataArray, index_tuple: Tuple[int, ...], value: Any
52
+ ) -> xr.DataArray:
53
+ """Set a value in a multi-dimensional xarray at the specified index position.
54
+
55
+ This function sets a value in an N-dimensional xarray using explicit matching on the
56
+ tuple length since direct indexing with variable length index tuples is not supported.
57
+
58
+ Args:
59
+ data_array (xr.DataArray): The data array to modify
60
+ index_tuple (Tuple[int, ...]): The index coordinates as a tuple
61
+ value (Any): The value to set at the specified position
62
+
63
+ Returns:
64
+ xr.DataArray: The modified data array
65
+ """
51
66
  match len(index_tuple):
52
67
  case 1:
53
68
  data_array[index_tuple[0]] = value
@@ -108,17 +123,52 @@ def set_xarray_multidim(data_array: xr.DataArray, index_tuple, value: float) ->
108
123
 
109
124
 
110
125
  def kwargs_to_input_cfg(worker_input_cfg: ParametrizedSweep, **kwargs) -> ParametrizedSweep:
126
+ """Create a configured instance of a ParametrizedSweep with the provided keyword arguments.
127
+
128
+ Args:
129
+ worker_input_cfg (ParametrizedSweep): The ParametrizedSweep class to instantiate
130
+ **kwargs: Keyword arguments to update the configuration with
131
+
132
+ Returns:
133
+ ParametrizedSweep: A configured instance of the worker_input_cfg class
134
+ """
111
135
  input_cfg = worker_input_cfg()
112
136
  input_cfg.param.update(kwargs)
113
137
  return input_cfg
114
138
 
115
139
 
116
- def worker_cfg_wrapper(worker, worker_input_cfg: ParametrizedSweep, **kwargs) -> dict:
140
+ def worker_cfg_wrapper(worker: Callable, worker_input_cfg: ParametrizedSweep, **kwargs) -> dict:
141
+ """Wrap a worker function to accept keyword arguments instead of a config object.
142
+
143
+ This wrapper creates an instance of the worker_input_cfg class, updates it with the
144
+ provided keyword arguments, and passes it to the worker function.
145
+
146
+ Args:
147
+ worker (Callable): The worker function that expects a config object
148
+ worker_input_cfg (ParametrizedSweep): The class defining the configuration
149
+ **kwargs: Keyword arguments to update the configuration with
150
+
151
+ Returns:
152
+ dict: The result of calling the worker function with the configured input
153
+ """
117
154
  input_cfg = kwargs_to_input_cfg(worker_input_cfg, **kwargs)
118
155
  return worker(input_cfg)
119
156
 
120
157
 
121
158
  def worker_kwargs_wrapper(worker: Callable, bench_cfg: BenchCfg, **kwargs) -> dict:
159
+ """Prepare keyword arguments and pass them to a worker function.
160
+
161
+ This wrapper helps filter out metadata parameters that should not be passed
162
+ to the worker function (like 'repeat', 'over_time', and 'time_event').
163
+
164
+ Args:
165
+ worker (Callable): The worker function to call
166
+ bench_cfg (BenchCfg): Benchmark configuration with parameters like pass_repeat
167
+ **kwargs: The keyword arguments to filter and pass to the worker
168
+
169
+ Returns:
170
+ dict: The result from the worker function
171
+ """
122
172
  function_input_deep = deepcopy(kwargs)
123
173
  if not bench_cfg.pass_repeat:
124
174
  function_input_deep.pop("repeat")
@@ -135,15 +185,32 @@ class Bench(BenchPlotServer):
135
185
  bench_name: str = None,
136
186
  worker: Callable | ParametrizedSweep = None,
137
187
  worker_input_cfg: ParametrizedSweep = None,
138
- run_cfg=None,
139
- report=None,
188
+ run_cfg: BenchRunCfg = None,
189
+ report: BenchReport = None,
140
190
  ) -> None:
141
- """Create a new Bench object from a function and a class defining the inputs to the function
191
+ """Create a new Bench object for benchmarking a worker function with parametrized inputs.
192
+
193
+ This initializes a benchmarking environment that can execute and visualize the performance
194
+ of a worker function across different parameter combinations. The worker can be either a
195
+ callable function or a ParametrizedSweep instance with a __call__ method.
142
196
 
143
197
  Args:
144
- bench_name (str): The name of the benchmark and output folder for the figures
145
- worker (Callable | ParametrizedSweep): A function that accepts a class of type (worker_input_config)
146
- worker_input_config (ParametrizedSweep): A class defining the parameters of the function.
198
+ bench_name (str): The name of the benchmark and output folder for the figures.
199
+ Must be a string.
200
+ worker (Callable | ParametrizedSweep, optional): Either a function that accepts a
201
+ ParametrizedSweep instance, or a ParametrizedSweep instance with a __call__ method.
202
+ Defaults to None.
203
+ worker_input_cfg (ParametrizedSweep, optional): A class defining the parameters expected
204
+ by the worker function. Only needed if worker is a function rather than a
205
+ ParametrizedSweep instance. Defaults to None.
206
+ run_cfg (BenchRunCfg, optional): Configuration parameters for the benchmark run,
207
+ such as caching settings, execution mode, etc. Defaults to None.
208
+ report (BenchReport, optional): An existing report to append benchmark results to.
209
+ If None, a new report will be created. Defaults to None.
210
+
211
+ Raises:
212
+ AssertionError: If bench_name is not a string.
213
+ RuntimeError: If worker is a class type instead of an instance.
147
214
  """
148
215
  assert isinstance(bench_name, str)
149
216
  self.bench_name = bench_name
@@ -178,21 +245,42 @@ class Bench(BenchPlotServer):
178
245
  self.plot = True
179
246
 
180
247
  def add_plot_callback(self, callback: Callable[[BenchResult], pn.panel], **kwargs) -> None:
181
- """Add a plotting callback that will be called on any result produced when calling a sweep function. You can pass additional arguments to the plotting function with kwargs. e.g. add_plot_callback(bch.BenchResult.to_video_grid,)
248
+ """Add a plotting callback to be called on benchmark results.
249
+
250
+ This method registers a plotting function that will be automatically called on any
251
+ BenchResult produced when running a sweep. You can pass additional arguments to
252
+ the plotting function using keyword arguments.
182
253
 
183
254
  Args:
184
- callback (Callable[[BenchResult], pn.panel]): _description_
255
+ callback (Callable[[BenchResult], pn.panel]): A function that takes a BenchResult
256
+ and returns a panel object. For example: BenchResult.to_video_grid
257
+ **kwargs: Additional keyword arguments to pass to the callback function
258
+
259
+ Examples:
260
+ >>> bench.add_plot_callback(BenchResult.to_video_grid, width=800)
185
261
  """
186
262
  self.plot_callbacks.append(partial(callback, **kwargs))
187
263
 
188
- def set_worker(self, worker: Callable, worker_input_cfg: ParametrizedSweep = None) -> None:
189
- """Set the benchmark worker function and optionally the type the worker expects
264
+ def set_worker(
265
+ self, worker: Callable | ParametrizedSweep, worker_input_cfg: ParametrizedSweep = None
266
+ ) -> None:
267
+ """Set the benchmark worker function and its input configuration.
268
+
269
+ This method sets up the worker function to be benchmarked. The worker can be either a
270
+ callable function that takes a ParametrizedSweep instance or a ParametrizedSweep
271
+ instance with a __call__ method. In the latter case, worker_input_cfg is not needed.
190
272
 
191
273
  Args:
192
- worker (Callable): The benchmark worker function
193
- worker_input_cfg (ParametrizedSweep, optional): The input type the worker expects. Defaults to None.
194
- """
274
+ worker (Callable | ParametrizedSweep): Either a function that will be benchmarked or a
275
+ ParametrizedSweep instance with a __call__ method. When a ParametrizedSweep is provided,
276
+ its __call__ method becomes the worker function.
277
+ worker_input_cfg (ParametrizedSweep, optional): The class defining the input parameters
278
+ for the worker function. Only needed if worker is a function rather than a
279
+ ParametrizedSweep instance. Defaults to None.
195
280
 
281
+ Raises:
282
+ RuntimeError: If worker is a class type instead of an instance.
283
+ """
196
284
  if isinstance(worker, ParametrizedSweep):
197
285
  self.worker_class_instance = worker
198
286
  # self.worker_class_type = type(worker)
@@ -210,7 +298,7 @@ class Bench(BenchPlotServer):
210
298
 
211
299
  def sweep_sequential(
212
300
  self,
213
- title="",
301
+ title: str = "",
214
302
  input_vars: List[ParametrizedSweep] = None,
215
303
  result_vars: List[ParametrizedSweep] = None,
216
304
  const_vars: List[ParametrizedSweep] = None,
@@ -218,9 +306,33 @@ class Bench(BenchPlotServer):
218
306
  run_cfg: BenchRunCfg = None,
219
307
  group_size: int = 1,
220
308
  iterations: int = 1,
221
- relationship_cb=None,
222
- plot_callbacks: List | bool = None,
309
+ relationship_cb: Callable = None,
310
+ plot_callbacks: List[Callable] | bool = None,
223
311
  ) -> List[BenchResult]:
312
+ """Run a sequence of benchmarks by sweeping through groups of input variables.
313
+
314
+ This method performs sweeps on combinations of input variables, potentially
315
+ using the optimal value from each sweep as constants for the next iteration.
316
+
317
+ Args:
318
+ title (str, optional): Base title for all the benchmark sweeps. Defaults to "".
319
+ input_vars (List[ParametrizedSweep], optional): Input variables to sweep through.
320
+ If None, defaults to all input variables from the worker class instance.
321
+ result_vars (List[ParametrizedSweep], optional): Result variables to collect. Defaults to None.
322
+ const_vars (List[ParametrizedSweep], optional): Variables to keep constant. Defaults to None.
323
+ optimise_var (ParametrizedSweep, optional): Variable to optimize on each sweep iteration.
324
+ The optimal value will be used as constant input for subsequent sweeps. Defaults to None.
325
+ run_cfg (BenchRunCfg, optional): Run configuration. Defaults to None.
326
+ group_size (int, optional): Number of input variables to sweep together in each run. Defaults to 1.
327
+ iterations (int, optional): Number of optimization iterations to perform. Defaults to 1.
328
+ relationship_cb (Callable, optional): Function to determine how to group variables for sweeping.
329
+ Defaults to itertools.combinations if None.
330
+ plot_callbacks (List[Callable] | bool, optional): Callbacks for plotting or bool to enable/disable.
331
+ Defaults to None.
332
+
333
+ Returns:
334
+ List[BenchResult]: A list of results from all the sweep runs
335
+ """
224
336
  if relationship_cb is None:
225
337
  relationship_cb = combinations
226
338
  if input_vars is None:
@@ -257,28 +369,43 @@ class Bench(BenchPlotServer):
257
369
  pass_repeat: bool = False,
258
370
  tag: str = "",
259
371
  run_cfg: BenchRunCfg = None,
260
- plot_callbacks: List | bool = None,
372
+ plot_callbacks: List[Callable] | bool = None,
261
373
  ) -> BenchResult:
262
- """The all in 1 function benchmarker and results plotter.
374
+ """The all-in-one function for benchmarking and results plotting.
375
+
376
+ This is the main function for performing benchmark sweeps. It handles all the setup,
377
+ execution, and visualization of benchmarks based on the input parameters.
263
378
 
264
379
  Args:
265
- input_vars (List[ParametrizedSweep], optional): _description_. Defaults to None.
266
- result_vars (List[ParametrizedSweep], optional): _description_. Defaults to None.
267
- const_vars (List[ParametrizedSweep], optional): A list of variables to keep constant with a specified value. Defaults to None.
268
- title (str, optional): The title of the benchmark. Defaults to None.
269
- description (str, optional): A description of the benchmark. Defaults to None.
270
- post_description (str, optional): A description that comes after the benchmark plots. Defaults to None.
271
- time_src (datetime, optional): Set a time that the result was generated. Defaults to datetime.now().
272
- pass_repeat (bool,optional) By default do not pass the kwarg 'repeat' to the benchmark function. Set to true if
273
- you want the benchmark function to be passed the repeat number
274
- tag (str,optional): Use tags to group different benchmarks together.
275
- run_cfg: (BenchRunCfg, optional): A config for storing how the benchmarks and run
276
- plot_callbacks: (List | bool) A list of plot callbacks to call on the results. Pass false or an empty list to turn off plotting
277
- Raises:
278
- ValueError: If a result variable is not set
380
+ title (str, optional): The title of the benchmark. If None, a title will be
381
+ generated based on the input variables. Defaults to None.
382
+ input_vars (List[ParametrizedSweep], optional): Variables to sweep through in the benchmark.
383
+ If None and worker_class_instance exists, uses input variables from it. Defaults to None.
384
+ result_vars (List[ParametrizedSweep], optional): Variables to collect results for.
385
+ If None and worker_class_instance exists, uses result variables from it. Defaults to None.
386
+ const_vars (List[ParametrizedSweep], optional): Variables to keep constant with specified values.
387
+ If None and worker_class_instance exists, uses default input values. Defaults to None.
388
+ time_src (datetime, optional): The timestamp for the benchmark. Used for time-series benchmarks.
389
+ Defaults to None, which will use the current time.
390
+ description (str, optional): A description displayed before the benchmark plots. Defaults to None.
391
+ post_description (str, optional): A description displayed after the benchmark plots.
392
+ Defaults to a generic message recommending to set a custom description.
393
+ pass_repeat (bool, optional): If True, passes the 'repeat' parameter to the worker function.
394
+ Defaults to False.
395
+ tag (str, optional): Tag to group different benchmarks together. Defaults to "".
396
+ run_cfg (BenchRunCfg, optional): Configuration for how the benchmarks are run.
397
+ If None, uses the instance's run_cfg or creates a default one. Defaults to None.
398
+ plot_callbacks (List[Callable] | bool, optional): Callbacks for plotting results.
399
+ If True, uses default plotting. If False, disables plotting.
400
+ If a list, uses the provided callbacks. Defaults to None.
279
401
 
280
402
  Returns:
281
- BenchResult: A class with all the data used to generate the results and the results
403
+ BenchResult: An object containing all the benchmark data and results
404
+
405
+ Raises:
406
+ RuntimeError: If an unsupported input variable type is provided
407
+ TypeError: If variable parameters are not of the correct type
408
+ FileNotFoundError: If only_plot=True and no cached results exist
282
409
  """
283
410
 
284
411
  input_vars_in = deepcopy(input_vars)
@@ -301,7 +428,7 @@ class Bench(BenchPlotServer):
301
428
  "No results variables passed, using all result variables in bench class:"
302
429
  )
303
430
  if self.result_vars is None:
304
- result_vars_in = self.worker_class_instance.get_results_only()
431
+ result_vars_in = self.get_result_vars(as_str=False)
305
432
  else:
306
433
  result_vars_in = deepcopy(self.result_vars)
307
434
 
@@ -427,8 +554,26 @@ class Bench(BenchPlotServer):
427
554
  return self.run_sweep(bench_cfg, run_cfg, time_src)
428
555
 
429
556
  def run_sweep(
430
- self, bench_cfg: BenchCfg, run_cfg: BenchRunCfg, time_src: datetime
557
+ self, bench_cfg: BenchCfg, run_cfg: BenchRunCfg, time_src: datetime = None
431
558
  ) -> BenchResult:
559
+ """Execute a benchmark sweep based on the provided configuration.
560
+
561
+ This method handles the caching, execution, and post-processing of a benchmark sweep
562
+ according to the provided configurations. It's typically called by `plot_sweep` rather
563
+ than directly by users.
564
+
565
+ Args:
566
+ bench_cfg (BenchCfg): Configuration defining inputs, results, and other benchmark parameters
567
+ run_cfg (BenchRunCfg): Configuration for how the benchmark should be executed
568
+ time_src (datetime, optional): The timestamp for the benchmark. Used for time-series benchmarks.
569
+ Defaults to None, which will use the current time.
570
+
571
+ Returns:
572
+ BenchResult: An object containing all benchmark data, results, and visualization
573
+
574
+ Raises:
575
+ FileNotFoundError: If only_plot=True and no cached results exist
576
+ """
432
577
  print("tag", bench_cfg.tag)
433
578
 
434
579
  bench_cfg.param.update(run_cfg.param.values())
@@ -495,14 +640,27 @@ class Bench(BenchPlotServer):
495
640
  var_type: str,
496
641
  run_cfg: Optional[BenchRunCfg],
497
642
  ) -> param.Parameter:
498
- """check that a variable is a subclass of param
643
+ """Convert various input formats to param.Parameter objects.
644
+
645
+ This method handles different ways of specifying variables in benchmark sweeps,
646
+ including direct param.Parameter objects, string names of parameters, or dictionaries
647
+ with parameter configuration details. It ensures all inputs are properly converted
648
+ to param.Parameter objects with the correct configuration.
499
649
 
500
650
  Args:
501
- variable (param.Parameter): the variable to check
502
- var_type (str): a string representation of the variable type for better error messages
651
+ variable (param.Parameter | str | dict | tuple): The variable to convert, can be:
652
+ - param.Parameter: Already a parameter object
653
+ - str: Name of a parameter in the worker_class_instance
654
+ - dict: Configuration with 'name' and optional 'values', 'samples', 'max_level'
655
+ - tuple: Tuple that can be converted to a parameter
656
+ var_type (str): Type of variable ('input', 'result', or 'const') for error messages
657
+ run_cfg (Optional[BenchRunCfg]): Run configuration for level settings
658
+
659
+ Returns:
660
+ param.Parameter: The converted parameter object
503
661
 
504
662
  Raises:
505
- TypeError: the input variable type is not a param.
663
+ TypeError: If the variable cannot be converted to a param.Parameter
506
664
  """
507
665
  if isinstance(variable, str):
508
666
  variable = self.worker_class_instance.param.objects(instance=False)[variable]
@@ -523,7 +681,17 @@ class Bench(BenchPlotServer):
523
681
  )
524
682
  return variable
525
683
 
526
- def cache_results(self, bench_res: BenchResult, bench_cfg_hash: int) -> None:
684
+ def cache_results(self, bench_res: BenchResult, bench_cfg_hash: str) -> None:
685
+ """Cache benchmark results for future retrieval.
686
+
687
+ This method stores benchmark results in the disk cache using the benchmark
688
+ configuration hash as the key. It temporarily removes non-pickleable objects
689
+ from the benchmark result before caching.
690
+
691
+ Args:
692
+ bench_res (BenchResult): The benchmark result to cache
693
+ bench_cfg_hash (str): The hash value to use as the cache key
694
+ """
527
695
  with Cache("cachedir/benchmark_inputs", size_limit=self.cache_size) as c:
528
696
  logging.info(f"saving results with key: {bench_cfg_hash}")
529
697
  self.bench_cfg_hashes.append(bench_cfg_hash)
@@ -539,33 +707,47 @@ class Bench(BenchPlotServer):
539
707
  logging.info(f"saving benchmark: {self.bench_name}")
540
708
  c[self.bench_name] = self.bench_cfg_hashes
541
709
 
542
- # def show(self, run_cfg: BenchRunCfg = None, pane=None) -> None:
543
- # """Launches a webserver with plots of the benchmark results, blocking
544
-
710
+ # def show(self, run_cfg: BenchRunCfg = None, pane: pn.panel = None) -> None:
711
+ # """Launch a web server with plots of the benchmark results.
712
+ #
713
+ # This method starts a Panel web server to display the benchmark results interactively.
714
+ # It is a blocking call that runs until the server is terminated.
715
+ #
545
716
  # Args:
546
- # run_cfg (BenchRunCfg, optional): Options for the webserve such as the port. Defaults to None.
547
-
717
+ # run_cfg (BenchRunCfg, optional): Configuration options for the web server,
718
+ # such as the port number. If None, uses the instance's last_run_cfg
719
+ # or creates a default one. Defaults to None.
720
+ # pane (pn.panel, optional): A custom panel to display instead of the default
721
+ # benchmark visualization. Defaults to None.
722
+ #
723
+ # Returns:
724
+ # None
548
725
  # """
549
726
  # if run_cfg is None:
550
727
  # if self.last_run_cfg is not None:
551
728
  # run_cfg = self.last_run_cfg
552
729
  # else:
553
730
  # run_cfg = BenchRunCfg()
554
-
731
+ #
555
732
  # return BenchPlotServer().plot_server(self.bench_name, run_cfg, pane)
556
733
 
557
734
  def load_history_cache(
558
- self, dataset: xr.Dataset, bench_cfg_hash: int, clear_history: bool
735
+ self, dataset: xr.Dataset, bench_cfg_hash: str, clear_history: bool
559
736
  ) -> xr.Dataset:
560
- """Load historical data from a cache if over_time=true
737
+ """Load historical data from a cache if over_time is enabled.
738
+
739
+ This method is used to retrieve and concatenate historical benchmark data from the cache
740
+ when tracking performance over time. If clear_history is True, it will clear any existing
741
+ historical data instead of loading it.
561
742
 
562
743
  Args:
563
- ds (xr.Dataset): Freshly calculated data
564
- bench_cfg_hash (int): Hash of the input variables used to generate the data
565
- clear_history (bool): Optionally clear the history
744
+ dataset (xr.Dataset): Freshly calculated benchmark data for the current run
745
+ bench_cfg_hash (str): Hash of the input variables used to identify cached data
746
+ clear_history (bool): If True, clears historical data instead of loading it
566
747
 
567
748
  Returns:
568
- xr.Dataset: historical data as an xr dataset
749
+ xr.Dataset: Combined dataset with both historical and current benchmark data,
750
+ or just the current data if no history exists or history is cleared
569
751
  """
570
752
  with Cache("cachedir/history", size_limit=self.cache_size) as c:
571
753
  if clear_history:
@@ -585,17 +767,23 @@ class Bench(BenchPlotServer):
585
767
 
586
768
  def setup_dataset(
587
769
  self, bench_cfg: BenchCfg, time_src: datetime | str
588
- ) -> tuple[BenchResult, List, List]:
589
- """A function for generating an n-d xarray from a set of input variables in the BenchCfg
770
+ ) -> tuple[BenchResult, List[tuple], List[str]]:
771
+ """Initialize an n-dimensional xarray dataset from benchmark configuration parameters.
772
+
773
+ This function creates the data structures needed to store benchmark results based on
774
+ the provided configuration. It sets up the xarray dimensions, coordinates, and variables
775
+ based on input variables and result variables.
590
776
 
591
777
  Args:
592
- bench_cfg (BenchCfg): description of the benchmark parameters
593
- time_src (datetime | str): a representation of the sample time
778
+ bench_cfg (BenchCfg): Configuration defining the benchmark parameters, inputs, and results
779
+ time_src (datetime | str): Timestamp or event name for the benchmark run
594
780
 
595
781
  Returns:
596
- tuple[BenchResult, List, List]: bench_result, function inputs, dimension names
782
+ tuple[BenchResult, List[tuple], List[str]]:
783
+ - A BenchResult object with the initialized dataset
784
+ - A list of function input tuples (index, value pairs)
785
+ - A list of dimension names for the dataset
597
786
  """
598
-
599
787
  if time_src is None:
600
788
  time_src = datetime.now()
601
789
  bench_cfg.meta_vars = self.define_extra_vars(bench_cfg, bench_cfg.repeats, time_src)
@@ -641,7 +829,17 @@ class Bench(BenchPlotServer):
641
829
 
642
830
  return bench_res, function_inputs, dims_cfg.dims_name
643
831
 
644
- def define_const_inputs(self, const_vars) -> dict:
832
+ def define_const_inputs(self, const_vars: List[Tuple[param.Parameter, Any]]) -> Optional[dict]:
833
+ """Convert constant variable tuples into a dictionary of name-value pairs.
834
+
835
+ Args:
836
+ const_vars (List[Tuple[param.Parameter, Any]]): List of (parameter, value) tuples
837
+ representing constant parameters and their values
838
+
839
+ Returns:
840
+ Optional[dict]: Dictionary mapping parameter names to their constant values,
841
+ or None if const_vars is None
842
+ """
645
843
  constant_inputs = None
646
844
  if const_vars is not None:
647
845
  const_vars, constant_values = [
@@ -653,16 +851,22 @@ class Bench(BenchPlotServer):
653
851
  constant_inputs = dict(zip(constant_names, constant_values))
654
852
  return constant_inputs
655
853
 
656
- def define_extra_vars(self, bench_cfg: BenchCfg, repeats: int, time_src) -> list[IntSweep]:
657
- """Define extra meta vars that are stored in the n-d array but are not passed to the benchmarking function, such as number of repeats and the time the function was called.
854
+ def define_extra_vars(
855
+ self, bench_cfg: BenchCfg, repeats: int, time_src: datetime | str
856
+ ) -> List[IntSweep]:
857
+ """Define extra meta variables for tracking benchmark execution details.
858
+
859
+ This function creates variables that aren't passed to the worker function but are stored
860
+ in the n-dimensional array to provide context about the benchmark, such as the number of
861
+ repeat measurements and timestamps.
658
862
 
659
863
  Args:
660
- bench_cfg (BenchCfg): description of the benchmark parameters
661
- repeats (int): the number of times to sample the function
662
- time_src (datetime): a representation of the sample time
864
+ bench_cfg (BenchCfg): The benchmark configuration to add variables to
865
+ repeats (int): The number of times each sample point should be measured
866
+ time_src (datetime | str): Either a timestamp or a string event name for temporal tracking
663
867
 
664
868
  Returns:
665
- _type_: _description_
869
+ List[IntSweep]: A list of additional parameter variables to include in the benchmark
666
870
  """
667
871
  bench_cfg.iv_repeat = IntSweep(
668
872
  default=repeats,
@@ -685,16 +889,26 @@ class Bench(BenchPlotServer):
685
889
  return extra_vars
686
890
 
687
891
  def calculate_benchmark_results(
688
- self, bench_cfg, time_src: datetime | str, bench_cfg_sample_hash, bench_run_cfg
892
+ self,
893
+ bench_cfg: BenchCfg,
894
+ time_src: datetime | str,
895
+ bench_cfg_sample_hash: str,
896
+ bench_run_cfg: BenchRunCfg,
689
897
  ) -> BenchResult:
690
- """A function for generating an n-d xarray from a set of input variables in the BenchCfg
898
+ """Execute the benchmark runs and collect results into an n-dimensional array.
899
+
900
+ This method handles the core benchmark execution process. It sets up the dataset,
901
+ initializes worker jobs, submits them to the sample cache for execution or retrieval,
902
+ and collects and stores the results.
691
903
 
692
904
  Args:
693
- bench_cfg (BenchCfg): description of the benchmark parameters
694
- time_src (datetime): a representation of the sample time
905
+ bench_cfg (BenchCfg): Configuration defining the benchmark parameters
906
+ time_src (datetime | str): Timestamp or event name for the benchmark run
907
+ bench_cfg_sample_hash (str): Hash of the benchmark configuration without repeats
908
+ bench_run_cfg (BenchRunCfg): Configuration for how the benchmark should be executed
695
909
 
696
910
  Returns:
697
- bench_cfg (BenchCfg): description of the benchmark parameters
911
+ BenchResult: An object containing all the benchmark data and results
698
912
  """
699
913
  bench_res, func_inputs, dims_name = self.setup_dataset(bench_cfg, time_src)
700
914
  bench_res.bench_cfg.hmap_kdims = sorted(dims_name)
@@ -748,6 +962,21 @@ class Bench(BenchPlotServer):
748
962
  worker_job: WorkerJob,
749
963
  bench_run_cfg: BenchRunCfg,
750
964
  ) -> None:
965
+ """Store the results from a benchmark worker job into the benchmark result dataset.
966
+
967
+ This method handles unpacking the results from worker jobs and placing them
968
+ in the correct locations in the n-dimensional result dataset. It supports different
969
+ types of result variables including scalars, vectors, references, and media.
970
+
971
+ Args:
972
+ job_result (JobFuture): The future containing the worker function result
973
+ bench_res (BenchResult): The benchmark result object to store results in
974
+ worker_job (WorkerJob): The job metadata needed to index the result
975
+ bench_run_cfg (BenchRunCfg): Configuration for how results should be handled
976
+
977
+ Raises:
978
+ RuntimeError: If an unsupported result variable type is encountered
979
+ """
751
980
  result = job_result.result()
752
981
  if result is not None:
753
982
  logging.info(f"{job_result.job.job_id}:")
@@ -806,7 +1035,19 @@ class Bench(BenchPlotServer):
806
1035
 
807
1036
  # bench_cfg.hmap = bench_cfg.hmaps[bench_cfg.result_hmaps[0].name]
808
1037
 
809
- def init_sample_cache(self, run_cfg: BenchRunCfg):
1038
+ def init_sample_cache(self, run_cfg: BenchRunCfg) -> FutureCache:
1039
+ """Initialize the sample cache for storing benchmark function results.
1040
+
1041
+ This method creates a FutureCache for storing and retrieving benchmark results
1042
+ based on the run configuration settings.
1043
+
1044
+ Args:
1045
+ run_cfg (BenchRunCfg): Configuration with cache settings such as overwrite policy,
1046
+ executor type, and whether to cache results
1047
+
1048
+ Returns:
1049
+ FutureCache: A configured cache for storing benchmark results
1050
+ """
810
1051
  return FutureCache(
811
1052
  overwrite=run_cfg.overwrite_sample_cache,
812
1053
  executor=run_cfg.executor,
@@ -816,23 +1057,30 @@ class Bench(BenchPlotServer):
816
1057
  cache_results=run_cfg.cache_samples,
817
1058
  )
818
1059
 
819
- def clear_tag_from_sample_cache(self, tag: str, run_cfg):
820
- """Clear all samples from the cache that match a tag
1060
+ def clear_tag_from_sample_cache(self, tag: str, run_cfg: BenchRunCfg) -> None:
1061
+ """Clear all samples from the cache that match a specific tag.
1062
+
1063
+ This method is useful when you want to rerun a benchmark with the same tag
1064
+ but want fresh results instead of using cached data.
1065
+
821
1066
  Args:
822
- tag(str): clear samples with this tag
1067
+ tag (str): The tag identifying samples to clear from the cache
1068
+ run_cfg (BenchRunCfg): Run configuration used to initialize the sample cache if needed
823
1069
  """
824
1070
  if self.sample_cache is None:
825
1071
  self.sample_cache = self.init_sample_cache(run_cfg)
826
1072
  self.sample_cache.clear_tag(tag)
827
1073
 
828
1074
  def add_metadata_to_dataset(self, bench_res: BenchResult, input_var: ParametrizedSweep) -> None:
829
- """Adds variable metadata to the xrarry so that it can be used to automatically plot the dimension units etc.
1075
+ """Add variable metadata to the xarray dataset for improved visualization.
1076
+
1077
+ This method adds metadata like units, long names, and descriptions to the xarray dataset
1078
+ attributes, which helps visualization tools properly label axes and tooltips.
830
1079
 
831
1080
  Args:
832
- bench_cfg (BenchCfg):
1081
+ bench_res (BenchResult): The benchmark result object containing the dataset to display
833
1082
  input_var (ParametrizedSweep): The variable to extract metadata from
834
1083
  """
835
-
836
1084
  for rv in bench_res.bench_cfg.result_vars:
837
1085
  if type(rv) is ResultVar:
838
1086
  bench_res.ds[rv.name].attrs["units"] = rv.units
@@ -851,29 +1099,113 @@ class Bench(BenchPlotServer):
851
1099
  if input_var.__doc__ is not None:
852
1100
  dsvar.attrs["description"] = input_var.__doc__
853
1101
 
854
- def report_results(self, bench_cfg: BenchCfg, print_xarray: bool, print_pandas: bool):
855
- """Optionally display the calculated benchmark data as either as pandas, xarray or plot
1102
+ def report_results(
1103
+ self, bench_res: BenchResult, print_xarray: bool, print_pandas: bool
1104
+ ) -> None:
1105
+ """Display the calculated benchmark data in various formats.
1106
+
1107
+ This method provides options to display the benchmark results as xarray data structures
1108
+ or pandas DataFrames for debugging and inspection.
856
1109
 
857
1110
  Args:
858
- bench_cfg (BenchCfg):
859
- print_xarray (bool):
860
- print_pandas (bool):
1111
+ bench_res (BenchResult): The benchmark result containing the dataset to display
1112
+ print_xarray (bool): If True, log the raw xarray Dataset structure
1113
+ print_pandas (bool): If True, log the dataset converted to a pandas DataFrame
861
1114
  """
862
1115
  if print_xarray:
863
- logging.info(bench_cfg.ds)
1116
+ logging.info(bench_res.ds)
864
1117
  if print_pandas:
865
- logging.info(bench_cfg.ds.to_dataframe())
1118
+ logging.info(bench_res.ds.to_dataframe())
866
1119
 
867
1120
  def clear_call_counts(self) -> None:
868
1121
  """Clear the worker and cache call counts, to help debug and assert caching is happening properly"""
869
1122
  self.sample_cache.clear_call_counts()
870
1123
 
871
1124
  def get_result(self, index: int = -1) -> BenchResult:
1125
+ """Get a specific benchmark result from the results list.
1126
+
1127
+ Args:
1128
+ index (int, optional): Index of the result to retrieve. Negative indices are supported,
1129
+ with -1 (default) returning the most recent result.
1130
+
1131
+ Returns:
1132
+ BenchResult: The benchmark result at the specified index
1133
+ """
872
1134
  return self.results[index]
873
1135
 
874
1136
  def get_ds(self, index: int = -1) -> xr.Dataset:
1137
+ """Get the xarray Dataset from a specific benchmark result.
1138
+
1139
+ This is a convenience method that retrieves a result and returns its dataset.
1140
+
1141
+ Args:
1142
+ index (int, optional): Index of the result to retrieve the dataset from.
1143
+ Negative indices are supported, with -1 (default) returning the most recent result.
1144
+
1145
+ Returns:
1146
+ xr.Dataset: The xarray Dataset from the benchmark result
1147
+ """
875
1148
  return self.get_result(index).to_xarray()
876
1149
 
877
- def publish(self, remote_callback: Callable) -> str:
1150
+ def publish(self, remote_callback: Callable[[str], str]) -> str:
1151
+ """Publish the benchmark report to a remote location.
1152
+
1153
+ Uses the provided callback to publish the benchmark report to a remote location
1154
+ such as a GitHub Pages site.
1155
+
1156
+ Args:
1157
+ remote_callback (Callable[[str], str]): A function that takes a branch name
1158
+ and publishes the report, returning the URL where it's published
1159
+
1160
+ Returns:
1161
+ str: The URL where the report has been published
1162
+ """
878
1163
  branch_name = f"{self.bench_name}_{self.run_cfg.run_tag}"
879
1164
  return self.report.publish(remote_callback, branch_name=branch_name)
1165
+
1166
+ def get_result_vars(self, as_str: bool = True) -> List[str | ParametrizedSweep]:
1167
+ """
1168
+ Retrieve the result variables from the worker class instance.
1169
+
1170
+ Args:
1171
+ as_str (bool): If True, the result variables are returned as strings.
1172
+ If False, they are returned in their original form.
1173
+ Default is True.
1174
+
1175
+ Returns:
1176
+ List[str | ParametrizedSweep]: A list of result variables, either as strings or in their original form.
1177
+
1178
+ Raises:
1179
+ RuntimeError: If the worker class instance is not set.
1180
+ """
1181
+ if self.worker_class_instance is not None:
1182
+ if as_str:
1183
+ return [i.name for i in self.worker_class_instance.get_results_only()]
1184
+ return self.worker_class_instance.get_results_only()
1185
+ raise RuntimeError("Worker class instance not set")
1186
+
1187
+ def to(
1188
+ self,
1189
+ result_type: BenchResult,
1190
+ result_var: Optional[Parameter] = None,
1191
+ override: bool = True,
1192
+ **kwargs: Any,
1193
+ ) -> BenchResult:
1194
+ # return
1195
+ """Return the current instance of BenchResult.
1196
+
1197
+ Returns:
1198
+ BenchResult: The current instance of the benchmark result
1199
+ """
1200
+ return self.get_result().to(
1201
+ result_type=result_type, result_var=result_var, override=override, **kwargs
1202
+ )
1203
+
1204
+ def add(
1205
+ self,
1206
+ result_type: BenchResult,
1207
+ result_var: Optional[Parameter] = None,
1208
+ override: bool = True,
1209
+ **kwargs: Any,
1210
+ ):
1211
+ self.report.append(self.to(result_type, result_var=result_var, override=override, **kwargs))