.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "tutorials/plot_06_field_monitors.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_06_field_monitors.py: Field monitors: watching a magic tee work ========================================= Every tutorial so far judged a device from the outside, by its S-parameters. This one goes inside. *Field monitors* record the electromagnetic field during a driven simulation — as snapshots in time or as phasors at chosen frequencies, on a point, a line, a plane or a volume — and turn a scattering matrix entry into something you can see. The device is a **magic tee**, the classic four-port waveguide hybrid: two collinear arms (ports 1 and 2), an H-arm attached in the plane of the broad walls (port 3), and an E-arm attached through a broad wall (port 4). Driving the H-arm splits the power equally and *in phase* onto the collinear arms; driving the E-arm splits it equally and in *anti-phase*; and the two drive arms are isolated from each other by symmetry alone. The S-parameters state these facts — the field pictures show why they are true. .. GENERATED FROM PYTHON SOURCE LINES 21-23 .. code-block:: Python :dedent: 1 .. GENERATED FROM PYTHON SOURCE LINES 25-36 The geometry: three bricks, one junction ---------------------------------------- All four arms are standard WR-90 rectangular waveguide (22.86 x 10.16 mm), and the whole tee is carved out of a PEC background — the same hole-in-metal trick as the coax shield and the spherical cavity before it. Three air bricks are enough: the collinear guide along :math:`x`, the H-arm along :math:`+y` in the same plane, and the E-arm along :math:`+z` through the broad wall. A :class:`~magnelio.geo.Union` merges them into a single shape whose overlap region *is* the junction. .. GENERATED FROM PYTHON SOURCE LINES 36-60 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np import magnelio as mio from magnelio import geo, monitors, plots, ports a = 22.86e-3 # WR-90 broad wall b = 10.16e-3 # WR-90 narrow wall arm = 30.0e-3 # arm length beyond the junction pec = mio.Material.pec() air = mio.Material.air() # collinear arms along x (broad wall in y, TE10 E-field along z) collinear = geo.Brick(origin=(-(a / 2 + arm), -a / 2, 0.0), size=(a + 2 * arm, a, b), material=air) # H-arm along +y (broad wall in x, E-field along z) h_arm = geo.Brick(origin=(-a / 2, 0.0, 0.0), size=(a, a / 2 + arm, b), material=air) # E-arm along +z (broad wall in y, E-field along x) e_arm = geo.Brick(origin=(-b / 2, -a / 2, 0.0), size=(b, a, b + arm), material=air) model = mio.GeometryModel(background=pec) model.add(geo.Union(collinear, h_arm, e_arm, name="tee")) .. rst-class:: sphx-glr-script-out .. code-block:: none GeometryModel(1 shapes, background=PEC) .. GENERATED FROM PYTHON SOURCE LINES 61-64 Two cross-sections show the whole device: the mid-height cut contains the collinear arms and the H-arm; the vertical cut along the collinear axis contains the E-arm standing on the broad wall. .. GENERATED FROM PYTHON SOURCE LINES 64-70 .. code-block:: Python fig, axes = plt.subplots(1, 2, figsize=(11, 4.2)) plots.plot_cross_section(model, "z", b / 2, ax=axes[0], title="mid-height cut (z = b/2)") plots.plot_cross_section(model, "y", 0.0, ax=axes[1], title="vertical cut (y = 0)") fig.tight_layout() .. image-sg:: /tutorials/images/sphx_glr_plot_06_field_monitors_001.png :alt: mid-height cut (z = b/2), vertical cut (y = 0) :srcset: /tutorials/images/sphx_glr_plot_06_field_monitors_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 71-85 Ports, mesh, and a band-limited excitation ------------------------------------------ One single-mode waveguide port terminates each arm. The excitation band is the WR-90 design band, 8.2 to 12.4 GHz — and here the lower limit matters for more than bookkeeping. The TE10 mode of these arms cuts off at 6.56 GHz, and spectral content near a cut-off travels with vanishing group velocity: it creeps along the arms and rings essentially forever. Setting ``f_min`` keeps the pulse energy away from that regime. The faint residue that reaches the cut-off anyway decays so slowly that we also relax the automatic stop criterion to 50 dB of port-signal decay and let a gentle time-domain taper absorb the small truncation remainder — for the −3 dB splitting curves of a tee, 50 dB is plenty. .. GENERATED FROM PYTHON SOURCE LINES 85-100 .. code-block:: Python model.add_port(ports.PortWaveguide(name="port1", plane="xmin", n_modes=1)) model.add_port(ports.PortWaveguide(name="port2", plane="xmax", n_modes=1)) model.add_port(ports.PortWaveguide(name="port3", plane="ymax", n_modes=1)) model.add_port(ports.PortWaveguide(name="port4", plane="zmax", n_modes=1)) f_min, f_max = 8.2e9, 12.4e9 mesh = mio.Mesh.from_geometry( model, mio.MeshControl(min_nodes_per_wavelength=15, min_cell_size=1.59e-3), f_max=f_max, ) print(f"grid: {mesh.Nx} x {mesh.Ny} x {mesh.Nz} cells") .. rst-class:: sphx-glr-script-out .. code-block:: none grid: 48 x 32 x 24 cells .. GENERATED FROM PYTHON SOURCE LINES 101-126 Declaring monitors ------------------ A monitor is declared *before* the run and handed to the analysis; the solver feeds it at every time step. The region is a box given by two opposite corners — an axis whose corner values coincide collapses, which is how the same class expresses a volume, a plane, a line, or a single point. ``None`` extends to the domain boundary. Four monitors cover this tutorial. A point monitor samples the field over time in one collinear arm — the time-domain "oscilloscope trace" behind every S-parameter. A plane monitor with an ``interval`` records movie frames of the mid-height cut. And two :class:`~magnelio.monitors.MonitorFieldFrequency` monitors accumulate the complex field pattern at a single frequency on the two cuts from above — a running Fourier transform over the whole run, one plane for each drive we are going to look at. A frequency monitor's ``.data`` is in **fields per 1 W of incident CW power** — E in V/m, H in A/m, per square root of a watt. The run divides its own excitation spectrum out of the accumulated transform to get there, so the numbers are absolute and comparable between runs, not merely proportional to whatever pulse drove them. Should you ever want the untouched transform, it stays available as ``.data_raw``. .. GENERATED FROM PYTHON SOURCE LINES 126-163 .. code-block:: Python zc = b / 2 # mid-height of the collinear and H arms f0 = 10.0e9 # pattern frequency probe = monitors.MonitorFieldTime( corners=((-25e-3, 0.0, zc), (-25e-3, 0.0, zc)), interval=5e-12, fields=["Ez"], name="probe_arm1", ) movie = monitors.MonitorFieldTime( corners=((None, None, zc), (None, None, zc)), interval=50e-12, fields=["E"], name="movie_midplane", ) pattern_h = monitors.MonitorFieldFrequency( corners=((None, None, zc), (None, None, zc)), freqs=[f0], fields=["E", "H"], name="pattern_midplane", ) pattern_e = monitors.MonitorFieldFrequency( corners=((None, 0.0, None), (None, 0.0, None)), freqs=[f0], fields=["E"], name="pattern_vertical", ) analysis = mio.AnalysisScatteringTD( mesh=mesh, f_min=f_min, f_max=f_max, monitors=(probe, movie, pattern_h, pattern_e), verbose=False, ) .. GENERATED FROM PYTHON SOURCE LINES 164-171 Driving the H-arm ----------------- Monitors witness one run: each excitation is its own time-domain simulation, and at its start every monitor is cleared. We therefore run one excitation at a time and harvest the pictures in between — first the H-arm. .. GENERATED FROM PYTHON SOURCE LINES 171-174 .. code-block:: Python result_h = analysis.run(excited=["port3"], port_signal_stop_db=50.0, taper_signals=True) .. GENERATED FROM PYTHON SOURCE LINES 175-181 The point monitor plots straight away — a 0D region over time gives the field at that spot as a trace. The pulse arrives after its trip from the H-arm port through the junction, and the tail that follows is the junction ringing down through all four ports (the run continues well past the window shown, until that tail has decayed by the requested 50 dB): .. GENERATED FROM PYTHON SOURCE LINES 181-185 .. code-block:: Python fig, ax = probe.plot(component="Ez") ax.set_xlim(0.0, 5.0) .. image-sg:: /tutorials/images/sphx_glr_plot_06_field_monitors_002.png :alt: probe_arm1 :srcset: /tutorials/images/sphx_glr_plot_06_field_monitors_002.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none (0.0, 5.0) .. GENERATED FROM PYTHON SOURCE LINES 186-192 The movie monitor holds every frame of the mid-height cut; three of them tell the story. The pulse runs down the H-arm, meets the junction, and leaves through the two collinear arms — with the *same* sign of :math:`E_z` on both sides. The H-arm sits on the mirror plane of the tee, so it can only excite the symmetric combination of the two arms: .. GENERATED FROM PYTHON SOURCE LINES 192-199 .. code-block:: Python fig, axes = plt.subplots(1, 3, figsize=(13.5, 3.8)) for ax, t in zip(axes, (0.85e-9, 1.1e-9, 1.4e-9)): movie.plot(component="Ez", t=t, plot_type="color", geometry=model, ax=ax) ax.set_title(f"$E_z$ at t = {t * 1e9:.2f} ns") fig.tight_layout() .. image-sg:: /tutorials/images/sphx_glr_plot_06_field_monitors_003.png :alt: $E_z$ at t = 0.85 ns, $E_z$ at t = 1.10 ns, $E_z$ at t = 1.40 ns :srcset: /tutorials/images/sphx_glr_plot_06_field_monitors_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 200-206 The frequency monitor condenses the whole run into one complex pattern per field component. At 10 GHz the in-phase split shows as a mirror-symmetric :math:`E_z` pattern, and the magnetic field — fully in-plane in this cut — curls through the junction accordingly. The standing-wave modulation visible in the H-arm is real physics: an unmatched tee reflects part of the drive back up its feed. .. GENERATED FROM PYTHON SOURCE LINES 206-214 .. code-block:: Python fig, axes = plt.subplots(1, 2, figsize=(11, 4.2)) pattern_h.plot(component="Ez", f=f0, plot_type="color", geometry=model, ax=axes[0]) pattern_h.plot(component="H", f=f0, plot_type="vector", geometry=model, ax=axes[1]) axes[0].set_title("H-arm drive: $E_z$ at 10 GHz") axes[1].set_title("H-arm drive: H field at 10 GHz") fig.tight_layout() .. image-sg:: /tutorials/images/sphx_glr_plot_06_field_monitors_004.png :alt: H-arm drive: $E_z$ at 10 GHz, H-arm drive: H field at 10 GHz :srcset: /tutorials/images/sphx_glr_plot_06_field_monitors_004.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 215-224 The S-parameters say the same thing without pictures: the drive splits evenly (``|S13|`` a little above −3 dB plus a reflected share), the two output shares are *identical* — not approximately, but to numerical precision, because the mirror symmetry of the grid enforces it — and nothing reaches the E-arm. The isolation gets an axis of its own on the right. Drawn on the shared scale it would sit 150 dB below everything else and squeeze the two curves that carry the actual splitting into one line. .. GENERATED FROM PYTHON SOURCE LINES 224-253 .. code-block:: Python fig, ax = result_h.plot_s(("port1", "port3"), ("port3", "port3")) ax2 = ax.twinx() ax2.plot( result_h.f_axis / 1e9, result_h.db("port4", "port3"), color="C2", label="S(port4 ← port3), right axis", ) ax2.set_ylabel("isolation |S| / dB") # one combined legend below the axes for both scales handles = ax.get_lines() + ax2.get_lines() ax.get_legend().remove() ax.legend( handles, [h.get_label() for h in handles], loc="upper center", bbox_to_anchor=(0.5, -0.15), ncol=3, fontsize=8, ) ax.set_title("H-arm drive") fig.tight_layout() s13 = result_h.S("port1", "port3") s23 = result_h.S("port2", "port3") print(f"max |S13 - S23| (equal split): {np.abs(s13 - s23).max():.1e}") print(f"max |S43| (E-arm isolation): {result_h.db('port4', 'port3').max():.1f} dB") .. image-sg:: /tutorials/images/sphx_glr_plot_06_field_monitors_005.png :alt: H-arm drive :srcset: /tutorials/images/sphx_glr_plot_06_field_monitors_005.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none max |S13 - S23| (equal split): 1.1e-08 max |S43| (E-arm isolation): -158.6 dB .. GENERATED FROM PYTHON SOURCE LINES 254-268 An isolation of −160 dB is not a converged small number — it is the numerical floor. The E-arm mode is *antisymmetric* under the tee's mirror plane, the H-arm drive is symmetric, and the Cartesian grid respects that mirror exactly, so the coupling is zero by symmetry at any resolution. Contrast this with the match ``|S33|``, which is protected by nothing and duly poor — a matching post in the junction would fix it, and a later tutorial will do exactly that. Driving the E-arm ----------------- The second run feeds port 4. Same monitors, fresh data — and the vertical cut now earns its keep, because this drive lives in that plane: .. GENERATED FROM PYTHON SOURCE LINES 268-278 .. code-block:: Python result_e = analysis.run(excited=["port4"], port_signal_stop_db=50.0, taper_signals=True) fig, axes = plt.subplots(1, 2, figsize=(11, 4.2)) pattern_e.plot(component="E", f=f0, plot_type="vector", geometry=model, ax=axes[0]) pattern_e.plot(component="Ez", f=f0, plot_type="color", geometry=model, ax=axes[1]) axes[0].set_title("E-arm drive: E field at 10 GHz") axes[1].set_title("E-arm drive: $E_z$ at 10 GHz") fig.tight_layout() .. image-sg:: /tutorials/images/sphx_glr_plot_06_field_monitors_006.png :alt: E-arm drive: E field at 10 GHz, E-arm drive: $E_z$ at 10 GHz :srcset: /tutorials/images/sphx_glr_plot_06_field_monitors_006.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 279-285 The arrows come down the E-arm and split at the junction — and because the E-arm's field points *along* the collinear axis, the two half-waves it launches leave with opposite sign: the colour panel shows :math:`E_z` flipping between the left and the right arm. This drive produces the antisymmetric combination, the exact counterpart of the H-arm's symmetric one: .. GENERATED FROM PYTHON SOURCE LINES 285-291 .. code-block:: Python s14 = result_e.S("port1", "port4") s24 = result_e.S("port2", "port4") print(f"max |S14 + S24| (anti-phase split): {np.abs(s14 + s24).max():.1e}") print(f"max |S34| (H-arm isolation): {result_e.db('port3', 'port4').max():.1f} dB") .. rst-class:: sphx-glr-script-out .. code-block:: none max |S14 + S24| (anti-phase split): 1.0e-08 max |S34| (H-arm isolation): -160.5 dB .. GENERATED FROM PYTHON SOURCE LINES 292-315 Sum and difference: that is the "magic". Two signals entering the collinear arms emerge at the H-arm added and at the E-arm subtracted, with the two drive arms never talking to each other — the property that makes this junction a monopulse comparator and a balanced mixer workhorse. Where to go next ---------------- New in this tutorial: field monitors declared on box regions (point to volume), time snapshots and single-frequency patterns on planes, and field pictures as the *why* behind S-parameter statements — symmetry-protected zeros you can trust at any resolution versus unprotected quantities like the match that carry discretisation error. In a notebook, every monitor also offers ``interact()``, a slider version of ``plot()`` — try it in the downloadable version of this page. For production accuracy you would raise the resolution and lengthen the arms. The monitors above lived in RAM and belonged to a single run each. The next tutorial moves simulations into a *project on disk*: every run keeps its monitor data, volumes stream without filling memory, results reopen after a restart, and the fields go to ParaView. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 19.370 seconds) .. _sphx_glr_download_tutorials_plot_06_field_monitors.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_06_field_monitors.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_06_field_monitors.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_06_field_monitors.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_