Clone
3
Home
vps1_gitea_admin edited this page 2026-05-11 18:30:17 +00:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

reactor

Reflects code as of b8247fc · regenerated 2026-05-11 via npm run wiki:all If this banner is stale, the page may be out of date. Treat as informative, not authoritative.

1. What this node is

reactor is an S88 Unit that wraps an ASM3 biological-process engine — either a CSTR (fully mixed tank) or a PFR (plug-flow with axial dispersion). It integrates 13 species (S_O, S_NH, X_H, X_TS, …) and emits the effluent vector each tick. Drives a settler downstream and accepts a recirculation pump child.

2. Position in the platform

flowchart LR
    upstream[reactor<br/>upstream<br/>Unit]:::unit
    reactor[reactor<br/>Unit]:::unit
    settler[settler<br/>downstream<br/>Unit]:::unit
    pump[rotatingMachine<br/>downstream<br/>Equipment]:::equip
    tsens[measurement<br/>temperature<br/>atequipment]:::ctrl
    osens[measurement<br/>oxygen<br/>position]:::ctrl

    upstream -.stateChange.-> reactor
    reactor -->|Fluent inlet=0| settler
    pump -->|child.register downstream| reactor
    tsens -->|temperature.measured.atequipment| reactor
    osens -->|quantity (oxygen).measured.&lt;position&gt;| reactor
    classDef unit fill:#50a8d9,color:#000
    classDef equip fill:#86bbdd,color:#000
    classDef ctrl fill:#a9daee,color:#000

S88 colours: Unit #50a8d9, Equipment #86bbdd, Control Module #a9daee. Source of truth: .claude/rules/node-red-flow-layout.md.

3. Capability matrix

Capability Status Notes
ASM3 13-species ODE integration CSTR + PFR engines under kinetics/.
CSTR (fully mixed) Single concentration vector per tick.
PFR (axial discretization) resolution_L grid cells; emits GridProfile alongside Fluent.
Multi-inlet mixing n_inlets; each inlet receives its own data.fluent with inlet index.
Temperature reconcile from measurement temperature.measured.atEquipment writes engine.temperature.
Oxygen reconcile (PFR) quantity (oxygen).measured.<distance> maps to nearest grid cell.
KLa-driven aeration reactor.kla > 0 enables internal mass transfer; falls back to data.otr.
Speed-up factor (sim time) reactor.speedUpFactor accelerates wall-clock → process time.
Dispersion override (PFR) data.dispersion updates axial D.
Hot-swap engine type reactor_type is read once in configure().

4. Code map

flowchart TB
    subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
        nc["buildDomainConfig()<br/>static DomainClass = Reactor<br/>static commands"]
    end
    subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
        sc["Reactor.configure()<br/>flatten config → build engine<br/>ChildRouter rules"]
    end
    subgraph kinetics["src/kinetics/"]
        be["baseEngine.js<br/>shared ASM3 rate vector"]
        cstr["cstr.js<br/>0-D integrator"]
        pfr["pfr.js<br/>spatial discretization + dispersion"]
    end
    subgraph commands["src/commands/"]
        cmds["index.js + handlers.js<br/>6 input topics"]
    end
    sc --> be
    sc --> cstr
    sc --> pfr
    nc --> sc
    nc --> cmds
Module Owns Read first if you're changing…
kinetics/baseEngine.js ASM3 stoichiometry + rate vector + species list. Stoichiometric matrix, kinetic constants.
kinetics/cstr.js 0-D CSTR integrator + _connectMeasurement + _connectReactor. Mixed-tank behaviour, child wiring.
kinetics/pfr.js Axial discretization, dispersion, grid profile emission. PFR-specific behaviour, grid math.
commands/ 6 input descriptors + handlers (clock, fluent, OTR, temperature, dispersion, child). Inbound topic API, alias deprecation.
reaction_modules/ Optional plug-in reaction modules (legacy — not yet refactored). Adding new bio-process modules.
additional_nodes/ Sibling Node-RED nodes (recirculation-pump, settling-basin) shipped from this repo. Cross-node deploy in same package.

5. Topic contract

Auto-generated from src/commands/index.js. Do NOT hand-edit between the markers. Re-run npm run wiki:contract.

