Coverage for qpdk / samples / simulate_resonator.py: 100%

24 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-14 10:27 +0000

1# --- 

2# jupyter: 

3# jupytext: 

4# text_representation: 

5# extension: .py 

6# format_name: percent 

7# format_version: '1.3' 

8# jupytext_version: 1.17.3 

9# --- 

10 

11# %% [markdown] 

12# # Export a resonator for simulation 

13# 

14 

15# %% 

16 

17import gdsfactory as gf 

18import numpy as np 

19from gdsfactory.export import to_stl 

20 

21from qpdk.cells.airbridge import cpw_with_airbridges 

22from qpdk.cells.launcher import launcher 

23from qpdk.cells.resonator import resonator_coupled 

24from qpdk.config import PATH 

25from qpdk.tech import LAYER, route_bundle_sbend_cpw 

26 

27# %% [markdown] 

28# ## System geometry 

29# 

30# Create a simulation layout with a resonator coupled to two probeline launchers. 

31# 

32# The layout consists of: 

33# - A coupled resonator (with open start and specified coupling length) 

34# - Two launcher components (mirrored and positioned symmetrically) 

35# - CPW routes with airbridges connecting launchers to resonator coupling ports 

36# - A simulation area layer enlarged around the layout 

37# - Ports added for both launchers with prefixes 

38 

39 

40# %% 

41@gf.cell 

42def resonator_simulation(coupling_gap: float = 12.0) -> gf.Component: 

43 """Create a resonator simulation layout with launchers and CPW routes.""" 

44 c = gf.Component() 

45 

46 res_ref = c << resonator_coupled( 

47 open_start=True, coupling_straight_length=300, coupling_gap=coupling_gap 

48 ) 

49 res_ref.movex(-res_ref.size_info.width / 4) 

50 

51 launcher_left = c << launcher() 

52 launcher_right = c << launcher() 

53 launcher_right.mirror() 

54 

55 width_offset = res_ref.size_info.width + 700 

56 launcher_left.move((-width_offset, 0)) 

57 launcher_right.move((width_offset, 0)) 

58 

59 route_bundle_sbend_cpw( 

60 c, 

61 [launcher_left["o1"], launcher_right["o1"]], 

62 [res_ref["coupling_o1"], res_ref["coupling_o2"]], 

63 cross_section=cpw_with_airbridges( 

64 airbridge_spacing=250.0, airbridge_padding=20.0 

65 ), 

66 ) 

67 

68 c.kdb_cell.shapes(LAYER.SIM_AREA).insert(c.bbox().enlarged(0, 100)) 

69 

70 c.add_ports(launcher_left.ports, prefix="left_") 

71 c.add_ports(launcher_right.ports, prefix="right_") 

72 

73 return c 

74 

75 

76if __name__ == "__main__": 

77 from qpdk import PDK 

78 

79 PDK.activate() 

80 

81 # Create and display the filled version 

82 c = gf.Component("resonator_simulation") 

83 res_ref = c << resonator_simulation() 

84 c.add_ports(res_ref.ports) 

85 c.plot( 

86 pixel_buffer_options=dict(width=1300, height=1000, oversampling=2, linewidth=3) 

87 ) 

88 c.show() 

89 

90 # Export 3D model 

91 to_stl( 

92 c, 

93 PATH.simulation / "resonator_simulations.stl", 

94 layer_stack=PDK.layer_stack, 

95 hull_invalid_polygons=True, 

96 ) 

97 

98 material_spec = { 

99 "Si": {"relative_permittivity": 11.45}, 

100 "Nb": {"relative_permittivity": np.inf}, 

101 "vacuum": {"relative_permittivity": 1}, 

102 } 

103 

104 # TODO implement running simulations here 

105 

106 # from gplugins.palace import run_scattering_simulation_palace 

107 # 

108 # results = run_scattering_simulation_palace( 

109 # c, 

110 # layer_stack=PDK.layer_stack, 

111 # material_spec=material_spec, 

112 # only_one_port=True, 

113 # driven_settings={ 

114 # "MinFreq": 0.1, 

115 # "MaxFreq": 5, 

116 # "FreqStep": 5, 

117 # }, 

118 # n_processes=1, 

119 # simulation_folder=Path().cwd() / "temporary", 

120 # mesh_parameters=dict( 

121 # background_tag="vacuum", 

122 # background_padding=(0,) * 5 + (700,), 

123 # port_names=[port.name for port in c.ports], 

124 # default_characteristic_length=200, 

125 # resolutions={ 

126 # "M1": { 

127 # "resolution": 15, 

128 # }, 

129 # "Silicon": { 

130 # "resolution": 40, 

131 # }, 

132 # "vacuum": { 

133 # "resolution": 40, 

134 # }, 

135 # **{ 

136 # f"M1__{port}": { # `__` is used as the layer to port delimiter for Elmer 

137 # "resolution": 20, 

138 # "DistMax": 30, 

139 # "DistMin": 10, 

140 # "SizeMax": 14, 

141 # "SizeMin": 3, 

142 # } 

143 # for port in c.ports 

144 # }, 

145 # }, 

146 # ), 

147 # ) 

148 # 

149 # display(results) 

150 

151 # 

152 # df = results.scattering_matrix 

153 # df.columns = df.columns.str.strip() 

154 # s_complex = 10 ** (df["|S[2][1]| (dB)"].values / 20) * np.exp( 

155 # 1j * np.deg2rad(df["arg(S[2][1]) (deg.)"].values) 

156 # ) 

157 # # Assuming a 1-port reflection measurement for capacitance extraction 

158 # y11 = (1 - s_complex) / (1 + s_complex) / 50.0 

159 # freq_hz = df["f (GHz)"].values * 1e9 

160 # cap = np.imag(y11) / (freq_hz * 2 * np.pi) 

161 # display(cap) 

162 # 

163 # plt.plot(freq_hz / 1e9, cap * 1e15) 

164 # plt.xlabel("Freq (GHz)") 

165 # plt.ylabel("C (fF)") 

166 # 

167 # if results.field_file_locations: 

168 # pv.start_xvfb() 

169 # pv.set_jupyter_backend("trame") 

170 # field = pv.read(results.field_file_locations[0]) 

171 # slice = field.slice_orthogonal(z=layer_stack.layers["bw"].zmin * 1e-6) 

172 # 

173 # p = pv.Plotter() 

174 # p.add_mesh(slice, scalars="Ue", cmap="turbo") 

175 # p.show_grid() 

176 # p.camera_position = "xy" 

177 # p.enable_parallel_projection() 

178 # p.show()