# Solving an eigenvalue probem for a hermitian matrix with Sympy

## Question:

I’m trying to find the eigenvalues of a Hermitian matrix using Sympy. We know from the linear algebra that the eigenvalues should be real, but the output of Sympy always contains infinitesimal imaginary part. I know this might not look a serious problem at first sight but when we trying to solve an eigenvalue symbolically, it makes lots of troubles.

``````from sympy import *
from sympy import init_printing
init_printing(use_latex=True)
t12=symbols('t12')
t13=symbols('t13')
t16=symbols('t16')
t34=symbols('t34')
x=symbols('x')
y=symbols('y')
kx=symbols('kx')
ky=symbols('ky')
H0=Matrix([[0 for i in range(6)] for j in range(6)])

H0[0,1]=-t12
H0[0,2]=-t13
H0[0,5]=-t16*exp(-4*pi*I/3)
H0[1,3]=-t13
H0[1,4]=-t16*exp(4*pi*I/3)
H0[2,3]=-t34*exp(-4*pi*I/3)
H0[2,4]=-t13
H0[3,5]=-t13
H0[4,5]=-t12

H0[1,0]=-t12
H0[2,0]=-t13
H0[3,1]=-t13
H0[3,2]=-t34*exp(4*pi*I/3)
H0[4,1]=-t16*exp(-4*pi*I/3)
H0[4,2]=-t13
H0[5,0]=-t16*exp(4*pi*I/3)
H0[5,3]=-t13
H0[5,4]=-t12

eig=H0.evalf(5).eigenvals()
``````

Any help would be appreciated.

``````In [6]: H0
Out[6]:
⎡                                                                             2⋅ⅈ⋅π⎤
⎢                                                                             ─────⎥
⎢                                                                               3  ⎥
⎢      0           -t₁₂          -t₁₃            0             0        -t₁₆⋅ℯ     ⎥
⎢                                                                                  ⎥
⎢                                                              -2⋅ⅈ⋅π              ⎥
⎢                                                              ───────             ⎥
⎢                                                                 3                ⎥
⎢    -t₁₂            0             0           -t₁₃      -t₁₆⋅ℯ              0     ⎥
⎢                                                                                  ⎥
⎢                                                 2⋅ⅈ⋅π                            ⎥
⎢                                                 ─────                            ⎥
⎢                                                   3                              ⎥
⎢    -t₁₃            0             0        -t₃₄⋅ℯ           -t₁₃            0     ⎥
⎢                                                                                  ⎥
⎢                                  -2⋅ⅈ⋅π                                          ⎥
⎢                                  ───────                                         ⎥
⎢                                     3                                            ⎥
⎢      0           -t₁₃      -t₃₄⋅ℯ              0             0           -t₁₃    ⎥
⎢                                                                                  ⎥
⎢                     2⋅ⅈ⋅π                                                        ⎥
⎢                     ─────                                                        ⎥
⎢                       3                                                          ⎥
⎢      0        -t₁₆⋅ℯ           -t₁₃            0             0           -t₁₂    ⎥
⎢                                                                                  ⎥
⎢      -2⋅ⅈ⋅π                                                                      ⎥
⎢      ───────                                                                     ⎥
⎢         3                                                                        ⎥
⎣-t₁₆⋅ℯ              0             0           -t₁₃          -t₁₂            0     ⎦
``````

The eigenvalues of a matrix are the roots of its characteristic polynomial. There are limitations in the way that the roots of a polynomial can be expressed symbolically. I cannot reproduce what you say with the code shown because I get:

``````In [3]: H0.eigenvals()
---------------------------------------------------------------------------
MatrixError: It is not always possible to express the eigenvalues of a matrix of size 5x5 or higher in radicals. We have CRootOf, but domains other than the rationals are not currently supported. If there are no symbols in the matrix, it should still be possible to compute numeric approximations of the eigenvalues using M.evalf().eigenvals() or M.charpoly().nroots().
``````

That error message is

It is not always possible to express the eigenvalues of a matrix of size 5×5 or higher in radicals. We have CRootOf, but domains other than the rationals are not currently supported. If there are no symbols in the matrix, it should still be possible to compute numeric approximations of the eigenvalues using M.evalf().eigenvals() or M.charpoly().nroots().

