Circuit Compilation & Optimization

MIMIQ provides tools to optimize and transform quantum circuits.

Cleaning Circuits

Remove Unused

The remove_unused() function cleans up a circuit by removing unused qubits, classical bits, and Z-variables.

new_circuit, qubit_map, bit_map, zvar_map = remove_unused(circuit)

It returns:

  1. The new optimized Circuit.

  2. A mapping (dict) of old qubit indices to new ones.

  3. A mapping (dict) of old bit indices to new ones.

  4. A mapping (dict) of old Z-variable indices to new ones.

Example:

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(GateH(), 0)
>>> c.push(GateCX(), 0, 2)  # Qubit 1 is unused
>>> new_c, qmap, bmap, zmap = remove_unused(c)
>>> new_c
2-qubit circuit with 2 instructions:
├── H @ q[0]
└── CX @ q[0], q[1]

Optimization Passes

Remove SWAPs

The remove_swaps() function eliminates SWAP gates by tracking qubit permutations and remapping subsequent operations.

new_circuit, final_permutation = remove_swaps(circuit, recursive=False)

Arguments:

  • circuit: The input circuit.

  • recursive (default False): If True, recursively removes swaps from nested blocks.

It returns:

  1. A new Circuit without SWAP gates.

  2. A list representing the final physical permutation of qubits.

Example:

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(GateH(), 1)
2-qubit circuit with 1 instruction:
└── H @ q[1]

>>> c.push(GateSWAP(), 1, 2)
3-qubit circuit with 2 instructions:
├── H @ q[1]
└── SWAP @ q[1:2]

>>> c.push(GateCX(), 2, 3)
4-qubit circuit with 3 instructions:
├── H @ q[1]
├── SWAP @ q[1:2]
└── CX @ q[2], q[3]

>>> new_c, perm = remove_swaps(c)
>>> new_c
4-qubit circuit with 2 instructions:
├── H @ q[1]
└── CX @ q[1], q[3]

>>> perm
[0, 2, 1, 3]

Reference

mimiqcircuits.remove_unused(self)[source]

Removes unused qubits, bits, and zvars from the given circuit. Returns (new_circuit, qubit_map, bit_map, zvar_map).

Examples

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(GateH(), 0)
1-qubit circuit with 1 instruction:
└── H @ q[0]

>>> c.push(GateCX(), 0, 2)  # qubit 1 is unused
3-qubit circuit with 2 instructions:
├── H @ q[0]
└── CX @ q[0], q[2]

>>> c.push(Measure(), 2, 2)  # bits 0 & 1 are unused
3-qubit, 3-bit circuit with 3 instructions:
├── H @ q[0]
├── CX @ q[0], q[2]
└── M @ q[2], c[2]

>>> c.push(ExpectationValue(GateX()), 0, 1)  # zvar 0 is unused
3-qubit, 3-bit, 2-zvar circuit with 4 instructions:
├── H @ q[0]
├── CX @ q[0], q[2]
├── M @ q[2], c[2]
└── ⟨X⟩ @ q[0], z[1]

>>> new_c, qmap, bmap, zmap = remove_unused(c)
>>> new_c
2-qubit, 1-bit, 1-zvar circuit with 4 instructions:
├── H @ q[0]
├── CX @ q[0], q[1]
├── M @ q[1], c[0]
└── ⟨X⟩ @ q[0], z[0]
>>> qmap
{0: 0, 2: 1}
>>> bmap
{2: 0}
>>> zmap
{1: 0}
mimiqcircuits.remove_swaps(circuit, recursive=False, cache=None)[source]

Remove all SWAP gates from a quantum circuit by tracking qubit permutations and remapping subsequent operations to their correct physical qubits.

Returns:

A tuple containing:
  • new_circuit: Circuit with SWAP gates removed and operations remapped

  • qubit_permutation: Final permutation where qubit_permutation[i] gives the physical qubit location of logical qubit i

Return type:

tuple

Parameters:
  • circuit – Input quantum circuit

  • recursive – If True, recursively remove swaps from nested blocks/subcircuits

Details:

When a SWAP gate is encountered on qubits (i, j), instead of keeping the gate:

  1. The qubit mapping is updated to track that logical qubits i and j have exchanged physical positions

  2. All subsequent gates are automatically remapped to operate on the correct physical qubits

This transformation preserves circuit semantics while eliminating SWAP overhead.

