Fix 3D MaterialGrid subpixel smoothing: kernel normalization (#3258) and gradient consistency (#3259) - #3263
Open
jin-castle wants to merge 2 commits into
Open
Conversation
…ient consistency) Fixes NanoComp#3258 and NanoComp#3259, with regression tests. - get_uproj_w(): the 3D branch divided by 4 / 3 * pi * rad^3, where 4 / 3 is integer division, so the smoothing kernel integrated to 4/3 instead of 1 and the smoothed tensor was scaled everywhere the material grid has a nonzero gradient (interface-parallel components by 4/3, the perpendicular one by 3/4). Use 4.0 / 3.0. The 1D and 2D branches were already correct. (NanoComp#3258) - eps_averaging=False was not propagated to the adjoint gradient: set_materials_from_geom_epsilon() stored the raw subpixel maxeval in geps even though structure_chunk::set_chi1inv() zeroes it when averaging is off, and the _set_materials Python wrapper left the header defaults in place when skipping set_materials_from_geom_epsilon entirely. The adjoint therefore differentiated a smoothed operator that the forward solve never used; measured directional finite-difference vs adjoint disagreement of ~70-100% in 3D with do_averaging=True and eps_averaging=False, which is the documented reason 3D adjoint users had to disable smoothing. (NanoComp#3259) - New Simulation(allow_3d_subpixel=...) convenience switch to turn off MaterialGrid do_averaging for a 3D run in one place; the default True leaves behavior unchanged. test_subpixel_3d.py checks the kernel normalization analytically in 1D/2D/3D and validates directional finite-difference vs adjoint gradients in 3D for smoothing on, off, and the mixed do_averaging=True/eps_averaging=False case (all now agree to ~1e-4).
Collaborator
|
@smartalecH and @romanodev, can you take a look at this? Is it done correctly in the SSP repository? |
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.
Fixes #3258 and fixes #3259, with regression tests. (Issue text contains the
full derivations; summary below.)
Defect A (#3258) — kernel normalization.
get_uproj_w()'s 3D branchdivides by
4 / 3 * pi * rad^3, where4 / 3is integer division. Thesmoothing kernel therefore integrates to 4/3 instead of 1, scaling the
smoothed tensor everywhere the material grid has a nonzero gradient
(interface-parallel components by 4/3, perpendicular by 3/4) — for an
isotropic
MaterialGridthat is essentially the whole design region, not justinterface pixels. One-character fix:
4.0 / 3.0. The 1D/2D branches werealready correct.
Defect B (#3259) — eps_averaging not propagated to the gradient.
structure_chunk::set_chi1inv()zeroesmaxevalwhen averaging is off, butset_materials_from_geom_epsilon()stored the rawmaxevalinto thegeom_epsilonused byget_material_gradient(), and the_set_materialsPython wrapper left the header defaults in place when it skipped
set_materials_from_geom_epsilonentirely. The adjoint thereforefinite-differenced a smoothed operator that the forward solve never used.
Measured: directional finite-difference vs adjoint disagreement of a factor
~78 in 3D with
do_averaging=True, eps_averaging=False— the reason 3Dadjoint users have had to disable smoothing entirely.
Convenience switch.
Simulation(allow_3d_subpixel=False)turns offdo_averagingon every reachableMaterialGridat structure initializationin a 3D run, so smoothing can be disabled in one place instead of per-grid.
Default
Trueleaves behavior unchanged.Tests (
python/tests/test_subpixel_3d.py): analytic kernel-normalizationchecks in 1D/2D/3D, and 3D directional finite-difference vs adjoint gradient
checks for smoothing on, off, and the mixed case. Before the fix the mixed
case is off by ~7800% and the smoothing-on case silently solves the
mis-normalized epsilon; after it, all three agree.
Please merge #3264 before this one. The gradient checks above measure a
quantity that #3264 fixes independently, and on this branch alone they are
only accurate while the design region sits inside a single chunk. The
fd/adjointratio for the smoothing-on case, measured on this branch byforcing the split with
Simulation(num_chunks=...):(Forcing the split serially reproduces the
mpirunnumbers exactly; that waschecked against
-np1, 2, 4 and 8 on the pre-fix code.) CI runs its MPI jobsat
-np 2, so the checks pass here, but at four chunks the smoothing-on casemisses its 1% tolerance by an order of magnitude.
That is #2578 rather than a defect in this PR. The underlying gradient vector
is 12-44% off in L2 at every split above one chunk; the directional
finite-difference check largely misses this because it probes along
g/|g|,i.e. along the erroneous gradient itself. With #3264 in place the ratios are
identical at every chunk count, and the 1% tolerance in these tests could be
tightened again.
🤖 Generated with Claude Code