Skip to content

Commit eb40d02

Browse files
authored
Merge pull request #8 from FESOM/numpy
Numpy
2 parents 565b3dd + 58e077d commit eb40d02

File tree

5 files changed

+211
-1
lines changed

5 files changed

+211
-1
lines changed

docs/implicit_filter.rst

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ implicit\_filter.cupy_filter module
2121
:undoc-members:
2222
:show-inheritance:
2323

24+
implicit\_filter.numpy_filter module
25+
------------------------------------
26+
27+
.. automodule:: implicit_filter.numpy_filter
28+
:members:
29+
:undoc-members:
30+
:show-inheritance:
31+
2432
implicit\_filter.filter module
2533
------------------------------
2634

implicit_filter/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .jax_filter import JaxFilter
2+
from .numpy_filter import NumpyFilter
23
from ._auxiliary import make_tri, convert_to_wavenumbers
34

45
# If CuPy is not installed this class won't be imported

implicit_filter/_numpy_functions.py

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import numpy as np
2+
3+
4+
def make_smooth(elem_area: np.ndarray, dx: np.ndarray, dy: np.ndarray, nn_num: np.ndarray,
5+
nn_pos: np.ndarray, tri: np.ndarray, n2d: int, e2d: int):
6+
"""
7+
Calculate the smoothness matrix and metric matrix for a given mesh.
8+
It does not support metric terms
9+
10+
Parameters:
11+
----------
12+
elem_area : np.ndarray
13+
A 1D NumPy array containing the area of each triangle.
14+
15+
dx : np.ndarray
16+
A 2D NumPy array of shape (e2d, 3) containing x-derivatives of P1 basis functions.
17+
18+
dy : np.ndarray
19+
A 2D NumPy array of shape (e2d, 3) containing y-derivatives of P1 basis functions.
20+
21+
nn_num : np.ndarray
22+
A 1D JAX NumPy array of shape (n2d,) containing the number of neighboring nodes for each node.
23+
24+
nn_pos : jnp.ndarray
25+
A 2D JAX NumPy array of shape (max_neighboring_nodes, n2d) containing positions of neighboring nodes.
26+
27+
tri : jnp.ndarray
28+
A 2D JAX NumPy array representing the connectivity of triangles in the mesh.
29+
30+
n2d : int
31+
The total number of nodes in the mesh.
32+
33+
e2d : int
34+
The total number of triangles (elements) in the mesh.
35+
36+
full : bool, optional
37+
A flag indicating whether to use the 'full' calculation including metric factors (True) or not (False).
38+
Default is True.
39+
40+
Returns:
41+
-------
42+
smooth_m : np.ndarray
43+
A 2D NumPy array of shape (max_neighboring_nodes, n2d) containing the smoothness matrix.
44+
45+
"""
46+
47+
smooth_m = np.zeros(nn_pos.shape) # Place for non - zero entries
48+
aux = np.zeros([n2d], dtype=int) # auxiliary array
49+
for j in range(e2d):
50+
enodes = tri[j, :] # vertices of triangle
51+
hh = np.sqrt(2 * elem_area[j])
52+
# This expression corresponds to the specific mesh above obtained
53+
# by splitting quads. A slightly different expression is needed
54+
# for equilateral triangles.
55+
56+
for n in range(3): # Each contributes to rows
57+
row = enodes[n] # and columns
58+
cc = nn_num[row] # num of neighbors
59+
tmp = nn_pos[0:cc, row]
60+
aux[tmp] = np.arange(0, cc) # Map their order
61+
for m in range(3):
62+
col = enodes[m]
63+
pos = aux[col] # Position of column among neighbors
64+
tmp_x = dx[j, m] * dx[j, n]
65+
tmp_y = dy[j, n] * dy[j, m]
66+
smooth_m[pos, row] += (tmp_x + tmp_y) * elem_area[j]
67+
68+
return smooth_m
69+
70+
71+
def make_smat(nn_pos: np.ndarray, nn_num: np.ndarray, smooth_m: np.ndarray, n2d: int, nza: int):
72+
"""
73+
Convert the smoothness matrix into a redundant sparse form (s(k), i(k), j(k)) as required by scipy.
74+
75+
Parameters:
76+
----------
77+
nn_pos : np.ndarray
78+
A 2D NumPy array of shape (max_neighboring_nodes, n2d) containing positions of neighboring nodes.
79+
80+
nn_num : np.ndarray
81+
A 1D NumPy array of shape (n2d,) containing the number of neighboring nodes for each node.
82+
83+
smooth_m : np.ndarray
84+
A 2D NumPy array of shape (max_neighboring_nodes, n2d) containing the smoothness matrix.
85+
86+
n2d : int
87+
The total number of nodes in the mesh.
88+
89+
nza : int
90+
The total number of nonzero elements.
91+
92+
Returns:
93+
-------
94+
ss : np.ndarray
95+
A 1D NumPy array of shape (nza,) containing the nonzero entries of the sparse matrix.
96+
97+
ii : np.ndarray
98+
A 1D NumPy array of shape (nza,) containing the row indices of the nonzero entries.
99+
100+
jj : np.ndarray
101+
A 1D NumPy array of shape (nza,) containing the column indices of the nonzero entries.
102+
"""
103+
nza = np.sum(nn_num) # The number of nonzero elements:
104+
ss = np.zeros([nza]) # Place for nonzero entries
105+
ii = np.zeros([nza]) # Place for their rows
106+
jj = np.zeros([nza]) # Place for their columns
107+
nz = 0
108+
for n in range(n2d):
109+
for m in range(nn_num[n]):
110+
ss[nz] = smooth_m[m, n]
111+
ii[nz] = n
112+
jj[nz] = nn_pos[m, n]
113+
nz += 1
114+
115+
return ss, ii, jj

implicit_filter/jax_filter.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def prepare(self, n2d: int, e2d: int, tri: np.ndarray, xcoord: np.ndarray, ycoor
165165
self._ne_pos = jnp.array(ne_pos)
166166
self._area = jnp.array(area)
167167

168-
smooth, metric = make_smooth(jMt, self._elem_area, self._dx, self._dy, jnn_num, jnn_pos, jtri, n2d, e2d, False)
168+
smooth, metric = make_smooth(jMt, self._elem_area, self._dx, self._dy, jnn_num, jnn_pos, jtri, n2d, e2d, full)
169169

170170
smooth = vmap(lambda n: smooth[:, n] / self._area[n])(jnp.arange(0, n2d)).T
171171
metric = vmap(lambda n: metric[:, n] / self._area[n])(jnp.arange(0, n2d)).T

implicit_filter/numpy_filter.py

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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

Comments
 (0)