77from qiskit .transpiler .passes import Unroller , Optimize1qGates
88from 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
1213import random , os , pickle , sys , errno
1314from dataclasses import dataclass
4243"""
4344
4445
45- # Dataclasses:
46+ # Dataclasses, containing partial (noise amplified) and final results
4647
4748@dataclass (frozen = True )
4849class 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 )
5759class 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." , "\n Pauli twirl=" , self .pauli_twirl ,
546- "\n Number of noise amplification factors=" , n_amp_factors ,
547- "\n Noise amplification factors=" , self .noise_amplification_factors , sep = "" )
548-
549- if verbose :
550- print ("-----\n Constructing 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 ("-----\n Computing 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 ("-----" , "\n Error mitigation done" ,
605- "\n Bare circuit expectation value: " , self .noise_amplified_exp_vals [0 ],
606- "\n Noise amplified expectation values: " ,self .noise_amplified_exp_vals ,
607- "\n -----" ,
608- "\n Mitigated expectation value: " , self .result , "\n -----" , sep = "" )
609-
610- return self .result
637+ print ("-----\n ERROR 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