#include"Assemble_Base.h" #include"Nedelec3D_Util.h" #include"PBC_Util.h" #include"SBC_NormalUtil.h" #include"../function/BF.h" #include"../function/Gauss.h" #include"../common/define.h" #include"../common/util.h" #include"../material/Material_Base.h" #include"../phy/Phy_Base.h" #include"../parser/mpParser.h" #include #include #include #include #include using namespace Eigen; // SBC 边界组装:按第三类边界条件 Robin 形式 K += γ,b += q // // 散射场 SBC(教材): dE_sc/dn + ik₀ E_sc = 0 → γ = ik₀,q = 0 // BELE 算例 (SBCType=0): E_s → Ae(γ) 进 A;E_b 的 ABC 切向项 + IBP 面项 → Assemble_BELE() 进 b // Robin q=0:SBC 上不组装 Be(E_inc) // // Nedelec 边元上面上的 γ 不能写成单个 K_nn+=γ,需面积分。 // 代码中 Ae(i,j) 来自 assembly_out.m,是 γ=ik₀√ε 在 Nedelec 基上的离散: // A_ij += ik₀√ε ∫ N_i·(n×(n×N_j)) dS // 这与标量 K+=γ 是同一 Robin 项在矢量一阶基下的写法,不是体域 Se/Te。 // // Be 仅用于 SBCType=1 显式入射(已知 q = Einc),不用于 BELE。 namespace { Vector3d crossNormal(const Vector3d& normal, const Vector3d& a) { return normal.cross(a.cross(normal)); } Vector3cd crossNormal(const Vector3d& normal, const Vector3cd& a) { return normal.cross(a.cross(normal)); } double getScalarEps(int domainOfTet, MaterialLib* matLib) { return matLib->GetEpsr(domainOfTet)(0, 0).real(); } bool getFaceEdgeIndices(Mesh_3D* mesh, int triIdx, int outEdges[3]) { Vector3i conn; mesh->GetCoonOfTri(triIdx, conn); const int numTet = conn(0); const int numFace = conn(1) + 1; if (numFace == 1) { outEdges[0] = mesh->GetEdgeOfTet(numTet, 0); outEdges[1] = mesh->GetEdgeOfTet(numTet, 1); outEdges[2] = mesh->GetEdgeOfTet(numTet, 3); } else if (numFace == 2) { outEdges[0] = mesh->GetEdgeOfTet(numTet, 0); outEdges[1] = mesh->GetEdgeOfTet(numTet, 2); outEdges[2] = mesh->GetEdgeOfTet(numTet, 4); } else if (numFace == 3) { outEdges[0] = mesh->GetEdgeOfTet(numTet, 1); outEdges[1] = mesh->GetEdgeOfTet(numTet, 2); outEdges[2] = mesh->GetEdgeOfTet(numTet, 5); } else if (numFace == 4) { outEdges[0] = mesh->GetEdgeOfTet(numTet, 3); outEdges[1] = mesh->GetEdgeOfTet(numTet, 4); outEdges[2] = mesh->GetEdgeOfTet(numTet, 5); } else return false; return true; } void getEdgeGeometry(Mesh_3D* mesh, int edgeId, Vector3d& midpoint, Vector3d& edgeVec) { const int v0 = mesh->GetEdge(edgeId, 0); const int v1 = mesh->GetEdge(edgeId, 1); Vector3d p0, p1; mesh->GetVertex(v0, p0); mesh->GetVertex(v1, p1); edgeVec = p1 - p0; midpoint = (p0 + p1) * 0.5; } Vector3cd evalE0AtPoint( mup::ParserX& parser, mup::Value& xx, mup::Value& yy, mup::Value& zz, const std::string& E0x, const std::string& E0y, const std::string& E0z, double x, double y, double z) { xx = x; yy = y; zz = z; parser.SetExpr(_T(E0x.c_str())); mup::Value vx = parser.Eval(); parser.SetExpr(_T(E0y.c_str())); mup::Value vy = parser.Eval(); parser.SetExpr(_T(E0z.c_str())); mup::Value vz = parser.Eval(); return Vector3cd( std::complex(vx.GetFloat(), vx.GetImag()), std::complex(vy.GetFloat(), vy.GetImag()), std::complex(vz.GetFloat(), vz.GetImag())); } Vector3cd parseEincConstant( mup::ParserX& parser, mup::Value& xx, mup::Value& yy, mup::Value& zz, const std::string& Eincx, const std::string& Eincy, const std::string& Eincz) { return evalE0AtPoint(parser, xx, yy, zz, Eincx, Eincy, Eincz, 0.0, 0.0, 0.0); } void applyDirichletBCReal( Eigen::SparseMatrix& A, Eigen::VectorXd& B, const Eigen::VectorXi& BCIndex, const Eigen::VectorXcd& BCValue) { for (int i = 0; i < BCValue.rows(); i++) { Eigen::VectorXd tempValue = Eigen::VectorXd::Zero(A.rows()); tempValue(BCIndex(i)) = 1.0; B = B - A * tempValue * BCValue(i).real(); } Eigen::SparseMatrix P(A.rows(), A.rows()); std::vector> tempTriple; int num = 0; for (int i = 0; i < A.rows(); i++) { if (num < BCIndex.rows() && i == BCIndex(num)) num++; else tempTriple.emplace_back(i, i, 1.0); } P.setFromTriplets(tempTriple.begin(), tempTriple.end()); A = P * A * P; tempTriple.clear(); for (int i = 0; i < BCIndex.rows(); i++) { tempTriple.emplace_back(BCIndex(i), BCIndex(i), 1.0); B(BCIndex(i)) = BCValue(i).real(); } P.setZero(); P.setFromTriplets(tempTriple.begin(), tempTriple.end()); A = A + P; A.makeCompressed(); } void applyDirichletBCComplex( Eigen::SparseMatrix, Eigen::RowMajor>& A, Eigen::VectorXcd& B, const Eigen::VectorXi& BCIndex, const Eigen::VectorXcd& BCValue) { for (int i = 0; i < BCValue.rows(); i++) { Eigen::VectorXcd tempValue = Eigen::VectorXcd::Zero(A.rows()); tempValue(BCIndex(i)) = 1.0; B = B - A * tempValue * BCValue(i); } Eigen::SparseMatrix, Eigen::RowMajor> P(A.rows(), A.rows()); std::vector>> tempTriple; int num = 0; for (int i = 0; i < A.rows(); i++) { if (num < BCIndex.rows() && i == BCIndex(num)) num++; else tempTriple.emplace_back(i, i, 1.0); } P.setFromTriplets(tempTriple.begin(), tempTriple.end()); A = P * A * P; tempTriple.clear(); for (int i = 0; i < BCIndex.rows(); i++) { tempTriple.emplace_back(BCIndex(i), BCIndex(i), 1.0); B(BCIndex(i)) = BCValue(i); } P.setZero(); P.setFromTriplets(tempTriple.begin(), tempTriple.end()); A = A + P; A.makeCompressed(); } void eliminateDirichletDofsComplex( Eigen::SparseMatrix, Eigen::RowMajor>& A, Eigen::VectorXcd& B, const Eigen::VectorXi& elimIndices, std::vector& freeIndicesOut) { const int n = static_cast(A.rows()); std::vector elim(static_cast(n), 0); for (int i = 0; i < elimIndices.rows(); i++) { const int idx = elimIndices(i); if (idx >= 0 && idx < n) elim[static_cast(idx)] = 1; } freeIndicesOut.clear(); freeIndicesOut.reserve(static_cast(n)); for (int i = 0; i < n; i++) if (!elim[static_cast(i)]) freeIndicesOut.push_back(i); const int nf = static_cast(freeIndicesOut.size()); if (nf == n) return; std::vector oldToNew(static_cast(n), -1); for (int i = 0; i < nf; i++) oldToNew[static_cast(freeIndicesOut[static_cast(i)])] = i; std::vector>> trips; trips.reserve(static_cast(A.nonZeros())); for (int col = 0; col < A.outerSize(); col++) { for (Eigen::SparseMatrix, Eigen::RowMajor>::InnerIterator it(A, col); it; ++it) { const int nr = oldToNew[static_cast(it.row())]; const int nc = oldToNew[static_cast(it.col())]; if (nr >= 0 && nc >= 0) trips.emplace_back(nr, nc, it.value()); } } Eigen::SparseMatrix, Eigen::RowMajor> Anew(nf, nf); Anew.setFromTriplets(trips.begin(), trips.end()); A = Anew; Eigen::VectorXcd Bnew(nf); for (int i = 0; i < nf; i++) Bnew(i) = B(freeIndicesOut[static_cast(i)]); B = Bnew; A.makeCompressed(); } void physicalPointOnFace( double u, double v, const Vector3d& x2, const Vector3d& y2, const Vector3d& z2, const Vector3d& x3, const Vector3d& y3, const Vector3d& z3, double& px, double& py, double& pz) { const double w = 1.0 - u - v; const double u2 = x2(0) * u + x2(1) * v + x2(2) * w; const double v2 = y2(0) * u + y2(1) * v + y2(2) * w; const double w2 = z2(0) * u + z2(1) * v + z2(2) * w; px = x3(0) * u2 + x3(1) * v2 + x3(2) * w2; py = y3(0) * u2 + y3(1) * v2 + y3(2) * w2; pz = z3(0) * u2 + z3(1) * v2 + z3(2) * w2; } void assembleSBCFace( Mesh_3D* mesh, MaterialLib* matLib, std::vector>>& tripleA, VectorXcd& B, double k0, double* u, double* v, double* wght, int nbrGP, BF& bfN, bool isInc, const Vector3cd& Einc, int triIdx, int elementOrder) { const int nBfFace = (elementOrder == 2) ? 8 : 3; const int domain = mesh->GetDomainOfTri(triIdx); Vector3i conn; mesh->GetCoonOfTri(triIdx, conn); const int numTet = conn(0); const int numFace = conn(1) + 1; double xv[4], yv[4], zv[4]; for (int i = 0; i < 4; i++) { Vector3d vtx; mesh->GetVertex(mesh->GetTet(numTet, i), vtx); xv[i] = vtx(0); yv[i] = vtx(1); zv[i] = vtx(2); } Vector3d x2, y2, z2; Vector3d x3, y3, z3; std::vector bfIndex(nBfFace); std::vector mappingIndex(nBfFace); if (elementOrder == 2) { if (numFace == 1) { x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 1; x3 << xv[0], xv[1], xv[2]; y3 << yv[0], yv[1], yv[2]; z3 << zv[0], zv[1], zv[2]; } else if (numFace == 2) { x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 0; x3 << xv[0], xv[1], xv[3]; y3 << yv[0], yv[1], yv[3]; z3 << zv[0], zv[1], zv[3]; } else if (numFace == 3) { x2 << 1, 0, 0; y2 << 0, 0, 0; z2 << 0, 1, 0; x3 << xv[0], xv[2], xv[3]; y3 << yv[0], yv[2], yv[3]; z3 << zv[0], zv[2], zv[3]; } else if (numFace == 4) { x2 << 0, 0, 0; y2 << 1, 0, 0; z2 << 0, 1, 0; x3 << xv[1], xv[2], xv[3]; y3 << yv[1], yv[2], yv[3]; z3 << zv[1], zv[2], zv[3]; } else return; int bfIdx[8], mapIdx[8]; Nedelec3D::sbcFaceSecondOrderBfIndex(numFace, bfIdx); Nedelec3D::buildSbcSecondOrderDofMap(mesh, numTet, numFace, mapIdx); for (int i = 0; i < 8; i++) { bfIndex[static_cast(i)] = bfIdx[i]; mappingIndex[static_cast(i)] = mapIdx[i]; } } else if (numFace == 1) { x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 1; x3 << xv[0], xv[1], xv[2]; y3 << yv[0], yv[1], yv[2]; z3 << zv[0], zv[1], zv[2]; bfIndex[0] = 1; bfIndex[1] = 2; bfIndex[2] = 4; mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 0); mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 1); mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 3); } else if (numFace == 2) { x2 << 1, 0, 0; y2 << 0, 1, 0; z2 << 0, 0, 0; x3 << xv[0], xv[1], xv[3]; y3 << yv[0], yv[1], yv[3]; z3 << zv[0], zv[1], zv[3]; bfIndex[0] = 1; bfIndex[1] = 3; bfIndex[2] = 5; mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 0); mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 2); mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 4); } else if (numFace == 3) { x2 << 1, 0, 0; y2 << 0, 0, 0; z2 << 0, 1, 0; x3 << xv[0], xv[2], xv[3]; y3 << yv[0], yv[2], yv[3]; z3 << zv[0], zv[2], zv[3]; bfIndex[0] = 2; bfIndex[1] = 3; bfIndex[2] = 6; mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 1); mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 2); mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 5); } else if (numFace == 4) { x2 << 0, 0, 0; y2 << 1, 0, 0; z2 << 0, 1, 0; x3 << xv[1], xv[2], xv[3]; y3 << yv[1], yv[2], yv[3]; z3 << zv[1], zv[2], zv[3]; bfIndex[0] = 4; bfIndex[1] = 5; bfIndex[2] = 6; mappingIndex[0] = mesh->GetEdgeOfTet(numTet, 3); mappingIndex[1] = mesh->GetEdgeOfTet(numTet, 4); mappingIndex[2] = mesh->GetEdgeOfTet(numTet, 5); } else { return; } Matrix3d Jac; Jac(0, 0) = xv[0] - xv[3]; Jac(0, 1) = yv[0] - yv[3]; Jac(0, 2) = zv[0] - zv[3]; Jac(1, 0) = xv[1] - xv[3]; Jac(1, 1) = yv[1] - yv[3]; Jac(1, 2) = zv[1] - zv[3]; Jac(2, 0) = xv[2] - xv[3]; Jac(2, 1) = yv[2] - yv[3]; Jac(2, 2) = zv[2] - zv[3]; const double detJ = Jac.determinant(); if (std::abs(detJ) < 1e-30) return; const Matrix3d InvJac = Jac.inverse(); const double a = std::sqrt((x3(0) - x3(1)) * (x3(0) - x3(1)) + (y3(0) - y3(1)) * (y3(0) - y3(1)) + (z3(0) - z3(1)) * (z3(0) - z3(1))); const double b = std::sqrt((x3(0) - x3(2)) * (x3(0) - x3(2)) + (y3(0) - y3(2)) * (y3(0) - y3(2)) + (z3(0) - z3(2)) * (z3(0) - z3(2))); const double c = std::sqrt((x3(1) - x3(2)) * (x3(1) - x3(2)) + (y3(1) - y3(2)) * (y3(1) - y3(2)) + (z3(1) - z3(2)) * (z3(1) - z3(2))); double heron = (a + b + c) * (a + b - c) * (a - b + c) * (b + c - a); if (heron < 0.0) heron = 0.0; const double integCoe = 0.25 * std::sqrt(heron); Vector3d meshNorm; mesh->GetNormOfFace(domain, meshNorm); const Vector3d faceP0(x3(0), y3(0), z3(0)); const Vector3d faceP1(x3(1), y3(1), z3(1)); const Vector3d faceP2(x3(2), y3(2), z3(2)); const Vector3d normal = OpticsFEM::computeScatterSBCNormal( isInc, domain, xv, yv, faceP0, faceP1, faceP2, &meshNorm); std::vector> E(static_cast(nbrGP), std::vector(nBfFace)); for (int gp = 0; gp < nbrGP; gp++) { const double wgp = 1.0 - u[gp] - v[gp]; const double u2 = x2(0) * u[gp] + x2(1) * v[gp] + x2(2) * wgp; const double v2 = y2(0) * u[gp] + y2(1) * v[gp] + y2(2) * wgp; const double w2 = z2(0) * u[gp] + z2(1) * v[gp] + z2(2) * wgp; for (int j = 0; j < nBfFace; j++) { bfN.GetValueBF(bfIndex[static_cast(j)], u2, v2, w2, E[static_cast(gp)][static_cast(j)]); E[static_cast(gp)][static_cast(j)] = InvJac * E[static_cast(gp)][static_cast(j)]; } } const int tetDomain = mesh->GetDomainOfTet(numTet); const double eps = getScalarEps(tetDomain, matLib); const std::complex nn = std::sqrt(std::complex(eps, 0.0)); const std::complex iUnit(0.0, 1.0); MatrixXcd Ae = MatrixXcd::Zero(nBfFace, nBfFace); for (int i = 0; i < nBfFace; i++) { for (int j = 0; j < nBfFace; j++) { for (int gp = 0; gp < nbrGP; gp++) { const Vector3d tEj = crossNormal(normal, E[static_cast(gp)][static_cast(j)]); Ae(i, j) += iUnit * k0 * nn * integCoe * wght[gp] * E[static_cast(gp)][static_cast(i)].dot(tEj) * 2.0; } } } std::vector> Be(static_cast(nBfFace), 0.0); if (isInc) { const Vector3cd tEinc = crossNormal(normal, Einc); for (int i = 0; i < nBfFace; i++) { for (int gp = 0; gp < nbrGP; gp++) { const std::complex dotE = E[static_cast(gp)][static_cast(i)](0) * tEinc(0) + E[static_cast(gp)][static_cast(i)](1) * tEinc(1) + E[static_cast(gp)][static_cast(i)](2) * tEinc(2); Be[static_cast(i)] -= iUnit * k0 * nn * 2.0 * integCoe * wght[gp] * dotE * 2.0; } } } for (int i = 0; i < nBfFace; i++) { for (int j = 0; j < nBfFace; j++) tripleA.emplace_back(mappingIndex[static_cast(i)], mappingIndex[static_cast(j)], Ae(i, j)); if (isInc) B(mappingIndex[static_cast(i)]) += Be[static_cast(i)]; } } void assembleSBCByType( Mesh_3D* mesh, MaterialLib* matLib, Phy_WaveOpticsModel* phy, std::vector>>& tripleA, VectorXcd& B, double k0, int sbcTypeFilter, bool isInc, const Vector3cd& Einc) { const int elementOrder = phy->GetElementOrder(); const int gaussTriOrder = Nedelec3D::gaussOrderTri(elementOrder); const int bfOrder = Nedelec3D::bfOrderParam(elementOrder); Gauss gauss; const int nbrGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, gaussTriOrder); double* u = new double[nbrGP]; double* v = new double[nbrGP]; double* w = new double[nbrGP]; double* wght = new double[nbrGP]; gauss.GetGaussPoints(TWODIM, TRIANGLE, u, v, w, wght); BF bfN; bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, bfOrder); const int nbrSBC = phy->GetNbrSBC(); for (int s = 0; s < nbrSBC; s++) { if (phy->GetSBCType(s) != sbcTypeFilter) continue; VectorXi triIndices; mesh->GetTriIndicesOfDomain(phy->GetSBCDomain(s), triIndices); for (int n = 0; n < triIndices.size(); n++) { assembleSBCFace(mesh, matLib, tripleA, B, k0, u, v, wght, nbrGP, bfN, isInc, Einc, triIndices(n), elementOrder); } } delete[] u; delete[] v; delete[] w; delete[] wght; } void assembleRobinOnDomains( Mesh_3D* mesh, MaterialLib* matLib, std::vector>>& tripleA, VectorXcd& B, double k0, const Eigen::VectorXi& domains) { Gauss gauss; const int nbrGP = gauss.GetNbrGaussPoints(TWODIM, TRIANGLE, BF_LINEFUNC * 2); double* u = new double[nbrGP]; double* v = new double[nbrGP]; double* w = new double[nbrGP]; double* wght = new double[nbrGP]; gauss.GetGaussPoints(TWODIM, TRIANGLE, u, v, w, wght); BF bfN; bfN.GetNbrBF(THREEDIM, TETRAHEDRON, BF_NEDELEC, BF_LINEFUNC); for (int d = 0; d < domains.rows(); d++) { VectorXi triIndices; mesh->GetTriIndicesOfDomain(domains(d), triIndices); for (int n = 0; n < triIndices.size(); n++) { assembleSBCFace(mesh, matLib, tripleA, B, k0, u, v, wght, nbrGP, bfN, false, Vector3cd::Zero(), triIndices(n), 1); } } delete[] u; delete[] v; delete[] w; delete[] wght; } void collectBoundaryEdgesOnDomains( Mesh_3D* mesh, const Eigen::VectorXi& domains, std::vector& edgeIds) { for (int i = 0; i < domains.rows(); i++) { Eigen::VectorXi triIndices; mesh->GetTriIndicesOfDomain(domains(i), triIndices); for (int t = 0; t < triIndices.size(); t++) { int faceEdges[3]; if (!getFaceEdgeIndices(mesh, triIndices(t), faceEdges)) continue; for (int n = 0; n < 3; n++) edgeIds.push_back(faceEdges[n]); } } std::sort(edgeIds.begin(), edgeIds.end()); edgeIds.erase(std::unique(edgeIds.begin(), edgeIds.end()), edgeIds.end()); } void collectBoundaryFacesOnDomains( Mesh_3D* mesh, const Eigen::VectorXi& domains, std::vector& faceIds) { for (int i = 0; i < domains.rows(); i++) { Eigen::VectorXi triIndices; mesh->GetTriIndicesOfDomain(domains(i), triIndices); for (int t = 0; t < triIndices.size(); t++) { Eigen::Vector3i conn; mesh->GetCoonOfTri(triIndices(t), conn); faceIds.push_back(mesh->GetFaceOfTet(conn(0), conn(1))); } } std::sort(faceIds.begin(), faceIds.end()); faceIds.erase(std::unique(faceIds.begin(), faceIds.end()), faceIds.end()); } void collectPecDofIndices( Mesh_3D* mesh, const Eigen::VectorXi& pecDomains, int elementOrder, std::vector& dofIndices) { std::vector pecEdges; collectBoundaryEdgesOnDomains(mesh, pecDomains, pecEdges); const int nE = mesh->GetNbrEdge(); const int nF = mesh->GetNbrFace(); for (int edgeId : pecEdges) { dofIndices.push_back(Nedelec3D::edgeGlobalDof(edgeId, 0, nE)); if (elementOrder == 2) dofIndices.push_back(Nedelec3D::edgeGlobalDof(edgeId, 1, nE)); } if (elementOrder == 2) { std::vector pecFaces; collectBoundaryFacesOnDomains(mesh, pecDomains, pecFaces); for (int faceId : pecFaces) { dofIndices.push_back(Nedelec3D::faceGlobalDof(faceId, 0, nE, nF)); dofIndices.push_back(Nedelec3D::faceGlobalDof(faceId, 1, nE, nF)); } } std::sort(dofIndices.begin(), dofIndices.end()); dofIndices.erase(std::unique(dofIndices.begin(), dofIndices.end()), dofIndices.end()); } } // namespace void OpticsFEM_3D_Scatter::Assemble_SBC() { const double k0 = 2.0 * Pi / _mSolver->GetLda0(); // SBCType=0: γ=ik₀,q=0 → 只修正 A assembleSBCByType(_mMesh, _mMatLib, _mPhy, _mTripleA_complex, _mB_complex, k0, 0, false, Vector3cd::Zero()); Vector3cd Einc = Vector3cd::Zero(); for (int s = 0; s < _mPhy->GetNbrSBC(); s++) { if (_mPhy->GetSBCType(s) != 1) continue; std::string sx, sy, sz; _mPhy->GetEinc(s, sx, sy, sz); mup::ParserX parser(mup::pckALL_COMPLEX); parser.EnableAutoCreateVar(true); mup::Value xx(0.0), yy(0.0), zz(0.0); parser.DefineVar(_T("x"), mup::Variable(&xx)); parser.DefineVar(_T("y"), mup::Variable(&yy)); parser.DefineVar(_T("z"), mup::Variable(&zz)); Einc = parseEincConstant(parser, xx, yy, zz, sx, sy, sz); break; } assembleSBCByType(_mMesh, _mMatLib, _mPhy, _mTripleA_complex, _mB_complex, k0, 1, true, Einc); } void OpticsFEM_3D_Scatter::Assemble_PEC_ELE() { const int elementOrder = _mPhy->GetElementOrder(); const int nbrPEC = _mPhy->GetNbrPEC(); const int nbrELE = _mPhy->GetNbrElE(); if (nbrPEC == 0 && nbrELE == 0) return; if (nbrELE > 0 && elementOrder == 2) { std::cerr << "[OpticsFEM] ElementOrder=2: ELE uses edge DOFs (×2) + zero face DOFs " << "(midpoint tangential projection)." << std::endl; } std::vector pecDofs; if (nbrPEC > 0) { Eigen::VectorXi PECDomain = Eigen::VectorXi::Zero(nbrPEC); for (int i = 0; i < nbrPEC; i++) PECDomain(i) = _mPhy->GetPECDomain(i) + 1; collectPecDofIndices(_mMesh, PECDomain, elementOrder, pecDofs); } Eigen::VectorXi triIndexOfELE, triNumOfELE; if (nbrELE > 0) { Eigen::VectorXi ELEDomain = Eigen::VectorXi::Zero(nbrELE); Eigen::VectorXi indexNum = Eigen::VectorXi::Zero(nbrELE); for (int i = 0; i < nbrELE; i++) { ELEDomain(i) = _mPhy->GetELEDomain(i); indexNum(i) = i; } _mMesh->GetTriIndexOfDomain2(ELEDomain, indexNum, triIndexOfELE, triNumOfELE); } const int dofsPerEleFace = (elementOrder == 2) ? 8 : 3; const int nbrELEEdges = (nbrELE > 0) ? static_cast(triIndexOfELE.size()) * dofsPerEleFace : 0; const int nbrPECDofs = (nbrPEC > 0) ? static_cast(pecDofs.size()) : 0; const int nbrBC = nbrPECDofs + nbrELEEdges; if (nbrBC == 0) return; Eigen::VectorXi BCIndex = Eigen::VectorXi::Zero(nbrBC); Eigen::VectorXcd BCValue = Eigen::VectorXcd::Zero(nbrBC); mup::ParserX parser(mup::pckALL_COMPLEX); parser.EnableAutoCreateVar(true); mup::Value xx, yy, zz; parser.DefineVar(_T("x"), mup::Variable(&xx)); parser.DefineVar(_T("y"), mup::Variable(&yy)); parser.DefineVar(_T("z"), mup::Variable(&zz)); int bcOffset = 0; for (int m = 0; m < triIndexOfELE.size(); m++) { int faceEdges[3]; if (!getFaceEdgeIndices(_mMesh, triIndexOfELE(m), faceEdges)) continue; std::string E0x, E0y, E0z; _mPhy->GetE0(E0x, E0y, E0z, triNumOfELE(m)); std::string ExFunc = E0x, EyFunc = E0y, EzFunc = E0z; if (_mPhy->GetNbrBELE() > 0) { std::string Ebx, Eby, Ebz; _mPhy->GetEb(Ebx, Eby, Ebz); ExFunc = E0x + "-" + Ebx; EyFunc = E0y + "-" + Eby; EzFunc = E0z + "-" + Ebz; } const int nE = _mMesh->GetNbrEdge(); if (elementOrder == 2) { Eigen::Vector3i conn; _mMesh->GetCoonOfTri(triIndexOfELE(m), conn); int mapIdx[8]; Nedelec3D::buildSbcSecondOrderDofMap(_mMesh, conn(0), conn(1) + 1, mapIdx); for (int n = 0; n < 3; n++) { Vector3d midpoint, edgeVec; getEdgeGeometry(_mMesh, faceEdges[n], midpoint, edgeVec); xx = midpoint(0); yy = midpoint(1); zz = midpoint(2); const Vector3cd E0 = evalE0AtPoint(parser, xx, yy, zz, ExFunc, EyFunc, EzFunc, midpoint(0), midpoint(1), midpoint(2)); const std::complex edgeVal = E0.dot(edgeVec); BCIndex(bcOffset + n) = mapIdx[n]; BCValue(bcOffset + n) = edgeVal; BCIndex(bcOffset + 3 + n) = mapIdx[3 + n]; BCValue(bcOffset + 3 + n) = edgeVal; } BCIndex(bcOffset + 6) = mapIdx[6]; BCValue(bcOffset + 6) = 0.0; BCIndex(bcOffset + 7) = mapIdx[7]; BCValue(bcOffset + 7) = 0.0; bcOffset += 8; (void)nE; } else { for (int n = 0; n < 3; n++) { Vector3d midpoint, edgeVec; getEdgeGeometry(_mMesh, faceEdges[n], midpoint, edgeVec); xx = midpoint(0); yy = midpoint(1); zz = midpoint(2); const Vector3cd E0 = evalE0AtPoint(parser, xx, yy, zz, ExFunc, EyFunc, EzFunc, midpoint(0), midpoint(1), midpoint(2)); BCIndex(bcOffset + n) = faceEdges[n]; BCValue(bcOffset + n) = E0.dot(edgeVec); } bcOffset += 3; } } for (int i = 0; i < nbrPECDofs; i++) { BCIndex(bcOffset + i) = pecDofs[static_cast(i)]; BCValue(bcOffset + i) = 0.0; } Eigen::VectorXi tempIndex = Eigen::VectorXi::LinSpaced(nbrBC, 0, nbrBC - 1); QuickSort(BCIndex, tempIndex, 0, nbrBC - 1); Unique(BCIndex, tempIndex); Eigen::VectorXcd tempValue = Eigen::VectorXcd::Zero(BCIndex.rows()); for (int i = 0; i < BCIndex.rows(); i++) tempValue(i) = BCValue(tempIndex(i)); BCValue = tempValue; // Port + PEC-only: match MATLAB FemMatrixAssembly (eliminate PEC DOFs before solve). // Do NOT use applyDirichletBC here — it breaks the augmented port system. if (!_mIsReal && _mPhy->HasNumericPort() && nbrELE == 0 && nbrPECDofs > 0) { eliminateDirichletDofsComplex(_mA_complex, _mB_complex, BCIndex, _mFreeDofIndices); return; } if (_mIsReal) applyDirichletBCReal(_mA_real, _mB_real, BCIndex, BCValue); else applyDirichletBCComplex(_mA_complex, _mB_complex, BCIndex, BCValue); } void OpticsFEM_3D_Scatter::Assemble_PBC() { if (_mPhy->GetNbrPBCGroups() <= 0) return; const int elementOrder = _mPhy->GetElementOrder(); const int dof = Nedelec3D::globalDofCount(_mMesh, elementOrder); assemblePbcProjectionMatrix(_mMesh, _mPhy, elementOrder, dof, _mIsReal, _mP_real, _mP_complex); }