holobench 1.25.2__py3-none-any.whl → 1.26.3__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 (81) hide show
  1. bencher/example/__init__.py +0 -0
  2. bencher/example/benchmark_data.py +196 -0
  3. bencher/example/example_all.py +45 -0
  4. bencher/example/example_categorical.py +99 -0
  5. bencher/example/example_composable_container.py +106 -0
  6. bencher/example/example_composable_container2.py +160 -0
  7. bencher/example/example_consts.py +39 -0
  8. bencher/example/example_custom_sweep.py +59 -0
  9. bencher/example/example_custom_sweep2.py +42 -0
  10. bencher/example/example_docs.py +34 -0
  11. bencher/example/example_filepath.py +27 -0
  12. bencher/example/example_float3D.py +101 -0
  13. bencher/example/example_float_cat.py +99 -0
  14. bencher/example/example_floats.py +89 -0
  15. bencher/example/example_floats2D.py +93 -0
  16. bencher/example/example_holosweep.py +98 -0
  17. bencher/example/example_holosweep_objects.py +111 -0
  18. bencher/example/example_holosweep_tap.py +144 -0
  19. bencher/example/example_image.py +155 -0
  20. bencher/example/example_levels.py +181 -0
  21. bencher/example/example_levels2.py +37 -0
  22. bencher/example/example_pareto.py +53 -0
  23. bencher/example/example_sample_cache.py +85 -0
  24. bencher/example/example_sample_cache_context.py +116 -0
  25. bencher/example/example_simple.py +134 -0
  26. bencher/example/example_simple_bool.py +35 -0
  27. bencher/example/example_simple_cat.py +48 -0
  28. bencher/example/example_simple_float.py +28 -0
  29. bencher/example/example_simple_float2d.py +29 -0
  30. bencher/example/example_strings.py +47 -0
  31. bencher/example/example_time_event.py +63 -0
  32. bencher/example/example_video.py +118 -0
  33. bencher/example/example_workflow.py +189 -0
  34. bencher/example/experimental/example_bokeh_plotly.py +38 -0
  35. bencher/example/experimental/example_hover_ex.py +45 -0
  36. bencher/example/experimental/example_hvplot_explorer.py +39 -0
  37. bencher/example/experimental/example_interactive.py +75 -0
  38. bencher/example/experimental/example_streamnd.py +49 -0
  39. bencher/example/experimental/example_streams.py +36 -0
  40. bencher/example/experimental/example_template.py +40 -0
  41. bencher/example/experimental/example_updates.py +84 -0
  42. bencher/example/experimental/example_vector.py +84 -0
  43. bencher/example/meta/example_meta.py +171 -0
  44. bencher/example/meta/example_meta_cat.py +25 -0
  45. bencher/example/meta/example_meta_float.py +23 -0
  46. bencher/example/meta/example_meta_levels.py +26 -0
  47. bencher/example/optuna/example_optuna.py +78 -0
  48. bencher/example/shelved/example_float2D_scatter.py +109 -0
  49. bencher/example/shelved/example_float3D_cone.py +96 -0
  50. bencher/example/shelved/example_kwargs.py +63 -0
  51. bencher/plotting/__init__.py +0 -0
  52. bencher/plotting/plot_filter.py +110 -0
  53. bencher/plotting/plt_cnt_cfg.py +75 -0
  54. bencher/results/__init__.py +0 -0
  55. bencher/results/bench_result.py +94 -0
  56. bencher/results/bench_result_base.py +476 -0
  57. bencher/results/composable_container/__init__.py +0 -0
  58. bencher/results/composable_container/composable_container_base.py +73 -0
  59. bencher/results/composable_container/composable_container_panel.py +39 -0
  60. bencher/results/composable_container/composable_container_video.py +184 -0
  61. bencher/results/float_formatter.py +44 -0
  62. bencher/results/holoview_result.py +753 -0
  63. bencher/results/optuna_result.py +354 -0
  64. bencher/results/panel_result.py +41 -0
  65. bencher/results/plotly_result.py +65 -0
  66. bencher/results/video_result.py +38 -0
  67. bencher/results/video_summary.py +222 -0
  68. bencher/variables/__init__.py +0 -0
  69. bencher/variables/inputs.py +202 -0
  70. bencher/variables/parametrised_sweep.py +208 -0
  71. bencher/variables/results.py +214 -0
  72. bencher/variables/sweep_base.py +162 -0
  73. bencher/variables/time.py +92 -0
  74. holobench-1.26.3.data/data/share/ament_index/resource_index/packages/bencher +0 -0
  75. holobench-1.26.3.data/data/share/bencher/package.xml +33 -0
  76. {holobench-1.25.2.dist-info → holobench-1.26.3.dist-info}/METADATA +5 -5
  77. holobench-1.26.3.dist-info/RECORD +93 -0
  78. holobench-1.25.2.dist-info/RECORD +0 -18
  79. {holobench-1.25.2.dist-info → holobench-1.26.3.dist-info}/LICENSE +0 -0
  80. {holobench-1.25.2.dist-info → holobench-1.26.3.dist-info}/WHEEL +0 -0
  81. {holobench-1.25.2.dist-info → holobench-1.26.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,476 @@
1
+ import logging
2
+ from typing import List, Any, Tuple, Optional
3
+ from enum import Enum, auto
4
+ import xarray as xr
5
+ from param import Parameter
6
+ import holoviews as hv
7
+ from functools import partial
8
+ import panel as pn
9
+
10
+ from bencher.utils import int_to_col, color_tuple_to_css, callable_name
11
+
12
+ from bencher.variables.parametrised_sweep import ParametrizedSweep
13
+ from bencher.variables.inputs import with_level
14
+
15
+ from bencher.variables.results import OptDir
16
+ from copy import deepcopy
17
+ from bencher.results.optuna_result import OptunaResult
18
+ from bencher.variables.results import ResultVar
19
+ from bencher.plotting.plot_filter import VarRange, PlotFilter
20
+ from bencher.utils import listify
21
+
22
+ from bencher.variables.results import (
23
+ ResultReference,
24
+ )
25
+
26
+ from bencher.results.composable_container.composable_container_panel import ComposableContainerPanel
27
+
28
+ # todo add plugins
29
+ # https://gist.github.com/dorneanu/cce1cd6711969d581873a88e0257e312
30
+ # https://kaleidoescape.github.io/decorated-plugins/
31
+
32
+
33
+ class ReduceType(Enum):
34
+ AUTO = auto() # automatically determine the best way to reduce the dataset
35
+ SQUEEZE = auto() # remove any dimensions of length 1
36
+ REDUCE = auto() # get the mean and std dev of the the "repeat" dimension
37
+ NONE = auto() # don't reduce
38
+
39
+
40
+ class EmptyContainer:
41
+ """A wrapper for list like containers that only appends if the item is not None"""
42
+
43
+ def __init__(self, pane) -> None:
44
+ self.pane = pane
45
+
46
+ def append(self, child):
47
+ if child is not None:
48
+ self.pane.append(child)
49
+
50
+ def get(self):
51
+ return self.pane if len(self.pane) > 0 else None
52
+
53
+
54
+ class BenchResultBase(OptunaResult):
55
+ def result_samples(self) -> int:
56
+ """The number of samples in the results dataframe"""
57
+ return self.ds.count()
58
+
59
+ def to_hv_dataset(
60
+ self, reduce: ReduceType = ReduceType.AUTO, result_var: ResultVar = None, level: int = None
61
+ ) -> hv.Dataset:
62
+ """Generate a holoviews dataset from the xarray dataset.
63
+
64
+ Args:
65
+ reduce (ReduceType, optional): Optionally perform reduce options on the dataset. By default the returned dataset will calculate the mean and standard devation over the "repeat" dimension so that the dataset plays nicely with most of the holoviews plot types. Reduce.Sqeeze is used if there is only 1 repeat and you want the "reduce" variable removed from the dataset. ReduceType.None returns an unaltered dataset. Defaults to ReduceType.AUTO.
66
+
67
+ Returns:
68
+ hv.Dataset: results in the form of a holoviews dataset
69
+ """
70
+
71
+ if reduce == ReduceType.NONE:
72
+ kdims = [i.name for i in self.bench_cfg.all_vars]
73
+ return hv.Dataset(self.to_dataset(reduce, result_var, level), kdims=kdims)
74
+ return hv.Dataset(self.to_dataset(reduce, result_var, level))
75
+
76
+ def to_dataset(
77
+ self, reduce: ReduceType = ReduceType.AUTO, result_var: ResultVar = None, level: int = None
78
+ ) -> xr.Dataset:
79
+ """Generate a summarised xarray dataset.
80
+
81
+ Args:
82
+ reduce (ReduceType, optional): Optionally perform reduce options on the dataset. By default the returned dataset will calculate the mean and standard devation over the "repeat" dimension so that the dataset plays nicely with most of the holoviews plot types. Reduce.Sqeeze is used if there is only 1 repeat and you want the "reduce" variable removed from the dataset. ReduceType.None returns an unaltered dataset. Defaults to ReduceType.AUTO.
83
+
84
+ Returns:
85
+ xr.Dataset: results in the form of an xarray dataset
86
+ """
87
+ if reduce == ReduceType.AUTO:
88
+ reduce = ReduceType.REDUCE if self.bench_cfg.repeats > 1 else ReduceType.SQUEEZE
89
+
90
+ ds_out = self.ds if result_var is None else self.ds[result_var.name]
91
+
92
+ match (reduce):
93
+ case ReduceType.REDUCE:
94
+ ds_reduce_mean = ds_out.mean(dim="repeat", keep_attrs=True)
95
+ ds_reduce_std = ds_out.std(dim="repeat", keep_attrs=True)
96
+
97
+ for v in ds_reduce_mean.data_vars:
98
+ ds_reduce_mean[f"{v}_std"] = ds_reduce_std[v]
99
+ ds_out = ds_reduce_mean
100
+ case ReduceType.SQUEEZE:
101
+ ds_out = ds_out.squeeze(drop=True)
102
+ if level is not None:
103
+ coords_no_repeat = {}
104
+ for c, v in ds_out.coords.items():
105
+ if c != "repeat":
106
+ coords_no_repeat[c] = with_level(v.to_numpy(), level)
107
+ return ds_out.sel(coords_no_repeat)
108
+ return ds_out
109
+
110
+ def get_optimal_vec(
111
+ self,
112
+ result_var: ParametrizedSweep,
113
+ input_vars: List[ParametrizedSweep],
114
+ ) -> List[Any]:
115
+ """Get the optimal values from the sweep as a vector.
116
+
117
+ Args:
118
+ result_var (bch.ParametrizedSweep): Optimal values of this result variable
119
+ input_vars (List[bch.ParametrizedSweep]): Define which input vars values are returned in the vector
120
+
121
+ Returns:
122
+ List[Any]: A vector of optimal values for the desired input vector
123
+ """
124
+
125
+ da = self.get_optimal_value_indices(result_var)
126
+ output = []
127
+ for iv in input_vars:
128
+ if da.coords[iv.name].values.size == 1:
129
+ # https://stackoverflow.com/questions/773030/why-are-0d-arrays-in-numpy-not-considered-scalar
130
+ # use [()] to convert from a 0d numpy array to a scalar
131
+ output.append(da.coords[iv.name].values[()])
132
+ else:
133
+ logging.warning(f"values size: {da.coords[iv.name].values.size}")
134
+ output.append(max(da.coords[iv.name].values[()]))
135
+ logging.info(f"Maximum value of {iv.name}: {output[-1]}")
136
+ return output
137
+
138
+ def get_optimal_value_indices(self, result_var: ParametrizedSweep) -> xr.DataArray:
139
+ """Get an xarray mask of the values with the best values found during a parameter sweep
140
+
141
+ Args:
142
+ result_var (bch.ParametrizedSweep): Optimal value of this result variable
143
+
144
+ Returns:
145
+ xr.DataArray: xarray mask of optimal values
146
+ """
147
+ result_da = self.ds[result_var.name]
148
+ if result_var.direction == OptDir.maximize:
149
+ opt_val = result_da.max()
150
+ else:
151
+ opt_val = result_da.min()
152
+ indicies = result_da.where(result_da == opt_val, drop=True).squeeze()
153
+ logging.info(f"optimal value of {result_var.name}: {opt_val.values}")
154
+ return indicies
155
+
156
+ def get_optimal_inputs(
157
+ self,
158
+ result_var: ParametrizedSweep,
159
+ keep_existing_consts: bool = True,
160
+ as_dict: bool = False,
161
+ ) -> Tuple[ParametrizedSweep, Any] | dict[ParametrizedSweep, Any]:
162
+ """Get a list of tuples of optimal variable names and value pairs, that can be fed in as constant values to subsequent parameter sweeps
163
+
164
+ Args:
165
+ result_var (bch.ParametrizedSweep): Optimal values of this result variable
166
+ keep_existing_consts (bool): Include any const values that were defined as part of the parameter sweep
167
+ as_dict (bool): return value as a dictionary
168
+
169
+ Returns:
170
+ Tuple[bch.ParametrizedSweep, Any]|[ParametrizedSweep, Any]: Tuples of variable name and optimal values
171
+ """
172
+ da = self.get_optimal_value_indices(result_var)
173
+ if keep_existing_consts:
174
+ output = deepcopy(self.bench_cfg.const_vars)
175
+ else:
176
+ output = []
177
+
178
+ for iv in self.bench_cfg.input_vars:
179
+ # assert da.coords[iv.name].values.size == (1,)
180
+ if da.coords[iv.name].values.size == 1:
181
+ # https://stackoverflow.com/questions/773030/why-are-0d-arrays-in-numpy-not-considered-scalar
182
+ # use [()] to convert from a 0d numpy array to a scalar
183
+ output.append((iv, da.coords[iv.name].values[()]))
184
+ else:
185
+ logging.warning(f"values size: {da.coords[iv.name].values.size}")
186
+ output.append((iv, max(da.coords[iv.name].values[()])))
187
+
188
+ logging.info(f"Maximum value of {iv.name}: {output[-1][1]}")
189
+ if as_dict:
190
+ return dict(output)
191
+ return output
192
+
193
+ def describe_sweep(self):
194
+ return self.bench_cfg.describe_sweep()
195
+
196
+ def get_best_holomap(self, name: str = None):
197
+ return self.get_hmap(name)[self.get_best_trial_params(True)]
198
+
199
+ def get_hmap(self, name: str = None):
200
+ try:
201
+ if name is None:
202
+ name = self.result_hmaps[0].name
203
+ if name in self.hmaps:
204
+ return self.hmaps[name]
205
+ except Exception as e:
206
+ raise RuntimeError(
207
+ "You are trying to plot a holomap result but it is not in the result_vars list. Add the holomap to the result_vars list"
208
+ ) from e
209
+ return None
210
+
211
+ def to_plot_title(self) -> str:
212
+ if len(self.bench_cfg.input_vars) > 0 and len(self.bench_cfg.result_vars) > 0:
213
+ return f"{self.bench_cfg.result_vars[0].name} vs {self.bench_cfg.input_vars[0].name}"
214
+ return ""
215
+
216
+ def title_from_ds(self, dataset: xr.Dataset, result_var: Parameter, **kwargs):
217
+ if "title" in kwargs:
218
+ return kwargs["title"]
219
+
220
+ if isinstance(dataset, xr.DataArray):
221
+ tit = [dataset.name]
222
+ for d in dataset.dims:
223
+ tit.append(d)
224
+ else:
225
+ tit = [result_var.name]
226
+ tit.extend(list(dataset.sizes))
227
+
228
+ return " vs ".join(tit)
229
+
230
+ def get_results_var_list(self, result_var: ParametrizedSweep = None) -> List[ResultVar]:
231
+ return self.bench_cfg.result_vars if result_var is None else listify(result_var)
232
+
233
+ def map_plots(
234
+ self,
235
+ plot_callback: callable,
236
+ result_var: ParametrizedSweep = None,
237
+ row: EmptyContainer = None,
238
+ ) -> Optional[pn.Row]:
239
+ if row is None:
240
+ row = EmptyContainer(pn.Row(name=self.to_plot_title()))
241
+ for rv in self.get_results_var_list(result_var):
242
+ row.append(plot_callback(rv))
243
+ return row.get()
244
+
245
+ def map_plot_panes(
246
+ self,
247
+ plot_callback: callable,
248
+ hv_dataset: hv.Dataset = None,
249
+ target_dimension: int = 2,
250
+ result_var: ResultVar = None,
251
+ result_types=None,
252
+ pane_collection: pn.pane = None,
253
+ **kwargs,
254
+ ) -> Optional[pn.Row]:
255
+ if hv_dataset is None:
256
+ hv_dataset = self.to_hv_dataset()
257
+
258
+ if pane_collection is None:
259
+ pane_collection = pn.Row()
260
+
261
+ row = EmptyContainer(pane_collection)
262
+
263
+ # kwargs= self.set_plot_size(**kwargs)
264
+ for rv in self.get_results_var_list(result_var):
265
+ if result_types is None or isinstance(rv, result_types):
266
+ row.append(
267
+ self.to_panes_multi_panel(
268
+ hv_dataset,
269
+ rv,
270
+ plot_callback=partial(plot_callback, **kwargs),
271
+ target_dimension=target_dimension,
272
+ )
273
+ )
274
+ return row.get()
275
+
276
+ def filter(
277
+ self,
278
+ plot_callback: callable,
279
+ plot_filter=None,
280
+ float_range: VarRange = VarRange(0, None),
281
+ cat_range: VarRange = VarRange(0, None),
282
+ vector_len: VarRange = VarRange(1, 1),
283
+ result_vars: VarRange = VarRange(1, 1),
284
+ panel_range: VarRange = VarRange(0, 0),
285
+ repeats_range: VarRange = VarRange(1, None),
286
+ input_range: VarRange = VarRange(1, None),
287
+ reduce: ReduceType = ReduceType.AUTO,
288
+ target_dimension: int = 2,
289
+ result_var: ResultVar = None,
290
+ result_types=None,
291
+ pane_collection: pn.pane = None,
292
+ **kwargs,
293
+ ):
294
+ plot_filter = PlotFilter(
295
+ float_range=float_range,
296
+ cat_range=cat_range,
297
+ vector_len=vector_len,
298
+ result_vars=result_vars,
299
+ panel_range=panel_range,
300
+ repeats_range=repeats_range,
301
+ input_range=input_range,
302
+ )
303
+ matches_res = plot_filter.matches_result(self.plt_cnt_cfg, callable_name(plot_callback))
304
+ if matches_res.overall:
305
+ return self.map_plot_panes(
306
+ plot_callback=plot_callback,
307
+ hv_dataset=self.to_hv_dataset(reduce=reduce),
308
+ target_dimension=target_dimension,
309
+ result_var=result_var,
310
+ result_types=result_types,
311
+ pane_collection=pane_collection,
312
+ **kwargs,
313
+ )
314
+ return matches_res.to_panel()
315
+
316
+ def to_panes_multi_panel(
317
+ self,
318
+ hv_dataset: hv.Dataset,
319
+ result_var: ResultVar,
320
+ plot_callback: callable = None,
321
+ target_dimension: int = 1,
322
+ **kwargs,
323
+ ):
324
+ dims = len(hv_dataset.dimensions())
325
+ if target_dimension is None:
326
+ target_dimension = dims
327
+ return self._to_panes_da(
328
+ hv_dataset.data,
329
+ plot_callback=plot_callback,
330
+ target_dimension=target_dimension,
331
+ horizontal=dims <= target_dimension + 1,
332
+ result_var=result_var,
333
+ **kwargs,
334
+ )
335
+
336
+ def _to_panes_da(
337
+ self,
338
+ dataset: xr.Dataset,
339
+ plot_callback: callable = None,
340
+ target_dimension=1,
341
+ horizontal=False,
342
+ result_var=None,
343
+ **kwargs,
344
+ ) -> pn.panel:
345
+ # todo, when dealing with time and repeats, add feature to allow custom order of dimension recursion
346
+ ##todo remove recursion
347
+ num_dims = len(dataset.sizes)
348
+ # print(f"num_dims: {num_dims}, horizontal: {horizontal}, target: {target_dimension}")
349
+ dims = list(d for d in dataset.sizes)
350
+
351
+ time_dim_delta = 0
352
+ if self.bench_cfg.over_time:
353
+ time_dim_delta = 0
354
+
355
+ if num_dims > (target_dimension + time_dim_delta) and num_dims != 0:
356
+ selected_dim = dims[-1]
357
+ # print(f"selected dim {dim_sel}")
358
+ dim_color = color_tuple_to_css(int_to_col(num_dims - 2, 0.05, 1.0))
359
+
360
+ outer_container = ComposableContainerPanel(
361
+ name=" vs ".join(dims), background_col=dim_color, horizontal=not horizontal
362
+ )
363
+ max_len = 0
364
+ for i in range(dataset.sizes[selected_dim]):
365
+ sliced = dataset.isel({selected_dim: i})
366
+ label_val = sliced.coords[selected_dim].values.item()
367
+ inner_container = ComposableContainerPanel(
368
+ name=outer_container.name,
369
+ width=num_dims - target_dimension,
370
+ var_name=selected_dim,
371
+ var_value=label_val,
372
+ horizontal=horizontal,
373
+ )
374
+
375
+ panes = self._to_panes_da(
376
+ sliced,
377
+ plot_callback=plot_callback,
378
+ target_dimension=target_dimension,
379
+ horizontal=len(sliced.sizes) <= target_dimension + 1,
380
+ result_var=result_var,
381
+ )
382
+ max_len = max(max_len, inner_container.label_len)
383
+ inner_container.append(panes)
384
+ outer_container.append(inner_container.container)
385
+ for c in outer_container.container:
386
+ c[0].width = max_len * 7
387
+ else:
388
+ return plot_callback(dataset=dataset, result_var=result_var, **kwargs)
389
+
390
+ return outer_container.container
391
+
392
+ def zero_dim_da_to_val(self, da_ds: xr.DataArray | xr.Dataset) -> Any:
393
+ # todo this is really horrible, need to improve
394
+ dim = None
395
+ if isinstance(da_ds, xr.Dataset):
396
+ dim = list(da_ds.keys())[0]
397
+ da = da_ds[dim]
398
+ else:
399
+ da = da_ds
400
+
401
+ for k in da.coords.keys():
402
+ dim = k
403
+ break
404
+ if dim is None:
405
+ return da_ds.values.squeeze().item()
406
+ return da.expand_dims(dim).values[0]
407
+
408
+ def ds_to_container(
409
+ self, dataset: xr.Dataset, result_var: Parameter, container, **kwargs
410
+ ) -> Any:
411
+ val = self.zero_dim_da_to_val(dataset[result_var.name])
412
+ if isinstance(result_var, ResultReference):
413
+ ref = self.object_index[val]
414
+ if ref is not None:
415
+ val = ref.obj
416
+ if ref.container is not None:
417
+ return ref.container(val, **kwargs)
418
+ if container is not None:
419
+ return container(val, styles={"background": "white"}, **kwargs)
420
+ try:
421
+ container = result_var.to_container()
422
+ if container is not None:
423
+ return container(val)
424
+ except AttributeError as _:
425
+ # TODO make sure all vars have to_container method
426
+ pass
427
+ return val
428
+
429
+ @staticmethod
430
+ def select_level(
431
+ dataset: xr.Dataset,
432
+ level: int,
433
+ include_types: List[type] = None,
434
+ exclude_names: List[str] = None,
435
+ ) -> xr.Dataset:
436
+ """Given a dataset, return a reduced dataset that only contains data from a specified level. By default all types of variables are filtered at the specified level. If you only want to get a reduced level for some types of data you can pass in a list of types to get filtered, You can also pass a list of variables names to exclude from getting filtered
437
+ Args:
438
+ dataset (xr.Dataset): dataset to filter
439
+ level (int): desired data resolution level
440
+ include_types (List[type], optional): Only filter data of these types. Defaults to None.
441
+ exclude_names (List[str], optional): Only filter data with these variable names. Defaults to None.
442
+
443
+ Returns:
444
+ xr.Dataset: A reduced dataset at the specified level
445
+
446
+ Example: a dataset with float_var: [1,2,3,4,5] cat_var: [a,b,c,d,e]
447
+
448
+ select_level(ds,2) -> [1,5] [a,e]
449
+ select_level(ds,2,(float)) -> [1,5] [a,b,c,d,e]
450
+ select_level(ds,2,exclude_names=["cat_var]) -> [1,5] [a,b,c,d,e]
451
+
452
+ see test_bench_result_base.py -> test_select_level()
453
+ """
454
+ coords_no_repeat = {}
455
+ for c, v in dataset.coords.items():
456
+ if c != "repeat":
457
+ vals = v.to_numpy()
458
+ print(vals.dtype)
459
+ include = True
460
+ if include_types is not None and vals.dtype not in listify(include_types):
461
+ include = False
462
+ if exclude_names is not None and c in listify(exclude_names):
463
+ include = False
464
+ if include:
465
+ coords_no_repeat[c] = with_level(v.to_numpy(), level)
466
+ return dataset.sel(coords_no_repeat)
467
+
468
+ # MAPPING TO LOWER LEVEL BENCHCFG functions so they are available at a top level.
469
+ def to_sweep_summary(self, **kwargs):
470
+ return self.bench_cfg.to_sweep_summary(**kwargs)
471
+
472
+ def to_title(self, panel_name: str = None) -> pn.pane.Markdown:
473
+ return self.bench_cfg.to_title(panel_name)
474
+
475
+ def to_description(self, width: int = 800) -> pn.pane.Markdown:
476
+ return self.bench_cfg.to_description(width)
File without changes
@@ -0,0 +1,73 @@
1
+ from enum import auto
2
+ from typing import Any, List
3
+ from dataclasses import dataclass, field
4
+ from strenum import StrEnum
5
+ from bencher.results.float_formatter import FormatFloat
6
+
7
+
8
+ # TODO enable these options
9
+ class ComposeType(StrEnum):
10
+ right = auto() # append the container to the right (creates a row)
11
+ down = auto() # append the container below (creates a column)
12
+ sequence = auto() # display the container after (in time)
13
+ # overlay = auto() # overlay on top of the current container (alpha blending)
14
+
15
+ def flip(self):
16
+ match self:
17
+ case ComposeType.right:
18
+ return ComposeType.down
19
+ case ComposeType.down:
20
+ return ComposeType.right
21
+ case _:
22
+ raise RuntimeError("cannot flip this type")
23
+
24
+ @staticmethod
25
+ def from_horizontal(horizontal: bool):
26
+ return ComposeType.right if horizontal else ComposeType.down
27
+
28
+
29
+ @dataclass(kw_only=True)
30
+ class ComposableContainerBase:
31
+ """A base class for renderer backends. A composable renderer"""
32
+
33
+ compose_method: ComposeType = ComposeType.right
34
+ container: List[Any] = field(default_factory=list)
35
+ label_len: int = 0
36
+
37
+ @staticmethod
38
+ def label_formatter(var_name: str, var_value: int | float | str) -> str:
39
+ """Take a variable name and values and return a pretty version with approximate fixed width
40
+
41
+ Args:
42
+ var_name (str): The name of the variable, usually a dimension
43
+ var_value (int | float | str): The value of the dimension
44
+
45
+ Returns:
46
+ str: Pretty string representation with fixed width
47
+ """
48
+
49
+ if isinstance(var_value, (int, float)):
50
+ var_value = FormatFloat()(var_value)
51
+ if var_name is not None and var_value is not None:
52
+ return f"{var_name}={var_value}"
53
+ if var_name is not None:
54
+ return f"{var_name}"
55
+ if var_value is not None:
56
+ return f"{var_value}"
57
+ return None
58
+
59
+ def append(self, obj: Any) -> None:
60
+ """Add an object to the container. The relationship between the objects is defined by the ComposeType
61
+
62
+ Args:
63
+ obj (Any): Object to add to the container
64
+ """
65
+ self.container.append(obj)
66
+
67
+ def render(self):
68
+ """Return a representation of the container that can be composed with other render() results. This function can also be used to defer layout and rending options until all the information about the container content is known. You may need to ovverride this method depending on the container. See composable_container_video as an example.
69
+
70
+ Returns:
71
+ Any: Visual representation of the container that can be combined with other containers
72
+ """
73
+ return self.container
@@ -0,0 +1,39 @@
1
+ import panel as pn
2
+ from bencher.results.composable_container.composable_container_base import ComposableContainerBase
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(kw_only=True)
7
+ class ComposableContainerPanel(ComposableContainerBase):
8
+ name: str = None
9
+ var_name: str = None
10
+ var_value: str = None
11
+ width: int = None
12
+ background_col: str = None
13
+ horizontal: bool = True
14
+
15
+ def __post_init__(
16
+ self,
17
+ ) -> None:
18
+ container_args = {
19
+ "name": self.name,
20
+ "styles": {},
21
+ }
22
+
23
+ if self.width is not None:
24
+ container_args["styles"]["border-bottom"] = f"{self.width}px solid grey"
25
+ if self.background_col is not None:
26
+ container_args["styles"]["background"] = self.background_col
27
+
28
+ if self.horizontal:
29
+ self.container = pn.Column(**container_args)
30
+ align = ("center", "center")
31
+ else:
32
+ self.container = pn.Row(**container_args)
33
+ align = ("end", "center")
34
+
35
+ label = self.label_formatter(self.var_name, self.var_value)
36
+ if label is not None:
37
+ self.label_len = len(label)
38
+ side = pn.pane.Markdown(label, align=align)
39
+ self.append(side)