22# License: MIT License
33# legacy.py code is replaced by numpy - exists just for benchmarking, testing and nostalgia
44from __future__ import annotations
5- from typing import Iterable
5+ from typing import Iterable , cast
66from itertools import repeat
7- from .linalg import Matrix , MatrixData , NDArray
7+ import reprlib
8+
9+ from .linalg import Matrix , MatrixData , NDArray , Solver
810
911__all__ = [
1012 "gauss_vector_solver" ,
1113 "gauss_matrix_solver" ,
1214 "gauss_jordan_solver" ,
1315 "gauss_jordan_inverse" ,
1416 "LUDecomposition" ,
15- "tridiagonal_vector_solver" ,
16- "tridiagonal_matrix_solver" ,
17- "detect_banded_matrix" ,
18- "compact_banded_matrix" ,
19- "BandedMatrixLU" ,
20- "banded_matrix" ,
2117]
2218
2319
@@ -264,4 +260,158 @@ def gauss_jordan_inverse(A: MatrixData) -> Matrix:
264260 else :
265261 matrix_a = list (A )
266262 nrows = len (matrix_a )
267- return gauss_jordan_solver (matrix_a , list (repeat ([0.0 ], nrows )))[0 ]
263+ return gauss_jordan_solver (matrix_a , list (repeat ([0.0 ], nrows )))[0 ]
264+
265+
266+ class LUDecomposition (Solver ):
267+ """Represents a `LU decomposition`_ matrix of A, raise :class:`ZeroDivisionError`
268+ for a singular matrix.
269+
270+ This algorithm is a little bit faster than the `Gauss-Elimination`_
271+ algorithm using CPython and much faster when using pypy.
272+
273+ The :attr:`LUDecomposition.matrix` attribute gives access to the matrix data
274+ as list of rows like in the :class:`Matrix` class, and the :attr:`LUDecomposition.index`
275+ attribute gives access to the swapped row indices.
276+
277+ Args:
278+ A: matrix [[a11, a12, ..., a1n], [a21, a22, ..., a2n], [a21, a22, ..., a2n],
279+ ... [an1, an2, ..., ann]]
280+
281+ raises:
282+ ZeroDivisionError: singular matrix
283+
284+ """
285+
286+ __slots__ = ("matrix" , "index" , "_det" )
287+
288+ def __init__ (self , A : MatrixData | NDArray ):
289+ lu : MatrixData = copy_float_matrix (A )
290+ n : int = len (lu )
291+ det : float = 1.0
292+ index : list [int ] = []
293+
294+ # find max value for each row, raises ZeroDivisionError for singular matrix!
295+ scaling : list [float ] = [1.0 / max (abs (v ) for v in row ) for row in lu ]
296+
297+ for k in range (n ):
298+ big : float = 0.0
299+ imax : int = k
300+ for i in range (k , n ):
301+ temp : float = scaling [i ] * abs (lu [i ][k ])
302+ if temp > big :
303+ big = temp
304+ imax = i
305+
306+ if k != imax :
307+ for col in range (n ):
308+ temp = lu [imax ][col ]
309+ lu [imax ][col ] = lu [k ][col ]
310+ lu [k ][col ] = temp
311+
312+ det = - det
313+ scaling [imax ] = scaling [k ]
314+
315+ index .append (imax )
316+ for i in range (k + 1 , n ):
317+ temp = lu [i ][k ] / lu [k ][k ]
318+ lu [i ][k ] = temp
319+ for j in range (k + 1 , n ):
320+ lu [i ][j ] -= temp * lu [k ][j ]
321+
322+ self .index : list [int ] = index
323+ self .matrix : MatrixData = lu
324+ self ._det : float = det
325+
326+ def __str__ (self ) -> str :
327+ return str (self .matrix )
328+
329+ def __repr__ (self ) -> str :
330+ return f"{ self .__class__ } { reprlib .repr (self .matrix )} "
331+
332+ @property
333+ def nrows (self ) -> int :
334+ """Count of matrix rows (and cols)."""
335+ return len (self .matrix )
336+
337+ def solve_vector (self , B : Iterable [float ]) -> list [float ]:
338+ """Solves the linear equation system given by the nxn Matrix A . x = B,
339+ right-hand side quantities as vector B with n elements.
340+
341+ Args:
342+ B: vector [b1, b2, ..., bn]
343+
344+ Returns:
345+ vector as list of floats
346+
347+ """
348+ X : list [float ] = [float (v ) for v in B ]
349+ lu : MatrixData = self .matrix
350+ index : list [int ] = self .index
351+ n : int = self .nrows
352+ ii : int = 0
353+
354+ if len (X ) != n :
355+ raise ValueError (
356+ "Item count of vector B has to be equal to matrix row count."
357+ )
358+
359+ for i in range (n ):
360+ ip : int = index [i ]
361+ sum_ : float = X [ip ]
362+ X [ip ] = X [i ]
363+ if ii != 0 :
364+ for j in range (ii - 1 , i ):
365+ sum_ -= lu [i ][j ] * X [j ]
366+ elif sum_ != 0.0 :
367+ ii = i + 1
368+ X [i ] = sum_
369+
370+ for row in range (n - 1 , - 1 , - 1 ):
371+ sum_ = X [row ]
372+ for col in range (row + 1 , n ):
373+ sum_ -= lu [row ][col ] * X [col ]
374+ X [row ] = sum_ / lu [row ][row ]
375+ return X
376+
377+ def solve_matrix (self , B : MatrixData | NDArray ) -> Matrix :
378+ """Solves the linear equation system given by the nxn Matrix A . x = B,
379+ right-hand side quantities as nxm Matrix B.
380+
381+ Args:
382+ B: matrix [[b11, b12, ..., b1m], [b21, b22, ..., b2m],
383+ ... [bn1, bn2, ..., bnm]]
384+
385+ Returns:
386+ matrix as :class:`Matrix` object
387+
388+ """
389+ if not isinstance (B , Matrix ):
390+ matrix_b = Matrix (matrix = [list (row ) for row in B ])
391+ else :
392+ matrix_b = cast (Matrix , B )
393+
394+ if matrix_b .nrows != self .nrows :
395+ raise ValueError ("Row count of self and matrix B has to match." )
396+
397+ return Matrix (
398+ matrix = [self .solve_vector (col ) for col in matrix_b .cols ()]
399+ ).transpose ()
400+
401+ def inverse (self ) -> Matrix :
402+ """Returns the inverse of matrix as :class:`Matrix` object,
403+ raise :class:`ZeroDivisionError` for a singular matrix.
404+
405+ """
406+ return self .solve_matrix (Matrix .identity (shape = (self .nrows , self .nrows )).matrix )
407+
408+ def determinant (self ) -> float :
409+ """Returns the determinant of matrix, raises :class:`ZeroDivisionError`
410+ if matrix is singular.
411+
412+ """
413+ det : float = self ._det
414+ lu : MatrixData = self .matrix
415+ for i in range (self .nrows ):
416+ det *= lu [i ][i ]
417+ return det
0 commit comments