Repository: Qiskit/qiskit

Issue:
Pulse: seconds_to_samples() fails when passed an array
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: 0.16.1
- **Python version**: 3.7
- **Operating system**: Linux (under WSL)

### What is the current behavior?

Passing a numpy array to ``qiskit.pulse.seconds_to_samples()`` raises an error: ``TypeError: only size-1 arrays can be converted to Python scalars``.

### Steps to reproduce the problem

Run the following test script:

```python
import numpy as np
import qiskit.pulse as qp
import qiskit.test.mock.fake_openpulse_2q as be

with qp.build(be.FakeOpenPulse2Q()) as test:
    times_s = np.array([1e-6, 1e-5])
    time_dt = qp.seconds_to_samples(times_s)
```

Traceback:
```
$ python ./test_qp_seconds_to_samples.py
Traceback (most recent call last):
  File "./test_qp_seconds_to_samples.py", line 7, in <module>
    time_dt = qp.seconds_to_samples(times_s)
  File "/nix/store/0wf80vpsyn5jmnarclzmdblwyaanhpw0-python3.7-qiskit-terra-0.16.1/lib/python3.7/site-packages/qiskit/pulse/builder.py", line 665, in seconds_to_samples
    return int(seconds / active_backend().configuration().dt)
TypeError: only size-1 arrays can be converted to Python scalars
```

### What is the expected behavior?

Based on the function signature, this should convert a list of numpy values from seconds to "dt". https://github.com/Qiskit/qiskit-terra/blob/ce15f8a5522ff2be865b6520659171288cbae43d/qiskit/pulse/builder.py#L731-L742

### Suggested solutions

Either change the function signature, or have conditional logic based on the type of the input argument ``seconds``: i.e. only apply ``int(result)`` if ``not isinstance(seconds, np.ndarray))``



Discussion:

Resolving patch:
diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py
--- a/qiskit/pulse/builder.py
+++ b/qiskit/pulse/builder.py
@@ -740,6 +740,8 @@ def seconds_to_samples(seconds: Union[float, np.ndarray]) -> Union[int, np.ndarr
     Returns:
         The number of samples for the time to elapse
     """
+    if isinstance(seconds, np.ndarray):
+        return (seconds / active_backend().configuration().dt).astype(int)
     return int(seconds / active_backend().configuration().dt)
 
 
diff --git a/qiskit/pulse/schedule.py b/qiskit/pulse/schedule.py
--- a/qiskit/pulse/schedule.py
+++ b/qiskit/pulse/schedule.py
@@ -1457,9 +1457,9 @@ def draw(self,
 
     Args:
         style: Stylesheet options. This can be dictionary or preset stylesheet classes. See
-            :py:class:~`qiskit.visualization.pulse_v2.stylesheets.IQXStandard`,
-            :py:class:~`qiskit.visualization.pulse_v2.stylesheets.IQXSimple`, and
-            :py:class:~`qiskit.visualization.pulse_v2.stylesheets.IQXDebugging` for details of
+            :py:class:`~qiskit.visualization.pulse_v2.stylesheets.IQXStandard`,
+            :py:class:`~qiskit.visualization.pulse_v2.stylesheets.IQXSimple`, and
+            :py:class:`~qiskit.visualization.pulse_v2.stylesheets.IQXDebugging` for details of
             preset stylesheets.
         backend (Optional[BaseBackend]): Backend object to play the input pulse program.
             If provided, the plotter may use to make the visualization hardware aware.


Test patch:
diff --git a/test/python/pulse/test_builder.py b/test/python/pulse/test_builder.py
--- a/test/python/pulse/test_builder.py
+++ b/test/python/pulse/test_builder.py
@@ -641,8 +641,18 @@ def test_samples_to_seconds(self):
             self.assertTrue(isinstance(time, float))
             self.assertEqual(pulse.samples_to_seconds(100), 10)
 
+    def test_samples_to_seconds_array(self):
+        """Test samples to time (array format)."""
+        config = self.backend.configuration()
+        config.dt = 0.1
+        with pulse.build(self.backend):
+            samples = np.array([100, 200, 300])
+            times = pulse.samples_to_seconds(samples)
+            self.assertTrue(np.issubdtype(times.dtype, np.floating))
+            np.testing.assert_allclose(times, np.array([10, 20, 30]))
+
     def test_seconds_to_samples(self):
-        """Test samples to time"""
+        """Test time to samples"""
         config = self.backend.configuration()
         config.dt = 0.1
         with pulse.build(self.backend):
@@ -650,6 +660,17 @@ def test_seconds_to_samples(self):
             self.assertTrue(isinstance(samples, int))
             self.assertEqual(pulse.seconds_to_samples(10), 100)
 
+    def test_seconds_to_samples_array(self):
+        """Test time to samples (array format)."""
+        config = self.backend.configuration()
+        config.dt = 0.1
+        with pulse.build(self.backend):
+            times = np.array([10, 20, 30])
+            samples = pulse.seconds_to_samples(times)
+            self.assertTrue(np.issubdtype(samples.dtype, np.integer))
+            np.testing.assert_allclose(pulse.seconds_to_samples(times),
+                                       np.array([100, 200, 300]))
+
 
 class TestMacros(TestBuilder):
     """Test builder macros."""