Modules

Modules define reusable block graph components that can be composed into larger surface code computations. Define a component once, then place instances of it wherever that operation is needed. Each instance has its own geometry placement and logical values, while sharing the same definition.

Reuse keeps repeated layouts consistent and makes larger graphs easier to build, check, and understand. A component exposes a small named interface, so its caller can connect operations without managing their internal block IDs or action names. Definitions can contain other instances, allowing a circuit to be assembled at several levels of detail.

A definition contains blocks, pipes, actions, and its public interface. An instance places that definition in a parent graph. The parent connects quantum ports and passes classical results between instances.

The root module, named main, is the entry point for the complete computation. It can contain its own blocks and actions as well as child instances, and connects those children into the larger graph. Its quantum ports define the computation’s external inputs and outputs, and its classical outputs expose named logical results. The other named definitions form its reusable component library.

Module structure

Part

BLOG spelling

Purpose

Definition

module Stage { ... }

Store a reusable block graph

Quantum port

in q_in: data = 0

Expose a logical boundary at a Port block

Classical input or output

in basis, out bit = mz

Receive or export a Boolean value

Instance

child0: Stage @ [0, 0, 0]

Place a definition in its parent

Quantum connection

child0.q_out -> child1.q_in

Join two quantum boundaries

Classical binding

read.bit => cap.basis

Supply a value to a child’s classical input

Definitions

A definition owns its local blocks, pipes, actions, and interface. Its block IDs and action names belong to that definition. Reusing it does not require renaming those elements by hand. Each instance gives them a separate context.

A definition may also contain child instances. Definitions can be nested, but cannot call themselves through a cycle.

Quantum ports

A quantum interface names the Port blocks through which logical states enter or leave a definition:

Declaration

Meaning

in q_in: data = 0

Incoming logical state at Port block 0

out q_out: data = 2

Outgoing logical state at Port block 2

The Port’s role and connection determine which spacetime boundary it exposes. Temporal Ports expose the past or future boundary of a patch. Spatial Ports expose a side boundary. A multiplex Port also retains the stationary output described in Blocks and Pipes.

The parent must connect every quantum port of each child instance exactly once. To expose a child port at the parent’s interface, connect it to a parent Port at the same position after rotation and translation, with the same input or output direction and resource type. To compose children, connect an output to an input of the same resource type. Their exposed neighboring blocks must be adjacent with compatible faces; a Hadamard seam supplies a basis change. Connections provide no automatic routing. The parent’s public inputs and outputs remain open to its caller.

The resource type is an annotation of a port’s intended use. It does not change the geometry or physical instructions generated by compilation. data labels an ordinary logical wire; ccz labels a qubit of a three-qubit \(|CCZ\rangle\) resource. The CCZ teleportation example declares three inputs such as in ccz_0: ccz = 3, one for each resource qubit. The caller supplies their joint state. Simulation and verification setups can use these annotations to choose input states and expected logical behavior. Module validation checks that connected ports have matching labels; the annotations themselves do not prepare or certify the resource state.

Classical inputs and outputs

A classical interface carries Boolean values. in basis declares an input that the parent must supply. out bit = mz exports a named measurement, while out odd = m1 ^ m2 exports an expression. Measurement values are corrected logical results, as explained in Actions.

The parent uses the public name, such as read.bit, rather than the child’s private measurement name mz. A binding such as read.bit => cap.basis supplies that result to another instance. It creates a classical dependency and does not add a pipe between patches.

Each required classical input binds once. Dependencies must remain acyclic, and an executable root has no unresolved classical inputs. Composition also rechecks named readouts against the quantum outputs that remain live after connection.

Instances and placement

An instance names its definition and gives its placement. For example, child1: Stage @ [0, 0, 1] rotate Z 180 rotates the definition about its own origin, then translates it into the parent. For a local position \(p\), the parent position is

\[ p_{\mathrm{parent}} = R p + t. \]

The transform applies to blocks, pipes, action targets, and interface positions. Nested instances compose these transforms. The names child0.q_in and child1.q_in identify different uses of the same public port.

Static definitions with default cube heights can rotate about any axis. Definitions containing T, selective, walking, patch rotation, or custom height blocks must preserve +Z. Y blocks permit coordinate half turns. The supported orientation is checked across the complete nested definition.

Quantum connections

The parent writes each quantum connection explicitly:

Connection

Effect

0 -> child0.q_in

Replace the child’s input Port with parent block 0

child0.q_out -> child1.q_in

