Menu

runParameterSweep.sh

runParameterSweep.sh

#!/bin/bash
# # runParameterSweep.sh
#
# Generate a parameter sweep from `sweep.params` (or a custom sweep file) and
# run each case sequentially in `simulationCases/<CaseNo>/`.
#
# ## Usage
# `./runParameterSweep.sh [options] [sweep_file]`
#
# ## Inputs
# - `sweep_file`: sweep configuration (default: `sweep.params`)
# - `BASE_CONFIG` inside the sweep file (starting parameter set)
#
# ## Outputs
# - Case directories under `simulationCases/`
#
# ## Related
# - `runSimulation.sh`
# - `src-local/parse_params.sh`

set -euo pipefail  # Exit on error, unset variables, pipeline failures

# ============================================================
# Configuration
# ============================================================
SCRIPT_DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"

# 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] [sweep_file]

Run parameter sweep with auto-incrementing CaseNo.
Creates case folders in simulationCases/<CaseNo>/ for each parameter combination.
Cases run sequentially (one at a time).

Options:
    -n, --dry-run        Show parameter combinations without running
    -v, --verbose        Verbose output
    -c, --compile-only   Compile only, don't run simulations
    -m, --mpi            Enable MPI parallel execution for all cases
    --cores N            Number of MPI cores (default: 4, requires --mpi)
    -h, --help          Show this help message

Parameter sweep file (default):
    \$0 sweep.params

If no sweep file specified, uses sweep.params from current directory.

Sweep file format:
    BASE_CONFIG=default.params
    CASE_START=1000
    CASE_END=1011
    SWEEP_We=1.0,10.0,100.0
    SWEEP_Ohd=1e-4,1e-3,1e-2

CaseNo auto-increments from CASE_START for each parameter combination.

Examples:
    # Run sweep with default file (serial)
    \$0

    # Dry run to see parameter combinations
    \$0 --dry-run

    # Run sweep with MPI parallel execution (4 cores per case)
    \$0 --mpi

    # Run sweep with 8 cores per case
    \$0 --mpi --cores 8

    # Run custom sweep file
    \$0 custom_sweep.params

For more information, see README.md
EOF
}

# ============================================================
# Parse Command Line Options
# ============================================================
DRY_RUN=0
VERBOSE=0
COMPILE_ONLY=0
MPI_ENABLED=0
MPI_CORES=4

while [[ \$# -gt 0 ]]; do
    case \$1 in
        -n|--dry-run)
            DRY_RUN=1
            shift
            ;;
        -v|--verbose)
            VERBOSE=1
            shift
            ;;
        -c|--compile-only)
            COMPILE_ONLY=1
            shift
            ;;
        -m|--mpi)
            MPI_ENABLED=1
            shift
            ;;
        --cores)
            MPI_CORES="\$2"
            if ! [[ "\$MPI_CORES" =~ ^[0-9]+\$ ]] || [ "\$MPI_CORES" -lt 1 ]; then
                echo "ERROR: --cores requires a positive integer, got: \$MPI_CORES" >&2
                exit 1
            fi
            shift 2
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        -*)
            echo "ERROR: Unknown option: \$1" >&2
            usage
            exit 1
            ;;
        *)
            break
            ;;
    esac
done

# ============================================================
# Determine Sweep File
# ============================================================
SWEEP_FILE="\${1:-sweep.params}"

if [ ! -f "\$SWEEP_FILE" ]; then
    echo "ERROR: Sweep file not found: \$SWEEP_FILE" >&2
    exit 1
fi

echo "========================================="
echo "Drop Impact - Parameter Sweep"
echo "========================================="
echo "Sweep file: \$SWEEP_FILE"
[ \$DRY_RUN -eq 1 ] && echo "Mode: Dry run (no execution)"
echo ""

# ============================================================
# Parse Sweep Configuration
# ============================================================
# Source the sweep file to get variables
source "\$SWEEP_FILE"

# Validate required variables
if [ -z "\$BASE_CONFIG" ]; then
    echo "ERROR: BASE_CONFIG not defined in sweep file" >&2
    exit 1
fi

if [ -z "\$CASE_START" ] || [ -z "\$CASE_END" ]; then
    echo "ERROR: CASE_START and CASE_END must be defined in sweep file" >&2
    exit 1
fi

# Validate CaseNo range
if [ "\$CASE_START" -lt 1000 ] || [ "\$CASE_START" -gt 9999 ]; then
    echo "ERROR: CASE_START must be 4-digit (1000-9999), got: \$CASE_START" >&2
    exit 1
fi

if [ "\$CASE_END" -lt "\$CASE_START" ] || [ "\$CASE_END" -gt 9999 ]; then
    echo "ERROR: CASE_END must be >= CASE_START and <= 9999, got: \$CASE_END" >&2
    exit 1
fi

if [ ! -f "\$BASE_CONFIG" ]; then
    echo "ERROR: Base configuration file not found: \$BASE_CONFIG" >&2
    exit 1
fi

echo "Base configuration: \$BASE_CONFIG"
echo "Case number range: \$CASE_START to \$CASE_END"
echo ""

# ============================================================
# Extract Sweep Variables
# ============================================================
SWEEP_VARS=()
SWEEP_VALUES=()

