Skip to content

Commit 253625f

Browse files
authored
Merge pull request #236 from kaipartmann/custom_export
Custom export fields
2 parents 5d5ccf0 + b3fedd0 commit 253625f

25 files changed

+1128
-72
lines changed

src/Peridynamics.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ abstract type AbstractBondSystemMaterial{Correction} <: AbstractMaterial end
7979
abstract type AbstractBondBasedMaterial{CM} <: AbstractBondSystemMaterial{CM} end
8080
abstract type AbstractCorrespondenceMaterial{CM,ZEM} <: AbstractBondSystemMaterial{ZEM} end
8181
abstract type AbstractRKCMaterial{CM,C} <: AbstractBondSystemMaterial{C} end
82-
abstract type AbstractBondAssociatedSystemMaterial <: AbstractMaterial end
82+
abstract type AbstractBondAssociatedSystemMaterial <: AbstractBondSystemMaterial{Nothing} end
8383
abstract type AbstractConstitutiveModel end
8484
abstract type AbstractStressIntegration end
8585
abstract type AbstractZEMStabilization <: AbstractCorrection end

src/auxiliary/io.jl

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,11 @@ end
102102
function check_export_fields(::Type{S}, fields::Vector{Symbol}) where {S}
103103
allowed_fields = point_data_fields(S)
104104
for f in fields
105-
if !in(f, allowed_fields)
105+
if !in(f, allowed_fields) && !custom_field(f)
106106
msg = "unknown point data field `:$(f)` specified for export!\n"
107-
msg *= "All point data fields of $S:\n"
107+
msg *= "If you intend to export a custom field, please define a method:"
108+
msg *= "\n `custom_field(::Val{:$(f)}) = true`\n"
109+
msg *= "Otherwise, see here all available point data fields of $S:\n"
108110
for allowed_name in allowed_fields
109111
msg *= " - $allowed_name\n"
110112
end
@@ -114,6 +116,11 @@ function check_export_fields(::Type{S}, fields::Vector{Symbol}) where {S}
114116
return nothing
115117
end
116118

