Skip to content

Commit cd091c6

Browse files
committed
refactored numerical_flux.recompute_advective_fluxes; added common operators and slices modules
1 parent 6ea3c89 commit cd091c6

File tree

3 files changed

+160
-47
lines changed

3 files changed

+160
-47
lines changed

src/pybella/flow_solver/physics/gas_dynamics/numerical_flux.py

Lines changed: 17 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,30 @@
11
# -*- coding: utf-8 -*-
22
import numpy as np
3-
import scipy as sp
43

4+
from ....utils.operators import create_convolution_kernels, apply_convolution_kernel, apply_u_kernel_convolution, apply_v_kernel_convolution_3d
5+
from ....utils.slices import get_inner_slice
56

6-
def recompute_advective_fluxes(flux, Sol, *args, **kwargs):
7-
"""
8-
Recompute the advective fluxes at the cell interfaces, i.e. the faces. This function updates the `flux` container in-place.
9-
10-
Parameters
11-
----------
12-
flux : :py:class:`management.variable.States`
13-
Data container for the fluxes at the cell interfaces.
14-
Sol : :py:class:`management.variable.States`
15-
Data container for the Solution.
16-
17-
Attention
18-
---------
19-
This function is a mess and requires cleaning up.
20-
21-
"""
7+
def recompute_advective_fluxes(flux, Sol, **kwargs):
8+
"""Recompute the advective fluxes at the cell interfaces."""
229
ndim = Sol.rho.ndim
23-
inner_idx = tuple([slice(1, -1)] * ndim)
24-
25-
if ndim == 2:
26-
kernel_u = np.array([[0.5, 1.0, 0.5], [0.5, 1.0, 0.5]])
27-
kernel_v = kernel_u.T
28-
elif ndim == 3:
29-
kernel_u = np.array(
30-
[[[1, 2, 1], [2, 4, 2], [1, 2, 1]], [[1, 2, 1], [2, 4, 2], [1, 2, 1]]]
31-
)
32-
kernel_v = np.swapaxes(kernel_u, 1, 0)
33-
kernel_w = np.swapaxes(kernel_u, 2, 0)
34-
10+
inner_idx = get_inner_slice(ndim)
11+
kernels = create_convolution_kernels(ndim)
12+
13+
# Handle 3D w-component first
14+
if ndim == 3:
3515
rhoYw = Sol.rhoY * Sol.rhow / Sol.rho
36-
flux[2].rhoY[inner_idx] = (
37-
sp.signal.fftconvolve(rhoYw, kernel_w, mode="valid") / kernel_w.sum()
38-
)
39-
else:
40-
assert 0, "Unsupported dimension in recompute_advective_flux"
41-
16+
flux[2].rhoY[inner_idx] = apply_convolution_kernel(rhoYw, kernels['w'])
17+
18+
# u-component (all dimensions)
4219
rhoYu = kwargs.get("u", Sol.rhoY * Sol.rhou / Sol.rho)
43-
44-
flux[0].rhoY[inner_idx] = np.moveaxis(
45-
sp.signal.fftconvolve(rhoYu, kernel_u, mode="valid") / kernel_u.sum(), 0, -1
46-
)
47-
20+
flux[0].rhoY[inner_idx] = apply_u_kernel_convolution(rhoYu, kernels['u'])
21+
22+
# v-component (all dimensions)
4823
rhoYv = kwargs.get("v", Sol.rhoY * Sol.rhov / Sol.rho)
4924
if ndim == 2:
50-
flux[1].rhoY[inner_idx] = (
51-
sp.signal.fftconvolve(rhoYv, kernel_v, mode="valid") / kernel_v.sum()
52-
)
25+
flux[1].rhoY[inner_idx] = apply_convolution_kernel(rhoYv, kernels['v'])
5326
elif ndim == 3:
54-
flux[1].rhoY[inner_idx] = np.moveaxis(
55-
sp.signal.fftconvolve(rhoYv, kernel_v, mode="valid") / kernel_v.sum(), -1, 0
56-
)
57-
# flux[1].rhoY[...,-1] = 0.
27+
flux[1].rhoY[inner_idx] = apply_v_kernel_convolution_3d(rhoYv, kernels['v'])
5828

5929

6030
def hll_solver(flux, Lefts, Rights, Sol, lmbda, ud, th):

