Repository: tencent-quantum-lab/tensorcircuit
Issue: Can't convert qiskit circuit with complex Instructions

## Issue Description

If the qiskit circuit has complex `Instruction`, tensorcircuit can not convert the circuit into tc `Circuit`.

## How to Reproduce

```python
import numpy as np
from qiskit.circuit.library import EfficientSU2
from tensorcircuit import Circuit

ansatz = EfficientSU2(num_qubits=4, reps=1, entanglement="linear")
ansatz = ansatz.assign_parameters(np.zeros(ansatz.num_parameters_settable))
c = Circuit.from_qiskit(ansatz)
```

### Error Output

```
---------------------------------------------------------------------------
CircuitError                              Traceback (most recent call last)
/tmp/ipykernel_1791043/4095531436.py in <module>
      5 ansatz = EfficientSU2(num_qubits=4, reps=1, entanglement="linear")
      6 ansatz = ansatz.assign_parameters(np.zeros(ansatz.num_parameters_settable))
----> 7 c = Circuit.from_qiskit(ansatz)

~/miniconda3/envs/cq/lib/python3.7/site-packages/tensorcircuit/abstractcircuit.py in from_qiskit(cls, qc, n, inputs, circuit_params)
    596             n = qc.num_qubits
    597 
--> 598         return qiskit2tc(qc.data, n, inputs, is_dm=cls.is_dm, circuit_params=circuit_params)  # type: ignore
    599 
    600     def vis_tex(self, **kws: Any) -> str:

~/miniconda3/envs/cq/lib/python3.7/site-packages/tensorcircuit/translation.py in qiskit2tc(qcdata, n, inputs, is_dm, circuit_params)
    308         else:  # unitary gate
    309             idx_inverse = (x for x in idx[::-1])
--> 310             tc_circuit.any(*idx_inverse, unitary=gate_info[0].to_matrix())
    311     return tc_circuit
    312 

~/miniconda3/envs/cq/lib/python3.7/site-packages/qiskit/circuit/gate.py in to_matrix(self)
     54             # pylint: disable=no-member
     55             return self.__array__(dtype=complex)
---> 56         raise CircuitError(f"to_matrix not defined for this {type(self)}")
     57 
     58     def power(self, exponent: float):

CircuitError: "to_matrix not defined for this <class 'qiskit.circuit.gate.Gate'>"
```

A quick fix to the above problem is to use the `decompose` function. The following code works
```python
import numpy as np
from qiskit.circuit.library import EfficientSU2
from tensorcircuit import Circuit

ansatz = EfficientSU2(num_qubits=4, reps=1, entanglement="linear")
ansatz = ansatz.assign_parameters(np.zeros(ansatz.num_parameters_settable))
c = Circuit.from_qiskit(ansatz.decompose())
```
However, the decomposition is "shallow", and does not work for general cases.
The following code doesn't work
```python
from qiskit_nature.second_q.circuit.library import UCCSD
from qiskit_nature.second_q.mappers import ParityMapper
from qiskit_nature.second_q.mappers import QubitConverter

mapper = ParityMapper()
converter = QubitConverter(mapper=mapper, two_qubit_reduction=True)

ansatz = UCCSD(2, [1, 1], converter)
ansatz = ansatz.assign_parameters(np.zeros(ansatz.num_parameters_settable))
c = Circuit.from_qiskit(ansatz.decompose())
```

## Environment Context

qiskit-terra: 0.22.3
qiskit-nature: 0.4.4
tensorcircuit: 0.6.0


Changed files:
tensorcircuit/translation.py, tests/test_circuit.py, tensorcircuit/abstractcircuit.py

diff --git a/tensorcircuit/abstractcircuit.py b/tensorcircuit/abstractcircuit.py
index 8f60a39..42f98c7 100644
--- a/tensorcircuit/abstractcircuit.py
+++ b/tensorcircuit/abstractcircuit.py
@@ -535,6 +535,23 @@ class AbstractCircuit:
         }
         self._extra_qir.append(d)
 
+    def barrier_instruction(self, *index: List[int]) -> None:
+        """
+        add a barrier instruction flag, no effect on numerical simulation
+
+        :param index: the corresponding qubits
+        :type index: List[int]
+        """
+        l = len(self._qir)
+        d = {
+            "index": index,
+            "name": "barrier",
+            "gatef": "barrier",
+            "instruction": True,
+            "pos": l,
+        }
+        self._extra_qir.append(d)
+
     def to_qiskit(self, enable_instruction: bool = False) -> Any:
         """
         Translate ``tc.Circuit`` to a qiskit QuantumCircuit object.
@@ -872,7 +889,9 @@ class AbstractCircuit:
         self.__dict__.update(newc.__dict__)
         return self
 
-    def append(self, c: "AbstractCircuit") -> "AbstractCircuit":
+    def append(
+        self, c: "AbstractCircuit", indices: Optional[List[int]] = None
+    ) -> "AbstractCircuit":
         """
         append circuit ``c`` before
 
@@ -894,11 +913,22 @@ class AbstractCircuit:
 
         :param c: The other circuit to be appended
         :type c: BaseCircuit
