240 lines
7.3 KiB
Python
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()
|