176 lines
5.7 KiB
Python
176 lines
5.7 KiB
Python
"""Build SBCmesh.mat / SBCmesh.dat from COMSOL SBC.mph (same logic as getMesh.m)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
import mph
|
|
import numpy as np
|
|
import scipy.io as sio
|
|
|
|
|
|
def _build_mesh_struct(mph_path: Path) -> dict:
|
|
client = mph.start()
|
|
model = client.load(str(mph_path))
|
|
m = model.java.component("comp1").mesh("mesh1")
|
|
|
|
vertex = np.array(m.getVertex(), dtype=float).T
|
|
nbr_vertex = vertex.shape[0]
|
|
|
|
tet_raw = np.array(m.getElem("tet"), dtype=int).T
|
|
nbr_tet = tet_raw.shape[0]
|
|
domain_of_tet = np.array(m.getElemEntity("tet"), dtype=int).reshape(-1, 1)
|
|
tet = np.sort(tet_raw, axis=1) + 1
|
|
|
|
el2no = tet.T
|
|
rows_n1 = [0, 0, 0, 1, 1, 2]
|
|
rows_n2 = [1, 2, 3, 2, 3, 3]
|
|
n1 = el2no[rows_n1, :].reshape(-1, order="F")
|
|
n2 = el2no[rows_n2, :].reshape(-1, order="F")
|
|
el_ed2no_array = np.column_stack([n1, n2])
|
|
edge, edge_of_tet_flat = np.unique(el_ed2no_array, axis=0, return_inverse=True)
|
|
nbr_edge = edge.shape[0]
|
|
edge_of_tet = edge_of_tet_flat.reshape(6, nbr_tet, order="F").T
|
|
|
|
rows_f1 = [0, 0, 0, 1]
|
|
rows_f2 = [1, 1, 2, 2]
|
|
rows_f3 = [2, 3, 3, 3]
|
|
f1 = el2no[rows_f1, :].reshape(-1, order="F")
|
|
f2 = el2no[rows_f2, :].reshape(-1, order="F")
|
|
f3 = el2no[rows_f3, :].reshape(-1, order="F")
|
|
el_face_array = np.column_stack([f1, f2, f3])
|
|
|
|
tri_raw = np.array(m.getElem("tri"), dtype=int).T
|
|
tri = np.sort(tri_raw, axis=1) + 1
|
|
domain_of_tri = np.array(m.getElemEntity("tri"), dtype=int).reshape(-1, 1)
|
|
nbr_tri = tri.shape[0]
|
|
|
|
conn_of_tri = np.zeros((nbr_tri, 2), dtype=int)
|
|
for i in range(nbr_tri):
|
|
matches = np.where(np.all(el_face_array == tri[i], axis=1))[0]
|
|
if matches.size == 0:
|
|
raise RuntimeError(f"boundary tri {i + 1} not found in tet faces")
|
|
index = int(matches[0]) + 1
|
|
conn_of_tri[i, 0] = (index - 1) // 4 + 1
|
|
conn_of_tri[i, 1] = index - (conn_of_tri[i, 0] - 1) * 4
|
|
|
|
norm_of_face = np.zeros((14, 3), dtype=float)
|
|
norm_of_face[0] = [-1, 0, 0]
|
|
norm_of_face[1] = [0, -1, 0]
|
|
norm_of_face[2] = [0, 0, -1]
|
|
norm_of_face[3] = [0, 0, 1]
|
|
norm_of_face[4] = [0, 1, 0]
|
|
norm_of_face[13] = [1, 0, 0]
|
|
|
|
return {
|
|
"NbrVertex": np.int32(nbr_vertex),
|
|
"Vertex": vertex,
|
|
"NbrTet": np.int32(nbr_tet),
|
|
"DomainOfTet": domain_of_tet,
|
|
"Tet": tet,
|
|
"NbrEdge": np.int32(nbr_edge),
|
|
"Edge": edge.astype(np.int32),
|
|
"EdgeOfTet": edge_of_tet.astype(np.int32),
|
|
"NbrTri": np.int32(nbr_tri),
|
|
"Tri": tri.astype(np.int32),
|
|
"DomainOfTri": domain_of_tri,
|
|
"ConnOfTri": conn_of_tri.astype(np.int32),
|
|
"NormOfFace": norm_of_face,
|
|
}
|
|
|
|
|
|
def _write_dat(mesh: dict, dat_path: Path) -> None:
|
|
lines: list[str] = []
|
|
lines.append("NbrVertex")
|
|
lines.append(str(int(mesh["NbrVertex"])))
|
|
lines.append("Vertex")
|
|
for row in mesh["Vertex"]:
|
|
lines.append(f"{row[0]:.16g} {row[1]:.16g} {row[2]:.16g}")
|
|
|
|
lines.append("NbrTet")
|
|
lines.append(str(int(mesh["NbrTet"])))
|
|
lines.append("Tet")
|
|
for row in mesh["Tet"]:
|
|
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])} {int(row[3])}")
|
|
|
|
lines.append("DomainOfTet")
|
|
for v in mesh["DomainOfTet"].reshape(-1):
|
|
lines.append(str(int(v)))
|
|
|
|
lines.append("NbrEdge")
|
|
lines.append(str(int(mesh["NbrEdge"])))
|
|
lines.append("Edge")
|
|
for row in mesh["Edge"]:
|
|
lines.append(f"{int(row[0])} {int(row[1])}")
|
|
|
|
lines.append("EdgeOfTet")
|
|
for row in mesh["EdgeOfTet"]:
|
|
lines.append(" ".join(str(int(x) + 1) for x in row))
|
|
|
|
lines.append("NbrTri")
|
|
lines.append(str(int(mesh["NbrTri"])))
|
|
lines.append("Tri")
|
|
for row in mesh["Tri"]:
|
|
lines.append(f"{int(row[0])} {int(row[1])} {int(row[2])}")
|
|
|
|
lines.append("DomainOfTri")
|
|
for v in mesh["DomainOfTri"].reshape(-1):
|
|
lines.append(str(int(v)))
|
|
|
|
lines.append("ConnOfTri")
|
|
for row in mesh["ConnOfTri"]:
|
|
lines.append(f"{int(row[0])} {int(row[1])}")
|
|
|
|
norm_rows = [1, 2, 3, 4, 5, 14]
|
|
lines.append("NormOfFace")
|
|
lines.append(str(len(norm_rows)))
|
|
for d in norm_rows:
|
|
n = mesh["NormOfFace"][d - 1]
|
|
lines.append(f"{d} {n[0]:.16g} {n[1]:.16g} {n[2]:.16g}")
|
|
|
|
dat_path.parent.mkdir(parents=True, exist_ok=True)
|
|
dat_path.write_text("\n".join(lines) + "\n", encoding="ascii")
|
|
|
|
|
|
def build_sbcmesh(mph_path: Path, out_dir: Path, deploy: Path | None = None) -> dict:
|
|
mesh = _build_mesh_struct(mph_path)
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
mat_path = out_dir / "SBCmesh.mat"
|
|
dat_path = out_dir / "SBCmesh.dat"
|
|
sio.savemat(str(mat_path), {"mesh": mesh}, do_compression=True)
|
|
_write_dat(mesh, dat_path)
|
|
|
|
if deploy is not None:
|
|
deploy.mkdir(parents=True, exist_ok=True)
|
|
import shutil
|
|
|
|
shutil.copy2(mat_path, deploy / "SBCmesh.mat")
|
|
shutil.copy2(dat_path, deploy / "SBCmesh.dat")
|
|
|
|
print(f"Wrote {mat_path}")
|
|
print(f"Wrote {dat_path}")
|
|
print(
|
|
f" NbrVertex={mesh['NbrVertex']}, NbrTet={mesh['NbrTet']}, "
|
|
f"NbrEdge={mesh['NbrEdge']}, NbrTri={mesh['NbrTri']}"
|
|
)
|
|
print(f" A COO estimate (NbrTet*36) = {int(mesh['NbrTet']) * 36}")
|
|
if deploy is not None:
|
|
print(f"Deployed to {deploy}")
|
|
return mesh
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Build SBCmesh from COMSOL mph")
|
|
parser.add_argument("mph_file", type=Path)
|
|
parser.add_argument("-o", "--output-dir", type=Path, default=None)
|
|
parser.add_argument("--deploy", type=Path, default=None)
|
|
args = parser.parse_args()
|
|
out_dir = args.output_dir or args.mph_file.parent
|
|
build_sbcmesh(args.mph_file, out_dir, deploy=args.deploy)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|