The reason for this is that you have a 6×6 matrix and its characteristic polynomial is of degree 6 and so the eigenvalues are the roots of a polynomial of degree 6. Polynomials of degree 5 or more might not be solvable in radicals due to the Abel-Ruffini theorem:
https://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_theorem

My guess is that you see this because you are substituting values for the symbols and then computing the eigenvalues for example like this:

``````In [18]: H0.subs({t12:1, t13:2, t16:3, t34:4}).evalf().eigenvals()
Out[18]:
{-6.21170948161553 + 2.04287290941429e-64⋅ⅈ: 1, -3.60555127546399 - 1.30254464798347e-64⋅ⅈ: 1, -0.6
43945118785511 + 1.4910290487412e-64⋅ⅈ: 1, 0.643945118785511 - 2.93808243220787e-64⋅ⅈ: 1, 3.6055512
7546399 + 4.04818363587083e-64⋅ⅈ: 1, 6.21170948161553 - 3.2972673293568e-64⋅ⅈ: 1}
``````

This is because your matrix has complex numbers in it and so the roots are calculated using complex floating point and then will have small imaginary parts from rounding error.

There are several ways around this that may or may not be applicable in different examples. One way is to first compute the characteristic polynomial exactly (without floats) and only then ask to find the roots:

``````In [22]: l = symbols('lambda')

In [23]: p = H0.subs({t12:1, t13:2, t16:3, t34:4}).charpoly(l).as_expr()

In [24]: p
Out[24]:
6       4    2 ⎛             2/3      3 ____⎞                2/3      3 ____
λ  - 52⋅λ  + λ ⋅⎝434 - 89⋅(-1)    + 89⋅╲╱ -1 ⎠ - 224 - 16⋅(-1)    + 16⋅╲╱ -1

In [25]: nroots(p)
Out[25]:
[-6.21170948161553, -3.60555127546399, -0.643945118785511, 0.643945118785511, 3.60555127546399, 6.2
1170948161553]
``````

Here `nroots` can figure out that the roots are exactly real because the coefficients of the polynomial are real. Exact arithmetic was needed to get to this point in order to determine that the coefficients are real even though their expressions involve complex numbers. You could also compute the characteristic polynomial and only substitute the values later since getting the polynomial is the slowest part of this.

Given a polynomial with (exact) rational coefficients SymPy can always represent its roots using RootOf. Yours has algebraic rather than rational coefficients but that means that it can still potentially be factorised with the right arguments:

``````In [26]: pf = p.factor(extension=True)

In [27]: pf
Out[27]:
⎛ 2     ⎞ ⎛ 4       2     ⎞
⎝λ  - 13⎠⋅⎝λ  - 39⋅λ  + 16⎠
``````

Here `extension=True` means to construct an extension field that can contain the coefficients of the polynomial and then factorise in the ring of polynomials over that field. In this case that turns out to be enough to factorise the polynomial. Now we have only polynomials with rational coefficients and so all roots can be expressed using RootOf:

``````In [28]: real_roots(pf)
Out[28]:
⎡       ⎛ 4       2        ⎞               ⎛ 4       2        ⎞         ⎛ 4       2        ⎞
⎣CRootOf⎝x  - 39⋅x  + 16, 0⎠, -√13, CRootOf⎝x  - 39⋅x  + 16, 1⎠, CRootOf⎝x  - 39⋅x  + 16, 2⎠, √13,

⎛ 4       2        ⎞⎤
CRootOf⎝x  - 39⋅x  + 16, 3⎠⎦

In [29]: [r.n() for r in real_roots(pf)]
Out[29]:
[-6.21170948161553, -3.60555127546399, -0.643945118785511, 0.643945118785511, 3.60555127546399, 6.2
1170948161553]
``````

The factorisation above might be possible without substituting values for the symbols like:

``````In [33]: p = H0.charpoly(l).as_expr()

In [34]: p
Out[34]:
6    4 ⎛       2        2        2      2⎞    2 ⎛   4        2    2       2/3    2    2   3 ____
λ  + λ ⋅⎝- 2⋅t₁₂  - 4⋅t₁₃  - 2⋅t₁₆  - t₃₄ ⎠ + λ ⋅⎝t₁₂  + 4⋅t₁₂ ⋅t₁₃  - (-1)   ⋅t₁₂ ⋅t₁₆  + ╲╱ -1 ⋅t

