You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add a Schur-complement (non-overlapping substructuring) domain-decomposition solver to the frequency-domain backend so we can run larger FD problems without the direct-solver memory wall, with a path to parallel scaling. The goal is purely solver scalability for a single monolithic FD solve, not coupling separate models (that is the substructuring-for-co-simulation track, see "Related" below).
Today the FD backend is purely direct: assemble_and_solve builds one global complex-symmetric system and factorizes it with PARDISO (mtype=6, METIS-ordered, upper-CSR) or faer LU via the SparseSolver trait. The fill-in of that single global factorization is the hard memory/time wall for large RFIC-scale meshes. Schur DD never factorizes the full matrix: it factorizes each subdomain's interior block independently and reduces the problem to a much smaller interface ("skeleton") system. Peak memory is then bounded by the largest subdomain factor plus the interface system, not by the whole.
Iterative solver: AMS-preconditioned Krylov for H(curl) systems #2 (AMS-preconditioned Krylov) is the iterative route: best asymptotic O(N) memory, but FD curl-curl is indefinite with a large gradient null-space, and high-frequency/indefinite Helmholtz convergence is genuinely hard. High ceiling, high risk.
Schur DD is the direct/hybrid route: each subdomain is solved by a direct factorization, so there is no global convergence fragility; only the (much smaller) interface problem is solved directly or iteratively. This is the approach production FD-FEM tools (e.g. HFSS DDM) use to fit large problems into bounded memory, and it is robust at high frequency because the local solves are direct. The two are complementary: AMS could later precondition the interface or the interior solves.
Math sketch (complex-symmetric, mtype=6)
Partition the free DOFs into per-subdomain interior sets I_s and the shared interface set Γ. With interior DOFs ordered first, the reduced system is
Assemble S = Σ_s S_s, g = Σ_s g_s, solve the interface system S x_Γ = g, then back-substitute per subdomain: x_I^(s) = (K_II^(s))^{-1} (b_I^(s) − K_IΓ^(s) x_Γ).
S is complex-symmetric (inherits mtype=6), so the same solver family applies.
Multi-RHS maps cleanly onto the existing "factor once, solve many RHS" port loop: b_I, b_Γ become matrices with one column per driven port; interior back-solves and the interface solve are multi-column.
Memory: each (K_II^(s))^{-1} is a small factor; the global factor is never formed. The remaining cost is S (dense N_Γ × N_Γ if formed explicitly) — see phasing.
Where it plugs into the code (grounded)
Solver seam: crates/rapidfem-fd/src/solver/mod.rs already defines SparseSolver (factorize/refactorize/solve) with PARDISO/faer/Accelerate backends and SolverChoice::from_env(). Schur DD is a layer that holds one SparseSolver per subdomain interior block plus a solver for the interface — it reuses, not replaces, the existing backends.
Assembly: crates/rapidfem-fd/src/assembly.rsassemble_and_solve_with_pml builds COO (rows, cols, data_e, data_b) via assemble_global_matrices, forms K = E − k0²·B + Robin, eliminates PEC DOFs into free_dofs / dof_to_free, then factorizes + solves per-port RHS (port_vectors). The DD path forks here: element-filtered assembly per subdomain into the I/Γ blocks instead of one global COO.
DOF model: crates/rapidfem-fd/src/basis.rsNedelec2Basis — n_field = 2·n_edges + 2·n_tris, edge_to_field (2 DOFs/edge), tri_to_field (8 DOFs/tri). DOFs live on edges and faces, which determines interface-DOF identification.
Mesh adjacency for partitioning + interface detection: crates/rapidfem-core/src/mesh.rs — tri_to_tet (face→ up to 2 tets), tet_to_tri, tet_to_edge, vtag_to_tet. A tet dual graph (tets joined across shared faces via tri_to_tet) feeds the partitioner; a DOF is an interface DOF iff its supporting edge/face is touched by tets in ≥2 subdomains.
Partition tets into k subdomains; classify DOFs into I_s / Γ.
Element-filtered assembly of the four blocks per subdomain.
Factorize each K_II^(s) (reuse SparseSolver); form S and g explicitly; solve S x_Γ = g (dense or sparse direct); back-substitute.
Gate: bit-for-bit agreement with the monolithic solve on WR-90 + coax (same S-parameters to solver precision). This is the non-negotiable correctness check.
Phase 1 — integration + memory relief.
Expose as SolverChoice::Schur (RAPIDFEM_SOLVER=schur) and/or ProblemFD(..., n_subdomains=k).
Instrument peak RAM + factorization time; produce the N vs peak-RAM curve vs monolithic on a mesh-refinement series (report-harness plot).
Target: clearly lower peak memory than monolithic at equal accuracy for k ≥ 2.
Phase 2 — scalable interface solve.
Replace explicit S with a matrix-free interface GMRES (matvec S·v = Σ_s [K_ΓΓ^(s) v − K_ΓI^(s)(K_II^(s))^{-1}(K_IΓ^(s) v)] via cached local factorizations) so the dense N_Γ × N_Γ is never stored — this is the real scaling lever for many subdomains/large interfaces.
Add an interface preconditioner (lumped / Neumann-Neumann, later a coarse space for subdomain-count-independent convergence).
Phase 3 — parallelism + robustness.
Parallelize subdomain factorizations and back-solves with rayon (already a dep behind the parallel feature).
Later: graph partitioning (METIS-style multilevel) on the tet dual graph for better edge-cut / interface-size minimization. Evaluate a Rust METIS binding vs a native multilevel partitioner.
Key risk: interior resonance
A non-overlapping interior block K_II^(s) can be singular or ill-conditioned at the solve frequency if the subdomain has a spurious interior resonance (a known DD pitfall for indefinite Maxwell). Mitigations to evaluate: a small complex frequency shift on interior blocks, or Robin/impedance transmission conditions on interior interfaces (which is exactly the optimized-Schwarz variant and reuses the existing port-Robin assembly). Phase 0/1 should detect near-singular K_II^(s) (PARDISO already uses pivot perturbation, iparm[9]=13) and flag it.
Validation plan
Correctness: S-parameters identical to monolithic (to solver precision) on WR-90, coax, and at least one multi-mode / reflective case; reciprocity and passivity gates from the existing FD suite must still pass.
Memory/scaling: peak-RAM and wall-time vs N and vs k (subdomain count), monolithic vs Schur, plotted in the report harness. Local testing only (never CI — FEM solves do not belong in runners).
Scope / non-goals
In scope: a memory-bounded, eventually-parallel solver for one FD problem; conforming interfaces (already produced by g.fragment() within one gmsh model).
Out of scope here: non-conforming/mortar interfaces; coupling separately authored meshes; ROM-level substructuring for transient co-simulation (shares the subdomain machinery but is tracked separately).
The same subdomain-factorization + interface machinery is the foundation for substructuring multiport ROMs (passive blocks reduced and connected at ports for transient co-simulation with embedded transistors).
Summary
Add a Schur-complement (non-overlapping substructuring) domain-decomposition solver to the frequency-domain backend so we can run larger FD problems without the direct-solver memory wall, with a path to parallel scaling. The goal is purely solver scalability for a single monolithic FD solve, not coupling separate models (that is the substructuring-for-co-simulation track, see "Related" below).
Today the FD backend is purely direct:
assemble_and_solvebuilds one global complex-symmetric system and factorizes it with PARDISO (mtype=6, METIS-ordered, upper-CSR) or faer LU via theSparseSolvertrait. The fill-in of that single global factorization is the hard memory/time wall for large RFIC-scale meshes. Schur DD never factorizes the full matrix: it factorizes each subdomain's interior block independently and reduces the problem to a much smaller interface ("skeleton") system. Peak memory is then bounded by the largest subdomain factor plus the interface system, not by the whole.Why Schur DD (and how it relates to #2)
Math sketch (complex-symmetric,
mtype=6)Partition the free DOFs into per-subdomain interior sets
I_sand the shared interface setΓ. With interior DOFs ordered first, the reduced system isK_IIis block-diagonal across subdomains (no interior-interior coupling between subdomains). Per subdomains:Assemble
S = Σ_s S_s,g = Σ_s g_s, solve the interface systemS x_Γ = g, then back-substitute per subdomain:x_I^(s) = (K_II^(s))^{-1} (b_I^(s) − K_IΓ^(s) x_Γ).Sis complex-symmetric (inheritsmtype=6), so the same solver family applies.b_I,b_Γbecome matrices with one column per driven port; interior back-solves and the interface solve are multi-column.(K_II^(s))^{-1}is a small factor; the global factor is never formed. The remaining cost isS(denseN_Γ × N_Γif formed explicitly) — see phasing.Where it plugs into the code (grounded)
crates/rapidfem-fd/src/solver/mod.rsalready definesSparseSolver(factorize/refactorize/solve) with PARDISO/faer/Accelerate backends andSolverChoice::from_env(). Schur DD is a layer that holds oneSparseSolverper subdomain interior block plus a solver for the interface — it reuses, not replaces, the existing backends.crates/rapidfem-fd/src/assembly.rsassemble_and_solve_with_pmlbuilds COO(rows, cols, data_e, data_b)viaassemble_global_matrices, formsK = E − k0²·B + Robin, eliminates PEC DOFs intofree_dofs/dof_to_free, then factorizes + solves per-port RHS (port_vectors). The DD path forks here: element-filtered assembly per subdomain into the I/Γ blocks instead of one global COO.crates/rapidfem-fd/src/basis.rsNedelec2Basis—n_field = 2·n_edges + 2·n_tris,edge_to_field(2 DOFs/edge),tri_to_field(8 DOFs/tri). DOFs live on edges and faces, which determines interface-DOF identification.crates/rapidfem-core/src/mesh.rs—tri_to_tet(face→ up to 2 tets),tet_to_tri,tet_to_edge,vtag_to_tet. A tet dual graph (tets joined across shared faces viatri_to_tet) feeds the partitioner; a DOF is an interface DOF iff its supporting edge/face is touched by tets in ≥2 subdomains.Proposed design and phasing
Phase 0 — correctness spike (single process, explicit S).
ksubdomains; classify DOFs intoI_s/Γ.K_II^(s)(reuseSparseSolver); formSandgexplicitly; solveS x_Γ = g(dense or sparse direct); back-substitute.Phase 1 — integration + memory relief.
SolverChoice::Schur(RAPIDFEM_SOLVER=schur) and/orProblemFD(..., n_subdomains=k).N vs peak-RAMcurve vs monolithic on a mesh-refinement series (report-harness plot).k≥ 2.Phase 2 — scalable interface solve.
Swith a matrix-free interface GMRES (matvecS·v = Σ_s [K_ΓΓ^(s) v − K_ΓI^(s)(K_II^(s))^{-1}(K_IΓ^(s) v)]via cached local factorizations) so the denseN_Γ × N_Γis never stored — this is the real scaling lever for many subdomains/large interfaces.Phase 3 — parallelism + robustness.
rayon(already a dep behind theparallelfeature).Partitioner
Key risk: interior resonance
A non-overlapping interior block
K_II^(s)can be singular or ill-conditioned at the solve frequency if the subdomain has a spurious interior resonance (a known DD pitfall for indefinite Maxwell). Mitigations to evaluate: a small complex frequency shift on interior blocks, or Robin/impedance transmission conditions on interior interfaces (which is exactly the optimized-Schwarz variant and reuses the existing port-Robin assembly). Phase 0/1 should detect near-singularK_II^(s)(PARDISO already uses pivot perturbation,iparm[9]=13) and flag it.Validation plan
Nand vsk(subdomain count), monolithic vs Schur, plotted in the report harness. Local testing only (never CI — FEM solves do not belong in runners).Scope / non-goals
g.fragment()within one gmsh model).Related
References