def placer_grid_cell_refs(component_factory, cols=1, rows=1, dx=10.0, dy=10.0, x0=0, y0=0, **settings): if callable(component_factory): settings_list = get_settings_list(**settings) component_list = [component_factory(**s) for s in settings_list] else: component_list = component_factory indices = [(i, j) for j in range(cols) for i in range(rows)] if rows * cols < len(component_list): raise ValueError( "Shape ({}, {}): Not enough emplacements ({}) for all these components ({})." .format(rows, cols, len(indices), len(component_list))) components = [] for component, (i, j) in zip(component_list, indices): c_ref = component.ref(position=(x0 + j * dx, y0 + i * dy)) components += [c_ref] return components
def load_placer_with_does(filepath, defaults={"do_permutation": True}): """ load placer settings Args: filepath: a yaml file containing the does and placer information Returns: a dictionnary of DOEs with: { doe_name1: [(component_factory_name, parameters), ...] doe_name2: [(component_factory_name, parameters), ...] ... } """ does = {} data = OmegaConf.load(filepath) placer_info = data.pop("placer") component_placement = data.pop("placement") gds_files = {} if "gds" in data.keys(): gds_files.update(data.pop("gds")) for doe_chunk in data.values(): doe_name = doe_chunk.pop("doe_name") component_type = doe_chunk.pop("component") if doe_name not in does: does[doe_name] = {"settings": [], "component_type": component_type} do_permutation = defaults["do_permutation"] if "do_permutation" in doe_chunk: do_permutation = doe_chunk.pop("do_permutation") doe = does[doe_name] # All the remaining parameters are component parameters if "settings" in doe_chunk: settings = doe_chunk.pop("settings") else: settings = {} doe["list_settings"] += get_settings_list(do_permutation, **settings) # check that the doe is valid (only one type of component) assert ( component_type == doe["component_type"] ), "There can be only one component type per doe. Got {} while expecting {}".format( component_type, doe["component_type"] ) return does, placer_info, component_placement, gds_files
def get_markdown_table(do_permutations=True, **kwargs): """ returns the markdown table for a parameter sweep """ list_settings = get_settings_list(do_permutations=do_permutations, **kwargs) # Convert table fields to strings head = [[str(field) for field in fields.keys()] for fields in list_settings][0] list_data_str = [[str(field) for field in fields.values()] for fields in list_settings] N = len(head) field_sizes = [0] * N # compute the max number of character in each field for header, fields in zip(head, list_data_str): field_sizes = [ max(field_sizes[i], len(fields[i]), len(head[i])) for i in range(N) ] field_sizes = [n + 2 for n in field_sizes] # Line formatting from fields def fmt_line(fields): fmt_fields = [ " " + fields[i] + " " * (field_sizes[i] - len(fields[i]) - 1) for i in range(N) ] return "|".join(fmt_fields) def table_head_sep(): return "|".join(["-" * n for n in field_sizes]) t = [] t.append(fmt_line(head)) t.append(table_head_sep()) for fields in list_data_str[0:]: t.append(fmt_line(fields)) return t
def write_doe( component_type, doe_name=None, do_permutations=True, list_settings=None, doe_settings=None, path=CONFIG["build_directory"], doe_metadata_path=CONFIG["doe_directory"], functions=None, name2function=name2function, component_factory=component_factory, **kwargs, ): """ writes each device GDS, together with metadata for each device: Returns a list of gdspaths - .gds geometry for each component - .json metadata for each component - .ports if any for each component - report.md for DOE - doe_name.json with DOE metadata pp.write_component_doe("mmi1x2", width_mmi=[5, 10], length_mmi=9) Args: component_type: component_name_or_function doe_name: autoname by default do_permutations: builds all permutations between the varying parameters list_settings: you can pass a list of settings or the variations in the kwargs doe_settings: shared settings for a DOE path: to store build artifacts functions: list of function names to apply to DOE name2function: function names to functions dict **kwargs: Doe default settings or variations """ if hasattr(component_type, "__call__"): component_type = component_type.__name__ doe_name = doe_name or component_type functions = functions or [] list_settings = list_settings or get_settings_list( do_permutations=do_permutations, **kwargs) assert isinstance(component_type, str), "{} not recognized".format(component_type) path.mkdir(parents=True, exist_ok=True) doe_gds_paths = [] cell_names = [] cell_settings = [] for settings in list_settings: # print(settings) component_name = get_component_name(component_type, **settings) component_function = component_factory[component_type] component = component_function(name=component_name, **settings) if "test" in kwargs: component.test_protocol = kwargs.get("test") if "analysis" in kwargs: component.data_analysis_protocol = kwargs.get("analysis") for f in functions: component = name2function[f](component) cell_names.append(component.name) cell_settings.append(settings) gdspath = path / (component.name + ".gds") doe_gds_paths += [gdspath] write_component(component, gdspath) """ write DOE metadata (report + JSON) """ write_doe_metadata( doe_name=doe_name, cell_names=cell_names, list_settings=list_settings, doe_settings=doe_settings, cell_settings=kwargs, doe_metadata_path=doe_metadata_path, ) return doe_gds_paths
def write_doe( component_type, doe_name=None, do_permutations=True, add_io_function=None, functions=None, list_settings=None, description=None, analysis=None, test=None, flag_write_component=True, **kwargs, ): """ writes each device GDS, together with metadata for each device: Returns a list of gdspaths - .gds geometry for each component - .json metadata for each component - .ports if any for each component - report.md for DOE - doe_name.json with DOE metadata pp.write_component_doe("mmi1x2", width_mmi=[5, 10], length_mmi=9) Args: component_type: component_name_or_function doe_name: autoname by default do_permutations: builds all permutations between the varying parameters add_io_function: add_io_optical list_settings: you can pass a list of settings or the variations in the kwargs descrption: description analysis: data analysis protocol test: test protocol **kwargs: Doe default settings or variations """ if hasattr(component_type, "__call__"): component_type = component_type.__name__ if doe_name is None: doe_name = component_type if functions is None: functions = [] assert isinstance(component_type, str), "{} not recognized".format(component_type) path = CONFIG["build_directory"] / "devices" if list_settings is None: list_settings = get_settings_list(do_permutations=do_permutations, **kwargs) doe_gds_paths = [] cell_names = [] cell_settings = [] for settings in list_settings: # print(settings) component_name = get_component_name(component_type, **settings) component_factory = component_type2factory[component_type] component = component_factory(name=component_name, **settings) component.function_name = component_factory.__name__ if test: component.test_protocol = test if analysis: component.data_analysis_protocol = analysis if add_io_function: component = add_io_function(component) for f in functions: component = name2function[f](component) cell_names.append(component.name) cell_settings.append(settings) gdspath = path / (component.name + ".gds") doe_gds_paths += [gdspath] if flag_write_component: write_component(component, gdspath) """ JSON """ doe_path = CONFIG["build_directory"] / "devices" / doe_name json_path = f"{doe_path}.json" d = {} d["type"] = "doe" d["name"] = doe_name d["cells"] = cell_names d["settings"] = cell_settings d["description"] = description d["analysis"] = analysis d["test"] = test with open(json_path, "w+") as fw: fw.write(json.dumps(d, indent=2)) """ Markdown report """ report_path = f"{doe_path}.md" with open(report_path, "w+") as fw: def w(line=""): fw.write(line + "\n") w("# {}".format(doe_name)) w("- Number of devices: {}".format(len(list_settings))) w("- Settings") if len(kwargs) > 0: w(json.dumps(kwargs)) # w(json.dumps(list_settings)) if not list_settings: return w() EOL = "" # Convert table fields to strings head = [[str(field) for field in fields.keys()] for fields in list_settings][0] list_data_str = [[str(field) for field in fields.values()] for fields in list_settings] N = len(head) field_sizes = [0] * N # compute the max number of character in each field for header, fields in zip(head, list_data_str): field_sizes = [ max(field_sizes[i], len(fields[i]), len(head[i])) for i in range(N) ] field_sizes = [n + 2 for n in field_sizes] # Line formatting from fields def fmt_line(fields): fmt_fields = [ " " + fields[i] + " " * (field_sizes[i] - len(fields[i]) - 1) for i in range(N) ] return "|".join(fmt_fields) + EOL def table_head_sep(): return "|".join(["-" * n for n in field_sizes]) + EOL w(fmt_line(head)) w(table_head_sep()) for fields in list_data_str[0:]: w(fmt_line(fields)) w() w("- Cells:") for cell_name in cell_names: w(cell_name) w() return doe_gds_paths
def generate_does( filepath, component_filter=default_component_filter, component_factory=component_factory, doe_root_path=CONFIG["cache_doe_directory"], doe_metadata_path=CONFIG["doe_directory"], n_cores=4, logger=logging, regenerate_report_if_doe_exists=False, precision=1e-9, ): """ Generates a DOEs of components specified in a yaml file allows for each DOE to have its own x and y spacing (more flexible than method1) similar to write_doe """ doe_root_path.mkdir(parents=True, exist_ok=True) doe_metadata_path.mkdir(parents=True, exist_ok=True) dicts, mask_settings = load_does(filepath) does, templates_by_type = separate_does_from_templates(dicts) dict_templates = (templates_by_type["template"] if "template" in templates_by_type else {}) default_use_cached_does = (mask_settings["cache"] if "cache" in mask_settings else False) list_args = [] for doe_name, doe in does.items(): doe["name"] = doe_name component = doe["component"] if component not in component_factory: raise ValueError(f"{component} not in {component_factory.keys()}") if "template" in doe: """ The keyword template is used to enrich the dictionnary from the template """ templates = doe["template"] if not isinstance(templates, list): templates = [templates] for template in templates: try: doe = update_dicts_recurse(doe, dict_templates[template]) except Exception: print(template, "does not exist") raise do_permutation = doe.pop("do_permutation") settings = doe["settings"] doe["list_settings"] = get_settings_list(do_permutation, **settings) list_args += [doe] does_running = [] start_times = {} finish_times = {} doe_name_to_process = {} while list_args: while len(does_running) < n_cores: if not list_args: break doe = list_args.pop() doe_name = doe["name"] """ Only launch a build process if we do not use the cache Or if the DOE is not built """ list_settings = doe["list_settings"] use_cached_does = (default_use_cached_does if "cache" not in doe else doe["cache"]) _doe_exists = False if "doe_template" in doe: """ In that case, the DOE is not built: this DOE points to another existing component """ _doe_exists = True logger.info("Using template - {}".format(doe_name)) save_doe_use_template(doe) elif use_cached_does: _doe_exists = doe_exists(doe_name, list_settings) if _doe_exists: logger.info("Cached - {}".format(doe_name)) if regenerate_report_if_doe_exists: component_names = load_doe_component_names(doe_name) write_doe_metadata( doe_name=doe["name"], cell_names=component_names, list_settings=doe["list_settings"], doe_metadata_path=doe_metadata_path, ) if not _doe_exists: start_times[doe_name] = time.time() p = Process( target=write_doe, args=(doe, component_factory), kwargs={ "component_filter": component_filter, "doe_root_path": doe_root_path, "doe_metadata_path": doe_metadata_path, "regenerate_report_if_doe_exists": regenerate_report_if_doe_exists, "precision": precision, "logger": logger, }, ) doe_name_to_process[doe_name] = p does_running += [doe_name] try: p.start() except Exception: print("Issue starting process for {}".format(doe_name)) print(type(component_factory)) raise to_rm = [] for i, doe_name in enumerate(does_running): _p = doe_name_to_process[doe_name] if not _p.is_alive(): to_rm += [i] finish_times[doe_name] = time.time() dt = finish_times[doe_name] - start_times[doe_name] line = "Done - {} ({:.1f}s)".format(doe_name, dt) logger.info(line) for i in to_rm[::-1]: does_running.pop(i) time.sleep(0.001) while does_running: to_rm = [] for i, _doe_name in enumerate(does_running): _p = doe_name_to_process[_doe_name] if not _p.is_alive(): to_rm += [i] for i in to_rm[::-1]: does_running.pop(i) time.sleep(0.05)