Skip to content

feat: PML, facet-bilinear & port boundary operators (+ examples) - #39

Merged
Shizheng-Wen merged 11 commits into
camlab-ethz:mainfrom
NicolasCayuela:wave-pml-port-api
Jul 22, 2026
Merged

feat: PML, facet-bilinear & port boundary operators (+ examples)#39
Shizheng-Wen merged 11 commits into
camlab-ethz:mainfrom
NicolasCayuela:wave-pml-port-api

Conversation

@NicolasCayuela

@NicolasCayuela NicolasCayuela commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

feat: open-domain wave FEM — PML, facet-bilinear & port boundary operators (+ examples)

Summary

Frequency-domain wave problems (acoustics, 2D optics) need two things the
library didn't have yet: a PML, and a way to
assemble boundary bilinear forms (Robin / impedance / plane-wave ports).
This PR adds both as small, composable pieces on top of the existing
complex-valued assembly, plus two worked examples.

Everything is scalar-Helmholtz, reuses ElementAssembler / SparseMatrix, and
adds no heavy dependencies.

What's added

Assemblers (tensormesh.assemble.builtin, …facet_bilinear)

  • AnisotropicLaplaceElementAssembler — tensor-coefficient stiffness
    $\int (\mathbf A,\nabla u)\cdot\nabla v$, complex A supported.
  • ScaledMassElementAssembler — coefficient mass $\int c,N_i N_j$.
  • FacetBilinearAssembler — boundary bilinear form
    $\int_\Gamma c,N_i N_j,\mathrm dS$ scattered to a SparseMatrix
    (the bilinear counterpart of FacetAssembler, which only does linear forms).

Helpers (tensormesh.pml, tensormesh.bc)

  • cartesian_pml — stretched-coordinate PML tensor $\mathbf\Lambda$ + mass
    scaling $s_\text{prod}$ for a rectangular frame.
  • robin_operator / port_source — first-order absorbing / plane-wave-port
    boundary matrix and incident load.

Examples using the new operators (examples/wave/)

  • helmholtz_resonator/ — port-driven 2D acoustic resonator (uses the port
    operators).
  • optical_ring_resonator/ — waveguide-coupled Si microdisk with a PML frame
    (2D TM, whispering-gallery mode); now ships a setup figure showing the
    materials, the line-current launch and the PML boundary.

Additional modal-analysis examples

These reuse the existing builtin assemblers (Laplace / Mass / ScaledMass /
LinearElasticity) with a SciPy generalized-eigenvalue solve — they do not use
the new PML / port operators, but round out the wave / vibration example set and
each validates against a closed-form reference.

  • examples/solid/beam_vibration_analysis/ — free-vibration eigenmodes of a
    clamped-free steel cylinder (3D elasticity, consistent vector mass); 3D
    deformed mode-shape gallery, checked vs the Euler-Bernoulli bending
    frequencies.
  • examples/wave/wave_guide_mode/ — scalar mode analysis of a rectangular
    dielectric waveguide, $(K - k_0^2 M_\varepsilon)E = -\beta^2 M E$ built from
    Laplace + Mass + ScaledMassElementAssembler; guided $E_{pq}$ field gallery,
    checked vs the separable Marcatili slab approximation.

Validation

Each piece is checked against an analytic or known-good reference:

Piece Check Result
AnisotropicLaplace (A = I) vs LaplaceElementAssembler max diff 0.0
FacetBilinear boundary mass $\int_\Gamma 1,\mathrm dS$ = edge length / perimeter exact
cartesian_pml point source vs 2D Hankel Green function corr 0.994
robin_operator / port_source plane-wave port vs $e^{-ikx}$ corr 0.98
Helmholtz example fundamental resonance 357 Hz, cavity gain ~12x
Optical example on-resonance disk field clean rim WGM
Beam vibration example 1st bending vs Euler-Bernoulli 37.2 vs 36.2 Hz (P1-tet, ~+3%)
Waveguide example $E_{pq}$ effective indices vs Marcatili agree to ~$10^{-4}$

