magnelio.fields#

The field container every analysis hands out and every field source takes in: the six Yee-staggered components together with the grid they live on, in physical units. It is the coupling channel between an eigenmode result, a monitor snapshot and the initial or incident field of a transient run.

SurfaceRecording is the second coupling object: the tangential fields on a closed box over time, written by MonitorFieldSurface and replayed by SourceFieldSurface, with save/load as the exchange format between two runs.

Fields — the public container for a field snapshot on a grid.

FieldState couples the six Yee-staggered field components to the grid lines they live on, so a field can be read at its own sample positions or at arbitrary points, sliced and plotted, and handed from one analysis to another (an eigenmode into a time-domain start, a monitor snapshot into a plot).

FieldRecording and FieldSpectrum are series of such snapshots on one grid — over time, and over frequency as complex patterns — with the same vocabulary frame by frame.

SurfaceRecording is the other coupling object: the tangential fields on a closed box over time, written by MonitorFieldSurface and replayed by SourceFieldSurface.

class magnelio.fields.ComponentRecord(c1, c2, normals, values)#

One tangential component of one box face, on its own Yee positions.

Parameters:
c1, c2

Sample coordinates [m] along the face’s two tangent axes — node or cell-centre lines, whichever the component sits on.

Type:

np.ndarray

normals#

Normal coordinate(s) [m] of the sampled layer(s). One for E, which the face corrections need on the node plane; two for H, which they need half a cell outside the face — by the spacing of the replaying grid, so the replay interpolates between these two.

Type:

tuple of float

values#

(n_t, n1, n2) for one layer, (n_t, 2, n1, n2) for two.

Type:

np.ndarray

at(u, v, normal=None)#

The whole time series at in-plane points (u, v).

Bilinear in the face and, for a two-layer record, linear in the normal — exact on the recorded positions, so a replay on the recording’s own grid reproduces it rather than smoothing it.

Parameters:

normal (float | None)

Return type:

ndarray

class magnelio.fields.FaceRecord(name, axis, sign, plane, tangent_axes, components)#

One face of a SurfaceRecording.

Parameters:
  • name (str)

  • axis (int)

  • sign (float)

  • plane (float)

  • tangent_axes (tuple)

  • components (dict)

name#

Face name ("xmin" … "zmax") in the recording’s own frame.

Type:

str

axis#

Axis index of the face normal (0/1/2).

Type:

int

sign#

Outward normal component along axis (±1).

Type:

float

plane#

Node-plane coordinate of the face [m].

Type:

float

tangent_axes#

The two in-plane axis indices, in ascending order.

Type:

tuple of int

components#

The four tangential components — E in [V/m], H in [A/m].

Type:

dict[str, ComponentRecord]

resample(comp, u, v, normal=None)#

The time series of comp at in-plane points (u, v).

Parameters:
  • comp (str)

  • normal (float | None)

Return type:

ndarray

class magnelio.fields.FieldRecording(grid, times, *, dt=None, **components)#

Frames of a time-domain field on one grid.

Parameters:
  • grid (GridLines) – The grid the samples live on (a monitor’s region as its own grid).

  • times (array_like) – Instants [s] of the electric-field frames, ascending.

  • dt (float, optional) – The time step [s]. The magnetic field of a leapfrog march is sampled half a step after the electric field; times_h states those instants. None when the recording did not come from a march.

  • **components (array_like) – Physical field arrays, E in V/m and H in A/m, each shaped (n_frames, *Yee shape) — a subset of Ex Ey Ez Hx Hy Hz.

at_time(t)#

The frame nearest to t [s].

Parameters:

t (float)

Return type:

FieldState

index_of(t)#

Index of the frame nearest to t [s].

Parameters:

t (float)

Return type:

int

plot(component='E', *, t=None, frame=None, **kwargs)#

Plot one frame on a slice plane.

