Quantum Circuits and Instructions
MimiqCircuitsBase.Instruction — Type
Instruction(op, qtargets, ctargets) <: AbstractInstructionRepresentation 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
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]
MimiqCircuitsBase.allow_bit_aliasing — Method
allow_bit_aliasing(::Type{<:Operation}) -> BoolWhether 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.
MimiqCircuitsBase.allow_zvar_aliasing — Method
allow_zvar_aliasing(::Type{<:Operation}) -> BoolWhether 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.
MimiqCircuitsBase.getbit — Function
getbit(instruction, i)i-th target classical bit of an instruction.
Examples
julia> inst = Instruction(Measure(), 1, 3)
M @ q[1], c[3]
julia> getbit(inst, 1)
3
See also
MimiqCircuitsBase.getbits — Function
getbits(instruction)Tuple of the classical bits which the instruction is applied to.
Examples
julia> inst = Instruction(Measure(), 1, 3)
M @ q[1], c[3]
julia> getbits(inst)
(3,)
See also
MimiqCircuitsBase.getqubit — Function
getqubit(instruction, i)i-th target qubit of an instruction.
Examples
julia> inst = Instruction(GateCX(), 1, 3)
CX @ q[1], q[3]
julia> getqubit(inst, 2)
3
See also
MimiqCircuitsBase.getqubits — Function
getqubits(instruction)Tuple of quantum bits which the instruction is applied to.
julia> inst = Instruction(GateCX(), 1, 3)
CX @ q[1], q[3]
julia> getqubits(inst)
(1, 3)
See also
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
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
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.
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)
endNotice how the range 4:10 is not fully used, since 2:4 is shorter.
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 => 2See also traverse_by_dfs and traverse_by_bfs, which iterate over the instructions in a topological order of this graph.
MimiqCircuitsBase.numbits — Method
numbits(insts::Vector{<:Instruction})
numbits(c::Circuit) -> IntCompute 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
MimiqCircuitsBase.numqubits — Method
numqubits(insts::Vector{<:Instruction})
numqubits(c::Circuit) -> IntCompute 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
MimiqCircuitsBase.numzvars — Method
numzvars(insts::Vector{<:Instruction})
numzvars(c::Circuit) -> IntCompute 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
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).
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_qubitMimiqCircuitsBase.@block — Macro
@block do ... endCreate a new Block and populate it with the instructions in the block.
MimiqCircuitsBase.@circuit — Macro
@circuit begin ... endCreate 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)
endMimiqCircuitsBase.@gatedecl — Macro
@gatedecl Name(args...) begin ... endDefine a new gate type Name with the given arguments and instructions.
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.
MimiqCircuitsBase.BreadthFirstIterator — Type
BreadthFirstIteratorIterator that traverses the AbstractCircuit in a breadth-first topological order (layer by layer). This uses Kahn's algorithm with a FIFO queue.
MimiqCircuitsBase.DepthFirstIterator — Type
DepthFirstIteratorIterator that traverses the AbstractCircuit in a depth-first topological order.
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.
MimiqCircuitsBase.traverse_by_bfs — Method
traverse_by_bfs(c::AbstractCircuit)Return an iterator that traverses the circuit in a breadth-first topological order.
MimiqCircuitsBase.traverse_by_dfs — Method
traverse_by_dfs(graph::AbstractCircuit)Return an iterator that traverses the circuit in a depth-first topological order.
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]
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.