Unit tests live in tests/assemble/test_wave.py (13 tests, all passing) and
assert the assembly-level identities above: A = I ⇒ Laplace, c = 1 ⇒ Mass,
boundary mass = perimeter / edge length, the PML stretch tensor, and the Robin /
port boundary mass and load (real and complex coefficients).

Helmholtz resonator

Port-driven duct with a necked side cavity; input impedance over a sweep and the
pressure field at resonance.

boundary_conditions acoustic_field impedance

Optical ring resonator (waveguide-coupled microdisk)

optical_ring_resonator_setup optical_ring_resonator

On resonance the disk lights up in a whispering-gallery mode (bright rim, the
waveguide depleted); the PML frame absorbs the radiated field.

Beam vibration (cantilever cylinder)

Free-vibration mode shapes: bending pairs (circular section ⇒ two planes), first
torsion and first axial mode.

vibration_cylinder_modes vibration_cylinder_frf

Rectangular waveguide modes

The guided $E_{pq}$ transverse fields of a rectangular dielectric core.

waveguide_modes

Notes

  • The boundary matrix from robin_operator has a boundary-only sparsity pattern,
    so it's folded onto the volume layout before adding (SparseMatrix.__add__
    needs matching layouts). A native cross-layout add would remove the fold — happy
    to do it as a follow-up.
  • The port BC is first-order absorbing (some reflection at grazing incidence);
    the PML is the higher-quality absorber for open domains.
  • Example figures are git-ignored (examples/**/*.png); they regenerate by
    running each script.
  • Follow-ups: rewrite the optical example on top of cartesian_pml +
    AnisotropicLaplace (currently an inline per-quadrature assembler for sharper
    interfaces); 3D PML.

Checklist

  • Conventional Commits, DCO Signed-off-by
  • NumPy-style docstrings on public API
  • No numpy in forward(); complex-safe
  • Unit tests under tests/tests/assemble/test_wave.py, 13 tests, all passing

@NicolasCayuela NicolasCayuela changed the title feat: PML, facet-bilinear & port boundary operators (+ 2 examples, need to create folders later, for example, for acoustics and electromagnetism) feat: PML, facet-bilinear & port boundary operators (+ examples) Jul 4, 2026
NicolasCayuela and others added 11 commits July 22, 2026 17:12
… BC)

Add the boundary and open-domain building blocks scalar-Helmholtz wave
problems (acoustics, 2D optics) need but the library lacked. Each piece
is validated against an analytic or known-good reference.

- AnisotropicLaplaceElementAssembler: tensor-coefficient stiffness
  int (A grad u).grad v with complex A (A = I reproduces Laplace).
- ScaledMassElementAssembler: coefficient mass int c N_i N_j.
- FacetBilinearAssembler: boundary bilinear form int_Gamma c N_i N_j dS
  scattered to a SparseMatrix (Robin / impedance / port matrices).
- pml.cartesian_pml: stretched-coordinate PML tensor + mass scaling;
  matches the 2D Hankel Green function to corr 0.994.
- bc.robin_operator / bc.port_source: first-order absorbing / plane-wave
  -port boundary matrix and load (traveling-wave corr 0.98).

Signed-off-by: NicolasCayuela <241774098+NicolasCayuela@users.noreply.github.com>
A 2D acoustic Helmholtz resonator (duct + neck + side cavity) driven by
a plane-wave port, solved on TensorMesh with the new boundary operators.

- port line mass and incident load via robin_operator / port_source,
  folded onto the volume layout for a single complex SparseMatrix
- input impedance Z(f) over a frequency sweep, plus the pressure field
  at the 357 Hz resonance (cavity gain ~12x)
- boundary-condition schematic figure

Signed-off-by: NicolasCayuela <241774098+NicolasCayuela@users.noreply.github.com>
A 2D TM (E_z) silicon bus waveguide evanescently coupled to a silicon
microdisk in silica, with a stretched-coordinate PML frame, on TensorMesh.

- inline anisotropic-Helmholtz + PML assembler evaluating eps and the
  stretch per quadrature point for sharp Si/SiO2 interfaces; the native
  pml.cartesian_pml path is documented as the reusable equivalent