Parameters:
  • component (str) – "E"/"H" for a vector plot or magnitude, "Ez", … for one component.

  • t (float, optional) – Instant [s]; the nearest frame is drawn.

  • frame (int, optional) – Frame index (default 0; exclusive with t).

  • **kwargs – Passed to magnelio.fields.FieldState.plot() — the plane (normal, position), plot_type, ax, geometry and the rest.

Return type:

fig, ax

property times: ndarray#

Instants [s] of the electric-field frames.

property times_h: ndarray#

times + dt/2.

Type:

Instants [s] of the magnetic-field frames

class magnelio.fields.FieldSpectrum(grid, frequencies, **components)#

Complex frames of a field on one grid, one per frequency.

Parameters:
  • grid (GridLines)

  • frequencies (array_like) – Frequencies [Hz] of the frames.

  • **components (array_like) – Complex field arrays, E in V/m and H in A/m (per √W for a monitor’s pattern), each shaped (n_frames, *Yee shape).

at_frequency(f)#

The (complex) frame nearest to f [Hz].

Parameters:

f (float)

Return type:

FieldState

index_of(f)#

Index of the frame nearest to f [Hz].

Parameters:

f (float)

Return type:

int

plot(component='E', *, f=None, frame=None, phase=None, **kwargs)#

Plot one frame on a slice plane.

Parameters:
  • component (str) – "E"/"H" for a vector plot or magnitude, "Ez", … for one component.

  • f (float, optional) – Frequency [Hz]; the nearest frame is drawn.

  • frame (int, optional) – Frame index (default 0; exclusive with f).

  • phase (float, optional) – Instant of the complex pattern in degrees, Re(F · exp(+j·phase)). Default: the instant of maximum energy on the slice (see FieldState.plot()). It does not act on "S": a time-averaged power density has no instant.

  • **kwargs – Passed to magnelio.fields.FieldState.plot().

Return type:

fig, ax

snapshot(f=None, *, frame=None, phase=0.0)#

The real field Re(F · exp(+j·phase)) of one frame, phase in degrees.

Parameters:
  • f (float | None)

  • frame (int | None)

  • phase (float)

property f: ndarray#

Alias of frequencies.

property frequencies: ndarray#

Frequencies [Hz] of the frames.

class magnelio.fields.FieldState(grid, Ex, Ey, Ez, Hx, Hy, Hz)#

Electric and magnetic field on a grid, sampled at the Yee positions.

Build one from physical component arrays (the constructor), from a function of position (from_function()) or as zeros (zeros()); analyses hand them out (an eigenmode’s field()). The arrays are copied on construction.

Parameters:
  • grid (GridLines) – The grid the samples live on.

  • Ex (array_like) – Electric field [V/m] on the primal edges, Yee shapes (see the module table).

  • Ey (array_like) – Electric field [V/m] on the primal edges, Yee shapes (see the module table).

  • Ez (array_like) – Electric field [V/m] on the primal edges, Yee shapes (see the module table).

  • Hx (array_like) – Magnetic field [A/m] on the dual edges through the primal faces.

  • Hy (array_like) – Magnetic field [A/m] on the dual edges through the primal faces.

  • Hz (array_like) – Magnetic field [A/m] on the dual edges through the primal faces.

Examples

>>> field = FieldState.from_function(
...     grid, E=lambda x, y, z: (0 * x, 0 * y, np.sin(np.pi * x / L))
... )
>>> field.Ez.shape, field.positions("Ez")[0][:3]
classmethod from_function(grid, *, E=None, H=None)#

Sample vector functions of position on the Yee positions.

Parameters:
  • grid (GridLines)

  • E (callable, optional) – fn(x, y, z) -> (fx, fy, fz) with x, y, z arrays of one shape (the sample positions of the component being filled) and each returned component broadcastable to it. The function is called once per component with that component’s own positions; None leaves the field zero.

  • H (callable, optional) – fn(x, y, z) -> (fx, fy, fz) with x, y, z arrays of one shape (the sample positions of the component being filled) and each returned component broadcastable to it. The function is called once per component with that component’s own positions; None leaves the field zero.

Return type:

