Repository: Qiskit/qiskit

Issue:
Pulse assember overwrite pulse from another schedule
<!-- ⚠️ 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?

When we have two different pulses with the same name in different schedules, assembler combines those pulses.

### Steps to reproduce the problem

```
from qiskit.test.mock.backends import FakeAlmaden
import qiskit

backend = FakeAlmaden()

schedules = []
for amp in (0.1, 0.2):
    sched = qiskit.pulse.Schedule()
    sched += qiskit.pulse.pulse_lib.gaussian(duration=100, amp=amp, sigma=30, name='my_gaussian')(qiskit.pulse.DriveChannel(0))
    sched += qiskit.scheduler.utils.measure(qubits=(0, ), backend=backend) << 100
    schedules.append(sched)

qobj = qiskit.assemble(schedules, backend)

for pulse_item in qobj.config.pulse_library:
    print(pulse_item.name)
```
this outputs
```
my_gaussian
M_m0_5637
```
you can see two `my_gaussian` pulses with different samples are combined.

### What is the expected behavior?

The second `my_gaussian` pulse should be renamed and independently registered to the pulse library.

### Suggested solutions





Discussion:

Resolving patch:
diff --git a/qiskit/assembler/assemble_schedules.py b/qiskit/assembler/assemble_schedules.py
--- a/qiskit/assembler/assemble_schedules.py
+++ b/qiskit/assembler/assemble_schedules.py
@@ -13,8 +13,8 @@
 # that they have been altered from the originals.
 
 """Assemble function for converting a list of circuits into a qobj."""
-from typing import Any, Dict, List, Tuple
 from collections import defaultdict
+from typing import Any, Dict, List, Tuple
 
 from qiskit.exceptions import QiskitError
 from qiskit.pulse import Schedule
@@ -99,11 +99,11 @@ def _assemble_experiments(
     user_pulselib = {}
     experiments = []
     for idx, schedule in enumerate(schedules):
-        qobj_instructions, user_pulses, max_memory_slot = _assemble_instructions(
+        qobj_instructions, max_memory_slot = _assemble_instructions(
             schedule,
             instruction_converter,
-            run_config)
-        user_pulselib.update(user_pulses)
+            run_config,
+            user_pulselib)
 
         # TODO: add other experimental header items (see circuit assembler)
         qobj_experiment_header = QobjExperimentHeader(
@@ -144,16 +144,21 @@ def _assemble_experiments(
 def _assemble_instructions(
         schedule: Schedule,
         instruction_converter: InstructionToQobjConverter,
-        run_config: RunConfig
-) -> Tuple[List[PulseQobjInstruction], Dict[str, Command], int]:
+        run_config: RunConfig,
+        user_pulselib: Dict[str, Command]
+) -> Tuple[List[PulseQobjInstruction], int]:
     """Assembles the instructions in a schedule into a list of PulseQobjInstructions and returns
-    related metadata that will be assembled into the Qobj configuration.
+    related metadata that will be assembled into the Qobj configuration. Lookup table for
+    pulses defined in all experiments are registered in ``user_pulselib``. This object should be
+    mutable python dictionary so that items are properly updated after each instruction assemble.
+    The dictionary is not returned to avoid redundancy.
 
     Args:
         schedule: Schedule to assemble.
         instruction_converter: A converter instance which can convert PulseInstructions to
                                PulseQobjInstructions.
         run_config: Configuration of the runtime environment.
+        user_pulselib: User pulse library from previous schedule.
 
     Returns:
         A list of converted instructions, the user pulse library dictionary (from pulse name to
@@ -161,7 +166,6 @@ def _assemble_instructions(
     """
     max_memory_slot = 0
     qobj_instructions = []
-    user_pulselib = {}
 
     acquire_instruction_map = defaultdict(list)
     for time, instruction in schedule.instructions:
@@ -209,7 +213,7 @@ def _assemble_instructions(
                     time, instructions[0],
                     qubits=qubits, memory_slot=mem_slots, register_slot=reg_slots))
 
-    return qobj_instructions, user_pulselib, max_memory_slot
+    return qobj_instructions, max_memory_slot
 
 
 def _validate_meas_map(instruction_map: Dict[Tuple[int, Acquire], List[AcquireInstruction]],


Test patch:
diff --git a/test/python/compiler/test_assembler.py b/test/python/compiler/test_assembler.py
--- a/test/python/compiler/test_assembler.py
+++ b/test/python/compiler/test_assembler.py
@@ -17,7 +17,6 @@
 import unittest
 
 import numpy as np
-
 import qiskit.pulse as pulse
 from qiskit.circuit import Instruction, Parameter
 from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit
@@ -25,10 +24,12 @@
 from qiskit.exceptions import QiskitError
 from qiskit.pulse import Schedule, Acquire
 from qiskit.pulse.channels import MemorySlot, AcquireChannel, DriveChannel, MeasureChannel
+from qiskit.pulse.pulse_lib import gaussian
 from qiskit.qobj import QasmQobj, validate_qobj_against_schema
 from qiskit.qobj.utils import MeasLevel, MeasReturnType
+from qiskit.scheduler import measure
 from qiskit.test import QiskitTestCase
-from qiskit.test.mock import FakeOpenPulse2Q, FakeOpenPulse3Q, FakeYorktown
+from qiskit.test.mock import FakeOpenPulse2Q, FakeOpenPulse3Q, FakeYorktown, FakeAlmaden
 from qiskit.validation.jsonschema import SchemaValidationError
 
 
@@ -575,6 +576,23 @@ def test_pulse_name_conflicts(self):
         self.assertEqual(qobj.experiments[0].instructions[0].name, 'pulse0')
         self.assertNotEqual(qobj.experiments[0].instructions[1].name, 'pulse0')
 
+    def test_pulse_name_conflicts_in_other_schedule(self):
+        """Test two pulses with the same name in different schedule can be resolved."""
+        backend = FakeAlmaden()
+
+        schedules = []
+        ch_d0 = pulse.DriveChannel(0)
+        for amp in (0.1, 0.2):
+            sched = Schedule()
+            sched += gaussian(duration=100, amp=amp, sigma=30, name='my_pulse')(ch_d0)
+            sched += measure(qubits=[0], backend=backend) << 100
+            schedules.append(sched)
+
+        qobj = assemble(schedules, backend)
+
+        # two user pulses and one measurement pulse should be contained
+        self.assertEqual(len(qobj.config.pulse_library), 3)
+
     def test_assemble_with_delay(self):
         """Test that delay instruction is ignored in assembly."""
         backend = FakeOpenPulse2Q()