Repository: Qiskit/qiskit

Issue:
Pulse's channel index validation fails
### Environment

- **Qiskit Terra version**: 0.25.0 (main)
- **Python version**: 3.9
- **Operating system**: Windows


### What is happening?

Channel index validation doesn't produce a `PulseError` when the channel is either non-integer or negative. Only when both are true.


### How can we reproduce the issue?

The following should produce a `PulseError`, but it doesn't:

```
from qiskit import pulse

pulse.DriveChannel(0.5)
pulse.DriveChannel(-1)
```

Because of [this line](https://github.com/Qiskit/qiskit-terra/blob/e55389f3f05e2d871fdea3814917c93b5c280e93/qiskit/pulse/channels.py#L124), only when the index is both not an integer *and* negative, an error is raised. This does raise a `PulseError`: 
```
pulse.DriveChannel(-1.5)
```

### What should happen?

A `PulseError` should be raised if either of the conditions are met.

### Any suggestions?

_No response_


Discussion:

Resolving patch:
diff --git a/qiskit/pulse/channels.py b/qiskit/pulse/channels.py
--- a/qiskit/pulse/channels.py
+++ b/qiskit/pulse/channels.py
@@ -121,7 +121,7 @@ def _validate_index(self, index: Any) -> None:
             if index.is_integer():
                 index = int(index)
 
-        if not isinstance(index, (int, np.integer)) and index < 0:
+        if not isinstance(index, (int, np.integer)) or index < 0:
             raise PulseError("Channel index must be a nonnegative integer")
 
     @property


Test patch:
diff --git a/test/python/pulse/test_channels.py b/test/python/pulse/test_channels.py
--- a/test/python/pulse/test_channels.py
+++ b/test/python/pulse/test_channels.py
@@ -25,6 +25,7 @@
     PulseChannel,
     RegisterSlot,
     SnapshotChannel,
+    PulseError,
 )
 from qiskit.test import QiskitTestCase
 
@@ -88,6 +89,13 @@ def test_default(self):
         self.assertEqual(memory_slot.name, "m123")
         self.assertTrue(isinstance(memory_slot, ClassicalIOChannel))
 
+    def test_validation(self):
+        """Test channel validation"""
+        with self.assertRaises(PulseError):
+            MemorySlot(0.5)
+        with self.assertRaises(PulseError):
+            MemorySlot(-1)
+
 
 class TestRegisterSlot(QiskitTestCase):
     """RegisterSlot tests."""
@@ -100,6 +108,13 @@ def test_default(self):
         self.assertEqual(register_slot.name, "c123")
         self.assertTrue(isinstance(register_slot, ClassicalIOChannel))
 
+    def test_validation(self):
+        """Test channel validation"""
+        with self.assertRaises(PulseError):
+            RegisterSlot(0.5)
+        with self.assertRaises(PulseError):
+            RegisterSlot(-1)
+
 
 class TestSnapshotChannel(QiskitTestCase):
     """SnapshotChannel tests."""
@@ -123,6 +138,13 @@ def test_default(self):
         self.assertEqual(drive_channel.index, 123)
         self.assertEqual(drive_channel.name, "d123")
 
+    def test_validation(self):
+        """Test channel validation"""
+        with self.assertRaises(PulseError):
+            DriveChannel(0.5)
+        with self.assertRaises(PulseError):
+            DriveChannel(-1)
+
 
 class TestControlChannel(QiskitTestCase):
     """ControlChannel tests."""
@@ -134,6 +156,13 @@ def test_default(self):
         self.assertEqual(control_channel.index, 123)
         self.assertEqual(control_channel.name, "u123")
 
+    def test_validation(self):
+        """Test channel validation"""
+        with self.assertRaises(PulseError):
+            ControlChannel(0.5)
+        with self.assertRaises(PulseError):
+            ControlChannel(-1)
+
 
 class TestMeasureChannel(QiskitTestCase):
     """MeasureChannel tests."""
@@ -145,6 +174,13 @@ def test_default(self):
         self.assertEqual(measure_channel.index, 123)
         self.assertEqual(measure_channel.name, "m123")
 
+    def test_validation(self):
+        """Test channel validation"""
+        with self.assertRaises(PulseError):
+            MeasureChannel(0.5)
+        with self.assertRaises(PulseError):
+            MeasureChannel(-1)
+
 
 if __name__ == "__main__":
     unittest.main()