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.
@@ -4,7 +4,9 @@ Generation of error measures and result tables
4
4
 
5
5
  from argparse import Namespace
6
6
  import csv
7
+ from itertools import product
7
8
  import os
9
+ from typing import List, Tuple, Union
8
10
 
9
11
  import numpy as np
10
12
  import numpy.linalg as la
@@ -17,10 +19,12 @@ from pygsti.tools import change_basis
17
19
  from pygsti.tools.optools import compute_povm_map
18
20
  from qiskit.quantum_info import SuperOp
19
21
  from qiskit.quantum_info.operators.measures import diamond_norm
20
- from scipy.linalg import logm, schur
22
+ from scipy.linalg import expm, logm, schur
21
23
  from scipy.optimize import linear_sum_assignment, minimize
24
+ import xarray as xr
22
25
 
23
- from mGST import additional_fns, algorithm, compatibility, low_level_jit
26
+ from iqm.benchmarks.benchmark_definition import BenchmarkObservationIdentifier
27
+ from mGST import additional_fns, algorithm, compatibility, low_level_jit, qiskit_interface
24
28
 
25
29
 
26
30
  def min_spectral_distance(X1, X2):
@@ -135,6 +139,33 @@ def gauge_opt(X, E, rho, target_mdl, weights):
135
139
  return compatibility.pygsti_model_to_arrays(gauge_optimized_mdl, basis="std")
136
140
 
137
141
 
142
+ def generate_basis_labels(pdim: int, basis: Union[str, None] = None) -> List[str]:
143
+ """Generate a list of labels for the Pauli basis or the standard basis
144
+
145
+ Args:
146
+ pdim: int
147
+ Physical dimension
148
+ basis: str
149
+ Which basis the labels correspond to, currently default is standard basis and "Pauli" can be choose
150
+ for Pauli basis labels like "II", "IX", "XX", ...
151
+
152
+ Returns:
153
+ labels: List[str]
154
+ A list of all string combinations for the given dimension and basis
155
+ """
156
+ separator = ""
157
+ if basis == "Pauli":
158
+ pauli_labels_loc = ["I", "X", "Y", "Z"]
159
+ pauli_labels_rep = [pauli_labels_loc for _ in range(int(np.log2(pdim)))]
160
+ labels = [separator.join(map(str, x)) for x in product(*pauli_labels_rep)]
161
+ else:
162
+ std_labels_loc = ["0", "1"]
163
+ std_labels_rep = [std_labels_loc for _ in range(int(np.log2(pdim)))]
164
+ labels = [separator.join(map(str, x)) for x in product(*std_labels_rep)]
165
+
166
+ return labels
167
+
168
+
138
169
  def report(X, E, rho, J, y, target_mdl, gate_labels):
139
170
  """Generation of pandas dataframes with gate and SPAM quality measures
140
171
  The resutls can be converted to .tex tables or other formats to be used for GST reports
@@ -163,8 +194,6 @@ def report(X, E, rho, J, y, target_mdl, gate_labels):
163
194
  DataFrame of gate quality measures
164
195
  df_o : Pandas DataFrame
165
196
  DataFrame of all other quality/error measures
166
- s_g : Pandas DataFrame.style object
167
- s_o : Pandas DataFrame.style object
168
197
  """
169
198
  pdim = int(np.sqrt(rho.shape[0]))
170
199
  X_t, E_t, rho_t = compatibility.pygsti_model_to_arrays(target_mdl, basis="std")
@@ -231,8 +260,6 @@ def quick_report(X, E, rho, J, y, target_mdl, gate_labels=None):
231
260
  DataFrame of gate quality measures
232
261
  df_o : Pandas DataFrame
233
262
  DataFrame of all other quality/error measures
234
- s_g : Pandas DataFrame.style object
235
- s_o : Pandas DataFrame.style object
236
263
  """
237
264
  pdim = int(np.sqrt(rho.shape[0]))
238
265
  d = X.shape[0]
@@ -359,6 +386,155 @@ def compute_sparsest_Pauli_Hamiltonian(U_set):
359
386
  return pauli_coeffs
360
387
 
361
388
 
389
+ def match_hamiltonian_phase(pauli_coeffs, pauli_coeffs_target):
390
+ """
391
+ Matches the sign of the phase of a target gate Hamiltonian represented by Pauli coefficients to a measured Hamiltonian.
392
+
393
+ Parameters:
394
+ pauli_coeffs (numpy array): Pauli coefficients of the measured Hamiltonian.
395
+ pauli_coeffs_target (numpy array): Pauli coefficients of the target Hamiltonian.
396
+
397
+ Returns:
398
+ numpy array: Pauli coefficients of the target Hamiltonian with matched phase.
399
+ """
400
+ dim = int(np.sqrt(len(pauli_coeffs)))
401
+ H_target = change_basis(pauli_coeffs_target, "pp", "std").reshape((dim, dim))
402
+ U_target = expm(-1j * H_target * np.pi * np.sqrt(dim) / 2)
403
+ U_target_alt = expm(1j * H_target * np.pi * np.sqrt(dim) / 2)
404
+
405
+ # Check if sign flip leads to same unitary
406
+ if np.linalg.norm(U_target - U_target_alt) < 1e-6:
407
+ # Check if sign flip leads to better match to target Hamiltonian in Pauli basis
408
+ if np.linalg.norm(pauli_coeffs_target - pauli_coeffs) > np.linalg.norm(pauli_coeffs_target + pauli_coeffs):
409
+ return -pauli_coeffs_target
410
+
411
+ return pauli_coeffs_target
412
+
413
+
414
+ def generate_rotation_param_results(
415
+ dataset: xr.Dataset,
416
+ qubit_layout: List[int],
417
+ X_opt: np.ndarray,
418
+ K_target: np.ndarray,
419
+ X_array: np.ndarray = None,
420
+ E_array: np.ndarray = None,
421
+ rho_array: np.ndarray = None,
422
+ ) -> Tuple[pd.DataFrame, dict]:
423
+ """
424
+ Produces result tables and data for Kraus rank 1 estimates.
425
+
426
+ This includes parameters of the Hamiltonian generators in the Pauli basis for all gates.
427
+ If bootstrapping data is available, error bars will also be generated.
428
+
429
+ Args:
430
+ dataset: xarray.Dataset
431
+ A dataset containing counts from the experiment and configurations
432
+ qubit_layout: List[int]
433
+ The list of qubits for the current GST experiment
434
+ X_opt: 3D numpy array
435
+ The gate set after gauge optimization
436
+ K_target: 4D numpy array
437
+ The Kraus operators of all target gates, used to compute distance measures.
438
+ X_array: ndarray, optional
439
+ Array of bootstrap gate estimates, used for error bars
440
+ E_array: ndarray, optional
441
+ Array of bootstrap POVM estimates, used for error bars
442
+ rho_array: ndarray, optional
443
+ Array of bootstrap state estimates, used for error bars
444
+
445
+ Returns:
446
+ Tuple[DataFrame, dict]:
447
+ - df_g_rotation: Pandas DataFrame containing Hamiltonian (rotation) parameters with formatted values
448
+ - hamiltonian_params: Dictionary with raw values and uncertainty bounds if bootstrapping was used
449
+ """
450
+ identifier = BenchmarkObservationIdentifier(qubit_layout).string_identifier
451
+ pauli_labels = generate_basis_labels(dataset.attrs["pdim"], basis="Pauli")
452
+
453
+ U_opt = phase_opt(X_opt, K_target)
454
+ pauli_coeffs = compute_sparsest_Pauli_Hamiltonian(U_opt)
455
+
456
+ bootstrap = X_array is not None and E_array is not None and rho_array is not None
457
+
458
+ if bootstrap:
459
+ bootstrap_pauli_coeffs = np.zeros((len(X_array), dataset.attrs["num_gates"], dataset.attrs["pdim"] ** 2))
460
+ for i, X_ in enumerate(X_array):
461
+ X_std, _, _ = compatibility.pp2std(X_, E_array[i], rho_array[i])
462
+ U_opt_ = phase_opt(X_std, K_target)
463
+ pauli_coeffs_ = compute_sparsest_Pauli_Hamiltonian(U_opt_)
464
+ bootstrap_pauli_coeffs[i, :, :] = pauli_coeffs_
465
+ pauli_coeffs_low, pauli_coeffs_high = np.nanpercentile(bootstrap_pauli_coeffs, [2.5, 97.5], axis=0)
466
+
467
+ # Pandas dataframe with formated confidence intervals
468
+ df_g_rotation = pd.DataFrame(
469
+ np.array(
470
+ [
471
+ [
472
+ number_to_str(
473
+ pauli_coeffs[i, j],
474
+ [pauli_coeffs_high[i, j], pauli_coeffs_low[i, j]] if bootstrap else None,
475
+ precision=5,
476
+ )
477
+ for i in range(dataset.attrs["num_gates"])
478
+ ]
479
+ for j in range(dataset.attrs["pdim"] ** 2)
480
+ ]
481
+ ).T
482
+ )
483
+ df_g_rotation.columns = [f"h_%s" % label for label in pauli_labels]
484
+ df_g_rotation.rename(index=dataset.attrs["gate_labels"][identifier], inplace=True)
485
+
486
+ hamiltonian_params = {
487
+ "values": pauli_coeffs,
488
+ "uncertainties": (pauli_coeffs_low, pauli_coeffs_high) if bootstrap else None,
489
+ }
490
+
491
+ return df_g_rotation, hamiltonian_params
492
+
493
+
494
+ def compute_matched_ideal_hamiltonian_params(dataset: xr.Dataset) -> Tuple[np.ndarray, np.ndarray]:
495
+ """
496
+ Computes the Hamiltonian parameters and matches the ideal Hamiltonian parameters to the measured ones (without changing the unitary it generates).
497
+
498
+ Args:
499
+ dataset (xarray.Dataset): A dataset containing counts from the experiment and configurations.
500
+
501
+ Returns:
502
+ Tuple[numpy.ndarray, numpy.ndarray]: A tuple containing:
503
+ - hamiltonian_params: Hamiltonian parameters of the measured gates.
504
+ - hamiltonian_params_ideal_matched: Ideal Hamiltonian parameters matched to the measured ones.
505
+ """
506
+
507
+ qubit_layouts = dataset.attrs["qubit_layouts"]
508
+ param_list_layouts = [
509
+ dataset.attrs[f"results_layout_{BenchmarkObservationIdentifier(layout).string_identifier}"][
510
+ "hamiltonian_params"
511
+ ]["values"]
512
+ for layout in qubit_layouts
513
+ ]
514
+ hamiltonian_params = np.array(param_list_layouts)
515
+
516
+ K_target = qiskit_interface.qiskit_gate_to_operator(dataset.attrs["gate_set"])
517
+ X_target = np.einsum("ijkl,ijnm -> iknlm", K_target, K_target.conj()).reshape(
518
+ (dataset.attrs["num_gates"], dataset.attrs["pdim"] ** 2, dataset.attrs["pdim"] ** 2)
519
+ )
520
+
521
+ param_list_layouts = []
522
+ for qubit_layout in qubit_layouts:
523
+ _, ideal_params = generate_rotation_param_results(dataset, qubit_layout, X_target, K_target)
524
+ ideal_params = ideal_params["values"]
525
+ param_list_layouts.append(ideal_params)
526
+ hamiltonian_params_ideal = np.array(param_list_layouts)
527
+
528
+ hamiltonian_params_ideal_matched = np.empty(hamiltonian_params_ideal.shape)
529
+ for i, _ in enumerate(qubit_layouts):
530
+ for j in range(hamiltonian_params_ideal.shape[1]):
531
+ hamiltonian_params_ideal_matched[i, j, :] = match_hamiltonian_phase(
532
+ hamiltonian_params[i, j, :], hamiltonian_params_ideal[i, j, :]
533
+ )
534
+
535
+ return hamiltonian_params, hamiltonian_params_ideal_matched
536
+
537
+
362
538
  def phase_err(angle, U, U_t):
363
539
  """Computes norm between two input unitaries after a global phase is added to one of them
