package pl.edu.fuw.fid.signalanalysis.ica;
import org.apache.commons.math.linear.Array2DRowRealMatrix;
import org.apache.commons.math.linear.EigenDecomposition;
import org.apache.commons.math.linear.EigenDecompositionImpl;
import org.apache.commons.math.linear.RealMatrix;
import org.apache.commons.math.linear.RealVector;
/**
* The core calculations of ICA (Independent Component Analysis) method.
* This is an implementation of FastICA algorithm, according to:
* Patil D., Das N., Routray A.: Implementation of Fast-ICA / A Performance
* Based Comparison Between Floating Point and Fixed Point DSP Platform,
* MEASUREMENT SCIENCE REVIEW, Volume 11, No. 4, 2011
*
* @author ptr@mimuw.edu.pl
*/
public class IcaMethodComputer {
/**
* Performs the calculations.
*
* @param data matrix of multichannel data (channels × samples)
* @return matrix of coefficients corresponding to independent components,
* row 0 corresponds to first component, and so on,
* with row count (# components) = column count (# given channels)
*
* @throws IcaMethodException
*/
public RealMatrix compute(RealMatrix data) throws IcaMethodException {
final int C = data.getRowDimension();
if (C == 0) {
throw new IcaMethodException("no channels selected");
}
final int N = data.getColumnDimension();
if (N == 0) {
throw new IcaMethodException("signal is empty");
}
// mean elimination
for (int i=0; i<C; ++i) {
double mean = 0.0;
double[] channel = data.getRow(i);
for (int n=0; n<N; ++n) {
mean += channel[n];
}
mean /= N;
for (int n=0; n<N; ++n) {
data.addToEntry(i, n, -mean);
}
}
// computation of covariance matrix
RealMatrix V = data.multiply(data.transpose()).scalarMultiply(1.0/N);
// computation of whitening matrix
V = power(V, -0.5);
for (int i=0; i<C; ++i) for (int j=0; j<C; ++j) {
double value = V.getEntry(i, j);
if (Double.isInfinite(value) || Double.isNaN(value)) {
throw new IcaMethodException("signal whitening failed");
}
}
// whitening the signal
RealMatrix v = V.multiply(data);
// rows of matrix BT will be IC coefficients
RealMatrix BT = new Array2DRowRealMatrix(C, C);
for (int iteration=0; iteration<C; ++iteration) {
double[] w0 = new double[C];
// Take a random initial vector w...
for (int i=0; i<C; ++i) {
w0[i] = 2*Math.random() - 1;
}
// ... and normalize it to unity
normalize(w0);
while (true) {
double[] w1 = new double[C];
// Set ...
for (int n=0; n<N; ++n) {
double sum = 0.0;
for (int i=0; i<C; ++i) {
sum += w0[i] * v.getEntry(i, n);
}
sum *= sum * sum;
for (int i=0; i<C; ++i) {
w1[i] += v.getEntry(i, n) * sum;
}
}
for (int i=0; i<C; ++i) {
w1[i] = w1[i] / N - 3 * w0[i];
}
// orthogonalize to previous solutions
for (int it=0; it<iteration; ++it) {
double prod = product(C, BT.getRow(it), w1);
for (int i=0; i<C; ++i) {
w1[i] -= prod * BT.getEntry(it, i);
}
}
normalize(w1);
double w0w1 = Math.abs(product(C, w0, w1));
w0 = w1;
if (Math.abs(w0w1 - 1.0) < 1.0e-6) {
break;
}
}
// another solution
BT.setRow(iteration, w0);
}
// final solution
RealMatrix ICA = BT.multiply(V);
// normalizing each component
for (int i=0; i<C; ++i) {
RealVector u = ICA.getRowVector(i).unitVector();
ICA.setRowVector(i, u);
}
return ICA;
}
/**
* Normalize a real vector.
* After operation, ||w||² = w · w = 1
*
* @param w vector (given as array) to normalize
*/
public static void normalize(double[] w) {
double length = Math.sqrt(product(w.length, w, w));
for (int i=0; i<w.length; ++i) {
w[i] /= length;
}
}
/**
* Raise given real symmetric matrix to any power.
*
* @param matrix symmetric matrix
* @param p real exponent
* @return matrix raised to the power of p
*/
public static RealMatrix power(final RealMatrix matrix, final double p) {
final EigenDecomposition eigen = new EigenDecompositionImpl(matrix, 0.0);
final double[] rEigenValues = eigen.getRealEigenvalues();
final int n = rEigenValues.length;
final double[][] d = new double[n][n];
for (int i = n - 1; i >= 0; --i) {
d[i][i] = Math.pow(rEigenValues[i], p);
}
final RealMatrix res = eigen.getV().multiply((new Array2DRowRealMatrix(d)).multiply(eigen.getVT()));
return res;
}
/**
* Compute scalar product of two real vectors.
* This operation is symmetric.
*
* @param N length of both vectors
* @param x first vector given as array
* @param y second vector given as array
* @return scalar product of two vectors
*/
public static double product(int N, double[] x, double[] y) {
double sum = 0.0;
for (int i=0; i<N; ++i) {
sum += x[i] * y[i];
}
return sum;
}
}