Skip to content

Commit 3586bc9

Browse files
committed
Calculate eigen values and vectors
Previously was only calculating values. Test made using numpy's eigen method. Signed-off-by: Kyle Corry <kylecorry31@gmail.com>
1 parent fa612c6 commit 3586bc9

3 files changed

Lines changed: 95 additions & 3 deletions

File tree

src/main/kotlin/com/kylecorry/sol/math/algebra/LinearAlgebra.kt

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,11 @@ object LinearAlgebra {
264264
}
265265

266266
r[j, j] = norm(v)
267-
q.setColumn(j, v.divide(r[j, j]).getRow(0))
267+
if (Arithmetic.isZero(r[j, j])) {
268+
q.setColumn(j, orthogonalUnitVector(q, j))
269+
} else {
270+
q.setColumn(j, v.divide(r[j, j]).getRow(0))
271+
}
268272
}
269273

270274
return q to r
@@ -279,21 +283,32 @@ object LinearAlgebra {
279283
}
280284
}
281285

282-
fun eigenvalues(m: Matrix, tolerance: Float = 1e-12f, maxIterations: Int = 1000): FloatArray {
286+
fun eigen(
287+
m: Matrix,
288+
tolerance: Float = 1e-12f,
289+
maxIterations: Int = 1000
290+
): EigenDecomposition {
291+
require(m.rows() == m.columns()) { "Matrix must be square" }
292+
require(m.rows() > 0) { "Matrix must not be empty" }
293+
require(tolerance > 0f) { "Tolerance must be greater than zero" }
294+
require(maxIterations >= 0) { "Max iterations must be non-negative" }
295+
283296
var old = m
284297
var new = m
298+
var vectors = Matrix.identity(m.rows())
285299

286300
var diff = Float.MAX_VALUE
287301
var iterations = 0
288302
while (diff > tolerance && iterations < maxIterations) {
289303
val (q, r) = qr(new)
290304
new = r.dot(q)
305+
vectors = vectors.dot(q)
291306
diff = max(new.subtract(old).mapped { it.absoluteValue })
292307
old = new
293308
iterations++
294309
}
295310

296-
return diagonal(new).getRow(0)
311+
return EigenDecomposition(Vector(diagonal(new).getRow(0)), normalizeEigenvectorSigns(vectors))
297312
}
298313

299314
fun norm(m: Matrix): Float {
@@ -451,6 +466,45 @@ object LinearAlgebra {
451466
return dot(a, x).toVector() - b
452467
}
453468

469+
private fun orthogonalUnitVector(q: Matrix, column: Int): FloatArray {
470+
for (attempt in 0..<q.rows()) {
471+
val vector = FloatArray(q.rows()) { index ->
472+
if (index == (column + attempt) % q.rows()) 1f else 0f
473+
}
474+
475+
for (previous in 0..<column) {
476+
var projection = 0f
477+
for (row in 0..<q.rows()) {
478+
projection += vector[row] * q[row, previous]
479+
}
480+
481+
for (row in 0..<q.rows()) {
482+
vector[row] -= projection * q[row, previous]
483+
}
484+
}
485+
486+
val norm = sqrt(vector.sumOf { it * it.toDouble() }).toFloat()
487+
if (!Arithmetic.isZero(norm)) {
488+
return FloatArray(vector.size) { index -> vector[index] / norm }
489+
}
490+
}
491+
492+
return FloatArray(q.rows()) { index -> if (index == column % q.rows()) 1f else 0f }
493+
}
494+
495+
private fun normalizeEigenvectorSigns(vectors: Matrix): Matrix {
496+
return Matrix.create(vectors.rows(), vectors.columns()) { row, column ->
497+
var maxIndex = 0
498+
for (i in 1..<vectors.rows()) {
499+
if (vectors[i, column].absoluteValue > vectors[maxIndex, column].absoluteValue) {
500+
maxIndex = i
501+
}
502+
}
503+
504+
vectors[row, column] * if (vectors[maxIndex, column] >= 0f) 1f else -1f
505+
}
506+
}
507+
454508
fun appendColumn(m: Matrix, value: Float): Matrix {
455509
return Matrix.create(m.rows(), m.columns() + 1) { r, c ->
456510
if (c < m.columns()) {
@@ -482,4 +536,6 @@ object LinearAlgebra {
482536
}
483537
}
484538

539+
data class EigenDecomposition(val values: Vector, val vectors: Matrix)
540+
485541
}

src/main/kotlin/com/kylecorry/sol/math/algebra/Matrix.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,18 @@ fun Matrix.add(other: Matrix): Matrix {
239239
return LinearAlgebra.add(this, other)
240240
}
241241

242+
fun Matrix.add(value: Float): Matrix {
243+
return LinearAlgebra.add(this, value)
244+
}
245+
242246
fun Matrix.subtract(other: Matrix): Matrix {
243247
return LinearAlgebra.subtract(this, other)
244248
}
245249

250+
fun Matrix.subtract(value: Float): Matrix {
251+
return LinearAlgebra.subtract(this, value)
252+
}
253+
246254
fun Matrix.multiply(other: Matrix): Matrix {
247255
return LinearAlgebra.multiply(this, other)
248256
}

src/test/kotlin/com/kylecorry/sol/math/algebra/LinearAlgebraTest.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,34 @@ class LinearAlgebraTest {
505505
assertEquals(sqrt(91.0).toFloat(), result, 0.00001f)
506506
}
507507

508+
@Test
509+
fun eigen() {
510+
val matrix = Matrix.create(
511+
arrayOf(
512+
floatArrayOf(4f, 1f, 1f),
513+
floatArrayOf(1f, 3f, 0.5f),
514+
floatArrayOf(1f, 0.5f, 2f)
515+
)
516+
)
517+
518+
val result = LinearAlgebra.eigen(matrix)
519+
520+
assertEquals(5.028030f, result.values[0], 0.0001f)
521+
assertEquals(2.392581f, result.values[1], 0.0001f)
522+
assertEquals(1.579389f, result.values[2], 0.0001f)
523+
assertEquals(3, result.vectors.rows())
524+
assertEquals(3, result.vectors.columns())
525+
assertEquals(0.805083f, result.vectors[0, 0], 0.0001f)
526+
assertEquals(0.482157f, result.vectors[1, 0], 0.0001f)
527+
assertEquals(0.345493f, result.vectors[2, 0], 0.0001f)
528+
assertEquals(-0.477410f, result.vectors[0, 1], 0.0001f)
529+
assertEquals(0.872386f, result.vectors[1, 1], 0.0001f)
530+
assertEquals(-0.104989f, result.vectors[2, 1], 0.0001f)
531+
assertEquals(-0.352024f, result.vectors[0, 2], 0.0001f)
532+
assertEquals(-0.080417f, result.vectors[1, 2], 0.0001f)
533+
assertEquals(0.932530f, result.vectors[2, 2], 0.0001f)
534+
}
535+
508536
@Test
509537
fun max() {
510538
val m1 = Matrix.create(3, 2, 0f)

0 commit comments

Comments
 (0)