예제 #1
0
    def draw_cylinder(self, center: np.ndarray, radius: float,
                      thickness: float, num_points: int, eps: float
                      or np.ndarray):
        """
        Draw a cylinder with permittivity epsilon. By default, the axis of the cylinder
        is assumed to be along the extrusion direction
        
        """
        center = np.array(center)
        # Validating input parameters
        if center.ndim != 1 or center.size != 3:
            raise GridError('Invalid center coordinate')

        if is_scalar(eps):
            eps = np.ones(self.shifts.shape[0]) * eps

        if eps.ndim != 1 or eps.size != self.shifts.shape[0]:
            raise GridError(
                'Invalid permittvity - must be scalar or vector of length equalling number of grids'
            )

        if not is_scalar(thickness):
            raise GridError('Invalid thickness')

        if not is_scalar(num_points):
            raise GridError('Invalid number of points on the cylinder')

        # Approximating the drawn cylinder with a polygon with number of vertices = num_points
        theta = np.linspace(0, 2.0 * np.pi, num_points)
        x = radius * np.sin(theta)
        y = radius * np.cos(theta)
        polygon = np.vstack((x, y)).T

        # Drawing polygon
        self.draw_polygon(center, polygon, thickness, eps)
예제 #2
0
    def draw_slab(self, dir_slab: Direction or float, center: float,
                  thickness: float, eps: float or np.ndarray):
        """
        Draw a slab
        """

        # Validating input arguments
        if isinstance(dir_slab, Direction):
            dir_slab = dir_slab.value
        elif not is_scalar(dir_slab):
            raise GridError('Invalid slab direction')
        elif not dir_slab in range(3):
            raise GridError('Invalid slab direction')

        if not is_scalar(center):
            raise GridError('Invalid slab center')

        if is_scalar(eps):
            eps = np.ones(self.shifts.shape[0]) * eps

        if eps.ndim != 1 or eps.size != self.shifts.shape[0]:
            raise GridError(
                'Invalid permittivity - must be a scalar or vector of length equalling number of grids'
            )

        dir_slab_par = np.delete(range(3), dir_slab)
        cuboid_cen = np.array(
            [self.center[a] if a != dir_slab else center for a in range(3)])
        cuboid_extent = np.array([1.5*np.abs(self.exyz[a][-1]-self.exyz[a][0]) if a !=dir_slab \
                                  else thickness for a in range(3)])
        self.draw_cuboid(cuboid_cen, cuboid_extent, eps)
예제 #3
0
    def draw_cuboid(self, center: np.ndarray, extent: np.ndarray, eps: float
                    or List[float]):
        """
        Draw a cuboid with permittivity epsilon
        """

        center = np.array(center)
        extent = np.array(extent)
        # Validating input parameters
        if center.ndim != 1 or center.size != 3:
            raise GridError('Invalid center coordinate')

        if extent.ndim != 1 or extent.size != 3:
            raise GridError('Invalid cuboid lengths')

        if is_scalar(eps):
            eps = np.ones(self.shifts.shape[0]) * eps
        if eps.ndim != 1 or eps.size != self.shifts.shape[0]:
            raise GridError(
                'Invalid permittivity - must be scalar or vector of length equalling number of grids'
            )

        # Calculating the polygon corresponding to the drawn cuboid
        polygon = 0.5 * np.array(
            [[-extent[self.planar_dir[0]], extent[self.planar_dir[1]]],
             [extent[self.planar_dir[0]], extent[self.planar_dir[1]]],
             [extent[self.planar_dir[0]], -extent[self.planar_dir[1]]],
             [-extent[self.planar_dir[0]], -extent[self.planar_dir[1]]]],
            dtype=float)

        thickness = extent[self.ext_dir]

        # Drawing polygon
        self.draw_polygon(center, polygon, thickness, eps)
