commit ed32338a7ab21414ac1835cc082f88b8d4179f71
parent 5807227ec123f67d5b0d3a663712c4ef951e34fc
Author: Anders Damsgaard <anders@adamsgaard.dk>
Date: Wed, 14 Jan 2026 08:44:34 +0100
test: add accuracy and benchmark helper scripts
Diffstat:
| A | test/accuracy.sh | | | 136 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | test/bench.sh | | | 150 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 286 insertions(+), 0 deletions(-)
diff --git a/test/accuracy.sh b/test/accuracy.sh
@@ -0,0 +1,136 @@
+#!/bin/sh
+# accuracy.sh - Compare numerical accuracy between two cngf-pf output files
+#
+# Usage:
+# ./accuracy.sh reference.txt test.txt
+# ./accuracy.sh -t 1e-6 reference.txt test.txt # Set tolerance
+#
+# Computes relative error for each field and reports max/mean errors.
+# Fields: z, v_x, sigma_n_eff, p_f, mu, gamma_dot_p, phi, I, tau, d_x
+
+set -e
+
+TOLERANCE=1e-10
+FIELDS="z v_x sigma_n_eff p_f mu gamma_dot_p phi I tau d_x"
+
+usage() {
+ echo "Usage: $0 [-t tolerance] reference.txt test.txt"
+ echo " -t tol Tolerance for pass/fail (default: 1e-10)"
+ exit 1
+}
+
+# Parse arguments
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -t) TOLERANCE="$2"; shift 2 ;;
+ -h|--help) usage ;;
+ -*) usage ;;
+ *) break ;;
+ esac
+done
+
+if [ $# -ne 2 ]; then
+ usage
+fi
+
+REF="$1"
+TEST="$2"
+
+if [ ! -f "$REF" ]; then
+ echo "Error: Reference file not found: $REF" >&2
+ exit 1
+fi
+
+if [ ! -f "$TEST" ]; then
+ echo "Error: Test file not found: $TEST" >&2
+ exit 1
+fi
+
+# Check line counts match
+ref_lines=$(wc -l < "$REF" | tr -d ' ')
+test_lines=$(wc -l < "$TEST" | tr -d ' ')
+
+if [ "$ref_lines" -ne "$test_lines" ]; then
+ echo "Error: Line count mismatch: reference=$ref_lines, test=$test_lines" >&2
+ exit 1
+fi
+
+# Create temporary files for awk processing
+tmp_ref=$(mktemp)
+tmp_test=$(mktemp)
+trap 'rm -f "$tmp_ref" "$tmp_test"' EXIT
+
+cp "$REF" "$tmp_ref"
+cp "$TEST" "$tmp_test"
+
+# Compute relative errors using awk
+awk -v tol="$TOLERANCE" '
+BEGIN {
+ split("z v_x sigma_n_eff p_f mu gamma_dot_p phi I tau d_x", field_names, " ")
+ for (i = 1; i <= 10; i++) {
+ max_err[i] = 0
+ sum_err[i] = 0
+ count[i] = 0
+ }
+}
+
+FNR == NR {
+ for (i = 1; i <= NF; i++) {
+ ref[FNR, i] = $i
+ }
+ nlines = FNR
+ next
+}
+
+{
+ for (i = 1; i <= NF; i++) {
+ test_val = $i
+ ref_val = ref[FNR, i]
+
+ # Compute relative error
+ if (ref_val == 0 && test_val == 0) {
+ rel_err = 0
+ } else if (ref_val == 0) {
+ rel_err = (test_val > 0) ? test_val : -test_val
+ } else {
+ diff = test_val - ref_val
+ if (diff < 0) diff = -diff
+ denom = ref_val
+ if (denom < 0) denom = -denom
+ rel_err = diff / denom
+ }
+
+ if (rel_err > max_err[i]) max_err[i] = rel_err
+ sum_err[i] += rel_err
+ count[i]++
+ }
+}
+
+END {
+ print "Field Max RelErr Mean RelErr Status"
+ print "---------------- ------------ ------------ ------"
+
+ all_pass = 1
+ for (i = 1; i <= 10; i++) {
+ if (count[i] > 0) {
+ mean_err = sum_err[i] / count[i]
+ } else {
+ mean_err = 0
+ }
+
+ status = (max_err[i] <= tol) ? "PASS" : "FAIL"
+ if (status == "FAIL") all_pass = 0
+
+ printf "%-16s %12.4e %12.4e %s\n", field_names[i], max_err[i], mean_err, status
+ }
+
+ print ""
+ if (all_pass) {
+ print "Overall: PASS (all fields within tolerance " tol ")"
+ exit 0
+ } else {
+ print "Overall: FAIL (some fields exceed tolerance " tol ")"
+ exit 1
+ }
+}
+' "$tmp_ref" "$tmp_test"
diff --git a/test/bench.sh b/test/bench.sh
@@ -0,0 +1,150 @@
+#!/bin/sh
+# bench.sh - Benchmark script for cngf-pf solver performance and stability
+#
+# Usage:
+# ./bench.sh # Run benchmarks on current build
+# ./bench.sh -c branch1 branch2 # Compare two branches
+# ./bench.sh -r N # Run N repetitions (default: 3)
+# ./bench.sh -o file.csv # Output results to CSV file
+# ./bench.sh -a file.csv # Append results to CSV file
+#
+# Output columns:
+# branch, test, elapsed_time, timesteps, poisson_iters, darcy_iters,
+# coupled_iters, stress_iters, iters_per_timestep
+
+set -e
+
+BIN=../cngf-pf
+REPS=3
+OUTFILE=""
+APPEND=0
+COMPARE=""
+BRANCH1=""
+BRANCH2=""
+
+# Benchmark test configurations
+# Note: transient mode (-T) skipped due to regression after inertia fix
+BENCH_TESTS="
+dry_small:-o 0.03 -L 0.64 -n 40e3
+dry_large:-L 10.0 -n 200e3 -e 10.0
+wet_small:-o 0.03 -L 0.64 -n 40e3 -F
+wet_large:-L 8.0 -n 150e3 -F -k 2e-17 -O 50e3 -a 50e3 -q 0.0000115741 -e 3600
+"
+
+usage() {
+ echo "Usage: $0 [-r reps] [-o outfile] [-a outfile] [-c branch1 branch2]"
+ echo " -r N Run N repetitions (default: 3)"
+ echo " -o file.csv Output results to CSV file"
+ echo " -a file.csv Append results to CSV file"
+ echo " -c b1 b2 Compare two git branches"
+ exit 1
+}
+
+# Parse arguments
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -r) REPS="$2"; shift 2 ;;
+ -o) OUTFILE="$2"; APPEND=0; shift 2 ;;
+ -a) OUTFILE="$2"; APPEND=1; shift 2 ;;
+ -c) COMPARE=1; BRANCH1="$2"; BRANCH2="$3"; shift 3 ;;
+ -h|--help) usage ;;
+ *) usage ;;
+ esac
+done
+
+# Parse benchmark output
+parse_output() {
+ local output="$1"
+ elapsed=$(echo "$output" | grep "elapsed_time" | sed 's/.*elapsed_time=//' | cut -d' ' -f1)
+ timesteps=$(echo "$output" | grep "timesteps=" | sed 's/.*timesteps=//' | cut -d' ' -f1)
+ poisson=$(echo "$output" | grep "poisson_iters=" | sed 's/.*poisson_iters=//' | cut -d' ' -f1)
+ darcy=$(echo "$output" | grep "darcy_iters=" | sed 's/.*darcy_iters=//' | cut -d' ' -f1)
+ coupled=$(echo "$output" | grep "coupled_iters=" | sed 's/.*coupled_iters=//' | cut -d' ' -f1)
+ stress=$(echo "$output" | grep "stress_iters=" | sed 's/.*stress_iters=//' | cut -d' ' -f1)
+
+ # Calculate total iterations per timestep
+ total_iters=$((poisson + darcy + coupled + stress))
+ if [ "$timesteps" -gt 0 ]; then
+ iters_per_step=$(echo "scale=2; $total_iters / $timesteps" | bc)
+ else
+ iters_per_step=0
+ fi
+
+ echo "$elapsed,$timesteps,$poisson,$darcy,$coupled,$stress,$iters_per_step"
+}
+
+# Run benchmarks for a given branch/state
+run_benchmarks() {
+ local branch_name="$1"
+
+ echo "$BENCH_TESTS" | while IFS=: read -r name opts; do
+ [ -z "$name" ] && continue
+
+ # Run multiple repetitions
+ for rep in $(seq 1 "$REPS"); do
+ output=$($BIN -B $opts 2>&1)
+ result=$(parse_output "$output")
+ echo "$branch_name,$name,$rep,$result"
+ done
+ done
+}
+
+# Header for CSV output
+csv_header() {
+ echo "branch,test,rep,elapsed_time,timesteps,poisson_iters,darcy_iters,coupled_iters,stress_iters,iters_per_timestep"
+}
+
+# Main execution
+if [ -n "$COMPARE" ]; then
+ # Compare two branches
+ current_branch=$(git rev-parse --abbrev-ref HEAD)
+
+ echo "Comparing $BRANCH1 vs $BRANCH2" >&2
+
+ # Build and benchmark branch 1
+ echo "Building $BRANCH1..." >&2
+ git checkout -q "$BRANCH1"
+ make -C .. clean >/dev/null 2>&1
+ make -C .. >/dev/null 2>&1
+ results1=$(run_benchmarks "$BRANCH1")
+
+ # Build and benchmark branch 2
+ echo "Building $BRANCH2..." >&2
+ git checkout -q "$BRANCH2"
+ make -C .. clean >/dev/null 2>&1
+ make -C .. >/dev/null 2>&1
+ results2=$(run_benchmarks "$BRANCH2")
+
+ # Return to original branch
+ git checkout -q "$current_branch"
+ make -C .. clean >/dev/null 2>&1
+ make -C .. >/dev/null 2>&1
+
+ # Output combined results
+ if [ -n "$OUTFILE" ]; then
+ if [ "$APPEND" -eq 0 ]; then
+ csv_header > "$OUTFILE"
+ fi
+ echo "$results1" >> "$OUTFILE"
+ echo "$results2" >> "$OUTFILE"
+ echo "Results written to $OUTFILE" >&2
+ else
+ csv_header
+ echo "$results1"
+ echo "$results2"
+ fi
+else
+ # Single run on current build
+ branch_name=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
+
+ if [ -n "$OUTFILE" ]; then
+ if [ "$APPEND" -eq 0 ]; then
+ csv_header > "$OUTFILE"
+ fi
+ run_benchmarks "$branch_name" >> "$OUTFILE"
+ echo "Results written to $OUTFILE" >&2
+ else
+ csv_header
+ run_benchmarks "$branch_name"
+ fi
+fi