1
- using AiDotNet . Enums . AlgorithmTypes ;
2
-
3
1
namespace AiDotNet . DecompositionMethods . MatrixDecomposition ;
4
2
5
3
public class BidiagonalDecomposition < T > : IMatrixDecomposition < T >
6
4
{
7
- private readonly INumericOperations < T > NumOps ;
5
+ private readonly INumericOperations < T > _numOps ;
8
6
7
+ /// <summary>
8
+ /// Gets the original matrix that was decomposed.
9
+ /// </summary>
9
10
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>
10
16
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>
11
23
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>
12
29
public Matrix < T > V { get ; private set ; }
13
30
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>
14
40
public BidiagonalDecomposition ( Matrix < T > matrix , BidiagonalAlgorithmType algorithm = BidiagonalAlgorithmType . Householder )
15
41
{
16
42
A = matrix ;
17
- NumOps = MathHelper . GetNumericOperations < T > ( ) ;
43
+ _numOps = MathHelper . GetNumericOperations < T > ( ) ;
18
44
U = new Matrix < T > ( matrix . Rows , matrix . Rows ) ;
19
45
B = new Matrix < T > ( matrix . Columns , matrix . Columns ) ;
20
46
V = new Matrix < T > ( matrix . Columns , matrix . Columns ) ;
21
47
Decompose ( algorithm ) ;
22
48
}
23
49
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>
24
59
public void Decompose ( BidiagonalAlgorithmType algorithm = BidiagonalAlgorithmType . Householder )
25
60
{
26
61
switch ( algorithm )
@@ -39,6 +74,13 @@ public void Decompose(BidiagonalAlgorithmType algorithm = BidiagonalAlgorithmTyp
39
74
}
40
75
}
41
76
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>
42
84
private void DecomposeHouseholder ( )
43
85
{
44
86
int m = A . Rows ;
@@ -54,7 +96,7 @@ private void DecomposeHouseholder()
54
96
Vector < T > v = HouseholderVector ( x ) ;
55
97
56
98
// 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 ) ) ) ;
58
100
Matrix < T > subB = B . GetSubMatrix ( k , k , m - k , n - k ) ;
59
101
B . SetSubMatrix ( k , k , P . Multiply ( subB ) ) ;
60
102
@@ -69,7 +111,7 @@ private void DecomposeHouseholder()
69
111
v = HouseholderVector ( x ) ;
70
112
71
113
// 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 ) ) ) ;
73
115
subB = B . GetSubMatrix ( k , k + 1 , m - k , n - k - 1 ) ;
74
116
B . SetSubMatrix ( k , k + 1 , subB . Multiply ( P ) ) ;
75
117
@@ -80,6 +122,13 @@ private void DecomposeHouseholder()
80
122
}
81
123
}
82
124
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>
83
132
private void DecomposeGivens ( )
84
133
{
85
134
int m = A . Rows ;
@@ -105,6 +154,14 @@ private void DecomposeGivens()
105
154
}
106
155
}
107
156
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>
108
165
private void DecomposeLanczos ( )
109
166
{
110
167
int m = A . Rows ;
@@ -120,7 +177,7 @@ private void DecomposeLanczos()
120
177
Random rand = new ( ) ;
121
178
for ( int i = 0 ; i < n ; i ++ )
122
179
{
123
- v [ i ] = NumOps . FromDouble ( rand . NextDouble ( ) ) ;
180
+ v [ i ] = _numOps . FromDouble ( rand . NextDouble ( ) ) ;
124
181
}
125
182
v = v . Divide ( v . Norm ( ) ) ;
126
183
@@ -142,6 +199,16 @@ private void DecomposeLanczos()
142
199
}
143
200
}
144
201
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>
145
212
public Vector < T > Solve ( Vector < T > b )
146
213
{
147
214
if ( b . Length != A . Rows )
@@ -153,6 +220,14 @@ public Vector<T> Solve(Vector<T> b)
153
220
return V . Multiply ( z ) ;
154
221
}
155
222
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>
156
231
public Matrix < T > Invert ( )
157
232
{
158
233
int n = A . Columns ;
@@ -161,46 +236,69 @@ public Matrix<T> Invert()
161
236
for ( int i = 0 ; i < n ; i ++ )
162
237
{
163
238
Vector < T > ei = new Vector < T > ( n ) ;
164
- ei [ i ] = NumOps . One ;
239
+ ei [ i ] = _numOps . One ;
165
240
inverse . SetColumn ( i , Solve ( ei ) ) ;
166
241
}
167
242
168
243
return inverse ;
169
244
}
170
245
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>
171
255
private Vector < T > HouseholderVector ( Vector < T > x )
172
256
{
173
257
T norm = x . Norm ( ) ;
174
258
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 ) ) ;
176
260
177
261
return v . Divide ( v . Norm ( ) ) ;
178
262
}
179
263
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>
180
278
private void GivensRotation ( Matrix < T > M , Matrix < T > Q , int i , int k , int j , int l , bool isLeft )
181
279
{
182
280
T a = M [ i , j ] ;
183
281
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 ) ;
187
285
188
286
if ( isLeft )
189
287
{
190
288
for ( int j2 = 0 ; j2 < M . Columns ; j2 ++ )
191
289
{
192
290
T temp1 = M [ i , j2 ] ;
193
291
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 ) ) ;
196
294
}
197
295
198
296
for ( int i2 = 0 ; i2 < Q . Rows ; i2 ++ )
199
297
{
200
298
T temp1 = Q [ i2 , i ] ;
201
299
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 ) ) ;
204
302
}
205
303
}
206
304
else
@@ -209,20 +307,30 @@ private void GivensRotation(Matrix<T> M, Matrix<T> Q, int i, int k, int j, int l
209
307
{
210
308
T temp1 = M [ i2 , j ] ;
211
309
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 ) ) ;
214
312
}
215
313
216
314
for ( int j2 = 0 ; j2 < Q . Columns ; j2 ++ )
217
315
{
218
316
T temp1 = Q [ j , j2 ] ;
219
317
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 ) ) ;
222
320
}
223
321
}
224
322
}
225
323
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>
226
334
private Vector < T > SolveBidiagonal ( Vector < T > y )
227
335
{
228
336
int n = B . Columns ;
@@ -232,13 +340,26 @@ private Vector<T> SolveBidiagonal(Vector<T> y)
232
340
{
233
341
T sum = y [ i ] ;
234
342
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 ] ) ;
237
345
}
238
346
239
347
return x ;
240
348
}
241
349
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>
242
363
public ( Matrix < T > U , Matrix < T > B , Matrix < T > V ) GetFactors ( )
243
364
{
244
365
return ( U , B , V ) ;
0 commit comments