QuLab 2.4.9__cp312-cp312-macosx_10_13_universal2.whl → 2.4.11__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.9.dist-info → QuLab-2.4.11.dist-info}/METADATA +1 -1
- {QuLab-2.4.9.dist-info → QuLab-2.4.11.dist-info}/RECORD +12 -12
- qulab/__main__.py +3 -1
- qulab/executor/__main__.py +122 -14
- qulab/executor/load.py +73 -11
- qulab/executor/schedule.py +138 -83
- qulab/fun.cpython-312-darwin.so +0 -0
- qulab/version.py +1 -1
- {QuLab-2.4.9.dist-info → QuLab-2.4.11.dist-info}/LICENSE +0 -0
- {QuLab-2.4.9.dist-info → QuLab-2.4.11.dist-info}/WHEEL +0 -0
- {QuLab-2.4.9.dist-info → QuLab-2.4.11.dist-info}/entry_points.txt +0 -0
- {QuLab-2.4.9.dist-info → QuLab-2.4.11.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
qulab/__init__.py,sha256=P-Mx2p4TVmL91SoxoeXcj8Qm0x4xUf5Q_FLk0Yc_gIQ,138
|
|
2
|
-
qulab/__main__.py,sha256=
|
|
2
|
+
qulab/__main__.py,sha256=ThCRooT22_qpwFtbAvVOyHzpBCuGlfDVkXBXvApzgps,597
|
|
3
3
|
qulab/dicttree.py,sha256=tRRMpGZYVOLw0TEByE3_2Ss8FdOmzuGL9e1DWbs8qoY,13684
|
|
4
|
-
qulab/fun.cpython-312-darwin.so,sha256=
|
|
5
|
-
qulab/version.py,sha256=
|
|
4
|
+
qulab/fun.cpython-312-darwin.so,sha256=txyNCW6qAn-UcLF753SolMzzw_uVfiqYAP0UBOyKjDQ,126864
|
|
5
|
+
qulab/version.py,sha256=KmwNyeRKbHsXW7QICo2toEfuVBo1wkujOcbChcYf2u4,22
|
|
6
6
|
qulab/executor/__init__.py,sha256=LosPzOMaljSZY1thy_Fxtbrgq7uubJszMABEB7oM7tU,101
|
|
7
|
-
qulab/executor/__main__.py,sha256=
|
|
8
|
-
qulab/executor/load.py,sha256=
|
|
9
|
-
qulab/executor/schedule.py,sha256=
|
|
7
|
+
qulab/executor/__main__.py,sha256=qgZcdmx1wBZInHf6ionpuuZytIJnaDje9WRv-a8o2OM,5870
|
|
8
|
+
qulab/executor/load.py,sha256=GSRrP2UXOmTgg5VDTmZy_y4xXk91H3Sd5SEX-tOEBqY,8689
|
|
9
|
+
qulab/executor/schedule.py,sha256=NVbBQbZ4ynoff3p9vNrt317PFRi-N42oHZHwWaXBeK0,11817
|
|
10
10
|
qulab/executor/storage.py,sha256=M66Q5_Uc5MMfc_QAuuaaexwAz7wxBPMkeleB5nRpQmI,4621
|
|
11
11
|
qulab/executor/transform.py,sha256=inaOn6eqCs22ZZ0xAQl8s8YCoEACaXSwFNNu7jqdwAk,2148
|
|
12
12
|
qulab/executor/utils.py,sha256=n3uCSKh-qdDFFeNvOpj7_es2_B4AaC-ASAlV9gPmSO0,3086
|
|
@@ -89,9 +89,9 @@ qulab/visualization/plot_seq.py,sha256=UWTS6p9nfX_7B8ehcYo6UnSTUCjkBsNU9jiOeW2ca
|
|
|
89
89
|
qulab/visualization/qdat.py,sha256=ZeevBYWkzbww4xZnsjHhw7wRorJCBzbG0iEu-XQB4EA,5735
|
|
90
90
|
qulab/visualization/rot3d.py,sha256=lMrEJlRLwYe6NMBlGkKYpp_V9CTipOAuDy6QW_cQK00,734
|
|
91
91
|
qulab/visualization/widgets.py,sha256=6KkiTyQ8J-ei70LbPQZAK35wjktY47w2IveOa682ftA,3180
|
|
92
|
-
QuLab-2.4.
|
|
93
|
-
QuLab-2.4.
|
|
94
|
-
QuLab-2.4.
|
|
95
|
-
QuLab-2.4.
|
|
96
|
-
QuLab-2.4.
|
|
97
|
-
QuLab-2.4.
|
|
92
|
+
QuLab-2.4.11.dist-info/LICENSE,sha256=PRzIKxZtpQcH7whTG6Egvzl1A0BvnSf30tmR2X2KrpA,1065
|
|
93
|
+
QuLab-2.4.11.dist-info/METADATA,sha256=8hTUY97KDuPwJvWPcsWRwnRKfu-nF2iV6-vxcr4kqmg,3699
|
|
94
|
+
QuLab-2.4.11.dist-info/WHEEL,sha256=iDXcyuxg-66TzzqHGH-kgw_HJdaJE_1RHznrvPNCSNs,115
|
|
95
|
+
QuLab-2.4.11.dist-info/entry_points.txt,sha256=b0v1GXOwmxY-nCCsPN_rHZZvY9CtTbWqrGj8u1m8yHo,45
|
|
96
|
+
QuLab-2.4.11.dist-info/top_level.txt,sha256=3T886LbAsbvjonu_TDdmgxKYUn939BVTRPxPl9r4cEg,6
|
|
97
|
+
QuLab-2.4.11.dist-info/RECORD,,
|
qulab/__main__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
|
-
from .executor.__main__ import create, maintain, run
|
|
3
|
+
from .executor.__main__ import create, get, maintain, run, set
|
|
4
4
|
from .monitor.__main__ import main as monitor
|
|
5
5
|
from .scan.server import server
|
|
6
6
|
from .sys.net.cli import dht
|
|
@@ -25,6 +25,8 @@ cli.add_command(server)
|
|
|
25
25
|
cli.add_command(maintain)
|
|
26
26
|
cli.add_command(run)
|
|
27
27
|
cli.add_command(create)
|
|
28
|
+
cli.add_command(set)
|
|
29
|
+
cli.add_command(get)
|
|
28
30
|
|
|
29
31
|
if __name__ == '__main__':
|
|
30
32
|
cli()
|
qulab/executor/__main__.py
CHANGED
|
@@ -1,15 +1,68 @@
|
|
|
1
|
+
import functools
|
|
1
2
|
import importlib
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
2
5
|
from pathlib import Path
|
|
3
6
|
|
|
4
7
|
import click
|
|
8
|
+
from loguru import logger
|
|
5
9
|
|
|
6
|
-
from .load import find_unreferenced_workflows
|
|
10
|
+
from .load import find_unreferenced_workflows, load_workflow
|
|
7
11
|
from .schedule import maintain as maintain_workflow
|
|
8
12
|
from .schedule import run as run_workflow
|
|
9
13
|
from .transform import set_config_api
|
|
10
14
|
from .utils import workflow_template
|
|
11
15
|
|
|
12
16
|
|
|
17
|
+
def load_config():
|
|
18
|
+
import yaml
|
|
19
|
+
|
|
20
|
+
config_paths = [
|
|
21
|
+
Path.home() / ".myapp/config.yaml", # 用户主目录
|
|
22
|
+
Path("config.yaml") # 当前目录
|
|
23
|
+
]
|
|
24
|
+
for path in config_paths:
|
|
25
|
+
if path.exists():
|
|
26
|
+
with open(path) as f:
|
|
27
|
+
return yaml.safe_load(f)
|
|
28
|
+
return {"defaults": {"log": "default.log", "debug": False}}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_config_value(option_name):
|
|
32
|
+
# 1. 尝试从环境变量读取
|
|
33
|
+
env_value = os.environ.get(f"MYAPP_{option_name.upper()}")
|
|
34
|
+
if env_value:
|
|
35
|
+
return env_value
|
|
36
|
+
|
|
37
|
+
# 2. 尝试从配置文件读取
|
|
38
|
+
config = load_config()
|
|
39
|
+
return config["defaults"].get(option_name)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def log_options(func):
|
|
43
|
+
|
|
44
|
+
@click.option("--debug", is_flag=True, help="Enable debug mode.")
|
|
45
|
+
@click.option("--log", type=str, help="Log file path.")
|
|
46
|
+
@functools.wraps(func) # 保持函数元信息
|
|
47
|
+
def wrapper(*args, log=None, debug=False, **kwargs):
|
|
48
|
+
if log is None and not debug:
|
|
49
|
+
logger.remove()
|
|
50
|
+
logger.add(sys.stderr, level='INFO')
|
|
51
|
+
elif log is None and debug:
|
|
52
|
+
logger.remove()
|
|
53
|
+
logger.add(sys.stderr, level='DEBUG')
|
|
54
|
+
elif log is not None and not debug:
|
|
55
|
+
logger.configure(handlers=[dict(sink=log, level='INFO')])
|
|
56
|
+
elif log is not None and debug:
|
|
57
|
+
logger.configure(handlers=[
|
|
58
|
+
dict(sink=log, level='DEBUG'),
|
|
59
|
+
dict(sink=sys.stderr, level='DEBUG')
|
|
60
|
+
])
|
|
61
|
+
return func(*args, **kwargs)
|
|
62
|
+
|
|
63
|
+
return wrapper
|
|
64
|
+
|
|
65
|
+
|
|
13
66
|
@click.group()
|
|
14
67
|
def cli():
|
|
15
68
|
pass
|
|
@@ -17,7 +70,7 @@ def cli():
|
|
|
17
70
|
|
|
18
71
|
@click.command()
|
|
19
72
|
@click.argument('workflow')
|
|
20
|
-
@click.option('--code', '-c', default=None)
|
|
73
|
+
@click.option('--code', '-c', default=None, help='The path of the code.')
|
|
21
74
|
def create(workflow, code):
|
|
22
75
|
"""
|
|
23
76
|
Create a new workflow file.
|
|
@@ -26,6 +79,7 @@ def create(workflow, code):
|
|
|
26
79
|
code = Path.cwd()
|
|
27
80
|
|
|
28
81
|
fname = Path(code) / f'{workflow}'
|
|
82
|
+
fname = Path(os.path.expanduser(fname))
|
|
29
83
|
if fname.exists():
|
|
30
84
|
click.echo(f'{workflow} already exists')
|
|
31
85
|
return
|
|
@@ -38,13 +92,50 @@ def create(workflow, code):
|
|
|
38
92
|
click.echo(f'{workflow} created')
|
|
39
93
|
|
|
40
94
|
|
|
95
|
+
@click.command()
|
|
96
|
+
@click.argument('key')
|
|
97
|
+
@click.argument('value', type=str)
|
|
98
|
+
@click.option('--api', '-a', default=None, help='The modlule name of the api.')
|
|
99
|
+
def set(key, value, api):
|
|
100
|
+
"""
|
|
101
|
+
Set a config.
|
|
102
|
+
"""
|
|
103
|
+
from . import transform
|
|
104
|
+
if api is not None:
|
|
105
|
+
api = importlib.import_module(api)
|
|
106
|
+
set_config_api(api.query_config, api.update_config)
|
|
107
|
+
try:
|
|
108
|
+
value = eval(value)
|
|
109
|
+
except:
|
|
110
|
+
pass
|
|
111
|
+
transform.update_config({key: value})
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@click.command()
|
|
115
|
+
@click.argument('key')
|
|
116
|
+
@click.option('--api', '-a', default=None, help='The modlule name of the api.')
|
|
117
|
+
def get(key, api):
|
|
118
|
+
"""
|
|
119
|
+
Get a config.
|
|
120
|
+
"""
|
|
121
|
+
from . import transform
|
|
122
|
+
if api is not None:
|
|
123
|
+
api = importlib.import_module(api)
|
|
124
|
+
set_config_api(api.query_config, api.update_config)
|
|
125
|
+
click.echo(transform.query_config(key))
|
|
126
|
+
|
|
127
|
+
|
|
41
128
|
@click.command()
|
|
42
129
|
@click.argument('workflow')
|
|
43
|
-
@click.option('--code', '-c', default=None)
|
|
44
|
-
@click.option('--data', '-d', default=None)
|
|
45
|
-
@click.option('--api', '-a', default=None)
|
|
46
|
-
@click.option('--plot', '-p', is_flag=True)
|
|
47
|
-
@click.option('--no-dependents',
|
|
130
|
+
@click.option('--code', '-c', default=None, help='The path of the code.')
|
|
131
|
+
@click.option('--data', '-d', default=None, help='The path of the data.')
|
|
132
|
+
@click.option('--api', '-a', default=None, help='The modlule name of the api.')
|
|
133
|
+
@click.option('--plot', '-p', is_flag=True, help='Plot the result.')
|
|
134
|
+
@click.option('--no-dependents',
|
|
135
|
+
'-n',
|
|
136
|
+
is_flag=True,
|
|
137
|
+
help='Do not run dependents.')
|
|
138
|
+
@log_options
|
|
48
139
|
def run(workflow, code, data, api, plot, no_dependents):
|
|
49
140
|
"""
|
|
50
141
|
Run a workflow.
|
|
@@ -57,18 +148,26 @@ def run(workflow, code, data, api, plot, no_dependents):
|
|
|
57
148
|
if data is None:
|
|
58
149
|
data = Path(code) / 'logs'
|
|
59
150
|
|
|
151
|
+
code = Path(os.path.expanduser(code))
|
|
152
|
+
data = Path(os.path.expanduser(data))
|
|
153
|
+
|
|
60
154
|
if no_dependents:
|
|
61
|
-
run_workflow(workflow, code, data, plot=plot)
|
|
155
|
+
run_workflow(load_workflow(workflow, code), code, data, plot=plot)
|
|
62
156
|
else:
|
|
63
|
-
maintain_workflow(workflow, code,
|
|
157
|
+
maintain_workflow(load_workflow(workflow, code),
|
|
158
|
+
code,
|
|
159
|
+
data,
|
|
160
|
+
run=True,
|
|
161
|
+
plot=plot)
|
|
64
162
|
|
|
65
163
|
|
|
66
164
|
@click.command()
|
|
67
165
|
@click.argument('workflow')
|
|
68
|
-
@click.option('--code', '-c', default=None)
|
|
69
|
-
@click.option('--data', '-d', default=None)
|
|
70
|
-
@click.option('--api', '-a', default=None)
|
|
71
|
-
@click.option('--plot', '-p', is_flag=True)
|
|
166
|
+
@click.option('--code', '-c', default=None, help='The path of the code.')
|
|
167
|
+
@click.option('--data', '-d', default=None, help='The path of the data.')
|
|
168
|
+
@click.option('--api', '-a', default=None, help='The modlule name of the api.')
|
|
169
|
+
@click.option('--plot', '-p', is_flag=True, help='Plot the result.')
|
|
170
|
+
@log_options
|
|
72
171
|
def maintain(workflow, code, data, api, plot):
|
|
73
172
|
"""
|
|
74
173
|
Maintain a workflow.
|
|
@@ -81,12 +180,21 @@ def maintain(workflow, code, data, api, plot):
|
|
|
81
180
|
if data is None:
|
|
82
181
|
data = Path(code) / 'logs'
|
|
83
182
|
|
|
84
|
-
|
|
183
|
+
code = Path(os.path.expanduser(code))
|
|
184
|
+
data = Path(os.path.expanduser(data))
|
|
185
|
+
|
|
186
|
+
maintain_workflow(load_workflow(workflow, code),
|
|
187
|
+
code,
|
|
188
|
+
data,
|
|
189
|
+
run=False,
|
|
190
|
+
plot=plot)
|
|
85
191
|
|
|
86
192
|
|
|
87
193
|
cli.add_command(maintain)
|
|
88
194
|
cli.add_command(run)
|
|
89
195
|
cli.add_command(create)
|
|
196
|
+
cli.add_command(set)
|
|
197
|
+
cli.add_command(get)
|
|
90
198
|
|
|
91
199
|
if __name__ == '__main__':
|
|
92
200
|
cli()
|
qulab/executor/load.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import hashlib
|
|
1
2
|
import inspect
|
|
3
|
+
import pickle
|
|
4
|
+
import re
|
|
5
|
+
import string
|
|
2
6
|
import warnings
|
|
3
7
|
from importlib.util import module_from_spec, spec_from_file_location
|
|
4
8
|
from pathlib import Path
|
|
5
9
|
from types import ModuleType
|
|
6
10
|
|
|
7
|
-
import
|
|
11
|
+
from loguru import logger
|
|
8
12
|
|
|
9
13
|
from .storage import Result
|
|
10
14
|
|
|
@@ -124,12 +128,15 @@ def find_unreferenced_workflows(path: str) -> list[str]:
|
|
|
124
128
|
for file_path in root.rglob("*.py"):
|
|
125
129
|
if file_path.name == "__init__.py":
|
|
126
130
|
continue
|
|
131
|
+
if file_path.name.endswith("_template.py") or re.match(
|
|
132
|
+
r'.*_tmp_[0-9a-fA-F]{8}.py', file_path.name):
|
|
133
|
+
continue
|
|
127
134
|
try:
|
|
128
135
|
rel_path = file_path.relative_to(root)
|
|
129
136
|
except ValueError:
|
|
130
137
|
continue
|
|
131
138
|
|
|
132
|
-
module =
|
|
139
|
+
module = load_workflow_from_module(str(rel_path), root)
|
|
133
140
|
|
|
134
141
|
if is_workflow(module):
|
|
135
142
|
rel_str = str(rel_path)
|
|
@@ -140,7 +147,7 @@ def find_unreferenced_workflows(path: str) -> list[str]:
|
|
|
140
147
|
|
|
141
148
|
# Check dependencies for each workflow module
|
|
142
149
|
for rel_str in workflows:
|
|
143
|
-
module =
|
|
150
|
+
module = load_workflow_from_module(rel_str, root)
|
|
144
151
|
|
|
145
152
|
depends_func = getattr(module, "depends", None)
|
|
146
153
|
if depends_func and callable(depends_func):
|
|
@@ -150,17 +157,15 @@ def find_unreferenced_workflows(path: str) -> list[str]:
|
|
|
150
157
|
)
|
|
151
158
|
continue
|
|
152
159
|
try:
|
|
153
|
-
depends_list = depends_func()
|
|
160
|
+
depends_list = depends_func()[0]
|
|
154
161
|
except Exception as e:
|
|
155
162
|
warnings.warn(f"Error calling depends() in {rel_str}: {e}")
|
|
156
163
|
continue
|
|
157
164
|
|
|
158
165
|
if not isinstance(depends_list, list) or not all(
|
|
159
|
-
|
|
160
|
-
):
|
|
166
|
+
isinstance(item, str) for item in depends_list):
|
|
161
167
|
warnings.warn(
|
|
162
|
-
f"depends() in {rel_str} did not return a list of strings"
|
|
163
|
-
)
|
|
168
|
+
f"depends() in {rel_str} did not return a list of strings")
|
|
164
169
|
continue
|
|
165
170
|
|
|
166
171
|
for dep in depends_list:
|
|
@@ -178,9 +183,9 @@ def find_unreferenced_workflows(path: str) -> list[str]:
|
|
|
178
183
|
return unreferenced
|
|
179
184
|
|
|
180
185
|
|
|
181
|
-
def
|
|
182
|
-
|
|
183
|
-
|
|
186
|
+
def load_workflow_from_module(file_name: str,
|
|
187
|
+
base_path: str | Path,
|
|
188
|
+
package='workflows') -> WorkflowType:
|
|
184
189
|
if file_name.startswith('cfg:'):
|
|
185
190
|
return SetConfigWorkflow(file_name[4:])
|
|
186
191
|
base_path = Path(base_path)
|
|
@@ -200,3 +205,60 @@ def load_workflow(file_name: str,
|
|
|
200
205
|
verify_check_method(module)
|
|
201
206
|
|
|
202
207
|
return module
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def load_workflow_from_template(file_name: str,
|
|
211
|
+
mappping: dict[str, str],
|
|
212
|
+
base_path: str | Path,
|
|
213
|
+
subtitle: str | None = None,
|
|
214
|
+
package='workflows') -> WorkflowType:
|
|
215
|
+
base_path = Path(base_path)
|
|
216
|
+
path = Path(file_name)
|
|
217
|
+
|
|
218
|
+
with open(base_path / path) as f:
|
|
219
|
+
content = f.read()
|
|
220
|
+
template = string.Template(content)
|
|
221
|
+
content = template.substitute(mappping)
|
|
222
|
+
|
|
223
|
+
hash_str = hashlib.md5(pickle.dumps(mappping)).hexdigest()[:8]
|
|
224
|
+
if subtitle is None:
|
|
225
|
+
path = path.parent / path.stem.replace('_template',
|
|
226
|
+
f'_tmp{hash_str}.py')
|
|
227
|
+
else:
|
|
228
|
+
path = path.parent / path.stem.replace('_template', f'_{subtitle}.py')
|
|
229
|
+
|
|
230
|
+
with open(base_path / path, 'w') as f:
|
|
231
|
+
f.write(content)
|
|
232
|
+
|
|
233
|
+
module = load_workflow_from_module(str(path), base_path, package)
|
|
234
|
+
|
|
235
|
+
return module
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def load_workflow(workflow: str | tuple[str, dict],
|
|
239
|
+
base_path: str | Path,
|
|
240
|
+
package='workflows') -> WorkflowType:
|
|
241
|
+
if isinstance(workflow, tuple):
|
|
242
|
+
if len(workflow) == 2:
|
|
243
|
+
file_name, mapping = workflow
|
|
244
|
+
w = load_workflow_from_template(file_name, mapping, base_path,
|
|
245
|
+
None, package)
|
|
246
|
+
elif len(workflow) == 3:
|
|
247
|
+
file_name, subtitle, mapping = workflow
|
|
248
|
+
w = load_workflow_from_template(file_name, mapping, base_path,
|
|
249
|
+
subtitle, package)
|
|
250
|
+
else:
|
|
251
|
+
raise ValueError(f"Invalid workflow: {workflow}")
|
|
252
|
+
w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
|
|
253
|
+
elif isinstance(workflow, str):
|
|
254
|
+
if workflow.startswith('cfg:'):
|
|
255
|
+
key = workflow[4:]
|
|
256
|
+
w = SetConfigWorkflow(key)
|
|
257
|
+
w.__workflow_id__ = workflow
|
|
258
|
+
else:
|
|
259
|
+
w = load_workflow_from_module(workflow, base_path, package)
|
|
260
|
+
w.__workflow_id__ = str(Path(w.__file__).relative_to(base_path))
|
|
261
|
+
else:
|
|
262
|
+
raise TypeError(f"Invalid workflow: {workflow}")
|
|
263
|
+
|
|
264
|
+
return w
|
qulab/executor/schedule.py
CHANGED
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from loguru import logger
|
|
7
7
|
|
|
8
8
|
from . import transform
|
|
9
|
-
from .load import load_workflow
|
|
9
|
+
from .load import WorkflowType, load_workflow
|
|
10
10
|
from .storage import (Result, find_result, get_head, renew_result,
|
|
11
11
|
revoke_result, save_result)
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ class CalibrationFailedError(Exception):
|
|
|
15
15
|
pass
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def check_state(workflow:
|
|
18
|
+
def check_state(workflow: WorkflowType, code_path: str | Path,
|
|
19
19
|
state_path: str | Path) -> bool:
|
|
20
20
|
"""
|
|
21
21
|
check state should report a pass if and only if the following are satisfied:
|
|
@@ -26,35 +26,42 @@ def check_state(workflow: str, code_path: str | Path,
|
|
|
26
26
|
4. All dependencies pass check state.
|
|
27
27
|
"""
|
|
28
28
|
logger.debug(f'check_state: "{workflow}"')
|
|
29
|
-
result = find_result(workflow, state_path)
|
|
29
|
+
result = find_result(workflow.__workflow_id__, state_path)
|
|
30
30
|
if not result:
|
|
31
|
-
logger.debug(
|
|
31
|
+
logger.debug(
|
|
32
|
+
f'check_state failed: No history found for "{workflow.__workflow_id__}"'
|
|
33
|
+
)
|
|
32
34
|
return False
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return
|
|
38
|
-
if
|
|
39
|
-
) > result.checked_time + timedelta(seconds=
|
|
40
|
-
logger.debug(
|
|
35
|
+
if hasattr(workflow, 'check_state') and callable(workflow.check_state):
|
|
36
|
+
logger.debug(
|
|
37
|
+
f'check_state: "{workflow.__workflow_id__}" has custom check_state method'
|
|
38
|
+
)
|
|
39
|
+
return workflow.check_state(result)
|
|
40
|
+
if workflow.__timeout__ is not None and datetime.now(
|
|
41
|
+
) > result.checked_time + timedelta(seconds=workflow.__timeout__):
|
|
42
|
+
logger.debug(
|
|
43
|
+
f'check_state failed: "{workflow.__workflow_id__}" has expired')
|
|
41
44
|
return False
|
|
42
45
|
if not result.in_spec:
|
|
43
|
-
logger.debug(
|
|
46
|
+
logger.debug(
|
|
47
|
+
f'check_state failed: "{workflow.__workflow_id__}" is out of spec')
|
|
44
48
|
return False
|
|
45
49
|
if result.bad_data:
|
|
46
|
-
logger.debug(
|
|
50
|
+
logger.debug(
|
|
51
|
+
f'check_state failed: "{workflow.__workflow_id__}" has bad data')
|
|
47
52
|
return False
|
|
48
53
|
for n in get_dependents(workflow, code_path):
|
|
49
54
|
r = find_result(n, state_path)
|
|
50
55
|
if r is None or r.checked_time > result.checked_time:
|
|
51
56
|
logger.debug(
|
|
52
|
-
f'check_state failed: "{workflow}" has outdated dependencies'
|
|
57
|
+
f'check_state failed: "{workflow.__workflow_id__}" has outdated dependencies'
|
|
58
|
+
)
|
|
53
59
|
return False
|
|
54
60
|
for n in get_dependents(workflow, code_path):
|
|
55
61
|
if not check_state(n, code_path, state_path):
|
|
56
62
|
logger.debug(
|
|
57
|
-
f'check_state failed: "{workflow}" has bad dependencies'
|
|
63
|
+
f'check_state failed: "{workflow.__workflow_id__}" has bad dependencies'
|
|
64
|
+
)
|
|
58
65
|
return False
|
|
59
66
|
return True
|
|
60
67
|
|
|
@@ -83,118 +90,153 @@ def call_plot(node, result, check=False):
|
|
|
83
90
|
|
|
84
91
|
|
|
85
92
|
@functools.lru_cache(maxsize=128)
|
|
86
|
-
def check_data(workflow:
|
|
87
|
-
plot: bool, session_id: str) -> Result:
|
|
93
|
+
def check_data(workflow: WorkflowType, code_path: str | Path,
|
|
94
|
+
state_path: str | Path, plot: bool, session_id: str) -> Result:
|
|
88
95
|
"""
|
|
89
96
|
check data answers two questions:
|
|
90
97
|
Is the parameter associated with this cal in spec,
|
|
91
98
|
and is the cal scan working as expected?
|
|
92
99
|
"""
|
|
93
|
-
|
|
94
|
-
history = find_result(workflow, state_path)
|
|
100
|
+
history = find_result(workflow.__workflow_id__, state_path)
|
|
95
101
|
|
|
96
102
|
if history is None:
|
|
97
|
-
logger.debug(f'No history found for "{workflow}"')
|
|
103
|
+
logger.debug(f'No history found for "{workflow.__workflow_id__}"')
|
|
98
104
|
result = Result()
|
|
99
105
|
result.in_spec = False
|
|
100
106
|
result.bad_data = False
|
|
101
107
|
return result
|
|
102
108
|
|
|
103
109
|
if history.bad_data:
|
|
104
|
-
logger.debug(
|
|
110
|
+
logger.debug(
|
|
111
|
+
f'History found for "{workflow.__workflow_id__}", but bad data')
|
|
105
112
|
return history
|
|
106
113
|
if not history.in_spec:
|
|
107
|
-
logger.debug(
|
|
114
|
+
logger.debug(
|
|
115
|
+
f'History found for "{workflow.__workflow_id__}", but out of spec')
|
|
108
116
|
return history
|
|
109
117
|
|
|
110
|
-
logger.debug(
|
|
118
|
+
logger.debug(
|
|
119
|
+
f'History found for "{workflow.__workflow_id__}", but has expired')
|
|
111
120
|
|
|
112
|
-
if hasattr(
|
|
113
|
-
|
|
121
|
+
if hasattr(workflow, 'check') and callable(workflow.check) and hasattr(
|
|
122
|
+
workflow, 'check_analyze') and callable(workflow.check_analyze):
|
|
114
123
|
logger.debug(f'Checking "{workflow}" with "check" method ...')
|
|
115
|
-
data =
|
|
124
|
+
data = workflow.check()
|
|
116
125
|
result = Result()
|
|
117
126
|
result.data = data
|
|
118
|
-
save_result(workflow, result, state_path)
|
|
127
|
+
save_result(workflow.__workflow_id__, result, state_path)
|
|
119
128
|
|
|
120
129
|
logger.debug(f'Checked "{workflow}" !')
|
|
121
|
-
result = call_analyzer(
|
|
130
|
+
result = call_analyzer(workflow, data, history, check=True, plot=plot)
|
|
122
131
|
if result.in_spec:
|
|
123
|
-
logger.debug(
|
|
124
|
-
|
|
132
|
+
logger.debug(
|
|
133
|
+
f'"{workflow.__workflow_id__}": checked in spec, renewing result'
|
|
134
|
+
)
|
|
135
|
+
renew_result(workflow.__workflow_id__, state_path)
|
|
125
136
|
else:
|
|
126
|
-
logger.debug(
|
|
127
|
-
|
|
137
|
+
logger.debug(
|
|
138
|
+
f'"{workflow.__workflow_id__}": checked out of spec, revoking result'
|
|
139
|
+
)
|
|
140
|
+
revoke_result(workflow.__workflow_id__, state_path)
|
|
128
141
|
else:
|
|
129
|
-
logger.debug(
|
|
130
|
-
|
|
142
|
+
logger.debug(
|
|
143
|
+
f'Checking "{workflow.__workflow_id__}" with "calibrate" method ...'
|
|
144
|
+
)
|
|
145
|
+
data = workflow.calibrate()
|
|
131
146
|
result = Result()
|
|
132
147
|
result.data = data
|
|
133
|
-
save_result(workflow, result, state_path)
|
|
148
|
+
save_result(workflow.__workflow_id__, result, state_path)
|
|
134
149
|
|
|
135
150
|
logger.debug(f'Calibrated "{workflow}" !')
|
|
136
|
-
result = call_analyzer(
|
|
137
|
-
save_result(workflow, result, state_path,
|
|
138
|
-
get_head(workflow, state_path))
|
|
151
|
+
result = call_analyzer(workflow, data, history, check=False, plot=plot)
|
|
152
|
+
save_result(workflow.__workflow_id__, result, state_path,
|
|
153
|
+
get_head(workflow.__workflow_id__, state_path))
|
|
139
154
|
|
|
140
155
|
return result
|
|
141
156
|
|
|
142
157
|
|
|
143
158
|
@functools.lru_cache(maxsize=128)
|
|
144
|
-
def calibrate(workflow
|
|
145
|
-
plot: bool, session_id: str) -> Result:
|
|
146
|
-
node = load_workflow(workflow, code_path)
|
|
159
|
+
def calibrate(workflow: WorkflowType, code_path: str | Path,
|
|
160
|
+
state_path: str | Path, plot: bool, session_id: str) -> Result:
|
|
147
161
|
history = find_result(workflow, state_path)
|
|
148
162
|
|
|
149
|
-
logger.debug(f'Calibrating "{workflow}" ...')
|
|
150
|
-
data =
|
|
163
|
+
logger.debug(f'Calibrating "{workflow.__workflow_id__}" ...')
|
|
164
|
+
data = workflow.calibrate()
|
|
151
165
|
result = Result()
|
|
152
166
|
result.data = data
|
|
153
|
-
save_result(workflow, result, state_path)
|
|
154
|
-
logger.debug(f'Calibrated "{workflow}" !')
|
|
155
|
-
result = call_analyzer(
|
|
156
|
-
save_result(workflow, result, state_path,
|
|
167
|
+
save_result(workflow.__workflow_id__, result, state_path)
|
|
168
|
+
logger.debug(f'Calibrated "{workflow.__workflow_id__}" !')
|
|
169
|
+
result = call_analyzer(workflow, data, history, check=False, plot=plot)
|
|
170
|
+
save_result(workflow.__workflow_id__, result, state_path,
|
|
171
|
+
get_head(workflow.__workflow_id__, state_path))
|
|
157
172
|
return result
|
|
158
173
|
|
|
159
174
|
|
|
160
|
-
def diagnose(
|
|
161
|
-
session_id: str):
|
|
175
|
+
def diagnose(workflow: WorkflowType, code_path: str | Path,
|
|
176
|
+
state_path: str | Path, plot: bool, session_id: str):
|
|
162
177
|
'''
|
|
163
178
|
Returns: True if node or dependent recalibrated.
|
|
164
179
|
'''
|
|
165
|
-
logger.debug(f'diagnose "{
|
|
180
|
+
logger.debug(f'diagnose "{workflow.__workflow_id__}"')
|
|
166
181
|
# check_data
|
|
167
|
-
result = check_data(
|
|
182
|
+
result = check_data(workflow, code_path, state_path, plot, session_id)
|
|
168
183
|
# in spec case
|
|
169
184
|
if result.in_spec:
|
|
170
|
-
logger.debug(
|
|
185
|
+
logger.debug(
|
|
186
|
+
f'"{workflow.__workflow_id__}": Checked! In spec, no need to diagnose'
|
|
187
|
+
)
|
|
171
188
|
return False
|
|
172
189
|
# bad data case
|
|
173
190
|
recalibrated = []
|
|
174
191
|
if result.bad_data:
|
|
192
|
+
logger.debug(
|
|
193
|
+
f'"{workflow.__workflow_id__}": Bad data, diagnosing dependents')
|
|
175
194
|
recalibrated = [
|
|
176
195
|
diagnose(n, code_path, state_path, plot, session_id)
|
|
177
|
-
for n in get_dependents(
|
|
196
|
+
for n in get_dependents(workflow, code_path)
|
|
178
197
|
]
|
|
179
|
-
if not any(recalibrated)
|
|
180
|
-
|
|
181
|
-
|
|
198
|
+
if not any(recalibrated):
|
|
199
|
+
if result.bad_data:
|
|
200
|
+
raise CalibrationFailedError(
|
|
201
|
+
f'"{workflow.__workflow_id__}": bad data but no dependents recalibrated.'
|
|
202
|
+
)
|
|
203
|
+
logger.debug(
|
|
204
|
+
f'"{workflow.__workflow_id__}": no dependents recalibrated.')
|
|
182
205
|
# calibrate
|
|
183
|
-
|
|
184
|
-
|
|
206
|
+
if any(recalibrated):
|
|
207
|
+
logger.debug(
|
|
208
|
+
f'recalibrate "{workflow.__workflow_id__}" because some dependents recalibrated.'
|
|
209
|
+
)
|
|
210
|
+
elif not result.in_spec and not result.bad_data:
|
|
211
|
+
logger.debug(
|
|
212
|
+
f'recalibrate "{workflow.__workflow_id__}" because out of spec.')
|
|
213
|
+
elif result.in_spec:
|
|
214
|
+
logger.error(
|
|
215
|
+
f'Never reach: recalibrate "{workflow.__workflow_id__}" because in spec.'
|
|
216
|
+
)
|
|
217
|
+
elif result.bad_data:
|
|
218
|
+
logger.error(
|
|
219
|
+
f'Never reach: recalibrate "{workflow.__workflow_id__}" because bad data.'
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
logger.error(f'Never reach: recalibrate "{workflow.__workflow_id__}"')
|
|
223
|
+
|
|
224
|
+
result = calibrate(workflow, code_path, state_path, plot, session_id)
|
|
185
225
|
if result.bad_data or not result.in_spec:
|
|
186
226
|
raise CalibrationFailedError(
|
|
187
|
-
f'"{
|
|
227
|
+
f'"{workflow.__workflow_id__}": All dependents passed, but calibration failed!'
|
|
228
|
+
)
|
|
188
229
|
transform.update_parameters(result)
|
|
189
230
|
return True
|
|
190
231
|
|
|
191
232
|
|
|
192
|
-
def get_dependents(workflow:
|
|
193
|
-
|
|
233
|
+
def get_dependents(workflow: WorkflowType,
|
|
234
|
+
code_path: str | Path) -> list[WorkflowType]:
|
|
235
|
+
return [load_workflow(n, code_path) for n in workflow.depends()[0]]
|
|
194
236
|
|
|
195
237
|
|
|
196
|
-
|
|
197
|
-
def maintain(
|
|
238
|
+
@logger.catch(reraise=True)
|
|
239
|
+
def maintain(workflow: WorkflowType,
|
|
198
240
|
code_path: str | Path,
|
|
199
241
|
state_path: str | Path,
|
|
200
242
|
session_id: str | None = None,
|
|
@@ -202,48 +244,61 @@ def maintain(node,
|
|
|
202
244
|
plot: bool = False):
|
|
203
245
|
if session_id is None:
|
|
204
246
|
session_id = uuid.uuid4().hex
|
|
205
|
-
logger.debug(f'run "{
|
|
247
|
+
logger.debug(f'run "{workflow.__workflow_id__}"'
|
|
248
|
+
if run else f'maintain "{workflow.__workflow_id__}"')
|
|
206
249
|
# recursive maintain
|
|
207
|
-
for n in get_dependents(
|
|
208
|
-
logger.debug(
|
|
250
|
+
for n in get_dependents(workflow, code_path):
|
|
251
|
+
logger.debug(
|
|
252
|
+
f'maintain "{n.__workflow_id__}" because it is depended by "{workflow.__workflow_id__}"'
|
|
253
|
+
)
|
|
209
254
|
maintain(n, code_path, state_path, session_id, run=False, plot=plot)
|
|
210
255
|
else:
|
|
211
|
-
logger.debug(
|
|
256
|
+
logger.debug(
|
|
257
|
+
f'"{workflow.__workflow_id__}": All dependents maintained')
|
|
212
258
|
# check_state
|
|
213
|
-
if check_state(
|
|
214
|
-
logger.debug(
|
|
259
|
+
if check_state(workflow, code_path, state_path) and not run:
|
|
260
|
+
logger.debug(
|
|
261
|
+
f'"{workflow.__workflow_id__}": In spec, no need to maintain')
|
|
215
262
|
return
|
|
216
263
|
# check_data
|
|
217
|
-
result = check_data(
|
|
264
|
+
result = check_data(workflow, code_path, state_path, plot, session_id)
|
|
218
265
|
if result.in_spec:
|
|
219
266
|
if not run:
|
|
220
|
-
logger.debug(
|
|
267
|
+
logger.debug(
|
|
268
|
+
f'"{workflow.__workflow_id__}": In spec, no need to maintain')
|
|
221
269
|
return
|
|
222
270
|
elif result.bad_data:
|
|
223
|
-
logger.debug(
|
|
224
|
-
|
|
225
|
-
|
|
271
|
+
logger.debug(
|
|
272
|
+
f'"{workflow.__workflow_id__}": Bad data, diagnosing dependents')
|
|
273
|
+
for n in get_dependents(workflow, code_path):
|
|
274
|
+
logger.debug(
|
|
275
|
+
f'diagnose "{n.__workflow_id__}" because of "{workflow.__workflow_id__}" bad data'
|
|
276
|
+
)
|
|
226
277
|
diagnose(n, code_path, state_path, plot, session_id)
|
|
227
278
|
else:
|
|
228
|
-
logger.debug(
|
|
279
|
+
logger.debug(
|
|
280
|
+
f'"{workflow.__workflow_id__}": All dependents diagnosed')
|
|
229
281
|
# calibrate
|
|
230
|
-
logger.debug(f'recalibrate "{
|
|
231
|
-
result = calibrate(
|
|
282
|
+
logger.debug(f'recalibrate "{workflow.__workflow_id__}"')
|
|
283
|
+
result = calibrate(workflow, code_path, state_path, plot, session_id)
|
|
232
284
|
if result.bad_data or not result.in_spec:
|
|
233
285
|
raise CalibrationFailedError(
|
|
234
|
-
f'"{
|
|
286
|
+
f'"{workflow.__workflow_id__}": All dependents passed, but calibration failed!'
|
|
287
|
+
)
|
|
235
288
|
transform.update_parameters(result)
|
|
236
289
|
return
|
|
237
290
|
|
|
238
291
|
|
|
239
|
-
|
|
292
|
+
@logger.catch(reraise=True)
|
|
293
|
+
def run(workflow: WorkflowType,
|
|
240
294
|
code_path: str | Path,
|
|
241
295
|
state_path: str | Path,
|
|
242
296
|
plot: bool = False):
|
|
243
|
-
logger.debug(f'run "{
|
|
244
|
-
result = calibrate(
|
|
297
|
+
logger.debug(f'run "{workflow.__workflow_id__}" without dependences.')
|
|
298
|
+
result = calibrate(workflow, code_path, state_path, plot)
|
|
245
299
|
if result.bad_data or not result.in_spec:
|
|
246
300
|
raise CalibrationFailedError(
|
|
247
|
-
f'"{
|
|
301
|
+
f'"{workflow.__workflow_id__}": All dependents passed, but calibration failed!'
|
|
302
|
+
)
|
|
248
303
|
transform.update_parameters(result)
|
|
249
304
|
return
|
qulab/fun.cpython-312-darwin.so
CHANGED
|
Binary file
|
qulab/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.4.
|
|
1
|
+
__version__ = "2.4.11"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|