cuda-quantum
cuda-quantum copied to clipboard
[RFC] [Language] Quantum allocation with state initialization
TODO:
- [x] Library mode implementation (@amccaskey)
- [x] Python support for kernel_builder (@amccaskey)
- [ ] Python support for ast_bridge kernels (@annagrin @amccaskey)
- [x] C++ Bridge Support for qvector / qubit initialization (@schweitzpgi)
- [x] C++ kernel_builder support for qalloc (@amccaskey)
- [x] Validate C++ kernel_builder approach (check Alex on his work, @schweitzpgi)
- [x] Error checking on number of elements in MLIR Verifier (@schweitzpgi )
- [x] Simulation subclass work (implementing
CircuitSimulator::addQubitsToState(with data)
), need kron-prod on GPU (@anthony-santana) - [x] Qubit initializer list
- [ ] Check
vector<complex>
kernel input works end-to-end (@anthony-santana) - [ ] House-keeping: tests, python tests errors (@anthony-santana, @annagrin)
- [ ] Examples (@anthony-santana)
- [ ] Documentation page (@anthony-santana @schweitzpgi @amccaskey)
- [ ] cudaq::state input (@1tnguyen and @schweitzpgi, requires #1467)
- [ ] Density Matrix and TensorNet backends updates will require #1467
- [ ] Implement original
from_state
decomposition in MLIR (@boschmitt) - [ ] Simulation scalar type work - compile time error messages for incompatible object files
Example with #1467
kernel(cudaq::state& initState) {
cudaq::qvector q = initState;
}
def kernel(initState : cudaq.state):
q = cudaq.qvector(initState)`
I propose we update the language to support quantum allocation with user-provided initial state specification. This should supersede functions like from_state(...)
on the kernel_builder
.
C++:
New constructors
qubit::qubit(const vector<complex<double>>&);
qubit::qubit(const initializer_list<complex<double>>&);
qvector::qvector(const vector<complex<double>>&);
qvector::qvector(const initializer_list<complex<double>>&);
New builder method
QuakeValue qalloc(vector<complex<double>> &)
Python
The Python builder would be similar as in the following.
v = [0., 1., 1., 0.]
qubits = kernel.qalloc(v)
@cudaq.kernel
def test(vec : list[complex]):
q = cudaq.qvector(vec)
...
C++ Usage
The following snippet demonstrates what this might look like:
__qpu__ auto test0() {
// Init from state vector
cudaq::qubit q = {0., 1.};
return mz(q);
}
__qpu__ auto test1() {
// Init from predefined state vectors
cudaq::qubit q = cudaq::ket::one;
return mz(q);
}
__qpu__ void test2() {
// Init from state vector
cudaq::qubit q = {M_SQRT1_2, M_SQRT1_2};
}
__qpu__ void test3() {
// Init from state vector
cudaq::qvector q = {M_SQRT1_2, 0., 0., M_SQRT1_2};
}
__qpu__ void test4(const std::vector<cudaq::complex> &state) {
// State vector from host
cudaq::qvector q = state;
}
void useBuilder() {
std::vector<cudaq::complex> state{M_SQRT1_2, 0., 0., M_SQRT1_2};
{
// (deferred) qubit allocation from concrete state vector
auto kernel = cudaq::make_kernel();
auto qubitsInitialized = kernel.qalloc(state);
}
{
// kernel parameterized on input state data
auto [kernel, inState] = cudaq::make_kernel<std::vector<cudaq::complex>>();
auto qubitsInitialized = kernel.qalloc(inState);
cudaq::sample(kernel, state).dump();
}
}
Python usage
Vectors of complex or floating-point numbers
Notes
- Implicit conversion from a list of float to a list of complex is allowed on argument passing.
- Automatic conversion of initializer elements will happen if the precision of the numbers in
qvector
initializer does not match the current simulation precision- Emit warning on conversion due to performance concerns, recommend using
cudaq.amplitudes
orcudaq.complex
.
- Emit warning on conversion due to performance concerns, recommend using
# Passing complex vectors as params
c = [.70710678 + 0j, 0., 0., 0.70710678]
@cudaq.kernel
def kernel(vec: list[complex]):
q = cudaq.qvector(vec)
# Capturing complex vectors
c = [.70710678 + 0j, 0., 0., 0.70710678]
@cudaq.kernel
def kernel():
q = cudaq.qvector(c)
# Capturing complex vectors and converting to
# numpy array inside the kernel
c = [.70710678 + 0j, 0., 0., 0.70710678]
@cudaq.kernel
def kernel():
q = cudaq.qvector(np.array(c))
# Creating complex arrays inside kernels
@cudaq.kernel
def kernel():
q = cudaq.qvector([1.0 + 0j, 0., 0., 1.])
Numpy arrays
# From np array created inside a kernel with a complex dtype
c = [.70710678 + 0j, 0., 0., 0.70710678]
@cudaq.kernel
def kernel(vec: list[complex]):
q = cudaq.qvector(np.array(vec, dtype=complex))
c = [.70710678 + 0j, 0., 0., 0.70710678]
@cudaq.kernel
def kernel(vec: list[complex]):
q = cudaq.qvector(np.array(vec, dtype=np.complex64))
# Using precision-agnostic API
c = [.70710678 + 0j, 0., 0., 0.70710678]
@cudaq.kernel
def kernel(vec: list[complex]):
q = cudaq.qvector(np.array(vec, dtype=cudaq.complex()))
c = cudaq.amplitudes([.70710678, 0., 0., 0.70710678])
@cudaq.kernel
def kernel(vec: list[complex]):
q = cudaq.qvector(vec)
# Passing np arrays as params
c = np.array(c, dtype=cudaq.complex())
@cudaq.kernel
def kernel(vec: np.array):
q = cudaq.qvector(vec)
c = np.array(c, dtype=cudaq.complex())
@cudaq.kernel
def kernel(vec: np.ndarray):
q = cudaq.qvector(vec)
c = np.array(c, dtype=cudaq.complex())
@cudaq.kernel
def kernel(vec: np.ndarray[any, complex]):
q = cudaq.qvector(vec)
For library-mode / simulation we pass the state data along to NVQIR. For physical backends, we can replace runtime state data with the result of a circuit synthesis pass (like the current implementation in from_state(...)
.