From 4b819c7d16dd047b879cc8d29c6beef74ed75b8b Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 28 Mar 2024 17:22:00 -0500 Subject: [PATCH 01/20] Add support for generating subexperiments with LO's translated to a native gate set --- .../cutting/cutting_experiments.py | 9 +- circuit_knitting/cutting/qpd/decompose.py | 35 +++- circuit_knitting/utils/__init__.py | 6 + circuit_knitting/utils/equivalence.py | 159 ++++++++++++++++++ ...ow_to_translate_sampled_instructions.ipynb | 148 ++++++++++++++++ ...nslate-sampled-gates-06b301581986fec4.yaml | 4 + test/cutting/qpd/test_qpd.py | 11 ++ test/cutting/test_cutting_experiments.py | 28 ++- test/utils/test_equivalence.py | 43 +++++ 9 files changed, 439 insertions(+), 4 deletions(-) create mode 100644 circuit_knitting/utils/equivalence.py create mode 100644 docs/circuit_cutting/how-tos/how_to_translate_sampled_instructions.ipynb create mode 100644 releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml 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 index 999a66437..a0b76d588 100644 --- a/circuit_knitting/cutting/qpd/decompose.py +++ b/circuit_knitting/cutting/qpd/decompose.py @@ -23,6 +23,7 @@ ) from .instructions import BaseQPDGate, TwoQubitQPDGate +from ...utils.equivalence import equivalence_libraries def decompose_qpd_instructions( @@ -30,6 +31,7 @@ def decompose_qpd_instructions( instruction_ids: Sequence[Sequence[int]], map_ids: Sequence[int] | None = None, *, + translate_to_qpu: str | None = None, inplace: bool = False, ) -> QuantumCircuit: r""" @@ -43,6 +45,9 @@ def decompose_qpd_instructions( 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. @@ -76,7 +81,9 @@ def decompose_qpd_instructions( 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) + _decompose_qpd_instructions( + circuit, instruction_ids, translate_to_qpu=translate_to_qpu + ) return circuit @@ -170,6 +177,7 @@ 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: @@ -198,6 +206,13 @@ def _decompose_qpd_instructions( 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): @@ -214,7 +229,23 @@ 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]])) + 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 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 new file mode 100644 index 000000000..901cd10e7 --- /dev/null +++ b/circuit_knitting/utils/equivalence.py @@ -0,0 +1,159 @@ +# 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/ + +""" +from collections import defaultdict + +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 = HeronEquivalenceLibrary = EagleEquivalenceLibrary = EquivalenceLibrary() +equivalence_libraries = defaultdict( + lambda: None, {"heron": EagleEquivalenceLibrary, "eagle": EagleEquivalenceLibrary} +) + +########## 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) 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/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml b/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml new file mode 100644 index 000000000..d1a58d04e --- /dev/null +++ b/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + The functions, :func:`.generate_qpd_experiments` and :func:`.decompose_qpd_instructions` now take an optional kwarg, ``translate_to_qpu``, which may be used to specify a QPU architecture for which to translate the sampled gates during creation of the subexperiments. This is useful for users who wish to transpile their cut circuit once and have the sampled local operations in the subexperiments be specified in terms of a native gate set. This prevents the need for any transpilation of subexperiments before running on the QPU. diff --git a/test/cutting/qpd/test_qpd.py b/test/cutting/qpd/test_qpd.py index 6d49135a7..e916e03cb 100644 --- a/test/cutting/qpd/test_qpd.py +++ b/test/cutting/qpd/test_qpd.py @@ -165,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] From b89583c8328facc8352d9a01611ebf1944b3ed00 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 28 Mar 2024 17:25:18 -0500 Subject: [PATCH 02/20] Improve release note --- .../notes/translate-sampled-gates-06b301581986fec4.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml b/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml index d1a58d04e..7ffcbb7e4 100644 --- a/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml +++ b/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml @@ -1,4 +1,4 @@ --- features: - | - The functions, :func:`.generate_qpd_experiments` and :func:`.decompose_qpd_instructions` now take an optional kwarg, ``translate_to_qpu``, which may be used to specify a QPU architecture for which to translate the sampled gates during creation of the subexperiments. This is useful for users who wish to transpile their cut circuit once and have the sampled local operations in the subexperiments be specified in terms of a native gate set. This prevents the need for any transpilation of subexperiments before running on the QPU. + The functions, :func:`.generate_qpd_experiments` and :func:`.decompose_qpd_instructions` now take an optional kwarg, ``translate_to_qpu``, which may be used to specify a QPU architecture for which to translate the sampled gates during creation of the subexperiments. This is useful for users who wish to transpile their cut circuit once and have the sampled local operations in the subexperiments be specified in terms of a native gate set. This prevents the need for any transpilation of subexperiments before running on the QPU. The accepted inputs are: ``{"eagle", "heron"}``. From 679fb7303785868eae4e93d55daa2da22f570ecd Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 28 Mar 2024 17:31:48 -0500 Subject: [PATCH 03/20] Don't include basis gates in the equiv library --- circuit_knitting/utils/equivalence.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py index 901cd10e7..7e84812f6 100644 --- a/circuit_knitting/utils/equivalence.py +++ b/circuit_knitting/utils/equivalence.py @@ -50,32 +50,6 @@ lambda: None, {"heron": EagleEquivalenceLibrary, "eagle": EagleEquivalenceLibrary} ) -########## 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 From 43ed2553a735f58871ad0540f6dca6564cdc2584 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 28 Mar 2024 17:38:00 -0500 Subject: [PATCH 04/20] ruff --- circuit_knitting/utils/equivalence.py | 1 - test/utils/test_equivalence.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py index 7e84812f6..4238ee09c 100644 --- a/circuit_knitting/utils/equivalence.py +++ b/circuit_knitting/utils/equivalence.py @@ -34,7 +34,6 @@ ZGate, HGate, SGate, - IGate, SdgGate, SXGate, SXdgGate, diff --git a/test/utils/test_equivalence.py b/test/utils/test_equivalence.py index 4aab6d9a7..98156b104 100644 --- a/test/utils/test_equivalence.py +++ b/test/utils/test_equivalence.py @@ -30,7 +30,7 @@ def setUp(self): def test_equivalence_library_dict(self): assert isinstance(self.heron_lib, EquivalenceLibrary) assert isinstance(self.eagle_lib, EquivalenceLibrary) - assert self.standard_lib == None + assert self.standard_lib is None def test_equivalence_heron(self): heron_equivalence = self.heron_lib.get_entry(SdgGate())[0] From 7a5fe8a1e92054254525b879440656276107e1ff Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 28 Mar 2024 17:41:12 -0500 Subject: [PATCH 05/20] Minor code clarity update --- circuit_knitting/utils/equivalence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py index 4238ee09c..19fc6cfdf 100644 --- a/circuit_knitting/utils/equivalence.py +++ b/circuit_knitting/utils/equivalence.py @@ -46,7 +46,7 @@ _eagle_sel = HeronEquivalenceLibrary = EagleEquivalenceLibrary = EquivalenceLibrary() equivalence_libraries = defaultdict( - lambda: None, {"heron": EagleEquivalenceLibrary, "eagle": EagleEquivalenceLibrary} + lambda: None, {"heron": HeronEquivalenceLibrary, "eagle": EagleEquivalenceLibrary} ) ###################################################################### From c654f6a1c4d0c8725254efa868332265d0ad7ae6 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Sat, 30 Mar 2024 12:17:59 -0500 Subject: [PATCH 06/20] Bug in RXGate. Add to roundtrip tests. --- circuit_knitting/utils/equivalence.py | 2 +- test/cutting/test_cutting_roundtrip.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py index 19fc6cfdf..0241f3fea 100644 --- a/circuit_knitting/utils/equivalence.py +++ b/circuit_knitting/utils/equivalence.py @@ -112,7 +112,7 @@ 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), SXGate(), RZGate(5 * np.pi / 2)]: def_rx.append(inst, [0], []) _eagle_sel.add_equivalence(RXGate(theta), def_rx) diff --git a/test/cutting/test_cutting_roundtrip.py b/test/cutting/test_cutting_roundtrip.py index b6b91c4ba..ca11031fd 100644 --- a/test/cutting/test_cutting_roundtrip.py +++ b/test/cutting/test_cutting_roundtrip.py @@ -142,8 +142,9 @@ def test_cutting_exact_reconstruction(example_circuit): subcircuits, bases, subobservables = partition_problem( qc, "AAB", observables=observables_nophase ) + qpu = [None, "eagle", "heron"][np.random.randint(2)] subexperiments, coefficients = generate_cutting_experiments( - subcircuits, subobservables, num_samples=np.inf + subcircuits, subobservables, num_samples=np.inf, translate_to_qpu=qpu, ) if np.random.randint(2): # Re-use a single sampler From 73bccc4fa2d931ce99c5b2fd2fa8c8a86fc6ff05 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Sat, 30 Mar 2024 12:23:15 -0500 Subject: [PATCH 07/20] Sample standard gate set 50% of time --- test/cutting/test_cutting_roundtrip.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/cutting/test_cutting_roundtrip.py b/test/cutting/test_cutting_roundtrip.py index ca11031fd..42d011a9f 100644 --- a/test/cutting/test_cutting_roundtrip.py +++ b/test/cutting/test_cutting_roundtrip.py @@ -142,9 +142,12 @@ def test_cutting_exact_reconstruction(example_circuit): subcircuits, bases, subobservables = partition_problem( qc, "AAB", observables=observables_nophase ) - qpu = [None, "eagle", "heron"][np.random.randint(2)] + qpu = [None, "eagle", "heron"][np.random.choice([0, 1, 2], p=[0.5, 0.25, 0.25])] subexperiments, coefficients = generate_cutting_experiments( - subcircuits, subobservables, num_samples=np.inf, translate_to_qpu=qpu, + subcircuits, + subobservables, + num_samples=np.inf, + translate_to_qpu=qpu, ) if np.random.randint(2): # Re-use a single sampler From 04720931519ea3ece5c0461891a99664ede1558b Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Sat, 30 Mar 2024 12:25:46 -0500 Subject: [PATCH 08/20] black --- circuit_knitting/utils/equivalence.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py index 0241f3fea..ed129816b 100644 --- a/circuit_knitting/utils/equivalence.py +++ b/circuit_knitting/utils/equivalence.py @@ -112,7 +112,13 @@ q = QuantumRegister(1, "q") def_rx = QuantumCircuit(q) theta = Parameter("theta") -for inst in [RZGate(np.pi / 2), SXGate(), RZGate(theta + np.pi), SXGate(), RZGate(5 * np.pi / 2)]: +for inst in [ + RZGate(np.pi / 2), + SXGate(), + RZGate(theta + np.pi), + SXGate(), + RZGate(5 * np.pi / 2), +]: def_rx.append(inst, [0], []) _eagle_sel.add_equivalence(RXGate(theta), def_rx) From fa0578c05518cd0b2ca1e6a005f63e562ef4be34 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Sat, 30 Mar 2024 12:59:36 -0500 Subject: [PATCH 09/20] theta name --- circuit_knitting/utils/equivalence.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py index ed129816b..6369ba412 100644 --- a/circuit_knitting/utils/equivalence.py +++ b/circuit_knitting/utils/equivalence.py @@ -111,7 +111,7 @@ # RXGate q = QuantumRegister(1, "q") def_rx = QuantumCircuit(q) -theta = Parameter("theta") +theta = Parameter("θ") for inst in [ RZGate(np.pi / 2), SXGate(), @@ -125,7 +125,7 @@ # RYGate q = QuantumRegister(1, "q") def_ry = QuantumCircuit(q) -theta = Parameter("theta") +theta = Parameter("θ") 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) @@ -133,6 +133,6 @@ # PhaseGate q = QuantumRegister(1, "q") def_p = QuantumCircuit(q) -theta = Parameter("theta") +theta = Parameter("θ") def_p.append(RZGate(theta), [0], []) _eagle_sel.add_equivalence(PhaseGate(theta), def_p) From 819cc90e2aedabfbe9d3fd449f1fc77f439d4b4f Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Sat, 30 Mar 2024 15:22:47 -0500 Subject: [PATCH 10/20] Change name of kwarg. --- circuit_knitting/cutting/cutting_experiments.py | 6 +++--- circuit_knitting/cutting/qpd/decompose.py | 16 ++++++++-------- .../how_to_translate_sampled_instructions.ipynb | 2 +- ...translate-sampled-gates-06b301581986fec4.yaml | 2 +- test/cutting/qpd/test_qpd.py | 2 +- test/cutting/test_cutting_experiments.py | 2 +- test/cutting/test_cutting_roundtrip.py | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/circuit_knitting/cutting/cutting_experiments.py b/circuit_knitting/cutting/cutting_experiments.py index 8c8f0fd24..3ea3a09e9 100644 --- a/circuit_knitting/cutting/cutting_experiments.py +++ b/circuit_knitting/cutting/cutting_experiments.py @@ -41,7 +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, + basis_gate_set: str | None = None, ) -> tuple[ list[QuantumCircuit] | dict[Hashable, list[QuantumCircuit]], list[tuple[float, WeightType]], @@ -75,7 +75,7 @@ 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 + basis_gate_set: 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. @@ -167,7 +167,7 @@ def generate_cutting_experiments( new_qc, subcirc_qpd_gate_ids[label], map_ids_tmp, - translate_to_qpu=translate_to_qpu, + basis_gate_set=basis_gate_set, inplace=True, ) _append_measurement_circuit(new_qc, cog, inplace=True) diff --git a/circuit_knitting/cutting/qpd/decompose.py b/circuit_knitting/cutting/qpd/decompose.py index a0b76d588..d1b99d968 100644 --- a/circuit_knitting/cutting/qpd/decompose.py +++ b/circuit_knitting/cutting/qpd/decompose.py @@ -31,7 +31,7 @@ def decompose_qpd_instructions( instruction_ids: Sequence[Sequence[int]], map_ids: Sequence[int] | None = None, *, - translate_to_qpu: str | None = None, + basis_gate_set: str | None = None, inplace: bool = False, ) -> QuantumCircuit: r""" @@ -45,7 +45,7 @@ def decompose_qpd_instructions( 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 + basis_gate_set: 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 @@ -82,7 +82,7 @@ def decompose_qpd_instructions( # Convert all instances of BaseQPDGate in the circuit to Qiskit instructions _decompose_qpd_instructions( - circuit, instruction_ids, translate_to_qpu=translate_to_qpu + circuit, instruction_ids, basis_gate_set=basis_gate_set ) return circuit @@ -177,7 +177,7 @@ def _decompose_qpd_instructions( circuit: QuantumCircuit, instruction_ids: Sequence[Sequence[int]], inplace: bool = True, - translate_to_qpu: str | None = None, + basis_gate_set: str | None = None, ) -> QuantumCircuit: """Decompose all BaseQPDGate instances, ignoring QPDMeasure().""" if not inplace: @@ -207,11 +207,11 @@ def _decompose_qpd_instructions( 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() + if basis_gate_set is not None: + basis_gate_set = basis_gate_set.lower() else: - translate_to_qpu = "standard" - equivalence = equivalence_libraries[translate_to_qpu] + basis_gate_set = "standard" + equivalence = equivalence_libraries[basis_gate_set] # Decompose all the QPDGates (should all be single qubit now) into Qiskit operations new_instruction_ids = [] 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 index 66447500f..4c4e75279 100644 --- a/docs/circuit_cutting/how-tos/how_to_translate_sampled_instructions.ipynb +++ b/docs/circuit_cutting/how-tos/how_to_translate_sampled_instructions.ipynb @@ -118,7 +118,7 @@ " circuits=subcircuits,\n", " observables=subobservables,\n", " num_samples=1000,\n", - " translate_to_qpu=\"eagle\",\n", + " basis_gate_set=\"eagle\",\n", ")\n", "subexperiments[\"A\"][0].draw(\"mpl\", style=\"iqp\", scale=0.8)" ] diff --git a/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml b/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml index 7ffcbb7e4..3a4243012 100644 --- a/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml +++ b/releasenotes/notes/translate-sampled-gates-06b301581986fec4.yaml @@ -1,4 +1,4 @@ --- features: - | - The functions, :func:`.generate_qpd_experiments` and :func:`.decompose_qpd_instructions` now take an optional kwarg, ``translate_to_qpu``, which may be used to specify a QPU architecture for which to translate the sampled gates during creation of the subexperiments. This is useful for users who wish to transpile their cut circuit once and have the sampled local operations in the subexperiments be specified in terms of a native gate set. This prevents the need for any transpilation of subexperiments before running on the QPU. The accepted inputs are: ``{"eagle", "heron"}``. + The functions, :func:`.generate_qpd_experiments` and :func:`.decompose_qpd_instructions` now take an optional kwarg, ``basis_gate_set``, which may be used to specify a QPU architecture for which to translate the sampled gates during creation of the subexperiments. This is useful for users who wish to transpile their cut circuit once and have the sampled local operations in the subexperiments be specified in terms of a native gate set. This prevents the need for any transpilation of subexperiments before running on the QPU. The accepted inputs are: ``{"eagle", "heron"}``. diff --git a/test/cutting/qpd/test_qpd.py b/test/cutting/qpd/test_qpd.py index e916e03cb..e227880d2 100644 --- a/test/cutting/qpd/test_qpd.py +++ b/test/cutting/qpd/test_qpd.py @@ -172,7 +172,7 @@ def test_decompose_qpd_instructions(self): 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" + circ, [[0]], map_ids=[1], basis_gate_set="eagle" ) for inst in decomp_circ.data: assert inst.operation.name in eagle_basis_gate_set diff --git a/test/cutting/test_cutting_experiments.py b/test/cutting/test_cutting_experiments.py index be34fffcb..866ded012 100644 --- a/test/cutting/test_cutting_experiments.py +++ b/test/cutting/test_cutting_experiments.py @@ -105,7 +105,7 @@ def test_generate_cutting_experiments(self): qc, PauliList(["ZZ"]), np.inf, - translate_to_qpu="eagle", + basis_gate_set="eagle", ) assert coeffs == comp_coeffs assert len(coeffs) == len(subexperiments) diff --git a/test/cutting/test_cutting_roundtrip.py b/test/cutting/test_cutting_roundtrip.py index 42d011a9f..7e192327f 100644 --- a/test/cutting/test_cutting_roundtrip.py +++ b/test/cutting/test_cutting_roundtrip.py @@ -142,12 +142,12 @@ def test_cutting_exact_reconstruction(example_circuit): subcircuits, bases, subobservables = partition_problem( qc, "AAB", observables=observables_nophase ) - qpu = [None, "eagle", "heron"][np.random.choice([0, 1, 2], p=[0.5, 0.25, 0.25])] + basis_gate_set = [None, "eagle", "heron"][np.random.choice([0, 1, 2], p=[0.5, 0.25, 0.25])] subexperiments, coefficients = generate_cutting_experiments( subcircuits, subobservables, num_samples=np.inf, - translate_to_qpu=qpu, + basis_gate_set=basis_gate_set, ) if np.random.randint(2): # Re-use a single sampler From 367e19cf9c5308b0d3a0ff5bf1261c610b189ce7 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Sat, 30 Mar 2024 15:57:07 -0500 Subject: [PATCH 11/20] black --- circuit_knitting/cutting/qpd/decompose.py | 4 +--- test/cutting/test_cutting_roundtrip.py | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/circuit_knitting/cutting/qpd/decompose.py b/circuit_knitting/cutting/qpd/decompose.py index d1b99d968..85e004d2a 100644 --- a/circuit_knitting/cutting/qpd/decompose.py +++ b/circuit_knitting/cutting/qpd/decompose.py @@ -81,9 +81,7 @@ def decompose_qpd_instructions( 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, basis_gate_set=basis_gate_set - ) + _decompose_qpd_instructions(circuit, instruction_ids, basis_gate_set=basis_gate_set) return circuit diff --git a/test/cutting/test_cutting_roundtrip.py b/test/cutting/test_cutting_roundtrip.py index 7e192327f..c2567c1dd 100644 --- a/test/cutting/test_cutting_roundtrip.py +++ b/test/cutting/test_cutting_roundtrip.py @@ -142,7 +142,9 @@ def test_cutting_exact_reconstruction(example_circuit): subcircuits, bases, subobservables = partition_problem( qc, "AAB", observables=observables_nophase ) - basis_gate_set = [None, "eagle", "heron"][np.random.choice([0, 1, 2], p=[0.5, 0.25, 0.25])] + basis_gate_set = [None, "eagle", "heron"][ + np.random.choice([0, 1, 2], p=[0.5, 0.25, 0.25]) + ] subexperiments, coefficients = generate_cutting_experiments( subcircuits, subobservables, From f2861310eb442e985b687e8404bbff11befc4d82 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Sun, 31 Mar 2024 12:17:23 -0500 Subject: [PATCH 12/20] Rename how-to --- ...nb => how_to_translate_subexperiments_to_basis_gate_set.ipynb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/circuit_cutting/how-tos/{how_to_translate_sampled_instructions.ipynb => how_to_translate_subexperiments_to_basis_gate_set.ipynb} (100%) diff --git a/docs/circuit_cutting/how-tos/how_to_translate_sampled_instructions.ipynb b/docs/circuit_cutting/how-tos/how_to_translate_subexperiments_to_basis_gate_set.ipynb similarity index 100% rename from docs/circuit_cutting/how-tos/how_to_translate_sampled_instructions.ipynb rename to docs/circuit_cutting/how-tos/how_to_translate_subexperiments_to_basis_gate_set.ipynb From c881fbc79a94b51c60ec8a4d9089b90eb17f61d7 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 1 Apr 2024 11:46:13 -0500 Subject: [PATCH 13/20] don't use defaultdict --- circuit_knitting/utils/equivalence.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py index 6369ba412..adfaa1f36 100644 --- a/circuit_knitting/utils/equivalence.py +++ b/circuit_knitting/utils/equivalence.py @@ -18,8 +18,6 @@ :toctree: ../stubs/ """ -from collections import defaultdict - import numpy as np from qiskit.circuit import ( EquivalenceLibrary, @@ -45,9 +43,7 @@ ) _eagle_sel = HeronEquivalenceLibrary = EagleEquivalenceLibrary = EquivalenceLibrary() -equivalence_libraries = defaultdict( - lambda: None, {"heron": HeronEquivalenceLibrary, "eagle": EagleEquivalenceLibrary} -) +equivalence_libraries = {"heron": HeronEquivalenceLibrary, "eagle": EagleEquivalenceLibrary} ###################################################################### From 8d1745d7e72cd3d384284f4b44580f2740e2c556 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 1 Apr 2024 11:46:30 -0500 Subject: [PATCH 14/20] Update circuit_knitting/cutting/qpd/decompose.py Co-authored-by: Jim Garrison --- circuit_knitting/cutting/qpd/decompose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuit_knitting/cutting/qpd/decompose.py b/circuit_knitting/cutting/qpd/decompose.py index 85e004d2a..91eb129e2 100644 --- a/circuit_knitting/cutting/qpd/decompose.py +++ b/circuit_knitting/cutting/qpd/decompose.py @@ -209,7 +209,7 @@ def _decompose_qpd_instructions( basis_gate_set = basis_gate_set.lower() else: basis_gate_set = "standard" - equivalence = equivalence_libraries[basis_gate_set] + equivalence = equivalence_libraries.get(basis_gate_set) # Decompose all the QPDGates (should all be single qubit now) into Qiskit operations new_instruction_ids = [] From ba8b7449baac96fef8fc43c31519f5318ab47636 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 1 Apr 2024 11:58:33 -0500 Subject: [PATCH 15/20] fix tests --- circuit_knitting/cutting/qpd/decompose.py | 2 +- circuit_knitting/utils/equivalence.py | 5 ++++- test/utils/test_equivalence.py | 6 +++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/circuit_knitting/cutting/qpd/decompose.py b/circuit_knitting/cutting/qpd/decompose.py index 85e004d2a..91eb129e2 100644 --- a/circuit_knitting/cutting/qpd/decompose.py +++ b/circuit_knitting/cutting/qpd/decompose.py @@ -209,7 +209,7 @@ def _decompose_qpd_instructions( basis_gate_set = basis_gate_set.lower() else: basis_gate_set = "standard" - equivalence = equivalence_libraries[basis_gate_set] + equivalence = equivalence_libraries.get(basis_gate_set) # Decompose all the QPDGates (should all be single qubit now) into Qiskit operations new_instruction_ids = [] diff --git a/circuit_knitting/utils/equivalence.py b/circuit_knitting/utils/equivalence.py index adfaa1f36..9c6d413f7 100644 --- a/circuit_knitting/utils/equivalence.py +++ b/circuit_knitting/utils/equivalence.py @@ -43,7 +43,10 @@ ) _eagle_sel = HeronEquivalenceLibrary = EagleEquivalenceLibrary = EquivalenceLibrary() -equivalence_libraries = {"heron": HeronEquivalenceLibrary, "eagle": EagleEquivalenceLibrary} +equivalence_libraries = { + "heron": HeronEquivalenceLibrary, + "eagle": EagleEquivalenceLibrary, +} ###################################################################### diff --git a/test/utils/test_equivalence.py b/test/utils/test_equivalence.py index 98156b104..51de6273e 100644 --- a/test/utils/test_equivalence.py +++ b/test/utils/test_equivalence.py @@ -23,9 +23,9 @@ 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"] + self.heron_lib = equivalence_libraries.get("heron") + self.eagle_lib = equivalence_libraries.get("eagle") + self.standard_lib = equivalence_libraries.get("standard") def test_equivalence_library_dict(self): assert isinstance(self.heron_lib, EquivalenceLibrary) From 63952052ca86fb90c34dbde36acce15516507898 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 3 Jun 2024 11:59:43 -0500 Subject: [PATCH 16/20] Implement translation as a function. Use regular dictionaries under hood. Move translation to QPD. --- circuit_knitting/cutting/qpd/__init__.py | 2 + circuit_knitting/cutting/qpd/decompose.py | 23 +-- circuit_knitting/cutting/qpd/equivalence.py | 216 ++++++++++++++++++++ circuit_knitting/utils/__init__.py | 6 - circuit_knitting/utils/equivalence.py | 137 ------------- test/cutting/qpd/test_equivalence.py | 39 ++++ test/utils/test_equivalence.py | 43 ---- 7 files changed, 262 insertions(+), 204 deletions(-) create mode 100644 circuit_knitting/cutting/qpd/equivalence.py delete mode 100644 circuit_knitting/utils/equivalence.py create mode 100644 test/cutting/qpd/test_equivalence.py delete mode 100644 test/utils/test_equivalence.py diff --git a/circuit_knitting/cutting/qpd/__init__.py b/circuit_knitting/cutting/qpd/__init__.py index ca0a6d334..11d1f0145 100644 --- a/circuit_knitting/cutting/qpd/__init__.py +++ b/circuit_knitting/cutting/qpd/__init__.py @@ -24,11 +24,13 @@ TwoQubitQPDGate, QPDMeasure, ) +from .equivalence import translate_qpd_gate __all__ = [ "qpdbasis_from_instruction", "generate_qpd_weights", "decompose_qpd_instructions", + "translate_qpd_gate", "QPDBasis", "BaseQPDGate", "TwoQubitQPDGate", diff --git a/circuit_knitting/cutting/qpd/decompose.py b/circuit_knitting/cutting/qpd/decompose.py index b1a5c5e79..a2107e396 100644 --- a/circuit_knitting/cutting/qpd/decompose.py +++ b/circuit_knitting/cutting/qpd/decompose.py @@ -23,7 +23,7 @@ ) from .instructions import BaseQPDGate, TwoQubitQPDGate -from ...utils.equivalence import equivalence_libraries +from .equivalence import translate_qpd_gate def decompose_qpd_instructions( @@ -207,9 +207,6 @@ def _decompose_qpd_instructions( # Get equivalence library if basis_gate_set is not None: basis_gate_set = basis_gate_set.lower() - else: - basis_gate_set = "standard" - equivalence = equivalence_libraries.get(basis_gate_set) # Decompose all the QPDGates (should all be single qubit now) into Qiskit operations new_instruction_ids = [] @@ -227,22 +224,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 == () - if equivalence is None: + if basis_gate_set is None or data.operation.name in {"qpd_measure"}: 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]]) - ) + equiv_circ = translate_qpd_gate(data.operation, basis_gate_set) + for d in equiv_circ.data: + tmp_data.append(CircuitInstruction(d.operation, qubits=[qubits[0]])) # Replace QPDGate with local operations if tmp_data: diff --git a/circuit_knitting/cutting/qpd/equivalence.py b/circuit_knitting/cutting/qpd/equivalence.py new file mode 100644 index 000000000..0be07b79f --- /dev/null +++ b/circuit_knitting/cutting/qpd/equivalence.py @@ -0,0 +1,216 @@ +# 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.cutting.qpd.equivalence + +.. autosummary:: + :toctree: ../stubs/ + +""" +from collections.abc import Callable + +import numpy as np +from qiskit.circuit import QuantumCircuit, QuantumRegister, Gate +from qiskit.circuit.library.standard_gates import ( + RZGate, + XGate, + YGate, + ZGate, + HGate, + SGate, + SdgGate, + SXGate, + SXdgGate, + TGate, + TdgGate, + RXGate, + RYGate, + PhaseGate, +) + + +_equivalence_from_gate_funcs: dict[str, Callable[[Gate], QuantumCircuit]] = {} + + +def _register_gate(*args): + def g(f): + for name in args: + _equivalence_from_gate_funcs[name] = f + return f + + return g + + +def translate_qpd_gate(gate: Gate, basis_gate_set: str, /) -> QuantumCircuit: + """ + Translate a ``gate`` into a given basis gate set. + + This function is designed to handle only the gates to which a :class:`.QPDBasis` can + decompose; therefore, not every Qiskit gate is supported by this function. + + Args: + gate: The gate to translate + + Returns: + A :class:`qiskit.QuantumCircuit` implementing the gate in the given basis gate set. + + Raises: + ValueError: Unsupported basis gate set + ValueError: Unsupported gate + """ + # We otherwise ignore this arg for now since our only two equivalences are equivalent :) + if basis_gate_set not in {"heron", "eagle"}: + raise ValueError(f"Unknown basis gate set: {basis_gate_set}") + try: + f = _equivalence_from_gate_funcs[gate.name] + except KeyError as exc: + raise ValueError(f"Cannot translate gate: {gate.name}") from exc + else: + return f(gate) + + +# XGate +@_register_gate("x") +def _(_: XGate): + q = QuantumRegister(1, "q") + def_x = QuantumCircuit(q) + def_x.x(0) + return def_x + + +# SXGate +@_register_gate("sx") +def _(_: SXGate): + q = QuantumRegister(1, "q") + def_sx = QuantumCircuit(q) + def_sx.sx(0) + return def_sx + + +# YGate +@_register_gate("y") +def _(_: YGate): + q = QuantumRegister(1, "q") + def_y = QuantumCircuit(q) + for inst in [RZGate(np.pi), XGate()]: + def_y.append(inst, [0], []) + return def_y + + +# ZGate +@_register_gate("z") +def _(_: ZGate): + q = QuantumRegister(1, "q") + def_z = QuantumCircuit(q) + def_z.append(RZGate(np.pi), [0], []) + return def_z + + +# HGate +@_register_gate("h") +def _(_: 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], []) + return def_h + + +# SGate +@_register_gate("s") +def _(_: SGate): + q = QuantumRegister(1, "q") + def_s = QuantumCircuit(q) + def_s.append(RZGate(np.pi / 2), [0], []) + return def_s + + +# SdgGate +@_register_gate("sdg") +def _(_: SdgGate): + q = QuantumRegister(1, "q") + def_sdg = QuantumCircuit(q) + def_sdg.append(RZGate(-np.pi / 2), [0], []) + return def_sdg + + +# SXdgGate +@_register_gate("sxdg") +def _(_: 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], []) + return def_sxdg + + +# TGate +@_register_gate("t") +def _(_: TGate): + q = QuantumRegister(1, "q") + def_t = QuantumCircuit(q) + def_t.append(RZGate(np.pi / 4), [0], []) + return def_t + + +# TdgGate +@_register_gate("tdg") +def _(_: TdgGate): + q = QuantumRegister(1, "q") + def_tdg = QuantumCircuit(q) + def_tdg.append(RZGate(-np.pi / 4), [0], []) + return def_tdg + + +# RXGate +@_register_gate("rx") +def _(gate: RXGate): + q = QuantumRegister(1, "q") + def_rx = QuantumCircuit(q) + param = gate.params[0] + for inst in [ + RZGate(np.pi / 2), + SXGate(), + RZGate(param + np.pi), + SXGate(), + RZGate(5 * np.pi / 2), + ]: + def_rx.append(inst, [0], []) + return def_rx + + +# RYGate +@_register_gate("ry") +def _(gate: RYGate): + q = QuantumRegister(1, "q") + def_ry = QuantumCircuit(q) + param = gate.params[0] + for inst in [SXGate(), RZGate(param + np.pi), SXGate(), RZGate(3 * np.pi)]: + def_ry.append(inst, [0], []) + + +# PhaseGate +@_register_gate("p") +def _(gate: PhaseGate): + q = QuantumRegister(1, "q") + def_p = QuantumCircuit(q) + param = gate.params[0] + def_p.append(RZGate(param), [0], []) + return def_p diff --git a/circuit_knitting/utils/__init__.py b/circuit_knitting/utils/__init__.py index 95368cf79..5c41eab99 100644 --- a/circuit_knitting/utils/__init__.py +++ b/circuit_knitting/utils/__init__.py @@ -59,10 +59,4 @@ =================================================================== .. 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 deleted file mode 100644 index 9c6d413f7..000000000 --- a/circuit_knitting/utils/equivalence.py +++ /dev/null @@ -1,137 +0,0 @@ -# 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, - SdgGate, - SXGate, - SXdgGate, - TGate, - TdgGate, - RXGate, - RYGate, - PhaseGate, -) - -_eagle_sel = HeronEquivalenceLibrary = EagleEquivalenceLibrary = EquivalenceLibrary() -equivalence_libraries = { - "heron": HeronEquivalenceLibrary, - "eagle": EagleEquivalenceLibrary, -} - -###################################################################### - -# 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("θ") -for inst in [ - RZGate(np.pi / 2), - SXGate(), - RZGate(theta + np.pi), - SXGate(), - 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("θ") -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("θ") -def_p.append(RZGate(theta), [0], []) -_eagle_sel.add_equivalence(PhaseGate(theta), def_p) diff --git a/test/cutting/qpd/test_equivalence.py b/test/cutting/qpd/test_equivalence.py new file mode 100644 index 000000000..41e9a938c --- /dev/null +++ b/test/cutting/qpd/test_equivalence.py @@ -0,0 +1,39 @@ +# 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 QPD gate translation.""" + +import unittest + +import pytest +import numpy as np +from qiskit.circuit.library.standard_gates import SdgGate, U3Gate + +from circuit_knitting.cutting.qpd import translate_qpd_gate + + +class TestQPDGateTranslation(unittest.TestCase): + def test_equivalence_heron(self): + equiv = translate_qpd_gate(SdgGate(), "heron") + assert equiv.data[0].operation.name == "rz" + assert equiv.data[0].operation.params == [-np.pi / 2] + def test_equivalence_eagle(self): + equiv = translate_qpd_gate(SdgGate(), "eagle") + assert equiv.data[0].operation.name == "rz" + assert equiv.data[0].operation.params == [-np.pi / 2] + def test_equivalence_unsupported_basis(self): + with pytest.raises(ValueError) as e_info: + translate_qpd_gate(SdgGate(), "falcon") + assert e_info.value.args[0] == "Unknown basis gate set: falcon" + def test_equivalence_unsupported_gate(self): + with pytest.raises(ValueError) as e_info: + translate_qpd_gate(U3Gate(1., 1., 1.), "eagle") + assert e_info.value.args[0] == "Cannot translate gate: u3" diff --git a/test/utils/test_equivalence.py b/test/utils/test_equivalence.py deleted file mode 100644 index 51de6273e..000000000 --- a/test/utils/test_equivalence.py +++ /dev/null @@ -1,43 +0,0 @@ -# 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.get("heron") - self.eagle_lib = equivalence_libraries.get("eagle") - self.standard_lib = equivalence_libraries.get("standard") - - def test_equivalence_library_dict(self): - assert isinstance(self.heron_lib, EquivalenceLibrary) - assert isinstance(self.eagle_lib, EquivalenceLibrary) - assert self.standard_lib is 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] From 9cf237288b5155f0d0b4f87a2c32d9048de04e9f Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 3 Jun 2024 12:01:21 -0500 Subject: [PATCH 17/20] Rename equivalence --> translation --- circuit_knitting/cutting/qpd/__init__.py | 2 +- circuit_knitting/cutting/qpd/{equivalence.py => translation.py} | 0 test/cutting/qpd/{test_equivalence.py => test_translation.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename circuit_knitting/cutting/qpd/{equivalence.py => translation.py} (100%) rename test/cutting/qpd/{test_equivalence.py => test_translation.py} (100%) diff --git a/circuit_knitting/cutting/qpd/__init__.py b/circuit_knitting/cutting/qpd/__init__.py index 11d1f0145..44f9f4785 100644 --- a/circuit_knitting/cutting/qpd/__init__.py +++ b/circuit_knitting/cutting/qpd/__init__.py @@ -24,7 +24,7 @@ TwoQubitQPDGate, QPDMeasure, ) -from .equivalence import translate_qpd_gate +from .translation import translate_qpd_gate __all__ = [ "qpdbasis_from_instruction", diff --git a/circuit_knitting/cutting/qpd/equivalence.py b/circuit_knitting/cutting/qpd/translation.py similarity index 100% rename from circuit_knitting/cutting/qpd/equivalence.py rename to circuit_knitting/cutting/qpd/translation.py diff --git a/test/cutting/qpd/test_equivalence.py b/test/cutting/qpd/test_translation.py similarity index 100% rename from test/cutting/qpd/test_equivalence.py rename to test/cutting/qpd/test_translation.py From f7e8cb10b837fd6deccca0669a0a45b1bb755b58 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 3 Jun 2024 12:03:31 -0500 Subject: [PATCH 18/20] style --- test/cutting/qpd/test_translation.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/cutting/qpd/test_translation.py b/test/cutting/qpd/test_translation.py index 41e9a938c..5550d0ed6 100644 --- a/test/cutting/qpd/test_translation.py +++ b/test/cutting/qpd/test_translation.py @@ -25,15 +25,18 @@ def test_equivalence_heron(self): equiv = translate_qpd_gate(SdgGate(), "heron") assert equiv.data[0].operation.name == "rz" assert equiv.data[0].operation.params == [-np.pi / 2] + def test_equivalence_eagle(self): equiv = translate_qpd_gate(SdgGate(), "eagle") assert equiv.data[0].operation.name == "rz" assert equiv.data[0].operation.params == [-np.pi / 2] + def test_equivalence_unsupported_basis(self): with pytest.raises(ValueError) as e_info: translate_qpd_gate(SdgGate(), "falcon") assert e_info.value.args[0] == "Unknown basis gate set: falcon" + def test_equivalence_unsupported_gate(self): with pytest.raises(ValueError) as e_info: - translate_qpd_gate(U3Gate(1., 1., 1.), "eagle") + translate_qpd_gate(U3Gate(1.0, 1.0, 1.0), "eagle") assert e_info.value.args[0] == "Cannot translate gate: u3" From 444850671d3297c9fb3b55e82bdf43d2f691442a Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 3 Jun 2024 12:49:17 -0500 Subject: [PATCH 19/20] Clean up bugs --- circuit_knitting/cutting/qpd/decompose.py | 2 +- circuit_knitting/cutting/qpd/translation.py | 17 +++++++++++++---- test/cutting/qpd/test_translation.py | 13 ++++++++++--- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/circuit_knitting/cutting/qpd/decompose.py b/circuit_knitting/cutting/qpd/decompose.py index a2107e396..7cd1fa3e5 100644 --- a/circuit_knitting/cutting/qpd/decompose.py +++ b/circuit_knitting/cutting/qpd/decompose.py @@ -23,7 +23,7 @@ ) from .instructions import BaseQPDGate, TwoQubitQPDGate -from .equivalence import translate_qpd_gate +from .translation import translate_qpd_gate def decompose_qpd_instructions( diff --git a/circuit_knitting/cutting/qpd/translation.py b/circuit_knitting/cutting/qpd/translation.py index 0be07b79f..833be04d5 100644 --- a/circuit_knitting/cutting/qpd/translation.py +++ b/circuit_knitting/cutting/qpd/translation.py @@ -82,22 +82,31 @@ def translate_qpd_gate(gate: Gate, basis_gate_set: str, /) -> QuantumCircuit: # XGate @_register_gate("x") -def _(_: XGate): +def _(gate: XGate): q = QuantumRegister(1, "q") def_x = QuantumCircuit(q) - def_x.x(0) + def_x.append(gate, [0], []) return def_x # SXGate @_register_gate("sx") -def _(_: SXGate): +def _(gate: SXGate): q = QuantumRegister(1, "q") def_sx = QuantumCircuit(q) - def_sx.sx(0) + def_sx.append(gate, [0], []) return def_sx +# RZGate +@_register_gate("rz") +def _(gate: RZGate): + q = QuantumRegister(1, "q") + def_rz = QuantumCircuit(q) + def_rz.append(gate, [0], []) + return def_rz + + # YGate @_register_gate("y") def _(_: YGate): diff --git a/test/cutting/qpd/test_translation.py b/test/cutting/qpd/test_translation.py index 5550d0ed6..a08bb7814 100644 --- a/test/cutting/qpd/test_translation.py +++ b/test/cutting/qpd/test_translation.py @@ -15,7 +15,8 @@ import pytest import numpy as np -from qiskit.circuit.library.standard_gates import SdgGate, U3Gate +from qiskit.circuit import Parameter +from qiskit.circuit.library.standard_gates import SdgGate, U3Gate, PhaseGate from circuit_knitting.cutting.qpd import translate_qpd_gate @@ -27,9 +28,15 @@ def test_equivalence_heron(self): assert equiv.data[0].operation.params == [-np.pi / 2] def test_equivalence_eagle(self): - equiv = translate_qpd_gate(SdgGate(), "eagle") + equiv = translate_qpd_gate(PhaseGate(1.0), "eagle") assert equiv.data[0].operation.name == "rz" - assert equiv.data[0].operation.params == [-np.pi / 2] + assert equiv.data[0].operation.params == [1.0] + + def test_unassigned_param(self): + param = Parameter("θ") + equiv = translate_qpd_gate(PhaseGate(param), "eagle") + assert equiv.data[0].operation.name == "rz" + assert equiv.data[0].operation.params == [param] def test_equivalence_unsupported_basis(self): with pytest.raises(ValueError) as e_info: From 4aac71fa56bbd892ae2c591d79f13396494e29c5 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Mon, 3 Jun 2024 12:52:54 -0500 Subject: [PATCH 20/20] Allow modern type hints for py38 users --- circuit_knitting/cutting/qpd/translation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/circuit_knitting/cutting/qpd/translation.py b/circuit_knitting/cutting/qpd/translation.py index 833be04d5..e9c86bc8b 100644 --- a/circuit_knitting/cutting/qpd/translation.py +++ b/circuit_knitting/cutting/qpd/translation.py @@ -18,6 +18,8 @@ :toctree: ../stubs/ """ +from __future__ import annotations + from collections.abc import Callable import numpy as np