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
bencher/utils.py CHANGED
@@ -41,16 +41,22 @@ def make_namedtuple(class_name: str, **fields) -> namedtuple:
41
41
  return namedtuple(class_name, fields)(*fields.values())
42
42
 
43
43
 
44
- def get_nearest_coords(dataset: xr.Dataset, collapse_list=False, **kwargs) -> dict:
45
- """Given an xarray dataset and kwargs of key value pairs of coordinate values, return a dictionary of the nearest coordinate name value pair that was found in the dataset
44
+ def get_nearest_coords(dataset: xr.Dataset, collapse_list: bool = False, **kwargs) -> dict:
45
+ """Find the nearest coordinates in an xarray dataset based on provided coordinate values.
46
+
47
+ Given an xarray dataset and kwargs of key-value pairs of coordinate values, return a dictionary
48
+ of the nearest coordinate name-value pair that was found in the dataset.
46
49
 
47
50
  Args:
48
- ds (xr.Dataset): dataset
51
+ dataset (xr.Dataset): The xarray dataset to search in
52
+ collapse_list (bool, optional): If True, when a coordinate value is a list, only the first
53
+ item is returned. Defaults to False.
54
+ **kwargs: Key-value pairs where keys are coordinate names and values are points to find
55
+ the nearest match for
49
56
 
50
57
  Returns:
51
- dict: nearest coordinate name value pair that matches the input coordinate name value pairs.
58
+ dict: Dictionary of coordinate name-value pairs with the nearest values found in the dataset
52
59
  """
53
-
54
60
  selection = dataset.sel(method="nearest", **kwargs)
55
61
  cd = selection.coords.to_dataset().to_dict()["coords"]
56
62
  cd2 = {}
@@ -61,7 +67,19 @@ def get_nearest_coords(dataset: xr.Dataset, collapse_list=False, **kwargs) -> di
61
67
  return cd2
62
68
 
63
69
 
64
- def get_nearest_coords1D(val: Any, coords) -> Any:
70
+ def get_nearest_coords1D(val: Any, coords: List[Any]) -> Any:
71
+ """Find the closest coordinate to a given value in a list of coordinates.
72
+
73
+ For numeric values, finds the value in coords that is closest to val.
74
+ For non-numeric values, returns the exact match if found, otherwise returns val.
75
+
76
+ Args:
77
+ val (Any): The value to find the closest coordinate for
78
+ coords (List[Any]): The list of coordinates to search in
79
+
80
+ Returns:
81
+ Any: The closest coordinate value from the list
82
+ """
65
83
  if isinstance(val, (int, float)):
66
84
  return min(coords, key=lambda x_: abs(x_ - val))
67
85
  for i in coords:
@@ -70,19 +88,28 @@ def get_nearest_coords1D(val: Any, coords) -> Any:
70
88
  return val
71
89
 
72
90
 
73
- def hash_sha1(var: any) -> str:
74
- """A hash function that avoids the PYTHONHASHSEED 'feature' which returns a different hash value each time the program is run"""
91
+ def hash_sha1(var: Any) -> str:
92
+ """A hash function that avoids the PYTHONHASHSEED 'feature' which returns a different hash value each time the program is run.
93
+
94
+ Converts input to a consistent SHA1 hash string.
95
+
96
+ Args:
97
+ var (Any): The variable to hash
98
+
99
+ Returns:
100
+ str: A hexadecimal SHA1 hash of the string representation of the variable
101
+ """
75
102
  return hashlib.sha1(str(var).encode("ASCII")).hexdigest()
76
103
 
77
104
 
78
- def capitalise_words(message: str):
79
- """Given a string of lowercase words, capitalise them
105
+ def capitalise_words(message: str) -> str:
106
+ """Given a string of lowercase words, capitalise them.
80
107
 
81
108
  Args:
82
109
  message (str): lower case string
83
110
 
84
111
  Returns:
85
- _type_: capitalised string
112
+ str: capitalised string where each word starts with an uppercase letter
86
113
  """