# Read sweep file and extract SWEEP_* variables
while IFS='=' read -r key value; do
    # Skip comments and empty lines
    [[ "\$key" =~ ^[[:space:]]*# ]] && continue
    [[ -z "\$key" ]] && continue

    # Match SWEEP_* variables
    if [[ "\$key" =~ ^[[:space:]]*SWEEP_([^=]+) ]]; then
        var_name="\${BASH_REMATCH[1]}"
        # Remove inline comments and whitespace
        value=\$(echo "\$value" | sed 's/#.*//' | xargs)

        SWEEP_VARS+=("\$var_name")
        SWEEP_VALUES+=("\$value")
    fi
done < "\$SWEEP_FILE"

if [ \${#SWEEP_VARS[@]} -eq 0 ]; then
    echo "ERROR: No SWEEP_* variables found in \$SWEEP_FILE" >&2
    exit 1
fi

echo "Sweep variables:"
for i in "\${!SWEEP_VARS[@]}"; do
    echo "  \${SWEEP_VARS[\$i]} = \${SWEEP_VALUES[\$i]}"
done
echo ""

# ============================================================
# Generate Parameter Combinations
# ============================================================
# Create temporary directory for generated parameter files
TEMP_DIR=\$(mktemp -d "\${TMPDIR:-/tmp}/sweep.XXXXXX")
trap "rm -rf \$TEMP_DIR" EXIT

CASE_NUM=\$CASE_START
COMBINATION_COUNT=0

# Recursive function to generate all combinations
generate_combinations() {
    local depth=\$1
    shift
    local current_values=("\$@")

    if [ \$depth -eq \${#SWEEP_VARS[@]} ]; then
        # Base case: all variables assigned, create parameter file
        local case_file="\${TEMP_DIR}/case_\$(printf "%04d" \$CASE_NUM).params"

        # Copy base config
        cp "\$BASE_CONFIG" "\$case_file"

        # Override CaseNo
        if grep -q "^CaseNo=" "\$case_file"; then
            sed -i.bak "s|^CaseNo=.*|CaseNo=\${CASE_NUM}|" "\$case_file"
        else
            echo "CaseNo=\${CASE_NUM}" >> "\$case_file"
        fi
        rm -f "\${case_file}.bak"

        # Override with sweep values
        for i in "\${!SWEEP_VARS[@]}"; do
            local var="\${SWEEP_VARS[\$i]}"
            local val="\${current_values[\$i]}"

            if grep -q "^\${var}=" "\$case_file"; then
                sed -i.bak "s|^\${var}=.*|\${var}=\${val}|" "\$case_file"
            else
                echo "\${var}=\${val}" >> "\$case_file"
            fi
            rm -f "\${case_file}.bak"
        done

        ((COMBINATION_COUNT++))

        # Print summary
        if [ \$DRY_RUN -eq 1 ] || [ \$VERBOSE -eq 1 ]; then
            echo "Case \$CASE_NUM:"
            for i in "\${!SWEEP_VARS[@]}"; do
                echo "  \${SWEEP_VARS[\$i]} = \${current_values[\$i]}"
            done
            echo ""
        fi

        ((CASE_NUM++))
        return
    fi

    # Recursive case: iterate through values for current variable
    local values="\${SWEEP_VALUES[\$depth]}"
    IFS=',' read -ra value_array <<< "\$values"

    for val in "\${value_array[@]}"; do
        val=\$(echo "\$val" | xargs)  # Trim whitespace
        generate_combinations \$((depth + 1)) "\${current_values[@]}" "\$val"
    done
}

# Start recursion
generate_combinations 0

echo "Generated \$COMBINATION_COUNT parameter combinations"

# Check if number of combinations matches the range
EXPECTED_COUNT=\$((CASE_END - CASE_START + 1))
if [ \$COMBINATION_COUNT -ne \$EXPECTED_COUNT ]; then
    echo "WARNING: Generated \$COMBINATION_COUNT combinations, but CASE_END suggests \$EXPECTED_COUNT" >&2
    echo "         Consider adjusting CASE_END in sweep file" >&2
fi

if [ \$COMBINATION_COUNT -gt \$EXPECTED_COUNT ]; then
    echo "ERROR: Too many combinations (\$COMBINATION_COUNT) for range \$CASE_START-\$CASE_END" >&2
    exit 1
fi

echo ""

# Exit if dry run
if [ \$DRY_RUN -eq 1 ]; then
    echo "Dry run complete. No simulations executed."
    exit 0
fi

# ============================================================
# Run Simulations
# ============================================================
echo "========================================="
echo "Running Simulations"
echo "========================================="

# Build list of parameter files
PARAM_FILES=()
for case_file in "\$TEMP_DIR"/case_*.params; do
    PARAM_FILES+=("\$case_file")
done

# Build flags to pass to runSimulation.sh
RUN_FLAGS=""
if [ \$COMPILE_ONLY -eq 1 ]; then
    RUN_FLAGS="\$RUN_FLAGS --compile-only"
fi
if [ \$MPI_ENABLED -eq 1 ]; then
    RUN_FLAGS="\$RUN_FLAGS --mpi --cores \$MPI_CORES"
fi

# Run simulations sequentially (one at a time)
echo "Running \$COMBINATION_COUNT simulations sequentially"
if [ \$MPI_ENABLED -eq 1 ]; then
    echo "Each case will use MPI with \$MPI_CORES cores"
fi
echo ""

for param_file in "\${PARAM_FILES[@]}"; do
    ./runSimulation.sh \$RUN_FLAGS "\$param_file"
done

echo ""
echo "========================================="
echo "Parameter Sweep Complete"
echo "========================================="
echo "Total cases: \$COMBINATION_COUNT"
echo "Case range: \$CASE_START to \$((CASE_START + COMBINATION_COUNT - 1))"
echo "Output location: simulationCases/"
echo ""