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'); }); });