-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy pathpvtk_grid.jl
358 lines (304 loc) · 11.7 KB
/
pvtk_grid.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# Helper types
struct PVTKArgs
part::Int
nparts::Int
ismain::Bool
ghost_level::Int
end
struct PVTKFile <: VTKFile
pvtkargs::PVTKArgs
xdoc::XMLDocument
vtk::DatasetFile
path::String
end
# This is just to make a PVTKFile work like a DatasetFile.
# Only used when writing VTKFieldData to a PVTK file.
# Note that data is always written as text (ASCII).
data_format(::PVTKFile) = :ascii
# Returns true if the arguments do *not* contain any cell vectors.
_pvtk_is_structured(x::AbstractVector{<:AbstractMeshCell}, args...) = Val(false)
_pvtk_is_structured(x, args...) = _pvtk_is_structured(args...)
_pvtk_is_structured() = Val(true)
_pvtk_nparts(structured::Val{false}; nparts::Integer, etc...) = nparts
_pvtk_nparts(structured::Val{true}; extents::AbstractArray, etc...) = length(extents)
_pvtk_extents(structured::Val{false}; etc...) = nothing
_pvtk_extents(structured::Val{true}; extents::AbstractArray, etc...) = extents
# Filter keyword arguments to be passed to vtk_grid.
_remove_parallel_kwargs(; nparts = nothing, extents = nothing, kws...) =
NamedTuple(kws)
# Determine whole extent from the local extents associated to each process.
function compute_whole_extent(extents::AbstractArray{<:Extent})
# Compute the minimum and maximum across each dimension
begins = mapreduce(ext -> map(first, ext), min, extents)
ends = mapreduce(ext -> map(last, ext), max, extents)
map((b, e) -> b:e, begins, ends)
end
compute_whole_extent(::Nothing) = nothing
"""
pvtk_grid(
filename, args...;
part, nparts, extents, ismain = (part == 1), ghost_level = 0,
kwargs...,
)
Returns a handler representing a parallel VTK file, which can be
eventually written to file with `vtk_save`.
Positional and keyword arguments in `args` and `kwargs` are passed to `vtk_grid`
verbatim.
Note that serial filenames are automatically generated from `filename` and from
the process id `part`.
The following keyword arguments only apply to parallel VTK file formats.
Mandatory ones are:
- `part`: current (1-based) part id,
- `nparts`: total number of parts (only required for **unstructured** grids),
- `extents`: array specifying the partitioning of a **structured** grid across
different processes (see below for details).
Optional ones are:
- `ismain`: `true` if the current part id `part` is the main (the only one that will write the `.pvtk` file),
- `ghost_level`: ghost level.
## Specifying extents for a structured grid
For structured grids, the partitioning of the dataset across different processes
must be specified via the `extents` argument.
This is an array where each element represents the data extent associated to a
given process.
For example, for a dataset of global dimensions ``15×12×4`` distributed across 4
processes, this array may look like the following:
```julia
extents = [
( 1:10, 1:5, 1:4), # process 1
(10:15, 1:5, 1:4), # process 2
( 1:10, 5:12, 1:4), # process 3
(10:15, 5:12, 1:4), # process 4
]
```
Some important notes:
- the `extents` argument must be the same for all processes;
- the extents **must overlap**, or VTK / ParaView will complain when trying to
open the files;
- the length of the `extents` array gives the number of processes.
Therefore, the `nparts` argument is redundant and does not need to be passed.
"""
function pvtk_grid(
filename::AbstractString, args...;
part, ismain = (part == 1), ghost_level = 0, kwargs...,
)
is_structured = _pvtk_is_structured(args...)
nparts = _pvtk_nparts(is_structured; kwargs...)
extents = _pvtk_extents(is_structured; kwargs...)
prefix = _pvtk_vtk_filename_prefix(filename; relative_to_pvtk = false, create_dirs = true)
filename_serial = _serial_filename(part, nparts, prefix, "")
vtk = let kws_vtk = _remove_parallel_kwargs(; kwargs...)
kws = if extents === nothing
kws_vtk
else
(; kws_vtk..., extent = extents[part])
end
vtk_grid(filename_serial, args...; kws...)
end
pvtkargs = PVTKArgs(part, nparts, ismain, ghost_level)
xdoc = XMLDocument()
_, ext = splitext(vtk.path)
path = filename * ".p" * ext[2:end]
pvtk = PVTKFile(pvtkargs, xdoc, vtk, path)
_init_pvtk!(pvtk, extents)
pvtk
end
# Add point and cell data as usual
function Base.setindex!(
pvtk::PVTKFile, data, name::AbstractString, loc::AbstractFieldData,
)
pvtk.vtk[name, loc] = data
end
# In the case of field data, also add it to the pvtk file.
# Otherwise field data (typically the time / "TimeValue") is not seen by ParaView.
function Base.setindex!(
pvtk::PVTKFile, data, name::AbstractString, loc::VTKFieldData,
)
pvtk.vtk[name, loc] = data
if pvtk.pvtkargs.ismain
add_field_data(pvtk, data, name, loc)
end
data
end
# Used in add_field_data.
# We need to find the PUnstructuredGrid / PStructuredGrid / ... node.
function find_base_xml_node_to_add_field(pvtk::PVTKFile, loc)
xroot = root(pvtk.xdoc)
pgrid_type = "P" * pvtk.vtk.grid_type
find_element(xroot, pgrid_type)
end
# If `loc` was not passed, try to guess which kind of data was passed.
function Base.setindex!(
pvtk::PVTKFile, data, name::AbstractString,
)
loc = guess_data_location(data, pvtk.vtk) :: AbstractFieldData
pvtk[name, loc] = data
end
# Write XML attribute.
# Example:
#
# pvtk[VTKCellData()] = Dict("HigherOrderDegrees" => "HigherOrderDegreesDataset")
#
function Base.setindex!(
pvtk::PVTKFile, attributes, loc::AbstractFieldData,
)
pvtk.vtk[loc] = attributes
end
# Save as usual
function vtk_save(pvtk::PVTKFile)
outfiles = String[]
if isopen(pvtk)
if pvtk.pvtkargs.ismain
_update_pvtk!(pvtk)
save_file(pvtk.xdoc, pvtk.path)
push!(outfiles, pvtk.path)
end
append!(outfiles, vtk_save(pvtk.vtk))
close(pvtk)
end
outfiles
end
# Helper functions
function _serial_filename(part, nparts, prefix, extension)
p = lpad(part, ndigits(nparts), '0')
prefix * "_$p" * extension
end
# Determine base filename (or "prefix") for serial VTK files included in a parallel VTK
# file.
# Here `path` is the basename of the main pvtk file (without the extension).
# Note that it can be in a subdirectory of `.`, and it can either be an absolute or relative
# path.
# If it is an absolute value, then always return an absolute value.
function _pvtk_vtk_filename_prefix(path; relative_to_pvtk, create_dirs = false)
dir_serial = path # directory where serial files will be written
if create_dirs
mkpath(dir_serial)
end
bname = basename(path)
if isabspath(path) || !relative_to_pvtk
joinpath(dir_serial, bname)
else
_, reldir = splitdir(dir_serial)
joinpath(reldir, bname)
end
end
function _init_pvtk!(pvtk::PVTKFile, extents)
# Recover some data
vtk = pvtk.vtk
pvtkargs = pvtk.pvtkargs
pgrid_type = "P" * vtk.grid_type
npieces = pvtkargs.nparts
pref, _ = splitext(pvtk.path)
_, ext = splitext(vtk.path)
prefix = _pvtk_vtk_filename_prefix(pref; relative_to_pvtk = true)
# VTKFile (root) node
pvtk_root = create_root(pvtk.xdoc, "VTKFile")
set_attribute(pvtk_root, "type", pgrid_type)
set_attribute(pvtk_root, "version", "1.0")
if IS_LITTLE_ENDIAN
set_attribute(pvtk_root, "byte_order", "LittleEndian")
else
set_attribute(pvtk_root, "byte_order", "BigEndian")
end
# Grid node
pvtk_grid = new_child(pvtk_root, pgrid_type)
set_attribute(pvtk_grid, "GhostLevel", string(pvtkargs.ghost_level))
# Pieces (i.e. Pointers to serial files)
for piece ∈ 1:npieces
pvtk_piece = new_child(pvtk_grid, "Piece")
fn = _serial_filename(piece, npieces, prefix, ext)
set_attribute(pvtk_piece, "Source", fn)
# Add local extent if necessary
if extents !== nothing
set_attribute(pvtk_piece, "Extent", extent_attribute(extents[piece]))
end
end
# Add whole extent if necessary
whole_extent = compute_whole_extent(extents)
if whole_extent !== nothing
set_attribute(pvtk_grid, "WholeExtent", extent_attribute(whole_extent))
end
# Getting original grid informations
# Recover point type and number of components
vtk_root = root(vtk.xdoc)
vtk_grid = find_element(vtk_root, vtk.grid_type)
# adding origin and spacing if necessary
origin = attribute(vtk_grid, "Origin")
if origin !== nothing
set_attribute(pvtk_grid, "Origin", origin)
end
spacing = attribute(vtk_grid, "Spacing")
if spacing !== nothing
set_attribute(pvtk_grid, "Spacing", spacing)
end
# Getting original piece informations
vtk_piece = find_element(vtk_grid, "Piece")
# If serial VTK has points
vtk_points = find_element(vtk_piece, "Points")
if vtk_points !== nothing
vtk_data_array = find_element(vtk_points, "DataArray")
point_type = attribute(vtk_data_array, "type")
Nc = attribute(vtk_data_array, "NumberOfComponents")
## PPoints node
pvtk_ppoints = new_child(pvtk_grid, "PPoints")
pvtk_pdata_array = new_child(pvtk_ppoints, "PDataArray")
set_attribute(pvtk_pdata_array, "type", point_type)
set_attribute(pvtk_pdata_array, "Name", "Points")
set_attribute(pvtk_pdata_array, "NumberOfComponents", Nc)
end
# If serial VTK has coordinates
vtk_coordinates = find_element(vtk_piece, "Coordinates")
if vtk_coordinates !== nothing
pvtk_pcoordinates = new_child(pvtk_grid, "PCoordinates")
for c in child_elements(vtk_coordinates)
pvtk_pdata_array = new_child(pvtk_pcoordinates, "PDataArray")
set_attribute(pvtk_pdata_array, "type", attribute(c, "type"))
set_attribute(pvtk_pdata_array, "Name", attribute(c, "Name"))
set_attribute(pvtk_pdata_array, "NumberOfComponents", attribute(c, "NumberOfComponents"))
end
end
pvtk
end
function _update_pvtk!(pvtk::PVTKFile)
vtk = pvtk.vtk
vtk_root = root(vtk.xdoc)
vtk_grid = find_element(vtk_root, vtk.grid_type)
vtk_piece = find_element(vtk_grid, "Piece")
pgrid_type = "P" * vtk.grid_type
pvtk_root = root(pvtk.xdoc)
pvtk_grid = find_element(pvtk_root, pgrid_type)
# Generate PPointData
vtk_point_data = find_element(vtk_piece, "PointData")
if vtk_point_data !== nothing
pvtk_ppoint_data = new_child(pvtk_grid, "PPointData")
for a in attributes(vtk_point_data)
set_attribute(pvtk_ppoint_data, name(a), value(a))
end
for vtk_data_array in child_elements(vtk_point_data)
t = attribute(vtk_data_array, "type")
name = attribute(vtk_data_array, "Name")
Nc = attribute(vtk_data_array, "NumberOfComponents")
pvtk_pdata_array = new_child(pvtk_ppoint_data, "PDataArray")
set_attribute(pvtk_pdata_array, "type", t)
set_attribute(pvtk_pdata_array, "Name", name)
set_attribute(pvtk_pdata_array, "NumberOfComponents", Nc)
end
end
# Generate PCellData
vtk_cell_data = find_element(vtk_piece, "CellData")
if vtk_cell_data !== nothing
pvtk_pcell_data = new_child(pvtk_grid, "PCellData")
for a in attributes(vtk_cell_data)
set_attribute(pvtk_pcell_data, name(a), value(a))
end
for vtk_data_array in child_elements(vtk_cell_data)
t = attribute(vtk_data_array, "type")
name = attribute(vtk_data_array, "Name")
Nc = attribute(vtk_data_array, "NumberOfComponents")
pvtk_pdata_array = new_child(pvtk_pcell_data, "PDataArray")
set_attribute(pvtk_pdata_array, "type", t)
set_attribute(pvtk_pdata_array, "Name", name)
set_attribute(pvtk_pdata_array, "NumberOfComponents", Nc)
end
end
pvtk
end