Non-unitary Operations

Contrary to unitary gates, non-unitary operations based on measurements make the quantum state collapse. Find in the following sections all the non-unitary operations supported by MIMIQ.

Note

As a rule of thumb all non-unitary operations can be added to the circuit using the function push() by giving the index of the targets in the following order:raw:: quantum register index -> classical register index.

Note

Noise can also be interpreted as a non-unitary operations but will not be treated here, check the noise documentation page to learn more about it.

Note

Once a non-unitary operation is added to your circuit the speed of execution might be reduced. This is because in this case the circuit needs to be re-run for every sample since the final quantum state might be different each time. This is always true except for Measure operations placed at the very end of a circuit. To learn more about this head to the simulation page.

Note

Some features of unitary gates are not available for non-unitary operations, for instance, matrix(), inverse(), power(), control(), parallel().

Measure

Mathematical definition

Measurements are defined by a basis of projection operators \(P_k\), one for each different possible outcome \(k\). The probability \(p_k\) of measuring outcome \(k\) is given by the expectation value of \(P_k\), that is

\[p_k = \bra{\psi} P_k \ket{\psi}.\]

If the outcome \(k\) is observed, the system is left in the state

\[\frac{P_k\ket{\psi}}{\sqrt{p_k}}.\]

It is common to measure in the Z basis (\(P_0=\ket{0}\bra{0}\) and \(P_1=\ket{1}\bra{1}\)), but measurements in other bases are possible too.

How to use measurements

Available measurement operations: Measure, MeasureX, MeasureY, MeasureZ, MeasureXX, MeasureYY, MeasureZZ.

With MIMIQ you can measure the qubits at any point in the circuit (not only at the end of the circuit) using one of the measurement operations (Measure…). You can add it to the circuit like gates using push(), but you will need to precise both the index for the quantum register (qubit to measure) and classical register (where to store the result):

>>> circuit = Circuit()
>>> circuit.push(Measure(), 0, 0)
1-qubit, 1-bit circuit with 1 instructions:
└── M @ q[0], c[0]

This will add a Measure() on the first qubit of the quantum register to the circuit and write the result on the first bit of the classical register. Recall that the targets are always ordered as quantum register -> classical register -> z register. To learn more about registers head to the Registers: quantum/classical/Z-register section.

You can also use iterators to Measure multiple qubits at once, as for gates:

>>> circuit.push(Measure(), range(0, 10), range(0,10))
10-qubit, 10-bit circuit with 11 instructions:
├── M @ q[0], c[0]
├── M @ q[0], c[0]
├── M @ q[1], c[1]
├── M @ q[2], c[2]
├── M @ q[3], c[3]
├── M @ q[4], c[4]
├── M @ q[5], c[5]
├── M @ q[6], c[6]
├── M @ q[7], c[7]
├── M @ q[8], c[8]
└── M @ q[9], c[9]

Note

In the absence of any non-unitary operations in the circuit, MIMIQ will sample (and, therefore, measure) all the qubits at the end of the circuit by default, see simulation page.

Reset

Available reset operations: Reset, ResetX, ResetY, ResetZ.

A reset operation consists in measuring the qubits in some basis and then applying an operation conditioned on the measurement outcome to leave the qubits in some pre-defined state. For example, Reset() leaves all qubits in \(\ket{0}\) (by measuring in \(Z\) and flipping the state if the outcome is 1).

Here is an example of how to add a reset operation to a circuit:

>>> circuit = Circuit()
>>> circuit.push(Reset(), 0)
1-qubit circuit with 1 instructions:
└── Reset @ q[0]

Importantly, even though a reset operation technically measures the qubits, the information is not stored in the classical register, so we only need to specify the qubit register. If you want to store the result, see the Measure-Reset section.

Note that a reset operation can be technically seen as noise and is described by the same mathematical machinery, see noise page. For this reason, some of the functionality provided by MIMIQ for noise is also available for resets. Here is one example:

>>> Reset().krausoperators()
[P₀(1.0), SigmaMinus(1.0)]

Measure-Reset

Available measure-reset operations: MeasureReset, MeasureResetX, MeasureResetY, MeasureResetZ.

A measure-reset operation is the same as a reset operation except that we store the result of the measurement, see Measure and Reset sections. Because of that, we need to specify both quantum and classical registers when adding it to a circuit:

>>> circuit = Circuit()
>>> circuit.push(MeasureReset(), 0, 0)
1-qubit, 1-bit circuit with 1 instructions:
└── MR @ q[0], c[0]

Conditional logic

If statements

An if statement consists in applying an operation conditional on the value of some classical register. In that sense, it resembles a classical if statement.

