import { describe, it, expect } from 'vitest'; import { signedUnit, sharedNoiseSimilarity, relativeDifference, oppositeConflictBalance, cancelAmountFromMetric, hasGroupsForDimContrast, computeDimRelationMetrics, paintWeightsForDim, applyGroupDimPaint, buildPointGroupPaintAttributes, } from '../src/visualizer/groupDimContrast.js'; describe('groupDimContrast math', () => { it('signedUnit treats 0 as positive', () => { expect(signedUnit(0)).toBe(1); expect(signedUnit(0.002)).toBe(1); expect(signedUnit(-0.018)).toBe(-1); }); it('sharedNoiseSimilarity is formula (b)', () => { // a=0.002, b=0.018 → 1 - 0.016/0.020 = 0.2 expect(sharedNoiseSimilarity(0.002, 0.018)).toBeCloseTo(0.2, 5); expect(sharedNoiseSimilarity(0.1, 0.1)).toBeCloseTo(1, 5); expect(sharedNoiseSimilarity(0, 0)).toBe(1); }); it('relativeDifference complements similarity on same magnitudes', () => { expect(relativeDifference(0.002, 0.018)).toBeCloseTo(0.8, 5); expect(relativeDifference(0.1, -0.1)).toBeCloseTo(1, 5); }); it('cancelAmountFromMetric mirrors zero-coverage on high metric', () => { expect(cancelAmountFromMetric(0.4, 0)).toBe(0); expect(cancelAmountFromMetric(0.4, 0.5)).toBe(0); // floor=0.5 expect(cancelAmountFromMetric(0.75, 0.5)).toBeCloseTo(0.5, 5); expect(cancelAmountFromMetric(1, 0.5)).toBeCloseTo(1, 5); }); }); describe('computeDimRelationMetrics', () => { it('requires ≥2 groups', () => { expect(hasGroupsForDimContrast([{ groupId: 'G1', embedding: [1] }])).toBe(false); expect(computeDimRelationMetrics([{ groupId: 'G1', embedding: [1] }])).toEqual([]); }); it('uses group means and classifies same vs opposite sign', () => { const items = [ { groupId: 'G1', embedding: [0.002, 0.1, -0.05] }, { groupId: 'G1', embedding: [0.002, 0.1, -0.05] }, { groupId: 'G2', embedding: [0.018, -0.1, -0.05] }, { groupId: 'G2', embedding: [0.018, -0.1, -0.05] }, ]; const m = computeDimRelationMetrics(items); expect(m).toHaveLength(3); expect(m[0].sameSign).toBe(true); expect(m[0].similarity).toBeCloseTo(0.2, 5); expect(m[1].sameSign).toBe(false); expect(m[1].difference).toBeCloseTo(1, 5); expect(m[2].sameSign).toBe(true); expect(m[2].similarity).toBeCloseTo(1, 5); }); }); describe('paintWeightsForDim / applyGroupDimPaint', () => { it('same-sign cancel only when enabled', () => { const metric = { meanA: 0.1, meanB: 0.1, sameSign: true, similarity: 1, difference: 0, }; expect(paintWeightsForDim(metric, { sameSignCancelEnabled: false, sameSignCancelCoverage: 90 })).toEqual({ cancel: 0, highlight: 0, }); const on = paintWeightsForDim(metric, { sameSignCancelEnabled: true, sameSignCancelCoverage: 90 }); expect(on.cancel).toBeGreaterThan(0.9); expect(on.highlight).toBe(0); }); it('opposite highlight scales by strength × conflict balance', () => { const metric = { meanA: 0.1, meanB: -0.1, sameSign: false, similarity: 0, difference: 1, conflictBalance: 1, }; const w = paintWeightsForDim(metric, { oppositeHighlightEnabled: true, oppositeHighlightStrength: 50, oppositeCancelCoverage: 0, }); expect(w.highlight).toBeCloseTo(0.5, 5); expect(w.cancel).toBe(0); }); it('conflict cover fades opposite dims linearly (not all-or-nothing)', () => { const metric = { meanA: 0.1, meanB: -0.1, sameSign: false, similarity: 0, difference: 1, conflictBalance: 1, }; const at0 = paintWeightsForDim(metric, { oppositeHighlightEnabled: true, oppositeHighlightStrength: 100, oppositeCancelCoverage: 0, }); expect(at0.cancel).toBe(0); expect(at0.highlight).toBeCloseTo(1, 5); const at45 = paintWeightsForDim(metric, { oppositeHighlightEnabled: true, oppositeHighlightStrength: 100, oppositeCancelCoverage: 45, }); expect(at45.cancel).toBeCloseTo(0.5, 5); expect(at45.highlight).toBeCloseTo(1, 5); const at90 = paintWeightsForDim(metric, { oppositeHighlightEnabled: true, oppositeHighlightStrength: 100, oppositeCancelCoverage: 90, }); expect(at90.cancel).toBeCloseTo(1, 5); }); it('oppositeConflictBalance is 1 for equal mags, lower when unbalanced', () => { expect(oppositeConflictBalance(0.1, -0.1)).toBeCloseTo(1, 5); expect(oppositeConflictBalance(0.1, -0.01)).toBeCloseTo(2 * 0.01 / 0.11, 5); }); it('replaces toward highlight then blackens', () => { const base = { r: 1, g: 1, b: 0, alpha: 1 }; const metric = { meanA: 1, meanB: -1, sameSign: false, similarity: 0, difference: 1, }; const painted = applyGroupDimPaint( base, metric, { oppositeHighlightEnabled: true, oppositeHighlightStrength: 100, oppositeCancelCoverage: 0, oppositeHighlightColor: '#00E5FF', }, { r: 0, g: 0, b: 0 }, { r: 0, g: 229 / 255, b: 1 } ); expect(painted.r).toBeCloseTo(0, 5); expect(painted.b).toBeCloseTo(1, 5); }); it('buildPointGroupPaintAttributes uses meta.dim', () => { const metrics = computeDimRelationMetrics([ { groupId: 'G1', embedding: [0.1, -0.1] }, { groupId: 'G2', embedding: [0.1, 0.1] }, ]); const points = [ { meta: { dim: 0 } }, { meta: { dim: 1 } }, ]; const { cancel, highlight } = buildPointGroupPaintAttributes(points, metrics, { sameSignCancelEnabled: true, sameSignCancelCoverage: 90, oppositeHighlightEnabled: true, oppositeHighlightStrength: 100, oppositeCancelCoverage: 0, }); expect(cancel[0]).toBeGreaterThan(0.5); // same-sign dim0 expect(highlight[0]).toBe(0); expect(highlight[1]).toBeGreaterThan(0.5); // opposite dim1 }); });