Running Palace#
[1]:
import numpy as np
from math import pi
import yaml
from matplotlib import pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format='retina'
[2]:
from zeroheliumkit import Rectangle, Anchor, Structure, Skeletone, Layer
from zeroheliumkit.src.settings import *
from zeroheliumkit.src.geometries import Meander, Rectangle, ArcLine
from zeroheliumkit.helpers.resonator_calc import CPW_params
from zeroheliumkit.fem import GMSHmaker, ExtrudeSettings, SurfaceSettings, PECSettings, MeshSettings, BoxFieldMeshSettings, DistanceFieldMeshSettings
[3]:
um = 1e-6
GHz = 1e9
plot_config = {
"wafer": LIGHTGRAY,
"gnd": BLUE,
"open": ORANGE,
"temp": WHITE,
"island": YELLOW2,
"ports": RED,
"anchors": RED
}
[4]:
def construct_resonator_skeletone(params: dict) -> LineString:
g_params = params["geometry"]
d_a = g_params['coupling_length']
d_w = g_params['distance_from_waveguide']
d_l = g_params['openend_length']
d_R = g_params['radius']
n = g_params['num']
w = g_params["w"]
g = g_params["g"]
cpw_params = CPW_params(params["eps"], w * um, g * um, params["substrate_h"] * um, 0)
cpw_params.resonator_length(params["frequency"] * GHz, params["type"], 0)
length = cpw_params.length/um
d_b = (length - (d_l + d_a + d_w + d_R * (3 * pi/2 + 2 * pi * n + 1)))/(2 * n + 1)
if d_b < 0:
raise Exception("Incorrect geometry input. Choose different 'bending_radius', 'coupling_length', 'openend_length' or 'num_wiggles'")
sk = Skeletone()
sk.add(LineString([(0, 0), (0, d_l)]))
sk.add(ArcLine(-d_R, 0, d_R, 0, 90, 6))
sk.add(LineString([(0, 0), (-d_a, 0)]))
sk.add(ArcLine(0, -d_R, d_R, 90, 180, 6))
sk.add(LineString([(0, 0), (0, -d_w)]))
sk.add(ArcLine(d_R, 0, d_R, 180, 270, 6))
sk.add(LineString([(0, 0), (d_b/2, 0)]), ignore_crossing=True)
for _ in range(n):
sk.add(Meander(d_b, d_R, direction=-90, num_segments=12), ignore_crossing=True)
sk.add(LineString([(0, 0), (d_b/2 + d_R, 0)]))
return sk
[5]:
### Creating Geometry
[6]:
with open('params.yaml', 'r') as file:
params = yaml.safe_load(file)
gp = params["resonator"]["geometry"]
fc = params["feedline"]
d = Structure()
d.skeletone = construct_resonator_skeletone(params["resonator"])
d.add(Layer("op", d.skeletone.buffer(gp["w"]/2 + gp["g"], cap_style="square", join_style="mitre")))
# d.buffer_line("op", gp["w"]/2 + gp["g"], cap_style="square", join_style="mitre")
d.skeletone.add(LineString([(0, 0), (10, 0)]))
d.add(Layer("temp", d.skeletone.buffer(gp["w"]/2, cap_style="square", join_style="mitre")))
# d.buffer_line("temp", gp["w"]/2, cap_style="square", join_style="mitre")
d.anchors.add([Anchor((0,0), -90, "end")])
island = Structure()
island.add(Layer("isl", Rectangle(100, 20, (0,0))))
island.isl.add(Rectangle(20, 100, (0,0)))
island.add(Layer("op", island.isl.buffer(5, join_style='mitre')))
island.anchors.add([Anchor((0, 57.5), -90, "i1")])
d.append(island, anchoring=("end", "i1"))
d.op.add(Rectangle(60, 40, (0, 45 - 57.5)))
d.op.cut(Rectangle(15, 30, (20, 45 - 57.5)))
d.op.cut(Rectangle(15, 30, (-20, 45 - 57.5)))
d.op.cut(Rectangle(55, 8, (0, 0)))
d.op.cut(d.temp.polygons)
d.remove("temp")
d.anchors.remove("isl", "end")
d.add(Layer("wafer", Rectangle(700, 1600, (-200, 600))))
d.add(Layer("gnd", Rectangle(700, 1600, (-200, 600))))
d.gnd.cut(d.op.polygons)
d.remove("op")
bbox = d.skeletone.lines.bounds
y_offset = 30
x_offset = 130
y_feedline = bbox[3] + y_offset
fdl = Structure()
fdl.skeletone.add(LineString([(bbox[0] - x_offset, y_feedline), (bbox[2] + x_offset, y_feedline)]))
fdl.add(Layer("gnd", fdl.skeletone.buffer(fc["w"]/2 + fc["g"], cap_style="square", join_style="mitre")))
fdl.gnd.cut(fdl.skeletone.buffer(fc["w"]/2, cap_style="square", join_style="mitre"))
d.gnd.cut(fdl.gnd.polygons)
d.add(Layer("ports", Rectangle(4, 200, (bbox[0] - x_offset, y_feedline))))
d.ports.add(Rectangle(4, 200, (bbox[2] + x_offset, y_feedline)))
d.ports.cut(d.gnd.polygons)
d.gnd.add(d.isl.polygons)
bbox_island = d.isl.polygons.bounds
# adding gnd to the island
d.gnd.add(Rectangle(5, 10, (bbox_island[0]/2 + bbox_island[2]/2, bbox_island[1])))
d.remove("isl")
d.skeletone.add(fdl.skeletone.lines, chaining=False)
msh_lines = d.skeletone
d.gnd.remove_holes(cut_position=-80)
d.quickplot(color_config=plot_config, show_idx=True)
------- ---------- --------- ------- --- ------- -------------- ------- -------
f0, GHz length, mm width, um gap, um eps eps_eff impedance, Ohm L, nH/m C, pF/m
7.7 3.974 8.0 4.5 11 6.0 52.57 429.52 155.423
------- ---------- --------- ------- --- ------- -------------- ------- -------
[6]:
<Axes: >
[7]:
### Creating Mesh
[8]:
d_wafer = 200
d_vac = 200
d_metal = 100
msh_filename = 'cpw'
save_dir = 'postpro/'
[9]:
Volumes = {
"wafer": ExtrudeSettings(d.wafer.polygons, -d_wafer, d_wafer, "DIELECTRIC"),
# "top": ExtrudeSettings(d.top, 0, d_metal, "METAL"),
"vacuum": ExtrudeSettings(d.wafer.polygons, 0, d_vac, "VACUUM")
}
Surfaces = {
"s0": SurfaceSettings(d.gnd.polygons, 0),
"s1": SurfaceSettings(d.ports.polygons, 0)
}
PECs = {
"conductor": PECSettings(d.gnd.polygons, [0,1,2,3], z=0),
"port1+": PECSettings(d.ports.polygons, [0], z=0),
"port1-": PECSettings(d.ports.polygons, [1], z=0),
"port2+": PECSettings(d.ports.polygons, [2], z=0),
"port2-": PECSettings(d.ports.polygons, [3], z=0)
}
mesh = MeshSettings(
dim = 3,
fields = {"Box": [BoxFieldMeshSettings(Thickness=400, VIn=1000, VOut=2000, box=[-10000, 10000, -10000, 10000, -1000, 1000])],
"Distance": [DistanceFieldMeshSettings(geometry=msh_lines.lines, base_z=0, sampling=100, SizeMin=10, SizeMax=80, DistMin=10, DistMax=30)]},
)
[10]:
mshmkr = GMSHmaker(
extrude = Volumes,
surfaces = Surfaces,
pecs = PECs,
mesh = mesh,
save = {"dir": save_dir, "filename": msh_filename},
open_gmsh = False,
debug_mode = False
)
on 0: mesh is constructed
on 0: mesh saved
Gmsh generation |███| 1/1 [100%] in 1.5s (0.66/s)
[11]:
mshmkr.print_physical()
Volume ID
---------- ----
VACUUM 1
DIELECTRIC 2
#-----------------------------------
Surface ID
--------- ----
conductor 3
port1+ 4
port1- 5
port2+ 6
port2- 7
Below is an example of created geometry and a mesh using GMSH.

