move dose2dtype() into masque.file.utils, add dtype2dose(), and add a note that use_dtype_as_dose

This commit is contained in:
Jan Petykiewicz 2020-05-19 00:13:50 -07:00
parent 1b0b056bf9
commit 8082743e17
2 changed files with 117 additions and 99 deletions

View File

@ -27,7 +27,7 @@ import gdsii.library
import gdsii.structure import gdsii.structure
import gdsii.elements 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 .. import Pattern, SubPattern, GridRepetition, PatternError, Label, Shape, subpattern_t
from ..shapes import Polygon, Path from ..shapes import Polygon, Path
from ..utils import rotation_matrix_2d, get_bit, set_bit, vector2, is_scalar, layer_t 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 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], def readfile(filename: Union[str, pathlib.Path],
*args, *args,
**kwargs, **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)`. 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`. If `True`, set the layer to `gds_layer` and the dose to `gds_datatype`.
Default `False`. 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. clean_vertices: If `True`, remove any redundant vertices when loading polygons.
The cleaning process removes any polygons with zero area or <3 vertices. The cleaning process removes any polygons with zero area or <3 vertices.
Default `True`. Default `True`.
@ -325,14 +243,9 @@ def read(stream: io.BufferedIOBase,
# Switch based on element type: # Switch based on element type:
if isinstance(element, gdsii.elements.Boundary): if isinstance(element, gdsii.elements.Boundary):
args = {'vertices': element.xy[:-1], 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) poly = Polygon(**args)
if clean_vertices: if clean_vertices:
@ -350,6 +263,7 @@ def read(stream: io.BufferedIOBase,
raise PatternError('Unrecognized path type: {}'.format(element.path_type)) raise PatternError('Unrecognized path type: {}'.format(element.path_type))
args = {'vertices': element.xy, args = {'vertices': element.xy,
'layer': (element.layer, element.data_type),
'width': element.width if element.width is not None else 0.0, 'width': element.width if element.width is not None else 0.0,
'cap': cap, 'cap': cap,
} }
@ -361,12 +275,6 @@ def read(stream: io.BufferedIOBase,
if element.end_extn is not None: if element.end_extn is not None:
args['cap_extensions'][1] = element.end_extn 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) path = Path(**args)
if clean_vertices: if clean_vertices:
@ -389,6 +297,10 @@ def read(stream: io.BufferedIOBase,
elif isinstance(element, gdsii.elements.ARef): elif isinstance(element, gdsii.elements.ARef):
pat.subpatterns.append(_aref_to_gridrep(element)) 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) patterns.append(pat)
# Create a dict of {pattern.name: pattern, ...}, then fix up all subpattern.pattern entries # Create a dict of {pattern.name: pattern, ...}, then fix up all subpattern.pattern entries

View File

@ -1,10 +1,11 @@
""" """
Helper functions for file reading and writing Helper functions for file reading and writing
""" """
import re
from typing import Set, Tuple, List 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: 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) subpat_dose_table = make_dose_table([subpat.pattern], subpat.dose * dose_multiplier)
dose_table = dose_table.union(subpat_dose_table) dose_table = dose_table.union(subpat_dose_table)
return 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