// Small, dependency-free teaching model. Layout: X[C][H][W], W[M][C/group][R][S]. export function outputSize(size, kernel, stride = 1, padBefore = 0, padAfter = 0, dilation = 1) { if (![size, kernel, stride, dilation].every(v => Number.isInteger(v) && v > 0) || ![padBefore, padAfter].every(v => Number.isInteger(v) && v >= 0)) throw new RangeError('Invalid shape'); return Math.max(0, Math.floor((size + padBefore + padAfter - dilation * (kernel - 1) - 1) / stride) + 1); } export function roundEven(value) { if (!Number.isFinite(value)) throw new RangeError('Finite number required'); const floor = Math.floor(value), fraction = value - floor; return fraction < 0.5 ? floor : fraction > 0.5 ? floor + 1 : floor % 2 === 0 ? floor : floor + 1; } export const clamp = (value, low, high) => Math.min(high, Math.max(low, value)); export function quantize(value, scale, zeroPoint = 0, low = -128, high = 127) { if (!(scale > 0) || !Number.isFinite(scale)) throw new RangeError('Positive finite scale required'); return clamp(roundEven(value / scale) + zeroPoint, low, high); } export const dequantize = (value, scale, zeroPoint = 0) => (value - zeroPoint) * scale; export function requantize(acc, multiplier, zeroPoint = 0, low = -128, high = 127) { return clamp(roundEven(acc * multiplier) + zeroPoint, low, high); } export function conv2d(x, weights, {stride = 1, pads = [0, 0, 0, 0], dilation = 1, groups = 1, bias = null} = {}) { const channels = x.length, height = x[0]?.length, width = x[0]?.[0]?.length; const outputs = weights.length, r = weights[0]?.[0]?.length, s = weights[0]?.[0]?.[0]?.length; if (!Number.isInteger(groups) || groups < 1 || channels % groups || outputs % groups) throw new RangeError('Invalid groups'); if (pads.length !== 4) throw new RangeError('Four explicit pads required'); if (!height || !width || !r || !s || x.some(c => c.length !== height || c.some(row => row.length !== width)) || weights.some(m => m.length !== channels / groups || m.some(c => c.length !== r || c.some(row => row.length !== s)))) throw new RangeError('Ragged or incompatible tensor'); if (bias && bias.length !== outputs) throw new RangeError('Bias shape'); if (x.flat(2).some(value => !Number.isSafeInteger(value)) || weights.flat(3).some(value => !Number.isSafeInteger(value)) || (bias && bias.some(value => !Number.isSafeInteger(value)))) throw new RangeError('Safe integer operands and bias required'); const [top, left, bottom, right] = pads; const outH = outputSize(height, r, stride, top, bottom, dilation); const outW = outputSize(width, s, stride, left, right, dilation); const y = Array.from({length: outputs}, () => Array.from({length: outH}, () => Array(outW).fill(0))); for (let m = 0; m < outputs; m++) for (let h = 0; h < outH; h++) for (let w = 0; w < outW; w++) { let acc = bias?.[m] ?? 0; const firstC = Math.floor(m / (outputs / groups)) * (channels / groups); for (let c = 0; c < channels / groups; c++) for (let kh = 0; kh < r; kh++) for (let kw = 0; kw < s; kw++) { const ih = h * stride - top + kh * dilation, iw = w * stride - left + kw * dilation; if (ih >= 0 && ih < height && iw >= 0 && iw < width) { const product = x[firstC + c][ih][iw] * weights[m][c][kh][kw]; if (!Number.isSafeInteger(product)) throw new RangeError('Unsafe integer product'); const next = acc + product; if (!Number.isSafeInteger(next)) throw new RangeError('Unsafe integer partial sum'); acc = next; } } if (!Number.isSafeInteger(acc)) throw new RangeError('This teaching model requires safe integer accumulators'); y[m][h][w] = acc; } return y; } export function im2col(x, kernelH, kernelW) { const height = x[0].length, width = x[0][0].length, rows = []; for (let h = 0; h <= height - kernelH; h++) for (let w = 0; w <= width - kernelW; w++) { const row = []; for (const channel of x) for (let r = 0; r < kernelH; r++) for (let s = 0; s < kernelW; s++) row.push(channel[h + r][w + s]); rows.push(row); } return rows; } export function dot(a, b) { if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) throw new RangeError('Incompatible dot vectors'); let acc = 0; for (let i = 0; i < a.length; i++) { if (!Number.isSafeInteger(a[i]) || !Number.isSafeInteger(b[i])) throw new RangeError('Safe integer dot operands required'); const product = a[i] * b[i]; if (!Number.isSafeInteger(product)) throw new RangeError('Unsafe integer dot product'); const next = acc + product; if (!Number.isSafeInteger(next)) throw new RangeError('Unsafe integer dot partial sum'); acc = next; } return acc; } export function maxPool2x2(matrix) { const output = []; for (let h = 0; h + 1 < matrix.length; h += 2) { const row = []; for (let w = 0; w + 1 < matrix[0].length; w += 2) row.push(Math.max(matrix[h][w], matrix[h][w + 1], matrix[h + 1][w], matrix[h + 1][w + 1])); output.push(row); } return output; }