holobench 1.25.2__py3-none-any.whl → 1.27.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bencher/bench_report.py +6 -109
- bencher/example/__init__.py +0 -0
- bencher/example/benchmark_data.py +196 -0
- bencher/example/example_all.py +45 -0
- bencher/example/example_categorical.py +99 -0
- bencher/example/example_composable_container.py +106 -0
- bencher/example/example_composable_container2.py +160 -0
- bencher/example/example_consts.py +39 -0
- bencher/example/example_custom_sweep.py +59 -0
- bencher/example/example_custom_sweep2.py +42 -0
- bencher/example/example_docs.py +34 -0
- bencher/example/example_filepath.py +27 -0
- bencher/example/example_float3D.py +101 -0
- bencher/example/example_float_cat.py +99 -0
- bencher/example/example_floats.py +89 -0
- bencher/example/example_floats2D.py +93 -0
- bencher/example/example_holosweep.py +98 -0
- bencher/example/example_holosweep_objects.py +111 -0
- bencher/example/example_holosweep_tap.py +144 -0
- bencher/example/example_image.py +155 -0
- bencher/example/example_levels.py +181 -0
- bencher/example/example_levels2.py +37 -0
- bencher/example/example_pareto.py +53 -0
- bencher/example/example_sample_cache.py +85 -0
- bencher/example/example_sample_cache_context.py +116 -0
- bencher/example/example_simple.py +134 -0
- bencher/example/example_simple_bool.py +35 -0
- bencher/example/example_simple_cat.py +48 -0
- bencher/example/example_simple_float.py +28 -0
- bencher/example/example_simple_float2d.py +29 -0
- bencher/example/example_strings.py +47 -0
- bencher/example/example_time_event.py +63 -0
- bencher/example/example_video.py +118 -0
- bencher/example/example_workflow.py +189 -0
- bencher/example/experimental/example_bokeh_plotly.py +38 -0
- bencher/example/experimental/example_hover_ex.py +45 -0
- bencher/example/experimental/example_hvplot_explorer.py +39 -0
- bencher/example/experimental/example_interactive.py +75 -0
- bencher/example/experimental/example_streamnd.py +49 -0
- bencher/example/experimental/example_streams.py +36 -0
- bencher/example/experimental/example_template.py +40 -0
- bencher/example/experimental/example_updates.py +84 -0
- bencher/example/experimental/example_vector.py +84 -0
- bencher/example/meta/example_meta.py +171 -0
- bencher/example/meta/example_meta_cat.py +25 -0
- bencher/example/meta/example_meta_float.py +23 -0
- bencher/example/meta/example_meta_levels.py +26 -0
- bencher/example/optuna/example_optuna.py +78 -0
- bencher/example/shelved/example_float2D_scatter.py +109 -0
- bencher/example/shelved/example_float3D_cone.py +96 -0
- bencher/example/shelved/example_kwargs.py +63 -0
- bencher/plotting/__init__.py +0 -0
- bencher/plotting/plot_filter.py +110 -0
- bencher/plotting/plt_cnt_cfg.py +75 -0
- bencher/results/__init__.py +0 -0
- bencher/results/bench_result.py +94 -0
- bencher/results/bench_result_base.py +476 -0
- bencher/results/composable_container/__init__.py +0 -0
- bencher/results/composable_container/composable_container_base.py +73 -0
- bencher/results/composable_container/composable_container_panel.py +39 -0
- bencher/results/composable_container/composable_container_video.py +184 -0
- bencher/results/float_formatter.py +44 -0
- bencher/results/holoview_result.py +753 -0
- bencher/results/optuna_result.py +354 -0
- bencher/results/panel_result.py +41 -0
- bencher/results/plotly_result.py +65 -0
- bencher/results/video_result.py +38 -0
- bencher/results/video_summary.py +222 -0
- bencher/variables/__init__.py +0 -0
- bencher/variables/inputs.py +202 -0
- bencher/variables/parametrised_sweep.py +208 -0
- bencher/variables/results.py +214 -0
- bencher/variables/sweep_base.py +162 -0
- bencher/variables/time.py +92 -0
- holobench-1.27.0.data/data/share/ament_index/resource_index/packages/bencher +0 -0
- holobench-1.27.0.data/data/share/bencher/package.xml +33 -0
- {holobench-1.25.2.dist-info → holobench-1.27.0.dist-info}/METADATA +5 -5
- holobench-1.27.0.dist-info/RECORD +93 -0
- holobench-1.25.2.dist-info/RECORD +0 -18
- {holobench-1.25.2.dist-info → holobench-1.27.0.dist-info}/LICENSE +0 -0
- {holobench-1.25.2.dist-info → holobench-1.27.0.dist-info}/WHEEL +0 -0
- {holobench-1.25.2.dist-info → holobench-1.27.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,222 @@
|
|
1
|
+
from typing import Optional, List
|
2
|
+
from copy import deepcopy
|
3
|
+
import panel as pn
|
4
|
+
import xarray as xr
|
5
|
+
from param import Parameter
|
6
|
+
from bencher.results.bench_result_base import BenchResultBase, ReduceType
|
7
|
+
from bencher.variables.results import ResultImage
|
8
|
+
from bencher.plotting.plot_filter import VarRange, PlotFilter
|
9
|
+
from bencher.utils import callable_name, int_to_col, color_tuple_to_255
|
10
|
+
from bencher.video_writer import VideoWriter
|
11
|
+
from bencher.results.video_result import VideoControls
|
12
|
+
from bencher.results.composable_container.composable_container_video import (
|
13
|
+
ComposableContainerVideo,
|
14
|
+
ComposeType,
|
15
|
+
RenderCfg,
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
class VideoSummaryResult(BenchResultBase):
|
20
|
+
def to_video_summary(
|
21
|
+
self,
|
22
|
+
result_var: Parameter = None,
|
23
|
+
reverse: bool = True,
|
24
|
+
result_types=(ResultImage,),
|
25
|
+
**kwargs,
|
26
|
+
) -> Optional[pn.panel]:
|
27
|
+
return self.to_video_grid(
|
28
|
+
result_var=result_var,
|
29
|
+
result_types=result_types,
|
30
|
+
time_sequence_dimension=-1,
|
31
|
+
reverse=reverse,
|
32
|
+
**kwargs,
|
33
|
+
)
|
34
|
+
|
35
|
+
def to_video_grid(
|
36
|
+
self,
|
37
|
+
result_var: Parameter = None,
|
38
|
+
result_types=(ResultImage,),
|
39
|
+
pane_collection: pn.pane = None,
|
40
|
+
time_sequence_dimension=0,
|
41
|
+
target_duration: float = None,
|
42
|
+
**kwargs,
|
43
|
+
) -> Optional[pn.panel]:
|
44
|
+
"""Returns the results compiled into a video
|
45
|
+
|
46
|
+
Args:
|
47
|
+
result_var (Parameter, optional): The result var to plot. Defaults to None.
|
48
|
+
result_types (tuple, optional): The types of result var to convert to video. Defaults to (ResultImage,).
|
49
|
+
collection (pn.pane, optional): If there are multiple results, use this collection to stack them. Defaults to pn.Row().
|
50
|
+
|
51
|
+
Returns:
|
52
|
+
Optional[pn.panel]: a panel pane with a video of all results concatenated together
|
53
|
+
"""
|
54
|
+
plot_filter = PlotFilter(
|
55
|
+
float_range=VarRange(0, None),
|
56
|
+
cat_range=VarRange(0, None),
|
57
|
+
panel_range=VarRange(1, None),
|
58
|
+
input_range=VarRange(1, None),
|
59
|
+
)
|
60
|
+
matches_res = plot_filter.matches_result(
|
61
|
+
self.plt_cnt_cfg, callable_name(self.to_video_grid_ds)
|
62
|
+
)
|
63
|
+
|
64
|
+
if pane_collection is None:
|
65
|
+
pane_collection = pn.Row()
|
66
|
+
|
67
|
+
if matches_res.overall:
|
68
|
+
ds = self.to_dataset(ReduceType.SQUEEZE)
|
69
|
+
for rv in self.get_results_var_list(result_var):
|
70
|
+
if isinstance(rv, result_types):
|
71
|
+
pane_collection.append(
|
72
|
+
self.to_video_grid_ds(
|
73
|
+
ds,
|
74
|
+
rv,
|
75
|
+
time_sequence_dimension=time_sequence_dimension,
|
76
|
+
target_duration=target_duration,
|
77
|
+
**kwargs,
|
78
|
+
)
|
79
|
+
)
|
80
|
+
return pane_collection
|
81
|
+
return matches_res.to_panel()
|
82
|
+
|
83
|
+
def to_video_grid_ds(
|
84
|
+
self,
|
85
|
+
dataset: xr.Dataset,
|
86
|
+
result_var: Parameter,
|
87
|
+
reverse=True,
|
88
|
+
time_sequence_dimension=0,
|
89
|
+
video_controls: VideoControls = None,
|
90
|
+
target_duration: float = None,
|
91
|
+
**kwargs,
|
92
|
+
):
|
93
|
+
cvc = self._to_video_panes_ds(
|
94
|
+
dataset,
|
95
|
+
self.plot_cb,
|
96
|
+
target_dimension=0,
|
97
|
+
horizontal=True,
|
98
|
+
compose_method=ComposeType.right,
|
99
|
+
time_sequence_dimension=time_sequence_dimension,
|
100
|
+
result_var=result_var,
|
101
|
+
final=True,
|
102
|
+
reverse=reverse,
|
103
|
+
target_duration=target_duration,
|
104
|
+
**kwargs,
|
105
|
+
)
|
106
|
+
|
107
|
+
filename = VideoWriter().write_video_raw(cvc)
|
108
|
+
|
109
|
+
if filename is not None:
|
110
|
+
if video_controls is None:
|
111
|
+
video_controls = VideoControls()
|
112
|
+
return video_controls.video_container(
|
113
|
+
filename, width=kwargs.get("width", None), height=kwargs.get("height", None)
|
114
|
+
)
|
115
|
+
return None
|
116
|
+
|
117
|
+
def plot_cb(self, dataset, result_var, **kwargs):
|
118
|
+
val = self.ds_to_container(dataset, result_var, container=None, **kwargs)
|
119
|
+
return val
|
120
|
+
|
121
|
+
def dataset_to_compose_list(
|
122
|
+
self,
|
123
|
+
dataset: xr.Dataset,
|
124
|
+
first_compose_method: ComposeType = ComposeType.down,
|
125
|
+
time_sequence_dimension: int = 0,
|
126
|
+
) -> List[ComposeType]:
|
127
|
+
""" "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.
|
128
|
+
|
129
|
+
Args:
|
130
|
+
dataset (xr.Dataset): the dataset to render
|
131
|
+
first_compose_method (ComposeType, optional): the direction of the first composition method. Defaults to ComposeType.right.
|
132
|
+
time_sequence_dimension (int, optional): The dimension to start time sequencing instead of composing in space. Defaults to 0.
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
List[ComposeType]: A list of composition methods for composing the dataset result
|
136
|
+
"""
|
137
|
+
|
138
|
+
num_dims = len(dataset.sizes)
|
139
|
+
if time_sequence_dimension == -1: # use time sequence for everything
|
140
|
+
compose_method_list = [ComposeType.sequence] * (num_dims + 1)
|
141
|
+
else:
|
142
|
+
compose_method_list = [first_compose_method]
|
143
|
+
compose_method_list.extend(
|
144
|
+
ComposeType.flip(compose_method_list[-1]) for _ in range(num_dims - 1)
|
145
|
+
)
|
146
|
+
compose_method_list.append(ComposeType.sequence)
|
147
|
+
|
148
|
+
for i in range(min(len(compose_method_list), time_sequence_dimension + 1)):
|
149
|
+
compose_method_list[i] = ComposeType.sequence
|
150
|
+
|
151
|
+
return compose_method_list
|
152
|
+
|
153
|
+
def _to_video_panes_ds(
|
154
|
+
self,
|
155
|
+
dataset: xr.Dataset,
|
156
|
+
plot_callback: callable = None,
|
157
|
+
target_dimension=0,
|
158
|
+
compose_method=ComposeType.right,
|
159
|
+
compose_method_list=None,
|
160
|
+
result_var=None,
|
161
|
+
time_sequence_dimension=0,
|
162
|
+
root_dimensions=None,
|
163
|
+
reverse=False,
|
164
|
+
target_duration: float = None,
|
165
|
+
**kwargs,
|
166
|
+
) -> pn.panel:
|
167
|
+
num_dims = len(dataset.sizes)
|
168
|
+
dims = list(d for d in dataset.sizes)
|
169
|
+
if reverse:
|
170
|
+
dims = list(reversed(dims))
|
171
|
+
|
172
|
+
if root_dimensions is None:
|
173
|
+
root_dimensions = num_dims
|
174
|
+
|
175
|
+
if compose_method_list is None:
|
176
|
+
compose_method_list = self.dataset_to_compose_list(
|
177
|
+
dataset, compose_method, time_sequence_dimension=time_sequence_dimension
|
178
|
+
)
|
179
|
+
|
180
|
+
# print(compose_method_list)
|
181
|
+
|
182
|
+
compose_method_list_pop = deepcopy(compose_method_list)
|
183
|
+
if len(compose_method_list_pop) > 1:
|
184
|
+
compose_method = compose_method_list_pop.pop()
|
185
|
+
|
186
|
+
if num_dims > (target_dimension) and num_dims != 0:
|
187
|
+
selected_dim = dims[-1]
|
188
|
+
outer_container = ComposableContainerVideo()
|
189
|
+
for i in range(dataset.sizes[selected_dim]):
|
190
|
+
sliced = dataset.isel({selected_dim: i})
|
191
|
+
label_val = sliced.coords[selected_dim].values.item()
|
192
|
+
inner_container = ComposableContainerVideo()
|
193
|
+
|
194
|
+
panes = self._to_video_panes_ds(
|
195
|
+
sliced,
|
196
|
+
plot_callback=plot_callback,
|
197
|
+
target_dimension=target_dimension,
|
198
|
+
compose_method_list=compose_method_list_pop,
|
199
|
+
result_var=result_var,
|
200
|
+
root_dimensions=root_dimensions,
|
201
|
+
time_sequence_dimension=time_sequence_dimension,
|
202
|
+
)
|
203
|
+
inner_container.append(panes)
|
204
|
+
|
205
|
+
rendered = inner_container.render(
|
206
|
+
RenderCfg(
|
207
|
+
var_name=selected_dim,
|
208
|
+
var_value=label_val,
|
209
|
+
compose_method=compose_method,
|
210
|
+
duration=target_duration,
|
211
|
+
)
|
212
|
+
)
|
213
|
+
outer_container.append(rendered)
|
214
|
+
return outer_container.render(
|
215
|
+
RenderCfg(
|
216
|
+
compose_method=compose_method,
|
217
|
+
duration=target_duration,
|
218
|
+
background_col=color_tuple_to_255(int_to_col(num_dims - 2, 0.05, 1.0)),
|
219
|
+
# background_col= (255,0,0),
|
220
|
+
)
|
221
|
+
)
|
222
|
+
return plot_callback(dataset=dataset, result_var=result_var, **kwargs)
|
File without changes
|
@@ -0,0 +1,202 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from typing import List, Any, Dict
|
3
|
+
|
4
|
+
import numpy as np
|
5
|
+
from param import Integer, Number, Selector
|
6
|
+
from bencher.variables.sweep_base import SweepBase, shared_slots
|
7
|
+
|
8
|
+
|
9
|
+
class SweepSelector(Selector, SweepBase):
|
10
|
+
"""A class to reprsent a parameter sweep of bools"""
|
11
|
+
|
12
|
+
__slots__ = shared_slots
|
13
|
+
|
14
|
+
def __init__(self, units: str = "ul", samples: int = None, **params):
|
15
|
+
SweepBase.__init__(self)
|
16
|
+
Selector.__init__(self, **params)
|
17
|
+
|
18
|
+
self.units = units
|
19
|
+
if samples is None:
|
20
|
+
self.samples = len(self.objects)
|
21
|
+
else:
|
22
|
+
self.samples = samples
|
23
|
+
|
24
|
+
def values(self) -> List[Any]:
|
25
|
+
"""return all the values for a parameter sweep. If debug is true return a reduced list"""
|
26
|
+
return self.indices_to_samples(self.samples, self.objects)
|
27
|
+
|
28
|
+
|
29
|
+
class BoolSweep(SweepSelector):
|
30
|
+
"""A class to reprsent a parameter sweep of bools"""
|
31
|
+
|
32
|
+
def __init__(self, units: str = "ul", samples: int = None, default=True, **params):
|
33
|
+
SweepSelector.__init__(
|
34
|
+
self,
|
35
|
+
units=units,
|
36
|
+
samples=samples,
|
37
|
+
default=default,
|
38
|
+
objects=[True, False] if default else [False, True],
|
39
|
+
**params,
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
class StringSweep(SweepSelector):
|
44
|
+
"""A class to reprsent a parameter sweep of strings"""
|
45
|
+
|
46
|
+
def __init__(
|
47
|
+
self,
|
48
|
+
string_list: List[str],
|
49
|
+
units: str = "ul",
|
50
|
+
samples: int = None,
|
51
|
+
**params,
|
52
|
+
):
|
53
|
+
SweepSelector.__init__(
|
54
|
+
self,
|
55
|
+
objects=string_list,
|
56
|
+
instantiate=True,
|
57
|
+
units=units,
|
58
|
+
samples=samples,
|
59
|
+
**params,
|
60
|
+
)
|
61
|
+
|
62
|
+
|
63
|
+
class EnumSweep(SweepSelector):
|
64
|
+
"""A class to reprsent a parameter sweep of enums"""
|
65
|
+
|
66
|
+
__slots__ = shared_slots
|
67
|
+
|
68
|
+
def __init__(self, enum_type: Enum | List[Enum], units="ul", samples=None, **params):
|
69
|
+
# The enum can either be an Enum type or a list of enums
|
70
|
+
list_of_enums = isinstance(enum_type, list)
|
71
|
+
selector_list = enum_type if list_of_enums else list(enum_type)
|
72
|
+
SweepSelector.__init__(
|
73
|
+
self,
|
74
|
+
objects=selector_list,
|
75
|
+
instantiate=True,
|
76
|
+
units=units,
|
77
|
+
samples=samples,
|
78
|
+
**params,
|
79
|
+
)
|
80
|
+
if not list_of_enums: # Grab the docs from the enum type def
|
81
|
+
self.doc = enum_type.__doc__
|
82
|
+
|
83
|
+
|
84
|
+
class IntSweep(Integer, SweepBase):
|
85
|
+
"""A class to reprsent a parameter sweep of ints"""
|
86
|
+
|
87
|
+
__slots__ = shared_slots + ["sample_values"]
|
88
|
+
|
89
|
+
def __init__(self, units="ul", samples=None, sample_values=None, **params):
|
90
|
+
SweepBase.__init__(self)
|
91
|
+
Integer.__init__(self, **params)
|
92
|
+
|
93
|
+
self.units = units
|
94
|
+
|
95
|
+
if sample_values is None:
|
96
|
+
if samples is None:
|
97
|
+
if self.bounds is None:
|
98
|
+
raise RuntimeError("You must define bounds for integer types")
|
99
|
+
self.samples = 1 + self.bounds[1] - self.bounds[0]
|
100
|
+
else:
|
101
|
+
self.samples = samples
|
102
|
+
self.sample_values = None
|
103
|
+
else:
|
104
|
+
self.sample_values = sample_values
|
105
|
+
self.samples = len(self.sample_values)
|
106
|
+
if "default" not in params:
|
107
|
+
self.default = sample_values[0]
|
108
|
+
|
109
|
+
def values(self) -> List[int]:
|
110
|
+
"""return all the values for a parameter sweep. If debug is true return the list"""
|
111
|
+
sample_values = (
|
112
|
+
self.sample_values
|
113
|
+
if self.sample_values is not None
|
114
|
+
else list(range(int(self.bounds[0]), int(self.bounds[1] + 1)))
|
115
|
+
)
|
116
|
+
|
117
|
+
return self.indices_to_samples(self.samples, sample_values)
|
118
|
+
|
119
|
+
###THESE ARE COPIES OF INTEGER VALIDATION BUT ALSO ALLOW NUMPY INT TYPES
|
120
|
+
def _validate_value(self, val, allow_None):
|
121
|
+
if callable(val):
|
122
|
+
return
|
123
|
+
|
124
|
+
if allow_None and val is None:
|
125
|
+
return
|
126
|
+
|
127
|
+
if not isinstance(val, (int, np.integer)):
|
128
|
+
raise ValueError(
|
129
|
+
"Integer parameter %r must be an integer, " "not type %r." % (self.name, type(val))
|
130
|
+
)
|
131
|
+
|
132
|
+
###THESE ARE COPIES OF INTEGER VALIDATION BUT ALSO ALLOW NUMPY INT TYPES
|
133
|
+
def _validate_step(self, val, step):
|
134
|
+
if step is not None and not isinstance(step, (int, np.integer)):
|
135
|
+
raise ValueError(
|
136
|
+
"Step can only be None or an " "integer value, not type %r" % type(step)
|
137
|
+
)
|
138
|
+
|
139
|
+
|
140
|
+
class FloatSweep(Number, SweepBase):
|
141
|
+
"""A class to represent a parameter sweep of floats"""
|
142
|
+
|
143
|
+
__slots__ = shared_slots + ["sample_values"]
|
144
|
+
|
145
|
+
def __init__(self, units="ul", samples=10, sample_values=None, step=None, **params):
|
146
|
+
SweepBase.__init__(self)
|
147
|
+
Number.__init__(self, step=step, **params)
|
148
|
+
|
149
|
+
self.units = units
|
150
|
+
|
151
|
+
self.sample_values = sample_values
|
152
|
+
|
153
|
+
if sample_values is None:
|
154
|
+
self.samples = samples
|
155
|
+
else:
|
156
|
+
self.samples = len(self.sample_values)
|
157
|
+
if "default" not in params:
|
158
|
+
self.default = sample_values[0]
|
159
|
+
|
160
|
+
def values(self) -> List[float]:
|
161
|
+
"""return all the values for a parameter sweep. If debug is true return a reduced list"""
|
162
|
+
samps = self.samples
|
163
|
+
if self.sample_values is None:
|
164
|
+
if self.step is None:
|
165
|
+
return np.linspace(self.bounds[0], self.bounds[1], samps)
|
166
|
+
|
167
|
+
return np.arange(self.bounds[0], self.bounds[1], self.step)
|
168
|
+
return self.sample_values
|
169
|
+
|
170
|
+
|
171
|
+
def box(name, center, width):
|
172
|
+
var = FloatSweep(default=center, bounds=(center - width, center + width))
|
173
|
+
var.name = name
|
174
|
+
return var
|
175
|
+
|
176
|
+
|
177
|
+
def p(
|
178
|
+
name: str, values: List[Any] = None, samples: int = None, max_level: int = None
|
179
|
+
) -> Dict[str, Any]:
|
180
|
+
"""
|
181
|
+
Create a parameter dictionary with optional values, samples, and max_level.
|
182
|
+
|
183
|
+
Args:
|
184
|
+
name (str): The name of the parameter.
|
185
|
+
values (List[Any], optional): A list of values for the parameter. Defaults to None.
|
186
|
+
samples (int, optional): The number of samples. Must be greater than 0 if provided. Defaults to None.
|
187
|
+
max_level (int, optional): The maximum level. Must be greater than 0 if provided. Defaults to None.
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
Dict[str, Any]: A dictionary containing the parameter details.
|
191
|
+
"""
|
192
|
+
if max_level is not None and max_level <= 0:
|
193
|
+
raise ValueError("max_level must be greater than 0")
|
194
|
+
|
195
|
+
if samples is not None and samples <= 0:
|
196
|
+
raise ValueError("samples must be greater than 0")
|
197
|
+
return {"name": name, "values": values, "max_level": max_level, "samples": samples}
|
198
|
+
|
199
|
+
|
200
|
+
def with_level(arr: list, level) -> list:
|
201
|
+
return IntSweep(sample_values=arr).with_level(level).values()
|
202
|
+
# return tmp.with_sample_values(arr).with_level(level).values()
|
@@ -0,0 +1,208 @@
|
|
1
|
+
from functools import partial
|
2
|
+
from typing import List, Tuple, Any
|
3
|
+
from param import Parameter, Parameterized
|
4
|
+
import holoviews as hv
|
5
|
+
import panel as pn
|
6
|
+
|
7
|
+
|
8
|
+
from bencher.utils import make_namedtuple, hash_sha1
|
9
|
+
from bencher.variables.results import ALL_RESULT_TYPES, ResultHmap
|
10
|
+
|
11
|
+
|
12
|
+
class ParametrizedSweep(Parameterized):
|
13
|
+
"""Parent class for all Sweep types that need a custom hash"""
|
14
|
+
|
15
|
+
@staticmethod
|
16
|
+
def param_hash(param_type: Parameterized, hash_value: bool = True) -> int:
|
17
|
+
"""A custom hash function for parametrised types with options for hashing the value of the type and hashing metadata
|
18
|
+
|
19
|
+
Args:
|
20
|
+
param_type (Parameterized): A parameter
|
21
|
+
hash_value (bool, optional): use the value as part of the hash. Defaults to True.
|
22
|
+
# hash_meta (bool, optional): use metadata as part of the hash. Defaults to False.
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
int: a hash
|
26
|
+
"""
|
27
|
+
|
28
|
+
curhash = 0
|
29
|
+
if hash_value:
|
30
|
+
for k, v in param_type.param.values().items():
|
31
|
+
if k != "name":
|
32
|
+
curhash = hash_sha1((curhash, hash_sha1(v)))
|
33
|
+
|
34
|
+
# if hash_meta:
|
35
|
+
# for k, v in param_type.param.objects().items():
|
36
|
+
# if k != "name":
|
37
|
+
# print(f"key:{k}, hash:{hash_sha1(k)}")
|
38
|
+
# print(f"value:{v}, hash:{hash_sha1(v)}")
|
39
|
+
# curhash = hash_sha1((curhash, hash_sha1(k), hash_sha1(v)))
|
40
|
+
return curhash
|
41
|
+
|
42
|
+
def hash_persistent(self) -> str:
|
43
|
+
"""A hash function that avoids the PYTHONHASHSEED 'feature' which returns a different hash value each time the program is run"""
|
44
|
+
return ParametrizedSweep.param_hash(self, True)
|
45
|
+
|
46
|
+
def update_params_from_kwargs(self, **kwargs) -> None:
|
47
|
+
"""Given a dictionary of kwargs, set the parameters of the passed class 'self' to the values in the dictionary."""
|
48
|
+
used_params = {}
|
49
|
+
for key in self.param.objects().keys():
|
50
|
+
if key in kwargs:
|
51
|
+
if key != "name":
|
52
|
+
used_params[key] = kwargs[key]
|
53
|
+
|
54
|
+
self.param.update(**used_params)
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
def get_input_and_results(cls, include_name: bool = False) -> Tuple[dict, dict]:
|
58
|
+
"""Get dictionaries of input parameters and result parameters
|
59
|
+
|
60
|
+
Args:
|
61
|
+
cls: A parametrised class
|
62
|
+
include_name (bool): Include the name parameter that all parametrised classes have. Default False
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
Tuple[dict, dict]: a tuple containing the inputs and result parameters as dictionaries
|
66
|
+
"""
|
67
|
+
inputs = {}
|
68
|
+
results = {}
|
69
|
+
for k, v in cls.param.objects().items():
|
70
|
+
if isinstance(
|
71
|
+
v,
|
72
|
+
ALL_RESULT_TYPES,
|
73
|
+
):
|
74
|
+
results[k] = v
|
75
|
+
else:
|
76
|
+
inputs[k] = v
|
77
|
+
|
78
|
+
if not include_name:
|
79
|
+
inputs.pop("name")
|
80
|
+
return make_namedtuple("inputresult", inputs=inputs, results=results)
|
81
|
+
|
82
|
+
def get_inputs_as_dict(self) -> dict:
|
83
|
+
"""Get the key:value pairs for all the input variables"""
|
84
|
+
inp = self.get_input_and_results().inputs
|
85
|
+
vals = self.param.values()
|
86
|
+
return {i: vals[i] for i, v in inp.items()}
|
87
|
+
|
88
|
+
def get_results_values_as_dict(self, holomap=None) -> dict:
|
89
|
+
"""Get a dictionary of result variables with the name and the current value"""
|
90
|
+
values = self.param.values()
|
91
|
+
output = {key: values[key] for key in self.get_input_and_results().results}
|
92
|
+
if holomap is not None:
|
93
|
+
output |= {"hmap": holomap}
|
94
|
+
return output
|
95
|
+
|
96
|
+
@classmethod
|
97
|
+
def get_inputs_only(cls) -> List[Parameter]:
|
98
|
+
"""Return a list of input parameters
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
List[param.Parameter]: A list of input parameters
|
102
|
+
"""
|
103
|
+
return list(cls.get_input_and_results().inputs.values())
|
104
|
+
|
105
|
+
@staticmethod
|
106
|
+
def filter_fn(item, p_name):
|
107
|
+
return item.name != p_name
|
108
|
+
|
109
|
+
@classmethod
|
110
|
+
def get_input_defaults(
|
111
|
+
cls,
|
112
|
+
override_defaults: List = None,
|
113
|
+
) -> List[Tuple[Parameter, Any]]:
|
114
|
+
inp = cls.get_inputs_only()
|
115
|
+
if override_defaults is None:
|
116
|
+
override_defaults = []
|
117
|
+
assert isinstance(override_defaults, list)
|
118
|
+
|
119
|
+
for p in override_defaults:
|
120
|
+
inp = filter(partial(ParametrizedSweep.filter_fn, p_name=p[0].name), inp)
|
121
|
+
|
122
|
+
return override_defaults + [[i, i.default] for i in inp]
|
123
|
+
|
124
|
+
@classmethod
|
125
|
+
def get_input_defaults_override(cls, **kwargs) -> dict[str, Any]:
|
126
|
+
inp = cls.get_inputs_only()
|
127
|
+
defaults = {}
|
128
|
+
for i in inp:
|
129
|
+
defaults[i.name] = i.default
|
130
|
+
|
131
|
+
for k, v in kwargs.items():
|
132
|
+
defaults[k] = v
|
133
|
+
|
134
|
+
return defaults
|
135
|
+
|
136
|
+
@classmethod
|
137
|
+
def get_results_only(cls) -> List[Parameter]:
|
138
|
+
"""Return a list of result parameters
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
List[param.Parameter]: A list of result parameters
|
142
|
+
"""
|
143
|
+
return list(cls.get_input_and_results().results.values())
|
144
|
+
|
145
|
+
@classmethod
|
146
|
+
def get_inputs_as_dims(
|
147
|
+
self, compute_values=False, remove_dims: str | List[str] = None
|
148
|
+
) -> List[hv.Dimension]:
|
149
|
+
inputs = self.get_inputs_only()
|
150
|
+
|
151
|
+
if remove_dims is not None:
|
152
|
+
if isinstance(remove_dims, str):
|
153
|
+
remove_dims = [remove_dims]
|
154
|
+
filtered_inputs = [i for i in inputs if i.name not in remove_dims]
|
155
|
+
inputs = filtered_inputs
|
156
|
+
|
157
|
+
return [iv.as_dim(compute_values) for iv in inputs]
|
158
|
+
|
159
|
+
def to_dynamic_map(
|
160
|
+
self, callback=None, name=None, remove_dims: str | List[str] = None, result_var: str = None
|
161
|
+
) -> hv.DynamicMap:
|
162
|
+
if callback is None:
|
163
|
+
callback = self.__call__
|
164
|
+
|
165
|
+
if result_var is None:
|
166
|
+
result_vars = self.get_input_and_results().results
|
167
|
+
for k, rv in result_vars.items():
|
168
|
+
if isinstance(rv, ResultHmap):
|
169
|
+
result_var = k
|
170
|
+
|
171
|
+
def callback_wrapper(**kwargs):
|
172
|
+
return callback(**kwargs)[result_var]
|
173
|
+
|
174
|
+
return hv.DynamicMap(
|
175
|
+
callback=callback_wrapper,
|
176
|
+
kdims=self.get_inputs_as_dims(compute_values=False, remove_dims=remove_dims),
|
177
|
+
name=name,
|
178
|
+
).opts(shared_axes=False, framewise=True, width=1000, height=1000)
|
179
|
+
|
180
|
+
def to_gui(self, result_var: str = None, **kwargs): # pragma: no cover
|
181
|
+
main = pn.Row(
|
182
|
+
self.to_dynamic_map(result_var=result_var, **kwargs),
|
183
|
+
)
|
184
|
+
main.show()
|
185
|
+
|
186
|
+
def to_holomap(self, callback, remove_dims: str | List[str] = None) -> hv.DynamicMap:
|
187
|
+
return hv.HoloMap(
|
188
|
+
hv.DynamicMap(
|
189
|
+
callback=callback,
|
190
|
+
kdims=self.get_inputs_as_dims(compute_values=True, remove_dims=remove_dims),
|
191
|
+
)
|
192
|
+
)
|
193
|
+
|
194
|
+
def __call__(self, **kwargs):
|
195
|
+
return self.get_results_values_as_dict()
|
196
|
+
|
197
|
+
def plot_hmap(self, **kwargs):
|
198
|
+
return self.__call__(**kwargs)["hmap"]
|
199
|
+
|
200
|
+
def to_bench(self, run_cfg=None, report=None, name: str = None):
|
201
|
+
from bencher import Bench
|
202
|
+
|
203
|
+
assert isinstance(self, ParametrizedSweep)
|
204
|
+
|
205
|
+
if name is None:
|
206
|
+
name = self.name[:-5] # param adds 5 digit number to the end, so remove it
|
207
|
+
|
208
|
+
return Bench(name, self, run_cfg=run_cfg, report=report)
|