iqm-benchmarks 2.44__py3-none-any.whl → 2.46__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.
@@ -2,11 +2,20 @@
2
2
  Generation of figures
3
3
  """
4
4
 
5
+ from typing import List, Union
6
+
5
7
  from matplotlib import ticker
6
8
  from matplotlib.colors import Normalize
9
+ from matplotlib.figure import Figure
7
10
  import matplotlib.pyplot as plt
11
+ from matplotlib.transforms import Bbox
8
12
  import numpy as np
9
13
  import numpy.linalg as la
14
+ from pandas import DataFrame
15
+ import xarray as xr
16
+
17
+ from iqm.benchmarks.benchmark_definition import BenchmarkObservationIdentifier
18
+ from mGST.reporting.reporting import compute_matched_ideal_hamiltonian_params, generate_basis_labels
10
19
 
11
20
 
12
21
  SMALL_SIZE = 8
@@ -21,7 +30,7 @@ plt.rc("ytick", labelsize=SMALL_SIZE) # fontsize of the tick labels
21
30
  plt.rc("legend", fontsize=SMALL_SIZE) # legend fontsize
22
31
  plt.rc("figure", titlesize=BIGGER_SIZE) # fontsize of the figure title
23
32
 
24
- cmap = plt.colormaps.get_cmap("RdBu")
33
+ cmap = plt.colormaps.get_cmap("coolwarm")
25
34
  norm = Normalize(vmin=-1, vmax=1)
26
35
 
27
36
 
@@ -48,7 +57,7 @@ def set_size(w, h, ax=None):
48
57
  ax.figure.set_size_inches(figw, figh)
49
58
 
50
59
 
51
- def plot_objf(res_list, delta, title):
60
+ def plot_objf(res_list, title, delta=None):
52
61
  """Plots the objective function over iterations in the algorithm
53
62
 
54
63
  Parameters:
@@ -58,12 +67,15 @@ def plot_objf(res_list, delta, title):
58
67
 
59
68
  Returns:
60
69
  """
61
- plt.semilogy(res_list)
70
+ if delta is not None:
71
+ plt.semilogy(res_list)
72
+ plt.axhline(delta, color="green", label="conv. threshold")
73
+ plt.legend()
74
+ else:
75
+ plt.semilogy(res_list)
62
76
  plt.ylabel(f"Objective function")
63
77
  plt.xlabel(f"Iterations")
64
- plt.axhline(delta, color="green", label="conv. threshold")
65
78
  plt.title(title)
66
- plt.legend()
67
79
  plt.show()
68
80
 
69
81
 
@@ -147,9 +159,7 @@ def generate_spam_err_pdf(filename, E, rho, E2, rho2, title=None, spam2_content=
147
159
  plt.close()
148
160
 
149
161
 
150
- def generate_spam_err_std_pdf(
151
- filename, E, rho, E2, rho2, basis_labels=False, title=None, magnification=10, return_fig=False
152
- ):
162
+ def generate_spam_err_std_pdf(filename, E, rho, E2, rho2, basis_labels=False, title=None, return_fig=False):
153
163
  """Generate pdf plots of two sets of POVM + state side by side in matrix shape - standard basis
154
164
  The input sets can be either POVM/state directly or a difference different SPAM parametrizations to
155
165
  visualize errors.
