Unitary Gates

Unitary gates are fundamental components of quantum circuits. Here we explain how to work with unitary gates in MIMIQ.

Mathematical background

State vector and probability

In quantum mechanics, every transformation applied to a quantum state must be unitary (in a closed system). To understand why, we can expand the quantum state as

\[\begin{aligned} \ket{\psi} = \sum_{i=1}^{k} c_{i} \ket{\psi_{i}} \end{aligned}\]

where \(\ket{\psi_i}\) are orthonormal basis states. For this state, the following condition must hold true:

\[\begin{aligned} \sum_{i=1}^{k} |c_i|² = 1 \end{aligned}\]

Since \(|c_i|^2\) corresponds to the probability of measuring state \(\ket{\psi_i}\), this condition simply says that the probabilities must add up to one. Unitary gates preserve this normalization condition, see below.

Unitary transformation

An alternative way to compute the probability is through the inner product. Given two states in Hilbert space, \(\ket{\alpha}\) and \(\ket{\psi}\), the squared inner product \(|\braket{\alpha|\psi}|^2\) reflects the probability of measuring the system in state \(\ket{\alpha}\). Thus, the normalization condition can be written as \(|\braket{\psi|\psi}|^2 = 1\). In other words, the length of the state vector in complex space must be one.

When evolving the state \(\ket{\psi}\) using an operator U, the normalization condition becomes (omitting the square):

\[\begin{aligned} \bra{\psi} U^\dagger U \ket{\psi} = 1 \end{aligned}\]

To fulfill this, the operator U must satisfy the condition:

\[\begin{aligned} U^\dagger U = I \end{aligned}\]

An operator that fulfills this requirement is called a unitary operator and its matrix representation is unitary too.

Unitary gates in MIMIQ.

MIMIQ offers a large number of gates to build quantum circuits. For an overview, type the following line in your Python session:

help(GATES)

Similarly, to get more information about a specific gate, you can type the following command in your Python session using the gate of your choice:

help(GateID)

There are different categories of gates depending on the number of targets, parameters etc. We discuss how to implement them in the following.

Single-qubit gates

List of single-qubit gates: GateID, GateX, GateY, GateZ, GateH, GateS, GateSDG, GateT, GateTDG, GateSX, GateSXDG. GateSY, GateSYDG.

For single-qubit gates you don’t need to give any argument to the gate constructor (ex: GateX()).
You only need to give the index of the target qubit when adding it to your circuit with the push() function.
>>> circuit = Circuit()
>>> circuit.push(GateX(), 0)
1-qubit circuit with 1 instructions:
└── X @ q[0]

Single-qubit parametric gates

List of single-qubit parametric gates: GateU, GateP, GateRX, GateRY, GateRZ, GateR, GateU1, GateU2, GateU3, Delay().

For single-qubit parametric gates you need to give the expected number of parameters to the gate constructor (ex: `GateU(0.5, 0.5, 0.5)` or `GateU1(0.5)`), if you are unsure of the expected number of parameters use the help() function in your Python interactive session and give it the oject you are interested in (ex: help(GateU)).
As for any single qubit gates you can add it to your circuit by using the push() function and give the index of the target qubit.
>>> circuit = Circuit()
>>> circuit.push(GateRX(math.pi/2), 0)
1-qubit circuit with 1 instructions:
└── RX(1.5707963267948966) @ q[0]

Two qubit gates

List of two qubits gates: GateCX, GateCY, GateCZ, GateCH, GateSWAP, GateISWAP, GateCS, GateCSDG, GateCSX, GateCSXDG, GateECR, GateDCX.

Two-qubit gates can be instantiated without any arguments just like single-qubit gates (ex: GateCX()).
You will need to give the index of both qubits to the push() function to add it to the circuit.
To understand the ordering of the targets check the documentation of each particular gate. For controlled gates we use the convention that the first register corresponds to the control and the second to the target.
>>> circuit = Circuit()
>>> circuit.push(GateCH(), 0, 1)
2-qubit circuit with 1 instructions:
└── CH @ q[0], q[1]

Two-qubit parametric gates

List of two qubits parametric gates : GateCP, GateCU, GateCRX, GateCRY, GateCRZ, GateRXX, GateRYY, GateRZZ, GateRZX, GateXXplusYY, GateXXminusYY.

