.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "tutorials/plot_11_losses.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_11_losses.py: Conductor and dielectric losses =============================== Every structure in this series so far was lossless: perfect metal, perfect dielectrics. That is the right starting point — S-parameters of well-matched devices are dominated by geometry — but real answers to real questions need dissipation: how many dB does this cable eat, how hot does this filter run, what is the Q of this cavity? Loss enters an electromagnetic model in two physically distinct places, and Magnelio treats them differently: * **Dielectric loss** happens in the volume. A lossy material is simply part of the field solution — declare it, and every result (S-parameters included) carries the loss. * **Conductor loss** happens in a skin depth of micrometres, far below any reasonable cell size. It comes in two models: the default treats walls as perfect conductors and books the dissipated power *afterwards* from the tangential magnetic field (fast, and exactly what a resonator Q needs) — or ``wall_model="sibc"`` folds a surface impedance into the time stepping itself, so that the S-parameters come out lossy. This tutorial exercises all three paths, each against a closed-form reference: a coax filled with a lossy PCB-class dielectric, a stainless-steel waveguide, and the Q of a copper cavity. .. GENERATED FROM PYTHON SOURCE LINES 31-47 A dielectric from datasheet numbers ----------------------------------- Substrate datasheets give two numbers: the relative permittivity and the loss tangent, both at a reference frequency — say εᵣ = 4.3 and tan δ = 0.02 at 3 GHz for an FR-4-class laminate. It is tempting to read that as "tan δ = 0.02 at *every* frequency", but such a material cannot exist: causality (the Kramers–Kronig relations) demands that a constant loss angle is paid for with a logarithmic fall of εᵣ over frequency. The standard causal reading of a datasheet — used by every time-domain solver — is the *wideband Debye* model: a chain of relaxation poles spread over many decades that holds tan δ nearly constant across the band while εᵣ drifts exactly as physics requires. :meth:`~magnelio.materials.DispersionModel.djordjevic_sarkar` builds that model from the two datasheet numbers alone. .. GENERATED FROM PYTHON SOURCE LINES 47-61 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np import magnelio as mio from magnelio import geo, materials, monitors, ports, post from magnelio.constants import * eps_r, tan_d, f_ref = 4.3, 0.02, 3e9 model_d = materials.DispersionModel.djordjevic_sarkar(eps_r, tan_d, f_ref=f_ref) fr4 = mio.Material.dispersive("FR-4 class", model_d) print(f"{len(model_d.poles)} relaxation poles, eps_inf = {model_d.eps_inf:.3f}") .. rst-class:: sphx-glr-script-out .. code-block:: none 19 relaxation poles, eps_inf = 3.950 .. GENERATED FROM PYTHON SOURCE LINES 62-70 The model spreads its relaxation over nine decades — evaluated inside our band it holds tan δ within ±2 % of the datasheet value. Its high-frequency limit ``eps_inf`` ≈ 3.95 is *below* the in-band 4.3: that is the Kramers–Kronig toll, not an error. The test vehicle is the RG-58 coax geometry from the earlier tutorials, with the polyethylene swapped for this material. At these loss levels 24 mm of line eats a measurable half dB. .. GENERATED FROM PYTHON SOURCE LINES 70-90 .. code-block:: Python r_i, r_o, L_coax = 0.405e-3, 1.475e-3, 24e-3 f_max = 6e9 pec = mio.Material.pec() dielectric = geo.Cylinder(origin=(0, 0, 0), radius=r_o, height=L_coax, axis="z", material=fr4) inner = geo.Cylinder(origin=(0, 0, 0), radius=r_i, height=L_coax, axis="z", material=pec) model = mio.GeometryModel(background=pec) model.add(geo.Difference(dielectric, inner)) model.add(inner) model.add_port(ports.PortWaveguide(name="port1", plane="zmin")) model.add_port(ports.PortWaveguide(name="port2", plane="zmax")) mesh = mio.Mesh.from_geometry( model, mio.MeshControl(max_cell_size=0.16e-3, min_cell_size=0.16e-3), f_max=f_max ) result = mio.AnalysisScatteringTD(mesh=mesh, f_max=f_max, verbose=False).run(excited=["port1"]) .. GENERATED FROM PYTHON SOURCE LINES 91-103 On a uniform matched line, ``|S21| = e^(−αL)``, so one transmission measurement hands us the attenuation constant. Two closed forms to compare against: the *exact* α of the wideband Debye model (from its complex permittivity), and the textbook low-loss formula ``α_d = π·f·√εᵣ·tan δ / c`` for a constant loss tangent — on this material the two are nearly indistinguishable, which is the point of the causal model. One honest footnote: the port launches the mode computed at the material's high-frequency permittivity, so on a strongly dispersive line the port is slightly mismatched to the in-band wave — here ``|S11|`` stays below −26 dB and shows up only as a gentle ripple. .. GENERATED FROM PYTHON SOURCE LINES 103-133 .. code-block:: Python f = result.f_axis alpha_num = -np.log(np.abs(result.S("port2", "port1"))) / L_coax eps_w = model_d.evaluate(2 * np.pi * f) alpha_model = (2j * np.pi * f / C0 * np.sqrt(eps_w)).real alpha_textbook = np.pi * f * np.sqrt(eps_r) * tan_d / C0 NP2DB = 20 / np.log(10) # nepers -> dB band = (f > 1e9) & (f < 6e9) fig, ax = plt.subplots(figsize=(7, 4.5)) ax.plot(f[band] / 1e9, NP2DB * alpha_num[band], "o-", ms=3, label="simulated, from |S21|") ax.plot(f[band] / 1e9, NP2DB * alpha_model[band], label="wideband Debye model, exact") ax.plot( f[band] / 1e9, NP2DB * alpha_textbook[band], "--", label=r"textbook $\pi f \sqrt{\epsilon_r} \tan\delta \,/\, c$", ) ax.set_xlabel("frequency (GHz)") ax.set_ylabel(r"dielectric attenuation $\alpha_d$ (dB/m)") ax.set_title("Coax filled with an FR-4-class dielectric") ax.grid(alpha=0.3) ax.legend() fig.tight_layout() k5 = int(np.argmin(np.abs(f - 5e9))) print(f"alpha at 5 GHz: {NP2DB * alpha_num[k5]:.1f} dB/m (model: {NP2DB * alpha_model[k5]:.1f})") .. image-sg:: /tutorials/images/sphx_glr_plot_11_losses_001.png :alt: Coax filled with an FR-4-class dielectric :srcset: /tutorials/images/sphx_glr_plot_11_losses_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none alpha at 5 GHz: 18.7 dB/m (model: 19.0) .. GENERATED FROM PYTHON SOURCE LINES 134-162 From 1.6 GHz up the simulated attenuation tracks the model within a few percent; below that, α·L shrinks toward the ripple floor and the extraction gets noisy — measuring a hundredth of a dB rides on the port match, in simulation exactly as on a bench. Conductor loss: one physics, two models --------------------------------------- Metal walls dissipate in a skin depth — copper at 10 GHz: 0.66 µm. No volume grid resolves that, and none has to: the loss per area follows from the tangential magnetic field and the surface resistance ``R_s = √(πfµ/σ)`` alone. What differs is *when* that formula is applied. The **perturbative** default (used automatically by the wall-loss tools) keeps every wall a perfect conductor during the run and books ``½·R_s·|H_tan|²`` per wall patch afterwards. The fields — and the S-parameters — stay lossless. The **surface-impedance boundary** (``wall_model="sibc"``) replaces the perfect-conductor wall by its causal surface impedance *inside* the time stepping. The wave loses energy as it propagates, and ``|S21|`` carries the attenuation. The fixture: a WR-90 rectangular waveguide, 100 mm of it, in stainless steel (σ = 1.45·10⁶ S/m — about 6× the surface resistance of copper, a realistic worst case and comfortably measurable). Flat walls also mean the closed form is trustworthy across the band. .. GENERATED FROM PYTHON SOURCE LINES 162-194 .. code-block:: Python a_wg, b_wg, L_wg = 22.86e-3, 10.16e-3, 100e-3 sigma_steel = 1.45e6 f_lo, f_hi = 8.2e9, 12.4e9 air = mio.Material.air() wg = mio.GeometryModel(background=air) wg.add(geo.Brick(origin=(0, 0, 0), size=(a_wg, b_wg, L_wg), material=air)) wg.add_port(ports.PortWaveguide(name="port1", plane="zmin", n_modes=1)) wg.add_port(ports.PortWaveguide(name="port2", plane="zmax", n_modes=1)) wg_mesh = mio.Mesh.from_geometry(wg, mio.MeshControl(min_nodes_per_wavelength=18), f_max=f_hi) def run_waveguide(**wall_kwargs): monitor = monitors.MonitorWallLoss( freqs=np.linspace(f_lo, f_hi, 9), reference_plane=("z", 5e-3), sigma=sigma_steel, bc_faces=("xmin", "xmax", "ymin", "ymax"), ) analysis = mio.AnalysisScatteringTD( mesh=wg_mesh, f_min=f_lo, f_max=f_hi, monitors=(monitor,), verbose=False, **wall_kwargs, ) return analysis.run(excited=["port1"]), monitor .. GENERATED FROM PYTHON SOURCE LINES 195-200 First the default. The monitor needs to know which conductor it is metering — the walls here are the domain boundary, so ``bc_faces`` lists them and ``sigma`` supplies their conductivity (a lossy-metal *solid* would carry its own). The reference plane turns the booked wall power into a fraction of the power flowing down the guide. .. GENERATED FROM PYTHON SOURCE LINES 200-209 .. code-block:: Python res_pec, mon_pec = run_waveguide() s21_pec = np.abs(res_pec.S("port2", "port1")) print(f"perturbative run: max | |S21| - 1 | = {np.abs(s21_pec - 1).max():.1e}") frac = mon_pec.dissipated_fraction["total"] alpha_pert = frac / (2 * L_wg) # fraction ~ 2*alpha*L for small loss .. rst-class:: sphx-glr-script-out .. code-block:: none perturbative run: max | |S21| - 1 | = 1.9e-04 .. GENERATED FROM PYTHON SOURCE LINES 210-216 ``|S21| = 1`` to a few 10⁻⁴: the run itself was lossless, yet the monitor already knows the walls would eat about 2 % of the power — the perturbative bargain in one sentence. (The factor 2: the fraction is a *power* ratio, α is a field amplitude rate.) Now the same guide with the surface impedance in the update. .. GENERATED FROM PYTHON SOURCE LINES 216-255 .. code-block:: Python res_sibc, mon_sibc = run_waveguide(wall_model="sibc", wall_sigma=sigma_steel) f_wg = res_sibc.f_axis alpha_sibc = -np.log(np.abs(res_sibc.S("port2", "port1"))) / L_wg k = 2 * np.pi * f_wg / C0 beta = np.sqrt(np.maximum(k**2 - (np.pi / a_wg) ** 2, 0.0)) R_s = np.sqrt(np.pi * f_wg * MU0 / sigma_steel) with np.errstate(divide="ignore", invalid="ignore"): alpha_te10 = R_s / (a_wg**3 * b_wg * beta * k * ETA0) * (2 * b_wg * np.pi**2 + a_wg**3 * k**2) in_band = (f_wg > f_lo) & (f_wg < f_hi) fig, ax = plt.subplots(figsize=(7, 4.5)) ax.plot( f_wg[in_band] / 1e9, NP2DB * alpha_sibc[in_band], label='wall_model="sibc", from |S21|', ) ax.plot(f_wg[in_band] / 1e9, NP2DB * alpha_te10[in_band], "--", label="TE10 closed form") ax.plot( mon_pec.f / 1e9, NP2DB * alpha_pert, "s", ms=5, fillstyle="none", label="perturbative monitor, lossless run", ) ax.set_xlabel("frequency (GHz)") ax.set_ylabel(r"conductor attenuation $\alpha_c$ (dB/m)") ax.set_title("WR-90 waveguide in stainless steel") ax.grid(alpha=0.3) ax.legend() fig.tight_layout() ratio = alpha_sibc[in_band] / alpha_te10[in_band] print(f"sibc vs closed form across the band: {ratio.min():.3f} .. {ratio.max():.3f}") .. image-sg:: /tutorials/images/sphx_glr_plot_11_losses_002.png :alt: WR-90 waveguide in stainless steel :srcset: /tutorials/images/sphx_glr_plot_11_losses_002.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none sibc vs closed form across the band: 0.969 .. 1.009 .. GENERATED FROM PYTHON SOURCE LINES 256-279 Three independent estimates of the same physics — the surface impedance marching in the update, the perturbative bookkeeping on a lossless run, and the closed form — agree within two percent. Which model when? If the *S-parameters* must carry the loss (an attenuator, insertion loss of a filter, anything driven), use ``wall_model="sibc"``. If the question is a loss *budget* or a Q, the perturbative tools answer it from a lossless run — and several material variants can be evaluated from one solve, since the fields never change. One caveat travels with both models on a Cartesian grid: sharply *curved* conductors need a handful of cells across the radius before the sampled wall field — and with it α — is trustworthy; the flat walls here book exactly. The Q of a cavity ----------------- For resonators the natural loss figure is the quality factor. The eigenmode solver finds the lossless modes exactly as in the eigenmode tutorial; :func:`~magnelio.post.wall_loss_Q` then books the perturbative wall loss of one mode and returns ``Q = ω·W / P_loss``. The reference: the classic closed form for the TE₁₀₁ mode of a rectangular copper cavity. .. GENERATED FROM PYTHON SOURCE LINES 279-305 .. code-block:: Python a_c, b_c, d_c = 20e-3, 10e-3, 25e-3 sigma_cu = 5.8e7 cavity = mio.GeometryModel(background=air) cavity.add(geo.Brick(origin=(0, 0, 0), size=(a_c, b_c, d_c), material=air)) f101 = C0 / 2 * np.sqrt((1 / a_c) ** 2 + (1 / d_c) ** 2) cav_mesh = mio.Mesh.from_geometry( cavity, mio.MeshControl(min_nodes_per_wavelength=20), f_max=1.2 * f101 ) modes = mio.AnalysisEigenmode(mesh=cav_mesh, n_modes=5, verbose=False).run() q = post.wall_loss_Q(modes, mode=0, sigma=sigma_cu) k101 = 2 * np.pi * modes.frequencies[0] / C0 R_s_cu = np.sqrt(np.pi * modes.frequencies[0] * MU0 / sigma_cu) Q_ref = ( (k101 * a_c * d_c) ** 3 * b_c * ETA0 / (2 * np.pi**2 * R_s_cu * (2 * a_c**3 * b_c + 2 * b_c * d_c**3 + a_c**3 * d_c + a_c * d_c**3)) ) print(f"TE101 at {modes.frequencies[0] / 1e9:.3f} GHz (analytical {f101 / 1e9:.3f} GHz)") print(f"wall_loss_Q: {q.Q:.0f} closed form: {Q_ref:.0f} ratio {q.Q / Q_ref:.3f}") .. rst-class:: sphx-glr-script-out .. code-block:: none TE101 at 9.585 GHz (analytical 9.598 GHz) wall_loss_Q: 7740 closed form: 7687 ratio 1.007 .. GENERATED FROM PYTHON SOURCE LINES 306-312 Within one percent of the closed form. The result also knows *where* the power goes — ``per_tag`` splits the wall loss by face, and the breakdown mirrors the mode: the two broad walls see the full tangential magnetic field of TE₁₀₁ and take half the loss between them; the four side walls, each touched by only one H component, share the rest. .. GENERATED FROM PYTHON SOURCE LINES 312-324 .. code-block:: Python tags = sorted(q.per_tag, key=q.per_tag.get, reverse=True) shares = [q.per_tag[t] / sum(q.per_tag.values()) for t in tags] fig, ax = plt.subplots(figsize=(7, 3.8)) ax.bar(range(len(tags)), [100 * s for s in shares], color="C0") ax.set_xticks(range(len(tags)), tags) ax.set_ylabel("share of wall loss (%)") ax.set_title(f"TE$_{{101}}$ copper cavity — Q = {q.Q:.0f}, per-wall loss breakdown") ax.grid(alpha=0.3, axis="y") fig.tight_layout() .. image-sg:: /tutorials/images/sphx_glr_plot_11_losses_003.png :alt: TE$_{101}$ copper cavity — Q = 7740, per-wall loss breakdown :srcset: /tutorials/images/sphx_glr_plot_11_losses_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 325-346 Where to go from here --------------------- Everything above extends without new concepts: * **Lossy metal solids.** :meth:`~magnelio.Material.lossy_metal` declares a conductor with its σ; both wall-loss models pick the value up from the material, no override needed. * **Surface roughness.** Real copper foil is rough, and roughness raises the effective surface resistance — by a factor approaching 2 at millimetre-wave frequencies. The standard correction models (:class:`~magnelio.materials.Hammerstad`, :class:`~magnelio.materials.Huray`) plug into the same ``roughness`` / ``wall_roughness`` hooks of every path shown here. * **Dielectric loss in any structure.** The wideband Debye material from the first section is an ordinary material — put it under a microstrip or into a filter and the S-parameters carry the loss automatically. The next tutorial leaves single simulations behind: parameter sweeps and optimization with ordinary Python loops. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 27.779 seconds) .. _sphx_glr_download_tutorials_plot_11_losses.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_11_losses.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_11_losses.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_11_losses.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_