Home Reference Source

src/math/core/tools/LinearAlgebra.js

/**
 * The script is part of konpeito.
 * 
 * AUTHOR:
 *  natade (http://twitter.com/natadea)
 * 
 * LICENSE:
 *  The MIT license https://opensource.org/licenses/MIT
 */

import Polyfill from "../../tools/Polyfill.js";
import Random from "./Random.js";
import Complex from "../Complex.js";
import Matrix from "../Matrix.js";

/**
 * Collection of functions for linear algebra.
 * @ignore
 */
class LinearAlgebraTool {

	/**
	 * Tridiagonalization of symmetric matrix.
	 * - Don't support complex numbers.
	 * - P*H*P'=A
	 * - P is orthonormal matrix.
	 * - H is tridiagonal matrix.
	 * - The eigenvalues of H match the eigenvalues of A.
	 * @param {import("../Matrix.js").KMatrixInputData} mat
	 * @returns {{P: Matrix, H: Matrix}}
	 */
	static tridiagonalize(mat) {

		const A = Matrix._toMatrix(mat);
		const a = A.getNumberMatrixArray();
		const tolerance_ = 1.0e-10;

		// 参考:奥村晴彦 (1991). C言語による最新アルゴリズム事典.
		// 3重対角化の成分を取得する
		
		/**
		 * Inner product of vector x1 and vector x2.
		 * @param {Array<number>} x1
		 * @param {Array<number>} x2
		 * @param {number} [index_offset=0] - Offset of the position of the vector to be calculated.
		 * @param {number} [index_max=x1.length] - Maximum value of position of vector to be calculated (do not include this value).
		 * @returns {number} 
		 */
		const innerproduct = function(x1, x2, index_offset, index_max) {
			let y = 0;
			const ioffset = index_offset ? index_offset : 0;
			const imax = index_max ? index_max : x1.length;
			for(let i = ioffset; i < imax; i++) {
				y += x1[i] * x2[i];
			}
			return y;
		};

		/**
		 * Householder transformation.
		 * @param {Array<number>} x
		 * @param {number} [index_offset=0] - Offset of the position of the vector to be calculated.
		 * @param {number} [index_max=x.length] - Maximum value of position of vector to be calculated (do not include this value).
		 * @returns {{y1: number, v: Array<number>}} 
		 */
		const house = function(x, index_offset, index_max) {
			const ioffset = index_offset ? index_offset : 0;
			const imax = index_max ? index_max : x.length;
			// xの内積の平方根(ノルム)を計算
			let y1 = Math.sqrt(innerproduct(x, x, ioffset, imax));
			const v = [];
			if(Math.abs(y1) >= tolerance_) {
				if(x[ioffset] < 0) {
					y1 = - y1;
				}
				let t;
				for(let i = ioffset, j = 0; i < imax; i++, j++) {
					if(i === ioffset) {
						v[j] = x[i] + y1;
						t = 1.0 / Math.sqrt(v[j] * y1);
						v[j] = v[j] * t;
					}
					else {
						v[j] = x[i] * t;
					}
				}
			}
			return {
				y1: - y1,	// 鏡像の1番目の要素(y2,y3,...は0)
				v : v		// 直行する単位ベクトル vT*v = 2
			};
		};

		const n = a.length;

		/**
		 * @type {Array<number>}
		 */
		const d = []; // 対角成分
		
		/**
		 * @type {Array<number>}
		 */
		const e = []; // 隣の成分
		{
			for(let k = 0; k < n - 2; k++) {
				const v = a[k];
				d[k] = v[k];
				{
					const H = house(v, k + 1, n);
					e[k] = H.y1;
					for(let i = 0; i < H.v.length; i++) {
						v[k + 1 + i] = H.v[i];
					}
				}
				if(Math.abs(e[k]) < tolerance_) {
					continue;
				}
				for(let i = k + 1; i < n; i++) {
					let s = 0;
					for(let j = k + 1; j < i; j++) {
						s += a[j][i] * v[j];
					}
					for(let j = i; j < n; j++) {
						s += a[i][j] * v[j];
					}
					d[i] = s;
				}
				const t = innerproduct(v, d, k + 1, n) / 2.0;
				for(let i = n - 1; i > k; i--) {
					const p = v[i];
					const q = d[i] - (t * p);
					d[i] = q;
					for(let j = i; j < n; j++) {
						const r = p * d[j] + q * v[j];
						a[i][j] = a[i][j] - r;
					}
				}
			}
			if(n >= 2) {
				d[n - 2] = a[n - 2][n - 2];
				e[n - 2] = a[n - 2][n - 1];
			}
			if(n >= 1) {
				d[n - 1] = a[n - 1][n - 1];
			}
		}

		//変換P行列を求める
		for(let k = n - 1; k >= 0; k--) {
			const v = a[k];
			if(k < n - 2) {
				for(let i = k + 1; i < n; i++) {
					const w = a[i];
					const t = innerproduct(v, w, k + 1, n);
					for(let j = k + 1; j < n; j++) {
						w[j] -= t * v[j];
					}
				}
			}
			for(let i = 0; i < n; i++) {
				v[i] = 0.0;
			}
			v[k] = 1.0;
		}

		// d と e の配列を使って、三重対角行列を作成する
		const H = Matrix.createMatrixDoEachCalculation(function(row, col) {
			if(row === col) {
				return new Complex(d[row]);
			}
			else if(Math.abs(row - col) === 1) {
				return new Complex(e[Math.trunc((row + col) * 0.5)]);
			}
			else {
				return Complex.ZERO;
			}
		}, n, n);

		return {
			P : (new Matrix(a)).T(),
			H : H
		};
	}

