qadence
qadence copied to clipboard
[Proto] Integrate the Pyqtorch's noisy simulation
This issue is to present a prototype for integrating pyq
's noise gates into qadence
. The primary goal is to ensure that noise handling is effectively incorporated without disrupting the existing functionalities. Below are the key points of this prototype:
1. Creation of NoisyPrimitivesBlocks:
• New blocks derived from PrimitivesBlock
have been created, called NoisyPrimitivesBlock
.
• These blocks introduce a new input parameter, noise_probability
, which allows specifying the probability of noise when creating a block.
In blocks/primitive.py
:
class NoisyPrimitiveBlock(PrimitiveBlock):
"""
NoisyPrimitiveBlock represents elementary unitary operations with noise.
This class adds a noise probability parameter to the primitive block,
representing the likelihood of an error occurring during the operation.
"""
name = "NoisyPrimitiveBlock"
def __init__(
self, qubit_support: tuple[int, ...], noise_probability: float | tuple[float, ...]
):
super().__init__(qubit_support)
self._noise_probability = noise_probability
@property
def noise_probability(self) -> float | tuple[float, ...]:
return self._noise_probability
def __eq__(self, other: object) -> bool:
if not isinstance(other, NoisyPrimitiveBlock):
return False
return super().__eq__(other) and self.noise_probability == other.noise_probability
def _to_dict(self) -> dict:
block_dict = super()._to_dict()
block_dict.update({"noise_probability": self.noise_probability})
return block_dict
@classmethod
def _from_dict(cls, d: dict) -> NoisyPrimitiveBlock:
return cls(d["qubit_support"], d["noise_probability"])
def __hash__(self) -> int:
return hash((super().__hash__(), self.noise_probability))
def dagger(self) -> PrimitiveBlock:
raise ValueError("Property `dagger` not available for noise gate.")
2. Create Noisy Gates in Qadence:
• Prior to this, we added the noise gates name to theOpName
class.
• Develop blocks that will represent our noisy gates within qadence
.
• Create subclasses of NoisyPrimitiveBlock
to implement these noise gates.
For instance with the BitFlip
gate. In operations/noise.py
:
class BitFlip(NoisyPrimitiveBlock):
"""The Bitflip noise gate."""
name = OpName.BITFLIP
def __init__(self, target: int, noise_probability: float | tuple[float, ...]):
super().__init__((target,), noise_probability)
@property
def generator(self) -> None:
raise ValueError("Property `generator` not available for non-unitary operator.")
@property
def eigenvalues_generator(self) -> None:
raise ValueError("Property `eigenvalues_generator` not available for non-unitary operator.")
3.Modify convert_block function for pyq:
• Prior to this, we created thesingle_qubit_noise_gateset
type list .
• During the conversion of blocks to gates for pyq
, add a condition for noise block in convert_block
.
• Ensure that the new noise_probability
parameter is taken into account during this conversion.
In operations/__init__.py
:
single_qubit_gateset = [X, Y, Z, H, I, RX, RY, RZ, U, S, SDagger, T, PHASE]
single_qubit_noise_gateset = [BitFlip]
In backends/pyqtorch/convert_ops.py
:
def convert_block():
...
elif isinstance(block, tuple(single_qubit_noise_gateset)):
pyq_cls = getattr(pyq, block.name)
op = pyq_cls(qubit_support[0], block.noise_probability) # type: ignore[attr-defined]
return [op]