3.1. Conversion example from Eka

Note

The widgets in this notebook are interactive!

We start by defining an Eka object that encodes a simple memory experiment on a distance 3 repetition code, that consists of a reset, a logical X and a measurement, with some syndromes extraction.

from loom.eka.block import Block
from loom.eka.eka import Eka
from loom.eka.lattice import Lattice
from loom.eka.operations.code_operation import (
    LogicalX,
    MeasureBlockSyndromes,
    MeasureLogicalZ,
    ResetAllDataQubits,
)
from loom.eka.pauli_operator import PauliOperator
from loom.eka.stabilizer import Stabilizer
import loom.visualizer as vis
from loom.interpreter.interpreter import interpret_eka
import plotly.io as pio

pio.renderers.default = "notebook"

# Set up a bit-flip repetition code with a distance of 3
distance = 3
lattice = Lattice.linear((distance + 1,))

base_position = (1,)
initial_rep_block = Block(
    unique_label="q1",
    stabilizers=tuple(
        Stabilizer(
            pauli="ZZ",
            data_qubits=(
                (base_position[0] + i, 0),
                (base_position[0] + i + 1, 0),
            ),
            ancilla_qubits=((base_position[0] + i, 1),),
        )
        for i in range(distance - 1)
    ),
    logical_x_operators=(
        PauliOperator("XXX", tuple((base_position[0] + i, 0) for i in range(distance))),
    ),
    logical_z_operators=(PauliOperator("Z", ((base_position[0], 0),)),),
)


meas_block_and_meas_log = [
    ResetAllDataQubits(initial_rep_block.unique_label, state="0"),
    MeasureBlockSyndromes(initial_rep_block.unique_label, n_cycles=1),
    LogicalX(initial_rep_block.unique_label),
    MeasureBlockSyndromes(initial_rep_block.unique_label, n_cycles=1),
    MeasureLogicalZ(initial_rep_block.unique_label),
]

eka = Eka(lattice, blocks=[initial_rep_block], operations=meas_block_and_meas_log)

interpreted_eka = interpret_eka(eka)

stab_plot = vis.StabilizerPlot(
    lattice,
    title=f"Repetition Code",
)
stab_plot.add_dqubit_traces()
stab_plot.plot_blocks(initial_rep_block)
# Sphinx formatting
stab_plot._fig.update_layout(margin=dict(t=60, l=30, b=30), width=680, height=400)
stab_plot.show()

3.1.1. Conversion to QASM

from loom.executor import EkaToQasmConverter
from qiskit import transpile, qasm3
from qiskit_aer import Aer

converter = EkaToQasmConverter()

# Convert the Eka circuit to QASM string representation
QASM_string, q_register, c_register = converter.convert(interpreted_eka)

# Execute the QASM circuit using Qiskit Aer simulator
backend = Aer.get_backend("aer_simulator")
qc = qasm3.loads(QASM_string)  # load QASM‑3
qc = transpile(qc, backend)  # prepare for backend
shots = 3
result = backend.run(qc, shots=shots).result().get_counts()  # run (no “execute”)

print("Simulation results: ")

converter.parse_target_run_outcome((result, c_register))
Simulation results: 
{'c_(1, 0)_0': [1, 1, 1],
 'c_(1, 1)_0': [0, 0, 0],
 'c_(1, 1)_1': [0, 0, 0],
 'c_(2, 0)_0': [1, 1, 1],
 'c_(2, 1)_0': [0, 0, 0],
 'c_(2, 1)_1': [0, 0, 0],
 'c_(3, 0)_0': [1, 1, 1]}

3.1.2. Conversion to Stim

from loom.executor import EkaToStimConverter


converter = EkaToStimConverter()

stim_circuit, q_register, c_register = converter.convert(interpreted_eka)

# Sample results
sampler = stim_circuit.compile_sampler()
num_samples = 3
results = sampler.sample(shots=num_samples)

# Format and print simulation results
print("Results:")
converter.parse_target_run_outcome((results, c_register))
Results:
{'c_(1, 0)_0': [1, 1, 1],
 'c_(1, 1)_0': [0, 0, 0],
 'c_(1, 1)_1': [0, 0, 0],
 'c_(2, 0)_0': [1, 1, 1],
 'c_(2, 1)_0': [0, 0, 0],
 'c_(2, 1)_1': [0, 0, 0],
 'c_(3, 0)_0': [1, 1, 1]}

3.1.3. Other converter available :

  • Xanadu’s Pennylane: EkaToPennylaneConverter

  • Nvidia’s CudaQ: EkaToCudaqConverter

  • QPerfect’s MimiQ: EkaToMimiqConverter

As expected, since there is no noise in the simulations used, the 2 rounds of syndromes extractions yield the same outputs. The measured outcome of the data qubits is always 1, which is what is expected after applying reset and X on the logical qubit.