	/**
	 * Eigendecomposition of symmetric matrix.
	 * - Don't support complex numbers.
	 * - V*D*V'=A.
	 * - V is orthonormal matrix. and columns of V are the right eigenvectors.
	 * - D is a matrix containing the eigenvalues on the diagonal component.
	 * @param {import("../Matrix.js").KMatrixInputData} mat - Symmetric matrix.
	 * @returns {{V: Matrix, D: Matrix}}
	 */
	static eig(mat) {
		const A = Matrix._toMatrix(mat);
		
		// QR法により固有値を求める
		let is_error = false;
		const tolerance_ = 1.0e-10;
		const PH = LinearAlgebraTool.tridiagonalize(A);
		const a = PH.P.getNumberMatrixArray();
		const h = PH.H.getNumberMatrixArray();
		const n = A.row_length;

		// 成分の抽出
		const d = []; // 対角成分
		const e = []; // 隣の成分
		for(let i = 0; i < n; i++) {
			d[i] = h[i][i];
			e[i] = (i === 0) ? 0.0 : h[i][i - 1];
		}

		// 参考:奥村晴彦 (1991). C言語による最新アルゴリズム事典.
		const MAX_ITER = 100;
		for(let h = n - 1; h > 0; h--) {
			let j = h;
			for(j = h;j >= 1; j--) {
				if(Math.abs(e[j]) <= (tolerance_ * (Math.abs(d[j - 1]) + Math.abs(d[j])))) {
					break;
				}
			}
			if(j == h) {
				continue;
			}
			let iter = 0;
			while(true) {
				iter++;
				if(iter > MAX_ITER) {
					is_error = true;
					break;
				}
				let w = (d[h - 1] - d[h]) / 2.0;

				/**
				 * @type {number}
				 */
				let t = e[h] * e[h];
				let s = Math.sqrt(w * w + t);
				if(w < 0) {
					s = - s;
				}
				let x = d[j] - d[h] + (t / (w + s));
				
				/**
				 * @type {number}
				 */
				let y = e[j + 1];
				for(let k = j; k < h; k++) {
					let c, s;
					if(Math.abs(x) >= Math.abs(y)) {
						t = - y / x;
						c = 1.0 / Math.sqrt(t * t + 1);
						s = t * c;
					}
					else {
						t = - x / y;
						s = 1.0 / Math.sqrt(t * t + 1);
						c = t * s;
					}
					w = d[k] - d[k + 1];
					t = (w * s + 2.0 * c * e[k + 1]) * s;
					d[k] -= t;
					d[k + 1] += t;
					if(k > j) {
						e[k] = c * e[k] - s * y;
					}
					e[k + 1] += s * (c * w - 2.0 * s * e[k + 1]);
					for(let i = 0; i < n; i++) {
						x = a[i][k];
						y = a[i][k + 1];
						a[i][k    ] = c * x - s * y;
						a[i][k + 1] = s * x + c * y;
					}
					if(k < h - 1) {
						x = e[k + 1];
						y = -s * e[k + 2];
						e[k + 2] *= c;
					}
				}
				if(Math.abs(e[h]) <= tolerance_ * (Math.abs(d[h - 1]) + Math.abs(d[h]))) {
					break;
				}
			}
			if(is_error) {
				break;
			}
		}

		// 固有値が大きいものから並べるソート
		/**
		 * @param {Matrix} V 
		 * @param {Array<number>} d 
		 */
		const vd_sort = function(V, d) {
			const len = d.length;
			const sortdata = [];
			for(let i = 0; i < len; i++) {
				sortdata[i] = {
					sigma : d[i],
					index : i
				};
			}
			/**
			 * @param {{sigma : number}} a 
			 * @param {{sigma : number}} b 
			 */
			const compare = function(a, b){
				if(a.sigma === b.sigma) {
					return 0;
				}
				return (a.sigma < b.sigma ? 1 : -1);
			};
			sortdata.sort(compare);
			const MOVE = Matrix.zeros(len);
			const ND = Matrix.zeros(len);
			for(let i = 0; i < len; i++) {
				ND.matrix_array[i][i] = new Complex(sortdata[i].sigma);
				MOVE.matrix_array[i][sortdata[i].index] = Complex.ONE;
			}
			return {
				V : V.mul(MOVE),
				D : ND
			};
		};
		const VD = vd_sort(new Matrix(a), d);
		return VD;
	}