Two-qubit parametric gates are instantiated exactly like single-qubit parametric gates. You will need to give the expected number of parameters of the gate to its constructor (ex: GateCU(math.pi, math.pi, math.pi)).
You can then add it to the circuit just like a two-qubit gate by giving the index of the target qubits to the push() function. Again, check each gate’s documentation to understand the qubit ordering; for controlled gates the first qubit corresponds to the control qubit, the second to the target.
>>> circuit = Circuit()
>>> circuit.push(GateRXX(math.pi/2), 0, 1)
2-qubit circuit with 1 instructions:
└── RXX(1.5707963267948966) @ q[0,1]

Multi-qubit gates

List of multi-qubit gates: GateCCX, GateC3X, GateCCP, GateCSWAP.

For the multi-qubit controlled gates you will need to give the index of each qubit to the push() function. As usual, first the control qubits, then the targets; check the specific documentation of each gate.

>>> circuit = Circuit()
>>> circuit.push(GateC3X(), 0, 1, 2, 3)
4-qubit circuit with 1 instructions:
└── C₃X @ q[0,1,2], q[3]

Generalized gates

Some common gate combinations are available as generalized gates: PauliString, QFT, PhaseGradient, Diffusion, PolynomialOracle.

Generalized gates can be applied to a variable number of qubits. It is highly recommended to check their docstrings to understand their usage help(QFT).

Here is an example of use:

>>> circuit = Circuit()
>>> circuit.push(PhaseGradient(10), *range(0, 10))
10-qubit circuit with 1 instructions:
└── PhaseGradient @ q[0,1,2,3,4,5,6,7,8,9]

These gates target a variable number of gates, so you have to specify in the constructor how many target qubits will be used, and give to the push() function one index per target qubit.

More about generalized gates on special operations.

Custom Gates

