Source code for capytaine.bem.linear_solvers
import logging
from abc import ABC, abstractmethod
from functools import singledispatch
from typing import Any
import numpy as np
from numpy.typing import NDArray
import scipy.linalg as sl
from capytaine.tools.array_backend import array_namespace, device
LOG = logging.getLogger(__name__)
############################### LU DECOMPOSITION ###############################
# ABSTRACT INTERFACE FOR LU DECOMPOSED MATRICES
[docs]
class AbstractLUDecomposedMatrix(ABC):
"""Base class of the LU decompositions of matrices.
New matrix types (e.g. matrices stored with another array library) can be
supported by the linear solvers of Capytaine by registering a function
returning a subclass of this class with :func:`lu_decompose`.
As for an array of the array API standard, the subclasses define the method
`__array_namespace__` and the attribute (or property) `device`, returning
the array library and the device of the matrix that has been decomposed.
"""
shape: tuple
dtype: object
device: Any
@abstractmethod
def __array_namespace__(self, *, api_version=None):
"""Array library of the matrix that has been decomposed, as for an array of the array API standard."""
[docs]
@abstractmethod
def solve(self, b):
"""Solve the linear system with the decomposed matrix as left-hand side and `b` as right-hand side."""
[docs]
def has_been_lu_decomposed(A):
return isinstance(A, AbstractLUDecomposedMatrix)
[docs]
@singledispatch
def lu_decompose(A, *, overwrite_a: bool = False) -> AbstractLUDecomposedMatrix:
"""Compute the LU decomposition of `A`.
This is a :func:`functools.singledispatch` function: the implementation for
a new type of matrix can be added with ``lu_decompose.register(MyMatrix, my_function)``,
where ``my_function(A, *, overwrite_a=False)`` returns an :class:`AbstractLUDecomposedMatrix`.
"""
raise NotImplementedError(f"No LU decomposition registered for {type(A)}")
# IMPLEMENTATION FOR NUMPY ARRAYS
[docs]
class LUDecomposedMatrix(AbstractLUDecomposedMatrix):
def __init__(self, A: NDArray, *, overwrite_a : bool = False):
LOG.debug("LU decomp of %s of shape %s",
A.__class__.__name__, A.shape)
self._lu_decomp = sl.lu_factor(A, overwrite_a=overwrite_a)
self.shape = A.shape
self.dtype = A.dtype
self._array_backend = array_namespace(A)
self.device = device(A)
def __array_namespace__(self, *, api_version=None):
return self._array_backend
[docs]
def solve(self, b: np.ndarray) -> np.ndarray:
LOG.debug("Called solve on %s of shape %s",
self.__class__.__name__, self.shape)
return sl.lu_solve(self._lu_decomp, b)
@lu_decompose.register(np.ndarray)
def _lu_decompose_ndarray(A, *, overwrite_a: bool = False):
return LUDecomposedMatrix(A, overwrite_a=overwrite_a)