-
Notifications
You must be signed in to change notification settings - Fork 19
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
Add minimum linewidth constraint to waveguide mode converter topology optimization using Meep #27
Conversation
CSV file of design to check against ruler. |
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.
Would be good to split up the transmission and reflection as separate objective functions, as we discussed. Should be trivial since you are already doing a minimax.
I think the most important thing, however, will be fixing the dummy parameter update.
lb = np.insert(lb, 0, 0) # lower bound: cannot be less than 0 | ||
ub = np.insert(ub, 0, 2) # upper bound: cannot be more than 2 | ||
x = np.insert(x, 0, 1.2) # initial guess for the worst error | ||
lb = np.insert(lb, 0, 0.) # lower bound: cannot be less than 0. |
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.
Don't put any bounds on the dummy parameter. As @stevengj has said before, just let the optimizer do it's thing (especially during the inner iterations).
solver.add_inequality_mconstraint( | ||
lambda r, x, g: glc(r, x, g, beta), | ||
[1e-8] * opt.nf, | ||
) | ||
x[:] = solver.optimize(x) |
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.
So we really need to recalibrate the dummy parameter (t
) before starting the optimization. You can see the optimizer is spending most of its time adjusting this during the second epoch.
You can do one of three things (in order of both increasing difficulty and increased expected performance):
- Reset
t
to the same default at the start of each epoch. Heuristically, it's much easier for the optimizer to bring it down (and all the constraints are satisfied) than to bring it up (where none of the constraints are satisfied). - Run a single forward run before starting each new optimization epoch, and manually set
t
as the maximum value. - Modify the CCSA algorithm to update
t
analytically (as described in the original Svanberg paper).
@@ -37,13 +39,14 @@ | |||
wvls = (1.265, 1.270, 1.275, 1.285, 1.290, 1.295) | |||
frqs = [1/wvl for wvl in wvls] | |||
|
|||
minimum_length = 0.09 # minimum length scale (μm) | |||
minimum_length = 0.05 # minimum length scale (μm) |
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.
Did you drop the minimum feature size because this is what's done in the other paper and you want to be consistent? You should still get great results with 90 nm.
The minimum length scale estimated by ruler is 0.0625. |
Thanks again for your additional suggestions, @smartalecH. I did the following: (1) removed the constraints on the epigraph variable Here are two designs generated using these modifications. Unfortunately, the final performance has gotten worse. The convergence history shows that during the start of the final epoch when the minimum linewidth constraint is applied, the performance gets significantly worse and does not change throughout the epoch. I wonder whether I need to keep adjusting the hyperparameter # insert epigraph variable and _no_ bounds as first element of 1d arrays
x = np.insert(x, 0, 1.2) # initial guess for the worst error (ignored)
lb = np.insert(lb, 0, -np.inf)
ub = np.insert(ub, 0, np.inf)
evaluation_history = []
cur_iter = [0]
betas = [8, 16, 32]
max_eval = 50
tol = np.array([1e-6] * opt.nf)
for beta in betas:
solver = nlopt.opt(algorithm, n + 1)
solver.set_lower_bounds(lb)
solver.set_upper_bounds(ub)
solver.set_min_objective(f)
solver.set_maxeval(max_eval)
solver.add_inequality_mconstraint(
lambda r, x, g: c(r, x, g, eta_i, beta),
tol,
)
# apply the minimum linewidth constraint
# only in the final "epoch"
if beta == betas[-1]:
solver.add_inequality_mconstraint(
lambda r, x, g: glc(r, x, g, beta),
[1e-8] * opt.nf,
)
# run a single forward run before each epoch
# and manually set the epigraph variable to
# the largest value of the objective function
# over the six wavelengths
t0 = opt(
[mapping(x[1:], eta_i, beta)],
need_gradient=False,
)
x[0] = np.amax(t0[0])
x[:] = solver.optimize(x) 1. 1. |
We're getting closer... So something now seems wrong with that first epoch. It shouldn't take 40+ iterations for it to start making progress on the FOM... Are you sure you are pulling the right value for the dummy parameter ( An easy way to check what's going on is to print the value of But the second epoch is looking better.
This is classic behavior that the hyperparameters aren't sufficiently chosen. The optimizer is struggling to satisfy the GLC (i.e. the constraint is too stiff). Be sure to print the two values of the GLC constraints after each iteration. If they are still positive, then the optimizer is stuck trying to bring those down. It's useful to plot the gradient of the GLC constraints too. When you do play with
What is this error relative to the resolution? As things go subpixel, the ruler is only so accurate... |
The results of the method are affected by implementation details. I have other versions based on
The resolution is 100, which means the pixel size is 10 nm. So the error 62.5-50=12.5 nm is slightly above the size of one pixel. I think such error is not quite unexpected. |
Note that it is always better for CCSA to start with a feasible starting point (one which satisfies the constraints). (Technically, it is not guaranteed to converge otherwise, although we do attempt to minimize the constraints to reach the feasible region.) In the case of an epigraph formulation, this means that you typically want to start each epoch with a dummy variable that equals the min/max of the functions you are maximizing/minimizing. |
The range of the objective function However, what I observe in my runs is that when the bound on There does not seem to be a bug in the setup of the minimax optimization using the epigraph formulation. Yet the results from (1) and (2) are unexpected. |
This behavior does not seem to be related to the dummy variable, but related to the small and incorrect adjoint gradients. As mentioned here, the adjoint gradient is small and incorrect at the uniform structure, which is |
Like we've discussed before, the current subpixel averaging code for material grids grows increasingly ill-conditioned as the norm of the spatial gradient of the material grid approaches zero (which is the case here, when u=0.5 everywhere). We should turn off subpixel smoothing for the material grid here. You aren't taking advantage of its features anyway. |
As mentioned here, I turned off the smoothing, but the adjoint gradient was still incorrect. |
Here is a plot for a typical run of the objective function Epoch 1 and 2
Epoch 3: apply minimum linewidth constraint
|
Adds a minimum linewidth constraint to the Meep toplogy optimization of the waveguide mode converter based on A.M. Hammond et al., Optics Express, Vol. 29, pp. 23916-23938, (2021). Thanks to @smartalecH for help in setting this up.
Here is an example for a minimum linewidth of 50 nm. The feature seems to be working although more ongoing work is necessary to improve the performance of the final design.