holobench 1.18.0__py2.py3-none-any.whl → 1.30.0__py2.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 (48) hide show
  1. bencher/__init__.py +13 -1
  2. bencher/bench_cfg.py +1 -1
  3. bencher/bench_report.py +6 -109
  4. bencher/bench_runner.py +1 -1
  5. bencher/bencher.py +117 -62
  6. bencher/example/benchmark_data.py +0 -4
  7. bencher/example/example_composable_container.py +106 -0
  8. bencher/example/example_composable_container2.py +160 -0
  9. bencher/example/example_consts.py +39 -0
  10. bencher/example/example_custom_sweep2.py +42 -0
  11. bencher/example/example_dataframe.py +48 -0
  12. bencher/example/example_filepath.py +27 -0
  13. bencher/example/example_image.py +31 -16
  14. bencher/example/example_image1.py +81 -0
  15. bencher/example/example_levels2.py +37 -0
  16. bencher/example/example_simple_float.py +15 -25
  17. bencher/example/example_simple_float2d.py +29 -0
  18. bencher/example/example_strings.py +3 -2
  19. bencher/example/example_video.py +2 -11
  20. bencher/example/meta/example_meta.py +2 -2
  21. bencher/example/meta/example_meta_cat.py +2 -2
  22. bencher/example/meta/example_meta_float.py +1 -1
  23. bencher/example/meta/example_meta_levels.py +2 -2
  24. bencher/optuna_conversions.py +3 -2
  25. bencher/plotting/plt_cnt_cfg.py +1 -0
  26. bencher/results/bench_result.py +3 -1
  27. bencher/results/bench_result_base.py +65 -8
  28. bencher/results/composable_container/composable_container_base.py +25 -12
  29. bencher/results/composable_container/composable_container_dataframe.py +52 -0
  30. bencher/results/composable_container/composable_container_panel.py +17 -18
  31. bencher/results/composable_container/composable_container_video.py +163 -55
  32. bencher/results/dataset_result.py +227 -0
  33. bencher/results/holoview_result.py +15 -7
  34. bencher/results/optuna_result.py +4 -3
  35. bencher/results/panel_result.py +1 -3
  36. bencher/results/video_summary.py +104 -99
  37. bencher/utils.py +29 -3
  38. bencher/variables/__init__.py +0 -0
  39. bencher/variables/inputs.py +24 -1
  40. bencher/variables/parametrised_sweep.py +8 -24
  41. bencher/variables/results.py +67 -9
  42. bencher/variables/time.py +22 -0
  43. bencher/video_writer.py +20 -74
  44. {holobench-1.18.0.dist-info → holobench-1.30.0.dist-info}/METADATA +77 -35
  45. {holobench-1.18.0.dist-info → holobench-1.30.0.dist-info}/RECORD +48 -34
  46. {holobench-1.18.0.dist-info → holobench-1.30.0.dist-info}/WHEEL +1 -1
  47. holobench-1.30.0.dist-info/licenses/LICENSE +21 -0
  48. resource/bencher +0 -0
bencher/utils.py CHANGED
@@ -8,8 +8,9 @@ from colorsys import hsv_to_rgb
8
8
  from pathlib import Path
9
9
  from uuid import uuid4
10
10
  from functools import partial
11
- from typing import Callable, Any, List
11
+ from typing import Callable, Any, List, Tuple
12
12
  import param
13
+ import numpy as np
13
14
 
14
15
 
15
16
  def hmap_canonical_input(dic: dict) -> tuple:
@@ -98,6 +99,23 @@ def un_camel(camel: str) -> str:
98
99
  return capitalise_words(re.sub("([a-z])([A-Z])", r"\g<1> \g<2>", camel.replace("_", " ")))
99
100
 
100
101
 
