Open EthanObadia opened 5 days ago
Discussion Point:
The idea is not to have heavily modified the Noise
class in pyq
, but simply to add a parameter, such as a primitive
parameter, which would indicate to which primitive gate we are adding noise, in order to have this syntax:
pyq.noise.BitFlip(primitive= x.__class__.__name__, target=x.target, proba=error_prob)
Now, the question is, how can we make pyq
's Noise
understand the error_prob
and the protocol
to use, knowing that the Noise
in qadence
is not written in the same way at all?
For me, it would require a conversion function either in qadence
or in pyq
. But I think pyq
should be completely independent of qadence
. Additionally, the whole purpose of this approach was to ensure that qadence
does not perform any additional operations. Therefore, I am not sure how to translate the following syntax so that the noise
parameter added to the primitive can understand it:
bitflip_noise = Noise(protocol=Noise.BITFLIP, options={"error_probability": 0.5})
Discussion Point:
The idea is not to have heavily modified the
Noise
class inpyq
, but simply to add a parameter, such as aprimitive
parameter, which would indicate to which primitive gate we are adding noise, in order to have this syntax:pyq.noise.BitFlip(primitive= x.__class__.__name__, target=x.target, proba=error_prob)
Now, the question is, how can we make
pyq
'sNoise
understand theerror_prob
and theprotocol
to use, knowing that theNoise
inqadence
is not written in the same way at all?For me, it would require a conversion function either in
qadence
or inpyq
. But I thinkpyq
should be completely independent ofqadence
. Additionally, the whole purpose of this approach was to ensure thatqadence
does not perform any additional operations. Therefore, I am not sure how to translate the following syntax so that thenoise
parameter added to the primitive can understand it:bitflip_noise = Noise(protocol=Noise.BITFLIP, options={"error_probability": 0.5})
4.Creation of a Noise
Type Equivalent to Qadence
:
• To avoid type conflicts, create a Noise
type equivalent to Qadence
, which will allow us to extract all information without type conflicts for the noise
attribute in Primitive
.
• The conversion between the Qadence
type and the pyq
type will be done in the converts_op.py
file in Qadence
.
In noise.py
:
class Noisy_protocols:
BITFLIP = "BitFlip"
PHASEFLIP = "PhaseFlip"
PAULI_CHANNEL = "PauliChannel"
AMPLITUDE_DAMPING = "AmplitudeDamping"
PHASE_DAMPING = "PhaseDamping"
GENERALIZED_AMPLITUDE_DAMPING = "GeneralizedAmplitudeDamping"
DEPHASING = "Dephasing"
def __init__(self, protocol: str, options: dict = dict()) -> None:
self.protocol: str = protocol
self.options: dict = options
def __repr__(self) -> str:
return f"protocol: {self.protocol}, options: {self.options}"
@property
def error_probability(self):
return self.options.get("error_probability")
def protocol_to_gate(self):
try:
gate_class = getattr(sys.modules[__name__], self.protocol)
return gate_class
except AttributeError:
raise ValueError(
f"The protocol {self.protocol} has not been implemented in pyqtorch yet."
)
The initially proposed equation for the evolution of the density matrix is incorrect:
$$\rho^{\prime} = X{noisy} \rho X^{\dagger}{noisy} = X(\text{Kraus}_1 + \text{Kraus}_2) \rho (\text{Kraus}^{\dagger}_1 + \text{Kraus}^{\dagger}_2)X^{\dagger}. $$
This formulation is incorrect because the Kraus operators cannot be simply factorized in this manner.
For a noisy gate, the correct evolution of the density matrix $\rho$ is given by:
$$ S(\rho) = \sum_i K_i \rho K^{\dagger}_i. $$
If a unitary operation $X$ is applied before the noisy gate, the correct evolution is:
$$ \rho' = \sum_i K_i (X \rho X^{\dagger })K^{\dagger}_i . $$
This change in calculation means that a loop is required to sum over the Kraus operators, which makes the calculation more involved. However, this does not affect the overall reasoning.
@EthanObadia you deserve a special mention for the 200th issue !
I'm glad you have documented this. This clarifies the implementation detail that I was interested in. Yep, you cannot simply add krauss operators, the noise acts independently to output its effect, which gets summed up later. Wish I looked up this thread sooner
This issue presents a prototype idea to modify noise for the
Qadence
implementation, where the noise is implemented such as:The goal is to modify the way single qubit gates have been implemented to minimize the number of manipulations in
Qadence
while maintaining consistent syntax. To achieve this, following the way noise is invoked inQadence
, we will no longer treat noise models as separate gates. Instead, they will be parameters that modify thePrimitive
gates, which will now be the only gates that can be directly called as seen in the example above.Prototype:
1. Add a Noise Parameter to Primitive Gates in
pyq
: • Introduce anoise
parameter toPrimitive
gates inpyq
. • By default, thisnoise
parameter is set toNone
.For instance:
2. Modification of the
Primitive
forward
function : • If thenoise
parameter remainsNone
, the gate behaves as a standardPrimitive
forward
pass. • If the noise parameter is an instance ofNoise
, thePrimitive
forward
function will call theNoise
forward
pass.In
primitive.py
:3. Modification of the
Noise
forward
function : • Create the noisy primitive tensor as $X_{noisy} = X(\text{Kraus}_1 +\text{Kraus}_2)$, • Using this tensor to compute :$$ \rho^{\prime} = X{noisy} \rho X^{\dagger}{noisy} = X(\text{Kraus}_1 +\text{Kraus}_2) \rho (\text{Kraus}^{\dagger}_1 +\text{Kraus}^{\dagger}_2)X^{\dagger}.$$
In
noise.py
(will add it later):