QuLab 2.4.0__cp312-cp312-macosx_10_13_universal2.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.
- QuLab-2.4.0.dist-info/LICENSE +21 -0
- QuLab-2.4.0.dist-info/METADATA +105 -0
- QuLab-2.4.0.dist-info/RECORD +97 -0
- QuLab-2.4.0.dist-info/WHEEL +5 -0
- QuLab-2.4.0.dist-info/entry_points.txt +2 -0
- QuLab-2.4.0.dist-info/top_level.txt +1 -0
- qulab/__init__.py +3 -0
- qulab/__main__.py +30 -0
- qulab/dicttree.py +511 -0
- qulab/executor/__init__.py +5 -0
- qulab/executor/__main__.py +89 -0
- qulab/executor/load.py +202 -0
- qulab/executor/schedule.py +223 -0
- qulab/executor/storage.py +143 -0
- qulab/executor/transform.py +90 -0
- qulab/executor/utils.py +107 -0
- qulab/fun.cpython-312-darwin.so +0 -0
- qulab/monitor/__init__.py +1 -0
- qulab/monitor/__main__.py +8 -0
- qulab/monitor/config.py +41 -0
- qulab/monitor/dataset.py +77 -0
- qulab/monitor/event_queue.py +54 -0
- qulab/monitor/mainwindow.py +234 -0
- qulab/monitor/monitor.py +93 -0
- qulab/monitor/ploter.py +123 -0
- qulab/monitor/qt_compat.py +16 -0
- qulab/monitor/toolbar.py +265 -0
- qulab/scan/__init__.py +3 -0
- qulab/scan/curd.py +221 -0
- qulab/scan/expression.py +646 -0
- qulab/scan/models.py +554 -0
- qulab/scan/optimize.py +76 -0
- qulab/scan/query.py +374 -0
- qulab/scan/record.py +603 -0
- qulab/scan/scan.py +1166 -0
- qulab/scan/server.py +533 -0
- qulab/scan/space.py +213 -0
- qulab/scan/utils.py +229 -0
- qulab/storage/__init__.py +0 -0
- qulab/storage/__main__.py +51 -0
- qulab/storage/backend/__init__.py +0 -0
- qulab/storage/backend/redis.py +204 -0
- qulab/storage/base_dataset.py +352 -0
- qulab/storage/chunk.py +60 -0
- qulab/storage/dataset.py +127 -0
- qulab/storage/file.py +273 -0
- qulab/storage/models/__init__.py +22 -0
- qulab/storage/models/base.py +4 -0
- qulab/storage/models/config.py +28 -0
- qulab/storage/models/file.py +89 -0
- qulab/storage/models/ipy.py +58 -0
- qulab/storage/models/models.py +88 -0
- qulab/storage/models/record.py +161 -0
- qulab/storage/models/report.py +22 -0
- qulab/storage/models/tag.py +93 -0
- qulab/storage/storage.py +95 -0
- qulab/sys/__init__.py +0 -0
- qulab/sys/chat.py +688 -0
- qulab/sys/device/__init__.py +3 -0
- qulab/sys/device/basedevice.py +229 -0
- qulab/sys/device/loader.py +86 -0
- qulab/sys/device/utils.py +79 -0
- qulab/sys/drivers/FakeInstrument.py +52 -0
- qulab/sys/drivers/__init__.py +0 -0
- qulab/sys/ipy_events.py +125 -0
- qulab/sys/net/__init__.py +0 -0
- qulab/sys/net/bencoder.py +205 -0
- qulab/sys/net/cli.py +169 -0
- qulab/sys/net/dhcp.py +543 -0
- qulab/sys/net/dhcpd.py +176 -0
- qulab/sys/net/kad.py +1142 -0
- qulab/sys/net/kcp.py +192 -0
- qulab/sys/net/nginx.py +192 -0
- qulab/sys/progress.py +190 -0
- qulab/sys/rpc/__init__.py +0 -0
- qulab/sys/rpc/client.py +0 -0
- qulab/sys/rpc/exceptions.py +96 -0
- qulab/sys/rpc/msgpack.py +1052 -0
- qulab/sys/rpc/msgpack.pyi +41 -0
- qulab/sys/rpc/router.py +35 -0
- qulab/sys/rpc/rpc.py +412 -0
- qulab/sys/rpc/serialize.py +139 -0
- qulab/sys/rpc/server.py +29 -0
- qulab/sys/rpc/socket.py +29 -0
- qulab/sys/rpc/utils.py +25 -0
- qulab/sys/rpc/worker.py +0 -0
- qulab/sys/rpc/zmq_socket.py +220 -0
- qulab/version.py +1 -0
- qulab/visualization/__init__.py +188 -0
- qulab/visualization/__main__.py +71 -0
- qulab/visualization/_autoplot.py +464 -0
- qulab/visualization/plot_circ.py +319 -0
- qulab/visualization/plot_layout.py +408 -0
- qulab/visualization/plot_seq.py +242 -0
- qulab/visualization/qdat.py +152 -0
- qulab/visualization/rot3d.py +23 -0
- qulab/visualization/widgets.py +86 -0
qulab/executor/utils.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from .load import load_workflow
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Node:
|
|
7
|
+
|
|
8
|
+
def __init__(self, name: str):
|
|
9
|
+
self.name = name
|
|
10
|
+
self.dependents = []
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Tree:
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.nodes = {}
|
|
17
|
+
self.heads = []
|
|
18
|
+
|
|
19
|
+
def add_node(self, node: str):
|
|
20
|
+
self.nodes[node] = Node(node)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def dependent_tree(node: str, code_path: str | Path) -> dict[str, list[str]]:
|
|
24
|
+
'''
|
|
25
|
+
Returns a dict of nodes and their dependents.
|
|
26
|
+
'''
|
|
27
|
+
tree = {}
|
|
28
|
+
for n in load_workflow(node, code_path).depends()[0]:
|
|
29
|
+
tree[n] = dependent_tree(n, code_path)
|
|
30
|
+
return tree
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def workflow_template(deps: list[str]) -> str:
|
|
34
|
+
return f"""
|
|
35
|
+
from loguru import logger
|
|
36
|
+
|
|
37
|
+
import numpy as np
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# 多长时间应该检查一次校准实验,单位是秒。
|
|
41
|
+
__timeout__ = 200
|
|
42
|
+
|
|
43
|
+
def depends():
|
|
44
|
+
return [{deps!r}]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def calibrate():
|
|
48
|
+
logger.info(f"run {{__name__}}")
|
|
49
|
+
|
|
50
|
+
# calibrate 是一个完整的校准实验,如power Rabi,Ramsey等。
|
|
51
|
+
# 你需要足够的扫描点,以使得后续的 analyze 可以拟合出合适的参数。
|
|
52
|
+
|
|
53
|
+
# 这里只是一个示例,实际上你需要在这里写上你的校准代码。
|
|
54
|
+
x = np.linspace(0, 2*np.pi, 101)
|
|
55
|
+
y = []
|
|
56
|
+
for i in x:
|
|
57
|
+
y.append(np.sin(i))
|
|
58
|
+
|
|
59
|
+
return x, y
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def analyze(*args, history):
|
|
63
|
+
import random
|
|
64
|
+
|
|
65
|
+
# 完整校准后的状态有两种:OK 和 Bad,分别对应校准成功和校准失败。
|
|
66
|
+
# 校准失败是指出现坏数据,无法简单通过重新运行本次校准解决,需要
|
|
67
|
+
# 检查前置步骤。
|
|
68
|
+
state = random.choice(['OK', 'Bad'])
|
|
69
|
+
|
|
70
|
+
# 参数是一个字典,包含了本次校准得到的参数,后续会更新到config表中。
|
|
71
|
+
parameters = {{'gate.R.Q1.params.amp':1}}
|
|
72
|
+
|
|
73
|
+
# 其他信息可以是任何可序列化的内容,你可以将你想要记录的信息放在这里。
|
|
74
|
+
# 下次校准分析时,这些信息也会在 history 参数中一起传入,帮助你在下
|
|
75
|
+
# 次分析时对比参考。
|
|
76
|
+
other_infomation = {{}}
|
|
77
|
+
|
|
78
|
+
return state, parameters, other_infomation
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def check():
|
|
82
|
+
logger.info(f"check {{__name__}}")
|
|
83
|
+
|
|
84
|
+
# check 是一个快速检查实验,用于检查校准是否过时。
|
|
85
|
+
# 你只需要少数扫描点,让后续的 check_analyze 知道参数是否漂移,数据
|
|
86
|
+
# 坏没坏就够了,不要求拟合。
|
|
87
|
+
|
|
88
|
+
# 这里只是一个示例,实际上你需要在这里写上你的检查代码。
|
|
89
|
+
x = np.linspace(0, 2*np.pi, 5)
|
|
90
|
+
y = []
|
|
91
|
+
for i in x:
|
|
92
|
+
y.append(np.sin(i))
|
|
93
|
+
|
|
94
|
+
return x, y
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def check_analyze(*args, history):
|
|
98
|
+
import random
|
|
99
|
+
|
|
100
|
+
# 状态有三种:Outdated, OK, Bad,分别对应过时、正常、坏数据。
|
|
101
|
+
# Outdated 是指数据过时,即参数漂了,需要重新校准。
|
|
102
|
+
# OK 是指数据正常,参数也没漂,不用重新校准。
|
|
103
|
+
# Bad 是指数据坏了,无法校准,需要检查前置步骤。
|
|
104
|
+
state = random.choice(['Outdated', 'OK', 'Bad'])
|
|
105
|
+
|
|
106
|
+
return state, {{}}, {{}}
|
|
107
|
+
"""
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .monitor import Monitor, get_monitor
|
qulab/monitor/config.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
style = '''
|
|
4
|
+
QWidget {
|
|
5
|
+
font: medium Ubuntu;
|
|
6
|
+
background-color: #011F2F;
|
|
7
|
+
font-size: 16px;
|
|
8
|
+
font-size: 16px;
|
|
9
|
+
color:#FFFFFF;
|
|
10
|
+
}
|
|
11
|
+
'''
|
|
12
|
+
#
|
|
13
|
+
Nroll = 6
|
|
14
|
+
|
|
15
|
+
# Format of the data.
|
|
16
|
+
forms = {
|
|
17
|
+
"mag": lambda w, ang: np.abs(w),
|
|
18
|
+
"phase": lambda w, ang: np.angle(w),
|
|
19
|
+
"real": lambda w, ang: np.real(w),
|
|
20
|
+
"imag": lambda w, ang: np.imag(w),
|
|
21
|
+
"rot": lambda w, ang: np.real(np.exp(1j * ang) * np.array(w))
|
|
22
|
+
}
|
|
23
|
+
form_keys = list(forms.keys())
|
|
24
|
+
#
|
|
25
|
+
COL_SEL = (0, 0, 0)
|
|
26
|
+
COL_UNSEL = (6, 6, 8)
|
|
27
|
+
#
|
|
28
|
+
|
|
29
|
+
defualt_colors = [
|
|
30
|
+
(200, 0, 0),
|
|
31
|
+
(55, 100, 180),
|
|
32
|
+
(40, 80, 150),
|
|
33
|
+
(30, 50, 110),
|
|
34
|
+
(25, 40, 70),
|
|
35
|
+
(25, 30, 50),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
widths = [3, 2, 2, 2, 1, 1]
|
|
39
|
+
SymSize = [5, 0, 0, 0, 0, 0]
|
|
40
|
+
ridx = list(range(Nroll))
|
|
41
|
+
ridx.reverse()
|
qulab/monitor/dataset.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from collections import defaultdict, deque
|
|
2
|
+
|
|
3
|
+
from .config import Nroll
|
|
4
|
+
|
|
5
|
+
PAUSE_TIME = 1
|
|
6
|
+
|
|
7
|
+
Number = int | float | complex
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def remove_duplicates(input_list: list[str]) -> list[str]:
|
|
11
|
+
"""
|
|
12
|
+
Remove duplicates from a list of strings, keeping the order of the elements.
|
|
13
|
+
"""
|
|
14
|
+
return list(dict.fromkeys(input_list))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Dataset():
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self.column_names = []
|
|
21
|
+
self.box = deque(maxlen=Nroll)
|
|
22
|
+
self.dirty = True
|
|
23
|
+
|
|
24
|
+
def clear(self):
|
|
25
|
+
self.box.clear()
|
|
26
|
+
|
|
27
|
+
def clear_history(self):
|
|
28
|
+
o = self.box.popleft()
|
|
29
|
+
self.box.clear()
|
|
30
|
+
self.box.appendleft(o)
|
|
31
|
+
|
|
32
|
+
def set_column_names(self, column_names: list[str]):
|
|
33
|
+
column_names = remove_duplicates(column_names)
|
|
34
|
+
if column_names != self.column_names:
|
|
35
|
+
self.clear()
|
|
36
|
+
self.column_names = column_names
|
|
37
|
+
|
|
38
|
+
def get_data(self, step: int, xname: str,
|
|
39
|
+
yname: str) -> tuple[list[Number], list[Number]]:
|
|
40
|
+
try:
|
|
41
|
+
b = self.box[step]
|
|
42
|
+
except IndexError:
|
|
43
|
+
return [], []
|
|
44
|
+
return b[xname], b[yname]
|
|
45
|
+
|
|
46
|
+
def append(self, dataframe: list[Number] | list[list[Number]]):
|
|
47
|
+
if not dataframe:
|
|
48
|
+
return
|
|
49
|
+
try:
|
|
50
|
+
iter(dataframe[0]) # test if dataframe is a list of list
|
|
51
|
+
self._append_traces(dataframe)
|
|
52
|
+
except TypeError:
|
|
53
|
+
self._append_points(dataframe)
|
|
54
|
+
|
|
55
|
+
def roll(self):
|
|
56
|
+
self.box.appendleft(defaultdict(list))
|
|
57
|
+
|
|
58
|
+
def _append_points(self, points: list[Number]):
|
|
59
|
+
self.dirty = True
|
|
60
|
+
assert (len(points) == len(
|
|
61
|
+
self.column_names)), (f"-PointDataBox\n"
|
|
62
|
+
f"-ap\n"
|
|
63
|
+
f"-Length Must be same\n"
|
|
64
|
+
f"the column_names : {self.column_names}\n"
|
|
65
|
+
f"given data : {points}")
|
|
66
|
+
for name, p in zip(self.column_names, points):
|
|
67
|
+
self.box[0][name].append(p)
|
|
68
|
+
|
|
69
|
+
def _append_traces(self, traces: list[list[Number]]):
|
|
70
|
+
self.dirty = True
|
|
71
|
+
assert (len(traces) == len(
|
|
72
|
+
self.column_names)), (f"-TraceDataBox\n"
|
|
73
|
+
f"-at\n"
|
|
74
|
+
f"-Length Must be same\n"
|
|
75
|
+
f"the column_names : {self.column_names}\n"
|
|
76
|
+
f"given data : {traces}")
|
|
77
|
+
self.box.appendleft(dict(zip(self.column_names, traces)))
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from multiprocessing import Queue
|
|
3
|
+
|
|
4
|
+
from .dataset import Dataset
|
|
5
|
+
from .toolbar import ToolBar
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EventQueue():
|
|
9
|
+
|
|
10
|
+
def __init__(self, queue: Queue, toolbar: ToolBar, point_dataset: Dataset,
|
|
11
|
+
trace_dataset: Dataset):
|
|
12
|
+
self.toolbar = toolbar
|
|
13
|
+
self.queue = queue
|
|
14
|
+
self.point_dataset = point_dataset
|
|
15
|
+
self.trace_dataset = trace_dataset
|
|
16
|
+
|
|
17
|
+
def flush(self):
|
|
18
|
+
while (not self.queue.empty()):
|
|
19
|
+
words = self.queue.get()
|
|
20
|
+
try:
|
|
21
|
+
assert (isinstance(
|
|
22
|
+
words, tuple)), "QueueHandler - fifo Content must be tuple"
|
|
23
|
+
assert (
|
|
24
|
+
len(words) == 2
|
|
25
|
+
), "QueueHandler -the tuple must be like (\"command_type\" , \"DATA\")"
|
|
26
|
+
cmd, data = words
|
|
27
|
+
assert (isinstance(
|
|
28
|
+
cmd, str)), "QueueHandler - the command should be a string"
|
|
29
|
+
except AssertionError as e:
|
|
30
|
+
warnings.warn(e.args[0])
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
self.handle(cmd, data)
|
|
34
|
+
|
|
35
|
+
def handle(self, cmd, data):
|
|
36
|
+
match cmd:
|
|
37
|
+
case "PN":
|
|
38
|
+
self.point_dataset.set_column_names(data)
|
|
39
|
+
self.toolbar.refresh_comb()
|
|
40
|
+
case "TN":
|
|
41
|
+
self.trace_dataset.set_column_names(data)
|
|
42
|
+
self.toolbar.refresh_comb()
|
|
43
|
+
case "PD":
|
|
44
|
+
self.point_dataset.append(data)
|
|
45
|
+
case "TD":
|
|
46
|
+
self.trace_dataset.append(data)
|
|
47
|
+
case "ROLL":
|
|
48
|
+
self.point_dataset.roll()
|
|
49
|
+
case "PXY":
|
|
50
|
+
self.toolbar.set_point_text(data)
|
|
51
|
+
case "TXY":
|
|
52
|
+
self.toolbar.set_trace_text(data)
|
|
53
|
+
case _:
|
|
54
|
+
warnings.warn(f"QueueHandler - unknown command : {cmd}")
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
from multiprocessing import Queue
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
from .config import forms, ridx, style
|
|
5
|
+
from .dataset import Dataset
|
|
6
|
+
from .event_queue import EventQueue
|
|
7
|
+
from .ploter import PlotWidget
|
|
8
|
+
from .qt_compat import (BottomDockWidgetArea, QtCore, QtWidgets,
|
|
9
|
+
ScrollBarAlwaysOff, ScrollBarAlwaysOn,
|
|
10
|
+
TopDockWidgetArea)
|
|
11
|
+
from .toolbar import ToolBar
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MainWindow(QtWidgets.QMainWindow):
|
|
15
|
+
|
|
16
|
+
def __init__(self,
|
|
17
|
+
queue: Queue,
|
|
18
|
+
ncols=3,
|
|
19
|
+
plot_minimum_height=350,
|
|
20
|
+
plot_colors: list[tuple[int, int, int]] | None = None):
|
|
21
|
+
super().__init__()
|
|
22
|
+
self.ncols = ncols
|
|
23
|
+
self.need_reshuffled = False
|
|
24
|
+
self.plot_minimum_height = plot_minimum_height
|
|
25
|
+
self.plot_widgets: list[PlotWidget] = []
|
|
26
|
+
self.plot_colors = plot_colors
|
|
27
|
+
self.toolbar = ToolBar()
|
|
28
|
+
self.trace_data_box = Dataset()
|
|
29
|
+
self.point_data_box = Dataset()
|
|
30
|
+
self.queue = EventQueue(queue, self.toolbar, self.point_data_box,
|
|
31
|
+
self.trace_data_box)
|
|
32
|
+
|
|
33
|
+
self.init_ui()
|
|
34
|
+
|
|
35
|
+
self.timer = QtCore.QTimer()
|
|
36
|
+
self.timer.timeout.connect(self.update)
|
|
37
|
+
self.timer.start(250)
|
|
38
|
+
|
|
39
|
+
def init_ui(self):
|
|
40
|
+
self.setStyleSheet(style)
|
|
41
|
+
self.setMinimumHeight(500)
|
|
42
|
+
self.setMinimumWidth(700)
|
|
43
|
+
self.scroll = QtWidgets.QScrollArea(
|
|
44
|
+
) # Scroll Area which contains the widgets, set as the centralWidget
|
|
45
|
+
self.widget = QtWidgets.QWidget(
|
|
46
|
+
) # Widget that contains the collection of Vertical Box
|
|
47
|
+
self.layout = QtWidgets.QGridLayout()
|
|
48
|
+
self.widget.setLayout(self.layout)
|
|
49
|
+
|
|
50
|
+
#Scroll Area Properties
|
|
51
|
+
#self.setCorner(Qt.TopSection, Qt.TopDockWidgetArea);
|
|
52
|
+
self.scroll.setVerticalScrollBarPolicy(ScrollBarAlwaysOn)
|
|
53
|
+
self.scroll.setHorizontalScrollBarPolicy(ScrollBarAlwaysOff)
|
|
54
|
+
self.scroll.setWidgetResizable(True)
|
|
55
|
+
self.scroll.setWidget(self.widget)
|
|
56
|
+
self.setCentralWidget(self.scroll)
|
|
57
|
+
|
|
58
|
+
self.dock = QtWidgets.QDockWidget(self)
|
|
59
|
+
self.dock.setAllowedAreas(TopDockWidgetArea | BottomDockWidgetArea)
|
|
60
|
+
self.addDockWidget(TopDockWidgetArea, self.dock)
|
|
61
|
+
self.dock.setFloating(False)
|
|
62
|
+
self.dock.setWidget(self.toolbar)
|
|
63
|
+
self.dock.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable)
|
|
64
|
+
#self.tabifyDockWidget(self.dock,None);
|
|
65
|
+
#self.addDockWidget(self.dock);
|
|
66
|
+
#self.setStatusBar(self.toolbar);
|
|
67
|
+
#self.layout.addWidget(self.toolbar , 0 , 0 , 1, self.ncol);
|
|
68
|
+
|
|
69
|
+
self.setWindowTitle('Scroll multi view')
|
|
70
|
+
self.show()
|
|
71
|
+
self.toolbar.set_mainwindow(self)
|
|
72
|
+
self.toolbar.pb.setChecked(True)
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def mode(self) -> Literal["P", "T"]:
|
|
76
|
+
return self.toolbar.mode
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def dataset(self) -> Dataset:
|
|
80
|
+
return {"P": self.point_data_box, "T": self.trace_data_box}[self.mode]
|
|
81
|
+
|
|
82
|
+
def set_ncols(self, x: int):
|
|
83
|
+
x = max(1, min(10, int(x)))
|
|
84
|
+
if (x != self.ncols):
|
|
85
|
+
self.need_reshuffled = True
|
|
86
|
+
self.ncols = x
|
|
87
|
+
|
|
88
|
+
def add_subplot(self):
|
|
89
|
+
n = len(self.plot_widgets)
|
|
90
|
+
pw = PlotWidget(self.plot_minimum_height, self.plot_colors)
|
|
91
|
+
self.plot_widgets.append(pw)
|
|
92
|
+
grid_row = n // self.ncols
|
|
93
|
+
grid_col = n % self.ncols
|
|
94
|
+
self.layout.addWidget(pw, grid_row + 1, grid_col)
|
|
95
|
+
return pw
|
|
96
|
+
|
|
97
|
+
def create_subplots(self, xy_pairs):
|
|
98
|
+
for xn, yn in xy_pairs:
|
|
99
|
+
pw = self.add_subplot()
|
|
100
|
+
pw.set_X_label(xn)
|
|
101
|
+
pw.set_Y_label(yn)
|
|
102
|
+
self.do_link()
|
|
103
|
+
self.all_enable_auto_range()
|
|
104
|
+
|
|
105
|
+
def clear_subplots(self):
|
|
106
|
+
for i in range(len(self.plot_widgets)):
|
|
107
|
+
self.layout.removeWidget(self.plot_widgets[i])
|
|
108
|
+
self.plot_widgets[i].setParent(None)
|
|
109
|
+
self.plot_widgets.clear()
|
|
110
|
+
|
|
111
|
+
def remove_plot(self, w: PlotWidget):
|
|
112
|
+
w.setParent(None)
|
|
113
|
+
self.plot_widgets.remove(w)
|
|
114
|
+
self.reshuffle()
|
|
115
|
+
|
|
116
|
+
def drop_last_plot(self, i_=-1):
|
|
117
|
+
# delete the one
|
|
118
|
+
i = int(i_)
|
|
119
|
+
if (i < len(self.plot_widgets)):
|
|
120
|
+
w = self.plot_widgets[i]
|
|
121
|
+
w.setParent(None)
|
|
122
|
+
del w
|
|
123
|
+
del self.plot_widgets[i]
|
|
124
|
+
self.reshuffle()
|
|
125
|
+
|
|
126
|
+
def reshuffle(self):
|
|
127
|
+
for idx, widget in enumerate(self.plot_widgets):
|
|
128
|
+
widget.setParent(None)
|
|
129
|
+
grid_row = idx // self.ncols
|
|
130
|
+
grid_col = idx % self.ncols
|
|
131
|
+
self.layout.addWidget(widget, grid_row + 1, grid_col)
|
|
132
|
+
|
|
133
|
+
def keyPressEvent(self, ev):
|
|
134
|
+
#print(ev.text());
|
|
135
|
+
tx = ev.text()
|
|
136
|
+
if (tx in ['_', '-']):
|
|
137
|
+
self.set_ncols(self.ncols - 1)
|
|
138
|
+
elif (tx in ['=', '+']):
|
|
139
|
+
self.set_ncols(self.ncols + 1)
|
|
140
|
+
|
|
141
|
+
def mouse_click(self):
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
def do_link(self):
|
|
145
|
+
"""
|
|
146
|
+
link the plot
|
|
147
|
+
|
|
148
|
+
share the same x or y axis
|
|
149
|
+
"""
|
|
150
|
+
same_X = {}
|
|
151
|
+
xy_pairs = self.toolbar.xypairs
|
|
152
|
+
for idx, xyn in enumerate(xy_pairs):
|
|
153
|
+
xn, yn = xyn
|
|
154
|
+
if xn not in same_X:
|
|
155
|
+
same_X[xn] = []
|
|
156
|
+
same_X[xn].append(idx)
|
|
157
|
+
|
|
158
|
+
sharex, sharey = self.toolbar.sharexy()
|
|
159
|
+
|
|
160
|
+
s_A = not (sharex and sharey)
|
|
161
|
+
for x, yidxs in same_X.items():
|
|
162
|
+
pre_yidx = -1
|
|
163
|
+
for yidx in yidxs:
|
|
164
|
+
if (-1 != pre_yidx):
|
|
165
|
+
if (s_A):
|
|
166
|
+
self.plot_widgets[pre_yidx].plotItem.vb.setXLink(None)
|
|
167
|
+
self.plot_widgets[pre_yidx].plotItem.vb.setYLink(None)
|
|
168
|
+
|
|
169
|
+
if sharex:
|
|
170
|
+
self.plot_widgets[pre_yidx].plotItem.vb.setXLink(
|
|
171
|
+
self.plot_widgets[yidx].plotItem.vb)
|
|
172
|
+
|
|
173
|
+
if sharey:
|
|
174
|
+
self.plot_widgets[pre_yidx].plotItem.vb.setYLink(
|
|
175
|
+
self.plot_widgets[yidx].plotItem.vb)
|
|
176
|
+
pre_yidx = yidx
|
|
177
|
+
|
|
178
|
+
def all_auto_range(self):
|
|
179
|
+
for pw in self.plot_widgets:
|
|
180
|
+
pw.auto_range()
|
|
181
|
+
|
|
182
|
+
def all_enable_auto_range(self):
|
|
183
|
+
for pw in self.plot_widgets:
|
|
184
|
+
pw.enable_auto_range()
|
|
185
|
+
|
|
186
|
+
def update(self):
|
|
187
|
+
# update the queue
|
|
188
|
+
self.queue.flush()
|
|
189
|
+
|
|
190
|
+
rescale = False
|
|
191
|
+
|
|
192
|
+
# setup the xyfm
|
|
193
|
+
if (self.toolbar.xypairs_dirty):
|
|
194
|
+
self.clear_subplots()
|
|
195
|
+
self.create_subplots(self.toolbar.xypairs)
|
|
196
|
+
self.toolbar.xypairs_dirty = False
|
|
197
|
+
rescale = True
|
|
198
|
+
|
|
199
|
+
if (self.toolbar.link_dirty):
|
|
200
|
+
self.do_link()
|
|
201
|
+
self.toolbar.link_dirty = False
|
|
202
|
+
|
|
203
|
+
if (self.need_reshuffled):
|
|
204
|
+
self.need_reshuffled = False
|
|
205
|
+
self.reshuffle()
|
|
206
|
+
|
|
207
|
+
# checking the log space
|
|
208
|
+
if (self.toolbar.xyfm_dirty):
|
|
209
|
+
for pw in self.plot_widgets:
|
|
210
|
+
pw.plotItem.ctrl.logXCheck.setChecked(self.toolbar.lx)
|
|
211
|
+
pw.plotItem.ctrl.logYCheck.setChecked(self.toolbar.ly)
|
|
212
|
+
|
|
213
|
+
#update the plot
|
|
214
|
+
# if clear is set then do the clear :
|
|
215
|
+
if (self.toolbar.CR_flag):
|
|
216
|
+
self.toolbar.CR_flag = False
|
|
217
|
+
self.dataset.clear_history()
|
|
218
|
+
self.dataset.dirty = True
|
|
219
|
+
|
|
220
|
+
if (self.dataset.dirty or self.toolbar.xyfm_dirty or rescale):
|
|
221
|
+
self.dataset.dirty = False
|
|
222
|
+
self.xyfm_dirty = False
|
|
223
|
+
for pw in self.plot_widgets:
|
|
224
|
+
|
|
225
|
+
fx = forms[self.toolbar.fx]
|
|
226
|
+
fy = forms[self.toolbar.fy]
|
|
227
|
+
for i in ridx:
|
|
228
|
+
x, y = self.dataset.get_data(i, pw.xname, pw.yname)
|
|
229
|
+
l = min(len(x), len(y))
|
|
230
|
+
x, y = fx(x[:l], 0), fy(y[:l], 0)
|
|
231
|
+
pw.set_data(i, x, y)
|
|
232
|
+
pw.update()
|
|
233
|
+
if rescale:
|
|
234
|
+
pw.auto_range()
|
qulab/monitor/monitor.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import multiprocessing as mp
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
# try:
|
|
5
|
+
# mp.set_start_method("spawn")
|
|
6
|
+
# except:
|
|
7
|
+
# pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main(queue: mp.Queue,
|
|
11
|
+
ncols: int = 4,
|
|
12
|
+
minimum_height: int = 400,
|
|
13
|
+
colors: list[tuple[int, int, int]] = []):
|
|
14
|
+
from .mainwindow import MainWindow
|
|
15
|
+
from .qt_compat import QtWidgets
|
|
16
|
+
|
|
17
|
+
app = QtWidgets.QApplication(sys.argv)
|
|
18
|
+
main = MainWindow(queue, ncols, minimum_height, colors)
|
|
19
|
+
sys.exit(app.exec())
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Monitor():
|
|
23
|
+
|
|
24
|
+
def __init__(self,
|
|
25
|
+
number_of_columns: int = 4,
|
|
26
|
+
minimum_height: int = 400,
|
|
27
|
+
colors: list[tuple[int, int, int]] = []):
|
|
28
|
+
self.colors = [tuple(color) for color in colors]
|
|
29
|
+
self.number_of_columns = number_of_columns
|
|
30
|
+
self.minimum_height = minimum_height
|
|
31
|
+
self.queue = mp.Queue(20)
|
|
32
|
+
self.process = None
|
|
33
|
+
self.start()
|
|
34
|
+
|
|
35
|
+
def start(self):
|
|
36
|
+
if self.process is not None and self.process.is_alive():
|
|
37
|
+
return
|
|
38
|
+
self.queue = mp.Queue(20)
|
|
39
|
+
self.process = mp.Process(target=main,
|
|
40
|
+
args=(self.queue, self.number_of_columns,
|
|
41
|
+
self.minimum_height, self.colors))
|
|
42
|
+
self.process.start()
|
|
43
|
+
|
|
44
|
+
def _put(self, w: tuple):
|
|
45
|
+
self.queue.put(w)
|
|
46
|
+
|
|
47
|
+
def roll(self):
|
|
48
|
+
self._put(("ROLL", None))
|
|
49
|
+
|
|
50
|
+
def set_column_names(self, *arg):
|
|
51
|
+
self._put(('PN', list(arg)))
|
|
52
|
+
|
|
53
|
+
def add_point(self, *arg):
|
|
54
|
+
self._put(('PD', list(arg)))
|
|
55
|
+
|
|
56
|
+
def set_plots(self, arg):
|
|
57
|
+
"""
|
|
58
|
+
arg: str, like "(x,y)" or "(x1,y1);(x2,y2);"
|
|
59
|
+
"""
|
|
60
|
+
self._put(('PXY', str(arg)))
|
|
61
|
+
|
|
62
|
+
def set_trace_column_names(self, *arg):
|
|
63
|
+
self._put(('TN', list(arg)))
|
|
64
|
+
|
|
65
|
+
def add_trace(self, *arg):
|
|
66
|
+
self._put(('TD', list(arg)))
|
|
67
|
+
|
|
68
|
+
def set_trace_plots(self, arg):
|
|
69
|
+
"""
|
|
70
|
+
arg: str, like "(x,y)" or "(x1,y1);(x2,y2);"
|
|
71
|
+
"""
|
|
72
|
+
self._put(('TXY', str(arg)))
|
|
73
|
+
|
|
74
|
+
def __del__(self):
|
|
75
|
+
try:
|
|
76
|
+
self.process.kill()
|
|
77
|
+
except:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
def is_alive(self):
|
|
81
|
+
return self.process.is_alive()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
_monitor = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_monitor(auto_open=True):
|
|
88
|
+
global _monitor
|
|
89
|
+
|
|
90
|
+
if auto_open and (_monitor is None or not _monitor.is_alive()):
|
|
91
|
+
_monitor = Monitor()
|
|
92
|
+
|
|
93
|
+
return _monitor
|