Source code for tidy3d.plugins.smatrix.component_modelers.modal
"""Tool for generating an S matrix automatically from a Tidy3d simulation and modal port definitions."""# TODO: The names "ComponentModeler" and "Port" should be changed to "ModalComponentModeler" and# "ModalPort" to explicitly differentiate these from "TerminalComponentModeler" and "LumpedPort".from__future__importannotationsfromtypingimportDict,List,Optional,Tupleimportnumpyasnpimportpydantic.v1aspdfrom....components.baseimportcached_propertyfrom....components.data.sim_dataimportSimulationDatafrom....components.monitorimportModeMonitorfrom....components.simulationimportSimulationfrom....components.source.fieldimportModeSourcefrom....components.source.timeimportGaussianPulsefrom....components.typesimportAx,Complexfrom....components.vizimportadd_ax_if_none,equal_aspectfrom....exceptionsimportSetupErrorfrom....web.api.containerimportBatchDatafrom..ports.modalimportModalPortDataArray,Portfrom.baseimportFWIDTH_FRAC,AbstractComponentModelerMatrixIndex=Tuple[str,pd.NonNegativeInt]# the 'i' in S_ijElement=Tuple[MatrixIndex,MatrixIndex]# the 'ij' in S_ij
[docs]classComponentModeler(AbstractComponentModeler):""" Tool for modeling devices and computing scattering matrix elements. .. TODO missing basic example See Also -------- **Notebooks** * `Computing the scattering matrix of a device <../../notebooks/SMatrix.html>`_ """ports:Tuple[Port,...]=pd.Field((),title="Ports",description="Collection of ports describing the scattering matrix elements. ""For each input mode, one simulation will be run with a modal source.",)element_mappings:Tuple[Tuple[Element,Element,Complex],...]=pd.Field((),title="Element Mappings",description="Mapping between elements of the scattering matrix, ""as specified by pairs of ``(port name, mode index)`` matrix indices, where the ""first element of the pair is the output and the second element of the pair is the input.""Each item of ``element_mappings`` is a tuple of ``(element1, element2, c)``, where ""the scattering matrix ``Smatrix[element2]`` is set equal to ``c * Smatrix[element1]``.""If all elements of a given column of the scattering matrix are defined by "" ``element_mappings``, the simulation corresponding to this column ""is skipped automatically.",)run_only:Optional[Tuple[MatrixIndex,...]]=pd.Field(None,title="Run Only",description="If given, a tuple of matrix indices, specified by (:class:`.Port`, ``int``),"" to run only, excluding the other rows from the scattering matrix. ""If this option is used, ""the data corresponding to other inputs will be missing in the resulting matrix.",)"""Finally, to exclude some rows of the scattering matrix, one can supply a ``run_only`` parameter to the :class:`ComponentModeler`. ``run_only`` contains the scattering matrix indices that the user wants to run as a source. If any indices are excluded, they will not be run."""verbose:bool=pd.Field(False,title="Verbosity",description="Whether the :class:`.ComponentModeler` should print status and progressbars.",)callback_url:str=pd.Field(None,title="Callback URL",description="Http PUT url to receive simulation finish event. ""The body content is a json file with fields ""``{'id', 'status', 'name', 'workUnit', 'solverVersion'}``.",)@pd.validator("simulation",always=True)def_sim_has_no_sources(cls,val):"""Make sure simulation has no sources as they interfere with tool."""iflen(val.sources)>0:raiseSetupError("'ComponentModeler.simulation' must not have any sources.")returnval@cached_propertydefsim_dict(self)->Dict[str,Simulation]:"""Generate all the :class:`.Simulation` objects for the S matrix calculation."""sim_dict={}mode_monitors=[self.to_monitor(port=port)forportinself.ports]forport_name,mode_indexinself.matrix_indices_run_sim:port=self.get_port_by_name(port_name=port_name)port_source=self.shift_port(port=port)mode_source=self.to_source(port=port_source,mode_index=mode_index)new_mnts=list(self.simulation.monitors)+mode_monitorssim_copy=self.simulation.copy(update=dict(sources=[mode_source],monitors=new_mnts))task_name=self._task_name(port=port,mode_index=mode_index)sim_dict[task_name]=sim_copyreturnsim_dict@cached_propertydefmatrix_indices_monitor(self)->Tuple[MatrixIndex,...]:"""Tuple of all the possible matrix indices (port, mode_index) in the Component Modeler."""matrix_indices=[]forportinself.ports:formode_indexinrange(port.mode_spec.num_modes):matrix_indices.append((port.name,mode_index))returntuple(matrix_indices)@cached_propertydefmatrix_indices_source(self)->Tuple[MatrixIndex,...]:"""Tuple of all the source matrix indices (port, mode_index) in the Component Modeler."""ifself.run_onlyisnotNone:returnself.run_onlyreturnself.matrix_indices_monitor@cached_propertydefmatrix_indices_run_sim(self)->Tuple[MatrixIndex,...]:"""Tuple of all the source matrix indices (port, mode_index) in the Component Modeler."""ifself.element_mappingsisNoneorself.element_mappings=={}:returnself.matrix_indices_source# all the (i, j) pairs in `S_ij` that are tagged as covered by `element_mappings`elements_determined_by_map=[element_outfor(_,element_out,_)inself.element_mappings]# loop through rows of the full s matrix and record rows that still need running.source_indices_needed=[]forcol_indexinself.matrix_indices_source:# loop through columns and keep track of whether each element is covered by mapping.matrix_elements_covered=[]forrow_indexinself.matrix_indices_monitor:element=(row_index,col_index)element_covered_by_map=elementinelements_determined_by_mapmatrix_elements_covered.append(element_covered_by_map)# if any matrix elements in row still not covered by map, a source is needed for row.ifnotall(matrix_elements_covered):source_indices_needed.append(col_index)returnsource_indices_needed@cached_propertydefport_names(self)->Tuple[List[str],List[str]]:"""List of port names for inputs and outputs, respectively."""defget_port_names(matrix_elements:Tuple[str,int])->List[str]:"""Get the port names from a list of (port name, mode index)."""port_names=[]forport_name,_inmatrix_elements:ifport_namenotinport_names:port_names.append(port_name)returnport_namesport_names_in=get_port_names(self.matrix_indices_source)port_names_out=get_port_names(self.matrix_indices_monitor)returnport_names_out,port_names_in
[docs]defto_monitor(self,port:Port)->ModeMonitor:"""Creates a mode monitor from a given port."""returnModeMonitor(center=port.center,size=port.size,freqs=self.freqs,mode_spec=port.mode_spec,name=port.name,)
[docs]defto_source(self,port:Port,mode_index:int,num_freqs:int=1,**kwargs)->List[ModeSource]:"""Creates a list of mode sources from a given port."""freq0=np.mean(self.freqs)fdiff=max(self.freqs)-min(self.freqs)fwidth=max(fdiff,freq0*FWIDTH_FRAC)returnModeSource(center=port.center,size=port.size,source_time=GaussianPulse(freq0=freq0,fwidth=fwidth),mode_spec=port.mode_spec,mode_index=mode_index,direction=port.direction,name=port.name,num_freqs=num_freqs,**kwargs,)
[docs]defshift_port(self,port:Port)->Port:"""Generate a new port shifted by the shift amount in normal direction."""shift_value=self._shift_value_signed(port=port)center_shifted=list(port.center)center_shifted[port.size.index(0.0)]+=shift_valueport_shifted=port.copy(update=dict(center=center_shifted))returnport_shifted
[docs]@equal_aspect@add_ax_if_nonedefplot_sim(self,x:float=None,y:float=None,z:float=None,ax:Ax=None)->Ax:"""Plot a :class:`.Simulation` with all sources added for each port, for troubleshooting."""plot_sources=[]forport_sourceinself.ports:mode_source_0=self.to_source(port=port_source,mode_index=0)plot_sources.append(mode_source_0)sim_plot=self.simulation.copy(update=dict(sources=plot_sources))returnsim_plot.plot(x=x,y=y,z=z,ax=ax)
[docs]@equal_aspect@add_ax_if_nonedefplot_sim_eps(self,x:float=None,y:float=None,z:float=None,ax:Ax=None,**kwargs)->Ax:"""Plot permittivity of the :class:`.Simulation` with all sources added for each port."""plot_sources=[]forport_sourceinself.ports:mode_source_0=self.to_source(port=port_source,mode_index=0)plot_sources.append(mode_source_0)sim_plot=self.simulation.copy(update=dict(sources=plot_sources))returnsim_plot.plot_eps(x=x,y=y,z=z,ax=ax,**kwargs)
def_normalization_factor(self,port_source:Port,sim_data:SimulationData)->complex:"""Compute the normalization amplitude based on the measured input mode amplitude."""port_monitor_data=sim_data[port_source.name]mode_index=sim_data.simulation.sources[0].mode_indexnormalize_amps=port_monitor_data.amps.sel(f=np.array(self.freqs),direction=port_source.direction,mode_index=mode_index,)returnnormalize_amps.values@cached_propertydefmax_mode_index(self)->Tuple[int,int]:"""maximum mode indices for the smatrix dataset for the in and out ports, respectively."""defget_max_mode_indices(matrix_elements:Tuple[str,int])->int:"""Get the maximum mode index for a list of (port name, mode index)."""returnmax(mode_indexfor_,mode_indexinmatrix_elements)max_mode_index_out=get_max_mode_indices(self.matrix_indices_monitor)max_mode_index_in=get_max_mode_indices(self.matrix_indices_source)returnmax_mode_index_out,max_mode_index_indef_construct_smatrix(self)->ModalPortDataArray:"""Post process :class:`.BatchData` to generate scattering matrix."""returnself._internal_construct_smatrix(batch_data=self.batch_data)def_internal_construct_smatrix(self,batch_data:BatchData)->ModalPortDataArray:"""Post process :class:`.BatchData` to generate scattering matrix, for internal use only."""max_mode_index_out,max_mode_index_in=self.max_mode_indexnum_modes_out=max_mode_index_out+1num_modes_in=max_mode_index_in+1port_names_out,port_names_in=self.port_namesvalues=np.zeros((len(port_names_out),len(port_names_in),num_modes_out,num_modes_in,len(self.freqs)),dtype=complex,)coords=dict(port_out=port_names_out,port_in=port_names_in,mode_index_out=range(num_modes_out),mode_index_in=range(num_modes_in),f=np.array(self.freqs),)s_matrix=ModalPortDataArray(values,coords=coords)# loop through source portsforcol_indexinself.matrix_indices_run_sim:port_name_in,mode_index_in=col_indexport_in=self.get_port_by_name(port_name=port_name_in)sim_data=batch_data[self._task_name(port=port_in,mode_index=mode_index_in)]forrow_indexinself.matrix_indices_monitor:port_name_out,mode_index_out=row_indexport_out=self.get_port_by_name(port_name=port_name_out)# directly compute the elementmode_amps_data=sim_data[port_out.name].copy().ampsdir_out="-"ifport_out.direction=="+"else"+"amp=mode_amps_data.sel(f=coords["f"],direction=dir_out,mode_index=mode_index_out)source_norm=self._normalization_factor(port_in,sim_data)s_matrix_elements=np.array(amp.data)/np.array(source_norm)s_matrix.loc[dict(port_in=port_name_in,mode_index_in=mode_index_in,port_out=port_name_out,mode_index_out=mode_index_out,)]=s_matrix_elements# element can be determined by user-defined mappingfor(row_in,col_in),(row_out,col_out),mult_byinself.element_mappings:port_out_from,mode_index_out_from=row_inport_in_from,mode_index_in_from=col_incoords_from=dict(port_in=port_in_from,mode_index_in=mode_index_in_from,port_out=port_out_from,mode_index_out=mode_index_out_from,)port_out_to,mode_index_out_to=row_outport_in_to,mode_index_in_to=col_outcoords_to=dict(port_in=port_in_to,mode_index_in=mode_index_in_to,port_out=port_out_to,mode_index_out=mode_index_out_to,)s_matrix.loc[coords_to]=mult_by*s_matrix.loc[coords_from].valuesreturns_matrix