Skip to content

Commit 78c895b

Browse files
committed
Starting to add documentation for all methods and public variables. Trying to make sure advanced concepts are explained in an easy to understand method so let me know if you like the for beginner sections
1 parent 6cdfdf7 commit 78c895b

31 files changed

+3625
-602
lines changed

src/DecompositionMethods/MatrixDecomposition/BidiagonalDecomposition.cs

+143-22
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,61 @@
1-
using AiDotNet.Enums.AlgorithmTypes;
2-
31
namespace AiDotNet.DecompositionMethods.MatrixDecomposition;
42

53
public class BidiagonalDecomposition<T> : IMatrixDecomposition<T>
64
{
7-
private readonly INumericOperations<T> NumOps;
5+
private readonly INumericOperations<T> _numOps;
86

7+
/// <summary>
8+
/// Gets the original matrix that was decomposed.
9+
/// </summary>
910
public Matrix<T> A { get; }
11+
12+
/// <summary>
13+
/// Gets the left orthogonal matrix in the decomposition.
14+
/// In simpler terms, this matrix helps transform the original matrix's columns.
15+
/// </summary>
1016
public Matrix<T> U { get; private set; }
17+
18+
/// <summary>
19+
/// Gets the bidiagonal matrix in the decomposition.
20+
/// A bidiagonal matrix is a special matrix where non-zero values appear only on the main diagonal
21+
/// and the diagonal immediately above it (called the superdiagonal).
22+
/// </summary>
1123
public Matrix<T> B { get; private set; }
24+
25+
/// <summary>
26+
/// Gets the right orthogonal matrix in the decomposition.
27+
/// In simpler terms, this matrix helps transform the original matrix's rows.
28+
/// </summary>
1229
public Matrix<T> V { get; private set; }
1330

31+
/// <summary>
32+
/// Creates a new bidiagonal decomposition of the specified matrix.
33+
/// </summary>
34+
/// <param name="matrix">The matrix to decompose.</param>
35+
/// <param name="algorithm">The algorithm to use for decomposition (default is Householder).</param>
36+
/// <remarks>
37+
/// Bidiagonal decomposition breaks down a matrix A into three simpler matrices: U, B, and V,
38+
/// where A = U*B*V^T. This makes many matrix operations easier to perform.
39+
/// </remarks>
1440
public BidiagonalDecomposition(Matrix<T> matrix, BidiagonalAlgorithmType algorithm = BidiagonalAlgorithmType.Householder)
1541
{
1642
A = matrix;
17-
NumOps = MathHelper.GetNumericOperations<T>();
43+
_numOps = MathHelper.GetNumericOperations<T>();
1844
U = new Matrix<T>(matrix.Rows, matrix.Rows);
1945
B = new Matrix<T>(matrix.Columns, matrix.Columns);
2046
V = new Matrix<T>(matrix.Columns, matrix.Columns);
2147
Decompose(algorithm);
2248
}
2349

50+
/// <summary>
51+
/// Performs the bidiagonal decomposition using the specified algorithm.
52+
/// </summary>
53+
/// <param name="algorithm">The algorithm to use for decomposition.</param>
54+
/// <exception cref="ArgumentException">Thrown when an unsupported algorithm is specified.</exception>
55+
/// <remarks>
56+
/// Different algorithms have different performance characteristics and numerical stability.
57+
/// Householder is generally the most stable and commonly used method.
58+
/// </remarks>
2459
public void Decompose(BidiagonalAlgorithmType algorithm = BidiagonalAlgorithmType.Householder)
2560
{
2661
switch (algorithm)
@@ -39,6 +74,13 @@ public void Decompose(BidiagonalAlgorithmType algorithm = BidiagonalAlgorithmTyp
3974
}
4075
}
4176

77+
/// <summary>
78+
/// Performs bidiagonal decomposition using Householder reflections.
79+
/// </summary>
80+
/// <remarks>
81+
/// Householder reflections are a way to transform vectors by reflecting them across a plane.
82+
/// This method is numerically stable and commonly used for matrix decompositions.
83+
/// </remarks>
4284
private void DecomposeHouseholder()
4385
{
4486
int m = A.Rows;
@@ -54,7 +96,7 @@ private void DecomposeHouseholder()
5496
Vector<T> v = HouseholderVector(x);
5597

5698
// Apply Householder reflection to B
57-
Matrix<T> P = Matrix<T>.CreateIdentity(m - k).Subtract(v.OuterProduct(v).Multiply(NumOps.FromDouble(2)));
99+
Matrix<T> P = Matrix<T>.CreateIdentity(m - k).Subtract(v.OuterProduct(v).Multiply(_numOps.FromDouble(2)));
58100
Matrix<T> subB = B.GetSubMatrix(k, k, m - k, n - k);
59101
B.SetSubMatrix(k, k, P.Multiply(subB));
60102

@@ -69,7 +111,7 @@ private void DecomposeHouseholder()
69111
v = HouseholderVector(x);
70112

71113
// Apply Householder reflection to B
72-
P = Matrix<T>.CreateIdentity(n - k - 1).Subtract(v.OuterProduct(v).Multiply(NumOps.FromDouble(2)));
114+
P = Matrix<T>.CreateIdentity(n - k - 1).Subtract(v.OuterProduct(v).Multiply(_numOps.FromDouble(2)));
73115
subB = B.GetSubMatrix(k, k + 1, m - k, n - k - 1);
74116
B.SetSubMatrix(k, k + 1, subB.Multiply(P));
75117

@@ -80,6 +122,13 @@ private void DecomposeHouseholder()
80122
}
81123
}
82124

