Special Operations#
MIMIQ offers further possibilities to create circuits, such as new gate declarations, or wrappers for common combinations of gates.
Contents#
Gate Declaration & Gate Calls#
Using MIMIQ you can define your own gates with a given name, arguments and instructions.
For examples if you wish to apply an H gate followed by an RX gate with a specific argument for
the rotation you can use GateDecl
as follows:
>>> from symengine import *
>>> rot = symbols('x')
>>> @gatedecl("ansatz")
... def ansatz(rot):
... insts = [Instruction(GateX(), (0,)), Instruction(GateRX(rot), (1,))]
... return insts
>>> ansatz(rot)
ansatz(x)
Here, ansatz is simply the name that will be shown when printing or drawing the circuit, (rot) defines the gate parameters.
As you can see in the code above, to generate your own gate declaration you will need to
instantiate push
except that no circuit is
passed as an argument.
>>> ansatz(rot)
ansatz(x)
You can check the instructions inside ansatz by using decompose method:
>>> ansatz(pi/2).decompose()
2-qubit circuit with 2 instructions:
├── X @ q[0]
└── RX((1/2)*pi) @ q[1]
After declaration you can add it to your circuit using push()
.
>>> c = Circuit()
>>> c.push(ansatz(pi/2), 0 , 1)
2-qubit circuit with 1 instructions:
└── ansatz((1/2)*pi) @ q[0,1]
Note
A gate declared with GateDecl
must be unitary.
Note
The gatedecl()
decorator transforms a function into one that produces
GateCall
objects based on the logic defined in a
GateDecl
. When you call ansatz(pi), it creates an instance of
GateCall
, representing a specific instantiation of the unitary gates
with the provided parameters.
Creating a gate declaration allows you to add easily the same sequence of gates in a very versatile way and manipulate your new gate
like you would with any other gate. This means that you can combine it with other gates via Control
, add noise to
the whole block in one call, use it as an operator for ExpectationValue
, use it within an IfStatement
etc.
See non-unitary operations, and noise pages.
For example, here is how to add noise to the previous gate declaration:
>>> c = Circuit()
>>> my_gate = ansatz(pi)
>>> c.push(my_gate, 0, 1)
2-qubit circuit with 1 instructions:
└── ansatz(pi) @ q[0,1]
>>> c.add_noise(my_gate, Depolarizing2(0.1))
2-qubit circuit with 2 instructions:
├── ansatz(pi) @ q[0,1]
└── Depolarizing(0.1) @ q[0,1]
>>> c.draw()
┌────────────┐ ┌───────────────────┐
q[0]: ╶┤0 ├──┤0 ├──────────────────────────────────╴
│ ansatz(pi)│ │ Depolarizing(0.1)│
q[1]: ╶┤1 ├──┤1 ├──────────────────────────────────╴
└────────────┘ └───────────────────┘
Gate declarations can be combined with other quantum operations like Control
, noise, or even conditional logic.
Use it within an IfStatement
:
>>> IfStatement(my_gate, BitString("111"))
IF (c==111) ansatz(pi)
Note that this type of combined operation does not work if we pass a circuit as an argument, instead of a declared gate
(more precisely, a GateCall
, see note above).
Composite Gates#
MIMIQ provides several composite gates to facilitate circuit building. These gates simplify constructing complex operations.
Pauli String#
A PauliString
is an N-qubit tensor product of Pauli operators of the form:
where each \(P_i \in \{ I, X, Y, Z \}\) is a single-qubit Pauli operator, including the identity.
To create an operator using PauliString
we simply pass as argument the Pauli string written as a String:
>>> c = Circuit()
>>> c.push(PauliString("IXYZ"), 1, 2, 3, 4)
5-qubit circuit with 1 instructions:
└── IXYZ @ q[1,2,3,4]
You can specify any number of Pauli operators.
Quantum Fourier Transform#
The QFT
gate implements the The Quantum Fourier Transform which is a
circuit used to realize a linear transformation on qubits and is a building block of many larger circuits such as Shor’s Algorithm or
the Quantum Phase Estimation.
The QFT maps an arbitrary quantum state \(\ket{x} = \sum_{j=0}^{N-1} x_{j} \ket{j}\) to a quantum state \(\sum_{k=0}^{N-1} y_{k} \ket{k}\) according to the formula:
where \(w_N = e^{2\pi i / N}\).
In MIMIQ, the QFT
gate allows you to quickly implement a QFT in your circuit on an arbitrary N
number of qubits.
You can instantiate the QFT gate by providing the number of qubits you want to use, QFT(N), and add it like any other gate in the circuit.
>>> c = Circuit()
>>> c.push(QFT(5), 1, 2, 3, 4, 5)
6-qubit circuit with 1 instructions:
└── QFT @ q[1,2,3,4,5]
This adds a 5-qubit QFT to the circuit.
Phase Gradient#
The PhaseGradient
applies a phase shift to a quantum register of N
qubits, where each computational basis state \(\ket{k}\)
experiences a phase proportional to its integer value k
:
To use it, you can simply provide the number of qubit targets and add it to the circuit as shown in the following examples:
>>> c = Circuit()
>>> c.push(PhaseGradient(5), 1, 2, 3, 4, 5)
6-qubit circuit with 1 instructions:
└── PhaseGradient @ q[1,2,3,4,5]
This will add a 5 qubits PhaseGradient
to the first 5 qubits of the quantum register.
Polynomial Oracle#
Warning
The PolynomialOracle
works only with the state vector simulator and not with MPS, because of
ancillas qubit use.
The PolynomialOracle
is a quantum oracle for a polynomial function of two
registers. It applies a \(\pi\) phase shift to any basis state that satisfies
\(a \cdot xy + b \cdot x + c \cdot y + d = 0\),
where \(\ket{x}\) and \(\ket{y}\) are the states of the two registers.
Here is how to use the PolynomialOracle
:
>>> c = Circuit()
>>> c.push(PolynomialOracle(5, 5, 1, 2, 3, 4), *range(10))
10-qubit circuit with 1 instructions:
└── PolynomialOracle(1, 2, 3, 4) @ q[0,1,2,3,4], q[5,6,7,8,9]
Diffusion#
The Diffusion
operator corresponds to Grover’s diffusion operator.
It implements the unitary transformation:
Here is how to use Diffusion
:
>>> c = Circuit()
>>> c.push(Diffusion(10), *range(10))
10-qubit circuit with 1 instructions:
└── Diffusion @ q[0,1,2,3,4,5,6,7,8,9]
You need to specify both the number of targets and their corresponding indices.
More about composite gates#
All composite gates can be decomposed using decompose()
to
extract their implementation, except for PolynomialOracle
.
>>> QFT(5).decompose()
5-qubit circuit with 15 instructions:
├── H @ q[4]
├── CP(0.5*pi) @ q[3], q[4]
├── H @ q[3]
├── CP(0.25*pi) @ q[2], q[4]
├── CP(0.5*pi) @ q[2], q[3]
├── H @ q[2]
├── CP(0.125*pi) @ q[1], q[4]
├── CP(0.25*pi) @ q[1], q[3]
├── CP(0.5*pi) @ q[1], q[2]
├── H @ q[1]
├── CP(0.0625*pi) @ q[0], q[4]
├── CP(0.125*pi) @ q[0], q[3]
├── CP(0.25*pi) @ q[0], q[2]
├── CP(0.5*pi) @ q[0], q[1]
└── H @ q[0]
Barrier#
The Barrier
is a non-op operation that does not affect the quantum state but
prevents compression or optimization across execution.
As of now, Barrier
is only useful when combined with the MPS backend.
To add barriers to the circuit, you can use the Barrier
operation:
Example usage:
# Add a Gate
>>> c.push(GateX(), 0)
1-qubit circuit with 1 instructions:
└── X @ q[0]
# Apply the Barrier on qubit 0.
>>> c.push(Barrier(1), 0)
1-qubit circuit with 2 instructions:
├── X @ q[0]
└── Barrier @ q[0]
# Add a Gate between barriers
>>> c.push(GateX(), 0)
1-qubit circuit with 3 instructions:
├── X @ q[0]
├── Barrier @ q[0]
└── X @ q[0]
# Apply individual barriers on multiple qubits
>>> c.push(Barrier(1), range(3))
3-qubit circuit with 6 instructions:
├── X @ q[0]
├── Barrier @ q[0]
├── X @ q[0]
├── Barrier @ q[0]
├── Barrier @ q[1]
└── Barrier @ q[2]
# Add gates on multiple qubits
>>> c.push(GateX(), range(3))
3-qubit circuit with 9 instructions:
├── X @ q[0]
├── Barrier @ q[0]
├── X @ q[0]
├── Barrier @ q[0]
├── Barrier @ q[1]
├── Barrier @ q[2]
├── X @ q[0]
├── X @ q[1]
└── X @ q[2]
# Apply one general Barrier on multiple qubits (effectively the same as above)
>>> c.push(Barrier(3), *range(3))
3-qubit circuit with 10 instructions:
├── X @ q[0]
├── Barrier @ q[0]
├── X @ q[0]
├── Barrier @ q[0]
├── Barrier @ q[1]
├── Barrier @ q[2]
├── X @ q[0]
├── X @ q[1]
├── X @ q[2]
└── Barrier @ q[0,1,2]
>>> c.draw()
┌─┐ ┌─┐ ┌─┐
q[0]: ╶┤X├░┤X├░──┤X├──────░───────────────────────────────────────────────────╴
└─┘░└─┘░ └─┘┌─┐ ░
q[1]: ╶────────░────┤X├───░───────────────────────────────────────────────────╴
░ └─┘┌─┐░
q[2]: ╶─────────░──────┤X├░───────────────────────────────────────────────────╴
░ └─┘░