# 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).
import logging
from string import Template
[docs]
def apply_template(filename_template, filename_output, rules):
with open(filename_template, encoding="utf-8") as filein:
src = Template(filein.read())
results = src.substitute(rules)
with open(filename_output, "w", encoding="utf-8") as fileout:
fileout.write(results)
# dirname_sondata = os.path.join(os.path.dirname(filename_output), "sondata")
# if not os.path.exists(dirname_sondata):
# os.mkdir(dirname_sondata)
# dirname_project = os.path.join(dirname_sondata, os.path.splitext(os.path.basename(filename_output))[0])
# if not os.path.exists(dirname_project):
# os.mkdir(dirname_project)
[docs]
def polygon_head(
nvertices, # number of vertices of the polygon
debugid, # unique number for sonnet internal debugging
ilevel=0, # sonnet layer number
mtype=-1, # metallization type index, -1 for lossless
filltype="N", # N for staircase, T for diagonal, V for conformal
xmin=1, # minimum subsection size
ymin=1, # minimum subsection size
xmax=100, # maximum subsection size
ymax=100, # maximum subsection size
conmax=0, # maximum length for conformal mesh subsection, 0 for auto
res=0, # reserved for sonnet future
edge_mesh="Y", # edge mesh on (Y) or off (N)
):
return (
f"{ilevel} {nvertices} {mtype} {filltype} {debugid} {xmin} {ymin} {xmax} {ymax} {conmax} {res} {res} "
f"{edge_mesh}\n"
)
[docs]
def symmetry(sym: bool = False):
sonnet_str = ""
if sym:
sonnet_str = "SYM"
return sonnet_str
[docs]
def box(
xwidth: float = 8000.0,
ywidth: float = 8000.0,
xcells: int = 8000,
ycells: int = 8000,
materials_type: str = "Si BT",
):
xcells2 = 2 * xcells
ycells2 = 2 * ycells
nsubs = 20 # placeholder for deprecated parameter
eeff = 0 # placeholder for deprecated parameter
materials = {
"Si RT": '3000 1 1 0 0 0 0 "vacuum"\n500 11.7 1 0 0 0 0 "Silicon (room temperature)"',
"Si BT": '3000 1 1 0 0 0 0 "vacuum"\n500 11.45 1 1e-006 0 0 0 "Silicon (10mK)"',
"SiOx+Si": '3000 1 1 0 0 0 0 "vacuum"\n0.55 3.78 11.7 1 0 0 0 "SiOx (10mK)"\n525 11.45 1 1e-06 0 0 0 "Si '
'(10mK)"',
"Si+Al": '3000 1 1 0 0 0 0 "vacuum"\n0.5 9.9 1 0.0001 0 0 0 "Alumina (99.5%)"\n0.45 1 1 0 0 0 0 "vacuum"'
'\n525 11.45 1 1e-06 0 0 0 "Si (10mK)"',
}[materials_type]
nlev = {"Si": 1, "Si BT": 1, "SiOx+Si": 2, "Si+Al": 3}[materials_type]
return f"BOX {nlev} {xwidth} {ywidth} {xcells2} {ycells2} {nsubs} {eeff}\n{materials}"
[docs]
def refplane(
position: str, length: int = 0, port_ipoly: str = "" # "LEFT" | "RIGHT" | "TOP" | "BOTTOM", # "LINK" or "FIX"
):
if port_ipoly != "":
plane_type = "LINK"
poly = f"POLY {port_ipoly[0]} 1\n0\n"
length = ""
else:
plane_type = "FIX"
poly = ""
return f"DRP1 {position} {plane_type} {length}\n{poly}"
[docs]
def refplanes(positions, length, port_ipolys):
sonnet_str = ""
for i, pos in enumerate(positions):
sonnet_str += refplane(pos, length, port_ipolys[i])
return sonnet_str
[docs]
def port(
portnum,
ipolygon,
ivertex,
port_type="STD", # STD for standard | AGND autogrounded | CUP cocalibrated
xcord=0, # pylint: disable=unused-argument
ycord=0, # pylint: disable=unused-argument
group="",
resist=50,
react=0,
induct=0,
capac=0,
):
if group:
group = '"' + group + '"'
logging.info(locals())
return f"POR1 {port_type} {group}\nPOLY {ipolygon} 1\n{ivertex}\n{portnum} {resist} {react} {induct} {capac}\n"
# {xcord} {ycord} [reftype rpcallen]
# def ports(shapes):
# sonnet_str = ""
# polygons = 0
#
# # FIXME Maybe the shapes will not have the same indexes as polygons in the region!
# for shape in shapes.each():
# if shape:
# polygons += 1
# ivertex = shape.property("sonnet_port_edge")
# portnum = shape.property("sonnet_port_nr")
# if ivertex!=None and portnum!=None:
# sonnet_str += port(ipolygon=polygons-1, portnum=portnum, ivertex=ivertex)
#
# return sonnet_str
[docs]
def control(control_type):
return {
"Simple": "SIMPLE", # Linear frequency sweep
"ABS": "ABS", # Sonnet guesses the resonances, simulates about 5 points around the resonance and interpolates
# the rest
"Sweep": "VARSWP",
}[control_type]
[docs]
def polygons(polygons, v, dbu, ilevel, fill_type):
sonnet_str = f"NUM {len(polygons)}\n"
for i, hole_poly in enumerate(polygons):
poly = hole_poly.resolved_holes()
if hasattr(poly, "isVia"):
sonnet_str += via(poly, debugid=i, ilevel=next(ilevel))
else:
sonnet_str += polygon_head(
nvertices=poly.num_points_hull() + 1, debugid=i + 1, ilevel=next(ilevel), filltype=fill_type
) # "Debugid" is actually used for mapping ports to polygons, 0 is
# not allowed
for _, point in enumerate(poly.each_point_hull()):
# sonnet Y-coordinate goes in the other direction
sonnet_str += f"{point.x * dbu + v.x} {-(point.y * dbu + v.y)}\n"
point = next(poly.each_point_hull()) # first point again to close the polygon
sonnet_str += f"{point.x * dbu + v.x} {-(point.y * dbu + v.y)}\nEND\n"
return sonnet_str
[docs]
def via(poly, debugid, ilevel):
via_head = polygon_head(nvertices=poly.num_points_hull() + 1, debugid=debugid, ilevel=ilevel, mtype=0)
return "VIA POLYGON\n" + via_head + "TOLEVEL 1 RING COVERS\n"