-
Notifications
You must be signed in to change notification settings - Fork 629
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Approximate material grid smoothing (β=∞) #1951
base: master
Are you sure you want to change the base?
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1951 +/- ##
==========================================
- Coverage 73.29% 65.05% -8.25%
==========================================
Files 17 17
Lines 4891 4916 +25
==========================================
- Hits 3585 3198 -387
- Misses 1306 1718 +412
|
See also #1399 for what to do when |
src/meepgeom.cpp
Outdated
} | ||
return true; | ||
} | ||
|
||
double get_material_grid_fill(meep::ndim dim, double d, double r, double u, double eta, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Analytically computes fill fraction.
src/meepgeom.cpp
Outdated
LOOP_OVER_DIRECTIONS(normal_vec_behind.dim, k) { normal_vec_behind.set_direction(k,normal_vec_behind.in_direction(k)*nabsinv); } | ||
|
||
double uval = matgrid_val(p_mat_behind, tp, oi, mat_behind)+this->u_p; | ||
double d = (mat_behind->eta-uval) * nabsinv; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
distance to an interface
src/meepgeom.cpp
Outdated
*/ | ||
double fill_front, fill_back; | ||
vector3 normal_front, normal_back; | ||
if (is_material_grid(mat)){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should only occur if the entire pixel is filled by the material grid.
src/meepgeom.cpp
Outdated
|
||
if (maxeval == 0) { | ||
if (maxeval == 0 || | ||
(!get_front_object(v, geometry_tree, p, &o, shiftby, mat, mat_behind, p_mat, p_mat_behind) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would change get_front_object
to return the number of objects found (treating the default material as an "object").
- If it's
> 2
, then we go to thenoavg:
branch. - If it
== 2
, then even if one of the objects is a material grid or a user material then we treat it as a uniform material (by evaluating atp_mat
orp_mat_behind
, respectively) and do the geometric-object averaging (if the resultingmat
andmat_behind
are still unequal). - If it
== 1
, then if it's a material grid we do the first-order expansion to find the fill fraction etcetera.
To debug the gradient step, make sure the forward, adjoint, and recombination steps all have the same geometry tree. See libctl |
Quick update:
|
Possible solution: do the β=infinity averaging for pixels that cross the u=η level set, but don't use ε1 and ε2.
|
Regarding the merge with #1399 — in order to retain piecewise differentiability, when you have an "edge" pixel that also crosses the u=η level set, instead of sampling the MG at an arbitrary point you should probably compute the fill fraction and then do a simple weighted average of the "effective" ε1 and ε2 as defined above. |
Status update: I've fixed almost all of the existing tests. I'm still having trouble with our jax test, I've also updated |
@@ -7,6 +7,8 @@ | |||
import meep as mp | |||
from scipy import special | |||
from scipy import signal | |||
import skfmm |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary to include this additional module just for its signed distance function (rather than write our own or find a similar function in e.g. SciPy)? skfmm does not seem to be widely used which could make installing it a challenge for some users building from source.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could make installing it a challenge for some users building from source.
From their docs:
pip install scikit-fmm
What's challenging about this particular package vs numpy
, scipy
, etc.?
It's convenient for testing, but we can remove it if it really is a challenge to install.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran into some package conflicts during the pip install
. In general, it is useful to keep the number of package dependencies, particularly smaller ones, to a minimum.
@smartalecH, I pushed a rebased version following the instructions from #2164 (comment) to fix conflicts from the reformatting. (If for some reason there is a problem with the rebase, the original branch is saved in the |
src/meepgeom.cpp
Outdated
uval = matgrid_val(p_mat, tp, oi, mat); | ||
normal = matgrid_grad(p_mat, tp, oi, mat); | ||
if (cvector3_re(normal).x == 0 && cvector3_re(normal).y == 0 && cvector3_re(normal).z == 0) | ||
/* couldn't get normal vector for this point; no averaging is needed, | ||
but we still need to interpolate onto our grid using duals */ | ||
goto mgavg; | ||
duals::duald u_prime = normalize_dual(normal); | ||
duals::duald d = (mat->eta - uval) / u_prime; | ||
double r = v.diameter() / 2; | ||
fill = get_material_grid_fill(v.dim, d, r, uval, mat->eta); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I think I have finally narrowed down the reason why some tests are failing.
Note that the matgrid_grad
is especially sensitive to roundoff error (it calls several routines from both meep
and libctl
). There are many cases where the normal vector should be (0,0,0)
(e.g. a uniform design region) but is typically off by a few
This is especially problematic when uval
≈mat->eta
, because then we get some weird d
and its dual derivative).
We need to think about some ways to make this part of the code more robust to roundoff error. This is a bit outside of my wheelhouse...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an example, suppose I'm optimizing a uniform design field (such that rho=0.5
everywhere). Of course, I'm doing TO, so I run this through a conic filter first. Ideally, my output is also rho_tilde=0.5
everywhere, but in reality there are some perturbations of the order of
Now, because of this slight variation, when I try to compute the normal vector at a particular point r
(i.e. the spatial gradient of the design field) using matgrid_grad
, I hope to get a vector that looks like (0,0,0)
but most of the time in these cases it looks something like (3.330669e-15, 6.661338e-15, 0.000000e+00)
, where again, I'm off by a few x
and y
directions.
Furthermore, when I try to compute -2.775558e-16
. Which means I have a really small number divided by an even smaller number (
I've tried some thresholded compare statements (i.e. create a function equals(double var1, double var2)
that checks if var1
and var2
are within a few
I'm thinking there's another way to tackle this problem that lets us avoid the issue of checking for roundoff altogether... just not sure what that is yet...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we try adding a check to the end of meep::vec material_grid_grad
to set the gradient vector to zero if all the components happen to be smaller than some threshold value? That way, we just need to modify only one place in the code. (For consistency, we also should do the same thing for other functions which compute the normal vector i.e. material_function::normal_vector
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a function, AreEqual()
, which checks two numbers (I templated it so that it works with doubles, floats, etc.) within
This mitigates many of the errors I've seen so far. But there are still some issues slipping through the cracks, again due to roundoff error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, I would expect our final effective ε to be insensitive to errors like this, because in a region where ε is nearly constant its arithmetic and harmonic means are nearly equal so the choice of the normal vector becomes irrelevant. So, we should have a well-conditioned function to differentiate.
This seems like a classic type of problem in numerical analysis where you have some nice well-conditioned function that you want to compute, but your computational algorithm involves some intermediate step (like the computation of d
) that is badly conditioned, and screws up your calculation. The solution is usually to reformulate the calculation algebraically to avoid the ill-conditioned step, e.g. to replace d
with d√|∇ρ|
. Sometimes it requires some clever algebra, e.g. see the exp1
and quadratic-formula examples in these notes.
As described in this comment, this PR attempts to perform subpixel smoothing assuming β=∞ for all actual values of β.
From a high-level (abstract) perspective, the technique seems to work (with some important exceptions). Here is the resulting geometry (plotted by pulling the trace of the ε tensor so we can explicitly see the smoothing) for various values of β:
β=2
β=64
β=512
β=∞
There are still some artifacts that need to be resolved for lower values of β, but it's clear that at least the routine is doing averaging across the boundries.
There is also a bug with the recombination step (while calculating the gradient). There are occasions when
get_front_object
does not setmat_behind
. This never happens during the forward/adjoint runs -- which is unexpected as the same "routines" are used between these two cases, so we shouldn't see different behavior...Of course, we still need to perform convergence tests, gradient tests, etc.
Incorporates & closes #1399.