Special Operations

MIMIQ offers further possibilities to create circuits, such as new gate declarations, or wrappers for common combinations of gates.

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):
...     c= Circuit()
...     c.push(GateX(),0)
...     c.push(GateP(rot),1)
...     return c

>>> 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 Circuit. A circuit is created by sequentially applying operations using push(), where each operation is followed by its targets. The target order follows the standard convention quantum register -> classical register -> Z-register order. 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]
└── P((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).

Blocks of Instructions

Blocks in MIMIQ allow you to encapsulate a collection of quantum operations as a reusable unit. They’re particularly useful when you want to group instructions together that implement a specific algorithm or subroutine.

Unlike gate declarations that create new gates, blocks simply group existing instructions without restriction on their nature or type.

A Block` can be created in several ways:

# From an existing circuit
>>> circ = Circuit()
>>> circ.push(GateH(), 0)
1-qubit circuit with 1 instructions:
└── H @ q[0]


>>> circ.push(GateCX(), 0, 1)
2-qubit circuit with 2 instructions:
├── H @ q[0]
└── CX @ q[0], q[1]


>>> block = Block(circ)
>>> block
2-qubit block ... with 2 instructions:
├── H @ q[0]
└── CX @ q[0], q[1]

# From a list of instructions
>>> inst = [Instruction(GateX(), (0,)), Instruction(GateY(), (1,))]
>>> block2 = Block(inst)
>>> block2
2-qubit block ... with 2 instructions:
├── X @ q[0]
└── Y @ q[1]

# Empty block with specified dimensions
>>> block3 = Block(2, 1, 0)  # 2 qubits, 1 classical bit, 0 z-variables
>>> block3
empty block

Example: Error Correction Code Block

>>> def error_detection_block():
...     c = Circuit()
...     c.push(GateCX(), 0, 1)
...     c.push(GateCX(), 0, 2)
...     c.push(MeasureZ(), 1, 0)  # Measure q[1] → c[0]
...     c.push(MeasureZ(), 2, 1)  # Measure q[2] → c[1]
...     c.push(IfStatement(GateX(), BitString("01")), 0, 0, 1)  # Error correction
...     c.push(IfStatement(GateX(), BitString("10")), 0, 0, 1)  # Error correction
...     return Block(c)

>>> error_detection = error_detection_block()
>>> error_detection
3-qubit, 2-bit block ... with 6 instructions:
├── CX @ q[0], q[1]
├── CX @ q[0], q[2]
├── M @ q[1], c[0]
├── M @ q[2], c[1]
├── IF (c==01) X @ q[0], c[0,1]
└── IF (c==10) X @ q[0], c[0,1]

>>> main_circuit = Circuit()
>>> main_circuit.push(error_detection, 0, 1, 2, 0, 1)
3-qubit, 2-bit circuit with 1 instructions:
└── block ... @ q[0,1,2], c[0,1]


>>> main_circuit.push(GateH(), 1)
3-qubit, 2-bit circuit with 2 instructions:
├── block ... @ q[0,1,2], c[0,1]
└── H @ q[1]



>>> main_circuit.push(error_detection, 0, 1, 2, 0, 1)
3-qubit, 2-bit circuit with 3 instructions:
├── block ... @ q[0,1,2], c[0,1]
├── H @ q[1]
└── block ... @ q[0,1,2], c[0,1]

Working with Blocks

>>> b = Block(2, 1, 0)
>>> b
empty block
>>> b.push(GateH(), 0)
2-qubit, 1-bit block 755fa548dc10 with 1 instructions:
└── H @ q[0]
>>> b.push(GateX(), 1)
2-qubit, 1-bit block 755fa548dc10 with 2 instructions:
├── H @ q[0]
└── X @ q[1]
>>> b.push(GateCX(), 0, 1)
2-qubit, 1-bit block 755fa548dc10 with 3 instructions:
├── H @ q[0]
├── X @ q[1]
└── CX @ q[0], q[1]

Blocks can be iterated over, indexed, and have a length just like circuits:

>>> len(b)
3

>>> b[0]
H @ q[0]

Trying to add operations that use more resources than the block dimensions will result in an error:

>>> try:
...     b.push(GateZ(), 3)
... except Exception as e:
...     print(type(e).__name__)
ValueError

When to Use Blocks vs Gate Declarations

Use Block when you want to:

  • Group a sequence of operations for organization

  • Reuse a routine with control/measurement logic

  • Include operations that are not unitary

Use GateDecl when you want to:

  • Define a new named unitary gate

  • Create parameterized gates

  • Use symbolic arguments and controlled gate behavior

Repeated Operations

The Repeat operation allows you to apply the same quantum instruction multiple times. operation allows you to apply the same quantum operation multiple times. This can be particularly useful for algorithms that require iterative application of the same operation, such as quantum walks or amplitude amplification.

Creating Repeated Operations

There are two main ways to create repeated operations:

# Method 1: Using the Repeat constructor directly
Repeat(n, operation)  # Repeats the given operation `n` times

# Method 2: Using the `repeat` helper function (recommended)
repeat(n, operation)  # Repeats the operation with optional simplification logic

# Alternative: Using the `.repeat()` method on an operation instance
operation.repeat(n)   # Shorthand for repeating an operation `n` times
>>> Repeat(3, GateX())
∏³ X

>>> GateRX(Symbol("theta")).repeat(5)
∏⁵ RX(theta)

>>> repeat(5, GateRX(Symbol("theta"))).evaluate({"theta": 1})
∏⁵ RX(1)

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


>>> c.push(repeat(5, GateRX(Symbol("θ"))), 1)
2-qubit circuit with 2 instructions:
├── ∏³ H @ q[0]
└── ∏⁵ RX(θ) @ q[1]


>>> c.decompose()
2-qubit circuit with 8 instructions:
├── H @ q[0]
├── H @ q[0]
├── H @ q[0]
├── RX(θ) @ q[1]
├── RX(θ) @ q[1]
├── RX(θ) @ q[1]
├── RX(θ) @ q[1]
└── RX(θ) @ q[1]

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:

\[P_1 \otimes P_2 \otimes P_3 \otimes \ldots \otimes P_N,\]

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:

\[y_{k} = \frac{1}{\sqrt{N}} \sum_{j=0}^{N-1} x_{j}w_{N}^{-jk}\]

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:

\[\operatorname{PhaseGradient} = \sum_{k=0}^{N-1} \mathrm{e}^{i \frac{2 \pi}{N} k} \ket{k}\bra{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]

RNZ Gate

The GateRNZ applies a rotation along the Z-axis conditioned on the parity of the qubits.

>>> c = Circuit()
>>> c.push(GateRNZ(5, 0.5), 1, 2, 3, 4, 5)
6-qubit circuit with 1 instructions:
└── RNZ(0.5) @ q[1,2,3,4,5]

Diffusion

The Diffusion operator corresponds to Grover’s diffusion operator. It implements the unitary transformation:

\[H^{\otimes n} (1 - 2\ket{0^n} \bra{0^n}) H^{\otimes n}\]

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├░───────────────────────────────────────────────────╴
                         ░      └─┘░

Reference

class mimiqcircuits.GateDecl(name, arguments, circuit)[source]

Bases: object

Simple declaration of gates using the @gatedecl decorator.

Examples

First Way

Import necessary libaries

>>> from symengine import symbols
>>> from mimiqcircuits import *
>>> x, y = symbols('x y')

Declare a gate using the @gatedecl decorator

>>> @gatedecl("ansatz")
... def ansatz(x):
...     c = Circuit()
...     c.push(GateX(), 0)
...     c.push(GateRX(x), 1)
...     return c

Create calls to the gate declariation

>>> ansatz(x)
ansatz(x)
>>> ansatz(y)
ansatz(y)

Decompose

>>> ansatz(x).decompose()
2-qubit circuit with 2 instructions:
├── X @ q[0]
└── RX(x) @ q[1]
>>> ansatz(2)
ansatz(2)
>>> ansatz(2).decompose()
2-qubit circuit with 2 instructions:
├── X @ q[0]
└── RX(2) @ q[1]

Second Way

>>> from symengine import *
>>> from mimiqcircuits import *

Define symbols for the gate arguments

>>> x, y = symbols('x y')

From a circuit, create a GateDecl object directly

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

>>> c.push(GateRX(x), 1)
2-qubit circuit with 2 instructions:
├── XXplusYY(x, y) @ q[0:1]
└── RX(x) @ q[1]

>>> gate_decl = GateDecl("ansatz", (x,y), c)
>>> gate_decl
gate ansatz(x, y) =
├── XXplusYY(x, y) @ q[0:1]
└── RX(x) @ q[1]

>>> GateCall(gate_decl, (2,4))
ansatz(2, 4)

Decompose the GateCall into a quantum circuit

>>> GateCall(gate_decl, (2,4)).decompose()
2-qubit circuit with 2 instructions:
├── XXplusYY(2, 4) @ q[0:1]
└── RX(2) @ q[1]

Add to Circuit

>>> g = GateCall(gate_decl, (2,4))
>>> c = Circuit()
>>> c.push(g,10,22)
23-qubit circuit with 1 instruction:
└── ansatz(2, 4) @ q[10,22]
__init__(name, arguments, circuit)[source]
property num_qubits
property num_bits
property num_zvars
mimiqcircuits.gatedecl(name)[source]
class mimiqcircuits.Block(*args)[source]

Bases: Operation

Block operation: group and reuse a sequence of instructions.

The Block class represents a reusable subcircuit. It encapsulates a fixed number of qubits, bits, and z-variables, along with a list of instructions. Blocks are used to define logical units in a circuit that can be inserted as composite operations.

You can construct a block in several ways:

  • Block(): Create an empty block with 0 qubits, bits, and zvars.

  • Block(circuit): Copy instructions from a circuit (or block) into a new block.

  • Block(instructions): Create a block from a list of Instruction objects.

  • Block(num_qubits, num_bits, num_zvars[, instructions]): Fully specify a block.

Notes

  • Once created, a block has a fixed number of qubits, bits, and zvars.

  • Adding an instruction that exceeds the declared dimensions will raise a ValueError.

  • Blocks are deep-copied upon construction to avoid accidental mutations.

Examples

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

<BLANKLINE> >>> c.push(GateCX(), 1, 3) 4-qubit circuit with 2 instructions: ├── CX @ q[1], q[2] └── CX @ q[1], q[3] <BLANKLINE>

>>> c.push(MeasureZZ(), 1, 2, 1)
4-qubit, 2-bit circuit with 3 instructions:
├── CX @ q[1], q[2]
├── CX @ q[1], q[3]
└── MZZ @ q[1:2], c[1]
>>> block = Block(c)
>>> block
4-qubit, 2-bit block 700db35cc170 with 3 instructions:
├── CX @ q[1], q[2]
├── CX @ q[1], q[3]
└── MZZ @ q[1:2], c[1]
>>> main = Circuit()
>>> main.push(block, 0, 1, 2, 3, 0, 1)
4-qubit, 2-bit circuit with 1 instruction:
└── block 700db35cc170 @ q[0,1,2,3], c[0,1]
>>> main.decompose()
4-qubit, 2-bit circuit with 3 instructions:
├── CX @ q[1], q[2]
├── CX @ q[1], q[3]
└── MZZ @ q[1:2], c[1]

See also

  • Instruction

  • Circuit

__init__(*args)[source]
push(operation, *args)[source]
iswrapper()[source]
blockid()[source]
format_with_targets(qubits, bits, zvars)[source]
class mimiqcircuits.Repeat(repeats, op)[source]

Bases: Operation

Repeat operation: applies the same operation multiple times.

Repeats a given quantum operation n times on the same qubits, bits, and z-variables. This is useful for constructing repeated sequences of the same gate without manually duplicating it.

Examples

>>> from mimiqcircuits import *
>>> from symengine import *
>>> Repeat(5, GateX())
∏⁵ X
>>> Repeat(3, GateRX(Symbol("x")))
∏³ RX(x)
>>> Repeat(2, Repeat(3, GateX()))
∏² ∏³ X
>>> Parallel(2, GateH()).repeat()
lazy repeat(?, ⨷ ² H)
>>> Parallel(2, GateH()).repeat()(10)
∏¹⁰ ⨷ ² H
>>> c = Circuit().push(Repeat(2, GateX()), 0)
>>> c
1-qubit circuit with 1 instruction:
└── ∏² X @ q[0]
>>> Repeat(2, GateX()).decompose()
1-qubit circuit with 2 instructions:
├── X @ q[0]
└── X @ q[0]
>>> Repeat(3, GateSWAP()).decompose()
2-qubit circuit with 3 instructions:
├── SWAP @ q[0:1]
├── SWAP @ q[0:1]
└── SWAP @ q[0:1]

Note

The repeat function is a shorthand that may return other types (e.g., Power) if simplifications apply. It returns a Repeat instance only when appropriate.

__init__(repeats, op)[source]
get_operation()[source]
getparam(name)[source]
getparams()[source]
power(pow)[source]
parallel(repeat)[source]
control(repeat)[source]
inverse()[source]
iswrapper()[source]
evaluate(d)[source]
copy()[source]
Creates a shallow copy of the operation.

To create a full copy use deepcopy() instead.

Returns:

A new Operation object containing references to the same attributes as the original circuit

Return type:

Operation

deepcopy()[source]

Creates a copy of the object and for all its attributes

Returns:

A new Operation object fully identical the original circuit

Return type:

Operation

format_with_targets(qubits, bits, zvars)[source]
mimiqcircuits.repeat(*args)[source]
class mimiqcircuits.IfStatement(operation, bitstring)[source]

Bases: Operation

Conditional operation

This operation applies a specific operation if a given classical bit condition is met.

Examples

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(IfStatement(GateX(), BitString('1')), 0, 0)
1-qubit, 1-bit circuit with 1 instruction:
└── IF(c==1) X @ q[0], condition[0]

>>> IfStatement(Parallel(4,GateH()), BitString("01"))
IF (c==01) ⨷ ⁴ H
__init__(operation, bitstring)[source]
property op
property bitstring
iswrapper()[source]
getparams()[source]
inverse()[source]
power(power)[source]
control(num_controls)[source]
format_with_targets(qubits, bits, zvars)[source]

Nested conditional formatting (explicit IF(c==..) chain).

evaluate(d)[source]
get_operation()[source]
get_bitstring()[source]
asciiwidth(qubits, bits, zvars)[source]
class mimiqcircuits.PauliString(pauli)[source]

Bases: Gate

PauliString.

N-qubit tensor product of Pauli operators.

The PauliString gate can represent any N-qubit tensor product of operators of the form P_1 ⊗ P_2 ⊗ P_3 ⊗ … ⊗ P_N, where each P_i ∈ { I, X, Y, Z } is a Pauli operator, including the identity.

This gate can be initialized by passing as argument a string of length N where each element is either ‘I’, ‘X’, ‘Y’, or ‘Z’. For example, PauliString(“IXXYZ”)

Examples

>>> from mimiqcircuits import *
>>> g= PauliString("XYZ")
>>> g.matrix()
[0, 0, 0, 0, 0, 0, -0.0 - 1.0*I, 0]
[0, 0, 0, 0, 0, 0, 0, 0.0 + 1.0*I]
[0, 0, 0, 0, 0.0 + 1.0*I, 0, 0, 0]
[0, 0, 0, 0, 0, -0.0 - 1.0*I, 0, 0]
[0, 0, -0.0 - 1.0*I, 0, 0, 0, 0, 0]
[0, 0, 0, 0.0 + 1.0*I, 0, 0, 0, 0]
[0.0 + 1.0*I, 0, 0, 0, 0, 0, 0, 0]
[0, -0.0 - 1.0*I, 0, 0, 0, 0, 0, 0]

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

>>> c.decompose()
3-qubit circuit with 3 instructions:
├── U(pi, 0, pi, 0.0) @ q[0]
├── U(pi, (1/2)*pi, (1/2)*pi, 0.0) @ q[1]
└── U(0, 0, pi, 0.0) @ q[2]
__init__(pauli)[source]
classmethod from_string(pauli_expr)[source]
property num_qubits
property parnames
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.

unwrapped_matrix()[source]
is_identity()[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)
isidentity()[source]
asciiwidth(qubits, bits, zvars)[source]
class mimiqcircuits.QFT(*args)[source]

Bases: Gate

Quantum Fourier transform.

Performs the quantum Fourier transform on a register of n qubits.

Parameters:

n (int) – The number of qubits in the quantum register.

Raises:

ValueError – If the number of qubits is less than 1.

Returns:

The Quantum Fourier Transform operation.

Return type:

QFT

name

The name of the operation.

Type:

str

num_qubits

The number of qubits in the quantum register.

Type:

int

qregsizes

The sizes of the quantum registers.

Type:

list of int

Examples

>>> from mimiqcircuits import *
>>> c=Circuit()
>>> c.push(QFT(2),1,2)
3-qubit circuit with 1 instruction:
└── QFT @ q[1:2]
__init__(num_qubits)[source]
class mimiqcircuits.PhaseGradient(*args)[source]

Bases: Gate

Phase Gradient gate

A phase gradient gate applies a phase shift to a quantum register of n qubits, where each computational basis state |k⟩ experiences a phase proportional to its integer value k

Parameters:

n (int) – The number of qubits in the quantum register.

Returns:

The PhaseGradient gate.

Return type:

PhaseGradient

name

The name of the operation.

Type:

str

num_qubits

The number of qubits in the quantum register.

Type:

int

qregsizes

The sizes of the quantum registers.

Type:

list of int

Examples

>>> from mimiqcircuits import *
>>> c=Circuit()
>>> c.push(PhaseGradient(2),9,8)
10-qubit circuit with 1 instruction:
└── PhaseGradient @ q[9,8]
__init__(num_qubits)[source]
class mimiqcircuits.PolynomialOracle(*args)[source]

Bases: Gate

Polynomial Oracle.

Parameters:
  • a (Num) – Coefficient a in the polynomial.

  • b (Num) – Coefficient b in the polynomial.

  • c (Num) – Coefficient c in the polynomial.

  • d (Num) – Coefficient d in the polynomial.

Raises:

ValueError – If the input parameters do not satisfy the required conditions.

Returns:

The Polynomial Oracle.

Return type:

PolynomialOracle

Examples

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(PolynomialOracle(5,5,1, 2, 3, 4), 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
11-qubit circuit with 1 instruction:
└── PolynomialOracle(1, 2, 3, 4) @ q[1:5], q[6:10]
__init__(nx, ny, a, b, c, d)[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.

class mimiqcircuits.GateRNZ(*args)[source]

Bases: Gate

Multi-qubit parity-phase rotation gate along the Z-axis.

Applies a phase of e^{±i θ/2} depending on the parity (number of 1s) in the computational basis state.

Parameters:
  • n (int) – Number of qubits

  • theta (float or symbolic) – Rotation angle

Examples:

>>> from mimiqcircuits import *
>>> from symengine import pi
>>> GateRNZ(2, pi/2)
RNZ((1/2)*pi)
>>> GateRNZ()
lazy GateRNZ(?, ?)
>>> GateRNZ(2)
lazy GateRNZ(2, ?)
>>> GateRNZ(2, pi/2).decompose()
2-qubit circuit with 3 instructions:
├── CX @ q[0], q[1]
├── RZ((1/2)*pi) @ q[1]
└── CX @ q[0], q[1]
>>> c = Circuit()
>>> c.push(GateRNZ(3, pi/2), 1, 2, 3)
4-qubit circuit with 1 instruction:
└── RNZ((1/2)*pi) @ q[1:3]
__init__(n=None, theta=None)[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.

power(pwr)[source]

Raise an error, as powers of non-unitary operators are not supported.

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

Parameters:

n (int) – The exponent to which the operator would be raised.

Raises:

NotImplementedError – If the method is called.

matrix()[source]

Compute the matrix representation of the operator.

This method returns a symengine Matrix object representing the operator. It simplifies the matrix expression and evaluates it to a floating-point precision.

Returns:

The matrix representation of the operator.

Return type:

symengine.Matrix

evaluate(d)[source]

Evaluate symbolic parameters with substitution dictionary d.

Parameters:

d (dict) – Mapping of symbolic variables to values.

Returns:

A new evaluated GateRNZ instance.

Return type:

GateRNZ

class mimiqcircuits.Diffusion(*args)[source]

Bases: Gate

Grover’s diffusion operator.

Parameters:

num_qubits (int) – The number of qubits.

Raises:

ValueError – If the number of qubits is not an integer or less than 1.

Returns:

Grover’s diffusion operator.

Return type:

Diffusion

num_qubits

The number of qubits for the diffusion operator.

Type:

int

Examples

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(Diffusion(2), 1, 2)
3-qubit circuit with 1 instruction:
└── Diffusion @ q[1:2]
__init__(num_qubits)[source]
class mimiqcircuits.Barrier(*args)[source]

Bases: Operation

Barrier operation.

A barrier is a special operation that does not affect the quantum state or the execution of a circuit, but it prevents compression or optimization operation from being applied across it.

Examples

Adding Barrier operation to the Circuit (The args can be: range, list, tuple, set or int)

>>> from mimiqcircuits import *
>>> c= Circuit()
>>> c.push(Barrier(1), 1)
2-qubit circuit with 1 instruction:
└── Barrier @ q[1]
>>> from mimiqcircuits import *
>>> c= Circuit()
>>> c.push(Barrier(1), range(0,4))
4-qubit circuit with 4 instructions:
├── Barrier @ q[0]
├── Barrier @ q[1]
├── Barrier @ q[2]
└── Barrier @ q[3]

Adding Barrier to the circuit as a multi-qubits gate

>>> from mimiqcircuits import *
>>> c= Circuit()
>>> c.push(Barrier(5),1,2,3,4,5)
6-qubit circuit with 1 instruction:
└── Barrier @ q[1:5]
__init__(num_qubits)[source]

Initialize a barrier operation

Parameters:

num_qubits – number of qubits the barrier will cover

Raises:

ValueError – if num_qubits is less than 1

inverse()[source]
power(p)[source]
control(num_qubits)[source]
iswrapper()[source]
asciiwidth(qubits, bits, zvars)[source]
static isunitary()[source]

Check if the class represents a unitary operator.

By default, this method returns False unless explicitly overridden in a subclass.