Hi CUDA-Q team, thank you for adding the quantum embedding helpers on main and resolving #2982. I had been working locally on the same feature area during unitaryHACK 2026, but I was not able to submit that work because I had reached the unitaryHACK 2026 PR limit. After testing the current main branch locally, I found one silent correctness issue and a few smaller robustness / docs follow-ups that seem worth tracking.
I checked the items below against main at commit 3344a8a99 (3344a8a997b93bba04e89a524386cc45c0cb3176, 2026-06-27). I did not find a duplicate issue for these in NVIDIA/cuda-quantum.
To reproduce from the same revision:
git clone --filter=blob:none https://github.com/NVIDIA/cuda-quantum.git
cd cuda-quantum
git checkout 3344a8a99 # or use current main
git rev-parse --short HEAD
Summary
| # |
Area |
Item |
Severity |
| 1 |
Python correctness |
amplitude_encode rejects 2-D NumPy arrays but silently flattens equivalent nested Python list / tuple inputs |
High |
| 2 |
C++ portability |
runtime/cudaq/builder/kernels.h uses std::optional without directly including <optional> |
Low |
| 3 |
Docs |
Unicode math glyphs are used inside Doxygen / Sphinx math expressions |
Low |
| 4 |
C++ robustness |
amplitude_encode(const state&) reads get_tensor(0) without validating tensor count / rank in that helper |
Low-Med |
| 5 |
C++ robustness |
nextPowerOfTwo can overflow into a non-terminating loop for pathological sizes |
Low |
| 6 |
Python API consistency |
Builder-mode angular_encode rejects NumPy angle arrays, while amplitude_encode accepts NumPy arrays |
Low / maybe intentional |
1. amplitude_encode silently flattens nested Python sequences
amplitude_encode documents that input must be one-dimensional, and it correctly rejects a 2-D NumPy array. The equivalent nested Python list or tuple, however, takes the list/tuple branch and is flattened with .ravel() before any ndim check.
Source check:
grep -n -A1 'isinstance(data, (list, tuple))' python/cudaq/contrib/encoding.py
grep -n -A2 'isinstance(data, np.ndarray)' python/cudaq/contrib/encoding.py
rg -n 'test_amplitude_encode_2d|np\.eye|expected a 1D vector' python/tests/contrib/test_amplitude_encode.py
Observed on 3344a8a99:
54: if isinstance(data, (list, tuple)):
55- return np, np.asarray(data, dtype=dtype).ravel()
57: if isinstance(data, np.ndarray):
58- if data.ndim != 1:
59- raise ValueError("amplitude_encode: expected a 1D vector.")
99:def test_amplitude_encode_2d_rejected():
101: cudaq.contrib.amplitude_encode(np.eye(2), pad=0)
Expected: all non-1D inputs raise ValueError("amplitude_encode: expected a 1D vector."), independent of whether the caller used a NumPy array, nested list, or nested tuple.
Observed: the existing test covers only the NumPy path. The list/tuple path accepts the nested input and normalizes the flattened vector.
Source-build runtime repro:
import numpy as np
import cudaq
for label, data in [
("NumPy 2-D array", np.eye(2)),
("nested Python list", [[1, 0], [0, 1]]),
("tuple of tuples", ((1, 0), (0, 1))),
]:
try:
state = cudaq.contrib.amplitude_encode(data, pad=0)
print(label, "accepted:", state.num_qubits(), np.asarray(state).round(4))
except Exception as e:
print(label, "raised:", type(e).__name__, e)
Expected output:
NumPy 2-D array raised: ValueError amplitude_encode: expected a 1D vector.
nested Python list raised: ValueError amplitude_encode: expected a 1D vector.
tuple of tuples raised: ValueError amplitude_encode: expected a 1D vector.
Observed from the current code path:
NumPy 2-D array raised: ValueError amplitude_encode: expected a 1D vector.
nested Python list accepted: 2 [0.7071+0.j 0. +0.j 0. +0.j 0.7071+0.j]
tuple of tuples accepted: 2 [0.7071+0.j 0. +0.j 0. +0.j 0.7071+0.j]
This looks like a silent correctness issue: a caller who accidentally passes a matrix-shaped nested sequence gets a valid-looking two-qubit state instead of the documented shape error.
2. kernels.h uses std::optional without including <optional>
runtime/cudaq/builder/kernels.h declares validateAngularEncodeSizes(std::optional<std::size_t> qSize, ...), but does not directly include <optional>.
Verify:
grep -n 'std::optional' runtime/cudaq/builder/kernels.h
grep -n '#include <optional>' runtime/cudaq/builder/kernels.h || true
grep -n '^#include' runtime/cudaq/builder/kernels.h
Observed:
194:inline void validateAngularEncodeSizes(std::optional<std::size_t> qSize,
11:#include "kernel_builder.h"
12:#include <complex>
13:#include <functional>
14:#include <span>
15:#include <stdexcept>
Expected: a header that names std::optional includes <optional> directly. It may compile today via a transitive include, but the direct include would make the header more portable.
3. Unicode glyphs inside math expressions
Some Doxygen / Sphinx math expressions use literal Unicode glyphs such as α, θ, ⟩, and ψ inside math. HTML rendering may tolerate this, but LaTeX/PDF builds are usually more robust when the math uses commands such as \alpha, \theta, \rangle, and \psi consistently.
Verify:
grep -nP '[^\x00-\x7F]' runtime/cudaq/algorithms/encoding.h python/cudaq/contrib/encoding.py
Selected observed lines:
runtime/cudaq/algorithms/encoding.h:34:/// Coefficients are \f$α_i = x'_i / \|\mathbf{x}'\|_2\f$.
runtime/cudaq/algorithms/encoding.h:37:/// \f$|\psi\rangle = \sum_{i=0}^{N-1} α_i |i\rangle\f$, where
python/cudaq/contrib/encoding.py:89: :math:`|\psi⟩ = \sum_{i=0}^{N-1} α_i |i⟩`, where
python/cudaq/contrib/encoding.py:204: |ψ⟩
python/cudaq/contrib/encoding.py:213: R_P(θ) = e^{-i θ P / 2}, \quad P \in \{X, Y, Z\},
Expected: math expressions use LaTeX commands consistently.
Observed: literal glyphs are mixed with LaTeX commands in the same formulas.
4. amplitude_encode(const state&) does not validate state tensor shape in the helper
The C++ state overload converts input through stateToAmplitudeVector, which reads the first tensor and then flat-indexes the state. I did not find a guard in that helper that the input has exactly one rank-1 state-vector tensor.
Inspect:
grep -nE 'get_tensor\(0\)|get_num_tensors|get_rank' runtime/cudaq/algorithms/encoding.cpp
sed -n '60,72p' runtime/cudaq/algorithms/encoding.cpp
Observed:
61: const auto tensor = data.get_tensor(0);
std::vector<std::complex<double>> stateToAmplitudeVector(const state &data) {
const auto tensor = data.get_tensor(0);
const std::size_t numElements = tensor.get_num_elements();
if (numElements == 0)
throw std::invalid_argument("amplitude_encode: input must be non-empty.");
std::vector<std::complex<double>> vec;
vec.reserve(numElements);
for (std::size_t i = 0; i < numElements; ++i)
vec.push_back(data[i]);
return vec;
}
Expected: either document that cudaq::state inputs are guaranteed to be a single rank-1 state vector before this point, or reject unsupported shapes with a clear exception.
Observed: this helper only checks numElements == 0; a non-vector-shaped state appears to be flat-read and re-normalized.
5. nextPowerOfTwo can overflow for pathological sizes
This is defensive only, because such an input cannot realistically fit in memory. Still, the helper can wrap p to zero for a size larger than the largest representable power of two, after which p < n remains true forever.
Where:
sed -n '17,26p' runtime/cudaq/algorithms/encoding.cpp
Standalone reproduction of the same loop:
g++ -std=c++20 -O2 -x c++ -o /tmp/cudaq_npot_check - <<'CPP'
#include <cstdio>
#include <cstddef>
int main() {
std::size_t n = (std::size_t(1) << 63) + 1;
std::size_t p = 1;
for (int iters = 1; p < n; ++iters) {
p <<= 1;
if (iters > 70) {
std::printf("p=%zu after wrap; loop would not terminate without cap\n", p);
return 0;
}
}
std::printf("terminated p=%zu\n", p);
}
CPP
/tmp/cudaq_npot_check
Observed:
p=0 after wrap; loop would not terminate without cap
Expected: reject impossible sizes before the shift can overflow, or otherwise avoid the wraparound loop.
6. Builder-mode angular_encode rejects NumPy angle arrays
This may be intentional, but it is a small API inconsistency with amplitude_encode, which accepts NumPy arrays. In builder mode, angular_encode accepts list / tuple or a kernel list argument, so a 1-D NumPy array of angles raises TypeError.
Inspect:
sed -n '162,190p' python/cudaq/contrib/encoding.py
Source-build runtime repro:
import numpy as np
import cudaq
kernel = cudaq.make_kernel()
q = kernel.qalloc(2)
cudaq.contrib.angular_encode(kernel, q, [0.1, 0.2], rotation="Y")
cudaq.contrib.angular_encode(kernel, q, np.array([0.1, 0.2]), rotation="Y")
Expected, if NumPy arrays are intended to be accepted consistently: both calls append the corresponding rotations.
Observed:
TypeError: cudaq.contrib.angular_encode: angles must be a list[float] or a kernel list argument
If NumPy arrays are intentionally unsupported in builder mode, documenting that explicitly would be enough.
Hi CUDA-Q team, thank you for adding the quantum embedding helpers on
mainand resolving #2982. I had been working locally on the same feature area during unitaryHACK 2026, but I was not able to submit that work because I had reached the unitaryHACK 2026 PR limit. After testing the currentmainbranch locally, I found one silent correctness issue and a few smaller robustness / docs follow-ups that seem worth tracking.I checked the items below against
mainat commit3344a8a99(3344a8a997b93bba04e89a524386cc45c0cb3176, 2026-06-27). I did not find a duplicate issue for these inNVIDIA/cuda-quantum.To reproduce from the same revision:
Summary
amplitude_encoderejects 2-D NumPy arrays but silently flattens equivalent nested Pythonlist/tupleinputsruntime/cudaq/builder/kernels.husesstd::optionalwithout directly including<optional>amplitude_encode(const state&)readsget_tensor(0)without validating tensor count / rank in that helpernextPowerOfTwocan overflow into a non-terminating loop for pathological sizesangular_encoderejects NumPy angle arrays, whileamplitude_encodeaccepts NumPy arrays1.
amplitude_encodesilently flattens nested Python sequencesamplitude_encodedocuments that input must be one-dimensional, and it correctly rejects a 2-D NumPy array. The equivalent nested Pythonlistortuple, however, takes the list/tuple branch and is flattened with.ravel()before anyndimcheck.Source check:
Observed on
3344a8a99:Expected: all non-1D inputs raise
ValueError("amplitude_encode: expected a 1D vector."), independent of whether the caller used a NumPy array, nested list, or nested tuple.Observed: the existing test covers only the NumPy path. The list/tuple path accepts the nested input and normalizes the flattened vector.
Source-build runtime repro:
Expected output:
Observed from the current code path:
This looks like a silent correctness issue: a caller who accidentally passes a matrix-shaped nested sequence gets a valid-looking two-qubit state instead of the documented shape error.
2.
kernels.husesstd::optionalwithout including<optional>runtime/cudaq/builder/kernels.hdeclaresvalidateAngularEncodeSizes(std::optional<std::size_t> qSize, ...), but does not directly include<optional>.Verify:
Observed:
Expected: a header that names
std::optionalincludes<optional>directly. It may compile today via a transitive include, but the direct include would make the header more portable.3. Unicode glyphs inside math expressions
Some Doxygen / Sphinx math expressions use literal Unicode glyphs such as
α,θ,⟩, andψinside math. HTML rendering may tolerate this, but LaTeX/PDF builds are usually more robust when the math uses commands such as\alpha,\theta,\rangle, and\psiconsistently.Verify:
grep -nP '[^\x00-\x7F]' runtime/cudaq/algorithms/encoding.h python/cudaq/contrib/encoding.pySelected observed lines:
Expected: math expressions use LaTeX commands consistently.
Observed: literal glyphs are mixed with LaTeX commands in the same formulas.
4.
amplitude_encode(const state&)does not validate state tensor shape in the helperThe C++
stateoverload converts input throughstateToAmplitudeVector, which reads the first tensor and then flat-indexes the state. I did not find a guard in that helper that the input has exactly one rank-1 state-vector tensor.Inspect:
Observed:
Expected: either document that
cudaq::stateinputs are guaranteed to be a single rank-1 state vector before this point, or reject unsupported shapes with a clear exception.Observed: this helper only checks
numElements == 0; a non-vector-shaped state appears to be flat-read and re-normalized.5.
nextPowerOfTwocan overflow for pathological sizesThis is defensive only, because such an input cannot realistically fit in memory. Still, the helper can wrap
pto zero for a size larger than the largest representable power of two, after whichp < nremains true forever.Where:
sed -n '17,26p' runtime/cudaq/algorithms/encoding.cppStandalone reproduction of the same loop:
Observed:
Expected: reject impossible sizes before the shift can overflow, or otherwise avoid the wraparound loop.
6. Builder-mode
angular_encoderejects NumPy angle arraysThis may be intentional, but it is a small API inconsistency with
amplitude_encode, which accepts NumPy arrays. In builder mode,angular_encodeacceptslist/tupleor a kernel list argument, so a 1-D NumPy array of angles raisesTypeError.Inspect:
sed -n '162,190p' python/cudaq/contrib/encoding.pySource-build runtime repro:
Expected, if NumPy arrays are intended to be accepted consistently: both calls append the corresponding rotations.
Observed:
If NumPy arrays are intentionally unsupported in builder mode, documenting that explicitly would be enough.