Skip to content

fixed symmetry bug - #431

Merged
ymahlau merged 3 commits into
mainfrom
fix_symmetry
Jul 30, 2026
Merged

fixed symmetry bug#431
ymahlau merged 3 commits into
mainfrom
fix_symmetry

Conversation

@ymahlau

@ymahlau ymahlau commented Jul 30, 2026

Copy link
Copy Markdown
Owner

fix: spatially-varying sources under config.symmetry (closes #425)

A GaussianPlaneSource spanning a symmetry plane was re-centred on the surviving quadrant instead
of staying centred on the plane. Tracking that down turned up a cluster of related defects in the
same area, all invisible to the existing tests because every symmetry test used a transversely
uniform source — a field with no transverse derivatives cannot detect a misplaced transverse
profile, a mirrored profile, or a mishandled cell row at the symmetry plane.

The reported bug

TFSFPlaneSource._get_center derived the profile centre from self.grid_shape, which is the
post-reduction shape. The geometry was clipped correctly, but the contents were regenerated from
the clipped extent, so the beam was rebuilt in the middle of the quadrant (peak at index (14,14)
of a 30×30 reduced plane instead of (0,0)). Moving the source made no difference, since the
profile was always recentred on whatever survived clipping.

Objects now remember their pre-clip extent in reduced coordinates
(SimulationObject.unreduced_grid_slice_tuple, with a negative start on axes where the object
crossed a plane), and the profile centre is derived from that — -0.5 for a plane-symmetric source,
i.e. half a cell below the reduced min edge. straddles_symmetry_plane() also replaces the previous
slice.start == 0 heuristic, which could not distinguish "was clipped by the plane" from "happens
to begin at the plane".

Adjacent defects found and fixed

  • direction="-" mirrored the transverse profile. The in-plane v basis came from
    cross(wave_vector, u_basis), so a backward-propagating source flipped its profile about the
    centre. Invisible for a centred radially symmetric spot; wrong for everything else, and it made
    the centre fix alone zero out the injected field.
  • Propagation along y transposed the profile, because the oriented transverse axes are
    descending there ((2, 0)). On a non-square plane it crashed outright:
    TypeError: div got incompatible shapes for broadcasting: (3,8,1,4), (1,4,1,8).
  • Absolute amplitudes were off by 2^k (normalize_by_energy=False) or 2^(k/2) (default),
    because profile.sum() and the energy sum ran over the clipped plane. Both now normalize over the
    full-domain extent, so a reduced run injects exactly what the unreduced one does.
  • ModePlaneSource / ModeOverlapDetector solved a different mode. Asking the mode solver for a
    symmetric solve on the reduced cross-section is inconsistent with how FDTDX rasterizes materials:
    it writes one cell-centred ε array per component while the solver samples on its staggered grid.
    Verified directly against tidy3d — with properly Yee-staggered ε its symmetric solve reproduces
    the full spectrum to 1e-15; with FDTDX's co-located arrays it is off by ~1e-1. neff error for a
    400×200 nm Si waveguide was −0.232 / −0.108 / −0.052 at 50/25/12.5 nm (first order in Δ, so it
    does not converge away). Both now mirror the reduced cross-section back to the full one, solve
    there, project onto the wall's parity subspace and restrict — reproducing the mode the unreduced
    simulation would use, to ≤ 2.4e-7 at every resolution. The projection also detects a wall type
    that cannot support the selected mode and raises instead of injecting noise. The mode-solver
    symmetry field is no longer auto-derived; it remains for hand-built half domains.
  • PMC symmetry planes were wrong for structured fields. The wall zeroed tangential H at the
    first cell — half a cell off the plane, where the odd symmetry relates it to its mirror image
    rather than making it vanish. Its condition now lives in the mirrored padding halo the E-update
    curl reads. Interior error for a diffracting beam went from 16% to a first-order residue
    (6.7% / 3.3% / 1.6% at 50/25/12.5 nm).
  • Detectors recorded half the field in the plane row. The co-location stencil takes a backward
    half-step average against a zero halo, so a detector on the symmetry plane read 0.36093 where
    the full domain has 0.72185. The same mirror halo fixes it; PEC reduction is now exact to ~1e-5
    across the whole plane.
  • unfold_* used a plain flip for every component. Components sampled on the plane
    (tangential E, normal H — and all co-located detector output along x and y) pair as m±j, so the
    plane row was duplicated and the mirrored half shifted by one cell. Per-component index maps now
    apply.

New errors and diagnostics

Placements a mirror plane cannot represent now fail with an explanation instead of silently
degrading: a point dipole on a plane (the plane is a cell edge, so the reduced run would model the
dipole plus its mirror half a cell away), a TFSF box reaching a plane (its faces in the discarded
half would be dropped, leaving an open surface), a DiffractiveDetector crossing a plane (its order
basis is set by the halved period), a plane source on a plane normal to its own propagation axis,
and a tilted or randomly displaced source crossing a plane.

Separately, a warning now names the fix when the polarization does not match the wall type. This is
worth calling out: the script in #425 uses symmetry=(+1,-1,0) with E ∥ x, which needs
(-1,+1,0) — PEC where E is normal, PMC where it is tangential. Nothing flagged it, so the reduced
run faithfully simulated a field odd about both planes.

Conventions (unchanged behaviour, now enforced and tested)

Plane-source amplitudes are chosen so a reduced run reproduces the full-domain run. Mode sources and
mode-overlap references keep carrying unit power through the plane they occupy, so α = 1 still
means a perfectly transmitted mode and S-parameters are unaffected; their fields are therefore
√(2^k) larger than the restriction of a full-domain unit-power mode.

Tests

  • tests/unit/objects/sources/test_symmetry_sources.py — 40 tests: reduced vs full-domain injection
    for PEC and PMC, one and two axes, all three propagation axes, both directions, plus amplitude,
    orientation, and the new errors/warning. No FDTD stepping needed, so tolerances are 1e-6 rather
    than physics-level. 33 of these fail on the unfixed source.
  • tests/unit/core/physics/test_symmetry.py — 16 tests for the parity table, the two mirror index
    maps and the parity projection.
  • tests/integration/fdtd/test_symmetry_mode_source.py — 12 tests: neff and modal fidelity
    against the full domain, flux convention, source/detector consistency, incompatible-wall error.
  • tests/simulation/physics/boundaries/test_symmetry_structured_field.py — a diffracting beam,
    which is what the existing uniform-plane-wave tests could not see, comparing both the reduced
    fields and the unfolded output.

Full suite green (2766 unit/integration/docs, 65 simulation including time-reversal and gradients,
plus the existing hand-built PEC/PMC quarter-domain tests), ty and pre-commit clean.

Known limitations, documented in the code

The residual PMC discrepancy is source rasterization, not the wall: profiles are applied per cell,
so a component sampled on a symmetry plane has its footprint centred half a cell off it in the
full-domain reference — the reduced run is the more symmetric of the two. Fixing that needs
per-component profile resampling, which changes every simulation, not just symmetric ones. A
user-placed (non-symmetry) PMC boundary also keeps its previous behaviour, so hand-built half
domains are untouched; the same half-cell argument applies there.

Summary by CodeRabbit

  • New Features
    • More accurate symmetry-reduced mirroring for modes, sources, detectors, and unfolded fields, including electric (PEC) vs magnetic (PMC) plane behavior.
    • Symmetry-aware object placement now preserves full object extents in reduced domains.
  • Bug Fixes
    • Corrected parity-projection diagnostics and residual handling for JIT-friendly tracing.
    • Mode solving and overlap detection now use symmetry-reduced solving when applicable; symmetry reduction no longer inserts PMC wall objects.
    • Improved halo/padding behavior for symmetry planes with periodic axes.
  • Documentation
    • Expanded guidance for electric vs magnetic symmetry planes and unfolding/index mapping conventions.
  • Tests
    • Added/updated integration and unit tests covering parity mapping, unsupported placements, and unfolding correctness.

@coderabbitai

coderabbitai Bot commented Jul 30, 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: e22b91d0-7040-4903-9908-0eac8af57511

📥 Commits

Reviewing files that changed from the base of the PR and between ab2cb39 and 534a3ee.

📒 Files selected for processing (4)
  • src/fdtdx/core/physics/modes.py
  • src/fdtdx/core/physics/symmetry.py
  • tests/unit/core/physics/test_modes.py
  • tests/unit/core/physics/test_symmetry.py

📝 Walkthrough

Walkthrough

Symmetry handling now distinguishes electric and magnetic planes across reduction, parity mapping, field padding, unfolding, mode solving, and source placement. New validations reject unsupported placements, while expanded tests cover reduced fields, detectors, modes, sources, and mirror index behavior.

Changes

Symmetry-aware simulation pipeline

Layer / File(s) Summary
Parity, mirroring, and reduced-domain metadata
src/fdtdx/core/physics/symmetry.py, src/fdtdx/fdtd/symmetry.py, src/fdtdx/objects/object.py, src/fdtdx/fdtd/initialization.py, src/fdtdx/objects/boundaries/*, src/fdtdx/config.py, .claude/skills/fdtdx/SKILL.md
Wall-aware parity pairing, unreduced object slices, electric-only symmetry walls, and related documentation are added.
Symmetry-reduced mode solving
src/fdtdx/core/physics/modes.py, src/fdtdx/objects/sources/mode.py, src/fdtdx/objects/detectors/mode.py, tests/integration/fdtd/test_symmetry_mode_source.py
Modes are solved on mirrored full cross-sections, projected onto configured parity, restricted to the kept half, and flux-normalized.
Mirror-aware updates and detector unfolding
src/fdtd/update.py, src/fdtd/symmetry.py, tests/unit/fdtd/*, tests/simulation/physics/boundaries/test_symmetry_structured_field.py
FDTD padding, detector interpolation, field unfolding, and detector reconstruction use electric-versus-magnetic plane index rules.
Symmetry-aware sources and placement validation
src/fdtdx/objects/sources/*, src/fdtdx/objects/detectors/diffractive.py, tests/unit/objects/sources/test_symmetry_sources.py
Source profiles account for axis order, direction, nonuniform coordinates, and multiplicity; unsupported placements are rejected or warned on.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SimulationConfig
  participant place_objects
  participant SymmetryReduction
  participant FDTDUpdates
  participant DetectorUnfolding
  SimulationConfig->>place_objects: provide symmetry planes
  place_objects->>SymmetryReduction: reduce slices and create electric walls
  SymmetryReduction->>FDTDUpdates: configure reduced grid and halo behavior
  FDTDUpdates->>DetectorUnfolding: produce reduced detector states
  DetectorUnfolding-->>SimulationConfig: reconstruct full-domain outputs
Loading

Possibly related PRs

  • ymahlau/fdtdx#430: Modifies the same symmetry-reduction internals and wall creation behavior.
  • ymahlau/fdtdx#405: Shares the mode-overlap detector reference-mode computation path.
  • ymahlau/fdtdx#418: Shares Gaussian source profile and symmetry mapping changes.

Suggested reviewers: teevee112, bruxillensis

Poem

I’m a rabbit hopping through mirrored light,
PEC on the edge, PMC out of sight.
Modes bloom wide, then fold with care,
Sources keep their profiles fair.
Fields leap home from halves anew.

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes broad mode, detector, boundary, and unfolding symmetry changes beyond the source-placement bug in #425. Split unrelated symmetry fixes into separate PRs or justify each change against the linked issue scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 45.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is related but too vague to describe the actual change. Use a concise, specific title like “Fix symmetry handling for spatially varying sources”.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR addresses #425 by preserving spatially varying source placement and profile under symmetry reduction.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.49112% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.63%. Comparing base (6e841ba) to head (534a3ee).

Files with missing lines Patch % Lines
src/fdtdx/fdtd/symmetry.py 73.91% 8 Missing and 4 partials ⚠️
src/fdtdx/objects/sources/tfsf_region.py 40.00% 2 Missing and 1 partial ⚠️
src/fdtdx/core/physics/modes.py 95.00% 1 Missing and 1 partial ⚠️
src/fdtdx/objects/object.py 89.47% 2 Missing ⚠️
src/fdtdx/objects/sources/tfsf.py 95.74% 1 Missing and 1 partial ⚠️
src/fdtdx/objects/detectors/diffractive.py 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #431      +/-   ##
==========================================
+ Coverage   90.52%   90.63%   +0.10%     
==========================================
  Files          92       93       +1     
  Lines       11920    12166     +246     
  Branches     1820     1864      +44     
==========================================
+ Hits        10791    11027     +236     
- Misses        783      787       +4     
- Partials      346      352       +6     

☔ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/fdtdx/core/physics/modes.py`:
- Around line 399-400: Add an explicit validation guard in the mode-solving path
around the bend parameters, including the path reached by
ModeOverlapDetector._compute_mode_fields: reject any configuration where both
bend_radius/bend_axis and a mirrored axis are set, before forwarding parameters
to the full-cross-section solve. Raise the established invalid-configuration
exception with a clear message, while preserving existing behavior for bends
without mirroring and mirroring without bends.

In `@src/fdtdx/core/physics/symmetry.py`:
- Around line 245-247: The mode-symmetry residual must remain a JAX array during
traced execution. Update project_onto_parity to return residual without
converting it via float, and revise compute_mode_symmetry_reduced and its
callers so the residual threshold check and diagnostic logging occur outside
jax.jit-traced code; otherwise explicitly prevent tracing of
ModePlaneSource.apply_params.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a7962834-122b-4b9e-a9bd-c7e075131318

📥 Commits

Reviewing files that changed from the base of the PR and between 6e841ba and 72841c0.

📒 Files selected for processing (21)
  • src/fdtdx/core/physics/modes.py
  • src/fdtdx/core/physics/symmetry.py
  • src/fdtdx/fdtd/initialization.py
  • src/fdtdx/fdtd/symmetry.py
  • src/fdtdx/fdtd/update.py
  • src/fdtdx/objects/boundaries/boundary.py
  • src/fdtdx/objects/boundaries/pmc.py
  • src/fdtdx/objects/detectors/diffractive.py
  • src/fdtdx/objects/detectors/mode.py
  • src/fdtdx/objects/object.py
  • src/fdtdx/objects/sources/dipole.py
  • src/fdtdx/objects/sources/linear_polarization.py
  • src/fdtdx/objects/sources/mode.py
  • src/fdtdx/objects/sources/tfsf.py
  • src/fdtdx/objects/sources/tfsf_region.py
  • tests/integration/fdtd/test_symmetry_mode_source.py
  • tests/integration/fdtd/test_symmetry_reduction.py
  • tests/simulation/physics/boundaries/test_symmetry_structured_field.py
  • tests/unit/core/physics/test_symmetry.py
  • tests/unit/fdtd/test_symmetry_unfold.py
  • tests/unit/objects/sources/test_symmetry_sources.py

Comment thread src/fdtdx/core/physics/modes.py
Comment thread src/fdtdx/core/physics/symmetry.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/fdtdx/core/physics/symmetry.py (1)

270-283: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Residual concretization under jax.jit still unresolved.

project_onto_parity is called from compute_mode_symmetry_reduced, which is invoked from ModePlaneSource.apply() / ModeOverlapDetector.apply(). float(residual) at line 282 forces a JAX value to a Python scalar; if these apply() paths are ever traced under jax.jit (e.g. a jitted optimization loop calling apply_params), this raises ConcretizationTypeError. This is the same code flagged in a prior review round and remains unchanged.

🐛 Suggested direction (unchanged from prior review)
-    residual = jnp.where(norm > 0, jnp.linalg.norm(jnp.abs(field - projected)) / norm, 0.0)
-    return projected, float(residual)
+    residual = jnp.where(norm > 0, jnp.linalg.norm(jnp.abs(field - projected)) / norm, 0.0)
+    return projected, residual

Then move the residual > 0.9 / residual > 0.3 branches in compute_mode_symmetry_reduced out of any traced call path (or document/enforce that ModePlaneSource.apply_params must not be traced).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/fdtdx/core/physics/symmetry.py` around lines 270 - 283, Remove the Python
scalar conversion from project_onto_parity by returning the JAX residual value
directly, and update compute_mode_symmetry_reduced to keep residual-threshold
branching outside traced execution. Ensure ModePlaneSource.apply and
ModeOverlapDetector.apply remain JIT-compatible, or explicitly enforce that
apply_params cannot be traced if the branching must stay in Python.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/fdtdx/core/physics/symmetry.py`:
- Around line 270-283: Remove the Python scalar conversion from
project_onto_parity by returning the JAX residual value directly, and update
compute_mode_symmetry_reduced to keep residual-threshold branching outside
traced execution. Ensure ModePlaneSource.apply and ModeOverlapDetector.apply
remain JIT-compatible, or explicitly enforce that apply_params cannot be traced
if the branching must stay in Python.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4aa8538e-546e-4b7b-ac08-3ad3a8334fc7

📥 Commits

Reviewing files that changed from the base of the PR and between 72841c0 and ab2cb39.

📒 Files selected for processing (16)
  • .claude/skills/fdtdx/SKILL.md
  • src/fdtdx/config.py
  • src/fdtdx/core/physics/modes.py
  • src/fdtdx/core/physics/symmetry.py
  • src/fdtdx/fdtd/initialization.py
  • src/fdtdx/fdtd/symmetry.py
  • src/fdtdx/fdtd/update.py
  • src/fdtdx/objects/boundaries/boundary.py
  • src/fdtdx/objects/boundaries/pmc.py
  • tests/integration/fdtd/test_symmetry_mode_source.py
  • tests/integration/fdtd/test_symmetry_reduction.py
  • tests/simulation/physics/boundaries/test_symmetry_structured_field.py
  • tests/unit/core/physics/test_symmetry.py
  • tests/unit/fdtd/test_symmetry_unfold.py
  • tests/unit/fdtd/test_update.py
  • tests/unit/objects/sources/test_symmetry_sources.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/fdtdx/fdtd/initialization.py
  • src/fdtdx/objects/boundaries/boundary.py
  • tests/unit/objects/sources/test_symmetry_sources.py
  • src/fdtdx/core/physics/modes.py

@ymahlau
ymahlau merged commit 77b4bc5 into main Jul 30, 2026
6 checks passed
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.

Symmetry bug(?) with spatially varying sources

1 participant