Logical circuit in Dual-rail encoding with perceval (efficiently)

Hi,

I am trying to to build a generic logical circuits Class ( à la Qiskit) based on LO components available in Perceval. The idea is to provide an CS friendly API with Hadamard etc and without having to handle the encoding manually.

I am not so familiar with Perceval’s best practices yet, but I tried to follow the doc to achieve what I am trying to do.

  • Provide an API/wrapper for some functions H, CNOT/CZ and phase shift notably.

  • Each logical qubit is encoded on 2 Perceval qubits. The logical 0 is 01, the logical 1 is 10 so that’s why I multiply the (logical) width by 2 (in init function) and I assume our initial state is always |0…0> (the common assumption in the circuit model as usual) so I use as initial state 01 01…01 in measurement via the function “with inputs”.

  • Measurement is performed using pcvl sampler and returning an object to the user.

Unfortunately, my current implementation seems to be very resource intensive, takes a lot of time, RAM and I can’t go beyond 3-4 logical qubits…

Are there any mistakes or any mis-uses in my implementation that could be fixed to improve efficiency?
I appreciate your assistance

Thanks

import numpy as np
import sympy as sp


from collections import Counter

import perceval as pcvl
import perceval.components as comp
from perceval.components import catalog



class LogicalCirc():
    def __init__(self,circ_width:int):
        # I have a doubt here, since we will be working will LO circs, so I should call pcvl 
        
        self.circuit = pcvl.Circuit(2*circ_width) 
        self.circ_width = circ_width#*2
        init_state = [0,1]*circ_width  # should I init them to 01 in dual ral is equivalent to |0>L?
        self.basic_state = pcvl.BasicState(init_state)
        self.p=pcvl.Processor("SLOS",2*circ_width)
    def x(self,wire):
        circ_x=comp.PERM([1,0])
        self.p.add(wire,2*circ_x)

    def y(self,wire):
        circ_y=comp.PERM([1,0]) // (0,PS(-np.pi/2)) // (1,PS(np.pi/2))
        self.p.add(wire,circ_y)
		
	def had(self,wire):
        circ_h=comp.BS.H()
        self.p.add(2*wire,circ_h)

    ## Rotation gates 
    # todo
    def phase_shift(self,wire,angle=sp.pi/2):
        ph_shifter = comp.PS(angle)
        self.p.add(1+wire*2,ph_shifter)
	def cnot(self,ctrl,target):
            cnot=catalog['heralded cnot'].as_processor().build()
            self.p.add([ctrl*2,1+ctrl*2,target*2,1+target*2],cnot)
            
    
    def cz(self,ctrl,target):
        #1. Making a Controlled-Z from a CNOT
        self.had(target)
        self.cnot(ctrl,target)
        self.had(target)
		
	def measure(self,nb_samples=50):
        self.p.with_input (self.basic_state)
        sampler = pcvl.algorithm.Sampler(self.p)
        samples = sampler.samples(nb_samples)
        
        samples_counter = dict()
        for state in samples['results']:
            if samples_counter.get(state)==None:
                samples_counter[state]=1
            else: samples_counter[state]+=1
#             print(state)
#         return max(samples_counter, key = samples_counter.get)
        return samples_counter
	def draw(self):
        return pcvl.pdisplay(self.p)

I had a look at your question. Normally, SLOS runs pretty well on a local machine up to 6 photons, so it’s unusual that 3-4 is so memory intensive. I am not the expert on Perceval’s memory management (@Eric is), but I would have a few quick questions/suggestions:

  1. Since you are running a noiseless/lossless simulation, have you tried the Clifford and Clifford Processor? Does it run any faster for you? [this is probably not the error, as you’re still well under 6 photons, but worth a try]
  2. Do you know which function is slowing you down the most? Is it measure? If you have a minimum example which runs slowly on 3-4 photons, it would be helpful for us to diagnose.