Repository: Qiskit/qiskit-terra (python)

Issue:
bug in scheduling: gate lengths are wrongly attached
```py
from qiskit.circuit.library import HiddenLinearFunction
from qiskit.test.mock import FakeMontreal
from qiskit import transpile
circ = HiddenLinearFunction([[1, 1, 0], [1, 0, 1], [0, 1, 1]])
circ.draw('mpl')
sc = transpile(circ, FakeMontreal(), scheduling_method='asap')
```

here if you look inside sc.data you'll see that the duration of CX_{0,1} is set to the duration of CX_{1,2}=2560 dt, but it should be 1728 dt as reported by backend. Seems like the second CX over-rides the first CX somewhere.


```py
print(FakeMontreal().properties().gate_length('cx', (0, 1)) / FakeMontreal().configuration().dt) # 1728
print(FakeMontreal().properties().gate_length('cx', (1, 2)) / FakeMontreal().configuration().dt) # 2560
```

This bug causes the following obviously wrong timeline:
![image](https://user-images.githubusercontent.com/8622381/106604368-8ab54000-652d-11eb-9dc2-e51875dc5cac.png)


Pull request:
Fix incorrect durations in scheduled circuit
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Summary
Fix #5771 

### Details and comments
Within scheduling passes, `Instruction` object is used to store duration. However, instructions in scheduled circuit was not always guaranteed to have different objects, and failed to report correct durations occasionally. This PR fix the bug by ensuring all instruction objects in scheduled circuit are different.



Patch:
diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py
index 74fd83d1f..e06a1cf2b 100644
--- a/qiskit/transpiler/passes/scheduling/alap.py
+++ b/qiskit/transpiler/passes/scheduling/alap.py
@@ -70,12 +70,14 @@ class ALAPSchedule(TransformationPass):
             start_time = max(qubit_time_available[q] for q in node.qargs)
             pad_with_delays(node.qargs, until=start_time, unit=time_unit)
 
-            new_node = new_dag.apply_operation_front(node.op, node.qargs, node.cargs,
-                                                     node.condition)
             duration = self.durations.get(node.op, node.qargs, unit=time_unit)
+
             # set duration for each instruction (tricky but necessary)
-            new_node.op.duration = duration
-            new_node.op.unit = time_unit
+            new_op = node.op.copy()  # need different op instance to store duration
+            new_op.duration = duration
+            new_op.unit = time_unit
+
+            new_dag.apply_operation_front(new_op, node.qargs, node.cargs, node.condition)
 
             stop_time = start_time + duration
             # update time table
diff --git a/qiskit/transpiler/passes/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/asap.py
index 00dd6fd3b..068b3319f 100644
--- a/qiskit/transpiler/passes/scheduling/asap.py
+++ b/qiskit/transpiler/passes/scheduling/asap.py
@@ -70,11 +70,14 @@ class ASAPSchedule(TransformationPass):
             start_time = max(qubit_time_available[q] for q in node.qargs)
             pad_with_delays(node.qargs, until=start_time, unit=time_unit)
 
-            new_node = new_dag.apply_operation_back(node.op, node.qargs, node.cargs, node.condition)
             duration = self.durations.get(node.op, node.qargs, unit=time_unit)
+
             # set duration for each instruction (tricky but necessary)
-            new_node.op.duration = duration
-            new_node.op.unit = time_unit
+            new_op = node.op.copy()  # need different op instance to store duration
+            new_op.duration = duration
+            new_op.unit = time_unit
+
+            new_dag.apply_operation_back(new_op, node.qargs, node.cargs, node.condition)
 
             stop_time = start_time + duration
             # update time table


Test patch:
diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py
index 3f1790518..1e32d5c48 100644
--- a/test/python/circuit/test_scheduled_circuit.py
+++ b/test/python/circuit/test_scheduled_circuit.py
@@ -13,7 +13,7 @@
 # pylint: disable=missing-function-docstring
 
 """Test scheduled circuit (quantum circuit with duration)."""
-
+from ddt import ddt, data
 from qiskit import QuantumCircuit, QiskitError
 from qiskit import transpile, assemble
 from qiskit.test.mock.backends import FakeParis
@@ -23,6 +23,7 @@ from qiskit.transpiler.instruction_durations import InstructionDurations
 from qiskit.test.base import QiskitTestCase
 
 
+@ddt
 class TestScheduledCircuit(QiskitTestCase):
     """Test scheduled circuit (quantum circuit with duration)."""
     def setUp(self):
@@ -290,3 +291,17 @@ class TestScheduledCircuit(QiskitTestCase):
                               dt=self.dt/2
                               )
         self.assertEqual(scheduled.duration, org_duration*2)
+
+    @data('asap', 'alap')
+    def test_duration_on_same_instruction_instance(self, scheduling_method):
+        """See: https://github.com/Qiskit/qiskit-terra/issues/5771"""
+        assert(self.backend_with_dt.properties().gate_length('cx', (0, 1))
+               != self.backend_with_dt.properties().gate_length('cx', (1, 2)))
+        qc = QuantumCircuit(3)
+        qc.cz(0, 1)
+        qc.cz(1, 2)
+        sc = transpile(qc,
+                       backend=self.backend_with_dt,
+                       scheduling_method=scheduling_method)
+        cxs = [inst for inst, _, _ in sc.data if inst.name == 'cx']
+        self.assertNotEqual(cxs[0].duration, cxs[1].duration)