magnelio.solver#

Solver components — the FIT time-domain engine and eigenmode results.

class magnelio.solver.EigenmodeResult(frequencies, modes, mesh, solver_info=<factory>)#

Result of a 3D cavity eigenmode analysis.

Parameters:
  • frequencies (np.ndarray) – Resonant frequencies [Hz], shape (n_modes,), ascending.

  • modes (list[FieldArrays]) – One FieldArrays per mode with E and H fields on the Yee grid. Field amplitudes are normalised so that e^T M_eps e = 1.

  • mesh (Mesh) – Reference mesh (grid, material library).

  • solver_info (dict) – Metadata: solver backend, number of iterations, residuals, etc.

field(mode=0)#

The mode’s field pattern as a FieldState.

The eigenvector is normalised to e^T M_eps e = 1, i.e. to a peak stored electric energy of 0.5 J; the physical amplitude follows from that, the spatial pattern is the physical content. A Bloch mode with a phase advance other than 0 or 180 degrees is complex.

Parameters:

mode (int) – Mode index (ascending in frequency).

plot(mode=0, 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)#

Plot a mode’s field pattern on a slice plane through the cavity.

The staggered Yee-grid components are interpolated onto cell centres and rendered on the plane selected by normal and position (magnelio.fields.FieldState.plot()). Mode fields are normalised eigenvectors, so the amplitudes are in arbitrary units; the spatial pattern is the physical content. A complex mode (Bloch phase advance other than 0 or 180 degrees) is drawn as the real snapshot at the instant of maximum field energy on the slice — the global phase of an eigenvector is arbitrary, and this choice makes the picture independent of it.

Parameters:
  • mode (int) – Mode index (ascending in frequency).

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

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

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

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

  • geometry (GeometryModel, optional) – Geometry for a 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 mode).

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

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

Returns:

  • fig (matplotlib.figure.Figure)

  • ax (matplotlib.axes.Axes)

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

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

The geometry viewer with a mode’s field laid on its cut and a slider over the modes (each labelled with its index and eigenfrequency), the position slider walking the layer through the cavity; a complex Bloch mode is shown at the instant of its maximum energy, with a phase slider to turn it. The result’s own mesh cuts the metal out of the sheet and names the symmetry planes the field is continued across, so the view shows the whole cavity. See magnelio.plots.show_field() for the arguments — frame= picks the mode shown first, the plane (normal, position), geometry, the rendering mode and the rest.

Parameters:

component (str) – "E", "H", or one component such as "Ez".

property n_modes: int#

Number of physical modes found.

class magnelio.solver.FITTimeDomainSolver(mesh, boundary_conditions=<factory>, ports=<factory>, sources=<factory>, total_time_steps=1000, dt=1e-12, verbose=True, energy_stop_db=None, port_signal_stop_db=None, port_signal_min_steps=None, max_time_steps=None, energy_check_interval=None, recorder=None, diagnostics=<factory>, monitors=<factory>, sink=None, backend='auto', precision=None, sibc=None)#

FIT leapfrog solver.

Parameters:
  • mesh (Mesh) – The simulation mesh.

  • boundary_conditions (dict) – Dict mapping face names to BC objects.

  • ports (list) – List of objects implementing the magnelio.ports.base.Port protocol (PortOperatorLumped, PortOperatorModal). Excitation is configured on each operator via set_excitation before run().

  • sources (list) – List of source objects (e.g. plane-wave TF/SF).

  • total_time_steps (int | None) – Number of leapfrog steps, or None for an unbounded run that marches until the energy criterion (or a graceful stop) ends it — None requires energy_stop_db.

  • dt (float) – Time step [s].

  • verbose (bool) – Print progress during simulation.

  • energy_stop_db (float | None) – Stop when energy decays by this many dB below peak.

  • port_signal_stop_db (float | None) – Stop when the modal-port |V| envelope decays by this many dB below its run peak — see the field comment.

  • max_time_steps (int | None) – Runtime cap for unbounded runs (absolute step bound; warns and stops when no criterion fired) — see the field comment.

  • recorder (object | None) – Optional PortSignalRecorder — receives V/I from each port at every step.

  • backend (str) – "auto" (default) runs on the GPU via CuPy when CuPy and a CUDA device are available and falls back to the NumPy CPU backend with a one-time notice otherwise; "cupy" requires the GPU (raises with a clear message when unavailable); "numpy" forces the CPU backend.

  • port_signal_min_steps (int | None)

  • energy_check_interval (int | None)

  • diagnostics (list)

  • monitors (list)

  • sink (object | None)

  • precision (str | None)

  • sibc (object | None)

