#
# 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
import warnings
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 Loss(Operation):
"""Loss operation.
A qubit-loss event: the qubit is lost with probability ``p``. ``Loss()`` is
``Loss(1.0)``, a certain loss. It replaces both ``LossErr`` and
``QubitLoss``.
The probability is resolved by :meth:`mimiqcircuits.Circuit.sample_losses`,
which turns each ``Loss(p)`` into ``Loss(1.0)`` or removes it, and a circuit
is turned into a runnable, loss-free form by
:meth:`mimiqcircuits.Circuit.resolve_losses`.
.. warning::
This operation is non-reversible.
Args:
p: The loss probability, real and between 0 and 1. Defaults to 1.0.
Examples:
>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(Loss(0.1), 0)
1-qubit circuit with 1 instruction:
└── Loss(0.1) @ q[0]
<BLANKLINE>
>>> c.push(Loss(), 1)
2-qubit circuit with 2 instructions:
├── Loss(0.1) @ q[0]
└── Loss(1.0) @ q[1]
<BLANKLINE>
See Also:
:class:`Reload`, :class:`Check`, :class:`MeasureCheck`
"""
_name = "Loss"
_num_qubits = 1
_num_bits = 0
_num_zvars = 0
_num_qregs = 1
_qregsizes = [1]
_parnames = ("p",)
[docs]
def __init__(self, p=1.0):
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 Loss(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 Loss(numeric_p)
[docs]
def inverse(self):
raise TypeError("Loss is not inversible")
[docs]
def power(self, p):
raise TypeError("Loss^p is not defined.")
[docs]
def control(self, num_qubits):
raise TypeError("Controlled Loss is not defined.")
[docs]
def iswrapper(self):
return False
def __str__(self):
return f"{self._name}({self.p})"
[docs]
class Reload(Operation):
"""Reload operation.
Re-initialise a qubit to :math:`|0\\rangle` and mark it present. By default
a reload always resets, regardless of whether the qubit was lost. It
replaces ``QubitReload``.
.. warning::
This operation is non-reversible.
Examples:
>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(Reload(), 0)
1-qubit circuit with 1 instruction:
└── Reload @ q[0]
<BLANKLINE>
See Also:
:class:`Loss`, :class:`Reset`
"""
_name = "Reload"
_num_qubits = 1
_num_bits = 0
_num_zvars = 0
_num_qregs = 1
_qregsizes = [1]
[docs]
def inverse(self):
raise TypeError("Reload is not inversible")
[docs]
def power(self, p):
raise TypeError("Reload^p is not defined.")
[docs]
def control(self, num_qubits):
raise TypeError("Controlled Reload is not defined.")
[docs]
def iswrapper(self):
return False
def __str__(self):
return self._name
[docs]
class Check(Operation):
"""Check operation.
Record whether a qubit is present into a classical bit (1 present, 0 lost).
Like :class:`Measure` records a qubit's value, ``Check`` records its
presence; it does not touch the quantum state. It replaces ``CheckLoss``.
.. warning::
This operation is non-reversible.
Examples:
>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(Check(), 0, 0)
1-qubit, 1-bit circuit with 1 instruction:
└── Check @ q[0], c[0]
<BLANKLINE>
See Also:
:class:`MeasureCheck`, :class:`Loss`, :class:`Reload`
"""
_name = "Check"
_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("Check is not inversible")
[docs]
def power(self, p):
raise TypeError("Check^p is not defined.")
[docs]
def control(self, num_qubits):
raise TypeError("Controlled Check is not defined.")
[docs]
def iswrapper(self):
return False
def __str__(self):
return self._name
[docs]
class MeasureCheck(Operation):
"""MeasureCheck operation.
Measure a qubit if it is present, and record its presence. The first bit is
the measurement result (0 if lost), the second is the presence (1 present,
0 lost). The state is collapsed only when the qubit is present. It replaces
``MeasureCheckLoss``.
.. warning::
This operation is non-reversible.
Examples:
>>> from mimiqcircuits import *
>>> c = Circuit()
>>> c.push(MeasureCheck(), 0, 0, 1)
1-qubit, 2-bit circuit with 1 instruction:
└── MeasureCheck @ q[0], c[0:1]
<BLANKLINE>
See Also:
:class:`Check`, :class:`Measure`, :class:`Loss`
"""
_name = "MeasureCheck"
_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("MeasureCheck is not inversible")
[docs]
def power(self, p):
raise TypeError("MeasureCheck^p is not defined.")
[docs]
def control(self, num_qubits):
raise TypeError("Controlled MeasureCheck is not defined.")
[docs]
def iswrapper(self):
return False
def __str__(self):
return self._name
# Deprecated loss operations. The old names map onto the current ones.
[docs]
def LossErr(p):
warnings.warn("LossErr is deprecated; use Loss(p).", DeprecationWarning, stacklevel=2)
return Loss(p)
[docs]
def QubitLoss():
warnings.warn("QubitLoss is deprecated; use Loss().", DeprecationWarning, stacklevel=2)
return Loss(1.0)
[docs]
def QubitReload():
warnings.warn("QubitReload is deprecated; use Reload().", DeprecationWarning, stacklevel=2)
return Reload()
[docs]
def CheckLoss():
warnings.warn("CheckLoss is deprecated; use Check().", DeprecationWarning, stacklevel=2)
return Check()
[docs]
def MeasureCheckLoss():
warnings.warn("MeasureCheckLoss is deprecated; use MeasureCheck().", DeprecationWarning, stacklevel=2)
return MeasureCheck()