364
540
 
@@ -379,7 +555,7 @@ def phase_err(angle, U, U_t):
379
555
  return la.norm(np.exp(1j * angle) * U - U_t)
380
556
 
381
557
 
382
- def phase_opt(X, K_t):
558
+ def phase_opt(X: np.ndarray, K_t: np.ndarray):
383
559
  """Return rK = 1 gate set with global phase fitting matching to target gate set
384
560
 
385
561
  Parameters
@@ -543,12 +719,12 @@ def bootstrap_errors(K, X, E, rho, mGST_args, bootstrap_samples, weights, gate_l
543
719
 
544
720
  Parameters
545
721
  ----------
546
- K : numpy array
722
+ K: numpy array
547
723
  Each subarray along the first axis contains a set of Kraus operators.
548
724
  The second axis enumerates Kraus operators for a gate specified by the first axis.
549
- X : 3D numpy array
725
+ X: 3D numpy array
550
726
  Array where reconstructed CPT superoperators in standard basis are stacked along the first axis.
551
- E : numpy array
727
+ E: numpy array
552
728
  Current POVM estimate
553
729
  rho : numpy array
554
730
  Current initial state estimate
@@ -611,7 +787,6 @@ def bootstrap_errors(K, X, E, rho, mGST_args, bootstrap_samples, weights, gate_l
611
787
  threshold_multiplier=ns.threshold_multiplier,
612
788
  target_rel_prec=ns.target_rel_prec,
613
789
  init=[K, E, rho],
614
- testing=False,
615
790
  )
616
791
 
617
792
  X_opt, E_opt, rho_opt = gauge_opt(X_, E_, rho_, target_mdl, weights)
@@ -711,3 +886,26 @@ def number_to_str(number, uncertainty=None, precision=3):
711
886
  return f"{number:.{precision}f}"
712
887
 
713
888
  return f"{number:.{precision}f} [{uncertainty[1]:.{precision}f},{uncertainty[0]:.{precision}f}]"
889
+
890
+
891
+ def result_str_to_floats(result_str: str, err: str) -> Tuple[float, float]:
892
+ """Converts formated string results from mgst to float (value, uncertainty) pairs
893
+
894
+ Args:
895
+ result_str: str
896
+ The value of a result parameter formated as str
897
+ err: str
898
+ The error interval of the parameters
899
+
900
+ Returns:
901
+ value: float
902
+ The parameter value as float
903
+ uncertainty: float
904
+ A single uncertainty value
905
+ """
906
+ if err:
907
+ value = float(result_str.split("[")[0])
908
+ rest = result_str.split("[")[1].split(",")
909
+ uncertainty = float(rest[1][:-1]) - float(rest[0])
910
+ return value, uncertainty
911
+ return float(result_str), np.NaN