In MIMIQ you can implement it using IfStatement, which requires two arguments: an operation to apply and a BitString as the condition (see Bitstrings page for more information):

>>> IfStatement(GateX(), BitString("111"))
IF (c==111) X

Note

At the moment, MIMIQ only allows to pass unitary gates as arguments to an if statement (which makes if statements unitary for now).

To add an IfStatement to a circuit use the push() function. The first (quantum) indices will determine the qubits to apply the gate to, whereas the last (classical) indices will be used to compare against the condition given. For example:

>>> circuit  = Circuit()

>>> # Apply a GateX on qubit 1 if the qubits 2 and 4 are in the state 1 and qubit 3 in the state 0.
>>> circuit.push(IfStatement(GateX(), BitString("101")), 0, 1, 2, 3)
1-qubit, 4-bit circuit with 1 instructions:
└── IF (c==101) X @ q[0], c[1,2,3]

Here, an X gate will be applied to qubit 1, if classical registers 2 and 4 are 1, and classical register 3 is 0. Of course, if the gate targets more than 1 qubit, then all qubit indices will be specified before the classical registers, as usual (see circuit page).

Operators

Mathematical definition

Operators refer to any linear operation on a state. An operator does not need to be unitary, as is the case of a gate. This means that any \(2^N \times 2^N\) matrix can in principle represent an operator on \(N\) qubits.

Note

Do not confuse operator with operation. In MIMIQ, the word operation is used as the supertype for all transformations of a quantum state (gates, measurements, statistical operations…), whereas an operator is a sort of generalized gate, a linear tranformation.

Operators available in MIMIQ

Custom operators: Operator

Special operators: DiagonalOp, SigmaPlus, SigmaMinus, Projector0, ProjectorZ0, Projector1, ProjectorZ1, ProjectorX0, ProjectorX1, ProjectorY0, ProjectorY1, Projector00, Projector01, Projector10, Projector11

Methods available: Gate;matrix().

How to use operators

Operators cannot be applied to a state directly (it cannot be added to a circuit using push()), because that would correspond to an unphysical transformation. However, they can be used within other operations such as ExpectationValue or to create custom noise models with Kraus, see noise and statistical operations pages.

Operators can be used to compute expectation values as follows (see also ExpectationValue):

>>> op = SigmaPlus()
>>> ev = ExpectationValue(op)
>>> circuit = Circuit()
>>> circuit.push(ev, 0, 0)
1-qubit, 1-zvar circuit with 1 instructions:
└── ⟨SigmaPlus(1)⟩ @ q[0], z[0]

Similarly, operators can also be used to define non-mixed unitary Kraus channels (see also Kraus). For example, we can define the amplitude damping channel as follows:

>>> gamma = 0.1
>>> k1 = DiagonalOp(1, math.sqrt(1-gamma))    # Kraus operator 1
>>> k2 = SigmaMinus(math.sqrt(gamma))    # Kraus operator 2
>>> kraus = Kraus([k1,k2])

This is equivalent to

>>> gamma = 0.1
>>> ampdamp = AmplitudeDamping(gamma)
>>> ampdamp.krausoperators()
[D(1, np.float64(0.9486832980505138)), SigmaMinus(0.31622776601683794)]

Note

Whenever possible, using specialized operators, such as DiagonalOp and SigmaMinus, as opposed to custom operators, such as Operator, is generally better for performance.

Reference

class mimiqcircuits.Measure[source]

Bases: AbstractMeasurement

Measure operation.

This operation performs a measurement in the computational basis (Z-basis) and stores the result in a classical register.

The measurement projects the quantum state onto either the \(|0⟩\) or \(|1⟩\) state, corresponding to the classical bit values of 0 and 1, respectively.

Warning

Measure is non-reversible.

Examples

Adding Measure operation to the Circuit (The qubits (first arg) and the bits (second arg) can be: range, list, tuple, set or int)

>>> from mimiqcircuits import *
>>> c= Circuit()
>>> c.push(Measure(),0,0)
1-qubit, 1-bit circuit with 1 instruction:
└── M @ q[0], c[0]
>>> from mimiqcircuits import *
>>> c= Circuit()
>>> c.push(Measure(), range(0,3), range(0,3))
3-qubit, 3-bit circuit with 3 instructions:
├── M @ q[0], c[0]
├── M @ q[1], c[1]
└── M @ q[2], c[2]
inverse()[source]
power(p)[source]
control(num_qubits)[source]
iswrapper()[source]
get_operation()[source]
class mimiqcircuits.Reset[source]

Bases: krauschannel

Reset operation.

Quantum operation that resets the status of one qubit to the \(\ket{0}\) state.

Warning

This operation is non-reversible.

Examples

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

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

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

See also

