# This code is part of KQCircuits
# Copyright (C) 2021 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 math import cos, sin, pi, radians
from kqcircuits.pya_resolver import pya
from kqcircuits.util.parameters import Param, pdt, add_parameters_from
from kqcircuits.util.geometry_helper import arc_points, round_dpath_width
from kqcircuits.elements.element import Element
from kqcircuits.elements.airbridges.airbridge import Airbridge
[docs]
@add_parameters_from(Airbridge, "airbridge_type")
class WaveguideCoplanarSplitter(Element):
"""
The PCell declaration of a multiway waveguide splitter. The number of ports is defined by the length of the
parameter lists. Ports are labelled by letters starting from ``a``
.. MARKERS_FOR_PNG -9,4 -1,-8 9,9
"""
lengths = Param(pdt.TypeList, "Waveguide length per port, measured from origin", [11, 11, 11])
angles = Param(pdt.TypeList, "Angle of each port (degrees)", [0, 180, 270])
use_airbridges = Param(pdt.TypeBoolean, "Use airbridges at a distance from the centre", False)
bridge_distance = Param(pdt.TypeDouble, "Bridges distance from centre", 80)
a_list = Param(
pdt.TypeList,
"Center conductor widths",
[],
unit="[μm]",
docstring="List of center conductor widths for each port."
" If empty, self.a will be used for all ports instead. [μm]",
)
b_list = Param(
pdt.TypeList,
"Gap widths",
[],
unit="[μm]",
docstring="List of gap widths for each port." " If empty, self.b will be used for all ports instead. [μm]",
)
port_names = Param(pdt.TypeList, "Port names", ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"])
[docs]
def build(self):
gap_shapes = []
trace_shapes = []
avoidance_shapes = []
# Tolerance to make sure that the trace shape is larger than the gap shape after integer conversion
rounding_tolerance = 10 * self.layout.dbu
# Convert a, b to lists of right length
a_list = self.a_list if (len(self.a_list) > 0 and self.a_list[0] != "") else [self.a] * len(self.angles)
b_list = self.b_list if (len(self.b_list) > 0 and self.b_list[0] != "") else [self.b] * len(self.angles)
for length_str, angle_str, port_name, a, b in zip(self.lengths, self.angles, self.port_names, a_list, b_list):
angle_deg = float(angle_str)
angle_rad = radians(angle_deg)
length = float(length_str)
a = float(a)
b = float(b)
# Generate port shapes
gap_shapes.append(
self._get_port_shape(angle_rad=angle_rad, length=length, width=a + 2 * b).to_itype(self.layout.dbu)
)
trace_shapes.append(
self._get_port_shape(angle_rad=angle_rad, length=length + rounding_tolerance, width=a).to_itype(
self.layout.dbu
)
)
avoidance_shapes.append(
self._get_port_shape(
angle_rad=angle_rad, length=length + self.margin, width=a + 2 * b + 2 * self.margin
).to_itype(self.layout.dbu)
)
# Port refpoints
self.add_port(
port_name,
pya.DPoint(length * cos(angle_rad), length * sin(angle_rad)),
pya.DVector(self.r * cos(angle_rad), self.r * sin(angle_rad)),
)
# Waveguide length annotation
self.cell.shapes(self.get_layer("waveguide_path")).insert(
round_dpath_width(
pya.DPath([self.refpoints[f"port_{port_name}"], self.refpoints["base"]], a), self.layout.dbu
)
)
# Airbridges
if self.use_airbridges:
ab_trans = pya.DCplxTrans(
1, angle_deg, False, self.bridge_distance * cos(angle_rad), self.bridge_distance * sin(angle_rad)
)
ab_cell = self.add_element(Airbridge, pad_length=14, pad_extra=2)
self.insert_cell(ab_cell, ab_trans)
# Merge and insert shapes
self.cell.shapes(self.get_layer("base_metal_gap_wo_grid")).insert(
pya.Region(gap_shapes) - pya.Region(trace_shapes)
)
self.add_protection(pya.Region(avoidance_shapes).merged())
def _get_port_shape(self, angle_rad, length, width):
# Generate a shape consisting of a rectangle (length, width) starting at (0, 0), with a round cap at the origin
# side.
r = width / 2 # Radius of round cap
# Straight section
c = cos(angle_rad)
s = sin(angle_rad)
points = [
pya.DPoint(length * c - r * s, length * s + r * c),
pya.DPoint(length * c + r * s, length * s - r * c),
]
# Corner section
angles = [radians(float(angle)) for angle in self.angles]
prev_rad = min(2 * pi if a == angle_rad else (angle_rad - a) % (2 * pi) for a in angles)
next_rad = min(2 * pi if a == angle_rad else (a - angle_rad) % (2 * pi) for a in angles)
if prev_rad <= pi / 2:
dist = r * cos(prev_rad) / sin(prev_rad)
if length > dist:
points.append(pya.DPoint(dist * c + r * s, dist * s - r * c))
else:
points += arc_points(r, angle_rad - pi / 2, angle_rad - prev_rad, self.n)
points.append(pya.DPoint(0.0, 0.0))
if next_rad <= pi / 2:
dist = r * cos(next_rad) / sin(next_rad)
if length > dist:
points.append(pya.DPoint(dist * c - r * s, dist * s + r * c))
else:
points += arc_points(r, angle_rad + next_rad, angle_rad + pi / 2, self.n)
return pya.DPolygon(points)
[docs]
def t_cross_parameters(
a=Element.get_schema()["a"].default,
b=Element.get_schema()["b"].default,
a2=Element.a,
b2=Element.b,
length_extra=0,
length_extra_side=0,
**kwargs,
):
"""A utility function to easily produce T-cross splitter (old WaveguideCoplanarTCross).
Args:
a: Width of center conductor
b: Width of gap
a2: Center conductor width of the side waveguide
b2: Gap of the side waveguide
length_extra: Extra length
length_extra_side: Extra length of the side waveguide
Returns:
dictionary of parameters for WaveguideCoplanarSplitter
"""
length = a2 / 2 + b2 + length_extra
length2 = a / 2 + b + length_extra_side
return {
"lengths": [length, length, length2],
"angles": [0, 180, 270],
"a_list": [a, a, a2],
"b_list": [b, b, b2],
"port_names": ["right", "left", "bottom"],
**kwargs,
}