If you need to use a specific unitary gate that is not provided by MIMIQ, you can use GateCustom` to create your own unitary gate.

Note

Only one qubit or two qubits gates can be created using MIMIQ’s GateCustom.

Note

Avoid using GateCustom if you can define the same gate using a pre-defined gate from MIMIQ’s library, as it could impact negatively peformance.

To create a custom unitary gate you first have to define the matrix of your gate in Python:

# define the matrix for a 2 qubits gate
>>> custom_matrix = np.array([[np.exp(1j*math.pi/3), 0, 0, 0], [0, np.exp(1j*math.pi/5), 0, 0 ], [0, 0, np.exp(1j*math.pi/7), 0], [0, 0, 0, np.exp(1j*math.pi/11)]])

Then you can create your unitary gate and use it like any other gate using push()

>>> circuit = Circuit()
>>> custom_gate = GateCustom(custom_matrix)
>>> circuit.push(custom_gate, 0, 1)
2-qubit circuit with 1 instructions:
└── Custom(...) @ q[0,1]

Composition: Control, Power, Inverse, Parallel

Gates in MIMIQ can be combined to create more complex gates using Control, Power, Inverse, Parallel.

Control

A controlled version of every gate can be built using the control() function. For example, CX can be built with the following instruction:

>>> CX = control(1, GateX())

The first argument indicates the number of control qubits and is completely up to the user. For example a CCCCCX can be built with the following instruction:

>>> CCCCCX = control(5, GateX())

Details

A wrapper for GateCX is already provided by MIMIQ. Whenever possible, it is recommended to use the gates already provided by the framework instead of creating your own composite gate to prevent performances loss.

Be careful when adding the new control gate to your circuit. When using the push() function, the first expected indices should be the control qubits specified in Control and the last indices the target qubits of the gate, for instance:

>>> circuit = Circuit()
>>> circuit.push(CCCCCX, 0, 1, 2, 3, 4, 5)
6-qubit circuit with 1 instructions:
└── C₅X @ q[0,1,2,3,4], q[5]

Power

To raise the power of a gate you can use the power() function. For example, \(\sqrt{\mathrm{GateS}} = \mathrm{GateT}\), therefore, the following instruction can be used to generate the GateS:

>>> power(GateS(), 1/2)
T

The power method will attempt to realize simplifications whenever it can, for example asking for the square of GateX will directly give you GateID.

Inverse

To get the inverse of an operator you can use the inverse() method. Remember that the inverse of a unitary matrix is the same as the adjoint (conjugate transpose), so this is a simple way to get the adjoint of a gate. For example here is how to get the inverse of a GateH

>>> inv_H = inverse(GateH())

Parallel

To create a composite gate applying a specific gate to multiple qubits at once you can use the parallel() method.

>>> circuit = Circuit()
>>> X_gate_4 = parallel(4, GateX())
>>> circuit.push(X_gate_4, 0, 1, 2, 3)
4-qubit circuit with 1 instructions:
└── ⨷ ⁴ X @ q[0], q[1], q[2], q[3]

>>> circuit.draw()
           ┌─┐
    q[0]: ╶┤X├────────────────────────────────────────────────────────────────────╴
           ├─┤
    q[1]: ╶┤X├────────────────────────────────────────────────────────────────────╴
           ├─┤
    q[2]: ╶┤X├────────────────────────────────────────────────────────────────────╴
           ├─┤
    q[3]: ╶┤X├────────────────────────────────────────────────────────────────────╴
           └─┘

To check the number of repetition of your custom parallel gate you can use the num_repeats() method:

>>> X_gate_4.num_repeats
4

Be careful when using a multi-qubit gate with parallel() as the index of the targeted qubits in push() can become confusing. for example see below the parallel applicatoin of a CX gate:

>>> circuit = Circuit()
>>> double_CX = Parallel(2, GateCX())
>>> circuit.push(double_CX, 0, 1, 2, 3)
4-qubit circuit with 1 instructions:
└── ⨷ ² CX @ q[0], q[1], q[2], q[3]

>>> circuit.draw()
        ┌────┐
 q[0]: ╶┤0   ├─────────────────────────────────────────────────────────────────╴
        │  CX│
 q[1]: ╶┤1   ├─────────────────────────────────────────────────────────────────╴
        ├────┤
 q[2]: ╶┤0   ├─────────────────────────────────────────────────────────────────╴
        │  CX│
 q[3]: ╶┤1   ├─────────────────────────────────────────────────────────────────╴
        └────┘

Here the index 0 and 1 correspond to the control and target of the first CX gate and 2 and 3 correspond to the second CX gate.

Extract information of unitary gates

MIMIQ priovides a few methods to extract information about the unitary gates.

Matrix

To get the matrix of a unitary gate you can use the matrix():

>>> GateCX().matrix()
[1.0, 0, 0, 0]
[0, 1.0, 0, 0]
[0, 0, 0, 1.0]
[0, 0, 1.0, 0]

Number of targets

Another way to know how many qubits, bits or z-variables are targeted by one unitary gate you can use num_qubits(), num_bits() and num_zvars(), respectively.

>>> GateCX().num_qubits, GateCX().num_bits, GateCX().num_zvars
(2, 0, 0)
>>> Measure().num_qubits, Measure().num_bits, Measure().num_zvars
(1, 1, 0)
>>> Amplitude("01").num_qubits,Amplitude("01").num_bits, Amplitude("01").num_zvars
(0, 0, 1)

See non-unitary operations and statistical operations pages for more information on Measure and Amplitude.

Reference

class mimiqcircuits.GateID[source]

Bases: Gate

Single qubit Identity gate.

Matrix representation:

\[\begin{split}\operatorname{ID} = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix}\end{split}\]

Examples

>>> from mimiqcircuits import *
>>> GateID()
ID
>>> GateID().matrix()
[1.0, 0]
[0, 1.0]

>>> c = Circuit().push(GateID(), 0)
>>> c
1-qubit circuit with 1 instruction:
└── ID @ q[0]

>>> GateID().power(2), GateID().inverse()
(ID, ID)
>>> GateID().decompose()
1-qubit circuit with 1 instruction:
└── U(0, 0, 0, 0.0) @ q[0]
inverse()[source]

Raise an error, as non-unitary operators cannot be inverted.

This method is not implemented for non-unitary operators and will raise a NotImplementedError if called.

Raises:

NotImplementedError – If the method is called.

isidentity()[source]
class mimiqcircuits.GateX[source]

Bases: Gate

Single qubit Pauli-X gate.

Matrix representation:

\[\begin{split}\operatorname{X} = \begin{pmatrix} 0 & 1 \\ 1 & 0 \end{pmatrix}\end{split}\]

Examples

>>> from mimiqcircuits import *
>>> GateX()
X
>>> GateX().matrix()
[0, 1.0]
[1.0, 0]

>>> c = Circuit().push(GateX(), 0)
>>> c
1-qubit circuit with 1 instruction:
└── X @ q[0]

>>> GateX().power(2), GateX().inverse()
(ID, X)
>>> GateX().decompose()
1-qubit circuit with 1 instruction:
└── U(pi, 0, pi, 0.0) @ q[0]
inverse()[source]

Raise an error, as non-unitary operators cannot be inverted.

This method is not implemented for non-unitary operators and will raise a NotImplementedError if called.

Raises:

NotImplementedError – If the method is called.

class mimiqcircuits.GateY[source]

Bases: Gate

Single qubit Pauli-Y gate.

Matrix representation:

\[\begin{split}\operatorname{Y} = \begin{pmatrix} 0 & -i \\ i & 0 \end{pmatrix}\end{split}\]

Examples

>>> from mimiqcircuits import *
>>> GateY()
Y
>>> GateY().matrix()
[0, -0.0 - 1.0*I]
[0.0 + 1.0*I, 0]

>>> c = Circuit().push(GateY(), 0)
>>> GateY().power(2), GateY().inverse()
(ID, Y)
>>> GateY().decompose()
1-qubit circuit with 1 instruction:
└── U(pi, (1/2)*pi, (1/2)*pi, 0.0) @ q[0]
inverse()[source]

Raise an error, as non-unitary operators cannot be inverted.

This method is not implemented for non-unitary operators and will raise a NotImplementedError if called.

Raises:

NotImplementedError – If the method is called.

class mimiqcircuits.GateZ[source]

Bases: Gate

Single qubit Pauli-Z gate.

Matrix representation:

\[\begin{split}\operatorname{Z} = \begin{pmatrix} 1 & 0 \\ 0 & -1 \end{pmatrix}\end{split}\]

Examples

>>> from mimiqcircuits import *
>>> GateZ()
Z
>>> GateZ().matrix()
[1.0, 0]
[0, -1.0]

>>> c = Circuit().push(GateZ(), 0)
>>> GateZ().power(2), GateZ().inverse()
(ID, Z)
>>> GateZ().decompose()
1-qubit circuit with 1 instruction:
└── P(pi) @ q[0]
inverse()[source]

Raise an error, as non-unitary operators cannot be inverted.

This method is not implemented for non-unitary operators and will raise a NotImplementedError if called.

Raises:

NotImplementedError – If the method is called.

class mimiqcircuits.GateH[source]

Bases: Gate

Single qubit Hadamard gate.

Matrix representation:

\[\begin{split}\operatorname{H} = \frac{1}{\sqrt{2}} \begin{pmatrix} 1 & 1 \\ 1 & -1 \end{pmatrix}\end{split}\]

Examples

>>> from mimiqcircuits import *
>>> GateH()
H
>>> GateH().matrix()
[0.707106781186548, 0.707106781186548]
[0.707106781186548, -0.707106781186548]

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

>>> GateH().power(2), GateH().inverse()
(ID, H)
>>> GateH().decompose()
1-qubit circuit with 1 instruction:
└── U((1/2)*pi, 0, pi, 0.0) @ q[0]
inverse()[source]

Raise an error, as non-unitary operators cannot be inverted.

This method is not implemented for non-unitary operators and will raise a NotImplementedError if called.

Raises:

NotImplementedError – If the method is called.

class mimiqcircuits.GateCX(num_controls=None, operation=None, *args, **kwargs)[source]

Bases: Control

Two qubit Controlled-X gate (or CNOT).

By convention, the first qubit is the control and the second is the target

Matrix representation:

\[\begin{split}\operatorname{CX} = \begin{pmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 0 & 1 & 0 \end{pmatrix}\end{split}\]

Examples

>>> from mimiqcircuits import *
>>> GateCX(), GateCX().num_controls, GateCX().num_targets
(CX, 1, 1)
>>> GateCX().matrix()
[1.0, 0, 0, 0]
[0, 1.0, 0, 0]
[0, 0, 0, 1.0]
[0, 0, 1.0, 0]

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

>>> GateCX().power(2), GateCX().inverse()
(CID, CX)
>>> GateCX().decompose()
2-qubit circuit with 1 instruction:
└── CX @ q[0], q[1]
__init__(num_controls=1, operation=None)[source]

Initialize a CX gate.

Parameters:
  • num_controls – Ignored, always 1 for CX.

  • operation – Ignored, always GateX() for CX.

class mimiqcircuits.GateRX(theta)[source]

Bases: Gate

Single qubit Rotation gate around the axis \(\hat{x}\)

Matrix representation:

\[\begin{split}\operatorname{RX}(\theta) = \begin{pmatrix} \cos\frac{\theta}{2} & -i\sin\frac{\theta}{2} \\ -i\sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{pmatrix}\end{split}\]
Parameters:

theta – Rotation angle in radians.

Examples

>>> from mimiqcircuits import *
>>> from symengine import *
>>> theta = Symbol('theta')
>>> GateRX(theta)
RX(theta)
>>> GateRX(theta).matrix()
[cos((1/2)*theta), -I*sin((1/2)*theta)]
[-I*sin((1/2)*theta), cos((1/2)*theta)]

>>> c = Circuit().push(GateRX(theta), 0)
>>> c
1-qubit circuit with 1 instruction:
└── RX(theta) @ q[0]

>>> GateRX(theta).power(2), GateRX(theta).inverse()
(RX(2*theta), RX(-theta))
>>> GateRX(theta).decompose()
1-qubit circuit with 1 instruction:
└── U(theta, (-1/2)*pi, (1/2)*pi, 0.0) @ q[0]
__init__(theta)[source]
inverse()[source]

Raise an error, as non-unitary operators cannot be inverted.

This method is not implemented for non-unitary operators and will raise a NotImplementedError if called.

Raises:

NotImplementedError – If the method is called.

isidentity()[source]
class mimiqcircuits.GateRY(theta)[source]

Bases: Gate

Single qubit Rotation gate around the axis \(\hat{y}\)

Matrix representation:

\[\begin{split}\operatorname{RY}(\theta) = \begin{pmatrix} \cos\frac{\theta}{2} & -\sin\frac{\theta}{2} \\ \sin\frac{\theta}{2} & \cos\frac{\theta}{2} \end{pmatrix}\end{split}\]
Parameters:

theta (float) – Rotation angle in radians.

Examples

>>> from mimiqcircuits import *
>>> from symengine import *
>>> theta = Symbol('theta')
>>> GateRY(theta)
RY(theta)
>>> GateRY(theta).matrix()
[cos((1/2)*theta), -sin((1/2)*theta)]
[sin((1/2)*theta), cos((1/2)*theta)]

>>> c = Circuit().push(GateRY(theta), 0)
>>> c
1-qubit circuit with 1 instruction:
└── RY(theta) @ q[0]

>>> GateRY(theta).power(2), GateRY(theta).inverse()
(RY(2*theta), RY(-theta))
>>> GateRY(theta).decompose()
1-qubit circuit with 1 instruction:
└── U(theta, 0, 0, 0.0) @ q[0]
__init__(theta)[source]
inverse()[source]

Raise an error, as non-unitary operators cannot be inverted.

This method is not implemented for non-unitary operators and will raise a NotImplementedError if called.

Raises:

NotImplementedError – If the method is called.

isidentity()[source]
class mimiqcircuits.GateRZ(lmbda)[source]

Bases: Gate

Single qubit Rotation gate around the axis \(\hat{z}\)

Matrix representation:

\[\begin{split}\operatorname{RZ}(\lambda) = \begin{pmatrix} e^{-i\frac{\lambda}{2}} & 0 \\ 0 & e^{i\frac{\lambda}{2}} \end{pmatrix}\end{split}\]
Parameters:

lambda – Rotation angle in radians.

Examples

>>> from mimiqcircuits import *
>>> from symengine import *
>>> lmbda = Symbol('lambda')
>>> GateRZ(lmbda)
RZ(lambda)
>>> GateRZ(lmbda).matrix()
[exp(-1/2*I*lambda), 0]
[0, exp(1/2*I*lambda)]

>>> c = Circuit().push(GateRZ(lmbda), 0)
>>> c
1-qubit circuit with 1 instruction:
└── RZ(lambda) @ q[0]

>>> GateRZ(lmbda).power(2), GateRZ(lmbda).inverse()
(RZ(2*lambda), RZ(-lambda))
>>> GateRZ(lmbda).decompose()
1-qubit circuit with 1 instruction:
└── U(0, 0, lambda, (-1/2)*lambda) @ q[0]
__init__(lmbda)[source]
inverse()[source]

Raise an error, as non-unitary operators cannot be inverted.

This method is not implemented for non-unitary operators and will raise a NotImplementedError if called.

Raises:

NotImplementedError – If the method is called.

isidentity()[source]
class mimiqcircuits.GateU(theta, phi, lmbda, gamma=0.0)[source]

Bases: Gate

Single qubit generic unitary phase gate.

Matrix representation:

\[\begin{split}\operatorname{U}(\theta, \phi, \lambda, \gamma) = \mathrm{e}^{i\gamma} \begin{pmatrix} \cos\left(\frac{\theta}{2}\right) & -\mathrm{e}^{i\lambda}\sin\left(\frac{\theta}{2}\right)\\ \mathrm{e}^{i\phi}\sin\left(\frac{\theta}{2}\right) & \mathrm{e}^{i(\phi+\lambda)}\cos\left (\frac{\theta}{2}\right) \end{pmatrix}\end{split}\]
Parameters:
  • theta (float) – Euler angle 1 in radians.

  • phi (float) – Euler angle 2 in radians.

  • lambda (float) – Euler angle 3 in radians.

  • gamma (float, optional) – Euler angle 4 in radians (default is 0).

Examples

>>> from mimiqcircuits import *
>>> from symengine import *
>>> theta, phi, lmbda, gamma = symbols('theta phi lambda gamma')
>>> GateU(theta, phi, lmbda, gamma)
U(theta, phi, lambda, gamma)
>>> GateU(theta, phi, lmbda, gamma).matrix()
[exp(I*gamma)*cos((1/2)*theta), -exp(I*(gamma + lambda))*sin((1/2)*theta)]
[exp(I*(gamma + phi))*sin((1/2)*theta), exp(I*(gamma + lambda + phi))*cos((1/2)*theta)]

>>> c = Circuit().push(GateU(theta, phi, lmbda, gamma), 0)
>>> c
1-qubit circuit with 1 instruction:
└── U(theta, phi, lambda, gamma) @ q[0]

>>> GateU(theta, phi, lmbda, gamma).power(2), GateU(theta, phi, lmbda, gamma).inverse()
(U(theta, phi, lambda, gamma)**2, U(-theta, -lambda, -phi, -gamma))
>>> GateU(theta, phi, lmbda, gamma).decompose()
1-qubit circuit with 1 instruction:
└── U(theta, phi, lambda, gamma) @ q[0]

>>> c = Circuit().push(GateU(theta, phi, lmbda, gamma), 0)
>>> c
1-qubit circuit with 1 instruction:
└── U(theta, phi, lambda, gamma) @ q[0]

>>> GateU(theta, phi, lmbda, gamma).power(2), GateU(theta, phi, lmbda, gamma).inverse()
(U(theta, phi, lambda, gamma)**2, U(-theta, -lambda, -phi, -gamma))
>>> GateU(theta, phi, lmbda, gamma).decompose()
1-qubit circuit with 1 instruction:
└── U(theta, phi, lambda, gamma) @ q[0]
__init__(theta, phi, lmbda, gamma=0.0)[source]
inverse()[source]

Raise an error, as non-unitary operators cannot be inverted.

This method is not implemented for non-unitary operators and will raise a NotImplementedError if called.

Raises:

NotImplementedError – If the method is called.

convert_to_numeric(matrix)[source]

Convert a symbolic matrix to a numeric numpy array.

class mimiqcircuits.GateCustom(matrix)[source]

Bases: Gate

One or Two qubit Custom gates.

Examples

>>> from mimiqcircuits import Circuit, GateCustom
>>> import numpy as np
>>> matrix = np.array([[1, 0, 0, 0],[0, 1, 0, 0],[0, 0, 0, -1j],[0, 0, 1j, 0]])
>>> c = Circuit()
>>> c.push(GateCustom(matrix), 0, 1)
2-qubit circuit with 1 instruction:
└── Custom(...) @ q[0:1]
__init__(matrix)[source]
matrix()[source]

Try to numerically evaluate entries when possible, otherwise keep symbolic.

property num_qubits
inverse()[source]

Raise an error, as non-unitary operators cannot be inverted.

This method is not implemented for non-unitary operators and will raise a NotImplementedError if called.

Raises:

NotImplementedError – If the method is called.

static is_unitary(matrix, tol=1e-08)[source]
pretty_print()[source]
evaluate(d)[source]

Substitute the symbolic parameters of the operator with numerical values.

This method evaluates the operator’s symbolic parameters using the values provided in the dictionary d. If the operator has no parameters, it returns the same instance. Otherwise, it creates a new instance of the operator with updated numerical parameters.

Parameters:

d (dict) – A dictionary where keys are symbolic parameter names and values are values for substitution.

Example

>>> from symengine import *
>>> from mimiqcircuits import *
>>> theta = symbols('theta')
>>> op = GateRX(theta)
>>> evaluated_op = op.evaluate({'theta': 0.5})
>>> print(evaluated_op)
RX(0.5)
unwrappedmatrix()[source]

Compute the matrix representation with all parameters evaluated.

This method returns the matrix representation of the operator with all symbolic parameters substituted with their numerical values. If any parameter cannot be evaluated to a numerical value, a ValueError is raised.

Returns:

The evaluated matrix representation of the operator.

Return type:

symengine.Matrix

Raises:

ValueError – If a parameter cannot be evaluated to a numerical value.

mimiqcircuits.control(*args)[source]

Apply a control to a quantum operation or creating a lazy expression.

This function can be used in two ways: 1. To create a controlled version of a Gate or Operation. 2. To create a lazy expression that will apply a control to a future argument.

Parameters:

*args

Variable length argument list. - If one argument is provided:

  • If it’s an operation, returns a lazy expression control(?, op).

  • If it’s an integer (num_controls), returns a lazy expression control(n, ?).

  • If two arguments are provided (num_controls, gate): - Returns the controlled operation gate.control(num_controls).

Returns:

The controlled operation or a lazy expression.

Return type:

Union[Operation, LazyExpr]

Raises:

TypeError – If the arguments are of invalid types or count.

Examples

>>> from mimiqcircuits import *
>>> op = control(2, GateX())
>>> op
C₂X
>>> lazy_ctrl = control(2)
>>> lazy_ctrl(GateX())
C₂X
mimiqcircuits.power(*args)[source]

Raise a quantum operation to a power or create a lazy expression.

Parameters:

*args

Variable length argument list. - If one argument is provided:

  • If it’s an operation, returns a lazy expression power(op, ?).

  • If it’s a number (exponent), returns a lazy expression power(?, exponent).

  • If two arguments are provided (gate, exponent): - Returns the powered operation gate.power(exponent).

Returns:

The powered operation or a lazy expression.

Return type:

Union[Operation, LazyExpr]

Raises:

TypeError – If the arguments are of invalid types or count.

Examples

>>> from mimiqcircuits import *
>>> op = power(GateX(), 0.5)
>>> op
SX
mimiqcircuits.inverse(op)[source]

Compute the inverse of a quantum operation.

Parameters:

op (Operation) – The operation to invert.

Returns:

The inverse of the operation.

Return type:

Operation

Examples

>>> from mimiqcircuits import *
>>> op = inverse(GateS())
>>> op
S†
mimiqcircuits.parallel(*args)[source]

Create a parallel execution of a quantum operation or a lazy expression.

This function can be used to apply an operation multiple times in parallel across different qubits.

Parameters:

*args

Variable length argument list. - If one argument is provided:

  • If it’s an operation, returns a lazy expression parallel(?, op).

  • If it’s an integer (num_repeats), returns a lazy expression parallel(n, ?).

  • If two arguments are provided (num_repeats, gate):
    • Returns the parallel operation gate.parallel(num_repeats).

Returns:

The parallel operation or a lazy expression.

Return type:

Union[Operation, LazyExpr]

Raises:

TypeError – If the arguments are of invalid types or count.

Examples

>>> from mimiqcircuits import *
>>> op = parallel(3, GateH())
>>> op
⨷ ³ H