holobench 1.25.2__py3-none-any.whl → 1.27.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bencher/bench_report.py +6 -109
- bencher/example/__init__.py +0 -0
- bencher/example/benchmark_data.py +196 -0
- bencher/example/example_all.py +45 -0
- bencher/example/example_categorical.py +99 -0
- bencher/example/example_composable_container.py +106 -0
- bencher/example/example_composable_container2.py +160 -0
- bencher/example/example_consts.py +39 -0
- bencher/example/example_custom_sweep.py +59 -0
- bencher/example/example_custom_sweep2.py +42 -0
- bencher/example/example_docs.py +34 -0
- bencher/example/example_filepath.py +27 -0
- bencher/example/example_float3D.py +101 -0
- bencher/example/example_float_cat.py +99 -0
- bencher/example/example_floats.py +89 -0
- bencher/example/example_floats2D.py +93 -0
- bencher/example/example_holosweep.py +98 -0
- bencher/example/example_holosweep_objects.py +111 -0
- bencher/example/example_holosweep_tap.py +144 -0
- bencher/example/example_image.py +155 -0
- bencher/example/example_levels.py +181 -0
- bencher/example/example_levels2.py +37 -0
- bencher/example/example_pareto.py +53 -0
- bencher/example/example_sample_cache.py +85 -0
- bencher/example/example_sample_cache_context.py +116 -0
- bencher/example/example_simple.py +134 -0
- bencher/example/example_simple_bool.py +35 -0
- bencher/example/example_simple_cat.py +48 -0
- bencher/example/example_simple_float.py +28 -0
- bencher/example/example_simple_float2d.py +29 -0
- bencher/example/example_strings.py +47 -0
- bencher/example/example_time_event.py +63 -0
- bencher/example/example_video.py +118 -0
- bencher/example/example_workflow.py +189 -0
- bencher/example/experimental/example_bokeh_plotly.py +38 -0
- bencher/example/experimental/example_hover_ex.py +45 -0
- bencher/example/experimental/example_hvplot_explorer.py +39 -0
- bencher/example/experimental/example_interactive.py +75 -0
- bencher/example/experimental/example_streamnd.py +49 -0
- bencher/example/experimental/example_streams.py +36 -0
- bencher/example/experimental/example_template.py +40 -0
- bencher/example/experimental/example_updates.py +84 -0
- bencher/example/experimental/example_vector.py +84 -0
- bencher/example/meta/example_meta.py +171 -0
- bencher/example/meta/example_meta_cat.py +25 -0
- bencher/example/meta/example_meta_float.py +23 -0
- bencher/example/meta/example_meta_levels.py +26 -0
- bencher/example/optuna/example_optuna.py +78 -0
- bencher/example/shelved/example_float2D_scatter.py +109 -0
- bencher/example/shelved/example_float3D_cone.py +96 -0
- bencher/example/shelved/example_kwargs.py +63 -0
- bencher/plotting/__init__.py +0 -0
- bencher/plotting/plot_filter.py +110 -0
- bencher/plotting/plt_cnt_cfg.py +75 -0
- bencher/results/__init__.py +0 -0
- bencher/results/bench_result.py +94 -0
- bencher/results/bench_result_base.py +476 -0
- bencher/results/composable_container/__init__.py +0 -0
- bencher/results/composable_container/composable_container_base.py +73 -0
- bencher/results/composable_container/composable_container_panel.py +39 -0
- bencher/results/composable_container/composable_container_video.py +184 -0
- bencher/results/float_formatter.py +44 -0
- bencher/results/holoview_result.py +753 -0
- bencher/results/optuna_result.py +354 -0
- bencher/results/panel_result.py +41 -0
- bencher/results/plotly_result.py +65 -0
- bencher/results/video_result.py +38 -0
- bencher/results/video_summary.py +222 -0
- bencher/variables/__init__.py +0 -0
- bencher/variables/inputs.py +202 -0
- bencher/variables/parametrised_sweep.py +208 -0
- bencher/variables/results.py +214 -0
- bencher/variables/sweep_base.py +162 -0
- bencher/variables/time.py +92 -0
- holobench-1.27.0.data/data/share/ament_index/resource_index/packages/bencher +0 -0
- holobench-1.27.0.data/data/share/bencher/package.xml +33 -0
- {holobench-1.25.2.dist-info → holobench-1.27.0.dist-info}/METADATA +5 -5
- holobench-1.27.0.dist-info/RECORD +93 -0
- holobench-1.25.2.dist-info/RECORD +0 -18
- {holobench-1.25.2.dist-info → holobench-1.27.0.dist-info}/LICENSE +0 -0
- {holobench-1.25.2.dist-info → holobench-1.27.0.dist-info}/WHEEL +0 -0
- {holobench-1.25.2.dist-info → holobench-1.27.0.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)
|