qiboteam / qibo

A framework for quantum computing
https://qibo.science
Apache License 2.0
287 stars 58 forks source link

Inconsistencies on gate definitions coming from `Gate.controlled_by` and `Circuit.gates_of_type` #1354

Closed renatomello closed 3 months ago

renatomello commented 3 months ago

There are some inconsistencies coming from these two methods, and I'll list them below:

  1. First, apparently controlled gates cannot receive more controls by using the Gate.controlled_by method, e.g.
    
    In [1]: from qibo import Circuit, gates, set_backend
    ...: set_backend("numpy")
    ...: 
    ...: gates.CRY(0, 1, 0.1).controlled_by(2)

[Qibo 0.2.8|INFO|2024-06-11 08:25:51]: Using numpy backend on /CPU:0 [Qibo 0.2.8|ERROR|2024-06-11 08:25:51]: Cannot use controlled_by method on gate <qibo.gates.gates.CRY object at 0x7cef2f413a60> because it is already controlled by (0,).

RuntimeError Traceback (most recent call last) Cell In[1], line 4 1 from qibo import Circuit, gates, set_backend 2 set_backend("numpy") ----> 4 gates.CRY(0, 1, 0.1).controlled_by(2)

File ~/.local/lib/python3.10/site-packages/qibo/gates/abstract.py:311, in Gate.check_controls..wrapper(self, args) 309 def wrapper(self, args): 310 if self.control_qubits: --> 311 raise_error( 312 RuntimeError, 313 "Cannot use controlled_by method " 314 + f"on gate {self} because it is already " 315 + f"controlled by {self.control_qubits}.", 316 ) 317 return func(self, *args)

File ~/.local/lib/python3.10/site-packages/qibo/config.py:46, in raise_error(exception, message) 39 """Raise exception with logging error. 40 41 Args: 42 exception (Exception): python exception. 43 message (str): the error message. 44 """ 45 log.error(message) ---> 46 raise exception(message)

RuntimeError: Cannot use controlled_by method on gate <qibo.gates.gates.CRY object at 0x7cef2f413a60> because it is already controlled by (0,).

Meanwhile, it is possible to create a $2$-controlled $RY$ gate using 
```python
In [3]: gates.RY(1, 0.1).controlled_by(0, 2)
Out[3]: <qibo.gates.gates.RY at 0x7cef2f2f8cd0>

This is an inconsistency because there should be no difference between a $2$-controlled $RY$ gate and a $1$-controlled $CRY$ gate. They are the same gate.

I used $RY$ as an example, but the same thing happens to other gates. For instance,

In [12]: gates.X(0).controlled_by(1, 2, 3)
Out[12]: <qibo.gates.gates.X at 0x7ceed33c5db0>

In [13]: gates.TOFFOLI(1, 2, 0).controlled_by(3)
[Qibo 0.2.8|ERROR|2024-06-11 08:37:52]: Cannot use `controlled_by` method on gate <qibo.gates.gates.TOFFOLI object at 0x7ceed3190850> because it is already controlled by (1, 2).
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[13], line 1
----> 1 gates.TOFFOLI(1, 2, 0).controlled_by(3)

File ~/.local/lib/python3.10/site-packages/qibo/gates/abstract.py:311, in Gate.check_controls.<locals>.wrapper(self, *args)
    309 def wrapper(self, *args):
    310     if self.control_qubits:
--> 311         raise_error(
    312             RuntimeError,
    313             "Cannot use `controlled_by` method "
    314             + f"on gate {self} because it is already "
    315             + f"controlled by {self.control_qubits}.",
    316         )
    317     return func(self, *args)

File ~/.local/lib/python3.10/site-packages/qibo/config.py:46, in raise_error(exception, message)
     39 """Raise exception with logging error.
     40 
     41 Args:
     42     exception (Exception): python exception.
     43     message (str): the error message.
     44 """
     45 log.error(message)
---> 46 raise exception(message)

RuntimeError: Cannot use `controlled_by` method on gate <qibo.gates.gates.TOFFOLI object at 0x7ceed3190850> because it is already controlled by (1, 2).
  1. The first issue leads to a second issue. When defining controlled $RY$ gates using the controlled_by method, somehow the gate class is changed to gates.CRY if only one control is passed. This leads to wrong gate counts when using the Circuit.gates_of_type method. Example:
    
    In [11]: circuit = Circuit(3)
    ...: circuit.add(gates.RY(0, 0.1))
    ...: circuit.add(gates.RY(0, 0.2).controlled_by(1))
    ...: circuit.add(gates.RY(0, 0.3).controlled_by(1, 2))
    ...: 
    ...: print(circuit.draw())
    ...: print()
    ...: print(f"# RY: {len(circuit.gates_of_type(gates.RY))}")
    ...: print(f"# cRY: {len(circuit.gates_of_type(gates.CRY))}")

q0: ─RY─RY─RY─ q1: ────o──o── q2: ───────o──

RY: 2

cRY: 1


If the $RY$ gate is controlled by a single qubit, it is counted as a $CRY$. Otherwise, it is counted as a $RY$ gate.