Repository: Qiskit/qiskit

Issue:
Custom fake backend stopped working after 0.23
### Environment

- **Qiskit Terra version**: 0.24.0.dev0+e016f9c
- **Python version**: 3.9


### What is happening?


The following script creates a custom fake backend and transpiles a QV for it:
```python
import rustworkx as rx

from qiskit.transpiler import Target, InstructionProperties
from qiskit.providers.backend import BackendV2
from qiskit.circuit.library import UGate, ECRGate
from qiskit.circuit import Parameter
from qiskit.circuit.controlflow import IfElseOp


class FakeBackend(BackendV2):

    def __init__(self):
        super().__init__()
        graph = rx.generators.path_graph(3)
        num_qubits = len(graph)
        self._target = Target("Fake backend", num_qubits=num_qubits)
        self._target.add_instruction(UGate(Parameter("theta"), Parameter("phi"), Parameter("lam")),
                                     {(qarg,): None for qarg in range(num_qubits)})
        self._target.add_instruction(IfElseOp, name="if_else")
        self._target.add_instruction(
            ECRGate(),
            {edge: InstructionProperties() for edge in graph.edge_list()},
        )

    @property
    def target(self):
        return self._target

    @property
    def max_circuits(self):
        ...

    @classmethod
    def _default_options(cls):
        ...

    def run(self, circuit, **kwargs):
        ...


backend = FakeBackend()

from qiskit.circuit.library import QuantumVolume
from qiskit.compiler import transpile

qc = QuantumVolume(3, depth=5, seed=12345678942).decompose()
print(transpile(qc, backend=backend))
```
It works fine in terra 0.23.*, but in current main fails like this:
```
Traceback (most recent call last):
  File "/<redacted>/qiskit-terra/bug_IfElseOp.py", line 47, in <module>
    print(transpile(qc, backend=backend))
  File "/<redacted>/qiskit-terra/qiskit/compiler/transpiler.py", line 381, in transpile
    _serial_transpile_circuit(
  File "/<redacted>/qiskit-terra/qiskit/compiler/transpiler.py", line 474, in _serial_transpile_circuit
    result = pass_manager.run(circuit, callback=callback, output_name=output_name)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passmanager.py", line 528, in run
    return super().run(circuits, output_name, callback)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passmanager.py", line 228, in run
    return self._run_single_circuit(circuits, output_name, callback)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passmanager.py", line 283, in _run_single_circuit
    result = running_passmanager.run(circuit, output_name=output_name, callback=callback)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/runningpassmanager.py", line 125, in run
    dag = self._do_pass(pass_, dag, passset.options)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/runningpassmanager.py", line 173, in _do_pass
    dag = self._run_this_pass(pass_, dag)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/runningpassmanager.py", line 202, in _run_this_pass
    new_dag = pass_.run(dag)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py", line 428, in run
    return self._run_main_loop(
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py", line 469, in _run_main_loop
    synth_dag = method.run(unitary, **kwargs)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py", line 713, in run
    decomposers2q = self._decomposer_2q_from_target(
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py", line 635, in _decomposer_2q_from_target
    supercontrolled_basis = {
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py", line 636, in <dictcomp>
    k: v for k, v in available_2q_basis.items() if is_supercontrolled(v)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py", line 627, in is_supercontrolled
    kak = TwoQubitWeylDecomposition(Operator(gate).data)
  File "/<redacted>/qiskit-terra/qiskit/quantum_info/operators/operator.py", line 99, in __init__
    raise QiskitError("Invalid input data format for Operator")
qiskit.exceptions.QiskitError: 'Invalid input data format for Operator'
```

### How can we reproduce the issue?

Run the code script.

### What should happen?

it should work as it was in 0.23.*

### Any suggestions?

No idea. Notice the following:

If you remove `self._target.add_instruction(IfElseOp, name="if_else")` ( in line 19) also works in 0.23.* and in main fails like this:
```
Traceback (most recent call last):
  File "/<redacted>/qiskit-terra/bug_IfElseOp.py", line 47, in <module>
    print(transpile(qc, backend=backend))
  File "/<redacted>/qiskit-terra/qiskit/compiler/transpiler.py", line 381, in transpile
    _serial_transpile_circuit(
  File "/<redacted>/qiskit-terra/qiskit/compiler/transpiler.py", line 474, in _serial_transpile_circuit
    result = pass_manager.run(circuit, callback=callback, output_name=output_name)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passmanager.py", line 528, in run
    return super().run(circuits, output_name, callback)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passmanager.py", line 228, in run
    return self._run_single_circuit(circuits, output_name, callback)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passmanager.py", line 283, in _run_single_circuit
    result = running_passmanager.run(circuit, output_name=output_name, callback=callback)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/runningpassmanager.py", line 125, in run
    dag = self._do_pass(pass_, dag, passset.options)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/runningpassmanager.py", line 173, in _do_pass
    dag = self._run_this_pass(pass_, dag)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/runningpassmanager.py", line 202, in _run_this_pass
    new_dag = pass_.run(dag)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py", line 428, in run
    return self._run_main_loop(
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py", line 469, in _run_main_loop
    synth_dag = method.run(unitary, **kwargs)
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py", line 713, in run
    decomposers2q = self._decomposer_2q_from_target(
  File "/<redacted>/qiskit-terra/qiskit/transpiler/passes/synthesis/unitary_synthesis.py", line 639, in _decomposer_2q_from_target
    basis_2q_fidelity = 1 - getattr(available_2q_props[basis_2q], "error", 0.0)
TypeError: unsupported operand type(s) for -: 'int' and 'NoneType'
```