예제 #4
0
    def pos2ind(self,
                r: np.ndarray or List,
                which_shifts: int or None,
                which_grid: GridType = GridType.PRIM,
                round_ind: bool = True,
                check_bounds: bool = True) -> np.ndarray:
        """
        Returns the indices corresponding to the specified natural position.
             The resulting position is clipped to within the outer centers of the grid.

        :param r: Natural position that we will convert into indices (3-element ndarray or list)
        :param which_shifts: which grid number (shifts) to use
        :param round_ind: Whether to round the returned indices to the nearest integers.
        :param check_bounds: Whether to throw an GridError if r is outside the grid edges
        :return: 3-element ndarray specifying the indices
        :raises: GridError
        """
        r = np.squeeze(r)
        if r.size != 3:
            raise GridError('r must be 3-element vector: {}'.format(r))

        if (which_shifts
                is not None) and (which_shifts >= self.shifts.shape[0]):
            raise GridError('')

        sexyz = self.shifted_exyz(which_shifts, which_grid)

        if check_bounds:
            for a in range(3):
                if self.shape[a] > 1 and (r[a] < sexyz[a][0]
                                          or r[a] > sexyz[a][-1]):
                    raise GridError('Position[{}] outside of grid!'.format(a))

        grid_pos = zeros((3, ))
        for a in range(3):
            xi = np.digitize(r[a],
                             sexyz[a]) - 1  # Figure out which cell we're in
            xi_clipped = np.clip(xi, 0, sexyz[a].size -
                                 2)  # Clip back into grid bounds

            # No need to interpolate if round_ind is true or we were outside the grid
            if round_ind or xi != xi_clipped:
                grid_pos[a] = xi_clipped
            else:
                # Interpolate
                x = self.shifted_exyz(which_shifts, which_grid)[a][xi]
                dx = self.shifted_dxyz(which_shifts, which_grid)[a][xi]
                f = (r[a] - x) / dx
                # Clip to centers
                grid_pos[a] = np.clip(xi + f, 0, sexyz[a].size - 1)
        return grid_pos
예제 #5
0
    def ind2pos(self,
                ind: np.ndarray or List,
                which_shifts: int or None,
                which_grid: GridType = GridType.PRIM,
                round_ind: bool = True,
                check_bounds: bool = True) -> np.ndarray:
        """
        Returns the natural position corresponding to the specified indices.
         The resulting position is clipped to the bounds of the grid
        (to cell centers if round_ind=True, or cell outer edges if round_ind=False)

        :param ind: Indices of the position. Can be fractional. (3-element ndarray or list)
        :param which_shifts: which grid number (shifts) to use
        :param round_ind: Whether to round ind to the nearest integer position before indexing
                (default True)
        :param check_bounds: Whether to raise an GridError if the provided ind is outside of
                the grid, as defined above (centers if round_ind, else edges) (default True)
        :return: 3-element ndarray specifying the natural position
        :raises: GridError
        """

        if which_shifts is not None and which_shifts >= self.shifts.shape[0]:
            raise GridError('Invalid shifts')

        ind = np.array(ind, dtype=float)

        if check_bounds:
            if round_ind:
                low_bound = 0.0
                high_bound = -1
            else:
                low_bound = -0.5
                high_bound = -0.5
            if (ind < low_bound).any() or (ind >
                                           self.shape - high_bound).any():
                raise GridError('Position outside of grid: {}'.format(ind))

        if round_ind:
            rind = np.clip(np.round(ind), 0, self.shape - 1)
            sxyz = self.shifted_xyz(which_shifts, which_grid)
            position = [sxyz[a][rind[a]].astype(int) for a in range(3)]
        else:
            sexyz = self.shifted_exyz(which_shifts, which_grid)
            position = [
                np.interp(ind[a],
                          np.arange(sexyz[a].size) - 0.5, sexyz[a])
                for a in range(3)
            ]
        return np.array(position, dtype=float)