125+
/// <summary>
126+
/// Performs bidiagonal decomposition using Givens rotations.
127+
/// </summary>
128+
/// <remarks>
129+
/// Givens rotations are a way to zero out specific elements in a matrix by rotating
130+
/// two rows or columns. This method is useful for creating bidiagonal matrices.
131+
/// </remarks>
83132
private void DecomposeGivens()
84133
{
85134
int m = A.Rows;
@@ -105,6 +154,14 @@ private void DecomposeGivens()
105154
}
106155
}
107156

157+
/// <summary>
158+
/// Performs bidiagonal decomposition using the Lanczos algorithm.
159+
/// </summary>
160+
/// <remarks>
161+
/// The Lanczos algorithm is an iterative method that can efficiently compute
162+
/// partial decompositions of large matrices. It's particularly useful for
163+
/// sparse matrices (matrices with mostly zero values).
164+
/// </remarks>
108165
private void DecomposeLanczos()
109166
{
110167
int m = A.Rows;
@@ -120,7 +177,7 @@ private void DecomposeLanczos()
120177
Random rand = new();
121178
for (int i = 0; i < n; i++)
122179
{
123-
v[i] = NumOps.FromDouble(rand.NextDouble());
180+
v[i] = _numOps.FromDouble(rand.NextDouble());
124181
}
125182
v = v.Divide(v.Norm());
126183

@@ -142,6 +199,16 @@ private void DecomposeLanczos()
142199
}
143200
}
144201

202+
/// <summary>
203+
/// Solves the linear system Ax = b using the bidiagonal decomposition.
204+
/// </summary>
205+
/// <param name="b">The right-hand side vector of the equation Ax = b.</param>
206+
/// <returns>The solution vector x.</returns>
207+
/// <exception cref="ArgumentException">Thrown when the length of vector b doesn't match the number of rows in matrix A.</exception>
208+
/// <remarks>
209+
/// This method uses the decomposition to efficiently solve the system without
210+
/// directly inverting the matrix, which is more numerically stable.
211+
/// </remarks>
145212
public Vector<T> Solve(Vector<T> b)
146213
{
147214
if (b.Length != A.Rows)
@@ -153,6 +220,14 @@ public Vector<T> Solve(Vector<T> b)
153220
return V.Multiply(z);
154221
}
155222

