Skip to content

Commit

Permalink
added data saving
Browse files Browse the repository at this point in the history
  • Loading branch information
jordivallsq committed Nov 15, 2024
1 parent 21aeb51 commit dd1a7fc
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 43 deletions.
6 changes: 5 additions & 1 deletion src/qililab/qprogram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,15 @@
from .calibration import Calibration
from .crosstalk_matrix import CrosstalkMatrix, FluxVector
from .experiment import Experiment
from .tracker import Tracker
from .qblox_compiler import QbloxCompilationOutput, QbloxCompiler
from .qprogram import QProgram
from .quantum_machines_compiler import QuantumMachinesCompilationOutput, QuantumMachinesCompiler
from .tracker import Tracker
from .tracker_writer import BlockMetadata, TrackerMetadata, TrackerWriter
from .variable import Domain

__all__ = [
"BlockMetadata",
"Calibration",
"CrosstalkMatrix",
"Domain",
Expand All @@ -65,4 +67,6 @@
"QuantumMachinesCompilationOutput",
"QuantumMachinesCompiler",
"Tracker",
"TrackerMetadata",
"TrackerWriter",
]
114 changes: 88 additions & 26 deletions src/qililab/qprogram/tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import threading
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from typing import Callable

from qililab.qprogram.experiment import Experiment

Check failure on line 19 in src/qililab/qprogram/tracker.py

View workflow job for this annotation

GitHub Actions / code-quality

Ruff (TCH001)

src/qililab/qprogram/tracker.py:19:41: TCH001 Move application import `qililab.qprogram.experiment.Experiment` into a type-checking block
from qililab.qprogram.tracker_writer import BlockMetadata, TrackerMetadata, TrackerWriter
from qililab.result.experiment_results import ExperimentResults

Check failure on line 21 in src/qililab/qprogram/tracker.py

View workflow job for this annotation

GitHub Actions / code-quality

Ruff (F401)

src/qililab/qprogram/tracker.py:21:47: F401 `qililab.result.experiment_results.ExperimentResults` imported but unused
from qililab.result.experiment_results_writer import VariableMetadata
from qililab.yaml import yaml


