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.MonitorFarField(freqs, name='far_field', margin_cells=3)#

Far-field (antenna pattern) monitor on an automatic Huygens box.

Records the surface DFT of the tangential fields on a closed box placed margin_cells inside 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.

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.MonitorFarField(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 cannot record() (it is not attached to a grid).

Parameters:

dump (dict)

Return type:

MonitorFarField

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 at t + 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:

FarFieldResult

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 f: ndarray#

Frequency axis [Hz].

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 as Brick.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 be None (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_max of 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 renormalize is 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 in from_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 DFT accumulators.

Return type:

None

component(name)#

Return DFT data for a single component.

Parameters:

name (str) – Component name, e.g. "Ez".

Returns:

Shape (n_freqs, <spatial dims>), complex128.

Return type:

np.ndarray

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)#

Interactive frequency slider for DFT field snapshots (Jupyter notebook).

Requires ipywidgets. The colour range (scalar) or arrow scale (vector) is fixed across all frequencies for visual stability.

Parameters:
  • component (str) – "E" or "H" for vector / amplitude plots. "Ex", "Ez", … for individual component scalar plots.

  • normal ({"x", "y", "z"}, optional) – Slice-plane normal for 3D monitors (required there); the slider then runs over frequency at a fixed slice plane.

  • position (float) – Slice-plane position along normal [m] (3D monitors only).

  • plot_type (str) – "vector", "color", or "contour".

  • phase (float) – Phase angle [degrees] for instantaneous field extraction.

  • scale_mm (bool) – Use millimetres for spatial axes.

  • cmap (str or None) – Colormap (None = auto-select).

  • geometry (list, optional) – Geometry objects for cross-section overlay (2D only).

  • flip (bool) – Swap horizontal and vertical axes (2D only).

  • density (int) – Target arrows per axis (vector mode only).

  • threshold (float) – Suppress arrows below this fraction of peak (vector mode only).

  • vmax (float or None) – Clip arrow length at this magnitude (vector mode only).

  • figsize ((float, float) or None) – Figure size in inches (width, height).

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, **kwargs)#

Plot DFT field data.

For 0D monitors: line plot over frequency (ignores f / f_index). For 2D monitors: colour-map, contour, or quiver plot at a frequency. For 3D monitors: the same plane plots on a slice selected with normal and position.

Parameters:
  • component (str) – "E" or "H" for vector / amplitude plots. "Ex", "Hz", … for a single component (scalar only).

  • f (float, optional) – Frequency [Hz]. Nearest is used.

  • f_index (int, optional) – Frequency index (overrides f).

  • normal ({"x", "y", "z"}, optional) – Slice-plane normal for 3D monitors (required there). For a 2D monitor it may be given for validation but is redundant.

  • position (float) – Slice-plane position along normal [m]; snapped to the nearest cell-centre plane (3D monitors only).

  • plot_type (str) – "vector", "color", or "contour".

  • phase (float) – Phase angle [degrees] for extracting the instantaneous field from complex phasors: Re(F · exp(j·phase·π/180)). Ignored for amplitude plots (component="E"/"H").

  • ax (matplotlib.axes.Axes, optional)

  • scale_mm (bool)

  • cmap (str or None) – Colourmap (None = auto-select).

  • geometry (list, optional) – Geometry objects for cross-section overlay (2D only).

  • flip (bool) – Swap horizontal and vertical axes (2D only).

  • 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.

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 * dt and H-fields at t_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.

With an interval, steps off the stride return before the cell-centre interpolation — 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, so the renormalised fields are exactly the fields of a 1 W CW excitation at each monitor frequency (gated by test_port_units.py:: test_frequency_monitor_fields_per_1w_cw).

The source spectrum is computed in the same Fourier convention as the internal DFT accumulator (exp(+jωt) with dt integration weight), 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

property data: dict[str, ndarray]#

Recorded fields per 1 W incident CW power.

Each bin is divided by the spectrum of the run’s excitation, so E is in V/m and H in A/m, both per √W of incident power. Raises if no source reference is available (see renormalize()); data_raw returns the undivided bins instead.

Returns:

Keys are component names. Values have shape (n_freqs, <spatial dims>), complex128. For a 0D monitor the spatial dims are squeezed away, giving shape (n_freqs,).

Return type:

dict[str, np.ndarray]

property data_raw: dict[str, ndarray]#

Raw DFT bins, in field units x seconds.

The running sum Σ field(t_n)·exp(+jω t_n)·dt as accumulated, i.e. the field folded with the spectrum of the excitation waveform. Always returns these, whether or not a source reference is set — data is the physical counterpart.

Returns:

Same layout as data.

Return type:

dict[str, np.ndarray]

property f: ndarray#

Frequency array [Hz].

property is_renormalized: bool#

Whether 1 W renormalization has been applied.

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 as Brick.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 be None (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 in from_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

component(name)#

Return recorded data for a single component.

Parameters:

name (str) – Component name, e.g. "Ez".

Returns:

Shape (n_times, <spatial dims>).

Return type:

np.ndarray

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)#