예제 #6
0
    def draw_polygon(self, center: np.ndarray, polygon: np.ndarray,
                     thickness: float, eps: float or List[float]):
        """
        Draws a polygon with coordinates in polygon and thickness
        Note on order of coordinates in polygon - 
        If ext_dir = x, then polygon has coordinates of form (y,z)
           ext_dir = y, then polygon has coordinates of form (x,y)
           ext_dir = z, then polygon has coordinates of form (x,y)
        """

        center = np.array(center)
        polygon = np.array(polygon)
        # Validating input arguments
        if polygon.ndim != 2 or polygon.shape[1] != 2:
            raise GridError(
                'Invalid format for specifying polygon - must be a Nx2 array')

        if polygon.shape[0] <= 2:
            raise GridError(
                'Malformed Polygon - must contain more than two points')

        if center.ndim != 1 or center.size != 3:
            raise GridError('Invalid format for the polygon center')

        if (not is_scalar(thickness)) or thickness <= 0:
            raise GridError('Invalid thickness')

        if is_scalar(eps):
            eps = np.ones(self.shifts.shape[0]) * eps
        elif eps.ndim != 1 and eps.size != self.shifts.shape[0]:
            raise GridErro(
                'Invalid permittivity - must be scalar or vector of length equalling number of grids'
            )
        # Translating polygon by its center
        polygon_translated = polygon + np.tile(center[self.planar_dir],
                                               (polygon.shape[0], 1))
        self.list_polygons.append((polygon_translated, eps))

        # Adding the z-coordinates of the z-coordinates of the added layers
        self.list_z.append([
            center[self.ext_dir] - 0.5 * thickness,
            center[self.ext_dir] + 0.5 * thickness
        ])
예제 #7
0
    def fill_slab(self, fill_dir: Direction, fill_pol: int, surf_center: float,
                  eps: float or np.ndarray):

        # Validating input arguments
        if isinstance(fill_dir, Direction):
            fill_dir = fill_dir.value
        elif not is_scalar(fill_dir):
            raise GridError('Invalid slab direction')
        elif not dir_slab in range(3):
            raise GridError('Invalid slab direction')

        if not is_scalar(fill_pol):
            raise GridError('Invalid polarity')
        if not fill_pol in [-1, 1]:
            raise GridError('Invalid polarity')

        if not is_scalar(surf_center):
            raise GridError('Invalid surface center')

        edge_lim = self.exyz[fill_dir][0] if fill_pol == -1 else self.exyz[
            fill_dir][-1]
        slab_thickness = 2 * np.abs(edge_lim - surf_center)
        slab_center = surf_center + 0.5 * fill_pol * slab_thickness
        self.draw_slab(fill_dir, slab_center, slab_thickness, eps)
예제 #8
0
    def fill_cuboid(self, fill_dir: Direction, fill_pol: int,
                    surf_center: np.ndarray, surf_extent: np.ndarray,
                    eps: float or np.ndarray):
        '''
        INPUTS:
        1. surf_extent - array of size 2 corresponding to the extent of the surface. If the fill direction
        is x, then the two elements correspond to y,z, if it is y then x,z and if it is z then x,y
        '''
        surf_center = np.array(surf_center)
        surf_extent = np.array(surf_extent)

        # Validating input arguments
        if isinstance(fill_dir, Direction):
            fill_dir = fill_dir.value
        elif not is_scalar(fill_dir):
            raise GridError('Invalid slab direction')
        elif not dir_slab in range(3):
            raise GridError('Invalid slab direction')

        if not is_scalar(fill_pol):
            raise GridError('Invalid polarity')
        if not fill_pol in [-1, 1]:
            raise GridError('Invalid polarity')

        if surf_center.ndim != 1 or surf_center.size != 3:
            raise GridError('Invalid surface center')

        if surf_extent.ndim != 1 or surf_extent.size != 2:
            raise GridError('Invalid surface extent')

        edge_lim = self.exyz[fill_dir][0] if fill_pol == -1 else self.exyz[
            fill_dir][-1]
        cuboid_extent = np.insert(surf_extent, fill_dir,
                                  2 * np.abs(edge_lim - surf_center[fill_dir]))


        cuboid_center = np.array([surf_center[a] if a != fill_dir else \
                                     (surf_center[a]+0.5*fill_pol*cuboid_extent[a]) for a in range(3)])

        self.draw_cuboid(cuboid_center, cuboid_extent, eps)
