Skip to content

Commit 1625237

Browse files
authored
Merge pull request #61 from tiqi-group/pycrystal
Support Pycrystal v2.1.8, Realtime ScanParameter
2 parents 5ce53da + db0e9fb commit 1625237

File tree

9 files changed

+271
-140
lines changed

9 files changed

+271
-140
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
emulator/
2+
output/
23
poetry.lock
34
.python-version
45

src/icon/server/__main__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,18 @@ def start_server() -> None:
5959
scheduler.start()
6060

6161
number_of_pre_processing_workers = get_config().server.pre_processing.workers
62-
pre_processing_update_queues: list[multiprocessing.Queue[UpdateQueue]] = []
62+
pre_processing_update_queues: list[multiprocessing.Queue[UpdateQueue]] = [
63+
multiprocessing.Queue() for _ in range(number_of_pre_processing_workers)
64+
]
6365

64-
for i in range(number_of_pre_processing_workers):
65-
pre_processing_update_queues.append(multiprocessing.Queue())
66-
pre_processing_worker = PreProcessingWorker(
66+
for i, queue in enumerate(pre_processing_update_queues):
67+
PreProcessingWorker(
6768
worker_number=i,
6869
hardware_processing_queue=icon.server.shared_resource_manager.hardware_processing_queue,
6970
pre_processing_queue=icon.server.shared_resource_manager.pre_processing_queue,
70-
update_queue=pre_processing_update_queues[i],
71+
update_queue=queue,
7172
manager=icon.server.shared_resource_manager.manager,
72-
)
73-
pre_processing_worker.start()
73+
).start()
7474

