# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2019.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Controlled unitary gate."""
from __future__ import annotations
import copy
from typing import Optional, Union
from qiskit.circuit.exceptions import CircuitError
# pylint: disable=cyclic-import
from .quantumcircuit import QuantumCircuit
from .gate import Gate
from .quantumregister import QuantumRegister
from ._utils import _ctrl_state_to_int
[Doku]class ControlledGate(Gate):
"""Controlled unitary gate."""
def __init__(
self,
name: str,
num_qubits: int,
params: list,
label: Optional[str] = None,
num_ctrl_qubits: Optional[int] = 1,
definition: Optional["QuantumCircuit"] = None,
ctrl_state: Optional[Union[int, str]] = None,
base_gate: Optional[Gate] = None,
):
"""Create a new ControlledGate. In the new gate the first ``num_ctrl_qubits``
of the gate are the controls.
Args:
name: The name of the gate.
num_qubits: The number of qubits the gate acts on.
params: A list of parameters for the gate.
label: An optional label for the gate.
num_ctrl_qubits: Number of control qubits.
definition: A list of gate rules for implementing this gate. The
elements of the list are tuples of (:meth:`~qiskit.circuit.Gate`, [qubit_list],
[clbit_list]).
ctrl_state: The control state in decimal or as
a bitstring (e.g. '111'). If specified as a bitstring the length
must equal num_ctrl_qubits, MSB on left. If None, use
2**num_ctrl_qubits-1.
base_gate: Gate object to be controlled.
Raises:
CircuitError: If ``num_ctrl_qubits`` >= ``num_qubits``.
CircuitError: ctrl_state < 0 or ctrl_state > 2**num_ctrl_qubits.
Examples:
Create a controlled standard gate and apply it to a circuit.
.. plot::
:include-source:
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library.standard_gates import HGate
qr = QuantumRegister(3)
qc = QuantumCircuit(qr)
c3h_gate = HGate().control(2)
qc.append(c3h_gate, qr)
qc.draw('mpl')
Create a controlled custom gate and apply it to a circuit.
.. plot::
:include-source:
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library.standard_gates import HGate
qc1 = QuantumCircuit(2)
qc1.x(0)
qc1.h(1)
custom = qc1.to_gate().control(2)
qc2 = QuantumCircuit(4)
qc2.append(custom, [0, 3, 1, 2])
qc2.draw('mpl')
"""
self.base_gate = None if base_gate is None else base_gate.copy()
super().__init__(name, num_qubits, params, label=label)
self._num_ctrl_qubits = 1
self.num_ctrl_qubits = num_ctrl_qubits
self.definition = copy.deepcopy(definition)
self._ctrl_state = None
self.ctrl_state = ctrl_state
self._name = name
@property
def definition(self) -> QuantumCircuit:
"""Return definition in terms of other basic gates. If the gate has
open controls, as determined from `self.ctrl_state`, the returned
definition is conjugated with X without changing the internal
`_definition`.
"""
if self._open_ctrl:
closed_gate = self.copy()
closed_gate.ctrl_state = None
bit_ctrl_state = bin(self.ctrl_state)[2:].zfill(self.num_ctrl_qubits)
qreg = QuantumRegister(self.num_qubits, "q")
qc_open_ctrl = QuantumCircuit(qreg)
for qind, val in enumerate(bit_ctrl_state[::-1]):
if val == "0":
qc_open_ctrl.x(qind)
qc_open_ctrl.append(closed_gate, qargs=qreg[:])
for qind, val in enumerate(bit_ctrl_state[::-1]):
if val == "0":
qc_open_ctrl.x(qind)
return qc_open_ctrl
else:
return super().definition
@definition.setter
def definition(self, excited_def: "QuantumCircuit"):
"""Set controlled gate definition with closed controls.
Args:
excited_def: The circuit with all closed controls.
"""
self._definition = excited_def
@property
def name(self) -> str:
"""Get name of gate. If the gate has open controls the gate name
will become:
<original_name_o<ctrl_state>
where <original_name> is the gate name for the default case of
closed control qubits and <ctrl_state> is the integer value of
the control state for the gate.
"""
if self._open_ctrl:
return f"{self._name}_o{self.ctrl_state}"
else:
return self._name
@name.setter
def name(self, name_str):
"""Set the name of the gate. Note the reported name may differ
from the set name if the gate has open controls.
"""
self._name = name_str
@property
def num_ctrl_qubits(self):
"""Get number of control qubits.
Returns:
int: The number of control qubits for the gate.
"""
return self._num_ctrl_qubits
@num_ctrl_qubits.setter
def num_ctrl_qubits(self, num_ctrl_qubits):
"""Set the number of control qubits.
Args:
num_ctrl_qubits (int): The number of control qubits.
Raises:
CircuitError: ``num_ctrl_qubits`` is not an integer in ``[1, num_qubits]``.
"""
if num_ctrl_qubits != int(num_ctrl_qubits):
raise CircuitError("The number of control qubits must be an integer.")
num_ctrl_qubits = int(num_ctrl_qubits)
# This is a range rather than an equality limit because some controlled gates represent a
# controlled version of the base gate whose definition also uses auxiliary qubits.
upper_limit = self.num_qubits - getattr(self.base_gate, "num_qubits", 0)
if num_ctrl_qubits < 1 or num_ctrl_qubits > upper_limit:
limit = "num_qubits" if self.base_gate is None else "num_qubits - base_gate.num_qubits"
raise CircuitError(f"The number of control qubits must be in `[1, {limit}]`.")
self._num_ctrl_qubits = num_ctrl_qubits
@property
def ctrl_state(self) -> int:
"""Return the control state of the gate as a decimal integer."""
return self._ctrl_state
@ctrl_state.setter
def ctrl_state(self, ctrl_state: Union[int, str, None]):
"""Set the control state of this gate.
Args:
ctrl_state: The control state of the gate.
Raises:
CircuitError: ctrl_state is invalid.
"""
self._ctrl_state = _ctrl_state_to_int(ctrl_state, self.num_ctrl_qubits)
@property
def params(self):
"""Get parameters from base_gate.
Returns:
list: List of gate parameters.
Raises:
CircuitError: Controlled gate does not define a base gate
"""
if self.base_gate:
return self.base_gate.params
else:
raise CircuitError("Controlled gate does not define base gate for extracting params")
@params.setter
def params(self, parameters):
"""Set base gate parameters.
Args:
parameters (list): The list of parameters to set.
Raises:
CircuitError: If controlled gate does not define a base gate.
"""
if self.base_gate:
self.base_gate.params = parameters
else:
raise CircuitError("Controlled gate does not define base gate for extracting params")
def __deepcopy__(self, _memo=None):
cpy = copy.copy(self)
cpy.base_gate = self.base_gate.copy()
if self._definition:
cpy._definition = copy.deepcopy(self._definition, _memo)
return cpy
@property
def _open_ctrl(self) -> bool:
"""Return whether gate has any open controls"""
return self.ctrl_state < 2**self.num_ctrl_qubits - 1
def __eq__(self, other) -> bool:
return (
isinstance(other, ControlledGate)
and self.num_ctrl_qubits == other.num_ctrl_qubits
and self.ctrl_state == other.ctrl_state
and self.base_gate == other.base_gate
and self.num_qubits == other.num_qubits
and self.num_clbits == other.num_clbits
and self.definition == other.definition
)
[Doku] def inverse(self) -> "ControlledGate":
"""Invert this gate by calling inverse on the base gate."""
return self.base_gate.inverse().control(self.num_ctrl_qubits, ctrl_state=self.ctrl_state)