ellalgo_rs/power_iteration.rs
1use ndarray::{Array1, ArrayView2};
2// use ndarray_linalg::Norm;
3
4/// Configurable options for the power iteration algorithm.
5///
6/// The `max_iters` field specifies the maximum number of iterations to perform.
7/// The `tolerance` field specifies the tolerance for convergence of the algorithm.
8pub struct Options {
9 pub max_iters: usize,
10 pub tolerance: f64,
11}
12
13/// Computes the L1 norm (sum of absolute values) of the elements in the given array.
14///
15/// This function is an inline helper function used internally by the power iteration algorithm.
16/// It calculates the L1 norm of the input array `x` by iterating through the elements and
17/// summing their absolute values.
18#[inline]
19fn norm_l1(x: &Array1<f64>) -> f64 {
20 x.iter().cloned().map(|x| x.abs()).sum()
21}
22
23/// Computes the dominant eigenvector of the given matrix `a` using the power iteration algorithm.
24///
25/// The power iteration algorithm is an iterative method for finding the dominant eigenvector of a matrix.
26/// This function takes the matrix `a`, an initial guess for the eigenvector `x`, and a set of options
27/// controlling the algorithm's behavior.
28///
29/// The function returns the dominant eigenvalue and the number of iterations performed.
30pub fn power_iteration(a: ArrayView2<f64>, x: &mut Array1<f64>, options: &Options) -> (f64, usize) {
31 *x /= x.dot(x).sqrt();
32 for niter in 0..options.max_iters {
33 let x1 = x.clone();
34 *x = a.dot(&x1);
35 *x /= x.dot(x).sqrt();
36 if norm_l1(&(&*x - &x1)) <= options.tolerance || norm_l1(&(&*x + &x1)) <= options.tolerance
37 {
38 return (x.dot(&a.dot(x)), niter);
39 }
40 }
41 (x.dot(&a.dot(x)), options.max_iters)
42}
43
44/// Computes the dominant eigenvector of the given matrix `a` using a modified power iteration algorithm.
45///
46/// This function is similar to the `power_iteration` function, but uses a different normalization
47/// approach. Instead of normalizing the eigenvector by its L2 norm, it is normalized by its L1 norm.
48/// This can sometimes lead to faster convergence, especially for sparse matrices.
49///
50/// The function takes the matrix `a`, an initial guess for the eigenvector `x`, and a set of options
51/// controlling the algorithm's behavior. It returns the dominant eigenvalue and the number of
52/// iterations performed.
53pub fn power_iteration4(
54 a: ArrayView2<f64>,
55 x: &mut Array1<f64>,
56 options: &Options,
57) -> (f64, usize) {
58 *x /= norm_l1(x);
59 for niter in 0..options.max_iters {
60 let x1 = x.clone();
61 *x = a.dot(&x1);
62 *x /= norm_l1(x);
63 if norm_l1(&(&*x - &x1)) <= options.tolerance || norm_l1(&(&*x + &x1)) <= options.tolerance
64 {
65 *x /= x.dot(x).sqrt();
66 return (x.dot(&a.dot(x)), niter);
67 }
68 }
69 *x /= x.dot(x).sqrt();
70 (x.dot(&a.dot(x)), options.max_iters)
71}
72
73/// Computes the dominant eigenvector of the given matrix `a` using a modified power iteration algorithm.
74///
75/// This function is similar to the `power_iteration` function, but uses a different normalization
76/// approach. Instead of normalizing the eigenvector by its L2 norm, it is normalized by its L1 norm.
77/// This can sometimes lead to faster convergence, especially for sparse matrices.
78///
79/// The function takes the matrix `a`, an initial guess for the eigenvector `x`, and a set of options
80/// controlling the algorithm's behavior. It returns the dominant eigenvalue and the number of
81/// iterations performed.
82pub fn power_iteration2(
83 a: ArrayView2<f64>,
84 x: &mut Array1<f64>,
85 options: &Options,
86) -> (f64, usize) {
87 // let (mut new, mut ld) = calc_core2(a, &mut x);
88 *x /= x.dot(x).sqrt();
89 let mut new = a.dot(x);
90 let mut ld = x.dot(&new);
91 for niter in 0..options.max_iters {
92 let ld1 = ld;
93 x.clone_from(&new);
94 // let (new_temp, ld_temp) = calc_core2(a, &mut x);
95 *x /= x.dot(x).sqrt();
96 new = a.dot(x);
97 ld = x.dot(&new);
98 if (ld1 - ld).abs() <= options.tolerance {
99 return (ld, niter);
100 }
101 }
102 (ld, options.max_iters)
103}
104
105/// Computes the dominant eigenvector of the given matrix `a` using a modified power iteration algorithm.
106///
107/// This function is similar to the `power_iteration` and `power_iteration2` functions, but uses a different normalization
108/// approach. It normalizes the eigenvector by its L2 norm, and also checks for very large values in the eigenvector
109/// to prevent numerical overflow.
110///
111/// The function takes the matrix `a`, an initial guess for the eigenvector `x`, and a set of options
112/// controlling the algorithm's behavior. It returns the dominant eigenvalue and the number of
113/// iterations performed.
114pub fn power_iteration3(
115 a: ArrayView2<f64>,
116 x: &mut Array1<f64>,
117 options: &Options,
118) -> (f64, usize) {
119 let mut new = a.dot(x);
120 let mut dot = x.dot(x);
121 let mut ld = x.dot(&new) / dot;
122 for niter in 0..options.max_iters {
123 let ld1 = ld;
124 x.clone_from(&new);
125 dot = x.dot(x);
126 if dot >= 1e150 {
127 *x /= x.dot(x).sqrt();
128 new = a.dot(x);
129 ld = x.dot(&new);
130 if (ld1 - ld).abs() <= options.tolerance {
131 return (ld, niter);
132 }
133 } else {
134 new = a.dot(x);
135 ld = x.dot(&new) / dot;
136 if (ld1 - ld).abs() <= options.tolerance {
137 *x /= x.dot(x).sqrt();
138 return (ld, niter);
139 }
140 }
141 }
142 (ld, options.max_iters)
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use ndarray::Array2;
149
150 #[test]
151 fn test_construct() {
152 let a = Array2::from_shape_vec(
153 (3, 3),
154 vec![3.7, -3.6, 0.7, -3.6, 4.3, -2.8, 0.7, -2.8, 5.4],
155 )
156 .unwrap();
157 let options = Options {
158 max_iters: 2000,
159 tolerance: 1e-7,
160 };
161
162 println!("1-----------------------------");
163 let mut x1 = Array1::from_vec(vec![0.3, 0.5, 0.4]);
164 let (ld, niter) = power_iteration(a.view(), &mut x1, &options);
165 println!("{:?}", x1);
166 println!("{}", ld);
167 assert_eq!(niter, 22);
168
169 println!("4-----------------------------");
170 let mut x4 = Array1::from_vec(vec![0.3, 0.5, 0.4]);
171 let (ld, niter) = power_iteration4(a.view(), &mut x4, &options);
172 println!("{:?}", x4);
173 println!("{}", ld);
174 assert_eq!(niter, 21);
175
176 let options = Options {
177 max_iters: 2000,
178 tolerance: 1e-14,
179 };
180
181 println!("2-----------------------------");
182 let mut x2 = Array1::from_vec(vec![0.3, 0.5, 0.4]);
183 let (ld, niter) = power_iteration2(a.view(), &mut x2, &options);
184 println!("{:?}", x2);
185 println!("{}", ld);
186 assert_eq!(niter, 23);
187
188 println!("3-----------------------------");
189 let mut x3 = Array1::from_vec(vec![0.3, 0.5, 0.4]);
190 let (ld, niter) = power_iteration3(a.view(), &mut x3, &options);
191 println!("{:?}", x3);
192 println!("{}", ld);
193 assert_eq!(niter, 23);
194 }
195}