Repository: qiskit-community/qiskit-braket-provider

Issue:
Extend `to_braket` to consider topology and the range of angles of native gates
<!-- ⚠️ 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. -->


Follow issue https://github.com/qiskit-community/qiskit-braket-provider/issues/138, the `to_braket` function converts the input QIskit circuit into a Braket circuit that contains the backend's native gates. However, such conversion does not consider the topology of the device yet. The range of angle is also not considered by the `to_braket` function.  The output circuit of  `to_braket` may currently contain angle not supported by the backend. 

### What is the expected behavior?
When apply the `to_braket` function to a QIskit circuit, shown as the code snippet below,
```
backend = AWSBraketProvider().get_backend("...")
circuit = QuantumCircuit(2)
circuit.x(0)
circuit.y(1)
circuit.rzz(1, 0, 1)
braket_circuit = to_braket(circuit, backend, native=True)
```
the output `braket_circuit` is a circuit in Braket Circuit class. The output Braket circuit should be equivalent to the input Qiskit circuit, but it only contains native gates of the device with the correct range of the angles. The two-qubit native gates apply only on the physical edge of the device.

### Acceptance criteria
- The output Braket circuit only use angles supported by the backend. Currently, the only backends that has such restriction in angles are Rigetti backends. As a native gate in Rigetti backends, the Rx can only accept +pi, -pi, +pi/2 and -pi/2.
- There is a extensible way to specify angle restriction for a backend. For example, a new restriction can be added to a backend without complex change in the code.
- The two-qubit native gates should respect the device topology.

Discussion:
@KaziMuktadirAhmed: Hello @yitchen-tim ,
I want to work on this issue for unitaryHack2025
Can I be assigned to it ?@yitchen-tim: Hi @KaziMuktadirAhmed, Based on the guideline of UnitaryHack, we will only assign the issue when it is completed. If you want to start working on it, you can post it here so that other folks can see that you are working on it :)@yitchen-tim: This issue is solved. The experience is
```
basis_gates = backend.get_gateset(native=True)
coupling_map = backend.coupling_map

braket_circuit = to_braket(qc_transpiled, connectivity=coupling_map, basis_gates=basis_gates, verbatim=True)
print(braket_circuit)
```

Solution patch:
diff --git a/qiskit_braket_provider/providers/adapter.py b/qiskit_braket_provider/providers/adapter.py
index 302ff8a..2019c82 100644
--- a/qiskit_braket_provider/providers/adapter.py
+++ b/qiskit_braket_provider/providers/adapter.py
@@ -409,6 +409,7 @@ def to_braket(
     circuit: QuantumCircuit,
     basis_gates: Optional[Iterable[str]] = None,
     verbatim: bool = False,
+    connectivity: Optional[list[list[int]]] = None,
 ) -> Circuit:
     """Return a Braket quantum circuit from a Qiskit quantum circuit.
 
@@ -419,6 +420,8 @@ def to_braket(
             Default: `None`.
         verbatim (bool): Whether to translate the circuit without any modification, in other
             words without transpiling it. Default: False.
+        connectivity (Optional[list[list[int]]): If provided, will transpile to a circuit
+            with this connectivity. Default: `None`.
 
     Returns:
         Circuit: Braket circuit
@@ -429,10 +432,16 @@ def to_braket(
     basis_gates = set(basis_gates or _TRANSLATABLE_QISKIT_GATE_NAMES)
 
     braket_circuit = Circuit()
-    if not verbatim and not {gate.name for gate, _, _ in circuit.data}.issubset(
-        basis_gates
-    ):
-        circuit = transpile(circuit, basis_gates=basis_gates, optimization_level=0)
+    needs_transpilation = connectivity or not {
+        gate.name for gate, _, _ in circuit.data
+    }.issubset(basis_gates)
+    if not verbatim and needs_transpilation:
+        circuit = transpile(
+            circuit,
+            basis_gates=basis_gates,
+            coupling_map=connectivity,
+            optimization_level=0,
+        )
 
     # Verify that ParameterVector would not collide with scalar variables after renaming.
     _validate_name_conflicts(circuit.parameters)


Test patch:
diff --git a/tests/providers/test_adapter.py b/tests/providers/test_adapter.py
index 8631a9a..18ce16a 100644
--- a/tests/providers/test_adapter.py
+++ b/tests/providers/test_adapter.py
@@ -538,6 +538,44 @@ class TestAdapter(TestCase):
             "mcx",
         }
 
+    def test_connectivity(self):
+        """Tests transpiling with connectivity"""
+        qiskit_circuit = QuantumCircuit(3)
+        qiskit_circuit.h(0)
+        qiskit_circuit.cx(0, 1)
+        qiskit_circuit.rxx(0.1, 0, 2)
+        connectivity = [[0, 1], [1, 0], [1, 2], [2, 1]]
+
+        braket_circuit = to_braket(qiskit_circuit, connectivity=connectivity)
+        braket_circuit_unconnected = to_braket(qiskit_circuit)
+        braket_circuit_verbatim = to_braket(
+            qiskit_circuit, verbatim=True, connectivity=connectivity
+        )
+
+        def gate_matches_connectivity(gate) -> bool:
+            return any(
+                (
+                    gate.target.union(gate.control).issubset(adjacency)
+                    for adjacency in connectivity
+                )
+            )
+
+        assert all(
+            (gate_matches_connectivity(gate) for gate in braket_circuit.instructions)
+        )
+        assert not all(
+            (
+                gate_matches_connectivity(gate)
+                for gate in braket_circuit_unconnected.instructions
+            )
+        )
+        assert not all(
+            (
+                gate_matches_connectivity(gate)
+                for gate in braket_circuit_verbatim.instructions
+            )
+        )
+
 
 class TestFromBraket(TestCase):
     """Test Braket circuit conversion."""