Remove the two child Ports and join their neighboring blocks

child1.q_out -> 1

Replace the child’s output Port with parent block 1

Place the instances so the exposed boundaries are compatible. Directions, resource types, face bases, and geometry must agree after transformation. Connections add no automatic routing. An authored -H> connection supplies a Hadamard basis change, with the corresponding endpoint frames.

Child action targets follow the instance’s rotation and translation. Feedback on a consumed Port transfers to the retained wire in its endpoint Pauli frame; X and Z exchange when it crosses a Hadamard pipe. The compiler composes measurement correlation surfaces across the seam, so feedback in child0 can flip a measurement in child1 and create a dependency between their actions. A child’s public Boolean inputs are supplied through explicit classical bindings.

Examples

Compose three CNOTs

main places child0, child1, and child2 from one CNOT definition. Each uses the same control and target logical qubits. The parent connects each CNOT’s outputs directly to the next CNOT’s inputs.

import bloq


def build_graph():
    cnot = bloq.BlockGraph.definition("CNOT", bloq.GalleryItem.CNOT.load())
    graph = bloq.BlockGraph.from_definitions([cnot, bloq.BlockGraph()])
    for index in range(3):
        graph.place_module(f"child{index}", "CNOT")
    graph.connect_modules("child0.control_out", "child1.control_in")
    graph.connect_modules("child1.control_out", "child2.control_in")
    return graph
use bloq::prelude::*;

fn build_graph() -> Result<BlockGraph> {
    let mut cnot = GalleryItem::CNOT.build();
    cnot.name = "CNOT".into();
    let mut graph = BlockGraph::from_definitions(vec![cnot, BlockGraph::new()])?;
    for index in 0..3 {
        graph.place_module(&format!("child{index}"), "CNOT")?;
    }
    graph.connect_modules("child0.control_out", "child1.control_in")?;
    graph.connect_modules("child1.control_out", "child2.control_in")?;
    Ok(graph)
}
BLOG 1.0

module CNOT {
  in control_in: data = 0
  out control_out: data = 3
  in target_in: data = 6
  out target_out: data = 9

  0: Port [0, 0, 0] <In_Control>
  1: ZXZ [0, 0, 1]
  2: ZXZ [0, 0, 2]
  3: Port [0, 0, 3] <Out_Control>
  4: ZXX [0, 1, 1]
  5: ZXZ [0, 1, 2]
  6: Port [1, 1, 0] <In_Target>
  7: ZXZ [1, 1, 1]
  8: ZXZ [1, 1, 2]
  9: Port [1, 1, 3] <Out_Target>
  0 -> +Z
  1 -> +Z
  1 -> +Y
  2 -> +Z
  4 -> +Z
  5 -> +X
  6 -> +Z
  7 -> +Z
  8 -> +Z

}

module main {
  in child0_control_in: data = 0
  in child0_target_in: data = 1
  out child2_control_out: data = 3
  out child2_target_out: data = 2

  child0: CNOT @ [0, 0, 0]
  child1: CNOT @ [0, 0, 2]
  child2: CNOT @ [0, 0, 4]

  0: Port [0, 0, 0] role=input <child0_control_in>
  1: Port [1, 1, 0] role=input <child0_target_in>
  2: Port [1, 1, 7] role=output <child2_target_out>
  3: Port [0, 0, 7] role=output <child2_control_out>

  0 -> child0.control_in
  1 -> child0.target_in
  child2.control_out -> 3
  child2.target_out -> 2
  child0.control_out -> child1.control_in
  child0.target_out -> child1.target_in
  child1.control_out -> child2.control_in
  child1.target_out -> child2.target_in
}

The internal Ports disappear at the seams. The external Ports remain the parent’s input and output interface. All three CNOT instances share one definition and its module color; the parent owns the four external Ports.

Controlled-adder

