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

240 lines
7.3 KiB
Python

#!/usr/bin/env python3
"""Compare MATLAB vs C++ PBC edge pairing on PBCmesh.dat."""
from __future__ import annotations
import math
from pathlib import Path
import numpy as np
def read_tag(lines: list[str], i: int, expected: str) -> int:
while i < len(lines) and not lines[i].strip():
i += 1
if i >= len(lines) or lines[i].strip() != expected:
got = lines[i].strip() if i < len(lines) else "EOF"
raise ValueError(f"Expected '{expected}' at line {i + 1}, got '{got}'")
return i + 1
def load_mesh(path: Path) -> dict:
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
i = 0
i = read_tag(lines, i, "NbrVertex")
n_vertex = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Vertex")
vertex = np.zeros((n_vertex, 3))
for r in range(n_vertex):
vertex[r] = np.array(list(map(float, lines[i].split())))
i += 1
i = read_tag(lines, i, "NbrTet")
n_tet = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "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())), dtype=np.int64)
i += 1
i = read_tag(lines, i, "DomainOfTet")
i += n_tet
i = read_tag(lines, i, "NbrEdge")
n_edge = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Edge")
edge = np.zeros((n_edge, 2), dtype=np.int64)
for r in range(n_edge):
edge[r] = np.array(list(map(int, lines[i].split())), dtype=np.int64)
i += 1
i = read_tag(lines, i, "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())), dtype=np.int64)
i += 1
i = read_tag(lines, i, "NbrTri")
n_tri = int(lines[i].strip())
i += 1
i = read_tag(lines, i, "Tri")
i += n_tri
i = read_tag(lines, i, "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())
i += 1
i = read_tag(lines, i, "ConnOfTri")
conn_of_tri = np.zeros((n_tri, 2), dtype=np.int64)
for r in range(n_tri):
conn_of_tri[r] = np.array(list(map(int, lines[i].split())), dtype=np.int64)
i += 1
return {
"vertex": vertex,
"edge": edge,
"tet": tet,
"edge_of_tet": edge_of_tet,
"domain_of_tri": domain_of_tri,
"conn_of_tri": conn_of_tri,
"n_edge": n_edge,
}
def tri_edges(mesh: dict, tri_idx: int) -> list[int]:
num_tet, num_face = mesh["conn_of_tri"][tri_idx]
num_tet -= 1
num_face += 1
eot = mesh["edge_of_tet"][num_tet]
if num_face == 1:
return [eot[0], eot[1], eot[3]]
if num_face == 2:
return [eot[0], eot[2], eot[4]]
if num_face == 3:
return [eot[1], eot[2], eot[5]]
if num_face == 4:
return [eot[3], eot[4], eot[5]]
return []
def collect_edges(mesh: dict, domains: list[int]) -> list[int]:
edges: list[int] = []
for tri_idx, dom in enumerate(mesh["domain_of_tri"]):
if dom in domains:
edges.extend(tri_edges(mesh, tri_idx))
return sorted(set(edges))
def edge_vertices(mesh: dict, edge_idx: int) -> tuple[np.ndarray, np.ndarray]:
e = mesh["edge"][edge_idx - 1]
v = mesh["vertex"]
return v[e[0] - 1], v[e[1] - 1]
def match_pair(mesh: dict, src_e: int, dst_e: int, theta: float, tol: float) -> int | None:
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],
]
)
v1, v2 = edge_vertices(mesh, src_e)
v3, v4 = edge_vertices(mesh, dst_e)
v3 = tra @ v3
v4 = tra @ v4
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:
return 1
if l3 + l4 < tol:
return -1
return None
def pair_matlab(mesh: dict, src_edges: list[int], dst_edges: list[int], theta: float) -> list[tuple[int, int, int]]:
tol = 0.01 * 0.00005
pairs: list[tuple[int, int, int]] = []
for src_e in src_edges:
matched = False
for dst_e in dst_edges:
sign = match_pair(mesh, src_e, dst_e, theta, tol)
if sign is not None:
pairs.append((src_e, dst_e, sign))
matched = True
break
if not matched:
pairs.append((src_e, src_e, 1))
return pairs
def pair_cpp(mesh: dict, src_edges: list[int], dst_edges: list[int], theta: float) -> list[tuple[int, int, int]]:
tol = 0.01 * 0.00005
dst_used = [False] * len(dst_edges)
pairs: list[tuple[int, int, int]] = []
for src_e in src_edges:
matched = False
for j, dst_e in enumerate(dst_edges):
if dst_used[j]:
continue
sign = match_pair(mesh, src_e, dst_e, theta, tol)
if sign is not None:
pairs.append((src_e, dst_e, sign))
dst_used[j] = True
matched = True
break
if not matched:
pairs.append((src_e, src_e, 1))
return pairs
def remove_self(pairs: list[tuple[int, int, int]]) -> list[tuple[int, int, int]]:
return [(s, d, sg) for s, d, sg in pairs if s != d]
def main() -> None:
mesh_path = Path(__file__).resolve().parents[1] / "build" / "Release" / "PBCmesh.dat"
mesh = load_mesh(mesh_path)
theta = math.pi / 3
phi = complex(0.5, math.sqrt(3) / 2)
src_domains = [1, 4]
dst_domains = [2, 5]
src_edges = collect_edges(mesh, src_domains)
dst_edges = collect_edges(mesh, dst_domains)
mat_pairs = remove_self(pair_matlab(mesh, src_edges, dst_edges, theta))
cpp_pairs = remove_self(pair_cpp(mesh, src_edges, dst_edges, theta))
mat_map = {(d, s): sg for s, d, sg in mat_pairs}
cpp_map = {(d, s): sg for s, d, sg in cpp_pairs}
print(f"src edges: {len(src_edges)}, dst edges: {len(dst_edges)}")
print(f"MATLAB pairs (no self): {len(mat_pairs)}")
print(f"C++ pairs (no self): {len(cpp_pairs)}")
sign_diff = []
dst_only_mat = []
dst_only_cpp = []
for key, sg_mat in mat_map.items():
if key not in cpp_map:
dst_only_mat.append(key)
elif cpp_map[key] != sg_mat:
sign_diff.append((key, sg_mat, cpp_map[key]))
for key in cpp_map:
if key not in mat_map:
dst_only_cpp.append(key)
print(f"\nSign mismatches: {len(sign_diff)}")
for key, a, b in sign_diff[:15]:
print(f" dst={key[0]} src={key[1]}: mat={a} cpp={b}")
print(f"Only in MATLAB map: {len(dst_only_mat)}")
print(f"Only in C++ map: {len(dst_only_cpp)}")
if dst_only_mat[:5]:
print(" mat examples:", dst_only_mat[:5])
if dst_only_cpp[:5]:
print(" cpp examples:", dst_only_cpp[:5])
# Check DOF 20552 (1-based edge index in mesh)
dof = 20552
for label, pairs in [("MATLAB", mat_pairs), ("C++", cpp_pairs)]:
roles = []
for s, d, sg in pairs:
if s == dof:
roles.append(f"src->{d} sign={sg} phi={sg*phi}")
if d == dof:
roles.append(f"dst<-{s} sign={sg} phi={sg*phi}")
print(f"\nEdge {dof} in {label} PBC: {roles or 'not in pairs'}")
if __name__ == "__main__":
main()