entropicalabs / openqaoa

Multi-backend SDK for quantum optimisation
MIT License
117 stars 58 forks source link

Integrate the Cirq backend with OpenQAOA #306

Open KilianPoirier opened 5 months ago

KilianPoirier commented 5 months ago

Issue Description

Can we add the Cirq backend to the OpenQAOA stack? Cirq provides a SDK to simulate and execute quantum circuits on multiple backends:

Note: someone already tackled this issue but couldn't finish the task. You can find the details of their attempt in this closed PR.

Changes to be made

In the same way we implemented different backends (physical QPU or simulators), implement a plugin package openqaoa-cirq that allows execution on Cirq's backend. More specifically, changes include:

shubhamkaushal765 commented 3 months ago

Hi @KilianPoirier! I would like to work on this issue of adding Cirq backend to OpenQAOA. Can you assign it to me?

KilianPoirier commented 3 months ago

Hi @shubhamkaushal765 , thanks for looking into openqaoa! You can go ahead and tackle the issue, it will be assigned when the PR has been accepted.

shubhamkaushal765 commented 3 months ago

Hi @KilianPoirier, Thanks! OpenQAOA looks great and I really wanted to contribute to it.

First of all, sorry for the long post, therefore added a TLDR.

TLDR:

So, I had been tackling this issue, and I have run into some creative challenges, some of which I had solved, and for others I wanted your suggestion on how to proceed. They are:

Cirq dependency conflicts

cirq==1.4.0: the latest cirq version is dependent on pydantic==1.10.16, attrs==21.4.0 and httpx==0.23.3. It conflicts with other packages installed which have much updated versions of these sub-packages. Downgrading cirq will only lead to much older versions of these sub-packages, hence the only option remains is to downgrade other packages. We can also try to find a balance, but I am not sure how good it will work. I have more forward with cirq==1.4.0 and built the openqaoa-cirq on it. If later we decided to change the version, I am sure only small modifications will be needed to suit the version.

Dependency Conflicts ```python amazon-braket-default-simulator 1.23.2 requires pydantic>2, but pydantic 1.10.16 is installed which is incompatible. amazon-braket-schemas 1.22.0 requires pydantic>2, but pydantic 1.10.16 is installed which is incompatible. cirq-rigetti==1.4.0 -> pyquil<4.0.0,>=3.2.0 -> qcs-api-client<0.22.0,>=0.21.0 -> pydantic 1.10.16 jsonschema 4.22.0 requires attrs>=22.2.0, but attrs 21.4.0 is installed which is incompatible. referencing 0.35.1 requires attrs>=22.2.0, but attrs 21.4.0 is installed which is incompatible. cirq==1.4.0 -> cirq-core==1.4.0 -> attrs 21.4.0 jupyterlab 4.2.2 requires httpx>=0.25.0, but httpx 0.23.3 is installed which is incompatible. cirq-rigetti==1.4.0 -> pyquil<4.0.0,>=3.2.0 -> qcs-api-client<0.22.0,>=0.21.0 -> httpx 0.23.3 ```

QPU availability by cirq

This link says

Access is currently only granted to those on an approved list. No public access to the service is available at this time.

So, I have added a boiler plate code which can be modified as required. Google also offers IONQ's QPU, which I plan to add in the coming few days.

QAOACirqBackend*Simulator

  1. cirq has no direct implementation of 2-qubit rotation gates such as RZZ, RZX, RXX, etc. We have to use the cirq.ZZPowGate which takes exponent as a keyword argument. So to make the rotation angles compatible with this format we can do rotation_angle / np.pi to get the exponent.
  2. cirq takes the exponent and approximates the representative fraction, which leads to small errors. This trickles down errors to wavefunction and expectation methods. The expectation values usually have an error of 1e-6, but the wavefunctions are going wild, with very different real and imaginary values.
