Skip to content

Commit 1aeb474

Browse files
committed
TL: full testing of FieldsIO, including MPI
1 parent 5a5375e commit 1aeb474

File tree

3 files changed

+204
-19
lines changed

3 files changed

+204
-19
lines changed

pySDC/playgrounds/dedalus/fieldsIO/base.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ def __init__(self, dtype, fileName):
5252
self.nItems = None # number of values (dof) stored into one field
5353

5454
def __str__(self):
55-
return f"FieldsIO[{self.__class__.__name__}, {self.dtype.__name__}, file:{self.fileName}]<{hex(id(self))}>"
55+
return f"FieldsIO[{self.__class__.__name__}|{self.dtype.__name__}|file:{self.fileName}]<{hex(id(self))}>"
56+
57+
def __repr__(self):
58+
return self.__str__()
5659

5760
@classmethod
5861
def fromFile(cls, fileName):
@@ -425,12 +428,12 @@ def readField(self, idx):
425428
y = np.linspace(0, 1, num=64, endpoint=False)
426429
nY = y.size
427430

428-
dim = 1
431+
nDim = 1
429432
dType = np.float64
430433

431-
if dim == 1:
434+
if nDim == 1:
432435
u0 = np.array([-1, 1])[:, None]*x[None, :]
433-
if dim == 2:
436+
if nDim == 2:
434437
u0 = np.array([-1, 1])[:, None, None]*x[None, :, None]*y[None, None, :]
435438
fileName = "test.pysdc"
436439

@@ -446,7 +449,7 @@ def readField(self, idx):
446449
fileName = "test_MPI.pysdc"
447450

448451

449-
if dim == 1:
452+
if nDim == 1:
450453
(iLocX, ), (nLocX, ) = bounds
451454
pRankX, = blocks.ranks
452455
Cart1D.setupMPI(comm, iLocX, nLocX)
@@ -460,7 +463,7 @@ def readField(self, idx):
460463
f1 = Cart1D(dType, fileName)
461464
f1.setHeader(nVar=u0.shape[0], gridX=x)
462465

463-
if dim == 2:
466+
if nDim == 2:
464467
(iLocX, iLocY), (nLocX, nLocY) = bounds
465468
pRankX, pRankY = blocks.ranks
466469
Cart2D.setupMPI(comm, iLocX, nLocX, iLocY, nLocY)

