Source code for circuit_knitting.cutting.wire_cutting_transforms

# This code is part of Qiskit.
#
# (C) Copyright IBM 2023.
#
# 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.

"""Function to transform a :class:`.CutWire` instruction to a :class:`.Move` instruction."""
from __future__ import annotations

from typing import Callable
from itertools import groupby

import numpy as np
from qiskit.circuit import Qubit, QuantumCircuit, Operation
from qiskit.circuit.exceptions import CircuitError
from qiskit.quantum_info import PauliList

from .instructions import Move
from .qpd.instructions import TwoQubitQPDGate


[docs] def cut_wires(circuit: QuantumCircuit, /) -> QuantumCircuit: r"""Transform all :class:`.CutWire` instructions in a circuit to :class:`.Move` instructions marked for cutting. The returned circuit will have one newly allocated qubit for every :class:`.CutWire` instruction. See Sec. 3 and Appendix A of `2302.03366v1 <https://arxiv.org/abs/2302.03366v1>`__ for more information about the two different representations of wire cuts: single-qubit (:class:`.CutWire`) vs. two-qubit (:class:`.Move`). Args: circuit: Original circuit with :class:`.CutWire` instructions Returns: circuit: New circuit with :class:`.CutWire` instructions replaced by :class:`.Move` instructions wrapped in :class:`TwoQubitQPDGate`\ s """ return _transform_cut_wires( circuit, lambda: TwoQubitQPDGate.from_instruction(Move()) )
def _transform_cuts_to_moves(circuit: QuantumCircuit, /) -> QuantumCircuit: """Transform all :class:`.CutWire` instructions in a circuit to :class:`.Move` instructions. Args: circuit: Original circuit with :class:`.CutWire` instructions Returns: circuit: New circuit with :class:`.CutWire` instructions replaced by :class`.Move` instructions """ return _transform_cut_wires(circuit, Move) def _transform_cut_wires( circuit: QuantumCircuit, factory: Callable[[], Operation], / ) -> QuantumCircuit: new_circuit, mapping = _circuit_structure_mapping(circuit) for instructions in circuit.data: gate_index = [circuit.find_bit(qubit).index for qubit in instructions.qubits] if instructions in circuit.get_instructions("cut_wire"): # Replace cut_wire with move instruction new_circuit.compose( other=factory(), qubits=[mapping[gate_index[0]], mapping[gate_index[0]] + 1], inplace=True, ) mapping[gate_index[0]] += 1 else: new_circuit.compose( other=instructions[0], qubits=[mapping[index] for index in gate_index], inplace=True, ) return new_circuit def _circuit_structure_mapping( circuit: QuantumCircuit, ) -> tuple[QuantumCircuit, list[int]]: new_circuit = QuantumCircuit() mapping = list(range(len(circuit.qubits))) cut_wire_index = [ circuit.find_bit(instruction.qubits[0]).index for instruction in circuit.get_instructions("cut_wire") ] cut_wire_freq = {key: len(list(group)) for key, group in groupby(cut_wire_index)} # Get intermediate mapping and add quantum bits to new_circuit for qubit in circuit.qubits: index = circuit.find_bit(qubit).index if index in cut_wire_freq.keys(): for _ in range(cut_wire_freq[index]): mapping[index + 1 :] = map( lambda item: item + 1, iter(mapping[index + 1 :]) ) new_circuit.add_bits([Qubit()]) new_circuit.add_bits([qubit]) # Add quantum and classical registers for qreg in circuit.qregs: new_circuit.add_register(qreg) new_circuit.add_bits(circuit.clbits) for creg in circuit.cregs: new_circuit.add_register(creg) return new_circuit, mapping
[docs] def expand_observables( observables: PauliList, original_circuit: QuantumCircuit, final_circuit: QuantumCircuit, /, ) -> PauliList: r"""Expand observable(s) according to the qubit mapping between ``original_circuit`` and ``final_circuit``. The :class:`.Qubit`\ s on ``final_circuit`` must be a superset of those on ``original_circuit``. Given a :class:`.PauliList` of observables, this function returns new observables with identity operators placed on the qubits that did not exist in ``original_circuit``. This way, observables on ``original_circuit`` can be mapped to appropriate observables on ``final_circuit``. This function is designed to be used after calling ``final_circuit = transform_cuts_to_moves(original_circuit)`` (see :func:`.transform_cuts_to_moves`). This function requires ``observables.num_qubits == original_circuit.num_qubits`` and returns new observables such that ``retval.num_qubits == final_circuit.num_qubits``. Args: observables: Observables corresponding to ``original_circuit`` original_circuit: Original circuit final_circuit: Final circuit, whose qubits the original ``observables`` should be expanded to Returns: New :math:`N`-qubit observables which are compatible with the :math:`N`-qubit ``final_circuit`` Raises: ValueError: ``observables`` and ``original_circuit`` have different number of qubits. ValueError: Qubit from ``original_circuit`` cannot be found in ``final_circuit``. """ if observables.num_qubits != original_circuit.num_qubits: raise ValueError( "The `observables` and `original_circuit` must have the same number " f"of qubits. ({observables.num_qubits} != {original_circuit.num_qubits})" ) mapping: list[int] = [] for i, qubit in enumerate(original_circuit.qubits): try: idx = final_circuit.find_bit(qubit)[0] except CircuitError as ex: raise ValueError( f"The {i}-th qubit of the `original_circuit` cannot be found " "in the `final_circuit`." ) from ex mapping.append(idx) dims = (len(observables), final_circuit.num_qubits) z = np.full(dims, False) x = np.full(dims, False) z[:, mapping] = observables.z x[:, mapping] = observables.x return PauliList.from_symplectic(z, x, observables.phase.copy())