87
114
  capitalized_message = " ".join([word.capitalize() for word in message.split(" ")])
88
115
  return capitalized_message
@@ -101,7 +128,16 @@ def un_camel(camel: str) -> str:
101
128
  return capitalise_words(re.sub("([a-z])([A-Z])", r"\g<1> \g<2>", camel.replace("_", " ")))
102
129
 
103
130
 
104
- def mult_tuple(inp: Tuple[float], val: float) -> Tuple[float]:
131
+ def mult_tuple(inp: Tuple[float, ...], val: float) -> Tuple[float, ...]:
132
+ """Multiply each element in a tuple by a scalar value.
133
+
134
+ Args:
135
+ inp (Tuple[float, ...]): The input tuple of floats to multiply
136
+ val (float): The scalar value to multiply each element by
137
+
138
+ Returns:
139
+ Tuple[float, ...]: A new tuple with each element multiplied by val
140
+ """
105
141
  return tuple(np.array(inp) * val)
106
142
 
107
143
 
@@ -118,15 +154,17 @@ def tabs_in_markdown(regular_str: str, spaces: int = 2) -> str:
118
154
  return regular_str.replace("\t", "".join(["&nbsp;"] * spaces))
119
155
 
120
156
 
121
- def int_to_col(int_val, sat=0.5, val=0.95, alpha=-1) -> tuple[float, float, float]:
157
+ def int_to_col(
158
+ int_val: int, sat: float = 0.5, val: float = 0.95, alpha: float = -1
159
+ ) -> tuple[float, float, float] | tuple[float, float, float, float]:
122
160
  """Uses the golden angle to generate colors programmatically with minimum overlap between colors.
123
161
  https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
124
162
 
125
163
  Args:
126
- int_val (_type_): index of an object you want to color, this is mapped to hue in HSV
164
+ int_val (int): index of an object you want to color, this is mapped to hue in HSV
127
165
  sat (float, optional): saturation in HSV. Defaults to 0.5.
128
166
  val (float, optional): value in HSV. Defaults to 0.95.
129
- alpha (int, optional): transparency. If -1 then only RGB is returned, if 0 or greater, RGBA is returned. Defaults to -1.
167
+ alpha (float, optional): transparency. If -1 then only RGB is returned, if 0 or greater, RGBA is returned. Defaults to -1.
130
168
 
131
169
  Returns:
132
170
  tuple[float, float, float] | tuple[float, float, float, float]: either RGB or RGBA vector
@@ -138,7 +176,23 @@ def int_to_col(int_val, sat=0.5, val=0.95, alpha=-1) -> tuple[float, float, floa
138
176
  return rgb
139
177
 
140
178
 
141
- def lerp(value, input_low: float, input_high: float, output_low: float, output_high: float):
179
+ def lerp(
180
+ value: float, input_low: float, input_high: float, output_low: float, output_high: float
181
+ ) -> float:
182
+ """Linear interpolation between two ranges.
183
+
184
+ Maps a value from one range [input_low, input_high] to another range [output_low, output_high].
185
+
186
+ Args:
187
+ value (float): The input value to interpolate
188
+ input_low (float): The lower bound of the input range
189
+ input_high (float): The upper bound of the input range
190
+ output_low (float): The lower bound of the output range
191
+ output_high (float): The upper bound of the output range
192
+
193
+ Returns:
194
+ float: The interpolated value in the output range
195
+ """
142
196
  input_low = float(input_low)
143
197
  return output_low + ((float(value) - input_low) / (float(input_high) - input_low)) * (
144
198
  float(output_high) - output_low
@@ -146,10 +200,26 @@ def lerp(value, input_low: float, input_high: float, output_low: float, output_h
146
200
 
147
201
 
148
202
  def color_tuple_to_css(color: tuple[float, float, float]) -> str:
203
+ """Convert a RGB color tuple to CSS rgb format string.
204
+
205
+ Args:
206
+ color (tuple[float, float, float]): RGB color tuple with values in range [0.0, 1.0]
207
+
208
+ Returns:
209
+ str: CSS color string in format 'rgb(r, g, b)' with values in range [0, 255]
210
+ """
149
211
  return f"rgb{(color[0] * 255, color[1] * 255, color[2] * 255)}"
150
212
 
151
213
 
152
- def color_tuple_to_255(color: tuple[float, float, float]) -> tuple[float, float, float]:
214
+ def color_tuple_to_255(color: tuple[float, float, float]) -> tuple[int, int, int]:
215
+ """Convert a RGB color tuple with values in range [0.0, 1.0] to values in range [0, 255].
216
+
217
+ Args:
218
+ color (tuple[float, float, float]): RGB color tuple with values in range [0.0, 1.0]
219
+
220
+ Returns:
221
+ tuple[int, int, int]: RGB color tuple with values clamped to range [0, 255]
222
+ """
153
223
  return (
154
224
  min(int(color[0] * 255), 255),
155
225
  min(int(color[1] * 255), 255),
@@ -157,25 +227,76 @@ def color_tuple_to_255(color: tuple[float, float, float]) -> tuple[float, float,
157
227
  )
158
228
 
159
229
 
160
- def gen_path(filename, folder="generic", suffix=".dat"):
230
+ def gen_path(filename: str, folder: str = "generic", suffix: str = ".dat") -> str:
231
+ """Generate a unique path for a file in the cache directory.
232
+
233
+ Creates a directory structure in the 'cachedir' folder and returns a path
234
+ with a UUID to ensure uniqueness.
235
+
236
+ Args:
237
+ filename (str): Base name for the file
238
+ folder (str, optional): Subfolder within cachedir. Defaults to "generic".
239
+ suffix (str, optional): File extension. Defaults to ".dat".
240
+
241
+ Returns:
242
+ str: Absolute path to a unique file location
243
+ """
161
244
  path = Path(f"cachedir/{folder}/{filename}/")
162
245
  path.mkdir(parents=True, exist_ok=True)
163
246
  return f"{path.absolute().as_posix()}/{filename}_{uuid4()}{suffix}"
164
247
 
165
248
 
166
249
  def gen_video_path(video_name: str = "vid", extension: str = ".mp4") -> str:
250
+ """Generate a unique path for a video file in the cache directory.
251
+
252
+ Args:
253
+ video_name (str, optional): Base name for the video file. Defaults to "vid".
254
+ extension (str, optional): Video file extension. Defaults to ".mp4".
255
+
256
+ Returns:
257
+ str: Absolute path to a unique video file location
258
+ """
167
259
  return gen_path(video_name, "vid", extension)
168
260
 
169
261
 
170
- def gen_image_path(image_name: str = "img", filetype=".png") -> str:
262
+ def gen_image_path(image_name: str = "img", filetype: str = ".png") -> str:
263
+ """Generate a unique path for an image file in the cache directory.
264
+
265
+ Args:
266
+ image_name (str, optional): Base name for the image file. Defaults to "img".
267
+ filetype (str, optional): Image file extension. Defaults to ".png".
268
+
269
+ Returns:
270
+ str: Absolute path to a unique image file location
271
+ """
171
272
  return gen_path(image_name, "img", filetype)
172
273
 
173
274
 
174
- def gen_rerun_data_path(rrd_name: str = "rrd", filetype=".rrd") -> str:
275
+ def gen_rerun_data_path(rrd_name: str = "rrd", filetype: str = ".rrd") -> str:
276
+ """Generate a unique path for a rerun data file in the cache directory.
277
+
278
+ Args:
279
+ rrd_name (str, optional): Base name for the rerun data file. Defaults to "rrd".
280
+ filetype (str, optional): File extension. Defaults to ".rrd".
281
+
282
+ Returns:
283
+ str: Absolute path to a unique rerun data file location
284
+ """
175
285
  return gen_path(rrd_name, "rrd", filetype)
176
286
 
177
287
 
178
288
  def callable_name(any_callable: Callable[..., Any]) -> str:
289
+ """Extract the name of a callable object, handling various callable types.
290
+
291
+ This function attempts to extract the name of a callable object, including
292
+ regular functions, partial functions, and other callables.
293
+
294
+ Args:
295
+ any_callable (Callable[..., Any]): The callable object to get the name from
296
+
297
+ Returns:
298
+ str: The name of the callable
299
+ """
179
300
  if isinstance(any_callable, partial):
180
301
  return any_callable.func.__name__
181
302
  try:
@@ -184,8 +305,20 @@ def callable_name(any_callable: Callable[..., Any]) -> str:
184
305
  return str(any_callable)
185
306
 
186
307
 
187
- def listify(obj) -> list:
188
- """Take an object and turn it into a list if its not already a list. However if the object is none, don't turn it into a list"""
308
+ def listify(obj: Any) -> List[Any] | None:
309
+ """Convert an object to a list if it's not already a list.
310
+
311
+ This function handles conversion of various object types to lists, with special
312
+ handling for None values and existing list/tuple types.
313
+
314
+ Args:
315
+ obj (Any): The object to convert to a list
316
+
317
+ Returns:
318
+ List[Any] | None: A list containing the object, the object itself if it was
319
+ already a list, a list from the tuple if it was a tuple, or None if the
320
+ input was None
321
+ """
189
322
  if obj is None:
190
323
  return None
191
324
  if isinstance(obj, list):
@@ -195,13 +328,29 @@ def listify(obj) -> list:
195
328
  return [obj]
196
329
 
197
330
 
198
- def get_name(var):
331
+ def get_name(var: Any) -> str:
332
+ """Extract the name from a variable, handling param.Parameter objects.
333
+
334
+ Args:
335
+ var (Any): The variable to extract the name from
336
+
337
+ Returns:
338
+ str: The name of the variable
339
+ """
199
340
  if isinstance(var, param.Parameter):
200
341
  return var.name
201
342
  return var
202
343
 
203
344
 
204
- def params_to_str(param_list: List[param.Parameter]):
345
+ def params_to_str(param_list: List[param.Parameter]) -> List[str]:
346
+ """Convert a list of param.Parameter objects to a list of their names.
347
+
348
+ Args:
349
+ param_list (List[param.Parameter]): List of parameter objects
350
+
351
+ Returns:
352
+ List[str]: List of parameter names
353
+ """
205
354
  return [get_name(i) for i in param_list]
206
355
 
207
356
 
@@ -212,8 +361,8 @@ def publish_file(filepath: str, remote: str, branch_name: str) -> str: # pragma
212
361
 
213
362
  def publish_args(branch_name) -> Tuple[str, str]:
214
363
  return (
215
- "https://github.com/dyson-ai/bencher.git",
216
- f"https://github.com/dyson-ai/bencher/blob/{branch_name}")
364
+ "https://github.com/blooop/bencher.git",
365
+ f"https://github.com/blooop/bencher/blob/{branch_name}")
217
366
 
218
367
 
219
368
  Args:
@@ -7,7 +7,15 @@ from bencher.variables.sweep_base import SweepBase, shared_slots
7
7
 
8
8
 
9
9
  class SweepSelector(Selector, SweepBase):
10
- """A class to represent a parameter sweep of bools"""
10
+ """A class representing a parameter sweep for selectable options.
11
+
12
+ This class extends both Selector and SweepBase to provide parameter sweeping
13
+ capabilities for categorical variables that have a predefined set of options.
14
+
15
+ Attributes:
16
+ units (str): The units of measurement for the parameter
17
+ samples (int): The number of samples to take from the available options
18
+ """
11
19
 
12
20
  __slots__ = shared_slots
13
21
 
@@ -22,14 +30,26 @@ class SweepSelector(Selector, SweepBase):
22
30
  self.samples = samples
23
31
 
24
32
  def values(self) -> List[Any]:
25
- """return all the values for a parameter sweep. If debug is true return a reduced list"""
33
+ """Return all the values for the parameter sweep.
34
+
35
+ Returns:
36
+ List[Any]: A list of parameter values to sweep through
37
+ """
26
38
  return self.indices_to_samples(self.samples, self.objects)
