> ## Documentation Index
> Fetch the complete documentation index at: https://b01t.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Ancilla Kit: Routing and Composition

> Reference for b01t's context-aware routing combinators: ancilla, compute, phase, apply, uncompute, par, and adjoint work in both exact and broad programs.

The routing combinators in `b01t.kit.routing` provide a single set of names that work correctly inside both exact (`@coherent`, `@primitive`) and broad (`@parametric`, `@adaptive`) build contexts. You write the same `ancilla`, `compute`, `phase`, `uncompute`, `par`, and `adjoint` calls regardless of decorator, and b01t routes to the appropriate implementation automatically. Import them from `b01t` directly.

```python theme={null}
from b01t import ancilla, compute, phase, apply, uncompute, par, adjoint
```

***

## ancilla

```python theme={null}
ancilla(size: int)
```

Creates an ancilla block context manager that allocates `size` fresh qubits. Use with a `with` statement; the returned register is only valid inside the block.

<ParamField path="size" type="int" required>
  Number of ancilla qubits to allocate.
</ParamField>

In an exact context (`@coherent`), the block enforces the full compute / phase (or apply) / uncompute discipline and certifies ancilla cleanliness. In a broad context (`@parametric`), it records an `AncillaBlockOp` in the IR.

Nested `ancilla` blocks are not supported and raise `DSLValidationError`.

```python theme={null}
from b01t import coherent, QReg, cx, z
from b01t import ancilla, compute, phase, uncompute

@coherent
def phase_kick(sys: QReg) -> None:
    with ancilla(1) as anc:
        compute(lambda: cx(sys[0], anc[0]))
        phase(lambda: z(anc[0]))
        uncompute()

prog = phase_kick.build_exact(("sys", 1))
```

***

## compute

```python theme={null}
compute(fn: Callable[[], None]) -> None
```

Marks the compute section of an ancilla block. `fn` is called immediately; any gates emitted inside it are captured as compute ops.

<ParamField path="fn" type="Callable[[], None]" required>
  A zero-argument callable that emits gates. In exact context, only permutation gates are allowed (`x`, `cx`, `ccx`, `swap`, `mcx`). In broad context, only gates from `_ALLOWED_COMPUTE` are accepted.
</ParamField>

`compute` must be the first section inside an ancilla block. Calling it outside a block raises `DSLValidationError`.

```python theme={null}
from b01t import coherent, QReg, cx, ccx, z
from b01t import ancilla, compute, phase, uncompute

@coherent
def carry(a: QReg, b: QReg) -> None:
    with ancilla(1) as anc:
        compute(lambda: (cx(a[0], anc[0]), cx(b[0], anc[0])))
        phase(lambda: z(anc[0]))
        uncompute()

prog = carry.build_exact(("a", 1), ("b", 1))
```

***

## phase

```python theme={null}
phase(fn: Callable[[], None]) -> None
```

Marks the phase (diagonal) section of an ancilla block. `fn` is called immediately; gates emitted inside it are captured as phase ops and must all be diagonal.

<ParamField path="fn" type="Callable[[], None]" required>
  A zero-argument callable that emits diagonal gates. In exact context, only `z`, `cz`, `ccz`, `s`, `sdg`, `t`, `tdg`, `mcz` are allowed. In broad context, only gates from `_ALLOWED_PHASE` (which also includes `rz`) are accepted.
</ParamField>

`phase` must follow `compute`. Calling it before `compute` or outside a block raises `DSLValidationError`.

```python theme={null}
from b01t import coherent, QReg, cx, t
from b01t import ancilla, compute, phase, uncompute

@coherent
def t_phase(sys: QReg) -> None:
    with ancilla(1) as anc:
        compute(lambda: cx(sys[0], anc[0]))
        phase(lambda: t(anc[0]))
        uncompute()

prog = t_phase.build_exact(("sys", 1))
```

***

## apply

```python theme={null}
apply(fn: Callable[[], None]) -> None
```

Marks the apply (CMA pattern) section of an ancilla block. In an exact context, `apply` allows any exact gate, but all wires used must be disjoint from the wires used in the compute block. In a broad context, `apply` falls through to `phase` semantics.

