XIAN-FEM-2026June/3D opticsfem-master/tools/diagnose_ab_paradox.py

103 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""Diagnose why ref A + cpp b works but cpp A + cpp b fails despite tiny A diff."""
from __future__ import annotations
from pathlib import Path
import numpy as np
from scipy import sparse
from scipy.sparse.linalg import spsolve
ROOT = Path(__file__).resolve().parents[2]
REF = ROOT / "三维matlab代码/2023-2-端口激励问题(四面体网格)/OutFile_fem4_ab"
CPP = ROOT / "3D opticsfem-master/port/Release/OutFile_fem4"
PCOEF = 2.69467866496258e15
def parse_av_line(s: str) -> complex:
s = s.strip()
if s.startswith("("):
body = s[1:-1]
a, b = body.split(",", 1)
return complex(float(a), float(b))
return complex(float(s))
def load_coo(prefix: Path) -> sparse.csr_matrix:
ai = np.loadtxt(prefix / "Ai.txt", dtype=np.int64)
aj = np.loadtxt(prefix / "Aj.txt", dtype=np.int64)
av = [parse_av_line(line) for line in (prefix / "Av.txt").read_text(encoding="utf-8", errors="ignore").splitlines() if line.strip()]
return sparse.coo_matrix((av, (ai, aj)), shape=(79616, 79616)).tocsr()
def load_b(prefix: Path) -> np.ndarray:
return np.loadtxt(prefix / "Bv_real.txt") + 1j * np.loadtxt(prefix / "Bv_imag.txt")
def main() -> int:
print("=== Load matrices ===")
A_ref = load_coo(REF)
A_cpp = load_coo(CPP)
b_ref = load_b(REF)
b_cpp = load_b(CPP)
print(f"A_ref nnz={A_ref.nnz}, A_cpp nnz={A_cpp.nnz}")
print(f"indices equal: {np.array_equal(A_ref.indices, A_cpp.indices) and np.array_equal(A_ref.indptr, A_cpp.indptr)}")
ddata = np.abs(A_ref.data - A_cpp.data)
print(f"A.data max|diff|={ddata.max():.6g}, mean|diff|={ddata.mean():.6g}")
print(f"A.data rel max|diff|={ddata.max()/max(np.abs(A_ref.data).max(),1e-30):.6g}")
print(f"b max|diff|={np.abs(b_ref-b_cpp).max():.6g}, count>1e-6={(np.abs(b_ref-b_cpp)>1e-6).sum()}")
# duplicate (i,j) in raw COO before CSR merge?
ai = np.loadtxt(REF / "Ai.txt", dtype=np.int64)
aj = np.loadtxt(REF / "Aj.txt", dtype=np.int64)
keys = np.column_stack([ai, aj])
uniq, counts = np.unique(keys, axis=0, return_counts=True)
ndup = int((counts > 1).sum())
print(f"raw COO duplicate (i,j) groups: {ndup}")
print("\n=== CRITICAL: same b_cpp, two A ===")
x_refA = spsolve(A_ref, b_cpp)
x_cppA = spsolve(A_cpp, b_cpp)
rel = np.linalg.norm(x_refA - x_cppA) / np.linalg.norm(x_refA)
print(f"||x_refA - x_cppA|| / ||x_refA|| = {rel:.6g}")
print(f"x_refA S11={x_refA[-2]/PCOEF} S21={x_refA[-1]/PCOEF}")
print(f"x_cppA S11={x_cppA[-2]/PCOEF} S21={x_cppA[-1]/PCOEF}")
print("\n=== Same A_ref, two b ===")
x1 = spsolve(A_ref, b_ref)
x2 = spsolve(A_ref, b_cpp)
print(f"||x(b_ref)-x(b_cpp)||/||x(b_ref)|| = {np.linalg.norm(x1-x2)/np.linalg.norm(x1):.6g}")
print(f"x(b_ref) S11={x1[-2]/PCOEF}")
print(f"x(b_cpp) S11={x2[-2]/PCOEF}")
print("\n=== C++ exported X ===")
x_exp = np.loadtxt(CPP / "X_real.txt") + 1j * np.loadtxt(CPP / "X_imag.txt")
free_len = 79616
x_red = x_exp # reduced? export is full 83350
# map: C++ exports full _mX; reduced solve is 79616
print(f"X size={x_exp.size}, e1={x_exp[83348]/PCOEF}, e2={x_exp[83349]/PCOEF}")
print(f"||x_cppA - x_exp[?]|| : reduced x vs full X needs free map")
# Where do A values differ most (relative)?
mask = ddata > 0
if mask.any():
rel_d = ddata[mask] / np.maximum(np.abs(A_ref.data[mask]), 1e-30)
top = np.argsort(-rel_d)[:10]
print("\nTop relative A diffs (CSR data index):")
for i in top:
print(f" idx={i} rel={rel_d[i]:.3g} abs={ddata[i]:.3g} ref={A_ref.data[i]:.6g} cpp={A_cpp.data[i]:.6g}")
# Port columns 79614,79615
for col in [79614, 79615]:
dcol = (A_ref[:, col] - A_cpp[:, col]).toarray().ravel()
nz = np.abs(dcol) > 0
print(f"\nPort col {col}: nnz diff={nz.sum()}, max|diff|={np.abs(dcol).max():.3g}")
return 0
if __name__ == "__main__":
raise SystemExit(main())