def eval(self, grid: gridlock.Grid, params: RenderParams): # Draw a cuboid in the original grid to overwrite any shapes in the # shape region, but draw the continuous permittivity on an extra # grid. grid.draw_cuboid(self.shape.pos, self.shape.extents, self.shape.material.permittivity(params.wlen)) new_grid = gridlock.Grid(grid.exyz, ext_dir=grid.ext_dir, initial=0, num_grids=3) contrast = self.shape.material2.permittivity( params.wlen) - self.shape.material.permittivity(params.wlen) shape_coords = self.shape.get_edge_coords() for axis in range(3): grid_coords = new_grid.shifted_exyz( axis, which_grid=gridlock.GridType.COMP) # Remove ghost cells at the end. grid_coords = [ c if c.shape == co.shape else c[:-1] for c, co in zip(grid_coords, grid.exyz) ] mat = get_rendering_matrix(shape_coords, grid_coords) grid_vals = contrast * mat @ self.shape.array.flatten() new_grid.grids[axis] = np.reshape(grid_vals, new_grid.grids[axis].shape) return new_grid
def _draw_mesh_on_grid(mesh: optplan.Mesh, grid: gridlock.Grid, gds: gdslib.GDSImport, wlen: Optional[float] = None) -> None: """Draws a mesh onto a grid. This is used to draw individual meshes onto a grid object. Args: mesh: Mesh to draw. grid: Grid to draw on. gds: GDS file from which to load polygons. wlen: Wavelength to use use for materials. """ eps_mat = _get_mat_index(mesh.material, wlen)**2 if mesh.type == "mesh.slab": extents = np.array(mesh.extents) center = extents.mean() thickness = np.diff(extents)[0] grid.draw_slab( dir_slab=grid.ext_dir, center=center, thickness=thickness, eps=eps_mat) elif mesh.type == "mesh.gds_mesh": layer = tuple(mesh.gds_layer) polygons = gds.get_polygons(layer) extents = np.array(mesh.extents) center = extents.mean() thickness = np.diff(extents)[0] polygon_center = np.zeros(3) polygon_center[grid.ext_dir] = center for polygon in polygons: polygon = np.around(polygon * NM_PER_UM) grid.draw_polygon( center=polygon_center, polygon=polygon, thickness=thickness, eps=eps_mat) else: raise ValueError("Encountered unknown mesh type: {}".format(mesh.type))
def _draw_gds_on_grid(gds_stack: List[optplan.GdsMaterialStackLayer], grid: gridlock.Grid, gds_path: str, wlen: Optional[float] = None) -> None: """Draws onto a `Grid` based on a GDS file. Args: gds_stack: Stack element info on the layer, extent and refractive index. grid: Grid object to draw on. gds_path: Path of the gds. wlen: Wavelength required for index calculations. """ # Load GDS. with open(gds_path, "rb") as gds_file: gds = gdslib.GDSImport(gds_file) # Draw layers for stack_element in gds_stack: layer = tuple(stack_element.gds_layer) polygons = gds.get_polygons(layer) extents = np.array(stack_element.extents) center = extents.mean() thickness = np.diff(extents)[0] polygon_center = np.zeros(3) polygon_center[grid.ext_dir] = center perm_bg = _get_mat_index(stack_element.background, wlen)**2 perm_fg = _get_mat_index(stack_element.foreground, wlen)**2 # Draw background grid.draw_slab( dir_slab=grid.ext_dir, center=center, thickness=thickness, eps=perm_bg) for polygon in polygons: polygon = np.around(polygon * NM_PER_UM) grid.draw_polygon( center=polygon_center, polygon=polygon, thickness=thickness, eps=perm_fg)
def grad(self, grid: gridlock.Grid, params: RenderParams): contrast = self.shape.material2.permittivity( params.wlen) - self.shape.material.permittivity(params.wlen) shape_coords = self.shape.get_edge_coords() grad = np.zeros_like(self.shape.array) for axis in range(3): grid_coords = grid.shifted_exyz(axis, which_grid=gridlock.GridType.COMP) # Remove ghost cells at the end. grid_coords = [ c if c.shape == co.shape else c[:-1] for c, co in zip(grid_coords, grid.exyz) ] mat = get_rendering_matrix(shape_coords, grid_coords) grid_vals = contrast * mat.T @ grid.grids[axis].flatten() grad += np.real(np.reshape(grid_vals, self.shape.array.shape)) # TODO(logansu): Fix complex gradient. if np.iscomplexobj(grid_vals): grad *= 2 return goos.PixelatedContShapeFlow.Grad(array_grad=grad)
def build_laguerre_gaussian_source(eps_grid: Grid, omega: float, w0: float, center: np.ndarray, axis: int, slices=List[slice], mu: List[np.ndarray] = None, theta: float = 0, psi: float = 0, polarization_angle: float = 0, m: int = 0, p : int = 0, polarity: int = 1, power: float = 1): """Builds a laguerre gaussian beam source. By default, the laguerre gaussian beam propagates along polarity of the given axis and is linearly polarized along the x-direction if axis is z, y-direction if x and z direction if y. `theta` rotates the propagation direction around the E-field, then 'psi' rotates source plane normal, and the polarization_angle rotates around the propagation direction. Args: eps_grid: gridlock.grid with the permittivity distribution. omega: The frequency of the mode. axis: Direction of propagation. slices: Source slice which define the position of the source in the grid. mu: Permeability distribution. theta: Rotation around the default E-component. psi: Rotation around the source plane normal. polarization_angle: Rotation around the propagation direction. m : parameters of the laguerre gaussian beam p : parameters of the laguerre gaussian beam polarity: 1 if forward propagating. -1 if backward propagating. power: Power is the gaussian beam. Returns: Current source J. """ # Make scalar fields. eps_val = np.real(np.average(eps_grid.grids[0][tuple(slices)])) scalar_field_function = lambda x, y, z, R: laguerre_gaussian_beam_z_axis_x_pol( x, y, z, w0, center, R, omega, m, p, polarity, eps_val) # Get vector fields. fields = scalar2rotated_vector_fields( eps_grid=eps_grid, scalar_field_function=scalar_field_function, mu=mu, omega=omega, axis=axis, slices=slices, theta=theta, psi=psi, polarization_angle=polarization_angle, polarity=polarity, power=power, full_fields=True) # Calculate the source. dxes = [eps_grid.dxyz, eps_grid.autoshifted_dxyz()] # make current field_slices = [slice(None, None)] * 3 J_slices = slices if polarity == -1: ind = slices[axis].start field_slices[axis] = slice(None, ind) J_slices[axis] = slice(ind - 1, ind + 1) else: ind = slices[axis].stop - 1 field_slices[axis] = slice(ind, None) J_slices[axis] = slice(ind - 1, ind + 1) E = np.zeros_like(fields['E']) for i in range(3): E[i][tuple(field_slices)] = fields['E'][i][tuple(field_slices)] full = operators.e_full(omega, dxes, vec(eps_grid.grids), bloch_vec=fields['wavevector']) J_vec = 1 / (-1j * omega) * full @ vec(E) J_temp = unvec(J_vec, E[0].shape) J = np.zeros_like(J_temp) for i in range(3): J[i][tuple(J_slices)] = J_temp[i][tuple(J_slices)] return J, fields['wavevector']
def build_plane_wave_source(eps_grid: Grid, omega: float, axis: int, slices=List[slice], mu: List[np.ndarray] = None, theta: float = 0, psi: float = 0, polarization_angle: float = 0, polarity: int = 1, border: int or List[int] = 0, power: float = 1): """Builds a plane wave source. By default, the plane wave propagates along polarity of the given axis and is linearly polarized along the x-direction if axis is z, y-direction if x and z direction if y. `theta` rotates the propagation direction around the E-field, then 'psi' rotates source plane normal, and the polarization_angle rotates around the propagation direction. Args: eps_grid: gridlock.grid with the permittivity distribution. omega: The frequency of the mode. axis: Direction of propagation. slices: Source slice which define the position of the source in the grid. mu: Permeability distribution. theta: Rotation around the default E-component. psi: Rotation around the source plane normal. polarization_angle: Rotation around the propagation direction. polarity: 1 if forward propagating. -1 if backward propagating. border: border in grid points where the intensity of the planewave decreased to 0. For example: [10, 10, 10] assuming radiation in the z direction will put 10 grid border on both sides in the x and y direction and ignore the z. (If the border is larger then the simulation it will be ignored aswell.) power: power transmitted through the slice. Returns: Current source J. """ # Perpare arguments. shape = eps_grid.shape # Make scalar fields. eps_val = np.real(np.average(eps_grid.grids[0][tuple(slices)])) scalar_field_function = lambda x, y, z, R: plane_wave_z_axis_x_pol( x, y, z, R, omega, polarity, eps_val) # Get vector fields. fields = scalar2rotated_vector_fields( eps_grid=eps_grid, scalar_field_function=scalar_field_function, mu=mu, omega=omega, axis=axis, slices=slices, theta=theta, psi=psi, polarization_angle=polarization_angle, polarity=polarity, power=power, full_fields=True) def make_mask(shape: List[int], slices: List, axis: int, border: List[int]) -> np.ndarray: """Builds a mask with a border. On the border the intensity goes from 1 to 0. Args: shape: Shape of the simulation. slices: List of slices where the plane wave is. axis: Direction of the plane wave. border: number of gridpoint that make up the border. Returns: mask """ size = [s.stop - s.start for s in slices] coords = [] for i in range(3): if (size[i] - 1) > 2 * border[i] and border[i] > 0: d = 1 / np.array(border[i]) x = np.hstack([ np.arange(1, 0, -d), np.zeros(size[i] - 2 * border[i]), np.arange(d, 1 + d, d) ]) else: x = np.zeros(size[i]) coords.append(x) coords[axis] = np.zeros(shape[axis]) ind = np.meshgrid(coords[0], coords[1], coords[2], indexing='ij') co = (ind[0]**2 + ind[1]**2 + ind[2]**2)**0.5 co[co > 1] = 1 mask_slice = 1 + 2 * co**3 - 3 * co**2 mask = np.zeros(shape) slices_mask = copy.deepcopy(slices) slices_mask[axis] = slice(None, None) mask[tuple(slices_mask)] = mask_slice return mask # Prepare border and the mask for be fields. if isinstance(border, int): border = 3 * [border] border[axis] = 0 # You can not have a border in the axis direction size = [s.stop - s.start for s in slices] for i, (b, s) in enumerate(zip(border, size)): if 2 * b < s: border[i] = b elif s == 1: border[i] = 0 else: raise ValueError("Two times the border is larger then the size" + " of the plane wave in axis {}.".format(i)) border = [b if 2 * b < s else 0 for b, s in zip(border, size)] mask = make_mask(shape, slices, axis, border) # Normalize fields dxes = [eps_grid.dxyz, eps_grid.autoshifted_dxyz()] slices_P = copy.deepcopy(slices) slices_P = [ slice(sl.start + b, sl.stop - b) for sl, b in zip(slices_P, border) ] P = waveguide_mode.compute_transmission_chew(E=fields['E'], H=fields['H'], axis=axis, omega=omega, dxes=dxes, slices=slices_P) fields['E'] /= np.sqrt(abs(P)) fields['H'] /= np.sqrt(abs(P)) fields['E'] = [mask * e for e in fields['E']] fields['H'] = [mask * h for h in fields['H']] # Calculate the source. dxes = [eps_grid.dxyz, eps_grid.autoshifted_dxyz()] # make current field_slices = [slice(None, None)] * 3 J_slices = slices if polarity == -1: ind = slices[axis].start field_slices[axis] = slice(None, ind) J_slices[axis] = slice(ind - 1, ind + 1) else: ind = slices[axis].stop - 1 field_slices[axis] = slice(ind, None) J_slices[axis] = slice(ind - 1, ind + 1) E = np.zeros_like(fields['E']) for i in range(3): E[i][tuple(field_slices)] = fields['E'][i][tuple(field_slices)] full = operators.e_full(omega, dxes, vec(eps_grid.grids), bloch_vec=fields['wavevector']) J_vec = 1 / (-1j * omega) * full @ vec(E) J_temp = unvec(J_vec, E[0].shape) J = np.zeros_like(J_temp) for i in range(3): J[i][tuple(J_slices)] = J_temp[i][tuple(J_slices)] return J, fields['wavevector']
def scalar2rotated_vector_fields(eps_grid: Grid, scalar_field_function: Callable, mu: List[np.ndarray], omega: float, axis: int, slices: List[slice], theta: float = 0, psi: float = 0, polarization_angle: float = 0, polarity: int = 1, power: float = 1, full_fields: bool = False): """ Given a function that evaluates the scalar field that propagates in the z-direction, this function will make the vector field taking into account three rotations. Args: eps_grid: gridlock.grid with the permittivity distribution. mu: Permeability distribution. omega: The frequency of the mode. axis: Direction of propagation. slices: Source slice which define the position of the source in the grid. theta: Rotation around the default E-component. psi: Rotation around the source plane normal. polarization_angle: Rotation around the propagation direction. polarity: 1 if forward propagating. -1 if backward propagating. power: power full_fields: True gives you the field in the entire simulation space. False gives only the fields in the slices, the rest of the simulation space will be zero. Returns: Results: dict with the wavevector, the e-field and the h-field. """ if mu is None: mu = [np.ones(eps_grid.shape)] * 3 dxes = [eps_grid.dxyz, eps_grid.autoshifted_dxyz()] epsilon = eps_grid.grids xyz_shift = [eps_grid.shifted_xyz(which_shifts=i) for i in range(3)] # Define rotation to set z as propagation direction. order = np.roll(range(3), 2 - axis) reverse_order = np.roll(range(3), axis - 2) #Make grid points xyz = xyz_shift[order[0]] x_Ex, y_Ex, z_Ex = np.meshgrid(xyz[order[0]], xyz[order[1]], xyz[order[2]], indexing='ij') xyz = xyz_shift[order[1]] x_Ey, y_Ey, z_Ey = np.meshgrid(xyz[order[0]], xyz[order[1]], xyz[order[2]], indexing='ij') xyz = xyz_shift[order[2]] x_Ez, y_Ez, z_Ez = np.meshgrid(xyz[order[0]], xyz[order[1]], xyz[order[2]], indexing='ij') #Make total rotation matrix k = np.array([0, 0, polarity]) e0 = np.array([1, 0, 0]) R_theta = rotation_matrix(e0, theta) R_psi = rotation_matrix(k, psi) k = R_psi @ R_theta @ k R_pol = rotation_matrix(k, polarization_angle) R = R_pol @ R_psi @ R_theta e0_rot = R @ e0 #Make wavevector ref_index = np.sqrt(np.real(np.average(epsilon[0][tuple(slices)]))) bloch_vector = (k * omega * ref_index) #Evaluate fields. ex_mag = scalar_field_function(x_Ex, y_Ex, z_Ex, np.linalg.inv(R)) * e0_rot[0] ey_mag = scalar_field_function(x_Ey, y_Ey, z_Ey, np.linalg.inv(R)) * e0_rot[1] ez_mag = scalar_field_function(x_Ez, y_Ez, z_Ez, np.linalg.inv(R)) * e0_rot[2] E_fields = [ex_mag, ey_mag, ez_mag] #Make H fields. dxes = [[dx[i] for i in order] for dx in dxes] e_vec = vec(E_fields) h_vec = operators.e2h(omega=omega, dxes=dxes, mu=vec([mu[i].transpose(order) for i in order]), bloch_vec=bloch_vector) @ e_vec H_fields = unvec(h_vec, E_fields[0].shape) #Normalize fields. #Roll back E = [None] * 3 H = [None] * 3 wavevector = np.zeros(3) for a, o in enumerate(reverse_order): E[a] = np.zeros_like(epsilon[0], dtype=complex) H[a] = np.zeros_like(epsilon[0], dtype=complex) if full_fields: E[a] = np.sqrt(power) * E_fields[o].transpose(reverse_order) H[a] = np.sqrt(power) * H_fields[o].transpose(reverse_order) else: E[a][tuple(slices)] = np.sqrt(power) * E_fields[o][tuple( [slices[i] for i in order])].transpose(reverse_order) H[a][tuple(slices)] = np.sqrt(power) * H_fields[o][tuple( [slices[i] for i in order])].transpose(reverse_order) wavevector[a] = bloch_vector[o] results = { 'wavevector': wavevector, 'H': H, 'E': E, } return results
def eval(self, grid: gridlock.Grid, params: RenderParams): radius = self.shape.radius.item() num_points = int(np.ceil(params.pts_per_arclen * 2 * np.pi * radius)) grid.draw_cylinder(self.shape.pos, radius, self.shape.height.item(), num_points, self.shape.material.permittivity(params.wlen))
def eval(self, grid: gridlock.Grid, params: RenderParams): if np.all(self.shape.extents != 0): grid.draw_cuboid(self.shape.pos, self.shape.extents, self.shape.material.permittivity(params.wlen))