mirror of
https://github.com/edoverg/MPID.git
synced 2026-10-14 23:26:38 +00:00
implemented new filtering system based on a Filter Class. Previously, filtering was made expensive by extreme padding, now this has been fixed by choosing a more appropriate padding size. The gradient chain based on unitcells has been implemented but the results have yet to be tested using FDTD, which will be the next step. Added some utilities to visualize the effect of filter radius on given input masks and added all data concerning unit cell simulations. Still to be done is to fix materials in the MEEP simulations, so as to account for true material behavior instead of relying on constant refractive indices.
This commit is contained in:
+135
-121
@@ -1,148 +1,162 @@
|
||||
import autograd.numpy as npa
|
||||
import numpy as np
|
||||
from scipy.fft import next_fast_len
|
||||
from numpy.typing import ArrayLike as ArrayLikeType
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
def _centered(arr: npa.ndarray, newshape: ArrayLikeType) -> npa.ndarray:
|
||||
"""Formats the output of an FFT to center the zero-frequency component.
|
||||
class Filter:
|
||||
def __init__(self, radius, space_grid):
|
||||
self.radius = radius
|
||||
self.space_grid = space_grid
|
||||
self.p = int(np.ceil(radius / self.space_grid.get_resolution()))
|
||||
self.Fx = next_fast_len(self.space_grid.get_Nx() + self.p)
|
||||
self.Fy = next_fast_len(self.space_grid.get_Ny() + self.p)
|
||||
self.H = self._construct_kernel(radius, space_grid)
|
||||
|
||||
def apply_filter(self, input_parameters):
|
||||
input_parameters_2d = input_parameters.reshape(self.space_grid.get_Nx(), self.space_grid.get_Ny())
|
||||
input_parameters_padded = self._edge_pad(input_parameters_2d, (((self.Fx-1)//2, (self.Fx)//2), ((self.Fy-1)//2, (self.Fy)//2)))
|
||||
#print("input_parameters_padded shape:",input_parameters_padded.shape)
|
||||
input_parameters_fft = npa.fft.fft2(input_parameters_padded)
|
||||
|
||||
Y = input_parameters_fft * self.H
|
||||
y = self._centered(
|
||||
npa.real(npa.fft.ifft2(Y)),
|
||||
(self.space_grid.get_Nx(), self.space_grid.get_Ny())
|
||||
)
|
||||
return y.flatten()
|
||||
|
||||
A helper function borrowed from SciPy:
|
||||
https://github.com/scipy/scipy/blob/v1.4.1/scipy/signal/signaltools.py#L263-L270
|
||||
def _centered(self, arr: npa.ndarray, newshape: ArrayLikeType) -> npa.ndarray:
|
||||
"""Formats the output of an FFT to center the zero-frequency component.
|
||||
|
||||
Args:
|
||||
arr: output array from an FFT operation.
|
||||
newshape: 1d array with two elements (integers) specifying the dimensions
|
||||
of the array to be returned.
|
||||
A helper function borrowed from SciPy:
|
||||
https://github.com/scipy/scipy/blob/v1.4.1/scipy/signal/signaltools.py#L263-L270
|
||||
|
||||
Returns:
|
||||
The input array with the zero-frequency component as the central element.
|
||||
"""
|
||||
newshape = npa.asarray(newshape)
|
||||
currshape = npa.array(arr.shape)
|
||||
startind = (currshape - newshape) // 2
|
||||
endind = startind + newshape
|
||||
myslice = [slice(startind[k], endind[k]) for k in range(len(endind))]
|
||||
Args:
|
||||
arr: output array from an FFT operation.
|
||||
newshape: 1d array with two elements (integers) specifying the dimensions
|
||||
of the array to be returned.
|
||||
|
||||
return arr[tuple(myslice)]
|
||||
Returns:
|
||||
The input array with the zero-frequency component as the central element.
|
||||
"""
|
||||
newshape = npa.asarray(newshape)
|
||||
currshape = npa.array(arr.shape)
|
||||
startind = (currshape - newshape) // 2
|
||||
endind = startind + newshape
|
||||
myslice = [slice(startind[k], endind[k]) for k in range(len(endind))]
|
||||
|
||||
def _edge_pad(arr: npa.ndarray, pad: npa.ndarray) -> npa.ndarray:
|
||||
"""Border-pads the edges of an array.
|
||||
return arr[tuple(myslice)]
|
||||
|
||||
Used to preprocess the design weights prior to convolution with the filter.
|
||||
Border padding an image will set the value of each padded pixel equal to
|
||||
the value of the nearest pixel in the image. Used to implement feature-
|
||||
preserving convolution filters that prevent unwanted edge effects.
|
||||
def _edge_pad(self, arr: npa.ndarray, pad: npa.ndarray) -> npa.ndarray:
|
||||
"""Border-pads the edges of an array.
|
||||
|
||||
Args:
|
||||
arr: 2d array whose borders contain the values to use for padding
|
||||
pad: 2x2 array of integers indicating the size
|
||||
of the borders to pad the array with.
|
||||
Used to preprocess the design weights prior to convolution with the filter.
|
||||
Border padding an image will set the value of each padded pixel equal to
|
||||
the value of the nearest pixel in the image. Used to implement feature-
|
||||
preserving convolution filters that prevent unwanted edge effects.
|
||||
|
||||
Returns:
|
||||
A 2d array with border padding.
|
||||
"""
|
||||
# fill sides
|
||||
left = npa.tile(arr[0, :], (pad[0][0], 1))
|
||||
right = npa.tile(arr[-1, :], (pad[0][1], 1))
|
||||
top = npa.tile(arr[:, 0], (pad[1][0], 1)).transpose()
|
||||
bottom = npa.tile(arr[:, -1], (pad[1][1], 1)).transpose()
|
||||
Args:
|
||||
arr: 2d array whose borders contain the values to use for padding
|
||||
pad: 2x2 array of integers indicating the size
|
||||
of the borders to pad the array with.
|
||||
|
||||
# fill corners
|
||||
top_left = npa.tile(arr[0, 0], (pad[0][0], pad[1][0]))
|
||||
top_right = npa.tile(arr[-1, 0], (pad[0][1], pad[1][0]))
|
||||
bottom_left = npa.tile(arr[0, -1], (pad[0][0], pad[1][1]))
|
||||
bottom_right = npa.tile(arr[-1, -1], (pad[0][1], pad[1][1]))
|
||||
Returns:
|
||||
A 2d array with border padding.
|
||||
"""
|
||||
# fill sides
|
||||
left = npa.tile(arr[0, :], (pad[0][0], 1))
|
||||
right = npa.tile(arr[-1, :], (pad[0][1], 1))
|
||||
top = npa.tile(arr[:, 0], (pad[1][0], 1)).transpose()
|
||||
bottom = npa.tile(arr[:, -1], (pad[1][1], 1)).transpose()
|
||||
|
||||
# fill corners
|
||||
top_left = npa.tile(arr[0, 0], (pad[0][0], pad[1][0]))
|
||||
top_right = npa.tile(arr[-1, 0], (pad[0][1], pad[1][0]))
|
||||
bottom_left = npa.tile(arr[0, -1], (pad[0][0], pad[1][1]))
|
||||
bottom_right = npa.tile(arr[-1, -1], (pad[0][1], pad[1][1]))
|
||||
|
||||
if pad[0][0] > 0 and pad[0][1] > 0 and pad[1][0] > 0 and pad[1][1] > 0:
|
||||
return npa.concatenate(
|
||||
(
|
||||
npa.concatenate((top_left, top, top_right)),
|
||||
npa.concatenate((left, arr, right)),
|
||||
npa.concatenate((bottom_left, bottom, bottom_right)),
|
||||
),
|
||||
axis=1,
|
||||
)
|
||||
elif pad[0][0] == 0 and pad[0][1] == 0 and pad[1][0] > 0 and pad[1][1] > 0:
|
||||
return npa.concatenate((top, arr, bottom), axis=1)
|
||||
elif pad[0][0] > 0 and pad[0][1] > 0 and pad[1][0] == 0 and pad[1][1] == 0:
|
||||
return npa.concatenate((left, arr, right), axis=0)
|
||||
elif pad[0][0] == 0 and pad[0][1] == 0 and pad[1][0] == 0 and pad[1][1] == 0:
|
||||
return arr
|
||||
else:
|
||||
raise ValueError("At least one of the padding numbers is invalid.")
|
||||
|
||||
def _quarter_to_full_kernel(self, arr: npa.ndarray, pad_to: npa.ndarray) -> npa.ndarray:
|
||||
"""Constructs the full kernel from its nonnegative quadrant.
|
||||
|
||||
Args:
|
||||
arr: 2d input array representing the nonnegative quadrant of a
|
||||
filter kernel with C4v symmetry.
|
||||
pad_to: 1d array with two elements (integers) specifying the size
|
||||
of the zero padding.
|
||||
|
||||
Returns:
|
||||
The complete kernel.
|
||||
"""
|
||||
pad_size = pad_to - 2 * npa.array(arr.shape) + 1
|
||||
|
||||
top = npa.zeros((pad_size[0], arr.shape[1]))
|
||||
bottom = npa.zeros((pad_size[0], arr.shape[1] - 1))
|
||||
middle = npa.zeros((pad_to[0], pad_size[1]))
|
||||
|
||||
top_left = arr[:, :]
|
||||
top_right = npa.flipud(arr[1:, :])
|
||||
bottom_left = npa.fliplr(arr[:, 1:])
|
||||
bottom_right = npa.flipud(
|
||||
npa.fliplr(arr[1:, 1:])
|
||||
) # equivalent to flip, but flip is incompatible with autograd
|
||||
|
||||
if pad[0][0] > 0 and pad[0][1] > 0 and pad[1][0] > 0 and pad[1][1] > 0:
|
||||
return npa.concatenate(
|
||||
(
|
||||
npa.concatenate((top_left, top, top_right)),
|
||||
npa.concatenate((left, arr, right)),
|
||||
middle,
|
||||
npa.concatenate((bottom_left, bottom, bottom_right)),
|
||||
),
|
||||
axis=1,
|
||||
)
|
||||
elif pad[0][0] == 0 and pad[0][1] == 0 and pad[1][0] > 0 and pad[1][1] > 0:
|
||||
return npa.concatenate((top, arr, bottom), axis=1)
|
||||
elif pad[0][0] > 0 and pad[0][1] > 0 and pad[1][0] == 0 and pad[1][1] == 0:
|
||||
return npa.concatenate((left, arr, right), axis=0)
|
||||
elif pad[0][0] == 0 and pad[0][1] == 0 and pad[1][0] == 0 and pad[1][1] == 0:
|
||||
return arr
|
||||
else:
|
||||
raise ValueError("At least one of the padding numbers is invalid.")
|
||||
|
||||
def _quarter_to_full_kernel(arr: npa.ndarray, pad_to: npa.ndarray) -> npa.ndarray:
|
||||
"""Constructs the full kernel from its nonnegative quadrant.
|
||||
def _construct_kernel(self,radius,space_grid):
|
||||
'''Applies a conic filter to the input parameters. The filter is defined as a
|
||||
conic region of a specified radius, where the values undergo a weighted average.
|
||||
|
||||
Args:
|
||||
arr: 2d input array representing the nonnegative quadrant of a
|
||||
filter kernel with C4v symmetry.
|
||||
pad_to: 1d array with two elements (integers) specifying the size
|
||||
of the zero padding.
|
||||
Args:
|
||||
input_parameters: the input parameters to be filtered (2D)
|
||||
radius: the radius of the conic filter in micrometers
|
||||
space_grid: a SpaceGrid object containing the size and resolution of the parameters
|
||||
|
||||
Returns:
|
||||
The filtered parameters (2D)
|
||||
'''
|
||||
Nx = space_grid.get_Nx()
|
||||
Ny = space_grid.get_Ny()
|
||||
|
||||
Returns:
|
||||
The complete kernel.
|
||||
"""
|
||||
pad_size = pad_to - 2 * npa.array(arr.shape) + 1
|
||||
#positive quadrant of the filter
|
||||
xv = npa.arange(0, Nx//2+1) * (space_grid.get_resolution())
|
||||
yv = npa.arange(0, Ny//2+1) * (space_grid.get_resolution())
|
||||
#note that the filter's center is at (0,0)
|
||||
Xv, Yv = npa.meshgrid(xv, yv)
|
||||
|
||||
top = npa.zeros((pad_size[0], arr.shape[1]))
|
||||
bottom = npa.zeros((pad_size[0], arr.shape[1] - 1))
|
||||
middle = npa.zeros((pad_to[0], pad_size[1]))
|
||||
h = npa.where(Xv**2 + Yv**2 < radius**2, (1 - npa.sqrt(abs(Xv**2 + Yv**2)) / radius),0)
|
||||
|
||||
top_left = arr[:, :]
|
||||
top_right = npa.flipud(arr[1:, :])
|
||||
bottom_left = npa.fliplr(arr[:, 1:])
|
||||
bottom_right = npa.flipud(
|
||||
npa.fliplr(arr[1:, 1:])
|
||||
) # equivalent to flip, but flip is incompatible with autograd
|
||||
#impulse response is padded to [Fx,Fy]
|
||||
h = self._quarter_to_full_kernel(h, npa.array([self.Fx + Nx - 1, self.Fy + Ny - 1]))
|
||||
|
||||
print("Fx, Fy:", self.Fx, self.Fy)
|
||||
|
||||
return npa.concatenate(
|
||||
(
|
||||
npa.concatenate((top_left, top, top_right)),
|
||||
middle,
|
||||
npa.concatenate((bottom_left, bottom, bottom_right)),
|
||||
),
|
||||
axis=1,
|
||||
)
|
||||
|
||||
def conic_filter(phase_mask,radius,space_grid):
|
||||
'''Applies a conic filter to the input phase mask. The filter is defined as a
|
||||
conic region of a specified radius, where the values are averaged.
|
||||
|
||||
Args:
|
||||
phase_mask: the input phase mask to be filtered (2D)
|
||||
radius: the radius of the conic filter in micrometers
|
||||
space_grid: an object containing the size and resolution of the phase mask
|
||||
|
||||
Returns:
|
||||
The filtered phase mask (2D)
|
||||
'''
|
||||
Nx = space_grid.get_Nx()
|
||||
Ny = space_grid.get_Ny()
|
||||
|
||||
#positive quadrant of the filter
|
||||
xv = npa.arange(0, Nx//2+1) * (space_grid.get_resolution())
|
||||
yv = npa.arange(0, Ny//2+1) * (space_grid.get_resolution())
|
||||
#note that the filter's center is at (0,0)
|
||||
Xv, Yv = npa.meshgrid(xv, yv)
|
||||
|
||||
h = npa.where(Xv**2 + Yv**2 < radius**2, (1 - npa.sqrt(abs(Xv**2 + Yv**2)) / radius),0)
|
||||
|
||||
h = _quarter_to_full_kernel(h, 3 * npa.array([Nx, Ny]))
|
||||
|
||||
h = h / npa.sum(h)
|
||||
#make a plot of the filter kernel
|
||||
H = npa.fft.fft2(h)
|
||||
|
||||
phase_mask_2d = phase_mask.reshape(Nx,Ny)
|
||||
phase_mask_padded = _edge_pad(phase_mask_2d, ((Nx, Nx), (Ny, Ny)))
|
||||
|
||||
phase_mask_fft = npa.fft.fft2(phase_mask_padded)
|
||||
|
||||
Y = phase_mask_fft * H
|
||||
|
||||
y = _centered(
|
||||
npa.real(npa.fft.ifft2(Y)),
|
||||
(Nx, Ny)
|
||||
)
|
||||
|
||||
return y.flatten()
|
||||
h = h / npa.sum(h)
|
||||
H = npa.fft.fft2(h)
|
||||
|
||||
return H
|
||||
@@ -7,9 +7,9 @@ import pyfftw
|
||||
import nlopt
|
||||
from autograd import tensor_jacobian_product, grad as gd
|
||||
import autograd.numpy as npa
|
||||
from filters import conic_filter
|
||||
from filters import Filter
|
||||
|
||||
def make_2Dplot_of(given_field:np.ndarray, grid_X:np.ndarray, grid_Y:np.ndarray, plot_zoom_x:float,plot_zoom_y:float,choose_quantity="amplitude",save_name="plot") -> None:
|
||||
def make_2Dplot_of(given_field:np.ndarray, grid_X:np.ndarray, grid_Y:np.ndarray, plot_zoom_x:float, plot_zoom_y:float, choose_quantity="amplitude", save_name="plot") -> None:
|
||||
'''Makes a plot of the given 2D field in a zoomed region.
|
||||
Saves the plot with the chosen filename to a PDF file.
|
||||
Args:
|
||||
@@ -25,7 +25,6 @@ def make_2Dplot_of(given_field:np.ndarray, grid_X:np.ndarray, grid_Y:np.ndarray,
|
||||
fig, ax = plt.subplots(figsize=(6, 6))
|
||||
if choose_quantity == "amplitude":
|
||||
ax.pcolormesh(grid_X*1e6,grid_Y*1e6,np.abs(given_field_2d),rasterized=True)
|
||||
#add a colorbar for amplitude
|
||||
plt.colorbar(ax.pcolormesh(grid_X*1e6,grid_Y*1e6,np.abs(given_field_2d),rasterized=True), ax=ax)
|
||||
elif choose_quantity == "phase":
|
||||
ax.pcolormesh(grid_X*1e6,grid_Y*1e6,(np.angle(given_field_2d)+2*np.pi)%(2*np.pi), cmap='hsv', vmin=0, vmax=2*np.pi, rasterized=True)
|
||||
@@ -595,9 +594,11 @@ class Simulation:
|
||||
self.filter_radius = None
|
||||
|
||||
if conic_filter_radius is not None:
|
||||
self.filter_radius = conic_filter_radius
|
||||
self.filter_radius = conic_filter_radius
|
||||
self.filter_obj = Filter(conic_filter_radius, self.space_grid)
|
||||
else:
|
||||
self.filter_radius = None
|
||||
self.filter_obj = None
|
||||
|
||||
def get_sources(self):
|
||||
return self.sources
|
||||
@@ -632,7 +633,8 @@ class Simulation:
|
||||
def _update_wavelength(self, new_wavelength):
|
||||
'''
|
||||
Updates the operating wavelength and related vectors (lda_vector and k_vectors) based on the new wavelength.
|
||||
The new_wavelength should be provided in meters.
|
||||
The new_wavelength should be provided in meters. If use_unit_cells is True, updating the wavelength
|
||||
will also update the current active unit cell.
|
||||
Args:
|
||||
new_wavelength (float): The new operating wavelength in meters.
|
||||
'''
|
||||
@@ -771,9 +773,10 @@ class Simulation:
|
||||
def _normParam_filter(self, norm_param: np.ndarray) -> np.ndarray:
|
||||
'''Applies a conic filter on normalized parameters. Normalized parameters are the ones the optimization algorithm
|
||||
uses and are always in the range [0,1].'''
|
||||
return conic_filter(norm_param,
|
||||
self.filter_radius,
|
||||
self.space_grid,)
|
||||
# return conic_filter(norm_param,
|
||||
# self.filter_radius,
|
||||
# self.space_grid,)
|
||||
return self.filter_obj.apply_filter(norm_param)
|
||||
|
||||
def _phase_filter(self, phase_mask: np.ndarray) -> np.ndarray:
|
||||
'''Applies a conic filter on the phase mask. The filtering action is equivalent to a local weighted averaging of the phase.
|
||||
@@ -794,17 +797,18 @@ class Simulation:
|
||||
phase_mask_cos = npa.cos(phase_mask)
|
||||
phase_mask_sin = npa.sin(phase_mask)
|
||||
|
||||
phase_mask_filtered_cos = conic_filter(
|
||||
phase_mask_cos,
|
||||
self.filter_radius,
|
||||
self.space_grid,
|
||||
)
|
||||
|
||||
phase_mask_filtered_sin = conic_filter(
|
||||
phase_mask_sin,
|
||||
self.filter_radius,
|
||||
self.space_grid,
|
||||
)
|
||||
# phase_mask_filtered_cos = conic_filter(
|
||||
# phase_mask_cos,
|
||||
# self.filter_radius,
|
||||
# self.space_grid,
|
||||
# )
|
||||
phase_mask_filtered_cos = self.filter_obj.apply_filter(phase_mask_cos)
|
||||
# phase_mask_filtered_sin = conic_filter(
|
||||
# phase_mask_sin,
|
||||
# self.filter_radius,
|
||||
# self.space_grid,
|
||||
# )
|
||||
phase_mask_filtered_sin = self.filter_obj.apply_filter(phase_mask_sin)
|
||||
return npa.arctan2(phase_mask_filtered_sin, phase_mask_filtered_cos)
|
||||
|
||||
def _forward_propagate(self, source_object: Source, returns_field: bool = False):
|
||||
@@ -1184,7 +1188,8 @@ class Simulation:
|
||||
#after the phase mask is set, we test the same mask across all targets
|
||||
C_all_wavelengths = []
|
||||
tested_wavelengths = []
|
||||
grad_partial = np.zeros_like(grad)
|
||||
#per-mask gradient w.r.t. the filter output, accumulated over targets (the filter Jacobian is target-independent)
|
||||
grads_wrt_filtered = None
|
||||
|
||||
for target in self.targets:
|
||||
|
||||
@@ -1197,22 +1202,27 @@ class Simulation:
|
||||
full_grads = [g * self.current_unit_cell.get_complete_gradient_from_norm_param(fw)
|
||||
for g, fw in zip(full_grads, filtered_w_masks)]
|
||||
|
||||
if self.use_filtering:
|
||||
#backpropagate through the filter, which acts on w (unit cells) or on the phase (no unit cells)
|
||||
if self.use_unit_cells:
|
||||
filter_fun, filter_inputs = self._normParam_filter, w_masks
|
||||
else:
|
||||
filter_fun, filter_inputs = self._phase_filter, phase_masks
|
||||
jac_grads = [tensor_jacobian_product(filter_fun)(inp, g) for inp, g in zip(filter_inputs, full_grads)]
|
||||
#the gradient depends on wavelength, because phase of the unit cell is wavelength-dependent.
|
||||
#Each mask accumulates only its own gradient.
|
||||
if grads_wrt_filtered is None:
|
||||
grads_wrt_filtered = [np.array(g, dtype=float) for g in full_grads]
|
||||
else:
|
||||
jac_grads = list(full_grads)
|
||||
for k, g in enumerate(full_grads):
|
||||
grads_wrt_filtered[k] += g
|
||||
|
||||
if self.space_grid.xy_symmetry:
|
||||
jac_grads = [self._reduce_symmetrized_gradient(j) for j in jac_grads]
|
||||
jac_grad = np.concatenate(jac_grads, axis=0) # = (dC / dPhi) * (dw_f / dw), recall Phi = 2pi * w_f
|
||||
#a single backward pass per mask through the filter, which acts on w (unit cells) or on the phase (no unit cells)
|
||||
if self.use_filtering:
|
||||
if self.use_unit_cells:
|
||||
filter_fun, filter_inputs = self._normParam_filter, w_masks
|
||||
else:
|
||||
filter_fun, filter_inputs = self._phase_filter, phase_masks
|
||||
jac_grads = [tensor_jacobian_product(filter_fun)(inp, g) for inp, g in zip(filter_inputs, grads_wrt_filtered)]
|
||||
else:
|
||||
jac_grads = list(grads_wrt_filtered)
|
||||
|
||||
#the gradient depends on wavelength, because phase of the unit cell is wavelength-dependent
|
||||
grad_partial[:] += jac_grad
|
||||
if self.space_grid.xy_symmetry:
|
||||
jac_grads = [self._reduce_symmetrized_gradient(j) for j in jac_grads]
|
||||
grad_partial = np.concatenate(jac_grads, axis=0) # = sum over targets of (dC / dPhi) * (dw_f / dw), recall Phi = 2pi * w_f
|
||||
|
||||
if grad.size > 0:
|
||||
|
||||
@@ -1454,64 +1464,125 @@ class Simulation:
|
||||
|
||||
self.set_space_grid(new_space_grid)
|
||||
|
||||
def inject_phase_masks(self, input_phase_masks: list):
|
||||
def inject_phase_masks(self, input_phase_masks: list, apply_filtering: bool = True):
|
||||
"""
|
||||
Inject the specified phase masks into the simulation.
|
||||
Inject the specified phase masks into the simulation, optionally applying filtering based on the `apply_filtering` flag.
|
||||
Args:
|
||||
input_phase_masks (list): A list of phase masks, flattened, to be injected into the simulation.
|
||||
apply_filtering (bool): Apply filtering to the injected phase masks. Defaults to True.
|
||||
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):
|
||||
#set the phase mask attribute with the 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:
|
||||
#self.w_norm[i*self.space_grid.get_S():(i+1)*self.space_grid.get_S()] = self._w_given_phase(phase_mask)
|
||||
|
||||
if apply_filtering: #filtering is applied regardless of the global optimization setting
|
||||
if self.use_unit_cells:
|
||||
selected_filter = lambda x: self._normParam_filter(x)
|
||||
else:
|
||||
selected_filter = lambda x: self._phase_filter(x)
|
||||
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():])
|
||||
filtered_phase_mask_1 = selected_filter(self.phase_mask[:self.space_grid.get_S()])
|
||||
filtered_phase_mask_2 = selected_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():])
|
||||
filtered_phase_mask_1 = selected_filter(self.phase_mask[:self.space_grid.get_S()])
|
||||
filtered_phase_mask_2 = selected_filter(self.phase_mask[self.space_grid.get_S():2*self.space_grid.get_S()])
|
||||
filtered_phase_mask_3 = selected_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):
|
||||
def _update_phase_masks_with_diameters(self, input_diameters_masks: list, apply_filtering: bool = False):
|
||||
"""
|
||||
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.
|
||||
Constructs the phase masks based on the provided diameter masks and the currently active unit cell.
|
||||
Args:
|
||||
distances (np.ndarray, optional): Array containing the propagation distances. If None, the current distances are used.
|
||||
input_diameters_masks (list): A list of diameter masks, flattened, to be used for updating the phase masks.
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if len(input_diameters_masks) != self.num_phase_masks:
|
||||
raise ValueError(f"Expected {self.num_phase_masks} masks, but got {len(input_diameters_masks)}.")
|
||||
|
||||
#check if unit current unit cell is set and not None
|
||||
if self.current_unit_cell is None:
|
||||
raise ValueError("Cannot generate phase masks from diameters if no unit cell is provided.")
|
||||
|
||||
mask1 = input_diameters_masks[0]
|
||||
mask2 = input_diameters_masks[1] if self.num_phase_masks > 1 else None
|
||||
mask3 = input_diameters_masks[2] if self.num_phase_masks > 2 else None
|
||||
|
||||
phase_mask_1 = self.current_unit_cell.get_phase_at_param(mask1)
|
||||
phase_mask_2 = self.current_unit_cell.get_phase_at_param(mask2)
|
||||
phase_mask_3 = self.current_unit_cell.get_phase_at_param(mask3) if mask3 is not None else None
|
||||
|
||||
phase_mask_list = [phase_mask_1, phase_mask_2, phase_mask_3][:self.num_phase_masks]
|
||||
self.inject_phase_masks(phase_mask_list, apply_filtering=apply_filtering)
|
||||
|
||||
def source_sweep(self, distances: np.ndarray = None, input_phase_masks: list = None, input_diameter_masks: list = None, apply_filtering: bool = True, save_output: bool = False):
|
||||
"""
|
||||
Runs the propagation algorithm for the specified sources and targets in the simulation object.
|
||||
There are two operating modes of this method: if phase masks are provided via the `input_phase_masks` argument,
|
||||
If 'input_diameter_masks' is provided, phase masks are
|
||||
updated based on the source wavelength and unit cell data. If none of the two are provided, the last phase masks
|
||||
One cannot provide both `input_phase_masks` and `input_diameter_masks` simultaneously.
|
||||
Example: if the simulation contains two targets T1 and T2, with associated sources S1, S2, at two distinct wavelengths L1, L2, passing input_diameters_masks
|
||||
will update the phase masks for each source by selecting the appropriate unit cell based on the source wavelength and the provided diameter mask.
|
||||
Unit cell data must be already be present in the simulation object.
|
||||
It is typically used for studying the wavelength-dependent performance of the system.
|
||||
Args:
|
||||
distances (np.ndarray, optional): Array containing the propagation distances. If None, the current distances in the simulation object 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.
|
||||
input_diameter_masks (list, optional): A list of diameter masks to be used for updating the phase masks based on the source wavelength and unit cell data.
|
||||
apply_filtering (bool, optional): Controls filtering with higher priority than Simulation settings. Defaults to True.
|
||||
save_output (bool, optional): Save the output results to a file. Default is False.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary mapping source wavelengths to the overlap between the output field and the associated target field.
|
||||
"""
|
||||
|
||||
#setup
|
||||
if input_diameter_masks is not None and input_phase_masks is not None:
|
||||
raise ValueError("Cannot choose: both input_phase_masks and input_diameter_masks were provided.")
|
||||
|
||||
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)
|
||||
|
||||
self.inject_phase_masks(input_phase_masks, apply_filtering=apply_filtering)
|
||||
|
||||
if input_diameter_masks is not None:
|
||||
use_unit_cells = True
|
||||
else:
|
||||
use_unit_cells = False
|
||||
|
||||
#simulate
|
||||
for target in self.targets:
|
||||
source = target.get_associated_source()
|
||||
self._update_wavelength(source.get_wavelength())
|
||||
wavelength = source.get_wavelength()
|
||||
self._update_wavelength(wavelength)
|
||||
#retrieve the appropriate phase mask from the simulation's active unit cell
|
||||
if use_unit_cells:
|
||||
self._update_phase_masks_with_diameters(input_diameter_masks, apply_filtering=apply_filtering)
|
||||
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
|
||||
if save_output:
|
||||
save_filename = f"{self.results_folder}/output_field_{wavelength*1e6}"
|
||||
save_filename = save_filename.replace('.', '_') + ".npy"
|
||||
np.save(save_filename, output_field)
|
||||
|
||||
return results_dict
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ d = np.array([d0, d1, d2])
|
||||
|
||||
ms_max_available_diameter = 122 #[um]
|
||||
output_fiber_core_diameter = 19 #[um]
|
||||
opt_max_eval = 1000
|
||||
opt_max_eval = 600
|
||||
|
||||
space_grid = pid.SpaceGrid(
|
||||
side_pixels=N,
|
||||
|
||||
@@ -30,7 +30,7 @@ mat_refr_index = np.array([n0, n1, n2])
|
||||
|
||||
ms_max_available_diameter = 122 #[um]
|
||||
output_fiber_core_diameter = 19 #[um]
|
||||
opt_max_eval = 140
|
||||
opt_max_eval = 550
|
||||
|
||||
def setup_optical_system()->pid.Simulation:
|
||||
'''
|
||||
@@ -47,7 +47,7 @@ def setup_optical_system()->pid.Simulation:
|
||||
'''
|
||||
central_wavelength_um = 1.55
|
||||
|
||||
wavelengths_to_test_um = [1.530, 1.550, 1.570] #[um]
|
||||
wavelengths_to_test_um = [1.53, 1.55, 1.57] #[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",
|
||||
@@ -65,7 +65,20 @@ def setup_optical_system()->pid.Simulation:
|
||||
associated_source=sources[i],
|
||||
wavelength=wvl,
|
||||
field_norm=True) for i, (file, wvl) in enumerate(zip(target_files, wavelengths_to_test_um))]
|
||||
|
||||
|
||||
#import unit cell data
|
||||
unit_cell_diameters_wvl_1530 = np.load("unit_cell/unit_cell_center_1530_h900nm_res50_wvl_diameters.npy")
|
||||
unit_cell_diameters_wvl_1550 = np.load("unit_cell/unit_cell_center_1550_h900nm_res50_wvl_diameters.npy")
|
||||
unit_cell_diameters_wvl_1570 = np.load("unit_cell/unit_cell_center_1570_h900nm_res50_wvl_diameters.npy")
|
||||
unit_cell_phases_wvl_1530 = np.load("unit_cell/unit_cell_center_1530_h900nm_res50_wvl_mode_phase.npy")
|
||||
unit_cell_phases_wvl_1550 = np.load("unit_cell/unit_cell_center_1550_h900nm_res50_wvl_mode_phase.npy")
|
||||
unit_cell_phases_wvl_1570 = np.load("unit_cell/unit_cell_center_1570_h900nm_res50_wvl_mode_phase.npy")
|
||||
#construct the UnitCell objects
|
||||
unit_cell_1530 = pid.UnitCell(wavelength=1.53, tune_param=unit_cell_diameters_wvl_1530, sim_phase=unit_cell_phases_wvl_1530)
|
||||
unit_cell_1550 = pid.UnitCell(wavelength=1.55, tune_param=unit_cell_diameters_wvl_1550, sim_phase=unit_cell_phases_wvl_1550)
|
||||
unit_cell_1570 = pid.UnitCell(wavelength=1.57, tune_param=unit_cell_diameters_wvl_1570, sim_phase=unit_cell_phases_wvl_1570)
|
||||
unit_cells_list = [unit_cell_1530, unit_cell_1550, unit_cell_1570]
|
||||
|
||||
initial_space_grid = pid.SpaceGrid(
|
||||
side_pixels=N,
|
||||
unit_cell_pitch=unit_cell_pitch,
|
||||
@@ -89,6 +102,7 @@ def setup_optical_system()->pid.Simulation:
|
||||
phase_mask_init_size=output_fiber_core_diameter/3,
|
||||
opt_max_eval=opt_max_eval,
|
||||
conic_filter_radius=1.5,
|
||||
unit_cells=unit_cells_list,
|
||||
results_while_solving = False,
|
||||
save_results = False,
|
||||
)
|
||||
|
||||
@@ -17,6 +17,18 @@ ms_max_available_diameter = 122 #[um]
|
||||
output_fiber_core_diameter = 19 #[um]
|
||||
opt_max_eval = 140
|
||||
|
||||
central_wavelength_um = 1.55 #[um]
|
||||
|
||||
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"]
|
||||
|
||||
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"]
|
||||
|
||||
def setup_optical_system()->pid.Simulation:
|
||||
'''
|
||||
Builds the mode converter optical system using
|
||||
@@ -30,26 +42,27 @@ def setup_optical_system()->pid.Simulation:
|
||||
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))]
|
||||
|
||||
#import unit cell data
|
||||
unit_cell_diameters_wvl_1530 = np.load("unit_cell/unit_cell_center_1530_h900nm_res50_wvl_diameters.npy")
|
||||
unit_cell_diameters_wvl_1550 = np.load("unit_cell/unit_cell_center_1550_h900nm_res50_wvl_diameters.npy")
|
||||
unit_cell_diameters_wvl_1570 = np.load("unit_cell/unit_cell_center_1570_h900nm_res50_wvl_diameters.npy")
|
||||
unit_cell_phases_wvl_1530 = np.load("unit_cell/unit_cell_center_1530_h900nm_res50_wvl_mode_phase.npy")
|
||||
unit_cell_phases_wvl_1550 = np.load("unit_cell/unit_cell_center_1550_h900nm_res50_wvl_mode_phase.npy")
|
||||
unit_cell_phases_wvl_1570 = np.load("unit_cell/unit_cell_center_1570_h900nm_res50_wvl_mode_phase.npy")
|
||||
#construct the UnitCell objects
|
||||
unit_cell_1530 = pid.UnitCell(wavelength=1.530, tune_param=unit_cell_diameters_wvl_1530, sim_phase=unit_cell_phases_wvl_1530)
|
||||
unit_cell_1550 = pid.UnitCell(wavelength=1.550, tune_param=unit_cell_diameters_wvl_1550, sim_phase=unit_cell_phases_wvl_1550)
|
||||
unit_cell_1570 = pid.UnitCell(wavelength=1.570, tune_param=unit_cell_diameters_wvl_1570, sim_phase=unit_cell_phases_wvl_1570)
|
||||
unit_cells_list = [unit_cell_1530, unit_cell_1550, unit_cell_1570]
|
||||
|
||||
initial_space_grid = pid.SpaceGrid(
|
||||
side_pixels=N,
|
||||
@@ -73,7 +86,8 @@ def setup_optical_system()->pid.Simulation:
|
||||
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,
|
||||
conic_filter_radius=1.0,
|
||||
unit_cells=unit_cells_list,
|
||||
results_while_solving = False,
|
||||
save_results = False,
|
||||
)
|
||||
@@ -84,17 +98,19 @@ def setup_optical_system()->pid.Simulation:
|
||||
if __name__ == "__main__":
|
||||
simulation = setup_optical_system()
|
||||
|
||||
results_folder_name = 'results_nf'
|
||||
results_folder_name = 'results'
|
||||
|
||||
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()
|
||||
#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()
|
||||
mask1 = np.load(f"{results_folder_name}/mask_1.npy").flatten()
|
||||
mask2 = np.load(f"{results_folder_name}/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])
|
||||
results = simulation.source_sweep(distances = d, input_diameter_masks=[mask1, mask2], save_output=True, apply_filtering=False)
|
||||
#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)
|
||||
@@ -105,4 +121,108 @@ if __name__ == "__main__":
|
||||
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")
|
||||
plt.savefig(f"{results_folder_name}/source_sweep_results.pdf")
|
||||
|
||||
#for each of the test wavelength, take the fields from the results folder and make plots of them
|
||||
for i, wvl in enumerate(wavelengths_to_test_um):
|
||||
save_filename = f"{results_folder_name}/output_field_{wvl}"
|
||||
save_filename = save_filename.replace('.', '_')
|
||||
output_field = np.load(save_filename+ ".npy").reshape(simulation.space_grid.get_Nx(), simulation.space_grid.get_Ny())
|
||||
|
||||
fig, ax = plt.subplots(figsize=(6, 6))
|
||||
ax.pcolormesh(simulation.space_grid.get_X()*1e6, simulation.space_grid.get_Y()*1e6, np.abs(output_field) / np.max(np.abs(output_field)), cmap='viridis', rasterized = True)
|
||||
colorbar = fig.colorbar(ax.collections[0], ax=ax, label='Magnitude [a.u.]')
|
||||
ax.set_aspect('equal')
|
||||
ax.tick_params(axis='x', labelsize=20)
|
||||
ax.tick_params(axis='y', labelsize=20)
|
||||
#set the tick fontsize to 20
|
||||
ax.tick_params(axis='both', which='major', labelsize=20)
|
||||
ax.set_xlim(-output_fiber_core_diameter/2-10, output_fiber_core_diameter/2+10)
|
||||
ax.set_ylim(-output_fiber_core_diameter/2-10, output_fiber_core_diameter/2+10)
|
||||
#overlay a red circle indicating the output fiber core
|
||||
circle = plt.Circle((0, 0), output_fiber_core_diameter/2, color='red', fill=False, linewidth=2)
|
||||
ax.add_artist(circle)
|
||||
ax.set_xlabel('X [um]', fontsize=20)
|
||||
ax.set_ylabel('Y [um]', fontsize=20)
|
||||
plt.title(r'Output Field at $\lambda=$' + str(wvl) + r' $\mu$m')
|
||||
plt.tight_layout()
|
||||
plt.savefig(save_filename + ".pdf")
|
||||
plt.close()
|
||||
|
||||
fig, ax = plt.subplots(figsize=(6, 6))
|
||||
ax.pcolormesh(simulation.space_grid.get_X()*1e6, simulation.space_grid.get_Y()*1e6, (np.angle(output_field)+2*np.pi)%(2*np.pi), cmap='hsv', vmin=0, vmax=2*np.pi, rasterized = True)
|
||||
colorbar = fig.colorbar(ax.collections[0], ax=ax, label='Phase [rad]')
|
||||
ax.set_aspect('equal')
|
||||
ax.tick_params(axis='x', labelsize=20)
|
||||
ax.tick_params(axis='y', labelsize=20)
|
||||
#set the tick fontsize to 20
|
||||
ax.tick_params(axis='both', which='major', labelsize=20)
|
||||
ax.set_xlim(-output_fiber_core_diameter/2-10, output_fiber_core_diameter/2+10)
|
||||
ax.set_ylim(-output_fiber_core_diameter/2-10, output_fiber_core_diameter/2+10)
|
||||
#overlay a red circle indicating the output fiber core
|
||||
circle = plt.Circle((0, 0), output_fiber_core_diameter/2, color='red', fill=False, linewidth=2)
|
||||
ax.add_artist(circle)
|
||||
ax.set_xlabel('X [um]', fontsize=20)
|
||||
ax.set_ylabel('Y [um]', fontsize=20)
|
||||
plt.title(r'Output Field Phase at $\lambda=$' + str(wvl) + r' $\mu$m')
|
||||
plt.tight_layout()
|
||||
plt.savefig(save_filename + "_phase.pdf")
|
||||
plt.close()
|
||||
|
||||
|
||||
target_field_filename = target_files[i]
|
||||
target_field = np.load(target_field_filename)
|
||||
fig, axs = plt.subplots(2, 2, figsize=(12, 12))
|
||||
axs = axs.flatten()
|
||||
# First subplot: output field amplitude
|
||||
im = axs[0].pcolormesh(simulation.space_grid.get_X()*1e6, simulation.space_grid.get_Y()*1e6, np.abs(output_field)/np.max(np.abs(output_field)), cmap='viridis', shading='auto', rasterized=True)
|
||||
fig.colorbar(im, ax=axs[0], label='Magnitude [a.u.]')
|
||||
axs[0].set_aspect('equal')
|
||||
axs[0].set_xlim(-output_fiber_core_diameter/2-10, output_fiber_core_diameter/2+10)
|
||||
axs[0].set_ylim(-output_fiber_core_diameter/2-10, output_fiber_core_diameter/2+10)
|
||||
circle = plt.Circle((0, 0), output_fiber_core_diameter/2, color='red', fill=False, linewidth=2)
|
||||
axs[0].add_artist(circle)
|
||||
axs[0].set_xlabel('X [um]', fontsize=20)
|
||||
axs[0].set_ylabel('Y [um]', fontsize=20)
|
||||
axs[0].tick_params(axis='x', labelsize=20)
|
||||
axs[0].tick_params(axis='y', labelsize=20)
|
||||
axs[0].tick_params(axis='both', which='major', labelsize=20)
|
||||
axs[0].set_title('Output Field Amplitude')
|
||||
axs[0].set_aspect('equal')
|
||||
# Second subplot: y cross section slice
|
||||
y_slice = output_field[:, output_field.shape[1]//2]
|
||||
y_target_slice = target_field[:, target_field.shape[1]//2]
|
||||
axs[1].plot(simulation.space_grid.get_Y()[:, 0]*1e6, np.abs(y_slice)/np.max(np.abs(y_slice)), label='Output', c='blue', linewidth=2)
|
||||
axs[1].plot(simulation.space_grid.get_Y()[:, 0]*1e6, np.abs(y_target_slice)/np.max(np.abs(y_target_slice)), label='Target', c='red', linewidth=2)
|
||||
axs[1].set_xlabel('Y [um]', fontsize=20)
|
||||
axs[1].set_ylabel('Magnitude [a.u.]', fontsize=20)
|
||||
axs[1].set_title('Y Cross Section')
|
||||
axs[1].set_ylim(0, 1)
|
||||
axs[1].set_xlim(-output_fiber_core_diameter/2-10, output_fiber_core_diameter/2+10)
|
||||
axs[1].legend(fontsize=20)
|
||||
axs[1].set_aspect('auto')
|
||||
axs[1].tick_params(axis='x', labelsize=20)
|
||||
axs[1].tick_params(axis='y', labelsize=20)
|
||||
axs[1].tick_params(axis='both', which='major', labelsize=20)
|
||||
# Third subplot: x cross section slice
|
||||
x_slice = output_field[output_field.shape[0]//2, :]
|
||||
x_target_slice = target_field[target_field.shape[0]//2, :]
|
||||
axs[2].plot(simulation.space_grid.get_X()[0, :]*1e6, np.abs(x_slice)/np.max(np.abs(x_slice)), label='Output', c='blue', linewidth=2)
|
||||
axs[2].plot(simulation.space_grid.get_X()[0, :]*1e6, np.abs(x_target_slice)/np.max(np.abs(x_target_slice)), label='Target', c='red', linewidth=2)
|
||||
axs[2].set_xlabel('X [um]', fontsize=20)
|
||||
axs[2].set_ylim(0, 1)
|
||||
axs[2].set_xlim(-output_fiber_core_diameter/2-10, output_fiber_core_diameter/2+10)
|
||||
axs[2].set_ylabel('Magnitude [a.u.]', fontsize=20)
|
||||
axs[2].set_title('X Cross Section')
|
||||
axs[2].legend(fontsize=20)
|
||||
axs[2].set_aspect('auto')
|
||||
axs[2].tick_params(axis='x', labelsize=20)
|
||||
axs[2].tick_params(axis='y', labelsize=20)
|
||||
axs[2].tick_params(axis='both', which='major', labelsize=20)
|
||||
#remove completely the fourth subplot axis since we only have three now
|
||||
fig.delaxes(axs[3])
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig(save_filename + "_comparison.pdf")
|
||||
plt.close()
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#use this file to import some test phase masks and observe
|
||||
#the effect of changing the filtering radius
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from autograd import tensor_jacobian_product
|
||||
|
||||
class TempGrid:
|
||||
def __init__(self, Nx, Ny, res): self.Nx, self.Ny, self.res = Nx, Ny, res
|
||||
def get_Nx(self): return self.Nx
|
||||
def get_Ny(self): return self.Ny
|
||||
def get_resolution(self): return self.res
|
||||
|
||||
|
||||
grid = TempGrid(Nx=1025, Ny=1025, res=0.7)
|
||||
radius_to_test = [2.0, 5.0, 11.0]
|
||||
|
||||
input_mask = np.load('results/mask_2.npy')
|
||||
#make a plot of the input mask
|
||||
# fig, ax = plt.subplots()
|
||||
# ax.imshow(input_mask, cmap='inferno')
|
||||
# #zoom in the center region
|
||||
# center_x, center_y = grid.get_Nx() // 2, grid.get_Ny() // 2
|
||||
# zoom_size = 100 # adjust as needed
|
||||
# ax.set_xlim(center_x - zoom_size, center_x + zoom_size)
|
||||
# ax.set_ylim(center_y - zoom_size, center_y + zoom_size)
|
||||
# plt.savefig('input_mask.pdf')
|
||||
|
||||
#apply the filter
|
||||
from filters import Filter
|
||||
|
||||
center_x, center_y = grid.get_Nx() // 2, grid.get_Ny() // 2
|
||||
zoom_size = 80
|
||||
|
||||
colormap = 'gray_r'
|
||||
fig, ax = plt.subplots(1,4, figsize=(12, 3))
|
||||
ax[0].imshow(input_mask, cmap=colormap)
|
||||
ax[0].set_title('Input', fontsize=20)
|
||||
ax[0].set_xlim(center_x - zoom_size, center_x + zoom_size)
|
||||
ax[0].set_ylim(center_y - zoom_size, center_y + zoom_size)
|
||||
ax[0].set_xticks([])
|
||||
ax[0].set_yticks([])
|
||||
for i, radius in enumerate(radius_to_test, start=1):
|
||||
filt = Filter(radius, grid)
|
||||
output_mask = filt.apply_filter(input_mask)
|
||||
ax[i].imshow(output_mask.reshape(grid.get_Nx(), grid.get_Ny()), cmap=colormap)
|
||||
ax[i].set_title(f'r={radius} \u03BCm', fontsize=20)
|
||||
ax[i].set_xlim(center_x - zoom_size, center_x + zoom_size)
|
||||
ax[i].set_ylim(center_y - zoom_size, center_y + zoom_size)
|
||||
ax[i].set_xticks([])
|
||||
ax[i].set_yticks([])
|
||||
plt.tight_layout()
|
||||
plt.savefig('filtered_masks.pdf')
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
nohup: ignoring input
|
||||
Rank 0 is processing diameter 0.100 (index 0)
|
||||
Rank 1 is processing diameter 0.361 (index 12)
|
||||
Rank 0 is processing diameter 0.122 (index 1)
|
||||
Rank 1 is processing diameter 0.383 (index 13)
|
||||
Rank 0 is processing diameter 0.143 (index 2)
|
||||
Rank 1 is processing diameter 0.404 (index 14)
|
||||
Rank 0 is processing diameter 0.165 (index 3)
|
||||
Rank 1 is processing diameter 0.426 (index 15)
|
||||
Rank 0 is processing diameter 0.187 (index 4)
|
||||
Rank 1 is processing diameter 0.448 (index 16)
|
||||
Rank 0 is processing diameter 0.209 (index 5)
|
||||
Rank 1 is processing diameter 0.470 (index 17)
|
||||
Rank 0 is processing diameter 0.230 (index 6)
|
||||
Rank 0 is processing diameter 0.252 (index 7)
|
||||
Rank 1 is processing diameter 0.491 (index 18)
|
||||
Rank 0 is processing diameter 0.274 (index 8)
|
||||
Rank 1 is processing diameter 0.513 (index 19)
|
||||
Rank 0 is processing diameter 0.296 (index 9)
|
||||
Rank 1 is processing diameter 0.535 (index 20)
|
||||
Rank 0 is processing diameter 0.317 (index 10)
|
||||
Rank 1 is processing diameter 0.557 (index 21)
|
||||
Rank 0 is processing diameter 0.339 (index 11)
|
||||
Rank 1 is processing diameter 0.578 (index 22)
|
||||
Rank 1 is processing diameter 0.600 (index 23)
|
||||
|
||||
Elapsed run time = 17749.7805 s
|
||||
|
||||
Elapsed run time = 17750.1165 s
|
||||
@@ -0,0 +1,25 @@
|
||||
% Model: cyljnderUnjtCell_solved.mph
|
||||
% Versjon: COMSOL 6.1.0.282
|
||||
% Date: Aug 7 2026, 11:04
|
||||
% Table: Table 3 - Global Evaluatjon 3
|
||||
% tPost (nm) R (nm) lambda0 (µm) S21 (1)
|
||||
900 50 1.55 -0.845669386929+0.5044362611307165j
|
||||
900 63.157894736842096 1.55 -0.8251549977113691+0.5387569255954204j
|
||||
900 76.31578947368419 1.55 -0.7941914472021001+0.5854009666643545j
|
||||
900 89.47368421052632 1.55 -0.7471355423796626+0.6469515347910716j
|
||||
900 102.6315789473684 1.55 -0.6739731961216431+0.7260451361240156j
|
||||
900 115.78947368421052 1.55 -0.5559252634519462+0.8236596878659627j
|
||||
900 128.94736842105263 1.55 -0.35623973597781955+0.9314716653309569j
|
||||
900 142.1052631578947 1.55 -0.005446778866258663+0.9995911107511558j
|
||||
900 155.26315789473682 1.55 0.5624693373844357+0.8192539801083378j
|
||||
900 168.42105263157896 1.55 0.9716453344468975-0.026879486169522106j
|
||||
900 181.57894736842104 1.55 0.33229983484123915-0.9109913324745208j
|
||||
900 194.73684210526312 1.55 -0.5138804677648452-0.8375112454139356j
|
||||
900 207.89473684210526 1.55 -0.8979812315659875-0.41599951560137755j
|
||||
900 221.05263157894737 1.55 -0.8666228661809928-0.35839703156981834j
|
||||
900 234.21052631578942 1.55 -0.9706534677777011+0.1672691323977627j
|
||||
900 247.36842105263153 1.55 -0.875683679549086+0.4301009457755804j
|
||||
900 260.52631578947364 1.55 -0.7186563387658285+0.6394472785795947j
|
||||
900 273.6842105263158 1.55 -0.6152456609406248+0.5634675095025711j
|
||||
900 286.8421052631579 1.55 -0.3669553952916399+0.7919528013254294j
|
||||
900 300 1.55 -0.07785011501821787+0.7863645334693915j
|
||||
@@ -0,0 +1,51 @@
|
||||
'''
|
||||
Utility script to test the gradients functionality of the unit cell class of mpid
|
||||
Given the simulated transmittance phase values and associated tune parameter,
|
||||
the unit cell class should return the gradient of the simulated phase with respect to the tuning parameter.
|
||||
the class also provides a method to compute the gradient at a specific tuning parameter value, through interpolation.
|
||||
'''
|
||||
|
||||
import numpy as np
|
||||
from matplotlib import pyplot as plt
|
||||
from mpid import UnitCell
|
||||
|
||||
wavelength_um_1_53 = 1.53
|
||||
wavelength_um_1_55 = 1.55
|
||||
wavelength_um_1_57 = 1.57
|
||||
unit_cell_diameters = np.load("unit_cell/unit_cell_center_1550_h900nm_res50_wvl_diameters.npy")
|
||||
unit_cell_phases_wvl_1530 = np.load("unit_cell/unit_cell_center_1530_h900nm_res50_wvl_mode_phase.npy")
|
||||
unit_cell_phases_wvl_1550 = np.load("unit_cell/unit_cell_center_1550_h900nm_res50_wvl_mode_phase.npy")
|
||||
unit_cell_phases_wvl_1570 = np.load("unit_cell/unit_cell_center_1570_h900nm_res50_wvl_mode_phase.npy")
|
||||
|
||||
unit_cell_1530 = UnitCell(wavelength=wavelength_um_1_53, tune_param=unit_cell_diameters, sim_phase=unit_cell_phases_wvl_1530)
|
||||
unit_cell_1550 = UnitCell(wavelength=wavelength_um_1_55, tune_param=unit_cell_diameters, sim_phase=unit_cell_phases_wvl_1550)
|
||||
unit_cell_1570 = UnitCell(wavelength=wavelength_um_1_57, tune_param=unit_cell_diameters, sim_phase=unit_cell_phases_wvl_1570)
|
||||
|
||||
fig, ax = plt.subplots(figsize=(6, 6))
|
||||
plt.rcParams['mathtext.default'] = 'regular'
|
||||
ax.plot(unit_cell_1530.get_tune_param(), unit_cell_1530.get_gradient(), color='r', label=r"$\lambda=1530\:[nm]$", linewidth=3)
|
||||
ax.plot(unit_cell_1550.get_tune_param(), unit_cell_1550.get_gradient(), color='g', label=r"$\lambda=1550\:[nm]$", linewidth=3)
|
||||
ax.plot(unit_cell_1570.get_tune_param(), unit_cell_1570.get_gradient(), color='b', label=r"$\lambda=1570\:[nm]$", linewidth=3)
|
||||
ax.set_xlabel("diameter [um]", fontsize=20)
|
||||
ax.set_ylabel(r"$\partial \phi/\partial d$", fontsize=20, rotation='vertical')
|
||||
ax.yaxis.set_label_coords(-0.11, 0.5)
|
||||
ax.set_aspect('auto')
|
||||
ax.set_xticks(np.arange(min(unit_cell_diameters), max(unit_cell_diameters)+0.1, 0.1))
|
||||
ax.tick_params(axis='x', labelsize=20)
|
||||
ax.tick_params(axis='y', labelsize=20)
|
||||
plt.tight_layout()
|
||||
plt.savefig("unit_cell_gradient.pdf")
|
||||
|
||||
fig, ax = plt.subplots(figsize=(6, 6))
|
||||
ax.plot(unit_cell_1530.get_tune_param(), unit_cell_1530.get_sim_phase(), color='r', label=r"$\lambda=1530\:[nm]$", linewidth=3)
|
||||
ax.plot(unit_cell_1550.get_tune_param(), unit_cell_1550.get_sim_phase(), color='g', label=r"$\lambda=1550\:[nm]$", linewidth=3)
|
||||
ax.plot(unit_cell_1570.get_tune_param(), unit_cell_1570.get_sim_phase(), color='b', label=r"$\lambda=1570\:[nm]$", linewidth=3)
|
||||
ax.set_xlabel("diameter [um]", fontsize=20)
|
||||
ax.set_ylabel(r"$\phi\:$[rad]", fontsize=20)
|
||||
ax.tick_params(axis='x', labelsize=20)
|
||||
ax.tick_params(axis='y', labelsize=20)
|
||||
ax.legend(fontsize=20, loc='best')
|
||||
ax.set_aspect('auto')
|
||||
ax.set_xticks(np.arange(min(unit_cell_diameters), max(unit_cell_diameters)+0.1, 0.1))
|
||||
plt.tight_layout()
|
||||
plt.savefig("unit_cell_phase.pdf")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,169 @@
|
||||
import meep as mp
|
||||
mp.verbosity(0)
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from mpi4py import MPI
|
||||
|
||||
comm = MPI.COMM_WORLD
|
||||
rank = comm.Get_rank()
|
||||
size = comm.Get_size()
|
||||
|
||||
resolution = 50 # pixels/unit length
|
||||
|
||||
wvl_min = 1.53
|
||||
wvl_max = 1.57
|
||||
wvl_cen = 1.57
|
||||
fmin = 1/wvl_max
|
||||
fmax = 1/wvl_min
|
||||
fcen = 1/wvl_cen
|
||||
df = fmax-fmin
|
||||
nfreq = 1
|
||||
|
||||
dpml = 1.0 #PML thickness
|
||||
dsub = 1.5 + wvl_cen #substrate thickness
|
||||
dpad = dsub #padding between cell and PML
|
||||
|
||||
k_point = mp.Vector3(0,0,0) #used for PBC
|
||||
|
||||
n_si = 3.45 #silicon refractive index
|
||||
si = mp.Medium(index=n_si)
|
||||
n_sio2 = 1.444
|
||||
sio2 = mp.Medium(index=n_sio2)
|
||||
n_air = 1.0 #air refractive index
|
||||
air = mp.Medium(index=n_air)
|
||||
|
||||
P = 0.7 #unit cell pitch
|
||||
h = 0.9 #pillar height
|
||||
|
||||
def unitCell_paramsweep(D):
|
||||
sim_x_size = dpml + dsub + dpad + dpml
|
||||
sim_z_size = P
|
||||
sim_y_size = P
|
||||
|
||||
cell_size = mp.Vector3(sim_x_size, sim_y_size, sim_z_size)
|
||||
|
||||
source_position = mp.Vector3(-wvl_cen,0,0) #source position in the middle of the substrate
|
||||
source = mp.Source(
|
||||
mp.GaussianSource(fcen, fwidth=df),
|
||||
component = mp.Ez,
|
||||
center = source_position,
|
||||
size = mp.Vector3(0, sim_y_size, sim_z_size),
|
||||
)
|
||||
|
||||
pml_layers = [mp.PML(dpml,direction=mp.X)]
|
||||
|
||||
monitor_position = mp.Vector3(h + wvl_cen,0,0)
|
||||
obs_point = monitor_position
|
||||
stop_cond = mp.stop_when_fields_decayed(50, mp.Ez, obs_point, 1e-9)
|
||||
#stop_cond = 10
|
||||
sim = mp.Simulation(
|
||||
resolution = resolution,
|
||||
cell_size = cell_size,
|
||||
boundary_layers = pml_layers,
|
||||
k_point = k_point,
|
||||
default_material = sio2,
|
||||
sources = [source],
|
||||
)
|
||||
|
||||
flux_mon = sim.add_flux(
|
||||
[fcen],
|
||||
mp.FluxRegion(center=monitor_position, size=mp.Vector3(0, sim_y_size, sim_z_size))
|
||||
)
|
||||
|
||||
sim.run(until_after_sources=stop_cond)
|
||||
input_flux = mp.get_fluxes(flux_mon)
|
||||
|
||||
sim.reset_meep()
|
||||
|
||||
pillar_geo = mp.Cylinder(
|
||||
material = si,
|
||||
radius = D/2,
|
||||
height = h,
|
||||
center = mp.Vector3(h/2,0,0),
|
||||
axis=mp.Vector3(1,0,0),
|
||||
)
|
||||
|
||||
substrate_geo = mp.Block(
|
||||
material = sio2,
|
||||
size = mp.Vector3(dsub+dpml, mp.inf, mp.inf),
|
||||
center = mp.Vector3(-(dsub+dpml)/2,0,0),
|
||||
)
|
||||
|
||||
geometries = [substrate_geo, pillar_geo]
|
||||
|
||||
sim = mp.Simulation(
|
||||
resolution = resolution,
|
||||
cell_size = cell_size,
|
||||
boundary_layers = pml_layers,
|
||||
geometry = geometries,
|
||||
k_point = k_point,
|
||||
sources = [source],
|
||||
)
|
||||
|
||||
mode_mon = sim.add_flux(
|
||||
[fcen],
|
||||
mp.FluxRegion(center=monitor_position, size=mp.Vector3(0, sim_y_size, sim_z_size))
|
||||
)
|
||||
|
||||
sim.run(until_after_sources=stop_cond)
|
||||
|
||||
freqs = mp.get_eigenmode_freqs(mode_mon)
|
||||
res = sim.get_eigenmode_coefficients(
|
||||
mode_mon, [1], eig_parity=mp.ODD_Z + mp.EVEN_Y
|
||||
)
|
||||
coeffs = res.alpha
|
||||
|
||||
mode_wvl = [1/freqs[nf] for nf in range(nfreq)]
|
||||
mode_tran = [abs(coeffs[0,nf,0])**2 / input_flux[nf] for nf in range(nfreq)]
|
||||
mode_phase = [np.angle(coeffs[0,nf,0]) for nf in range(nfreq)]
|
||||
|
||||
return mode_wvl, mode_tran, mode_phase
|
||||
|
||||
if rank == 0:
|
||||
diameters = np.linspace(0.1, 0.6, 24)
|
||||
else:
|
||||
diameters = None
|
||||
diameters = comm.bcast(diameters, root=0)
|
||||
|
||||
local_diameters = np.array_split(diameters, size)[rank]
|
||||
local_indices = np.array_split(np.arange(len(diameters)), size)[rank]
|
||||
|
||||
local_mode_tran_arr = np.zeros(len(diameters))
|
||||
local_mode_phase_arr = np.zeros(len(diameters))
|
||||
|
||||
for local_i, (global_i, D) in enumerate(zip(local_indices, local_diameters)):
|
||||
print(f"Rank {rank} is processing diameter {D:.3f} (index {global_i})")
|
||||
mode_wvl, mode_tran, mode_phase = unitCell_paramsweep(D)
|
||||
local_mode_tran_arr[global_i] = mode_tran[0]
|
||||
local_mode_phase_arr[global_i] = mode_phase[0]
|
||||
|
||||
comm.Barrier()
|
||||
mode_tran_arr = comm.gather(local_mode_tran_arr, root=0)
|
||||
mode_phase_arr = comm.gather(local_mode_phase_arr, root=0)
|
||||
final_mode_tran_arr = np.zeros(len(diameters))
|
||||
final_mode_phase_arr = np.zeros(len(diameters))
|
||||
if rank == 0:
|
||||
for i in range(size):
|
||||
final_mode_tran_arr += mode_tran_arr[i]
|
||||
final_mode_phase_arr += mode_phase_arr[i]
|
||||
|
||||
|
||||
np.save("unit_cell_center_wvl_diameters.npy", diameters)
|
||||
np.save("unit_cell_center_wvl_mode_wvl.npy", mode_wvl)
|
||||
np.save("unit_cell_center_wvl_mode_tran.npy", final_mode_tran_arr)
|
||||
np.save("unit_cell_center_wvl_mode_phase.npy", final_mode_phase_arr)
|
||||
|
||||
plt.figure()
|
||||
plt.subplot(1,2,1)
|
||||
plt.plot(diameters,final_mode_tran_arr)
|
||||
plt.xlabel("Pillar Diameter (um)")
|
||||
plt.ylabel("Transmission")
|
||||
plt.subplot(1,2,2)
|
||||
plt.plot(diameters,final_mode_phase_arr)
|
||||
plt.xlabel("Pillar Diameter (um)")
|
||||
plt.ylabel("Phase (rad)")
|
||||
plt.subplots_adjust(wspace=0.5)
|
||||
plt.savefig("unit_cell_fcen.pdf")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,203 @@
|
||||
import meep as mp
|
||||
mp.verbosity(0)
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from mpi4py import MPI
|
||||
|
||||
comm = MPI.COMM_WORLD
|
||||
rank = comm.Get_rank()
|
||||
size = comm.Get_size()
|
||||
|
||||
resolution = 50 # pixels/unit length
|
||||
|
||||
wvl_min = 1.5
|
||||
wvl_max = 1.6
|
||||
wvl_cen = 0.5*(wvl_min+wvl_max)
|
||||
fmin = 1/wvl_max
|
||||
fmax = 1/wvl_min
|
||||
fcen = 1/wvl_cen
|
||||
df = fmax-fmin
|
||||
nfreq = 20
|
||||
|
||||
dpml = 1.0 #PML thickness
|
||||
dsub = 1.5 + wvl_cen #substrate thickness
|
||||
dpad = dsub #padding between cell and PML
|
||||
|
||||
k_point = mp.Vector3(0,0,0) #used for PBC
|
||||
|
||||
n_si = 3.45 #silicon refractive index
|
||||
si = mp.Medium(index=n_si)
|
||||
n_sio2 = 1.45
|
||||
sio2 = mp.Medium(index=n_sio2)
|
||||
n_air = 1.0 #air refractive index
|
||||
air = mp.Medium(index=n_air)
|
||||
|
||||
P = 0.7 #unit cell pitch
|
||||
h = 0.9 #pillar height
|
||||
|
||||
def unitCell_paramsweep(D):
|
||||
print("Rank {}: Running simulation for diameter {}".format(rank, D))
|
||||
sim_x_size = dpml + dsub + dpad + dpml
|
||||
sim_z_size = P
|
||||
sim_y_size = P
|
||||
|
||||
cell_size = mp.Vector3(sim_x_size, sim_y_size, sim_z_size)
|
||||
|
||||
source_position = mp.Vector3(-wvl_cen,0,0) #source position in the middle of the substrate
|
||||
source = mp.Source(
|
||||
mp.GaussianSource(fcen, fwidth=df),
|
||||
component = mp.Ez,
|
||||
center = source_position,
|
||||
size = mp.Vector3(0, sim_y_size, sim_z_size),
|
||||
)
|
||||
|
||||
pml_layers = [mp.PML(dpml,direction=mp.X)]
|
||||
|
||||
monitor_position = mp.Vector3(h + wvl_cen,0,0)
|
||||
obs_point = monitor_position
|
||||
stop_cond = mp.stop_when_fields_decayed(50, mp.Ez, obs_point, 1e-9)
|
||||
#stop_cond = 10
|
||||
sim = mp.Simulation(
|
||||
resolution = resolution,
|
||||
cell_size = cell_size,
|
||||
boundary_layers = pml_layers,
|
||||
k_point = k_point,
|
||||
default_material = sio2,
|
||||
sources = [source],
|
||||
)
|
||||
|
||||
flux_mon = sim.add_flux(
|
||||
fcen,
|
||||
df,
|
||||
nfreq,
|
||||
mp.FluxRegion(center=monitor_position, size=mp.Vector3(0, sim_y_size, sim_z_size))
|
||||
)
|
||||
|
||||
sim.run(until_after_sources=stop_cond)
|
||||
input_flux = mp.get_fluxes(flux_mon)
|
||||
|
||||
sim.reset_meep()
|
||||
|
||||
pillar_geo = mp.Cylinder(
|
||||
material = si,
|
||||
radius = D/2,
|
||||
height = h,
|
||||
center = mp.Vector3(h/2,0,0),
|
||||
axis=mp.Vector3(1,0,0),
|
||||
)
|
||||
|
||||
substrate_geo = mp.Block(
|
||||
material = sio2,
|
||||
size = mp.Vector3(dsub+dpml, mp.inf, mp.inf),
|
||||
center = mp.Vector3(-(dsub+dpml)/2,0,0),
|
||||
)
|
||||
|
||||
geometries = [substrate_geo, pillar_geo]
|
||||
|
||||
sim = mp.Simulation(
|
||||
resolution = resolution,
|
||||
cell_size = cell_size,
|
||||
boundary_layers = pml_layers,
|
||||
geometry = geometries,
|
||||
k_point = k_point,
|
||||
sources = [source],
|
||||
)
|
||||
|
||||
mode_mon = sim.add_flux(
|
||||
fcen, df, nfreq, mp.FluxRegion(center=monitor_position, size=mp.Vector3(0, sim_y_size, sim_z_size))
|
||||
)
|
||||
|
||||
sim.run(until_after_sources=stop_cond)
|
||||
|
||||
freqs = mp.get_eigenmode_freqs(mode_mon)
|
||||
res = sim.get_eigenmode_coefficients(
|
||||
mode_mon, [1], eig_parity=mp.ODD_Z + mp.EVEN_Y
|
||||
)
|
||||
coeffs = res.alpha
|
||||
|
||||
mode_wvl = [1/freqs[nf] for nf in range(nfreq)]
|
||||
mode_tran = [abs(coeffs[0,nf,0])**2 / input_flux[nf] for nf in range(nfreq)]
|
||||
mode_phase = [np.angle(coeffs[0,nf,0]) for nf in range(nfreq)]
|
||||
|
||||
return mode_wvl, mode_tran, mode_phase
|
||||
|
||||
if rank == 0:
|
||||
diameters = np.linspace(0.1, 0.6, 24)
|
||||
else:
|
||||
diameters = None
|
||||
diameters = comm.bcast(diameters, root=0)
|
||||
local_diameters = np.array_split(diameters, size)[rank]
|
||||
local_indices = np.array_split(np.arange(len(diameters)), size)[rank]
|
||||
|
||||
local_mode_tran_arr = np.zeros((len(diameters), nfreq))
|
||||
local_mode_phase_arr = np.zeros((len(diameters), nfreq))
|
||||
|
||||
for local_i, (global_i, D) in enumerate(zip(local_indices,local_diameters)):
|
||||
mode_wvl, mode_tran, mode_phase = unitCell_paramsweep(D)
|
||||
local_mode_tran_arr[global_i,:] = mode_tran
|
||||
local_mode_phase_arr[global_i,:] = mode_phase
|
||||
|
||||
|
||||
mode_tran_arr = comm.gather(local_mode_tran_arr, root=0)
|
||||
mode_phase_arr = comm.gather(local_mode_phase_arr, root=0)
|
||||
|
||||
if rank == 0:
|
||||
final_mode_tran_arr = np.zeros((len(diameters), nfreq))
|
||||
final_mode_phase_arr = np.zeros((len(diameters), nfreq))
|
||||
num_diameters_per_rank = len(diameters) // size
|
||||
for i in range(0, size):
|
||||
for j in range(num_diameters_per_rank):
|
||||
global_index = i * num_diameters_per_rank + j
|
||||
final_mode_tran_arr[global_index, :] = mode_tran_arr[i][global_index, :]
|
||||
final_mode_phase_arr[global_index, :] = mode_phase_arr[i][global_index, :]
|
||||
|
||||
|
||||
np.save("unit_cell_param_sweep_diameters.npy", diameters)
|
||||
np.save("unit_cell_param_sweep_mode_wvl.npy", mode_wvl)
|
||||
np.save("unit_cell_param_sweep_mode_tran.npy", final_mode_tran_arr)
|
||||
np.save("unit_cell_param_sweep_mode_phase.npy", final_mode_phase_arr)
|
||||
|
||||
plt.figure()
|
||||
plt.subplot(1,2,1)
|
||||
plt.pcolormesh(
|
||||
mode_wvl,
|
||||
diameters,
|
||||
final_mode_tran_arr,
|
||||
cmap='hot_r',
|
||||
shading='gouraud',
|
||||
vmin=0,
|
||||
vmax=final_mode_tran_arr.max(),
|
||||
)
|
||||
|
||||
plt.axis([wvl_min, wvl_max, diameters[0], diameters[-1]])
|
||||
plt.xlabel("Wavelength (um)")
|
||||
plt.xticks([t for t in np.linspace(wvl_min, wvl_max, 3)])
|
||||
plt.ylabel("Pillar Diameter (um)")
|
||||
plt.title("Transmission")
|
||||
cbar=plt.colorbar()
|
||||
cbar.set_ticks([t for t in np.arange(0, 1.2, 0.2)])
|
||||
|
||||
plt.subplot(1,2,2)
|
||||
plt.pcolormesh(
|
||||
mode_wvl,
|
||||
diameters,
|
||||
final_mode_phase_arr,
|
||||
cmap='RdBu',
|
||||
shading='gouraud',
|
||||
vmin=final_mode_phase_arr.min(),
|
||||
vmax=final_mode_phase_arr.max(),
|
||||
)
|
||||
|
||||
plt.axis([wvl_min, wvl_max, diameters[0], diameters[-1]])
|
||||
plt.xlabel("Wavelength (um)")
|
||||
plt.xticks([t for t in np.linspace(wvl_min, wvl_max, 3)])
|
||||
plt.ylabel("Pillar Diameter (um)")
|
||||
plt.title("phase")
|
||||
cbar=plt.colorbar()
|
||||
cbar.set_ticks([t for t in np.arange(-3, 4, 1)])
|
||||
|
||||
|
||||
plt.subplots_adjust(wspace=0.5)
|
||||
plt.savefig("unit_cell_param_sweep.pdf")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
#import the parameter sweep arrays for the central wavelength
|
||||
diameters = np.load("unit_cell/unit_cell_center_1550_h900nm_res50_wvl_diameters.npy")
|
||||
print(diameters)
|
||||
mode_wvl = np.load("unit_cell/unit_cell_center_1550_h900nm_res50_wvl_mode_wvl.npy")
|
||||
mode_tran_arr = np.load("unit_cell/unit_cell_center_1550_h900nm_res50_wvl_mode_tran.npy")
|
||||
print(mode_tran_arr)
|
||||
mode_phase_arr = np.load("unit_cell/unit_cell_center_1550_h900nm_res50_wvl_mode_phase.npy")
|
||||
print(mode_wvl)
|
||||
unwrap_mode_phase_arr = np.abs(np.unwrap(mode_phase_arr - mode_phase_arr[0], period=2*np.pi))
|
||||
#import the reference value array
|
||||
ref_results = np.loadtxt("unit_cell/results_S21_p700nm_h900nm.txt", skiprows=5,dtype=complex)
|
||||
R_ref = np.real(ref_results[:,1])
|
||||
S21_ref = ref_results[:,3]
|
||||
S21_ref_mag = np.abs(S21_ref)
|
||||
S21_ref_phase = np.abs(np.unwrap(np.angle(S21_ref) - np.angle(S21_ref[0]), period=2*np.pi))
|
||||
|
||||
|
||||
|
||||
plt.figure()
|
||||
plt.subplot(1,2,1)
|
||||
plt.plot(1e3*diameters/2,mode_tran_arr, '^r-.', label='FDTD')
|
||||
plt.plot(R_ref,S21_ref_mag, '*k--',label='FEM')
|
||||
plt.xlabel("Pillar Radius (nm)")
|
||||
plt.xticks([t for t in np.linspace(1e3*diameters[0]/2, 1e3*diameters[-1]/2, 5)])
|
||||
plt.gca().set_xticklabels([f"{t:.0f}" for t in np.linspace(1e3*diameters[0]/2, 1e3*diameters[-1]/2, 5)])
|
||||
plt.ylabel("Transmission")
|
||||
plt.legend()
|
||||
|
||||
plt.subplot(1,2,2)
|
||||
plt.plot(1e3*diameters/2,unwrap_mode_phase_arr, '^r-.', label='FDTD')
|
||||
plt.plot(R_ref,S21_ref_phase, '*k--', label='FEM')
|
||||
plt.xlabel("Pillar Radius (nm)")
|
||||
plt.xticks([t for t in np.linspace(1e3*diameters[0]/2, 1e3*diameters[-1]/2, 5)])
|
||||
plt.gca().set_xticklabels([f"{t:.0f}" for t in np.linspace(1e3*diameters[0]/2, 1e3*diameters[-1]/2, 5)])
|
||||
plt.ylabel("Phase (rad)")
|
||||
plt.legend()
|
||||
|
||||
plt.subplots_adjust(wspace=0.5)
|
||||
plt.savefig("unit_cell/unit_cell_param_sweep_lineplot.pdf")
|
||||
Reference in New Issue
Block a user