@@ -170,8 +180,6 @@ def generate_spam_err_std_pdf(
170
180
  The Figure title
171
181
  basis_labels : list[str]
172
182
  A list of labels for the basis elements. For the standard basis this could be ["00", "01",...]
173
- magnification : float
174
- A factor to be applied to magnify errors in the rightmost plot.
175
183
  return_fig : bool
176
184
  If set to True, a figure object is returned by the function, otherwise the plot is saved as <filename>
177
185
  Returns
@@ -188,24 +196,31 @@ def generate_spam_err_std_pdf(
188
196
  if i == 0:
189
197
  plot_matrices = [np.real(rho), np.real(rho2), np.real(rho - rho2)]
190
198
  axes[i, 0].set_ylabel(f"rho", rotation=90, fontsize="large")
199
+ axes[i, 0].set_title(f"Estimate", fontsize="large")
200
+ axes[i, 1].set_title(f"Reference", fontsize="large")
201
+ axes[i, 2].set_title(f"Deviation", fontsize="large")
191
202
  else:
192
- plot_matrices = [np.real(E[i - 1]), np.real(E2[i - 1]), np.real(E[i - 1] - E2[i - 1]) * magnification]
203
+ plot_matrices = [np.real(E[i - 1]), np.real(E2[i - 1]), np.real(E[i - 1] - E2[i - 1])]
193
204
  axes[i, 0].set_ylabel(f"E_%i" % (i - 1), rotation=90, fontsize="large")
194
205
 
195
206
  for j in range(3):
196
207
  ax = axes[i, j]
197
208
  ax.patch.set_facecolor("whitesmoke")
198
209
  ax.set_aspect("equal")
210
+ max_entry = np.abs(plot_matrices[j]).max()
211
+ max_weight = 2 ** np.ceil(np.log2(max_entry, where=max_entry > 0))
199
212
  for (x, y), w in np.ndenumerate(plot_matrices[j].reshape(pdim, pdim)):
200
- size = np.sqrt(np.abs(w))
213
+ if j == 2:
214
+ size = np.sqrt(np.abs(w) / max_weight)
215
+ else:
216
+ size = np.sqrt(np.abs(w))
201
217
  rect = plt.Rectangle(
202
218
  [x + (1 - size) / 2, y + (1 - size) / 2],
203
219
  size,
204
220
  size,
205
- facecolor=cmap((w + 1) / 2),
206
- edgecolor=cmap((w + 1) / 2),
221
+ facecolor="#d62728" if w < 0 else "#1f77b4",
222
+ edgecolor="#d62728" if w < 0 else "#1f77b4",
207
223
  )
208
- # print(cmap(size))
209
224
  ax.add_patch(rect)
210
225
  ax.invert_yaxis()
211
226
  ax.set_xticks(np.arange(pdim + 1), labels=[])
@@ -222,15 +237,14 @@ def generate_spam_err_std_pdf(
222
237
  ax.grid(visible="True", alpha=0.4)
223
238
  ax.set_xticks(np.arange(pdim) + 0.5, minor=True, labels=basis_labels, rotation=45, fontsize=6)
224
239
  ax.set_yticks(np.arange(pdim) + 0.5, minor=True, labels=basis_labels, fontsize=6)
225
- if title:
226
- fig.suptitle(title)
240
+ fig.suptitle(title)
227
241
 
228
- if dim > 16:
229
- fig.colorbar(plt.cm.ScalarMappable(norm=norm, cmap=cmap), ax=axes, pad=0, shrink=0.6)
230
- fig.subplots_adjust(left=0, right=0.7, top=0.90, bottom=0.05, wspace=-0.6, hspace=0.4)
242
+ if dim >= 16:
243
+ fig.subplots_adjust(left=0, right=1, top=0.9, bottom=0.05, wspace=0.2, hspace=0.2)
244
+ set_size(np.sqrt(dim), 2 * np.sqrt(dim))
231
245
  else:
232
- fig.colorbar(plt.cm.ScalarMappable(norm=norm, cmap=cmap), ax=axes, pad=0)
233
- fig.subplots_adjust(left=0, right=0.7, top=0.90, bottom=0.05, wspace=-0.6, hspace=0.8)
246
+ fig.subplots_adjust(left=0, right=1, top=0.76, bottom=0.05, wspace=-0.8, hspace=0.4)
247
+ set_size(3 * np.sqrt(dim), 1.2 * np.sqrt(dim))
234
248
 
235
249
  if return_fig:
236
250
  return fig
@@ -240,11 +254,10 @@ def generate_spam_err_std_pdf(
240
254
  return None
241
255
 
242
256
 
243
- def generate_gate_err_pdf(
244
- filename, gates1, gates2, basis_labels=False, gate_labels=False, magnification=5, return_fig=False
245
- ):
257
+ def generate_gate_err_pdf(filename, gates1, gates2, basis_labels=False, gate_labels=False, return_fig=False):
246
258
  """Main routine to generate plots of reconstructed gates, ideal gates and the noise channels
247
- of the reconstructed gates.
259
+ of the reconstructed gates. The matrices are shown as Hinton diagrams, where the size of each square represents
260
+ the magnitude of the matrix element and the color represents its sign as well as the magnitude.
248
261
  The basis is arbitrary but using gates in the Pauli basis is recommended.
249
262
 
250
263
  Parameters
@@ -260,75 +273,395 @@ def generate_gate_err_pdf(
260
273
  and for the Pauli basis ["I", "X", "Y", "Z"] or the multi-qubit version.
261
274
  gate_labels : list[str]
262
275
  A list of names for the gates
263
- magnification : float
264
- A factor to be applied to magnify errors in the rightmost plot.
265
276
  return_fig : bool
266
277
  If set to True, a figure object is returned by the function, otherwise the plots are saved as <filename>
267
278
  """
268
279
  d = gates1.shape[0]
269
280
  dim = gates1[0].shape[0]
270
- if not basis_labels:
271
- basis_labels = np.arange(dim)
272
- if not gate_labels:
273
- gate_labels = [f"G%i" % k for k in range(d)]
281
+ basis_labels = basis_labels or np.arange(dim)
282
+ gate_labels = gate_labels or [f"G%i" % k for k in range(d)]
274
283
  plot3_title = r"id - G U^{-1}"
275
284
 
276
285
  figures = []
277
286
  for i in range(d):
278
- if dim > 16:
279
- fig, axes = plt.subplots(ncols=1, nrows=3, gridspec_kw={"height_ratios": [1, 1, 1]}, sharex=True)
280
- else:
281
- fig, axes = plt.subplots(ncols=3, nrows=1, gridspec_kw={"width_ratios": [1, 1, 1]}, sharex=True)
282
- dim = gates1[0].shape[0]
287
+ # Determine layout based on dimension
288
+ is_large_dim = dim > 16
289
+ layout_params = {
290
+ "ncols": 1 if is_large_dim else 3,
291
+ "nrows": 3 if is_large_dim else 1,
292
+ "gridspec_kw": {"height_ratios": [1, 1, 1]} if is_large_dim else {"width_ratios": [1, 1, 1]},
293
+ "sharex": True,
294
+ }
295
+
296
+ fig, axes = plt.subplots(**layout_params)
283
297
  plot_matrices = [
284
298
  np.real(gates1[i]),
285
299
  np.real(gates2[i]),
286
- magnification * (np.eye(dim) - np.real(gates1[i] @ la.inv(gates2[i]))),
300
+ np.eye(dim) - np.real(gates1[i] @ la.inv(gates2[i])),
287
301
  ]
288
302
 
289
303
  for j in range(3):
290
304
  ax = axes[j]
291
305
  ax.patch.set_facecolor("whitesmoke")
292
306
  ax.set_aspect("equal")
307
+ max_weight = 2 ** np.ceil(np.log2(np.abs(plot_matrices[j]).max()))
308
+
309
+ # Plot matrix elements as rectangles, using normalization for the error plot (j==2)
293
310
  for (x, y), w in np.ndenumerate(plot_matrices[j].T):
294
- size = np.sqrt(np.abs(w))
311
+ size = np.sqrt(np.abs(w) / max_weight) if j == 2 else np.sqrt(np.abs(w))
312
+ color = "#d62728" if w < 0 else "#1f77b4"
313
+
295
314
  rect = plt.Rectangle(
296
315
  [x + (1 - size) / 2, y + (1 - size) / 2],
297
316
  size,
298
317
  size,
299
- facecolor=cmap((w + 1) / 2),
300
- edgecolor=cmap((w + 1) / 2),
318
+ facecolor=color,
319
+ edgecolor=color,
301
320
  )
302
321
  ax.add_patch(rect)
322
+
303
323
  ax.invert_yaxis()
304
324
  ax.set_xticks(np.arange(dim + 1), labels=[])
305
325
  ax.set_yticks(np.arange(dim + 1), labels=[])
306
326
 
307
- if dim > 16:
308
- ax.grid(visible="True", alpha=0.4, lw=0.1)
309
- ax.set_xticks(np.arange(dim) + 0.5, minor=True, labels=basis_labels, fontsize=2, rotation=45)
327
+ grid_params = {"visible": "True", "alpha": 0.4}
328
+ if is_large_dim:
329
+ grid_params["lw"] = 0.1
330
+ x_tick_params = {"fontsize": 2, "rotation": 45}
331
+ y_tick_params = {"fontsize": 2}
310
332
  else:
311
- ax.grid(visible="True", alpha=0.4)
312
- ax.set_xticks(np.arange(dim) + 0.5, minor=True, labels=basis_labels, rotation=45)
313
- ax.set_yticks(np.arange(dim) + 0.5, minor=True, labels=basis_labels)
333
+ x_tick_params = {"rotation": 45}
334
+ y_tick_params = {}
335
+
336
+ ax.grid(**grid_params)
337
+ ax.set_xticks(np.arange(dim) + 0.5, minor=True, labels=basis_labels, **x_tick_params)
338
+ ax.set_yticks(np.arange(dim) + 0.5, minor=True, labels=basis_labels, **y_tick_params)
314
339
  ax.tick_params(which="major", length=0) # Turn dummy ticks invisible
315
340
  ax.tick_params(which="minor", top=True, labeltop=True, bottom=False, labelbottom=False, length=0)
316
341
 
317
342
  axes[0].set_title(r"G (estimate)", fontsize="large")
318
343
  axes[0].set_ylabel(gate_labels[i], rotation=90, fontsize="large")
319
344
  axes[1].set_title(r"U (ideal gate)", fontsize="large")
320
- axes[2].set_title(plot3_title, fontsize="large")
321
- fig.suptitle(f"Process matrices in the Pauli basis", va="bottom")
345
+ axes[2].set_title(plot3_title + "\n(renormalized)", fontsize="large")
346
+ fig.suptitle(f"Process matrices in the Pauli basis\n(red:<0; blue:>0)")
347
+
348
+ # Configure layout based on dimension size - reduce top margin
349
+ rect = [0, 0, 1, 0.85 if dim < 5 else 0.95]
350
+ fig.tight_layout(rect=rect)
351
+
352
+ width = 0.5 * np.sqrt(dim) if is_large_dim else 2 * np.sqrt(dim)
353
+ height = 1.3 * np.sqrt(dim) if is_large_dim else 0.8 * np.sqrt(dim)
354
+ set_size(width, height)
322
355
 
323
- if dim > 16:
324
- fig.colorbar(plt.cm.ScalarMappable(norm=norm, cmap=cmap), ax=axes.tolist(), pad=0, shrink=0.6)
325
- fig.subplots_adjust(left=0.1, right=0.76, top=0.85, bottom=0.03)
326
- set_size(0.5 * np.sqrt(dim), 1.3 * np.sqrt(dim))
327
- else:
328
- fig.colorbar(plt.cm.ScalarMappable(norm=norm, cmap=cmap), ax=axes.tolist(), pad=0)
329
- fig.subplots_adjust(left=0.1, right=0.76, top=0.85, bottom=0.03, hspace=0.2)
330
- set_size(2 * np.sqrt(dim), 0.8 * np.sqrt(dim))
331
356
  figures.append(fig)
332
357
  if not return_fig:
333
358
  plt.savefig(filename + f"G%i.pdf" % i, dpi=150, transparent=True, bbox_inches="tight")
359
+
334
360
  return figures
361
+
362
+
363
+ def plot_largest_errors(
364
+ param_delta,
365
+ param_labels,
366
+ n_errs,
367
+ threshold,
368
+ gate_label,
369
+ has_uncertainties=False,
370
+ yerr_low=None,
371
+ yerr_high=None,
372
+ param_delta_high=None,
373
+ param_delta_low=None,
374
+ ):
375
+ """Generate a bar plot showing the largest coherent errors.
376
+
377
+ Parameters
378
+ ----------
379
+ param_delta : numpy.ndarray
380
+ Difference between measured and ideal parameters
381
+ param_labels : list
382
+ Labels for the Pauli basis elements
383
+ n_errs : int
384
+ Number of largest errors to display
385
+ threshold : float
386
+ Minimum threshold for errors to display
387
+ gate_label : str
388
+ Label of the gate being visualized
389
+ has_uncertainties : bool, optional
390
+ Whether uncertainty data is available
391
+ yerr_low : numpy.ndarray, optional
392
+ Lower error bounds
393
+ yerr_high : numpy.ndarray, optional
394
+ Upper error bounds
395
+ param_delta_high : numpy.ndarray, optional
396
+ Upper bound differences
397
+ param_delta_low : numpy.ndarray, optional
398
+ Lower bound differences
399
+
400
+ Returns
401
+ -------
402
+ matplotlib.figure.Figure
403
+ The generated bar plot figure
404
+ """
405
+ # Create figure and axis
406
+ fig_bar, ax = plt.subplots(figsize=(6, 4))
407
+
408
+ # Sort indices by absolute magnitude
409
+ sorting_indices = np.argsort(np.abs(param_delta))[::-1]
410
+ param_delta_sorted = param_delta[sorting_indices]
411
+
412
+ # Apply a threshold if provided to show more than n_errs errors
413
+ if threshold is not None:
414
+ mask = np.abs(param_delta_sorted) >= threshold
415
+ n_errs = max(n_errs, np.sum(mask))
416
+
417
+ # Truncate to determined number of errors
418
+ param_delta_sorted = param_delta_sorted[:n_errs]
419
+ sorting_indices = sorting_indices[:n_errs]
420
+
421
+ # Create bars
422
+ bars = ax.bar(
423
+ range(n_errs),
424
+ param_delta_sorted,
425
+ color="#1f77b4",
426
+ alpha=0.7,
427
+ )
428
+
429
+ # Add error bars if available
430
+ if has_uncertainties:
431
+ error_low = yerr_low[sorting_indices]
432
+ error_high = yerr_high[sorting_indices]
433
+ error_positions = np.arange(n_errs)
434
+
435
+ ax.errorbar(
436
+ error_positions,
437
+ param_delta_sorted,
438
+ yerr=[error_low, error_high],
439
+ fmt="none",
440
+ ecolor=[0.2, 0.2, 0.2, 0.5],
441
+ capsize=3,
442
+ )
443
+ error_extend = np.max([np.max(np.abs(param_delta_high)), np.max(np.abs(param_delta_low))])
444
+ ax.set_ylim(-error_extend * 1.1, error_extend * 1.1)
445
+ else:
446
+ param_range = np.max(param_delta_sorted) - np.min(param_delta_sorted)
447
+ ax.set_ylim(np.min(param_delta_sorted) - param_range / 10, np.max(param_delta_sorted) + param_range / 10)
448
+
449
+ # Configure axis labels and title
450
+ ax.axhline(0, color="black", linewidth=0.8)
451
+ ax.set_xticks(range(n_errs))
452
+ ax.set_xticklabels(np.array(list(param_labels))[sorting_indices])
453
+ ax.set_xlabel("Pauli labels")
454
+ ax.set_ylabel("Deviation from target")
455
+ ax.set_title(f"Largest coherent errors for {gate_label}", fontsize=10)
456
+
457
+ # Add values on top of each bar
458
+ for i, bar_ in enumerate(bars):
459
+ value = param_delta[sorting_indices[i]]
460
+ height = bar_.get_height()
461
+ ax.annotate(
462
+ f"{value:.2e}",
463
+ xy=(bar_.get_x() + bar_.get_width() / 2, height),
464
+ xytext=(0, 2) if height > 0 else (0, -11), # vertical offset above or below the bar
465
+ textcoords="offset points",
466
+ ha="center",
467
+ va="bottom",
468
+ fontsize=9,
469
+ )
470
+
471
+ return fig_bar
472
+
473
+
474
+ def generate_hamiltonian_visualizations(
475
+ dataset: xr.Dataset, n_errs: int = 4, threshold: float = 0.01
476
+ ) -> dict[str, Figure]:
477
+ """
478
+ Plots the coherent errors as the difference of the entries of the Hamiltonian in the Pauli basis to its ideal values.
479
+ 1. Matrix plots showing all parameters with color coding
480
+ 2. Bar plots showing the largest coherent errors
481
+
482
+ Args:
483
+ dataset (xarray.Dataset): A dataset containing counts from the experiment, results, and configurations.
484
+ n_errs (int): Number of largest errors to plot in bar charts. Default is 4.
485
+ threshold (float, optional): Minimum threshold for errors to display in bar charts. The threshold can lead to
486
+ more than n_errs errors being displayed.
487
+
488
+ Returns:
489
+ Tuple[List[List[matplotlib.figure.Figure]], List[List[matplotlib.figure.Figure]]]:
490
+ A tuple containing two nested lists of figures for each qubit layout and gate:
491
+ - Matrix plots
492
+ - Bar plots
493
+ """
494
+ # Get the Hamiltonian parameters and their ideal values
495
+ hamiltonian_params, hamiltonian_params_ideal = compute_matched_ideal_hamiltonian_params(dataset)
496
+ param_labels = generate_basis_labels(dataset.attrs["pdim"], basis="Pauli")
497
+ gate_labels = list(dataset.attrs["gate_labels"].values())
498
+
499
+ # Collect upper and lower end of the confidence intervals
500
+ qubit_layouts = dataset.attrs["qubit_layouts"]
501
+ has_uncertainties = all(
502
+ dataset.attrs[f"results_layout_{BenchmarkObservationIdentifier(layout).string_identifier}"][
503
+ "hamiltonian_params"
504
+ ].get("uncertainties")
505
+ is not None
506
+ for layout in qubit_layouts
507
+ )
508
+
509
+ if has_uncertainties:
510
+ hamiltonian_params_low = np.array(
511
+ [
512
+ dataset.attrs[f"results_layout_{BenchmarkObservationIdentifier(layout).string_identifier}"][
513
+ "hamiltonian_params"
514
+ ]["uncertainties"][0]
515
+ for layout in qubit_layouts
516
+ ]
517
+ )
518
+ hamiltonian_params_high = np.array(
519
+ [
520
+ dataset.attrs[f"results_layout_{BenchmarkObservationIdentifier(layout).string_identifier}"][
521
+ "hamiltonian_params"
522
+ ]["uncertainties"][1]
523
+ for layout in qubit_layouts
524
+ ]
525
+ )
526
+ else:
527
+ # Create dummy arrays if no uncertainties are available
528
+ hamiltonian_params_low = hamiltonian_params.copy()
529
+ hamiltonian_params_high = hamiltonian_params.copy()
530
+
531
+ plots = {}
532
+
533
+ for l_idx, layout in enumerate(qubit_layouts):
534
+ # Iterate through each layout's parameters
535
+ for params, params_ideal, params_low, params_high, gate_label in zip(
536
+ hamiltonian_params[l_idx],
537
+ hamiltonian_params_ideal[l_idx],
538
+ hamiltonian_params_low[l_idx],
539
+ hamiltonian_params_high[l_idx],
540
+ gate_labels[l_idx].values(),
541
+ ):
542
+
543
+ # Generate figures for each gate in the layout
544
+ param_delta = params - params_ideal
545
+ param_delta_low = params_low - params_ideal if has_uncertainties else param_delta
546
+ param_delta_high = params_high - params_ideal if has_uncertainties else param_delta
547
+
548
+ # Get uncertainties as difference vectors for error bars
549
+ yerr_low = np.abs(param_delta - param_delta_low) if has_uncertainties else None
550
+ yerr_high = np.abs(param_delta_high - param_delta) if has_uncertainties else None
551
+
552
+ params_reshaped = params.reshape(-1, 1)
553
+ params_low_reshaped = params_low.reshape(-1, 1)
554
+ params_high_reshaped = params_high.reshape(-1, 1)
555
+ shape = params_reshaped.shape
556
+ max_size = 16
557
+ num_splits = int(np.ceil(shape[0] / max_size))
558
+
559
+ # Split param_vector into a smaller array to plot
560
+ param_splits = np.array_split(params_reshaped, num_splits, axis=0)
561
+ param_splits_low = np.array_split(params_low_reshaped, num_splits, axis=0)
562
+ param_splits_high = np.array_split(params_high_reshaped, num_splits, axis=0)
563
+
564
+ fig_matrix, axes = plt.subplots(len(param_splits), 1, figsize=(10, len(param_splits)))
565
+ if len(param_splits) == 1:
566
+ axes = [axes]
567
+
568
+ for idx, (param_split, param_split_low, param_split_high, ax) in enumerate(
569
+ zip(param_splits, param_splits_low, param_splits_high, axes)
570
+ ):
571
+ split_size = param_split.shape[0]
572
+ im = ax.matshow(param_split.T, cmap="coolwarm", vmin=-0.05, vmax=0.05)
573
+ ax.set_yticks(np.arange(1), labels=[], rotation=0)
574
+ ax.set_xticks(
575
+ np.arange(split_size), labels=list(param_labels)[idx * split_size : (idx + 1) * split_size]
576
+ )
577
+ for ind_combined, (param_value, param_split_high, param_split_low) in enumerate(
578
+ zip(param_split.reshape(-1), param_split_high.reshape(-1), param_split_low.reshape(-1))
579
+ ):
580
+ i, j = divmod(ind_combined, param_split.shape[1])
581
+ if has_uncertainties:
582
+
583
+ # Display upper bound above the parameter value
584
+ ax.text(
585
+ i, j - 0.2, f"{param_split_high:.1e}", va="center", ha="center", color="black", fontsize=6
586
+ )
587
+ # Display parameter value in the middle
588
+ ax.text(i, j, f"{param_value:.1e}", va="center", ha="center", color="black", fontweight="bold", fontsize=6)
589
+ # Display lower bound below the parameter value
590
+ ax.text(
591
+ i, j + 0.2, f"{param_split_low:.1e}", va="center", ha="center", color="black", fontsize=6
592
+ )
593
+ else:
594
+ # Just display the parameter value if no uncertainties
595
+ ax.text(i, j, f"{param_value:.1e}", va="center", ha="center", color="black", fontsize=6)
596
+
597
+ plt.title(f"Hamiltonian parameters for {gate_label}", fontsize=10)
598
+
599
+ fig_matrix.colorbar(im, ax=axes, fraction=0.005, pad=0.04)
600
+ plots.update({f"layout_{layout}_{gate_label}_Hamiltonian": fig_matrix})
601
+ plt.close(fig_matrix)
602
+
603
+ fig_bar = plot_largest_errors(
604
+ param_delta,
605
+ param_labels,
606
+ n_errs,
607
+ threshold,
608
+ gate_label,
609
+ has_uncertainties=has_uncertainties,
610
+ yerr_low=yerr_low,
611
+ yerr_high=yerr_high,
612
+ param_delta_high=param_delta_high,
613
+ param_delta_low=param_delta_low,
614
+ )
615
+ plots.update({f"layout_{layout}_{gate_label}_largest_coherent_errs": fig_bar})
616
+ plt.close(fig_bar)
617
+
618
+ return plots
619
+
620
+
621
+ def dataframe_to_figure(
622
+ df: DataFrame, row_labels: Union[List[str], None] = None, col_width: float = 2, fontsize: int = 12
623
+ ) -> Figure:
624
+ """Turns a pandas DataFrame into a figure
625
+ This is needed to conform with the standard file saving routine of QCVV.
626
+
627
+ Args:
628
+ df: Pandas DataFrame
629
+ A dataframe table containing GST results
630
+ row_labels: List[str]
631
+ The row labels for the dataframe
632
+ col_width: int
633
+ Used to control cell width in the table
634
+ fontsize: int
635
+ Font size of text/numbers in table cells
636
+
637
+ Returns:
638
+ figure: Matplotlib figure object
639
+ A figure representing the dataframe.
640
+ """
641
+
642
+ if row_labels is None:
643
+ row_labels = list(np.arange(df.shape[0]))
644
+
645
+ row_height = fontsize / 70 * 2
646
+ n_cols = df.shape[1]
647
+ n_rows = df.shape[0]
648
+ figsize = np.array([n_cols + 1, n_rows + 1]) * np.array([col_width, row_height])
649
+
650
+ fig, ax = plt.subplots(figsize=figsize)
651
+
652
+ fig.patch.set_visible(False)
653
+ ax.axis("off")
654
+ ax.axis("tight")
655
+ data_array = (df.to_numpy(dtype="str")).copy()
656
+ column_names = df.columns.tolist()
657
+ table = ax.table(
658
+ cellText=data_array,
659
+ colLabels=column_names,
660
+ rowLabels=row_labels,
661
+ cellLoc="center",
662
+ colColours=["#7FA1C3" for _ in range(n_cols)],
663
+ bbox=Bbox([[0, 0], [1, 1]]),
664
+ )
665
+ table.set_fontsize(fontsize)
666
+ table.set_figure(fig)
667
+ return fig