Coverage for qpdk / models / cpw.py: 96%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-02 17:50 +0000

1r"""Coplanar waveguide (CPW) and microstrip electromagnetic analysis. 

2 

3This module provides JAX-jittable functions for computing the characteristic 

4impedance, effective permittivity, and propagation constant of coplanar 

5waveguides and microstrip lines. All results are obtained analytically so 

6the functions compose freely with JAX transformations (``jit``, ``grad``, 

7``vmap``, …). 

8 

9The electromagnetic core functions are provided by :mod:`sax.models.rf` and 

10re-exported here for convenience. This module adds layout-to-model helpers 

11that extract physical dimensions from the qpdk layer stack and cross-section 

12specifications. 

13 

14CPW Theory 

15---------- 

16The quasi-static CPW analysis follows the conformal-mapping approach 

17described by Simons :cite:`simonsCoplanarWaveguideCircuits2001` (ch. 2) and 

18Ghione & Naldi :cite:`ghioneAnalyticalFormulasCoplanar1984`. 

19Conductor thickness corrections use the first-order formulae of 

20Gupta, Garg, Bahl & Bhartia :cite:`guptaMicrostripLinesSlotlines1996` 

21(§7.3, Eqs. 7.98-7.100). 

22 

23Microstrip Theory 

24----------------- 

25The microstrip analysis uses the Hammerstad-Jensen 

26:cite:`hammerstadAccurateModelsMicrostrip1980` closed-form expressions for 

27effective permittivity and characteristic impedance, as presented in 

28Pozar :cite:`m.pozarMicrowaveEngineering2012` (ch. 3, §3.8). 

29 

30General 

31------- 

32The ABCD-to-S-parameter conversion is the standard microwave-network 

33relation from Pozar :cite:`m.pozarMicrowaveEngineering2012` (ch. 4). 

34 

35The implementation was cross-checked against the Qucs-S model 

36(see `Qucs technical documentation`_, §12 for CPW, §11 for microstrip). 

37 

38.. _Qucs technical documentation: 

39 https://qucs.sourceforge.net/docs/technical/technical.pdf 

40 

41Functions 

42--------- 

43All geometry parameters are in **SI base units** (metres, etc.) unless 

44noted otherwise. Frequency is in **Hz**. 

45""" 

46 

47from functools import cache 

48from typing import cast 

49 

50import gdsfactory as gf 

51import jax.numpy as jnp 

52from gdsfactory.typings import CrossSectionSpec 

53from jax.typing import ArrayLike 

54from sax.models.rf import ( 

55 cpw_epsilon_eff, 

56 cpw_thickness_correction, 

57 cpw_z0, 

58 microstrip_epsilon_eff, 

59 microstrip_thickness_correction, 

60 microstrip_z0, 

61 propagation_constant, 

62 transmission_line_s_params, 

63) 

64 

65from qpdk.tech import LAYER_STACK, get_etch_section, material_properties 

66 

67__all__ = [ 

68 "cpw_ep_r_from_cross_section", 

69 "cpw_epsilon_eff", 

70 "cpw_parameters", 

71 "cpw_thickness_correction", 

72 "cpw_z0", 

73 "cpw_z0_from_cross_section", 

74 "get_cpw_dimensions", 

75 "get_cpw_substrate_params", 

76 "microstrip_epsilon_eff", 

77 "microstrip_thickness_correction", 

78 "microstrip_z0", 

79 "propagation_constant", 

80 "transmission_line_s_params", 

81] 

82 

83 

84# =================================================================== 

85# Layout-to-Model Helpers 

86# =================================================================== 

87 

88 

89@cache 

90def get_cpw_substrate_params() -> tuple[float, float, float]: 

91 """Extract substrate parameters from the PDK layer stack. 

92 

93 Returns: 

94 ``(h, t, ep_r)`` — substrate height (µm), conductor thickness (µm), 

95 and relative permittivity. 

96 """ 

97 h = LAYER_STACK.layers["Substrate"].thickness # µm 

98 t = LAYER_STACK.layers["M1"].thickness # µm 

99 ep_r = material_properties[cast(str, LAYER_STACK.layers["Substrate"].material)][ 

100 "relative_permittivity" 

101 ] 

102 return float(h), float(t), float(ep_r) 

103 

104 

