Source code for kqcircuits.simulations.partition_region

# This code is part of KQCircuits
# Copyright (C) 2024 IQM Finland Oy
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# https://www.gnu.org/licenses/gpl-3.0.html.
#
# The software distribution should follow IQM trademark policy for open-source software
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code.
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement)
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement).

from kqcircuits.pya_resolver import pya


[docs] def get_list_of_two(dims): """Returns list of two terms when 'dims' is given as a scalar or list.""" if isinstance(dims, list): return [dims[0], dims[1]] return [dims, dims]
[docs] class PartitionRegion: """Class to enable partitioning of simulation geometry into sub-regions""" def __init__( self, name="part", region=None, z=None, face=None, vertical_dimensions=None, metal_edge_dimensions=None, visualise=False, ): """ Args: name: Suffix of the partition layers. Must not end with a number. region: Area to which the partition region is limited. Can be given as pya.DBox, pya.DPolygon, or list of those. Accepts also pya.Region if called from custom Simulation.get_partition_regions function. Use None to cover full domain. z: Lower and upper bound for the partition region as scalar or list. Use None to cover full height. face: The face name to which the partition region is applied. If this is used, the vertical_dimensions and metal_edge_dimensions are applied. vertical_dimensions: Vertical dimensions of the partition region on face as scalar or list. The terms in the list correspond to expansion dimensions into directions of substrate and vacuum, respectively. Scalar means the substrate and vacuum expansions are equal. This is applied only if face is given. metal_edge_dimensions: Lateral dimensions to limit the partition region next to the metal edges. If given as list, the terms correspond to expansions into directions of gap and metal, respectively. If given as scalar, the gap and metal expansions are equal. Use None to disable the metal edge limitation. This is applied only if face is given. visualise: Visualises the partition region in the preview of the simulation geometry. """ if name[-1] in "0123456789": raise ValueError(f"PartitionRegion name must not end with a number, but {name} is given.") if name == "": raise ValueError("PartitionRegion name must not be an empty string.") if any(n in name for n in ["layerMA", "layerMS", "layerSA", "substrate", "vacuum"]): raise ValueError(f"PartitionRegion name {name} contains reserved layer name.") self.name = name self.region = region self.z = z self.face = face self.vertical_dimensions = vertical_dimensions self.metal_edge_dimensions = metal_edge_dimensions self.visualise = visualise
[docs] def limit_box(self, bottom, top, box, dbu): """Limits the region and z-levels into simulation dimensions. Args: bottom: bottom of the simulation domain. top: top of the simulation domain. box: lateral dimensions of simulation domain as pya.DBox. dbu: layout database unit """ # update self.z using bottom and top self.z = get_list_of_two(self.z) if self.z[0] is None or self.z[0] < bottom: self.z[0] = bottom if self.z[1] is None or top < self.z[1]: self.z[1] = top # update self.region using box box_region = pya.Region(box.to_itype(dbu)) if self.region is None: self.region = box_region elif isinstance(self.region, pya.Region): self.region = self.region & box_region elif isinstance(self.region, list): merged_region = pya.Region() for r in self.region: merged_region += pya.Region(r.to_itype(dbu)) self.region = merged_region & box_region else: self.region = pya.Region(self.region.to_itype(dbu)) & box_region
[docs] def limit_face(self, z, sign, metal_region, etch_region, dbu): """Limits the region and z-levels on face. Function limit_box should be called once before this. Args: z: z-level of the face sign: 1 if substrate is below vacuum, -1 otherwise metal_region: metallization area as pya.Region etch_region: area where metal is etched away as pya.Region dbu: layout database unit """ # Reset face to indicate that face limitation is applied self.face = None # update self.z using self.vertical_dimensions vd = get_list_of_two(self.vertical_dimensions) if vd[sign < 0] is not None and self.z[0] < z - vd[sign < 0]: self.z[0] = z - vd[sign < 0] if vd[sign > 0] is not None and z + vd[sign > 0] < self.z[1]: self.z[1] = z + vd[sign > 0] # update self.region using self.metal_edge_dimensions ed = get_list_of_two(self.metal_edge_dimensions) if ed[0] is not None: self.region &= metal_region.sized(ed[0] / dbu) if ed[1] is not None: self.region &= etch_region.sized(ed[1] / dbu)