-
Notifications
You must be signed in to change notification settings - Fork 57
Naive Qubit Mapping and Routing Algorithm in Runtime #1928
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Hello. You may have forgotten to update the changelog!
|
@ritu-thombre99 can you fix the checks? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, I'm excited to add this feature to Catalyst! To merge this in we'll have to meet the PennyLane development standards, like adding tests (both unit and integration), code docs, and make sure there is a good way to control this feature :)
runtime/lib/capi/RuntimeCAPI.cpp
Outdated
RT_FAIL_IF(control == target, | ||
"Invalid input for CNOT gate. Control and target qubit operands must be distinct."); | ||
|
||
std::pair<int,int> routedQubits = getRoutedQubits(control, target, modifiers); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a flag here that controls whether routing is performed or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added option to execution context 4ae4a46#diff-4718a857bb4ed45b3ed299bdb7926e5beca237e53aa645d124f295e0cbbd2357R312
Checks happening before every two-qubit gate 4ae4a46#diff-1a3e5dcf898ac446b6355c7f97c3c62d9039d75db6bb79268d07e3e6d7072bd1R655
runtime/lib/capi/RuntimeCAPI.cpp
Outdated
getQuantumDevicePtr()->StartTapeRecording(); | ||
} | ||
|
||
// Extract coupling map from the kwargs passed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be split out into its own function.
In fact, we want to take all these functions and put them in a new file (Routing.cpp/hpp) that you can then call into.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modularized in Routing.hpp 4ae4a46
const std::vector<QubitIdType> &controlled_wires = {}, | ||
const std::vector<bool> &controlled_values = {}) | ||
{ | ||
// Print to see what naive router does on Null qubits |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add some runtime tests for the routing functionality
runtime/lib/capi/RuntimeCAPI.cpp
Outdated
{/* control = */ routedQubits.first, | ||
/* target = */ routedQubits.second}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should keep the type that is passed to the function as a QubitIdType
(even if you don't use it in your algorithm internally).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed types in 4227d89
runtime/lib/capi/RuntimeCAPI.cpp
Outdated
if (distanceMatrix[std::make_pair(*j_itr,*i_itr)] + distanceMatrix[std::make_pair(*i_itr,*k_itr)] < distanceMatrix[std::make_pair(*j_itr,*k_itr)] ) | ||
{ | ||
distanceMatrix[std::make_pair(*j_itr,*k_itr)] = distanceMatrix[std::make_pair(*j_itr,*i_itr)] + distanceMatrix[std::make_pair(*i_itr,*k_itr)]; | ||
predecessorMatrix[std::make_pair(*j_itr,*k_itr)] = predecessorMatrix[std::make_pair(*i_itr,*k_itr)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The next question is where we want to store this kind of information. If we don't want to touch device backends, but still want it to specific to each device (unlike the global you have right now), I think the natural place for it would be in the RTDevice
class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defined them as class variables in Routing.hpp 4ae4a46 which are initialized only if wires passed to PennyLane device are in a format of list of tuples representing coupling map of the hardware
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally we'll want to device how to pass down the coupling map from the frontend to the runtime.
I think the shortcut via the device_kwargs is okay for now, although normally those are meant to go the Device class constructor. We may want to associate them closer to the runtime then than the device, like we do with shots or qubit management.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few fly-by comments from me.
Feel free to ignore them as needed
@@ -0,0 +1,180 @@ | |||
// Copyright 2025 Xanadu Quantum Technologies Inc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this file meant to be the header, or the implementation?
If the header, can we add an #pragma once
at the top? If the implementation, we should probably split the header interface and implementation details separately.
runtime/lib/capi/Routing.hpp
Outdated
|
||
#include "RuntimeCAPI.h" | ||
|
||
const int MAXIMUM = 1e9; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than having scope visibility across this header, can this be isolated? Otherwise, this will be exported and visible in the generated translation unit.
Either programmatically passing this as an argument to a function/method where needed with a given default value, or (if it is required we set this magic variable), it can be isolated in an anonymous namespace, which should ensure it isn't visible in the compiled code outside of where it is needed.
runtime/lib/capi/RuntimeCAPI.cpp
Outdated
/** | ||
* @brief Global routing pass pointer. | ||
*/ | ||
static std::unique_ptr<RoutingPass> RUNTIME_ROUTER = nullptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this to be global? Is there a way we can isolate this so it isn't exposed? Any concerns with thread locality?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, the router should be able to be scoped to each device instance (RTDevice)
d978ed6
to
4227d89
Compare
Context:
Description of the Change:
When running a quantum circuit on a hardware with certain connectivity constraints, two-qubit gates like CNOTs can only be executed using physical qubits that are connected by an edge on the hardware.
Hence, necessary SWAPs need to be inserted in order to route the logical qubits so that they are mapped to an edge on the device, while respecting other compiling constraint i.e. order the gate dependencies from the input quantum circuit, and ensuring compiled quantum circuit is equivalent to the input quantum circuit.
Following image shows a simple example:
Benefits:
Possible Drawbacks:
Related GitHub Issues:
Example usage:
Currently, seeing what SWAP gates are added can be viewed by using
null.qubit
done by printingNamedOperation
inruntime/lib/backend/null_qubit/NullQubit.hpp
Output: