@@ -214,3 +214,78 @@ function submit!(study::Study; kwargs...)
214214 print_log (stdout , msg)
215215 return nothing
216216end
217+
218+ """
219+ process_each_job(f::Function, study::Study, default_result::NamedTuple)
220+
221+ $(internal_api_warning ())
222+ $(experimental_api_warning ())
223+
224+ Apply a processing function to each successfully completed job in a parameter study.
225+ This function iterates through all jobs in the study and applies the user-defined
226+ processing function `f` to jobs that completed successfully. Failed jobs or jobs where
227+ the processing function errors will use the `default_result` instead.
228+
229+ # Arguments
230+ - `f::Function`: A processing function with signature `f(job::Job, setup::NamedTuple)`
231+ that returns a `NamedTuple` containing the processed results. The function receives:
232+ - `job`: The [`Job`](@ref) object for the simulation
233+ - `setup`: The parameter configuration `NamedTuple` for this job
234+ - `study::Study`: The study containing the jobs to process
235+ - `default_result::NamedTuple`: The default result to use when a job failed or when
236+ the processing function throws an error
237+
238+ # Returns
239+ - `Vector{<:NamedTuple}`: A vector of results with the same length as the number of
240+ jobs in the study. Each element is either the result from applying `f` or the
241+ `default_result` for failed/errored cases.
242+
243+ # Behavior
244+ - Only processes jobs where `study.sim_success[i] == true`
245+ - Failed jobs automatically receive `default_result` (warning logged)
246+ - If processing function `f` throws an error, that job receives `default_result` (error logged)
247+ - Processing is sequential (one job at a time)
248+ - All jobs in the study will have a corresponding entry in the results vector
249+
250+ # Example
251+ ```julia
252+ # After running a parameter study
253+ study = Study(create_job, setups; root="my_study")
254+ submit!(study)
255+
256+ # Define a function to extract maximum displacement from results
257+ function 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))
262+ return (; E=setup.E, velocity=setup.velocity, max_displacement=max_u)
263+ end
264+
265+ default = (; E=0.0, velocity=0.0, max_displacement=NaN)
266+ results = process_each_job(extract_max_displacement, study, default)
267+
268+ # Filter successful results
269+ successful_results = [r for r in results if !isnan(r.max_displacement)]
270+ ```
271+
272+ See also: [`Study`](@ref), [`submit!`](@ref)
273+ """
274+ function process_each_job (f:: F , study:: Study , default_result:: NamedTuple ) where {F}
275+ results = fill (default_result, length (study. jobs))
276+ for (i, job) in enumerate (study. jobs)
277+ if study. sim_success[i]
278+ setup = study. setups[i]
279+ res = try
280+ f (job, setup)
281+ catch err
282+ @error " error processing job $(job. options. root) " error= err
283+ default_result
284+ end
285+ results[i] = res
286+ else
287+ @warn " skipping processing for failed job $(job. options. root) "
288+ end
289+ end
290+ return results
291+ end
0 commit comments