postprocessing utility

This commit is contained in:
Jukka Aho
2016-07-14 12:43:41 +03:00
parent 8f92a41b8a
commit 1bd1ec9fa9
21 changed files with 499 additions and 276 deletions
+4 -2
View File
@@ -65,7 +65,8 @@ export AbstractSolver, Solver, Nonlinear, NonlinearSolver, Linear, LinearSolver,
get_unknown_field_name, get_formulation_type, get_problems,
get_field_problems, get_boundary_problems,
get_field_assembly, get_boundary_assembly,
initialize!, create_projection, eliminate_interior_dofs
initialize!, create_projection, eliminate_interior_dofs,
is_field_problem, is_boundary_problem
include("solvers_modal.jl")
export Modal
@@ -135,7 +136,8 @@ export calc_nodal_values!,
copy_field!,
calculate_area,
calculate_center_of_mass,
calculate_second_moment_of_mass
calculate_second_moment_of_mass,
extract
include("postprocess_xdmf.jl")
export XDMF, xdmf_new_result!, xdmf_save_field!, xdmf_save!
export DataFrame
+62 -18
View File
@@ -6,7 +6,6 @@ importall Base
using JuliaFEM
using JuliaFEM.Preprocess
using JuliaFEM.Postprocess
using LightXML
### Model definitions for ABAQUS data model
@@ -265,8 +264,9 @@ end
typealias BOUNDARY_CONDITIONS Union{BOUNDARY, CLOAD, DLOAD, DSLOAD}
@register_abaqus_keyword("NODE PRINT")
@register_abaqus_keyword("EL PRINT")
@register_abaqus_keyword("SECTION PRINT")
typealias OUTPUT_REQUESTS Union{NODE_PRINT, SECTION_PRINT}
typealias OUTPUT_REQUESTS Union{NODE_PRINT, EL_PRINT, SECTION_PRINT}
## Properties
@@ -391,7 +391,7 @@ end
""" Dirichlet boundary condition. """
function create_boundary_problem(model::Model, bc::AbstractBoundaryCondition, ::BOUNDARY; verbose=true)
dim = determine_problem_dimension(model)
problem = Problem(Dirichlet, "Dirichlet bc *BOUNDARY", dim, "displacement")
problem = Problem(Dirichlet, "Dirichlet boundary *BOUNDARY", dim, "displacement")
for row in bc.data
if isa(row[1], AbstractString) # node set given
@@ -462,7 +462,7 @@ function create_boundary_problem(model::Model, bc::AbstractBoundaryCondition, ::
child_element = Element(JuliaFEM.(child_element_type), child_element_connectivity)
update!(child_element, "geometry", model.mesh.nodes)
update!(child_element, "surface pressure", pressure)
update!(child_element, "surface pressure", -pressure)
push!(problem.elements, child_element)
end
return problem
@@ -472,13 +472,18 @@ end
function create_boundary_problem(model::Model, bc::AbstractBoundaryCondition, ::CLOAD; verbose=false)
dim = determine_problem_dimension(model)
problem = Problem(Elasticity, "Concentrated load *CLOAD", dim)
for row in bc.data
node, dof, load = row
nodes = sort(unique([row[1] for row in bc.data]))
elements = Dict()
for node in nodes
element = Element(Poi1, [node])
update!(element, "geometry", model.mesh.nodes)
update!(element, "displacement traction force $dof", load)
push!(problem.elements, element)
elements[node] = element
end
for row in bc.data
node, dof, load = row
update!(elements[node], "concentrated force $dof", load)
end
problem.elements = collect(values(elements))
return problem
end
@@ -531,8 +536,6 @@ function get_child_element(element_type::Symbol, element_side::Symbol,
process_output_request(model, solver, output_request, kind, target)
end
using DataFrames
function process_output_request(model::Model, solver::Solver, output_request::AbstractOutputRequest,
::Type{Val{:NODE}}, ::Type{Val{:PRINT}})
data = output_request.data
@@ -546,7 +549,7 @@ function process_output_request(model::Model, solver::Solver, output_request::Ab
for row in data
info(repeat("-", 80))
codes = join(row, ", ")
info("*NODE OUTPUT request, with fields $codes")
info("*NODE PRINT request, with fields $codes")
if length(options) != 0
info("Additional options: $options")
end
@@ -554,15 +557,56 @@ function process_output_request(model::Model, solver::Solver, output_request::Ab
tables = Any[]
for code in row
haskey(code_mapping, code) || continue
for problem in model.problems
field_name = code_mapping[code]
abbr = get(abbr_mapping, code, code)
table = problem(DataFrame, field_name, abbr, solver.time)
push!(tables, table)
end
field_name = code_mapping[code]
abbr = get(abbr_mapping, code, code)
table = solver(DataFrame, field_name, abbr, solver.time)
push!(tables, table)
end
length(tables) != 0 || continue
results = join(tables..., on=:id, kind=:outer)
results = join(tables..., on=:NODE, kind=:outer)
sort!(results, cols=[:NODE])
println()
println(results)
println()
end
end
function process_output_request(model::Model, solver::Solver, output_request::AbstractOutputRequest,
::Type{Val{:EL}}, ::Type{Val{:PRINT}})
data = output_request.data
options = output_request.options
code_mapping = Dict(
:COORD => "geometry",
:S => "stress",
:E => "strain")
abbr_mapping = Dict(:COORD => :COOR)
for row in data
info(repeat("-", 80))
codes = join(row, ", ")
info("*EL PRINT request, with fields $codes")
if length(options) != 0
info("Additional options: $options")
end
info(repeat("-", 80))
tables = Any[]
for code in row
haskey(code_mapping, code) || continue
field_name = code_mapping[code]
abbr = get(abbr_mapping, code, code)
table = solver(DataFrame, solver.time, Val{code})
push!(tables, table)
end
length(tables) != 0 || continue
results = first(tables)
if length(tables) > 1
for i=2:length(tables)
results = join(results, tables[i], on=:ELEMENT, kind=:outer)
end
end
sort!(results, cols=[:ELEMENT, :IP])
# filter out elements with id -1, they are automatically created boundary elements
fel = find(results[:ELEMENT] .!= Symbol("E-1"))
results = results[fel, :]
println()
println(results)
println()
+11 -1
View File
@@ -168,11 +168,13 @@ function update!(element::Element, field_name::AbstractString, datas::Union{Real
end
end
function update!(element::Element, field_name, datas::Pair...)
#=
function update!(element::Element, field_name, data::Pair...)
for data in datas
update!(element, field_name, data)
end
end
=#
function update!(element::Element, field_name, data::Pair{Float64, Vector{Any}})
if haskey(element, field_name)
@@ -190,6 +192,14 @@ function update!(element::Element, field_name, data::Pair{Float64, Vector{Int64}
end
end
function update!(element::Element, field_name, data::Pair{Float64, Vector{Float64}})
if haskey(element, field_name)
update!(element[field_name], data)
else
element[field_name] = data
end
end
function update!(element::Element, field_name, data::Pair{Float64, Vector{Vector{Float64}}})
if haskey(element, field_name)
update!(element[field_name], data)
+4
View File
@@ -22,6 +22,10 @@ function get_basis(element::Element{Poi1}, ip, time)
return [1]
end
function get_dbasis(element::Element{Poi1}, ip, time)
return [0]
end
function call(element::Element{Poi1}, ip, time::Float64, ::Type{Val{:detJ}})
return 1.0
end
+106 -21
View File
@@ -7,6 +7,7 @@ using JuliaFEM
using DataFrames
using HDF5
using LightXML
using StringUtils
import HDF5: h5read, h5write
@@ -47,21 +48,14 @@ function convert(::Type{DataFrame}, dfs::AbstractString)
return readtable(fn)
end
function getindex(df::DataFrame, ids::Vector{Symbol}, cols::Vector{Symbol})
rows = Int64[find(df[:id] .== id)[1] for id in ids]
return df[rows, cols]
end
function getindex(df::DataFrame, id::Symbol, col::Symbol)
return getindex(df, [id], [col])
end
function getindex(df::DataFrame, id::Symbol, cols::Vector{Symbol})
return getindex(df, [id], cols)
end
function getindex(df::DataFrame, ids::Vector{Symbol}, col::Symbol)
return getindex(df, ids, [col])
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)
@@ -72,6 +66,12 @@ 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
end
"""
Calculate field values to nodal points from Gauss points using least-squares fitting.
"""
@@ -201,9 +201,13 @@ end
function call(problem::Problem, field_name::AbstractString, time::Float64=0.0)
f = Dict()
for element in get_elements(problem)
haskey(element, field_name) || continue
for (c, v) in zip(get_connectivity(element), element(field_name, time))
if haskey(f, c)
@assert isapprox(f[c], v)
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
end
f[c] = v
end
@@ -211,11 +215,10 @@ function call(problem::Problem, field_name::AbstractString, time::Float64=0.0)
return f
end
function call(problem::Problem, ::Type{DataFrame}, field_name::AbstractString,
abbreviation::Symbol, time::Float64=0.0)
u = problem(field_name, time)
function to_dataframe(u::Dict, abbreviation::Symbol)
length(u) != 0 || return DataFrame()
node_ids = collect(keys(u))
column_names = [:id]
column_names = [:NODE]
n = length(u[first(node_ids)])
index = [Symbol("N$id") for id in node_ids]
result = Any[index]
@@ -224,10 +227,92 @@ function call(problem::Problem, ::Type{DataFrame}, field_name::AbstractString,
push!(column_names, Symbol("$abbreviation$dof"))
end
df = DataFrame(result, column_names)
sort!(df, cols=[:id])
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)
u = Dict()
for problem in get_problems(solver)
u = merge(u, problem(field_name, time))
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)
+11 -4
View File
@@ -91,13 +91,21 @@ function filter_by_element_set(mesh::Mesh, set_name)
filter_by_element_id(mesh::Mesh, collect(mesh.element_sets[set_name]))
end
function create_element(mesh::Mesh, id::Int)
connectivity = mesh.elements[id]
element_type = JuliaFEM.(mesh.element_types[id])
element = Element(element_type, connectivity)
update!(element, "geometry", mesh.nodes)
element.id = id
return element
end
function create_elements(mesh::Mesh; element_type=nothing)
element_ids = collect(keys(mesh.elements))
if element_type != nothing
filter!(id -> mesh.element_types[id] == element_type, element_ids)
end
elements = [Element(JuliaFEM.(mesh.element_types[id]), mesh.elements[id]) for id in element_ids]
update!(elements, "geometry", mesh.nodes)
elements = [create_element(mesh, id) for id in element_ids]
return elements
end
@@ -115,8 +123,7 @@ function create_elements(mesh::Mesh, element_sets::Symbol...; element_type=nothi
filter!(id -> mesh.element_types[id] == element_type, element_ids)
end
elements = [Element(JuliaFEM.(mesh.element_types[id]), mesh.elements[id]) for id in element_ids]
update!(elements, "geometry", mesh.nodes)
elements = [create_element(mesh, id) for id in element_ids]
return elements
end
+71 -69
View File
@@ -141,63 +141,53 @@ function get_assembly(problem)
return problem.assembly
end
""" Initialize unknown field ready for nonlinear iterations, i.e.,
take last known value and set it as a initial quess for next
time increment.
"""
function initialize!(problem::Problem, time=0.0)
""" Initialize element ready for calculation. """
function initialize!(problem::Problem, element::Element, time::Float64)
field_name = get_unknown_field_name(problem)
field_dim = get_unknown_field_dimension(problem)
for element in get_elements(problem)
gdofs = get_gdofs(problem, element)
if haskey(element, field_name)
# if field is found, copy last known solution to new time as initial guess
field = last(element[field_name])
if !isa(field, TimeVariantField)
info("Unable to initialize field $field_name for problem, is not time variant?")
continue
end
nnodes = length(element)
if !isapprox(field.time, time)
last_data = copy(last(element[field_name]).data)
push!(element[field_name], time => last_data)
end
else # if field not found at all, initialize new zero field.
data = Vector{Float64}[zeros(field_dim) for i in 1:length(element)]
element[field_name] = (time => data)
# initialize primary field
if !haskey(element, field_name)
if field_dim == 1
update!(element, field_name, time => zeros(nnodes))
else
update!(element, field_name, time => [zeros(field_dim) for i=1:nnodes])
end
end
# if this is boundary problem and not dirichlet problem, initialize field
# for primary variable too
# if boundary problem, initialize field for main problem too
is_boundary_problem(problem) || return
#is_dirichlet_problem(problem) && return
field_name = get_parent_field_name(problem)
for element in get_elements(problem)
gdofs = get_gdofs(problem, element)
if haskey(element, field_name)
# if field is found, copy last known solution to new time as initial guess
if !isapprox(last(element[field_name]).time, time)
last_data = copy(last(element[field_name]).data)
push!(element[field_name], time => last_data)
end
else # if field not found at all, initialize new zero field.
data = Vector{Float64}[zeros(field_dim) for i in 1:length(element)]
element[field_name] = (time => data)
if !haskey(element, field_name)
if field_dim == 1
update!(element, field_name, time => zeros(nnodes))
else
update!(element, field_name, time => [zeros(field_dim) for i=1:nnodes])
end
end
end
""" Update problem solution vector for assembly. """
function update_assembly!(problem, u, la; verbose=false)
function initialize!(problem::Problem, time::Float64=0.0)
for element in get_elements(problem)
initialize!(problem, element, time)
end
end
assembly = get_assembly(problem)
""" Update problem solution vector for assembly. """
function update!(problem::Problem, assembly::Assembly, u::Vector, la::Vector; verbose=false)
# resize & fill with zeros vectors if length mismatch with current solution
if length(u) != length(assembly.u)
info("resizing solution vector u")
resize!(assembly.u, length(u))
fill!(assembly.u, 0.0)
end
if length(la) != length(assembly.la)
info("resizing lagrange multipliers vector u")
resize!(assembly.la, length(la))
fill!(assembly.la, 0.0)
end
@@ -228,47 +218,54 @@ function update_assembly!(problem, u, la; verbose=false)
# calculate change of norm
assembly.u_norm_change = norm(assembly.u - assembly.u_prev)
assembly.la_norm_change = norm(assembly.la - assembly.la_prev)
#return assembly.u_norm_change, assembly.la_norm_change
return assembly.u, assembly.la
end
""" Update solutions to elements.
""" Return global solution (u, la) for problem.
Notes
-----
This assumes that element is properly initialized so that last known field data
is from current time. For boundary problems solution is updated from lambda vector
and for field problems from actual solution vector.
If length of solution vector != number of nodes, i.e. field dimension is
something other than 1, reshape vectors so it's length matches to the
number of nodes so that one can easily get nodal results.
"""
function update_elements!{P<:FieldProblem}(problem::Problem{P}, u, la)
field_name = get_unknown_field_name(problem)
function get_global_solution(problem::Problem, assembly::Assembly)
u = assembly.u
la = assembly.la
field_dim = get_unknown_field_dimension(problem)
nnodes = round(Int, length(u)/field_dim)
solution = reshape(u, field_dim, nnodes)
for element in get_elements(problem)
connectivity = get_connectivity(element) # node ids
local_sol = Vector{Float64}[solution[:, node_id] for node_id in connectivity]
last(element[field_name]).data = local_sol
if field_dim == 1
return u, la
else
nnodes = round(Int, length(u)/field_dim)
u = reshape(u, field_dim, nnodes)
u = Vector{Float64}[u[:,i] for i in 1:nnodes]
la = reshape(la, field_dim, nnodes)
la = Vector{Float64}[la[:,i] for i in 1:nnodes]
return u, la
end
end
function update_elements!{P<:BoundaryProblem}(problem::Problem{P}, u, la)
""" Update solution from assebly to elements. """
function update!{P<:FieldProblem}(problem::Problem{P}, assembly::Assembly, elements::Vector{Element}, time::Float64)
u, la = get_global_solution(problem, assembly)
field_name = get_unknown_field_name(problem)
field_dim = get_unknown_field_dimension(problem)
nnodes = round(Int, length(u)/field_dim)
solution = reshape(la, field_dim, nnodes)
for element in get_elements(problem)
connectivity = get_connectivity(element) # node ids
local_sol = Vector{Float64}[solution[:, node_id] for node_id in connectivity]
last(element[field_name]).data = local_sol
# update solution u for elements
for element in elements
connectivity = get_connectivity(element)
update!(element, field_name, time => u[connectivity])
end
# if boundary problem is not dirichlet, update also data of main problem
# is_dirichlet_problem(problem) && return
field_name = get_parent_field_name(problem)
solution = reshape(u, field_dim, nnodes)
for element in get_elements(problem)
connectivity = get_connectivity(element) # node ids
local_sol = Vector{Float64}[solution[:, node_id] for node_id in connectivity]
last(element[field_name]).data = local_sol
end
function update!{P<:BoundaryProblem}(problem::Problem{P}, assembly::Assembly, elements::Vector{Element}, time::Float64)
u, la = get_global_solution(problem, assembly)
parent_field_name = get_parent_field_name(problem) # displacement
field_name = get_unknown_field_name(problem) # reaction force
# update solution u and reaction force λ for boundary elements
for element in elements
connectivity = get_connectivity(element)
update!(element, parent_field_name, time => u[connectivity])
# FIXME
update!(element, field_name, time => -la[connectivity])
end
end
@@ -276,12 +273,16 @@ function get_elements(problem::Problem)
return problem.elements
end
function get_assembly(problem::Problem)
return problem.assembly
end
function length(problem::Problem)
return length(problem.elements)
end
function update!(problem::Problem, field_name, field)
update!(problem.elements, field_name, field)
function update!(problem::Problem, field_name::AbstractString, data)
update!(problem.elements, field_name::AbstractString, data)
end
""" Return the dimension of the unknown field of this problem. """
@@ -380,3 +381,4 @@ function find_nodes_by_dofs(dim, dofs)
end
return nodes
end
+38
View File
@@ -530,6 +530,7 @@ function assemble{El<:Elasticity3DSurfaceElements}(problem::Problem{Elasticity},
Kg = zeros(dim*nnodes, dim*nnodes)
f = zeros(dim*nnodes)
has_concentrated_forces = false
for ip in get_integration_points(element)
detJ = element(ip, time, Val{:detJ})
w = ip.weight*detJ
@@ -543,6 +544,11 @@ function assemble{El<:Elasticity3DSurfaceElements}(problem::Problem{Elasticity},
T = element("displacement traction force $i", ip, time)
f[i:dim:end] += w*vec(T*N)
end
if haskey(element, "concentrated force $i")
has_concentrated_forces = true
T = element("concentrated force $i", ip, time)
f[i:dim:end] += w*vec(T*N)
end
end
if haskey(element, "surface pressure")
J = element(ip, time, Val{:Jacobian})'
@@ -553,6 +559,9 @@ function assemble{El<:Elasticity3DSurfaceElements}(problem::Problem{Elasticity},
f += w*p*vec(n*N)
end
end
if has_concentrated_forces
update!(element, "concentrated force", time => Any[f])
end
return Km, Kg, f
end
@@ -710,3 +719,32 @@ end
=#
function call(problem::Problem, element::Element, ip, time::Float64, ::Type{Val{:E}})
haskey(element, "displacement") || return nothing
gradu = element("displacement", ip, time, Val{:Grad})
eps = 0.5*(gradu + gradu')
return eps
end
function call(problem::Problem, element::Element, ip, time::Float64, ::Type{Val{:S}})
haskey(element, "displacement") || return nothing
props = problem.properties
eps = problem(element, ip, time, Val{:E})
eps == nothing && return nothing
E = element("youngs modulus", ip, time)
nu = element("poissons ratio", ip, time)
mu = E/(2.0*(1.0+nu))
la = E*nu/((1.0+nu)*(1.0-2.0*nu))
if props.formulation in [:plane_stress, :plane_strain]
la = 2.0*la*mu/(la+2.0*mu)
end
S = la*trace(eps)*I + 2.0*mu*eps
return S
end
function call(problem::Problem, element::Element, ip, time::Float64, ::Type{Val{:COORD}})
haskey(element, "geometry") || return nothing
return element("geometry", ip, time)
end
+6 -2
View File
@@ -408,8 +408,12 @@ function update!(solver::Solver, u::Vector, la::Vector; show_info=true)
show_info && info("Updating problems ...")
t0 = Base.time()
for problem in solver.problems
u_new, la_new = update_assembly!(problem, u, la)
update_elements!(problem, u_new, la_new)
assembly = get_assembly(problem)
elements = get_elements(problem)
# update solution, first for assembly (u,la) ...
update!(problem, assembly, u, la)
# .. and then from assembly (u,la) to elements
update!(problem, assembly, elements, solver.time)
end
t1 = round(Base.time()-t0, 2)
show_info && info("Updated problems in $t1 seconds.")
+10 -8
View File
@@ -101,21 +101,23 @@ function call(solver::Solver{Modal}; show_info=true, debug=false)
info("Eigenvalues computed in $t1 seconds. Eigenvalues: $om2")
for i=1:length(om2)
freq = real(sqrt(om2[i])/(2.0*pi))
u = props.eigvecs[:,i]
field_dim = get_unknown_field_dimension(solver)
field_name = get_unknown_field_name(solver)
nnodes = round(Int, length(u)/field_dim)
solution = reshape(u, field_dim, nnodes)
if field_dim != 1
nnodes = round(Int, length(u)/field_dim)
u = reshape(u, field_dim, nnodes)
u = Vector{Float64}[u[:,i] for i in 1:nnodes]
end
for problem in get_problems(solver)
local_sol = Dict{Int64, Vector{Float64}}()
for node_id in get_connectivity(problem)
local_sol[node_id] = solution[:, node_id]
for element in get_elements(problem)
connectivity = get_connectivity(element)
update!(element, field_name, freq => u[connectivity])
end
freq = real(sqrt(om2[i])/(2.0*pi))
update!(problem, field_name, freq => local_sol)
end
end
return true
end