#!/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()