azure-quantum 2.2.0.dev6__py3-none-any.whl → 2.3.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.
@@ -1,497 +0,0 @@
1
- ##
2
- # Copyright (c) Microsoft Corporation. All rights reserved.
3
- # Licensed under the MIT License.
4
- ##
5
- __all__ = ['MicrosoftEstimatorResult']
6
-
7
- from typing import Any, Dict, List, Optional, Union
8
-
9
- import json
10
- import markdown
11
-
12
-
13
- class HTMLWrapper:
14
- """
15
- Simple HTML wrapper to expose _repr_html_ for Jupyter clients.
16
- """
17
- def __init__(self, content: str):
18
- self.content = content
19
-
20
- def _repr_html_(self):
21
- return self.content
22
-
23
- class MicrosoftEstimatorResult(dict):
24
- """
25
- Microsoft Resource Estimator result.
26
-
27
- Job results from the `microsoft.estimator` target are represented by
28
- instances of this class. The class represents simple resource estimation
29
- results as well as batching resource estimation results. The latter can
30
- be indexed by an integer index to access an individual result from the
31
- batching result.
32
- """
33
- MAX_DEFAULT_ITEMS_IN_TABLE = 5
34
-
35
- def __init__(self, data: Union[Dict, List]):
36
- self._data = data
37
-
38
- if isinstance(data, dict):
39
- super().__init__(data)
40
-
41
- self._is_simple = True
42
- if MicrosoftEstimatorResult._is_succeeded(self):
43
- self._repr = self._item_result_table()
44
- self.summary = HTMLWrapper(self._item_result_summary_table())
45
- self.diagram = EstimatorResultDiagram(self.data().copy())
46
-
47
- elif isinstance(data, list):
48
- super().__init__({idx: MicrosoftEstimatorResult(item_data)
49
- for idx, item_data in enumerate(data)})
50
-
51
- self._data = data
52
- self._is_simple = False
53
- num_items = len(data)
54
- self._repr = ""
55
- if num_items > self.MAX_DEFAULT_ITEMS_IN_TABLE:
56
- self._repr += "<p><b>Info:</b> <i>The overview table is " \
57
- "cut off after " \
58
- f"{self.MAX_DEFAULT_ITEMS_IN_TABLE} items. If " \
59
- "you want to see all items, suffix the result " \
60
- "variable with <code>[:]</code></i></p>"
61
- num_items = self.MAX_DEFAULT_ITEMS_IN_TABLE
62
- self._repr += self._batch_result_table(range(num_items))
63
-
64
- # Add plot function for batching jobs
65
- self.plot = self._plot
66
- self.summary_data_frame = self._summary_data_frame
67
-
68
- def _is_succeeded(self):
69
- return 'status' in self and self['status'] == "success"
70
-
71
- def data(self, idx: Optional[int] = None) -> Any:
72
- """
73
- Returns raw data of the result object.
74
-
75
- In case of a batching job, you can pass an index to access a specific
76
- item.
77
- """
78
- if idx is None:
79
- return self._data
80
- elif not self._is_simple:
81
- return self._data[idx]
82
- else:
83
- msg = "Cannot pass parameter 'idx' to 'data' for non-batching job"
84
- raise ValueError(msg)
85
-
86
- def _repr_html_(self):
87
- """
88
- HTML table representation of the result.
89
- """
90
- return self._repr
91
-
92
- def __getitem__(self, key):
93
- """
94
- If the result represents a batching job and key is a slice, a
95
- side-by-side table comparison is shown for the indexes represented by
96
- the slice.
97
-
98
- Otherwise, the key is used to access the raw data directly.
99
- """
100
- if isinstance(key, slice):
101
- if self._is_simple:
102
- msg = "Cannot pass slice to '__getitem__' for non-batching job"
103
- raise ValueError(msg)
104
- return HTMLWrapper(self._batch_result_table(range(len(self))[key]))
105
- else:
106
- return super().__getitem__(key)
107
-
108
- def _plot(self, **kwargs):
109
- """
110
- Plots all result items in a space time plot, where the x-axis shows
111
- total runtime, and the y-axis shows total number of physical qubits.
112
- Both axes are in log-scale.
113
- Attributes:
114
- labels (list): List of labels for the legend.
115
- """
116
- try:
117
- import matplotlib.pyplot as plt
118
- except ImportError:
119
- raise ImportError(
120
- "Missing optional 'matplotlib' dependency. To install run: "
121
- "pip install matplotlib"
122
- )
123
-
124
- labels = kwargs.pop("labels", [])
125
-
126
- [xs, ys] = zip(*[
127
- (self.data(i)['physicalCounts']['runtime'],
128
- self.data(i)['physicalCounts']['physicalQubits'])
129
- for i in range(len(self))])
130
-
131
- _ = plt.figure(figsize=(15, 8))
132
-
133
- plt.ylabel('Physical qubits')
134
- plt.xlabel('Runtime')
135
- plt.loglog()
136
- for i, (x, y) in enumerate(zip(xs, ys)):
137
- if isinstance(labels, list) and i < len(labels):
138
- label = labels[i]
139
- else:
140
- label = str(i)
141
- plt.scatter(x=[x], y=[y], label=label, marker="os+x"[i % 4])
142
-
143
- nsec = 1
144
- usec = 1e3 * nsec
145
- msec = 1e3 * usec
146
- sec = 1e3 * msec
147
- min = 60 * sec
148
- hour = 60 * min
149
- day = 24 * hour
150
- week = 7 * day
151
- month = 31 * day
152
- year = 365 * month
153
- decade = 10 * year
154
- century = 10 * decade
155
-
156
- time_units = [
157
- nsec, usec, msec, sec, min, hour, day, week,
158
- month, year, decade, century]
159
- time_labels = [
160
- "1 ns", "1 µs", "1 ms", "1 s", "1 min", "1 hour", "1 day",
161
- "1 week", "1 month", "1 year", "1 decade", "1 century"]
162
-
163
- cutoff = next(
164
- (i for i, x in enumerate(time_units) if x > max(xs)),
165
- len(time_units) - 1) + 1
166
-
167
- plt.xticks(time_units[0:cutoff], time_labels[0:cutoff], rotation=90)
168
- plt.legend(loc="upper left")
169
- plt.show()
170
-
171
- @property
172
- def call_graph(self):
173
- """
174
- Shows the call graph of a simple resource estimation result with
175
- profiling information.
176
- """
177
- try:
178
- import graphviz
179
- except ImportError:
180
- raise ImportError(
181
- "Missing optional 'graphviz' dependency. To install run: "
182
- "pip install graphviz"
183
- )
184
-
185
- if not self._is_simple:
186
- raise ValueError("The `call_graph` method cannot be called on a "
187
- "batching result, try indexing into the result "
188
- "first")
189
-
190
- if not hasattr(self, "_call_graph"):
191
- from itertools import groupby
192
-
193
- data = self.data().get("callGraph", None)
194
-
195
- if data is None:
196
- raise ValueError("The result does not contain any profiling "
197
- "information. Set "
198
- "`profiling.call_stack_depth` to some value")
199
-
200
- g = graphviz.Digraph()
201
- g.attr('node', shape='box', style='rounded, filled',
202
- fontname='Arial', fontsize='10', margin='0.05,0.05',
203
- height='0', width='0', fillcolor='#f6f6f6', color='#e3e3e3')
204
- g.attr('edge', color='#d0d0d0')
205
-
206
- nodes_indexed = [{**node, 'index': index}
207
- for index, node in enumerate(data['nodes'])]
208
-
209
- def sorter(node): return node['depth']
210
- nodes_indexed.sort(key=sorter)
211
- for _, nodes in groupby(nodes_indexed, sorter):
212
- with g.subgraph() as s:
213
- s.attr(rank='same')
214
- for node in nodes:
215
- s.node(str(node['index']), node['name'])
216
-
217
- for edge in data['edges']:
218
- g.edge(str(edge[0]), str(edge[1]))
219
-
220
- self._call_graph = g
221
-
222
- return self._call_graph
223
-
224
- @property
225
- def profile(self):
226
- """
227
- """
228
- if not self._is_simple:
229
- raise ValueError("The `call_graph` method cannot be called on a "
230
- "batching result, try indexing into the result "
231
- "first")
232
-
233
- if not hasattr(self, "_profile"):
234
- import base64
235
- import json
236
-
237
- profile = self.data().get("profile", None)
238
-
239
- if profile is None:
240
- raise ValueError("The result does not contain any profiling "
241
- "information. Set "
242
- "`profiling.call_stack_depth` to some value")
243
-
244
- profile_encoded = json.dumps(profile).encode('utf-8')
245
- data64 = base64.b64encode(profile_encoded).decode('utf-8')
246
-
247
- self._profile = f"""
248
- <a href="data:text/json;base64,{data64}" download="profile.json">
249
- Download the profile</a> to your computer. Then open the profile
250
- by dragging it into <a href="https://speedscope.app"
251
- target="_blank">speedscope</a>.
252
- """
253
-
254
- return HTMLWrapper(self._profile)
255
-
256
- @property
257
- def json(self):
258
- """
259
- Returns a JSON representation of the resource estimation result data.
260
- """
261
- if not hasattr(self, "_json"):
262
- import json
263
- self._json = json.dumps(self._data)
264
-
265
- return self._json
266
-
267
- def _summary_data_frame(self, **kwargs):
268
- try:
269
- import pandas as pd
270
- except ImportError:
271
- raise ImportError(
272
- "Missing optional 'pandas' dependency. To install run: "
273
- "pip install pandas"
274
- )
275
-
276
- # get labels or use default value, then extend with missing elements,
277
- # and truncate extra elements
278
- labels = kwargs.pop("labels", [])
279
- labels.extend(range(len(labels), len(self)))
280
- labels = labels[:len(self)]
281
-
282
- def get_row(result):
283
- if MicrosoftEstimatorResult._is_succeeded(result):
284
- formatted = result["physicalCountsFormatted"]
285
-
286
- return (
287
- formatted["algorithmicLogicalQubits"],
288
- formatted["logicalDepth"],
289
- formatted["numTstates"],
290
- result["logicalQubit"]["codeDistance"],
291
- formatted["numTfactories"],
292
- formatted["physicalQubitsForTfactoriesPercentage"],
293
- formatted["physicalQubits"],
294
- formatted["rqops"],
295
- formatted["runtime"]
296
- )
297
- else:
298
- return ['No solution found'] * 9
299
-
300
- data = [get_row(self.data(index)) for index in range(len(self))]
301
- columns = ["Logical qubits", "Logical depth", "T states",
302
- "Code distance", "T factories", "T factory fraction",
303
- "Physical qubits", "rQOPS", "Physical runtime"]
304
- return pd.DataFrame(data, columns=columns, index=labels)
305
-
306
- def _item_result_table(self):
307
- html = ""
308
-
309
- md = markdown.Markdown(extensions=['mdx_math'])
310
- for group in self['reportData']['groups']:
311
- html += f"""
312
- <details {"open" if group['alwaysVisible'] else ""}>
313
- <summary style="display:list-item">
314
- <strong>{group['title']}</strong>
315
- </summary>
316
- <table>"""
317
- for entry in group['entries']:
318
- val = self
319
- for key in entry['path'].split("/"):
320
- val = val[key]
321
- explanation = md.convert(entry["explanation"])
322
- html += f"""
323
- <tr>
324
- <td style="font-weight: bold; vertical-align: top; white-space: nowrap">{entry['label']}</td>
325
- <td style="vertical-align: top; white-space: nowrap">{val}</td>
326
- <td style="text-align: left">
327
- <strong>{entry["description"]}</strong>
328
- <hr style="margin-top: 2px; margin-bottom: 0px; border-top: solid 1px black" />
329
- {explanation}
330
- </td>
331
- </tr>
332
- """
333
- html += "</table></details>"
334
-
335
- html += f"<details><summary style=\"display:list-item\"><strong>Assumptions</strong></summary><ul>"
336
- for assumption in self['reportData']['assumptions']:
337
- html += f"<li>{md.convert(assumption)}</li>"
338
- html += "</ul></details>"
339
-
340
- return html
341
-
342
- def _item_result_summary_table(self):
343
- html = """
344
- <style>
345
- .aqre-tooltip {
346
- position: relative;
347
- border-bottom: 1px dotted black;
348
- }
349
-
350
- .aqre-tooltip .aqre-tooltiptext {
351
- font-weight: normal;
352
- visibility: hidden;
353
- width: 600px;
354
- background-color: #e0e0e0;
355
- color: black;
356
- text-align: center;
357
- border-radius: 6px;
358
- padding: 5px 5px;
359
- position: absolute;
360
- z-index: 1;
361
- top: 150%;
362
- left: 50%;
363
- margin-left: -200px;
364
- border: solid 1px black;
365
- }
366
-
367
- .aqre-tooltip .aqre-tooltiptext::after {
368
- content: "";
369
- position: absolute;
370
- bottom: 100%;
371
- left: 50%;
372
- margin-left: -5px;
373
- border-width: 5px;
374
- border-style: solid;
375
- border-color: transparent transparent black transparent;
376
- }
377
-
378
- .aqre-tooltip:hover .aqre-tooltiptext {
379
- visibility: visible;
380
- }
381
- </style>"""
382
-
383
- md = markdown.Markdown(extensions=['mdx_math'])
384
- for group in self['reportData']['groups']:
385
- html += f"""
386
- <details {"open" if group['alwaysVisible'] else ""}>
387
- <summary style="display:list-item">
388
- <strong>{group['title']}</strong>
389
- </summary>
390
- <table>"""
391
- for entry in group['entries']:
392
- val = self
393
- for key in entry['path'].split("/"):
394
- val = val[key]
395
- explanation = md.convert(entry["explanation"])
396
- html += f"""
397
- <tr class="aqre-tooltip">
398
- <td style="font-weight: bold"><span class="aqre-tooltiptext">{explanation}</span>{entry['label']}</td>
399
- <td>{val}</td>
400
- <td style="text-align: left">{entry["description"]}</td>
401
- </tr>
402
- """
403
- html += "</table></details>"
404
-
405
- html += f"<details><summary style=\'display:list-item\'><strong>Assumptions</strong></summary><ul>"
406
- for assumption in self['reportData']['assumptions']:
407
- html += f"<li>{md.convert(assumption)}</li>"
408
- html += "</ul></details>"
409
-
410
- return html
411
-
412
- def _batch_result_table(self, indices):
413
- succeeded_item_indices = [i for i in indices if MicrosoftEstimatorResult._is_succeeded(self[i])]
414
- if len(succeeded_item_indices) == 0:
415
- print("None of the jobs succeeded")
416
- return ""
417
-
418
- first_succeeded_item_index = succeeded_item_indices[0]
419
-
420
- html = ""
421
-
422
- md = markdown.Markdown(extensions=['mdx_math'])
423
-
424
- item_headers = "".join(f"<th>{i}</th>" for i in indices)
425
-
426
- for group_index, group in enumerate(self[first_succeeded_item_index]['reportData']['groups']):
427
- html += f"""
428
- <details {"open" if group['alwaysVisible'] else ""}>
429
- <summary style="display:list-item">
430
- <strong>{group['title']}</strong>
431
- </summary>
432
- <table>
433
- <thead><tr><th>Item</th>{item_headers}</tr></thead>"""
434
-
435
- visited_entries = set()
436
-
437
- for entry in [entry for index in succeeded_item_indices for entry in self[index]['reportData']['groups'][group_index]['entries']]:
438
- label = entry['label']
439
- if label in visited_entries:
440
- continue
441
- visited_entries.add(label)
442
-
443
- html += f"""
444
- <tr>
445
- <td style="font-weight: bold; vertical-align: top; white-space: nowrap">{label}</td>
446
- """
447
-
448
- for index in indices:
449
- val = self[index]
450
- if index in succeeded_item_indices:
451
- for key in entry['path'].split("/"):
452
- if key in val:
453
- val = val[key]
454
- else:
455
- val = "N/A"
456
- break
457
- else:
458
- val = "N/A"
459
- html += f"""
460
- <td style="vertical-align: top; white-space: nowrap">{val}</td>
461
- """
462
-
463
- html += """
464
- </tr>
465
- """
466
- html += "</table></details>"
467
-
468
- html += f"<details><summary style=\"display:list-item\"><strong>Assumptions</strong></summary><ul>"
469
- for assumption in self[first_succeeded_item_index]['reportData']['assumptions']:
470
- html += f"<li>{md.convert(assumption)}</li>"
471
- html += "</ul></details>"
472
-
473
- return html
474
-
475
- @staticmethod
476
- def _is_succeeded(obj):
477
- return 'status' in obj and obj['status'] == "success"
478
-
479
- class EstimatorResultDiagram:
480
- def __init__(self, data):
481
- data.pop("reportData")
482
- self.data_json = json.dumps(data).replace(" ", "")
483
- self.vis_lib = "https://cdn-aquavisualization-prod.azureedge.net/resource-estimation/index.js"
484
- self.space = HTMLWrapper(self._space_diagram())
485
- self.time = HTMLWrapper(self._time_diagram())
486
-
487
- def _space_diagram(self):
488
- html = f"""
489
- <script src={self.vis_lib}></script>
490
- <re-space-diagram data={self.data_json}></re-space-diagram>"""
491
- return html
492
-
493
- def _time_diagram(self):
494
- html = f"""
495
- <script src={self.vis_lib}></script>
496
- <re-time-diagram data={self.data_json}></re-time-diagram>"""
497
- return html