From 00394a62f0843625d5bba5b1cca9d70efdc84448 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 11 May 2020 18:58:57 -0700 Subject: [PATCH] Formally allow instances to point to None (i.e. an 'empty' pattern) --- masque/file/gdsii.py | 10 ++++++++-- masque/file/svg.py | 4 ++++ masque/file/utils.py | 2 ++ masque/pattern.py | 39 +++++++++++++++++++++++++++------------ masque/repetition.py | 7 ++++++- masque/subpattern.py | 9 +++++++-- 6 files changed, 54 insertions(+), 17 deletions(-) diff --git a/masque/file/gdsii.py b/masque/file/gdsii.py index 4f3b46e..0040006 100644 --- a/masque/file/gdsii.py +++ b/masque/file/gdsii.py @@ -100,7 +100,9 @@ def write(patterns: Pattern or List[Pattern], # Get a dict of id(pattern) -> pattern patterns_by_id = {id(pattern): pattern for pattern in patterns} for pattern in patterns: - patterns_by_id.update(pattern.referenced_patterns_by_id()) + for i, p in pattern.referenced_patterns_by_id().items(): + if p is not None: + patterns_by_id[i] = p disambiguate_func(patterns_by_id.values()) @@ -170,7 +172,9 @@ def dose2dtype(patterns: List[Pattern], # Get a dict of id(pattern) -> pattern patterns_by_id = {id(pattern): pattern for pattern in patterns} for pattern in patterns: - patterns_by_id.update(pattern.referenced_patterns_by_id()) + for i, p in pattern.referenced_patterns_by_id().items(): + if p is not None: + patterns_by_id[i] = p # Get a table of (id(pat), written_dose) for each pattern and subpattern sd_table = make_dose_table(patterns) @@ -466,6 +470,8 @@ def _subpatterns_to_refs(subpatterns: List[SubPattern or GridRepetition] ) -> List[gdsii.elements.ARef or gdsii.elements.SRef]: refs = [] for subpat in subpatterns: + if subpat.pattern is None: + continue encoded_name = subpat.pattern.name # Note: GDS mirrors first and rotates second diff --git a/masque/file/svg.py b/masque/file/svg.py index 8752ca2..2251f54 100644 --- a/masque/file/svg.py +++ b/masque/file/svg.py @@ -61,6 +61,8 @@ def writefile(pattern: Pattern, # Now create a group for each row in sd_table (ie, each pattern + dose combination) # and add in any Boundary and Use elements for pat in patterns_by_id.values(): + if pat is None: + continue svg_group = svg.g(id=mangle_name(pat), fill='blue', stroke='red') for shape in pat.shapes: @@ -75,6 +77,8 @@ def writefile(pattern: Pattern, svg_group.add(path) for subpat in pat.subpatterns: + if subpat.pattern is None: + continue transform = 'scale({:g}) rotate({:g}) translate({:g},{:g})'.format( subpat.scale, subpat.rotation, subpat.offset[0], subpat.offset[1]) use = svg.use(href='#' + mangle_name(subpat.pattern), transform=transform) diff --git a/masque/file/utils.py b/masque/file/utils.py index 5b1e658..9092ab9 100644 --- a/masque/file/utils.py +++ b/masque/file/utils.py @@ -38,6 +38,8 @@ def make_dose_table(patterns: List[Pattern], dose_multiplier: float=1.0) -> Set[ dose_table = {(id(pattern), dose_multiplier) for pattern in patterns} for pattern in patterns: for subpat in pattern.subpatterns: + if subpat.pattern is None: + continue subpat_dose_entry = (id(subpat.pattern), subpat.dose * dose_multiplier) if subpat_dose_entry not in dose_table: subpat_dose_table = make_dose_table([subpat.pattern], subpat.dose * dose_multiplier) diff --git a/masque/pattern.py b/masque/pattern.py index e16af5b..1849946 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -151,7 +151,9 @@ class Pattern: A Pattern containing all the shapes and subpatterns for which the parameter functions return True """ - def do_subset(src): + def do_subset(src: Optional['Pattern']) -> Optional['Pattern']: + if src is None: + return None pat = Pattern(name=src.name) if shapes_func is not None: pat.shapes = [s for s in src.shapes if shapes_func(s)] @@ -165,6 +167,8 @@ class Pattern: pat = self.apply(do_subset) else: pat = do_subset(self) + + assert(pat is not None) return pat def apply(self, @@ -197,8 +201,12 @@ class Pattern: if pat_id not in memo: memo[pat_id] = None pat = func(self) - for subpat in pat.subpatterns: - subpat.pattern = subpat.pattern.apply(func, memo) + if pat is not None: + for subpat in pat.subpatterns: + if subpat.pattern is None: + subpat.pattern = func(None) + else: + subpat.pattern = subpat.pattern.apply(func, memo) memo[pat_id] = pat elif memo[pat_id] is None: raise PatternError('.apply() called on pattern with circular reference') @@ -277,11 +285,12 @@ class Pattern: else: sp_transform = False - subpattern.pattern = subpattern.pattern.dfs(visit_before=visit_before, - visit_after=visit_after, - transform=sp_transform, - memo=memo, - hierarchy=hierarchy + (self,)) + if subpattern.pattern is not None: + subpattern.pattern = subpattern.pattern.dfs(visit_before=visit_before, + visit_after=visit_after, + transform=sp_transform, + memo=memo, + hierarchy=hierarchy + (self,)) if visit_after is not None: pat = visit_after(pat, hierarchy=hierarchy, memo=memo, transform=transform) @@ -311,7 +320,8 @@ class Pattern: (shape.to_polygons(poly_num_points, poly_max_arclen) for shape in old_shapes))) for subpat in self.subpatterns: - subpat.pattern.polygonize(poly_num_points, poly_max_arclen) + if subpat.pattern is not None: + subpat.pattern.polygonize(poly_num_points, poly_max_arclen) return self def manhattanize(self, @@ -368,6 +378,8 @@ class Pattern: if recursive: for subpat in self.subpatterns: + if subpat.pattern is None: + continue subpat.pattern.subpatternize(recursive=True, norm_value=norm_value, exclude_types=exclude_types) @@ -431,7 +443,8 @@ class Pattern: for subpat in self.subpatterns: if id(subpat.pattern) not in ids: ids[id(subpat.pattern)] = subpat.pattern - ids.update(subpat.pattern.referenced_patterns_by_id()) + if subpat.pattern is not None: + ids.update(subpat.pattern.referenced_patterns_by_id()) return ids def referenced_patterns_by_name(self) -> List[Tuple[str, 'Pattern']]: @@ -446,7 +459,7 @@ class Pattern: List of `(pat.name, pat)` tuples for all referenced Pattern objects """ pats_by_id = self.referenced_patterns_by_id() - pat_list = [(p.name, p) for p in pats_by_id.values()] + pat_list = [(p.name if p is not None else None, p) for p in pats_by_id.values()] return pat_list def get_bounds(self) -> Union[numpy.ndarray, None]: @@ -496,6 +509,8 @@ class Pattern: self.subpatterns = [] shape_counts = {} for subpat in subpatterns: + if subpat.pattern is None: + continue subpat.pattern.flatten() p = subpat.as_pattern() @@ -839,7 +854,7 @@ class Pattern: if pat in memo: return memo - children = set(sp.pattern for sp in pat.subpatterns) + children = set(sp.pattern for sp in pat.subpatterns if sp.pattern is not None) new_children = children - memo memo |= children diff --git a/masque/repetition.py b/masque/repetition.py index 5b0c23c..79cc38d 100644 --- a/masque/repetition.py +++ b/masque/repetition.py @@ -298,6 +298,7 @@ class GridRepetition: A copy of self.pattern which has been scaled, rotated, repeated, etc. etc. according to this `GridRepetition`'s properties. """ + assert(self.pattern is not None) patterns = [] for a in range(self.a_count): @@ -411,7 +412,7 @@ class GridRepetition: self.rotation *= -1 return self - def get_bounds(self) -> numpy.ndarray or None: + def get_bounds(self) -> Optional[numpy.ndarray]: """ Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the extent of the `GridRepetition` in each dimension. @@ -420,6 +421,8 @@ class GridRepetition: Returns: `[[x_min, y_min], [x_max, y_max]]` or `None` """ + if self.pattern is None: + return None return self.as_pattern().get_bounds() def scale_by(self, c: float) -> 'GridRepetition': @@ -496,6 +499,7 @@ class GridRepetition: Returns: self """ + assert(self.pattern is not None) self.lock() self.pattern.deeplock() return self @@ -510,6 +514,7 @@ class GridRepetition: Returns: self """ + assert(self.pattern is not None) self.unlock() self.pattern.deepunlock() return self diff --git a/masque/subpattern.py b/masque/subpattern.py index 2577ce9..16c3c7c 100644 --- a/masque/subpattern.py +++ b/masque/subpattern.py @@ -55,7 +55,7 @@ class SubPattern: #TODO more documentation? def __init__(self, - pattern: 'Pattern' or None, + pattern: Optional['Pattern'], offset: vector2 = (0.0, 0.0), rotation: float = 0.0, mirrored: List[bool] = None, @@ -176,6 +176,7 @@ class SubPattern: A copy of self.pattern which has been scaled, rotated, etc. according to this `SubPattern`'s properties. """ + assert(self.pattern is not None) pattern = self.pattern.deepcopy().deepunlock() pattern.scale_by(self.scale) [pattern.mirror(ax) for ax, do in enumerate(self.mirrored) if do] @@ -242,7 +243,7 @@ class SubPattern: self.rotation *= -1 return self - def get_bounds(self) -> numpy.ndarray or None: + def get_bounds(self) -> Optional[numpy.ndarray]: """ Return a `numpy.ndarray` containing `[[x_min, y_min], [x_max, y_max]]`, corresponding to the extent of the `SubPattern` in each dimension. @@ -251,6 +252,8 @@ class SubPattern: Returns: `[[x_min, y_min], [x_max, y_max]]` or `None` """ + if self.pattern is None: + return None return self.as_pattern().get_bounds() def scale_by(self, c: float) -> 'SubPattern': @@ -311,6 +314,7 @@ class SubPattern: Returns: self """ + assert(self.pattern is not None) self.lock() self.pattern.deeplock() return self @@ -325,6 +329,7 @@ class SubPattern: Returns: self """ + assert(self.pattern is not None) self.unlock() self.pattern.deepunlock() return self