//
// Math.NET Numerics, part of the Math.NET Project
// http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics
//
// Copyright (c) 2009-2013 Math.NET
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
using System;
namespace IStation.Numerics.LinearAlgebra.Complex.Factorization
{
using Complex = System.Numerics.Complex;
///
/// A class which encapsulates the functionality of the singular value decomposition (SVD) for .
/// Suppose M is an m-by-n matrix whose entries are real numbers.
/// Then there exists a factorization of the form M = UΣVT where:
/// - U is an m-by-m unitary matrix;
/// - Σ is m-by-n diagonal matrix with nonnegative real numbers on the diagonal;
/// - VT denotes transpose of V, an n-by-n unitary matrix;
/// Such a factorization is called a singular-value decomposition of M. A common convention is to order the diagonal
/// entries Σ(i,i) in descending order. In this case, the diagonal matrix Σ is uniquely determined
/// by M (though the matrices U and V are not). The diagonal entries of Σ are known as the singular values of M.
///
///
/// The computation of the singular value decomposition is done at construction time.
///
internal sealed class UserSvd : Svd
{
///
/// Initializes a new instance of the class. This object will compute the
/// the singular value decomposition when the constructor is called and cache it's decomposition.
///
/// The matrix to factor.
/// Compute the singular U and VT vectors or not.
/// If is null.
///
public static UserSvd Create(Matrix matrix, bool computeVectors)
{
var nm = Math.Min(matrix.RowCount + 1, matrix.ColumnCount);
var matrixCopy = matrix.Clone();
var s = Vector.Build.SameAs(matrixCopy, nm);
var u = Matrix.Build.SameAs(matrixCopy, matrixCopy.RowCount, matrixCopy.RowCount, fullyMutable: true);
var vt = Matrix.Build.SameAs(matrixCopy, matrixCopy.ColumnCount, matrixCopy.ColumnCount, fullyMutable: true);
const int maxiter = 1000;
var e = new Complex[matrixCopy.ColumnCount];
var work = new Complex[matrixCopy.RowCount];
int i, j;
int l, lp1;
Complex t;
var ncu = matrixCopy.RowCount;
// Reduce matrixCopy to bidiagonal form, storing the diagonal elements
// In s and the super-diagonal elements in e.
var nct = Math.Min(matrixCopy.RowCount - 1, matrixCopy.ColumnCount);
var nrt = Math.Max(0, Math.Min(matrixCopy.ColumnCount - 2, matrixCopy.RowCount));
var lu = Math.Max(nct, nrt);
for (l = 0; l < lu; l++)
{
lp1 = l + 1;
if (l < nct)
{
// Compute the transformation for the l-th column and place the l-th diagonal in VectorS[l].
s[l] = Cnrm2Column(matrixCopy, matrixCopy.RowCount, l, l);
if (s[l].Magnitude != 0.0)
{
if (matrixCopy.At(l, l).Magnitude != 0.0)
{
s[l] = Csign(s[l], matrixCopy.At(l, l));
}
CscalColumn(matrixCopy, matrixCopy.RowCount, l, l, 1.0/s[l]);
matrixCopy.At(l, l, (Complex.One + matrixCopy.At(l, l)));
}
s[l] = -s[l];
}
for (j = lp1; j < matrixCopy.ColumnCount; j++)
{
if (l < nct)
{
if (s[l].Magnitude != 0.0)
{
// Apply the transformation.
t = -Cdotc(matrixCopy, matrixCopy.RowCount, l, j, l)/matrixCopy.At(l, l);
if (t != Complex.Zero)
{
for (var ii = l; ii < matrixCopy.RowCount; ii++)
{
matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (t*matrixCopy.At(ii, l)));
}
}
}
}
// Place the l-th row of matrixCopy into e for the
// Subsequent calculation of the row transformation.
e[j] = matrixCopy.At(l, j).Conjugate();
}
if (computeVectors && l < nct)
{
// Place the transformation in u for subsequent back multiplication.
for (i = l; i < matrixCopy.RowCount; i++)
{
u.At(i, l, matrixCopy.At(i, l));
}
}
if (l >= nrt)
{
continue;
}
// Compute the l-th row transformation and place the l-th super-diagonal in e(l).
var enorm = Cnrm2Vector(e, lp1);
e[l] = enorm;
if (e[l].Magnitude != 0.0)
{
if (e[lp1].Magnitude != 0.0)
{
e[l] = Csign(e[l], e[lp1]);
}
CscalVector(e, lp1, 1.0/e[l]);
e[lp1] = Complex.One + e[lp1];
}
e[l] = -e[l].Conjugate();
if (lp1 < matrixCopy.RowCount && e[l].Magnitude != 0.0)
{
// Apply the transformation.
for (i = lp1; i < matrixCopy.RowCount; i++)
{
work[i] = Complex.Zero;
}
for (j = lp1; j < matrixCopy.ColumnCount; j++)
{
if (e[j] != Complex.Zero)
{
for (var ii = lp1; ii < matrixCopy.RowCount; ii++)
{
work[ii] += e[j]*matrixCopy.At(ii, j);
}
}
}
for (j = lp1; j < matrixCopy.ColumnCount; j++)
{
var ww = (-e[j]/e[lp1]).Conjugate();
if (ww != Complex.Zero)
{
for (var ii = lp1; ii < matrixCopy.RowCount; ii++)
{
matrixCopy.At(ii, j, matrixCopy.At(ii, j) + (ww*work[ii]));
}
}
}
}
if (computeVectors)
{
// Place the transformation in v for subsequent back multiplication.
for (i = lp1; i < matrixCopy.ColumnCount; i++)
{
vt.At(i, l, e[i]);
}
}
}
// Set up the final bidiagonal matrixCopy or order m.
var m = Math.Min(matrixCopy.ColumnCount, matrixCopy.RowCount + 1);
var nctp1 = nct + 1;
var nrtp1 = nrt + 1;
if (nct < matrixCopy.ColumnCount)
{
s[nctp1 - 1] = matrixCopy.At((nctp1 - 1), (nctp1 - 1));
}
if (matrixCopy.RowCount < m)
{
s[m - 1] = Complex.Zero;
}
if (nrtp1 < m)
{
e[nrtp1 - 1] = matrixCopy.At((nrtp1 - 1), (m - 1));
}
e[m - 1] = Complex.Zero;
// If required, generate u.
if (computeVectors)
{
for (j = nctp1 - 1; j < ncu; j++)
{
for (i = 0; i < matrixCopy.RowCount; i++)
{
u.At(i, j, Complex.Zero);
}
u.At(j, j, Complex.One);
}
for (l = nct - 1; l >= 0; l--)
{
if (s[l].Magnitude != 0.0)
{
for (j = l + 1; j < ncu; j++)
{
t = -Cdotc(u, matrixCopy.RowCount, l, j, l)/u.At(l, l);
if (t != Complex.Zero)
{
for (var ii = l; ii < matrixCopy.RowCount; ii++)
{
u.At(ii, j, u.At(ii, j) + (t*u.At(ii, l)));
}
}
}
CscalColumn(u, matrixCopy.RowCount, l, l, -1.0);
u.At(l, l, Complex.One + u.At(l, l));
for (i = 0; i < l; i++)
{
u.At(i, l, Complex.Zero);
}
}
else
{
for (i = 0; i < matrixCopy.RowCount; i++)
{
u.At(i, l, Complex.Zero);
}
u.At(l, l, Complex.One);
}
}
}
// If it is required, generate v.
if (computeVectors)
{
for (l = matrixCopy.ColumnCount - 1; l >= 0; l--)
{
lp1 = l + 1;
if (l < nrt)
{
if (e[l].Magnitude != 0.0)
{
for (j = lp1; j < matrixCopy.ColumnCount; j++)
{
t = -Cdotc(vt, matrixCopy.ColumnCount, l, j, lp1)/vt.At(lp1, l);
if (t != Complex.Zero)
{
for (var ii = l; ii < matrixCopy.ColumnCount; ii++)
{
vt.At(ii, j, vt.At(ii, j) + (t*vt.At(ii, l)));
}
}
}
}
}
for (i = 0; i < matrixCopy.ColumnCount; i++)
{
vt.At(i, l, Complex.Zero);
}
vt.At(l, l, Complex.One);
}
}
// Transform s and e so that they are real .
for (i = 0; i < m; i++)
{
Complex r;
if (s[i].Magnitude != 0.0)
{
t = s[i].Magnitude;
r = s[i]/t;
s[i] = t;
if (i < m - 1)
{
e[i] = e[i]/r;
}
if (computeVectors)
{
CscalColumn(u, matrixCopy.RowCount, i, 0, r);
}
}
// Exit
if (i == m - 1)
{
break;
}
if (e[i].Magnitude != 0.0)
{
t = e[i].Magnitude;
r = t/e[i];
e[i] = t;
s[i + 1] = s[i + 1]*r;
if (computeVectors)
{
CscalColumn(vt, matrixCopy.ColumnCount, i + 1, 0, r);
}
}
}
// Main iteration loop for the singular values.
var mn = m;
var iter = 0;
while (m > 0)
{
// Quit if all the singular values have been found. If too many iterations have been performed,
// throw exception that Convergence Failed
if (iter >= maxiter)
{
throw new NonConvergenceException();
}
// This section of the program inspects for negligible elements in the s and e arrays. On
// completion the variables case and l are set as follows.
// Case = 1 if VectorS[m] and e[l-1] are negligible and l < m
// Case = 2 if VectorS[l] is negligible and l < m
// Case = 3 if e[l-1] is negligible, l < m, and VectorS[l, ..., VectorS[m] are not negligible (qr step).
// Case = 4 if e[m-1] is negligible (convergence).
double ztest;
double test;
for (l = m - 2; l >= 0; l--)
{
test = s[l].Magnitude + s[l + 1].Magnitude;
ztest = test + e[l].Magnitude;
if (ztest.AlmostEqualRelative(test, 15))
{
e[l] = Complex.Zero;
break;
}
}
int kase;
if (l == m - 2)
{
kase = 4;
}
else
{
int ls;
for (ls = m - 1; ls > l; ls--)
{
test = 0.0;
if (ls != m - 1)
{
test = test + e[ls].Magnitude;
}
if (ls != l + 1)
{
test = test + e[ls - 1].Magnitude;
}
ztest = test + s[ls].Magnitude;
if (ztest.AlmostEqualRelative(test, 15))
{
s[ls] = Complex.Zero;
break;
}
}
if (ls == l)
{
kase = 3;
}
else if (ls == m - 1)
{
kase = 1;
}
else
{
kase = 2;
l = ls;
}
}
l = l + 1;
// Perform the task indicated by case.
int k;
double f;
double cs;
double sn;
switch (kase)
{
// Deflate negligible VectorS[m].
case 1:
f = e[m - 2].Real;
e[m - 2] = Complex.Zero;
double t1;
for (var kk = l; kk < m - 1; kk++)
{
k = m - 2 - kk + l;
t1 = s[k].Real;
Srotg(ref t1, ref f, out cs, out sn);
s[k] = t1;
if (k != l)
{
f = -sn*e[k - 1].Real;
e[k - 1] = cs*e[k - 1];
}
if (computeVectors)
{
Csrot(vt, matrixCopy.ColumnCount, k, m - 1, cs, sn);
}
}
break;
// Split at negligible VectorS[l].
case 2:
f = e[l - 1].Real;
e[l - 1] = Complex.Zero;
for (k = l; k < m; k++)
{
t1 = s[k].Real;
Srotg(ref t1, ref f, out cs, out sn);
s[k] = t1;
f = -sn*e[k].Real;
e[k] = cs*e[k];
if (computeVectors)
{
Csrot(u, matrixCopy.RowCount, k, l - 1, cs, sn);
}
}
break;
// Perform one qr step.
case 3:
// Calculate the shift.
var scale = 0.0;
scale = Math.Max(scale, s[m - 1].Magnitude);
scale = Math.Max(scale, s[m - 2].Magnitude);
scale = Math.Max(scale, e[m - 2].Magnitude);
scale = Math.Max(scale, s[l].Magnitude);
scale = Math.Max(scale, e[l].Magnitude);
var sm = s[m - 1].Real/scale;
var smm1 = s[m - 2].Real/scale;
var emm1 = e[m - 2].Real/scale;
var sl = s[l].Real/scale;
var el = e[l].Real/scale;
var b = (((smm1 + sm)*(smm1 - sm)) + (emm1*emm1))/2.0;
var c = (sm*emm1)*(sm*emm1);
var shift = 0.0;
if (b != 0.0 || c != 0.0)
{
shift = Math.Sqrt((b*b) + c);
if (b < 0.0)
{
shift = -shift;
}
shift = c/(b + shift);
}
f = ((sl + sm)*(sl - sm)) + shift;
var g = sl*el;
// Chase zeros.
for (k = l; k < m - 1; k++)
{
Srotg(ref f, ref g, out cs, out sn);
if (k != l)
{
e[k - 1] = f;
}
f = (cs*s[k].Real) + (sn*e[k].Real);
e[k] = (cs*e[k]) - (sn*s[k]);
g = sn*s[k + 1].Real;
s[k + 1] = cs*s[k + 1];
if (computeVectors)
{
Csrot(vt, matrixCopy.ColumnCount, k, k + 1, cs, sn);
}
Srotg(ref f, ref g, out cs, out sn);
s[k] = f;
f = (cs*e[k].Real) + (sn*s[k + 1].Real);
s[k + 1] = (-sn*e[k]) + (cs*s[k + 1]);
g = sn*e[k + 1].Real;
e[k + 1] = cs*e[k + 1];
if (computeVectors && k < matrixCopy.RowCount)
{
Csrot(u, matrixCopy.RowCount, k, k + 1, cs, sn);
}
}
e[m - 2] = f;
iter = iter + 1;
break;
// Convergence.
case 4:
// Make the singular value positive
if (s[l].Real < 0.0)
{
s[l] = -s[l];
if (computeVectors)
{
CscalColumn(vt, matrixCopy.ColumnCount, l, 0, -1.0);
}
}
// Order the singular value.
while (l != mn - 1)
{
if (s[l].Real >= s[l + 1].Real)
{
break;
}
t = s[l];
s[l] = s[l + 1];
s[l + 1] = t;
if (computeVectors && l < matrixCopy.ColumnCount)
{
Swap(vt, matrixCopy.ColumnCount, l, l + 1);
}
if (computeVectors && l < matrixCopy.RowCount)
{
Swap(u, matrixCopy.RowCount, l, l + 1);
}
l = l + 1;
}
iter = 0;
m = m - 1;
break;
}
}
if (computeVectors)
{
vt = vt.ConjugateTranspose();
}
// Adjust the size of s if rows < columns. We are using ported copy of linpack's svd code and it uses
// a singular vector of length mRows+1 when mRows < mColumns. The last element is not used and needs to be removed.
// we should port lapack's svd routine to remove this problem.
if (matrixCopy.RowCount < matrixCopy.ColumnCount)
{
nm--;
var tmp = Vector.Build.SameAs(matrixCopy, nm);
for (i = 0; i < nm; i++)
{
tmp[i] = s[i];
}
s = tmp;
}
return new UserSvd(s, u, vt, computeVectors);
}
UserSvd(Vector s, Matrix u, Matrix vt, bool vectorsComputed)
: base(s, u, vt, vectorsComputed)
{
}
///
/// Calculates absolute value of multiplied on signum function of
///
/// Complex value z1
/// Complex value z2
/// Result multiplication of signum function and absolute value
static Complex Csign(Complex z1, Complex z2)
{
return z1.Magnitude*(z2/z2.Magnitude);
}
///
/// Interchanges two vectors and
///
/// Source matrix
/// The number of rows in
/// Column A index to swap
/// Column B index to swap
static void Swap(Matrix a, int rowCount, int columnA, int columnB)
{
for (var i = 0; i < rowCount; i++)
{
var z = a.At(i, columnA);
a.At(i, columnA, a.At(i, columnB));
a.At(i, columnB, z);
}
}
///
/// Scale column by starting from row
///
/// Source matrix
/// The number of rows in
/// Column to scale
/// Row to scale from
/// Scale value
static void CscalColumn(Matrix a, int rowCount, int column, int rowStart, Complex z)
{
for (var i = rowStart; i < rowCount; i++)
{
a.At(i, column, a.At(i, column)*z);
}
}
///
/// Scale vector by starting from index
///
/// Source vector
/// Row to scale from
/// Scale value
static void CscalVector(Complex[] a, int start, Complex z)
{
for (var i = start; i < a.Length; i++)
{
a[i] = a[i]*z;
}
}
///
/// Given the Cartesian coordinates (da, db) of a point p, these function return the parameters da, db, c, and s
/// associated with the Givens rotation that zeros the y-coordinate of the point.
///
/// Provides the x-coordinate of the point p. On exit contains the parameter r associated with the Givens rotation
/// Provides the y-coordinate of the point p. On exit contains the parameter z associated with the Givens rotation
/// Contains the parameter c associated with the Givens rotation
/// Contains the parameter s associated with the Givens rotation
/// This is equivalent to the DROTG LAPACK routine.
static void Srotg(ref double da, ref double db, out double c, out double s)
{
double r, z;
var roe = db;
var absda = Math.Abs(da);
var absdb = Math.Abs(db);
if (absda > absdb)
{
roe = da;
}
var scale = absda + absdb;
if (scale == 0.0)
{
c = 1.0;
s = 0.0;
r = 0.0;
z = 0.0;
}
else
{
var sda = da/scale;
var sdb = db/scale;
r = scale*Math.Sqrt((sda*sda) + (sdb*sdb));
if (roe < 0.0)
{
r = -r;
}
c = da/r;
s = db/r;
z = 1.0;
if (absda > absdb)
{
z = s;
}
if (absdb >= absda && c != 0.0)
{
z = 1.0/c;
}
}
da = r;
db = z;
}
///
/// Calculate Norm 2 of the column in matrix starting from row
///
/// Source matrix
/// The number of rows in
/// Column index
/// Start row index
/// Norm2 (Euclidean norm) of the column
static double Cnrm2Column(Matrix a, int rowCount, int column, int rowStart)
{
var s = 0.0;
for (var i = rowStart; i < rowCount; i++)
{
s += a.At(i, column).Magnitude*a.At(i, column).Magnitude;
}
return Math.Sqrt(s);
}
///
/// Calculate Norm 2 of the vector starting from index
///
/// Source vector
/// Start index
/// Norm2 (Euclidean norm) of the vector
static double Cnrm2Vector(Complex[] a, int rowStart)
{
var s = 0.0;
for (var i = rowStart; i < a.Length; i++)
{
s += a[i].Magnitude*a[i].Magnitude;
}
return Math.Sqrt(s);
}
///
/// Calculate dot product of and conjugating the first vector.
///
/// Source matrix
/// The number of rows in
/// Index of column A
/// Index of column B
/// Starting row index
/// Dot product value
static Complex Cdotc(Matrix a, int rowCount, int columnA, int columnB, int rowStart)
{
var z = Complex.Zero;
for (var i = rowStart; i < rowCount; i++)
{
z += a.At(i, columnA).Conjugate()*a.At(i, columnB);
}
return z;
}
///
/// Performs rotation of points in the plane. Given two vectors x and y ,
/// each vector element of these vectors is replaced as follows: x(i) = c*x(i) + s*y(i); y(i) = c*y(i) - s*x(i)
///
/// Source matrix
/// The number of rows in
/// Index of column A
/// Index of column B
/// scalar cos value
/// scalar sin value
static void Csrot(Matrix a, int rowCount, int columnA, int columnB, double c, double s)
{
for (var i = 0; i < rowCount; i++)
{
var z = (c*a.At(i, columnA)) + (s*a.At(i, columnB));
var tmp = (c*a.At(i, columnB)) - (s*a.At(i, columnA));
a.At(i, columnB, tmp);
a.At(i, columnA, z);
}
}
///
/// Solves a system of linear equations, AX = B, with A SVD factorized.
///
/// The right hand side , B.
/// The left hand side , X.
public override void Solve(Matrix input, Matrix result)
{
if (!VectorsComputed)
{
throw new InvalidOperationException("The singular vectors were not computed.");
}
// The solution X should have the same number of columns as B
if (input.ColumnCount != result.ColumnCount)
{
throw new ArgumentException("Matrix column dimensions must agree.");
}
// The dimension compatibility conditions for X = A\B require the two matrices A and B to have the same number of rows
if (U.RowCount != input.RowCount)
{
throw new ArgumentException("Matrix row dimensions must agree.");
}
// The solution X row dimension is equal to the column dimension of A
if (VT.ColumnCount != result.RowCount)
{
throw new ArgumentException("Matrix column dimensions must agree.");
}
var mn = Math.Min(U.RowCount, VT.ColumnCount);
var bn = input.ColumnCount;
var tmp = new Complex[VT.ColumnCount];
for (var k = 0; k < bn; k++)
{
for (var j = 0; j < VT.ColumnCount; j++)
{
var value = Complex.Zero;
if (j < mn)
{
for (var i = 0; i < U.RowCount; i++)
{
value += U.At(i, j).Conjugate()*input.At(i, k);
}
value /= S[j];
}
tmp[j] = value;
}
for (var j = 0; j < VT.ColumnCount; j++)
{
var value = Complex.Zero;
for (var i = 0; i < VT.ColumnCount; i++)
{
value += VT.At(i, j).Conjugate()*tmp[i];
}
result.At(j, k, value);
}
}
}
///
/// Solves a system of linear equations, Ax = b, with A SVD factorized.
///
/// The right hand side vector, b.
/// The left hand side , x.
public override void Solve(Vector input, Vector result)
{
if (!VectorsComputed)
{
throw new InvalidOperationException("The singular vectors were not computed.");
}
// Ax=b where A is an m x n matrix
// Check that b is a column vector with m entries
if (U.RowCount != input.Count)
{
throw new ArgumentException("All vectors must have the same dimensionality.");
}
// Check that x is a column vector with n entries
if (VT.ColumnCount != result.Count)
{
throw Matrix.DimensionsDontMatch(VT, result);
}
var mn = Math.Min(U.RowCount, VT.ColumnCount);
var tmp = new Complex[VT.ColumnCount];
for (var j = 0; j < VT.ColumnCount; j++)
{
var value = Complex.Zero;
if (j < mn)
{
for (var i = 0; i < U.RowCount; i++)
{
value += U.At(i, j).Conjugate()*input[i];
}
value /= S[j];
}
tmp[j] = value;
}
for (var j = 0; j < VT.ColumnCount; j++)
{
var value = Complex.Zero;
for (var i = 0; i < VT.ColumnCount; i++)
{
value += VT.At(i, j).Conjugate()*tmp[i];
}
result[j] = value;
}
}
}
}