Source code for kqcircuits.simulations.single_element_simulation

# This code is part of KQCircuits
# Copyright (C) 2023 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 typing import Callable
from kqcircuits.elements.element import Element
from kqcircuits.junctions import junction_type_choices
from kqcircuits.simulations.simulation import Simulation
from kqcircuits.pya_resolver import pya
from kqcircuits.simulations.partition_region import PartitionRegion
from kqcircuits.simulations.port import InternalPort, EdgePort
from kqcircuits.util.parameters import Param, pdt, add_parameters_from
from kqcircuits.util.refpoints import RefpointToInternalPort, RefpointToEdgePort, WaveguideToSimPort, JunctionSimPort


def _get_build_function(
    element_class, ignore_ports, transformation_from_center, sim_junction_type, deembed_cross_sections
):
    def _build_for_element_class(self):
        if sim_junction_type not in junction_type_choices:
            raise ValueError(
                f"Unknown sim_junction_type {sim_junction_type}. "
                f"Junction type should be listed in junction_type_choices"
            )

        simulation_cell = self.add_element(
            element_class, **{**self.get_parameters(), "junction_type": sim_junction_type, "fluxline_type": "none"}
        )

        element_trans = pya.DTrans(0, False, self.box.center())
        if transformation_from_center is not None:
            element_trans *= transformation_from_center(simulation_cell)
        _, refp = self.insert_cell(simulation_cell, element_trans, rec_levels=None)
        self.refpoints = refp

        if deembed_cross_sections is not None:
            deembed_cs_names = deembed_cross_sections.keys()

        # Add ports
        port_i = 0
        for port in element_class.get_sim_ports(self):
            if ignore_ports is not None and port.refpoint in ignore_ports:
                continue

            if deembed_cross_sections is not None and port.refpoint in deembed_cs_names:
                port.deembed_cross_section = deembed_cross_sections[port.refpoint]

            if isinstance(port, RefpointToInternalPort):
                self.ports.append(
                    InternalPort(
                        number=(port_i := port_i + 1),
                        signal_location=refp[port.refpoint],
                        ground_location=None if not port.ground_refpoint else refp[port.ground_refpoint],
                        resistance=port.resistance,
                        reactance=port.reactance,
                        inductance=port.inductance,
                        capacitance=port.capacitance,
                        face=port.face,
                        junction=port.junction,
                        signal_layer=port.signal_layer,
                    )
                )
            elif isinstance(port, RefpointToEdgePort):
                self.ports.append(
                    EdgePort(
                        number=(port_i := port_i + 1),
                        signal_location=refp[port.refpoint],
                        resistance=port.resistance,
                        reactance=port.reactance,
                        inductance=port.inductance,
                        capacitance=port.capacitance,
                        deembed_len=port.deembed_len,
                        face=port.face,
                        junction=port.junction,
                        size=port.size,
                        deembed_cross_section=port.deembed_cross_section,
                    )
                )
            elif isinstance(port, WaveguideToSimPort):
                towards = port.towards
                if port.towards is None:
                    towards = f"{port.refpoint}_corner"
                self.produce_waveguide_to_port(
                    refp[port.refpoint],
                    refp[towards],
                    (port_i := port_i + 1),
                    side=port.side,
                    a=port.a,
                    b=port.b,
                    term1=port.term1,
                    turn_radius=port.turn_radius,
                    use_internal_ports=port.use_internal_ports,
                    waveguide_length=port.waveguide_length,
                    face=port.face,
                    airbridge=port.airbridge,
                    deembed_cross_section=port.deembed_cross_section,
                )

            elif isinstance(port, JunctionSimPort):
                if self.separate_island_internal_ports:
                    self.ports.append(InternalPort((port_i := port_i + 1), refp[port.refpoint], face=port.face))
                    self.ports.append(InternalPort((port_i := port_i + 1), refp[port.other_refpoint], face=port.face))
                else:  # Junction between the islands
                    self.ports.append(
                        InternalPort(
                            (port_i := port_i + 1),
                            *self.etched_line(refp[port.refpoint], refp[port.other_refpoint]),
                            face=port.face,
                            inductance=self.junction_inductance,
                            capacitance=self.junction_capacitance,
                            junction=True,
                            floating=port.floating,
                        )
                    )

    return _build_for_element_class


[docs] def get_single_element_sim_class( element_class: Element, ignore_ports: list[str] | None = None, transformation_from_center: Callable[[pya.Cell], pya.DTrans] | None = None, partition_region_function: Callable[[Simulation], list[PartitionRegion]] | None = None, sim_junction_type: str = "Sim", deembed_cross_sections: dict[str] = None, ) -> type[Simulation]: """Formulates a simulation class containing a single cell of a given Element class Args: element_class: an Element class for which a simulation class is returned ignore_ports: If list of strings is given, simulation ports will not be created for the given refpoints in the simulation class. transformation_from_center: If None, simulated element is placed in the middle of simulation's box. Otherwise should be a function that takes an element cell as argument and returns a DTrans object. The returned transformation is applied to the element cell after placing it in the middle of simulation's box. The function should not cause any side-effects, i.e. change the cell parameters partition_region_function: optional. Function that the simulation instance will use to define partition regions, which may look up instances parameters and refpoints to derive the regions. deembed_cross_sections: optional dictionary for cross-section simulation that can be used for deembeding. The naming convention in the dictionary is `deembed_cross_sections[port_refpoint]=cross_section_name`, where `cross_section_name` is the name given to the correction cuts. For example, see `simulation.epr.smooth_capacitor.py`, deembed_cross_sections['port_a']='port_amer'. """ overriden_class_attributes = { "separate_island_internal_ports": Param( pdt.TypeBoolean, "Add InternalPorts on both islands (if applicable). Use for capacitive simulations", False, ), "junction_inductance": Param(pdt.TypeList, "Junction inductance (if junction exists)", 11.497e-9, unit="H"), "junction_capacitance": Param(pdt.TypeList, "Junction capacitance (if junction exists)", 0.1e-15, unit="F"), "build": _get_build_function( element_class, ignore_ports, transformation_from_center, sim_junction_type, deembed_cross_sections ), } if partition_region_function: overriden_class_attributes["get_partition_regions"] = partition_region_function element_sim_class = type( f"SingleElementSimulationClassFor{element_class.__name__}", (Simulation,), overriden_class_attributes, ) add_parameters_from(element_class)(element_sim_class) return element_sim_class