119+
custom_field(field::Symbol) = custom_field(Val(field))
120+
# this function can be specialized to indicate custom fields
121+
# if this is not done, the export_field function will error out!
122+
custom_field(::Val{field}) where {field} = false
123+
117124
function get_vtk_filebase(body::AbstractBody, root::AbstractString)
118125
body_name = replace(string(get_name(body)), " " => "_")
119126
filebase = isempty(body_name) ? "timestep" : body_name * "_timestep"
@@ -134,7 +141,7 @@ function _export_results(options::AbstractJobOptions, chunk::AbstractBodyChunk,
134141
filename = get_filename(options, chunk.body_name, n)
135142
position = get_loc_position(chunk)
136143
pvtk_grid(filename, position, chunk.cells; part=chunk_id, nparts=n_chunks) do vtk
137-
export_fields!(vtk, chunk, options.fields)
144+
export_fields!(vtk, chunk, options.fields, t)
138145
vtk["time", VTKFieldData()] = t
139146
end
140147
return nothing
@@ -152,20 +159,41 @@ end
152159
return @sprintf("%s_%d", vtk_filebase[body_name], n)
153160
end
154161

155-
function export_fields!(vtk, chunk, fields::Vector{Symbol})
162+
function export_fields!(vtk, chunk, fields::Vector{Symbol}, t)
163+
(; mat, system, storage, paramsetup) = chunk
156164
for field in fields
157-
point_data = get_loc_point_data(chunk.storage, chunk.system, field)
165+
point_data = export_field(Val(field), mat, system, storage, paramsetup, t)
158166
vtk[string(field), VTKPointData()] = point_data
159167
end
160168
return nothing
161169
end
162170

163-
function export_fields!(vtk, chunk, fields_spec::Dict{Symbol,Vector{Symbol}})
171+
function export_fields!(vtk, chunk, fields_spec::Dict{Symbol,Vector{Symbol}}, t)
164172
fields = fields_spec[chunk.body_name]
165-
export_fields!(vtk, chunk, fields)
173+
export_fields!(vtk, chunk, fields, t)
166174
return nothing
167175
end
168176

177+
# this function can be specialized for each field, even custom export fields can be written!
178+
function export_field(::Val{field}, mat, system, storage, paramsetup, t) where {field}
179+
return get_loc_point_data(storage, system, field)
180+
end
181+
169182
@inline function get_loc_position(chunk::AbstractBodyChunk)
170183
return @views chunk.storage.position[:, 1:get_n_loc_points(chunk)]
171184
end
185+
186+
function msg_export_fields(fields::Vector{Symbol}; indentation=2)
187+
return msg_vec("exported fields", fields; continuation_label=" "^15, indentation)
188+
end
189+
190+
function msg_export_fields(fields_spec::Dict{Symbol,Vector{Symbol}}; indentation=2)
191+
msg = " "^indentation * "EXPORTED FIELDS PER BODY\n"
192+
for (body_name, fields) in fields_spec
193+
body_name_str = string(body_name)
194+
n = length(body_name_str)
195+
ind = indentation + 2
196+
msg *= msg_vec(body_name_str, fields; continuation_label=" "^n, indentation=ind)
197+
end
198+
return msg
199+
end

src/auxiliary/logs.jl

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,224 @@ function msg_qty(descr::Any, qty::Any; kwargs...)
214214
return msg_qty(string(descr), string(qty); kwargs...)
215215
end
216216

217+
function msg_path(descr_raw::AbstractString, path_raw::AbstractString;
218+
linewidth::Union{Int,Nothing}=nothing,
219+
leftwidth::Union{Int,Nothing}=nothing, indentation::Int=2,
220+
filler::Char='.', separator::AbstractString="",
221+
delimiter::AbstractString=" ",
222+
continuation_label::AbstractString="(continued)")
223+
descr, path = strip(descr_raw), strip(path_raw)
224+
225+
# Determine effective linewidth
226+
effective_linewidth = something(linewidth, leftwidth !== nothing ? default_linewidth() : nothing, default_linewidth())
227+
228+
# Try to fit on one line
229+
len_filling = get_len_filling(linewidth, leftwidth, indentation, descr, separator, path)
230+
required_length = indentation + length(descr) + length(separator) + max(len_filling, 1) + length(path)
231+
232+
if required_length <= effective_linewidth
233+
# Fits on one line - use msg_qty
234+
return msg_qty(descr, path; linewidth=linewidth, leftwidth=leftwidth,
235+
indentation=indentation, filler=filler, separator=separator,
236+
delimiter=delimiter, newline=true)
237+
end
238+
239+
# Path is too long - split into multiple lines
240+
return _msg_path_multiline(descr, path, effective_linewidth, indentation, filler,
241+
separator, delimiter, continuation_label)
242+
end
243+
244+
function _msg_path_multiline(descr, path, linewidth, indentation, filler, separator, delimiter, continuation_label)
245+
min_filler = 2 * length(delimiter)
246+
247+
# First line: calculate budget for path
248+
first_line_budget = linewidth - indentation - length(descr) - length(separator) - min_filler
249+
break_idx = _find_path_break(path, first_line_budget)
250+
251+
path_first = break_idx > 0 ? path[1:break_idx] : ""
252+
path_rest = break_idx > 0 ? path[break_idx+1:end] : path
253+
254+
# Build first line
255+
first_line = if !isempty(path_first)
256+
len_fill = linewidth - indentation - length(descr) - length(separator) - length(path_first)
257+
_msg_qty(descr, path_first, len_fill, indentation, filler, separator, delimiter, true)
258+
else
259+
len_fill = linewidth - indentation - length(descr) - length(separator)
260+
_msg_qty(descr, "", len_fill, indentation, filler, separator, delimiter, true)
261+
end
262+
263+
# Build continuation lines
264+
continuation_lines = _build_continuation_lines(path_rest, continuation_label, linewidth,
265+
indentation, filler, delimiter)
266+
267+
return first_line * continuation_lines
268+
end
269+
270+
function _build_continuation_lines(remaining_path, label, linewidth, indentation, filler, delimiter)
271+
isempty(remaining_path) && return ""
272+
273+
result = ""
274+
min_filler = 2 * length(delimiter)
275+
prefix_len = indentation + length(label)
276+
277+
while !isempty(remaining_path)
278+
budget = linewidth - prefix_len - min_filler
279+
280+
if length(remaining_path) <= budget
281+
# Last piece fits
282+
len_fill = linewidth - prefix_len - length(remaining_path)
283+
result *= _msg_qty(label, remaining_path, len_fill, indentation, filler, "", delimiter, true)
284+
break
285+
else
286+
# Need another split
287+
break_idx = _find_path_break(remaining_path, budget)
288+
piece = break_idx > 0 ? remaining_path[1:break_idx] : remaining_path[1:min(budget, end)]
289+
remaining_path = length(piece) < length(remaining_path) ? remaining_path[length(piece)+1:end] : ""
290+
291+
len_fill = linewidth - prefix_len - length(piece)
292+
result *= _msg_qty(label, piece, len_fill, indentation, filler, "", delimiter, true)
293+
end
294+
end
295+
296+
return result
297+
end
298+
299+
function _find_path_break(path::AbstractString, max_length::Int)
300+
length(path) <= max_length && return length(path)
301+
max_length <= 0 && return 0
302+
303+
# Find last directory separator within budget
304+
last_sep = findlast(c -> c == '/' || c == '\\', @view path[1:min(max_length, end)])
305+
306+
return something(last_sep, min(max_length, length(path)))
307+
end
308+
309+
function msg_vec(descr_raw::AbstractString, vec::AbstractVector;
310+
linewidth::Union{Int,Nothing}=nothing,
311+
leftwidth::Union{Int,Nothing}=nothing, indentation::Int=2,
312+
filler::Char='.', separator::AbstractString="",
313+
delimiter::AbstractString=" ",
314+
continuation_label::AbstractString="(continued)",
315+
vec_delimiter::AbstractString=", ",
316+
vec_brackets::Tuple{AbstractString,AbstractString}=("[", "]"))
317+
descr = strip(descr_raw)
318+
319+
# Format vector as string
320+
vec_str = _format_vector(vec, vec_delimiter, vec_brackets)
321+
322+
# Determine effective linewidth
323+
effective_linewidth = something(linewidth, leftwidth !== nothing ? default_linewidth() : nothing, default_linewidth())
324+
325+
# Try to fit on one line
326+
len_filling = get_len_filling(linewidth, leftwidth, indentation, descr, separator, vec_str)
327+
required_length = indentation + length(descr) + length(separator) + max(len_filling, 1) + length(vec_str)
328+
329+
if required_length <= effective_linewidth
330+
# Fits on one line - use msg_qty
331+
return msg_qty(descr, vec_str; linewidth=linewidth, leftwidth=leftwidth,
332+
indentation=indentation, filler=filler, separator=separator,
333+
delimiter=delimiter, newline=true)
334+
end
335+
336+
# Vector string is too long - split into multiple lines
337+
return _msg_vec_multiline(descr, vec_str, effective_linewidth, indentation, filler,
338+
separator, delimiter, continuation_label)
339+
end
340+
341+
function _format_vector(vec::AbstractVector, vec_delimiter::AbstractString,
342+
vec_brackets::Tuple{AbstractString,AbstractString})
343+
isempty(vec) && return vec_brackets[1] * vec_brackets[2]
344+
elements = _format_elements(vec)
345+
vec_str = vec_brackets[1] * join(elements, vec_delimiter) * vec_brackets[2]
346+
return vec_str
347+
end
348+
349+
function _format_elements(vec::AbstractVector{<:Real})
350+
return [@sprintf("%.7g", x) for x in vec]
351+
end
352+
function _format_elements(vec::AbstractVector{T}) where {T}
353+
return [string(x) for x in vec]
354+
end
355+
356+
function _msg_vec_multiline(descr, vec_str, linewidth, indentation, filler, separator,
357+
delimiter, continuation_label)
358+
min_filler = 2 * length(delimiter)
359+
360+
# First line: calculate budget for vector string
361+
first_line_budget = linewidth - indentation - length(descr) - length(separator) - min_filler
362+
break_idx = _find_vec_break(vec_str, first_line_budget)
363+
364+
vec_first = break_idx > 0 ? vec_str[1:break_idx] : ""
365+
vec_rest = break_idx > 0 ? vec_str[break_idx+1:end] : vec_str
366+
367+
# Build first line
368+
first_line = if !isempty(vec_first)
369+
len_fill = linewidth - indentation - length(descr) - length(separator) - length(vec_first)
370+
_msg_qty(descr, vec_first, len_fill, indentation, filler, separator, delimiter, true)
371+
else
372+
len_fill = linewidth - indentation - length(descr) - length(separator)
373+
_msg_qty(descr, "", len_fill, indentation, filler, separator, delimiter, true)
374+
end
375+
376+
# Build continuation lines
377+
continuation_lines = _build_vec_continuation_lines(vec_rest, continuation_label, linewidth,
378+
indentation, filler, delimiter)
379+
380+
return first_line * continuation_lines
381+
end
382+
383+
function _build_vec_continuation_lines(remaining_vec, label, linewidth, indentation, filler, delimiter)
384+
isempty(remaining_vec) && return ""
385+
386+
result = ""
387+
min_filler = 2 * length(delimiter)
388+
prefix_len = indentation + length(label)
389+
390+
while !isempty(remaining_vec)
391+
budget = linewidth - prefix_len - min_filler
392+
393+
if length(remaining_vec) <= budget
394+
# Last piece fits
395+
len_fill = linewidth - prefix_len - length(remaining_vec)
396+
result *= _msg_qty(label, remaining_vec, len_fill, indentation, filler, "", delimiter, true)
397+
break
398+
else
399+
# Need another split
400+
break_idx = _find_vec_break(remaining_vec, budget)
401+
piece = break_idx > 0 ? remaining_vec[1:break_idx] : remaining_vec[1:min(budget, end)]
402+
remaining_vec = length(piece) < length(remaining_vec) ? remaining_vec[length(piece)+1:end] : ""
403+
404+
len_fill = linewidth - prefix_len - length(piece)
405+
result *= _msg_qty(label, piece, len_fill, indentation, filler, "", delimiter, true)
406+
end
407+
end
408+
409+
return result
410+
end
411+
412+
function _find_vec_break(vec_str::AbstractString, max_length::Int)
413+
length(vec_str) <= max_length && return length(vec_str)
414+
max_length <= 0 && return 0
415+
416+
# Find last comma or space within budget (prefer comma)
417+
last_comma = findlast(==(','), @view vec_str[1:min(max_length, end)])
418+
419+
if last_comma !== nothing
420+
# Include the comma and any following spaces
421+
break_point = last_comma
422+
while break_point < min(max_length, length(vec_str)) &&
423+
vec_str[break_point + 1] == ' '
424+
break_point += 1
425+
end
426+
return break_point
427+
end
428+
429+
# If no comma, look for space
430+
last_space = findlast(==(' '), @view vec_str[1:min(max_length, end)])
431+
432+
return something(last_space, min(max_length, length(vec_str)))
433+
end
434+
217435
function msg_fields_inline(obj::T) where {T}
218436
fields = fieldnames(T)
219437
values = Tuple(getfield(obj, field) for field in fields)

src/auxiliary/nans.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ function containsnan(K::T) where {T<:AbstractArray}
55
return false
66
end
77

8-
function nancheck(chunk::AbstractBodyChunk, t, Δt)
9-
if containsnan(chunk.storage.b_int)
8+
function nancheck(storage::AbstractStorage, t, Δt)
9+
if containsnan(storage.b_int)
1010
n = (t > 0 && Δt > 0) ? Int(t ÷ Δt) : 0
1111
msg = "NaN's found in field `b_int` at simulation time $(t), step $(n)!\n"
1212
error(msg)

src/core/data_handler.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
function initialize!(dh, solver)
2+
calc_force_density!(dh, 0.0, solver.Δt)
23
return nothing
34
end
45

src/core/job.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,16 @@ function get_root_and_freq(o::Dict{Symbol,Any})
180180

181181
return root, freq
182182
end
183+
184+
function log_job_options(options::JobOptions)
185+
msg = "EXPORT OPTIONS:\n"
186+
if options.export_allowed
187+
msg *= msg_qty("export frequency (export every nth step)", options.freq)
188+
msg *= msg_path("root path", options.root; continuation_label=" ")
189+
msg *= msg_export_fields(options.fields)
190+
else
191+
msg *= msg_qty("export allowed", "false")
192+
end
193+
log_it(options, msg)
194+
return nothing
195+
end

src/core/mpi_body_data_handler.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,8 @@ function export_reference_results(dh::MPIBodyDataHandler, options::AbstractJobOp
421421
return nothing
422422
end
423423

424-
function initialize!(::AbstractMPIBodyDataHandler, ::AbstractTimeSolver)
424+
function initialize!(dh::AbstractMPIBodyDataHandler, solver::AbstractTimeSolver)
425+
calc_force_density!(dh, 0.0, solver.Δt)
425426
return nothing
426427
end
427428

src/core/submit.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ function submit_mpi(job::Job)
4848
initialize!(dh, job.time_solver)
4949
log_create_data_handler_end()
5050
log_data_handler(job.options, dh)
51+
log_job_options(job.options)
5152
log_timesolver(job.options, job.time_solver)
5253
end
5354
@timeit_debug TO "solve!" begin
@@ -72,6 +73,7 @@ function submit_threads(job::Job, n_chunks::Int)
7273
initialize!(dh, job.time_solver)
7374
log_create_data_handler_end()
7475
log_data_handler(job.options, dh)
76+
log_job_options(job.options)
7577
log_timesolver(job.options, job.time_solver)
7678
solve!(dh, job.time_solver, job.options)
7779
end

src/core/threads_multibody_data_handler.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ function update_caches!(dh::ThreadsMultibodyDataHandler)
8585
return nothing
8686
end
8787

88-
function initialize!(dh::AbstractThreadsMultibodyDataHandler, ::AbstractTimeSolver)
88+
function initialize!(dh::AbstractThreadsMultibodyDataHandler, solver::AbstractTimeSolver)
8989
init_contact_nhs!(dh)
90+
calc_force_density!(dh, 0.0, solver.Δt)
9091
return nothing
9192
end
9293

0 commit comments

Comments
 (0)