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

24 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-02 17:50 +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 

45 Returns: 

46 The resonator simulation component. 

47 """ 

48 c = gf.Component() 

49 

50 res_ref = c << resonator_coupled( 

51 open_start=True, coupling_straight_length=300, coupling_gap=coupling_gap 

52 ) 

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

54 

55 launcher_left = c << launcher() 

56 launcher_right = c << launcher() 

57 launcher_right.mirror() 

58 

59 width_offset = res_ref.size_info.width + 700 

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

61 launcher_right.move((width_offset, 0)) 

62 

63 route_bundle_sbend_cpw( 

64 c, 

65 [launcher_left["o1"], launcher_right["o1"]], 

66 [res_ref["coupling_o1"], res_ref["coupling_o2"]], 

67 cross_section=cpw_with_airbridges( 

68 airbridge_spacing=250.0, airbridge_padding=20.0 

69 ), 

70 ) 

71 

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

73 

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

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

76 

77 return c 

78 

79 

80if __name__ == "__main__": 

81 from qpdk import PDK 

82 

83 PDK.activate() 

84 

85 # Create and display the filled version 

86 c = gf.Component("resonator_simulation") 

87 res_ref = c << resonator_simulation() 

88 c.add_ports(res_ref.ports) 

89 c.plot( 

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

91 ) 

92 c.show() 

93 

94 # Export 3D model 

95 to_stl( 

96 c, 

97 PATH.simulation / "resonator_simulations.stl", 

98 layer_stack=PDK.layer_stack, 

99 hull_invalid_polygons=True, 

100 ) 

101 

102 material_spec = { 

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

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

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

106 } 

107 

108 # TODO implement running simulations here 

109 

110 # from gplugins.palace import run_scattering_simulation_palace 

111 # 

112 # results = run_scattering_simulation_palace( 

113 # c, 

114 # layer_stack=PDK.layer_stack, 

115 # material_spec=material_spec, 

116 # only_one_port=True, 

117 # driven_settings={ 

118 # "MinFreq": 0.1, 

119 # "MaxFreq": 5, 

120 # "FreqStep": 5, 

121 # }, 

122 # n_processes=1, 

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

124 # mesh_parameters=dict( 

125 # background_tag="vacuum", 

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

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

128 # default_characteristic_length=200, 

129 # resolutions={ 

130 # "M1": { 

131 # "resolution": 15, 

132 # }, 

133 # "Silicon": { 

134 # "resolution": 40, 

135 # }, 

136 # "vacuum": { 

137 # "resolution": 40, 

138 # }, 

139 # **{ 

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

141 # "resolution": 20, 

142 # "DistMax": 30, 

143 # "DistMin": 10, 

144 # "SizeMax": 14, 

145 # "SizeMin": 3, 

146 # } 

147 # for port in c.ports 

148 # }, 

149 # }, 

150 # ), 

151 # ) 

152 # 

153 # display(results) 

154 

155 # 

156 # df = results.scattering_matrix 

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

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

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

160 # ) 

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

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

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

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

165 # display(cap) 

166 # 

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

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

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

170 # 

171 # if results.field_file_locations: 

172 # pv.start_xvfb() 

173 # pv.set_jupyter_backend("trame") 

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

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

176 # 

177 # p = pv.Plotter() 

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

179 # p.show_grid() 

180 # p.camera_position = "xy" 

181 # p.enable_parallel_projection() 

182 # p.show()