Interactive time-step slider for field snapshots (Jupyter notebook).

Requires ipywidgets. The colour range (scalar) or arrow scale (vector) is fixed across all time steps for visual stability.

Parameters:
  • component (str) – "E" or "H" for vector / amplitude plots. "Ex", "Ez", … for individual component scalar plots.

  • normal ({"x", "y", "z"}, optional) – Slice-plane normal for 3D monitors (required there); the slider then runs over time at a fixed slice plane.

  • position (float) – Slice-plane position along normal [m] (3D monitors only).

  • plot_type (str) – "vector", "color", or "contour".

  • scale_mm (bool) – Use millimetres for spatial axes.

  • cmap (str or None) – Colormap (None = auto-select).

  • geometry (list, optional) – Geometry objects for cross-section overlay (2D only).

  • flip (bool) – Swap horizontal and vertical axes (2D only).

  • density (int) – Target arrows per axis (vector mode only).

  • threshold (float) – Suppress arrows below this fraction of peak (vector mode only).

  • vmax (float or None) – Clip arrow length at this magnitude (vector mode only).

  • figsize ((float, float) or None) – Figure size in inches (width, height).

load_state_dict(sd)#

Restore the target-time 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, **kwargs)#

Plot recorded field data.

For 0D monitors: line plot over time (ignores t / t_index). For 1D monitors: line plot at a specific time. For 2D monitors: colour-map, contour, or quiver plot at a time. For 3D monitors: the same plane plots on a slice selected with normal and position.

Parameters:
  • component (str) – "E" or "H" for vector magnitude or vector plot. "Ex", "Hy", … for a single component (scalar only).

  • t (float, optional) – Time point [s]. Nearest recorded time is used.

  • t_index (int, optional) – Time index (overrides t).

  • normal ({"x", "y", "z"}, optional) – Slice-plane normal for 3D monitors (required there). For a 2D monitor it may be given for validation but is redundant.

  • position (float) – Slice-plane position along normal [m]; snapped to the nearest cell-centre plane (3D monitors only).

  • plot_type (str) – "vector", "color", or "contour".

  • ax (matplotlib.axes.Axes, optional)

  • scale_mm (bool)

  • cmap (str or None) – Colourmap (None = auto-select).

  • geometry (list, optional) – Geometry objects for cross-section overlay (2D only).

  • flip (bool) – Swap horizontal and vertical axes (2D only).

  • 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.

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 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 its data/t accumulation is unchanged.

Return type:

tuple[list[float], dict[str, ndarray]]

record(fields, n, t, dt)#

Record a snapshot if t matches a requested time point.

Called at every time step by the solver.

Parameters:
  • n (int)

  • t (float)

  • dt (float)

Return type:

None

state_dict()#

Checkpoint the target-time cursor for a bit-exact resume.

Only _next_idx is state a continuation must restore — the recorded snapshots themselves live in the run’s results.h5 (streamed), and the region is re-resolved on attach. _next_idx equals the number of snapshots recorded so far, so it also drives the monitor-stream truncation on resume.

Return type:

dict

property data: dict[str, ndarray]#

All snapshots stacked along a leading time axis.

Returns:

Keys are component names (e.g. "Ex"). Values have shape (n_times, <spatial dims>). For a 0D monitor the spatial dims are empty, giving shape (n_times,).

Return type:

dict[str, np.ndarray]

property region: MonitorRegion | None#

Resolved grid region (available after attach()).

property t: ndarray#

Actually recorded time points [s].

class magnelio.monitors.MonitorFluxTime(plane, 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:
  • plane (tuple[str, float]) – The cross-section plane as a (normal, position) pair: normal axis ('x'/'y'/'z') and its position along that axis [m], e.g. ("z", 5e-3) — the same plane vocabulary as MonitorWallLoss.reference_plane. Snapped to the nearest grid node.

  • name (str) – Monitor label.

Examples

>>> flux = MonitorFluxTime(plane=("z", 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 to results.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_energy accumulation 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_idx equals 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 power: ndarray#

Instantaneous Poynting flux [W] vs. time.

property t: ndarray#

Time axis [s].

property total_energy: float#

Time-integrated Poynting energy [J].

class magnelio.monitors.MonitorWallLoss(freqs, reference_plane, 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].

  • reference_plane (tuple[str, float]) – (axis, position) of the power-reference cross-section, e.g. ("z", 1e-3) — a plane between the excited port and the lossy walls. 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 sigma applies 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_faces are ignored in that mode (the spec already resolved them). Wired automatically by AnalysisScatteringTD on wall_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_bins is normally filled lazily on the first record; loading it here pre-populates it, and record then 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 what dissipated_fraction returns makes reader == monitor true by construction.

  • h_bins/ref_bins — the raw accumulators, the resume source.

tags travels 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 f: ndarray#

Frequency axis [Hz].

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().