Source code for mimiqcircuits.operations.control

#
# Copyright © 2022-2023 University of Strasbourg. 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.
#

import mimiqcircuits as mc
import mimiqcircuits.operations.decompositions.control as ctrldecomp
from mimiqcircuits.printutils import print_wrapped_parens
import symengine as se
import sympy as sp
import mimiqcircuits.lazy as lz


[docs] class Control(mc.Operation): """Control operation. A Control is a special operation that applies multi-control gates to the Circuit at once. Examples: >>> from mimiqcircuits import * >>> c = Circuit() >>> c.push(Control(3,GateX()),1,2,3,4) 5-qubit circuit with 1 instructions: └── C₃X @ q[1,2,3], q[4] <BLANKLINE> >>> Control(2, GateX()).matrix() [1.0, 0, 0, 0, 0, 0, 0, 0] [0, 1.0, 0, 0, 0, 0, 0, 0] [0, 0, 1.0, 0, 0, 0, 0, 0] [0, 0, 0, 1.0, 0, 0, 0, 0] [0, 0, 0, 0, 1.0, 0, 0, 0] [0, 0, 0, 0, 0, 1.0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 1.0] [0, 0, 0, 0, 0, 0, 1.0, 0] <BLANKLINE> """ _name = "Control" _num_qubits = None _num_bits = 0 _num_cregs = 0 _num_qregs = 2 _num_controls = None _op = None def __init__(self, num_controls, operation, *args, **kwargs): if isinstance(operation, type) and issubclass(operation, mc.Operation): op = operation(*args, **kwargs) elif isinstance(operation, mc.Operation): op = operation else: raise TypeError("Operation must be an Operation object or type.") if isinstance(operation, (mc.Barrier, mc.Reset, mc.Measure)): raise TypeError( f"{operation.__class__.__name__} cannot be controlled operation." ) if op.num_bits != 0: raise TypeError("Power operation cannot act on classical bits.") if num_controls < 1: raise ValueError("Controlled operations must have at least one control.") super().__init__() # TODO: check for possible problems when doing this, since we are not # using explicitly the operation given. if isinstance(op, Control): self._num_qubits = op.op.num_qubits + op.num_controls + num_controls self._num_controls = op.num_controls + num_controls self._op = op.op self._qregsizes = [self._num_controls] self._qregsizes.extend(op.op.qregsizes) else: self._num_qubits = op.num_qubits + num_controls self._num_controls = num_controls self._op = op self._qregsizes = [num_controls] self._qregsizes.extend(op.qregsizes)
[docs] def matrix(self): Mdim = 2**self.op.num_qubits Ldim = 2 ** (self.op.num_qubits + self.num_controls) mat = se.zeros(Ldim, Ldim) mat[Ldim - Mdim :, Ldim - Mdim :] = self.op.matrix() for i in range(0, Ldim - Mdim): mat[i, i] = 1 return se.Matrix(sp.simplify(sp.Matrix(mat).evalf()))
@property def num_controls(self): return self._num_controls @num_controls.setter def num_controls(self, value): raise ValueError("Cannot set num_controls. Read only parameter.") @property def num_targets(self): return self.num_qubits - self.num_controls @num_targets.setter def num_targets(self, value): raise ValueError("Cannot set num_targets. Read only parameter.") @property def op(self): return self._op @op.setter def op(self, op): raise ValueError("Cannot set op. Read only parameter.")
[docs] def inverse(self): return Control(self.num_controls, self.op.inverse())
[docs] def getparams(self): return self.op.getparams()
[docs] def get_operation(self): return self.op
[docs] def control(self, *args): if len(args) == 0: return lz.control(self) elif len(args) == 1: num_controls = args[0] return Control(self.num_controls + num_controls, self.op) else: raise ValueError("Invalid number of arguments.")
def _power(self, pwr): return Control(self.num_controls, self.op.power(pwr))
[docs] def power(self, *args): if len(args) == 0: return lz.power(self) elif len(args) == 1: p = args[0] return self._power(p) else: raise ValueError("Invalid number of arguments.")
[docs] def parallel(self, *args): if len(args) == 0: return lz.parallel(self) elif len(args) == 1: num_repeats = args[0] return mc.Parallel(num_repeats, self) else: raise ValueError("Invalid number of arguments.")
def __pow__(self, p): return self.power(p)
[docs] def iswrapper(self): return True
def __str__(self): controls_subscript = str.maketrans("0123456789", "₀₁₂₃₄₅₆₇₈₉") ctext = "" if self.num_controls > 1: ctext = str(self.num_controls).translate(controls_subscript) return f"C{ctext}{print_wrapped_parens(self.op)}"
[docs] def evaluate(self, d): ncontrol = self.num_controls return self.op.evaluate(d).control(ncontrol)
def _decompose(self, circ, qubits, bits): decompose_map = { (2, mc.GateX): mc.GateCCX._decompose, (1, mc.GateX): mc.GateCX._decompose, (1, mc.GateY): mc.GateCY._decompose, (1, mc.GateZ): mc.GateCZ._decompose, (3, mc.GateX): mc.GateC3X._decompose, (1, mc.GateP): mc.GateCP._decompose, (2, mc.GateP): mc.GateCCP._decompose, (1, mc.GateRX): mc.GateCRX._decompose, (1, mc.GateRY): mc.GateCRY._decompose, (1, mc.GateRZ): mc.GateCRZ._decompose, (1, mc.GateSWAP): mc.GateCSWAP._decompose, (1, mc.GateU): mc.GateCU._decompose, (1, mc.GateH): mc.GateCH._decompose, (1, mc.GateSX): mc.GateCSX._decompose, (1, mc.GateS): mc.GateCS._decompose, (1, mc.GateSXDG): mc.GateCSXDG._decompose, (1, mc.GateSDG): mc.GateCSDG._decompose, } key = (self.num_controls, type(self.op)) if key in decompose_map: return decompose_map[key](self, circ, qubits, bits) elif self.num_controls == 1 and self.num_qubits != 1: newcirc = self.op._decompose(mc.Circuit(), qubits[1:], bits) for inst in newcirc: return circ.push(Control(1, inst._operation), qubits[0], qubits[1:]) elif not isinstance(self.op, Control) and self.op.num_qubits > 1: return circ.push(self, *qubits) else: return ctrldecomp.control_decompose(circ, self.op, qubits[:-1], qubits[-1])
__all__ = ["Control"]