pyqtorch
pyqtorch copied to clipboard
[Proto] Noise modifications for Qadence implementation
This issue presents a prototype idea to modify noise for the Qadence
implementation, where the noise is implemented such as:
bitflip_noise = Noise(protocol=Noise.BITFLIP, options={"error_probability": 0.5})
phaseflip_noise = Noise(protocol=Noise.PHASEFLIP, options={"error_probability": 0.2})
noise = {
"bitflip": bitflip_noise,
"phaseflip": phaseflip_noise
}
x = X(target = 0, noise = noise)
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 in Qadence
, we will no longer treat noise models as separate gates. Instead, they will be parameters that modify the Primitive
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 a noise
parameter to Primitive
gates in pyq
.
• By default, this noise
parameter is set to None
.
For instance:
class X(Primitive):
def __init__(self, target: int, noise: Noisy_protocols | dict[str, Noisy_protocols] | None = None):
super().__init__(OPERATIONS_DICT["X"], target, noise)
2. Modification of the Primitive
forward
function :
• If the noise
parameter remains None
, the gate behaves as a standard Primitive
forward
pass.
• If the noise parameter is an instance of Noise
, the Primitive
forward
function will call the Noise
forward
pass.
In primitive.py
:
def forward(
self, state: Tensor, values: dict[str, Tensor] | Tensor = dict()
) -> Tensor:
if self.noise:
if isinstance(self.noise, dict):
for noise_instance in self.noise.values():
protocol = noise_instance.protocol_to_gate()
noise_gate = protocol(
primitive=self.unitary(values),
target=self.target,
probability= noise_instance.error_probability,
)
# CALCULUS
else:
protocol = self.noise.protocol_to_gate()
noise_gate = protocol(
primitive = self.unitary(values),
target = self.target,
probability = self.noise.error_probability,
)
return noise_gate(state)
else:
if isinstance(state, DensityMatrix):
# FIXME: fix error type int | tuple[int, ...] expected "int"
# Only supports single-qubit gates
return operator_product(
self.unitary(values),
operator_product(state, self.dagger(values), self.target), # type: ignore [arg-type]
self.target, # type: ignore [arg-type]
)
else:
return apply_operator(
state,
self.unitary(values),
self.qubit_support,
len(state.size()) - 1,
)
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):
def forward(
self, state: Tensor, values: dict[str, Tensor] | Tensor = dict()
) -> Tensor: