#!/bin/bash
# runSimulation.sh - Run single bubble coalescence simulation from root directory
# Creates case folder in simulationCases/<CaseNo>/ and runs simulation there
#
# IMPORTANT: This script uses TWO-STAGE EXECUTION because distance.h is
# incompatible with MPI. Run Stage 1 first to generate the restart file,
# then run Stage 2 for the full simulation.
set -euo pipefail # Exit on error, unset variables, pipeline failures
# ============================================================
# Configuration
# ============================================================
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
# Stage 1 tmax - short run to generate restart file
STAGE1_TMAX="5e-2"
# Source version configuration for install instructions
if [ -f "\${SCRIPT_DIR}/src-local/basilisk_version.sh" ]; then
# shellcheck disable=SC1091
source "\${SCRIPT_DIR}/src-local/basilisk_version.sh"
fi
# Source project configuration
if [ -f "\${SCRIPT_DIR}/.project_config" ]; then
# shellcheck disable=SC1090
source "\${SCRIPT_DIR}/.project_config"
else
echo "WARNING: .project_config not found. BASILISK path may not be set." >&2
echo " Install Basilisk first (creates .project_config):" >&2
echo " curl -sL \${BASILISK_INSTALL_URL:-https://raw.githubusercontent.com/comphy-lab/basilisk-C/main/reset_install_basilisk-ref-locked.sh} | bash -s -- --ref=\${BASILISK_REF:-v2026-01-13}" >&2
fi
# Source parameter parsing library
if [ -f "\${SCRIPT_DIR}/src-local/parse_params.sh" ]; then
source "\${SCRIPT_DIR}/src-local/parse_params.sh"
else
echo "ERROR: src-local/parse_params.sh not found" >&2
exit 1
fi
# ============================================================
# Usage Information
# ============================================================
usage() {
cat <<EOF
Usage: \$0 [OPTIONS] [params_file]
Run single bubble coalescence simulation from root directory.
Creates case folder in simulationCases/<CaseNo>/ based on parameter file.
This script uses TWO-STAGE EXECUTION:
Stage 1: Generate restart file (distance.h incompatible with MPI)
Stage 2: Full simulation from restart file
Stage Selection (mutually exclusive):
(default) Run both Stage 1 + Stage 2
--stage1 Run Stage 1 only (generate restart file)
--stage2 Run Stage 2 only (full simulation from restart)
Parallelization:
--fopenmp [N] Enable OpenMP with N threads (default: 8)
Stage 1: Linux only (macOS always serial)
Stage 2: Linux only (macOS runs serial with warning)
--mpi [N] Enable MPI with N cores (default: 2, Stage 2 only)
Other Options:
-c, --compile-only Compile but don't run simulation
-d, --debug Compile with debug flags (-g -DTRASH=1)
-f, --force Force overwrite of case.params and source file
(by default, existing files are preserved for reruns)
-v, --verbose Verbose output
-h, --help Show this help message
Parameter file mode (default):
\$0 default.params
If no parameter file specified, uses default.params from current directory.
Environment variables:
QCC_FLAGS Additional qcc compiler flags
Examples:
# Run both stages (default)
\$0 default.params # Serial, Stage 1 + 2
\$0 --mpi 8 default.params # Stage 1 serial, Stage 2 MPI
# Stage 1 only: Generate restart file
\$0 --stage1 default.params # Serial
\$0 --stage1 --fopenmp default.params # OpenMP (Linux only)
# Stage 2 only: Full simulation (requires existing restart)
\$0 --stage2 default.params # Serial
\$0 --stage2 --mpi 8 default.params # MPI, 8 cores
# Compile only (check for errors)
\$0 --compile-only default.params
For more information, see README.md
EOF
}
# ============================================================
# Parse Command Line Options
# ============================================================
COMPILE_ONLY=0
DEBUG_FLAGS=""
VERBOSE=0
FORCE_OVERWRITE=0
STAGE=0 # Default to both stages (0 = both, 1 = stage1, 2 = stage2)
FOPENMP_ENABLED=0
FOPENMP_THREADS=8 # Default thread count
MPI_ENABLED=0
MPI_CORES=2 # Default core count
STAGE_EXPLICITLY_SET=0
QCC_FLAGS="\${QCC_FLAGS:-}"
while [[ \$# -gt 0 ]]; do
case \$1 in
-c|--compile-only)
COMPILE_ONLY=1
shift
;;
-d|--debug)
DEBUG_FLAGS="-g -DTRASH=1"
shift
;;
-f|--force)
FORCE_OVERWRITE=1
shift
;;
--stage1)
if [ \$STAGE_EXPLICITLY_SET -eq 1 ] && [ \$STAGE -eq 2 ]; then
echo "ERROR: Cannot use both --stage1 and --stage2" >&2
exit 1
fi
STAGE=1
STAGE_EXPLICITLY_SET=1
shift
;;
--stage2)
if [ \$STAGE_EXPLICITLY_SET -eq 1 ] && [ \$STAGE -eq 1 ]; then
echo "ERROR: Cannot use both --stage1 and --stage2" >&2
exit 1
fi
STAGE=2
STAGE_EXPLICITLY_SET=1
shift
;;
--fopenmp)
FOPENMP_ENABLED=1
# Check if next arg is a number (optional thread count)
if [[ "\${2:-}" =~ ^[0-9]+\$ ]]; then
FOPENMP_THREADS="\$2"
shift
fi
shift
;;
--mpi)
MPI_ENABLED=1
# Check if next arg is a number (optional core count)
if [[ "\${2:-}" =~ ^[0-9]+\$ ]]; then
MPI_CORES="\$2"
shift
fi
shift
;;
-v|--verbose)
VERBOSE=1
shift
;;
-h|--help)
usage
exit 0
;;
-*)
echo "ERROR: Unknown option: \$1" >&2
usage
exit 1
;;
*)
break
;;
esac
done
# ============================================================
# Detect OS
# ============================================================
OS_TYPE=\$(uname -s)
# ============================================================
# Validation
# ============================================================
# Check --mpi only valid with stage2 (not stage1-only)
if [ \$MPI_ENABLED -eq 1 ] && [ \$STAGE -eq 1 ]; then
echo "ERROR: --mpi is only valid with --stage2 (Stage 1 cannot use MPI due to distance.h)" >&2
exit 1
fi
# For both-stages mode with MPI, MPI only applies to Stage 2
if [ \$MPI_ENABLED -eq 1 ] && [ \$STAGE -eq 0 ]; then
echo "Note: MPI will be used for Stage 2 only (Stage 1 runs serial)"
fi
# Check --mpi and --fopenmp are mutually exclusive
if [ \$MPI_ENABLED -eq 1 ] && [ \$FOPENMP_ENABLED -eq 1 ]; then
echo "ERROR: --mpi and --fopenmp are mutually exclusive" >&2
exit 1
fi
# Check --fopenmp on macOS
if [ "\$OS_TYPE" = "Darwin" ] && [ \$FOPENMP_ENABLED -eq 1 ]; then
if [ \$STAGE -eq 1 ]; then
echo "ERROR: --fopenmp not supported on macOS for Stage 1 (always runs serial)" >&2
exit 1
else
echo "WARNING: OpenMP not available on macOS, Stage 2 will run serial" >&2
FOPENMP_ENABLED=0
fi
fi
# Verify MPI tools if MPI is enabled
if [ \$MPI_ENABLED -eq 1 ]; then
if ! command -v mpicc &> /dev/null; then
echo "ERROR: mpicc not found. MPI compilation requires mpicc (OpenMPI or MPICH)." >&2
echo " Install MPI tools or run without --mpi flag for serial execution." >&2
exit 1
fi
if ! command -v mpirun &> /dev/null; then
echo "ERROR: mpirun not found. MPI execution requires mpirun (OpenMPI or MPICH)." >&2
echo " Install MPI tools or run without --mpi flag for serial execution." >&2
exit 1
fi
fi
# ============================================================
# Determine Parameter File
# ============================================================
PARAM_FILE="\${1:-default.params}"
if [ ! -f "\$PARAM_FILE" ]; then
echo "ERROR: Parameter file not found: \$PARAM_FILE" >&2
exit 1
fi
[ \$VERBOSE -eq 1 ] && echo "Parameter file: \$PARAM_FILE"
# ============================================================
# Parse Parameters
# ============================================================
parse_param_file "\$PARAM_FILE"
CASE_NO=\$(get_param "CaseNo")
OhOut=\$(get_param "OhOut" "1e-2")
RhoIn=\$(get_param "RhoIn" "1e-3")
Rr=\$(get_param "Rr" "1.0")
MAXlevel=\$(get_param "MAXlevel" "10")
tmax=\$(get_param "tmax" "40.0")
zWall=\$(get_param "zWall" "0.01")
if [ -z "\$CASE_NO" ]; then
echo "ERROR: CaseNo not found in parameter file" >&2
exit 1
fi
# Validate CaseNo is 4 digits
if ! [[ "\$CASE_NO" =~ ^[0-9]{4}\$ ]] || [ "\$CASE_NO" -lt 1000 ] || [ "\$CASE_NO" -gt 9999 ]; then
echo "ERROR: CaseNo must be 4-digit (1000-9999), got: \$CASE_NO" >&2
exit 1
fi
CASE_DIR="simulationCases/\${CASE_NO}"
# ============================================================
# Create Case Directory
# ============================================================
if [ ! -d "\$CASE_DIR" ]; then
echo "Creating case directory: \$CASE_DIR"
mkdir -p "\$CASE_DIR"
else
echo "Case directory exists"
fi
# Copy or preserve case.params
if [ \$FORCE_OVERWRITE -eq 1 ]; then
cp "\$PARAM_FILE" "\$CASE_DIR/case.params"
echo "Copied case.params (--force: overwriting existing)"
elif [ ! -f "\$CASE_DIR/case.params" ]; then
cp "\$PARAM_FILE" "\$CASE_DIR/case.params"
else
echo "Using existing case.params (manual edits preserved; use --force to overwrite)"
# Re-parse from case.params to get potentially modified values
parse_param_file "\$CASE_DIR/case.params"
OhOut=\$(get_param "OhOut" "1e-2")
RhoIn=\$(get_param "RhoIn" "1e-3")
Rr=\$(get_param "Rr" "1.0")
MAXlevel=\$(get_param "MAXlevel" "10")
tmax=\$(get_param "tmax" "40.0")
zWall=\$(get_param "zWall" "0.01")
fi
# ============================================================
# Display Configuration
# ============================================================
# NOTE: Display AFTER preservation logic so values reflect what will actually be used
echo ""
echo "========================================="
echo "Bubble Coalescence Simulation"
echo "========================================="
echo "Case Number: \$CASE_NO"
echo "Case Directory: \$CASE_DIR"
echo "Parameter File: \$PARAM_FILE"
echo ""
echo "Physical Parameters:"
echo " OhOut=\$OhOut, RhoIn=\$RhoIn, Rr=\$Rr"
echo " MAXlevel=\$MAXlevel, tmax=\$tmax, zWall=\$zWall"
echo ""
if [ \$STAGE -eq 0 ]; then
echo "Stage: Both (Stage 1 + Stage 2)"
elif [ \$STAGE -eq 1 ]; then
echo "Stage: 1 only (generate restart)"
else
echo "Stage: 2 only (full simulation)"
fi
if [ \$MPI_ENABLED -eq 1 ]; then
echo "Parallelization: MPI (\$MPI_CORES cores)"
elif [ \$FOPENMP_ENABLED -eq 1 ]; then
echo "Parallelization: OpenMP (\$FOPENMP_THREADS threads)"
else
echo "Parallelization: Serial"
fi
echo ""
# Change to case directory
cd "\$CASE_DIR"
[ \$VERBOSE -eq 1 ] && echo "Working directory: \$(pwd)"
# ============================================================
# Source File Setup
# ============================================================
SRC_FILE_ORIG="../coalescenceBubble.c"
SRC_FILE_LOCAL="coalescenceBubble.c"
EXECUTABLE="coalescenceBubble"
# Check if source file exists
if [ ! -f "\$SRC_FILE_ORIG" ]; then
echo "ERROR: Source file \$SRC_FILE_ORIG not found" >&2
exit 1
fi
# Copy or preserve source file
if [ \$FORCE_OVERWRITE -eq 1 ]; then
cp "\$SRC_FILE_ORIG" "\$SRC_FILE_LOCAL"
echo "Copied source file (--force: overwriting existing)"
elif [ ! -f "\$SRC_FILE_LOCAL" ]; then
cp "\$SRC_FILE_ORIG" "\$SRC_FILE_LOCAL"
echo "Copied source file to case directory"
else
echo "Using existing coalescenceBubble.c (local edits preserved; use --force to overwrite)"
fi
# Create symlink to DataFiles (required for initial condition loading)
if [ ! -e "DataFiles" ]; then
ln -s ../DataFiles DataFiles
echo "Created symlink to DataFiles"
fi
# ============================================================
# Stage 1: Generate Restart File
# ============================================================
if [ \$STAGE -eq 1 ] || [ \$STAGE -eq 0 ]; then
echo ""
echo "========================================="
echo "Stage 1: Generate Initial Condition"
echo "========================================="
# Compilation
if [ \$FOPENMP_ENABLED -eq 1 ]; then
echo "Compiling with OpenMP..."
[ \$VERBOSE -eq 1 ] && echo "Compiler: qcc"
[ \$VERBOSE -eq 1 ] && echo "Include paths: -I../../src-local"
[ \$VERBOSE -eq 1 ] && echo "Flags: -Wall -O2 -disable-dimensions -fopenmp \$DEBUG_FLAGS \$QCC_FLAGS"
qcc -I../../src-local \
-Wall -O2 -disable-dimensions -fopenmp \
\$DEBUG_FLAGS \$QCC_FLAGS \
"\$SRC_FILE_LOCAL" -o "\$EXECUTABLE" -lm
else
echo "Compiling for serial execution..."
[ \$VERBOSE -eq 1 ] && echo "Compiler: qcc"
[ \$VERBOSE -eq 1 ] && echo "Include paths: -I../../src-local"
[ \$VERBOSE -eq 1 ] && echo "Flags: -Wall -O2 -disable-dimensions \$DEBUG_FLAGS \$QCC_FLAGS"
qcc -I../../src-local \
-Wall -O2 -disable-dimensions \
\$DEBUG_FLAGS \$QCC_FLAGS \
"\$SRC_FILE_LOCAL" -o "\$EXECUTABLE" -lm
fi
if [ \$? -ne 0 ]; then
echo "ERROR: Stage 1 compilation failed" >&2
exit 1
fi
echo "Compilation successful: \$EXECUTABLE"
# Exit if compile-only mode
if [ \$COMPILE_ONLY -eq 1 ]; then
echo ""
echo "Compile-only mode: Stopping here"
cd ../..
exit 0
fi
# Execution
echo ""
echo "Running briefly to generate restart file..."
if [ \$FOPENMP_ENABLED -eq 1 ]; then
echo " OMP_NUM_THREADS=\$FOPENMP_THREADS"
export OMP_NUM_THREADS=\$FOPENMP_THREADS
else
echo " Running single-threaded"
fi
echo " Command: ./\${EXECUTABLE} \$OhOut \$RhoIn \$Rr \$MAXlevel \$STAGE1_TMAX \$zWall"
./\${EXECUTABLE} \$OhOut \$RhoIn \$Rr \$MAXlevel \$STAGE1_TMAX \$zWall
# Validate the restart file was created successfully
if ! validate_restart_file "restart"; then
echo "ERROR: Stage 1 failed - restart file validation failed" >&2
exit 1
fi
echo ""
echo "========================================="
echo "Stage 1 complete: restart file created"
echo "Restart file location: \$CASE_DIR/restart"
if [ \$STAGE -eq 1 ]; then
echo ""
echo "To run Stage 2:"
echo " \$0 --stage2 \$PARAM_FILE"
echo " \$0 --stage2 --mpi \$PARAM_FILE"
fi
echo "========================================="
fi
# ============================================================
# Stage 2: Full Simulation
# ============================================================
if [ \$STAGE -eq 2 ] || [ \$STAGE -eq 0 ]; then
# Validate restart file (exists, non-empty, readable)
if ! validate_restart_file "restart"; then
echo " Run Stage 1 first: \$0 --stage1 \$PARAM_FILE" >&2
exit 1
fi
echo ""
echo "========================================="
echo "Stage 2: Full Simulation"
echo "========================================="
# Compilation
if [ \$MPI_ENABLED -eq 1 ]; then
echo "Compiling with MPI..."
if [ "\$OS_TYPE" = "Darwin" ]; then
# macOS
[ \$VERBOSE -eq 1 ] && echo "Compiler: CC99='mpicc -std=c99' qcc"
[ \$VERBOSE -eq 1 ] && echo "Include paths: -I../../src-local"
[ \$VERBOSE -eq 1 ] && echo "Flags: -Wall -O2 -D_MPI=1 -disable-dimensions \$DEBUG_FLAGS \$QCC_FLAGS"
CC99='mpicc -std=c99' qcc -I../../src-local \
-Wall -O2 -D_MPI=1 -disable-dimensions \
\$DEBUG_FLAGS \$QCC_FLAGS \
"\$SRC_FILE_LOCAL" -o "\$EXECUTABLE" -lm
else
# Linux
[ \$VERBOSE -eq 1 ] && echo "Compiler: CC99='mpicc -std=c99 -D_GNU_SOURCE=1' qcc"
[ \$VERBOSE -eq 1 ] && echo "Include paths: -I../../src-local"
[ \$VERBOSE -eq 1 ] && echo "Flags: -Wall -O2 -D_MPI=1 -disable-dimensions \$DEBUG_FLAGS \$QCC_FLAGS"
CC99='mpicc -std=c99 -D_GNU_SOURCE=1' qcc -I../../src-local \
-Wall -O2 -D_MPI=1 -disable-dimensions \
\$DEBUG_FLAGS \$QCC_FLAGS \
"\$SRC_FILE_LOCAL" -o "\$EXECUTABLE" -lm
fi
elif [ \$FOPENMP_ENABLED -eq 1 ]; then
echo "Compiling with OpenMP..."
[ \$VERBOSE -eq 1 ] && echo "Compiler: qcc"
[ \$VERBOSE -eq 1 ] && echo "Include paths: -I../../src-local"
[ \$VERBOSE -eq 1 ] && echo "Flags: -Wall -O2 -disable-dimensions -fopenmp \$DEBUG_FLAGS \$QCC_FLAGS"
qcc -I../../src-local \
-Wall -O2 -disable-dimensions -fopenmp \
\$DEBUG_FLAGS \$QCC_FLAGS \
"\$SRC_FILE_LOCAL" -o "\$EXECUTABLE" -lm
else
echo "Compiling for serial execution..."
[ \$VERBOSE -eq 1 ] && echo "Compiler: qcc"
[ \$VERBOSE -eq 1 ] && echo "Include paths: -I../../src-local"
[ \$VERBOSE -eq 1 ] && echo "Flags: -Wall -O2 -disable-dimensions \$DEBUG_FLAGS \$QCC_FLAGS"
qcc -I../../src-local \
-Wall -O2 -disable-dimensions \
\$DEBUG_FLAGS \$QCC_FLAGS \
"\$SRC_FILE_LOCAL" -o "\$EXECUTABLE" -lm
fi
if [ \$? -ne 0 ]; then
echo "ERROR: Stage 2 compilation failed" >&2
exit 1
fi
echo "Compilation successful: \$EXECUTABLE"
# Exit if compile-only mode
if [ \$COMPILE_ONLY -eq 1 ]; then
echo ""
echo "Compile-only mode: Stopping here"
cd ../..
exit 0
fi
# Execution
echo ""
echo "Starting full simulation..."
echo " Command args: \$OhOut \$RhoIn \$Rr \$MAXlevel \$tmax \$zWall"
echo "========================================="
if [ \$MPI_ENABLED -eq 1 ]; then
[ \$VERBOSE -eq 1 ] && echo "Command: mpirun -np \$MPI_CORES ./\$EXECUTABLE \$OhOut \$RhoIn \$Rr \$MAXlevel \$tmax \$zWall"
mpirun -np \$MPI_CORES ./\$EXECUTABLE \$OhOut \$RhoIn \$Rr \$MAXlevel \$tmax \$zWall
elif [ \$FOPENMP_ENABLED -eq 1 ]; then
export OMP_NUM_THREADS=\$FOPENMP_THREADS
[ \$VERBOSE -eq 1 ] && echo "OMP_NUM_THREADS=\$FOPENMP_THREADS"
[ \$VERBOSE -eq 1 ] && echo "Command: ./\$EXECUTABLE \$OhOut \$RhoIn \$Rr \$MAXlevel \$tmax \$zWall"
./\$EXECUTABLE \$OhOut \$RhoIn \$Rr \$MAXlevel \$tmax \$zWall
else
[ \$VERBOSE -eq 1 ] && echo "Command: ./\$EXECUTABLE \$OhOut \$RhoIn \$Rr \$MAXlevel \$tmax \$zWall"
./\$EXECUTABLE \$OhOut \$RhoIn \$Rr \$MAXlevel \$tmax \$zWall
fi
EXIT_CODE=\$?
echo "========================================="
if [ \$EXIT_CODE -eq 0 ]; then
echo "Simulation completed successfully"
echo "Output location: \$CASE_DIR"
else
echo "Simulation failed with exit code \$EXIT_CODE"
fi
echo "========================================="
# Return to root directory
cd ../..
exit \$EXIT_CODE
fi
# Return to root directory
cd ../..
exit 0