Non-unitary Operations ======================== Contrary to :doc:`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. .. doctest:: quick_start :hide: >>> from mimiqcircuits import * >>> import numpy as np >>> import os >>> import math >>> conn = MimiqConnection(url="https://mimiqfast.qperfect.io") >>> conn.connect(os.getenv("MIMIQUSER"), os.getenv("MIMIQPASS")) MimiqConnection: ├── url: https://mimiqfast.qperfect.io ├── Computing time: 0/10000 minutes ├── Max time limit per request: 180 minutes ├── Default time limit is equal to max time limit: 180 minutes └── status: open .. note:: As a rule of thumb all non-unitary operations can be added to the circuit using the function :meth:`~mimiqcircuits.Circuit.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 :doc:`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 :class:`~mimiqcircuits.Measure` operations placed at the very end of a circuit. To learn more about this head to the :ref:`simulation ` page. .. note:: Some features of unitary gates are not available for non-unitary operations, for instance, :func:`~mimiqcircuits.matrix`, :func:`~mimiqcircuits.inverse`, :func:`~mimiqcircuits.power`, :func:`~mimiqcircuits.control`, :func:`~mimiqcircuits.parallel`. .. _Measure: Measure ------------------------------------------------------------------ Mathematical definition ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Measurements are defined by a basis of projection operators :math:`P_k`, one for each different possible outcome :math:`k`. The probability :math:`p_k` of measuring outcome :math:`k` is given by the expectation value of :math:`P_k`, that is .. math:: p_k = \bra{\psi} P_k \ket{\psi}. If the outcome :math:`k` is observed, the system is left in the state .. math:: \frac{P_k\ket{\psi}}{\sqrt{p_k}}. It is common to measure in the Z basis (:math:`P_0=\ket{0}\bra{0}` and :math:`P_1=\ket{1}\bra{1}`), but measurements in other bases are possible too. How to use measurements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Available measurement operations: :class:`~mimiqcircuits.Measure`, :class:`~mimiqcircuits.MeasureX`, :class:`~mimiqcircuits.MeasureY`, :class:`~mimiqcircuits.MeasureZ`, :class:`~mimiqcircuits.MeasureXX`, :class:`~mimiqcircuits.MeasureYY`, :class:`~mimiqcircuits.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 (:class:`~mimiqcircuits.Measure`...). You can add it to the circuit like gates using :meth:`~mimiqcircuits.Circuit.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): .. doctest:: non_unitary >>> circuit = Circuit() >>> circuit.push(Measure(), 0, 0) 1-qubit, 1-bit circuit with 1 instructions: └── M @ q[0], c[0] This will add a :func:`~mimiqcircuits.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 :ref:`registers` section. You can also use iterators to Measure multiple qubits at once, as for gates: .. doctest:: non_unitary >>> 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 :doc:`simulation ` page. .. _Reset: Reset ------------------------------------------------------------------ Available reset operations: :class:`~mimiqcircuits.Reset`, :class:`~mimiqcircuits.ResetX`, :class:`~mimiqcircuits.ResetY`, :class:`~mimiqcircuits.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, :func:`~mimiqcircuits.Reset` leaves all qubits in :math:`\ket{0}` (by measuring in :math:`Z` and flipping the state if the outcome is `1`). Here is an example of how to add a reset operation to a circuit: .. doctest:: non_unitary >>> 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 :ref:`Measure Reset` section. Note that a reset operation can be technically seen as noise and is described by the same mathematical machinery, see :doc:`noise ` page. For this reason, some of the functionality provided by MIMIQ for noise is also available for resets. Here is one example: .. doctest:: non_unitary >>> Reset().krausoperators() [P₀(1.0), SigmaMinus(1.0)] .. _Measure Reset: Measure-Reset ------------------------------------------------------------------ Available measure-reset operations: :class:`~mimiqcircuits.MeasureReset`, :class:`~mimiqcircuits.MeasureResetX`, :class:`~mimiqcircuits.MeasureResetY`, :class:`~mimiqcircuits.MeasureResetZ`. A measure-reset operation is the same as a reset operation except that we store the result of the measurement, see :ref:`Measure` and :ref:`Reset` sections. Because of that, we need to specify both quantum and classical registers when adding it to a circuit: .. doctest:: non_unitary >>> 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 :class:`~mimiqcircuits.IfStatement`, which requires two arguments: an operation to apply and a :class:`~mimiqcircuits.BitString` as the condition (see :ref:`Bitstrings ` page for more information): .. doctest:: non_unitary >>> 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 :class:`~mimiqcircuits.IfStatement` to a circuit use the :meth:`~mimiqcircuits.Circuit.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: .. doctest:: non_unitary >>> 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 :doc:`circuit ` page). The body of an :class:`~mimiqcircuits.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 :meth:`~mimiqcircuits.Circuit.push` call. See the :ref:`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 :class:`~mimiqcircuits.IfStatement` and resembles a classical ``while`` loop. In MIMIQ you can implement it using :class:`~mimiqcircuits.WhileStatement`, which takes the same two arguments as :class:`~mimiqcircuits.IfStatement`: an operation to apply each iteration and a :class:`~mimiqcircuits.BitString` acting as the loop condition. .. doctest:: non_unitary >>> WhileStatement(Not(), BitString('1')) WHILE(c==1) ! :class:`~mimiqcircuits.WhileStatement` is added to a circuit with :meth:`~mimiqcircuits.Circuit.push` using the same target layout as :class:`~mimiqcircuits.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: .. doctest:: non_unitary >>> 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, :class:`~mimiqcircuits.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 :ref:`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 :class:`~mimiqcircuits.IfStatement`, the inner operation must currently be unitary. .. _Target aliasing: 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()`` and ``allow_zvar_aliasing()`` class-level flags. Operations that opt into bit aliasing today are :class:`~mimiqcircuits.And`, :class:`~mimiqcircuits.Or`, :class:`~mimiqcircuits.Xor`, :class:`~mimiqcircuits.IfStatement`, and :class:`~mimiqcircuits.WhileStatement`. For example, you can read and write the same bit in a single classical operation: .. doctest:: non_unitary >>> circuit = Circuit() >>> # c[0] = c[0] AND c[1] >>> circuit.push(And(), 0, 0, 1) 2-bit circuit with 1 instruction: └── c[0] = c[0] & c[1] Or use a bit both as the condition and as the body's target inside an :class:`~mimiqcircuits.IfStatement`: .. doctest:: non_unitary >>> 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 :class:`~mimiqcircuits.Add`, :class:`~mimiqcircuits.Multiply`, and :class:`~mimiqcircuits.Pow` — see the :doc:`Z-register operations ` page for examples like ``z[0] += z[0] + z[1]``. .. _Operators: 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 :math:`2^N \times 2^N` matrix can in principle represent an operator on :math:`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: :class:`~mimiqcircuits.Operator` Special operators: :class:`~mimiqcircuits.DiagonalOp`, :class:`~mimiqcircuits.SigmaPlus`, :class:`~mimiqcircuits.SigmaMinus`, :class:`~mimiqcircuits.Projector0`, :class:`~mimiqcircuits.ProjectorZ0`, :class:`~mimiqcircuits.Projector1`, :class:`~mimiqcircuits.ProjectorZ1`, :class:`~mimiqcircuits.ProjectorX0`, :class:`~mimiqcircuits.ProjectorX1`, :class:`~mimiqcircuits.ProjectorY0`, :class:`~mimiqcircuits.ProjectorY1`, :class:`~mimiqcircuits.Projector00`, :class:`~mimiqcircuits.Projector01`, :class:`~mimiqcircuits.Projector10`, :class:`~mimiqcircuits.Projector11` Methods available: :meth:`~mimiqcircuits.Gate;matrix`. How to use operators ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. doctest:: operators :hide: >>> from mimiqcircuits import * >>> import math Operators cannot be applied to a state directly (it cannot be added to a circuit using :meth:`~mimiqcircuits.Circuit.push`), because that would correspond to an unphysical transformation. However, they can be used within other operations such as :class:`~mimiqcircuits.ExpectationValue` or to create custom noise models with :class:`~mimiqcircuits.Kraus`, see :doc:`noise ` and :doc:`statistical operations ` pages. Operators can be used to compute expectation values as follows (see also :class:`~mimiqcircuits.ExpectationValue`): .. doctest:: operators >>> op = SigmaPlus() >>> ev = ExpectationValue(op) .. doctest:: operators >>> 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 :class:`~mimiqcircuits.Kraus`). For example, we can define the amplitude damping channel as follows: .. doctest:: operators >>> 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 .. doctest:: operators >>> 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 --------- .. autoclass:: mimiqcircuits.Measure :noindex: .. autoclass:: mimiqcircuits.Reset :noindex: .. autoclass:: mimiqcircuits.MeasureReset :noindex: .. autoclass:: mimiqcircuits.IfStatement :noindex: .. autoclass:: mimiqcircuits.WhileStatement :noindex: .. autoclass:: mimiqcircuits.Operator :noindex: .. autoclass:: mimiqcircuits.Kraus :noindex: .. autoclass:: mimiqcircuits.DiagonalOp :noindex: .. autoclass:: mimiqcircuits.SigmaPlus :noindex: .. autoclass:: mimiqcircuits.SigmaMinus :noindex: