Source code for kqcircuits.simulations.cross_section_simulation

# This code is part of KQCircuits
# Copyright (C) 2022 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).


import abc

import logging

from kqcircuits.elements.element import Element
from kqcircuits.pya_resolver import pya
from kqcircuits.simulations.simulation import Simulation, get_simulation_layer_by_name
from kqcircuits.util.geometry_helper import merge_points_and_match_on_edges
from kqcircuits.util.parameters import add_parameters_from


[docs] @add_parameters_from(Simulation, "name", "material_dict", "extra_json_data") class CrossSectionSimulation: """Base class for cross-section simulation geometries. The geometry consist of 2-dimensional layers which are thought as cross sections of 3-dimensional geometry. This allows for modeling waveguide-like geometries where the cross section is constant for relatively long segments. This class is intended to be subclassed by a specific simulation implementation; The subclass should implement the method 'build' which defines the simulation geometry and material properties. The helper function 'insert_layer' should be called internally to build the geometry. """ LIBRARY_NAME = None # This is needed by some methods inherited from Element. def __init__(self, layout, **kwargs): """Initialize a CrossSectionSimulation. The initializer parses parameters, creates a top cell, and then calls `self.build` to create the simulation geometry. Args: layout: the layout on which to create the simulation Keyword arguments: `**kwargs`: Any parameter can be passed as a keyword argument. """ if layout is None or not isinstance(layout, pya.Layout): error_text = "Cannot create simulation with invalid or nil layout." error = ValueError(error_text) logging.error(error_text) raise error self.layout = layout schema = type(self).get_schema() # Apply kwargs or default value for parameter, item in schema.items(): if parameter in kwargs: setattr(self, parameter, kwargs[parameter]) else: setattr(self, parameter, item.default) self.cell = kwargs["cell"] if "cell" in kwargs else layout.create_cell(self.name) self.layers = {} self.units = kwargs.get("units", "um") self.build() self.process_layers() # Inherit specific methods from Element get_schema = classmethod(Element.get_schema.__func__) get_layers = Simulation.get_layers get_parameters = Simulation.get_parameters is_metal = Simulation.is_metal get_material_dict = Simulation.get_material_dict check_material_dict = Simulation.check_material_dict
[docs] @abc.abstractmethod def build(self): """Build simulation geometry. This method is to be overridden, and the overriding method should create the geometry to be simulated. """ return
[docs] def process_layers(self): """Process and check validity of self.layers. Is called after build method. Internally, * call merge_points_and_match_on_edges to avoid small-scale geometry artifacts * ignore layers with empty region * check if 'excitation' keywords are set correctly * insert layer shapes to self.cell.shapes and replace 'region' keyword with 'layer' keyword. """ merge_points_and_match_on_edges([layer["region"] for layer in self.layers.values()], tolerance=1) layers = {} for name, data in self.layers.items(): if data["region"].is_empty(): continue # ignore layer with empty region # check if excitation keyword is set correctly if self.is_metal(data.get("material")): if "excitation" not in data: raise ValueError(f"Layer {name} is metal but integer excitation value is not set.") if not isinstance(data["excitation"], int): raise ValueError(f"Excitation should be integer but {data['excitation']} is set for layer {name}.") elif "excitation" in data: raise ValueError(f"Layer {name} is dielectric but excitation value is set.") # insert layer shape to self.cell.shapes and replace region keyword with layer keyword layer_info = get_simulation_layer_by_name(name) self.cell.shapes(self.layout.layer(layer_info)).insert(data["region"]) layers[name] = {"layer": layer_info.layer, **{k: v for k, v in data.items() if k != "region"}} self.layers = layers
[docs] def insert_layer(self, layer_name: str, region: pya.Region, material: str, **params): """Add layer parameters into 'self.layers' if region is non-empty.""" if not region.is_empty(): self.layers[layer_name] = {"region": region.dup(), "material": material, **params}
[docs] def restrict_layer_regions(self, bbox: pya.DBox): """Limit the regions of self.layers inside the bounding box.""" region = pya.Region(bbox.to_itype(self.layout.dbu)) for layer in self.layers.values(): layer["region"] &= region
[docs] def get_unfilled_region(self, bbox: pya.DBox) -> pya.Region: """Return region inside bbox that is not covered yet by layers.""" region = pya.Region(bbox.to_itype(self.layout.dbu)) for layer in self.layers.values(): region -= layer["region"] return region
[docs] def get_simulation_data(self): """Return the simulation data in dictionary form. Returns: dictionary of relevant parameters for simulation """ simulation_data = { "simulation_name": self.name, "units": self.units, "layers": self.layers, "material_dict": self.check_material_dict(), } return simulation_data