Skip to content

Commit 2a2334e

Browse files
committed
implemented error controlled sampling and data classes
1 parent 1236ee1 commit 2a2334e

File tree

1 file changed

+144
-110
lines changed

1 file changed

+144
-110
lines changed

zero_noise_extrapolation_cnot.py

Lines changed: 144 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from qiskit.transpiler.passes import Unroller, Optimize1qGates
88
from qiskit.transpiler.preset_passmanagers.level3 import level_3_pass_manager
99

10-
from numpy import asarray, ndarray, shape, zeros, empty, average
10+
from numpy import asarray, ndarray, shape, zeros, empty, average, transpose, dot
11+
from numpy.linalg import solve
1112

1213
import random, os, pickle, sys, errno
1314
from dataclasses import dataclass
@@ -42,24 +43,40 @@
4243
"""
4344

4445

45-
# Dataclasses:
46+
# Dataclasses, containing partial (noise amplified) and final results
4647

4748
@dataclass(frozen=True)
4849
class NoiseAmplifiedResult:
49-
amplification_factor: int
50-
exp_val: float
51-
circuit: QuantumCircuit
52-
depth: int
50+
amp_factor: int
5351
shots: int
52+
qc: QuantumCircuit
53+
depth: int
54+
exp_val: float
55+
variance: float
5456

5557

5658
@dataclass(frozen=True)
5759
class ZeroNoiseExtrapolationResult:
58-
result: float
59-
circuit: QuantumCircuit
60-
all_exp_vals: ndarray
61-
noise_amplified_exp_vals: ndarray
62-
depths: ndarray
60+
qc: QuantumCircuit
61+
noise_amplified_results: ndarray[NoiseAmplifiedResult]
62+
noise_amplification_factors: ndarray[int]
63+
gamma_coefficients: ndarray[float]
64+
exp_val: float
65+
66+
def get_bare_exp_val(self) -> float:
67+
return self.noise_amplified_results[0].exp_val
68+
69+
def get_noise_amplified_exp_vals(self) -> ndarray:
70+
return asarray([result.exp_val for result in self.noise_amplified_results])
71+
72+
def get_noise_amplified_variances(self) -> ndarray:
73+
return asarray([result.variance for result in self.noise_amplified_results])
74+
75+
def get_noise_amplified_circuit_depths(self) -> ndarray:
76+
return asarray([result.qc.depth() for result in self.noise_amplified_results])
77+
78+
def get_noise_amplified_circuit_shots(self) -> ndarray:
79+
return asarray([result.shots for result in self.noise_amplified_results])
6380

6481

6582
# Zero-noise extrapolation class:
@@ -68,7 +85,8 @@ class ZeroNoiseExtrapolation:
6885
def __init__(self, qc: QuantumCircuit, exp_val_func: Callable, backend=None, exp_val_filter=None,
6986
noise_model: Union[NoiseModel, dict] = None, n_amp_factors: int = 3, shots: int = 8192,
7087
pauli_twirl: bool = False, pass_manager: PassManager = None,
71-
save_results: bool = False, experiment_name: str = "", option: dict = None):
88+
save_results: bool = False, experiment_name: str = "", option: dict = None,
89+
error_controlled_sampling: bool = False, max_shots: int = 100*8192, error_tol: float = 0.01):
7290
"""
7391
CONSTRUCTOR
7492
@@ -80,11 +98,12 @@ def __init__(self, qc: QuantumCircuit, exp_val_func: Callable, backend=None, exp
8098
desired expectation value can be estimated.
8199
82100
exp_val_func : Callable
83-
A function that computes the desired expectation value based on the measurement results outputted by the
84-
quantum circuit.
101+
A function that computes the desired expectation value, and its variance, based on the measurement results
102+
outputted by an execution of a quantum circuit.
85103
The function should take two arguments: a list of qiskit.result.result.ExperimentResult objects as its first
86104
argument, and a possible filter as its second.
87-
The function should return: An array of computed expectation values
105+
The function should return: A numpy.ndarray of expectation values corresponding to each ExperimentResult,
106+
and a numpy.ndarray of variances in similar fashion.
88107
89108
backend : A valid qiskit backend, IBMQ device or simulator
90109
A qiskit backend, either an IBMQ quantum backend or a simulator backend, for circuit executions.
@@ -145,7 +164,8 @@ def __init__(self, qc: QuantumCircuit, exp_val_func: Callable, backend=None, exp
145164
self.noise_model = noise_model
146165

147166
self.n_amp_factors = n_amp_factors
148-
self.noise_amplification_factors = asarray([(2*i + 1) for i in range(0, n_amp_factors)])
167+
self.gamma_coefficients = self.compute_extrapolation_coefficients(n_amp_factors=self.n_amp_factors)
168+
self.noise_amplification_factors = asarray([(2*i + 1) for i in range(0, self.n_amp_factors)])
149169

150170
self.pauli_twirl = pauli_twirl
151171

@@ -174,6 +194,11 @@ def __init__(self, qc: QuantumCircuit, exp_val_func: Callable, backend=None, exp
174194
if not circuit_read_from_file:
175195
self.qc = self.transpile_circuit(qc, custom_pass_manager=pass_manager)
176196

197+
# variables involved in error controlled sampling:
198+
self.error_controlled_sampling = error_controlled_sampling
199+
self.max_shots = max_shots
200+
self.error_tol = error_tol
201+
177202
"""
178203
--- Initialization of other variables for later use:
179204
"""
@@ -188,10 +213,17 @@ def __init__(self, qc: QuantumCircuit, exp_val_func: Callable, backend=None, exp
188213
self.noise_amplified_exp_vals = zeros(0)
189214
self.noise_amplified_variances = zeros(0)
190215

216+
self.noise_amplified_results = empty((self.n_amp_factors,), dtype=NoiseAmplifiedResult)
217+
191218
self.result = None
192219

220+
self.mitigated_exp_val = None
221+
193222
self.measurement_results = []
194223

224+
def __repr__(self):
225+
pass
226+
195227
def partition_shots(self, tot_shots: int) -> (int, int):
196228
"""
197229
IBMQ devices limits circuit executions to a max of 8192 shots per experiment. To perform more than 8192 shots,
@@ -223,6 +255,36 @@ def set_shots(self, shots: int):
223255
def get_shots(self):
224256
return self.repeats * self.shots
225257

258+
def get_noise_amplified_exp_vals(self):
259+
return asarray([result.exp_val for result in self.noise_amplified_results])
260+
261+
def compute_extrapolation_coefficients(self, n_amp_factors: int = None) -> ndarray:
262+
if n_amp_factors is None:
263+
n_amp_factors = self.n_amp_factors
264+
if n_amp_factors == 1:
265+
return asarray([1])
266+
267+
amplification_factors = asarray([2*i + 1 for i in range(n_amp_factors)])
268+
269+
A = zeros((n_amp_factors, n_amp_factors))
270+
b = zeros((n_amp_factors, 1))
271+
272+
for k in range(1, n_amp_factors):
273+
A[k, :] = amplification_factors**k
274+
275+
gamma_coefficients = solve(A, b)
276+
277+
return gamma_coefficients
278+
279+
def extrapolate(self, noise_amplified_exp_vals: ndarray):
280+
if self.n_amp_factors == 1:
281+
return noise_amplified_exp_vals[0]
282+
if not shape(noise_amplified_exp_vals) == shape(self.gamma_coefficients):
283+
raise Exception("Shape mismatch between noise_amplified_exp_vals and gamma_coefficients." +
284+
" Shape={:}".format(shape(noise_amplified_exp_vals)) +
285+
" does not match shape={:}".format(shape(self.gamma_coefficients)))
286+
return dot(transpose(noise_amplified_exp_vals), self.gamma_coefficients)[0]
287+
226288
def set_experiment_name(self, experiment_name):
227289
"""
228290
Construct the experiment name that will form the base for the filenames that will be read from / written to
@@ -237,7 +299,7 @@ def set_experiment_name(self, experiment_name):
237299
238300
"""
239301
if self.save_results and experiment_name == "":
240-
raise Exception("experiment_name cannot be empty when writing/reading results from disk is activated")
302+
raise Exception("experiment_name cannot be empty when writing/reading results from disk is activated.")
241303
self.experiment_name = experiment_name
242304
self.experiment_name += "__ZNE_CNOT_REP_"
243305
self.experiment_name += "_backend" + self.backend.name()
@@ -451,7 +513,7 @@ def execute_circuit(self, qc: QuantumCircuit, shots=None) -> Result:
451513
job = execute(execution_circuits, backend=self.backend,
452514
pass_manager=PassManager(), shots=shots)
453515

454-
circuit_measurement_results = job.result()
516+
circuit_measurement_results = job.exp_val()
455517

456518
return circuit_measurement_results
457519

@@ -478,46 +540,46 @@ def compute_exp_val(self, result: Result) -> (float, float, ndarray):
478540

479541
return average(experiment_exp_vals), average(experiment_variances), asarray(experiment_exp_vals)
480542

481-
def compute_noise_amplified_exp_val(self):
482-
return
543+
def compute_error_controlled_exp_val(self, noise_amplified_qc: QuantumCircuit, gamma_coeff: float,
544+
shots: int = None, conf_index: int = 1) -> (float, float, float):
545+
if shots is None:
546+
shots = self.shots
483547

484-
def sample_error(self):
485-
return
548+
result = self.execute_circuit(qc=noise_amplified_qc, shots=shots)
486549

487-
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.
550+
exp_val, variance, _ = self.compute_exp_val(result)
490551

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
552+
if not self.error_controlled_sampling:
553+
return exp_val, variance, shots
497554

498-
Returns
499-
-------
500-
result: float
501-
The resulting mitigated expectation value.
502-
"""
503-
if noise_amplified_exp_vals is None:
504-
noise_amplified_exp_vals = self.noise_amplified_exp_vals
505-
if n_amp_factors is None:
506-
n_amp_factors = self.n_amp_factors
555+
total_shots = int(self.n_amp_factors * (gamma_coeff**2) * (conf_index**2) * (variance / self.error_tol**2))
507556

508-
if type(noise_amplified_exp_vals) != ndarray:
509-
raise Exception("noise_amplified_exp_vals was of type {:}. Must be numpy.ndarray.".format(type(noise_amplified_exp_vals)))
510-
if shape(noise_amplified_exp_vals)[0] < n_amp_factors:
511-
raise Exception("Number of noise amplified expectation values was {:}".format(shape(noise_amplified_exp_vals))
512-
+ ". Must be equal or larger than n_amp_factors={:}".format(n_amp_factors))
513-
elif n_amp_factors <= 1:
514-
raise Exception("Number of amplification factors was set to n_amp_factors={:}. Must be larger than 1.".format(n_amp_factors))
557+
if total_shots <= shots:
558+
return exp_val, variance, shots
559+
elif total_shots > self.max_shots:
560+
total_shots = self.max_shots
515561

516-
amplification_factors = asarray([2*i + 1 for i in range(n_amp_factors)])
562+
new_shots = int(total_shots - shots)
563+
564+
result = self.execute_circuit(qc=noise_amplified_qc, shots=new_shots)
565+
566+
new_exp_val, new_variance, _ = self.compute_exp_val(result)
567+
568+
error_controlled_exp_val = (shots/total_shots) * exp_val + (new_shots/total_shots) * new_exp_val
569+
error_controlled_variance = (shots/total_shots) * variance + (new_shots/total_shots) * new_variance
517570

518-
result, _ = Richardson_extrapolate(noise_amplified_exp_vals[0:n_amp_factors], amplification_factors)
571+
return error_controlled_exp_val, error_controlled_variance, total_shots
519572

520-
return result[0]
573+
def compute_noise_amplified_exp_val(self, amp_factor, gamma_coeff):
574+
noise_amplified_qc = self.noise_amplify_and_pauli_twirl_cnots(qc=self.qc, amp_factor=amp_factor,
575+
pauli_twirl=self.pauli_twirl)
576+
577+
exp_val, variance, total_shots = self.compute_error_controlled_exp_val(noise_amplified_qc, gamma_coeff)
578+
579+
noise_amplified_result = NoiseAmplifiedResult(amp_factor=amp_factor, shots=total_shots,
580+
qc=noise_amplified_qc, depth=noise_amplified_qc.depth(),
581+
exp_val=exp_val, variance=variance)
582+
return noise_amplified_result
521583

522584
def mitigate(self, verbose: bool = False) -> float:
523585
"""
@@ -534,80 +596,52 @@ def mitigate(self, verbose: bool = False) -> float:
534596
The mitigated expectation value.
535597
"""
536598

537-
n_amp_factors = shape(self.noise_amplification_factors)[0]
538-
539-
self.noise_amplified_exp_vals = zeros((n_amp_factors,))
540-
self.noise_amplified_variances = zeros((n_amp_factors,))
541-
self.all_exp_vals = zeros((n_amp_factors, self.repeats))
599+
for i, amp_factor in enumerate(self.noise_amplification_factors):
542600

543-
if verbose:
544-
print("Shots per circuit=", self.repeats * self.shots, ", executed as ", self.shots, " shots per repeat for ",
545-
self.repeats, " experiment repeats.", "\nPauli twirl=", self.pauli_twirl,
546-
"\nNumber of noise amplification factors=", n_amp_factors,
547-
"\nNoise amplification factors=", self.noise_amplification_factors, sep="")
548-
549-
if verbose:
550-
print("-----\nConstructing circuits:")
551-
552-
noise_amplified_circuits = []
553-
554-
for j, amp_factor in enumerate(self.noise_amplification_factors):
555-
noise_amplified_circuits.append(self.noise_amplify_and_pauli_twirl_cnots(qc=self.qc, amp_factor=amp_factor,
556-
pauli_twirl=self.pauli_twirl))
557-
self.depths[j] = noise_amplified_circuits[-1].depth()
558-
559-
if verbose:
560-
print("Circuit depths=", self.depths, sep="")
561-
562-
print("-----\nComputing expectation values:")
563-
564-
for i in range(n_amp_factors):
565-
print("Noise amplification factor ", i + 1, " of ", n_amp_factors)
566-
567-
circuit_measurement_results, circuit_read_from_file = None, False
601+
noise_amplified_result, result_read_from_file = None, False
568602

603+
# If self.save_results=True, attempt to read noise amplified result from disk
569604
if self.save_results:
570-
tmp = self.read_from_file(self.experiment_name + "_r{:}.results".format(self.noise_amplification_factors[i]))
571-
if tmp != None:
572-
circuit_measurement_results = tmp
573-
circuit_read_from_file = True
605+
temp = self.read_from_file(filename=self.experiment_name + "_r{:}.result".format(amp_factor))
606+
if (temp is not None) and (type(temp) is NoiseAmplifiedResult):
607+
noise_amplified_result = temp
608+
result_read_from_file = True
574609
if verbose:
575-
print("Results successfully read from disk.")
610+
print("Noise amplified result successfully read from disk.")
576611
else:
577612
if verbose:
578613
print("Tried to read results from disk, but results were not found.")
579614

580-
# circuit_read_from_file equals False if either the option for reading from/writing to disk is off, e.g.,
581-
# if save_results=Flase, or if the result was attempted to be read but not found.
582-
# In either case the experiment must be run from scratch.
583-
if not circuit_read_from_file:
584-
if verbose:
585-
print("Executing circuit.")
586-
circuit_measurement_results = self.execute_circuit(noise_amplified_circuits[i])
615+
gamma_coeff = self.gamma_coefficients[i]
616+
617+
if not result_read_from_file:
618+
noise_amplified_result = self.compute_noise_amplified_exp_val(amp_factor=amp_factor,
619+
gamma_coeff=gamma_coeff)
620+
587621
if self.save_results:
588-
# Write the noise amplified expectation value to disk
589-
self.write_to_file(self.experiment_name + "_r{:}.results".format(self.noise_amplification_factors[i]),
590-
circuit_measurement_results)
591-
if verbose:
592-
print("Results successfully written to disk.")
622+
self.write_to_file(filename=self.experiment_name + "_r{:}.result".format(amp_factor),
623+
data=noise_amplified_result)
593624

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)
625+
self.noise_amplified_results[i] = noise_amplified_result
596626

597-
self.measurement_results.append(circuit_measurement_results)
627+
mitigated_exp_val = self.extrapolate(self.get_noise_amplified_exp_vals())
598628

599-
# Find the mitigated expectation value by Richardson extrapolation
600-
result,_ = Richardson_extrapolate(self.noise_amplified_exp_vals, self.noise_amplification_factors)
601-
self.result = result[0]
629+
self.result = ZeroNoiseExtrapolationResult(qc=self.qc,
630+
noise_amplified_results=self.noise_amplified_results,
631+
noise_amplification_factors=self.noise_amplification_factors,
632+
gamma_coefficients=self.gamma_coefficients,
633+
exp_val=mitigated_exp_val
634+
)
602635

603636
if verbose:
604-
print("-----", "\nError mitigation done",
605-
"\nBare circuit expectation value: ", self.noise_amplified_exp_vals[0],
606-
"\nNoise amplified expectation values: ",self.noise_amplified_exp_vals,
607-
"\n-----",
608-
"\nMitigated expectation value: ", self.result, "\n-----", sep="")
609-
610-
return self.result
637+
print("-----\nERROR MITIGATION DONE\n" +
638+
"Bare circuit expectation value: {:}\n".format(self.result.get_bare_exp_val()) +
639+
"Noise amplified expectation values: {:}\n".format(self.result.get_noise_amplified_exp_vals()) +
640+
"Circuit depths: {:}\n".format(self.result.get_noise_amplified_circuit_depths()) +
641+
"-----\n" +
642+
"Mitigated expectation value: {:}\n".format(self.result.exp_val))
643+
644+
return mitigated_exp_val
611645

612646
"""
613647
--- PAULI TWIRLING AND NOISE AMPLIFICATION HELP FUNCTIONS

0 commit comments

Comments
 (0)