From 8082743e1736bec7f77c7e555d7751e593f8cd41 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Tue, 19 May 2020 00:13:50 -0700 Subject: [PATCH] move dose2dtype() into masque.file.utils, add dtype2dose(), and add a note that use_dtype_as_dose --- masque/file/gdsii.py | 106 ++++------------------------------------- masque/file/utils.py | 110 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 99 deletions(-) diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index 94c6928..60755d6 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -27,7 +27,7 @@ import gdsii.library import gdsii.structure import gdsii.elements -from .utils import mangle_name, make_dose_table +from .utils import mangle_name, make_dose_table, dose2dtype, dtype2dose from .. import Pattern, SubPattern, GridRepetition, PatternError, Label, Shape, subpattern_t from ..shapes import Polygon, Path from ..utils import rotation_matrix_2d, get_bit, set_bit, vector2, is_scalar, layer_t @@ -173,90 +173,6 @@ def writefile(patterns: Union[List[Pattern], Pattern], return results -def dose2dtype(patterns: List[Pattern], - ) -> Tuple[List[Pattern], List[float]]: - """ - For each shape in each pattern, set shape.layer to the tuple - (base_layer, datatype), where: - layer is chosen to be equal to the original shape.layer if it is an int, - or shape.layer[0] if it is a tuple. `str` layers raise a PatterError. - datatype is chosen arbitrarily, based on calcualted dose for each shape. - Shapes with equal calcualted dose will have the same datatype. - A list of doses is retured, providing a mapping between datatype - (list index) and dose (list entry). - - Note that this function modifies the input Pattern(s). - - Args: - patterns: A `Pattern` or list of patterns to write to file. Modified by this function. - - Returns: - (patterns, dose_list) - patterns: modified input patterns - dose_list: A list of doses, providing a mapping between datatype (int, list index) - and dose (float, list entry). - """ - # Get a dict of id(pattern) -> pattern - patterns_by_id = {id(pattern): pattern for pattern in patterns} - for pattern in patterns: - for i, p in pattern.referenced_patterns_by_id().items(): - patterns_by_id[i] = p - - # Get a table of (id(pat), written_dose) for each pattern and subpattern - sd_table = make_dose_table(patterns) - - # Figure out all the unique doses necessary to write this pattern - # This means going through each row in sd_table and adding the dose values needed to write - # that subpattern at that dose level - dose_vals = set() - for pat_id, pat_dose in sd_table: - pat = patterns_by_id[pat_id] - for shape in pat.shapes: - dose_vals.add(shape.dose * pat_dose) - - if len(dose_vals) > 256: - raise PatternError('Too many dose values: {}, maximum 256 when using dtypes.'.format(len(dose_vals))) - - dose_vals_list = list(dose_vals) - - # Create a new pattern for each non-1-dose entry in the dose table - # and update the shapes to reflect their new dose - new_pats = {} # (id, dose) -> new_pattern mapping - for pat_id, pat_dose in sd_table: - if pat_dose == 1: - new_pats[(pat_id, pat_dose)] = patterns_by_id[pat_id] - continue - - old_pat = patterns_by_id[pat_id] - pat = old_pat.copy() # keep old subpatterns - pat.shapes = copy.deepcopy(old_pat.shapes) - pat.labels = copy.deepcopy(old_pat.labels) - - encoded_name = mangle_name(pat, pat_dose) - if len(encoded_name) == 0: - raise PatternError('Zero-length name after mangle+encode, originally "{}"'.format(pat.name)) - pat.name = encoded_name - - for shape in pat.shapes: - data_type = dose_vals_list.index(shape.dose * pat_dose) - if isinstance(shape.layer, int): - shape.layer = (shape.layer, data_type) - elif isinstance(shape.layer, tuple): - shape.layer = (shape.layer[0], data_type) - else: - raise PatternError(f'Invalid layer for gdsii: {shape.layer}') - - new_pats[(pat_id, pat_dose)] = pat - - # Go back through all the dose-specific patterns and fix up their subpattern entries - for (pat_id, pat_dose), pat in new_pats.items(): - for subpat in pat.subpatterns: - dose_mult = subpat.dose * pat_dose - subpat.pattern = new_pats[(id(subpat.pattern), dose_mult)] - - return patterns, dose_vals_list - - def readfile(filename: Union[str, pathlib.Path], *args, **kwargs, @@ -302,6 +218,8 @@ def read(stream: io.BufferedIOBase, use_dtype_as_dose: If `False`, set each polygon's layer to `(gds_layer, gds_datatype)`. If `True`, set the layer to `gds_layer` and the dose to `gds_datatype`. Default `False`. + NOTE: This will be deprecated in the future in favor of + `pattern.apply(masque.file.utils.dtype2dose)`. clean_vertices: If `True`, remove any redundant vertices when loading polygons. The cleaning process removes any polygons with zero area or <3 vertices. Default `True`. @@ -325,14 +243,9 @@ def read(stream: io.BufferedIOBase, # Switch based on element type: if isinstance(element, gdsii.elements.Boundary): args = {'vertices': element.xy[:-1], + 'layer': (element.layer, element.data_type), } - if use_dtype_as_dose: - args['dose'] = element.data_type - args['layer'] = element.layer - else: - args['layer'] = (element.layer, element.data_type) - poly = Polygon(**args) if clean_vertices: @@ -350,6 +263,7 @@ def read(stream: io.BufferedIOBase, raise PatternError('Unrecognized path type: {}'.format(element.path_type)) args = {'vertices': element.xy, + 'layer': (element.layer, element.data_type), 'width': element.width if element.width is not None else 0.0, 'cap': cap, } @@ -361,12 +275,6 @@ def read(stream: io.BufferedIOBase, if element.end_extn is not None: args['cap_extensions'][1] = element.end_extn - if use_dtype_as_dose: - args['dose'] = element.data_type - args['layer'] = element.layer - else: - args['layer'] = (element.layer, element.data_type) - path = Path(**args) if clean_vertices: @@ -389,6 +297,10 @@ def read(stream: io.BufferedIOBase, elif isinstance(element, gdsii.elements.ARef): pat.subpatterns.append(_aref_to_gridrep(element)) + if use_dtype_as_dose: + logger.warning('use_dtype_as_dose will be removed in the future!') + pat = dtype2dose(pat) + patterns.append(pat) # Create a dict of {pattern.name: pattern, ...}, then fix up all subpattern.pattern entries diff --git a/masque/file/utils.py b/masque/file/utils.py index 9092ab9..e36765e 100644 --- a/masque/file/utils.py +++ b/masque/file/utils.py @@ -1,10 +1,11 @@ """ Helper functions for file reading and writing """ -import re from typing import Set, Tuple, List +import re +import copy -from masque.pattern import Pattern +from .. import Pattern, PatternError def mangle_name(pattern: Pattern, dose_multiplier: float=1.0) -> str: @@ -45,3 +46,108 @@ def make_dose_table(patterns: List[Pattern], dose_multiplier: float=1.0) -> Set[ subpat_dose_table = make_dose_table([subpat.pattern], subpat.dose * dose_multiplier) dose_table = dose_table.union(subpat_dose_table) return dose_table + + +def dtype2dose(pattern: Pattern) -> Pattern: + """ + For each shape in the pattern, if the layer is a tuple, set the + layer to the tuple's first element and set the dose to the + tuple's second element. + + Generally intended for use with `Pattern.apply()`. + + Args: + pattern: Pattern to modify + + Returns: + pattern + """ + for shape in pattern.shapes: + if isinstance(shape.layer, tuple): + shape.dose = shape.layer[1] + shape.layer = shape.layer[0] + return pattern + + +def dose2dtype(patterns: List[Pattern], + ) -> Tuple[List[Pattern], List[float]]: + """ + For each shape in each pattern, set shape.layer to the tuple + (base_layer, datatype), where: + layer is chosen to be equal to the original shape.layer if it is an int, + or shape.layer[0] if it is a tuple. `str` layers raise a PatterError. + datatype is chosen arbitrarily, based on calcualted dose for each shape. + Shapes with equal calcualted dose will have the same datatype. + A list of doses is retured, providing a mapping between datatype + (list index) and dose (list entry). + + Note that this function modifies the input Pattern(s). + + Args: + patterns: A `Pattern` or list of patterns to write to file. Modified by this function. + + Returns: + (patterns, dose_list) + patterns: modified input patterns + dose_list: A list of doses, providing a mapping between datatype (int, list index) + and dose (float, list entry). + """ + # Get a dict of id(pattern) -> pattern + patterns_by_id = {id(pattern): pattern for pattern in patterns} + for pattern in patterns: + for i, p in pattern.referenced_patterns_by_id().items(): + patterns_by_id[i] = p + + # Get a table of (id(pat), written_dose) for each pattern and subpattern + sd_table = make_dose_table(patterns) + + # Figure out all the unique doses necessary to write this pattern + # This means going through each row in sd_table and adding the dose values needed to write + # that subpattern at that dose level + dose_vals = set() + for pat_id, pat_dose in sd_table: + pat = patterns_by_id[pat_id] + for shape in pat.shapes: + dose_vals.add(shape.dose * pat_dose) + + if len(dose_vals) > 256: + raise PatternError('Too many dose values: {}, maximum 256 when using dtypes.'.format(len(dose_vals))) + + dose_vals_list = list(dose_vals) + + # Create a new pattern for each non-1-dose entry in the dose table + # and update the shapes to reflect their new dose + new_pats = {} # (id, dose) -> new_pattern mapping + for pat_id, pat_dose in sd_table: + if pat_dose == 1: + new_pats[(pat_id, pat_dose)] = patterns_by_id[pat_id] + continue + + old_pat = patterns_by_id[pat_id] + pat = old_pat.copy() # keep old subpatterns + pat.shapes = copy.deepcopy(old_pat.shapes) + pat.labels = copy.deepcopy(old_pat.labels) + + encoded_name = mangle_name(pat, pat_dose) + if len(encoded_name) == 0: + raise PatternError('Zero-length name after mangle+encode, originally "{}"'.format(pat.name)) + pat.name = encoded_name + + for shape in pat.shapes: + data_type = dose_vals_list.index(shape.dose * pat_dose) + if isinstance(shape.layer, int): + shape.layer = (shape.layer, data_type) + elif isinstance(shape.layer, tuple): + shape.layer = (shape.layer[0], data_type) + else: + raise PatternError(f'Invalid layer for gdsii: {shape.layer}') + + new_pats[(pat_id, pat_dose)] = pat + + # Go back through all the dose-specific patterns and fix up their subpattern entries + for (pat_id, pat_dose), pat in new_pats.items(): + for subpat in pat.subpatterns: + dose_mult = subpat.dose * pat_dose + subpat.pattern = new_pats[(id(subpat.pattern), dose_mult)] + + return patterns, dose_vals_list