Repository: Qiskit/qiskit-terra (python)

Issue:
Transpilation does not take place if an instruction with {None: None} properties is added to the target 
### Environment

- **Qiskit Terra version**: 0.22.0
- **Python version**: 3.9.12
- **Operating system**: Windows 10


### What is happening?

Tranpilation return a not-properly-routed circuit.

### How can we reproduce the issue?

The documentation of `qiskit.transpiler.Target.add_instruction` says `If there are no constraints on the instruction (as in a noisless/ideal simulation) this can be set to {None, None} which will indicate it runs on all qubits (or all available permutations of qubits for multi-qubit gates).`. 

So, let's create a target and add a single-qubit instruction to it by specifying `{None: None}` as its properties

```
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.circuit.library import CXGate, RXGate
from qiskit.compiler import transpile
from qiskit.transpiler import Target

n_qubits = 3
target = Target()
target.add_instruction(CXGate(), {(i, i+1): None for i in range(n_qubits - 1)})
target.add_instruction(RXGate(Parameter('theta')), {None: None})

assert target.build_coupling_map() is None

qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.cx(1, 2)
qc.cx(2, 0)

qc_transpiled = transpile(qc, target=target, optimization_level=2, layout_method='trivial')
print(qc_transpiled.draw())
```
This code prints the following circuit
```
               ┌───┐
q_0: ──■───────┤ X ├
     ┌─┴─┐     └─┬─┘
q_1: ┤ X ├──■────┼──
     └───┘┌─┴─┐  │  
q_2: ─────┤ X ├──■──
          └───┘    
```
which is clearly not routed against the target. 

The root cause seems to be, that in this scenario the transpiler target fails to generate the coupling map. Note how the `assert` line in the above code snippet passes, meaning that the coupling map is None.

### What should happen?

Transpiler target should have a properly defined coupling map and transpilation should work correctly

### Any suggestions?

_No response_

Pull request:
Fix handling of global ideal gates for target build_coupling_map()


<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Summary

This commit fixes the handling of the `build_coupling_map()` method so that it correctly handles edge cases around a mix of ideal globally available gates and gates that have qubit/connectivity constraints. Previously, the method would incorrectly treat the presence of a globally defined ideal gate as meaning there were no connectivity constraints, even if that global gate was only on 1 qubit. While if the gate operates on two qubits this is correct behavior.

### Details and comments

Fixes #8971

Patch:
diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py
index 0a1a57894..ee4f88991 100644
--- a/qiskit/transpiler/target.py
+++ b/qiskit/transpiler/target.py
@@ -460,9 +460,10 @@ class Target(Mapping):
     @property
     def qargs(self):
         """The set of qargs in the target."""
-        if None in self._qarg_gate_map:
+        qargs = set(self._qarg_gate_map)
+        if len(qargs) == 1 and next(iter(qargs)) is None:
             return None
-        return self._qarg_gate_map.keys()
+        return qargs
 
     def qargs_for_operation_name(self, operation):
         """Get the qargs for a given operation name
@@ -813,8 +814,16 @@ class Target(Mapping):
         self._coupling_graph = rx.PyDiGraph(multigraph=False)
         self._coupling_graph.add_nodes_from([{} for _ in range(self.num_qubits)])
         for gate, qarg_map in self._gate_map.items():
+            if qarg_map is None:
+                if self._gate_name_map[gate].num_qubits == 2:
+                    self._coupling_graph = None
+                    return
+                continue
             for qarg, properties in qarg_map.items():
                 if qarg is None:
+                    if self._gate_name_map[gate].num_qubits == 2:
+                        self._coupling_graph = None
+                        return
                     continue
                 if len(qarg) == 1:
                     self._coupling_graph[qarg[0]] = properties
@@ -830,6 +839,13 @@ class Target(Mapping):
     def build_coupling_map(self, two_q_gate=None):
         """Get a :class:`~qiskit.transpiler.CouplingMap` from this target.
 
+        If there is a mix of two qubit operations that have a connectivity
+        constraint and those that are globally defined this will also return
+        ``None`` because the globally connectivity means there is no contstraint
+        on the target. If you wish to see the constraints of the two qubit
+        operations that have constraints you should use the ``two_q_gate``
+        argument to limit the output to the gates which have a constraint.
+
         Args:
             two_q_gate (str): An optional gate name for a two qubit gate in
                 the Target to generate the coupling map for. If specified the
@@ -837,16 +853,17 @@ class Target(Mapping):
                 this gate is present.
         Returns:
             CouplingMap: The :class:`~qiskit.transpiler.CouplingMap` object