예제 #9
0
    def get_slice(self,
                  surface_normal: Direction or int,
                  center: float,
                  which_shifts: int = 0,
                  sample_period: int = 1) -> np.ndarray:
        """
            Retrieve a slice of a grid.
            Interpolates if given a position between two planes.

            :param surface_normal: Axis normal to the plane we're displaying. Can be a Direction or
             integer in range(3)
            :param center: Scalar specifying position along surface_normal axis.
            :param which_shifts: Which grid to display. Default is the first grid (0).
            :param sample_period: Period for down-sampling the image. Default 1 (disabled)
            :return Array containing the portion of the grid.
        """
        if not is_scalar(center) and np.isreal(center):
            raise GridError('center must be a real scalar')

        sp = round(sample_period)
        if sp <= 0:
            raise GridError('sample_period must be positive')

        if not is_scalar(which_shifts) or which_shifts < 0:
            raise GridError('Invalid which_shifts')

        # Turn surface_normal into its integer representation
        if isinstance(surface_normal, Direction):
            surface_normal = surface_normal.value
        if surface_normal not in range(3):
            raise GridError('Invalid surface_normal direction')

        surface = np.delete(range(3), surface_normal)

        # Extract indices and weights of planes
        center3 = np.insert([0, 0], surface_normal, (center, ))
        center_index = self.pos2ind(center3,
                                    which_shifts,
                                    round_ind=False,
                                    check_bounds=False)[surface_normal]
        centers = np.unique([floor(center_index),
                             ceil(center_index)]).astype(int)
        if len(centers) == 2:
            fpart = center_index - floor(center_index)
            w = [1 - fpart, fpart]  # longer distance -> less weight
        else:
            w = [1]

        c_min, c_max = (self.xyz[surface_normal][i] for i in [0, -1])
        if center < c_min or center > c_max:
            raise GridError(
                'Coordinate of selected plane must be within simulation domain'
            )

        # Extract grid values from planes above and below visualized slice
        sliced_grid = zeros(self.shape[surface])
        for ci, weight in zip(centers, w):
            s = tuple(ci if a == surface_normal else np.s_[::sp]
                      for a in range(3))
            sliced_grid += weight * self.grids[which_shifts][tuple(s)]

        # Remove extra dimensions
        sliced_grid = np.squeeze(sliced_grid)

        return sliced_grid