- driven at the disk whispering-gallery resonance -> clean rim mode
- Re(E_z) and |E_z| field figure

Signed-off-by: NicolasCayuela <241774098+NicolasCayuela@users.noreply.github.com>
The Brillouin-zone paths and gradient dot-products used the LaTeX
negative-thin-space "\!", which GitHub's math renderer prints literally
as stray "!" ("M! - !\Gamma! - !X! - !M"). Drop it; write the k-paths
with arrows and switch "\rm" to "\mathrm".

Signed-off-by: NicolasCayuela <241774098+NicolasCayuela@users.noreply.github.com>
Document the two new wave examples in the style of the phononic-crystal
README (model, workflow block, run, what the figure shows):

- helmholtz_resonator: port-driven acoustic resonator; the port operators
  come from robin_operator / port_source.
- optical_ring_resonator: waveguide-coupled microdisk with a PML frame;
  notes the cartesian_pml library path and the P1 WGM-resonance shift.

Signed-off-by: NicolasCayuela <241774098+NicolasCayuela@users.noreply.github.com>
GitHub's KaTeX renderer rejects \operatorname ("macros are not allowed").
Write the PML stretch tensor with \mathrm{diag} instead.

Signed-off-by: NicolasCayuela <241774098+NicolasCayuela@users.noreply.github.com>
13 tests covering the new API against exact references:
- AnisotropicLaplace: A=I equals Laplace, complex-A scaling, symmetry
- ScaledMass: c=1 equals Mass, constant-c scaling
- FacetBilinear: boundary mass = perimeter / edge length / coefficient
- cartesian_pml: interior stretch = I, PML diag = (sy/sx, sx/sy), s_prod
- robin_operator / port_source: boundary mass and load = perimeter, complex

Signed-off-by: NicolasCayuela <241774098+NicolasCayuela@users.noreply.github.com>
Add a `plot_setup` function to the waveguide-microdisk coupler
example that draws the physical model before the field solve, so a
reader can see the problem definition at a glance.

- render materials (Si core waveguide + microdisk, SiO2 cladding)
  with a Wong colorblind-safe palette
- draw the PML frame as a grey border with a legend entry
- mark the line-current launch on the bus waveguide
- wire the figure into `run_demo` (saved before the solve) and add a
  `--setup-output` CLI flag

Example-only; no public API is added.

Signed-off-by: NicolasCayuela <241774098+NicolasCayuela@users.noreply.github.com>
Two standalone modal-analysis examples exercising the generalized
eigenproblem on assembler-built sparse matrices, each validated
against a closed-form reference (no external tools required).

- examples/solid/beam_vibration_analysis: free-vibration eigenmodes of
  a clamped-free steel cylinder. LinearElasticity stiffness + consistent
  vector mass (scalar mass lifted to the 3 components), scipy eigsh,
  3D deformed mode-shape gallery. Sanity-checked vs the Euler-Bernoulli
  cantilever bending frequencies.
- examples/wave/wave_guide_mode: scalar mode analysis of a rectangular
  dielectric waveguide cross-section. Laplace + Mass + ScaledMass build
  the transverse Helmholtz eigenproblem (K - k0^2 M_eps) E = -beta^2 M E;
  guided E_pq modes plotted as field maps. Compared to the separable
  Marcatili slab approximation.

Both are example-only; no public API is added.

Signed-off-by: NicolasCayuela <241774098+NicolasCayuela@users.noreply.github.com>
Replace the isotropic line-current drive of the waveguide-microdisk
coupler with a modal soft source, so the bus mode is launched cleanly
toward the disk rather than radiating both ways from a current blob.

- add slab_mode(): analytic fundamental even E_z slab mode, effective
  index from the even-mode dispersion kx*tan(kx*w/2) = gamma
- add launch_field(): imprint the transverse profile on a thin plane
  at the PML/cladding interface, phased as a two-plane directional
  launch (-i, a quarter guided-wavelength ahead) so power travels +y
- drop the source-disk geometry and src_r; the launch plane is src_y
  (= pml), matching an inlet at the absorbing frame
