q1simulator 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- q1simulator/Qblox_QCM_pulse_response.hdf5 +0 -0
- q1simulator/__init__.py +8 -0
- q1simulator/analogue_filter.py +56 -0
- q1simulator/channel_data.py +78 -0
- q1simulator/cluster.py +216 -0
- q1simulator/gui/__init__.py +0 -0
- q1simulator/gui/program_browser.py +114 -0
- q1simulator/gui/program_details.py +138 -0
- q1simulator/gui/program_plotter.py +163 -0
- q1simulator/gui/qt_utils.py +159 -0
- q1simulator/q1core.py +360 -0
- q1simulator/q1parser.py +247 -0
- q1simulator/q1plotter.py +232 -0
- q1simulator/q1sequencer.py +545 -0
- q1simulator/q1simulator.py +459 -0
- q1simulator/q1viewer.py +99 -0
- q1simulator/qblox_version.py +16 -0
- q1simulator/rt_renderer.py +676 -0
- q1simulator/trigger_sorting.py +71 -0
- q1simulator/triggers.py +44 -0
- q1simulator-1.0.1.dist-info/METADATA +316 -0
- q1simulator-1.0.1.dist-info/RECORD +25 -0
- q1simulator-1.0.1.dist-info/WHEEL +5 -0
- q1simulator-1.0.1.dist-info/licenses/LICENSE +21 -0
- q1simulator-1.0.1.dist-info/top_level.txt +1 -0
|
Binary file
|
q1simulator/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import xarray as xr
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AnalogueFilter:
|
|
8
|
+
warned = False
|
|
9
|
+
|
|
10
|
+
def __init__(self, model_name: str, output_frequency: float = 4e9):
|
|
11
|
+
self.output_frequency = output_frequency
|
|
12
|
+
|
|
13
|
+
if not AnalogueFilter.warned:
|
|
14
|
+
print(f"WARNING: Analogue output of simulated {model_name} can differ "
|
|
15
|
+
"significantly from real hardware!")
|
|
16
|
+
AnalogueFilter.warned = True
|
|
17
|
+
|
|
18
|
+
pulse_response_dir = os.path.dirname(__file__)
|
|
19
|
+
if model_name == "QCM":
|
|
20
|
+
fname = pulse_response_dir + "/Qblox_QCM_pulse_response.hdf5"
|
|
21
|
+
amplitude_correction = 1.003
|
|
22
|
+
self.min_vstep = 5.0/2**16 # 0.076 mV
|
|
23
|
+
else:
|
|
24
|
+
raise Exception(f"Uknown model_name {model_name}")
|
|
25
|
+
|
|
26
|
+
pulse_response = xr.open_dataset(fname, engine='h5netcdf')
|
|
27
|
+
t_response = pulse_response.coords['t'].data
|
|
28
|
+
self.pulse_response = pulse_response['y'].data * amplitude_correction
|
|
29
|
+
sr = round(1/(t_response[1]-t_response[0]))
|
|
30
|
+
sub_sample = int(round(sr / (output_frequency*1e-9)))
|
|
31
|
+
self.sr = sr // sub_sample
|
|
32
|
+
self.pulse_response = self.pulse_response[::sub_sample]
|
|
33
|
+
|
|
34
|
+
self.n_before = round(-t_response[0] * self.sr)
|
|
35
|
+
self.n_after = len(self.pulse_response) - self.n_before - 1
|
|
36
|
+
|
|
37
|
+
def quantize_amplitude(self, wave):
|
|
38
|
+
return np.round(wave/self.min_vstep) * self.min_vstep
|
|
39
|
+
|
|
40
|
+
def get_awg_output(self, t, samples, analogue_shift: float | None = 0.0):
|
|
41
|
+
samples = self.quantize_amplitude(samples)
|
|
42
|
+
if t[1] - t[0] != 1:
|
|
43
|
+
raise Exception(f"Expecting input with 1 ns sample period. Got {t[1]-t[0]}")
|
|
44
|
+
t = np.linspace(t[0], t[-1]+1, len(t)*self.sr, endpoint=False)
|
|
45
|
+
d = np.zeros(len(samples)*self.sr)
|
|
46
|
+
d[::self.sr] = samples
|
|
47
|
+
|
|
48
|
+
d = np.convolve(d, self.pulse_response)
|
|
49
|
+
return t+analogue_shift, d[self.n_before: -self.n_after]
|
|
50
|
+
|
|
51
|
+
def apply_filter(self, samples):
|
|
52
|
+
"""Apply filter to samples without quantizing or shifting time."""
|
|
53
|
+
d = np.zeros(len(samples)*self.sr)
|
|
54
|
+
d[::self.sr] = samples
|
|
55
|
+
d = np.convolve(d, self.pulse_response)
|
|
56
|
+
return d[self.n_before: -self.n_after]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class SampledOutput:
|
|
10
|
+
t_min: int
|
|
11
|
+
t_max: int
|
|
12
|
+
sample_rate: int # GSa/s
|
|
13
|
+
data: NDArray
|
|
14
|
+
|
|
15
|
+
def get_time_data(self):
|
|
16
|
+
"""Time is [ns]"""
|
|
17
|
+
n = (self.t_max-self.t_min)*self.sample_rate
|
|
18
|
+
return np.linspace(self.t_min, self.t_max, n, endpoint=False)
|
|
19
|
+
|
|
20
|
+
def __add__(self, rhs: SampledOutput):
|
|
21
|
+
if self.t_min != rhs.t_min:
|
|
22
|
+
raise Exception(f"t_min not equal for sample output: {self.t_min} <> {rhs.t_min}")
|
|
23
|
+
if self.sample_rate != rhs.sample_rate:
|
|
24
|
+
raise Exception(f"sample rates not equal for sample output: {self.sample_rate} <> {rhs.sample_rate}")
|
|
25
|
+
|
|
26
|
+
t_max = max(self.t_max, rhs.t_max)
|
|
27
|
+
if self.t_max < t_max:
|
|
28
|
+
lhs_data = np.zeros(t_max * self.sample_rate)
|
|
29
|
+
lhs_data[:self.t_max * self.sample_rate] = self.data
|
|
30
|
+
else:
|
|
31
|
+
lhs_data = self.data
|
|
32
|
+
if rhs.t_max < t_max:
|
|
33
|
+
rhs_data = np.zeros(t_max * self.sample_rate)
|
|
34
|
+
rhs_data[:rhs.t_max * self.sample_rate] = rhs.data
|
|
35
|
+
else:
|
|
36
|
+
rhs_data = rhs.data
|
|
37
|
+
|
|
38
|
+
return SampledOutput(self.t_min, t_max, self.sample_rate, lhs_data+rhs_data)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class MarkerOutput:
|
|
43
|
+
t_min: int
|
|
44
|
+
t_max: int
|
|
45
|
+
points: list[tuple[int, int]]
|
|
46
|
+
|
|
47
|
+
def get_xy_lines(self):
|
|
48
|
+
line = np.array(self.points).T
|
|
49
|
+
return (line[0], line[1])
|
|
50
|
+
|
|
51
|
+
def to_samples(self):
|
|
52
|
+
n = self.t_max - self.t_min
|
|
53
|
+
data = np.zeros(n)
|
|
54
|
+
ip = 0
|
|
55
|
+
points = self.points
|
|
56
|
+
while points[ip][0] < self.t_min:
|
|
57
|
+
ip += 1
|
|
58
|
+
if int(points[ip][1]) == int(points[ip+1][1]):
|
|
59
|
+
# Skip point if level doesn't change.
|
|
60
|
+
# This can occur at start: [[0, 0], [0, 0], [0, 1], ...]
|
|
61
|
+
ip += 1
|
|
62
|
+
it = points[ip][0]
|
|
63
|
+
data[:it] = points[ip][1]
|
|
64
|
+
ip += 1
|
|
65
|
+
while ip+1 < len(points) and points[ip+1][0] <= self.t_max:
|
|
66
|
+
it1 = points[ip][0]
|
|
67
|
+
it2 = points[ip+1][0]
|
|
68
|
+
data[it1:it2] = points[ip][1]
|
|
69
|
+
ip += 2
|
|
70
|
+
return SampledOutput(self.t_min, self.t_max, 1, data)
|
|
71
|
+
|
|
72
|
+
def __add__(self, rhs: MarkerOutput | SampledOutput):
|
|
73
|
+
if isinstance(rhs, MarkerOutput):
|
|
74
|
+
rhs = rhs.to_samples()
|
|
75
|
+
return self.to_samples() + rhs
|
|
76
|
+
|
|
77
|
+
def __radd__(self, lhs: MarkerOutput | SampledOutput):
|
|
78
|
+
return self + lhs
|
q1simulator/cluster.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from functools import partial
|
|
3
|
+
|
|
4
|
+
import matplotlib.pyplot as pt
|
|
5
|
+
import qcodes as qc
|
|
6
|
+
|
|
7
|
+
from qblox_instruments import (
|
|
8
|
+
InstrumentClass, InstrumentType,
|
|
9
|
+
SystemStatuses, SystemStatus, SystemStatusSlotFlags,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from .qblox_version import check_qblox_instrument_version
|
|
13
|
+
from .q1simulator import Q1Module, run_sequencers
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ClusterModule(qc.InstrumentChannel, Q1Module):
|
|
20
|
+
def __init__(self, root_instrument, name, slot, n_sequencers=6, sim_type=None):
|
|
21
|
+
super().__init__(root_instrument, name)
|
|
22
|
+
self._slot = slot
|
|
23
|
+
super().init_module(n_sequencers, sim_type)
|
|
24
|
+
|
|
25
|
+
def present(self):
|
|
26
|
+
return True
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def slot_idx(self):
|
|
30
|
+
return self._slot
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class EmptySlot(qc.InstrumentChannel):
|
|
34
|
+
def __init__(self, root_instrument, name):
|
|
35
|
+
super().__init__(root_instrument, name)
|
|
36
|
+
|
|
37
|
+
def present(self):
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Cluster(qc.Instrument):
|
|
42
|
+
_cluster_parameters = [
|
|
43
|
+
'trigger_monitor_latest',
|
|
44
|
+
]
|
|
45
|
+
_log_only_params = [
|
|
46
|
+
'led_brightness',
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
def __init__(self, name, modules={}):
|
|
50
|
+
check_qblox_instrument_version()
|
|
51
|
+
super().__init__(name)
|
|
52
|
+
|
|
53
|
+
# TODO return trigger count
|
|
54
|
+
for par_name in self._cluster_parameters:
|
|
55
|
+
self.add_parameter(par_name, set_cmd=partial(self._set, par_name))
|
|
56
|
+
for i in range(1, 16):
|
|
57
|
+
par_name = f'trigger{i}_monitor_count'
|
|
58
|
+
self.add_parameter(par_name, set_cmd=partial(self._set, par_name))
|
|
59
|
+
|
|
60
|
+
for par_name in self._log_only_params:
|
|
61
|
+
self.add_parameter(par_name,
|
|
62
|
+
set_cmd=partial(self._log_set, par_name))
|
|
63
|
+
|
|
64
|
+
self._modules = {}
|
|
65
|
+
for slot in range(1, 21):
|
|
66
|
+
name = f'module{slot}'
|
|
67
|
+
if slot in modules:
|
|
68
|
+
module = ClusterModule(self, name, slot, sim_type=modules[slot])
|
|
69
|
+
else:
|
|
70
|
+
module = EmptySlot(self, name)
|
|
71
|
+
self.add_submodule(name, module)
|
|
72
|
+
self._modules[slot] = module
|
|
73
|
+
|
|
74
|
+
def get_idn(self):
|
|
75
|
+
return dict(vendor='Q1Simulator', model='Cluster', serial='', firmware='')
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def instrument_class(self):
|
|
79
|
+
return InstrumentClass.CLUSTER
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def instrument_type(self):
|
|
83
|
+
return InstrumentType.MM
|
|
84
|
+
|
|
85
|
+
def get_num_system_error(self):
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
def get_system_error(self):
|
|
89
|
+
return '0,"No error"'
|
|
90
|
+
|
|
91
|
+
def get_system_status(self):
|
|
92
|
+
return SystemStatus(
|
|
93
|
+
SystemStatuses.OKAY,
|
|
94
|
+
[],
|
|
95
|
+
SystemStatusSlotFlags({}))
|
|
96
|
+
|
|
97
|
+
def get_connected_modules(self, filter_fn=None):
|
|
98
|
+
result = {}
|
|
99
|
+
for slot, module in self._modules.items():
|
|
100
|
+
if module.present():
|
|
101
|
+
result[slot] = module
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
def _check_module_present(self, slot):
|
|
105
|
+
if not self._modules[slot].present():
|
|
106
|
+
raise Exception(f'No module in slot {slot}')
|
|
107
|
+
|
|
108
|
+
def _set(self, name, value):
|
|
109
|
+
logger.info(f'{self.name}:{name}={value}')
|
|
110
|
+
|
|
111
|
+
def get_sequencer_status(self, slot, seq_nr, timeout=0):
|
|
112
|
+
return self._modules[slot].get_sequencer_status(seq_nr, timeout)
|
|
113
|
+
|
|
114
|
+
def get_acquisition_status(self, slot, seq_nr, timeout=0):
|
|
115
|
+
return self.self._modules[slot].get_acquisition_status(seq_nr, timeout)
|
|
116
|
+
|
|
117
|
+
def arm_sequencer(self, slot: int | None = None, sequencer: int | None = None) -> None:
|
|
118
|
+
if slot is not None:
|
|
119
|
+
self._check_module_present(slot)
|
|
120
|
+
self._modules[slot].arm_sequencer(sequencer)
|
|
121
|
+
else:
|
|
122
|
+
for module in self.get_connected_modules().values():
|
|
123
|
+
module.arm_sequencer(sequencer)
|
|
124
|
+
|
|
125
|
+
def start_sequencer(self, slot: int | None = None, sequencer: int | None = None) -> None:
|
|
126
|
+
if slot is not None:
|
|
127
|
+
self._check_module_present(slot)
|
|
128
|
+
modules = [self._modules[slot]]
|
|
129
|
+
else:
|
|
130
|
+
modules = self.get_connected_modules().values()
|
|
131
|
+
|
|
132
|
+
# Get list of armed sequencers
|
|
133
|
+
# pass to sequence executor
|
|
134
|
+
sequencers = []
|
|
135
|
+
for module in modules:
|
|
136
|
+
seq_numbers = [sequencer] if sequencer is not None else module.armed_seq
|
|
137
|
+
for seq_number in seq_numbers:
|
|
138
|
+
sequencers.append(module.sequencers[seq_number])
|
|
139
|
+
|
|
140
|
+
run_sequencers(sequencers)
|
|
141
|
+
|
|
142
|
+
def stop_sequencer(self, slot: int | None = None, sequencer: int | None = None) -> None:
|
|
143
|
+
if slot is not None:
|
|
144
|
+
self._check_module_present(slot)
|
|
145
|
+
self._modules[slot].stop_sequencer(sequencer)
|
|
146
|
+
else:
|
|
147
|
+
for module in self.get_connected_modules().values():
|
|
148
|
+
module.stop_sequencer(sequencer)
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def modules(self):
|
|
152
|
+
return list(self.submodules.values())
|
|
153
|
+
|
|
154
|
+
def reset(self):
|
|
155
|
+
self.invalidate_cache()
|
|
156
|
+
for module in self.get_connected_modules().values():
|
|
157
|
+
module.reset()
|
|
158
|
+
|
|
159
|
+
def _log_set(self, name, value):
|
|
160
|
+
logger.info(f'{self.name}: {name}={value}')
|
|
161
|
+
|
|
162
|
+
def config(self, name, value):
|
|
163
|
+
for module in self.get_connected_modules().values():
|
|
164
|
+
module.config(name, value)
|
|
165
|
+
|
|
166
|
+
def get_simulation_end_time(self):
|
|
167
|
+
return max(module.get_simulation_end_time() for module in self.get_connected_modules().values())
|
|
168
|
+
|
|
169
|
+
def plot(self,
|
|
170
|
+
t_min: float | None = None,
|
|
171
|
+
t_max: float | None = None,
|
|
172
|
+
channels: list[str] | list[int] | None = None,
|
|
173
|
+
modules: list[int] | None = None,
|
|
174
|
+
create_figure: bool | str = True,
|
|
175
|
+
analogue_filter: bool = False,
|
|
176
|
+
analogue_output_frequency: float = 4e9,
|
|
177
|
+
output_per_sequencer: bool = True,
|
|
178
|
+
**kwargs):
|
|
179
|
+
"""Plots the simulated output of the cluster.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
t_min: minimum time in the plot.
|
|
183
|
+
t_max: maximum time in the plot.
|
|
184
|
+
channels: If not None specifies the channels to plot by name or sequencer number.
|
|
185
|
+
modules: If not None specifies the modules to plot by slot number.
|
|
186
|
+
create_figure:
|
|
187
|
+
If True create a new figure.
|
|
188
|
+
If False only pyplot.plot() is called without creating figure or setting axis labels.
|
|
189
|
+
If "modules" creates a new figure per module.
|
|
190
|
+
analogue_filter: plot result after applying (estimated) analog filter.
|
|
191
|
+
analogue_output_frequency: sample rate of analogue output
|
|
192
|
+
output_per_sequencer:
|
|
193
|
+
if True: plot data for individual sequencers.
|
|
194
|
+
if False: plot data for physical front panel output.
|
|
195
|
+
"""
|
|
196
|
+
if create_figure is True:
|
|
197
|
+
pt.figure()
|
|
198
|
+
pt.title('Cluster')
|
|
199
|
+
pt.grid(True)
|
|
200
|
+
pt.xlabel('[ns]')
|
|
201
|
+
pt.ylabel('[V]')
|
|
202
|
+
for slot, module in self.get_connected_modules().items():
|
|
203
|
+
if modules is not None and slot not in modules:
|
|
204
|
+
# skip module
|
|
205
|
+
continue
|
|
206
|
+
if create_figure == "module":
|
|
207
|
+
pt.figure()
|
|
208
|
+
pt.title(module.label)
|
|
209
|
+
pt.grid(True)
|
|
210
|
+
pt.xlabel('[ns]')
|
|
211
|
+
pt.ylabel('[V]')
|
|
212
|
+
module.plot(t_min=t_min, t_max=t_max, channels=channels, analogue_filter=analogue_filter,
|
|
213
|
+
analogue_output_frequency=analogue_output_frequency,
|
|
214
|
+
output_per_sequencer=output_per_sequencer)
|
|
215
|
+
pt.legend()
|
|
216
|
+
pt.show()
|
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from qtpy import QtCore, QtWidgets
|
|
5
|
+
|
|
6
|
+
from q1simulator.gui.program_details import ProgramDetailsWidget
|
|
7
|
+
from q1simulator.gui.qt_utils import (
|
|
8
|
+
# qt_log_exception,
|
|
9
|
+
qt_init,
|
|
10
|
+
qt_create_app,
|
|
11
|
+
qt_run_app,
|
|
12
|
+
qt_set_darkstyle,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_app = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Q1ProgramBrowser(QtWidgets.QMainWindow):
|
|
23
|
+
"""
|
|
24
|
+
Q1Program browser.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_WINDOW_TITLE: str = "Q1Program Browser"
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
path: str | None = None,
|
|
32
|
+
gui_style: str | None = None
|
|
33
|
+
):
|
|
34
|
+
"""Creates program browser
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
path: base directory. If None uses current working directory.
|
|
38
|
+
gui_style: if "dark" uses dark style, otherwise normal style.
|
|
39
|
+
"""
|
|
40
|
+
global _app
|
|
41
|
+
logger.debug("Init program browser")
|
|
42
|
+
|
|
43
|
+
qt_app_runing = qt_init()
|
|
44
|
+
if not qt_app_runing:
|
|
45
|
+
# note: store reference to avoid garbage collection.
|
|
46
|
+
# reference is also used to restart browser 2nd time in Python console.
|
|
47
|
+
_app = qt_create_app()
|
|
48
|
+
|
|
49
|
+
if gui_style == "dark":
|
|
50
|
+
qt_set_darkstyle()
|
|
51
|
+
|
|
52
|
+
super().__init__()
|
|
53
|
+
|
|
54
|
+
if path is None:
|
|
55
|
+
path = os.getcwd()
|
|
56
|
+
|
|
57
|
+
self._path = path
|
|
58
|
+
|
|
59
|
+
self.setWindowTitle(self._WINDOW_TITLE)
|
|
60
|
+
self.resize(860, 600)
|
|
61
|
+
self.create_ui()
|
|
62
|
+
self.fill_program_list()
|
|
63
|
+
self.show()
|
|
64
|
+
|
|
65
|
+
if _app is not None:
|
|
66
|
+
qt_run_app(_app)
|
|
67
|
+
|
|
68
|
+
def create_ui(self):
|
|
69
|
+
content = QtWidgets.QWidget()
|
|
70
|
+
self._list_widget = QtWidgets.QListWidget()
|
|
71
|
+
self._list_widget.setMinimumWidth(300)
|
|
72
|
+
self._list_widget.itemSelectionChanged.connect(self._show_program_info)
|
|
73
|
+
|
|
74
|
+
self._program_details = ProgramDetailsWidget(self)
|
|
75
|
+
|
|
76
|
+
layout = QtWidgets.QHBoxLayout()
|
|
77
|
+
layout.addWidget(self._list_widget)
|
|
78
|
+
layout.addWidget(self._program_details, 1)
|
|
79
|
+
|
|
80
|
+
content.setLayout(layout)
|
|
81
|
+
self.setCentralWidget(content)
|
|
82
|
+
|
|
83
|
+
@QtCore.Slot()
|
|
84
|
+
def _show_program_info(self):
|
|
85
|
+
selected_items = self._list_widget.selectedItems()
|
|
86
|
+
if not selected_items:
|
|
87
|
+
return
|
|
88
|
+
item = selected_items[0]
|
|
89
|
+
path = item.data(QtCore.Qt.UserRole)
|
|
90
|
+
self._program_details.show_details(path)
|
|
91
|
+
|
|
92
|
+
def fill_program_list(self):
|
|
93
|
+
w = self._list_widget
|
|
94
|
+
w.clear()
|
|
95
|
+
for entry in os.scandir(self._path):
|
|
96
|
+
if not entry.is_dir():
|
|
97
|
+
continue
|
|
98
|
+
i = w.count()
|
|
99
|
+
w.addItem(entry.name)
|
|
100
|
+
item = w.item(i)
|
|
101
|
+
item.setData(QtCore.Qt.UserRole, entry.path)
|
|
102
|
+
|
|
103
|
+
def _show_error_message(self, title, message):
|
|
104
|
+
msg = QtWidgets.QMessageBox()
|
|
105
|
+
msg.setIcon(QtWidgets.QMessageBox.Critical)
|
|
106
|
+
msg.setText(message)
|
|
107
|
+
msg.setWindowTitle("Q1ProgramBrowser: " + title)
|
|
108
|
+
msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
|
|
109
|
+
msg.exec_()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
path = r"C:\measurements\qblox_programs"
|
|
114
|
+
ui = Q1ProgramBrowser(path, gui_style="dark")
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from qtpy import QtWidgets, QtCore
|
|
3
|
+
|
|
4
|
+
from .program_plotter import plot_simulation
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ProgramDetailsWidget(QtWidgets.QWidget):
|
|
8
|
+
def __init__(self,
|
|
9
|
+
parent: QtWidgets.QWidget,
|
|
10
|
+
):
|
|
11
|
+
super().__init__(parent)
|
|
12
|
+
self._prog_config = None
|
|
13
|
+
self._path = None
|
|
14
|
+
|
|
15
|
+
layout = QtWidgets.QVBoxLayout()
|
|
16
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
17
|
+
self.setLayout(layout)
|
|
18
|
+
|
|
19
|
+
self._table = self.create_table()
|
|
20
|
+
layout.addWidget(self._table, 1)
|
|
21
|
+
layout.addLayout(self.create_controls())
|
|
22
|
+
|
|
23
|
+
self._plot_btn.clicked.connect(self.plot)
|
|
24
|
+
|
|
25
|
+
def create_table(self):
|
|
26
|
+
table = QtWidgets.QTreeWidget()
|
|
27
|
+
table.setMinimumHeight(400)
|
|
28
|
+
table.setMinimumWidth(540)
|
|
29
|
+
table.setColumnCount(7)
|
|
30
|
+
table.setColumnWidth(0, 100)
|
|
31
|
+
table.setColumnWidth(1, 100)
|
|
32
|
+
table.setColumnWidth(2, 50)
|
|
33
|
+
table.setColumnWidth(3, 50)
|
|
34
|
+
table.setColumnWidth(4, 50)
|
|
35
|
+
table.setColumnWidth(5, 90)
|
|
36
|
+
table.setColumnWidth(6, 80)
|
|
37
|
+
table.setHeaderLabels([
|
|
38
|
+
"Channel",
|
|
39
|
+
"Module",
|
|
40
|
+
"Paths",
|
|
41
|
+
"Out",
|
|
42
|
+
"In",
|
|
43
|
+
"NCO",
|
|
44
|
+
"Duration",
|
|
45
|
+
])
|
|
46
|
+
table.setRootIsDecorated(False)
|
|
47
|
+
return table
|
|
48
|
+
|
|
49
|
+
def create_controls(self):
|
|
50
|
+
min_time = QtWidgets.QSpinBox()
|
|
51
|
+
min_time.setRange(0, 100_000_000)
|
|
52
|
+
min_time.setSingleStep(1000)
|
|
53
|
+
self._min_time_spin = min_time
|
|
54
|
+
max_time = QtWidgets.QSpinBox()
|
|
55
|
+
max_time.setSpecialValueText("Duration")
|
|
56
|
+
max_time.setRange(0, 100_000_000)
|
|
57
|
+
max_time.setSingleStep(1000)
|
|
58
|
+
self._max_time_spin = max_time
|
|
59
|
+
|
|
60
|
+
self._analogue_cb = QtWidgets.QCheckBox("Analogue filter")
|
|
61
|
+
self._plot_btn = QtWidgets.QPushButton("Plot")
|
|
62
|
+
|
|
63
|
+
layout = QtWidgets.QHBoxLayout()
|
|
64
|
+
layout.addWidget(QtWidgets.QLabel("t min."))
|
|
65
|
+
layout.addWidget(min_time)
|
|
66
|
+
layout.addWidget(QtWidgets.QLabel("t max."))
|
|
67
|
+
layout.addWidget(max_time)
|
|
68
|
+
layout.addWidget(self._analogue_cb)
|
|
69
|
+
layout.addWidget(self._plot_btn)
|
|
70
|
+
|
|
71
|
+
return layout
|
|
72
|
+
|
|
73
|
+
def show_details(self, path):
|
|
74
|
+
table = self._table
|
|
75
|
+
table.clear()
|
|
76
|
+
self._prog_config = None
|
|
77
|
+
try:
|
|
78
|
+
with open(path + "/q1program.json", "r") as fp:
|
|
79
|
+
data = json.load(fp)
|
|
80
|
+
self._path = path
|
|
81
|
+
self._prog_config = data
|
|
82
|
+
duration = -1
|
|
83
|
+
rows = []
|
|
84
|
+
for ch_name, settings in data.items():
|
|
85
|
+
f_nco = settings["nco"]
|
|
86
|
+
seq_duration = settings.get("duration")
|
|
87
|
+
if seq_duration is not None:
|
|
88
|
+
duration = max(seq_duration, duration)
|
|
89
|
+
duration_text = f"{seq_duration} ns"
|
|
90
|
+
else:
|
|
91
|
+
duration_text = "--"
|
|
92
|
+
q1asm = settings.get("sequence")
|
|
93
|
+
if not q1asm:
|
|
94
|
+
duration_text = "no sequence"
|
|
95
|
+
|
|
96
|
+
item = QtWidgets.QTreeWidgetItem(table)
|
|
97
|
+
item.setText(0, ch_name)
|
|
98
|
+
item.setData(0, QtCore.Qt.CheckStateRole, QtCore.Qt.Checked if q1asm else QtCore.Qt.Unchecked)
|
|
99
|
+
|
|
100
|
+
item.setText(1, settings["module"])
|
|
101
|
+
item.setText(2, str(settings["paths"]))
|
|
102
|
+
item.setText(3, str(settings["out_channels"]))
|
|
103
|
+
item.setText(4, str(settings.get("in_channels", "")))
|
|
104
|
+
item.setText(5, f"{f_nco/1e6} MHz" if f_nco is not None else "")
|
|
105
|
+
item.setText(6, duration_text)
|
|
106
|
+
rows.append(item)
|
|
107
|
+
table.insertTopLevelItems(0, rows)
|
|
108
|
+
|
|
109
|
+
# Show duration
|
|
110
|
+
|
|
111
|
+
except FileNotFoundError:
|
|
112
|
+
print("'program_config.json' not found")
|
|
113
|
+
|
|
114
|
+
def plot(self):
|
|
115
|
+
if not self._prog_config:
|
|
116
|
+
print("no program")
|
|
117
|
+
return
|
|
118
|
+
table = self._table
|
|
119
|
+
channels = []
|
|
120
|
+
for i in range(table.topLevelItemCount()):
|
|
121
|
+
item = table.topLevelItem(i)
|
|
122
|
+
if item.checkState(0) == QtCore.Qt.Checked:
|
|
123
|
+
channels.append(item.text(0))
|
|
124
|
+
|
|
125
|
+
min_time = self._min_time_spin.value()
|
|
126
|
+
max_time = self._max_time_spin.value()
|
|
127
|
+
if max_time == 0:
|
|
128
|
+
max_time = None
|
|
129
|
+
|
|
130
|
+
plot_simulation(
|
|
131
|
+
self._path,
|
|
132
|
+
self._prog_config,
|
|
133
|
+
channels,
|
|
134
|
+
min_time=min_time,
|
|
135
|
+
max_time=max_time,
|
|
136
|
+
analogue_filter=self._analogue_cb.isChecked(),
|
|
137
|
+
analogue_output_frequency=4e9,
|
|
138
|
+
)
|