276 lines
9.7 KiB
Python
276 lines
9.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Replicate MATLAB FemMatrixAssembly (wave+port+PEC) and compare with reference."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import h5py
|
|
import numpy as np
|
|
from scipy import sparse
|
|
from scipy.sparse.linalg import spsolve
|
|
|
|
from compare_wave_asm import assemble_wave_matlab, bf_edge, build_edges
|
|
from export_ab_coo import export_ab_coo
|
|
|
|
|
|
def gauss_tri_order2() -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
xft = np.array([1.0 / 3, 0.6, 0.2, 0.2])
|
|
yft = np.array([1.0 / 3, 0.2, 0.6, 0.2])
|
|
pft = np.array([-0.28125, 25.0 / 96, 25.0 / 96, 25.0 / 96])
|
|
return xft, yft, pft
|
|
|
|
|
|
def bf_et(i: int, u: float, v: float) -> np.ndarray:
|
|
if i == 1:
|
|
return np.array([1.0 - v, u, 0.0])
|
|
if i == 2:
|
|
return np.array([v, 1.0 - u, 0.0])
|
|
if i == 3:
|
|
return np.array([-v, u, 0.0])
|
|
raise ValueError(i)
|
|
|
|
|
|
def bf_ez(i: int, u: float, v: float) -> np.ndarray:
|
|
return np.array([0.0, 0.0, [1 - u - v, u, v][i - 1]])
|
|
|
|
|
|
def bf_curl_et(i: int, u: float, v: float) -> np.ndarray:
|
|
return np.array([0.0, 0.0, [2.0, -2.0, 2.0][i - 1]])
|
|
|
|
|
|
def bf_curl_ez(i: int, u: float, v: float) -> np.ndarray:
|
|
if i == 1:
|
|
return np.array([-1.0, 1.0, 0.0])
|
|
if i == 2:
|
|
return np.array([0.0, -1.0, 0.0])
|
|
if i == 3:
|
|
return np.array([1.0, 0.0, 0.0])
|
|
raise ValueError(i)
|
|
|
|
|
|
def get_tet_face(num_face: int) -> tuple[list[int], list[int]]:
|
|
mapping = {
|
|
1: ([0, 1, 2], [0, 1, 3]),
|
|
2: ([0, 1, 3], [0, 2, 4]),
|
|
3: ([0, 2, 3], [1, 2, 5]),
|
|
4: ([1, 2, 3], [3, 4, 5]),
|
|
}
|
|
return mapping[num_face]
|
|
|
|
|
|
def assemble_port_face(
|
|
nodes: np.ndarray,
|
|
elements: np.ndarray,
|
|
eoe: np.ndarray,
|
|
mode: dict,
|
|
face_idx: int,
|
|
is_input: bool,
|
|
) -> tuple[np.ndarray, np.ndarray, np.ndarray, complex, complex]:
|
|
n_edges = int(eoe.max()) + 1
|
|
fc = mode["facesConn"][face_idx]
|
|
num_ele = int(fc[0]) - 1
|
|
num_face = int(fc[1])
|
|
face_node, face_bf = get_tet_face(num_face)
|
|
idx = elements[num_ele] - 1
|
|
x, y, z = nodes[idx, 0], nodes[idx, 1], nodes[idx, 2]
|
|
pairs = [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
|
|
l = np.array([np.linalg.norm(nodes[idx[a]] - nodes[idx[b]]) for a, b in pairs])
|
|
|
|
mu = 1.0
|
|
gamma = complex(mode["gamma"][0], mode["gamma"][1])
|
|
power = mode["powerCoef"]
|
|
normal = np.array(mode["normal"], dtype=float)
|
|
et_coef = np.array(mode["Et_re"]) + 1j * np.array(mode["Et_im"])
|
|
ez_coef = np.array(mode["Ez_re"]) + 1j * np.array(mode["Ez_im"])
|
|
port_edge = np.array(mode["portEdgeOfFace"][face_idx], dtype=int)
|
|
port_ez = np.array(mode["portNewFaces"][face_idx], dtype=int)
|
|
|
|
jac = np.column_stack([x[:3] - x[3], y[:3] - y[3], z[:3] - z[3]])
|
|
inv_jac = np.linalg.inv(jac)
|
|
xx, yy, zz = x[face_node], y[face_node], z[face_node]
|
|
|
|
f_jac = np.zeros((3, 3))
|
|
f_jac[0, 0] = -xx[0] + xx[1]
|
|
f_jac[0, 1] = -yy[0] + yy[1]
|
|
f_jac[1, 0] = -xx[0] + xx[2]
|
|
f_jac[1, 1] = -yy[0] + yy[2]
|
|
f_jac[2, 2] = 1.0
|
|
inv_f = np.linalg.inv(f_jac)
|
|
f_jac_s = np.zeros((3, 3))
|
|
inv22 = np.linalg.inv(f_jac[:2, :2])
|
|
f_jac_s[0, 0] = inv22[1, 1]
|
|
f_jac_s[0, 1] = -inv22[1, 0]
|
|
f_jac_s[1, 0] = -inv22[0, 1]
|
|
f_jac_s[1, 1] = inv22[0, 0]
|
|
f_jac_s[2, 2] = 1.0
|
|
f_t_jac = f_jac.T / np.linalg.det(f_jac)
|
|
f_det = abs(np.linalg.det(f_jac))
|
|
|
|
f_jac2 = np.zeros((2, 3))
|
|
f_jac2[0, 0] = -xx[0] + xx[1]
|
|
f_jac2[0, 1] = -yy[0] + yy[1]
|
|
f_jac2[1, 0] = -xx[0] + xx[2]
|
|
f_jac2[1, 1] = -yy[0] + yy[2]
|
|
v_jac = np.linalg.inv(jac)
|
|
|
|
xft, yft, pft = gauss_tri_order2()
|
|
g = np.zeros(n_edges, dtype=complex)
|
|
st = np.zeros(n_edges, dtype=complex)
|
|
b_edge = np.zeros(n_edges, dtype=complex)
|
|
p = 0.0 + 0.0j
|
|
b1 = 0.0 + 0.0j
|
|
|
|
for gp in range(len(pft)):
|
|
f_e = np.zeros(3, dtype=complex)
|
|
f_curl_e = np.zeros(3, dtype=complex)
|
|
for i in range(3):
|
|
et = inv_f @ bf_et(i + 1, xft[gp], yft[gp]) * l[face_bf[i]]
|
|
ez = bf_ez(i + 1, xft[gp], yft[gp])
|
|
curl_et = f_t_jac @ bf_curl_et(i + 1, xft[gp], yft[gp]) * l[face_bf[i]]
|
|
curl_ez = f_jac_s @ bf_curl_ez(i + 1, xft[gp], yft[gp])
|
|
f_e += et * et_coef[port_edge[i]] + ez * ez_coef[port_ez[i]]
|
|
f_curl_e += curl_et * et_coef[port_edge[i]] + curl_ez * ez_coef[port_ez[i]]
|
|
|
|
rot = np.array([f_e[1], -f_e[0], 0.0])
|
|
if is_input:
|
|
f_n0 = f_e * power
|
|
f_curl_n0 = (f_curl_e + gamma * rot) * power
|
|
f_n1 = f_e
|
|
f_curl_n1 = f_curl_e - gamma * rot
|
|
else:
|
|
f_n2 = f_e
|
|
f_curl_n2 = f_curl_e + gamma * rot
|
|
f_w = f_e
|
|
|
|
ref = np.array([xft[gp], yft[gp]]) @ f_jac2 + np.array([xx[0], yy[0], zz[0]])
|
|
ref3 = ref @ v_jac - np.array([x[3], y[3], z[3]]) @ v_jac
|
|
|
|
if is_input:
|
|
for i in range(3):
|
|
bf = inv_jac @ bf_edge(face_bf[i] + 1, ref3[0], ref3[1], ref3[2]) * l[face_bf[i]]
|
|
edge_id = int(eoe[num_ele, face_bf[i]])
|
|
g[edge_id] += pft[gp] * f_det * (f_w[0] * bf[0] + f_w[1] * bf[1])
|
|
temp = mu * np.cross(normal, f_curl_n1)
|
|
st[edge_id] += pft[gp] * f_det * np.vdot(bf, temp)
|
|
temp0 = mu * np.cross(normal, f_curl_n0)
|
|
b_edge[edge_id] += pft[gp] * f_det * np.vdot(bf, temp0)
|
|
p += pft[gp] * f_det * (f_w[0] * f_n1[0] + f_w[1] * f_n1[1])
|
|
b1 += pft[gp] * f_det * (f_w[0] * f_n0[0] + f_w[1] * f_n0[1])
|
|
else:
|
|
for i in range(3):
|
|
bf = inv_jac @ bf_edge(face_bf[i] + 1, ref3[0], ref3[1], ref3[2]) * l[face_bf[i]]
|
|
edge_id = int(eoe[num_ele, face_bf[i]])
|
|
g[edge_id] += pft[gp] * f_det * (f_w[0] * bf[0] + f_w[1] * bf[1])
|
|
temp = mu * np.cross(normal, f_curl_n2)
|
|
st[edge_id] += pft[gp] * f_det * np.vdot(bf, temp)
|
|
p += pft[gp] * f_det * (f_w[0] * f_n2[0] + f_w[1] * f_n2[1])
|
|
|
|
return st, g, b_edge, p, b1
|
|
|
|
|
|
from scipy.sparse import bmat
|
|
|
|
|
|
def block_append_input(a: sparse.csr_matrix, b: np.ndarray, s: np.ndarray, g: np.ndarray, p: complex, b1: complex):
|
|
n = b.size
|
|
s_col = sparse.csr_matrix(s.reshape(-1, 1))
|
|
g_row = sparse.csr_matrix(g.reshape(1, -1))
|
|
p_corner = sparse.csr_matrix(np.array([[-p]]))
|
|
a2 = bmat([[a, s_col], [g_row, p_corner]], format="csr")
|
|
b2 = np.concatenate([-b, [b1]])
|
|
return a2, b2
|
|
|
|
|
|
def block_append_output(a: sparse.csr_matrix, b: np.ndarray, t: np.ndarray, g: np.ndarray, p: complex):
|
|
n = b.size
|
|
t_col = sparse.csr_matrix(t.reshape(-1, 1))
|
|
g_row = sparse.csr_matrix(g.reshape(1, -1))
|
|
p_corner = sparse.csr_matrix(np.array([[-p]]))
|
|
a2 = bmat([[a, t_col], [g_row, p_corner]], format="csr")
|
|
b2 = np.concatenate([b, [0.0]])
|
|
return a2, b2
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser(description=__doc__)
|
|
ap.add_argument(
|
|
"--export",
|
|
type=Path,
|
|
default=None,
|
|
help="export reduced A/b (COO) to this directory",
|
|
)
|
|
args = ap.parse_args()
|
|
|
|
root = Path(__file__).resolve().parents[2]
|
|
rel = root / "3D opticsfem-master/port/Release"
|
|
with h5py.File(next(root.rglob("MeshData2x.mat")), "r") as f:
|
|
nodes = np.asarray(f["Mesh/Nodes"]).T
|
|
elements = np.asarray(f["Mesh/Elements"]).T.astype(int)
|
|
faces = np.asarray(f["Mesh/Faces"]).T.astype(int)
|
|
faces_index = np.asarray(f["Mesh/FacesIndex"]).reshape(-1).astype(int)
|
|
domains = np.asarray(f["Mesh/Domains"]).reshape(-1).astype(int)
|
|
|
|
modes = json.loads(rel.joinpath("port_modes_fem4.json").read_text())
|
|
_, eoe = build_edges(elements)
|
|
|
|
a = assemble_wave_matlab(nodes, elements, domains)
|
|
b = np.zeros(a.shape[0], dtype=complex)
|
|
|
|
n_edge = int(eoe.max()) + 1
|
|
|
|
for mode, is_in in [(modes["input"], True), (modes["output"], False)]:
|
|
st = np.zeros(a.shape[0], dtype=complex)
|
|
g = np.zeros(a.shape[0], dtype=complex)
|
|
bedge = np.zeros(n_edge, dtype=complex)
|
|
p = 0.0 + 0.0j
|
|
b1 = 0.0 + 0.0j
|
|
for fi in range(len(mode["facesConn"])):
|
|
s1, g1, be1, p1, b1e = assemble_port_face(nodes, elements, eoe, mode, fi, is_in)
|
|
st[:n_edge] += s1
|
|
g[:n_edge] += g1
|
|
if is_in:
|
|
bedge += be1
|
|
b1 += b1e
|
|
p += p1
|
|
if is_in:
|
|
b[:n_edge] += bedge
|
|
a, b = block_append_input(a, b, st[: a.shape[0]], g[: a.shape[0]], p, b1)
|
|
print(f"after input |b|={np.linalg.norm(b):.6g} b1={b1:.6g}")
|
|
else:
|
|
a, b = block_append_output(a, b, st[: a.shape[0]], g[: a.shape[0]], p)
|
|
print(f"after output |b|={np.linalg.norm(b):.6g}")
|
|
|
|
pec_faces = faces[np.isin(faces_index, [1, 2, 5, 24])]
|
|
pec_pairs = set()
|
|
for tri in pec_faces:
|
|
for a0, b0 in [(tri[0], tri[1]), (tri[1], tri[2]), (tri[2], tri[0])]:
|
|
pec_pairs.add(tuple(sorted((int(a0), int(b0)))))
|
|
edge_u, _ = build_edges(elements)
|
|
pec_idx = {i for i, e in enumerate(edge_u) if tuple(e) in pec_pairs}
|
|
free = [i for i in range(a.shape[0]) if i not in pec_idx]
|
|
a = a.tocsr()[free, :][:, free]
|
|
b = b[free]
|
|
|
|
export_dir = args.export
|
|
if export_dir is None:
|
|
export_dir = root / "三维matlab代码/2023-2-端口激励问题(四面体网格)/OutFile_fem4_ab"
|
|
export_ab_coo(a, b, export_dir)
|
|
|
|
x = spsolve(a, b)
|
|
pcoef = modes["input"]["powerCoef"]
|
|
e1, e2 = x[-2], x[-1]
|
|
print(f"Python S11={e1/pcoef} S21={e2/pcoef}")
|
|
ref = next(root.rglob("OutFile_fem4/S_params.txt"))
|
|
vals = ref.read_text().strip().splitlines()[1].split()
|
|
print(f"MATLAB S11={complex(float(vals[0]), float(vals[1]))} S21={complex(float(vals[2]), float(vals[3]))}")
|
|
cpp = rel / "OutFile_fem4" / "S_params.txt"
|
|
print("C++", cpp.read_text().strip())
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|