QuLab 2.10.1__cp312-cp312-win_amd64.whl → 2.10.3__cp312-cp312-win_amd64.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/executor/cli.py +130 -46
- qulab/executor/load.py +66 -33
- qulab/executor/schedule.py +55 -22
- qulab/executor/utils.py +7 -7
- qulab/fun.cp312-win_amd64.pyd +0 -0
- qulab/sys/device/basedevice.py +24 -8
- qulab/sys/drivers/FakeInstrument.py +5 -5
- qulab/version.py +1 -1
- {qulab-2.10.1.dist-info → qulab-2.10.3.dist-info}/METADATA +1 -1
- {qulab-2.10.1.dist-info → qulab-2.10.3.dist-info}/RECORD +14 -14
- {qulab-2.10.1.dist-info → qulab-2.10.3.dist-info}/WHEEL +0 -0
- {qulab-2.10.1.dist-info → qulab-2.10.3.dist-info}/entry_points.txt +0 -0
- {qulab-2.10.1.dist-info → qulab-2.10.3.dist-info}/licenses/LICENSE +0 -0
- {qulab-2.10.1.dist-info → qulab-2.10.3.dist-info}/top_level.txt +0 -0
qulab/executor/cli.py
CHANGED
@@ -30,11 +30,16 @@ def boot(script_path):
|
|
30
30
|
|
31
31
|
|
32
32
|
@logger.catch(reraise=True)
|
33
|
-
def check_toplogy(workflow: WorkflowType,
|
33
|
+
def check_toplogy(workflow: WorkflowType,
|
34
|
+
code_path: str | Path,
|
35
|
+
veryfy_source_code: bool = True) -> dict:
|
34
36
|
graph = {}
|
35
37
|
try:
|
36
|
-
graphlib.TopologicalSorter(
|
37
|
-
|
38
|
+
graphlib.TopologicalSorter(
|
39
|
+
make_graph(workflow,
|
40
|
+
graph,
|
41
|
+
code_path,
|
42
|
+
veryfy_source_code=veryfy_source_code)).static_order()
|
38
43
|
except graphlib.CycleError as e:
|
39
44
|
logger.error(
|
40
45
|
f"Workflow {workflow.__workflow_id__} has a circular dependency: {e}"
|
@@ -164,10 +169,26 @@ def get(key, api):
|
|
164
169
|
help='Do not run dependents.')
|
165
170
|
@click.option('--retry', '-r', default=1, type=int, help='Retry times.')
|
166
171
|
@click.option('--freeze', is_flag=True, help='Freeze the config table.')
|
172
|
+
@click.option('--fail-fast',
|
173
|
+
'-f',
|
174
|
+
is_flag=True,
|
175
|
+
help='Fail immediately on first error.')
|
176
|
+
@click.option('--veryfy-source-code',
|
177
|
+
is_flag=True,
|
178
|
+
help='Veryfy the source code.')
|
167
179
|
@log_options
|
168
180
|
@command_option('run')
|
169
181
|
@async_command
|
170
|
-
async def run(workflow,
|
182
|
+
async def run(workflow,
|
183
|
+
code,
|
184
|
+
data,
|
185
|
+
api,
|
186
|
+
plot,
|
187
|
+
no_dependents,
|
188
|
+
retry,
|
189
|
+
freeze,
|
190
|
+
fail_fast,
|
191
|
+
veryfy_source_code=True):
|
171
192
|
"""
|
172
193
|
Run a workflow.
|
173
194
|
|
@@ -198,41 +219,73 @@ async def run(workflow, code, data, api, plot, no_dependents, retry, freeze):
|
|
198
219
|
code = Path(os.path.expanduser(code))
|
199
220
|
data = Path(os.path.expanduser(data))
|
200
221
|
|
201
|
-
wf = load_workflow(workflow, code)
|
202
|
-
check_toplogy(wf, code)
|
222
|
+
wf = load_workflow(workflow, code, veryfy_source_code=veryfy_source_code)
|
223
|
+
check_toplogy(wf, code, veryfy_source_code=veryfy_source_code)
|
203
224
|
|
204
225
|
for i in range(retry):
|
205
226
|
try:
|
206
227
|
if no_dependents:
|
207
228
|
if hasattr(wf, 'entries'):
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
229
|
+
exceptions = []
|
230
|
+
for entry in get_entries(
|
231
|
+
wf, code, veryfy_source_code=veryfy_source_code):
|
232
|
+
try:
|
233
|
+
await run_workflow(
|
234
|
+
entry,
|
235
|
+
code,
|
236
|
+
data,
|
237
|
+
plot=plot,
|
238
|
+
freeze=freeze,
|
239
|
+
veryfy_source_code=veryfy_source_code,
|
240
|
+
)
|
241
|
+
except Exception as e:
|
242
|
+
if fail_fast:
|
243
|
+
raise e
|
244
|
+
exceptions.append(e)
|
245
|
+
if any(exceptions):
|
246
|
+
raise exceptions[0]
|
214
247
|
else:
|
215
|
-
await run_workflow(
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
248
|
+
await run_workflow(
|
249
|
+
wf,
|
250
|
+
code,
|
251
|
+
data,
|
252
|
+
plot=plot,
|
253
|
+
freeze=freeze,
|
254
|
+
veryfy_source_code=veryfy_source_code,
|
255
|
+
)
|
220
256
|
else:
|
221
257
|
if hasattr(wf, 'entries'):
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
258
|
+
exceptions = []
|
259
|
+
for entry in get_entries(
|
260
|
+
wf, code, veryfy_source_code=veryfy_source_code):
|
261
|
+
try:
|
262
|
+
await maintain_workflow(
|
263
|
+
entry,
|
264
|
+
code,
|
265
|
+
data,
|
266
|
+
run=True,
|
267
|
+
plot=plot,
|
268
|
+
freeze=freeze,
|
269
|
+
fail_fast=fail_fast,
|
270
|
+
veryfy_source_code=veryfy_source_code,
|
271
|
+
)
|
272
|
+
except Exception as e:
|
273
|
+
if fail_fast:
|
274
|
+
raise e
|
275
|
+
exceptions.append(e)
|
276
|
+
if any(exceptions):
|
277
|
+
raise exceptions[0]
|
229
278
|
else:
|
230
|
-
await maintain_workflow(
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
279
|
+
await maintain_workflow(
|
280
|
+
wf,
|
281
|
+
code,
|
282
|
+
data,
|
283
|
+
run=True,
|
284
|
+
plot=plot,
|
285
|
+
freeze=freeze,
|
286
|
+
fail_fast=fail_fast,
|
287
|
+
veryfy_source_code=veryfy_source_code,
|
288
|
+
)
|
236
289
|
break
|
237
290
|
except CalibrationFailedError as e:
|
238
291
|
if i == retry - 1:
|
@@ -245,10 +298,24 @@ async def run(workflow, code, data, api, plot, no_dependents, retry, freeze):
|
|
245
298
|
@click.argument('workflow')
|
246
299
|
@click.option('--retry', '-r', default=1, type=int, help='Retry times.')
|
247
300
|
@click.option('--plot', '-p', is_flag=True, help='Plot the report.')
|
301
|
+
@click.option('--fail-fast',
|
302
|
+
'-f',
|
303
|
+
is_flag=True,
|
304
|
+
help='Fail immediately on first error.')
|
305
|
+
@click.option('--veryfy-source-code',
|
306
|
+
is_flag=True,
|
307
|
+
help='Veryfy the source code.')
|
248
308
|
@log_options
|
249
309
|
@command_option('maintain')
|
250
310
|
@async_command
|
251
|
-
async def maintain(workflow,
|
311
|
+
async def maintain(workflow,
|
312
|
+
code,
|
313
|
+
data,
|
314
|
+
api,
|
315
|
+
retry,
|
316
|
+
plot,
|
317
|
+
fail_fast,
|
318
|
+
veryfy_source_code=True):
|
252
319
|
"""
|
253
320
|
Maintain a workflow.
|
254
321
|
|
@@ -275,26 +342,43 @@ async def maintain(workflow, code, data, api, retry, plot):
|
|
275
342
|
code = Path(os.path.expanduser(code))
|
276
343
|
data = Path(os.path.expanduser(data))
|
277
344
|
|
278
|
-
wf = load_workflow(workflow, code)
|
279
|
-
check_toplogy(wf, code)
|
345
|
+
wf = load_workflow(workflow, code, veryfy_source_code=veryfy_source_code)
|
346
|
+
check_toplogy(wf, code, veryfy_source_code=veryfy_source_code)
|
280
347
|
|
281
348
|
for i in range(retry):
|
282
349
|
try:
|
283
350
|
if hasattr(wf, 'entries'):
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
351
|
+
exceptions = []
|
352
|
+
for entry in get_entries(
|
353
|
+
wf, code, veryfy_source_code=veryfy_source_code):
|
354
|
+
try:
|
355
|
+
await maintain_workflow(
|
356
|
+
entry,
|
357
|
+
code,
|
358
|
+
data,
|
359
|
+
run=False,
|
360
|
+
plot=plot,
|
361
|
+
freeze=False,
|
362
|
+
fail_fast=fail_fast,
|
363
|
+
veryfy_source_code=veryfy_source_code,
|
364
|
+
)
|
365
|
+
except Exception as e:
|
366
|
+
if fail_fast:
|
367
|
+
raise e
|
368
|
+
exceptions.append(e)
|
369
|
+
if any(exceptions):
|
370
|
+
raise exceptions[0]
|
291
371
|
else:
|
292
|
-
await maintain_workflow(
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
372
|
+
await maintain_workflow(
|
373
|
+
wf,
|
374
|
+
code,
|
375
|
+
data,
|
376
|
+
run=False,
|
377
|
+
plot=plot,
|
378
|
+
freeze=False,
|
379
|
+
fail_fast=fail_fast,
|
380
|
+
veryfy_source_code=veryfy_source_code,
|
381
|
+
)
|
298
382
|
break
|
299
383
|
except CalibrationFailedError as e:
|
300
384
|
if i == retry - 1:
|
qulab/executor/load.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
import atexit
|
2
2
|
import inspect
|
3
3
|
import pickle
|
4
|
+
import os
|
5
|
+
import time
|
6
|
+
import shutil
|
4
7
|
import tempfile
|
5
8
|
import warnings
|
6
9
|
from importlib.util import module_from_spec, spec_from_file_location
|
@@ -305,7 +308,8 @@ def find_unreferenced_workflows(path: str) -> list[str]:
|
|
305
308
|
|
306
309
|
def load_workflow_from_file(file_name: str,
|
307
310
|
base_path: str | Path,
|
308
|
-
package='workflows'
|
311
|
+
package='workflows',
|
312
|
+
veryfy_source_code: bool = True) -> WorkflowType:
|
309
313
|
base_path = Path(base_path)
|
310
314
|
path = Path(file_name)
|
311
315
|
if not (base_path / path).exists():
|
@@ -319,7 +323,8 @@ def load_workflow_from_file(file_name: str,
|
|
319
323
|
module.__source__ = source_code
|
320
324
|
|
321
325
|
if hasattr(module, 'entries'):
|
322
|
-
|
326
|
+
if veryfy_source_code:
|
327
|
+
verify_entries(module, base_path)
|
323
328
|
return module
|
324
329
|
|
325
330
|
if not hasattr(module, '__timeout__'):
|
@@ -327,9 +332,11 @@ def load_workflow_from_file(file_name: str,
|
|
327
332
|
|
328
333
|
if not hasattr(module, 'depends'):
|
329
334
|
module.depends = lambda: []
|
330
|
-
|
331
|
-
|
332
|
-
|
335
|
+
|
336
|
+
if veryfy_source_code:
|
337
|
+
verify_depends(module, base_path)
|
338
|
+
verify_calibrate_method(module)
|
339
|
+
verify_check_method(module)
|
333
340
|
|
334
341
|
return module
|
335
342
|
|
@@ -380,12 +387,14 @@ def _generate_target_file_path(template_path: str | Path, hash_str: str,
|
|
380
387
|
return Path('run') / path
|
381
388
|
|
382
389
|
|
383
|
-
def load_workflow_from_template(
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
390
|
+
def load_workflow_from_template(
|
391
|
+
template_path: str,
|
392
|
+
mapping: dict[str, str],
|
393
|
+
base_path: str | Path,
|
394
|
+
target_path: str | None = None,
|
395
|
+
package='workflows',
|
396
|
+
mtime: float = 0,
|
397
|
+
veryfy_source_code: bool = True) -> WorkflowType:
|
389
398
|
base_path = Path(base_path)
|
390
399
|
path = Path(template_path)
|
391
400
|
|
@@ -413,7 +422,8 @@ def load_workflow_from_template(template_path: str,
|
|
413
422
|
f"`{file}` already exists and is different from the new one generated from template `{template_path}`"
|
414
423
|
)
|
415
424
|
|
416
|
-
module = load_workflow_from_file(str(path), base_path, package
|
425
|
+
module = load_workflow_from_file(str(path), base_path, package,
|
426
|
+
veryfy_source_code)
|
417
427
|
module.__mtime__ = max(mtime, module.__mtime__)
|
418
428
|
if module.__source__ == content:
|
419
429
|
module.__source__ = template, mapping, str(template_path)
|
@@ -425,26 +435,31 @@ def load_workflow(workflow: str | tuple[str, dict],
|
|
425
435
|
base_path: str | Path,
|
426
436
|
package='workflows',
|
427
437
|
mtime: float = 0,
|
428
|
-
inject: dict | None = None
|
438
|
+
inject: dict | None = None,
|
439
|
+
veryfy_source_code: bool = True) -> WorkflowType:
|
429
440
|
if isinstance(workflow, tuple):
|
430
441
|
if len(workflow) == 2:
|
431
442
|
file_name, mapping = workflow
|
432
443
|
if inject is None:
|
433
444
|
w = load_workflow_from_template(file_name, mapping, base_path,
|
434
|
-
None, package, mtime
|
445
|
+
None, package, mtime,
|
446
|
+
veryfy_source_code)
|
435
447
|
else:
|
436
448
|
w = load_workflow_from_template(file_name, inject, base_path,
|
437
|
-
None, package, mtime
|
449
|
+
None, package, mtime,
|
450
|
+
veryfy_source_code)
|
438
451
|
elif len(workflow) == 3:
|
439
452
|
template_path, target_path, mapping = workflow
|
440
453
|
if inject is None:
|
441
454
|
w = load_workflow_from_template(template_path, mapping,
|
442
455
|
base_path, target_path,
|
443
|
-
package, mtime
|
456
|
+
package, mtime,
|
457
|
+
veryfy_source_code)
|
444
458
|
else:
|
445
459
|
w = load_workflow_from_template(template_path, inject,
|
446
460
|
base_path, target_path,
|
447
|
-
package, mtime
|
461
|
+
package, mtime,
|
462
|
+
veryfy_source_code)
|
448
463
|
else:
|
449
464
|
raise ValueError(f"Invalid workflow: {workflow}")
|
450
465
|
w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
|
@@ -454,7 +469,8 @@ def load_workflow(workflow: str | tuple[str, dict],
|
|
454
469
|
w = SetConfigWorkflow(key)
|
455
470
|
w.__workflow_id__ = workflow
|
456
471
|
else:
|
457
|
-
w = load_workflow_from_file(workflow, base_path, package
|
472
|
+
w = load_workflow_from_file(workflow, base_path, package,
|
473
|
+
veryfy_source_code)
|
458
474
|
w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
|
459
475
|
else:
|
460
476
|
raise TypeError(f"Invalid workflow: {workflow}")
|
@@ -462,11 +478,15 @@ def load_workflow(workflow: str | tuple[str, dict],
|
|
462
478
|
return w
|
463
479
|
|
464
480
|
|
465
|
-
def _load_workflow_list(workflow, lst, code_path):
|
481
|
+
def _load_workflow_list(workflow, lst, code_path, veryfy_source_code):
|
466
482
|
ret = []
|
467
483
|
for i, n in enumerate(lst):
|
468
484
|
try:
|
469
|
-
ret.append(
|
485
|
+
ret.append(
|
486
|
+
load_workflow(n,
|
487
|
+
code_path,
|
488
|
+
mtime=workflow.__mtime__,
|
489
|
+
veryfy_source_code=veryfy_source_code))
|
470
490
|
except TemplateKeyError as e:
|
471
491
|
msg, *_ = e.args
|
472
492
|
e.args = (
|
@@ -475,16 +495,18 @@ def _load_workflow_list(workflow, lst, code_path):
|
|
475
495
|
return ret
|
476
496
|
|
477
497
|
|
478
|
-
def get_dependents(workflow: WorkflowType,
|
479
|
-
|
498
|
+
def get_dependents(workflow: WorkflowType, code_path: str | Path,
|
499
|
+
veryfy_source_code: bool) -> list[WorkflowType]:
|
480
500
|
if callable(getattr(workflow, 'depends', None)):
|
481
501
|
if not can_call_without_args(workflow.depends):
|
482
502
|
raise AttributeError(
|
483
503
|
f'Workflow {workflow.__workflow_id__} "depends" function should not have any parameters'
|
484
504
|
)
|
485
|
-
return _load_workflow_list(workflow, workflow.depends(), code_path
|
505
|
+
return _load_workflow_list(workflow, workflow.depends(), code_path,
|
506
|
+
veryfy_source_code)
|
486
507
|
elif isinstance(getattr(workflow, 'depends', None), (list, tuple)):
|
487
|
-
return _load_workflow_list(workflow, workflow.depends, code_path
|
508
|
+
return _load_workflow_list(workflow, workflow.depends, code_path,
|
509
|
+
veryfy_source_code)
|
488
510
|
elif getattr(workflow, 'depends', None) is None:
|
489
511
|
return []
|
490
512
|
else:
|
@@ -493,16 +515,18 @@ def get_dependents(workflow: WorkflowType,
|
|
493
515
|
)
|
494
516
|
|
495
517
|
|
496
|
-
def get_entries(workflow: WorkflowType,
|
497
|
-
|
518
|
+
def get_entries(workflow: WorkflowType, code_path: str | Path,
|
519
|
+
veryfy_source_code: bool) -> list[WorkflowType]:
|
498
520
|
if callable(getattr(workflow, 'entries', None)):
|
499
521
|
if not can_call_without_args(workflow.entries):
|
500
522
|
raise AttributeError(
|
501
523
|
f'Workflow {workflow.__workflow_id__} "entries" function should not have any parameters'
|
502
524
|
)
|
503
|
-
return _load_workflow_list(workflow, workflow.entries(), code_path
|
525
|
+
return _load_workflow_list(workflow, workflow.entries(), code_path,
|
526
|
+
veryfy_source_code)
|
504
527
|
elif isinstance(getattr(workflow, 'entries', None), (list, tuple)):
|
505
|
-
return _load_workflow_list(workflow, workflow.entries, code_path
|
528
|
+
return _load_workflow_list(workflow, workflow.entries, code_path,
|
529
|
+
veryfy_source_code)
|
506
530
|
elif getattr(workflow, 'entries', None) is None:
|
507
531
|
return []
|
508
532
|
else:
|
@@ -511,19 +535,28 @@ def get_entries(workflow: WorkflowType,
|
|
511
535
|
)
|
512
536
|
|
513
537
|
|
514
|
-
def make_graph(workflow: WorkflowType,
|
538
|
+
def make_graph(workflow: WorkflowType,
|
539
|
+
graph: dict,
|
540
|
+
code_path: str | Path,
|
541
|
+
veryfy_source_code: bool = True):
|
515
542
|
if workflow.__workflow_id__ in graph:
|
516
543
|
return graph
|
517
544
|
graph[workflow.__workflow_id__] = []
|
518
545
|
|
519
546
|
if hasattr(workflow, 'entries'):
|
520
|
-
for w in get_entries(workflow, code_path):
|
547
|
+
for w in get_entries(workflow, code_path, veryfy_source_code):
|
521
548
|
graph[workflow.__workflow_id__].append(w.__workflow_id__)
|
522
|
-
make_graph(w,
|
549
|
+
make_graph(w,
|
550
|
+
graph=graph,
|
551
|
+
code_path=code_path,
|
552
|
+
veryfy_source_code=veryfy_source_code)
|
523
553
|
elif hasattr(workflow, 'depends'):
|
524
|
-
for w in get_dependents(workflow, code_path):
|
554
|
+
for w in get_dependents(workflow, code_path, veryfy_source_code):
|
525
555
|
graph[workflow.__workflow_id__].append(w.__workflow_id__)
|
526
|
-
make_graph(w,
|
556
|
+
make_graph(w,
|
557
|
+
graph=graph,
|
558
|
+
code_path=code_path,
|
559
|
+
veryfy_source_code=veryfy_source_code)
|
527
560
|
if graph[workflow.__workflow_id__] == []:
|
528
561
|
del graph[workflow.__workflow_id__]
|
529
562
|
|
qulab/executor/schedule.py
CHANGED
@@ -71,7 +71,7 @@ def veryfy_analyzed_report(report: Report, script: str, method: str):
|
|
71
71
|
|
72
72
|
|
73
73
|
def check_state(workflow: WorkflowType, code_path: str | Path,
|
74
|
-
state_path: str | Path) -> bool:
|
74
|
+
state_path: str | Path, veryfy_source_code: bool) -> bool:
|
75
75
|
"""
|
76
76
|
check state should report a pass if and only if the following are satisfied:
|
77
77
|
|
@@ -110,15 +110,15 @@ def check_state(workflow: WorkflowType, code_path: str | Path,
|
|
110
110
|
logger.debug(
|
111
111
|
f'check_state failed: "{workflow.__workflow_id__}" has bad data')
|
112
112
|
return False
|
113
|
-
for n in get_dependents(workflow, code_path):
|
113
|
+
for n in get_dependents(workflow, code_path, veryfy_source_code):
|
114
114
|
r = find_report(n.__workflow_id__, state_path)
|
115
115
|
if r is None or r.checked_time > report.checked_time:
|
116
116
|
logger.debug(
|
117
117
|
f'check_state failed: "{workflow.__workflow_id__}" has outdated dependencies'
|
118
118
|
)
|
119
119
|
return False
|
120
|
-
for n in get_dependents(workflow, code_path):
|
121
|
-
if not check_state(n, code_path, state_path):
|
120
|
+
for n in get_dependents(workflow, code_path, veryfy_source_code):
|
121
|
+
if not check_state(n, code_path, state_path, veryfy_source_code):
|
122
122
|
logger.debug(
|
123
123
|
f'check_state failed: "{workflow.__workflow_id__}" has bad dependencies'
|
124
124
|
)
|
@@ -126,7 +126,7 @@ def check_state(workflow: WorkflowType, code_path: str | Path,
|
|
126
126
|
return True
|
127
127
|
|
128
128
|
|
129
|
-
@logger.catch()
|
129
|
+
@logger.catch(reraise=False)
|
130
130
|
async def call_plot(node: WorkflowType, report: Report, check=False):
|
131
131
|
if hasattr(node, 'plot') and callable(node.plot):
|
132
132
|
if inspect.iscoroutinefunction(node.plot):
|
@@ -361,7 +361,8 @@ async def calibrate(workflow: WorkflowType, state_path: str | Path, plot: bool,
|
|
361
361
|
|
362
362
|
|
363
363
|
async def diagnose(workflow: WorkflowType, code_path: str | Path,
|
364
|
-
state_path: str | Path, plot: bool, session_id: str
|
364
|
+
state_path: str | Path, plot: bool, session_id: str,
|
365
|
+
fail_fast: bool, veryfy_source_code: bool):
|
365
366
|
'''
|
366
367
|
Returns: True if node or dependent recalibrated.
|
367
368
|
'''
|
@@ -379,10 +380,20 @@ async def diagnose(workflow: WorkflowType, code_path: str | Path,
|
|
379
380
|
if report.bad_data:
|
380
381
|
logger.debug(
|
381
382
|
f'"{workflow.__workflow_id__}": Bad data, diagnosing dependents')
|
382
|
-
recalibrated = [
|
383
|
-
|
384
|
-
|
385
|
-
|
383
|
+
recalibrated = []
|
384
|
+
exceptions = []
|
385
|
+
for n in get_dependents(workflow, code_path, veryfy_source_code):
|
386
|
+
try:
|
387
|
+
flag = await diagnose(n, code_path, state_path, plot,
|
388
|
+
session_id, fail_fast,
|
389
|
+
veryfy_source_code)
|
390
|
+
except Exception as e:
|
391
|
+
if fail_fast:
|
392
|
+
raise e
|
393
|
+
exceptions.append(e)
|
394
|
+
recalibrated.append(flag)
|
395
|
+
if any(exceptions):
|
396
|
+
raise exceptions[0]
|
386
397
|
if not any(recalibrated):
|
387
398
|
if report.bad_data:
|
388
399
|
raise CalibrationFailedError(
|
@@ -427,28 +438,41 @@ async def maintain(workflow: WorkflowType,
|
|
427
438
|
session_id: str | None = None,
|
428
439
|
run: bool = False,
|
429
440
|
plot: bool = False,
|
430
|
-
freeze: bool = False
|
441
|
+
freeze: bool = False,
|
442
|
+
fail_fast: bool = False,
|
443
|
+
veryfy_source_code: bool = True):
|
431
444
|
if session_id is None:
|
432
445
|
session_id = uuid.uuid4().hex
|
433
446
|
logger.debug(f'run "{workflow.__workflow_id__}"'
|
434
447
|
if run else f'maintain "{workflow.__workflow_id__}"')
|
435
448
|
# recursive maintain
|
436
|
-
|
449
|
+
exceptions = []
|
450
|
+
for n in get_dependents(workflow, code_path, veryfy_source_code):
|
437
451
|
logger.debug(
|
438
452
|
f'maintain "{n.__workflow_id__}" because it is depended by "{workflow.__workflow_id__}"'
|
439
453
|
)
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
454
|
+
try:
|
455
|
+
await maintain(n,
|
456
|
+
code_path,
|
457
|
+
state_path,
|
458
|
+
session_id,
|
459
|
+
run=False,
|
460
|
+
plot=plot,
|
461
|
+
freeze=freeze,
|
462
|
+
fail_fast=fail_fast,
|
463
|
+
veryfy_source_code=veryfy_source_code)
|
464
|
+
except Exception as e:
|
465
|
+
if fail_fast:
|
466
|
+
raise e
|
467
|
+
exceptions.append(e)
|
447
468
|
else:
|
448
469
|
logger.debug(
|
449
470
|
f'"{workflow.__workflow_id__}": All dependents maintained')
|
471
|
+
if any(exceptions):
|
472
|
+
raise exceptions[0]
|
450
473
|
# check_state
|
451
|
-
if check_state(workflow, code_path, state_path
|
474
|
+
if check_state(workflow, code_path, state_path,
|
475
|
+
veryfy_source_code) and not run:
|
452
476
|
logger.debug(
|
453
477
|
f'"{workflow.__workflow_id__}": In spec, no need to maintain')
|
454
478
|
return
|
@@ -462,14 +486,23 @@ async def maintain(workflow: WorkflowType,
|
|
462
486
|
elif report.bad_data:
|
463
487
|
logger.debug(
|
464
488
|
f'"{workflow.__workflow_id__}": Bad data, diagnosing dependents')
|
465
|
-
|
489
|
+
exceptions = []
|
490
|
+
for n in get_dependents(workflow, code_path, veryfy_source_code):
|
466
491
|
logger.debug(
|
467
492
|
f'diagnose "{n.__workflow_id__}" because of "{workflow.__workflow_id__}" bad data'
|
468
493
|
)
|
469
|
-
|
494
|
+
try:
|
495
|
+
await diagnose(n, code_path, state_path, plot, session_id,
|
496
|
+
fail_fast, veryfy_source_code)
|
497
|
+
except Exception as e:
|
498
|
+
if fail_fast:
|
499
|
+
raise e
|
500
|
+
exceptions.append(e)
|
470
501
|
else:
|
471
502
|
logger.debug(
|
472
503
|
f'"{workflow.__workflow_id__}": All dependents diagnosed')
|
504
|
+
if any(exceptions):
|
505
|
+
raise exceptions[0]
|
473
506
|
# calibrate
|
474
507
|
logger.debug(f'recalibrate "{workflow.__workflow_id__}"')
|
475
508
|
report = await calibrate(workflow, state_path, plot, session_id)
|
qulab/executor/utils.py
CHANGED
@@ -33,11 +33,11 @@ def dependent_tree(node: str, code_path: str | Path) -> dict[str, list[str]]:
|
|
33
33
|
|
34
34
|
|
35
35
|
def workflow_template(workflow: str, deps: list[str]) -> str:
|
36
|
-
return f"""
|
37
|
-
|
36
|
+
return f"""
|
38
37
|
import numpy as np
|
39
38
|
from loguru import logger
|
40
39
|
|
40
|
+
from qulab import VAR
|
41
41
|
from qulab.typing import Report
|
42
42
|
|
43
43
|
|
@@ -48,7 +48,7 @@ def depends():
|
|
48
48
|
return {deps!r}
|
49
49
|
|
50
50
|
|
51
|
-
def calibrate():
|
51
|
+
async def calibrate():
|
52
52
|
logger.info(f"running {workflow} ...")
|
53
53
|
|
54
54
|
# calibrate 是一个完整的校准实验,如power Rabi,Ramsey等。
|
@@ -64,7 +64,7 @@ def calibrate():
|
|
64
64
|
return x, y
|
65
65
|
|
66
66
|
|
67
|
-
def analyze(report: Report, history: Report | None = None) -> Report:
|
67
|
+
async def analyze(report: Report, history: Report | None = None) -> Report:
|
68
68
|
\"\"\"
|
69
69
|
分析校准结果。
|
70
70
|
|
@@ -95,7 +95,7 @@ def analyze(report: Report, history: Report | None = None) -> Report:
|
|
95
95
|
return report
|
96
96
|
|
97
97
|
|
98
|
-
def check():
|
98
|
+
async def check():
|
99
99
|
logger.info(f"checking {workflow} ...")
|
100
100
|
|
101
101
|
# check 是一个快速检查实验,用于检查校准是否过时。
|
@@ -112,7 +112,7 @@ def check():
|
|
112
112
|
return x, y
|
113
113
|
|
114
114
|
|
115
|
-
def check_analyze(report: Report, history: Report | None = None) -> Report:
|
115
|
+
async def check_analyze(report: Report, history: Report | None = None) -> Report:
|
116
116
|
\"\"\"
|
117
117
|
分析检查结果。
|
118
118
|
|
@@ -136,7 +136,7 @@ def check_analyze(report: Report, history: Report | None = None) -> Report:
|
|
136
136
|
return report
|
137
137
|
|
138
138
|
|
139
|
-
def oracle(report: Report,
|
139
|
+
async def oracle(report: Report,
|
140
140
|
history: Report | None = None,
|
141
141
|
system_state: dict[str:str] | None = None) -> Report:
|
142
142
|
\"\"\"
|
qulab/fun.cp312-win_amd64.pyd
CHANGED
Binary file
|
qulab/sys/device/basedevice.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import copy
|
1
2
|
import itertools
|
2
3
|
import logging
|
3
4
|
import re
|
@@ -127,7 +128,8 @@ class BaseDevice(metaclass=DeviceMeta):
|
|
127
128
|
__log__ = None
|
128
129
|
|
129
130
|
def __init__(self, address: str = None, **options):
|
130
|
-
self.
|
131
|
+
self._state = {}
|
132
|
+
self._status = "NOT CONNECTED"
|
131
133
|
self.address = address
|
132
134
|
self.options = options
|
133
135
|
|
@@ -144,28 +146,40 @@ class BaseDevice(metaclass=DeviceMeta):
|
|
144
146
|
f"{self.__class__.__module__}.{self.__class__.__name__}")
|
145
147
|
return self.__log__
|
146
148
|
|
149
|
+
@property
|
150
|
+
def state(self):
|
151
|
+
return copy.deepcopy(self._state)
|
152
|
+
|
153
|
+
def get_state(self, key: str, default=None) -> Any:
|
154
|
+
return self._state.get(key, default)
|
155
|
+
|
156
|
+
@property
|
157
|
+
def status(self):
|
158
|
+
return self._status
|
159
|
+
|
147
160
|
def open(self) -> None:
|
148
|
-
|
161
|
+
self._status = "CONNECTED"
|
149
162
|
|
150
163
|
def close(self) -> None:
|
151
|
-
|
164
|
+
self._status = "NOT CONNECTED"
|
165
|
+
self._state.clear()
|
152
166
|
|
153
167
|
def reset(self) -> None:
|
154
|
-
self.
|
168
|
+
self._state.clear()
|
155
169
|
|
156
170
|
def get(self, key: str, default: Any = None) -> Any:
|
157
171
|
self.log.info(f'Get {key!r}')
|
158
172
|
if key in self.__get_actions__:
|
159
173
|
result = self.__get_actions__[key](self)
|
160
|
-
self.
|
174
|
+
self._state[key] = result
|
161
175
|
return result
|
162
176
|
else:
|
163
|
-
return self.
|
177
|
+
return self._state.get(key, default)
|
164
178
|
|
165
179
|
def set(self, key: str, value: Any = None) -> None:
|
166
180
|
self.log.info(f'Set {key!r} = {value!r}')
|
167
181
|
self.__set_actions__[key](self, value)
|
168
|
-
self.
|
182
|
+
self._state[key] = value
|
169
183
|
|
170
184
|
def post(self, key: str, value: Any = None) -> Any:
|
171
185
|
self.log.info(f'Post {key!r} = {value!r}')
|
@@ -174,7 +188,7 @@ class BaseDevice(metaclass=DeviceMeta):
|
|
174
188
|
def delete(self, key: str) -> None:
|
175
189
|
self.log.info(f'Delete {key!r}')
|
176
190
|
self.__delete_actions__[key](self)
|
177
|
-
del self.
|
191
|
+
del self._state[key]
|
178
192
|
|
179
193
|
def __repr__(self) -> str:
|
180
194
|
return f'{self.__class__.__name__}({self.address!r})'
|
@@ -193,8 +207,10 @@ class VisaDevice(BaseDevice):
|
|
193
207
|
rm = pyvisa.ResourceManager()
|
194
208
|
self.resource = rm.open_resource(self.address, **kwds)
|
195
209
|
self.errors = []
|
210
|
+
super().open()
|
196
211
|
|
197
212
|
def close(self) -> None:
|
213
|
+
super().close()
|
198
214
|
self.resource.close()
|
199
215
|
|
200
216
|
def reset(self) -> None:
|
@@ -35,7 +35,7 @@ class Device(BaseDevice):
|
|
35
35
|
|
36
36
|
# @get('${channel}.Vpp', channel=exclude(['M1', 'M2']))
|
37
37
|
# def get_voltage(self, channel: str, default=0.0) -> float:
|
38
|
-
# return self.
|
38
|
+
# return self._state.get(f'{channel}.Vpp', default)
|
39
39
|
|
40
40
|
@set('${channel}.Offset', channel=exclude(['M1', 'M2']))
|
41
41
|
def set_offset(
|
@@ -47,7 +47,7 @@ class Device(BaseDevice):
|
|
47
47
|
|
48
48
|
# @get('${channel}.Offset', channel=exclude(['M1', 'M2']))
|
49
49
|
# def get_offset(self, channel: str, default=0.0) -> float:
|
50
|
-
# return self.
|
50
|
+
# return self._state.get(f'{channel}.Offset', default)
|
51
51
|
|
52
52
|
@set('${channel}.Waveform', channel=exclude(['M1', 'M2']))
|
53
53
|
def set_waveform(self, value, channel: str) -> None:
|
@@ -55,7 +55,7 @@ class Device(BaseDevice):
|
|
55
55
|
|
56
56
|
# @get('${channel}.Waveform', channel=exclude(['M1', 'M2']))
|
57
57
|
# def get_waveform(self, channel: str, default=None) -> str:
|
58
|
-
# return self.
|
58
|
+
# return self._state.get(f'{channel}.Waveform', default)
|
59
59
|
|
60
60
|
@set('${channel}.Size', channel=['M1', 'M2'])
|
61
61
|
def set_size(self, value, channel: str) -> None:
|
@@ -63,6 +63,6 @@ class Device(BaseDevice):
|
|
63
63
|
|
64
64
|
@get('${channel}.Trace', channel=['M1', 'M2'])
|
65
65
|
def get_random_data(self, channel: str) -> np.ndarray:
|
66
|
-
size = self.
|
67
|
-
shots = self.
|
66
|
+
size = self._state.get(f'{channel}.Size', 1024)
|
67
|
+
shots = self._state.get(f'{channel}.Shots', 128)
|
68
68
|
return np.random.randn(shots, size)
|
qulab/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "2.10.
|
1
|
+
__version__ = "2.10.3"
|
@@ -1,23 +1,23 @@
|
|
1
1
|
qulab/__init__.py,sha256=ulapyzt9DsDgT68EEbzuhniofHb512pNTFc-Sxfrr8Y,375
|
2
2
|
qulab/__main__.py,sha256=FL4YsGZL1jEtmcPc5WbleArzhOHLMsWl7OH3O-1d1ss,72
|
3
3
|
qulab/dicttree.py,sha256=ZoSJVWK4VMqfzj42gPb_n5RqLlM6K1Me0WmLIfLEYf8,14195
|
4
|
-
qulab/fun.cp312-win_amd64.pyd,sha256=
|
4
|
+
qulab/fun.cp312-win_amd64.pyd,sha256=iEgqMzxZAoOtwAFLdeIk4Fg7fy9CzlhHAZ2tTNLd7A0,32256
|
5
5
|
qulab/typing.py,sha256=PRtwbCHWY2ROKK8GHq4Bo8llXrIGo6xC73DrQf7S9os,71
|
6
6
|
qulab/utils.py,sha256=65N2Xj7kqRsQ4epoLNY6tL-i5ts6Wk8YuJYee3Te6zI,3077
|
7
|
-
qulab/version.py,sha256=
|
7
|
+
qulab/version.py,sha256=XIYhk5PoReQgD4uSOa1eXmCSwNRY66HHGSW3_z-nibk,22
|
8
8
|
qulab/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
9
|
qulab/cli/commands.py,sha256=ZTs32yQjvwPIsFjXYWNr4KqEvly0NISIqyh--96qCAY,627
|
10
10
|
qulab/cli/config.py,sha256=A3UnyaRtiofpokyzYkJnUdwzcsYX7H-xZvBVduIOSdg,5617
|
11
11
|
qulab/cli/decorators.py,sha256=UmJab1o-XH-8DVS_LZFGY2SPjyVgZ1QrJwkauTZDUik,626
|
12
12
|
qulab/executor/__init__.py,sha256=LosPzOMaljSZY1thy_Fxtbrgq7uubJszMABEB7oM7tU,101
|
13
13
|
qulab/executor/analyze.py,sha256=VoSthE2RTarY6Wj3QFKh4FqReMYL7djSAyuitgHs5K0,5837
|
14
|
-
qulab/executor/cli.py,sha256=
|
15
|
-
qulab/executor/load.py,sha256=
|
16
|
-
qulab/executor/schedule.py,sha256=
|
14
|
+
qulab/executor/cli.py,sha256=ylsKlr1qiloTVPhF2xoTxbz5DD98VQkRXQpvmiVJdxY,15047
|
15
|
+
qulab/executor/load.py,sha256=0-EtO4dkP6jB1vsgQlniv-OtPH1AZLjbtuvaWHsgbPs,20335
|
16
|
+
qulab/executor/schedule.py,sha256=1YWz2d5YLmnEpXGX1aVroE0SOKdU28WwHGEioxBpTKQ,21411
|
17
17
|
qulab/executor/storage.py,sha256=8K73KGLAVgchJdtd4rKHXkr1CQOJORWH-Gi57w8IYsw,21081
|
18
18
|
qulab/executor/template.py,sha256=dKQM3IlADdTi9qp0fnOYjyycRNEl7KeSCBZhwbmP8bQ,10828
|
19
19
|
qulab/executor/transform.py,sha256=rk4CLIKVjGRaFzi5FVSgadUxAKKVLSopEHZCaAzDwDg,3435
|
20
|
-
qulab/executor/utils.py,sha256=
|
20
|
+
qulab/executor/utils.py,sha256=3OLRMBJu-1t78BeuZs4fv4jioEXnRNygaPnSoibzfgs,6405
|
21
21
|
qulab/monitor/__init__.py,sha256=xEVDkJF8issrsDeLqQmDsvtRmrf-UiViFcGTWuzdlFU,43
|
22
22
|
qulab/monitor/__main__.py,sha256=k2H1H5Zf9LLXTDLISJkbikLH-z0f1e5i5i6wXXYPOrE,105
|
23
23
|
qulab/monitor/config.py,sha256=y_5StMkdrbZO1ziyKBrvIkB7Jclp9RCPK1QbsOhCxnY,785
|
@@ -62,10 +62,10 @@ qulab/sys/chat.py,sha256=t7wSlREOQ_Ke0pO9bfpkik9wY1qeiTbRF1dial9IbII,23137
|
|
62
62
|
qulab/sys/ipy_events.py,sha256=rGjsApv58_Gwd_TH6ecPnyXK34_ZYZsaX8tk32ozLGY,3014
|
63
63
|
qulab/sys/progress.py,sha256=2iYskzRdOC2EM4wtbzm0u1kovjGrF_A0W1kCxbftyzE,5540
|
64
64
|
qulab/sys/device/__init__.py,sha256=ZZxPJ9_MHModvghQoZOSWjIdeo3vhg2TJETDdrwQvkU,152
|
65
|
-
qulab/sys/device/basedevice.py,sha256=
|
65
|
+
qulab/sys/device/basedevice.py,sha256=mdKUzu0TpHIVMVQ4y7eADtlvGjFAjO-_NSEucLyVv3o,7450
|
66
66
|
qulab/sys/device/loader.py,sha256=RyUoykxGyfn-k22NWo-B06_AK_TMH8to4FzSkODLJHM,2583
|
67
67
|
qulab/sys/device/utils.py,sha256=5uqGOcaZlubCIw7gsknpB-tiFQyq8y6ebQxHcouQGUs,2393
|
68
|
-
qulab/sys/drivers/FakeInstrument.py,sha256=
|
68
|
+
qulab/sys/drivers/FakeInstrument.py,sha256=w5ItiveUppBoH2egZtfS6u7_heORA98iUdYeMjQkx8A,2474
|
69
69
|
qulab/sys/drivers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
70
70
|
qulab/sys/net/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
71
71
|
qulab/sys/net/bencoder.py,sha256=tsMjTG-E_EQokfWrZgJvxO3dTGaM6NOVzPPKsify23A,5164
|
@@ -99,9 +99,9 @@ qulab/visualization/plot_seq.py,sha256=Uo1-dB1YE9IN_A9tuaOs9ZG3S5dKDQ_l98iD2Wbxp
|
|
99
99
|
qulab/visualization/qdat.py,sha256=HubXFu4nfcA7iUzghJGle1C86G6221hicLR0b-GqhKQ,5887
|
100
100
|
qulab/visualization/rot3d.py,sha256=jGHJcqj1lEWBUV-W4GUGONGacqjrYvuFoFCwPse5h1Y,757
|
101
101
|
qulab/visualization/widgets.py,sha256=HcYwdhDtLreJiYaZuN3LfofjJmZcLwjMfP5aasebgDo,3266
|
102
|
-
qulab-2.10.
|
103
|
-
qulab-2.10.
|
104
|
-
qulab-2.10.
|
105
|
-
qulab-2.10.
|
106
|
-
qulab-2.10.
|
107
|
-
qulab-2.10.
|
102
|
+
qulab-2.10.3.dist-info/licenses/LICENSE,sha256=b4NRQ-GFVpJMT7RuExW3NwhfbrYsX7AcdB7Gudok-fs,1086
|
103
|
+
qulab-2.10.3.dist-info/METADATA,sha256=xpIJzNygtIMGd9wGlhVT99BwC_RTRsGEhn7YkWFLvdk,3860
|
104
|
+
qulab-2.10.3.dist-info/WHEEL,sha256=ovhA9_Ei_7ok2fAych90j-feDV4goiAxbO7REePtvw0,101
|
105
|
+
qulab-2.10.3.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
|
106
|
+
qulab-2.10.3.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
|
107
|
+
qulab-2.10.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|