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
If the outcome \(k\) is observed, the system is left in the state
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, MeasurePauli.
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]
MeasurePauli measures a multi-qubit Pauli product observable, such as \(X_0 Z_2\), and writes a single outcome bit (0 for eigenvalue \(+1\), 1 for \(-1\)). It targets one qubit per character of the Pauli string; qubits marked I are not measured:
>>> circuit = Circuit()
>>> circuit.push(MeasurePauli("XIZ"), 0, 1, 2, 0)
3-qubit, 1-bit circuit with 1 instruction:
└── MP(XIZ) @ q[0:2], c[0]
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, and PrepareStateT, which leaves the qubit in the magic state \(T\ket{+}\).
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).
The body of an IfStatement is allowed to write to one of the bits used in the condition. The classical-target layout is [op_bits..., condition_bits...], so when the body and the condition share a bit you simply repeat that bit index in the push() call. See the Target aliasing section below for the rules.
While statements¶
A while statement applies an operation repeatedly while a classical register matches a given pattern. It is the loop counterpart of IfStatement and resembles a classical while loop.
In MIMIQ you can implement it using WhileStatement, which takes the same two arguments as IfStatement: an operation to apply each iteration and a BitString acting as the loop condition.
>>> WhileStatement(Not(), BitString('1'))
WHILE(c==1) !
WhileStatement is added to a circuit with push() using the same target layout as IfStatement: quantum targets first, then the body’s classical bits, then the condition bits. For example, the loop below decrements c[0] while it is set:
>>> circuit = Circuit()
>>> circuit.push(WhileStatement(Not(), BitString('1')), 0, 0)
1-bit circuit with 1 instruction:
└── WHILE(c==1) ! @ c[0], condition[0]
For the loop to make progress, the body must mutate at least one bit that appears in the condition — otherwise the register never changes and the loop runs forever. Because of this, WhileStatement explicitly allows the body’s bit targets to overlap (alias) the condition’s bit targets, even though most operations forbid repeated bit targets (see Target aliasing below).
Warning
There is no built-in iteration cap: a non-terminating circuit is the user’s responsibility, exactly as in any classical language. Make sure the body can falsify the condition.
Note
As with IfStatement, the inner operation must currently be unitary.
Target aliasing¶
Most operations require their qubit, classical-bit, and z-variable targets to be all distinct within a single instruction — pushing the same index twice raises an error. Two exceptions:
Qubits are always required to be unique. This reflects the no-cloning theorem and is never relaxed.
Bits and z-variables can be reused by operations that opt in via the
allow_bit_aliasing()andallow_zvar_aliasing()class-level flags.
Operations that opt into bit aliasing today are And, Or, Xor, IfStatement, and WhileStatement. For example, you can read and write the same bit in a single classical operation:
>>> circuit = Circuit()
>>> # c[0] = c[0] AND c[1]
>>> circuit.push(And(), 0, 0, 1)
2-bit circuit with 1 instruction:
└── c[0] &= c[1]
Or use a bit both as the condition and as the body’s target inside an IfStatement:
>>> circuit = Circuit()
>>> # Toggle c[0] when c[0] == 1 (the condition bit is also the body's target).
>>> circuit.push(IfStatement(Not(), BitString('1')), 0, 0)
1-bit circuit with 1 instruction:
└── IF(c==1) ! @ c[0], condition[0]
Operations that opt into z-variable aliasing today are Add, Multiply, and Pow — see the Z-register operations page for examples like z[0] += z[0] + z[1].
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:
AbstractMeasurementMeasure 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]
- supports_canonical_rewrite()[source]
Whether CanonicalRewrite may decompose this operation.
Default is conservative: unsupported operations must opt in. This keeps “non-terminal” separate from “canonically rewriteable”.
- power(p)[source]
- control(num_qubits)[source]
- iswrapper()[source]
- get_operation()[source]
- class mimiqcircuits.MeasurePauli(pauli)[source]
Bases:
AbstractMeasurementPauli product measurement.
Projective measurement of the \(N\)-qubit observable \(P = P_1 \otimes P_2 \otimes \dots \otimes P_N\), with each \(P_i \in \{I, X, Y, Z\}\).
The instruction targets \(N\) qubits and one classical bit. The state is projected onto the \(+1\) or \(-1\) eigenspace of \(P\) and the bit records the outcome:
0for \(+1\),1for \(-1\). Qubits markedIare targets of the instruction but are not measured.To measure \(-P\), measure \(P\) and flip the bit with
Not.- Parameters:
pauli (PauliString or str) – the observable, as a
PauliStringor a string ofI,X,Y,Z.- Raises:
TypeError – if
pauliis neither aPauliStringnor astr.ValueError – if the string is empty, contains a character other than
I,X,Y,Z, or is the identity.
See also
PauliString,RPauli,MeasureZZ,MeasureXX,MeasureYY,pauli_based_computation()Examples
Measure \(X_0 Z_2\) on qubits 0 to 2 and store the outcome in bit 0. Qubit 1 is a target, but is left untouched:
>>> from mimiqcircuits import * >>> MeasurePauli("XIZ") MP(XIZ) >>> c = Circuit() >>> c.push(MeasurePauli("XIZ"), 0, 1, 2, 0) 3-qubit, 1-bit circuit with 1 instruction: └── MP(XIZ) @ q[0:2], c[0]
Decomposition:
Each measured qubit is rotated to the Z basis (
HforX,SDGthenHforY), CX gates collect the parity of the measured qubits on the last one, that qubit is measured, and the CX gates and rotations are undone:>>> decompose_step(MeasurePauli("XZ")) 2-qubit, 1-bit circuit with 5 instructions: ├── H @ q[0] ├── CX @ q[0], q[1] ├── M @ q[1], c[0] ├── CX @ q[0], q[1] └── H @ q[0]
Only
H,S,SDG,CXandMeasureappear, soQASMBasisdecomposes it.StimBasiskeeps it, as Stim measures Pauli products natively (MPP), and so doesCliffordTBasis.- __init__(pauli)[source]
- pstring()[source]
Return the Pauli string as a plain
str.
- inverse()[source]
- power(p)[source]
- control(num_qubits)[source]
- iswrapper()[source]
- get_operation()[source]
- asciiwidth(qubits, bits, zvars)[source]
- class mimiqcircuits.Reset[source]
Bases:
krauschannelReset 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]
- class mimiqcircuits.PrepareStateT[source]
Bases:
krauschannelPrepare one qubit in the magic state.
Prepares the magic state \(T\ket{+} = (\ket{0} + e^{i\pi/4}\ket{1})/\sqrt{2}\), whatever the previous state of the qubit.
A magic state is the resource consumed by a \(\pi/8\) Pauli rotation when the rotation is carried out with Pauli product measurements (see
pauli_based_computation()). Fault-tolerant architectures produce these states separately, by distillation, so a dedicated operation lets them be counted and scheduled.The operation is a Kraus channel with operators \(T\ket{+}\bra{0}\) and \(T\ket{+}\bra{1}\), and is equivalent to
Reset,GateH,GateT. It cannot be inverted, raised to a power or controlled.Examples
>>> from mimiqcircuits import * >>> PrepareStateT() PrepareStateT >>> c = Circuit() >>> c.push(PrepareStateT(), 0) 1-qubit circuit with 1 instruction: └── PrepareStateT @ q[0]
The decomposition resets the qubit to \(\ket{0}\), then applies
HandT:>>> PrepareStateT().decompose() 1-qubit circuit with 3 instructions: ├── Reset @ q[0] ├── H @ q[0] └── T @ q[0]
See also
Reset,ResetX,MeasurePauli- inverse()[source]
- power(pwr)[source]
- control(num_qubits)[source]
- iswrapper()[source]
- supports_canonical_rewrite()[source]
Whether CanonicalRewrite may decompose this operation.
Default is conservative: unsupported operations must opt in. This keeps “non-terminal” separate from “canonically rewriteable”.
- class mimiqcircuits.MeasureReset[source]
Bases:
AbstractMeasurementMeasureReset operation.
Measures a qubit in the Z basis, stores the outcome in a classical bit, then applies
GateXto the qubit if the outcome is 1, which leaves it in the \(\ket{0}\) state. It decomposes intoMeasurefollowed by anIfStatementofGateXon that bit.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 2 instructions: ├── M @ q[1], c[0] └── IF(c==1) U(pi, 0, pi, 0.0) @ q[1], condition[0]
- inverse()[source]
- power(p)[source]
- control(num_qubits)[source]
- iswrapper()[source]
- get_operation()[source]
- class mimiqcircuits.IfStatement(operation, bitstring)[source]
Bases:
OperationConditional 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]
- supports_canonical_rewrite()[source]
Whether CanonicalRewrite may decompose this operation.
Default is conservative: unsupported operations must opt in. This keeps “non-terminal” separate from “canonically rewriteable”.
- 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.WhileStatement(operation, bitstring)[source]
Bases:
OperationConditional loop operation.
Repeatedly applies the provided operation while the classical register matches the specified
BitString.WhileStatementis the loop counterpart ofIfStatement. The body operationopis executed once for every iteration in which the classical register’s state equals theBitString. For the loop to terminate,opmust mutate at least one of the bits used in the condition;WhileStatementallows body bits to alias condition bits, which is the only way to make progress.There is no built-in iteration cap — a non-terminating circuit is the user’s responsibility, mirroring how every classical language treats
while.Examples
>>> from mimiqcircuits import * >>> c = Circuit() >>> c.push(WhileStatement(Not(), BitString('1')), 0, 0) 1-bit circuit with 1 instruction: └── WHILE(c==1) ! @ c[0], condition[0]
See also
IfStatement
- __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]
- evaluate(d)[source]
- get_operation()[source]
- get_bitstring()[source]
- asciiwidth(qubits, bits, zvars)[source]
- class mimiqcircuits.Operator(mat)[source]
Bases:
AbstractOperatorN-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:
mat (symengine.Matrix | sympy.Matrix | 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]
Numeric matrix representation as a
numpy.ndarray[complex128].Simulators and anything else that wants raw numbers (e.g. TensorWeaver) should call this instead of
matrix(). It bypasses SymEngine entirely for gates that override_matrix_numeric()— seemimiqcircuits.numerics— and otherwise falls back to building the SymEngine matrix and casting it.Operators fully determined by their type cache the result at class level as a read-only array.
- Raises:
UnexpectedSymbolics – If any parameter is still symbolic.
- Returns:
complex128matrix.- Return type:
- __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).
- class mimiqcircuits.Kraus(E, rtol=1e-12)[source]
Bases:
krauschannelKraus(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.
A Kraus channel becomes loss-aware simply by including one or more
LossyOperatorbranches inE. In that case,hasloss()returnsTrueandlossoperators(),survivaloperators(), andlosseffect()can be used to inspect the leakage structure.The entries of a
LossyOperatorare amplitudes, not probabilities. The corresponding loss probabilities appear inlosseffect(), which sums \(L_k^\dagger L_k\) over all lossy branches \(L_k\).See also
MixedUnitaryGateCustomLossyOperatorOperator- 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]
# Example 4: Loss-aware Kraus with a LossyOperator branch
>>> lossy = LossyOperator(Matrix([[0, 0], [0, sqrt(0.2)]])) >>> survival = Operator(Matrix([[1, 0], [0, sqrt(0.8)]])) >>> lk = Kraus([survival, lossy]) >>> lk.hasloss() True >>> lk.lossoperators() [1-qubit LossyOperator (lossy=(1,)): ├── 0 0 └── 0 0.447213595499958] >>> lk.survivaloperators() [1-qubit Operator: ├── 1 0 └── 0 0.894427190999916] >>> lk.losseffect() 1-qubit Operator: ├── 0.0 + 0.0*I 0.0 + 0.0*I └── 0.0 + 0.0*I 0.2 + 0.0*I
# Example 5: Symbolic Kraus evaluation
>>> x = Symbol("x") >>> g = Kraus([Projector0(), Projector1(x)]) >>> g Kraus(P₀(1), P₁(x)) >>> g.evaluate({x: 1}) Kraus(P₀(1), P₁(1))
- __init__(E, rtol=1e-12)[source]
- evaluate(values)[source]
Evaluates symbolic parameters in Kraus matrices using a dictionary of values.
- krausoperators()[source]
Returns the Kraus operators.
- hasloss()[source]
Return True when the channel contains any LossyOperator branch.
- lossoperators()[source]
Return the LossyOperator branches of the channel.
- survivaloperators()[source]
Return the non-lossy branches of the channel.
- losseffect(tol=1e-12)[source]
Compute the loss-probability operator over all lossy branches.
This returns the operator
\[\Lambda = \sum_k L_k^\dagger L_k,\]where \(L_k\) runs over the
LossyOperatorbranches of the channel.\(\Lambda\) is a positive semidefinite operator whose diagonal entries give the loss probabilities for each computational basis state. For a basis state \(|i\rangle\),
\[\langle i|\Lambda|i\rangle\]is the probability that \(|i\rangle\) leaks out of the computational subspace. For a general state \(|\psi\rangle\), the total loss probability is \(\langle\psi|\Lambda|\psi\rangle\).
Note
The entries of
LossyOperatorare amplitudes, not probabilities.losseffect()converts them into probabilities by summing \(L_k^\dagger L_k\).The survival and lossy operators together satisfy
\[\sum_k S_k^\dagger S_k + \Lambda = I,\]where \(S_k\) are the non-lossy branches.
If the channel has no lossy operators, the zero matrix is returned.
See also
Kraus,lossoperators(),survivaloperators()Examples
>>> from mimiqcircuits import * >>> from symengine import Matrix, sqrt >>> g = Kraus([Matrix([[1, 0], [0, sqrt(0.9)]]), ... LossyOperator(Matrix([[0, 0], [0, sqrt(0.1)]]))]) >>> g.losseffect() 1-qubit Operator: ├── 0.0 + 0.0*I 0.0 + 0.0*I └── 0.0 + 0.0*I 0.1 + 0.0*I
- __str__()[source]
String representation, listing either AbstractOperators or matrix slices.
- unwrappedkrausmatrices()[source]
Returns the unwrapped Kraus matrices.
- class mimiqcircuits.DiagonalOp(a, b)[source]
Bases:
AbstractOperatorOne-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:
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
- opsquared()[source]
Return the operator with each parameter squared.
- class mimiqcircuits.SigmaPlus(a=1)[source]
Bases:
AbstractOperatorOne-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]
- class mimiqcircuits.SigmaMinus(a=1)[source]
Bases:
AbstractOperatorOne-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]