diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 6003ffbd20dd..f23a7dfed9b3 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -59,6 +59,7 @@ from .classicalregister import ClassicalRegister, Clbit from .quantumregister import QuantumRegister, Qubit from .gate import Gate +# pylint: disable=cyclic-import from .controlledgate import ControlledGate from .instruction import Instruction from .instructionset import InstructionSet diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py index ae85a063b62e..df74771de7da 100644 --- a/qiskit/circuit/add_control.py +++ b/qiskit/circuit/add_control.py @@ -14,22 +14,39 @@ """ Add control to operation if supported. """ -from qiskit import QiskitError +from typing import Union, Optional + +from qiskit.circuit.exceptions import CircuitError from qiskit.extensions import UnitaryGate +from . import ControlledGate, Gate, QuantumRegister, QuantumCircuit + + +def add_control(operation: Union[Gate, ControlledGate], + num_ctrl_qubits: int, + label: Union[str, None], + ctrl_state: Union[int, str, None]) -> ControlledGate: + """For standard gates, if the controlled version already exists in the + library, it will be returned (e.g. XGate.control() = CnotGate(). + For more generic gates, this method implements the controlled + version by first decomposing into the ['u1', 'u3', 'cx'] basis, then + controlling each gate in the decomposition. -def add_control(operation, num_ctrl_qubits, label): - """Add num_ctrl_qubits controls to operation + Open controls are implemented by conjugating the control line with + X gates. Adds num_ctrl_qubits controls to operation. Args: - operation (Gate or ControlledGate): operation to add control to. - num_ctrl_qubits (int): number of controls to add to gate (default=1) - label (str): optional gate label + operation: Operation for which control will be added. + num_ctrl_qubits: The number of controls to add to gate (default=1). + label: Optional gate label. + ctrl_state (int or str or None): 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. Returns: - ControlledGate: controlled version of gate. This default algorithm - uses num_ctrl_qubits-1 ancillae qubits so returns a gate of size - num_qubits + 2*num_ctrl_qubits - 1. + Controlled version of gate. + """ import qiskit.extensions.standard as standard if isinstance(operation, standard.RZGate) or operation.name == 'rz': @@ -42,29 +59,34 @@ def add_control(operation, num_ctrl_qubits, label): if isinstance(operation, UnitaryGate): # attempt decomposition operation._define() - return control(operation, num_ctrl_qubits=num_ctrl_qubits, label=label) + return control(operation, num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) -def control(operation, num_ctrl_qubits=1, label=None): +def control(operation: Union[Gate, ControlledGate], + num_ctrl_qubits: Optional[int] = 1, + label: Optional[Union[None, str]] = None, + ctrl_state: Optional[Union[None, int, str]] = None) -> ControlledGate: """Return controlled version of gate using controlled rotations Args: - operation (Gate or Controlledgate): gate to create ControlledGate from - num_ctrl_qubits (int): number of controls to add to gate (default=1) - label (str): optional gate label + operation: gate to create ControlledGate from + num_ctrl_qubits: number of controls to add to gate (default=1) + label: optional gate label + 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. + Returns: - ControlledGate: controlled version of gate. This default algorithm - uses num_ctrl_qubits-1 ancillae qubits so returns a gate of size - num_qubits + 2*num_ctrl_qubits - 1. + Controlled version of gate. Raises: - QiskitError: gate contains non-gate in definitionl + CircuitError: gate contains non-gate in definition """ from math import pi # pylint: disable=cyclic-import import qiskit.circuit.controlledgate as controlledgate - from qiskit.circuit.quantumregister import QuantumRegister - from qiskit.circuit.quantumcircuit import QuantumCircuit # pylint: disable=unused-import import qiskit.extensions.standard.multi_control_rotation_gates import qiskit.extensions.standard.multi_control_toffoli_gate @@ -73,7 +95,6 @@ def control(operation, num_ctrl_qubits=1, label=None): q_control = QuantumRegister(num_ctrl_qubits, name='control') q_target = QuantumRegister(operation.num_qubits, name='target') q_ancillae = None # TODO: add - qc = QuantumCircuit(q_control, q_target) if operation.name == 'x' or ( @@ -122,7 +143,7 @@ def control(operation, num_ctrl_qubits=1, label=None): None, mode='noancilla') else: - raise QiskitError('gate contains non-controllable instructions') + raise CircuitError('gate contains non-controllable instructions') instr = qc.to_instruction() if isinstance(operation, controlledgate.ControlledGate): new_num_ctrl_qubits = num_ctrl_qubits + operation.num_ctrl_qubits @@ -146,20 +167,19 @@ def control(operation, num_ctrl_qubits=1, label=None): operation.params, label=label, num_ctrl_qubits=new_num_ctrl_qubits, - definition=instr.definition) + definition=instr.definition, + ctrl_state=ctrl_state) cgate.base_gate = base_gate return cgate def _gate_to_circuit(operation): - from qiskit.circuit.quantumcircuit import QuantumCircuit - from qiskit.circuit.quantumregister import QuantumRegister qr = QuantumRegister(operation.num_qubits) qc = QuantumCircuit(qr, name=operation.name) if hasattr(operation, 'definition') and operation.definition: for rule in operation.definition: if rule[0].name in {'id', 'barrier', 'measure', 'snapshot'}: - raise QiskitError('Cannot make controlled gate with {} instruction'.format( + raise CircuitError('Cannot make controlled gate with {} instruction'.format( rule[0].name)) qc.append(rule[0], qargs=[qr[bit.index] for bit in rule[1]], cargs=[]) else: diff --git a/qiskit/circuit/controlledgate.py b/qiskit/circuit/controlledgate.py index 5d67261d1b6d..6f97eef1870b 100644 --- a/qiskit/circuit/controlledgate.py +++ b/qiskit/circuit/controlledgate.py @@ -15,17 +15,21 @@ """ Controlled unitary gate. """ - from qiskit.circuit.exceptions import CircuitError from .gate import Gate +from . import QuantumRegister class ControlledGate(Gate): """Controlled unitary gate.""" def __init__(self, name, num_qubits, params, label=None, num_ctrl_qubits=1, - definition=None): - """Create a new gate. + definition=None, ctrl_state=None): + """Create a controlled gate. + + Attributes: + num_ctrl_qubits (int): The number of control qubits. + ctrl_state (int): The control state in decimal notation. Args: name (str): The Qobj name of the gate. @@ -34,8 +38,13 @@ def __init__(self, name, num_qubits, params, label=None, num_ctrl_qubits=1, label (str or None): An optional label for the gate [Default: None] num_ctrl_qubits (int): Number of control qubits. definition (list): list of gate rules for implementing this gate. + ctrl_state (int or str or None): 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. Raises: CircuitError: num_ctrl_qubits >= num_qubits + CircuitError: ctrl_state < 0 or ctrl_state > 2**num_ctrl_qubits. """ super().__init__(name, num_qubits, params, label=label) if num_ctrl_qubits < num_qubits: @@ -50,14 +59,76 @@ def __init__(self, name, num_qubits, params, label=None, num_ctrl_qubits=1, self.base_gate = base_gate.base_gate else: self.base_gate = base_gate + self._ctrl_state = None + self.ctrl_state = ctrl_state + + @property + def definition(self): + """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.""" + if not self._definition: + self._define() + # pylint: disable=cyclic-import + from qiskit.extensions.standard import XGate, CnotGate + bit_ctrl_state = bin(self.ctrl_state)[2:].zfill(self.num_ctrl_qubits) + # hacky way to get register assuming single register + if self._definition: + qreg = self._definition[0][1][0].register + elif isinstance(self, CnotGate): + qreg = QuantumRegister(self.num_qubits, 'q') + self._definition = [(self, [qreg[0], qreg[1]], [])] + open_rules = [] + for qind, val in enumerate(bit_ctrl_state[::-1]): + if val == '0': + open_rules.append([XGate(), [qreg[qind]], []]) + return open_rules + self._definition + open_rules + + @definition.setter + def definition(self, excited_def): + """Set controlled gate definition with closed controls.""" + super(Gate, self.__class__).definition.fset(self, excited_def) + + @property + def ctrl_state(self): + """Return the control state of the gate as a decimal integer.""" + return self._ctrl_state + + @ctrl_state.setter + def ctrl_state(self, ctrl_state): + """Set the control state of this gate. + + Args: + ctrl_state (int or str or None): The control state of the gate. + + Raises: + CircuitError: ctrl_state is invalid. + """ + if isinstance(ctrl_state, str): + try: + assert len(ctrl_state) == self.num_ctrl_qubits + ctrl_state = int(ctrl_state, 2) + except ValueError: + raise CircuitError('invalid control bit string: ' + ctrl_state) + except AssertionError: + raise CircuitError('invalid control bit string: length != ' + 'num_ctrl_qubits') + if isinstance(ctrl_state, int): + if 0 <= ctrl_state < 2**self.num_ctrl_qubits: + self._ctrl_state = ctrl_state + else: + raise CircuitError('invalid control state specification') + elif ctrl_state is None: + self._ctrl_state = 2**self.num_ctrl_qubits - 1 + else: + raise CircuitError('invalid control state specification') def __eq__(self, other): if not isinstance(other, ControlledGate): return False else: return (other.num_ctrl_qubits == self.num_ctrl_qubits and - self.base_gate == other.base_gate and - super().__eq__(other)) + self.base_gate == other.base_gate) def inverse(self): """Invert this gate by calling inverse on the base gate.""" diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py index a8095fded48c..618435474f23 100644 --- a/qiskit/circuit/gate.py +++ b/qiskit/circuit/gate.py @@ -106,24 +106,24 @@ def label(self, name): else: raise TypeError('label expects a string or None') - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Return controlled version of gate Args: num_ctrl_qubits (int): number of controls to add to gate (default=1) - label (str): optional gate label + label (str or None): optional gate label + ctrl_state (int or str or None): The control state in decimal or as + a bitstring (e.g. '111'). If None, use 2**num_ctrl_qubits-1. Returns: - ControlledGate: controlled version of gate. This default algorithm - uses num_ctrl_qubits-1 ancillae qubits so returns a gate of size - num_qubits + 2*num_ctrl_qubits - 1. + ControlledGate: controlled version of gate. Raises: - QiskitError: unrecognized mode + QiskitError: unrecognized mode or invalid ctrl_state """ # pylint: disable=cyclic-import from .add_control import add_control - return add_control(self, num_ctrl_qubits, label) + return add_control(self, num_ctrl_qubits, label, ctrl_state) @staticmethod def _broadcast_single_argument(qarg): diff --git a/qiskit/extensions/standard/h.py b/qiskit/extensions/standard/h.py index 002bca37b813..5e3d1629235f 100644 --- a/qiskit/extensions/standard/h.py +++ b/qiskit/extensions/standard/h.py @@ -46,19 +46,23 @@ def _define(self): definition.append(inst) self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CHGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CHGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" diff --git a/qiskit/extensions/standard/rx.py b/qiskit/extensions/standard/rx.py index c14335e654a8..49003f14c85f 100644 --- a/qiskit/extensions/standard/rx.py +++ b/qiskit/extensions/standard/rx.py @@ -46,19 +46,23 @@ def _define(self): definition.append(inst) self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CrxGate(self.params[0]) - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CrxGate(self.params[0]) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate. diff --git a/qiskit/extensions/standard/ry.py b/qiskit/extensions/standard/ry.py index f9d86892e47c..071ff385a0ea 100644 --- a/qiskit/extensions/standard/ry.py +++ b/qiskit/extensions/standard/ry.py @@ -46,19 +46,23 @@ def _define(self): definition.append(inst) self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CryGate(self.params[0]) - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CryGate(self.params[0]) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate. diff --git a/qiskit/extensions/standard/rz.py b/qiskit/extensions/standard/rz.py index f215c276a9a5..83571433811b 100644 --- a/qiskit/extensions/standard/rz.py +++ b/qiskit/extensions/standard/rz.py @@ -43,19 +43,23 @@ def _define(self): definition.append(inst) self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CrzGate(self.params[0]) - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CrzGate(self.params[0]) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate. diff --git a/qiskit/extensions/standard/swap.py b/qiskit/extensions/standard/swap.py index 1cfce87a4840..43aee0c1c4ec 100644 --- a/qiskit/extensions/standard/swap.py +++ b/qiskit/extensions/standard/swap.py @@ -46,19 +46,23 @@ def _define(self): definition.append(inst) self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return FredkinGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return FredkinGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" diff --git a/qiskit/extensions/standard/u1.py b/qiskit/extensions/standard/u1.py index 014d24d90718..c8a506110299 100644 --- a/qiskit/extensions/standard/u1.py +++ b/qiskit/extensions/standard/u1.py @@ -42,19 +42,23 @@ def _define(self): definition.append(inst) self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return Cu1Gate(*self.params) - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return Cu1Gate(*self.params) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" diff --git a/qiskit/extensions/standard/u3.py b/qiskit/extensions/standard/u3.py index 3a4449a69b28..832b8c04d680 100644 --- a/qiskit/extensions/standard/u3.py +++ b/qiskit/extensions/standard/u3.py @@ -39,19 +39,23 @@ def inverse(self): """ return U3Gate(-self.params[0], -self.params[2], -self.params[1]) - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return Cu3Gate(*self.params) - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return Cu3Gate(*self.params) + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def to_matrix(self): """Return a Numpy.array for the U3 gate.""" diff --git a/qiskit/extensions/standard/x.py b/qiskit/extensions/standard/x.py index 93457b083fb3..83f7de4642d0 100644 --- a/qiskit/extensions/standard/x.py +++ b/qiskit/extensions/standard/x.py @@ -47,21 +47,25 @@ def _define(self): definition.append(inst) self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CnotGate() - elif num_ctrl_qubits == 2: - return ToffoliGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CnotGate() + elif num_ctrl_qubits == 2: + return ToffoliGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" @@ -114,19 +118,23 @@ def __init__(self): super().__init__("cx", 2, [], num_ctrl_qubits=1) self.base_gate = XGate() - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return ToffoliGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return ToffoliGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" diff --git a/qiskit/extensions/standard/y.py b/qiskit/extensions/standard/y.py index 25f85bd34586..8a66c1957808 100644 --- a/qiskit/extensions/standard/y.py +++ b/qiskit/extensions/standard/y.py @@ -42,19 +42,23 @@ def _define(self): definition.append(inst) self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CyGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CyGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" diff --git a/qiskit/extensions/standard/z.py b/qiskit/extensions/standard/z.py index 2c970b8781f5..ff1dbb3d0cde 100644 --- a/qiskit/extensions/standard/z.py +++ b/qiskit/extensions/standard/z.py @@ -42,19 +42,23 @@ def _define(self): definition.append(inst) self.definition = definition - def control(self, num_ctrl_qubits=1, label=None): + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): """Controlled version of this gate. Args: num_ctrl_qubits (int): number of control qubits. label (str or None): An optional label for the gate [Default: None] + ctrl_state (int or str or None): control state expressed as integer, + string (e.g. '110'), or None. If None, use all 1s. Returns: ControlledGate: controlled version of this gate. """ - if num_ctrl_qubits == 1: - return CzGate() - return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label) + if ctrl_state is None: + if num_ctrl_qubits == 1: + return CzGate() + return super().control(num_ctrl_qubits=num_ctrl_qubits, label=label, + ctrl_state=ctrl_state) def inverse(self): """Invert this gate.""" diff --git a/qiskit/extensions/unitary.py b/qiskit/extensions/unitary.py index a5247c57a6ed..c2b0c6f88586 100644 --- a/qiskit/extensions/unitary.py +++ b/qiskit/extensions/unitary.py @@ -22,6 +22,7 @@ from qiskit.circuit import Gate from qiskit.circuit import QuantumCircuit from qiskit.circuit import QuantumRegister +from qiskit.exceptions import QiskitError from qiskit.extensions.standard import U3Gate from qiskit.quantum_info.operators.predicates import matrix_equal from qiskit.quantum_info.operators.predicates import is_unitary_matrix @@ -111,44 +112,24 @@ def _define(self): raise NotImplementedError("Not able to generate a subcircuit for " "a {}-qubit unitary".format(self.num_qubits)) - def control(self, num_ctrl_qubits=1, label=None): - """Return controlled version of gate + def control(self, num_ctrl_qubits=1, label=None, ctrl_state=None): + r"""Return controlled version of gate Args: num_ctrl_qubits (int): number of controls to add to gate (default=1) label (str): optional gate label + ctrl_state (int or str or None): The control state in decimal or as a + bit string (e.g. '1011'). If None, use 2**num_ctrl_qubits-1. Returns: UnitaryGate: controlled version of gate. Raises: - QiskitError: unrecognized mode + QiskitError: invalid ctrl_state """ - cmat = self._compute_control_matrix(self.to_matrix(), num_ctrl_qubits) + cmat = _compute_control_matrix(self.to_matrix(), num_ctrl_qubits) return UnitaryGate(cmat, label=label) - def _compute_control_matrix(self, base_mat, num_ctrl_qubits): - """ - Compute the controlled version of the input matrix with qiskit ordering. - - Args: - base_mat (ndarray): unitary to be controlled - num_ctrl_qubits (int): number of controls for new unitary - - Returns: - ndarray: controlled version of base matrix. - """ - num_target = int(numpy.log2(base_mat.shape[0])) - ctrl_dim = 2**num_ctrl_qubits - ctrl_grnd = numpy.repeat([[1], [0]], [1, ctrl_dim-1]) - full_mat_dim = ctrl_dim * base_mat.shape[0] - full_mat = numpy.zeros((full_mat_dim, full_mat_dim), dtype=base_mat.dtype) - ctrl_proj = numpy.diag(numpy.roll(ctrl_grnd, ctrl_dim - 1)) - full_mat = (numpy.kron(numpy.eye(2**num_target), - numpy.eye(ctrl_dim) - ctrl_proj) - + numpy.kron(base_mat, ctrl_proj)) - return full_mat - def qasm(self): """ The qasm for a custom unitary gate This is achieved by adding a custom gate that corresponds to the definition @@ -199,6 +180,52 @@ def qasm(self): return self._qasm_definition + self._qasmif(self._qasm_name) +def _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=None): + r""" + Compute the controlled version of the input matrix with qiskit ordering. + This function computes the controlled unitary with :math:`n` control qubits + and :math:`m` target qubits, + + .. math:: + + V_n^j(U_{2^m}) = (U_{2^m} \otimes |j\rangle\!\langle j|) + + (I_{2^m} \otimes (I_{2^n} - |j\rangle\!\langle j|)). + + where :math:`|j\rangle \in \mathcal{H}^{2^n}` is the control state. + + Args: + base_mat (ndarray): unitary to be controlled + num_ctrl_qubits (int): number of controls for new unitary + ctrl_state (int or str or None): The control state in decimal or as + a bitstring (e.g. '111'). If None, use 2**num_ctrl_qubits-1. + + Returns: + ndarray: controlled version of base matrix. + + Raises: + QiskitError: unrecognized mode or invalid ctrl_state + """ + num_target = int(numpy.log2(base_mat.shape[0])) + ctrl_dim = 2**num_ctrl_qubits + ctrl_grnd = numpy.repeat([[1], [0]], [1, ctrl_dim-1]) + if ctrl_state is None: + ctrl_state = ctrl_dim - 1 + elif isinstance(ctrl_state, str): + ctrl_state = int(ctrl_state, 2) + if isinstance(ctrl_state, int): + if not 0 <= ctrl_state < ctrl_dim: + raise QiskitError('Invalid control state value specified.') + else: + raise QiskitError('Invalid control state type specified.') + full_mat_dim = ctrl_dim * base_mat.shape[0] + full_mat = numpy.zeros((full_mat_dim, full_mat_dim), dtype=base_mat.dtype) + ctrl_proj = numpy.diag(numpy.roll(ctrl_grnd, ctrl_state)) + full_mat = (numpy.kron(numpy.eye(2**num_target), + numpy.eye(ctrl_dim) - ctrl_proj) + + numpy.kron(base_mat, ctrl_proj)) + return full_mat + + def unitary(self, obj, qubits, label=None): """Apply u2 to q.""" if isinstance(qubits, QuantumRegister): diff --git a/releasenotes/notes/add-open-controls-bb21111b866ada43.yaml b/releasenotes/notes/add-open-controls-bb21111b866ada43.yaml new file mode 100644 index 000000000000..8efb4bde8f38 --- /dev/null +++ b/releasenotes/notes/add-open-controls-bb21111b866ada43.yaml @@ -0,0 +1,19 @@ +--- +features: + - | + Add ability to specify control conditioned on a qubit being in the + ground state. The state of the control qubits is represented by a + decimal integer. For example: + + from qiskit import QuantumCircuit + from qiskit.extensions.standard import XGate + + qc = QuantumCircuit(4) + cgate = XGate().control(3, ctrl_state=6) + qc.append(cgate, [0, 1, 2, 3]) + + Creates a four qubit gate where the fourth qubit gets flipped if + the first qubit is in the ground state and the second and third + qubits are in the excited state. If ctrl_state is None, the + default, control is conditioned on all control qubits being + excited. diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index d0ad34219a07..c1bfb7d63e83 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -19,13 +19,15 @@ from inspect import signature import numpy as np from numpy import pi -import scipy from ddt import ddt, data from qiskit import QuantumRegister, QuantumCircuit, execute, BasicAer, QiskitError from qiskit.test import QiskitTestCase from qiskit.circuit import ControlledGate +from qiskit.circuit.exceptions import CircuitError from qiskit.quantum_info.operators.predicates import matrix_equal, is_unitary_matrix +from qiskit.quantum_info.random import random_unitary +from qiskit.quantum_info.states import Statevector import qiskit.circuit.add_control as ac from qiskit.transpiler.passes import Unroller from qiskit.converters.circuit_to_dag import circuit_to_dag @@ -37,7 +39,7 @@ RYGate, CryGate, CrxGate, FredkinGate, U3Gate, CHGate, CrzGate, Cu3Gate, MSGate, Barrier, RCCXGate, RCCCXGate) -from qiskit.extensions.unitary import UnitaryGate +from qiskit.extensions.unitary import _compute_control_matrix import qiskit.extensions.standard as allGates @@ -156,8 +158,8 @@ def test_single_controlled_composite_gate(self): num_target = cgate.width() gate = cgate.to_gate() cont_gate = gate.control(num_ctrl_qubits=num_ctrl) - control = QuantumRegister(num_ctrl) - target = QuantumRegister(num_target) + control = QuantumRegister(num_ctrl, 'control') + target = QuantumRegister(num_target, 'target') qc = QuantumCircuit(control, target) qc.append(cont_gate, control[:]+target[:]) simulator = BasicAer.get_backend('unitary_simulator') @@ -400,13 +402,52 @@ def test_controlled_unitary(self, num_ctrl_qubits): def test_controlled_random_unitary(self, num_ctrl_qubits): """test controlled unitary""" num_target = 2 - base_gate = UnitaryGate(scipy.stats.unitary_group.rvs(num_target)) + base_gate = random_unitary(2**num_target).to_instruction() base_mat = base_gate.to_matrix() cgate = base_gate.control(num_ctrl_qubits) test_op = Operator(cgate) cop_mat = _compute_control_matrix(base_mat, num_ctrl_qubits) self.assertTrue(matrix_equal(cop_mat, test_op.data, ignore_phase=True)) + @data(1, 2, 3) + def test_open_controlled_unitary_matrix(self, num_ctrl_qubits): + """test open controlled unitary matrix""" + # verify truth table + num_target_qubits = 2 + num_qubits = num_ctrl_qubits + num_target_qubits + target_op = Operator(XGate()) + for i in range(num_target_qubits - 1): + target_op = target_op.tensor(XGate()) + print('') + for i in range(2**num_qubits): + input_bitstring = bin(i)[2:].zfill(num_qubits) + input_target = input_bitstring[0:num_target_qubits] + input_ctrl = input_bitstring[-num_ctrl_qubits:] + phi = Statevector.from_label(input_bitstring) + cop = Operator(_compute_control_matrix(target_op.data, + num_ctrl_qubits, + ctrl_state=input_ctrl)) + for j in range(2**num_qubits): + output_bitstring = bin(j)[2:].zfill(num_qubits) + output_target = output_bitstring[0:num_target_qubits] + output_ctrl = output_bitstring[-num_ctrl_qubits:] + psi = Statevector.from_label(output_bitstring) + cxout = np.dot(phi.data, psi.evolve(cop).data) + if input_ctrl == output_ctrl: + # flip the target bits + cond_output = ''.join([str(int(not int(a))) for a in input_target]) + else: + cond_output = input_target + if cxout == 1: + self.assertTrue( + (output_ctrl == input_ctrl) and + (output_target == cond_output)) + else: + self.assertTrue(( + (output_ctrl == input_ctrl) and + (output_target != cond_output)) or + output_ctrl != input_ctrl) + def test_base_gate_setting(self): """ Test all gates in standard extensions which are of type ControlledGate @@ -516,28 +557,51 @@ def test_relative_phase_toffoli_gates(self, num_ctrl_qubits): # compare simulated matrix with the matrix representation provided by the class self.assertTrue(matrix_equal(simulated_mat, repr_mat)) - -def _compute_control_matrix(base_mat, num_ctrl_qubits): - """ - Compute the controlled version of the input matrix with qiskit ordering. - - Args: - base_mat (ndarray): unitary to be controlled - num_ctrl_qubits (int): number of controls for new unitary - - Returns: - ndarray: controlled version of base matrix. - """ - num_target = int(np.log2(base_mat.shape[0])) - ctrl_dim = 2**num_ctrl_qubits - ctrl_grnd = np.repeat([[1], [0]], [1, ctrl_dim-1]) - full_mat_dim = ctrl_dim * base_mat.shape[0] - full_mat = np.zeros((full_mat_dim, full_mat_dim), dtype=base_mat.dtype) - ctrl_proj = np.diag(np.roll(ctrl_grnd, ctrl_dim - 1)) - full_mat = (np.kron(np.eye(2**num_target), - np.eye(ctrl_dim) - ctrl_proj) - + np.kron(base_mat, ctrl_proj)) - return full_mat + def test_open_controlled_gate(self): + """ + Test controlled gates with control on '0' + """ + base_gate = XGate() + base_mat = base_gate.to_matrix() + num_ctrl_qubits = 3 + + ctrl_state = 5 + cgate = base_gate.control(num_ctrl_qubits, ctrl_state=ctrl_state) + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) + self.assertEqual(Operator(cgate), Operator(target_mat)) + + ctrl_state = None + cgate = base_gate.control(num_ctrl_qubits, ctrl_state=ctrl_state) + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) + self.assertEqual(Operator(cgate), Operator(target_mat)) + + ctrl_state = 0 + cgate = base_gate.control(num_ctrl_qubits, ctrl_state=ctrl_state) + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) + self.assertEqual(Operator(cgate), Operator(target_mat)) + + ctrl_state = 7 + cgate = base_gate.control(num_ctrl_qubits, ctrl_state=ctrl_state) + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) + self.assertEqual(Operator(cgate), Operator(target_mat)) + + ctrl_state = '110' + cgate = base_gate.control(num_ctrl_qubits, ctrl_state=ctrl_state) + target_mat = _compute_control_matrix(base_mat, num_ctrl_qubits, ctrl_state=ctrl_state) + self.assertEqual(Operator(cgate), Operator(target_mat)) + + def test_open_controlled_gate_raises(self): + """ + Test controlled gates with open controls raises if ctrl_state isn't allowed. + """ + base_gate = XGate() + num_ctrl_qubits = 3 + with self.assertRaises(CircuitError): + base_gate.control(num_ctrl_qubits, ctrl_state=-1) + with self.assertRaises(CircuitError): + base_gate.control(num_ctrl_qubits, ctrl_state=2**num_ctrl_qubits) + with self.assertRaises(CircuitError): + base_gate.control(num_ctrl_qubits, ctrl_state='201') if __name__ == '__main__': diff --git a/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle b/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle index 2dc1af8cf877..bf9d7480c7c8 100644 Binary files a/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle and b/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle differ diff --git a/test/python/pickles/TestsBasicSwap_handle_measurement.pickle b/test/python/pickles/TestsBasicSwap_handle_measurement.pickle index 27eb154a547d..f4fdaab4e45f 100644 Binary files a/test/python/pickles/TestsBasicSwap_handle_measurement.pickle and b/test/python/pickles/TestsBasicSwap_handle_measurement.pickle differ diff --git a/test/python/pickles/TestsBasicSwap_initial_layout.pickle b/test/python/pickles/TestsBasicSwap_initial_layout.pickle index dc47de80b9fa..acade011a9e0 100644 Binary files a/test/python/pickles/TestsBasicSwap_initial_layout.pickle and b/test/python/pickles/TestsBasicSwap_initial_layout.pickle differ diff --git a/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle b/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle index 1528506f8ca0..2e4333db9551 100644 Binary files a/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle and b/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle differ diff --git a/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle b/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle index 8b3dc7f56807..b4a70c461dfb 100644 Binary files a/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle and b/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle differ diff --git a/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle b/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle index 90be0799bc10..ce58363424ba 100644 Binary files a/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle and b/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle differ diff --git a/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle b/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle index 2cfd35193b9a..8ae086d5c19c 100644 Binary files a/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle and b/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle differ diff --git a/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle b/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle index 5491cda0cc0c..392d06808324 100644 Binary files a/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle and b/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle differ diff --git a/test/python/pickles/TestsStochasticSwap_initial_layout.pickle b/test/python/pickles/TestsStochasticSwap_initial_layout.pickle index 15fc03083b37..972cd2de297f 100644 Binary files a/test/python/pickles/TestsStochasticSwap_initial_layout.pickle and b/test/python/pickles/TestsStochasticSwap_initial_layout.pickle differ