---
# System prepended metadata

title: Jaccobi Eigenvalue Algorithm
tags: [Math]

---

# Jaccobi Eigenvalue Algorithm

應用時機: 求解**實對稱矩陣**的特徵值與特徵向量，在矩陣階數小於10以下，相對於QR分解快速。

## 原理
如果矩陣 $A$ 是實對稱矩陣，可以透過一系列精心設計過的旋轉矩陣組成正交矩陣 $V$，使得 $D=V^TAV$ 為對角矩陣。該變換實則為相似變換，所以矩陣 $D$ 的對角元素為 $A$ 的特徵值。

Jacobi特徵法由 $D = R_k^T...R_1^TAR_1...R_k$ 形式組成。每個正交變換(**Jacobi rotations**)都是在旋轉平面，主要是在消滅**非對角**元素。

最後該方法收斂的證明詳見 [線代啟示錄-Jacobi 特徵值算法](https://ccjou.wordpress.com/2015/09/17/jacobi-%E7%89%B9%E5%BE%B5%E5%80%BC%E7%AE%97%E6%B3%95/)。

:::spoiler Given Rotation 如何消滅非對角元素?
詳見[Givens 旋轉於 QR 分解的應用](https://ccjou.wordpress.com/2010/02/18/givens-%E6%97%8B%E8%BD%89%E6%96%BC-qr-%E5%88%86%E8%A7%A3%E7%9A%84%E6%87%89%E7%94%A8/)

舉2維空間作為例子，令$c=cos\theta$,$s=sin\theta$。Given Rotation旨在以$x_p$為旋轉軸，消滅$x_q$，如下所述：

$$
\begin{bmatrix}
c & -s\\
s & c \\
\end{bmatrix}
\begin{bmatrix}
x_p \\
x_q \\
\end{bmatrix}
\begin{bmatrix}
r \\
0 \\
\end{bmatrix}
$$

:::

:::spoiler Jacobi Rotation 如何消滅非對角元素?
根據上述式子 $D=V^TAV$，分成兩個部分介紹：$D$ 對角矩陣 以及 $V$ 正交矩陣。 
==**(1) $D$ 對角矩陣由來**==

舉2維空間作為例子，精心設計$\theta$，使得$d_{pq}=d_{qp}=0$，求$\theta$：
  
$$
D=
\begin{bmatrix}
d_{pp} & d_{pq} \\
d_{qp} & d_{qq} \\
\end{bmatrix}
=R^TAR=
\begin{bmatrix}
cos\theta & sin\theta \\
-sin\theta & cos\theta \\
\end{bmatrix}
\begin{bmatrix}
a_{pp} & a_{pq} \\
a_{pq} & a_{qq} \\
\end{bmatrix}
\begin{bmatrix}
cos\theta & -sin\theta \\
sin\theta & cos\theta \\
\end{bmatrix}
$$
    
乘開可得，並令其為0：
 
$$
d_{pq}=cos\theta sin\theta (a_{qq}-a_{pp}) + (cos^2\theta - sin^2\theta) a_{pq}=d_{qp}=0
$$

透過倍角公式 $cos2\theta = cos^2\theta - sin^2\theta$，$sin2\theta = 2cos\theta sin\theta$ 以及 $tan2\theta = \frac{2tan\theta}{1-tan^2\theta}$ ，可得：

$$
tan2\theta = \frac{2a_{pq}}{a_{pp}-a_{qq}}=\frac{2tan\theta}{1-tan^2\theta}
$$

令其 ==$\phi=1/tan2\theta$== 以及 ==$t=tan\theta$==，

$$
\phi=\frac{ 1 - t^2 }{ 2t }=\frac{ a_{pp} - a_{qq} }{ 2a_{pq} } \implies t^2+2t\phi -1 = 0
$$

選擇絕對值較小的解 $t_{min}=sgn(\phi)(-|\phi|+\sqrt{\phi^2+1})$，為了避免兩個相近大小的數相減造成[catastrophic cancellation](https://zh.wikipedia.org/wiki/%E7%81%BE%E9%9A%BE%E6%80%A7%E6%8A%B5%E6%B6%88)，對 $t_{min}$ 乘上 $\frac{|\phi+\sqrt{\phi^2+1}|}{|\phi+\sqrt{\phi^2+1}|}$ ，最後得出：

<center>
    
==$t=\frac{sgn(\phi)}{|\phi|+\sqrt{\phi^2+1}}$==

</center>

但如果 $\phi >> 1$ ，恐會讓 $\phi^2$ 造成overflow，因此在該狀況下讓 ==$t=\frac{1}{2\phi}$==。

另外Jacobi特徵法的參數值如下，
<center>
    
==$c=cos\theta=\frac{1}{\sqrt{1+t^2}}$== , ==$s=sin\theta=t \cdot c$==

</center>
    
當$2\theta = arctan\frac{2a_{pq}}{a_{qq}-a_{pp}}$時，使得$d_{pq}=d_{qp}=0$。證明了透過jacobi rotation二階實對稱矩陣可正交對角化。

**擴展至n維狀況**:
正交相似變換定義為$A'=R_{pq}^T \cdot A \cdot R_{pq}$，其中旋轉矩陣如下所示：


$$
R_{pq}=
\begin{bmatrix}
1 &   &   &   &  &    &  \\
  &...&   &   &  &    &  \\
  &   & c &...&-s&    &  \\
  &   & : & 1 & :&    &  \\
  &   & s &...& c&    &  \\
  &   &   &   &  &... &  \\
  &   &   &   &  &    & 1\\
\end{bmatrix}
$$
    

上述操作可知 $R_{pq}^T \cdot A$ **改變**了 $A$ 的==p,q列== 以及$A \cdot R_{pq}$則**改變**了 $A$的==p,q行==。 如下圖所示，黃線處為變動項：

<center>

<img width=45% src=https://hackmd.io/_uploads/HkrP3wEQj.png>

</center>


乘開後便有以下規律:

<center>

\begin{cases}
  a'_{rp}=ca_{rp}-sa_{rq}, & \text{$r \neq p, r\neq q$} \\
  a'_{rq}=ca_{rq}+sa_{rp}, & \text{$r \neq p, r\neq q$} \\
  a'_{pp}=c^2a_{pp}+s^2a_{qq}-2sc\cdot a_{pq} \\
  a'_{qq}=s^2a_{pp}+c^2a_{qq}+2sc\cdot a_{pq} \\
  a'_{pq} = (c^2-s^2)a_{pq} + sc(a_{pp}-a_{qq})=0
\end{cases}

</center>

整理成:

$$
\begin{bmatrix}
a'_{rp}\\
a'_{rq}\\
\end{bmatrix}=
\begin{bmatrix}
c & -s\\
s &  c\\
\end{bmatrix}
\begin{bmatrix}
a_{rp}\\
a_{rq}\\
\end{bmatrix},\quad r \neq p, r\neq q \\
a'_{pp}=a_{pp}-t \cdot a_{pq} \\
a'_{qq}=a_{qq}+t \cdot a_{pq} \\
a'_{pq}=0
$$

==**(2) $V$ 正交矩陣由來**==
從上述迭代式，可知正交矩陣 $V$ 之形式：
$$
A=VDV^T \Longleftrightarrow D=V^TAV=(R_k^T \cdots R_1^T)A(R_1 \cdots R_k)
$$
代表 $V=I\cdot R_1\cdots R_k$，公式整理如下：

$$
\begin{bmatrix}
v'_{rp}\\
v'_{rq}\\
\end{bmatrix}=
\begin{bmatrix}
c & -s\\
s &  c\\
\end{bmatrix}
\begin{bmatrix}
v_{rp}\\
v_{rq}\\
\end{bmatrix}
$$
:::

  
::: spoiler 那些Jacobi Rotations以C語言實現的坑

因為$A$矩陣為實對稱矩陣，實質上也只需要尋訪上三角$n(n-1)/2$個。而因為ｃ語言實現，故旋轉就分成三種case。

<center>
<img width=45% src=https://hackmd.io/_uploads/HJxTlgBXi.png>
</center>

```csharp
//1. Case of rotations 0 ≤ j< p.
for (int j = 0; j<ip; j++)
    Rotate(ref A, j, ip, j, iq, c, s);

//2. Case of rotations p+1 ≤ j< q.
for (int j = ip+1; j<iq; j++)
    Rotate(ref A, ip, j, j, iq, c, s);

//3. Case of rotations q+1 ≤ j< n.
for (int j = iq+1; j<n; j++)
    Rotate(ref A, ip, j, iq, j, c, s);

void Rotate(double[,] A, int r1, int c1, int r2, int c2, double c, double s)
{
    double num = A[r1, c1];
    double num2 = A[r2, c2];
    A[r1, c1] = num * c - num2 * s;
    A[r2, c2] = num * s + num2 * c;
}
```
:::

::: spoiler Cyclic Jacobi method 代碼
```csharp=
//From Numerical Recipes in C. Second Edition
public static void JacobiEigenvalueAlgorithm(
    double[,] A,
    ref double[] W,
    ref double[,] V,
    out int totalitera)
{
    int n = A.GetLength(0);

    totalitera = 0;
    int Maxitera = n * n * 30;

    for (int i = 0; i < n; i++)
    {
        //Initialize V to the identity matrix
        if (V != null)
            V[i, i] = 1.0;
        //Initialize W to the diagonal of A matrix
        W[i] = A[i, i];
    }

    double Magnitude_OffDiag, h, t, c, s, diagAddtemr;
    double threshold;
    double sum_OffDiag;
    for (int i = 0; i<Maxitera; i++)
    {
        //Sum off-diagonal elements.
        sum_OffDiag=0;
        for (int ip = 0; ip<n-1; ip++)
            for (int iq = ip+1; iq<n; iq++)
                sum_OffDiag +=Math.Abs(A[ip, iq]);

        // if the sum of off-diagonal elements is zeros,
        //  then it reaches convergence.
        if (sum_OffDiag <2.2204460492503131E-16)
            break;

        threshold = (i<4) ? 0.2*sum_OffDiag/(n*n) : 0;
        for (int ip = 0; ip<n-1; ip++)
        {
            for (int iq = ip+1; iq<n; iq++)
            {
                Magnitude_OffDiag=100.0*Math.Abs(A[ip, iq]);
                //After four sweeps,
                //skip the rotation 
                //if the off-diagonal element is small 
                //(W[ip] and W[iq] >>  100*|A[ip,iq]| ).
                if (i>3
                    && (Math.Abs(W[ip])+Magnitude_OffDiag)==Math.Abs(W[ip])
                    && (Math.Abs(W[iq])+Magnitude_OffDiag)==Math.Abs(W[iq])
                    )
                {
                    A[ip, iq]=0;
                }
                else if (Math.Abs(A[ip, iq])>threshold)
                {
                    h=W[iq]-W[ip];
                    if ((Math.Abs(h)+Magnitude_OffDiag)==Math.Abs(h))
                        t=A[ip, iq]/h;
                    else
                    {
                        //1. phi = (A[p,p] -A[q,q]) / (2*A[p,q]);
                        //2. t = tan(theta) =  sgn(phi) /(|phi|+sqrt(phi^2 +1));
                        double phi = 0.5 * h/A[ip, iq];
                        t = 1.0/(Math.Abs(phi)+Math.Sqrt(1.0+phi*phi));
                        if (phi<0)
                            t=-t;
                    }
                    //3. c= cos(theta) = 1 / sqrt(1 + tan(theta)^2 )
                    //4. s= sin(theta) = tan(theta) * cos(theta)
                    c=1.0/Math.Sqrt(1+t*t);
                    s=t*c;

                    // Follow eq(11.1.14)
                    diagAddtemr=t*A[ip, iq];
                    W[ip] -= diagAddtemr;
                    W[iq] += diagAddtemr;

                    A[ip, iq]=0;
                    //Case of rotations 0 ≤ j< p.
                    for (int j = 0; j<ip; j++)
                        Rotate(ref A, j, ip, j, iq, c, s);

                    //Case of rotations p+1 ≤ j< q.
                    for (int j = ip+1; j<iq; j++)
                        Rotate(ref A, ip, j, j, iq, c, s);

                    //Case of rotations q+1 ≤ j< n.
                    for (int j = iq+1; j<n; j++)
                        Rotate(ref A, ip, j, iq, j, c, s);

                    if (V != null)
                    {
                        for (int j = 0; j<n; j++)
                            Rotate(ref V, j, ip, j, iq, c, s);
                    }

                    totalitera++;
                }
            }
        }
    }

    //sorting by descent via bubble sort
    for (int j = 0; j < n - 1; j++)
    {
        int key = j;
        for (int i = j + 1; i < n; i++)
        {
            if (Math.Abs(W[key]) < Math.Abs(W[i]))
                key = i;

        }

        if (j != key)
        {
            swap(ref W[key], ref W[j]);
            if (V!=null)
            {
                for (int i = 0; i < n; i++)
                    swap(ref V[i, key], ref V[i, j]);
            }
        }
    }
}
private static void Rotate(
    ref double[,] A,
    int row1, int col1,
    int row2, int col2,
    double c, double s)
{
    double num = A[row1, col1];
    double num2 = A[row2, col2];
    A[row1, col1] = num * c - num2 * s;
    A[row2, col2] = num * s + num2 * c;
}
```
:::

## Reference:

1. Numerical Recipes in C , Second Edition (1992), Ch11.
2. Jacobi eigenvalue algorithm, WIKI
3. [線代啟示錄-Jacobi 特徵值算法](https://ccjou.wordpress.com/2015/09/17/jacobi-%E7%89%B9%E5%BE%B5%E5%80%BC%E7%AE%97%E6%B3%95/)
4. [Givens 旋轉於 QR 分解的應用](https://ccjou.wordpress.com/2010/02/18/givens-%E6%97%8B%E8%BD%89%E6%96%BC-qr-%E5%88%86%E8%A7%A3%E7%9A%84%E6%87%89%E7%94%A8/)

###### tags: `Math`