Repository: Qiskit/qiskit

Issue:
Convert `BIPMaping` transpiler pass into an external plugin
### What should we add?

The [`BIPMapping` transpiler pass](https://qiskit.org/documentation/stubs/qiskit.transpiler.passes.BIPMapping.html) has been in a weird state since it's introduction. It is a standalone transpiler pass that is used for both the layout and routing stage but we never integrated it into `transpile` because it has an external dependency on `cplex` which is a proprietary software package that most people don't have access too.. Now that we've added an external plugin interface for transpiler stages (which was added in #8305) we have a better option for how to advertise and use the `BipMapping` pass. We can create an external package for the pass that contains the code of the transpiler pass and offers a plugin that cleanly integrates into the `transpile()` function. Basically the workflow for using the bip mapper would change to be `pip install qiskit-bip-mapper` (or whatever the package name we pick is) and then doing `transpile(qc, backend, layout_method="bip_mapping")`. This make it easier for users to run and also separates the dependency on cplex into a separate package where we can more cleanly document the extra requirements around installing and/or purchasing a commercial license depending on the size of the circuits.

Steps:

- [x] Create a new package for the `BipMapping` pass
    - [x] Add layout plugin for the pass
- [x] Deprecate `BIPMapping` pass in qiskit-terra after release of p


Discussion:

Resolving patch:
diff --git a/qiskit/transpiler/passes/routing/algorithms/bip_model.py b/qiskit/transpiler/passes/routing/algorithms/bip_model.py
--- a/qiskit/transpiler/passes/routing/algorithms/bip_model.py
+++ b/qiskit/transpiler/passes/routing/algorithms/bip_model.py
@@ -26,6 +26,7 @@
     trace_to_fid,
 )
 from qiskit.utils import optionals as _optionals
+from qiskit.utils.deprecation import deprecate_func
 
 logger = logging.getLogger(__name__)
 
@@ -41,6 +42,12 @@ class BIPMappingModel:
             the solution will be stored in :attr:`solution`). None if it's not yet set.
     """
 
+    @deprecate_func(
+        since="0.24.0",
+        additional_msg="This has been replaced by a new transpiler plugin package: "
+        "qiskit-bip-mapper. More details can be found here: "
+        "https://github.com/qiskit-community/qiskit-bip-mapper",
+    )  # pylint: disable=bad-docstring-quotes
     def __init__(self, dag, coupling_map, qubit_subset, dummy_timesteps=None):
         """
         Args:
diff --git a/qiskit/transpiler/passes/routing/bip_mapping.py b/qiskit/transpiler/passes/routing/bip_mapping.py
--- a/qiskit/transpiler/passes/routing/bip_mapping.py
+++ b/qiskit/transpiler/passes/routing/bip_mapping.py
@@ -23,6 +23,7 @@
 from qiskit.transpiler.exceptions import TranspilerError
 from qiskit.transpiler.passes.routing.algorithms.bip_model import BIPMappingModel
 from qiskit.transpiler.target import target_to_backend_properties, Target
+from qiskit.utils.deprecation import deprecate_func
 
 logger = logging.getLogger(__name__)
 
