Repository: Qiskit/qiskit

Issue:
Using multiple SamplePulses with the same name overwritten in Qobj pulse library
<!-- ⚠️ 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?
In the Qobj specification, pulse are identified by their name. In the pulse module pulses are identified by their object instance and when assembled are stored in the Qobj pulse library by name. If two `SamplePulse`s of the same name exist, the second will overwrite the first, causing undesired behaviour. 


### Steps to reproduce the problem
Create a pulse schedule with two different pulses of the same name and assemble into a Qobj.


### What is the expected behavior?
Unique pulses should be stored uniquely in the Qobj pulse library.


### Suggested solutions
If a name collision occurs when building the pulse library. Modify the name of the sample pulse in some consistent manner, and adjust the commands that refer to this pulse. 




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
@@ -16,7 +16,7 @@
 import logging
 
 from qiskit.exceptions import QiskitError
-from qiskit.pulse.commands import PulseInstruction, AcquireInstruction
+from qiskit.pulse.commands import PulseInstruction, AcquireInstruction, SamplePulse
 from qiskit.qobj import (PulseQobj, QobjExperimentHeader,
                          PulseQobjInstruction, PulseQobjExperimentConfig,
                          PulseQobjExperiment, PulseQobjConfig, PulseLibraryItem)
@@ -53,21 +53,28 @@ def assemble_schedules(schedules, qobj_id, qobj_header, run_config):
 
     # Pack everything into the Qobj
     qobj_schedules = []
-    user_pulselib = set()
+    user_pulselib = {}
     for idx, schedule in enumerate(schedules):
         # instructions
         qobj_instructions = []
         # Instructions are returned as tuple of shifted time and instruction
         for shift, instruction in schedule.instructions:
             # TODO: support conditional gate
-            qobj_instructions.append(instruction_converter(shift, instruction))
             if isinstance(instruction, PulseInstruction):
+                name = instruction.command.name
+                if name in user_pulselib and instruction.command != user_pulselib[name]:
+                    name = "{0}-{1:x}".format(name, hash(instruction.command.samples.tostring()))
+                    instruction = PulseInstruction(
+                        command=SamplePulse(name=name, samples=instruction.command.samples),
+                        name=instruction.name,
+                        channel=instruction.timeslots.channels[0])
                 # add samples to pulse library
-                user_pulselib.add(instruction.command)
+                user_pulselib[name] = instruction.command
             if isinstance(instruction, AcquireInstruction):
                 if meas_map:
                     # verify all acquires satisfy meas_map
                     _validate_meas_map(instruction, meas_map)
+            qobj_instructions.append(instruction_converter(shift, instruction))
 
         # experiment header
         qobj_experiment_header = QobjExperimentHeader(
@@ -81,7 +88,7 @@ def assemble_schedules(schedules, qobj_id, qobj_header, run_config):
 
     # setup pulse_library
     qobj_config['pulse_library'] = [PulseLibraryItem(name=pulse.name, samples=pulse.samples)
-                                    for pulse in user_pulselib]
+                                    for pulse in user_pulselib.values()]
 
     # create qob experiment field
     experiments = []


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
@@ -257,7 +257,8 @@ def setUp(self):
         self.device = pulse.DeviceSpecification.create_from(FakeOpenPulse2Q())
 
         test_pulse = pulse.SamplePulse(
-            samples=np.array([0.02739068, 0.05, 0.05, 0.05, 0.02739068], dtype=np.complex128)
+            samples=np.array([0.02739068, 0.05, 0.05, 0.05, 0.02739068], dtype=np.complex128),
+            name='pulse0'
         )
         acquire = pulse.Acquire(5)
 
@@ -390,6 +391,24 @@ def test_assemble_meas_map(self):
         with self.assertRaises(QiskitError):
             assemble(schedule, meas_map=[[0, 1, 2]])
 
+    def test_pulse_name_conflicts(self):
+        """Test that pulse name conflicts can be resolved."""
+        name_conflict_pulse = pulse.SamplePulse(
+            samples=np.array([0.02, 0.05, 0.05, 0.05, 0.02], dtype=np.complex128),
+            name='pulse0'
+        )
+        self.schedule = self.schedule.insert(1, name_conflict_pulse(self.device.q[1].drive))
+        qobj = assemble(self.schedule,
+                        qobj_header=self.header,
+                        qubit_lo_freq=self.default_qubit_lo_freq,
+                        meas_lo_freq=self.default_meas_lo_freq,
+                        schedule_los=[],
+                        **self.config)
+
+        self.assertNotEqual(qobj.config.pulse_library[1], 'pulse0')
+        self.assertEqual(qobj.experiments[0].instructions[0].name, 'pulse0')
+        self.assertNotEqual(qobj.experiments[0].instructions[1].name, 'pulse0')
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)