xen-dev-utils
Version:
Utility functions used by the Scale Workshop ecosystem
801 lines • 27.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.solveDiophantine = exports.respell = exports.nearestPlane = exports.canonical = exports.defactoredHnf = exports.fractionalMatsub = exports.fractionalMatadd = exports.fractionalMatscale = exports.matsub = exports.matadd = exports.matscale = exports.minor = exports.fractionalTranspose = exports.fractionalDet = exports.det = exports.fractionalMatmul_ = exports.fractionalMatmul = exports.matmul = exports.fractionalInv = exports.inv = exports.fractionalEye = exports.eye = exports.fractionalLenstraLenstraLovasz = exports.lenstraLenstraLovasz = exports.fractionalGram = exports.gram = void 0;
const fraction_1 = require("./fraction");
const monzo_1 = require("./monzo");
const number_array_1 = require("./number-array");
const hnf_1 = require("./hnf");
/**
* Perform Gram–Schmidt process without normalization.
* @param basis Array of basis elements.
* @param epsilon Threshold for zero.
* @returns The orthogonalized basis and its dual (duals of near-zero basis elements are coerced to zero).
*/
function gram(basis, epsilon = 1e-12) {
const ortho = [];
const squaredLengths = [];
const dual = [];
for (let i = 0; i < basis.length; ++i) {
ortho.push([...basis[i]]);
for (let j = 0; j < i; ++j) {
ortho[i] = (0, monzo_1.sub)(ortho[i], (0, monzo_1.scale)(ortho[j], (0, number_array_1.dot)(ortho[i], dual[j])));
}
squaredLengths.push((0, number_array_1.dot)(ortho[i], ortho[i]));
if (squaredLengths[i] > epsilon) {
dual.push((0, monzo_1.scale)(ortho[i], 1 / squaredLengths[i]));
}
else {
dual.push((0, monzo_1.scale)(ortho[i], 0));
}
}
return { ortho, squaredLengths, dual };
}
exports.gram = gram;
/**
* Perform Gram–Schmidt process without normalization.
* @param basis Array of rational basis elements.
* @returns The orthogonalized basis and its dual as arrays of fractions (duals of zero basis elements are coerced to zero).
*/
function fractionalGram(basis) {
const ortho = [];
const squaredLengths = [];
const dual = [];
for (let i = 0; i < basis.length; ++i) {
ortho.push(basis[i].map(f => new fraction_1.Fraction(f)));
for (let j = 0; j < i; ++j) {
ortho[i] = (0, monzo_1.fractionalSub)(ortho[i], (0, monzo_1.fractionalScale)(ortho[j], (0, monzo_1.fractionalDot)(ortho[i], dual[j])));
}
squaredLengths.push((0, monzo_1.fractionalDot)(ortho[i], ortho[i]));
if (squaredLengths[i].n) {
dual.push((0, monzo_1.fractionalScale)(ortho[i], squaredLengths[i].inverse()));
}
else {
dual.push((0, monzo_1.fractionalScale)(ortho[i], squaredLengths[i]));
}
}
return { ortho, squaredLengths, dual };
}
exports.fractionalGram = fractionalGram;
/**
* Preform Lenstra-Lenstra-Lovász basis reduction.
* @param basis Array of basis elements.
* @param delta Lovász coefficient.
* @param epsilon Threshold for zero.
* @param maxIterations Maximum number of iterations to perform.
* @returns The basis processed to be short and nearly orthogonal alongside Gram-Schmidt coefficients.
*/
function lenstraLenstraLovasz(basis, delta = 0.75, epsilon = 1e-12, maxIterations = 10000) {
// https://en.wikipedia.org/wiki/Lenstra%E2%80%93Lenstra%E2%80%93Lov%C3%A1sz_lattice_basis_reduction_algorithm#LLL_algorithm_pseudocode
basis = basis.map(row => [...row]);
let { ortho, squaredLengths, dual } = gram(basis, epsilon);
let k = 1;
while (k < basis.length && maxIterations--) {
for (let j = k - 1; j >= 0; --j) {
const mu = (0, number_array_1.dot)(basis[k], dual[j]);
if (Math.abs(mu) > 0.5) {
basis[k] = (0, monzo_1.sub)(basis[k], (0, monzo_1.scale)(basis[j], Math.round(mu)));
({ ortho, squaredLengths, dual } = gram(basis, epsilon));
}
}
const mu = (0, number_array_1.dot)(basis[k], dual[k - 1]);
if (squaredLengths[k] > (delta - mu * mu) * squaredLengths[k - 1] ||
!squaredLengths[k - 1]) {
k++;
}
else {
const bk = basis[k];
basis[k] = basis[k - 1];
basis[k - 1] = bk;
({ ortho, squaredLengths, dual } = gram(basis, epsilon));
k = Math.max(k - 1, 1);
}
}
return {
basis,
gram: {
ortho,
squaredLengths,
dual,
},
};
}
exports.lenstraLenstraLovasz = lenstraLenstraLovasz;
const HALF = new fraction_1.Fraction(1, 2);
/**
* Preform Lenstra-Lenstra-Lovász basis reduction using rational numbers.
* @param basis Array of rational basis elements.
* @param delta Lovász coefficient.
* @param maxIterations Maximum number of iterations to perform.
* @returns The basis processed to be short and nearly orthogonal alongside Gram-Schmidt coefficients.
*/
function fractionalLenstraLenstraLovasz(basis, delta = '3/4', maxIterations = 10000) {
const result = basis.map(row => row.map(f => new fraction_1.Fraction(f)));
const delta_ = new fraction_1.Fraction(delta);
let { ortho, squaredLengths, dual } = fractionalGram(result);
let k = 1;
while (k < result.length && maxIterations--) {
for (let j = k - 1; j >= 0; --j) {
const mu = (0, monzo_1.fractionalDot)(result[k], dual[j]);
if (mu.abs().compare(HALF) > 0) {
result[k] = (0, monzo_1.fractionalSub)(result[k], (0, monzo_1.fractionalScale)(result[j], mu.round()));
({ ortho, squaredLengths, dual } = fractionalGram(result));
}
}
const mu = (0, monzo_1.fractionalDot)(result[k], dual[k - 1]);
if (squaredLengths[k].compare(delta_.sub(mu.mul(mu)).mul(squaredLengths[k - 1])) > 0 ||
!squaredLengths[k - 1].n) {
k++;
}
else {
const bk = result[k];
result[k] = result[k - 1];
result[k - 1] = bk;
({ ortho, squaredLengths, dual } = fractionalGram(result));
k = Math.max(k - 1, 1);
}
}
return {
basis: result,
gram: {
ortho,
squaredLengths,
dual,
},
};
}
exports.fractionalLenstraLenstraLovasz = fractionalLenstraLenstraLovasz;
/**
* Return a 2-D array with ones on the diagonal and zeros elsewhere.
* @param N Number of rows in the output.
* @param M Number of columns in the output.
* @param k Index of the diagonal.
* @returns An array where all elements are equal to zero, except for the `k`-th diagonal, whose values are equal to one.
*/
function eye(N, M, k = 0) {
M ?? (M = N);
const result = [];
for (let i = 0; i < N; ++i) {
result.push(Array(M).fill(0));
if (i >= k && i + k < M) {
result[i][i + k] = 1;
}
}
return result;
}
exports.eye = eye;
/**
* Return a 2-D array with ones on the diagonal and zeros elsewhere.
* @param N Number of rows in the output.
* @param M Number of columns in the output.
* @param k Index of the diagonal.
* @returns An array where all elements are equal to zero, except for the `k`-th diagonal, whose values are equal to one.
*/
function fractionalEye(N, M, k = 0) {
M ?? (M = N);
const result = [];
for (let i = 0; i < N; ++i) {
const row = [];
for (let j = 0; j < M; ++j) {
if (j === i + k) {
row.push(new fraction_1.Fraction(1));
}
else {
row.push(new fraction_1.Fraction(0));
}
}
result.push(row);
}
return result;
}
exports.fractionalEye = fractionalEye;
// XXX: I'm sorry. This matrix inversion algorithm is not particularly sophisticated. Existing solutions just come with too much bloat.
/**
* Compute the (multiplicative) inverse of a matrix.
* @param matrix Matrix to be inverted.
* @returns The multiplicative inverse.
* @throws An error if the matrix is not square or not invertible.
*/
function inv(matrix) {
let width = 0;
const height = matrix.length;
for (const row of matrix) {
width = Math.max(width, row.length);
}
if (width !== height) {
throw new Error('Non-square matrix');
}
const result = [];
for (let i = 0; i < height; ++i) {
result.push(Array(width).fill(0));
result[i][i] = 1;
}
// Don't modify input
matrix = matrix.map(row => [...row]);
// Coerce missing entries to zeros
for (let y = 0; y < height; ++y) {
for (let x = matrix[y].length; x < width; ++x) {
matrix[y][x] = 0;
}
}
// Put ones along the diagonal, zeros in the lower triangle
for (let x = 0; x < width; ++x) {
// Maintain row echelon form by pivoting on the most dominant row.
let pivot;
let s = 0;
for (let y = x; y < height; ++y) {
if (Math.abs(matrix[y][x]) > Math.abs(s)) {
pivot = y;
s = matrix[y][x];
}
}
if (pivot === undefined) {
throw new Error('Matrix is singular');
}
if (x !== pivot) {
let temp = matrix[pivot];
matrix[pivot] = matrix[x];
matrix[x] = temp;
temp = result[pivot];
result[pivot] = result[x];
result[x] = temp;
}
if (s !== 1) {
s = 1 / s;
matrix[x] = matrix[x].map(a => a * s);
result[x] = result[x].map(a => a * s);
}
for (let y = x + 1; y < height; ++y) {
s = matrix[y][x];
if (s) {
result[y] = result[y].map((a, i) => a - s * result[x][i]);
// Ignore entries that are not used later on.
for (let i = x + 1; i < width; ++i) {
matrix[y][i] -= s * matrix[x][i];
}
// Full row operation for reference:
// matrix[y] = matrix[y].map((a, i) => a - s * matrix[x][i]);
}
}
}
// Eliminate remaining entries in the upper triangle
for (let x = width - 1; x > 0; --x) {
for (let y = x - 1; y >= 0; --y) {
const s = matrix[y][x];
if (s) {
// No need to keep track of these entries anymore.
// matrix[y] = matrix[y].map((a, i) => a - s * matrix[x][i]);
result[y] = result[y].map((a, i) => a - s * result[x][i]);
}
}
}
return result;
}
exports.inv = inv;
/**
* Compute the (multiplicative) inverse of a matrix.
* @param matrix Matrix to be inverted.
* @returns The multiplicative inverse.
* @throws An error if the matrix is not square or not invertible.
*/
function fractionalInv(matrix) {
let width = 0;
const height = matrix.length;
for (const row of matrix) {
width = Math.max(width, row.length);
}
if (width !== height) {
throw new Error('Non-square matrix');
}
const result = [];
for (let i = 0; i < height; ++i) {
const row = [];
for (let j = 0; j < width; ++j) {
if (i === j) {
row.push(new fraction_1.Fraction(1));
}
else {
row.push(new fraction_1.Fraction(0));
}
}
result.push(row);
}
// Don't modify input
const matrix_ = matrix.map(row => row.map(f => new fraction_1.Fraction(f)));
// Coerce missing entries to zeros
for (let y = 0; y < height; ++y) {
for (let x = matrix_[y].length; x < width; ++x) {
matrix_[y][x] = new fraction_1.Fraction(0);
}
}
// Put ones along the diagonal, zeros in the lower triangle
for (let x = 0; x < width; ++x) {
let s = matrix_[x][x];
if (!s.n) {
// Row echelon form (pivoting makes no difference over rationals)
// TODO: Figure out if there's a strategy to avoid blowing safe limits during manipulation.
for (let y = x + 1; y < height; ++y) {
if (matrix_[y][x].n) {
let temp = matrix_[y];
matrix_[y] = matrix_[x];
matrix_[x] = temp;
temp = result[y];
result[y] = result[x];
result[x] = temp;
break;
}
}
s = matrix_[x][x];
if (!s.n) {
throw new Error('Matrix is singular');
}
}
if (!s.isUnity()) {
matrix_[x] = matrix_[x].map(a => a.div(s));
result[x] = result[x].map(a => a.div(s));
}
for (let y = x + 1; y < height; ++y) {
s = matrix_[y][x];
if (s.n) {
result[y] = result[y].map((a, i) => a.sub(s.mul(result[x][i])));
// Ignore entries that are not used later on.
for (let i = x + 1; i < width; ++i) {
matrix_[y][i] = matrix_[y][i].sub(s.mul(matrix_[x][i]));
}
// Full row operation for reference:
// matrix_[y] = matrix_[y].map((a, i) => a.sub(s.mul(matrix_[x][i])));
}
}
}
// Eliminate remaining entries in the upper triangle
for (let x = width - 1; x > 0; --x) {
for (let y = x - 1; y >= 0; --y) {
const s = matrix_[y][x];
if (s.n) {
// No need to keep track of these entries anymore.
// matrix_[y] = matrix_[y].map(...);
result[y] = result[y].map((a, i) => a.sub(s.mul(result[x][i])));
}
}
}
return result;
}
exports.fractionalInv = fractionalInv;
function matmul(A, B) {
let numVectors = 0;
if (!Array.isArray(A[0])) {
A = [A];
numVectors++;
}
if (!Array.isArray(B[0])) {
B = B.map(c => [c]);
numVectors++;
}
const result = matmul_(A, B);
if (numVectors === 1) {
return result.flat();
}
else if (numVectors === 2) {
return result[0][0];
}
return result;
}
exports.matmul = matmul;
function matmul_(A, B) {
const height = A.length;
let width = 0;
for (const row of B) {
width = Math.max(width, row.length);
}
let n = 0;
for (const row of A) {
n = Math.max(n, row.length);
}
B = [...B];
while (B.length < n) {
B.push([]);
}
const result = [];
for (let i = 0; i < height; ++i) {
const row = Array(width).fill(0);
const rowA = A[i];
for (let j = 0; j < width; ++j) {
for (let k = 0; k < rowA.length; ++k) {
row[j] += rowA[k] * (B[k][j] ?? 0);
}
}
result.push(row);
}
return result;
}
function fractionalMatmul(A, B) {
let numVectors = 0;
if (!Array.isArray(A[0])) {
A = [A];
numVectors++;
}
if (!Array.isArray(B[0])) {
B = B.map(c => [c]);
numVectors++;
}
const result = fractionalMatmul_(A, B);
if (numVectors === 1) {
return result.flat();
}
else if (numVectors === 2) {
return result[0][0];
}
return result;
}
exports.fractionalMatmul = fractionalMatmul;
function fractionalMatmul_(A, B) {
const height = A.length;
let width = 0;
for (const row of B) {
width = Math.max(width, row.length);
}
let n = 0;
for (const row of A) {
n = Math.max(n, row.length);
}
const B_ = B.map(row => row.map(f => new fraction_1.Fraction(f)));
while (B_.length < n) {
B_.push([]);
}
for (const row of B_) {
while (row.length < width) {
row.push(new fraction_1.Fraction(0));
}
}
const result = [];
for (let i = 0; i < height; ++i) {
const row = [];
while (row.length < width) {
row.push(new fraction_1.Fraction(0));
}
const rowA = A[i].map(f => new fraction_1.Fraction(f));
for (let j = 0; j < width; ++j) {
for (let k = 0; k < rowA.length; ++k) {
row[j] = row[j].add(rowA[k].mul(B_[k][j]));
}
}
result.push(row);
}
return result;
}
exports.fractionalMatmul_ = fractionalMatmul_;
/**
* Compute the determinant of a matrix.
* @param matrix Array of arrays of numbers to calculate determinant for.
* @returns The determinant.
*/
function det(matrix) {
let width = 0;
const height = matrix.length;
for (const row of matrix) {
width = Math.max(width, row.length);
}
if (width !== height) {
throw new Error('Non-square matrix');
}
matrix = matrix.map(row => [...row]);
let result = 1;
for (let x = 0; x < width; ++x) {
// Maintain row echelon form by pivoting on the most dominant row.
let pivot;
let d = 0;
for (let y = x; y < height; ++y) {
if (Math.abs(matrix[y][x]) > Math.abs(d)) {
pivot = y;
d = matrix[y][x];
}
}
if (pivot === undefined) {
return 0;
}
if (x !== pivot) {
const temp = matrix[pivot];
matrix[pivot] = matrix[x];
matrix[x] = temp;
result = -result;
}
result *= d;
d = 1 / d;
for (let y = x + 1; y < height; ++y) {
const row = matrix[y];
const s = row[x] * d;
if (s) {
// Skip over entries that are not used later.
const upperRow = matrix[x];
for (let i = x + 1; i < upperRow.length; ++i) {
row[i] -= s * upperRow[i];
}
// Full row operation for reference:
// matrix[y] = matrix[y].map((a, i) => a - s * (matrix[x][i] ?? 0));
}
}
}
return result;
}
exports.det = det;
/**
* Compute the determinant of a matrix with rational entries.
* @param matrix Array of arrays of fractions to calculate determinant for.
* @returns The determinant.
*/
function fractionalDet(matrix) {
let width = 0;
const height = matrix.length;
for (const row of matrix) {
width = Math.max(width, row.length);
}
if (width !== height) {
throw new Error('Non-square matrix');
}
const matrix_ = matrix.map(row => row.map(f => new fraction_1.Fraction(f)));
for (const row of matrix_) {
while (row.length < width) {
row.push(new fraction_1.Fraction(0));
}
}
let result = new fraction_1.Fraction(1);
for (let x = 0; x < width; ++x) {
let d = matrix_[x][x];
if (!d.n) {
// Row echelon form
for (let y = x + 1; y < height; ++y) {
if (matrix_[y][x].n) {
const temp = matrix_[y];
matrix_[y] = matrix_[x];
matrix_[x] = temp;
result = result.neg();
break;
}
}
d = matrix_[x][x];
if (!d.n) {
return new fraction_1.Fraction(0);
}
}
result = result.mul(d);
for (let y = x + 1; y < height; ++y) {
const row = matrix_[y];
const s = row[x].div(d);
if (s.n) {
// Skip over entries that are not used later.
const upperRow = matrix_[x];
for (let i = x + 1; i < width; ++i) {
row[i] = row[i].sub(s.mul(upperRow[i]));
}
}
}
}
return result;
}
exports.fractionalDet = fractionalDet;
/**
* Transpose a 2-D matrix with rational entries (swap rows and columns).
* @param matrix Matrix to transpose.
* @returns The transpose.
*/
function fractionalTranspose(matrix) {
let width = 0;
for (const row of matrix) {
width = Math.max(row.length, width);
}
const result = [];
for (let i = 0; i < width; ++i) {
const row = [];
for (let j = 0; j < matrix.length; ++j) {
row.push(new fraction_1.Fraction(matrix[j][i] ?? 0));
}
result.push(row);
}
return result;
}
exports.fractionalTranspose = fractionalTranspose;
/**
* Obtain the minor a matrix.
* @param matrix The input matrix.
* @param i The row to remove.
* @param j The column to remove.
* @returns The spliced matrix.
*/
function minor(matrix, i, j) {
matrix = matrix.map(row => [...row]);
matrix.splice(i, 1);
for (const row of matrix) {
row.splice(j, 1);
}
return matrix;
}
exports.minor = minor;
/**
* Scale a matrix by a scalar.
* @param matrix The matrix to scale.
* @param amount The amount to scale by.
* @returns The scalar multiple.
*/
function matscale(matrix, amount) {
return matrix.map(row => (0, monzo_1.scale)(row, amount));
}
exports.matscale = matscale;
/**
* Add two matrices.
* @param A The first matrix.
* @param B The second matrix.
* @returns The sum.
*/
function matadd(A, B) {
const result = [];
const numRows = Math.max(A.length, B.length);
for (let i = 0; i < numRows; ++i) {
result.push((0, monzo_1.add)(A[i] ?? [], B[i] ?? []));
}
return result;
}
exports.matadd = matadd;
/**
* Subtract two matrices.
* @param A The matrix to subtract from.
* @param B The matrix to subtract by.
* @returns The difference.
*/
function matsub(A, B) {
const result = [];
const numRows = Math.max(A.length, B.length);
for (let i = 0; i < numRows; ++i) {
result.push((0, monzo_1.sub)(A[i] ?? [], B[i] ?? []));
}
return result;
}
exports.matsub = matsub;
/**
* Scale a matrix by a scalar.
* @param matrix The matrix to scale.
* @param amount The amount to scale by.
* @returns The scalar multiple.
*/
function fractionalMatscale(matrix, amount) {
return matrix.map(row => (0, monzo_1.fractionalScale)(row, amount));
}
exports.fractionalMatscale = fractionalMatscale;
/**
* Add two matrices.
* @param A The first matrix.
* @param B The second matrix.
* @returns The sum.
*/
function fractionalMatadd(A, B) {
const result = [];
const numRows = Math.max(A.length, B.length);
for (let i = 0; i < numRows; ++i) {
result.push((0, monzo_1.fractionalAdd)(A[i] ?? [], B[i] ?? []));
}
return result;
}
exports.fractionalMatadd = fractionalMatadd;
/**
* Subtract two matrices.
* @param A The matrix to subtract from.
* @param B The matrix to subtract by.
* @returns The difference.
*/
function fractionalMatsub(A, B) {
const result = [];
const numRows = Math.max(A.length, B.length);
for (let i = 0; i < numRows; ++i) {
result.push((0, monzo_1.fractionalSub)(A[i] ?? [], B[i] ?? []));
}
return result;
}
exports.fractionalMatsub = fractionalMatsub;
// Finds the Hermite normal form and 'defactors' it.
// Defactoring is also known as saturation.
// This removes torsion from the map.
// Algorithm as described by:
//
// Clément Pernet and William Stein.
// Fast Computation of HNF of Random Integer Matrices.
// Journal of Number Theory.
// https://doi.org/10.1016/j.jnt.2010.01.017
// See section 8.
/**
* Compute the Hermite normal form with torsion removed.
* @param M The input matrix.
* @returns The defactored Hermite normal form.
*/
function defactoredHnf(M) {
// Need to convert to bigint so that intermediate results don't blow up.
const bigM = M.map(row => row.map(BigInt));
const K = (0, hnf_1.hnf)((0, hnf_1.transpose)(bigM));
while (K.length > M.length) {
K.pop();
}
const determinant = (0, hnf_1.integerDet)(K);
if (determinant === 1n) {
return (0, hnf_1.hnf)(bigM).map(row => row.map(Number));
}
const S = inv((0, hnf_1.transpose)(K).map(row => row.map(Number)));
const D = matmul(S, M).map(row => row.map(Math.round));
return (0, hnf_1.hnf)(D);
}
exports.defactoredHnf = defactoredHnf;
/**
* Compute the canonical form of the input.
* @param M Input maps.
* @returns Defactored Hermite normal form or the antitranspose sandwich for commas bases.
*/
function canonical(M) {
for (const row of M) {
if (row.length < M.length) {
// Comma basis
return (0, hnf_1.antitranspose)(defactoredHnf((0, hnf_1.antitranspose)(M)));
}
}
return defactoredHnf(M);
}
exports.canonical = canonical;
// Babai's nearest plane algorithm for solving approximate CVP
// `basis` should be LLL reduced first
/**
* Solve approximate CVP using Babai's nearest plane algorithm.
* @param v Vector to reduce.
* @param basis LLL basis to reduce with.
* @param dual Optional precalculated geometric duals of the orthogonalized basis.
* @returns The reduced vector.
*/
function nearestPlane(v, basis, dual) {
// Body moved to respell to save a sub() call.
return (0, monzo_1.sub)(v, respell(v, basis, dual));
}
exports.nearestPlane = nearestPlane;
/**
* Respell a monzo represting a rational number to a simpler form.
* @param monzo Array of prime exponents to simplify.
* @param commas Monzos representing near-unisons to simplify by. Should be LLL reduced to work properly.
* @param commaOrthoDuals Optional precalculated geometric duals of the orthogonalized comma basis.
* @returns An array of prime exponents representing a simpler rational number.
*/
function respell(monzo, commas, commaOrthoDuals) {
if (commaOrthoDuals === undefined) {
commaOrthoDuals = gram(commas).dual;
}
monzo = [...monzo];
for (let i = commaOrthoDuals.length - 1; i >= 0; --i) {
const mu = (0, number_array_1.dot)(monzo, commaOrthoDuals[i]);
monzo = (0, monzo_1.sub)(monzo, (0, monzo_1.scale)(commas[i], Math.round(mu)));
}
return monzo;
}
exports.respell = respell;
function solveDiophantine(A, b) {
const hasMultiple = Array.isArray(b[0]);
const B = hasMultiple
? (0, hnf_1.padMatrix)(b).M
: b.map(c => [c]);
// Need to convert to bigint so that intermediate results don't blow up.
const { width, height, M } = (0, hnf_1.padMatrix)(A.map(row => row.map(BigInt)));
for (let i = 0; i < height; ++i) {
M[i].push(...B[i].map(BigInt));
}
const H = (0, hnf_1.hnf)(M);
while (H.length > width) {
H.pop();
}
const c = [];
for (const row of H) {
c.push(row.splice(width, row.length - width).map(Number));
}
const S = inv(H.map(row => row.map(Number)));
const sol = matmul(S, c).map(row => row.map(Math.round));
// Check solution(s).
const BS = matmul(A, sol);
for (let i = 0; i < B.length; ++i) {
if (!(0, monzo_1.monzosEqual)(B[i], BS[i])) {
throw new Error('Could not solve system');
}
}
return hasMultiple ? sol : sol.map(row => row[0]);
}
exports.solveDiophantine = solveDiophantine;
//# sourceMappingURL=basis.js.map