	/**
	 * Treat matrices as vectors, make them orthonormal, and make matrices of Q and R.
	 * The method of Gram-Schmidt orthonormalization is used.
	 * @param {Matrix} mat - Square matrix.
	 * @returns {{Q: Matrix, R: Matrix, non_orthogonalized : Array<number>}}
	 */
	static doGramSchmidtOrthonormalization(mat) {
		// グラム・シュミットの正規直交化法を使用する
		// 参考:Gilbert Strang (2007). Computational Science and Engineering.

		const M = Matrix._toMatrix(mat);
		const len = M.column_length;
		const A = M.matrix_array;
		const Q_Matrix = Matrix.zeros(len);
		const R_Matrix = Matrix.zeros(len);
		const Q = Q_Matrix.matrix_array;
		const R = R_Matrix.matrix_array;
		const non_orthogonalized = [];
		const a = new Array(len);
		
		for(let col = 0; col < len; col++) {
			// i列目を抽出
			for(let row = 0; row < len; row++) {
				a[row] = A[row][col];
			}
			// 直行ベクトルを作成
			if(col > 0) {
				// Rのi列目を内積で計算する
				for(let j = 0; j < col; j++) {
					for(let k = 0; k < len; k++) {
						R[j][col] = R[j][col].add(A[k][col].dot(Q[k][j]));
					}
				}
				for(let j = 0; j < col; j++) {
					for(let k = 0; k < len; k++) {
						a[k] = a[k].sub(R[j][col].mul(Q[k][j]));
					}
				}
			}
			{
				// 正規化と距離を1にする
				for(let j = 0; j < len; j++) {
					R[col][col] = R[col][col].add(a[j].square());
				}
				R[col][col] = R[col][col].sqrt();
				if(R[col][col].isZero(1e-10)) {
					// 直行化が不可能だった列の番号をメモして、その列はゼロで埋める
					non_orthogonalized.push(col);
					for(let j = 0;j < len;j++) {
						Q[j][col] = Complex.ZERO;
					}
				}
				else {
					// ここで R[i][i] === 0 の場合、直行させたベクトルaは0であり、
					// ランク落ちしており、計算不可能である。
					// 0割りした値を、j列目のQに記録していくがInfとなる。
					for(let j = 0;j < len;j++) {
						Q[j][col] = a[j].div(R[col][col]);
					}
				}
			}
		}
		return {
			Q : Q_Matrix,
			R : R_Matrix,
			non_orthogonalized : non_orthogonalized
		};
	}
	
	/**
	 * Create orthogonal vectors for all row vectors of the matrix.
	 * - If the vector can not be found, it returns NULL.
	 * @param {import("../Matrix.js").KMatrixInputData} mat
	 * @param {number} [tolerance=1.0e-10] - Calculation tolerance of calculation.
	 * @returns {Matrix|null} An orthogonal vector.
	 */
	static createOrthogonalVector(mat, tolerance) {
		const M = new Matrix(mat);
		const column_length = M.column_length;
		const m = M.matrix_array;
		const tolerance_ = tolerance ? tolerance : 1.0e-10;
		// 正則行列をなす場合に問題となる行番号を取得
		const not_regular_rows = LinearAlgebraTool.getLinearDependenceVector(M, tolerance_);
		// 不要な行を削除する
		{
			// not_regular_rowsは昇順リストなので、後ろから消していく
			for(let i = not_regular_rows.length - 1; i >= 0; i--) {
				m.splice(not_regular_rows[i], 1);
				M.row_length--;
			}
		}
		// 追加できるベクトルの数
		const add_vectors = column_length - m.length;
		if(add_vectors <= 0) {
			return null;
		}
		// ランダムベクトル(seed値は毎回同一とする)
		const noise = new Random(0);
		let orthogonal_matrix = null;
		for(let i = 0; i < 100; i++) {
			// 直行ベクトルを作るために、いったん行と列を交換する
			// これは、グラム・シュミットの正規直交化法が列ごとに行う手法のため。
			const M2 = M.T();
			// ランダム行列を作成する
			const R = Matrix.createMatrixDoEachCalculation(function() {
				return new Complex(noise.nextGaussian());
			}, M2.row_length, add_vectors);
			// 列に追加する
			M2._concatRight(R);
			// 正規直行行列を作成する
			orthogonal_matrix = LinearAlgebraTool.doGramSchmidtOrthonormalization(M2);
			// 正しく作成できていたら完了
			if(orthogonal_matrix.non_orthogonalized.length === 0) {
				break;
			}
		}
		if(orthogonal_matrix.non_orthogonalized.length !== 0) {
			// 普通は作成できないことはないが・・・
			console.log("miss");
			return null;
		}
		// 作成した列を切り出す
		const y = new Array(add_vectors);
		const q = orthogonal_matrix.Q.matrix_array;
		for(let row = 0; row < add_vectors; row++) {
			y[row] = new Array(column_length);
			for(let col = 0; col < column_length; col++) {
				y[row][col] = q[col][column_length - add_vectors + row];
			}
		}
		return new Matrix(y);
	}

	/**
	 * Row number with the largest norm value in the specified column of the matrix.
	 * @param {import("../Matrix.js").KMatrixInputData} mat
	 * @param {number} column_index - Number of column of matrix.
	 * @param {number} [row_index_offset=0] - Offset of the position of the vector to be calculated.
	 * @param {number} [row_index_max] - Maximum value of position of vector to be calculated (do not include this value).
	 * @returns {{index: number, max: number}} Matrix row number.
	 */
	static getMaxRowNumber(mat, column_index, row_index_offset, row_index_max) {
		const M = Matrix._toMatrix(mat);
		let row_index = 0;
		let row_max = 0;
		let row = row_index_offset ? row_index_offset : 0;
		const row_imax = row_index_max ? row_index_max : M.row_length;
		// n列目で最も大きな行を取得
		for(; row < row_imax; row++) {
			const norm = M.matrix_array[row][column_index].norm;
			if(norm > row_max) {
				row_max = norm;
				row_index = row;
			}
		}
		return {
			index : row_index,
			max : row_max
		};
	}