223+
/// <summary>
224+
/// Computes the inverse of the original matrix A using the bidiagonal decomposition.
225+
/// </summary>
226+
/// <returns>The inverse matrix of A.</returns>
227+
/// <remarks>
228+
/// Matrix inversion is computationally expensive and can be numerically unstable.
229+
/// When possible, use the Solve method instead of explicitly computing the inverse.
230+
/// </remarks>
156231
public Matrix<T> Invert()
157232
{
158233
int n = A.Columns;
@@ -161,46 +236,69 @@ public Matrix<T> Invert()
161236
for (int i = 0; i < n; i++)
162237
{
163238
Vector<T> ei = new Vector<T>(n);
164-
ei[i] = NumOps.One;
239+
ei[i] = _numOps.One;
165240
inverse.SetColumn(i, Solve(ei));
166241
}
167242

168243
return inverse;
169244
}
170245

246+
/// <summary>
247+
/// Computes a Householder reflection vector for the given input vector.
248+
/// </summary>
249+
/// <param name="x">The input vector.</param>
250+
/// <returns>A Householder vector that can be used to create a reflection matrix.</returns>
251+
/// <remarks>
252+
/// A Householder reflection is a transformation that reflects a vector across a plane.
253+
/// It's used to zero out specific elements in a matrix during decomposition.
254+
/// </remarks>
171255
private Vector<T> HouseholderVector(Vector<T> x)
172256
{
173257
T norm = x.Norm();
174258
Vector<T> v = x.Copy();
175-
v[0] = NumOps.Add(v[0], NumOps.Multiply(NumOps.SignOrZero(x[0]), norm));
259+
v[0] = _numOps.Add(v[0], _numOps.Multiply(_numOps.SignOrZero(x[0]), norm));
176260

177261
return v.Divide(v.Norm());
178262
}
179263

264+
/// <summary>
265+
/// Applies a Givens rotation to the specified matrices.
266+
/// </summary>
267+
/// <param name="M">The matrix to which the rotation is applied.</param>
268+
/// <param name="Q">The orthogonal matrix that accumulates the rotations.</param>
269+
/// <param name="i">The first row/column index for the rotation.</param>
270+
/// <param name="k">The second row/column index for the rotation.</param>
271+
/// <param name="j">The first column/row index for the rotation.</param>
272+
/// <param name="l">The second column/row index for the rotation.</param>
273+
/// <param name="isLeft">If true, applies a left rotation (row operation); otherwise, applies a right rotation (column operation).</param>
274+
/// <remarks>
275+
/// A Givens rotation is a simple rotation in a plane spanned by two coordinate axes.
276+
/// It's used to selectively zero out specific elements in a matrix during decomposition.
277+
/// </remarks>
180278
private void GivensRotation(Matrix<T> M, Matrix<T> Q, int i, int k, int j, int l, bool isLeft)
181279
{
182280
T a = M[i, j];
183281
T b = M[k, l];
184-
T r = NumOps.Sqrt(NumOps.Add(NumOps.Multiply(a, a), NumOps.Multiply(b, b)));
185-
T c = NumOps.Divide(a, r);
186-
T s = NumOps.Divide(b, r);
282+
T r = _numOps.Sqrt(_numOps.Add(_numOps.Multiply(a, a), _numOps.Multiply(b, b)));
283+
T c = _numOps.Divide(a, r);
284+
T s = _numOps.Divide(b, r);
187285

188286
if (isLeft)
189287
{
190288
for (int j2 = 0; j2 < M.Columns; j2++)
191289
{
192290
T temp1 = M[i, j2];
193291
T temp2 = M[k, j2];
194-
M[i, j2] = NumOps.Add(NumOps.Multiply(c, temp1), NumOps.Multiply(s, temp2));
195-
M[k, j2] = NumOps.Subtract(NumOps.Multiply(NumOps.Negate(s), temp1), NumOps.Multiply(c, temp2));
292+
M[i, j2] = _numOps.Add(_numOps.Multiply(c, temp1), _numOps.Multiply(s, temp2));
293+
M[k, j2] = _numOps.Subtract(_numOps.Multiply(_numOps.Negate(s), temp1), _numOps.Multiply(c, temp2));
196294
}
197295

198296
for (int i2 = 0; i2 < Q.Rows; i2++)
199297
{
200298
T temp1 = Q[i2, i];
201299
T temp2 = Q[i2, k];
202-
Q[i2, i] = NumOps.Add(NumOps.Multiply(c, temp1), NumOps.Multiply(s, temp2));
203-
Q[i2, k] = NumOps.Subtract(NumOps.Multiply(NumOps.Negate(s), temp1), NumOps.Multiply(c, temp2));
300+
Q[i2, i] = _numOps.Add(_numOps.Multiply(c, temp1), _numOps.Multiply(s, temp2));
301+
Q[i2, k] = _numOps.Subtract(_numOps.Multiply(_numOps.Negate(s), temp1), _numOps.Multiply(c, temp2));
204302
}
205303
}
206304
else
@@ -209,20 +307,30 @@ private void GivensRotation(Matrix<T> M, Matrix<T> Q, int i, int k, int j, int l
209307
{
210308
T temp1 = M[i2, j];
211309
T temp2 = M[i2, l];
212-
M[i2, j] = NumOps.Add(NumOps.Multiply(c, temp1), NumOps.Multiply(s, temp2));
213-
M[i2, l] = NumOps.Subtract(NumOps.Multiply(NumOps.Negate(s), temp1), NumOps.Multiply(c, temp2));
310+
M[i2, j] = _numOps.Add(_numOps.Multiply(c, temp1), _numOps.Multiply(s, temp2));
311+
M[i2, l] = _numOps.Subtract(_numOps.Multiply(_numOps.Negate(s), temp1), _numOps.Multiply(c, temp2));
214312
}
215313

216314
for (int j2 = 0; j2 < Q.Columns; j2++)
217315
{
218316
T temp1 = Q[j, j2];
219317
T temp2 = Q[l, j2];
220-
Q[j, j2] = NumOps.Add(NumOps.Multiply(c, temp1), NumOps.Multiply(s, temp2));
221-
Q[l, j2] = NumOps.Subtract(NumOps.Multiply(NumOps.Negate(s), temp1), NumOps.Multiply(c, temp2));
318+
Q[j, j2] = _numOps.Add(_numOps.Multiply(c, temp1), _numOps.Multiply(s, temp2));
319+
Q[l, j2] = _numOps.Subtract(_numOps.Multiply(_numOps.Negate(s), temp1), _numOps.Multiply(c, temp2));
222320
}
223321
}
224322
}
225323

