1818expectation value to the zero-noise limit.
1919
2020The noise amplified and mitigated is specifically noise in CNOT-gates. The noise is amplified by n amplification
21- factors 1, 3, 5, ..., 2n + 1, where 1 means the bare circuit without noise amplification, and 3 means every
22- CNOT gate is extended as CNOT*CNOT*CNOT.
21+ factors c=1, 3, 5, ..., 2n + 1 by repeating each CNOT gates c times. E.g. c=1 corresponds to the bare circuit and
22+ c=3 corresponds to each CNOT gate replaced by CNOT*CNOT*CNOT. Each CNOT acting on the same control- and target-qubits
23+ as the original CNOT-gate.
2324
24- As CNOT*CNOT = Id, the identity, in the noise-free case the amplified CNOT have the same action as a single CNOT,
25- but ~3 times the noise.
25+ As CNOT*CNOT = Id, the identity, in the noise-free case the amplified CNOT (eqaul to CNOT^c) have the same action as a
26+ single CNOT. In the noise-afflicted case, the action will be close to that of a single CNOT for a weak noise, but with
27+ the CNOT-noise applied c times to the qubit throughout the process.
2628
2729"""
2830
@@ -52,7 +54,8 @@ def __init__(self, qc: QuantumCircuit, exp_val_func, backend=None, noise_model=N
5254 self .backend = backend
5355 self .is_simulator = backend .configuration ().simulator
5456
55- # Do an initial heavy optimization of the input circuit
57+ # Do an initial optimization of the quantum circuit. If no custom pass manager is passer, the optimizaiton is
58+ # done with the optimization_level=3 preset for the qiskit transpiler (heaviest optimization)
5659 self .qc = self .transpile_circuit (qc , custom_pass_manager = pass_manager )
5760
5861 self .exp_val_func = exp_val_func
@@ -83,7 +86,7 @@ def __init__(self, qc: QuantumCircuit, exp_val_func, backend=None, noise_model=N
8386 self .all_exp_vals = zeros (0 )
8487 self .mitigated_exp_vals = zeros (0 )
8588
86- self .result = None
89+ self .measurement_results = []
8790
8891 def set_shots (self , shots : int ):
8992 if shots <= 8192 :
@@ -109,18 +112,19 @@ def noise_amplify_and_pauli_twirl_cnots(self, qc: QuantumCircuit, amp_factor: in
109112 """
110113
111114 if (amp_factor - 1 ) % 2 != 0 :
112- print ("Invalid amplification factor: " , amp_factor )
115+ raise Exception ("Invalid amplification factors " , amp_factor )
113116
114117 # The circuit may be expressed in terms of various types of gates.
115118 # The 'Unroller' transpiler pass 'unrolls' (decomposes) the circuit gates to be expressed in terms of the
116119 # physical gate set [u1,u2,u3,cx]
117120
118- # This is still general for backends with possibly different native gate sets, as we can still express the
119- # circuit, and do the noise amplification + pauli twirling, in terms of the [u1,u2,u3,cx] gate set, and then
120- # if necessary convert to the native gate set of the backend at a later point
121+ # The cz, cy (controlled-Z and -Y) gates can be constructed from a single cx-gate and sinlge-qubit gates.
122+ # For backends with native gate sets consisting of some set of single-qubit gates and either the cx, cz or cy,
123+ # unrolling the circuit to the ["u3", "cx"] basis, amplifying the cx-gates, then unrolling back to the native
124+ # gate set and doing a single-qubit optimization transpiler pass, is thus still general.
121125
122- unroller = Unroller (["u1" ,"u2" ,"u3" ,"cx" ])
123- pm = PassManager (unroller )
126+ unroller_ugatesandcx = Unroller (["u1" ,"u2" ,"u3" ,"cx" ])
127+ pm = PassManager (unroller_ugatesandcx )
124128
125129 unrolled_qc = pm .run (qc )
126130
@@ -141,26 +145,30 @@ def noise_amplify_and_pauli_twirl_cnots(self, qc: QuantumCircuit, amp_factor: in
141145
142146 new_qc = QuantumCircuit .from_qasm_str (new_circuit_qasm_str )
143147
144- # The "Optimize1qGates" transpiler pass optimizes chains of single-qubit gates by collapsing them into
145- # a single, equivalent u3-gate. We want to collapse unnecessary single-qubit gates, but not CNOT-gates, as
146- # these give us the noise amplification.
148+ # The "Optimize1qGates" transpiler pass optimizes adjacent single-qubit gates, for a native gate set with the
149+ # u3 gates it collapses any chain of adjacent single-qubit gates into a single, equivalent u3-gate.
150+ # We want to collapse unnecessary single-qubit gates to minimize circuit depth, but not CNOT-gates
151+ # as these give us the noise amplification.
152+ unroller_backendspecific = Unroller (self .backend .configuration ().coupling_map ())
147153 optimize1qates = Optimize1qGates ()
148- pm = PassManager (optimize1qates )
154+
155+ pm = PassManager ([unroller_backendspecific , optimize1qates ])
149156
150157 return pm .run (new_qc )
151158
152159 def transpile_circuit (self , qc : QuantumCircuit , custom_pass_manager : PassManager = None ) -> QuantumCircuit :
153160 """
154- Transpile and optimize the quantum circuit using qiskits PassManager class and the level_3_pass_manager,
155- which contains the transpiler passes for the optimalization level 3 preset ( the preset with highest
156- level of optimization) .
161+ Transpile and optimize the input circuit, optionally using a custom pass manager.
162+ If no custom pass manager is given, use the optimization_level 3 preset for the qiskit transpiler,
163+ which gives heaviest circuit optimization.
157164
158165 As we want to add additional CNOTs for noise amplification and possibly additional single qubit gates
159- for Pauli twirling, we need to transpile the circuit before, to avoid the additional gates to be removed
160- by the transpiler.
166+ for Pauli twirling, we need to transpile the circuit before both the noise amplification is applied and
167+ before circuit execution. This is to avoid the additional CNOT-gates beinng removed by the transpiler.
161168
162169 The Optimize1qGates transpiler pass will be used later to optimize single qubit gates added during
163- the Pauli-twirling
170+ the Pauli-twirling, as well as the Unroller pass which merely decomposes the given circuit gates into
171+ the given set of basis gates.
164172
165173 :return: The transpiled circuit
166174 """
@@ -205,7 +213,7 @@ def execute_circuits(self, circuits: list) -> list:
205213 result = execute (execution_circuits , backend = self .backend ,
206214 pass_manager = PassManager (), shots = self .shots ).result ()
207215
208- self .result = result # Saving the result in a member variable. Might remove.
216+ self .measurement_results . append ( result ) # Saving the result in a member variable. Might remove.
209217
210218 return result .get_counts ()
211219
@@ -293,8 +301,8 @@ def mitigate(self, repeats: int = 1, verbose: bool = False) -> float:
293301 n_amp_factors = shape (self .noise_amplification_factors )[0 ]
294302
295303 if verbose :
296- print ("shots =" , self . shots , ", n_amp_factors =" , self .n_amp_factors , ", paulitwirl =" , self .pauli_twirl ,
297- ", repeats =" , repeats , sep = "" )
304+ print ("repeats =" , repeats , ", shots per repats =" , self .shots , ", n_amp_factors =" , self .n_amp_factors ,
305+ ", paulitwirl =" , self . pauli_twirl , sep = "" )
298306 print ("noise amplification factors=" , self .noise_amplification_factors , sep = "" )
299307
300308 if verbose :
@@ -317,6 +325,8 @@ def mitigate(self, repeats: int = 1, verbose: bool = False) -> float:
317325
318326 counts = self .execute_circuits (circuits )
319327
328+ self .counts = counts
329+
320330 exp_vals = self .compute_exp_vals (counts )
321331
322332 # Process the resulting expectation values:
@@ -400,15 +410,16 @@ def find_cnot_control_and_target(qasm_line: str) -> (int, int):
400410
401411def propagate (control_in : str , target_in : str ):
402412 """
403- Propagates Pauli gates through a CNOT in accordance with the following circuit identities:
413+ Finds the c,d gates such that (a x b) CNOT (c x d) = CNOT for an ideal CNOT-gate, based on the a (control_in)
414+ and b (target_in) pauli gates by "propagating" the a,b gates over a CNOT-gate by the following identities:
404415
405416 (X x I) CNOT = CNOT (X x X)
406417 (I x X) CNOT = CNOT (I x X)
407418 (Z x I) CNOT = CNOT (I x Z)
408419 (I x Z) CNOT = XNOT (Z x Z)
409420
410421 Note that instead of Pauli-twirling with [X,Z,Y] we use [X,Z,XZ] where XZ = -i*Y.
411- The inverse of XZ is ZX = -XZ = i*Y. Propagating over the CNOT, the complex factors cancels .
422+ The inverse of XZ is ZX = -XZ = i*Y. The factors of plus minus i are global phase factors which can be ignored .
412423
413424 :param control_in: Pauli gates on control qubit before CNOT
414425 :param target_in: Pauli gates on target qubit before CNOT
0 commit comments