Python API docs and docstrings contradict the code in several places
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 1.1k
- Forks
- 455
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 165
Description
Required prerequisites
- Consult the security policy
- Make sure you've read the documentation
- Search the issue tracker
- If possible, make a PR with a failing test → PR opened alongside this issue
Describe the bug
Ten separate places in the published Python documentation state something the
code does not do. Each was checked against an installed cudaq package, and
each is a documentation defect — in every case the code is right and the prose,
the parameter name, or the example is wrong.
Two of them are worse than cosmetic, because they do not fail loudly:
-
The
cudaq.apply_noisecustom-channel example cannot be used as written.
In the API reference, the.. code-block:: pythonunder theerror_type
parameter putsdef __init__at the same indentation level as
class CustomNoiseChannel, so the constructor is a module-level function
rather than a method, and the two trailingnoise = cudaq.NoiseModel()/
noise.register_channel(...)lines are indented inside that function body
and never run. Copying the block out of the docs and constructing the channel
raisesRuntimeError: std::bad_cast; driving it throughcudaq.apply_noise
aborts the interpreter withSIGABRT. The C++ tab of the same paragraph is
correct, and the repository's owntest_NoiseModel.pyuses the correct
shape. -
The specification's "fine-grained access to the bits and counts" example
prints wrong data silently. It usesfor bits, count in counts:.
SampleResultiterates over bit-string keys, like adict, so for the
two-qubitbellkernel the example demonstrates, Python unpacks the
bit-string'00'into two characters and printsObserved: 0, 0— which
looks plausible next to the C++ tab'sObserved: 00, 514, but contains none
of the real counts. At any other qubit count it raisesValueError.
The remaining eight are ordinary wrong-name / wrong-type / invalid-syntax
defects, listed under "Steps to reproduce".
Steps to reproduce the bug
Everything below was run against cudaq 0.15.1 on macOS arm64. Locations are
given against 228f099a0.
Defect 1 — apply_noise example indentation — docs/sphinx/api/languages/python_api.rst
Transcribed verbatim from the doc block:
import cudaq
import numpy as np
class CustomNoiseChannel(cudaq.KrausChannel):
num_parameters = 1
num_targets = 1
def __init__(self, params: list[float]): # doc indentation, verbatim
cudaq.KrausChannel.__init__(self)
p = params[0]
self.append(cudaq.KrausOperator(
np.array([[np.sqrt(1 - p), 0], [0, np.sqrt(1 - p)]], dtype=np.complex128)))
self.append(cudaq.KrausOperator(
np.array([[0, np.sqrt(p)], [np.sqrt(p), 0]], dtype=np.complex128)))
self.noise_type = cudaq.NoiseModelType.Unknown
noise = cudaq.NoiseModel()
noise.register_channel(CustomNoiseChannel)
print("CustomNoiseChannel has own __init__ :", "__init__" in CustomNoiseChannel.__dict__)
c = CustomNoiseChannel([0.1])
CustomNoiseChannel has own __init__ : False
RuntimeError: std::bad_cast
Continuing to cudaq.apply_noise(CustomNoiseChannel, 0.1, q) and
cudaq.sample(...) on density-matrix-cpu:
libc++abi: terminating due to uncaught exception of type
nanobind::python_error: RuntimeError: std::bad_cast
exit status 134
With __init__ indented into the class body and the two registration lines
dedented, the same block runs to completion and samples normally.
Defect 2 — SampleResult iteration — docs/sphinx/specification/cudaq/algorithmic_primitives.rst
@cudaq.kernel()
def bell():
q = cudaq.qvector(2)
h(q[0])
x.ctrl(q[0], q[1])
counts = cudaq.sample(bell) # { 00:479 11:521 }
for bits, count in counts: # as documented
print('Observed: {}, {}'.format(bits, count))
Observed: 0, 0
Observed: 1, 1
With counts.items():
Observed: 00, 479
Observed: 11, 521
Changing the kernel to three qubits and leaving the documented form in place:
ValueError: too many values to unpack (expected 2)
Defect 3 — Two .. code-block:: python blocks in the specification are not Python
Same file. Both are transliterations of the adjacent C++ tab:
result = cudaq::observe(kernel, spinOp, *args)
# ^ SyntaxError: invalid syntax
h = 5.907 - 2.1433 * x(0) * x(1) - 2.1433 * y(0) * y(1) +
.21829 * z(0) - 6.125 * z(1)
# ^ SyntaxError
ast.parse over every .. code-block:: python in that file fails on exactly
these two and no others.
Defect 4 — Docstring parameter names that do not exist
>>> kernel = cudaq.make_kernel(); q = kernel.qalloc()
>>> kernel.mz(target=q, register_name="r")
TypeError: PyKernel.mz() got an unexpected keyword argument 'register_name'
>>> kernel.mz(target=q, regName="r") # actual signature
Kernel.mz, Kernel.mx and Kernel.my all document register_name; the
parameter is regName. Same shape in the operators module:
>>> cudaq.operators.custom.define(op_id=..., ...)
TypeError: define() got an unexpected keyword argument 'op_id' # it is `id`
>>> cudaq.operators.custom.instantiate(operator_id=..., degrees=0)
TypeError: instantiate() got an unexpected keyword argument 'operator_id'
# it is `op_id`
and in QuakeValue.slice, whose docstring documents start where the
signature is startIdx.
Defect 5 — cudaq.translate documents the opposite of what it returns
The docstring says the returned string is "the circuit, without measurement
operations". On a kernel containing mz(q):
>>> print(cudaq.translate(bell_pair, format="openqasm2"))
...
creg var3[2];
measure var0 -> var3;
and with format="qir" the output contains
call i64 @__quantum__qis__mz_handle__to__register(...) twice. The docstring's
own worked example, a few lines further down, also shows __quantum__qis__mz
calls. The sentence is copied from cudaq.draw, where it is correct.
Defect 6 — SampleResult.__getitem__ documents float, returns int
>>> type(result['00']), result['00']
(<class 'int'>, 47)
>>> print(cudaq.SampleResult.__getitem__.__doc__)
__getitem__(self, bitstring: str) -> int <- nanobind's own signature
...
Returns:
float: The number of times the given `bitstring` was measured ...
The generated signature and the hand-written text disagree inside the same
__doc__. The sibling count() documents int correctly.
Defect 7 — Nine QuakeValue dunders document an error that is never raised
__neg__, __mul__, __rmul__, __truediv__, __rtruediv__, __add__,
__radd__, __sub__ and __rsub__ all carry
Raises: RuntimeError: if the underlying QuakeValue type is not a float.
With kernel, n = cudaq.make_kernel(int):
n * 5 -> arith.muli %arg0, %c5_i64 : i64
n + 5 -> arith.addi ...
n - 5 -> arith.subi ...
n / 5 -> arith.divsi ...
-n -> arith.negf ... : f64 (promoted, not rejected)
No RuntimeError in any case. __checkTypesAndCreateQuakeValue has emitted
integer arithmetic since before these Raises: lines were added.
Defect 8 — cudaq.get_unitary's docstring code block is not valid Python
Two of its four kernel lines are wrapped in literal backticks and the trailing
print is under-indented, so the block renders with the backticks visible and
fails with SyntaxError if copied. (This one is not reachable from the Sphinx
API pages — get_unitary has no .. autofunction:: entry — but it is what
help(cudaq.get_unitary) prints.)
Expected behavior
Every example in the documentation should run as printed, and every documented
parameter name, return type and raised exception should match the code. In
particular the apply_noise example should be copy-pasteable, and the
SampleResult iteration example should print the counts it appears to print.
Is this a regression? If it is, put the last known working version here.
Not a regression. Each item has been present since the text was introduced; the
oldest date to 2024.
Environment
- CUDA-Q version: 0.15.1 (
aca5853a7); documentation read at228f099a0 - Python version: 3.13
- C++ compiler: n/a (pip-installed wheel)
- Operating system: macOS 26.5.1 (arm64)
Suggestions
A pull request is open alongside this issue covering nine of the ten. It is
comment-, docstring- and whitespace-only; no executable line changes. Defect 1
(the apply_noise indentation in python_api.rst) is deliberately left out of
it, for the reason in the first bullet below, so that file is untouched there.
Two overlaps are worth flagging for whoever triages this:
- The
apply_noiseblock is also rewritten by #2969, which moves large
documentation code blocks into standalone files. Its extracted snippet has
the indentation right, so it would resolve Defect 1 as a side effect. The
corrections PR therefore leavespython_api.rstalone entirely, and Defect 1
is best handled wherever that page is next touched. - Two more
default_ops.rstdefects and theexamples.rstdeuteron
parameter-sweep defect are deliberately not in this issue, because open
pull requests already touch those files. The deuteron one has been raised as
a review comment on the PR that is extracting that file instead.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Review the listed defects in docs/sphinx/api/languages/python_api.rst and docs/sphinx/specification/cudaq/algorithmic_primitives.rst, then compare the named Python docstrings with their installed signatures. Run test_NoiseModel.py and validate the documented snippets, including ast.parse over Python blocks; done means examples execute and parameter, return-type, and Raises text match behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100