xen-dev-utils
Version:
Utility functions used by the Scale Workshop ecosystem
770 lines • 30 kB
JavaScript
"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