Repository: Qiskit/qiskit

Issue:
Supported meas_levels should be validated on client-side at assembly
<!-- ⚠️ 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?
The new `ibmq_armonk` device only supports `meas_levels=[1,2]`. An optional validation error should be raised if a user tries to submit a `meas_level=1` job. This is related to #3606.


Discussion:
Hi @taalexander , I would like to have a look at this. 
Should I (just) add a validation error to this, similar to #3606? 
I see that #3606 also includes a test, but I don't know if that's necessary for this? 
Thank you @eembees! I think you have the right idea @eembees. A test would be nice to verify this is working you could just use an empty schedule `pulse.Schedule()` and add it to `test.python.compiler.test_assembler`.
I implemented a basic fix, but I have some clarifying questions I want to have cleared up pre-PR:
1. I assume it should raise a ValidationError when `meas_levels = 0`, not 1? 
2. I don't exactly know how to make the validation optional.
 I suppose we could add a keyword `validate=T/F` but I don't see exactly where this should be located - should it be in `backendconfiguration`? Or should we put it directly in the `assemble` function?. Any guidance on this would be super useful!
3. If we don't add the validate kw, there are two tests in `test.python.result.test_results`: `test_meas_level_0_avg` and `test_meas_level_0_single`,  that I think could be removed, as it doesn't make sense to keep them around if we don't support anything with `meas_level=0`. Correct me if I'm wrong on this, please.

@taalexander tagging you so you will see it. 
1. Yes, I think that is what he meant. Rather than hardcoding, you should check if `meas_level` given by the user is not in the list `backend.configuration().meas_levels`
2. I think it would be an argument to `assemble`. However, since Matt is likely to do the same work for #3606, I'd ignore that requirement for now, and copy/rebase off his work to avoid redundant work and conflicts.
3. Meas level 0 is sometimes supported, just not by `ibmq_armonk`, so we can leave those tests 👍 

Thanks again for your patience, and welcome to our Terra community! 🎉 

Resolving patch:
diff --git a/qiskit/compiler/assemble.py b/qiskit/compiler/assemble.py
--- a/qiskit/compiler/assemble.py
+++ b/qiskit/compiler/assemble.py
@@ -24,6 +24,7 @@
 from qiskit.qobj import QobjHeader
 from qiskit.validation.exceptions import ModelValidationError
 from qiskit.qobj.utils import MeasLevel, MeasReturnType
+from qiskit.validation.jsonschema import SchemaValidationError
 
 
 # TODO: parallelize over the experiments (serialize each separately, then add global header/config)
@@ -230,6 +231,8 @@ def _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq, qubit_lo_range,
     Returns:
         RunConfig: a run config, which is a standardized object that configures the qobj
             and determines the runtime environment.
+    Raises:
+        SchemaValidationError: if the given meas_level is not allowed for the given `backend`.
     """
     # grab relevant info from backend if it exists
     backend_config = None
@@ -248,6 +251,12 @@ def _parse_pulse_args(backend, qubit_lo_freq, meas_lo_freq, qubit_lo_range,
                 meas_freq_est=backend_config_defaults.get('meas_freq_est')
             )
 
+        if meas_level not in getattr(backend_config, 'meas_levels', [MeasLevel.CLASSIFIED]):
+            raise SchemaValidationError(
+                ('meas_level = {} not supported for backend {}, only {} is supported'
+                 ).format(meas_level, backend_config.backend_name, backend_config.meas_levels)
+            )
+
     meas_map = meas_map or getattr(backend_config, 'meas_map', None)
 
     schedule_los = schedule_los or []


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
@@ -28,6 +28,7 @@
 from qiskit.qobj.utils import MeasLevel, MeasReturnType
 from qiskit.test import QiskitTestCase
 from qiskit.test.mock import FakeOpenPulse2Q
+from qiskit.validation.jsonschema import SchemaValidationError
 
 
 class TestCircuitAssembler(QiskitTestCase):
@@ -688,6 +689,25 @@ def test_missing_lo_ranges(self):
                         rep_time=self.rep_time)
         validate_qobj_against_schema(qobj)
 
+    def test_unsupported_meas_level(self):
+        """Test that assembly raises an error if meas_level is not supported"""
+        # pylint: disable=unused-variable
+        backend = FakeOpenPulse2Q()
+        backend.configuration().meas_levels = [1, 2]
+        with self.assertRaises(SchemaValidationError):
+            qobj = assemble(self.schedule,
+                            backend,
+                            qubit_lo_freq=self.qubit_lo_freq,
+                            meas_lo_freq=self.meas_lo_freq,
+                            qubit_lo_range=self.qubit_lo_range,
+                            meas_lo_range=self.meas_lo_range,
+                            schedule_los=self.schedule_los,
+                            meas_level=0,
+                            meas_map=self.meas_map,
+                            memory_slots=self.memory_slots,
+                            rep_time=self.rep_time,
+                            )
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)