MeasurementResult#

class MeasurementResult[source]#

Raw bitstring counts returned by Backend.run().

run() always returns a MeasurementResult. For standard (non-batched) backends the object arrives pre-populated (data is immediately available without any further call). For BatchedBackend the object starts empty and is filled only when dispatch() is called. Any attempt to read the data before population raises RuntimeError.

MeasurementResult is a Mapping, so all dict-style access works unchanged.

Examples

The typical way to obtain a MeasurementResult is through run():

from qrisp import QuantumCircuit
from qrisp.default_backend import QrispSimulatorBackend

circuit = QuantumCircuit(1)
circuit.h(0)
circuit.measure(0)

backend = QrispSimulatorBackend()
result = backend.run(circuit, shots=1024)

The object supports all standard dict-style operations:

>>> print(result["0"])                      # e.g. 512
>>> print(result["1"])                      # e.g. 512
>>> print(len(result))                      # 2
>>> print(set(result))                      # {'1', '0'}
>>> for bitstring, count in result.items():
...     print(bitstring, count)

Equality comparison works against a plain dict:

>>> result == {"0": 512, "1": 512}         # True if counts match exactly

Membership check:

>>> isinstance(result, Mapping)             # True
>>> isinstance(result, dict)                # False

For the batched (lazy) case, the object is returned before the circuits are executed. Accessing data before dispatch() is called raises RuntimeError:

from qrisp import QuantumFloat
from qrisp.default_backend import QrispSimulatorBackend

batched_backend = QrispSimulatorBackend().batched()

qf = QuantumFloat(3)
qf[:] = 5

result = qf.get_measurement(backend=batched_backend)  # returns immediately (empty)

# result["0"]   # RuntimeError: Call dispatch() first.

batched_backend.dispatch()
print(result)   # {5: 1.0}

Methods#

MeasurementResult._populate() None[source]#

Raise until the result is populated via _inject().

Re-raises _error if one was stored by _inject_error(), otherwise raises RuntimeError to signal that dispatch() has not been called yet.

MeasurementResult._inject(counts: dict) None[source]#

Populate with raw backend results.

Called by run() for standard backends and by dispatch() for batched backends.

Parameters:
countsdict

Raw bitstring-to-count mapping produced by the backend.

MeasurementResult._inject_error(exc: Exception) None[source]#

Store exc so it is raised on every subsequent data access.

After this call _populated remains False, so every subsequent access calls _populate(), which immediately re-raises the stored exception. This ensures the error is visible no matter how many times the result is accessed.

Parameters:
excException

The exception to store and re-raise on access.

DecodedMeasurementResult#

class DecodedMeasurementResult(raw: LazyDict, decoder: Callable[[int], object])[source]#

Human-readable measurement result produced by QuantumVariable.get_measurement().

Wraps a raw or intermediate LazyDict together with a decoder callable. On first access the raw data is read, decoder is applied to every key, duplicate labels are merged by summing their counts, and the result is cached sorted by probability (descending).

Because decoding is deferred, this object can be returned before dispatch() is called when using a BatchedBackend. The decoded data becomes available automatically once the underlying raw result is populated.

Parameters:
rawLazyDict

The source of integer-keyed, normalised probability data (typically an _IntKeyedResult returned by get_measurement_from_qc).

decoderCallable[[int], object]

Maps an integer bitstring index to a user-facing label. This is QuantumVariable.decoder.

Examples

DecodedMeasurementResult is returned directly by get_measurement():

from qrisp import QuantumFloat
from qrisp.default_backend import QrispSimulatorBackend

backend = QrispSimulatorBackend()

qf = QuantumFloat(4)
qf[:] = 7
result = qf.get_measurement(backend=backend)

>>> print(result)               # {7: 1.0}
>>> print(result[7])            # 1.0
>>> print(len(result))          # 1
>>> print(result == {7: 1.0})   # True

When multiple outcomes are possible, the result is sorted by probability, highest first:

from qrisp import QuantumFloat, h

qf = QuantumFloat(2)
h(qf[0])

result = qf.get_measurement(backend=backend, shots=1024)
# e.g. {0: 0.51, 1: 0.49} or {1: 0.52, 0: 0.48} (highest first)

In the batched case the object is returned immediately and decoding happens lazily on first access after dispatch():

from qrisp import QuantumFloat
from qrisp.default_backend import QrispSimulatorBackend

batched_backend = QrispSimulatorBackend().batched()

a = QuantumFloat(4); a[:] = 3
b = QuantumFloat(4); b[:] = 5

res_a = a.get_measurement(backend=batched_backend)
res_b = b.get_measurement(backend=batched_backend)

print(res_a)        # <DecodedMeasurementResult pending>
print(res_b)        # <DecodedMeasurementResult pending>

batched_backend.dispatch()       # populates raw results

print(res_a)        # {3: 1.0}  decoded on first access
print(res_b)        # {5: 1.0}

LazyDict#

class LazyDict[source]#

Abstract lazy mapping whose data is computed exactly once on first access.

LazyDict is the common base class for all result types returned by Qrisp backends. Subclasses implement _populate() to fill self._data the first time any Mapping operation is called. Subsequent accesses reuse the cached data without re-running _populate().

If _populate raises an exception, _populated stays False and the next access will call _populate again. Concrete subclasses such as MeasurementResult exploit this to implement persistent error propagation: they re-raise the stored exception on every call.

All standard Mapping operations ([], len, iter, ==) are supported and trigger population on first use.

Implementing a custom LazyDict

Subclass LazyDict and implement _populate() to fill self._data with a plain dict:

class MyResult(LazyDict):
    def _populate(self):
        self._data = {"answer": 42}

r = MyResult()
print(r._populated)  # False
print(r["answer"])   # 42 (population happens here)
print(r._populated)  # True

Methods#

abstractmethod LazyDict._populate() None[source]#

Fill self._data. Called at most once by _ensure_populated().

LazyDict._ensure_populated() None[source]#

Trigger population if it has not happened yet.

Calls _populate() exactly once on first access. If _populate raises, _populated stays False and the next access will call _populate again.