from __future__ import annotations
import json
import logging
from dataclasses import asdict, dataclass, field
from typing import Any, Dict, List, Optional, Union
import qcodes as qc
from qcodes import validators as vals
import nanotune as nt
logger = logging.getLogger(__name__)
[docs]@dataclass()
class TuningResult:
"""Container to hold tuning result data
Parameters:
stage (str): the tuning stage, e.g. 'chargediagram'. Can be any string,
not necessarily a nt.tuningstage.
success (bool): whether the stage terminated successfully
guids (list): List of GUIDs of the measurements taken during the
stage
termination_reasons (list): List of reasons why the stage did not
succeed. Example: 'no current'.
data_ids (list): List of qc.run_id of the the measurements taken during
the stage. Optional, for convenience.
db_name (str): The database where to find data_ids, optional.
db_folder (str): The database folder where to find db_name and thus
data_ids. Optional
comment (str): Optional string if there is anything to say about the
tuning.
timestamp (str): time stamp when the stage finished.
"""
stage: str
success: bool
guids: List[str] = field(default_factory=list)
ml_result: Dict[str, Any] = field(default_factory=dict)
data_ids: List[str] = field(default_factory=list)
db_name: str = ""
db_folder: str = ""
termination_reasons: List[str] = field(default_factory=list)
comment: str = ""
timestamp: str = ""
status: Dict[str, Any] = field(default_factory=dict)
[docs] def to_dict(self) -> Dict[str, Any]:
"""
Returns:
dict: TuningResult instance as dict
"""
return asdict(self)
[docs] def to_json(self) -> str:
"""
Returns:
str: serialized version TuningResult instance
"""
return json.dumps(self.to_dict())
[docs]class MeasurementHistory:
"""Container to save tuning results. Each device should have its own
instance. Results are saved in a dictionary mapping string indentifiers to
TuningResult instances.
Parameters:
device_name (str): Name of device tuned.
tuningresults (dict): Dictionary mapping string identifiers to
instances of TuningResults.
"""
def __init__(
self,
device_name: str,
) -> None:
"""
Args:
device_name (str): Name of device tuned.
"""
self.device_name = device_name
self._tuningresults: Dict[str, TuningResult] = {}
self.last_added: Optional[TuningResult] = None
@property
def tuningresults(self):
"""tuningresults property getter """
return self._tuningresults
[docs] def to_dict(self) -> Dict[str, Any]:
"""Merges all MeasurementHistory attributed into a dict
Returns:
dict: all MeasurementHistory data in a dict
"""
self_dict = {k: v.to_dict() for (k, v) in self.tuningresults.items()}
self_dict["device_name"] = self.device_name
return self_dict
[docs] def to_json(self) -> str:
"""Serializes MeasurementHistory instance to a JSON formatted sting.
Returns:
str: JSON formatted serial version of MeasurementHistory instance
"""
return json.dumps(self.to_dict())
[docs] def add_result(
self,
tuningresult: TuningResult,
identifier: Optional[str] = None,
) -> None:
"""Adds TuningResult instance to self.tuningresults dictionary.
Args:
tuningresult (TuningResult):
identifier (Optional[str]): default if not supplied is
tuningresult.stage
"""
if identifier is None:
self._add_tuningresult(tuningresult)
else:
self._add_tuningresult({identifier: tuningresult})
self.last_added = tuningresult
[docs] def update(self, other_measurement_history: MeasurementHistory) -> None:
""" """
assert other_measurement_history.device_name == self.device_name
# not using dict.merge in case of key duplicates - which are taken
# care of in add_result
for key, result in other_measurement_history.tuningresults.items():
self.add_result(result, key)
def _add_tuningresult(
self,
new_tuningresult: Union[TuningResult, Dict[str, TuningResult]],
) -> None:
"""tuningresults property setter. Checks if keys in new_tuningresult
already exist in self.tuningresults.
If a bare TuningResult instance is given, the TuningResult.stage serves
as identifier.
Args:
new_tuningresult (Union[TuningResult, Dict[str, TuningResult]]):
Returns:
"""
if isinstance(new_tuningresult, TuningResult):
new_tuningresult = {new_tuningresult.stage: new_tuningresult}
common_keys = list(
set(new_tuningresult.keys()) & set(self._tuningresults.keys())
)
for key in common_keys:
if new_tuningresult[key] != self._tuningresults[key]:
try:
append_idx = new_tuningresult[key].guids[-1]
except IndexError:
append_idx = new_tuningresult[key].data_ids[-1]
new_key = key + f"_{append_idx}"
new_tuningresult[new_key] = new_tuningresult[key]
del new_tuningresult[key]
self._tuningresults.update(new_tuningresult)