Skip to content

Commit d78911d

Browse files
committed
feat: update benchmark parameters and add comprehensive tests for SparseMatrix operations
1 parent 12758f9 commit d78911d

File tree

4 files changed

+367
-90
lines changed

4 files changed

+367
-90
lines changed

benchmark/benchmark.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,12 @@ function printWinner(label1, avg1, label2, avg2) {
5454

5555
function runBenchmarks() {
5656
const m = 128;
57-
const n = 64;
57+
const n = 128;
5858
const p = 128;
59-
const densityA = 0.03;
60-
const densityB = 0.03;
59+
const densityA = 0.01;
60+
const densityB = 0.01;
61+
62+
const nbIterations = 3;
6163
const A = randomSparseMatrix(m, n, densityA);
6264
const B = randomSparseMatrix(n, p, densityB);
6365

@@ -66,10 +68,11 @@ function runBenchmarks() {
6668

6769
const AOld = new SparseMatrixOld(denseA);
6870
const BOld = new SparseMatrixOld(denseB);
69-
A.cardinality;
71+
7072
denseA = new Matrix(denseA);
7173
denseB = new Matrix(denseB);
7274

75+
denseA.mmul(denseB);
7376
// 1. add vs addNew
7477
// const addAvg = benchmark(() => {
7578
// const a = AOld.clone();
@@ -90,23 +93,23 @@ function runBenchmarks() {
9093
A.mmul(B);
9194
},
9295
'mmulNew',
93-
3,
96+
nbIterations,
9497
);
9598

9699
const mmulAvg = benchmark(
97100
() => {
98101
AOld.mmul(BOld);
99102
},
100103
'mmul',
101-
3,
104+
nbIterations,
102105
);
103106

104107
const denseAvg = benchmark(
105108
() => {
106109
denseA.mmul(denseB);
107110
},
108111
'denseMatrix',
109-
3,
112+
nbIterations,
110113
);
111114

112115
printWinner('mmul', mmulAvg, 'mmulNew', mmulNewAvg);

src/__tests__/index.test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,29 @@ describe('Sparse Matrix', () => {
4242
[0, 2],
4343
[0, 0],
4444
]);
45+
46+
// Compare with dense multiplication
47+
const denseM1 = m1.to2DArray();
48+
const denseM2 = m2.to2DArray();
49+
const expectedDense = denseMatrixMultiply(denseM1, denseM2);
50+
expect(m3.to2DArray()).toStrictEqual(expectedDense);
51+
});
52+
53+
it('mmul', () => {
54+
const size = 32;
55+
const density = 0.1;
56+
const m1 = randomSparseMatrix(size, size, density);
57+
const m2 = randomSparseMatrix(size, size, density);
58+
let m3 = m1.mmul(m2);
59+
60+
const denseM1 = m1.to2DArray();
61+
const denseM2 = m2.to2DArray();
62+
63+
const newSparse = new SparseMatrix(denseM1);
64+
expect(newSparse.to2DArray()).toStrictEqual(denseM1);
65+
const expectedDense = denseMatrixMultiply(denseM1, denseM2);
66+
67+
expect(m3.to2DArray()).toStrictEqual(expectedDense);
4568
});
4669

4770
it('kronecker', () => {
@@ -144,3 +167,32 @@ describe('Banded matrices', () => {
144167
expect(matrix4.isBanded(1)).toBe(true);
145168
});
146169
});
170+
171+
function denseMatrixMultiply(A, B) {
172+
const rowsA = A.length;
173+
const colsA = A[0].length;
174+
const colsB = B[0].length;
175+
const result = Array.from({ length: rowsA }, () => Array(colsB).fill(0));
176+
for (let i = 0; i < rowsA; i++) {
177+
for (let j = 0; j < colsB; j++) {
178+
for (let k = 0; k < colsA; k++) {
179+
result[i][j] += A[i][k] * B[k][j];
180+
}
181+
}
182+
}
183+
return result;
184+
}
185+
186+
function randomSparseMatrix(rows, cols, density = 0.01) {
187+
const matrix = [];
188+
for (let i = 0; i < rows; i++) {
189+
const row = new Float64Array(cols);
190+
for (let j = 0; j < cols; j++) {
191+
if (Math.random() < density) {
192+
row[j] = Math.random() * 10;
193+
}
194+
}
195+
matrix.push(row);
196+
}
197+
return new SparseMatrix(matrix);
198+
}

