Overview

Readout is the mechanism that controls what information is extracted from the quantum backend after a functional is executed. It is specified separately from the functional itself, so the same circuit or schedule can be measured in different ways without any code change to the functional.

All uses start with an instance of the class Readout.

Readout is a builder that accumulates readout methods by chaining with_* calls. Each call returns a new spec with one additional slot filled; the original object is never modified.

from qilisdk.readout import Readout
from qilisdk.analog import Z

spec = Readout()                                  # no readout selected yet
spec = spec.with_sampling(nshots=1000)            # add sampling
spec = spec.with_expectation(observables=[Z(0)])  # add expectation values

# or in one line:
spec = Readout().with_sampling(nshots=1000).with_expectation(observables=[Z(0)])

The finished spec is passed to execute() (or submit() for remote hardware):

from qilisdk.backends import QiliSim
from qilisdk.digital import Circuit
from qilisdk.functionals import DigitalPropagation

backend = QiliSim()
circuit = Circuit(2)
functional = DigitalPropagation(circuit)
result = backend.execute(functional, readout=spec)

Note

At least one readout method must be added before calling execute. Passing an empty Readout() raises a ValueError.

Any combination of the three readout types can be requested in a single execution by chaining the with_* calls. The backend performs one circuit run and extracts all requested outputs.

from qilisdk.analog import Z
from qilisdk.readout import Readout

spec = (
    Readout()
    .with_sampling(nshots=500)
    .with_expectation(observables=[Z(0)])
    .with_state_tomography()
)
result = backend.execute(functional, readout=spec)

counts = result.get_samples()              # from sampling
ev     = result.get_expectation_values()   # from expectation
state  = result.get_state()                # from state tomography

Accessing Results

execute() returns a FunctionalResult. There are two ways to access the readout data.

Convenient Shortcuts

The quickest way to access results:

Property

Type

Requires

get_samples()

dict[str, int]

with_sampling()

get_probabilities()

dict[str, float]

with_sampling() or with_state_tomography()

get_expectation_values()

list[float]

with_expectation()

get_state()

QTensor

with_state_tomography()

These raise ValueError at runtime if the corresponding readout was not requested.

Typed Forwarding Properties

For production code or when combining multiple readout types, use the typed forwarding properties. These return the raw result objects and let the type checker verify access without runtime guards:

from qilisdk.analog import Z
from qilisdk.readout import Readout

spec = Readout().with_sampling(nshots=1000).with_expectation(observables=[Z(0)])
result = backend.execute(functional, readout=spec)

# result.sampling is SamplingReadoutResult - the type checker knows this
top2 = result.sampling.get_probabilities(n=2)

# result.expectation is ExpectationReadoutResult - the type checker knows this
raw_evs = result.expectation.expectation_values

Property

Returns

Requires

result.sampling

SamplingReadoutResult | None

with_sampling()

result.expectation

ExpectationReadoutResult | None

with_expectation()

result.state_tomography

StateTomographyReadoutResult | None

with_state_tomography()

Complete Example

The following example runs an analog annealing schedule, collects expectation values and the final state at every time step, then plots the observable trajectory.

import numpy as np
import matplotlib.pyplot as plt

from qilisdk.analog import Schedule, X, Z
from qilisdk.backends import QutipBackend
from qilisdk.core import ket
from qilisdk.functionals import AnalogEvolution
from qilisdk.readout import Readout

T = 5.0
schedule = Schedule(
    hamiltonians={"driver": X(0), "problem": Z(0)},
    coefficients={
        "driver": {(0.0, T): lambda t: 1.0 - t / T},
        "problem": {(0.0, T): lambda t: t / T},
    },
    dt=0.1,
)

functional = AnalogEvolution(
    schedule=schedule,
    initial_state=(ket(0) - ket(1)).unit(),
    store_intermediate_results=True,
)

spec = (
    Readout()
    .with_expectation(observables=[Z(0)])
    .with_state_tomography()
)

result = QutipBackend().execute(functional, readout=spec)

# Final results
print("Final <Z>:", result.get_expectation_values()[0])
print("Final state:", result.get_state())

# Time-resolved expectation values
ev_trajectory = [step[0] for step in result.get_intermediate_expectation_values()]
plt.plot(np.linspace(0, T, len(ev_trajectory)), ev_trajectory)
plt.xlabel("Time")
plt.ylabel("⟨Z⟩")
plt.title("Expectation value during annealing")
plt.show()