ResetX, ResetY, ResetZ - Variants of the Reset operation along different axes.

inverse()[source]
power(pwr)[source]
control(num_qubits)[source]
iswrapper()[source]
asciiwidth(qubits, bits, zvars)[source]
krausoperators(normaliz=None)[source]

Returns the Kraus operators associated with the given Kraus channel.

This should be implemented for each specific channel.

Returns:

A list of matrices representing the Kraus operators.

Return type:

list

class mimiqcircuits.MeasureReset[source]

Bases: AbstractMeasurement

MeasureReset operation.

This operation measures a qubit q, stores the value in a classical bit c, then applies a X operation to the qubit if the measured value is 1, effectively resetting the qubit to the \(\ket{0}\) state.

Examples

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(MeasureReset(), 1, 0)
2-qubit, 1-bit circuit with 1 instruction:
└── MR @ q[1], c[0]

>>> c.decompose()
2-qubit, 1-bit circuit with 1 instruction:
└── MR @ q[1], c[0]
inverse()[source]
power(p)[source]
control(num_qubits)[source]
iswrapper()[source]
get_operation()[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.Operator(mat)[source]

Bases: AbstractOperator

N-qubit operator specified by a \(2^N \times 2^N\) matrix.

Note

Only one and two qubit operators are supported.

This operator does not have to be unitary.

See also

AbstractOperator, ExpectationValue, KrausChannel

Parameters:

matrix (list or np.ndarray) – The \(2^N \times 2^N\) matrix representing the operator.

Examples

>>> from mimiqcircuits import *
>>> from symengine import Matrix
>>> op = Operator(Matrix([[1, 2], [3, 4]]))
>>> op
1-qubit Operator:
├── 1 2
└── 3 4
>>> op = Operator(Matrix([[1, 0, 0, 1], [0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 1]]))
>>> op
2-qubit Operator:
├── 1 0 0 1
├── 0 0 0 0
├── 0 0 0 0
└── 1 0 0 1

Operators can be used for expectation values:

>>> c = Circuit()
>>> c.push(ExpectationValue(Operator(Matrix([[0, 1], [0, 0]]))), 1, 1)
2-qubit, 2-zvar circuit with 1 instruction:
└── ⟨Operator([[0, 1], [0, 0]])⟩ @ q[1], z[1]
__init__(mat)[source]
static is_valid_power_of_2(n)[source]

Check if a number is a power of 2.

opname()[source]

Return the operator name.

property parnames

Return the parameter names.

iswrapper()[source]

Placeholder method.

unwrappedmatrix()[source]

Return the matrix unwrapped (if needed).

__repr__()[source]

Return a pretty-printed representation.

__str__()[source]

String representation for printing.

pretty_print()[source]

Return a detailed string representation of the operator.

opsquared()[source]

Return the operator squared (O’ * O).

rescale(scale)[source]

Return a new rescaled Operator by scaling the matrix.

rescale_inplace(scale)[source]

In-place rescaling of the operator matrix.

class mimiqcircuits.Kraus(E, rtol=1e-12)[source]

Bases: krauschannel

Kraus(kmatrices).

Custom N-qubit Kraus channel specified by a list of 2^N x 2^N Kraus matrices.

A Kraus channel is defined by:

\[\mathcal{E}(\rho) = \sum_k E_k \rho E_k^\dagger,\]

where \(E_k\) are Kraus matrices that need to fulfill \(\sum_k E_k^\dagger E_k = I\).

If the Kraus matrices are all proportional to unitaries, use MixedUnitary() instead.

The Kraus matrices are defined in the computational basis in the usual textbook order (the first qubit corresponds to the left-most qubit). For 1 qubit, we have \(|0\rangle\), \(|1\rangle\). For 2 qubits, we have \(|00\rangle\), \(|01\rangle\), \(|10\rangle\), \(|11\rangle\).

Note: Currently, only 1 and 2-qubit custom Kraus channels are supported.

See also

MixedUnitary GateCustom

Parameters:

kmatrices (list) – List of \(2^N \times 2^N\) complex matrices. The number of qubits is equal to \(N\).

Examples

>>> from mimiqcircuits import *
>>> from symengine import *
>>> c = Circuit()
>>> c.push(Kraus([Matrix([[1, 0], [0, sqrt(0.9)]]), Matrix([[0, sqrt(0.1)], [0, 0]])]), 0)
1-qubit circuit with 1 instruction:
└── Kraus(Operator([[1, 0], [0, 0.948683298050514]]), Operator([[0, 0.316227766016838], [0, 0]])) @ q[0]

Kraus Matrices

>>> g=Kraus([Matrix([[1, 0], [0, sqrt(0.9)]]), Matrix([[0, sqrt(0.1)], [0, 0]])])
>>> g.krausmatrices()
[[1.0, 0]
[0, 0.948683298050514]
, [0, 0.316227766016838]
[0, 0]
]

# Example 2: Kraus with Projector0 and Projector1

>>> c = Circuit()
>>> c.push(Kraus([Projector0(), Projector1()]), 0)
1-qubit circuit with 1 instruction:
└── Kraus(P₀(1), P₁(1)) @ q[0]

# Example 3: Kraus with a matrix and Projector1

>>> c = Circuit()
>>> c.push(Kraus([Matrix([[1, 0], [0, 0]]), Projector1()]), 0)
1-qubit circuit with 1 instruction:
└── Kraus(Operator([[1, 0], [0, 0]]), P₁(1)) @ q[0]
__init__(E, rtol=1e-12)[source]
evaluate(values)[source]

Evaluates symbolic parameters in Kraus matrices using a dictionary of values.

Parameters:

values (dict) – A dictionary where keys are symbolic variables and values are the corresponding numerical values.

Returns:

A new Kraus instance with evaluated matrices.

Return type:

Kraus

krausoperators()[source]

Returns the Kraus operators.

__str__()[source]

String representation, listing either AbstractOperators or matrix slices.

unwrappedkrausmatrices()[source]

Returns the unwrapped Kraus matrices.

class mimiqcircuits.DiagonalOp(a, b)[source]

Bases: AbstractOperator

One-qubit diagonal operator.

The corresponding matrix is given by:

Matrix Representation

\[\begin{split}\begin{pmatrix} a & 0 \\ 0 & b \end{pmatrix}\end{split}\]

where a and b are complex numbers.

See also

Operator, Projector0, Projector1

Parameters:
  • a (complex) – The top-left entry of the diagonal matrix.

  • b (complex) – The bottom-right entry of the diagonal matrix.

Examples

>>> from mimiqcircuits import *
>>> op = DiagonalOp(1, 0.5)
>>> c = Circuit()
>>> c.push(ExpectationValue(DiagonalOp(1, 0.5)), 1, 2)
2-qubit, 3-zvar circuit with 1 instruction:
└── ⟨D(1, 0.5)⟩ @ q[1], z[2]
__init__(a, b)[source]
property parnames
rescale(scale)[source]

Return a new rescaled DiagonalOp operator.

rescale_inplace(scale)[source]

In-place rescaling of the DiagonalOp operator.

opsquared()[source]

Return the operator with each parameter squared.

class mimiqcircuits.SigmaPlus(a=1)[source]

Bases: AbstractOperator

One-qubit operator corresponding to \(|1 \rangle\langle 0|\).

Matrix Representation

\[\begin{split}\begin{pmatrix} 0 & 0\\ a & 0 \end{pmatrix}\end{split}\]

This matrix is parametrized by a to allow for phases/rescaling.

The parameter a is optional and is set to 1 by default.

See also

SigmaMinus

Parameters:

a (complex, optional) – Scaling factor for the matrix. Defaults to 1.

Examples

>>> from mimiqcircuits import *
>>> SigmaPlus()
SigmaPlus(1)
>>> SigmaPlus(0.5)
SigmaPlus(0.5)
>>> c = Circuit()
>>> c.push(ExpectationValue(SigmaPlus()), 1, 1)
2-qubit, 2-zvar circuit with 1 instruction:
└── ⟨SigmaPlus(1)⟩ @ q[1], z[1]
__init__(a=1)[source]
property parnames
property num_qubits
opsquared()[source]
rescale(scale)[source]
class mimiqcircuits.SigmaMinus(a=1)[source]

Bases: AbstractOperator

One-qubit operator corresponding to \(|0 \rangle\langle 1|\).

Matrix Representation

\[\begin{split}\begin{pmatrix} 0 & a\\ 0 & 0 \end{pmatrix}\end{split}\]

This matrix is parametrized by a to allow for phases/rescaling.

The parameter a is optional and is set to 1 by default.

See also

SigmaPlus

Parameters:

a (complex, optional) – Scaling factor for the matrix. Defaults to 1.

Examples

>>> from mimiqcircuits import *
>>> SigmaMinus()
SigmaMinus(1)
>>> SigmaMinus(0.5)
SigmaMinus(0.5)
>>> c = Circuit()
>>> c.push(ExpectationValue(SigmaMinus()), 1, 1)
2-qubit, 2-zvar circuit with 1 instruction:
└── ⟨SigmaMinus(1)⟩ @ q[1], z[1]
__init__(a=1)[source]
property parnames
property num_qubits
opsquared()[source]
rescale(scale)[source]
rescale_inplace(scale)[source]