Files

125 lines
5.1 KiB
JavaScript
Raw Permalink Normal View History

import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import { OWAFusion } from '../../src/utils/OWAFusion.js';
function createRng(seed) {
let state = seed >>> 0;
return () => {
state = (1664525 * state + 1013904223) >>> 0;
return state / 0x100000000;
};
}
function randInt(rng, max) {
return Math.floor(rng() * max);
}
function randFloat(rng, min = 0, max = 1) {
return min + (max - min) * rng();
}
function approxEqual(a, b, eps = 1e-6) {
return Math.abs(a - b) <= eps;
}
describe('OWA aggregation properties', () => {
test('bounded and translation properties across modes', () => {
const rng = createRng(42);
const modes = ['max', 'min', 'average', 'majority', 'median', 'optimistic', 'pessimistic', 'top2', 'top3'];
const iterations = 200;
for (let i = 0; i < iterations; i++) {
const length = randInt(rng, 8) + 1;
const values = new Array(length).fill(0).map(() => randFloat(rng, -50, 50));
const metas = new Array(length).fill(null);
const min = Math.min(...values);
const max = Math.max(...values);
for (const mode of modes) {
const weights = OWAFusion.generateOWAWeights(length, mode, null, true);
const result = OWAFusion.fuseWithMeta(values, metas, weights, mode, true).value;
assert.ok(result >= min - 1e-6 && result <= max + 1e-6, `mode ${mode} bounds`);
const weightSum = weights.reduce((sum, w) => sum + w, 0);
if (approxEqual(weightSum, 1.0)) {
const delta = randFloat(rng, -10, 10);
const shifted = values.map(v => v + delta);
const shiftedResult = OWAFusion.fuseWithMeta(shifted, metas, weights, mode, true).value;
assert.ok(approxEqual(shiftedResult - result, delta, 1e-5), `mode ${mode} translation`);
}
}
}
});
test('extreme strategies behave as expected', () => {
const rng = createRng(7);
for (let i = 0; i < 200; i++) {
const length = randInt(rng, 8) + 1;
const values = new Array(length).fill(0).map(() => randFloat(rng, -20, 20));
const metas = new Array(length).fill(null);
const max = Math.max(...values);
const min = Math.min(...values);
const maxWeights = OWAFusion.generateOWAWeights(length, 'max', null, true);
const minWeights = OWAFusion.generateOWAWeights(length, 'min', null, true);
const maxResult = OWAFusion.fuseWithMeta(values, metas, maxWeights, 'max', true).value;
const minResult = OWAFusion.fuseWithMeta(values, metas, minWeights, 'min', true).value;
assert.ok(approxEqual(maxResult, max, 1e-6), 'max aggregator');
assert.ok(approxEqual(minResult, min, 1e-6), 'min aggregator');
}
});
test('custom weights respect convex combination', () => {
const rng = createRng(13);
for (let i = 0; i < 200; i++) {
const length = randInt(rng, 8) + 1;
const values = new Array(length).fill(0).map(() => randFloat(rng, -100, 100));
const metas = new Array(length).fill(null);
let weights = new Array(length).fill(0).map(() => randFloat(rng, 0, 1));
const sum = weights.reduce((a, b) => a + b, 0) || 1;
weights = weights.map(w => w / sum);
const min = Math.min(...values);
const max = Math.max(...values);
const result = OWAFusion.fuseWithMeta(values, metas, weights, 'custom', true).value;
assert.ok(result >= min - 1e-6 && result <= max + 1e-6, 'custom convex bounds');
}
});
test('sum weights returns total', () => {
const rng = createRng(99);
for (let i = 0; i < 200; i++) {
const length = randInt(rng, 8) + 1;
const values = new Array(length).fill(0).map(() => randFloat(rng, -5, 5));
const metas = new Array(length).fill(null);
const weights = OWAFusion.generateOWAWeights(length, 'sum', null, false);
const result = OWAFusion.fuseWithMeta(values, metas, weights, 'sum', false).value;
const expected = values.reduce((a, b) => a + b, 0);
assert.ok(approxEqual(result, expected, 1e-6), 'sum matches total');
}
});
test('sum_unbounded matches sum', () => {
const rng = createRng(101);
for (let i = 0; i < 200; i++) {
const length = randInt(rng, 8) + 1;
const values = new Array(length).fill(0).map(() => randFloat(rng, -5, 5));
const metas = new Array(length).fill(null);
const sumWeights = OWAFusion.generateOWAWeights(length, 'sum', null, false);
const unboundedWeights = OWAFusion.generateOWAWeights(length, 'sum_unbounded', null, false);
const sumResult = OWAFusion.fuseWithMeta(values, metas, sumWeights, 'sum', false).value;
const unboundedResult = OWAFusion.fuseWithMeta(values, metas, unboundedWeights, 'sum_unbounded', false).value;
assert.ok(approxEqual(sumResult, unboundedResult, 1e-6), 'sum_unbounded matches sum');
}
});
test('priority weights are normalized proportions', () => {
const priorities = [1, 5, 3];
const weights = OWAFusion.generateOWAWeights(priorities.length, 'priority', priorities, true);
const total = weights.reduce((sum, w) => sum + w, 0);
assert.ok(approxEqual(total, 1.0, 1e-6));
assert.ok(weights[1] > weights[2] && weights[2] > weights[0], 'weights follow priorities');
});
});