A bulk stage of a controlled adder combines the gallery’s CCZ-injected AND, MAJ (majority), and UMA (unmajority and add) components, following the arithmetic layouts in Low et al., A Denser Planar Surface Code. AND forms the temporary bit \(i'_k = q \land i_k\). MAJ combines it with the incoming carry \(c_k\) and target bit \(t_k\) to produce the outgoing carry \(c_{k+1}\). UMA uncomputes the temporary carry information and extracts the sum bit out_k.

main places these definitions as child0, child1, and child2. The AND output feeds MAJ, and four MAJ outputs feed UMA. The two Hadamard seams match the components’ endpoint Pauli frames. AND and MAJ each receive one supplied \(|CCZ\rangle\) state through three resource Ports.

import bloq


def build_graph():
    definitions = [
        bloq.BlockGraph.definition("AND", bloq.GalleryItem.CCZ_INJECTED_AND.load()),
        bloq.BlockGraph.definition("MAJ", bloq.GalleryItem.CCZ_INJECTED_MAJ.load()),
        bloq.BlockGraph.definition("UMA", bloq.GalleryItem.UMA.load()),
    ]
    graph = bloq.BlockGraph.from_definitions([*definitions, bloq.BlockGraph()])
    for index, name in enumerate(["AND", "MAJ", "UMA"]):
        graph.place_module(f"child{index}", name)
    graph.connect_modules("child0.qi_k", "child1.i_prime_k", compact=False)
    graph.connect_modules("child1.c_k_out", "child2.c_k")
    return graph
use bloq::graph::ModuleJoinOptions;
use bloq::prelude::*;

fn build_graph() -> Result<BlockGraph> {
    let mut definitions = Vec::new();
    for (name, item) in [
        ("AND", GalleryItem::CCZInjectedAnd),
        ("MAJ", GalleryItem::CCZInjectedMaj),
        ("UMA", GalleryItem::UMA),
    ] {
        let mut definition = item.build();
        definition.name = name.into();
        definitions.push(definition);
    }
    definitions.push(BlockGraph::new());
    let mut graph = BlockGraph::from_definitions(definitions)?;
    for (index, name) in ["AND", "MAJ", "UMA"].into_iter().enumerate() {
        graph.place_module(&format!("child{index}"), name)?;
    }
    graph.connect_modules_with(
        "child0.qi_k",
        "child1.i_prime_k",
        ModuleJoinOptions {
            compact: false,
            ..Default::default()
        },
    )?;
    graph.connect_modules("child1.c_k_out", "child2.c_k")?;
    Ok(graph)
}
module main {
  in child0_ccz_2: ccz = 0
  in child0_ccz_0: ccz = 1
  in child0_ccz_1: ccz = 2
  in child0_q: data = 3
  in child0_i_k: data = 4
  in child1_ccz_1: ccz = 10
  in child1_ccz_3: ccz = 9
  in child1_ccz_2: ccz = 6
  in child1_c_k: data = 7
  in child1_t_k: data = 11
  out child1_c_k_plus_1: data = 8
  out child2_out_k: data = 12

  child0: AND @ [0, 0, 0]
  child1: MAJ @ [0, 0, 3]
  child2: UMA @ [3, 0, 8]

  0: Port [-1, 0, 0] role=input <child0_ccz_2>
  1: Port [-1, 2, 0] role=input <child0_ccz_0>
  2: Port [-1, 1, 0] role=input <child0_ccz_1>
  3: Port [5, 2, 0] role=multiplex <child0_q>
  4: Port [5, 1, 0] role=multiplex <child0_i_k>
  5: XZZ [0, 1, 2]
  6: Port [-1, 2, 5] role=input <child1_ccz_2>
  7: Port [2, -1, 3] role=input <child1_c_k>
  8: Port [2, 3, 3] role=output <child1_c_k_plus_1>
  9: Port [-1, 1, 5] role=input <child1_ccz_3>
  10: Port [-1, 0, 5] role=input <child1_ccz_1>
  11: Port [5, 1, 6] role=input <child1_t_k>
  12: Port [5, 0, 9] role=output <child2_out_k>

  0 -> child0.ccz_2
  1 -> child0.ccz_0
  2 -> child0.ccz_1
  3 -> child0.q
  4 -> child0.i_k
  10 -> child1.ccz_1
  9 -> child1.ccz_3
  6 -> child1.ccz_2
  7 -> child1.c_k
  11 -> child1.t_k
  child1.c_k_plus_1 -> 8
  child2.out_k -> 12
  child0.qi_k -> 5
  5 -> child1.i_prime_k
  child1.c_k_out -H> child2.c_k
  child1.c_k_plus_1_duplicate -> child2.c_k_plus_1
  child1.c_k_xor_i_prime_k -> child2.c_k_xor_i_prime_k
  child1.c_k_xor_t_k -H> child2.c_k_xor_t_k
}

Each component retains its own measurements, resolves, and feedback. The parent owns the external Ports and the connecting cube. This standalone bulk slice is an analysis example: its named readouts still touch live outputs. The complete controlled adder connects neighboring stages and closes those readouts for execution.

Continue with BLOG Format to write and exchange complete module hierarchies as source files.