Examples

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(GateH(), 1)
2-qubit circuit with 1 instruction:
└── H @ q[1]

>>> c.push(GateSWAP(), 1, 2)
3-qubit circuit with 2 instructions:
├── H @ q[1]
└── SWAP @ q[1:2]

>>> c.push(GateCX(), 2, 3)
4-qubit circuit with 3 instructions:
├── H @ q[1]
├── SWAP @ q[1:2]
└── CX @ q[2], q[3]

>>> new_c, perm = remove_swaps(c)
>>> new_c
4-qubit circuit with 2 instructions:
├── H @ q[1]
└── CX @ q[1], q[3]

>>> perm
[0, 2, 1, 3]
>>> c = Circuit()
>>> c.push(GateSWAP(), 1, 2)
3-qubit circuit with 1 instruction:
└── SWAP @ q[1:2]

>>> c.push(GateSWAP(), 2, 3)
4-qubit circuit with 2 instructions:
├── SWAP @ q[1:2]
└── SWAP @ q[2:3]

>>> c.push(GateCX(), 1, 3)
4-qubit circuit with 3 instructions:
├── SWAP @ q[1:2]
├── SWAP @ q[2:3]
└── CX @ q[1], q[3]

>>> new_c, perm = remove_swaps(c)
>>> new_c
3-qubit circuit with 1 instruction:
└── CX @ q[2], q[1]

>>> perm
[0, 2, 3, 1]

# Example of checking recursive removel of swaps

>>> inner = Circuit()
>>> _ = inner.push(GateSWAP(), 0, 1)
>>> _ = inner.push(GateCX(), 1, 0)
>>> Inner = GateDecl("Inner", [], inner)
>>> mid = Circuit()
>>> _ = mid.push(GateSWAP(), 0, 1)
>>> _ = mid.push(GateCall(Inner, ()), 0, 1)
>>> Mid = GateDecl("Mid", [], mid)
>>> outer = Circuit()
>>> _ = outer.push(GateSWAP(), 1, 2)
>>> _ = outer.push(GateCall(Mid, ()), 1, 2)
>>> Outer = GateDecl("Outer", [], outer)
>>> c = Circuit()
>>> _ = c.push(GateSWAP(), 1, 2)
>>> _ = c.push(GateCall(Outer, ()), 0, 1, 2)
>>> c
3-qubit circuit with 2 instructions:
├── SWAP @ q[1:2]
└── Outer() @ q[0:2]
>>> c.decompose()
3-qubit circuit with 13 instructions:
├── CX @ q[1], q[2]
├── CX @ q[2], q[1]
├── CX @ q[1], q[2]
├── CX @ q[1], q[2]
├── CX @ q[2], q[1]
├── CX @ q[1], q[2]
├── CX @ q[1], q[2]
├── CX @ q[2], q[1]
├── CX @ q[1], q[2]
├── CX @ q[1], q[2]
├── CX @ q[2], q[1]
├── CX @ q[1], q[2]
└── CX @ q[2], q[1]
>>> c.decompose().decompose()
3-qubit circuit with 13 instructions:
├── CX @ q[1], q[2]
├── CX @ q[2], q[1]
├── CX @ q[1], q[2]
├── CX @ q[1], q[2]
├── CX @ q[2], q[1]
├── CX @ q[1], q[2]
├── CX @ q[1], q[2]
├── CX @ q[2], q[1]
├── CX @ q[1], q[2]
├── CX @ q[1], q[2]
├── CX @ q[2], q[1]
├── CX @ q[1], q[2]
└── CX @ q[2], q[1]

# Test to see that all swaps are removed recursively

>>> new_c, _ = remove_swaps(c, recursive=True)
>>> any(isinstance(i.get_operation(), GateSWAP) for i in new_c)
False
>>> Outercheck = new_c.instructions[0].get_operation()._decl
>>> any(isinstance(i.get_operation(), GateSWAP) for i in Outercheck.circuit)
False
>>> Midcheck = Outercheck.circuit.instructions[0].get_operation()._decl
>>> any(isinstance(i.get_operation(), GateSWAP) for i in Midcheck.circuit)
False
>>> Innercheck = Midcheck.circuit.instructions[0].get_operation()._decl
>>> any(isinstance(i.get_operation(), GateSWAP) for i in Innercheck.circuit)
False