QuBlock is a lightweight Python library for block-encoding primitives, semantic
simulation on statevectors, and OpenQASM export. The pip package name is
qublock, and the import namespace is blockflow.
- Explicit block-encoding invariants with normalization, error tolerance, and success tracking.
- Fast semantic execution for algorithm validation without circuits.
- Recipe-based circuits with declared wire requirements.
- OpenQASM 2 or 3 export with a minimal gate set.
- Small dependency footprint (NumPy only at runtime).
- Python 3.9+
- NumPy >= 1.22
pip install qublockOptional extras:
pip install qublock[gpu] # CuPy backend
pip install qublock[torch] # PyTorch backend
pip install qublock[sparse] # SciPy sparse operatorsimport numpy as np
from blockflow import (
ApplyBlockEncodingStep,
BlockEncoding,
NumpyMatrixOperator,
Program,
ResourceEstimate,
SemanticExecutor,
StateVector,
)
mat = np.array([[0, 1], [1, 0]], dtype=complex)
op = NumpyMatrixOperator(mat)
be = BlockEncoding(op=op, alpha=1.0, resources=ResourceEstimate())
program = Program([ApplyBlockEncodingStep(be)])
state = StateVector(np.array([1.0, 0.0], dtype=complex))
final_state, report = SemanticExecutor().run(program, state, renormalize_each_step=True)from blockflow import (
Capabilities,
Circuit,
StaticCircuitRecipe,
WireSpec,
)
circ = Circuit(num_qubits=1)
circ.add("h", [0])
recipe = StaticCircuitRecipe(WireSpec(system_qubits=1), circ)
be_with_recipe = BlockEncoding(
op=op,
alpha=1.0,
resources=ResourceEstimate(),
recipe=recipe,
capabilities=Capabilities(supports_circuit_recipe=True),
)
qasm = be_with_recipe.export_openqasm(flavor="qasm3")- Linear operators: implement the
LinearOperatorprotocol (shape,apply,apply_adjoint). - Block encodings:
BlockEncodingwraps an operator withalpha,epsilon, resources, and recipes. - Vector encodings:
VectorEncodingrepresents normalized state-prep with optional recipes. - Capabilities and success:
CapabilitiesandSuccessModelcapture supported operations and postselection success rates. - Resources:
ResourceEstimatetracks ancillas, depth, and gate counts. - Recipes and circuits:
CircuitRecipeproduces backend-agnosticCircuitobjects with aWireSpec. - QASM export:
to_openqasmemits a minimal gate set to QASM2/QASM3.
SemanticExecutor applies each program step directly to the system statevector.
This is useful for validating algorithmic structure before committing to a
specific circuit implementation.
program = Program([ApplyBlockEncodingStep(be)])
state = StateVector(np.array([1.0, 0.0], dtype=complex))
final_state, report = SemanticExecutor().run(program, state)The RunReport accumulates uses, success probabilities, and ancilla peaks.
Semantic execution defaults to NumPy. You can switch backends without changing code by setting environment variables:
BLOCKFLOW_BACKEND=numpy|cupy|torch|auto(default: numpy)BLOCKFLOW_DEVICE=cuda(torch only, optional)BLOCKFLOW_DTYPE=complex64|complex128|float32|float64(optional)BLOCKFLOW_COPY_STATE=0to avoid copying the initial state
Example:
BLOCKFLOW_BACKEND=cupy python your_script.pyTo apply a block encoding multiple times in one step, use repeat:
program = Program([ApplyBlockEncodingStep(be, repeat=5)])Recipes declare required wires and return a backend-agnostic circuit. The block encoding verifies that the recipe matches its resource claims.
from blockflow import Capabilities, Circuit, StaticCircuitRecipe, WireSpec
circ = Circuit(num_qubits=2)
circ.add("h", [0])
circ.add("cx", [0, 1])
recipe = StaticCircuitRecipe(WireSpec(system_qubits=2), circ)
be = BlockEncoding(
op=op,
alpha=1.0,
resources=ResourceEstimate(),
recipe=recipe,
capabilities=Capabilities(supports_circuit_recipe=True),
)
qasm3 = be.export_openqasm(flavor="qasm3")
qasm2 = be.export_openqasm(flavor="qasm2", optimize=False)Export currently supports a minimal gate set (h, x, y, z, s, t,
cx, cz, swap, rx, ry, rz, and measure) plus controlled variants in QASM3.
If you have a 2^n x 2^n matrix, QuBlock can synthesize a block-encoding circuit
without attaching a recipe. There are two paths:
- If
A / alphais unitary (2x2 only), it synthesizes a 1-qubit circuit that implements it. - Otherwise, it builds an LCU block encoding using the Pauli expansion of
A(requiresalpha == sum(|Pauli coeffs|)).
For LCU synthesis you can choose a strategy:
prep_selectusesceil(log2(m))ancillas (plus one phase ancilla if needed) and multi-controlled select gates, wheremis the number of nonzero Pauli terms.sparseuses one ancilla per term and single-controlled select gates.
Both LCU strategies export only to QASM3 because they use controlled rotations.
mat = np.array([[0, 1], [1, 0]], dtype=complex)
be = BlockEncoding(
op=NumpyMatrixOperator(mat),
alpha=1.0,
resources=ResourceEstimate(),
capabilities=Capabilities(supports_circuit_recipe=True),
synthesis_strategy="prep_select", # or "sparse"
)
qasm = be.export_openqasm()Use optimize_circuit to apply simple peephole optimizations, or disable it
when you need a 1:1 recipe export.
optimized = be.build_circuit(optimize=True)
raw = be.build_circuit(optimize=False)notebooks/lcu_demo.ipynbwalks through LCU synthesis and QASM export.
Tests run with coverage enforcement:
pytestRuff is configured for linting and import sorting:
ruff check .tests/test_qiskit_integration.py compares QASM output against Qiskit statevector
simulation. Install Qiskit to enable those tests.