Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #71 +/- ##
==========================================
+ Coverage 90.18% 91.36% +1.17%
==========================================
Files 31 31
Lines 5317 5869 +552
==========================================
+ Hits 4795 5362 +567
+ Misses 522 507 -15 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
qci-amos
marked this pull request as ready for review
August 19, 2026 14:52
qci-amos
requested review from
JoelPasvolsky,
randomir and
thisac
and removed request for
thisac
August 19, 2026 14:52
This was referenced Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Client-side fixes for findings from the QCDL alpha test report (2026-08-18). Each
of these was reported as something
dwave-gateaccepts silently and the serviceeither rejects one round trip later or, worse, executes into a plausible-looking
wrong answer.
Closes 11 findings: QCDL-03, 04, 05, 06, 07, 10, 12, 13, 14, 25, 26.
Partially addresses QCDL-23: adds the missing
sxdggate.Nothing here touches the service, the transpiler, or
dwave-cloud-client, so thereport's two critical findings (QCDL-16 register-driven rotations, QCDL-09 mixed
register arithmetic) are still open. See Follow-ups.
What changed
@qcdldecorator validation — QCDL-03 / 04 / 05qcdl_circuit.pynum_qubitsis validated when the decorator is built:< 1, non-integers, anda bare
@qcdl(used without parentheses, so the function itself arrives asnum_qubits) all raiseQCDLUserError. Previously@qcdl(0)and@qcdl(-1)built an empty program.
q<N>silently receives nothing. Any required parameter left unbound now raises a
TypeErrorthat keeps Python's ownmissing 1 required positional argument: 'alpha'wording and then explains the rule and what was actually supplied.This was the single most confusing failure in the report.
num_qubitsgenerates a qubit the signature has no parameter for, thatqubit was previously dropped from the program in silence. It now raises,
naming the dropped qubit. Environment mode is exempt, since an environment
legitimately supplies its whole set.
Gate argument validation — QCDL-06 / 07
operations.pyA
_validate_qubit_argsdecorator on all 34 operations checks the qubitarguments before the statement is recorded:
QCDLUserError: h() parameter 'qubit' must be a qubit, not int (5); …instead of surfacing later asAttributeError: 'int' object has no attribute 'procedure'.cx() needs distinct qubits, but parameters 'control_qubit' and 'target_qubit' are both q0.cx(q0, q0)previously built cleanly and failed only at submission.
Whether the qubits must be distinct is derived from the signature rather than
configured per gate: each named qubit parameter is a separate role in the
operation, whereas a
*qubitsparameter is a set, soinitialize(q0, q1, q0)and
barrier(q0, q0)remain legal.A wrong number of arguments still reports against the real signature, and
functools.wrapskeeps signatures and docstrings intact for sphinx.Duplicate register declarations — QCDL-10
components.py,registers.pyThe compiler keeps the first allocation of a register name, so re-declaring
one was a silent no-op that discarded the second initial value.
Procedure.register_memory_allocationnow tracks names per module per procedureand reports the collision. The same name on different qubits, and in a nested
procedure, is still fine.
Re-declaring deliberately is allowed via the existing
alias=True/ignore_reallocation=Truearguments — but not together with an explicitinitial_value, because that value is information the caller stated explicitlyand the compiler would throw away:
ignore_reallocation=True, no valueignore_reallocation=True, explicit valuealias=True, no valuealias=True, explicit valuealias=Trueraises even for a fresh name: an alias never emits an allocation, soits initial value is dead on arrival. String aliases (
alias="other") are leftalone — those do emit an allocation carrying the value, so it is the compiler's
call.
To tell an omitted initial value from an explicit
0,RegisterandFixedPointRegisternow defaultinitial_valuetoNoneand resolve it perdtype (
0/0.0).Arrayneeded no change: itsinitial_valueis a requiredpositional, so array contents always count as explicit.
one_to_allargument types — QCDL-14components.pyQCDLModule.one_to_all(destinations, …)accepted only aScope, so passingscope.qcdl_modules— as the guide does — failed with an internal-soundingAttributeError: 'list' object has no attribute 'qcdl_modules'. It now accepts aScope, a singleQCDLModule, or a sequence of either, deduplicated and inorder.
scope_idis carried only by a realScope, since a bare sequence has noidentity. Anything else gets a message naming what was expected.
Result API — QCDL-12 / 13
results.pyget_counts()andget_memory()now default their register toget_measurements_register(), so a qubit that was never measured is left outrather than padded into the bitstring as
_. A three-qubit circuit measuringtwo qubits used to report
'1_'; it now reports'1'. The old behaviour isstill reachable by passing
register=get_default_register(...)explicitly, andthe all-qubits register remains the fallback when nothing was measured at all.
YieldHandling.apply()no longer assumes string keys. Feeding itcount_measurements(key_format=None)output raisedTypeError: argument of type 'numpy.int64' is not a container; integer keyscannot hold an erasure, so they are now treated as splat-free. The
all-erasures fallback had the same assumption one line down.
apply({})raisesValueErrorinstead of leakingStopIteration, whichsilently terminates any generator it is raised in. An empty counts dict is a
natural output of
get_counts(post_select=True).Namespace and missing operations — QCDL-25 / 26 / 23
operations.py__all__, sofrom dwave.gate.qcdl.operations import *brings in theoperations and
AngleTypeonly, rather than also bindingnp,Any,Sequence,TypeAliasand theimplementationsmodule. Dropped the unusedSequenceimport.reset(qubit). It was taught in the guide asq0.reset(), which workedonly through
__getattr__, so it had no importable function, signature ordocstring. The operation emits a byte-identical statement to
q0.reset(),asserted by a test, so it is an alias for the documented behaviour rather than
a variant.
sxdg(qubit), one of the three Qiskit-parity gaps in QCDL-23. It emitsthe op name directly, matching
sdg/tdg, sincesxdgis a realQuantumCircuitmethod and the transpiler is Qiskit-based.Documentation
docs/workflow.rstresetoperation. One of them alsoimported
initializeand never used it.detect_erasure_example(q)declared a parameter namedq, which can neverreceive a qubit. Renamed to
q0. It would have raised under the new QCDL-03check; it survived because the example is defined and never called.
all_to_allexample (and the matching docstring incomponents.py)re-declared a register name to get a single-qubit view of shared memory. That
is exactly what
alias=Trueis for, so both now use it. Semantics areunchanged — the aliased register still writes to
q0only, without mirroring.Behaviour changes reviewers should know about
Several things that previously succeeded now raise. All of them were reported as
defects, but they are visible:
@qcdl(0),@qcdl(-1),@qcdl(2.5), bare@qcdl→QCDLUserError.@qcdl(N)with a signature that does not name all N qubits →QCDLUserError.QCDLUserError.QCDLUserError, notAttributeError.QCDLUserError.alias=Trueorignore_reallocation=Truetogether with aninitial_value→QCDLUserError.Register(None, name="r")used to raise;Nonenow means "use the default".get_counts()/get_memory()bitstrings may be narrower than before, sinceunmeasured qubits are dropped.
from dwave.gate.qcdl.operations import *no longer bindsnp,implementations, or the typing helpers.Testing
pytest— 438 passing, up from 300 onmain. New coverage intest_qcdl_circuit.py,test_operations.py,test_registers.py,test_scope.pyandtest_results.pyfor every fix above, including thehappy paths each new check could plausibly break.
make -C docs doctest— 102testcodeblocks execute. Two pre-existingfailures in
implementations.pydocstrings (they useRegisterwithoutimporting it) are untouched and pass only because an earlier block in the same
document imports it.
One thing that is not verified:
sxdgis checked for the QCDL it emits, butnot against a live solver's operation whitelist. It needs one integration smoke
test. If the service rejects it, the fallback is emitting
rx(-π/2)the waysy/sydgemitry(±π/2), at the cost of a global phase.The
qcdl_alpha_testssuite is deliberately untouched. Six of itsxfail(strict=True)cases now XPASS — QCDL-03, 04, 06, 10, 12, 13 — which is thereport's own signal that a finding is fixed, and eleven tests that pinned the old
behaviour now fail on purpose. That repo and the report need a follow-up pass.
Follow-ups
Not in this PR, roughly in the order I would do them:
Register/FixedPointRegisterarithmetic.OpsMixin._broadcasted_opis thesingle chokepoint; it needs
RegisterExpressionto carry a dtype so the checkpropagates through nested expressions. That would also fix the related latent
bug where a float-valued expression exposes
&,|,^and>>.in the service transpiler, but
dwave-gatecan stop the silent failure byrefusing register-valued angles on any gate the transpiler rewrites (only
rzand
pare verified correct). Needs a decision on raise vs. warn.ResultandYieldHandlingfromdwave.gate(theguide already cross-references
dwave.gate.Resultin five places, all broken)and add a documented constructor for
Result.model_validate(orjson.loads(future.answer_data.read())).and still carries a
.. todo::.QCDLStatementBridgein thepayload, failing at encode time rather than at the statement.
the list of ops the compiler accepts, so it needs coordination.
dwave-cloud-clientrespectively, not this repo.Also noticed while working here, not from the report:
initialize()andbarrier()with no arguments raiseIndexError: tuple index out of range.@qcdlexamples in the guide define a circuit and never call it, somake docs doctestnever executes the body. That is how theq-instead-of-q0parameter above survived; adding the call line to each example would catch the
next one.
renois a dev dependency but there is noreleasenotes/directory, so thisPR adds no release note.