QuLab 2.10.10__cp313-cp313-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.
Files changed (107) hide show
  1. qulab/__init__.py +33 -0
  2. qulab/__main__.py +4 -0
  3. qulab/cli/__init__.py +0 -0
  4. qulab/cli/commands.py +30 -0
  5. qulab/cli/config.py +170 -0
  6. qulab/cli/decorators.py +28 -0
  7. qulab/dicttree.py +523 -0
  8. qulab/executor/__init__.py +5 -0
  9. qulab/executor/analyze.py +188 -0
  10. qulab/executor/cli.py +434 -0
  11. qulab/executor/load.py +563 -0
  12. qulab/executor/registry.py +185 -0
  13. qulab/executor/schedule.py +543 -0
  14. qulab/executor/storage.py +615 -0
  15. qulab/executor/template.py +259 -0
  16. qulab/executor/utils.py +194 -0
  17. qulab/expression.py +827 -0
  18. qulab/fun.cpython-313-darwin.so +0 -0
  19. qulab/monitor/__init__.py +1 -0
  20. qulab/monitor/__main__.py +8 -0
  21. qulab/monitor/config.py +41 -0
  22. qulab/monitor/dataset.py +77 -0
  23. qulab/monitor/event_queue.py +54 -0
  24. qulab/monitor/mainwindow.py +234 -0
  25. qulab/monitor/monitor.py +115 -0
  26. qulab/monitor/ploter.py +123 -0
  27. qulab/monitor/qt_compat.py +16 -0
  28. qulab/monitor/toolbar.py +265 -0
  29. qulab/scan/__init__.py +2 -0
  30. qulab/scan/curd.py +221 -0
  31. qulab/scan/models.py +554 -0
  32. qulab/scan/optimize.py +76 -0
  33. qulab/scan/query.py +387 -0
  34. qulab/scan/record.py +603 -0
  35. qulab/scan/scan.py +1166 -0
  36. qulab/scan/server.py +450 -0
  37. qulab/scan/space.py +213 -0
  38. qulab/scan/utils.py +234 -0
  39. qulab/storage/__init__.py +0 -0
  40. qulab/storage/__main__.py +51 -0
  41. qulab/storage/backend/__init__.py +0 -0
  42. qulab/storage/backend/redis.py +204 -0
  43. qulab/storage/base_dataset.py +352 -0
  44. qulab/storage/chunk.py +60 -0
  45. qulab/storage/dataset.py +127 -0
  46. qulab/storage/file.py +273 -0
  47. qulab/storage/models/__init__.py +22 -0
  48. qulab/storage/models/base.py +4 -0
  49. qulab/storage/models/config.py +28 -0
  50. qulab/storage/models/file.py +89 -0
  51. qulab/storage/models/ipy.py +58 -0
  52. qulab/storage/models/models.py +88 -0
  53. qulab/storage/models/record.py +161 -0
  54. qulab/storage/models/report.py +22 -0
  55. qulab/storage/models/tag.py +93 -0
  56. qulab/storage/storage.py +95 -0
  57. qulab/sys/__init__.py +2 -0
  58. qulab/sys/chat.py +688 -0
  59. qulab/sys/device/__init__.py +3 -0
  60. qulab/sys/device/basedevice.py +255 -0
  61. qulab/sys/device/loader.py +86 -0
  62. qulab/sys/device/utils.py +79 -0
  63. qulab/sys/drivers/FakeInstrument.py +68 -0
  64. qulab/sys/drivers/__init__.py +0 -0
  65. qulab/sys/ipy_events.py +125 -0
  66. qulab/sys/net/__init__.py +0 -0
  67. qulab/sys/net/bencoder.py +205 -0
  68. qulab/sys/net/cli.py +169 -0
  69. qulab/sys/net/dhcp.py +543 -0
  70. qulab/sys/net/dhcpd.py +176 -0
  71. qulab/sys/net/kad.py +1142 -0
  72. qulab/sys/net/kcp.py +192 -0
  73. qulab/sys/net/nginx.py +194 -0
  74. qulab/sys/progress.py +190 -0
  75. qulab/sys/rpc/__init__.py +0 -0
  76. qulab/sys/rpc/client.py +0 -0
  77. qulab/sys/rpc/exceptions.py +96 -0
  78. qulab/sys/rpc/msgpack.py +1052 -0
  79. qulab/sys/rpc/msgpack.pyi +41 -0
  80. qulab/sys/rpc/router.py +35 -0
  81. qulab/sys/rpc/rpc.py +412 -0
  82. qulab/sys/rpc/serialize.py +139 -0
  83. qulab/sys/rpc/server.py +29 -0
  84. qulab/sys/rpc/socket.py +29 -0
  85. qulab/sys/rpc/utils.py +25 -0
  86. qulab/sys/rpc/worker.py +0 -0
  87. qulab/sys/rpc/zmq_socket.py +227 -0
  88. qulab/tools/__init__.py +0 -0
  89. qulab/tools/connection_helper.py +39 -0
  90. qulab/typing.py +2 -0
  91. qulab/utils.py +95 -0
  92. qulab/version.py +1 -0
  93. qulab/visualization/__init__.py +188 -0
  94. qulab/visualization/__main__.py +71 -0
  95. qulab/visualization/_autoplot.py +464 -0
  96. qulab/visualization/plot_circ.py +319 -0
  97. qulab/visualization/plot_layout.py +408 -0
  98. qulab/visualization/plot_seq.py +242 -0
  99. qulab/visualization/qdat.py +152 -0
  100. qulab/visualization/rot3d.py +23 -0
  101. qulab/visualization/widgets.py +86 -0
  102. qulab-2.10.10.dist-info/METADATA +110 -0
  103. qulab-2.10.10.dist-info/RECORD +107 -0
  104. qulab-2.10.10.dist-info/WHEEL +5 -0
  105. qulab-2.10.10.dist-info/entry_points.txt +2 -0
  106. qulab-2.10.10.dist-info/licenses/LICENSE +21 -0
  107. qulab-2.10.10.dist-info/top_level.txt +1 -0