	/**
	 * Extract linearly dependent rows when each row of matrix is a vector.
	 * @param {import("../Matrix.js").KMatrixInputData} mat
	 * @param {number} [tolerance=1.0e-10] - Calculation tolerance of calculation.
	 * @returns {Array<number>} Array of matrix row numbers in ascending order.
	 */
	static getLinearDependenceVector(mat, tolerance) {
		const M = new Matrix(mat);
		const m = M.matrix_array;
		const tolerance_ = tolerance ? Matrix._toDouble(tolerance) : 1.0e-10;
		// 確認する行番号(ここから終わった行は削除していく)
		const row_index_array = new Array(M.row_length);
		for(let i = 0; i < M.row_length; i++) {
			row_index_array[i] = i;
		}
		// ガウスの消去法を使用して、行ベクトルを抽出していく
		for(let col_target = 0; col_target < M.column_length; col_target++) {
			let row_max_index = 0;
			{
				let row_max = 0;
				let row_max_key = 0;
				// n列目で絶対値が最も大きな行を取得
				for(const row_key in row_index_array) {
					const row = row_index_array[row_key];
					const norm = m[row][col_target].norm;
					if(norm > row_max) {
						row_max = norm;
						row_max_key = parseInt(row_key, 10);
						row_max_index = row;
					}
				}
				// 大きいのが0である=その列は全て0である
				if(row_max <= tolerance_) {
					continue;
				}
				// 大きな値があった行は、リストから除去する
				row_index_array.splice(row_max_key, 1);
				if(col_target === M.column_length - 1) {
					break;
				}
			}
			// 次の列から、大きな値があった行の成分を削除
			for(const row_key in row_index_array) {
				const row = row_index_array[row_key];
				const inv = m[row][col_target].div(m[row_max_index][col_target]);
				for(let col = col_target; col < M.column_length; col++) {
					m[row][col] = m[row][col].sub(m[row_max_index][col].mul(inv));
				}
			}
		}
		return row_index_array;
	}

}

/**
 * Class for linear algebra for `Matrix` class.
 * - These methods can be used in the `Matrix` method chain.
 * - This class cannot be called directly.
 */
export default class LinearAlgebra {

	/**
	 * Inner product/Dot product.
	 * @param {import("../Matrix.js").KMatrixInputData} A
	 * @param {import("../Matrix.js").KMatrixInputData} B
	 * @param {import("../Matrix.js").KMatrixInputData} [dimension=1] - Dimension of matrix used for calculation. (1 or 2)
	 * @returns {Matrix} A・B
	 */
	static inner(A, B, dimension) {
		const M1 = Matrix._toMatrix(A);
		const M2 = Matrix._toMatrix(B);
		const x1 = M1.matrix_array;
		const x2 = M2.matrix_array;
		const dim = dimension ? Matrix._toInteger(dimension) : 1;
		if(M1.isScalar() && M2.isScalar()) {
			return new Matrix(M1.scalar.dot(M2.scalar));
		}
		if(M1.isVector() && M2.isVector()) {
			let sum = Complex.ZERO;
			for(let i = 0; i < M1.length; i++) {
				sum = sum.add(M1.getComplex(i).dot(M2.getComplex(i)));
			}
			return new Matrix(sum);
		}
		if((M1.row_length !== M2.row_length) || (M1.column_length !== M2.column_length)) {
			throw "Matrix size does not match";
		}
		if(dim === 1) {
			const y = new Array(1);
			y[0] = new Array(M1.column_length);
			for(let col = 0; col < M1.column_length; col++) {
				let sum = Complex.ZERO;
				for(let row = 0; row < M1.row_length; row++) {
					sum = sum.add(x1[row][col].dot(x2[row][col]));
				}
				y[0][col] = sum;
			}
			return new Matrix(y);
		}
		else if(dim === 2) {
			const y = new Array(M1.row_length);
			for(let row = 0; row < M1.row_length; row++) {
				let sum = Complex.ZERO;
				for(let col = 0; col < M1.column_length; col++) {
					sum = sum.add(x1[row][col].dot(x2[row][col]));
				}
				y[row] = [sum];
			}
			return new Matrix(y);
		}
		else {
			throw "dim";
		}
	}

