Remove support for dose

Since there isn't GDS/OASIS level support for dose, this can be mostly
handled by using arbitrary layers/dtypes directly. Dose scaling isn't
handled as nicely that way, but it corresponds more directly to what
gets written to file.
This commit is contained in:
Jan Petykiewicz 2023-01-18 18:14:33 -08:00 committed by jan
commit c7f3e7ee52
18 changed files with 57 additions and 340 deletions

View file

@ -26,8 +26,8 @@ def writefile(
Note that this function modifies the Pattern.
If `custom_attributes` is `True`, non-standard `pattern_layer` and `pattern_dose` attributes
are written to the relevant elements.
If `custom_attributes` is `True`, a non-standard `pattern_layer` attribute
is written to the relevant elements.
It is often a good idea to run `pattern.subpatternize()` on pattern prior to
calling this function, especially if calling `.polygonize()` will result in very
@ -39,8 +39,8 @@ def writefile(
Args:
pattern: Pattern to write to file. Modified by this function.
filename: Filename to write to.
custom_attributes: Whether to write non-standard `pattern_layer` and
`pattern_dose` attributes to the SVG elements.
custom_attributes: Whether to write non-standard `pattern_layer` attribute to the
SVG elements.
"""
pattern = library[top]
@ -61,8 +61,7 @@ def writefile(
svg = svgwrite.Drawing(filename, profile='full', viewBox=viewbox_string,
debug=(not custom_attributes))
# Now create a group for each row in sd_table (ie, each pattern + dose combination)
# and add in any Boundary and Use elements
# Now create a group for each pattern and add in any Boundary and Use elements
for name, pat in library.items():
svg_group = svg.g(id=mangle_name(name), fill='blue', stroke='red')
@ -73,7 +72,6 @@ def writefile(
path = svg.path(d=path_spec)
if custom_attributes:
path['pattern_layer'] = polygon.layer
path['pattern_dose'] = polygon.dose
svg_group.add(path)
@ -82,8 +80,6 @@ def writefile(
continue
transform = f'scale({subpat.scale:g}) rotate({subpat.rotation:g}) translate({subpat.offset[0]:g},{subpat.offset[1]:g})'
use = svg.use(href='#' + mangle_name(subpat.target), transform=transform)
if custom_attributes:
use['pattern_dose'] = subpat.dose
svg_group.add(use)
svg.defs.add(svg_group)

View file

@ -15,23 +15,18 @@ from ..shapes import Polygon, Path
logger = logging.getLogger(__name__)
def mangle_name(name: str, dose_multiplier: float = 1.0) -> str:
def mangle_name(name: str) -> str:
"""
Create a new name using `name` and the `dose_multiplier`.
Sanitize a name.
Args:
name: Name we want to mangle.
dose_multiplier: Dose multiplier to mangle with.
Returns:
Mangled name.
"""
if dose_multiplier == 1:
full_name = name
else:
full_name = f'{name}_dm{dose_multiplier}'
expression = re.compile(r'[^A-Za-z0-9_\?\$]')
sanitized_name = expression.sub('_', full_name)
sanitized_name = expression.sub('_', name)
return sanitized_name
@ -59,134 +54,6 @@ def clean_pattern_vertices(pat: Pattern) -> Pattern:
return pat
def make_dose_table(
top_names: Iterable[str],
library: Mapping[str, Pattern],
dose_multiplier: float = 1.0,
) -> Set[Tuple[str, float]]:
"""
Create a set containing `(name, written_dose)` for each pattern (including subpatterns)
Args:
top_names: Names of all topcells
pattern: Source Patterns.
dose_multiplier: Multiplier for all written_dose entries.
Returns:
`{(name, written_dose), ...}`
"""
dose_table = {(top_name, dose_multiplier) for top_name in top_names}
for name, pattern in library.items():
for subpat in pattern.subpatterns:
if subpat.target is None:
continue
subpat_dose_entry = (subpat.target, subpat.dose * dose_multiplier)
if subpat_dose_entry not in dose_table:
subpat_dose_table = make_dose_table(subpat.target, library, 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(
library: Mapping[str, 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).
"""
logger.warning('TODO: dose2dtype() needs to be tested!')
if not isinstance(library, Library):
library = WrapROLibrary(library)
# Get a table of (id(pat), written_dose) for each pattern and subpattern
sd_table = make_dose_table(library.find_toplevel(), library)
# 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 name, pat_dose in sd_table:
pat = library[name]
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_names = {} # {(old name, dose): new name} mapping
new_lib = {} # {new_name: new_pattern} mapping
for name, pat_dose in sd_table:
mangled_name = mangle_name(name, pat_dose)
new_names[(name, pat_dose)] = mangled_name
old_pat = library[name]
if pat_dose == 1:
new_lib[mangled_name] = old_pat
continue
pat = old_pat.deepcopy()
if len(mangled_name) == 0:
raise PatternError(f'Zero-length name after mangle, originally "{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_lib[mangled_name] = pat
return new_lib, dose_vals_list
def is_gzipped(path: pathlib.Path) -> bool:
with open(path, 'rb') as stream:
magic_bytes = stream.read(2)