Home Reference Source

src/math/SingularValueDecomposition.js

import { Matrix3, Vector2, Vector3 } from "three";
import { Givens } from "./Givens";
import { Schur } from "./Schur";
import { SymmetricMatrix3 } from "./SymmetricMatrix3";

/**
 * A threshold for pseudo inversions.
 *
 * @type {Number}
 * @private
 */

const PSEUDOINVERSE_THRESHOLD = 1e-1;

/**
 * The number of SVD sweeps.
 *
 * @type {Number}
 * @private
 */

const SVD_SWEEPS = 5;

/**
 * A symmetric matrix.
 *
 * @type {SymmetricMatrix3}
 * @private
 */

const sm = new SymmetricMatrix3();

/**
 * A matrix.
 *
 * @type {Matrix3}
 * @private
 */

const m = new Matrix3();

/**
 * A vector.
 *
 * @type {Vector2}
 * @private
 */

const a = new Vector2();

/**
 * A vector that holds the singular values.
 *
 * @type {Vector3}
 * @private
 */

const b = new Vector3();

/**
 * Rotates the matrix element from the first row, second column.
 *
 * @private
 * @param {SymmetricMatrix3} vtav - A symmetric matrix.
 * @param {Matrix3} v - A matrix.
 */

function rotate01(vtav, v) {

	const se = vtav.elements;
	const ve = v.elements;

	let coefficients;

	if(se[1] !== 0.0) {

		coefficients = Givens.calculateCoefficients(se[0], se[1], se[3]);

		Schur.rotateQXY(a.set(se[0], se[3]), se[1], coefficients);
		se[0] = a.x; se[3] = a.y;

		Schur.rotateXY(a.set(se[2], se[4]), coefficients);
		se[2] = a.x; se[4] = a.y;

		se[1] = 0.0;

		Schur.rotateXY(a.set(ve[0], ve[3]), coefficients);
		ve[0] = a.x; ve[3] = a.y;

		Schur.rotateXY(a.set(ve[1], ve[4]), coefficients);
		ve[1] = a.x; ve[4] = a.y;

		Schur.rotateXY(a.set(ve[2], ve[5]), coefficients);
		ve[2] = a.x; ve[5] = a.y;

	}

}

/**
 * Rotates the matrix element from the first row, third column.
 *
 * @private
 * @param {SymmetricMatrix3} vtav - A symmetric matrix.
 * @param {Matrix3} v - A matrix.
 */

function rotate02(vtav, v) {

	const se = vtav.elements;
	const ve = v.elements;

	let coefficients;

	if(se[2] !== 0.0) {

		coefficients = Givens.calculateCoefficients(se[0], se[2], se[5]);

		Schur.rotateQXY(a.set(se[0], se[5]), se[2], coefficients);
		se[0] = a.x; se[5] = a.y;

		Schur.rotateXY(a.set(se[1], se[4]), coefficients);
		se[1] = a.x; se[4] = a.y;

		se[2] = 0.0;

		Schur.rotateXY(a.set(ve[0], ve[6]), coefficients);
		ve[0] = a.x; ve[6] = a.y;

		Schur.rotateXY(a.set(ve[1], ve[7]), coefficients);
		ve[1] = a.x; ve[7] = a.y;

		Schur.rotateXY(a.set(ve[2], ve[8]), coefficients);
		ve[2] = a.x; ve[8] = a.y;

	}

}

/**
 * Rotates the matrix element from the second row, third column.
 *
 * @private
 * @param {SymmetricMatrix3} vtav - A symmetric matrix.
 * @param {Matrix3} v - A matrix.
 */