102
+ def mult_tuple(inp: Tuple[float], val: float) -> Tuple[float]:
103
+ return tuple(np.array(inp) * val)
104
+
105
+
106
+ def tabs_in_markdown(regular_str: str, spaces: int = 2) -> str:
107
+ """Given a string with tabs in the form \t convert the to &ensp; which is a double space in markdown
108
+
109
+ Args:
110
+ regular_str (str): A string with tabs in it
111
+ spaces (int): the number of spaces per tab
112
+
113
+ Returns:
114
+ str: A string with sets of &nbsp; to represent the tabs in markdown
115
+ """
116
+ return regular_str.replace("\t", "".join(["&nbsp;"] * spaces))
117
+
118
+
101
119
  def int_to_col(int_val, sat=0.5, val=0.95, alpha=-1) -> tuple[float, float, float]:
102
120
  """Uses the golden angle to generate colors programmatically with minimum overlap between colors.
103
121
  https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
@@ -129,13 +147,21 @@ def color_tuple_to_css(color: tuple[float, float, float]) -> str:
129
147
  return f"rgb{(color[0] * 255, color[1] * 255, color[2] * 255)}"
130
148
 
131
149
 
132
- def gen_path(filename, folder, suffix):
150
+ def color_tuple_to_255(color: tuple[float, float, float]) -> tuple[float, float, float]:
151
+ return (
152
+ min(int(color[0] * 255), 255),
153
+ min(int(color[1] * 255), 255),
154
+ min(int(color[2] * 255), 255),
155
+ )
156
+
157
+
158
+ def gen_path(filename, folder="generic", suffix=".dat"):
133
159
  path = Path(f"cachedir/{folder}/{filename}/")
134
160
  path.mkdir(parents=True, exist_ok=True)
135
161
  return f"{path.absolute().as_posix()}/{filename}_{uuid4()}{suffix}"
136
162
 
137
163
 
138
- def gen_video_path(video_name: str = "vid", extension: str = ".webm") -> str:
164
+ def gen_video_path(video_name: str = "vid", extension: str = ".mp4") -> str:
139
165
  return gen_path(video_name, "vid", extension)
140
166
 
141
167
 
File without changes
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import List, Any
2
+ from typing import List, Any, Dict
3
3
 
4
4
  import numpy as np
5
5
  from param import Integer, Number, Selector
@@ -174,6 +174,29 @@ def box(name, center, width):
174
174
  return var
175
175
 
176
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
+
177
200
  def with_level(arr: list, level) -> list:
178
201
  return IntSweep(sample_values=arr).with_level(level).values()
179
202
  # return tmp.with_sample_values(arr).with_level(level).values()
@@ -3,19 +3,11 @@ from typing import List, Tuple, Any
3
3
  from param import Parameter, Parameterized
4
4
  import holoviews as hv
5
5
  import panel as pn
6
-
6
+ from copy import deepcopy
7
7
 
8
8
  from bencher.utils import make_namedtuple, hash_sha1
9
- from bencher.variables.results import (
10
- ResultVar,
11
- ResultVec,
12
- ResultHmap,
13
- ResultVideo,
14
- ResultImage,
15
- ResultString,
16
- ResultContainer,
17
- ResultReference,
18
- )
9
+ from bencher.variables.results import ALL_RESULT_TYPES, ResultHmap
10
+ from bencher.bench_cfg import BenchRunCfg
19
11
 
20
12
 
21
13
  class ParametrizedSweep(Parameterized):
@@ -78,16 +70,7 @@ class ParametrizedSweep(Parameterized):
78
70
  for k, v in cls.param.objects().items():
79
71
  if isinstance(
80
72
  v,
81
- (
82
- ResultVar,
83
- ResultVec,
84
- ResultHmap,
85
- ResultVideo,
86
- ResultImage,
87
- ResultString,
88
- ResultContainer,
89
- ResultReference,
90
- ),
73
+ ALL_RESULT_TYPES,
91
74
  ):
92
75
  results[k] = v
93
76
  else:
@@ -144,7 +127,7 @@ class ParametrizedSweep(Parameterized):
144
127
  inp = cls.get_inputs_only()
145
128
  defaults = {}
146
129
  for i in inp:
147
- defaults[i.name] = i.default
130
+ defaults[i.name] = deepcopy(i.default)
148
131
 
149
132
  for k, v in kwargs.items():
150
133
  defaults[k] = v
@@ -209,13 +192,14 @@ class ParametrizedSweep(Parameterized):
209
192
  )
210
193
  )
211
194
 
212
- def __call__(self):
195
+ def __call__(self, **kwargs):
213
196
  return self.get_results_values_as_dict()
214
197
 
215
198
  def plot_hmap(self, **kwargs):
216
199
  return self.__call__(**kwargs)["hmap"]
217
200
 
218
- def to_bench(self, run_cfg=None, report=None, name: str = None):
201
+ # TODO Add type hints here and fix the circular imports
202
+ def to_bench(self, run_cfg: BenchRunCfg = None, report=None, name: str = None):
219
203
  from bencher import Bench
220
204
 
221
205
  assert isinstance(self, ParametrizedSweep)
@@ -1,6 +1,6 @@
1
1
  from enum import auto
2
2
  from typing import List, Callable, Any, Optional
3
-
3
+ from functools import partial
4
4
  import panel as pn
5
5
  import param
6
6
  from param import Number
@@ -99,7 +99,7 @@ def curve(
99
99
  return hv.Curve(zip(x_vals, y_vals), kdims=[x_name], vdims=[y_name], label=label, **kwargs)
100
100
 
101
101
 
102
- class PathResult(param.Filename):
102
+ class ResultPath(param.Filename):
103
103
  __slots__ = ["units"]
104
104
 
105
105
  def __init__(self, default=None, units="path", **params):
@@ -110,15 +110,33 @@ class PathResult(param.Filename):
110
110
  """A hash function that avoids the PYTHONHASHSEED 'feature' which returns a different hash value each time the program is run"""
111
111
  return hash_sha1(self)
112
112
 
113
+ def to_container(self):
114
+ """Returns a partial function for creating a FileDownload widget with embedding enabled. This function is used to create a panel container to represent the ResultPath object"""
115
+ return partial(pn.widgets.FileDownload, embed=True)
116
+
113
117
 
114
- class ResultVideo(PathResult):
115
- def __init__(self, default=None, units="video", **params):
116
- super().__init__(default=default, units=units, **params)
118
+ class ResultVideo(param.Filename):
119
+ __slots__ = ["units"]
120
+
121
+ def __init__(self, default=None, units="path", **params):
122
+ super().__init__(default=default, check_exists=False, **params)
123
+ self.units = units
117
124
 
125
+ def hash_persistent(self) -> str:
126
+ """A hash function that avoids the PYTHONHASHSEED 'feature' which returns a different hash value each time the program is run"""
127
+ return hash_sha1(self)
118
128
 
119
- class ResultImage(PathResult):
120
- def __init__(self, default=None, units="image", **params):
121
- super().__init__(default=default, units=units, **params)
129
+
130
+ class ResultImage(param.Filename):
131
+ __slots__ = ["units"]
132
+
133
+ def __init__(self, default=None, units="path", **params):
134
+ super().__init__(default=default, check_exists=False, **params)
135
+ self.units = units
136
+
137
+ def hash_persistent(self) -> str:
138
+ """A hash function that avoids the PYTHONHASHSEED 'feature' which returns a different hash value each time the program is run"""
139
+ return hash_sha1(self)
122
140
 
123
141
 
124
142
  class ResultString(param.String):
@@ -168,6 +186,25 @@ class ResultReference(param.Parameter):
168
186
  return hash_sha1(self)
169
187
 
170
188
 
189
+ class ResultDataSet(param.Parameter):
190
+ __slots__ = ["units", "obj"]
191
+
192
+ def __init__(
193
+ self,
194
+ obj: Any = None,
195
+ default: Any = None,
196
+ units: str = "dataset",
197
+ **params,
198
+ ):
199
+ super().__init__(default=default, **params)
200
+ self.units = units
201
+ self.obj = obj
202
+
203
+ def hash_persistent(self) -> str:
204
+ """A hash function that avoids the PYTHONHASHSEED 'feature' which returns a different hash value each time the program is run"""
205
+ return hash_sha1(self)
206
+
207
+
171
208
  class ResultVolume(param.Parameter):
172
209
  __slots__ = ["units", "obj"]
173
210
 
@@ -181,4 +218,25 @@ class ResultVolume(param.Parameter):
181
218
  return hash_sha1(self)
182
219
 
183
220
 
184
- PANEL_TYPES = (ResultImage, ResultVideo, ResultContainer, ResultString, ResultReference)
221
+ PANEL_TYPES = (
222
+ ResultPath,
223
+ ResultImage,
224
+ ResultVideo,
225
+ ResultContainer,
226
+ ResultString,
227
+ ResultReference,
228
+ ResultDataSet,
229
+ )
230
+
231
+ ALL_RESULT_TYPES = (
232
+ ResultVar,
233
+ ResultVec,
234
+ ResultHmap,
235
+ ResultPath,
236
+ ResultVideo,
237
+ ResultImage,
238
+ ResultString,
239
+ ResultContainer,
240
+ ResultDataSet,
241
+ ResultReference,
242
+ )
bencher/variables/time.py CHANGED
@@ -9,6 +9,28 @@ from bencher.variables.sweep_base import SweepBase, shared_slots
9
9
  class TimeBase(SweepBase, Selector):
10
10
  """A class to capture a time snapshot of benchmark values. Time is reprented as a continous value i.e a datetime which is converted into a np.datetime64. To represent time as a discrete value use the TimeEvent class. The distinction is because holoview and plotly code makes different assumptions about discrete vs continous variables"""
11
11
 
12
+ def __init__(
13
+ self,
14
+ objects=None,
15
+ default=None,
16
+ instantiate=False,
17
+ compute_default_fn=None,
18
+ check_on_set=None,
19
+ allow_None=None,
20
+ empty_default=False,
21
+ **params,
22
+ ):
23
+ super().__init__(
24
+ objects,
25
+ default,
26
+ instantiate,
27
+ compute_default_fn,
28
+ check_on_set,
29
+ allow_None,
30
+ empty_default,
31
+ **params,
32
+ )
33
+
12
34
  __slots__ = shared_slots
13
35
 
14
36
  def values(self) -> List[str]:
bencher/video_writer.py CHANGED
@@ -4,13 +4,6 @@ from pathlib import Path
4
4
  from .utils import gen_video_path, gen_image_path
5
5
 
6
6
  import moviepy
7
- from moviepy.editor import (
8
- VideoFileClip,
9
- ImageClip,
10
- ImageSequenceClip,
11
- clips_array,
12
- concatenate_videoclips,
13
- )
14
7
  from PIL import Image, ImageDraw
15
8
 
16
9
 
@@ -25,97 +18,50 @@ class VideoWriter:
25
18
  self.images.append(img)
26
19
 
27
20
  def write(self) -> str:
28
- clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(
29
- self.images, fps=30, with_mask=False, load_images=True
30
- )
31
- self.write_video_raw(clip)
21
+ if len(self.images) > 0:
22
+ clip = moviepy.video.io.ImageSequenceClip.ImageSequenceClip(
23
+ self.images, fps=30, with_mask=False, load_images=True
24
+ )
25
+ self.write_video_raw(clip)
32
26
  return self.filename
33
27
 
34
28
  @staticmethod
35
- def create_label(label, width=None, height=14):
29
+ def create_label(label, width=None, height=16, color=(255, 255, 255)):
36
30
  if width is None:
37
- width = len(label) * 8
38
- new_img = Image.new("RGB", (width, height), (255, 255, 255))
31
+ width = len(label) * 10
32
+ new_img = Image.new("RGB", (width, height), color=color)
39
33
  # ImageDraw.Draw(new_img).text((width/2, 0), label, (0, 0, 0),align="center",achor="ms")
40
- ImageDraw.Draw(new_img).text((width / 2.0, 0), label, (0, 0, 0), anchor="mt", font_size=12)
34
+ ImageDraw.Draw(new_img).text(
35
+ (width / 2.0, 0), label, (0, 0, 0), anchor="mt", font_size=height
36
+ )
41
37
 
42
38
  return new_img
43
39
 
44
40
  @staticmethod
45
- def label_image(path: Path, label, padding=20) -> Path:
41
+ def label_image(path: Path, label, padding=20, color=(255, 255, 255)) -> Path:
46
42
  image = Image.open(path)
47
- new_img = VideoWriter.create_label(label, image.size[0], image.size[1] + padding)
43
+ new_img = VideoWriter.create_label(
44
+ label, image.size[0], image.size[1] + padding, color=color
45
+ )
48
46
  new_img.paste(image, (0, padding))
49
47
  return new_img
50
48
 
51
- def append_file(self, filepath, label=None):
52
- if label is not None:
53
- path = Path(filepath)
54
- new_path = path.with_name(path.stem + "_labelled" + path.suffix).as_posix()
55
- padding = 20
56
- match path.suffix:
57
- case ".png" | ".jpg":
58
- image = Image.open(filepath)
59
- new_img = self.create_label(label, image.size[0], image.size[1] + padding)
60
- new_img.paste(image, (0, padding))
61
- new_img.save(new_path)
62
- self.image_files.append(new_path)
63
- case ".webm":
64
- import warnings
65
-
66
- video_clip = VideoFileClip(filepath)
67
- new_img = self.create_label(label, video_clip.w, padding)
68
-
69
- # Convert PIL image to MoviePy clip
70
- label_clip = ImageClip(np.array(new_img), duration=video_clip.duration)
71
-
72
- labeled_video_clip = clips_array([[label_clip], [video_clip]])
73
-
74
- # otherwise ffmpeg complains that the file is not getting read. We don't need the file just the size
75
- with warnings.catch_warnings():
76
- warnings.simplefilter(action="ignore")
77
- labeled_video_clip.write_videofile(new_path, remove_temp=True, logger=None)
78
- self.video_files.append(new_path)
79
- else:
80
- self.image_files.append(filepath)
81
-
82
- def to_images_sequence(self, images, target_duration: float = 10.0, frame_time=None, **kwargs):
83
- target_duration = kwargs.pop("target_duration", target_duration)
84
- if isinstance(images, list) and len(images) > 0:
85
- if frame_time is None:
86
- fps = len(images) / target_duration
87
- fps = max(fps, 1) # never slower that 1 seconds per frame
88
- fps = min(fps, 30)
89
- else:
90
- fps = 1.0 / frame_time
91
- return ImageSequenceClip(images, fps=fps, with_mask=False)
92
- return None
93
-
94
- def write_png(self, **kwargs):
95
- clip = None
96
- if len(self.image_files) > 0:
97
- clip = self.to_images_sequence(self.image_files, **kwargs)
98
- if len(self.video_files) > 0:
99
- clip = concatenate_videoclips([VideoFileClip(f) for f in self.video_files])
100
- if clip is not None:
101
- clip.write_videofile(self.filename)
102
- return self.filename
103
- return None
104
-
105
49
  def write_video_raw(self, video_clip: moviepy.video.VideoClip, fps: int = 30) -> str:
106
50
  video_clip.write_videofile(
107
51
  self.filename,
108
- codec="libvpx",
52
+ codec="libx264",
109
53
  audio=False,
110
54
  bitrate="0",
111
55
  fps=fps,
112
- ffmpeg_params=["-crf", "34"],
56
+ ffmpeg_params=["-crf", "23"],
57
+ threads=8,
113
58
  )
114
59
  video_clip.close()
115
60
  return self.filename
116
61
 
117
62
 
118
- def add_image(np_array: np.ndarray, name: str = "img"):
63
+ def add_image(np_array: np.ndarray, name: str = "img") -> str:
64
+ """Creates a file on disk from a numpy array and returns the created image path"""
119
65
  filename = gen_image_path(name)
120
66
  Image.fromarray(np_array).save(filename)
121
67
  return filename
@@ -1,37 +1,39 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: holobench
3
- Version: 1.18.0
3
+ Version: 1.30.0
4
4
  Summary: A package for benchmarking the performance of arbitrary functions
5
- Author-email: Austin Gregg-Smith <blooop@gmail.com>
6
- Description-Content-Type: text/markdown
7
- Requires-Dist: holoviews>=1.15,<=1.18.3
8
- Requires-Dist: numpy>=1.0,<=1.26.4
9
- Requires-Dist: param>=1.13.0,<=2.1.0
10
- Requires-Dist: hvplot>=0.8,<=0.10.0
11
- Requires-Dist: matplotlib>=3.6.3,<=3.8.4
12
- Requires-Dist: panel>=1.3.6,<=1.4.2
13
- Requires-Dist: diskcache>=5.6,<=5.6.3
14
- Requires-Dist: optuna>=3.2,<=3.6.1
15
- Requires-Dist: xarray>=2023.7,<=2024.5.0
16
- Requires-Dist: plotly>=5.15,<=5.22.0
17
- Requires-Dist: sortedcontainers>=2.4,<=2.4
18
- Requires-Dist: pandas>=2.0,<=2.2.2
19
- Requires-Dist: strenum>=0.4.0,<=0.4.15
20
- Requires-Dist: scikit-learn>=1.2,<=1.4.2
21
- Requires-Dist: str2bool>=1.1,<=1.1
22
- Requires-Dist: scoop>=0.7.0,<=0.7.2.0
23
- Requires-Dist: moviepy>=1.0.3,<=1.0.3
24
- Requires-Dist: black>=23,<=24.4.2 ; extra == "test"
25
- Requires-Dist: pylint>=2.16,<=3.1.0 ; extra == "test"
26
- Requires-Dist: pytest-cov>=4.1,<=5.0.0 ; extra == "test"
27
- Requires-Dist: pytest>=7.4,<=8.2.0 ; extra == "test"
28
- Requires-Dist: hypothesis>=6.82,<=6.101.0 ; extra == "test"
29
- Requires-Dist: ruff>=0.0.280,<=0.4.4 ; extra == "test"
30
- Requires-Dist: coverage>=7.2.7,<=7.5.1 ; extra == "test"
31
- Project-URL: Documentation, https://bencher.readthedocs.io/en/latest/
32
- Project-URL: Home, https://github.com/dyson-ai/bencher
33
5
  Project-URL: Repository, https://github.com/dyson-ai/bencher
6
+ Project-URL: Home, https://github.com/dyson-ai/bencher
7
+ Project-URL: Documentation, https://bencher.readthedocs.io/en/latest/
8
+ Author-email: Austin Gregg-Smith <blooop@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Requires-Dist: diskcache<=5.6.3,>=5.6
12
+ Requires-Dist: holoviews<=1.19.1,>=1.15
13
+ Requires-Dist: hvplot<=0.10.0,>=0.8
14
+ Requires-Dist: matplotlib<=3.9.2,>=3.6.3
15
+ Requires-Dist: moviepy-fix-codec
16
+ Requires-Dist: numpy<=2.1.0,>=1.0
17
+ Requires-Dist: optuna<=4.0.0,>=3.2
18
+ Requires-Dist: pandas<=2.2.2,>=2.0
19
+ Requires-Dist: panel<=1.4.5,>=1.3.6
20
+ Requires-Dist: param<=2.1.1,>=1.13.0
21
+ Requires-Dist: plotly<=5.24.0,>=5.15
22
+ Requires-Dist: scikit-learn<=1.5.1,>=1.2
23
+ Requires-Dist: scoop<=0.7.2.0,>=0.7.0
24
+ Requires-Dist: sortedcontainers<=2.4,>=2.4
25
+ Requires-Dist: str2bool<=1.1,>=1.1
26
+ Requires-Dist: strenum<=0.4.15,>=0.4.0
27
+ Requires-Dist: xarray<=2024.7.0,>=2023.7
34
28
  Provides-Extra: test
29
+ Requires-Dist: black<=24.8.0,>=23; extra == 'test'
30
+ Requires-Dist: coverage<=7.6.1,>=7.5.4; extra == 'test'
31
+ Requires-Dist: hypothesis<=6.112.2,>=6.104.2; extra == 'test'
32
+ Requires-Dist: pylint<=3.3.1,>=3.2.5; extra == 'test'
33
+ Requires-Dist: pytest-cov<=5.0.0,>=4.1; extra == 'test'
34
+ Requires-Dist: pytest<=8.3.3,>=7.4; extra == 'test'
35
+ Requires-Dist: ruff<=0.6.8,>=0.5.0; extra == 'test'
36
+ Description-Content-Type: text/markdown
35
37
 
36
38
  # Bencher
37
39
 
@@ -43,10 +45,16 @@ Provides-Extra: test
43
45
  [![GitHub issues](https://img.shields.io/github/issues/dyson-ai/bencher.svg)](https://GitHub.com/dyson-ai/bencher/issues/)
44
46
  [![GitHub pull-requests merged](https://badgen.net/github/merged-prs/dyson-ai/bencher)](https://github.com/dyson-ai/bencher/pulls?q=is%3Amerged)
45
47
  [![PyPI](https://img.shields.io/pypi/v/holobench)](https://pypi.org/project/holobench/)
46
- [![GitHub release](https://img.shields.io/github/release/dyson-ai/bencher.svg)](https://GitHub.com/dyson-ai/bencher/releases/)
48
+ [![PyPI - Downloads](https://img.shields.io/pypi/dm/holobench)](https://pypistats.org/packages/holobench)
47
49
  [![License](https://img.shields.io/pypi/l/bencher)](https://opensource.org/license/mit/)
48
- [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11-blue)](https://www.python.org/downloads/release/python-310/)
50
+ [![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-blue)](https://www.python.org/downloads/)
51
+ [![Pixi Badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/prefix-dev/pixi/main/assets/badge/v0.json)](https://pixi.sh)
52
+
53
+ ## Install
49
54
 
55
+ ```bash
56
+ pip install holobench
57
+ ```
50
58
 
51
59
  ## Intro
52
60
 
@@ -77,9 +85,43 @@ Bencher is designed to work with stochastic pure functions with no side effects.
77
85
  combine latest data with historical data
78
86
 
79
87
  store the results using the input hash as a key
80
- deduce the type of plot based on the input types
88
+ deduce the type of plot based on the input and output types
81
89
  return data and plot
82
90
 
83
- ### Example Output
84
91
 
85
- https://dyson-ai.github.io/bencher/
92
+ ## Demo
93
+
94
+ if you have [pixi](https://github.com/prefix-dev/pixi/) installed you can run a demo example with:
95
+
96
+ ```bash
97
+ pixi run demo
98
+ ```
99
+
100
+ An example of the type of output bencher produces can be seen here:
101
+
102
+ https://dyson-ai.github.io/bencher/
103
+
104
+
105
+ ## Examples
106
+
107
+ Most of the features that are supported are demonstrated in the examples folder.
108
+
109
+ Start with example_simple_float.py and explore other examples based on your data types:
110
+ - example_float.py: More complex float operations
111
+ - example_float2D.py: 2D float sweeps
112
+ - example_float3D.py: 3D float sweeps
113
+ - example_categorical.py: Sweeping categorical values (enums)
114
+ - example_strings.py: Sweeping categorical string values
115
+ - example_float_cat.py: Mixing float and categorical values
116
+ - example_image.py: Output images as part of the sweep
117
+ - example_video.py: Output videos as part of the sweep
118
+ - example_filepath.py: Output arbitrary files as part of the sweep
119
+ - and many others
120
+
121
+
122
+ ## Documentation
123
+
124
+ API documentation can be found at https://bencher.readthedocs.io/en/latest/
125
+
126
+ More documentation is needed for the examples and general workflow.
127
+