diff --git a/tests/test_meshgenerate.py b/tests/test_meshgenerate.py index 0aea022ec8..20df9224fe 100644 --- a/tests/test_meshgenerate.py +++ b/tests/test_meshgenerate.py @@ -326,4 +326,62 @@ def test_grid_refinement(): ) -# # print(max_shrink) +def test_parse_structures(): + """Test some aspects of the structure parsing.""" + + source = td.PointDipole( + source_time=td.GaussianPulse(freq0=1e14, fwidth=1e13), + size=(0, 0, 0), + polarization="Ex", + ) + + box1 = td.Structure( + geometry=td.Box(center=(0, 0, 0), size=(2, 2, 2)), medium=td.Medium(permittivity=9) + ) + # covers box1 along x and y but not z, smaller permittivity + box2 = td.Structure( + geometry=td.Box(center=(0, 0, 0), size=(200, 200, 1)), medium=td.Medium(permittivity=4) + ) + # covers box1 along x only, smaller permittivity + box3 = td.Structure( + geometry=td.Box(center=(0, 1.5, 0), size=(200, 4, 1)), medium=td.Medium(permittivity=4) + ) + # fully covers one edge of box1 + box4 = td.Structure( + geometry=td.Box(center=(0, 1.01, 0), size=(200, 0.2, 2)), medium=td.Medium(permittivity=2) + ) + + # Test that the box2 permittivity is used along z in the region where it fully covers box1 + sim = td.Simulation( + size=(3, 3, 3), + grid_spec=td.GridSpec.auto(), + run_time=1e-13, + structures=[box1, box2], + sources=[source], + ) + sizes = sim.grid.sizes.to_list[2] + assert sizes[sizes.size // 2] > 0.1 + + # Test that the box3 permittivity is not used along z as it doesn't fully cover box1 + sim = td.Simulation( + size=(3, 3, 3), + grid_spec=td.GridSpec.auto(), + run_time=1e-13, + structures=[box1, box3], + sources=[source], + ) + sizes = sim.grid.sizes.to_list[2] + assert sizes[sizes.size // 2] < 0.1 + + # Test that there is no grid boundary along y at the box1 right side covered by box4 + boundaries = sim.grid.boundaries.to_list[1] + assert 1.0 in boundaries + sim = td.Simulation( + size=(3, 3, 3), + grid_spec=td.GridSpec.auto(), + run_time=1e-13, + structures=[box1, box4], + sources=[source], + ) + boundaries = sim.grid.boundaries.to_list[1] + assert 1.0 not in boundaries diff --git a/tidy3d/components/grid/grid_spec.py b/tidy3d/components/grid/grid_spec.py index 2d9cd43f46..b00ef07ae0 100644 --- a/tidy3d/components/grid/grid_spec.py +++ b/tidy3d/components/grid/grid_spec.py @@ -12,6 +12,7 @@ from ..types import Axis, Symmetry from ..source import SourceType from ..structure import Structure +from ..geometry import Box from ...log import SetupError, log from ...constants import C_0, MICROMETER @@ -22,11 +23,9 @@ class GridSpec1d(Tidy3dBaseModel, ABC): def make_coords( # pylint:disable = too-many-arguments self, - center: float, - size: float, axis: Axis, structures: List[Structure], - symmetry: Symmetry, + symmetry: Tuple[Symmetry, Symmetry, Symmetry], wavelength: pd.PositiveFloat, num_pml_layers: Tuple[pd.NonNegativeInt, pd.NonNegativeInt], ) -> Coords1D: @@ -35,17 +34,13 @@ def make_coords( # pylint:disable = too-many-arguments Parameters ---------- - center : float - Center of simulation domain along a given axis. - size : float - Size of simulation domain along a given axis. axis : Axis Axis of this direction. structures : List[Structure] - List of structures present in simulation. - symmetry : Symmetry - Reflection symmetry across a plane bisecting the simulation domain normal - to a given axis. + List of structures present in simulation, the first one being the simulation domain. + symmetry : Tuple[Symmetry, Symmetry, Symmetry] + Reflection symmetry across a plane bisecting the simulation domain + normal to each of the three axes. wavelength : float Free-space wavelength. num_pml_layers : Tuple[int, int] @@ -59,16 +54,21 @@ def make_coords( # pylint:disable = too-many-arguments # Determine if one should apply periodic boundary condition. # This should only affect auto nonuniform mesh generation for now. - is_periodic = sum(num_pml_layers) == 0 + is_periodic = sum(num_pml_layers) == 0 and symmetry[axis] == 0 # generate boundaries bound_coords = self._make_coords_initial( - center, size, axis, structures, wavelength, is_periodic + axis=axis, + structures=structures, + wavelength=wavelength, + symmetry=symmetry, + is_periodic=is_periodic, ) # incooperate symmetries - if symmetry != 0: + if symmetry[axis] != 0: # Offset to center if symmetry present + center = structures[0].geometry.center[axis] center_ind = np.argmin(np.abs(center - bound_coords)) bound_coords += center - bound_coords[center_ind] bound_coords = bound_coords[bound_coords >= center] @@ -81,9 +81,9 @@ def make_coords( # pylint:disable = too-many-arguments @abstractmethod def _make_coords_initial( self, - center: float, - size: pd.NonNegativeFloat, - *args, + axis: Axis, + structures: List[Structure], + **kwargs, ) -> Coords1D: """Generate 1D coords to be used as grid boundaries, based on simulation parameters. Symmetry, PML etc. are not considered in this method. @@ -92,11 +92,9 @@ def _make_coords_initial( Parameters ---------- - center : float - Center of simulation domain along a given axis. - size : float - Sie of simulation domain along a given axis. - *args + structures : List[Structure] + List of structures present in simulation, the first one being the simulation domain. + **kwargs Other arguments Returns @@ -152,19 +150,19 @@ class UniformGrid(GridSpec1d): def _make_coords_initial( self, - center: float, - size: float, - *args, + axis: Axis, + structures: List[Structure], + **kwargs, ) -> Coords1D: """Uniform 1D coords to be used as grid boundaries. Parameters ---------- - center : float - Center of simulation domain along a given axis. - size : float - Size of simulation domain along a given axis. - *args: + axis : Axis + Axis of this direction. + structures : List[Structure] + List of structures present in simulation, the first one being the simulation domain. + **kwargs: Other arguments all go here. Returns @@ -173,6 +171,8 @@ def _make_coords_initial( 1D coords to be used as grid boundaries. """ + center, size = structures[0].geometry.center[axis], structures[0].geometry.size[axis] + # Take a number of steps commensurate with the size; make dl a bit smaller if needed num_cells = int(np.ceil(size / self.dl)) @@ -210,19 +210,19 @@ class CustomGrid(GridSpec1d): def _make_coords_initial( self, - center: float, - size: float, - *args, + axis: Axis, + structures: List[Structure], + **kwargs, ) -> Coords1D: """Customized 1D coords to be used as grid boundaries. Parameters ---------- - center : float - Center of simulation domain along a given axis. - size : float - Size of simulation domain along a given axis. - *args + axis : Axis + Axis of this direction. + structures : List[Structure] + List of structures present in simulation, the first one being the simulation domain. + *kwargs Other arguments all go here. Returns @@ -231,6 +231,8 @@ def _make_coords_initial( 1D coords to be used as grid boundaries. """ + center, size = structures[0].geometry.center[axis], structures[0].geometry.size[axis] + # get bounding coordinates dl = np.array(self.dl) bound_coords = np.append(0.0, np.cumsum(dl)) @@ -284,29 +286,27 @@ class AutoGrid(GridSpec1d): description="The type of mesher to use to generate the grid automatically.", ) - def _make_coords_initial( # pylint:disable = arguments-differ, too-many-arguments + def _make_coords_initial( # pylint:disable=too-many-arguments,arguments-differ,too-many-locals self, - center: float, - size: float, axis: Axis, structures: List[Structure], wavelength: float, + symmetry: Symmetry, is_periodic: bool, ) -> Coords1D: """Customized 1D coords to be used as grid boundaries. Parameters ---------- - center : float - Center of simulation domain along a given axis. - size : float - Size of simulation domain along a given axis. axis : Axis Axis of this direction. structures : List[Structure] List of structures present in simulation. wavelength : float Free-space wavelength. + symmetry : Tuple[Symmetry, Symmetry, Symmetry] + Reflection symmetry across a plane bisecting the simulation domain + normal to each of the three axes. is_periodic : bool Apply periodic boundary condition or not. @@ -316,14 +316,29 @@ def _make_coords_initial( # pylint:disable = arguments-differ, too-many-argumen 1D coords to be used as grid boundaries. """ + sim_cent = list(structures[0].geometry.center) + sim_size = list(structures[0].geometry.size) + for dim, sym in enumerate(symmetry): + if sym != 0: + sim_cent[dim] += sim_size[dim] / 4 + sim_size[dim] /= 2 + + struct_list = [ + Structure(geometry=Box(center=sim_cent, size=sim_size), medium=structures[0].medium) + ] + # Remove structures that are outside the simulation domain (with symmetry applied) + for structure in structures[1:]: + if struct_list[0].geometry.intersects(structure.geometry): + struct_list.append(structure) + # parse structures interval_coords, max_dl_list = self.mesher.parse_structures( - axis, structures, wavelength, self.min_steps_per_wvl + axis, struct_list, wavelength, self.min_steps_per_wvl ) # Put just a single pixel if 2D-like simulation if interval_coords.size == 1: dl = wavelength / self.min_steps_per_wvl - return np.array([center - dl / 2, center + dl / 2]) + return np.array([sim_cent[axis] - dl / 2, sim_cent[axis] + dl / 2]) # generate mesh steps interval_coords = np.array(interval_coords).flatten() @@ -444,8 +459,6 @@ def make_grid( Entire simulation grid. """ - center, size = structures[0].geometry.center, structures[0].geometry.size - # Set up wavelength for automatic mesh generation if needed. wavelength = self.wavelength if self.wavelength is None and self.auto_grid_used: @@ -453,29 +466,23 @@ def make_grid( log.info(f"Auto meshing using wavelength {wavelength:1.4f} defined from sources.") coords_x = self.grid_x.make_coords( - center=center[0], - size=size[0], axis=0, structures=structures + self.override_structures, - symmetry=symmetry[0], + symmetry=symmetry, wavelength=wavelength, num_pml_layers=num_pml_layers[0], ) coords_y = self.grid_y.make_coords( - center=center[1], - size=size[1], axis=1, structures=structures + self.override_structures, - symmetry=symmetry[1], + symmetry=symmetry, wavelength=wavelength, num_pml_layers=num_pml_layers[1], ) coords_z = self.grid_z.make_coords( - center=center[2], - size=size[2], axis=2, structures=structures + self.override_structures, - symmetry=symmetry[2], + symmetry=symmetry, wavelength=wavelength, num_pml_layers=num_pml_layers[2], ) diff --git a/tidy3d/components/grid/mesher.py b/tidy3d/components/grid/mesher.py index ebbe343cc7..c7b4feab58 100644 --- a/tidy3d/components/grid/mesher.py +++ b/tidy3d/components/grid/mesher.py @@ -8,7 +8,7 @@ from scipy.optimize import root_scalar from ..base import Tidy3dBaseModel -from ..types import Axis +from ..types import Axis, Array from ..structure import Structure from ...log import SetupError, ValidationError from ...constants import C_0, fp_eps @@ -24,17 +24,17 @@ def parse_structures( structures: List[Structure], wavelength: pd.PositiveFloat, min_steps_per_wvl: pd.NonNegativeInt, - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> Tuple[Array[float], Array[float]]: """Calculate the positions of all bounding box interfaces along a given axis.""" @abstractmethod def make_grid_multiple_intervals( self, - max_dl_list: np.ndarray, - len_interval_list: np.ndarray, + max_dl_list: Array[float], + len_interval_list: Array[float], max_scale: float, is_periodic: bool, - ) -> List[np.ndarray]: + ) -> List[Array[float]]: """Create grid steps in multiple connecting intervals.""" @@ -49,7 +49,7 @@ def parse_structures( structures: List[Structure], wavelength: pd.PositiveFloat, min_steps_per_wvl: pd.NonNegativeInt, - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> Tuple[Array[float], Array[float]]: """Calculate the positions of all bounding box interfaces along a given axis. In this implementation, in most cases the complexity should be O(len(structures)**2), although the worst-case complexity may approach O(len(structures)**3). @@ -59,7 +59,7 @@ def parse_structures( ---------- axis : Axis Axis index along which to operate. - structures : List[Structures] + structures : List[Structure] List of structures, with the simulation structure being the first item. wavelength : pd.PositiveFloat Wavelength to use for the step size and for dispersive media epsilon. @@ -84,78 +84,61 @@ def parse_structures( sim_bmin, sim_bmax = structures[0].geometry.bounds domain_bounds = np.array([sim_bmin[axis], sim_bmax[axis]]) - # Required minimum steps in every material - medium_steps = [] - for structure in structures: - n, k = structure.medium.eps_complex_to_nk(structure.medium.eps_model(C_0 / wavelength)) - index = max(abs(n), abs(k)) - medium_steps.append(wavelength / index / min_steps_per_wvl) - medium_steps = np.array(medium_steps) + # Required maximum steps in every structure + structure_steps = self.structure_steps(structures, wavelength, min_steps_per_wvl) # If empty simulation, return if len(structures) == 1: - return (domain_bounds, medium_steps) + return (domain_bounds, structure_steps) - # Coordinates of all bounding boxes - interval_coords = np.array(domain_bounds) + # Bounding boxes with the meshing axis rotated to z + struct_bbox = self.rotate_structure_bounds(structures, axis) - # Bounding squares in the plane normal to axis (xmin, ymin, xmax, ymax) - _, pinds = structures[0].geometry.pop_axis([0, 1, 2], axis=axis) - sim_plane_bbox = [sim_bmin[pinds[0]], sim_bmin[pinds[1]]] - sim_plane_bbox += [sim_bmax[pinds[0]], sim_bmax[pinds[1]]] - plane_bbox = [np.array(sim_plane_bbox)] # will have len equal to len(structures) - - # list of indexes of structures which are contained in each interval + # Array of coordinates of all intervals; add the simulation domain bounds already + interval_coords = np.array(domain_bounds) + # List of indexes of structures which are present in each interval interval_structs = [[0]] # will have len equal to len(interval_coords) - 1 + # List of indexes of structures that every structre contains in 2D + struct_contains = [] # will have len equal to len(structures) - # list of indexes of structures that are contained in 2D inside another structure - struct_contains = [[]] # will have len equal to len(structures) + for struct_ind in range(len(structures) - 1, 0, -1): + bbox = struct_bbox[struct_ind] - for struct_ind, structure in enumerate(structures[1:]): - # get 3D bounding box and write 2D bouding box - bmin, bmax = structure.geometry.bounds - bounds_2d = np.array([bmin[pinds[0]], bmin[pinds[1]], bmax[pinds[0]], bmax[pinds[1]]]) - plane_bbox.append(bounds_2d) - - # indexes of structures that are fully covered in 2D by the current structure - struct_contain_inds = [] - for ind, plane_bounds in enumerate(plane_bbox[:-1]): - # faster to do the comparison this way than with e.g. np.all - if ( - bounds_2d[0] <= plane_bounds[0] - and bounds_2d[1] <= plane_bounds[1] - and bounds_2d[2] >= plane_bounds[2] - and bounds_2d[3] >= plane_bounds[3] - ): - struct_contain_inds.append(ind) - struct_contains.append(struct_contain_inds) - - # figure out where to place the bounding box coordinates of current structure - coord_min, coord_max = bmin[axis], bmax[axis] - indsmin = np.argwhere(coord_min < interval_coords) - indsmax = np.argwhere(coord_max > interval_coords) - - # Exit if structure is outside of domain bounds - if ( - indsmin.size == 0 - or indsmax.size == 0 - or np.any(bounds_2d[:2] >= sim_plane_bbox[2:]) - or np.any(bounds_2d[2:] <= sim_plane_bbox[:2]) - ): - continue + # indexes of structures that the current structure contains in 2D + struct_contains.append(self.contains_2d(bbox, struct_bbox[:struct_ind])) - # Add current structure bounding box coordinates + # Figure out where to place the bounding box coordinates of current structure + indsmin = np.argwhere(bbox[0, 2] <= interval_coords) indmin = int(indsmin[0]) - indmax = int(indsmax[-1]) + 2 - interval_coords = np.insert(interval_coords, indmin, coord_min) - interval_coords = np.insert(interval_coords, indmax, coord_max) - # Copy the structure containment list to the newly created interval - structs_list_copy = interval_structs[max(0, indmin - 1)].copy() - interval_structs.insert(indmin, structs_list_copy) - structs_list_copy = interval_structs[min(indmax - 1, len(interval_structs) - 1)].copy() - interval_structs.insert(indmax, structs_list_copy) + bbox0 = np.copy(bbox) + bbox0[1, 2] = bbox0[0, 2] + if not self.is_contained(bbox0, struct_bbox[struct_ind + 1 :]): + # Add current structure bounding box coordinates + interval_coords = np.insert(interval_coords, indmin, bbox[0, 2]) + # Copy the structure containment list to the newly created interval + struct_list = interval_structs[max(0, indmin - 1)] + interval_structs.insert(indmin, struct_list.copy()) + + indsmax = np.argwhere(bbox[1, 2] >= interval_coords) + indmax = int(indsmax[-1]) + bbox0 = np.copy(bbox) + bbox0[0, 2] = bbox0[1, 2] + if not self.is_contained(bbox0, struct_bbox[struct_ind + 1 :]): + indmax += 1 + # Add current structure bounding box coordinates + interval_coords = np.insert(interval_coords, indmax, bbox[1, 2]) + # Copy the structure containment list to the newly created interval + struct_list = interval_structs[min(indmax - 1, len(interval_structs) - 1)] + interval_structs.insert(indmax, struct_list.copy()) + + # Add the structure index to all intervals that it spans for interval_ind in range(indmin, indmax): - interval_structs[interval_ind].append(struct_ind + 1) + interval_structs[interval_ind].append(struct_ind) + + # The simulation domain is the lowest structure and doesn't contain anything + struct_contains.append([]) + # Reverse to match the order in the structures list + struct_contains = struct_contains[::-1] # Truncate intervals to domain bounds b_array = np.array(interval_coords) @@ -164,7 +147,7 @@ def parse_structures( interval_structs = [interval_structs[int(i)] for i in in_domain if i < b_array.size - 1] # Remove intervals that are smaller than the absolute smallest min_step - min_step = np.amin(medium_steps) + min_step = np.amin(structure_steps) coords_filter = [interval_coords[0]] structs_filter = [] for coord_ind, coord in enumerate(interval_coords[1:]): @@ -177,8 +160,8 @@ def parse_structures( # Compute the maximum allowed step size in each interval max_steps = [] for coord_ind, _ in enumerate(interval_coords[:-1]): - # Structure indexes inside current interval; reverse so first structure on top - struct_list = interval_structs[coord_ind][::-1] + # Structure indexes inside current intervall in order of latter structures come first + struct_list = interval_structs[coord_ind] struct_list_filter = [] # Handle containment for ind, struct_ind in enumerate(struct_list): @@ -192,27 +175,140 @@ def parse_structures( struct_list = [ind for ind in struct_list if ind not in contains] # Define the max step as the minimum over all medium steps of media in this interval - max_step = np.amin(medium_steps[struct_list_filter]) + max_step = np.amin(structure_steps[struct_list_filter]) max_steps.append(float(max_step)) return interval_coords, np.array(max_steps) + @staticmethod + def structure_steps( + structures: List[Structure], wavelength: float, min_steps_per_wvl: float + ) -> Array[float]: + """Get the minimum mesh required in each structure. + + Parameters + ---------- + structures : List[Structure] + List of structures, with the simulation structure being the first item. + wavelength : float + Wavelength to use for the step size and for dispersive media epsilon. + min_steps_per_wvl : float + Minimum requested steps per wavelength. + """ + min_steps = [] + for structure in structures: + n, k = structure.medium.eps_complex_to_nk(structure.medium.eps_model(C_0 / wavelength)) + index = max(abs(n), abs(k)) + min_steps.append(wavelength / index / min_steps_per_wvl) + return np.array(min_steps) + + @staticmethod + def rotate_structure_bounds(structures: List[Structure], axis: Axis) -> List[Array[float]]: + """Get sturcture bounding boxes with a given ``axis`` rotated to z. + + Parameters + ---------- + structures : List[Structure] + List of structures, with the simulation structure being the first item. + axis : Axis + Axis index to place last. + + Returns + ------- + List[Array[float]] + A list of the bounding boxes of shape ``(2, 3)`` for each structure, with the bounds + along ``axis`` being ``(:, 2)``. + """ + struct_bbox = [] + for structure in structures: + # Get 3D bounding box and rotate axes + bmin, bmax = structure.geometry.bounds + bmin_ax, bmin_plane = structure.geometry.pop_axis(bmin, axis=axis) + bmax_ax, bmax_plane = structure.geometry.pop_axis(bmax, axis=axis) + bounds = np.array([list(bmin_plane) + [bmin_ax], list(bmax_plane) + [bmax_ax]]) + struct_bbox.append(bounds) + return struct_bbox + + @staticmethod + def is_contained(bbox0: Array[float], bbox_list: List[Array[float]]) -> bool: + """Check if a bounding box is contained in any of a list of bounding boxes. + + Parameters + ---------- + bbox0 : Array[float] + Bounding box to check. + bbox_list : List[Array[float]] + List of bounding boxes to check if they contain ``bbox0``. + + Returns + ------- + contained : bool + ``True`` if ``bbox0`` is contained in any of the boxes in the list. + """ + contained = False + for bounds in bbox_list: + # It can be much faster to write out the conditions one by one than e.g. to use np.all + # on the bottom values and np.all on the top values + if all( + [ + bbox0[0, 0] >= bounds[0, 0], + bbox0[1, 0] <= bounds[1, 0], + bbox0[0, 1] >= bounds[0, 1], + bbox0[1, 1] <= bounds[1, 1], + bbox0[0, 2] >= bounds[0, 2], + bbox0[1, 2] <= bounds[1, 2], + ] + ): + contained = True + return contained + + @staticmethod + def contains_2d(bbox0: Array[float], bbox_list: List[Array[float]]) -> List[int]: + """Check if a bounding box contains along the first two dimensions any of a list of + bounding boxes. + + Parameters + ---------- + bbox0 : Array[float] + Bounding box to check. + bbox_list : List[Array[float]] + List of bounding boxes to check if they are contained in ``bbox0``. + + Returns + ------- + List[int] + A list with all the indexes into the ``bbox_list`` that are contained in ``bbox0`` + along the first two dimensions. + """ + struct_contains_inds = [] + for ind, bounds in enumerate(bbox_list): + if all( + [ + bbox0[0, 0] <= bounds[0, 0], + bbox0[1, 0] >= bounds[1, 0], + bbox0[0, 1] <= bounds[0, 1], + bbox0[1, 1] >= bounds[1, 1], + ] + ): + struct_contains_inds.append(ind) + return struct_contains_inds + def make_grid_multiple_intervals( # pylint:disable=too-many-locals self, - max_dl_list: np.ndarray, - len_interval_list: np.ndarray, + max_dl_list: Array[float], + len_interval_list: Array[float], max_scale: float, is_periodic: bool, - ) -> List[np.ndarray]: + ) -> List[Array[float]]: """Create grid steps in multiple connecting intervals of length specified by ``len_interval_list``. The maximal allowed step size in each interval is given by ``max_dl_list``. The maximum ratio between neighboring steps is bounded by ``max_scale``. Parameters ---------- - max_dl_list : np.ndarray + max_dl_list : Array[float] Maximal allowed step size of each interval. - len_interval_list : np.ndarray + len_interval_list : Array[float] A list of interval lengths max_scale : float Maximal ratio between consecutive steps. @@ -221,7 +317,7 @@ def make_grid_multiple_intervals( # pylint:disable=too-many-locals Returns ------- - List[np.ndarray] + List[Array[float]] A list of of step sizes in each interval. """ @@ -296,19 +392,19 @@ def make_grid_multiple_intervals( # pylint:disable=too-many-locals def grid_multiple_interval_analy_refinement( self, - max_dl_list: np.ndarray, - len_interval_list: np.ndarray, + max_dl_list: Array[float], + len_interval_list: Array[float], max_scale: float, is_periodic: bool, - ) -> Tuple[np.ndarray, np.ndarray]: + ) -> Tuple[Array[float], Array[float]]: """Analytical refinement for multiple intervals. "analytical" meaning we allow non-integar step sizes, so that we don't consider snapping here. Parameters ---------- - max_dl_list : np.ndarray + max_dl_list : Array[float] Maximal allowed step size of each interval. - len_interval_list : np.ndarray + len_interval_list : Array[float] A list of interval lengths max_scale : float Maximal ratio between consecutive steps. @@ -317,7 +413,7 @@ def grid_multiple_interval_analy_refinement( Returns ------- - Tuple[np.ndarray, np.ndarray] + Tuple[Array[float], Array[float]] left and right step sizes of each interval. """ @@ -385,7 +481,7 @@ def make_grid_in_interval( max_dl: float, max_scale: float, len_interval: float, - ) -> np.ndarray: + ) -> Array[float]: """Create a set of grid steps in an interval of length ``len_interval``, with first step no larger than ``max_scale * left_neighbor_dl`` and last step no larger than ``max_scale * right_neighbor_dl``, with maximum ratio ``max_scale`` between @@ -406,7 +502,7 @@ def make_grid_in_interval( Returns ------- - np.ndarray + Array[float] A list of step sizes in the interval. """ @@ -491,7 +587,7 @@ def grid_grow_plateau_decrease_in_interval( max_dl: float, max_scale: float, len_interval: float, - ) -> np.ndarray: + ) -> Array[float]: """In an interval, grid grows, plateau, and decrease, resembling Lambda letter but with plateau in the connection part.. @@ -560,7 +656,7 @@ def grid_grow_decrease_in_interval( right_dl: float, max_scale: float, len_interval: float, - ) -> np.ndarray: + ) -> Array[float]: """In an interval, grid grows, and decrease, resembling Lambda letter. Parameters @@ -648,7 +744,7 @@ def grid_grow_plateau_in_interval( large_dl: float, max_scale: float, len_interval: float, - ) -> np.ndarray: + ) -> Array[float]: """In an interval, grid grows, then plateau. Parameters @@ -664,7 +760,7 @@ def grid_grow_plateau_in_interval( Returns ------- - np.ndarray + Array[float] A list of step sizes in the interval, in ascending order. """ # steps for scaling @@ -702,7 +798,7 @@ def grid_grow_in_interval( small_dl: float, max_scale: float, len_interval: float, - ) -> np.ndarray: + ) -> Array[float]: """Mesh simply grows in an interval. Parameters @@ -716,7 +812,7 @@ def grid_grow_in_interval( Returns ------- - np.ndarray + Array[float] A list of step sizes in the interval, in ascending order. """ diff --git a/tidy3d/components/simulation.py b/tidy3d/components/simulation.py index 1c4fb70f1c..09643feb7a 100644 --- a/tidy3d/components/simulation.py +++ b/tidy3d/components/simulation.py @@ -48,7 +48,7 @@ class Simulation(Box): # pylint:disable=too-many-public-methods Example ------- >>> from tidy3d import Sphere, Cylinder, PolySlab - >>> from tidy3d import CurrentSource, GaussianPulse + >>> from tidy3d import UniformCurrentSource, GaussianPulse >>> from tidy3d import FieldMonitor, FluxMonitor >>> from tidy3d import GridSpec, AutoGrid >>> sim = Simulation( @@ -66,7 +66,7 @@ class Simulation(Box): # pylint:disable=too-many-public-methods ... ), ... ], ... sources=[ - ... CurrentSource( + ... UniformCurrentSource( ... size=(0, 0, 0), ... center=(0, 0.5, 0), ... polarization="Hx", diff --git a/tidy3d/version.py b/tidy3d/version.py index a080e9c76c..c586f54319 100644 --- a/tidy3d/version.py +++ b/tidy3d/version.py @@ -1,3 +1,3 @@ """Defines the front end version of tidy3d""" -__version__ = "1.3.0" +__version__ = "1.3.1"