def compile_tomo_expts(self, pauli_list=None): """ This method compiles the tomography experiment circuits and prepares them for simulation. Every time the circuits are adjusted, re-compiling the tomography experiments is required to affect the outcome. """ self.offset = 0 # use Forest's sorting algo from the Tomography suite # to group Pauli measurements together experiments = [] if pauli_list is None: pauli_list = self.pauli_list for term in pauli_list: # if the Pauli term is an identity operator, # add the term's coefficient directly to the VQE class' offset if len(term.operations_as_set()) == 0: self.offset += term.coefficient.real else: # initial state and term pair. experiments.append(ExperimentSetting( TensorProductState(), term)) suite = Experiment(experiments, program=Program()) gsuite = group_experiments(suite) grouped_list = [] for setting in gsuite: group = [] for term in setting: group.append(term.out_operator) grouped_list.append(group) if self.verbose: print('Number of tomography experiments: ', len(grouped_list)) self.experiment_list = [] for group in grouped_list: self.experiment_list.append( GroupedPauliSetting(group, qc=self.qc, ref_state=self.ref_state, ansatz=self.ansatz, shotN=self.shotN, parametric_way=self.parametric_way, n_qubits=self.n_qubits, method=self.method, verbose=self.verbose, cq=self.custom_qubits, ))
def expval(self, observable): wires = observable.wires # Single-qubit observable if len(wires) == 1: # identify Experiment Settings for each of the possible single-qubit observables wire = wires[0] qubit = self.wiring[wire] d_expt_settings = { "Identity": [ExperimentSetting(TensorProductState(), sI(qubit))], "PauliX": [ExperimentSetting(TensorProductState(), sX(qubit))], "PauliY": [ExperimentSetting(TensorProductState(), sY(qubit))], "PauliZ": [ExperimentSetting(TensorProductState(), sZ(qubit))], "Hadamard": [ ExperimentSetting(TensorProductState(), float(np.sqrt(1 / 2)) * sX(qubit)), ExperimentSetting(TensorProductState(), float(np.sqrt(1 / 2)) * sZ(qubit)) ] } # expectation values for single-qubit observables if observable.name in [ "PauliX", "PauliY", "PauliZ", "Identity", "Hadamard" ]: prep_prog = Program() for instr in self.program.instructions: if isinstance(instr, Gate): # split gate and wires -- assumes 1q and 2q gates tup_gate_wires = instr.out().split(' ') gate = tup_gate_wires[0] str_instr = str(gate) # map wires to qubits for w in tup_gate_wires[1:]: str_instr += f' {int(w)}' prep_prog += Program(str_instr) if self.readout_error is not None: prep_prog.define_noisy_readout(qubit, p00=self.readout_error[0], p11=self.readout_error[1]) # All observables are rotated and can be measured in the PauliZ basis tomo_expt = Experiment(settings=d_expt_settings["PauliZ"], program=prep_prog) grouped_tomo_expt = group_experiments(tomo_expt) meas_obs = list( measure_observables( self.qc, grouped_tomo_expt, active_reset=self.active_reset, symmetrize_readout=self.symmetrize_readout, calibrate_readout=self.calibrate_readout)) return np.sum( [expt_result.expectation for expt_result in meas_obs]) elif observable.name == 'Hermitian': # <H> = \sum_i w_i p_i Hkey = tuple(par[0].flatten().tolist()) w = self._eigs[Hkey]['eigval'] return w[0] * p0 + w[1] * p1 return super().expval(observable)
def expval(self, observable, wires, par): # Single-qubit observable if len(wires) == 1: # identify Experiment Settings for each of the possible single-qubit observables wire = wires[0] qubit = self.wiring[wire] d_expt_settings = { "Identity": [ExperimentSetting(TensorProductState(), sI(qubit))], "PauliX": [ExperimentSetting(TensorProductState(), sX(qubit))], "PauliY": [ExperimentSetting(TensorProductState(), sY(qubit))], "PauliZ": [ExperimentSetting(TensorProductState(), sZ(qubit))], "Hadamard": [ ExperimentSetting(TensorProductState(), float(np.sqrt(1 / 2)) * sX(qubit)), ExperimentSetting(TensorProductState(), float(np.sqrt(1 / 2)) * sZ(qubit)) ] } # expectation values for single-qubit observables if observable in [ "PauliX", "PauliY", "PauliZ", "Identity", "Hadamard" ]: prep_prog = Program() for instr in self.program.instructions: if isinstance(instr, Gate): # split gate and wires -- assumes 1q and 2q gates tup_gate_wires = instr.out().split(' ') gate = tup_gate_wires[0] str_instr = str(gate) # map wires to qubits for w in tup_gate_wires[1:]: str_instr += f' {int(w)}' prep_prog += Program(str_instr) if self.readout_error is not None: prep_prog.define_noisy_readout(qubit, p00=self.readout_error[0], p11=self.readout_error[1]) tomo_expt = Experiment(settings=d_expt_settings[observable], program=prep_prog) grouped_tomo_expt = group_experiments(tomo_expt) meas_obs = list( measure_observables( self.qc, grouped_tomo_expt, active_reset=self.active_reset, symmetrize_readout=self.symmetrize_readout, calibrate_readout=self.calibrate_readout)) return np.sum( [expt_result.expectation for expt_result in meas_obs]) elif observable == 'Hermitian': # <H> = \sum_i w_i p_i Hkey = tuple(par[0].flatten().tolist()) w = self._eigs[Hkey]['eigval'] return w[0] * p0 + w[1] * p1 # Multi-qubit observable # ---------------------- # Currently, we only support qml.expval.Hermitian(A, wires), # where A is a 2^N x 2^N matrix acting on N wires. # # Eventually, we will also support tensor products of Pauli # matrices in the PennyLane UI. probs = self.probabilities(wires) if observable == 'Hermitian': Hkey = tuple(par[0].flatten().tolist()) w = self._eigs[Hkey]['eigval'] # <A> = \sum_i w_i p_i return w @ probs
def expval(self, observable): wires = observable.wires # `measure_observables` called only when parametric compilation is turned off if not self.parametric_compilation: # Single-qubit observable if len(wires) == 1: # Ensure sensible observable assert observable.name in [ "PauliX", "PauliY", "PauliZ", "Identity", "Hadamard" ], "Unknown observable" # Create appropriate PauliZ operator wire = wires[0] qubit = self.wiring[wire] pauli_obs = sZ(qubit) # Multi-qubit observable elif len(wires) > 1 and isinstance( observable, Tensor) and not self.parametric_compilation: # All observables are rotated to be measured in the Z-basis, so we just need to # check which wires exist in the observable, map them to physical qubits, and measure # the product of PauliZ operators on those qubits pauli_obs = sI() for wire in observable.wires: qubit = wire pauli_obs *= sZ(self.wiring[qubit]) # Program preparing the state in which to measure observable prep_prog = Program() for instr in self.program.instructions: if isinstance(instr, Gate): # split gate and wires -- assumes 1q and 2q gates tup_gate_wires = instr.out().split(" ") gate = tup_gate_wires[0] str_instr = str(gate) # map wires to qubits for w in tup_gate_wires[1:]: str_instr += f" {int(w)}" prep_prog += Program(str_instr) if self.readout_error is not None: for wire in observable.wires: qubit = wire prep_prog.define_noisy_readout(self.wiring[qubit], p00=self.readout_error[0], p11=self.readout_error[1]) # Measure out multi-qubit observable tomo_expt = Experiment( settings=[ExperimentSetting(TensorProductState(), pauli_obs)], program=prep_prog) grouped_tomo_expt = group_experiments(tomo_expt) meas_obs = list( measure_observables( self.qc, grouped_tomo_expt, active_reset=self.active_reset, symmetrize_readout=self.symmetrize_readout, calibrate_readout=self.calibrate_readout, )) # Return the estimated expectation value return np.sum( [expt_result.expectation for expt_result in meas_obs]) # Calculation of expectation value without using `measure_observables` return super().expval(observable)