# function: src/openqaoa-cirq/tests/test_sim_cirq.py:test_qaoa_circuit_wavefunction_expectation_equivalence_1
# expectation value
>           self.assertAlmostEqual(
                cirq_expectation, vector_expectation, places=ASSERT_ALMOST_EQUAL_PLACES
            )
E           AssertionError: -0.302997738123 != -0.30299750696 within 7 places (2.3116300001957413e-07 difference)
==========================================================================

# wavefunction
Cirq WaveFunction: [ 0.11789444+0.2546382j  -0.33760953+0.31751314j -0.17041622+0.21223776j -0.26151618+0.252959j -0.26151618+0.252959j   -0.17041622+0.21223776j -0.33760953+0.31751314j  0.11789444+0.2546382j ]
Vector Wavefunction [(-0.11338235549177214+0.25667908366165665j), (-0.36265249809327543-0.029362249318695896j), (-0.27191651336185224+0.01216375954595944j), (-0.4613775050174799-0.04388124877541683j), (-0.4613775050174799-0.04388124877541683j), (-0.27191651336185224+0.01216375954595944j), (-0.36265249809327543-0.029362249318695896j), (-0.11338235549177214+0.25667908366165665j)]

Comparing circuit by both Qiskit and Cirq

Keep in mind that qiskit shows rotation_angle with ZZ, whereas cirq shows exponent.

# Qiskit circuit
      ┌───┐                          ┌────────┐
q1_0: ┤ H ├─■────────────────■───────┤ Rx(-2) ├
      ├───┤ │ZZ(6)           │       ├────────┤
q1_1: ┤ H ├─■───────■────────┼───────┤ Rx(-2) ├
      ├───┤         │ZZ(12)  │ZZ(18) ├────────┤
q1_2: ┤ H ├─────────■────────■───────┤ Rx(-2) ├
      └───┘                          └────────┘

wavefunction = [-0.11338236+0.25667908j -0.3626525 -0.02936225j -0.27191651+0.01216376j
 -0.46137751-0.04388125j -0.46137751-0.04388125j -0.27191651+0.01216376j
 -0.3626525 -0.02936225j -0.11338236+0.25667908j]
================================================

# Cirq circuit
                                  ┌───────────────────┐
0: ───H───ZZ───────────────────────ZZ─────────────────────Rx(-0.637π)───
          │                        │
1: ───H───ZZ^(-1/11)───ZZ──────────┼───────Rx(-0.637π)──────────────────
                       │           │
2: ───H────────────────ZZ^-0.18────ZZ^-0.27───────────────Rx(-0.637π)───
                                  └───────────────────┘
wavefunction = [ 0.11789444+0.2546382j  -0.33760953+0.31751314j -0.17041622+0.21223776j
 -0.26151618+0.252959j   -0.26151618+0.252959j   -0.17041622+0.21223776j
 -0.33760953+0.31751314j  0.11789444+0.2546382j ]

I tried building custom ZZ and other gates to overcome the approximation challenge, but as numpy function does not support sympy.Symbol, it is proving to be a challenge. I can explore more along this line, but frankly, I wanted your suggestion on this problem.

My implementation of ZZGate is as follows:

# self.theta is of type sympy.Symbol which is incompatible with np.exp
class RZZGate(cirq.Gate):
    def __init__(self, theta):
        super().__init__()
        self.theta = theta

    def _unitary_(self):
        return np.array(
            [
                [np.exp(-1j * self.theta / 2), 0, 0, 0],
                [0, np.exp(1j * self.theta / 2), 0, 0],
                [0, 0, np.exp(1j * self.theta / 2), 0],
                [0, 0, 0, np.exp(-1j * self.theta / 2)],
            ]
        )

    def _num_qubits_(self):
        return 2

    def _circuit_diagram_info_(self, args):
        return f"RZZ({self.theta})", f"RZZ({self.theta})"
KilianPoirier commented 3 months ago

Hi @shubhamkaushal765 thanks for tackling this issue!

Let me address your points in order:

I hope this helps. Let me know if you have any additional questions!