.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "howto/plot_current_path_source.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_howto_plot_current_path_source.py: Impressed currents: driving a model with a known current ======================================================== Not every drive is a port. Sometimes the current is the thing you know — a coil wound to a specification, an interference current measured on a harness, a lightning channel, a probe injecting a known signal — and what you want out of the simulation is the field it produces. ``SourceCurrentPath`` says exactly that: a current ``I(t)`` prescribed along a curve through the model. This page calibrates the source against two textbook antennas, which is the useful thing to do once before trusting it on a real structure: a short filament in free space (the Hertzian dipole) and the same filament standing on a ground plane (the short monopole). Each gives a radiated power and a directivity that are known in closed form, so a single number per case says whether the current that reached the grid is the current that was asked for. .. GENERATED FROM PYTHON SOURCE LINES 20-59 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np import magnelio as mio from magnelio import geo, monitors, signals, sources from magnelio.constants import C0, ETA0 F0 = 5e9 # the frequency the pattern is read at F_MAX = 10e9 # the analysis band HALF = 90e-3 # half the domain edge [m] — 1.5 λ of clearance at F0 LENGTH = 5e-3 # filament length [m], λ/12 at F0 OPEN = dict.fromkeys(("xmin", "xmax", "ymin", "ymax", "zmin", "zmax"), "CPML") def air_box(boundaries, z0=-HALF, height=2 * HALF): model = mio.GeometryModel(boundary_conditions=boundaries) model.add( geo.Brick(origin=(-HALF, -HALF, z0), size=(2 * HALF, 2 * HALF, height), material="air") ) return model def radiate(model, name): """One transient run; returns the far-field pattern at ``F0``.""" mesh = mio.Mesh.from_geometry(model, mio.MeshControl(min_nodes_per_wavelength=12), f_max=F_MAX) pattern = monitors.MonitorFarFieldFrequency(name="pattern", freqs=[F0], margin_cells=2) result = mio.AnalysisTD(mesh=mesh, monitors=[pattern], verbose=False).run( excitations=[ mio.Excitation(name, waveform=signals.WaveformGaussian(f_max=F_MAX), amplitude=1.0) ], t_end=2000e-12, energy_stop_db=60, ) result.renormalize(name) return pattern.result(F0) .. GENERATED FROM PYTHON SOURCE LINES 61-68 A filament in free space ------------------------ The path is a list of points — here just two, five millimetres apart — and the excitation carries the current in amperes. The endpoints become grid planes, so the filament is exactly as long as it was declared, whatever the cell size. .. GENERATED FROM PYTHON SOURCE LINES 68-75 .. code-block:: Python model = air_box(OPEN) model.add_source( sources.SourceCurrentPath(name="fil", path=[(0, 0, -LENGTH / 2), (0, 0, LENGTH / 2)]) ) dipole = radiate(model, "fil") .. rst-class:: sphx-glr-script-out .. code-block:: none mesh | feature planes mesh | grid lines mesh | materials mesh | conformal cells mesh | PEC masks mesh | 92 x 92 x 96 cells .. GENERATED FROM PYTHON SOURCE LINES 76-81 The Hertzian dipole radiates :math:`\eta_0 (k I L)^2 / 6\pi` and has a peak directivity of 1.5. Amplitudes in Magnelio are effective values — the same convention that makes a port's ``amplitude`` of 1 read as one watt of continuous wave — so the reference is the root-mean-square form of the textbook expression. .. GENERATED FROM PYTHON SOURCE LINES 81-88 .. code-block:: Python k = 2.0 * np.pi * F0 / C0 p_hertz = ETA0 * (k * LENGTH) ** 2 / (6.0 * np.pi) print(f"free space: P_rad / Hertzian {dipole.P_rad / p_hertz:.3f} (target 1.00 +- 0.05)") print(f"free space: peak directivity {dipole.directivity.max():.3f} (1.50, see below)") print(f"free space: far-field closure {dipole.power_balance:.3f} (target above 0.95)") .. rst-class:: sphx-glr-script-out .. code-block:: none free space: P_rad / Hertzian 0.973 (target 1.00 +- 0.05) free space: peak directivity 1.548 (1.50, see below) free space: far-field closure 0.994 (target above 0.95) .. GENERATED FROM PYTHON SOURCE LINES 89-98 The same filament on a ground plane ----------------------------------- Closing the bottom face with PEC and standing the filament on it turns the dipole into a monopole. Nothing about the source changes: the same current, on a path that now starts at the wall. Image theory does the rest — the far-field monitor leaves the box open on that face and completes it with the mirror image, so the pattern is masked below the plane and the horizon directivity doubles. .. GENERATED FROM PYTHON SOURCE LINES 98-108 .. code-block:: Python grounded = dict(OPEN, zmin="PEC") model = air_box(grounded, z0=0.0, height=HALF) model.add_source(sources.SourceCurrentPath(name="mono", path=[(0, 0, 0.0), (0, 0, LENGTH)])) monopole = radiate(model, "mono") p_mono = ETA0 * (k * LENGTH) ** 2 / (3.0 * np.pi) print(f"ground plane: P_rad / monopole {monopole.P_rad / p_mono:.3f} (target 1.00 +- 0.05)") print(f"ground plane: peak directivity {monopole.directivity.max():.3f} (3.00, see below)") .. rst-class:: sphx-glr-script-out .. code-block:: none mesh | feature planes mesh | grid lines mesh | materials mesh | conformal cells mesh | PEC masks mesh | 92 x 92 x 49 cells ground plane: P_rad / monopole 0.960 (target 1.00 +- 0.05) ground plane: peak directivity 3.135 (3.00, see below) .. GENERATED FROM PYTHON SOURCE LINES 109-121 Both powers hold to a few percent, which is the number that matters: the current that reached the grid is the current that was asked for. The monopole radiates half the power of the two-sided dipole it images into, into half the solid angle — hence twice the free-space power for the same current, and twice the directivity. The directivities come out three to four percent high. That excess is not the source but the near-to-far-field transform, which reads a cubic box and converges towards the exact pattern only as the box is sampled more finely; ``power_balance`` near 1 says the transform is at least self-consistent. Read the overlay below as the shape check it is, and the radiated power as the calibration. .. GENERATED FROM PYTHON SOURCE LINES 121-143 .. code-block:: Python fig, ax = plt.subplots(figsize=(6.0, 5.5), subplot_kw={"projection": "polar"}) for pattern, label, style in ( (dipole, "free space", "-"), (monopole, "on a ground plane", "--"), ): cut = pattern.directivity[:, 0] ax.plot(pattern.theta, cut, style, lw=1.8, label=label) ax.plot( dipole.theta, 1.5 * np.sin(dipole.theta) ** 2, ":", color="0.4", lw=1.4, label=r"$1.5\sin^2\theta$", ) ax.set_theta_zero_location("N") ax.set_theta_direction(-1) ax.set_title("Directivity in the $\\varphi = 0$ plane") ax.legend(loc="lower center", bbox_to_anchor=(0.5, -0.22)) fig.tight_layout() .. image-sg:: /howto/images/sphx_glr_plot_current_path_source_001.png :alt: Directivity in the $\varphi = 0$ plane :srcset: /howto/images/sphx_glr_plot_current_path_source_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 144-167 Paths that are not straight --------------------------- ``path`` takes any :class:`~magnelio.geo.Curve`, so a coil is a ``helix`` and a loop a pair of arcs chained into a closed curve. A closed path is a magnetic dipole: no charge accumulates anywhere, and the pattern is the dipole's turned on its side. .. code-block:: python from magnelio import geo a = 4e-3 loop = geo.Curve.arc((a, 0, 0), (-a, 0, 0), (0, -a, 0)).joined( geo.Curve.arc((0, -a, 0), (a, 0, 0), (0, a, 0)) ) model.add_source(sources.SourceCurrentPath(name="loop", path=loop)) A curved path is walked as a staircase across the cells, which costs nothing in the dipole moment — the staircase runs monotonically from end to end, so its signed steps sum to the exact chord — but does ask for enough cells across the curve if the finer structure of the field matters. .. GENERATED FROM PYTHON SOURCE LINES 169-190 What to watch ------------- * **The current is prescribed, not solved for.** Nothing the model does changes ``I(t)``. That is the right description of a driven coil or a known interference current, and the wrong one for an antenna whose current distribution is the answer you are after — feed that with a port. * **The amplitude is in amperes**, and reads as an effective value in the frequency domain, like every other amplitude in the library. ``source.amplitude_unit`` says so. * **Edges held at zero take no current.** A path inside a perfect conductor, or running along a PEC wall, radiates less than asked; the source warns instead of doing it quietly. Currents *on* a conductor belong on a port or a lumped element. * **Give the far-field box room.** A dipole's reactive near zone reaches about :math:`\lambda / 2\pi`; with less than half a wavelength of clearance the near-to-far-field transform stops closing, and ``power_balance`` says by how much. * **Under a symmetry declaration the mesh is the kept half.** Give the path in that half and let the symmetry wall supply its image. .. GENERATED FROM PYTHON SOURCE LINES 190-192 .. code-block:: Python plt.show() .. rst-class:: sphx-glr-timing **Total running time of the script:** (2 minutes 37.857 seconds) .. _sphx_glr_download_howto_plot_current_path_source.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_current_path_source.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_current_path_source.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_current_path_source.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_