324+
/// <summary>
325+
/// Solves a bidiagonal system of linear equations.
326+
/// </summary>
327+
/// <param name="y">The right-hand side vector of the equation Bx = y.</param>
328+
/// <returns>The solution vector x.</returns>
329+
/// <remarks>
330+
/// This method efficiently solves a system where the coefficient matrix is bidiagonal
331+
/// (has non-zero elements only on the main diagonal and the superdiagonal).
332+
/// It uses back-substitution, which is much faster than general matrix inversion.
333+
/// </remarks>
226334
private Vector<T> SolveBidiagonal(Vector<T> y)
227335
{
228336
int n = B.Columns;
@@ -232,13 +340,26 @@ private Vector<T> SolveBidiagonal(Vector<T> y)
232340
{
233341
T sum = y[i];
234342
if (i < n - 1)
235-
sum = NumOps.Subtract(sum, NumOps.Multiply(B[i, i + 1], x[i + 1]));
236-
x[i] = NumOps.Divide(sum, B[i, i]);
343+
sum = _numOps.Subtract(sum, _numOps.Multiply(B[i, i + 1], x[i + 1]));
344+
x[i] = _numOps.Divide(sum, B[i, i]);
237345
}
238346

239347
return x;
240348
}
241349

350+
/// <summary>
351+
/// Returns the three factor matrices of the bidiagonal decomposition.
352+
/// </summary>
353+
/// <returns>
354+
/// A tuple containing:
355+
/// - U: The left orthogonal matrix
356+
/// - B: The bidiagonal matrix
357+
/// - V: The right orthogonal matrix
358+
/// </returns>
359+
/// <remarks>
360+
/// The original matrix A can be reconstructed as A = U * B * V^T.
361+
/// This decomposition is useful for solving linear systems and computing singular values.
362+
/// </remarks>
242363
public (Matrix<T> U, Matrix<T> B, Matrix<T> V) GetFactors()
243364
{
244365
return (U, B, V);

0 commit comments

Comments
 (0)