	/**
	 * p-norm.
	 * @param {import("../Matrix.js").KMatrixInputData} mat
	 * @param {import("../Matrix.js").KMatrixInputData} [p=2]
	 * @returns {number}
	 */
	static norm(mat, p) {
		const M = Matrix._toMatrix(mat);
		const p_number = (p === undefined) ? 2 : Matrix._toDouble(p);
		if(p_number === 1) {
			// 行列の1ノルム
			const y = M.matrix_array;
			// 行ノルムを計算する
			if(M.isRow()) {
				let sum = 0.0;
				for(let col = 0; col < M.column_length; col++) {
					sum += y[0][col].norm;
				}
				return sum;
			}
			// 列ノルムを計算する
			else if(M.isColumn()) {
				let sum = 0.0;
				for(let row = 0; row < M.row_length; row++) {
					sum += y[row][0].norm;
				}
				return sum;
			}
			// 列の和の最大値
			let max = 0;
			// 列を固定して行の和を計算
			for(let col = 0; col < M.column_length; col++) {
				let sum = 0;
				for(let row = 0; row < M.row_length; row++) {
					sum += y[row][col].norm;
				}
				if(max < sum) {
					max = sum;
				}
			}
			return max;
		}
		else if(p_number === 2) {
			// 行列の2ノルム
			const y = M.matrix_array;
			// 行ノルムを計算する
			if(M.isRow()) {
				let sum = 0.0;
				for(let col = 0; col < M.column_length; col++) {
					sum += y[0][col].square().real;
				}
				return Math.sqrt(sum);
			}
			// 列ノルムを計算する
			else if(M.isColumn()) {
				let sum = 0.0;
				for(let row = 0; row < M.row_length; row++) {
					sum += y[row][0].square().real;
				}
				return Math.sqrt(sum);
			}
			return M.svd().S.diag().max().scalar.real;
		}
		else if((p_number === Number.POSITIVE_INFINITY) || (p_number === Number.NEGATIVE_INFINITY)) {
			const y = M.matrix_array;
			let compare_number = p_number === Number.POSITIVE_INFINITY ? 0 : Number.POSITIVE_INFINITY;
			const compare_func = p_number === Number.POSITIVE_INFINITY ? Math.max : Math.min;
			// 行ノルムを計算する
			if(M.isRow()) {
				for(let col = 0; col < M.column_length; col++) {
					compare_number = compare_func(compare_number, y[0][col].norm);
				}
				return compare_number;
			}
			// 列ノルムを計算する
			if(M.isColumn()) {
				for(let row = 0; row < M.row_length; row++) {
					compare_number = compare_func(compare_number, y[row][0].norm);
				}
				return compare_number;
			}
			// 行列の場合は、列の和の最大値
			compare_number = 0;
			for(let row = 0; row < M.row_length; row++) {
				let sum = 0.0;
				for(let col = 0; col < M.column_length; col++) {
					sum += y[row][col].norm;
				}
				compare_number = Math.max(compare_number, sum);
			}
			return compare_number;
		}
		else if(M.isVector()) {
			// 一般化ベクトルpノルム
			let sum = 0.0;
			for(let i = 0; i < M.length; i++) {
				sum += Math.pow(M.getComplex(i).norm, p_number);
			}
			return Math.pow(sum, 1.0 / p_number);
		}
		// 未実装
		throw "norm";
	}
	
	/**
	 * Condition number of the matrix
	 * @param {import("../Matrix.js").KMatrixInputData} mat
	 * @param {import("../Matrix.js").KMatrixInputData} [p=2]
	 * @returns {number}
	 */
	static cond(mat, p) {
		const M = Matrix._toMatrix(mat);
		const p_number = (p === undefined) ? 2 : Matrix._toInteger(p);
		if(p_number === 2) {
			// 零行列は Inf
			if(M.isZeros()) {
				return Number.POSITIVE_INFINITY;
			}
			// ベクトルは1
			if(M.isVector()) {
				return 1;
			}
			// ユニタリは1
			if(M.isUnitary()) {
				return 1;
			}
			const s = M.svd().S.diag();
			return s.max().scalar.real / s.min().scalar.real;
		}
		return M.norm(p) * M.pinv().norm(p);
	}

	/**
	 * Inverse condition number.
	 * @param {import("../Matrix.js").KMatrixInputData} mat
	 * @returns {number}
	 */
	static rcond(mat) {
		return 1.0 / LinearAlgebra.cond(Matrix._toMatrix(mat), 1);
	}

	/**
	 * Rank.
	 * @param {import("../Matrix.js").KMatrixInputData} mat
	 * @param {import("../Matrix.js").KMatrixInputData} [tolerance] - Calculation tolerance of calculation.
	 * @returns {number} rank(A)
	 */
	static rank(mat, tolerance) {
		const M = Matrix._toMatrix(mat);
		const t = tolerance !== undefined ? Matrix._toDouble(tolerance) : undefined;
		// 横が長い行列の場合
		if(M.row_length <= M.column_length) {
			return Math.min(M.row_length, M.column_length) - (LinearAlgebraTool.getLinearDependenceVector(M, t)).length;
		}
		else {
			return M.row_length - (LinearAlgebraTool.getLinearDependenceVector(M, t)).length;
		}
	}

	/**
	 * Trace of a matrix.
	 * Sum of diagonal elements.
	 * @param {import("../Matrix.js").KMatrixInputData} mat
	 * @returns {Complex}
	 */
	static trace(mat) {
		const M = Matrix._toMatrix(mat);
		const len = Math.min(M.row_length, M.column_length);
		let sum = Complex.ZERO;
		for(let i = 0; i < len; i++) {
			sum = sum.add(M.matrix_array[i][i]);
		}
		return sum;
	}

