250 lines
7.8 KiB
Python
250 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare PBC-projected A matrix: MATLAB vs C++, and verify PBC projection pipeline."""
|
|
import re
|
|
import math
|
|
from pathlib import Path
|
|
import numpy as np
|
|
from scipy import sparse
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
MATLAB_DIR = ROOT.parent / "三维matlab代码" / "matlab 3D一阶基+散射边界条件+单周期边界"
|
|
CPP_OUT = ROOT / "build" / "Release" / "OutFile"
|
|
MAT_OUT = MATLAB_DIR / "OutFile"
|
|
MAT_ASM = MATLAB_DIR / "OutFile_asm"
|
|
MESH_FILE = MATLAB_DIR / "PBCmesh.dat"
|
|
PBC_ANGLE = math.pi / 3
|
|
PBC_PHI = complex(0.5, 0.8660254037844386)
|
|
|
|
|
|
def load_coo(prefix: Path) -> sparse.csr_matrix:
|
|
def read_col(p):
|
|
with open(p, encoding="utf-8", errors="ignore") as f:
|
|
return np.array([int(x.strip()) for x in f if x.strip()], dtype=np.int64)
|
|
|
|
def read_val(p):
|
|
vals = []
|
|
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, dtype=np.complex128)
|
|
|
|
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 read_mesh_dat(path: Path):
|
|
"""Minimal PBCmesh.dat reader (0-based indices)."""
|
|
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
|
|
i = 0
|
|
|
|
def read_block(name):
|
|
nonlocal i
|
|
while i < len(lines) and lines[i].strip() != name:
|
|
i += 1
|
|
if i >= len(lines):
|
|
raise KeyError(name)
|
|
i += 1
|
|
return i
|
|
|
|
def read_int():
|
|
nonlocal i
|
|
v = int(lines[i].strip())
|
|
i += 1
|
|
return v
|
|
|
|
def read_floats(n):
|
|
nonlocal i
|
|
vals = list(map(float, lines[i].split()))
|
|
i += 1
|
|
return np.array(vals[:n])
|
|
|
|
read_block("NbrVertex")
|
|
n_vertex = read_int()
|
|
read_block("Vertex")
|
|
vertex = np.zeros((n_vertex, 3))
|
|
for r in range(n_vertex):
|
|
vertex[r] = read_floats(3)
|
|
|
|
read_block("NbrEdge")
|
|
n_edge = read_int()
|
|
read_block("Edge")
|
|
edge = np.zeros((n_edge, 2), dtype=np.int64)
|
|
for r in range(n_edge):
|
|
a, b = map(int, lines[i].split())
|
|
edge[r] = (a - 1, b - 1)
|
|
i += 1
|
|
|
|
read_block("NbrTet")
|
|
n_tet = read_int()
|
|
read_block("Tet")
|
|
tet = np.zeros((n_tet, 4), dtype=np.int64)
|
|
for r in range(n_tet):
|
|
tet[r] = np.array(list(map(int, lines[i].split()))) - 1
|
|
i += 1
|
|
|
|
read_block("EdgeOfTet")
|
|
edge_of_tet = np.zeros((n_tet, 6), dtype=np.int64)
|
|
for r in range(n_tet):
|
|
edge_of_tet[r] = np.array(list(map(int, lines[i].split()))) - 1
|
|
i += 1
|
|
|
|
read_block("NbrTri")
|
|
n_tri = read_int()
|
|
read_block("ConnOfTri")
|
|
conn_of_tri = np.zeros((n_tri, 2), dtype=np.int64)
|
|
for r in range(n_tri):
|
|
a, b = map(int, lines[i].split())
|
|
conn_of_tri[r] = (a - 1, b - 1)
|
|
i += 1
|
|
|
|
read_block("DomainOfTri")
|
|
domain_of_tri = np.zeros(n_tri, dtype=np.int64)
|
|
for r in range(n_tri):
|
|
domain_of_tri[r] = int(lines[i].strip()) # keep 1-based like MATLAB mesh
|
|
i += 1
|
|
|
|
return {
|
|
"vertex": vertex,
|
|
"edge": edge,
|
|
"tet": tet,
|
|
"edge_of_tet": edge_of_tet,
|
|
"conn_of_tri": conn_of_tri,
|
|
"domain_of_tri": domain_of_tri,
|
|
"n_edge": n_edge,
|
|
}
|
|
|
|
|
|
def find_tri(domains, mesh):
|
|
domains = np.atleast_1d(domains)
|
|
out = []
|
|
for j in range(mesh["conn_of_tri"].shape[0]):
|
|
num_tet, _ = mesh["conn_of_tri"][j]
|
|
dom = mesh["domain_of_tri"][j]
|
|
# DomainOfTri in dat is 1-based after +1 in C++ loader; MATLAB c6 may differ.
|
|
# PBCmesh.dat stores 0-based domain ids before C++ +1.
|
|
if dom in domains or (dom + 1) in domains:
|
|
out.append(j)
|
|
return np.array(out, dtype=np.int64)
|
|
|
|
|
|
def face_edges(mesh, tri_idx):
|
|
num_tet, num_face = mesh["conn_of_tri"][tri_idx]
|
|
e = mesh["edge_of_tet"][num_tet]
|
|
face = num_face + 1
|
|
if face == 1:
|
|
return [e[0], e[1], e[3]]
|
|
if face == 2:
|
|
return [e[0], e[2], e[4]]
|
|
if face == 3:
|
|
return [e[1], e[2], e[5]]
|
|
if face == 4:
|
|
return [e[3], e[4], e[5]]
|
|
raise ValueError(face)
|
|
|
|
|
|
def find_pbc_index(src, dst, theta, mesh):
|
|
src_tris = find_tri(src, mesh)
|
|
dst_tris = find_tri(dst, mesh)
|
|
src_edges, dst_edges = [], []
|
|
for tri in src_tris:
|
|
src_edges.extend(face_edges(mesh, tri))
|
|
for tri in dst_tris:
|
|
dst_edges.extend(face_edges(mesh, tri))
|
|
src_edges = np.unique(src_edges)
|
|
dst_edges = np.unique(dst_edges)
|
|
|
|
tra = np.array(
|
|
[[math.cos(theta), -math.sin(theta), 0.0],
|
|
[math.sin(theta), math.cos(theta), 0.0],
|
|
[0.0, 0.0, 1.0]]
|
|
)
|
|
tol = 0.01 * 0.00005
|
|
pairs = []
|
|
for si in src_edges:
|
|
v1 = mesh["vertex"][mesh["edge"][si, 0]]
|
|
v2 = mesh["vertex"][mesh["edge"][si, 1]]
|
|
matched = False
|
|
for dj, di in enumerate(dst_edges):
|
|
v3 = tra @ mesh["vertex"][mesh["edge"][di, 0]]
|
|
v4 = tra @ mesh["vertex"][mesh["edge"][di, 1]]
|
|
l1 = np.linalg.norm(v1 - v3)
|
|
l2 = np.linalg.norm(v2 - v4)
|
|
l3 = np.linalg.norm(v1 - v4)
|
|
l4 = np.linalg.norm(v2 - v3)
|
|
if l1 + l2 < tol:
|
|
pairs.append((si, di, 1))
|
|
matched = True
|
|
break
|
|
if l3 + l4 < tol:
|
|
pairs.append((si, di, -1))
|
|
matched = True
|
|
break
|
|
if not matched:
|
|
pairs.append((si, -1, 0))
|
|
pairs = [(s, d, sg) for s, d, sg in pairs if s != d and d >= 0]
|
|
return pairs
|
|
|
|
|
|
def build_P(dof, pairs):
|
|
P = sparse.lil_matrix((dof, dof), dtype=np.complex128)
|
|
for i in range(dof):
|
|
P[i, i] = 1.0
|
|
dst_cols = []
|
|
for src, dst, sign in pairs:
|
|
P[dst, src] = sign * PBC_PHI
|
|
dst_cols.append(dst)
|
|
keep = [c for c in range(dof) if c not in dst_cols]
|
|
return P[:, keep].tocsr()
|
|
|
|
|
|
def diff_stats(A, B, label):
|
|
D = A - B
|
|
print(f"\n=== {label} ===")
|
|
print(f" max |diff| = {np.max(np.abs(D.data)) if D.nnz else 0:.6g}")
|
|
print(f" max |real diff| = {np.max(np.abs(D.real.data)) if D.nnz else 0:.6g}")
|
|
print(f" max |imag diff| = {np.max(np.abs(D.imag.data)) if D.nnz else 0:.6g}")
|
|
if D.nnz and A.shape[0] == B.shape[0]:
|
|
di = np.abs(A.diagonal().imag - B.diagonal().imag)
|
|
print(f" diag imag max diff = {di.max():.6g}, >10 count = {(di > 10).sum()}")
|
|
|
|
|
|
def main():
|
|
print("Loading matrices...")
|
|
A_cpp = load_coo(CPP_OUT)
|
|
A_mat = load_coo(MAT_OUT)
|
|
A_asm = load_coo(MAT_ASM)
|
|
diff_stats(A_cpp, A_mat, "C++ OutFile vs MATLAB OutFile (after PBC)")
|
|
|
|
print("\nLoading mesh and building PBC pairs...")
|
|
mesh = read_mesh_dat(MESH_FILE)
|
|
# domains in case_config: src=[1,4], dst=[2,5] (1-based in MATLAB)
|
|
pairs = find_pbc_index(np.array([1, 4]), np.array([2, 5]), PBC_ANGLE, mesh)
|
|
print(f" PBC pairs: {len(pairs)}")
|
|
signs = [p[2] for p in pairs]
|
|
print(f" sign +1: {signs.count(1)}, sign -1: {signs.count(-1)}")
|
|
|
|
P = build_P(mesh["n_edge"], pairs)
|
|
A_proj = P.conj().T @ A_asm @ P
|
|
diff_stats(A_proj, A_mat, "MATLAB asm projected with Python P vs MATLAB OutFile")
|
|
diff_stats(A_proj, A_cpp, "MATLAB asm projected with Python P vs C++ OutFile")
|
|
|
|
# Test sign-flip hypothesis on all -1 pairs
|
|
pairs_flip = [(s, d, -sg if sg < 0 else sg) for s, d, sg in pairs]
|
|
Pf = build_P(mesh["n_edge"], pairs_flip)
|
|
Af = Pf.conj().T @ A_asm @ Pf
|
|
diff_stats(Af, A_cpp, "MATLAB asm with flipped -1 signs vs C++ OutFile")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|