import os import sys import shutil import subprocess import numpy as np def run_regression_test(): workspace_dir = "/home/ignis/workspace/incomp-flame-post" code_dir = os.path.join(workspace_dir, "code") binary_path = os.path.join(code_dir, "x-edge-cold-bc-uPrime-hybrid") # 1. Create a temporary testing directory in workspace test_run_dir = os.path.join(workspace_dir, "scratch", "ic1_regression_test") if os.path.exists(test_run_dir): shutil.rmtree(test_run_dir) os.makedirs(test_run_dir, exist_ok=True) # 2. Copy config files from historical backup 'ic1' backup_ic1_dir = "/tank/ingest/from_sata_sdk_4tb/161/ignis/dns/incomp-flame-post/ic1" shutil.copy(os.path.join(backup_ic1_dir, "post-edge-cold-bc-hybrid-intro"), test_run_dir) shutil.copy(os.path.join(backup_ic1_dir, "otape"), test_run_dir) shutil.copy(binary_path, test_run_dir) # 3. Create symlinks for all fort.* data files in testing dir print("Linking fort.* data files...") linked_count = 0 for item in os.listdir(backup_ic1_dir): if item.startswith("fort."): src_file = os.path.join(backup_ic1_dir, item) dst_link = os.path.join(test_run_dir, item) os.symlink(src_file, dst_link) linked_count += 1 print(f"Linked {linked_count} data files to temporary run directory.") # 4. Run the compiled binary using mpirun # Using -np 12 to safely utilize 12 cores with moderate memory profile print("Running compiled MPI binary inside test run directory...") # Change Cwd to test_run_dir and execute run_cmd = ["mpirun", "--oversubscribe", "-np", "12", "./x-edge-cold-bc-uPrime-hybrid"] try: result = subprocess.run( run_cmd, cwd=test_run_dir, stdout=None, stderr=None, check=True ) print("MPI Binary run completed successfully!") except subprocess.CalledProcessError as e: print("MPI Binary execution FAILED!") sys.exit(1) # 5. Load and compare qEdge_X.dat new_result_path = os.path.join(test_run_dir, "qEdge_X.dat") ref_result_path = os.path.join(backup_ic1_dir, "qEdge_X.dat") if not os.path.exists(new_result_path): print(f"Error: Generated qEdge_X.dat not found in {test_run_dir}!") sys.exit(1) print("Comparing new qEdge_X.dat with reference qEdge_X.dat...") # Parse headers with open(new_result_path, 'r') as f: new_header = f.readline().strip().split() with open(ref_result_path, 'r') as f: ref_header = f.readline().strip().split() print("New output variables:", new_header) print("Reference variables:", ref_header) # Read numeric data # Skip the header line new_data = np.loadtxt(new_result_path, skiprows=1) ref_data = np.loadtxt(ref_result_path, skiprows=1) if new_data.shape != ref_data.shape: print(f"Shape Mismatch: New data shape {new_data.shape} != Reference data shape {ref_data.shape}") sys.exit(1) # Compare each column max_diffs = [] failed = False # We will match columns by header to be absolutely precise header_mapping = {} for i, col_name in enumerate(new_header): if col_name in ref_header: header_mapping[i] = ref_header.index(col_name) # Using 2e-10 to accommodate tiny floating-point associative addition discrepancies due to different MPI rank counts (8 ranks vs 24 ranks) tolerance = 2e-10 print("\nColumn-by-column numeric verification:") print(f"{'Variable Name':<20} | {'Max Abs Error':<13} | {'Max Rel Error':<13} | {'Status':<10}") print("-" * 68) for new_idx, ref_idx in header_mapping.items(): var_name = new_header[new_idx] new_col = new_data[:, new_idx] ref_col = ref_data[:, ref_idx] # Check that NaN locations match exactly nan_mismatch = not np.array_equal(np.isnan(new_col), np.isnan(ref_col)) abs_diff = np.abs(new_col - ref_col) if np.all(np.isnan(abs_diff)): max_diff = 0.0 else: max_diff = np.nanmax(abs_diff) max_diffs.append(max_diff) # Calculate maximum relative difference (ignoring NaNs and points close to zero) with np.errstate(divide='ignore', invalid='ignore'): rel_diff = abs_diff / np.abs(ref_col) # ignore points close to zero to avoid division by near-zero blowup in relative error rel_diff[np.isnan(rel_diff) | np.isinf(rel_diff) | (np.abs(ref_col) < 1e-12)] = 0.0 if np.all(rel_diff == 0.0): max_rel = 0.0 else: max_rel = np.nanmax(rel_diff) status = "PASSED" if nan_mismatch: status = "NAN_MISMATCH" failed = True else: # Pass if EITHER absolute error < tolerance OR relative error < 1e-4 is_passed = (max_diff <= tolerance) or (max_rel <= 1e-4) if not is_passed: status = "FAILED" failed = True print(f"{var_name:<20} | {max_diff:<13.4e} | {max_rel:<13.4e} | {status:<10}") if failed: print("\nVerification FAILED: Some columns exceeded numerical tolerance limits!") sys.exit(1) else: print(f"\nVerification SUCCESSFUL! All columns matched within tolerance ({tolerance:.1e}).") print("HPC DNS post-processing system is modernized and 100% numerically verified.") sys.exit(0) if __name__ == '__main__': run_regression_test()