config/sample-runs.sh
#!/bin/bash

# Copyright (c) 2010-2025, 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-9]}[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"'
'"moonolith"
   "Moonolith examples:"
   "examples/moonolith"
   "ex1.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-quality.cpp
    polar-nc.cpp reflector.cpp shaper.cpp trimmer.cpp twist.cpp
    mesh-optimizer.cpp minimal-surface.cpp"'
'"adjoint"
   "Adjoint miniapps:"
   "miniapps/adjoint"
   "cvsRoberts_ASAi_dns.cpp"'
'"autodiff"
   "Autodiff miniapps:"
   "miniapps/autodiff"
   "seq_example.cpp seq_test.cpp"' # 'seq_test.cpp' has no sample runs
'"dpg"
   "DPG miniapps:"
   "miniapps/dpg"
   "{acoustics,convection-diffusion,diffusion,maxwell}.cpp"'
'"gslib"
   "GSLIB miniapps:"
   "miniapps/gslib"
   "field-diff.cpp field-interp.cpp findpts.cpp schwarz_ex1.cpp "'
# todo: miniapps/mtop
'"nurbs"
   "NURBS miniapps:"
   "miniapps/nurbs"
   "nurbs_ex1.cpp"'
# todo: add other nurbs miniapps
# todo: miniapps/solvers (serial)
'"tools"
   "Tools miniapps:"
   "miniapps/tools"
   "convert-dc.cpp display-basis.cpp get-values.cpp load-dc.cpp
    lor-transfer.cpp"'
# todo: add other tools miniapps
'"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-9]}[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"'
'"moonolith"
   "Moonolith examples:"
   "examples/moonolith"
   "ex{1,2}p.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
    fit-node-position.cpp"'
'"electromagnetics"
   "Electromagnetics miniapps:"
   "miniapps/electromagnetics"
   "joule.cpp"'
#   "{joule,maxwell,tesla,volta}.cpp"' # todo: multiline sample runs
'"adjoint"
   "Adjoint miniapps:"
   "miniapps/adjoint"
   "adjoint_advection_diffusion.cpp"'
'"autodiff"
   "Autodiff miniapps:"
   "miniapps/autodiff"
   "par_example.cpp"'
'"dpg"
   "DPG miniapps:"
   "miniapps/dpg"
   "p{acoustics,convection-diffusion,diffusion,maxwell}.cpp"'
'"gslib"
   "GSLIB miniapps:"
   "miniapps/gslib"
   "pfindpts.cpp schwarz_ex1p.cpp"'
'"hdiv-linear-solver"
   "H(div) linear solver miniapps:"
   "miniapps/hdiv-linear-solver"
   "grad_div.cpp darcy.cpp"'
# 'miniapps/hooke/hooke.cpp' has no sample runs
# todo: miniapps/mtop
# todo: miniapps/multidomain
'"navier"
   "Navier miniapps:"
   "miniapps/navier"
   "navier_cht.cpp"'
# todo: add other navier miniapps
'"nurbs"
   "NURBS miniapps:"
   "miniapps/nurbs"
   "nurbs_ex1p.cpp nurbs_ex11p.cpp"'
'"shifted"
   "Shifted miniapps:"
   "miniapps/shifted"
   "distance.cpp"'
# todo: add other shifted miniapps
'"solvers"
   "Solvers miniapps:"
   "miniapps/solvers"
   "block-solvers.cpp"'
# todo: add other solvers miniapps
# todo: miniapps/spde
'"tools"
   "Tools miniapps:"
   "miniapps/tools"
   "convert-dc.cpp get-values.cpp load-dc.cpp"'
# todo: add other tools miniapps
'"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-9]}[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"'
'"moonolith"
   "Moonolith examples:"
   "examples/moonolith"
   "ex1.cpp ex{1,2}p.cpp"'
'"pumi"
   "PUMI examples:"
   "examples/pumi"
   "ex1.cpp ex2.cpp ex1p.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 mesh-quality.cpp
    polar-nc.cpp reflector.cpp shaper.cpp trimmer.cpp twist.cpp
    {,p}mesh-optimizer.cpp pmesh-fitting.cpp {,p}minimal-surface.cpp
    fit-node-position.cpp"'
'"electromagnetics"
   "Electromagnetics miniapps:"
   "miniapps/electromagnetics"
   "joule.cpp"'
#   "{joule,maxwell,tesla,volta}.cpp"' # todo: multiline sample runs
'"adjoint"
   "Adjoint miniapps:"
   "miniapps/adjoint"
   "cvsRoberts_ASAi_dns.cpp adjoint_advection_diffusion.cpp"'
'"autodiff"
   "Autodiff miniapps:"
   "miniapps/autodiff"
   "seq_example.cpp seq_test.cpp par_example.cpp"'
# 'seq_test.cpp' has no sample runs
'"dpg"
   "DPG miniapps:"
   "miniapps/dpg"
   "{,p}{acoustics,convection-diffusion,diffusion,maxwell}.cpp"'
'"gslib"
   "GSLIB miniapps:"
   "miniapps/gslib"
   "field-diff.cpp field-interp.cpp findpts.cpp schwarz_ex1.cpp pfindpts.cpp
    schwarz_ex1p.cpp"'
'"hdiv-linear-solver"
   "H(div) linear solver miniapps:"
   "miniapps/hdiv-linear-solver"
   "grad_div.cpp darcy.cpp"'
# 'miniapps/hooke/hooke.cpp' has no sample runs
# todo: miniapps/mtop
# todo: miniapps/multidomain
'"navier"
   "Navier miniapps:"
   "miniapps/navier"
   "navier_cht.cpp"'
# todo: add other navier miniapps
'"nurbs"
   "NURBS miniapps:"
   "miniapps/nurbs"
   "nurbs_ex1.cpp nurbs_ex1p.cpp nurbs_ex11p.cpp"'
# todo: add other nurbs miniapps
'"shifted"
   "Shifted miniapps:"
   "miniapps/shifted"
   "distance.cpp"'
# todo: add other shifted miniapps
'"solvers"
   "Solvers miniapps:"
   "miniapps/solvers"
   "block-solvers.cpp"'
# todo: add other solvers miniapps
# todo: miniapps/spde
'"tools"
   "Tools miniapps:"
   "miniapps/tools"
   "convert-dc.cpp display-basis.cpp get-values.cpp load-dc.cpp
    lor-transfer.cpp"'
# todo: add other tools miniapps
'"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}"$'\n'"${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 <<EOF

   $0 [options]

   Options:
      -h|-help    Print this usage information and exit
      -p|-par     Build the parallel MFEM library + examples + miniapps.
                  The default is to build the serial MFEM library + examples +
                  miniapps. The build can be customized by setting the variable
                  'mfem_config' or by building separately and using '-b'
      -g <dir> <pattern>
                  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 <dir>    [${output_dir:-"<empty>: output goes to stdout"}]
                  If not empty, save output to files inside <dir>
      -d <dir>    [${mfem_build_dir}]
                  If <dir> is different from <mfem_dir> then use an
                  out-of-source build in <dir>
      -j <np>     [${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"
      <var>=<value>
                  Set a shell script variable; see below for valid variables
       *          Any other parameter is treated as <mfem_dir>
      <mfem_dir>  [${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 <var>=<value>:
      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 be set using the respective uppercase environment
         variable
      mfem_build_dir [${mfem_build_dir}]
         Same as '-d': set this variable to something different from <mfem_dir>
         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
