CQCL / pytket-quest

Apache License 2.0
2 stars 0 forks source link

Familiarize with pytket #1

Closed ferbetanzo closed 6 months ago

ferbetanzo commented 7 months ago
TerraVenil commented 6 months ago

Playing around with integration ticket backend API with pytket-qulacs, let's considering the following circuit c = Circuit(4).X(0).CX(0, 1).CX(1, 0) Because qulacs supports SWAP gate I assume that after execution get_compiled_circuit I will get something like [Rx(1) q[0]; SWAP q[1], q[0]; ] because during compilation pass we are provided list of support gates, in out case https://github.com/CQCL/pytket-qulacs/blob/main/pytket/extensions/qulacs/qulacs_convert.py#L37, but instead received [Rx(1) q[0]; CX q[1], q[0]; ] Indeed it's possible to get another circuit https://github.com/CQCL/tket/blob/main/pytket/pytket/backends/backend.py#L175 but assuming that a new circuit will preserve the same functionality (H -> ZXZ decomposition, X -> Rx(1) and etc.) as was in the original one. Right? Will be great to understand what logic behind such optimization. Then I did manually calculation initial and compiled circuit and they as you might guess gave different state vectors. Ok, let's run this compiled/optimised circuit with run_circuit. Then I found another place https://github.com/CQCL/pytket-qulacs/blob/eabf3ffb9630d0f20810a53e3a9648ed5d78660d/pytket/extensions/qulacs/qulacs_convert.py#L51-L52 where circuit modified again but during circuit execution. And here additional SWAP gate was added to the circuit. Basically it's not really obvious that after compilation circuit something new will be added to it, according to https://github.com/CQCL/tket/blob/main/pytket/pytket/backends/backend.py#L380 run_circuit only submits provided circuit to backend so I expect that backend will execute provided circuit and will not modify it internally. Is there any design consideration behind this (hiding such modifications can lead to confusion as it was in my case until I deeg into the code)? And also from technical perspective instead of the original circuit of 1 X gate and 2 CNOT gates we have 1 Rx gate, 1 CNOT gate and one SWAP gate (generally can be represented by 3 CNOT gates but in case of simulation probably we can do better) which is also complicates it internally. At the end the result of the state vector was correct but you know some magic was executed behind to make this happen 😏

I have added very simple example to Colab if you need playaround https://colab.research.google.com/drive/1hEthoq6ExIgXFGcxlNQil1erZ_SZQ8Cx#scrollTo=atIumAbMm-P3.

ferbetanzo commented 6 months ago

@TerraVenil This is a great question. Indeed, I think pytket could do a better job of communicating/showing the user what is going on under the hood while optimizing circuits.

In this case when you did the QulacsBackend().get_compiled_circuit() the default_compilation_pass of the Qulacs backend was executed with the default optimization level, which is 2. If you look at the default_compilation_pass code, you see that the optimization passes applied for optimization level 2 are:

            return SequencePass(
                [
                    DecomposeBoxes(),
                    FlattenRegisters(),
                    FullPeepholeOptimise(),
                    self.rebase_pass(),
                ]

The last one, self.rebase_pass(), is the transpilation pass that converts the circuit to the gate set supported by Qulacs. The second to last pass FullPeepholeOptimise(), is the one that more heavily optimizes the circuit and can remove one of the CX gates. The inner workings of this pass are probably too much to explain here (feel free to have a look at the code to get an idea), but the important thing is to realize that depending on the optimization level chosen, the circuit returned by get_compiled_circuit() can indeed be very different than the input circuit, but will (or should) always be equivalent.

Now, I'm intrigued by your statement that when executing the circuit with run_circuit(), you noticed a SWAP gate being added to the circuit. Given that CX is already a 2 qubit gate supported by Qulacs, I'm not sure why this would be changed for a SWAP. Maybe the Qulacs simulator prefers SWAPs over CXs?

TerraVenil commented 6 months ago

can indeed be very different than the input circuit, but will (or should) always be equivalent.

Yes, I definitely understand that aggressive optimisation can remove some gates or replace/decompose existing one and returned circuit can be veeery different, nevertheless it MUST be equivalent, otherwise, how can I trust to the final simulation results :thinking: To illustrate that circuits are not only different but also lead to different results, Colab example was updated.

Maybe the Qulacs simulator prefers SWAPs over CXs?

An example with an additional SWAP gate was included to give more details and I will highlight the statement that was at the beginning why this happens. To give a context form where I found such gates composition https://github.com/CQCL/pytket-qulacs/blob/main/tests/test_qulacs_backend.py#L166. Probably I have to add this information at the beginning.

Then I found another place https://github.com/CQCL/pytket-qulacs/blob/eabf3ffb9630d0f20810a53e3a9648ed5d78660d/pytket/extensions/qulacs/qulacs_convert.py#L51-L52 where circuit modified again but during circuit execution. And here additional SWAP gate was added to the circuit.