Expand Down Expand Up @@ -44,6 +49,20 @@ def __init__(
self.total_data: dict[str, list] = {}
self.experiment_dims: dict[str, list] = {}

def _measure_execution_time(self, execution_completed: threading.Event):
"""Measures the execution time while waiting for the experiment to finish."""
# Start measuring execution time
start_time = perf_counter()

Check failure on line 55 in src/qililab/qprogram/tracker.py

View workflow job for this annotation

GitHub Actions / code-quality

Ruff (F821)

src/qililab/qprogram/tracker.py:55:22: F821 Undefined name `perf_counter`

# Wait for the experiment to finish
execution_completed.wait()

# Stop measuring execution time
end_time = perf_counter()

Check failure on line 61 in src/qililab/qprogram/tracker.py

View workflow job for this annotation

GitHub Actions / code-quality

Ruff (F821)

src/qililab/qprogram/tracker.py:61:20: F821 Undefined name `perf_counter`

# Return the execution time
return end_time - start_time

def build_measure_block(
self,
alias: str,
Expand All @@ -64,7 +83,7 @@ def build_measure_block(

def run_tracker(
self,
parameter_alias:str,
parameter_alias: str,
set_parameter: Callable,
values: list,
initial_guess: float,
Expand All @@ -78,29 +97,72 @@ def run_tracker(
values (list): values for the parameter
guess (float): first starting point for the window
"""

# if self.alias_list empty:
# raise Error
guess = initial_guess

for value in values:
# Set the parameter
set_parameter(value)

for operation in self.alias_list:
# Update the window
window, predicted_guess = self.update_window_dict[operation](guess)
self.windows[operation].append(window)
self.guessed_path[operation].append(predicted_guess)

# Do the experiment
data, dims = self.measure_dict[operation](window, predicted_guess, tracker_path)
self.total_data[operation].append(data)
self.experiment_dims[operation].append(dims)

# Measure the point of interest
guess = self.find_relevant_point_dict[operation](data, window)
self.real_path[operation].append(guess)
return


# ASK VYRON HOW TO SAVE THE REST OF THE DATA INSIDE EXPERIMENTS, MODIFY EXPERIMENTS IF NECESSARY
# ADD MODIFYIABLE VARIABLES INSIDE EXPERIMENTS TO SAVE THEM LATER IN THE SAME PLACE, VARIABLES THAT CHANGE INSIDE THE LOOP
executed_at = datetime.now()

experiments = BlockMetadata(
alias=self.alias,
real_path=self.real_path,
guessed_path=self.guessed_path,
windows=self.windows,
total_data=self.total_data,
experiment_dims=self.experiment_dims,
)
metadata = TrackerMetadata(
executed_at=executed_at,
execution_time=0.0,
values=VariableMetadata(label=parameter_alias, values=values),
experiments=experiments,
)

tracker_writer = TrackerWriter(tracker_path, metadata)

execution_completed = threading.Event()

with ThreadPoolExecutor() as executor:
# Start the _measure_execution_time in a separate thread
execution_time_future = executor.submit(self._measure_execution_time, execution_completed)

for value in values:
# Set the parameter
set_parameter(value)

for operation in self.alias_list:
# Update the window
window, predicted_guess = self.update_window_dict[operation](guess)
self.windows[operation].append(window)
self.guessed_path[operation].append(predicted_guess)

# Do the experiment
data, dims = self.measure_dict[operation](
window, predicted_guess, tracker_writer.experiment_path[operation][value]
)
self.total_data[operation].append(data)
self.experiment_dims[operation].append(dims)

# Measure the point of interest
guess = self.find_relevant_point_dict[operation](data, window)
self.real_path[operation].append(guess)

tracker_writer.set(
alias=operation,
window=self.windows[operation],
guess_path=self.guessed_path[operation],
data=self.total_data[operation],
real_path=self.real_path[operation],
)

# Signal that the execution has completed
execution_completed.set()
# Retrieve the execution time from the Future
execution_time = execution_time_future.result()
# Now write the execution time to the results writer
with tracker_writer:
tracker_writer.execution_time = execution_time

return tracker_writer.path


# ADD MODIFIABLE VARIABLES INSIDE EXPERIMENTS TO SAVE THEM LATER IN THE SAME PLACE, VARIABLES THAT CHANGE INSIDE THE LOOP
45 changes: 31 additions & 14 deletions src/qililab/qprogram/tracker_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# mypy: disable-error-code="attr-defined"
import os
from datetime import datetime
from typing import TypedDict

import os
import h5py
import numpy as np

Check failure on line 20 in src/qililab/qprogram/tracker_writer.py

View workflow job for this annotation

GitHub Actions / code-quality

Ruff (F401)

src/qililab/qprogram/tracker_writer.py:20:17: F401 `numpy` imported but unused

from qililab.result.experiment_results import ExperimentResults
from qililab.result.experiment_results_writer import (
ExperimentMetadata,
MeasurementMetadata,
VariableMetadata,
)
from qililab.result.experiment_results_writer import ExperimentMetadata, MeasurementMetadata, VariableMetadata

Check failure on line 23 in src/qililab/qprogram/tracker_writer.py

View workflow job for this annotation

GitHub Actions / code-quality

Ruff (F401)

src/qililab/qprogram/tracker_writer.py:23:54: F401 `qililab.result.experiment_results_writer.ExperimentMetadata` imported but unused

Check failure on line 23 in src/qililab/qprogram/tracker_writer.py

View workflow job for this annotation

GitHub Actions / code-quality

Ruff (F401)

src/qililab/qprogram/tracker_writer.py:23:74: F401 `qililab.result.experiment_results_writer.MeasurementMetadata` imported but unused


class BlockMetadata(TypedDict):
"""Metadata for a measurement in the experiment.
Expand All @@ -43,6 +40,7 @@ class BlockMetadata(TypedDict):
total_data: dict[str, list]
experiment_dims: dict[str, list]


class TrackerMetadata(TypedDict):
"""Metadata for an experiment.
Expand All @@ -54,10 +52,10 @@ class TrackerMetadata(TypedDict):
executed_at: datetime
execution_time: float
values: VariableMetadata
experiments: dict[str, BlockMetadata]
experiments: BlockMetadata


class TrackerWriter(TrackerResults):
class TrackerWriter:
"""
Allows for real-time saving of results from an experiment using the provided metadata information.
Expand All @@ -82,13 +80,15 @@ def __enter__(self):
Returns:
ExperimentResultsWriter: The ExperimentResultsWriter instance.
"""
self.path = self._create_results_path(self._metadata["executed_at"])
self._create_experiment_path()
self._tracker_file = h5py.File(f"{self.path}/tracker_data.h5", mode="w")
self._create_results_access()
self._create_file()

return self

def __setitem__(self, alias: tuple, window: float, guess_path, data, real_path):
def __setitem__(self, alias, window, guess_path, data, real_path):
"""Sets an item in the results dataset.
Args:
Expand All @@ -101,6 +101,25 @@ def __setitem__(self, alias: tuple, window: float, guess_path, data, real_path):
self.data[alias] = data
self.real[alias] = real_path

def _create_results_path(self, executed_at: datetime):
# Format date and time for directory names
date = executed_at.strftime("%Y%m%d")
timestamp = executed_at.strftime("%H%M%S")

# Format the path based on the path's format
time_path = f"/{date}/{timestamp}"

# Construct the full path
path_file = os.path.join(self.path, time_path)

# Ensure it is an absolute path
path_file = os.path.abspath(path_file)

# Create the directories if they don't exist
os.makedirs(os.path.dirname(path_file), exist_ok=True)

return path_file

# pylint: disable=too-many-locals
def _create_file(self):
"""Creates the HDF5 file structure and registers loops as dimension scales.
Expand Down Expand Up @@ -145,12 +164,10 @@ def _create_file(self):
def _create_experiment_path(self):
for alias in self._metadata["experiments"]["alias"]:
self.experiment_path[alias] = {}
if not os.path.exists(f"{self.path}/{alias}"):
os.makedirs(f"{self.path}/{alias}")
os.makedirs(f"{self.path}/{alias}", exist_ok=True)
for value in self._metadata["experiments"]["values"]:
if not os.path.exists(f"{self.path}/{alias}/{value["label"]}_{value["values"]}"):
os.makedirs(f"{self.path}/{alias}/{value["label"]}_{value["values"]}")
self.experiment_path[alias][value["values"]]=f"{self.path}/{alias}/{value["label"]}_{value["values"]}"
os.makedirs(f"{self.path}/{alias}/{value['label']}_{value['values']}", exist_ok=True)
self.experiment_path[alias][value["values"]] = f"{self.path}/{alias}/{value['label']}_{value['values']}"

def _create_results_access(self):
"""Sets up internal data structures to allow for real-time data writing to the HDF5 file."""
Expand Down
4 changes: 2 additions & 2 deletions src/qililab/result/experiment_results_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def _create_results_file(self):
# Attach the extra dimension (usually for I/Q) to the results dataset
results_ds.dims[len(qprogram_data["dims"]) + len(measurement_data["dims"])].label = "I/Q"

def _create_resuts_access(self):
def _create_results_access(self):
"""Sets up internal data structures to allow for real-time data writing to the HDF5 file."""
if "qprograms" in self._metadata:
for qprogram_name, qprogram_data in self._metadata["qprograms"].items():
Expand All @@ -185,7 +185,7 @@ def __enter__(self):
"""
self._file = h5py.File(self.path, mode="w")
self._create_results_file()
self._create_resuts_access()
self._create_results_access()

return self

Expand Down

0 comments on commit dd1a7fc

Please sign in to comment.