load_state_dict(state)#

Restore a checkpoint captured by state_dict().

The solver must be constructed identically (same mesh, dt, ports and boundary conditions) to the one that produced state — those fix the constant operators; this call only repopulates the evolving state. Call before run(), which then continues the marching loop from n_completed.

Parameters:

state (dict)

Return type:

None

request_stop()#

Ask the marching loop to stop gracefully at the next step.

Cooperative and thread-safe by construction (a single bool flag): the loop checks it at the top of each iteration, so the in-flight step completes and the break lands on a consistent leapfrog pair. The run then persists a resume checkpoint (if a sink is attached) and returns with _aborted set. Wire a SIGINT handler to this for Ctrl-C, or call it from a monitor / GUI.

Return type:

None

run()#

Execute the leapfrog time-stepping loop.

Return type:

FieldArrays

setup()#

Pre-compute operators and allocate field arrays.

Return type:

None

state_dict()#

Capture the full leapfrog state for a bit-exact resume.

Gathers everything the marching loop mutates — the E/H field, the completed-step count and pre-decay energy peak, and every stateful boundary (CPML ψ) and port (Mur previous-values, TF/SF source buffer, exact DTBC convolution history), keyed by face and port name. Constant operators (material matrices, absorbing- boundary kernels) are re-derived on the resuming solver, never stored.

Return type:

dict

magnelio.solver.courant_dt(grid, accuracy='normal', min_effective_eps=None, min_effective_mu=None)#

Compute the maximum stable time step for the given grid.

Parameters:
  • grid (GridLines) – The mesh grid.

  • accuracy (str) – One of 'draft' (0.90), 'normal' (0.95), 'high' (0.99).

  • min_effective_eps (float or None) – Minimum effective ε_r across all active (non-PEC) edges, as returned by compute_min_effective_eps(). When < 1, reduces dt to account for increased local wave speed at conformal boundary edges.

  • min_effective_mu (float or None) – Minimum effective μ_r across all H-faces (conformal sub-cell faces have μ_eff < 1 because the geometric A_face_free / A_face reduction lowers M_μ while the primal-face area in the geometric factor stays nominal). When < 1, reduces dt by √(μ_eff_min) symmetrically with the ε factor.

Returns:

Time step dt in seconds.

Return type:

float

Raises:

ValueError – If accuracy is not recognized.

magnelio.solver.spectral_dt(mesh, accuracy='normal', m_eps=None, m_mu=None)#

Sharp CFL limit from the spectral radius of the update operator.

The exact leapfrog stability limit is

dt_max = 2 / sqrt(lambda_max(M_eps^-1 C^T M_mu^-1 C))

restricted to the DOFs the solver actually updates: PEC-masked edges and frozen M_eps = 0 edges are removed, frozen H-faces enter through the exact 1/M_mu = 0. This replaces the sqrt(eps_min * mu_min) worst-case product of courant_dt(), which assumes the worst edge, the worst face and the smallest cell coincide — on conformal meshes they never do, and the product under-estimates the stable step by more than an order of magnitude (the measured limit sits at 67-75 % of the geometric Courant value where the heuristic reports 2-4 %).

lambda_max is measured with a matrix-free Lanczos iteration on the symmetrised operator D^-1/2 A D^-1/2. If Lanczos does not converge, the row-sum (Gershgorin) upper bound on the same operator is used instead — strictly safe, typically within ~20 % of the exact value. The measured lambda_max is cached on the mesh, so repeated calls (report + run) pay the eigensolve once.

Parameters:
  • mesh (Mesh) – Fully populated mesh.

  • accuracy (str) – Safety factor selector, same map as courant_dt().

  • m_eps (np.ndarray, optional) – Prebuilt mass matrices (build_M_eps / build_M_mu). Built from the mesh when omitted.

  • m_mu (np.ndarray, optional) – Prebuilt mass matrices (build_M_eps / build_M_mu). Built from the mesh when omitted.

Returns:

Time step dt in seconds.

Return type:

float