	/**
	 * Determinant.
	 * @param {import("../Matrix.js").KMatrixInputData} mat
	 * @returns {Matrix} |A|
	 */
	static det(mat) {
		const M = Matrix._toMatrix(mat);
		if(!M.isSquare()) {
			throw "not square";
		}
		const len = M.length;
		if(len < 5) {
			/**
			 * @param {Array<Array<Complex>>} x 
			 */
			const calcDet = function(x) {
				if(x.length === 2) {
					// 2次元の行列式になったら、たすき掛け計算する
					return x[0][0].mul(x[1][1]).sub(x[0][1].mul(x[1][0]));
				}
				let y = Complex.ZERO;
				for(let i = 0; i < x.length; i++) {
					// N次元の行列式を、N-1次元の行列式に分解していく

					/**
					 * @type {Array<Array<Complex>>}
					 */
					const D = [];
					const a = x[i][0];
					for(let row = 0, D_low = 0; row < x.length; row++) {
						if(i === row) {
							continue;
						}
						D[D_low] = [];
						for(let col = 1, D_col = 0; col < x.length; col++, D_col++) {
							D[D_low][D_col] = x[row][col];
						}
						D_low++;
					}
					if((i % 2) === 0) {
						y = y.add(a.mul(calcDet(D)));
					}
					else {
						y = y.sub(a.mul(calcDet(D)));
					}
				}
				return y;
			};
			return new Matrix(calcDet(M.matrix_array));
		}
		else {
			// サイズが大きい場合は、lu分解を利用する
			const lup = LinearAlgebra.lup(M);
			const exchange_count = (len - lup.P.diag().sum().scalar.real) / 2;
			// 上行列の対角線上の値を掛け算する
			let y = lup.U.diag().prod();
			if((exchange_count % 2) === 1) {
				y = y.negate();
			}
			return new Matrix(y);
		}
	}

	/**
	 * LUP decomposition.
	 * - P'*L*U=A
	 * - P is permutation matrix.
	 * - L is lower triangular matrix.
	 * - U is upper triangular matrix.
	 * @param {import("../Matrix.js").KMatrixInputData} mat - A
	 * @returns {{P: Matrix, L: Matrix, U: Matrix}} {L, U, P}
	 */
	static lup(mat) {
		const A = new Matrix(mat);
		const L = Matrix.zeros(A.row_length);
		const U = A;
		const P = Matrix.eye(A.row_length);
		const l = L.matrix_array;
		const u = U.matrix_array;
		// ガウスの消去法で連立1次方程式の未知数を求める
		//前進消去
		for(let k = 0; k < A.column_length; k++) {
			// ピポットの選択
			let pivot;
			{
				// k列目で最も大きな行を取得(k列目から調べる)
				const max_row_number = LinearAlgebraTool.getMaxRowNumber(U, k, k);
				pivot = max_row_number.index;
				if(max_row_number.max === 0.0) {
					continue;
				}
				//交換を行う
				if(k !== pivot) {
					L._exchangeRow(k, pivot);
					U._exchangeRow(k, pivot);
					P._exchangeRow(k, pivot);
				}
			}
			// 消去
			for(let row = k + 1;row < A.row_length; row++) {
				const temp = u[row][k].div(u[k][k]);
				l[row][k] = temp;
				//lの値だけ行交換が必要?
				for(let col = k; col < A.column_length; col++) {
					u[row][col] = u[row][col].sub(u[k][col].mul(temp));
				}
			}
		}
		L._resize(A.row_length, Math.min(A.row_length, A.column_length));
		U._resize(Math.min(A.row_length, A.column_length), A.column_length);
		// L の対角線に1を代入
		L._each(function(num, row, col) {
			return row === col ? Complex.ONE : num;
		});
		return {
			L : L,
			U : U,
			P : P
		};
	}

	/**
	 * LU decomposition.
	 * - L*U=A
	 * - L is lower triangular matrix.
	 * - U is upper triangular matrix.
	 * @param {import("../Matrix.js").KMatrixInputData} mat - A
	 * @returns {{L: Matrix, U: Matrix}} {L, U}
	 */
	static lu(mat) {
		const lup = LinearAlgebra.lup(mat);
		const L = lup.P.T().mul(lup.L);
		return {
			L : L,
			U : lup.U
		};
	}