pySDC/playgrounds/dedalus/fieldsIO/blocks.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class BlockDecomposition(object):
1212
algo : str, optional
1313
Algorithm used for hte block decomposition :
1414
15-
- Hybrid : approach minimizing interface communication, taken from
15+
- Hybrid : approach minimizing interface communication, inspired from
1616
the `[Hybrid CFD solver] <https://web.stanford.edu/group/ctr/ResBriefs07/5_larsson1_pp47_58.pdf>`_.
1717
- ChatGPT : quickly generated using `[ChatGPT] <https://chatgpt.com>`_.
1818
@@ -22,7 +22,6 @@ class BlockDecomposition(object):
2222
order : str, optional
2323
The order used when computing the rank block distribution. Default is `C`.
2424
"""
25-
2625
def __init__(self, nProcs, gridSizes, algo="Hybrid", gRank=None, order="C"):
2726
dim = len(gridSizes)
2827
assert dim in [1, 2, 3], "block decomposition only works for 1D, 2D or 3D domains"

pySDC/playgrounds/dedalus/fieldsIO/tests.py

Lines changed: 194 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import pytest
22
import numpy as np
33

4+
from base import DTYPES
45

5-
@pytest.mark.parametrize("dtypeIdx", range(6))
6+
7+
@pytest.mark.parametrize("dtypeIdx", DTYPES.keys())
68
@pytest.mark.parametrize("nDim", range(3))
79
def testHeader(nDim, dtypeIdx):
8-
from base import FieldsIO, Scal0D, Cart1D, Cart2D, DTYPES
10+
from base import FieldsIO, Scal0D, Cart1D, Cart2D
911

1012
fileName = "testHeader.pysdc"
1113
dtype = DTYPES[dtypeIdx]
@@ -24,6 +26,7 @@ def testHeader(nDim, dtypeIdx):
2426
args = {"nVar": 10, "gridX": gridX, "gridY": gridY}
2527

2628
f1 = Class(dtype, fileName)
29+
assert f1.__str__() == f1.__repr__(), "__repr__ and __str__ do not return the same result"
2730
try:
2831
f1.initialize()
2932
except AssertionError:
@@ -56,11 +59,11 @@ def testHeader(nDim, dtypeIdx):
5659
assert np.allclose(val, f2.header[key]), f"header's discrepancy for {key} in written {f2}"
5760

5861

59-
@pytest.mark.parametrize("dtypeIdx", range(6))
62+
@pytest.mark.parametrize("dtypeIdx", DTYPES.keys())
6063
@pytest.mark.parametrize("nSteps", [1, 2, 10, 100])
6164
@pytest.mark.parametrize("nVar", [1, 2, 5])
6265
def testScal0D(nVar, nSteps, dtypeIdx):
63-
from base import FieldsIO, Scal0D, DTYPES
66+
from base import FieldsIO, Scal0D
6467

6568
fileName = "testScal0D.pysdc"
6669
dtype = DTYPES[dtypeIdx]
@@ -75,7 +78,8 @@ def testScal0D(nVar, nSteps, dtypeIdx):
7578
times = np.arange(nSteps)/nSteps
7679

7780
for t in times:
78-
f1.addField(t, u0*t)
81+
ut = (u0*t).astype(f1.dtype)
82+
f1.addField(t, ut)
7983

8084
assert f1.nFields == nSteps, f"{f1} do not have nFields == nSteps"
8185
assert np.allclose(f1.times, times), f"{f1} has wrong times stored in file"
@@ -95,7 +99,7 @@ def testScal0D(nVar, nSteps, dtypeIdx):
9599
assert np.allclose(u2, u1), f"{idx}'s fields in {f1} has incorrect values"
96100

97101

98-
@pytest.mark.parametrize("dtypeIdx", range(6))
102+
@pytest.mark.parametrize("dtypeIdx", DTYPES.keys())
99103
@pytest.mark.parametrize("nSteps", [1, 2, 5, 10])
100104
@pytest.mark.parametrize("nX", [5, 10, 16, 32, 64])
101105
@pytest.mark.parametrize("nVar", [1, 2, 5])
@@ -119,7 +123,8 @@ def testCart1D(nVar, nX, nSteps, dtypeIdx):
119123
times = np.arange(nSteps)/nSteps
120124

121125
for t in times:
122-
f1.addField(t, u0*t)
126+
ut = (u0*t).astype(f1.dtype)
127+
f1.addField(t, ut)
123128

124129
assert f1.nFields == nSteps, f"{f1} do not have nFields == nSteps"
125130
assert np.allclose(f1.times, times), f"{f1} has wrong times stored in file"
@@ -140,15 +145,15 @@ def testCart1D(nVar, nX, nSteps, dtypeIdx):
140145

141146

142147

143-
@pytest.mark.parametrize("dtypeIdx", range(6))
148+
@pytest.mark.parametrize("dtypeIdx", DTYPES.keys())
144149
@pytest.mark.parametrize("nSteps", [1, 2, 5, 10])
145150
@pytest.mark.parametrize("nY", [5, 10, 16])
146151
@pytest.mark.parametrize("nX", [5, 10, 16])
147152
@pytest.mark.parametrize("nVar", [1, 2, 5])
148153
def testCart2D(nVar, nX, nY, nSteps, dtypeIdx):
149154
from base import FieldsIO, Cart2D, DTYPES
150155

151-
fileName = "testCart1D.pysdc"
156+
fileName = "testCart2D.pysdc"
152157
dtype = DTYPES[dtypeIdx]
153158

154159
gridX = np.linspace(0, 1, num=nX, endpoint=False)
@@ -166,7 +171,8 @@ def testCart2D(nVar, nX, nY, nSteps, dtypeIdx):
166171
times = np.arange(nSteps)/nSteps
167172

168173
for t in times:
169-
f1.addField(t, u0*t)
174+
ut = (u0*t).astype(f1.dtype)
175+
f1.addField(t, ut)
170176

171177
assert f1.nFields == nSteps, f"{f1} do not have nFields == nSteps"
172178
assert np.allclose(f1.times, times), f"{f1} has wrong times stored in file"
@@ -183,4 +189,181 @@ def testCart2D(nVar, nX, nY, nSteps, dtypeIdx):
183189
t2, u2 = f2.readField(idx)
184190
assert t2 == t, f"{idx}'s fields in {f1} has incorrect time"
185191
assert u2.shape == u1.shape, f"{idx}'s fields in {f1} has incorrect shape"
186-
assert np.allclose(u2, u1), f"{idx}'s fields in {f1} has incorrect values"
192+
assert np.allclose(u2, u1), f"{idx}'s fields in {f1} has incorrect values"
193+
194+
195+
def initGrid(nVar, nX, nY=None):
196+
nDim = 1
197+
if nY is not None:
198+
nDim += 1
199+
x = np.linspace(0, 1, num=nX, endpoint=False)
200+
grids = (x, )
201+
gridSizes = (nX, )
202+
u0 = np.array(np.arange(nVar)+1)[:, None]*x[None, :]
203+
204+
if nDim > 1:
205+
y = np.linspace(0, 1, num=nY, endpoint=False)
206+
grids += (y, )
207+
gridSizes += (nY, )
208+
u0 = u0[:, :, None]*y[None, None, :]
209+
210+
return grids, gridSizes, u0
211+
212+
213+
def writeFields_MPI(fileName, nDim, dtypeIdx, algo, nSteps, nVar, nX, nY=None):
214+
grids, gridSizes, u0 = initGrid(nVar, nX, nY)
215+
216+
from mpi4py import MPI
217+
comm = MPI.COMM_WORLD
218+
MPI_SIZE = comm.Get_size()
219+
MPI_RANK = comm.Get_rank()
220+
221+
from blocks import BlockDecomposition
222+
blocks = BlockDecomposition(MPI_SIZE, gridSizes, algo, MPI_RANK)
223+
224+
from time import sleep
225+
226+
from base import Cart1D, Cart2D
227+
if nDim == 1:
228+
(iLocX, ), (nLocX, ) = blocks.localBounds
229+
pRankX, = blocks.ranks
230+
Cart1D.setupMPI(comm, iLocX, nLocX)
231+
u0 = u0[:, iLocX:iLocX+nLocX]
232+
233+
MPI.COMM_WORLD.Barrier()
234+
sleep(0.01*MPI_RANK)
235+
print(f"[Rank {MPI_RANK}] pRankX={pRankX} ({iLocX}, {nLocX})")
236+
MPI.COMM_WORLD.Barrier()
237+
238+
f1 = Cart1D(DTYPES[dtypeIdx], fileName)
239+
f1.setHeader(nVar=nVar, gridX=grids[0])
240+
241+
if nDim == 2:
242+
(iLocX, iLocY), (nLocX, nLocY) = blocks.localBounds
243+
Cart2D.setupMPI(comm, iLocX, nLocX, iLocY, nLocY)
244+
u0 = u0[:, iLocX:iLocX+nLocX, iLocY:iLocY+nLocY]
245+
246+
f1 = Cart2D(DTYPES[dtypeIdx], fileName)
247+
f1.setHeader(nVar=nVar, gridX=grids[0], gridY=grids[1])
248+
249+
u0 = np.asarray(u0, dtype=f1.dtype)
250+
f1.initialize()
251+
252+
times = np.arange(nSteps)/nSteps
253+
for t in times:
254+
ut = (u0*t).astype(f1.dtype)
255+
f1.addField(t, ut)
256+
257+
return u0
258+
259+
260+
def compareFields_MPI(fileName, u0, nSteps):
261+
from base import FieldsIO
262+
f2 = FieldsIO.fromFile(fileName)
263+
264+
times = np.arange(nSteps)/nSteps
265+
for idx, t in enumerate(times):
266+
u1 = u0*t
267+
t2, u2 = f2.readField(idx)
268+
assert t2 == t, f"{idx}'s fields in {f2} has incorrect time"
269+
assert u2.shape == u1.shape, f"{idx}'s fields in {f2} has incorrect shape"
270+
assert np.allclose(u2, u1), f"{idx}'s fields in {f2} has incorrect values"
271+
272+
273+
@pytest.mark.parametrize("nX", [61, 16, 32])
274+
@pytest.mark.parametrize("nVar", [1, 4])
275+
@pytest.mark.parametrize("nSteps", [1, 10])
276+
@pytest.mark.parametrize("algo", ["ChatGPT", "Hybrid"])
277+
@pytest.mark.parametrize("dtypeIdx", [0, 1])
278+
@pytest.mark.parametrize("nProcs", [1, 2, 4])
279+
def testCart1D_MPI(nProcs, dtypeIdx, algo, nSteps, nVar, nX):
280+
281+
import subprocess
282+
fileName = "testCart1D_MPI.pysdc"
283+
284+
cmd = f"mpirun -np {nProcs} python {__file__} --fileName {fileName} --nDim 1 "
285+
cmd += f"--dtypeIdx {dtypeIdx} --algo {algo} --nSteps {nSteps} --nVar {nVar} --nX {nX}"
286+
287+
p = subprocess.Popen(cmd.split(), cwd=".")
288+
p.wait()
289+
assert p.returncode == 0, f"MPI write with {nProcs} did not return code 0, but {p.returncode}"
290+
291+
from base import FieldsIO, Cart1D
292+
293+
f2:Cart1D = FieldsIO.fromFile(fileName)
294+
295+
assert type(f2) == Cart1D, f"incorrect type in MPI written fields {f2}"
296+
assert f2.nFields == nSteps, f"incorrect nFields in MPI written fields {f2}"
297+
assert f2.nVar == nVar, f"incorrect nVar in MPI written fields {f2}"
298+
assert f2.nX == nX, f"incorrect nX in MPI written fields {f2}"
299+
300+
grids, _, u0 = initGrid(nVar, nX)
301+
assert np.allclose(f2.header['gridX'], grids[0]), f"incorrect gridX in MPI written fields {f2}"
302+
303+
times = np.arange(nSteps)/nSteps
304+
for idx, t in enumerate(times):
305+
u1 = u0*t
306+
t2, u2 = f2.readField(idx)
307+
assert t2 == t, f"{idx}'s fields in {f2} has incorrect time"
308+
assert u2.shape == u1.shape, f"{idx}'s fields in {f2} has incorrect shape"
309+
assert np.allclose(u2, u1), f"{idx}'s fields in {f2} has incorrect values"
310+
311+
312+
@pytest.mark.parametrize("nY", [61, 16, 32])
313+
@pytest.mark.parametrize("nX", [61, 16, 32])
314+
@pytest.mark.parametrize("nVar", [1, 4])
315+
@pytest.mark.parametrize("nSteps", [1, 10])
316+
@pytest.mark.parametrize("algo", ["ChatGPT", "Hybrid"])
317+
@pytest.mark.parametrize("dtypeIdx", [0, 1])
318+
@pytest.mark.parametrize("nProcs", [1, 2, 4])
319+
def testCart2D_MPI(nProcs, dtypeIdx, algo, nSteps, nVar, nX, nY):
320+
321+
import subprocess
322+
fileName = "testCart2D_MPI.pysdc"
323+
324+
cmd = f"mpirun -np {nProcs} python {__file__} --fileName {fileName} --nDim 2 "
325+
cmd += f"--dtypeIdx {dtypeIdx} --algo {algo} --nSteps {nSteps} --nVar {nVar} --nX {nX} --nY {nY}"
326+
327+
p = subprocess.Popen(cmd.split(), cwd=".")
328+
p.wait()
329+
assert p.returncode == 0, f"MPI write with {nProcs} did not return code 0, but {p.returncode}"
330+
331+
from base import FieldsIO, Cart2D
332+
333+
f2:Cart2D = FieldsIO.fromFile(fileName)
334+
335+
assert type(f2) == Cart2D, f"incorrect type in MPI written fields {f2}"
336+
assert f2.nFields == nSteps, f"incorrect nFields in MPI written fields {f2}"
337+
assert f2.nVar == nVar, f"incorrect nVar in MPI written fields {f2}"
338+
assert f2.nX == nX, f"incorrect nX in MPI written fields {f2}"
339+
assert f2.nY == nY, f"incorrect nY in MPI written fields {f2}"
340+
341+
grids, _, u0 = initGrid(nVar, nX, nY)
342+
assert np.allclose(f2.header['gridX'], grids[0]), f"incorrect gridX in MPI written fields {f2}"
343+
assert np.allclose(f2.header['gridY'], grids[1]), f"incorrect gridY in MPI written fields {f2}"
344+
345+
times = np.arange(nSteps)/nSteps
346+
for idx, t in enumerate(times):
347+
u1 = u0*t
348+
t2, u2 = f2.readField(idx)
349+
assert t2 == t, f"{idx}'s fields in {f2} has incorrect time"
350+
assert u2.shape == u1.shape, f"{idx}'s fields in {f2} has incorrect shape"
351+
assert np.allclose(u2, u1), f"{idx}'s fields in {f2} has incorrect values"
352+
353+
354+
if __name__ == "__main__":
355+
import argparse
356+
357+
parser = argparse.ArgumentParser()
358+
parser.add_argument('--fileName', type=str, help='fileName of the file')
359+
parser.add_argument('--nDim', type=int, help='space dimension', choices=[1, 2])
360+
parser.add_argument('--dtypeIdx', type=int, help="dtype index", choices=DTYPES.keys())
361+
parser.add_argument('--algo', type=str, help="algorithm used for block decomposition", choices=["ChatGPT", "Hybrid"])
362+
parser.add_argument('--nSteps', type=int, help="number of field variables")
363+
parser.add_argument('--nVar', type=int, help="number of field variables")
364+
parser.add_argument('--nX', type=int, help="number of grid points in x dimension")
365+
parser.add_argument('--nY', type=int, help="number of grid points in y dimension")
366+
args = parser.parse_args()
367+
368+
u0 = writeFields_MPI(**args.__dict__)
369+
compareFields_MPI(args.fileName, u0, args.nSteps)

0 commit comments

Comments
 (0)