src/__tests__/matrix.test.js

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { describe, expect, it } from 'vitest';
2+
3+
import { SparseMatrix } from '../index.js';
4+
5+
describe('Sparse Matrix', () => {
6+
it('add', () => {
7+
let m1 = new SparseMatrix([
8+
[2, 0, 1],
9+
[0, 0, 3],
10+
[2, 0, 1],
11+
]);
12+
let m2 = new SparseMatrix([
13+
[0, 1, 5],
14+
[2, 0, 0],
15+
[-2, 0, -1],
16+
]);
17+
let m3 = m1.add(m2).to2DArray();
18+
expect(m3).toStrictEqual([
19+
[2, 1, 6],
20+
[2, 0, 3],
21+
[0, 0, 0],
22+
]);
23+
});
24+
it('mmul', () => {
25+
let m1 = new SparseMatrix([
26+
[2, 0, 1],
27+
[0, 0, 3],
28+
]);
29+
let m2 = new SparseMatrix([
30+
[0, 1],
31+
[2, 0],
32+
[0, 0],
33+
]);
34+
let m3 = m1.mmul(m2);
35+
36+
expect(m1.cardinality).toBe(3);
37+
expect(m2.cardinality).toBe(2);
38+
expect(m3.cardinality).toBe(1);
39+
40+
expect(m3.get(0, 1)).toBe(2);
41+
expect(m3.to2DArray()).toStrictEqual([
42+
[0, 2],
43+
[0, 0],
44+
]);
45+
46+
// Compare with dense multiplication
47+
const denseM1 = m1.to2DArray();
48+
const denseM2 = m2.to2DArray();
49+
const expectedDense = denseMatrixMultiply(denseM1, denseM2);
50+
expect(m3.to2DArray()).toStrictEqual(expectedDense);
51+
});
52+
53+
it('mmul', () => {
54+
const size = 32;
55+
const density = 0.1;
56+
const m1 = randomSparseMatrix(size, size, density);
57+
const m2 = randomSparseMatrix(size, size, density);
58+
let m3 = m1.mmul(m2);
59+
60+
const denseM1 = m1.to2DArray();
61+
const denseM2 = m2.to2DArray();
62+
63+
const newSparse = new SparseMatrix(denseM1);
64+
expect(newSparse.to2DArray()).toStrictEqual(denseM1);
65+
const expectedDense = denseMatrixMultiply(denseM1, denseM2);
66+
67+
expect(m3.to2DArray()).toStrictEqual(expectedDense);
68+
});
69+
70+
it('kronecker', () => {
71+
const matrix1 = new SparseMatrix([
72+
[1, 2],
73+
[3, 4],
74+
]);
75+
const matrix2 = new SparseMatrix([
76+
[0, 5],
77+
[6, 7],
78+
]);
79+
const product = matrix1.kroneckerProduct(matrix2);
80+
expect(product.to2DArray()).toStrictEqual([
81+
[0, 5, 0, 10],
82+
[6, 7, 12, 14],
83+
[0, 15, 0, 20],
84+
[18, 21, 24, 28],
85+
]);
86+
});
87+
88+
it('isSymmetric', () => {
89+
expect(new SparseMatrix(10, 10).isSymmetric()).toBe(true);
90+
expect(new SparseMatrix(15, 10).isSymmetric()).toBe(false);
91+
92+
let m = new SparseMatrix([
93+
[0, 1],
94+
[1, 0],
95+
]);
96+
expect(m.isSymmetric()).toBe(true);
97+
98+
m = new SparseMatrix([
99+
[0, 1],
100+
[0, 1],
101+
]);
102+
expect(m.isSymmetric()).toBe(false);
103+
});
104+
105+
it('transpose', () => {
106+
const matrix = new SparseMatrix([
107+
[1, 2],
108+
[3, 4],
109+
]);
110+
expect(matrix.transpose().to2DArray()).toStrictEqual([
111+
[1, 3],
112+
[2, 4],
113+
]);
114+
});
115+
});
116+
117+
describe('Banded matrices', () => {
118+
it('Check band size', () => {
119+
const matrix1 = new SparseMatrix([
120+
[1, 0],
121+
[0, 1],
122+
]);
123+
const matrix2 = new SparseMatrix([
124+
[1, 0, 0],
125+
[0, 1, 0],
126+
]);
127+
const matrix3 = new SparseMatrix([
128+
[1, 0, 1],
129+
[0, 1, 0],
130+
]);
131+
const matrix4 = new SparseMatrix([
132+
[1, 0, 0],
133+
[1, 1, 0],
134+
]);
135+
const matrix5 = new SparseMatrix([
136+
[0, 0, 0],
137+
[1, 0, 0],
138+
[0, 1, 0],
139+
]);
140+
expect(matrix1.bandWidth()).toBe(0);
141+
expect(matrix2.bandWidth()).toBe(0);
142+
expect(matrix3.bandWidth()).toBe(2);
143+
expect(matrix4.bandWidth()).toBe(1);
144+
expect(matrix5.bandWidth()).toBe(0);
145+
});
146+
147+
it('isBanded', () => {
148+
const matrix1 = new SparseMatrix([
149+
[1, 0],
150+
[0, 1],
151+
]);
152+
const matrix2 = new SparseMatrix([
153+
[1, 0, 0],
154+
[0, 1, 0],
155+
]);
156+
const matrix3 = new SparseMatrix([
157+
[1, 0, 1],
158+
[0, 1, 0],
159+
]);
160+
const matrix4 = new SparseMatrix([
161+
[1, 0, 0],
162+
[1, 1, 0],
163+
]);
164+
expect(matrix1.isBanded(1)).toBe(true);
165+
expect(matrix2.isBanded(1)).toBe(true);
166+
expect(matrix3.isBanded(1)).toBe(false);
167+
expect(matrix4.isBanded(1)).toBe(true);
168+
});
169+
});
170+
171+
function denseMatrixMultiply(A, B) {
172+
const rowsA = A.length;
173+
const colsA = A[0].length;
174+
const colsB = B[0].length;
175+
const result = Array.from({ length: rowsA }, () => Array(colsB).fill(0));
176+
for (let i = 0; i < rowsA; i++) {
177+
for (let j = 0; j < colsB; j++) {
178+
for (let k = 0; k < colsA; k++) {
179+
result[i][j] += A[i][k] * B[k][j];
180+
}
181+
}
182+
}
183+
return result;
184+
}
185+
186+
function randomSparseMatrix(rows, cols, density = 0.01) {
187+
const matrix = [];
188+
for (let i = 0; i < rows; i++) {
189+
const row = new Float64Array(cols);
190+
for (let j = 0; j < cols; j++) {
191+
if (Math.random() < density) {
192+
row[j] = Math.random() * 10;
193+
}
194+
}
195+
matrix.push(row);
196+
}
197+
return new SparseMatrix(matrix);
198+
}

0 commit comments

Comments
 (0)