holobench 1.41.0__py3-none-any.whl → 1.43.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bencher/__init__.py +20 -2
- bencher/bench_cfg.py +262 -54
- bencher/bench_report.py +2 -2
- bencher/bench_runner.py +96 -10
- bencher/bencher.py +421 -89
- bencher/class_enum.py +70 -7
- bencher/example/example_dataframe.py +2 -2
- bencher/example/example_levels.py +17 -173
- bencher/example/example_pareto.py +107 -31
- bencher/example/example_rerun2.py +1 -1
- bencher/example/example_simple_bool.py +2 -2
- bencher/example/example_simple_float2d.py +6 -1
- bencher/example/example_video.py +2 -0
- bencher/example/experimental/example_hvplot_explorer.py +2 -2
- bencher/example/inputs_0D/example_0_in_1_out.py +25 -15
- bencher/example/inputs_0D/example_0_in_2_out.py +12 -3
- bencher/example/inputs_0_float/example_0_cat_in_2_out.py +88 -0
- bencher/example/inputs_0_float/example_1_cat_in_2_out.py +98 -0
- bencher/example/inputs_0_float/example_2_cat_in_2_out.py +107 -0
- bencher/example/inputs_0_float/example_3_cat_in_2_out.py +111 -0
- bencher/example/inputs_1D/example1d_common.py +48 -12
- bencher/example/inputs_1D/example_0_float_1_cat.py +33 -0
- bencher/example/inputs_1D/example_1_cat_in_2_out_repeats.py +68 -0
- bencher/example/inputs_1D/example_1_float_2_cat_repeats.py +3 -0
- bencher/example/inputs_1D/example_1_int_in_1_out.py +98 -0
- bencher/example/inputs_1D/example_1_int_in_2_out.py +101 -0
- bencher/example/inputs_1D/example_1_int_in_2_out_repeats.py +99 -0
- bencher/example/inputs_1_float/example_1_float_0_cat_in_2_out.py +117 -0
- bencher/example/inputs_1_float/example_1_float_1_cat_in_2_out.py +124 -0
- bencher/example/inputs_1_float/example_1_float_2_cat_in_2_out.py +132 -0
- bencher/example/inputs_1_float/example_1_float_3_cat_in_2_out.py +140 -0
- bencher/example/inputs_2D/example_2_cat_in_4_out_repeats.py +104 -0
- bencher/example/inputs_2_float/example_2_float_0_cat_in_2_out.py +98 -0
- bencher/example/inputs_2_float/example_2_float_1_cat_in_2_out.py +112 -0
- bencher/example/inputs_2_float/example_2_float_2_cat_in_2_out.py +122 -0
- bencher/example/inputs_2_float/example_2_float_3_cat_in_2_out.py +138 -0
- bencher/example/inputs_3_float/example_3_float_0_cat_in_2_out.py +111 -0
- bencher/example/inputs_3_float/example_3_float_1_cat_in_2_out.py +117 -0
- bencher/example/inputs_3_float/example_3_float_2_cat_in_2_out.py +124 -0
- bencher/example/inputs_3_float/example_3_float_3_cat_in_2_out.py +129 -0
- bencher/example/meta/generate_examples.py +118 -7
- bencher/example/meta/generate_meta.py +88 -40
- bencher/job.py +174 -9
- bencher/plotting/plot_filter.py +52 -17
- bencher/results/bench_result.py +117 -25
- bencher/results/bench_result_base.py +117 -8
- bencher/results/dataset_result.py +6 -200
- bencher/results/explorer_result.py +23 -0
- bencher/results/{hvplot_result.py → histogram_result.py} +3 -18
- bencher/results/holoview_results/__init__.py +0 -0
- bencher/results/holoview_results/bar_result.py +79 -0
- bencher/results/holoview_results/curve_result.py +110 -0
- bencher/results/holoview_results/distribution_result/__init__.py +0 -0
- bencher/results/holoview_results/distribution_result/box_whisker_result.py +73 -0
- bencher/results/holoview_results/distribution_result/distribution_result.py +109 -0
- bencher/results/holoview_results/distribution_result/scatter_jitter_result.py +92 -0
- bencher/results/holoview_results/distribution_result/violin_result.py +70 -0
- bencher/results/holoview_results/heatmap_result.py +319 -0
- bencher/results/holoview_results/holoview_result.py +346 -0
- bencher/results/holoview_results/line_result.py +240 -0
- bencher/results/holoview_results/scatter_result.py +107 -0
- bencher/results/holoview_results/surface_result.py +158 -0
- bencher/results/holoview_results/table_result.py +14 -0
- bencher/results/holoview_results/tabulator_result.py +20 -0
- bencher/results/optuna_result.py +30 -115
- bencher/results/video_controls.py +38 -0
- bencher/results/video_result.py +39 -36
- bencher/results/video_summary.py +2 -2
- bencher/results/{plotly_result.py → volume_result.py} +29 -8
- bencher/utils.py +175 -26
- bencher/variables/inputs.py +122 -15
- bencher/video_writer.py +2 -1
- bencher/worker_job.py +31 -3
- {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/METADATA +24 -24
- holobench-1.43.0.dist-info/RECORD +147 -0
- bencher/example/example_levels2.py +0 -37
- bencher/example/inputs_1D/example_1_in_1_out.py +0 -62
- bencher/example/inputs_1D/example_1_in_2_out.py +0 -63
- bencher/example/inputs_1D/example_1_in_2_out_repeats.py +0 -61
- bencher/results/holoview_result.py +0 -796
- bencher/results/panel_result.py +0 -41
- holobench-1.41.0.dist-info/RECORD +0 -114
- {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/WHEEL +0 -0
- {holobench-1.41.0.dist-info → holobench-1.43.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,346 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import List, Optional
|
3
|
+
import panel as pn
|
4
|
+
import holoviews as hv
|
5
|
+
from param import Parameter
|
6
|
+
from functools import partial
|
7
|
+
import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
|
8
|
+
import hvplot.pandas # noqa pylint: disable=duplicate-code,unused-import
|
9
|
+
import xarray as xr
|
10
|
+
|
11
|
+
from bencher.utils import (
|
12
|
+
hmap_canonical_input,
|
13
|
+
get_nearest_coords,
|
14
|
+
listify,
|
15
|
+
)
|
16
|
+
from bencher.results.video_result import VideoResult
|
17
|
+
from bencher.results.bench_result_base import ReduceType
|
18
|
+
|
19
|
+
from bencher.variables.results import ResultVar, ResultImage, ResultVideo
|
20
|
+
|
21
|
+
hv.extension("bokeh", "plotly")
|
22
|
+
|
23
|
+
# Flag to enable or disable tap tool functionality in visualizations
|
24
|
+
use_tap = True
|
25
|
+
|
26
|
+
|
27
|
+
class HoloviewResult(VideoResult):
|
28
|
+
@staticmethod
|
29
|
+
def set_default_opts(width: int = 600, height: int = 600) -> dict:
|
30
|
+
"""Set default options for HoloViews visualizations.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
width (int, optional): Default width for visualizations. Defaults to 600.
|
34
|
+
height (int, optional): Default height for visualizations. Defaults to 600.
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
dict: Dictionary containing width, height, and tools settings.
|
38
|
+
"""
|
39
|
+
width_height = {"width": width, "height": height, "tools": ["hover"]}
|
40
|
+
hv.opts.defaults(
|
41
|
+
hv.opts.Curve(**width_height),
|
42
|
+
hv.opts.Points(**width_height),
|
43
|
+
hv.opts.Bars(**width_height),
|
44
|
+
hv.opts.Scatter(**width_height),
|
45
|
+
hv.opts.BoxWhisker(**width_height),
|
46
|
+
hv.opts.Violin(**width_height),
|
47
|
+
hv.opts.HeatMap(cmap="plasma", **width_height, colorbar=True),
|
48
|
+
# hv.opts.Surface(**width_heigh),
|
49
|
+
hv.opts.GridSpace(plot_size=400),
|
50
|
+
)
|
51
|
+
return width_height
|
52
|
+
|
53
|
+
def to_hv_type(self, hv_type: type, reduce: ReduceType = ReduceType.AUTO, **kwargs) -> hv.Chart:
|
54
|
+
"""Convert the dataset to a specific HoloViews visualization type.
|
55
|
+
|
56
|
+
Args:
|
57
|
+
hv_type (type): The HoloViews chart type to convert to (e.g., hv.Points, hv.Curve).
|
58
|
+
reduce (ReduceType, optional): How to reduce dataset dimensions. Defaults to ReduceType.AUTO.
|
59
|
+
**kwargs: Additional parameters to pass to the chart constructor.
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
hv.Chart: A HoloViews chart of the specified type.
|
63
|
+
"""
|
64
|
+
return self.to_hv_dataset(reduce).to(hv_type, **kwargs)
|
65
|
+
|
66
|
+
def overlay_plots(self, plot_callback: callable) -> Optional[hv.Overlay | pn.Row]:
|
67
|
+
"""Create an overlay of plots by applying a callback to each result variable.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
plot_callback (callable): Function to apply to each result variable to create a plot.
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
Optional[hv.Overlay | pn.Row]: An overlay of plots or Row of plots, or None if no results.
|
74
|
+
"""
|
75
|
+
results = []
|
76
|
+
markdown_results = pn.Row()
|
77
|
+
for rv in self.bench_cfg.result_vars:
|
78
|
+
res = plot_callback(rv)
|
79
|
+
if res is not None:
|
80
|
+
if isinstance(res, pn.pane.Markdown):
|
81
|
+
markdown_results.append(res)
|
82
|
+
else:
|
83
|
+
results.append(res)
|
84
|
+
if len(results) > 0:
|
85
|
+
overlay = hv.Overlay(results).collate()
|
86
|
+
if len(markdown_results) == 0:
|
87
|
+
return overlay
|
88
|
+
return pn.Row(overlay, markdown_results)
|
89
|
+
if len(markdown_results) > 0:
|
90
|
+
return markdown_results
|
91
|
+
return None
|
92
|
+
|
93
|
+
def layout_plots(self, plot_callback: callable) -> Optional[hv.Layout]:
|
94
|
+
"""Create a layout of plots by applying a callback to each result variable.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
plot_callback (callable): Function to apply to each result variable to create a plot.
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
Optional[hv.Layout]: A layout of plots or None if no results.
|
101
|
+
"""
|
102
|
+
if len(self.bench_cfg.result_vars) > 0:
|
103
|
+
pt = hv.Layout()
|
104
|
+
got_results = False
|
105
|
+
for rv in self.bench_cfg.result_vars:
|
106
|
+
res = plot_callback(rv)
|
107
|
+
if res is not None:
|
108
|
+
got_results = True
|
109
|
+
pt += plot_callback(rv)
|
110
|
+
return pt if got_results else None
|
111
|
+
return plot_callback(self.bench_cfg.result_vars[0])
|
112
|
+
|
113
|
+
def time_widget(self, title: str) -> dict:
|
114
|
+
"""Create widget configuration for time-based visualizations.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
title (str): Title for the widget.
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
dict: Widget configuration dictionary.
|
121
|
+
"""
|
122
|
+
return {"title": title}
|
123
|
+
# if self.bench_cfg.over_time:
|
124
|
+
# time_widget_args = {"widget_type": "scrubber", "widget_location": "bottom"}
|
125
|
+
# time_widget_args["title"] = None # use the title generated by the widget instead
|
126
|
+
# else:
|
127
|
+
# time_widget_args = {"widget_type": "individual"}
|
128
|
+
# time_widget_args["title"] = title
|
129
|
+
|
130
|
+
# return time_widget_args
|
131
|
+
|
132
|
+
def hv_container_ds(
|
133
|
+
self, dataset: xr.Dataset, result_var: Parameter, container: hv.Chart = None, **kwargs
|
134
|
+
) -> hv.Chart:
|
135
|
+
"""Convert an xarray Dataset to a HoloViews container for a specific result variable.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
dataset (xr.Dataset): The xarray Dataset containing the data.
|
139
|
+
result_var (Parameter): The result variable to visualize.
|
140
|
+
container (hv.Chart, optional): The HoloViews container type to use. Defaults to None.
|
141
|
+
**kwargs: Additional options to pass to the chart's opts() method.
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
hv.Chart: A HoloViews chart containing the visualization.
|
145
|
+
"""
|
146
|
+
return hv.Dataset(dataset[result_var.name]).to(container).opts(**kwargs)
|
147
|
+
|
148
|
+
def to_hv_container(
|
149
|
+
self,
|
150
|
+
container: pn.pane.panel,
|
151
|
+
reduce_type: ReduceType = ReduceType.AUTO,
|
152
|
+
target_dimension: int = 2,
|
153
|
+
result_var: Parameter = None,
|
154
|
+
result_types: tuple = (ResultVar),
|
155
|
+
**kwargs,
|
156
|
+
) -> Optional[pn.pane.panel]:
|
157
|
+
"""Convert the data to a HoloViews container with specified dimensions and options.
|
158
|
+
|
159
|
+
Args:
|
160
|
+
container (pn.pane.panel): The panel container type to use.
|
161
|
+
reduce_type (ReduceType, optional): How to reduce the dataset dimensions. Defaults to ReduceType.AUTO.
|
162
|
+
target_dimension (int, optional): Target dimension for the visualization. Defaults to 2.
|
163
|
+
result_var (Parameter, optional): Specific result variable to visualize. Defaults to None.
|
164
|
+
result_types (tuple, optional): Types of result variables to include. Defaults to (ResultVar).
|
165
|
+
**kwargs: Additional visualization options.
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
Optional[pn.pane.panel]: A panel containing the visualization, or None if no valid results.
|
169
|
+
"""
|
170
|
+
return self.map_plot_panes(
|
171
|
+
partial(self.hv_container_ds, container=container),
|
172
|
+
hv_dataset=self.to_hv_dataset(reduce_type),
|
173
|
+
target_dimension=target_dimension,
|
174
|
+
result_var=result_var,
|
175
|
+
result_types=result_types,
|
176
|
+
**kwargs,
|
177
|
+
)
|
178
|
+
|
179
|
+
def result_var_to_container(self, result_var: Parameter) -> type:
|
180
|
+
"""Determine the appropriate container type for a given result variable.
|
181
|
+
|
182
|
+
Args:
|
183
|
+
result_var (Parameter): The result variable to find a container for.
|
184
|
+
|
185
|
+
Returns:
|
186
|
+
type: The appropriate panel container type (PNG, Video, or Column).
|
187
|
+
"""
|
188
|
+
if isinstance(result_var, ResultImage):
|
189
|
+
return pn.pane.PNG
|
190
|
+
return pn.pane.Video if isinstance(result_var, ResultVideo) else pn.Column
|
191
|
+
|
192
|
+
def setup_results_and_containers(
|
193
|
+
self,
|
194
|
+
result_var_plots: Parameter | List[Parameter],
|
195
|
+
container: type | List[type] = None,
|
196
|
+
**kwargs,
|
197
|
+
) -> tuple[List[Parameter], List[pn.pane.panel]]:
|
198
|
+
"""Set up appropriate containers for result variables.
|
199
|
+
|
200
|
+
Args:
|
201
|
+
result_var_plots (Parameter | List[Parameter]): Result variables to visualize.
|
202
|
+
container (type | List[type], optional): Container types to use. Defaults to None.
|
203
|
+
**kwargs: Additional options to pass to the container constructors.
|
204
|
+
|
205
|
+
Returns:
|
206
|
+
tuple[List[Parameter], List[pn.pane.panel]]: Tuple containing:
|
207
|
+
- List of result variables
|
208
|
+
- List of initialized container instances
|
209
|
+
"""
|
210
|
+
result_var_plots = listify(result_var_plots)
|
211
|
+
if container is None:
|
212
|
+
containers = [self.result_var_to_container(rv) for rv in result_var_plots]
|
213
|
+
else:
|
214
|
+
containers = listify(container)
|
215
|
+
|
216
|
+
cont_instances = [c(**kwargs) if c is not None else None for c in containers]
|
217
|
+
return result_var_plots, cont_instances
|
218
|
+
|
219
|
+
def to_error_bar(self, result_var: Parameter | str = None, **kwargs) -> hv.Bars:
|
220
|
+
"""Convert the dataset to an ErrorBars visualization for a specific result variable.
|
221
|
+
|
222
|
+
Args:
|
223
|
+
result_var (Parameter | str, optional): Result variable to visualize. Defaults to None.
|
224
|
+
**kwargs: Additional options for dataset reduction.
|
225
|
+
|
226
|
+
Returns:
|
227
|
+
hv.Bars: A HoloViews ErrorBars object showing error ranges.
|
228
|
+
"""
|
229
|
+
return self.to_hv_dataset(ReduceType.REDUCE, result_var=result_var, **kwargs).to(
|
230
|
+
hv.ErrorBars
|
231
|
+
)
|
232
|
+
|
233
|
+
def to_points(self, reduce: ReduceType = ReduceType.AUTO) -> hv.Points:
|
234
|
+
"""Convert the dataset to a Points visualization with optional error bars.
|
235
|
+
|
236
|
+
Args:
|
237
|
+
reduce (ReduceType, optional): How to reduce the dataset dimensions. Defaults to ReduceType.AUTO.
|
238
|
+
|
239
|
+
Returns:
|
240
|
+
hv.Points: A HoloViews Points object, potentially with ErrorBars if reduction is applied.
|
241
|
+
"""
|
242
|
+
ds = self.to_hv_dataset(reduce)
|
243
|
+
pt = ds.to(hv.Points)
|
244
|
+
# if reduce:
|
245
|
+
# pt *= ds.to(hv.ErrorBars)
|
246
|
+
return pt
|
247
|
+
|
248
|
+
def to_nd_layout(self, hmap_name: str) -> hv.NdLayout:
|
249
|
+
"""Convert a HoloMap to an NdLayout for multi-dimensional visualization.
|
250
|
+
|
251
|
+
Args:
|
252
|
+
hmap_name (str): Name of the HoloMap to convert.
|
253
|
+
|
254
|
+
Returns:
|
255
|
+
hv.NdLayout: A HoloViews NdLayout object with the visualization.
|
256
|
+
"""
|
257
|
+
return hv.NdLayout(self.get_hmap(hmap_name), kdims=self.bench_cfg.hmap_kdims).opts(
|
258
|
+
shared_axes=False, shared_datasource=False
|
259
|
+
)
|
260
|
+
|
261
|
+
def to_holomap(self, name: str = None) -> hv.HoloMap:
|
262
|
+
"""Convert an NdLayout to a HoloMap for animated/interactive visualization.
|
263
|
+
|
264
|
+
Args:
|
265
|
+
name (str, optional): Name of the HoloMap to use. Defaults to None.
|
266
|
+
|
267
|
+
Returns:
|
268
|
+
hv.HoloMap: A HoloViews HoloMap object with the visualization.
|
269
|
+
"""
|
270
|
+
return hv.HoloMap(self.to_nd_layout(name)).opts(shared_axes=False)
|
271
|
+
|
272
|
+
def to_holomap_list(self, hmap_names: List[str] = None) -> pn.Column:
|
273
|
+
"""Create a column of HoloMaps from multiple named maps.
|
274
|
+
|
275
|
+
Args:
|
276
|
+
hmap_names (List[str], optional): List of HoloMap names to include.
|
277
|
+
If None, uses all result_hmaps. Defaults to None.
|
278
|
+
|
279
|
+
Returns:
|
280
|
+
pn.Column: A panel Column containing multiple HoloMaps.
|
281
|
+
"""
|
282
|
+
if hmap_names is None:
|
283
|
+
hmap_names = [i.name for i in self.result_hmaps]
|
284
|
+
col = pn.Column()
|
285
|
+
for name in hmap_names:
|
286
|
+
self.to_holomap(name)
|
287
|
+
return col
|
288
|
+
|
289
|
+
def get_nearest_holomap(self, name: str = None, **kwargs) -> hv.HoloMap:
|
290
|
+
"""Get the HoloMap element closest to the specified coordinates.
|
291
|
+
|
292
|
+
Args:
|
293
|
+
name (str, optional): Name of the HoloMap to use. Defaults to None.
|
294
|
+
**kwargs: Coordinate values to find nearest match for.
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
hv.HoloMap: The nearest matching HoloMap element.
|
298
|
+
"""
|
299
|
+
canonical_inp = hmap_canonical_input(
|
300
|
+
get_nearest_coords(self.ds, collapse_list=True, **kwargs)
|
301
|
+
)
|
302
|
+
return self.get_hmap(name)[canonical_inp].opts(framewise=True)
|
303
|
+
|
304
|
+
def to_dynamic_map(self, name: str = None) -> hv.DynamicMap:
|
305
|
+
"""Create a DynamicMap from the HoloMap dictionary.
|
306
|
+
|
307
|
+
Uses the values stored in the holomap dictionary to populate a dynamic map.
|
308
|
+
This is much faster than passing the holomap to a holomap object as the
|
309
|
+
values are calculated on the fly.
|
310
|
+
|
311
|
+
Args:
|
312
|
+
name (str, optional): Name of the HoloMap to use. Defaults to None.
|
313
|
+
|
314
|
+
Returns:
|
315
|
+
hv.DynamicMap: A HoloViews DynamicMap for interactive visualization.
|
316
|
+
"""
|
317
|
+
|
318
|
+
def cb(**kwargs):
|
319
|
+
return self.get_hmap(name)[hmap_canonical_input(kwargs)].opts(
|
320
|
+
framewise=True, shared_axes=False
|
321
|
+
)
|
322
|
+
|
323
|
+
kdims = []
|
324
|
+
for i in self.bench_cfg.input_vars + [self.bench_cfg.iv_repeat]:
|
325
|
+
kdims.append(i.as_dim(compute_values=True))
|
326
|
+
|
327
|
+
return hv.DynamicMap(cb, kdims=kdims)
|
328
|
+
|
329
|
+
def to_grid(self, inputs: List[str] = None) -> hv.GridSpace:
|
330
|
+
"""Create a grid visualization from a HoloMap.
|
331
|
+
|
332
|
+
Args:
|
333
|
+
inputs (List[str], optional): Input variable names to use for the grid dimensions.
|
334
|
+
If None, uses bench_cfg.inputs_as_str(). Defaults to None.
|
335
|
+
|
336
|
+
Returns:
|
337
|
+
hv.GridSpace: A HoloViews GridSpace object showing the data as a grid.
|
338
|
+
"""
|
339
|
+
if inputs is None:
|
340
|
+
inputs = self.bench_cfg.inputs_as_str()
|
341
|
+
if len(inputs) > 2:
|
342
|
+
inputs = inputs[:2]
|
343
|
+
return self.to_holomap().grid(inputs)
|
344
|
+
|
345
|
+
|
346
|
+
HoloviewResult.set_default_opts()
|
@@ -0,0 +1,240 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import List, Optional
|
3
|
+
import panel as pn
|
4
|
+
import holoviews as hv
|
5
|
+
from param import Parameter
|
6
|
+
from functools import partial
|
7
|
+
import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
|
8
|
+
import xarray as xr
|
9
|
+
|
10
|
+
from bencher.utils import (
|
11
|
+
get_nearest_coords1D,
|
12
|
+
)
|
13
|
+
from bencher.results.bench_result_base import ReduceType
|
14
|
+
from bencher.plotting.plot_filter import VarRange
|
15
|
+
from bencher.variables.results import ResultVar
|
16
|
+
from bencher.results.holoview_results.holoview_result import HoloviewResult
|
17
|
+
|
18
|
+
|
19
|
+
class LineResult(HoloviewResult):
|
20
|
+
"""A class for creating line plot visualizations from benchmark results.
|
21
|
+
|
22
|
+
Line plots are effective for visualizing trends in data over a continuous variable.
|
23
|
+
This class provides methods to generate interactive line plots from benchmark data,
|
24
|
+
with options for adding interactive tap functionality to display detailed information
|
25
|
+
about specific data points.
|
26
|
+
"""
|
27
|
+
|
28
|
+
def to_plot(
|
29
|
+
self,
|
30
|
+
result_var: Parameter = None,
|
31
|
+
tap_var=None,
|
32
|
+
tap_container: pn.pane.panel = None,
|
33
|
+
target_dimension=2,
|
34
|
+
override: bool = True,
|
35
|
+
use_tap: bool = None,
|
36
|
+
**kwargs,
|
37
|
+
) -> Optional[pn.panel]:
|
38
|
+
"""Generates a line plot from benchmark data.
|
39
|
+
|
40
|
+
This is a convenience method that calls to_line() with the same parameters.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
result_var (Parameter, optional): The result variable to plot. If None, uses the default.
|
44
|
+
tap_var: Variables to display when tapping on line plot points.
|
45
|
+
tap_container (pn.pane.panel, optional): Container to hold tapped information.
|
46
|
+
target_dimension (int, optional): Target dimensionality for the plot. Defaults to 2.
|
47
|
+
override (bool, optional): Whether to override filter restrictions. Defaults to True.
|
48
|
+
use_tap (bool, optional): Whether to enable tap functionality.
|
49
|
+
**kwargs: Additional keyword arguments passed to the plot rendering.
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
Optional[pn.panel]: A panel containing the line plot if data is appropriate,
|
53
|
+
otherwise returns filter match results.
|
54
|
+
"""
|
55
|
+
return self.to_line(
|
56
|
+
result_var=result_var,
|
57
|
+
tap_var=tap_var,
|
58
|
+
tap_container=tap_container,
|
59
|
+
target_dimension=target_dimension,
|
60
|
+
override=override,
|
61
|
+
use_tap=use_tap,
|
62
|
+
**kwargs,
|
63
|
+
)
|
64
|
+
|
65
|
+
def to_line(
|
66
|
+
self,
|
67
|
+
result_var: Parameter = None,
|
68
|
+
tap_var=None,
|
69
|
+
tap_container: pn.pane.panel = None,
|
70
|
+
target_dimension=2,
|
71
|
+
override: bool = True,
|
72
|
+
use_tap: bool = None,
|
73
|
+
**kwargs,
|
74
|
+
) -> Optional[pn.panel]:
|
75
|
+
"""Generates a line plot from benchmark data.
|
76
|
+
|
77
|
+
This method applies filters to ensure the data is appropriate for a line plot
|
78
|
+
and then passes the filtered data to the appropriate rendering method. If tap
|
79
|
+
functionality is enabled, it will create an interactive line plot that displays
|
80
|
+
additional information when data points are selected.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
result_var (Parameter, optional): The result variable to plot. If None, uses the default.
|
84
|
+
tap_var: Variables to display when tapping on line plot points.
|
85
|
+
tap_container (pn.pane.panel, optional): Container to hold tapped information.
|
86
|
+
target_dimension (int, optional): Target dimensionality for the plot. Defaults to 2.
|
87
|
+
override (bool, optional): Whether to override filter restrictions. Defaults to True.
|
88
|
+
use_tap (bool, optional): Whether to enable tap functionality.
|
89
|
+
**kwargs: Additional keyword arguments passed to the plot rendering.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
Optional[pn.panel]: A panel containing the line plot if data is appropriate,
|
93
|
+
otherwise returns filter match results.
|
94
|
+
"""
|
95
|
+
if tap_var is None:
|
96
|
+
tap_var = self.plt_cnt_cfg.panel_vars
|
97
|
+
elif not isinstance(tap_var, list):
|
98
|
+
tap_var = [tap_var]
|
99
|
+
|
100
|
+
if len(tap_var) == 0 or self.plt_cnt_cfg.inputs_cnt > 1 or not use_tap:
|
101
|
+
line_cb = self.to_line_ds
|
102
|
+
else:
|
103
|
+
line_cb = partial(
|
104
|
+
self.to_line_tap_ds, result_var_plots=tap_var, container=tap_container
|
105
|
+
)
|
106
|
+
|
107
|
+
return self.filter(
|
108
|
+
line_cb,
|
109
|
+
float_range=VarRange(1, 1),
|
110
|
+
cat_range=VarRange(0, None),
|
111
|
+
repeats_range=VarRange(1, 1),
|
112
|
+
panel_range=VarRange(0, None),
|
113
|
+
reduce=ReduceType.SQUEEZE,
|
114
|
+
target_dimension=target_dimension,
|
115
|
+
result_var=result_var,
|
116
|
+
result_types=(ResultVar),
|
117
|
+
override=override,
|
118
|
+
**kwargs,
|
119
|
+
)
|
120
|
+
|
121
|
+
def to_line_ds(self, dataset: xr.Dataset, result_var: Parameter, **kwargs):
|
122
|
+
"""Creates a basic line plot from the provided dataset.
|
123
|
+
|
124
|
+
Given a filtered dataset, this method generates a line plot visualization showing
|
125
|
+
the relationship between a continuous input variable and the result variable.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
dataset (xr.Dataset): The dataset containing benchmark results.
|
129
|
+
result_var (Parameter): The result variable to plot.
|
130
|
+
**kwargs: Additional keyword arguments passed to the line plot options.
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
hvplot.element.Curve: A line plot visualization of the benchmark data.
|
134
|
+
"""
|
135
|
+
x = self.plt_cnt_cfg.float_vars[0].name
|
136
|
+
by = None
|
137
|
+
if self.plt_cnt_cfg.cat_cnt >= 1:
|
138
|
+
by = self.plt_cnt_cfg.cat_vars[0].name
|
139
|
+
da_plot = dataset[result_var.name]
|
140
|
+
title = self.title_from_ds(da_plot, result_var, **kwargs)
|
141
|
+
time_widget_args = self.time_widget(title)
|
142
|
+
return da_plot.hvplot.line(x=x, by=by, **time_widget_args, **kwargs)
|
143
|
+
|
144
|
+
def to_line_tap_ds(
|
145
|
+
self,
|
146
|
+
dataset: xr.Dataset,
|
147
|
+
result_var: Parameter,
|
148
|
+
result_var_plots: List[Parameter] = None,
|
149
|
+
container: pn.pane.panel = pn.pane.panel,
|
150
|
+
**kwargs,
|
151
|
+
) -> pn.Row:
|
152
|
+
"""Creates an interactive line plot with tap functionality.
|
153
|
+
|
154
|
+
This method generates a line plot with interactive hover and tap functionality that
|
155
|
+
displays additional information about selected points in separate containers.
|
156
|
+
|
157
|
+
Args:
|
158
|
+
dataset (xr.Dataset): The dataset containing benchmark results.
|
159
|
+
result_var (Parameter): The primary result variable to plot in the line plot.
|
160
|
+
result_var_plots (List[Parameter], optional): Additional result variables to display when a point is tapped.
|
161
|
+
container (pn.pane.panel, optional): Container to display tapped information.
|
162
|
+
**kwargs: Additional keyword arguments passed to the line plot options.
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
pn.Row: A panel row containing the interactive line plot and containers for tapped information.
|
166
|
+
"""
|
167
|
+
htmap = self.to_line_ds(dataset, result_var).opts(tools=["hover"], **kwargs)
|
168
|
+
result_var_plots, cont_instances = self.setup_results_and_containers(
|
169
|
+
result_var_plots, container
|
170
|
+
)
|
171
|
+
title = pn.pane.Markdown("Selected: None")
|
172
|
+
|
173
|
+
state = dict(x=None, y=None, update=False)
|
174
|
+
|
175
|
+
def tap_plot_line(x, y): # pragma: no cover
|
176
|
+
# print(f"{x},{y}")
|
177
|
+
# print(dataset)
|
178
|
+
|
179
|
+
# xv = self.bench_cfg.input_vars[0].name
|
180
|
+
# yv = self.bench_cfg.input_vars[1].name
|
181
|
+
|
182
|
+
# x_nearest_new = get_nearest_coords1D(
|
183
|
+
# x, dataset.coords[self.bench_cfg.input_vars[0].name].data
|
184
|
+
# )
|
185
|
+
# y_nearest_new = get_nearest_coords1D(
|
186
|
+
# y, dataset.coords[self.bench_cfg.input_vars[1].name].data
|
187
|
+
# )
|
188
|
+
|
189
|
+
# kwargs = {xv: x, yv: y}
|
190
|
+
|
191
|
+
# nearest = get_nearest_coords(dataset, **kwargs)
|
192
|
+
# print(nearest)
|
193
|
+
|
194
|
+
x_nearest_new = get_nearest_coords1D(
|
195
|
+
x, dataset.coords[self.bench_cfg.input_vars[0].name].data
|
196
|
+
)
|
197
|
+
|
198
|
+
if x_nearest_new != state["x"]:
|
199
|
+
state["x"] = x_nearest_new
|
200
|
+
state["update"] = True
|
201
|
+
|
202
|
+
if self.plt_cnt_cfg.inputs_cnt > 1:
|
203
|
+
y_nearest_new = get_nearest_coords1D(
|
204
|
+
y, dataset.coords[self.bench_cfg.input_vars[1].name].data
|
205
|
+
)
|
206
|
+
if y_nearest_new != state["y"]:
|
207
|
+
state["y"] = y_nearest_new
|
208
|
+
state["update"] = True
|
209
|
+
|
210
|
+
if state["update"]:
|
211
|
+
kdims = {}
|
212
|
+
kdims[self.bench_cfg.input_vars[0].name] = state["x"]
|
213
|
+
if self.plt_cnt_cfg.inputs_cnt > 1:
|
214
|
+
kdims[self.bench_cfg.input_vars[1].name] = state["y"]
|
215
|
+
|
216
|
+
if hasattr(htmap, "current_key"):
|
217
|
+
for d, k in zip(htmap.kdims, htmap.current_key):
|
218
|
+
kdims[d.name] = k
|
219
|
+
for rv, cont in zip(result_var_plots, cont_instances):
|
220
|
+
ds = dataset[rv.name]
|
221
|
+
val = ds.sel(**kdims)
|
222
|
+
item = self.zero_dim_da_to_val(val)
|
223
|
+
title.object = "Selected: " + ", ".join([f"{k}:{v}" for k, v in kdims.items()])
|
224
|
+
cont.object = item
|
225
|
+
if hasattr(cont, "autoplay"): # container is a video, set to autoplay
|
226
|
+
cont.paused = False
|
227
|
+
cont.time = 0
|
228
|
+
cont.loop = True
|
229
|
+
cont.autoplay = True
|
230
|
+
state["update"] = False
|
231
|
+
|
232
|
+
def on_exit(x, y): # pragma: no cover # pylint: disable=unused-argument
|
233
|
+
state["update"] = True
|
234
|
+
|
235
|
+
htmap_posxy = hv.streams.PointerXY(source=htmap)
|
236
|
+
htmap_posxy.add_subscriber(tap_plot_line)
|
237
|
+
ls = hv.streams.MouseLeave(source=htmap)
|
238
|
+
ls.add_subscriber(on_exit)
|
239
|
+
bound_plot = pn.Column(title, *cont_instances)
|
240
|
+
return pn.Row(htmap, bound_plot)
|
@@ -0,0 +1,107 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Optional
|
3
|
+
import panel as pn
|
4
|
+
|
5
|
+
import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
|
6
|
+
import hvplot.pandas # noqa pylint: disable=duplicate-code,unused-import
|
7
|
+
from bencher.results.bench_result_base import ReduceType
|
8
|
+
|
9
|
+
from bencher.plotting.plot_filter import VarRange, PlotFilter
|
10
|
+
|
11
|
+
|
12
|
+
from bencher.results.holoview_results.holoview_result import HoloviewResult
|
13
|
+
|
14
|
+
|
15
|
+
class ScatterResult(HoloviewResult):
|
16
|
+
"""A class for creating scatter plots from benchmark results.
|
17
|
+
|
18
|
+
Scatter plots are useful for visualizing the distribution of individual data points
|
19
|
+
and identifying patterns, clusters, or outliers. This class provides methods to
|
20
|
+
generate scatter plots with jittering capabilities to better visualize overlapping
|
21
|
+
points, particularly useful for displaying benchmark results across multiple repetitions.
|
22
|
+
|
23
|
+
Methods include:
|
24
|
+
- to_scatter_jitter: Creates scatter plots with jittering for better visualization of overlapping points
|
25
|
+
- to_scatter: Creates standard scatter plots that can be grouped by categorical variables
|
26
|
+
"""
|
27
|
+
|
28
|
+
def to_plot(self, override: bool = True, **kwargs) -> Optional[pn.panel]:
|
29
|
+
"""Creates a standard scatter plot from benchmark data.
|
30
|
+
|
31
|
+
This is a convenience method that calls to_scatter() with the same parameters.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
override (bool, optional): Whether to override filter restrictions. Defaults to True.
|
35
|
+
**kwargs: Additional keyword arguments passed to the scatter plot options.
|
36
|
+
|
37
|
+
Returns:
|
38
|
+
Optional[pn.panel]: A panel containing the scatter plot if data is appropriate,
|
39
|
+
otherwise returns filter match results.
|
40
|
+
"""
|
41
|
+
return self.to_scatter(override=override, **kwargs)
|
42
|
+
|
43
|
+
def to_scatter(self, override: bool = True, **kwargs) -> Optional[pn.panel]:
|
44
|
+
"""Creates a standard scatter plot from benchmark data.
|
45
|
+
|
46
|
+
This method applies filters to ensure the data is appropriate for a scatter plot
|
47
|
+
and generates a visualization with points representing individual data points.
|
48
|
+
When categorical variables are present, the scatter points can be grouped.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
override (bool, optional): Whether to override filter restrictions. Defaults to True.
|
52
|
+
**kwargs: Additional keyword arguments passed to the scatter plot options.
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
Optional[pn.panel]: A panel containing the scatter plot if data is appropriate,
|
56
|
+
otherwise returns filter match results.
|
57
|
+
"""
|
58
|
+
match_res = PlotFilter(
|
59
|
+
float_range=VarRange(0, 0),
|
60
|
+
cat_range=VarRange(0, None),
|
61
|
+
repeats_range=VarRange(1, 1),
|
62
|
+
).matches_result(self.plt_cnt_cfg, "to_hvplot_scatter", override=override)
|
63
|
+
if match_res.overall:
|
64
|
+
hv_ds = self.to_hv_dataset(ReduceType.SQUEEZE)
|
65
|
+
by = None
|
66
|
+
subplots = False
|
67
|
+
if self.plt_cnt_cfg.cat_cnt > 1:
|
68
|
+
by = [v.name for v in self.bench_cfg.input_vars[1:]]
|
69
|
+
subplots = False
|
70
|
+
return hv_ds.data.hvplot.scatter(by=by, subplots=subplots, **kwargs).opts(
|
71
|
+
title=self.to_plot_title()
|
72
|
+
)
|
73
|
+
return match_res.to_panel(**kwargs)
|
74
|
+
|
75
|
+
# def to_scatter_jitter(
|
76
|
+
# self, result_var: Parameter = None, override: bool = True, **kwargs
|
77
|
+
# ) -> Optional[pn.panel]:
|
78
|
+
# return self.filter(
|
79
|
+
# self.to_scatter_ds,
|
80
|
+
# float_range=VarRange(0, 0),
|
81
|
+
# cat_range=VarRange(0, None),
|
82
|
+
# repeats_range=VarRange(2, None),
|
83
|
+
# reduce=ReduceType.NONE,
|
84
|
+
# target_dimension=2,
|
85
|
+
# result_var=result_var,
|
86
|
+
# result_types=(ResultVar),
|
87
|
+
# override=override,
|
88
|
+
# **kwargs,
|
89
|
+
# )
|
90
|
+
|
91
|
+
# def to_scatter_ds(self, dataset: xr.Dataset, result_var: Parameter, **kwargs) -> hv.Scatter:
|
92
|
+
# by = None
|
93
|
+
# if self.plt_cnt_cfg.cat_cnt >= 2:
|
94
|
+
# by = self.plt_cnt_cfg.cat_vars[1].name
|
95
|
+
# # print(dataset)
|
96
|
+
# da_plot = dataset[result_var.name]
|
97
|
+
# # da_plot = dataset
|
98
|
+
# # print(da_plot)
|
99
|
+
# print("by", by)
|
100
|
+
# title = self.title_from_ds(da_plot, result_var, **kwargs)
|
101
|
+
# time_widget_args = self.time_widget(title)
|
102
|
+
# print(da_plot)
|
103
|
+
# # return da_plot.hvplot.box(by=by, **time_widget_args, **kwargs)
|
104
|
+
# # return da_plot.hvplot.box( **time_widget_args, **kwargs)
|
105
|
+
# return da_plot.hvplot.scatter(
|
106
|
+
# y=result_var.name, x="repeat", by=by, **time_widget_args, **kwargs
|
107
|
+
# ).opts(jitter=0.1)
|