Skip to content

Commit

Permalink
Merge pull request #582 from qiboteam/kernels
Browse files Browse the repository at this point in the history
kernels
  • Loading branch information
andrea-pasquale authored Feb 2, 2024
2 parents 2f7d459 + b366c54 commit 438bc57
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 82 deletions.
159 changes: 89 additions & 70 deletions src/qibocal/auto/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from copy import deepcopy
from dataclasses import asdict, dataclass
from functools import wraps
from pathlib import Path
from typing import Callable, Generic, NewType, Optional, TypeVar, Union

import numpy as np
Expand All @@ -24,11 +25,9 @@
QubitsPairs = dict[tuple[QubitId, QubitId], QubitPair]


DATAFILE = "data.npz"
"""Name of the file where data acquired (arrays) by calibration are dumped."""
JSONFILE = "conf.json"
"""Name of the file where data acquired (global configuration) by calibration are dumped."""
RESULTSFILE = "results.json"
DATAFILE = "data"
"""Name of the file where data are dumped."""
RESULTSFILE = "results"
"""Name of the file where results are dumped."""


Expand Down Expand Up @@ -98,68 +97,96 @@ def load(cls, input_parameters):
return instantiated_class


class Data:
"""Data resulting from acquisition routine."""
class AbstractData:
"""Abstract data class."""

data: dict[Union[tuple[QubitId, int], QubitId], npt.NDArray]
"""Data object to store arrays"""

@property
def qubits(self):
"""Access qubits from data structure."""
if set(map(type, self.data)) == {tuple}:
return list({q[0] for q in self.data})
return [q for q in self.data]

@property
def pairs(self):
"""Access qubit pairs ordered alphanumerically from data structure."""
return list({tuple(sorted(q[:2])) for q in self.data})
def __init__(
self, data: dict[Union[tuple[QubitId, int], QubitId], npt.NDArray] = None
):
self.data = data if data is not None else {}

def __getitem__(self, qubit: Union[QubitId, tuple[QubitId, int]]):
"""Access data attribute member."""
return self.data[qubit]

@property
def global_params(self) -> dict:
def params(self) -> dict:
"""Convert non-arrays attributes into dict."""
global_dict = asdict(self)
global_dict.pop("data")
if hasattr(self, "data"):
global_dict.pop("data")
return global_dict

def save(self, path):
"""Store results."""
self._to_json(path)
self._to_npz(path)
def save(self, path: Path, filename: str):
"""Dump class to file."""
self._to_json(path, filename)
self._to_npz(path, filename)

def _to_npz(self, path):
def _to_npz(self, path: Path, filename: str):
"""Helper function to use np.savez while converting keys into strings."""
np.savez(path / DATAFILE, **{json.dumps(i): self.data[i] for i in self.data})
if hasattr(self, "data"):
np.savez(
path / f"{filename}.npz",
**{json.dumps(i): self.data[i] for i in self.data},
)

