labcode 0.1.0__tar.gz → 0.1.2__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.1.0 → labcode-0.1.2}/PKG-INFO +4 -4
- {labcode-0.1.0 → labcode-0.1.2}/SPECIFICATIONS.md +54 -1
- labcode-0.1.2/examples/README.md +85 -0
- labcode-0.1.2/examples/outputs/plate_line.boundary.yaml +13 -0
- labcode-0.1.2/examples/outputs/plate_line.observation.yaml +202 -0
- labcode-0.1.2/examples/outputs/plate_line.plan.yaml +144 -0
- labcode-0.1.2/examples/outputs/plate_line.svg +2 -0
- labcode-0.1.2/examples/plate_line.boundary.yaml +18 -0
- {labcode-0.1.0 → labcode-0.1.2}/examples/plate_line.env.yaml +66 -6
- labcode-0.1.2/examples/plate_line.workflow.yaml +104 -0
- labcode-0.1.2/examples/render_plate_line.py +105 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode/backend.py +38 -11
- {labcode-0.1.0 → labcode-0.1.2}/labcode/dialect.py +11 -0
- labcode-0.1.2/labcode/idgen.py +63 -0
- labcode-0.1.2/labcode/objectid.py +231 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode/run_cli.py +16 -13
- labcode-0.1.2/labcode/runner.py +123 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/PKG-INFO +4 -4
- {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/SOURCES.txt +10 -0
- labcode-0.1.2/labcode.egg-info/requires.txt +11 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/scm_file_list.json +10 -0
- labcode-0.1.2/labcode.egg-info/scm_version.json +8 -0
- {labcode-0.1.0 → labcode-0.1.2}/pyproject.toml +9 -8
- {labcode-0.1.0 → labcode-0.1.2}/tests/test_backend.py +14 -9
- {labcode-0.1.0 → labcode-0.1.2}/tests/test_dialect.py +11 -0
- labcode-0.1.2/tests/test_objectid.py +137 -0
- {labcode-0.1.0 → labcode-0.1.2}/tests/test_run_cli.py +88 -21
- labcode-0.1.0/examples/README.md +0 -40
- labcode-0.1.0/examples/plate_line.workflow.yaml +0 -71
- labcode-0.1.0/labcode.egg-info/requires.txt +0 -11
- labcode-0.1.0/labcode.egg-info/scm_version.json +0 -8
- {labcode-0.1.0 → labcode-0.1.2}/.github/workflows/ci.yml +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/.github/workflows/publish.yml +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/.gitignore +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/LICENSE +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/README.md +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode/__init__.py +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode/__main__.py +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode/_child.py +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode/cli.py +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode/py.typed +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/dependency_links.txt +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/entry_points.txt +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/labcode.egg-info/top_level.txt +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/setup.cfg +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/tests/fixtures/device_script.env.yaml +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/tests/fixtures/device_script.workflow.yaml +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/tests/fixtures/transport.env.yaml +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/tests/fixtures/transport.workflow.yaml +0 -0
- {labcode-0.1.0 → labcode-0.1.2}/tests/test_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: labcode
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
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.3
|
|
24
|
+
Requires-Dist: ofplang-schedule>=0.1.3
|
|
25
|
+
Requires-Dist: ofplang-run>=0.1.10
|
|
26
26
|
Provides-Extra: test
|
|
27
27
|
Requires-Dist: pytest>=7.0; extra == "test"
|
|
28
28
|
Provides-Extra: dev
|
|
@@ -128,7 +128,60 @@ Cadence: the effective poll period is `poll_interval × seconds_per_tick`. labco
|
|
|
128
128
|
sub-second, which would flood the replan loop); `lc run --seconds-per-tick/--speed/
|
|
129
129
|
--poll-interval/--margin` override it.
|
|
130
130
|
|
|
131
|
-
## 4.
|
|
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)
|
|
132
185
|
|
|
133
186
|
- **Device / transporter `x-labcode`** — connection and availability information
|
|
134
187
|
(e.g. SiLA2 address) consolidated on `devices[]` / `transporters[]`, used for a
|
|
@@ -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
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
time:
|
|
2
|
+
unit: second
|
|
3
|
+
now: 14
|
|
4
|
+
interface:
|
|
5
|
+
inputs:
|
|
6
|
+
tube: rack.slot
|
|
7
|
+
outputs:
|
|
8
|
+
tube: rack.slot
|
|
9
|
+
activities:
|
|
10
|
+
- kind: transport
|
|
11
|
+
start: 0
|
|
12
|
+
end: 1
|
|
13
|
+
from_spot: rack.slot
|
|
14
|
+
to_spot: dispenser.tube
|
|
15
|
+
transporter: arm
|
|
16
|
+
arc:
|
|
17
|
+
from:
|
|
18
|
+
node: []
|
|
19
|
+
port: tube
|
|
20
|
+
to:
|
|
21
|
+
node:
|
|
22
|
+
- Dispense
|
|
23
|
+
port: tube
|
|
24
|
+
status: completed
|
|
25
|
+
- kind: processing
|
|
26
|
+
start: 0
|
|
27
|
+
end: 1
|
|
28
|
+
process: load
|
|
29
|
+
mode: m0
|
|
30
|
+
node:
|
|
31
|
+
- Load
|
|
32
|
+
devices:
|
|
33
|
+
- loader
|
|
34
|
+
output_spots:
|
|
35
|
+
plate: loader.stage
|
|
36
|
+
status: completed
|
|
37
|
+
- kind: transport
|
|
38
|
+
start: 1
|
|
39
|
+
end: 3
|
|
40
|
+
from_spot: loader.stage
|
|
41
|
+
to_spot: dispenser.deck
|
|
42
|
+
transporter: arm
|
|
43
|
+
arc:
|
|
44
|
+
from:
|
|
45
|
+
node:
|
|
46
|
+
- Load
|
|
47
|
+
port: plate
|
|
48
|
+
to:
|
|
49
|
+
node:
|
|
50
|
+
- Dispense
|
|
51
|
+
port: plate
|
|
52
|
+
status: completed
|
|
53
|
+
- kind: processing
|
|
54
|
+
start: 3
|
|
55
|
+
end: 5
|
|
56
|
+
process: dispense
|
|
57
|
+
mode: m0
|
|
58
|
+
node:
|
|
59
|
+
- Dispense
|
|
60
|
+
devices:
|
|
61
|
+
- dispenser
|
|
62
|
+
input_spots:
|
|
63
|
+
plate: dispenser.deck
|
|
64
|
+
tube: dispenser.tube
|
|
65
|
+
output_spots:
|
|
66
|
+
plate: dispenser.deck
|
|
67
|
+
tube: dispenser.tube
|
|
68
|
+
status: completed
|
|
69
|
+
- kind: transport
|
|
70
|
+
start: 5
|
|
71
|
+
end: 7
|
|
72
|
+
from_spot: dispenser.deck
|
|
73
|
+
to_spot: reader.stage
|
|
74
|
+
transporter: arm
|
|
75
|
+
arc:
|
|
76
|
+
from:
|
|
77
|
+
node:
|
|
78
|
+
- Dispense
|
|
79
|
+
port: plate
|
|
80
|
+
to:
|
|
81
|
+
node:
|
|
82
|
+
- Read
|
|
83
|
+
port: plate
|
|
84
|
+
status: completed
|
|
85
|
+
- kind: transport
|
|
86
|
+
start: 7
|
|
87
|
+
end: 9
|
|
88
|
+
from_spot: dispenser.tube
|
|
89
|
+
to_spot: rack.slot
|
|
90
|
+
transporter: arm
|
|
91
|
+
arc:
|
|
92
|
+
from:
|
|
93
|
+
node:
|
|
94
|
+
- Dispense
|
|
95
|
+
port: tube
|
|
96
|
+
to:
|
|
97
|
+
node: []
|
|
98
|
+
port: tube
|
|
99
|
+
status: completed
|
|
100
|
+
- kind: processing
|
|
101
|
+
start: 7
|
|
102
|
+
end: 10
|
|
103
|
+
process: read
|
|
104
|
+
mode: m0
|
|
105
|
+
node:
|
|
106
|
+
- Read
|
|
107
|
+
devices:
|
|
108
|
+
- reader
|
|
109
|
+
input_spots:
|
|
110
|
+
plate: reader.stage
|
|
111
|
+
output_spots:
|
|
112
|
+
plate: reader.stage
|
|
113
|
+
status: completed
|
|
114
|
+
- kind: transport
|
|
115
|
+
start: 10
|
|
116
|
+
end: 12
|
|
117
|
+
from_spot: reader.stage
|
|
118
|
+
to_spot: sealer.stage
|
|
119
|
+
transporter: arm
|
|
120
|
+
arc:
|
|
121
|
+
from:
|
|
122
|
+
node:
|
|
123
|
+
- Read
|
|
124
|
+
port: plate
|
|
125
|
+
to:
|
|
126
|
+
node:
|
|
127
|
+
- Store
|
|
128
|
+
port: plate
|
|
129
|
+
status: completed
|
|
130
|
+
- kind: processing
|
|
131
|
+
start: 12
|
|
132
|
+
end: 14
|
|
133
|
+
process: store
|
|
134
|
+
mode: m0
|
|
135
|
+
node:
|
|
136
|
+
- Store
|
|
137
|
+
devices:
|
|
138
|
+
- sealer
|
|
139
|
+
input_spots:
|
|
140
|
+
plate: sealer.stage
|
|
141
|
+
status: completed
|
|
142
|
+
objective:
|
|
143
|
+
kind: makespan
|
|
144
|
+
value: 14
|
|
@@ -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 296" width="784" height="296" 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 14 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="220.0"/><text fill="#666666" x="200.0" y="28.0" text-anchor="middle">0</text><line stroke="#b3b3b3" stroke-width="1" x1="280.0" y1="34.0" x2="280.0" y2="220.0"/><text fill="#666666" x="280.0" y="28.0" text-anchor="middle">2</text><line stroke="#b3b3b3" stroke-width="1" x1="360.0" y1="34.0" x2="360.0" y2="220.0"/><text fill="#666666" x="360.0" y="28.0" text-anchor="middle">4</text><line stroke="#b3b3b3" stroke-width="1" x1="440.0" y1="34.0" x2="440.0" y2="220.0"/><text fill="#666666" x="440.0" y="28.0" text-anchor="middle">6</text><line stroke="#b3b3b3" stroke-width="1" x1="520.0" y1="34.0" x2="520.0" y2="220.0"/><text fill="#666666" x="520.0" y="28.0" text-anchor="middle">8</text><line stroke="#b3b3b3" stroke-width="1" x1="600.0" y1="34.0" x2="600.0" y2="220.0"/><text fill="#666666" x="600.0" y="28.0" text-anchor="middle">10</text><line stroke="#b3b3b3" stroke-width="1" x1="680.0" y1="34.0" x2="680.0" y2="220.0"/><text fill="#666666" x="680.0" y="28.0" text-anchor="middle">12</text><line stroke="#b3b3b3" stroke-width="1" x1="760.0" y1="34.0" x2="760.0" y2="220.0"/><text fill="#666666" x="760.0" y="28.0" text-anchor="middle">14</text><text fill="#666666" x="200.0" y="240.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">dispenser</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">loader</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">rack</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="160.0" x2="784.0" y2="160.0"/><text fill="#1a1a1a" x="190.0" y="149.0" text-anchor="end">reader</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="190.0" x2="784.0" y2="190.0"/><text fill="#1a1a1a" x="190.0" y="179.0" text-anchor="end">sealer</text><line stroke="#d9d9d9" stroke-width="1" x1="0" y1="220.0" x2="784.0" y2="220.0"/><text fill="#1a1a1a" x="190.0" y="209.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="220.0"/><text fill="#dc2626" x="763.0" y="28.0">now=14</text><rect fill="#f59e0b" x="200.0" y="195.0" width="40.0" height="20" rx="3"/><text fill="#3a2a00" x="204.0" y="209.0">> D…</text><rect fill="#f59e0b" fill-opacity="0.2" x="200.0" y="105.0" width="40.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="200.0" y="45.0" width="40.0" height="20" rx="3"/><rect fill="#3b82f6" x="200.0" y="75.0" width="40.0" height="20" rx="3"/><text fill="#ffffff" x="204.0" y="89.0">Load</text><rect fill="#f59e0b" x="240.0" y="195.0" width="80.0" height="20" rx="3"/><text fill="#3a2a00" x="244.0" y="209.0">> Dispense</text><rect fill="#f59e0b" fill-opacity="0.2" x="240.0" y="75.0" width="80.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="240.0" y="45.0" width="80.0" height="20" rx="3"/><rect fill="#3b82f6" x="320.0" y="45.0" width="80.0" height="20" rx="3"/><text fill="#ffffff" x="324.0" y="59.0">Dispense</text><rect fill="#f59e0b" x="400.0" y="195.0" width="80.0" height="20" rx="3"/><text fill="#3a2a00" x="404.0" y="209.0">> Read</text><rect fill="#f59e0b" fill-opacity="0.2" x="400.0" y="45.0" width="80.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="400.0" y="135.0" width="80.0" height="20" rx="3"/><rect fill="#f59e0b" x="480.0" y="195.0" width="80.0" height="20" rx="3"/><text fill="#3a2a00" x="484.0" y="209.0">> transport</text><rect fill="#f59e0b" fill-opacity="0.2" x="480.0" y="45.0" width="80.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="480.0" y="105.0" width="80.0" height="20" rx="3"/><rect fill="#3b82f6" x="480.0" y="135.0" width="120.0" height="20" rx="3"/><text fill="#ffffff" x="484.0" y="149.0">Read</text><rect fill="#f59e0b" x="600.0" y="195.0" width="80.0" height="20" rx="3"/><text fill="#3a2a00" x="604.0" y="209.0">> Store</text><rect fill="#f59e0b" fill-opacity="0.2" x="600.0" y="135.0" width="80.0" height="20" rx="3"/><rect fill="#f59e0b" fill-opacity="0.2" x="600.0" y="165.0" width="80.0" height="20" rx="3"/><rect fill="#3b82f6" x="680.0" y="165.0" width="80.0" height="20" rx="3"/><text fill="#ffffff" x="684.0" y="179.0">Store</text></g></svg>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Run boundary (dev-notes D28) for plate_line.workflow.yaml.
|
|
2
|
+
#
|
|
3
|
+
# The whole workflow takes one `Tube` in and passes the same Tube out (plus the Pure
|
|
4
|
+
# Data measurement `od`). A boundary port has two halves: `spot` (where an Object sits)
|
|
5
|
+
# and `view` (its observable value).
|
|
6
|
+
#
|
|
7
|
+
# - `tube` is Object-bearing, so it MUST name a spot: it starts in the standalone tube
|
|
8
|
+
# rack (`rack.slot`, bound to no process). The arm carries it to the dispenser for the
|
|
9
|
+
# dispense and back. `Tube` has no view fields, so no `view` is supplied.
|
|
10
|
+
# - `od` is Pure Data (no spot); its produced view is filled in at run end.
|
|
11
|
+
# - the `tube` output is pinned back to its rack slot, so it round-trips (its view is
|
|
12
|
+
# filled in at run end).
|
|
13
|
+
boundary:
|
|
14
|
+
inputs:
|
|
15
|
+
tube: { spot: rack.slot }
|
|
16
|
+
outputs:
|
|
17
|
+
od: {}
|
|
18
|
+
tube: { spot: rack.slot }
|
|
@@ -3,16 +3,27 @@
|
|
|
3
3
|
# The workflow stays portable v0 (`ofplang-schedule` just tolerates the x- keys).
|
|
4
4
|
#
|
|
5
5
|
# Every process mode AND every transport route carries a script here. The process
|
|
6
|
-
# scripts are Object-bearing: `load` returns the new Plate's view; `
|
|
7
|
-
#
|
|
6
|
+
# scripts are Object-bearing: `load` returns the new Plate's view; `dispense` takes the
|
|
7
|
+
# Plate and the Tube and passes *both* through (objects.map), so its script returns
|
|
8
|
+
# nothing; `read` returns the measured `od` (the Plate is carried); `store` takes it.
|
|
8
9
|
# The transport scripts move the plate and may read its view (the barcode).
|
|
10
|
+
#
|
|
11
|
+
# Physical layout: the Plate is loaded on `loader`, dispensed into on the `dispenser`
|
|
12
|
+
# deck, read on `reader`, sealed on `sealer`; the `arm` moves it along. The source Tube
|
|
13
|
+
# starts in a standalone `rack` slot -- a plain holding location bound to no process --
|
|
14
|
+
# enters and leaves the run there (see plate_line.boundary.yaml), and the `arm` carries
|
|
15
|
+
# it to the dispenser's `tube` holder for the dispense and back to the rack afterwards.
|
|
9
16
|
time:
|
|
10
17
|
unit: second
|
|
11
18
|
|
|
12
19
|
devices:
|
|
13
|
-
- { id: loader,
|
|
14
|
-
- { id:
|
|
15
|
-
- { id:
|
|
20
|
+
- { id: loader, spots: [stage] }
|
|
21
|
+
- { id: dispenser, spots: [deck, tube] }
|
|
22
|
+
- { id: reader, spots: [stage] }
|
|
23
|
+
- { id: sealer, spots: [stage] }
|
|
24
|
+
# A tube rack: a plain holding location, used by no process. The source Tube lives
|
|
25
|
+
# here at the run boundary; the arm ferries it to/from the dispenser.
|
|
26
|
+
- { id: rack, spots: [slot] }
|
|
16
27
|
|
|
17
28
|
transporters:
|
|
18
29
|
- { id: arm }
|
|
@@ -20,7 +31,7 @@ transporters:
|
|
|
20
31
|
transports:
|
|
21
32
|
- transporter: arm
|
|
22
33
|
from: loader.stage
|
|
23
|
-
to:
|
|
34
|
+
to: dispenser.deck
|
|
24
35
|
duration: 2
|
|
25
36
|
x-labcode:
|
|
26
37
|
script:
|
|
@@ -31,6 +42,16 @@ transports:
|
|
|
31
42
|
barcode = (view or {}).get("barcode")
|
|
32
43
|
# a real arm would move the plate here; e.g. robot.move(from_spot, to_spot)
|
|
33
44
|
|
|
45
|
+
- transporter: arm
|
|
46
|
+
from: dispenser.deck
|
|
47
|
+
to: reader.stage
|
|
48
|
+
duration: 2
|
|
49
|
+
x-labcode:
|
|
50
|
+
script:
|
|
51
|
+
language: python
|
|
52
|
+
code: |
|
|
53
|
+
barcode = (view or {}).get("barcode")
|
|
54
|
+
|
|
34
55
|
- transporter: arm
|
|
35
56
|
from: reader.stage
|
|
36
57
|
to: sealer.stage
|
|
@@ -41,6 +62,30 @@ transports:
|
|
|
41
62
|
code: |
|
|
42
63
|
barcode = (view or {}).get("barcode")
|
|
43
64
|
|
|
65
|
+
# The source Tube's round trip: rack -> dispenser for the dispense, then back.
|
|
66
|
+
- transporter: arm
|
|
67
|
+
from: rack.slot
|
|
68
|
+
to: dispenser.tube
|
|
69
|
+
duration: 2
|
|
70
|
+
x-labcode:
|
|
71
|
+
script:
|
|
72
|
+
language: python
|
|
73
|
+
code: |
|
|
74
|
+
# Move the source tube to the dispenser. `view` is the Tube's view; it has no
|
|
75
|
+
# fields here. A real arm would grip and move the tube; e.g. robot.move(...).
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
- transporter: arm
|
|
79
|
+
from: dispenser.tube
|
|
80
|
+
to: rack.slot
|
|
81
|
+
duration: 2
|
|
82
|
+
x-labcode:
|
|
83
|
+
script:
|
|
84
|
+
language: python
|
|
85
|
+
code: |
|
|
86
|
+
# Return the tube to its rack slot.
|
|
87
|
+
pass
|
|
88
|
+
|
|
44
89
|
processes:
|
|
45
90
|
|
|
46
91
|
load:
|
|
@@ -55,6 +100,21 @@ processes:
|
|
|
55
100
|
# Create the Plate's view (its observable data). Returns the one output port.
|
|
56
101
|
return {"plate": {"barcode": "P001"}}
|
|
57
102
|
|
|
103
|
+
dispense:
|
|
104
|
+
modes:
|
|
105
|
+
- devices: [dispenser]
|
|
106
|
+
duration: 3
|
|
107
|
+
input_spots: { plate: dispenser.deck, tube: dispenser.tube }
|
|
108
|
+
output_spots: { plate: dispenser.deck, tube: dispenser.tube } # both stay in place
|
|
109
|
+
x-labcode:
|
|
110
|
+
script:
|
|
111
|
+
language: python
|
|
112
|
+
code: |
|
|
113
|
+
# `plate` and `tube` are the incoming Objects' views. A real dispenser would
|
|
114
|
+
# aspirate from the tube and dispense into the plate here. Both Objects are
|
|
115
|
+
# carried through by objects.map, so the script returns nothing.
|
|
116
|
+
return {}
|
|
117
|
+
|
|
58
118
|
read:
|
|
59
119
|
modes:
|
|
60
120
|
- devices: [reader]
|