#include "PortModeSolver.h" #include "../mesh/Mesh_Base.h" #include "../material/Material_Base.h" #include "../function/BF.h" #include "../function/Gauss.h" #include "../common/define.h" #include "../nlohmann/json.hpp" #include "../Eigen/Sparse" #include "../Eigen/SparseLU" #include "../Eigen/Eigenvalues" #include #include #include #include #include #include #include #include #include using namespace Eigen; using json = nlohmann::json; namespace { struct PortBoundaryMesh { MatrixXd nodes; // nNodes x 3 MatrixXi newFaces; // nf x 3 (0-based local node) MatrixXi facesConn; // nf x 2 (1-based tet, 1-based local face) VectorXi volDomain; // nf (1-based volume domain for material) MatrixXi edgeOfFace; // nf x 3 (0-based edge DOF) MatrixXi edgeNodes; // nEdges x 2 (0-based local nodes) int nNodes = 0; int nEdges = 0; int nFaces = 0; }; bool domainInList(int domain, const VectorXi& list) { for (int i = 0; i < list.size(); ++i) if (list(i) == domain) return true; return false; } bool extractPortMesh(Mesh_3D* mesh, const VectorXi& domains, PortBoundaryMesh& out) { std::vector triIds; const int nTri = mesh->GetNbrTri(); for (int t = 0; t < nTri; ++t) { if (domainInList(mesh->GetDomainOfTri(t), domains)) triIds.push_back(t); } if (triIds.empty()) { std::cerr << "[PortModeSolver] no port triangles for domains" << std::endl; return false; } const int nf = static_cast(triIds.size()); MatrixXi oldFaces(nf, 3); out.facesConn = MatrixXi::Zero(nf, 2); out.volDomain = VectorXi::Zero(nf); std::vector globalNodes; globalNodes.reserve(nf * 3); for (int i = 0; i < nf; ++i) { const int tri = triIds[i]; for (int k = 0; k < 3; ++k) { oldFaces(i, k) = mesh->GetTri(tri, k); globalNodes.push_back(oldFaces(i, k)); } Vector3i conn; mesh->GetCoonOfTri(tri, conn); out.facesConn(i, 0) = conn(0) + 1; out.facesConn(i, 1) = conn(1) + 1; out.volDomain(i) = mesh->GetDomainOfTet(conn(0)); } std::sort(globalNodes.begin(), globalNodes.end()); globalNodes.erase(std::unique(globalNodes.begin(), globalNodes.end()), globalNodes.end()); out.nNodes = static_cast(globalNodes.size()); out.nodes = MatrixXd::Zero(out.nNodes, 3); std::map g2l; for (int i = 0; i < out.nNodes; ++i) { g2l[globalNodes[i]] = i; Vector3d v; mesh->GetVertex(globalNodes[i], v); out.nodes.row(i) = v.transpose(); } out.newFaces = MatrixXi::Zero(nf, 3); for (int i = 0; i < nf; ++i) for (int k = 0; k < 3; ++k) out.newFaces(i, k) = g2l[oldFaces(i, k)]; // Local edges like MATLAB: (n1,n2),(n1,n3),(n2,n3) without sorting pair; unique by lexicographic rows. std::vector> edgeRows; edgeRows.reserve(static_cast(nf) * 3); for (int i = 0; i < nf; ++i) { const int a = out.newFaces(i, 0); const int b = out.newFaces(i, 1); const int c = out.newFaces(i, 2); edgeRows.push_back({ a, b }); edgeRows.push_back({ a, c }); edgeRows.push_back({ b, c }); } std::vector> uniqueEdges = edgeRows; std::sort(uniqueEdges.begin(), uniqueEdges.end()); uniqueEdges.erase(std::unique(uniqueEdges.begin(), uniqueEdges.end()), uniqueEdges.end()); out.nEdges = static_cast(uniqueEdges.size()); std::map, int> e2i; for (int i = 0; i < out.nEdges; ++i) e2i[uniqueEdges[i]] = i; out.edgeOfFace = MatrixXi::Zero(nf, 3); out.nFaces = nf; for (int i = 0; i < nf; ++i) { for (int k = 0; k < 3; ++k) out.edgeOfFace(i, k) = e2i[edgeRows[static_cast(i) * 3 + k]]; } out.edgeNodes = MatrixXi::Zero(out.nEdges, 2); for (int i = 0; i < out.nEdges; ++i) { out.edgeNodes(i, 0) = uniqueEdges[static_cast(i)][0]; out.edgeNodes(i, 1) = uniqueEdges[static_cast(i)][1]; } return true; } void assemblePortEigen( const PortBoundaryMesh& pm, MaterialLib* matLib, double lam0, SparseMatrix, RowMajor>& Ared, SparseMatrix, RowMajor>& Bred, SparseMatrix, RowMajor>& P, int& dofFull) { const double k0 = 2.0 * Pi / lam0; const int nNodes = pm.nNodes; const int nEdges = pm.nEdges; dofFull = nNodes + nEdges; Gauss gauss; const int nGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, BF_LINEFUNC * 2); std::vector u(nGP), v(nGP), w(nGP), wght(nGP); gauss.GetGaussPoints(TWODIM, TRIANGLE, u.data(), v.data(), w.data(), wght.data()); BF bfEt, bfCurlEt, bfEz, bfGradEz; bfEt.GetNbrBF(TWODIM, TRIANGLE, BF_NEDELEC, BF_LINEFUNC); bfCurlEt.GetNbrBF(TWODIM, TRIANGLE, BF_CURL_NEDELEC, BF_LINEFUNC); bfEz.GetNbrBF(TWODIM, TRIANGLE, BF_LAGRANGE, BF_LINEFUNC); bfGradEz.GetNbrBF(TWODIM, TRIANGLE, BF_GRAD_LAGRANGE, BF_LINEFUNC); using Trip = Triplet>; std::vector tripsA, tripsB; tripsA.reserve(static_cast(pm.nFaces) * 9); tripsB.reserve(static_cast(pm.nFaces) * 36); std::vector edgeCount(static_cast(nEdges), 0); for (int f = 0; f < pm.nFaces; ++f) for (int k = 0; k < 3; ++k) edgeCount[static_cast(pm.edgeOfFace(f, k))]++; for (int n = 0; n < pm.nFaces; ++n) { double x[3], y[3], l[3]; for (int i = 0; i < 3; ++i) { x[i] = pm.nodes(pm.newFaces(n, i), 0); y[i] = pm.nodes(pm.newFaces(n, i), 1); } l[0] = std::sqrt((x[0] - x[1]) * (x[0] - x[1]) + (y[0] - y[1]) * (y[0] - y[1])); l[1] = std::sqrt((x[0] - x[2]) * (x[0] - x[2]) + (y[0] - y[2]) * (y[0] - y[2])); l[2] = std::sqrt((x[2] - x[1]) * (x[2] - x[1]) + (y[2] - y[1]) * (y[2] - y[1])); Matrix3d Jac = Matrix3d::Zero(); Jac(0, 0) = x[1] - x[0]; Jac(0, 1) = y[1] - y[0]; Jac(1, 0) = x[2] - x[0]; Jac(1, 1) = y[2] - y[0]; Jac(2, 2) = 1.0; const Matrix3d InvJac = Jac.inverse(); const double DetJac = std::abs(Jac.determinant()); const Matrix3d TJac = Jac.transpose() / Jac.determinant(); std::vector Et(3 * nGP), curlEt(3 * nGP), Ez(3 * nGP), gradEz(3 * nGP); auto at = [nGP](std::vector& a, int j, int gp) -> Vector3d& { return a[static_cast(j * nGP + gp)]; }; for (int gp = 0; gp < nGP; ++gp) { for (int j = 0; j < 3; ++j) { Vector3d temp; bfEt.GetValueBF(j + 1, u[gp], v[gp], 0.0, temp); at(Et, j, gp) = InvJac * temp * l[j]; bfCurlEt.GetValueBF(j + 1, u[gp], v[gp], 0.0, temp); at(curlEt, j, gp) = TJac * temp * l[j]; bfEz.GetValueBF(j + 1, u[gp], v[gp], 0.0, temp); at(Ez, j, gp) = temp; bfGradEz.GetValueBF(j + 1, u[gp], v[gp], 0.0, temp); at(gradEz, j, gp) = InvJac * temp; } } const int domain = pm.volDomain(n); Matrix3cd epsr = matLib->GetEpsr(domain); const Matrix3d sigma = matLib->GetSigma(domain); epsr = epsr - sigma * std::complex(0.0, 1.0 / k0 * 120.0 * Pi); const Matrix3cd mur = matLib->GetMur(domain); const std::complex mu = mur(0, 0); const std::complex epsilon = epsr(0, 0); Matrix3cd St = Matrix3cd::Zero(), Tte = Matrix3cd::Zero(); Matrix3cd Sz = Matrix3cd::Zero(), Tz = Matrix3cd::Zero(); Matrix3cd G = Matrix3cd::Zero(), Ttu = Matrix3cd::Zero(); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { for (int gp = 0; gp < nGP; ++gp) { const double wgt = wght[gp] * DetJac; St(i, j) += wgt / mu * at(curlEt, i, gp).dot(at(curlEt, j, gp)); Tte(i, j) += wgt * k0 * k0 * epsilon * at(Et, i, gp).dot(at(Et, j, gp)); Sz(i, j) += wgt / mu * at(gradEz, i, gp).dot(at(gradEz, j, gp)); Tz(i, j) += wgt * k0 * k0 * epsilon * at(Ez, i, gp).dot(at(Ez, j, gp)); G(i, j) += wgt / mu * at(Et, i, gp).dot(at(gradEz, j, gp)); Ttu(i, j) += wgt / mu * at(Et, i, gp).dot(at(Et, j, gp)); } } } const Matrix3cd Gt = G.transpose(); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { const int si = pm.newFaces(n, i); const int sj = pm.newFaces(n, j); const int vi = pm.edgeOfFace(n, i) + nNodes; const int vj = pm.edgeOfFace(n, j) + nNodes; tripsA.emplace_back(vi, vj, St(i, j) - Tte(i, j)); tripsB.emplace_back(vi, vj, Ttu(i, j)); tripsB.emplace_back(si, sj, Sz(i, j) - Tz(i, j)); tripsB.emplace_back(vi, sj, G(i, j)); tripsB.emplace_back(si, vj, Gt(i, j)); } } } SparseMatrix, RowMajor> Afull(dofFull, dofFull), Bfull(dofFull, dofFull); Afull.setFromTriplets(tripsA.begin(), tripsA.end()); Bfull.setFromTriplets(tripsB.begin(), tripsB.end()); // Auto PEC: boundary edges (count==1) and their nodes (MATLAB EigenModeMatrixAssembly) std::set pec; for (int e = 0; e < nEdges; ++e) { if (edgeCount[static_cast(e)] != 1) continue; pec.insert(pm.edgeNodes(e, 0)); pec.insert(pm.edgeNodes(e, 1)); pec.insert(e + nNodes); } std::vector pecInd(pec.begin(), pec.end()); std::sort(pecInd.begin(), pecInd.end()); std::vector isPec(static_cast(dofFull), 0); for (int idx : pecInd) if (idx >= 0 && idx < dofFull) isPec[static_cast(idx)] = 1; std::vector freeOfFull; freeOfFull.reserve(static_cast(dofFull)); for (int i = 0; i < dofFull; ++i) if (!isPec[static_cast(i)]) freeOfFull.push_back(i); const int nFree = static_cast(freeOfFull.size()); std::vector pTrips; pTrips.reserve(static_cast(nFree)); for (int j = 0; j < nFree; ++j) pTrips.emplace_back(freeOfFull[j], j, 1.0); P = SparseMatrix, RowMajor>(dofFull, nFree); P.setFromTriplets(pTrips.begin(), pTrips.end()); Ared = SparseMatrix, RowMajor>(nFree, nFree); Bred = SparseMatrix, RowMajor>(nFree, nFree); Ared = SparseMatrix, RowMajor>(P.transpose() * Afull * P); Bred = SparseMatrix, RowMajor>(P.transpose() * Bfull * P); } double computePowerCoef( const PortBoundaryMesh& pm, MaterialLib* matLib, double lam0, const std::complex& gamma, const VectorXcd& Ez, const VectorXcd& Et) { const double k0 = 2.0 * Pi / lam0; Gauss gauss; const int nGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, BF_LINEFUNC * 2); std::vector u(nGP), v(nGP), w(nGP), wght(nGP); gauss.GetGaussPoints(TWODIM, TRIANGLE, u.data(), v.data(), w.data(), wght.data()); BF bfEt, bfCurlEt, bfEz, bfCurlEz; bfEt.GetNbrBF(TWODIM, TRIANGLE, BF_NEDELEC, BF_LINEFUNC); bfCurlEt.GetNbrBF(TWODIM, TRIANGLE, BF_CURL_NEDELEC, BF_LINEFUNC); bfEz.GetNbrBF(TWODIM, TRIANGLE, BF_LAGRANGE, BF_LINEFUNC); bfCurlEz.GetNbrBF(TWODIM, TRIANGLE, BF_CURL_LAGRANGE, BF_LINEFUNC); double power = 0.0; for (int n = 0; n < pm.nFaces; ++n) { double x[3], y[3], l[3]; for (int i = 0; i < 3; ++i) { x[i] = pm.nodes(pm.newFaces(n, i), 0); y[i] = pm.nodes(pm.newFaces(n, i), 1); } l[0] = std::sqrt((x[0] - x[1]) * (x[0] - x[1]) + (y[0] - y[1]) * (y[0] - y[1])); l[1] = std::sqrt((x[0] - x[2]) * (x[0] - x[2]) + (y[0] - y[2]) * (y[0] - y[2])); l[2] = std::sqrt((x[2] - x[1]) * (x[2] - x[1]) + (y[2] - y[1]) * (y[2] - y[1])); Matrix3d Jac = Matrix3d::Zero(); Jac(0, 0) = x[1] - x[0]; Jac(0, 1) = y[1] - y[0]; Jac(1, 0) = x[2] - x[0]; Jac(1, 1) = y[2] - y[0]; Jac(2, 2) = 1.0; const Matrix3d InvJac = Jac.inverse(); const double DetJac = std::abs(Jac.determinant()); const Matrix3d TJac = Jac.transpose() / Jac.determinant(); Matrix3d JacS = Matrix3d::Zero(); JacS(0, 0) = InvJac(1, 1); JacS(0, 1) = -InvJac(1, 0); JacS(1, 0) = -InvJac(0, 1); JacS(1, 1) = InvJac(0, 0); JacS(2, 2) = 1.0; const int domain = pm.volDomain(n); const double mu = std::real(matLib->GetMur(domain)(0, 0)); const std::complex physicsIndex = std::complex(0.0, 1.0) / (k0 * 120.0 * Pi) / mu; for (int gp = 0; gp < nGP; ++gp) { Vector3cd EE = Vector3cd::Zero(); Vector3cd curlEE = Vector3cd::Zero(); for (int i = 0; i < 3; ++i) { Vector3d temp; bfEt.GetValueBF(i + 1, u[gp], v[gp], 0.0, temp); const Vector3d et = InvJac * temp * l[i]; bfEz.GetValueBF(i + 1, u[gp], v[gp], 0.0, temp); const Vector3d ezVec(0, 0, temp(2)); bfCurlEt.GetValueBF(i + 1, u[gp], v[gp], 0.0, temp); const Vector3d curlEtVec = TJac * Vector3d(0, 0, temp(2)) * l[i]; bfCurlEz.GetValueBF(i + 1, u[gp], v[gp], 0.0, temp); const Vector3d curlEzVec = JacS * Vector3d(temp(0), temp(1), 0.0); const int mapEt = pm.edgeOfFace(n, i); const int mapEz = pm.newFaces(n, i); EE += et.cast>() * Et(mapEt) + ezVec.cast>() * Ez(mapEz); curlEE += curlEtVec.cast>() * Et(mapEt) + curlEzVec.cast>() * Ez(mapEz); } Vector3cd HH = curlEE; HH(0) += EE(1) * gamma; HH(1) -= EE(0) * gamma; HH *= physicsIndex; const double rEx = EE(0).real(), iEx = EE(0).imag(); const double rEy = EE(1).real(), iEy = EE(1).imag(); const double rHx = HH(0).real(), iHx = HH(0).imag(); const double rHy = HH(1).real(), iHy = HH(1).imag(); power += wght[gp] * 0.5 * DetJac * (-rEy * rHx - iEy * iHx + rEx * rHy + iEx * iHy); } } if (!(power > 0.0)) { std::cerr << "[PortModeSolver] power<=0 (" << power << "), powerCoef=1" << std::endl; return 1.0; } return std::sqrt(1.0 / power); } bool solveOnePort( Mesh_3D* mesh, MaterialLib* matLib, const VectorXi& domains, double lambda0, double targetNeff, int modeNum, int portType, bool normalizePower, PortNumericMode& mode) { PortBoundaryMesh pm; if (!extractPortMesh(mesh, domains, pm)) return false; SparseMatrix, RowMajor> A, B, P; int dofFull = 0; assemblePortEigen(pm, matLib, lambda0, A, B, P, dofFull); const double k0 = 2.0 * Pi / lambda0; // MATLAB: sigma = -(k0*targetNeff)^2, A x = λ B x, gamma=sqrt(λ), neff=(-i*gamma)/k0 const std::complex sigma(-(k0 * targetNeff) * (k0 * targetNeff), 0.0); const int nFree = static_cast(A.rows()); if (nFree <= 0) { std::cerr << "[PortModeSolver] empty free DOF after PEC" << std::endl; return false; } const MatrixXcd Ad = MatrixXcd(A); const MatrixXcd Bd = MatrixXcd(B); const MatrixXcd As = Ad - sigma * Bd; Eigen::PartialPivLU lu(As); if (lu.determinant() == std::complex(0, 0)) { std::cerr << "[PortModeSolver] (A-σB) singular" << std::endl; return false; } // M = (A-σB)^{-1} B ; eig(M)=ν=1/(λ-σ) ⇒ λ = σ + 1/ν const MatrixXcd M = lu.solve(Bd); Eigen::ComplexEigenSolver ces(M); if (ces.info() != Eigen::Success) { std::cerr << "[PortModeSolver] ComplexEigenSolver failed" << std::endl; return false; } const VectorXcd nu = ces.eigenvalues(); const MatrixXcd V = ces.eigenvectors(); // Prefer largest |ν| (closest to σ), then pick among top few by Re(neff)~target std::vector order(static_cast(nu.size())); for (int i = 0; i < nu.size(); ++i) order[static_cast(i)] = i; std::sort(order.begin(), order.end(), [&](int a, int b) { return std::abs(nu(a)) > std::abs(nu(b)); }); int best = order[0]; double bestScore = 1e300; const int nCand = std::min(modeNum + 8, static_cast(order.size())); for (int c = 0; c < nCand; ++c) { const int i = order[static_cast(c)]; if (std::abs(nu(i)) < 1e-30) continue; const std::complex lam = sigma + std::complex(1.0, 0.0) / nu(i); const std::complex gam = std::sqrt(lam); const std::complex ne = -std::complex(0.0, 1.0) * gam / k0; const double score = std::abs(ne.real() - targetNeff) + 0.25 * std::abs(ne.imag()); if (score < bestScore) { bestScore = score; best = i; } } const std::complex lamBest = sigma + std::complex(1.0, 0.0) / nu(best); std::complex gamma = std::sqrt(lamBest); // Branch: prefer Im(gamma)>0 for decaying envelope convention used with neff≈-i*gamma/k0 std::complex neff = -std::complex(0.0, 1.0) * gamma / k0; if (neff.real() < 0.0) { gamma = -gamma; neff = -std::complex(0.0, 1.0) * gamma / k0; } VectorXcd xFree = V.col(best); VectorXcd xFull = P * xFree; std::cout << "[PortModeSolver] type=" << portType << " neff=" << neff << " gamma=" << gamma << " nFaces=" << pm.nFaces << " nNodes=" << pm.nNodes << " nEdges=" << pm.nEdges << " nFree=" << nFree << std::endl; VectorXcd Ez = xFull.head(pm.nNodes); VectorXcd Et = xFull.segment(pm.nNodes, pm.nEdges); if (std::abs(gamma) > 0.0) Et /= gamma; // Phase gauge e^{iφ}=1: make the peak-|Et| DOF real and positive (COMSOL-like Mode phase 0). { Eigen::Index iPeak = 0; Et.cwiseAbs().maxCoeff(&iPeak); const std::complex peak = Et(iPeak); if (std::abs(peak) > 0.0) { const std::complex fix = std::abs(peak) / peak; Et *= fix; Ez *= fix; std::cout << "[PortModeSolver] phase gauge: arg(Et_peak)->0 (e^{iφ}=1)" << " iPeak=" << iPeak << std::endl; } } double powerCoef = 1.0; if (normalizePower && portType == 1) powerCoef = computePowerCoef(pm, matLib, lambda0, gamma, Ez, Et); mode.type = portType; mode.gamma = gamma; mode.powerCoef = powerCoef; mode.normal = (portType == 1) ? Vector3d(0, 0, -1) : Vector3d(0, 0, 1); mode.facesConn = pm.facesConn; mode.portNewFaces = pm.newFaces; mode.portEdgeOfFace = pm.edgeOfFace; mode.portNodes = pm.nodes; mode.Ez = Ez; mode.Et = Et; std::cout << "[PortModeSolver] powerCoef=" << powerCoef << " |Et|_max=" << Et.cwiseAbs().maxCoeff() << " |Ez|_max=" << Ez.cwiseAbs().maxCoeff() << std::endl; return true; } } // namespace bool ComputePortModesFromMesh( Mesh_3D* mesh, MaterialLib* matLib, const VectorXi& domainsInc, const VectorXi& domainsOut, double lambda0, double targetNeff, int modeNum, PortModeLibrary& outModes, bool normalizeInputPower) { outModes.Clear(); PortNumericMode inMode, outMode; if (!solveOnePort(mesh, matLib, domainsInc, lambda0, targetNeff, modeNum, 1, normalizeInputPower, inMode)) return false; if (!solveOnePort(mesh, matLib, domainsOut, lambda0, targetNeff, modeNum, 2, false, outMode)) return false; outModes.AddMode(inMode); outModes.AddMode(outMode); return true; } bool SavePortModesToFile(const PortModeLibrary& modes, const std::string& path) { if (modes.GetNbrModes() < 2) return false; auto pack = [](const PortNumericMode& m) { json j; j["type"] = m.type; j["gamma"] = { m.gamma.real(), m.gamma.imag() }; j["powerCoef"] = m.powerCoef; j["normal"] = { m.normal(0), m.normal(1), m.normal(2) }; j["facesConn"] = json::array(); for (int i = 0; i < m.facesConn.rows(); ++i) j["facesConn"].push_back({ m.facesConn(i, 0), m.facesConn(i, 1) }); j["portNewFaces"] = json::array(); for (int i = 0; i < m.portNewFaces.rows(); ++i) j["portNewFaces"].push_back({ m.portNewFaces(i, 0), m.portNewFaces(i, 1), m.portNewFaces(i, 2) }); j["portEdgeOfFace"] = json::array(); for (int i = 0; i < m.portEdgeOfFace.rows(); ++i) j["portEdgeOfFace"].push_back({ m.portEdgeOfFace(i, 0), m.portEdgeOfFace(i, 1), m.portEdgeOfFace(i, 2) }); j["portNodes"] = json::array(); for (int i = 0; i < m.portNodes.rows(); ++i) j["portNodes"].push_back({ m.portNodes(i, 0), m.portNodes(i, 1), m.portNodes(i, 2) }); j["Ez_re"] = json::array(); j["Ez_im"] = json::array(); for (int i = 0; i < m.Ez.size(); ++i) { j["Ez_re"].push_back(m.Ez(i).real()); j["Ez_im"].push_back(m.Ez(i).imag()); } j["Et_re"] = json::array(); j["Et_im"] = json::array(); for (int i = 0; i < m.Et.size(); ++i) { j["Et_re"].push_back(m.Et(i).real()); j["Et_im"].push_back(m.Et(i).imag()); } return j; }; json js; js["input"] = pack(modes.GetMode(0)); js["output"] = pack(modes.GetMode(1)); std::ofstream ofs(path); if (!ofs.is_open()) return false; ofs << js.dump(); return true; }