Skip to content

bug fix: spatially varying plane sources and PMC walls under mirror symme… - #430

Closed
bruxillensis wants to merge 1 commit into
ymahlau:mainfrom
bruxillensis:fix-425-symmetry-source
Closed

bug fix: spatially varying plane sources and PMC walls under mirror symme…#430
bruxillensis wants to merge 1 commit into
ymahlau:mainfrom
bruxillensis:fix-425-symmetry-source

Conversation

@bruxillensis

@bruxillensis bruxillensis commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Fix for #425. I hope its alright I tackled this @ymahlau, I felt responsible :).

Four independent bugs made a symmetry-reduced simulation wrong for any source with spatial variation along a symmetry axis. All were invisible to the existing symmetry tests, which only use UniformPlaneSource with normalize_by_energy=False and direction="+" -- a combination blind to every one of them.

  1. Profile center re-centered on the reduced quadrant. TFSFPlaneSource._get_center derived the transverse center from the object's clipped extent, so a Gaussian spot moved to the middle of the kept half instead of staying on the symmetry plane. The reduction discarded the pre-clip extent, so the source could not know it had been cut; reduce_resolved_slices now reports the per-axis low-side clip and place_objects records it as SimulationObject._symmetry_clip_low.

  2. Normalization summed over the clipped plane. normalize_by_energy and the Gaussian profile's own sum normalization both divided by a quarter-plane sum, inflating the injected amplitude by 2**(n_axes/2) -- exactly 2x for two symmetric axes.

  3. Profile mirrored about center along the vertical axis. v_basis = cross(wave_vector, u_basis) has a sign that depends on the propagation axis and direction, so the sampled profile was reflected for e.g. direction="-" along z (and, oppositely, direction="+" along y). Predates the symmetry feature by ~14 months and is invisible for any profile symmetric about its own center; it also blocked fix 1, since a correctly off-center profile got flipped out of the array.

  4. The PMC symmetry wall over-constrained the field. Along a symmetry axis the six components split by Yee offset: tangential E and normal H sit at offset 0, normal E and tangential H at offset 1/2. The mirror plane lands on the reduced domain's low cell edge, so the tangential-H node a magnetic mirror must zero is at local -1 -- outside the array, where zero-padding already supplies it. Inserting a PerfectMagneticConductor instead zeroed tangential H at local 0, a full cell inside the domain. PEC still needs its wall (tangential E has no node on the plane). make_symmetry_walls now builds walls only for electric (-1) planes.

Validation. A symmetry-reduced run is compared cell-for-cell against the half of an equivalent full-domain run that it is supposed to represent (no unfolding involved). The figure quoted is the maximum absolute deviation of E over that half, divided by the peak |E| of the full-domain run -- so 0.0019 means the worst cell in the reduced domain is off by 0.19% of the full-domain field peak. Source is a Gaussian beam except where a dipole group is named. Values are before -> after this change:

PEC on x/y/z (single axis) 0.0019 -> 0.0019 (unchanged)
PMC on x/y/z (single axis) 0.2768 -> 0.0019
PEC+PMC pairs (4 orientations) 0.2753 -> 0.0019
PMC on x/y (mirror dipole pair) 0.6819 -> 0.0002
PMC+PMC (mirror dipole quad) 1.8677 -> 0.0003

PEC-only axes are untouched by this commit; they are listed for contrast, since they were already correct and are what made the PMC error stand out. The residual ~0.2% on the beam cases is dominated by PML reflection and beam truncation, not by the symmetry plane -- the dipole cases, which have neither, land at 0.02-0.03%.

The PMC fix converges under grid refinement -- 0.00142, 0.00053, 0.00032 at 200, 100 and 50 nm spacing -- so it is a consistent discretization rather than a cancellation that happens to work at one resolution.

With all four applied the injected reduced profile equals the kept half of the full-domain profile to float32 roundoff (~1e-6 relative).

Known remaining limitation, not addressed here: unfold_fields applies one array flip to all three components, but the correct reconstruction map differs by Yee staggering class. Measured over the PML-free interior, three components per axis reconstruct to ~0.1% of the full-domain peak and the other three to 2-12%, and which class is exact flips between axes. The reduced simulation itself is correct; only the unfolded reconstruction is affected.

