"""Defines cells for the EME simulation."""from__future__importannotationsfromabcimportABC,abstractmethodfromtypingimportList,Literal,Tuple,Unionimportnumpyasnpimportpydantic.v1aspdfrom...constantsimportRADIAN,fp_epsfrom...exceptionsimportSetupError,ValidationErrorfrom..baseimportTidy3dBaseModel,skip_if_fields_missingfrom..geometry.baseimportBoxfrom..grid.gridimportCoords1Dfrom..mode_specimportModeSpecfrom..typesimportArrayFloat1D,Axis,Coordinate,Size,TrackFreq# grid limitsMAX_NUM_MODES=100MAX_NUM_EME_CELLS=100
[docs]classEMEModeSpec(ModeSpec):"""Mode spec for EME cells. Overrides some of the defaults and allowed values."""track_freq:Union[TrackFreq,None]=pd.Field(None,title="Mode Tracking Frequency",description="Parameter that turns on/off mode tracking based on their similarity. ""Can take values ``'lowest'``, ``'central'``, or ``'highest'``, which correspond to ""mode tracking based on the lowest, central, or highest frequency. ""If ``None`` no mode tracking is performed, which is the default for best performance.",)angle_theta:Literal[0.0]=pd.Field(0.0,title="Polar Angle",description="Polar angle of the propagation axis from the injection axis. Not currently ""supported in EME cells. Use an additional 'ModeSolverMonitor' and ""'sim_data.smatrix_in_basis' to achieve off-normal injection in EME.",units=RADIAN,)angle_phi:Literal[0.0]=pd.Field(0.0,title="Azimuth Angle",description="Azimuth angle of the propagation axis in the plane orthogonal to the ""injection axis. Not currently supported in EME cells. Use an additional ""'ModeSolverMonitor' and 'sim_data.smatrix_in_basis' to achieve off-normal ""injection in EME.",units=RADIAN,)# this method is not supported because not all ModeSpec features are supported# @classmethod# def _from_mode_spec(cls, mode_spec: ModeSpec) -> EMEModeSpec:# """Convert to ordinary :class:`.ModeSpec`."""# return cls(# num_modes=mode_spec.num_modes,# target_neff=mode_spec.target_neff,# num_pml=mode_spec.num_pml,# filter_pol=mode_spec.filter_pol,# angle_theta=mode_spec.angle_theta,# angle_phi=mode_spec.angle_phi,# precision=mode_spec.precision,# bend_radius=mode_spec.bend_radius,# bend_axis=mode_spec.bend_axis,# track_freq=mode_spec.track_freq,# group_index_step=mode_spec.group_index_step,# )def_to_mode_spec(self)->ModeSpec:"""Convert to ordinary :class:`.ModeSpec`."""returnModeSpec(num_modes=self.num_modes,target_neff=self.target_neff,num_pml=self.num_pml,filter_pol=self.filter_pol,angle_theta=self.angle_theta,angle_phi=self.angle_phi,precision=self.precision,bend_radius=self.bend_radius,bend_axis=self.bend_axis,track_freq=self.track_freq,group_index_step=self.group_index_step,)
classEMEGridSpec(Tidy3dBaseModel,ABC):"""Specification for an EME grid. An EME grid is a 1D grid aligned with the propagation axis, dividing the simulation into cells. Modes and mode coefficients are defined at the central plane of each cell. Typically, cell boundaries are aligned with interfaces between structures in the simulation. """@abstractmethoddefmake_grid(self,center:Coordinate,size:Size,axis:Axis)->EMEGrid:"""Generate EME grid from the EME grid spec. Parameters ---------- center: :class:`.Coordinate` Center of the EME simulation. size: :class:`.Size` Size of the EME simulation. axis: :class:`.Axis` Propagation axis for the EME simulation. Returns ------- :class:`.EMEGrid` An EME grid dividing the EME simulation into cells, as defined by the EME grid spec. """
[docs]classEMEUniformGrid(EMEGridSpec):"""Specification for a uniform EME grid. Example ------- >>> from tidy3d import EMEModeSpec >>> mode_spec = EMEModeSpec(num_modes=10) >>> eme_grid = EMEUniformGrid(num_cells=10, mode_spec=mode_spec) """num_cells:pd.PositiveInt=pd.Field(...,title="Number of cells",description="Number of cells in the uniform EME grid.")mode_spec:EMEModeSpec=pd.Field(...,title="Mode Specification",description="Mode specification for the uniform EME grid.")
[docs]defmake_grid(self,center:Coordinate,size:Size,axis:Axis)->EMEGrid:"""Generate EME grid from the EME grid spec. Parameters ---------- center: :class:`.Coordinate` Center of the EME simulation. size: :class:`.Size` Size of the EME simulation. axis: :class:`.Axis` Propagation axis for the EME simulation. Returns ------- :class:`.EMEGrid` An EME grid dividing the EME simulation into cells, as defined by the EME grid spec. """rmin=center[axis]-size[axis]/2rmax=center[axis]+size[axis]/2boundaries=np.linspace(rmin,rmax,self.num_cells+1)mode_specs=[self.mode_specfor_inrange(len(boundaries)-1)]returnEMEGrid(boundaries=boundaries,mode_specs=mode_specs,center=center,size=size,axis=axis)
[docs]classEMEExplicitGrid(EMEGridSpec):"""EME grid with explicitly defined internal boundaries. Example ------- >>> from tidy3d import EMEExplicitGrid, EMEModeSpec >>> mode_spec1 = EMEModeSpec(num_modes=10) >>> mode_spec2 = EMEModeSpec(num_modes=20) >>> eme_grid = EMEExplicitGrid( ... mode_specs=[mode_spec1, mode_spec2], ... boundaries=[1], ... ) """mode_specs:List[EMEModeSpec]=pd.Field(...,title="Mode Specifications",description="Mode specifications for each cell ""in the explicit EME grid.",)boundaries:ArrayFloat1D=pd.Field(...,title="Boundaries",description="List of coordinates of internal cell boundaries along the propagation axis. ""Must contain one fewer item than 'mode_specs', and must be strictly increasing. ""Each cell spans the region between an adjacent pair of boundaries. ""The first (last) cell spans the region between the first (last) boundary ""and the simulation boundary.",)@pd.validator("boundaries",always=True)@skip_if_fields_missing(["mode_specs"])def_validate_boundaries(cls,val,values):"""Check that boundaries is increasing and contains one fewer element than mode_specs."""mode_specs=values["mode_specs"]boundaries=valiflen(mode_specs)-1!=len(boundaries):raiseValidationError("There must be exactly one fewer item in 'boundaries' than ""in 'mode_specs'.")iflen(boundaries)>0:rmin=boundaries[0]forrmaxinboundaries[1:]:ifrmax<rmin:raiseValidationError("The 'boundaries' must be increasing.")rmin=rmaxreturnval
[docs]defmake_grid(self,center:Coordinate,size:Size,axis:Axis)->EMEGrid:"""Generate EME grid from the EME grid spec. Parameters ---------- center: :class:`.Coordinate` Center of the EME simulation. size: :class:`.Size` Size of the EME simulation. axis: :class:`.Axis` Propagation axis for the EME simulation. Returns ------- :class:`.EMEGrid` An EME grid dividing the EME simulation into cells, as defined by the EME grid spec. """sim_rmin=center[axis]-size[axis]/2sim_rmax=center[axis]+size[axis]/2iflen(self.boundaries)>0:ifsim_rmin-self.boundaries[0]>fp_eps:raiseValidationError("The first item in 'boundaries' is outside the simulation domain.")ifself.boundaries[-1]-sim_rmax>fp_eps:raiseValidationError("The last item in 'boundaries' is outside the simulation domain.")boundaries=[sim_rmin]+list(self.boundaries)+[sim_rmax]returnEMEGrid(boundaries=boundaries,center=center,size=size,axis=axis,mode_specs=self.mode_specs,)
[docs]classEMECompositeGrid(EMEGridSpec):"""EME grid made out of multiple subgrids. Example ------- >>> from tidy3d import EMEUniformGrid, EMEModeSpec >>> mode_spec1 = EMEModeSpec(num_modes=10) >>> mode_spec2 = EMEModeSpec(num_modes=20) >>> subgrid1 = EMEUniformGrid(num_cells=5, mode_spec=mode_spec1) >>> subgrid2 = EMEUniformGrid(num_cells=10, mode_spec=mode_spec2) >>> eme_grid = EMECompositeGrid( ... subgrids=[subgrid1, subgrid2], ... subgrid_boundaries=[1] ... ) """subgrids:List[EMESubgridType]=pd.Field(...,title="Subgrids",description="Subgrids in the composite grid.")subgrid_boundaries:ArrayFloat1D=pd.Field(...,title="Subgrid Boundaries",description="List of coordinates of internal subgrid boundaries along the propagation axis. ""Must contain one fewer item than 'subgrids', and must be strictly increasing. ""Each subgrid spans the region between an adjacent pair of subgrid boundaries. ""The first (last) subgrid spans the region between the first (last) subgrid boundary ""and the simulation boundary.",)@pd.validator("subgrid_boundaries",always=True)def_validate_subgrid_boundaries(cls,val,values):"""Check that subgrid boundaries is increasing and contains one fewer element than subgrids."""subgrids=values["subgrids"]subgrid_boundaries=valiflen(subgrids)-1!=len(subgrid_boundaries):raiseValidationError("There must be exactly one fewer item in 'subgrid_boundaries' than ""in 'subgrids'.")rmin=subgrid_boundaries[0]forrmaxinsubgrid_boundaries[1:]:ifrmax<rmin:raiseValidationError("The 'subgrid_boundaries' must be increasing.")rmin=rmaxreturnval
[docs]defsubgrid_bounds(self,center:Coordinate,size:Size,axis:Axis)->List[Tuple[float,float]]:"""Subgrid bounds: a list of pairs (rmin, rmax) of the bounds of the subgrids along the propagation axis. Parameters ---------- center: :class:`.Coordinate` Center of the EME simulation. size: :class:`.Size` Size of the EME simulation. axis: :class:`.Axis` Propagation axis for the EME simulation. Returns ------- List[Tuple[float, float]] A list of pairs (rmin, rmax) of the bounds of the subgrids along the propagation axis. """bounds=[]sim_rmin=center[axis]-size[axis]/2sim_rmax=center[axis]+size[axis]/2ifsim_rmin-self.subgrid_boundaries[0]>fp_eps:raiseValidationError("The first item in 'subgrid_boundaries' is outside the simulation domain.")ifself.subgrid_boundaries[-1]-sim_rmax>fp_eps:raiseValidationError("The last item in 'subgrid_boundaries' is outside the simulation domain.")rmin=sim_rminforrmaxinself.subgrid_boundaries:bounds.append((rmin,rmax))rmin=rmaxrmax=sim_rmaxbounds.append((rmin,rmax))returnbounds
[docs]defmake_grid(self,center:Coordinate,size:Size,axis:Axis)->EMEGrid:"""Generate EME grid from the EME grid spec. Parameters ---------- center: :class:`.Coordinate` Center of the EME simulation. size: :class:`.Size` Size of the EME simulation. axis: :class:`.Axis` Propagation axis for the EME simulation. Returns ------- :class:`.EMEGrid` An EME grid dividing the EME simulation into cells, as defined by the EME grid spec. """boundaries=[]mode_specs=[]subgrid_center=list(center)subgrid_size=list(size)subgrid_bounds=self.subgrid_bounds(center,size,axis)forsubgrid_spec,boundsinzip(self.subgrids,subgrid_bounds):subgrid_center[axis]=(bounds[0]+bounds[1])/2subgrid_size[axis]=bounds[1]-bounds[0]subgrid=subgrid_spec.make_grid(center=subgrid_center,size=subgrid_size,axis=axis)boundaries+=list(subgrid.boundaries[:-1])mode_specs+=list(subgrid.mode_specs)boundaries.append(subgrid_bounds[-1][1])returnEMEGrid(boundaries=boundaries,center=center,size=size,axis=axis,mode_specs=mode_specs,)
[docs]classEMEGrid(Box):"""EME grid. An EME grid is a 1D grid aligned with the propagation axis, dividing the simulation into cells. Modes and mode coefficients are defined at the central plane of each cell. Typically, cell boundaries are aligned with interfaces between structures in the simulation. """axis:Axis=pd.Field(...,title="Propagation axis",description="Propagation axis for the EME simulation.")mode_specs:List[EMEModeSpec]=pd.Field(...,title="Mode Specifications",description="Mode specifications for the EME cells.")boundaries:Coords1D=pd.Field(...,title="Cell boundaries",description="Boundary coordinates of the EME cells.")@pd.validator("mode_specs",always=True)def_validate_size(cls,val):"""Check grid size and num modes."""num_eme_cells=len(val)ifnum_eme_cells>MAX_NUM_EME_CELLS:raiseSetupError(f"Simulation has {num_eme_cells:.2e} EME cells, "f"a maximum of {MAX_NUM_EME_CELLS:.2e} are allowed.")num_modes=np.max([mode_spec.num_modesformode_specinval])ifnum_modes>MAX_NUM_MODES:raiseSetupError(f"Simulation has {num_modes:.2e} EME modes, "f"a maximum of {MAX_NUM_MODES:.2e} are allowed.")returnval@pd.validator("boundaries",always=True,pre=False)@skip_if_fields_missing(["mode_specs","axis","center","size"])def_validate_boundaries(cls,val,values):"""Check that boundaries is increasing, in simulation domain, and contains one more element than 'mode_specs'."""mode_specs=values["mode_specs"]boundaries=valaxis=values["axis"]center=values["center"][axis]size=values["size"][axis]sim_rmin=center-size/2sim_rmax=center+size/2iflen(mode_specs)+1!=len(boundaries):raiseValidationError("There must be exactly one more item in 'boundaries' than in 'mode_specs', ""so that there is one mode spec per EME cell.")rmin=boundaries[0]ifsim_rmin-rmin>fp_eps:raiseValidationError("The first item in 'boundaries' is outside the simulation domain.")forrmaxinboundaries[1:]:ifrmax<rmin:raiseValidationError("The 'subgrid_boundaries' must be increasing.")rmin=rmaxifrmax-sim_rmax>fp_eps:raiseValidationError("The last item in 'boundaries' is outside the simulation domain.")returnval@propertydefcenters(self)->Coords1D:"""Centers of the EME cells along the propagation axis."""rmin=self.boundaries[0]centers=[]forrmaxinself.boundaries[1:]:center=(rmax+rmin)/2centers.append(center)rmin=rmaxreturncenters@propertydeflengths(self)->List[pd.NonNegativeFloat]:"""Lengths of the EME cells along the propagation axis."""rmin=self.boundaries[0]lengths=[]forrmaxinself.boundaries[1:]:length=rmax-rminlengths.append(length)rmin=rmaxreturnlengths@propertydefnum_cells(self)->pd.NonNegativeInteger:"""The number of cells in the EME grid."""returnlen(self.centers)@propertydefmode_planes(self)->List[Box]:"""Planes for mode solving, aligned with cell centers."""size=list(self.size)center=list(self.center)axis=self.axissize[axis]=0mode_planes=[]forcell_centerinself.centers:center[axis]=cell_centermode_planes.append(Box(center=center,size=size))returnmode_planes@propertydefboundary_planes(self)->List[Box]:"""Planes aligned with cell boundaries."""size=list(self.size)center=list(self.center)axis=self.axissize[axis]=0boundary_planes=[]forcell_boundaryinself.boundaries:center[axis]=cell_boundaryboundary_planes.append(Box(center=center,size=size))returnboundary_planes@propertydefcells(self)->List[Box]:"""EME cells in the grid. Each cell is a :class:`.Box`."""size=list(self.size)center=list(self.center)axis=self.axiscells=[]forcell_center,lengthinzip(self.centers,self.lengths):size[axis]=lengthcenter[axis]=cell_centercells.append(Box(center=center,size=size))returncells
[docs]defcell_indices_in_box(self,box:Box)->List[pd.NonNegativeInteger]:"""Indices of cells that overlap with 'box'. Used to determine which data is recorded by a monitor. Parameters ---------- box: :class:`.Box` The box to check for intersecting cells. Returns ------- List[pd.NonNegativeInteger] The indices of the cells that intersect the provided box. """indices=[]fori,cellinenumerate(self.cells):ifcell.intersects(box):indices.append(i)returnindices