labcode 0.6.0__tar.gz → 0.8.0__tar.gz
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.
- {labcode-0.6.0 → labcode-0.8.0}/PKG-INFO +23 -3
- {labcode-0.6.0 → labcode-0.8.0}/README.md +20 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/README.md +102 -11
- labcode-0.8.0/examples/outputs/shared_bench.boundary.yaml +33 -0
- labcode-0.8.0/examples/outputs/shared_bench.observation.yaml +141 -0
- labcode-0.8.0/examples/outputs/shared_bench.plan.yaml +134 -0
- labcode-0.8.0/examples/outputs/shared_bench.svg +2 -0
- labcode-0.8.0/examples/render_shared_bench.py +109 -0
- labcode-0.8.0/examples/shared_bench.afternoon.boundary.yaml +15 -0
- labcode-0.8.0/examples/shared_bench.env.yaml +91 -0
- labcode-0.8.0/examples/shared_bench.morning.boundary.yaml +15 -0
- labcode-0.8.0/examples/shared_bench.run.yaml +25 -0
- labcode-0.8.0/examples/shared_bench.workflow.yaml +61 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/backend.py +28 -9
- {labcode-0.6.0 → labcode-0.8.0}/labcode/objectid.py +41 -12
- {labcode-0.6.0 → labcode-0.8.0}/labcode/record.py +5 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/run_cli.py +111 -24
- {labcode-0.6.0 → labcode-0.8.0}/labcode/runner.py +94 -16
- {labcode-0.6.0 → labcode-0.8.0}/labcode.egg-info/PKG-INFO +23 -3
- {labcode-0.6.0 → labcode-0.8.0}/labcode.egg-info/SOURCES.txt +12 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode.egg-info/requires.txt +2 -2
- {labcode-0.6.0 → labcode-0.8.0}/labcode.egg-info/scm_file_list.json +12 -0
- labcode-0.8.0/labcode.egg-info/scm_version.json +8 -0
- {labcode-0.6.0 → labcode-0.8.0}/pyproject.toml +12 -7
- labcode-0.8.0/tests/fixtures/joint.run.yaml +6 -0
- labcode-0.8.0/tests/test_joint.py +309 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_objectid.py +63 -0
- labcode-0.6.0/labcode.egg-info/scm_version.json +0 -8
- {labcode-0.6.0 → labcode-0.8.0}/.github/workflows/ci.yml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/.github/workflows/publish.yml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/.gitignore +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/LICENSE +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/MANIFEST.in +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/SPECIFICATIONS.md +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/outputs/plate_line.boundary.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/outputs/plate_line.observation.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/outputs/plate_line.plan.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/outputs/plate_line.svg +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/outputs/sila2_plate_cycle.boundary.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/outputs/sila2_plate_cycle.observation.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/outputs/sila2_plate_cycle.plan.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/outputs/sila2_plate_cycle.svg +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/plate_line.boundary.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/plate_line.env.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/plate_line.workflow.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/preflight_sila2_env.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/render_plate_line.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/render_sila2_plate_cycle.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/run_all_sila2_examples.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/run_sila2_plate_cycle.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/run_sila2_plate_cycle_no_atc.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/run_sila2_seal.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/sila2_plate_cycle.boundary.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/sila2_plate_cycle.workflow.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/sila2_plate_cycle.wrapped.env.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/sila2_plate_cycle_no_atc.boundary.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/sila2_plate_cycle_no_atc.workflow.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/sila2_plate_cycle_no_atc.wrapped.env.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/sila2_seal.boundary.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/sila2_seal.env.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/sila2_seal.workflow.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/examples/sila2_seal.wrapped.env.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/__init__.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/__main__.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/_child.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/cli.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/dialect.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/extension.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/idgen.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/otel.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/otel_sila2.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/probe.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/py.typed +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/sila2.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/sila2_commands.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode/sila2_instrument.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode.egg-info/dependency_links.txt +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode.egg-info/entry_points.txt +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/labcode.egg-info/top_level.txt +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/setup.cfg +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/fixtures/device_script.env.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/fixtures/device_script.workflow.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/fixtures/replenishment.env.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/fixtures/reroute_device.env.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/fixtures/reroute_transporter.env.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/fixtures/transport.env.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/fixtures/transport.workflow.yaml +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_backend.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_cli.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_dialect.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_otel.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_otel_child.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_otel_grpc.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_otel_sila2.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_probe.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_record.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_recording.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_run_cli.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_sila2.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_sila2_commands.py +0 -0
- {labcode-0.6.0 → labcode-0.8.0}/tests/test_sila2_instrument.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labcode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: labcode -- a dialect wrapper over the Object-Flow Programming Language toolchain
|
|
5
5
|
Author-email: Kazunari Kaizu <kwaizu@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,8 +21,8 @@ Requires-Python: >=3.10
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: ofplang-validate<0.3,>=0.2
|
|
24
|
-
Requires-Dist: ofplang-schedule<0.
|
|
25
|
-
Requires-Dist: ofplang-run<0.
|
|
24
|
+
Requires-Dist: ofplang-schedule<0.12,>=0.9
|
|
25
|
+
Requires-Dist: ofplang-run<0.13,>=0.11
|
|
26
26
|
Provides-Extra: test
|
|
27
27
|
Requires-Dist: pytest>=7.0; extra == "test"
|
|
28
28
|
Requires-Dist: opentelemetry-sdk>=1.20; extra == "test"
|
|
@@ -145,9 +145,29 @@ lc run <workflow> --env <env>
|
|
|
145
145
|
[--seconds-per-tick S] [--op-timeout S | --no-op-timeout] [--no-probe]
|
|
146
146
|
[--max-transport-legs N] [--ignore-resources] [--trace] [--mission-id ID]
|
|
147
147
|
[--object-ids seeded|real]
|
|
148
|
+
|
|
149
|
+
lc run --jobs <run doc> --env <env> [--on-job-failure continue|stop] [...]
|
|
148
150
|
```
|
|
149
151
|
|
|
150
152
|
- `<workflow>` — the portable v0 workflow: *what* happens.
|
|
153
|
+
- `--jobs RUNDOC` — run **several jobs together** in one laboratory (schedule SPEC
|
|
154
|
+
§6.11) in place of the single `<workflow>`. The run document names each job — an `id`,
|
|
155
|
+
its workflow, its own boundary, and when it may start — plus what the laboratory's own
|
|
156
|
+
stocks hold to begin with and which spots it is already holding. They are planned
|
|
157
|
+
*together*, so they compete for the same machines and share a refill neither needs
|
|
158
|
+
alone. Each job's workflow goes through both front doors separately and a rejection
|
|
159
|
+
names the job it came from; a boundary belongs to a job, so `--boundary` is not used
|
|
160
|
+
with `--jobs`.
|
|
161
|
+
🔴 Object identities are minted **per job**: two jobs of one workflow bind the same
|
|
162
|
+
port names and render the same node paths, so a reproducible generator keyed on those
|
|
163
|
+
alone would give one job's plate the other's `_id`. `--trace` records the job on every
|
|
164
|
+
operation (`ofp.job`) for the same reason — the node alone does not say whose work it
|
|
165
|
+
was. A run of a single workflow names no job and is recorded exactly as it always was.
|
|
166
|
+
- `--on-job-failure continue|stop` — what one job's failure does to the rest of a
|
|
167
|
+
`--jobs` run. `continue` (the default) stops that job alone and lets the others
|
|
168
|
+
finish — which is why they were planned together — while `stop` stops the whole run.
|
|
169
|
+
A stopped job's plate stays where it is, and the plan is made around it rather than
|
|
170
|
+
onto it. A single workflow is a single job, so this makes no difference to it.
|
|
151
171
|
- `--env` (required) — the labcode environment: the execution environment (spec §5) plus
|
|
152
172
|
the `x-labcode` extension saying *how* each operation is carried out.
|
|
153
173
|
- `--boundary DOC` — the whole-workflow I/O as one document: a `boundary:` mapping with a
|
|
@@ -101,9 +101,29 @@ lc run <workflow> --env <env>
|
|
|
101
101
|
[--seconds-per-tick S] [--op-timeout S | --no-op-timeout] [--no-probe]
|
|
102
102
|
[--max-transport-legs N] [--ignore-resources] [--trace] [--mission-id ID]
|
|
103
103
|
[--object-ids seeded|real]
|
|
104
|
+
|
|
105
|
+
lc run --jobs <run doc> --env <env> [--on-job-failure continue|stop] [...]
|
|
104
106
|
```
|
|
105
107
|
|
|
106
108
|
- `<workflow>` — the portable v0 workflow: *what* happens.
|
|
109
|
+
- `--jobs RUNDOC` — run **several jobs together** in one laboratory (schedule SPEC
|
|
110
|
+
§6.11) in place of the single `<workflow>`. The run document names each job — an `id`,
|
|
111
|
+
its workflow, its own boundary, and when it may start — plus what the laboratory's own
|
|
112
|
+
stocks hold to begin with and which spots it is already holding. They are planned
|
|
113
|
+
*together*, so they compete for the same machines and share a refill neither needs
|
|
114
|
+
alone. Each job's workflow goes through both front doors separately and a rejection
|
|
115
|
+
names the job it came from; a boundary belongs to a job, so `--boundary` is not used
|
|
116
|
+
with `--jobs`.
|
|
117
|
+
🔴 Object identities are minted **per job**: two jobs of one workflow bind the same
|
|
118
|
+
port names and render the same node paths, so a reproducible generator keyed on those
|
|
119
|
+
alone would give one job's plate the other's `_id`. `--trace` records the job on every
|
|
120
|
+
operation (`ofp.job`) for the same reason — the node alone does not say whose work it
|
|
121
|
+
was. A run of a single workflow names no job and is recorded exactly as it always was.
|
|
122
|
+
- `--on-job-failure continue|stop` — what one job's failure does to the rest of a
|
|
123
|
+
`--jobs` run. `continue` (the default) stops that job alone and lets the others
|
|
124
|
+
finish — which is why they were planned together — while `stop` stops the whole run.
|
|
125
|
+
A stopped job's plate stays where it is, and the plan is made around it rather than
|
|
126
|
+
onto it. A single workflow is a single job, so this makes no difference to it.
|
|
107
127
|
- `--env` (required) — the labcode environment: the execution environment (spec §5) plus
|
|
108
128
|
the `x-labcode` extension saying *how* each operation is carried out.
|
|
109
129
|
- `--boundary DOC` — the whole-workflow I/O as one document: a `boundary:` mapping with a
|
|
@@ -59,12 +59,24 @@ python examples/render_plate_line.py
|
|
|
59
59
|
|
|
60
60
|
🔴 **These are a record of one run, not a reproducible golden.** The labcode backend
|
|
61
61
|
runs its operations out of process against a **wall clock** (`seconds_per_tick`), so
|
|
62
|
-
which tick
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
which tick an operation finishes on varies between runs. Do not treat a difference here
|
|
63
|
+
as a regression, and do not gate a change on these files being byte-identical — the way
|
|
64
|
+
the scheduler's and the runner's own artifacts can be, both of those being driven by a
|
|
65
|
+
virtual clock.
|
|
66
|
+
|
|
67
|
+
Measured on one machine, three runs of the same commit: **the same nine activities
|
|
68
|
+
every time**, and a makespan of 144, 128 and 137. What moves is *when* each activity
|
|
69
|
+
happened and, because a rolling run replans against what it has observed, *the
|
|
70
|
+
arrangement* the plan settles on — one run loads the plate first, the next moves the
|
|
71
|
+
tube first. What does not move is **what ran**, the `_id` each Object carries, and the
|
|
72
|
+
values the scripts produced: `outputs/plate_line.boundary.yaml` came back **byte-identical
|
|
73
|
+
across all three**, being the one artifact here that carries no times at all.
|
|
74
|
+
|
|
75
|
+
🔴 It is not only the subprocesses. `LabcodeRunner` accepts an injected `monotonic` /
|
|
76
|
+
`sleep`, and a **scriptless** run driven by a fake clock still varied run to run
|
|
77
|
+
(measured) — so "inject a clock" is not the fix, and making labcode's execution output
|
|
78
|
+
deterministic is an open problem rather than an oversight. The plan the *scheduler*
|
|
79
|
+
produces from a given input is reproducible, and the tests are where that is pinned.
|
|
68
80
|
|
|
69
81
|
- [`outputs/plate_line.plan.yaml`](outputs/plate_line.plan.yaml) — the **final execution
|
|
70
82
|
schedule** (the §6/§7 status document: every activity, `completed`).
|
|
@@ -78,21 +90,100 @@ which the tests cover.
|
|
|
78
90
|
boundary**, echoing the produced `od` and the returned `tube` (as `--boundary-out`
|
|
79
91
|
writes it).
|
|
80
92
|
|
|
81
|
-
Because the labcode backend runs each op out-of-process on a wall clock, the exact times
|
|
82
|
-
(and makespan) may vary slightly between runs; the sequence and produced values do not.
|
|
83
|
-
|
|
84
93
|
Every Object's view carries a reserved **`_id`** — labcode's implicit, value-layer Object
|
|
85
94
|
identity (see [`../SPECIFICATIONS.md`](../SPECIFICATIONS.md) §4). In the observation you can
|
|
86
95
|
follow the *same* Plate (one `_id`) from `load` through `dispense`/`read` to `store`, and
|
|
87
96
|
the Tube's `_id` round-trips from the input boundary to the output. The ids are
|
|
88
|
-
reproducible (a seeded, provenance-keyed generator
|
|
89
|
-
re-generate
|
|
97
|
+
reproducible (a seeded, provenance-keyed generator, keyed **per job** in a `--jobs`
|
|
98
|
+
run), so the ids in these outputs are stable to re-generate even though their times are
|
|
99
|
+
not.
|
|
90
100
|
|
|
91
101
|
> The scripts here are mocks (they just return values / reference their locals). Replace a
|
|
92
102
|
> script's body with real device calls — e.g. `robot.move(from_spot, to_spot)` in a
|
|
93
103
|
> transport, or an instrument read in `read` — to drive real hardware; the code may
|
|
94
104
|
> `import` anything the host Python can.
|
|
95
105
|
|
|
106
|
+
## `shared_bench` — two jobs, one laboratory
|
|
107
|
+
|
|
108
|
+
`plate_line` runs one workflow. A bench does not: the morning's tubes and the
|
|
109
|
+
afternoon's are the same work on the same machines, and running them one after the other
|
|
110
|
+
leaves the reader idle while somebody fetches the next rack. `lc run --jobs` plans them
|
|
111
|
+
**together** (schedule SPEC §6.11), so the plan decides who gets the bench.
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
rack.slot_a ─┐ ┌─ rack.slot_a morning
|
|
115
|
+
├─ arm ─▶ reader.stage ┤
|
|
116
|
+
rack.slot_b ─┘ └─ rack.slot_b afternoon
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A slot per job, and **one reader and one arm** — the contention is the subject. Both jobs
|
|
120
|
+
run the *same* workflow file and differ only in their boundary: which tube, and which
|
|
121
|
+
slot it lives on.
|
|
122
|
+
|
|
123
|
+
- [`shared_bench.workflow.yaml`](shared_bench.workflow.yaml) — fetch a Tube, measure it,
|
|
124
|
+
put it back. Nothing about it is joint, which is the point: a workflow says what
|
|
125
|
+
happens to one piece of work.
|
|
126
|
+
- [`shared_bench.env.yaml`](shared_bench.env.yaml) — the laboratory, with an
|
|
127
|
+
`x-labcode.script` on the measure mode and on every route.
|
|
128
|
+
- [`shared_bench.run.yaml`](shared_bench.run.yaml) — the **run document**: the two jobs,
|
|
129
|
+
each naming its workflow and its boundary.
|
|
130
|
+
- [`shared_bench.morning.boundary.yaml`](shared_bench.morning.boundary.yaml) /
|
|
131
|
+
[`.afternoon.`](shared_bench.afternoon.boundary.yaml) — a boundary belongs to a *job*,
|
|
132
|
+
which is why the run document names one per job rather than the command line carrying
|
|
133
|
+
one for all of them.
|
|
134
|
+
|
|
135
|
+
### Run it
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
lc run --jobs examples/shared_bench.run.yaml --env examples/shared_bench.env.yaml --seed 0 --seconds-per-tick 0.2 --boundary-out /tmp/shared_bench.boundary.yaml
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
`--on-job-failure continue` (the default) stops a failing job alone and lets the other
|
|
142
|
+
finish — which is why they were planned together; `stop` stops the whole run.
|
|
143
|
+
|
|
144
|
+
### Produce the outputs
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
python examples/render_shared_bench.py
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- [`outputs/shared_bench.plan.yaml`](outputs/shared_bench.plan.yaml) — the final
|
|
151
|
+
schedule. Every activity carries the `job` it belongs to, and the roster at the top
|
|
152
|
+
carries each job's promised completion.
|
|
153
|
+
- [`outputs/shared_bench.svg`](outputs/shared_bench.svg) — the Gantt chart. The reader's
|
|
154
|
+
lane is where the joint plan shows: the two jobs take turns on it rather than one run
|
|
155
|
+
waiting for the other to end.
|
|
156
|
+
- [`outputs/shared_bench.observation.yaml`](outputs/shared_bench.observation.yaml) — the
|
|
157
|
+
observation document, each record naming its job.
|
|
158
|
+
- [`outputs/shared_bench.boundary.yaml`](outputs/shared_bench.boundary.yaml) — the result
|
|
159
|
+
boundary, **per job**.
|
|
160
|
+
|
|
161
|
+
🔴 **The result boundary is where to look.** Both jobs render the same node paths and
|
|
162
|
+
bind the same port names, so what tells their work apart is the job:
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
jobs:
|
|
166
|
+
morning:
|
|
167
|
+
boundary:
|
|
168
|
+
outputs:
|
|
169
|
+
sample: { spot: rack.slot_a, view: { label: morning, _id: d3eb614f-… } }
|
|
170
|
+
od: { view: 0.7 }
|
|
171
|
+
afternoon:
|
|
172
|
+
boundary:
|
|
173
|
+
outputs:
|
|
174
|
+
sample: { spot: rack.slot_b, view: { label: afternoon, _id: 9393759b-… } }
|
|
175
|
+
od: { view: 0.9 }
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Two tubes, two identities. labcode mints each Object's `_id` **per job** for exactly this
|
|
179
|
+
reason — a reproducible generator keyed on the node path alone would hand both tubes one
|
|
180
|
+
identity, silently and the same way on every run — and `--trace` records the job on every
|
|
181
|
+
operation (`ofp.job`) so a record says whose work it was. A run of a single workflow names
|
|
182
|
+
no job and is minted and recorded exactly as it always was.
|
|
183
|
+
|
|
184
|
+
The times here move between runs like every other labcode execution output (above); the
|
|
185
|
+
result boundary does not, carrying none.
|
|
186
|
+
|
|
96
187
|
## `sila2_seal` — the same idea, driven by real SiLA2 servers
|
|
97
188
|
|
|
98
189
|
Where `plate_line`'s scripts are mocks that only return values, this example's scripts open
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
jobs:
|
|
2
|
+
morning:
|
|
3
|
+
boundary:
|
|
4
|
+
inputs:
|
|
5
|
+
sample:
|
|
6
|
+
spot: rack.slot_a
|
|
7
|
+
view:
|
|
8
|
+
label: morning
|
|
9
|
+
_id: d3eb614f-1607-4f72-946c-4e7b4ff459b9
|
|
10
|
+
outputs:
|
|
11
|
+
sample:
|
|
12
|
+
spot: rack.slot_a
|
|
13
|
+
view:
|
|
14
|
+
label: morning
|
|
15
|
+
_id: d3eb614f-1607-4f72-946c-4e7b4ff459b9
|
|
16
|
+
od:
|
|
17
|
+
view: 0.7
|
|
18
|
+
afternoon:
|
|
19
|
+
boundary:
|
|
20
|
+
inputs:
|
|
21
|
+
sample:
|
|
22
|
+
spot: rack.slot_b
|
|
23
|
+
view:
|
|
24
|
+
label: afternoon
|
|
25
|
+
_id: 9393759b-b778-4196-8be5-392dc38f1f97
|
|
26
|
+
outputs:
|
|
27
|
+
sample:
|
|
28
|
+
spot: rack.slot_b
|
|
29
|
+
view:
|
|
30
|
+
label: afternoon
|
|
31
|
+
_id: 9393759b-b778-4196-8be5-392dc38f1f97
|
|
32
|
+
od:
|
|
33
|
+
view: 0.9
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
schema: ofplang-observation/v0
|
|
2
|
+
time:
|
|
3
|
+
unit: second
|
|
4
|
+
---
|
|
5
|
+
kind: transport
|
|
6
|
+
job: morning
|
|
7
|
+
from_spot: rack.slot_a
|
|
8
|
+
to_spot: reader.stage
|
|
9
|
+
transporter: arm
|
|
10
|
+
arc:
|
|
11
|
+
from:
|
|
12
|
+
node: []
|
|
13
|
+
port: sample
|
|
14
|
+
to:
|
|
15
|
+
node:
|
|
16
|
+
- Measure
|
|
17
|
+
port: sample
|
|
18
|
+
start: 0
|
|
19
|
+
end: 1
|
|
20
|
+
moved:
|
|
21
|
+
view:
|
|
22
|
+
label: morning
|
|
23
|
+
_id: d3eb614f-1607-4f72-946c-4e7b4ff459b9
|
|
24
|
+
---
|
|
25
|
+
kind: processing
|
|
26
|
+
job: morning
|
|
27
|
+
process: measure
|
|
28
|
+
mode: m0
|
|
29
|
+
node:
|
|
30
|
+
- Measure
|
|
31
|
+
devices:
|
|
32
|
+
- reader
|
|
33
|
+
input_spots:
|
|
34
|
+
sample: reader.stage
|
|
35
|
+
output_spots:
|
|
36
|
+
measured: reader.stage
|
|
37
|
+
start: 1
|
|
38
|
+
end: 3
|
|
39
|
+
inputs:
|
|
40
|
+
sample:
|
|
41
|
+
view:
|
|
42
|
+
label: morning
|
|
43
|
+
_id: d3eb614f-1607-4f72-946c-4e7b4ff459b9
|
|
44
|
+
outputs:
|
|
45
|
+
measured:
|
|
46
|
+
view:
|
|
47
|
+
label: morning
|
|
48
|
+
_id: d3eb614f-1607-4f72-946c-4e7b4ff459b9
|
|
49
|
+
od:
|
|
50
|
+
view: 0.7
|
|
51
|
+
---
|
|
52
|
+
kind: transport
|
|
53
|
+
job: morning
|
|
54
|
+
from_spot: reader.stage
|
|
55
|
+
to_spot: rack.slot_a
|
|
56
|
+
transporter: arm
|
|
57
|
+
arc:
|
|
58
|
+
from:
|
|
59
|
+
node:
|
|
60
|
+
- Measure
|
|
61
|
+
port: measured
|
|
62
|
+
to:
|
|
63
|
+
node: []
|
|
64
|
+
port: sample
|
|
65
|
+
start: 3
|
|
66
|
+
end: 5
|
|
67
|
+
moved:
|
|
68
|
+
view:
|
|
69
|
+
label: morning
|
|
70
|
+
_id: d3eb614f-1607-4f72-946c-4e7b4ff459b9
|
|
71
|
+
---
|
|
72
|
+
kind: transport
|
|
73
|
+
job: afternoon
|
|
74
|
+
from_spot: rack.slot_b
|
|
75
|
+
to_spot: reader.stage
|
|
76
|
+
transporter: arm
|
|
77
|
+
arc:
|
|
78
|
+
from:
|
|
79
|
+
node: []
|
|
80
|
+
port: sample
|
|
81
|
+
to:
|
|
82
|
+
node:
|
|
83
|
+
- Measure
|
|
84
|
+
port: sample
|
|
85
|
+
start: 5
|
|
86
|
+
end: 7
|
|
87
|
+
moved:
|
|
88
|
+
view:
|
|
89
|
+
label: afternoon
|
|
90
|
+
_id: 9393759b-b778-4196-8be5-392dc38f1f97
|
|
91
|
+
---
|
|
92
|
+
kind: processing
|
|
93
|
+
job: afternoon
|
|
94
|
+
process: measure
|
|
95
|
+
mode: m0
|
|
96
|
+
node:
|
|
97
|
+
- Measure
|
|
98
|
+
devices:
|
|
99
|
+
- reader
|
|
100
|
+
input_spots:
|
|
101
|
+
sample: reader.stage
|
|
102
|
+
output_spots:
|
|
103
|
+
measured: reader.stage
|
|
104
|
+
start: 7
|
|
105
|
+
end: 9
|
|
106
|
+
inputs:
|
|
107
|
+
sample:
|
|
108
|
+
view:
|
|
109
|
+
label: afternoon
|
|
110
|
+
_id: 9393759b-b778-4196-8be5-392dc38f1f97
|
|
111
|
+
outputs:
|
|
112
|
+
measured:
|
|
113
|
+
view:
|
|
114
|
+
label: afternoon
|
|
115
|
+
_id: 9393759b-b778-4196-8be5-392dc38f1f97
|
|
116
|
+
od:
|
|
117
|
+
view: 0.9
|
|
118
|
+
---
|
|
119
|
+
kind: transport
|
|
120
|
+
job: afternoon
|
|
121
|
+
from_spot: reader.stage
|
|
122
|
+
to_spot: rack.slot_b
|
|
123
|
+
transporter: arm
|
|
124
|
+
arc:
|
|
125
|
+
from:
|
|
126
|
+
node:
|
|
127
|
+
- Measure
|
|
128
|
+
port: measured
|
|
129
|
+
to:
|
|
130
|
+
node: []
|
|
131
|
+
port: sample
|
|
132
|
+
start: 9
|
|
133
|
+
end: 10
|
|
134
|
+
moved:
|
|
135
|
+
view:
|
|
136
|
+
label: afternoon
|
|
137
|
+
_id: 9393759b-b778-4196-8be5-392dc38f1f97
|
|
138
|
+
---
|
|
139
|
+
final: true
|
|
140
|
+
now: 10
|
|
141
|
+
outcome: completed
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
time:
|
|
2
|
+
unit: second
|
|
3
|
+
now: 10
|
|
4
|
+
jobs:
|
|
5
|
+
- id: morning
|
|
6
|
+
release: 0
|
|
7
|
+
bound: 10
|
|
8
|
+
fingerprint: ec69dad347c6ba08
|
|
9
|
+
interface:
|
|
10
|
+
inputs:
|
|
11
|
+
sample: rack.slot_a
|
|
12
|
+
outputs:
|
|
13
|
+
sample: rack.slot_a
|
|
14
|
+
- id: afternoon
|
|
15
|
+
release: 0
|
|
16
|
+
bound: 20
|
|
17
|
+
fingerprint: ec69dad347c6ba08
|
|
18
|
+
interface:
|
|
19
|
+
inputs:
|
|
20
|
+
sample: rack.slot_b
|
|
21
|
+
outputs:
|
|
22
|
+
sample: rack.slot_b
|
|
23
|
+
objective:
|
|
24
|
+
kind:
|
|
25
|
+
- completion_time_sum
|
|
26
|
+
- makespan
|
|
27
|
+
value:
|
|
28
|
+
- 15
|
|
29
|
+
- 10
|
|
30
|
+
activities:
|
|
31
|
+
- kind: transport
|
|
32
|
+
job: morning
|
|
33
|
+
status: completed
|
|
34
|
+
start: 0
|
|
35
|
+
end: 1
|
|
36
|
+
from_spot: rack.slot_a
|
|
37
|
+
to_spot: reader.stage
|
|
38
|
+
transporter: arm
|
|
39
|
+
arc:
|
|
40
|
+
from:
|
|
41
|
+
node: []
|
|
42
|
+
port: sample
|
|
43
|
+
to:
|
|
44
|
+
node:
|
|
45
|
+
- Measure
|
|
46
|
+
port: sample
|
|
47
|
+
seq: 0
|
|
48
|
+
- kind: processing
|
|
49
|
+
job: morning
|
|
50
|
+
status: completed
|
|
51
|
+
start: 1
|
|
52
|
+
end: 3
|
|
53
|
+
process: measure
|
|
54
|
+
mode: m0
|
|
55
|
+
node:
|
|
56
|
+
- Measure
|
|
57
|
+
devices:
|
|
58
|
+
- reader
|
|
59
|
+
input_spots:
|
|
60
|
+
sample: reader.stage
|
|
61
|
+
output_spots:
|
|
62
|
+
measured: reader.stage
|
|
63
|
+
- kind: transport
|
|
64
|
+
job: morning
|
|
65
|
+
status: completed
|
|
66
|
+
start: 3
|
|
67
|
+
end: 5
|
|
68
|
+
from_spot: reader.stage
|
|
69
|
+
to_spot: rack.slot_a
|
|
70
|
+
transporter: arm
|
|
71
|
+
arc:
|
|
72
|
+
from:
|
|
73
|
+
node:
|
|
74
|
+
- Measure
|
|
75
|
+
port: measured
|
|
76
|
+
to:
|
|
77
|
+
node: []
|
|
78
|
+
port: sample
|
|
79
|
+
seq: 0
|
|
80
|
+
- kind: transport
|
|
81
|
+
job: afternoon
|
|
82
|
+
status: completed
|
|
83
|
+
start: 5
|
|
84
|
+
end: 7
|
|
85
|
+
from_spot: rack.slot_b
|
|
86
|
+
to_spot: reader.stage
|
|
87
|
+
transporter: arm
|
|
88
|
+
arc:
|
|
89
|
+
from:
|
|
90
|
+
node: []
|
|
91
|
+
port: sample
|
|
92
|
+
to:
|
|
93
|
+
node:
|
|
94
|
+
- Measure
|
|
95
|
+
port: sample
|
|
96
|
+
seq: 0
|
|
97
|
+
- kind: processing
|
|
98
|
+
job: afternoon
|
|
99
|
+
status: completed
|
|
100
|
+
start: 7
|
|
101
|
+
end: 9
|
|
102
|
+
process: measure
|
|
103
|
+
mode: m0
|
|
104
|
+
node:
|
|
105
|
+
- Measure
|
|
106
|
+
devices:
|
|
107
|
+
- reader
|
|
108
|
+
input_spots:
|
|
109
|
+
sample: reader.stage
|
|
110
|
+
output_spots:
|
|
111
|
+
measured: reader.stage
|
|
112
|
+
- kind: transport
|
|
113
|
+
job: afternoon
|
|
114
|
+
status: completed
|
|
115
|
+
start: 9
|
|
116
|
+
end: 10
|
|
117
|
+
from_spot: reader.stage
|
|
118
|
+
to_spot: rack.slot_b
|
|
119
|
+
transporter: arm
|
|
120
|
+
arc:
|
|
121
|
+
from:
|
|
122
|
+
node:
|
|
123
|
+
- Measure
|
|
124
|
+
port: measured
|
|
125
|
+
to:
|
|
126
|
+
node: []
|
|
127
|
+
port: sample
|
|
128
|
+
seq: 0
|
|
129
|
+
meta:
|
|
130
|
+
workflow:
|
|
131
|
+
- <in-memory>
|
|
132
|
+
- <in-memory>
|
|
133
|
+
environment: C:\Users\kaizu\Documents\Python Scripts\ofplang-labcode\examples\shared_bench.env.yaml
|
|
134
|
+
status: <in-memory>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 784 206" width="784" height="206" font-family="system-ui, sans-serif" font-size="12"><text fill="#1a1a1a" font-size="15" font-weight="600" x="16" y="24">Schedule — device view</text><text fill="#666666" x="16" y="40">makespan 10 second</text><defs><marker id="ah" markerWidth="8" markerHeight="8" refX="7" refY="3" orient="auto"><path d="M0,0 L7,3 L0,6 Z" fill="#9333ea"/></marker></defs><g transform="translate(0,48)"><line stroke="#b3b3b3" stroke-width="1" x1="200.0" y1="34.0" x2="200.0" y2="130.0"/><text fill="#666666" x="200.0" y="28.0" text-anchor="middle">0</text><line stroke="#b3b3b3" stroke-width="1" x1="256.0" y1="34.0" x2="256.0" y2="130.0"/><text fill="#666666" x="256.0" y="28.0" text-anchor="middle">1</text><line stroke="#b3b3b3" stroke-width="1" x1="312.0" y1="34.0" x2="312.0" y2="130.0"/><text fill="#666666" x="312.0" y="28.0" text-anchor="middle">2</text><line stroke="#b3b3b3" stroke-width="1" x1="368.0" y1="34.0" x2="368.0" y2="130.0"/><text fill="#666666" x="368.0" y="28.0" text-anchor="middle">3</text><line stroke="#b3b3b3" stroke-width="1" x1="424.0" y1="34.0" x2="424.0" y2="130.0"/><text fill="#666666" x="424.0" y="28.0" text-anchor="middle">4</text><line stroke="#b3b3b3" stroke-width="1" x1="480.0" y1="34.0" x2="480.0" y2="130.0"/><text fill="#666666" x="480.0" y="28.0" text-anchor="middle">5</text><line stroke="#b3b3b3" stroke-width="1" x1="536.0" y1="34.0" x2="536.0" y2="130.0"/><text fill="#666666" x="536.0" y="28.0" text-anchor="middle">6</text><line stroke="#b3b3b3" stroke-width="1" x1="592.0" y1="34.0" x2="592.0" y2="130.0"/><text fill="#666666" x="592.0" y="28.0" text-anchor="middle">7</text><line stroke="#b3b3b3" stroke-width="1" x1="648.0" y1="34.0" x2="648.0" y2="130.0"/><text fill="#666666" x="648.0" y="28.0" text-anchor="middle">8</text><line stroke="#b3b3b3" stroke-width="1" x1="704.0" y1="34.0" x2="704.0" y2="130.0"/><text fill="#666666" x="704.0" y="28.0" text-anchor="middle">9</text><line stroke="#b3b3b3" stroke-width="1" x1="760.0" y1="34.0" x2="760.0" y2="130.0"/><text fill="#666666" x="760.0" y="28.0" text-anchor="middle">10</text><text fill="#666666" x="200.0" y="150.0">time (second)</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="70.0" x2="784.0" y2="70.0"/><text fill="#1a1a1a" x="190.0" y="59.0" text-anchor="end">rack</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="100.0" x2="784.0" y2="100.0"/><text fill="#1a1a1a" x="190.0" y="89.0" text-anchor="end">reader</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="130.0" x2="784.0" y2="130.0"/><text fill="#1a1a1a" x="190.0" y="119.0" text-anchor="end">arm (transporter)</text><line stroke="#dc2626" stroke-width="1.5" stroke-dasharray="4 3" x1="760.0" y1="34.0" x2="760.0" y2="130.0"/><text fill="#dc2626" x="763.0" y="28.0">now=10</text><rect fill="#f59e0b" x="200.0" y="105.0" width="56.0" height="20" rx="3"/><text fill="#3a2a00" x="204.0" y="119.0">> morn…</text><rect fill="#f59e0b" fill-opacity="0.2" x="200.0" y="45.0" width="56.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="200.0" y="75.0" width="56.0" height="20" rx="3"/><rect fill="#3b82f6" x="256.0" y="75.0" width="112.0" height="20" rx="3"/><text fill="#ffffff" x="260.0" y="89.0">morning:Measure</text><rect fill="#f59e0b" x="368.0" y="105.0" width="112.0" height="20" rx="3"/><text fill="#3a2a00" x="372.0" y="119.0">> morning:trans…</text><rect fill="#f59e0b" fill-opacity="0.2" x="368.0" y="75.0" width="112.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="368.0" y="45.0" width="112.0" height="20" rx="3"/><rect fill="#f59e0b" x="480.0" y="105.0" width="112.0" height="20" rx="3"/><text fill="#3a2a00" x="484.0" y="119.0">> afternoon:Mea…</text><rect fill="#f59e0b" fill-opacity="0.2" x="480.0" y="45.0" width="112.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="480.0" y="75.0" width="112.0" height="20" rx="3"/><rect fill="#3b82f6" x="592.0" y="75.0" width="112.0" height="20" rx="3"/><text fill="#ffffff" x="596.0" y="89.0">afternoon:Measu…</text><rect fill="#f59e0b" x="704.0" y="105.0" width="56.0" height="20" rx="3"/><text fill="#3a2a00" x="708.0" y="119.0">> afte…</text><rect fill="#f59e0b" fill-opacity="0.2" x="704.0" y="75.0" width="56.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="704.0" y="45.0" width="56.0" height="20" rx="3"/></g></svg>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Run the `shared_bench` example -- two jobs in one laboratory -- and write its outputs.
|
|
2
|
+
|
|
3
|
+
The companion of `render_plate_line.py`, driving `labcode.run_labcode` exactly as
|
|
4
|
+
`lc run --jobs examples/shared_bench.run.yaml --env examples/shared_bench.env.yaml`
|
|
5
|
+
does. Where that example runs one workflow down a line, this one runs the *same* workflow
|
|
6
|
+
twice at once and lets the plan sort out who gets the bench:
|
|
7
|
+
|
|
8
|
+
rack.slot_a ─┐ ┌─ rack.slot_a morning
|
|
9
|
+
├─ arm ─▶ reader.stage ┤
|
|
10
|
+
rack.slot_b ─┘ └─ rack.slot_b afternoon
|
|
11
|
+
|
|
12
|
+
One reader and one arm, a slot per job. The two jobs are planned **together** (schedule
|
|
13
|
+
SPEC §6.11), so they take turns on the reader instead of one run waiting for the other
|
|
14
|
+
to finish -- which is what planning them together is for.
|
|
15
|
+
|
|
16
|
+
🔴 What to look at: the two jobs run the same file, so every node path and port name
|
|
17
|
+
they render is identical. What tells their work apart is the **job** -- each Object's
|
|
18
|
+
`_id` is minted per job (see the printed result boundary: two different tubes), and
|
|
19
|
+
`--trace` records the job on every operation. Without that a reproducible generator
|
|
20
|
+
would hand both tubes one identity.
|
|
21
|
+
|
|
22
|
+
Run it:
|
|
23
|
+
|
|
24
|
+
python examples/render_shared_bench.py
|
|
25
|
+
|
|
26
|
+
It writes four artifacts under examples/outputs/:
|
|
27
|
+
- shared_bench.plan.yaml -- the final execution schedule (§6/§7), every activity
|
|
28
|
+
carrying the `job` it belongs to
|
|
29
|
+
- shared_bench.observation.yaml -- the observation document (D38), likewise per job
|
|
30
|
+
- shared_bench.svg -- a Gantt chart (device view): the reader's lane shows
|
|
31
|
+
the two jobs queueing for it
|
|
32
|
+
- shared_bench.boundary.yaml -- the result boundary, **per job**: each tube back on
|
|
33
|
+
its own slot, with its own `_id` and its own `od`
|
|
34
|
+
|
|
35
|
+
🔴 These are a record of one run, not a golden: labcode runs out of process on a wall
|
|
36
|
+
clock, so the times and the arrangement move between runs (examples/README.md). The one
|
|
37
|
+
artifact that does not is the result boundary, which carries no times.
|
|
38
|
+
|
|
39
|
+
Requires the sibling `ofplang-schedule` (the runner replans through it, and its
|
|
40
|
+
visualizer draws the SVG).
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
|
|
47
|
+
import yaml
|
|
48
|
+
from ofplang.run.runner import load_document, parse_run_document, serialize_document
|
|
49
|
+
from ofplang.schedule.scheduler.visualize import render_svg
|
|
50
|
+
|
|
51
|
+
from labcode.runner import run_labcode
|
|
52
|
+
|
|
53
|
+
HERE = Path(__file__).parent
|
|
54
|
+
OUT = HERE / "outputs"
|
|
55
|
+
RUN_DOCUMENT = HERE / "shared_bench.run.yaml"
|
|
56
|
+
ENVIRONMENT = HERE / "shared_bench.env.yaml"
|
|
57
|
+
|
|
58
|
+
# As in render_plate_line.py: a small real-seconds-per-tick keeps the out-of-process demo
|
|
59
|
+
# quick. The reader takes 6 ticks and each move 2, so two jobs sharing one reader is a
|
|
60
|
+
# few seconds of wall clock.
|
|
61
|
+
SECONDS_PER_TICK = 0.2
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main() -> None:
|
|
65
|
+
OUT.mkdir(exist_ok=True)
|
|
66
|
+
|
|
67
|
+
# The run document is the joint entry: which jobs, each one's workflow and boundary.
|
|
68
|
+
# Parsed the way `lc run --jobs` parses it, relative to this directory.
|
|
69
|
+
run_doc = parse_run_document(
|
|
70
|
+
load_document(str(RUN_DOCUMENT)), RUN_DOCUMENT.parent
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# `run_labcode` takes the roster in place of a workflow. It does per job what it does
|
|
74
|
+
# for a lone workflow -- inject `_id` into the Object types, mint the boundary's ids,
|
|
75
|
+
# share one IdGenerator with the backend -- and keys the minting by the job, so the
|
|
76
|
+
# two tubes are two tubes.
|
|
77
|
+
result = run_labcode(
|
|
78
|
+
list(run_doc.jobs),
|
|
79
|
+
str(ENVIRONMENT),
|
|
80
|
+
# No boundary for the run: a boundary belongs to a job, and each carries its own.
|
|
81
|
+
poll_interval=1,
|
|
82
|
+
running_task_margin=1,
|
|
83
|
+
random_seed=0,
|
|
84
|
+
seconds_per_tick=SECONDS_PER_TICK,
|
|
85
|
+
observation_out=str(OUT / "shared_bench.observation.yaml"),
|
|
86
|
+
# The default, spelled out because it is the interesting one here: if the morning
|
|
87
|
+
# tube is dropped, the afternoon's still gets measured.
|
|
88
|
+
on_job_failure="continue",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
status = result.status
|
|
92
|
+
status.setdefault("objective", {"kind": "makespan", "value": status.get("now")})
|
|
93
|
+
|
|
94
|
+
(OUT / "shared_bench.plan.yaml").write_text(
|
|
95
|
+
serialize_document(status), encoding="utf-8"
|
|
96
|
+
)
|
|
97
|
+
(OUT / "shared_bench.svg").write_text(
|
|
98
|
+
render_svg(status, view="device"), encoding="utf-8"
|
|
99
|
+
)
|
|
100
|
+
(OUT / "shared_bench.boundary.yaml").write_text(
|
|
101
|
+
serialize_document(result.result_boundary), encoding="utf-8"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
print(f"makespan: {status.get('now')}")
|
|
105
|
+
print(yaml.safe_dump(result.result_boundary, sort_keys=False).rstrip())
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
main()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# The afternoon job's boundary: its tube starts on `slot_b` and goes back there.
|
|
2
|
+
#
|
|
3
|
+
# A boundary belongs to a *job*, not to the run -- which is why a run document names one
|
|
4
|
+
# per job rather than the command line carrying one for all of them. The two jobs here
|
|
5
|
+
# differ in exactly this file: same workflow, same laboratory, different slot and
|
|
6
|
+
# different tube.
|
|
7
|
+
boundary:
|
|
8
|
+
inputs:
|
|
9
|
+
sample:
|
|
10
|
+
spot: rack.slot_b
|
|
11
|
+
view:
|
|
12
|
+
label: afternoon
|
|
13
|
+
outputs:
|
|
14
|
+
sample: { spot: rack.slot_b }
|
|
15
|
+
od: {}
|