def _to_json(self, path):
"""Helper function to dump to json in JSONFILE path."""
if self.global_params:
(path / JSONFILE).write_text(
json.dumps(serialize(self.global_params), indent=4)
def _to_json(self, path: Path, filename: str):
"""Helper function to dump to json."""
if self.params:
(path / f"{filename}.json").write_text(
json.dumps(serialize(self.params), indent=4)
)

@classmethod
def load(cls, path):
with open(path / DATAFILE) as f:
raw_data_dict = dict(np.load(path / DATAFILE))
def load(cls, path: Path, filename: str):
"""Generic load method."""
data_dict = cls.load_data(path, filename)
params = cls.load_params(path, filename)
if data_dict is not None:
if params is not None:
return cls(data=data_dict, **params)
else:
return cls(data=data_dict)
elif params is not None:
return cls(**params)

@staticmethod
def load_data(path: Path, filename: str):
"""Load data stored in a npz file."""
file = path / f"{filename}.npz"
if file.is_file():
raw_data_dict = dict(np.load(file))
data_dict = {}

for data_key, array in raw_data_dict.items():
data_dict[load(data_key)] = np.rec.array(array)
if (path / JSONFILE).is_file():
params = json.loads((path / JSONFILE).read_text())

return data_dict

@staticmethod
def load_params(path: Path, filename: str):
"""Load parameters stored in a json file."""
file = path / f"{filename}.json"
if file.is_file():
params = json.loads(file.read_text())
params = deserialize(params)
obj = cls(data=data_dict, **params)
else:
obj = cls(data=data_dict)
return params


return obj
class Data(AbstractData):
"""Data resulting from acquisition routine."""

@property
def qubits(self):
"""Access qubits from data structure."""
if set(map(type, self.data)) == {tuple}:
return list({q[0] for q in self.data})
return [q for q in self.data]

@property
def pairs(self):
"""Access qubit pairs ordered alphanumerically from data structure."""
return list({tuple(sorted(q[:2])) for q in self.data})

def register_qubit(self, dtype, data_keys, data_dict):
"""Store output for single qubit.
Expand All @@ -180,38 +207,30 @@ def register_qubit(self, dtype, data_keys, data_dict):
else:
self.data[data_keys] = np.rec.array(ar)

def save(self, path: Path):
"""Store data to file."""
super()._to_json(path, DATAFILE)
super()._to_npz(path, DATAFILE)

@dataclass
class Results:
"""Generic runcard update.
As for the case of :class:`Parameters` the explicit structure is only useful
to fill the specific update, but in this case there should be a generic way
Each field might be annotated with an ``update`` metadata field, in order
to mark them for later use in the runcard::
@dataclass
class Cmd1Res(Results):
res: str = field(metadata=dict(update="myres"))
num: int
.. todo::
Implement them as ``source: dest``, where ``source`` will be the field
name in the class, corresponding to the same field in ``Result``
@classmethod
def load(cls, path: Path):
"""Load data and parameters."""
return super().load(path, filename=DATAFILE)

"""

def save(self, path):
"""Store results to json."""
(path / RESULTSFILE).write_text(json.dumps(serialize(asdict(self))))
@dataclass
class Results(AbstractData):
"""Generic runcard update."""

@classmethod
def load(cls, path):
params = json.loads((path / RESULTSFILE).read_text())
params = deserialize(params)
return cls(**params)
def load(cls, path: Path):
"""Load results."""
return super().load(path, filename=RESULTSFILE)

def save(self, path: Path):
"""Store results to file."""
super()._to_json(path, RESULTSFILE)
super()._to_npz(path, RESULTSFILE)


# Internal types, in particular `_ParametersT` is used to address function
Expand Down
8 changes: 0 additions & 8 deletions src/qibocal/auto/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
)
from .mode import ExecutionMode
from .operation import (
DATAFILE,
RESULTSFILE,
Data,
DummyPars,
Qubits,
Expand Down Expand Up @@ -209,9 +207,6 @@ def datapath(self):
@property
def results(self):
"""Access task's results."""
if not (self.datapath / RESULTSFILE).is_file():
return None

if self._results is None:
Results = self.task.operation.results_type
self._results = Results.load(self.datapath)
Expand All @@ -226,9 +221,6 @@ def results(self, results: Results):
@property
def data(self):
"""Access task's data."""
# FIXME: temporary fix for coverage
if not (self.datapath / DATAFILE).is_file(): # pragma: no cover
return None
if self._data is None:
Data = self.task.operation.data_type
self._data = Data.load(self.datapath)
Expand Down
4 changes: 4 additions & 0 deletions src/qibocal/protocols/characterization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
from .resonator_punchout_attenuation import resonator_punchout_attenuation
from .resonator_spectroscopy import resonator_spectroscopy
from .resonator_spectroscopy_attenuation import resonator_spectroscopy_attenuation
from .signal_experiments.calibrate_state_discrimination import (
calibrate_state_discrimination,
)
from .signal_experiments.time_of_flight_readout import time_of_flight_readout
from .two_qubit_interaction import chevron, chsh_circuits, chsh_pulses, cz_virtualz

Expand Down Expand Up @@ -114,3 +117,4 @@ class Operation(Enum):
dispersive_shift_qutrit = dispersive_shift_qutrit
coupler_resonator_spectroscopy = coupler_resonator_spectroscopy
coupler_qubit_spectroscopy = coupler_qubit_spectroscopy
calibrate_state_discrimination = calibrate_state_discrimination
4 changes: 2 additions & 2 deletions src/qibocal/protocols/characterization/dispersive_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ def state_one(self):
("phase", np.float64),
]
)
"""Custom dtype for rabi amplitude."""
"""Custom dtype for dispersive shift."""


@dataclass
class DispersiveShiftData(Data):
"""Dipsersive shift acquisition outputs."""
"""Dispersive shift acquisition outputs."""

resonator_type: str
"""Resonator type."""
Expand Down
Loading

0 comments on commit 438bc57

Please sign in to comment.