2    2        2    2         2/3        2         3 ____        2             2/3        2
₁₂ ⋅t₁₆  + 2⋅t₁₂ ⋅t₃₄  - 4⋅(-1)   ⋅t₁₂⋅t₁₃ ⋅t₁₆ + 4⋅╲╱ -1 ⋅t₁₂⋅t₁₃ ⋅t₁₆ - 2⋅(-1)   ⋅t₁₂⋅t₁₃ ⋅t₃₄ +

3 ____        2            4        2    2        2              4        2    2⎞      4    2
2⋅╲╱ -1 ⋅t₁₂⋅t₁₃ ⋅t₃₄ + 4⋅t₁₃  + 4⋅t₁₃ ⋅t₁₆  - 4⋅t₁₃ ⋅t₁₆⋅t₃₄ + t₁₆  + 2⋅t₁₆ ⋅t₃₄ ⎠ - t₁₂ ⋅t₃₄  - 2

3 ____    3    2             2/3    3    2            2    4         2/3    2    2             3 _
⋅╲╱ -1 ⋅t₁₂ ⋅t₁₃ ⋅t₃₄ + 2⋅(-1)   ⋅t₁₂ ⋅t₁₃ ⋅t₃₄ - 4⋅t₁₂ ⋅t₁₃  - 2⋅(-1)   ⋅t₁₂ ⋅t₁₃ ⋅t₁₆⋅t₃₄ + 2⋅╲╱

___    2    2           3 ____    2    2    2       2/3    2    2    2     3 ____        4
-1 ⋅t₁₂ ⋅t₁₃ ⋅t₁₆⋅t₃₄ - ╲╱ -1 ⋅t₁₂ ⋅t₁₆ ⋅t₃₄  + (-1)   ⋅t₁₂ ⋅t₁₆ ⋅t₃₄  - 4⋅╲╱ -1 ⋅t₁₂⋅t₁₃ ⋅t₁₆ + 4⋅

2/3        4             2/3        2    2         3 ____        2    2            4    2
(-1)   ⋅t₁₂⋅t₁₃ ⋅t₁₆ - 2⋅(-1)   ⋅t₁₂⋅t₁₃ ⋅t₁₆ ⋅t₃₄ + 2⋅╲╱ -1 ⋅t₁₂⋅t₁₃ ⋅t₁₆ ⋅t₃₄ - 4⋅t₁₃ ⋅t₁₆  + 4⋅t

2    3          4    2
₁₃ ⋅t₁₆ ⋅t₃₄ - t₁₆ ⋅t₃₄

In [35]: %time pf = p.factor(extension=True)
...
``````

This last step is slow though so I don’t know how long it will take or even if it will succeed because it is not guaranteed that the polynomial factorises. If it does succeed and gives a product of a quadratic and a quartic then it should be possible to express the roots symbolically in radicals without substituting values for the symbols. Those formulae might also have the problem of small imaginary parts due to casus irreducibilus though:
https://en.wikipedia.org/wiki/Casus_irreducibilis

EDIT: I see now that `H0.evalf().eigenvals()` does actually return some complicated expressions for the roots involving the symbols and lots of small floating point numbers. This happens for all of the reasons that I said anyway because the matrix has complex numbers in it after calling evalf.

Also here is a way to get an exact representation of the eigenvalues. First in your code use a symbol `a` instead of `exp(4*pi*I/3)` and likewise `1/a` for `exp(-4*pi*I/3)`. Then you can find the characteristic polynomial and factorise it:

``````In [78]: p = H0.charpoly(l).as_expr().factor().as_numer_denom()[0]

In [79]: p
Out[79]:
⎛   2              2        2        2          ⎞ ⎛   2  2              2        2        2
-⎝- a ⋅t₁₂⋅t₁₆ - a⋅λ  + a⋅t₁₂  + a⋅t₁₆  - t₁₂⋅t₁₆⎠⋅⎝- a ⋅λ ⋅t₁₂⋅t₁₆ - 2⋅a ⋅t₁₂⋅t₁₃ ⋅t₃₄ + a ⋅t₁₂⋅t₁

