Repository: Qiskit/qiskit

Issue:
pulse builder measure cannot handle multiple qubits
<!-- ⚠️ 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**:
- **Python version**:
- **Operating system**:

### What is the current behavior?

A code inserts multiple `pulse.measure` instruction creates strange output.

![image](https://user-images.githubusercontent.com/39517270/88929910-ba9d3580-d2b5-11ea-832e-999ff6593960.png)

### Steps to reproduce the problem

```python
from qiskit import pulse

with pulse.build(backend) as sched:
    with pulse.align_left():
        for i in range(5):
            pulse.measure(i)
```

### What is the expected behavior?

All measure and acquire instructions are aligned in parallel.

### Suggested solutions

This is a defect arising from the constraints of IQX backends measurement operation. Currently it inserts the acquire instruction for all qubits while a measurement pulse is inserted for the specific qubit. Thus repeating `pulse.measure` occupies timeslots of other acquire channels and they cannot be aligned. 

This issue will be automatically solved when the acquisition for single qubit is supported by backend, or we can temporarily solve  this by allowing iterator as `qubit` argument.


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
@@ -212,6 +212,7 @@
     Tuple,
     TypeVar,
     Union,
+    NewType
 )
 
 import numpy as np
@@ -235,6 +236,8 @@
 
 T = TypeVar('T')  # pylint: disable=invalid-name
 
+StorageLocation = NewType('StorageLocation', Union[chans.MemorySlot, chans.RegisterSlot])
+
 
 def _compile_lazy_circuit_before(function: Callable[..., T]
                                  ) -> Callable[..., T]:
@@ -1292,7 +1295,7 @@ def play(pulse: Union[library.Pulse, np.ndarray],
 
 def acquire(duration: int,
             qubit_or_channel: Union[int, chans.AcquireChannel],
-            register: Union[chans.RegisterSlot, chans.MemorySlot],
+            register: StorageLocation,
             **metadata: Union[configuration.Kernel,
                               configuration.Discriminator]):
     """Acquire for a ``duration`` on a ``channel`` and store the result
@@ -1636,9 +1639,9 @@ def barrier(*channels_or_qubits: Union[chans.Channel, int]):
 
 
 # Macros
-def measure(qubit: int,
-            register: Union[chans.MemorySlot, chans.RegisterSlot] = None,
-            ) -> Union[chans.MemorySlot, chans.RegisterSlot]:
+def measure(qubits: Union[List[int], int],
+            registers: Union[List[StorageLocation], StorageLocation] = None,
+            ) -> Union[List[StorageLocation], StorageLocation]:
     """Measure a qubit within the currently active builder context.
 
     At the pulse level a measurement is composed of both a stimulus pulse and
@@ -1686,25 +1689,39 @@ def measure(qubit: int,
     .. note:: Requires the active builder context to have a backend set.
 
     Args:
-        qubit: Physical qubit to measure.
-        register: Register to store result in. If not selected the current
+        qubits: Physical qubit to measure.
+        registers: Register to store result in. If not selected the current
             behaviour is to return the :class:`MemorySlot` with the same
             index as ``qubit``. This register will be returned.
     Returns:
         The ``register`` the qubit measurement result will be stored in.
     """
     backend = active_backend()
-    if not register:
-        register = chans.MemorySlot(qubit)
+
+    try:
+        qubits = list(qubits)
+    except TypeError:
+        qubits = [qubits]
+
+    if registers is None:
+        registers = [chans.MemorySlot(qubit) for qubit in qubits]
+    else:
+        try:
+            registers = list(registers)
+        except TypeError:
+            registers = [registers]
 
     measure_sched = macros.measure(
-        qubits=[qubit],
+        qubits=qubits,
         inst_map=backend.defaults().instruction_schedule_map,
         meas_map=backend.configuration().meas_map,
-        qubit_mem_slots={register.index: register.index})
+        qubit_mem_slots={qubit: register.index for qubit, register in zip(qubits, registers)})
     call_schedule(measure_sched)
 
-    return register
+    if len(qubits) == 1:
+        return registers[0]
+    else:
+        return registers
 
 
 def measure_all() -> List[chans.MemorySlot]:


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
@@ -685,6 +685,20 @@ def test_measure(self):
 
         self.assertEqual(schedule, reference)
 
+    def test_measure_multi_qubits(self):
+        """Test utility function - measure with multi qubits."""
+        with pulse.build(self.backend) as schedule:
+            regs = pulse.measure([0, 1])
+
+        self.assertListEqual(regs, [pulse.MemorySlot(0), pulse.MemorySlot(1)])
+
+        reference = macros.measure(
+            qubits=[0, 1],
+            inst_map=self.inst_map,
+            meas_map=self.configuration.meas_map)
+
+        self.assertEqual(schedule, reference)
+
     def test_measure_all(self):
         """Test utility function - measure."""
         with pulse.build(self.backend) as schedule: