Repository: Qiskit/qiskit

Issue:
Converting custom pulse play instructions to Qobj raises an error
### Environment

- **Qiskit version**: 1.0
- **Python version**: 3.9
- **Operating system**: Windows


### What is happening?

When you try to convert to Qobj a circuit with a custom pulse in it, a `QiskitError` is raised.

### How can we reproduce the issue?

```python
import qiskit
from qiskit import pulse
from qiskit import circuit
from qiskit.providers.fake_provider import GenericBackendV2

with pulse.build() as sch:
    pulse.play(pulse.Triangle(32, 0.1, 0.2), pulse.DriveChannel(0))

qc = circuit.QuantumCircuit(1, 1)
qc.x(0)
qc.measure_all()
qc.add_calibration("x", (0,), sch)

transpiled = qiskit.transpile(qc, backend=GenericBackendV2(2))

qiskit.assemble(transpiled)
```
The pulse choice here is immaterial, as long as it is a pulse type which ~~is not recognized by the backend~~ does not appear in [ParametricPulseShapes](https://github.com/Qiskit/qiskit/blob/12f24a76b6fbba4ebd9d7cf09c42f2c6c6e3c8f7/qiskit/qobj/converters/pulse_instruction.py#L35). `Triangle` ~~is not recognized by the backend~~ does not appear there. ~~(Edit - `GenericBackendV2` doesn't define the field `parametric_pulses` so all pulse types will behave the same way here).~~

### What should happen?

The circuit should convert successfully to Qobj.

### Any suggestions?

PR #11410 changed the error raised in [ParametricPulseShapes](https://github.com/Qiskit/qiskit/blob/12f24a76b6fbba4ebd9d7cf09c42f2c6c6e3c8f7/qiskit/qobj/converters/pulse_instruction.py#L68) from a `ValueError` to a `QiskitError`. However, other modules relied on the `ValueError` to identify pulses not supported by the backend - [here](https://github.com/Qiskit/qiskit/blob/12f24a76b6fbba4ebd9d7cf09c42f2c6c6e3c8f7/qiskit/assembler/assemble_schedules.py#L233).


Discussion:
Thanks @yiiyama for the initial report.

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
@@ -62,10 +62,10 @@ def from_instance(
         Raises:
             QiskitError: When pulse instance is not recognizable type.
         """
-        try:
+        if isinstance(instance, library.SymbolicPulse):
             return cls(instance.pulse_type)
-        except ValueError as ex:
-            raise QiskitError(f"'{instance}' is not valid pulse type.") from ex
+
+        raise QiskitError(f"'{instance}' is not valid pulse type.")
 
     @classmethod
     def to_type(cls, name: str) -> library.SymbolicPulse:


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
@@ -528,6 +528,24 @@ def test_pulse_gates_single_circ(self):
         self.assertEqual(len(lib), 2)
         self.assertTrue(all(len(item.samples) == 50 for item in lib))
 
+    def test_custom_pulse_gates_single_circ(self):
+        """Test that we can add calibrations to circuits of pulses which are not in
+        qiskit.qobj.converters.pulse_instruction.ParametricPulseShapes"""
+        circ = QuantumCircuit(2)
+        circ.h(0)
+
+        with pulse.build() as custom_h_schedule:
+            pulse.play(pulse.library.Triangle(50, 0.1, 0.2), pulse.DriveChannel(0))
+
+        circ.add_calibration("h", [0], custom_h_schedule)
+
+        qobj = assemble(circ, FakeOpenPulse2Q())
+        lib = qobj.config.pulse_library
+        self.assertEqual(len(lib), 1)
+        np.testing.assert_almost_equal(
+            lib[0].samples, pulse.library.Triangle(50, 0.1, 0.2).get_waveform().samples
+        )
+
     def test_pulse_gates_with_parameteric_pulses(self):
         """Test that pulse gates are assembled efficiently for backends that enable
         parametric pulses.