Skip to content

Enforce PEC boundaries and the GDS PEC mask after external field excitation - #166

Merged
ajnonaka merged 3 commits into
AMReX-Microelectronics:developmentfrom
ajnonaka:PEC_Excitation_Order
Nov 11, 2026
Merged

ajnonaka merged 3 commits into
AMReX-Microelectronics:developmentfrom
ajnonaka:PEC_Excitation_Order

Conversation

@ajnonaka

@ajnonaka ajnonaka commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

External field excitations are applied in OneStep_nosub after EvolveE/EvolveB have already applied the field boundary conditions and the PEC mask. A hard or soft source whose flag region touches a PEC domain boundary, or a PEC layer from a GDS/numpy file, therefore overwrites them. For example, it can leave a nonzero tangential E on a PEC wall or along a metal trace.

With this PR, PEC always wins: after any excitation, the domain PEC boundary condition and the PEC mask are re-applied, regardless of the source type.

This was the original motivation for #20. That PR is now stale (~1900 commits behind), and rebasing it as written would reintroduce soft-source double counting, because it moved the excitation inside the per-level/per-patch evolve routines. This PR fixes the issue without moving the excitation calls.

Behavior change

Inputs that drive a port by placing a hard source on a PEC wall (tangential E on the wall) relied on the old ordering, where the excitation overwrote PEC. With this PR those sources are zeroed by PEC. Such ports must be placed one cell inside the domain, on the first interior node.

Examples/Tests/circuits/inputs_regression_roger_pec is updated accordingly (see below). Other inputs that use the same pattern (e.g., the roger_pec* family) should be checked.

Changes

Re-apply PEC after excitation (WarpXExternalEMFields.cpp)

At the end of ApplyExternalFieldExcitationOnGrid(int externalfieldtype, DtType):

  • E (AllExternal, EfieldExternal, EfieldExternalPML):
    • ApplyEfieldBoundary(lev, PatchType::fine), i.e., domain PEC, including the split E fields in the PML
    • ApplyPECMaskToEfield(lev, include_guard_cells=true), i.e., the GDS/numpy PEC mask on the grid (valid + guard cells, since the excitation also writes guard cells) and, if extended, in the PML
  • B (AllExternal, BfieldExternal): PEC::ApplyPECtoBfield only. The full ApplyBfieldBoundary is intentionally not called, because its Silver-Mueller update is not idempotent and must not run twice in a half step.

Each step only runs when it applies: domain PEC when PEC::isAnyBoundaryPEC(), the mask when use_PEC_mask is on with the macroscopic solver, and only for fields whose excitation parser is active.

Because the fix lives inside the excitation routine, it covers every call site: the t=0 AllExternal source, both B half-steps, E, E in the PML (ApplyExcitationInPML), and the LLG call in SyncCurrentAndRho. The existing DtType soft-source scaling is unchanged.

Consolidate the PEC mask (WarpX.H, WarpXFieldBoundaries.cpp)

New WarpX::ApplyPECMaskToEfield(lev, include_guard_cells):

  • grid: E *= PEC_fp on the fine level, optionally including guard cells
  • PML (with extend_PEC_mask_into_pml): mask both split components; for the CPML, also reset the ψ_E memory variables on masked edges
  • no-op unless use_PEC_mask is on and em_solver_medium == Macroscopic. The mask is only initialized by MacroscopicProperties, so the old unconditional multiply in OneStep would have used an uninitialized mask with the vacuum solver.

Use the helper in MacroscopicEvolveE (WarpXPushFieldsEM.cpp)

The two inline mask blocks (grid, then PML + CPML ψ) are replaced by a single ApplyPECMaskToEfield(lev, false) call after the PML update. It is still applied before FillBoundaryE / PML::Exchange, so guard cells and the PML interface plane see masked E. The PML E update does not read grid E, so applying the grid mask after the PML update instead of before is equivalent.

Remove the redundant mask in OneStep_nosub (WarpXEvolve.cpp)

The mask block after the E excitation is removed. It is now covered by MacroscopicEvolveE (no excitation) and by ApplyExternalFieldExcitationOnGrid (after excitation). The old block also only masked valid cells and did not touch the PML.

