#
# Copyright © 2022-2024 University of Strasbourg. All Rights Reserved.
# 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.
#
"""Conditional execution (IfStatement)."""
from mimiqcircuits.operations.operation import Operation
import mimiqcircuits as mc
from mimiqcircuits.instruction import _find_unit_range, _string_with_square
[docs]
class IfStatement(Operation):
"""Conditional 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]
<BLANKLINE>
>>> IfStatement(Parallel(4,GateH()), BitString("01"))
IF (c==01) ⨷ ⁴ H
"""
_name = "If"
_num_qubits = None
_num_bits = None
_num_cregs = 1
_num_zvars = None
_op = None
_bitstring = None
# IfStatement's classical-target layout is [op_bits..., condition_bits...];
# the body operation may read or write a bit it tests against (for example
# ``IfStatement(Not(), bs"1")`` toggling the same bit).
_allow_bit_aliasing = True
_allow_zvar_aliasing = True
[docs]
def __init__(self, operation, bitstring: mc.BitString):
if isinstance(operation, type) and issubclass(operation, Operation):
op = operation()
elif isinstance(operation, Operation):
op = operation
else:
raise ValueError("Operation must be an Operation object or type.")
if not isinstance(bitstring, mc.BitString):
raise ValueError("bitstring must be a BitString object.")
super().__init__()
self._num_qubits = op.num_qubits
self._num_qregs = op._num_qregs
self._num_zvars = op.num_zvars
self._num_zregs = op._num_zregs
self._num_bits = op._num_bits + len(bitstring)
self._cregsizes = [self._num_bits]
self._qregsizes = [self._num_qubits]
self._zregsizes = [self._num_zvars]
self._op = op
self._bitstring = bitstring
@property
def op(self):
return self._op
@op.setter
def op(self, op):
raise ValueError("Cannot set op. Read only parameter.")
@property
def bitstring(self):
return self._bitstring
[docs]
def iswrapper(self):
return True
[docs]
def supports_canonical_rewrite(self):
return self.op.supports_canonical_rewrite()
[docs]
def getparams(self):
return self.op.getparams()
[docs]
def inverse(self):
raise NotImplementedError("Inverse not implemented for IfStatement")
[docs]
def power(self, power):
raise NotImplementedError("Power not implemented for IfStatement")
[docs]
def control(self, num_controls):
raise NotImplementedError("Control not implemented for IfStatement")
def __str__(self):
return f"IF (c=={self.bitstring.to01()}) {self.op}"
[docs]
def evaluate(self, d):
if not self.is_symbolic():
return self
return IfStatement(self.op.evaluate(d), self.bitstring)
[docs]
def get_operation(self):
return self.op
[docs]
def get_bitstring(self):
return self.bitstring
[docs]
def asciiwidth(self, qubits, bits, zvars):
val = self.get_bitstring()
gw = self.op.asciiwidth(qubits, [], [])
bstr = _string_with_square(_find_unit_range(bits), ",")
iw = len(f"c{bstr}==" + val.to01()) + 2
return max(gw, iw)
def _decompose(self, circuit, qtargets, ctargets, ztargets):
from mimiqcircuits.decomposition.rules import CanonicalRewrite
decomposed = _decompose_ifstatement(
CanonicalRewrite(), self, qtargets, ctargets, ztargets
)
for inst in decomposed:
circuit.push(inst.operation, *inst.qubits, *inst.bits, *inst.zvars)
return circuit
def _decompose_ifstatement(basis, op, qubits, bits, zvars):
"""One decomposition step of the body of an IfStatement, kept conditional.
Decomposes the body of ``op`` once with ``basis`` (a ``DecompositionBasis``
or a ``RewriteRule``) and conditions every resulting instruction on the
same bits. Bases reuse it so that a conditional gate follows the same rules
as the bare gate. Nested conditions are merged into one IfStatement.
"""
from mimiqcircuits.decomposition.abstract import DecompositionBasis
inner = op.op
condition = op.bitstring
nb = inner.num_bits
target_bits = list(bits[:nb])
condition_bits = list(bits[nb:])
if isinstance(basis, DecompositionBasis):
decomposed = basis.decompose(inner, qubits, target_bits, zvars)
else:
decomposed = basis.decompose_step(inner, qubits, target_bits, zvars)
circ = mc.Circuit()
for inst in decomposed:
body = inst.operation
if isinstance(body, mc.IfStatement):
# Flatten the if statements, the topmost condition goes to the back.
merged = mc.BitString(list(body.bitstring) + list(condition))
body = mc.IfStatement(body.op, merged)
else:
body = mc.IfStatement(body, condition)
circ.push(body, *inst.qubits, *inst.bits, *condition_bits, *inst.zvars)
return circ
__all__ = ["IfStatement"]