Files
generalFunctions/helper/nrmse/errorMetrics.js
2025-05-26 17:09:18 +02:00

155 lines
5.3 KiB
JavaScript

//load local dependencies
const EventEmitter = require('events');
//load all config modules
const defaultConfig = require('./nrmseConfig.json');
const ConfigUtils = require('../configUtils');
class ErrorMetrics {
constructor(config = {}, logger) {
this.emitter = new EventEmitter(); // Own EventEmitter
this.configUtils = new ConfigUtils(defaultConfig);
this.config = this.configUtils.initConfig(config);
// Init after config is set
this.logger = logger;
// For long-term NRMSD accumulation
this.cumNRMSD = 0;
this.cumCount = 0;
}
//INCLUDE timestamps in the next update OLIFANT
meanSquaredError(predicted, measured) {
if (predicted.length !== measured.length) {
this.logger.error("Comparing MSE Arrays must have the same length.");
return 0;
}
let sumSqError = 0;
for (let i = 0; i < predicted.length; i++) {
const err = predicted[i] - measured[i];
sumSqError += err * err;
}
return sumSqError / predicted.length;
}
rootMeanSquaredError(predicted, measured) {
return Math.sqrt(this.meanSquaredError(predicted, measured));
}
normalizedRootMeanSquaredError(predicted, measured, processMin, processMax) {
const range = processMax - processMin;
if (range <= 0) {
this.logger.error("Invalid process range: processMax must be greater than processMin.");
}
const rmse = this.rootMeanSquaredError(predicted, measured);
return rmse / range;
}
longTermNRMSD(input) {
const storedNRMSD = this.cumNRMSD;
const storedCount = this.cumCount;
const newCount = storedCount + 1;
// Update cumulative values
this.cumCount = newCount;
// Calculate new running average
if (storedCount === 0) {
this.cumNRMSD = input; // First value
} else {
// Running average formula: newAvg = oldAvg + (newValue - oldAvg) / newCount
this.cumNRMSD = storedNRMSD + (input - storedNRMSD) / newCount;
}
if(newCount >= 100) {
// Return the current NRMSD value, not just the contribution from this sample
return this.cumNRMSD;
}
return 0;
}
normalizeUsingRealtime(predicted, measured) {
const realtimeMin = Math.min(Math.min(...predicted), Math.min(...measured));
const realtimeMax = Math.max(Math.max(...predicted), Math.max(...measured));
const range = realtimeMax - realtimeMin;
if (range <= 0) {
throw new Error("Invalid process range: processMax must be greater than processMin.");
}
const rmse = this.rootMeanSquaredError(predicted, measured);
return rmse / range;
}
detectImmediateDrift(nrmse) {
let ImmDrift = {};
this.logger.debug(`checking immediate drift with thresholds : ${this.config.thresholds.NRMSE_HIGH} ${this.config.thresholds.NRMSE_MEDIUM} ${this.config.thresholds.NRMSE_LOW}`);
switch (true) {
case( nrmse > this.config.thresholds.NRMSE_HIGH ) :
ImmDrift = {level : 3 , feedback : "High immediate drift detected"};
break;
case( nrmse > this.config.thresholds.NRMSE_MEDIUM ) :
ImmDrift = {level : 2 , feedback : "Medium immediate drift detected"};
break;
case(nrmse > this.config.thresholds.NRMSE_LOW ):
ImmDrift = {level : 1 , feedback : "Low immediate drift detected"};
break;
default:
ImmDrift = {level : 0 , feedback : "No drift detected"};
}
return ImmDrift;
}
detectLongTermDrift(longTermNRMSD) {
let LongTermDrift = {};
this.logger.debug(`checking longterm drift with thresholds : ${this.config.thresholds.LONG_TERM_HIGH} ${this.config.thresholds.LONG_TERM_MEDIUM} ${this.config.thresholds.LONG_TERM_LOW}`);
switch (true) {
case(Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_HIGH) :
LongTermDrift = {level : 3 , feedback : "High long-term drift detected"};
break;
case (Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_MEDIUM) :
LongTermDrift = {level : 2 , feedback : "Medium long-term drift detected"};
break;
case ( Math.abs(longTermNRMSD) > this.config.thresholds.LONG_TERM_LOW ) :
LongTermDrift = {level : 1 , feedback : "Low long-term drift detected"};
break;
default:
LongTermDrift = {level : 0 , feedback : "No drift detected"};
}
return LongTermDrift;
}
detectDrift(nrmse, longTermNRMSD) {
const ImmDrift = this.detectImmediateDrift(nrmse);
const LongTermDrift = this.detectLongTermDrift(longTermNRMSD);
return { ImmDrift, LongTermDrift };
}
// asses the drift
assessDrift(predicted, measured, processMin, processMax) {
// Compute NRMSE and check for immediate drift
const nrmse = this.normalizedRootMeanSquaredError(predicted, measured, processMin, processMax);
this.logger.debug(`NRMSE: ${nrmse}`);
// cmopute long-term NRMSD and add result to cumalitve NRMSD
const longTermNRMSD = this.longTermNRMSD(nrmse);
// return the drift
// Return the drift assessment object
const driftAssessment = this.detectDrift(nrmse, longTermNRMSD);
return {
nrmse,
longTermNRMSD,
immediateLevel: driftAssessment.ImmDrift.level,
immediateFeedback: driftAssessment.ImmDrift.feedback,
longTermLevel: driftAssessment.LongTermDrift.level,
longTermFeedback: driftAssessment.LongTermDrift.feedback
};
}
}
module.exports = ErrorMetrics;