#!/bin/bash # Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced # at the Lawrence Livermore National Laboratory. All Rights reserved. See files # LICENSE and NOTICE for details. LLNL-CODE-806117. # # This file is part of the MFEM library. For more information and source code # availability visit https://mfem.org. # # MFEM is free software; you can redistribute it and/or modify it under the # terms of the BSD-3 license. We welcome feedback and contributions, see file # CONTRIBUTING.md for details. make="${MAKE:-make}" mpiexec="${MPIEXEC:-mpirun}" mpiexec_np="${MPIEXEC_NP:--np}" run_prefix="" run_vg="valgrind --leak-check=full --show-reachable=yes --track-origins=yes" run_suffix="-no-vis" skip_gen_meshes="yes" # filter-out device runs ("no") or non-device runs ("yes"): device_runs="no" cur_dir="${PWD}" mfem_dir="$(cd "$(dirname "$0")"/.. && pwd)" mfem_build_dir="" build_log="" output_dir="" output_sfx=".out" # The group format is: '"group-name" "group-summary-title" "group-directory" # "group-source-patterns"' groups_serial=( '"examples" "Examples:" "examples" "ex{,1,2,3}[0-9].cpp"' # "ex1.cpp"' '"sundials" "SUNDIALS examples:" "examples/sundials" "ex{9,10,16}.cpp"' '"performance" "Performance miniapps:" "miniapps/performance" "ex1.cpp"' '"amgx" "AmgX examples:" "examples/amgx" "ex1.cpp"' '"caliper" "Caliper examples:" "examples/caliper" "ex1.cpp"' '"ginkgo" "Ginkgo examples:" "examples/ginkgo" "ex1.cpp"' '"hiop" "HiOp examples:" "examples/hiop" "ex9.cpp"' '"pumi" "PUMI examples:" "examples/pumi" "ex1.cpp ex2.cpp"' # ""' '"meshing" "Meshing miniapps:" "miniapps/meshing" "mobius-strip.cpp klein-bottle.cpp extruder.cpp toroid.cpp mesh-optimizer.cpp minimal-surface.cpp"' '"adjoint" "Adjoint miniapps:" "miniapps/adjoint" "cvsRoberts_ASAi_dns.cpp"' '"gslib" "GSLIB miniapps:" "miniapps/gslib" "field-diff.cpp field-interp.cpp findpts.cpp schwarz_ex1.cpp "' '"nurbs" "NURBS miniapps:" "miniapps/nurbs" "nurbs_ex1.cpp"' '"tools" "Tools miniapps:" "miniapps/tools" "convert-dc.cpp display-basis.cpp get-values.cpp load-dc.cpp lor-transfer.cpp"' '"toys" "Toys miniapps:" "miniapps/toys" "automata.cpp life.cpp lissajous.cpp mandel.cpp mondrian.cpp rubik.cpp snake.cpp"' '"convergence" "Convergence tests:" "tests/convergence" "rates.cpp"' ) # Parallel groups groups_parallel=( '"examples" "Examples:" "examples" "ex{,1,2,3}[0-9]p.cpp"' # "ex1p.cpp"' '"sundials" "SUNDIALS examples:" "examples/sundials" "ex{9,10,16}p.cpp"' '"petsc" "PETSc examples:" "examples/petsc" "ex{,1}[0-9]p.cpp"' '"performance" "Performance miniapps:" "miniapps/performance" "ex1p.cpp"' '"amgx" "AmgX examples:" "examples/amgx" "ex1p.cpp"' '"caliper" "Caliper examples:" "examples/caliper" "ex1p.cpp"' '"hiop" "HiOp examples:" "examples/hiop" "ex9p.cpp"' '"pumi" "PUMI examples:" "examples/pumi" "ex1p.cpp ex6p.cpp"' '"superlu" "Superlu examples:" "examples/superlu" "ex1p.cpp"' # ""' '"meshing" "Meshing miniapps:" "miniapps/meshing" "pmesh-optimizer.cpp pmesh-fitting.cpp pminimal-surface.cpp"' '"electromagnetics" "Electromagnetics miniapps:" "miniapps/electromagnetics" "joule.cpp"' # "{volta,tesla,joule}.cpp"' # todo: multiline sample runs '"adjoint" "Adjoint miniapps:" "miniapps/adjoint" "adjoint_advection_diffusion.cpp"' '"gslib" "GSLIB miniapps:" "miniapps/gslib" "pfindpts.cpp schwarz_ex1p.cpp"' '"navier" "Navier miniapps:" "miniapps/navier" "navier_cht.cpp"' '"nurbs" "NURBS miniapps:" "miniapps/nurbs" "nurbs_ex1p.cpp nurbs_ex11p.cpp"' '"shifted" "Shifted miniapps:" "miniapps/shifted" "distance.cpp"' '"solvers" "Solvers miniapps:" "miniapps/solvers" "block-solvers.cpp"' '"tools" "Tools miniapps:" "miniapps/tools" "convert-cd.cpp get-values.cpp load-dc.cpp"' '"convergence" "Convergence tests:" "tests/convergence" "prates.cpp"' '"par-mesh-format" "Parallel mesh tests:" "tests/par-mesh-format" "ex1p.cpp"' ) # All groups serial + parallel runs mixed in the same group: groups_all=( '"examples" "Examples:" "examples" "ex\"{,1,2,3}[0-9]\"{,p}.cpp"' '"sundials" "SUNDIALS examples:" "examples/sundials" "ex\"{9,10,16}\"{,p}.cpp"' '"petsc" "PETSc examples:" "examples/petsc" "ex{,1}[0-9]p.cpp"' '"performance" "Performance miniapps:" "miniapps/performance" "ex1{,p}.cpp"' '"amgx" "AmgX examples:" "examples/amgx" "ex1.cpp ex1p.cpp"' '"caliper" "Caliper examples:" "examples/caliper" "ex1.cpp ex1p.cpp"' '"ginkgo" "Ginkgo examples:" "examples/ginkgo" "ex1.cpp"' '"hiop" "HiOp examples:" "examples/hiop" "ex9.cpp ex9p.cpp"' '"pumi" "PUMI examples:" "examples/pumi" "ex1.cpp ex1p.cpp ex2.cpp ex6p.cpp"' '"superlu" "Superlu examples:" "examples/superlu" "ex1p.cpp"' '"meshing" "Meshing miniapps:" "miniapps/meshing" "mobius-strip.cpp klein-bottle.cpp extruder.cpp toroid.cpp {,p}mesh-optimizer.cpp pmesh-fitting.cpp {,p}minimal-surface.cpp"' '"electromagnetics" "Electromagnetics miniapps:" "miniapps/electromagnetics" "joule.cpp"' # "{volta,tesla,joule}.cpp"' # todo: multiline sample runs '"adjoint" "Adjoint miniapps:" "miniapps/adjoint" "adjoint_advection_diffusion.cpp cvsRoberts_ASAi_dns.cpp"' '"gslib" "GSLIB miniapps:" "miniapps/gslib" "field-diff.cpp field-interp.cpp findpts.cpp schwarz_ex1.cpp pfindpts.cpp schwarz_ex1p.cpp"' '"navier" "Navier miniapps:" "miniapps/navier" "navier_cht.cpp"' '"nurbs" "NURBS miniapps:" "miniapps/nurbs" "nurbs_ex1.cpp nurbs_ex1p.cpp nurbs_ex11p.cpp"' '"shifted" "Shifted miniapps:" "miniapps/shifted" "distance.cpp"' '"solvers" "Solvers miniapps:" "miniapps/solvers" "block-solvers.cpp"' '"tools" "Tools miniapps:" "miniapps/tools" "convert-dc.cpp display-basis.cpp get-values.cpp load-dc.cpp lor-transfer.cpp"' '"toys" "Toys miniapps:" "miniapps/toys" "automata.cpp life.cpp lissajous.cpp mandel.cpp mondrian.cpp rubik.cpp snake.cpp"' '"convergence" "Convergence tests:" "tests/convergence" "{,p}rates.cpp"' '"par-mesh-format" "Parallel mesh tests:" "tests/par-mesh-format" "ex1p.cpp"' ) make_all="all" base_timeformat=$'real: %3Rs user: %3Us sys: %3Ss %%cpu: %P' # separator sep='----------------------------------------------------------------' # Command line parameters: opt_help="no" opt_show="no" mfem_config="MFEM_USE_MPI=NO MFEM_DEBUG=NO" groups=() group_name="" group_title="" valgrind="no" make_j="-j $(getconf _NPROCESSORS_ONLN)" color="no" built="no" timing="no" # Read the sample runs from the source "$1" and put them in the array variable # "runs". function extract_sample_runs() { local old_IFS="${IFS}" sruns="" pruns="" local src="$1" if [ "${src}" == "" ]; then runs=(); return 1; fi local app=${src%.cpp} local vg_app="${app}" if [ "${valgrind}" == "yes" ]; then vg_app="${run_vg} ${app}"; fi # parallel sample runs are lines matching "^//.* mpirun .* ${app}" with # everything in front of "mpirun" removed: pruns=`grep "^//.* mpirun .* ${app}" "${src}" | sed -e "s/.* mpirun \(.*\) ${app}/${mpiexec} \1 ${app}/g" \ -e "s/ -np\(.*\) ${app}/ ${mpiexec_np}\1 ${vg_app}/g"` # serial sample runs are lines that are not parallel sample runs and matching # "^//.* ${app}" with everything in front of "${app}" removed: sruns=`grep -v "^//.* mpirun .* ${app}" "${src}" | grep "^//.* ${app}" | sed -e "s/.* ${app}/${vg_app}/g"` runs="${sruns}${pruns}" if [ "$skip_gen_meshes" == "yes" ]; then runs=`printf "%s" "$runs" | grep -v ".* -m .*\.gen"` fi if [ "$device_runs" == "yes" ]; then runs=`printf "%s" "$runs" | grep ".* -d .*"` if [ "$have_occa" == "no" ]; then runs=`printf "%s" "$runs" | grep -v ".* -d occa-.*"` fi if [ "$have_raja" == "no" ]; then runs=`printf "%s" "$runs" | grep -v ".* -d raja-.*"` fi if [ "$have_ceed" == "no" ]; then runs=`printf "%s" "$runs" | grep -v ".* -d ceed-.*"` fi else runs=`printf "%s" "$runs" | grep -v ".* -d .*"` fi IFS=$'\n' runs=(${runs}) IFS="${old_IFS}" } # Echo usage information function help_message() { cat < Specify explicitly a group (dir + file pattern) to run; This option can be used multiple times to define multiple groups -dev configure only sample runs using devices. To test with a parallel build, the parallel (-p|-par) option should be set first on the command line. -v Enable valgrind -o [${output_dir:-": output goes to stdout"}] If not empty, save output to files inside -d [${mfem_build_dir}] If is different from then use an out-of-source build in -j [${make_j}] Specify the number of jobs to use for building -c|-color Always use colors for the status messages: OK, FAILED, etc -b|-built Do NOT rebuild the library and the executables -t|-time Measure and print execution time for each sample run -s|-show Show all configured sample runs and exit -n Dry run: replace "\$sample_run" with "echo \$sample_run" = Set a shell script variable; see below for valid variables * Any other parameter is treated as [${mfem_dir}] is the MFEM source directory This script tests all the sample runs listed in the beginning comments of MFEM's serial or parallel example and miniapp codes. The list of sample runs is auto-generated and can be viewed with the -s|-show option. The following shell script variables can be set with =: output_dir [${output_dir}] Same as '-o': if not empty, save output to files in that directory output_sfx [${output_sfx}] Suffix to append to the output files mfem_config [${mfem_config}] Set MFEM configuration options make [${make}], mpiexec [${mpiexec}], mpiexec_np [${mpiexec_np}] Their values can also set using the respective uppercase environment variable mfem_build_dir [${mfem_build_dir}] Same as '-d': set this variable to something different from to use an out-of-source build For other valid variables, see the script source. The following environment variables, if non-empty, are used: MAKE, MPIEXEC, MPIEXEC_NP Example usage: $0 -s [ Show all configured sample runs ] $0 -o baseline [ Serial build; run and save all built sample runs ] $0 -p -o baseline [ Parallel build; run and save all sample runs ] $0 -b -g examples ex8.cpp [ Use the existing build; run ex8 sample runs ] EOF } function show_runs() { echo "${sep}" for group_params in "${groups[@]}"; do eval params=(${group_params}) name="${params[0]}" title="${params[1]}" group_dir="${mfem_dir}/${params[2]}" pattern="${params[3]}" printf "group name: [%s]\n" "${name}" printf "summary title: [%s]\n" "${title}" printf "directory: [%s]\n" "${group_dir}" printf "pattern: [%s]\n" "${pattern}" cd "${cur_dir}"; cd "${group_dir}" || exit 1 eval sources=(${pattern}) eval sources=("${sources[@]}") printf "sources: (%s)\n" "${sources[*]}" printf "sample runs:\n" for src in "${sources[@]}"; do extract_sample_runs "${src}" for run in "${runs[@]}"; do printf " %s\n" "${run}" done done echo "${sep}" done } # Process command line parameters while [ $# -gt 0 ]; do case "$1" in -h|-help) opt_help="yes" ;; -p|-par) mfem_config="MFEM_USE_MPI=YES MFEM_DEBUG=NO" ;; -g) gbasename="$(basename "$2")" gname="${group_name:-${gbasename}}" gtitle="${group_title:-"Group <${gbasename}>:"}" test_group="\"${gname}\" \"${gtitle}\" \"$2\" \"$3\"" groups=("${groups[@]}" "${test_group}") shift 2 ;; -dev) device_runs="yes" mfem_config+=" MFEM_USE_CUDA=YES MFEM_USE_OPENMP=YES" # OCCA, RAJA, libCEED are enabled below, if available ;; -v) valgrind="yes" ;; -o) shift output_dir="$1" ;; -d) shift mfem_build_dir="$1" ;; -j) shift make_j="-j $1" ;; -c|-color) color="yes" ;; -b|-built) built="yes" ;; -t|-time) timing="yes" ;; -s|-show) opt_show="yes" ;; -n) run_prefix="echo" ;; -*) echo "unknown option: '$1'" exit 1 ;; *=*) eval $1 ;; *) mfem_dir="$1" ;; esac shift done # while ... mfem_build_dir="${mfem_build_dir:-${mfem_dir}}" build_log="${build_log:-${mfem_build_dir}/config/sample-runs-build.log}" if [ 0 -eq ${#groups[*]} ]; then groups=("${groups_all[@]}") # These can be used as command line arguments: # 'groups=("${groups_serial[@]}")' # 'groups=("${groups_parallel[@]}")' fi if [ "${opt_help}" == "yes" ]; then help_message exit fi if [ "${opt_show}" == "yes" ]; then show_runs exit fi # Setup colors if [ -t 1 ] && [ -z "${output_dir}" ] || [ "${color}" == "yes" ]; then red='\033[0;31m' green='\033[0;32m' yellow='\033[0;33m' magenta='\033[0;35m' cyan='\033[0;36m' none='\033[0m' else red= green= yellow= magenta= cyan= none= fi # Run the given command, saving the rune time in the variable "timer". function timed_run() { timer="$({ time "$@" 1>&3 2>&4; } 2>&1)" } 3>&1 4>&2 # This function is used to execute the sample runs function go() { # Strip leading and trailing spaces from $1 and store the result in cmd_line shopt -s extglob local cmd_line="${1##+( )}" cmd_line="${cmd_line%%+( )}" shopt -u extglob local res="" echo $sep echo "<${group}>" "${cmd_line}" echo $sep if [ "${timing}" == "yes" ]; then timed_run eval "${cmd_line}" else eval "${cmd_line}" fi if [ "$?" -eq 0 ]; then res="${green} OK ${none}" else res="${red}FAILED${none}" fi printf "[${res}] <${group}> ${cmd_line}\n" if [ "${timing}" == "yes" ]; then printf "Run time: %s\n" "${timer}" timer=(${timer}) timer="${timer[1]}" printf -v line "[$res](%8s) ${cmd_line}" "$timer" summary=("${summary[@]}" "$line") else summary=("${summary[@]}" "[${res}] ${cmd_line}") fi echo $sep } # This function is used to run a group of sample runs (in the same directory) function go_group() { local res="" if [ $# -eq 0 ]; then return 0; fi local group_output_dir="" output_file="" output="" if [ ! -z "$output_dir" ]; then group_output_dir="${output_dir}/${group_dir}" mkdir -p "${group_output_dir}" || exit 1 fi for src in "$@"; do ex_run_suffix=${run_suffix} && [[ $src =~ ex0p?\.cpp ]] \ && ex_run_suffix="" cd "${mfem_dir}/${group_dir}" || exit 1 extract_sample_runs "${src}" || continue [ "${#runs[@]}" -eq 0 ] && continue cd "${mfem_build_dir}/${group_dir}" || exit 1 if [ ! -x "${src%.cpp}" ]; then res="${magenta} SKIP ${none}" echo $sep printf "[${res}] <${group}> <${src}>\n" echo $sep summary=("${summary[@]}" "[${res}] <${src}>") continue fi if [ ! -z "$output_dir" ]; then output_file="${group_output_dir}/${src}${output_sfx}" : > "${output_file}" output=">> \"${output_file}\" 2>&1" fi for run in "${runs[@]}"; do if [ "${run}" == "" ]; then continue; fi eval go \"\${run_prefix} \${run} \${ex_run_suffix}\" $output done done ${make} clean-exec } # Make sure $mfem_dir exists and we can cd into it cd "$mfem_dir" || exit 1 # Make sure $mfem_dir is an absolute path mfem_dir="$PWD" cd "${cur_dir}" if [ "${built}" == "no" ]; then mkdir -p "${mfem_build_dir}" || exit 1 fi # Make sure $mfem_build_dir exists and we can cd into it cd "${mfem_build_dir}" || exit 1 # Make sure $mfem_build_dir is an absolute path mfem_build_dir="$PWD" # Setup 'output_dir' if [ ! -z "$output_dir" ]; then cd "${cur_dir}" mkdir -p "${output_dir}" && cd "${output_dir}" || exit 1 output_dir="$PWD" echo "Sending output to files in: [${output_dir}]" echo "Using suffix: [${output_sfx}]" fi TIMEFORMAT="${base_timeformat}" # Setup optional libraries when not using externally built MFEM: if [ "${built}" == "no" ]; then have_occa="no" have_raja="no" have_ceed="no" if [ "${device_runs}" == "yes" ]; then if [ -n "${CUDA_ARCH}" ]; then mfem_config+=" CUDA_ARCH=${CUDA_ARCH}" fi if [ -d "${mfem_dir}/../occa" ]; then mfem_config+=" MFEM_USE_OCCA=YES" have_occa="yes" fi if [ -d "${mfem_dir}/../raja" ]; then mfem_config+=" MFEM_USE_RAJA=YES" have_raja="yes" fi if [ -d "${mfem_dir}/../libCEED" ]; then mfem_config+=" MFEM_USE_CEED=YES" have_ceed="yes" fi fi fi function set_echo_log() { local dirname=`dirname "$1"` cd "${cur_dir}" mkdir -p "${dirname}" && cd "${dirname}" || exit 1 echo_log="$PWD"/`basename "$1"` } # Echo the given command line; then run it sending all output to $echo_log function echo_run() { echo " $@" { echo " $@"; echo "$sep"; eval "$@" echo "$sep"; } >> "$echo_log" 2>&1 } # Function that builds the mfem library, examples and miniapps function build_all() { printf "Building MFEM with all examples and miniapps:\n" set_echo_log "${build_log}" echo " ### build log: [$echo_log]" { echo "$sep"; echo " MFEM build log"; echo "$sep"; } > "$echo_log" echo_run cd "${mfem_build_dir}" if [ "${mfem_dir}" != "${mfem_build_dir}" ]; then echo_run ${make} -f "${mfem_dir}"/makefile config fi # Don't use 'make distclean' as it will delete the default $build_log echo_run ${make} clean || exit 1 echo_run ${make} config ${mfem_config} || exit 1 echo_run ${make} ${make_j} || exit 1 echo_run ${make} ${make_all} ${make_j} || exit 1 # Build groups in directories other than the directories built by 'make all': for group_params in "${groups[@]}"; do eval params=(${group_params}) group_dir="${params[2]}" case "$group_dir" in (examples*|miniapps*) # Built by 'make all' ;; (*) if [ "${mfem_dir}" != "${mfem_build_dir}" ]; then echo_run mkdir -p "${group_dir}" || exit 1 echo_run cd "${group_dir}" || exit 1 echo_run cp -af "${mfem_dir}/${group_dir}/makefile" . || exit 1 else echo_run cd "${group_dir}" || exit 1 fi echo_run ${make} clean || exit 1 echo_run ${make} MFEM_DIR="${mfem_dir}" ${make_j} || exit 1 echo_run cd "${mfem_build_dir}" || exit 1 ;; esac done } # Function that runs all sample runs, given by the array variable "groups". function all_go() { for group_params in "${groups[@]}"; do eval params=(${group_params}) group="${params[0]}" group_dir="${params[2]}" cd "${mfem_dir}/${group_dir}" || exit 1 eval sources=(${params[3]}) eval sources=("${sources[@]}") summary=("${summary[@]}" "${params[1]}") go_group "${sources[@]}" done printf "Summary:\n--------\n" for line in "${summary[@]}"; do printf "${line}\n" done } function main() { # Build all mfem examples and miniapps if [ "${built}" == "no" ]; then if [ "${timing}" == "yes" ]; then timed_run build_all printf "Build time: %s\n" "${timer}" else build_all fi fi summary=() PATH=.:$PATH # Print the MFEM configuration info cd "${mfem_build_dir}" echo "$sep" echo "MFEM configuration" echo "$sep" ${make} info echo "$sep" # Run all sample runs. if [ "${timing}" == "yes" ]; then timed_run all_go printf "Total run time: %s\n" "${timer}" else all_go fi echo } output="" if [ ! -z "$output_dir" ]; then output=">> \"${output_dir}/main${output_sfx}\" 2>&1" fi eval main $output