Files
JuliaFEM.jl/src/io.jl
T

519 lines
16 KiB
Julia
Raw Normal View History

2016-08-01 01:15:41 +03:00
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
using HDF5
using LightXML
2018-07-05 11:21:10 +03:00
mutable struct Xdmf <: AbstractResultsWriter
2017-01-21 10:30:23 +02:00
name :: String
xml :: XMLElement
hdf :: HDF5File
2017-03-21 08:36:18 +02:00
hdf_counter :: Int
format :: String
2016-08-02 02:51:32 +03:00
end
2017-01-21 10:30:23 +02:00
function Xdmf()
return Xdmf(tempname())
2016-08-02 02:51:32 +03:00
end
2017-01-21 10:30:23 +02:00
function h5file(xdmf::Xdmf)
return xdmf.name*".h5"
2016-08-02 02:51:32 +03:00
end
2017-01-21 10:30:23 +02:00
function xmffile(xdmf::Xdmf)
return xdmf.name*".xmf"
2016-08-04 13:15:19 +03:00
end
2017-08-23 15:28:20 +03:00
"""
Xdmf(name, version="3.0", overwrite=false)
Initialize a new Xdmf object.
"""
2017-03-21 08:36:18 +02:00
function Xdmf(name::String; version="3.0", overwrite=false)
2017-01-21 10:30:23 +02:00
xdmf = new_element("Xdmf")
h5file = "$name.h5"
xmlfile = "$name.xmf"
2017-08-23 15:28:20 +03:00
2017-01-21 10:30:23 +02:00
if isfile(h5file)
if overwrite
2018-09-06 12:02:56 +03:00
@debug("Result file $h5file exists, removing old file.")
2017-01-21 10:30:23 +02:00
rm(h5file)
else
error("Result file $h5file exists, use Xdmf($name; overwrite=true) to rewrite results")
end
end
2017-08-23 15:28:20 +03:00
2017-01-21 10:30:23 +02:00
if isfile(xmlfile)
if overwrite
2018-09-06 12:02:56 +03:00
@debug("Result file $xmlfile exists, removing old file.")
2017-01-21 10:30:23 +02:00
rm(xmlfile)
else
error("Result file $xmlfile exists, use Xdmf($name; overwrite=true) to rewrite results")
end
end
set_attribute(xdmf, "xmlns:xi", "http://www.w3.org/2001/XInclude")
2017-03-21 08:36:18 +02:00
set_attribute(xdmf, "Version", version)
2017-01-21 10:30:23 +02:00
flag = isfile(h5file) ? "r+" : "w"
hdf = h5open(h5file, flag)
2017-03-21 08:36:18 +02:00
return Xdmf(name, xdmf, hdf, 1, "HDF")
2016-08-04 13:15:19 +03:00
end
2017-08-23 15:28:20 +03:00
"""
get_temporal_collection(xdmf)
Return the basic structure of Xdmf document.
Creates a new TemporalCollection if not found.
2017-01-21 10:30:23 +02:00
Basic structure for XML part of Xdmf file is
<?xml version="1.0" encoding="utf-8"?>
<Xdmf xmlns:xi="http://www.w3.org/2001/XInclude" Version="2.1">
<Domain>
<Grid CollectionType="Temporal" GridType="Collection">
</Grid>
</Domain>
</Xdmf>
"""
function get_temporal_collection(xdmf::Xdmf)
domain = find_element(xdmf.xml, "Domain")
grid = nothing
if domain == nothing
domain = new_child(xdmf.xml, "Domain")
grid = new_child(domain, "Grid")
set_attribute(grid, "CollectionType", "Temporal")
set_attribute(grid, "GridType", "Collection")
2016-08-04 13:15:19 +03:00
end
2017-01-21 10:30:23 +02:00
grid = find_element(domain, "Grid")
return grid
2016-08-04 13:15:19 +03:00
end
2017-08-23 15:28:20 +03:00
"""
xdmf_filter(child_elements, child_name)
Returns some spesific child xml element from an array of XMLElement based on,
2017-01-21 10:30:23 +02:00
"Xdmf extensions" see [1] for details.
Parameters
----------
child_elements :: Vector{XMLElement}
A vector of XMLElements where to perform filtering.
child_name :: String
Child element name, maybe containing Xdmf instructions
Returns
-------
nothing if nothing is found, otherwise XMLElement matching to filtering
2017-08-23 15:28:20 +03:00
#Examples
2017-01-21 10:30:23 +02:00
julia> grid1 = new_element("Grid")
julia> add_text(grid1, "I am first grid")
julia> grid2 = new_element("Grid")
julia> add_text(grid2, "I am second grid")
julia> set_attribute(grid2, "Name", "Frame 2")
julia> grid3 = new_element("Grid")
julia> add_text(grid3, "I am third grid")
julia> grids = [grid1, grid2, grid3]
2016-08-04 13:15:19 +03:00
2017-01-21 10:30:23 +02:00
To return second Grid element, one can use
julia> xdmf_filter(grids, "Grid[2]")
To return Grid which has attribute Name="Frame 2", use
julia> xdmf_filter(grids, "Grid[@name=Frame 2]")
To pick last Grid, use [end], e.g.
julia> xdmf_filter(grids, "Grid[end]").
References
----------
2016-08-04 13:15:19 +03:00
[1] http://www.xdmf.org/index.php/XDMF_Model_and_Format
"""
2017-01-21 10:30:23 +02:00
function xdmf_filter(child_elements, child_name)
if '/' in child_name # needs path traversal
return nothing
end
# filter children elements using syntax child[X] -> rename child_name
2016-08-02 02:51:32 +03:00
m = match(r"(\w+)\[(.+)\]", child_name)
if m != nothing
child_name = m[1]
end
2017-01-21 10:30:23 +02:00
# first find any relevant child elements (has same tag)
2016-08-02 02:51:32 +03:00
childs = []
2017-01-21 10:30:23 +02:00
for child in child_elements
if LightXML.name(child) == child_name
2016-08-02 02:51:32 +03:00
push!(childs, child)
end
end
2017-01-21 10:30:23 +02:00
# childs not found at all
2016-08-02 02:51:32 +03:00
length(childs) == 0 && return nothing
2017-01-21 10:30:23 +02:00
# by default return first
2016-08-02 02:51:32 +03:00
m == nothing && return first(childs)
2017-01-21 10:30:23 +02:00
# if [end] return last
2016-08-02 02:51:32 +03:00
m[2] == "end" && return childs[end]
2017-01-21 10:30:23 +02:00
# otherwise try parse int and return nth children from list
parsed_int = tryparse(Int, m[2])
if !isnull(parsed_int)
idx = get(parsed_int)
if (idx > 0) && (idx <= length(childs))
return childs[idx]
else
# wrong index
return nothing
end
end
# [X] is something else than integer, filter children elements using syntax child[@attr=value]
2016-08-02 02:51:32 +03:00
m2 = match(r"@(.+)=(.+)", m[2])
m2 == nothing && throw("Unable to parse: $(m[2])")
2017-01-05 11:29:29 +02:00
attr_name = convert(String, m2[1])
attr_value = convert(String, m2[2])
2016-08-02 02:51:32 +03:00
for child in childs
has_attribute(child, attr_name) || continue
2017-01-21 10:30:23 +02:00
if attribute(child, attr_name) == attr_value
2016-08-02 02:51:32 +03:00
return child
end
end
2017-01-21 10:30:23 +02:00
# nothing found
return nothing
2016-08-02 02:51:32 +03:00
end
2017-08-23 15:28:20 +03:00
"""
traverse(xdmf, x, attr_name)
Traverse XML path. Xdmf filtering can be used, so it's possible to find
data from xml using syntax e.g.
#Example
2017-01-21 10:30:23 +02:00
julia> traverse(xdmf, x, "/Domain/Grid[2]/Grid[@Name=Frame 1]/DataItem")
"""
function traverse(xdmf::Xdmf, x::XMLElement, attr_name::String)
2016-08-02 02:51:32 +03:00
attr_name = strip(attr_name, '/')
2017-01-21 10:30:23 +02:00
if has_attribute(x, attr_name)
return attribute(x, attr_name)
2016-08-02 02:51:32 +03:00
end
2017-01-21 10:30:23 +02:00
childs = child_elements(x)
2016-08-02 02:51:32 +03:00
2017-01-21 10:30:23 +02:00
if '/' in attr_name
items = split(attr_name, '/')
new_item = xdmf_filter(childs, first(items))
2017-03-21 08:36:18 +02:00
if new_item == nothing
2018-09-06 12:02:56 +03:00
@debug("traverse: childs:")
2017-03-21 08:36:18 +02:00
for child in childs
2018-09-06 12:02:56 +03:00
@debug(LightXML.name(child))
2017-03-21 08:36:18 +02:00
end
error("traverse: failed, items = $items, xdmf_filter not find child")
end
2017-01-21 10:30:23 +02:00
new_path = join(items[2:end], '/')
return traverse(xdmf, new_item, new_path)
end
2017-08-23 15:28:20 +03:00
2017-01-21 10:30:23 +02:00
child = xdmf_filter(childs, attr_name)
return child
2016-08-02 02:51:32 +03:00
end
2017-08-23 15:28:20 +03:00
"""
read(xdmf, path)
Read data from Xdmf file.
#Example
2017-01-21 10:30:23 +02:00
Traversing is supported, so one can easily traverse XML tree e.g.
julia> read(xdmf, "/Domain/Grid/Grid[2]/Geometry")
"""
2016-11-22 10:48:19 +02:00
function read(xdmf::Xdmf, path::String)
2017-01-21 10:30:23 +02:00
result = traverse(xdmf, xdmf.xml, path)
2016-08-02 02:51:32 +03:00
if endswith(path, "DataItem")
2017-01-21 10:30:23 +02:00
format = attribute(result, "Format"; required=true)
2017-03-21 08:36:18 +02:00
if format == "HDF"
h5file, path = map(String, split(content(result), ':'))
h5file = dirname(xdmf.name) * "/" * h5file
isfile(h5file) || throw("Xdmf: h5 file $h5file not found!")
return read(xdmf.hdf, path)
else
error("Read from Xdmf, reading from $format not implemented")
end
2016-08-02 02:51:32 +03:00
else
return result
end
end
2017-08-23 15:28:20 +03:00
"""
save!(xdmf)
2016-08-01 01:15:41 +03:00
2017-08-23 15:28:20 +03:00
Save the xdmf file.
"""
2016-08-02 02:51:32 +03:00
function save!(xdmf::Xdmf)
doc = XMLDocument()
set_root(doc, xdmf.xml)
save_file(doc, xmffile(xdmf))
2016-08-01 01:15:41 +03:00
end
2018-09-06 12:02:56 +03:00
function Base.close(xdmf::Xdmf)
close(xdmf.hdf)
end
2018-07-05 11:21:10 +03:00
function new_dataitem(xdmf::Xdmf, path::String, data::Array{T,N}) where {T,N}
2016-08-01 01:15:41 +03:00
dataitem = new_element("DataItem")
2018-09-06 12:02:56 +03:00
datatype = replace("$T", "64" => "")
2016-10-18 11:19:48 +03:00
dimensions = join(reverse(size(data)), " ")
2016-08-02 02:51:32 +03:00
set_attribute(dataitem, "DataType", datatype)
set_attribute(dataitem, "Dimensions", dimensions)
2017-03-21 08:36:18 +02:00
set_attribute(dataitem, "Format", xdmf.format)
if xdmf.format == "HDF"
2016-08-02 02:51:32 +03:00
hdf = basename(h5file(xdmf))
if exists(xdmf.hdf, path)
2018-09-06 12:02:56 +03:00
@debug("Xdmf: $path already existing in h5 file, not overwriting.")
else
2016-08-02 02:51:32 +03:00
write(xdmf.hdf, path, data)
end
2016-08-01 01:15:41 +03:00
add_text(dataitem, "$hdf:$path")
2017-03-21 08:36:18 +02:00
elseif xdmf.format == "XML"
text_data = string(data')
text_data = strip(text_data, ['[', ']'])
text_data = replace(text_data, ';', '\n')
text_data = "\n" * text_data * "\n"
add_text(dataitem, text_data)
else
error("Unsupported Xdmf big data format $(xdmf.format)")
2016-08-01 01:15:41 +03:00
end
return dataitem
end
2017-08-23 15:28:20 +03:00
"""
new_dataitem(xdmf, data)
Create a new DataItem element, hdf path automatically determined.
"""
2018-07-05 11:21:10 +03:00
function new_dataitem(xdmf::Xdmf, data::Array{T,N}) where {T,N}
2017-03-21 08:36:18 +02:00
if xdmf.format == "XML"
# Path can be whatever as XML format does not store to HDF at all
return new_dataitem(xdmf, "/whatever", data)
else
path = "/DataItem_$(xdmf.hdf_counter)"
while exists(xdmf.hdf, path)
xdmf.hdf_counter += 1
path = "/DataItem_$(xdmf.hdf_counter)"
end
return new_dataitem(xdmf, path, data)
end
end
global const xdmf_element_mapping = Dict(
"Poi1" => "Polyvertex",
"Seg2" => "Polyline",
"Tri3" => "Triangle",
"Quad4" => "Quadrilateral",
"Tet4" => "Tetrahedron",
"Pyramid5" => "Pyramid",
"Wedge6" => "Wedge",
"Hex8" => "Hexahedron",
"Seg3" => "Edge_3",
"Tri6" => "Tri_6",
"Quad8" => "Quad_8",
"Tet10" => "Tet_10",
"Pyramid13" => "Pyramid_13",
"Wedge15" => "Wedge_15",
"Hex20" => "Hex_20")
2018-09-20 19:59:45 +03:00
get_xdmf_element_code(::Element{Poi1}) = 1
get_xdmf_element_code(::Element{Seg2}) = 2
# get_xdmf_element_code(::Element{Polygon}) = 3
get_xdmf_element_code(::Element{Tri3}) = 4
get_xdmf_element_code(::Element{Quad4}) = 5
get_xdmf_element_code(::Element{Tet4}) = 6
get_xdmf_element_code(::Element{Pyr5}) = 7
get_xdmf_element_code(::Element{Wedge6}) = 8
get_xdmf_element_code(::Element{Hex8}) = 9
# get_xdmf_element_code(::Element{Polyhedron}) = 16
get_xdmf_element_code(::Element{Seg3}) = 34
get_xdmf_element_code(::Element{Quad9}) = 35
get_xdmf_element_code(::Element{Tri6}) = 36
get_xdmf_element_code(::Element{Quad8}) = 37
get_xdmf_element_code(::Element{Tet10}) = 38
# get_xdmf_element_code(::Element{Pyr13}) = 39
get_xdmf_element_code(::Element{Wedge15}) = 40
# get_xdmf_element_code(::Element{Wedge18}) = 41
get_xdmf_element_code(::Element{Hex20}) = 48
# get_xdmf_element_code(::Element{Hex24}) = 49
get_xdmf_element_code(::Element{Hex27}) = 50
2018-09-06 12:02:56 +03:00
"""
get_spatial_collection()
Return a SpatialCollection at given time either by creating new one or returning
existing one.
"""
function get_spatial_collection(temporal_collection, time)
for spatial_collection in get_elements_by_tagname(temporal_collection, "Grid")
time_element = find_element(spatial_collection, "Time")
time_value = Meta.parse(attribute(time_element, "Value"; required=true))
isapprox(time_value, time) && return spatial_collection
end
# did not find, create new one
spatial_collection = new_child(temporal_collection, "Grid")
set_attribute(spatial_collection, "GridType", "Collection")
set_attribute(spatial_collection, "Name", "Problems")
set_attribute(spatial_collection, "CollectionType", "Spatial")
time_element = new_child(spatial_collection, "Time")
set_attribute(time_element, "Value", time)
return spatial_collection
end
2017-08-23 15:28:20 +03:00
"""
update_xdmf!(xdmf, problem, time, fields)
Write new fields to Xdmf file.
2017-03-21 08:36:18 +02:00
2017-08-23 15:28:20 +03:00
#Example
2017-03-21 08:36:18 +02:00
To write displacement and temperature fields from p1 at time t=0.0:
julia> update_xdmf!(p1, 0.0, ["displacement", "temperature"])
"""
function update_xdmf!(xdmf::Xdmf, problem::Problem, time::Float64, fields::Vector)
2018-09-06 12:02:56 +03:00
@debug("Xdmf: storing fields $fields of problem $(problem.name) at time $time")
2017-03-21 08:36:18 +02:00
# 1. find domain
xml = xdmf.xml
domain = find_element(xml, "Domain")
if domain == nothing
2018-09-06 12:02:56 +03:00
@debug("Xdmf: Domain not found, creating.")
2017-03-21 08:36:18 +02:00
domain = new_child(xml, "Domain")
end
# 2. find for TemporalCollection
temporal_collection = find_element(domain, "Grid")
if temporal_collection == nothing
2018-09-06 12:02:56 +03:00
@debug("Xdmf: Temporal collection not found, creating.")
2017-03-21 08:36:18 +02:00
temporal_collection = new_child(domain, "Grid")
set_attribute(temporal_collection, "GridType", "Collection")
set_attribute(temporal_collection, "Name", "Time")
set_attribute(temporal_collection, "CollectionType", "Temporal")
end
# 2.1 make sure that Grid element we found really is TemporalCollection
collection_type = attribute(temporal_collection, "CollectionType"; required=true)
@assert collection_type == "Temporal"
2018-09-06 12:02:56 +03:00
spatial_collection = get_spatial_collection(temporal_collection, time)
2017-03-21 08:36:18 +02:00
for frame in get_elements_by_tagname(spatial_collection, "Grid")
frame_name = attribute(frame, "Name")
if frame_name == problem.name
2018-09-06 12:02:56 +03:00
@warn("Xdmf: Already found Grid with name $frame_name for time $time, skipping.")
2017-03-21 08:36:18 +02:00
return
end
end
frame_name = problem.name
2018-09-06 12:02:56 +03:00
@debug("Xdmf: Creating Grid for problem $frame_name")
2017-03-21 08:36:18 +02:00
frame = new_child(spatial_collection, "Grid")
set_attribute(frame, "Name", frame_name)
# 4. save geometry
X_dict = problem("geometry", time)
node_ids = sort(collect(keys(X_dict)))
node_mapping = Dict(j => i for (i, j) in enumerate(node_ids))
X_array = hcat([X_dict[nid] for nid in node_ids]...)
ndim, nnodes = size(X_array)
geom_type = (ndim == 2 ? "XY" : "XYZ")
2018-09-06 12:02:56 +03:00
@debug("Xdmf: Creating geometry, type = $geom_type, number of nodes = $nnodes")
2017-03-21 08:36:18 +02:00
X_dataitem = new_dataitem(xdmf, X_array)
geometry = new_child(frame, "Geometry")
set_attribute(geometry, "Type", geom_type)
add_child(geometry, X_dataitem)
# 5. save topology
2018-09-20 19:59:45 +03:00
mesh_type = "unstructured"
if mesh_type == "unstructured"
element_conn = Int64[]
for element in get_elements(problem)
xdmf_element_code = get_xdmf_element_code(element)
xdmf_element_code > 0 || continue
push!(element_conn, xdmf_element_code)
if xdmf_element_code == 2
push!(element_conn, length(element))
end
for j in get_connectivity(element)
push!(element_conn, node_mapping[j]-1)
end
end
2017-03-21 08:36:18 +02:00
topology_dataitem = new_dataitem(xdmf, element_conn)
topology = new_child(frame, "Topology")
2018-09-20 19:59:45 +03:00
set_attribute(topology, "TopologyType", "Mixed")
2017-03-21 08:36:18 +02:00
add_child(topology, topology_dataitem)
2018-09-20 19:59:45 +03:00
else
all_elements = get_elements(problem)
nelements = length(all_elements)
element_types = unique(map(get_element_type, all_elements))
nelement_types = length(element_types)
@debug("Xdmf: Saving topology of $nelements elements total, $nelement_types different element types.")
if nelement_types != 1
error("Xdmf: only single type of element supported by structured grid type!")
end
for element_type in element_types
elements = collect(filter_by_element_type(element_type, all_elements))
nelements = length(elements)
@debug("Xdmf: $nelements elements of type $element_type")
sort!(elements, by=get_element_id)
element_ids = map(get_element_id, elements)
element_conn = map(element -> [node_mapping[j]-1 for j in get_connectivity(element)], elements)
element_conn = hcat(element_conn...)
element_code = split(string(element_type), ".")[end]
topology_dataitem = new_dataitem(xdmf, element_conn)
topology = new_child(frame, "Topology")
set_attribute(topology, "TopologyType", xdmf_element_mapping[element_code])
set_attribute(topology, "NumberOfElements", length(elements))
add_child(topology, topology_dataitem)
end
2017-03-21 08:36:18 +02:00
end
# 6. save requested fields
for field_name in fields
field_dict = problem(field_name, time)
field_center = "Node"
field_node_ids = sort(collect(keys(field_dict)))
2018-09-20 19:59:45 +03:00
if node_ids != field_node_ids
@error("geom node ids = $node_ids")
@error("field node ids = $field_node_ids")
error("!=, geometry does not match with field.")
end
2017-03-21 08:36:18 +02:00
field_dim = length(field_dict[first(field_node_ids)])
if field_dim == 2
2018-09-06 12:02:56 +03:00
@debug("Xdmf: Field dimension = 2, extending to 3")
2017-03-21 08:36:18 +02:00
for nid in field_node_ids
field_dict[nid] = [field_dict[nid]; 0.0]
end
field_dim = 3
2017-03-21 08:36:18 +02:00
end
field_type = Dict(1 => "Scalar", 3 => "Vector", 6 => "Tensor6")[field_dim]
2018-09-06 12:02:56 +03:00
@debug("Xdmf: Saving field $field_name, type = $field_type, dimension = $field_dim, center = $field_center")
2017-03-21 08:36:18 +02:00
field_array = hcat([field_dict[nid] for nid in field_node_ids]...)
field_dataitem = new_dataitem(xdmf, field_array)
attribute = new_child(frame, "Attribute")
2018-09-06 12:02:56 +03:00
set_attribute(attribute, "Name", uppercasefirst(field_name))
2017-03-21 08:36:18 +02:00
set_attribute(attribute, "Center", field_center)
set_attribute(attribute, "AttributeType", field_type)
add_child(attribute, field_dataitem)
end
save!(xdmf)
2018-09-06 12:02:56 +03:00
@debug("Xdmf: all done.")
2017-03-21 08:36:18 +02:00
end