27
39
 
28
40
 
29
41
  class BoolSweep(SweepSelector):
30
- """A class to represent a parameter sweep of bools"""
42
+ """A class representing a parameter sweep for boolean values.
43
+
44
+ This class extends SweepSelector to provide parameter sweeping capabilities
45
+ specifically for boolean values (True and False).
31
46
 
32
- def __init__(self, units: str = "ul", samples: int = None, default=True, **params):
47
+ Attributes:
48
+ units (str): The units of measurement for the parameter
49
+ samples (int): The number of samples to take (typically 2 for booleans)
50
+ """
51
+
52
+ def __init__(self, units: str = "ul", samples: int = None, default: bool = True, **params):
33
53
  SweepSelector.__init__(
34
54
  self,
35
55
  units=units,
@@ -41,7 +61,15 @@ class BoolSweep(SweepSelector):
41
61
 
42
62
 
43
63
  class StringSweep(SweepSelector):
44
- """A class to represent a parameter sweep of strings"""
64
+ """A class representing a parameter sweep for string values.
65
+
66
+ This class extends SweepSelector to provide parameter sweeping capabilities
67
+ specifically for a list of string values.
68
+
69
+ Attributes:
70
+ units (str): The units of measurement for the parameter
71
+ samples (int): The number of samples to take from the available strings
72
+ """
45
73
 
46
74
  def __init__(
47
75
  self,
@@ -61,11 +89,21 @@ class StringSweep(SweepSelector):
61
89
 
62
90
 
63
91
  class EnumSweep(SweepSelector):
64
- """A class to represent a parameter sweep of enums"""
92
+ """A class representing a parameter sweep for enum values.
93
+
94
+ This class extends SweepSelector to provide parameter sweeping capabilities
95
+ specifically for enumeration types, supporting both enum types and lists of enum values.
96
+
97
+ Attributes:
98
+ units (str): The units of measurement for the parameter
99
+ samples (int): The number of samples to take from the available enum values
100
+ """
65
101
 
66
102
  __slots__ = shared_slots
67
103
 
68
- def __init__(self, enum_type: Enum | List[Enum], units="ul", samples=None, **params):
104
+ def __init__(
105
+ self, enum_type: Enum | List[Enum], units: str = "ul", samples: int = None, **params
106
+ ):
69
107
  # The enum can either be an Enum type or a list of enums
70
108
  list_of_enums = isinstance(enum_type, list)
71
109
  selector_list = enum_type if list_of_enums else list(enum_type)
@@ -82,11 +120,23 @@ class EnumSweep(SweepSelector):
82
120
 
83
121
 
84
122
  class IntSweep(Integer, SweepBase):
85
- """A class to represent a parameter sweep of ints"""
123
+ """A class representing a parameter sweep for integer values.
124
+
125
+ This class extends both Integer and SweepBase to provide parameter sweeping capabilities
126
+ specifically for integer values within specified bounds or with custom sample values.
127
+
128
+ Attributes:
129
+ units (str): The units of measurement for the parameter
130
+ samples (int): The number of samples to take from the range
131
+ sample_values (List[int], optional): Specific integer values to use as samples instead of
132
+ generating them from bounds. If provided, overrides the samples parameter.
133
+ """
86
134
 
87
135
  __slots__ = shared_slots + ["sample_values"]
88
136
 
89
- def __init__(self, units="ul", samples=None, sample_values=None, **params):
137
+ def __init__(
138
+ self, units: str = "ul", samples: int = None, sample_values: List[int] = None, **params
139
+ ):
90
140
  SweepBase.__init__(self)
91
141
  Integer.__init__(self, **params)
92
142
 
@@ -107,7 +157,14 @@ class IntSweep(Integer, SweepBase):
107
157
  self.default = sample_values[0]
108
158
 
109
159
  def values(self) -> List[int]:
110
- """return all the values for a parameter sweep. If debug is true return the list"""
160
+ """Return all the values for the parameter sweep.
161
+
162
+ If sample_values is provided, returns those values. Otherwise generates values
163
+ within the specified bounds.
164
+
165
+ Returns:
166
+ List[int]: A list of integer values to sweep through
167
+ """
111
168
  sample_values = (
112
169
  self.sample_values
113
170
  if self.sample_values is not None
@@ -136,11 +193,29 @@ class IntSweep(Integer, SweepBase):
136
193
 
137
194
 
138
195
  class FloatSweep(Number, SweepBase):
139
- """A class to represent a parameter sweep of floats"""
196
+ """A class representing a parameter sweep for floating point values.
197
+
198
+ This class extends both Number and SweepBase to provide parameter sweeping capabilities
199
+ specifically for floating point values within specified bounds or with custom sample values.
200
+
201
+ Attributes:
202
+ units (str): The units of measurement for the parameter
203
+ samples (int): The number of samples to take from the range
204
+ sample_values (List[float], optional): Specific float values to use as samples instead of
205
+ generating them from bounds. If provided, overrides the samples parameter.
206
+ step (float, optional): Step size between samples when generating values from bounds
207
+ """
140
208
 
141
209
  __slots__ = shared_slots + ["sample_values"]
142
210
 
143
- def __init__(self, units="ul", samples=10, sample_values=None, step=None, **params):
211
+ def __init__(
212
+ self,
213
+ units: str = "ul",
214
+ samples: int = 10,
215
+ sample_values: List[float] = None,
216
+ step: float = None,
217
+ **params,
218
+ ):
144
219
  SweepBase.__init__(self)
145
220
  Number.__init__(self, step=step, **params)
146
221
 
@@ -156,7 +231,14 @@ class FloatSweep(Number, SweepBase):
156
231
  self.default = sample_values[0]
157
232
 
158
233
  def values(self) -> List[float]:
159
- """return all the values for a parameter sweep. If debug is true return a reduced list"""
234
+ """Return all the values for the parameter sweep.
235
+
236
+ If sample_values is provided, returns those values. Otherwise generates values
237
+ within the specified bounds, either using linspace (when step is None) or arange.
238
+
239
+ Returns:
240
+ List[float]: A list of float values to sweep through
241
+ """
160
242
  samps = self.samples
161
243
  if self.sample_values is None:
162
244
  if self.step is None:
@@ -166,7 +248,20 @@ class FloatSweep(Number, SweepBase):
166
248
  return self.sample_values
167
249
 
168
250
 
169
- def box(name, center, width):
251
+ def box(name: str, center: float, width: float) -> FloatSweep:
252
+ """Create a FloatSweep parameter centered around a value with a given width.
253
+
254
+ This is a convenience function to create a bounded FloatSweep parameter with
255
+ bounds centered on a specific value, extending by the width in both directions.
256
+
257
+ Args:
258
+ name (str): The name of the parameter
259
+ center (float): The center value of the parameter
260
+ width (float): The distance from the center to the bounds in both directions
261
+
262
+ Returns:
263
+ FloatSweep: A FloatSweep parameter with the specified name, default, and bounds
264
+ """
170
265
  var = FloatSweep(default=center, bounds=(center - width, center + width))
171
266
  var.name = name
172
267
  return var
@@ -195,6 +290,18 @@ def p(
195
290
  return {"name": name, "values": values, "max_level": max_level, "samples": samples}
196
291
 
197
292
 
198
- def with_level(arr: list, level) -> list:
293
+ def with_level(arr: list, level: int) -> list:
294
+ """Apply level-based sampling to a list of values.
295
+
296
+ This function uses an IntSweep with the provided values and applies level-based
297
+ sampling to it, returning the resulting values.
298
+
299
+ Args:
300
+ arr (list): List of values to sample from
301
+ level (int): The sampling level to apply (higher levels provide more samples)
302
+
303
+ Returns:
304
+ list: The level-sampled values
305
+ """
199
306
  return IntSweep(sample_values=arr).with_level(level).values()
200
307
  # return tmp.with_sample_values(arr).with_level(level).values()
bencher/video_writer.py CHANGED
@@ -88,7 +88,8 @@ class VideoWriter:
88
88
  output_path = Path(output_path)
89
89
 
90
90
  with moviepy.video.io.VideoFileClip.VideoFileClip(video_path) as video:
91
- frame_time = time if time is not None else video.duration
91
+ frame_time = time if time is not None else video.duration - 2.0 / video.fps
92
+ frame_time = max(frame_time, 0)
92
93
  frame = video.get_frame(frame_time)
93
94
  Image.fromarray(frame).save(output_path)
94
95
 
bencher/worker_job.py CHANGED
@@ -6,8 +6,30 @@ from bencher.utils import hmap_canonical_input
6
6
 
7
7
  @dataclass
8
8
  class WorkerJob:
9
- function_input_vars: List
10
- index_tuple: Tuple[int]
9
+ """Represents a benchmark worker job with input variables and caching information.
10
+
11
+ This class encapsulates the information needed to execute a benchmark function,
12
+ including input variables, dimension information, and caching metadata. It handles
13
+ the preparation of function inputs and calculation of hash signatures for caching.
14
+
15
+ Attributes:
16
+ function_input_vars (List): The values of the input variables to pass to the function
17
+ index_tuple (Tuple[int]): The indices of these values in the N-dimensional result array
18
+ dims_name (List[str]): The names of the input dimensions
19
+ constant_inputs (dict): Dictionary of any constant input values
20
+ bench_cfg_sample_hash (str): Hash of the benchmark configuration without repeats
21
+ tag (str): Tag for grouping related jobs
22
+ function_input (dict): Complete input as a dictionary with dimension names as keys
23
+ canonical_input (Tuple[Any]): Canonical representation of inputs for caching
24
+ fn_inputs_sorted (List[Tuple[str, Any]]): Sorted representation of function inputs
25
+ function_input_signature_pure (str): Hash of the function inputs and tag
26
+ function_input_signature_benchmark_context (str): Comprehensive hash including benchmark context
27
+ found_in_cache (bool): Whether this job result was found in cache
28
+ msgs (List[str]): Messages related to this job's execution
29
+ """
30
+
31
+ function_input_vars: List[Any]
32
+ index_tuple: Tuple[int, ...]
11
33
  dims_name: List[str]
12
34
  constant_inputs: dict
13
35
  bench_cfg_sample_hash: str
@@ -15,13 +37,19 @@ class WorkerJob:
15
37
 
16
38
  function_input: dict = None
17
39
  canonical_input: Tuple[Any] = None
18
- fn_inputs_sorted: List[str] = None
40
+ fn_inputs_sorted: List[Tuple[str, Any]] = None
19
41
  function_input_signature_pure: str = None
20
42
  function_input_signature_benchmark_context: str = None
21
43
  found_in_cache: bool = False
22
44
  msgs: List[str] = field(default_factory=list)
23
45
 
24
46
  def setup_hashes(self) -> None:
47
+ """Set up the function inputs and calculate hash signatures for caching.
48
+
49
+ This method prepares the function inputs by combining function input variables
50
+ with dimensions and constant inputs. It also calculates hash signatures used
51
+ for caching results and tracking job execution.
52
+ """
25
53
  self.function_input = dict(zip(self.dims_name, self.function_input_vars))
26
54
 
27
55
  self.canonical_input = hmap_canonical_input(self.function_input)