labcode 0.0.1__tar.gz → 0.1.1__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.0.1 → labcode-0.1.1}/PKG-INFO +26 -9
- {labcode-0.0.1 → labcode-0.1.1}/README.md +22 -5
- labcode-0.1.1/SPECIFICATIONS.md +188 -0
- labcode-0.1.1/examples/README.md +85 -0
- labcode-0.1.1/examples/outputs/plate_line.boundary.yaml +13 -0
- labcode-0.1.1/examples/outputs/plate_line.observation.yaml +202 -0
- labcode-0.1.1/examples/outputs/plate_line.plan.yaml +144 -0
- labcode-0.1.1/examples/outputs/plate_line.svg +2 -0
- labcode-0.1.1/examples/plate_line.boundary.yaml +18 -0
- labcode-0.1.1/examples/plate_line.env.yaml +146 -0
- labcode-0.1.1/examples/plate_line.workflow.yaml +104 -0
- labcode-0.1.1/examples/render_plate_line.py +105 -0
- labcode-0.1.1/labcode/_child.py +62 -0
- labcode-0.1.1/labcode/backend.py +238 -0
- {labcode-0.0.1 → labcode-0.1.1}/labcode/cli.py +7 -5
- labcode-0.1.1/labcode/dialect.py +151 -0
- labcode-0.1.1/labcode/idgen.py +63 -0
- labcode-0.1.1/labcode/objectid.py +231 -0
- labcode-0.1.1/labcode/run_cli.py +189 -0
- labcode-0.1.1/labcode/runner.py +123 -0
- {labcode-0.0.1 → labcode-0.1.1}/labcode.egg-info/PKG-INFO +26 -9
- labcode-0.1.1/labcode.egg-info/SOURCES.txt +44 -0
- labcode-0.1.1/labcode.egg-info/requires.txt +11 -0
- labcode-0.1.1/labcode.egg-info/scm_file_list.json +40 -0
- labcode-0.1.1/labcode.egg-info/scm_version.json +8 -0
- {labcode-0.0.1 → labcode-0.1.1}/pyproject.toml +10 -7
- labcode-0.1.1/tests/fixtures/device_script.env.yaml +28 -0
- labcode-0.1.1/tests/fixtures/device_script.workflow.yaml +26 -0
- labcode-0.1.1/tests/fixtures/transport.env.yaml +42 -0
- labcode-0.1.1/tests/fixtures/transport.workflow.yaml +41 -0
- labcode-0.1.1/tests/test_backend.py +201 -0
- labcode-0.1.1/tests/test_dialect.py +104 -0
- labcode-0.1.1/tests/test_objectid.py +137 -0
- labcode-0.1.1/tests/test_run_cli.py +192 -0
- labcode-0.0.1/labcode.egg-info/SOURCES.txt +0 -19
- labcode-0.0.1/labcode.egg-info/requires.txt +0 -11
- labcode-0.0.1/labcode.egg-info/scm_file_list.json +0 -15
- labcode-0.0.1/labcode.egg-info/scm_version.json +0 -8
- {labcode-0.0.1 → labcode-0.1.1}/.github/workflows/ci.yml +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/.github/workflows/publish.yml +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/.gitignore +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/LICENSE +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/labcode/__init__.py +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/labcode/__main__.py +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/labcode/py.typed +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/labcode.egg-info/dependency_links.txt +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/labcode.egg-info/entry_points.txt +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/labcode.egg-info/top_level.txt +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/setup.cfg +0 -0
- {labcode-0.0.1 → labcode-0.1.1}/tests/test_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labcode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.1
|
|
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
|
|
@@ -20,9 +20,9 @@ Classifier: Typing :: Typed
|
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: ofplang-validate>=0.1.
|
|
24
|
-
Requires-Dist: ofplang-schedule>=0.1.
|
|
25
|
-
Requires-Dist: ofplang-run>=0.1.
|
|
23
|
+
Requires-Dist: ofplang-validate>=0.1.2
|
|
24
|
+
Requires-Dist: ofplang-schedule>=0.1.3
|
|
25
|
+
Requires-Dist: ofplang-run>=0.1.9
|
|
26
26
|
Provides-Extra: test
|
|
27
27
|
Requires-Dist: pytest>=7.0; extra == "test"
|
|
28
28
|
Provides-Extra: dev
|
|
@@ -43,14 +43,31 @@ ofplang toolchain and exposes it under a single command:
|
|
|
43
43
|
```sh
|
|
44
44
|
lc validate ... # check a workflow is well-formed portable v0
|
|
45
45
|
lc schedule ... # compute a schedule for a workflow
|
|
46
|
-
lc run ... # execute a workflow
|
|
46
|
+
lc run ... # execute a workflow on the labcode backend
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
labcode is where a site-specific dialect and a custom runner (real lab hardware)
|
|
50
|
-
are developed on top of the ofplang toolchain.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
are developed on top of the ofplang toolchain. `lc validate` and `lc schedule`
|
|
51
|
+
forward to the ofplang siblings unchanged; **`lc run` is the labcode dialect's own
|
|
52
|
+
runner**: it drives the workflow on the labcode backend, running each device
|
|
53
|
+
operation's script — supplied in the environment as an `x-labcode.script` extension
|
|
54
|
+
on a process mode — out-of-process on a wall clock, so a long-running real operation
|
|
55
|
+
never blocks the replan loop. See [`SPECIFICATIONS.md`](SPECIFICATIONS.md) for the
|
|
56
|
+
`x-labcode` extension.
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
# in the execution environment: how a (process, mode) is carried out
|
|
60
|
+
processes:
|
|
61
|
+
measure_od:
|
|
62
|
+
modes:
|
|
63
|
+
- id: v0
|
|
64
|
+
duration: 45
|
|
65
|
+
x-labcode:
|
|
66
|
+
script:
|
|
67
|
+
language: python
|
|
68
|
+
code: |
|
|
69
|
+
return {"od": read_plate(plate)}
|
|
70
|
+
```
|
|
54
71
|
|
|
55
72
|
## Install
|
|
56
73
|
|
|
@@ -10,14 +10,31 @@ ofplang toolchain and exposes it under a single command:
|
|
|
10
10
|
```sh
|
|
11
11
|
lc validate ... # check a workflow is well-formed portable v0
|
|
12
12
|
lc schedule ... # compute a schedule for a workflow
|
|
13
|
-
lc run ... # execute a workflow
|
|
13
|
+
lc run ... # execute a workflow on the labcode backend
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
labcode is where a site-specific dialect and a custom runner (real lab hardware)
|
|
17
|
-
are developed on top of the ofplang toolchain.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
are developed on top of the ofplang toolchain. `lc validate` and `lc schedule`
|
|
18
|
+
forward to the ofplang siblings unchanged; **`lc run` is the labcode dialect's own
|
|
19
|
+
runner**: it drives the workflow on the labcode backend, running each device
|
|
20
|
+
operation's script — supplied in the environment as an `x-labcode.script` extension
|
|
21
|
+
on a process mode — out-of-process on a wall clock, so a long-running real operation
|
|
22
|
+
never blocks the replan loop. See [`SPECIFICATIONS.md`](SPECIFICATIONS.md) for the
|
|
23
|
+
`x-labcode` extension.
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
# in the execution environment: how a (process, mode) is carried out
|
|
27
|
+
processes:
|
|
28
|
+
measure_od:
|
|
29
|
+
modes:
|
|
30
|
+
- id: v0
|
|
31
|
+
duration: 45
|
|
32
|
+
x-labcode:
|
|
33
|
+
script:
|
|
34
|
+
language: python
|
|
35
|
+
code: |
|
|
36
|
+
return {"od": read_plate(plate)}
|
|
37
|
+
```
|
|
21
38
|
|
|
22
39
|
## Install
|
|
23
40
|
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# labcode dialect specification
|
|
2
|
+
|
|
3
|
+
labcode is a dialect of the Object-Flow Programming Language (ofplang). A labcode
|
|
4
|
+
workflow **is** a portable v0 ofplang workflow; the dialect lives entirely in the
|
|
5
|
+
**execution environment** (§5), as an `x-labcode` extension that says *how* each device
|
|
6
|
+
operation is physically carried out. `lc run` drives the workflow on the labcode backend
|
|
7
|
+
(`ofplang.run.SubprocessBackend`), sourcing each operation's script from `x-labcode` and
|
|
8
|
+
running it out-of-process on a wall clock.
|
|
9
|
+
|
|
10
|
+
This document is the reference for the `x-labcode` extension; `labcode.dialect` is its
|
|
11
|
+
conformance validator, run at the `lc run` front door.
|
|
12
|
+
|
|
13
|
+
## 1. `x-labcode` on a process mode (P5)
|
|
14
|
+
|
|
15
|
+
An environment process mode (§5) may carry an `x-labcode` mapping. In this version it
|
|
16
|
+
holds a single key, `script`: the Python that carries out that `(process, mode)`.
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
processes:
|
|
20
|
+
measure_od:
|
|
21
|
+
modes:
|
|
22
|
+
- id: v0
|
|
23
|
+
devices: [reader]
|
|
24
|
+
duration: 45 # the scheduler's estimate; real time is the script's own
|
|
25
|
+
x-labcode:
|
|
26
|
+
script:
|
|
27
|
+
language: python # the only supported language
|
|
28
|
+
code: |
|
|
29
|
+
return {"od": read_plate(plate)}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`x-labcode` is tolerated (and ignored) by `ofplang-schedule` (>= 0.1.2): the environment
|
|
33
|
+
still validates and schedules as plain v0. Only labcode interprets it.
|
|
34
|
+
|
|
35
|
+
### 1.1 Shape
|
|
36
|
+
|
|
37
|
+
- `x-labcode` MUST be a mapping.
|
|
38
|
+
- `x-labcode.script`, if present, MUST be a mapping with:
|
|
39
|
+
- `language`: MUST be `python`.
|
|
40
|
+
- `code`: MUST be a string (an implementation-provided Python function body).
|
|
41
|
+
|
|
42
|
+
### 1.2 Calling convention (process)
|
|
43
|
+
|
|
44
|
+
The script runs as the body of a function whose parameters are the operation's **input
|
|
45
|
+
port names**, each bound to that port's view value (Pure Data or an Object's view record)
|
|
46
|
+
— as in a v0 §22 `python_script_processes` script. External `import` is allowed; there is
|
|
47
|
+
no sandbox.
|
|
48
|
+
|
|
49
|
+
**Partial outputs.** Unlike v0 §22.2 (which requires the script to return *every* output
|
|
50
|
+
exactly), a labcode process script `return`s only the outputs it **computes** — a subset.
|
|
51
|
+
The backend fills the rest:
|
|
52
|
+
|
|
53
|
+
- an Object output declared in `objects.map` is **carried from its input** (the same
|
|
54
|
+
Object, its view unchanged) — so a pass-through need not restate it;
|
|
55
|
+
- any other unset output gets a **typed default** for its type.
|
|
56
|
+
|
|
57
|
+
The script's returned values override these. Each returned value must conform to its port's
|
|
58
|
+
type, and **returning a name that is not a declared output is an error** (this catches a
|
|
59
|
+
typo'd output name). A §22.2-strict script — one that returns every output explicitly —
|
|
60
|
+
works unchanged.
|
|
61
|
+
|
|
62
|
+
So for `read` (input `plate`, outputs `plate` via `objects.map` + `od`), all three are
|
|
63
|
+
equivalent to returning `{"plate": plate, "od": 0.42}`… except the defaulted forms:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
return {"plate": plate, "od": 0.42} # explicit
|
|
67
|
+
return {"od": 0.42} # plate carried by objects.map
|
|
68
|
+
return {} # plate carried; od defaults to 0.0
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 1.3 `x-labcode` on a transport route
|
|
72
|
+
|
|
73
|
+
An environment `transports[]` route may carry an `x-labcode` with a `script`: the Python
|
|
74
|
+
that physically carries out that move (e.g. commanding a robot arm). Same shape as §1.1
|
|
75
|
+
(`language: python`, string `code`).
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
transports:
|
|
79
|
+
- transporter: arm
|
|
80
|
+
from: reader.stage
|
|
81
|
+
to: sealer.stage
|
|
82
|
+
duration: 3
|
|
83
|
+
x-labcode:
|
|
84
|
+
script:
|
|
85
|
+
language: python
|
|
86
|
+
code: |
|
|
87
|
+
grip = "gentle" if (view or {}).get("fragile") else "firm"
|
|
88
|
+
move_plate(from_spot, to_spot, grip=grip)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Calling convention (transport).** The script runs as a function body with these locals:
|
|
92
|
+
`from_spot`, `to_spot`, `transporter` (the physical route), and `view` — the view value of
|
|
93
|
+
the moved Object. `view` is **best-effort and MAY be `None`** (the runner resolves it from
|
|
94
|
+
the producing arc; when it cannot, it is `None`), so a script that reads it should tolerate
|
|
95
|
+
`None`. A transport script is **side-effect only**: its return value is ignored and no
|
|
96
|
+
output is verified. Success is "it ran without raising"; an exception is a graceful failure
|
|
97
|
+
(the move ends `failed`, no material is moved — the run stops).
|
|
98
|
+
|
|
99
|
+
A route with no `x-labcode.script` runs as a plain timed move — the runner's material
|
|
100
|
+
bookkeeping only, with no device command (a warned no-op for a real move, from != to).
|
|
101
|
+
|
|
102
|
+
## 2. Code source resolution and exclusivity
|
|
103
|
+
|
|
104
|
+
For a dispatched `(process, mode)`, labcode resolves the code to run in this order:
|
|
105
|
+
|
|
106
|
+
1. the mode's `x-labcode.script.code` (2 — the labcode device script), else
|
|
107
|
+
2. the workflow process's own `script.code` (1 — a v0 §22 script process), else
|
|
108
|
+
3. none — the operation runs as a **typed-default no-op** (its outputs are typed
|
|
109
|
+
defaults; a device not yet scripted).
|
|
110
|
+
|
|
111
|
+
**Exclusivity (error).** A process MUST NOT carry both a workflow `script` (1) and an env
|
|
112
|
+
`x-labcode.script` (2) on any of its modes; that is ambiguous and is rejected.
|
|
113
|
+
|
|
114
|
+
**Typed-default reachability (warning).** A process with neither (1) nor (2) on any mode
|
|
115
|
+
will run as a typed-default no-op. This is allowed — convenient while mocking a device —
|
|
116
|
+
but `lc run` warns about it, so an unimplemented device is not silently a no-op.
|
|
117
|
+
|
|
118
|
+
## 3. Execution model
|
|
119
|
+
|
|
120
|
+
Each dispatched operation runs in its own child process (real, wall-clock-paced); the
|
|
121
|
+
runner discovers completion by polling, so a multi-minute computation never blocks it.
|
|
122
|
+
The advisory `duration` is the scheduler's estimate; the real duration is the script's.
|
|
123
|
+
A script error (an exception, a wrong/ missing output name, a non-conformant value) is a
|
|
124
|
+
graceful runtime failure (§22.2): the operation ends `failed` and the run stops.
|
|
125
|
+
|
|
126
|
+
Cadence: the effective poll period is `poll_interval × seconds_per_tick`. labcode defaults
|
|
127
|
+
`seconds_per_tick` to ~20 s (so a real op is polled at an observable cadence, not
|
|
128
|
+
sub-second, which would flood the replan loop); `lc run --seconds-per-tick/--speed/
|
|
129
|
+
--poll-interval/--margin` override it.
|
|
130
|
+
|
|
131
|
+
## 4. Object identity — the reserved `_id` view key
|
|
132
|
+
|
|
133
|
+
labcode gives every Object a stable, value-layer identity so it can be traced across
|
|
134
|
+
steps and in the observation document. The identity lives in the Object's **view** under
|
|
135
|
+
the reserved key **`_id`** (a `String`). This is a dialect feature layered on portable
|
|
136
|
+
v0: the workflow the user writes carries no `_id`; `lc run` injects and mints it.
|
|
137
|
+
|
|
138
|
+
### 4.1 Type rewrite
|
|
139
|
+
|
|
140
|
+
Before running, `lc run` rewrites the workflow in memory: it adds `_id: { type: String }`
|
|
141
|
+
to the `view` of **every `domain: object` type** (creating `view` if the type had none).
|
|
142
|
+
`_id` is an ordinary legal v0 view field (a leading-underscore identifier, not reserved
|
|
143
|
+
in core, and a primitive `String`), so the rewritten document validates and schedules
|
|
144
|
+
unchanged, and the runner's closed-shape view conformance treats `_id` as a normal
|
|
145
|
+
declared field. labcode runs this rewritten document directly (no temp file:
|
|
146
|
+
`ofplang.run.run_workflow` accepts an in-memory document).
|
|
147
|
+
|
|
148
|
+
**Reserved (error).** A user type that itself declares a `_id` view field is rejected at
|
|
149
|
+
the dialect front door — labcode owns `_id`, and silently clobbering the field would be
|
|
150
|
+
worse than a clear error.
|
|
151
|
+
|
|
152
|
+
### 4.2 Where an id comes from
|
|
153
|
+
|
|
154
|
+
An Object's `_id` is set at its two points of origin, then **carried** everywhere else —
|
|
155
|
+
`objects.map` and transport copy the whole view, so `_id` propagates for free:
|
|
156
|
+
|
|
157
|
+
- **`objects.create`** — a newly created Object's `_id` is minted when the operation
|
|
158
|
+
produces it (in the backend's output fill). A device script need not know about `_id`:
|
|
159
|
+
it returns only what it computes, and the fill supplies `_id` (like any other unset
|
|
160
|
+
output, §1.2).
|
|
161
|
+
- **run boundary** — a whole-workflow Object *input* enters at the boundary; `lc run`
|
|
162
|
+
mints its `_id` (filling any other declared view field with a typed default so the
|
|
163
|
+
seeded value conforms), **unless the boundary already carries one** — so a result
|
|
164
|
+
boundary fed back in round-trips its ids.
|
|
165
|
+
- **`objects.map`** — a mapped Object output carries its input's `_id` unchanged
|
|
166
|
+
(identity preserved), even if a §22.2-strict script returned the port explicitly.
|
|
167
|
+
|
|
168
|
+
### 4.3 Reproducibility
|
|
169
|
+
|
|
170
|
+
Ids come from a swappable generator (`labcode.idgen.IdGenerator`). The default
|
|
171
|
+
(`SeededUuid4Generator`) mints **reproducible** uuid4-shaped ids from a seed and a
|
|
172
|
+
*provenance key* — the node instance + output port for a create, the port name for a
|
|
173
|
+
boundary input — **not** draw order. So the same workflow yields the same ids on every
|
|
174
|
+
run, and the wall-clock backend's jittering completion order cannot change them (which is
|
|
175
|
+
what keeps checked-in example observations stable). A real run wanting globally-unique
|
|
176
|
+
ids per physical Object swaps in `RealUuid4Generator` (via
|
|
177
|
+
`labcode_backend_factory(id_generator=...)`).
|
|
178
|
+
|
|
179
|
+
> The provenance key is the runner's node-instance identity + port. Today each create
|
|
180
|
+
> node runs once, so node-path + port is unique; when dynamic control flow (e.g.
|
|
181
|
+
> `do_while`) is added, that node-instance identity must include the iteration index so
|
|
182
|
+
> ids stay unique and reproducible.
|
|
183
|
+
|
|
184
|
+
## 5. Not yet in this version (roadmap)
|
|
185
|
+
|
|
186
|
+
- **Device / transporter `x-labcode`** — connection and availability information
|
|
187
|
+
(e.g. SiLA2 address) consolidated on `devices[]` / `transporters[]`, used for a
|
|
188
|
+
connect/command/disconnect wrapper and for `down_devices` availability probing.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# labcode examples
|
|
2
|
+
|
|
3
|
+
## `plate_line` — an Object-bearing line, driven by environment scripts
|
|
4
|
+
|
|
5
|
+
A `Plate` flows down a four-station line, a `Tube` is dispensed into it, and a
|
|
6
|
+
measurement is read off it:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
(in) tube ──────────────┐
|
|
10
|
+
▼
|
|
11
|
+
load ──[move]──> dispense ──[move]──> read ──[move]──> store
|
|
12
|
+
│ │
|
|
13
|
+
(out) tube (out) od
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- [`plate_line.workflow.yaml`](plate_line.workflow.yaml) is **portable ofplang v0** — it
|
|
17
|
+
says only *what* happens. The whole workflow takes one `Tube` in and passes the *same*
|
|
18
|
+
Tube back out, alongside the Pure Data measurement `od`. `dispense` and `read` are
|
|
19
|
+
**Object-bearing**: their Objects go in and the *same* Objects come out (`objects.map` —
|
|
20
|
+
identity preserved). The Plate is created inside the workflow by `load`; the Tube enters
|
|
21
|
+
at the run boundary.
|
|
22
|
+
- [`plate_line.boundary.yaml`](plate_line.boundary.yaml) is the **run boundary** (dev-notes
|
|
23
|
+
D28). It supplies the `Tube` input — an Object, so it names a `spot`: a standalone tube
|
|
24
|
+
`rack` slot bound to no process, where the Tube starts and returns (`Tube` has no view
|
|
25
|
+
fields, so no `view`). The arm carries it to the dispenser and back. It also names the
|
|
26
|
+
outputs `od` and `tube`, whose produced values are echoed in the result boundary.
|
|
27
|
+
- [`plate_line.env.yaml`](plate_line.env.yaml) is the **labcode environment** — it says
|
|
28
|
+
*how* each step is carried out, as an `x-labcode.script` (see [`../SPECIFICATIONS.md`](../SPECIFICATIONS.md)).
|
|
29
|
+
**Every process mode and every transport route carries a script**: `load` makes the
|
|
30
|
+
Plate, `dispense` dispenses the Tube into the Plate (both Objects are carried through by
|
|
31
|
+
`objects.map`, so its script returns nothing), `read` measures `od` (the Plate is
|
|
32
|
+
carried, so the script returns only what it computes — labcode's *partial outputs*, see
|
|
33
|
+
the spec), `store` takes it; the transport scripts perform the move and may read the
|
|
34
|
+
moved Plate's `view` (its barcode).
|
|
35
|
+
|
|
36
|
+
labcode runs each script **out-of-process** on a wall clock, discovering completion by
|
|
37
|
+
polling — so a real, slow device operation never blocks the runner.
|
|
38
|
+
|
|
39
|
+
### Run it
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
lc run examples/plate_line.workflow.yaml --env examples/plate_line.env.yaml \
|
|
43
|
+
--boundary examples/plate_line.boundary.yaml --seconds-per-tick 0.2
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`--seconds-per-tick` sets the real seconds per environment time tick; it defaults to a
|
|
47
|
+
coarse value suited to real hardware, so a small value here keeps the demo quick. The run
|
|
48
|
+
completes with every activity `completed`; the produced measurement (`od = 0.42`) can be
|
|
49
|
+
written out with `--boundary-out result.yaml`.
|
|
50
|
+
|
|
51
|
+
### Produce the outputs
|
|
52
|
+
|
|
53
|
+
[`render_plate_line.py`](render_plate_line.py) drives the same run on the labcode backend
|
|
54
|
+
and writes its artifacts under [`outputs/`](outputs/):
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
python examples/render_plate_line.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- [`outputs/plate_line.plan.yaml`](outputs/plate_line.plan.yaml) — the **final execution
|
|
61
|
+
schedule** (the §6/§7 status document: every activity, `completed`).
|
|
62
|
+
- [`outputs/plate_line.observation.yaml`](outputs/plate_line.observation.yaml) — the
|
|
63
|
+
**observation document** (D38): each completed activity's I/O views, as `lc run
|
|
64
|
+
--observation-out` would stream it.
|
|
65
|
+
- [`outputs/plate_line.svg`](outputs/plate_line.svg) — a **Gantt chart** of that schedule
|
|
66
|
+
(device view), drawn by the scheduler's visualizer. (The ofplang toolchain renders
|
|
67
|
+
SVG/HTML; open it in a browser, or convert to PNG with any SVG rasterizer.)
|
|
68
|
+
- [`outputs/plate_line.boundary.yaml`](outputs/plate_line.boundary.yaml) — the **result
|
|
69
|
+
boundary**, echoing the produced `od` and the returned `tube` (as `--boundary-out`
|
|
70
|
+
writes it).
|
|
71
|
+
|
|
72
|
+
Because the labcode backend runs each op out-of-process on a wall clock, the exact times
|
|
73
|
+
(and makespan) may vary slightly between runs; the sequence and produced values do not.
|
|
74
|
+
|
|
75
|
+
Every Object's view carries a reserved **`_id`** — labcode's implicit, value-layer Object
|
|
76
|
+
identity (see [`../SPECIFICATIONS.md`](../SPECIFICATIONS.md) §4). In the observation you can
|
|
77
|
+
follow the *same* Plate (one `_id`) from `load` through `dispense`/`read` to `store`, and
|
|
78
|
+
the Tube's `_id` round-trips from the input boundary to the output. The ids are
|
|
79
|
+
reproducible (a seeded, provenance-keyed generator), so these outputs are stable to
|
|
80
|
+
re-generate.
|
|
81
|
+
|
|
82
|
+
> The scripts here are mocks (they just return values / reference their locals). Replace a
|
|
83
|
+
> script's body with real device calls — e.g. `robot.move(from_spot, to_spot)` in a
|
|
84
|
+
> transport, or an instrument read in `read` — to drive real hardware; the code may
|
|
85
|
+
> `import` anything the host Python can.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
schema: ofplang-observation/v0
|
|
2
|
+
time:
|
|
3
|
+
unit: second
|
|
4
|
+
interface:
|
|
5
|
+
inputs:
|
|
6
|
+
tube: rack.slot
|
|
7
|
+
outputs:
|
|
8
|
+
tube: rack.slot
|
|
9
|
+
---
|
|
10
|
+
kind: transport
|
|
11
|
+
from_spot: rack.slot
|
|
12
|
+
to_spot: dispenser.tube
|
|
13
|
+
transporter: arm
|
|
14
|
+
arc:
|
|
15
|
+
from:
|
|
16
|
+
node: []
|
|
17
|
+
port: tube
|
|
18
|
+
to:
|
|
19
|
+
node:
|
|
20
|
+
- Dispense
|
|
21
|
+
port: tube
|
|
22
|
+
start: 0
|
|
23
|
+
end: 1
|
|
24
|
+
moved:
|
|
25
|
+
view:
|
|
26
|
+
_id: 60dfcfab-2234-4c90-b7aa-c8d984cbd3c5
|
|
27
|
+
---
|
|
28
|
+
kind: processing
|
|
29
|
+
process: load
|
|
30
|
+
mode: m0
|
|
31
|
+
node:
|
|
32
|
+
- Load
|
|
33
|
+
devices:
|
|
34
|
+
- loader
|
|
35
|
+
output_spots:
|
|
36
|
+
plate: loader.stage
|
|
37
|
+
start: 0
|
|
38
|
+
end: 1
|
|
39
|
+
inputs: {}
|
|
40
|
+
outputs:
|
|
41
|
+
plate:
|
|
42
|
+
view:
|
|
43
|
+
barcode: P001
|
|
44
|
+
_id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
|
|
45
|
+
---
|
|
46
|
+
kind: transport
|
|
47
|
+
from_spot: loader.stage
|
|
48
|
+
to_spot: dispenser.deck
|
|
49
|
+
transporter: arm
|
|
50
|
+
arc:
|
|
51
|
+
from:
|
|
52
|
+
node:
|
|
53
|
+
- Load
|
|
54
|
+
port: plate
|
|
55
|
+
to:
|
|
56
|
+
node:
|
|
57
|
+
- Dispense
|
|
58
|
+
port: plate
|
|
59
|
+
start: 1
|
|
60
|
+
end: 3
|
|
61
|
+
moved:
|
|
62
|
+
view:
|
|
63
|
+
barcode: P001
|
|
64
|
+
_id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
|
|
65
|
+
---
|
|
66
|
+
kind: processing
|
|
67
|
+
process: dispense
|
|
68
|
+
mode: m0
|
|
69
|
+
node:
|
|
70
|
+
- Dispense
|
|
71
|
+
devices:
|
|
72
|
+
- dispenser
|
|
73
|
+
input_spots:
|
|
74
|
+
plate: dispenser.deck
|
|
75
|
+
tube: dispenser.tube
|
|
76
|
+
output_spots:
|
|
77
|
+
plate: dispenser.deck
|
|
78
|
+
tube: dispenser.tube
|
|
79
|
+
start: 3
|
|
80
|
+
end: 5
|
|
81
|
+
inputs:
|
|
82
|
+
plate:
|
|
83
|
+
view:
|
|
84
|
+
barcode: P001
|
|
85
|
+
_id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
|
|
86
|
+
tube:
|
|
87
|
+
view:
|
|
88
|
+
_id: 60dfcfab-2234-4c90-b7aa-c8d984cbd3c5
|
|
89
|
+
outputs:
|
|
90
|
+
plate:
|
|
91
|
+
view:
|
|
92
|
+
barcode: P001
|
|
93
|
+
_id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
|
|
94
|
+
tube:
|
|
95
|
+
view:
|
|
96
|
+
_id: 60dfcfab-2234-4c90-b7aa-c8d984cbd3c5
|
|
97
|
+
---
|
|
98
|
+
kind: transport
|
|
99
|
+
from_spot: dispenser.deck
|
|
100
|
+
to_spot: reader.stage
|
|
101
|
+
transporter: arm
|
|
102
|
+
arc:
|
|
103
|
+
from:
|
|
104
|
+
node:
|
|
105
|
+
- Dispense
|
|
106
|
+
port: plate
|
|
107
|
+
to:
|
|
108
|
+
node:
|
|
109
|
+
- Read
|
|
110
|
+
port: plate
|
|
111
|
+
start: 5
|
|
112
|
+
end: 7
|
|
113
|
+
moved:
|
|
114
|
+
view:
|
|
115
|
+
barcode: P001
|
|
116
|
+
_id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
|
|
117
|
+
---
|
|
118
|
+
kind: transport
|
|
119
|
+
from_spot: dispenser.tube
|
|
120
|
+
to_spot: rack.slot
|
|
121
|
+
transporter: arm
|
|
122
|
+
arc:
|
|
123
|
+
from:
|
|
124
|
+
node:
|
|
125
|
+
- Dispense
|
|
126
|
+
port: tube
|
|
127
|
+
to:
|
|
128
|
+
node: []
|
|
129
|
+
port: tube
|
|
130
|
+
start: 7
|
|
131
|
+
end: 9
|
|
132
|
+
moved:
|
|
133
|
+
view:
|
|
134
|
+
_id: 60dfcfab-2234-4c90-b7aa-c8d984cbd3c5
|
|
135
|
+
---
|
|
136
|
+
kind: processing
|
|
137
|
+
process: read
|
|
138
|
+
mode: m0
|
|
139
|
+
node:
|
|
140
|
+
- Read
|
|
141
|
+
devices:
|
|
142
|
+
- reader
|
|
143
|
+
input_spots:
|
|
144
|
+
plate: reader.stage
|
|
145
|
+
output_spots:
|
|
146
|
+
plate: reader.stage
|
|
147
|
+
start: 7
|
|
148
|
+
end: 10
|
|
149
|
+
inputs:
|
|
150
|
+
plate:
|
|
151
|
+
view:
|
|
152
|
+
barcode: P001
|
|
153
|
+
_id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
|
|
154
|
+
outputs:
|
|
155
|
+
plate:
|
|
156
|
+
view:
|
|
157
|
+
barcode: P001
|
|
158
|
+
_id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
|
|
159
|
+
od:
|
|
160
|
+
view: 0.42
|
|
161
|
+
---
|
|
162
|
+
kind: transport
|
|
163
|
+
from_spot: reader.stage
|
|
164
|
+
to_spot: sealer.stage
|
|
165
|
+
transporter: arm
|
|
166
|
+
arc:
|
|
167
|
+
from:
|
|
168
|
+
node:
|
|
169
|
+
- Read
|
|
170
|
+
port: plate
|
|
171
|
+
to:
|
|
172
|
+
node:
|
|
173
|
+
- Store
|
|
174
|
+
port: plate
|
|
175
|
+
start: 10
|
|
176
|
+
end: 12
|
|
177
|
+
moved:
|
|
178
|
+
view:
|
|
179
|
+
barcode: P001
|
|
180
|
+
_id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
|
|
181
|
+
---
|
|
182
|
+
kind: processing
|
|
183
|
+
process: store
|
|
184
|
+
mode: m0
|
|
185
|
+
node:
|
|
186
|
+
- Store
|
|
187
|
+
devices:
|
|
188
|
+
- sealer
|
|
189
|
+
input_spots:
|
|
190
|
+
plate: sealer.stage
|
|
191
|
+
start: 12
|
|
192
|
+
end: 14
|
|
193
|
+
inputs:
|
|
194
|
+
plate:
|
|
195
|
+
view:
|
|
196
|
+
barcode: P001
|
|
197
|
+
_id: 7e50cfbd-8065-490c-a9ed-f4a50451bac1
|
|
198
|
+
outputs: {}
|
|
199
|
+
---
|
|
200
|
+
final: true
|
|
201
|
+
now: 14
|
|
202
|
+
outcome: completed
|