Source code for mqed.BEM.verify_bem_fresnel

"""Verify BEM vs Fresnel dyadic Green's functions via least-squares scaling.

Reads the CSV produced by compare_BEM_dyadic.py, fits a complex scalar
s_ij for each tensor component such that ||s_ij * G_BEM - G_Fresnel||^2
is minimised, then reports the per-component and averaged scale factors
along with the relative RMS error after scaling.

To use this script: 
python -m mqed.BEM.verify_bem_fresnel path/to/bem_vs_fresnel.csv
"""

import argparse
import numpy as np
import pandas as pd
from pathlib import Path

from loguru import logger

COMPONENTS = ["Gxx", "Gxy", "Gxz", "Gyx", "Gyy", "Gyz", "Gzx", "Gzy", "Gzz"]


[docs] def fit_scale(re_bem, im_bem, re_fresnel, im_fresnel): """Fit complex scalar s minimising ||s * G_BEM - G_Fresnel||^2.""" Gb = re_bem.to_numpy() + 1j * im_bem.to_numpy() Gf = re_fresnel.to_numpy() + 1j * im_fresnel.to_numpy() if np.all(Gb == 0): logger.warning("BEM data is all zero, cannot fit scale factor.") return 0 else: s = np.vdot(Gb, Gf) / np.vdot(Gb, Gb) return s
[docs] def relative_rms(s, re_bem, im_bem, re_fresnel, im_fresnel): """Relative RMS error after applying scale factor s.""" Gb = re_bem.to_numpy() + 1j * im_bem.to_numpy() Gf = re_fresnel.to_numpy() + 1j * im_fresnel.to_numpy() return np.linalg.norm(s * Gb - Gf) / np.linalg.norm(Gf)
def main(): parser = argparse.ArgumentParser( description="Verify BEM vs Fresnel dyadic GF from a comparison CSV." ) parser.add_argument( "csv_path", type=str, help="Path to the CSV file produced by compare_BEM_dyadic.py", ) args = parser.parse_args() csv_path = Path(args.csv_path) if not csv_path.exists(): raise FileNotFoundError(f"CSV not found: {csv_path}") df = pd.read_csv(csv_path) scales = {} for comp in COMPONENTS: re_bem_col = f"Re_{comp}_BEM" im_bem_col = f"Im_{comp}_BEM" re_fr_col = f"Re_{comp}_Fresnel" im_fr_col = f"Im_{comp}_Fresnel" # 1. Check if the columns exist if re_bem_col not in df.columns: logger.warning(f"Component {comp} not found in CSV, skipping.") continue # 2. Check if the columns are all zeros # This checks if the sum of absolute values is zero is_bem_zero = (df[re_bem_col].abs().sum() == 0) and (df[im_bem_col].abs().sum() == 0) is_fr_zero = (df[re_fr_col].abs().sum() == 0) and (df[im_fr_col].abs().sum() == 0) if is_bem_zero or is_fr_zero: logger.info(f"Component {comp} contains only zero values, skipping scale fit.") continue # 3. Proceed with fitting if data exists s = fit_scale(df[re_bem_col], df[im_bem_col], df[re_fr_col], df[im_fr_col]) rms = relative_rms(s, df[re_bem_col], df[im_bem_col], df[re_fr_col], df[im_fr_col]) scales[comp] = s logger.info(f"s_{comp} = {s:.6f} (rel. RMS after scaling = {rms:.6e})") if not scales: logger.error("No components found in CSV.") return s_avg = np.mean([s for s in scales.values() if s != 0]) logger.info(f"s_avg = {s_avg:.6f} (averaged over {len(scales)} components)") rms_all = [] for comp, s in scales.items(): rms = relative_rms( s_avg, df[f"Re_{comp}_BEM"], df[f"Im_{comp}_BEM"], df[f"Re_{comp}_Fresnel"], df[f"Im_{comp}_Fresnel"], ) rms_all.append(rms) logger.success( f"Mean rel. RMS error (using s_avg) = {np.mean(rms_all):.6e} " f"over {len(rms_all)} components" ) if __name__ == "__main__": main()