[3/13][Adjoint Module] Fix adjoint source weighting on a symmetry plane - #3296
Open
smartalecH wants to merge 2 commits into
Open
[3/13][Adjoint Module] Fix adjoint source weighting on a symmetry plane#3296smartalecH wants to merge 2 commits into
smartalecH wants to merge 2 commits into
Conversation
Meep has no adjoint test that declares a symmetry, and the behaviour is easy to
misread as a bug: with Mirror(Y) the design gradient comes back with all of its
magnitude in y >= 0, which looks like a routine that forgot to mirror the
gradient back over the design region. It is not. Meep builds the structure on
the reduced grid volume, so weights below the plane are never read, and a weight
above it drives both halves of the structure at once. Finite differences
confirm both halves of that: the gradient really is zero below the plane, and
really is exactly twice the no-symmetry value above it.
Two cases are genuinely wrong and are recorded as expected failures rather than
skipped, so that a fix flips them:
- An objective on the symmetry plane gives 3/4 of the correct gradient.
Exactly 0.7500 at resolution 40 and 0.7484 at 20, so this is a scaling
error, not discretization.
- A monitor spanning the plane -- the usual case -- is low by one row's
worth: 4.0% at resolution 20, 2.9% at 30, 1.8% at 40, i.e. first order in
the grid spacing.
Everything with the objective clear of the plane matches a finite difference to
the solver's usual accuracy, which places the fault in how the adjoint source is
weighted on the plane rather than in the gradient accumulation.
fourier_sourcedata divides each interpolated adjoint source by the multiplicity of the point it sits at, to undo the replication a reduced symmetric domain applies. In the yee_grid = false branch the amplitude is split over the four Yee corners of a voxel, but the divisor was evaluated once at the voxel centre and reused for all four. Those disagree exactly on a symmetry plane. A point there is its own image, so its multiplicity is 1, while the centre half a pixel away has 2. The corner on the plane was therefore halved when it should not have been, leaving the adjoint source short. For an objective sitting on the plane the source's three Yee rows carry weights 1/4, 1/2, 1/4 and the halved one is the middle row, so exactly 1/4 of the source goes missing at any resolution -- the gradient came out at 3/4 of the finite difference (0.7484 at resolution 20, 0.7500 at 40). For an extended monitor merely spanning the plane the same single row is diluted over the O(1/dx) rows covered, which showed up as a first-order error: 4.0% / 2.9% / 1.8% at resolution 20 / 30 / 40. Take the multiplicity of the site each quarter is written to. Off the plane nothing changes, and without a symmetry every multiplicity is 1, so this is a no-op for the vast majority of runs. The two cases recorded as expected failures in the previous commit now hold, so they become plain assertions. test_adjoint_solver.py (44 gradient checks) and test_adjoint_cyl.py are unaffected.
Collaborator
|
This makes sense to me. |
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.
Supersedes #3292.
Fixes #3291.
fourier_sourcedatadivides each interpolated adjoint source by the multiplicity of the point it sits at, to undo the replication a reduced symmetric domain applies. In theyee_grid = falsebranch the amplitude is split over the four Yee corners of a voxel, but the divisor was evaluated once at the voxel centre and reused for all four.Those disagree exactly on a symmetry plane. A point there is its own image, so its multiplicity is 1, while the centre half a pixel away has 2. The corner on the plane was therefore halved when it should not have been, leaving the adjoint source short.
(finite difference = 1; no symmetry declared is 1.0002 throughout)
The on-plane number is exactly 3/4 and resolution independent — the source's three Yee rows carry weights 1/4, 1/2, 1/4 and the halved one is the middle row, so exactly a quarter of the source goes missing. An extended monitor merely spanning the plane dilutes that single row over the
O(1/dx)rows it covers, which is why it presented as a first-order error rather than a constant one. The last row is the common arrangement for a symmetric device, so most symmetric adjoint runs were carrying a few percent of gradient error.The fix takes the multiplicity of the site each quarter is actually written to. Off the plane nothing changes, and with no symmetry declared every multiplicity is 1, so this is a no-op for the vast majority of runs.
Tests
Meep had no adjoint test that declared a symmetry at all. The first commit adds one, including the part that looks like a bug and is not: with
Mirror(Y)the design gradient puts all of its magnitude iny >= 0, because Meep builds the structure on the reduced grid volume, so weights below the plane are never read and a weight above it drives both halves at once. Finite differences confirm both halves — zero below the plane, and exactly2.0000xabove it.Those two commits are separated deliberately: the first records the on-plane and plane-crossing failures as
expectedFailure, and the second turns them into plain assertions, so the diff shows the fix flipping them.test_adjoint_solver.py(44 gradient checks) andtest_adjoint_cyl.pyboth pass unchanged.