cngf-pf

continuum model for granular flows with pore-pressure dynamics (renamed from 1d_fd_simple_shear)
git clone git://src.adamsgaard.dk/cngf-pf # fast
git clone https://src.adamsgaard.dk/cngf-pf.git # slow
Log | Files | Refs | README | LICENSE Back to index

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:
Atest/accuracy.sh | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest/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