Source code for qiskit.ignis.verification.tomography.basis.circuits

# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2019, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.


"""
Quantum tomography circuit generation.
"""

import logging
from typing import List, Union, Tuple, Optional
import itertools as it
import re

from qiskit import QuantumRegister
from qiskit.circuit import Qubit
from qiskit import ClassicalRegister
from qiskit import QuantumCircuit
from qiskit import QiskitError
from qiskit.circuit.measure import Measure
from qiskit.circuit.reset import Reset

from .tomographybasis import TomographyBasis
from .paulibasis import PauliBasis
from .gatesetbasis import default_gateset_basis, GateSetBasis
from .sicbasis import SICBasis

# Create logger
logger = logging.getLogger(__name__)

###########################################################################
# State tomography circuits for measurement in Pauli basis
###########################################################################


[docs]def state_tomography_circuits( circuit: QuantumCircuit, measured_qubits: QuantumRegister, meas_labels: Union[str, Tuple[str], List[Tuple[str]]] = 'Pauli', meas_basis: Union[str, TomographyBasis] = 'Pauli' ) -> List[QuantumCircuit]: """ Return a list of quantum state tomography circuits. This performs measurement in the Pauli-basis resulting in :math:`3^n` circuits for an n-qubit state tomography experiment. Args: circuit: the state preparation circuit to be tomographed. measured_qubits: the qubits to be measured. This can also be a list of whole QuantumRegisters or individual QuantumRegister qubit tuples. meas_labels: (default: 'Pauli') The measurement operator labels. meas_basis: (default: 'Pauli') The measurement basis. Returns: A list containing copies of the original circuit with state tomography measurements appended at the end. Additional Information: The returned circuits are named by the measurement basis. To perform tomography measurement in a custom basis, or to generate a subset of state tomography circuits for a partial tomography experiment use the general function `tomography_circuits`. """ return _tomography_circuits(circuit, measured_qubits, None, meas_labels=meas_labels, meas_basis=meas_basis, prep_labels=None, prep_basis=None)
########################################################################### # Process tomography circuits for preparation and measurement in Pauli basis ###########################################################################
[docs]def process_tomography_circuits( circuit: QuantumCircuit, measured_qubits: QuantumRegister, prepared_qubits: Optional[QuantumRegister] = None, meas_labels: Union[str, Tuple[str], List[Tuple[str]]] = 'Pauli', meas_basis: Union[str, TomographyBasis] = 'Pauli', prep_labels: Union[str, Tuple[str], List[Tuple[str]]] = 'Pauli', prep_basis: Union[str, TomographyBasis] = 'Pauli' ) -> List[QuantumCircuit]: r"""Return a list of quantum process tomography circuits. This performs preparation in the minimial Pauli-basis eigenstates * ``"Z_p"``: :math:`|0\rangle` * ``"Z_m"``: :math:`|1\rangle` * ``"X_p"``: :math:`|+\rangle` * ``"Y_m"``: :math:`|+i\rangle` on each qubit, and measurement in the Pauli-basis X, Y, Z resulting in :math:`4^n 3^n` circuits for an n-qubit process tomography experiment. Args: circuit: the QuantumCircuit circuit to be tomographed. measured_qubits: the qubits to be measured. This can also be a list of whole QuantumRegisters or individual QuantumRegister qubit tuples. prepared_qubits: the qubits to have state preparation applied, if different from measured_qubits. If None measured_qubits will be used for prepared qubits meas_labels: (default: 'Pauli') The measurement operator labels. meas_basis: (default: 'Pauli') The measurement basis. prep_labels: (default: 'Pauli') The preparation operator labels. prep_basis: (default: 'Pauli') The preparation basis. Returns: A list of QuantumCircuit objects containing the original circuit with state preparation circuits prepended, and measurement circuits appended. The returned circuits are named by the preparation and measurement basis. """ return _tomography_circuits(circuit, measured_qubits, prepared_qubits, meas_labels=meas_labels, meas_basis=meas_basis, prep_labels=prep_labels, prep_basis=prep_basis)
########################################################################### # Gate set tomography circuits for preparation and measurement ########################################################################### def gateset_tomography_circuits(measured_qubits: Optional[List[int]] = None, gateset_basis: Union[str, GateSetBasis] = 'default' ) -> List[QuantumCircuit]: r"""Return a list of quantum gate set tomography (GST) circuits. The circuits are fully constructed from the data given in gateset_basis. Note that currently this is only implemented for the single-qubits. Args: measured_qubits: The qubits to perform GST. If None GST will be performed on qubit-0. gateset_basis: The gateset and SPAM data. Returns: A list of QuantumCircuit objects containing the original circuit with state preparation circuits prepended, and measurement circuits appended. Raises: QiskitError: If called for more than 1 measured qubit. Additional Information: Gate set tomography is performed on a gate set (G0, G1,...,Gm) with the additional information of SPAM circuits (F0,F1,...,Fn) that are constructed from the gates in the gate set. In gate set tomography, we assume a single initial state rho and a single POVM measurement operator E. The SPAM circuits now provide us with a complete set of initial state F_j|rho> and measurements <E|F_i. We perform three types of experiments: 1) :math:`\langle E | F_i G_k F_j |\rho \rangle` for 1 <= i,j <= n and 1 <= k <= m: This experiment enables us to obtain data on the gate G_k 2) :math:`\langle E | F_i F_j |\rho \rangle` for 1 <= i,j <= n: This experiment enables us to obtain the Gram matrix required to "invert" the results of experiments of type 1 in order to reconstruct (a matrix similar to) the gate G_k 3) :math:`\langle E | F_j |\rho \rangle` for 1 <= j <= n: This experiment enables us to reconstruct <E| and rho The result of this method is the set of all the circuits needed for these experiments, suitably labeled with a tuple of the corresponding gate/SPAM labels """ if measured_qubits is None: measured_qubits = [0] if len(measured_qubits) > 1: raise QiskitError("Only 1-qubit gate set tomography " "is currently supported") num_qubits = 1 + max(measured_qubits) all_circuits = [] if gateset_basis == 'default': gateset_basis = default_gateset_basis() meas_basis = gateset_basis.get_tomography_basis() prep_basis = gateset_basis.get_tomography_basis() meas_labels = meas_basis.measurement_labels prep_labels = prep_basis.preparation_labels # qubit = QuantumRegister(num_qubits) # Experiments of the form <E|F_i G_k F_j|rho> for gate in gateset_basis.gate_labels: circuit = QuantumCircuit(num_qubits) # we assume only 1 qubit for now qubit = circuit.qubits[measured_qubits[0]] gateset_basis.add_gate_to_circuit(circuit, qubit, gate) gst_circuits = _tomography_circuits(circuit, qubit, qubit, meas_labels=meas_labels, meas_basis=meas_basis, prep_labels=prep_labels, prep_basis=prep_basis) for tomography_circuit in gst_circuits: # Getting the names of Fi and Fj using regex res = re.search("'(.*)'.*'(.*)'", tomography_circuit.name) tomography_circuit.name = str((res.group(1), gate, res.group(2))) all_circuits = all_circuits + gst_circuits # Experiments of the form <E|F_i F_j|rho> # Can be skipped if one of the gates is ideal identity circuit = QuantumCircuit(num_qubits) qubit = circuit.qubits[measured_qubits[0]] gst_circuits = _tomography_circuits(circuit, qubit, qubit, meas_labels=meas_labels, meas_basis=meas_basis, prep_labels=prep_labels, prep_basis=prep_basis) for tomography_circuit in gst_circuits: # Getting the names of Fi and Fj using regex res = re.search("'(.*)'.*'(.*)'", tomography_circuit.name) tomography_circuit.name = str((res.group(1), res.group(2))) all_circuits = all_circuits + gst_circuits # Experiments of the form <E|F_j|rho> circuit = QuantumCircuit(num_qubits) qubit = circuit.qubits[measured_qubits[0]] gst_circuits = _tomography_circuits(circuit, qubit, qubit, meas_labels=meas_labels, meas_basis=meas_basis, prep_labels=None, prep_basis=None) for tomography_circuit in gst_circuits: # Getting the name of Fj using regex res = re.search("'(.*)'", tomography_circuit.name) tomography_circuit.name = str((res.group(1),)) all_circuits = all_circuits + gst_circuits return all_circuits ########################################################################### # General state and process tomography circuit functions ########################################################################### def _tomography_circuits( circuit: QuantumCircuit, measured_qubits: QuantumRegister, prepared_qubits: Optional[QuantumRegister] = None, meas_labels: Union[str, Tuple[str], List[Tuple[str]]] = 'Pauli', meas_basis: Union[str, TomographyBasis] = 'Pauli', prep_labels: Union[str, Tuple[str], List[Tuple[str]]] = 'Pauli', prep_basis: Union[str, TomographyBasis] = 'Pauli' ) -> List[QuantumCircuit]: """Return a list of quantum tomography circuits. This is the general circuit preparation function called by `state_tomography_circuits` and `process_tomography_circuits` and allows partial tomography circuits to be generated, or tomography circuits with custom preparation and measurement operators. Args: circuit: the QuantumCircuit circuit to be tomographed. measured_qubits: the qubits to be measured. This can also be a list of whole QuantumRegisters or individual QuantumRegister qubit tuples. prepared_qubits: the qubits to have state preparation applied, if different from measured_qubits. If None measured_qubits will be used for prepared qubits meas_labels: (default: 'Pauli') The measurement operator labels. If None no measurements will be appended. See additional information for details meas_basis: (default: 'Pauli') The measurement basis. prep_labels: (default: 'Pauli') The preparation operator labels. If None no preparations will be appended. See additional information for details prep_basis: (default: 'Pauli') The preparation basis. Raises: QiskitError: If the measurement/preparation basis is invalid. ValueError: If the measurement/preparation basis is not specified Returns: A list of QuantumCircuit objects containing the original circuit with state preparation circuits prepended, and measurement circuits appended. Additional Information Specifying Labels ----------------- `meas_labels` and `prep_labels` may be specified as either: - None: no measurements, or preparation, will be added. - str: use a built-in basis. For meas_labels the built-in basis is 'Pauli'. For prep_labels the built-in bases are 'Pauli' and 'SIC'. - tuple(str): specify single qubit operator labels and generate all n-qubit combinations. - list(tuple(str)): specify a custom list of n-qubit label tuples. If a str argument is used then it is not necessary to specify the corresponding meas_circuit_fn or prep_circuit_fn as the defaults will be used for the corresponding basis. However, when using a tuple or list value these functions must be manually specified using either a str for the build in bases, or a function (See below for documentation.) Specifying a tuple can be used to only measure certain operators. For example if we specify `meas_labels=('Z', )` the resulting circuits will only contain measurements in the Z-basis. Specifying `meas_labels=('X','Z')` will only contain :math:`2^n` measurements in X and Z basis etc. Specifying a tuple is necessary when using a custom `meas_circuit_fn` or `prep_circuit_fn` as these will be the str passed to the function to return the corresponding QuantumCircuit objects. Specifying a list of tuples will override an automatic generation. This can be for partial tomography. For example for a 2-qubit state tomography experiment we might only specify correlated measurements eg: meas_labels=[('X','X'), ('Y','Y'), ('Z','Z')] Custom Measurement Circuit Function ---------------------------------- Custom measurement circuit functions can be used by passing the function using the `meas_circuit_fn` keyword. These functions should have the signature: meas_circuit_fn(op, qubit, clbit) Args: op (str): the operator label qubit (Qubit): measured qubit clbit (Clbit): measurement clbit Returns: A QuantumCircuit object for the measurement. See the built-in function `pauli_measurement_circuit` for an example. The built-in Pauli measurement function `pauli_measurement_circuit` may be invoked using the meas_circuit_fn='Pauli'. Custom Preparation Circuit Function ---------------------------------- Custom preparation circuit functions can be used by passing the function using the `prep_circuit_fn` keyword. These functions should have the signature: prep_circuit_fn(op, qubit) Args: op (str): the operator label qubit (Qubit): measured qubit Returns: A QuantumCircuit object for the preparation gates. See the build-in function `pauli_preparation_circuit` for an example. See the built-in function `pauli_measurement_circuit` for an example. The built-in Pauli preparation function `pauli_preparation_circuit` may be invoked using the prep_circuit_fn='Pauli'. The built-in SIC-POVM preparation function `sicpovm_preparation_circuit` may be invoked using the prep_circuit_fn='SIC'. """ # Check for different prepared qubits if prepared_qubits is None: prepared_qubits = measured_qubits # Check input circuit for measurements and measured qubits if isinstance(measured_qubits, (list, tuple)): # Unroll list of registers if isinstance((measured_qubits[0]), int): measured_qubits = [circuit.qubits[i] for i in measured_qubits] meas_qubits = _format_registers(*measured_qubits) else: meas_qubits = _format_registers(measured_qubits) if isinstance(prepared_qubits, (list, tuple)): # Unroll list of registers if isinstance(prepared_qubits[0], int): prepared_qubits = [circuit.qubits[i] for i in prepared_qubits] prep_qubits = _format_registers(*prepared_qubits) else: prep_qubits = _format_registers(prepared_qubits) if len(prep_qubits) != len(meas_qubits): raise QiskitError( "prepared_qubits and measured_qubits are different length.") num_qubits = len(meas_qubits) meas_qubit_registers = set(q.register for q in meas_qubits) # Check qubits being measured are defined in circuit for reg in meas_qubit_registers: if reg not in circuit.qregs: logger.warning('WARNING: circuit does not contain ' 'measured QuantumRegister: %s', reg.name) prep_qubit_registers = set(q.register for q in prep_qubits) # Check qubits being measured are defined in circuit for reg in prep_qubit_registers: if reg not in circuit.qregs: logger.warning('WARNING: circuit does not contain ' 'prepared QuantumRegister: %s', reg.name) # Get combined registers qubit_registers = prep_qubit_registers.union(meas_qubit_registers) # Check if there are already measurements in the circuit for op in circuit: if isinstance(op, Measure): logger.warning('WARNING: circuit already contains measurements') if isinstance(op, Reset): logger.warning('WARNING: circuit contains resets') # Load built-in circuit functions if callable(meas_basis): measurement = meas_basis else: measurement = default_basis(meas_basis) if isinstance(measurement, TomographyBasis): if measurement.measurement is not True: raise QiskitError("Invalid measurement basis") measurement = measurement.measurement_circuit if callable(prep_basis): preparation = prep_basis else: preparation = default_basis(prep_basis) if isinstance(preparation, TomographyBasis): if preparation.preparation is not True: raise QiskitError("Invalid preparation basis") preparation = preparation.preparation_circuit # Check we have circuit functions defined if measurement is None and meas_labels is not None: raise ValueError("Measurement basis is not specified.") if preparation is None and prep_labels is not None: raise ValueError("Preparation basis is not specified.") # Load built-in basis labels if isinstance(meas_labels, str): meas_labels = _default_measurement_labels(meas_labels) if isinstance(prep_labels, str): prep_labels = _default_preparation_labels(prep_labels) # Generate n-qubit labels meas_labels = _generate_labels(meas_labels, num_qubits) prep_labels = _generate_labels(prep_labels, num_qubits) # Note if the input circuit already has classical registers defined # the returned circuits add a new classical register for the tomography # measurements which will be inserted as the first classical register in # the list of returned circuits. registers = qubit_registers.copy() if measurement is not None: clbits = ClassicalRegister(num_qubits) registers.add(clbits) # Generate the circuits qst_circs = [] for prep_label in prep_labels: prep = QuantumCircuit(*registers) # Generate preparation circuit if prep_label is not None: for j in range(num_qubits): prep += preparation(prep_label[j], prep_qubits[j]) prep.barrier(*qubit_registers) # Add circuit being tomographed prep += circuit # Generate Measurement circuit for meas_label in meas_labels: meas = QuantumCircuit(*registers) if meas_label is not None: meas.barrier(*qubit_registers) for j in range(num_qubits): meas += measurement(meas_label[j], meas_qubits[j], clbits[j]) circ = prep + meas if prep_label is None: # state tomography circuit circ.name = str(meas_label) else: # process tomography circuit circ.name = str((prep_label, meas_label)) qst_circs.append(circ) return qst_circs ########################################################################### # Built-in circuit functions ########################################################################### def default_basis(basis: Optional[Union[str, TomographyBasis]]) -> TomographyBasis: """Built in Tomography Bases. Args: basis: the tomography basis. Raises: ValueError: In case the given basis is not recognized Returns: The requested tomography basis. Additional Information: If the input basis is ``None`` or a :class:`TomographyBasis` it will be returned unchanged. If it is a `"Pauli"` or `"SIC"` it will return the built in tomography basis object for that basis. """ if basis is None: return None if isinstance(basis, str): if basis == 'Pauli': return PauliBasis if basis == 'SIC': return SICBasis if isinstance(basis, TomographyBasis): return basis raise ValueError('Unrecognized basis: {}'.format(basis)) def _default_measurement_labels(basis: Union[str, TomographyBasis] ) -> Tuple[str]: """Built in measurement basis labels. Args: basis: A tomography basis or a name of standard one. Raises: ValueError: In case the basis is not recognized Returns: In case the base is Pauli: The labels ('X','Y','Z'). """ if default_basis(basis) == PauliBasis: return ('X', 'Y', 'Z') raise ValueError('Unrecognized basis string "{}"'.format(basis)) def _default_preparation_labels(basis: Union[str, TomographyBasis] ) -> Tuple[str]: """Built in preparation basis labels. Args: basis: A tomography basis or a name of standard one. Raises: ValueError: In case the basis is not recognized Returns: - In case the base is Pauli: The labels ('Zp', 'Zm', 'Xp', 'Yp'). - In case the base is SIC: The labels ('S0', 'S1', 'S2', 'S3'). """ tomo_basis = default_basis(basis) if tomo_basis == PauliBasis: return ('Zp', 'Zm', 'Xp', 'Yp') if tomo_basis == SICBasis: return ('S0', 'S1', 'S2', 'S3') raise ValueError('Unrecognized basis string "{}"'.format(basis)) ########################################################################### # Helper functions ########################################################################### def tomography_circuit_tuples( measured_qubits: Union[int, QuantumRegister, List[QuantumRegister]], meas_labels: Union[str, TomographyBasis, Tuple] = 'Pauli', prep_labels: Optional[Union[str, TomographyBasis, Tuple]] = None ) -> List: r"""Return list of tomography circuit label tuples. Args: measured_qubits: Either qubits the tomography will be applied to or their length meas_labels: (default: 'Pauli') The measurement basis labels or the basis itself. prep_labels: The preparation basis labels or the basis itself. Returns: A list of all pairs :math:`[m_l, p_k]` where :math:`m_1,\ldots m_t` are all the n-qubit measurement labels and :math:`p_1,\ldots, p_s` are all the n-qubit preparation labels Note that if prep_labels are empty, it will be considered as containing only the empty string "", so a list with all measurement lables will still be generated. """ if isinstance(meas_labels, (str, TomographyBasis)): meas_labels = _default_measurement_labels(meas_labels) if isinstance(prep_labels, (str, TomographyBasis)): prep_labels = _default_preparation_labels(prep_labels) mls = _generate_labels(meas_labels, measured_qubits) pls = _generate_labels(prep_labels, measured_qubits) return [(ml, pl) for pl, ml in it.product(mls, pls)] def _generate_labels(labels: Optional[Union[Tuple[str], List[Tuple[str]], str]], measured_qubits: Union[int, QuantumRegister, List[QuantumRegister]] ) -> List[Tuple[str]]: """Return list of n-qubit measurement circuit labels. Args: labels: A tuple of the basis labels, or a string of the basis labels separated by spaces, or a list of pre-made tuples. measured_qubits: Either qubits the tomography will be applied to or their length Raises: ValueError: if the label specification is wrong. Returns: A list of length n-tuples containing all possible combinations of the given labels. """ if labels is None: return [None] # Generate n-qubit tuples for single qubit tuples if isinstance(labels, tuple): labels = _operator_tuples(labels, measured_qubits) if isinstance(labels, str): labels = _operator_tuples(labels.split(" "), measured_qubits) if isinstance(labels, list): return labels raise ValueError( 'Invalid labels specification: must be None, list, string, or tuple') def _format_registers(*registers: Union[Qubit, QuantumRegister] ) -> List[Qubit]: """Return a list of qubit QuantumRegister tuples. Args: registers: Any nonzero number of qubits or quantum registers, all unique Raises: QiskitError: If no qubits/registers were passed or non-unique qubits passed Returns: A flat list of all qubits passed. """ if not registers: raise QiskitError('No registers are being measured!') qubits = [] for tuple_element in registers: if isinstance(tuple_element, QuantumRegister): for j in range(tuple_element.size): qubits.append(tuple_element[j]) else: qubits.append(tuple_element) # Check registers are unique if len(qubits) != len(set(qubits)): raise QiskitError('Qubits to be measured are not unique!') return qubits def _operator_tuples(labels: Tuple[str], qubits: Union[int, QuantumRegister, List[QuantumRegister]] ) -> List[Tuple[str]]: """Return a list of all length-n tuples of labels. Args: labels: The basis labels to build tuples from qubits: Either qubits the tomography will be applied to or their length Returns: All n-length sequences of elements of **labels**. For example, if **labels** = ('X', 'Y', 'Z') and the number of qubits n=2, then the result will be the list [('X', 'X'), ('X', 'Y'), ('X', 'Z'), ('Y', 'X'), ('Y', 'Y'), ('Y', 'Z'), ('Z', 'X'), ('Z', 'Y'), ('Z', 'Z')] """ if isinstance(qubits, int): num_qubits = qubits elif isinstance(qubits, list): num_qubits = len(_format_registers(*qubits)) else: num_qubits = len(_format_registers(qubits)) return list(it.product(labels, repeat=num_qubits))