2      4      2    2        2    2      2    2      2    2        2    2          4          2
₆⋅t₃₄  + a⋅λ  - a⋅λ ⋅t₁₂  - 4⋅a⋅λ ⋅t₁₃  - a⋅λ ⋅t₁₆  - a⋅λ ⋅t₃₄  + a⋅t₁₂ ⋅t₃₄  + 4⋅a⋅t₁₃  - 4⋅a⋅t₁₃

2    2    2                    2                  2⎞
⋅t₁₆⋅t₃₄ + a⋅t₁₆ ⋅t₃₄  - λ ⋅t₁₂⋅t₁₆ - 2⋅t₁₂⋅t₁₃ ⋅t₃₄ + t₁₂⋅t₁₆⋅t₃₄ ⎠
``````

You can use `roots(p, l)` to get some fairly complicated expressions for the roots. We want to substitute `a` back though and that might make it possible to factorise:

``````In [96]: pf = Mul(*(f.factor(extension=True).collect(l) for f in Mul.make_args(p.subs(a, exp(4*pi*I/3
...: ))))).as_independent(l)[1]

In [97]: pf
Out[97]:
⎛   2      2                2⎞ ⎛ 4    2 ⎛     2                  2      2      2⎞      2    2
⎝- λ  + t₁₂  + t₁₂⋅t₁₆ + t₁₆ ⎠⋅⎝λ  + λ ⋅⎝- t₁₂  + t₁₂⋅t₁₆ - 4⋅t₁₃  - t₁₆  - t₃₄ ⎠ + t₁₂ ⋅t₃₄  + 2⋅t

2                  2        4        2              2    2⎞
₁₂⋅t₁₃ ⋅t₃₄ - t₁₂⋅t₁₆⋅t₃₄  + 4⋅t₁₃  - 4⋅t₁₃ ⋅t₁₆⋅t₃₄ + t₁₆ ⋅t₃₄ ⎠
``````

These can give nicer expressions for the roots because we’ve eliminated all of the complex numbers:

``````In [98]: r1, r2, r3, r4, r5, r6 = roots(pf, l)

In [99]: r1
Out[99]:
_______________________
╱    2                2
-╲╱  t₁₂  + t₁₂⋅t₁₆ + t₁₆

In [100]: r2
Out[100]:
_______________________
╱    2                2
╲╱  t₁₂  + t₁₂⋅t₁₆ + t₁₆

In [101]: r3
Out[101]:
____________________________________________________________________________________________
╱                                            ________________________________________________
╱     2                         2      2     ╱    4        3            2    2        2    2
╱   t₁₂    t₁₂⋅t₁₆        2   t₁₆    t₃₄    ╲╱  t₁₂  - 2⋅t₁₂ ⋅t₁₆ + 8⋅t₁₂ ⋅t₁₃  + 3⋅t₁₂ ⋅t₁₆  -
-  ╱    ──── - ─────── + 2⋅t₁₃  + ──── + ──── - ───────────────────────────────────────────────────
╲╱      2        2                2      2

___________________________________________________________________________________________________
___________________________________________________________________________________________________
2    2            2                2                3                2        2    2
2⋅t₁₂ ⋅t₃₄  - 8⋅t₁₂⋅t₁₃ ⋅t₁₆ - 8⋅t₁₂⋅t₁₃ ⋅t₃₄ - 2⋅t₁₂⋅t₁₆  + 2⋅t₁₂⋅t₁₆⋅t₃₄  + 8⋅t₁₃ ⋅t₁₆  + 16⋅t₁₃
───────────────────────────────────────────────────────────────────────────────────────────────────
2

_____________________________________________________
____________________________________________________
2                2    2      4        2    2      4
⋅t₁₆⋅t₃₄ + 8⋅t₁₃ ⋅t₃₄  + t₁₆  - 2⋅t₁₆ ⋅t₃₄  + t₃₄
────────────────────────────────────────────────────
``````

Be aware that substituting values for the symbols into these formulae might again give small imaginary parts due to casus irreducibilus.

Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.