Repository: CQCL/pytket-qiskit

Issue:
pytket to qiskit issue: Mutability of Gates
Hi,

I was using pytket-qiskit package to get circuits between tket and qiskit via `qiskit_to_tk` and `tk_to_qiskit` functions. In particular, circuits involving CnZ gates in tket are so convenient, such that I was creating them in tket and transferring to qiskit.

Recently though some updates in tket, qiskit and pytket-qiskit packages created some issues. Some issues are simple, such as the nomenclatura changes from `auto_rebases_pass` to `AutoRebase`. But, some are more fundamental. The `tk_to_qiskit` function, upon encountering circuits involving CnZ gates, now returning the following error:

```
File [~/.pyenv/versions/jaxqml/lib/python3.12/site-packages/pytket/extensions/qiskit/qiskit_convert.py:1031](http://localhost:8888/lab/workspaces/auto-e/tree/Dropbox/aQa/aQa_codes/Class_H_Learn/shadow_first_hypergraph/pythonProject1/~/.pyenv/versions/jaxqml/lib/python3.12/site-packages/pytket/extensions/qiskit/qiskit_convert.py#line=1030), in tk_to_qiskit(tkcirc, replace_implicit_swaps, perm_warning)
   1028 supported_gate_rebase.apply(tkc)
   1030 for command in tkc:
-> 1031     append_tk_command_to_qiskit(
   1032         command.op, command.args, qcirc, qregmap, cregmap, symb_map, range_preds
   1033     )
   1034 qcirc.global_phase += param_to_qiskit(tkc.phase, symb_map)
   1036 # if UUID stored in name, set parameter uuids accordingly (see qiskit_to_tk)

File [~/.pyenv/versions/jaxqml/lib/python3.12/site-packages/pytket/extensions/qiskit/qiskit_convert.py:886](http://localhost:8888/lab/workspaces/auto-e/tree/Dropbox/aQa/aQa_codes/Class_H_Learn/shadow_first_hypergraph/pythonProject1/~/.pyenv/versions/jaxqml/lib/python3.12/site-packages/pytket/extensions/qiskit/qiskit_convert.py#line=885), in append_tk_command_to_qiskit(op, args, qcirc, qregmap, cregmap, symb_map, range_preds)
    884 if optype == OpType.CnZ:
    885     new_gate = qiskit_gates.ZGate().control(len(qargs) - 1)
--> 886     new_gate.name = "mcz"
    887     return qcirc.append(new_gate, qargs)
    888 if optype == OpType.CnRy:
    889     # might as well do a bit more checking

File [~/.pyenv/versions/jaxqml/lib/python3.12/site-packages/qiskit/circuit/singleton.py:312](http://localhost:8888/lab/workspaces/auto-e/tree/Dropbox/aQa/aQa_codes/Class_H_Learn/shadow_first_hypergraph/pythonProject1/~/.pyenv/versions/jaxqml/lib/python3.12/site-packages/qiskit/circuit/singleton.py#line=311), in _impl_init_subclass.<locals>.__init_subclass__.<locals>._Singleton.__setattr__(self, key, value)
    311 def __setattr__(self, key, value):
--> 312     raise TypeError(
    313         f"This '{self.base_class.__name__}' object is immutable."
    314         " You can get a mutable version by calling 'to_mutable()'."
    315     )

TypeError: This 'CZGate' object is immutable. You can get a mutable version by calling 'to_mutable()'.
```

I was wondering if you can fix this issue.

Best,
Onur

Hints from issue comments:
@cqc-alec: Thanks for raising this issue. As it relates to pytket-qiskit I will transfer it to that repo...@CalMacCQ: Which version of pytket-qiskit, pytket and qiskit are you using? The following code works fine for the with the latest version of pytket-qiskit (0.63.0). 

```python
from pytket import Circuit, OpType
from pytket.extensions.qiskit import tk_to_qiskit

circ = Circuit(4).add_gate(OpType.CnZ, [0, 1, 2, 3])

qc = tk_to_qiskit(circ)

print(qc)
```

Output

<img width="95" alt="Image" src="https://github.com/user-attachments/assets/96cc2e6d-fa98-452b-ae66-2bf0b00ed87e" />


If you could include code to reproduce your issue that would be great.@yao-cqc: This is indeed an issue when the number of control qubits is 1.

```python
new_gate = qiskit_gates.ZGate().control(1)
new_gate.name = "abc"
```

I'm working on a fix.

Solution patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 4b7f874..21ddcc4 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -8,6 +8,7 @@
 
 - Improve {py:func}`qiskit_to_tk` to handle `IfElseOp` (generated by `QuantumCircuit.if_test`) 
 with register conditions. Only conditions on an entire register are supported.
+- Fix conversion of `CnZ` gates when the number of control qubits is one.
 
 
 ## 0.64.0
diff --git a/pytket/extensions/qiskit/qiskit_convert.py b/pytket/extensions/qiskit/qiskit_convert.py
index bdaa9a3..5df0561 100644
--- a/pytket/extensions/qiskit/qiskit_convert.py
+++ b/pytket/extensions/qiskit/qiskit_convert.py
@@ -893,8 +893,11 @@ order or only one bit of one register"""
     if optype == OpType.CnY:
         return qcirc.append(qiskit_gates.YGate().control(len(qargs) - 1), qargs)
     if optype == OpType.CnZ:
-        new_gate = qiskit_gates.ZGate().control(len(qargs) - 1)
-        new_gate.name = "mcz"
+        if len(qargs) == 2:
+            new_gate = qiskit_gates.CZGate()
+        else:
+            new_gate = qiskit_gates.ZGate().control(len(qargs) - 1)
+            new_gate.name = "mcz"
         return qcirc.append(new_gate, qargs)
     if optype == OpType.CnRy:
         # might as well do a bit more checking


Test patch:
diff --git a/tests/qiskit_convert_test.py b/tests/qiskit_convert_test.py
index b34b712..afea44e 100644
--- a/tests/qiskit_convert_test.py
+++ b/tests/qiskit_convert_test.py
@@ -505,6 +505,19 @@ def test_cnx() -> None:
     assert cmd.qubits[4] == Qubit(qregname, 4)
 
 
+def test_convert_cnz_to_qiskit() -> None:
+    # https://github.com/CQCL/pytket-qiskit/issues/460
+    circ = Circuit(1).add_gate(OpType.CnZ, [0])
+    qc = tk_to_qiskit(circ)
+    assert qc[0].name == "z"
+    circ = Circuit(2).add_gate(OpType.CnZ, [0, 1])
+    qc = tk_to_qiskit(circ)
+    assert qc[0].name == "cz"
+    circ = Circuit(3).add_gate(OpType.CnZ, [0, 1, 2])
+    qc = tk_to_qiskit(circ)
+    assert qc[0].name == "mcz"
+
+
 def test_gate_str_2_optype() -> None:
     samples = {
         "barrier": OpType.Barrier,