Canonical topic Aliases Payload Unit Effect
data.clock clock any Push the simulation clock tick (timestamp / dt) to the ASM solver.
data.fluent Fluent object Push the influent stream (payload: {F: flow m3/h, C: [concentrations mg/L]}).
data.otr OTR any Push the current oxygen-transfer rate into the reactor.
data.temperature Temperature any Push the current reactor temperature.
data.dispersion Dispersion any Push a dispersion/mixing parameter update.
child.register registerChild any Register a child node (settler / measurement) with this reactor.

6. Child registration

flowchart LR
    subgraph kids["accepted children (softwareType)"]
        m_t["measurement<br/>temperature"]:::ctrl
        m_o["measurement<br/>quantity (oxygen)"]:::ctrl
        r_up["reactor<br/>upstream"]:::unit
    end
    m_t -->|temperature.measured.atEquipment| h_meas[engine._connectMeasurement]
    m_o -->|quantity (oxygen).measured.&lt;pos&gt;| h_meas
    r_up -.stateChange.-> h_react[engine._connectReactor]
    h_meas --> reconcile[reconcile T / O2 into engine state]
    h_react --> pull[pull upstream effluent → Fs/Cs_in]
    classDef ctrl fill:#a9daee,color:#000
    classDef unit fill:#50a8d9,color:#000
softwareType filter wired to side-effect
measurement any engine._connectMeasurement temperature.measured.atEquipmentengine.temperature. PFR additionally honours quantity (oxygen).measured.<distance> → nearest grid cell DO.
reactor upstream engine._connectReactor Subscribes to upstream reactor's stateChange; pulls effluent into Fs[0] / Cs_in[0] before next integration step.

7. Lifecycle — what one data.clock advance does

sequenceDiagram
    participant clock as clock injector
    participant reactor as reactor
    participant engine as kinetics engine
    participant downstream as settler / next reactor
    participant out as Port-0 output

    clock->>reactor: data.clock { timestamp }
    reactor->>engine: updateState(timestamp)
    Note over engine: n_iter steps,<br/>each timeStep × speedUpFactor
    engine->>engine: integrate ASM3 rates
    engine->>engine: emit 'stateChange'
    reactor->>reactor: notifyOutputChanged
    reactor->>out: Fluent { inlet=0, F, C[13] }
    alt PFR
        reactor->>out: GridProfile { grid, n_x, d_x, … }
    end
    out->>downstream: Fluent envelope

stateChange re-emits on reactor.emitter (BaseDomain emitter) so downstream reactors / settlers can listen. The effluent emission goes through the BaseNodeAdapter tick pipeline.

8. Data model — getOutput()

Port-0 process payload is the Fluent envelope (+ optional GridProfile for PFR). Port-1 telemetry is the scalar snapshot below.

Key Type Unit Sample
S_HCO number 5
S_I number 30
S_N2 number 0
S_NH number 25
S_NO number 0
S_O number 0
S_S number 70
X_A number 200
X_H number 2000
X_I number 1000
X_S number 100
X_STO number 0
X_TS number 3500
flow_total number 0
temperature number 20

Concrete sample (CSTR mid-integration, nitrifying):

{
  "flow_total": 1000,
  "temperature": 15.2,
  "S_O": 2.1,
  "S_I": 30,
  "S_S": 12.4,
  "S_NH": 0.8,
  "S_N2": 4.3,
  "S_NO": 18.6,
  "S_HCO": 4.2,
  "X_I": 1050,
  "X_S": 65,
  "X_H": 2150,
  "X_STO": 4.5,
  "X_A": 215,
  "X_TS": 3680
}

Species ordering follows ASM3: indices 06 are soluble, 712 are particulate. flow_total is the effluent flow (m³/d); the reactor uses days as the time unit internally.

9. Configuration — editor form ↔ config keys

flowchart TB
    subgraph editor["Node-RED editor form"]
        f1[Reactor type CSTR / PFR]
        f2[Volume m3]
        f3[Length m + resolution]
        f4[Alpha dispersion]
        f5[KLa 1/h]
        f6[Time step + speed-up]
        f7[Initial state 13 species]
    end
    subgraph config["Domain config slice"]
        c1[reactor.reactor_type]
        c2[reactor.volume]
        c3[reactor.length<br/>reactor.resolution_L]
        c4[reactor.alpha]
        c5[reactor.kla]
        c6[reactor.timeStep<br/>reactor.speedUpFactor]
        c7[initialState.* ASM3 keys]
    end
    f1 --> c1
    f2 --> c2
    f3 --> c3
    f4 --> c4
    f5 --> c5
    f6 --> c6
    f7 --> c7
