11"""
22 Study(jobcreator::Function, setups::Vector{<:NamedTuple}; root::String)
33
4- $(internal_api_warning ())
5- $(experimental_api_warning ())
6-
74A structure for managing parameter studies with multiple peridynamic simulations. The
85`Study` type coordinates the execution of multiple simulation jobs with different parameter
96configurations, tracks their status, and logs all results to a central logfile.
@@ -38,11 +35,13 @@ configurations, tracks their status, and logs all results to a central logfile.
3835
3936# Example
4037```julia
38+ # some function that creates a Job from a parameter setup
4139function create_job(setup::NamedTuple, root::String)
4240 body = Body(BBMaterial(), uniform_box(1.0, 1.0, 1.0, 0.1))
4341 material!(body, horizon=0.3, E=setup.E, rho=1000, Gc=100)
4442 velocity_ic!(body, :all_points, :x, setup.velocity)
4543 solver = VelocityVerlet(steps=1000)
44+ # create a unique path for this job based on parameters
4645 path = joinpath(root, "sim_E\$ (setup.E)_v\$ (setup.velocity)")
4746 return Job(body, solver; path=path, freq=10)
4847end
@@ -75,7 +74,17 @@ struct Study{F,S,J}
7574 check_jobpaths_unique (jobpaths)
7675 sim_success = fill (false , length (jobs))
7776 logfile = joinpath (root, " study_log.log" )
78- new {F,S,J} (jobcreator, setups, jobs, jobpaths, root, logfile, sim_success)
77+ st = new {F,S,J} (jobcreator, setups, jobs, jobpaths, root, logfile, sim_success)
78+ # If a logfile already exists from a previous run, initialize sim_success
79+ # from the logfile so processing or resuming works across interrupted runs.
80+ if isfile (st. logfile)
81+ try
82+ update_sim_success_from_log! (st)
83+ catch err
84+ @warn " failed to refresh study status from logfile" error= err
85+ end
86+ end
87+ return st
7988 end
8089end
8190
119128"""
120129 submit!(study::Study; kwargs...)
121130
122- $(internal_api_warning ())
123- $(experimental_api_warning ())
124-
125131Submit and execute all simulation jobs in a parameter study. Jobs are run sequentially,
126132and each job can utilize MPI or multithreading as configured. If a job fails, the error
127133is logged and execution continues with the remaining jobs.
@@ -166,16 +172,45 @@ See also: [`Study`](@ref), [`submit`](@ref)
166172"""
167173function submit! (study:: Study ; kwargs... )
168174 # Create root directory if it doesn't exist
169- if ! isdir (study. root)
170- mkpath (study. root)
175+ isdir (study. root) || mkpath (study. root)
176+
177+ # If logfile exists, refresh sim_success and append a resume marker. Otherwise
178+ # create a new logfile with header.
179+ if isfile (study. logfile)
180+ try
181+ update_sim_success_from_log! (study)
182+ catch err
183+ @warn " failed to refresh study status from existing logfile" error= err
184+ end
185+ open (study. logfile, " a" ) do io
186+ datetime = Dates. format (Dates. now (), " yyyy-mm-dd, HH:MM:SS" )
187+ write (io, " \n --- RESUMED: $datetime ---\n\n " )
188+ end
189+ else
190+ open (study. logfile, " w+" ) do io
191+ write (io, get_logfile_head ())
192+ write (io, peridynamics_banner (color= false ))
193+ write (io, " \n SIMULATION STUDY LOGFILE\n\n " )
194+ end
171195 end
172196
173- open (study. logfile, " w+" ) do io
174- write (io, get_logfile_head ())
175- write (io, peridynamics_banner (color= false ))
176- write (io, " \n SIMULATION STUDY LOGFILE\n\n " )
177- end
197+ # Now submit each job
178198 for (i, job) in enumerate (study. jobs)
199+ # If this job already completed in a previous run, skip execution
200+ open (study. logfile, " a" ) do io
201+ msg = " Simulation `$(study. jobpaths[i]) `:\n "
202+ for (key, value) in pairs (study. setups[i])
203+ msg *= " $(key) : $(value) \n "
204+ end
205+ write (io, msg)
206+ end
207+ if study. sim_success[i]
208+ # Write a short note about skipping to the study logfile
209+ open (study. logfile, " a" ) do io
210+ write (io, " status: skipped (already completed)\n\n " )
211+ end
212+ continue
213+ end
179214 success = false
180215 simtime = @elapsed begin
181216 try
@@ -188,23 +223,18 @@ function submit!(study::Study; kwargs...)
188223 log_it (job. options, " \n ERROR: Simulation failed with error!\n " )
189224 log_it (job. options, sprint (showerror, err, catch_backtrace ()))
190225 log_it (job. options, " \n " )
191- catch log_err
192- # If logging fails, just continue - error will be recorded in study log
226+ catch
227+ # If logging fails, just continue - error will be recorded in job log
193228 end
194229 end
195230 end
196231 study. sim_success[i] = success
197232 open (study. logfile, " a" ) do io
198- msg = " Simulation `$(study. jobpaths[i]) `:\n "
199- for (key, value) in pairs (study. setups[i])
200- msg *= " $(key) : $(value) \n "
201- end
202233 if success
203- msg *= @sprintf (" status: completed ✓ (%.2f seconds)\n " , simtime)
234+ msg *= @sprintf (" status: completed ✓ (%.2f seconds)\n\n " , simtime)
204235 else
205- msg *= " status: failed ✗\n "
236+ msg *= " status: failed ✗\n\n "
206237 end
207- msg *= " \n "
208238 write (io, msg)
209239 end
210240 end
@@ -216,10 +246,62 @@ function submit!(study::Study; kwargs...)
216246end
217247
218248"""
219- process_each_job(f::Function, study::Study, default_result::NamedTuple )
249+ update_sim_success_from_log!( study::Study)
220250
221251$(internal_api_warning ())
222- $(experimental_api_warning ())
252+
253+ Read the `study.logfile` and update `study.sim_success` flags according to the
254+ last recorded status for each job. This allows resuming processing or submission
255+ after an interrupted run.
256+ """
257+ function update_sim_success_from_log! (study:: Study )
258+ isfile (study. logfile) || return false
259+
260+ # Read logfile line by line
261+ lines = readlines (study. logfile)
262+
263+ for (i, path) in enumerate (study. jobpaths)
264+ # Search for the Simulation line for this job path
265+ sim_line_pattern = " Simulation `$(path) `:"
266+ sim_line_idx = findfirst (line -> occursin (sim_line_pattern, line), lines)
267+
268+ if sim_line_idx === nothing
269+ # No record for this job in logfile
270+ study. sim_success[i] = false
271+ continue
272+ end
273+
274+ # Look for status line in the next few lines after the simulation line
275+ found_status = false
276+ for j in (sim_line_idx + 1 ): length (lines)
277+ if occursin (" status: completed" , lines[j])
278+ study. sim_success[i] = true
279+ found_status = true
280+ break
281+ elseif occursin (" status: failed" , lines[j])
282+ study. sim_success[i] = false
283+ found_status = true
284+ break
285+ elseif occursin (" status: skipped" , lines[j])
286+ study. sim_success[i] = true
287+ found_status = true
288+ break
289+ elseif occursin (" Simulation `" , lines[j])
290+ # Reached next simulation entry without finding status
291+ break
292+ end
293+ end
294+
295+ if ! found_status
296+ # No status found after simulation line
297+ study. sim_success[i] = false
298+ end
299+ end
300+ return true
301+ end
302+
303+ """
304+ process_each_job(f::Function, study::Study, default_result::NamedTuple)
223305
224306Apply a processing function to each successfully completed job in a parameter study.
225307This function iterates through all jobs in the study and applies the user-defined
@@ -255,10 +337,8 @@ submit!(study)
255337
256338# Define a function to extract maximum displacement from results
257339function extract_max_displacement(job::Job, setup::NamedTuple)
258- # Read results from job output directory
259- results_file = joinpath(job.options.root, "results_step_0010.jld2")
260- data = load_results(results_file)
261- max_u = maximum(norm, eachcol(data.displacement))
340+ # Calculate maximum displacement
341+ max_u = ...
262342 return (; E=setup.E, velocity=setup.velocity, max_displacement=max_u)
263343end
264344
@@ -272,6 +352,15 @@ successful_results = [r for r in results if !isnan(r.max_displacement)]
272352See also: [`Study`](@ref), [`submit!`](@ref)
273353"""
274354function process_each_job (f:: F , study:: Study , default_result:: NamedTuple ) where {F}
355+ # Refresh sim_success from logfile if it exists (in case study was interrupted/resumed)
356+ if isfile (study. logfile)
357+ try
358+ update_sim_success_from_log! (study)
359+ catch err
360+ @warn " failed to refresh study status from logfile before processing" error= err
361+ end
362+ end
363+
275364 results = fill (default_result, length (study. jobs))
276365 for (i, job) in enumerate (study. jobs)
277366 if study. sim_success[i]
0 commit comments