Source code for cosapp.core.numerics.solve.linear_solver
from abc import abstractmethod
from typing import Any, Dict
import numpy
from scipy.linalg import LinAlgWarning, lu_factor, lu_solve
from scipy.sparse.linalg import splu
from cosapp.utils.json import jsonify
from cosapp.utils.options_dictionary import HasOptions
from cosapp.utils.state_io import object__getstate__
[docs]
class AbstractLinearSolver(HasOptions):
def __getstate__(self) -> Dict[str, Any]:
"""Creates a state of the object.
The state type does NOT match type specified in
https://docs.python.org/3/library/pickle.html#object.__getstate__
to allow custom serialization.
Returns
-------
Dict[str, Any]:
state
"""
return object__getstate__(self)
def __json__(self) -> Dict[str, Any]:
"""Creates a JSONable dictionary representation of the object.
Break circular dependencies by removing some slots from the
state.
Returns
-------
Dict[str, Any]
The dictionary
"""
state = self.__getstate__().copy()
return jsonify(state)
[docs]
@abstractmethod
def solve(self, x: numpy.ndarray) -> numpy.ndarray:
"""Solves the linear problem."""
pass
@property
@abstractmethod
def need_jacobian(self) -> bool:
"""Gets whether the implementation needs eager computation of
a Jacobian matrix or not."""
pass
[docs]
@abstractmethod
def has_valid_state(self, size: int) -> bool:
"""Gets whether the implementation is in valid state or not."""
pass
[docs]
@abstractmethod
def eval(self, dx: numpy.ndarray) -> numpy.ndarray:
"""Evaluates a residue delta from an input delta."""
pass
[docs]
class GradientBasedLS(AbstractLinearSolver):
@property
@abstractmethod
def jacobian(self) -> numpy.ndarray:
"""Gets the Jacobian matrix."""
pass
@jacobian.setter
@abstractmethod
def jacobian(self, value: numpy.ndarray) -> None:
"""Sets the Jacobian matrix."""
pass
[docs]
class DenseLUSolver(GradientBasedLS):
"""Linear solver based on dense LU decomposition."""
def __init__(self):
super().__init__()
self._jac = None
self._lu_piv = None
@property
def need_jacobian(self) -> bool:
"""Gets whether the implementation needs eager computation of
a Jacobian matrix or not."""
return True
[docs]
def setup(self, jac: numpy.ndarray) -> None:
"""Performs setup of the linear solver from the Jacobian matrix."""
lu, piv = lu_factor(jac, check_finite=True)
min_diag = numpy.abs(lu.diagonal()).min()
if min_diag < 1e-14:
raise LinAlgWarning(
f"Quasi-singular Jacobian matrix; min diag element of U matrix is {min_diag}"
)
self._jac = jac
self._lu_piv = lu, piv
@property
def jacobian(self) -> numpy.ndarray:
"""Gets the Jacobian matrix."""
return self._jac
@jacobian.setter
def jacobian(self, value: numpy.ndarray) -> None:
"""Sets the Jacobian matrix."""
self._jac = value
self.setup(self._jac)
[docs]
def has_valid_state(self, size: int) -> bool:
"""Gets whether the implementation is in valid state or not."""
return self._lu_piv is not None and self._lu_piv[0].shape == (size, size)
[docs]
def solve(self, x: numpy.ndarray) -> numpy.ndarray:
"""Solves the linear problem."""
return -lu_solve(self._lu_piv, x)
[docs]
def eval(self, dx: numpy.ndarray) -> numpy.ndarray:
"""Evaluates a residue delta from an input delta."""
return self._jac.dot(dx)
[docs]
class SparseLUSolver(AbstractLinearSolver):
"""Linear solver based on sparse LU decomposition."""
def __init__(self):
super().__init__()
self._splu = None
self._jac = None
[docs]
def setup(self, jac: numpy.ndarray) -> None:
"""Performs setup of the linear solver from the Jacobian matrix."""
self._splu = splu(jac)
@property
def need_jacobian(self) -> bool:
"""Gets whether the implementation needs eager computation of
a Jacobian matrix or not."""
return True
@property
def jacobian(self) -> numpy.ndarray:
"""Gets the Jacobian matrix."""
return self._jac
@jacobian.setter
def jacobian(self, value: numpy.ndarray) -> None:
"""Sets the Jacobian matrix."""
self._jac = value
self.setup(self._jac)
[docs]
def has_valid_state(self, size: int) -> bool:
"""Gets whether the implementation is in valid state or not."""
return self._splu is not None and self._splu.shape == (size, size)
[docs]
def solve(self, x: numpy.ndarray) -> numpy.ndarray:
"""Solves the linear problem."""
return -self._splu.solve(x)
[docs]
def eval(self, dx: numpy.ndarray) -> numpy.ndarray:
"""Evaluates a residue delta from an input delta."""
L, U = self._splu.L, self._splu.U
return L.dot(U).dot(dx)