Repository: Qiskit/qiskit

Issue:
Return parametric pulse names
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?
In pusle command definition with parametric pulses, the name of pulse becomes `parametric_pulse` and we cannot infer what is being played, ie pulse drawer.

The QobjInstruction of `y90p` pulse looks like
```
PulseQobjInstruction(name="parametric_pulse", t0=0, ch="d0", pulse_shape="drag", parameters={'amp': (0.00023655993186320415+0.09826971941800593j), 'beta': 0.91118629367975, 'duration': 160, 'sigma': 40})
```
but it is hard to know original pulse name from this information. To tell Qiskit what the pulse entry is, we need to add `label` to Schema so that backend can send pulse default object with proper pulse names.


Discussion:

Resolving patch:
diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py
--- a/qiskit/qobj/converters/pulse_instruction.py
+++ b/qiskit/qobj/converters/pulse_instruction.py
@@ -12,14 +12,15 @@
 
 """Helper class used to convert a pulse instruction into PulseQobjInstruction."""
 
+import hashlib
 import re
 import warnings
 
 from enum import Enum
 
 from qiskit.pulse import channels, instructions, library
-from qiskit.pulse.exceptions import QiskitError
 from qiskit.pulse.configuration import Kernel, Discriminator
+from qiskit.pulse.exceptions import QiskitError
 from qiskit.pulse.parser import parse_string_expr
 from qiskit.pulse.schedule import ParameterizedSchedule, Schedule
 from qiskit.qobj import QobjMeasurementOption
@@ -640,6 +641,15 @@ def convert_named_drive(self, instruction):
     def convert_parametric(self, instruction):
         """Return the ParametricPulse implementation that is described by the instruction.
 
+        If parametric pulse label is not provided by the backend, this method naively generates
+        a pulse name based on the pulse shape and bound parameters. This pulse name is formatted
+        to, for example, `gaussian_a4e3`, here the last four digits are a part of
+        the hash string generated based on the pulse shape and the parameters.
+        Because we are using a truncated hash for readability,
+        there may be a small risk of pulse name collision with other pulses.
+        Basically the parametric pulse name is used just for visualization purpose and
+        the pulse module should not have dependency on the parametric pulse names.
+
         Args:
             instruction (PulseQobjInstruction): pulse qobj
         Returns:
@@ -647,7 +657,19 @@ def convert_parametric(self, instruction):
         """
         t0 = instruction.t0
         channel = self.get_channel(instruction.ch)
-        pulse = ParametricPulseShapes[instruction.pulse_shape].value(**instruction.parameters)
+
+        try:
+            pulse_name = instruction.label
+        except AttributeError:
+            sorted_params = sorted(tuple(instruction.parameters.items()), key=lambda x: x[0])
+            base_str = '{pulse}_{params}'.format(
+                pulse=instruction.pulse_shape,
+                params=str(sorted_params))
+            short_pulse_id = hashlib.md5(base_str.encode('utf-8')).hexdigest()[:4]
+            pulse_name = '{0}_{1}'.format(instruction.pulse_shape, short_pulse_id)
+
+        pulse = ParametricPulseShapes[instruction.pulse_shape].value(**instruction.parameters,
+                                                                     name=pulse_name)
         return instructions.Play(pulse, channel) << t0
 
     @bind_name('snapshot')


Test patch:
diff --git a/test/python/qobj/test_pulse_converter.py b/test/python/qobj/test_pulse_converter.py
--- a/test/python/qobj/test_pulse_converter.py
+++ b/test/python/qobj/test_pulse_converter.py
@@ -12,20 +12,21 @@
 
 """Converter Test."""
 
+import hashlib
 import numpy as np
 
-from qiskit.test import QiskitTestCase
-from qiskit.qobj import (PulseQobjInstruction, PulseQobjExperimentConfig, PulseLibraryItem,
-                         QobjMeasurementOption)
-from qiskit.qobj.converters import (InstructionToQobjConverter, QobjToInstructionConverter,
-                                    LoConfigConverter)
-from qiskit.pulse.instructions import (SetPhase, ShiftPhase, SetFrequency, ShiftFrequency, Play,
-                                       Delay, Acquire, Snapshot)
+from qiskit.pulse import LoConfig, Kernel, Discriminator
 from qiskit.pulse.channels import (DriveChannel, ControlChannel, MeasureChannel, AcquireChannel,
                                    MemorySlot, RegisterSlot)
+from qiskit.pulse.instructions import (SetPhase, ShiftPhase, SetFrequency, ShiftFrequency, Play,
+                                       Delay, Acquire, Snapshot)
 from qiskit.pulse.library import Waveform, Gaussian, GaussianSquare, Constant, Drag
 from qiskit.pulse.schedule import ParameterizedSchedule, Schedule
-from qiskit.pulse import LoConfig, Kernel, Discriminator
+from qiskit.qobj import (PulseQobjInstruction, PulseQobjExperimentConfig, PulseLibraryItem,
+                         QobjMeasurementOption)
+from qiskit.qobj.converters import (InstructionToQobjConverter, QobjToInstructionConverter,
+                                    LoConfigConverter)
+from qiskit.test import QiskitTestCase
 
 
 class TestInstructionToQobjConverter(QiskitTestCase):
@@ -206,9 +207,11 @@ def test_drive_instruction(self):
 
     def test_parametric_pulses(self):
         """Test converted qobj from ParametricInstruction."""
-        instruction = Play(Gaussian(duration=25, sigma=15, amp=-0.5 + 0.2j), DriveChannel(0))
+        instruction = Play(Gaussian(duration=25, sigma=15, amp=-0.5 + 0.2j, name='pulse1'),
+                           DriveChannel(0))
         qobj = PulseQobjInstruction(
             name='parametric_pulse',
+            label='pulse1',
             pulse_shape='gaussian',
             ch='d0',
             t0=0,
@@ -217,6 +220,22 @@ def test_parametric_pulses(self):
         self.assertEqual(converted_instruction.start_time, 0)
         self.assertEqual(converted_instruction.duration, 25)
         self.assertEqual(converted_instruction.instructions[0][-1], instruction)
+        self.assertEqual(converted_instruction.instructions[0][-1].pulse.name, 'pulse1')
+
+    def test_parametric_pulses_no_label(self):
+        """Test converted qobj from ParametricInstruction without label."""
+        base_str = "gaussian_[('amp', (-0.5+0.2j)), ('duration', 25), ('sigma', 15)]"
+        short_pulse_id = hashlib.md5(base_str.encode('utf-8')).hexdigest()[:4]
+        pulse_name = 'gaussian_{}'.format(short_pulse_id)
+
+        qobj = PulseQobjInstruction(
+            name='parametric_pulse',
+            pulse_shape='gaussian',
+            ch='d0',
+            t0=0,
+            parameters={'duration': 25, 'sigma': 15, 'amp': -0.5 + 0.2j})
+        converted_instruction = self.converter(qobj)
+        self.assertEqual(converted_instruction.instructions[0][-1].pulse.name, pulse_name)
 
     def test_frame_change(self):
         """Test converted qobj from ShiftPhase."""