FieldState

classmethod zeros(grid, dtype=<class 'float'>)#

A zero field on grid.

Parameters:

grid (GridLines)

Return type:

FieldState

at(points)#

E and H at arbitrary points, interpolated per component.

Each component is interpolated trilinearly between its own samples, so the staggering is honoured exactly; within half a cell of the domain boundary — where a component has no sample on one side — the interpolation is continued linearly.

Parameters:

points (array_like, shape (n, 3) or (3,)) – Positions [m] inside the grid’s bounding box.

Returns:

E, H – The field vectors at the points.

Return type:

np.ndarray, shape (n, 3)

cell_centred(components=None, corners=None)#

Components averaged onto the cell centres (cell_centres).

Parameters:
  • components (sequence of str, optional) – Subset of the six field names; default all. "Sx", "Sy", "Sz" are the Poynting vector (poynting()), derived here from all six.

  • corners (tuple of tuple, optional) – Two opposite corners [m] of a sub-box; default the whole grid.

Returns:

{name: array} with shape (nx, ny, nz) of the box.

Return type:

dict[str, np.ndarray]

component(name)#

The physical samples of one component (a new array).

Parameters:

name (str)

Return type:

ndarray

energy()#

The stored energy [J] of the field, full-model booking.

½ eᵀMε e + ½ hᵀMμ h on the field’s own samples, with the material operators of the region the field carries — a monitor’s frame does (live or read back from a project), an eigenmode or a field assembled by hand does not, and a RuntimeError says so. For a frame of a march, whose magnetic samples lead the electric ones by half a step, the magnetic term is the one the leapfrog conserves — h(t−dt/2)·Mμ·h(t+dt/2), formed from the frame alone through the discrete Faraday law — so a recording of the whole domain reproduces the run’s energy trace. A complex frame is read as an RMS phasor — a spectrum’s frames per 1 W CW are — and gives the time-averaged energy. Where the region is cut out of the domain, the dual patches on its boundary count with the half that lies inside, so the energies of two adjoining regions add up to that of their union. A model solved on a symmetry-reduced domain reports the joules of the whole model.

Return type:

float

flux(normal, position)#

Poynting flux [W] through the field’s cross-section at a plane.

The FIT pairing Σ e·h of the samples on the plane — the identity MonitorFluxTime records during a march, here on a recorded frame, over the region’s extent — positive along the axis; the plane snaps to the nearest grid node; the pairing takes the electric samples on that node plane and the magnetic ones in the cell above it, as the flux monitor does. A complex frame is read as an RMS phasor — a spectrum’s frames per 1 W CW are — and gives the time-averaged real power Re Σ e·h*, so on a matched line a spectrum’s flux is the transmitted watts per watt incident. Needs the region’s operators like energy(); a model solved on a symmetry-reduced domain reports the watts of the whole cross-section.

Parameters:
  • normal ({"x", "y", "z"}) – Normal axis of the plane.

  • position (float) – Position [m] along that axis.

Return type:

float

mirrored(*mirrors)#

The field continued across symmetry planes, on the extended grid.

A model declared with symmetry planes is solved on the reduced domain; this returns the field of the whole structure — the simulated part plus its mirror images — with every component continued by its own rule: across a magnetic (PMC) plane E continues like a polar vector (normal component odd, tangential even) and H like a pseudovector, across an electric (PEC) plane the roles swap. The staggering is kept: a sample sitting on the wall appears once, and the cell a magnetic wall bisects gets its centre sample from the wall value (zero for an odd component, the neighbour’s value for an even one).

Parameters:

*mirrors (MirrorSpec) – One plane each (magnelio.post._symmetry.MirrorSpec: axis, wall position, "PEC"/"PMC", side). A plane must bound the grid: on the wall for PEC, half the boundary cell outside it for PMC.

Return type:

FieldState

plot(component='E', *, 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, title=None, unit=None, colorbar=True)#

Plot the field on a slice plane.