If you decompose again in line 46 (`qc = QuantumVolume(...).decompose().decompose()` it works in main.

So, it seems like a problem in `unitary_synthesis`, which got a recent update in https://github.com/Qiskit/qiskit-terra/pull/9175


Discussion:

Resolving patch:
diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py
--- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py
+++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py
@@ -31,7 +31,7 @@
     TwoQubitWeylDecomposition,
 )
 from qiskit.quantum_info import Operator
-from qiskit.circuit import ControlFlowOp
+from qiskit.circuit import ControlFlowOp, Gate
 from qiskit.circuit.library.standard_gates import (
     iSwapGate,
     CXGate,
@@ -152,18 +152,23 @@ def _error(circuit, target=None, qubits=None):
             keys = target.operation_names_for_qargs(inst_qubits)
             for key in keys:
                 target_op = target.operation_from_name(key)
-                # pylint: disable=unidiomatic-typecheck
-                if type(target_op) == type(inst.operation) and (
+                if isinstance(target_op, type(inst.operation)) and (
                     target_op.is_parameterized()
                     or all(
                         isclose(float(p1), float(p2))
                         for p1, p2 in zip(target_op.params, inst.operation.params)
                     )
                 ):
-                    error = getattr(target[key][inst_qubits], "error", 0.0) or 0.0
-                    duration = getattr(target[key][inst_qubits], "duration", 0.0) or 0.0
-                    gate_fidelities.append(1 - error)
-                    gate_durations.append(duration)
+                    inst_props = target[key].get(inst_qubits, None)
+                    if inst_props is not None:
+                        error = getattr(inst_props, "error", 0.0) or 0.0
+                        duration = getattr(inst_props, "duration", 0.0) or 0.0
+                        gate_fidelities.append(1 - error)
+                        gate_durations.append(duration)
+                    else:
+                        gate_fidelities.append(1.0)
+                        gate_durations.append(0.0)
+
                     break
             else:
                 raise KeyError
@@ -599,7 +604,10 @@ def _decomposer_2q_from_target(self, target, qubits, approximation_degree):
         try:
             keys = target.operation_names_for_qargs(qubits_tuple)
             for key in keys:
-                available_2q_basis[key] = target.operation_from_name(key)
+                op = target.operation_from_name(key)
+                if not isinstance(op, Gate):
+                    continue
+                available_2q_basis[key] = op
                 available_2q_props[key] = target[key][qubits_tuple]
         except KeyError:
             pass
@@ -607,7 +615,10 @@ def _decomposer_2q_from_target(self, target, qubits, approximation_degree):
             keys = target.operation_names_for_qargs(reverse_tuple)
             for key in keys:
                 if key not in available_2q_basis:
-                    available_2q_basis[key] = target.operation_from_name(key)
+                    op = target.operation_from_name(key)
+                    if not isinstance(op, Gate):
+                        continue
+                    available_2q_basis[key] = op
                     available_2q_props[key] = target[key][reverse_tuple]
         except KeyError:
             pass
@@ -636,7 +647,11 @@ def is_controlled(gate):
             k: v for k, v in available_2q_basis.items() if is_supercontrolled(v)
         }
         for basis_1q, basis_2q in product(available_1q_basis, supercontrolled_basis.keys()):
-            basis_2q_fidelity = 1 - getattr(available_2q_props[basis_2q], "error", 0.0)
+            props = available_2q_props.get(basis_2q)
+            if props is None:
+                basis_2q_fidelity = 1.0
+            else:
+                basis_2q_fidelity = 1 - getattr(props, "error", 0.0)
             if approximation_degree is not None:
                 basis_2q_fidelity *= approximation_degree
             decomposer = TwoQubitBasisDecomposer(
@@ -654,7 +669,11 @@ def is_controlled(gate):
         for k, v in controlled_basis.items():
             strength = 2 * TwoQubitWeylDecomposition(Operator(v).data).a  # pi/2: fully entangling
             # each strength has its own fidelity
-            basis_2q_fidelity[strength] = 1 - getattr(available_2q_props[k], "error", 0.0)
+            props = available_2q_props.get(k)
+            if props is None:
+                basis_2q_fidelity[strength] = 1.0
+            else:
+                basis_2q_fidelity[strength] = 1 - getattr(props, "error", 0.0)
             # rewrite XX of the same strength in terms of it
             embodiment = XXEmbodiments[type(v)]
             if len(embodiment.parameters) == 1:


Test patch:
diff --git a/test/python/transpiler/test_unitary_synthesis.py b/test/python/transpiler/test_unitary_synthesis.py
--- a/test/python/transpiler/test_unitary_synthesis.py
+++ b/test/python/transpiler/test_unitary_synthesis.py
@@ -55,6 +55,7 @@
     RZZGate,
     RXXGate,
 )
+from qiskit.circuit.controlflow import IfElseOp
 from qiskit.circuit import Parameter
 
 
@@ -821,6 +822,20 @@ def test_single_qubit_identity_with_target(self):
         result_qc = dag_to_circuit(result_dag)
         self.assertEqual(result_qc, QuantumCircuit(1))
 
+    def test_unitary_synthesis_with_ideal_and_variable_width_ops(self):
+        """Test unitary synthesis works with a target that contains ideal and variadic ops."""
+        qc = QuantumCircuit(2)
+        qc.unitary(np.eye(4), [0, 1])
+        dag = circuit_to_dag(qc)
+        target = FakeBelemV2().target
+        target.add_instruction(IfElseOp, name="if_else")
+        target.add_instruction(ZGate())
+        target.add_instruction(ECRGate())
+        unitary_synth_pass = UnitarySynthesis(target=target)
+        result_dag = unitary_synth_pass.run(dag)
+        result_qc = dag_to_circuit(result_dag)
+        self.assertEqual(result_qc, QuantumCircuit(2))
+
 
 if __name__ == "__main__":
     unittest.main()