Found issue, not addressed here: GaussianPlaneSource profile is transposed for propagation along y. GaussianPlaneSource._get_amplitude_raw builds its profile as (horizontal_axis, vertical_axis) and then inserts the singleton propagation axis. For propagation along y, get_oriented_transverse_axes(1) returns (2, 0) — horizontal = z, vertical = x — which is the only axis ordering where horizontal > vertical. The resulting array is therefore (z, 1, x) where the field slice expects (x, 1, z).

Consequences:

  • Non-square transverse plane → hard failure. TypeError: div got incompatible shapes for broadcasting: (3, 12, 1, 16), (1, 16, 1, 12) raised from the impedance division in LinearlyPolarizedPlaneSource.apply.
  • Square transverse plane → passes, because the built-in profile is circular with equal radii on both axes, so the transpose is numerically a no-op.

Summary by CodeRabbit

  • Bug Fixes

    • Improved mirror-symmetry handling for reduced simulation domains.
    • Corrected plane-source orientation, centering, and amplitude normalization under symmetry reduction.
    • Magnetic symmetry boundaries now rely on natural domain padding instead of creating explicit walls.
    • Preserved object grid-shape information after symmetry clipping.
  • Tests

    • Added coverage validating reduced-domain source profiles, amplitude scaling, symmetry clipping, and boundary behavior against full-domain simulations.

