.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "howto/plot_field_source_reuse.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_field_source_reuse.py: Field sources: simulate a radiator once, reuse it anywhere ========================================================== A radiator rarely sits alone. The antenna goes on a mast, the connector into a housing, the scatterer into a scene — and each time the whole thing is meshed together, the fine structure of the radiator sets the cell size for a domain that is mostly empty air. The equivalence principle offers a way out. The tangential :math:`\mathbf{E}` and :math:`\mathbf{H}` on a closed surface stand for everything the surface encloses: replayed on that surface, they radiate the same field outwards and nothing inwards. So the radiator can be simulated **once**, on a small grid of its own, and its surface recording used as a source wherever it is needed — at any position, and turned by any multiple of 90°. This page records the field scattered by a PEC sphere and replays it in an empty model. The recipe is the same whatever sits inside the box. Two numbers say whether the replay is faithful: the field *outside* the box must match the original run, and the field *inside* must vanish, because the surface has taken the radiator's place. .. GENERATED FROM PYTHON SOURCE LINES 24-42 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np import magnelio as mio from magnelio import geo, monitors, signals, sources F_MAX = 15e9 L = 30e-3 # domain edge [m] R = 3e-3 # sphere radius [m] TFSF = ((-5e-3,) * 3, (5e-3,) * 3) # plane-wave box, hugging the sphere BOX = ((-8e-3,) * 3, (8e-3,) * 3) # Huygens box, outside it PROBE = ((-1e-3, -1e-3, 9.5e-3), (1e-3, 1e-3, 11e-3)) INSIDE = ((-1e-3,) * 3, (1e-3,) * 3) TIMES = np.arange(40e-12, 400e-12, 4e-12) T_END = 410e-12 .. GENERATED FROM PYTHON SOURCE LINES 44-52 The model --------- A sphere in air. The plane wave's total-field box is drawn close around the sphere, so everything outside it — where the Huygens box goes — carries the **scattered** field alone. That matters: an equivalent source stands for what radiates *from* inside the box, and the incident wave does not. .. GENERATED FROM PYTHON SOURCE LINES 52-90 .. code-block:: Python def air_domain(): model = mio.GeometryModel( boundary_conditions=dict.fromkeys(("xmin", "xmax", "ymin", "ymax", "zmin", "zmax"), "CPML"), ) model.add(geo.Brick(origin=(-L / 2,) * 3, size=(L, L, L), material="air")) return model def scene_with_sphere(): model = mio.GeometryModel( boundary_conditions=dict.fromkeys(("xmin", "xmax", "ymin", "ymax", "zmin", "zmax"), "CPML"), ) box = geo.Brick(origin=(-L / 2,) * 3, size=(L, L, L), material="air") ball = geo.Sphere(center=(0, 0, 0), radius=R, material="pec") model.add(geo.Difference(box, ball, material="air")) model.add(ball) return model def mesh_of(model): return mio.Mesh.from_geometry(model, mio.MeshControl(min_nodes_per_wavelength=12), f_max=F_MAX) def probes(): return [ monitors.MonitorFieldTime(name="out", corners=PROBE, fields=["E"], times=TIMES), monitors.MonitorFieldTime(name="in", corners=INSIDE, fields=["E"], times=TIMES), ] def trace(monitor): """One scalar time trace out of a small probe box.""" values = monitor.recording.cell_centred(["Ex"])["Ex"] return values.reshape(values.shape[0], -1).mean(axis=1) .. GENERATED FROM PYTHON SOURCE LINES 91-97 Run 1 — record the surface -------------------------- ``MonitorFieldSurface`` samples the tangential fields on the box and keeps them as a time series. ``recording()`` hands the result over; ``save`` writes it to a file, which is all the second model needs. .. GENERATED FROM PYTHON SOURCE LINES 97-119 .. code-block:: Python model = scene_with_sphere() model.add_source( sources.SourcePlaneWave(name="pw", direction=(0, 0, 1), polarization=(1, 0, 0), corners=TFSF) ) surface = monitors.MonitorFieldSurface(name="scatterer", corners=BOX) analysis = mio.AnalysisTD(mesh=mesh_of(model), monitors=[surface, *probes()], verbose=False) recorded = analysis.run( excitations=[ mio.Excitation("pw", waveform=signals.WaveformGaussian(f_max=F_MAX), amplitude=1.0) ], t_end=T_END, energy_stop_db=None, ) recording = surface.recording() print(recording) print( f"sample interval {recording.interval * 1e12:6.2f} ps over {recording.duration * 1e12:.0f} ps" ) .. rst-class:: sphx-glr-script-out .. code-block:: none mesh | feature planes mesh | grid lines mesh | materials mesh | conformal cells mesh | PEC masks mesh | 56 x 56 x 56 cells SurfaceRecording(name='scatterer', faces=6, samples=50, duration=4.075e-10 s) sample interval 7.56 ps over 408 ps .. GENERATED FROM PYTHON SOURCE LINES 120-126 Run 2 — replay it in an empty model ----------------------------------- The sphere is gone. ``SourceFieldSurface`` puts the recording back on a box of the same size; its excitation carries no waveform — the recording *is* the time function — only a scale factor. .. GENERATED FROM PYTHON SOURCE LINES 126-136 .. code-block:: Python replay_model = air_domain() replay_model.add_source(sources.SourceFieldSurface(recording=recording, name="scatterer")) replayed = mio.AnalysisTD(mesh=mesh_of(replay_model), monitors=probes(), verbose=False).run( excitations=[mio.Excitation("scatterer", amplitude=1.0)], t_end=T_END, energy_stop_db=None, ) .. rst-class:: sphx-glr-script-out .. code-block:: none mesh | feature planes mesh | grid lines mesh | materials mesh | conformal cells mesh | PEC masks mesh | 40 x 40 x 40 cells .. GENERATED FROM PYTHON SOURCE LINES 137-139 The two numbers --------------- .. GENERATED FROM PYTHON SOURCE LINES 139-150 .. code-block:: Python out_ref = trace(recorded.monitors["out"]) out_new = trace(replayed.monitors["out"]) inside = trace(replayed.monitors["in"]) peak = np.abs(out_ref).max() ratio = np.abs(out_new).max() / peak leak = 20 * np.log10(np.abs(inside).max() / peak) print(f"outside amplitude, replay / original : {ratio:.3f} (target 1.00 +- 0.02)") print(f"inside the box, relative to outside : {leak:.1f} dB (target below -30 dB)") .. rst-class:: sphx-glr-script-out .. code-block:: none outside amplitude, replay / original : 0.987 (target 1.00 +- 0.02) inside the box, relative to outside : -40.6 dB (target below -30 dB) .. GENERATED FROM PYTHON SOURCE LINES 151-154 Both hold: the replayed field outside the box follows the original to about a percent, and the inside of the box — where the sphere used to be — stays some 40 dB down. .. GENERATED FROM PYTHON SOURCE LINES 154-172 .. code-block:: Python fig, (ax, ax2) = plt.subplots(2, 1, figsize=(7.0, 6.0), sharex=True) t_ps = TIMES * 1e12 ax.plot(t_ps, out_ref, label="original run (sphere meshed)", lw=2.0) ax.plot(t_ps, out_new, "--", label="replayed from the recording", lw=1.6) ax.set_ylabel(r"$E_x$ outside the box [V/m]") ax.legend(loc="upper right") ax.grid(alpha=0.3) ax.set_title("A recorded surface replaces the radiator") ax2.plot(t_ps, inside, color="tab:red", lw=1.4) ax2.set_xlabel("time [ps]") ax2.set_ylabel(r"$E_x$ inside the box [V/m]") ax2.set_ylim(ax.get_ylim()) ax2.grid(alpha=0.3) ax2.set_title("Inside the box the equivalent source leaves nothing behind") fig.tight_layout() .. image-sg:: /howto/images/sphx_glr_plot_field_source_reuse_001.png :alt: A recorded surface replaces the radiator, Inside the box the equivalent source leaves nothing behind :srcset: /howto/images/sphx_glr_plot_field_source_reuse_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 173-195 Placing it somewhere else ------------------------- ``position`` moves the box in the new model, ``rotation`` turns it. Only multiples of 90° are available: the box is spanned by grid-node planes, and a tilted recording would cross the target grid obliquely, with no samples where the source is applied. A free angle raises rather than rounding. .. code-block:: python recording.save("scatterer.h5") # ... in another script, another day, another model model.add_source( sources.SourceFieldSurface.from_file( "scatterer.h5", name="scatterer", position=(0.0, 0.0, 0.25), # where the box goes now rotation=("z", 90), # turned a quarter turn ) ) .. GENERATED FROM PYTHON SOURCE LINES 197-225 What to watch ------------- * **The box must enclose the radiator, and only the radiator.** Here the plane wave's total-field box is drawn *inside* the Huygens box, so the recording holds the scattered field alone. Record the total field instead and the replay would have to conjure up an incident wave arriving from the absorber, which it cannot. * **An equivalent source replays outgoing fields.** That is what "everything inside the surface" means. A field passing *through* the box is not what the construction stands for. * **A conductor must not cut a box face.** It would carry current across the surface, and the recording would be short of exactly that part; the monitor warns. A ground plane is the exception — it closes a domain face, the box is left open there, and the recording then belongs in a model that continues that plane. * **The sampling rate follows the field, not the setting.** The default records eight samples per period at ``f_max``, well above Nyquist because the replay interpolates linearly in time. A field that carries energy above ``f_max`` — anything started from a sharply localised state — needs a shorter ``interval``. * **The recording is only as long as the run.** It ends where its samples end; the replay holds the last one rather than extrapolating, so let the recording run until the fields have decayed. * **Draw the box close.** Its size sets the cost of the recording (faces times patches times samples), which is held in memory until the run ends. .. GENERATED FROM PYTHON SOURCE LINES 225-227 .. code-block:: Python plt.show() .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 15.904 seconds) .. _sphx_glr_download_howto_plot_field_source_reuse.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_field_source_reuse.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_field_source_reuse.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_field_source_reuse.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_