The staggered components are averaged onto cell centres and rendered on the plane selected by normal and position. A complex field is drawn as the real snapshot at the instant of maximum energy on the slice.

Parameters:
  • component (str) – "E", "H" or "S" for a vector plot / vector magnitude; "Ex", "Hy", "Sz", … for a single component (scalar only). "S" is the Poynting vector (poynting()) — instantaneous for a real field, time-averaged for a complex one — and needs all six components.

  • normal ({"x", "y", "z"}, optional) – Normal axis of the slice plane; default the thinnest axis.

  • position (float) – 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) – Axis coordinates in mm instead of m.

  • cmap (str, optional)

  • geometry (GeometryModel, optional) – Cross-section overlay of the slice 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).

  • normalize_arrows (bool) – Unit-length arrows, colour = magnitude (vector).

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

  • title (str, optional) – Plot title; default names the component and the plane.

  • unit (str, optional) – Colour-bar unit label; default "V/m" / "A/m".

  • colorbar (bool, default True) – Draw the colour bar; False for a panel of a shared figure.

Returns:

  • fig (matplotlib.figure.Figure)

  • ax (matplotlib.axes.Axes)

positions(component)#

The 1-D coordinate vectors (x, y, z) of a component’s samples.

component(name)[i, j, k] sits at (x[i], y[j], z[k]).

Parameters:

component (str)

Return type:

tuple[ndarray, ndarray, ndarray]

poynting(corners=None, *, complex_product=False)#

The Poynting vector [W/m²] on the cell centres.

E × H formed on the cell centres both fields interpolate to (cell_centred()), so the staggering is honoured and the three components of the result live at one point. Unlike energy() and flux() this needs no material operators — any field states it.

A real frame is an instant and gives the instantaneous power density. A complex frame is an RMS phasor — a spectrum’s frames per 1 W CW are — and gives the time-averaged density Re(E × H*), with no further factor of a half, so its integral over a cross-section is the transmitted watts.

For the power through a plane, prefer flux(): that one is the exact FIT identity on the samples themselves. Summing this field over a cross-section instead needs the physical patch of every cell, which is not dx·dy at a boundary: at a magnetic wall — a PMC face or a magnetic symmetry plane — the wall lies half an outer cell beyond the outermost grid line, so a naive sum is short by that half cell on every such face and no warning fires. This method is the spatial distribution — where the power flows, not how much crosses a surface.

Parameters:
  • corners (tuple of tuple, optional) – Two opposite corners [m] of a sub-box; default the whole grid.

  • complex_product (bool, default False) – For a complex frame return E × H* itself instead of its real part: the real part is the time-averaged power density as before, the imaginary part the reactive power density of the stored near field. Ignored for a real frame.

Returns:

Shape (nx, ny, nz, 3) over the box, the trailing axis the vector components.

Return type:

np.ndarray

Raises:

KeyError – If the frame comes from a monitor that recorded only some of the six components — a magnetic field of zeros would otherwise read as a vanishing power density.

Notes

In a frame of a march the magnetic samples lead the electric ones by half a step, so the instantaneous product carries that half step; the time-averaged reading of a spectrum does not.

This is a density at a place, not a total, so unlike energy() and flux() it carries no full-model factor on a symmetry-reduced model: the values are the model’s own, and the pictures continue them across the planes.

real()#

The real part (the field of a complex mode at its zero-phase instant).

Return type:

FieldState

scaled(factor)#

A copy multiplied by a (possibly complex) scalar.

Return type:

FieldState

show(component='E', **kwargs)#

Interactive 3D view of the field on a cutting plane.

The geometry viewer with the field laid on its cut: the exposed cell layer as a coloured sheet ("E"/"H" magnitude, or one signed component such as "Ez") with arrows for a field group, walked through the volume by the position slider. See magnelio.plots.show_field() for the arguments — the plane (normal, position), geometry and mesh overlays, phase for a complex field, and the rendering mode.

Parameters:

component (str)

surface_current(mesh, *, tag=None, exclude_faces=None, **kwargs)#

