diff --git a/frontend/catalyst/device/qjit_device.py b/frontend/catalyst/device/qjit_device.py index df1fc2675d..1d447c474c 100644 --- a/frontend/catalyst/device/qjit_device.py +++ b/frontend/catalyst/device/qjit_device.py @@ -142,7 +142,7 @@ class BackendInfo: # pylint: disable=too-many-branches @debug_logger -def extract_backend_info(device: qml.devices.QubitDevice) -> BackendInfo: +def extract_backend_info(device: qml.devices.QubitDevice, device_capabilities=None) -> BackendInfo: """Extract the backend info from a quantum device. The device is expected to carry a reference to a valid TOML config file.""" @@ -192,6 +192,7 @@ def extract_backend_info(device: qml.devices.QubitDevice) -> BackendInfo: for k, v in getattr(device, "device_kwargs", {}).items(): if k not in device_kwargs: # pragma: no branch device_kwargs[k] = v + device_kwargs["coupling_map"] = getattr(device_capabilities, "coupling_map") return BackendInfo(dname, device_name, device_lpath, device_kwargs) @@ -308,9 +309,9 @@ class QJITDevice(qml.devices.Device): @staticmethod @debug_logger - def extract_backend_info(device) -> BackendInfo: + def extract_backend_info(device, device_capabilities=None) -> BackendInfo: """Wrapper around extract_backend_info in the runtime module.""" - return extract_backend_info(device) + return extract_backend_info(device, device_capabilities) @debug_logger_init def __init__(self, original_device): @@ -319,14 +320,40 @@ def __init__(self, original_device): for key, value in original_device.__dict__.items(): self.__setattr__(key, value) - check_device_wires(original_device.wires) - - super().__init__(wires=original_device.wires) + if (original_device.wires is not None) and any( + isinstance(wire_label, tuple) and (len(wire_label) >= 2) + for wire_label in original_device.wires.labels + ): + wires_from_cmap = set() + for wire_label in original_device.wires.labels: + wires_from_cmap.add(wire_label[0]) + wires_from_cmap.add(wire_label[1]) + wires_from_cmap = qml.wires.Wires(list(wires_from_cmap)) + # check_device_wires(wires_from_cmap) not called + # since automatic qubit management + super().__init__(wires=wires_from_cmap, shots=original_device.shots) + else: + check_device_wires(original_device.wires) + super().__init__(wires=original_device.wires, shots=original_device.shots) # Capability loading - device_capabilities = get_device_capabilities(original_device, self.original_device.shots) + device_capabilities = get_device_capabilities(original_device) + + # TODO: This is a temporary measure to ensure consistency of behaviour. Remove this + # when customizable multi-pathway decomposition is implemented. (Epic 74474) + if hasattr(original_device, "_to_matrix_ops"): + _to_matrix_ops = getattr(original_device, "_to_matrix_ops") + setattr(device_capabilities, "to_matrix_ops", _to_matrix_ops) + if _to_matrix_ops and not device_capabilities.supports_operation("QubitUnitary"): + raise CompileError( + "The device that specifies to_matrix_ops must support QubitUnitary." + ) + if original_device.wires is not None: + setattr(device_capabilities, "coupling_map", original_device.wires.labels) + else: + setattr(device_capabilities, "coupling_map", None) - backend = QJITDevice.extract_backend_info(original_device) + backend = QJITDevice.extract_backend_info(original_device, device_capabilities) self.backend_name = backend.c_interface_name self.backend_lib = backend.lpath diff --git a/frontend/test/pytest/test_routing.py b/frontend/test/pytest/test_routing.py new file mode 100644 index 0000000000..82cc3165c1 --- /dev/null +++ b/frontend/test/pytest/test_routing.py @@ -0,0 +1,120 @@ +# Copyright 2025 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Integration tests for routing at runtime""" + +from functools import partial + +import pennylane as qml +import pytest +from pennylane import numpy as np +from pennylane.transforms.transpile import transpile + + +def qfunc_ops(wires, x, y, z): + qml.Hadamard(wires=wires[0]) + qml.RZ(z, wires=wires[2]) + qml.CNOT(wires=[wires[2], wires[0]]) + qml.CNOT(wires=[wires[1], wires[0]]) + qml.RX(x, wires=wires[0]) + qml.CNOT(wires=[wires[0], wires[2]]) + qml.RZ(-z, wires=wires[2]) + qml.RX(y, wires=wires[0]) + qml.PauliY(wires=wires[2]) + qml.CY(wires=[wires[1], wires[2]]) + + +# pylint: disable=too-many-public-methods +class TestRouting: + """Unit tests for testing routing function at runtime""" + + all_to__all_device = qml.device("lightning.qubit") + linear_device = qml.device("lightning.qubit", wires=[(0, 1), (1, 2)]) + + input_devices = ((all_to__all_device, linear_device),) + + @pytest.mark.parametrize("all_to__all_device, linear_device", input_devices) + def test_state_invariance_under_routing(self, all_to__all_device, linear_device): + """test that transpile does not alter output for state measurement""" + def circuit(wires, x, y, z): + qfunc_ops(wires, x, y, z) + return qml.state() + + all_to_all_qnode = qml.qjit(qml.QNode(circuit, all_to__all_device)) + linear_qnode = qml.qjit(qml.QNode(circuit, linear_device)) + + assert np.allclose( + all_to_all_qnode([0, 1, 2], 0.1, 0.2, 0.3), linear_qnode([0, 1, 2], 0.1, 0.2, 0.3) + ) + + @pytest.mark.parametrize("all_to__all_device, linear_device", input_devices) + def test_probs_invariance_under_routing(self, all_to__all_device, linear_device): + """test that transpile does not alter output for probs measurement""" + def circuit(wires, x, y, z): + qfunc_ops(wires, x, y, z) + return qml.probs() + + all_to_all_qnode = qml.qjit(qml.QNode(circuit, all_to__all_device)) + linear_qnode = qml.qjit(qml.QNode(circuit, linear_device)) + + assert np.allclose( + all_to_all_qnode([0, 1, 2], 0.1, 0.2, 0.3), linear_qnode([0, 1, 2], 0.1, 0.2, 0.3) + ) + + @pytest.mark.parametrize("all_to__all_device, linear_device", input_devices) + def test_sample_invariance_under_routing(self, all_to__all_device, linear_device): + """test that transpile does not alter output for sample measurement""" + def circuit(wires, x, y, z): + qfunc_ops(wires, x, y, z) + return qml.sample() + + all_to_all_qnode = qml.qjit( + partial(qml.set_shots, shots=10)(qml.QNode(circuit, all_to__all_device)), seed=37 + ) + linear_qnode = qml.qjit( + partial(qml.set_shots, shots=10)(qml.QNode(circuit, linear_device)), seed=37 + ) + assert np.allclose( + all_to_all_qnode([0, 1, 2], 0.1, 0.2, 0.3), linear_qnode([0, 1, 2], 0.1, 0.2, 0.3) + ) + + @pytest.mark.parametrize("all_to__all_device, linear_device", input_devices) + def test_counts_invariance_under_routing(self, all_to__all_device, linear_device): + """test that transpile does not alter output for counts measurement""" + def circuit(wires, x, y, z): + qfunc_ops(wires, x, y, z) + return qml.counts() + + all_to_all_qnode = qml.qjit( + partial(qml.set_shots, shots=10)(qml.QNode(circuit, all_to__all_device)), seed=37 + ) + linear_qnode = qml.qjit( + partial(qml.set_shots, shots=10)(qml.QNode(circuit, linear_device)), seed=37 + ) + assert np.allclose( + all_to_all_qnode([0, 1, 2], 0.1, 0.2, 0.3), linear_qnode([0, 1, 2], 0.1, 0.2, 0.3) + ) + + @pytest.mark.parametrize("all_to__all_device, linear_device", input_devices) + def test_expvals_invariance_under_routing(self, all_to__all_device, linear_device): + """test that transpile does not alter output for expectation value measurement""" + def circuit(wires, x, y, z): + qfunc_ops(wires, x, y, z) + return qml.expval(qml.X(0) @ qml.Y(1)), qml.var(qml.Z(2)) + + all_to_all_qnode = qml.qjit(qml.QNode(circuit, all_to__all_device)) + linear_qnode = qml.qjit(qml.QNode(circuit, linear_device)) + assert np.allclose( + all_to_all_qnode([0, 1, 2], 0.1, 0.2, 0.3), linear_qnode([0, 1, 2], 0.1, 0.2, 0.3) + ) diff --git a/runtime/lib/capi/ExecutionContext.hpp b/runtime/lib/capi/ExecutionContext.hpp index 56b7543980..7bc1a10c83 100644 --- a/runtime/lib/capi/ExecutionContext.hpp +++ b/runtime/lib/capi/ExecutionContext.hpp @@ -29,6 +29,7 @@ #include "Exception.hpp" #include "QuantumDevice.hpp" +#include "Routing.hpp" #include "Types.h" namespace Catalyst::Runtime { @@ -170,6 +171,8 @@ class RTDevice { std::unique_ptr rtd_dylib{nullptr}; std::unique_ptr rtd_qdevice{nullptr}; + // device specific routing pass pointer. + std::unique_ptr RUNTIME_ROUTER{nullptr}; RTDeviceStatus status{RTDeviceStatus::Inactive}; @@ -224,6 +227,20 @@ class RTDevice { _pl2runtime_device_info(rtd_lib, rtd_name); } + explicit RTDevice(std::string_view _rtd_lib, std::string_view _rtd_name, + std::string_view _rtd_kwargs, bool _auto_qubit_management, + std::string_view coupling_map_str) + : rtd_lib(_rtd_lib), rtd_name(_rtd_name), rtd_kwargs(_rtd_kwargs), + auto_qubit_management(_auto_qubit_management) + { + // Extract coupling map from the kwargs passed + // If coupling map is provided then it takes in the form {...,'couplingMap' ((a,b),(b,c))} + // else {...,'couplingMap' (a,b,c)} + if (coupling_map_str.find("((") != std::string::npos) + RUNTIME_ROUTER = std::make_unique(coupling_map_str); + _pl2runtime_device_info(rtd_lib, rtd_name); + } + ~RTDevice() = default; RTDevice(const RTDevice &other) = delete; RTDevice &operator=(const RTDevice &other) = delete; @@ -264,6 +281,10 @@ class RTDevice { void setDeviceStatus(RTDeviceStatus new_status) noexcept { status = new_status; } bool getQubitManagementMode() { return auto_qubit_management; } + [[nodiscard]] auto getRuntimeRouter() -> std::unique_ptr & + { + return RUNTIME_ROUTER; + } [[nodiscard]] auto getDeviceStatus() const -> RTDeviceStatus { return status; } @@ -320,13 +341,14 @@ class ExecutionContext final { } [[nodiscard]] auto getOrCreateDevice(std::string_view rtd_lib, std::string_view rtd_name, - std::string_view rtd_kwargs, bool auto_qubit_management) + std::string_view rtd_kwargs, bool auto_qubit_management, + std::string_view coupling_map_str = {}) -> const std::shared_ptr & { std::lock_guard lock(pool_mu); - auto device = - std::make_shared(rtd_lib, rtd_name, rtd_kwargs, auto_qubit_management); + auto device = std::make_shared(rtd_lib, rtd_name, rtd_kwargs, + auto_qubit_management, coupling_map_str); const size_t key = device_pool.size(); for (size_t i = 0; i < key; i++) { diff --git a/runtime/lib/capi/Routing.hpp b/runtime/lib/capi/Routing.hpp new file mode 100644 index 0000000000..3abc0710d4 --- /dev/null +++ b/runtime/lib/capi/Routing.hpp @@ -0,0 +1,185 @@ +// Copyright 2025 Xanadu Quantum Technologies Inc. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Catalyst::Runtime { + +class RoutingPass final { + private: + const int MAXIMUM = 1e9; + std::set physicalQubits; + std::map wireMap; + std::map, bool> couplingMap; + std::map, int> distanceMatrix; + std::map, int> predecessorMatrix; + + public: + RoutingPass(std::string_view coupling_map_str) + { + auto string_index = 1; + while (string_index < coupling_map_str.size() - 1) { + size_t next_closing_bracket = coupling_map_str.find(")", string_index); + std::string curr_coupling_map_str = std::string( + coupling_map_str.substr(string_index + 1, next_closing_bracket - string_index - 1)); + std::istringstream iss(curr_coupling_map_str); + QubitIdType first_qubit_id, second_qubit_id; + char comma; + iss >> first_qubit_id >> comma &&comma == ',' && iss >> second_qubit_id; + this->physicalQubits.insert(first_qubit_id); + this->physicalQubits.insert(second_qubit_id); + this->couplingMap[std::make_pair(first_qubit_id, second_qubit_id)] = true; + this->couplingMap[std::make_pair(second_qubit_id, first_qubit_id)] = true; + string_index = next_closing_bracket + 3; + } + for (auto i_itr = this->physicalQubits.begin(); i_itr != this->physicalQubits.end(); + i_itr++) { + // initial mapping i->i + this->wireMap[*i_itr] = *i_itr; + // self-distances : 0 + this->distanceMatrix[std::make_pair(*i_itr, *i_itr)] = 0; + // parent(self) = self + this->predecessorMatrix[std::make_pair(*i_itr, *i_itr)] = *i_itr; + } + + // initial distances maximum + for (auto i_itr = this->physicalQubits.begin(); i_itr != this->physicalQubits.end(); + i_itr++) { + for (auto j_itr = this->physicalQubits.begin(); j_itr != this->physicalQubits.end(); + j_itr++) { + this->distanceMatrix[std::make_pair(*i_itr, *j_itr)] = MAXIMUM; + this->predecessorMatrix[std::make_pair(*i_itr, *j_itr)] = -1; + } + } + + // edge-distances : 1 + for (auto &entry : this->couplingMap) { + const std::pair &key = entry.first; + bool value = entry.second; + if (value) { + this->distanceMatrix[std::make_pair(key.first, key.second)] = 1; + this->predecessorMatrix[std::make_pair(key.first, key.second)] = key.first; + } + } + // run floyd-warshall + for (auto i_itr = this->physicalQubits.begin(); i_itr != this->physicalQubits.end(); + i_itr++) { + for (auto j_itr = this->physicalQubits.begin(); j_itr != this->physicalQubits.end(); + j_itr++) { + for (auto k_itr = this->physicalQubits.begin(); k_itr != this->physicalQubits.end(); + k_itr++) { + if (this->distanceMatrix[std::make_pair(*j_itr, *i_itr)] + + this->distanceMatrix[std::make_pair(*i_itr, *k_itr)] < + this->distanceMatrix[std::make_pair(*j_itr, *k_itr)]) { + this->distanceMatrix[std::make_pair(*j_itr, *k_itr)] = + this->distanceMatrix[std::make_pair(*j_itr, *i_itr)] + + this->distanceMatrix[std::make_pair(*i_itr, *k_itr)]; + this->predecessorMatrix[std::make_pair(*j_itr, *k_itr)] = + this->predecessorMatrix[std::make_pair(*i_itr, *k_itr)]; + } + } + } + } + } + + QubitIdType getMappedWire(QubitIdType wire) { return this->wireMap[wire]; } + + std::vector getShortestPath(QubitIdType source, QubitIdType target) + { + std::vector path; + if (this->predecessorMatrix.at(std::make_pair(source, target)) == -1 && source != target) { + return path; + } + + QubitIdType current = target; + while (current != source) { + path.push_back(current); + current = this->predecessorMatrix.at(std::make_pair(source, current)); + if (current == -1 && path.size() > 0) { + path.clear(); + return path; + } + } + path.push_back(source); + std::reverse(path.begin(), path.end()); + return path; + } + + std::tuple> getRoutedQubits(QUBIT *control, + QUBIT *target) + { + QubitIdType firstQubitMapping = this->wireMap[reinterpret_cast(control)]; + QubitIdType secondQubitMapping = this->wireMap[reinterpret_cast(target)]; + std::vector swapPath = {}; + + if (!this->couplingMap[std::make_pair(firstQubitMapping, secondQubitMapping)]) { + swapPath = this->getShortestPath(firstQubitMapping, secondQubitMapping); + // iwireMap.begin(); it != this->wireMap.end(); ++it) { + // update logical -> phyiscal mapping + if (this->wireMap[it->first] == u) + this->wireMap[it->first] = v; + else if (this->wireMap[it->first] == v) + this->wireMap[it->first] = u; + } + } + firstQubitMapping = this->wireMap[reinterpret_cast(control)]; + secondQubitMapping = this->wireMap[reinterpret_cast(target)]; + } + return std::make_tuple(firstQubitMapping, secondQubitMapping, swapPath); + } + + void findFinalSwaps(QubitIdType currWireIndex, + std::vector> *finalSwaps) + { + if (currWireIndex == this->wireMap[currWireIndex]) + return; + QubitIdType nextWireIndex = this->wireMap[currWireIndex]; + QubitIdType temp = this->wireMap[currWireIndex]; + this->wireMap[currWireIndex] = this->wireMap[nextWireIndex]; + this->wireMap[nextWireIndex] = temp; + (*finalSwaps).push_back({this->wireMap[currWireIndex], this->wireMap[nextWireIndex]}); + findFinalSwaps(nextWireIndex, finalSwaps); + return; + } + std::vector> getFinalPermuteSwaps() + { + std::vector> finalSwaps; + for (auto it = this->wireMap.begin(); it != this->wireMap.end(); ++it) { + while (true) { + findFinalSwaps(this->wireMap[it->first], &finalSwaps); + if (this->wireMap[it->first] == this->wireMap[it->second]) + break; + } + } + return finalSwaps; + } + + ~RoutingPass() = default; +}; +} // namespace Catalyst::Runtime diff --git a/runtime/lib/capi/RuntimeCAPI.cpp b/runtime/lib/capi/RuntimeCAPI.cpp index 94ed41e13a..d9ddecb4f1 100644 --- a/runtime/lib/capi/RuntimeCAPI.cpp +++ b/runtime/lib/capi/RuntimeCAPI.cpp @@ -77,9 +77,11 @@ std::vector getModifiersControlledValues(const Modifiers *modifiers) * to the new initialized device pointer. */ [[nodiscard]] bool initRTDevicePtr(std::string_view rtd_lib, std::string_view rtd_name, - std::string_view rtd_kwargs, bool auto_qubit_management) + std::string_view rtd_kwargs, bool auto_qubit_management, + std::string_view coupling_map_str = {}) { - auto &&device = CTX->getOrCreateDevice(rtd_lib, rtd_name, rtd_kwargs, auto_qubit_management); + auto &&device = CTX->getOrCreateDevice(rtd_lib, rtd_name, rtd_kwargs, auto_qubit_management, + coupling_map_str); if (device) { RTD_PTR = device.get(); return RTD_PTR ? true : false; @@ -267,8 +269,23 @@ static int __catalyst__rt__device_init__impl(int8_t *rtd_lib, int8_t *rtd_name, const std::vector args{ reinterpret_cast(rtd_lib), (rtd_name ? reinterpret_cast(rtd_name) : ""), (rtd_kwargs ? reinterpret_cast(rtd_kwargs) : "")}; - RT_FAIL_IF(!initRTDevicePtr(args[0], args[1], args[2], auto_qubit_management), - "Failed initialization of the backend device"); + + // Extract coupling map from the kwargs passed + // If coupling map is provided then it takes in the form {...,'couplingMap' ((a,b),(b,c))} + // else {...,'couplingMap' (a,b,c)} + size_t start = args[2].find("coupling_map': ") + 15; // Find key and opening parenthesis + size_t end = args[2].find("}", start); // Find closing parenthesis + std::string coupling_map_str = std::string(args[2].substr(start, end - start)); + + if (coupling_map_str.find("((") != std::string::npos) { + RT_FAIL_IF( + !initRTDevicePtr(args[0], args[1], args[2], auto_qubit_management, coupling_map_str), + "Failed initialization of the backend device"); + } + else { + RT_FAIL_IF(!initRTDevicePtr(args[0], args[1], args[2], auto_qubit_management), + "Failed initialization of the backend device"); + } getQuantumDevicePtr()->SetDeviceShots(shots); if (CTX->getDeviceRecorderStatus()) { getQuantumDevicePtr()->StartTapeRecording(); @@ -489,6 +506,7 @@ void __catalyst__qis__GlobalPhase(double phi, const Modifiers *modifiers) void __catalyst__qis__SetState(MemRefT_CplxT_double_1d *data, uint64_t numQubits, ...) { + // set_state is at the beginning of the circuit starting with 1->1 mapping RT_ASSERT(numQubits > 0); va_list args; @@ -518,7 +536,9 @@ void __catalyst__qis__PCPhase(double theta, double dim, const Modifiers *modifie wires[i] = va_arg(args, QubitIdType); } va_end(args); - + RT_FAIL_IF(RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr, + "Only single and two-qubit gates are supported when compiling to a given hardware " + "coupling map."); getQuantumDevicePtr()->NamedOperation("PCPhase", {theta, dim}, wires, /* modifiers */ MODIFIERS_ARGS(modifiers)); } @@ -554,165 +574,436 @@ void __catalyst__qis__Identity(const Modifiers *modifiers, int64_t numQubits, .. wires[i] = va_arg(args, QubitIdType); } va_end(args); - + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + for (int64_t i = 0; i < numQubits; i++) + wires[i] = RTD_PTR->getRuntimeRouter()->getMappedWire(wires[i]); + } getQuantumDevicePtr()->NamedOperation("Identity", {}, wires, /* modifiers */ MODIFIERS_ARGS(modifiers)); } void __catalyst__qis__PauliX(QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation("PauliX", {}, {reinterpret_cast(qubit)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("PauliX", {}, {mapped_wire}, + MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation("PauliX", {}, {reinterpret_cast(qubit)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__PauliY(QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation("PauliY", {}, {reinterpret_cast(qubit)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("PauliY", {}, {mapped_wire}, + MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation("PauliY", {}, {reinterpret_cast(qubit)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__PauliZ(QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation("PauliZ", {}, {reinterpret_cast(qubit)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("PauliZ", {}, {mapped_wire}, + MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation("PauliZ", {}, {reinterpret_cast(qubit)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__Hadamard(QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation("Hadamard", {}, {reinterpret_cast(qubit)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("Hadamard", {}, {mapped_wire}, + MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "Hadamard", {}, {reinterpret_cast(qubit)}, MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__S(QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation("S", {}, {reinterpret_cast(qubit)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("S", {}, {mapped_wire}, MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation("S", {}, {reinterpret_cast(qubit)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__T(QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation("T", {}, {reinterpret_cast(qubit)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("T", {}, {mapped_wire}, MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation("T", {}, {reinterpret_cast(qubit)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__PhaseShift(double theta, QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation( - "PhaseShift", {theta}, {reinterpret_cast(qubit)}, MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("PhaseShift", {theta}, {mapped_wire}, + MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation("PhaseShift", {theta}, + {reinterpret_cast(qubit)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__RX(double theta, QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation("RX", {theta}, {reinterpret_cast(qubit)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("RX", {theta}, {mapped_wire}, + MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation("RX", {theta}, {reinterpret_cast(qubit)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__RY(double theta, QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation("RY", {theta}, {reinterpret_cast(qubit)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("RY", {theta}, {mapped_wire}, + MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation("RY", {theta}, {reinterpret_cast(qubit)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__RZ(double theta, QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation("RZ", {theta}, {reinterpret_cast(qubit)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("RZ", {theta}, {mapped_wire}, + MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation("RZ", {theta}, {reinterpret_cast(qubit)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__Rot(double phi, double theta, double omega, QUBIT *qubit, const Modifiers *modifiers) { - getQuantumDevicePtr()->NamedOperation("Rot", {phi, theta, omega}, - {reinterpret_cast(qubit)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + QubitIdType mapped_wire = + RTD_PTR->getRuntimeRouter()->getMappedWire(reinterpret_cast(qubit)); + getQuantumDevicePtr()->NamedOperation("Rot", {phi, theta, omega}, {mapped_wire}, + MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation("Rot", {phi, theta, omega}, + {reinterpret_cast(qubit)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__CNOT(QUBIT *control, QUBIT *target, const Modifiers *modifiers) { RT_FAIL_IF(control == target, "Invalid input for CNOT gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("CNOT", {}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("CNOT", {}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "CNOT", {}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__CY(QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF(control == target, - "Invalid input for CY gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("CY", {}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for CY gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("CY", {}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "CY", {}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__CZ(QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF(control == target, - "Invalid input for CZ gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("CZ", {}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for CZ gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("CZ", {}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "CZ", {}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__SWAP(QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF(control == target, - "Invalid input for SWAP gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("SWAP", {}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for SWAP gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("SWAP", {}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "SWAP", {}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__IsingXX(double theta, QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF( - control == target, - "Invalid input for IsingXX gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("IsingXX", {theta}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for IsingXX gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("IsingXX", {theta}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "IsingXX", {theta}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__IsingYY(double theta, QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF( - control == target, - "Invalid input for IsingYY gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("IsingYY", {theta}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for IsingYY gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("IsingYY", {theta}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "IsingYY", {theta}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__IsingXY(double theta, QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF( - control == target, - "Invalid input for IsingXY gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("IsingXY", {theta}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for IsingXY gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("IsingXY", {theta}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "IsingXY", {theta}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__IsingZZ(double theta, QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF( - control == target, - "Invalid input for IsingZZ gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("IsingZZ", {theta}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for IsingZZ gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("IsingZZ", {theta}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "IsingZZ", {theta}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__SingleExcitation(double phi, QUBIT *wire0, QUBIT *wire1, @@ -720,10 +1011,31 @@ void __catalyst__qis__SingleExcitation(double phi, QUBIT *wire0, QUBIT *wire1, { RT_FAIL_IF(wire0 == wire1, "Invalid input for SingleExcitation gate. All two qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation( - "SingleExcitation", {phi}, - {reinterpret_cast(wire0), reinterpret_cast(wire1)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(wire0, wire1); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("SingleExcitation", {phi}, + {routedQubitFirst, routedQubitSecond}, + MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "SingleExcitation", {phi}, + {reinterpret_cast(wire0), reinterpret_cast(wire1)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__DoubleExcitation(double phi, QUBIT *wire0, QUBIT *wire1, QUBIT *wire2, @@ -733,6 +1045,9 @@ void __catalyst__qis__DoubleExcitation(double phi, QUBIT *wire0, QUBIT *wire1, Q (wire0 == wire1 || wire0 == wire2 || wire0 == wire3 || wire1 == wire2 || wire1 == wire3 || wire2 == wire3), "Invalid input for DoubleExcitation gate. All four qubit operands must be distinct."); + RT_FAIL_IF(RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr, + "Only single and two-qubit gates are supported when compiling to a given hardware " + "coupling map."); getQuantumDevicePtr()->NamedOperation( "DoubleExcitation", {phi}, {reinterpret_cast(wire0), reinterpret_cast(wire1), @@ -743,69 +1058,210 @@ void __catalyst__qis__DoubleExcitation(double phi, QUBIT *wire0, QUBIT *wire1, Q void __catalyst__qis__ControlledPhaseShift(double theta, QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF(control == target, "Invalid input for ControlledPhaseShift gate. Control and target " - "qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("ControlledPhaseShift", {theta}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF(control == target, + "Invalid input for ControlledPhaseShift gate. Control and target " + "qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("ControlledPhaseShift", {theta}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "ControlledPhaseShift", {theta}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__CRX(double theta, QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF(control == target, - "Invalid input for CRX gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("CRX", {theta}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for CRX gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("CRX", {theta}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "CRX", {theta}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__CRY(double theta, QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF(control == target, - "Invalid input for CRY gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("CRY", {theta}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for CRY gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("CRY", {theta}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "CRY", {theta}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__CRZ(double theta, QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF(control == target, - "Invalid input for CRZ gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("CRZ", {theta}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for CRZ gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("CRZ", {theta}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "CRZ", {theta}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__MS(double theta, QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF(control == target, - "Invalid input for MS gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("MS", {theta}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for MS gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("MS", {theta}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "MS", {theta}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__CRot(double phi, double theta, double omega, QUBIT *control, QUBIT *target, const Modifiers *modifiers) { - RT_FAIL_IF(control == target, - "Invalid input for CRot gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation("CRot", {phi, theta, omega}, - {/* control = */ reinterpret_cast(control), - /* target = */ reinterpret_cast(target)}, - /* modifiers */ MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + control == target, + "Invalid input for CRot gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(control, target); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("CRot", {phi, theta, omega}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "CRot", {phi, theta, omega}, + {/* control = */ reinterpret_cast(control), + /* target = */ reinterpret_cast(target)}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__CSWAP(QUBIT *control, QUBIT *aswap, QUBIT *bswap, const Modifiers *modifiers) { RT_FAIL_IF((control == aswap || aswap == bswap || control == bswap), "Invalid input for CSWAP gate. Control and target qubit operands must be distinct."); + RT_FAIL_IF(RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr, + "Only single and two-qubit gates are supported when compiling to a given hardware " + "coupling map."); getQuantumDevicePtr()->NamedOperation("CSWAP", {}, {reinterpret_cast(control), reinterpret_cast(aswap), @@ -817,6 +1273,9 @@ void __catalyst__qis__Toffoli(QUBIT *wire0, QUBIT *wire1, QUBIT *wire2, const Mo { RT_FAIL_IF((wire0 == wire1 || wire1 == wire2 || wire0 == wire2), "Invalid input for Toffoli gate. All three qubit operands must be distinct."); + RT_FAIL_IF(RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr, + "Only single and two-qubit gates are supported when compiling to a given hardware " + "coupling map."); getQuantumDevicePtr()->NamedOperation("Toffoli", {}, {reinterpret_cast(wire0), reinterpret_cast(wire1), @@ -827,6 +1286,9 @@ void __catalyst__qis__Toffoli(QUBIT *wire0, QUBIT *wire1, QUBIT *wire2, const Mo void __catalyst__qis__MultiRZ(double theta, const Modifiers *modifiers, int64_t numQubits, ...) { RT_ASSERT(numQubits >= 0); + RT_FAIL_IF(RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr, + "Only single and two-qubit gates are supported when compiling to a given hardware " + "coupling map."); va_list args; va_start(args, numQubits); @@ -842,21 +1304,66 @@ void __catalyst__qis__MultiRZ(double theta, const Modifiers *modifiers, int64_t void __catalyst__qis__ISWAP(QUBIT *wire0, QUBIT *wire1, const Modifiers *modifiers) { - RT_FAIL_IF(wire0 == wire1, - "Invalid input for ISWAP gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation( - "ISWAP", {}, {reinterpret_cast(wire0), reinterpret_cast(wire1)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + wire0 == wire1, + "Invalid input for ISWAP gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(wire0, wire1); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("ISWAP", {}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "ISWAP", {}, + {reinterpret_cast(wire0), reinterpret_cast(wire1)}, + MODIFIERS_ARGS(modifiers)); + } } void __catalyst__qis__PSWAP(double phi, QUBIT *wire0, QUBIT *wire1, const Modifiers *modifiers) { - RT_FAIL_IF(wire0 == wire1, - "Invalid input for PSWAP gate. Control and target qubit operands must be distinct."); - getQuantumDevicePtr()->NamedOperation( - "PSWAP", {phi}, - {reinterpret_cast(wire0), reinterpret_cast(wire1)}, - MODIFIERS_ARGS(modifiers)); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + RT_FAIL_IF( + wire0 == wire1, + "Invalid input for PSWAP gate. Control and target qubit operands must be distinct."); + std::tuple> resultTuple = + RTD_PTR->getRuntimeRouter()->getRoutedQubits(wire0, wire1); + QubitIdType routedQubitFirst = std::get<0>(resultTuple); + QubitIdType routedQubitSecond = std::get<1>(resultTuple); + std::vector swapPath = std::get<2>(resultTuple); + if (swapPath.size() > 0) { + for (auto i = 1; i < swapPath.size() - 1; i++) { + QubitIdType u = swapPath[i - 1]; + QubitIdType v = swapPath[i]; + RTD_PTR->getQuantumDevicePtr()->NamedOperation("SWAP", {}, {u, v}, + MODIFIERS_ARGS(modifiers)); + } + } + getQuantumDevicePtr()->NamedOperation("PSWAP", {phi}, + {/* control = */ routedQubitFirst, + /* target = */ routedQubitSecond}, + /* modifiers */ MODIFIERS_ARGS(modifiers)); + } + else { + getQuantumDevicePtr()->NamedOperation( + "PSWAP", {phi}, + {reinterpret_cast(wire0), reinterpret_cast(wire1)}, + MODIFIERS_ARGS(modifiers)); + } } static void _qubitUnitary_impl(MemRefT_CplxT_double_2d *matrix, int64_t numQubits, @@ -897,6 +1404,10 @@ void __catalyst__qis__QubitUnitary(MemRefT_CplxT_double_2d *matrix, const Modifi RT_FAIL("Invalid number of wires"); } + RT_FAIL_IF(RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr, + "Only single and two-qubit gates are supported when compiling to a given hardware " + "coupling map."); + va_list args; std::vector> coeffs; std::vector wires; @@ -920,6 +1431,10 @@ ObsIdType __catalyst__qis__HermitianObs(MemRefT_CplxT_double_2d *matrix, int64_t RT_FAIL("The Hermitian matrix must be initialized"); } + RT_FAIL_IF(RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr, + "Only single and two-qubit gates are supported when compiling to a given hardware " + "coupling map."); + const size_t num_rows = matrix->sizes[0]; const size_t num_col = matrix->sizes[1]; const size_t expected_size = std::pow(2, numQubits); @@ -979,6 +1494,10 @@ ObsIdType __catalyst__qis__HamiltonianObs(MemRefT_double_1d *coeffs, int64_t num "The coefficients list must be initialized."); } + RT_FAIL_IF(RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr, + "Only single and two-qubit gates are supported when compiling to a given hardware " + "coupling map."); + const size_t coeffs_size = coeffs->sizes[0]; if (static_cast(numObs) != coeffs_size) { @@ -1008,13 +1527,37 @@ RESULT *__catalyst__qis__Measure(QUBIT *wire, int32_t postselect) if (postselect != 0 && postselect != 1) { postselectOpt = std::nullopt; } - - return getQuantumDevicePtr()->Measure(reinterpret_cast(wire), postselectOpt); + QubitIdType mappedWire = reinterpret_cast(wire); + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) + mappedWire = RTD_PTR->getRuntimeRouter()->getMappedWire(mappedWire); + return getQuantumDevicePtr()->Measure(mappedWire, postselectOpt); } -double __catalyst__qis__Expval(ObsIdType obsKey) { return getQuantumDevicePtr()->Expval(obsKey); } +double __catalyst__qis__Expval(ObsIdType obsKey) +{ + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + std::vector> finalSwaps = + RTD_PTR->getRuntimeRouter()->getFinalPermuteSwaps(); + for (auto i = 0; i < finalSwaps.size(); i++) { + RTD_PTR->getQuantumDevicePtr()->NamedOperation( + "SWAP", {}, {std::get<0>(finalSwaps[i]), std::get<1>(finalSwaps[i])}); + } + } + return getQuantumDevicePtr()->Expval(obsKey); +} -double __catalyst__qis__Variance(ObsIdType obsKey) { return getQuantumDevicePtr()->Var(obsKey); } +double __catalyst__qis__Variance(ObsIdType obsKey) +{ + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + std::vector> finalSwaps = + RTD_PTR->getRuntimeRouter()->getFinalPermuteSwaps(); + for (auto i = 0; i < finalSwaps.size(); i++) { + RTD_PTR->getQuantumDevicePtr()->NamedOperation( + "SWAP", {}, {std::get<0>(finalSwaps[i]), std::get<1>(finalSwaps[i])}); + } + } + return getQuantumDevicePtr()->Var(obsKey); +} void __catalyst__qis__State(MemRefT_CplxT_double_1d *result, int64_t numQubits, ...) { @@ -1033,6 +1576,14 @@ void __catalyst__qis__State(MemRefT_CplxT_double_1d *result, int64_t numQubits, result_p->sizes, result_p->strides); if (wires.empty()) { + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + std::vector> finalSwaps = + RTD_PTR->getRuntimeRouter()->getFinalPermuteSwaps(); + for (auto i = 0; i < finalSwaps.size(); i++) { + RTD_PTR->getQuantumDevicePtr()->NamedOperation( + "SWAP", {}, {std::get<0>(finalSwaps[i]), std::get<1>(finalSwaps[i])}); + } + } getQuantumDevicePtr()->State(view); } else { @@ -1062,6 +1613,14 @@ void __catalyst__qis__Probs(MemRefT_double_1d *result, int64_t numQubits, ...) result_p->strides); if (wires.empty()) { + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + std::vector> finalSwaps = + RTD_PTR->getRuntimeRouter()->getFinalPermuteSwaps(); + for (auto i = 0; i < finalSwaps.size(); i++) { + RTD_PTR->getQuantumDevicePtr()->NamedOperation( + "SWAP", {}, {std::get<0>(finalSwaps[i]), std::get<1>(finalSwaps[i])}); + } + } getQuantumDevicePtr()->Probs(view); } else { @@ -1093,6 +1652,14 @@ void __catalyst__qis__Sample(MemRefT_double_2d *result, int64_t numQubits, ...) result_p->strides); if (wires.empty()) { + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + std::vector> finalSwaps = + RTD_PTR->getRuntimeRouter()->getFinalPermuteSwaps(); + for (auto i = 0; i < finalSwaps.size(); i++) { + RTD_PTR->getQuantumDevicePtr()->NamedOperation( + "SWAP", {}, {std::get<0>(finalSwaps[i]), std::get<1>(finalSwaps[i])}); + } + } getQuantumDevicePtr()->Sample(view); } else { @@ -1126,6 +1693,14 @@ void __catalyst__qis__Counts(PairT_MemRefT_double_int64_1d *result, int64_t numQ result_counts_p->sizes, result_counts_p->strides); if (wires.empty()) { + if (RTD_PTR != nullptr && RTD_PTR->getRuntimeRouter() != nullptr) { + std::vector> finalSwaps = + RTD_PTR->getRuntimeRouter()->getFinalPermuteSwaps(); + for (auto i = 0; i < finalSwaps.size(); i++) { + RTD_PTR->getQuantumDevicePtr()->NamedOperation( + "SWAP", {}, {std::get<0>(finalSwaps[i]), std::get<1>(finalSwaps[i])}); + } + } getQuantumDevicePtr()->Counts(eigvals_view, counts_view); } else {