Skip to content

Commit

Permalink
Fixed issues with determinant calculation in upper and lower triangul…
Browse files Browse the repository at this point in the history
…ar matrix inversion calculation.
  • Loading branch information
jacobdwatters committed Jul 10, 2024
1 parent 751e815 commit 5887265
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 210 deletions.
205 changes: 33 additions & 172 deletions src/main/java/org/flag4j/linalg/decompositions/hess/SymmHess.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@


import org.flag4j.dense.Matrix;
import org.flag4j.linalg.decompositions.Decomposition;
import org.flag4j.linalg.transformations.Householder;
import org.flag4j.util.Flag4jConstants;
import org.flag4j.util.ParameterChecks;
import org.flag4j.util.exceptions.LinearAlgebraException;

Expand All @@ -52,67 +50,19 @@
* [ 0 0 0 x x ]]</pre>
* </p>
*/
public class SymmHess implements Decomposition<Matrix> {
public class SymmHess extends RealHess {

/**
* Flag indicating if an explicit check should be made that the matrix to be decomposed is symmetric.
*/
protected boolean enforceSymmetric;

/**
* Storage for the symmetric tri-diagonal matrix and if requested, the Householder vectors used to bring the original matrix
* into upper Hessenberg form. The symmetric tri-diagonal matrix will be stored in the principle diagonal and the first
* super-diagonal (Since the matrix is symmetric there is no need to store the first sub-diagonal). The rows of the strictly
* lower-triangular portion of the matrix will be used to store the Householder vectors used to transform the source matrix
* to upper Hessenburg form if it is requested via {@link #computeQ}. These can be used to compute the full orthogonal matrix
* {@code Q} of the Hessenberg decomposition.
*/
protected Matrix transformMatrix;
/**
* For storing norms of columns in A when computing Householder reflectors.
*/
double norm;
/**
* Size of the symmetric matrix to be decomposed. That is, the number of rows and columns.
*/
protected int size;
/**
* Flag indicating if the orthogonal transformation matrix from the Hessenburg decomposition should be explicitly computed.
*/
protected boolean computeQ;
/**
* Stores the scalar factor for the current Householder reflector.
*/
double currentFactor;
/**
* Storage of the scalar factors for the Householder reflectors used in the decomposition.
*/
protected double[] qFactors;
/**
* For storing a Householder vectors.
*/
protected double[] householderVector;
/**
* For temporarily storage when applying Householder vectors. This is useful for
* avoiding unneeded garbage collection and for improving cache performance when traversing columns.
*/
protected double[] workArray;
/**
* Flag indicating if a Householder reflector was needed for the current column meaning an update needs to be applied.
*/
protected boolean applyUpdate;
/**
* Stores the shifted value of the first entry in a Householder vector.
*/
private double shift;


/**
* Constructs a Hessenberg decomposer for symmetric matrices. To compute the
* Constructs a Hessenberg decomposer for symmetric matrices. By default, the Householder vectors used in the decomposition will be
* stored so that the full orthogonal {@code Q} matrix can be formed by calling {@link #getQ()}.
*/
public SymmHess() {
computeQ = false;
enforceSymmetric = false;
super();
}


Expand All @@ -122,8 +72,7 @@ public SymmHess() {
* If true, then the {@code Q} matrix will be computed explicitly.
*/
public SymmHess(boolean computeQ) {
enforceSymmetric = false;
this.computeQ = computeQ;
super(computeQ);
}


Expand All @@ -137,8 +86,8 @@ public SymmHess(boolean computeQ) {
* matrix is not symmetric, then the values in the upper triangular portion of the matrix are taken to be the values.
*/
public SymmHess(boolean computeQ, boolean enforceSymmetric) {
super(computeQ);
this.enforceSymmetric = enforceSymmetric;
this.computeQ = computeQ;
}


Expand All @@ -150,14 +99,7 @@ public SymmHess(boolean computeQ, boolean enforceSymmetric) {
*/
@Override
public SymmHess decompose(Matrix src) {
setUp(src);
int stop = size-2;

for(int k=0; k<stop; k++) {
computeHouseholder(k+1);
if(applyUpdate) updateData(k+1);
}

super.decompose(src);
return this;
}

Expand All @@ -166,17 +108,18 @@ public SymmHess decompose(Matrix src) {
* Gets the Hessenberg matrix from the decomposition. The matrix will be symmetric tri-diagonal.
* @return The symmetric tri-diagonal (Hessenberg) matrix from this decomposition.
*/
@Override
public Matrix getH() {
Matrix H = new Matrix(size);
Matrix H = new Matrix(numRows);
H.entries[0] = transformMatrix.entries[0];

int idx1;
int idx0;
int rowOffset = H.numRows;
int rowOffset = numRows;

for(int i=1; i<size; i++) {
for(int i=1; i<numRows; i++) {
idx1 = rowOffset + i;
idx0 = idx1 - H.numRows;
idx0 = idx1 - numRows;

H.entries[idx1] = transformMatrix.entries[idx1]; // extract diagonal value.

Expand All @@ -186,114 +129,32 @@ public Matrix getH() {
H.entries[idx1 - 1] = a;

// Update row index.
rowOffset += H.numRows;
rowOffset += numRows;
}

if(size > 1) {
int rowColBase = size*size - 1;
if(numRows > 1) {
int rowColBase = numRows*numRows - 1;
H.entries[rowColBase] = transformMatrix.entries[rowColBase];
H.entries[rowColBase - 1] = transformMatrix.entries[rowColBase - size];
H.entries[rowColBase - 1] = transformMatrix.entries[rowColBase - numRows];
}

return H;
}


/**
* <p>Gets the unitary {@code Q} matrix from the Hessenberg decomposition.</p>
*
* <p>Note, if the reflectors for this decomposition were not saved, then {@code Q} can not be computed and this method will be
* null.</p>
*
* @return The {@code Q} matrix from the {@code QR} decomposition. Note, if the reflectors for this decomposition were not saved,
* then {@code Q} can not be computed and this method will return {@code null}.
*/
public Matrix getQ() {
if(!computeQ)
return null;

Matrix Q = Matrix.I(size);

for(int j=size - 1; j>=1; j--) {
householderVector[j] = 1.0; // Ensure first value of reflector is 1.

for(int i=j + 1; i<size; i++) {
householderVector[i] = transformMatrix.entries[i*size + j - 1]; // Extract column containing reflector vector.
}

if(qFactors[j]!=0) { // Otherwise, no reflector to apply.
Householder.leftMultReflector(Q, householderVector, qFactors[j], j, j, size, workArray);
}
}

return Q;
}


/**
* Computes the Householder vector for the first column of the sub-matrix with upper left corner at {@code (j, j)}.
*
* @param j Index of the upper left corner of the sub-matrix for which to compute the Householder vector for the first column.
* That is, a Householder vector will be computed for the portion of column {@code j} below row {@code j}.
*/
protected void computeHouseholder(int j) {
// Initialize storage array for Householder vector and compute maximum absolute value in jth column at or below jth row.
double maxAbs = findMaxAndInit(j);
norm = 0; // Ensure norm is reset.

applyUpdate = maxAbs >= Flag4jConstants.EPS_F64;

if(!applyUpdate) {
currentFactor = 0;
} else {
computePhasedNorm(j, maxAbs);

householderVector[j] = 1.0; // Ensure first value in Householder vector is one.
for(int i=j+1; i<size; i++) {
householderVector[i] /= shift; // Scale all but first entry of the Householder vector.
}
}

qFactors[j] = currentFactor; // Store the factor for the Householder vector.
}


/**
* Computes the norm of column {@code j} below the {@code j}th row of the matrix to be decomposed. The norm will have the same
* parity as the first entry in the sub-column.
* @param j Column to compute norm of below the {@code j}th row.
* @param maxAbs Maximum absolute value in the column. Used for scaling norm to minimize potential overflow issues.
*/
protected void computePhasedNorm(int j, double maxAbs) {
// Computes the 2-norm of the column.
for(int i=j; i<size; i++) {
householderVector[i] /= maxAbs; // Scale entries of the householder vector to help reduce potential overflow.
double scaledValue = householderVector[i];
norm += scaledValue*scaledValue;
}
norm = Math.sqrt(norm); // Finish 2-norm computation for the column.

// Change sign of norm depending on first entry in column for stability purposes in Householder vector.
if(householderVector[j] < 0) norm = -norm;

shift = householderVector[j] + norm;
currentFactor = shift/norm;
norm *= maxAbs; // Rescale norm.
}


/**
* Finds the maximum value in {@link #transformMatrix} at column {@code j} at or below the {@code j}th row. This method also initializes
* the first {@code numRows-j} entries of the storage array {@link #householderVector} to the entries of this column.
* @param j Index of column (and starting row) to compute max of.
* @return The maximum value in {@link #transformMatrix} at column {@code j} at or below the {@code j}th row.
*/
@Override
protected double findMaxAndInit(int j) {
double maxAbs = 0;

// Compute max-abs value in row. (Equivalent to max value in column since matrix is symmetric.)
int rowU = (j-1)*size;
for(int i=j; i<size; i++) {
int rowU = (j-1)*numRows;
for(int i=j; i<numRows; i++) {
double d = householderVector[i] = transformMatrix.entries[rowU + i];
maxAbs = Math.max(Math.abs(d), maxAbs);
}
Expand All @@ -308,31 +169,30 @@ protected double findMaxAndInit(int j) {
* @throws LinearAlgebraException If the matrix is not symmetric and {@link #enforceSymmetric} is true or if the matrix is not
* square regardless of the value of {@link #enforceSymmetric}.
*/
@Override
protected void setUp(Matrix src) {
if(enforceSymmetric && !src.isSymmetric()) // If requested, check the matrix is symmetric.
throw new LinearAlgebraException("Decomposition only supports symmetric matrices.");
else
ParameterChecks.assertSquareMatrix(src.shape); // Otherwise, Just ensure the matrix is square.

size = src.numRows;
householderVector = new double[size];
qFactors = new double[size];
workArray = new double[size];
copyUpperTri(src);
numRows = numCols = minAxisSize = src.numRows;
copyUpperTri(src); // Initializes transform matrix.
initWorkArrays(numRows);
}


/**
* Copies the upper triangular portion of a matrix to the working matrix {@link #transformMatrix}.
* @param src The source matrix to decompose of.
*/
protected void copyUpperTri(Matrix src) {
transformMatrix = new Matrix(size);
private void copyUpperTri(Matrix src) {
transformMatrix = new Matrix(numRows);

// Copy upper triangular portion.
for(int i=0; i<size; i++) {
int pos = i*size + i;
System.arraycopy(src.entries, pos, transformMatrix.entries, pos, size - i);
for(int i=0; i<numRows; i++) {
int pos = i*numRows + i;
System.arraycopy(src.entries, pos, transformMatrix.entries, pos, numRows - i);
}
}

Expand All @@ -341,15 +201,16 @@ protected void copyUpperTri(Matrix src) {
* Updates the {@link #transformMatrix} matrix using the computed Householder vector from {@link #computeHouseholder(int)}.
* @param j Index of sub-matrix for which the Householder reflector was computed for.
*/
@Override
protected void updateData(int j) {
Householder.symmLeftRightMultReflector(transformMatrix, householderVector, currentFactor, j, workArray);

if(j < size) transformMatrix.entries[(j-1)*size + j] = -norm;
if(computeQ) {
if(j < numRows) transformMatrix.entries[(j-1)*numRows + j] = -norm;
if(storeReflectors) {
// Store the Q matrix in the lower portion of the transformation data matrix.
int col = j-1;
for(int i=j+1; i<size; i++) {
transformMatrix.entries[i*size + col] = householderVector[i];
for(int i=j+1; i<numRows; i++) {
transformMatrix.entries[i*numRows + col] = householderVector[i];
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ public abstract class RealUnitaryDecomposition extends UnitaryDecomposition<Matr
/**
* Scalar factor of the currently computed Householder reflector.
*/
private double currentFactor;
protected double currentFactor;
/**
* Stores the shifted value of the first entry in a Householder vector.
*/
private double shift;
protected double shift;


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ public void decomposeBase(T src) {

for(int j=0; j<minAxisSize-offSet; j++) {
computeHouseholder(j + subDiagonal); // Compute the householder reflector.

if(applyUpdate) updateData(j + subDiagonal); // Update the upper-triangular matrix and store the reflectors.
}
}
Expand Down Expand Up @@ -173,7 +172,7 @@ public void decomposeBase(T src) {
* Initializes storage and other parameters for the decomposition.
* @param src Source matrix to be decomposed.
*/
private void setUp(T src) {
protected void setUp(T src) {
transformMatrix = src.copy(); // Initialize QR as the matrix to be decomposed.
numRows = transformMatrix.numRows();
numCols = transformMatrix.numCols();
Expand Down
Loading

0 comments on commit 5887265

Please sign in to comment.