Repository: qiskit-community/qiskit-quimb

Issue:
Make something like `_gen_quimb_gates` public
In my experience with qiskit-quimb so far, I already want something with the functionality of `_gen_quimb_gates` to be public.  For instance, I believe this function is necessary to apply Qiskit gate(s) to an existing `CircuitMPS`, which can be done as `circuitmps.apply_gates(_gen_quimb_gates(gate, (q0, q1)))`.

It could really use a better name, though.

Hints from issue comments:
@kevinsung: Of course, we can make it public. Any suggestions for the name?

Solution patch:
diff --git a/src/qiskit_quimb/__init__.py b/src/qiskit_quimb/__init__.py
index 4648e99..84d0e30 100644
--- a/src/qiskit_quimb/__init__.py
+++ b/src/qiskit_quimb/__init__.py
@@ -8,9 +8,10 @@
 # copyright notice, and modified files need to carry a notice indicating
 # that they have been altered from the originals.
 
-from qiskit_quimb.circuit import quimb_circuit, quimb_gates
+from qiskit_quimb.circuit import quimb_circuit, quimb_gate, quimb_gates
 
 __all__ = [
     "quimb_circuit",
+    "quimb_gate",
     "quimb_gates",
 ]
diff --git a/src/qiskit_quimb/circuit.py b/src/qiskit_quimb/circuit.py
index 121064d..8dd3a8b 100644
--- a/src/qiskit_quimb/circuit.py
+++ b/src/qiskit_quimb/circuit.py
@@ -9,7 +9,7 @@
 # that they have been altered from the originals.
 
 import math
-from collections.abc import Iterator
+from collections.abc import Iterator, Sequence
 
 import quimb.tensor
 from qiskit.circuit import Instruction, QuantumCircuit
@@ -50,31 +50,51 @@ def quimb_gates(circuit: QuantumCircuit) -> list[quimb.tensor.Gate]:
 
 
 def _gen_quimb_gates(
-    op: Instruction, qubits: list[int], **kwargs
+    op: Instruction, qubits: Sequence[int], **kwargs
 ) -> Iterator[quimb.tensor.Gate]:
     """Convert a Qiskit gate to quimb gates."""
     match op.name:
         case "barrier":
             pass
+        case "xx_plus_yy":
+            theta, beta = op.params
+            phi = beta + 0.5 * math.pi
+            a, b = qubits
+            yield quimb.tensor.Gate("RZ", params=[phi], qubits=[a], **kwargs)
+            yield quimb.tensor.Gate(
+                "GIVENS", params=[0.5 * theta], qubits=[a, b], **kwargs
+            )
+            yield quimb.tensor.Gate("RZ", params=[-phi], qubits=[a], **kwargs)
+        case _:
+            yield quimb_gate(op, qubits, **kwargs)
+
+
+def quimb_gate(
+    op: Instruction, qubits: Sequence[int], **kwargs
+) -> quimb.tensor.Gate | None:
+    """Convert a Qiskit gate to a quimb gate, or ``None`` in case of a barrier."""
+    match op.name:
+        case "barrier":
+            return None
         case "ccx":