Fill mask guard cells at initialization (MacroscopicProperties.cpp)

InitializePECFromSigma only sets the valid region, so PEC_fp guard cells stayed 1. A FillBoundary on PEC_fp[x,y,z] now fills interior and periodic guard cells from the neighboring grids, so the mask can be applied to E guard cells. Guard cells outside the domain stay 1; the PML has its own mask.

Regression input (Examples/Tests/circuits/inputs_regression_roger_pec)

  • The Ez port hard source is moved from the PEC wall (y = −Ly/2) to the first interior Ez node (y = −Ly/2 + dy). On the wall it is now zeroed by PEC, which left all fields identically zero.
  • The Ez excitation value is restricted to the port's x/z window, so the flagged Nb metal regions on the port row are held at 0 instead of being driven with the port voltage.
  • The regression benchmark needs to be regenerated.

Gaps fixed

Case Before After
Source on / touching a PEC domain boundary excitation overwrites PEC PEC enforced
Source overlapping the GDS mask on the grid masked (valid cells only); guard cells stale masked, valid + guard cells
Source overlapping the GDS mask in the PML (ApplyExcitationInPML=1) excitation overwrites mask masked; CPML ψ reset
t=0 hard source overlapping PEC / mask unmasked for the first B half-step masked

Not changed

  • H / H_bias (LLG): there is no PEC boundary condition or mask for H, so a hard H source on a PEC wall or inside metal is not corrected.
  • B is not masked by the GDS PEC mask.
  • PML guard cells after a PML excitation: only valid PML cells are masked; guard cells are refreshed by the FillBoundaryE that follows DampPML.
  • Silver-Mueller boundaries are not re-applied after excitation, so a source on a Silver-Mueller boundary still overrides it for that step.
  • inputs_regression_roger_pec: in the Nb layer under the center line, the metal and port flag terms overlap and sum to 2, so those cells act as a soft source. This predates this PR.

Testing

  • Syntax check (-fsyntax-only, 3D, AMReX 25.07) with and without WARPX_MAG_LLG
  • Full build (GNUmake / CMake), with and without USE_LLG
  • inputs_regression_roger_pec: nonzero fields with the port moved inward; benchmark regenerated
  • Other roger_pec* inputs checked for ports on PEC walls
  • GDS case with no excitation: results unchanged vs. development (up to roundoff)
  • Hard-source plane through a metal trace: tangential E on masked edges is exactly zero on the grid
  • Same with ApplyExcitationInPML=1, split-field PML and CPML: masked edges zero in the PML, ψ_E zero there
  • Hard-source plane intersecting a PEC domain boundary: tangential E on the wall stays zero
  • Multi-box run: masked E consistent across box boundaries (guard cells)
  • Existing regression tests unchanged (other than the updated benchmark)

Supersedes #20.

…olveE`/`EvolveB` have already applied the field boundary conditions and the PEC mask. A hard (or soft) source whose flag region touches a PEC domain boundary, or a PEC layer from a GDS/numpy file, therefore overwrites them. For example, it can leave a nonzero tangential E on a PEC wall or along a metal trace.

This was the original motivation for AMReX-Microelectronics#20. That PR is now stale (~1900 commits behind), and rebasing it as written would reintroduce soft-source double counting, because it moved the excitation inside the per-level/per-patch evolve routines. This PR fixes the issue without moving the excitation calls.

At the end of `ApplyExternalFieldExcitationOnGrid(int externalfieldtype, DtType)`, the routine now re-enforces PEC on whatever was just excited:

- **E** (`AllExternal`, `EfieldExternal`, `EfieldExternalPML`):
  - `ApplyEfieldBoundary(lev, PatchType::fine)`, i.e., domain PEC, including the split E fields in the PML
  - `ApplyPECMaskToEfield(lev, include_guard_cells=true)`, i.e., the GDS/numpy PEC mask on the grid (valid + guard cells, since the excitation also writes guard cells) and, if extended, in the PML
- **B** (`AllExternal`, `BfieldExternal`): `PEC::ApplyPECtoBfield` only. The full `ApplyBfieldBoundary` is intentionally not called, because its Silver-Mueller update is not idempotent and must not run twice in a half step.

Each step only runs when it applies: domain PEC when `PEC::isAnyBoundaryPEC()`, the mask when `use_PEC_mask` is on with the macroscopic solver, and only for fields whose excitation parser is active.

Because the fix lives inside the excitation routine, it covers every call site: the t=0 `AllExternal` source, both B half-steps, E, E in the PML (`ApplyExcitationInPML`), and the LLG call in `SyncCurrentAndRho`. The existing `DtType` soft-source scaling is unchanged.

New `WarpX::ApplyPECMaskToEfield(lev, include_guard_cells)`:

- grid: `E *= PEC_fp` on the fine level, optionally including guard cells
- PML (with `extend_PEC_mask_into_pml`): mask both split components; for the CPML, also reset the ψ_E memory variables on masked edges
- no-op unless `use_PEC_mask` is on and `em_solver_medium == Macroscopic`. The mask is only initialized by `MacroscopicProperties`, so the old unconditional multiply in `OneStep` would have used an uninitialized mask with the vacuum solver.

The two inline mask blocks (grid, then PML + CPML ψ) are replaced by a single `ApplyPECMaskToEfield(lev, false)` call after the PML update. It is still applied *before* `FillBoundaryE` / `PML::Exchange`, so guard cells and the PML interface plane see masked E. The PML E update does not read grid E, so applying the grid mask after the PML update instead of before is equivalent.

The mask block after the E excitation is removed. It is now covered by `MacroscopicEvolveE` (no excitation) and by `ApplyExternalFieldExcitationOnGrid` (after excitation). The old block also only masked valid cells and did not touch the PML.

`InitializePECFromSigma` only sets the valid region, so `PEC_fp` guard cells stayed 1. A `FillBoundary` on `PEC_fp[x,y,z]` now fills interior and periodic guard cells from the neighboring grids, so the mask can be applied to E guard cells. Guard cells outside the domain stay 1; the PML has its own mask.

| Case | Before | After |
|---|---|---|
| Source touching a PEC domain boundary | excitation overwrites PEC | PEC enforced |
| Source overlapping the GDS mask on the grid | masked (valid cells only); guard cells stale | masked, valid + guard cells |
| Source overlapping the GDS mask in the PML (`ApplyExcitationInPML=1`) | excitation overwrites mask | masked; CPML ψ reset |
| t=0 hard source overlapping PEC / mask | unmasked for the first B half-step | masked |

- H / H_bias (LLG): there is no PEC boundary condition or mask for H, so a hard H source on a PEC wall or inside metal is not corrected.
- B is not masked by the GDS PEC mask.
- PML guard cells after a PML excitation: only valid PML cells are masked; guard cells are refreshed by the `FillBoundaryE` that follows `DampPML`.
- Silver-Mueller boundaries are not re-applied after excitation, so a source on a Silver-Mueller boundary still overrides it for that step.

- [x] Syntax check (`-fsyntax-only`, 3D, AMReX 25.07) with and without `WARPX_MAG_LLG`
- [ ] Full build (GNUmake / CMake), with and without `USE_LLG`
- [ ] GDS case with no excitation: results unchanged vs. `development` (up to roundoff)
- [ ] Hard-source plane through a metal trace: tangential E on masked edges is exactly zero on the grid
- [ ] Same with `ApplyExcitationInPML=1`, split-field PML and CPML: masked edges zero in the PML, ψ_E zero there
- [ ] Hard-source plane intersecting a PEC domain boundary: tangential E on the wall stays zero
- [ ] Multi-box run: masked E consistent across box boundaries (guard cells)
- [ ] Existing regression tests unchanged

Supersedes AMReX-Microelectronics#20.
@ajnonaka
ajnonaka marked this pull request as ready for review November 11, 2026 00:06
@ajnonaka
ajnonaka merged commit 8a2400f into AMReX-Microelectronics:development Nov 11, 2026
15 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.

1 participant