mirror of
https://github.com/edoverg/modeconverter.git
synced 2026-10-14 23:06:39 +00:00
improved handling of mirror-symmetry boundary conditions
This commit is contained in:
@@ -24,8 +24,6 @@ save_folder = "results_sym"
|
|||||||
if not os.path.exists(save_folder):
|
if not os.path.exists(save_folder):
|
||||||
os.makedirs(save_folder)
|
os.makedirs(save_folder)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
#physics and simulation domain
|
#physics and simulation domain
|
||||||
wavelength = 1.55e-6
|
wavelength = 1.55e-6
|
||||||
@@ -45,23 +43,38 @@ opt_max_eval = 150
|
|||||||
|
|
||||||
unit_cell_pitch = 700e-9 #equivalent to spatial sampling
|
unit_cell_pitch = 700e-9 #equivalent to spatial sampling
|
||||||
|
|
||||||
Nx = 1024 #pixels per dimension
|
Nx = 1025 #pixels per dimension
|
||||||
Ny = Nx
|
Ny = Nx
|
||||||
|
nx = (Nx - 1) // 2 #indices without axes (= #512)
|
||||||
|
ny = (Ny - 1) // 2
|
||||||
|
dof = 2*((nx + 1)**2) #number of degrees of freedom per metasurface
|
||||||
S = Nx * Ny
|
S = Nx * Ny
|
||||||
|
|
||||||
size_x = unit_cell_pitch * Nx #actual physical size
|
size_x = unit_cell_pitch * Nx #actual physical size
|
||||||
size_y = unit_cell_pitch * Ny
|
size_y = unit_cell_pitch * Ny
|
||||||
|
|
||||||
xs = np.linspace(-size_x/2, size_x/2 - size_x / Nx, Nx)
|
xs = np.arange(-Nx//2+1, Nx//2+1) * (size_x / Nx)
|
||||||
ys = np.linspace(-size_y/2, size_y/2 - size_y / Ny, Ny)
|
ys = np.arange(-Ny//2+1, Ny//2+1) * (size_y / Ny)
|
||||||
|
|
||||||
|
#important checks on spatial grid
|
||||||
|
try:
|
||||||
|
assert(len(xs) == Nx)
|
||||||
|
assert(len(ys) == Ny)
|
||||||
|
assert(xs[0] == xs[-1]*-1)
|
||||||
|
assert(ys[0] == ys[-1]*-1)
|
||||||
|
assert(xs[nx]==0)
|
||||||
|
assert(ys[ny]==0)
|
||||||
|
assert(np.abs(np.abs(xs[1]-xs[0]) - unit_cell_pitch) < 1e-12)
|
||||||
|
except:
|
||||||
|
raise ValueError("Error while setting up the spatial grid.")
|
||||||
|
|
||||||
X, Y = np.meshgrid(xs, ys)
|
X, Y = np.meshgrid(xs, ys)
|
||||||
rho = np.sqrt(X**2 + Y**2)
|
rho = np.sqrt(X**2 + Y**2)
|
||||||
|
|
||||||
sampling_period = xs[1] - xs[0]
|
sampling_period = xs[1] - xs[0]
|
||||||
|
|
||||||
d0 = 403e-6 #propagation distance: source - MS1
|
d0 = 535e-6 #propagation distance: source - MS1
|
||||||
d1 = 158e-6 #propagation distance: MS1 - MS2
|
d1 = 415e-6 #propagation distance: MS1 - MS2
|
||||||
d2 = d0 #propagation distance: MS2 - target
|
d2 = d0 #propagation distance: MS2 - target
|
||||||
|
|
||||||
phase_min = 0
|
phase_min = 0
|
||||||
@@ -356,28 +369,37 @@ def make_polarPlot_of(given_field,choose_quantity="amplitude",save_name="polar_p
|
|||||||
cbar = plt.colorbar(im, ax=ax)
|
cbar = plt.colorbar(im, ax=ax)
|
||||||
plt.savefig(f"{save_folder}/" + save_name + ".pdf")
|
plt.savefig(f"{save_folder}/" + save_name + ".pdf")
|
||||||
|
|
||||||
def symmetrize(w, nx, ny):
|
def symmetrize(w):
|
||||||
'''
|
'''
|
||||||
Takes an input array, turns it into a 2D array, and symmetrizes it along the x and y axes.
|
Takes an input array, which must be full in size, turns it into a 2D array, and symmetrizes it along the x and y axes.
|
||||||
Returns the first quadrant of shape (nx,ny), flattened
|
Returns the first quadrant of shape (nx,ny), including the zero axis, flattened
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
w_2d = w.reshape((Nx, Ny))
|
w_2d = w.reshape((Nx, Ny))
|
||||||
except ValueError:
|
except:
|
||||||
raise ValueError(f"Input array w cannot be reshaped to ({Nx}, {Ny}). Current shape: {w.shape}")
|
raise ValueError(f"Input array w cannot be reshaped to ({Nx}, {Ny}). Current shape: {w.shape}")
|
||||||
w_symm = np.zeros((nx, ny))
|
|
||||||
w_symm[:,:] = w_2d[:nx, :ny]
|
w_symm = np.zeros((nx+1, ny+1))
|
||||||
|
w_symm[:,:] = w_2d[:nx+1, :ny+1]
|
||||||
|
|
||||||
return w_symm.flatten()
|
return w_symm.flatten()
|
||||||
|
|
||||||
def expand_symmetrize(w_symm, nx, ny):
|
def expand_symmetrize(w_symm):
|
||||||
'''
|
'''
|
||||||
Takes a symmetrized array of shape (nx, ny) and expands it to a full 2D array of shape (Nx, Ny) by mirroring, flattened.
|
Takes a array of shape (nx+1, ny+1), representing the first quadrant, including zero axes, and
|
||||||
|
expands it to a full 2D array of shape (Nx, Ny) by mirroring, flattened.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
w_symm_2d = w_symm.reshape((nx+1, ny+1))
|
||||||
|
|
||||||
w_full = np.zeros((Nx, Ny))
|
w_full = np.zeros((Nx, Ny))
|
||||||
w_full[:nx, :ny] = w_symm.reshape((nx, ny))
|
|
||||||
w_full[:nx, ny:] = np.flip(w_symm.reshape((nx, ny)), axis=1)
|
w_full[:nx+1, :ny+1] = w_symm_2d
|
||||||
w_full[nx:, :ny] = np.flip(w_symm.reshape((nx, ny)), axis=0)
|
w_full[:nx+1, ny+1:] = np.flip(w_full[:nx+1, :ny], axis=1)
|
||||||
w_full[nx:, ny:] = np.flip(np.flip(w_symm.reshape((nx, ny)), axis=0), axis=1)
|
w_full[nx+1:, :ny+1] = np.flip(w_full[:nx, :ny+1], axis=0)
|
||||||
|
w_full[nx+1:, ny+1:] = np.flip(np.flip(w_full[:nx, :ny], axis=1), axis=0)
|
||||||
|
w_full[nx, ny] = w_symm_2d[-1, -1]
|
||||||
|
|
||||||
return w_full.flatten()
|
return w_full.flatten()
|
||||||
|
|
||||||
###########################
|
###########################
|
||||||
@@ -421,15 +443,15 @@ make_polarPlot_of(target_Efield_2d, choose_quantity="phase", save_name="target_f
|
|||||||
###########################
|
###########################
|
||||||
#initialize the normalized phase masks and global phase mask
|
#initialize the normalized phase masks and global phase mask
|
||||||
w_norm = np.zeros((2*S)) #concatenated normalized parameters for both masks
|
w_norm = np.zeros((2*S)) #concatenated normalized parameters for both masks
|
||||||
X, Y = np.meshgrid(np.linspace(-size_x/2, size_x/2, Nx), np.linspace(-size_y/2, size_y/2, Ny))
|
|
||||||
rho = np.sqrt(X**2 + Y**2)
|
rho = np.sqrt(X**2 + Y**2)
|
||||||
circular_mask_1 = np.where(rho.flatten() < output_fiber_core_diameter/3, 1, 0)
|
circular_mask_1 = np.where(rho.flatten() < output_fiber_core_diameter/3, 1, 0)
|
||||||
#w_norm[:S] += circular_mask_1
|
#w_norm[:S] += circular_mask_1
|
||||||
target_pattern_phase = (np.angle(target_Efield) + 2 * np.pi) % (2 * np.pi)*circular_mask_1 #make sure the phase is between 0 and 2pi
|
target_pattern_phase = (np.angle(target_Efield) + 2 * np.pi) % (2 * np.pi)*circular_mask_1 #make sure the phase is between 0 and 2pi
|
||||||
w_norm[S:] += target_pattern_phase / (2 * np.pi) #normalize the target phase to be between 0 and 1
|
w_norm[S:] += target_pattern_phase / (2 * np.pi) #normalize the target phase to be between 0 and 1
|
||||||
|
|
||||||
w_norm_symm_1 = symmetrize(w_norm[:S], Nx//2, Ny//2)
|
w_norm_symm_1 = symmetrize(w_norm[:S])
|
||||||
w_norm_symm_2 = symmetrize(w_norm[S:], Nx//2, Ny//2)
|
w_norm_symm_2 = symmetrize(w_norm[S:])
|
||||||
|
|
||||||
w_norm_symm = np.concatenate((w_norm_symm_1, w_norm_symm_2), axis=0)
|
w_norm_symm = np.concatenate((w_norm_symm_1, w_norm_symm_2), axis=0)
|
||||||
|
|
||||||
@@ -441,7 +463,7 @@ norm_phase_min = 0
|
|||||||
norm_phase_max = 1
|
norm_phase_max = 1
|
||||||
mask_buffer = 100e-6
|
mask_buffer = 100e-6
|
||||||
mask_upper_bounds = norm_phase_max * np.where((np.abs(X.flatten()) > size_x/2 - mask_buffer) | (np.abs(Y.flatten()) > size_y/2 - mask_buffer), 0, 1)
|
mask_upper_bounds = norm_phase_max * np.where((np.abs(X.flatten()) > size_x/2 - mask_buffer) | (np.abs(Y.flatten()) > size_y/2 - mask_buffer), 0, 1)
|
||||||
mask_upper_bounds_symm = mask_upper_bounds.reshape((Nx,Ny))[:Nx//2, :Ny//2].flatten()
|
mask_upper_bounds_symm = mask_upper_bounds.reshape((Nx,Ny))[:nx+1, :ny+1].flatten()
|
||||||
|
|
||||||
mask_upper_bounds_symm = np.concatenate((mask_upper_bounds_symm, mask_upper_bounds_symm),axis=0)
|
mask_upper_bounds_symm = np.concatenate((mask_upper_bounds_symm, mask_upper_bounds_symm),axis=0)
|
||||||
mask_lower_bounds_symm = np.zeros_like(mask_upper_bounds_symm)
|
mask_lower_bounds_symm = np.zeros_like(mask_upper_bounds_symm)
|
||||||
@@ -485,23 +507,25 @@ def forward_propagate() -> None:
|
|||||||
intermediate_fields[1] = np.fft.fftshift(ifft_operator.output_array.copy())
|
intermediate_fields[1] = np.fft.fftshift(ifft_operator.output_array.copy())
|
||||||
|
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
make_2Dplot_of(intermediate_fields[0], choose_quantity="amplitude", save_name="intermediate_field_1_amplitude", plot_zoom_x=full_view_x, plot_zoom_y=full_view_y)
|
#make_2Dplot_of(intermediate_fields[0], choose_quantity="amplitude", save_name="intermediate_field_1_amplitude", plot_zoom_x=full_view_x, plot_zoom_y=full_view_y)
|
||||||
make_2Dplot_of(intermediate_fields[1], choose_quantity="amplitude", save_name="intermediate_field_2_amplitude", plot_zoom_x=full_view_x, plot_zoom_y=full_view_y)
|
#make_2Dplot_of(intermediate_fields[1], choose_quantity="amplitude", save_name="intermediate_field_2_amplitude", plot_zoom_x=full_view_x, plot_zoom_y=full_view_y)
|
||||||
|
|
||||||
print("Propagation finished in {:.6f} seconds.".format(end_time - start_time))
|
print("Propagation finished in {:.6f} seconds.".format(end_time - start_time))
|
||||||
|
|
||||||
|
|
||||||
def reduce_symmetrized_gradient(g_full, nx, ny):
|
def reduce_symmetrized_gradient(g_full):
|
||||||
g_2d = g_full.reshape((Nx, Ny))
|
g_2d = g_full.reshape((Nx, Ny))
|
||||||
|
|
||||||
g_reduced = (
|
g_reduced = g_2d[:nx+1, :ny+1]
|
||||||
g_2d[:nx, :ny]
|
third = g_2d[nx+1:,:ny+1]
|
||||||
+ np.flip(g_2d[:nx, ny:], axis=1)
|
second = g_2d[:nx+1, ny+1:]
|
||||||
+ np.flip(g_2d[nx:, :ny], axis=0)
|
fourth = g_2d[nx+1:, ny+1:]
|
||||||
+ np.flip(np.flip(g_2d[nx:, ny:], axis=0), axis=1)
|
|
||||||
)
|
|
||||||
|
|
||||||
return g_reduced.flatten()
|
g_2d[:nx, ny+1:] += np.flip(fourth, axis = 0)
|
||||||
|
g_2d[:nx, :ny+1] += np.flip(third, axis = 0)
|
||||||
|
g_2d[:nx+1, :ny] += np.flip(second, axis = 1)
|
||||||
|
|
||||||
|
return g_2d[:nx+1, :ny+1].flatten()
|
||||||
|
|
||||||
def adjoint_propagate():
|
def adjoint_propagate():
|
||||||
'''Backpropagates the output field using the adjoint of the propagation matrix P.
|
'''Backpropagates the output field using the adjoint of the propagation matrix P.
|
||||||
@@ -560,16 +584,13 @@ def cost_fun(x, grad):
|
|||||||
#since the objective function is to maximize the overlap, and the target mode is C4 symmetric,
|
#since the objective function is to maximize the overlap, and the target mode is C4 symmetric,
|
||||||
#we can let the algorithm optimize only a fourth of the phase mask and then synthetize automatically the remaining three quadrants.
|
#we can let the algorithm optimize only a fourth of the phase mask and then synthetize automatically the remaining three quadrants.
|
||||||
#Since we have two metasurfaces, the total number of parameters the algorithm needs to optimize is S/2 (S/4 for each metasurface)
|
#Since we have two metasurfaces, the total number of parameters the algorithm needs to optimize is S/2 (S/4 for each metasurface)
|
||||||
ms1_first_quadrant = x[:S//4].reshape(Nx//2, Ny//2)
|
ms1_first_quadrant = x[:dof//2].reshape(nx+1, ny+1)
|
||||||
ms2_first_quadrant = x[S//4:].reshape(Nx//2, Ny//2)
|
ms2_first_quadrant = x[dof//2:].reshape(nx+1, ny+1)
|
||||||
|
|
||||||
ms1_first_second = np.concatenate((ms1_first_quadrant, np.flip(ms1_first_quadrant, axis=1)), axis=1)
|
ms1 = expand_symmetrize(ms1_first_quadrant)
|
||||||
ms1 = np.concatenate((ms1_first_second, np.flip(ms1_first_second, axis=0)), axis=0)
|
ms2 = expand_symmetrize(ms2_first_quadrant)
|
||||||
|
|
||||||
ms2_first_second = np.concatenate((ms2_first_quadrant, np.flip(ms2_first_quadrant, axis=1)), axis=1)
|
all_ms = np.concatenate((ms1, ms2))
|
||||||
ms2 = np.concatenate((ms2_first_second, np.flip(ms2_first_second, axis=0)), axis=0)
|
|
||||||
|
|
||||||
all_ms = np.concatenate((ms1.flatten(), ms2.flatten()))
|
|
||||||
|
|
||||||
w_norm[:] = all_ms
|
w_norm[:] = all_ms
|
||||||
|
|
||||||
@@ -592,15 +613,15 @@ def cost_fun(x, grad):
|
|||||||
|
|
||||||
if grad.size > 0:
|
if grad.size > 0:
|
||||||
full_grad1, full_grad2 = adjoint_propagate()
|
full_grad1, full_grad2 = adjoint_propagate()
|
||||||
reduced_grad1 = reduce_symmetrized_gradient(full_grad1, Nx//2, Ny//2)
|
reduced_grad1 = reduce_symmetrized_gradient(full_grad1)
|
||||||
reduced_grad2 = reduce_symmetrized_gradient(full_grad2, Nx//2, Ny//2)
|
reduced_grad2 = reduce_symmetrized_gradient(full_grad2)
|
||||||
reduced_grads = np.concatenate((reduced_grad1, reduced_grad2), axis=0)
|
reduced_grads = np.concatenate((reduced_grad1, reduced_grad2), axis=0)
|
||||||
grad[:] = reduced_grads * 2 * np.pi
|
grad[:] = reduced_grads * 2 * np.pi
|
||||||
return C
|
return C
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
#initialize nlopt solver
|
#initialize nlopt solver
|
||||||
solver = nlopt.opt(nlopt.LD_CCSAQ, S//2)
|
solver = nlopt.opt(nlopt.LD_CCSAQ, dof)
|
||||||
|
|
||||||
solver.set_lower_bounds(mask_lower_bounds_symm.flatten())
|
solver.set_lower_bounds(mask_lower_bounds_symm.flatten())
|
||||||
solver.set_upper_bounds(mask_upper_bounds_symm.flatten())
|
solver.set_upper_bounds(mask_upper_bounds_symm.flatten())
|
||||||
@@ -614,8 +635,8 @@ if __name__ == "__main__":
|
|||||||
start = True
|
start = True
|
||||||
if start:
|
if start:
|
||||||
w_norm_symm[:] = solver.optimize(w_norm_symm)
|
w_norm_symm[:] = solver.optimize(w_norm_symm)
|
||||||
w_norm[:S] = expand_symmetrize(w_norm_symm[:S//4], Nx//2, Ny//2)
|
w_norm[:S] = expand_symmetrize(w_norm_symm[:dof//2])
|
||||||
w_norm[S:] = expand_symmetrize(w_norm_symm[S//4:], Nx//2, Ny//2)
|
w_norm[S:] = expand_symmetrize(w_norm_symm[dof//2:])
|
||||||
|
|
||||||
print("Optimization completed.")
|
print("Optimization completed.")
|
||||||
print("LAST OPTIMUM VALUE: ", solver.last_optimum_value())
|
print("LAST OPTIMUM VALUE: ", solver.last_optimum_value())
|
||||||
|
|||||||
Reference in New Issue
Block a user