@@ -0,0 +1,265 @@
1
+ import itertools
2
+ import re
3
+ from typing import Callable
4
+
5
+ from .config import style
6
+ from .qt_compat import AlignRight, QtWidgets
7
+
8
+
9
+ def matched_xy_pairs(patterns: str, lst: list[str]) -> list[tuple[str, str]]:
10
+ patterns = patterns.replace(" ", "").split(";")
11
+ pairs = []
12
+ for x, y in itertools.product(lst, repeat=2):
13
+ test = f"{x},{y}"
14
+ for pattern in patterns:
15
+ r = re.match(pattern, test)
16
+ if r and r.group(0) == test:
17
+ pairs.append((x, y))
18
+ break
19
+ return pairs
20
+
21
+
22
+ class FormatCombo(QtWidgets.QComboBox):
23
+
24
+ def __init__(self):
25
+ super().__init__()
26
+ self.on_change_callable = None
27
+
28
+ def set_on_change_event_action(self, callback: Callable[[], None]):
29
+ self.on_change_callable = callback
30
+ self.activated.connect(callback)
31
+
32
+ def set_idx(self, idx: int):
33
+ self.setCurrentIndex(idx)
34
+ if (callable(self.on_change_callable)):
35
+ self.on_change_callable()
36
+
37
+
38
+ class XFormatCombo(FormatCombo):
39
+
40
+ def __init__(self):
41
+ super().__init__()
42
+ self.addItem("real")
43
+ self.addItem("imag")
44
+ self.addItem("mag")
45
+ self.addItem("phase")
46
+
47
+
48
+ class YFormatCombo(FormatCombo):
49
+
50
+ def __init__(self):
51
+ super().__init__()
52
+ self.addItem("mag")
53
+ self.addItem("phase")
54
+ self.addItem("real")
55
+ self.addItem("imag")
56
+
57
+
58
+ class LineEdit(QtWidgets.QLineEdit):
59
+
60
+ def set_on_change_event_action(self, callback: Callable[[], None]):
61
+ self.on_change_callable = callback
62
+ self.editingFinished.connect(callback)
63
+
64
+ def set_text(self, w):
65
+ self.setText(w)
66
+ if (callable(self.on_change_callable)):
67
+ self.on_change_callable()
68
+
69
+
70
+ class SelectionBundle():
71
+
72
+ def __init__(self):
73
+ self.stx = LineEdit()
74
+ # select text
75
+ self.fx = XFormatCombo()
76
+ self.fy = YFormatCombo()
77
+ self.lx = QtWidgets.QCheckBox("logX")
78
+ self.ly = QtWidgets.QCheckBox("logY")
79
+ self.linkx = QtWidgets.QCheckBox("ShareX")
80
+ self.linky = QtWidgets.QCheckBox("ShareY")
81
+ self.sels = []
82
+ # tuple enumeration
83
+
84
+ def set_on_change_event_actions(self, on_text_edited, on_format_changed,
85
+ on_log_scale_marker_changed):
86
+ self.stx.set_on_change_event_action(on_text_edited)
87
+ self.fx.set_on_change_event_action(on_format_changed)
88
+ self.fy.set_on_change_event_action(on_format_changed)
89
+ self.lx.toggled.connect(on_format_changed)
90
+ self.ly.toggled.connect(on_format_changed)
91
+ self.linkx.toggled.connect(on_log_scale_marker_changed)
92
+ self.linky.toggled.connect(on_log_scale_marker_changed)
93
+
94
+ def rm4l(self): # remove from layout
95
+ self.stx.setParent(None)
96
+ self.fx.setParent(None)
97
+ self.fy.setParent(None)
98
+ self.lx.setParent(None)
99
+ self.ly.setParent(None)
100
+ self.linkx.setParent(None)
101
+ self.linky.setParent(None)
102
+
103
+ def a2l(self, layout): # add to layout
104
+ i = 3
105
+ layout.addWidget(self.stx, 0, i)
106
+ i += 2
107
+ layout.addWidget(self.fx, 0, i)
108
+ i += 2
109
+ layout.addWidget(self.fy, 0, i)
110
+ i += 2
111
+ layout.addWidget(self.lx, 0, 10)
112
+ layout.addWidget(self.ly, 0, 11)
113
+ layout.addWidget(self.linkx, 0, 12)
114
+ layout.addWidget(self.linky, 0, 13)
115
+
116
+
117
+ class ToolBar(QtWidgets.QWidget):
118
+
119
+ def __init__(self):
120
+ super().__init__()
121
+
122
+ # the buttons
123
+ # button for points
124
+ self.mode = 'P'
125
+ self.setStyleSheet(style)
126
+
127
+ self.pb = QtWidgets.QRadioButton('Points')
128
+ self.pb.setChecked(True)
129
+ self.pb.toggled.connect(self.toggle_mode)
130
+
131
+ # button for Traces
132
+ self.tb = QtWidgets.QRadioButton('Traces')
133
+ self.tb.toggled.connect(self.toggle_mode)
134
+
135
+ # text labels
136
+ self.ytxt_lb = QtWidgets.QLabel("(X,Y)")
137
+ self.ytxt_lb.setAlignment(AlignRight)
138
+ self.fx_lb = QtWidgets.QLabel("fx")
139
+ self.fx_lb.setAlignment(AlignRight)
140
+ self.fy_lb = QtWidgets.QLabel("fy")
141
+ self.fy_lb.setAlignment(AlignRight)
142
+
143
+ # enumeration
144
+ ps = SelectionBundle()
145
+ ps.set_on_change_event_actions(self.textEdited, self.generateXYFM,
146
+ self.link_edited)
147
+
148
+ ts = SelectionBundle()
149
+ ts.set_on_change_event_actions(self.textEdited, self.generateXYFM,
150
+ self.link_edited)
151
+
152
+ # connections :
153
+ self.ps = ps
154
+ self.ts = ts
155
+
156
+ # plot format configures
157
+ self.xypairs = []
158
+ self.xypairs_dirty = True
159
+ self.fx = None
160
+ self.fy = None
161
+ self.xyfm_dirty = True
162
+ self.link_dirty = True
163
+ self.lx = False
164
+ self.ly = False
165
+ # setting layout
166
+ self.layout = QtWidgets.QGridLayout()
167
+ self.layout.setContentsMargins(0, 0, 0, 0)
168
+ self.setLayout(self.layout)
169
+
170
+ self.layout.addWidget(self.pb, 0, 0)
171
+ self.layout.addWidget(self.tb, 0, 1)
172
+
173
+ self.layout.addWidget(self.ytxt_lb, 0, 2)
174
+ self.layout.addWidget(self.fx_lb, 0, 4)
175
+ self.layout.addWidget(self.fy_lb, 0, 6)
176
+
177
+ self.AR = QtWidgets.QPushButton("AR")
178
+ self.AR.setMaximumWidth(30)
179
+ self.AR.setToolTip("Auto Range")
180
+ self.CR = QtWidgets.QPushButton("CLR")
181
+ self.CR.setMaximumWidth(30)
182
+ self.CR.setToolTip("Clearing History Plots")
183
+ self.CR_flag = False
184
+ self.layout.addWidget(self.AR, 0, 8)
185
+ self.layout.addWidget(self.CR, 0, 9)
186
+
187
+ self.refresh_layout()
188
+
189
+ @property
190
+ def column_names(self) -> list[str]:
191
+ return {
192
+ "P": self.mainwindow.point_data_box.column_names,
193
+ "T": self.mainwindow.trace_data_box.column_names
194
+ }[self.mode]
195
+
196
+ @property
197
+ def selections(self) -> SelectionBundle:
198
+ return {"P": self.ps, "T": self.ts}[self.mode]
199
+
200
+ def set_trace_text(self, text: str):
201
+ self.ts.stx.set_text(text)
202
+
203
+ def set_point_text(self, text: str):
204
+ self.ps.stx.set_text(text)
205
+
206
+ def sharexy(self):
207
+ return self.selections.linkx.isChecked(
208
+ ), self.selections.linky.isChecked()
209
+
210
+ def set_mainwindow(self, mainwindow):
211
+ self.mainwindow = mainwindow
212
+ self.AR.clicked.connect(self.mainwindow.all_enable_auto_range)
213
+ self.CR.clicked.connect(self.CR_action)
214
+
215
+ def AR_action(self):
216
+ self.mainwindow.enable_all_auto_range()
217
+
218
+ def CR_action(self):
219
+ self.CR_flag = True
220
+
221
+ def refresh_layout(self):
222
+ if self.mode == 'P':
223
+ self.ts.rm4l()
224
+ self.ps.a2l(self.layout)
225
+ elif self.mode == 'T':
226
+ self.ps.rm4l()
227
+ self.ts.a2l(self.layout)
228
+
229
+ def toggle_mode(self):
230
+ if (self.pb.isChecked()):
231
+ self.mode = 'P'
232
+ elif (self.tb.isChecked()):
233
+ self.mode = 'T'
234
+ self.refresh_layout()
235
+ self.refresh_comb()
236
+
237
+ def refresh_comb(self, ):
238
+ self.generateXYFM()
239
+ self.textEdited()
240
+ #set tooltips
241
+ self.ytxt_lb.setToolTip(str(self.column_names))
242
+
243
+ def link_edited(self):
244
+ # print("LinkEdited")
245
+ self.link_dirty = True
246
+
247
+ def generateXYFM(self):
248
+ self.fx = self.selections.fx.currentText()
249
+ self.fy = self.selections.fy.currentText()
250
+ self.lx = self.selections.lx.isChecked()
251
+ self.ly = self.selections.ly.isChecked()
252
+ self.xyfm_dirty = True
253
+ #self.show_info() ;
254
+
255
+ def textEdited(self):
256
+ new_pairs = matched_xy_pairs(self.selections.stx.text(),
257
+ self.column_names)
258
+ if (len(self.xypairs) != len(new_pairs)):
259
+ self.xypairs_dirty = True
260
+ else:
261
+ for i, xy in enumerate(new_pairs):
262
+ if (xy != self.xypairs[i]):
263
+ self.xypairs_dirty = True
264
+ break
265
+ self.xypairs = new_pairs
qulab/scan/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ from .query import get_record, load_record, lookup, lookup_list
2
+ from .scan import Scan
qulab/scan/curd.py ADDED
@@ -0,0 +1,221 @@
1
+ import lzma
2
+ import pickle
3
+ from datetime import date, datetime, timezone
4
+ from pathlib import Path
5
+ from typing import Sequence, Type, Union
6
+
7
+ from sqlalchemy.orm import Query, Session, aliased
8
+ from sqlalchemy.orm.exc import NoResultFound
9
+ from sqlalchemy.orm.session import Session
10
+ from ..dicttree import foldDict
11
+
12
+ from .models import (Cell, Comment, Config, InputText, Notebook, Record,
13
+ Report, Sample, Tag, utcnow)
14
+
15
+
16
+ def tag(session: Session, tag_text: str) -> Tag:
17
+ """Get a tag from the database or create a new if not exists."""
18
+ try:
19
+ return session.query(Tag).filter(Tag.text == tag_text).one()
20
+ except NoResultFound:
21
+ tag = Tag(text=tag_text)
22
+ return tag
23
+
24
+
25
+ def tag_it(session: Session, tag_text: str, obj: Union[Sample, Record,
26
+ Report]) -> Tag:
27
+ """Tag an object."""
28
+ if obj.id is None:
29
+ session.add(obj)
30
+ obj.tags.append(tag(session, tag_text))
31
+ else:
32
+ session.query(type(obj)).filter(
33
+ type(obj).id == obj.id).one().tags.append(tag(session, tag_text))
34
+ session.commit()
35
+
36
+
37
+ def get_object_with_tags(session: Session,
38
+ cls: Union[Type[Comment], Type[Sample], Type[Record],
39
+ Type[Report]], *tags: str) -> Query:
40
+ """
41
+ Query objects with the given tags.
42
+
43
+ Parameters
44
+ ----------
45
+ session : :class:`sqlalchemy.orm.Session`
46
+ The database session.
47
+ cls : :class:`sqlalchemy.orm.Mapper`
48
+ The object class.
49
+ tags : str
50
+ The tags.
51
+
52
+ Returns
53
+ -------
54
+ :class:`sqlalchemy.orm.Query`
55
+ The query.
56
+ """
57
+ if isinstance(session, Query):
58
+ q = session
59
+ else:
60
+ q = session.query(cls)
61
+ if not hasattr(cls, 'tags'):
62
+ return []
63
+
64
+ aliase = {tag: aliased(Tag) for tag in tags}
65
+
66
+ for tag, a in aliase.items():
67
+ q = q.join(a, cls.tags)
68
+ if '*' in tag:
69
+ q = q.filter(a.text.like(tag.replace('*', '%')))
70
+ else:
71
+ q = q.filter(a.text == tag)
72
+ return q
73
+
74
+
75
+ def query_record(session: Session,
76
+ offset: int = 0,
77
+ limit: int = 10,
78
+ app: str | None = None,
79
+ tags: Sequence[str] = (),
80
+ before: datetime | date | None = None,
81
+ after: datetime | date | None = None):
82
+ tz_offset = datetime.now(timezone.utc).astimezone().utcoffset()
83
+ table = {'header': ['ID', 'App', 'tags', 'created time'], 'body': []}
84
+ apps = sorted(
85
+ set([
86
+ n for n, *_ in get_object_with_tags(session.query(Record.app),
87
+ Record, *tags).all()
88
+ ]))
89
+ apps = foldDict(dict([(app, None) for app in apps]))
90
+
91
+ query = get_object_with_tags(session, Record, *tags)
92
+
93
+ if app is not None:
94
+ if app.endswith('*'):
95
+ query = query.filter(Record.app.like(app[:-1] + '%'))
96
+ else:
97
+ query = query.filter(Record.app == app)
98
+ if before is not None:
99
+ if isinstance(before, date):
100
+ before = datetime(before.year, before.month, before.day)
101
+ query = query.filter(Record.ctime <= before - tz_offset)
102
+ if after is not None:
103
+ if isinstance(after, date):
104
+ after = datetime(after.year, after.month, after.day)
105
+ query = query.filter(Record.ctime >= after - tz_offset)
106
+ total = query.count()
107
+ for r in query.order_by(Record.ctime.desc()).limit(limit).offset(offset):
108
+ tags = sorted([t.text for t in r.tags])
109
+ ctime = r.ctime + tz_offset
110
+ row = [r.id, r.app, tags, ctime]
111
+ table['body'].append(row)
112
+
113
+ return total, apps, table
114
+
115
+
116
+ def update_tags(session: Session,
117
+ record_id: int,
118
+ tags: Sequence[str],
119
+ append: bool = False):
120
+ record = session.get(Record, record_id)
121
+ if record is None:
122
+ return False
123
+ if append:
124
+ old = [t.text for t in record.tags]
125
+ for t in old:
126
+ if t not in tags:
127
+ tags.append(t)
128
+ record.tags = [tag(session, t) for t in tags]
129
+ try:
130
+ session.commit()
131
+ except Exception:
132
+ session.rollback()
133
+ return False
134
+ return True
135
+
136
+
137
+ def remove_tags(session: Session, record_id: int, tags: Sequence[str]):
138
+ record = session.get(Record, record_id)
139
+ if record is None:
140
+ return False
141
+ old = [t.text for t in record.tags]
142
+ record.tags = [tag(session, t) for t in old if t not in tags]
143
+ try:
144
+ session.commit()
145
+ except Exception:
146
+ session.rollback()
147
+ return False
148
+ return True
149
+
150
+
151
+ def create_notebook(session: Session, notebook_name: str) -> Notebook:
152
+ """Create a notebook in the database."""
153
+ notebook = Notebook(name=notebook_name)
154
+ session.add(notebook)
155
+ return notebook
156
+
157
+
158
+ def create_input_text(session: Session, input_text: str) -> InputText:
159
+ """Create an input text in the database."""
160
+ input = InputText()
161
+ input.text = input_text
162
+ try:
163
+ input = session.query(InputText).filter(
164
+ InputText.hash == input.hash,
165
+ InputText.text_field == input_text).one()
166
+ except NoResultFound:
167
+ session.add(input)
168
+ return input
169
+
170
+
171
+ def create_cell(session: Session, notebook: Notebook, input_text: str) -> Cell:
172
+ """Create a cell in the database."""
173
+ cell = Cell()
174
+ cell.notebook = notebook
175
+ cell.input = create_input_text(session, input_text)
176
+ cell.index = len(notebook.cells) - 1
177
+ session.add(cell)
178
+ notebook.atime = cell.ctime
179
+ return cell
180
+
181
+
182
+ def create_config(session: Session, config: dict | bytes, base: Path,
183
+ filename: str) -> Config:
184
+ """Create a config in the database."""
185
+
186
+ if not isinstance(config, bytes):
187
+ buf = pickle.dumps(config)
188
+ buf = lzma.compress(buf)
189
+ content_type = 'application/pickle+lzma'
190
+ else:
191
+ buf = config
192
+ content_type = 'application/octet-stream'
193
+ config = Config(buf)
194
+ config.content_type = content_type
195
+ for cfg in session.query(Config).filter(Config.hash == config.hash).all():
196
+ with open(base / cfg.file, 'rb') as f:
197
+ if f.read() == buf:
198
+ cfg.atime = utcnow()
199
+ return cfg
200
+ else:
201
+ path = base / filename
202
+ path.parent.mkdir(parents=True, exist_ok=True)
203
+ with open(path, 'wb') as f:
204
+ f.write(buf)
205
+ config.file = filename
206
+ session.add(config)
207
+ return config
208
+
209
+
210
+ def get_config(session: Session, config_id: int, base: Path):
211
+ config = session.get(Config, config_id)
212
+ if config is None:
213
+ return None
214
+ config.atime = utcnow()
215
+ path = base / config.file
216
+ with open(path, 'rb') as f:
217
+ buf = f.read()
218
+ if config.content_type == 'application/pickle+lzma':
219
+ buf = lzma.decompress(buf)
220
+ buf = pickle.loads(buf)
221
+ return buf