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 |
|
Store a reusable block graph |
Quantum port |
|
Expose a logical boundary at a Port block |
Classical input or output |
|
Receive or export a Boolean value |
Instance |
|
Place a definition in its parent |
Quantum connection |
|
Join two quantum boundaries |
Classical binding |
|
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 |
|---|---|
|
Incoming logical state at Port block 0 |
|
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
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 |
|---|---|
|
Replace the child’s input Port with parent block 0 |
|
Remove the two child Ports and join their neighboring blocks |
|
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.