Statistical Operations

Statistical operations are simulator specific mathematical operations allowing you to compute properties of the simulated quantum state without making it collapse. All statistical operations will result in a real or complex number that will be stored in the Z-Register, and can be accessed from the results of the simulation through the zstates option, see Circuits and cloud execution page.

On this page you will find all statistical operations available on MIMIQ with explanations and examples.

Expectation value

Mathematical definition

An expectation value for a pure state \(| \psi \rangle\) is defined as

\[\langle O \rangle = \langle \psi | O | \psi \rangle\]

where \(O\) is an operator. With respect to a density matrix \(\rho\) it’s given by

\[\langle O \rangle = \mathrm{Tr}(\rho O).\]

Usage on MIMIQ

First we need to define the operator \(O\) of which we will compute the expectation value

>>> op = SigmaPlus()

SigmaPlus is only one of the many operators available. Of course, every gate can be used as an operator, for example op = GateZ(). However, MIMIQ also supports many non-unitary operators such as SigmaPlus, more about this on the Operators page.

To ask MIMIQ to compute the expectation value for a circuit you can create an ExpectationValue object and push() it to the circuit like this:

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


>>> # Ask to compute the expectation value
>>> ev = ExpectationValue(op)
>>> circuit.push(ev, 0, 0)
1-qubit, 1-zvar circuit with 2 instructions:
├── H @ q[0]
└── ⟨SigmaPlus(1)⟩ @ q[0], z[0]
As for all statistical operations, the arguments to give to the push() function always follow the order of quantum register index first, classical register second (none in this case), and Z-register index last.
In the example above, the first 0 is the index for the first qubit of the quantum register and the second 0 is the index of the Z-Register.

Notice that the expectation value will be computed with respect to the quantum state of the system at the point in the circuit where the ExpectationValue is added.

Entanglement

MIMIQ supports statistical operations on entanglement for ordered bipartitions. For instance, for qubits [1...N] MIMIQ can compute the entanglement between the bipartitions [1...k-1] and [k...N].

For this reason when you push() an entanglement operation to a circuit, you need to give it the qubit index k that separates the two bipartitions, as well as the Z-register to store the result.

Warning

The following functions can only be used with the MPS backend.

Von Neumann Entropy

Mathematical definition

The entanglement entropy for a bipartition into subsystems A and B is defined for a pure state \(\rho = | \psi \rangle\langle \psi\) | as

\[\mathcal{S}(\rho_A) = - \mathrm{Tr}(\rho_A \log_2 \rho_A) = - \mathrm{Tr}(\rho_B \log_2 \rho_B) = \mathcal{S}(\rho_B)\]

where \(\rho_A = \mathrm{Tr}_B(\rho)\) is the reduced density matrix. A product state has \(\mathcal{S}(\rho_A)=0\) and a maximally entangled state between A and B gives \(\mathcal{S}(\rho_A)=1\).

We only consider bipartitions where \(A=\{1,\ldots,k-1\}\) and \(B=\{k,\ldots,N\}\), for some k and where N is the total number of qubits.

Usage on MIMIQ

The entanglement entropy for a bipartition into subsystems A and B can be obtained using the VonNeumannEntropy function.
You do not need to provide any argument to VonNeumannEntropy. To indicate where to create the separation into two subsystems MIMIQ will use the first qubit index given to the push() function, here is an example:
>>> circuit = Circuit()
>>> # Asking to compute the Von Neumann entropy between the two subsystems separated between qubit 1 and 2
>>> circuit.push(VonNeumannEntropy(), 2, 0)
3-qubit, 1-zvar circuit with 1 instructions:
└── VonNeumannEntropy @ q[2], z[0]

Here we compute the Von neumann entropy between the two subsystems [1,2] and [3...N] and write the results into the index 0 of the Z-register.

Note

For k=1, A is empty and the Von Neumann entropy will always return 1.

Bond Dimension

Mathematical definition

The bond dimension is only defined for a matrix-product state (MPS), which can be written as (with \(i_1=i_{N+1}=1\))

\[|\psi \rangle = \sum_{s_1,s_2,\ldots=1}^2 \sum_{i_2}^{\chi_2} \sum_{i_3}^{\chi_3} \ldots \sum_{i_{N}}^{\chi_{N}} A^{(s_1)}_{i_1i_2} A^{(s_2)}_{i_2 i_3} A^{(s_3)}_{i_3 i_4} \ldots A^{(s_N)}_{i_{N}i_{N+1}} | s_1, s_2, s_3, \ldots, s_N \rangle .\]