[ ]:
### Configuring Palace Simulation
[12]:
from zeroheliumkit.fem.palacer import (PalaceRunner, PalaceConfig, ProblemConfig, ModelConfig,
MaterialsConfig, PostProEnergyConfig, PostProProbeConfig,
DomainConfig, ElementConfig, LumpedPortConfig, BoundaryConfig,
DrivenConfig, EigenConfig, SolverConfig)
[ ]:
### Frequency sweep
[13]:
problem_cfg = ProblemConfig(Type="Driven", Verbose=2, Output="postpro/")
# model_cfg = ModelConfig(Mesh="postpro/geo/cpw.msh", L0=1.0e-6, Refinement={"Tol": 1.0e-2, "MaxIts": 10, "MaxSize": 1e6})
model_cfg = ModelConfig(Mesh="postpro/geo/cpw.msh", L0=1.0e-6, Refinement={})
air = MaterialsConfig(Attributes=[2], Permeability=1.0, Permittivity=1.0, LossTan=0.0)
silicon = MaterialsConfig(Attributes=[1], Permeability=1.0, Permittivity=11, LossTan=0.0)
postpro = {"Energy": [PostProEnergyConfig(Index=1, Attributes=[1])],
}
domain_cfg = DomainConfig(Materials=[air, silicon], Postprocessing=postpro)
lp1_cfg = LumpedPortConfig(Index=1, R=50.0, Excitation=1, Elements=[ElementConfig([4],"+Y"), ElementConfig([5],"-Y")])
lp2_cfg = LumpedPortConfig(Index=2, R=50.0, Excitation=0, Elements=[ElementConfig([6],"+Y"), ElementConfig([7],"-Y")])
boundary_cfg = BoundaryConfig(PEC={"Attributes": [3]},
LumpedPort=[lp1_cfg, lp2_cfg])
f1 = 7.0
f2 = 7.4
df = (f2 - f1)/100
solver_cfg = SolverConfig(Order=2, Device="CPU", Driven=DrivenConfig(MinFreq=f1, MaxFreq=f2, FreqStep=df, Save=[f1,f2], AdaptiveTol=1.0e-3))
[14]:
palace_app_path = "/Users/helium/spack/opt/spack/darwin-m1/palace-0.14.0-2v4weqz6drx675yhbuoyu2nmnqolzf5n/bin/palace"
palace_cfg = PalaceConfig(Problem=problem_cfg,
Model=model_cfg,
Domains=domain_cfg,
Boundaries=boundary_cfg,
Solver=solver_cfg)
pal = PalaceRunner(json_name="fsweep", config=palace_cfg, exec_path=palace_app_path)
[15]:
# this has been tested only on macs, windows may have issues with multiprocessing
# json config file is already created, refer to AWS Palace website for more information
pal.run(multicore=8)
tab 1 of window id 13378
[16]:
data = np.loadtxt('postpro/port-S.csv', skiprows=1, delimiter=',')
plt.figure(figsize=(8,3))
ax1 = plt.subplot(121)
ax1.plot(data[:,0], data[:,3], '-o')
ax1.set_xlabel('Frequency (GHz)')
ax1.set_ylabel('S21 Magnitude')
ax2 = plt.subplot(122)
ax2.plot(data[:,0], data[:,4], '-o')
ax2.set_xlabel('Frequency (GHz)')
ax2.set_ylabel('S21 Phase')
plt.tight_layout()
plt.show()
[ ]:
### Eigenfrequency calculations
[36]:
problem_cfg = ProblemConfig(Type="Eigenmode", Verbose=2, Output="postpro/")
model_cfg = ModelConfig(Mesh="postpro/cpw.msh2", L0=1.0e-6, Refinement={"Tol": 1.0e-2, "MaxIts": 10, "MaxSize": 1e6})
air = MaterialsConfig(Attributes=[1], Permeability=1.0, Permittivity=1.0, LossTan=0.0)
silicon = MaterialsConfig(Attributes=[2], Permeability=1.0, Permittivity=11, LossTan=0.0)
postpro = {"Energy": [PostProEnergyConfig(Index=1, Attributes=[1])],
}
domain_cfg = DomainConfig(Materials=[air, silicon], Postprocessing=postpro)
boundary_cfg = BoundaryConfig(PEC={"Attributes": [3]},
LumpedPort=[])
solver_cfg = SolverConfig(Order=2, Device="CPU", Eigenmode=EigenConfig(N=2, Tol=1e-6, Target=6.5, Save=2))
[37]:
palace_cfg = PalaceConfig(Problem=problem_cfg,
Model=model_cfg,
Domains=domain_cfg,
Boundaries=boundary_cfg,
Solver=solver_cfg)
pal = PalaceRunner(json_name="eigen", config=palace_cfg, exec_path=palace_app_path)
[6]:
eigen = np.loadtxt('postpro/eig.csv', skiprows=1, delimiter=',')
print("Eigenfrequencies (GHz):")
print(eigen[:,1])
Eigenfrequencies (GHz):
[ 7.35171928 16.86640487 22.08328345 32.6005022 36.81162642]
[38]:
pal.run(multicore=8)
tab 1 of window id 49926
Statistics |
Electric Field (first mode at 7.35171928 GHz) |
|---|---|
|
|

