Skip to content

Commit

Permalink
Use Noisy_protocols form pyq
Browse files Browse the repository at this point in the history
  • Loading branch information
EthanObadia committed Jul 11, 2024
1 parent b5209f2 commit 23a073e
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 48 deletions.
11 changes: 9 additions & 2 deletions qadence/backends/pyqtorch/convert_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
)
from qadence.blocks.primitive import ProjectorBlock
from qadence.blocks.utils import parameters
from qadence.noise import Noise
from qadence.operations import (
U,
multi_qubit_gateset,
Expand Down Expand Up @@ -125,7 +126,7 @@ def convert_block(
elif (
is_single_qubit_chain(block)
and config.use_single_qubit_composition
and all([b.noise is None for b in block])
and all([b.noise is None for b in block]) # type: ignore[attr-defined]
):
return [
pyq.Merge(ops)
Expand All @@ -143,13 +144,19 @@ def convert_block(
return [getattr(pyq, block.name)(qubit_support[0])]
elif isinstance(block, tuple(single_qubit_gateset)):
pyq_cls = getattr(pyq, block.name)
pyq_noise = block.noise # type: ignore[attr-defined]
if pyq_noise:
if isinstance(pyq_noise, dict):
pyq_noise = {k: v.to_pyq() for k, v in pyq_noise.items()}
elif isinstance(pyq_noise, Noise):
pyq_noise = pyq_noise.to_pyq()
if isinstance(block, ParametricBlock):
if isinstance(block, U):
op = pyq_cls(qubit_support[0], *config.get_param_name(block))
else:
op = pyq_cls(qubit_support[0], config.get_param_name(block)[0])
else:
op = pyq_cls(qubit_support[0])
op = pyq_cls(qubit_support[0], pyq_noise) # type: ignore[attr-defined]
return [op]
elif isinstance(block, tuple(two_qubit_gateset)):
pyq_cls = getattr(pyq, block.name)
Expand Down
44 changes: 33 additions & 11 deletions qadence/blocks/primitive.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ class PrimitiveBlock(AbstractBlock):
"""
Primitive blocks represent elementary unitary operations.
#TODO: Add a description of the noise attribut
Examples are single/multi-qubit gates or Hamiltonian evolution.
See [`qadence.operations`](operations.md) for a full list of
primitive blocks.
Expand Down Expand Up @@ -91,19 +89,23 @@ def __eq__(self, other: object) -> bool:
if not isinstance(other, AbstractBlock):
raise TypeError(f"Cant compare {type(self)} to {type(other)}")
if isinstance(other, type(self)):
return self.qubit_support == other.qubit_support and self.noise == self.noise
return self.qubit_support == other.qubit_support and self.noise == other.noise
return False

def _to_dict(self) -> dict:
if self.noise is None:
noise_info = None
elif isinstance(self.noise, Noise):
noise_info = self.noise._to_dict()
elif isinstance(self.noise, dict):
noise_info = {k: v._to_dict() for k, v in self.noise.items()}
else:
noise_info = dict()
return {
"type": type(self).__name__,
"qubit_support": self.qubit_support,
"tag": self.tag,
"noise": self.noise._to_dict()
if isinstance(self.noise, Noise)
else {k: v._to_dict() for k, v in self.noise.items()}
if self.noise
else None,
"noise": noise_info,
}

@classmethod
Expand Down Expand Up @@ -154,7 +156,6 @@ def dagger(self) -> PrimitiveBlock:
class ParametricBlock(PrimitiveBlock):
"""Parameterized primitive blocks."""

# TODO: add noise param here
name = "ParametricBlock"

# a tuple of Parameter's specifies which parameters go into this block
Expand All @@ -177,7 +178,11 @@ def _block_title(self) -> str:
params_str.append(val)
else:
params_str.append(stringify(p))

if self.noise:
noise_index = s.find(", Noise")
primitive_part = s[:noise_index].strip()
noise_part = s[noise_index:].strip()
return f"{primitive_part} [params: {params_str}]{noise_part}"
return s + rf" \[params: {params_str}]"

@property
Expand Down Expand Up @@ -225,6 +230,7 @@ def __eq__(self, other: object) -> bool:
return (
self.qubit_support == other.qubit_support
and self.parameters.parameter == other.parameters.parameter
and self.noise == other.noise
)
return False

Expand All @@ -237,20 +243,36 @@ def __contains__(self, other: object) -> bool:
return False

def _to_dict(self) -> dict:
if self.noise is None:
noise_info = None
elif isinstance(self.noise, Noise):
noise_info = self.noise._to_dict()
elif isinstance(self.noise, dict):
noise_info = {k: v._to_dict() for k, v in self.noise.items()}
else:
noise_info = dict()

return {
"type": type(self).__name__,
"qubit_support": self.qubit_support,
"tag": self.tag,
"parameters": self.parameters._to_dict(),
"noise": noise_info,
}

@classmethod
def _from_dict(cls, d: dict) -> ParametricBlock:
params = ParamMap._from_dict(d["parameters"])
noise = d.get("noise")
if isinstance(noise, dict):
noise = {k: Noise._from_dict(v) for k, v in noise.items()}
elif noise is not None:
noise = Noise._from_dict(noise)
target = d["qubit_support"][0]
return cls(target, params) # type: ignore[call-arg]
return cls((target,), noise, params) # type: ignore[call-arg]

def dagger(self) -> ParametricBlock:
# Do not do the dagger of the noise gate. Only of the parametric one.
exprs = self.parameters.expressions()
params = tuple(-extract_original_param_entry(param) for param in exprs)
return type(self)(*self.qubit_support, *params) # type: ignore[arg-type]
Expand Down
5 changes: 5 additions & 0 deletions qadence/noise/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from dataclasses import dataclass
from typing import Callable, Counter, cast

from pyqtorch.noise import Noisy_protocols

PROTOCOL_TO_MODULE = {
"readout": "qadence.noise.readout",
}
Expand Down Expand Up @@ -39,6 +41,9 @@ def _to_dict(self) -> dict:
def __repr__(self) -> str:
return f"protocol: {self.protocol}, options: {self.options}"

def to_pyq(self) -> Noisy_protocols:
return Noisy_protocols(self.protocol, self.options)

@classmethod
def _from_dict(cls, d: dict) -> Noise | None:
if d:
Expand Down
47 changes: 34 additions & 13 deletions qadence/operations/parametric.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from logging import getLogger

import numpy as np
import sympy
import torch
from torch import Tensor, cdouble

Expand All @@ -15,12 +14,13 @@
add, # noqa
chain,
)
from qadence.noise import Noise
from qadence.parameters import (
Parameter,
ParamMap,
evaluate,
)
from qadence.types import OpName, TNumber, TParameter
from qadence.types import OpName, TParameter

from .primitive import I, X, Y, Z

Expand All @@ -32,10 +32,15 @@ class PHASE(ParametricBlock):

name = OpName.PHASE

def __init__(self, target: int, parameter: Parameter | TNumber | sympy.Expr | str):
def __init__(
self,
target: int,
parameter: Parameter | TParameter | ParamMap,
noise: Noise | dict[str, Noise] | None = None,
):
self.parameters = ParamMap(parameter=parameter)
self.generator = I(target) - Z(target)
super().__init__((target,))
self.generator = I(target) - Z(target) # ? Do I need to add the noise param here ?
super().__init__((target,), noise)

@classmethod
def num_parameters(cls) -> int:
Expand All @@ -56,13 +61,18 @@ class RX(ParametricBlock):

name = OpName.RX

def __init__(self, target: int, parameter: Parameter | TParameter | ParamMap):
def __init__(
self,
target: int,
parameter: Parameter | TParameter | ParamMap,
noise: Noise | dict[str, Noise] | None = None,
):
# TODO: should we give them more meaningful names? like 'angle'?
self.parameters = (
parameter if isinstance(parameter, ParamMap) else ParamMap(parameter=parameter)
)
self.generator = X(target)
super().__init__((target,))
super().__init__((target,), noise)

@classmethod
def num_parameters(cls) -> int:
Expand All @@ -84,12 +94,17 @@ class RY(ParametricBlock):

name = OpName.RY

def __init__(self, target: int, parameter: Parameter | TParameter | ParamMap):
def __init__(
self,
target: int,
parameter: Parameter | TParameter | ParamMap,
noise: Noise | dict[str, Noise] | None = None,
):
self.parameters = (
parameter if isinstance(parameter, ParamMap) else ParamMap(parameter=parameter)
)
self.generator = Y(target)
super().__init__((target,))
super().__init__((target,), noise)

@classmethod
def num_parameters(cls) -> int:
Expand All @@ -111,12 +126,17 @@ class RZ(ParametricBlock):

name = OpName.RZ

def __init__(self, target: int, parameter: Parameter | TParameter | ParamMap):
def __init__(
self,
target: int,
parameter: Parameter | TParameter | ParamMap,
noise: Noise | dict[str, Noise] | None = None,
):
self.parameters = (
parameter if isinstance(parameter, ParamMap) else ParamMap(parameter=parameter)
)
self.generator = Z(target)
super().__init__((target,))
super().__init__((target,), noise)

@classmethod
def num_parameters(cls) -> int:
Expand Down Expand Up @@ -147,10 +167,11 @@ def __init__(
phi: Parameter | TParameter,
theta: Parameter | TParameter,
omega: Parameter | TParameter,
noise: Noise | dict[str, Noise] | None = None,
):
self.parameters = ParamMap(phi=phi, theta=theta, omega=omega)
self.generator = chain(Z(target), Y(target), Z(target))
super().__init__((target,))
super().__init__((target,), noise)

@classmethod
def num_parameters(cls) -> int:
Expand All @@ -177,4 +198,4 @@ def digital_decomposition(self) -> AbstractBlock:
RZ(self.qubit_support[0], self.parameters.phi),
RY(self.qubit_support[0], self.parameters.theta),
RZ(self.qubit_support[0], self.parameters.omega),
)
) # ? Add noise here ?
Loading

0 comments on commit 23a073e

Please sign in to comment.