Quantum Circuits and Instructions

MimiqCircuitsBase.Instruction — Type
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.Instruction — Method
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_aliasing — Method
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_aliasing — Method
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.getztarget — Function
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.getztargets — Function
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.AbstractCircuit — Type
AbstractCircuit{T}

This abstract type is the base of Any Circuit representation in MIMIQ. It can be seen as a wrapper for list of Instructions, but it also includes a graph representation of this list. The structure of the graph is up to the inheriting struct to decide & implement.

This class also directly implements the functions needed to add Instructions to the list, the different getter and setter for the children struct properties as well as the caching system for the properties of the circuit.

source
MimiqCircuitsBase.Circuit — Type
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.graph — Function
graph(circuit)

Return the dependency graph of the circuit, as a Graphs.SimpleDiGraph.

Vertex i is the i-th instruction. There is an edge from i to j when j is the next instruction after i acting on one of the same qubits, bits or z-variables. Observables that read the whole state, such as Amplitude or BondDim, depend on every qubit.

The graph is cached and rebuilt only after the circuit changes. Do not modify it.

Examples

julia> c = push!(Circuit(), GateH(), 1);

julia> push!(c, GateCX(), 1, 2);

julia> push!(c, GateX(), 3);

julia> g = graph(c);

julia> collect(MimiqCircuitsBase.Graphs.edges(g))
1-element Vector{Graphs.SimpleGraphs.SimpleEdge{Int64}}:
 Edge 1 => 2

See also traverse_by_dfs and traverse_by_bfs, which iterate over the instructions in a topological order of this graph.

source
MimiqCircuitsBase.numbits — Method
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.numqubits — Method
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.numzvars — Method
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_qubits — Method
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.specify_operations — Method
specify_operations(circuit)

Print how many instructions of the circuit act on each number of qubits and bits. Returns nothing.

Examples

julia> c = push!(Circuit(), GateH(), 1);

julia> push!(c, GateH(), 2);

julia> specify_operations(c)
Total number of operations: 2
└── 2 x 1_qubit
source
MimiqCircuitsBase.@circuit — Macro
@circuit begin ... end

Create a new Circuit and populate it with the instructions in the block. The block supports the @on syntax for defining instructions.

Examples

c = @circuit begin
    @on GateH() q=1
    @on GateCX() q=(1, 2)
end
source
MimiqCircuitsBase.@on — Macro
@on operation q=... c=... z=...

Add operation on the given qubits (q), classical bits (c) and z-variables (z) to the circuit being built. @on is syntax read by @circuit, @block and @gatedecl, and only as a top-level statement of their block. Anywhere else, including inside a loop or if in that block, it throws an error.

source
MimiqCircuitsBase.topological_sort_by_bfs — Method
topological_sort_by_bfs(g::AbstractGraph)

Perform a topological sort of the graph using a breadth-first search (Kahn's algorithm). Returns a vector of vertices in topological order.

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.draw — Function
draw(circuit; compact=true)

Draw an ascii representation of a circuit.

With compact=true, instructions on disjoint wires share a column, and consecutive instructions of the same operation type on disjoint wires, such as those from one broadcast push!, start in the same column. Instructions that touch the classical or Z register are always drawn one after the other. With compact=false, every instruction gets its own column, in circuit order.

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

source