Skip to content

[7/n][Adjoint Module] Evaluate the dispersive adjoint operator at the discrete lineshape - #3287

Draft
smartalecH wants to merge 1 commit into
fix/material-grid-susceptibilitiesfrom
fix/adjoint-discrete-dispersion
Draft

[7/n][Adjoint Module] Evaluate the dispersive adjoint operator at the discrete lineshape#3287
smartalecH wants to merge 1 commit into
fix/material-grid-susceptibilitiesfrom
fix/adjoint-discrete-dispersion

Conversation

@smartalecH

Copy link
Copy Markdown
Collaborator

No description provided.

`get_chi1_tensor_disp` built the dispersive permittivity from the continuum
model -- `lorentzian_susceptibility::chi1` and `1 + i*sigma/(2*pi*f)` -- while
the FDTD timesteps a discrete recurrence.  The adjoint gradient was therefore
the exact derivative of an operator slightly different from the one being
simulated, and disagreed with a finite difference of the discrete objective at
`O((freq*dt)^2)`.

Matching the recurrences that are actually run:

  Conductivity.  The D update is
    D' = ((1 - sigma dt/2) D + dcurl) / (1 + sigma dt/2)
  (step_db.cpp, condinv from structure.cpp).  With D ~ exp(-i w t), dividing
  through by exp(-i w dt/2) gives
    D [ -(2/dt) sin(w dt/2) i + sigma cos(w dt/2) ] = curl
  against the continuum D [-i w + sigma] = curl, so the permittivity factor
  1 + i sigma / w becomes 1 + i sigma (dt/2) cot(pi freq dt).

  Lorentzian.  From update_P(),
    P'(1 + g dt/2) = P (2 - w0^2 dt^2) - P_prev (1 - g dt/2) + w0^2 dt^2 sigma E
  the same substitution replaces freq^2 by [sin(pi freq dt)/(pi dt)]^2 and
  gamma*freq by gamma sin(2 pi freq dt)/(2 pi dt), leaving the numerator alone.
  Drude is the no_omega_0_denominator case and drops the resonance term.

Both reduce to the continuum expressions as dt -> 0.  Relative disagreement
between the adjoint gradient and a central difference of the objective, at
resolution 20 / 40 / 80:

  D-conductivity   0.455%  0.107%  0.027%   ->  0.001%  0.000%  0.001%
  Drude pole       0.232%  0.058%  0.015%   ->  0.000%  0.000%  0.000%

The 4x-per-doubling on the left is the second-order signature being removed;
the right-hand columns sit at the same roundoff floor as a non-dispersive
structure.

The discrete lineshape is a separate `chi1_discrete` rather than a change to
`chi1`, because reporting material properties to the user is the opposite
case: `get_epsilon(frequency=...)` must keep describing the material, not the
discretization, or it would return resolution-dependent numbers for a
resolution-independent medium.  A test pins that.  The base implementation
falls back to `chi1`, so gyrotropic and multilevel susceptibilities -- which
derive from `susceptibility` directly and have their own update equations --
are unaffected.

`dt` reaches the material model through `geom_epsilon`, alongside the `tol` and
`maxeval` already stashed there for the gradient calculation; zero means "not
known" and selects the continuum form.  The copy constructor was dropping all
of these, which would not fail so much as quietly compute against a different
operator than the original was built with.

The coarse-resolution test is the sharpest of the four: the error is largest
where dt is largest, so a formula that were merely closer than the continuum
one would still converge and still pass at resolution 20.
@smartalecH smartalecH changed the title Evaluate the dispersive adjoint operator at the discrete lineshape [7/n][Adjoint Module] Evaluate the dispersive adjoint operator at the discrete lineshape Aug 31, 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.

1 participant