1010from numpy import asarray , ndarray , shape , zeros , empty , average
1111
1212import random , os , pickle , sys , errno
13+ from dataclasses import dataclass
1314
1415abs_path = os .path .dirname (__file__ )
1516sys .path .append (abs_path )
4142"""
4243
4344
45+ # Dataclasses:
46+
47+ @dataclass (frozen = True )
48+ class NoiseAmplifiedResult :
49+ amplification_factor : int
50+ exp_val : float
51+ circuit : QuantumCircuit
52+ depth : int
53+ shots : int
54+
55+
56+ @dataclass (frozen = True )
57+ class ZeroNoiseExtrapolationResult :
58+ result : float
59+ circuit : QuantumCircuit
60+ all_exp_vals : ndarray
61+ noise_amplified_exp_vals : ndarray
62+ depths : ndarray
63+
64+
65+ # Zero-noise extrapolation class:
4466class ZeroNoiseExtrapolation :
4567
4668 def __init__ (self , qc : QuantumCircuit , exp_val_func : Callable , backend = None , exp_val_filter = None ,
@@ -62,7 +84,7 @@ def __init__(self, qc: QuantumCircuit, exp_val_func: Callable, backend=None, exp
6284 quantum circuit.
6385 The function should take two arguments: a list of qiskit.result.result.ExperimentResult objects as its first
6486 argument, and a possible filter as its second.
65- The function should return: An array
87+ The function should return: An array of computed expectation values
6688
6789 backend : A valid qiskit backend, IBMQ device or simulator
6890 A qiskit backend, either an IBMQ quantum backend or a simulator backend, for circuit executions.
@@ -162,8 +184,9 @@ def __init__(self, qc: QuantumCircuit, exp_val_func: Callable, backend=None, exp
162184
163185 # Will store expectation values for each individual circuit execution
164186 self .all_exp_vals = zeros (0 )
165- # Will store expectation values for each noise amplified circuit, averaged over all sub-executions
187+ # Will store expectation values and variances from the execution of each noise amplified circuit
166188 self .noise_amplified_exp_vals = zeros (0 )
189+ self .noise_amplified_variances = zeros (0 )
167190
168191 self .result = None
169192
@@ -172,8 +195,8 @@ def __init__(self, qc: QuantumCircuit, exp_val_func: Callable, backend=None, exp
172195 def partition_shots (self , tot_shots : int ) -> (int , int ):
173196 """
174197 IBMQ devices limits circuit executions to a max of 8192 shots per experiment. To perform more than 8192 shots,
175- the experiment has to be repeated. Therefore, if shots > 8192, we partition the execution into several repeats
176- of less than 8192 shots each.
198+ the experiment has to be partitioned into a set of circuit executions, each with less than 8192 shots.
199+ Therefore, if shots > 8192, we partition the execution into several repeats of less than 8192 shots each.
177200
178201 Parameters
179202 ----------
@@ -304,7 +327,7 @@ def noise_amplify_and_pauli_twirl_cnots(self, qc: QuantumCircuit, amp_factor: in
304327 # The 'Unroller' transpiler pass 'unrolls' (decomposes) the circuit gates to be expressed in terms of the
305328 # physical gate set [u1,u2,u3,cx]
306329
307- # The cz, cy (controlled-Z and -Y) gates can be constructed from a single cx-gate and sinlge -qubit gates.
330+ # The cz, cy (controlled-Z and -Y) gates can be constructed from a single cx-gate and single -qubit gates.
308331 # For backends with native gate sets consisting of some set of single-qubit gates and either the cx, cz or cy,
309332 # unrolling the circuit to the ["u3", "cx"] basis, amplifying the cx-gates, then unrolling back to the native
310333 # gate set and doing a single-qubit optimization transpiler pass, is thus still general.
@@ -369,7 +392,7 @@ def transpile_circuit(self, qc: QuantumCircuit, custom_pass_manager: PassManager
369392 The transpiled quantum circuit.
370393 """
371394
372- if custom_pass_manager == None :
395+ if custom_pass_manager is None :
373396 pass_manager_config = PassManagerConfig (basis_gates = ["id" , "u1" , "u2" , "u3" , "cx" ],
374397 backend_properties = self .backend .properties ())
375398 if not self .is_simulator :
@@ -393,6 +416,9 @@ def transpile_circuit(self, qc: QuantumCircuit, custom_pass_manager: PassManager
393416
394417 def execute_circuit (self , qc : QuantumCircuit , shots = None ) -> Result :
395418 """
419+ Execute a single experiment consisting of the execution of a quantum circuit over a specified number of shots.
420+ One experiment may need to be partitioned into a set of several identical circuit executions. This is due to the
421+ IBMQ quantum devices limiting circuit executions to a maximum of 8192 shots per.
396422
397423 Parameters
398424 ----------
@@ -429,28 +455,51 @@ def execute_circuit(self, qc: QuantumCircuit, shots=None) -> Result:
429455
430456 return circuit_measurement_results
431457
432- def compute_exp_val (self , result : Result ) -> (float , ndarray ):
458+ def compute_exp_val (self , result : Result ) -> (float , float , ndarray ):
433459 """
434- Compute the expectation value for
460+ Compute the expectation value and variance for a set of circuit executions. We assume that all separate circuit
461+ execution was run with the same number of shots.
435462
436463 Parameters
437464 ----------
438465 result : qiskit.result.result.Result
439466 A qiskit Result object containing all measurement results from a set of quantum circuit executions.
467+
440468 Returns
441469 -------
442- averaged_experiment_exp_vals, experiment_exp_vals : Tuple[float, numpy.ndarray]
443- The final experiment expectation value, averaged over all circuit sub-executions, and a numpy array
444- containing the expectation values for each circuit sub-execution.
470+ averaged_experiment_exp_vals, averaged_experiment_variances experiment_exp_vals : Tuple[float, numpy.ndarray]
471+ The final estimated experiment expectation value and variance , averaged over all circuit sub-executions,
472+ and a numpy array containing the expectation values for each circuit sub-execution.
445473 """
446474
447475 experiment_results = result .results
448476
449- experiment_exp_vals = self .exp_val_func (experiment_results , self .exp_val_filter )
477+ experiment_exp_vals , experiment_variances = self .exp_val_func (experiment_results , self .exp_val_filter )
478+
479+ return average (experiment_exp_vals ), average (experiment_variances ), asarray (experiment_exp_vals )
450480
451- return average (experiment_exp_vals ), asarray (experiment_exp_vals )
481+ def compute_noise_amplified_exp_val (self ):
482+ return
483+
484+ def sample_error (self ):
485+ return
452486
453487 def extrapolate (self , n_amp_factors : int = None , noise_amplified_exp_vals : ndarray = None ) -> float :
488+ """
489+ Perform the extrapolation step of the zero-noise extrapolation.
490+
491+ Parameters
492+ ----------
493+ n_amp_factors: int
494+ The number of amplification factors used.
495+ noise_amplified_exp_vals:
496+ The noise amplified expectation value from which to extrapolate to the zero-noise case
497+
498+ Returns
499+ -------
500+ result: float
501+ The resulting mitigated expectation value.
502+ """
454503 if noise_amplified_exp_vals is None :
455504 noise_amplified_exp_vals = self .noise_amplified_exp_vals
456505 if n_amp_factors is None :
@@ -488,6 +537,7 @@ def mitigate(self, verbose: bool = False) -> float:
488537 n_amp_factors = shape (self .noise_amplification_factors )[0 ]
489538
490539 self .noise_amplified_exp_vals = zeros ((n_amp_factors ,))
540+ self .noise_amplified_variances = zeros ((n_amp_factors ,))
491541 self .all_exp_vals = zeros ((n_amp_factors , self .repeats ))
492542
493543 if verbose :
@@ -541,8 +591,8 @@ def mitigate(self, verbose: bool = False) -> float:
541591 if verbose :
542592 print ("Results successfully written to disk." )
543593
544- self .noise_amplified_exp_vals [i ], self .all_exp_vals [i , :] = self . compute_exp_val (
545- circuit_measurement_results )
594+ self .noise_amplified_exp_vals [i ], self .noise_amplified_variances [ i ], self . all_exp_vals [i , :] \
595+ = self . compute_exp_val ( circuit_measurement_results )
546596
547597 self .measurement_results .append (circuit_measurement_results )
548598
@@ -827,20 +877,20 @@ def noise_amplify_cnots(qc: QuantumCircuit, amp_factor: int):
827877 # unrolling the circuit to the ["u3", "cx"] basis, amplifying the cx-gates, then unrolling back to the native
828878 # gate set and doing a single-qubit optimization transpiler pass, is thus still general.
829879
830- unroller_ugatesandcx = Unroller (["u1" , "u2" , "u3 " , "cx" ])
880+ unroller_ugatesandcx = Unroller (["u " , "cx" ])
831881 pm = PassManager (unroller_ugatesandcx )
832882
833883 unrolled_qc = pm .run (qc )
834884
835885 circuit_qasm = unrolled_qc .qasm ()
836886 new_circuit_qasm_str = ""
837887
838- qreg_name = find_qreg_name (circuit_qasm )
888+ # qreg_name = find_qreg_name(circuit_qasm)
839889
840890 for i , line in enumerate (circuit_qasm .splitlines ()):
841891 if line [0 :2 ] == "cx" :
842892 for j in range (amp_factor ):
843- new_circuit_qasm_str += pauli_twirl_cnot_gate ( qreg_name , line )
893+ new_circuit_qasm_str += line + " \n "
844894 else :
845895 new_circuit_qasm_str += line + "\n "
846896
0 commit comments