mirror of
https://github.com/edoverg/modeconverter.git
synced 2026-10-14 23:06:39 +00:00
repository update. Added metaprop_fibermode_doublet_sym.py which implements a constrained optimization enforcing mirror symmetry about the x and y axes. Mirror symmetry can be exploited in this problem since the target mode has such symmetry. Each metasurface has effectively a quarter of the degrees of freedom compared to the unconstrained problem.
This commit is contained in:
@@ -26,19 +26,23 @@ if not os.path.exists("results"):
|
||||
##############################
|
||||
#physics and simulation domain
|
||||
wavelength = 1.55e-6
|
||||
n0 = 1.0
|
||||
n0 = 1.45
|
||||
n1 = 1.0
|
||||
n2 = 1.0
|
||||
n2 = 1.45
|
||||
|
||||
k0 = 2 * np.pi / wavelength
|
||||
k1 = k0
|
||||
k2 = k0
|
||||
lda0 = wavelength / n0
|
||||
lda1 = wavelength / n1
|
||||
lda2 = wavelength / n2
|
||||
|
||||
opt_max_eval = 250
|
||||
k0 = 2 * np.pi / lda0
|
||||
k1 = 2 * np.pi / lda1
|
||||
k2 = 2 * np.pi / lda2
|
||||
|
||||
opt_max_eval = 150
|
||||
|
||||
unit_cell_pitch = 700e-9 #equivalent to spatial sampling
|
||||
|
||||
Nx = 2048 #pixels per dimension
|
||||
Nx = 1024 #pixels per dimension
|
||||
Ny = Nx
|
||||
S = Nx * Ny
|
||||
|
||||
@@ -53,10 +57,9 @@ rho = np.sqrt(X**2 + Y**2)
|
||||
|
||||
sampling_period = xs[1] - xs[0]
|
||||
|
||||
d0 = 300e-6 #propagation distance: source - MS1
|
||||
d1 = 500e-6 #propagation distance: MS1 - MS2
|
||||
d2 = 400e-6 #propagation distance: MS2 - target
|
||||
d = [d1, d2] #d = [d1,d2] d1:distance MS1-MS2, d2: distance MS2-target
|
||||
d0 = 403e-6 #propagation distance: source - MS1
|
||||
d1 = 158e-6 #propagation distance: MS1 - MS2
|
||||
d2 = d0 #propagation distance: MS2 - target
|
||||
|
||||
phase_min = 0
|
||||
phase_max = 2 * np.pi
|
||||
@@ -88,13 +91,13 @@ KX, KY = np.meshgrid(kappas, kappas)
|
||||
K_parallel = np.sqrt(KX**2 + KY**2)
|
||||
nu_parallel = K_parallel / (2 * np.pi)
|
||||
|
||||
phase_factor_0 = k0 * d0 * np.sqrt(1 - (wavelength * nu_parallel) ** 2 + 0*1j)
|
||||
phase_factor_0 = k0 * d0 * np.sqrt(1 - (lda0 * nu_parallel) ** 2 + 0*1j)
|
||||
P_0 = np.exp(1j * phase_factor_0)
|
||||
P_0_nat = np.fft.ifftshift(P_0)
|
||||
phase_factor_1 = k0 * d1 * np.sqrt(1 - (wavelength * nu_parallel) ** 2 + 0*1j)
|
||||
phase_factor_1 = k1 * d1 * np.sqrt(1 - (lda1 * nu_parallel) ** 2 + 0*1j)
|
||||
P_1 = np.exp(1j * phase_factor_1)
|
||||
P_1_nat = np.fft.ifftshift(P_1)
|
||||
phase_factor_2 = k0 * d2 * np.sqrt(1 - (wavelength * nu_parallel) ** 2 + 0*1j)
|
||||
phase_factor_2 = k2 * d2 * np.sqrt(1 - (lda2 * nu_parallel) ** 2 + 0*1j)
|
||||
P_2 = np.exp(1j * phase_factor_2)
|
||||
P_2_nat = np.fft.ifftshift(P_2)
|
||||
|
||||
@@ -106,7 +109,6 @@ P_2_dagger_nat = np.fft.ifftshift(P_2).T.conj()
|
||||
|
||||
##############################
|
||||
#Plot zoom parameters [um]
|
||||
#Plot zoom parameters [um]
|
||||
zoom_x = 150
|
||||
zoom_y = 150
|
||||
zoom_output_fiber_radius = 63
|
||||
@@ -146,7 +148,7 @@ def get_fiber_mode_pattern(mode_list:list)->list:
|
||||
for enum_index, n in enumerate(mode_list):
|
||||
#try to load mode index n
|
||||
try:
|
||||
pattern_file = fiber_modes_input_folder + f"/LP{n}1_field_distribution.npy"
|
||||
pattern_file = fiber_modes_input_folder + f"/LP{n}1_field_distribution_{Nx}.npy"
|
||||
pattern = np.load(pattern_file)
|
||||
output_patterns.append(pattern)
|
||||
print("Loaded fiber patterns for mode index: ", n)
|
||||
@@ -209,7 +211,7 @@ def get_fiber_mode_pattern(mode_list:list)->list:
|
||||
else:#outside core
|
||||
E_field[i,j] = kv(n, chi_cl * r) / kv(n, chi_cl * a) * np.cos(n * phi)
|
||||
#save the computed pattern to a file for future use
|
||||
pattern_file = fiber_modes_input_folder + f"/LP{n}1_field_distribution.npy"
|
||||
pattern_file = fiber_modes_input_folder + f"/LP{n}1_field_distribution_{Nx}.npy"
|
||||
np.save(pattern_file, E_field)
|
||||
output_patterns.append(E_field)
|
||||
return output_patterns
|
||||
@@ -366,6 +368,8 @@ make_2Dplot_of(source_field_2d, choose_quantity="phase", save_name="source_field
|
||||
#initialize input field (propagated source field)
|
||||
input_field = propagate_source(source_field)
|
||||
input_field_2d = input_field.reshape((Nx, Ny)) #reshape to 2D for plotting
|
||||
#save the input field to numpy array
|
||||
np.save("results/input_field.npy", input_field_2d)
|
||||
#input field integrated intensity
|
||||
input_field_intensity = np.sum(np.abs(input_field)**2)
|
||||
print("Input field integrated intensity: %.4e" % input_field_intensity)
|
||||
@@ -376,7 +380,7 @@ make_2Dplot_of(input_field_2d, choose_quantity="phase", save_name="input_field_p
|
||||
|
||||
###########################
|
||||
#initialize target field
|
||||
target_mode_indices = [1]
|
||||
target_mode_indices = [2]
|
||||
fiber_mode_pattern = get_fiber_mode_pattern(target_mode_indices)
|
||||
target_Efield_2d = fiber_mode_pattern / np.sqrt(np.sum(np.abs(fiber_mode_pattern)**2))
|
||||
target_Efield = target_Efield_2d.flatten() #normalized target field (intensity = 1)
|
||||
@@ -416,7 +420,7 @@ def forward_propagate() -> None:
|
||||
print("Starting propagating...")
|
||||
start_time = time.time()
|
||||
|
||||
fft_input_array[:,:] = (input_field * np.exp(1j * phase_mask[:S])).reshape((Nx, Ny))
|
||||
fft_input_array[:,:] = np.fft.ifftshift((input_field * np.exp(1j * phase_mask[:S])).reshape((Nx, Ny)))
|
||||
fft_operator()
|
||||
#print("BEFORE")
|
||||
fft_power = np.sum(np.abs(fft_operator.output_array)**2)/S
|
||||
@@ -434,14 +438,14 @@ def forward_propagate() -> None:
|
||||
#print("FFT power: %.4e, Propagating power: %.4e, Non-propagating power: %.4e" % (fft_power, propagating_power, fft_power - propagating_power))
|
||||
|
||||
ifft_operator()
|
||||
intermediate_fields[0] = ifft_operator.output_array.copy()
|
||||
intermediate_fields[0] = np.fft.fftshift(ifft_operator.output_array.copy())
|
||||
print("Field intensity after first phase mask and propagation: %.4e" % np.sum(np.abs(intermediate_fields[0])**2))
|
||||
|
||||
fft_input_array[:,:] = (ifft_operator.output_array * np.exp(1j * phase_mask[S:].reshape((Nx, Ny))))
|
||||
fft_input_array[:,:] = np.fft.ifftshift(np.fft.fftshift(ifft_operator.output_array) * np.exp(1j * phase_mask[S:].reshape((Nx, Ny))))
|
||||
fft_operator()
|
||||
ifft_input_array[:,:] = fft_operator.output_array * P_2_nat #element wise product
|
||||
ifft_operator()
|
||||
intermediate_fields[1] = ifft_operator.output_array.copy()
|
||||
intermediate_fields[1] = np.fft.fftshift(ifft_operator.output_array.copy())
|
||||
|
||||
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)
|
||||
@@ -462,19 +466,19 @@ def adjoint_propagate():
|
||||
Phi_2_dagger = np.exp(-1j * phase_mask[S:]).reshape((Nx, Ny))
|
||||
|
||||
#compute the adjoint source
|
||||
fft_input_array[:,:] = np.sum(intermediate_fields[1] * np.conj(target_Efield_2d)) * target_Efield_2d
|
||||
fft_input_array[:,:] = np.fft.ifftshift(np.sum(intermediate_fields[1] * np.conj(target_Efield_2d)) * target_Efield_2d)
|
||||
fft_operator()
|
||||
ifft_input_array[:,:] = fft_operator.output_array * P_2_dagger_nat
|
||||
ifft_operator()
|
||||
fft_input_array[:,:] = ifft_operator.output_array * Phi_2_dagger
|
||||
fft_input_array[:,:] = np.fft.ifftshift(np.fft.fftshift(ifft_operator.output_array) * Phi_2_dagger)
|
||||
|
||||
grad_C_phi_2 = 2 * np.real(-1j * intermediate_fields[0].conj() * fft_input_array).flatten()
|
||||
grad_C_phi_2 = 2 * np.real(-1j * intermediate_fields[0].conj() * np.fft.fftshift(fft_input_array)).flatten()
|
||||
|
||||
fft_operator()
|
||||
ifft_input_array[:,:] = fft_operator.output_array * P_1_dagger_nat #element wise
|
||||
ifft_operator()
|
||||
|
||||
grad_C_phi_1 = 2 * np.real(-1j * input_field_2d.conj() * ifft_operator.output_array * Phi_1_dagger).flatten()
|
||||
grad_C_phi_1 = 2 * np.real(-1j * input_field_2d.conj() * np.fft.fftshift(ifft_operator.output_array) * Phi_1_dagger).flatten()
|
||||
|
||||
end_time = time.time()
|
||||
print("Adjoint finished in {:.6f} seconds.".format(end_time - start_time))
|
||||
@@ -494,6 +498,7 @@ def _compute_cost():
|
||||
return C_t
|
||||
|
||||
opt_history = []
|
||||
iter_num = [0]
|
||||
def cost_fun(x, grad):
|
||||
''' Cost function to be minimized.
|
||||
Args:
|
||||
@@ -509,15 +514,16 @@ def cost_fun(x, grad):
|
||||
C = _compute_cost()
|
||||
|
||||
opt_history.append(C)
|
||||
|
||||
plt.figure()
|
||||
plt.plot(opt_history)
|
||||
plt.xlabel('Iteration')
|
||||
plt.ylabel('Cost Function Value')
|
||||
plt.title('Optimization History')
|
||||
plt.tight_layout()
|
||||
plt.savefig("results/modeconv1_optimization_history.pdf")
|
||||
plt.close()
|
||||
iter_num[0] += 1
|
||||
if iter_num[0] % 10 == 0:
|
||||
plt.figure()
|
||||
plt.plot(opt_history)
|
||||
plt.xlabel('Iteration')
|
||||
plt.ylabel('Cost Function Value')
|
||||
plt.title('Optimization History')
|
||||
plt.tight_layout()
|
||||
plt.savefig("results/optimization_history.pdf")
|
||||
plt.close()
|
||||
|
||||
if grad.size > 0:
|
||||
grad[:] = adjoint_propagate() * 2 * np.pi
|
||||
@@ -539,10 +545,11 @@ if __name__ == "__main__":
|
||||
if start:
|
||||
w_norm[:] = solver.optimize(w_norm)
|
||||
print("Optimization completed.")
|
||||
|
||||
print("LAST OPTIMUM VALUE: ", solver.last_optimum_value())
|
||||
#verify the results
|
||||
phase_mask = phase_given_w(w_norm)
|
||||
forward_propagate()
|
||||
field_input_ms2 = intermediate_fields[0]
|
||||
output_field = intermediate_fields[-1]
|
||||
|
||||
input_power = np.sum(np.abs(input_field)**2)
|
||||
@@ -552,9 +559,10 @@ if __name__ == "__main__":
|
||||
print("Norm. output power: %.4e" % output_power)
|
||||
|
||||
#save the output field for later use
|
||||
save_output_field = False
|
||||
save_output_field = True
|
||||
if save_output_field:
|
||||
np.savetxt("results/optimized_output_field.txt", output_field.reshape((Nx, Ny)))
|
||||
np.save("results/intermediate_field_0.npy", intermediate_fields[0].reshape((Nx, Ny)))
|
||||
np.save("results/optimized_output_field.npy", output_field.reshape((Nx, Ny)))
|
||||
# Plot results in individual PDF figures
|
||||
|
||||
# Reshape fields to 2D for visualization
|
||||
@@ -580,7 +588,7 @@ if __name__ == "__main__":
|
||||
#save the phase masks for later use
|
||||
savePhase_masks = True
|
||||
if savePhase_masks:
|
||||
np.savetxt("results/optimized_phase_mask_1.txt", phase_mask_1)
|
||||
np.savetxt("results/optimized_phase_mask_2.txt", phase_mask_2)
|
||||
np.save("results/optimized_phase_mask_1.npy", phase_mask_1)
|
||||
np.save("results/optimized_phase_mask_2.npy", phase_mask_2)
|
||||
|
||||
print("All plots saved to results/ folder.")
|
||||
Reference in New Issue
Block a user