#include "PBC_Util.h" #include "Nedelec3D_Util.h" #include #include namespace { int findMemberIndex(const std::vector& values, int key) { for (size_t i = 0; i < values.size(); i++) { if (values[i] == key) return static_cast(i); } return -1; } std::vector findDuplicateValues(const std::vector& values) { std::unordered_map counts; for (int v : values) counts[v]++; std::vector duplicates; for (const auto& entry : counts) { if (entry.second > 1) duplicates.push_back(entry.first); } std::sort(duplicates.begin(), duplicates.end()); return duplicates; } } // namespace PbcEdgePairs collectPbcEdgePairs( Mesh_3D* mesh, const std::vector& srcDomains, const std::vector& dstDomains, const Eigen::VectorXd& pbcAngles, const Eigen::MatrixXd& pbcWaveVecs, bool useFaceStyleEdgeTol) { const int nbrGroups = static_cast(srcDomains.size()); Eigen::VectorXcd unitPhi = Eigen::VectorXcd::Ones(nbrGroups); Eigen::VectorXi srcEdgeIndex; Eigen::VectorXi dstEdgeIndex; Eigen::VectorXcd edgePhi; mesh->GetIndexOfPBC(srcDomains, dstDomains, unitPhi, pbcAngles, pbcWaveVecs, srcEdgeIndex, dstEdgeIndex, edgePhi, useFaceStyleEdgeTol); sortUniquePbcPairs(srcEdgeIndex, dstEdgeIndex, edgePhi); PbcEdgePairs pairs; const int n = static_cast(srcEdgeIndex.size()); pairs.src.resize(n); pairs.dst.resize(n); pairs.sign.resize(n); for (int i = 0; i < n; i++) { pairs.src[i] = srcEdgeIndex(i); pairs.dst[i] = dstEdgeIndex(i); pairs.sign[i] = edgePhi(i); } return pairs; } PbcEdgePairs collectPbcFacePairs( Mesh_3D* mesh, const std::vector& srcDomains, const std::vector& dstDomains, const Eigen::VectorXd& pbcAngles, const Eigen::MatrixXd& pbcWaveVecs) { Eigen::VectorXi srcFaceIndex; Eigen::VectorXi dstFaceIndex; Eigen::VectorXcd facePhi; mesh->GetIndexOfPBCFaces(srcDomains, dstDomains, pbcAngles, pbcWaveVecs, srcFaceIndex, dstFaceIndex, facePhi); PbcEdgePairs pairs; const int n = static_cast(srcFaceIndex.size()); pairs.src.resize(n); pairs.dst.resize(n); pairs.sign.resize(n); for (int i = 0; i < n; i++) { pairs.src[i] = srcFaceIndex(i); pairs.dst[i] = dstFaceIndex(i); pairs.sign[i] = facePhi(i); } return pairs; } void mergeDoublePbcPairs( const PbcEdgePairs& pairs1, std::complex blochPhi1, const PbcEdgePairs& pairs2, std::complex blochPhi2, PbcEdgePairs& merged, std::vector>& mergedPhi) { const int nbr2 = static_cast(pairs1.src.size()); const int nbr3 = static_cast(pairs2.src.size()); std::vector allDst; allDst.reserve(nbr2 + nbr3); allDst.insert(allDst.end(), pairs1.dst.begin(), pairs1.dst.end()); allDst.insert(allDst.end(), pairs2.dst.begin(), pairs2.dst.end()); const std::vector ovDstIndex = findDuplicateValues(allDst); const int nbr1 = static_cast(ovDstIndex.size()); merged.src.clear(); merged.dst.clear(); merged.sign.clear(); mergedPhi.clear(); merged.src.reserve(nbr2 + nbr3 - nbr1); merged.dst.reserve(nbr2 + nbr3 - nbr1); merged.sign.reserve(nbr2 + nbr3 - nbr1); mergedPhi.reserve(nbr2 + nbr3 - nbr1); std::vector remove1(nbr2, 0); std::vector remove2(nbr3, 0); for (int ovDst : ovDstIndex) { const int index1 = findMemberIndex(pairs2.dst, ovDst); if (index1 < 0) continue; const int src2 = pairs2.src[index1]; const int index3 = findMemberIndex(pairs1.dst, src2); if (index3 < 0) continue; const int src1 = pairs1.src[index3]; const std::complex sign = pairs2.sign[index1] * pairs1.sign[index3]; merged.src.push_back(src1); merged.dst.push_back(ovDst); merged.sign.push_back(sign); mergedPhi.push_back(blochPhi1 * blochPhi2 * sign); remove1[index3] = 1; remove2[index1] = 1; for (int i = 0; i < nbr2; i++) { if (pairs1.dst[i] == ovDst) remove1[i] = 1; } for (int i = 0; i < nbr3; i++) { if (pairs2.dst[i] == ovDst) remove2[i] = 1; } } for (int i = 0; i < nbr2; i++) { if (remove1[i]) continue; merged.src.push_back(pairs1.src[i]); merged.dst.push_back(pairs1.dst[i]); merged.sign.push_back(pairs1.sign[i]); mergedPhi.push_back(blochPhi1 * pairs1.sign[i]); } for (int i = 0; i < nbr3; i++) { if (remove2[i]) continue; merged.src.push_back(pairs2.src[i]); merged.dst.push_back(pairs2.dst[i]); merged.sign.push_back(pairs2.sign[i]); mergedPhi.push_back(blochPhi2 * pairs2.sign[i]); } } void sortUniquePbcPairs( Eigen::VectorXi& srcEdgeIndex, Eigen::VectorXi& dstEdgeIndex, Eigen::VectorXcd& edgePhi) { if (dstEdgeIndex.size() == 0) return; Eigen::VectorXi tempIndex = Eigen::VectorXi::LinSpaced(dstEdgeIndex.size(), 0, dstEdgeIndex.size() - 1); QuickSort(dstEdgeIndex, tempIndex, 0, static_cast(dstEdgeIndex.size()) - 1); Unique(dstEdgeIndex, tempIndex); Eigen::VectorXcd sortedPhi = Eigen::VectorXcd::Zero(dstEdgeIndex.size()); Eigen::VectorXi sortedSrc = Eigen::VectorXi::Zero(dstEdgeIndex.size()); for (int i = 0; i < dstEdgeIndex.size(); i++) { sortedPhi(i) = edgePhi(tempIndex(i)); sortedSrc(i) = srcEdgeIndex(tempIndex(i)); } edgePhi = sortedPhi; srcEdgeIndex = sortedSrc; } void fillPbcGroupFromPhy( const Phy_WaveOpticsModel* phy, int group, std::vector& srcDomains, std::vector& dstDomains, Eigen::VectorXd& pbcAngles, Eigen::MatrixXd& pbcWaveVecs) { const int nbrPBC = phy->GetNbrPBCInGroup(group); srcDomains.resize(nbrPBC); dstDomains.resize(nbrPBC); pbcAngles = Eigen::VectorXd(nbrPBC); pbcWaveVecs = Eigen::MatrixXd::Zero(3, nbrPBC); for (int i = 0; i < nbrPBC; i++) { phy->GetSrcDomainList(group, i, srcDomains[i]); phy->GetDstDomainList(group, i, dstDomains[i]); pbcAngles(i) = phy->GetPBCAngle(group, i); Eigen::Vector3d waveVec; phy->GetPBCWaveVec(group, i, waveVec); pbcWaveVecs.col(i) = waveVec; } } namespace { void expandOrder2PbcPairs( Mesh_3D* mesh, const PbcEdgePairs& edgePairs, const std::vector>& edgePhi, const PbcEdgePairs& facePairs, const std::vector>& facePhi, Eigen::VectorXi& srcDofIndex, Eigen::VectorXi& dstDofIndex, Eigen::VectorXcd& dofPhi) { // Order-2 tangential PBC = order-1 edge pairing/signs, with each edge enriched // to two DOFs (e, e+nE) sharing the same Bloch×orient φ. // // Face DOFs are intentionally NOT constrained here: // - getBF face modes (BF 13–20) have vanishing tangential trace on their face, // so Et continuity on PBC walls is carried entirely by edge DOFs; // - MATLAB-style identity face expand (f→f, f+nF→f+nF) is wrong on this // translation mesh (Face vertex order usually does not match under +dis) // and actively breaks L/R symmetry (A/B: with faces L/R~0.67, without ~0.98). (void)facePairs; (void)facePhi; const int nE = mesh->GetNbrEdge(); std::vector srcAll, dstAll; std::vector> phiAll; srcAll.reserve(edgePairs.src.size() * 2); dstAll.reserve(edgePairs.dst.size() * 2); phiAll.reserve(srcAll.capacity()); for (size_t i = 0; i < edgePairs.src.size(); i++) { const std::complex phi = edgePhi[i]; srcAll.push_back(Nedelec3D::edgeGlobalDof(edgePairs.src[i], 0, nE)); dstAll.push_back(Nedelec3D::edgeGlobalDof(edgePairs.dst[i], 0, nE)); phiAll.push_back(phi); srcAll.push_back(Nedelec3D::edgeGlobalDof(edgePairs.src[i], 1, nE)); dstAll.push_back(Nedelec3D::edgeGlobalDof(edgePairs.dst[i], 1, nE)); phiAll.push_back(phi); } std::vector filteredSrc, filteredDst; std::vector> filteredPhi; for (size_t i = 0; i < srcAll.size(); i++) { if (srcAll[i] == dstAll[i]) continue; filteredSrc.push_back(srcAll[i]); filteredDst.push_back(dstAll[i]); filteredPhi.push_back(phiAll[i]); } const int n = static_cast(filteredDst.size()); srcDofIndex = Eigen::VectorXi(n); dstDofIndex = Eigen::VectorXi(n); dofPhi = Eigen::VectorXcd(n); for (int i = 0; i < n; i++) { srcDofIndex(i) = filteredSrc[static_cast(i)]; dstDofIndex(i) = filteredDst[static_cast(i)]; dofPhi(i) = filteredPhi[static_cast(i)]; } sortUniquePbcPairs(srcDofIndex, dstDofIndex, dofPhi); } bool collectMergedPbcPairGroups( Mesh_3D* mesh, const Phy_WaveOpticsModel* phy, int elementOrder, PbcEdgePairs& mergedEdges, std::vector>& mergedEdgePhi, PbcEdgePairs& mergedFaces, std::vector>& mergedFacePhi) { const int nbrGroups = phy->GetNbrPBCGroups(); if (nbrGroups <= 0) return false; // Do NOT loosen edge tol for order-2: it wrecked the validated top (z=+5e-6) face. const bool looseEdgeTol = false; std::vector srcDomains; std::vector dstDomains; Eigen::VectorXd pbcAngles; Eigen::MatrixXd pbcWaveVecs; fillPbcGroupFromPhy(phy, 0, srcDomains, dstDomains, pbcAngles, pbcWaveVecs); const PbcEdgePairs edgePairs1 = collectPbcEdgePairs(mesh, srcDomains, dstDomains, pbcAngles, pbcWaveVecs, looseEdgeTol); const PbcEdgePairs facePairs1 = collectPbcFacePairs(mesh, srcDomains, dstDomains, pbcAngles, pbcWaveVecs); if (nbrGroups >= 2) { std::vector srcDomains2; std::vector dstDomains2; Eigen::VectorXd pbcAngles2; Eigen::MatrixXd pbcWaveVecs2; fillPbcGroupFromPhy(phy, 1, srcDomains2, dstDomains2, pbcAngles2, pbcWaveVecs2); const PbcEdgePairs edgePairs2 = collectPbcEdgePairs(mesh, srcDomains2, dstDomains2, pbcAngles2, pbcWaveVecs2, looseEdgeTol); const PbcEdgePairs facePairs2 = collectPbcFacePairs(mesh, srcDomains2, dstDomains2, pbcAngles2, pbcWaveVecs2); const std::complex phi1 = phy->GetPBCPhi(0, 0); const std::complex phi2 = phy->GetPBCPhi(1, 0); mergeDoublePbcPairs(edgePairs1, phi1, edgePairs2, phi2, mergedEdges, mergedEdgePhi); mergeDoublePbcPairs(facePairs1, phi1, facePairs2, phi2, mergedFaces, mergedFacePhi); } else { const std::complex phi1 = phy->GetPBCPhi(0, 0); mergedEdges = edgePairs1; mergedFaces = facePairs1; mergedEdgePhi.resize(edgePairs1.src.size()); mergedFacePhi.resize(facePairs1.src.size()); for (size_t i = 0; i < edgePairs1.src.size(); i++) mergedEdgePhi[i] = phi1 * edgePairs1.sign[i]; for (size_t i = 0; i < facePairs1.src.size(); i++) mergedFacePhi[i] = phi1 * facePairs1.sign[i]; } return mergedEdges.dst.size() > 0 || mergedFaces.dst.size() > 0; } } // namespace bool collectMergedPbcConstraints( Mesh_3D* mesh, const Phy_WaveOpticsModel* phy, int elementOrder, Eigen::VectorXi& srcDofIndex, Eigen::VectorXi& dstDofIndex, Eigen::VectorXcd& dofPhi) { PbcEdgePairs mergedEdges; std::vector> mergedEdgePhi; PbcEdgePairs mergedFaces; std::vector> mergedFacePhi; if (!collectMergedPbcPairGroups(mesh, phy, elementOrder, mergedEdges, mergedEdgePhi, mergedFaces, mergedFacePhi)) return false; if (elementOrder == 2) { expandOrder2PbcPairs(mesh, mergedEdges, mergedEdgePhi, mergedFaces, mergedFacePhi, srcDofIndex, dstDofIndex, dofPhi); return dstDofIndex.size() > 0; } const int n = static_cast(mergedEdges.src.size()); srcDofIndex = Eigen::VectorXi(n); dstDofIndex = Eigen::VectorXi(n); dofPhi = Eigen::VectorXcd(n); for (int i = 0; i < n; i++) { srcDofIndex(i) = mergedEdges.src[static_cast(i)]; dstDofIndex(i) = mergedEdges.dst[static_cast(i)]; dofPhi(i) = mergedEdgePhi[static_cast(i)]; } sortUniquePbcPairs(srcDofIndex, dstDofIndex, dofPhi); return dstDofIndex.size() > 0; } bool assemblePbcProjectionMatrix( Mesh_3D* mesh, const Phy_WaveOpticsModel* phy, int elementOrder, int dof, bool isReal, Eigen::SparseMatrix& P_real, Eigen::SparseMatrix, Eigen::RowMajor>& P_complex) { Eigen::VectorXi srcDofIndex; Eigen::VectorXi dstDofIndex; Eigen::VectorXcd dofPhi; if (!collectMergedPbcConstraints(mesh, phy, elementOrder, srcDofIndex, dstDofIndex, dofPhi)) return false; if (isReal) buildPeriodicProjectionReal(dof, dstDofIndex, srcDofIndex, dofPhi, P_real); else buildPeriodicProjectionComplex(dof, dstDofIndex, srcDofIndex, dofPhi, P_complex); return true; } void buildPeriodicProjectionReal( int dof, const Eigen::VectorXi& dstIndex, const Eigen::VectorXi& srcIndex, const Eigen::VectorXcd& indexPhi, Eigen::SparseMatrix& P) { std::vector> tempP; int num = 0; for (int i = 0; i < dof; i++) { tempP.emplace_back(i, i, 1.0); if (num < dstIndex.size() && i == dstIndex(num)) { tempP.emplace_back(dstIndex(num), srcIndex(num), indexPhi(num).real()); num++; } } std::sort(tempP.begin(), tempP.end(), [](const Eigen::Triplet& a, const Eigen::Triplet& b) { return a.col() < b.col(); }); std::vector> tripleP; num = 0; for (int i = 0; i < dof; i++) { bool deleteCol = false; for (int j = 0; j < dstIndex.size(); j++) { if (i == dstIndex(j)) { deleteCol = true; break; } } if (!deleteCol) { for (const auto& triplet : tempP) { if (triplet.col() == i) tripleP.emplace_back(triplet.row(), triplet.col() - num, triplet.value()); } } else { num++; } } P = Eigen::SparseMatrix(dof, dof - dstIndex.size()); P.setFromTriplets(tripleP.begin(), tripleP.end()); } void buildPeriodicProjectionComplex( int dof, const Eigen::VectorXi& dstIndex, const Eigen::VectorXi& srcIndex, const Eigen::VectorXcd& indexPhi, Eigen::SparseMatrix, Eigen::RowMajor>& P) { std::vector>> tempP; int num = 0; for (int i = 0; i < dof; i++) { tempP.emplace_back(i, i, 1.0); if (num < dstIndex.size() && i == dstIndex(num)) { tempP.emplace_back(dstIndex(num), srcIndex(num), indexPhi(num)); num++; } } std::sort(tempP.begin(), tempP.end(), [](const Eigen::Triplet>& a, const Eigen::Triplet>& b) { return a.col() < b.col(); }); std::vector>> tripleP; num = 0; for (int i = 0; i < dof; i++) { bool deleteCol = false; for (int j = 0; j < dstIndex.size(); j++) { if (i == dstIndex(j)) { deleteCol = true; break; } } if (!deleteCol) { for (const auto& triplet : tempP) { if (triplet.col() == i) tripleP.emplace_back(triplet.row(), triplet.col() - num, triplet.value()); } } else { num++; } } P = Eigen::SparseMatrix, Eigen::RowMajor>(dof, dof - dstIndex.size()); P.setFromTriplets(tripleP.begin(), tripleP.end()); }