Source code for mimiqcircuits.operations.losschannel

#
# Copyright © 2023-2025 QPerfect. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Loss-channel operations."""

from __future__ import annotations

from mimiqcircuits.operations.operation import Operation
from mimiqcircuits.symbolics import UndefinedValue, unwrapvalue


def _validate_probability(p):
    try:
        value = unwrapvalue(p)
    except UndefinedValue:
        return

    if isinstance(value, complex):
        if value.imag != 0:
            raise ValueError("Loss probability p must be real.")
        value = value.real

    if not (0 <= value <= 1):
        raise ValueError("Loss probability p must be between 0 and 1.")


[docs] class QubitLoss(Operation): """QubitLoss operation. This operation deterministically marks a qubit as lost. It is useful for representing qubit-loss events explicitly in a circuit. .. warning:: This operation is non-reversible. Examples: >>> from mimiqcircuits import * >>> c = Circuit() >>> c.push(QubitLoss(), 1) 2-qubit circuit with 1 instruction: └── QubitLoss @ q[1] <BLANKLINE> See Also: :class:`QubitReload`, :class:`LossErr`, :class:`CheckLoss`, :class:`MeasureCheckLoss` """ _name = "QubitLoss" _num_qubits = 1 _num_bits = 0 _num_zvars = 0 _num_qregs = 1 _qregsizes = [1]
[docs] def inverse(self): raise TypeError("QubitLoss is not inversible")
[docs] def power(self, p): raise TypeError("QubitLoss^p is not defined.")
[docs] def control(self, num_qubits): raise TypeError("Controlled QubitLoss is not defined.")
[docs] def iswrapper(self): return False
def __str__(self): return self._name
[docs] class QubitReload(Operation): """QubitReload operation. This operation reloads a qubit that was previously marked as lost. It can be used to model qubit reintroduction after a loss event. .. warning:: This operation is non-reversible. Examples: >>> from mimiqcircuits import * >>> c = Circuit() >>> c.push(QubitReload(), 1) 2-qubit circuit with 1 instruction: └── QubitReload @ q[1] <BLANKLINE> See Also: :class:`QubitLoss`, :class:`LossErr`, :class:`CheckLoss`, :class:`MeasureCheckLoss` """ _name = "QubitReload" _num_qubits = 1 _num_bits = 0 _num_zvars = 0 _num_qregs = 1 _qregsizes = [1]
[docs] def inverse(self): raise TypeError("QubitReload is not inversible")
[docs] def power(self, p): raise TypeError("QubitReload^p is not defined.")
[docs] def control(self, num_qubits): raise TypeError("Controlled QubitReload is not defined.")
[docs] def iswrapper(self): return False
def __str__(self): return self._name
[docs] class LossErr(Operation): """LossErr operation. This operation models a probabilistic qubit-loss event with probability ``p``. During simulation or sampling, it can be resolved into a concrete qubit-loss event according to the specified probability. .. warning:: This operation is non-reversible. Args: p: The probability of a qubit-loss event. It must be real and between 0 and 1. Examples: >>> from mimiqcircuits import * >>> c = Circuit() >>> c.push(LossErr(0.5), 0) 1-qubit circuit with 1 instruction: └── LossErr(0.5) @ q[0] <BLANKLINE> Seeded sampling makes stochastic loss reproducible: >>> import random >>> c = Circuit() >>> c.push(LossErr(0.5), 1) 2-qubit circuit with 1 instruction: └── LossErr(0.5) @ q[1] <BLANKLINE> >>> c.push(GateH(), 1) 2-qubit circuit with 2 instructions: ├── LossErr(0.5) @ q[1] └── H @ q[1] <BLANKLINE> >>> c.sample_losses(random.Random(0)) 2-qubit circuit with 1 instruction: └── H @ q[1] <BLANKLINE> >>> c.sample_losses(random.Random(7)) 2-qubit circuit with 1 instruction: └── QubitLoss @ q[1] <BLANKLINE> Symbolic probabilities must be evaluated before sampling: >>> from symengine import Symbol >>> p = Symbol("p") >>> c = Circuit() >>> c.push(LossErr(p), 0) 1-qubit circuit with 1 instruction: └── LossErr(p) @ q[0] <BLANKLINE> >>> c = c.evaluate({p: 0.5}) >>> c.sample_losses(random.Random(7)) 1-qubit circuit with 1 instruction: └── QubitLoss @ q[0] <BLANKLINE> Loss-model rules can rewrite gates that touch surviving qubits: >>> c = Circuit() >>> c.push(QubitLoss(), 1) 2-qubit circuit with 1 instruction: └── QubitLoss @ q[1] <BLANKLINE> >>> c.push(GateCX(), 0, 1) 2-qubit circuit with 2 instructions: ├── QubitLoss @ q[1] └── CX @ q[0], q[1] <BLANKLINE> >>> lm = LossModel().add_replace(GateCX(), Depolarizing1(0.2)) >>> c.sample_losses(lm) 2-qubit circuit with 2 instructions: ├── QubitLoss @ q[1] └── Depolarizing(0.2) @ q[0] <BLANKLINE> See Also: :class:`QubitLoss`, :class:`QubitReload`, :class:`CheckLoss`, :class:`MeasureCheckLoss`, :class:`mimiqcircuits.lossmodel.LossModel` """ _name = "LossErr" _num_qubits = 1 _num_bits = 0 _num_zvars = 0 _num_qregs = 1 _qregsizes = [1] _parnames = ("p",)
[docs] def __init__(self, p): self.p = p _validate_probability(p) super().__init__()
[docs] def evaluate(self, d={}): evaluated_p = self.p.subs(d) if hasattr(self.p, "subs") else self.p try: numeric_p = unwrapvalue(evaluated_p) except UndefinedValue: return LossErr(evaluated_p) if isinstance(numeric_p, complex): if numeric_p.imag != 0: raise ValueError("Loss probability p must be real after evaluation.") numeric_p = numeric_p.real return LossErr(numeric_p)
[docs] def inverse(self): raise TypeError("LossErr is not inversible")
[docs] def power(self, p): raise TypeError("LossErr^p is not defined.")
[docs] def control(self, num_qubits): raise TypeError("Controlled LossErr is not defined.")
[docs] def iswrapper(self): return False
def __str__(self): return f"{self._name}({self.p})"
[docs] class CheckLoss(Operation): """CheckLoss operation. This operation checks whether a qubit is present or lost, and stores the result in a classical bit. .. warning:: This operation is non-reversible. Examples: >>> from mimiqcircuits import * >>> c = Circuit() >>> c.push(CheckLoss(), 0, 0) 1-qubit, 1-bit circuit with 1 instruction: └── CL @ q[0], c[0] <BLANKLINE> See Also: :class:`QubitLoss`, :class:`QubitReload`, :class:`LossErr`, :class:`MeasureCheckLoss` """ _name = "CL" _num_qubits = 1 _num_bits = 1 _num_zvars = 0 _num_qregs = 1 _num_cregs = 1 _qregsizes = [1] _cregsizes = [1]
[docs] def inverse(self): raise TypeError("CheckLoss is not inversible")
[docs] def power(self, p): raise TypeError("CheckLoss^p is not defined.")
[docs] def control(self, num_qubits): raise TypeError("Controlled CheckLoss is not defined.")
[docs] def iswrapper(self): return False
def __str__(self): return self._name
[docs] class MeasureCheckLoss(Operation): """MeasureCheckLoss operation. This operation measures a qubit and reports both the measurement result and whether the qubit is present or lost. .. warning:: This operation is non-reversible. Examples: >>> from mimiqcircuits import * >>> c = Circuit() >>> c.push(MeasureCheckLoss(), 0, 0, 1) 1-qubit, 2-bit circuit with 1 instruction: └── MCL @ q[0], c[0:1] <BLANKLINE> See Also: :class:`QubitLoss`, :class:`QubitReload`, :class:`LossErr`, :class:`CheckLoss` """ _name = "MCL" _num_qubits = 1 _num_bits = 2 _num_zvars = 0 _num_qregs = 1 _num_cregs = 1 _qregsizes = [1] _cregsizes = [2]
[docs] def inverse(self): raise TypeError("MeasureCheckLoss is not inversible")
[docs] def power(self, p): raise TypeError("MeasureCheckLoss^p is not defined.")
[docs] def control(self, num_qubits): raise TypeError("Controlled MeasureCheckLoss is not defined.")
[docs] def iswrapper(self): return False
def __str__(self): return self._name
__all__ = [ "QubitLoss", "QubitReload", "LossErr", "CheckLoss", "MeasureCheckLoss", ]