Repository: Qiskit/qiskit

Issue:
Scheduler adding unnecessary barriers
```
for i in range(2):
 circ.x([qr[i]])
 circ.u1(np.pi, [qr[i]])
 circ.x([qr[i]])
```
if I do a code block like above it pushes the next qubit after the first when I schedule. For example

<img width="744" alt="image" src="https://user-images.githubusercontent.com/32201347/68183260-dd3e3000-ff69-11e9-91b8-eb880c622a88.png">


<!-- ⚠️ 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?



### Steps to reproduce the problem



### What is the expected behavior?



### Suggested solutions





Discussion:
please post all input given to the scheduler

Resolving patch:
diff --git a/qiskit/scheduler/methods/basic.py b/qiskit/scheduler/methods/basic.py
--- a/qiskit/scheduler/methods/basic.py
+++ b/qiskit/scheduler/methods/basic.py
@@ -89,18 +89,18 @@ def as_late_as_possible(circuit: QuantumCircuit,
     Returns:
         A schedule corresponding to the input `circuit` with pulses occurring as late as possible
     """
-    circuit.barrier()  # Adding a final barrier is an easy way to align the channel end times.
     sched = Schedule(name=circuit.name)
-
+    # Align channel end times.
+    circuit.barrier()
     # We schedule in reverse order to get ALAP behaviour. We need to know how far out from t=0 any
     # qubit will become occupied. We add positive shifts to these times as we go along.
-    qubit_available_until = defaultdict(lambda: float("inf"))
+    # The time is initialized to 0 because all qubits are involved in the final barrier.
+    qubit_available_until = defaultdict(lambda: 0)
 
-    def update_times(inst_qubits: List[int], shift: int = 0) -> None:
+    def update_times(inst_qubits: List[int], shift: int = 0, cmd_start_time: int = 0) -> None:
         """Update the time tracker for all inst_qubits to the given time."""
         for q in inst_qubits:
-            # A newly scheduled instruction on q starts at t=0 as we move backwards
-            qubit_available_until[q] = 0
+            qubit_available_until[q] = cmd_start_time
         for q in qubit_available_until.keys():
             if q not in inst_qubits:
                 # Uninvolved qubits might be free for the duration of the new instruction
@@ -108,21 +108,16 @@ def update_times(inst_qubits: List[int], shift: int = 0) -> None:
 
     circ_pulse_defs = translate_gates_to_pulse_defs(circuit, schedule_config)
     for circ_pulse_def in reversed(circ_pulse_defs):
-        if isinstance(circ_pulse_def.schedule, Barrier):
-            update_times(circ_pulse_def.qubits)
-        else:
-            cmd_sched = circ_pulse_def.schedule
-            # The new instruction should end when one of its qubits becomes occupied
-            cmd_start_time = (min([qubit_available_until[q] for q in circ_pulse_def.qubits])
-                              - cmd_sched.duration)
-            if cmd_start_time == float("inf"):
-                # These qubits haven't been used yet, so schedule the instruction at t=0
-                cmd_start_time = 0
-            # We have to translate qubit times forward when the cmd_start_time is negative
-            shift_amount = max(0, -cmd_start_time)
-            cmd_start_time = max(cmd_start_time, 0)
+        cmd_sched = circ_pulse_def.schedule
+        # The new instruction should end when one of its qubits becomes occupied
+        cmd_start_time = (min([qubit_available_until[q] for q in circ_pulse_def.qubits])
+                          - getattr(cmd_sched, 'duration', 0))  # Barrier has no duration
+        # We have to translate qubit times forward when the cmd_start_time is negative
+        shift_amount = max(0, -cmd_start_time)
+        cmd_start_time = max(cmd_start_time, 0)
+        if not isinstance(circ_pulse_def.schedule, Barrier):
             sched = cmd_sched.shift(cmd_start_time).insert(shift_amount, sched, name=sched.name)
-            update_times(circ_pulse_def.qubits, shift_amount)
+        update_times(circ_pulse_def.qubits, shift_amount, cmd_start_time)
     return sched
 
 


Test patch:
diff --git a/test/python/scheduler/test_basic_scheduler.py b/test/python/scheduler/test_basic_scheduler.py
--- a/test/python/scheduler/test_basic_scheduler.py
+++ b/test/python/scheduler/test_basic_scheduler.py
@@ -225,3 +225,58 @@ def test_circuit_name_kept(self):
         self.assertEqual(sched.name, qc.name)
         sched = schedule(qc, self.backend, method="alap")
         self.assertEqual(sched.name, qc.name)
+
+    def test_can_add_gates_into_free_space(self):
+        """The scheduler does some time bookkeeping to know when qubits are free to be
+        scheduled. Make sure this works for qubits that are used in the future. This was
+        a bug, uncovered by this example:
+
+           q0 =  - - - - |X|
+           q1 = |X| |u2| |X|
+
+        In ALAP scheduling, the next operation on qubit 0 would be added at t=0 rather
+        than immediately before the X gate.
+        """
+        qr = QuantumRegister(2)
+        qc = QuantumCircuit(qr)
+        for i in range(2):
+            qc.u2(0, 0, [qr[i]])
+            qc.u1(3.14, [qr[i]])
+            qc.u2(0, 0, [qr[i]])
+        sched = schedule(qc, self.backend, method="alap")
+        expected = Schedule(
+            self.cmd_def.get('u2', [0], 0, 0),
+            self.cmd_def.get('u2', [1], 0, 0),
+            (28, self.cmd_def.get('u1', [0], 3.14)),
+            (28, self.cmd_def.get('u1', [1], 3.14)),
+            (28, self.cmd_def.get('u2', [0], 0, 0)),
+            (28, self.cmd_def.get('u2', [1], 0, 0)))
+        for actual, expected in zip(sched.instructions, expected.instructions):
+            self.assertEqual(actual[0], expected[0])
+            self.assertEqual(actual[1].command, expected[1].command)
+            self.assertEqual(actual[1].channels, expected[1].channels)
+
+    def test_barriers_in_middle(self):
+        """As a follow on to `test_can_add_gates_into_free_space`, similar issues
+        arose for barriers, specifically.
+        """
+        qr = QuantumRegister(2)
+        qc = QuantumCircuit(qr)
+        for i in range(2):
+            qc.u2(0, 0, [qr[i]])
+            qc.barrier(qr[i])
+            qc.u1(3.14, [qr[i]])
+            qc.barrier(qr[i])
+            qc.u2(0, 0, [qr[i]])
+        sched = schedule(qc, self.backend, method="alap")
+        expected = Schedule(
+            self.cmd_def.get('u2', [0], 0, 0),
+            self.cmd_def.get('u2', [1], 0, 0),
+            (28, self.cmd_def.get('u1', [0], 3.14)),
+            (28, self.cmd_def.get('u1', [1], 3.14)),
+            (28, self.cmd_def.get('u2', [0], 0, 0)),
+            (28, self.cmd_def.get('u2', [1], 0, 0)))
+        for actual, expected in zip(sched.instructions, expected.instructions):
+            self.assertEqual(actual[0], expected[0])
+            self.assertEqual(actual[1].command, expected[1].command)
+            self.assertEqual(actual[1].channels, expected[1].channels)