@@ -63,6 +64,12 @@ class BIPMapping(TransformationPass):
     `arXiv:2106.06446 <https://arxiv.org/abs/2106.06446>`_
     """
 
+    @deprecate_func(
+        since="0.24.0",
+        additional_msg="This has been replaced by a new transpiler plugin package: "
+        "qiskit-bip-mapper. More details can be found here: "
+        "https://github.com/qiskit-community/qiskit-bip-mapper",
+    )  # pylint: disable=bad-docstring-quotes
     def __init__(
         self,
         coupling_map,


Test patch:
diff --git a/test/python/transpiler/test_bip_mapping.py b/test/python/transpiler/test_bip_mapping.py
--- a/test/python/transpiler/test_bip_mapping.py
+++ b/test/python/transpiler/test_bip_mapping.py
@@ -36,7 +36,8 @@ def test_empty(self):
         """Returns the original circuit if the circuit is empty."""
         coupling = CouplingMap([[0, 1]])
         circuit = QuantumCircuit(2)
-        actual = BIPMapping(coupling)(circuit)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling)(circuit)
         self.assertEqual(circuit, actual)
 
     def test_no_two_qubit_gates(self):
@@ -49,8 +50,8 @@ def test_no_two_qubit_gates(self):
 
         circuit = QuantumCircuit(2)
         circuit.h(0)
-
-        actual = BIPMapping(coupling)(circuit)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling)(circuit)
 
         self.assertEqual(circuit, actual)
 
@@ -70,7 +71,8 @@ def test_trivial_case(self):
         circuit.h(0)
         circuit.cx(2, 0)
 
-        actual = BIPMapping(coupling)(circuit)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling)(circuit)
         self.assertEqual(3, len(actual))
         for inst, _, _ in actual.data:  # there are no swaps
             self.assertFalse(isinstance(inst, SwapGate))
@@ -82,7 +84,8 @@ def test_no_swap(self):
         circuit = QuantumCircuit(3)
         circuit.cx(1, 2)
 
-        actual = BIPMapping(coupling)(circuit)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling)(circuit)
 
         q = QuantumRegister(3, name="q")
         expected = QuantumCircuit(q)
@@ -98,7 +101,8 @@ def test_ignore_initial_layout(self):
         circuit.cx(1, 2)
 
         property_set = {"layout": Layout.generate_trivial_layout(*circuit.qubits)}
-        actual = BIPMapping(coupling)(circuit, property_set)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling)(circuit, property_set)
 
         q = QuantumRegister(3, name="q")
         expected = QuantumCircuit(q)
@@ -117,7 +121,8 @@ def test_can_map_measurements_correctly(self):
         circuit.measure(qr[1], cr[0])
         circuit.measure(qr[2], cr[1])
 
-        actual = BIPMapping(coupling)(circuit)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling)(circuit)
 
         q = QuantumRegister(3, "q")
         expected = QuantumCircuit(q, cr)
@@ -139,7 +144,8 @@ def test_can_map_measurements_correctly_with_target(self):
         circuit.measure(qr[1], cr[0])
         circuit.measure(qr[2], cr[1])
 
-        actual = BIPMapping(target)(circuit)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(target)(circuit)
 
         q = QuantumRegister(3, "q")
         expected = QuantumCircuit(q, cr)
@@ -162,8 +168,9 @@ def test_never_modify_mapped_circuit(self):
         circuit.measure(2, 1)
         dag = circuit_to_dag(circuit)
 
-        mapped_dag = BIPMapping(coupling).run(dag)
-        remapped_dag = BIPMapping(coupling).run(mapped_dag)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            mapped_dag = BIPMapping(coupling).run(dag)
+            remapped_dag = BIPMapping(coupling).run(mapped_dag)
 
         self.assertEqual(mapped_dag, remapped_dag)
 
@@ -177,7 +184,8 @@ def test_no_swap_multi_layer(self):
         circuit.cx(qr[0], qr[3])
 
         property_set = {}
-        actual = BIPMapping(coupling, objective="depth")(circuit, property_set)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling, objective="depth")(circuit, property_set)
         self.assertEqual(2, actual.depth())
 
         CheckMap(coupling)(actual, property_set)
@@ -194,7 +202,8 @@ def test_unmappable_cnots_in_a_layer(self):
         circuit.measure(qr, cr)
 
         coupling = CouplingMap([[0, 1], [1, 2], [1, 3]])  # {0: [1], 1: [2, 3]}
-        actual = BIPMapping(coupling)(circuit)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling)(circuit)
 
         # Fails to map and returns the original circuit
         self.assertEqual(circuit, actual)
@@ -233,7 +242,8 @@ def test_multi_cregs(self):
 
         coupling = CouplingMap([[0, 1], [0, 2], [2, 3]])  # linear [1, 0, 2, 3]
         property_set = {}
-        actual = BIPMapping(coupling, objective="depth")(circuit, property_set)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling, objective="depth")(circuit, property_set)
         self.assertEqual(5, actual.depth())
 
         CheckMap(coupling)(actual, property_set)
@@ -264,7 +274,8 @@ def test_swaps_in_dummy_steps(self):
 
         coupling = CouplingMap.from_line(4)
         property_set = {}
-        actual = BIPMapping(coupling, objective="depth")(circuit, property_set)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling, objective="depth")(circuit, property_set)
         self.assertEqual(7, actual.depth())
 
         CheckMap(coupling)(actual, property_set)
@@ -295,7 +306,8 @@ def test_different_number_of_virtual_and_physical_qubits(self):
 
         coupling = CouplingMap.from_line(5)
         with self.assertRaises(TranspilerError):
-            BIPMapping(coupling)(circuit)
+            with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+                BIPMapping(coupling)(circuit)
 
     def test_qubit_subset(self):
         """Test if `qubit_subset` option works as expected."""
@@ -306,7 +318,8 @@ def test_qubit_subset(self):
 
         coupling = CouplingMap([(0, 1), (1, 3), (3, 2)])
         qubit_subset = [0, 1, 3]
-        actual = BIPMapping(coupling, qubit_subset=qubit_subset)(circuit)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            actual = BIPMapping(coupling, qubit_subset=qubit_subset)(circuit)
         # all used qubits are in qubit_subset
         bit_indices = {bit: index for index, bit in enumerate(actual.qubits)}
         for _, qargs, _ in actual.data:
@@ -323,7 +336,8 @@ def test_unconnected_qubit_subset(self):
 
         coupling = CouplingMap([(0, 1), (1, 3), (3, 2)])
         with self.assertRaises(TranspilerError):
-            BIPMapping(coupling, qubit_subset=[0, 1, 2])(circuit)
+            with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+                BIPMapping(coupling, qubit_subset=[0, 1, 2])(circuit)
 
     def test_objective_function(self):
         """Test if ``objective`` functions prioritize metrics correctly."""
@@ -345,13 +359,15 @@ def test_objective_function(self):
         qc.dcx(0, 1)
         qc.cx(2, 3)
         coupling = CouplingMap(FakeLima().configuration().coupling_map)
-        dep_opt = BIPMapping(coupling, objective="depth", qubit_subset=[0, 1, 3, 4])(qc)
-        err_opt = BIPMapping(
-            coupling,
-            objective="gate_error",
-            qubit_subset=[0, 1, 3, 4],
-            backend_prop=FakeLima().properties(),
-        )(qc)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            dep_opt = BIPMapping(coupling, objective="depth", qubit_subset=[0, 1, 3, 4])(qc)
+        with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"):
+            err_opt = BIPMapping(
+                coupling,
+                objective="gate_error",
+                qubit_subset=[0, 1, 3, 4],
+                backend_prop=FakeLima().properties(),
+            )(qc)
         # depth = number of su4 layers (mirrored gates have to be consolidated as single su4 gates)
         pm_ = PassManager([Collect2qBlocks(), ConsolidateBlocks(basis_gates=["cx", "u"])])
         dep_opt = pm_.run(dep_opt)