Repository: Qiskit/qiskit

Issue:
`FakePrague` list of supported operations is incorrect
Looking at this list:
```python
from qiskit.providers.fake_provider import FakePrague
backend = FakePrague()
list(zip(backend.operation_names, [type(x) for x in backend.operations]))
```
```python
[('id', qiskit.circuit.library.standard_gates.i.IGate),
 ('rz', qiskit.circuit.library.standard_gates.rz.RZGate),
 ('sx', qiskit.circuit.library.standard_gates.sx.SXGate),
 ('x', qiskit.circuit.library.standard_gates.x.XGate),
 ('cz', qiskit.circuit.gate.Gate),
 ('reset', qiskit.circuit.reset.Reset),
 ('measure', qiskit.circuit.measure.Measure),
 ('delay', qiskit.circuit.delay.Delay)]
```

you see that the entry for `cz` is not an instance of `CZGate`.  This is apparently the origin of the bug referenced 
in #9935 #9937 #9982 .

The gate with name `cz` in the list above is not recognized as a CZ gate when doing unitary synthesis here
https://github.com/Qiskit/qiskit-terra/blob/971a203deb33bdc309b1467d00e39efc5eda8936/qiskit/transpiler/passes/synthesis/unitary_synthesis.py#L646
and here
https://github.com/Qiskit/qiskit-terra/blob/971a203deb33bdc309b1467d00e39efc5eda8936/qiskit/transpiler/passes/synthesis/unitary_synthesis.py#L677

Furthermore:
```python
In [37]: badcz = backend.operations[4]

In [38]: badcz.definition  # None

In [39]: badcz.to_matrix()  # Raises error CircuitError: "to_matrix not defined for this <class 'qiskit.circuit.gate.Gate'
```


Discussion:
That's a really good catch and yeah that's definitely the underlying cause of the issue. It still points to a bug in the synthesis path because we should be able to run `transpile()` without erroring in the case of a custom 2q basis gate. But that's independent of fixing the target construction for the fake backends.

The root cause is the custom target builder for the fake backends: https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/providers/fake_provider/utils/backend_converter.py#L30 hasn't been updated to handle the new basis gates. But instead of hardcoding that we should probably use the more general https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/providers/backend_compat.py#L33 version used in the backend converter. If we just change the usage to the more general function here: https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/providers/fake_provider/fake_backend.py#L171 it should solve this and also be more future proof against any other new gates being used.

That being said the 2 things off the top of my head which are different between the two functions are that the fake backend version works with the raw deserialized json dictionary. We'd have to create `BackendConfiguration`,`BackendProperties`, and `PulseDefault` files from the payloads (this is why the two functions weren't combined at the time of the compat converter's introduction). The second is I believe the fake provider copy has special handling for lazy loading pulse schedules from the defaults payload we would need to ensure this persists. The lazy loading is important because generating the pulse schedules is very heavy and adds a lot of overhead to just using a backend which shouldn't ever be needed unless you're working with pulses.
> without erroring in the case of a custom 2q basis gate

Right a gate defined by  a user or other part of Qiskit should work if it satisfies some requirements. But if it's intended to be a controlled gate, there should be a way to determine this. ~~I don't know if `is_controlled` is a proxy for (or equivalent to) "entangling gate" . If we are doing unitary synthesis it seems reasonable to require at least one entangling gate be available.~~ Looking at the literature, it's clear that the condition checked for in `is_controlled` is that the gate is locally equivalent to controlled `U` for  some 1q `U`. So this is really checking for controlled gates and not entangling gates in general. A basis set doesn't need a controlled gate, but should have an entangling gate.

Suppose a basis set has a non-control entangling gate. Does it still make sense for it to be sent to `DefaultUnitarySynthesis`? If so, then I guess the correct thing to do is simply skip those synthesizers that require a controlled gate. In this path at least, this appears to be all of them.
The logic of the file is hard for me to follow but surely we should still try to synthesize even without a controlled basis - the supercontrolled decomposer works for eg iSWAP so i think just skip adding XXDecomposer to the list
Currently the logic in unitary_synthesis.py creates a list of decomposers to try. If there are any super-controlled gates in the basis, it will unconditionally include decomposers that use these.

To clarify, the cases that are raising these issues are really anomalous. There are *no* entangling gates at all in the basis set. So you can't synthesize gates for circuits that have entangling gates, only, possibly, circuits with no entangling gates. It's not clear whether or why we should support this. But in fact there is a test in the  test suite that relies on synthesizing a 2q identity gate with no entangling gates in the basis. 

