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}