Skip to content

Commit 5e119c9

Browse files
committed
feat: add determinant algorithm
1 parent 19b4ced commit 5e119c9

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed

maths/determinant.ts

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* @description
3+
* Computes the determinant of the given matrix using elimination.
4+
* - Rounding errors may occur for some matrices.
5+
* - Only handles 6 decimal places. Rounds thereafter.
6+
* @Complexity_Analysis
7+
* Time complexity: O(n^3)
8+
* Space Complexity: O(n^2)
9+
* @param {number[][]} m - A square matrix (2D array)
10+
* @return {number} - The determinant
11+
* @example det([[1,1],[1,1]]) = 0
12+
*/
13+
14+
function interchange(m: number[][], from: number, to: number): number[][] {
15+
;[m[to], m[from]] = [m[from], m[to]]
16+
return m
17+
}
18+
19+
function addition(
20+
m: number[][],
21+
from: number,
22+
to: number,
23+
c: number
24+
): number[][] {
25+
m[to] = m[to].map((e, i) => e + c * m[from][i])
26+
return m
27+
}
28+
29+
function diagProduct(m: number[][]): number {
30+
let product = 1
31+
for (let i = 0; i < m.length; i++) {
32+
product *= m[i][i]
33+
}
34+
return product
35+
}
36+
37+
export function det(m: number[][]): number {
38+
if (m.some((r) => r.length != m.length)) {
39+
throw new Error('only square matrices can have determinants')
40+
}
41+
42+
const decPlaces = 6
43+
const epsilon = 1e-6
44+
45+
// track the number of applied interchange operations
46+
let appliedICs = 0
47+
for (let i = 0; i < m[0].length; i++) {
48+
// partial pivotting
49+
let idealPivot = null
50+
let maxValue = 0
51+
for (let j = i; j < m.length; j++) {
52+
if (Math.abs(m[j][i]) > maxValue) {
53+
maxValue = Math.abs(m[j][i])
54+
idealPivot = j
55+
}
56+
}
57+
if (idealPivot === null) {
58+
return 0
59+
}
60+
if (idealPivot != i) {
61+
m = interchange(m, i, idealPivot)
62+
appliedICs++
63+
}
64+
// eliminate entries under the pivot
65+
for (let j = i + 1; j < m.length; j++) {
66+
if (Math.abs(m[j][i]) > epsilon) {
67+
m = addition(m, i, j, -m[j][i] / m[i][i])
68+
}
69+
}
70+
}
71+
const result = diagProduct(m) * (-1) ** appliedICs
72+
return parseFloat(result.toFixed(decPlaces))
73+
}

maths/test/determinant.test.ts

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { det } from '../determinant'
2+
3+
describe('determinant', () => {
4+
test.each([
5+
[
6+
[
7+
[1, 2],
8+
[3, 4, 5]
9+
]
10+
],
11+
[
12+
[
13+
[1, 2, 3],
14+
[4, 5, 6]
15+
]
16+
],
17+
[
18+
[
19+
[1, 2],
20+
[3, 4],
21+
[5, 6]
22+
]
23+
]
24+
])('should throw an error for non square matrix %p', (matrix) => {
25+
expect(() => det(matrix)).toThrow(
26+
'only square matrices can have determinants'
27+
)
28+
})
29+
30+
test.each([
31+
[
32+
[
33+
[1, 2],
34+
[3, 4]
35+
],
36+
-2
37+
],
38+
[
39+
[
40+
[1, 1],
41+
[1, 1]
42+
],
43+
0
44+
],
45+
[
46+
[
47+
[1, 2],
48+
[0, 0]
49+
],
50+
0
51+
],
52+
[
53+
[
54+
[8, 1, 5],
55+
[9, 3, 7],
56+
[1, 4, 4]
57+
],
58+
8
59+
],
60+
[
61+
[
62+
[15, 85, 32],
63+
[76, 83, 23],
64+
[28, 56, 92]
65+
],
66+
-382536
67+
],
68+
[
69+
[
70+
[2, -1, 0, 3],
71+
[4, 0, 1, 2],
72+
[3, 2, -1, 1],
73+
[1, 3, 2, -2]
74+
],
75+
-42
76+
],
77+
[
78+
[
79+
[0.75483643, 0.68517541, 0.53548329, 0.5931435],
80+
[0.37031247, 0.80103707, 0.82563949, 0.91266224],
81+
[0.39293451, 0.27228353, 0.54093836, 0.51963319],
82+
[0.60997323, 0.40161682, 0.58330774, 0.17392144]
83+
],
84+
-0.051073
85+
]
86+
])('determinant of %p should be %d', (matrix, expected) => {
87+
expect(det(matrix)).toBe(expected)
88+
})
89+
})

0 commit comments

Comments
 (0)