Spaces:
Sleeping
Sleeping
| /** | |
| * ReliefMath.js | |
| * Funciones puras (sin Three.js, sin DOM) para alinear y combinar matrices de | |
| * relieve multi-documento. Aisladas aquí para que sean testeables sin un canvas. | |
| * | |
| * Una "matriz" es `number[Z][X]`: Z = chunk_index, X = dimensión (768). | |
| * Documentos distintos tienen distinto número de chunks (Z), así que para | |
| * compararlos celda a celda (modos Overlay/Delta/Inspector) hay que alinear el | |
| * eje Z a una longitud común. Dos estrategias: | |
| * - 'NORMALIZED': re-muestrea todas las matrices a la MAYOR cantidad de chunks | |
| * mediante interpolación lineal sobre [0,1] (preserva la forma | |
| * global aunque los largos difieran). Estrategia por defecto. | |
| * - 'INDEX' : alinea por índice crudo de chunk y recorta a la MENOR. | |
| */ | |
| // Re-muestrea una matriz a `targetLen` filas por interpolación lineal sobre el | |
| // eje Z. Cada fila destino i mapea a la posición continua t = i/(targetLen-1) en | |
| // el rango [0, srcLen-1] y se interpola entre las dos filas vecinas. Las columnas | |
| // (dimensiones) se preservan 1:1. Devuelve una matriz nueva (no muta la entrada). | |
| export function resampleMatrixZ(matrix, targetLen) { | |
| const srcLen = matrix.length; | |
| if (srcLen === 0 || targetLen <= 0) return []; | |
| const cols = matrix[0].length; | |
| if (srcLen === targetLen) return matrix.map(row => row.slice()); | |
| const out = new Array(targetLen); | |
| for (let i = 0; i < targetLen; i++) { | |
| const t = targetLen === 1 ? 0 : (i * (srcLen - 1)) / (targetLen - 1); | |
| const lo = Math.floor(t); | |
| const hi = Math.min(lo + 1, srcLen - 1); | |
| const frac = t - lo; | |
| const rowLo = matrix[lo]; | |
| const rowHi = matrix[hi]; | |
| const row = new Array(cols); | |
| for (let c = 0; c < cols; c++) { | |
| row[c] = rowLo[c] * (1 - frac) + rowHi[c] * frac; | |
| } | |
| out[i] = row; | |
| } | |
| return out; | |
| } | |
| // Re-muestrea una serie 1D (perfil de una dimensión a lo largo de los chunks) a | |
| // `targetLen` puntos por interpolación lineal. Usado por el inspector (modo C) | |
| // para normalizar el eje X entre documentos de distinto largo. | |
| export function resampleSeries(series, targetLen) { | |
| const srcLen = series.length; | |
| if (srcLen === 0 || targetLen <= 0) return []; | |
| if (srcLen === targetLen) return series.slice(); | |
| const out = new Array(targetLen); | |
| for (let i = 0; i < targetLen; i++) { | |
| const t = targetLen === 1 ? 0 : (i * (srcLen - 1)) / (targetLen - 1); | |
| const lo = Math.floor(t); | |
| const hi = Math.min(lo + 1, srcLen - 1); | |
| const frac = t - lo; | |
| out[i] = series[lo] * (1 - frac) + series[hi] * frac; | |
| } | |
| return out; | |
| } | |
| // Alinea una lista de matrices a una longitud Z común según la estrategia. | |
| // Devuelve { aligned: matrix[], length }. No muta las entradas. | |
| export function alignMatrices(matrices, mode = 'NORMALIZED') { | |
| const valid = matrices.filter(m => m && m.length > 0); | |
| if (valid.length === 0) return { aligned: [], length: 0 }; | |
| const lengths = valid.map(m => m.length); | |
| const targetLen = mode === 'INDEX' | |
| ? Math.min(...lengths) | |
| : Math.max(...lengths); | |
| if (mode === 'INDEX') { | |
| const aligned = valid.map(m => m.slice(0, targetLen).map(row => row.slice())); | |
| return { aligned, length: targetLen }; | |
| } | |
| const aligned = valid.map(m => resampleMatrixZ(m, targetLen)); | |
| return { aligned, length: targetLen }; | |
| } | |
| // Resta celda a celda dos matrices (A - B) tras alinearlas en Z. | |
| // Devuelve la matriz delta (mismas dimensiones de columna, `length` filas). | |
| export function computeDelta(matrixA, matrixB, mode = 'NORMALIZED') { | |
| if (!matrixA || !matrixB || matrixA.length === 0 || matrixB.length === 0) return []; | |
| const { aligned } = alignMatrices([matrixA, matrixB], mode); | |
| const [a, b] = aligned; | |
| const rows = a.length; | |
| const cols = a[0].length; | |
| const delta = new Array(rows); | |
| for (let z = 0; z < rows; z++) { | |
| const rowA = a[z]; | |
| const rowB = b[z]; | |
| const row = new Array(cols); | |
| for (let x = 0; x < cols; x++) { | |
| row[x] = rowA[x] - rowB[x]; | |
| } | |
| delta[z] = row; | |
| } | |
| return delta; | |
| } | |
| // Extrae el perfil de una dimensión `dimIndex` a lo largo de los chunks de una | |
| // matriz → array de activaciones (eje X del inspector = chunk_index). | |
| export function dimensionSeries(matrix, dimIndex) { | |
| if (!matrix || matrix.length === 0) return []; | |
| const cols = matrix[0].length; | |
| if (dimIndex < 0 || dimIndex >= cols) return []; | |
| return matrix.map(row => row[dimIndex]); | |
| } | |
| // Máximo valor absoluto sobre toda una matriz (para normalización simétrica de | |
| // color/altura). Igual semántica que el helper interno del renderer, expuesto | |
| // para que el orquestador comparta el mismo |max| entre documentos. | |
| export function computeAbsMax(matrix) { | |
| let m = 0; | |
| for (let z = 0; z < matrix.length; z++) { | |
| const row = matrix[z]; | |
| for (let x = 0; x < row.length; x++) { | |
| const a = Math.abs(row[x]); | |
| if (a > m) m = a; | |
| } | |
| } | |
| return m; | |
| } | |
| // Computa la similitud de una dimensión entre dos series de tiempo / ejes latentes | |
| // basándose en un umbral de tolerancia absoluta elemento a elemento. | |
| // Devuelve el porcentaje de elementos (entre 0 y 100) donde la diferencia | |
| // absoluta es estrictamente menor que `tolerance`. | |
| export function computeDimensionSimilarity(seriesA, seriesB, tolerance = 0.01) { | |
| if (!seriesA || !seriesB) return 0; | |
| const len = seriesA.length; | |
| if (len === 0 || seriesB.length !== len) return 0; | |
| let count = 0; | |
| for (let i = 0; i < len; i++) { | |
| if (Math.abs(seriesA[i] - seriesB[i]) < tolerance) { | |
| count++; | |
| } | |
| } | |
| return (count / len) * 100; | |
| } | |
| // Computa la similitud de coseno entre los centroides semánticos promediados de | |
| // dos matrices de relieve. | |
| export function computeGlobalSimilarity(matrixA, matrixB) { | |
| const lenA = matrixA ? matrixA.length : 0; | |
| const lenB = matrixB ? matrixB.length : 0; | |
| const colsA = lenA > 0 ? matrixA[0].length : 0; | |
| const colsB = lenB > 0 ? matrixB[0].length : 0; | |
| const D = Math.max(colsA, colsB); | |
| if (D === 0) return 0; | |
| const centroidA = new Array(D).fill(0); | |
| if (lenA > 0) { | |
| for (let d = 0; d < D; d++) { | |
| let sum = 0; | |
| for (let z = 0; z < lenA; z++) { | |
| sum += matrixA[z][d] || 0; | |
| } | |
| centroidA[d] = sum / lenA; | |
| } | |
| } | |
| const centroidB = new Array(D).fill(0); | |
| if (lenB > 0) { | |
| for (let d = 0; d < D; d++) { | |
| let sum = 0; | |
| for (let z = 0; z < lenB; z++) { | |
| sum += matrixB[z][d] || 0; | |
| } | |
| centroidB[d] = sum / lenB; | |
| } | |
| } | |
| let dotProduct = 0; | |
| let normA2 = 0; | |
| let normB2 = 0; | |
| for (let d = 0; d < D; d++) { | |
| const valA = centroidA[d]; | |
| const valB = centroidB[d]; | |
| dotProduct += valA * valB; | |
| normA2 += valA * valA; | |
| normB2 += valB * valB; | |
| } | |
| const normA = Math.sqrt(normA2); | |
| const normB = Math.sqrt(normB2); | |
| return dotProduct / (normA * normB + 1e-9); | |
| } | |
| /** | |
| * Computa la varianza de cada dimensión de la matriz a lo largo de todos sus chunks. | |
| * Devuelve una lista ordenada de objetos `{ dimIndex, variance }` de mayor a menor varianza. | |
| * @param {number[][]} matrix - Matriz Z x D de activaciones. | |
| * @returns {{dimIndex: number, variance: number}[]} | |
| */ | |
| export function sortDimensionsByVariance(matrix) { | |
| if (!matrix || matrix.length === 0) return []; | |
| const Z = matrix.length; | |
| const D = matrix[0].length; | |
| const result = []; | |
| for (let d = 0; d < D; d++) { | |
| let sum = 0; | |
| let sumSq = 0; | |
| for (let z = 0; z < Z; z++) { | |
| const val = matrix[z][d] || 0; | |
| sum += val; | |
| sumSq += val * val; | |
| } | |
| const mean = sum / Z; | |
| const variance = (sumSq / Z) - (mean * mean); | |
| result.push({ dimIndex: d, variance }); | |
| } | |
| return result.sort((a, b) => b.variance - a.variance); | |
| } | |
| /** | |
| * Devuelve las primeras `limit` dimensiones con mayor varianza. | |
| * Si no hay matriz, devuelve un fallback secuencial de 0 a limit-1. | |
| * @param {number[][]} matrix - Matriz Z x D de activaciones. | |
| * @param {number} limit - Límite de dimensiones a devolver. | |
| * @returns {{dimIndex: number, variance: number}[]} | |
| */ | |
| export function getHighVarianceDimensions(matrix, limit = 100) { | |
| const dCount = matrix && matrix[0] ? matrix[0].length : 768; | |
| const actualLimit = Math.min(limit, dCount); | |
| if (!matrix || matrix.length === 0) { | |
| const fallback = []; | |
| for (let i = 0; i < actualLimit; i++) { | |
| fallback.push({ dimIndex: i, variance: 0 }); | |
| } | |
| return fallback; | |
| } | |
| const sorted = sortDimensionsByVariance(matrix); | |
| return sorted.slice(0, actualLimit); | |
| } | |
| // ============================================================================ | |
| // HELPERS DE LINEARIZACIÓN 1D (FLOAT32ARRAY) Y FALLBACKS SÍNCRONOS | |
| // ============================================================================ | |
| export function flattenMatrix2D(matrix) { | |
| if (!matrix || matrix.length === 0) return new Float32Array(0); | |
| const rows = matrix.length; | |
| const cols = matrix[0].length; | |
| const flat = new Float32Array(rows * cols); | |
| for (let r = 0; r < rows; r++) { | |
| const row = matrix[r]; | |
| const offset = r * cols; | |
| for (let c = 0; c < cols; c++) { | |
| flat[offset + c] = row[c] || 0; | |
| } | |
| } | |
| return flat; | |
| } | |
| export function unflattenMatrix2D(flatArray, rows, cols) { | |
| if (!flatArray || rows === 0 || cols === 0) return []; | |
| const matrix = new Array(rows); | |
| for (let r = 0; r < rows; r++) { | |
| const row = new Array(cols); | |
| const offset = r * cols; | |
| for (let c = 0; c < cols; c++) { | |
| row[c] = flatArray[offset + c]; | |
| } | |
| matrix[r] = row; | |
| } | |
| return matrix; | |
| } | |
| function sinc(x) { | |
| if (x === 0) return 1.0; | |
| const piX = Math.PI * x; | |
| return Math.sin(piX) / piX; | |
| } | |
| export function executeResampleZFallback(sourceData, sourceZ, sourceX, targetZ, targetX) { | |
| const output = new Float32Array(targetZ * targetX); | |
| const ratioZ = sourceZ / targetZ; | |
| const ratioX = sourceX / targetX; | |
| for (let tz = 0; tz < targetZ; tz++) { | |
| const srcFloatZ = tz * ratioZ; | |
| const minZ = Math.max(0, Math.floor(srcFloatZ) - 3); | |
| const maxZ = Math.min(sourceZ - 1, Math.floor(srcFloatZ) + 3); | |
| for (let tx = 0; tx < targetX; tx++) { | |
| const srcFloatX = tx * ratioX; | |
| const minX = Math.max(0, Math.floor(srcFloatX) - 3); | |
| const maxX = Math.min(sourceX - 1, Math.floor(srcFloatX) + 3); | |
| let accumulator = 0.0; | |
| let normalization = 0.0; | |
| for (let sz = minZ; sz <= maxZ; sz++) { | |
| const weightZ = sinc(srcFloatZ - sz); | |
| for (let sx = minX; sx <= maxX; sx++) { | |
| const weightX = sinc(srcFloatX - sx); | |
| const weight = weightZ * weightX; | |
| accumulator += sourceData[sz * sourceX + sx] * weight; | |
| normalization += weight; | |
| } | |
| } | |
| output[tz * targetX + tx] = normalization === 0 ? 0.0 : accumulator / normalization; | |
| } | |
| } | |
| return output; | |
| } | |
| export function executeDeltaAndSimilarityFallback(matrixA, matrixB, length) { | |
| const delta = new Float32Array(length); | |
| let dotProduct = 0.0; | |
| let normA = 0.0; | |
| let normB = 0.0; | |
| for (let i = 0; i < length; i++) { | |
| const valA = matrixA[i]; | |
| const valB = matrixB[i]; | |
| delta[i] = valA - valB; | |
| dotProduct += valA * valB; | |
| normA += valA * valA; | |
| normB += valB * valB; | |
| } | |
| const similarity = normA === 0 || normB === 0 | |
| ? 0.0 | |
| : dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); | |
| return { delta, similarity }; | |
| } | |
| export function executeVarianceSortFallback(data, rows, cols) { | |
| const variances = new Float32Array(cols); | |
| const indices = new Int32Array(cols); | |
| for (let c = 0; c < cols; c++) { | |
| indices[c] = c; | |
| let sum = 0.0; | |
| let sumSq = 0.0; | |
| for (let r = 0; r < rows; r++) { | |
| const val = data[r * cols + c]; | |
| sum += val; | |
| sumSq += val * val; | |
| } | |
| const mean = sum / rows; | |
| variances[c] = (sumSq / rows) - (mean * mean); | |
| } | |
| const indexArray = Array.from(indices); | |
| indexArray.sort((a, b) => variances[b] - variances[a]); | |
| const sortedIndices = new Int32Array(indexArray); | |
| return { indices: sortedIndices, variances }; | |
| } | |