Skip to content

Commit 60a5219

Browse files
authored
Refactor singular value computation in Matrix class b to return both the maximum singular value and vector. (#1041)
1 parent 8cfdb11 commit 60a5219

2 files changed

Lines changed: 63 additions & 51 deletions

File tree

lib/util/matrix.js

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4147,35 +4147,6 @@ export default class Matrix {
41474147
return ev
41484148
}
41494149

4150-
/**
4151-
* Returns the maximum singular value.
4152-
* @param {number} [maxIteration] Maximum iteration
4153-
* @returns {number} Maximum singular value
4154-
*/
4155-
singularValuePowerIteration(maxIteration = 1.0e4) {
4156-
const tol = 1.0e-15
4157-
let v = Matrix.randn(this.cols, 1)
4158-
let u = Matrix.randn(this.rows, 1)
4159-
v.div(v.norm())
4160-
u.div(u.norm())
4161-
let ps = Infinity
4162-
let maxCount = maxIteration
4163-
while (maxCount-- > 0) {
4164-
const v2 = this.tDot(u)
4165-
const s = v.tDot(v2).at(0, 0)
4166-
v2.div(v2.norm())
4167-
u = this.dot(v2)
4168-
u.div(u.norm())
4169-
const e = Math.abs(s - ps)
4170-
if (e < tol || isNaN(e)) {
4171-
return s
4172-
}
4173-
v = v2
4174-
ps = s
4175-
}
4176-
throw new MatrixException('singularValuePowerIteration not converged.', this)
4177-
}
4178-
41794150
/**
41804151
* Returns a singular value decomposition.
41814152
* @returns {[Matrix<number>, number[], Matrix<number>]} Unitary matrix and singular values
@@ -4288,6 +4259,35 @@ export default class Matrix {
42884259
}
42894260
}
42904261

4262+
/**
4263+
* Returns the maximum singular value and vector.
4264+
* @param {number} [maxIteration] Maximum iteration
4265+
* @returns {[Matrix<number>, number, Matrix<number>]} Maximum singular value and vector
4266+
*/
4267+
svdPowerIteration(maxIteration = 1.0e4) {
4268+
const tol = 1.0e-15
4269+
let v = Matrix.randn(this.cols, 1)
4270+
let u = Matrix.randn(this.rows, 1)
4271+
v.div(v.norm())
4272+
u.div(u.norm())
4273+
let ps = Infinity
4274+
let maxCount = maxIteration
4275+
while (maxCount-- > 0) {
4276+
const v2 = this.tDot(u)
4277+
const s = v.tDot(v2).toScaler()
4278+
v2.div(v2.norm())
4279+
u = this.dot(v2)
4280+
u.div(u.norm())
4281+
const e = Math.abs(s - ps)
4282+
if (e < tol || isNaN(e)) {
4283+
return [u, s, v2]
4284+
}
4285+
v = v2
4286+
ps = s
4287+
}
4288+
throw new MatrixException('svdPowerIteration not converged.', this)
4289+
}
4290+
42914291
/**
42924292
* Returns a cholesky decomposition.
42934293
* @returns {Matrix<number>} Cholesky decomposition matrix

tests/lib/util/matrix.test.js

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { expect } from 'vitest'
12
import Matrix from '../../../lib/util/matrix.js'
23
import Tensor from '../../../lib/util/tensor.js'
34

@@ -6252,28 +6253,6 @@ describe('Matrix', () => {
62526253
})
62536254
})
62546255

6255-
describe('singularValuePowerIteration', () => {
6256-
test.each([
6257-
[10, 6],
6258-
[6, 10],
6259-
])('size [%i, %i]', (r, c) => {
6260-
const mat = Matrix.randn(r, c)
6261-
const sv = mat.singularValuePowerIteration()
6262-
expect(sv).toBeGreaterThanOrEqual(0)
6263-
6264-
const [ev] = mat.tDot(mat).eigenPowerIteration()
6265-
expect(sv ** 2).toBeCloseTo(ev)
6266-
})
6267-
6268-
test('iteration not converged', () => {
6269-
const mat = new Matrix(2, 2, [
6270-
[-1, -2],
6271-
[2, -2],
6272-
])
6273-
expect(() => mat.singularValuePowerIteration(1)).toThrow('singularValuePowerIteration not converged.')
6274-
})
6275-
})
6276-
62776256
describe('svd', () => {
62786257
test.each([
62796258
[10, 10],
@@ -6503,6 +6482,39 @@ describe('Matrix', () => {
65036482
})
65046483
})
65056484

6485+
describe('svdPowerIteration', () => {
6486+
test.each([
6487+
[10, 6],
6488+
[6, 10],
6489+
])('size [%i, %i]', (r, c) => {
6490+
const mat = Matrix.randn(r, c)
6491+
const [u, sv, v] = mat.svdPowerIteration()
6492+
expect(sv).toBeGreaterThanOrEqual(0)
6493+
expect(u.norm()).toBeCloseTo(1)
6494+
expect(v.norm()).toBeCloseTo(1)
6495+
6496+
const mv = mat.dot(v)
6497+
for (let i = 0; i < r; i++) {
6498+
expect(mv.at(i, 0)).toBeCloseTo(sv * u.at(i, 0))
6499+
}
6500+
const mu = mat.tDot(u)
6501+
for (let i = 0; i < c; i++) {
6502+
expect(mu.at(i, 0)).toBeCloseTo(sv * v.at(i, 0))
6503+
}
6504+
6505+
const [ev] = mat.tDot(mat).eigenPowerIteration()
6506+
expect(sv ** 2).toBeCloseTo(ev)
6507+
})
6508+
6509+
test('iteration not converged', () => {
6510+
const mat = new Matrix(2, 2, [
6511+
[-1, -2],
6512+
[2, -2],
6513+
])
6514+
expect(() => mat.svdPowerIteration(1)).toThrow('svdPowerIteration not converged.')
6515+
})
6516+
})
6517+
65066518
describe('cholesky', () => {
65076519
test('success', () => {
65086520
const n = 10

0 commit comments

Comments
 (0)