Skip to content

Commit f4df805

Browse files
authored
Merge pull request #32 from anlavandier/master
Enhancement: extending `pyevtk` to allow for usage of Parallel VTK files
2 parents d46c0f0 + 77567f4 commit f4df805

4 files changed

Lines changed: 484 additions & 12 deletions

File tree

examples/rectilinear.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@
3030
# * This example shows how to export a rectilinear grid. *
3131
# **************************************************************
3232

33-
from pyevtk.hl import gridToVTK
33+
from pyevtk.hl import gridToVTK, writeParallelVTKGrid
3434
import numpy as np
3535

36+
# ==================
37+
# Serial example
38+
# ==================
39+
3640
# Dimensions
3741
nx, ny, nz = 6, 6, 2
3842
lx, ly, lz = 1.0, 1.0, 1.0
@@ -58,3 +62,26 @@
5862
cellData={"pressure": pressure},
5963
pointData={"temp": temp},
6064
)
65+
66+
67+
# ==================
68+
# Parallel example
69+
# ==================
70+
71+
# Dimensions
72+
x1 = np.linspace(0, 1, 10)
73+
x2 = np.linspace(1, 2, 20)
74+
75+
y = np.linspace(0, 1, 10)
76+
z = np.linspace(0, 1, 10)
77+
78+
gridToVTK("test.0", x1, y, z, start=(0, 0, 0))
79+
gridToVTK("test.1", x2, y, z, start=(9, 0, 0))
80+
81+
writeParallelVTKGrid(
82+
"test_full",
83+
coordsData=((29, 10, 10), x.dtype),
84+
starts=[(0, 0, 0), (9, 0, 0)],
85+
ends=[(9, 9, 9), (28, 9, 9)],
86+
sources=["test.0.vtr", "test.1.vtr"],
87+
)

examples/structured.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,14 @@
3030
# * This example shows how to export a structured grid. *
3131
# **************************************************************
3232

33-
from pyevtk.hl import gridToVTK
33+
from pyevtk.hl import gridToVTK, writeParallelVTKGrid
3434
import numpy as np
3535
import random as rnd
3636

37+
# ===================
38+
# Serial Example
39+
# ===================
40+
3741
# Dimensions
3842
nx, ny, nz = 6, 6, 2
3943
lx, ly, lz = 1.0, 1.0, 1.0
@@ -72,3 +76,55 @@
7276
cellData={"pressure": pressure},
7377
pointData={"temp": temp},
7478
)
79+
80+
81+
# ===================
82+
# Parallel example
83+
# ===================
84+
85+
# Dimensions
86+
x1 = np.linspace(0, 1, 20)
87+
88+
x2_1 = np.linspace(0, 0.5, 10)
89+
x2_2 = np.linspace(0.5, 1, 10)
90+
91+
x3 = np.linspace(0, 2, 30)
92+
93+
XX1_1, XX2_1, XX3_1 = np.meshgrid(x1, x2_1, x3, indexing="ij")
94+
XX1_2, XX2_2, XX3_2 = np.meshgrid(x1, x2_2, x3, indexing="ij")
95+
96+
pi = np.pi
97+
sphere = lambda R, Th, Ph: (
98+
R * np.cos(pi * Ph) * np.sin(pi * Th),
99+
R * np.sin(pi * Ph) * np.sin(pi * Th),
100+
R * np.cos(pi * Th),
101+
)
102+
103+
# First Half sphere
104+
gridToVTK(
105+
"sphere.0",
106+
*sphere(XX1_1, XX2_1, XX3_1),
107+
start=(0, 0, 0),
108+
pointData={"R": XX1_1, "Theta": XX2_1, "Phi": XX3_1}
109+
)
110+
# Second Half sphere
111+
gridToVTK(
112+
"sphere.1",
113+
*sphere(XX1_2, XX2_2, XX3_2),
114+
start=(0, 9, 0),
115+
pointData={"R": XX1_2, "Theta": XX2_2, "Phi": XX3_2}
116+
)
117+
118+
# Write parallel file
119+
writeParallelVTKGrid(
120+
"sphere_full",
121+
coordsData=((20, 19, 30), XX1_1.dtype),
122+
starts=[(0, 0, 0), (0, 9, 0)],
123+
ends=[(19, 9, 29), (19, 18, 29)],
124+
sources=["sphere.0.vts", "sphere.1.vts"],
125+
pointData={
126+
"R": (XX1_1.dtype, 1),
127+
"Theta": (XX2_1.dtype, 1),
128+
"Phi": (XX3_1.dtype, 1),
129+
},
130+
)

pyevtk/hl.py