function rotate12(vtav, v) {

	const se = vtav.elements;
	const ve = v.elements;

	let coefficients;

	if(se[4] !== 0.0) {

		coefficients = Givens.calculateCoefficients(se[3], se[4], se[5]);

		Schur.rotateQXY(a.set(se[3], se[5]), se[4], coefficients);
		se[3] = a.x; se[5] = a.y;

		Schur.rotateXY(a.set(se[1], se[2]), coefficients);
		se[1] = a.x; se[2] = a.y;

		se[4] = 0.0;

		Schur.rotateXY(a.set(ve[3], ve[6]), coefficients);
		ve[3] = a.x; ve[6] = a.y;

		Schur.rotateXY(a.set(ve[4], ve[7]), coefficients);
		ve[4] = a.x; ve[7] = a.y;

		Schur.rotateXY(a.set(ve[5], ve[8]), coefficients);
		ve[5] = a.x; ve[8] = a.y;

	}

}

/**
 * Calculates the singular values.
 *
 * @private
 * @param {SymmetricMatrix3} vtav - A symmetric matrix.
 * @param {Matrix3} v - An identity matrix.
 * @return {Vector3} The singular values.
 */

function solveSymmetric(vtav, v) {

	const e = vtav.elements;

	let i;

	for(i = 0; i < SVD_SWEEPS; ++i) {

		// Rotate the upper right (lower left) triagonal.
		rotate01(vtav, v);
		rotate02(vtav, v);
		rotate12(vtav, v);

	}

	return b.set(e[0], e[3], e[5]);

}

/**
 * Computes the pseudo inverse of a given value.
 *
 * @private
 * @param {Number} x - The value to invert.
 * @return {Number} The inverted value.
 */

function invert(x) {

	const invX = (Math.abs(x) < PSEUDOINVERSE_THRESHOLD) ? 0.0 : 1.0 / x;

	return (Math.abs(invX) < PSEUDOINVERSE_THRESHOLD) ? 0.0 : invX;

}

/**
 * Calculates the pseudo inverse of v using the singular values.
 *
 * @private
 * @param {Matrix3} v - A matrix.
 * @param {Vector3} sigma - The singular values.
 * @return {Matrix3} The inverted matrix.
 */

function pseudoInverse(v, sigma) {

	const ve = v.elements;

	const v00 = ve[0], v01 = ve[3], v02 = ve[6];
	const v10 = ve[1], v11 = ve[4], v12 = ve[7];
	const v20 = ve[2], v21 = ve[5], v22 = ve[8];

	const d0 = invert(sigma.x);
	const d1 = invert(sigma.y);
	const d2 = invert(sigma.z);

	return v.set(

		// First row.
		v00 * d0 * v00 + v01 * d1 * v01 + v02 * d2 * v02,
		v00 * d0 * v10 + v01 * d1 * v11 + v02 * d2 * v12,
		v00 * d0 * v20 + v01 * d1 * v21 + v02 * d2 * v22,

		// Second row.
		v10 * d0 * v00 + v11 * d1 * v01 + v12 * d2 * v02,
		v10 * d0 * v10 + v11 * d1 * v11 + v12 * d2 * v12,
		v10 * d0 * v20 + v11 * d1 * v21 + v12 * d2 * v22,

		// Third row.
		v20 * d0 * v00 + v21 * d1 * v01 + v22 * d2 * v02,
		v20 * d0 * v10 + v21 * d1 * v11 + v22 * d2 * v12,
		v20 * d0 * v20 + v21 * d1 * v21 + v22 * d2 * v22

	);

}

/**
 * A Singular Value Decomposition solver.
 *
 * Decomposes the given linear system into the matrices U, D and V and solves
 * the equation: U D V^T x = b.
 *
 * See http://mathworld.wolfram.com/SingularValueDecomposition.html for more
 * information.
 */

export class SingularValueDecomposition {

	/**
	 * Performs the Singular Value Decomposition to solve the given linear system.
	 *
	 * @param {SymmetricMatrix3} ata - ATA. Will not be modified.
	 * @param {Vector3} atb - ATb. Will not be modified.
	 * @param {Vector3} x - A target vector to store the result in.
	 */

	static solve(ata, atb, x) {

		const sigma = solveSymmetric(sm.copy(ata), m.identity());
		const invV = pseudoInverse(m, sigma);

		x.copy(atb).applyMatrix3(invV);

	}

}