dsh-labscript 0.2.3 → 0.2.5
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.
- package/CHANGELOG.md +30 -0
- package/README.md +5 -3
- package/lib/tools.js +42 -9
- package/package.json +1 -1
- package/python/bridge.py +27 -4
- package/python/tests/run_tests.py +59 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.5
|
|
4
|
+
|
|
5
|
+
- labs_status can list every panel global: keys=["*"] prints all of them, sorted
|
|
6
|
+
(raw=true prints the raw expressions). This makes leftover array globals visible,
|
|
7
|
+
which is what makes labs_protocol refuse to run with N_SHOTS_MISMATCH.
|
|
8
|
+
- labs_status reports scan axes: the array-valued globals with their lengths and
|
|
9
|
+
n_shots_from_arrays (their product), and flags when the panel n_shots differs from
|
|
10
|
+
that product.
|
|
11
|
+
- labs_wait fails immediately when an explicit run folder does not exist and is not
|
|
12
|
+
this session's pending run (it used to wait for the full timeout), and the result
|
|
13
|
+
shows whether the folder exists.
|
|
14
|
+
- labs_data description: run_folder defaults to the pending run, else the last run
|
|
15
|
+
submitted through this plugin (persisted across restarts).
|
|
16
|
+
- cmd_progress returns folder_exists.
|
|
17
|
+
- Tests: scan axes, all-globals rendering, wait fast-fail, progress folder_exists.
|
|
18
|
+
|
|
19
|
+
## 0.2.4
|
|
20
|
+
|
|
21
|
+
- Fix: labs_data no longer answers h5: 0 for a run folder that does not exist. A missing
|
|
22
|
+
folder raises RUN_FOLDER_MISSING with the raw and mapped paths plus a hint: runmanager's
|
|
23
|
+
shot output folder is the folder the NEXT run will use and only exists after engage, so
|
|
24
|
+
historical runs must be passed explicitly. An existing folder with no shots reports
|
|
25
|
+
empty: true instead.
|
|
26
|
+
- Fix: labs_status with keys=[...] drops keys that are not panel globals instead of
|
|
27
|
+
returning null, so the rendered output shows <missing>. UI group names (for example
|
|
28
|
+
Select_Sequence) are not globals: read their leaf keys or a run's h5.
|
|
29
|
+
- Descriptions: labs_status says the shot output folder is the NEXT run folder and that
|
|
30
|
+
keys are leaf global names; labs_data explains the run_folder default and the error.
|
|
31
|
+
- Tests: missing run folder, empty run folder, missing keys dropped.
|
|
32
|
+
|
|
3
33
|
## 0.2.3
|
|
4
34
|
|
|
5
35
|
- Fix: labs_status now prints panel values. keys=[...] and raw=true were accepted but the
|
package/README.md
CHANGED
|
@@ -125,8 +125,8 @@ is not a physics simulation.
|
|
|
125
125
|
## Development
|
|
126
126
|
|
|
127
127
|
npm install # only runtime dependency: yaml
|
|
128
|
-
npm test # node --test,
|
|
129
|
-
python python/tests/run_tests.py #
|
|
128
|
+
npm test # node --test, 38 tests (fake bridge, no python needed)
|
|
129
|
+
python python/tests/run_tests.py # 19 tests (needs numpy + h5py)
|
|
130
130
|
LABSCRIPT_PYTHON=/path/to/python3 node scripts/demo.mjs # end-to-end mock demo
|
|
131
131
|
|
|
132
132
|
A fresh clone must run `npm install` before `npm test`; the CI workflow does this.
|
|
@@ -157,7 +157,9 @@ A fresh clone must run `npm install` before `npm test`; the CI workflow does thi
|
|
|
157
157
|
partial). compiled_not_executed means shots exist but show no acquisition data yet;
|
|
158
158
|
BLACS may be paused or not executing the queue.
|
|
159
159
|
- Real runmanager engage does not return a run folder; the bridge captures the
|
|
160
|
-
output folder before engage and reports it as the run folder.
|
|
160
|
+
output folder before engage and reports it as the run folder. That shot output
|
|
161
|
+
folder is the NEXT run's folder and does not exist until engage, so labs_data
|
|
162
|
+
errors (RUN_FOLDER_MISSING) instead of reporting h5: 0 for a path that is not there.
|
|
161
163
|
- The panel is snapshotted before every write; labs_restore restores only the keys the
|
|
162
164
|
write touched (full=true restores the whole panel), so unrelated concurrent edits survive.
|
|
163
165
|
- Actions are logged to events.jsonl next to the snapshots.
|
package/lib/tools.js
CHANGED
|
@@ -46,11 +46,16 @@ function globalText(value, limit = 240) {
|
|
|
46
46
|
function globalLines(args, value) {
|
|
47
47
|
const globals = value.globals || {}
|
|
48
48
|
const keys = Array.isArray(args.keys) ? args.keys : []
|
|
49
|
+
const raw = args.raw === true && value.raw_globals ? value.raw_globals : null
|
|
50
|
+
if (keys.length === 1 && keys[0] === '*') {
|
|
51
|
+
const shown = raw || globals
|
|
52
|
+
const names = Object.keys(shown).sort()
|
|
53
|
+
return ['globals: all ' + names.length].concat(names.map((key) => ' ' + key + ' = ' + globalText(shown[key])))
|
|
54
|
+
}
|
|
49
55
|
if (keys.length === 0) {
|
|
50
56
|
const count = Object.keys(globals).length
|
|
51
|
-
return [count > 0 ? 'globals: ' + count + ' keys (pass keys=[...] to read values)' : 'globals: none']
|
|
57
|
+
return [count > 0 ? 'globals: ' + count + ' keys (pass keys=[...] or keys=["*"] to read values)' : 'globals: none']
|
|
52
58
|
}
|
|
53
|
-
const raw = args.raw === true && value.raw_globals ? value.raw_globals : null
|
|
54
59
|
const out = ['globals:']
|
|
55
60
|
for (const key of keys) {
|
|
56
61
|
const shown = raw ? raw[key] : globals[key]
|
|
@@ -59,6 +64,16 @@ function globalLines(args, value) {
|
|
|
59
64
|
return out
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
function scanLine(value) {
|
|
68
|
+
const axes = value.scan_axes && typeof value.scan_axes === 'object' ? value.scan_axes : {}
|
|
69
|
+
const names = Object.keys(axes).sort()
|
|
70
|
+
const product = value.n_shots_from_arrays
|
|
71
|
+
const mismatch = typeof product === 'number' && typeof value.n_shots === 'number' && value.n_shots !== product
|
|
72
|
+
if (names.length === 0 && !mismatch) return ''
|
|
73
|
+
const detail = names.length ? names.map((key) => key + '=' + axes[key]).join(', ') : 'none'
|
|
74
|
+
return 'scan axes: ' + detail + ' n_shots_from_arrays=' + product + (mismatch ? ' (panel n_shots=' + value.n_shots + ' differs)' : '')
|
|
75
|
+
}
|
|
76
|
+
|
|
62
77
|
async function pendingProgress(runtime, exec) {
|
|
63
78
|
const pending = runtime.state.pending
|
|
64
79
|
if (!pending) return null
|
|
@@ -129,11 +144,11 @@ export function registerTools(ctx, runtime) {
|
|
|
129
144
|
|
|
130
145
|
ctx.tools.register({
|
|
131
146
|
name: 'labs_status',
|
|
132
|
-
description: 'Read the labscript runmanager panel: loaded labscript, globals (evaluated; raw expressions with raw=true), n_shots, error_in_globals, run_shots, shot output folder, pending run progress, and the panel snapshot directory. Read-only; call this before any write. Pass keys=[...] to print the selected panel values (240 chars each; raw=true prints the raw expression). Paths in the output are already mapped for this machine through pathMap; do not map them again.',
|
|
147
|
+
description: 'Read the labscript runmanager panel: loaded labscript, globals (evaluated; raw expressions with raw=true), n_shots, error_in_globals, run_shots, shot output folder, pending run progress, and the panel snapshot directory. Read-only; call this before any write. Pass keys=[...] (leaf global names; UI group names are not globals) to print the selected panel values (240 chars each; raw=true prints the raw expression), missing keys are shown as <missing>; keys=["*"] prints every global. scan axes lists array-valued globals (their lengths multiply to n_shots_from_arrays) and a differing panel n_shots is flagged. The shot output folder is the folder the NEXT run will use; it does not exist until engage. Paths in the output are already mapped for this machine through pathMap; do not map them again.',
|
|
133
148
|
parameters: {
|
|
134
149
|
type: 'object',
|
|
135
150
|
properties: {
|
|
136
|
-
keys: { type: 'array', items: { type: 'string' }, description: 'Only return these panel globals' },
|
|
151
|
+
keys: { type: 'array', items: { type: 'string' }, description: 'Only return these panel globals; ["*"] returns every global' },
|
|
137
152
|
raw: { type: 'boolean', description: 'Also return raw (expression) values' },
|
|
138
153
|
},
|
|
139
154
|
additionalProperties: false,
|
|
@@ -146,11 +161,14 @@ export function registerTools(ctx, runtime) {
|
|
|
146
161
|
'n_shots: ' + value.n_shots + ' error_in_globals: ' + value.error_in_globals + ' run_shots: ' + value.run_shots,
|
|
147
162
|
progressLine(value.last_run),
|
|
148
163
|
'panel history: ' + (value.panel_history ? value.panel_history.dir + ' (' + value.panel_history.count + ' snapshots)' : 'n/a'),
|
|
164
|
+
scanLine(value),
|
|
149
165
|
...globalLines(args, value),
|
|
150
166
|
]))),
|
|
151
167
|
async execute(args, exec) {
|
|
168
|
+
const keys = Array.isArray(args.keys) ? args.keys : []
|
|
169
|
+
const all = keys.length === 1 && keys[0] === '*'
|
|
152
170
|
const data = await runtime.runBridge('status', {
|
|
153
|
-
keys:
|
|
171
|
+
keys: all || keys.length === 0 ? undefined : keys,
|
|
154
172
|
raw: args.raw === true,
|
|
155
173
|
}, { signal: exec.signal })
|
|
156
174
|
if (runtime.state.pending) data.last_run = await pendingProgress(runtime, exec)
|
|
@@ -230,7 +248,7 @@ export function registerTools(ctx, runtime) {
|
|
|
230
248
|
|
|
231
249
|
ctx.tools.register({
|
|
232
250
|
name: 'labs_wait',
|
|
233
|
-
description: 'Wait for the pending run to finish (blocking, timeout in seconds). If the pending run came from labs_protocol run, this call also finalizes it: postprocess, finish write-back of panel and calibration history, and panel restore. Returns expected vs actual shots and, for protocols, the postprocess/finish/restore results.',
|
|
251
|
+
description: 'Wait for the pending run to finish (blocking, timeout in seconds). If the pending run came from labs_protocol run, this call also finalizes it: postprocess, finish write-back of panel and calibration history, and panel restore. Returns expected vs actual shots and, for protocols, the postprocess/finish/restore results. A run folder that does not exist and is not the pending run of this session fails immediately instead of waiting.',
|
|
234
252
|
parameters: {
|
|
235
253
|
type: 'object',
|
|
236
254
|
properties: {
|
|
@@ -244,6 +262,7 @@ export function registerTools(ctx, runtime) {
|
|
|
244
262
|
output: looseOutput((_args, value) => text(lines([
|
|
245
263
|
'run: ' + (value.mapped_run_folder || value.run_folder),
|
|
246
264
|
value.aborted ? 'wait cancelled by labs_abort (run aborted)' : '',
|
|
265
|
+
value.folder_exists === false ? 'folder: missing' : '',
|
|
247
266
|
'shots: ' + value.n_h5 + '/' + (value.expected_n_shots || '?') + ' complete: ' + value.complete + (value.timed_out ? ' TIMED OUT' : '') + (value.diagnosis ? ' diagnosis: ' + value.diagnosis : ''),
|
|
248
267
|
value.hint ? 'hint: ' + value.hint : '',
|
|
249
268
|
value.finalize && value.finalize.postprocess ? 'postprocess: ' + JSON.stringify(value.finalize.postprocess).slice(0, 400) : '',
|
|
@@ -256,6 +275,20 @@ export function registerTools(ctx, runtime) {
|
|
|
256
275
|
const runFolder = args.run_folder || (pending ? pending.run_folder : '')
|
|
257
276
|
if (!runFolder) throw new LabError('NO_PENDING_RUN', 'no run folder: submit with labs_engage or labs_protocol run first, or pass run_folder')
|
|
258
277
|
const expected = pending && pending.run_folder === runFolder ? pending.expected_n_shots : undefined
|
|
278
|
+
if (!(pending && pending.run_folder === runFolder)) {
|
|
279
|
+
let probe = null
|
|
280
|
+
try {
|
|
281
|
+
probe = await runtime.runBridge('progress', {
|
|
282
|
+
run_folder: runFolder,
|
|
283
|
+
expected_n_shots: expected,
|
|
284
|
+
}, { signal: exec.signal })
|
|
285
|
+
} catch (error) {
|
|
286
|
+
probe = null
|
|
287
|
+
}
|
|
288
|
+
if (probe && probe.folder_exists === false) {
|
|
289
|
+
throw new LabError('RUN_FOLDER_MISSING', 'run folder does not exist: ' + (probe.mapped_run_folder || runFolder) + '. Pass the folder of a run that exists (labs_engage / labs_protocol return one), or check pathMap.')
|
|
290
|
+
}
|
|
291
|
+
}
|
|
259
292
|
const timeoutS = Number(args.timeout_s || runtime.config.engage.timeoutS || 900)
|
|
260
293
|
let waited
|
|
261
294
|
try {
|
|
@@ -339,11 +372,11 @@ export function registerTools(ctx, runtime) {
|
|
|
339
372
|
|
|
340
373
|
ctx.tools.register({
|
|
341
374
|
name: 'labs_data',
|
|
342
|
-
description: 'Read a run folder HDF5: globals of the first shot (execution facts) and, with scan: a per-shot table of scan values and numeric results. Read-only.',
|
|
375
|
+
description: 'Read a run folder HDF5: globals of the first shot (execution facts) and, with scan: a per-shot table of scan values and numeric results. Read-only. run_folder defaults to the pending run, else the last run submitted through this plugin (persisted across restarts); for a historical run pass its folder explicitly. A missing folder is an error: runmanager\'s shot output is the NEXT run folder and only exists after engage.',
|
|
343
376
|
parameters: {
|
|
344
377
|
type: 'object',
|
|
345
378
|
properties: {
|
|
346
|
-
run_folder: { type: 'string', description: 'Run folder; defaults to the
|
|
379
|
+
run_folder: { type: 'string', description: 'Run folder; defaults to the run submitted from this DSH session (pass a historical run folder explicitly)' },
|
|
347
380
|
scan: { type: 'string', description: 'Panel global name used as the scan axis; adds a per-shot table' },
|
|
348
381
|
globals: { type: 'boolean', description: 'Include the first shot globals (default true)' },
|
|
349
382
|
results: { type: 'boolean', description: 'Include the results layout' },
|
|
@@ -353,7 +386,7 @@ export function registerTools(ctx, runtime) {
|
|
|
353
386
|
isConcurrencySafe: () => true,
|
|
354
387
|
output: looseOutput((_args, value) => text(lines([
|
|
355
388
|
'run: ' + (value.mapped_run_folder || value.run_folder),
|
|
356
|
-
'h5: ' + value.n_h5,
|
|
389
|
+
'h5: ' + value.n_h5 + (value.empty ? ' (folder exists, no shots yet)' : ''),
|
|
357
390
|
value.scan_table ? 'scan ' + value.scan_table.scan + ': ' + value.scan_table.n_points + ' shots' : '',
|
|
358
391
|
value.globals ? 'globals: ' + Object.keys(value.globals).length + ' keys' : '',
|
|
359
392
|
]))),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-labscript",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Control labscript-suite experiments from DeepSeek Harness: runmanager globals and scans, submits and waits, YAML fixed-operation protocols with postprocess write-back, panel snapshots, a built-in mock backend and a read-only doctor.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/python/bridge.py
CHANGED
|
@@ -919,17 +919,33 @@ def cmd_status(config, args):
|
|
|
919
919
|
backend = make_backend(config)
|
|
920
920
|
keys = args.get("keys")
|
|
921
921
|
raw = bool(args.get("raw"))
|
|
922
|
-
|
|
922
|
+
panel_values = backend.get_globals(raw=False)
|
|
923
923
|
raw_values = backend.get_globals(raw=True)
|
|
924
|
+
scan_axes = {}
|
|
925
|
+
n_shots_from_arrays = 1
|
|
926
|
+
for name, value in panel_values.items():
|
|
927
|
+
if isinstance(value, str) or not hasattr(value, "__len__"):
|
|
928
|
+
continue
|
|
929
|
+
try:
|
|
930
|
+
size = int(len(value))
|
|
931
|
+
except Exception:
|
|
932
|
+
continue
|
|
933
|
+
if size <= 1:
|
|
934
|
+
continue
|
|
935
|
+
scan_axes[str(name)] = size
|
|
936
|
+
n_shots_from_arrays *= size
|
|
937
|
+
values = dict(panel_values)
|
|
924
938
|
if keys:
|
|
925
|
-
values = dict((k,
|
|
926
|
-
raw_values = dict((k, raw_values
|
|
939
|
+
values = dict((k, panel_values[k]) for k in keys if k in panel_values)
|
|
940
|
+
raw_values = dict((k, raw_values[k]) for k in keys if k in raw_values)
|
|
927
941
|
out = {
|
|
928
942
|
"backend": config.get("backend"),
|
|
929
943
|
"version": backend.version() if hasattr(backend, "version") else None,
|
|
930
944
|
"labscript": map_path(config, backend.labscript_file()),
|
|
931
945
|
"shot_output_folder": map_path(config, backend.shot_output_folder()),
|
|
932
946
|
"globals": jsonable(values),
|
|
947
|
+
"scan_axes": scan_axes,
|
|
948
|
+
"n_shots_from_arrays": n_shots_from_arrays,
|
|
933
949
|
"n_shots": backend.n_shots(),
|
|
934
950
|
"error_in_globals": backend.error_in_globals(),
|
|
935
951
|
"run_shots": backend.run_shots(),
|
|
@@ -1127,7 +1143,7 @@ def cmd_progress(config, args):
|
|
|
1127
1143
|
expected = int(args.get("expected_n_shots") or 0)
|
|
1128
1144
|
folder = map_path(config, run_folder)
|
|
1129
1145
|
shots = sorted(glob.glob(os.path.join(folder, "**", "*.h5"), recursive=True)) if folder else []
|
|
1130
|
-
return {"run_folder": run_folder, "mapped_run_folder": folder, "n_h5": len(shots), "expected_n_shots": expected, "complete": bool(expected and len(shots) >= expected)}
|
|
1146
|
+
return {"run_folder": run_folder, "mapped_run_folder": folder, "folder_exists": bool(folder) and os.path.isdir(folder), "n_h5": len(shots), "expected_n_shots": expected, "complete": bool(expected and len(shots) >= expected)}
|
|
1131
1147
|
|
|
1132
1148
|
|
|
1133
1149
|
|
|
@@ -1182,9 +1198,16 @@ def cmd_data(config, args):
|
|
|
1182
1198
|
if not run_folder:
|
|
1183
1199
|
raise BridgeError("BAD_ARGS", "run_folder is required")
|
|
1184
1200
|
folder = map_path(config, run_folder)
|
|
1201
|
+
if not os.path.isdir(folder):
|
|
1202
|
+
raise BridgeError("RUN_FOLDER_MISSING", "run folder does not exist: " + str(folder), {
|
|
1203
|
+
"run_folder": run_folder,
|
|
1204
|
+
"mapped_run_folder": folder,
|
|
1205
|
+
"hint": "runmanager's shot output folder is the folder the NEXT run will use; it does not exist until engage. Pass a historical run folder explicitly.",
|
|
1206
|
+
})
|
|
1185
1207
|
shots = sorted(glob.glob(os.path.join(folder, "**", "*.h5"), recursive=True))
|
|
1186
1208
|
out = {"run_folder": run_folder, "mapped_run_folder": folder, "n_h5": len(shots)}
|
|
1187
1209
|
if not shots:
|
|
1210
|
+
out["empty"] = True
|
|
1188
1211
|
return out
|
|
1189
1212
|
scan = args.get("scan")
|
|
1190
1213
|
with h5py.File(shots[0], "r", locking=False) as handle:
|
|
@@ -344,6 +344,65 @@ def test_progress_reports_mapped_run_folder():
|
|
|
344
344
|
out = bridge.cmd_progress(cfg, {"run_folder": "E:\\Experiments\\nope", "expected_n_shots": 1})
|
|
345
345
|
assert out["run_folder"] == "E:\\Experiments\\nope", out
|
|
346
346
|
assert out["mapped_run_folder"] == "\\\\lab-pc\\Experiments\\nope", out
|
|
347
|
+
assert out["folder_exists"] is False, out
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def test_data_missing_run_folder():
|
|
351
|
+
tmp = tempfile.mkdtemp(prefix="dsh-labscript-data-missing-")
|
|
352
|
+
try:
|
|
353
|
+
cfg = make_config(tmp)
|
|
354
|
+
missing = os.path.join(tmp, "no_such_run")
|
|
355
|
+
try:
|
|
356
|
+
bridge.cmd_data(cfg, {"run_folder": missing})
|
|
357
|
+
except bridge.BridgeError as exc:
|
|
358
|
+
assert exc.code == "RUN_FOLDER_MISSING", exc.message
|
|
359
|
+
assert exc.detail["mapped_run_folder"] == missing, exc.detail
|
|
360
|
+
assert "next" in exc.detail["hint"].lower(), exc.detail
|
|
361
|
+
else:
|
|
362
|
+
raise AssertionError("expected RUN_FOLDER_MISSING")
|
|
363
|
+
finally:
|
|
364
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def test_data_empty_run_folder():
|
|
368
|
+
tmp = tempfile.mkdtemp(prefix="dsh-labscript-data-empty-")
|
|
369
|
+
try:
|
|
370
|
+
cfg = make_config(tmp)
|
|
371
|
+
run_dir = os.path.join(tmp, "empty_run")
|
|
372
|
+
os.makedirs(run_dir, exist_ok=True)
|
|
373
|
+
out = bridge.cmd_data(cfg, {"run_folder": run_dir})
|
|
374
|
+
assert out["n_h5"] == 0, out
|
|
375
|
+
assert out.get("empty") is True, out
|
|
376
|
+
finally:
|
|
377
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def test_status_drops_missing_keys():
|
|
381
|
+
tmp = tempfile.mkdtemp(prefix="dsh-labscript-status-missing-")
|
|
382
|
+
try:
|
|
383
|
+
cfg = make_config(tmp)
|
|
384
|
+
out = bridge.cmd_status(cfg, {"keys": ["amplitude", "Select_Sequence"], "raw": True})
|
|
385
|
+
assert "amplitude" in out["globals"], out["globals"]
|
|
386
|
+
assert "Select_Sequence" not in out["globals"], out["globals"]
|
|
387
|
+
assert "Select_Sequence" not in out["raw_globals"], out["raw_globals"]
|
|
388
|
+
finally:
|
|
389
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def test_status_scan_axes():
|
|
393
|
+
tmp = tempfile.mkdtemp(prefix="dsh-labscript-scan-axes-")
|
|
394
|
+
try:
|
|
395
|
+
cfg = make_config(tmp)
|
|
396
|
+
bridge.cmd_set(cfg, {"values": {"duration_test_us": [0.0, 0.5, 1.0]}})
|
|
397
|
+
out = bridge.cmd_status(cfg, {})
|
|
398
|
+
assert out["scan_axes"].get("duration_test_us") == 3, out["scan_axes"]
|
|
399
|
+
assert out["n_shots_from_arrays"] == 3, out["n_shots_from_arrays"]
|
|
400
|
+
assert out["n_shots"] == 3, out["n_shots"]
|
|
401
|
+
partial = bridge.cmd_status(cfg, {"keys": ["amplitude"]})
|
|
402
|
+
assert "duration_test_us" not in partial["globals"], partial["globals"]
|
|
403
|
+
assert partial["scan_axes"].get("duration_test_us") == 3, partial["scan_axes"]
|
|
404
|
+
finally:
|
|
405
|
+
shutil.rmtree(tmp, ignore_errors=True)
|
|
347
406
|
|
|
348
407
|
|
|
349
408
|
def main():
|