def test_sparameters_straight_mpi_pool(dataframe_regression): """Checks Sparameters for a straight waveguide using an MPI pool""" components = [] for length in [2]: c = gf.components.straight(length=length) p = 3 c = gf.add_padding_container(c, default=0, top=p, bottom=p) components.append(c) filepaths = gm.write_sparameters_meep_mpi_pool([{ "component": c, "overwrite": True } for c in components], ) filepath = filepaths[0] df = pd.read_csv(filepath) filepath2 = sim.get_sparameters_path_meep(component=c, layer_stack=LAYER_STACK) assert (filepath2 == filepaths[0] ), f"filepath returned {filepaths[0]} differs from {filepath2}" # Check reasonable reflection/transmission assert np.allclose(df["s12m"], 1, atol=1e-02) assert np.allclose(df["s21m"], 1, atol=1e-02) assert np.allclose(df["s11m"], 0, atol=5e-02) assert np.allclose(df["s22m"], 0, atol=5e-02) if dataframe_regression: dataframe_regression.check(df)
def test_sparameters_straight_symmetric(dataframe_regression): """Checks Sparameters for a straight waveguide""" c = gf.components.straight(length=2) p = 3 c = gf.add_padding_container(c, default=0, top=p, bottom=p) # port_symmetries for straight port_symmetries = { "o1": { "s11": ["s22"], "s21": ["s12"], } } df = gm.write_sparameters_meep(c, overwrite=True, resolution=RESOLUTION, port_symmetries=port_symmetries) # Check reasonable reflection/transmission assert np.allclose(df["s12m"], 1, atol=1e-02), df["s12m"] assert np.allclose(df["s21m"], 1, atol=1e-02), df["s21m"] assert np.allclose(df["s11m"], 0, atol=5e-02), df["s11m"] assert np.allclose(df["s22m"], 0, atol=5e-02), df["s22m"] if dataframe_regression: dataframe_regression.check(df)
def test_sparameters_straight_mpi(dataframe_regression): """Checks Sparameters for a straight waveguide using MPI""" c = gf.components.straight(length=2) p = 3 c = gf.add_padding_container(c, default=0, top=p, bottom=p) filepath = gm.write_sparameters_meep_mpi(c, overwrite=True) df = pd.read_csv(filepath) # Check reasonable reflection/transmission assert np.allclose(df["s12m"], 1, atol=1e-02) assert np.allclose(df["s21m"], 1, atol=1e-02) assert np.allclose(df["s11m"], 0, atol=5e-02) assert np.allclose(df["s22m"], 0, atol=5e-02) if dataframe_regression: dataframe_regression.check(df)
def write_sparameters_meep( component: Component, port_symmetries: Optional[PortSymmetries] = None, resolution: int = 20, wl_min: float = 1.5, wl_max: float = 1.6, wl_steps: int = 50, dirpath: Path = sparameters_path, layer_stack: LayerStack = LAYER_STACK, port_margin: float = 2, port_monitor_offset: float = -0.1, port_source_offset: float = -0.1, filepath: Optional[Path] = None, overwrite: bool = False, animate: bool = False, lazy_parallelism: bool = False, run: bool = True, dispersive: bool = False, xmargin: float = 0, ymargin: float = 0, xmargin_left: float = 0, xmargin_right: float = 0, ymargin_top: float = 0, ymargin_bot: float = 0, **settings, ) -> pd.DataFrame: r"""Compute Sparameters and writes them to a CSV filepath. Simulates each time using a different input port (by default, all of them) unless you specify port_symmetries: port_symmetries = {"o1": { "s11": ["s22","s33","s44"], "s21": ["s21","s34","s43"], "s31": ["s13","s24","s42"], "s41": ["s14","s23","s32"], } } - Only simulations using the outer key port names will be run - The associated value is another dict whose keys are the S-parameters computed when this source is active - The values of this inner Dict are lists of s-parameters whose values are copied This allows you doing less simulations TODO: automate this for common component types (geometrical symmetries, reciprocal materials, etc.) TODO: enable other port naming conventions, such as (in0, in1, out0, out1) .. code:: top view ________________________________ | | | xmargin_left | port_extension |<------> port_margin ||<--> ___|___________ _________||___ | \ / | | \ / | | ====== | | / \ | ___|___________/ \__________|___ | | <-------->| | |ymargin_bot xmargin_right| | | | |___|___________________________| side view ________________________________ | | | | | | | zmargin_top | |ymargin | | |<---> _____ _|___ | | | | | | | | | | | | | | |_____| |_____| | | | | | | | | |zmargin_bot | | | | |_______|_______________________| Args: component: to simulate. resolution: in pixels/um (20: for coarse, 120: for fine) port_symmetries: Dict to specify port symmetries, to save number of simulations source_ports: list of port string names to use as sources dirpath: directory to store Sparameters layer_stack: LayerStack class port_margin: margin on each side of the port port_monitor_offset: offset between monitor Component port and monitor MEEP port port_source_offset: offset between source Component port and source MEEP port filepath: to store pandas Dataframe with Sparameters in CSV format. Defaults to dirpath/component_.csv overwrite: overwrites stored simulation results. animate: saves a MP4 images of the simulation for inspection, and also outputs during computation. The name of the file is the source index lazy_parallelism: toggles the flag "meep.divide_parallel_processes" to perform the simulations with different sources in parallel run: runs simulation, if False, only plots simulation dispersive: use dispersive models for materials (requires higher resolution) xmargin: left and right distance from component to PML. xmargin_left: west distance from component to PML. xmargin_right: east distance from component to PML. ymargin: top and bottom distance from component to PML. ymargin_top: north distance from component to PML. ymargin_bot: south distance from component to PML. keyword Args: extend_ports_length: to extend ports beyond the PML (um). zmargin_top: thickness for cladding above core (um). zmargin_bot: thickness for cladding below core (um) tpml: PML thickness (um). clad_material: material for cladding. is_3d: if True runs in 3D wl_min: wavelength min (um). wl_max: wavelength max (um). wl_steps: wavelength steps dfcen: delta frequency port_source_name: input port name port_field_monitor_name: port_margin: margin on each side of the port (um). distance_source_to_monitors: in (um). port_source_offset: offset between source Component port and source MEEP port port_monitor_offset: offset between monitor Component port and monitor MEEP port Returns: sparameters in a pandas Dataframe (wavelengths, s11a, s12m, ...) where `a` is the angle in radians and `m` the module """ port_symmetries = port_symmetries or {} xmargin_left = xmargin_left or xmargin xmargin_right = xmargin_right or xmargin ymargin_top = ymargin_top or ymargin ymargin_bot = ymargin_bot or ymargin sim_settings = dict( resolution=resolution, port_symmetries=port_symmetries, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, port_margin=port_margin, port_monitor_offset=port_monitor_offset, port_source_offset=port_source_offset, dispersive=dispersive, ymargin_top=ymargin_top, ymargin_bot=ymargin_bot, xmargin_left=xmargin_left, xmargin_right=xmargin_right, **settings, ) filepath = filepath or get_sparameters_path( component=component, dirpath=dirpath, layer_stack=layer_stack, **sim_settings, ) sim_settings = sim_settings.copy() sim_settings["layer_stack"] = layer_stack.to_dict() sim_settings["component"] = component.to_dict() filepath = pathlib.Path(filepath) filepath_sim_settings = filepath.with_suffix(".yml") # filepath_sim_settings.write_text(OmegaConf.to_yaml(sim_settings)) # logger.info(f"Write simulation settings to {filepath_sim_settings!r}") # return filepath_sim_settings component = gf.add_padding_container( component, default=0, top=ymargin_top, bottom=ymargin_bot, left=xmargin_left, right=xmargin_right, ) if not run: sim_dict = get_simulation( component=component, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, layer_stack=layer_stack, port_margin=port_margin, port_monitor_offset=port_monitor_offset, port_source_offset=port_source_offset, **settings, ) sim_dict["sim"].plot2D(plot_eps_flag=True) plt.show() return if filepath.exists() and not overwrite: logger.info(f"Simulation loaded from {filepath!r}") return pd.read_csv(filepath) # Parse ports (default) monitor_indices = [] source_indices = [] component_ref = component.ref() for port_name in component_ref.ports.keys(): if component_ref.ports[port_name].port_type == "optical": monitor_indices.append(re.findall("[0-9]+", port_name)[0]) if bool(port_symmetries): # user-specified for port_name in port_symmetries.keys(): source_indices.append(re.findall("[0-9]+", port_name)[0]) else: # otherwise cycle through all source_indices = monitor_indices # Create S-parameter storage object sp = {} @pydantic.validate_arguments def sparameter_calculation( n, component: Component, port_symmetries: Optional[PortSymmetries] = port_symmetries, monitor_indices: Tuple = monitor_indices, wl_min: float = wl_min, wl_max: float = wl_max, wl_steps: int = wl_steps, dirpath: Path = dirpath, animate: bool = animate, dispersive: bool = dispersive, **settings, ) -> Dict: sim_dict = get_simulation( component=component, port_source_name=f"o{monitor_indices[n]}", resolution=resolution, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, port_margin=port_margin, port_monitor_offset=port_monitor_offset, port_source_offset=port_source_offset, dispersive=dispersive, **settings, ) sim = sim_dict["sim"] monitors = sim_dict["monitors"] # freqs = sim_dict["freqs"] # wavelengths = 1 / freqs # print(sim.resolution) # Make termination when field decayed enough across ALL monitors termination = [] for monitor_name in monitors: termination.append( mp.stop_when_fields_decayed( dt=50, c=mp.Ez, pt=monitors[monitor_name].regions[0].center, decay_by=1e-9, )) if animate: sim.use_output_directory() animate = mp.Animate2D( sim, fields=mp.Ez, realtime=True, field_parameters={ "alpha": 0.8, "cmap": "RdBu", "interpolation": "none", }, eps_parameters={"contour": True}, normalize=True, ) sim.run(mp.at_every(1, animate), until_after_sources=termination) animate.to_mp4(30, monitor_indices[n] + ".mp4") else: sim.run(until_after_sources=termination) # call this function every 50 time spes # look at simulation and measure Ez component # when field_monitor_point decays below a certain 1e-9 field threshold # Calculate mode overlaps # Get source monitor results component_ref = component.ref() source_entering, source_exiting = parse_port_eigenmode_coeff( monitor_indices[n], component_ref.ports, sim_dict) # Get coefficients for monitor_index in monitor_indices: j = monitor_indices[n] i = monitor_index if monitor_index == monitor_indices[n]: sii = source_exiting / source_entering siia = np.unwrap(np.angle(sii)) siim = np.abs(sii) sp[f"s{i}{i}a"] = siia sp[f"s{i}{i}m"] = siim else: monitor_entering, monitor_exiting = parse_port_eigenmode_coeff( monitor_index, component_ref.ports, sim_dict) sij = monitor_exiting / source_entering sija = np.unwrap(np.angle(sij)) sijm = np.abs(sij) sp[f"s{i}{j}a"] = sija sp[f"s{i}{j}m"] = sijm sij = monitor_entering / source_entering sija = np.unwrap(np.angle(sij)) sijm = np.abs(sij) if bool(port_symmetries) is True: for key in port_symmetries[f"o{monitor_indices[n]}"].keys(): values = port_symmetries[f"o{monitor_indices[n]}"][key] for value in values: sp[f"{value}m"] = sp[f"{key}m"] sp[f"{value}a"] = sp[f"{key}a"] return sp # Since source is defined upon sim object instanciation, loop here # for port_index in monitor_indices: num_sims = len(port_symmetries.keys()) or len(source_indices) if lazy_parallelism: from mpi4py import MPI cores = min([num_sims, multiprocessing.cpu_count()]) n = mp.divide_parallel_processes(cores) comm = MPI.COMM_WORLD size = comm.Get_size() rank = comm.Get_rank() sp = sparameter_calculation( n, component=component, port_symmetries=port_symmetries, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, animate=animate, monitor_indices=monitor_indices, **settings, ) # Synchronize dicts if rank == 0: for i in range(1, size, 1): data = comm.recv(source=i, tag=11) sp.update(data) df = pd.DataFrame(sp) df["wavelengths"] = np.linspace(wl_min, wl_max, wl_steps) df["freqs"] = 1 / df["wavelengths"] df.to_csv(filepath, index=False) logger.info(f"Write simulation results to {filepath!r}") filepath_sim_settings.write_text(OmegaConf.to_yaml(sim_settings)) logger.info( f"Write simulation settings to {filepath_sim_settings!r}") return df else: comm.send(sp, dest=0, tag=11) else: for n in tqdm(range(num_sims)): sp.update( sparameter_calculation( n, component=component, port_symmetries=port_symmetries, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, animate=animate, monitor_indices=monitor_indices, **settings, )) df = pd.DataFrame(sp) df["wavelengths"] = np.linspace(wl_min, wl_max, wl_steps) df["freqs"] = 1 / df["wavelengths"] df.to_csv(filepath, index=False) logger.info(f"Write simulation results to {filepath!r}") filepath_sim_settings.write_text(OmegaConf.to_yaml(sim_settings)) logger.info(f"Write simulation settings to {filepath_sim_settings!r}") return df
write_sparameters_meep_mpi_pool_lr = gf.partial( write_sparameters_meep_mpi_pool, ymargin_top=3, ymargin_bot=3 ) write_sparameters_meep_mpi_pool_lt = gf.partial( write_sparameters_meep_mpi_pool, ymargin_bot=3, xmargin_right=3 ) if __name__ == "__main__": # Multicore pools example c1 = gf.c.straight(length=5) p = 3 c1 = gf.add_padding_container(c1, default=0, top=p, bottom=p) c2 = gf.c.straight(length=4) p = 3 c2 = gf.add_padding_container(c2, default=0, top=p, bottom=p) c1_dict = { "component": c1, "run": True, "overwrite": True, "lazy_parallelism": True, "filepath": Path("c1_dict.csv"), } c2_dict = { "component": c2, "run": True,
""" info: - default - changed - full - info (derived properties) - child: if any Calculated/derived properties are stored in info """ import gdsfactory as gf def test_args(): c1 = gf.c.pad((150, 150)) assert c1.info.full.size[0] == 150 if __name__ == "__main__": test_args() # assert c1.settings.size.full[0] == 150 c1 = gf.c.pad((150, 150)) c2 = gf.add_padding_container(c1) c2.show() c3 = gf.add_padding_container(c2)