…try (ymahlau#425)

Four independent defects made a symmetry-reduced simulation wrong for any source
with spatial variation along a symmetry axis. All were invisible to the existing
symmetry tests, which only use UniformPlaneSource with normalize_by_energy=False
and direction="+" -- a combination blind to every one of them.

1. Profile center re-centered on the reduced quadrant.
   TFSFPlaneSource._get_center derived the transverse center from the object's
   clipped extent, so a Gaussian spot moved to the middle of the kept half
   instead of staying on the symmetry plane. The reduction discarded the pre-clip
   extent, so the source could not know it had been cut; reduce_resolved_slices
   now reports the per-axis low-side clip and place_objects records it as
   SimulationObject._symmetry_clip_low.

2. Normalization summed over the clipped plane.
   normalize_by_energy and the Gaussian profile's own sum normalization both
   divided by a quarter-plane sum, inflating the injected amplitude by
   2**(n_axes/2) -- exactly 2x for two symmetric axes.

3. Profile mirrored about `center` along the vertical axis.
   v_basis = cross(wave_vector, u_basis) has a sign that depends on the
   propagation axis and direction, so the sampled profile was reflected for e.g.
   direction="-" along z (and, oppositely, direction="+" along y). Predates the
   symmetry feature by ~14 months and is invisible for any profile symmetric
   about its own center; it also blocked fix 1, since a correctly off-center
   profile got flipped out of the array.

4. The PMC symmetry wall over-constrained the field.
   Along a symmetry axis the six components split by Yee offset: tangential E and
   normal H sit at offset 0, normal E and tangential H at offset 1/2. The mirror
   plane lands on the reduced domain's low cell edge, so the tangential-H node a
   magnetic mirror must zero is at local -1 -- outside the array, where
   zero-padding already supplies it. Inserting a PerfectMagneticConductor instead
   zeroed tangential H at local 0, a full cell inside the domain. PEC still needs
   its wall (tangential E has no node on the plane). make_symmetry_walls now
   builds walls only for electric (-1) planes.

Validation. A symmetry-reduced run is compared cell-for-cell against the half of
an equivalent full-domain run that it is supposed to represent (no unfolding
involved). The figure quoted is the maximum absolute deviation of E over that
half, divided by the peak |E| of the full-domain run -- so 0.0019 means the worst
cell in the reduced domain is off by 0.19% of the full-domain field peak. Source
is a Gaussian beam except where a dipole group is named. Values are before -> after
this change:

  PEC on x/y/z (single axis)         0.0019 -> 0.0019   (unchanged)
  PMC on x/y/z (single axis)         0.2768 -> 0.0019
  PEC+PMC pairs (4 orientations)     0.2753 -> 0.0019
  PMC on x/y (mirror dipole pair)    0.6819 -> 0.0002
  PMC+PMC     (mirror dipole quad)   1.8677 -> 0.0003

PEC-only axes are untouched by this commit; they are listed for contrast, since
they were already correct and are what made the PMC error stand out. The residual
~0.2% on the beam cases is dominated by PML reflection and beam truncation, not by
the symmetry plane -- the dipole cases, which have neither, land at 0.02-0.03%.

The PMC fix converges under grid refinement -- 0.00142, 0.00053, 0.00032 at 200,
100 and 50 nm spacing -- so it is a consistent discretization rather than a
cancellation that happens to work at one resolution.

With all four applied the injected reduced profile equals the kept half of the
full-domain profile to float32 roundoff (~1e-6 relative).

Known remaining limitation, not addressed here: unfold_fields applies one array
flip to all three components, but the correct reconstruction map differs by Yee
staggering class. Measured over the PML-free interior, three components per axis
reconstruct to ~0.1% of the full-domain peak and the other three to 2-12%, and
which class is exact flips between axes. The reduced simulation itself is
correct; only the unfolded reconstruction is affected.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b23eb443-2bab-46c5-b603-442991965ed5

📥 Commits

Reviewing files that changed from the base of the PR and between 6e841ba and 340d74f.

📒 Files selected for processing (8)
  • src/fdtdx/fdtd/initialization.py
  • src/fdtdx/fdtd/symmetry.py
  • src/fdtdx/objects/object.py
  • src/fdtdx/objects/sources/linear_polarization.py
  • src/fdtdx/objects/sources/tfsf.py
  • tests/integration/fdtd/test_symmetry_plane_source.py
  • tests/integration/fdtd/test_symmetry_reduction.py
  • tests/simulation/physics/boundaries/test_symmetry_gaussian_source.py

📝 Walkthrough

Walkthrough

Symmetry reduction now records low-side clipping, propagates it to placed objects, adjusts plane-source positioning and normalization, and creates explicit walls only for PEC symmetry. New tests cover profile orientation, Gaussian-field equivalence, amplitude scaling, and wall generation.

Changes

Symmetry-aware source reduction

Layer / File(s) Summary
Reduction metadata and symmetry walls
src/fdtdx/fdtd/symmetry.py, tests/integration/fdtd/test_symmetry_reduction.py
Reduction returns per-object low-side clipping metadata, while symmetry-wall creation is limited to PEC planes.
Clip metadata on placed objects
src/fdtdx/fdtd/initialization.py, src/fdtdx/objects/object.py
Placement attaches clipping metadata to reduced objects and the simulation volume; objects expose their unreduced grid shape.
Symmetry-aware source geometry and scaling
src/fdtdx/objects/sources/tfsf.py, src/fdtdx/objects/sources/linear_polarization.py
Plane-source centers, polarization orientation, and amplitude normalization account for symmetry clipping and fold factors.
Plane-source placement regressions
tests/integration/fdtd/test_symmetry_plane_source.py
Integration coverage validates profile orientation, symmetry-plane centering, reduced profiles, and clipping metadata.
Gaussian symmetry physics regressions
tests/simulation/physics/boundaries/test_symmetry_gaussian_source.py
Simulation coverage compares unfolded and kept-half Gaussian fields, amplitude scaling, and PEC-only wall behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SimulationConfig
  participant place_objects
  participant reduce_resolved_slices
  participant PlaneSource
  SimulationConfig->>place_objects: configure symmetry and objects
  place_objects->>reduce_resolved_slices: reduce object slices
  reduce_resolved_slices-->>place_objects: reduced slices and clip_low
  place_objects->>PlaneSource: attach _symmetry_clip_low
  PlaneSource->>PlaneSource: adjust center and normalization
Loading

Possibly related PRs

  • ymahlau/fdtdx#353: Introduces the related symmetry-reduction implementation extended by this change.
  • ymahlau/fdtdx#405: Modifies overlapping polarization-vector handling in linear_polarization.py.

Suggested reviewers: ymahlau, renaissancenerd

Poem

I clipped the grid with careful cheer,
And kept the symmetry planes near.
PEC walls stand, PMC rests,
Gaussian waves pass their tests.
A bunny hops through fields anew—
Reduced domains now work as two!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main fixes: spatially varying plane sources and PMC wall handling under mirror symmetry.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.54%. Comparing base (6e841ba) to head (340d74f).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #430      +/-   ##
==========================================
+ Coverage   90.52%   90.54%   +0.01%     
==========================================
  Files          92       92              
  Lines       11920    11943      +23     
  Branches     1820     1823       +3     
==========================================
+ Hits        10791    10814      +23     
  Misses        783      783              
  Partials      346      346              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ymahlau

ymahlau commented Jul 30, 2026

Copy link
Copy Markdown
Owner

@bruxillensis, my AI agent also did a fix over night, see #431 :D I guess we duplicated work here a little. I would tend towards going forward with my PR because it also fixes ModeSource and works with non-square profiles

@coderabbitai coderabbitai Bot mentioned this pull request Jul 30, 2026
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