	/**
	 * Solving a system of linear equations to be Ax = B
	 * @param {import("../Matrix.js").KMatrixInputData} mat - A
	 * @param {import("../Matrix.js").KMatrixInputData} number - B
	 * @returns {Matrix} x
	 * @todo 安定化のためQR分解を用いた手法に切り替える。あるいはlup分解を使用した関数に作り替える。
	 */
	static linsolve(mat, number) {
		const A = Matrix._toMatrix(mat);
		const B = Matrix._toMatrix(number);
		if(!A.isSquare()) {
			throw "Matrix size does not match";
		}
		// 連立一次方程式を解く
		const arg = B;
		if((B.row_length !== A.row_length) || (B.column_length > 1)) {
			throw "Matrix size does not match";
		}
		// 行列を準備する
		const M = new Matrix(A);
		M._concatRight(arg);
		const long_matrix_array = M.matrix_array;
		const long_length = M.column_length;
		const len = A.column_length;
		// ガウスの消去法で連立1次方程式の未知数を求める
		//前進消去
		for(let k = 0; k < (len - 1); k++) {
			//ピポットの選択
			{
				// k列目で最も大きな行を取得(k列目から調べる)
				const row_num = LinearAlgebraTool.getMaxRowNumber(M, k, k).index;
				//交換を行う
				M._exchangeRow(k, row_num);
			}
			//ピポットの正規化
			{
				const normalize_value = long_matrix_array[k][k].inv();
				for(let row = k, col = k; col < long_length; col++) {
					long_matrix_array[row][col] = long_matrix_array[row][col].mul(normalize_value);
				}
			}
			//消去
			for(let row = k + 1;row < len; row++) {
				const temp = long_matrix_array[row][k];
				for(let col = k; col < long_length; col++) {
					long_matrix_array[row][col] = long_matrix_array[row][col].sub(long_matrix_array[k][col].mul(temp));
				}
			}
		}
		//後退代入
		const y = new Array(len);
		y[len - 1] = long_matrix_array[len - 1][len].div(long_matrix_array[len - 1][len - 1]);
		for(let row = len - 2; row >= 0; row--) {
			y[row] = long_matrix_array[row][long_length - 1];
			for(let j = row + 1; j < len; j++) {
				y[row] = y[row].sub(long_matrix_array[row][j].mul(y[j]));
			}
			y[row] = y[row].div(long_matrix_array[row][row]);
		}
		const y2 = new Array(A.row_length);
		for(let row = 0; row < A.row_length; row++) {
			y2[row] = [y[row]];
		}

		return new Matrix(y2);
	}

	/**
	 * QR decomposition.
	 * - Q*R=A
	 * - Q is orthonormal matrix.
	 * - R is upper triangular matrix.
	 * @param {import("../Matrix.js").KMatrixInputData} mat - A
	 * @returns {{Q: Matrix, R: Matrix}} {Q, R}
	 */
	static qr(mat) {
		// 行列を準備する
		const M = new Matrix(mat);
		// 作成後のQとRのサイズ
		const Q_row_length = M.row_length;
		const Q_column_length = M.row_length;
		const R_row_length = M.row_length;
		const R_column_length = M.column_length;
		// 計算時の行と列のサイズ
		const dummy_size = Math.max(M.row_length, M.column_length);
		// 正方行列にする
		M._resize(dummy_size, dummy_size);
		// 正規直行化
		const orthogonal_matrix = LinearAlgebraTool.doGramSchmidtOrthonormalization(M);
		// 計算したデータを取得
		let Q_Matrix = orthogonal_matrix.Q;
		const R_Matrix = orthogonal_matrix.R;
		const non_orthogonalized = orthogonal_matrix.non_orthogonalized;

		// Qのサイズを成型する
		if(non_orthogonalized.length === M.row_length) {
			// 零行列の場合の特別処理
			Q_Matrix = Matrix.eye(M.row_length);
		}
		else if(non_orthogonalized.length !== 0) {
			// 一部、直行化できていない列があるため直行化できてない列以外を抽出
			/**
			 * @type {any}
			 */
			const map = {};
			for(let i = 0; i < non_orthogonalized.length; i++) {
				map[non_orthogonalized[i]] = 1;
			}
			const orthogonalized = [];
			for(let i = 0; i < dummy_size; i++) {
				if(map[i]) {
					continue;
				}
				const array = [];
				for(let j = 0; j < dummy_size; j++) {
					array[j] = Q_Matrix.matrix_array[j][i];
				}
				orthogonalized.push(array);
			}
			// 直行ベクトルを作成する
			const orthogonal_vector = LinearAlgebraTool.createOrthogonalVector(orthogonalized);
			// 直行化できていない列を差し替える
			for(let i = 0; i < non_orthogonalized.length; i++) {
				const q_col = non_orthogonalized[i];
				for(let j = 0; j < dummy_size; j++) {
					Q_Matrix.matrix_array[j][q_col] = orthogonal_vector.matrix_array[i][j];
				}
			}
		}
		Q_Matrix._resize(Q_row_length, Q_column_length);
		// Rのサイズを成形する
		R_Matrix._resize(R_row_length, R_column_length);
		return {
			Q : Q_Matrix,
			R : R_Matrix
		};
	}

	/**
	 * Tridiagonalization of symmetric matrix.
	 * - Don't support complex numbers.
	 * - P*H*P'=A
	 * - P is orthonormal matrix.
	 * - H is tridiagonal matrix.
	 * - The eigenvalues of H match the eigenvalues of A.
	 * @param {import("../Matrix.js").KMatrixInputData} mat - A
	 * @returns {{P: Matrix, H: Matrix}} {P, H}
	 */
	static tridiagonalize(mat) {
		const M = new Matrix(mat);
		if(!M.isSquare()) {
			throw "not square matrix";
		}
		if(!M.isSymmetric()) {
			throw "not Symmetric";
		}
		if(M.isComplex()) {
			throw "not Real Matrix";
		}
		return LinearAlgebraTool.tridiagonalize(M);
	}

