added mpid support for three layer stacks. Added sorter_to_gauss as example usage of the three stack.

This commit is contained in:
Edoardo Vergerio
2026-10-04 15:33:04 +02:00
parent 31859271da
commit bb2544954e
5 changed files with 800 additions and 365 deletions
+367 -57
View File
@@ -75,18 +75,37 @@ class SpaceGrid:
Spatial dimensions are intended to be measured in micrometers [um]. Spatial dimensions are intended to be measured in micrometers [um].
''' '''
def __init__(self, side_pixels: int, unit_cell_pitch: float, materials_refr_index: np.ndarray, prop_distances: np.ndarray = None, xy_symmetry=False): def __init__(self, side_pixels: int, unit_cell_pitch: float, materials_refr_index: np.ndarray, prop_distances: np.ndarray = None, xy_symmetry=False):
if prop_distances is None:
self.propagation_distances_set = False
self.d = None
else:
self.propagation_distances_set = True
self.d = prop_distances * 1e-6
self.materials_refr_index = materials_refr_index
self.num_phase_masks = 0
if self.propagation_distances_set:
self.num_phase_masks = len(self.d) - 1
#determine the number of phase masks required based on the number of propagation distances
self.num_phase_masks = 0
if self.propagation_distances_set:
self.num_phase_masks = len(self.d) - 1
self.Nx = side_pixels#for the moment we consider only square grids self.Nx = side_pixels#for the moment we consider only square grids
self.Ny = side_pixels self.Ny = side_pixels
self.unit_cell_pitch = unit_cell_pitch * 1e-6 self.unit_cell_pitch = unit_cell_pitch * 1e-6 # convert to [m]
self.nx = (self.Nx - 1) // 2 self.nx = (self.Nx - 1) // 2
self.ny = (self.Ny - 1) // 2 self.ny = (self.Ny - 1) // 2
self.xy_symmetry = xy_symmetry self.xy_symmetry = xy_symmetry
self.S = self.Nx * self.Ny self.S = self.Nx * self.Ny
if self.xy_symmetry: if self.xy_symmetry:
self.dof = 2*((self.nx + 1)**2) self.dof = self.num_phase_masks * ((self.nx + 1)**2)
self.S_symm = (self.nx + 1)**2
else: else:
self.dof = 2 * self.S self.dof = self.num_phase_masks * self.S
self.size_x = self.unit_cell_pitch * self.Nx self.size_x = self.unit_cell_pitch * self.Nx
self.size_y = self.unit_cell_pitch * self.Ny self.size_y = self.unit_cell_pitch * self.Ny
@@ -96,14 +115,8 @@ class SpaceGrid:
self.rho = np.sqrt(self.X**2 + self.Y**2) self.rho = np.sqrt(self.X**2 + self.Y**2)
self.sampling_period = self.unit_cell_pitch self.sampling_period = self.unit_cell_pitch
if prop_distances is None: def get_num_phase_masks(self):
self.propagation_distances_set = False return self.num_phase_masks
self.d = None
else:
self.propagation_distances_set = True
self.d = prop_distances * 1e-6
self.materials_refr_index = materials_refr_index
def get_grid_shape(self): def get_grid_shape(self):
return (self.Nx, self.Ny) return (self.Nx, self.Ny)
@@ -121,10 +134,16 @@ class SpaceGrid:
return self.sampling_period return self.sampling_period
def get_resolution(self): def get_resolution(self):
return self.unit_cell_pitch '''
Returns the resolution of the spatial grid in micrometers [um].
'''
return self.unit_cell_pitch * 1e6 # convert to [um]
def get_unit_cell_pitch(self): def get_unit_cell_pitch(self) -> float:
return self.unit_cell_pitch '''
Returns the unit cell pitch of the spatial grid in micrometers [um].
'''
return self.unit_cell_pitch * 1e6 # convert back to [um]
def get_size_x(self): def get_size_x(self):
return self.size_x return self.size_x
@@ -150,6 +169,11 @@ class SpaceGrid:
def get_S(self): def get_S(self):
return self.S return self.S
def get_S_symm(self):
if not self.xy_symmetry:
raise ValueError("S_symm is only defined when xy_symmetry is set True")
return self.S_symm
def get_X(self): def get_X(self):
return self.X return self.X
@@ -429,6 +453,9 @@ class Simulation:
def _phase_given_w(self, input_normalized_phase): def _phase_given_w(self, input_normalized_phase):
return 2 * np.pi * input_normalized_phase return 2 * np.pi * input_normalized_phase
def _w_given_phase(self, input_phase):
return input_phase / (2 * np.pi)
def _symmetrize(self, w): def _symmetrize(self, w):
''' '''
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. 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.
@@ -467,7 +494,7 @@ class Simulation:
target_Efields = [target.get_field(space_grid=self.space_grid, flatten=True) for target in self.targets] target_Efields = [target.get_field(space_grid=self.space_grid, flatten=True) for target in self.targets]
target_centers = [target.get_center() for target in self.targets] target_centers = [target.get_center() for target in self.targets]
self.w_norm = np.zeros((2*self.space_grid.get_S())) self.w_norm = np.zeros((self.num_phase_masks*self.space_grid.get_S()))
circular_mask_source = [self._build_circular_mask(source_center, self.phase_mask_init_size) for source_center in source_centers] circular_mask_source = [self._build_circular_mask(source_center, self.phase_mask_init_size) for source_center in source_centers]
circular_mask_target = [self._build_circular_mask(target_center, self.phase_mask_init_size) for target_center in target_centers] circular_mask_target = [self._build_circular_mask(target_center, self.phase_mask_init_size) for target_center in target_centers]
@@ -483,17 +510,29 @@ class Simulation:
self.w_norm[:self.space_grid.get_S()] = ((cumulated_source_phase + 2*np.pi) % (2*np.pi)) / (2*np.pi) self.w_norm[:self.space_grid.get_S()] = ((cumulated_source_phase + 2*np.pi) % (2*np.pi)) / (2*np.pi)
#initialize mask on target side #initialize mask on target side
cumulated_target_phase = np.zeros_like(self.w_norm[self.space_grid.get_S():]) cumulated_target_phase = np.zeros(self.space_grid.get_S())
for _, target_phase in enumerate(target_pattern_phase): for _, target_phase in enumerate(target_pattern_phase):
cumulated_target_phase += target_phase / (2 * np.pi) #normalize the target phase to be between 0 and 1 cumulated_target_phase += target_phase / (2 * np.pi) #normalize the target phase to be between 0 and 1
if self.num_phase_masks ==2:
self.w_norm[self.space_grid.get_S():] = ((cumulated_target_phase + 2*np.pi) % (2*np.pi)) / (2*np.pi) self.w_norm[self.space_grid.get_S():] = ((cumulated_target_phase + 2*np.pi) % (2*np.pi)) / (2*np.pi)
elif self.num_phase_masks == 3:
self.w_norm[2*self.space_grid.get_S():] = ((cumulated_target_phase + 2*np.pi) % (2*np.pi)) / (2*np.pi)
if self.space_grid.xy_symmetry: if self.space_grid.xy_symmetry:
if self.num_phase_masks == 2:
w_norm_symm_1 = self._symmetrize(self.w_norm[:self.space_grid.get_S()]) w_norm_symm_1 = self._symmetrize(self.w_norm[:self.space_grid.get_S()])
w_norm_symm_2 = self._symmetrize(self.w_norm[self.space_grid.get_S():]) w_norm_symm_2 = self._symmetrize(self.w_norm[self.space_grid.get_S():])
elif self.num_phase_masks == 3:
w_norm_symm_1 = self._symmetrize(self.w_norm[:self.space_grid.get_S()])
w_norm_symm_2 = self._symmetrize(self.w_norm[self.space_grid.get_S():2*self.space_grid.get_S()])
w_norm_symm_3 = self._symmetrize(self.w_norm[2*self.space_grid.get_S():])
if self.num_phase_masks == 2:
self.w_norm_symm[:] = np.concatenate((w_norm_symm_1, w_norm_symm_2), axis=0) self.w_norm_symm[:] = np.concatenate((w_norm_symm_1, w_norm_symm_2), axis=0)
elif self.num_phase_masks == 3:
self.w_norm_symm[:] = np.concatenate((w_norm_symm_1, w_norm_symm_2, w_norm_symm_3), axis=0)
self.phase_mask[:] = self._phase_given_w(self.w_norm) self.phase_mask[:] = self._phase_given_w(self.w_norm)
@@ -529,10 +568,20 @@ class Simulation:
) )
return npa.arctan2(phase_mask_filtered_sin, phase_mask_filtered_cos) return npa.arctan2(phase_mask_filtered_sin, phase_mask_filtered_cos)
def _forward_propagate(self, source_object: Source): def _forward_propagate(self, source_object: Source, returns_field: bool = False):
#source field #source field
source_to_propagate = source_object.get_field(space_grid=self.space_grid) source_to_propagate = source_object.get_field(space_grid=self.space_grid)
#we retrieve the phase masks by managing the filtered phase mask array
#depending on the number of phase masks, we will split the filtered phase mask array accordingly
if self.num_phase_masks == 2:
filtered_phase_mask_1 = self.filtered_phase_mask[:self.space_grid.get_S()]
filtered_phase_mask_2 = self.filtered_phase_mask[self.space_grid.get_S():]
elif self.num_phase_masks == 3:
filtered_phase_mask_1 = self.filtered_phase_mask[:self.space_grid.get_S()]
filtered_phase_mask_2 = self.filtered_phase_mask[self.space_grid.get_S():2*self.space_grid.get_S()]
filtered_phase_mask_3 = self.filtered_phase_mask[2*self.space_grid.get_S():]
self.fft_input_array[:,:] = np.fft.ifftshift(source_to_propagate.reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))) self.fft_input_array[:,:] = np.fft.ifftshift(source_to_propagate.reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny())))
self.fft_operator() self.fft_operator()
@@ -545,7 +594,7 @@ class Simulation:
input_field = self.intermediate_electric_fields[0].flatten() input_field = self.intermediate_electric_fields[0].flatten()
self.fft_input_array[:,:] = np.fft.ifftshift((input_field * np.exp(1j * self.filtered_phase_mask[:self.space_grid.get_S()])).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))) self.fft_input_array[:,:] = np.fft.ifftshift((input_field * np.exp(1j * filtered_phase_mask_1)).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny())))
self.fft_operator() self.fft_operator()
self.ifft_input_array[:,:] = self.fft_operator.output_array * self.P_nat[1] #element wise product self.ifft_input_array[:,:] = self.fft_operator.output_array * self.P_nat[1] #element wise product
self.ifft_operator() self.ifft_operator()
@@ -555,16 +604,35 @@ class Simulation:
#print("Field intensity after first phase mask and propagation: %.4e" % np.sum(np.abs(self.intermediate_electric_fields[0])**2)) #print("Field intensity after first phase mask and propagation: %.4e" % np.sum(np.abs(self.intermediate_electric_fields[0])**2))
self.fft_input_array[:,:] = np.fft.ifftshift(np.fft.fftshift(self.ifft_operator.output_array) * np.exp(1j * self.filtered_phase_mask[self.space_grid.get_S():].reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny())))) self.fft_input_array[:,:] = np.fft.ifftshift(np.fft.fftshift(self.ifft_operator.output_array) * np.exp(1j * filtered_phase_mask_2.reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))))
self.fft_operator() self.fft_operator()
self.ifft_input_array[:,:] = self.fft_operator.output_array * self.P_nat[2] #element wise product self.ifft_input_array[:,:] = self.fft_operator.output_array * self.P_nat[2] #element wise product
self.ifft_operator() self.ifft_operator()
#output field #output field if the number of phase masks is 2, otherwise this is the input field on MS3
self.intermediate_electric_fields[2] = np.fft.fftshift(self.ifft_operator.output_array.copy()) self.intermediate_electric_fields[2] = np.fft.fftshift(self.ifft_operator.output_array.copy())
#if the number of phase masks is 2, stop here
if self.num_phase_masks == 2:
self.output_field = self.intermediate_electric_fields[-1].flatten()
if returns_field:
return self.output_field
else:
return None
self.fft_input_array[:,:] = np.fft.ifftshift(np.fft.fftshift(self.ifft_operator.output_array) * np.exp(1j * filtered_phase_mask_3.reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))))
self.fft_operator()
self.ifft_input_array[:,:] = self.fft_operator.output_array * self.P_nat[3] #element wise product
self.ifft_operator()
self.intermediate_electric_fields[3] = np.fft.fftshift(self.ifft_operator.output_array.copy())
self.output_field = self.intermediate_electric_fields[-1].flatten() self.output_field = self.intermediate_electric_fields[-1].flatten()
if returns_field:
return self.output_field
else:
return None
#make a plot of intermediate fields #make a plot of intermediate fields
#for i, field in enumerate(self.intermediate_electric_fields): #for i, field in enumerate(self.intermediate_electric_fields):
# make_2Dplot_of(field, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=60, plot_zoom_y=60, choose_quantity="amplitude", save_name=f"results/intermediate_field_{i+1}_plot") # make_2Dplot_of(field, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=60, plot_zoom_y=60, choose_quantity="amplitude", save_name=f"results/intermediate_field_{i+1}_plot")
@@ -589,25 +657,58 @@ class Simulation:
return g_2d[:self.space_grid.get_nx()+1, :self.space_grid.get_ny()+1].flatten() return g_2d[:self.space_grid.get_nx()+1, :self.space_grid.get_ny()+1].flatten()
def _adjoint_propagate(self, target): def _adjoint_propagate(self, target):
Phi_1_dagger = np.exp(-1j * self.filtered_phase_mask[:self.space_grid.get_S()]).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny())) #we need to manage the case of two or three phase masks by defining the associated filtered phase masks
Phi_2_dagger = np.exp(-1j * self.filtered_phase_mask[self.space_grid.get_S():]).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny())) if self.num_phase_masks == 2:
filtered_phase_mask_1 = self.filtered_phase_mask[:self.space_grid.get_S()]
filtered_phase_mask_2 = self.filtered_phase_mask[self.space_grid.get_S():]
elif self.num_phase_masks == 3:
filtered_phase_mask_1 = self.filtered_phase_mask[:self.space_grid.get_S()]
filtered_phase_mask_2 = self.filtered_phase_mask[self.space_grid.get_S():2*self.space_grid.get_S()]
filtered_phase_mask_3 = self.filtered_phase_mask[2*self.space_grid.get_S():]
Phi_dagger = []
Phi_1_dagger = np.exp(-1j * filtered_phase_mask_1).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))
Phi_2_dagger = np.exp(-1j * filtered_phase_mask_2).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))
Phi_dagger.append(Phi_1_dagger)
Phi_dagger.append(Phi_2_dagger)
if self.num_phase_masks == 3:
Phi_3_dagger = np.exp(-1j * filtered_phase_mask_3).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))
Phi_dagger.append(Phi_3_dagger)
grad_C_phi = [None] * self.num_phase_masks
#compute the adjoint source #compute the adjoint source
self.fft_input_array[:,:] = np.fft.ifftshift(np.sum(self.intermediate_electric_fields[2] * np.conj(target.get_field(space_grid=self.space_grid))) * target.get_field(space_grid=self.space_grid)) S = np.sum(self.intermediate_electric_fields[-1] * np.conj(target.get_field(space_grid=self.space_grid)))
self.fft_operator() a = S * target.get_field(space_grid=self.space_grid)
self.ifft_input_array[:,:] = self.fft_operator.output_array * self.P_dagger_nat[2]
self.ifft_operator()
self.fft_input_array[:,:] = np.fft.ifftshift(np.fft.fftshift(self.ifft_operator.output_array) * Phi_2_dagger)
grad_C_phi_2 = 2 * np.real(-1j * self.intermediate_electric_fields[1].conj() * np.fft.fftshift(self.fft_input_array)).flatten() self.fft_input_array[:,:] = np.fft.ifftshift(a)
self.fft_operator()
self.ifft_input_array[:,:] = self.fft_operator.output_array * self.P_dagger_nat[-1]
self.ifft_operator()
self.fft_input_array[:,:] = np.fft.ifftshift(np.fft.fftshift(self.ifft_operator.output_array) * Phi_dagger[-1])
grad_C_phi[-1] = 2 * np.real(-1j * self.intermediate_electric_fields[-2].conj() * np.fft.fftshift(self.fft_input_array)).flatten()
#grad_C_phi_2 = 2 * np.real(-1j * self.intermediate_electric_fields[1].conj() * np.fft.fftshift(self.fft_input_array)).flatten()
self.fft_operator() self.fft_operator()
self.ifft_input_array[:,:] = self.fft_operator.output_array * self.P_dagger_nat[1] #element wise self.ifft_input_array[:,:] = self.fft_operator.output_array * self.P_dagger_nat[-2] #element wise
self.ifft_operator() self.ifft_operator()
self.fft_input_array[:,:] = np.fft.ifftshift(np.fft.fftshift(self.ifft_operator.output_array) * Phi_dagger[-2])
grad_C_phi_1 = 2 * np.real(-1j * self.intermediate_electric_fields[0].conj() * np.fft.fftshift(self.ifft_operator.output_array) * Phi_1_dagger).flatten() grad_C_phi[-2] = 2 * np.real(-1j * self.intermediate_electric_fields[-3].conj() * np.fft.fftshift(self.fft_input_array)).flatten()
return grad_C_phi_1, grad_C_phi_2 if self.num_phase_masks == 2:
return grad_C_phi
self.fft_operator()
self.ifft_input_array[:,:] = self.fft_operator.output_array * self.P_dagger_nat[-3] #element wise
self.ifft_operator()
self.fft_input_array[:,:] = np.fft.ifftshift(np.fft.fftshift(self.ifft_operator.output_array) * Phi_dagger[-3])
grad_C_phi[-3] = 2 * np.real(-1j * self.intermediate_electric_fields[-4].conj() * np.fft.fftshift(self.fft_input_array)).flatten()
return grad_C_phi
#return grad_C_phi_1, grad_C_phi_2
def _value_and_grad(self, target): def _value_and_grad(self, target):
''' '''
@@ -621,9 +722,9 @@ class Simulation:
self._forward_propagate(source) self._forward_propagate(source)
C_s = np.real(np.abs(np.sum(self.output_field * np.conj(target.get_field(space_grid=self.space_grid, flatten=True)))) ** 2) C_s = np.real(np.abs(np.sum(self.output_field * np.conj(target.get_field(space_grid=self.space_grid, flatten=True)))) ** 2)
grad_C_phi_1, grad_C_phi_2 = self._adjoint_propagate(target) grads = self._adjoint_propagate(target)
return C_s, grad_C_phi_1, grad_C_phi_2 return C_s, grads
def _expand_symmetrize(self, w_symm): def _expand_symmetrize(self, w_symm):
''' '''
@@ -661,28 +762,52 @@ class Simulation:
print("evaluating epigraph constraint") print("evaluating epigraph constraint")
epigraph = epigraph_and_weights[0] epigraph = epigraph_and_weights[0]
x = epigraph_and_weights[1:] x = epigraph_and_weights[1:]
#we need to manage the number of phase masks, depending on the current object instance
if self.space_grid.xy_symmetry: if self.space_grid.xy_symmetry:
ms1_first_quadrant = x[:self.space_grid.get_dof()//2].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1) if self.num_phase_masks == 2:
ms2_first_quadrant = x[self.space_grid.get_dof()//2:].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1) ms1_first_quadrant = x[:self.space_grid.get_S_symm()].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1)
ms2_first_quadrant = x[self.space_grid.get_S_symm():].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1)
ms1 = self._expand_symmetrize(ms1_first_quadrant) ms1 = self._expand_symmetrize(ms1_first_quadrant)
ms2 = self._expand_symmetrize(ms2_first_quadrant) ms2 = self._expand_symmetrize(ms2_first_quadrant)
elif self.num_phase_masks == 3:
ms1_first_quadrant = x[:self.space_grid.get_S_symm()].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1)
ms2_first_quadrant = x[self.space_grid.get_S_symm(): 2*self.space_grid.get_S_symm()].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1)
ms3_first_quadrant = x[2*self.space_grid.get_S_symm():].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1)
ms1 = self._expand_symmetrize(ms1_first_quadrant)
ms2 = self._expand_symmetrize(ms2_first_quadrant)
ms3 = self._expand_symmetrize(ms3_first_quadrant)
else: else:
ms1 = x[:self.space_grid.get_dof()//2] if self.num_phase_masks == 2:
ms2 = x[self.space_grid.get_dof()//2:] ms1 = x[:self.space_grid.get_S()]
ms2 = x[self.space_grid.get_S():]
elif self.num_phase_masks == 3:
ms1 = x[:self.space_grid.get_S()]
ms2 = x[self.space_grid.get_S(): 2*self.space_grid.get_S()]
ms3 = x[2*self.space_grid.get_S():]
if self.num_phase_masks == 2:
all_ms = np.concatenate((ms1, ms2)) all_ms = np.concatenate((ms1, ms2))
elif self.num_phase_masks == 3:
all_ms = np.concatenate((ms1, ms2, ms3))
self.w_norm[:] = all_ms self.w_norm[:] = all_ms
self.phase_mask[:] = self._phase_given_w(self.w_norm) self.phase_mask[:] = self._phase_given_w(self.w_norm)
if self.use_filtering: if self.use_filtering:
if self.num_phase_masks == 2:
phase_mask_1 = self.phase_mask[:self.space_grid.get_S()] phase_mask_1 = self.phase_mask[:self.space_grid.get_S()]
phase_mask_2 = self.phase_mask[self.space_grid.get_S():] phase_mask_2 = self.phase_mask[self.space_grid.get_S():]
filtered_phase_mask_1 = self._phase_filter(phase_mask_1) filtered_phase_mask_1 = self._phase_filter(phase_mask_1)
filtered_phase_mask_2 = self._phase_filter(phase_mask_2) filtered_phase_mask_2 = self._phase_filter(phase_mask_2)
self.filtered_phase_mask[:] = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2)) self.filtered_phase_mask[:] = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2))
elif self.num_phase_masks == 3:
phase_mask_1 = self.phase_mask[:self.space_grid.get_S()]
phase_mask_2 = self.phase_mask[self.space_grid.get_S(): 2*self.space_grid.get_S()]
phase_mask_3 = self.phase_mask[2*self.space_grid.get_S():]
filtered_phase_mask_1 = self._phase_filter(phase_mask_1)
filtered_phase_mask_2 = self._phase_filter(phase_mask_2)
filtered_phase_mask_3 = self._phase_filter(phase_mask_3)
self.filtered_phase_mask[:] = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2, filtered_phase_mask_3))
else: else:
self.filtered_phase_mask[:] = self.phase_mask self.filtered_phase_mask[:] = self.phase_mask
@@ -692,25 +817,45 @@ class Simulation:
for idx, target in enumerate(self.targets): for idx, target in enumerate(self.targets):
C, full_grad_1, full_grad_2 = self._value_and_grad(target) C, full_grads = self._value_and_grad(target)
#minus sign required because the constraint is in the form of f - t < 0 and we minimize the epigraph t #minus sign required because the constraint is in the form of f - t < 0 and we minimize the epigraph t
results_array[idx] = - C results_array[idx] = - C
if self.use_filtering: if self.use_filtering:
jac_grad_1 = tensor_jacobian_product(self._phase_filter)(phase_mask_1, full_grad_1) if self.num_phase_masks == 2:
jac_grad_1 = tensor_jacobian_product(self._phase_filter)(phase_mask_1, full_grads[0])
print("jac 1 done") print("jac 1 done")
jac_grad_2 = tensor_jacobian_product(self._phase_filter)(phase_mask_2, full_grad_2) jac_grad_2 = tensor_jacobian_product(self._phase_filter)(phase_mask_2, full_grads[1])
print("jac 2 done") print("jac 2 done")
elif self.num_phase_masks == 3:
jac_grad_1 = tensor_jacobian_product(self._phase_filter)(phase_mask_1, full_grads[0])
print("jac 1 done")
jac_grad_2 = tensor_jacobian_product(self._phase_filter)(phase_mask_2, full_grads[1])
print("jac 2 done")
jac_grad_3 = tensor_jacobian_product(self._phase_filter)(phase_mask_3, full_grads[2])
print("jac 3 done")
else: else:
jac_grad_1 = full_grad_1 if self.num_phase_masks == 2:
jac_grad_2 = full_grad_2 jac_grad_1 = full_grads[0]
jac_grad_2 = full_grads[1]
elif self.num_phase_masks == 3:
jac_grad_1 = full_grads[0]
jac_grad_2 = full_grads[1]
jac_grad_3 = full_grads[2]
if self.space_grid.xy_symmetry: if self.space_grid.xy_symmetry:
jac_grad_1_symm = self._reduce_symmetrized_gradient(jac_grad_1) jac_grad_1_symm = self._reduce_symmetrized_gradient(jac_grad_1)
jac_grad_2_symm = self._reduce_symmetrized_gradient(jac_grad_2) jac_grad_2_symm = self._reduce_symmetrized_gradient(jac_grad_2)
if self.num_phase_masks == 3:
jac_grad_3_symm = self._reduce_symmetrized_gradient(jac_grad_3)
jac_grad = np.concatenate((jac_grad_1_symm, jac_grad_2_symm, jac_grad_3_symm), axis=0)
else:
jac_grad = np.concatenate((jac_grad_1_symm, jac_grad_2_symm), axis=0) jac_grad = np.concatenate((jac_grad_1_symm, jac_grad_2_symm), axis=0)
else: else:
jac_grad = np.concatenate((jac_grad_1, jac_grad_2),axis=0) if self.num_phase_masks == 2:
jac_grad = np.concatenate((jac_grad_1, jac_grad_2), axis=0)
elif self.num_phase_masks == 3:
jac_grad = np.concatenate((jac_grad_1, jac_grad_2, jac_grad_3), axis=0)
grads_matrix[idx, :] = jac_grad grads_matrix[idx, :] = jac_grad
@@ -749,26 +894,53 @@ class Simulation:
def _cost_fun(self, x, grad): def _cost_fun(self, x, grad):
self.iter_num[0] = self.iter_num[0] + 1 self.iter_num[0] = self.iter_num[0] + 1
if self.space_grid.xy_symmetry: if self.space_grid.xy_symmetry:
ms1_first_quadrant = x[:self.space_grid.get_dof()//2].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1) if self.num_phase_masks == 2:
ms2_first_quadrant = x[self.space_grid.get_dof()//2:].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1) ms1_first_quadrant = x[:self.space_grid.get_S_symm()].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1)
ms2_first_quadrant = x[self.space_grid.get_S_symm():].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1)
ms1 = self._expand_symmetrize(ms1_first_quadrant) ms1 = self._expand_symmetrize(ms1_first_quadrant)
ms2 = self._expand_symmetrize(ms2_first_quadrant) ms2 = self._expand_symmetrize(ms2_first_quadrant)
elif self.num_phase_masks == 3:
ms1_first_quadrant = x[:self.space_grid.get_S_symm()].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1)
ms2_first_quadrant = x[self.space_grid.get_S_symm():2*self.space_grid.get_S_symm()].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1)
ms3_first_quadrant = x[2*self.space_grid.get_S_symm():].reshape(self.space_grid.get_Nx()//2+1, self.space_grid.get_Ny()//2+1)
ms1 = self._expand_symmetrize(ms1_first_quadrant)
ms2 = self._expand_symmetrize(ms2_first_quadrant)
ms3 = self._expand_symmetrize(ms3_first_quadrant)
else: else:
if self.num_phase_masks == 2:
ms1 = x[:self.space_grid.get_dof()//2] ms1 = x[:self.space_grid.get_dof()//2]
ms2 = x[self.space_grid.get_dof()//2:] ms2 = x[self.space_grid.get_dof()//2:]
elif self.num_phase_masks == 3:
ms1 = x[:self.space_grid.get_dof()//3]
ms2 = x[self.space_grid.get_dof()//3:2*self.space_grid.get_dof()//3]
ms3 = x[2*self.space_grid.get_dof()//3:]
if self.num_phase_masks == 2:
all_ms = np.concatenate((ms1, ms2)) all_ms = np.concatenate((ms1, ms2))
elif self.num_phase_masks == 3:
all_ms = np.concatenate((ms1, ms2, ms3))
self.w_norm[:] = all_ms self.w_norm[:] = all_ms
self.phase_mask[:] = self._phase_given_w(self.w_norm) self.phase_mask[:] = self._phase_given_w(self.w_norm)
if self.use_filtering: if self.use_filtering:
if self.num_phase_masks == 2:
phase_mask_1 = self.phase_mask[:self.space_grid.get_S()] phase_mask_1 = self.phase_mask[:self.space_grid.get_S()]
phase_mask_2 = self.phase_mask[self.space_grid.get_S():] phase_mask_2 = self.phase_mask[self.space_grid.get_S():]
filtered_phase_mask_1 = self._phase_filter(phase_mask_1) filtered_phase_mask_1 = self._phase_filter(phase_mask_1)
filtered_phase_mask_2 = self._phase_filter(phase_mask_2) filtered_phase_mask_2 = self._phase_filter(phase_mask_2)
elif self.num_phase_masks == 3:
phase_mask_1 = self.phase_mask[:self.space_grid.get_S()]
phase_mask_2 = self.phase_mask[self.space_grid.get_S():2*self.space_grid.get_S()]
phase_mask_3 = self.phase_mask[2*self.space_grid.get_S():]
filtered_phase_mask_1 = self._phase_filter(phase_mask_1)
filtered_phase_mask_2 = self._phase_filter(phase_mask_2)
filtered_phase_mask_3 = self._phase_filter(phase_mask_3)
if self.num_phase_masks == 2:
self.filtered_phase_mask[:] = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2)) self.filtered_phase_mask[:] = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2))
elif self.num_phase_masks == 3:
self.filtered_phase_mask[:] = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2, filtered_phase_mask_3))
else: else:
self.filtered_phase_mask[:] = self.phase_mask self.filtered_phase_mask[:] = self.phase_mask
@@ -779,23 +951,44 @@ class Simulation:
for target in self.targets: for target in self.targets:
C, full_grad_1, full_grad_2 = self._value_and_grad(target) C, full_grads = self._value_and_grad(target)
C_all_wavelengths.append(C) C_all_wavelengths.append(C)
tested_wavelengths.append(target.get_wavelength()) tested_wavelengths.append(target.get_wavelength())
if self.use_filtering: if self.use_filtering:
if self.num_phase_masks == 2:
full_grad_1, full_grad_2 = full_grads
jac_grad_1 = tensor_jacobian_product(self._phase_filter)(phase_mask_1, full_grad_1) jac_grad_1 = tensor_jacobian_product(self._phase_filter)(phase_mask_1, full_grad_1)
jac_grad_2 = tensor_jacobian_product(self._phase_filter)(phase_mask_2, full_grad_2) jac_grad_2 = tensor_jacobian_product(self._phase_filter)(phase_mask_2, full_grad_2)
elif self.num_phase_masks == 3:
full_grad_1, full_grad_2, full_grad_3 = full_grads
jac_grad_1 = tensor_jacobian_product(self._phase_filter)(phase_mask_1, full_grad_1)
jac_grad_2 = tensor_jacobian_product(self._phase_filter)(phase_mask_2, full_grad_2)
jac_grad_3 = tensor_jacobian_product(self._phase_filter)(phase_mask_3, full_grad_3)
else: else:
if self.num_phase_masks == 2:
full_grad_1, full_grad_2 = full_grads
jac_grad_1 = full_grad_1 jac_grad_1 = full_grad_1
jac_grad_2 = full_grad_2 jac_grad_2 = full_grad_2
elif self.num_phase_masks == 3:
full_grad_1, full_grad_2, full_grad_3 = full_grads
jac_grad_1 = full_grad_1
jac_grad_2 = full_grad_2
jac_grad_3 = full_grad_3
if self.space_grid.xy_symmetry: if self.space_grid.xy_symmetry:
jac_grad_1_symm = self._reduce_symmetrized_gradient(jac_grad_1) jac_grad_1_symm = self._reduce_symmetrized_gradient(jac_grad_1)
jac_grad_2_symm = self._reduce_symmetrized_gradient(jac_grad_2) jac_grad_2_symm = self._reduce_symmetrized_gradient(jac_grad_2)
if self.num_phase_masks == 2:
jac_grad = np.concatenate((jac_grad_1_symm, jac_grad_2_symm), axis=0) jac_grad = np.concatenate((jac_grad_1_symm, jac_grad_2_symm), axis=0)
elif self.num_phase_masks == 3:
jac_grad_3_symm = self._reduce_symmetrized_gradient(jac_grad_3)
jac_grad = np.concatenate((jac_grad_1_symm, jac_grad_2_symm, jac_grad_3_symm), axis=0)
else: else:
if self.num_phase_masks == 2:
jac_grad = np.concatenate((jac_grad_1, jac_grad_2),axis=0) jac_grad = np.concatenate((jac_grad_1, jac_grad_2),axis=0)
elif self.num_phase_masks == 3:
jac_grad = np.concatenate((jac_grad_1, jac_grad_2, jac_grad_3),axis=0)
grad_partial[:] += jac_grad grad_partial[:] += jac_grad
@@ -840,19 +1033,25 @@ class Simulation:
''' '''
if self.use_filtering: if self.use_filtering:
if self.num_phase_masks == 2:
filtered_phase_mask_1 = self._phase_filter(self.phase_mask[:self.space_grid.get_S()]) filtered_phase_mask_1 = self._phase_filter(self.phase_mask[:self.space_grid.get_S()])
filtered_phase_mask_2 = self._phase_filter(self.phase_mask[self.space_grid.get_S():]) filtered_phase_mask_2 = self._phase_filter(self.phase_mask[self.space_grid.get_S():])
self.filtered_phase_mask = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2)) self.filtered_phase_mask = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2))
elif self.num_phase_masks == 3:
filtered_phase_mask_1 = self._phase_filter(self.phase_mask[:self.space_grid.get_S()])
filtered_phase_mask_2 = self._phase_filter(self.phase_mask[self.space_grid.get_S():2*self.space_grid.get_S()])
filtered_phase_mask_3 = self._phase_filter(self.phase_mask[2*self.space_grid.get_S():])
self.filtered_phase_mask = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2, filtered_phase_mask_3))
else: else:
self.filtered_phase_mask = self.phase_mask self.filtered_phase_mask = self.phase_mask
mask_upper_bounds = self.norm_phase_max * np.where( self.space_grid.get_X().flatten()**2 + self.space_grid.get_Y().flatten()**2 < (self.ms_max_avail_diameter/2)**2, 1, 0) mask_upper_bounds = self.norm_phase_max * np.where(self.space_grid.get_X().flatten()**2 + self.space_grid.get_Y().flatten()**2 < (self.ms_max_avail_diameter/2)**2, 1, 0)
mask_upper_bounds_full = np.concatenate((mask_upper_bounds, mask_upper_bounds),axis=0) mask_upper_bounds_full = np.tile(mask_upper_bounds,self.num_phase_masks)
mask_lower_bounds_full = np.zeros_like(mask_upper_bounds_full) mask_lower_bounds_full = np.zeros_like(mask_upper_bounds_full)
if self.space_grid.xy_symmetry: if self.space_grid.xy_symmetry:
mask_upper_bounds_symm = mask_upper_bounds.reshape((self.space_grid.get_Nx(),self.space_grid.get_Ny()))[:self.space_grid.get_nx()+1, :self.space_grid.get_ny()+1].flatten() mask_upper_bounds_symm = mask_upper_bounds.reshape((self.space_grid.get_Nx(),self.space_grid.get_Ny()))[:self.space_grid.get_nx()+1, :self.space_grid.get_ny()+1].flatten()
mask_upper_bounds_symm_full = np.concatenate((mask_upper_bounds_symm, mask_upper_bounds_symm),axis=0) mask_upper_bounds_symm_full = np.tile(mask_upper_bounds_symm,self.num_phase_masks)
mask_lower_bounds_symm_full = np.zeros_like(mask_upper_bounds_symm_full) mask_lower_bounds_symm_full = np.zeros_like(mask_upper_bounds_symm_full)
self.chosen_mask_upper_bounds = mask_upper_bounds_symm_full self.chosen_mask_upper_bounds = mask_upper_bounds_symm_full
self.chosen_mask_lower_bounds = mask_lower_bounds_symm_full self.chosen_mask_lower_bounds = mask_lower_bounds_symm_full
@@ -923,23 +1122,45 @@ class Simulation:
make_2Dplot_of(output_field[-1], self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase", save_name=f"{self.results_folder}/output_fields_{i}_phase_plot") make_2Dplot_of(output_field[-1], self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase", save_name=f"{self.results_folder}/output_fields_{i}_phase_plot")
#phase masks #phase masks
if self.num_phase_masks == 2:
phase_mask_1 = self._phase_given_w(self.w_norm[:self.space_grid.get_S()]).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny())) phase_mask_1 = self._phase_given_w(self.w_norm[:self.space_grid.get_S()]).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))
phase_mask_2 = self._phase_given_w(self.w_norm[self.space_grid.get_S():]).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny())) phase_mask_2 = self._phase_given_w(self.w_norm[self.space_grid.get_S():]).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))
elif self.num_phase_masks == 3:
phase_mask_1 = self._phase_given_w(self.w_norm[:self.space_grid.get_S()]).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))
phase_mask_2 = self._phase_given_w(self.w_norm[self.space_grid.get_S():2*self.space_grid.get_S()]).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))
phase_mask_3 = self._phase_given_w(self.w_norm[2*self.space_grid.get_S():]).reshape((self.space_grid.get_Nx(), self.space_grid.get_Ny()))
if self.use_filtering: if self.use_filtering:
if self.num_phase_masks == 2:
filtered_phase_mask_1 = self._phase_filter(phase_mask_1) filtered_phase_mask_1 = self._phase_filter(phase_mask_1)
filtered_phase_mask_2 = self._phase_filter(phase_mask_2) filtered_phase_mask_2 = self._phase_filter(phase_mask_2)
elif self.num_phase_masks == 3:
filtered_phase_mask_1 = self._phase_filter(phase_mask_1)
filtered_phase_mask_2 = self._phase_filter(phase_mask_2)
filtered_phase_mask_3 = self._phase_filter(phase_mask_3)
else: else:
if self.num_phase_masks == 2:
filtered_phase_mask_1 = phase_mask_1 filtered_phase_mask_1 = phase_mask_1
filtered_phase_mask_2 = phase_mask_2 filtered_phase_mask_2 = phase_mask_2
elif self.num_phase_masks == 3:
filtered_phase_mask_1 = phase_mask_1
filtered_phase_mask_2 = phase_mask_2
filtered_phase_mask_3 = phase_mask_3
make_2Dplot_of(phase_mask_1, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase_mask", save_name=f"{self.results_folder}/phase_mask_1_plot") make_2Dplot_of(phase_mask_1, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase_mask", save_name=f"{self.results_folder}/phase_mask_1_plot")
make_2Dplot_of(phase_mask_2, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase_mask", save_name=f"{self.results_folder}/phase_mask_2_plot") make_2Dplot_of(phase_mask_2, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase_mask", save_name=f"{self.results_folder}/phase_mask_2_plot")
make_2Dplot_of(filtered_phase_mask_1, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase_mask", save_name=f"{self.results_folder}/filtered_phase_mask_1_plot") make_2Dplot_of(filtered_phase_mask_1, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase_mask", save_name=f"{self.results_folder}/filtered_phase_mask_1_plot")
make_2Dplot_of(filtered_phase_mask_2, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase_mask", save_name=f"{self.results_folder}/filtered_phase_mask_2_plot") make_2Dplot_of(filtered_phase_mask_2, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase_mask", save_name=f"{self.results_folder}/filtered_phase_mask_2_plot")
if self.num_phase_masks == 3:
make_2Dplot_of(phase_mask_3, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase_mask", save_name=f"{self.results_folder}/phase_mask_3_plot")
make_2Dplot_of(filtered_phase_mask_3, self.space_grid.get_X(), self.space_grid.get_Y(), plot_zoom_x=1e6*self.get_ms_max_avail_diameter()/2, plot_zoom_y=1e6*self.get_ms_max_avail_diameter()/2, choose_quantity="phase_mask", save_name=f"{self.results_folder}/filtered_phase_mask_3_plot")
np.save(f"{self.results_folder}/optimized_phase_mask_1.npy", phase_mask_1) np.save(f"{self.results_folder}/optimized_phase_mask_1.npy", phase_mask_1)
np.save(f"{self.results_folder}/optimized_phase_mask_2.npy", phase_mask_2) np.save(f"{self.results_folder}/optimized_phase_mask_2.npy", phase_mask_2)
np.save(f"{self.results_folder}/filtered_phase_mask_1.npy", filtered_phase_mask_1) np.save(f"{self.results_folder}/filtered_phase_mask_1.npy", filtered_phase_mask_1)
np.save(f"{self.results_folder}/filtered_phase_mask_2.npy", filtered_phase_mask_2) np.save(f"{self.results_folder}/filtered_phase_mask_2.npy", filtered_phase_mask_2)
if self.num_phase_masks == 3:
np.save(f"{self.results_folder}/optimized_phase_mask_3.npy", phase_mask_3)
np.save(f"{self.results_folder}/filtered_phase_mask_3.npy", filtered_phase_mask_3)
def get_best_opt_val(self): def get_best_opt_val(self):
if self.is_optimized_flag: if self.is_optimized_flag:
@@ -968,6 +1189,7 @@ class Simulation:
raise ValueError("The provided space grid is not compatible with the target fields.") raise ValueError("The provided space grid is not compatible with the target fields.")
self.space_grid = new_space_grid self.space_grid = new_space_grid
self.num_phase_masks = self.space_grid.get_num_phase_masks()
self.space_grid_is_set = False self.space_grid_is_set = False
self.is_optimized_flag = False self.is_optimized_flag = False
@@ -989,12 +1211,14 @@ class Simulation:
self.nu_parallel = self.K_parallel / (2 * np.pi) self.nu_parallel = self.K_parallel / (2 * np.pi)
#phase masks initialization #phase masks initialization
self.w_norm = np.zeros((2*self.space_grid.get_S())) self.w_norm = np.zeros((self.num_phase_masks*self.space_grid.get_S()))
self.w_norm_symm = np.zeros(self.space_grid.get_dof()) self.w_norm_symm = np.zeros(self.space_grid.get_dof())
self.phase_mask = self._phase_given_w(self.w_norm) self.phase_mask = self._phase_given_w(self.w_norm)
#initialize intermediate fields #initialize intermediate fields
self.intermediate_electric_fields = [None, None, None] self.intermediate_electric_fields = [None] * (self.num_phase_masks + 1)
self.intermediate_magnetic_fields = [None, None, None] self.intermediate_magnetic_fields = [None] * (self.num_phase_masks + 1)
#pyfftw init #pyfftw init
self.fft_input_array = pyfftw.empty_aligned((self.space_grid.get_Nx(), self.space_grid.get_Ny()), dtype='complex128', n=16) self.fft_input_array = pyfftw.empty_aligned((self.space_grid.get_Nx(), self.space_grid.get_Ny()), dtype='complex128', n=16)
self.fft_output_array = pyfftw.empty_aligned((self.space_grid.get_Nx(), self.space_grid.get_Ny()), dtype='complex128', n=16) self.fft_output_array = pyfftw.empty_aligned((self.space_grid.get_Nx(), self.space_grid.get_Ny()), dtype='complex128', n=16)
@@ -1017,7 +1241,7 @@ class Simulation:
self.opt_history = [] #updating distances resets the history self.opt_history = [] #updating distances resets the history
new_space_grid = SpaceGrid( new_space_grid = SpaceGrid(
side_pixels=self.space_grid.get_Nx(), side_pixels=self.space_grid.get_Nx(),
unit_cell_pitch=self.space_grid.get_unit_cell_pitch() * 1e6, unit_cell_pitch=self.space_grid.get_unit_cell_pitch(),
prop_distances=new_distances, prop_distances=new_distances,
materials_refr_index=self.space_grid.get_materials_refr_index(), materials_refr_index=self.space_grid.get_materials_refr_index(),
xy_symmetry=True, xy_symmetry=True,
@@ -1025,6 +1249,67 @@ class Simulation:
self.set_space_grid(new_space_grid) self.set_space_grid(new_space_grid)
def inject_phase_masks(self, input_phase_masks: list):
"""
Inject the specified phase masks into the simulation.
Args:
input_phase_masks (list): A list of phase masks, flattened, to be injected into the simulation.
Returns:
None
"""
#check if the length of the list of input_phase_masks we are trying to inject
#has the same number of phase masks in this simulation object.
if len(input_phase_masks) != self.num_phase_masks:
raise ValueError(f"Expected {self.num_phase_masks} phase masks, but got {len(input_phase_masks)}.")
for i, phase_mask in enumerate(input_phase_masks):
self.phase_mask[i*self.space_grid.get_S():(i+1)*self.space_grid.get_S()] = phase_mask
self.w_norm[i*self.space_grid.get_S():(i+1)*self.space_grid.get_S()] = self._w_given_phase(phase_mask)
if self.use_filtering:
if self.num_phase_masks == 2:
filtered_phase_mask_1 = self._phase_filter(self.phase_mask[:self.space_grid.get_S()])
filtered_phase_mask_2 = self._phase_filter(self.phase_mask[self.space_grid.get_S():])
self.filtered_phase_mask = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2))
elif self.num_phase_masks == 3:
filtered_phase_mask_1 = self._phase_filter(self.phase_mask[:self.space_grid.get_S()])
filtered_phase_mask_2 = self._phase_filter(self.phase_mask[self.space_grid.get_S():2*self.space_grid.get_S()])
filtered_phase_mask_3 = self._phase_filter(self.phase_mask[2*self.space_grid.get_S():])
self.filtered_phase_mask = np.concatenate((filtered_phase_mask_1, filtered_phase_mask_2, filtered_phase_mask_3))
else:
self.filtered_phase_mask = self.phase_mask
def source_sweep(self, distances: np.ndarray = None, input_phase_masks: list = None):
"""
This method runs the propagation algorithm for the specified sources and targets in the simulation object.
For every propagation step, it enforces the same phase masks. It can optionally update propagation distances and phase masks.
If not specified, it is assumed the simulation object has already been optimized and that the required quantities are already set.
It is useful for studying the wavelength-dependent performance of the system.
Args:
distances (np.ndarray, optional): Array containing the propagation distances. If None, the current distances are used.
input_phase_masks (list, optional): A list of phase masks to be injected into the simulation. If None, the current phase masks are used.
Returns:
dict: A dictionary mapping source wavelengths to the overlap between the output field and the associated target field.
"""
results_dict = {}
if distances is not None:
self.update_propagation_distances(distances)
if input_phase_masks is not None:
self.inject_phase_masks(input_phase_masks)
for target in self.targets:
source = target.get_associated_source()
self._update_wavelength(source.get_wavelength())
output_field = self._forward_propagate(source, returns_field = True)
#compute the overlap between the output field and associated target field
target_field = target.get_field(self.get_space_grid(), flatten = True)
overlap = np.abs(np.sum(np.conj(target_field) * output_field))**2
results_dict[source.get_wavelength()] = overlap
return results_dict
def run(self): def run(self):
""" """
Run the optimization process for the simulation. Run the optimization process for the simulation.
@@ -1033,6 +1318,7 @@ class Simulation:
""" """
if self.space_grid_is_set: if self.space_grid_is_set:
if self.is_optimization_set: if self.is_optimization_set:
print(f"Using {self.num_phase_masks} phase masks")
print("Initializing solver...") print("Initializing solver...")
self._solver_init() self._solver_init()
print("Starting optimization...") print("Starting optimization...")
@@ -1050,16 +1336,31 @@ class Simulation:
self.is_optimized_flag = True self.is_optimized_flag = True
self.best_opt_val = self.solver.last_optimum_value() self.best_opt_val = self.solver.last_optimum_value()
self.w_norm[:self.space_grid.get_S()] = self._expand_symmetrize(self.w_norm_symm[:self.space_grid.get_dof()//2]) if self.num_phase_masks == 2:
self.w_norm[self.space_grid.get_S():] = self._expand_symmetrize(self.w_norm_symm[self.space_grid.get_dof()//2:]) self.w_norm[:self.space_grid.get_S()] = self._expand_symmetrize(self.w_norm_symm[:self.space_grid.get_S_symm()])
self.w_norm[self.space_grid.get_S():] = self._expand_symmetrize(self.w_norm_symm[self.space_grid.get_S_symm():])
self.phase_mask[:] = self._phase_given_w(self.w_norm)
elif self.num_phase_masks == 3:
self.w_norm[:self.space_grid.get_S()] = self._expand_symmetrize(self.w_norm_symm[:self.space_grid.get_S_symm()])
self.w_norm[self.space_grid.get_S():2*self.space_grid.get_S()] = self._expand_symmetrize(self.w_norm_symm[self.space_grid.get_S_symm():2*self.space_grid.get_S_symm()])
self.w_norm[2*self.space_grid.get_S():] = self._expand_symmetrize(self.w_norm_symm[2*self.space_grid.get_S_symm():])
self.phase_mask[:] = self._phase_given_w(self.w_norm) self.phase_mask[:] = self._phase_given_w(self.w_norm)
if self.use_filtering: if self.use_filtering:
if self.num_phase_masks == 2:
phase_mask_1 = self.phase_mask[:self.space_grid.get_S()] phase_mask_1 = self.phase_mask[:self.space_grid.get_S()]
phase_mask_2 = self.phase_mask[self.space_grid.get_S():] phase_mask_2 = self.phase_mask[self.space_grid.get_S():]
filtered_phase_mask_1 = self._phase_filter(phase_mask_1) filtered_phase_mask_1 = self._phase_filter(phase_mask_1)
filtered_phase_mask_2 = self._phase_filter(phase_mask_2) filtered_phase_mask_2 = self._phase_filter(phase_mask_2)
self.filtered_phase_mask[:] = np.concatenate([filtered_phase_mask_1, filtered_phase_mask_2]) self.filtered_phase_mask[:] = np.concatenate([filtered_phase_mask_1, filtered_phase_mask_2])
elif self.num_phase_masks == 3:
phase_mask_1 = self.phase_mask[:self.space_grid.get_S()]
phase_mask_2 = self.phase_mask[self.space_grid.get_S():2*self.space_grid.get_S()]
phase_mask_3 = self.phase_mask[2*self.space_grid.get_S():]
filtered_phase_mask_1 = self._phase_filter(phase_mask_1)
filtered_phase_mask_2 = self._phase_filter(phase_mask_2)
filtered_phase_mask_3 = self._phase_filter(phase_mask_3)
self.filtered_phase_mask[:] = np.concatenate([filtered_phase_mask_1, filtered_phase_mask_2, filtered_phase_mask_3])
else: else:
self.filtered_phase_mask[:] = self.phase_mask self.filtered_phase_mask[:] = self.phase_mask
@@ -1077,11 +1378,20 @@ class Simulation:
self.phase_mask[:] = self._phase_given_w(self.w_norm) self.phase_mask[:] = self._phase_given_w(self.w_norm)
if self.use_filtering: if self.use_filtering:
if self.num_phase_masks == 2:
phase_mask_1 = self.phase_mask[:self.space_grid.get_S()] phase_mask_1 = self.phase_mask[:self.space_grid.get_S()]
phase_mask_2 = self.phase_mask[self.space_grid.get_S():] phase_mask_2 = self.phase_mask[self.space_grid.get_S():]
filtered_phase_mask_1 = self._phase_filter(phase_mask_1) filtered_phase_mask_1 = self._phase_filter(phase_mask_1)
filtered_phase_mask_2 = self._phase_filter(phase_mask_2) filtered_phase_mask_2 = self._phase_filter(phase_mask_2)
self.filtered_phase_mask[:] = np.concatenate([filtered_phase_mask_1, filtered_phase_mask_2]) self.filtered_phase_mask[:] = np.concatenate([filtered_phase_mask_1, filtered_phase_mask_2])
elif self.num_phase_masks == 3:
phase_mask_1 = self.phase_mask[:self.space_grid.get_S()]
phase_mask_2 = self.phase_mask[self.space_grid.get_S():2*self.space_grid.get_S()]
phase_mask_3 = self.phase_mask[2*self.space_grid.get_S():]
filtered_phase_mask_1 = self._phase_filter(phase_mask_1)
filtered_phase_mask_2 = self._phase_filter(phase_mask_2)
filtered_phase_mask_3 = self._phase_filter(phase_mask_3)
self.filtered_phase_mask[:] = np.concatenate([filtered_phase_mask_1, filtered_phase_mask_2, filtered_phase_mask_3])
else: else:
self.filtered_phase_mask[:] = self.phase_mask self.filtered_phase_mask[:] = self.phase_mask
+14 -15
View File
@@ -7,16 +7,19 @@ if not os.path.exists("results"):
N = 1025 N = 1025
unit_cell_pitch = 0.700 unit_cell_pitch = 0.700
n0 = 1.45 n0 = 1.444
n1 = 1.0 n1 = 1.0
n2 = 1.45 n2 = 1.444
mat_refr_index = np.array([n0, n1, n2]) mat_refr_index = np.array([n0, n1, n2])
#d0 = 400 #propagation distance: source - MS1 [um] NO FILT #d0 = 400 #propagation distance: source - MS1 [um] NO FILT
#d1 = 280 #propagation distance: MS1 - MS2 [um] NO FILT #d1 = 280 #propagation distance: MS1 - MS2 [um] NO FILT
d0 = 535 #propagation distance: source - MS1 [um] FILTERED #d0 = 516 #propagation distance: source - MS1 [um] FILTERED mf (using LP01 of FMF)
d1 = 460 #propagation distance: MS1 - MS2 [um] FILTERED #d1 = 467 #propagation distance: MS1 - MS2 [um] FILTERED mf (using LP01 of FMF)
d0 = 195 #propagation distance: source - MS1 [um] FILTERED mf (using LP01 of SMF)
d1 = 129 #propagation distance: MS1 - MS2 [um] FILTERED mf (using LP01 of SMF)
d2 = d0 #propagation distance: MS2 - target [um] d2 = d0 #propagation distance: MS2 - target [um]
d = np.array([d0, d1, d2]) d = np.array([d0, d1, d2])
@@ -33,14 +36,10 @@ space_grid = pid.SpaceGrid(
) )
#define the source #define the source
beam_waist = 5.2
gaussian_field = np.exp(-(space_grid.get_rho())**2 / (beam_waist)**2) #Gaussian input field, flattened
gaussian_field_normalized = (gaussian_field / np.sqrt(np.sum(np.abs(gaussian_field)**2))) #normalize the input field to have power = 1
wavelengths_to_test_um = [1.530,1.550,1.570] #[um] wavelengths_to_test_um = [1.530,1.550,1.570] #[um]
source_files = ["fiber_modes/LP01_field_distribution_lda_1530_N_1025.npy", source_files = ["fiber_modes/SMF/SMF_LP01_field_distribution_lda_1530_N_1025.npy",
"fiber_modes/LP01_field_distribution_lda_1550_N_1025.npy", "fiber_modes/SMF/SMF_LP01_field_distribution_lda_1550_N_1025.npy",
"fiber_modes/LP01_field_distribution_lda_1570_N_1025.npy"] "fiber_modes/SMF/SMF_LP01_field_distribution_lda_1570_N_1025.npy"]
sources = [pid.Source(input_field=np.load(file), sources = [pid.Source(input_field=np.load(file),
wavelength=wvl, wavelength=wvl,
field_norm=True) for file, wvl in zip(source_files, wavelengths_to_test_um)] field_norm=True) for file, wvl in zip(source_files, wavelengths_to_test_um)]
@@ -48,9 +47,9 @@ sources = [pid.Source(input_field=np.load(file),
#sources[0].plot2D(plot_zoom_x=38,plot_zoom_y=38,choose_quantity='amplitude', save_name='results/input_field') #sources[0].plot2D(plot_zoom_x=38,plot_zoom_y=38,choose_quantity='amplitude', save_name='results/input_field')
#define targets #define targets
#import field data from files and create target objects #import field data from files and create target objects
target_files = ["fiber_modes/LP21_field_distribution_lda_1530_N_1025.npy", target_files = ["fiber_modes/FMF/FMF_LP21_field_distribution_lda_1530_N_1025.npy",
"fiber_modes/LP21_field_distribution_lda_1550_N_1025.npy", "fiber_modes/FMF/FMF_LP21_field_distribution_lda_1550_N_1025.npy",
"fiber_modes/LP21_field_distribution_lda_1570_N_1025.npy"] "fiber_modes/FMF/FMF_LP21_field_distribution_lda_1570_N_1025.npy"]
target_fields = [np.load(file) for file in target_files] target_fields = [np.load(file) for file in target_files]
@@ -76,7 +75,7 @@ simulation.set_optimization(
ms_max_avail_diameter=ms_max_available_diameter, ms_max_avail_diameter=ms_max_available_diameter,
phase_mask_init_size=output_fiber_core_diameter/3, phase_mask_init_size=output_fiber_core_diameter/3,
opt_max_eval=opt_max_eval, opt_max_eval=opt_max_eval,
conic_filter_radius=1.0, #conic_filter_radius=1.0,
) )
simulation.run() simulation.run()
+150 -140
View File
@@ -23,9 +23,9 @@ if not os.path.exists("results"):
N = 1025 N = 1025
unit_cell_pitch = 0.700 #i.e., 700 [nm] unit_cell_pitch = 0.700 #i.e., 700 [nm]
n0 = 1.45 n0 = 1.444
n1 = 1.0 n1 = 1.0
n2 = 1.45 n2 = 1.444
mat_refr_index = np.array([n0, n1, n2]) mat_refr_index = np.array([n0, n1, n2])
ms_max_available_diameter = 122 #[um] ms_max_available_diameter = 122 #[um]
@@ -46,36 +46,26 @@ def setup_optical_system()->pid.Simulation:
float: The best objective value obtained from the optimization. float: The best objective value obtained from the optimization.
''' '''
central_wavelength_um = 1.55 central_wavelength_um = 1.55
wavelength = central_wavelength_um
wavelengths_to_test_um = [1.530, 1.550, 1.570] #[um] wavelengths_to_test_um = [1.530, 1.550, 1.570] #[um]
source_files = ["fiber_modes/LP01_field_distribution_lda_1530_N_1025.npy", source_files = ["fiber_modes/SMF/SMF_LP01_field_distribution_lda_1530_N_1025.npy",
"fiber_modes/LP01_field_distribution_lda_1550_N_1025.npy", "fiber_modes/SMF/SMF_LP01_field_distribution_lda_1550_N_1025.npy",
"fiber_modes/LP01_field_distribution_lda_1570_N_1025.npy"] "fiber_modes/SMF/SMF_LP01_field_distribution_lda_1570_N_1025.npy"]
# source = pid.Source(input_field='fiber_modes/LP01_field_distribution_lda_1550_N_1025.npy',
# wavelength=wavelength,
# field_norm=True)
sources = [pid.Source(input_field=np.load(file), sources = [pid.Source(input_field=np.load(file),
wavelength=wvl, wavelength=wvl,
field_norm=True) for file, wvl in zip(source_files, wavelengths_to_test_um)] field_norm=True) for file, wvl in zip(source_files, wavelengths_to_test_um)]
target_files = ["fiber_modes/LP21_field_distribution_lda_1530_N_1025.npy", target_files = ["fiber_modes/FMF/FMF_LP21_field_distribution_lda_1530_N_1025.npy",
"fiber_modes/LP21_field_distribution_lda_1550_N_1025.npy", "fiber_modes/FMF/FMF_LP21_field_distribution_lda_1550_N_1025.npy",
"fiber_modes/LP21_field_distribution_lda_1570_N_1025.npy"] "fiber_modes/FMF/FMF_LP21_field_distribution_lda_1570_N_1025.npy"]
targets = [pid.Target(input_field=np.load(file), targets = [pid.Target(input_field=np.load(file),
associated_source=sources[i], associated_source=sources[i],
wavelength=wvl, wavelength=wvl,
field_norm=True) for i, (file, wvl) in enumerate(zip(target_files, wavelengths_to_test_um))] field_norm=True) for i, (file, wvl) in enumerate(zip(target_files, wavelengths_to_test_um))]
# target = pid.Target(input_field='fiber_modes/LP21_field_distribution_lda_1550_N_1025.npy',
# associated_source=source,
# wavelength=wavelength,
# field_norm=True)
initial_space_grid = pid.SpaceGrid( initial_space_grid = pid.SpaceGrid(
side_pixels=N, side_pixels=N,
unit_cell_pitch=unit_cell_pitch, unit_cell_pitch=unit_cell_pitch,
@@ -105,8 +95,8 @@ def setup_optical_system()->pid.Simulation:
return simulation return simulation
def EI(GP, points, f_min): def EI(GP, points, f_min): #f_min is always negative
pred = - GP.predict_values(points) #GP predicts overlaps (positive) pred = - GP.predict_values(points) #GP predicts overlaps, which are always positive
var = GP.predict_variances(points) var = GP.predict_variances(points)
args0 = (f_min - pred) / np.sqrt(var) args0 = (f_min - pred) / np.sqrt(var)
args1 = (f_min - pred) * norm.cdf(args0) args1 = (f_min - pred) * norm.cdf(args0)
@@ -118,44 +108,31 @@ def EI(GP, points, f_min):
ei = args1 + args2 ei = args1 + args2
return ei[0,0] return ei[0,0]
def leave_one_out(initial_dataset): def leave_one_out(dataset, corr_function):
full_x_doe = initial_dataset[:, :2] full_x_doe = dataset[0]
full_y_doe = initial_dataset[:, 2].reshape(-1, 1) full_y_doe = dataset[1]
loo = LeaveOneOut() loo = LeaveOneOut()
#available kernels: ‘matern52’, ‘matern32’ ‘abs_exp’, ‘squar_exp’
kernels_to_test = ['abs_exp', 'squar_exp', 'matern52', 'matern32']
all_z_values = []
for kernel in kernels_to_test:
errs = []
z_values = [] z_values = []
true_values = [] true_values = []
predicted_values = [] predicted_values = []
predicted_confidence = [] predicted_confidence = []
for i, (train_index, test_index) in enumerate(loo.split(full_x_doe)): for i, (train_index, test_index) in enumerate(loo.split(full_x_doe)):
print(f"LOO Iteration {i}") print(f"LOO Iteration {i}")
train_values = full_x_doe[train_index] train_values = full_x_doe[train_index]
test_values = full_x_doe[test_index] test_values = full_x_doe[test_index]
train_outputs = full_y_doe[train_index] train_outputs = full_y_doe[train_index]
test_outputs = full_y_doe[test_index] test_outputs = full_y_doe[test_index]
sm_loo = KRG(design_space=design_space, sm_loo = KRG(design_space=design_space,
n_start=40, n_start=40,
print_global=False, print_global=False,
eval_noise=True, eval_noise=False,
corr=kernel, corr=corr_function,
) )
#no_filt has a good fit using matern32
#filt has a good fit using matern32
sm_loo.set_training_values(train_values, train_outputs) sm_loo.set_training_values(train_values, train_outputs)
sm_loo.train() sm_loo.train()
#the predicted value from the surrogate model
#this is the predicted value from the surrogate model
test_pred_values = sm_loo.predict_values(test_values) test_pred_values = sm_loo.predict_values(test_values)
#for each point in the surrogate model we can get an estimate of the prediction variance, in other #for each point in the surrogate model we can get an estimate of the prediction variance, in other
#words, a measure of how much the model trusts the prediction at that point #words, a measure of how much the model trusts the prediction at that point
@@ -170,11 +147,9 @@ def leave_one_out(initial_dataset):
predicted_values.append(test_pred_values) predicted_values.append(test_pred_values)
predicted_confidence.append(np.sqrt(test_pred_variances)) #standard deviation predicted_confidence.append(np.sqrt(test_pred_variances)) #standard deviation
sq_err = np.mean((test_pred_values - test_outputs)**2) sq_err = np.mean((test_pred_values - test_outputs)**2)
print(f" Squared error for LOO iteration {i}: {sq_err:.6e}") print(f" Squared error for LOO iteration {i}: {sq_err:.6e}")
errs.append(sq_err)
all_z_values.append(z_values)
#SCATTER PLOT WITH ERROR BAR ON EACH SAMPLE loo cv
fig, ax = plt.subplots(figsize=(10, 6)) fig, ax = plt.subplots(figsize=(10, 6))
for i in range(len(true_values)): for i in range(len(true_values)):
#confidence interval is set to 3 standard deviations (99.7%) #confidence interval is set to 3 standard deviations (99.7%)
@@ -184,13 +159,14 @@ def leave_one_out(initial_dataset):
ax.set_xlabel('LOO Iteration', fontsize=20) ax.set_xlabel('LOO Iteration', fontsize=20)
ax.set_ylabel('Value', fontsize=20) ax.set_ylabel('Value', fontsize=20)
ax.set_title('LOO Predicted vs True Values', fontsize=20) ax.set_title('LOO Predicted vs True Values', fontsize=20)
ax.tick_params(axis='both', labelsize=18) ax.set_xticks(range(1, len(true_values), 2))
ax.tick_params(axis='both', labelsize=20)
ax.grid(True, alpha=0.3) ax.grid(True, alpha=0.3)
ax.legend() ax.legend()
plt.savefig(f'{results_folder}/loo_validation_kernel_{kernel}.pdf') plt.savefig(f'{surrogate_folder_name}/loo_validation_kernel_{corr_function}.pdf')
plt.close(fig) plt.close(fig)
#make a line plot to show on a diagonal how predicted and true values compare #DIAGONAL COMPARISON LINE true/predicted
true_values_array = np.concatenate(true_values).ravel() true_values_array = np.concatenate(true_values).ravel()
predicted_values_array = np.concatenate(predicted_values).ravel() predicted_values_array = np.concatenate(predicted_values).ravel()
@@ -203,82 +179,63 @@ def leave_one_out(initial_dataset):
ax.set_xlabel('True Value', fontsize=20) ax.set_xlabel('True Value', fontsize=20)
ax.set_ylabel('Predicted Value', fontsize=20) ax.set_ylabel('Predicted Value', fontsize=20)
ax.set_title('LOO Predicted vs True Values', fontsize=20) ax.set_title('LOO Predicted vs True Values', fontsize=20)
ax.tick_params(axis='both', labelsize=18) ax.tick_params(axis='both', labelsize=20)
ax.grid(True, alpha=0.3) ax.grid(True, alpha=0.3)
ax.legend() ax.legend()
plt.savefig(f'{results_folder}/loo_predicted_vs_true_diagonal_kernel_{kernel}.pdf') plt.savefig(f'{surrogate_folder_name}/loo_predicted_vs_true_diagonal_kernel_{corr_function}.pdf')
plt.close(fig) plt.close(fig)
#GAUSSIAN FIT standard normal vs. standardized residuals
fig, ax = plt.subplots(figsize=(10, 6))
z_values_array = np.concatenate(z_values)
mu, std = norm.fit(z_values_array)
x = np.linspace(-4, 4, 100)
p = norm.pdf(x, mu, std)
ax.plot(x, p, 'r-', label='Fitted Gaussian')
mu_norm, std_norm = 0, 1
ax.plot(x, norm.pdf(x, mu_norm, std_norm), 'g--', label='Standard Normal')
ax.hist(z_values_array, bins=20, density=True, alpha=0.6, color='blue', label='Standardized residuals')
ax.set_xlabel('Standardized Residual', fontsize=20)
ax.set_ylabel('Density', fontsize=20)
ax.set_title('Gaussian Fit vs Standardized Residuals', fontsize=20)
ax.tick_params(axis='both', labelsize=20)
ax.grid(True, alpha=0.3)
ax.legend()
plt.savefig(f'{surrogate_folder_name}/loo_gaussian_fit_kernel_{corr_function}.pdf')
plt.close(fig)
#STANDARDIZED RESIDUALS PLOT
fig, ax = plt.subplots(figsize=(10, 6)) fig, ax = plt.subplots(figsize=(10, 6))
z_values_array = np.concatenate(z_values) z_values_array = np.concatenate(z_values)
ax.scatter(range(len(z_values_array)), z_values_array, color='blue', label='Standardized residuals') ax.scatter(range(len(z_values_array)), z_values_array, color='blue', label='Standardized residuals')
ax.set_ylabel('Standardized Residual', fontsize=20) ax.set_ylabel('Standardized Residual', fontsize=20)
ax.set_title('LOO Standardized Residuals', fontsize=20) #ax.set_title('LOO Standardized Residuals', fontsize=20)
ax.set_xticks(range(len(z_values_array))) ax.set_xticks(range(1, len(z_values_array), 2))
ax.set_ylim(min(z_values_array) - 0.5, max(z_values_array) + 0.5) ax.set_ylim(-np.max(np.abs(z_values_array)), np.max(np.abs(z_values_array)))
ax.tick_params(axis='both', labelsize=18) ax.tick_params(axis='both', labelsize=20)
ax.grid(True, alpha=0.3) ax.grid(True, alpha=0.3)
ax.legend() ax.legend()
plt.savefig(f'{results_folder}/loo_standardized_residuals_kernel_{kernel}.pdf') plt.savefig(f'{surrogate_folder_name}/loo_standardized_residuals_kernel_{corr_function}.pdf')
plt.close(fig) plt.close(fig)
#in the same plot, represent the normal distribution (0,1) and return 0
#all the fitted standardized residuals from the different KRG kernels
fig, ax = plt.subplots(figsize=(10, 6))
all_std_values = []
x = np.linspace(-4, 4, 100)
for kernel_name, z_vals in zip(kernels_to_test, all_z_values):
mu, std = norm.fit(np.array(z_vals))
all_std_values.append(std)
print(f"FIT VALUES for kernel {kernel_name}: mu =", mu, "std =", std)
p = norm.pdf(x, mu, std)
ax.plot(x, p, label=f'ker={kernel_name}')
def run_ego(initial_dataset, surrogate_model, n_ego_iter, xlimits, simulation):
ax.plot(x, norm.pdf(x, 0, 1), 'g--', label='N(0,1) Gaussian')
ax.set_xlabel('std. residual', fontsize=20)
ax.set_ylabel('Standardized Residual', fontsize=20)
ax.tick_params(axis='both', labelsize=18)
ax.grid(True, alpha=0.3)
ax.legend()
plt.savefig(f'{results_folder}/loo_standardized_residuals_gaussian_fit.pdf')
plt.close(fig)
best_kernel = kernels_to_test[np.argmin(np.array(all_std_values))]
#train the model using the whole dataset
sm_final = KRG(design_space=design_space,
n_start=25,
print_global=False,
eval_noise=True,
corr=best_kernel)
sm_final.set_training_values(full_x_doe, full_y_doe)
sm_final.train()
#save the train data
with open(f'{surrogate_folder_name}/train_data.pkl', 'wb') as f:
pickle.dump((full_x_doe, full_y_doe), f)
#save the trained surrogate model
with open(f'{surrogate_folder_name}/trained_surrogate_model_kernel_{best_kernel}.pkl', 'wb') as f:
pickle.dump(sm_final, f)
return sm_final
def run_ego(surrogate_model, n_ego_iter, xlimits, simulation):
sm = surrogate_model #the surrogate is trained on overlap values (positive) sm = surrogate_model #the surrogate is trained on overlap values (positive)
#load the associated fold training data #load the associated fold training data
xdoe_train_loaded, ydoe_train_loaded = pickle.load(open(f'{surrogate_folder_name}/train_data.pkl', 'rb')) xdoe_train_loaded = copy.deepcopy(initial_dataset[:, :-1])
x_data = copy.deepcopy(xdoe_train_loaded) ydoe_train_loaded = copy.deepcopy(initial_dataset[:, -1]) # ydoe_train_loaded > 0
y_data = - copy.deepcopy(ydoe_train_loaded) x_data = copy.deepcopy(initial_dataset[:, :-1])
n_start = 25 #number of initial random points to sample for EGO refinement y_data = copy.deepcopy(initial_dataset[:, -1]) # y_data > 0
n_start = 40 #number of initial random points to sample for EGO refinement
x_et_k_tested_history = [] x_et_k_tested_history = []
EI_history = [] EI_history = []
for k in range(n_ego_iter): for k in range(n_ego_iter):
d0_start = np.atleast_2d(xlimits[0][0] + np.random.rand(n_start)*(xlimits[0][1]-xlimits[0][0])) d0_start = np.atleast_2d(xlimits[0][0] + np.random.rand(n_start)*(xlimits[0][1]-xlimits[0][0]))
d1_start = np.atleast_2d(xlimits[1][0] + np.random.rand(n_start)*(xlimits[1][1]-xlimits[1][0])) d1_start = np.atleast_2d(xlimits[1][0] + np.random.rand(n_start)*(xlimits[1][1]-xlimits[1][0]))
x_start = np.hstack((d0_start.T, d1_start.T)) x_start = np.hstack((d0_start.T, d1_start.T))
f_min_k = np.min(y_data) #the most negative data point is the best point f_min_k = np.min(- y_data) #the most negative data point is the best point
opt_all = np.array(#maximizes the expected improvement in a region around x_st, an element of the start point opt_all = np.array(#maximizes the expected improvement in a region around x_st, an element of the start point
[ [
minimize(lambda x: float(-EI(sm, np.atleast_2d(x), f_min_k)), x_st, method="SLSQP", bounds=xlimits) minimize(lambda x: float(-EI(sm, np.atleast_2d(x), f_min_k)), x_st, method="SLSQP", bounds=xlimits)
@@ -302,7 +259,7 @@ def run_ego(surrogate_model, n_ego_iter, xlimits, simulation):
print(d_et_k) print(d_et_k)
simulation.update_propagation_distances(d_et_k) simulation.update_propagation_distances(d_et_k)
simulation.run() simulation.run()
y_et_k = - simulation.get_best_opt_val() #evaluate the objective function at the new point y_et_k = simulation.get_best_opt_val() #evaluate the objective function at the new point; y_et_k < 0
x_et_k_tested = np.atleast_2d(x_et_k) x_et_k_tested = np.atleast_2d(x_et_k)
x_et_k_tested_history.append(x_et_k_tested) x_et_k_tested_history.append(x_et_k_tested)
@@ -333,31 +290,52 @@ def run_ego(surrogate_model, n_ego_iter, xlimits, simulation):
ax.set_ylim(xlimits[1]) ax.set_ylim(xlimits[1])
ax.legend(loc='upper left', framealpha=0.5) ax.legend(loc='upper left', framealpha=0.5)
fig.tight_layout() fig.tight_layout()
fig.savefig(f'{surrogate_folder_name}/optimisation_{k}.png') fig.savefig(f'{surrogate_folder_name}/optimisation_{k}.pdf')
plt.close(fig) plt.close(fig)
fig, ax = plt.subplots(figsize=(8,6))
d0_grid, d1_grid = np.meshgrid(np.linspace(xlimits[0][0], xlimits[0][1], 100),
np.linspace(xlimits[1][0], xlimits[1][1], 100))
y_pred_grid = np.zeros_like(d0_grid)
for i in range(d0_grid.shape[0]):
for j in range(d0_grid.shape[1]):
y_pred_grid[i,j] = sm.predict_values(np.atleast_2d([d0_grid[i,j], d1_grid[i,j]]))
contour = ax.contourf(d0_grid, d1_grid, y_pred_grid, levels=50, cmap='viridis')
fig.colorbar(contour, ax=ax, label='predicted overlap')
ax.scatter(xdoe_train_loaded[:,0], xdoe_train_loaded[:,1], color='blue', label='DOE Points')
ax.scatter(x_data[len(xdoe_train_loaded):,0], x_data[len(xdoe_train_loaded):,1], color='red', label='EGO Evaluated Points')
ax.set_xlabel('d0 [um]', fontsize=15)
ax.set_ylabel('d1 [um]', fontsize=15)
ax.set_xlim(xlimits[0])
ax.set_ylim(xlimits[1])
ax.legend(loc='upper left', framealpha=0.5, fontsize=15)
for spine in ax.spines.values():
spine.set_linewidth(1.5)
ax.set_aspect('equal')
fig.tight_layout()
fig.savefig(f'{surrogate_folder_name}/surrogate_landscape_{k}.pdf')
plt.close(fig)
y_data = np.atleast_2d(np.append(y_data, y_et_k)).reshape(-1,1) y_data = np.atleast_2d(np.append(y_data, y_et_k)).reshape(-1,1)
x_data = np.atleast_2d(np.append(x_data, x_et_k_tested, axis=0)) x_data = np.atleast_2d(np.append(x_data, x_et_k_tested, axis=0))
sm.set_training_values(x_data, - y_data) #train the surrogate with positive values. sm.set_training_values(x_data, y_data) #train the surrogate with positive values.
sm.train() #train the model with the new data point added to the previous ones sm.train() #train the model with the new data point
print(f"{k}-th EGO refinement complete") print(f"{k+1}-th EGO refinement complete")
#for this k iteration out of the n_start, make a plot of the EI surface and append the poisition of the
#selected point. Save the figure to a plot list ims, which will be used to create an animation at the end of the EGO loop
#To test the EI surface, use a custom grid of d0,d1 points, and compute the EI for each point in the grid. Then plot the EI surface as a contour plot, and mark the selected point with a red dot.
ind_best = np.argmin(y_data) ind_best = np.argmax(y_data)
x_opt = x_data[ind_best] x_opt = x_data[ind_best]
y_opt = - y_data[ind_best] y_opt = y_data[ind_best]
np.savetxt(f'{surrogate_folder_name}/ego_opt_results.txt', np.hstack((x_opt,y_opt))) np.savetxt(f'{surrogate_folder_name}/ego_opt_results.txt', np.hstack((x_opt,y_opt)))
ims = [] ims = []
fig = plt.figure(figsize=[10, 10]) fig = plt.figure(figsize=[10, 10])
ax = plt.gca() ax = plt.gca()
ax.axes.get_xaxis().set_visible(False) ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False) ax.axes.get_yaxis().set_visible(False)
for k in range(n_ego_iter): for k in range(n_ego_iter):
image_pt = mpimg.imread(f"{surrogate_folder_name}/optimisation_{k}.png") image_pt = mpimg.imread(f"{surrogate_folder_name}/optimisation_{k}.pdf")
im = plt.imshow(image_pt) im = plt.imshow(image_pt)
ims.append([im]) ims.append([im])
@@ -366,7 +344,6 @@ def run_ego(surrogate_model, n_ego_iter, xlimits, simulation):
ani.save(f'{surrogate_folder_name}/ego_optimization.gif', writer='pillow') ani.save(f'{surrogate_folder_name}/ego_optimization.gif', writer='pillow')
#plot of the EI history #plot of the EI history
fig, ax = plt.subplots(figsize=(8,6)) fig, ax = plt.subplots(figsize=(8,6))
ax.plot(range(1, n_ego_iter+1), EI_history, marker='o') ax.plot(range(1, n_ego_iter+1), EI_history, marker='o')
ax.set_title('Expected Improvement History') ax.set_title('Expected Improvement History')
@@ -387,7 +364,7 @@ def run_ego(surrogate_model, n_ego_iter, xlimits, simulation):
ax = fig.add_subplot(111, projection='3d') ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(d0_grid_mesh, d1_grid_mesh, sm_predictions, cmap='viridis', alpha=0.7, edgecolor='none') surf = ax.plot_surface(d0_grid_mesh, d1_grid_mesh, sm_predictions, cmap='viridis', alpha=0.7, edgecolor='none')
ax.scatter(xdoe_train_loaded[:,0], xdoe_train_loaded[:,1], ydoe_train_loaded.flatten(), color='blue', label='DOE Points') ax.scatter(xdoe_train_loaded[:,0], xdoe_train_loaded[:,1], ydoe_train_loaded.flatten(), color='blue', label='DOE Points')
ax.scatter(x_data[len(xdoe_train_loaded):,0], x_data[len(xdoe_train_loaded):,1], - y_data[len(xdoe_train_loaded):].flatten(), color='red', label='EGO Evaluated Points') ax.scatter(x_data[len(xdoe_train_loaded):,0], x_data[len(xdoe_train_loaded):,1], y_data[len(xdoe_train_loaded):].flatten(), color='red', label='EGO Evaluated Points')
ax.scatter([x_opt[0]], [x_opt[1]], [y_opt], color='green', s=100, label='Best Point', edgecolor='black') ax.scatter([x_opt[0]], [x_opt[1]], [y_opt], color='green', s=100, label='Best Point', edgecolor='black')
ax.set_xlabel('d0 [um]') ax.set_xlabel('d0 [um]')
ax.set_ylabel('d1 [um]') ax.set_ylabel('d1 [um]')
@@ -395,6 +372,7 @@ def run_ego(surrogate_model, n_ego_iter, xlimits, simulation):
fig.tight_layout() fig.tight_layout()
fig.savefig(f'{surrogate_folder_name}/refined_surrogate_model_surface.pdf') fig.savefig(f'{surrogate_folder_name}/refined_surrogate_model_surface.pdf')
plt.close(fig) plt.close(fig)
#contour plot #contour plot
fig, ax = plt.subplots(figsize=(8,6)) fig, ax = plt.subplots(figsize=(8,6))
cp = ax.contourf(d0_grid_mesh, d1_grid_mesh, sm_predictions, levels=50, cmap='viridis') cp = ax.contourf(d0_grid_mesh, d1_grid_mesh, sm_predictions, levels=50, cmap='viridis')
@@ -412,33 +390,26 @@ def run_ego(surrogate_model, n_ego_iter, xlimits, simulation):
plt.close(fig) plt.close(fig)
pickle.dump(sm, open(f'{surrogate_folder_name}/refined_surrogate_model.pkl', 'wb')) pickle.dump(sm, open(f'{surrogate_folder_name}/refined_surrogate_model.pkl', 'wb'))
pickle.dump((x_data, y_data), open(f'{surrogate_folder_name}/refined_model_training_data.pkl', 'wb')) pickle.dump((x_data, y_data), open(f'{surrogate_folder_name}/refined_dataset.pkl', 'wb'))
return x_opt, y_opt, ind_best, x_data, y_data return x_opt, y_opt, ind_best, x_data, y_data
def train_surrogate_model(initial_dataset, corr_function):
full_x_doe = initial_dataset[:, :2]
full_y_doe = initial_dataset[:, 2].reshape(-1, 1)
############################## sm = KRG(design_space = design_space,
#EGO setup n_start = 40,
############################## print_global = False,
if __name__ == "__main__": eval_noise = False,
n_ego_iter = 20 corr = corr_function,
xlimits = np.array([[100,700], [100,700]]) # d0, d1 in [um] )
seed = 777 sm.set_training_values(full_x_doe, full_y_doe)
np.random.seed(seed) sm.train()
design_space = DesignSpace(xlimits, seed=seed)
criterion = 'EI'
ndoe = 25 #all the samples will be used for leave-one-out cross-validation
#first step: check if a DOE exists, if not not, build one return sm
surrogate_folder_name = 'single_converter_symm_surrogate_mf_filt'
results_folder = 'single_converter_symm_results_ego_mf_filt'
if not os.path.exists(surrogate_folder_name):
os.makedirs(surrogate_folder_name)
if not os.path.exists(results_folder):
os.makedirs(results_folder)
simulation = setup_optical_system() def get_doe():
if not os.path.exists(f'{doe_folder_name}/ego_doe.npy'):
if not os.path.exists(f'{surrogate_folder_name}/ego_doe.npy'):
#build DOE #build DOE
print("Building DOE...") print("Building DOE...")
sampling = LHS(xlimits=xlimits, seed=seed) sampling = LHS(xlimits=xlimits, seed=seed)
@@ -462,18 +433,57 @@ if __name__ == "__main__":
f.write(f"{xd[0]:.6e} {xd[1]:.6e} {yd[0]:.6e}\n") f.write(f"{xd[0]:.6e} {xd[1]:.6e} {yd[0]:.6e}\n")
else: else:
print("Loading existing DOE...") print("Loading existing DOE...")
initial_dataset = np.load(f'{surrogate_folder_name}/ego_doe.npy') initial_dataset = np.load(f'{doe_folder_name}/ego_doe.npy')
xdoe = initial_dataset[:, :xlimits.shape[0]] xdoe = initial_dataset[:, :xlimits.shape[0]]
ydoe = initial_dataset[:, xlimits.shape[0]:] ydoe = initial_dataset[:, xlimits.shape[0]:]
initial_dataset = np.hstack((xdoe,ydoe)) initial_dataset = np.hstack((xdoe,ydoe))
#save the doe #save the doe
np.save(f'{surrogate_folder_name}/ego_doe.npy', initial_dataset) np.save(f'{doe_folder_name}/ego_doe.npy', initial_dataset)
np.savetxt(f'{doe_folder_name}/ego_doe.txt', initial_dataset)
trained_surrogate_model = leave_one_out(initial_dataset) return initial_dataset
##############################
#EGO setup
##############################
if __name__ == "__main__":
xlimits = np.array([[100,700], [100,700]]) # d0, d1 in [um]
seed = 777
np.random.seed(seed)
design_space = DesignSpace(xlimits, seed=seed)
ndoe = 20 #number of DOE samples
n_ego_iter = 15 #number of EGO refinement iterations
corr_function_name = 'matern32'
doe_folder_name = 'DOE'
surrogate_folder_name = corr_function_name + '_surrogate_filtered'
if not os.path.exists(doe_folder_name):
os.makedirs(doe_folder_name)
if not os.path.exists(surrogate_folder_name):
os.makedirs(surrogate_folder_name)
simulation = setup_optical_system()
#DEFINE AN INITIAL DESIGN OF EXPERIMENT (DOE)
initial_dataset = get_doe()
#TRAIN THE SURROGATE ON THE DOE using specified correlation function
trained_surrogate_model = train_surrogate_model(initial_dataset,
corr_function=corr_function_name)
#RUN EGO REFINEMENT for n_ego_iter iterations
print("Starting EGO optimization...") print("Starting EGO optimization...")
x_opt, y_opt, ind_best, x_data, y_data = run_ego(surrogate_model=trained_surrogate_model, x_opt, y_opt, ind_best, x_data, y_data = run_ego(
initial_dataset=initial_dataset,
surrogate_model=trained_surrogate_model,
n_ego_iter=n_ego_iter, n_ego_iter=n_ego_iter,
xlimits=xlimits, xlimits=xlimits,
simulation=simulation) simulation=simulation)
#EVALUATE THE MODEL PERFORMANCE BY RUNNING A LOO CROSS-VALIDATION
refined_dataset = pickle.load(open(f'{surrogate_folder_name}/refined_dataset.pkl', 'rb'))
leave_one_out(refined_dataset, corr_function_name)
+108
View File
@@ -0,0 +1,108 @@
import mpid as pid
import numpy as np
import matplotlib.pyplot as plt
import os
if not os.path.exists("results"):
os.makedirs("results")
#Space grid definition
N = 1025
unit_cell_pitch = 0.700 #i.e., 700 [nm]
n0 = 1.444
n1 = 1.0
n2 = 1.444
mat_refr_index = np.array([n0, n1, n2])
ms_max_available_diameter = 122 #[um]
output_fiber_core_diameter = 19 #[um]
opt_max_eval = 140
def setup_optical_system()->pid.Simulation:
'''
Builds the mode converter optical system using
the specified propagation distances `d`, runs the optimization
and returns the best objective value.
Args:
xdoe_points (np.ndarray): Array containing the DOE points.
Each point consists of the propagation distances d0 and d1
Returns:
float: The best objective value obtained from the optimization.
'''
central_wavelength_um = 1.55
wavelengths_to_test_um = [1.530, 1.550, 1.570] #[um]
source_files = ["fiber_modes/SMF/SMF_LP01_field_distribution_lda_1530_N_1025.npy",
"fiber_modes/SMF/SMF_LP01_field_distribution_lda_1550_N_1025.npy",
"fiber_modes/SMF/SMF_LP01_field_distribution_lda_1570_N_1025.npy"]
sources = [pid.Source(input_field=np.load(file),
wavelength=wvl,
field_norm=True) for file, wvl in zip(source_files, wavelengths_to_test_um)]
target_files = ["fiber_modes/FMF/FMF_LP21_field_distribution_lda_1530_N_1025.npy",
"fiber_modes/FMF/FMF_LP21_field_distribution_lda_1550_N_1025.npy",
"fiber_modes/FMF/FMF_LP21_field_distribution_lda_1570_N_1025.npy"]
targets = [pid.Target(input_field=np.load(file),
associated_source=sources[i],
wavelength=wvl,
field_norm=True) for i, (file, wvl) in enumerate(zip(target_files, wavelengths_to_test_um))]
initial_space_grid = pid.SpaceGrid(
side_pixels=N,
unit_cell_pitch=unit_cell_pitch,
materials_refr_index=mat_refr_index,
xy_symmetry=True,
)
#setup the simulation instance
simulation = pid.Simulation(
sources=sources,
targets=targets,
central_wavelength=central_wavelength_um,
space_grid=initial_space_grid,
n_cpu=1
)
simulation.set_optimization(
opt_param_min=0,
opt_param_max=1,
ms_max_avail_diameter=ms_max_available_diameter,
phase_mask_init_size=output_fiber_core_diameter/3,
opt_max_eval=opt_max_eval,
#conic_filter_radius=1.0,
results_while_solving = False,
save_results = False,
)
return simulation
if __name__ == "__main__":
simulation = setup_optical_system()
results_folder_name = 'results_nf'
phase_mask_1 = np.load(f"{results_folder_name}/filtered_phase_mask_1.npy").flatten()
phase_mask_2 = np.load(f"{results_folder_name}/filtered_phase_mask_2.npy").flatten()
d0 = 195 #propagation distance: source - MS1 [um] FILTERED mf (using LP01 of SMF)
d1 = 129 #propagation distance: MS1 - MS2 [um] FILTERED mf (using LP01 of SMF)
d2 = d0 #propagation distance: MS2 - target [um]
d = np.array([d0, d1, d2])
results = simulation.source_sweep(distances = d, input_phase_masks=[phase_mask_1, phase_mask_2])
#make a plot of the results, Use the keys as x axis and values as y axis
fig, ax = plt.subplots(figsize=(6, 6))
ax.plot(np.array(list(results.keys()))*1e9, np.array(list(results.values())), marker='o', linewidth=2)
#make the y tick labels to only have two decimal places
ax.yaxis.set_major_formatter(plt.FormatStrFormatter('%.3f'))
ax.set_xlabel('wavelength [nm]', fontsize=15)
ax.set_ylabel('overlap', fontsize=15)
ax.tick_params(axis='both', which='major', labelsize=15)
ax.set_aspect('auto')
plt.tight_layout()
plt.savefig(f"{results_folder_name}/source_sweep_results.pdf")
+31 -23
View File
@@ -1,26 +1,28 @@
import mpid as pid import mpid as pid
import numpy as np import numpy as np
import os import os
if not os.path.exists("results"): if not os.path.exists("results_sorter"):
os.makedirs("results") os.makedirs("results_sorter")
#Space grid definition #Space grid definition
N = 1025 N = 1025
unit_cell_pitch = 0.700 #i.e., 700 [nm] unit_cell_pitch = 0.700 #i.e., 700 [nm]
n0 = 1.45 n0 = 1.45
n1 = 1.0 n1 = 1.45
n2 = 1.45 n2 = 1.45
mat_refr_index = np.array([n0, n1, n2]) n3 = 1.45
mat_refr_index = np.array([n0, n1, n2, n3])
d0 = 476 #propagation distance: source - MS1 [um] d0 = 500 #propagation distance: source - MS1 [um]
d1 = 510 #propagation distance: MS1 - MS2 [um] d1 = d0 #propagation distance: MS1 - MS2 [um]
d2 = d0 #propagation distance: MS2 - target [um] d2 = d0 #propagation distance: MS2 - MS3 [um]
d = np.array([d0, d1, d2]) d3 = d0 #propagation distance: MS3 - target [um]
d = np.array([d0, d1, d2, d3])
ms_max_available_diameter = 122 #[um] ms_max_available_diameter = 250 #[um]
output_fiber_core_diameter = 19 #[um] output_fiber_core_diameter = 19 #[um]
opt_max_eval = 140 opt_max_eval = 200
space_grid = pid.SpaceGrid( space_grid = pid.SpaceGrid(
side_pixels=N, side_pixels=N,
@@ -32,38 +34,44 @@ space_grid = pid.SpaceGrid(
wavelength = 1.55 wavelength = 1.55
#define the sources #define the sources
source1 = pid.Source(input_field='fiber_modes/LP11_field_distribution_lda_1550_N_1025.npy', source1 = pid.Source(input_field='fiber_modes/FMF/FMF_LP01_field_distribution_lda_1550_N_1025.npy',
wavelength=wavelength, wavelength=wavelength,
#space_grid=space_grid, #space_grid=space_grid,
field_norm=True) field_norm=True)
source2 = pid.Source(input_field='fiber_modes/LP21_field_distribution_lda_1550_N_1025.npy', source2 = pid.Source(input_field='fiber_modes/FMF/FMF_LP11_field_distribution_lda_1550_N_1025.npy',
wavelength=wavelength,
#space_grid=space_grid,
field_norm=True)
source3 = pid.Source(input_field='fiber_modes/FMF/FMF_LP21_field_distribution_lda_1550_N_1025.npy',
wavelength=wavelength, wavelength=wavelength,
#space_grid=space_grid, #space_grid=space_grid,
field_norm=True) field_norm=True)
target = pid.Target(input_field='fiber_modes/LP01_field_distribution_lda_1550_N_1025.npy', target1 = pid.Target(input_field='fiber_modes/SMF/SMF_LP01_field_distribution_lda_1550_N_1025.npy',
center=np.array([20,0]), center=np.array([20,0]),
associated_source=source1, associated_source=source1,
wavelength=wavelength, wavelength=wavelength,
#space_grid=space_grid, #space_grid=space_grid,
field_norm=True) field_norm=True)
target2 = pid.Target(input_field='fiber_modes/LP01_field_distribution_lda_1550_N_1025.npy', target2 = pid.Target(input_field='fiber_modes/SMF/SMF_LP01_field_distribution_lda_1550_N_1025.npy',
center=np.array([-20,0]), center=np.array([0,0]),
associated_source=source2, associated_source=source2,
wavelength=wavelength, wavelength=wavelength,
#space_grid=space_grid, #space_grid=space_grid,
field_norm=True) field_norm=True)
#make a plot of sources and targets target3 = pid.Target(input_field='fiber_modes/SMF/SMF_LP01_field_distribution_lda_1550_N_1025.npy',
#target.plot2D(plot_zoom_x=100, plot_zoom_y=100, choose_quantity="amplitude", save_name="results/target_plot") center=np.array([-20,0]),
#target2.plot2D(plot_zoom_x=100, plot_zoom_y=100, choose_quantity="amplitude", save_name="results/target2_plot") associated_source=source3,
#source1.plot2D(plot_zoom_x=100, plot_zoom_y=100, choose_quantity="amplitude", save_name="results/source1_plot") wavelength=wavelength,
#source2.plot2D(plot_zoom_x=100, plot_zoom_y=100, choose_quantity="amplitude", save_name="results/source2_plot") #space_grid=space_grid,
field_norm=True)
sim = pid.Simulation( sim = pid.Simulation(
sources=[source1, source2], sources=[source1, source2, source3],
targets=[target, target2], targets=[target1, target2, target3],
central_wavelength=wavelength, central_wavelength=wavelength,
space_grid=space_grid, space_grid=space_grid,
n_cpu=1 n_cpu=1
@@ -78,7 +86,7 @@ sim.set_optimization(opt_param_min=opt_param_min,
phase_mask_init_size=output_fiber_core_diameter, phase_mask_init_size=output_fiber_core_diameter,
opt_max_eval=opt_max_eval, opt_max_eval=opt_max_eval,
strategy = 'mean', strategy = 'mean',
#conic_filter_radius = 1.0, #i.e. 700 [nm] #conic_filter_radius = 1.0,
) )
sim.run() sim.run()