UNPKG

xen-dev-utils

Version:

Utility functions used by the Scale Workshop ecosystem

311 lines 8.57 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.preimage = exports.cokernel = exports.kernel = exports.transpose = exports.antitranspose = exports.integerDet = exports.pruneZeroRows = exports.hnf = exports.padMatrix = void 0; /** * Algorithm adapted from https://github.com/lan496/hsnf * Guaranteed to not overflow with BigInt matrices */ const monzo_1 = require("./monzo"); function abs(x) { if (typeof x === 'number') { return Math.abs(x); } return (0, monzo_1.bigAbs)(x); } function floorDiv(x, y) { if (typeof x === 'number') { return Math.floor(x / y); } if (x >= 0n !== y >= 0n && x % y) { // @ts-ignore return x / y - 1n; } return (x / y); } function getPivot(A, i1, j) { let idx; let valmin; for (let i = i1; i < A.length; ++i) { if (!A[i][j]) { continue; } if (valmin === undefined || abs(A[i][j]) < valmin) { idx = i; valmin = abs(A[i][j]); } } return idx; } /** * Fix a 2-D matrix to have full rows (pad with zeros). * @param M Input matrix. * @returns Height, width, the padded matrix and the corresponding 0 or 0n and 1 or 1n. */ function padMatrix(M) { const height = M.length; if (!height) { return { height, width: 0, zero: 0, one: 1, M: [], }; } let width = 0; let zero; for (const row of M) { width = Math.max(width, row.length); if (row.length) { // @ts-ignore zero = typeof row[0] === 'number' ? 0 : 0n; } } M = M.map(row => [...row]); if (zero === undefined) { return { height, width, zero: 0, one: 1, M, }; } const one = typeof zero === 'number' ? 1 : 1n; for (const row of M) { while (row.length < width) { row.push(zero); } } return { height, width, zero, one, M, }; } exports.padMatrix = padMatrix; /** * Compute the Hermite normal form of a matrix of integers. * @param A The input matrix. * @returns The input in Hermite normal form. */ function hnf(A) { const { width, height, zero, one, M } = padMatrix(A); let si = 0; let sj = 0; // @ts-ignore const negOne = -one; while (true) { if (si === height || sj === width) { return M; } // choose a pivot const row = getPivot(M, si, sj); if (row === undefined) { // if there does not remain non-zero elements, go to a next column sj = sj + 1; continue; } // swap [M[si], M[row]] = [M[row], M[si]]; // eliminate the s-th column entries for (let i = si + 1; i < height; ++i) { if (M[i][sj]) { const k = floorDiv(M[i][sj], M[si][sj]); for (let j = 0; j < width; ++j) { // @ts-ignore M[i][j] -= k * M[si][j]; } } } // if there does not remain non-zero element in s-th column, find a next entry let rowDone = true; for (let i = si + 1; i < height; ++i) { if (M[i][sj]) { rowDone = false; } } if (rowDone) { if (M[si][sj] < zero) { for (let j = 0; j < width; ++j) { // @ts-ignore M[si][j] *= negOne; } } if (M[si][sj]) { for (let i = 0; i < si; ++i) { const k = floorDiv(M[i][sj], M[si][sj]); if (k) { for (let j = 0; j < width; ++j) { // @ts-ignore M[i][j] -= k * M[si][j]; } } } } si += 1; sj += 1; } } } exports.hnf = hnf; /** * Prune rows filled with falsy values from a 2-D matrix. * @param A Matrix to prune in-place. */ function pruneZeroRows(A) { for (let i = 0; i < A.length; ++i) { if (!A[i].some(Boolean)) { A.splice(i, 1); i--; } } } exports.pruneZeroRows = pruneZeroRows; // exact integer determinant using Bareiss algorithm // modified slightly from: // https://stackoverflow.com/questions/66192894/precise-determinant-of-integer-nxn-matrix /** * Compute the determinant of a matrix of integers. * @param A The input matrix. * @returns The determinant. */ function integerDet(A) { const { width, height, zero, one, M } = padMatrix(A); if (!width || !height || width !== height) { return zero; } let sign = one; let prev = sign; for (let i = 0; i < width - 1; ++i) { if (!M[i][i]) { // swap with another row having nonzero i's elem let swapto; for (let j = i + 1; j < height; ++j) { if (M[j][i]) { swapto = j; break; } } if (swapto === undefined) { return zero; // all M[*][i] are zero => zero determinant } // swap rows [M[i], M[swapto]] = [M[swapto], M[i]]; sign = -sign; } for (let j = i + 1; j < height; ++j) { for (let k = i + 1; k < width; ++k) { // assert (M[j, k] * M[i, i] - M[j, i] * M[i, k]) % prev == 0 // @ts-ignore M[j][k] = floorDiv(M[j][k] * M[i][i] - M[j][i] * M[i][k], prev); } } prev = M[i][i]; } // @ts-ignore return sign * M.pop().pop(); } exports.integerDet = integerDet; /** * Anti-transpose a 2-D matrix (swap rows and columns along the other diagonal). * @param matrix Matrix to antitranspose. * @returns The antitranspose. */ function antitranspose(matrix) { const { width, height, M } = padMatrix(matrix); const result = []; for (let i = width - 1; i >= 0; --i) { const row = []; for (let j = height - 1; j >= 0; --j) { row.push(M[j][i]); } result.push(row); } return result; } exports.antitranspose = antitranspose; /** * Transpose a 2-D matrix (swap rows and columns). * @param matrix Matrix to transpose. * @returns The transpose. */ function transpose(matrix) { const { width, height, M } = padMatrix(matrix); const result = []; for (let i = 0; i < width; ++i) { const row = []; for (let j = 0; j < height; ++j) { row.push(M[j][i]); } result.push(row); } return result; } exports.transpose = transpose; // Find the left kernel (nullspace) of M. // Adjoin an identity block matrix and solve for HNF. // This is equivalent to the highschool maths method, // but using HNF instead of Gaussian elimination. /** * Find the left kernel (nullspace) of the input matrix. * @param A The input matrix. * @returns The kernel matrix. */ function kernel(A) { const { width, height, zero, one, M } = padMatrix(A); for (let i = 0; i < width; ++i) { const row = Array(width).fill(zero); row[i] = one; M.push(row); } const K = transpose(hnf(transpose(M))); for (let i = 0; i < height; ++i) { K.shift(); } for (const row of K) { for (let i = 0; i < height; ++i) { row.shift(); } } return K; } exports.kernel = kernel; /** * Find the right kernel (nullspace) of the input matrix. * @param A The input matrix. * @returns The kernel matrix. */ function cokernel(A) { return transpose(kernel(transpose(A))); } exports.cokernel = cokernel; /** * Find the preimage X of A such that AX = I. * @param A The input matrix. * @returns The preimage. */ function preimage(A) { const { width, height, zero, one, M } = padMatrix(A); const B = transpose(M); for (let i = 0; i < width; ++i) { for (let j = 0; j < width; ++j) { // @ts-ignore B[i].push(i === j ? one : zero); } } const H = hnf(B); while (H.length > height) { H.pop(); } for (const row of H) { for (let i = 0; i < height; ++i) { row.shift(); } } return transpose(H); } exports.preimage = preimage; //# sourceMappingURL=hnf.js.map