-                for this target.
+                for this target. If there are no connectivity constraints in
+                the target this will return ``None``.
 
         Raises:
             ValueError: If a non-two qubit gate is passed in for ``two_q_gate``.
             IndexError: If an Instruction not in the Target is passed in for
                 ``two_q_gate``.
         """
-        if None in self._qarg_gate_map:
+        if self.qargs is None:
             return None
-        if any(len(x) > 2 for x in self.qargs):
+        if None not in self.qargs and any(len(x) > 2 for x in self.qargs):
             logger.warning(
                 "This Target object contains multiqubit gates that "
                 "operate on > 2 qubits. This will not be reflected in "
@@ -865,7 +882,6 @@ class Target(Mapping):
             cmap = CouplingMap()
             cmap.graph = coupling_graph
             return cmap
-
         if self._coupling_graph is None:
             self._build_coupling_graph()
         # if there is no connectivity constraints in the coupling graph treat it as not
diff --git a/releasenotes/notes/fix-mixed-ideal-target-coupling-map-7fca04f9c5139a49.yaml b/releasenotes/notes/fix-mixed-ideal-target-coupling-map-7fca04f9c5139a49.yaml
new file mode 100644
index 000000000..497183ec9
--- /dev/null
+++ b/releasenotes/notes/fix-mixed-ideal-target-coupling-map-7fca04f9c5139a49.yaml
@@ -0,0 +1,15 @@
+---
+fixes:
+  - |
+    Fixed an issue with the :meth:`.Target.build_coupling_map` method where
+    it would incorrectly return ``None`` for a :class:`~.Target` object
+    with a mix of ideal globally available instructions and instructions
+    that have qubit constraints. Now in such cases the
+    :meth:`.Target.build_coupling_map` will return a coupling map for the
+    constrained instruction (unless it's a 2 qubit operation which will
+    return ``None`` because globally there is no connectivity constraint).
+    Fixed `#8971 <https://github.com/Qiskit/qiskit-terra/issues/8971>`__
+  - |
+    Fixed an issue with the :attr:`.Target.qargs` attribute where it would
+    incorrectly return ``None`` for a :class:`~.Target` object that contained
+    any globally available ideal instruction.


Test patch:
diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py
index ccf879605..efb3e4a2a 100644
--- a/test/python/transpiler/test_target.py
+++ b/test/python/transpiler/test_target.py
@@ -513,6 +513,30 @@ class TestTarget(QiskitTestCase):
         with self.assertRaises(ValueError):
             fake_target.build_coupling_map("ccx")
 
+    def test_coupling_map_mixed_ideal_global_1q_and_2q_gates(self):
+        n_qubits = 3
+        target = Target()
+        target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)})
+        target.add_instruction(RXGate(Parameter("theta")), {None: None})
+        cmap = target.build_coupling_map()
+        self.assertEqual([(0, 1), (1, 2)], cmap.get_edges())
+
+    def test_coupling_map_mixed_global_1q_and_2q_gates(self):
+        n_qubits = 3
+        target = Target()
+        target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)})
+        target.add_instruction(RXGate(Parameter("theta")))
+        cmap = target.build_coupling_map()
+        self.assertEqual([(0, 1), (1, 2)], cmap.get_edges())
+
+    def test_coupling_map_mixed_ideal_global_2q_and_real_2q_gates(self):
+        n_qubits = 3
+        target = Target()
+        target.add_instruction(CXGate(), {(i, i + 1): None for i in range(n_qubits - 1)})
+        target.add_instruction(ECRGate())
+        cmap = target.build_coupling_map()
+        self.assertIsNone(cmap)
+
     def test_physical_qubits(self):
         self.assertEqual([], self.empty_target.physical_qubits)
         self.assertEqual(list(range(5)), self.ibm_target.physical_qubits)
@@ -1388,6 +1412,16 @@ class TestGlobalVariableWidthOperations(QiskitTestCase):
         self.assertEqual(expected_aqt, self.aqt_target.qargs)
         self.assertEqual(None, self.target_global_gates_only.qargs)
 
+    def test_qargs_single_qarg(self):
+        target = Target()
+        target.add_instruction(XGate(), {(0,): None})
+        self.assertEqual(
+            {
+                (0,),
+            },
+            target.qargs,
+        )
+
     def test_qargs_for_operation_name(self):
         self.assertEqual(
             self.ibm_target.qargs_for_operation_name("rz"), {(0,), (1,), (2,), (3,), (4,)}