magnelio.monitors#
Monitors — record fields, fluxes and wall losses during a run.
Monitors are attached to the solver via the analysis monitors=
parameter: attach once, record every step, finalize after
the run.
- class magnelio.monitors.MonitorFarFieldFrequency(freqs, name='far_field', margin_cells=3)#
Far-field (antenna pattern) monitor at fixed frequencies on an automatic Huygens box.
Records the surface DFT of the tangential fields on a closed box placed
margin_cellsinside the physical domain (the absorber layers are excluded automatically). After a scattering run,result()returns the far-field pattern at one of the requested frequencies.Domain faces closed with PEC or PMC — a ground plane — and declared symmetry planes are handled by image theory: the box is left open there and the mirror images of the recorded surface complete it. For a plain PEC/PMC boundary the pattern is masked to the physical half-space; a symmetry plane keeps the full sphere.
A feed guide that crosses the box — a waveguide port in an absorbing face — is handled the way a waveguide-fed antenna is usually treated: the box face it crosses is sampled at the absorber interface, the patches inside the guide (no external source) and inside conductors are left out, and the currents on the guide’s outer wall beyond the box, which the absorber removes, are the approximation. The normalisation is to the incident power the run actually launched at each frequency, which for a TE/TM feed differs from the excitation waveform by the mode’s wave impedance.
- Parameters:
freqs (array_like) – Frequencies [Hz] to record.
name (str) – Monitor name (store key).
margin_cells (int, default 3) – Clearance in grid cells between the box and the absorber (or domain edge). At least 1, so the two-layer node-plane sampling never reads absorber cells.
Examples
>>> from magnelio import monitors >>> ff = monitors.MonitorFarFieldFrequency(freqs=[2.45e9], name="pattern")
- classmethod from_result_dump(dump)#
Rebuild a result-serving monitor from a
result_dump().The store reader’s path: the dump carries the box geometry and image planes, so no mesh is needed — the rebuilt monitor answers
result()but cannotrecord()(it is not attached to a grid).- Parameters:
dump (dict)
- Return type:
- attach(mesh)#
Place the box on mesh and allocate fresh DFT accumulators.
- Return type:
None
- load_result_dump(dump)#
Restore accumulators written by
result_dump()(resume).The monitor must already be attached to the same mesh, so the fresh accumulators have the dumped shapes.
- Parameters:
dump (dict)
- Return type:
None
- plot_3d(f=None, *, f_index=None, **kwargs)#
3D radiation surface at one recorded frequency.
Keyword arguments beyond f/f_index go to
FarFieldResult.plot_3d().- Parameters:
f (float | None)
f_index (int | None)
- plot_cut(f=None, *, f_index=None, **kwargs)#
Polar cut of the pattern at one recorded frequency.
Keyword arguments beyond f/f_index go to
FarFieldResult.plot_cut()(plane=,angle=,quantity=and the drawing options).- Parameters:
f (float | None)
f_index (int | None)
- record(fields, n, t, dt)#
Accumulate this step’s surface DFT contribution.
E samples are at time
t, H samples att + dt/2(the leapfrog stagger); each goes into its accumulator with its own time stamp.- Parameters:
n (int)
t (float)
dt (float)
- Return type:
None
- renormalize(source_signal)#
Normalize the surface DFT to 1 W incident CW power.
Called for you at the end of a scattering run; call directly only for hand-driven solver runs. Stores the excitation spectrum as the divisor — the accumulated bins stay untouched, so repeating the call just replaces the reference.
- Return type:
None
- result(f=None, *, f_index=None, theta=None, phi=None)#
The far-field pattern at one recorded frequency.
- Parameters:
f (float, optional) – Frequency [Hz]; must be one of
freqs(omit for a single-frequency monitor).f_index (int, optional) – Index into
freqs, alternative to f.theta (array_like, optional) – Spherical evaluation grids [rad]; defaults to 2° over the full sphere.
phi (array_like, optional) – Spherical evaluation grids [rad]; defaults to 2° over the full sphere.
- Return type:
- result_dump()#
The accumulators plus the box geometry, for store and resume.
The face geometry and image planes travel with the bins so a reader can rebuild the transform inputs without the mesh — reader == monitor by construction.
- Return type:
dict
- property is_renormalized: bool#
Whether the 1 W renormalization has been applied.
- class magnelio.monitors.MonitorFieldFrequency(freqs, corners=None, fields=<factory>, interval=None, name='')#
Record complex-valued fields at specified frequencies via running DFT.
- Parameters:
corners (tuple of tuple, optional) – Two opposite corners
((x0, y0, z0), (x1, y1, z1))of the recorded box [m] — the same form asBrick.from_corners(). Corner order does not matter. An axis whose two values coincide is degenerate and records a single cell layer (plane, line, point). A component may beNone(or±math.inf) to reach the domain boundary on that side. Omit corners entirely for the whole domain.freqs (array_like) – Target frequencies [Hz].
fields (list[str]) – Field groups or components to record.
"E"expands to["Ex", "Ey", "Ez"],"H"to["Hx", "Hy", "Hz"].interval (float, optional) –
Seconds between DFT contributions. The default (
None) accumulates at every time step, which for a whole-volume monitor is arithmetic comparable to the solver itself and can double a run’s wall-clock time. Sub-sampling cuts that cost proportionally: the recorded step count, and with it the cell-centre interpolation and the complex accumulation, drop by the same factor.Unlike
MonitorFieldTime, where the interval only decides how many snapshots are kept, this one is real under-sampling of an oscillating integrand. Two conditions must hold, and only the first can be checked here:the interval must resolve the monitor’s own highest frequency — below four samples per period the run is rejected, below ten it warns;
the fields must carry nothing above the resulting Nyquist frequency, or that content folds onto the requested bins. The monitor cannot know the excitation bandwidth, so this is the caller’s judgement: an interval chosen from
f_maxof the analysis rather than from the monitor’s own frequencies is always safe.
Rounded down to a whole number of time steps (at least one), so the realised spacing never exceeds the one asked for; the integration weight follows exactly, so the result stays in the same units and
renormalizeis unaffected.name (str) – Monitor label (must be unique within a simulation).
Examples
>>> mon = MonitorFieldFrequency( ... corners=((None, None, 5e-3), (None, None, 5e-3)), ... freqs=np.linspace(1e9, 10e9, 50), ... fields=["E", "H"], ... name="EH_xy_5GHz", ... )
A whole-volume monitor on a band that ends at 3.4 GHz, sampled at 20 points per period of that top frequency instead of every step:
>>> mon = MonitorFieldFrequency( ... freqs=[2.87e9, 2.91e9], ... fields=["E"], ... interval=1.0 / (20 * 3.4e9), ... name="E_volume", ... )
- classmethod from_ranges(*, x1=None, x2=None, dx=None, y1=None, y2=None, dy=None, z1=None, z2=None, dz=None, **kwargs)#
Build the same monitor from one coordinate range per axis.
The range spelling of
corners=, as infrom_ranges(): each axis takes up to two of its three keywords — the two bounds (x1,x2) or a bound and an extent (x1,dx/x2,dx). Here an axis may also be open: give nothing for the whole domain extent, or a single bound to reach the domain boundary on the other side. All remaining keyword arguments are forwarded to the constructor.Examples
>>> mon = MonitorFieldFrequency.from_ranges(x1=0, dx=5e-3, freqs=[2.9e9], fields=["E"])
- attach(mesh)#
Snap to grid and allocate one DFT accumulator per staggered component.
- Return type:
None
- attach_operators(mesh, M_eps, M_mu)#
Take the region’s cut of the solver’s material diagonals (DD-260).
Called by the solver after
attach(); the spectrum then states its time-averaged energy and power flow (energy(),flux()).- Return type:
None
- finalize()#
Called after the simulation completes (no-op for DFT monitors).
- Return type:
None
- interact(component='E', *, normal=None, position=0.0, plot_type='vector', phase=0.0, scale_mm=True, cmap=None, geometry=None, flip=False, density=20, threshold=0.02, vmax=None, figsize=None)#
A notebook slider over the frequencies (needs ipywidgets).
The colour range (scalar) or arrow scale (vector) is fixed over every frequency for visual stability. The arguments are those of
plot().- Parameters:
component (str)
normal (str | None)
position (float)
plot_type (str)
phase (float)
scale_mm (bool)
cmap (str | None)
flip (bool)
density (int)
threshold (float)
vmax (float | None)
figsize (tuple[float, float] | None)
- load_result_dump(dump)#
Restore the DFT accumulators from a
result_dump()(resume).The monitor must already be attached (fresh zero accumulators of the right shape); this overwrites their bins in place so the resumed run keeps integrating from the checkpointed partial DFT.
- Parameters:
dump (dict)
- Return type:
None
- plot(component='E', f=None, f_index=None, *, normal=None, position=0.0, plot_type='vector', phase=0.0, ax=None, scale_mm=True, cmap=None, geometry=None, flip=False, vmin=None, vmax=None, density=20, normalize_arrows=False, threshold=0.02, quiver_scale=None, colorbar=True)#
Plot the pattern at one frequency.
A point region draws its magnitude over frequency (ignores f / f_index); a line region the values along its axis; a plane region the pattern on its own plane; a volume the plane selected with normal and position. Only the drawn layer is averaged onto cell centres. The pattern is per 1 W CW (see
spectrum).- Parameters:
component (str) –
"E"or"H"for a vector plot or the envelope magnitude,"Ex","Hz", … for a single component.f (float, optional) – Frequency [Hz]; the nearest frame is drawn.
f_index (int, optional) – Frame index (overrides f).
normal ({"x", "y", "z"}, optional) – Slice-plane normal for a volume (required there).
position (float) – Slice-plane position along normal [m]; snapped to the nearest cell-centre plane.
plot_type (str) –
"vector","color", or"contour".phase (float) – Instant of the complex pattern in degrees,
Re(F · exp(+j·phase)), the pattern atw t = phase, so it advances with time; a group magnitude is the envelope and ignores it.ax (matplotlib.axes.Axes, optional)
scale_mm (bool)
cmap (str or None) – Colourmap (None = auto-select).
geometry (GeometryModel, optional) – Cross-section overlay of the plane.
flip (bool) – Swap horizontal and vertical axes.
vmin (float, optional) – Colour limits (scalar) or arrow clipping (vector).
vmax (float, optional) – Colour limits (scalar) or arrow clipping (vector).
density (int) – Target arrows per axis (vector mode).
normalize_arrows (bool) – Unit-length arrows, colour = magnitude.
threshold (float) – Suppress arrows below this fraction of peak.
quiver_scale (float or None) – Fixed quiver scale override.
colorbar (bool, default True) – Draw the colour bar;
Falsefor a panel of a shared figure.
- Returns:
fig (matplotlib.figure.Figure)
ax (matplotlib.axes.Axes)
- record(fields, n, t, dt)#
Accumulate DFT contribution from the current time step.
E-fields are at time
t = n * dtand H-fields att_H = (n + 0.5) * dt. The correct time is passed to the DFT accumulator for each component so that the Leapfrog staggering is handled automatically.The bins accumulate the grid quantities on the region’s Yee positions; nothing is averaged until the spectrum is read. The time stamps are the ones the port recorder uses for the same step (
tfor the sample ofetaken after step n) and the transform carries the same sign, so a renormalised pattern is phase-consistent with the run’s S-parameters.With an
interval, steps off the stride return before the copy — which is where a whole-volume monitor spends its time, so the saving is proportional. The stride is keyed on the absolute step index, so a resumed run samples the same instants as an uninterrupted one.- Parameters:
n (int)
t (float)
dt (float)
- Return type:
None
- renormalize(source_signal)#
Normalize DFT data to 1 W incident CW power.
A monitor that took part in a scattering run is renormalised for you when the run ends, and so is one read back from a project store — call this only for a monitor filled by hand, or to divide by a reference other than the run’s own excitation.
Divides each DFT frequency bin by the source-waveform spectrum. The excitation waveform is the incident power-wave amplitude
a(t)in √W for lumped, TEM and quasi-TEM feeds, so the renormalised fields are exactly the fields of a 1 W CW excitation at each monitor frequency (gated bytest_port_units.py::test_frequency_monitor_fields_per_1w_cw). A TE/TM feed launches a frequency-dependent power per unit waveform (its wave impedance varies across the band); the analysis wires that ratio into the monitor after the run so the same statement holds there.The source spectrum is computed in the same Fourier convention as the internal DFT accumulator (
exp(-jωt)withdtintegration weight — the sign of the S-parameters and ofat_frequencies()), so the division is consistent.Repeating the call is harmless: the accumulated bins are never modified, so this only replaces the divisor.
- Parameters:
source_signal (Signal1D) – Excitation waveform of the run — pass
result.reference_signal.- Return type:
None
- result_dump()#
The DFT result + geometry needed to persist and reload it.
Unlike a time monitor, the accumulator does not stream append-only: it is a fixed-size running sum, both the live result (a partial DFT, readable from the first dump) and the resume state (reloaded to keep integrating). The bins are the raw complex sums — renormalising to 1 W stays a reader/user step, exactly as for the in-RAM monitor. The reader/hydrator needs the region coordinates and frequencies too, so they travel with the bins.
- Return type:
dict
- show(component='E', **kwargs)#
Interactive 3D view of the DFT field on a cutting plane.
The geometry viewer with the field laid on its cut, a phase slider for the complex pattern, a frequency slider when several bins were recorded, and the position slider walking through the region. See
magnelio.plots.show_field()for the arguments — the bin (f=orframe=),phase, the plane (normal,position),volumefor the region behind the cut, and the renderingmode.The monitor carries the field alone: pass
geometry=to draw the model with it, andmesh=to cut the metal cells out of the sheet, to name the symmetry planes the field is continued across, and (withshow_grid=True) to draw the grid cells.- Parameters:
component (str)
- property is_renormalized: bool#
Whether 1 W renormalization has been applied.
- property spectrum#
The pattern as a
FieldSpectrum, per 1 W CW.Every frame keeps the Yee staggering of the region. Raises without a source reference (see
renormalize());spectrum_rawis the undivided transform.
- property spectrum_raw#
The raw transform as a
FieldSpectrum.
- class magnelio.monitors.MonitorFieldSurface(corners=None, interval=None, oversample=8.0, name='field_surface', margin_cells=3)#
Record the tangential fields on a closed Huygens box.
The recording is the model’s radiated field in transportable form: hand it to
SourceFieldSurfaceand a second model is driven by this one’s radiator without meshing it again.- Parameters:
corners (tuple of tuple, optional) – Two opposite corners
((x0, y0, z0), (x1, y1, z1))of the box [m]. Omit it to take the largest box the physical domain allows (margin_cellsinside the absorber) — right for a model built around one radiator, while a box drawn close around the radiator keeps the recording small. Faces are snapped to the nearest grid-node plane and clamped out of the absorber.interval (float, optional) – Recording interval [s]. Defaults to
1 / (oversample · f_max)of the mesh — the samples the bandwidth actually carries. Give it explicitly only to override that.oversample (float, default 8.0) – Samples per period at
f_maxwhen interval is not given. Well above Nyquist on purpose: the replay interpolates the recording linearly in time, and that error falls as the square of the sample spacing, not with the sampling theorem. Measured on a sphere’s scattered field, the replay reaches its spatial floor at eight samples per period and stays 1.2 % short of it at four.name (str) – Monitor name (store key).
margin_cells (int, default 3) – Clearance in grid cells between the box and the absorber (or domain edge). At least 1, so the two-layer node-plane sampling never reads absorber cells.
Notes
The recording is held in memory until the run ends and is written with
SurfaceRecording.save(); size is faces × patches × samples, which a box drawn close around the radiator keeps small.The equivalence principle needs a closed surface: the box must enclose every source of the field it stands for. A domain face closed with PEC/PMC — a ground plane — leaves the box open there, and the recording is then valid only for a second model that continues that wall (the replay says so).
Examples
>>> from magnelio import monitors >>> box = monitors.MonitorFieldSurface(name="antenna_box")
- attach(mesh)#
Place the box on mesh and start a fresh recording.
- Return type:
None
- finalize()#
Nothing to settle — the samples are the result.
- Return type:
None
- load_state_dict(sd)#
Restore the schedule cursor (see
state_dict()).- Parameters:
sd (dict)
- Return type:
None
- record(fields, n, t, dt)#
Sample the box faces when the schedule asks for it.
The solver calls this after the H update of step n, where
estands att + dtandhhalf a step later. Both stamps are kept: the recording stores the E time base and the half-step offset, and the replay interpolates each field at its own instant.At most one sample per step — a schedule finer than the time step has no more information to record, and two samples sharing an instant would leave the replay dividing by a zero interval.
- Parameters:
n (int)
t (float)
dt (float)
- Return type:
None
- recording()#
The recorded surface as a
SurfaceRecording.Hand the result to
from_recording()to drive a second model with it.
- state_dict()#
Checkpoint the schedule cursor for a bit-exact resume.
- Return type:
dict
- class magnelio.monitors.MonitorFieldTime(corners=None, times=None, interval=None, start=0.0, fields=<factory>, name='')#
Record field snapshots at specified time points.
Give either an explicit list of times or a recording interval. The interval form is the one to use when the run length is decided by a stop criterion rather than by you: it keeps sampling for as long as the simulation lasts, with no end time to guess.
- Parameters:
corners (tuple of tuple, optional) – Two opposite corners
((x0, y0, z0), (x1, y1, z1))of the recorded box [m] — the same form asBrick.from_corners(). Corner order does not matter. An axis whose two values coincide is degenerate and records a single cell layer: that is how a plane, a line or a point is expressed. A component may beNone(or±math.inf) to reach the domain boundary on that side. Omit corners entirely for the whole domain.times (array_like, optional) – Explicit recording time points [s]. Mutually exclusive with interval.
interval (float, optional) – Record every interval seconds until the run ends. Mutually exclusive with times. Note that an open-ended monitor on a long run accumulates snapshots: give the analysis a
project=so they stream to disk instead of filling RAM.start (float, default 0.0) – First recording time [s] of the interval form.
fields (list[str]) – Field groups or components to record.
"E"expands to["Ex", "Ey", "Ez"],"H"to["Hx", "Hy", "Hz"].name (str) – Monitor label (must be unique within a simulation).
Examples
A plane at z = 5 mm spanning the whole cross-section, at a fixed set of instants:
>>> mon = MonitorFieldTime( ... corners=((None, None, 5e-3), (None, None, 5e-3)), ... times=np.arange(0, 10e-9, 0.5e-9), ... fields=["E"], ... name="E_xy_plane", ... )
A box, sampled every 0.5 ns however long the run turns out to be:
>>> mon = MonitorFieldTime( ... corners=((0, 0, -20e-3), (5e-3, 5e-3, 20e-3)), ... interval=0.5e-9, ... fields=["E"], ... name="E_box", ... )
The whole domain, same cadence:
>>> mon = MonitorFieldTime(interval=0.5e-9, fields=["E"])
- classmethod from_ranges(*, x1=None, x2=None, dx=None, y1=None, y2=None, dy=None, z1=None, z2=None, dz=None, **kwargs)#
Build the same monitor from one coordinate range per axis.
The range spelling of
corners=, as infrom_ranges(): each axis takes up to two of its three keywords — the two bounds (x1,x2) or a bound and an extent (x1,dx/x2,dx). Here an axis may also be open: give nothing for the whole domain extent, or a single bound to reach the domain boundary on the other side. All remaining keyword arguments are forwarded to the constructor.Examples
>>> mon = MonitorFieldTime.from_ranges(z1=5e-3, z2=5e-3, interval=0.5e-9, fields=["E"])
- attach(mesh)#
Snap monitor region to the simulation grid.
Called once by the solver during setup.
- Return type:
None
- attach_operators(mesh, M_eps, M_mu)#
Take the region’s cut of the solver’s material diagonals (DD-260).
Called by the solver after
attach(), with its ownM_εandM_μdiagonals; the recording then states its energy and flux (energy()).- Return type:
None
- finalize()#
Called after the simulation completes.
- Return type:
None
- interact(component='E', *, normal=None, position=0.0, plot_type='vector', scale_mm=True, cmap=None, geometry=None, flip=False, density=20, threshold=0.02, vmax=None, figsize=None)#
A notebook slider over the recorded frames (needs ipywidgets).
The colour range (scalar) or arrow scale (vector) is fixed over every frame for visual stability. The arguments are those of
plot(); a volume needs its slice plane (normal, position), and the slider then runs over time on it.- Parameters:
component (str)
normal (str | None)
position (float)
plot_type (str)
scale_mm (bool)
cmap (str | None)
flip (bool)
density (int)
threshold (float)
vmax (float | None)
figsize (tuple[float, float] | None)
- load_state_dict(sd)#
Restore the schedule cursor (see
state_dict()).- Parameters:
sd (dict)
- Return type:
None
- plot(component='E', t=None, t_index=None, *, normal=None, position=0.0, plot_type='vector', ax=None, scale_mm=True, cmap=None, geometry=None, flip=False, vmin=None, vmax=None, density=20, normalize_arrows=False, threshold=0.02, quiver_scale=None, colorbar=True)#
Plot one frame of the recording.
A point region draws its value over time (ignores t / t_index); a line region the values along its axis at one instant; a plane region the frame on its own plane; a volume the plane selected with normal and position. Only the drawn layer is averaged onto cell centres.
- Parameters:
component (str) –
"E"or"H"for a vector plot or magnitude,"Ex","Hy", … for a single component.t (float, optional) – Instant [s]; the nearest frame is drawn.
t_index (int, optional) – Frame index (overrides t).
normal ({"x", "y", "z"}, optional) – Slice-plane normal for a volume (required there); for a plane region it may name the plane’s own normal.
position (float) – Slice-plane position along normal [m]; snapped to the nearest cell-centre plane.
plot_type (str) –
"vector","color", or"contour".ax (matplotlib.axes.Axes, optional)
scale_mm (bool)
cmap (str or None) – Colourmap (None = auto-select).
geometry (GeometryModel, optional) – Cross-section overlay of the plane.
flip (bool) – Swap horizontal and vertical axes.
vmin (float, optional) – Colour limits (scalar) or arrow clipping (vector).
vmax (float, optional) – Colour limits (scalar) or arrow clipping (vector).
density (int) – Target arrows per axis (vector mode).
normalize_arrows (bool) – Unit-length arrows, colour = magnitude.
threshold (float) – Suppress arrows below this fraction of peak.
quiver_scale (float or None) – Fixed quiver scale override.
colorbar (bool, default True) – Draw the colour bar;
Falsefor a panel of a shared figure.
- Returns:
fig (matplotlib.figure.Figure)
ax (matplotlib.axes.Axes)
- pop_pending()#
Drain the snapshots recorded since the last call (streaming).
Returns the pending recorded times and, per component, the pending snapshots — grid quantities on the region’s Yee positions, shape
(k, *Yee shape)— stacked along a leading time axis, then clears the in-RAM snapshot buffer — so a project-backed run stays memory-bounded (the run sink flushes each batch to disk instead of the monitor holding every snapshot)._next_idx(target-time progress) is kept, so recording continues at the right time point. The in-RAM path never calls this, so itsdata/taccumulation is unchanged.- Return type:
tuple[list[float], dict[str, ndarray]]
- record(fields, n, t, dt)#
Record a snapshot when the electric field’s instant meets a target.
Called at every time step by the solver, after the H update of step n:
ethen stands att + dtandhhalf a step later. The snapshot is a copy of the grid quantities on the region’s Yee positions — no averaging — stamped with the electric instant; the recording states the magnetic one astimes + dt/2.- Parameters:
n (int)
t (float)
dt (float)
- Return type:
None
- show(component='E', **kwargs)#
Interactive 3D view of the recorded field on a cutting plane.
The geometry viewer with the field laid on its cut, a time slider over the recorded frames, and the position slider walking through the region. See
magnelio.plots.show_field()for the arguments — the frame (t=orframe=), the plane (normal,position),volumefor the region behind the cut, and the renderingmode.The monitor carries the field alone: pass
geometry=to draw the model with it, andmesh=to cut the metal cells out of the sheet, to name the symmetry planes the field is continued across, and (withshow_grid=True) to draw the grid cells.- Parameters:
component (str)
- state_dict()#
Checkpoint the schedule cursor for a bit-exact resume.
Two counters: the targets consumed (
next_idx) and the frames recorded (n_recorded) — they differ when the schedule is finer than the time step. The frames themselves live in the run’sresults.h5(streamed), and the region is re-resolved on attach;n_recordeddrives the monitor-stream truncation on resume.- Return type:
dict
- property recording#
The snapshots as a
FieldRecording.Every frame keeps the Yee staggering of the region; the magnetic field’s instants are
recording.times_h. Raises when nothing has been recorded — on a project-backed run the frames stream to the store and are read back through the project’s monitors.
- class magnelio.monitors.MonitorFluxTime(normal, position, name='')#
Integrate normal Poynting flux through a mesh-aligned surface.
The flux is always integrated over the full cross-section of the domain (a partial-aperture flux is not what this monitor measures), so the surface is fully described by an axis-aligned plane.
- Parameters:
normal (str) – Normal axis of the cross-section plane (
"x","y"or"z").position (float) – Position of the plane along that axis [m]; snapped to the nearest grid node.
name (str) – Monitor label.
Examples
>>> flux = MonitorFluxTime(normal="z", position=5e-3, name="flux_z")
- attach(mesh)#
Snap to nearest grid node and pre-compute the flux weights.
Full-model booking on a symmetric run: every symmetry plane whose axis lies IN the cross-section halves the meshed aperture, so the recorded flux doubles per such plane. This is source-independent because the sources themselves declare full-model amplitudes — a modal port injects ×1/√2 per cutting plane (fields at full-model level, half the full-model power into the meshed half), and a plane wave is field-normalised anyway. A plane parallel to the monitor surface leaves the aperture whole (factor 1). Under the earlier half-window-normalised excitation this ×2 was a factor-2 error (measured in
validation/symmetry_full_vs_half_certificate.py).- Return type:
None
- finalize()#
Called after the simulation completes (no-op).
- Return type:
None
- load_state_dict(sd)#
Restore the recorded-sample count (see
state_dict()).- Parameters:
sd (dict)
- Return type:
None
- plot(ax=None)#
Plot instantaneous Poynting flux vs. time.
- Returns:
fig (matplotlib.figure.Figure)
ax (matplotlib.axes.Axes)
- pop_pending()#
Drain the
(time, power)samples recorded since the last call.Like
MonitorFieldTime.pop_pending(), this returns the pending samples and clears the in-RAM buffer so a project-backed run stays memory-bounded (the run sink appends each batch toresults.h5)._next_idx(the total recorded count) is kept, so it drives the flux-stream truncation on resume. The in-RAM path never calls this, so its.power/.t/.total_energyaccumulation is unchanged.- Return type:
tuple[list[float], list[float]]
- record(fields, n, t, dt)#
Record instantaneous Poynting flux at this time step.
- Parameters:
n (int)
t (float)
dt (float)
- Return type:
None
- state_dict()#
Checkpoint the recorded-sample count for a bit-exact resume.
Only the running count is state a continuation must restore — the samples themselves live in the run’s
results.h5(streamed)._next_idxequals the number of samples recorded so far (flux records every step), so it also drives the flux-stream truncation on resume.- Return type:
dict
- property total_energy: float#
Time-integrated Poynting energy [J].
- class magnelio.monitors.MonitorWallLoss(freqs, normal, position, sigma=None, mu=1.0, roughness=None, bc_faces=(), name='wall_loss', sibc=None, masked_faces=(), wall_overrides=None)#
Frequency-domain wall-loss monitor for time-domain runs.
- Parameters:
freqs (array_like) – Evaluation frequencies [Hz].
normal (str) – Normal axis (
"x","y"or"z") of the power-reference cross-section — a plane between the excited port and the lossy walls.position (float) – Position of that plane along its normal axis [m]; snapped to the nearest grid node.
sigma (float, optional) – Conductivity [S/m] for walls that are not lossy metals (plain-PEC solids and PEC boundary walls); lossy-metal solids use their own material values.
mu (float, optional) – Relative permeability accompanying
sigma(default 1).roughness (SurfaceRoughness, optional) – Surface-roughness model for the same walls
sigmaapplies to; lossy-metal solids always use their own. It raises R_s per DFT bin, so the reported fraction is frequency-shaped by K(f) rather than scaled by a constant.bc_faces (tuple[str, ...]) – Domain-boundary faces to treat as PEC walls (
"xmin"…). Port faces must not be listed.name (str) – Monitor label.
sibc (SIBCSpec, optional) – When set (an SIBC run), the monitor reports the SIBC’s own dissipated power: surfaces come from the spec’s update topology and
R_s(f) = Re Z_s(f)from its fits.sigma/mu/roughness/bc_facesare ignored in that mode (the spec already resolved them). Wired automatically byAnalysisScatteringTDonwall_model="sibc"runs; not part of the recipe (re-derived on resume).masked_faces (tuple[str, ...])
wall_overrides (dict)
- load_result_dump(dump)#
Restore the accumulators from a
result_dump()(resume).The monitor must already be attached (fresh zero accumulators of the right shape).
_ref_binsis normally filled lazily on the firstrecord; loading it here pre-populates it, andrecordthen adds onto the restored slabs instead of re-zeroing them.- Parameters:
dump (dict)
- Return type:
None
- power_loss(P_in=1.0)#
Per-tag wall loss [W] for P_in Watts through the reference plane, plus
"total".- Parameters:
P_in (float)
- Return type:
dict
- raw_power_loss()#
Per-tag wall loss in (state scale)^2 W (pairs with
reference_power).Perturbative mode uses the roughness-corrected surface resistance; SIBC mode the real part of the operator’s own rational fit — the loss the solver actually extracted per bin.
- Return type:
dict
- result_dump()#
The result + the accumulators needed to persist and resume it.
Like a MonitorFieldFrequency’s DFT this is a fixed-size running sum, not an append stream — but unlike it, the RESULT is a reduction (P_loss/P_flow per tag) rather than the accumulators themselves. So the dump carries both:
fraction— what a reader serves. Recomputing it from the raw bins would need the mesh, the surface enumeration and the material resolution, i.e. a second place that produces (and could get wrong) the same number; writing whatdissipated_fractionreturns makes reader == monitor true by construction.h_bins/ref_bins— the raw accumulators, the resume source.
tagstravels as its own list because tags are heterogeneous (material ids are ints, BC walls are face-name strings) and the arrays are stored in its order.- Return type:
dict
- property dissipated_fraction: dict#
Per-tag
P_loss(f) / P_flow(f)(scale-free), plus"total".Full-model semantics on a symmetric run: losses double per symmetry plane and so does the reference power for planes cutting the reference cross-section — those cancel; a symmetry plane parallel to the reference cross-section contributes the remaining factor 2.
- property reference_power: ndarray#
P_flow(f) through the reference plane, in (state scale)^2 W.
FIT identity: P = 1/2 Re( sum e_hat*conj(h_hat) ) over the staggered patch pairs — no area weights in the grid-quantity basis. Only meaningful relative to
raw_power_loss().