UNPKG

xen-dev-utils

Version:

Utility functions used by the Scale Workshop ecosystem

770 lines 30 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const vitest_1 = require("vitest"); const basis_1 = require("../basis"); const monzo_1 = require("../monzo"); const number_array_1 = require("../number-array"); const primes_1 = require("../primes"); const fraction_1 = require("../fraction"); const hnf_1 = require("../hnf"); const FUZZ = 'FUZZ' in process.env; function naiveDet(matrix) { if (!matrix.length) { return 1; } let result = 0; for (let i = 0; i < matrix.length; ++i) { result += (-1) ** i * matrix[0][i] * naiveDet((0, basis_1.minor)(matrix, 0, i)); } return result; } (0, vitest_1.describe)('Gram process', () => { (0, vitest_1.it)('orthogonalizes a basis', () => { const basis = [ [1, 2, 3], [4, -5, 6], [-7, 8, 9], ]; const { ortho, dual } = (0, basis_1.gram)(basis); // Leading orientation (0, vitest_1.expect)((0, monzo_1.monzosEqual)(basis[0], ortho[0])); // Geometric duals (0, vitest_1.expect)((0, number_array_1.dot)(ortho[0], dual[0])).toBeCloseTo(1); (0, vitest_1.expect)((0, number_array_1.dot)(ortho[1], dual[1])).toBeCloseTo(1); (0, vitest_1.expect)((0, number_array_1.dot)(ortho[2], dual[2])).toBeCloseTo(1); // Orthogonality (0, vitest_1.expect)((0, number_array_1.dot)(ortho[0], ortho[1])).toBeCloseTo(0); (0, vitest_1.expect)((0, number_array_1.dot)(ortho[0], ortho[2])).toBeCloseTo(0); (0, vitest_1.expect)((0, number_array_1.dot)(ortho[1], ortho[2])).toBeCloseTo(0); // Value (0, vitest_1.expect)(ortho.map(o => o.map(c => c.toFixed(2)))).toEqual([ ['1.00', '2.00', '3.00'], ['3.14', '-6.71', '3.43'], ['-7.46', '-1.66', '3.59'], ]); }); (0, vitest_1.it)('handles non-basis', () => { const basis = [ [1, 2, 3, 4], [0, 1, 1, 0], [1, 1, 2, 4], [-1, 2, 0, 0], ]; const { ortho } = (0, basis_1.gram)(basis); // Pseudo-orthogonality (0, vitest_1.expect)((0, number_array_1.dot)(ortho[0], ortho[1])).toBeCloseTo(0); (0, vitest_1.expect)((0, number_array_1.dot)(ortho[0], ortho[2])).toBeCloseTo(0); (0, vitest_1.expect)((0, number_array_1.dot)(ortho[0], ortho[3])).toBeCloseTo(0); (0, vitest_1.expect)((0, number_array_1.dot)(ortho[1], ortho[2])).toBeCloseTo(0); (0, vitest_1.expect)((0, number_array_1.dot)(ortho[1], ortho[3])).toBeCloseTo(0); (0, vitest_1.expect)((0, number_array_1.dot)(ortho[2], ortho[3])).toBeCloseTo(0); }); vitest_1.it.runIf(FUZZ)('Fuzzes for random bases', () => { for (let k = 0; k < 100000; ++k) { const basis = []; for (let i = Math.random() * 10; i > 0; --i) { const row = []; for (let j = Math.random() * 10; j > 0; --j) { row.push(Math.random() * 100 - 50); } basis.push(row); } const { ortho } = (0, basis_1.gram)(basis); // Pseudo-orthogonality for (let i = 0; i < basis.length; ++i) { for (let j = 0; j < i; ++j) { (0, vitest_1.expect)((0, number_array_1.dot)(ortho[i], ortho[j])).toBeCloseTo(0); } } } }); }); (0, vitest_1.describe)('Gram process for arrays of fractions', () => { (0, vitest_1.it)('orthogonalizes a basis', () => { const basis = [ [1, 2, 3], [4, -5, 6], [-7, 8, 9], ]; const { ortho, dual } = (0, basis_1.fractionalGram)(basis); // Leading orientation (0, vitest_1.expect)((0, monzo_1.fractionalMonzosEqual)(basis[0], ortho[0])); // Geometric duals (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[0], dual[0]).toFraction()).toBe('1'); (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[1], dual[1]).toFraction()).toBe('1'); (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[2], dual[2]).toFraction()).toBe('1'); // Orthogonality (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[0], ortho[1]).n).toBe(0); (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[0], ortho[2]).n).toBe(0); (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[1], ortho[2]).n).toBe(0); // Value (0, vitest_1.expect)(ortho.map(o => o.map(c => c.toFraction()))).toEqual([ ['1', '2', '3'], ['22/7', '-47/7', '24/7'], ['-3483/467', '-774/467', '1677/467'], ]); }); (0, vitest_1.it)('handles non-basis', () => { const basis = [ [1, 2, 3, 4], [0, 1, 1, 0], [1, 1, 2, 4], [-1, 2, 0, 0], ]; const { ortho } = (0, basis_1.fractionalGram)(basis); // Pseudo-orthogonality (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[0], ortho[1]).n).toBeCloseTo(0); (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[0], ortho[2]).n).toBeCloseTo(0); (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[0], ortho[3]).n).toBeCloseTo(0); (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[1], ortho[2]).n).toBeCloseTo(0); (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[1], ortho[3]).n).toBeCloseTo(0); (0, vitest_1.expect)((0, monzo_1.fractionalDot)(ortho[2], ortho[3]).n).toBeCloseTo(0); }); }); (0, vitest_1.describe)('LLL basis reduction', () => { (0, vitest_1.it)('can LLL reduce', () => { const basis = [ [1, 1, 1], [-1, 0, 2], [3, 5, 6], ]; const lll = (0, basis_1.lenstraLenstraLovasz)(basis); // Size-reduction for (let i = 0; i < 3; ++i) { for (let j = 0; j < i; ++j) { (0, vitest_1.expect)(Math.abs((0, number_array_1.dot)(lll.basis[i], lll.gram.dual[j]))).toBeLessThanOrEqual(0.5); } } // Lovász condition for (let k = 1; k < 3; ++k) { const ok = lll.gram.ortho[k]; const ok1 = lll.gram.ortho[k - 1]; const mu = (0, number_array_1.dot)(lll.basis[k], lll.gram.dual[k - 1]); const n1 = (0, number_array_1.dot)(ok1, ok1); (0, vitest_1.expect)((n1 * 3) / 4).toBeLessThanOrEqual((0, number_array_1.dot)(ok, ok) + n1 * mu * mu); } (0, vitest_1.expect)(lll.basis).toEqual([ [0, 1, 0], [1, 0, 1], [-1, 0, 2], ]); }); (0, vitest_1.it)('handles non-basis', () => { const basis = [ [1, 2, 3, 4], [0, 1, 1, 0], [1, 1, 2, 4], [-1, 2, 0, 0], ]; const lll = (0, basis_1.lenstraLenstraLovasz)(basis); (0, vitest_1.expect)(lll.basis).toEqual([ [0, 0, 0, 0], [0, 1, 1, 0], [-1, 1, -1, 0], [0, 0, -1, 4], ]); }); (0, vitest_1.it)('can mess up the basis of miracle with naïve weights', () => { const basis = ['225/224', '1029/1024'].map(monzo_1.toMonzo); const lll = (0, basis_1.lenstraLenstraLovasz)(basis); (0, vitest_1.expect)(lll.basis.map(m => (0, monzo_1.monzoToFraction)(m).toFraction())).toEqual([ '225/224', '2401/2400', ]); }); (0, vitest_1.it)('can fix the basis of miracle with Tenney weights', () => { const basis = ['225/224', '2401/2400'] .map(monzo_1.toMonzo) .map(m => (0, monzo_1.applyWeights)(m, primes_1.LOG_PRIMES)); const lll = (0, basis_1.lenstraLenstraLovasz)(basis); const commas = lll.basis .map(m => (0, monzo_1.unapplyWeights)(m, primes_1.LOG_PRIMES).map(Math.round)) .map(m => (0, monzo_1.monzoToFraction)(m).toFraction()); (0, vitest_1.expect)(commas).toEqual(['225/224', '1029/1024']); }); vitest_1.it.runIf(FUZZ)('Fuzzes for random bases', () => { for (let k = 0; k < 1000; ++k) { let basis = []; for (let i = Math.random() * 10; i > 0; --i) { const row = []; for (let j = Math.random() * 10; j > 0; --j) { row.push(Math.random() * 100 - 50); } basis.push(row); } if (Math.random() < 0.5) { basis = basis.map(row => row.map(Math.round)); } const lll = (0, basis_1.lenstraLenstraLovasz)(basis); if (lll.gram.squaredLengths.every(l => l)) { // Size-reduction for (let i = 0; i < basis.length; ++i) { for (let j = 0; j < i; ++j) { (0, vitest_1.expect)(Math.abs((0, number_array_1.dot)(lll.basis[i], lll.gram.dual[j]))).toBeLessThanOrEqual(0.5); } } // Lovász condition for (let k = 1; k < basis.length; ++k) { const ok = lll.gram.ortho[k]; const ok1 = lll.gram.ortho[k - 1]; const mu = (0, number_array_1.dot)(lll.basis[k], lll.gram.dual[k - 1]); const n1 = (0, number_array_1.dot)(ok1, ok1); (0, vitest_1.expect)((n1 * 3) / 4).toBeLessThanOrEqual((0, number_array_1.dot)(ok, ok) + n1 * mu * mu); } } } }); }); (0, vitest_1.describe)('Precise LLL basis reduction', () => { (0, vitest_1.it)('can LLL reduce', () => { const basis = [ [1, 1, 1], [-1, 0, 2], [3, 5, 6], ]; const lll = (0, basis_1.fractionalLenstraLenstraLovasz)(basis); // Size-reduction for (let i = 0; i < 3; ++i) { for (let j = 0; j < i; ++j) { (0, vitest_1.expect)((0, monzo_1.fractionalDot)(lll.basis[i], lll.gram.dual[j]).compare(0.5)).toBeLessThanOrEqual(0); } } // Lovász condition for (let k = 1; k < 3; ++k) { const ok = lll.gram.ortho[k]; const ok1 = lll.gram.ortho[k - 1]; const mu = (0, monzo_1.fractionalDot)(lll.basis[k], lll.gram.dual[k - 1]); const n1 = (0, monzo_1.fractionalDot)(ok1, ok1); (0, vitest_1.expect)(n1.mul('3/4').compare((0, monzo_1.fractionalDot)(ok, ok).add(n1.mul(mu.mul(mu))))).toBeLessThanOrEqual(0); } (0, vitest_1.expect)(lll.basis.map(row => row.map(f => f.valueOf()))).toEqual([ [0, 1, 0], [1, 0, 1], [-1, 0, 2], ]); }); (0, vitest_1.it)('handles non-basis', () => { const basis = [ [1, 2, 3, 4], [0, 1, 1, 0], [1, 1, 2, 4], [-1, 2, 0, 0], ]; const lll = (0, basis_1.fractionalLenstraLenstraLovasz)(basis); (0, vitest_1.expect)(lll.basis.map(row => row.map(f => f.valueOf()))).toEqual([ [0, 0, 0, 0], [0, 1, 1, 0], [-1, 1, -1, 0], [0, 0, -1, 4], ]); }); vitest_1.it.runIf(FUZZ)('Fuzzes for random bases', () => { for (let k = 0; k < 500; ++k) { let basis = []; for (let i = Math.random() * 10; i > 0; --i) { const row = []; for (let j = Math.random() * 10; j > 0; --j) { row.push(Math.random() * 20 - 10); } basis.push(row); } basis = basis.map(row => row.map(Math.round)); try { const lll = (0, basis_1.fractionalLenstraLenstraLovasz)(basis); if (lll.gram.squaredLengths.every(l => l.n)) { // Size-reduction for (let i = 0; i < basis.length; ++i) { for (let j = 0; j < i; ++j) { (0, vitest_1.expect)((0, monzo_1.fractionalDot)(lll.basis[i], lll.gram.dual[j]) .abs() .compare(0.5)).toBeLessThanOrEqual(0); } } // Lovász condition for (let k = 1; k < basis.length; ++k) { const ok = lll.gram.ortho[k]; const ok1 = lll.gram.ortho[k - 1]; const mu = (0, monzo_1.fractionalDot)(lll.basis[k], lll.gram.dual[k - 1]); const n1 = (0, monzo_1.fractionalDot)(ok1, ok1); (0, vitest_1.expect)(n1 .mul('3/4') .compare((0, monzo_1.fractionalDot)(ok, ok).add(n1.mul(mu).mul(mu)))).toBeLessThanOrEqual(0); } } } catch (e) { const message = e instanceof Error ? e.message : String(e); (0, vitest_1.expect)(message).includes('above safe limit'); } } }); }); (0, vitest_1.describe)('Matrix multiplication', () => { (0, vitest_1.it)('multiplies two matrices', () => { const A = [ [1, 0, 1], [2, 1, 1], [0, 1, 1], [1, 1, 2], ]; const B = [ [1, 2, 1], [2, 3, 1], [4, 2, 2], ]; const C = (0, basis_1.matmul)(A, B); (0, vitest_1.expect)(C).toEqual([ [5, 4, 3], [8, 9, 5], [6, 5, 3], [11, 9, 6], ]); }); (0, vitest_1.it)('multiplies a matrix with a vector', () => { const A = [ [1, 2], [3, 4], ]; const v = [5, 6]; const u = (0, basis_1.matmul)(A, v); (0, vitest_1.expect)(u).toEqual([17, 39]); }); (0, vitest_1.it)('multiplies a vector with a matrix', () => { const A = [ [1, 2], [3, 4], ]; const v = [5, 6]; const u = (0, basis_1.matmul)(v, A); (0, vitest_1.expect)(u).toEqual([23, 34]); }); (0, vitest_1.it)('multiplies a vector with a vector', () => { const u = [1, 2]; const v = [5, 6]; const s = (0, basis_1.matmul)(u, v); (0, vitest_1.expect)(s).toEqual(17); }); (0, vitest_1.it)('multiplies two matrices (fractions)', () => { const A = [ [1, 0, 1], [2, 1, 1], [0, 0.5, 1], [1, 1, 2], ]; const B = [ [1, 2, 1], [2, 3, 1], ['4/3', 2, 2], ]; const C = (0, basis_1.fractionalMatmul)(A, B); (0, vitest_1.expect)(C.map(row => row.map(f => f.toFraction()))).toEqual([ ['7/3', '4', '3'], ['16/3', '9', '5'], ['7/3', '7/2', '5/2'], ['17/3', '9', '6'], ]); }); (0, vitest_1.it)('multiplies a matrix with a vector (fractions)', () => { const A = [ [1, 2], [3, 0.5], ]; const v = [5, '1/3']; const u = (0, basis_1.fractionalMatmul)(A, v); (0, vitest_1.expect)(u.map(f => f.toFraction())).toEqual(['17/3', '91/6']); }); (0, vitest_1.it)('multiplies a vector with a matrix (fractions)', () => { const A = [ [1, 2], [3, '1/3'], ]; const v = [5, 0.5]; const u = (0, basis_1.fractionalMatmul)(v, A); (0, vitest_1.expect)(u.map(f => f.toFraction())).toEqual(['13/2', '61/6']); }); (0, vitest_1.it)('multiplies a vector with a vector (fractions)', () => { const u = [0.5, 2]; const v = [5, '5/7']; const s = (0, basis_1.fractionalMatmul)(u, v); (0, vitest_1.expect)(s.toFraction()).toBe('55/14'); }); }); (0, vitest_1.describe)('Matrix inverse', () => { (0, vitest_1.it)('computes a 3x3 inverse', () => { const mat = [ [2, -1], // Missing entry interpreted as 0 [-1, 2, -1], [0, -1, 2], ]; const inverse = (0, basis_1.inv)(mat).map(row => row.map(x => Math.round(4 * x) / 4)); (0, vitest_1.expect)(inverse).toEqual([ [0.75, 0.5, 0.25], [0.5, 1, 0.5], [0.25, 0.5, 0.75], ]); }); (0, vitest_1.it)('computes another 3x3 inverse', () => { const mat = [ [-2, -1, 2], [2, 1, 4], [-3, 3, -1], ]; const inverse = (0, basis_1.inv)(mat).map(row => row.map(x => Math.round(100000 * x))); (0, vitest_1.expect)(inverse).toEqual([ [-24074, 9259, -11111], [-18519, 14815, 22222], [16667, 16667, 0], ]); }); vitest_1.it.each([2, 3, 4, 5, 6, 7, 8, 9, 10, 11])('computes inverse of a Vandermonde matrix %s', (N) => { const mat = []; for (const p of primes_1.PRIMES.slice(0, N)) { const row = [...Array(N).keys()].map(i => p ** -i); mat.push(row); } (0, vitest_1.expect)((0, basis_1.matmul)(mat, (0, basis_1.inv)(mat)).map(row => row.map(x => Math.round(10000 * x) / 10000 || 0))).toEqual((0, basis_1.eye)(N)); }); (0, vitest_1.it)('throws for non-square matrix', () => { (0, vitest_1.expect)(() => (0, basis_1.inv)([ [1, 2], [3, 4], [5, 6], ])).toThrow('Non-square matrix'); }); (0, vitest_1.it)('throws for singular matrix', () => { (0, vitest_1.expect)(() => (0, basis_1.inv)([ [1, 0], [0, 0], ])).toThrow('Matrix is singular'); }); (0, vitest_1.it)('computes a 3x3 with fractional entries', () => { const mat = [ [2, -1], // Missing entry interpreted as 0 [-1, '2/1', -1], [0, -1, '2'], ]; const inverse = (0, basis_1.fractionalInv)(mat).map(row => row.map(x => x.toFraction())); (0, vitest_1.expect)(inverse).toEqual([ ['3/4', '1/2', '1/4'], ['1/2', '1', '1/2'], ['1/4', '1/2', '3/4'], ]); }); (0, vitest_1.it)('computes another 3x3 inverse with fractional result', () => { const mat = [ [-2, -1, 2], [2, 1, 4], [-3, 3, -1], ]; const inverse = (0, basis_1.fractionalInv)(mat).map(row => row.map(x => x.toFraction())); (0, vitest_1.expect)(inverse).toEqual([ ['-13/54', '5/54', '-1/9'], ['-5/27', '4/27', '2/9'], ['1/6', '1/6', '0'], ]); }); vitest_1.it.each([2, 3, 4, 5, 6])('computes exact inverse of a Vandermonde matrix %s', (N) => { const mat = []; for (const p of primes_1.PRIMES.slice(0, N)) { const row = [...Array(N).keys()].map(i => new fraction_1.Fraction(p).pow(-i)); mat.push(row); } (0, vitest_1.expect)((0, basis_1.fractionalMatmul)(mat, (0, basis_1.fractionalInv)(mat)).map(row => row.map(x => x.valueOf()))).toEqual((0, basis_1.eye)(N)); }); (0, vitest_1.it)('throws for non-square matrix with fractional entries', () => { (0, vitest_1.expect)(() => (0, basis_1.fractionalInv)([ [1, 2], [3, 4], [5, 6], ])).toThrow('Non-square matrix'); }); (0, vitest_1.it)('throws for singular matrix with fractional entries', () => { (0, vitest_1.expect)(() => (0, basis_1.fractionalInv)([ [1, 0], [0, 0], ])).toThrow('Matrix is singular'); }); vitest_1.it.runIf(FUZZ)('fuzzes for random inverses', () => { for (let k = 0; k < 10000; ++k) { const mat = []; const N = Math.ceil(1 + Math.random() * 10); for (let i = 0; i < N; ++i) { const row = []; for (let j = 0; j < N; ++j) { row.push(Math.random() * 10 - 5); } mat.push(row); } const inverse = (0, basis_1.inv)(mat); const I = (0, basis_1.matmul)(mat, inverse).map(row => row.map(x => Math.round(x * 1024) / 1024 || 0)); (0, vitest_1.expect)(I).toEqual((0, basis_1.eye)(N)); } }); vitest_1.it.runIf(FUZZ)('fuzzes for random inverses (fractional)', () => { for (let k = 0; k < 1000; ++k) { const mat = []; const N = Math.ceil(1 + Math.random() * 7); for (let i = 0; i < N; ++i) { const row = []; for (let j = 0; j < N; ++j) { row.push(Math.round(Math.random() * 10 - 5)); } mat.push(row); } const determinant = naiveDet(mat); let inverse; try { inverse = (0, basis_1.fractionalInv)(mat); } catch (e) { const message = e instanceof Error ? e.message : String(e); if (!determinant) { (0, vitest_1.expect)(message).toBe('Matrix is singular'); } /** empty */ } if (inverse) { (0, vitest_1.expect)(determinant).not.toBe(0); const I = (0, basis_1.fractionalMatmul)(mat, inverse); (0, vitest_1.expect)(I).toEqual((0, basis_1.fractionalEye)(N)); } } }); }); (0, vitest_1.describe)('Determinant', () => { (0, vitest_1.it)('computes the determinant of a 3x3 matrix', () => { const mat = [ [-2, -1, 2], [2, 1, 4], [-3, 3, -1], ]; const determinant = (0, basis_1.det)(mat); (0, vitest_1.expect)(determinant).toBe(54); }); vitest_1.it.each([2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13])('computes the determinant of a Vandermonde matrix %s', (N) => { const mat = []; for (const p of primes_1.PRIMES.slice(0, N)) { const row = [...Array(N).keys()].map(i => p ** -i); mat.push(row); } let analytic = 1; for (let i = 0; i < N; ++i) { for (let j = i + 1; j < N; ++j) { analytic *= 1 / primes_1.PRIMES[j] - 1 / primes_1.PRIMES[i]; } } (0, vitest_1.expect)((0, basis_1.det)(mat) / analytic).toBeCloseTo(1, 1); }); (0, vitest_1.it)('computes 0 for the origin', () => { const mat = [[0, 0], []]; const determinant = (0, basis_1.det)(mat); (0, vitest_1.expect)(determinant).toBe(0); }); (0, vitest_1.it)('computes the area of a square', () => { const sides = [ [1, 1], [-1, 1], ]; const determinant = (0, basis_1.det)(sides); (0, vitest_1.expect)(determinant).toBe(2); }); (0, vitest_1.it)('computes the volume of a skew-aligned box', () => { const sides = [ [1, 1, 1], [-Math.SQRT2, Math.SQRT1_2, Math.SQRT1_2], [0, 1, -1], ]; const determinant = (0, basis_1.det)(sides); (0, vitest_1.expect)(determinant).toBe(-3 * Math.SQRT2); }); (0, vitest_1.it)('computes the determinant of a 3x3 matrix with fractional entries', () => { const mat = [ [-2, -1, 2], [2, 0.5, 4], [-3, 3, '-1/3'], ]; const d = (0, basis_1.fractionalDet)(mat); (0, vitest_1.expect)(d.toFraction()).toBe('152/3'); }); (0, vitest_1.it)('computes the determinant of a 4x4 matrix', () => { const mat = [ [-0, -1, 3, 2], [-0, -3, -0, 3], [2, 1, 0, -4], [-4, -3, -5, -3], ]; (0, vitest_1.expect)((0, basis_1.det)(mat)).toBe(-186); }); (0, vitest_1.it)('computes the determinant of a 4x4 matrix (fractional)', () => { const mat = [ [-0, -1, 3, 2], [-0, -3, -0, 3], [2, 1, 0, -4], [-4, -3, -5, -3], ]; (0, vitest_1.expect)((0, basis_1.fractionalDet)(mat).valueOf()).toBe(-186); }); vitest_1.it.runIf(FUZZ)('agrees with the naïve implementation', () => { for (let i = 0; i < 1000; ++i) { const mat = []; const N = Math.ceil(1 + Math.random() * 8); for (let i = 0; i < N; ++i) { const row = []; for (let j = 0; j < N; ++j) { row.push(Math.random() * 10 - 5); } mat.push(row); } const determinant = (0, basis_1.det)(mat); const naive = naiveDet(mat); (0, vitest_1.expect)(determinant).toBeCloseTo(naive); } }); vitest_1.it.runIf(FUZZ)('agrees with the naïve implementation (fractional)', () => { for (let i = 0; i < 100; ++i) { const mat = []; const N = Math.ceil(1 + Math.random() * 8); for (let i = 0; i < N; ++i) { const row = []; for (let j = 0; j < N; ++j) { row.push(Math.round(Math.random() * 10 - 5)); } mat.push(row); } const determinant = (0, basis_1.fractionalDet)(mat); const naive = naiveDet(mat); (0, vitest_1.expect)(determinant.valueOf()).toBe(naive); } }); }); (0, vitest_1.describe)('Transpose', () => { (0, vitest_1.it)('transposes a 3x2 matrix with rational entries', () => { const mat = [[1, 0.5], [3], ['2/7', 5]]; (0, vitest_1.expect)((0, basis_1.fractionalTranspose)(mat).map(row => row.map(f => f.toFraction()))).toEqual([ ['1', '3', '2/7'], ['1/2', '0', '5'], ]); }); }); (0, vitest_1.describe)('Auxiliary matrix methods', () => { (0, vitest_1.it)('scales', () => { const A = [ [1, 2], [3, 4], ]; (0, vitest_1.expect)((0, basis_1.matscale)(A, 2)).toEqual([ [2, 4], [6, 8], ]); (0, vitest_1.expect)((0, basis_1.fractionalMatscale)(A, 2).map(row => row.map(f => f.valueOf()))).toEqual([ [2, 4], [6, 8], ]); }); (0, vitest_1.it)('adds', () => { const A = [ [1, 2], [3, 4], ]; const B = [ [-1, 5], [6, 7], ]; (0, vitest_1.expect)((0, basis_1.matadd)(A, B)).toEqual([ [0, 7], [9, 11], ]); (0, vitest_1.expect)((0, basis_1.fractionalMatadd)(A, B).map(row => row.map(f => f.valueOf()))).toEqual([ [0, 7], [9, 11], ]); }); (0, vitest_1.it)('subtracts', () => { const A = [ [1, 2], [3, 4], ]; const B = [ [-1, 5], [6, 7], ]; (0, vitest_1.expect)((0, basis_1.matsub)(A, B)).toEqual([ [2, -3], [-3, -3], ]); (0, vitest_1.expect)((0, basis_1.fractionalMatsub)(A, B).map(row => row.map(f => f.valueOf()))).toEqual([ [2, -3], [-3, -3], ]); }); }); (0, vitest_1.describe)("Sin-tel's example", () => { (0, vitest_1.it)('agrees with temper/example.py', () => { // 31 & 19 const tMap = [ [19, 30, 44, 53], [31, 49, 72, 87], ]; // Canonical form const c = [ [1, 0, -4, -13], [0, 1, 4, 10], ]; const can = (0, basis_1.canonical)(tMap); (0, vitest_1.expect)(can).toEqual(c); let commas = (0, hnf_1.transpose)((0, hnf_1.kernel)(can)); (0, vitest_1.expect)(commas.map(comma => (0, monzo_1.monzoToFraction)(comma).toFraction())).toEqual([ '126/125', '1275989841/1220703125', ]); commas = (0, basis_1.lenstraLenstraLovasz)(commas).basis; (0, vitest_1.expect)(commas.map(comma => (0, monzo_1.monzoToFraction)(comma).toFraction())).toEqual([ '126/125', '81/80', ]); const gens = (0, hnf_1.transpose)((0, hnf_1.preimage)(can)); (0, vitest_1.expect)(gens.map(gen => (0, monzo_1.monzoToFraction)(gen).toFraction())).toEqual([ '20253807/9765625', '3', ]); const simpleGens = gens.map(gen => (0, basis_1.respell)(gen, commas)); (0, vitest_1.expect)(simpleGens.map(gen => (0, monzo_1.monzoToFraction)(gen).toFraction())).toEqual([ '2', '3', ]); }); (0, vitest_1.it)('agrees with temper/example_commas.py', () => { const commas = ['81/80', '225/224'].map(monzo_1.toMonzo); (0, vitest_1.expect)(commas).toEqual([ [-4, 4, -1], [-5, 2, 2, -1], ]); const tMap = (0, hnf_1.cokernel)((0, hnf_1.transpose)(commas)); (0, vitest_1.expect)(tMap).toEqual([ [1, 0, -4, -13], [0, 1, 4, 10], ]); }); }); (0, vitest_1.describe)('Diophantine equation solver', () => { vitest_1.it.skip('solves a diophantine equation', () => { const A = [ [1, 2, 3], [-4, 5, 6], [7, -8, 9], ]; const b = [4, 28, -28]; const solution = (0, basis_1.solveDiophantine)(A, b); (0, vitest_1.expect)(solution).toEqual([-3, 2, 1]); }); (0, vitest_1.it)('converts standard basis commas to subgroup basis monzos (pinkan)', () => { const subgroup = '2.3.13/5.19/5'.split('.').map(monzo_1.toMonzo); const commas = ['676/675', '1216/1215'].map(monzo_1.toMonzo); const subgroupMonzos = (0, basis_1.solveDiophantine)((0, hnf_1.transpose)(subgroup), (0, hnf_1.transpose)(commas)); (0, vitest_1.expect)((0, hnf_1.transpose)(subgroupMonzos)).toEqual([ [2, -3, 2, 0], [6, -5, 0, 1], ]); }); }); //# sourceMappingURL=basis.spec.js.map