Error‑Correction Workflow with ParameterTable

Error‑correction experiments are a stress‑test for hybrid programming:

  • you repeat a syndrome‑measurement circuit many times,

  • extract classical information (the syndrome),

  • use that information to choose or parametrize a recovery,

  • and log data for later analysis — all while keeping the quantum hardware busy.

This page explains:

  1. What usually makes hybrid error‑correction workflows painful, and

  2. How the ParameterTable interface helps by making the classical–quantum boundary explicit.

It builds on the example shown in the README and in the Home page.

1. The usual challenges in hybrid error‑correction programs

1.1 Two worlds with different assumptions

When writing an error‑correction loop, you typically have:

  • Qiskit circuits describing:

    • state preparation / encoding,

    • syndrome‑measurement steps,

    • and (optionally) recovery circuits.

  • QUA programs (or other control code) that:

    • manage loops over shots and cycles,

    • talk to the hardware (pulses, measurements, streaming),

    • and push / pull data to and from the classical host.

These two worlds use different abstractions:

  • Qiskit assumes a single circuit run at a time, with parameters bound either at compile time or at primitive submission time;

  • QUA assumes long‑running programs with nested loops, streaming, and explicit buffers.

Bridging them manually often leads to a lot of glue code.

1.2 Classical data flow is awkward to express

In a realistic error‑correction experiment you might need to:

  • record a syndrome bitstring (or integer) every cycle,

  • pass that syndrome to a classical controller (on DGX, another server, or Python),

  • receive back recovery parameters or decisions,

  • and then update QUA‑side variables accordingly.

With “plain” Qiskit circuits alone, you quickly run into:

  • circuit explosion: one circuit per possible branch or per time step;

  • or ad‑hoc data plumbing: many custom classical registers, custom JSON blobs, and brittle indexing on both sides.

1.3 Keeping qubit‑level structure and parameters in sync

Error‑correction codes also care deeply about which qubit carries which role (data vs ancilla), and which parameters (angles, thresholds, syndrome history) are used where.

Without a consistent interface, it is easy to end up with:

  • parameter names that differ between Qiskit and QUA,

  • mismatched dimensions (e.g. you think you streamed num_cycles × num_qubits values but the program expects a different shape),

  • confusion about which piece of classical data corresponds to which Qiskit parameter.

2. How ParameterTable helps

ParameterTable is designed as a single source of truth for real‑time parameters and classical data flowing between Qiskit and QUA. In an error‑correction workflow, it plays three key roles:

  1. Declaring QUA‑side variables with a clear name, type, and direction.

  2. Describing how classical data is streamed in and out of the program.

  3. Providing a Python‑side handle to push values to the OPX or fetch results.

2.1 Making the classical–quantum boundary explicit

Instead of scattering variables between ad‑hoc QUA declarations and Python dictionaries, you define them centrally as Parameter objects (or in a dictionary) and bundle them into a ParameterTable. For example:

from qiskit_qm_provider import Parameter, ParameterTable, Direction, InputType

syndrome_data = Parameter(
    "syndrome_data",
    0,
    input_type=InputType.INPUT_STREAM,
    direction=Direction.INCOMING,
)

recovery_vars = ParameterTable.from_qiskit(
    recovery_circuit,
    input_type=InputType.INPUT_STREAM,
)
  • syndrome_data represents the syndrome integer that will be sent back to the host.

  • recovery_vars is a table of all parameters used by the recovery circuit.

QUA‑side, you simply call:

recovery_vars.declare_variables()
syndrome_data.declare_variable()
syndrome_data.declare_stream()

to make these variables live inside the program.

2.2 Streaming syndrome data out

Inside the QUA loop, after running the syndrome‑measurement circuit via backend.quantum_circuit_to_qua, you use get_measurement_outcomes to obtain a state_int representing the measured syndrome:

from qiskit_qm_provider.backend.backend_utils import get_measurement_outcomes

syndrome_meas_result = backend.quantum_circuit_to_qua(syndrome_circuit)
syndrome_meas_dict = get_measurement_outcomes(syndrome_circuit, syndrome_meas_result)
state_int_val = syndrome_meas_dict[ancilla_creg.name]["state_int"]

syndrome_data.assign(state_int_val)
syndrome_data.stream_back(reset=True)

Conceptually:

  • state_int_val is the hybrid handshake: a compact representation of the measured syndrome.

  • syndrome_data.assign(...) makes it available as a QUA variable.

  • syndrome_data.stream_back(...) pushes it to the output stream so the host can read it.

On the Python side, you can then fetch the stream and apply any classical processing you need (decoding, look‑up tables, machine‑learning models, etc.).

2.3 Streaming recovery parameters in

Once the host has computed new recovery parameters, you can push them back to the OPX using the same ParameterTable:

# Python side (after some classical processing of syndrome history)
param_dict = {
    "theta_0": value_for_cycle_0,
    "theta_1": value_for_cycle_1,
    # ...
}

recovery_vars.push_to_opx(param_dict, job, qm, verbosity=0)

In the QUA program, you then “wake up” these parameters at the right time:

recovery_vars.load_input_values()
backend.quantum_circuit_to_qua(recovery_circuit, recovery_vars)
  • load_input_values() reads the streamed values into the QUA variables.

  • quantum_circuit_to_qua(..., recovery_vars) binds them into the recovery circuit.

Because the same ParameterTable definition is used on both sides, you avoid:

  • name mismatches,

  • shape mismatches,

  • and ad‑hoc JSON or array indexing conventions.

2.4 Keeping the workflow scalable

By decoupling:

  • what data you exchange (captured in ParameterTable and Parameter definitions), from

  • how often / when you exchange it (captured in QUA loops and host‑side logic),

the system stays scalable as you:

  • increase the number of cycles,

  • change the code distance or number of ancillas,

  • or extend the set of recovery parameters.

You are not forced to re‑encode this structure in dozens of Qiskit circuits or ad‑hoc buffers: the mapping remains localized in the parameter definitions.

3. Putting it all together

The full error‑correction example in the README (and referenced on the Home page) illustrates this pattern:

  1. A syndrome‑measurement circuit and a recovery circuit are authored in Qiskit.

  2. ParameterTable and Parameter objects declare which classical values will flow between host and device.

  3. backend.quantum_circuit_to_qua embeds the circuits inside a structured QUA program with loops over memory experiments and cycles.

  4. get_measurement_outcomes and syndrome_data.stream_back(...) expose the syndrome data as a compact, streamable integer.

  5. recovery_vars.push_to_opx(...) and recovery_vars.load_input_values() realize the feedback step by feeding processed parameters back into QUA.

The end result is a hybrid error‑correction loop where:

  • Qiskit remains the language for circuits and gates,

  • QUA remains the language for real‑time control and streaming,

  • and ParameterTable is the contract that keeps both views aligned.

This is the kind of workflow qiskit-qm-provider is designed to make natural rather than painful.