NVIDIA / NVIDIA/cuda-quantum

Python API docs and docstrings contradict the code in several places

Open
#5,181 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

stale-notified
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:

  1. The cudaq.apply_noise custom-channel example cannot be used as written.
    In the API reference, the .. code-block:: python under the error_type
    parameter puts def __init__ at the same indentation level as
    class CustomNoiseChannel, so the constructor is a module-level function
    rather than a method, and the two trailing noise = 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
    raises RuntimeError: std::bad_cast; driving it through cudaq.apply_noise
    aborts the interpreter with SIGABRT. The C++ tab of the same paragraph is
    correct, and the repository's own test_NoiseModel.py uses the correct
    shape.

  2. The specification's "fine-grained access to the bits and counts" example
    prints wrong data silently.
    It uses for bits, count in counts:.
    SampleResult iterates over bit-string keys, like a dict, so for the
    two-qubit bell kernel the example demonstrates, Python unpacks the
    bit-string '00' into two characters and prints Observed: 0, 0 — which
    looks plausible next to the C++ tab's Observed: 00, 514, but contains none
    of the real counts. At any other qubit count it raises ValueError.

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 at 228f099a0
  • 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_noise block 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 leaves python_api.rst alone entirely, and Defect 1
    is best handled wherever that page is next touched.
  • Two more default_ops.rst defects and the examples.rst deuteron
    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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.