+        :param indices: the qubit indices to which ``c`` is appended on.
+            Defaults to None, which means plain concatenation.
+        :type indices: Optional[List[int]], optional
         :return: The composed circuit
         :rtype: BaseCircuit
         """
         qir1 = self.to_qir()
         qir2 = c.to_qir()
+        if indices is not None:
+            qir2_old = qir2
+            qir2 = []
+            for d in qir2_old:
+                # avoid modifying the original circuit
+                d = d.copy()
+                d["index"] = [indices[i] for i in d["index"]]
+                qir2.append(d)
         newc = type(self).from_qir(qir1 + qir2, self.circuit_param)
         self.__dict__.update(newc.__dict__)
         return self
diff --git a/tensorcircuit/translation.py b/tensorcircuit/translation.py
index 3fb5481..289f83a 100644
--- a/tensorcircuit/translation.py
+++ b/tensorcircuit/translation.py
@@ -147,7 +147,7 @@ def qir2qiskit(
                 _get_float(parameters, "theta"),
                 _get_float(parameters, "phi"),
                 _get_float(parameters, "lbd"),
-                *index
+                *index,
             )
         elif gate_name == "cu":
             getattr(qiskit_circ, "cu")(
@@ -155,7 +155,7 @@ def qir2qiskit(
                 _get_float(parameters, "phi"),
                 _get_float(parameters, "lbd"),
                 0,  # gamma
-                *index
+                *index,
             )
         elif gate_name == "iswap":
             qiskit_circ.append(
@@ -197,6 +197,8 @@ def qir2qiskit(
             qiskit_circ.measure(index, index)
         elif gate_name == "reset":
             qiskit_circ.reset(index)
+        elif gate_name == "barrier":
+            qiskit_circ.barrier(*index)
         else:  # r cr any gate
             gatem = np.reshape(
                 backend.numpy(gate_info["gatef"](**parameters).tensor),
@@ -308,8 +310,6 @@ def qiskit2tc(
                 tc_circuit.multicontrol(
                     *idx, ctrl=ctrl_state, unitary=gates._x_matrix, name="x"
                 )
-        elif gate_name == "barrier":
-            pass
         elif gate_name[:3] == "mcx":
             if gate_name[3:] == "":
                 tc_circuit.multicontrol(
@@ -320,7 +320,7 @@ def qiskit2tc(
                 tc_circuit.multicontrol(
                     *idx, ctrl=ctrl_state, unitary=gates._x_matrix, name="x"
                 )
-        elif gate_name[0] == "c":
+        elif gate_name[0] == "c" and gate_name[:7] != "circuit":
             for i in range(1, len(gate_name)):
                 if (gate_name[-i] == "o") & (gate_name[-i - 1] == "_"):
                     break
@@ -350,6 +350,20 @@ def qiskit2tc(
             # logger.warning(
             #     "qiskit to tc translation currently doesn't support reset instruction, just skipping"
             # )
+        elif gate_name == "barrier":
+            tc_circuit.barrier_instruction(*idx)
+        elif not hasattr(gate_info[0], "__array__"):
+            # an instruction containing a lot of gates.
+            # the condition is based on
+            # https://github.com/Qiskit/qiskit-terra/blob/2f5944d60140ceb6e30d5b891e8ffec735247ce9/qiskit/circuit/gate.py#L43
+            qiskit_circuit = gate_info[0].definition
+            if qiskit_circuit is None:
+                logger.warning(
+                    f"qiskit to tc translation doesn't support {gate_name} instruction, skipping"
+                )
+                continue
+            c = Circuit.from_qiskit(qiskit_circuit)
+            tc_circuit.append(c, idx)
         else:  # unitary gate
             idx_inverse = (x for x in idx[::-1])
             tc_circuit.any(*idx_inverse, unitary=gate_info[0].to_matrix())
diff --git a/tests/test_circuit.py b/tests/test_circuit.py
index e620222..1c3f9e5 100644
--- a/tests/test_circuit.py
+++ b/tests/test_circuit.py
@@ -795,6 +795,12 @@ def test_append_circuit(backend):
     c.prepend(c1)
     np.testing.assert_allclose(c.expectation_ps(z=[1]), -1.0)
 
+    c = tc.Circuit(2)
+    c1 = tc.Circuit(1)
+    c1.x(0)
+    c.append(c1, [1])
+    np.testing.assert_allclose(c.state(), [0, 1, 0, 0])
+
 
 @pytest.mark.parametrize("backend", [lf("npb"), lf("tfb"), lf("jaxb")])
 def test_apply_mpo_gate(backend):
@@ -974,6 +980,10 @@ def test_qiskit2tc():
     qisc.swap(0, 1)
     qisc.iswap(0, 1)
     qisc.toffoli(0, 1, 2)
+    # test Instructions
+    qisc2 = QuantumCircuit(1)
+    qisc2.h(0)
+    qisc.compose(qisc2, qubits=[1], inplace=True, wrap=True)
     qisc.barrier(0, 1, 2)
     qisc.s(1)
     qisc.t(1)
@@ -1013,6 +1023,10 @@ def test_qiskit2tc():
     qis_unitary = np.reshape(qis_unitary, [2**n, 2**n])
     p_mat = perm_matrix(n)
     np.testing.assert_allclose(p_mat @ tc_unitary @ p_mat, qis_unitary, atol=1e-5)
+    qisc_from_tc = c.to_qiskit(enable_instruction=True)
+    qis_unitary2 = qi.Operator(qisc_from_tc)
+    qis_unitary2 = np.reshape(qis_unitary2, [2**n, 2**n])
+    np.testing.assert_allclose(qis_unitary2, qis_unitary, atol=1e-5)
 
 
 @pytest.mark.parametrize("backend", [lf("npb"), lf("tfb"), lf("jaxb")])