diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py index df7b3a814d01..c399f86a41bc 100644 --- a/qiskit/circuit/add_control.py +++ b/qiskit/circuit/add_control.py @@ -102,12 +102,6 @@ def control(operation: Union[Gate, ControlledGate], # pylint: disable=unused-import import qiskit.circuit.library.standard_gates.multi_control_rotation_gates - # check args - if num_ctrl_qubits == 0: - return operation - elif num_ctrl_qubits < 0: - raise CircuitError('number of control qubits must be positive integer') - q_control = QuantumRegister(num_ctrl_qubits, name='control') q_target = QuantumRegister(operation.num_qubits, name='target') q_ancillae = None # TODO: add diff --git a/qiskit/circuit/controlledgate.py b/qiskit/circuit/controlledgate.py index a080c3857fee..aa62959f0e59 100644 --- a/qiskit/circuit/controlledgate.py +++ b/qiskit/circuit/controlledgate.py @@ -85,10 +85,8 @@ def __init__(self, name: str, num_qubits: int, params: List, qc2.draw() """ super().__init__(name, num_qubits, params, label=label) - if num_ctrl_qubits < num_qubits: - self.num_ctrl_qubits = num_ctrl_qubits - else: - raise CircuitError('number of control qubits must be less than the number of qubits') + self._num_ctrl_qubits = 1 + self.num_ctrl_qubits = num_ctrl_qubits self.base_gate = None if definition: self.definition = definition @@ -132,6 +130,31 @@ def definition(self, excited_def: List): """Set controlled gate definition with closed controls.""" super(Gate, self.__class__).definition.fset(self, excited_def) + @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 in [1, num_qubits-1]. + + Raises: + CircuitError: num_ctrl_qubits is not an integer in [1, num_qubits - 1]. + """ + if (num_ctrl_qubits == int(num_ctrl_qubits) and + 1 <= num_ctrl_qubits < self.num_qubits): + self._num_ctrl_qubits = num_ctrl_qubits + else: + raise CircuitError('The number of control qubits must be in [1, num_qubits-1]') + @property def ctrl_state(self) -> int: """Return the control state of the gate as a decimal integer.""" diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index ac998678e591..e593c5409918 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -739,7 +739,7 @@ class MCXGate(ControlledGate): def __new__(cls, num_ctrl_qubits=None, label=None, ctrl_state=None): """Create a new MCX instance. - Depending on the number of controls, this creates an explicit X, CX, CCX, C3X or C4X + Depending on the number of controls, this creates an explicit CX, CCX, C3X or C4X instance or a generic MCX gate. """ # these gates will always be implemented for all modes of the MCX if the number of control @@ -748,8 +748,6 @@ def __new__(cls, num_ctrl_qubits=None, label=None, ctrl_state=None): 1: CXGate, 2: CCXGate } - if num_ctrl_qubits == 0: - return XGate(label=label) if num_ctrl_qubits in explicit.keys(): gate_class = explicit[num_ctrl_qubits] gate = gate_class.__new__(gate_class, label=label, ctrl_state=ctrl_state) diff --git a/releasenotes/notes/disallow_num_ctrl_qubits_zero-eb102aa087f30250.yaml b/releasenotes/notes/disallow_num_ctrl_qubits_zero-eb102aa087f30250.yaml new file mode 100644 index 000000000000..aad8236a695e --- /dev/null +++ b/releasenotes/notes/disallow_num_ctrl_qubits_zero-eb102aa087f30250.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + Previously it was possible to set the number of control qubits to zero in which case the + the original, potentially non-controlled, operation would be returned. This could cause + an AttributeError if the caller attempts to access an attribute which only ControlledGates + have. This fix adds a getter/setter for num_ctrl_qubits to handle validation. diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py index a9c89863e2ee..9de8c2bc281e 100644 --- a/test/python/circuit/test_controlled_gate.py +++ b/test/python/circuit/test_controlled_gate.py @@ -541,14 +541,14 @@ def test_multi_controlled_y_rotation_matrix_basic_mode(self, num_controls, use_b with self.subTest(msg='control state = {}'.format(ctrl_state)): self.assertTrue(matrix_equal(simulated, expected)) - @data(0, 1, 2) + @data(1, 2) def test_mcx_gates_yield_explicit_gates(self, num_ctrl_qubits): """Test the creating a MCX gate yields the explicit definition if we know it.""" cls = MCXGate(num_ctrl_qubits).__class__ - explicit = {0: XGate, 1: CXGate, 2: CCXGate} + explicit = {1: CXGate, 2: CCXGate} self.assertEqual(cls, explicit[num_ctrl_qubits]) - @data(0, 3, 4, 5, 8) + @data(3, 4, 5, 8) def test_mcx_gates(self, num_ctrl_qubits): """Test the mcx gates.""" backend = BasicAer.get_backend('statevector_simulator') @@ -860,6 +860,16 @@ def test_open_controlled_gate_raises(self): with self.assertRaises(CircuitError): base_gate.control(num_ctrl_qubits, ctrl_state='201') + @data(-1, 0, 1.4, '1', 4, 10) + def test_improper_num_ctrl_qubits(self, num_ctrl_qubits): + """ + Test improperly specified num_ctrl_qubits. + """ + num_qubits = 4 + with self.assertRaises(CircuitError): + ControlledGate(name='cgate', num_qubits=num_qubits, + params=[], num_ctrl_qubits=num_ctrl_qubits) + def test_open_controlled_equality(self): """ Test open controlled gates are equal if their base gates and control states are equal.