But I realize now that #9994 is not good enough. It will throw an error if you try to synthesize a non-trivial circuit with no controlled gates. But there may be super-controlled gates in the original basis and decomposers for them in the list. All decomposers are tried and the best result is chosen. The decomposer that take the empty list of controlled gates will throw an error even if the decomposer using super-controlled gates succeeds. To be clear this was the situation before #9994 as well. But there was no test to that triggered it.... there must be some test with a basis set that includes iSWAP, but not controlled gates. I'll check...

> here must be some test with a basis set that includes iSWAP, but not controlled gates.

Evidently not. I made a target with iSwap and no controlled gates and tried to synthesize CX gate converted to a unitary matrix. It raised the index error in `XXDecomposer`. I'll talk about options to fix it in an issue.

Resolving patch:
diff --git a/qiskit/providers/backend_compat.py b/qiskit/providers/backend_compat.py
--- a/qiskit/providers/backend_compat.py
+++ b/qiskit/providers/backend_compat.py
@@ -104,9 +104,17 @@ def convert_to_target(
             if filter_faulty:
                 if not properties.is_qubit_operational(qubit):
                     continue
+            try:
+                duration = properties.readout_length(qubit)
+            except BackendPropertyError:
+                duration = None
+            try:
+                error = properties.readout_error(qubit)
+            except BackendPropertyError:
+                error = None
             measure_props[(qubit,)] = InstructionProperties(
-                duration=properties.readout_length(qubit),
-                error=properties.readout_error(qubit),
+                duration=duration,
+                error=error,
             )
         target.add_instruction(Measure(), measure_props)
     # Parse from configuration because properties doesn't exist
diff --git a/qiskit/providers/fake_provider/fake_backend.py b/qiskit/providers/fake_provider/fake_backend.py
--- a/qiskit/providers/fake_provider/fake_backend.py
+++ b/qiskit/providers/fake_provider/fake_backend.py
@@ -25,20 +25,20 @@
 from typing import List, Iterable
 
 from qiskit import circuit
-from qiskit.providers.models import BackendProperties
+from qiskit.providers.models import BackendProperties, BackendConfiguration, PulseDefaults
 from qiskit.providers import BackendV2, BackendV1
 from qiskit import pulse
 from qiskit.exceptions import QiskitError
 from qiskit.utils import optionals as _optionals
 from qiskit.providers import basicaer
 from qiskit.transpiler import Target
+from qiskit.providers.backend_compat import convert_to_target
 
 from .utils.json_decoder import (
     decode_backend_configuration,
     decode_backend_properties,
     decode_pulse_defaults,
 )
-from .utils.backend_converter import convert_to_target
 
 
 class _Credentials:
@@ -168,10 +168,16 @@ def target(self) -> Target:
                 self._set_props_dict_from_json()
             if self._defs_dict is None:
                 self._set_defs_dict_from_json()
+            conf = BackendConfiguration.from_dict(self._conf_dict)
+            props = None
+            if self._props_dict is not None:
+                props = BackendProperties.from_dict(self._props_dict)
+            defaults = None
+            if self._defs_dict is not None:
+                defaults = PulseDefaults.from_dict(self._defs_dict)
+
             self._target = convert_to_target(
-                conf_dict=self._conf_dict,
-                props_dict=self._props_dict,
-                defs_dict=self._defs_dict,
+                conf, props, defaults, add_delay=True, filter_faulty=True
             )
 
         return self._target


Test patch:
diff --git a/test/python/providers/test_fake_backends.py b/test/python/providers/test_fake_backends.py
--- a/test/python/providers/test_fake_backends.py
+++ b/test/python/providers/test_fake_backends.py
@@ -34,6 +34,8 @@
     FakeYorktown,
     FakeMumbai,
     FakeWashington,
+    FakeSherbrooke,
+    FakePrague,
 )
 from qiskit.providers.backend_compat import BackendV2Converter
 from qiskit.providers.models.backendproperties import BackendProperties
@@ -55,6 +57,8 @@
     RGate,
     MCXGrayCode,
     RYGate,
+    CZGate,
+    ECRGate,
 )
 from qiskit.circuit import ControlledGate, Parameter
 from qiskit.quantum_info.operators.channel.quantum_channel import QuantumChannel
@@ -230,6 +234,12 @@ def test_converter_delay_circuit(self):
         res = transpile(qc, backend_v2)
         self.assertIn("delay", res.count_ops())
 
+    def test_non_cx_tests(self):
+        backend = FakePrague()
+        self.assertIsInstance(backend.target.operation_from_name("cz"), CZGate)
+        backend = FakeSherbrooke()
+        self.assertIsInstance(backend.target.operation_from_name("ecr"), ECRGate)
+
     @unittest.skipUnless(optionals.HAS_AER, "Aer required for this test")
     def test_converter_simulator(self):
         class MCSXGate(ControlledGate):