Here, \(\chi_k`\) is the bond dimension, i.e. the dimension of the index \(i_k\). The first and last bond dimensions are dummies, \(chi_1=chi_{N+1}=1\). A bond dimension of 1 means there is no entanglement between the two halves of the system.

Usage on MIMIQ

To compute the bond dimension between two halves of a system you can use the BondDim operator and push() to the circuit like any entanglement measure:

>>> circuit = Circuit()
>>> # Asking to compute the BondDim between the second and third qubits
>>> circuit.push(BondDim(), 2, 0)
3-qubit, 1-zvar circuit with 1 instructions:
└── BondDim @ q[2], z[0]

Here we compute the bond dimension between the two subsystems [1,2] and [3...N] and write the results into index 0 of the Z-Register.

Note

For k=1 the bond dimension returned will always be 1.

Schmidt Rank

Mathematical definition

A Schmidt decomposition for a bipartition into subsystems A and B is defined for a pure state as

\[|\psi\rangle = \sum_{i=1}^{r} s_i |\alpha_i\rangle \otimes |\beta_i\rangle,\]

where \(|\alpha_i\rangle (|\beta_i\rangle)\) are orthonormal states acting on A (B). The Schmidt rank is the number of terms r in the sum. A product state gives r=1 and r>1 signals entanglement.

We only consider bipartitions where \(A=\{1,\ldots,k-1\}\) and \(B=\{k,\ldots,N\}\), for some k and where N is the total number of qubits.

Usage on MIMIQ

To compute the Schmidt rank of a bipartition you can use the SchmidtRank operator and push() like all entanglement measures:

>>> circuit = Circuit()
>>> # Asking to compute the Schmidt rank between the two subsystems separated between qubits 1 and 2
>>> circuit.push(SchmidtRank(), 2, 0)
3-qubit, 1-zvar circuit with 1 instructions:
└── SchmidtRank @ q[2], z[0]

Here we compute the Schmidt rank between the two subsystems [1,2] and [3...N] and write the results into index 0 of the Z-Register.

Note

For k=1, A is empty and the Schmidt rank will always return 1.

Amplitude

With MIMIQ you can extract quantum state amplitudes in the computational basis at any point in the circuit using Amplitude.
You will need to give the Amplitude function the BitString matching the state for which you want the amplitude.
For more information on BitString check the BitString documentation page.

You can add the Amplitude object to the circuit exactly like any other gate:

>>> mystery_circuit = Circuit()
>>> mystery_circuit.push(GateH(), range(0, 3))
3-qubit circuit with 3 instructions:
├── H @ q[0]
├── H @ q[1]
└── H @ q[2]


>>> # Define the Amplitude operator
>>> amp = Amplitude(BitString("101"))

>>> # Add the amplitude operator to the circuit and write the result in the first complex number of the Z-Register
>>> mystery_circuit.push(amp, 0)
3-qubit, 1-zvar circuit with 4 instructions:
├── H @ q[0]
├── H @ q[1]
├── H @ q[2]
└── Amplitude(bs"101") @ z[0]

This will extract the amplitude of the basis state \(\ket{101}\). When adding the amplitude operation you do not need to give it any specific qubit target, the only index needed is for the Z-register to use for storing the result.

Reference

class mimiqcircuits.ExpectationValue(op)[source]

Bases: Operation

Operation to compute and store the expectation value of an Operator in a z-register.

An expectation value for a pure state \(| \psi \rangle\) is defined as:

Expectation Value for Pure State

\[\langle O \rangle = \langle \psi | O | \psi \rangle\]

where \(O\) is an operator. With respect to a density matrix \(\rho\), it’s given by:

Expectation Value for Density Matrix

\[\langle O \rangle = \mathrm{Tr}(\rho O).\]

However, when using quantum trajectories to solve noisy circuits, the expectation value is computed with respect to the pure state of each trajectory.

The argument op can be any gate or non-unitary operator.

Note

ExpectationValue is currently restricted to one and two qubit operators.

See also

AbstractOperator, Gate

Examples

In push!, the first argument corresponds to the qubit, and the second to the z-register.

>>> from mimiqcircuits import *
>>> ExpectationValue(GateX())
⟨X⟩
>>> c = Circuit()
>>> c.push(ExpectationValue(GateX()), 1, 1)
2-qubit, 2-zvar circuit with 1 instruction:
└── ⟨X⟩ @ q[1], z[1]
>>> c.push(ExpectationValue(SigmaPlus()), 1, 2)
2-qubit, 3-zvar circuit with 2 instructions:
├── ⟨X⟩ @ q[1], z[1]
└── ⟨SigmaPlus(1)⟩ @ q[1], z[2]
__init__(op)[source]
opname()[source]
property qregsizes
property cregsizes
property zregsizes
inverse()[source]
power(_)[source]
static isunitary()[source]

Check if the class represents a unitary operator.

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

iswrapper()[source]
asciiwidth(qubits, bits=[], zvars=[])[source]

Calculate the width for ASCII drawing.

get_operation()[source]
getparams()[source]
evaluate(d)[source]
class mimiqcircuits.VonNeumannEntropy[source]

Bases: Operation

Operation to get the bipartite Von Neumann entanglement entropy and store it in a z-register.

The entanglement entropy for a bipartition into subsystems \(A\) and \(B\) is defined for a pure state \(\rho = | \psi \rangle\langle \psi |\) as:

Entanglement Entropy

\[\mathcal{S}(\rho_A) = - \mathrm{Tr}(\rho_A \log_2 \rho_A) = - \mathrm{Tr}(\rho_B \log_2 \rho_B) = \mathcal{S}(\rho_A)\]

where \(\rho_A = \mathrm{Tr}_B(\rho)\) is the reduced density matrix. A product state has \(\mathcal{S}(\rho_A)=0\) and a maximally entangled state between \(A\) and \(B\) gives \(\mathcal{S}(\rho_A)=1\).

We only consider bipartitions where \(A=\{1,\ldots,k-1\}\) and \(B=\{k,\ldots,N\}\), for some \(k\) and where \(N\) is the total number of qubits.

When the system is open (i.e., with noise) and we are using quantum trajectories, the entanglement entropy of each trajectory is returned during execution.

See also

BondDim, SchmidtRank

Examples

When pushing to a circuit, the qubit index k takes the role of the above bipartition into A and B. For k=1, A is empty and the entanglement entropy will always return 0.

>>> from mimiqcircuits import *
>>> k = 5
>>> c = Circuit()
>>> c.push(VonNeumannEntropy(), k, 1)
6-qubit, 2-zvar circuit with 1 instruction:
└── VonNeumannEntropy @ q[5], z[1]
__init__()[source]
property num_qubits
property parnames
iswrapper()[source]
inverse()[source]
power(p)[source]
control(num_qubits)[source]
static isunitary()[source]

Check if the class represents a unitary operator.

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

class mimiqcircuits.BondDim[source]

Bases: Operation

Operation to get the bond dimension between two halves of the system and store it in a z-register.

The bond dimension is only defined for a matrix-product state (MPS), which can be written as:

State Representation

\[|\psi \rangle = \sum_{s_1,s_2,\ldots=1}^2 \sum_{i_1}^{\chi_1} \sum_{i_2}^{\chi_2} \ldots \sum_{i_N}^{\chi_N} A^{(s_1)}_{i_0i_1} A^{(s_2)}_{i_1 i_2} A^{(s_3)}_{i_2 i_3} \ldots A^{(s_N)}_{i_{N-1}i_N} | s_1, s_2, s_3, \ldots, s_N \rangle .\]

Here, \(\chi_k\) is the bond dimension, i.e., the dimension of the index \(i_k\). The first and last bond dimensions are dummies, \(\chi_0=\chi_N=1\). A bond dimension of 1 means there is no entanglement between the two halves of the system.

See also

VonNeumannEntropy, SchmidtRank

Examples

When pushing to a circuit, the qubit index k that we give will return the bond dimension \(i_{k-1}\) in the above notation. In other words, we associate link k with qubit k+1. For k=1, the bond dimension returned will always be 1.

>>> from mimiqcircuits import *
>>> k = 5
>>> c = Circuit()
>>> c.push(BondDim(), k, 1)
6-qubit, 2-zvar circuit with 1 instruction:
└── BondDim @ q[5], z[1]
__init__()[source]
property num_qubits
property parnames
iswrapper()[source]
inverse()[source]
power(p)[source]
control(num_qubits)[source]
static isunitary()[source]

Check if the class represents a unitary operator.

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

class mimiqcircuits.SchmidtRank[source]

Bases: Operation

Operation to get the Schmidt rank of a bipartition and store it in a z-register.

A Schmidt decomposition for a bipartition into subsystems \(A\) and \(B\) is defined for a pure state as:

Schmidt Decomposition

\[|\psi\rangle = \sum_{i=1}^{r} s_i |\alpha_i\rangle \otimes |\beta_i\rangle,\]

where \(|\alpha_i\rangle\) (\(|\beta_i\rangle\)) are orthonormal states acting on \(A\) (\(B\)). The Schmidt rank is the number of terms \(r\) in the sum. A product state gives \(r=1\), and \(r>1\) signals entanglement.

We only consider bipartitions where \(A=\{1,\ldots,k-1\}\) and \(B=\{k,\ldots,N\}\), for some \(k\) and where \(N\) is the total number of qubits.

In MPS (Matrix Product States), when the state is optimally compressed, the Schmidt rank should be equal to the bond dimension (see BondDim).

See also

VonNeumannEntropy, BondDim

Examples

When pushing to a circuit, the qubit index k takes the role of the above bipartition into A and B. For k=1, A is empty and the Schmidt rank will always return 1.

>>> from mimiqcircuits import *
>>> k = 5
>>> c = Circuit()
>>> c.push(SchmidtRank(), k, 1)
6-qubit, 2-zvar circuit with 1 instruction:
└── SchmidtRank @ q[5], z[1]
__init__()[source]
property num_qubits
property parnames
iswrapper()[source]
inverse()[source]
power(p)[source]
control(num_qubits)[source]
static isunitary()[source]

Check if the class represents a unitary operator.

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

class mimiqcircuits.Amplitude(bs)[source]

Bases: Operation

Amplitude operation

multi qubit Amplitude operation in the computational basis

The operation projects the quantum states complex variables and stores in a z-register.

Examples

>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(Amplitude(BitString(2)),0)
1-zvar circuit with 1 instruction:
└── Amplitude(bs"00") @ z[0]
__init__(bs)[source]
property zregsizes
iswrapper()[source]
inverse()[source]
static isunitary()[source]

Check if the class represents a unitary operator.

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