From 40efe7a45027aea415e42d85de3bd79222739038 Mon Sep 17 00:00:00 2001 From: Forgejo Actions Date: Sun, 19 Apr 2026 16:15:06 -0700 Subject: [PATCH] [docs] clarify FDFD-to-FDTD field reconstruction --- README.md | 14 ++++++++++++++ docs/index.md | 9 +++++++++ examples/waveguide_real.py | 11 +++++++---- meanas/fdtd/__init__.py | 16 ++++++++++++++++ meanas/test/test_waveguide_fdtd_fdfd.py | 6 +++++- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be410e3..ce9bcc1 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,10 @@ For a broadband or continuous-wave FDTD run: part of the simulation, and compare the extracted phasor to the FDFD field or residual. +This is the primary FDTD/FDFD equivalence workflow. The phasor extraction step +filters the time-domain run down to the guided `+\omega` content that FDFD +solves for directly, so it is the cleanest apples-to-apples comparison. + ### Real-field reconstruction workflow For a continuous-wave real-valued FDTD run: @@ -211,4 +215,14 @@ For a continuous-wave real-valued FDTD run: pieces to see whether the remaining mismatch is actually in the mode or in weak nonguided tails. +This is a stricter diagnostic, not the primary equivalence benchmark. A raw +monitor slice contains both the guided field and the remaining orthogonal +content on that plane, + +$$ E_{\text{monitor}} = E_{\text{guided}} + E_{\text{residual}} , $$ + +so its full-plane instantaneous error is naturally noisier than the extracted +phasor comparison even when the underlying guided `+\omega` content matches +well. + `examples/waveguide_real.py` is the reference implementation of this workflow. diff --git a/docs/index.md b/docs/index.md index cc86992..bc4cdeb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,6 +26,15 @@ Relevant starting examples: mode-weighted, and guided-mode / residual comparisons - `examples/fdfd.py` for direct frequency-domain waveguide excitation +For solver equivalence, prefer the phasor-based examples first. They compare +the extracted `+\omega` content of the FDTD run directly against the FDFD +solution and are the main accuracy benchmarks in the test suite. + +`examples/waveguide_real.py` answers a different, stricter question: how well a +late raw real snapshot matches `Re(E_\omega e^{i\omega t})` on a monitor plane. +That diagnostic is useful, but it also includes orthogonal residual structure +that the phasor comparison intentionally filters out. + The API pages are better read as a toolbox map and derivation reference: - Use the [FDTD API](api/fdtd.md) for time-domain stepping, CPML, phasor diff --git a/examples/waveguide_real.py b/examples/waveguide_real.py index 99474c0..03a20ac 100644 --- a/examples/waveguide_real.py +++ b/examples/waveguide_real.py @@ -9,9 +9,12 @@ FDFD" workflow: 3. solve the matching FDFD problem from the analytic source phasor, and 4. compare late real monitor slices against `fdtd.reconstruct_real_e/h(...)`. -Unlike the complex-source examples, this script does not use phasor extraction -as the main output. The comparison target is the real field itself, with both -full-plane and mode-weighted monitor errors reported. +Unlike the phasor-based examples, this script does not use extracted phasors as +the main output. It is a stricter diagnostic: the comparison target is the raw +real field itself, with full-plane, mode-weighted, guided-mode, and orthogonal- +residual errors reported. Strong phasor agreement can coexist with visibly +larger raw-snapshot error because the latter includes weak nonguided tails on +the monitor plane. """ import numpy @@ -104,7 +107,7 @@ def main() -> None: epsilon=epsilon, ) # A small global phase aligns the real-valued source with the late-cycle - # monitor comparison. The underlying phasor problem is unchanged. + # raw-snapshot diagnostic. The underlying phasor problem is unchanged. j_mode *= numpy.exp(1j * SOURCE_PHASE) monitor_mode = waveguide_3d.solve_mode( 0, diff --git a/meanas/fdtd/__init__.py b/meanas/fdtd/__init__.py index 1b275e8..6291815 100644 --- a/meanas/fdtd/__init__.py +++ b/meanas/fdtd/__init__.py @@ -165,6 +165,11 @@ with caller-provided sample times and weights. In this codebase the matching electric-current convention is typically `E -= dt * J / epsilon` in FDTD and `-i \omega J` on the right-hand side of the FDFD wave equation. +For FDTD/FDFD equivalence, this phasor path is the primary comparison workflow. +It isolates the guided `+\omega` response that the frequency-domain solver +targets directly, regardless of whether the underlying FDTD run used real- or +complex-valued fields. + For exact pulsed FDTD/FDFD equivalence it is often simplest to keep the injected source, fields, and CPML auxiliary state complex-valued. The `real_injection_scale(...)` helper is instead for the more ordinary one-run @@ -172,6 +177,17 @@ real-valued source path, where the intended positive-frequency waveform is injected through `numpy.real(scale * waveform)` and any remaining negative- frequency leakage is controlled by the pulse bandwidth and run length. +`reconstruct_real(...)` is for a different question: given a phasor, what late +real-valued field snapshot should it produce? That raw-snapshot comparison is +stricter and noisier because a monitor plane generally contains both the guided +field and the remaining orthogonal content, + +$$ E_{\text{monitor}} = E_{\text{guided}} + E_{\text{residual}} . $$ + +Phasor/modal comparisons mostly validate the guided `+\omega` term. Raw +real-field comparisons expose both terms at once, so they should be treated as +secondary diagnostics rather than the main solver-equivalence benchmark. + The Ricker wavelet (normalized second derivative of a Gaussian) is commonly used for the pulse shape. It can be written diff --git a/meanas/test/test_waveguide_fdtd_fdfd.py b/meanas/test/test_waveguide_fdtd_fdfd.py index 6370360..167b91e 100644 --- a/meanas/test/test_waveguide_fdtd_fdfd.py +++ b/meanas/test/test_waveguide_fdtd_fdfd.py @@ -711,7 +711,11 @@ def test_straight_waveguide_fdtd_fdfd_overlap_and_flux_agree() -> None: assert result.overlap_phase_deg < 0.5 -def test_straight_waveguide_real_monitor_fields_match_reconstructed_real_fields() -> None: +def test_straight_waveguide_real_snapshot_diagnostic_tracks_guided_content_and_bounded_residual() -> None: + # The phasor-based waveguide tests above are the primary FDTD/FDFD + # equivalence benchmark. This raw real-field check is intentionally stricter: + # it validates that late monitor snapshots keep the guided content close to + # the reconstructed FDFD field while the orthogonal residual stays bounded. result = _run_real_field_straight_waveguide_case() ranked_snapshots = sorted( result.snapshots,