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