Exemple #1
0
 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
Exemple #2
0
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))
Exemple #3
0
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)
Exemple #4
0
    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
Exemple #8
0
 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))
Exemple #9
0
 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))