def compute_area(c: Component, target_layer: Tuple[int, int]) -> float64: """Returns Computed area of the component for a given layer.""" # _print("Computing area ", c.name) c.flatten() # return c.area(by_spec=True)[layer] polys_by_spec = c.get_polygons(by_spec=True) _area = 0 for (layer, polys) in polys_by_spec.items(): # _print(layer) if layer == target_layer: joined_polys = gp.boolean(polys, None, operation="or") _print(joined_polys) try: _area += sum([abs(area(p)) for p in joined_polys.polygons]) except BaseException: print( f"Warning, {c.name} joinedpoly {joined_polys} could not be added" ) return _area
def get_transmission_2ports( component: Component, extend_ports_length: Optional[float] = 4.0, layer_core: int = 1, layer_source: int = 110, layer_monitor1: int = 101, layer_monitor2: int = 102, layer_simulation_region: int = 2, res: int = 20, t_clad_bot: float = 1.0, t_core: float = 0.22, t_clad_top: float = 1.0, dpml: int = 1, clad_material: Medium = mp.Medium(epsilon=2.25), core_material: Medium = mp.Medium(epsilon=12), is_3d: bool = False, run: bool = True, wavelengths: ndarray = np.linspace(1.5, 1.6, 50), field_monitor_point: Tuple[int, int, int] = (0, 0, 0), dfcen: float = 0.2, ) -> Dict[str, Any]: """Returns dict with Sparameters for a 2port gf.component requires source and port monitors in the GDS based on meep directional coupler example https://meep.readthedocs.io/en/latest/Python_Tutorials/GDSII_Import/ https://support.lumerical.com/hc/en-us/articles/360042095873-Metamaterial-S-parameter-extraction Args: component: gf.Component extend_ports_function: function to extend the ports for a component to ensure it goes beyond the PML layer_core: GDS layer for the Component material layer_source: for the source monitor layer_monitor1: monitor layer for port 1 layer_monitor2: monitor layer for port 2 layer_simulation_region: for simulation region res: resolution (pixels/um) For example: (10: 100nm step size) t_clad_bot: thickness for cladding below core t_core: thickness of the core material t_clad_top: thickness for cladding above core dpml: PML thickness (um) clad_material: material for cladding core_material: material for core is_3d: if True runs in 3D run: if True runs simulation, False only build simulation wavelengths: iterable of wavelengths to simulate field_monitor_point: monitors the field and stops simulation after field decays by 1e-9 dfcen: delta frequency Returns: Dict: sim: simulation object Make sure you visualize the simulation region with gf.before you simulate a component .. code:: import gdsfactory as gf import gmeep as gm component = gf.components.bend_circular() margin = 2 cm = gm.add_monitors(component) cm.show() """ assert isinstance( component, Component ), f"component needs to be a Component, got Type {type(component)}" if extend_ports_length: component = gf.components.extension.extend_ports( component=component, length=extend_ports_length, centered=True ) component.flatten() gdspath = component.write_gds() gdspath = str(gdspath) freqs = 1 / wavelengths fcen = np.mean(freqs) frequency_width = dfcen * fcen cell_thickness = dpml + t_clad_bot + t_core + t_clad_top + dpml cell_zmax = 0.5 * cell_thickness if is_3d else 0 cell_zmin = -0.5 * cell_thickness if is_3d else 0 core_zmax = 0.5 * t_core if is_3d else 10 core_zmin = -0.5 * t_core if is_3d else -10 geometry = mp.get_GDSII_prisms( core_material, gdspath, layer_core, core_zmin, core_zmax ) cell = mp.GDSII_vol(gdspath, layer_core, cell_zmin, cell_zmax) sim_region = mp.GDSII_vol(gdspath, layer_simulation_region, cell_zmin, cell_zmax) cell.size = mp.Vector3( sim_region.size[0] + 2 * dpml, sim_region.size[1] + 2 * dpml, sim_region.size[2] ) cell_size = cell.size zsim = t_core + t_clad_top + t_clad_bot + 2 * dpml m_zmin = -zsim / 2 m_zmax = +zsim / 2 src_vol = mp.GDSII_vol(gdspath, layer_source, m_zmin, m_zmax) sources = [ mp.EigenModeSource( src=mp.GaussianSource(fcen, fwidth=frequency_width), size=src_vol.size, center=src_vol.center, eig_band=1, eig_parity=mp.NO_PARITY if is_3d else mp.EVEN_Y + mp.ODD_Z, eig_match_freq=True, ) ] sim = mp.Simulation( resolution=res, cell_size=cell_size, boundary_layers=[mp.PML(dpml)], sources=sources, geometry=geometry, default_material=clad_material, ) sim_settings = dict( resolution=res, cell_size=cell_size, fcen=fcen, field_monitor_point=field_monitor_point, layer_core=layer_core, t_clad_bot=t_clad_bot, t_core=t_core, t_clad_top=t_clad_top, is_3d=is_3d, dmp=dpml, ) m1_vol = mp.GDSII_vol(gdspath, layer_monitor1, m_zmin, m_zmax) m2_vol = mp.GDSII_vol(gdspath, layer_monitor2, m_zmin, m_zmax) m1 = sim.add_mode_monitor( freqs, mp.ModeRegion(center=m1_vol.center, size=m1_vol.size), ) m1.z = 0 m2 = sim.add_mode_monitor( freqs, mp.ModeRegion(center=m2_vol.center, size=m2_vol.size), ) m2.z = 0 # if 0: # ''' Useful for debugging. ''' # sim.run(until=50) # sim.plot2D(fields=mp.Ez) # plt.show() # quit() r = dict(sim=sim, cell_size=cell_size, sim_settings=sim_settings) if run: sim.run( until_after_sources=mp.stop_when_fields_decayed( dt=50, c=mp.Ez, pt=field_monitor_point, decay_by=1e-9 ) ) # call this function every 50 time spes # look at simulation and measure component that we want to measure (Ez component) # when field_monitor_point decays below a certain 1e-9 field threshold # Calculate the mode overlaps m1_results = sim.get_eigenmode_coefficients(m1, [1]).alpha m2_results = sim.get_eigenmode_coefficients(m2, [1]).alpha # Parse out the overlaps a1 = m1_results[:, :, 0] # forward wave b1 = m1_results[:, :, 1] # backward wave a2 = m2_results[:, :, 0] # forward wave # b2 = m2_results[:, :, 1] # backward wave # Calculate the actual scattering parameters from the overlaps s11 = np.squeeze(b1 / a1) s12 = np.squeeze(a2 / a1) s22 = s11.copy() s21 = s12.copy() # s22 and s21 requires another simulation, with the source on the other port # Luckily, if the device is symmetric, we can assume that s22=s11 and s21=s12. # visualize results plt.figure() plt.plot( wavelengths, 10 * np.log10(np.abs(s11) ** 2), "-o", label="Reflection", ) plt.plot( wavelengths, 10 * np.log10(np.abs(s12) ** 2), "-o", label="Transmission", ) plt.ylabel("Power (dB)") plt.xlabel(r"Wavelength ($\mu$m)") plt.legend() plt.grid(True) r.update(dict(s11=s11, s12=s12, s21=s21, s22=s22, wavelengths=wavelengths)) keys = [key for key in r.keys() if key.startswith("S")] s = {f"{key}a": list(np.unwrap(np.angle(r[key].flatten()))) for key in keys} s_mod = {f"{key}m": list(np.abs(r[key].flatten())) for key in keys} s.update(**s_mod) s = pd.DataFrame(s) return r
def import_gds( gdspath: Union[str, Path], cellname: Optional[str] = None, flatten: bool = False, snap_to_grid_nm: Optional[int] = None, name: Optional[str] = None, decorator: Optional[Callable] = None, gdsdir: Optional[Union[str, Path]] = None, **kwargs, ) -> Component: """Returns a Componenent from a GDS file. Adapted from phidl/geometry.py if any cell names are found on the component CACHE we append a $ with a number to the name Args: gdspath: path of GDS file. cellname: cell of the name to import (None) imports top cell. flatten: if True returns flattened (no hierarchy) snap_to_grid_nm: snap to different nm grid (does not snap if False) name: Optional name. Over-rides the default imported name. decorator: function to apply over the imported gds. gdsdir: optional GDS directory. kwargs: settings for the imported component (polarization, wavelength ...). """ gdspath = Path(gdsdir) / Path(gdspath) if gdsdir else Path(gdspath) if not gdspath.exists(): raise FileNotFoundError(f"No file {gdspath!r} found") metadata_filepath = gdspath.with_suffix(".yml") gdsii_lib = gdspy.GdsLibrary() gdsii_lib.read_gds(str(gdspath)) top_level_cells = gdsii_lib.top_level() cellnames = [c.name for c in top_level_cells] if cellname is not None: if cellname not in gdsii_lib.cells: raise ValueError( f"cell {cellname} is not in file {gdspath} with cells {cellnames}" ) topcell = gdsii_lib.cells[cellname] elif cellname is None and len(top_level_cells) == 1: topcell = top_level_cells[0] elif cellname is None and len(top_level_cells) > 1: raise ValueError( f"import_gds() There are multiple top-level cells in {gdspath!r}, " f"you must specify `cellname` to select of one of them among {cellnames}" ) if name: if name in CACHE or name in CACHE_IMPORTED_CELLS: raise ValueError( f"name = {name!r} already on cache. " "Please, choose a different name or set name = None. ") else: topcell.name = name if flatten: component = Component(name=name or cellname or cellnames[0]) polygons = topcell.get_polygons(by_spec=True) for layer_in_gds, polys in polygons.items(): component.add_polygon(polys, layer=layer_in_gds) component = avoid_duplicated_cells(component) else: D_list = [] cell_to_device = {} for c in gdsii_lib.cells.values(): D = Component(name=c.name) D.polygons = c.polygons D.references = c.references D.name = c.name for label in c.labels: rotation = label.rotation if rotation is None: rotation = 0 label_ref = D.add_label( text=label.text, position=np.asfarray(label.position), magnification=label.magnification, rotation=rotation * 180 / np.pi, layer=(label.layer, label.texttype), ) label_ref.anchor = label.anchor D = avoid_duplicated_cells(D) D.unlock() cell_to_device.update({c: D}) D_list += [D] for D in D_list: # First convert each reference so it points to the right Device converted_references = [] for e in D.references: ref_device = cell_to_device[e.ref_cell] if isinstance(e, gdspy.CellReference): dr = DeviceReference( device=ref_device, origin=e.origin, rotation=e.rotation, magnification=e.magnification, x_reflection=e.x_reflection, ) dr.owner = D converted_references.append(dr) elif isinstance(e, gdspy.CellArray): dr = CellArray( device=ref_device, columns=e.columns, rows=e.rows, spacing=e.spacing, origin=e.origin, rotation=e.rotation, magnification=e.magnification, x_reflection=e.x_reflection, ) dr.owner = D converted_references.append(dr) D.references = converted_references # Next convert each Polygon # temp_polygons = list(D.polygons) # D.polygons = [] # for p in temp_polygons: # D.add_polygon(p) # Next convert each Polygon temp_polygons = list(D.polygons) D.polygons = [] for p in temp_polygons: if snap_to_grid_nm: points_on_grid = snap_to_grid(p.polygons[0], nm=snap_to_grid_nm) p = gdspy.Polygon(points_on_grid, layer=p.layers[0], datatype=p.datatypes[0]) D.add_polygon(p) component = cell_to_device[topcell] cast(Component, component) name = name or component.name component.name = name if metadata_filepath.exists(): logger.info(f"Read YAML metadata from {metadata_filepath}") metadata = OmegaConf.load(metadata_filepath) for port_name, port in metadata.ports.items(): if port_name not in component.ports: component.add_port( name=port_name, midpoint=port.midpoint, width=port.width, orientation=port.orientation, layer=port.layer, port_type=port.port_type, ) component.info = metadata.info component.info.update(**kwargs) component.name = name component.info.name = name if decorator: component_new = decorator(component) component = component_new or component if flatten: component.flatten() component.lock() return component