예제 #10
0
    def render_polygon(self, polygon: np.ndarray, z_extent: np.ndarray,
                       eps: np.ndarray):
        """
        Function to render grid with contribution due to polygon 'polygon'.
        
        INPUTS:
        polygon - list of (x,y) vertices of the polygon being rendered
        z_extent - extent (z_1, z_2) along the extrusion direction of the polygon being rendered
        eps - permittivity of the polygon being rendered
        
        OUTPUTS:
        updates self.grids with the properly aliased polygon permittivity
        reduces self.frac_bg by the fraction of space occupied by the polygon 'polygon'
        """

        # Validating input arguments
        if polygon.ndim != 2 or polygon.shape[1] != 2:
            raise GridError(
                'Invalid format for specifying polygon - must be a Nx2 array')
        if polygon.shape[0] <= 2:
            raise GridError(
                'Malformed Polygon - must contain more than two points')
        if z_extent.ndim != 1 or z_extent.size != 2:
            raise GridError(
                'Invalid format for specifying z-extent - must be a vector of length 2'
            )

        def to_3D(vector: List or np.ndarray,
                  z: float = 0.5 * (z_extent[0] + z_extent[1])):
            return np.insert(vector, self.ext_dir, z)

        def get_zi(z: float, which_shifts: float):
            pos_3D = to_3D([0, 0], z)
            grid_coords = self.pos2ind(pos_3D,
                                       which_shifts,
                                       which_grid=GridType.PRIM,
                                       check_bounds=False)
            return grid_coords[self.ext_dir]

        # Calculating slice affected by polygon
        pbd_min = polygon.min(axis=0)
        pbd_max = polygon.max(axis=0)
        z_min = z_extent.min()
        z_max = z_extent.max()

        for n, grid in enumerate(self.grids):
            '''
            Computing the in-plane pixel values
            '''
            # Shape of the complementary grid
            comp_shape = np.array([self.shape[a]+2 if self.comp_shifts[n][a] == 0 else self.shape[a]+1 \
                          for a in range(3)])

            # Calculating the indices of the maximum and minimum polygon coordinate
            ind_xy_min = self.pos2ind(to_3D(pbd_min),
                                      which_shifts=n,
                                      which_grid=GridType.PRIM,
                                      round_ind=True,
                                      check_bounds=False)

            ind_xy_max = self.pos2ind(to_3D(pbd_max),
                                      which_shifts=n,
                                      which_grid=GridType.PRIM,
                                      round_ind=True,
                                      check_bounds=False)

            # Calculating the points on the grid that are affected by the drawn polygons
            corner_xy_min = ind_xy_min[self.planar_dir].astype(int)
            corner_xy_max = np.minimum(ind_xy_max[self.planar_dir] + 1,
                                       self.shape[self.planar_dir] -
                                       1).astype(int)

            # Calculating the points of the complementary grid that need to be passed
            comp_corner_xy_min = corner_xy_min.astype(int)
            comp_corner_xy_max = np.minimum(
                corner_xy_max + 1, comp_shape[self.planar_dir] - 1).astype(int)

            # Setting up slices
            edge_slice_xy = [
                np.s_[j:f + 1]
                for j, f in zip(comp_corner_xy_min, comp_corner_xy_max)
            ]

            # Calling the rastering function
            aa_x, aa_y = (self.shifted_exyz(which_shifts = n, which_grid = GridType.COMP)[a][s] \
                          for a,s in zip(self.planar_dir, edge_slice_xy))
            w_xy = raster_2D(polygon.T, aa_x, aa_y)
            '''
            Computing the pixel value along the surface normal
            '''
            # Calculating the indices of the start and stop point
            ind_z_min = get_zi(z_min, which_shifts=n)
            ind_z_max = get_zi(z_max, which_shifts=n)
            corner_z_min = ind_z_min.astype(int)
            corner_z_max = np.minimum(ind_z_max + 1,
                                      self.shape[self.ext_dir] - 1).astype(int)
            comp_corner_z_min = corner_z_min.astype(int)
            comp_corner_z_max = np.minimum(
                corner_z_max + 1, comp_shape[self.ext_dir] - 1).astype(int)

            edge_slice_z = np.s_[comp_corner_z_min:comp_corner_z_max + 1]
            aa_z = self.shifted_exyz(
                which_shifts=n,
                which_grid=GridType.COMP)[self.ext_dir][edge_slice_z]
            w_z = raster_1D(z_extent, aa_z)

            # Combining the extrusion and planar area calculation
            w = (w_xy[:, :, np.newaxis] * w_z).transpose(
                np.insert([0, 1], self.ext_dir, (2, )))

            # Adding to the grid
            center_slice = [None for a in range(3)]
            center_slice[self.ext_dir] = np.s_[corner_z_min:corner_z_max + 1]
            for i in range(2):
                center_slice[self.planar_dir[i]] = np.s_[
                    corner_xy_min[i]:corner_xy_max[i] + 1]

            # Updating permittivity
            self.grids[n][tuple(center_slice)] += eps[n] * w
            self.frac_bg[n][tuple(center_slice)] -= w
