Skip to content

Commit 1ebea95

Browse files
committed
updated the CNOT ZNE-class
1 parent c431b8e commit 1ebea95

1 file changed

Lines changed: 38 additions & 27 deletions

File tree

zero_noise_extrapolation_cnot.py

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
expectation value to the zero-noise limit.
1919
2020
The 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

401411
def 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

Comments
 (0)