Source code for oitg.circuits.protocols.process_tomo.generate

from itertools import chain, product
from typing import List, Tuple
import numpy as np
from ...gate import Gate, GateSequence, remap_operands


[docs] def generate_process_tomography_sequences(target: GateSequence, num_qubits: int) -> List[GateSequence]: """Return a list of gate sequences to perform process tomography on the given target sequence. The system is prepared in a tensor product of single-qubit Pauli-operator eigenstates before applying the target sequence and measuring the expectation value of a tensor product of Pauli operators. See ``generate_process_tomography_fiducial_pairs()``. :param target: The gate sequence to perform tomography on. :param num_qubits: The number of qubits making up the Hilbert space of interest. """ fiducial_pairs = generate_process_tomography_fiducial_pairs(num_qubits) return wrap_target_in_process_tomography_fiducials(target, fiducial_pairs)
[docs] def wrap_target_in_process_tomography_fiducials( target: GateSequence, fiducial_pairs: List[Tuple[GateSequence, GateSequence]]) -> List[GateSequence]: """Return a list of gate sequences to perform process tomography on the given target sequence. The system is prepared in a tensor product of single-qubit Pauli-operator eigenstates before applying the target sequence and measuring the expectation value of a tensor product of Pauli operators. :param target: The gate sequence to perform tomography on. :param fiducial_pairs: See ``generate_process_tomography_fiducial_pairs()`` """ return [tuple(chain(prep, target, measure)) for prep, measure in fiducial_pairs]
[docs] def generate_process_tomography_fiducial_pairs( num_qubits: int) -> List[Tuple[GateSequence, GateSequence]]: """Return a list of tuples of gate sequences implementing the preparation and measurement for process tomography. For state preparation, all six Pauli eigenstates are created (i.e. ±x, ±y, ±z). Even though this yields an over-complete set of input states (:math:`6^n` instead of the :math:`4^n` required ones), this is a small price to pay for the resulting symmetry – even for two-qubit gates this only just more than doubles (:math:`9 / 4`) the number of sequences, so we can always just take fewer shots per sequence to compensate. :param num_qubits: The number of qubits making up the Hilbert space of interest. """ fiducials = [ (Gate("ry", (np.pi / 2, ), (0, )), ), # +x (Gate("rx", (-np.pi / 2, ), (0, )), ), # +y (), # +z (Gate("ry", (-np.pi / 2, ), (0, )), ), # -x (Gate("rx", (np.pi / 2, ), (0, )), ), # -y (Gate("rx", (np.pi, ), (0, )), ), # -z ] def make_combinations(qubit_seqs): return [ tuple( chain.from_iterable( remap_operands(seq, {0: i}) for (i, seq) in enumerate(seqs))) for seqs in product(qubit_seqs, repeat=num_qubits) ] return [(prep, measure[::-1]) for prep in make_combinations(fiducials) for measure in make_combinations(fiducials[:3])]