UNPKG

speedy-vision

Version:

GPU-accelerated Computer Vision for JavaScript

1,307 lines (1,048 loc) 46.7 kB
/* * speedy-vision.js * GPU-accelerated Computer Vision for JavaScript * Copyright 2020-2022 Alexandre Martins <alemartf(at)gmail.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * matrix.js * Unit testing */ describe('Matrix', function() { beforeEach(function() { jasmine.addMatchers(speedyMatchers); }); function RandomMatrix(rows, columns = rows, type = 'float32') { const data = new Array(rows * columns).fill(0).map(_ => randomInt(100) + 0.25 * randomInt(5)); return Speedy.Matrix(rows, columns, data, type); } describe('Read/write operations', function() { it('creates matrices and reads their data', async function() { const types = [ 'float32' ]; for(const type of types) { const data = [1, 8, 91, 81, 7, 4, 77, 10, 0]; const matrix = Speedy.Matrix(3, 3, data, type); printm(matrix); const readData = matrix.read(); expect(readData).toBeElementwiseEqual(data); } }); it('creates matrices with random entries and reads their data', async function() { const cnt = 10; for(let i = 0; i < cnt; i++) { const data = new Array(25).fill(0).map(_ => (1000 * Math.random()) | 0); const matrix = Speedy.Matrix(5, 5, data); //printm(matrix); const readData = matrix.read(); expect(readData).toBeElementwiseNearlyEqual(data); } }); it('creates a matrix filled with zeros', async function() { const n = 10; const data = new Array(n * n).fill(0); const matrix = Speedy.Matrix.Zeros(n); printm(matrix); const readData = matrix.read(); expect(readData.length).toEqual(data.length); expect(readData).toBeElementwiseEqual(data); }); it('creates a matrix filled with ones', async function() { const n = 10; const data = new Array(n * n).fill(1); const matrix = Speedy.Matrix.Ones(n); printm(matrix); const readData = matrix.read(); expect(readData.length).toEqual(data.length); expect(readData).toBeElementwiseEqual(data); }); it('creates an identity matrix', async function() { const data = [1, 0, 0, 0, 1, 0, 0, 0, 1]; const matrix = Speedy.Matrix.Eye(3); printm(matrix); const readData = matrix.read(); expect(readData.length).toEqual(data.length); expect(readData).toBeElementwiseEqual(data); }); it('can\'t create an empty matrix', async function() { expect(() => Speedy.Matrix(0, 0)).toThrow(); expect(() => Speedy.Matrix.Zeros(0, 0)).toThrow(); expect(() => Speedy.Matrix.Zeros(-1, -1)).toThrow(); }); it('fills a matrix', async function() { const x = 10; const data = new Array(9).fill(x); const matrix = Speedy.Matrix.Zeros(3); await matrix.fill(x); printm(matrix); expect(data).toBeElementwiseEqual(matrix.read()); }); it('synchronously fills a matrix', function() { const x = 10; const data = new Array(9).fill(x); const matrix = Speedy.Matrix.Zeros(3); matrix.fillSync(x); printm(matrix); expect(data).toBeElementwiseEqual(matrix.read()); }); it('fills a row', async function() { const n = 5; const ones = new Array(n).fill(1); for(let i = 0; i < n; i++) { const matrix = Speedy.Matrix.Zeros(n); await matrix.row(i).fill(1); printm(matrix); const readOnes = matrix.row(i).read(); expect(readOnes).toBeElementwiseEqual(ones); const readData = matrix.read(); const sumOfEntries = readData.reduce((sum, aij) => sum += aij, 0); expect(sumOfEntries).toEqual(n); } }); it('fills a column', async function() { const n = 5; const ones = new Array(n).fill(1); for(let i = 0; i < n; i++) { const matrix = Speedy.Matrix.Zeros(n); await matrix.column(i).fill(1); printm(matrix); const readOnes = matrix.column(i).read(); expect(readOnes).toBeElementwiseEqual(ones); const readData = matrix.read(); const sumOfEntries = readData.reduce((sum, aij) => sum += aij, 0); expect(sumOfEntries).toEqual(n); } }); it('fills a diagonal', async function() { const n = 5; for(let i = 1; i <= n; i++) { const eye = Speedy.Matrix.Eye(i); const matrix = Speedy.Matrix.Zeros(i); await matrix.diagonal().fill(1); printm(matrix); const readOnes = matrix.diagonal().read(); expect(readOnes).toBeElementwiseEqual(Array(i).fill(1)); const readData = matrix.read(); const sumOfEntries = readData.reduce((sum, aij) => sum += aij, 0); expect(sumOfEntries).toEqual(i); const readEye = eye.read(); expect(readEye).toBeElementwiseEqual(readData); } }); it('reads a diagonal', async function() { const n = 5; for(let i = 1; i <= n; i++) { const eye = Speedy.Matrix.Eye(i); const matrix = Speedy.Matrix.Zeros(eye.rows, eye.columns); await matrix.setTo(eye.times(i * i)); printm(matrix); const diag = matrix.diagonal(); expect(diag.columns).toEqual(1); expect(diag.rows).toEqual(i); printm(diag); const diagData = diag.read(); expect(diagData).toBeElementwiseEqual(Array(i).fill(i * i)); } }); it('handles assignment expressions', async function() { let A = Speedy.Matrix.Zeros(3, 3); let B = Speedy.Matrix.Zeros(3, 3); let I = Speedy.Matrix.Eye(3); await A.setTo(await B.setTo(I)); printm('A:', A, 'B:', B, 'I:', I); expect(A.read()).toBeElementwiseEqual(I.read()); expect(B.read()).toBeElementwiseEqual(I.read()); }); it('handles synchronous assignment expressions', function() { let A = Speedy.Matrix.Zeros(3, 3); let B = Speedy.Matrix.Zeros(3, 3); let I = Speedy.Matrix.Eye(3); A.setToSync(B.setToSync(I)); printm('A:', A, 'B:', B, 'I:', I); expect(A.read()).toBeElementwiseEqual(I.read()); expect(B.read()).toBeElementwiseEqual(I.read()); }); it('creates a new matrix from an expression', function() { let alpha = 0.5, beta = 2; let A = Speedy.Matrix.Ones(3); let B = Speedy.Matrix.Eye(3); let C = Speedy.Matrix( (A.times(alpha)).plus(B.times(beta)) ); let a = A.read(), b = B.read(); let result = Array.from({ length: 9 }, (_, i) => a[i] * alpha + b[i] * beta); printm('A:', A, 'B:', B, 'alpha:', alpha, 'beta:', beta, 'alpha A + beta B:', C); expect(C.read()).toBeElementwiseEqual(result); }); }); describe('Matrix algebra', function() { const n = 5; it('adds two matrices', async function() { let A, B, C, a, b, c; A = RandomMatrix(n); B = RandomMatrix(n); C = Speedy.Matrix.Zeros(n); await C.setTo(A.plus(B)); printm(A); print('+'); printm(B); print('='); printm(C); a = A.read(); b = B.read(); c = C.read(); expect(c).toBeElementwiseNearlyEqual(add(a, b)); await C.setTo(B.plus(A)); c = C.read(); expect(c).toBeElementwiseNearlyEqual(add(a, b)); }); it('subtracts two matrices', async function() { let A, B, C, a, b, c; A = RandomMatrix(n); B = RandomMatrix(n); C = Speedy.Matrix.Zeros(n); await C.setTo(A.minus(B)); printm(A); print('-'); printm(B); print('='); printm(C); a = A.read(); b = B.read(); c = C.read(); expect(c).toBeElementwiseNearlyEqual(subtract(a, b)); }); it('can\'t add nor subtract matrices of incompatible sizes', async function() { let A = RandomMatrix(n); let B = RandomMatrix(n+1); let C = Speedy.Matrix.Zeros(n+1); expect(() => C.setTo(A.plus(B))).toThrow(); expect(() => C.setTo(B.plus(A))).toThrow(); expect(() => C.setTo(A.minus(B))).toThrow(); expect(() => C.setTo(B.minus(A))).toThrow(); }); it('multiplies two matrices', async function() { let A = Speedy.Matrix(2, 3, [ 1, 4, 2, 5, 3, 6, ]); let B = Speedy.Matrix(3, 2, [ 7, 9, 11, 8, 10, 12, ]); let C = Speedy.Matrix(2, 2, [ 58, 139, 64, 154 ]); let AB = Speedy.Matrix.Zeros(2); await AB.setTo(A.times(B)); printm(A); print("*"); printm(B); print("="); printm(AB); const expected = C.read(); const actual = AB.read(); expect(actual).toBeElementwiseEqual(expected); }); it('multiplies a matrix by its transpose', async function() { let A = Speedy.Matrix(2, 3, [ 1, 4, 2, 5, 3, 6 ]); let AAt = Speedy.Matrix(2, 2, [ 14, 32, 32, 77, ]); let AtA = Speedy.Matrix(3, 3, [ 17, 22, 27, 22, 29, 36, 27, 36, 45 ]); let At_ = Speedy.Matrix.Zeros(3, 2); let AAt_ = Speedy.Matrix.Zeros(2, 2); let AtA_ = Speedy.Matrix.Zeros(3, 3); await At_.setTo(A.transpose()); await AAt_.setTo(A.times(A.transpose())); await AtA_.setTo(A.transpose().times(A)); printm( 'A:', A, 'A^T:', At_, 'A A^T:', AAt_, 'A^T A:', AtA_, '-----' ); let aat = AAt.read(); let ata = AtA.read(); let aat_ = AAt_.read(); let ata_ = AtA_.read(); expect(aat_).toBeElementwiseEqual(aat); expect(ata_).toBeElementwiseEqual(ata); }); it('can\'t multiply matrices of incompatible shapes', async function() { let A = Speedy.Matrix.Zeros(3, 2); let B = Speedy.Matrix.Ones(5); expect(() => A.times(A)).toThrow(); expect(() => A.transpose().times(A.transpose())).toThrow(); expect(() => A.times(B)).toThrow(); expect(() => B.times(A)).toThrow(); }); it('multiplies by a scalar', async function() { let A = RandomMatrix(n); let B = Speedy.Matrix.Zeros(n); const scalars = [0, 1, 10]; for(const scalar of scalars) { await B.setTo(A.times(scalar)); let a = A.read(); let b = B.read(); let c = multiply(a, scalar); printm( 'A:', A, 'alpha: ' + scalar, '(alpha) A:', B, '-----' ); expect(b).toBeElementwiseEqual(c); } }); it('multiplies by the identity matrix', async function() { let A = RandomMatrix(n); let I = Speedy.Matrix.Eye(n); let IA = Speedy.Matrix.Zeros(n); let AI = Speedy.Matrix.Zeros(n); await IA.setTo(I.times(A)); await AI.setTo(A.times(I)); printm( 'A:', A, 'I:', I, 'I A:', IA, 'A I:', AI, ); let a = A.read(); let ai = AI.read(); let ia = IA.read(); expect(ai).toBeElementwiseEqual(a); expect(ia).toBeElementwiseEqual(a); }); it('multiplies a matrix by a column-vector', async function() { let A = Speedy.Matrix(4, 3, [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ]); let x = Speedy.Matrix(3, 1, [ 10, 20, 30, ]); let soln = [ 380, 440, 500, 560 ]; let Ax = Speedy.Matrix.Zeros(4, 1); await Ax.setTo(A.times(x)); printm( 'A:', A, 'x:', x, 'A x:', Ax, ); let ax = Ax.read(); expect(ax).toBeElementwiseEqual(soln); }); it('does component-wise multiplication', async function() { let A = Speedy.Matrix(3, 3, [ 1, 2, 3, 4, 5, 6, 7, 8, 9, ]); let B = Speedy.Matrix(3, 3, [ -1, -2, -3, -4, -5, -6, -7, -8, -9, ]); let C = Speedy.Matrix(3, 3, [ -1, -4, -9, -16, -25, -36, -49, -64, -81, ]); let AB = Speedy.Matrix.Zeros(3, 3); let BA = Speedy.Matrix.Zeros(3, 3); await AB.setTo(A.compMult(B)); await BA.setTo(B.compMult(A)); printm( 'A:', A, 'B:', B, 'A <comp-mult> B:', AB, 'B <comp-mult> A:', BA, ); let a = A.read(); let b = B.read(); let c = C.read(); let ab = AB.read(); let ba = BA.read(); let _ab = a.map((ai, i) => ai * b[i]); expect(ab).toBeElementwiseEqual(c); expect(ba).toBeElementwiseEqual(c); expect(ab).toBeElementwiseEqual(_ab); }); it('multiplies large matrices', async function() { let A = Speedy.Matrix.Zeros(100, 10); let u = Speedy.Matrix.Zeros(100, 1); let v = Speedy.Matrix.Ones(10, 1); let w = Speedy.Matrix.Ones(100, 1); for(let i = 0; i < A.rows; i++) { await Speedy.Promise.all([ A.row(i).fill(i), w.row(i).fill(i * A.columns) ]); } await u.setTo(A.times(v)); // u = A v = A [ 1 1 ... 1 ]^T printm( 'A:', A, 'v:', v, 'A v:', u ); const _u = u.read(); const _w = w.read(); const result = [ ...(new Array(A.rows).keys()) ].map(i => A.columns * i); expect(_u).toBeElementwiseEqual(_w); expect(_u).toBeElementwiseEqual(result); }); it('transposes a matrix', async function() { let A = Speedy.Matrix(2, 3, [ 1, 4, 2, 5, 3, 6, ]); let At = Speedy.Matrix(3, 2, [ 1, 2, 3, 4, 5, 6, ]); let At_ = Speedy.Matrix.Zeros(A.columns, A.rows); let Att_ = Speedy.Matrix.Zeros(A.rows, A.columns); let Att__ = Speedy.Matrix.Zeros(A.rows, A.columns); await At_.setTo(A.transpose()); await Att_.setTo(At_.transpose()); await Att__.setTo(A.transpose().transpose()); printm(A); printm(At_); let at = At.read(); let at_ = At_.read(); let att = Att__.read(); let att_ = Att_.read(); expect(at).toBeElementwiseEqual(at_); expect(att).toBeElementwiseEqual(att_); }); it('evaluates expressions with multiple operations', async function() { let I = Speedy.Matrix.Eye(3); let Z = Speedy.Matrix.Zeros(3); let T = Speedy.Matrix(3, 3, [ 1, 0, 0, 0, 1, 0, 10, 5, 1, ]); let R = Speedy.Matrix(3, 3, [ 0, -1, 0, 1, 0, 0, 0, 0, 1, ]); let S = Speedy.Matrix(3, 3, [ 2, 0, 0, 0, 2, 0, 0, 0, 1, ]); let x = Speedy.Matrix(3, 1, [ 1, 0, 1 ]); let t = Speedy.Matrix(3, 1, [ 10, 5, 0 ]); let xPlusT = Speedy.Matrix(3, 1, [ 1+10, 0+5, 1 ]); let RtimesXplusT = Speedy.Matrix(3, 1, [ 0+5, -(1+10), 1 ]); let StimesRtimesXplusT = Speedy.Matrix(3, 1, [ 2*5, 2*(-(1+10)), 1 ]); printm('S:', S, 'R:', R, 'T:', T, 'x:', x, 't:', t); let y1 = S.times(R.times(T.times(x))); let y2 = S.times(R).times(T.times(x)); let y3 = S.times(R).times(T).times(x); let y4 = S.times(R).times(xPlusT); let y5 = S.times(RtimesXplusT); let y6 = ((Z.plus(Z.times(I))).plus(I)).times(StimesRtimesXplusT); let y7 = StimesRtimesXplusT; let y8 = R.times(T.times(x)); let y9 = (R.times(T)).times(x); let y10 = R.times(x.plus(t)); let y11 = RtimesXplusT; let z11 = await Speedy.Matrix.Zeros(3, 1).setTo(y11); let z10 = await Speedy.Matrix.Zeros(3, 1).setTo(y10); let z9 = await Speedy.Matrix.Zeros(3, 1).setTo(y9); let z8 = await Speedy.Matrix.Zeros(3, 1).setTo(y8); let z7 = await Speedy.Matrix.Zeros(3, 1).setTo(y7); let z6 = await Speedy.Matrix.Zeros(3, 1).setTo(y6); let z5 = await Speedy.Matrix.Zeros(3, 1).setTo(y5); let z4 = await Speedy.Matrix.Zeros(3, 1).setTo(y4); let z3 = await Speedy.Matrix.Zeros(3, 1).setTo(y3); let z2 = await Speedy.Matrix.Zeros(3, 1).setTo(y2); let z1 = await Speedy.Matrix.Zeros(3, 1).setTo(y1); printm('----------------'); printm('R T x:', z11); printm('R (T x):', z8); printm('(R T) x:', z9); printm('R (x+t):', z10); printm('----------------'); printm('S R T x:', z7); printm('S ( R ( Tx ) ):', z1); printm('(S R) (T x):', z2); printm('( (S R) T ) x:', z3); printm('(S R) (x + t):', z4); printm('S ( R(x+t) ):', z5); printm('SR(x+t):', z6); expect(z1.read()).toBeElementwiseEqual(z7.read()); expect(z2.read()).toBeElementwiseEqual(z7.read()); expect(z3.read()).toBeElementwiseEqual(z7.read()); expect(z4.read()).toBeElementwiseEqual(z7.read()); expect(z5.read()).toBeElementwiseEqual(z7.read()); expect(z6.read()).toBeElementwiseEqual(z7.read()); expect(z8.read()).toBeElementwiseEqual(z11.read()); expect(z9.read()).toBeElementwiseEqual(z11.read()); expect(z10.read()).toBeElementwiseEqual(z11.read()); }); describe('Inverse matrix', function() { it('computes the inverse of 1x1 matrices', async function() { const eye = Speedy.Matrix.Eye(1); const id = eye.read(); const mats = [ Speedy.Matrix(1, 1, [-5]), Speedy.Matrix(1, 1, [4]), Speedy.Matrix(1, 1, [2]), Speedy.Matrix(1, 1, [1]), ]; for(let mat of mats) { const inv = Speedy.Matrix.Zeros(eye.rows); await inv.setTo(mat.inverse()); const mm1 = Speedy.Matrix.Zeros(eye.rows); const m1m = Speedy.Matrix.Zeros(eye.rows); await mm1.setTo(mat.times(inv)); await m1m.setTo(inv.times(mat)); printm( 'M:', mat, 'M^(-1):', inv, 'M * M^(-1):', mm1, 'M^(-1) * M:', m1m, '--------------------' ); expect(mm1.read()).toBeElementwiseNearlyEqual(id); expect(m1m.read()).toBeElementwiseNearlyEqual(id); } }); it('computes the inverse of 2x2 matrices', async function() { const eye = Speedy.Matrix.Eye(2); const id = eye.read(); const mats = [ Speedy.Matrix(2, 2, [5, -7, 2, -3]), Speedy.Matrix(2, 2, [-3, 5, 1, -2]), Speedy.Matrix(2, 2, [1, 3, 2, 4]), Speedy.Matrix(2, 2, [2, 1, 1, 1]), ]; for(let mat of mats) { const inv = Speedy.Matrix.Zeros(eye.rows); await inv.setTo(mat.inverse()); const mm1 = Speedy.Matrix.Zeros(eye.rows); const m1m = Speedy.Matrix.Zeros(eye.rows); await mm1.setTo(mat.times(inv)); await m1m.setTo(inv.times(mat)); printm( 'M:', mat, 'M^(-1):', inv, 'M * M^(-1):', mm1, 'M^(-1) * M:', m1m, '--------------------' ); expect(mm1.read()).toBeElementwiseNearlyEqual(id); expect(m1m.read()).toBeElementwiseNearlyEqual(id); } }); it('computes the inverse of 3x3 matrices', async function() { const eye = Speedy.Matrix.Eye(3); const id = eye.read(); const mats = [ Speedy.Matrix(3, 3, [1, 0, 5, 2, 1, 6, 3, 4, 0]), Speedy.Matrix(3, 3, [2, 3, 3, 3, 4, 7, 1, 1, 2]), Speedy.Matrix(3, 3, [3, 2, 0, 0, 0, 1, 2,-2, 1]), Speedy.Matrix(3, 3, [4, 8, 7,-2,-3,-2, 3, 5, 4]), ]; for(let mat of mats) { const inv = Speedy.Matrix.Zeros(eye.rows); await inv.setTo(mat.inverse()); const mm1 = Speedy.Matrix.Zeros(eye.rows); const m1m = Speedy.Matrix.Zeros(eye.rows); await mm1.setTo(mat.times(inv)); await m1m.setTo(inv.times(mat)); printm( 'M:', mat, 'M^(-1):', inv, 'M * M^(-1):', mm1, 'M^(-1) * M:', m1m, '--------------------' ); expect(mm1.read()).toBeElementwiseNearlyEqual(id); expect(m1m.read()).toBeElementwiseNearlyEqual(id); } }); it('computes the inverse of 4x4 matrices', async function() { const eye = Speedy.Matrix.Eye(4); const id = eye.read(); const mats = [ Speedy.Matrix(4, 4, [1, 1, 1,-1, 1, 1,-1, 1, 1,-1, 1, 1,-1, 1, 1, 1]), Speedy.Matrix(4, 4, [0, 0, 0, 4, 0, 0, 3, 0, 0, 2, 0, 0, 1, 0, 0, 0]), Speedy.Matrix(4, 4, [2, 1, 7, 1, 5, 4, 8, 5, 0, 2, 9, 7, 8, 6, 3, 8]), Speedy.Matrix(4, 4, [3, 1, 6,13, 5, 4, 3, 5, 7, 7, 9, 4, 2, 2,17,16]), ]; for(let mat of mats) { const inv = Speedy.Matrix.Zeros(eye.rows); await inv.setTo(mat.inverse()); const mm1 = Speedy.Matrix.Zeros(eye.rows); const m1m = Speedy.Matrix.Zeros(eye.rows); await mm1.setTo(mat.times(inv)); await m1m.setTo(inv.times(mat)); printm( 'M:', mat, 'M^(-1):', inv, 'M * M^(-1):', mm1, 'M^(-1) * M:', m1m, '--------------------' ); expect(mm1.read()).toBeElementwiseNearlyEqual(id); expect(m1m.read()).toBeElementwiseNearlyEqual(id); } }); it('computes the inverse of 5x5 matrices', async function() { const eye = Speedy.Matrix.Eye(5); const id = eye.read(); const mats = [ Speedy.Matrix(5, 5, [1,0,0,0,0,0,1,0,0,0,0,0,-1,0,0,0,0,-1,1,0,0,0,0,0,1]), Speedy.Matrix(5, 5, [2,-1,0,0,0,-1,2,-1,0,0,0,-1,2,-1,0,0,0,-1,2,-1,0,0,0,-1,2]), Speedy.Matrix(5, 5, [1,0,1,0,0,0,0,-7,4,2,0,1,0,2,0,0,0,4,-7,1,0,0,2,1,-7]), ]; for(let mat of mats) { const inv = Speedy.Matrix.Zeros(eye.rows); await inv.setTo(mat.inverse()); const mm1 = Speedy.Matrix.Zeros(eye.rows); const m1m = Speedy.Matrix.Zeros(eye.rows); await mm1.setTo(mat.times(inv)); await m1m.setTo(inv.times(mat)); printm( 'M:', mat, 'M^(-1):', inv, 'M * M^(-1):', mm1, 'M^(-1) * M:', m1m, '--------------------' ); expect(mm1.read()).toBeElementwiseNearlyEqual(id); expect(m1m.read()).toBeElementwiseNearlyEqual(id); } }); it('fails to compute the inverse of singular matrices', async function() { const eye = Speedy.Matrix.Eye(3); const mats = [ Speedy.Matrix(1, 1, [0]), Speedy.Matrix(2, 2, [8, 4, 2, 1]), Speedy.Matrix(3, 3, [1, 1, 2, 2, 2, 4, 0, 1, 0]), ]; for(let mat of mats) { const inv = Speedy.Matrix.Zeros(mat.rows); await inv.setTo(mat.inverse()); printm('M:', mat, 'M^(-1):', inv, '--------------------'); const data = inv.read(); const finite = data.map(Number.isFinite).reduce((a, b) => a || b, false); expect(finite).toEqual(false); } }); }); }); describe('Linear algebra', function() { const eps = 1e-4; describe('QR decomposition', function() { it('computes a QR decomposition of a square matrix', async function() { let A = Speedy.Matrix(3, 3, [ 0, 1, 0, 1, 1, 0, 1, 2, 3, ]); let Q = Speedy.Matrix.Zeros(3, 3); let R = Speedy.Matrix.Zeros(3, 3); let QR = Speedy.Matrix.Zeros(3, 3); let QQt = Speedy.Matrix.Zeros(Q.rows, Q.rows); let I = Speedy.Matrix.Eye(Q.rows, Q.columns); await Speedy.Matrix.qr(Q, R, A); await QR.setTo(Q.times(R)); await QQt.setTo(Q.times(Q.transpose())); printm('A = ', A); printm('Q = ', Q); printm('R = ', R); printm('QR = ', QR); let a = A.read(); let q = Q.read(); let qr = QR.read(); // check if A = QR expect(qr).toBeElementwiseNearlyEqual(a, eps); // check if Q^(-1) = Q^T let qqt = QQt.read(); let i_ = I.read(); expect(qqt).toBeElementwiseNearlyEqual(i_, eps); // check if P = Q (Q^T) is a projector (i.e., P = P^2) let P = Speedy.Matrix.Zeros(QQt.rows, QQt.columns); await P.setTo(QQt); let P2 = Speedy.Matrix.Zeros(P.rows, P.columns); await P2.setTo(P.times(P)); let p = P.read(), p2 = P2.read(); expect(p2).toBeElementwiseNearlyEqual(p, eps); // check if P = Q (Q^T) is an orthogonal projector (i.e., P = P^T) let Pt = Speedy.Matrix.Zeros(P.columns, P.rows); await Pt.setTo(P.transpose()); let pt = Pt.read(); expect(pt).toBeElementwiseNearlyEqual(p, eps); // check if R is upper triangular for(let jj = 0; jj < R.columns; jj++) { let rj = R.block(jj, R.rows-1, jj, jj).read(); let rjj = R.block(jj, jj, jj, jj).read(); expect(norm(rj)).toBeCloseTo(Math.abs(rjj[0])); } let D = Speedy.Matrix.Zeros(Math.min(R.rows, R.columns), 1); await D.setTo(R.diagonal()); let dr = D.read(); expect(norm(dr)).not.toBeCloseTo(0); }); it('computes a full QR decomposition of a non-square matrix', async function() { let A = RandomMatrix(4, 3); // m x n let Q = Speedy.Matrix.Zeros(4, 4); // m x m let R = Speedy.Matrix.Zeros(4, 3); // m x n let QR = Speedy.Matrix.Zeros(4, 3); let QQt = Speedy.Matrix.Zeros(4, 4); // I let I = Speedy.Matrix.Eye(Q.rows, Q.columns); await Speedy.Matrix.qr(Q, R, A, { mode: 'full' }); await QR.setTo(Q.times(R)); await QQt.setTo(Q.times(Q.transpose())); printm('A = ', A); printm('Q = ', Q); printm('R = ', R); printm('QR = ', QR); let a = A.read(); let qr = QR.read(); // check if A = QR expect(qr).toBeElementwiseNearlyEqual(a, eps); // check if Q^(-1) = Q^T let qqt = QQt.read(); let i_ = I.read(); expect(qqt).toBeElementwiseNearlyEqual(i_, eps); // check if P = Q (Q^T) is a projector (i.e., P = P^2) let P = Speedy.Matrix.Zeros(QQt.rows, QQt.columns); await P.setTo(QQt); let P2 = Speedy.Matrix.Zeros(P.rows, P.columns); await P2.setTo(P.times(P)); let p = P.read(), p2 = P2.read(); expect(p2).toBeElementwiseNearlyEqual(p, eps); // check if P = Q (Q^T) is an orthogonal projector (i.e., P = P^T) let Pt = Speedy.Matrix.Zeros(P.columns, P.rows); await Pt.setTo(P.transpose()); let pt = Pt.read(); expect(pt).toBeElementwiseNearlyEqual(p, eps); // check if R is upper triangular for(let jj = 0; jj < R.columns; jj++) { let rj = R.block(jj, R.rows-1, jj, jj).read(); let rjj = R.block(jj, jj, jj, jj).read(); expect(norm(rj)).toBeCloseTo(Math.abs(rjj[0])); } let D = Speedy.Matrix.Zeros(Math.min(R.rows, R.columns), 1); await D.setTo(R.diagonal()); let dr = D.read(); expect(norm(dr)).not.toBeCloseTo(0); }); it('computes a reduced QR decomposition of a non-square matrix', async function() { let A = RandomMatrix(4, 3); // m x n let Q = Speedy.Matrix.Zeros(4, 3); // m x n let R = Speedy.Matrix.Zeros(3, 3); // n x n let QR = Speedy.Matrix.Zeros(4, 3); let QQt = Speedy.Matrix.Zeros(4, 4); let I = Speedy.Matrix.Eye(Q.rows, Q.columns); await Speedy.Matrix.qr(Q, R, A, { mode: 'reduced' }); await QR.setTo(Q.times(R)); await QQt.setTo(Q.times(Q.transpose())); printm('A = ', A); printm('Q = ', Q); printm('R = ', R); printm('QR = ', QR); let a = A.read(); let qr = QR.read(); // check if A = QR expect(qr).toBeElementwiseNearlyEqual(a, eps); // check if P = Q (Q^T) is a projector (i.e., P = P^2) let P = Speedy.Matrix.Zeros(QQt.rows, QQt.columns); await P.setTo(QQt); let P2 = Speedy.Matrix.Zeros(P.rows, P.columns); await P2.setTo(P.times(P)); let p = P.read(), p2 = P2.read(); expect(p2).toBeElementwiseNearlyEqual(p, eps); // check if P = Q (Q^T) is an orthogonal projector (i.e., P = P^T) let Pt = Speedy.Matrix.Zeros(P.columns, P.rows); await Pt.setTo(P.transpose()); let pt = Pt.read(); expect(pt).toBeElementwiseNearlyEqual(p, eps); // check if R is upper triangular for(let jj = 0; jj < R.columns; jj++) { let rj = R.block(jj, R.rows-1, jj, jj).read(); let rjj = R.block(jj, jj, jj, jj).read(); expect(norm(rj)).toBeCloseTo(Math.abs(rjj[0])); } let D = Speedy.Matrix.Zeros(Math.min(R.rows, R.columns), 1); await D.setTo(R.diagonal()); let dr = D.read(); expect(norm(dr)).not.toBeCloseTo(0); }); }); describe('Linear systems of equations', function() { const methods = [ 'qr' ]; for(const method of methods) { describe('Solve with: "' + method + '"', function() { it('solves a system of 2 equations and 2 unknowns', async function() { let A = Speedy.Matrix(2, 2, [ 1, 1, -1, 1, ]); let b = Speedy.Matrix(2, 1, [ 9, 6 ]); let x = Speedy.Matrix.Zeros(2, 1); let soln = [ 7.5, -1.5 ]; await Speedy.Matrix.solve(x, A, b, { method }); printm('A = ', A); printm('b = ', b); printm('Solution for Ax = b:', x); expect(x.read()).toBeElementwiseNearlyEqual(soln); }); it('solves a system of 3 equations and 3 unknowns', async function() { let A = Speedy.Matrix(3, 3, [ -3, -3, 0, 1, 0, 1, -1, 1, -5, ]); let b = Speedy.Matrix(3, 1, [ -2, 4, 0 ]); let x = Speedy.Matrix.Zeros(3, 1); let soln = [ -2, -10, -2 ]; await Speedy.Matrix.solve(x, A, b, { method }); printm('A = ', A); printm('b = ', b); printm('Solution for Ax = b:', x); expect(x.read()).toBeElementwiseNearlyEqual(soln); }); it('solves another system of 3 equations and 3 unknowns', async function() { let A = Speedy.Matrix(3, 3, [ 1, 2, 1, 2, 1, 2, -1, 1, 1, ]); let b = Speedy.Matrix(3, 1, [ 4, -2, 2, ]); let x = Speedy.Matrix.Zeros(3, 1); let soln = [ -5/3, 7/3, -1 ]; await Speedy.Matrix.solve(x, A, b, { method }); printm('A = ', A); printm('b = ', b); printm('Solution for Ax = b:', x); expect(x.read()).toBeElementwiseNearlyEqual(soln); }); it('can\'t solve an impossible system of equations', async function() { let A = Speedy.Matrix(2, 2, [ 1, 1, 1, 1 ]); let b = Speedy.Matrix(2, 1, [ 0, 1 ]); let x = Speedy.Matrix.Zeros(2, 1); await Speedy.Matrix.solve(x, A, b, { method }); let soln = x.read(); printm('A = ', A); printm('b = ', b); printm('Solution for Ax = b:', x); for(const xi of soln) expect(Number.isNaN(xi)).toBeTruthy(); }); it('can\'t solve an underdetermined system of equations', async function() { let A = Speedy.Matrix(2, 2, [ 1, 1, 1, 1 ]); let b = Speedy.Matrix(2, 1, [ 0, 0 ]); let x = Speedy.Matrix.Zeros(2, 1); await Speedy.Matrix.solve(x, A, b, { method }); let soln = x.read(); printm('A = ', A); printm('b = ', b); printm('Solution for Ax = b:', x); for(const xi of soln) expect(Number.isNaN(xi)).toBeTruthy(); }); it('can\'t handle a non-square matrix', async function() { let A = Speedy.Matrix(4, 3, [ 1, 1, 0, 0, 0, 0, 1, -1, -1, -3, 1, 1, ]); let b = Speedy.Matrix(4, 1, [ 4, 6, -1, 2 ]); expect(() => Speedy.Matrix.solve(x, A, b, { method })).toThrow(); }); it('solves a upper-triangular system', async function() { let R = Speedy.Matrix(4, 4, [ 1, 0, 0, 0, 2, -4, 0, 0, 1, 1, -2, 0, -1, 7, 1, -1, ]); let b = Speedy.Matrix(4, 1, [ 5, 1, 1, 3, ]) let x = Speedy.Matrix.Zeros(4, 1); let soln = [16, -6, -2, -3]; await Speedy.Matrix.solve(x, R, b, { method }); printm('R = ', R, 'b = ', b, 'Solution of Rx = b:', x); let actual = x.read(); expect(actual).toBeElementwiseEqual(soln); }); it('solves another upper-triangular system', async function() { let R = Speedy.Matrix(3, 3, [ -1, 0, 0, 2, 3, 0, -1, 6, -5, ]); let b = Speedy.Matrix(3, 1, [ 0, 24, -15, ]) let x = Speedy.Matrix.Zeros(3, 1); let soln = [ 1, 2, 3 ]; await Speedy.Matrix.solve(x, R, b, { method }); printm('R = ', R, 'b = ', b, 'Solution of Rx = b:', x); let actual = x.read(); expect(actual).toBeElementwiseEqual(soln); }); it('solves a diagonal system', async function() { let D = Speedy.Matrix(3, 3, [ -1, 0, 0, 0, 3, 0, 0, 0, -5, ]); let b = Speedy.Matrix(3, 1, [ -1, 6, -15, ]) let x = Speedy.Matrix.Zeros(3, 1); let soln = [ 1, 2, 3 ]; await Speedy.Matrix.solve(x, D, b, { method }); printm('D = ', D, 'b = ', b, 'Solution of Dx = b:', x); let actual = x.read(); expect(actual).toBeElementwiseEqual(soln); }); }); } describe('Least squares', function() { const method = 'qr'; it('finds the best fit solution for an overdetermined system of 3 equations and 2 unknowns', async function() { let A = Speedy.Matrix(3, 2, [ 1, 1, 2, -1, 1, 1, ]); let b = Speedy.Matrix(3, 1, [ 2, 4, 8 ]); let x = Speedy.Matrix.Zeros(2, 1); let soln = [ 23/7, 8/7 ]; await Speedy.Matrix.ols(x, A, b, { method }); printm('A = ', A); printm('b = ', b); printm('Best-fit solution for Ax = b:', x); const r = Speedy.Matrix.Zeros(b.rows, b.columns); await r.setTo(b.minus(A.times(x))); printm('Residual |b - Ax|:', norm(r.read())); expect(x.read()).toBeElementwiseNearlyEqual(soln); }); it('finds the best fit solution for an overdetermined system of 4 equations and 3 unknowns', async function() { let A = Speedy.Matrix(4, 3, [ 1, 1, 0, 0, 0, 0, 1, -1, -1, -3, 1, 1, ]); let b = Speedy.Matrix(4, 1, [ 4, 6, -1, 2 ]); let x = Speedy.Matrix.Zeros(3, 1); let soln = [ 4.5, -1.5, -0.25 ]; await Speedy.Matrix.ols(x, A, b, { method }); printm('A = ', A); printm('b = ', b); printm('Best-fit solution for Ax = b:', x); const r = Speedy.Matrix.Zeros(b.rows, b.columns); await r.setTo(b.minus(A.times(x))); printm('Residual |b - Ax|:', norm(r.read())); expect(x.read()).toBeElementwiseNearlyEqual(soln); }); it('can\'t find the best-fit solution for an underdetermined system of equations', async function() { let A = Speedy.Matrix(2, 2, [ 1, 1, 1, 1 ]); let b = Speedy.Matrix(2, 1, [ 0, 0 ]); let x = Speedy.Matrix.Zeros(2, 1); await Speedy.Matrix.ols(x, A, b, { method }); let soln = x.read(); printm('A = ', A); printm('b = ', b); printm('Best-fit solution for Ax = b:', x); for(const xi of soln) expect(Number.isNaN(xi)).toBeTruthy(); }); it('finds the exact solution of a system of 3 equations and 2 unknowns', async function() { let A = Speedy.Matrix(3, 2, [ 1, 1, 1, -1, -1, 1, ]); let b = Speedy.Matrix(3, 1, [ 9, 9, 6 ]); let x = Speedy.Matrix.Zeros(2, 1); let soln = [ 7.5, -1.5 ]; await Speedy.Matrix.ols(x, A, b, { method }); printm('A = ', A); printm('b = ', b); printm('Best-fit solution for Ax = b:', x); const r = Speedy.Matrix.Zeros(b.rows, b.columns); await r.setTo(b.minus(A.times(x))); printm('Residual |b - Ax|:', norm(r.read())); expect(x.read()).toBeElementwiseNearlyEqual(soln); expect(norm(r.read())).toBeCloseTo(0); }); xit('find via matrix expression', async function() { }); }); }); }); });