// MatrixEigenClassicalJacobi
/*
------------------------------------------------------
作者   : Black Ghost
日期   : 2018-11-30
版本   : 0.0.0
------------------------------------------------------
    求解n阶对称矩阵A的全部特征值及其特征向量，经典雅可比法
理论：
    参考 李信真, 车刚明, 欧阳洁, 等. 计算方法. 西北工业大学
       出版社, 2000, pp 84-89.
------------------------------------------------------
输入   :
    A       系数矩阵
    tol     最大容许误差
    n       最大迭代步数
输出   :
    Bbar    主特征值矩阵（n阶对角矩阵）
    Rbar    主特征值所对应的特征向量（n维矩阵，第i列即对应于
            第i个特征值的特征向量）
   (err)    解出标志：false-未解出或达到步数上限；
                     true-全部解出
------------------------------------------------------
*/

package goNum

import (
	"math"
)

//判断矩阵是否对称
func isSymMatrix_MatrixEigenClassicalJacobi(A Matrix) bool {
	//是否方阵
	if A.Columns != A.Rows {
		return false
	}

	//是否对称
	for i := 0; i < A.Rows; i++ {
		for j := 0; j < A.Columns; j++ {
			if j != i {
				if A.GetFromMatrix(i, j) != A.GetFromMatrix(j, i) {
					return false
				}
			}
		}
	}

	return true
}

//取最大非对角元素
func maxElementElse_MatrixEigenClassicalJacobi(A Matrix) (int, int) {
	var p, q int
	var max float64
	for i := 0; i < A.Rows-1; i++ {
		for j := i + 1; j < A.Rows; j++ {
			c := A.GetFromMatrix(i, j)
			if math.Abs(max) < math.Abs(c) {
				max = c
				p = i
				q = j
			}
		}
	}
	return p, q
}

//计算cos(theta)及sin(theta)
func cosSinTheta_MatrixEigenClassicalJacobi(A Matrix, p, q int) (float64, float64) {
	apq := A.GetFromMatrix(p, q)
	app := A.GetFromMatrix(p, p)
	aqq := A.GetFromMatrix(q, q)

	//情况1，app-aqq=0
	if app == aqq {
		c := math.Sqrt(2.0) / 2.0
		switch {
		case apq < 0:
			return c, -1.0 * c
		case apq > 0:
			return c, c
		default:
			panic("MatrixEigenClassicalJacobi/cosSinTheta: A(p, q) = 0")
		}
	}

	//其他情况
	c := (app - aqq) / (2.0 * apq)
	d := 2.0 * apq / (app - aqq)
	//如果|apq| << |app - aqq|，用d
	if 1000.0*math.Abs(apq) < math.Abs(app-aqq) {
		tant := d / (1.0 + math.Sqrt(1.0+d*d))
		cost := 1.0 / math.Sqrt(1.0+tant*tant)
		sint := tant * cost
		return cost, sint
	}
	// 用c
	switch {
	case c > 0:
		tant := 1.0 / (c + math.Sqrt(1.0+c*c))
		cost := 1.0 / math.Sqrt(1.0+tant*tant)
		sint := tant * cost
		return cost, sint
	case c < 0:
		tant := -1.0 / (math.Abs(c) + math.Sqrt(1.0+c*c))
		cost := 1.0 / math.Sqrt(1.0+tant*tant)
		sint := tant * cost
		return cost, sint
	default:
		panic("MatrixEigenClassicalJacobi/cosSinTheta: A(p, q) = 0, c = 0")
	}
	return 0.0, 0.0
}

//非主对角元素平方之和
func sum2Else_MatrixEigenClassicalJacobi(A Matrix) float64 {
	var sum2 float64
	for i := 0; i < A.Rows-1; i++ {
		for j := i + 1; j < A.Columns; j++ {
			if i != j {
				sum2 += A.GetFromMatrix(i, j) * A.GetFromMatrix(i, j)
			}
		}
	}
	return 2.0 * sum2
}

// MatrixEigenClassicalJacobi 求解n阶对称矩阵A的全部特征值及其特征向量，经典雅可比法
func MatrixEigenClassicalJacobi(A Matrix, tol float64, n int) (Matrix, Matrix, bool) {
	/*
		求解n阶对称矩阵A的全部特征值及其特征向量，经典雅可比法
		输入   :
		    A       系数矩阵
		    tol     最大容许误差
		    n       最大迭代步数
		输出   :
		    Bbar    主特征值矩阵（n阶对角矩阵）
		    Rbar    主特征值所对应的特征向量（n维矩阵，第i列即对应于
		            第i个特征值的特征向量）
		   (err)    解出标志：false-未解出或达到步数上限；
		                     true-全部解出
	*/

	//判断A是否对称矩阵
	if !isSymMatrix_MatrixEigenClassicalJacobi(A) {
		return ZeroMatrix(A.Rows, A.Columns), ZeroMatrix(A.Rows, A.Columns), false
	}

	//第一步
	//Rbar最终为特征向量矩阵
	Rbar := IdentityE(A.Rows)
	//复制A矩阵为B，B为迭代过程中逐渐改变的矩阵，最终将成为特征值矩阵
	B := ZeroMatrix(A.Rows, A.Columns)
	Bbar := ZeroMatrix(A.Rows, A.Columns)
	for i := 0; i < len(A.Data); i++ {
		B.Data[i] = A.Data[i]
	}

	//迭代步
	for i := 0; i < n; i++ {
		//第二步，最大元素所在行列号
		p, q := maxElementElse_MatrixEigenClassicalJacobi(B)
		//第三步，计算cos(theta)及sin(theta)
		cost, sint := cosSinTheta_MatrixEigenClassicalJacobi(B, p, q)
		//第四步，计算R及Rbar，迭代B矩阵
		//R为迭代矩阵
		R := IdentityE(A.Rows)
		R.SetMatrix(p, p, cost)
		R.SetMatrix(p, q, sint)
		R.SetMatrix(q, p, -1.0*sint)
		R.SetMatrix(q, q, cost)
		Bbar = DotPruduct(DotPruduct(R, B), R.Transpose()) //A1 = RARt
		//Rbar = Rbar*Rt
		Rbar = DotPruduct(Rbar, R.Transpose())
		//第五步，判断误差
		if sum2Else_MatrixEigenClassicalJacobi(Bbar) <= tol {
			return Bbar, Rbar, true
		}
		//A = A1
		for i := 0; i < len(Bbar.Data); i++ {
			B.Data[i] = Bbar.Data[i]
		}
	}

	return ZeroMatrix(A.Rows, A.Columns), ZeroMatrix(A.Rows, A.Columns), false
}
