Use package AbaqusReader.jl (#127)

Source code related to read and parse ABAQUS .inp files is now living in
it's own repository `AbaqusReader.jl` and in this commit we cleanup the
same files from this repository.

- add AbaqusReader to .travis.yml because it's not registered package yet
- initialize Mesh from AbaqusReader.jl dict
- remove ABAQUS tests and files moved to AbaqusReader.jl
- remove references to old module Abaqus
- move ABAQUS code to preprocess.jl (what is left)
- close issue #122
- close issue #55
This commit is contained in:
Jukka Aho
2017-07-21 00:40:52 +03:00
committed by GitHub
parent 9ad66be08b
commit a666bb4bd8
13 changed files with 76 additions and 1296 deletions
+1
View File
@@ -18,6 +18,7 @@ before_script:
- julia --color=yes -e 'Pkg.add("Lint")'
- julia --color=yes -e 'Pkg.clone("https://github.com/ahojukka5/CheckHeader.jl.git")'
- julia --color=yes -e 'Pkg.clone("https://github.com/ahojukka5/CheckTabs.jl.git")'
- julia --color=yes -e 'Pkg.clone("https://github.com/JuliaFEM/AbaqusReader.jl.git")'
script:
- julia --color=yes -e 'Pkg.build("JuliaFEM")'
- julia --color=yes -e 'using CheckHeader; checkheader("JuliaFEM")'
+4 -1
View File
@@ -6,6 +6,7 @@
```@meta
DocTestSetup = quote
using JuliaFEM
using JuliaFEM.Preprocess
end
```
@@ -15,7 +16,9 @@ Add here.
## Functions
Add here.
```@docs
JuliaFEM.Preprocess.create_surface_elements
```
## Index
+2 -9
View File
@@ -137,15 +137,14 @@ export create_elements, Mesh, add_node!, add_nodes!, add_element!,
add_elements!, add_element_to_element_set!, add_node_to_node_set!,
find_nearest_nodes, find_nearest_node, reorder_element_connectivity!,
create_node_set_from_element_set!
include("preprocess_abaqus_reader.jl")
export parse_abaqus, parse_section, parse_element_section,
abaqus_read_mesh, abaqus_read_model
include("preprocess_aster_reader.jl")
export aster_create_elements, parse_aster_med_file, is_aster_mail_keyword,
parse_aster_header, aster_parse_nodes, aster_renumber_nodes!,
aster_renumber_elements!, aster_combine_meshes, aster_read_mesh,
filter_by_element_set, filter_by_element_id, MEDFile, aster_read_data,
aster_read_mesh_names, aster_read_node_sets, aster_read_nodes, RMEDFile
end
module Postprocess
@@ -155,10 +154,4 @@ export calc_nodal_values!, get_nodal_vector, get_nodal_dict, copy_field!,
calculate_second_moment_of_mass, extract
end
module Abaqus
include("abaqus.jl")
export abaqus_read_model, abaqus_run_model, abaqus_open_results, create_surface_elements
end
end
-765
View File
@@ -1,765 +0,0 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
import Base: getindex, length
using JuliaFEM
using JuliaFEM.Preprocess
using JuliaFEM.Postprocess
### Model definitions for ABAQUS data model
abstract type AbstractMaterial end
abstract type AbstractMaterialProperty end
abstract type AbstractProperty end
abstract type AbstractStep end
abstract type AbstractBoundaryCondition end
abstract type AbstractOutputRequest end
type Model
path :: AbstractString
name :: AbstractString
mesh :: Mesh
materials :: Dict{Symbol, AbstractMaterial}
properties :: Vector{AbstractProperty}
boundary_conditions :: Vector{AbstractBoundaryCondition}
steps :: Vector{AbstractStep}
problems :: Vector{Problem}
end
type SolidSection <: AbstractProperty
element_set :: Symbol
material_name :: Symbol
end
type Material <: AbstractMaterial
name :: Symbol
properties :: Vector{AbstractMaterialProperty}
end
type Elastic <: AbstractMaterialProperty
E :: Float64
nu :: Float64
end
type Step <: AbstractStep
kind :: Nullable{Symbol} # STATIC, ...
boundary_conditions :: Vector{AbstractBoundaryCondition}
output_requests :: Vector{AbstractOutputRequest}
end
type BoundaryCondition <: AbstractBoundaryCondition
kind :: Symbol # BOUNDARY, CLOAD, DLOAD, DSLOAD, ...
data :: Vector
options :: Dict
end
type OutputRequest <: AbstractOutputRequest
kind :: Symbol # NODE, EL, SECTION, ...
data :: Vector
options :: Dict
target :: Symbol # PRINT, FILE
end
### Utility functions to parse ABAQUS .inp file to data model
type Keyword
name :: AbstractString
options :: Vector{Union{AbstractString, Pair}}
end
function getindex(kw::Keyword, s)
return parse(Dict(kw.options)[s])
end
type AbaqusReaderState
section :: Nullable{Keyword}
material :: Nullable{AbstractMaterial}
property :: Nullable{AbstractProperty}
step :: Nullable{AbstractStep}
data :: Vector{AbstractString}
end
function get_data(state::AbaqusReaderState)
data = []
for row in state.data
row = strip(row, [' ', ','])
col = split(row, ',')
col = map(parse, col)
push!(data, col)
end
return data
end
function get_options(state::AbaqusReaderState)
return Dict(get(state.section).options)
end
function get_option(state::AbaqusReaderState, what::AbstractString)
return get_options(state)[what]
end
function length(state::AbaqusReaderState)
return length(state.data)
end
function is_comment(line)
return startswith(line, "**")
end
function is_keyword(line)
return startswith(line, "*") && !is_comment(line)
end
function parse_keyword(line; uppercase_keyword=true)
args = split(line, ",")
args = map(strip, args)
keyword_name = strip(args[1], '*')
if uppercase_keyword
keyword_name = uppercase(keyword_name)
end
keyword = Keyword(keyword_name, [])
for option in args[2:end]
pair = split(option, "=")
if uppercase_keyword
pair[1] = uppercase(pair[1])
end
if length(pair) == 1
push!(keyword.options, pair[1])
elseif length(pair) == 2
push!(keyword.options, pair[1] => pair[2])
else
error("Keyword failure: $line, $option, $pair")
end
end
return keyword
end
macro register_abaqus_keyword(keyword)
underscored = Symbol(replace(keyword, " ", "_"))
quote
global is_abaqus_keyword_registered
typealias $underscored Type{Val{Symbol($keyword)}}
is_abaqus_keyword_registered(::Type{Val{Symbol($keyword)}}) = true
end
end
function is_abaqus_keyword_registered(s::AbstractString)
return is_abaqus_keyword_registered(Val{Symbol(s)})
end
function is_abaqus_keyword_registered(others)
return false
end
function is_new_section(line)
is_keyword(line) || return false
section = parse_keyword(line)
is_abaqus_keyword_registered(section.name) || return false
return true
end
function maybe_close_section!(model, state; verbose=true)
isnull(state.section) && return
section_name = get(state.section).name
verbose && info("Close section: $section_name")
args = Tuple{Model, AbaqusReaderState, Type{Val{Symbol(section_name)}}}
if method_exists(close_section!, args)
close_section!(model, state, Val{Symbol(section_name)})
else
verbose && warn("no close_section! found for $section_name")
end
state.section = nothing
end
function maybe_open_section!(model, state; verbose=true)
section_name = get(state.section).name
section_options = get(state.section).options
verbose && info("New section: $section_name with options $section_options")
args = Tuple{Model, AbaqusReaderState, Type{Val{Symbol(section_name)}}}
if method_exists(open_section!, args)
open_section!(model, state, Val{Symbol(section_name)})
else
verbose && warn("no open_section! found for $section_name")
end
end
function new_section!(model, state, line::AbstractString; verbose=true)
maybe_close_section!(model, state; verbose=verbose)
state.data = []
state.section = parse_keyword(line)
maybe_open_section!(model, state; verbose=verbose)
end
# open_section! is called right after keyword is found
function open_section! end
# close_section! is called at the end or section or before new keyword
function close_section! end
function process_line!(model, state, line; verbose=false)
if isnull(state.section)
verbose && info("section = nothing! line = $line")
return
end
if is_keyword(line)
warn("missing keyword? line = $line")
# close section, this is probably keyword and collecting data should stop.
maybe_close_section!(model, state)
return
end
push!(state.data, line)
end
function abaqus_read_model(fn; read_mesh=true)
model_path = dirname(fn)
model_name = first(splitext(basename(fn)))
model = Model(model_path, model_name, Mesh(), Dict(), [], [], [], [])
if read_mesh
model.mesh = abaqus_read_mesh(fn)
end
state = AbaqusReaderState(nothing, nothing, nothing, nothing, [])
fid = open(fn)
for line in eachline(fid)
line = strip(line)
is_comment(line) && continue
if is_new_section(line)
new_section!(model, state, line)
else
process_line!(model, state, line)
end
end
close(fid)
maybe_close_section!(model, state)
return model
end
### Code to parse ABAQUS .inp to data model
# add here only keywords when planning to define open_section! and/or
# close_section!, i.e. actually parse keyword to model. little bit of
# magic is happening here, but after calling macro there is typealias
# defined i.e. typealias SOLID_SECTION Type{Val{Symbol("SOLID_SECTION")}}
# and also is_keyword_registered("SOLID SECTION") returns true after
# registration, also notice underscoring
@register_abaqus_keyword("SOLID SECTION")
@register_abaqus_keyword("MATERIAL")
@register_abaqus_keyword("ELASTIC")
@register_abaqus_keyword("STEP")
@register_abaqus_keyword("STATIC")
@register_abaqus_keyword("END STEP")
@register_abaqus_keyword("BOUNDARY")
@register_abaqus_keyword("CLOAD")
@register_abaqus_keyword("DLOAD")
@register_abaqus_keyword("DSLOAD")
const BOUNDARY_CONDITIONS = Union{BOUNDARY,CLOAD,DLOAD,DSLOAD}
@register_abaqus_keyword("NODE PRINT")
@register_abaqus_keyword("EL PRINT")
@register_abaqus_keyword("SECTION PRINT")
const OUTPUT_REQUESTS = Union{NODE_PRINT,EL_PRINT,SECTION_PRINT}
## Properties
function open_section!(model, state, ::SOLID_SECTION)
element_set = get_option(state, "ELSET")
material_name = get_option(state, "MATERIAL")
property = SolidSection(element_set, material_name)
state.property = property
push!(model.properties, property)
end
function close_section!(model, state, ::SOLID_SECTION)
state.property = nothing
end
## Materials
function open_section!(model, state, ::MATERIAL)
material_name = Symbol(get_option(state, "NAME"))
material = Material(material_name, [])
state.material = material
if haskey(model.materials, material_name)
warn("Material $material_name already exists in model, skipping definition.")
else
model.materials[material_name] = material
end
end
function close_section!(model, state, ::ELASTIC)
# FIXME
@assert length(state) == 1
E, nu = first(get_data(state))
material_property = Elastic(E, nu)
material = get(state.material)
push!(material.properties, material_property)
end
## Steps
function open_section!(model, state, ::STEP)
step = Step(nothing, Vector(), Vector())
state.step = step
push!(model.steps, step)
end
function open_section!(model, state, ::STATIC)
isnull(state.step) && error("*STATIC outside *STEP ?")
get(state.step).kind = :STATIC
end
function open_section!(model, state, ::END_STEP)
state.step = nothing
end
## Steps -- boundary conditions
function close_section!(model, state, ::BOUNDARY_CONDITIONS)
kind = Symbol(get(state.section).name)
data = get_data(state)
options = get_options(state)
bc = BoundaryCondition(kind, data, options)
if isnull(state.step)
push!(model.boundary_conditions, bc)
else
step = get(state.step)
push!(step.boundary_conditions, bc)
end
end
## Steps -- output requests
function close_section!(model, state, ::OUTPUT_REQUESTS)
kind, target = map(parse, split(get(state.section).name, " "))
data = get_data(state)
options = get_options(state)
request = OutputRequest(kind, data, options, target)
step = get(state.step)
push!(step.output_requests, request)
end
### Code to use JuliaFEM to run ABAQUS data model
function determine_problem_type(model::Model)
# FIXME
return Elasticity
end
function determine_problem_dimension(model::Model)
# FIXME
return 3
end
function get_element_section(model::Model, element_set_name::Symbol)
sections = filter(s -> s.element_set == element_set_name, model.properties)
length(sections) == 1 || error("Multiple sections found for element set $element_set_name")
return sections[1]
end
function get_material(model::Model, material_name)
return model.materials[material_name]
end
function create_problem(model::Model, element_set_name::Symbol; verbose=true)
problem_type = determine_problem_type(model)
problem_name = "$problem_type $element_set_name"
problem_dimension = determine_problem_dimension(model)
problem = Problem(problem_type, problem_name, problem_dimension)
problem.elements = create_elements(model.mesh, element_set_name)
section = get_element_section(model, element_set_name)
material = get_material(model, section.material_name)
for mp in material.properties
if isa(mp, Elastic)
verbose && info("$element_set_name: elastic material, E = $(mp.E), nu = $(mp.nu)")
update!(problem.elements, "youngs modulus", mp.E)
update!(problem.elements, "poissons ratio", mp.nu)
end
end
return problem
end
""" Dirichlet boundary condition. """
function create_boundary_problem(model::Model, bc::AbstractBoundaryCondition, ::BOUNDARY; verbose=true)
dim = determine_problem_dimension(model)
problem = Problem(Dirichlet, "Dirichlet boundary *BOUNDARY", dim, "displacement")
for row in bc.data
if isa(row[1], AbstractString) # node set given
nodes = model.mesh.node_sets[bc_name]
else # single node given
nodes = [row[1]]
end
elements = [Element(Poi1, [id]) for id in nodes]
update!(elements, "geometry", model.mesh.nodes)
for dof in row[2]:row[end]
# FIXME
val = 0.0
update!(elements, "displacement $dof", val)
verbose && info("Nodes ", join(nodes, ", "), " dof $dof => $val")
end
push!(problem, elements)
end
return problem
end
""" Distributed surface load (DSLOAD). """
function create_boundary_problem(model::Model, bc::AbstractBoundaryCondition, ::DSLOAD; verbose=false)
dim = determine_problem_dimension(model)
problem = Problem(Elasticity, "Distributed surface load *DSLOAD", dim)
for row in bc.data
bc_name, bc_type, pressure = row
bc_type == :P || error("bc_type = $bc_type != :P")
elements = []
for (parent_element_id, parent_element_side) in model.mesh.surface_sets[bc_name]
parent_element_type = model.mesh.element_types[parent_element_id]
parent_element_connectivity = model.mesh.elements[parent_element_id]
child_element_type, child_element_lconn, child_element_connectivity =
get_child_element(parent_element_type, parent_element_side,
parent_element_connectivity)
verbose && info("parent element : $parent_element_id, $parent_element_type, $parent_element_connectivity, $parent_element_side")
verbose && info("child element : $child_element_type, $child_element_connectivity")
child_element = Element(JuliaFEM.(child_element_type), child_element_connectivity)
push!(elements, child_element)
end
update!(elements, "geometry", model.mesh.nodes)
update!(elements, "surface pressure", pressure)
push!(problem, elements)
end
return problem
end
""" Distributed load (DLOAD). """
function create_boundary_problem(model::Model, bc::AbstractBoundaryCondition, ::DLOAD; verbose=false)
dim = determine_problem_dimension(model)
problem = Problem(Elasticity, "Distributed load *DLOAD", dim)
for row in bc.data
parent_element_id, parent_element_side, pressure = row
parent_element_type = model.mesh.element_types[parent_element_id]
parent_element_connectivity = model.mesh.elements[parent_element_id]
child_element_type, child_element_lconn, child_element_connectivity =
get_child_element(parent_element_type, parent_element_side,
parent_element_connectivity)
verbose && info("parent element : $parent_element_id, $parent_element_type, $parent_element_connectivity, $parent_element_side")
verbose && info("child element : $child_element_type, $child_element_connectivity")
child_element = Element(getfield(JuliaFEM, child_element_type), child_element_connectivity)
update!(child_element, "geometry", model.mesh.nodes)
update!(child_element, "surface pressure", -pressure)
push!(problem.elements, child_element)
end
return problem
end
""" Concentrated load (CLOAD). """
function create_boundary_problem(model::Model, bc::AbstractBoundaryCondition, ::CLOAD; verbose=false)
dim = determine_problem_dimension(model)
problem = Problem(Elasticity, "Concentrated load *CLOAD", dim)
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)
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
function create_boundary_problem(model::Model, bc::AbstractBoundaryCondition)
create_boundary_problem(model, bc, Val{bc.kind})
end
""" Given element code, element side and global connectivity, determine boundary
element. E.g. for Tet4 we have 4 sides S1..S4 and boundary element is of type Tri3.
"""
function get_child_element(element_type::Symbol, element_side::Symbol,
element_connectivity::Vector{Int64})
element_mapping = Dict(
:Tet4 => Dict(
:S1 => (:Tri3, [1, 3, 2]),
:S2 => (:Tri3, [1, 2, 4]),
:S3 => (:Tri3, [2, 3, 4]),
:S4 => (:Tri3, [1, 4, 3])),
:Tet10 => Dict(
:S1 => (:Tri6, [1, 3, 2, 7, 6, 5]),
:S2 => (:Tri6, [1, 2, 4, 5, 9, 8]),
:S3 => (:Tri6, [2, 3, 4, 6, 10, 9]),
:S4 => (:Tri6, [1, 4, 3, 8, 10, 7])),
:Hex8 => Dict(
:P1 => (:Quad4, [1, 2, 3, 4]),
:P2 => (:Quad4, [5, 8, 7, 6]),
:P3 => (:Quad4, [1, 5, 6, 2]),
:P4 => (:Quad4, [2, 6, 7, 3]),
:P5 => (:Quad4, [3, 7, 8, 4]),
:P6 => (:Quad4, [4, 8, 5, 1]))
)
if !haskey(element_mapping, element_type)
error("Unable to find child element for element of type $element_type for side $element_side, check mapping.")
end
if !haskey(element_mapping[element_type], element_side)
error("Unable to find child element side mapping for element of type $element_type for side $element_side, check mapping.")
end
child_element, child_element_lconn = element_mapping[element_type][element_side]
child_element_gconn = element_connectivity[child_element_lconn]
return child_element, child_element_lconn, child_element_gconn
end
function determine_solver_type(model::Model, step::AbstractStep)
# FIXME
return Linear
end
function process_output_request(model::Model, solver::Solver, output_request::AbstractOutputRequest)
kind = Val{output_request.kind}
target = Val{output_request.target}
process_output_request(model, solver, output_request, kind, target)
end
function process_output_request(model::Model, solver::Solver, output_request::AbstractOutputRequest,
::Type{Val{:NODE}}, ::Type{Val{:PRINT}})
data = output_request.data
options = output_request.options
code_mapping = Dict(
:COORD => "geometry",
:U => "displacement",
:CF => "concentrated force",
:RF => "reaction force")
abbr_mapping = Dict(:COORD => :COOR)
for row in data
info(repeat("-", 80))
codes = join(row, ", ")
info("*NODE 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, field_name, abbr, solver.time)
#push!(tables, table)
end
length(tables) != 0 || continue
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))
#= to be fixed
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()
=#
end
end
function process_output_request(model::Model, solver::Solver, output_request::AbstractOutputRequest,
::Type{Val{:SECTION}}, ::Type{Val{:PRINT}})
data = output_request.data
options = output_request.options
info("SECTION PRINT output request, with data $data and options $options")
end
function (model::Model)()
info("Starting JuliaFEM-ABAQUS solver.")
# 1. create field problems and add elements
element_sets = collect(keys(model.mesh.element_sets))
info("Creating problems for element sets ", join(element_sets, ", "))
model.problems = [create_problem(model, elset) for elset in element_sets]
# 2. create boundary problems (the ones defined before *STEP)
info("Boundary conditions defined before *STEP")
for (i, bc) in enumerate(model.boundary_conditions)
info("$i $(bc.kind)")
end
boundary_problems = [create_boundary_problem(model, bc) for bc in model.boundary_conditions]
# 3. loop steps
for step in model.steps
# 3.1 add boundary conditions defined inside *STEP
step_problems = [create_boundary_problem(model, bc) for bc in step.boundary_conditions]
# 3.2 create solver and solve set of problems
solver_type = determine_solver_type(model, step)
solver_description = "$solver_type solver"
solver = Solver(solver_type, solver_description)
all_problems = [model.problems; boundary_problems; step_problems]
push!(solver, all_problems...)
solver()
info(repeat("-", 80))
info("Simulation ready, processing output requests")
info(repeat("-", 80))
# 3.3 postprocessing based on output requests
for output_request in step.output_requests
process_output_request(model, solver, output_request)
end
end
return 0
end
function abaqus_download(name)
fn = "$name.inp"
if !haskey(ENV, "ABAQUS_DOWNLOAD_URL")
info("""
ABAQUS input file $fn not found and ABAQUS_DOWNLOAD_URL not set, unable to
download file. To enable automatic model downloading, set environment variable
ABAQUS_URL to point url to models.""")
return 1
end
url = ENV["ABAQUS_DOWNLOAD_URL"]
if haskey(ENV, "ABAQUS_DOWNLOAD_DIR")
fn = rstrip(ENV["ABAQUS_DOWNLOAD_DIR"], '/') * "/" * fn
end
if !isfile(fn)
info("Downloading model $name ...")
download("$url/$name.inp", fn)
end
return 0
end
""" Return input file name. """
function abaqus_input_file_name(name)
fn = "$name.inp"
isfile(fn) && return fn
if haskey(ENV, "ABAQUS_DOWNLOAD_DIR")
fn = rstrip(ENV["ABAQUS_DOWNLOAD_DIR"], '/') * "/" * fn
end
isfile(fn) && return fn
return ""
end
function abaqus_input_file_path(name)
return dirname(abaqus_input_file_name(name))
end
function abaqus_open_results(name)
path = abaqus_input_file_path(name)
result_file = "$path/$name.xmf"
return Xdmf(result_file)
end
### JuliaFEM-ABAQUS interface entry point
"""
Run ABAQUS model. If input file is not found, attempt to fetch it from internet
if fetch is set to true and ABAQUS_DOWNLOAD_URL is set. Return exit code 0 if
execution of model is success.
"""
function abaqus_run_model(name; fetch=false, verbose=false)
if !isfile("$name.inp") && fetch
status = abaqus_download(name)
status == 0 || return status # download failed
end
fn = abaqus_input_file_name(name)
if verbose
println(repeat("-", 80))
println("Running ABAQUS model $name from file $fn")
println(repeat("-", 80))
println(readstring(fn))
println(repeat("-", 80))
end
model = abaqus_read_model(fn)
status = model()
return status
end
"""
This function gerates surface elements from solid elements
slave = create_surface_elements(mesh, :slave_surf)
master = create_surface_elements(mesh, :master_surf)
"""
function create_surface_elements(mesh::Mesh, surface_name::Symbol)
elements = []
for (parent_element_id, parent_element_side) in mesh.surface_sets[surface_name]
parent_element_type = mesh.element_types[parent_element_id]
parent_element_connectivity = mesh.elements[parent_element_id]
child_element_type, child_element_lconn, child_element_connectivity =
get_child_element(parent_element_type, parent_element_side,
parent_element_connectivity)
child_element = Element(getfield(JuliaFEM, child_element_type), child_element_connectivity)
push!(elements, child_element)
end
update!(elements, "geometry", mesh.nodes)
return elements
end
function create_surface_elements(mesh::Mesh, surface_name::String)
return create_surface_elements(mesh, Symbol(surface_name))
end
+69
View File
@@ -31,6 +31,31 @@ function Mesh()
return Mesh(Dict(), Dict(), Dict(), Dict(), Dict(), Dict(), Dict(), Dict())
end
"""
Mesh(m::Dict)
Create new `Mesh` using data `m`. It is assumed that `m` is in format what
`abaqus_read_mesh` in `AbaqusReader.jl` is returning.
"""
function Mesh(m::Dict)
mesh = Mesh()
mesh.nodes = m["nodes"]
mesh.elements = m["elements"]
mesh.element_types = m["element_types"]
mesh.surface_sets = m["surface_sets"]
mesh.surface_types = m["surface_types"]
for (nset_name, node_ids) in m["node_sets"]
mesh.node_sets[Symbol(nset_name)] = Set(node_ids)
end
for (elset_name, element_ids) in m["element_sets"]
mesh.element_sets[Symbol(elset_name)] = Set(element_ids)
end
for (surfset_name, surfaces) in m["surface_sets"]
mesh.surface_sets[Symbol(surfset_name)] = surfaces
end
return mesh
end
function add_node!(mesh::Mesh, nid::Int, ncoords::Vector{Float64})
mesh.nodes[nid] = ncoords
end
@@ -203,3 +228,47 @@ function JuliaFEM.Problem{P<:BoundaryProblem}(mesh::Mesh, ::Type{P}, name, dimen
problem.elements = create_elements(mesh, name)
return problem
end
using AbaqusReader
function abaqus_read_mesh(fn::String)
m = AbaqusReader.abaqus_read_mesh(fn)
return Mesh(m)
end
"""
create_surface_elements(mesh::Mesh, surface_name::Symbol)
Create a set of surface elements from solid elements.
Notation follow what is defined in ABAQUS. For example, if solid elements
are Tet10, surface elements will be Tri6 and they can be used to define
boundary conditions.
"""
function create_surface_elements(mesh::Mesh, surface_name::Symbol)
elements = Element[]
for (elid, elsi) in mesh.surface_sets[surface_name]
elty = mesh.element_types[elid]
elco = mesh.elements[elid]
chel, chcon = AbaqusReader.create_surface_element(elty, elsi, elco)
ch = Element(getfield(JuliaFEM, chel), chcon)
push!(elements, ch)
end
update!(elements, "geometry", mesh.nodes)
return elements
end
"""
create_surface_elements(mesh::Mesh, surface_name::String)
Create a set of surface elements from solid elements.
Notation follow what is defined in ABAQUS. For example, if solid elements
are Tet10, surface elements will be Tri6 and they can be used to define
boundary conditions.
"""
function create_surface_elements(mesh::Mesh, surface_name::String)
return create_surface_elements(mesh, Symbol(surface_name))
end
export abaqus_read_mesh, create_surface_elements
-264
View File
@@ -1,264 +0,0 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
element_has_nodes(::Type{Val{:C3D4}}) = 4
element_has_type( ::Type{Val{:C3D4}}) = :Tet4
element_has_nodes(::Type{Val{:C3D8}}) = 8
element_has_type( ::Type{Val{:C3D8}}) = :Hex8
element_has_nodes(::Type{Val{:C3D10}}) = 10
element_has_type(::Type{Val{:C3D10}}) = :Tet10
element_has_nodes(::Type{Val{:C3D20}}) = 20
element_has_nodes(::Type{Val{:C3D20E}}) = 20
element_has_nodes(::Type{Val{:S3}}) = 3
element_has_type( ::Type{Val{:S3}}) = :Tri3
element_has_nodes(::Type{Val{:STRI65}}) = 6
element_has_type(::Type{Val{:STRI65}}) = :Tri6
element_has_nodes(::Type{Val{:CPS4}}) = 4
element_has_type(::Type{Val{:CPS4}}) = :Quad4
"""
Checks for if line is a comment line or just empty
"""
function empty_or_comment_line{T<:AbstractString}(line::T)
startswith(line, "**") || (length(line) == 0)
end
"""
Function for parsing nodes from the file
"""
function parse_section(model, lines, key::Symbol, idx_start,
idx_end, ::Type{Val{:NODE}})
info("Parsing *NODE block between lines $idx_start .. $idx_end")
nnodes = 0
ids = Integer[]
definition = lines[idx_start]
debug("Definition line = $definition")
for line in lines[idx_start + 1: idx_end]
if !(empty_or_comment_line(line))
m = matchall(r"[-0-9.eE+]+", line)
node_id = parse(Int, m[1])
coords = float(m[2:end])
model["nodes"][node_id] = coords
nnodes += 1
end
end
info("$nnodes nodes found")
has_set_def = match(r"NSET=([\w\_\-]+)", definition)
if has_set_def != nothing
set_name = has_set_def[1]
info("Creating node set $set_name")
model["nsets"][set_name] = ids
end
end
"""
Custon regex to find match from string. Index used if there are multiple matches
"""
function regex_match(regex_str, line, idx)
return match(regex_str, line).captures[idx]
println(eltype_sym)
end
"""
Simple iterator for comsuming element list. Depending
on the used element, connectivity nodes might be listed
in multiple lines, which is why iterator is used to handle
this problem.
"""
function consumeList(arr, start, stop)
function _it()
for i=start:stop
produce(arr[i])
end
end
Task(_it)
end
"""
Parse elements from input.
"""
function parse_section(model, lines, key, idx_start, idx_end, ::Type{Val{:ELEMENT}})
#definition = uppercase(lines[idx_start])
definition = lines[idx_start]
element_type = regex_match(r"TYPE=([\w\-\_]+)", definition, 1)
eltype_sym = Symbol(element_type)
eltype_nodes = element_has_nodes(Val{eltype_sym})
element_type = element_has_type(Val{eltype_sym})
info("Parsing elements. Type: $(element_type)")
list_iterator = consumeList(lines, idx_start+1, idx_end)
ids = Integer[]
line = consume(list_iterator)
while line != nothing
arr_num_as_str = matchall(r"[0-9]+", line)
numbers = map(x-> parse(Int, x), arr_num_as_str)
if !(empty_or_comment_line(line))
id = numbers[1]
push!(ids, id)
connectivity = numbers[2:end]
while length(connectivity) != eltype_nodes
@assert length(connectivity) < eltype_nodes
line = consume(list_iterator)
arr_num_as_str = matchall(r"[0-9]+", line)
numbers = map(x-> parse(Int, x), arr_num_as_str)
push!(connectivity, numbers...)
end
model["elements"][id] = Dict(("type"=>element_type),
("connectivity"=>connectivity))
end
line = consume(list_iterator)
end
has_set_def = match(r"ELSET=([\w\_\-]+)", definition)
if has_set_def != nothing
set_name = has_set_def[1]
info("Creating elset $set_name")
model["elsets"][set_name] = ids
end
end
"""
Parsing Node- and ElementSets.
"""
function parse_section(model, lines, key, idx_start, idx_end, ::Union{Type{Val{:NSET}}, Type{Val{:ELSET}}})
set_regex_string = Dict(:NSET => r"NSET=([\w\-\_]+)",
:ELSET => r"ELSET=([\w\-\_]+)" )
#definition = uppercase(lines[idx_start])
# FIXME: do not uppercase set names
definition = lines[idx_start]
regex_string = set_regex_string[key]
set_name = regex_match(regex_string, definition, 1)
info("Creating $(lowercase(string(key))) $set_name")
data = Integer[]
if endswith(strip(definition), "GENERATE")
line = lines[idx_start + 1]
m = matchall(r"[0-9]+", line)
first_id, last_id, step = map(x-> parse(Int, x), m)
set_ids = collect(first_id:step:last_id)
push!(data, set_ids...)
else
for line in lines[idx_start + 1: idx_end]
if !(empty_or_comment_line(line))
m = matchall(r"[0-9]+", line)
set_ids = map(s -> parse(Int, s), m)
try
push!(data, set_ids...)
catch err
if isa(err, MethodError)
warn("Problems with element set creation")
end
end
end
end
end
selected_set = key == :NSET ? "nsets" : "elsets"
model[selected_set][set_name] = data
end
"""
Parse SURFACE keyword
"""
function parse_section(model, lines, key, idx_start, idx_end, ::Type{Val{:SURFACE}})
debug("Parsing surface")
#definition = uppercase(lines[idx_start])
definition = lines[idx_start]
#has_set_def = match(r"TYPE=([\w\_\-]+),.*NAME=([\w\_\-]+)", definition)
has_set_def = Dict(map(y -> lowercase(strip(y[1])) => strip(y[2]), map(x -> split(x, "="), matchall(r"([\w\_\-]+[ ]*=[ ]*[\w\_\-]+)", definition))))
has_set_def != nothing || return
debug(has_set_def)
set_type = Symbol(get(has_set_def, "type", "UNKNOWN"))
set_name = Symbol(has_set_def["name"])
data = Vector{Tuple{Int64, Symbol}}()
for line in lines[idx_start + 1: idx_end]
empty_or_comment_line(line) && continue
m = match(r"(?P<element_id>\d+),.*(?P<element_side>S\d+).*", line)
if isa(m, Void)
warn("read_abaqus, parsing surface: line $line")
continue
end
element_id = parse(Int, m[:element_id])
element_side = Symbol(m[:element_side])
push!(data, (element_id, element_side))
end
model["surface_types"][set_name] = set_type
model["surfaces"][set_name] = data
return
end
"""
Find lines, which contain keywords, for example "*NODE"
"""
function find_keywords(lines)
indexes = Integer[]
for (idx, line) in enumerate(lines)
if startswith(line, "*") && !startswith(line, "**")
push!(indexes, idx)
end
end
return indexes
end
"""
Main function for parsing Abaqus input file.
"""
function parse_abaqus(fid::IOStream)
lines = readlines(fid)
keyword_indexes = find_keywords(lines)
nkeyword_indexes = length(keyword_indexes)
debug("$nkeyword_indexes keyword indexes found: $keyword_indexes")
push!(keyword_indexes, length(lines)+1)
idx_start = keyword_indexes[1]
keyword_sym::Symbol = :none
parser::Function = x->()
model = Dict{AbstractString, Any}()
model["nodes"] = Dict{Int64, Vector{Float64}}()
model["nsets"] = Dict{AbstractString, Vector{Int64}}()
model["elsets"] = Dict{AbstractString, Vector{Int64}}()
model["elements"] = Dict{Integer, Any}()
model["surfaces"] = Dict{Symbol, Vector{Tuple{Int64, Symbol}}}()
model["surface_types"] = Dict{Symbol, Symbol}()
for idx_end in keyword_indexes[2:end]
keyword_line = strip(uppercase(lines[idx_start]))
keyword = strip(regex_match(r"\s*([\w ]+)", keyword_line, 1))
k_sym = Symbol(keyword)
args = Tuple{Dict, Vector{Int}, Symbol, Int, Int, Type{Val{k_sym}}}
if method_exists(parse_section, args)
parse_section(model, lines, k_sym, idx_start, idx_end-1, Val{k_sym})
else
debug("Unknown section: '$(keyword)'")
debug("keyword_line = '$keyword_line'")
debug("idx_start = $idx_start, idx_end = $idx_end")
end
idx_start = idx_end
end
return model
end
function abaqus_read_mesh(fn)
model = open(parse_abaqus, fn)
mesh = Mesh()
mesh.nodes = model["nodes"]
for (nset_name, node_ids) in model["nsets"]
mesh.node_sets[Symbol(nset_name)] = Set(node_ids)
end
for (elid, eldata) in model["elements"]
eltype = eldata["type"]
elcon = eldata["connectivity"]
mesh.elements[elid] = elcon
mesh.element_types[elid] = eltype
end
for (elset_name, element_ids) in model["elsets"]
mesh.element_sets[Symbol(elset_name)] = Set(element_ids)
end
mesh.surface_sets = model["surfaces"]
mesh.surface_types = model["surface_types"]
return mesh
end
-95
View File
@@ -1,95 +0,0 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
using JuliaFEM
using JuliaFEM.Preprocess
using JuliaFEM.Postprocess
using JuliaFEM.Abaqus
using JuliaFEM.Testing
using DataFrames
# to turn on automatic file download, set
# ENV["ABAQUS_DOWNLOAD_URL"] = "http://<domain>:2080/v2016/books/eif"
# if don't want to download all stuff to current directory, set also
# ENV["ABAQUS_DOWNLOAD_DIR"] = "/tmp"
""" Run test, return true if simulation is succesfull, i.e. no errors raise
during parsing .inp file or execution of model. This doesn't mean that results
are meaningful; they must be checked in separately. Running model only verifies
that no catastrophic failures happen during file parsing. """
function abaqus_run_test(name)
return_code = abaqus_run_model(name; fetch=true, verbose=true)
return_code == 0 && return true
return false
end
@testset "JuliaFEM-ABAQUS interface" begin
@testset "1 Element Verification" begin
@testset "1.2 Eigenvalue tests" begin
@testset "1.2.1 Eigenvalue extraction for single unconstrained elements" begin
@testset "Acoustic elements" begin
@testset "AC1D2 elements." begin
# abaqus_run_test("ec12afe1") || return
end
end
@testset "Three-dimensional continuum elements" begin
@testset "C3D10 elements." begin
# abaqus_run_test("ec3asfe1") || return
end
end
end
end
@testset "1.3 Simple load tests" begin
@testset "1.3.1 Membrane loading of plane stress, plane strain, membrane, and shell elements" begin
@testset "CPS4 elements." begin
# abaqus_run_test("ecs4sfs1") || return
end
end
@testset "1.3.3 Three-dimensional solid elements" begin
@testset "C3D8 elements." begin
abaqus_run_test("ec38sfs2") || return
#res = abaqus_open_results("ec38sfs2")
node_output1 = wsv"""
NODE U1 U2 U3 COOR1 COOR2 COOR3
1 -2.0000E-33 -2.0000E-33 -2.0000E-33 0.000 0.000 0.000
2 -2.6667E-05 -1.0000E-33 -1.7333E-04 2.000 0.000 0.000
3 -2.0000E-04 -2.6667E-05 -1.7333E-04 2.000 2.000 0.000
4 -1.7333E-04 -2.6667E-05 -1.0000E-33 0.000 2.000 0.000
5 -3.6777E-48 -8.6667E-05 -1.3333E-05 0.000 0.000 1.000
6 -2.6667E-05 -8.6667E-05 -1.8667E-04 2.000 0.000 1.000
7 -2.0000E-04 -1.1333E-04 -1.8667E-04 2.000 2.000 1.000
8 -1.7333E-04 -1.1333E-04 -1.3333E-05 0.000 2.000 1.000
"""
node_output_2 = wsv"""
NODE RF1 RF2 RF3 CF1 CF2 CF3
1 1500.000 1500.000 1000.000 0.000 0.000 0.000
2 0.000 500.000 0.000 1500.000 0.000 0.000
3 0.000 0.000 0.000 500.000 500.000 -1000.000
4 0.000 0.000 0.000 500.000 1500.000 0.000
5 -500.000 0.000 0.000 0.000 -500.000 1000.000
6 0.000 0.000 0.000 -500.000 -1500.000 0.000
7 0.000 0.000 0.000 -1500.000 -1500.000 -1000.000
8 0.000 0.000 0.000 -1500.000 -500.000 0.000
"""
#= to check also results:
side, opts = read_result(xdmf, "SECTION/side")
@test isapprox(side["SOFM"], 3464.0)
@test isapprox(side["SOF1"], 2000.0)
@test isapprox(side["SOF2"], 2000.0)
@test isapprox(side["SOF3"], 2000.0)
@test isapprox(side["SOMM"], 2828.0)
@test isapprox(side["SOM1"], 0.0)
@test isapprox(side["SOM2"], 2000.0)
@test isapprox(side["SOM3"], -2000.0)
@test isapprox(side["SOAREA"], 2.000)
@test isapprox(side["SOCF1"], 2/3)
@test isapprox(side["SOCF2"], 2/3)
@test isapprox(side["SOCF3"], 1/6)
=#
end
end
end
end
end
-110
View File
@@ -1,110 +0,0 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
using JuliaFEM
using JuliaFEM.Preprocess
using JuliaFEM.Abaqus
using JuliaFEM.Testing
using JuliaFEM.Preprocess: element_has_nodes, element_has_type
@testset "read inp file" begin
model = open(parse_abaqus, joinpath(Pkg.dir("JuliaFEM"),"geometry","3d_beam","palkki.inp"))
@test length(model["nodes"]) == 298
@test length(model["elements"]) == 120
@test length(model["elsets"]["Body1"]) == 120
@test length(model["nsets"]["SUPPORT"]) == 9
@test length(model["nsets"]["LOAD"]) == 9
@test length(model["nsets"]["TOP"]) == 83
end
@testset "test read element section" begin
data = """*ELEMENT, TYPE=C3D10, ELSET=BEAM
1, 243, 240, 191, 117, 245, 242, 244,
1, 2, 196
2, 204, 199, 175, 130, 207, 208, 209,
3, 4, 176
"""
data = split(data, "\n")
model = Dict{AbstractString, Any}()
model["nsets"] = Dict{AbstractString, Vector{Int}}()
model["elsets"] = Dict{AbstractString, Vector{Int}}()
model["elements"] = Dict{Integer, Any}()
parse_section(model, data, :ELEMENT, 1, 5, Val{:ELEMENT})
@test length(model["elements"]) == 2
@test model["elements"][1]["connectivity"] == [243, 240, 191, 117, 245, 242, 244, 1, 2, 196]
@test model["elements"][2]["connectivity"] == [204, 199, 175, 130, 207, 208, 209, 3, 4, 176]
@test model["elsets"]["BEAM"] == [1, 2]
end
@testset "parse nodes from abaqus .inp file to Mesh" begin
fn = joinpath(Pkg.dir("JuliaFEM"), "test", "testdata","cube_tet4.inp")
mesh = abaqus_read_mesh(fn)
info(mesh.surface_sets)
@test length(mesh.nodes) == 10
@test length(mesh.elements) == 17
@test haskey(mesh.elements, 1)
@test mesh.elements[1] == [8, 10, 1, 2]
@test mesh.element_types[1] == :Tet4
@test haskey(mesh.node_sets, :SYM12)
@test haskey(mesh.element_sets, :CUBE)
@test haskey(mesh.surface_sets, :LOAD)
@test haskey(mesh.surface_sets, :ORDER)
@test length(mesh.surface_sets[:LOAD]) == 2
@test mesh.surface_sets[:LOAD][1] == (16, :S1)
@test mesh.surface_types[:LOAD] == :ELEMENT
@test length(Set(map(size, values(mesh.nodes)))) == 1
elements = create_surface_elements(mesh,:LOAD)
@test get_connectivity(elements[1]) == [8,10,9]
end
@testset "parse nodes from abaqus .inp file to Mesh (NX export)" begin
fn = joinpath(Pkg.dir("JuliaFEM"), "test", "testdata","nx_export_problem.inp")
mesh = abaqus_read_mesh(fn)
@test length(mesh.nodes) == 3
end
@testset "parse abaqus .inp created using hypermesh" begin
data = """
**
** ABAQUS Input Deck Generated by HyperMesh Version : 14.0.120.28
** Generated using HyperMesh-Abaqus Template Version : 14.0.120
**
** Template: ABAQUS/STANDARD 3D
**
*NODE , Nset = nset_csys0
1, 2.649428 , -21.93735 , 217.2934
2, 27.54531 , 1.108443 , 228.8077
"""
fn = tempname() * ".inp"
open(fn, "w") do fid write(fid, data) end
mesh = abaqus_read_mesh(fn)
@test length(mesh.nodes) == 2
end
@testset "Elemement types and nodes" begin
@test element_has_nodes(Val{:C3D4}) == 4
@test element_has_type(Val{:C3D4}) == :Tet4
@test element_has_nodes(Val{:C3D8}) == 8
@test element_has_type(Val{:C3D8}) == :Hex8
@test element_has_nodes(Val{:C3D10}) == 10
@test element_has_type(Val{:C3D10}) == :Tet10
@test element_has_nodes(Val{:C3D20}) == 20
@test element_has_nodes(Val{:C3D20E}) == 20
@test element_has_nodes(Val{:S3}) == 3
@test element_has_type(Val{:S3}) == :Tri3
@test element_has_nodes(Val{:STRI65}) == 6
@test element_has_type(Val{:STRI65}) == :Tri6
@test element_has_nodes(Val{:CPS4}) == 4
@test element_has_type(Val{:CPS4}) == :Quad4
end
@testset "GENERATE keyword" begin
data = """
*NSET, NSET=testgen, GENERATE
7,13,2
"""
fn = tempname() * ".inp"
open(fn, "w") do fid write(fid, data) end
mesh = abaqus_read_mesh(fn)
@test mesh.node_sets[:testgen] == Set([7,9,13,11])
end
-1
View File
@@ -5,7 +5,6 @@ using JuliaFEM
using JuliaFEM.Preprocess
using JuliaFEM.Postprocess
using JuliaFEM.Testing
using JuliaFEM.Abaqus: create_surface_elements
tet4_meshfile = "test_problems_contact_3d/tet4.inp"
tet10_meshfile = "test_problems_contact_3d/tet10.inp"
-1
View File
@@ -5,7 +5,6 @@ using JuliaFEM
using JuliaFEM.Preprocess
using JuliaFEM.Postprocess
using JuliaFEM.Testing
using JuliaFEM.Abaqus: create_surface_elements
### temperature patch tests, sl tet4, dl tet4, sl tet10, dl tet 10
-1
View File
@@ -5,7 +5,6 @@ using JuliaFEM
using JuliaFEM.Preprocess
using JuliaFEM.Postprocess
using JuliaFEM.Testing
using JuliaFEM.Abaqus: create_surface_elements
@testset "forget to add elements to problem" begin
X = Dict(
-48
View File
@@ -1,48 +0,0 @@
# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/JuliaFEM.jl/blob/master/LICENSE.md
using JuliaFEM
using JuliaFEM.Preprocess
using JuliaFEM.Postprocess
using JuliaFEM.Abaqus
using JuliaFEM.Testing
@testset "parse abaqus inp file to AbaqusModel" begin
fn = Pkg.dir("JuliaFEM") * "/test/testdata/cube_tet4.inp"
model = abaqus_read_model(fn)
@test length(model.properties) == 1
section = first(model.properties)
@test section.element_set == :CUBE
@test section.material_name == :MAT
@test haskey(model.materials, :MAT)
material = model.materials[:MAT]
@test isapprox(first(material.properties).E, 208.0e3)
@test length(model.steps) == 1
step = first(model.steps)
@test length(step.boundary_conditions) == 2
bc = step.boundary_conditions[1]
@test bc.data[1] == [:SYM12, 3]
@test bc.data[2] == [:SYM23, 1]
@test bc.data[3] == [:SYM13, 2]
load = step.boundary_conditions[2]
@test load.data[1] == [:LOAD, :P, 1.00000]
end
#=
@testset "given abaqus model solve field" begin
fn = Pkg.dir("JuliaFEM") * "/test/testdata/cube_tet4.inp"
model = abaqus_read_model(fn)
problems = model()
body = first(problems)
info(body("displacement", 0.0))
result = XDMF()
xdmf_new_result!(result, body, 0.0)
xdmf_save_field!(result, body, 0.0, "displacement"; field_type="Vector")
xdmf_save!(result, "/tmp/cube_tet4.xmf")
end
=#
-1
View File
@@ -5,7 +5,6 @@ using JuliaFEM
using JuliaFEM.Preprocess
using JuliaFEM.Postprocess
using JuliaFEM.Testing
using JuliaFEM.Abaqus: create_surface_elements
datadir = first(splitext(basename(@__FILE__)))