Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add preparser to julia native #48

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ version = "0.2.0"

[deps]
Ferrite = "c061ca5d-56c9-439f-9c0e-210fe06d3992"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"

[compat]
Aqua = "0.6"
Ferrite = "0.3, 1"
OrderedCollections = "1"
julia = "1"

[extras]
Expand Down
3 changes: 3 additions & 0 deletions src/FerriteMeshParser.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ using Ferrite:
Ferrite, Grid, Node, Vec,
getcells, getnodes, getcoordinates, getncells

using OrderedCollections:
LittleDict

# Convenience when debugging
const DEBUG_PARSE = false

Expand Down
250 changes: 146 additions & 104 deletions src/abaqusreader.jl
Original file line number Diff line number Diff line change
@@ -1,76 +1,135 @@
isabaquskeyword(l) = startswith(l, "*")

function join_multiline_elementdata(element_data::Vector{<:AbstractString})
fixed_element_data = copy(element_data)
i = 0
i_fixed = 0
nrows = length(element_data)
while i < nrows
i += 1
length(element_data[i])==0 && continue
i_fixed += 1
fixed_element_data[i_fixed] = element_data[i]
while endswith(fixed_element_data[i_fixed], ',') && i < nrows
i += 1
fixed_element_data[i_fixed] = fixed_element_data[i_fixed]*element_data[i]
struct AbaqusInpBlock
keyword::String
parameters::LittleDict{String}
data::Vector{Vector}
end

struct AbaqusInp
blocks::Vector{AbaqusInpBlock}
end
Comment on lines +9 to +11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this wrapper?
Seems better to me to store as OrderedDict{String, AbaqusInpBlock} or Vector{Pair{String, AbaqusInpBlock}} to allow iterating over the keys.


inpblocks(inp::AbaqusInp) = inp.blocks
keyword(datablock::AbaqusInpBlock) = datablock.keyword
datalines(datablock::AbaqusInpBlock) = datablock.data
parameters(datablock::AbaqusInpBlock) = datablock.parameters

function parse_abaqus(s)
parsed = tryparse(Int, s)
!isnothing(parsed) && return parsed
parsed = tryparse(Float64, s)
!isnothing(parsed) && return parsed
startswith(s, '"') && endswith(s, '"') && return s[2:end-1]
return s
end