-            yield quimb.tensor.Gate("CCX", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("CCX", params=[], qubits=qubits, **kwargs)
         case "ccz":
-            yield quimb.tensor.Gate("CCZ", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("CCZ", params=[], qubits=qubits, **kwargs)
         case "cp":
             (theta,) = op.params
-            yield quimb.tensor.Gate("CU1", params=[theta], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("CU1", params=[theta], qubits=qubits, **kwargs)
         case "cx":
-            yield quimb.tensor.Gate("CX", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("CX", params=[], qubits=qubits, **kwargs)
         case "cy":
-            yield quimb.tensor.Gate("CY", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("CY", params=[], qubits=qubits, **kwargs)
         case "cz":
-            yield quimb.tensor.Gate("CZ", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("CZ", params=[], qubits=qubits, **kwargs)
         case "h":
-            yield quimb.tensor.Gate("H", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("H", params=[], qubits=qubits, **kwargs)
         case "id":
-            yield quimb.tensor.Gate("IDEN", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("IDEN", params=[], qubits=qubits, **kwargs)
         case "iswap":
-            yield quimb.tensor.Gate("ISWAP", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("ISWAP", params=[], qubits=qubits, **kwargs)
         case "measure":
             raise ValueError(
                 "Encountered a measurement gate, which is not allowed. "
@@ -82,60 +102,51 @@ def _gen_quimb_gates(
             )
         case "p":
             (theta,) = op.params
-            yield quimb.tensor.Gate("U1", params=[theta], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("U1", params=[theta], qubits=qubits, **kwargs)
         case "rx":
             (theta,) = op.params
-            yield quimb.tensor.Gate("RX", params=[theta], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("RX", params=[theta], qubits=qubits, **kwargs)
         case "rxx":
             (theta,) = op.params
-            yield quimb.tensor.Gate("RXX", params=[theta], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("RXX", params=[theta], qubits=qubits, **kwargs)
         case "ryy":
             (theta,) = op.params
-            yield quimb.tensor.Gate("RYY", params=[theta], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("RYY", params=[theta], qubits=qubits, **kwargs)
         case "rzz":
             (theta,) = op.params
-            yield quimb.tensor.Gate("RZZ", params=[theta], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("RZZ", params=[theta], qubits=qubits, **kwargs)
         case "ry":
             (theta,) = op.params
-            yield quimb.tensor.Gate("RY", params=[theta], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("RY", params=[theta], qubits=qubits, **kwargs)
         case "rz":
             (theta,) = op.params
-            yield quimb.tensor.Gate("RZ", params=[theta], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("RZ", params=[theta], qubits=qubits, **kwargs)
         case "s":
-            yield quimb.tensor.Gate("S", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("S", params=[], qubits=qubits, **kwargs)
         case "sdg":
-            yield quimb.tensor.Gate("SDG", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("SDG", params=[], qubits=qubits, **kwargs)
         case "swap":
-            yield quimb.tensor.Gate("SWAP", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("SWAP", params=[], qubits=qubits, **kwargs)
         case "t":
-            yield quimb.tensor.Gate("T", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("T", params=[], qubits=qubits, **kwargs)
         case "tdg":
-            yield quimb.tensor.Gate("TDG", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("TDG", params=[], qubits=qubits, **kwargs)
         case "u1":
             (theta,) = op.params
-            yield quimb.tensor.Gate("U1", params=[theta], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("U1", params=[theta], qubits=qubits, **kwargs)
         case "u2":
             (phi, lam) = op.params
-            yield quimb.tensor.Gate("U2", params=[phi, lam], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("U2", params=[phi, lam], qubits=qubits, **kwargs)
         case "u3":
             (theta, phi, lam) = op.params
-            yield quimb.tensor.Gate(
+            return quimb.tensor.Gate(
                 "U3", params=[theta, phi, lam], qubits=qubits, **kwargs
             )
         case "x":
-            yield quimb.tensor.Gate("X", params=[], qubits=qubits, **kwargs)
-        case "xx_plus_yy":
-            theta, beta = op.params
-            phi = beta + 0.5 * math.pi
-            a, b = qubits
-            yield quimb.tensor.Gate("RZ", params=[phi], qubits=[a], **kwargs)
-            yield quimb.tensor.Gate(
-                "GIVENS", params=[0.5 * theta], qubits=[a, b], **kwargs
-            )
-            yield quimb.tensor.Gate("RZ", params=[-phi], qubits=[a], **kwargs)
+            return quimb.tensor.Gate("X", params=[], qubits=qubits, **kwargs)
         case "y":
-            yield quimb.tensor.Gate("Y", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("Y", params=[], qubits=qubits, **kwargs)
         case "z":
-            yield quimb.tensor.Gate("Z", params=[], qubits=qubits, **kwargs)
+            return quimb.tensor.Gate("Z", params=[], qubits=qubits, **kwargs)
         case _:
             raise ValueError(f"Unsupported gate: {op.name}.")


Test patch:
diff --git a/tests/circuit_test.py b/tests/circuit_test.py
index f21db3f..d9c1cb2 100644
--- a/tests/circuit_test.py
+++ b/tests/circuit_test.py
@@ -45,7 +45,7 @@
 )
 from qiskit.quantum_info import Statevector
 
-from qiskit_quimb import quimb_circuit
+from qiskit_quimb import quimb_circuit, quimb_gate
 
 
 def test_quimb_circuit():
@@ -103,3 +103,7 @@ def test_quimb_circuit():
     qiskit_vec = np.array(Statevector(circuit))
     quimb_vec = quimb_circ.to_dense(reverse=True).reshape(-1)
     np.testing.assert_allclose(quimb_vec, qiskit_vec)
+
+
+def test_quimb_gate_on_barrier():
+    assert quimb_gate(Barrier(4), range(4)) is None