old version

This commit is contained in:
2026-03-12 15:20:34 +00:00
parent c4dda5955f
commit 71d600b4dd
5 changed files with 1016 additions and 0 deletions

589
diffuser_class.js Normal file
View File

@@ -0,0 +1,589 @@
/*
Copyright:
Year : (c) 2023
Author : Rene De Ren
Contact details : zn375ix3@gmail.com
Location : The Netherlands
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The author shall be notified of any and all improvements or adaptations this software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
//Diffised Aeration Devices (example fine bubble aeration)
class Diffuser{
/*------------------- Construct and set vars -------------------*/
constructor(supplier,type) {
this.init = false;
/* --------------------load depenencies ------------- */
this.Interpolation = require('../../predict/dependencies/interpolation') ; //load class
this.interpolation = new this.Interpolation; //general use of interpolation object
this.Fysics = require('./../../convert/dependencies/fysics') ; //load class
this.fysics = new this.Fysics; //general use of fysics
this.Graph = require('./graph') ; //load class
this.graph = new this.Graph; //general use of fysics
//load default pressure curve depending on type
this.specs = this.load_specs();
//after loading specs load curve builder
this.Predict = require('../../predict/dependencies/predict_class') ; //load class
this.predict_otr = new this.Predict(); //load otr curve
this.predict_otr.i_curve = this.specs.otr_curve;
this.predict_p = new this.Predict();
this.predict_p.i_curve = this.specs.p_curve; // load pressure curve
this.predict_p.i_f = 0; // set f to dim 0 because there is no other dim for pressure
this.predictO2saturation = new this.Predict();
this.predictO2saturation.i_curve = this.fysics.o2Solubility; // load solubility curve for o2 in water
//load converter ./dependencies/index
this.convert = require('./../../convert/dependencies/index');
/* ------------ static vars ---------------*/
this.name = ""; // user defined name
this.number = 0 ; // user defined number
this.id = ""; // unique id from node red?
this.idle = true; // is this idle (not outputting kg o2 / h)
this.desc = "diffuser"; // description of the current object
this.supplier = supplier; // supplier of diffuser
this.type = type; // type of diffuser
/* ---------- Inputs -------------- */
this.i_flow = 0 ; // input actual flow rate expressed in Nm3/h
this.i_diff_density = 5; // intput actual density of diffusers
this.i_pressure = 0 ; // this is pressure in header expressed in mbar to calculated air density.
this.i_local_atm_pressure = 1013,25 ; // local atm pressure in mbar
this.i_alfa_factor = 0.7 ; // stnd alfa factor
this.i_water_density = 997 ; // this.water_molar_mass * this.num_moles_water // water density in kg/m3;
this.i_m_water = 0; // input actual height in meter water ABOVE the diffuser
this.i_n_elements = 1; // input for amount of diffusers we need to divide the flow over to get the nm per element
/*---------calculated parameters ----------*/
this.n_flow = 0 ; // converted input to normalized conditions of the diffusers flow parameter (x)
this.n_kg = this.fysics.calc_air_dens(this.specs.units.Nm3.pressure,this.specs.units.Nm3.RH,this.specs.units.Nm3.temp); // calculated normalized kg for air density depending on specs kg /m3
/* ---------- Outputs -------------- */
this.o_otr = 0; // predicted oxygen transfer rate
this.o_p_flow = 0; // predicted pressure loss over flow rate
this.o_p_water = 0; // predicted pressure loss over meter water above diffuser
this.o_p_total = 0; // predicted total pressure loss
this.o_kg = 0; // predicted kg of air
this.o_kg_h = 0; // predicted kg per hour
this.o_kgo2_h = 0; // predicted oxygen transfer per hour
this.o_kgo2_h_min = 0; // predicted min kgo2 / hour for zone controller
this.o_kgo2_h_max = 0; // predicted max kgo2 / hour for zone controller
this.o_kgo2 = 0 ; // current oxygen input
this.o_flow_element = 0 ; // flow per element
this.o_otr_max = 0; // store max otr for easy access
this.o_otr_min = 0; // store min otr for easy access
this.o_combined_eff = 0; // combined efficiency
this.o_histogram = 0; // keep track of histogram x % of time for OTR (efficiency) over a max historical value ? or a counter which gets higher over time without.
this.o_slope = 0;
/*-----------alarms---------------*/
//putting alarms in 1 array always gets all the alarms that are currently active or inactive
//an alarm is a trigger to stop any process feeding the diffusers
this.alarm = {
text:[],
state:false,
flow:{
min:{state:false,hyst:10}, //when there isnt enough flow to ensure the correct distribution of air
max:{state:false,hyst:10},//when there is to much flow per diffuser and exceeds the suppliers limits
},
pressure:{
min:{state:false,hyst:10}, // see min flow
max:{state:false,hyst:10}, // see max flow
},
};
/*-----------warnings---------------*/
//putting alarms in 1 array always gets all the alarms that are currently active or inactive
//a warning is a trigger to alert users on eradic behavior or some other activity that might cause damage in the future so users can investigate what is going on
this.warning = {
text:[], // fill this with the warnings we want to display
state:false,
deviation:{
pressure:{state:false,hyst:2},//when there is a deviation versus expected pressures of the baseline that exceeds a hyst of x % of deviation before becomming true
},
flow:{
min:{state:false,hyst:2}, //when there isnt enough flow to ensure the correct distribution of air
max:{state:false,hyst:2},//when there is to much flow per diffuser and exceeds the suppliers limits
},
pressure:{
min:{state:false,hyst:2}, // see min flow
max:{state:false,hyst:2}, // see max flow
},
};
/*-------------error handling -----------*/
this.error = {
text:[],
state:false,
}
this.init = true;
}
/*------------------- GETTER/SETTERS -------------------*/
set i_n_elements(x){
//check if this input is a number
if(Number.isNaN(x)){
this.error.state = true;
this.error.text.push("number of elements not of type number");
}
// you cant have partial elements
x = Math.round(x);
//check if init has allready been exectued
if ( x <= 0 ) {
this.error.state = true;
this.error.text.push("0 elements input");
}
//set densi3ty to value
this._i_n_elements = x;
}
get i_n_elements(){
return this._i_n_elements;
}
get i_diff_density(){
return this._i_diff_density;
}
set i_diff_density(value){
//set densi3ty to value
this._i_diff_density = value;
//check if init has allready been exectued
if(this.init == true){
//submit new value to prediction
this.predict_otr.i_f = value;
//refresh predictions
this.o_otr = this.predict_otr.o_y;
}
}
set o_otr(value){
//set density to value
this._o_otr = Math.round( value * 100 ) / 100;
//check if init has allready been exectued
if(this.init == true){
//current output in kg o2 / h
this.o_kgo2_h = this.convert ( this.o_otr * this.n_flow * this.i_m_water ).from('g').to('kg') ;
/*make max and min calculations to use them in a kg load zone controller*/
this.o_kgo2_h_min = this.convert ( this.o_otr_min * this.n_flow * this.i_m_water ).from('g').to('kg') ;
this.o_kgo2_h_max = this.convert ( this.o_otr_max * this.n_flow * this.i_m_water ).from('g').to('kg') ;
//divide by 3600 to get the current ouput in kg
this.o_kgo2 = this.o_kgo2_h / 3600 ;
}
}
get o_otr(){
return this._o_otr;
}
//set meter water column above the diffuser in meters
set i_m_water(value){
//set density to value
this._i_m_water = value;
//convert height to pressure in mbar
this.o_p_water = this.fysics.heigth_to_pressure(this.i_water_density,value);
this.o_p_total = this.o_p_water + this.o_p_flow ;
//recalc values
this.i_flow = this.i_flow;
}
get i_m_water(){
return this._i_m_water
}
set o_kgo2_h(value){
this._o_kgo2_h = Math.round( value * 100 ) / 100;
}
get o_kgo2_h(){
return this._o_kgo2_h;
}
set o_p_total(value){
this._o_p_total = Math.round( value * 100 ) / 100;
}
get o_p_total(){
return this._o_p_total;
}
set o_p_flow(value){
//set density to value
this._o_p_flow = Math.round( value * 100 ) / 100;
//recalc total
this.o_p_total = this.o_p_water + value ;
}
get o_p_flow(){
return this._o_p_flow;
}
set i_flow(value){
//set density to value
this._i_flow = value;
if(this.init == true){
//any flow smaller or equal to zero means diffusers are not being supplied with air and are not active.
if(value > 0){
// idle to off
this.idle = false;
//calc otr and p
this.calc_otr_p(value);
}
else{
this.idle = true;
this.o_otr = 0;
this.o_p_flow = 0;
this.o_flow_element = 0;
this.o_p_total = 0;
}
}
}
get i_flow(){
return this._i_flow;
}
/*------------------------- functions ----------------------------*/
//a diffuser has a combined efficiency
combine_eff(o_otr,o_otr_min,o_otr_max,o_p_flow,o_p_min,o_p_max){
//highest otr is best efficiency possible
let eff1 = this.interpolation.interpolate_lin_single_point(o_otr,o_otr_min,o_otr_max,0,1);
//lowest pressure is best pressure possible
let eff2 = this.interpolation.interpolate_lin_single_point(o_p_flow,o_p_min,o_p_max,1,0);
let result = eff1 * eff2 * 100 ;
return result;
}
// do all actions in order to calculate the outputs for flow and pressure related stuf
calc_otr_p(flow){
//convert to kg using pressure,rh,temperature we need to get actual data to do this else we assume that normalized equals site
//total input pressure equals atm pressure + measured pressure in system
let tot_i_pressure = this.convert(this.i_local_atm_pressure + this.i_pressure).from('mbar').to('bar'); //calculated output in kg air
this.o_kg = this.fysics.calc_air_dens(tot_i_pressure,0,20);
this.o_kg_h = this.o_kg * flow ;
//convert this to the normal m3 of diffuser density data
this.n_flow = ( this.o_kg / this.n_kg ) * flow;
//calculate how much flow per element
this.o_flow_element = Math.round( ( this.n_flow / this.i_n_elements ) * 100 ) / 100 ;
// input x values in predictors (we could make functions and return outputs dunno whats better)
this.predict_otr.i_x = this.o_flow_element;
this.predict_p.i_x = this.o_flow_element;
//store otr max and min
this.o_otr_min = this.predict_otr.c_fxy_y_min;
this.o_otr_max = this.predict_otr.c_fxy_y_max;
//store min and max pressure
this.o_p_min = this.predict_p.c_fxy_y_min;
this.o_p_max = this.predict_p.c_fxy_y_max;
// predict oxygen transfer rate
this.o_otr = this.predict_otr.o_y;
// predict pressure output of diffuser
this.o_p_flow = this.predict_p.o_y;
//calculated combined efficiency in % where 100 % is ideal efficieny and 0 is worst
this.o_combined_eff = Math.round(this.combine_eff(this.o_otr,this.o_otr_min,this.o_otr_max,this.o_p_flow,this.o_p_min,this.o_p_max) * 100 ) /100;
//slope otr curve
this.graph.calc(this.predict_otr.i_x,this.predict_otr.o_y);
this.o_slope = this.graph.slope;
//go through functions
this.warning_check();
this.alarm_check();
}
// calculate average saturation over the water depth of the diffuser
calcAvgSolubility(temp){
//calculate average pressure in water column
let avgPMbar = ( this.o_p_water + this.i_local_atm_pressure ) / 2;
let avgPBar = this.convert(avgPMbar).from('mbar').to('bar');
// input x values in predictors (we could make functions and return outputs dunno whats better)
this.predictO2saturation.i_f = avgPBar; // set f to temperature
this.predictO2saturation.i_x = temp; // set temperature
// predict oxygen transfer rate
return this.predictO2saturation.o_y; //return average saturation value
}
warning_check(){
//warnings do not require to be resetted. we need to log them somewhere in the database so its easy to check if these values are being exceeded
//empty tekst before filling again and always reset state
this.warning.text = [];
this.warning.state = false;
/* ------------------ Flow warnings ------------------*/
//define hyst for flow
let min_flow_hyst = this.predict_p.c_fxy_x_min * 1 * ( this.warning.flow.min.hyst / 100);
let max_flow_hyst = this.predict_p.c_fxy_x_min * 1 * ( this.warning.flow.max.hyst / 100);
//min flow
if(this.o_flow_element < this.predict_p.c_fxy_x_min - min_flow_hyst ){
this.warning.flow.min.state = true;
this.warning.state = true;
this.warning.text.push(" Warning : input flow " + this.o_flow_element + " is less then minimum allowed flow of " + ( this.predict_p.c_fxy_x_min - min_flow_hyst ));
}
else{ //auto reset
this.warning.flow.min.state = false;
}
//max flow
if(this.o_flow_element > this.predict_p.c_fxy_x_max + max_flow_hyst ){
this.warning.flow.max.state = true;
this.warning.state = true;
this.warning.text.push("Warning input flow " + this.o_flow_element + " is exceeding nominal value of " + ( this.predict_p.c_fxy_x_max + max_flow_hyst ));
}
else{ //auto reset
this.warning.flow.max.state = false;
}
}
alarm_check(){
//warnings do not require to be resetted. we need to log them somewhere in the database so its easy to check if these values are being exceeded
//empty tekst before filling again and reset general state
this.alarm.text = [];
this.alarm.state = false;
/* ------------------ Flow warnings ------------------*/
//define hyst for flow
let min_flow_hyst = this.predict_p.c_fxy_x_min * 1 * ( this.alarm.flow.min.hyst / 100);
let max_flow_hyst = this.predict_p.c_fxy_x_min * 1 * ( this.alarm.flow.max.hyst / 100);
//min flow
if(this.o_flow_element < this.predict_p.c_fxy_x_min - min_flow_hyst ){
this.alarm.flow.min.state = true;
this.alarm.state = true;
this.alarm.text.push("Alarm input flow " + this.o_flow_element + " is less then minimum allowed flow of " + ( this.predict_p.c_fxy_x_min - min_flow_hyst ));
}
else{ //auto reset
this.warning.flow.min.state = false;
}
//max flow
if(this.o_flow_element > this.predict_p.c_fxy_x_max + max_flow_hyst ){
this.alarm.flow.max.state = true;
this.alarm.state = true;
this.alarm.text.push("Alarm input flow " + this.o_flow_element + " is exceeding an absolute max value of " + ( this.predict_p.c_fxy_x_max + max_flow_hyst ));
}
else{ //auto reset
this.alarm.flow.max.state = false;
}
}
//fetch curve and load it into object var
load_specs(){
//for now hardcoded will do as example from sulzer
let Sulzerspecs = {
supplier : "sulzer",
type : "pik300",
units:{
Nm3: { "temp": 20, "pressure" : 1.01325 , "RH" : 0 },
t_otr_curve : { f : "diffuser density in % ", x : "Nm3/h", y: { o2_weight : "g", flow : "Nm3/h" , height: "m" } } , // according to DIN
t_p_curve : { x : "flow", y : "mbar" }
},
otr_curve: {
5: // diffuser density expressed in %
{
x:[1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10], // flow expressed in normal m3/h
y:[28, 27, 26, 25, 24.5, 24, 23.5, 23.2, 23, 22.9], //oxygen transfer rate expressed in gram o2 / normal m3/h / per m
},
10: // diffuser density expressed in %
{
x:[1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10], // flow expressed in normal m3/h
y:[31, 29.5, 28.5, 27, 26, 25.5, 25, 24.7, 24.2, 24], //oxygen transfer rate expressed in gram o2 / normal m3/h / per m
}
},
p_curve:{ //difuser pressure loss curve
0: // if curve doesnt have more than 1 dimension just fill in zero here or whatever
{
x:[1.5, 2, 3, 4, 5, 6, 7, 8, 9, 10], // Flow expressed in nm3/h
y:[25,26,28,30,35,38,42,48,55,68] //pressure expressed in mbar
}
},
solubility_curve:{
1: // abs bar
{
x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius
y:[14.6,12.8,11.3,10.1,9.1,8.3,7.6,7,6.5,6,5.6], // mg/l
},
2: // abs bar
{
x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius
y:[29.2,25.5,22.6,20.2,18.2,16.5,15.2,14,12.9,12,11.3], // mg/l
},
4: // abs bar
{
x:[0,5,10,15,20,25,30,35,40,45,50], // temp in degrees celcius
y:[58.4,51.1,45.1,40.3,36.4,33.1,30.3,27.9,25.9,24,22.7], // mg/l
},
}
}
//for now hardcoded will do as example from sulzer
let specs = {
supplier : "GVA",
type : "ELASTOX-R",
units:{
Nm3: { "temp": 20, "pressure" : 1.01325 , "RH" : 0 },
t_otr_curve : { f : "diffuser density in % ", x : "Nm3/h", y: { o2_weight : "g", flow : "Nm3/h" , height: "m" } } , // according to DIN
t_p_curve : { x : "flow", y : "mbar" }
},
otr_curve: {
2.4: // diffuser density expressed in %
{
x:[2,3,4,5,6,7,8,9,10], // flow expressed in normal m3/h
y:[26,25,24,23.5,23,22.75,22.5,22.25,22], //oxygen transfer rate expressed in gram o2 / normal m3/h / per m
}
},
p_curve:{ //difuser pressure loss curve
0: // if curve doesnt have more than 1 dimension just fill in zero here or whatever
{
x:[2,3,4,5,6,7,8,9,10,11,12], // Flow expressed in nm3/h
y:[40,42.5,45,47.5,50,51.5,53,54.5,56,57.5,59] //pressure expressed in mbar
}
}
}
return specs;
}
//testing converter function
converttest(){
let test = this.convert(1).from('l').to('ml');
return test ;
}
} // end of class
/*
var diffuser = new Diffuser("sulzer","PIK300");
// define inputs for diffuser
diffuser.i_m_water = 5; // set water column above diffuser
diffuser.i_n_elements = 1;
diffuser.i_diff_density = 5; // set density in %
diffuser.i_flow = 4; // set flow rate in m3/h
// outputs are
console.log("--------------Inputs--------------")
console.log("Diffuser density : "+ diffuser.i_diff_density + " %");
console.log("Flow : " + diffuser.i_flow + " m3/h" );
console.log("Flow rate per element : " + diffuser.o_flow_element + " m3/h");
console.log("Number of elements : " + diffuser.i_n_elements );
console.log("--------------Outputs--------------")
console.log("converted input flow to diffuser normalized flow : " + diffuser.n_flow + " m3/h ");
console.log("Oxygen transfer rate : " + diffuser.o_otr + " o2 / m3/h / meter");
console.log("Pressure loss over diffusers : " + diffuser.o_p_flow + " mbar" );
console.log("Pressure loss over water column:" + diffuser.o_p_water + " mbar" );
console.log("Total pressure loss : " + diffuser.o_p_total + " mbar");
console.log("predicted diffuser oxygen input for bio : " + diffuser.o_kgo2_h + " kg o2 / h");
console.log("Predicted actual input o2 : " + diffuser.o_kgo2 + " kg o2 / s " );
console.log("max otr " + diffuser.o_otr_max);
console.log("displaying warnings : " + JSON.stringify(diffuser.warning.text) );
//change flow
diffuser.i_diff_density = 5; // set density in %
//diffuser.i_flow = 4;
console.log("--------------Inputs--------------")
console.log("Diffuser density : "+ diffuser.i_diff_density + " %");
console.log("Flow : " + diffuser.i_flow + " m3/h" );
console.log("--------------Outputs--------------")
console.log("Oxygen transfer rate : " + diffuser.o_otr + " o2 / m3/h / meter");
console.log("Pressure loss: " + diffuser.o_p_flow + " mbar" );
console.log("slope: " + diffuser.o_slope );
diffuser.i_flow = 5;
console.log("--------------Inputs--------------")
console.log("Diffuser density : "+ diffuser.i_diff_density + " %");
console.log("Flow : " + diffuser.i_flow + " m3/h" );
console.log("--------------Outputs--------------")
console.log("Oxygen transfer rate : " + diffuser.o_otr + " o2 / m3/h / meter");
console.log("Pressure loss: " + diffuser.o_p_flow + " mbar" );
console.log("slope: " + diffuser.o_slope );
diffuser.i_diff_density = 0;
console.log("--------------Inputs--------------")
console.log("Diffuser density : "+ diffuser.i_diff_density + " %");
console.log("Flow : " + diffuser.i_flow + " m3/h" );
console.log("--------------Outputs--------------")
console.log("Oxygen transfer rate : " + diffuser.o_otr + " o2 / m3/h / meter");
console.log("Pressure loss: " + diffuser.o_p_flow + " mbar" );
//
//*/
module.exports = Diffuser;