<ParamField path="fn" type="Callable[[], None]" required>
  A zero-argument callable that emits gates on wires disjoint from the compute block's wires.
</ParamField>

`apply` is an alternative to `phase` when you want to run gates that are not diagonal — for example, applying a gate to system qubits while ancilla holds a value. The wire-disjointness constraint (the PreservesFirst condition) is checked at block finalization.

```python theme={null}
from b01t import coherent, QReg, cx, x
from b01t import ancilla, compute, apply, uncompute

@coherent
def controlled_x(ctrl: QReg, tgt: QReg) -> None:
    with ancilla(1) as anc:
        compute(lambda: cx(ctrl[0], anc[0]))
        apply(lambda: x(tgt[0]))   # tgt is disjoint from ctrl/anc
        uncompute()

prog = controlled_x.build_exact(("ctrl", 1), ("tgt", 1))
```

***

## uncompute

```python theme={null}
uncompute() -> None
```

Auto-generates the uncompute section by reversing the compute ops and applying their inverses. You do not write the inverse gates by hand — b01t derives them for you.

`uncompute` must follow a `phase` or `apply` section. Calling it before either raises `DSLValidationError`. After `uncompute`, you can start a new compute / phase / uncompute cycle within the same `ancilla` block.

```python theme={null}
from b01t import coherent, QReg, cx, z
from b01t import ancilla, compute, phase, uncompute

@coherent
def double_cycle(sys: QReg) -> None:
    with ancilla(1) as anc:
        compute(lambda: cx(sys[0], anc[0]))
        phase(lambda: z(anc[0]))
        uncompute()
        # start a new cycle in the same block
        compute(lambda: cx(sys[1], anc[0]))
        phase(lambda: z(anc[0]))
        uncompute()

prog = double_cycle.build_exact(("sys", 2))
```

***

## par

```python theme={null}
par(fn1: Callable[[], None], fn2: Callable[[], None]) -> None
```

Parallel (tensor) composition. `fn1` and `fn2` are executed in separate capture contexts, and b01t verifies that they operate on disjoint wire sets. If any wire is shared, `DSLValidationError` is raised.

<ParamField path="fn1" type="Callable[[], None]" required>
  First sub-program. Emits gates on one subset of wires.
</ParamField>

<ParamField path="fn2" type="Callable[[], None]" required>
  Second sub-program. Emits gates on a disjoint subset of wires.
</ParamField>

```python theme={null}
from b01t import coherent, QReg, h, x
from b01t import par

@coherent
def parallel_ops(a: QReg, b: QReg) -> None:
    par(
        lambda: h(a[0]),
        lambda: x(b[0]),
    )

prog = parallel_ops.build_exact(("a", 1), ("b", 1))
```

***

## adjoint

```python theme={null}
adjoint(fn, *args) -> None
```

Emits the inverse (adjoint) of a coherent subroutine into the current program. `fn` is re-executed in a capture context, and all its ops are reversed and inverted. The result is appended to the current program's op sequence.

<ParamField path="fn" type="ExactDSLFunction | DSLFunction" required>
  The coherent function whose inverse to emit. Must be decorated with `@coherent` (exact context) or `@parametric` with `Effect.COHERENT` (broad context). Passing an `@adaptive` function raises `DSLValidationError`.
</ParamField>

<ParamField path="args" type="QReg" required>
  The register arguments to pass to `fn` when capturing its ops. These must match `fn`'s parameter signature.
</ParamField>

`adjoint` cannot be called inside an ancilla `compute` / `phase` block.

```python theme={null}
from b01t import coherent, QReg, h, cx
from b01t import adjoint

@coherent
def entangle(a: QReg, b: QReg) -> None:
    h(a[0])
    cx(a[0], b[0])

@coherent
def entangle_then_undo(a: QReg, b: QReg) -> None:
    entangle(a, b)
    adjoint(entangle, a, b)  # emits cx(a[0], b[0]), h(a[0]) in reverse

prog = entangle_then_undo.build_exact(("a", 1), ("b", 1))
```