The conductor’s surface current J_s = n × H [A/m].

One value per wall patch of mesh: the magnitude from the booking the wall loss uses — so power_loss() reproduces MonitorWallLoss — and the direction from n × H with the patch’s own outward normal, which on a curved conductor is the true surface normal and not a staircase axis.

The field must carry all three magnetic components over the whole grid of mesh; a monitor cut to a box cannot state a current on patches it does not cover, and says so.

Parameters:
  • mesh (Mesh)

  • tag (optional) – Restrict to one conductor (material id, or a boundary face name such as "zmin").

  • exclude_faces (tuple of str, optional) – Domain boundary faces whose conductor coverage is not a wall — a port plane, where the feed’s cross-section shows and the structure continues through the face. Left unset on a model whose conductor reaches a boundary, the call asks once which faces those are; () answers “none”.

  • **kwargs – Passed to the wall enumeration — bc_pec_faces=("zmin",) to include a PEC boundary wall.

Return type:

SurfaceCurrent

property Ex: ndarray#

E_x [V/m] on the x-edges, shape (Nx, Ny+1, Nz+1).

property Ey: ndarray#

E_y [V/m] on the y-edges, shape (Nx+1, Ny, Nz+1).

property Ez: ndarray#

E_z [V/m] on the z-edges, shape (Nx+1, Ny+1, Nz).

property Hx: ndarray#

H_x [A/m] on the x-faces, shape (Nx+1, Ny, Nz).

property Hy: ndarray#

H_y [A/m] on the y-faces, shape (Nx, Ny+1, Nz).

property Hz: ndarray#

H_z [A/m] on the z-faces, shape (Nx, Ny, Nz+1).

property cell_centres: tuple[ndarray, ndarray, ndarray]#

The 1-D cell-centre coordinates (xc, yc, zc) of cell_centred().

property grid: GridLines#

The grid lines the samples refer to.

property is_complex: bool#

Whether the samples are complex (a Bloch mode with a phase advance).

class magnelio.fields.SurfaceRecording(name, faces, times, half_step, bounds, open_faces)#

Tangential fields on a closed box, sampled over time.

Parameters:
  • name (str)

  • faces (dict)

  • times (ndarray)

  • half_step (float)

  • bounds (tuple)

  • open_faces (tuple)

name#

The recording monitor’s name.

Type:

str

faces#

The recorded faces, keyed by name.

Type:

dict[str, FaceRecord]

times#

Sample instants [s] of the E time base.

Type:

np.ndarray

half_step#

Time offset [s] of the H samples against times (half the leapfrog step of the recording run).

Type:

float

bounds#

((x0, x1), (y0, y1), (z0, z1)) — the box extent [m] in the recording’s own frame.

Type:

tuple

open_faces#

The faces that were actually recorded. Fewer than six means the box was left open at a PEC/PMC wall, and the recording is valid only where that wall continues.

Type:

tuple of str

Examples

>>> rec = monitor.recording()
>>> rec.duration, rec.interval
classmethod from_h5(group)#

Read a recording written by to_h5().

Return type:

SurfaceRecording

classmethod load(path)#

Read a recording written by save().

Return type:

SurfaceRecording

save(path)#

Write the recording to an HDF5 file.

The file is the exchange format between the two runs: the model that recorded it and the model that replays it need share nothing else, not even a project.

Examples

>>> rec.save("antenna.h5")
Return type:

None

time_weights(t, *, magnetic=False)#

Interpolation indices and weight for instant(s) t [s].

magnetic shifts the query by the recorded half step, so an H component is read on the time base it was sampled on.

Parameters:

magnetic (bool)

to_h5(group)#

Write the recording into an open HDF5 group.

Return type:

None

property centre: tuple#

Centre of the box [m] in the recording’s own frame.

property closed: bool#

Whether all six faces were recorded.

property duration: float#

Recorded time span [s].

property interval: float#

Sample interval [s] (the recording is uniformly sampled).

property size: tuple#

Edge lengths of the box [m].