1
1
import numpy as np
2
-
2
+ from typing import Tuple , Union
3
+ import numpy .typing as npt
3
4
4
5
def power_iteration (
5
- input_matrix : np .ndarray ,
6
- vector : np .ndarray ,
6
+ input_matrix : npt . NDArray [ Union [ np .float64 , np . complex128 ]] ,
7
+ vector : npt . NDArray [ Union [ np .float64 , np . complex128 ]] ,
7
8
error_tol : float = 1e-12 ,
8
9
max_iterations : int = 100 ,
9
- ) -> tuple [float , np .ndarray ]:
10
+ ) -> Tuple [float , npt . NDArray [ Union [ np .float64 , np . complex128 ]] ]:
10
11
"""
11
12
Power Iteration.
12
13
Find the largest eigenvalue and corresponding eigenvector
13
14
of matrix input_matrix given a random vector in the same space.
14
- Will work so long as vector has component of largest eigenvector.
15
+ Works as long as vector has a component of the largest eigenvector.
15
16
input_matrix must be either real or Hermitian.
16
17
17
- Input
18
- input_matrix: input matrix whose largest eigenvalue we will find.
19
- Numpy array. np.shape(input_matrix) == (N,N).
20
- vector: random initial vector in same space as matrix.
21
- Numpy array. np.shape(vector) == (N,) or (N,1)
18
+ Input:
19
+ input_matrix: Input matrix whose largest eigenvalue we will find.
20
+ A square numpy array. Shape: (N, N).
21
+ vector: Random initial vector in the same space as the matrix.
22
+ Numpy array. Shape: (N,) or (N,1).
22
23
23
- Output
24
- largest_eigenvalue: largest eigenvalue of the matrix input_matrix.
25
- Float. Scalar.
26
- largest_eigenvector: eigenvector corresponding to largest_eigenvalue.
27
- Numpy array. np.shape(largest_eigenvector) == (N,) or (N,1).
24
+ Output:
25
+ largest_eigenvalue: Largest eigenvalue of the matrix.
26
+ largest_eigenvector: Eigenvector corresponding to largest eigenvalue.
28
27
28
+ Example:
29
29
>>> import numpy as np
30
30
>>> input_matrix = np.array([
31
- ... [41, 4, 20],
32
- ... [ 4, 26, 30],
31
+ ... [41, 4, 20],
32
+ ... [4, 26, 30],
33
33
... [20, 30, 50]
34
34
... ])
35
- >>> vector = np.array([41,4, 20])
36
- >>> power_iteration(input_matrix,vector)
35
+ >>> vector = np.array([41, 4, 20])
36
+ >>> power_iteration(input_matrix, vector)
37
37
(79.66086378788381, array([0.44472726, 0.46209842, 0.76725662]))
38
38
"""
39
-
40
39
# Ensure matrix is square.
41
- assert np .shape (input_matrix )[0 ] == np .shape (input_matrix )[1 ]
40
+ N , M = np .shape (input_matrix )
41
+ assert N == M , "Input matrix must be square."
42
42
# Ensure proper dimensionality.
43
- assert np . shape ( input_matrix )[ 0 ] == np .shape (vector )[0 ]
43
+ assert N == np .shape (vector )[0 ], "Vector must be compatible with matrix dimensions."
44
44
# Ensure inputs are either both complex or both real
45
- assert np .iscomplexobj (input_matrix ) == np .iscomplexobj (vector )
45
+ assert np .iscomplexobj (input_matrix ) == np .iscomplexobj (vector ), "Both inputs must be either real or complex."
46
+
46
47
is_complex = np .iscomplexobj (input_matrix )
47
48
if is_complex :
48
- # Ensure complex input_matrix is Hermitian
49
- assert np .array_equal (input_matrix , input_matrix .conj ().T )
50
-
51
- # Set convergence to False. Will define convergence when we exceed max_iterations
52
- # or when we have small changes from one iteration to next.
49
+ # Ensure complex input_matrix is Hermitian (A == A*)
50
+ assert np .array_equal (input_matrix , input_matrix .conj ().T ), "Input matrix must be Hermitian if complex."
53
51
54
52
convergence = False
55
- lambda_previous = 0
53
+ lambda_previous = 0.0
56
54
iterations = 0
57
- error = 1e12
55
+ error = float ( "inf" )
58
56
59
57
while not convergence :
60
- # Multiple matrix by the vector.
58
+ # Multiply matrix by the vector.
61
59
w = np .dot (input_matrix , vector )
62
60
# Normalize the resulting output vector.
63
61
vector = w / np .linalg .norm (w )
64
- # Find rayleigh quotient
65
- # (faster than usual b/c we know vector is normalized already)
62
+ # Find the Rayleigh quotient (faster since we know vector is normalized).
66
63
vector_h = vector .conj ().T if is_complex else vector .T
67
64
lambda_ = np .dot (vector_h , np .dot (input_matrix , vector ))
68
65
69
66
# Check convergence.
70
- error = np .abs (lambda_ - lambda_previous ) / lambda_
67
+ error = np .abs (lambda_ - lambda_previous ) / np . abs ( lambda_ )
71
68
iterations += 1
72
69
73
70
if error <= error_tol or iterations >= max_iterations :
74
71
convergence = True
75
72
76
73
lambda_previous = lambda_
77
74
75
+ # Ensure lambda_ is real if the matrix is complex.
78
76
if is_complex :
79
77
lambda_ = np .real (lambda_ )
80
78
@@ -83,7 +81,8 @@ def power_iteration(
83
81
84
82
def test_power_iteration () -> None :
85
83
"""
86
- >>> test_power_iteration() # self running tests
84
+ Test function for power_iteration.
85
+ Runs tests on real and complex matrices using the power_iteration function.
87
86
"""
88
87
real_input_matrix = np .array ([[41 , 4 , 20 ], [4 , 26 , 30 ], [20 , 30 , 50 ]])
89
88
real_vector = np .array ([41 , 4 , 20 ])
@@ -105,19 +104,12 @@ def test_power_iteration() -> None:
105
104
eigen_value , eigen_vector = power_iteration (input_matrix , vector )
106
105
107
106
# Numpy implementation.
108
-
109
- # Get eigenvalues and eigenvectors using built-in numpy
110
- # eigh (eigh used for symmetric or hermetian matrices).
111
107
eigen_values , eigen_vectors = np .linalg .eigh (input_matrix )
112
- # Last eigenvalue is the maximum one.
113
108
eigen_value_max = eigen_values [- 1 ]
114
- # Last column in this matrix is eigenvector corresponding to largest eigenvalue.
115
109
eigen_vector_max = eigen_vectors [:, - 1 ]
116
110
117
- # Check our implementation and numpy gives close answers.
111
+ # Check that our implementation and numpy's give close answers.
118
112
assert np .abs (eigen_value - eigen_value_max ) <= 1e-6
119
- # Take absolute values element wise of each eigenvector.
120
- # as they are only unique to a minus sign.
121
113
assert np .linalg .norm (np .abs (eigen_vector ) - np .abs (eigen_vector_max )) <= 1e-6
122
114
123
115
0 commit comments