results: register defaults, YieldHandling key formats, and root exports - #76
results: register defaults, YieldHandling key formats, and root exports#76qci-amos wants to merge 5 commits into
Conversation
Both fell back to get_default_register, which lists every qubit in the circuit whether or not it was measured, so an unmeasured qubit was padded into the bitstring and read as an outcome. They now default to get_measurements_register and fall back to every qubit only when nothing was measured at all. Passing get_default_register explicitly still gets the padded register. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
apply() tested keys with `"*" not in k`, which raises TypeError on the integer keys count_measurements produces for key_format=None, and built its all-erasures fallback key with str.replace. Only string keys can hold a splat, so the erasure test now checks for that and the fallback key falls back to 0 for a non-string key. An empty distribution raised StopIteration from the fallback, which is not a useful error for a counts dict that post selection emptied; it now raises ValueError. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The user guide refers to these as dwave.gate.Result and dwave.gate.YieldHandling, which did not resolve. They are re-exported from the package root and remain importable from dwave.gate.results, which is still where they are defined, so __module__ does not move. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #76 +/- ##
==========================================
+ Coverage 90.18% 90.71% +0.53%
==========================================
Files 31 31
Lines 5317 5377 +60
==========================================
+ Hits 4795 4878 +83
+ Misses 522 499 -23 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
|
||
| Only the string key formats can hold a splat: the | ||
| :func:`.count_measurements` function produces integer keys only for | ||
| memory that is entirely 0s and 1s. |
There was a problem hiding this comment.
wouldn't splay be held as -1? Maybe not here.
There was a problem hiding this comment.
What's at stake here are the data types that count_measurements can return. The key_format there may be bin, hex, and None. Maybe we should remove hex and None, I don't know of any current use of those (count_measurements was written before erasures).
But regardless, hex and "integer" for keys don't support splats. We only have -1 outside of a bitstring context or * for a bitstring context.
There was a problem hiding this comment.
That said, I can imagine that at scale we'll want some representation more compact than strings... but we're not at the point where that matters yet and Qiskit requires strings afaik.
| :exception:`ValueError`: If the distribution is empty, or if the | ||
| yield is too low for | ||
| :attr:`.renormalize_distribution_or_raise`. | ||
| :exception:`ZeroDivisionError`: If nothing survives post selection |
There was a problem hiding this comment.
Is this the best error type for this case?
There was a problem hiding this comment.
It's raised here:
dwave-gate/dwave/gate/results.py
Line 711 in 5d1f965
I generally try to use the built-in types where it makes sense and it seems like this qualifies: there really is a "divide by zero" that would occur here. We could do a custom error if that's what you mean?
There was a problem hiding this comment.
I think I meant that our code should prevent an known divide-by-zero error if it's caused by a bad user input (not allow such an input) or a bad calculation on our part (return some message that there are insufficient samples or some such).
If it means a big change to the application, I don't have a preference for a custom error. See what makes sense to you
There was a problem hiding this comment.
The cause of this could be a circuit that's too deep or wide. So it's not that the input is "bad" in a validation sense nor a bug in the qpu/simulator.
We've debated in the past whether this case should be a log message (that might be missed) or a raised exception and we've gone with the later because otherwise downstream logic (e.g., qiskit) might raise their own less-informative exception if they receive an empty bitstring distribution.
| # The user guide refers to these as dwave.gate.Result and | ||
| # dwave.gate.YieldHandling, so re-export them from the package root. They remain | ||
| # importable from dwave.gate.results, which is where they are defined. |
There was a problem hiding this comment.
This comment doesn't really add any value.
| # The user guide refers to these as dwave.gate.Result and | |
| # dwave.gate.YieldHandling, so re-export them from the package root. They remain | |
| # importable from dwave.gate.results, which is where they are defined. |
| # A qubit with no measurements has no bit to contribute, and padding | ||
| # one in makes the bitstring look like an outcome. Fall back to every | ||
| # qubit only when nothing was measured at all. | ||
| register = cast(RegisterType, self.get_measurements_register(tag)) or None |
There was a problem hiding this comment.
It might be correct to use here, but I really don't like typing.cast.
There was a problem hiding this comment.
Is it even needed here? RegisterType is just list[str | None] and self.get_measurements_register() returns list[str].
| # --------------------------------------------------------------------------- | ||
| # Documented import paths | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
|
|
||
| def test_result_classes_are_importable_from_the_package_root(): | ||
| """The guide refers to these as dwave.gate.Result / dwave.gate.YieldHandling.""" | ||
| import dwave.gate | ||
|
|
||
| assert dwave.gate.Result is Result | ||
| assert dwave.gate.YieldHandling is YieldHandling | ||
| assert set(dwave.gate.__all__) == {"Result", "YieldHandling"} | ||
|
|
||
|
|
||
| def test_re_export_leaves_the_defining_module_alone(): | ||
| """Sphinx and pickle both key off __module__, so it must not move.""" | ||
| assert Result.__module__ == "dwave.gate.results" | ||
| assert YieldHandling.__module__ == "dwave.gate.results" | ||
|
|
||
|
|
There was a problem hiding this comment.
I would probably skip these tests since we're not usually testing every single import or namespace. Seems a bit excessive.
Part of splitting #71 into reviewable pieces. This is the non-QCDL half —
dwave.gate.results— and is independent of the other four PRs.Three independent fixes, one commit each.
1.
get_memory/get_countsdefault to the measured registerBoth fell back to
get_default_register, which lists every qubit in thecircuit whether or not it was measured. An unmeasured qubit was therefore
padded into the bitstring with
unmeasured_valueand read as an outcome:a three-qubit circuit measuring only
q1andq2returned{"01_": 3}.They now default to
get_measurements_register, falling back to every qubitonly when nothing was measured at all.
get_default_registeris unchanged andstill available — pass it explicitly to get the padded register back — and its
docstring now says which methods use it and when.
Compatibility: this changes the default output shape for any circuit with
unmeasured qubits. Callers passing an explicit
registerare unaffected.2.
YieldHandling.applyhandles every counts key formatapplytested keys with"*" not in k.count_measurements(..., key_format=None)produces integer keys, so composing the two — which is the obvious thing to
do — raised
TypeError: argument of type 'int' is not iterable. Theall-erasures fallback had the same assumption, building its zeros key with
str.replace.Only the string key formats can hold a splat, so the erasure test now checks
for that, and the fallback key falls back to
0for a non-string key.An empty distribution raised
StopIterationout of that fallback, which is nota useful error for a counts dict that post-selection emptied; it now raises
ValueError.applyalso picked up the docstring it never had.3. Export
ResultandYieldHandlingfrom the package rootThe user guide refers to these as
dwave.gate.Resultanddwave.gate.YieldHandling, which did not resolve. They are re-exported fromthe package root and remain importable from
dwave.gate.results, which isstill where they are defined — a test asserts
__module__does not move, sinceSphinx and pickle both key off it.
Testing
pytest tests/passes.tests/test_results.pycovers the register default(including the explicit-register and nothing-measured paths),
applyagainstevery key format
count_measurementsproduces, and the import paths.🤖 Generated with Claude Code