Lines changed: 142 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@
2727
import numpy as np
2828
from .vtk import (
2929
VtkFile,
30+
VtkParallelFile,
3031
VtkUnstructuredGrid,
3132
VtkImageData,
3233
VtkRectilinearGrid,
3334
VtkStructuredGrid,
35+
VtkPImageData,
36+
VtkPRectilinearGrid,
37+
VtkPStructuredGrid,
38+
VtkUnstructuredGrid,
3439
VtkVertex,
3540
VtkLine,
3641
VtkPolyLine,
@@ -81,6 +86,33 @@ def _addDataToFile(vtkFile, cellData, pointData, fieldData=None):
8186
vtkFile.closeData("Field")
8287

8388

89+
def _addDataToParallelFile(vtkParallelFile, cellData, pointData):
90+
assert isinstance(vtkParallelFile, VtkParallelFile)
91+
# Point data
92+
if pointData:
93+
keys = list(pointData.keys())
94+
# find first scalar and vector data key to set it as attribute
95+
scalars = next((key for key in keys if pointData[key][1] == 1), None)
96+
vectors = next((key for key in keys if pointData[key][1] == 3), None)
97+
vtkParallelFile.openData("PPoint", scalars=scalars, vectors=vectors)
98+
for key in keys:
99+
dtype, ncomp = pointData[key]
100+
vtkParallelFile.addHeader(key, dtype=dtype, ncomp=ncomp)
101+
vtkParallelFile.closeData("PPoint")
102+
103+
# Cell data
104+
if cellData:
105+
keys = list(cellData.keys())
106+
# find first scalar and vector data key to set it as attribute
107+
scalars = next((key for key in keys if cellData[key][1] == 1), None)
108+
vectors = next((key for key in keys if cellData[key][1] == 3), None)
109+
vtkParallelFile.openData("PCell", scalars=scalars, vectors=vectors)
110+
for key in keys:
111+
dtype, ncomp = cellData[key]
112+
vtkParallelFile.addHeader(key, dtype=dtype, ncomp=ncomp)
113+
vtkParallelFile.closeData("PCell")
114+
115+
84116
def _appendDataToFile(vtkFile, cellData, pointData, fieldData=None):
85117
# Append data to binary section
86118
if pointData is not None:
@@ -112,6 +144,7 @@ def imageToVTK(
112144
cellData=None,
113145
pointData=None,
114146
fieldData=None,
147+
start=(0, 0, 0),
115148
):
116149
"""
117150
Export data values as a rectangular image.
@@ -120,6 +153,10 @@ def imageToVTK(
120153
----------
121154
path : str
122155
name of the file without extension where data should be saved.
156+
start : tuple, optional
157+
start of the coordinates.
158+
Used in the distributed context where each process
159+
writes its own vtk file. Default is (0, 0, 0).
123160
origin : tuple, optional
124161
grid origin.
125162
The default is (0.0, 0.0, 0.0).
@@ -138,7 +175,7 @@ def imageToVTK(
138175
Arrays must have same dimension in each direction and
139176
they should be equal to the dimensions of the cell data plus one and
140177
can contain scalar data ([n+1,n+1,n+1]) or
141-
+1,n+1,n+1],[n+1,n+1,n+1],[n+1,n+1,n+1]).
178+
([n+1,n+1,n+1],[n+1,n+1,n+1],[n+1,n+1,n+1]).
142179
The default is None.
143180
fieldData : dict, optional
144181
dictionary with variables associated with the field.
@@ -157,7 +194,6 @@ def imageToVTK(
157194
assert cellData is not None or pointData is not None
158195

159196
# Extract dimensions
160-
start = (0, 0, 0)
161197
end = None
162198
if cellData is not None:
163199
keys = list(cellData.keys())
@@ -188,9 +224,11 @@ def imageToVTK(
188224

189225

190226
# ==============================================================================
191-
def gridToVTK(path, x, y, z, cellData=None, pointData=None, fieldData=None):
227+
def gridToVTK(
228+
path, x, y, z, cellData=None, pointData=None, fieldData=None, start=(0, 0, 0)
229+
):
192230
"""
193-
Write data values as a rectilinear or rectangular grid.
231+
Write data values as a rectilinear or structured grid.
194232
195233
Parameters
196234
----------
@@ -202,6 +240,10 @@ def gridToVTK(path, x, y, z, cellData=None, pointData=None, fieldData=None):
202240
y coordinate axis.
203241
z : array-like
204242
z coordinate axis.
243+
start : tuple, optional
244+
start of the coordinates.
245+
Used in the distributed context where each process
246+
writes its own vtk file. Default is (0, 0, 0).
205247
cellData : dict, optional
206248
dictionary containing arrays with cell centered data.
207249
Keys should be the names of the data arrays.
@@ -216,12 +258,10 @@ def gridToVTK(path, x, y, z, cellData=None, pointData=None, fieldData=None):
216258
fieldData : dict, optional
217259
dictionary with variables associated with the field.
218260
Keys should be the names of the variable stored in each array.
219-
220261
Returns
221262
-------
222263
str
223264
Full path to saved file.
224-
225265
Notes
226266
-----
227267
coordinates of the nodes of the grid. They can be 1D or 3D depending if
@@ -235,8 +275,6 @@ def gridToVTK(path, x, y, z, cellData=None, pointData=None, fieldData=None):
235275
In both cases the arrays dimensions should be
236276
equal to the number of nodes of the grid.
237277
"""
238-
# Extract dimensions
239-
start = (0, 0, 0)
240278
nx = ny = nz = 0
241279

242280
if x.ndim == 1 and y.ndim == 1 and z.ndim == 1:
@@ -249,13 +287,22 @@ def gridToVTK(path, x, y, z, cellData=None, pointData=None, fieldData=None):
249287
isRect = False
250288
ftype = VtkStructuredGrid
251289
else:
252-
assert False
253-
end = (nx, ny, nz)
290+
raise ValueError(
291+
f"x, y and z should have ndim == 3 but they have ndim of {x.ndim}, {y.ndim}"
292+
f" and {z.ndim} respectively"
293+
)
254294

295+
# Write extent
296+
end = (start[0] + nx, start[1] + ny, start[2] + nz)
297+
298+
# Open File
255299
w = VtkFile(path, ftype)
300+
301+
# Open Grid part
256302
w.openGrid(start=start, end=end)
257303
w.openPiece(start=start, end=end)
258304

305+
# Add coordinates description
259306
if isRect:
260307
w.openElement("Coordinates")
261308
w.addData("x_coordinates", x)
@@ -267,16 +314,101 @@ def gridToVTK(path, x, y, z, cellData=None, pointData=None, fieldData=None):
267314
w.addData("points", (x, y, z))
268315
w.closeElement("Points")
269316

317+
# Add data description
270318
_addDataToFile(w, cellData, pointData, fieldData)
319+
320+
# Close Grid part
271321
w.closePiece()
272322
w.closeGrid()
323+
273324
# Write coordinates
274325
if isRect:
275326
w.appendData(x).appendData(y).appendData(z)
276327
else:
277328
w.appendData((x, y, z))
278329
# Write data
279330
_appendDataToFile(w, cellData, pointData, fieldData)
331+
332+
# Close file
333+
w.save()
334+
335+
return w.getFileName()
336+
337+
338+
def writeParallelVTKGrid(
339+
path, coordsData, starts, ends, sources, ghostlevel=0, cellData=None, pointData=None
340+
):
341+
"""
342+
Writes a parallel vtk file from grid-like data:
343+
VTKStructuredGrid or VTKRectilinearGrid
344+
345+
Parameters
346+
----------
347+
path : str
348+
name of the file without extension.
349+
coordsData : tuple
350+
2-tuple (shape, dtype) where shape is the
351+
shape of the coordinates of the full mesh
352+
and dtype is the dtype of the coordinates.
353+
starts : list
354+
list of 3-tuple representing where each source file starts
355+
in each dimension
356+
source : list
357+
list of the relative paths of the source files where the actual data is found
358+
ghostlevel : int, optional
359+
Number of ghost-levels by which
360+
the extents in the individual source files overlap.
361+
pointData : dict
362+
dictionnary containing the information about the arrays
363+
containing node centered data.
364+
Keys shoud be the names of the arrays.
365+
Values are (dtype, number of components)
366+
cellData :
367+
dictionnary containing the information about the arrays
368+
containing cell centered data.
369+
Keys shoud be the names of the arrays.
370+
Values are (dtype, number of components)
371+
"""
372+
# Check that every source as a start and an end
373+
assert len(starts) == len(ends) == len(sources)
374+
375+
# Get the extension + check that it's consistent accros all source files
376+
common_ext = sources[0].split(".")[-1]
377+
assert all(s.split(".")[-1] == common_ext for s in sources)
378+
379+
if common_ext == "vts":
380+
ftype = VtkPStructuredGrid
381+
is_Rect = False
382+
elif common_ext == "vtr":
383+
ftype = VtkPRectilinearGrid
384+
is_Rect = True
385+
else:
386+
raise ValueError("This functions is meant to work only with ")
387+
388+
w = VtkParallelFile(path, ftype)
389+
start = (0, 0, 0)
390+
(s_x, s_y, s_z), dtype = coordsData
391+
end = s_x - 1, s_y - 1, s_z - 1
392+
393+
w.openGrid(start=start, end=end, ghostlevel=ghostlevel)
394+
395+
_addDataToParallelFile(w, cellData=cellData, pointData=pointData)
396+
397+
if is_Rect:
398+
w.openElement("PCoordinates")
399+
w.addHeader("x_coordinates", dtype=dtype, ncomp=1)
400+
w.addHeader("y_coordinates", dtype=dtype, ncomp=1)
401+
w.addHeader("z_coordinates", dtype=dtype, ncomp=1)
402+
w.closeElement("PCoordinates")
403+
else:
404+
w.openElement("PPoints")
405+
w.addHeader("points", dtype=dtype, ncomp=3)
406+
w.closeElement("PPoints")
407+
408+
for start_source, end_source, source in zip(starts, ends, sources):
409+
w.addPiece(start_source, end_source, source)
410+
411+
w.closeGrid()
280412
w.save()
281413
return w.getFileName()
282414

0 commit comments

Comments
 (0)