Coverage for qpdk / models / cpw.py: 99%
151 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-14 10:27 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-14 10:27 +0000
1r"""Coplanar waveguide (CPW) and microstrip electromagnetic analysis.
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``, …).
9CPW Theory
10----------
11The quasi-static CPW analysis follows the conformal-mapping approach
12described by Simons :cite:`simonsCoplanarWaveguideCircuits2001` (ch. 2) and
13Ghione & Naldi :cite:`ghioneAnalyticalFormulasCoplanar1984`.
14Conductor thickness corrections use the first-order formulae of
15Gupta, Garg, Bahl & Bhartia :cite:`guptaMicrostripLinesSlotlines1996`
16(§7.3, Eqs. 7.98-7.100).
18Microstrip Theory
19-----------------
20The microstrip analysis uses the Hammerstad-Jensen
21:cite:`hammerstadAccurateModelsMicrostrip1980` closed-form expressions for
22effective permittivity and characteristic impedance, as presented in
23Pozar :cite:`m.pozarMicrowaveEngineering2012` (ch. 3, §3.8).
25General
26-------
27The ABCD-to-S-parameter conversion is the standard microwave-network
28relation from Pozar :cite:`m.pozarMicrowaveEngineering2012` (ch. 4).
30The implementation was cross-checked against the Qucs-S model
31(see `Qucs technical documentation`_, §12 for CPW, §11 for microstrip).
33.. _Qucs technical documentation:
34 https://qucs.sourceforge.net/docs/technical/technical.pdf
36Functions
37---------
38All geometry parameters are in **SI base units** (metres, etc.) unless
39noted otherwise. Frequency is in **Hz**.
40"""
42from functools import cache, partial
43from typing import cast
45import gdsfactory as gf
46import jax
47import jax.numpy as jnp
48from gdsfactory.typings import CrossSectionSpec
49from jax.typing import ArrayLike
51from qpdk.models.constants import c_0, π
52from qpdk.models.math import ellipk_ratio
53from qpdk.tech import LAYER_STACK, material_properties
55# ===================================================================
56# Coplanar Waveguide (CPW)
57# ===================================================================
60@partial(jax.jit, inline=True)
61def cpw_epsilon_eff(
62 w: ArrayLike,
63 s: ArrayLike,
64 h: ArrayLike,
65 ep_r: ArrayLike,
66) -> jax.Array:
67 r"""Effective permittivity of a CPW on a finite-height substrate.
69 Uses conformal mapping
70 (Simons :cite:`simonsCoplanarWaveguideCircuits2001`, Eq. 2.37;
71 Ghione & Naldi :cite:`ghioneAnalyticalFormulasCoplanar1984`):
73 .. math::
75 \begin{aligned}
76 k_0 &= \frac{w}{w + 2s} \\
77 k_1 &= \frac{\sinh(\pi w / 4h)}
78 {\sinh\bigl(\pi(w + 2s) / 4h\bigr)} \\
79 q_1 &= \frac{K(k_1^2)\,/\,K(1 - k_1^2)}
80 {K(k_0^2)\,/\,K(1 - k_0^2)} \\
81 \varepsilon_{\mathrm{eff}}
82 &= 1 + \frac{q_1\,(\varepsilon_r - 1)}{2}
83 \end{aligned}
85 where :math:`K` is the complete elliptic integral of the first kind in
86 the *parameter* convention (:math:`m = k^2`).
88 Args:
89 w: Centre-conductor width (m).
90 s: Gap to ground plane (m).
91 h: Substrate height (m).
92 ep_r: Relative permittivity of the substrate.
94 Returns:
95 Effective permittivity (dimensionless).
96 """
97 w = jnp.asarray(w, dtype=float)
98 s = jnp.asarray(s, dtype=float)
99 h = jnp.asarray(h, dtype=float)
100 ε_r = jnp.asarray(ep_r, dtype=float)
102 # Free-space modulus
103 k0 = w / (w + 2.0 * s)
105 # Substrate-corrected modulus
106 k1 = jnp.sinh(π * w / (4.0 * h)) / jnp.sinh(π * (w + 2.0 * s) / (4.0 * h))
108 # Filling factor q₁ = [K(k₁)/K(k₁')] / [K(k₀)/K(k₀')]
109 q1 = ellipk_ratio(k1**2) / ellipk_ratio(k0**2)
111 return 1.0 + q1 * (ε_r - 1.0) / 2.0
114@partial(jax.jit, inline=True)
115def cpw_z0(
116 w: ArrayLike,
117 s: ArrayLike,
118 ep_eff: ArrayLike,
119) -> jax.Array:
120 r"""Characteristic impedance of a CPW.
122 .. math::
124 Z_0 = \frac{30\,\pi}
125 {\sqrt{\varepsilon_{\mathrm{eff}}}\;
126 K(k_0^2)\,/\,K(1 - k_0^2)}
128 (Simons :cite:`simonsCoplanarWaveguideCircuits2001`, Eq. 2.38.)
130 Note that our :math:`w` and :math:`s` correspond to Simons' :math:`s` and :math:`w`, respectively.
132 Args:
133 w: Centre-conductor width (m).
134 s: Gap to ground plane (m).
135 ep_eff: Effective permittivity (see :func:`cpw_epsilon_eff`).
137 Returns:
138 Characteristic impedance (Ω).
139 """
140 w = jnp.asarray(w, dtype=float)
141 s = jnp.asarray(s, dtype=float)
142 ε_eff = jnp.asarray(ep_eff, dtype=float)
144 k0 = w / (w + 2.0 * s)
145 return 30.0 * π / (jnp.sqrt(ε_eff) * ellipk_ratio(k0**2))
148@partial(jax.jit, inline=True)
149def cpw_thickness_correction(
150 w: ArrayLike,
151 s: ArrayLike,
152 t: ArrayLike,
153 ep_eff: ArrayLike,
154) -> tuple[jax.Array, jax.Array]:
155 r"""Apply conductor thickness correction to CPW ε_eff and Z₀.
157 First-order correction from
158 Gupta, Garg, Bahl & Bhartia :cite:`guptaMicrostripLinesSlotlines1996`
159 (§7.3, Eqs. 7.98-7.100):
161 .. math::
163 \Delta &= \frac{1.25\,t}{\pi}
164 \left(1 + \ln\\frac{4\pi w}{t}\right) \\
165 k_e &= k_0 + (1 - k_0^2)\,\frac{\Delta}{2s} \\
166 \varepsilon_{\mathrm{eff},t}
167 &= \varepsilon_{\mathrm{eff}}
168 - \frac{0.7\,(\varepsilon_{\mathrm{eff}} - 1)\,t/s}
169 {K(k_0^2)/K(1-k_0^2) + 0.7\,t/s} \\
170 Z_{0,t} &= \frac{30\pi}
171 {\sqrt{\varepsilon_{\mathrm{eff},t}}\;
172 K(k_e^2)/K(1-k_e^2)}
174 Args:
175 w: Centre-conductor width (m).
176 s: Gap to ground plane (m).
177 t: Conductor thickness (m).
178 ep_eff: Uncorrected effective permittivity.
180 Returns:
181 ``(ep_eff_t, z0_t)`` — thickness-corrected effective permittivity
182 and characteristic impedance (Ω).
183 """
184 w = jnp.asarray(w, dtype=float)
185 s = jnp.asarray(s, dtype=float)
186 t = jnp.asarray(t, dtype=float)
187 ε_eff = jnp.asarray(ep_eff, dtype=float)
189 k0 = w / (w + 2.0 * s)
190 q0 = ellipk_ratio(k0**2)
192 # Avoid 0 * log(inf) -> NaN when t = 0
193 t_safe = jnp.where(t < 1e-15, 1e-15, t)
195 # Effective width increase (GGBB96 Eq. 7.98)
196 Δ = (1.25 * t / π) * (1.0 + jnp.log(4.0 * π * w / t_safe))
198 # Modified modulus for Z₀ (GGBB96, between 7.99 and 7.100)
199 ke = k0 + (1.0 - k0**2) * Δ / (2.0 * s)
200 ke = jnp.clip(ke, 1e-12, 1.0 - 1e-12)
202 # Modified ε_eff (GGBB96 Eq. 7.100)
203 ε_eff_t = ε_eff - (0.7 * (ε_eff - 1.0) * t / s) / (q0 + 0.7 * t / s)
205 # Modified Z₀
206 z0_t = 30.0 * π / (jnp.sqrt(ε_eff_t) * ellipk_ratio(ke**2))
208 # Disable thickness correction if t <= 0
209 ε_eff_t = jnp.where(t <= 0, ε_eff, ε_eff_t)
210 z0_t = jnp.where(t <= 0, cpw_z0(w, s, ε_eff), z0_t)
212 return ε_eff_t, z0_t
215# ===================================================================
216# Microstrip
217# ===================================================================
220@partial(jax.jit, inline=True)
221def microstrip_epsilon_eff(
222 w: ArrayLike,
223 h: ArrayLike,
224 ep_r: ArrayLike,
225) -> jax.Array:
226 r"""Effective permittivity of a microstrip line.
228 Uses the Hammerstad-Jensen
229 :cite:`hammerstadAccurateModelsMicrostrip1980` formula as given in
230 Pozar :cite:`m.pozarMicrowaveEngineering2012` (Eq. 3.195-3.196):
232 .. math::
234 \varepsilon_{\mathrm{eff}} = \frac{\varepsilon_r + 1}{2}
235 + \frac{\varepsilon_r - 1}{2}
236 \left(\frac{1}{\sqrt{1 + 12\,h/w}}
237 + 0.04\,(1 - w/h)^2\;\Theta(1 - w/h)\right)
239 where the last term contributes only for narrow strips (:math:`w/h < 1`).
241 Args:
242 w: Strip width (m).
243 h: Substrate height (m).
244 ep_r: Relative permittivity of the substrate.
246 Returns:
247 Effective permittivity (dimensionless).
248 """
249 w = jnp.asarray(w, dtype=float)
250 h = jnp.asarray(h, dtype=float)
251 ε_r = jnp.asarray(ep_r, dtype=float)
253 u = w / h
254 f_u = 1.0 / jnp.sqrt(1.0 + 12.0 / u)
256 # Extra correction for narrow strips (w/h < 1)
257 narrow_correction = 0.04 * (1.0 - u) ** 2
258 f_u = jnp.where(u < 1.0, f_u + narrow_correction, f_u)
260 return (ε_r + 1.0) / 2.0 + (ε_r - 1.0) / 2.0 * f_u
263@partial(jax.jit, inline=True)
264def microstrip_z0(
265 w: ArrayLike,
266 h: ArrayLike,
267 ep_eff: ArrayLike,
268) -> jax.Array:
269 r"""Characteristic impedance of a microstrip line.
271 Uses the Hammerstad-Jensen
272 :cite:`hammerstadAccurateModelsMicrostrip1980` approximation as given in
273 Pozar :cite:`m.pozarMicrowaveEngineering2012` (Eq. 3.197-3.198):
275 .. math::
277 Z_0 = \begin{cases}
278 \displaystyle\frac{60}{\sqrt{\varepsilon_{\mathrm{eff}}}}
279 \ln\!\left(\frac{8h}{w} + \frac{w}{4h}\right)
280 & w/h \le 1 \\[6pt]
281 \displaystyle\frac{120\pi}
282 {\sqrt{\varepsilon_{\mathrm{eff}}}\,
283 \bigl[w/h + 1.393 + 0.667\ln(w/h + 1.444)\bigr]}
284 & w/h \ge 1
285 \end{cases}
287 Args:
288 w: Strip width (m).
289 h: Substrate height (m).
290 ep_eff: Effective permittivity (see :func:`microstrip_epsilon_eff`).
292 Returns:
293 Characteristic impedance (Ω).
294 """
295 w = jnp.asarray(w, dtype=float)
296 h = jnp.asarray(h, dtype=float)
297 ε_eff = jnp.asarray(ep_eff, dtype=float)
299 u = w / h
301 # Narrow strip (w/h <= 1)
302 z_narrow = (60.0 / jnp.sqrt(ε_eff)) * jnp.log(8.0 / u + u / 4.0)
304 # Wide strip (w/h >= 1)
305 z_wide = 120.0 * π / (jnp.sqrt(ε_eff) * (u + 1.393 + 0.667 * jnp.log(u + 1.444)))
307 return jnp.where(u <= 1.0, z_narrow, z_wide)
310@partial(jax.jit, inline=True)
311def microstrip_thickness_correction(
312 w: ArrayLike,
313 h: ArrayLike,
314 t: ArrayLike,
315 ep_r: ArrayLike,
316 ep_eff: ArrayLike,
317) -> tuple[jax.Array, jax.Array, jax.Array]:
318 r"""Conductor thickness correction for a microstrip line.
320 Uses the widely-adopted Schneider correction as presented in
321 Pozar :cite:`m.pozarMicrowaveEngineering2012` (§3.8) and
322 Gupta et al. :cite:`guptaMicrostripLinesSlotlines1996`:
324 .. math::
326 w_e &= w + \frac{t}{\pi}
327 \ln\frac{4e}{\sqrt{(t/h)^2 + (t/(w\pi + 1.1t\pi))^2}} \\
328 \varepsilon_{\mathrm{eff},t}
329 &= \varepsilon_{\mathrm{eff}}
330 - \frac{(\varepsilon_r - 1)\,t/h}
331 {4.6\,\sqrt{w/h}}
333 Then the corrected :math:`Z_0` is computed with the effective width
334 :math:`w_e` and corrected :math:`\varepsilon_{\mathrm{eff},t}`.
336 Args:
337 w: Strip width (m).
338 h: Substrate height (m).
339 t: Conductor thickness (m).
340 ep_r: Relative permittivity of the substrate.
341 ep_eff: Uncorrected effective permittivity.
343 Returns:
344 ``(w_eff, ep_eff_t, z0_t)`` — effective width (m),
345 thickness-corrected effective permittivity,
346 and characteristic impedance (Ω).
347 """
348 w = jnp.asarray(w, dtype=float)
349 h = jnp.asarray(h, dtype=float)
350 t = jnp.asarray(t, dtype=float)
351 ε_r = jnp.asarray(ep_r, dtype=float)
352 ε_eff = jnp.asarray(ep_eff, dtype=float)
354 # Effective width (Schneider)
355 term = jnp.sqrt((t / h) ** 2 + (t / (w * π + 1.1 * t * π)) ** 2)
356 # Avoid 0 * log(inf) -> NaN when t = 0
357 term_safe = jnp.where(term < 1e-15, 1.0, term)
358 w_eff = w + (t / π) * jnp.log(4.0 * jnp.e / term_safe)
360 # Corrected epsilon_eff
361 ε_eff_t = ε_eff - (ε_r - 1.0) * t / h / (4.6 * jnp.sqrt(w / h))
363 # Corrected Z0
364 z0_t = microstrip_z0(w_eff, h, ε_eff_t)
366 # Disable thickness correction if t <= 0
367 w_eff = jnp.where(t <= 0, w, w_eff)
368 ε_eff_t = jnp.where(t <= 0, ε_eff, ε_eff_t)
369 z0_t = jnp.where(t <= 0, microstrip_z0(w, h, ε_eff), z0_t)
371 return w_eff, ε_eff_t, z0_t
374# ===================================================================
375# Common: propagation & S-parameters
376# ===================================================================
379@partial(jax.jit, inline=True)
380def propagation_constant(
381 f: ArrayLike,
382 ep_eff: ArrayLike,
383 tand: ArrayLike = 0.0,
384 ep_r: ArrayLike = 1.0,
385) -> jax.Array:
386 r"""Complex propagation constant of a quasi-TEM transmission line.
388 For the general lossy case
389 (Pozar :cite:`m.pozarMicrowaveEngineering2012`, §3.8):
391 .. math::
393 \gamma = \alpha_d + j\,\beta
395 where the **dielectric attenuation** is
397 .. math::
399 \alpha_d = \frac{\pi f}{c_0}
400 \frac{\varepsilon_r}{\sqrt{\varepsilon_{\mathrm{eff}}}}
401 \frac{\varepsilon_{\mathrm{eff}} - 1}
402 {\varepsilon_r - 1}
403 \tan\delta
405 and the **phase constant** is
407 .. math::
409 \beta = \frac{2\pi f}{c_0}\,\sqrt{\varepsilon_{\mathrm{eff}}}
411 For a superconducting line (:math:`\tan\delta = 0`) the propagation
412 is purely imaginary: :math:`\gamma = j\beta`.
414 Args:
415 f: Frequency (Hz).
416 ep_eff: Effective permittivity.
417 tand: Dielectric loss tangent (default 0 — lossless).
418 ep_r: Substrate relative permittivity (only needed when ``tand > 0``).
420 Returns:
421 Complex propagation constant :math:`\gamma` (1/m).
422 """
423 f = jnp.asarray(f, dtype=float)
424 ε_eff = jnp.asarray(ep_eff, dtype=float)
425 tanδ = jnp.asarray(tand, dtype=float)
426 ε_r = jnp.asarray(ep_r, dtype=float)
428 β = 2.0 * π * f * jnp.sqrt(ε_eff) / c_0
430 # Use safe denominator to prevent NaN gradients in JAX backward pass
431 denom = jnp.where(jnp.abs(ε_r - 1.0) < 1e-15, 1.0, ε_r - 1.0)
433 # Dielectric attenuation constant (Simons Eq. 2.2.41)
434 α_d = π * f / c_0 * (ε_r / jnp.sqrt(ε_eff)) * ((ε_eff - 1.0) / denom) * tanδ
435 # Guard against ep_r == 1 (vacuum) where division would be 0/0.
436 # When ep_r == 1 there is no substrate, so α_d = 0 by definition.
437 α_d = jnp.where(jnp.abs(ε_r - 1.0) < 1e-15, 0.0, α_d)
439 return α_d + 1j * β
442@partial(jax.jit, inline=True)
443def transmission_line_s_params(
444 gamma: ArrayLike,
445 z0: ArrayLike,
446 length: ArrayLike,
447 z_ref: ArrayLike | None = None,
448) -> tuple[jax.Array, jax.Array]:
449 r"""S-parameters of a uniform transmission line (ABCD→S conversion).
451 The ABCD matrix of a line with characteristic impedance :math:`Z_0`,
452 propagation constant :math:`\gamma`, and length :math:`\ell` is
454 .. math::
456 \begin{pmatrix} A & B \\ C & D \end{pmatrix}
457 = \begin{pmatrix}
458 \cosh\theta & Z_0\sinh\theta \\
459 \sinh\theta / Z_0 & \cosh\theta
460 \end{pmatrix}, \quad \theta = \gamma\,\ell.
462 Converting to S-parameters referenced to :math:`Z_{\mathrm{ref}}`
463 (Pozar :cite:`m.pozarMicrowaveEngineering2012`, Table 4.2):
465 .. math::
467 \begin{aligned}
468 S_{11} &= \frac{A + B/Z_{\mathrm{ref}} - C\,Z_{\mathrm{ref}} - D}
469 {A + B/Z_{\mathrm{ref}} + C\,Z_{\mathrm{ref}} + D} \\
470 S_{21} &= \frac{2}
471 {A + B/Z_{\mathrm{ref}} + C\,Z_{\mathrm{ref}} + D}
472 \end{aligned}
474 When ``z_ref`` is ``None`` the reference impedance defaults to ``z0``
475 (matched case), giving :math:`S_{11} = 0` and
476 :math:`S_{21} = e^{-\gamma\ell}`.
478 Args:
479 gamma: Complex propagation constant (1/m).
480 z0: Characteristic impedance (Ω).
481 length: Physical length (m).
482 z_ref: Reference (port) impedance (Ω). Defaults to ``z0``.
484 Returns:
485 ``(S11, S21)`` — complex S-parameter arrays.
486 """
487 γ = jnp.asarray(gamma, dtype=complex)
488 z0 = jnp.asarray(z0, dtype=complex)
489 length = jnp.asarray(length, dtype=float)
491 if z_ref is None:
492 z_ref = z0
493 z_ref = jnp.asarray(z_ref, dtype=complex)
495 θ = γ * length
497 cosh_t = jnp.cosh(θ)
498 sinh_t = jnp.sinh(θ)
500 # ABCD elements (symmetric line: A = D)
501 a = cosh_t
502 b = z0 * sinh_t
503 c = sinh_t / z0
505 denom = a + b / z_ref + c * z_ref + a # A + B/Zr + C·Zr + D with D = A
506 s11 = (b / z_ref - c * z_ref) / denom # (A-D) = 0 for symmetric line
507 s21 = 2.0 / denom
509 return s11, s21
512# ===================================================================
513# Layout-to-Model Helpers
514# ===================================================================
517@cache
518def get_cpw_substrate_params() -> tuple[float, float, float]:
519 """Extract substrate parameters from the PDK layer stack.
521 Returns:
522 ``(h, t, ep_r)`` — substrate height (µm), conductor thickness (µm),
523 and relative permittivity.
524 """
525 h = LAYER_STACK.layers["Substrate"].thickness # µm
526 t = LAYER_STACK.layers["M1"].thickness # µm
527 ep_r = material_properties[cast(str, LAYER_STACK.layers["Substrate"].material)][
528 "relative_permittivity"
529 ]
530 return float(h), float(t), float(ep_r)
533def get_cpw_dimensions(
534 cross_section: CrossSectionSpec, **kwargs
535) -> tuple[float, float]:
536 """Extracts CPW width and gap from a cross-section specification.
538 Args:
539 cross_section: A gdsfactory cross-section specification.
540 **kwargs: Additional keyword arguments passed to `gf.get_cross_section`.
542 Returns:
543 tuple[float, float]: Width and gap of the CPW.
544 """
545 # Make sure a PDK is activated
546 from qpdk import PDK
548 PDK.activate()
549 xs = gf.get_cross_section(cross_section, **kwargs)
551 width = xs.width
552 try:
553 gap = next(
554 section.width
555 for section in xs.sections
556 if section.name and "etch_offset" in section.name
557 )
558 except StopIteration as e:
559 msg = (
560 f"Cross-section does not have a section with 'etch_offset' in the name. "
561 f"Found sections: {[s.name for s in xs.sections]}"
562 )
563 raise ValueError(msg) from e
564 return width, gap
567@cache
568def cpw_parameters(
569 width: float,
570 gap: float,
571) -> tuple[float, float]:
572 r"""Compute effective permittivity and characteristic impedance for a CPW.
574 Uses the JAX-jittable functions from :mod:`qpdk.models.cpw` with the
575 PDK layer stack (substrate height, conductor thickness, material
576 permittivity).
578 Conductor thickness corrections follow
579 Gupta, Garg, Bahl & Bhartia :cite:`guptaMicrostripLinesSlotlines1996`
580 (§7.3, Eqs. 7.98-7.100).
582 Args:
583 width: Centre-conductor width in µm.
584 gap: Gap between centre conductor and ground plane in µm.
586 Returns:
587 ``(ep_eff, z0)`` — effective permittivity (dimensionless) and
588 characteristic impedance (Ω).
589 """
590 width = float(width)
591 gap = float(gap)
593 h_um, t_um, ep_r = get_cpw_substrate_params()
595 # Convert to SI (metres)
596 w_m = width * 1e-6
597 s_m = gap * 1e-6
598 h_m = h_um * 1e-6
599 t_m = t_um * 1e-6
601 # Base (zero-thickness) quantities
602 ep_eff = cpw_epsilon_eff(w_m, s_m, h_m, ep_r)
604 if t_um > 0:
605 ep_eff_t, z0_val = cpw_thickness_correction(w_m, s_m, t_m, ep_eff)
606 return float(ep_eff_t), float(z0_val)
608 z0_val = cpw_z0(w_m, s_m, ep_eff)
609 return float(ep_eff), float(z0_val)
612def cpw_z0_from_cross_section(
613 cross_section: CrossSectionSpec,
614 f: ArrayLike | None = None,
615) -> jnp.ndarray:
616 """Characteristic impedance of a CPW defined by a layout cross-section.
618 Args:
619 cross_section: A gdsfactory cross-section specification.
620 f: Frequency array (Hz). Used only to determine the output shape;
621 the impedance is frequency-independent in the quasi-static model.
623 Returns:
624 Characteristic impedance broadcast to the shape of *f* (Ω).
625 """
626 width, gap = get_cpw_dimensions(cross_section)
627 _ep_eff, z0_val = cpw_parameters(width, gap)
628 z0 = jnp.asarray(z0_val)
629 if f is not None:
630 f = jnp.asarray(f)
631 z0 = jnp.broadcast_to(z0, f.shape)
632 return z0
635def cpw_ep_r_from_cross_section(
636 cross_section: CrossSectionSpec, # noqa: ARG001
637) -> float:
638 r"""Substrate relative permittivity for a given cross-section.
640 .. note::
641 The substrate permittivity is determined by the PDK layer stack
642 (``LAYER_STACK["Substrate"]``), not by the cross-section geometry.
643 All CPW cross-sections on the same substrate share the same
644 :math:`\varepsilon_r`. The *cross_section* parameter is accepted
645 for API symmetry with :func:`cpw_z0_from_cross_section`.
647 Args:
648 cross_section: A gdsfactory cross-section specification.
650 Returns:
651 Relative permittivity of the substrate.
652 """
653 _h, _t, ep_r = get_cpw_substrate_params()
654 return ep_r