예제 #11
0
    def __init__(self,
                 pixel_edge_coordinates: List[np.ndarray],
                 ext_dir: Direction = Direction.z,
                 shifts: np.ndarray or List = Yee_Shifts_E,
                 comp_shifts: np.ndarray or List = Yee_Shifts_H,
                 initial: float or np.ndarray or List[float]
                 or List[np.ndarray] = (1.0, ) * 3,
                 num_grids: int = None,
                 periodic: bool or List[bool] = False):

        # Backgrdound permittivity and fraction of background permittivity in the grid
        self.grids_bg = []  # type: List[np.ndarray]
        self.frac_bg = []  # type: List[np.ndarray]

        # [[x0 y0 z0], [x1 y1 z1], ...] offsets for primary grid 0,1,...
        self.exyz = [np.unique(pixel_edge_coordinates[i]) for i in range(3)]
        for i in range(3):
            if len(self.exyz[i]) != len(pixel_edge_coordinates[i]):
                warnings.warn(
                    'Dimension {} had duplicate edge coordinates'.format(i))

        if is_scalar(periodic):
            self.periodic = [periodic] * 3
        else:
            self.periodic = [False] * 3

        self.shifts = np.array(shifts, dtype=float)
        self.comp_shifts = np.array(comp_shifts, dtype=float)
        if self.shifts.shape[1] != 3:
            GridError(
                'Misshapen shifts on the primary grid; second axis size should be 3,'
                ' shape is {}'.format(self.shifts.shape))
        if self.comp_shifts.shape[1] != 3:
            GridError(
                'Misshapen shifts on the complementary grid: second axis size should be 3,'
                ' shape is {}'.format(self.comp_shifts.shape))
        if self.comp_shifts.shape[0] != self.shifts.shape[0]:
            GridError(
                'Inconsistent number of shifts in the primary and complementary grid'
            )
        if not ((self.shifts >= 0).all() and (self.comp_shifts >= 0).all()):
            GridError(
                'Shifts are required to be non-negative for both primary and complementary grid'
            )

        num_shifts = self.shifts.shape[0]
        if num_grids is None:
            num_grids = num_shifts
        elif num_grids > num_shifts:
            raise GridError('Number of grids exceeds number of shifts (%u)' %
                            num_shifts)

        grids_shape = hstack((num_grids, self.shape))
        if is_scalar(initial):
            self.grids_bg = np.full(grids_shape, initial, dtype=complex)
        else:
            if len(initial) < num_grids:
                raise GridError('Too few initial grids specified!')

            self.grids_bg = [None] * num_grids
            for i in range(num_grids):
                if is_scalar(initial[i]):
                    if initial[i] is not None:
                        self.grids_bg[i] = np.full(self.shape,
                                                   initial[i],
                                                   dtype=complex)
                else:
                    if not np.array_equal(initial[i].shape, self.shape):
                        raise GridError(
                            'Initial grid sizes must match given coordinates')
                    self.grids_bg[i] = initial[i]

        if isinstance(ext_dir, Direction):
            self.ext_dir = ext_dir.value
        elif is_scalar(ext_dir):
            if ext_dir in range(3):
                self.ext_dir = ext_dir
            else:
                raise GridError('Invalid extrusion direction')
        else:
            raise GridError('Invalid extrusion direction')

        self.grids = np.full(
            grids_shape, 0.0,
            dtype=complex)  # contains the rendering of objects on the grid
        self.frac_bg = np.full(
            grids_shape, 1.0,
            dtype=complex)  # contains the fraction of background permittivity
        self.planar_dir = np.delete(range(3), self.ext_dir)
        self.list_polygons = [
        ]  # List of polygons corresponding to each block specified by user
        self.layer_polygons = [
        ]  # List of polygons after bifurcating extrusion direction into layers
        self.reduced_layer_polygons = [
        ]  # List of polygons after removing intersections

        self.list_z = [
        ]  # List of z coordinates of the different blocks specified by user
        self.layer_z = [
        ]  # List of z coordinates of the different distinct layers