2 Commits

Author SHA1 Message Date
znetsixe
7c8722b324 changed colours and icon based on s88 2025-10-14 13:52:55 +02:00
442ddc60ed Fix syntax error 2025-10-01 11:50:35 +02:00
2 changed files with 84 additions and 80 deletions

View File

@@ -1,9 +1,19 @@
<!--
| S88-niveau | Primair (blokkleur) | Tekstkleur |
| ---------------------- | ------------------- | ---------- |
| **Area** | `#0f52a5` | wit |
| **Process Cell** | `#0c99d9` | wit |
| **Unit** | `#50a8d9` | zwart |
| **Equipment (Module)** | `#86bbdd` | zwart |
| **Control Module** | `#a9daee` | zwart |
-->
<script src="/reactor/menu.js"></script> <script src="/reactor/menu.js"></script>
<script type="text/javascript"> <script type="text/javascript">
RED.nodes.registerType("reactor", { RED.nodes.registerType("reactor", {
category: "WWTP", category: "EVOLV",
color: "#c4cce0", color: "#50a8d9",
defaults: { defaults: {
name: { value: "" }, name: { value: "" },
reactor_type: { value: "CSTR", required: true }, reactor_type: { value: "CSTR", required: true },
@@ -39,7 +49,7 @@
outputs: 3, outputs: 3,
inputLabels: ["input"], inputLabels: ["input"],
outputLabels: ["process", "dbase", "parent"], outputLabels: ["process", "dbase", "parent"],
icon: "font-awesome/fa-recycle", icon: "font-awesome/fa-flask",
label: function() { label: function() {
return this.name || "Reactor"; return this.name || "Reactor";
}, },

View File

@@ -12,7 +12,6 @@ const math = create(all, mathConfig);
const S_O_INDEX = 0; const S_O_INDEX = 0;
const NUM_SPECIES = 13; const NUM_SPECIES = 13;
const BC_PADDING = 2;
const DEBUG = false; const DEBUG = false;
class Reactor { class Reactor {
@@ -28,10 +27,6 @@ class Reactor {
this.measurements = new MeasurementContainer(); this.measurements = new MeasurementContainer();
this.upstreamReactor = null; this.upstreamReactor = null;
this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility this.childRegistrationUtils = new childRegistrationUtils(this); // Child registration utility
this.parent = []; // Gets assigned via child registration
this.upstreamReactor = null;
this.downstreamReactor = null;
this.asm = new ASM3(); this.asm = new ASM3();
@@ -46,7 +41,7 @@ class Reactor {
this.currentTime = Date.now(); // milliseconds since epoch [ms] this.currentTime = Date.now(); // milliseconds since epoch [ms]
this.timeStep = 1 / (24*60*60) * this.config.timeStep; // time step in seconds, converted to days. this.timeStep = 1 / (24*60*60) * this.config.timeStep; // time step in seconds, converted to days.
this.speedUpFactor = 100; // speed up factor for simulation, 60 means 1 minute per simulated second this.speedUpFactor = 60; // speed up factor for simulation, 60 means 1 minute per simulated second
} }
/** /**
@@ -118,23 +113,24 @@ class Reactor {
} }
} }
_connectMeasurement(measurementChild) { _connectMeasurement(measurement) {
if (!measurementChild) { if (!measurement) {
this.logger.warn("Invalid measurement provided."); this.logger.warn("Invalid measurement provided.");
return; return;
} }
let position; let position;
if (measurementChild.config.functionality.distance !== 'undefined') { if (measurement.config.functionality.distance !== 'undefined') {
position = measurementChild.config.functionality.distance; position = measurement.config.functionality.distance;
} else { } else {
position = measurementChild.config.functionality.positionVsParent; position = measurement.config.functionality.positionVsParent;
} }
const measurementType = measurementChild.config.asset.type; const measurementType = measurement.config.asset.type;
const key = `${measurementType}_${position}`;
const eventName = `${measurementType}.measured.${position}`; const eventName = `${measurementType}.measured.${position}`;
// Register event listener for measurement updates // Register event listener for measurement updates
measurementChild.measurements.emitter.on(eventName, (eventData) => { measurement.measurements.emitter.on(eventName, (eventData) => {
this.logger.debug(`${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`); this.logger.debug(`${position} ${measurementType} from ${eventData.childName}: ${eventData.value} ${eventData.unit}`);
// Store directly in parent's measurement container // Store directly in parent's measurement container
@@ -149,16 +145,15 @@ class Reactor {
} }
_connectReactor(reactorChild) { _connectReactor(reactor) {
if (!reactorChild) { if (!reactor) {
this.logger.warn("Invalid reactor provided."); this.logger.warn("Invalid reactor provided.");
return; return;
} }
this.upstreamReactor = reactorChild; this.upstreamReactor = reactor;
reactorChild.downstreamReactor = this;
reactorChild.emitter.on("stateChange", (data) => { reactor.emitter.on("stateChange", (data) => {
this.logger.debug(`State change of upstream reactor detected.`); this.logger.debug(`State change of upstream reactor detected.`);
this.updateState(data); this.updateState(data);
}); });
@@ -251,15 +246,11 @@ class Reactor_PFR extends Reactor {
this.alpha = config.alpha; this.alpha = config.alpha;
this.state = Array.from(Array(this.n_x), () => config.initialState.slice()); this.state = Array.from(Array(this.n_x), () => config.initialState.slice())
this.extendedState = Array.from(Array(this.n_x + 2*BC_PADDING), () => new Array(NUM_SPECIES).fill(0));
// initialise extended state
this.state.forEach((row, i) => this.extendedState[i+BC_PADDING] = row);
this.D = 0.0; // axial dispersion [m2 d-1] this.D = 0.0; // axial dispersion [m2 d-1]
this.D_op = this._makeDoperator(); this.D_op = this._makeDoperator(true, true);
assertNoNaN(this.D_op, "Derivative operator"); assertNoNaN(this.D_op, "Derivative operator");
this.D2_op = this._makeD2operator(); this.D2_op = this._makeD2operator();
@@ -297,26 +288,25 @@ class Reactor_PFR extends Reactor {
* @returns {Array} - New reactor state. * @returns {Array} - New reactor state.
*/ */
tick(time_step) { tick(time_step) {
this._applyBoundaryConditions(); const dispersion = math.multiply(this.D / (this.d_x*this.d_x), this.D2_op, this.state);
const advection = math.multiply(-1 * math.sum(this.Fs) / (this.A*this.d_x), this.D_op, this.state);
const dispersion = math.multiply(this.D / (this.d_x*this.d_x), this.D2_op, this.extendedState); const reaction = this.state.map((state_slice) => this.asm.compute_dC(state_slice, this.temperature));
const advection = math.multiply(-1 * math.sum(this.Fs) / (this.A*this.d_x), this.D_op, this.extendedState); const transfer = Array.from(Array(this.n_x), () => new Array(NUM_SPECIES).fill(0));
const reaction = this.extendedState.map((state_slice) => this.asm.compute_dC(state_slice, this.temperature));
const transfer = Array.from(Array(this.n_x+2*BC_PADDING), () => new Array(NUM_SPECIES).fill(0));
if (isNaN(this.kla)) { // calculate OTR if kla is not NaN, otherwise use externally calculated OTR if (isNaN(this.kla)) { // calculate OTR if kla is not NaN, otherwise use externally calculated OTR
for (let i = BC_PADDING+1; i < BC_PADDING+this.n_x - 1; i++) { for (let i = 1; i < this.n_x - 1; i++) {
transfer[i][S_O_INDEX] = this.OTR * this.n_x/(this.n_x-2); transfer[i][S_O_INDEX] = this.OTR * this.n_x/(this.n_x-2);
} }
} else { } else {
for (let i = BC_PADDING+1; i < BC_PADDING+this.n_x - 1; i++) { for (let i = 1; i < this.n_x - 1; i++) {
transfer[i][S_O_INDEX] = this._calcOTR(this.extendedState[i][S_O_INDEX], this.temperature) * this.n_x/(this.n_x-2); transfer[i][S_O_INDEX] = this._calcOTR(this.state[i][S_O_INDEX], this.temperature) * this.n_x/(this.n_x-2);
} }
} }
const dC_total = math.multiply(math.add(dispersion, advection, reaction, transfer).slice(BC_PADDING, this.n_x+BC_PADDING), time_step); const dC_total = math.multiply(math.add(dispersion, advection, reaction, transfer), time_step);
const stateNew = math.add(this.state, dC_total); const stateNew = math.add(this.state, dC_total);
this._applyBoundaryConditions(stateNew);
if (DEBUG) { if (DEBUG) {
assertNoNaN(dispersion, "dispersion"); assertNoNaN(dispersion, "dispersion");
@@ -327,7 +317,6 @@ class Reactor_PFR extends Reactor {
} }
this.state = this._arrayClip2Zero(stateNew); this.state = this._arrayClip2Zero(stateNew);
this.state.forEach((row, i) => this.extendedState[i+BC_PADDING] = row);
return stateNew; return stateNew;
} }
@@ -346,51 +335,57 @@ class Reactor_PFR extends Reactor {
* Apply boundary conditions to the reactor state. * Apply boundary conditions to the reactor state.
* for inlet, apply generalised Danckwerts BC, if there is not flow, apply Neumann BC with no flux * for inlet, apply generalised Danckwerts BC, if there is not flow, apply Neumann BC with no flux
* for outlet, apply regular Danckwerts BC (Neumann BC with no flux) * for outlet, apply regular Danckwerts BC (Neumann BC with no flux)
* @param {Array} state - Current reactor state without enforced BCs.
*/ */
_applyBoundaryConditions() { _applyBoundaryConditions(state) {
if (this.upstreamReactor) { if (math.sum(this.Fs) > 0) { // Danckwerts BC
for (let i = 0; i < BC_PADDING; i++) { const BC_C_in = math.multiply(1 / math.sum(this.Fs), [this.Fs], this.Cs_in)[0];
this.extendedState[i] = this.upstreamReactor.state.at(i-BC_PADDING); const BC_dispersion_term = (1-this.alpha)*this.D*this.A/(math.sum(this.Fs)*this.d_x);
} state[0] = math.multiply(1/(1+BC_dispersion_term), math.add(BC_C_in, math.multiply(BC_dispersion_term, state[1])));
} else { } else {
if (math.sum(this.Fs) > 0) { // Danckwerts BC state[0] = state[1];
const BC_C_in = math.multiply(1 / math.sum(this.Fs), [this.Fs], this.Cs_in)[0];
const BC_dispersion_term = (1-this.alpha)*this.D*this.A/(math.sum(this.Fs)*this.d_x);
this.extendedState[BC_PADDING] = math.multiply(1/(1+BC_dispersion_term), math.add(BC_C_in, math.multiply(BC_dispersion_term, this.extendedState[BC_PADDING+1])));
this.extendedState[BC_PADDING-1] = math.add(math.multiply(2, this.extendedState[BC_PADDING]), math.multiply(-2, this.extendedState[BC_PADDING+2]), this.extendedState[BC_PADDING+3]);
} else {
for (let i = 0; i < BC_PADDING; i++) {
this.extendedState[i] = this.extendedState[BC_PADDING];
}
}
}
if (this.downstreamReactor) {
for (let i = 0; i < BC_PADDING; i++) {
this.extendedState[this.n_x+BC_PADDING+i] = this.downstreamReactor.state[i];
}
} else {
// Neumann BC (no flux)
for (let i = 0; i < BC_PADDING; i++) {
this.extendedState[BC_PADDING+this.n_x+i] = this.extendedState.at(-1-BC_PADDING);
}
} }
// Neumann BC (no flux)
state[this.n_x-1] = state[this.n_x-2];
} }
/** /**
* Create finite difference first derivative operator. * Create finite difference first derivative operator.
* @param {boolean} central - Use central difference scheme if true, otherwise use upwind scheme.
* @param {boolean} higher_order - Use higher order scheme if true, otherwise use first order scheme.
* @returns {Array} - First derivative operator matrix. * @returns {Array} - First derivative operator matrix.
*/ */
_makeDoperator() { // create gradient operator _makeDoperator(central = false, higher_order = false) { // create gradient operator
const D_size = this.n_x+2*BC_PADDING; if (higher_order) {
const I = math.resize(math.diag(Array(D_size).fill(1/12), -2), [D_size, D_size]); if (central) {
const A = math.resize(math.diag(Array(D_size).fill(-2/3), -1), [D_size, D_size]); const I = math.resize(math.diag(Array(this.n_x).fill(1/12), -2), [this.n_x, this.n_x]);
const B = math.resize(math.diag(Array(D_size).fill(2/3), 1), [D_size, D_size]); const A = math.resize(math.diag(Array(this.n_x).fill(-2/3), -1), [this.n_x, this.n_x]);
const C = math.resize(math.diag(Array(D_size).fill(-1/12), 2), [D_size, D_size]); const B = math.resize(math.diag(Array(this.n_x).fill(2/3), 1), [this.n_x, this.n_x]);
const D = math.add(I, A, B, C); const C = math.resize(math.diag(Array(this.n_x).fill(-1/12), 2), [this.n_x, this.n_x]);
// set by BCs elsewhere const D = math.add(I, A, B, C);
D.forEach((row, i) => i < BC_PADDING || i >= this.n_x+BC_PADDING ? row.fill(0) : row); const NearBoundary = Array(this.n_x).fill(0.0);
return D; NearBoundary[0] = -1/4;
NearBoundary[1] = -5/6;
NearBoundary[2] = 3/2;
NearBoundary[3] = -1/2;
NearBoundary[4] = 1/12;
D[1] = NearBoundary;
NearBoundary.reverse();
D[this.n_x-2] = math.multiply(-1, NearBoundary);
D[0] = Array(this.n_x).fill(0); // set by BCs elsewhere
D[this.n_x-1] = Array(this.n_x).fill(0);
return D;
} else {
throw new Error("Upwind higher order method not implemented! Use central scheme instead.");
}
} else {
const I = math.resize(math.diag(Array(this.n_x).fill(1 / (1+central)), central), [this.n_x, this.n_x]);
const A = math.resize(math.diag(Array(this.n_x).fill(-1 / (1+central)), -1), [this.n_x, this.n_x]);
const D = math.add(I, A);
D[0] = Array(this.n_x).fill(0); // set by BCs elsewhere
D[this.n_x-1] = Array(this.n_x).fill(0);
return D;
}
} }
/** /**
@@ -398,13 +393,12 @@ class Reactor_PFR extends Reactor {
* @returns {Array} - Second derivative operator matrix. * @returns {Array} - Second derivative operator matrix.
*/ */
_makeD2operator() { // create the central second derivative operator _makeD2operator() { // create the central second derivative operator
const D_size = this.n_x+2*BC_PADDING; const I = math.diag(Array(this.n_x).fill(-2), 0);
const I = math.diag(Array(D_size).fill(-2), 0); const A = math.resize(math.diag(Array(this.n_x).fill(1), 1), [this.n_x, this.n_x]);
const A = math.resize(math.diag(Array(D_size).fill(1), 1), [D_size, D_size]); const B = math.resize(math.diag(Array(this.n_x).fill(1), -1), [this.n_x, this.n_x]);
const B = math.resize(math.diag(Array(D_size).fill(1), -1), [D_size, D_size]);
const D2 = math.add(I, A, B); const D2 = math.add(I, A, B);
// set by BCs elsewhere D2[0] = Array(this.n_x).fill(0); // set by BCs elsewhere
D2.forEach((row, i) => i < BC_PADDING || i >= this.n_x+BC_PADDING ? row.fill(0) : row); D2[this.n_x - 1] = Array(this.n_x).fill(0);
return D2; return D2;
} }
} }