potassco-benchmark-tool 2.1.1__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.
@@ -0,0 +1,477 @@
1
+ """
2
+ Generate jupyter notebook for result visualization
3
+ """
4
+
5
+ import nbformat as nbf
6
+
7
+
8
+ # mypy: disable-error-code="no-untyped-call"
9
+ def gen_ipynb(parquet_file: str, file_name: str) -> None:
10
+ """
11
+ Generate jupyter notebook for result visualization.
12
+
13
+ Attributes
14
+ parquet_file (str): Name of the parquet file containing the data.
15
+ file_name (str): Name of the Jupyter notebook file.
16
+ """
17
+ intro = """\
18
+ # Visualization of results
19
+
20
+ You can install all required packages for this notebook by using the following command
21
+ inside the benchmark-tool directory.
22
+ ```bash
23
+ $ pip install .[plot]
24
+ ```
25
+ """
26
+
27
+ data_heading = """\
28
+ ### Obtain data
29
+ """
30
+
31
+ data_code = f'''\
32
+ from typing import Any
33
+
34
+ import ipywidgets as widgets
35
+ import numpy as np
36
+ import pandas as pd
37
+ import plotly.express as px
38
+ import plotly.graph_objects as go
39
+ from ipywidgets.widgets.interaction import fixed
40
+
41
+ df_in = pd.read_parquet("{parquet_file}")
42
+
43
+ settings: set[str] = set()
44
+ measures: set[str] = set()
45
+
46
+
47
+ def get_metadata(df: pd.DataFrame) -> dict[str, Any]:
48
+ """
49
+ Extract metadata from dataframe.
50
+
51
+ Attributes
52
+ df (pd.DataFrame): DataFrame.
53
+ """
54
+ meta: dict[str, Any] = {{}}
55
+ for col in df.columns:
56
+ if col[0] == "_metadata":
57
+ meta[col[1]] = list(df.loc[df[col[0]][col[1]] != "nan", (col[0], col[1])])
58
+ elif col[0] != "":
59
+ measures.add(col[0])
60
+ settings.add(col[1])
61
+ df.drop("_metadata", axis=1, level=0, inplace=True)
62
+ return meta
63
+
64
+
65
+ metadata = get_metadata(df_in)
66
+
67
+ df_fill = (
68
+ df_in.loc[: int(float(metadata["offset"][0])), [("", "instance")]]
69
+ .replace("<NA>", np.nan)
70
+ .ffill()
71
+ .combine_first(df_in.drop(columns=("", "instance")))
72
+ .loc[: int(float(metadata["offset"][0])),]
73
+ )
74
+ '''
75
+
76
+ funcs_heading = """\
77
+ ### Helper functions
78
+ """
79
+ funcs_code = '''\
80
+ def multi_checkbox_widget(options_dict: dict[str, widgets.Checkbox]) -> widgets.VBox:
81
+ """
82
+ Widget with a search field and lots of checkboxes.
83
+ Based on 'https://gist.github.com/MattJBritton/9dc26109acb4dfe17820cf72d82f1e6f'.
84
+
85
+ Attributes:
86
+ options_dict (dict): Widget options.
87
+ """
88
+ search_widget = widgets.Text()
89
+ output_widget = widgets.Output()
90
+ options = list(options_dict.values())
91
+ options_layout = widgets.Layout(
92
+ overflow="auto", border="1px solid black", width="300px", height="300px", flex_flow="column", display="flex"
93
+ )
94
+
95
+ # selected_widget = wid.Box(children=[options[0]])
96
+ options_widget = widgets.VBox(options, layout=options_layout)
97
+ # left_widget = wid.VBox(search_widget, selected_widget)
98
+ multi_select = widgets.VBox([search_widget, options_widget])
99
+
100
+ @output_widget.capture()
101
+ def on_checkbox_change(change):
102
+ """
103
+ Helper function to sort checkboxes based on selection.
104
+ """
105
+ # change["owner"].description
106
+ # print(options_widget.children)
107
+ # selected_item = wid.Button(description = change["new"])
108
+ # selected_widget.children = [] #selected_widget.children + [selected_item]
109
+ options_widget.children = sorted(list(options_widget.children), key=lambda x: x.value, reverse=True)
110
+
111
+ for checkbox in options:
112
+ checkbox.observe(on_checkbox_change, names="value")
113
+
114
+ @output_widget.capture()
115
+ def on_text_change(change):
116
+ """
117
+ Helper function to filter checkboxes based on search field.
118
+ """
119
+ search_input = change["new"]
120
+ if search_input == "":
121
+ # Reset search field
122
+ new_options = sorted(options, key=lambda x: x.value, reverse=True)
123
+ else:
124
+ # Filter by search field using difflib.
125
+ # close_matches = difflib.get_close_matches(search_input, list(options_dict.keys()), cutoff=0.0)
126
+ close_matches = [x for x in list(options_dict.keys()) if str.lower(search_input.strip("")) in str.lower(x)]
127
+ new_options = sorted(
128
+ [x for x in options if x.description in close_matches], key=lambda x: x.value, reverse=True
129
+ ) # [options_dict[x] for x in close_matches]
130
+ options_widget.children = new_options
131
+
132
+ search_widget.observe(on_text_change, names="value")
133
+ display(output_widget)
134
+ return multi_select
135
+
136
+
137
+ def prepare_data(data: pd.DataFrame, measure: str, merge: str) -> pd.DataFrame:
138
+ """
139
+ Prepare data for plotting.
140
+
141
+ Attributes:
142
+ data_frame (pd.DataFrame): Input data.
143
+ measure (str): Measure to plot.
144
+ merge (str): How to merge runs (none, mean, median).
145
+ """
146
+ cs = list(data[measure].columns)
147
+ df_plot = pd.DataFrame()
148
+ df_plot["instance"] = data.loc[:, ("", "instance")]
149
+ for c in cs:
150
+ df_plot[c] = pd.to_numeric(data.loc[:, (measure, c)], errors="coerce")
151
+
152
+ if merge == "median":
153
+ df_plot = df_plot.groupby(
154
+ "instance", dropna=False).median().reset_index()
155
+ elif merge == "mean":
156
+ df_plot = df_plot.groupby(
157
+ "instance", dropna=False).mean().reset_index()
158
+
159
+ df_plot = df_plot.drop(["instance"], axis=1)
160
+ df_plot.loc[-1] = [0 for x in range(len(df_plot.columns))]
161
+ df_plot.sort_index(inplace=True)
162
+ df_plot.index = df_plot.index + 1
163
+
164
+ res = pd.DataFrame()
165
+ for c in cs:
166
+ s = df_plot[c].sort_values(ignore_index=True).drop_duplicates(keep="last")
167
+ key = "_to_" + c
168
+ if key in metadata:
169
+ tl = max(map(float, metadata[key]))
170
+ s.mask(s.ge(tl), inplace=True)
171
+ res = pd.concat([res, s], axis=1)
172
+ return res.sort_index()
173
+
174
+
175
+ def prepare_plots(
176
+ data: pd.DataFrame, measure: str, merge: str, mode: str, width: int, height: int
177
+ ) -> tuple[go.FigureWidget, dict[str, int]]:
178
+ """
179
+ Prepare plotly figure and traces.
180
+ Attributes:
181
+ data (pd.DataFrame): Input data.
182
+ measure (str): Measure to plot.
183
+ merge (str): How to merge runs (none, mean, median).
184
+ mode (str): Plot mode (cactus, cdf).
185
+ width (int): Plot width.
186
+ height (int): Plot height.
187
+ """
188
+ plot_data = prepare_data(data, measure, merge)
189
+
190
+ fig = go.Figure()
191
+ colors = px.colors.qualitative.G10
192
+ switch_xy = False
193
+
194
+ # set up multiple traces
195
+ i = 0
196
+ lookup = {}
197
+ for col in plot_data.columns:
198
+ val = plot_data[[col]].dropna()
199
+ if mode == "Survivor":
200
+ x_vals = val[col].cumsum()
201
+ y_vals = val.index
202
+ switch_xy = False
203
+ elif mode == "Cactus":
204
+ x_vals = val.index
205
+ y_vals = val[col]
206
+ switch_xy = True
207
+ else:
208
+ x_vals = val[col]
209
+ y_vals = val.index
210
+ switch_xy = False
211
+ fig.add_trace(
212
+ go.Scatter(
213
+ x=x_vals,
214
+ y=y_vals,
215
+ name=col,
216
+ visible=False,
217
+ line={"shape": "hv"},
218
+ marker=dict(color=colors[i % len(colors)], size=15),
219
+ )
220
+ )
221
+ lookup[col] = i
222
+ i += 1
223
+
224
+ if switch_xy:
225
+ fig.update_layout(
226
+ xaxis={"title": {"text": "# of instances"}},
227
+ yaxis={"title": {"text": "Time in s"}},
228
+ updatemenus=[
229
+ {
230
+ "buttons": [
231
+ {"label": "Linear", "method": "relayout", "args": [{"yaxis.type": "linear"}]},
232
+ {"label": "Log", "method": "relayout", "args": [{"yaxis.type": "log"}]},
233
+ ]
234
+ }
235
+ ],
236
+ )
237
+ else:
238
+ fig.update_layout(
239
+ xaxis={"title": {"text": "Time in s"}},
240
+ yaxis={"title": {"text": "# of instances"}},
241
+ updatemenus=[
242
+ {
243
+ "buttons": [
244
+ {"label": "Linear", "method": "relayout", "args": [{"xaxis.type": "linear"}]},
245
+ {"label": "Log", "method": "relayout", "args": [{"xaxis.type": "log"}]},
246
+ ]
247
+ }
248
+ ],
249
+ )
250
+
251
+ fig.update_layout(
252
+ title={"text": f"{mode} plot"},
253
+ autosize=False,
254
+ width=width,
255
+ height=height,
256
+ # font=dict(
257
+ # family="Courier New, monospace",
258
+ # size=18,
259
+ # color="RebeccaPurple"
260
+ # ),
261
+ legend={
262
+ "orientation": "h",
263
+ "yanchor": "bottom",
264
+ "y": -0.4 * 500 / height,
265
+ "xanchor": "center",
266
+ "x": 0.5,
267
+ "maxheight": 0.1,
268
+ "title_text": "Setting",
269
+ },
270
+ margin={
271
+ "l": 50,
272
+ "r": 50,
273
+ "b": 50,
274
+ "t": 50,
275
+ "pad": 10,
276
+ },
277
+ )
278
+ fig.update_yaxes(automargin=True)
279
+ fig.update_xaxes(automargin=True)
280
+ return go.FigureWidget(fig), lookup
281
+
282
+
283
+ def plot(
284
+ data: pd.DataFrame,
285
+ measure: str,
286
+ merge: str,
287
+ mode: str,
288
+ width: int,
289
+ height: int,
290
+ opts: dict[str, Any],
291
+ sets: dict[str, list[str]],
292
+ ) -> None:
293
+ """
294
+ Prepare plot and traces.
295
+ Attributes:
296
+ data (pd.DataFrame): Input data.
297
+ measure (str): Measure to plot
298
+ merge (str): How to merge runs (mean, median).
299
+ mode (str): Plot mode (cactus, cdf).
300
+ width (int): Plot width.
301
+ height (int): Plot height.
302
+ opts (dict): Widget options.
303
+ sets (dict): System:Settings association
304
+ """
305
+
306
+ def f(**args: Any) -> None:
307
+ """
308
+ Update trace visibility based on selected options.
309
+ Attributes:
310
+ args: Should contain selected settings, figure widget, lookup table, and sets.
311
+ """
312
+ figure_widget = args.pop("fig")
313
+ lookup = args.pop("lookup")
314
+ sets = args.pop("sets")
315
+ s = sorted([key for key, value in args.items() if value])
316
+ select = []
317
+ for ss in s:
318
+ if ss in sets:
319
+ select += sets[ss]
320
+ else:
321
+ select.append(ss)
322
+
323
+ for col, i in lookup.items():
324
+ if col in select:
325
+ figure_widget.data[i].visible = True
326
+ else:
327
+ figure_widget.data[i].visible = False
328
+
329
+ display(figure_widget)
330
+
331
+ fig, lookup = prepare_plots(data, measure, merge, mode, width, height)
332
+ opts["fig"] = fixed(fig)
333
+ opts["lookup"] = fixed(lookup)
334
+ opts["sets"] = fixed(sets)
335
+
336
+ out = widgets.interactive_output(f, opts)
337
+ display(out)
338
+
339
+
340
+ def get_gui(data: pd.DataFrame) -> tuple[widgets.HBox, widgets.Output]:
341
+ """
342
+ Create GUI for plotting.
343
+
344
+ Attributes:
345
+ data (pd.DataFrame): Input data.
346
+ """
347
+ # Create sets for system selection
348
+ sets: dict[str, list[str]] = {}
349
+ for setting in settings:
350
+ system_setting = setting.split("/")
351
+ if system_setting[0] in sets:
352
+ sets[system_setting[0]].append(setting)
353
+ else:
354
+ sets[system_setting[0]] = [setting]
355
+
356
+ options_dict = {
357
+ x: widgets.Checkbox(description=x, value=False, style={"description_width": "0px"})
358
+ for x in sorted(list(settings) + list(sets.keys()))
359
+ }
360
+
361
+ ui = multi_checkbox_widget(options_dict)
362
+
363
+ measure_opts = ["time"] + sorted(measures - {"time"}) if "time" in measures else sorted(measures)
364
+ select_measure = widgets.ToggleButtons(
365
+ options=measure_opts,
366
+ description="Measure:",
367
+ disabled=False,
368
+ button_style="", # 'success', 'info', 'warning', 'danger' or ''
369
+ #tooltips=["Cactus plot", "CDF plot"],
370
+ )
371
+ select_merge = widgets.ToggleButtons(
372
+ options=["do not merge", "median", "mean"],
373
+ description="Merge:",
374
+ disabled=False,
375
+ button_style="", # 'success', 'info', 'warning', 'danger' or ''
376
+ tooltips=["Merge runs using median", "Merge runs using mean"],
377
+ )
378
+ select_mode = widgets.ToggleButtons(
379
+ options=["Survivor", "Cactus", "CDF"],
380
+ description="Mode:",
381
+ disabled=False,
382
+ button_style="", # 'success', 'info', 'warning', 'danger' or ''
383
+ tooltips=["Cactus plot", "CDF plot"],
384
+ )
385
+ width_slider = widgets.IntSlider(
386
+ value=1000,
387
+ min=500,
388
+ max=1500,
389
+ step=100,
390
+ orientation="horizontal",
391
+ continuous_update=False,
392
+ )
393
+ height_slider = widgets.IntSlider(
394
+ value=500,
395
+ min=500,
396
+ max=1500,
397
+ step=100,
398
+ orientation="horizontal",
399
+ continuous_update=False,
400
+ )
401
+ sliders = widgets.VBox(
402
+ [
403
+ widgets.Label("Width of the plot in px"),
404
+ width_slider,
405
+ widgets.Label("Height of the plot in px"),
406
+ height_slider,
407
+ ]
408
+ )
409
+
410
+ out = widgets.interactive_output(
411
+ plot,
412
+ {
413
+ "measure": select_measure,
414
+ "merge": select_merge,
415
+ "mode": select_mode,
416
+ "width": width_slider,
417
+ "height": height_slider,
418
+ "opts": fixed(options_dict),
419
+ "data": fixed(data),
420
+ "sets": fixed(sets),
421
+ },
422
+ )
423
+
424
+ return (
425
+ widgets.HBox(
426
+ [widgets.VBox([select_measure, select_merge, select_mode, sliders], layout=widgets.Layout(width="60%")), ui]
427
+ ),
428
+ out,
429
+ )
430
+ '''
431
+
432
+ plot_heading = """\
433
+ # Plots
434
+
435
+ The first row on the left select which kind of plot will be created.
436
+
437
+ The second row on the left determines how multiple runs (if there are more than 1) will be mergedinto a single result.
438
+
439
+ The sliders can be used to adjust the size of the plot.
440
+
441
+ Finally, select individual settings or all settings of a system to plot using the checkboxes on the right. The input field at the top can be used to search through all possible selections.
442
+
443
+ The scale of the time axis can be selected to the left of the plot.
444
+ ---
445
+
446
+ All plots below are interactive. When hovering over a plot, a toolbar will appear, which can be used to zoom, move, and save the plot.
447
+ You can show the full output, without a scrollbar, by clicking the gray bar to the left of the output cell.
448
+
449
+ """
450
+ plot_code = """\
451
+ gui, out_plot = get_gui(df_fill)
452
+ display(gui)
453
+ display(out_plot)
454
+ """
455
+
456
+ nb = nbf.v4.new_notebook()
457
+ nb["cells"] = [
458
+ nbf.v4.new_markdown_cell(intro),
459
+ nbf.v4.new_markdown_cell(data_heading),
460
+ nbf.v4.new_code_cell(data_code),
461
+ nbf.v4.new_markdown_cell(funcs_heading),
462
+ nbf.v4.new_code_cell(funcs_code),
463
+ nbf.v4.new_markdown_cell(plot_heading),
464
+ nbf.v4.new_code_cell(plot_code),
465
+ ]
466
+ fname = file_name
467
+ nb.cells[1]["metadata"]["jp-MarkdownHeadingCollapsed"] = True
468
+ nb.cells[3]["metadata"]["jp-MarkdownHeadingCollapsed"] = True
469
+ # nb.cells[6]["metadata"]["jupyter"] = {"source_hidden": True}
470
+
471
+ try:
472
+ nbf.validate(nb)
473
+ except nbf.validator.NotebookValidationError as e: # nocoverage
474
+ raise RuntimeError("Generated notebook is invalid") from e
475
+
476
+ with open(fname, "w", encoding="utf-8") as f:
477
+ nbf.write(nb, f)
@@ -0,0 +1,42 @@
1
+ """
2
+ ODS configuration.
3
+ """
4
+
5
+ STYLES_XML = """<?xml version="1.0" encoding="UTF-8"?>
6
+ <office:document-styles
7
+ xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
8
+ xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
9
+ xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
10
+ xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
11
+ xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"
12
+ xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
13
+ xmlns:xlink="http://www.w3.org/1999/xlink"
14
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
15
+ xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0"
16
+ xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"
17
+ xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"
18
+ xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"
19
+ xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0"
20
+ xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"
21
+ xmlns:math="http://www.w3.org/1998/Math/MathML"
22
+ xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0"
23
+ xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0"
24
+ xmlns:ooo="http://openoffice.org/2004/office"
25
+ xmlns:ooow="http://openoffice.org/2004/writer"
26
+ xmlns:oooc="http://openoffice.org/2004/calc"
27
+ xmlns:dom="http://www.w3.org/2001/xml-events"
28
+ xmlns:rpt="http://openoffice.org/2005/report"
29
+ xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2"
30
+ xmlns:rdfa="http://docs.oasis-open.org/opendocument/meta/rdfa#"
31
+ office:version="1.2">
32
+ <office:styles>
33
+ <style:style style:name="Default" style:family="table-cell"/>
34
+ <style:style style:name="cellBest" style:family="table-cell" style:parent-style-name="Default">
35
+ <style:table-cell-properties fo:background-color="#00ff00"/>
36
+ </style:style>
37
+ <style:style style:name="cellWorst" style:family="table-cell" style:parent-style-name="Default">
38
+ <style:table-cell-properties fo:background-color="#ff0000"/>
39
+ </style:style>
40
+ </office:styles>
41
+ </office:document-styles>
42
+ """