def _noise_model_program_header(noise_model, name_translator): """ Generate the header for a pyquil Program that uses ``noise_model`` to overload noisy gates. :param NoiseModel noise_model: The assumed noise model. :return: A quil Program with the noise pragmas. :rtype: pyquil.quil.Program """ from pyquil.quil import Program p = Program() for k in noise_model.gates: name = name_translator(k.gate) p.define_noisy_gate(name, k.targets, k.kraus_ops) for q, ap in noise_model.assignment_probs.items(): p.define_noisy_readout(q, p00=ap[0, 0], p11=ap[1, 1]) return p
def test_pragma_with_placeholders(): q = QubitPlaceholder() q2 = QubitPlaceholder() p = Program() p.inst(Pragma('FENCE', [q, q2])) address_map = {q: 0, q2: 1} addressed_pragma = address_qubits(p, address_map)[0] parse_equals('PRAGMA FENCE 0 1\n', addressed_pragma) pq = Program(X(q)) pq.define_noisy_readout(q, .8, .9) pq.inst(X(q2)) pq.define_noisy_readout(q2, .9, .8) ret = address_qubits(pq, address_map).out() assert ret == """X 0
def _modified_noise_model_program_header(noise_model: NoiseModel) -> "Program": """ Generate the header for a pyquil Program that uses ``noise_model`` to overload noisy gates. The program header consists of 3 sections: - The ``DEFGATE`` statements that define the meaning of the newly introduced "noisy" gate names. - The ``PRAGMA ADD-KRAUS`` statements to overload these noisy gates on specific qubit targets with their noisy implementation. - THe ``PRAGMA READOUT-POVM`` statements that define the noisy readout per qubit. :param noise_model: The assumed noise model. :return: A quil Program with the noise pragmas. """ from pyquil.quil import Program p = Program() defgates: Set[str] = set() for k in noise_model.gates: # obtain ideal gate matrix and new, noisy name by looking it up in the NOISY_GATES dict try: ideal_gate, new_name = get_modified_noisy_gate( k.gate, tuple(k.params)) # if ideal version of gate has not yet been DEFGATE'd, do this if new_name not in defgates: p.defgate(new_name, ideal_gate) defgates.add(new_name) except NoisyGateUndefined: print( "WARNING: Could not find ideal gate definition for gate {}". format(k.gate), file=sys.stderr, ) new_name = k.gate # define noisy version of gate on specific targets p.define_noisy_gate(new_name, k.targets, k.kraus_ops) # define noisy readouts for q, ap in noise_model.assignment_probs.items(): p.define_noisy_readout(q, p00=ap[0, 0], p11=ap[1, 1]) return p
def test_pragma_with_placeholders(): q = QubitPlaceholder() q2 = QubitPlaceholder() p = Program() p.inst(Pragma("FENCE", [q, q2])) address_map = {q: 0, q2: 1} addressed_pragma = address_qubits(p, address_map)[0] parse_equals("PRAGMA FENCE 0 1\n", addressed_pragma) pq = Program(X(q)) pq.define_noisy_readout(q, 0.8, 0.9) pq.inst(X(q2)) pq.define_noisy_readout(q2, 0.9, 0.8) ret = address_qubits(pq, address_map).out() assert (ret == """X 0 PRAGMA READOUT-POVM 0 "(0.8 0.09999999999999998 0.19999999999999996 0.9)" X 1 PRAGMA READOUT-POVM 1 "(0.9 0.19999999999999996 0.09999999999999998 0.8)" """)
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): # assumes single qubit gates gate, _ = instr.out().split(' ') prep_prog += Program(str(gate) + ' ' + str(qubit)) 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 = TomographyExperiment( 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 test_define_noisy_readout(): pq = Program(X(0)) pq.define_noisy_readout(0, 0.8, 0.9) pq.inst(X(1)) pq.define_noisy_readout(1, 0.9, 0.8) ret = pq.out() assert (ret == """X 0 PRAGMA READOUT-POVM 0 "(0.8 0.09999999999999998 0.19999999999999996 0.9)" X 1 PRAGMA READOUT-POVM 1 "(0.9 0.19999999999999996 0.09999999999999998 0.8)" """) # test error due to bad normalization with pytest.raises(ValueError): pq.define_noisy_readout(0, 1.1, 0.5) # test error due to bad normalization with pytest.raises(ValueError): pq.define_noisy_readout(0, 0.5, 1.5) # test error due to negative probability with pytest.raises(ValueError): pq.define_noisy_readout(0, -0.1, 0.5) # test error due to negative probability with pytest.raises(ValueError): pq.define_noisy_readout(0, 0.5, -1.0) # test error due to bad qubit_index value with pytest.raises(ValueError): pq.define_noisy_readout(-1, 0.5, 0.5) # test error due to bad qubit_index type with pytest.raises(TypeError): pq.define_noisy_readout(1.0, 0.5, 0.5)
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 = 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)