# skips comment lines and supports line continuation
# removes leading and trailing whitespace
function eatline_abaqus(f)
line = readline(f)
while startswith(line, "**")
line = strip(readline(f))
isodd(count('"', line)) && throw(InvalidFileContent("Quoted strings cannot span multiple lines!"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose a single quotation mark is allowed inside a comment so we shouldn't error here?

end
while endswith(line, ",")
eof(f) && throw(InvalidFileContent("Reached end of file on line continuation!"))
next = strip(readline(f))
isodd(count('"', line)) && throw(InvalidFileContent("Quoted strings cannot span multiple lines!"))
startswith(next, "**") && continue
startswith(next, '*') && throw(InvalidFileContent("Ran into new keyword during line continuation"))
line *= next
end
DEBUG_PARSE && println("Ate: " * line)
return line
end

# removes whitespace, splits at comma, and uppercases everything
# while reserving quoted strings
function clean_and_split(line)
quoted_split = split(line, '"') # even indices -> quoted; odd inices not quoted
sections = String[""]
for (i, s) in pairs(quoted_split)
if isodd(i)
s = replace(s, " " => "")
s = uppercase(s)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if this is not taken into account for Abaqus, we should not enforce that all is upper-cased. This isn't nice for users since they keys in the sets don't match their expectations.

s_split = split(s, ',', keepempty=true)
sections[end] *= first(s_split)
append!(sections, s_split[2:end])
else
sections[end] *= '"' * s * '"'
end
end
return sections
end

function parsekeywordline(line)
sections = clean_and_split(line)
parameters = LittleDict{String, Any}()
for parameter in sections[2:end]
if contains(parameter, '=')
(key, value) = split(parameter, '=')
parameters[key] = parse_abaqus(value)
else
parameters[parameter] = nothing
end
end
return fixed_element_data[1:i_fixed]
return AbaqusInpBlock(sections[1], parameters, Vector[])
end

function parsedataline(line)
sections = clean_and_split(line)
return parse_abaqus.(sections)
end

function read_abaqus_nodes!(f, node_numbers::Vector{Int}, coord_vec::Vector{Float64})
local coords
node_data = readlinesuntil(f; stopsign='*')
for nodeline in node_data
node = split(nodeline, ',', keepempty = false)
length(node) == 0 && continue
push!(node_numbers, parse(Int, node[1]))
coords = parse.(Float64, node[2:end])
append!(coord_vec, coords)
function parse_abaqus_inp(filename)
keywords = AbaqusInpBlock[]
open(filename) do f
while !eof(f)
line = eatline_abaqus(f)
line == "" && continue
if isabaquskeyword(line)
push!(keywords, parsekeywordline(line))
else
isempty(keywords) && throw(InvalidFileContent("The first non-comment line must be a keyword line!"))
push!(keywords[end].data, parsedataline(line))
end
end
end
return length(coords)
return AbaqusInp(keywords)
end

function read_abaqus_elements!(f, topology_vectors, element_number_vectors, element_type::AbstractString, element_set="", element_sets=nothing)
if !haskey(topology_vectors, element_type)
topology_vectors[element_type] = Int[]
element_number_vectors[element_type] = Int[]
function read_abaqus_nodes!(inpblock::AbaqusInpBlock, node_numbers, coord_vec)
for dataline in datalines(inpblock)
push!(node_numbers, dataline[1])
append!(coord_vec, dataline[2:end])
end
topology_vec = topology_vectors[element_type]
element_numbers = element_number_vectors[element_type]
element_numbers_new = Int[]
element_data_raw = readlinesuntil(f; stopsign='*')
element_data = join_multiline_elementdata(element_data_raw)
for elementline in element_data
element = split(elementline, ',', keepempty = false)
length(element) == 0 && continue
n = parse(Int, element[1])
push!(element_numbers_new, n)
vertices = [parse(Int, element[i]) for i in 2:length(element)]
append!(topology_vec, vertices)
return length(datalines(inpblock)[1]) - 1
end

function read_abaqus_elements!(inpblock::AbaqusInpBlock, topology_vectors, element_number_vectors, element_sets)
topology_vec = get!(topology_vectors, parameters(inpblock)["TYPE"], Int[])
element_numbers = get!(element_number_vectors, parameters(inpblock)["TYPE"], Int[])
element_numers_new = Int[]
for dataline in datalines(inpblock)
push!(element_numers_new, dataline[1])
append!(topology_vec, dataline[2:end])
end
append!(element_numbers, element_numbers_new)
if element_set != ""
element_sets[element_set] = copy(element_numbers_new)
append!(element_numbers, element_numers_new)
elset = get(parameters(inpblock), "ELSET", nothing)
if !isnothing(elset)
element_sets[elset] = element_numers_new
end
end

function read_abaqus_set!(f, sets, setname::AbstractString)
if endswith(setname, "generate")
split_line = split(strip(eat_line(f)), ",", keepempty = false)
start, stop, step = [parse(Int, x) for x in split_line]
function read_abaqus_set!(inpblock::AbaqusInpBlock, sets)
if haskey(parameters(inpblock), "GENERATE")
start, stop, step = only(datalines(inpblock))
indices = collect(start:step:stop)
setname = split(setname, [','])[1]
else
data = readlinesuntil(f; stopsign='*')
indices = Int[]
for line in data
indices_str = split(line, ',', keepempty = false)
for v in indices_str
push!(indices, parse(Int, v))
end
end
indices = reduce(vcat, datalines(inpblock))
end
setname = parameters(inpblock)[keyword(inpblock)[2:end]]
sets[setname] = indices
end

Expand All @@ -87,64 +146,47 @@ function read_mesh(filename, ::AbaqusMeshFormat)
part_counter = 0
instance_counter = 0

open(filename) do f
while !eof(f)
header = eat_line(f)
if header == ""
continue
end
DEBUG_PARSE && println("H: $header")
if startswith(lowercase(header), "*node")
DEBUG_PARSE && println("Reading nodes")
read_dim = read_abaqus_nodes!(f, node_numbers, coord_vec)
dim == 0 && (dim = read_dim) # Set dim if not yet set
read_dim != dim && throw(DimensionMismatch("Not allowed to mix nodes in different dimensions"))
elseif startswith(lowercase(header), "*element")
if ((m = match(r"\*Element, type=(.*), ELSET=(.*)"i, header)) !== nothing)
DEBUG_PARSE && println("Reading elements with elset")
read_abaqus_elements!(f, topology_vectors, element_number_vectors, m.captures[1], m.captures[2], elementsets)
elseif ((m = match(r"\*Element, type=(.*)"i, header)) !== nothing)
DEBUG_PARSE && println("Reading elements without elset")
read_abaqus_elements!(f, topology_vectors, element_number_vectors, m.captures[1])
end
elseif ((m = match(r"\*Elset, elset=(.*)"i, header)) !== nothing)
DEBUG_PARSE && println("Reading elementset")
read_abaqus_set!(f, elementsets, m.captures[1])
elseif ((m = match(r"\*Nset, nset=(.*)"i, header)) !== nothing)
DEBUG_PARSE && println("Reading nodeset")
read_abaqus_set!(f, nodesets, m.captures[1])
elseif startswith(lowercase(header), "*part")
DEBUG_PARSE && println("Increment part counter")
part_counter += 1
elseif startswith(lowercase(header), "*instance")
DEBUG_PARSE && println("Increment instance counter")
instance_counter += 1
discardlinesuntil(f, stopsign='*') # Instances contain translations, or start with *Node if independent mesh
elseif isabaquskeyword(header) # Ignore unused keywords
DEBUG_PARSE && println("Discarding keyword content")
discardlinesuntil(f, stopsign='*')
else
eof(f) && break # discardlinesuntil will stop at eof, and last line read again and incorrectly considered a "header"
throw(InvalidFileContent("Unknown header, \"$header\", in file \"$filename\". Could also indicate an incomplete file"))
end
end
inp = parse_abaqus_inp(filename)

if part_counter > 1 || instance_counter > 1
msg = "Multiple parts or instances are not supported\n"
msg *= "Tip: If you want a single grid, merge parts and differentiated by Abaqus sets\n"
msg *= " If you want multiple grids, split into multiple input files"
throw(InvalidFileContent(msg))
for inpblock in inpblocks(inp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be nice if inpblocks(inp) was an ordered dict,
for (keyword, block) in ... would be nicer.

if keyword(inpblock) == "*NODE"
DEBUG_PARSE && println("Reading nodes")
read_dim = read_abaqus_nodes!(inpblock, node_numbers, coord_vec)
dim == 0 && (dim = read_dim) # Set dim if not yet set
read_dim != dim && throw(DimensionMismatch("Not allowed to mix nodes in different dimensions"))
elseif keyword(inpblock) == "*ELEMENT"
DEBUG_PARSE && println("Reading elements")
read_abaqus_elements!(inpblock, topology_vectors, element_number_vectors, elementsets)
elseif keyword(inpblock) == "*ELSET"
DEBUG_PARSE && println("Reading elementset")
read_abaqus_set!(inpblock, elementsets)
elseif keyword(inpblock) == "*NSET"
DEBUG_PARSE && println("Reading nodeset")
read_abaqus_set!(inpblock, nodesets)
elseif keyword(inpblock) == "*PART"
DEBUG_PARSE && println("Increment part counter")
part_counter += 1
elseif keyword(inpblock) == "*INSTANCE"
DEBUG_PARSE && println("Increment instance counter")
instance_counter += 1
end
end

if part_counter > 1 || instance_counter > 1
msg = "Multiple parts or instances are not supported\n"
msg *= "Tip: If you want a single grid, merge parts and differentiated by Abaqus sets\n"
msg *= " If you want multiple grids, split into multiple input files"
throw(InvalidFileContent(msg))
end

elements = Dict{String, RawElements}()
for element_type in keys(topology_vectors)
topology_vec = topology_vectors[element_type]
element_numbers = element_number_vectors[element_type]
n_elements = length(element_numbers)
topology_matrix = reshape(topology_vec, length(topology_vec) ÷ n_elements, n_elements)
topology_matrix = reshape(topology_vec, :, n_elements)
elements[element_type] = RawElements(numbers=element_numbers, topology=topology_matrix)
end
nodes = RawNodes(numbers=node_numbers, coordinates=reshape(coord_vec, dim, length(coord_vec) ÷ dim))
nodes = RawNodes(numbers=node_numbers, coordinates=reshape(coord_vec, dim, :))
return RawMesh(elements=elements, nodes=nodes, nodesets=nodesets, elementsets=elementsets)
end
14 changes: 7 additions & 7 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ end
@testset "facet set generation" begin
filename = gettestfile("compact_tension.inp")
grid = get_ferrite_grid(filename)
face_set = create_facetset(grid, getnodeset(grid, "Hole"))
@test getfacetset(grid, "Hole") == face_set
@test face_set == create_facetset(grid, getnodeset(grid, "Hole"), getcellset(grid, "Hole")) # Test that including cells doesn't change the created sets
face_set = create_facetset(grid, getnodeset(grid, "HOLE"))
@test getfacetset(grid, "HOLE") == face_set
@test face_set == create_facetset(grid, getnodeset(grid, "HOLE"), getcellset(grid, "HOLE")) # Test that including cells doesn't change the created sets
end

@testset "exceptions" begin
Expand All @@ -100,7 +100,7 @@ end
@test contains(String(take!(io)), test_string)

grid = get_ferrite_grid(gettestfile("compact_tension.inp"))
nset = getnodeset(grid, "Hole")
nset = getnodeset(grid, "HOLE")
@test_throws ErrorException("create_faceset is no longer supported, use create_facetset instead") create_faceset(grid, nset)
end

Expand Down Expand Up @@ -131,12 +131,12 @@ end
@testset "sets" begin
filename = gettestfile("generated_set.inp")
grid = get_ferrite_grid(filename)
@test getcellset(grid, "lower") == Set(1:8)
@test getnodeset(grid, "lower") == Set((1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 20, 21, 22))
@test getcellset(grid, "LOWER") == Set(1:8)
@test getnodeset(grid, "LOWER") == Set((1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 20, 21, 22))

filename = gettestfile("2D_UnitArea_Quadratic.inp")
grid = get_ferrite_grid(filename)
@test getcellset(grid, "mysetname") == Set((1,))
@test getcellset(grid, "MYSETNAME") == Set((1,))
end


Expand Down