Files
reactor/wiki/Home.md
znetsixe cb49bb8b4d docs(wiki): full 5-page wiki matching the rotatingMachine reference format
Replaces the prior stub/partial wiki with a Home + Reference-{Architecture,
Contracts,Examples,Limitations} + _Sidebar structure. Topic-contract and
data-model sections wrapped in AUTOGEN markers for the future wiki-gen tool.
Source-vs-spec contradictions surfaced and flagged inline (not silently
fixed). Pending-review notes mark sections that need a full node review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:42:11 +02:00

183 lines
9.6 KiB
Markdown

# reactor
![code-ref](https://img.shields.io/badge/code--ref-0e34403-blue) ![s88](https://img.shields.io/badge/S88-Unit-50a8d9) ![status](https://img.shields.io/badge/status-pending--review-orange)
A `reactor` models a single biological-treatment tank governed by the ASM3 (Activated Sludge Model No.&nbsp;3) kinetics. It wraps either a CSTR (fully-mixed) or PFR (plug-flow with axial dispersion) integrator, accepts an influent stream + aeration rate, integrates the 13 ASM3 species each tick, and emits the effluent vector for the next Unit downstream (typically a `settler` or another `reactor`). A `diffuser` (Equipment Module) supplies aeration via `data.otr`; `measurement` children supply temperature and (PFR-only) dissolved-oxygen reconciliation.
> [!NOTE]
> Pending full node review (2026-05). Content reflects `CONTRACT.md` and current source only.
---
## At a glance
| Thing | Value |
|:---|:---|
| What it represents | One biological-treatment tank running ASM3 kinetics &mdash; aerated, anoxic, or anaerobic |
| S88 level | Unit |
| Use it when | You need an activated-sludge tank with nitrification / denitrification / heterotrophic growth modelled species-by-species |
| Don't use it for | Passive equalisation tanks (no reactions), simple residence-time delays (lighter buffer is better), aerobic-only contactors where ASM3's full 13-species vector is overkill |
| Children it accepts | `measurement` (temperature at equipment; PFR also: dissolved oxygen at numeric distance); upstream `reactor` |
| Parents / sinks it talks to | downstream `reactor` or `settler` (via `Fluent` on Port 0); `diffuser` pushes `data.otr` in |
---
## How it fits
```mermaid
flowchart LR
upstream[reactor<br/>upstream<br/>Unit]:::unit
rx[reactor<br/>Unit]:::unit
settler[settler<br/>downstream<br/>Unit]:::unit
diffuser[diffuser<br/>Equipment]:::equip
tsens[measurement<br/>temperature<br/>atEquipment]:::ctrl
osens[measurement<br/>quantity (oxygen)<br/>at numeric distance, PFR only]:::ctrl
upstream -.stateChange.-> rx
rx -->|Fluent inlet=0| settler
diffuser -->|data.otr| rx
tsens -.measured.-> rx
osens -.measured.-> rx
tsens -->|child.register| rx
osens -->|child.register| rx
upstream -->|child.register<br/>positionVsParent=upstream| rx
classDef unit fill:#50a8d9,color:#000
classDef equip fill:#86bbdd,color:#000
classDef ctrl fill:#a9daee,color:#000
```
S88 colours are anchored in `.claude/rules/node-red-flow-layout.md`.
reactor sits on lane **L4** (Unit). The `diffuser` (lane L3) is **not** a registered child &mdash; it just pushes aeration via the `data.otr` topic. A reactor chain (multi-stage treatment, e.g. anoxic &rarr; aerobic &rarr; aerobic) is built by registering each upstream reactor with `positionVsParent: 'upstream'`; downstream reactors then `getEffluent` from the upstream on every `stateChange`.
---
## Try it &mdash; 3-minute demo
Import the basic example flow, deploy, and watch a CSTR consume influent over the simulation clock.
```bash
curl -X POST -H 'Content-Type: application/json' \
--data @nodes/reactor/examples/basic.flow.json \
http://localhost:1880/flow
```
What to click after deploy (each inject maps one-to-one to a topic in [Reference &mdash; Contracts](Reference-Contracts#topic-contract)):
1. `data.fluent` &mdash; inject an influent stream `{inlet: 0, F: 1000, C: [...13 species...]}` (m³/d, mg/L). The 13 species follow ASM3 ordering.
2. `data.temperature` &mdash; set reactor temperature (default 20 &deg;C; nitrification rates depend on this).
3. `data.otr` (if `kla` is `NaN`) **or** rely on the configured `kla` for internal aeration.
4. `data.clock` &mdash; push wall-clock `msg.timestamp` to advance the integrator. The engine computes `n_iter = floor(speedUpFactor &times; &Delta;t_wall / timeStep_days)` internal Euler / FD steps and integrates them in one shot.
5. Watch Port 0 (`Fluent` envelope on every advance) and Port 1 (InfluxDB scalar fields: `flow_total`, `temperature`, `S_O`&hellip;`X_TS`).
> [!IMPORTANT]
> **GIF needed.** Demo recording of steps 1&ndash;5 with `S_NH` falling and `S_NO` rising (nitrification proceeding). Save as `wiki/_partial-gifs/reactor/01-basic-cstr.gif`, target &le; 1&nbsp;MB after `gifsicle -O3 --lossy=80`.
---
## The six things you'll send
| Topic | Aliases | Payload | What it does |
|:---|:---|:---|:---|
| `data.clock` | `clock` | `{timestamp: ms}` (or use `msg.timestamp`) | Advance the integrator. `updateState` computes how many internal steps fit between `currentTime` and the supplied timestamp (scaled by `speedUpFactor`) and runs them. |
| `data.fluent` | `Fluent` | `{inlet: number, F: number, C: number[13]}` | Set the per-inlet flow rate (`F`) and concentration vector (`C`). Stored in `engine.Fs[inlet]` / `engine.Cs_in[inlet]`. |
| `data.otr` | `OTR` | numeric | Set the externally-supplied oxygen transfer rate. Used when `kla` is `NaN`; ignored otherwise (internal mass transfer takes over). |
| `data.temperature` | `Temperature` | numeric or `{value: number}` | Set `engine.temperature` (&deg;C). Non-numeric payloads are warned and ignored. |
| `data.dispersion` | `Dispersion` | numeric | **PFR only** &mdash; set axial dispersion coefficient `D` (m²/d). Triggers Peclet / Courant guard warnings on the next `updateState`. |
| `child.register` | `registerChild` | child node id (string) | Register a sibling node (`measurement`, upstream `reactor`) with this reactor. Port 2 wiring does this automatically in normal flows. |
> [!NOTE]
> Pending full node review (2026-05). reactor's command surface is data-push only &mdash; there is **no FSM, no setpoint, no mode**. The kinetics engine runs continuous-state ODE / PDE integration; the only stateful event is `stateChange` after every successful advance.
---
## What you'll see come out
Sample Port 0 message (CSTR mid-integration, nitrifying):
```json
{
"topic": "Fluent",
"payload": {
"inlet": 0,
"F": 1000,
"C": [2.1, 30, 12.4, 0.8, 4.3, 18.6, 4.2, 1050, 65, 2150, 4.5, 215, 3680]
},
"timestamp": 1747500000000
}
```
The `C` array is the 13-species ASM3 vector in fixed order (indices 0&ndash;6 soluble, 7&ndash;12 particulate). For a PFR an additional message goes out on the same port **before** the effluent each advance:
```json
{
"topic": "GridProfile",
"payload": {
"grid": [[...13...], [...13...], "...n_x rows..."],
"n_x": 10,
"d_x": 1.0,
"length": 10,
"species": ["S_O","S_I","S_S","S_NH","S_N2","S_NO","S_HCO","X_I","X_S","X_H","X_STO","X_A","X_TS"],
"timestamp": 1747500000000
}
}
```
Port 1 (InfluxDB telemetry) carries the same data flattened as scalar fields &mdash; `flow_total` (m³/d), `temperature` (&deg;C), and one field per species (`S_O`, `S_I`, `S_S`, `S_NH`, `S_N2`, `S_NO`, `S_HCO`, `X_I`, `X_S`, `X_H`, `X_STO`, `X_A`, `X_TS`, mg/L; `S_HCO` is mmol/L).
| Field | Meaning |
|:---|:---|
| `S_O` | Dissolved oxygen. Capped to saturation at each tick via `_capDissolvedOxygen`. |
| `S_I` | Inert soluble COD. |
| `S_S` | Readily biodegradable substrate. |
| `S_NH` | Ammonium nitrogen. Drops during nitrification. |
| `S_N2` | Dinitrogen (denitrification end product). |
| `S_NO` | Nitrate / nitrite nitrogen. Rises during nitrification. |
| `S_HCO` | Alkalinity (bicarbonate, mmol/L). |
| `X_I` | Inert particulate COD. |
| `X_S` | Slowly biodegradable substrate. |
| `X_H` | Heterotrophic biomass. |
| `X_STO` | Stored COD in biomass. |
| `X_A` | Autotrophic biomass. **Must be &ge; ~50 mg/L for nitrification to proceed.** |
| `X_TS` | Total suspended solids. Drives the downstream settler split. |
| `flow_total` | Effluent volumetric flow (m³/d) &mdash; `sum(Fs)`. |
| `temperature` | Reactor temperature (&deg;C). |
---
## The interesting bits
### CSTR vs PFR
The engine is selected once at `configure()` from `reactor.reactor_type`. The same input topics drive both, but PFR additionally:
- Discretises the tank along the `length` axis into `resolution_L` grid cells (`n_x`).
- Emits a `GridProfile` message **before** the effluent each `updateState`.
- Honours `data.dispersion` to set the axial dispersion coefficient.
- Reconciles oxygen measurements at a **numeric** `positionVsParent` (interpreted as distance from inlet) into the nearest grid cell.
- Warns when local Peclet &ge; 2 or Courant &ge; 0.5 (stability of the explicit FD scheme).
Hot-swapping engine type at runtime is not supported &mdash; redeploy the flow.
### Aeration: internal `kla` vs external `data.otr`
`reactor.kla > 0` enables internal mass-transfer: `OTR = kla &times; (sat(T) &minus; S_O)`. Set `kla = NaN` to fall through to the externally-pushed `data.otr` value (the path a `diffuser` Equipment node uses).
### `X_A` footgun
The HTML editor form's default initial autotroph biomass is `0.001` mg/L &mdash; effectively zero, so nitrification never starts. The JSON schema default is `200` mg/L. Always check the deployed node's form value before expecting `S_NH` to drop. See [Reference &mdash; Limitations](Reference-Limitations#x_a-initial-default-footgun).
---
## Need more?
| Page | What you'll find |
|:---|:---|
| [Reference &mdash; Contracts](Reference-Contracts) | Full topic contract, config schema, child registration filters |
| [Reference &mdash; Architecture](Reference-Architecture) | Code map, integration sequence, kinetics layout, output ports |
| [Reference &mdash; Examples](Reference-Examples) | Shipped example flows + debug recipes |
| [Reference &mdash; Limitations](Reference-Limitations) | When not to use, known limitations, open questions |
[EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home) &middot; [Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) &middot; [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)