105def get_cpw_dimensions( 

106 cross_section: CrossSectionSpec, **kwargs 

107) -> tuple[float, float]: 

108 """Extracts CPW width and gap from a cross-section specification. 

109 

110 Args: 

111 cross_section: A gdsfactory cross-section specification. 

112 **kwargs: Additional keyword arguments passed to `gf.get_cross_section`. 

113 

114 Returns: 

115 tuple[float, float]: Width and gap of the CPW. 

116 """ 

117 # Make sure a PDK is activated 

118 from qpdk import PDK # noqa: PLC0415 

119 

120 PDK.activate() 

121 xs = gf.get_cross_section(cross_section, **kwargs) 

122 

123 width = xs.width 

124 etch_section = get_etch_section(xs) 

125 return width, etch_section.width 

126 

127 

128@cache 

129def cpw_parameters( 

130 width: float, 

131 gap: float, 

132) -> tuple[float, float]: 

133 r"""Compute effective permittivity and characteristic impedance for a CPW. 

134 

135 Uses the JAX-jittable functions from :mod:`sax.models.rf` with the 

136 PDK layer stack (substrate height, conductor thickness, material 

137 permittivity). 

138 

139 Conductor thickness corrections follow 

140 Gupta, Garg, Bahl & Bhartia :cite:`guptaMicrostripLinesSlotlines1996` 

141 (§7.3, Eqs. 7.98-7.100). 

142 

143 Args: 

144 width: Centre-conductor width in µm. 

145 gap: Gap between centre conductor and ground plane in µm. 

146 

147 Returns: 

148 ``(ep_eff, z0)`` — effective permittivity (dimensionless) and 

149 characteristic impedance (Ω). 

150 """ 

151 width = float(width) 

152 gap = float(gap) 

153 

154 h_um, t_um, ep_r = get_cpw_substrate_params() 

155 

156 # Convert to SI (metres) 

157 w_m = width * 1e-6 

158 s_m = gap * 1e-6 

159 h_m = h_um * 1e-6 

160 t_m = t_um * 1e-6 

161 

162 # Base (zero-thickness) quantities 

163 ep_eff = cpw_epsilon_eff(w_m, s_m, h_m, ep_r) 

164 

165 if t_um > 0: 

166 ep_eff_t, z0_val = cpw_thickness_correction(w_m, s_m, t_m, ep_eff) 

167 return float(ep_eff_t), float(z0_val) 

168 

169 z0_val = cpw_z0(w_m, s_m, ep_eff) 

170 return float(ep_eff), float(z0_val) 

171 

172 

173def cpw_z0_from_cross_section( 

174 cross_section: CrossSectionSpec, 

175 f: ArrayLike | None = None, 

176) -> jnp.ndarray: 

177 """Characteristic impedance of a CPW defined by a layout cross-section. 

178 

179 Args: 

180 cross_section: A gdsfactory cross-section specification. 

181 f: Frequency array (Hz). Used only to determine the output shape; 

182 the impedance is frequency-independent in the quasi-static model. 

183 

184 Returns: 

185 Characteristic impedance broadcast to the shape of *f* (Ω). 

186 """ 

187 width, gap = get_cpw_dimensions(cross_section) 

188 _ep_eff, z0_val = cpw_parameters(width, gap) 

189 z0 = jnp.asarray(z0_val) 

190 if f is not None: 

191 f = jnp.asarray(f) 

192 z0 = jnp.broadcast_to(z0, f.shape) 

193 return z0 

194 

195 

196def cpw_ep_r_from_cross_section( 

197 cross_section: CrossSectionSpec, # noqa: ARG001 

198) -> float: 

199 r"""Substrate relative permittivity for a given cross-section. 

200 

201 .. note:: 

202 The substrate permittivity is determined by the PDK layer stack 

203 (``LAYER_STACK["Substrate"]``), not by the cross-section geometry. 

204 All CPW cross-sections on the same substrate share the same 

205 :math:`\varepsilon_r`. The *cross_section* parameter is accepted 

206 for API symmetry with :func:`cpw_z0_from_cross_section`. 

207 

208 Args: 

209 cross_section: A gdsfactory cross-section specification. 

210 

211 Returns: 

212 Relative permittivity of the substrate. 

213 """ 

214 _h, _t, ep_r = get_cpw_substrate_params() 

215 return ep_r