qiboteam / qibo

A full-stack framework for quantum computing.
https://qibo.science
Apache License 2.0
294 stars 60 forks source link

Noise as a gate vs. Noise everywhere #55

Closed igres26 closed 4 years ago

igres26 commented 4 years ago

Quick issue to outline the noise discussion we had in the meeting.

Having the possibility to add noise exactly where we want to as an extra gate is really useful. However, if one wanted to add noise to an already designed circuit, adding a gate after every gate of the system would be very time consuming.

Therefore, an option to run an existing (noiseless) circuit with an extra variable to automatically implement a noise gate after every circuit gate would be really helpful.

scarrazza commented 4 years ago

This sounds like moving the noise to a callback, right?

stavros11 commented 4 years ago

What I had in mind was to implement a method with_noise(px, py, pz) under models.Circuit that returns the same circuit but with a noise channel after every gate. The user could then do

# define a noiseless circuit
circuit = Circuit(5)
circuit.add(gates.X(0))
circuit.add(gates.Y(1))
# etc. ...

noisy_circuit = circuit.with_noise(px=0.1, pz=0.1)
# creates a new circuit that has the same gates as `circuit` including
# a NoiseChannel with the given probabilities after each gate.

# execute noiseless circuit
noiseless_final_state = circuit()
# execute noisy circuit
noisy_final_state = noisy_circuit()

This is straightforward to implement and I believe it is also flexible from the user perspective as one can do things like:

I am not sure if the callback approach is useful here because the idea of callbacks was to track specific quantities (like the entanglement entropy) as the states propagates through the circuit. The equivalent here would have to return the full density matrix after every gate which would consume a lot of memory and is not required as people are typically interested only in the final density matrix (or measurement results).

scarrazza commented 4 years ago

Thanks for the explanation, this approach is quite transparent. Maybe we should also consider the possibility to implement a circuit.copy function.

stavros11 commented 4 years ago

Thanks for the explanation, this approach is quite transparent. Maybe we should also consider the possibility to implement a circuit.copy function.

circuit.copy should also be easy to implement. I don't have many use-cases in mind, but it could certainly be useful somewhere. The only thing to decide is whether the gates in the copied circuit are the same Python objects as the original or we want to construct new gates with the same qubit indices. For with_noise I was planning to use the same objects, as I don't see any issues with this and we also avoid recalculating the cached indices, etc..

scarrazza commented 4 years ago

We probably can consider the distinction like copy and deepcopy, so I think a copy method should be ok, and eventually could be the return of with_noise.

igres26 commented 4 years ago

This with_noise implementation sounds just like the thing I was looking for.

scarrazza commented 4 years ago

For with_noise I was planning to use the same objects, as I don't see any issues with this and we also avoid recalculating the cached indices, etc..

yes, this sounds like a sensible option.

scarrazza commented 4 years ago

Just to unlock this discussion, I think we can have for the time being something simple like:

def with_noise(self, px=0, py=0, pz=0):
    c = BaseCircuit(self.nqubits)
    c.queue = self.queue
    c.add(...) # add noise after each gate
    return c
scarrazza commented 4 years ago

And keep the copy/deepcopy for later, if really needed.

stavros11 commented 4 years ago

@scarrazza, yes I already started implementing this yesterday, I just left in the middle to look at the multi-GPU benchmarks. I was planning to add copy and with_noise for now and leave deepcopy, which is slightly more complicated for later. I will probably open a PR for this later today.

scarrazza commented 4 years ago

Perfect thanks.