7575
hardware_processing_worker = HardwareProcessingWorker(
7676
hardware_processing_queue=icon.server.shared_resource_manager.hardware_processing_queue,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Add realtime column to ScanParameter model
2+
3+
Revision ID: fc9af856df20
4+
Revises: 2bc7556020cf
5+
Create Date: 2025-09-18 13:13:16.093552
6+
7+
"""
8+
9+
from collections.abc import Sequence
10+
11+
import sqlalchemy as sa
12+
from alembic import op
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = "fc9af856df20"
16+
down_revision: str | None = "2bc7556020cf"
17+
branch_labels: str | Sequence[str] | None = None
18+
depends_on: str | Sequence[str] | None = None
19+
20+
21+
def upgrade() -> None:
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
with op.batch_alter_table("scan_parameters", schema=None) as batch_op:
24+
batch_op.add_column(
25+
sa.Column("realtime", sa.Boolean(), nullable=False, server_default="False")
26+
)
27+
# ### end Alembic commands ###
28+
29+
30+
def downgrade() -> None:
31+
# ### commands auto generated by Alembic - please adjust! ###
32+
with op.batch_alter_table("scan_parameters", schema=None) as batch_op:
33+
batch_op.drop_column("realtime")
34+
35+
# ### end Alembic commands ###

src/icon/server/data_access/models/sqlite/scan_parameter.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import TYPE_CHECKING, Any
33

44
import sqlalchemy
5+
import sqlalchemy.event
56
import sqlalchemy.orm
67

78
from icon.server.data_access.db_context.influxdb_v1 import DatabaseValueType
@@ -79,6 +80,9 @@ class ScanParameter(Base):
7980
back_populates="scan_parameters", lazy="joined"
8081
)
8182
"""Relationship to the device associated with this parameter."""
83+
realtime: sqlalchemy.orm.Mapped[bool] = sqlalchemy.orm.mapped_column(
84+
default=False, nullable=False
85+
)
8286

8387
def __repr__(self) -> str:
8488
return f"<Parameter '{self.unique_id()}'>"
@@ -96,3 +100,20 @@ def unique_id(self) -> str:
96100
if self.device is not None
97101
else self.variable_id
98102
)
103+
104+
105+
@sqlalchemy.event.listens_for(ScanParameter, "before_insert")
106+
def receive_before_insert(
107+
mapper: sqlalchemy.orm.Mapper[ScanParameter],
108+
connection: sqlalchemy.engine.Connection,
109+
target: ScanParameter,
110+
) -> None:
111+
if target.realtime and target.variable_id != "Real Time":
112+
raise ValueError(
113+
f"Cannot set 'continuous' on {ScanParameter} with id {target.variable_id!r}"
114+
" != 'Real Time'"
115+
)
116+
117+
118+
def contains_realtime_parameter(params: list[ScanParameter]) -> bool:
119+
return any(sp.realtime for sp in params)

src/icon/server/data_access/repositories/experiment_data_repository.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
from icon.config.config import get_config
1313
from icon.server.data_access.db_context.influxdb_v1 import DatabaseValueType
14-
from icon.server.data_access.models.sqlite.scan_parameter import ScanParameter
14+
from icon.server.data_access.models.sqlite.scan_parameter import (
15+
ScanParameter,
16+
contains_realtime_parameter,
17+
)
1518
from icon.server.data_access.repositories.job_repository import JobRepository
1619
from icon.server.data_access.repositories.job_run_repository import JobRunRepository
1720
from icon.server.utils.h5py import get_hdf5_dtype, get_result_channels_dataset
@@ -362,6 +365,8 @@ def update_metadata_by_job_id( # noqa: PLR0913
362365
h5file.attrs["experiment_id"] = job.experiment_source.experiment_id
363366
h5file.attrs["job_id"] = job_id
364367
h5file.attrs["repetitions"] = repetitions
368+
h5file.attrs["realtime_scan"] = contains_realtime_parameter(parameters)
369+
365370
if local_parameter_timestamp is not None:
366371
h5file.attrs["local_parameter_timestamp"] = local_parameter_timestamp
367372

@@ -582,6 +587,7 @@ def get_experiment_data_by_job_id(
582587
"vector_channels": {},
583588
"scan_parameters": {},
584589
"json_sequences": [],
590+
"realtime_scan": False,
585591
}
586592

587593
filename = get_filename_by_job_id(job_id)
@@ -596,6 +602,9 @@ def get_experiment_data_by_job_id(
596602
f"{ExperimentDataRepository.LOCK_EXTENSION}"
597603
)
598604
with FileLock(lock_path), h5py.File(file, "r") as h5file:
605+
data["realtime_scan"] = h5file.attrs["realtime_scan"]
606+
# Parse JSON strings in relevant columns back into Python objects
607+
599608
scan_parameters: npt.NDArray = h5file["scan_parameters"][:] # type: ignore
600609
data["scan_parameters"] = {
601610
param: {

src/icon/server/data_access/repositories/pycrystal_library_repository.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ async def generate_json_sequence(
124124
exp_module_name: str,
125125
exp_instance_name: str,
126126
parameter_dict: dict[str, DatabaseValueType],
127+
n_shots: int,
127128
) -> str:
128129
"""Generate a JSON sequence for an experiment.
129130
@@ -140,6 +141,7 @@ async def generate_json_sequence(
140141
"key_val_dict": parameter_dict,
141142
"module_name": exp_module_name,
142143
"exp_instance_name": exp_instance_name,
144+
"n_shots": n_shots,
143145
}
144146

145147
code = PycrystalLibraryRepository._get_code(

src/icon/server/data_access/templates/generate_pycrystal_sequence.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22

33
import pycrystal.database.local_cache
4+
import pycrystal.experiment
45
import pycrystal.parameters
56
from pycrystal.utils.helpers import get_config_from_module_name
67

@@ -9,6 +10,7 @@
910
logging.getLogger("pycrystal").setLevel(log_level)
1011
logging.getLogger("ionpulse_sequence_generator").setLevel(log_level)
1112
KEY_VAL_DICT = {key_val_dict}
13+
N_SHOTS = {n_shots}
1214

1315
pycrystal.parameters.Parameter.db = pycrystal.database.local_cache.LocalCache(
1416
key_val_dict=KEY_VAL_DICT,
@@ -32,6 +34,7 @@
3234

3335
experiment_library.hardware_description.hardware.hardware.init()
3436
exp_instance._init()
37+
pycrystal.experiment.Experiment.shots = N_SHOTS
3538
exp_instance._initialize_scan(debug_level=log_level)
3639
sequence = exp_instance.pulse_sequence()
3740
sequence_json = sequence.get_json_string(exp_instance._sequence_header)

src/icon/server/hardware_processing/hardware_controller.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ def _update_zedboard_sequence(self, *, sequence: str) -> None:
4040
if self._zedboard is not None:
4141
self._zedboard.sequence_JSON_parser.Sequence_JSON = sequence # type: ignore
4242

43-
def _update_number_of_shots(self, *, number_of_shots: int) -> None:
44-
if self._zedboard is not None:
45-
self._zedboard.sequence_JSON_parser.Shots = number_of_shots # type: ignore
46-
4743
def run(self, *, sequence: str, number_of_shots: int) -> ResultDict:
4844
if not self.connected:
4945
self.connect()
@@ -52,7 +48,6 @@ def run(self, *, sequence: str, number_of_shots: int) -> ResultDict:
5248
raise RuntimeError("Could not connect to the Zedboard")
5349

5450
self._update_zedboard_sequence(sequence=sequence)
55-
self._update_number_of_shots(number_of_shots=number_of_shots)
5651
self._zedboard.sequence_JSON_parser.Parse_JSON_Header() # type: ignore
5752
results: tiqi_zedboard.zedboard.Result = self._zedboard.sequence_JSON_parser() # type: ignore
5853

0 commit comments

Comments
 (0)