From f83dcd742233ab14b1d68ff970f054f7842fbba9 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 29 Feb 2024 16:02:17 -0600 Subject: [PATCH 1/5] Add an equivalence library for Eagle devices --- circuit_knitting/utils/equivalence.py | 144 ++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 circuit_knitting/utils/equivalence.py diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py new file mode 100644 index 000000000..9eee86415 --- /dev/null +++ b/circuit_knitting/utils/equivalence.py @@ -0,0 +1,144 @@ +# This code is a Qiskit project. + +# (C) Copyright IBM 2024. + +# 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. + +""" +Equivalence utilities. + +.. currentmodule:: circuit_knitting.utils.equivalence + +.. autosummary:: + :toctree: ../stubs/ + +""" + +import numpy as np +from qiskit.circuit import EquivalenceLibrary, QuantumCircuit, QuantumRegister, Parameter +from qiskit.circuit.library.standard_gates import ( + RZGate, + XGate, + YGate, + ZGate, + HGate, + SGate, + IGate, + SdgGate, + SXGate, + SXdgGate, + TGate, + TdgGate, + RXGate, + RYGate, + PhaseGate, +) + +_eagle_sel = EagleEquivalenceLibrary = EquivalenceLibrary() + +########## Single-qubit Eagle native gate set: x, sx, rz, i ########## +# XGate +q = QuantumRegister(1, "q") +def_x = QuantumCircuit(q) +def_x.append(XGate(), [0], []) +_eagle_sel.add_equivalence(XGate(), def_x) + +# SXGate +q = QuantumRegister(1, "q") +def_sx = QuantumCircuit(q) +def_sx.append(SXGate(), [0], []) +_eagle_sel.add_equivalence(SXGate(), def_sx) + +# RZGate +q = QuantumRegister(1, "q") +def_rz = QuantumCircuit(q) +theta = Parameter("theta") +def_rz.append(RZGate(theta), [0], []) +_eagle_sel.add_equivalence(RZGate(theta), def_rz) + +# IGate +q = QuantumRegister(1, "q") +def_i = QuantumCircuit(q) +def_i.append(IGate(), [0], []) +_eagle_sel.add_equivalence(IGate(), def_i) + +###################################################################### + +# YGate +q = QuantumRegister(1, "q") +def_y = QuantumCircuit(q) +for inst in [RZGate(np.pi), XGate()]: + def_y.append(inst, [0], []) +_eagle_sel.add_equivalence(YGate(), def_y) + +# ZGate +q = QuantumRegister(1, "q") +def_z = QuantumCircuit(q) +def_z.append(RZGate(np.pi), [0], []) +_eagle_sel.add_equivalence(ZGate(), def_z) + +# HGate +q = QuantumRegister(1, "q") +def_h = QuantumCircuit(q) +for inst in [RZGate(np.pi/2), SXGate(), RZGate(np.pi/2)]: + def_h.append(inst, [0], []) +_eagle_sel.add_equivalence(HGate(), def_h) + +# SGate +q = QuantumRegister(1, "q") +def_s = QuantumCircuit(q) +def_s.append(RZGate(np.pi/2), [0], []) +_eagle_sel.add_equivalence(SGate(), def_s) + +# SdgGate +q = QuantumRegister(1, "q") +def_sdg = QuantumCircuit(q) +def_sdg.append(RZGate(-np.pi/2), [0], []) +_eagle_sel.add_equivalence(SdgGate(), def_sdg) + +# SXdgGate +q = QuantumRegister(1, "q") +def_sxdg = QuantumCircuit(q) +for inst in [RZGate(np.pi/2), RZGate(np.pi/2), SXGate(), RZGate(np.pi/2), RZGate(np.pi/2)]: + def_sxdg.append(inst, [0], []) +_eagle_sel.add_equivalence(SXdgGate(), def_sxdg) + +# TGate +q = QuantumRegister(1, "q") +def_t = QuantumCircuit(q) +def_t.append(RZGate(np.pi/4), [0], []) +_eagle_sel.add_equivalence(TGate(), def_t) + +# TdgGate +q = QuantumRegister(1, "q") +def_tdg = QuantumCircuit(q) +def_tdg.append(RZGate(-np.pi/4), [0], []) +_eagle_sel.add_equivalence(TdgGate(), def_tdg) + +# RXGate +q = QuantumRegister(1, "q") +def_rx = QuantumCircuit(q) +theta = Parameter("theta") +for inst in [RZGate(np.pi/2), SXGate(), RZGate(theta+np.pi), RZGate(5*np.pi/2)]: + def_rx.append(inst, [0], []) +_eagle_sel.add_equivalence(RXGate(theta), def_rx) + +# RYGate +q = QuantumRegister(1, "q") +def_ry = QuantumCircuit(q) +theta = Parameter("theta") +for inst in [SXGate(), RZGate(theta+np.pi), SXGate(), RZGate(3*np.pi)]: + def_ry.append(inst, [0], []) +_eagle_sel.add_equivalence(RYGate(theta), def_ry) + +# PhaseGate +q = QuantumRegister(1, "q") +def_p = QuantumCircuit(q) +theta = Parameter("theta") +def_p.append(RZGate(theta), [0], []) +_eagle_sel.add_equivalence(PhaseGate(theta), def_p) From bd32f393660190deea736219bf366a063018f076 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 29 Feb 2024 16:13:24 -0600 Subject: [PATCH 2/5] Add Heron library. Translate gates inside qpd module --- circuit_knitting/cutting/qpd/qpd.py | 8 ++++++- circuit_knitting/utils/__init__.py | 6 ++++++ circuit_knitting/utils/equivalence.py | 31 ++++++++++++++++++--------- 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/circuit_knitting/cutting/qpd/qpd.py b/circuit_knitting/cutting/qpd/qpd.py index dc56e3e3e..de8fda455 100644 --- a/circuit_knitting/cutting/qpd/qpd.py +++ b/circuit_knitting/cutting/qpd/qpd.py @@ -73,6 +73,7 @@ from .instructions import BaseQPDGate, TwoQubitQPDGate, QPDMeasure from ..instructions import Move from ...utils.iteration import unique_by_id, strict_zip +from ...utils.equivalence import EagleEquivalenceLibrary logger = logging.getLogger(__name__) @@ -1143,7 +1144,12 @@ def _decompose_qpd_instructions( for data in inst.operation.definition.data: # Can ignore clbits here, as QPDGates don't use clbits directly assert data.clbits == () - tmp_data.append(CircuitInstruction(data.operation, qubits=[qubits[0]])) + try: + equiv = EagleEquivalenceLibrary.get_entry(data.operation)[0] + for d in equiv.data: + tmp_data.append(CircuitInstruction(d.operation, qubits=[qubits[0]])) + except IndexError: + tmp_data.append(CircuitInstruction(data.operation, qubits=[qubits[0]])) # Replace QPDGate with local operations if tmp_data: # Overwrite the QPDGate with first instruction diff --git a/circuit_knitting/utils/__init__.py b/circuit_knitting/utils/__init__.py index 5c41eab99..95368cf79 100644 --- a/circuit_knitting/utils/__init__.py +++ b/circuit_knitting/utils/__init__.py @@ -59,4 +59,10 @@ =================================================================== .. automodule:: circuit_knitting.utils.transpiler_passes + +=================================================================== +Gate equivalence rules (:mod:`circuit_knitting.utils.equivalence`) +=================================================================== + +.. automodule:: circuit_knitting.utils.equivalence """ diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py index 9eee86415..3f7be93ce 100644 --- a/circuit_knitting/utils/equivalence.py +++ b/circuit_knitting/utils/equivalence.py @@ -20,7 +20,12 @@ """ import numpy as np -from qiskit.circuit import EquivalenceLibrary, QuantumCircuit, QuantumRegister, Parameter +from qiskit.circuit import ( + EquivalenceLibrary, + QuantumCircuit, + QuantumRegister, + Parameter, +) from qiskit.circuit.library.standard_gates import ( RZGate, XGate, @@ -39,7 +44,7 @@ PhaseGate, ) -_eagle_sel = EagleEquivalenceLibrary = EquivalenceLibrary() +_eagle_sel = HeronEquivalenceLibrary = EagleEquivalenceLibrary = EquivalenceLibrary() ########## Single-qubit Eagle native gate set: x, sx, rz, i ########## # XGate @@ -85,46 +90,52 @@ # HGate q = QuantumRegister(1, "q") def_h = QuantumCircuit(q) -for inst in [RZGate(np.pi/2), SXGate(), RZGate(np.pi/2)]: +for inst in [RZGate(np.pi / 2), SXGate(), RZGate(np.pi / 2)]: def_h.append(inst, [0], []) _eagle_sel.add_equivalence(HGate(), def_h) # SGate q = QuantumRegister(1, "q") def_s = QuantumCircuit(q) -def_s.append(RZGate(np.pi/2), [0], []) +def_s.append(RZGate(np.pi / 2), [0], []) _eagle_sel.add_equivalence(SGate(), def_s) # SdgGate q = QuantumRegister(1, "q") def_sdg = QuantumCircuit(q) -def_sdg.append(RZGate(-np.pi/2), [0], []) +def_sdg.append(RZGate(-np.pi / 2), [0], []) _eagle_sel.add_equivalence(SdgGate(), def_sdg) # SXdgGate q = QuantumRegister(1, "q") def_sxdg = QuantumCircuit(q) -for inst in [RZGate(np.pi/2), RZGate(np.pi/2), SXGate(), RZGate(np.pi/2), RZGate(np.pi/2)]: +for inst in [ + RZGate(np.pi / 2), + RZGate(np.pi / 2), + SXGate(), + RZGate(np.pi / 2), + RZGate(np.pi / 2), +]: def_sxdg.append(inst, [0], []) _eagle_sel.add_equivalence(SXdgGate(), def_sxdg) # TGate q = QuantumRegister(1, "q") def_t = QuantumCircuit(q) -def_t.append(RZGate(np.pi/4), [0], []) +def_t.append(RZGate(np.pi / 4), [0], []) _eagle_sel.add_equivalence(TGate(), def_t) # TdgGate q = QuantumRegister(1, "q") def_tdg = QuantumCircuit(q) -def_tdg.append(RZGate(-np.pi/4), [0], []) +def_tdg.append(RZGate(-np.pi / 4), [0], []) _eagle_sel.add_equivalence(TdgGate(), def_tdg) # RXGate q = QuantumRegister(1, "q") def_rx = QuantumCircuit(q) theta = Parameter("theta") -for inst in [RZGate(np.pi/2), SXGate(), RZGate(theta+np.pi), RZGate(5*np.pi/2)]: +for inst in [RZGate(np.pi / 2), SXGate(), RZGate(theta + np.pi), RZGate(5 * np.pi / 2)]: def_rx.append(inst, [0], []) _eagle_sel.add_equivalence(RXGate(theta), def_rx) @@ -132,7 +143,7 @@ q = QuantumRegister(1, "q") def_ry = QuantumCircuit(q) theta = Parameter("theta") -for inst in [SXGate(), RZGate(theta+np.pi), SXGate(), RZGate(3*np.pi)]: +for inst in [SXGate(), RZGate(theta + np.pi), SXGate(), RZGate(3 * np.pi)]: def_ry.append(inst, [0], []) _eagle_sel.add_equivalence(RYGate(theta), def_ry) From 65fcdb48b429c75918b70d2fd74bef0b60cb4ef4 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 29 Feb 2024 16:20:47 -0600 Subject: [PATCH 3/5] Clean up try block --- circuit_knitting/cutting/qpd/qpd.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/circuit_knitting/cutting/qpd/qpd.py b/circuit_knitting/cutting/qpd/qpd.py index de8fda455..5290f5f5d 100644 --- a/circuit_knitting/cutting/qpd/qpd.py +++ b/circuit_knitting/cutting/qpd/qpd.py @@ -1146,10 +1146,11 @@ def _decompose_qpd_instructions( assert data.clbits == () try: equiv = EagleEquivalenceLibrary.get_entry(data.operation)[0] - for d in equiv.data: - tmp_data.append(CircuitInstruction(d.operation, qubits=[qubits[0]])) except IndexError: tmp_data.append(CircuitInstruction(data.operation, qubits=[qubits[0]])) + else: + for d in equiv.data: + tmp_data.append(CircuitInstruction(d.operation, qubits=[qubits[0]])) # Replace QPDGate with local operations if tmp_data: # Overwrite the QPDGate with first instruction From 512060f10eb8529c9358e1e892e776b2f6c0d41b Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 29 Feb 2024 17:01:05 -0600 Subject: [PATCH 4/5] Dont use try except --- circuit_knitting/cutting/qpd/qpd.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/circuit_knitting/cutting/qpd/qpd.py b/circuit_knitting/cutting/qpd/qpd.py index 5290f5f5d..79573ad18 100644 --- a/circuit_knitting/cutting/qpd/qpd.py +++ b/circuit_knitting/cutting/qpd/qpd.py @@ -1144,12 +1144,12 @@ def _decompose_qpd_instructions( for data in inst.operation.definition.data: # Can ignore clbits here, as QPDGates don't use clbits directly assert data.clbits == () - try: - equiv = EagleEquivalenceLibrary.get_entry(data.operation)[0] - except IndexError: + equiv = EagleEquivalenceLibrary.get_entry(data.operation)[0] + if equiv == []: tmp_data.append(CircuitInstruction(data.operation, qubits=[qubits[0]])) else: - for d in equiv.data: + # CKT equivalence libraries only define one mapping per input + for d in equiv[0].data: tmp_data.append(CircuitInstruction(d.operation, qubits=[qubits[0]])) # Replace QPDGate with local operations if tmp_data: From 73d8ab9d8e4574097307453d0a2ac75f36486f5e Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 28 Mar 2024 16:58:29 -0500 Subject: [PATCH 5/5] Fix the interface --- .../cutting/cutting_experiments.py | 9 +- circuit_knitting/cutting/qpd/decompose.py | 265 ++++++++++++++++++ circuit_knitting/utils/equivalence.py | 4 + ...ow_to_translate_sampled_instructions.ipynb | 148 ++++++++++ test/cutting/qpd/test_qpd.py | 34 ++- test/cutting/test_cutting_experiments.py | 28 +- test/utils/test_equivalence.py | 43 +++ 7 files changed, 525 insertions(+), 6 deletions(-) create mode 100644 circuit_knitting/cutting/qpd/decompose.py create mode 100644 docs/circuit_cutting/how-tos/how_to_translate_sampled_instructions.ipynb create mode 100644 test/utils/test_equivalence.py diff --git a/circuit_knitting/cutting/cutting_experiments.py b/circuit_knitting/cutting/cutting_experiments.py index ae434d2a2..8c8f0fd24 100644 --- a/circuit_knitting/cutting/cutting_experiments.py +++ b/circuit_knitting/cutting/cutting_experiments.py @@ -41,6 +41,7 @@ def generate_cutting_experiments( circuits: QuantumCircuit | dict[Hashable, QuantumCircuit], observables: PauliList | dict[Hashable, PauliList], num_samples: int | float, + translate_to_qpu: str | None = None, ) -> tuple[ list[QuantumCircuit] | dict[Hashable, list[QuantumCircuit]], list[tuple[float, WeightType]], @@ -74,6 +75,8 @@ def generate_cutting_experiments( num_samples: The number of samples to draw from the quasi-probability distribution. If set to infinity, the weights will be generated rigorously rather than by sampling from the distribution. + translate_to_qpu: A QPU architecture for which the sampled instructions should be + translated. Supported inputs are: {"heron", "eagle", None} Returns: A tuple containing the cutting experiments and their associated coefficients. If the input circuits is a :class:`QuantumCircuit` instance, the output subexperiments @@ -161,7 +164,11 @@ def generate_cutting_experiments( for j, cog in enumerate(so.groups): new_qc = _append_measurement_register(subcircuit, cog) decompose_qpd_instructions( - new_qc, subcirc_qpd_gate_ids[label], map_ids_tmp, inplace=True + new_qc, + subcirc_qpd_gate_ids[label], + map_ids_tmp, + translate_to_qpu=translate_to_qpu, + inplace=True, ) _append_measurement_circuit(new_qc, cog, inplace=True) subexperiments_dict[label].append(new_qc) diff --git a/circuit_knitting/cutting/qpd/decompose.py b/circuit_knitting/cutting/qpd/decompose.py new file mode 100644 index 000000000..a0b76d588 --- /dev/null +++ b/circuit_knitting/cutting/qpd/decompose.py @@ -0,0 +1,265 @@ +# This code is a Qiskit project. + +# (C) Copyright IBM 2024. + +# 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 replace all QPD instructions in the circuit with local Qiskit operations and measurements.""" + +from __future__ import annotations + +from collections.abc import Sequence + +from qiskit.circuit import ( + QuantumCircuit, + ClassicalRegister, + CircuitInstruction, + Measure, +) + +from .instructions import BaseQPDGate, TwoQubitQPDGate +from ...utils.equivalence import equivalence_libraries + + +def decompose_qpd_instructions( + circuit: QuantumCircuit, + instruction_ids: Sequence[Sequence[int]], + map_ids: Sequence[int] | None = None, + *, + translate_to_qpu: str | None = None, + inplace: bool = False, +) -> QuantumCircuit: + r""" + Replace all QPD instructions in the circuit with local Qiskit operations and measurements. + + Args: + circuit: The circuit containing QPD instructions + instruction_ids: A 2D sequence, such that each inner sequence corresponds to indices + of instructions comprising one decomposition in the circuit. The elements within a + common sequence belong to a common decomposition and should be sampled together. + map_ids: Indices to a specific linear mapping to be applied to the decompositions + in the circuit. If no map IDs are provided, the circuit will be decomposed randomly + according to the decompositions' joint probability distribution. + translate_to_qpu: A QPU architecture for which the sampled instructions should be + translated. Supported inputs are: {"heron", "eagle", None} + inplace: Whether to modify the input circuit directly + + Returns: + Circuit which has had all its :class:`BaseQPDGate` instances decomposed into local operations. + + The circuit will contain a new, final classical register to contain the QPD measurement + outcomes (accessible at ``retval.cregs[-1]``). + + Raises: + ValueError: An index in ``instruction_ids`` corresponds to a gate which is not a + :class:`BaseQPDGate` instance. + ValueError: A list within instruction_ids is not length 1 or 2. + ValueError: The total number of indices in ``instruction_ids`` does not equal the number + of :class:`BaseQPDGate` instances in the circuit. + ValueError: Gates within the same decomposition hold different QPD bases. + ValueError: Length of ``map_ids`` does not equal the number of decompositions in the circuit. + """ + _validate_qpd_instructions(circuit, instruction_ids) + + if not inplace: + circuit = circuit.copy() # pragma: no cover + + if map_ids is not None: + if len(instruction_ids) != len(map_ids): + raise ValueError( + f"The number of map IDs ({len(map_ids)}) must equal the number of " + f"decompositions in the circuit ({len(instruction_ids)})." + ) + # If mapping is specified, set each gate's mapping + for i, decomp_gate_ids in enumerate(instruction_ids): + for gate_id in decomp_gate_ids: + circuit.data[gate_id].operation.basis_id = map_ids[i] + + # Convert all instances of BaseQPDGate in the circuit to Qiskit instructions + _decompose_qpd_instructions( + circuit, instruction_ids, translate_to_qpu=translate_to_qpu + ) + + return circuit + + +def _validate_qpd_instructions( + circuit: QuantumCircuit, instruction_ids: Sequence[Sequence[int]] +): + """Ensure the indices in instruction_ids correctly describe all the decompositions in the circuit.""" + # Make sure all instruction_ids correspond to QPDGates, and make sure each QPDGate in a given decomposition has + # an equivalent QPDBasis to its sibling QPDGates + for decomp_ids in instruction_ids: + if len(decomp_ids) not in [1, 2]: + raise ValueError( + "Each decomposition must contain either one or two elements. Found a " + f"decomposition with ({len(decomp_ids)}) elements." + ) + if not isinstance(circuit.data[decomp_ids[0]].operation, BaseQPDGate): + raise ValueError( + f"A circuit data index ({decomp_ids[0]}) corresponds to a non-QPDGate " + f"({circuit.data[decomp_ids[0]].operation.name})." + ) + compare_basis = circuit.data[decomp_ids[0]].operation.basis + for gate_id in decomp_ids: + if not isinstance(circuit.data[gate_id].operation, BaseQPDGate): + raise ValueError( + f"A circuit data index ({gate_id}) corresponds to a non-QPDGate " + f"({circuit.data[gate_id].operation.name})." + ) + tmp_basis = circuit.data[gate_id].operation.basis + if compare_basis != tmp_basis: + raise ValueError( + "Gates within the same decomposition must share an equivalent QPDBasis." + ) + + # Make sure the total number of QPD gate indices equals the number of QPDGates in the circuit + num_qpd_gates = sum(len(x) for x in instruction_ids) + qpd_gate_total = 0 + for inst in circuit.data: + if isinstance(inst.operation, BaseQPDGate): + qpd_gate_total += 1 + if qpd_gate_total != num_qpd_gates: + raise ValueError( + f"The total number of QPDGates specified in instruction_ids ({num_qpd_gates}) " + f"does not equal the number of QPDGates in the circuit ({qpd_gate_total})." + ) + + +def _decompose_qpd_measurements( + circuit: QuantumCircuit, inplace: bool = True +) -> QuantumCircuit: + """ + Create mid-circuit measurements. + + Convert all QPDMeasure instances to Measure instructions. Add any newly created + classical bits to a new "qpd_measurements" register. + """ + if not inplace: + circuit = circuit.copy() # pragma: no cover + + # Loop through the decomposed circuit to find QPDMeasure markers so we can + # replace them with measurement instructions. We can't use `_ids` + # here because it refers to old indices, before the decomposition. + qpd_measure_ids = [ + i + for i, instruction in enumerate(circuit.data) + if instruction.operation.name.lower() == "qpd_measure" + ] + + # Create a classical register for the qpd measurement results. This is + # partly for convenience, partly to work around + # https://github.com/Qiskit/qiskit-aer/issues/1660. + reg = ClassicalRegister(len(qpd_measure_ids), name="qpd_measurements") + circuit.add_register(reg) + + # Place the measurement instructions + for idx, i in enumerate(qpd_measure_ids): + gate = circuit.data[i] + inst = CircuitInstruction( + operation=Measure(), qubits=[gate.qubits], clbits=[reg[idx]] + ) + circuit.data[i] = inst + + # If the user wants to access the qpd register, it will be the final + # classical register of the returned circuit. + assert circuit.cregs[-1] is reg + + return circuit + + +def _decompose_qpd_instructions( + circuit: QuantumCircuit, + instruction_ids: Sequence[Sequence[int]], + inplace: bool = True, + translate_to_qpu: str | None = None, +) -> QuantumCircuit: + """Decompose all BaseQPDGate instances, ignoring QPDMeasure().""" + if not inplace: + circuit = circuit.copy() # pragma: no cover + + # Decompose any 2q QPDGates into single qubit QPDGates + qpdgate_ids_2q = [] + for decomp in instruction_ids: + if len(decomp) != 1: + continue # pragma: no cover + if isinstance(circuit.data[decomp[0]].operation, TwoQubitQPDGate): + qpdgate_ids_2q.append(decomp[0]) + + qpdgate_ids_2q = sorted(qpdgate_ids_2q) + data_id_offset = 0 + for i in qpdgate_ids_2q: + inst = circuit.data[i + data_id_offset] + qpdcirc_2q_decomp = inst.operation.definition + inst1 = CircuitInstruction( + qpdcirc_2q_decomp.data[0].operation, qubits=[inst.qubits[0]] + ) + inst2 = CircuitInstruction( + qpdcirc_2q_decomp.data[1].operation, qubits=[inst.qubits[1]] + ) + circuit.data[i + data_id_offset] = inst1 + data_id_offset += 1 + circuit.data.insert(i + data_id_offset, inst2) + + # Get equivalence library + if translate_to_qpu is not None: + translate_to_qpu = translate_to_qpu.lower() + else: + translate_to_qpu = "standard" + equivalence = equivalence_libraries[translate_to_qpu] + + # Decompose all the QPDGates (should all be single qubit now) into Qiskit operations + new_instruction_ids = [] + for i, inst in enumerate(circuit.data): + if isinstance(inst.operation, BaseQPDGate): + new_instruction_ids.append(i) + data_id_offset = 0 + for i in new_instruction_ids: + inst = circuit.data[i + data_id_offset] + qubits = inst.qubits + # All gates in decomposition should be local + assert len(qubits) == 1 + # Gather instructions with which we will replace the QPDGate + tmp_data = [] + for data in inst.operation.definition.data: + # Can ignore clbits here, as QPDGates don't use clbits directly + assert data.clbits == () + if equivalence is None: + tmp_data.append(CircuitInstruction(data.operation, qubits=[qubits[0]])) + else: + equiv_entry = equivalence.get_entry(data.operation) + # CKT SELs currently only provide at most one translation + assert len(equiv_entry) <= 1 + if equiv_entry == []: + tmp_data.append( + CircuitInstruction(data.operation, qubits=[qubits[0]]) + ) + else: + new_insts = equiv_entry[0] + for d in new_insts.data: + tmp_data.append( + CircuitInstruction(d.operation, qubits=[qubits[0]]) + ) + + # Replace QPDGate with local operations + if tmp_data: + # Overwrite the QPDGate with first instruction + circuit.data[i + data_id_offset] = tmp_data[0] + # Append remaining instructions immediately after original QPDGate position + for data in tmp_data[1:]: + data_id_offset += 1 + circuit.data.insert(i + data_id_offset, data) + + # If QPDGate decomposes to an identity operation, just delete it + else: + del circuit.data[i + data_id_offset] + data_id_offset -= 1 + + _decompose_qpd_measurements(circuit) + + return circuit diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py index 3f7be93ce..901cd10e7 100644 --- a/circuit_knitting/utils/equivalence.py +++ b/circuit_knitting/utils/equivalence.py @@ -18,6 +18,7 @@ :toctree: ../stubs/ """ +from collections import defaultdict import numpy as np from qiskit.circuit import ( @@ -45,6 +46,9 @@ ) _eagle_sel = HeronEquivalenceLibrary = EagleEquivalenceLibrary = EquivalenceLibrary() +equivalence_libraries = defaultdict( + lambda: None, {"heron": EagleEquivalenceLibrary, "eagle": EagleEquivalenceLibrary} +) ########## Single-qubit Eagle native gate set: x, sx, rz, i ########## # XGate diff --git a/docs/circuit_cutting/how-tos/how_to_translate_sampled_instructions.ipynb b/docs/circuit_cutting/how-tos/how_to_translate_sampled_instructions.ipynb new file mode 100644 index 000000000..66447500f --- /dev/null +++ b/docs/circuit_cutting/how-tos/how_to_translate_sampled_instructions.ipynb @@ -0,0 +1,148 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f9e40036", + "metadata": {}, + "source": [ + "## How to translate sampled instructions\n", + "\n", + "This how-to guide is intended to show users how they can generate subexperiments which are already translated to a specified QPU architecture. This is useful as it prevents the need for transpiling each individual subexperiment. Users should now be able to transpile the cut circuit a single time and generate subexperiments which are already transpiled for the backend." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "072055cb", + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit import QuantumCircuit\n", + "from qiskit.quantum_info import PauliList\n", + "\n", + "from circuit_knitting.cutting import (\n", + " partition_problem,\n", + " generate_cutting_experiments,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "940334fd", + "metadata": {}, + "source": [ + "Prepare inputs to `generate_cutting_experiments`" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dc4af922", + "metadata": {}, + "outputs": [], + "source": [ + "circuit = QuantumCircuit(2)\n", + "circuit.h(0)\n", + "circuit.cx(0, 1)\n", + "observables = PauliList([\"ZZ\"])\n", + "partitioned_problem = partition_problem(\n", + " circuit=circuit, partition_labels=\"AB\", observables=observables\n", + ")\n", + "subcircuits = partitioned_problem.subcircuits\n", + "subobservables = partitioned_problem.subobservables" + ] + }, + { + "cell_type": "markdown", + "id": "d6361a9d", + "metadata": {}, + "source": [ + "Call `generate_cutting_experiments` and don't specify any translation." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d095701f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subexperiments, coefficients = generate_cutting_experiments(\n", + " circuits=subcircuits,\n", + " observables=subobservables,\n", + " num_samples=1000,\n", + ")\n", + "subexperiments[\"A\"][0].draw(\"mpl\", style=\"iqp\", scale=0.8)" + ] + }, + { + "cell_type": "markdown", + "id": "bc59b1be", + "metadata": {}, + "source": [ + "Now call `generate_cutting_experiments` and translate the sampled instructions to the specified architecture. Valid input arguments are `\"heron\"` and `\"eagle\"`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "7a74f709", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subexperiments, coefficients = generate_cutting_experiments(\n", + " circuits=subcircuits,\n", + " observables=subobservables,\n", + " num_samples=1000,\n", + " translate_to_qpu=\"eagle\",\n", + ")\n", + "subexperiments[\"A\"][0].draw(\"mpl\", style=\"iqp\", scale=0.8)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/test/cutting/qpd/test_qpd.py b/test/cutting/qpd/test_qpd.py index e451e12c2..e916e03cb 100644 --- a/test/cutting/qpd/test_qpd.py +++ b/test/cutting/qpd/test_qpd.py @@ -20,11 +20,18 @@ import numpy as np import numpy.typing as npt from ddt import ddt, data, unpack -from qiskit.circuit import CircuitInstruction +from qiskit.circuit import QuantumCircuit, ClassicalRegister, CircuitInstruction from qiskit.circuit.library import ( EfficientSU2, CXGate, + CYGate, CZGate, + CHGate, + CPhaseGate, + CSGate, + CSdgGate, + CSXGate, + ECRGate, CRXGate, CRYGate, CRZGate, @@ -32,19 +39,27 @@ RYYGate, RZZGate, RZXGate, + SwapGate, + iSwapGate, + DCXGate, ) -from circuit_knitting.utils.iteration import unique_by_eq +from circuit_knitting.utils.iteration import unique_by_eq, strict_zip +from circuit_knitting.cutting.instructions import Move from circuit_knitting.cutting.qpd import ( QPDBasis, SingleQubitQPDGate, TwoQubitQPDGate, + WeightType, generate_qpd_weights, + decompose_qpd_instructions, + qpdbasis_from_instruction, ) -from circuit_knitting.cutting.qpd.qpd import * -from circuit_knitting.cutting.qpd.qpd import ( +from circuit_knitting.cutting.qpd.weights import ( _generate_qpd_weights, _generate_exact_weights_and_conditional_probabilities, +) +from circuit_knitting.cutting.qpd.decompositions import ( _nonlocal_qpd_basis_from_u, _u_from_thetavec, _explicitly_supported_instructions, @@ -150,6 +165,17 @@ def test_decompose_qpd_instructions(self): decomp_circ = decompose_qpd_instructions(circ, [[0]], map_ids=[0]) circ_compare.add_register(ClassicalRegister(0, name="qpd_measurements")) self.assertEqual(decomp_circ, circ_compare) + with self.subTest("Single QPD gate with translation"): + eagle_basis_gate_set = {"id", "rz", "sx", "x", "measure"} + circ = QuantumCircuit(2) + qpd_basis = QPDBasis.from_instruction(RXXGate(np.pi / 3)) + qpd_gate = TwoQubitQPDGate(qpd_basis) + circ.data.append(CircuitInstruction(qpd_gate, qubits=[0, 1])) + decomp_circ = decompose_qpd_instructions( + circ, [[0]], map_ids=[1], translate_to_qpu="eagle" + ) + for inst in decomp_circ.data: + assert inst.operation.name in eagle_basis_gate_set with self.subTest("Incorrect map index size"): with pytest.raises(ValueError) as e_info: decomp_circ = decompose_qpd_instructions( diff --git a/test/cutting/test_cutting_experiments.py b/test/cutting/test_cutting_experiments.py index 6d435f34a..be34fffcb 100644 --- a/test/cutting/test_cutting_experiments.py +++ b/test/cutting/test_cutting_experiments.py @@ -86,7 +86,33 @@ def test_generate_cutting_experiments(self): assert len(coeffs) == len(subexperiments["A"]) for circ in subexperiments["A"]: assert isinstance(circ, QuantumCircuit) - + with self.subTest("translation"): + eagle_basis_gate_set = {"id", "rz", "sx", "x", "measure"} + qc = QuantumCircuit(2) + qc.append( + TwoQubitQPDGate(QPDBasis.from_instruction(CXGate()), label="cut_cx"), + qargs=[0, 1], + ) + comp_coeffs = [ + (0.5, WeightType.EXACT), + (0.5, WeightType.EXACT), + (0.5, WeightType.EXACT), + (-0.5, WeightType.EXACT), + (0.5, WeightType.EXACT), + (-0.5, WeightType.EXACT), + ] + subexperiments, coeffs = generate_cutting_experiments( + qc, + PauliList(["ZZ"]), + np.inf, + translate_to_qpu="eagle", + ) + assert coeffs == comp_coeffs + assert len(coeffs) == len(subexperiments) + for exp in subexperiments: + assert isinstance(exp, QuantumCircuit) + for inst in exp.data: + assert inst.operation.name in eagle_basis_gate_set with self.subTest("test bad num_samples"): qc = QuantumCircuit(4) with pytest.raises(ValueError) as e_info: diff --git a/test/utils/test_equivalence.py b/test/utils/test_equivalence.py new file mode 100644 index 000000000..4aab6d9a7 --- /dev/null +++ b/test/utils/test_equivalence.py @@ -0,0 +1,43 @@ +# This code is a Qiskit project. + +# (C) Copyright IBM 2024. + +# 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. + +"""Tests for CKT equivalence libraries.""" + +import unittest + +import numpy as np +from qiskit.circuit import EquivalenceLibrary +from qiskit.circuit.library.standard_gates import SdgGate + + +from circuit_knitting.utils.equivalence import equivalence_libraries + + +class TestEquivalenceLibraries(unittest.TestCase): + def setUp(self): + self.heron_lib = equivalence_libraries["heron"] + self.eagle_lib = equivalence_libraries["eagle"] + self.standard_lib = equivalence_libraries["standard"] + + def test_equivalence_library_dict(self): + assert isinstance(self.heron_lib, EquivalenceLibrary) + assert isinstance(self.eagle_lib, EquivalenceLibrary) + assert self.standard_lib == None + + def test_equivalence_heron(self): + heron_equivalence = self.heron_lib.get_entry(SdgGate())[0] + assert heron_equivalence.data[0].operation.name == "rz" + assert heron_equivalence.data[0].operation.params == [-np.pi / 2] + + def test_equivalence_eagle(self): + eagle_equivalence = self.eagle_lib.get_entry(SdgGate())[0] + assert eagle_equivalence.data[0].operation.name == "rz" + assert eagle_equivalence.data[0].operation.params == [-np.pi / 2]