1010
1111from 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