	/**
	 * Eigendecomposition of symmetric matrix.
	 * - Don't support complex numbers.
	 * - V*D*V'=A.
	 * - V is orthonormal matrix. and columns of V are the right eigenvectors.
	 * - D is a matrix containing the eigenvalues on the diagonal component.
	 * @param {import("../Matrix.js").KMatrixInputData} mat - A
	 * @returns {{V: Matrix, D: Matrix}} {D, V}
	 * @todo 対称行列しか対応できていないので、対称行列ではないものはQR分解を用いた手法に切り替える予定。
	 */
	static eig(mat) {
		const M = new Matrix(mat);
		if(!M.isSquare()) {
			throw "not square matrix";
		}
		if(!M.isSymmetric()) {
			throw "not Symmetric";
		}
		if(M.isComplex()) {
			throw "not Real Matrix";
		}
		return LinearAlgebraTool.eig(M);
	}

	/**
	 * Singular Value Decomposition (SVD).
	 * - U*S*V'=A
	 * - U and V are orthonormal matrices.
	 * - S is a matrix with singular values in the diagonal.
	 * @param {import("../Matrix.js").KMatrixInputData} mat - A
	 * @returns {{U: Matrix, S: Matrix, V: Matrix}} U*S*V'=A
	 */
	static svd(mat) {
		const M = new Matrix(mat);
		if(M.isComplex()) {
			// 複素数が入っている場合は、eig関数が使用できないので非対応
			throw "Unimplemented";
		}
		const rank = LinearAlgebra.rank(M);
		// SVD分解
		// 参考:Gilbert Strang (2007). Computational Science and Engineering.
		const VD = LinearAlgebra.eig(M.T().mul(M));
		const sigma = Matrix.zeros(M.row_length, M.column_length);
		sigma._each(function(num, row, col) {
			if((row === col) && (row < rank)) {
				return VD.D.getComplex(row, row).sqrt();
			}
		});
		const s_size = Math.min(M.row_length, M.column_length);
		const sing = Matrix.createMatrixDoEachCalculation(function(row, col) {
			if(row === col) {
				const x = sigma.matrix_array[row][row];
				if(x.isZero()) {
					return Complex.ZERO;
				}
				else {
					return x.inv();
				}
			}
			else {
				return Complex.ZERO;
			}
		}, s_size);
		const V_rank = VD.V.resize(VD.V.row_length, s_size);
		const u = M.mul(V_rank).mul(sing);
		const QR = LinearAlgebra.qr(u);
		return {
			U : QR.Q,
			S : sigma,
			V : VD.V
		};
	}

	/**
	 * Inverse matrix of this matrix.
	 * @param {import("../Matrix.js").KMatrixInputData} mat - A
	 * @returns {Matrix} A^-1
	 */
	static inv(mat) {
		const X = new Matrix(mat);
		if(X.isScalar()) {
			return new Matrix(Complex.ONE.div(X.scalar));
		}
		if(!X.isSquare()) {
			throw "not square";
		}
		if(X.isDiagonal()) {
			// 対角行列の場合は、対角成分のみ逆数をとる
			const y = X.T();
			const size = Math.min(y.row_length, y.column_length);
			for(let i = 0; i < size; i++) {
				y.matrix_array[i][i] = y.matrix_array[i][i].inv();
			}
			return y;
		}
		// (ここで正規直交行列の場合なら、転置させるなど入れてもいい?判定はできないけども)
		const len = X.column_length;
		// ガウス・ジョルダン法
		// 初期値の設定
		const M = new Matrix(X);
		M._concatRight(Matrix.eye(len));
		const long_matrix_array = M.matrix_array;
		const long_length = M.column_length;

		//前進消去
		for(let k = 0; k < len; k++) {
			//ピポットの選択
			{
				// k列目で最も大きな行を取得(k列目から調べる)
				const row_num = LinearAlgebraTool.getMaxRowNumber(M, k, k).index;
				//交換を行う
				M._exchangeRow(k, row_num);
			}
			//ピポットの正規化
			{
				const normalize_value = long_matrix_array[k][k].inv();
				for(let row = k, col = k; col < long_length; col++) {
					long_matrix_array[row][col] = long_matrix_array[row][col].mul(normalize_value);
				}
			}
			//消去
			for(let row = 0;row < len; row++) {
				if(row === k) {
					continue;
				}
				const temp = long_matrix_array[row][k];
				for(let col = k; col < long_length; col++)
				{
					long_matrix_array[row][col] = long_matrix_array[row][col].sub(long_matrix_array[k][col].mul(temp));
				}
			}
		}

		const y = new Array(len);
		//右の列を抜き取る
		for(let row = 0; row < len; row++) {
			y[row] = new Array(len);
			for(let col = 0; col < len; col++) {
				y[row][col] = long_matrix_array[row][len + col];
			}
		}

		return new Matrix(y);
	}

	/**
	 * Pseudo-inverse matrix.
	 * @param {import("../Matrix.js").KMatrixInputData} mat - A
	 * @returns {Matrix} A^+
	 */
	static pinv(mat) {
		const M = new Matrix(mat);
		const USV = LinearAlgebra.svd(M);
		const U = USV.U;
		const S = USV.S;
		const V = USV.V;
		const sing = Matrix.createMatrixDoEachCalculation(function(row, col) {
			if(row === col) {
				const x = S.matrix_array[row][row];
				if(x.isZero()) {
					return Complex.ZERO;
				}
				else {
					return x.inv();
				}
			}
			else {
				return Complex.ZERO;
			}
		}, M.column_length, M.row_length);
		return V.mul(sing).mul(U.T());
	}



}