//
// 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using IStation.Numerics.Distributions;
using IStation.Numerics.LinearAlgebra.Solvers;
namespace IStation.Numerics.LinearAlgebra.Complex32.Solvers
{
///
/// A Multiple-Lanczos Bi-Conjugate Gradient stabilized iterative matrix solver.
///
///
///
/// The Multiple-Lanczos Bi-Conjugate Gradient stabilized (ML(k)-BiCGStab) solver is an 'improvement'
/// of the standard BiCgStab solver.
///
///
/// The algorithm was taken from:
/// ML(k)BiCGSTAB: A BiCGSTAB variant based on multiple Lanczos starting vectors
///
/// Man-Chung Yeung and Tony F. Chan
///
/// SIAM Journal of Scientific Computing
///
/// Volume 21, Number 4, pp. 1263 - 1290
///
///
/// The example code below provides an indication of the possible use of the
/// solver.
///
///
public sealed class MlkBiCgStab : IIterativeSolver
{
///
/// The default number of starting vectors.
///
const int DefaultNumberOfStartingVectors = 50;
///
/// The collection of starting vectors which are used as the basis for the Krylov sub-space.
///
IList>? _startingVectors = null;
///
/// The number of starting vectors used by the algorithm
///
int _numberOfStartingVectors = DefaultNumberOfStartingVectors;
///
/// Gets or sets the number of starting vectors.
///
///
/// Must be larger than 1 and smaller than the number of variables in the matrix that
/// for which this solver will be used.
///
public int NumberOfStartingVectors
{
[DebuggerStepThrough]
get => _numberOfStartingVectors;
[DebuggerStepThrough]
set
{
if (value <= 1)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
_numberOfStartingVectors = value;
}
}
///
/// Resets the number of starting vectors to the default value.
///
public void ResetNumberOfStartingVectors()
{
_numberOfStartingVectors = DefaultNumberOfStartingVectors;
}
///
/// Gets or sets a series of orthonormal vectors which will be used as basis for the
/// Krylov sub-space.
///
public IList> StartingVectors
{
[DebuggerStepThrough]
get => _startingVectors;
[DebuggerStepThrough]
set
{
if ((value == null) || (value.Count == 0))
{
_startingVectors = null;
}
else
{
_startingVectors = value;
}
}
}
///
/// Gets the number of starting vectors to create
///
/// Maximum number
/// Number of variables
/// Number of starting vectors to create
static int NumberOfStartingVectorsToCreate(int maximumNumberOfStartingVectors, int numberOfVariables)
{
// Create no more starting vectors than the size of the problem - 1
return Math.Min(maximumNumberOfStartingVectors, (numberOfVariables - 1));
}
///
/// Returns an array of starting vectors.
///
/// The maximum number of starting vectors that should be created.
/// The number of variables.
///
/// An array with starting vectors. The array will never be larger than the
/// but it may be smaller if
/// the is smaller than
/// the .
///
static IList> CreateStartingVectors(int maximumNumberOfStartingVectors, int numberOfVariables)
{
// Create no more starting vectors than the size of the problem - 1
// Get random values and then orthogonalize them with
// modified Gramm - Schmidt
var count = NumberOfStartingVectorsToCreate(maximumNumberOfStartingVectors, numberOfVariables);
// Get a random set of samples based on the standard normal distribution with
// mean = 0 and sd = 1
var distribution = new Normal();
var matrix = new DenseMatrix(numberOfVariables, count);
for (var i = 0; i < matrix.ColumnCount; i++)
{
var samples = new Numerics.Complex32[matrix.RowCount];
var samplesRe = distribution.Samples().Take(matrix.RowCount).ToArray();
var samplesIm = distribution.Samples().Take(matrix.RowCount).ToArray();
for (int j = 0; j < matrix.RowCount; j++)
{
samples[j] = new Numerics.Complex32((float)samplesRe[j], (float)samplesIm[j]);
}
// Set the column
matrix.SetColumn(i, samples);
}
// Compute the orthogonalization.
var gs = matrix.GramSchmidt();
var orthogonalMatrix = gs.Q;
// Now transfer this to vectors
var result = new List>(orthogonalMatrix.ColumnCount);
for (var i = 0; i < orthogonalMatrix.ColumnCount; i++)
{
result.Add(orthogonalMatrix.Column(i));
// Normalize the result vector
result[i].Multiply(1/(float) result[i].L2Norm(), result[i]);
}
return result;
}
///
/// Create random vectors array
///
/// Number of vectors
/// Size of each vector
/// Array of random vectors
static Vector[] CreateVectorArray(int arraySize, int vectorSize)
{
var result = new Vector[arraySize];
for (var i = 0; i < result.Length; i++)
{
result[i] = new DenseVector(vectorSize);
}
return result;
}
///
/// Calculates the true residual of the matrix equation Ax = b according to: residual = b - Ax
///
/// Source A.
/// Residual data.
/// x data.
/// b data.
static void CalculateTrueResidual(Matrix matrix, Vector residual, Vector x, Vector b)
{
// -Ax = residual
matrix.Multiply(x, residual);
residual.Multiply(-1, residual);
// residual + b
residual.Add(b, residual);
}
///
/// Solves the matrix equation Ax = b, where A is the coefficient matrix, b is the
/// solution vector and x is the unknown vector.
///
/// The coefficient matrix, A.
/// The solution vector, b
/// The result vector, x
/// The iterator to use to control when to stop iterating.
/// The preconditioner to use for approximations.
public void Solve(Matrix matrix, Vector input, Vector result, Iterator iterator, IPreconditioner preconditioner)
{
if (matrix.RowCount != matrix.ColumnCount)
{
throw new ArgumentException("Matrix must be square.", nameof(matrix));
}
if (result.Count != input.Count)
{
throw new ArgumentException("All vectors must have the same dimensionality.");
}
if (input.Count != matrix.RowCount)
{
throw Matrix.DimensionsDontMatch(input, matrix);
}
if (iterator == null)
{
iterator = new Iterator();
}
if (preconditioner == null)
{
preconditioner = new UnitPreconditioner();
}
preconditioner.Initialize(matrix);
// Choose an initial guess x_0
// Take x_0 = 0
var xtemp = new DenseVector(input.Count);
// Choose k vectors q_1, q_2, ..., q_k
// Build a new set if:
// a) the stored set doesn't exist (i.e. == null)
// b) Is of an incorrect length (i.e. too long)
// c) The vectors are of an incorrect length (i.e. too long or too short)
var useOld = false;
if (_startingVectors != null)
{
// We don't accept collections with zero starting vectors so ...
if (_startingVectors.Count <= NumberOfStartingVectorsToCreate(_numberOfStartingVectors, input.Count))
{
// Only check the first vector for sizing. If that matches we assume the
// other vectors match too. If they don't the process will crash
if (_startingVectors[0].Count == input.Count)
{
useOld = true;
}
}
}
_startingVectors = useOld ? _startingVectors : CreateStartingVectors(_numberOfStartingVectors, input.Count);
// Store the number of starting vectors. Not really necessary but easier to type :)
var k = _startingVectors.Count;
// r_0 = b - Ax_0
// This is basically a SAXPY so it could be made a lot faster
var residuals = new DenseVector(matrix.RowCount);
CalculateTrueResidual(matrix, residuals, xtemp, input);
// Define the temporary values
var c = new Numerics.Complex32[k];
// Define the temporary vectors
var gtemp = new DenseVector(residuals.Count);
var u = new DenseVector(residuals.Count);
var utemp = new DenseVector(residuals.Count);
var temp = new DenseVector(residuals.Count);
var temp1 = new DenseVector(residuals.Count);
var temp2 = new DenseVector(residuals.Count);
var zd = new DenseVector(residuals.Count);
var zg = new DenseVector(residuals.Count);
var zw = new DenseVector(residuals.Count);
var d = CreateVectorArray(_startingVectors.Count, residuals.Count);
// g_0 = r_0
var g = CreateVectorArray(_startingVectors.Count, residuals.Count);
residuals.CopyTo(g[k - 1]);
var w = CreateVectorArray(_startingVectors.Count, residuals.Count);
// FOR (j = 0, 1, 2 ....)
var iterationNumber = 0;
while (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) == IterationStatus.Continue)
{
// SOLVE M g~_((j-1)k+k) = g_((j-1)k+k)
preconditioner.Approximate(g[k - 1], gtemp);
// w_((j-1)k+k) = A g~_((j-1)k+k)
matrix.Multiply(gtemp, w[k - 1]);
// c_((j-1)k+k) = q^T_1 w_((j-1)k+k)
c[k - 1] = _startingVectors[0].ConjugateDotProduct(w[k - 1]);
if (c[k - 1].Real.AlmostEqualNumbersBetween(0, 1) && c[k - 1].Imaginary.AlmostEqualNumbersBetween(0, 1))
{
throw new NumericalBreakdownException();
}
// alpha_(jk+1) = q^T_1 r_((j-1)k+k) / c_((j-1)k+k)
var alpha = _startingVectors[0].ConjugateDotProduct(residuals)/c[k - 1];
// u_(jk+1) = r_((j-1)k+k) - alpha_(jk+1) w_((j-1)k+k)
w[k - 1].Multiply(-alpha, temp);
residuals.Add(temp, u);
// SOLVE M u~_(jk+1) = u_(jk+1)
preconditioner.Approximate(u, temp1);
temp1.CopyTo(utemp);
// rho_(j+1) = -u^t_(jk+1) A u~_(jk+1) / ||A u~_(jk+1)||^2
matrix.Multiply(temp1, temp);
var rho = temp.ConjugateDotProduct(temp);
// If rho is zero then temp is a zero vector and we're probably
// about to have zero residuals (i.e. an exact solution).
// So set rho to 1.0 because in the next step it will turn to zero.
if (rho.Real.AlmostEqualNumbersBetween(0, 1) && rho.Imaginary.AlmostEqualNumbersBetween(0, 1))
{
rho = 1.0f;
}
rho = -u.ConjugateDotProduct(temp)/rho;
// r_(jk+1) = rho_(j+1) A u~_(jk+1) + u_(jk+1)
u.CopyTo(residuals);
// Reuse temp
temp.Multiply(rho, temp);
residuals.Add(temp, temp2);
temp2.CopyTo(residuals);
// x_(jk+1) = x_((j-1)k_k) - rho_(j+1) u~_(jk+1) + alpha_(jk+1) g~_((j-1)k+k)
utemp.Multiply(-rho, temp);
xtemp.Add(temp, temp2);
temp2.CopyTo(xtemp);
gtemp.Multiply(alpha, gtemp);
xtemp.Add(gtemp, temp2);
temp2.CopyTo(xtemp);
// Check convergence and stop if we are converged.
if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue)
{
// Calculate the true residual
CalculateTrueResidual(matrix, residuals, xtemp, input);
// Now recheck the convergence
if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue)
{
// We're all good now.
// Exit from the while loop.
break;
}
}
// FOR (i = 1,2, ...., k)
for (var i = 0; i < k; i++)
{
// z_d = u_(jk+1)
u.CopyTo(zd);
// z_g = r_(jk+i)
residuals.CopyTo(zg);
// z_w = 0
zw.Clear();
// FOR (s = i, ...., k-1) AND j >= 1
Numerics.Complex32 beta;
if (iterationNumber >= 1)
{
for (var s = i; s < k - 1; s++)
{
// beta^(jk+i)_((j-1)k+s) = -q^t_(s+1) z_d / c_((j-1)k+s)
beta = -_startingVectors[s + 1].ConjugateDotProduct(zd)/c[s];
// z_d = z_d + beta^(jk+i)_((j-1)k+s) d_((j-1)k+s)
d[s].Multiply(beta, temp);
zd.Add(temp, temp2);
temp2.CopyTo(zd);
// z_g = z_g + beta^(jk+i)_((j-1)k+s) g_((j-1)k+s)
g[s].Multiply(beta, temp);
zg.Add(temp, temp2);
temp2.CopyTo(zg);
// z_w = z_w + beta^(jk+i)_((j-1)k+s) w_((j-1)k+s)
w[s].Multiply(beta, temp);
zw.Add(temp, temp2);
temp2.CopyTo(zw);
}
}
beta = rho*c[k - 1];
if (beta.Real.AlmostEqualNumbersBetween(0, 1) && beta.Imaginary.AlmostEqualNumbersBetween(0, 1))
{
throw new NumericalBreakdownException();
}
// beta^(jk+i)_((j-1)k+k) = -(q^T_1 (r_(jk+1) + rho_(j+1) z_w)) / (rho_(j+1) c_((j-1)k+k))
zw.Multiply(rho, temp2);
residuals.Add(temp2, temp);
beta = -_startingVectors[0].ConjugateDotProduct(temp)/beta;
// z_g = z_g + beta^(jk+i)_((j-1)k+k) g_((j-1)k+k)
g[k - 1].Multiply(beta, temp);
zg.Add(temp, temp2);
temp2.CopyTo(zg);
// z_w = rho_(j+1) (z_w + beta^(jk+i)_((j-1)k+k) w_((j-1)k+k))
w[k - 1].Multiply(beta, temp);
zw.Add(temp, temp2);
temp2.CopyTo(zw);
zw.Multiply(rho, zw);
// z_d = r_(jk+i) + z_w
residuals.Add(zw, zd);
// FOR (s = 1, ... i - 1)
for (var s = 0; s < i - 1; s++)
{
// beta^(jk+i)_(jk+s) = -q^T_s+1 z_d / c_(jk+s)
beta = -_startingVectors[s + 1].ConjugateDotProduct(zd)/c[s];
// z_d = z_d + beta^(jk+i)_(jk+s) * d_(jk+s)
d[s].Multiply(beta, temp);
zd.Add(temp, temp2);
temp2.CopyTo(zd);
// z_g = z_g + beta^(jk+i)_(jk+s) * g_(jk+s)
g[s].Multiply(beta, temp);
zg.Add(temp, temp2);
temp2.CopyTo(zg);
}
// d_(jk+i) = z_d - u_(jk+i)
zd.Subtract(u, d[i]);
// g_(jk+i) = z_g + z_w
zg.Add(zw, g[i]);
// IF (i < k - 1)
if (i < k - 1)
{
// c_(jk+1) = q^T_i+1 d_(jk+i)
c[i] = _startingVectors[i + 1].ConjugateDotProduct(d[i]);
if (c[i].Real.AlmostEqualNumbersBetween(0, 1) && c[i].Imaginary.AlmostEqualNumbersBetween(0, 1))
{
throw new NumericalBreakdownException();
}
// alpha_(jk+i+1) = q^T_(i+1) u_(jk+i) / c_(jk+i)
alpha = _startingVectors[i + 1].ConjugateDotProduct(u)/c[i];
// u_(jk+i+1) = u_(jk+i) - alpha_(jk+i+1) d_(jk+i)
d[i].Multiply(-alpha, temp);
u.Add(temp, temp2);
temp2.CopyTo(u);
// SOLVE M g~_(jk+i) = g_(jk+i)
preconditioner.Approximate(g[i], gtemp);
// x_(jk+i+1) = x_(jk+i) + rho_(j+1) alpha_(jk+i+1) g~_(jk+i)
gtemp.Multiply(rho*alpha, temp);
xtemp.Add(temp, temp2);
temp2.CopyTo(xtemp);
// w_(jk+i) = A g~_(jk+i)
matrix.Multiply(gtemp, w[i]);
// r_(jk+i+1) = r_(jk+i) - rho_(j+1) alpha_(jk+i+1) w_(jk+i)
w[i].Multiply(-rho*alpha, temp);
residuals.Add(temp, temp2);
temp2.CopyTo(residuals);
// We can check the residuals here if they're close
if (iterator.DetermineStatus(iterationNumber, xtemp, input, residuals) != IterationStatus.Continue)
{
// Recalculate the residuals and go round again. This is done to ensure that
// we have the proper residuals.
CalculateTrueResidual(matrix, residuals, xtemp, input);
}
}
} // END ITERATION OVER i
iterationNumber++;
}
// copy the temporary result to the real result vector
xtemp.CopyTo(result);
}
}
}