Decomposing a circuit for X8 chip implementation

Hello. I wanted to try to implement the following circuit in the X8 chip. Can anyone please help me decompose the following circuit so I can implement it? Thanks in advance.

import numpy as np
import strawberryfields as sf
from strawberryfields.ops import *


from numpy import pi, sqrt
import math
import random


def U_D(l,qp,params):
    Xgate(params[0]) | qp[0]
    Xgate(params[1]) | qp[1]
    Xgate(params[2]) | qp[2]
    Xgate(params[3]) | qp[3]

    
    CZgate(params[4]) | (qp[0], qp[1])
    CZgate(params[5]) | (qp[1], qp[2])
    CZgate(params[6]) | (qp[2], qp[3])
    
    
    return qp

paramsf = [  0.67298385,   0.37791076,   0.20837424,   0.59441259,
         0.55759042,   0.43001499,   0.52092983,   4.80069928,
        24.24655461,   0.84556549, -6.36143337]

N=4
n_layers=1
prog = sf.Program(N) # defining photonic quantum circuit with N subsystems (wires)
eng = sf.Engine(backend="fock", backend_options={"cutoff_dim": 10})
with prog.context as q:
     
        for l in range(n_layers):
            
            U_D(l,q,paramsf)

            results = eng.run(prog)

Hey @Pranav_Chandarana!

Most gates should have a decompose method (see here in the documentation). The functionality states that it will “decompose the operation into elementary operations supported by the backend API.” So, when using X8 via, say, eng = RemoteEngine("X8"), the decompose method should decompose any operations to the supported ones on X8. That said, some operations don’t have a decomposition implemented.

Let me know if this helps!

So now I have tried this:

def U_D(l,qp,params):
    
    Xgate(params[0]).decompose(qp[0])
    Xgate(params[1]).decompose(qp[1])
    Xgate(params[2]).decompose(qp[2]) 
    Xgate(params[3]).decompose(qp[3])
    
    Xgate(params[0]).decompose(qp[4]) 
    Xgate(params[1]).decompose(qp[5])
    Xgate(params[2]).decompose(qp[6]) 
    Xgate(params[3]).decompose(qp[7])

    
    CZgate(params[4]).decompose((qp[0], qp[1]))  
    CZgate(params[5]).decompose((qp[1], qp[2]))  
    CZgate(params[6]).decompose((qp[2], qp[3]))
    
    CZgate(params[4]).decompose((qp[4], qp[5]))  
    CZgate(params[5]).decompose((qp[5], qp[6]))  
    CZgate(params[6]).decompose((qp[6], qp[7]))
    
    
    return qp

paramsf = [  0.67298385,   0.37791076,   0.20837424,   0.59441259,
         0.55759042,   0.43001499,   0.52092983,   4.80069928,
        24.24655461,   0.84556549, -6.36143337]

N=8
n_layers=1
prog = sf.Program(N) # defining photonic quantum circuit with N subsystems (wires)
eng = sf.RemoteEngine("X8")
with prog.context as q:
     
        for l in range(n_layers):
            
            U_D(l,q,paramsf)
            MeasureFock() | q

prog_compiled = prog.compile(device=eng.device)
prog_compiled.print()

but this gives the output

S2gate(0, 0) | (q[0], q[4])
S2gate(0, 0) | (q[1], q[5])
S2gate(0, 0) | (q[2], q[6])
S2gate(0, 0) | (q[3], q[7])
MZgate(3.142, 0) | (q[0], q[1])
MZgate(3.142, 0) | (q[2], q[3])
MZgate(3.142, 0) | (q[1], q[2])
MZgate(3.142, 0) | (q[0], q[1])
MZgate(3.142, 0) | (q[2], q[3])
MZgate(3.142, 0) | (q[1], q[2])
Rgate(0) | (q[0])
Rgate(0) | (q[1])
Rgate(0) | (q[2])
Rgate(0) | (q[3])
MZgate(3.142, 0) | (q[4], q[5])
MZgate(3.142, 0) | (q[6], q[7])
MZgate(3.142, 0) | (q[5], q[6])
MZgate(3.142, 0) | (q[4], q[5])
MZgate(3.142, 0) | (q[6], q[7])
MZgate(3.142, 0) | (q[5], q[6])
Rgate(0) | (q[4])
Rgate(0) | (q[5])
Rgate(0) | (q[6])
Rgate(0) | (q[7])
MeasureFock | (q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7])

And when I perform the same circuit with the local fock backend, it doesn’t give any circuit. I don’t know if that’s the problem with my code or anything else. Coul you please send me a snippet of the code that works? That would be a huge help.

Ah, nice! compile is probably a better option compared to decompose in this case.

And when I perform the same circuit with the local fock backend, it doesn’t give any circuit.

If I use your code and simply add this at the end, it works for me.

eng = sf.Engine('fock', backend_options={"cutoff_dim": 2})
results = eng.run(prog_compiled)
print(results.samples)

This would work I think but the decompositions are not correct. just try

prog.print()

It would return just

MeasureFock | (q[0], q[1], q[2], q[3], q[4], q[5], q[6], q[7])

So, any of the gates are not applying to the X8 device. It is just Measurements to the initial vacuum state. Please correct me if I am wrong at any point. This is what is happening in my code.

So prog isn’t the thing you actually care about here. prog_compiled is the object that you want to run on X8, so if you look at prog_compiled.print() it shows the proper gates :slight_smile:. Then you want to run that program via results = eng.run(prog_compiled).

Thanks for the clarification. I tried that and found out the quad_exp value by

[results.state.quad_expectation(i)[0] for i in range(N)]

and its showing

[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

which is not the expected solution. Also, even if we change the “paramsf” values, the circuit output is the same independent of the value of the parameters. Could you please check if that is the case for your code as well? If yes then, there is some error in the code somehow right? Thanks for the time!

Hey @Pranav_Chandarana, I just need to consult one of my colleagues and I’ll get back to you shortly!

Hey @Pranav_Chandarana! I just spoke with someone who is more familiar with the X series chips.

I think the confusion comes from the documentation for X8. If you look at the allowable operations, it says that an arbitrary 4x4 unitary is allowed, but it must be built using BSgate, MZgate, Rgate and Interferometer. That might make it seem like any unitary (including CZgate) is allowable, but the qualifier “4x4” means the unitary is at the level of the mode transformations (i.e. how they transform the raising and lowering operators) — not the Hilbert space. Indeed, CZgates are unitary at the level of the Hilbert space, but if you look at their transformation on the raising and lowering operators, it is not a unitary. Only linear optical transformations (i.e. BSgate, MZgate, Rgate and Interferometer) are unitary at the level of the mode transformations.

Since you have no gates that are acceptable, it just ended compiling a default circuit that seems to consist of all the squeezers being off and the interferometer not interacting any of the modes. So your circuit can’t be programmed on X8.

1 Like