src/pybella/utils/operators.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import numpy as np
2+
import scipy as sp
3+
from numba import njit
4+
from functools import lru_cache
5+
6+
7+
@lru_cache(maxsize=2)
8+
def create_convolution_kernels(ndim):
9+
"""Create convolution kernels for advective flux computation.
10+
11+
Parameters
12+
----------
13+
ndim : int
14+
Number of dimensions (2 or 3)
15+
16+
Returns
17+
-------
18+
dict
19+
Dictionary containing kernels for each direction
20+
21+
Notes
22+
-----
23+
Results are cached since kernels don't change during computation.
24+
"""
25+
if ndim == 2:
26+
kernel_u = np.array([[0.5, 1.0, 0.5], [0.5, 1.0, 0.5]])
27+
return {
28+
'u': kernel_u,
29+
'v': kernel_u.T
30+
}
31+
elif ndim == 3:
32+
kernel_u = np.array([
33+
[[1, 2, 1], [2, 4, 2], [1, 2, 1]],
34+
[[1, 2, 1], [2, 4, 2], [1, 2, 1]]
35+
])
36+
return {
37+
'u': kernel_u,
38+
'v': np.swapaxes(kernel_u, 1, 0),
39+
'w': np.swapaxes(kernel_u, 2, 0)
40+
}
41+
else:
42+
raise ValueError(f"Unsupported dimension: {ndim}")
43+
44+
45+
@njit
46+
def _numba_convolve_2d(data, kernel):
47+
"""Numba-compiled 2D convolution for better performance."""
48+
data_h, data_w = data.shape
49+
kernel_h, kernel_w = kernel.shape
50+
51+
result_h = data_h - kernel_h + 1
52+
result_w = data_w - kernel_w + 1
53+
result = np.zeros((result_h, result_w))
54+
55+
for i in range(result_h):
56+
for j in range(result_w):
57+
for ki in range(kernel_h):
58+
for kj in range(kernel_w):
59+
result[i, j] += data[i + ki, j + kj] * kernel[ki, kj]
60+
61+
return result
62+
63+
64+
@njit
65+
def _numba_convolve_3d(data, kernel):
66+
"""Numba-compiled 3D convolution for better performance."""
67+
data_d, data_h, data_w = data.shape
68+
kernel_d, kernel_h, kernel_w = kernel.shape
69+
70+
result_d = data_d - kernel_d + 1
71+
result_h = data_h - kernel_h + 1
72+
result_w = data_w - kernel_w + 1
73+
result = np.zeros((result_d, result_h, result_w))
74+
75+
for i in range(result_d):
76+
for j in range(result_h):
77+
for k in range(result_w):
78+
for ki in range(kernel_d):
79+
for kj in range(kernel_h):
80+
for kk in range(kernel_w):
81+
result[i, j, k] += data[i + ki, j + kj, k + kk] * kernel[ki, kj, kk]
82+
83+
return result
84+
85+
86+
def apply_convolution_kernel(data, kernel, normalize=True, axis_swap=None, use_numba=True):
87+
"""Apply convolution kernel with optional normalization and axis swapping.
88+
89+
Parameters
90+
----------
91+
data : np.ndarray
92+
Input data array
93+
kernel : np.ndarray
94+
Convolution kernel
95+
normalize : bool, default=True
96+
Whether to normalize by kernel sum
97+
axis_swap : tuple or None, default=None
98+
Tuple of (from_axis, to_axis) for np.moveaxis
99+
use_numba : bool, default=True
100+
Whether to use Numba-compiled convolution (faster for repeated calls)
101+
102+
Returns
103+
-------
104+
np.ndarray
105+
Convolved result
106+
107+
Notes
108+
-----
109+
For large arrays or single calls, scipy.signal.fftconvolve might be faster.
110+
For repeated calls on smaller arrays, Numba convolution is typically faster.
111+
"""
112+
if use_numba and data.ndim in (2, 3):
113+
if data.ndim == 2:
114+
result = _numba_convolve_2d(data, kernel)
115+
else: # 3D
116+
result = _numba_convolve_3d(data, kernel)
117+
else:
118+
# Fallback to scipy for other dimensions or when requested
119+
result = sp.signal.fftconvolve(data, kernel, mode="valid")
120+
121+
if normalize:
122+
result = result / kernel.sum()
123+
124+
if axis_swap is not None:
125+
result = np.moveaxis(result, axis_swap[0], axis_swap[1])
126+
127+
return result
128+
129+
130+
# Convenience functions for specific operations
131+
def apply_u_kernel_convolution(data, kernel_u, normalize=True):
132+
"""Apply u-direction kernel with standard axis swap."""
133+
return apply_convolution_kernel(data, kernel_u, normalize=normalize, axis_swap=(0, -1))
134+
135+
136+
def apply_v_kernel_convolution_3d(data, kernel_v, normalize=True):
137+
"""Apply v-direction kernel for 3D with standard axis swap."""
138+
return apply_convolution_kernel(data, kernel_v, normalize=normalize, axis_swap=(-1, 0))

src/pybella/utils/slices.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
3+
def get_inner_slice(ndim):
4+
"""Get slice tuple for inner cells (excluding outermost ghost cells)."""
5+
return tuple([slice(1, -1)] * ndim)

0 commit comments

Comments
 (0)