algebraeon_rings/matrix/
jordan_normal_form.rs

1use super::*;
2use crate::module::{
3    finitely_free_module::FinitelyFreeModuleStructure,
4    finitely_free_submodule::FinitelyFreeSubmodule,
5};
6
7#[derive(Debug, Clone)]
8pub struct JordanBlock<FS: AlgebraicClosureSignature>
9where
10    PolynomialStructure<FS::BFS, FS::BFS>: FactoringMonoidSignature<FactoredExponent = NaturalCanonicalStructure>
11        + SetSignature<Set = Polynomial<<FS::BFS as SetSignature>::Set>>,
12{
13    eigenvalue: FS::Set,
14    blocksize: usize,
15}
16
17impl<FS: AlgebraicClosureSignature> JordanBlock<FS>
18where
19    PolynomialStructure<FS::BFS, FS::BFS>: FactoringMonoidSignature<FactoredExponent = NaturalCanonicalStructure>
20        + SetSignature<Set = Polynomial<<FS::BFS as SetSignature>::Set>>,
21{
22    pub fn matrix(&self, field: &FS) -> Matrix<FS::Set> {
23        // let base_field = field.base_field();
24        Matrix::construct(self.blocksize, self.blocksize, |r, c| {
25            if r == c {
26                self.eigenvalue.clone()
27            } else if r + 1 == c {
28                field.one()
29            } else {
30                field.zero()
31            }
32        })
33    }
34}
35
36#[derive(Debug, Clone)]
37pub struct JordanNormalForm<FS: AlgebraicClosureSignature>
38where
39    PolynomialStructure<FS::BFS, FS::BFS>: FactoringMonoidSignature<FactoredExponent = NaturalCanonicalStructure>
40        + SetSignature<Set = Polynomial<<FS::BFS as SetSignature>::Set>>,
41{
42    field: FS,
43    blocks: Vec<JordanBlock<FS>>,
44}
45
46impl<FS: AlgebraicClosureSignature> JordanNormalForm<FS>
47where
48    PolynomialStructure<FS::BFS, FS::BFS>: FactoringMonoidSignature<FactoredExponent = NaturalCanonicalStructure>
49        + SetSignature<Set = Polynomial<<FS::BFS as SetSignature>::Set>>,
50{
51    pub fn matrix(&self) -> Matrix<FS::Set> {
52        let ac_mat_structure = MatrixStructure::new(self.field.clone());
53        ac_mat_structure.join_diag(
54            self.blocks
55                .iter()
56                .map(|block| block.matrix(&self.field))
57                .collect(),
58        )
59    }
60}
61
62impl<FS: AlgebraicClosureSignature, FSB: BorrowedStructure<FS>> MatrixStructure<FS, FSB>
63where
64    PolynomialStructure<FS::BFS, FS::BFS>: FactoringMonoidSignature<FactoredExponent = NaturalCanonicalStructure>
65        + SetSignature<Set = Polynomial<<FS::BFS as SetSignature>::Set>>,
66{
67    pub fn eigenvalues_list(&self, mat: Matrix<<FS::BFS as SetSignature>::Set>) -> Vec<FS::Set> {
68        let base_field_mat_structure = MatrixStructure::new(self.ring().base_field().clone());
69        self.ring()
70            .all_roots_list(
71                &base_field_mat_structure
72                    .characteristic_polynomial(mat)
73                    .unwrap(),
74            )
75            .unwrap()
76    }
77
78    pub fn eigenvalues_unique(&self, mat: Matrix<<FS::BFS as SetSignature>::Set>) -> Vec<FS::Set> {
79        let base_field_mat_structure = MatrixStructure::new(self.ring().base_field().clone());
80        self.ring()
81            .all_roots_unique(
82                &base_field_mat_structure
83                    .characteristic_polynomial(mat)
84                    .unwrap(),
85            )
86            .unwrap()
87    }
88
89    pub fn eigenvalues_powers(
90        &self,
91        mat: Matrix<<FS::BFS as SetSignature>::Set>,
92    ) -> Vec<(FS::Set, usize)> {
93        let base_field_mat_structure = MatrixStructure::new(self.ring().base_field().clone());
94        self.ring()
95            .all_roots_powers(
96                &base_field_mat_structure
97                    .characteristic_polynomial(mat)
98                    .unwrap(),
99            )
100            .unwrap()
101    }
102
103    pub fn generalized_col_eigenspace(
104        &self,
105        mat: &Matrix<<FS::BFS as SetSignature>::Set>,
106        eigenvalue: &FS::Set,
107        k: usize,
108    ) -> FinitelyFreeSubmodule<FS::Set> {
109        let n = mat.rows();
110        assert_eq!(n, mat.cols());
111        //compute ker((M - xI)^k)
112        self.col_kernel(
113            self.nat_pow(
114                &self
115                    .add(
116                        &mat.apply_map(|x| self.ring().base_field_inclusion(x)),
117                        &self.neg(self.mul_scalar(self.ident(n), eigenvalue)),
118                    )
119                    .unwrap(),
120                &Natural::from(k),
121            )
122            .unwrap(),
123        )
124    }
125
126    pub fn generalized_row_eigenspace(
127        &self,
128        mat: &Matrix<<FS::BFS as SetSignature>::Set>,
129        eigenvalue: &FS::Set,
130        k: usize,
131    ) -> FinitelyFreeSubmodule<FS::Set> {
132        self.generalized_col_eigenspace(&mat.transpose_ref(), eigenvalue, k)
133    }
134
135    pub fn col_eigenspace(
136        &self,
137        mat: &Matrix<<FS::BFS as SetSignature>::Set>,
138        eigenvalue: &FS::Set,
139    ) -> FinitelyFreeSubmodule<FS::Set> {
140        self.generalized_col_eigenspace(mat, eigenvalue, 1)
141    }
142
143    pub fn row_eigenspace(
144        &self,
145        mat: &Matrix<<FS::BFS as SetSignature>::Set>,
146        eigenvalue: &FS::Set,
147    ) -> FinitelyFreeSubmodule<FS::Set> {
148        self.generalized_row_eigenspace(mat, eigenvalue, 1)
149    }
150
151    //return the jordan normal form F of the matrix M and a basis matrix B such that
152    // B^-1 M B = J
153    pub fn jordan_algorithm(
154        &self,
155        mat: &Matrix<<FS::BFS as SetSignature>::Set>,
156    ) -> (JordanNormalForm<FS>, Matrix<FS::Set>) {
157        let n = mat.rows();
158        assert_eq!(n, mat.cols());
159
160        let ac_field = self.ring();
161        let ac_mat_structure = self;
162
163        let ac_mat = mat.apply_map(|x| self.ring().base_field_inclusion(x));
164
165        let mut basis = vec![];
166        let mut eigenvalues = vec![]; //store (gesp_basis, eigenvalue, multiplicity)
167        for (eigenvalue, multiplicity) in self.eigenvalues_powers(mat.clone()) {
168            let eigenspace = self.generalized_col_eigenspace(mat, &eigenvalue, multiplicity);
169            debug_assert_eq!(eigenspace.rank(), multiplicity);
170            basis.append(&mut eigenspace.basis());
171            eigenvalues.push((eigenvalue, multiplicity));
172        }
173
174        //b = direct sum of generalized eigenspace
175        let gesp_basis = Matrix::construct(mat.rows(), basis.len(), |r, c| basis[c][r].clone());
176        //b^-1 * mat * b = block diagonal of generalized eigenspaces
177        let gesp_blocks_mat = ac_mat_structure
178            .mul(
179                &ac_mat_structure.inv(gesp_basis.clone()).unwrap(),
180                &ac_mat_structure.mul(&ac_mat, &gesp_basis).unwrap(),
181            )
182            .unwrap();
183
184        let mut idx_to_block = vec![];
185        for (b, (_eval, mult)) in eigenvalues.iter().enumerate() {
186            for _i in 0..*mult {
187                idx_to_block.push(b);
188            }
189        }
190
191        //extract the blocks from the block diagonal gesp_blocks_mat
192        let mut gesp_blocks = vec![];
193        let mut cum_mult = 0;
194        for (eval, mult) in eigenvalues {
195            gesp_blocks.push((
196                eval,
197                mult,
198                Matrix::construct(mult, mult, |r, c| {
199                    gesp_blocks_mat
200                        .at(cum_mult + r, cum_mult + c)
201                        .unwrap()
202                        .clone()
203                }),
204            ));
205            cum_mult += mult;
206        }
207        debug_assert_eq!(cum_mult, n);
208        drop(gesp_blocks_mat);
209
210        // Vec<(eval, multiplicity, Vec<Jordan Block>)>
211        let jnf_info = gesp_blocks
212            .into_iter()
213            .map(|(eval, m, mat_t)| {
214                debug_assert_eq!(mat_t.rows(), m);
215                debug_assert_eq!(mat_t.cols(), m);
216                //all eigenvalues of T are eval
217                //let S = T - x I so that all eigenvlues of S are zero
218                let mat_s = ac_mat_structure
219                    .add(
220                        &mat_t,
221                        &ac_mat_structure
222                            .mul_scalar(ac_mat_structure.ident(m), &ac_field.neg(&eval)),
223                    )
224                    .unwrap();
225
226                let jb_basis = {
227                    debug_assert!(m >= 1);
228
229                    let mut mat_s_pows = vec![ac_mat_structure.ident(m), mat_s.clone()];
230                    for _i in 0..(m - 1) {
231                        mat_s_pows.push(
232                            ac_mat_structure
233                                .mul(mat_s_pows.last().unwrap(), &mat_s)
234                                .unwrap(),
235                        );
236                    }
237                    debug_assert!(
238                        ac_mat_structure.equal(&ac_mat_structure.zero(m, m), &mat_s_pows[m])
239                    );
240
241                    let mat_s_pow_kers = mat_s_pows
242                        .into_iter()
243                        .map(|s_mat_pow| ac_mat_structure.col_kernel(s_mat_pow))
244                        .collect_vec();
245                    // ker(S) in ker(S^2) in ker(S^3) in ...
246
247                    let module = FinitelyFreeModuleStructure::<FS, _>::new(ac_field, m);
248                    let mut accounted = module.submodules().zero_submodule();
249
250                    let mut jordan_block_bases = vec![];
251                    for k in (0..m).rev() {
252                        //extend the basis by stuff in ker(S^{k+1}) but not in ker(S^k) and their images under S, and which are not already accounted for
253                        let ker_ext = module.submodules().span(
254                            module
255                                .submodules()
256                                .extension_basis(&mat_s_pow_kers[k], &mat_s_pow_kers[k + 1])
257                                .iter()
258                                .collect(),
259                        );
260
261                        let unaccounted_ker_ext_basis = module.submodules().extension_basis(
262                            &module
263                                .submodules()
264                                .intersect(accounted.clone(), ker_ext.clone()),
265                            &ker_ext,
266                        );
267
268                        for ukeb in unaccounted_ker_ext_basis {
269                            //one new jordan block for each ukeb
270
271                            let mut jb_basis = vec![ukeb];
272                            for _i in 0..k {
273                                let ukeb_img = ac_mat_structure
274                                    .mul(
275                                        &mat_s,
276                                        &Matrix::construct(m, 1, |r, _| {
277                                            jb_basis.last().unwrap()[r].clone()
278                                        }),
279                                    )
280                                    .unwrap();
281                                assert_eq!(ukeb_img.cols(), 1);
282                                assert_eq!(ukeb_img.rows(), m);
283                                // ac_mat_structure.pprint(&ukeb_img);
284                                jb_basis.push(ukeb_img.get_col(0));
285                            }
286
287                            accounted = module.submodules().add(
288                                accounted,
289                                module.submodules().span(jb_basis.iter().collect()),
290                            );
291                            jordan_block_bases.push(jb_basis.into_iter().rev().collect_vec());
292                        }
293                    }
294
295                    jordan_block_bases
296                };
297
298                (eval, m, jb_basis)
299            })
300            .collect_vec();
301
302        let mut jordan_blocks = vec![];
303        let mut jnf_basis_rel_gesp_basis: Vec<Matrix<FS::Set>> = vec![];
304        for (eval, mult, blocks) in jnf_info {
305            // println!("eval={:?}, mult={}", eval, mult);
306            let mut eigenblock_basis = vec![];
307            for mut block in blocks {
308                jordan_blocks.push(JordanBlock {
309                    eigenvalue: eval.clone(),
310                    blocksize: block.len(),
311                });
312                eigenblock_basis.append(&mut block);
313            }
314            jnf_basis_rel_gesp_basis.push(Matrix::join_cols(
315                mult,
316                eigenblock_basis
317                    .into_iter()
318                    .map(|col| Matrix::from_cols(vec![col]))
319                    .collect(),
320            ));
321        }
322        let jnf = JordanNormalForm {
323            field: self.ring().clone(),
324            blocks: jordan_blocks,
325        };
326        let jordan_blocks_basis = ac_mat_structure.join_diag(jnf_basis_rel_gesp_basis);
327
328        // ac_mat_structure.pprint(&jnf.matrix());
329
330        // println!("jordan_blocks_basis");
331        // ac_mat_structure.pprint(&jordan_blocks_basis);
332
333        let jnf_basis = ac_mat_structure
334            .mul(&gesp_basis, &jordan_blocks_basis)
335            .unwrap();
336        // println!("jnf_basis");
337        // ac_mat_structure.pprint(&jnf_basis);
338
339        //check that B^-1 M B = JNF
340        debug_assert!(
341            ac_mat_structure.equal(
342                &ac_mat_structure
343                    .mul(
344                        &ac_mat_structure.inv(jnf_basis.clone()).unwrap(),
345                        &ac_mat_structure.mul(&ac_mat, &jnf_basis).unwrap(),
346                    )
347                    .unwrap(),
348                &jnf.matrix()
349            )
350        );
351        // println!("jnf");
352        // ac_mat_structure.pprint(&jnf);
353
354        // todo!()
355
356        (jnf, jnf_basis)
357    }
358
359    pub fn jordan_normal_form(
360        &self,
361        mat: &Matrix<<FS::BFS as SetSignature>::Set>,
362    ) -> Matrix<FS::Set> {
363        self.jordan_algorithm(mat).0.matrix()
364    }
365
366    //TODO: find basis which make two matrices similar if one exists
367    /*
368    def similar_basis(self, other):
369    #find a basis in which self looks like other
370    #equivelently, find P such that P^-1*self*P == other
371    if type(self) == type(other) == Matrix:
372        if self.n == other.n:
373            if self.jcf_spec() == other.jcf_spec():
374                #need to find a jcf basis for self and other such that the jcf matrices are identical (including order (thats the only hard part))
375                #NOTE - by the implementation of the algorithm used, each eigen block will be consistently ordered - largest first
376                #HOWEVER, the order of the eigen block is still unknown (and an order cant be imposed in the algorithm because in general, arbitrary number things cant be ordered in a consistent way)
377                self_jcf_info = self.jcf_info()
378                other_jcf_info = other.jcf_info()
379                #rewrite these in terms of {e_val : info}
380                self_jcf_info = {info["ev"] : info for info in self_jcf_info}
381                other_jcf_info = {info["ev"] : info for info in other_jcf_info}
382                assert self_jcf_info.keys() == other_jcf_info.keys()
383                keys = list(self_jcf_info.keys()) #decide a consistent order here
384                #reorder the info
385                self_jcf_info = [self_jcf_info[ev] for ev in keys]
386                other_jcf_info = [other_jcf_info[ev] for ev in keys]
387                #now both info lists have the eigen values in the same order
388                #as well as all blocks within each eigen block being in the right order
389                self_jcf_basis = Matrix.jcf_info_to_jcf_basis(self_jcf_info)
390                other_jcf_basis = Matrix.jcf_info_to_jcf_basis(other_jcf_info)
391                return self_jcf_basis * other_jcf_basis ** -1
392            else:
393                raise Exception("Matrices are not similar so cant find a basis in which one looks like the other")
394    raise NotImplementedError
395    */
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401    use crate::isolated_algebraic::ComplexAlgebraic;
402
403    #[test]
404    fn jordan_normal_form() {
405        let mat = Matrix::from_rows(vec![
406            vec![
407                Rational::from(3),
408                Rational::from(1),
409                Rational::from(0),
410                Rational::from(1),
411            ],
412            vec![
413                Rational::from(-1),
414                Rational::from(5),
415                Rational::from(4),
416                Rational::from(1),
417            ],
418            vec![
419                Rational::from(0),
420                Rational::from(0),
421                Rational::from(2),
422                Rational::from(0),
423            ],
424            vec![
425                Rational::from(0),
426                Rational::from(0),
427                Rational::from(0),
428                Rational::from(4),
429            ],
430        ]);
431
432        mat.pprint();
433        for root in
434            MatrixStructure::new(ComplexAlgebraic::structure()).eigenvalues_list(mat.clone())
435        {
436            println!("{}", root);
437        }
438
439        let (j, b) = MatrixStructure::new(ComplexAlgebraic::structure()).jordan_algorithm(&mat);
440        println!("{:?}", j);
441        j.matrix().pprint();
442        b.pprint();
443
444        let mat = Matrix::<Rational>::from_rows(vec![vec![1, 0, 0], vec![0, 0, -1], vec![0, 2, 0]]);
445
446        mat.pprint();
447        for root in
448            MatrixStructure::new(ComplexAlgebraic::structure()).eigenvalues_list(mat.clone())
449        {
450            println!("{}", root);
451        }
452
453        let (j, b) = MatrixStructure::new(ComplexAlgebraic::structure()).jordan_algorithm(&mat);
454        println!("{:?}", j);
455        j.matrix().pprint();
456        b.pprint();
457    }
458}