Skip to content

Commit 6c69385

Browse files
authored
Add some singular / eigen value calculate method (#904)
1 parent 200c470 commit 6c69385

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

lib/util/matrix.js

+64
Original file line numberDiff line numberDiff line change
@@ -3614,6 +3614,35 @@ export default class Matrix {
36143614
return ev
36153615
}
36163616

3617+
/**
3618+
* Returns the maximum singular value.
3619+
* @param {number} [maxIteration] Maximum iteration
3620+
* @returns {number} Maximum singular value
3621+
*/
3622+
singularValuePowerIteration(maxIteration = 1.0e4) {
3623+
const tol = 1.0e-15
3624+
let v = Matrix.randn(this.cols, 1)
3625+
let u = Matrix.randn(this.rows, 1)
3626+
v.div(v.norm())
3627+
u.div(u.norm())
3628+
let ps = Infinity
3629+
let maxCount = maxIteration
3630+
while (maxCount-- > 0) {
3631+
const v2 = this.tDot(u)
3632+
const s = v.tDot(v2).at(0, 0)
3633+
v2.div(v2.norm())
3634+
u = this.dot(v2)
3635+
u.div(u.norm())
3636+
const e = Math.abs(s - ps)
3637+
if (e < tol || isNaN(e)) {
3638+
return s
3639+
}
3640+
v = v2
3641+
ps = s
3642+
}
3643+
throw new MatrixException('singularValuePowerIteration not converged.', this)
3644+
}
3645+
36173646
/**
36183647
* Returns a singular value decomposition.
36193648
* @returns {[Matrix, number[], Matrix]} Unitary matrix and singular values
@@ -4390,6 +4419,41 @@ export default class Matrix {
43904419
throw new MatrixException('eigenPowerIteration not converged.', this)
43914420
}
43924421

4422+
/**
4423+
* Returns the k highest eigenvalues and its eigenvectors.
4424+
* @param {number} k Number of calculated eigenvalues
4425+
* @param {number} [maxIteration] Maximum iteration
4426+
* @returns {[number[], Matrix]} The k highest eigenvalues and its eigenvectors. Eigenvectors correspond to each column of the matrix.
4427+
*/
4428+
eigenSimultaneousIteration(k, maxIteration = 1.0e4) {
4429+
if (!this.isSymmetric(1.0e-15)) {
4430+
throw new MatrixException('Simultaneous iteration method can only use symmetric matrix.', this)
4431+
}
4432+
4433+
const n = this.rows
4434+
const tol = 1.0e-8
4435+
let px = Matrix.ones(n, k)
4436+
px.div(px.norm())
4437+
let pl = new Matrix(1, k, Infinity)
4438+
const r0 = this.row(0)
4439+
let maxCount = maxIteration
4440+
while (maxCount-- > 0) {
4441+
const x = this.dot(px)
4442+
const [q] = x.qr()
4443+
q.resize(q.rows, k)
4444+
let ev = r0.dot(q)
4445+
ev.div(q.row(0))
4446+
pl.sub(ev)
4447+
const e = pl.reduce((s, v) => s + Math.abs(v), 0) / k
4448+
if (e < tol || isNaN(e)) {
4449+
return [ev.value, q]
4450+
}
4451+
px = q
4452+
pl = ev
4453+
}
4454+
throw new MatrixException('eigenSimultaneousIteration not converged.', this)
4455+
}
4456+
43934457
/**
43944458
* Returns the nearest eigenvalue and its eigenvector to the specified value.
43954459
* @param {number} [ev] Target value

tests/lib/util/matrix.test.js

+83
Original file line numberDiff line numberDiff line change
@@ -5923,6 +5923,28 @@ describe('Matrix', () => {
59235923
})
59245924
})
59255925

5926+
describe('singularValuePowerIteration', () => {
5927+
test.each([
5928+
[10, 6],
5929+
[6, 10],
5930+
])('size [%i, %i]', (r, c) => {
5931+
const mat = Matrix.randn(r, c)
5932+
const sv = mat.singularValuePowerIteration()
5933+
expect(sv).toBeGreaterThanOrEqual(0)
5934+
5935+
const [ev] = mat.tDot(mat).eigenPowerIteration()
5936+
expect(sv ** 2).toBeCloseTo(ev)
5937+
})
5938+
5939+
test('iteration not converged', () => {
5940+
const mat = new Matrix(2, 2, [
5941+
[-1, -2],
5942+
[2, -2],
5943+
])
5944+
expect(() => mat.singularValuePowerIteration(1)).toThrow('singularValuePowerIteration not converged.')
5945+
})
5946+
})
5947+
59265948
describe('svd', () => {
59275949
test.each([
59285950
[10, 10],
@@ -7018,6 +7040,67 @@ describe('Matrix', () => {
70187040
})
70197041
})
70207042

7043+
describe('eigenSimultaneousIteration', () => {
7044+
test.each([
7045+
[1, 1],
7046+
[2, 2],
7047+
[5, 3],
7048+
])('symmetric %i,%i', (n, k) => {
7049+
const mat = Matrix.randn(n, n).gram()
7050+
const [eigvalues, eigvectors] = mat.eigenSimultaneousIteration(k)
7051+
7052+
for (let t = 0; t < k; t++) {
7053+
const cmat = mat.copy()
7054+
for (let i = 0; i < n; i++) {
7055+
cmat.subAt(i, i, eigvalues[t])
7056+
}
7057+
expect(cmat.det()).toBeCloseTo(0)
7058+
}
7059+
7060+
const x = mat.dot(eigvectors)
7061+
const y = Matrix.mult(eigvectors, new Matrix(1, k, eigvalues))
7062+
for (let t = 0; t < k; t++) {
7063+
for (let i = 0; i < n; i++) {
7064+
expect(x.at(i, t)).toBeCloseTo(y.at(i, t))
7065+
}
7066+
}
7067+
const eye = eigvectors.tDot(eigvectors)
7068+
for (let t = 0; t < k; t++) {
7069+
expect(eye.at(t, t)).toBeCloseTo(1)
7070+
}
7071+
})
7072+
7073+
test('non symmetric', () => {
7074+
const mat = new Matrix(4, 4, [
7075+
[16, -1, 1, 2],
7076+
[2, 12, 1, -1],
7077+
[1, 3, -24, 2],
7078+
[4, -2, 1, 20],
7079+
])
7080+
expect(() => mat.eigenSimultaneousIteration(1)).toThrow(
7081+
'Simultaneous iteration method can only use symmetric matrix.'
7082+
)
7083+
})
7084+
7085+
test.each([
7086+
[2, 3],
7087+
[3, 2],
7088+
])('fail(%i, %i)', (r, c) => {
7089+
const mat = Matrix.randn(r, c)
7090+
expect(() => mat.eigenSimultaneousIteration(1)).toThrow(
7091+
'Simultaneous iteration method can only use symmetric matrix.'
7092+
)
7093+
})
7094+
7095+
test('iteration not converged', () => {
7096+
const mat = new Matrix(2, 2, [
7097+
[-1, 2],
7098+
[2, -2],
7099+
])
7100+
expect(() => mat.eigenSimultaneousIteration(1, 1)).toThrow('eigenSimultaneousIteration not converged.')
7101+
})
7102+
})
7103+
70217104
describe('eigenInverseIteration', () => {
70227105
test.each([1, 2, 5])('symmetric %i', n => {
70237106
const mat = Matrix.randn(n, n).gram()

0 commit comments

Comments
 (0)