Skip to content

Commit 4218591

Browse files
3d Gray-Scott (#506)
* Added test for generic mpi4py-fft Laplacian class * Expanded tests * 3D initial conditions in Gray-Scott
1 parent b81b47b commit 4218591

File tree

4 files changed

+126
-37
lines changed

4 files changed

+126
-37
lines changed

pySDC/implementations/problem_classes/GrayScott_MPIFFT.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,6 @@ def u_exact(self, t, seed=10700000):
200200
Exact solution.
201201
"""
202202
assert t == 0.0, 'Exact solution only valid as initial condition'
203-
assert self.ndim == 2, 'The initial conditions are 2D for now..'
204203

205204
xp = self.xp
206205

@@ -222,12 +221,13 @@ def u_exact(self, t, seed=10700000):
222221
_v[...] = xp.sqrt(F) * (A + xp.sqrt(A**2 - 4)) / 2
223222

224223
for _ in range(-self.num_blobs):
225-
x0, y0 = rng.random(size=2) * self.L[0] - self.L[0] / 2
226-
lx, ly = rng.random(size=2) * self.L[0] / self.nvars[0] * 30
224+
x0 = rng.random(size=self.ndim) * self.L[0] - self.L[0] / 2
225+
l = rng.random(size=self.ndim) * self.L[0] / self.nvars[0] * 30
227226

228-
mask_x = xp.logical_and(self.X[0] > x0, self.X[0] < x0 + lx)
229-
mask_y = xp.logical_and(self.X[1] > y0, self.X[1] < y0 + ly)
230-
mask = xp.logical_and(mask_x, mask_y)
227+
masks = [xp.logical_and(self.X[i] > x0[i], self.X[i] < x0[i] + l[i]) for i in range(self.ndim)]
228+
mask = masks[0]
229+
for m in masks[1:]:
230+
mask = xp.logical_and(mask, m)
231231

232232
_u[mask] = rng.random()
233233
_v[mask] = rng.random()
@@ -236,6 +236,7 @@ def u_exact(self, t, seed=10700000):
236236
"""
237237
Blobs as in https://www.chebfun.org/examples/pde/GrayScott.html
238238
"""
239+
assert self.ndim == 2, 'The initial conditions are 2D for now..'
239240

240241
inc = self.L[0] / (self.num_blobs + 1)
241242

pySDC/implementations/problem_classes/generic_MPIFFT_Laplacian.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
import numpy as np
22
from mpi4py import MPI
3-
from mpi4py_fft import PFFT
3+
from mpi4py_fft import PFFT, newDistArray
44

55
from pySDC.core.errors import ProblemError
66
from pySDC.core.problem import Problem, WorkCounter
77
from pySDC.implementations.datatype_classes.mesh import mesh, imex_mesh
88

9-
from mpi4py_fft import newDistArray
10-
119

1210
class IMEX_Laplacian_MPIFFT(Problem):
1311
r"""
1412
Generic base class for IMEX problems using a spectral method to solve the Laplacian implicitly and a possible rest
1513
explicitly. The FFTs are done with``mpi4py-fft`` [1]_.
14+
Works in two and three dimensions.
1615
1716
Parameters
1817
----------
@@ -99,14 +98,24 @@ def __init__(
9998
'nvars', 'spectral', 'L', 'alpha', 'comm', 'x0', 'useGPU', localVars=locals(), readOnly=True
10099
)
101100

102-
# get local mesh
101+
self.getLocalGrid()
102+
self.getLaplacian()
103+
104+
# Need this for diagnostics
105+
self.dx = self.L[0] / nvars[0]
106+
self.dy = self.L[1] / nvars[1]
107+
108+
# work counters
109+
self.work_counters['rhs'] = WorkCounter()
110+
111+
def getLocalGrid(self):
103112
X = list(self.xp.ogrid[self.fft.local_slice(False)])
104113
N = self.fft.global_shape()
105114
for i in range(len(N)):
106-
X[i] = x0 + (X[i] * L[i] / N[i])
115+
X[i] = self.x0 + (X[i] * self.L[i] / N[i])
107116
self.X = [self.xp.broadcast_to(x, self.fft.shape(False)) for x in X]
108117

109-
# get local wavenumbers and Laplace operator
118+
def getLaplacian(self):
110119
s = self.fft.local_slice()
111120
N = self.fft.global_shape()
112121
k = [self.xp.fft.fftfreq(n, 1.0 / n).astype(int) for n in N]
@@ -117,14 +126,7 @@ def __init__(
117126
Ks[i] = (Ks[i] * Lp[i]).astype(float)
118127
K = [self.xp.broadcast_to(k, self.fft.shape(True)) for k in Ks]
119128
K = self.xp.array(K).astype(float)
120-
self.K2 = self.xp.sum(K * K, 0, dtype=float) # Laplacian in spectral space
121-
122-
# Need this for diagnostics
123-
self.dx = self.L[0] / nvars[0]
124-
self.dy = self.L[1] / nvars[1]
125-
126-
# work counters
127-
self.work_counters['rhs'] = WorkCounter()
129+
self.K2 = self.xp.sum(K * K, 0, dtype=float)
128130

129131
def eval_f(self, u, t):
130132
"""

pySDC/projects/GPU/configs/GS_configs.py

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class GrayScott(Config):
2727
num_frames = 200
2828
sweeper_type = 'IMEX'
2929
res_per_blob = 2**7
30+
ndim = 3
3031

3132
def get_LogToFile(self, ranks=None):
3233
import numpy as np
@@ -49,16 +50,14 @@ def process_solution(L):
4950
't': L.time + L.dt,
5051
'u': uend[0].get().view(np.ndarray),
5152
'v': uend[1].get().view(np.ndarray),
52-
'X': L.prob.X[0].get().view(np.ndarray),
53-
'Y': L.prob.X[1].get().view(np.ndarray),
53+
'X': L.prob.X.get().view(np.ndarray),
5454
}
5555
else:
5656
return {
5757
't': L.time + L.dt,
5858
'u': uend[0],
5959
'v': uend[1],
60-
'X': L.prob.X[0],
61-
'Y': L.prob.X[1],
60+
'X': L.prob.X,
6261
}
6362

6463
def logging_condition(L):
@@ -75,7 +74,7 @@ def logging_condition(L):
7574
LogToFile.logging_condition = logging_condition
7675
return LogToFile
7776

78-
def plot(self, P, idx, n_procs_list): # pragma: no cover
77+
def plot(self, P, idx, n_procs_list, projection='xy'): # pragma: no cover
7978
import numpy as np
8079
from matplotlib import ticker as tkr
8180

@@ -99,19 +98,49 @@ def plot(self, P, idx, n_procs_list): # pragma: no cover
9998
vmax['u'] = max([vmax['u'], buffer[f'u-{rank}']['u'].real.max()])
10099

101100
for rank in range(n_procs_list[2]):
102-
im = ax.pcolormesh(
103-
buffer[f'u-{rank}']['X'],
104-
buffer[f'u-{rank}']['Y'],
105-
buffer[f'u-{rank}']['v'].real,
106-
vmin=vmin['v'],
107-
vmax=vmax['v'],
108-
cmap='binary',
109-
)
101+
if len(buffer[f'u-{rank}']['X']) == 2:
102+
ax.set_xlabel('$x$')
103+
ax.set_ylabel('$y$')
104+
im = ax.pcolormesh(
105+
buffer[f'u-{rank}']['X'][0],
106+
buffer[f'u-{rank}']['X'][1],
107+
buffer[f'u-{rank}']['v'].real,
108+
vmin=vmin['v'],
109+
vmax=vmax['v'],
110+
cmap='binary',
111+
)
112+
else:
113+
v3d = buffer[f'u-{rank}']['v'].real
114+
115+
if projection == 'xy':
116+
slices = [slice(None), slice(None), v3d.shape[2] // 2]
117+
x = buffer[f'u-{rank}']['X'][0][*slices]
118+
y = buffer[f'u-{rank}']['X'][1][*slices]
119+
ax.set_xlabel('$x$')
120+
ax.set_ylabel('$y$')
121+
elif projection == 'xz':
122+
slices = [slice(None), v3d.shape[1] // 2, slice(None)]
123+
x = buffer[f'u-{rank}']['X'][0][*slices]
124+
y = buffer[f'u-{rank}']['X'][2][*slices]
125+
ax.set_xlabel('$x$')
126+
ax.set_ylabel('$z$')
127+
elif projection == 'yz':
128+
slices = [v3d.shape[0] // 2, slice(None), slice(None)]
129+
x = buffer[f'u-{rank}']['X'][1][*slices]
130+
y = buffer[f'u-{rank}']['X'][2][*slices]
131+
ax.set_xlabel('$y$')
132+
ax.set_ylabel('$z$')
133+
134+
im = ax.pcolormesh(
135+
x,
136+
y,
137+
v3d[*slices],
138+
vmin=vmin['v'],
139+
vmax=vmax['v'],
140+
cmap='binary',
141+
)
110142
fig.colorbar(im, cax, format=tkr.FormatStrFormatter('%.1f'))
111143
ax.set_title(f't={buffer[f"u-{rank}"]["t"]:.2f}')
112-
ax.set_xlabel('$x$')
113-
ax.set_ylabel('$y$')
114-
ax.set_aspect(1.0)
115144
ax.set_aspect(1.0)
116145
return fig
117146

@@ -130,7 +159,7 @@ def get_description(self, *args, res=-1, **kwargs):
130159
desc['sweeper_params']['QI'] = 'MIN-SR-S'
131160
desc['sweeper_params']['QE'] = 'PIC'
132161

133-
desc['problem_params']['nvars'] = (2**8 if res == -1 else res,) * 2
162+
desc['problem_params']['nvars'] = (2**8 if res == -1 else res,) * self.ndim
134163
desc['problem_params']['Du'] = 0.00002
135164
desc['problem_params']['Dv'] = 0.00001
136165
desc['problem_params']['A'] = 0.04
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import pytest
2+
3+
4+
@pytest.mark.mpi4py
5+
@pytest.mark.parametrize('nx', [8, 16])
6+
@pytest.mark.parametrize('ny', [8, 16])
7+
@pytest.mark.parametrize('nz', [0, 8])
8+
@pytest.mark.parametrize('f', [1, 3])
9+
@pytest.mark.parametrize('spectral', [True, False])
10+
@pytest.mark.parametrize('direction', [0, 1, 10])
11+
def test_derivative(nx, ny, nz, f, spectral, direction):
12+
from pySDC.implementations.problem_classes.generic_MPIFFT_Laplacian import IMEX_Laplacian_MPIFFT
13+
14+
nvars = (nx, ny)
15+
if nz > 0:
16+
nvars += (nz,)
17+
prob = IMEX_Laplacian_MPIFFT(nvars=nvars, spectral=spectral)
18+
19+
xp = prob.xp
20+
21+
if direction == 0:
22+
_u = xp.sin(f * prob.X[0])
23+
du_expect = -(f**2) * xp.sin(f * prob.X[0])
24+
elif direction == 1:
25+
_u = xp.sin(f * prob.X[1])
26+
du_expect = -(f**2) * xp.sin(f * prob.X[1])
27+
elif direction == 10:
28+
_u = xp.sin(f * prob.X[1]) + xp.cos(f * prob.X[0])
29+
du_expect = -(f**2) * xp.sin(f * prob.X[1]) - f**2 * xp.cos(f * prob.X[0])
30+
else:
31+
raise
32+
33+
if spectral:
34+
u = prob.fft.forward(_u)
35+
else:
36+
u = _u
37+
38+
_du = prob.eval_f(u, 0).impl
39+
40+
if spectral:
41+
du = prob.fft.backward(_du)
42+
else:
43+
du = _du
44+
assert xp.allclose(du, du_expect), 'Got unexpected derivative'
45+
46+
u2 = prob.solve_system(_du, factor=1e8, u0=du, t=0) * -1e8
47+
48+
if spectral:
49+
_u2 = prob.fft.backward(u2)
50+
else:
51+
_u2 = u2
52+
53+
assert xp.allclose(_u2, _u, atol=1e-7), 'Got unexpected inverse derivative'
54+
55+
56+
if __name__ == '__main__':
57+
test_derivative(6, 6, 6, 3, False, 1)

0 commit comments

Comments
 (0)