mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-09-17 17:22:10 +00:00
439 lines
13 KiB
Julia
439 lines
13 KiB
Julia
# This file is a part of JuliaFEM.
|
|
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
|
|
|
|
importall Base
|
|
|
|
using JuliaFEM
|
|
using DataFrames
|
|
using HDF5
|
|
using LightXML
|
|
using Formatting
|
|
#using StringUtils # not in metadata
|
|
|
|
import HDF5: h5read, h5write
|
|
|
|
function h5read{T<:DataFrame}(::Type{T}, filename, name::ByteString)
|
|
raw_data = h5read(filename, name)
|
|
index = raw_data["index"]
|
|
column_names = raw_data["column_names"]
|
|
column_names = map(parse, column_names)
|
|
n, m = size(raw_data["data"])
|
|
data = Any[index]
|
|
for i=1:m
|
|
push!(data, raw_data["data"][:,i])
|
|
end
|
|
return DataFrame(data, column_names)
|
|
end
|
|
|
|
function h5write(filename, name::ByteString, data::DataFrame)
|
|
column_names = DataFrames._names(data)
|
|
column_names = map(string, column_names)
|
|
index = convert(Vector, data[:,1])
|
|
data = convert(Matrix, data[:,2:end])
|
|
h5write(filename, "$name/column_names", column_names)
|
|
h5write(filename, "$name/index", index)
|
|
h5write(filename, "$name/data", data)
|
|
end
|
|
|
|
function convert(::Type{AbstractString}, df::DataFrame)
|
|
fn = tempname()
|
|
writetable(fn, df)
|
|
return readall(fn)
|
|
end
|
|
|
|
function convert(::Type{DataFrame}, dfs::AbstractString)
|
|
fn = tempname()
|
|
fid = open(fn, "w")
|
|
write(fid, dfs)
|
|
close(fid)
|
|
return readtable(fn)
|
|
end
|
|
|
|
function extract(df::DataFrame, args...; kwargs...)
|
|
result = copy(df)
|
|
for (k,v) in kwargs
|
|
rows = find(df[k] .== v)
|
|
result = result[rows, :]
|
|
end
|
|
foo = Symbol[si for si in args]
|
|
return result[foo]
|
|
end
|
|
|
|
function vec(df::DataFrame)
|
|
return vec(convert(Matrix{Float64}, df))
|
|
end
|
|
|
|
function isapprox(d1::DataFrame, d2::Vector)
|
|
return isapprox(vec(d1), d2)
|
|
end
|
|
|
|
""" A more appropriate representation for floats in results. """
|
|
function DataFrames.ourshowcompact(io::IO, x::Float64)
|
|
#print(io, u"\% 0.4E(x)")
|
|
#return
|
|
print(io, sprintf1("% 0.4E", x))
|
|
end
|
|
|
|
"""
|
|
Calculate field values to nodal points from Gauss points using least-squares fitting.
|
|
"""
|
|
function calc_nodal_values!(elements::Vector, field_name, field_dim, time;
|
|
F=nothing, nz=nothing, b=nothing, return_F_and_nz=false)
|
|
|
|
if F == nothing
|
|
A = SparseMatrixCOO()
|
|
for element in elements
|
|
gdofs = get_connectivity(element)
|
|
for ip in get_integration_points(element)
|
|
detJ = element(ip, time, Val{:detJ})
|
|
w = ip.weight*detJ
|
|
N = element(ip, time)
|
|
add!(A, gdofs, gdofs, w*kron(N', N))
|
|
end
|
|
end
|
|
nz = get_nonzero_rows(A)
|
|
A = sparse(A)
|
|
A = 1/2*(A + A')
|
|
F = ldltfact(A[nz,nz])
|
|
end
|
|
|
|
if b == nothing
|
|
b = SparseMatrixCOO()
|
|
for element in elements
|
|
gdofs = get_connectivity(element)
|
|
for ip in get_integration_points(element)
|
|
if !haskey(ip, field_name)
|
|
info("warning: integration point does not have field $field_name")
|
|
continue
|
|
end
|
|
detJ = element(ip, time, Val{:detJ})
|
|
w = ip.weight*detJ
|
|
f = ip(field_name, time)
|
|
N = element(ip, time)
|
|
for dim=1:field_dim
|
|
add!(b, gdofs, w*f[dim]*N, dim)
|
|
end
|
|
end
|
|
end
|
|
b = sparse(b)
|
|
end
|
|
|
|
x = zeros(size(b)...)
|
|
x[nz, :] = F \ b[nz, :]
|
|
nodal_values = Dict()
|
|
for i=1:size(x,1)
|
|
nodal_values[i] = vec(x[i,:])
|
|
end
|
|
update!(elements, field_name, time => nodal_values)
|
|
if return_F_and_nz
|
|
return F, nz
|
|
end
|
|
end
|
|
|
|
function calc_nodal_values!(problem::Problem, field_name::AbstractString, field_dim::Int, time::Float64)
|
|
# after all, it's just a mass matrix ...
|
|
# isempty(problem.assembly.M) && assemble!(problem, time, Val{:mass_matrix}; density=1.0, dual_basis=false, dim=1)
|
|
# M = sparse(problem.assembly.M)
|
|
# TODO: make test before implementation
|
|
calc_nodal_values!(problem.elements, field_name, field_dim, time)
|
|
end
|
|
|
|
"""
|
|
Return node ids + vector of values
|
|
"""
|
|
function get_nodal_vector(elements::Vector, field_name::AbstractString, time::Float64)
|
|
f = Dict()
|
|
for element in elements
|
|
for (c, v) in zip(get_connectivity(element), element[field_name](time))
|
|
if haskey(f, c)
|
|
@assert isapprox(f[c], v)
|
|
end
|
|
f[c] = v
|
|
end
|
|
end
|
|
node_ids = sort(collect(keys(f)))
|
|
field = [f[nid] for nid in node_ids]
|
|
return node_ids, field
|
|
end
|
|
|
|
""" Return nodal values in Dict format. """
|
|
function get_nodal_dict(T::DataType, elements, field_name, time)
|
|
f = T()
|
|
for element in elements
|
|
for (c, v) in zip(get_connectivity(element), element(field_name, time))
|
|
if haskey(f, c)
|
|
@assert isapprox(f[c], v)
|
|
end
|
|
f[c] = v
|
|
end
|
|
end
|
|
return f
|
|
end
|
|
|
|
""" Update nodal field values from set of elements to another. Can be used to
|
|
transform e.g. reaction force from boundary element set to surface of
|
|
volume elements for easier postprocess.
|
|
"""
|
|
function copy_field!(src_elements::Vector, dst_elements::Vector, field_name, time)
|
|
dst_nodes = Set{Int64}()
|
|
for element in dst_elements
|
|
push!(dst_nodes, get_connectivity(element)...)
|
|
end
|
|
node_ids, field = get_nodal_vector(src_elements, field_name, time)
|
|
z = 0.0*first(field)
|
|
d = Dict()
|
|
for j in dst_nodes
|
|
d[j] = z
|
|
end
|
|
for (j, f) in zip(node_ids, field)
|
|
d[j] = f
|
|
end
|
|
for element in dst_elements
|
|
c = get_connectivity(element)
|
|
f = [d[j] for j in c]
|
|
update!(element, field_name, time => f)
|
|
end
|
|
end
|
|
|
|
function copy_field!(src_problem::Problem, dst_problem::Problem, field_name, time)
|
|
copy_field!(src_problem.elements, dst_problem.elements, field_name, time)
|
|
end
|
|
|
|
""" Return field calculated to nodal points for elements in problem p. """
|
|
function call(problem::Problem, field_name::AbstractString, time::Float64=0.0)
|
|
f = nothing
|
|
for element in get_elements(problem)
|
|
haskey(element, field_name) || continue
|
|
for (c, v) in zip(get_connectivity(element), element(field_name, time))
|
|
if f == nothing
|
|
f = Dict(c => v)
|
|
continue
|
|
end
|
|
if haskey(f, c)
|
|
if !isapprox(f[c], v)
|
|
info("several values for single node when returning field $field_name")
|
|
info("already have: $(f[c]), and trying to set $v")
|
|
end
|
|
else
|
|
f[c] = v
|
|
end
|
|
end
|
|
end
|
|
return f
|
|
end
|
|
|
|
function to_dataframe(u::Dict, abbreviation::Symbol)
|
|
length(u) != 0 || return DataFrame()
|
|
node_ids = collect(keys(u))
|
|
column_names = [:NODE]
|
|
n = length(u[first(node_ids)])
|
|
index = [Symbol("N$id") for id in node_ids]
|
|
result = Any[index]
|
|
for dof=1:n
|
|
push!(result, [u[id][dof] for id in node_ids])
|
|
push!(column_names, Symbol("$abbreviation$dof"))
|
|
end
|
|
df = DataFrame(result, column_names)
|
|
sort!(df, cols=[:NODE])
|
|
return df
|
|
end
|
|
|
|
function call(problem::Problem, ::Type{DataFrame}, field_name::AbstractString,
|
|
abbreviation::Symbol, time::Float64=0.0)
|
|
u = problem(field_name, time)
|
|
return to_dataframe(u, abbreviation)
|
|
end
|
|
|
|
function call(solver::Solver, ::Type{DataFrame}, field_name::AbstractString,
|
|
abbreviation::Symbol, time::Float64=0.0)
|
|
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
|
|
|
|
function get_components(n, m)
|
|
if n == m
|
|
if n == 1
|
|
return Vector{Int}[[1,1]]
|
|
end
|
|
if n == 2
|
|
return Vector{Int}[[1,1], [2,2], [1,2]]
|
|
elseif n == 3
|
|
return Vector{Int}[[1,1], [2,2], [3,3], [1,2], [1,3], [2,3]]
|
|
else
|
|
error("get_components, n=$n, m=$m!")
|
|
end
|
|
end
|
|
end
|
|
|
|
""" Return T in integration points. """
|
|
function call{T}(problem::Problem, ::Type{DataFrame}, element::Element, time::Float64,
|
|
::Type{Val{T}})
|
|
column_names = [:ELEMENT, :IP]
|
|
ips = get_integration_points(element)
|
|
field = Any[problem(element, ip, time, Val{T}) for ip in ips]
|
|
# FIXME, handle better ..?
|
|
first(field) == nothing && return DataFrame()
|
|
m = length(field)
|
|
n = length(first(field))
|
|
result = Any[]
|
|
push!(result, [Symbol("E$(element.id)") for i=1:m])
|
|
push!(result, [Symbol("P$i") for i=1:m])
|
|
is_tensor_field = isa(first(field), Matrix)
|
|
if is_tensor_field
|
|
n, m = size(first(field))
|
|
components = get_components(n, m)
|
|
for (j, k) in components
|
|
push!(column_names, Symbol("$T$j$k"))
|
|
push!(result, [S[j,k] for S in field])
|
|
end
|
|
else
|
|
components = collect(1:n)
|
|
for j in components
|
|
push!(column_names, Symbol("$T$j"))
|
|
push!(result, [S[j] for S in field])
|
|
end
|
|
end
|
|
df = DataFrame(result, column_names)
|
|
sort!(df, cols=[:IP])
|
|
end
|
|
|
|
function call{T}(problem::Problem, ::Type{DataFrame}, time::Float64, ::Type{Val{T}})
|
|
tables = [problem(DataFrame, element, time, Val{T}) for element in get_elements(problem)]
|
|
results = [tables...;]
|
|
return results
|
|
end
|
|
|
|
function call{T}(solver::Solver, ::Type{DataFrame}, time::Float64, ::Type{Val{T}})
|
|
problems = get_problems(solver)
|
|
tables = Any[]
|
|
for problem in get_problems(solver)
|
|
try
|
|
push!(tables, problem(DataFrame, time, Val{T}))
|
|
catch
|
|
warn("Unable to obtain results $T for problem $(problem.name)")
|
|
end
|
|
end
|
|
results = [tables...;]
|
|
return results
|
|
end
|
|
|
|
""" Interpolate field from a set of elements. """
|
|
function call(problem::Problem, field_name::AbstractString, X::Vector, time::Float64=0.0; fillna=NaN)
|
|
for element in get_elements(problem)
|
|
if inside(element, X, time)
|
|
xi = get_local_coordinates(element, X, time)
|
|
return element(field_name, xi, time)
|
|
end
|
|
end
|
|
return fillna
|
|
end
|
|
|
|
""" Interpolate field from a set of elements. """
|
|
function call(problem::Problem, field_name::AbstractString, X::Vector, time::Float64, ::Type{Val{:Grad}}; fillna=NaN)
|
|
for element in get_elements(problem)
|
|
if inside(element, X, time)
|
|
xi = get_local_coordinates(element, X, time)
|
|
return element(field_name, xi, time, Val{:Grad})
|
|
end
|
|
end
|
|
return fillna
|
|
end
|
|
|
|
function call(solver::Solver, field_name::AbstractString, X::Vector, time::Float64; fillna=NaN)
|
|
for problem in get_problems(solver)
|
|
for element in get_elements(problem)
|
|
if inside(element, X, time)
|
|
xi = get_local_coordinates(element, X, time)
|
|
return element(field_name, xi, time)
|
|
end
|
|
end
|
|
end
|
|
return fillna
|
|
end
|
|
|
|
""" Calculate area of cross-section. """
|
|
function calculate_area(problem::Problem, X=[0.0, 0.0], time=0.0)
|
|
A = 0.0
|
|
for element in get_elements(problem)
|
|
elsize = size(element)
|
|
elsize[1] == 2 || error("wrong dimension of problem for area calculation, element size = $elsize")
|
|
for ip in get_integration_points(element)
|
|
w = ip.weight*element(ip, time, Val{:detJ})
|
|
A += w
|
|
end
|
|
end
|
|
return A
|
|
end
|
|
|
|
""" Calculate volume of body. """
|
|
function calculate_volume(problem::Problem, X=[0.0, 0.0, 0.0], time=0.0)
|
|
V = 0.0
|
|
for element in get_elements(problem)
|
|
elsize = size(element)
|
|
elsize[1] == 3 || error("wrong dimension of problem for area calculation, element size = $elsize")
|
|
for ip in get_integration_points(element)
|
|
w = ip.weight*element(ip, time, Val{:detJ})
|
|
V += w
|
|
end
|
|
end
|
|
return V
|
|
end
|
|
|
|
""" Calculate center of mass of body with respect to X.
|
|
https://en.wikipedia.org/wiki/Center_of_mass
|
|
"""
|
|
function calculate_center_of_mass(problem::Problem, X=[0.0, 0.0, 0.0], time=0.0)
|
|
M = 0.0
|
|
Xc = zeros(X)
|
|
for element in get_elements(problem)
|
|
for ip in get_integration_points(element)
|
|
w = ip.weight*element(ip, time, Val{:detJ})
|
|
M += w
|
|
rho = haskey(element, "density") ? element("density", ip, time) : 1.0
|
|
Xp = element("geometry", ip, time)
|
|
Xc += w*rho*(Xp-X)
|
|
end
|
|
end
|
|
return 1.0/M * Xc
|
|
end
|
|
|
|
""" Calculate second moment of mass with respect to X.
|
|
https://en.wikipedia.org/wiki/Second_moment_of_area
|
|
"""
|
|
function calculate_second_moment_of_mass(problem::Problem, X=[0.0, 0.0, 0.0], time=0.0)
|
|
n = length(X)
|
|
I = zeros(n, n)
|
|
for element in get_elements(problem)
|
|
for ip in get_integration_points(element)
|
|
w = ip.weight*element(ip, time, Val{:detJ})
|
|
rho = haskey(element, "density") ? element("density", ip, time) : 1.0
|
|
Xp = element("geometry", ip, time) - X
|
|
I += w*rho*Xp*Xp'
|
|
end
|
|
end
|
|
return I
|
|
end
|
|
|
|
function getindex(problem::Problem, field_name::AbstractString)
|
|
info("fetching result $field_name")
|
|
timeframes = []
|
|
for frame in first(problem.elements)[field_name].data
|
|
push!(timeframes, frame.time)
|
|
end
|
|
info("time frames: $timeframes")
|
|
conn = get_connectivity(problem)
|
|
increments = Increment[]
|
|
for time in timeframes
|
|
p = problem(field_name, time)
|
|
data = [p[id] for id in conn]
|
|
push!(increments, Increment(time, data))
|
|
end
|
|
return DVTV(increments)
|
|
end
|