Quantum Circuits and Instructions

MimiqCircuitsBase.InstructionType
Instruction(op, qtargets, ctargets) <: AbstractInstruction

Representation of an operation applied to specific qubit and bit targets.

Example

julia> Instruction(GateX(), (1,), (), ())
X @ q[1]

julia> Instruction(GateCX(), (1,2), (), ())
CX @ q[1], q[2]

julia> Instruction(Measure(), (3,), (3,), ())
M @ q[3], c[3]

See also

AbstractInstruction, Operation

source
MimiqCircuitsBase.InstructionMethod
Instruction(op, targets...)

Constructors an instruction from an operation and a list of targets.

By convention, if op is an N-qubit and M-bit operations, then the first N targets are used as qubits and the last M as bits.

Examples

julia> Instruction(GateX(), 1)
X @ q[1]

julia> Instruction(GateCX(), 1,2)
CX @ q[1], q[2]

julia> Instruction(Measure(), 3, 3)
M @ q[3], c[3]
source
MimiqCircuitsBase.allow_bit_aliasingMethod
allow_bit_aliasing(::Type{<:Operation}) -> Bool

Whether the operation accepts repeated classical-bit targets in a single instruction. Defaults to false. Operations that read and write the same bit (e.g. c[1] = c[1] & c[2]), or wrappers whose layout exposes condition bits alongside body bits (e.g. IfStatement, WhileStatement), should opt in.

Qubit uniqueness is a physical constraint (no-cloning) and is never relaxed.

source
MimiqCircuitsBase.allow_zvar_aliasingMethod
allow_zvar_aliasing(::Type{<:Operation}) -> Bool

Whether the operation accepts repeated z-variable targets in a single instruction. Defaults to false. Operations like Add, Multiply, Pow whose output may also appear as an input opt in.

source
MimiqCircuitsBase.getztargetFunction
getztarget(instruction)

Tuple of the classical bits which the instruction is applied to.

Examples

julia> inst = Instruction(ExpectationValue(pauli"ZZ"), 1, 2, 1)
⟨ZZ⟩ @ q[1:2], z[1]

julia> getztarget(inst,1)
1

See also

getbit, getqubit, getbit

source
MimiqCircuitsBase.getztargetsFunction
getztargets(instruction)

Tuple of the classical bits which the instruction is applied to.

Examples

julia> inst = Instruction(ExpectationValue(pauli"ZZ"), 1, 2, 1)
⟨ZZ⟩ @ q[1:2], z[1]

julia> getztargets(inst)
(1,)

See also

getbit, getqubits, getbits

source
MimiqCircuitsBase.CircuitType
Circuit([instructions])

Representation of a quantum circuit as a vector of instructions applied to the qubits.

The circuit can be initialized with an optional vector of instructions.

A Circuit can be manipulated as either a list of inctructions or as a direct acyclic graph (DAG) of instructions.

See OPERATIONS, GATES, or GENERALIZED for the list of operations to add to circuits.

Examples

Operation can be added one by one to a circuit with the push!(circuit, operation, targets...) function

julia> c = Circuit()
empty circuit

julia> push!(c, GateH(), 1)
1-qubit circuit with 1 instruction:
└── H @ q[1]

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

julia> push!(c, GateRX(π / 4), 1)
2-qubit circuit with 3 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
└── RX(π/4) @ q[1]

julia> push!(c, Barrier(2), 1, 3)
3-qubit circuit with 4 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── RX(π/4) @ q[1]
└── Barrier @ q[1,3]

julia> push!(c, Measure(), 1, 1)
3-qubit, 1-bit circuit with 5 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── RX(π/4) @ q[1]
├── Barrier @ q[1,3]
└── M @ q[1], c[1]

Targets are not restricted to be single values, but also vectors. In this case a single push! will add multiple operations.

julia> push!(Circuit(), GateCCX(), 1, 2:4, 4:10)
6-qubit circuit with 3 instructions:
├── C₂X @ q[1:2], q[4]
├── C₂X @ q[1,3], q[5]
└── C₂X @ q[1,4], q[6]

is equivalent to

for (i, j) in zip(2:4, 4:10)
    push!(c, GateCX(), 1, i)
end

Notice how the range 4:10 is not fully used, since 2:4 is shorter.

source
MimiqCircuitsBase.numbitsMethod
numbits(insts::Vector{<:Instruction})
numbits(c::Circuit) -> Int

Compute the highest index of c-targets in the given circuit.

Examples

julia> c = Circuit()
empty circuit

julia> push!(c, Measure(), 1:2, 1:2)
2-qubit, 2-bit circuit with 2 instructions:
├── M @ q[1], c[1]
└── M @ q[2], c[2]

julia> numbits(c)
2
source
MimiqCircuitsBase.numqubitsMethod
numqubits(insts::Vector{<:Instruction})
numqubits(c::Circuit) -> Int

Compute the highest index of q-targets in the given vector of instructions or circuit.

Examples

julia> c = Circuit()
empty circuit

julia> push!(c, Measure(), 1:2, 1:2)
2-qubit, 2-bit circuit with 2 instructions:
├── M @ q[1], c[1]
└── M @ q[2], c[2]

julia> numqubits(c)
2
source
MimiqCircuitsBase.numzvarsMethod
numzvars(insts::Vector{<:Instruction})
numzvars(c::Circuit) -> Int

Compute the highest index of z-targets in the given circuit.

Examples

julia> c = Circuit()
empty circuit

julia> push!(c, Amplitude(bs"01"), 1:2)
2-vars circuit with 2 instructions:
├── Amplitude(bs"01") @ z[1]
└── Amplitude(bs"01") @ z[2]

julia> numzvars(c)
2
source
MimiqCircuitsBase.reorder_qubitsMethod
reorder_qubits(c::Circuit, perm)

Return a new Circuit with every qubit index rewritten through perm.

perm[q] == new_position: a 1-qubit gate previously acting on qubit q ends up on qubit perm[q] in the result.

The permutation is also folded into any operation that embeds qubit references outside getqubits(inst). Today the only such payload is Amplitude.bs, a computational-basis state keyed by qubit index; after a permutation the rewritten Amplitude must read the same amplitude of the same quantum-state component. The general- operation wrappers (IfStatement, WhileStatement, Repeat) forward the permutation to their inner op so a wrapped Amplitude is rewritten too. Classical-bit conditions on IfStatement / WhileStatement are qubit-independent and stay unchanged. The gate- only wrappers (Control, Inverse, Power, Parallel) cannot hold an Amplitude at the type level (T<:AbstractGate), so no override is needed.

Classical-bit (getbits) and z-register (getztargets) indices are left alone — they index a side register that the qubit permutation does not touch. The output circuit is therefore equivalent to c from the point of view of the post-execution cstate / zstate; only the internal qubit ordering changes.

Throws ArgumentError when perm is not a permutation of 1:numqubits(c).

source
MimiqCircuitsBase.depthMethod
depth(circuit)

Compute the depth of a quantum circuit.

The depth of a quantum circuit is a metric computing the maximum time (in units of quantum gates application) between the input and output of the circuit.

source
MimiqCircuitsBase.remove_swapsFunction
remove_swaps(circuit; recursive=false)

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

Returns a tuple of:

  • 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

Arguments

  • circuit: Input quantum circuit
  • recursive=false: 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

julia> c = Circuit()
       push!(c, GateH(), 1)
       push!(c, GateSWAP(), 1, 2)
       push!(c, GateCX(), 2, 3)
       new_c, perm = remove_swaps(c)
       new_c
3-qubit circuit with 2 instructions:
├── H @ q[1]
└── CX @ q[1], q[3]

julia> perm  # Logical qubit 1 is at physical position 2, logical 2 at position 1
3-element Vector{Int64}:
 2
 1
 3
julia> c = Circuit()
       push!(c, GateSWAP(), 1, 2)
       push!(c, GateSWAP(), 2, 3)
       push!(c, GateCX(), 1, 3)  # After swaps: logical 1 -> physical 3, logical 3 -> physical 1
       new_c, perm = remove_swaps(c)
       new_c
3-qubit circuit with 2 instructions:
├── CX @ q[2], q[1]
└── ID @ q[3]

julia> perm
3-element Vector{Int64}:
 2
 3
 1
source
MimiqCircuitsBase.remove_unusedMethod
remove_unused(circuit)

Removes unused qubits, bits, and zvars from the given quantum circuit.

Returns the modified circuit and the mappings from old indices to new indices.

Example

begin
    c = Circuit()
    push!(c, GateH(), 1)
    push!(c, GateCX(), 1, 3:2:7)
    push!(c, Measure(), 1:2:7, 1:2:7)
    push!(c, ExpectationValue(GateZ()), 9, 9)
end

cr, qubit_map, bit_map, zvar_map = remove_unused(c)
cr

# output

5-qubit, 4-bit, 1-vars circuit with 9 instructions:
├── H @ q[1]
├── CX @ q[1], q[2]
├── CX @ q[1], q[3]
├── CX @ q[1], q[4]
├── M @ q[1], c[1]
├── M @ q[2], c[2]
├── M @ q[3], c[3]
├── M @ q[4], c[4]
└── ⟨Z⟩ @ q[5], z[1]
source
MimiqCircuitsBase.emplace!Function
emplace!(circuit, operation, registers...)

Emplace an operation at the end of a circuit and applies it to the given registers.

julia> emplace!(Circuit(), control(3, GateSWAP()), [1,2,3], [4,5])
5-qubit circuit with 1 instruction:
└── C₃SWAP @ q[1:3], q[4:5]

julia> QFT()
lazy QFT(?)

julia> emplace!(Circuit(), QFT(), [1,2,3])
3-qubit circuit with 1 instruction:
└── QFT @ q[1:3]
source
MimiqCircuitsBase.drawFunction
draw(circuit)

Draw an ascii representation of a circuit.

NOTE it automatically detects the screen width and will split the circuit if it is too wide.

source