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

111 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""Project MATLAB OutFile_asm with PBC and compare to OutFile / C++."""
from __future__ import annotations
import math
import re
from pathlib import Path
import numpy as np
from scipy import sparse
from compare_pbc_pairs import collect_edges, load_mesh, pair_matlab, remove_self
def load_coo(prefix: Path) -> sparse.csr_matrix:
def read_col(p: Path) -> np.ndarray:
with open(p, encoding="utf-8", errors="ignore") as f:
return np.array([int(x.strip()) for x in f if x.strip()])
def read_val(p: Path) -> np.ndarray:
vals: list[complex] = []
with open(p, encoding="utf-8", errors="ignore") as f:
for line in f:
line = line.strip()
if not line:
continue
m = re.match(r"\(([-+0-9.eE]+),([-+0-9.eE]+)\)", line)
if m:
vals.append(complex(float(m.group(1)), float(m.group(2))))
else:
vals.append(complex(float(line), 0.0))
return np.array(vals)
ai = read_col(prefix / "Ai.txt")
aj = read_col(prefix / "Aj.txt")
av = read_val(prefix / "Av.txt")
n = int(max(ai.max(), aj.max()) + 1)
return sparse.csr_matrix((av, (ai, aj)), shape=(n, n))
def build_p_matrix(dof: int, pairs: list[tuple[int, int, int]], phi: complex) -> sparse.csr_matrix:
# 1-based edge indices in mesh -> use as matrix indices directly (MATLAB convention)
rows: list[int] = []
cols: list[int] = []
data: list[complex] = []
for i in range(1, dof + 1):
rows.append(i)
cols.append(i)
data.append(1.0)
for src, dst, sign in pairs:
rows.append(dst)
cols.append(src)
data.append(sign * phi)
p_full = sparse.csr_matrix((data, (np.array(rows) - 1, np.array(cols) - 1)), shape=(dof, dof))
dst_cols = sorted({dst - 1 for _, dst, _ in pairs})
keep = np.ones(dof, dtype=bool)
keep[dst_cols] = False
return p_full[:, keep]
def main() -> None:
root = Path(__file__).resolve().parents[2]
mat_dir = root / "三维matlab代码" / "matlab 3D一阶基+散射边界条件+单周期边界"
cpp_dir = root / "3D opticsfem-master" / "build" / "Release" / "OutFile"
mesh = load_mesh(root / "3D opticsfem-master" / "build" / "Release" / "PBCmesh.dat")
phi = complex(0.5, math.sqrt(3) / 2)
theta = math.pi / 3
src_edges = collect_edges(mesh, [1, 4])
dst_edges = collect_edges(mesh, [2, 5])
pairs = remove_self(pair_matlab(mesh, src_edges, dst_edges, theta))
a_asm = load_coo(mat_dir / "OutFile_asm")
a_out = load_coo(mat_dir / "OutFile")
a_cpp = load_coo(cpp_dir)
dof = a_asm.shape[0]
p = build_p_matrix(dof, pairs, phi)
a_proj = p.conj().T @ a_asm @ p
d_self = a_proj - a_out
d_cpp = a_cpp - a_out
print("Projected asm vs mat OutFile:")
print(" max |diff|", np.max(np.abs(d_self.data)))
print(" max |real diff|", np.max(np.abs(d_self.real.data)))
print(" max |imag diff|", np.max(np.abs(d_self.imag.data)))
print("\nC++ vs mat OutFile:")
print(" max |imag diff|", np.max(np.abs(d_cpp.imag.data)))
i = 20552 - 1 # 0-based for scipy
print(f"\nDOF 20552:")
print(" mat out ", a_out[i, i])
print(" proj asm", a_proj[i, i])
print(" cpp out ", a_cpp[i, i])
print(" asm pre ", a_asm[i, i])
# If projected asm matches mat out, pre-PBC asm from C++ is wrong
# Compare asm sizes / check if cpp pre-projection differs
r, c, v = sparse.find(d_self)
order = np.argsort(-np.abs(v.imag))
print("\nTop projected-asm vs mat-out imag diffs:")
for k in order[:5]:
ri, ci = int(r[k]), int(c[k])
print(f" ({ri+1},{ci+1}) proj={a_proj[ri,ci]:.6g} mat={a_out[ri,ci]:.6g}")
if __name__ == "__main__":
main()