.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "tutorials/plot_09_microstrip_line.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_tutorials_plot_09_microstrip_line.py: Microstrip lines: quasi-TEM ports ================================= The tutorials so far split into two worlds: closed metal pipes (waveguides, tutorial 06/07) and ideal two-conductor lines (coax, tutorials 02–04). This one opens the third and — for most RF work — most important world: **printed transmission lines**. A microstrip is a flat conductor trace on a dielectric substrate over a ground plane, and its cross-section is *inhomogeneous*: part of the field travels in the dielectric, part in the air above. That single fact changes the character of the fundamental mode, and this tutorial is about understanding — and measuring — exactly how. The structure is deliberately plain: a straight 50 Ω line on an FR4-class substrate inside a shielding box. Bends, junctions and components come in the next tutorial; here the line itself is the subject. .. GENERATED FROM PYTHON SOURCE LINES 20-22 .. code-block:: Python :dedent: 1 .. GENERATED FROM PYTHON SOURCE LINES 24-72 The geometry: substrate, trace, shield -------------------------------------- Three bricks build the cross-section: the dielectric substrate (εᵣ = 4.3, 0.8 mm — FR4 territory), the air volume above it with the trace cut out, and the PEC trace itself, 1.2 mm wide and 0.2 mm thick. Everything sits in a PEC shield box, whose floor doubles as the ground plane — the same hole-in-metal pattern as before, just with two filling materials instead of one. Two remarks on the box. It is wide and tall enough (side walls more than four trace widths away, lid five substrate heights up) that it barely disturbs the line. And like every closed metal enclosure it has resonances of its own: box modes that would sit on top of the line's behaviour. The band below stays under the first one — for this cross-section the lowest box mode comes in near 17 GHz, and we stop at 15. Half the model is enough ------------------------ The cross-section is mirror-symmetric about the vertical plane through the trace centre, and so is the mode we want: the field pushes straight down from trace to ground, so the transverse field component *across* that plane vanishes on it, and the magnetic field threads through it at right angles. That is exactly a **magnetic wall**, and declaring one on the ``xmin`` face lets the mesher stop at the plane and simulate the right half only: .. code-block:: python mio.GeometryModel(boundary_conditions={"xmin": "SymmetryPMC"}) The geometry stays as it is — full bricks, centred on the plane. The declaration alone decides how much of it gets meshed, so switching the symmetry off later means deleting one argument, not rebuilding the model. Half the cells means half the memory and roughly half the run time, and it costs nothing in accuracy — if anything the opposite, because the domain now ends exactly at the trace centre, so the discretisation is symmetric about it by construction rather than by luck. What symmetry does cost is *modes*. A magnetic wall keeps only the fields that are symmetric about it, so any resonance of the box that happens to be antisymmetric is filtered out of the model entirely — convenient here, where such a mode could only be spurious clutter, but worth remembering whenever a symmetry plane is declared: the structure and the excitation must both respect it. .. GENERATED FROM PYTHON SOURCE LINES 72-114 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np import magnelio as mio from magnelio import geo, ports from magnelio.constants import * h_sub = 0.8e-3 # substrate height w_strip = 1.2e-3 # trace width (tuned for 50 ohm, see below) t_strip = 0.2e-3 # trace thickness W_box = 8.0e-3 # shield width H_box = 5.0e-3 # shield height L = 20.0e-3 # line length eps_r = 4.3 f_max = 15.0e9 pec = mio.Material.pec() air = mio.Material.air() fr4 = mio.Material.from_isotropic(name="FR4", epsilon=eps_r) substrate = geo.Brick(origin=(-W_box / 2, 0.0, 0.0), size=(W_box, h_sub, L), material=fr4) air_cap = geo.Brick(origin=(-W_box / 2, h_sub, 0.0), size=(W_box, H_box - h_sub, L), material=air) strip = geo.Brick(origin=(-w_strip / 2, h_sub, 0.0), size=(w_strip, t_strip, L), material=pec) model = mio.GeometryModel(boundary_conditions={"xmin": "SymmetryPMC"}) model.add(substrate) model.add(geo.Difference(air_cap, strip)) model.add(strip) model.add_port(ports.PortWaveguide(name="port1", plane="zmin", n_modes=1)) model.add_port(ports.PortWaveguide(name="port2", plane="zmax", n_modes=1)) mesh = mio.Mesh.from_geometry( model, mio.MeshControl(min_nodes_per_wavelength=25), f_max=f_max, ) print(f"grid: {mesh.Nx} x {mesh.Ny} x {mesh.Nz} cells") fig, ax = model.plot_cross_section("z", L / 2, mesh=mesh, title="microstrip cross-section") .. image-sg:: /tutorials/images/sphx_glr_plot_09_microstrip_line_001.png :alt: microstrip cross-section :srcset: /tutorials/images/sphx_glr_plot_09_microstrip_line_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none grid: 14 x 27 x 52 cells .. GENERATED FROM PYTHON SOURCE LINES 115-134 The cross-section plot shows the mesher at work on thin layers: the 0.8 mm substrate and the 0.2 mm trace anchor grid planes at their material boundaries, so the y-cells grade from fine around the trace to coarse in the air above. Nobody meshed this by hand — the geometry *is* the meshing instruction. It also shows the symmetry plane doing its work: the drawn structure still spans the full width, but the grid covers only the right half of it. The quasi-TEM mode ------------------ In a coax, air everywhere, the fundamental mode is exactly TEM and its impedance is a closed formula. Here no exact TEM mode exists: the field would have to travel at two different speeds at once, in the substrate and in the air. The physical fundamental is **quasi-TEM** — almost transverse, zero cut-off, but with its properties set by a weighted compromise between the two dielectrics. There is no textbook formula for that compromise; the port solves the 2D cross-section problem numerically, before any time stepping: .. GENERATED FROM PYTHON SOURCE LINES 134-144 .. code-block:: Python analysis = mio.AnalysisScatteringTD(mesh=mesh, f_max=f_max, verbose=False) report = analysis.solve_ports()["port1"] print(report) qtem = report.modes[0] eps_eff_static = (C0 * qtem.gamma(10e9).imag / (2 * np.pi * 10e9)) ** 2 print(f"eps_eff (quasi-static): {eps_eff_static:.3f}") .. rst-class:: sphx-glr-script-out .. code-block:: none Port 'port1' — 1 mode(s) cut by symmetry plane(s) xmin (PMC) — impedances are full-model values z_line = 51.54 Ω (numerical) [0] QTEM_lap00 TEM f_c = 0.0000 GHz z_line = 51.54 Ω eps_eff (quasi-static): 2.987 .. GENERATED FROM PYTHON SOURCE LINES 145-168 Two numbers to hold on to. The line impedance comes out at **51.5 Ω** — the trace width was picked to land near 50 Ω, and it does so within 3 %. The classic Hammerstad hand formula (open microstrip, infinitely thin trace) predicts about 58 Ω for this width; the shield lid pushes the impedance down and so does the very real 0.2 mm trace thickness. Closed formulas stop where real cross-sections begin — which is precisely why the port runs a numerical mode solver. Note what the report says above the numbers: the port window is cut by the symmetry plane, and the impedance is reported for the *full* model. On the meshed half the mode solver actually measures twice that value, since half a trace over half a ground plane holds half the capacitance; the two halves sit in parallel, and the port does that bookkeeping so the number on screen is the one the physical line has. And the effective permittivity is **2.99**: between air (1) and substrate (4.3), the exact weighting of the field's split residence. The mode profile shows that split directly — the field crowds into the substrate under the trace, with a fringing skirt in the air. It is drawn across the full width: only half of it was solved, and the mirror image is filled in for the picture. .. GENERATED FROM PYTHON SOURCE LINES 168-172 .. code-block:: Python fig, ax = qtem.plot(geometry=model) ax.set_title("quasi-TEM mode, transverse E") .. image-sg:: /tutorials/images/sphx_glr_plot_09_microstrip_line_002.png :alt: quasi-TEM mode, transverse E :srcset: /tutorials/images/sphx_glr_plot_09_microstrip_line_002.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none Text(0.5, 1.0, 'quasi-TEM mode, transverse E') .. GENERATED FROM PYTHON SOURCE LINES 173-178 Running the line and reading the S-parameters --------------------------------------------- A matched straight line is the simplest possible S-parameter test: everything should go through, nothing should come back. .. GENERATED FROM PYTHON SOURCE LINES 178-189 .. code-block:: Python result = analysis.run(excited=["port1"]) fig, ax = result.plot_s(("port2", "port1"), ("port1", "port1")) ax.set_title("straight 50 Ω microstrip") s11 = result.S("port1", "port1") s21 = result.S("port2", "port1") print(f"|S21|: min {20 * np.log10(np.abs(s21).min()):.2f} dB") print(f"|S11|: max {20 * np.log10(np.abs(s11).max()):.1f} dB") .. image-sg:: /tutorials/images/sphx_glr_plot_09_microstrip_line_003.png :alt: straight 50 Ω microstrip :srcset: /tutorials/images/sphx_glr_plot_09_microstrip_line_003.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none |S21|: min -0.00 dB |S11|: max -32.8 dB .. GENERATED FROM PYTHON SOURCE LINES 190-211 Transmission hugs 0 dB. The reflection sits near −32 dB at its worst — and it is worth understanding what that number *is*. It is not a property of the line (a uniform line reflects nothing); it is the residual of the port termination absorbing a dispersive quasi-TEM wave. For exact-TEM lines, tutorial 03 showed floors beyond −100 dB, because there the termination can be made analytically exact. A quasi-TEM mode has no such exact absorber, and the −30 dB class is the honest broadband floor of its termination — background, not physics, and far below anything a real component (or a real connector) will reflect. Dispersion: the microstrip's signature -------------------------------------- The port's ε_eff was *one number* — the quasi-static limit. But a microstrip is dispersive: as frequency rises the field retreats into the substrate and ε_eff creeps upward toward εᵣ. The 3D simulation contains that physics, and the phase of S21 is the instrument to extract it: over a line of length L, the mode accumulates φ = −β L, so β — and with it ε_eff = (c₀ β / ω)² — can be read off per frequency: .. GENERATED FROM PYTHON SOURCE LINES 211-229 .. code-block:: Python f_axis = result.f_axis phase = np.unwrap(np.angle(s21)) eps_eff_td = (C0 * (-phase) / (2 * np.pi * f_axis * L)) ** 2 sel = f_axis >= 1.0e9 # phase-derived values are 0/0-noisy near DC fig, ax = plt.subplots(figsize=(7, 4.2)) ax.plot(f_axis[sel] / 1e9, eps_eff_td[sel], label="3D simulation (from S21 phase)") ax.axhline(eps_eff_static, color="gray", ls="--", label="port mode solver (quasi-static)") ax.set_xlabel("frequency [GHz]") ax.set_ylabel(r"$\varepsilon_\mathrm{eff}$") ax.legend() ax.set_title("microstrip dispersion") fig.tight_layout() for f_probe in (5e9, 10e9, 15e9): print(f"eps_eff({f_probe / 1e9:.0f} GHz) = {float(np.interp(f_probe, f_axis, eps_eff_td)):.3f}") .. image-sg:: /tutorials/images/sphx_glr_plot_09_microstrip_line_004.png :alt: microstrip dispersion :srcset: /tutorials/images/sphx_glr_plot_09_microstrip_line_004.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none eps_eff(5 GHz) = 3.031 eps_eff(10 GHz) = 3.140 eps_eff(15 GHz) = 3.276 .. GENERATED FROM PYTHON SOURCE LINES 230-249 The curve starts at the quasi-static value and rises to ≈ 3.3 at 15 GHz — a 10 % walk toward εᵣ across the band, right in the range classical dispersion models of the Getsinger family predict for this geometry. This is the practical reason quasi-static design formulas come with frequency disclaimers, and why a broadband design gets verified in a full-wave solver: the line the formulas describe at 1 GHz is measurably *electrically longer* at 15. Where to go next ---------------- New in this tutorial: an inhomogeneous cross-section built from two dielectrics plus a trace, a symmetry plane that halves the model for free, the quasi-TEM port with its numerically solved impedance and mode profile, the honest reading of a quasi-TEM termination floor, and dispersion extracted from the S21 phase. The next tutorial bends this line around corners and builds a real component out of it — a Wilkinson power divider, including its lumped isolation resistor. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 4.235 seconds) .. _sphx_glr_download_tutorials_plot_09_microstrip_line.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_09_microstrip_line.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_09_microstrip_line.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_09_microstrip_line.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_