Form field Config key Default Range Where used
Reactor type reactor.reactor_type CSTR enum: CSTR / PFR engine selection in _buildEngine
Volume (m³) reactor.volume 1000 > 0 residence time, mass balance
Length (m) reactor.length 10 > 0 PFR only — axial extent
Resolution L reactor.resolution_L 10 ≥ 1 PFR grid cell count
Alpha reactor.alpha 0.5 01 dispersion vs plug-flow blend
Inlets reactor.n_inlets 1 ≥ 1 Fs[] / Cs_in[] array sizes
KLa (1/h) reactor.kla 0 ≥ 0 aeration mass transfer (NaN → use data.otr)
Time step (h) reactor.timeStep 0.001 ≥ 0.0001 integrator inner step
Speed-up factor reactor.speedUpFactor 1 ≥ 1 wall-clock → process-time multiplier
Initial S_NH initialState.S_NH 25 ≥ 0 (mg/L) starting ammonium
Initial X_H initialState.X_H 2000 ≥ 0 (mg/L) starting heterotroph biomass
Initial X_A initialState.X_A 200 ≥ 0 (mg/L) starting autotroph biomass — must be ≥ ~50 for nitrification
Initial X_TS initialState.X_TS 3500 ≥ 0 (mg/L) starting TSS — drives settler split

10. State chart

Skipped — reactor has no FSM. It runs continuous-state ODE integration; the engine's only stateful event is stateChange, fired after every successful integration advance. See section 7 for the integration sequence.

11. Examples

Tier File What it shows Status
Basic examples/basic.flow.json CSTR with one inlet, watch Fluent effluent in repo
Integration examples/integration.flow.json upstream reactor → reactor → settler chain in repo
Edge examples/edge.flow.json PFR with dispersion + multi-inlet in repo
Companions additional_nodes/* recirculation-pump + settling-basin Node-RED nodes shipped from this repo in repo

One screenshot per tier where helpful. PNG ≤ 200 KB under wiki/_partial-screenshots/reactor/.

12. Debug recipes

Symptom First thing to check Where to look
Nitrification doesn't proceed (S_NH stays high) initialState.X_A must be ≥ ~50 mg/L. Defaulting to 0.001 (a known footgun) means no autotrophs. generalFunctions/src/configs/reactor.json
Fluent effluent flow zero No data.clock ticks arriving, or data.fluent never set Fs[0] > 0. commands/handlers.js, engine setInfluent
PFR GridProfile not emitted reactor_type set to CSTR — only PFR emits grid. _buildEngine switch
Settler downstream not updating stateChange event listener path: settler must subscribe to reactor.emitter, NOT reactor.measurements.emitter. settler _connectReactor
Temperature reconcile silently ignored Child measurement's asset.type not temperature exactly, or positionVsParent not atEquipment. engine._connectMeasurement
Integrator slow / stalls reactor.timeStep too small for speedUpFactor. Internal n_iter count blows up. engine.updateState
wiki:datamodel script slow / hangs mathjs cold-start ~13 s; instantiation depends on it transitively. See known-limitations row 1. kinetics/baseEngine.js

13. When you would NOT use this node

  • Use reactor for ASM3 biological treatment modelling (activated sludge, nitrification, denitrification). For aerobic-only or simpler kinetics, the ASM3 species vector is overkill.
  • Don't use reactor for a passive equalisation tank — the kinetics engines assume reactions are happening.
  • Skip reactor when you only need a residence-time delay; a simple buffer node is lighter and doesn't require mathjs.

14. Known limitations / current issues

# Issue Tracked in
1 mathjs cold-start adds ~13 s to first require()wiki:datamodel auto-gen may time out on the 60 s wrapper. Falls back to the hand-curated concrete sample block. .claude/refactor/OPEN_QUESTIONS.md — "mathjs slow load"
2 initialState.X_A default of 200 mg/L is correct; older config snapshots used 0.001 which silently disabled nitrification. Verify on every new deploy. generalFunctions/src/configs/reactor.json
3 getEffluent shape historically varied (array vs single envelope) — settler's _connectReactor tolerates both. Don't break the contract without updating settler. nodes/settler/src/specificClass.js → _connectReactor
4 additional_nodes/recirculation-pump and settling-basin are legacy companions — not yet refactored to BaseDomain. P6.5 follow-up
5 reaction_modules/ is a legacy plug-in directory not consumed by the current engines. Removal pending. P6.5 follow-up