Skip to content

Commit f5efa69

Browse files
committed
Added saving functionality for partial results
1 parent e86284f commit f5efa69

File tree

1 file changed

+64
-29
lines changed

1 file changed

+64
-29
lines changed

zero_noise_extrapolation_cnot.py

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from zero_noise_extrapolation import Richardson_extrapolate
1212

13-
import random
13+
import random, os, pickle
1414

1515
"""
1616
-- ZERO NOISE EXTRAPOLATION for CNOT-gates --
@@ -35,7 +35,7 @@ class ZeroNoiseExtrapolation:
3535

3636
def __init__(self, qc: QuantumCircuit, exp_val_func, backend=None, noise_model=None,
3737
n_amp_factors: int = 3, shots: int = 8192, pauli_twirl: bool = False, pass_manager: PassManager = None,
38-
save_results: bool = False, experiment_name: str = None, option: dict = None):
38+
save_results: bool = False, experiment_name: str = "", option: dict = None):
3939
""" CONSTRUCTOR
4040
:param qc: The quantum circuit to be mitigated
4141
:param exp_val_func: A function that computes the observed expectation value of some operator measured by the
@@ -59,10 +59,6 @@ def __init__(self, qc: QuantumCircuit, exp_val_func, backend=None, noise_model=N
5959
self.backend = backend
6060
self.is_simulator = backend.configuration().simulator
6161

62-
# Do an initial optimization of the quantum circuit. Either with a custom pass manager, or with the
63-
# optimization_level=3 transpiler preset (the heaviest optimization preset)
64-
self.qc = self.transpile_circuit(qc, custom_pass_manager=pass_manager)
65-
6662
self.exp_val_func = exp_val_func
6763

6864
self.noise_model = noise_model
@@ -72,20 +68,29 @@ def __init__(self, qc: QuantumCircuit, exp_val_func, backend=None, noise_model=N
7268

7369
self.pauli_twirl = pauli_twirl
7470

75-
self.save_results = save_results
76-
self.experiment_name = experiment_name
77-
if save_results and experiment_name == None:
78-
raise Exception("experiment_name cannot be empty when saving results")
79-
if option == None:
80-
self.option = {"directory": "/results/"}
81-
else:
82-
self.option = option
71+
# Variables involved in saving of results to disk
72+
self.save_results, self.option = save_results, option
73+
self.experiment_name = ""
74+
self.set_experiment_name(experiment_name)
75+
if self.option == None:
76+
self.option = {}
8377

8478
# Max number of shots for one circuit execution on IBMQ devices is 8192.
8579
# To do more shots, we have to partition them up into several executions.
8680
self.shots, self.repeats = None, None
8781
self.shots, self.repeats = self.partition_shots(shots)
8882

83+
# Do an initial optimization of the quantum circuit. Either with a custom pass manager, or with the
84+
# optimization_level=3 transpiler preset (the heaviest optimization preset)
85+
circuit_read_from_file = False
86+
if self.save_results:
87+
qc_from_file = self.read_from_file(self.experiment_name + ".circuit")
88+
if qc_from_file != None:
89+
circuit_read_from_file = True
90+
self.qc = qc_from_file
91+
if not circuit_read_from_file:
92+
self.qc = self.transpile_circuit(qc, custom_pass_manager=pass_manager)
93+
8994
# Initialization of variables for later use:
9095

9196
self.counts = []
@@ -117,13 +122,30 @@ def set_shots(self, shots: int):
117122
def get_shots(self):
118123
return self.repeats * self.shots
119124

120-
def save_result(self):
121-
if self.option == None:
122-
option = {"directory": "/results/"}
125+
def set_experiment_name(self, experiment_name):
126+
if self.save_results:
127+
if experiment_name == "":
128+
raise Exception("experiment_name cannot be empty when saving results")
129+
self.experiment_name = experiment_name
130+
self.experiment_name += "_ZNE_CNOTrep"
131+
self.experiment_name += "_backend" + self.backend.name()
132+
self.experiment_name += "_shots" + str(self.get_shots())
133+
self.experiment_name += "_paulitwirling" + str(self.pauli_twirl)
134+
135+
def read_from_file(self, filename: str):
136+
directory = self.option.get("directory", "results")
137+
if os.path.isfile(directory + "/" + filename):
138+
file = open(directory + "/" + filename, "rb")
139+
data = pickle.load(file)
140+
file.close()
141+
return data
123142
else:
124-
option = self.option
143+
return None
125144

126-
def read_result(self):
145+
def write_to_file(self, filename: str, data):
146+
directory = self.option.get("directory", "results")
147+
file = open(directory + "/" + filename)
148+
pickle.dump(data, file)
127149

128150
def noise_amplify_and_pauli_twirl_cnots(self, qc: QuantumCircuit, amp_factor: int,
129151
pauli_twirl: bool) -> QuantumCircuit:
@@ -219,20 +241,20 @@ def transpile_circuit(self, qc: QuantumCircuit, custom_pass_manager: PassManager
219241
# As there is some randomness involved in the qiskit transpiling, we might want to save
220242
# the specific transpiled circuit that is used
221243
if self.save_results:
222-
# TODO: Save transpiled circuit
223-
pass
244+
filename = self.experiment_name + ".circuit"
245+
self.write_to_file(filename, transpiled_circuit)
224246

225247
return transpiled_circuit
226248

227-
def execute_circuits(self, circuits: list, shots=None) -> Result:
249+
def execute_circuits(self, qc: QuantumCircuit, shots=None) -> Result:
228250
"""
229251
Execute all circuits and return measurement counts. If shots > 8192, we need to partition the execution
230252
into several sub-executions.
231253
232254
Circuits are transpiled and optimized beforehand, thus we pass an empty PassManager to qiskit.execute
233255
to avoid unnecessary time spent on transpiling.
234256
235-
:param circuits: All circuits to be executed
257+
:param qc: Circuit to be executed
236258
:return: A list of count-dictionaries
237259
"""
238260

@@ -246,9 +268,7 @@ def execute_circuits(self, circuits: list, shots=None) -> Result:
246268
# The max number of shots on a single execution on the IBMQ devices is 8192.
247269
# If shots > 8192, we have to partition the execution into several sub-executions.
248270
# Note that several circuits can be entered into the IBMQ queue at once by being passed as a list.
249-
execution_circuits = []
250-
for qc in circuits:
251-
execution_circuits += [qc.copy() for i in range(repeats)]
271+
execution_circuits = [qc.copy() for i in range(repeats)]
252272

253273
# non-simulator backends throws unexpected argument when passing noise_model argument to them
254274
if self.is_simulator:
@@ -260,8 +280,6 @@ def execute_circuits(self, circuits: list, shots=None) -> Result:
260280

261281
circuit_measurement_results = job.result()
262282

263-
self.measurement_results.append(circuit_measurement_results)
264-
265283
return circuit_measurement_results
266284

267285
def compute_exp_val(self, result: Result) -> (float, ndarray):
@@ -308,7 +326,24 @@ def mitigate(self, shots=None, verbose: bool = False) -> float:
308326
for i in range(n_amp_factors):
309327
print("Noise amplification factor ", i + 1, " of ", n_amp_factors)
310328

311-
circuit_measurement_results = self.execute_circuits(noise_amplified_circuits)
329+
circuit_measurement_results, circuit_read_from_file = None, False
330+
331+
if self.save_results:
332+
tmp = self.read_from_file(self.experiment_name + "_r{:}.results".format(self.noise_amplification_factors[i]))
333+
if tmp != None:
334+
circuit_measurement_results = tmp
335+
circuit_read_from_file = True
336+
if verbose:
337+
print("Results successfully read")
338+
else:
339+
if verbose:
340+
print("Results not found")
341+
342+
if not circuit_read_from_file:
343+
circuit_measurement_results = self.execute_circuits(noise_amplified_circuits[i])
344+
if self.save_results:
345+
self.write_to_file(self.experiment_name + "_r{:].results".format(self.noise_amplification_factors[i]),
346+
circuit_measurement_results)
312347

313348
self.noise_amplified_exp_vals[i], self.all_exp_vals[i, :] = self.compute_exp_val(
314349
circuit_measurement_results)

0 commit comments

Comments
 (0)