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
@@ -1,18 +1,16 @@
|
|
1
1
|
from typing import Optional
|
2
|
+
from functools import partial
|
3
|
+
|
2
4
|
import panel as pn
|
3
5
|
from param import Parameter
|
4
|
-
from bencher.results.bench_result_base import BenchResultBase, ReduceType
|
5
|
-
|
6
|
-
|
7
|
-
from functools import partial
|
8
6
|
import holoviews as hv
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
|
8
|
+
from bencher.variables.results import PANEL_TYPES
|
9
|
+
from bencher.results.bench_result_base import BenchResultBase, ReduceType
|
12
10
|
|
13
11
|
|
14
12
|
class DataSetResult(BenchResultBase):
|
15
|
-
def
|
13
|
+
def to_plot(
|
16
14
|
self,
|
17
15
|
result_var: Parameter = None,
|
18
16
|
hv_dataset=None,
|
@@ -33,195 +31,3 @@ class DataSetResult(BenchResultBase):
|
|
33
31
|
result_types=PANEL_TYPES,
|
34
32
|
**kwargs,
|
35
33
|
)
|
36
|
-
|
37
|
-
|
38
|
-
# class DataSetResult(BenchResultBase):
|
39
|
-
|
40
|
-
# def to_datatset(
|
41
|
-
# self,
|
42
|
-
# result_var: Parameter = None,
|
43
|
-
# result_types=(ResultDataSet,),
|
44
|
-
# pane_collection: pn.pane = None,
|
45
|
-
# time_sequence_dimension=0,
|
46
|
-
# target_duration: float = None,
|
47
|
-
# **kwargs,
|
48
|
-
# ) -> Optional[pn.panel]:
|
49
|
-
# """Returns the results compiled into a video
|
50
|
-
|
51
|
-
# Args:
|
52
|
-
# result_var (Parameter, optional): The result var to plot. Defaults to None.
|
53
|
-
# result_types (tuple, optional): The types of result var to convert to video. Defaults to (ResultDataSet,).
|
54
|
-
# collection (pn.pane, optional): If there are multiple results, use this collection to stack them. Defaults to pn.Row().
|
55
|
-
|
56
|
-
# Returns:
|
57
|
-
# Optional[pn.panel]: a panel pane with a video of all results concatenated together
|
58
|
-
# """
|
59
|
-
# plot_filter = PlotFilter(
|
60
|
-
# float_range=VarRange(0, None),
|
61
|
-
# cat_range=VarRange(0, None),
|
62
|
-
# panel_range=VarRange(1, None),
|
63
|
-
# input_range=VarRange(0, None),
|
64
|
-
# )
|
65
|
-
# matches_res = plot_filter.matches_result(
|
66
|
-
# self.plt_cnt_cfg, callable_name(self.to_video_grid_ds)
|
67
|
-
# )
|
68
|
-
|
69
|
-
# if pane_collection is None:
|
70
|
-
# pane_collection = pn.Row()
|
71
|
-
|
72
|
-
# if matches_res.overall:
|
73
|
-
# ds = self.to_dataset(ReduceType.SQUEEZE)
|
74
|
-
# for rv in self.get_results_var_list(result_var):
|
75
|
-
# if isinstance(rv, result_types):
|
76
|
-
# pane_collection.append(
|
77
|
-
# self.to_video_grid_ds(
|
78
|
-
# ds,
|
79
|
-
# rv,
|
80
|
-
# time_sequence_dimension=time_sequence_dimension,
|
81
|
-
# target_duration=target_duration,
|
82
|
-
# **kwargs,
|
83
|
-
# )
|
84
|
-
# )
|
85
|
-
# return pane_collection
|
86
|
-
# return matches_res.to_panel()
|
87
|
-
|
88
|
-
# def to_video_grid_ds(
|
89
|
-
# self,
|
90
|
-
# dataset: xr.Dataset,
|
91
|
-
# result_var: Parameter,
|
92
|
-
# reverse=True,
|
93
|
-
# time_sequence_dimension=0,
|
94
|
-
# video_controls: VideoControls = None,
|
95
|
-
# target_duration: float = None,
|
96
|
-
# **kwargs,
|
97
|
-
# ):
|
98
|
-
# cvc = self._to_video_panes_ds(
|
99
|
-
# dataset,
|
100
|
-
# self.plot_cb,
|
101
|
-
# target_dimension=0,
|
102
|
-
# horizontal=True,
|
103
|
-
# compose_method=ComposeType.right,
|
104
|
-
# time_sequence_dimension=time_sequence_dimension,
|
105
|
-
# result_var=result_var,
|
106
|
-
# final=True,
|
107
|
-
# reverse=reverse,
|
108
|
-
# target_duration=target_duration,
|
109
|
-
# **kwargs,
|
110
|
-
# )
|
111
|
-
|
112
|
-
# filename = VideoWriter().write_video_raw(cvc)
|
113
|
-
|
114
|
-
# if filename is not None:
|
115
|
-
# if video_controls is None:
|
116
|
-
# video_controls = VideoControls()
|
117
|
-
# return video_controls.video_container(
|
118
|
-
# filename, width=kwargs.get("width", None), height=kwargs.get("height", None)
|
119
|
-
# )
|
120
|
-
# return None
|
121
|
-
|
122
|
-
# def plot_cb(self, dataset, result_var, **kwargs):
|
123
|
-
# val = self.ds_to_container(dataset, result_var, container=None, **kwargs)
|
124
|
-
# return val
|
125
|
-
|
126
|
-
# def dataset_to_compose_list(
|
127
|
-
# self,
|
128
|
-
# dataset: xr.Dataset,
|
129
|
-
# first_compose_method: ComposeType = ComposeType.down,
|
130
|
-
# time_sequence_dimension: int = 0,
|
131
|
-
# ) -> List[ComposeType]:
|
132
|
-
# """ "Given a dataset, chose an order for composing the results. By default will flip between right and down and the last dimension will be a time sequence.
|
133
|
-
|
134
|
-
# Args:
|
135
|
-
# dataset (xr.Dataset): the dataset to render
|
136
|
-
# first_compose_method (ComposeType, optional): the direction of the first composition method. Defaults to ComposeType.right.
|
137
|
-
# time_sequence_dimension (int, optional): The dimension to start time sequencing instead of composing in space. Defaults to 0.
|
138
|
-
|
139
|
-
# Returns:
|
140
|
-
# List[ComposeType]: A list of composition methods for composing the dataset result
|
141
|
-
# """
|
142
|
-
|
143
|
-
# num_dims = len(dataset.sizes)
|
144
|
-
# if time_sequence_dimension == -1: # use time sequence for everything
|
145
|
-
# compose_method_list = [ComposeType.sequence] * (num_dims + 1)
|
146
|
-
# else:
|
147
|
-
# compose_method_list = [first_compose_method]
|
148
|
-
# compose_method_list.extend(
|
149
|
-
# ComposeType.flip(compose_method_list[-1]) for _ in range(num_dims - 1)
|
150
|
-
# )
|
151
|
-
# compose_method_list.append(ComposeType.sequence)
|
152
|
-
|
153
|
-
# for i in range(min(len(compose_method_list), time_sequence_dimension + 1)):
|
154
|
-
# compose_method_list[i] = ComposeType.sequence
|
155
|
-
|
156
|
-
# return compose_method_list
|
157
|
-
|
158
|
-
# def _to_video_panes_ds(
|
159
|
-
# self,
|
160
|
-
# dataset: xr.Dataset,
|
161
|
-
# plot_callback: callable = None,
|
162
|
-
# target_dimension=0,
|
163
|
-
# compose_method=ComposeType.right,
|
164
|
-
# compose_method_list=None,
|
165
|
-
# result_var=None,
|
166
|
-
# time_sequence_dimension=0,
|
167
|
-
# root_dimensions=None,
|
168
|
-
# reverse=False,
|
169
|
-
# target_duration: float = None,
|
170
|
-
# **kwargs,
|
171
|
-
# ) -> pn.panel:
|
172
|
-
# num_dims = len(dataset.sizes)
|
173
|
-
# dims = list(d for d in dataset.sizes)
|
174
|
-
# if reverse:
|
175
|
-
# dims = list(reversed(dims))
|
176
|
-
|
177
|
-
# if root_dimensions is None:
|
178
|
-
# root_dimensions = num_dims
|
179
|
-
|
180
|
-
# if compose_method_list is None:
|
181
|
-
# compose_method_list = self.dataset_to_compose_list(
|
182
|
-
# dataset, compose_method, time_sequence_dimension=time_sequence_dimension
|
183
|
-
# )
|
184
|
-
|
185
|
-
# # print(compose_method_list)
|
186
|
-
|
187
|
-
# compose_method_list_pop = deepcopy(compose_method_list)
|
188
|
-
# if len(compose_method_list_pop) > 1:
|
189
|
-
# compose_method = compose_method_list_pop.pop()
|
190
|
-
|
191
|
-
# if num_dims > (target_dimension) and num_dims != 0:
|
192
|
-
# selected_dim = dims[-1]
|
193
|
-
# outer_container = ComposableContainerVideo()
|
194
|
-
# for i in range(dataset.sizes[selected_dim]):
|
195
|
-
# sliced = dataset.isel({selected_dim: i})
|
196
|
-
# label_val = sliced.coords[selected_dim].values.item()
|
197
|
-
# inner_container = ComposableContainerVideo()
|
198
|
-
|
199
|
-
# panes = self._to_video_panes_ds(
|
200
|
-
# sliced,
|
201
|
-
# plot_callback=plot_callback,
|
202
|
-
# target_dimension=target_dimension,
|
203
|
-
# compose_method_list=compose_method_list_pop,
|
204
|
-
# result_var=result_var,
|
205
|
-
# root_dimensions=root_dimensions,
|
206
|
-
# time_sequence_dimension=time_sequence_dimension,
|
207
|
-
# )
|
208
|
-
# inner_container.append(panes)
|
209
|
-
|
210
|
-
# rendered = inner_container.render(
|
211
|
-
# RenderCfg(
|
212
|
-
# var_name=selected_dim,
|
213
|
-
# var_value=label_val,
|
214
|
-
# compose_method=compose_method,
|
215
|
-
# duration=target_duration,
|
216
|
-
# )
|
217
|
-
# )
|
218
|
-
# outer_container.append(rendered)
|
219
|
-
# return outer_container.render(
|
220
|
-
# RenderCfg(
|
221
|
-
# compose_method=compose_method,
|
222
|
-
# duration=target_duration,
|
223
|
-
# background_col=color_tuple_to_255(int_to_col(num_dims - 2, 0.05, 1.0)),
|
224
|
-
# # background_col= (255,0,0),
|
225
|
-
# )
|
226
|
-
# )
|
227
|
-
# return plot_callback(dataset=dataset, result_var=result_var, **kwargs)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import panel as pn
|
3
|
+
import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
|
4
|
+
import hvplot.pandas # noqa pylint: disable=duplicate-code,unused-import
|
5
|
+
|
6
|
+
from bencher.results.video_result import VideoResult
|
7
|
+
|
8
|
+
|
9
|
+
class ExplorerResult(VideoResult):
|
10
|
+
def to_plot(self, **kwargs) -> pn.pane.Pane: # noqa pylint: disable=unused-argument
|
11
|
+
"""Produces a hvplot explorer instance to explore the generated dataset
|
12
|
+
see: https://hvplot.holoviz.org/getting_started/explorer.html
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
pn.pane.Pane: A dynamic pane for exploring a dataset
|
16
|
+
"""
|
17
|
+
|
18
|
+
if len(self.bench_cfg.input_vars) > 0:
|
19
|
+
return self.to_xarray().hvplot.explorer()
|
20
|
+
|
21
|
+
# For some reason hvplot doesn't like 1D datasets in xarray, so convert to pandas which it has no problem with
|
22
|
+
# TODO look into why this is, its probably due to how I am setting up the indexing in xarray.
|
23
|
+
return self.to_pandas().hvplot.explorer()
|
@@ -6,30 +6,15 @@ import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
|
|
6
6
|
import hvplot.pandas # noqa pylint: disable=duplicate-code,unused-import
|
7
7
|
import xarray as xr
|
8
8
|
|
9
|
-
from bencher.results.
|
9
|
+
from bencher.results.video_result import VideoResult
|
10
10
|
from bencher.results.bench_result_base import ReduceType
|
11
11
|
|
12
12
|
from bencher.plotting.plot_filter import VarRange
|
13
13
|
from bencher.variables.results import ResultVar
|
14
14
|
|
15
15
|
|
16
|
-
class
|
17
|
-
def
|
18
|
-
"""Produces a hvplot explorer instance to explore the generated dataset
|
19
|
-
see: https://hvplot.holoviz.org/getting_started/explorer.html
|
20
|
-
|
21
|
-
Returns:
|
22
|
-
pn.pane.Pane: A dynamic pane for exploring a dataset
|
23
|
-
"""
|
24
|
-
|
25
|
-
if len(self.bench_cfg.input_vars) > 0:
|
26
|
-
return self.to_xarray().hvplot.explorer()
|
27
|
-
|
28
|
-
# For some reason hvplot doesn't like 1D datasets in xarray, so convert to pandas which it has no problem with
|
29
|
-
# TODO look into why this is, its probably due to how I am setting up the indexing in xarray.
|
30
|
-
return self.to_pandas().hvplot.explorer()
|
31
|
-
|
32
|
-
def to_histogram(self, result_var: Parameter = None, **kwargs) -> Optional[pn.pane.Pane]:
|
16
|
+
class HistogramResult(VideoResult):
|
17
|
+
def to_plot(self, result_var: Parameter = None, **kwargs) -> Optional[pn.pane.Pane]:
|
33
18
|
return self.filter(
|
34
19
|
self.to_histogram_ds,
|
35
20
|
float_range=VarRange(0, 0),
|
File without changes
|
@@ -0,0 +1,79 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Optional
|
3
|
+
import panel as pn
|
4
|
+
from param import Parameter
|
5
|
+
import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
|
6
|
+
import xarray as xr
|
7
|
+
|
8
|
+
from bencher.results.bench_result_base import ReduceType
|
9
|
+
from bencher.plotting.plot_filter import VarRange
|
10
|
+
from bencher.variables.results import ResultVar
|
11
|
+
from bencher.results.holoview_results.holoview_result import HoloviewResult
|
12
|
+
|
13
|
+
|
14
|
+
class BarResult(HoloviewResult):
|
15
|
+
"""A class for creating bar chart visualizations from benchmark results.
|
16
|
+
|
17
|
+
Bar charts are effective for comparing values across categorical variables or
|
18
|
+
discrete data points. This class provides methods to generate bar charts that
|
19
|
+
display benchmark results, particularly useful for comparing performance metrics
|
20
|
+
between different configurations or categories.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def to_plot(
|
24
|
+
self, result_var: Parameter = None, override: bool = True, **kwargs
|
25
|
+
) -> Optional[pn.panel]:
|
26
|
+
return self.to_bar(result_var, override, **kwargs)
|
27
|
+
|
28
|
+
def to_bar(
|
29
|
+
self, result_var: Parameter = None, override: bool = True, **kwargs
|
30
|
+
) -> Optional[pn.panel]:
|
31
|
+
"""Generates a bar chart from benchmark data.
|
32
|
+
|
33
|
+
This method applies filters to ensure the data is appropriate for a bar chart
|
34
|
+
and then passes the filtered data to to_bar_ds for rendering.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
result_var (Parameter, optional): The result variable to plot. If None, uses the default.
|
38
|
+
override (bool, optional): Whether to override filter restrictions. Defaults to True.
|
39
|
+
**kwargs: Additional keyword arguments passed to the plot rendering.
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
Optional[pn.panel]: A panel containing the bar chart if data is appropriate,
|
43
|
+
otherwise returns filter match results.
|
44
|
+
"""
|
45
|
+
return self.filter(
|
46
|
+
self.to_bar_ds,
|
47
|
+
float_range=VarRange(0, 0),
|
48
|
+
cat_range=VarRange(0, None),
|
49
|
+
repeats_range=VarRange(1, 1),
|
50
|
+
panel_range=VarRange(0, None),
|
51
|
+
reduce=ReduceType.SQUEEZE,
|
52
|
+
target_dimension=2,
|
53
|
+
result_var=result_var,
|
54
|
+
result_types=(ResultVar),
|
55
|
+
override=override,
|
56
|
+
**kwargs,
|
57
|
+
)
|
58
|
+
|
59
|
+
def to_bar_ds(self, dataset: xr.Dataset, result_var: Parameter = None, **kwargs):
|
60
|
+
"""Creates a bar chart from the provided dataset.
|
61
|
+
|
62
|
+
Given a filtered dataset, this method generates a bar chart visualization showing
|
63
|
+
values of the result variable, potentially grouped by categorical variables.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
dataset (xr.Dataset): The dataset containing benchmark results.
|
67
|
+
result_var (Parameter, optional): The result variable to plot. If None, uses the default.
|
68
|
+
**kwargs: Additional keyword arguments passed to the bar chart options.
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
hvplot.element.Bars: A bar chart visualization of the benchmark data.
|
72
|
+
"""
|
73
|
+
by = None
|
74
|
+
if self.plt_cnt_cfg.cat_cnt >= 2:
|
75
|
+
by = self.plt_cnt_cfg.cat_vars[1].name
|
76
|
+
da_plot = dataset[result_var.name]
|
77
|
+
title = self.title_from_ds(da_plot, result_var, **kwargs)
|
78
|
+
time_widget_args = self.time_widget(title)
|
79
|
+
return da_plot.hvplot.bar(by=by, **time_widget_args, **kwargs)
|
@@ -0,0 +1,110 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Optional
|
3
|
+
import holoviews as hv
|
4
|
+
from param import Parameter
|
5
|
+
import hvplot.xarray # noqa pylint: disable=duplicate-code,unused-import
|
6
|
+
import xarray as xr
|
7
|
+
|
8
|
+
from bencher.results.bench_result_base import ReduceType
|
9
|
+
from bencher.plotting.plot_filter import VarRange
|
10
|
+
from bencher.variables.results import ResultVar
|
11
|
+
from bencher.results.holoview_results.holoview_result import HoloviewResult
|
12
|
+
|
13
|
+
|
14
|
+
class CurveResult(HoloviewResult):
|
15
|
+
"""A class for creating curve plots from benchmark results.
|
16
|
+
|
17
|
+
Curve plots are useful for visualizing the relationship between a continuous
|
18
|
+
input variable and a result variable. This class provides methods to generate
|
19
|
+
line plots that can also display standard deviation bounds when benchmark runs
|
20
|
+
include multiple repetitions.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def to_plot(
|
24
|
+
self, result_var: Parameter = None, override: bool = True, **kwargs
|
25
|
+
) -> Optional[hv.Curve]:
|
26
|
+
"""Generates a curve plot from benchmark data.
|
27
|
+
|
28
|
+
This is a convenience method that calls to_curve() with the same parameters.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
result_var (Parameter, optional): The result variable to plot. If None, uses the default.
|
32
|
+
override (bool, optional): Whether to override filter restrictions. Defaults to True.
|
33
|
+
**kwargs: Additional keyword arguments passed to the plot rendering.
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
Optional[hv.Curve]: A curve plot if data is appropriate,
|
37
|
+
otherwise returns filter match results.
|
38
|
+
"""
|
39
|
+
return self.to_curve(result_var=result_var, override=override, **kwargs)
|
40
|
+
|
41
|
+
def to_curve(self, result_var: Parameter = None, override: bool = True, **kwargs):
|
42
|
+
"""Generates a curve plot from benchmark data.
|
43
|
+
|
44
|
+
This method applies filters to ensure the data is appropriate for a curve plot
|
45
|
+
and then passes the filtered data to to_curve_ds for rendering.
|
46
|
+
|
47
|
+
Args:
|
48
|
+
result_var (Parameter, optional): The result variable to plot. If None, uses the default.
|
49
|
+
override (bool, optional): Whether to override filter restrictions. Defaults to True.
|
50
|
+
**kwargs: Additional keyword arguments passed to the plot rendering.
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
Optional[hv.Curve]: A curve plot if data is appropriate,
|
54
|
+
otherwise returns filter match results.
|
55
|
+
"""
|
56
|
+
return self.filter(
|
57
|
+
self.to_curve_ds,
|
58
|
+
float_range=VarRange(1, 1),
|
59
|
+
cat_range=VarRange(0, None),
|
60
|
+
repeats_range=VarRange(2, None),
|
61
|
+
reduce=ReduceType.REDUCE,
|
62
|
+
# reduce=ReduceType.MINMAX,
|
63
|
+
target_dimension=2,
|
64
|
+
result_var=result_var,
|
65
|
+
result_types=(ResultVar),
|
66
|
+
override=override,
|
67
|
+
**kwargs,
|
68
|
+
)
|
69
|
+
|
70
|
+
def to_curve_ds(
|
71
|
+
self, dataset: xr.Dataset, result_var: Parameter, **kwargs
|
72
|
+
) -> Optional[hv.Curve]:
|
73
|
+
"""Creates a curve plot from the provided dataset.
|
74
|
+
|
75
|
+
Given a filtered dataset, this method generates a curve visualization showing
|
76
|
+
the relationship between a continuous input variable and the result variable.
|
77
|
+
When multiple benchmark repetitions are available, standard deviation bounds
|
78
|
+
can also be displayed using a spread plot.
|
79
|
+
|
80
|
+
Args:
|
81
|
+
dataset (xr.Dataset): The dataset containing benchmark results.
|
82
|
+
result_var (Parameter): The result variable to plot.
|
83
|
+
**kwargs: Additional keyword arguments passed to the curve plot options.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
Optional[hv.Curve]: A curve plot with optional standard deviation spread.
|
87
|
+
"""
|
88
|
+
hvds = hv.Dataset(dataset)
|
89
|
+
title = self.title_from_ds(dataset, result_var, **kwargs)
|
90
|
+
# print(result_var.name)
|
91
|
+
# print( dataset)
|
92
|
+
pt = hv.Overlay()
|
93
|
+
# find pairs of {var_name} {var_name}_std to plot the line and their spreads.
|
94
|
+
var = result_var.name
|
95
|
+
std_var = f"{var}_std"
|
96
|
+
pt *= hvds.to(hv.Curve, vdims=var, label=var).opts(title=title, **kwargs)
|
97
|
+
# Only create a Spread if the matching _std variable exists
|
98
|
+
if std_var in dataset.data_vars:
|
99
|
+
pt *= hvds.to(hv.Spread, vdims=[var, std_var])
|
100
|
+
|
101
|
+
# for var in dataset.data_vars:
|
102
|
+
# print(var)
|
103
|
+
# if not var.endswith("_std"):
|
104
|
+
# std_var = f"{var}_std"
|
105
|
+
# pt *= hvds.to(hv.Curve, vdims=var, label=var).opts(title=title, **kwargs)
|
106
|
+
# #Only create a Spread if the matching _std variable exists
|
107
|
+
# if std_var in dataset.data_vars:
|
108
|
+
# pt *= hvds.to(hv.Spread, vdims=[var, std_var])
|
109
|
+
|
110
|
+
return pt.opts(legend_position="right")
|
File without changes
|
@@ -0,0 +1,73 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Optional, Any
|
3
|
+
import panel as pn
|
4
|
+
import holoviews as hv
|
5
|
+
from param import Parameter
|
6
|
+
import xarray as xr
|
7
|
+
|
8
|
+
from bencher.results.holoview_results.distribution_result.distribution_result import (
|
9
|
+
DistributionResult,
|
10
|
+
)
|
11
|
+
|
12
|
+
|
13
|
+
class BoxWhiskerResult(DistributionResult):
|
14
|
+
"""A class for creating box and whisker plots from benchmark results.
|
15
|
+
|
16
|
+
Box and whisker plots are useful for visualizing the distribution of data,
|
17
|
+
including the median, quartiles, and potential outliers. This class provides
|
18
|
+
methods to generate these plots from benchmark data, particularly useful for
|
19
|
+
comparing distributions across different categorical variables or between
|
20
|
+
different repetitions of the same benchmark.
|
21
|
+
|
22
|
+
Box plots show:
|
23
|
+
- The median (middle line in the box)
|
24
|
+
- The interquartile range (IQR) as a box (25th to 75th percentile)
|
25
|
+
- Whiskers extending to the furthest data points within 1.5*IQR
|
26
|
+
- Outliers as individual points beyond the whiskers
|
27
|
+
"""
|
28
|
+
|
29
|
+
def to_plot(
|
30
|
+
self, result_var: Optional[Parameter] = None, override: bool = True, **kwargs: Any
|
31
|
+
) -> Optional[pn.panel]:
|
32
|
+
"""Generates a box and whisker plot from benchmark data.
|
33
|
+
|
34
|
+
This method applies filters to ensure the data is appropriate for a box plot
|
35
|
+
and then passes the filtered data to to_boxplot_ds for rendering.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
result_var (Optional[Parameter]): The result variable to plot. If None, uses the default.
|
39
|
+
override (bool): Whether to override filter restrictions. Defaults to True.
|
40
|
+
**kwargs (Any): Additional keyword arguments passed to the plot rendering.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
Optional[pn.panel]: A panel containing the box plot if data is appropriate,
|
44
|
+
otherwise returns filter match results.
|
45
|
+
"""
|
46
|
+
return self.to_distribution_plot(
|
47
|
+
self.to_boxplot_ds,
|
48
|
+
result_var=result_var,
|
49
|
+
override=override,
|
50
|
+
**kwargs,
|
51
|
+
)
|
52
|
+
|
53
|
+
def to_boxplot_ds(
|
54
|
+
self, dataset: xr.Dataset, result_var: Parameter, **kwargs: Any
|
55
|
+
) -> hv.BoxWhisker:
|
56
|
+
"""Creates a box and whisker plot from the provided dataset.
|
57
|
+
|
58
|
+
Given a filtered dataset, this method generates a box and whisker visualization showing
|
59
|
+
the distribution of values for a result variable, potentially grouped by a categorical variable.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
dataset (xr.Dataset): The dataset containing benchmark results.
|
63
|
+
result_var (Parameter): The result variable to plot.
|
64
|
+
**kwargs (Any): Additional keyword arguments for plot customization such as:
|
65
|
+
- box_fill_color: Color for the box
|
66
|
+
- whisker_color: Color for the whiskers
|
67
|
+
- outlier_color: Color for outlier points
|
68
|
+
- line_width: Width of lines in the plot
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
hv.BoxWhisker: A HoloViews BoxWhisker plot of the benchmark data.
|
72
|
+
"""
|
73
|
+
return self._plot_distribution(dataset, result_var, hv.BoxWhisker, **kwargs)
|
@@ -0,0 +1,109 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Optional, Callable, Any, Type
|
3
|
+
import panel as pn
|
4
|
+
import holoviews as hv
|
5
|
+
from param import Parameter
|
6
|
+
import xarray as xr
|
7
|
+
|
8
|
+
from bencher.results.bench_result_base import ReduceType
|
9
|
+
from bencher.plotting.plot_filter import VarRange
|
10
|
+
from bencher.variables.results import ResultVar
|
11
|
+
from bencher.results.holoview_results.holoview_result import HoloviewResult
|
12
|
+
from bencher.utils import params_to_str
|
13
|
+
|
14
|
+
|
15
|
+
class DistributionResult(HoloviewResult):
|
16
|
+
"""A base class for creating distribution plots (violin, box-whisker) from benchmark results.
|
17
|
+
|
18
|
+
This class provides common functionality for various distribution plot types that show
|
19
|
+
the distribution shape of the data. Child classes implement specific plot types
|
20
|
+
(e.g., violin plots, box and whisker plots) but share filtering and data preparation logic.
|
21
|
+
|
22
|
+
Distribution plots are particularly useful for visualizing the statistical spread of
|
23
|
+
benchmark metrics across different configurations, allowing for better understanding
|
24
|
+
of performance variability.
|
25
|
+
"""
|
26
|
+
|
27
|
+
def to_distribution_plot(
|
28
|
+
self,
|
29
|
+
plot_method: Callable[[xr.Dataset, Parameter, Any], hv.Element],
|
30
|
+
result_var: Optional[Parameter] = None,
|
31
|
+
override: bool = True,
|
32
|
+
**kwargs: Any,
|
33
|
+
) -> Optional[pn.panel]:
|
34
|
+
"""Generates a distribution plot from benchmark data.
|
35
|
+
|
36
|
+
This method applies filters to ensure the data is appropriate for distribution plots
|
37
|
+
and then passes the filtered data to the specified plot method for rendering.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
plot_method: The method to use for creating the specific plot type (e.g., violin, box-whisker)
|
41
|
+
result_var: The result variable to plot. If None, uses the default.
|
42
|
+
override: Whether to override filter restrictions. Defaults to True.
|
43
|
+
**kwargs: Additional keyword arguments passed to the plot rendering.
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
A panel containing the plot if data is appropriate,
|
47
|
+
otherwise returns filter match results.
|
48
|
+
"""
|
49
|
+
return self.filter(
|
50
|
+
plot_method,
|
51
|
+
float_range=VarRange(0, 0),
|
52
|
+
cat_range=VarRange(0, None),
|
53
|
+
repeats_range=VarRange(2, None),
|
54
|
+
reduce=ReduceType.NONE,
|
55
|
+
target_dimension=self.plt_cnt_cfg.cat_cnt + 1, # +1 cos we have a repeats dimension
|
56
|
+
result_var=result_var,
|
57
|
+
result_types=(ResultVar),
|
58
|
+
override=override,
|
59
|
+
**kwargs,
|
60
|
+
)
|
61
|
+
|
62
|
+
def _plot_distribution(
|
63
|
+
self,
|
64
|
+
dataset: xr.Dataset,
|
65
|
+
result_var: Parameter,
|
66
|
+
plot_class: Type[hv.Selection1DExpr],
|
67
|
+
**kwargs: Any,
|
68
|
+
) -> hv.Element:
|
69
|
+
"""Prepares data for distribution plots and creates the plot.
|
70
|
+
|
71
|
+
This method handles common operations needed for all distribution plot types,
|
72
|
+
including converting data formats, setting up dimensions, and configuring
|
73
|
+
plot aesthetics.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
dataset: The dataset containing benchmark results.
|
77
|
+
result_var: The result variable to plot.
|
78
|
+
plot_class: The HoloViews plot class to use (e.g., hv.Violin, hv.BoxWhisker)
|
79
|
+
**kwargs: Additional keyword arguments for plot customization.
|
80
|
+
|
81
|
+
Returns:
|
82
|
+
A HoloViews Element representing the distribution plot.
|
83
|
+
"""
|
84
|
+
# Get the name of the result variable (which is the data we want to plot)
|
85
|
+
var_name = result_var.name
|
86
|
+
|
87
|
+
# Create plot title
|
88
|
+
title = self.title_from_ds(dataset[var_name], result_var, **kwargs)
|
89
|
+
|
90
|
+
# Convert dataset to dataframe for HoloViews
|
91
|
+
df = dataset[var_name].to_dataframe().reset_index()
|
92
|
+
kdims = params_to_str(self.plt_cnt_cfg.cat_vars)
|
93
|
+
|
94
|
+
if not isinstance(plot_class, list):
|
95
|
+
plot_class = [plot_class]
|
96
|
+
|
97
|
+
overlay = hv.Overlay()
|
98
|
+
for plot in plot_class:
|
99
|
+
overlay *= plot(
|
100
|
+
df,
|
101
|
+
kdims=kdims,
|
102
|
+
vdims=[var_name],
|
103
|
+
).opts(
|
104
|
+
title=title,
|
105
|
+
ylabel=f"{var_name} [{result_var.units}]",
|
106
|
+
xrotation=30, # Rotate x-axis labels by 30 degrees
|
107
|
+
**kwargs,
|
108
|
+
)
|
109
|
+
return overlay
|