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)
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)
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)
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 ])
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)
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)
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
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