298 lines
11 KiB
JavaScript
298 lines
11 KiB
JavaScript
const ErrorMetrics = require('./errorMetrics');
|
|
|
|
// Dummy logger for tests
|
|
const logger = {
|
|
error: console.error,
|
|
debug: console.log,
|
|
info: console.log
|
|
};
|
|
|
|
const config = {
|
|
thresholds: {
|
|
NRMSE_LOW: 0.05,
|
|
NRMSE_MEDIUM: 0.10,
|
|
NRMSE_HIGH: 0.15,
|
|
LONG_TERM_LOW: 0.02,
|
|
LONG_TERM_MEDIUM: 0.04,
|
|
LONG_TERM_HIGH: 0.06
|
|
}
|
|
};
|
|
|
|
class ErrorMetricsTester {
|
|
constructor() {
|
|
this.totalTests = 0;
|
|
this.passedTests = 0;
|
|
this.failedTests = 0;
|
|
this.errorMetrics = new ErrorMetrics(config, logger);
|
|
}
|
|
|
|
assert(condition, message) {
|
|
this.totalTests++;
|
|
if (condition) {
|
|
console.log(`✓ PASS: ${message}`);
|
|
this.passedTests++;
|
|
} else {
|
|
console.log(`✗ FAIL: ${message}`);
|
|
this.failedTests++;
|
|
}
|
|
}
|
|
|
|
testMeanSquaredError() {
|
|
console.log("\nTesting Mean Squared Error...");
|
|
const predicted = [1, 2, 3];
|
|
const measured = [1, 3, 5];
|
|
const mse = this.errorMetrics.meanSquaredError(predicted, measured);
|
|
this.assert(Math.abs(mse - 1.67) < 0.1, "MSE correctly calculated");
|
|
}
|
|
|
|
testRootMeanSquaredError() {
|
|
console.log("\nTesting Root Mean Squared Error...");
|
|
const predicted = [1, 2, 3];
|
|
const measured = [1, 3, 5];
|
|
const rmse = this.errorMetrics.rootMeanSquaredError(predicted, measured);
|
|
this.assert(Math.abs(rmse - 1.29) < 0.1, "RMSE correctly calculated");
|
|
}
|
|
|
|
testNormalizedRMSE() {
|
|
console.log("\nTesting Normalized RMSE...");
|
|
const predicted = [100, 102, 104];
|
|
const measured = [98, 103, 107];
|
|
const processMin = 90, processMax = 110;
|
|
const nrmse = this.errorMetrics.normalizedRootMeanSquaredError(predicted, measured, processMin, processMax);
|
|
this.assert(typeof nrmse === 'number' && nrmse > 0, "Normalized RMSE calculated correctly");
|
|
}
|
|
|
|
testNormalizeUsingRealtime() {
|
|
console.log("\nTesting Normalize Using Realtime...");
|
|
const predicted = [100, 102, 104];
|
|
const measured = [98, 103, 107];
|
|
|
|
try {
|
|
const nrmse = this.errorMetrics.normalizeUsingRealtime(predicted, measured);
|
|
this.assert(typeof nrmse === 'number' && nrmse > 0, "Normalize using realtime calculated correctly");
|
|
} catch (error) {
|
|
this.assert(false, `Normalize using realtime failed: ${error.message}`);
|
|
}
|
|
|
|
// Test with identical values to check error handling
|
|
const sameValues = [100, 100, 100];
|
|
try {
|
|
this.errorMetrics.normalizeUsingRealtime(sameValues, sameValues);
|
|
this.assert(false, "Should throw error with identical values");
|
|
} catch (error) {
|
|
this.assert(true, "Correctly throws error when min/max are the same");
|
|
}
|
|
}
|
|
|
|
testLongTermNRMSD() {
|
|
console.log("\nTesting Long Term NRMSD Accumulation...");
|
|
// Reset the accumulation values
|
|
this.errorMetrics.cumNRMSD = 0;
|
|
this.errorMetrics.cumCount = 0;
|
|
|
|
let lastValue = 0;
|
|
for (let i = 0; i < 100; i++) {
|
|
lastValue = this.errorMetrics.longTermNRMSD(0.1 + i * 0.001);
|
|
}
|
|
|
|
this.assert(
|
|
this.errorMetrics.cumCount === 100 &&
|
|
this.errorMetrics.cumNRMSD !== 0 &&
|
|
lastValue !== 0,
|
|
"Long term NRMSD accumulates over 100 iterations"
|
|
);
|
|
|
|
// Test that values are returned only after accumulating 100 samples
|
|
this.errorMetrics.cumNRMSD = 0;
|
|
this.errorMetrics.cumCount = 0;
|
|
|
|
for (let i = 0; i < 99; i++) {
|
|
const result = this.errorMetrics.longTermNRMSD(0.1);
|
|
this.assert(result === 0, "No longTermNRMSD returned before 100 samples");
|
|
}
|
|
|
|
// Use a different value for the 100th sample to ensure a non-zero result
|
|
const result = this.errorMetrics.longTermNRMSD(0.2);
|
|
this.assert(result !== 0, "longTermNRMSD returned after 100 samples");
|
|
}
|
|
|
|
testDetectImmediateDrift() {
|
|
console.log("\nTesting Immediate Drift Detection...");
|
|
|
|
// Test high drift
|
|
let drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_HIGH + 0.01);
|
|
this.assert(drift.level === 3, "Detects high immediate drift correctly");
|
|
|
|
// Test medium drift
|
|
drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_MEDIUM + 0.01);
|
|
this.assert(drift.level === 2, "Detects medium immediate drift correctly");
|
|
|
|
// Test low drift
|
|
drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_LOW + 0.01);
|
|
this.assert(drift.level === 1, "Detects low immediate drift correctly");
|
|
|
|
// Test no drift
|
|
drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_LOW - 0.01);
|
|
this.assert(drift.level === 0, "Detects no immediate drift correctly");
|
|
}
|
|
|
|
testDetectLongTermDrift() {
|
|
console.log("\nTesting Long Term Drift Detection...");
|
|
|
|
// Test high drift
|
|
let drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_HIGH + 0.01);
|
|
this.assert(drift.level === 3, "Detects high long-term drift correctly");
|
|
|
|
// Test medium drift
|
|
drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_MEDIUM + 0.01);
|
|
this.assert(drift.level === 2, "Detects medium long-term drift correctly");
|
|
|
|
// Test low drift
|
|
drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_LOW + 0.01);
|
|
this.assert(drift.level === 1, "Detects low long-term drift correctly");
|
|
|
|
// Test no drift
|
|
drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_LOW - 0.01);
|
|
this.assert(drift.level === 0, "Detects no long-term drift correctly");
|
|
|
|
// Test negative drift values
|
|
drift = this.errorMetrics.detectLongTermDrift(-config.thresholds.LONG_TERM_HIGH - 0.01);
|
|
this.assert(drift.level === 3, "Detects negative high long-term drift correctly");
|
|
}
|
|
|
|
testDriftDetection() {
|
|
console.log("\nTesting Combined Drift Detection...");
|
|
|
|
let nrmseHigh = config.thresholds.NRMSE_HIGH + 0.01;
|
|
let ltNRMSD = 0;
|
|
|
|
let result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
|
|
|
|
this.assert(
|
|
result !== null &&
|
|
result.ImmDrift &&
|
|
result.ImmDrift.level === 3 &&
|
|
result.LongTermDrift.level === 0,
|
|
"Detects high immediate drift with no long-term drift"
|
|
);
|
|
|
|
nrmseHigh = config.thresholds.NRMSE_LOW - 0.01;
|
|
ltNRMSD = config.thresholds.LONG_TERM_MEDIUM + 0.01;
|
|
result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
|
|
this.assert(
|
|
result !== null &&
|
|
result.ImmDrift.level === 0 &&
|
|
result.LongTermDrift &&
|
|
result.LongTermDrift.level === 2,
|
|
"Detects medium long-term drift with no immediate drift"
|
|
);
|
|
|
|
nrmseHigh = config.thresholds.NRMSE_MEDIUM + 0.01;
|
|
ltNRMSD = config.thresholds.LONG_TERM_MEDIUM + 0.01;
|
|
result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
|
|
this.assert(
|
|
result.ImmDrift.level === 2 &&
|
|
result.LongTermDrift.level === 2,
|
|
"Detects both medium immediate and medium long-term drift"
|
|
);
|
|
|
|
nrmseHigh = config.thresholds.NRMSE_LOW - 0.01;
|
|
ltNRMSD = config.thresholds.LONG_TERM_LOW - 0.01;
|
|
result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
|
|
this.assert(
|
|
result.ImmDrift.level === 0 &&
|
|
result.LongTermDrift.level === 0,
|
|
"No significant drift detected when under thresholds"
|
|
);
|
|
}
|
|
|
|
testAssessDrift() {
|
|
console.log("\nTesting assessDrift function...");
|
|
|
|
// Reset accumulation for testing
|
|
this.errorMetrics.cumNRMSD = 0;
|
|
this.errorMetrics.cumCount = 0;
|
|
|
|
const predicted = [100, 101, 102, 103];
|
|
const measured = [90, 91, 92, 93];
|
|
const processMin = 90, processMax = 110;
|
|
|
|
let result = this.errorMetrics.assessDrift(predicted, measured, processMin, processMax);
|
|
|
|
this.assert(
|
|
result !== null &&
|
|
typeof result.nrmse === 'number' &&
|
|
typeof result.longTermNRMSD === 'number' &&
|
|
typeof result.immediateLevel === 'number' &&
|
|
typeof result.immediateFeedback === 'string' &&
|
|
typeof result.longTermLevel === 'number' &&
|
|
typeof result.longTermFeedback === 'string',
|
|
"assessDrift returns complete result structure"
|
|
);
|
|
|
|
this.assert(
|
|
result.immediateLevel > 0,
|
|
"assessDrift detects immediate drift with significant difference"
|
|
);
|
|
|
|
// Test with identical values
|
|
result = this.errorMetrics.assessDrift(predicted, predicted, processMin, processMax);
|
|
this.assert(
|
|
result.nrmse === 0 &&
|
|
result.immediateLevel === 0,
|
|
"assessDrift indicates no immediate drift when predicted equals measured"
|
|
);
|
|
|
|
// Test with slight drift
|
|
const measuredSlight = [100, 100.5, 101, 101.5];
|
|
result = this.errorMetrics.assessDrift(predicted, measuredSlight, processMin, processMax);
|
|
|
|
this.assert(
|
|
result !== null &&
|
|
result.nrmse < 0.05 &&
|
|
(result.immediateLevel < 2),
|
|
"assessDrift returns appropriate levels for slight drift"
|
|
);
|
|
|
|
// Test long-term drift accumulation
|
|
for (let i = 0; i < 100; i++) {
|
|
this.errorMetrics.assessDrift(
|
|
predicted,
|
|
measured.map(m => m + (Math.random() * 2 - 1)), // Add small random fluctuation
|
|
processMin,
|
|
processMax
|
|
);
|
|
}
|
|
|
|
result = this.errorMetrics.assessDrift(predicted, measured, processMin, processMax);
|
|
this.assert(
|
|
result.longTermNRMSD !== 0,
|
|
"Long-term drift accumulates over multiple assessments"
|
|
);
|
|
}
|
|
|
|
async runAllTests() {
|
|
console.log("\nStarting Error Metrics Tests...\n");
|
|
this.testMeanSquaredError();
|
|
this.testRootMeanSquaredError();
|
|
this.testNormalizedRMSE();
|
|
this.testNormalizeUsingRealtime();
|
|
this.testLongTermNRMSD();
|
|
this.testDetectImmediateDrift();
|
|
this.testDetectLongTermDrift();
|
|
this.testDriftDetection();
|
|
this.testAssessDrift();
|
|
|
|
console.log("\nTest Summary:");
|
|
console.log(`Total Tests: ${this.totalTests}`);
|
|
console.log(`Passed: ${this.passedTests}`);
|
|
console.log(`Failed: ${this.failedTests}`);
|
|
|
|
process.exit(this.failedTests > 0 ? 1 : 0);
|
|
}
|
|
}
|
|
|
|
// Run all tests
|
|
const tester = new ErrorMetricsTester();
|
|
tester.runAllTests().catch(console.error);
|