- update plot_setup (launch line + arrow), docstrings, README

Coupling into the whispering-gallery mode improves markedly
(mean |E_z| disk/whole ~3.2); example run verified end to end.

Signed-off-by: NicolasCayuela <241774098+NicolasCayuela@users.noreply.github.com>
…mespaces

Maintainer touch-up on top of the rebase: tensormesh/bc.py becomes
tensormesh/operator/boundary.py (robin_operator / port_source live
with the other boundary operators - Condenser, BlochReducer) and
tensormesh/pml.py becomes tensormesh/functional/pml.py (a coefficient-
field builder, like the rest of tensormesh.functional). Top-level
re-exports are unchanged: tensormesh.robin_operator / port_source /
cartesian_pml keep working exactly as in the original PR.

Also derive robin_operator's scalar-coefficient dtype from the mesh
points (float64 mesh -> complex128/float64 coefficient) instead of
torch's float32 default.

Signed-off-by: Shizheng-Wen <wenshizheng0710@gmail.com>
@Shizheng-Wen

Copy link
Copy Markdown
Collaborator

Thanks Nicolas — this is exactly the missing piece for open-domain wave problems, and the validation table is a pleasure to review. Landing it ahead of the next release.

Maintainer housekeeping I pushed to your branch (hope you don't mind):

  1. Rebased onto current main — the mixed-assembly work (feat(assemble): mixed multi-field block assembly + generalized order pairs #38/feat: mixed multi-field assembly, Taylor-Hood fluid examples, and the matching docs/i18n sync #46) had moved the import blocks in tensormesh/__init__.py etc.; one trivial conflict resolved, your ten commits are otherwise untouched.
  2. Namespace homes (top-level API unchanged — tensormesh.robin_operator / port_source / cartesian_pml work exactly as in your examples): tensormesh/bc.pytensormesh/operator/boundary.py (with the other boundary operators — Condenser, BlochReducer), and tensormesh/pml.pytensormesh/functional/pml.py (a coefficient-field builder, like the rest of functional).
  3. Small dtype fix: robin_operator now derives a scalar coefficient's dtype from the mesh points (float64 mesh → complex128/float64) instead of torch's float32 default.

Docs (API entries + gallery pages for the four examples) will follow in a separate PR on our side. Follow-ups we'd welcome anytime: the cross-layout SparseMatrix.__add__ you noted, an autograd check through robin_operator/PML (the metamaterial-TopOpt adjoint path), and porting transmission_slab.py onto the new public operators.

@Shizheng-Wen
Shizheng-Wen merged commit c7fa46d into camlab-ethz:main Jul 22, 2026
4 checks passed
Shizheng-Wen added a commit that referenced this pull request Jul 22, 2026
#48)

Documentation for PR #39's API surface and examples:

- New gallery chapter open_domain_wave.rst - the port-driven Helmholtz
  resonator (357 Hz fundamental, 12x cavity gain) and the PML-framed
  optical microdisk (whispering-gallery mode), with freshly generated
  figures and the four-line robin_operator / port_source recipe.
- New gallery chapter modal_analysis.rst - rectangular-waveguide modes
  (Marcatili agreement ~1e-4) and cantilever-cylinder vibration
  (Euler-Bernoulli +3%), both generalized-eigenproblem workflows.
- API reference: FacetBilinearAssembler + AnisotropicLaplace /
  ScaledMass builtins (assemble), robin_operator / port_source
  (operator), functional.pml section.
- forms builtin table + boundary_conditions: Robin/port conditions are
  now first-class, the 'not built in' bullet retires.
- README example/architecture rows; ROADMAP item 3 marks the PML half
  shipped (metamaterial TopOpt remains, with an autograd-through-
  boundary-ops prerequisite noted); example READMEs' source links
  follow the operator/functional namespace move; a handful of
  docstring cross-reference fixes surfaced by the API build.
- zh_CN catalogs synced: 67 new entries translated and reviewed.

Both builds clean; ZH adds zero warnings over the EN baseline.

Signed-off-by: Shizheng-Wen <wenshizheng0710@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants