[11/13][Adjoint Module] Subpixel-smooth dispersive materials and differentiate their shape - #3293
Draft
smartalecH wants to merge 3 commits into
Draft
Conversation
This was referenced Sep 2, 2026
Closed
Draft
Draft
`structure_chunk::set_chi1inv` volume-averages the instantaneous permittivity while `add_susceptibility` point-samples sigma, so a dispersive material switches on a whole pixel at a time as an object moves. That is a staircase, and a staircase has no derivative -- the same wall the geometry gradients hit, reached through the material rather than the shape. Add `material_function::eff_sigma_row`, standing to `sigma_row` as `eff_chi1inv_row` stands to `chi1p1`, and call it from the two sigma loops with the pixel volume the chi1inv loop already uses. The default implementation samples the volume centre, so this is behaviour-preserving on its own: nothing overrides it yet. The sigma loops now index in ivec like the chi1inv loop above them, since the half-pixel offdiagonal shift and the smoothing diameter have to agree between the two for the eventual averaging to be consistent. Verified neutral: a Drude MaterialGrid's transmittance is unchanged bit for bit, and test_adjoint_dispersion, test_material_grid, test_material_dispersion, test_dispersive_eigenmode, test_faraday_rotation, test_multilevel_atom, test_geometry_gradient and test_conductivity all pass.
Taper each medium's pole amplitude by the pixel's filling fraction, using the
same front object, shift and fill that eff_chi1inv_matrix uses, so the
permittivity and the susceptibility describe the same interface.
Swept sub-pixel across one pixel, a Drude slab whose eps_infinity matches the
background -- so that the only position dependence there is comes from sigma:
point sampled constant to the last digit at 10 of 11 offsets, then a
single 20% jump
averaged smooth, symmetric about the pixel centre, and 60x less
total variation across the sweep
Linear in fill rather than Kottke, deliberately. Kottke's normal component is
a harmonic average of a permittivity; there is no analogous exact result for a
resonant amplitude, and a harmonic average against a medium that lacks the pole
would be zero in every boundary pixel. Scaling each medium's whole sigma
tensor by a non-negative weight and adding keeps the result positive
semidefinite, which is what passivity depends on, and makes the amplitude
exactly linear in fill, so the shape derivative is a constant rather than
something to be differenced.
Multilevel atoms and gyrotropic media are excluded and stay point sampled. A
multilevel atom carries per-pixel level populations with their own nonlinear
dynamics, so a partially filled pixel is not a weaker gain medium -- without
this exclusion test_multilevel_atom fails. A gyrotropic sigma is
antisymmetric, so the positive-semidefiniteness argument does not hold for it
either. Material grids and user materials also stay point sampled: their
material already varies continuously in space, and mixing a level set with a
geometric filling fraction is a separate question.
The pole lookup is factored out of sigma_row into material_sigma_row so both
paths ask the same question of a material.
`geometry_addgradient` contracted only the real, non-dispersive `eff_chi1inv_matrix`, so a Drude or Lorentz object took a branch that dropped its poles entirely: the gradient described a structure the forward solve never ran. That is the piece a metal reflector's position needs. Add `eff_chi1inv_row_disp_fill`, the dispersive counterpart of the `fill_override` on `eff_chi1inv_matrix`. `eff_chi1inv_row_disp` cannot serve, because it resolves the material at a point and so knows nothing about a filling fraction. The construction matches what the forward solve now runs: the instantaneous part is Kottke-smoothed, and each pole's amplitude is taken linearly in fill from whichever medium carries it, exactly as `eff_sigma_row` writes into the sigma arrays. Since chi1 is linear in sigma, a pole contributes `lineshape(freq) * weight * sigma` and the two media's pole lists never have to be matched against each other -- the same reason `epsilon_material_grid` can concatenate rather than blend. The branch is chosen from the materials at *this* interface rather than from `md->trivial` of the front object. That distinction matters: `trivial` is a property of a material, but under mixing it becomes a property of a pixel, so a plain dielectric sitting in front of a metal would otherwise take the non-dispersive branch and drop the whole contribution at precisely the boundary pixels that carry the derivative. `interface_fill` now hands back both materials, since the caller needs them to build the mixture. Against a central difference of the objective, at resolution 20: dielectric (control) 0.139% Drude metal 0.017% both inside the 2% the existing shape-derivative tests use, which is set by the O(dx) accuracy of a shape derivative rather than by anything dispersive.
smartalecH
force-pushed
the
feat/adjoint-dispersive-smoothing
branch
from
September 4, 2026 17:32
01b9ea1 to
373fc60
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #3287.
Second-order subpixel smoothing for dispersive and conductive materials, and the matching shape derivative, which is what a metal reflector's position needs.
geometry_addgradientcontracted only the real non-dispersive tensor, so a Drude block's poles contributed nothing and the gradient described a structure that was never simulated.Agrees with a finite difference to 0.017%. The union-of-poles construction is preserved throughout -- each medium's sigma is scaled by its own weight and poles are never blended, which keeps the tensor positive semidefinite and avoids the non-passive
[[0,s],[s,0]]form behind #666 / #3229.