|
| 1 | +import math |
| 2 | +from typing import Tuple |
| 3 | + |
| 4 | +import numpy as np |
| 5 | +from scipy.sparse import csc_matrix, identity |
| 6 | +from scipy.sparse.linalg import cg |
| 7 | + |
| 8 | +from implicit_filter._auxiliary import neighbouring_nodes, neighboring_triangles, areas |
| 9 | +from implicit_filter._numpy_functions import make_smooth, make_smat |
| 10 | +from implicit_filter._utils import VeryStupidIdeaError, SolverNotConvergedError |
| 11 | +from implicit_filter.filter import Filter |
| 12 | + |
| 13 | + |
| 14 | +class NumpyFilter(Filter): |
| 15 | + """ |
| 16 | + A class for filtering data using JAX-based implicit filtering techniques. |
| 17 | + Extends the base Filter class. |
| 18 | + """ |
| 19 | + |
| 20 | + def _check_filter_order(self, n: int) -> None: |
| 21 | + if n < 1: |
| 22 | + raise ValueError("Filter order must be positive") |
| 23 | + elif n > 2: |
| 24 | + raise VeryStupidIdeaError("Filter order too large", ["It really shouldn't be larger than 2"]) |
| 25 | + |
| 26 | + def compute(self, n: int, k: float, data: np.ndarray) -> np.ndarray: |
| 27 | + self._check_filter_order(n) |
| 28 | + return self._compute(n, k, data) |
| 29 | + |
| 30 | + def compute_velocity(self, n: int, k: float, ux: np.ndarray, vy: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: |
| 31 | + raise NotImplementedError("Filtering non-scalar values are not supported with NumPy Filer") |
| 32 | + |
| 33 | + def prepare(self, n2d: int, e2d: int, tri: np.ndarray, xcoord: np.ndarray, ycoord: np.ndarray, meshtype: str = 'r', |
| 34 | + carthesian: bool = False, cyclic_length: float = 360.0 * math.pi / 180.0, full: bool = False): |
| 35 | + if full: |
| 36 | + raise NotImplementedError("Computation including metric terms are not supported with NumPy Filer") |
| 37 | + |
| 38 | + ne_num, ne_pos = neighboring_triangles(n2d, e2d, tri) |
| 39 | + nn_num, nn_pos = neighbouring_nodes(n2d, tri, ne_num, ne_pos) |
| 40 | + area, elem_area, dx, dy, Mt = areas(n2d, e2d, tri, xcoord, ycoord, ne_num, ne_pos, meshtype, carthesian, |
| 41 | + cyclic_length) |
| 42 | + |
| 43 | + self._elem_area = elem_area |
| 44 | + self._dx = dx |
| 45 | + self._dy = dy |
| 46 | + self._ne_num = ne_num |
| 47 | + self._ne_pos = ne_pos |
| 48 | + self._area = area |
| 49 | + |
| 50 | + smooth = make_smooth(self._elem_area, self._dx, self._dy, nn_num, nn_pos, tri, n2d, e2d) |
| 51 | + |
| 52 | + for i in range(n2d): |
| 53 | + smooth[:, i] /= self._area[i] |
| 54 | + |
| 55 | + self._ss, self._ii, self._jj = make_smat(nn_pos, nn_num, smooth, n2d, int(np.sum(nn_num))) |
| 56 | + self._n2d = n2d |
| 57 | + self._full = full |
| 58 | + |
| 59 | + def __transform_atribute(self, atr: str, lmbd, fill=None): |
| 60 | + """ |
| 61 | + If atribute atr exists then transform it using given Callable lmbd, otherwise it set with fill value |
| 62 | + """ |
| 63 | + if hasattr(self, atr): |
| 64 | + setattr(self, atr, lmbd(getattr(self, atr))) |
| 65 | + else: |
| 66 | + setattr(self, atr, fill) |
| 67 | + |
| 68 | + def __init__(self, *initial_data, **kwargs): |
| 69 | + super().__init__(initial_data, kwargs) |
| 70 | + # Transform from Numpy array |
| 71 | + self.__transform_atribute("_n2d", lambda x: int(x), 0) |
| 72 | + self.__transform_atribute("_full", lambda x: bool(x), False) |
| 73 | + |
| 74 | + def _compute(self, n, kl, ttu, tol=1e-6, maxiter=150000): |
| 75 | + Smat1 = csc_matrix((self._ss * (1.0 / np.square(kl)), (self._ii, self._jj)), shape=(self._n2d, self._n2d)) |
| 76 | + Smat = identity(self._n2d) + 0.5 * (Smat1 ** n) |
| 77 | + |
| 78 | + ttw = ttu - Smat @ ttu # Work with perturbations |
| 79 | + |
| 80 | + tts, code = cg(Smat, ttw, tol=tol, maxiter=maxiter) |
| 81 | + if code != 0: |
| 82 | + raise SolverNotConvergedError("Solver has not converged without metric terms", |
| 83 | + [f"output code with code: {code}"]) |
| 84 | + |
| 85 | + tts += ttu |
| 86 | + return np.array(tts) |
0 commit comments