diff --git a/src/JuliaFEM.jl b/src/JuliaFEM.jl index c8d0f36..416f2a3 100644 --- a/src/JuliaFEM.jl +++ b/src/JuliaFEM.jl @@ -21,7 +21,7 @@ module Testing end include("io.jl") -export ModelIO +export Xdmf, h5file, xmffile, has_child, get_child, new_dataitem include("fields.jl") export Field, DCTI, DVTI, DCTV, DVTV, CCTI, CVTI, CCTV, CVTV, Increment diff --git a/src/io.jl b/src/io.jl index 977366f..9a07c0c 100644 --- a/src/io.jl +++ b/src/io.jl @@ -3,51 +3,168 @@ using HDF5 using LightXML +importall LightXML +importall Base -type ModelIO +function new_element(name::AbstractString, attrs::Dict) + x = new_element(name) + for (k, v) in attrs + x[k] = v + end + return x +end + +function setindex!(x::XMLElement, content::Any, attr_name::AbstractString) + set_attribute(x, attr_name, content) +end + +function haskey(x::XMLElement, key::AbstractString) + return has_child(x, key) || has_attribute(x, key) +end + +function get_child(x::XMLElement, child_name::AbstractString) + '/' in child_name && return nothing + m = match(r"(\w+)\[(.+)\]", child_name) + if m != nothing + child_name = m[1] + end + childs = [] + for child in child_elements(x) + if name(child) == child_name + push!(childs, child) + end + end + length(childs) == 0 && return nothing + m == nothing && return first(childs) + j = tryparse(Int, m[2]) + isnull(j) || return childs[get(j)] + m[2] == "end" && return childs[end] + m2 = match(r"@(.+)=(.+)", m[2]) + m2 == nothing && throw("Unable to parse: $(m[2])") + attr_name = m2[1] + attr_value = m2[2] + for child in childs + has_attribute(child, attr_name) || continue + if get_attribute(child, attr_name) == attr_value + return child + end + end + throw("Unable to parse: $(m[2])") +end + +function has_child(x::XMLElement, child_name::AbstractString) + return get_child(x, child_name) != nothing +end + +function get_attribute(x::XMLElement, attr_name::AbstractString) + attr = attribute(x, attr_name) + numeric = tryparse(Int64, attr) + isnull(numeric) && (numeric = tryparse(Float64, attr)) + isnull(numeric) && return attr + return get(numeric) +end + +function getindex(x::XMLElement, attr_name::AbstractString) + attr_name = strip(attr_name, '/') + child = get_child(x, attr_name) + child == nothing || return child + has_attribute(x, attr_name) && return get_attribute(x, attr_name) + if '/' in attr_name + items = split(attr_name, '/') + attr_name = first(items) + length(items) > 1 || throw(KeyError(attr_name)) + haskey(x, attr_name) || throw(KeyError(attr_name)) + path = join(items[2:end], '/') + new_item = getindex(x, attr_name) + return new_item[path] + else + throw(KeyError(attr_name)) + end +end + +function new_child(xparent::XMLElement, name::AbstractString, attrs::Dict) + x = new_child(xparent, name) + for (k, v) in attrs + x[k] = v + end + return x +end + +function new_child(xparent::XMLElement, name::AbstractString, attrs::Pair...) + x = new_child(xparent, name) + for (k, v) in attrs + x[k] = v + end + return x +end + +type Xdmf name :: AbstractString - xdmf :: XMLElement + xml :: XMLElement + hdf :: HDF5File end -function ModelIO() - return ModelIO(tempname()) +function new_child(xdmf::Xdmf, args...; kwargs...) + new_child(xdmf.xml, args...; kwargs...) end -function ModelIO(name::AbstractString) +function read(xdmf::Xdmf, path::AbstractString) + result = getindex(xdmf.xml, path) + if endswith(path, "DataItem") + format = get_attribute(result, "Format") + @assert format == "HDF" + h5file, path = map(ASCIIString, split(content(result), ':')) + h5file = dirname(xdmf.name) * "/" * h5file + isfile(h5file) || throw("Xdmf: h5 file $h5file not found!") + return read(xdmf.hdf, path) + else + return result + end +end + +function Xdmf() + return Xdmf(tempname()) +end + +function h5file(xdmf::Xdmf) + return xdmf.name*".h5" +end + +function xmffile(xdmf::Xdmf) + return xdmf.name*".xmf" +end + +function Xdmf(name::AbstractString) xdmf = new_element("Xdmf") set_attribute(xdmf, "xmlns:xi", "http://www.w3.org/2001/XInclude") set_attribute(xdmf, "Version", "2.1") - return ModelIO(name, xdmf) + h5file = "$name.h5" + flag = isfile(h5file) ? "r+" : "w" + hdf = h5open(h5file, flag) + return Xdmf(name, xdmf, hdf) end -function h5file(mio::ModelIO) - return mio.name*".h5" +function save!(xdmf::Xdmf) + doc = XMLDocument() + set_root(doc, xdmf.xml) + save_file(doc, xmffile(xdmf)) end -function put!{T,N}(mio::ModelIO, path::AbstractString, data::Array{T,N}) - hdf = h5file(mio) - h5write(hdf, path, data) -end - -function get_dataitem{T,N}(mio::ModelIO, path::AbstractString, data::Array{T,N}; format="HDF") +function new_dataitem{T,N}(xdmf::Xdmf, path::AbstractString, data::Array{T,N}; format="HDF") dataitem = new_element("DataItem") - n, m = size(data) - set_attribute(dataitem, "DataType", "$T") - set_attribute(dataitem, "Dimensions", "$n $m") + datatype = replace("$T", "64", "") + dimensions = join(size(data), " ") + set_attribute(dataitem, "DataType", datatype) + set_attribute(dataitem, "Dimensions", dimensions) set_attribute(dataitem, "Format", format) if format == "HDF" - hdf = basename(h5file(mio)) + hdf = basename(h5file(xdmf)) + if !exists(xdmf.hdf, path) + write(xdmf.hdf, path, data) + end add_text(dataitem, "$hdf:$path") + elseif format == "XML" + add_text(dataitem, strip(string(data), ['[', ']'])) end return dataitem end - -function get(mio::ModelIO, path::AbstractString) - h5read(mio.name*".h5", path) -end - -function save!(mio::ModelIO) - doc = XMLDocument() - set_root(doc, mio.xdmf) - save_file(doc, mio.name*".xmf") -end diff --git a/src/postprocess_utils.jl b/src/postprocess_utils.jl index 70d51b3..a861c62 100644 --- a/src/postprocess_utils.jl +++ b/src/postprocess_utils.jl @@ -246,9 +246,12 @@ end function call(solver::Solver, ::Type{DataFrame}, field_name::AbstractString, abbreviation::Symbol, time::Float64=0.0) - u = Dict() - for problem in get_problems(solver) - u = merge(u, problem(field_name, time)) + fields = [problem(field_name, time) for problem in get_problems(solver)] + fields = filter(f -> f != nothing, fields) + if length(fields) != 0 + u = merge(fields...) + else + u = Dict() end return to_dataframe(u, abbreviation) end diff --git a/src/postprocess_xdmf.jl b/src/postprocess_xdmf.jl index c397524..3ad67ab 100644 --- a/src/postprocess_xdmf.jl +++ b/src/postprocess_xdmf.jl @@ -192,4 +192,3 @@ end function xdmf_save!(xdmf, filename) save_file(xdmf.xdoc, filename) end - diff --git a/src/solvers.jl b/src/solvers.jl index 5fd06d7..ba4c4dc 100644 --- a/src/solvers.jl +++ b/src/solvers.jl @@ -9,7 +9,7 @@ type Solver{S<:AbstractSolver} problems :: Vector{Problem} norms :: Vector{Tuple} # solution norms for convergence studies ndofs :: Int # number of degrees of freedom in problem - io :: Nullable{ModelIO} # input/output handle + xdmf :: Nullable{Xdmf} # input/output handle properties :: S end @@ -457,15 +457,17 @@ function call(solver::Solver, field_name::AbstractString, time::Float64) return merge(fields...) end -function get_temporal_collection(mio::ModelIO) - grid = find_element(mio.xdmf, "Grid") - if grid == nothing +function get_temporal_collection(xdmf::Xdmf) + domain = find_element(xdmf.xml, "Domain") + grid = nothing + if domain == nothing info("Xdmf: creating new temporal collection") - domain = new_child(mio.xdmf, "Domain") + domain = new_child(xdmf.xml, "Domain") grid = new_child(domain, "Grid") set_attribute(grid, "CollectionType", "Temporal") set_attribute(grid, "GridType", "Collection") end + grid = find_element(domain, "Grid") return grid end @@ -473,6 +475,7 @@ end function update!(solver::Solver, u::Vector, la::Vector; show_info=true) show_info && info("Updating problems ...") t0 = Base.time() + for problem in solver.problems assembly = get_assembly(problem) elements = get_elements(problem) @@ -483,27 +486,21 @@ function update!(solver::Solver, u::Vector, la::Vector; show_info=true) end # if io is attached to solver, update hdf / xml also - if !isnull(solver.io) - io = get(solver.io) - xdmf = io.xdmf - temporal_collection = get_temporal_collection(io) + if !isnull(solver.xdmf) + xdmf = get(solver.xdmf) + temporal_collection = get_temporal_collection(xdmf) frame = new_child(temporal_collection, "Grid") - time_item = new_child(frame, "Time") - set_attribute(time_item, "Value", solver.time) + new_child(frame, "Time", Dict("Value" => solver.time)) # save geometry X = solver("geometry", solver.time) node_ids = sort(collect(keys(X))) geometry = hcat([X[nid] for nid in node_ids]...) - put!(io, "/Node IDs", node_ids) - path = "/Geometry" - put!(io, path, geometry) - dataitem = get_dataitem(io, path, geometry) - ndim, nnodes = size(geometry) geom_type = ndim == 2 ? "XY" : "XYZ" - geom = new_child(frame, "Geometry") - set_attribute(geom, "Type", geom_type) + dataitem = new_dataitem(xdmf, "/Node IDs", node_ids) + geom = new_child(frame, "Geometry", Dict("Type" => geom_type)) + dataitem = new_dataitem(xdmf, "/Geometry", geometry) add_child(geom, dataitem) # save topology @@ -511,10 +508,21 @@ function update!(solver::Solver, u::Vector, la::Vector; show_info=true) nelements = length(all_elements) element_types = unique(map(get_element_type, all_elements)) - element_mapping = Dict( - "Quad4" => "Quadrilateral", + xdmf_element_mapping = Dict( "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") for element_type in element_types elements = filter_by_element_type(element_type, all_elements) @@ -523,12 +531,10 @@ function update!(solver::Solver, u::Vector, la::Vector; show_info=true) element_conn = map(get_connectivity, elements) element_conn = transpose(hcat(element_conn...)) - 1 element_code = split(string(element_type), ".")[end] - put!(io, "/Topology/$element_code/Element IDs", element_ids) - path = "/Topology/$element_code/Connectivity" - put!(io, path, element_conn) - dataitem = get_dataitem(io, path, element_conn) + dataitem = new_dataitem(xdmf, "/Topology/$element_code/Element IDs", element_ids) + dataitem = new_dataitem(xdmf, "/Topology/$element_code/Connectivity", element_conn) topology = new_child(frame, "Topology") - set_attribute(topology, "TopologyType", element_mapping[element_code]) + set_attribute(topology, "TopologyType", xdmf_element_mapping[element_code]) set_attribute(topology, "NumberOfElements", length(elements)) add_child(topology, dataitem) end @@ -551,14 +557,12 @@ function update!(solver::Solver, u::Vector, la::Vector; show_info=true) end U = hcat([U[nid] for nid in node_ids]...) unknown_field_name = ucfirst(unknown_field_name) - path = "/Results/$time/Nodal Fields/$unknown_field_name" - put!(io, path, U) - dataitem = get_dataitem(io, path, U) + dataitem = new_dataitem(xdmf, "/Results/$time/Nodal Fields/$unknown_field_name", U) attribute = new_child(frame, "Attribute") set_attribute(attribute, "Name", unknown_field_name) set_attribute(attribute, "Center", field_center) add_child(attribute, dataitem) - save!(io) + save!(xdmf) end t1 = round(Base.time()-t0, 2) diff --git a/test/test_io.jl b/test/test_io.jl index b3ce12e..077519d 100644 --- a/test/test_io.jl +++ b/test/test_io.jl @@ -3,18 +3,56 @@ using JuliaFEM using JuliaFEM.Testing -importall Base +using LightXML -@testset "create new result" begin - r = ModelIO() - expected = """""" - @test string(r.xdmf) == expected +@testset "create new Xdmf object" begin + r = Xdmf() + expected = "" + @test string(r.xml) == expected end -@testset "put and get result" begin - r = ModelIO() - put!(r, "/1/2/3", [1 2 3]) - @test isapprox(get(r, "/1/2/3"), [1 2 3]) +@testset "put and get to Xdmf, low level" begin + io = Xdmf() + # h5 + write(io.hdf, "/Xdmf/Domain/Geometry", [1 2 3]) + @test isapprox(read(io.hdf, "/Xdmf/Domain/Geometry"), [1 2 3]) + # xml + obj = new_child(io.xml, "Domain") + set_attribute(obj, "Name", "Test Domain") + obj2 = find_element(io.xml, "Domain") + @test attribute(obj2, "Name") == "Test Domain" +end + +@testset "put and get to xdmf" begin + xdmf = Xdmf() + domain = new_child(xdmf, "Domain") + grid = new_child(domain, "Grid") + grid["CollectionType"] = "Temporal" + grid["GridType"] = "Collection" + + frame1 = new_child(grid, "Grid") + new_child(frame1, "Time", "Value" => 0.0) + X1 = new_child(frame1, "Geometry", Dict("Type" => "XY")) + + frame2 = new_child(grid, "Grid", "Name" => "Frame 2") + new_child(frame2, "Time", "Value" => 1.0) + X2 = new_child(frame2, "Geometry", "Type" => "XY") + + add_child(grid, frame1) + add_child(grid, frame2) + + dataitem = new_dataitem(xdmf, "/Domain/Grid/Grid/2/Geometry", [1.0, 2.0]) + add_child(X2, dataitem) + + println(xdmf.xml) + @test has_child(xdmf.xml, "Domain") + @test isa(get_child(xdmf.xml, "Domain"), XMLElement) + @test !has_attribute(xdmf.xml, "Domain") + @test isapprox(read(xdmf, "/Domain/Grid/Grid/Time/Value"), 0.0) + @test isapprox(read(xdmf, "/Domain/Grid/Grid[2]/Time/Value"), 1.0) + @test isapprox(read(xdmf, "/Domain/Grid/Grid[end]/Time/Value"), 1.0) + @test isapprox(read(xdmf, "/Domain/Grid/Grid[@Name=Frame 2]/Time/Value"), 1.0) + @test isapprox(read(xdmf, "/Domain/Grid/Grid[2]/Geometry/DataItem"), [1.0, 2.0]) end @testset "save results to disk" begin @@ -26,7 +64,8 @@ end element = Element(Quad4, [1, 2, 3, 4]) update!(element, "geometry", X) update!(element, "temperature thermal conductivity", 6.0) - update!(element, "temperature load", 12.0) + update!(element, "temperature load", 0.0 => 12.0) + update!(element, "temperature load", 1.0 => 18.0) problem = Problem(Heat, "one element heat problem", 1) problem.properties.formulation = "2D" push!(problem, element) @@ -36,18 +75,43 @@ end bc = Problem(Dirichlet, "fixed", 1, "temperature") push!(bc, boundary_element) solver = Solver(Linear, problem, bc) - solver.io = ModelIO() + solver.xdmf = Xdmf() + + solver.time = 0.0 solver() - io = get(solver.io) - info("h5 file = $(io.name).h5") - E = get(io, "/Topology/Quad4/Element IDs") - C = get(io, "/Topology/Quad4/Connectivity") - N = get(io, "/Node IDs") - X = get(io, "/Geometry") - T = get(io, "/Results/Time 0.0/Nodal Fields/Temperature") + empty!(problem.assembly) + solver.time = 1.0 + solver() + + info(solver("temperature", 0.0)) + info(solver("temperature", 1.0)) + info(element("temperature load", [0.0, 0.0], 0.0)) + info(element("temperature load", [0.0, 0.0], 1.0)) + + xdmf = get(solver.xdmf) + info("h5 file = $(h5file(xdmf))") + E = read(xdmf.hdf, "/Topology/Quad4/Element IDs") + C = read(xdmf.hdf, "/Topology/Quad4/Connectivity") + N = read(xdmf.hdf, "/Node IDs") + X = read(xdmf.hdf, "/Geometry") + T1 = read(xdmf.hdf, "/Results/Time 0.0/Nodal Fields/Temperature") + T2 = read(xdmf.hdf, "/Results/Time 1.0/Nodal Fields/Temperature") @test isapprox(E, [-1]) @test isapprox(C, [0 1 2 3]) @test isapprox(N, [1, 2, 3, 4]) - @test isapprox(X, [0.0 0.0; 1.0 0.0; 1.0 1.0; 0.0 1.0]') - @test isapprox(T, [0.0 0.0 1.0 1.0]) + X_expected = [0.0 0.0; 1.0 0.0; 1.0 1.0; 0.0 1.0]' + T1_expected = [0.0 0.0 1.0 1.0] + T2_expected = [0.0 0.0 0.5 0.5] + @test isapprox(X, X_expected) + @test isapprox(T1, T1_expected) + @test isapprox(T2, T2_expected) + @test isapprox(read(xdmf, "/Domain/Grid/Grid/Time/Value"), 0.0) + @test read(xdmf, "/Domain/Grid/Grid/Geometry/Type") == "XY" + @test isapprox(read(xdmf, "/Domain/Grid/Grid/Geometry/DataItem"), X_expected) + @test isapprox(read(xdmf, "/Domain/Grid/Grid/Topology/DataItem"), [0 1 2 3]) + @test isapprox(read(xdmf, "/Domain/Grid/Grid/Topology[@TopologyType=Polyline]/DataItem"), [0 1]) + @test isapprox(read(xdmf, "/Domain/Grid/Grid[1]/Attribute[@Name=Temperature]/DataItem"), T1_expected) + @test isapprox(read(xdmf, "/Domain/Grid/Grid[2]/Attribute[@Name=Temperature]/DataItem"), T2_expected) + @test isapprox(read(xdmf, "/Domain/Grid/Grid[end]/Time/Value"), 1.0) + @test isapprox(read(xdmf, "/Domain/Grid/Grid[end]/Topology/DataItem"), [0 1 2 3]) end