blob: 8a7668036c1fde765186d79cbf1ed5384aea8d55 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.math.linear;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.math.ConvergenceException;
import org.apache.commons.math.MathRuntimeException;
import org.apache.commons.math.MaxIterationsExceededException;
import org.apache.commons.math.util.MathUtils;
/**
* Calculates the eigen decomposition of a <strong>symmetric</strong> matrix.
* <p>The eigen decomposition of symmetric matrix A is a set of two matrices:
* V and D such that A = V D V<sup>T</sup>. A, V and D are all m &times; m
* matrices.</p>
* <p>This implementation only uses the upper part of the matrix, the part below the
* diagonal is not accessed at all.</p>
* <p>Eigenvalues are computed as soon as the matrix is decomposed, but eigenvectors
* are computed only when required, i.e. only when one of the {@link #getEigenvector(int)},
* {@link #getV()}, {@link #getVT()}, {@link #getInverse()}, {@link #solve(double[])},
* {@link #solve(RealMatrix)}, {@link #solve(RealVector)} or {@link #solve(RealVectorImpl)}
* methods is called.</p>
* <p>This implementation is based on Inderjit Singh Dhillon thesis
* <a href="http://www.cs.utexas.edu/users/inderjit/public_papers/thesis.pdf">A
* New O(n<sup>2</sup>) Algorithm for the Symmetric Tridiagonal Eigenvalue/Eigenvector
* Problem</a>, on Beresford N. Parlett and Osni A. Marques paper <a
* href="http://www.netlib.org/lapack/lawnspdf/lawn155.pdf">An Implementation of the
* dqds Algorithm (Positive Case)</a> and on the corresponding LAPACK routines (DLARRE,
* DLASQ2, DLAZQ3, DLAZQ4, DLASQ5 and DLASQ6).</p>
* @author Beresford Parlett, University of California, Berkeley, USA (fortran version)
* @author Jim Demmel, University of California, Berkeley, USA (fortran version)
* @author Inderjit Dhillon, University of Texas, Austin, USA(fortran version)
* @author Osni Marques, LBNL/NERSC, USA (fortran version)
* @author Christof Voemel, University of California, Berkeley, USA(fortran version)
* @version $Revision$ $Date$
* @since 2.0
*/
public class EigenDecompositionImpl implements EigenDecomposition {
/** Serializable version identifier. */
private static final long serialVersionUID = -4976315828448620858L;
/** Tolerance. */
private static final double TOLERANCE = 100 * MathUtils.EPSILON;
/** Squared tolerance. */
private static final double TOLERANCE_2 = TOLERANCE * TOLERANCE;
/** Split tolerance. */
private double splitTolerance;
/** Main diagonal of the tridiagonal matrix. */
private double[] main;
/** Secondary diagonal of the tridiagonal matrix. */
private double[] secondary;
/** Squared secondary diagonal of the tridiagonal matrix. */
private double[] squaredSecondary;
/** Orthogonal matrix of tridiagonal transformation. */
private RealMatrix orthoTridiag;
/** Lower bound of spectra. */
private double lowerSpectra;
/** Upper bound of spectra. */
private double upperSpectra;
/** Minimum pivot in the Sturm sequence. */
private double minPivot;
/** Current shift. */
private double sigma;
/** Low part of the current shift. */
private double sigmaLow;
/** Shift increment to apply. */
private double tau;
/** Work array for all decomposition algorithms. */
private double[] work;
/** Shift within qd array for ping-pong implementation. */
private int pingPong;
/** Max value of diagonal elements in current segment. */
private double qMax;
/** Min value of off-diagonal elements in current segment. */
private double eMin;
/** Type of the last dqds shift. */
private int tType;
/** Minimal value on current state of the diagonal. */
private double dMin;
/** Minimal value on current state of the diagonal, excluding last element. */
private double dMin1;
/** Minimal value on current state of the diagonal, excluding last two elements. */
private double dMin2;
/** Last value on current state of the diagonal. */
private double dN;
/** Last but one value on current state of the diagonal. */
private double dN1;
/** Last but two on current state of the diagonal. */
private double dN2;
/** Shift ratio with respect to dMin used when tType == 6. */
private double g;
/** Eigenvalues. */
private double[] eigenvalues;
/** Eigenvectors. */
private RealVectorImpl[] eigenvectors;
/** Cached value of V. */
private RealMatrix cachedV;
/** Cached value of D. */
private RealMatrix cachedD;
/** Cached value of Vt. */
private RealMatrix cachedVt;
/**
* Build a new instance.
* <p>Note that {@link #decompose(RealMatrix)} <strong>must</strong> be called
* before any of the {@link #getV()}, {@link #getD()}, {@link #getVT()},
* {@link #getEignevalues()}, {@link #solve(double[])}, {@link #solve(RealMatrix)},
* {@link #solve(RealVector)} or {@link #solve(RealVectorImpl)} methods can be
* called.</p>
* @see #decompose(RealMatrix)
*/
public EigenDecompositionImpl() {
setRelativeAccuracySplitTolerance(MathUtils.SAFE_MIN);
}
/**
* Calculates the eigen decomposition of the given symmetric matrix.
* <p>Calling this constructor is equivalent to first call the no-arguments
* constructor and then call {@link #decompose(RealMatrix)}.</p>
* <p>The specified matrix is assumed to be symmetrical without any check.
* Only the upper triangular part of the matrix is used.</p>
* @param matrix The <strong>symmetric</strong> matrix to decompose.
* @exception InvalidMatrixException (wrapping a {@link ConvergenceException}
* if algorithm fails to converge
*/
public EigenDecompositionImpl(final RealMatrix matrix)
throws InvalidMatrixException {
setRelativeAccuracySplitTolerance(MathUtils.SAFE_MIN);
decompose(matrix);
}
/**
* Set split tolerance based on absolute off-diagonal elements.
* @param tolerance tolerance to set
*/
public void setAbsoluteSplitTolerance(final double tolerance) {
splitTolerance = -Math.abs(tolerance);
}
/**
* Set split tolerance preserving relative accuracy.
* @param tolerance tolerance to set
*/
public void setRelativeAccuracySplitTolerance(final double tolerance) {
splitTolerance = Math.abs(tolerance);
}
/**
* Decompose a <strong>symmetric</strong> matrix.
* <p>The specified matrix is assumed to be symmetrical without any check.
* Only the upper triangular part of the matrix is used.</p>
* @param matrix symmetric matrix to decompose
* @exception InvalidMatrixException if matrix cannot be diagonalized
*/
public void decompose(final RealMatrix matrix)
throws InvalidMatrixException {
cachedV = null;
cachedD = null;
cachedVt = null;
work = new double[6 * matrix.getRowDimension()];
// compute tridiagonal representation of the initial matrix
transformToTridiagonal(matrix);
computeGershgorinCircles();
// find all the eigenvalues
findEigenvalues();
// we will search for eigenvectors only if required
eigenvectors = null;
}
/** {@inheritDoc} */
public RealMatrix getV()
throws InvalidMatrixException {
if (cachedV == null) {
cachedV = getVT().transpose();
}
// return the cached matrix
return cachedV;
}
/** {@inheritDoc} */
public RealMatrix getD()
throws InvalidMatrixException {
if (cachedD == null) {
checkDecomposed();
final int m = eigenvalues.length;
final double[][] sData = new double[m][m];
for (int i = 0; i < m; ++i) {
sData[i][i] = eigenvalues[i];
}
// cache the matrix for subsequent calls
cachedD = new RealMatrixImpl(sData, false);
}
return cachedD;
}
/** {@inheritDoc} */
public RealMatrix getVT()
throws InvalidMatrixException {
if (cachedVt == null) {
checkDecomposed();
if (eigenvectors == null) {
findEigenVectors();
}
final double[][] vtData = new double[eigenvectors.length][];
for (int k = 0; k < eigenvectors.length; ++k) {
vtData[k] = eigenvectors[k].getData();
}
// cache the matrix for subsequent calls
cachedVt = new RealMatrixImpl(vtData, false);
}
// return the cached matrix
return cachedVt;
}
/** {@inheritDoc} */
public double[] getEigenvalues()
throws InvalidMatrixException {
checkDecomposed();
return eigenvalues.clone();
}
/** {@inheritDoc} */
public double getEigenvalue(final int i)
throws InvalidMatrixException, ArrayIndexOutOfBoundsException {
checkDecomposed();
return eigenvalues[i];
}
/** {@inheritDoc} */
public RealVector getEigenvector(final int i)
throws InvalidMatrixException, ArrayIndexOutOfBoundsException {
checkDecomposed();
if (eigenvectors == null) {
findEigenVectors();
}
return eigenvectors[i].copy();
}
/** {@inheritDoc} */
public boolean isNonSingular()
throws IllegalStateException {
for (double lambda : eigenvalues) {
if (lambda == 0) {
return false;
}
}
return true;
}
/** {@inheritDoc} */
public double[] solve(final double[] b)
throws IllegalArgumentException, InvalidMatrixException {
checkNonSingular();
final int m = eigenvalues.length;
if (b.length != m) {
throw new IllegalArgumentException("constant vector has wrong length");
}
if (eigenvectors == null) {
findEigenVectors();
}
final double[] bp = new double[m];
for (int i = 0; i < m; ++i) {
final RealVectorImpl v = eigenvectors[i];
final double s = v.dotProduct(b) / eigenvalues[i];
final double[] vData = v.getDataRef();
for (int j = 0; j < m; ++j) {
bp[j] += s * vData[j];
}
}
return bp;
}
/** {@inheritDoc} */
public RealVector solve(final RealVector b)
throws IllegalArgumentException, InvalidMatrixException {
try {
return solve((RealVectorImpl) b);
} catch (ClassCastException cce) {
checkNonSingular();
final int m = eigenvalues.length;
if (b.getDimension() != m) {
throw new IllegalArgumentException("constant vector has wrong length");
}
if (eigenvectors == null) {
findEigenVectors();
}
final double[] bp = new double[m];
for (int i = 0; i < m; ++i) {
final RealVectorImpl v = eigenvectors[i];
final double s = v.dotProduct(b) / eigenvalues[i];
final double[] vData = v.getDataRef();
for (int j = 0; j < m; ++j) {
bp[j] += s * vData[j];
}
}
return new RealVectorImpl(bp, false);
}
}
/**
* Solve the linear equation A &times; X = B.
* <p>The A matrix is implicit here. It <strong>must</strong> have
* already been provided by a previous call to {@link #decompose(RealMatrix)}.</p>
* @param b right-hand side of the equation A &times; X = B
* @return a vector X such that A &times; X = B
* @throws IllegalArgumentException if matrices dimensions don't match
* @throws InvalidMatrixException if decomposed matrix is singular
*/
public RealVectorImpl solve(final RealVectorImpl b)
throws IllegalArgumentException, InvalidMatrixException {
return new RealVectorImpl(solve(b.getDataRef()), false);
}
/** {@inheritDoc} */
public RealMatrix solve(final RealMatrix b)
throws IllegalArgumentException, InvalidMatrixException {
checkNonSingular();
final int m = eigenvalues.length;
if (b.getRowDimension() != m) {
throw new IllegalArgumentException("Incorrect row dimension");
}
if (eigenvectors == null) {
findEigenVectors();
}
final int nColB = b.getColumnDimension();
final double[][] bp = new double[m][nColB];
for (int k = 0; k < nColB; ++k) {
for (int i = 0; i < m; ++i) {
final double[] vData = eigenvectors[i].getDataRef();
double s = 0;
for (int j = 0; j < m; ++j) {
s += vData[j] * b.getEntry(j, k);
}
s /= eigenvalues[i];
for (int j = 0; j < m; ++j) {
bp[j][k] += s * vData[j];
}
}
}
return new RealMatrixImpl(bp, false);
}
/** {@inheritDoc} */
public RealMatrix getInverse()
throws IllegalStateException, InvalidMatrixException {
checkNonSingular();
final int m = eigenvalues.length;
final double[][] invData = new double[m][m];
if (eigenvectors == null) {
findEigenVectors();
}
for (int i = 0; i < m; ++i) {
final double[] invI = invData[i];
for (int j = 0; j < m; ++j) {
double invIJ = 0;
for (int k = 0; k < m; ++k) {
final double[] vK = eigenvectors[k].getDataRef();
invIJ += vK[i] * vK[j] / eigenvalues[k];
}
invI[j] = invIJ;
}
}
return new RealMatrixImpl(invData, false);
}
/** {@inheritDoc} */
public double getDeterminant()
throws IllegalStateException {
double determinant = 1;
for (double lambda : eigenvalues) {
determinant *= lambda;
}
return determinant;
}
/**
* Transform matrix to tridiagonal.
* @param matrix matrix to transform
*/
private void transformToTridiagonal(final RealMatrix matrix) {
// transform the matrix to tridiagonal
TriDiagonalTransformer transformer = new TriDiagonalTransformer(matrix);
main = transformer.getMainDiagonalRef();
secondary = transformer.getSecondaryDiagonalRef();
// pre-compute some elements
squaredSecondary = new double[secondary.length];
for (int i = 0; i < squaredSecondary.length; ++i) {
final double s = secondary[i];
squaredSecondary[i] = s * s;
}
orthoTridiag = transformer.getQ();
}
/**
* Compute the Gershgorin circles for all rows.
*/
private void computeGershgorinCircles() {
final int m = main.length;
final int lowerStart = 4 * m;
final int upperStart = 5 * m;
lowerSpectra = Double.POSITIVE_INFINITY;
upperSpectra = Double.NEGATIVE_INFINITY;
double eMax = 0;
double eCurrent = 0;
for (int i = 0; i < m - 1; ++i) {
final double dCurrent = main[i];
final double ePrevious = eCurrent;
eCurrent = Math.abs(secondary[i]);
eMax = Math.max(eMax, eCurrent);
final double radius = ePrevious + eCurrent;
final double lower = dCurrent - radius;
work[lowerStart + i] = lower;
lowerSpectra = Math.min(lowerSpectra, lower);
final double upper = dCurrent + radius;
work[upperStart + i] = upper;
upperSpectra = Math.max(upperSpectra, upper);
}
final double dCurrent = main[m - 1];
work[lowerStart + m - 1] = dCurrent - eCurrent;
work[upperStart + m - 1] = dCurrent + eCurrent;
minPivot = MathUtils.SAFE_MIN * Math.max(1.0, eMax * eMax);
}
/**
* Find the eigenvalues.
* @exception InvalidMatrixException if a block cannot be diagonalized
*/
private void findEigenvalues()
throws InvalidMatrixException {
// compute splitting points
List<Integer> splitIndices = computeSplits();
// find eigenvalues in each block
eigenvalues = new double[main.length];
int begin = 0;
for (final int end : splitIndices) {
final int n = end - begin;
switch (n) {
case 1:
// apply dedicated method for dimension 1
process1RowBlock(begin);
break;
case 2:
// apply dedicated method for dimension 2
process2RowsBlock(begin);
break;
case 3:
// apply dedicated method for dimension 3
process3RowsBlock(begin);
break;
default:
// choose an initial shift for LDL<sup>T</sup> decomposition
final double[] range = eigenvaluesRange(begin, n);
final double oneFourth = 0.25 * (3 * range[0] + range[1]);
final int oneFourthCount = countEigenValues(oneFourth, begin, n);
final double threeFourth = 0.25 * (range[0] + 3 * range[1]);
final int threeFourthCount = countEigenValues(threeFourth, begin, n);
final boolean chooseLeft = (oneFourthCount - 1) >= (n - threeFourthCount);
final double lambda = chooseLeft ? range[0] : range[1];
tau = (range[1] - range[0]) * MathUtils.EPSILON * n + 2 * minPivot;
// decompose T&lambda;I as LDL<sup>T</sup>
ldlTDecomposition(lambda, begin, n);
// apply general dqd/dqds method
processGeneralBlock(n);
// extract eigenvalues
if (chooseLeft) {
for (int i = 0; i < n; ++i) {
eigenvalues[begin + i] = lambda + work[4 * i];
}
} else {
for (int i = 0; i < n; ++i) {
eigenvalues[begin + i] = lambda - work[4 * i];
}
}
}
begin = end;
}
// sort the eigenvalues in decreasing order
Arrays.sort(eigenvalues);
for (int i = 0, j = eigenvalues.length - 1; i < j; ++i, --j) {
final double tmp = eigenvalues[i];
eigenvalues[i] = eigenvalues[j];
eigenvalues[j] = tmp;
}
}
/**
* Compute splitting points.
* @return list of indices after matrix can be split
*/
private List<Integer> computeSplits() {
final List<Integer> list = new ArrayList<Integer>();
if (splitTolerance < 0) {
// splitting based on absolute off-diagonal value
final double max = Math.abs(splitTolerance) * (upperSpectra - lowerSpectra);
for (int i = 0; i < secondary.length; ++i) {
if (Math.abs(secondary[i]) <= max) {
list.add(i + 1);
secondary[i] = 0;
squaredSecondary[i] = 0;
}
}
} else {
// splitting preserving relative accuracy
double absDCurrent = Math.abs(0);
for (int i = 0; i < secondary.length; ++i) {
final double absDPrevious = absDCurrent;
absDCurrent = Math.abs(i + 1);
final double max = splitTolerance * Math.sqrt(absDPrevious * absDCurrent);
if (Math.abs(secondary[i]) <= max) {
list.add(i + 1);
secondary[i] = 0;
squaredSecondary[i] = 0;
}
}
}
list.add(secondary.length + 1);
return list;
}
/**
* Find eigenvalue in a block with 1 row.
* <p>In low dimensions, we simply solve the characteristic polynomial.</p>
* @param index index of the first row of the block
*/
private void process1RowBlock(final int index) {
eigenvalues[index] = main[index];
}
/**
* Find eigenvalues in a block with 2 rows.
* <p>In low dimensions, we simply solve the characteristic polynomial.</p>
* @param index index of the first row of the block
* @exception InvalidMatrixException if characteristic polynomial cannot be solved
*/
private void process2RowsBlock(final int index)
throws InvalidMatrixException {
// the characteristic polynomial is
// X^2 - (q0 + q1) X + q0 q1 - e1^2
final double q0 = main[index];
final double q1 = main[index + 1];
final double e12 = squaredSecondary[index];
final double s = q0 + q1;
final double p = q0 * q1 - e12;
final double delta = s * s - 4 * p;
if (delta < 0) {
throw new InvalidMatrixException("cannot solve degree {0} equation", new Object[] { 2 });
}
final double largestRoot = 0.5 * (s + Math.sqrt(delta));
eigenvalues[index] = largestRoot;
eigenvalues[index + 1] = p / largestRoot;
}
/**
* Find eigenvalues in a block with 3 rows.
* <p>In low dimensions, we simply solve the characteristic polynomial.</p>
* @param index index of the first row of the block
* @exception InvalidMatrixException if diagonal elements are not positive
*/
private void process3RowsBlock(final int index)
throws InvalidMatrixException {
// the characteristic polynomial is
// X^3 - (q0 + q1 + q2) X^2 + (q0 q1 + q0 q2 + q1 q2 - e1^2 - e2^2) X + q0 e2^2 + q2 e1^2 - q0 q1 q2
final double q0 = main[index];
final double q1 = main[index + 1];
final double q2 = main[index + 2];
final double e12 = squaredSecondary[index];
final double q1q2Me22 = q1 * q2 - squaredSecondary[index + 1];
// compute coefficients of the cubic equation as: x^3 + b x^2 + c x + d = 0
final double b = -(q0 + q1 + q2);
final double c = q0 * q1 + q0 * q2 + q1q2Me22 - e12;
final double d = q2 * e12 - q0 * q1q2Me22;
// solve cubic equation
final double b2 = b * b;
final double q = (3 * c - b2) / 9;
final double r = ((9 * c - 2 * b2) * b - 27 * d) / 54;
final double delta = q * q * q + r * r;
if (delta >= 0) {
// in fact, there are solutions to the equation, but in the context
// of symmetric eigenvalues problem, there should be three distinct
// real roots, so we throw an error if this condition is not met
throw new InvalidMatrixException("cannot solve degree {0} equation", new Object[] { 3 });
}
final double sqrtMq = Math.sqrt(-q);
final double theta = Math.acos(r / (-q * sqrtMq));
final double alpha = 2 * sqrtMq;
final double beta = b / 3;
double z0 = alpha * Math.cos(theta / 3) - beta;
double z1 = alpha * Math.cos((theta + 2 * Math.PI) / 3) - beta;
double z2 = alpha * Math.cos((theta + 4 * Math.PI) / 3) - beta;
if (z0 < z1) {
final double t = z0;
z0 = z1;
z1 = t;
}
if (z1 < z2) {
final double t = z1;
z1 = z2;
z2 = t;
}
if (z0 < z1) {
final double t = z0;
z0 = z1;
z1 = t;
}
eigenvalues[index] = z0;
eigenvalues[index + 1] = z1;
eigenvalues[index + 2] = z2;
}
/**
* Find eigenvalues using dqd/dqds algorithms.
* <p>This implementation is based on Beresford N. Parlett
* and Osni A. Marques paper <a
* href="http://www.netlib.org/lapack/lawnspdf/lawn155.pdf">An
* Implementation of the dqds Algorithm (Positive Case)</a> and on the
* corresponding LAPACK routine DLASQ2.</p>
* @param n number of rows of the block
* @exception InvalidMatrixException if block cannot be diagonalized
* after 30 * n iterations
*/
private void processGeneralBlock(final int n)
throws InvalidMatrixException {
// check decomposed matrix data range
final int fourN1 = 4 * (n - 1);
double sumDiag = 0;
double sumOffDiag = 0;
// qMax = Double.NEGATIVE_INFINITY;
// eMin = Double.POSITIVE_INFINITY;
for (int i = 0; i < n - 1; ++i) {
final int fourI = 4 * i;
final double qi = work[fourI];
final double ei = work[fourI + 2];
// qMax = Math.max(qMax, qi);
// eMin = Math.min(eMin, ei);
sumDiag += qi;
sumOffDiag += ei;
}
final double qi = work[fourN1];
// qMax = Math.max(qMax, qi);
sumDiag += qi;
if (sumOffDiag == 0) {
// matrix is already diagonal
return;
}
// initial checks for splits (see Parlett & Marques section 3.3)
flipIfWarranted(n, 2);
// two iterations with Li's test for initial splits
initialSplits(n);
// initialize parameters used by goodStep
tType = 0;
dMin1 = 0;
dMin2 = 0;
dN = 0;
dN1 = 0;
dN2 = 0;
tau = 0;
// process split segments
int i0 = 0;
int n0 = n;
while (n0 > 0) {
// retrieve shift that was temporarily stored as a negative off-diagonal element
sigma = (n0 == n) ? 0 : -work[4 * n0 - 2];
sigmaLow = 0;
// find start of a new split segment to process
double eMin = (i0 == n0) ? 0 : work[4 * n0 - 6];
double eMax = 0;
double qMax = work[4 * n0 - 4];
double qMin = qMax;
i0 = 0;
for (int i = 4 * (n0 - 2); i >= 0; i -= 4) {
if (work[i + 2] <= 0) {
i0 = 1 + i / 4;
break;
}
if (qMin >= 4 * eMax) {
qMin = Math.min(qMin, work[i + 4]);
eMax = Math.max(eMax, work[i + 2]);
}
qMax = Math.max(qMax, work[i] + work[i + 2]);
eMin = Math.min(eMin, work[i + 2]);
}
work[4 * n0 - 2] = eMin;
// lower bound of Gershgorin disk
dMin = -Math.max(0, qMin - 2 * Math.sqrt(qMin * eMax));
pingPong = 0;
int maxIter = 30 * (n0 - i0);
for (int k = 0; i0 < n0; ++k) {
if (k >= maxIter) {
throw new InvalidMatrixException(new MaxIterationsExceededException(maxIter));
}
// perform one step
n0 = goodStep(i0, n0);
pingPong = 1 - pingPong;
// check for new splits after "ping" steps
// when the last elements of qd array are very small
if ((pingPong == 0) && (n0 - i0 > 3) &&
(work[4 * n0 - 1] <= TOLERANCE_2 * qMax) &&
(work[4 * n0 - 2] <= TOLERANCE_2 * sigma)) {
int split = i0 - 1;
qMax = work[4 * i0];
eMin = work[4 * i0 + 2];
double previousEMin = work[4 * i0 + 3];
for (int i = 4 * i0; i < 4 * n0 - 11; i += 4) {
if ((work[i + 3] <= TOLERANCE_2 * work[i]) &&
(work[i + 2] <= TOLERANCE_2 * sigma)) {
// insert a split here
work[i + 2] = -sigma;
split = i / 4;
qMax = 0;
eMin = work[i + 6];
previousEMin = work[i + 7];
} else {
qMax = Math.max(qMax, work[i + 4]);
eMin = Math.min(eMin, work[i + 2]);
previousEMin = Math.min(previousEMin, work[i + 3]);
}
}
work[4 * n0 - 2] = eMin;
work[4 * n0 - 1] = previousEMin;
i0 = split + 1;
}
}
}
}
/**
* Perform two iterations with Li's tests for initial splits.
* @param n number of rows of the matrix to process
*/
private void initialSplits(final int n) {
pingPong = 0;
for (int k = 0; k < 2; ++k) {
// apply Li's reverse test
double d = work[4 * (n - 1) + pingPong];
for (int i = 4 * (n - 2) + pingPong; i >= 0; i -= 4) {
if (work[i + 2] <= TOLERANCE_2 * d) {
work[i + 2] = -0.0;
d = work[i];
} else {
d *= work[i] / (d + work[i + 2]);
}
}
// apply dqd plus Li's forward test.
// eMin = work[4 + pingPong];
d = work[pingPong];
for (int i = 2 + pingPong; i < 4 * n - 2; i += 4) {
final int j = i - 2 * pingPong - 1;
work[j] = d + work[i];
if (work[i] <= TOLERANCE_2 * d) {
work[i] = -0.0;
work[j] = d;
work[j + 2] = 0.0;
d = work[i + 2];
} else if ((MathUtils.SAFE_MIN * work[i + 2] < work[j]) &&
(MathUtils.SAFE_MIN * work[j] < work[i + 2])) {
final double tmp = work[i + 2] / work[j];
work[j + 2] = work[i] * tmp;
d *= tmp;
} else {
work[j + 2] = work[i + 2] * (work[i] / work[j]);
d *= work[i + 2] / work[j];
}
// eMin = Math.min(eMin, work[j + 2]);
}
work[4 * n - 3 - pingPong] = d;
// // find qMax
// qMax = Double.NEGATIVE_INFINITY;
// for (int i = 1 - pingPong; i < 4 * n; i += 4) {
// qMax = Math.max(qMax, work[i]);
// }
// from ping to pong
pingPong = 1 - pingPong;
}
}
/**
* Perform one "good" dqd/dqds step.
* <p>This implementation is based on Beresford N. Parlett
* and Osni A. Marques paper <a
* href="http://www.netlib.org/lapack/lawnspdf/lawn155.pdf">An
* Implementation of the dqds Algorithm (Positive Case)</a> and on the
* corresponding LAPACK routine DLAZQ3.</p>
* @param start start index
* @param end end index
* @return new end (maybe deflated)
*/
private int goodStep(final int start, final int end) {
g = 0.0;
// step 1: accepting eigenvalues
int deflatedEnd = end;
for (boolean deflating = true; deflating;) {
if (start >= deflatedEnd) {
// the array has been completely deflated
return deflatedEnd;
}
final int k = 4 * deflatedEnd + pingPong - 1;
if ((start == deflatedEnd - 1) ||
((start != deflatedEnd - 2) &&
((work[k - 5] <= TOLERANCE_2 * (sigma + work[k - 3])) ||
(work[k - 2 * pingPong - 4] <= TOLERANCE_2 * work[k - 7])))) {
// one eigenvalue found, deflate array
work[4 * deflatedEnd - 4] = sigma + work[4 * deflatedEnd - 4 + pingPong];
deflatedEnd -= 1;
} else if ((start == deflatedEnd - 2) ||
(work[k - 9] <= TOLERANCE_2 * sigma) ||
(work[k - 2 * pingPong - 8] <= TOLERANCE_2 * work[k - 11])) {
// two eigenvalues found, deflate array
if (work[k - 3] > work[k - 7]) {
final double tmp = work[k - 3];
work[k - 3] = work[k - 7];
work[k - 7] = tmp;
}
if (work[k - 5] > TOLERANCE_2 * work[k - 3]) {
double t = 0.5 * ((work[k - 7] - work[k - 3]) + work[k - 5]);
double s = work[k - 3] * (work[k - 5] / t);
if (s <= t) {
s = work[k - 3] * work[k - 5] / (t * (1 + Math.sqrt(1 + s / t)));
} else {
s = work[k - 3] * work[k - 5] / (t + Math.sqrt(t * (t + s)));
}
t = work[k - 7] + (s + work[k - 5]);
work[k - 3] *= work[k - 7] / t;
work[k - 7] = t;
}
work[4 * deflatedEnd - 8] = sigma + work[k - 7];
work[4 * deflatedEnd - 4] = sigma + work[k - 3];
deflatedEnd -= 2;
} else {
// no more eigenvalues found, we need to iterate
deflating = false;
}
}
final int l = 4 * deflatedEnd + pingPong - 1;
// step 2: flip array if needed
if ((dMin <= 0) || (deflatedEnd < end)) {
if (flipIfWarranted(deflatedEnd, 1)) {
dMin2 = Math.min(dMin2, work[l - 1]);
work[l - 1] =
Math.min(work[l - 1],
Math.min(work[3 + pingPong], work[7 + pingPong]));
work[l - 2 * pingPong] =
Math.min(work[l - 2 * pingPong],
Math.min(work[6 + pingPong], work[6 + pingPong]));
qMax = Math.max(qMax, Math.max(work[3 + pingPong], work[7 + pingPong]));
dMin = -0.0;
}
}
if ((dMin < 0) ||
(MathUtils.SAFE_MIN * qMax < Math.min(work[l - 1],
Math.min(work[l - 9],
dMin2 + work[l - 2 * pingPong])))) {
// step 3: choose a shift
computeShiftIncrement(start, deflatedEnd, end - deflatedEnd);
// step 4a: dqds
for (boolean loop = true; loop;) {
// perform one dqds step with the chosen shift
dqds(start, deflatedEnd);
// check result of the dqds step
if ((dMin >= 0) && (dMin1 > 0)) {
// the shift was good
updateSigma(tau);
return deflatedEnd;
} else if ((dMin < 0.0) &&
(dMin1 > 0.0) &&
(work[4 * deflatedEnd - 5 - pingPong] < TOLERANCE * (sigma + dN1)) &&
(Math.abs(dN) < TOLERANCE * sigma)) {
// convergence hidden by negative DN.
work[4 * deflatedEnd - 3 - pingPong] = 0.0;
dMin = 0.0;
updateSigma(tau);
return deflatedEnd;
} else if (dMin < 0.0) {
// tau too big. Select new tau and try again.
if (tType < -22) {
// failed twice. Play it safe.
tau = 0.0;
} else if (dMin1 > 0.0) {
// late failure. Gives excellent shift.
tau = (tau + dMin) * (1.0 - 2.0 * MathUtils.EPSILON);
tType -= 11;
} else {
// early failure. Divide by 4.
tau *= 0.25;
tType -= 12;
}
} else if (Double.isNaN(dMin)) {
tau = 0.0;
} else {
// possible underflow. Play it safe.
loop = false;
}
}
}
// perform a dqd step (i.e. no shift)
dqd(start, deflatedEnd);
return deflatedEnd;
}
/**
* Flip qd array if warranted.
* @param n number of rows in the block
* @param step within the array (1 for flipping all elements, 2 for flipping
* only every other element)
* @return true if qd array was flipped
*/
private boolean flipIfWarranted(final int n, final int step) {
if (1.5 * work[pingPong] < work[4 * (n - 1) + pingPong]) {
// flip array
for (int i = 0, j = 4 * n - 1; i < j; i += 4, j -= 4) {
for (int k = 0; k < 4; k += step) {
final double tmp = work[i + k];
work[i + k] = work[j - k];
work[j - k] = tmp;
}
}
return true;
}
return false;
}
/**
* Compute an interval containing all eigenvalues of a block.
* @param index index of the first row of the block
* @param n number of rows of the block
* @return an interval containing the eigenvalues
*/
private double[] eigenvaluesRange(final int index, final int n) {
// find the bounds of the spectra of the local block
final int lowerStart = 4 * main.length;
final int upperStart = 5 * main.length;
double lower = Double.POSITIVE_INFINITY;
double upper = Double.NEGATIVE_INFINITY;
for (int i = 0; i < n; ++i) {
lower = Math.min(lower, work[lowerStart + index +i]);
upper = Math.max(upper, work[upperStart + index +i]);
}
// set thresholds
final double tNorm = Math.max(Math.abs(lower), Math.abs(upper));
final double relativeTolerance = Math.sqrt(MathUtils.EPSILON);
final double absoluteTolerance = 4 * minPivot;
final int maxIter =
2 + (int) ((Math.log(tNorm + minPivot) - Math.log(minPivot)) / Math.log(2.0));
final double margin = 2 * (tNorm * MathUtils.EPSILON * n + 2 * minPivot);
// search lower eigenvalue
double left = lower - margin;
double right = upper + margin;
for (int i = 0; i < maxIter; ++i) {
final double range = right - left;
if ((range < absoluteTolerance) ||
(range < relativeTolerance * Math.max(Math.abs(left), Math.abs(right)))) {
// search has converged
break;
}
final double middle = 0.5 * (left + right);
if (countEigenValues(middle, index, n) >= 1) {
right = middle;
} else {
left = middle;
}
}
lower = Math.max(lower, left - 100 * MathUtils.EPSILON * Math.abs(left));
// search upper eigenvalue
left = lower - margin;
right = upper + margin;
for (int i = 0; i < maxIter; ++i) {
final double range = right - left;
if ((range < absoluteTolerance) ||
(range < relativeTolerance * Math.max(Math.abs(left), Math.abs(right)))) {
// search has converged
break;
}
final double middle = 0.5 * (left + right);
if (countEigenValues(middle, index, n) >= n) {
right = middle;
} else {
left = middle;
}
}
upper = Math.min(upper, right + 100 * MathUtils.EPSILON * Math.abs(right));
return new double[] { lower, upper };
}
/**
* Count the number of eigenvalues below a point.
* @param t value below which we must count the number of eigenvalues
* @param index index of the first row of the block
* @param n number of rows of the block
* @return number of eigenvalues smaller than t
*/
private int countEigenValues(final double t, final int index, final int n) {
double ratio = main[index] - t;
int count = (ratio > 0) ? 0 : 1;
for (int i = 1; i < n; ++i) {
ratio = main[index + i] - squaredSecondary[index + i - 1] / ratio - t;
if (ratio <= 0) {
++count;
}
}
return count;
}
/**
* Decompose the shifted tridiagonal matrix T-&lambda;I as LDL<sup>T</sup>.
* <p>A shifted symmetric tridiagonal matrix T can be decomposed as
* LDL<sup>T</sup> where L is a lower bidiagonal matrix with unit diagonal
* and D is a diagonal matrix. This method is an implementation of
* algorithm 4.4.7 from Dhillon's thesis.</p>
* @param lambda shift to add to the matrix before decomposing it
* to ensure it is positive definite
* @param index index of the first row of the block
* @param n number of rows of the block
*/
private void ldlTDecomposition(final double lambda, final int index, final int n) {
double di = main[index] - lambda;
work[0] = Math.abs(di);
for (int i = 1; i < n; ++i) {
final int fourI = 4 * i;
final double eiM1 = secondary[index + i - 1];
final double ratio = eiM1 / di;
work[fourI - 2] = ratio * ratio * Math.abs(di);
di = (main[index + i] - lambda) - eiM1 * ratio;
work[fourI] = Math.abs(di);
}
}
/**
* Perform a dqds step, using current shift increment.
* <p>This implementation is a translation of the LAPACK routine DLASQ5.</p>
* @param start start index
* @param end end index
*/
private void dqds(final int start, final int end) {
eMin = work[4 * start + pingPong + 4];
double d = work[4 * start + pingPong] - tau;
dMin = d;
dMin1 = -work[4 * start + pingPong];
if (pingPong == 0) {
for (int j4 = 4 * start + 3; j4 <= 4 * (end - 3); j4 += 4) {
work[j4 - 2] = d + work[j4 - 1];
final double tmp = work[j4 + 1] / work[j4 - 2];
d = d * tmp - tau;
dMin = Math.min(dMin, d);
work[j4] = work[j4 - 1] * tmp;
eMin = Math.min(work[j4], eMin);
}
} else {
for (int j4 = 4 * start + 3; j4 <= 4 * (end - 3); j4 += 4) {
work[j4 - 3] = d + work[j4];
final double tmp = work[j4 + 2] / work[j4 - 3];
d = d * tmp - tau;
dMin = Math.min(dMin, d);
work[j4 - 1] = work[j4] * tmp;
eMin = Math.min(work[j4 - 1], eMin);
}
}
// unroll last two steps.
dN2 = d;
dMin2 = dMin;
int j4 = 4 * (end - 2) - pingPong - 1;
int j4p2 = j4 + 2 * pingPong - 1;
work[j4 - 2] = dN2 + work[j4p2];
work[j4] = work[j4p2 + 2] * (work[j4p2] / work[j4 - 2]);
dN1 = work[j4p2 + 2] * (dN2 / work[j4 - 2]) - tau;
dMin = Math.min(dMin, dN1);
dMin1 = dMin;
j4 = j4 + 4;
j4p2 = j4 + 2 * pingPong - 1;
work[j4 - 2] = dN1 + work[j4p2];
work[j4] = work[j4p2 + 2] * (work[j4p2] / work[j4 - 2]);
dN = work[j4p2 + 2] * (dN1 / work[j4 - 2]) - tau;
dMin = Math.min(dMin, dN);
work[j4 + 2] = dN;
work[4 * end - pingPong - 1] = eMin;
}
/**
* Perform a dqd step.
* <p>This implementation is a translation of the LAPACK routine DLASQ6.</p>
* @param start start index
* @param end end index
*/
private void dqd(final int start, final int end) {
eMin = work[4 * start + pingPong + 4];
double d = work[4 * start + pingPong];
dMin = d;
if (pingPong == 0) {
for (int j4 = 4 * start + 3; j4 < 4 * (end - 3); j4 += 4) {
work[j4 - 2] = d + work[j4 - 1];
if (work[j4 - 2] == 0.0) {
work[j4] = 0.0;
d = work[j4 + 1];
dMin = d;
eMin = 0.0;
} else if ((MathUtils.SAFE_MIN * work[j4 + 1] < work[j4 - 2]) &&
(MathUtils.SAFE_MIN * work[j4 - 2] < work[j4 + 1])) {
final double tmp = work[j4 + 1] / work[j4 - 2];
work[j4] = work[j4 - 1] * tmp;
d *= tmp;
} else {
work[j4] = work[j4 + 1] * (work[j4 - 1] / work[j4 - 2]);
d *= work[j4 + 1] / work[j4 - 2];
}
dMin = Math.min(dMin, d);
eMin = Math.min(eMin, work[j4]);
}
} else {
for (int j4 = 4 * start + 3; j4 < 4 * (end - 3); j4 += 4) {
work[j4 - 3] = d + work[j4];
if (work[j4 - 3] == 0.0) {
work[j4 - 1] = 0.0;
d = work[j4 + 2];
dMin = d;
eMin = 0.0;
} else if ((MathUtils.SAFE_MIN * work[j4 + 2] < work[j4 - 3]) &&
(MathUtils.SAFE_MIN * work[j4 - 3] < work[j4 + 2])) {
final double tmp = work[j4 + 2] / work[j4 - 3];
work[j4 - 1] = work[j4] * tmp;
d *= tmp;
} else {
work[j4 - 1] = work[j4 + 2] * (work[j4] / work[j4 - 3]);
d *= work[j4 + 2] / work[j4 - 3];
}
dMin = Math.min(dMin, d);
eMin = Math.min(eMin, work[j4 - 1]);
}
}
// Unroll last two steps
dN2 = d;
dMin2 = dMin;
int j4 = 4 * (end - 2) - pingPong - 1;
int j4p2 = j4 + 2 * pingPong - 1;
work[j4 - 2] = dN2 + work[j4p2];
if (work[j4 - 2] == 0.0) {
work[j4] = 0.0;
dN1 = work[j4p2 + 2];
dMin = dN1;
eMin = 0.0;
} else if ((MathUtils.SAFE_MIN * work[j4p2 + 2] < work[j4 - 2]) &&
(MathUtils.SAFE_MIN * work[j4 - 2] < work[j4p2 + 2])) {
final double tmp = work[j4p2 + 2] / work[j4 - 2];
work[j4] = work[j4p2] * tmp;
dN1 = dN2 * tmp;
} else {
work[j4] = work[j4p2 + 2] * (work[j4p2] / work[j4 - 2]);
dN1 = work[j4p2 + 2] * (dN2 / work[j4 - 2]);
}
dMin = Math.min(dMin, dN1);
dMin1 = dMin;
j4 = j4 + 4;
j4p2 = j4 + 2 * pingPong - 1;
work[j4 - 2] = dN1 + work[j4p2];
if (work[j4 - 2] == 0.0) {
work[j4] = 0.0;
dN = work[j4p2 + 2];
dMin = dN;
eMin = 0.0;
} else if ((MathUtils.SAFE_MIN * work[j4p2 + 2] < work[j4 - 2]) &&
(MathUtils.SAFE_MIN * work[j4 - 2] < work[j4p2 + 2])) {
final double tmp = work[j4p2 + 2] / work[j4 - 2];
work[j4] = work[j4p2] * tmp;
dN = dN1 * tmp;
} else {
work[j4] = work[j4p2 + 2] * (work[j4p2] / work[j4 - 2]);
dN = work[j4p2 + 2] * (dN1 / work[j4 - 2]);
}
dMin = Math.min(dMin, dN);
work[j4 + 2] = dN;
work[4 * end - pingPong - 1] = eMin;
}
/**
* Compute the shift increment as an estimate of the smallest eigenvalue.
* <p>This implementation is a translation of the LAPACK routine DLAZQ4.</p>
* @param start start index
* @param end end index
* @param deflated number of eigenvalues just deflated
*/
private void computeShiftIncrement(final int start, final int end, final int deflated) {
final double cnst1 = 0.563;
final double cnst2 = 1.010;
final double cnst3 = 1.05;
// a negative dMin forces the shift to take that absolute value
// tType records the type of shift.
if (dMin <= 0.0) {
tau = -dMin;
tType = -1;
return;
}
int nn = 4 * end + pingPong - 1;
switch (deflated) {
case 0 : // no eigenvalues deflated.
if (dMin == dN || dMin == dN1) {
double b1 = Math.sqrt(work[nn - 3]) * Math.sqrt(work[nn - 5]);
double b2 = Math.sqrt(work[nn - 7]) * Math.sqrt(work[nn - 9]);
double a2 = work[nn - 7] + work[nn - 5];
if (dMin == dN && dMin1 == dN1) {
// cases 2 and 3.
final double gap2 = dMin2 - a2 - dMin2 * 0.25;
final double gap1 = a2 - dN - ((gap2 > 0.0 && gap2 > b2) ? (b2 / gap2) * b2 : (b1 + b2));
if (gap1 > 0.0 && gap1 > b1) {
tau = Math.max(dN - (b1 / gap1) * b1, 0.5 * dMin);
tType = -2;
} else {
double s = 0.0;
if (dN > b1) {
s = dN - b1;
}
if (a2 > (b1 + b2)) {
s = Math.min(s, a2 - (b1 + b2));
}
tau = Math.max(s, 0.333 * dMin);
tType = -3;
}
} else {
// case 4.
tType = -4;
double s = 0.25 * dMin;
double gam;
int np;
if (dMin == dN) {
gam = dN;
a2 = 0.0;
if (work[nn - 5] > work[nn - 7]) {
return;
}
b2 = work[nn - 5] / work[nn - 7];
np = nn - 9;
} else {
np = nn - 2 * pingPong;
b2 = work[np - 2];
gam = dN1;
if (work[np - 4] > work[np - 2]) {
return;
}
a2 = work[np - 4] / work[np - 2];
if (work[nn - 9] > work[nn - 11]) {
return;
}
b2 = work[nn - 9] / work[nn - 11];
np = nn - 13;
}
// approximate contribution to norm squared from i < nn-1.
a2 = a2 + b2;
for (int i4 = np; i4 >= 4 * start + 2 + pingPong; i4 -= 4) {
if(b2 == 0.0) {
break;
}
b1 = b2;
if (work[i4] > work[i4 - 2]) {
return;
}
b2 = b2 * (work[i4] / work[i4 - 2]);
a2 = a2 + b2;
if (100 * Math.max(b2, b1) < a2 || cnst1 < a2) {
break;
}
}
a2 = cnst3 * a2;
// rayleigh quotient residual bound.
if (a2 < cnst1) {
s = gam * (1 - Math.sqrt(a2)) / (1 + a2);
}
tau = s;
}
} else if (dMin == dN2) {
// case 5.
tType = -5;
double s = 0.25 * dMin;
// compute contribution to norm squared from i > nn-2.
final int np = nn - 2 * pingPong;
double b1 = work[np - 2];
double b2 = work[np - 6];
final double gam = dN2;
if (work[np - 8] > b2 || work[np - 4] > b1) {
return;
}
double a2 = (work[np - 8] / b2) * (1 + work[np - 4] / b1);
// approximate contribution to norm squared from i < nn-2.
if (end - start > 2) {
b2 = work[nn - 13] / work[nn - 15];
a2 = a2 + b2;
for (int i4 = nn - 17; i4 >= 4 * start + 2 + pingPong; i4 -= 4) {
if (b2 == 0.0) {
break;
}
b1 = b2;
if (work[i4] > work[i4 - 2]) {
return;
}
b2 = b2 * (work[i4] / work[i4 - 2]);
a2 = a2 + b2;
if (100 * Math.max(b2, b1) < a2 || cnst1 < a2) {
break;
}
}
a2 = cnst3 * a2;
}
if (a2 < cnst1) {
tau = gam * (1 - Math.sqrt(a2)) / (1 + a2);
} else {
tau = s;
}
} else {
// case 6, no information to guide us.
if (tType == -6) {
g += 0.333 * (1 - g);
} else if (tType == -18) {
g = 0.25 * 0.333;
} else {
g = 0.25;
}
tau = g * dMin;
tType = -6;
}
break;
case 1 : // one eigenvalue just deflated. use dMin1, dN1 for dMin and dN.
if (dMin1 == dN1 && dMin2 == dN2) {
// cases 7 and 8.
tType = -7;
double s = 0.333 * dMin1;
if (work[nn - 5] > work[nn - 7]) {
return;
}
double b1 = work[nn - 5] / work[nn - 7];
double b2 = b1;
if (b2 != 0.0) {
for (int i4 = 4 * end - 10 + pingPong; i4 >= 4 * start + 2 + pingPong; i4 -= 4) {
final double oldB1 = b1;
if (work[i4] > work[i4 - 2]) {
return;
}
b1 = b1 * (work[i4] / work[i4 - 2]);
b2 = b2 + b1;
if (100 * Math.max(b1, oldB1) < b2) {
break;
}
}
}
b2 = Math.sqrt(cnst3 * b2);
final double a2 = dMin1 / (1 + b2 * b2);
final double gap2 = 0.5 * dMin2 - a2;
if (gap2 > 0.0 && gap2 > b2 * a2) {
tau = Math.max(s, a2 * (1 - cnst2 * a2 * (b2 / gap2) * b2));
} else {
tau = Math.max(s, a2 * (1 - cnst2 * b2));
tType = -8;
}
} else {
// case 9.
tau = 0.25 * dMin1;
if (dMin1 == dN1) {
tau = 0.5 * dMin1;
}
tType = -9;
}
break;
case 2 : // two eigenvalues deflated. use dMin2, dN2 for dMin and dN.
// cases 10 and 11.
if (dMin2 == dN2 && 2 * work[nn - 5] < work[nn - 7]) {
tType = -10;
final double s = 0.333 * dMin2;
if (work[nn - 5] > work[nn - 7]) {
return;
}
double b1 = work[nn - 5] / work[nn - 7];
double b2 = b1;
if (b2 != 0.0){
for (int i4 = 4 * end - 9 + pingPong; i4 >= 4 * start + 2 + pingPong; i4 -= 4) {
if (work[i4] > work[i4 - 2]) {
return;
}
b1 *= work[i4] / work[i4 - 2];
b2 += b1;
if (100 * b1 < b2) {
break;
}
}
}
b2 = Math.sqrt(cnst3 * b2);
final double a2 = dMin2 / (1 + b2 * b2);
final double gap2 = work[nn - 7] + work[nn - 9] -
Math.sqrt(work[nn - 11]) * Math.sqrt(work[nn - 9]) - a2;
if (gap2 > 0.0 && gap2 > b2 * a2) {
tau = Math.max(s, a2 * (1 - cnst2 * a2 * (b2 / gap2) * b2));
} else {
tau = Math.max(s, a2 * (1 - cnst2 * b2));
}
} else {
tau = 0.25 * dMin2;
tType = -11;
}
break;
default : // case 12, more than two eigenvalues deflated. no information.
tau = 0.0;
tType = -12;
}
}
/**
* Update sigma.
* @param tau shift to apply to sigma
*/
private void updateSigma(final double tau) {
// BEWARE: do NOT attempt to simplify the following statements
// the expressions below take care to accumulate the part of sigma
// that does not fit within a double variable into sigmaLow
if (tau < sigma) {
sigmaLow += tau;
final double t = sigma + sigmaLow;
sigmaLow -= t - sigma;
sigma = t;
} else {
final double t = sigma + tau;
sigmaLow += sigma - (t - tau);
sigma = t;
}
}
/**
* Find eigenvectors.
*/
private void findEigenVectors() {
final int m = main.length;
eigenvectors = new RealVectorImpl[m];
// perform an initial non-shifted LDLt decomposition
final double[] d = new double[m];
final double[] l = new double[m - 1];
double di = main[0];
d[0] = di;
for (int i = 1; i < m; ++i) {
final double eiM1 = secondary[i - 1];
final double ratio = eiM1 / di;
di = main[i] - eiM1 * ratio;
l[i - 1] = ratio;
d[i] = di;
}
// compute eigenvectors
for (int i = 0; i < m; ++i) {
eigenvectors[i] = findEigenvector(eigenvalues[i], d, l);
}
}
/**
* Find an eigenvector corresponding to an eigenvalue, using bidiagonals.
* <p>This method corresponds to algorithm X from Dhillon's thesis.</p>
*
* @param eigenvalue eigenvalue for which eigenvector is desired
* @param d diagonal elements of the initial non-shifted D matrix
* @param l off-diagonal elements of the initial non-shifted L matrix
* @return an eigenvector
*/
private RealVectorImpl findEigenvector(final double eigenvalue,
final double[] d, final double[] l) {
// compute the LDLt and UDUt decompositions of the
// perfectly shifted tridiagonal matrix
final int m = main.length;
stationaryQuotientDifferenceWithShift(d, l, eigenvalue);
progressiveQuotientDifferenceWithShift(d, l, eigenvalue);
// select the twist index leading to
// the least diagonal element in the twisted factorization
int r = m - 1;
double minG = Math.abs(work[6 * r] + work[6 * r + 3] + eigenvalue);
for (int i = 0, sixI = 0; i < m - 1; ++i, sixI += 6) {
final double g = work[sixI] + d[i] * work[sixI + 9] / work[sixI + 10];
final double absG = Math.abs(g);
if (absG < minG) {
r = i;
minG = absG;
}
}
// solve the singular system by ignoring the equation
// at twist index and propagating upwards and downwards
double[] eigenvector = new double[m];
double n2 = 1;
eigenvector[r] = 1;
double z = 1;
for (int i = r - 1; i >= 0; --i) {
z *= -work[6 * i + 2];
eigenvector[i] = z;
n2 += z * z;
}
z = 1;
for (int i = r + 1; i < m; ++i) {
z *= -work[6 * i - 1];
eigenvector[i] = z;
n2 += z * z;
}
// normalize vector
final double inv = 1.0 / Math.sqrt(n2);
for (int i = 0; i < m; ++i) {
eigenvector[i] *= inv;
}
return new RealVectorImpl(orthoTridiag.operate(eigenvector), true);
}
/**
* Decompose matrix LDL<sup>T</sup> - &lambda; I as
* L<sub>+</sub>D<sub>+</sub>L<sub>+</sub><sup>T</sup>.
* <p>This method corresponds to algorithm 4.4.3 (dstqds) from Dhillon's thesis.</p>
* @param d diagonal elements of D,
* @param l off-diagonal elements of L
* @param lambda shift to apply
*/
private void stationaryQuotientDifferenceWithShift(final double[] d, final double[] l,
final double lambda) {
final int nM1 = d.length - 1;
double si = -lambda;
for (int i = 0, sixI = 0; i < nM1; ++i, sixI += 6) {
final double di = d[i];
final double li = l[i];
final double diP1 = di + si;
final double liP1 = li * di / diP1;
work[sixI] = si;
work[sixI + 1] = diP1;
work[sixI + 2] = liP1;
si = li * liP1 * si - lambda;
}
work[6 * nM1 + 1] = d[nM1] + si;
work[6 * nM1] = si;
}
/**
* Decompose matrix LDL<sup>T</sup> - &lambda; I as
* U<sub>-</sub>D<sub>-</sub>U<sub>-</sub><sup>T</sup>.
* <p>This method corresponds to algorithm 4.4.5 (dqds) from Dhillon's thesis.</p>
* @param d diagonal elements of D
* @param l off-diagonal elements of L
* @param lambda shift to apply
*/
private void progressiveQuotientDifferenceWithShift(final double[] d, final double[] l,
final double lambda) {
final int nM1 = d.length - 1;
double pi = d[nM1] - lambda;
for (int i = nM1 - 1, sixI = 6 * i; i >= 0; --i, sixI -= 6) {
final double di = d[i];
final double li = l[i];
final double diP1 = di * li * li + pi;
final double t = di / diP1;
work[sixI + 9] = pi;
work[sixI + 10] = diP1;
work[sixI + 5] = li * t;
pi = pi * t - lambda;
}
work[3] = pi;
work[4] = pi;
}
/**
* Check if decomposition has been performed.
* @exception IllegalStateException if {@link #decompose(RealMatrix) decompose}
* has not been called
*/
private void checkDecomposed()
throws IllegalStateException {
if (eigenvalues == null) {
throw MathRuntimeException.createIllegalStateException("no matrix have been decomposed yet", null);
}
}
/**
* Check if decomposed matrix is non singular.
* @exception IllegalStateException if {@link #decompose(RealMatrix) decompose}
* has not been called
* @exception SingularMatrixException if decomposed matrix is singular
*/
private void checkNonSingular()
throws IllegalStateException, SingularMatrixException {
checkDecomposed();
if (!isNonSingular()) {
throw new SingularMatrixException();
}
}
}