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
@@ -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)