Core (magnelio)#

The model container and run vocabulary, the problem classes, the project-store entry points and the progress-output default — the fourteen names every simulation script starts from. Everything else lives in the domain namespaces.

Magnelio — Python library for full-wave 3D electromagnetic field simulation.

This namespace is the core: the model container and run vocabulary (GeometryModel, Material, Mesh/MeshControl, BoundaryConditions, Excitation), the problem classes (Analysis*), the project-store entry points (open_project, resume) and the progress-output default (set_verbosity). Every other public name lives in exactly one domain namespace — magnelio.geo, magnelio.materials, magnelio.mesh, magnelio.boundaries, magnelio.ports, magnelio.sources, magnelio.monitors, magnelio.fields, magnelio.circuit, magnelio.signals, magnelio.solver, magnelio.analysis, magnelio.post, magnelio.plots, magnelio.io, magnelio.constants — and underscore modules are internal.

class magnelio.AnalysisEigenmode(mesh, verbose=None, project=None, geometry=None, params=None, backend='auto', precision=None, method='auto', solver=None, n_modes=5, sigma=None, phase_advance_deg=None)#

High-level eigenmode analysis for 3D cavities.

Parameters:
  • mesh (Mesh) – The simulation mesh, carrying the boundary closure it was built with (declared on the GeometryModel or on Mesh.from_grid). PEC, PMC and Periodic faces are meaningful for an eigenmode problem; CPML is rejected.

  • n_modes (int) – Number of physical resonant modes to find (default 5).

  • solver (str or None) – Inner-solve strategy. None → auto-dispatch (SuperLU for small, AMG-CG for large). "arpack-amg" forces AMG, "arpack-superlu" forces SuperLU.

  • sigma (float or None) – ARPACK shift σ [(rad/s)²]. None → auto-estimated from geometry and materials; if a solve returns fewer physical modes than requested, the shift is raised automatically and the modes found so far are kept. Pass an explicit value (sigma=(2*pi*f_estimate)**2) to pin the shift; a run that still returns fewer than n_modes modes emits a RuntimeWarning either way.

  • verbose (bool, optional) – Print solver progress. None (the default) follows magnelio.set_verbosity().

  • project (object | None) – Project-store hooks: project names a directory the model and the result are written into, geometry the model to store with it, params a free dict recorded alongside.

  • geometry (object | None) – Project-store hooks: project names a directory the model and the result are written into, geometry the model to store with it, params a free dict recorded alongside.

  • params (dict | None) – Project-store hooks: project names a directory the model and the result are written into, geometry the model to store with it, params a free dict recorded alongside.

  • backend (str) – The arguments every analysis shares. The eigenmode solve runs in double precision on the CPU (ARPACK), so only the defaults "auto" / None are accepted here.

  • precision (str | None) – The arguments every analysis shares. The eigenmode solve runs in double precision on the CPU (ARPACK), so only the defaults "auto" / None are accepted here.

  • method (str) – The arguments every analysis shares. The eigenmode solve runs in double precision on the CPU (ARPACK), so only the defaults "auto" / None are accepted here.

  • phase_advance_deg (float, dict or None) – Bloch phase advance [degrees] across the mesh’s "Periodic" face pair — the phase by which the field in one period leads the next. Sweeping it from 0 to 180 traces the dispersion diagram of the infinite periodic structure. A number serves a single periodic axis; with several, pass {axis: degrees}. None (default) is zero phase. Phases other than 0 and 180 degrees give complex mode fields and need the default (SuperLU) solver.

run()#

Execute the eigenmode analysis.

Returns:

Frequencies [Hz] and E/H field patterns for each mode. When project is set, the model and the eigenmode result are written into the project directory and a read-only Project reader is returned instead — the same object open_project() returns, and it answers to the result’s own members (frequencies, n_modes, field, show, plot), so a script reads the same either way; the result itself is .eigenmodes. Eigenmode analysis has no time-marching state, so it is a one-shot result — the streaming/resume machinery does not apply.

Return type:

EigenmodeResult or Project

property boundary_conditions: dict[str, str]#

The mesh’s boundary closure as {face: type_str}.

n_modes: int = 5#
phase_advance_deg: float | dict[str, float] | None = None#
sigma: float | None = None#
class magnelio.AnalysisScatteringTD(mesh, verbose=None, project=None, geometry=None, params=None, backend='auto', precision=None, method='auto', solver=None, f_max=None, ports=None, elements=None, sources=None, monitors=<factory>, port_model='modal', port_source='frozen', wall_model='perturbative', wall_sigma=None, wall_mu=1.0, wall_roughness=None, f_min=0.0, n_freq=201, waveform=None, band_options=None)#

High-level FIT-TD scattering analysis for multi-port networks.

Parameters:
  • mesh (Mesh)

  • ports (list of port spec, optional) – None (default) uses the declarative ports the mesh carries (declared before meshing via add_port()). Passing ports= overrides the mesh declarations completely: declarative ports (PortWaveguide / PortAnalytical) and/or specs (PortSpecCoax / PortSpecRectWG / PortSpecNumerical / PortSpecMultiConductor / PortSpecLumped), freely mixed. Declarative ports are resolved into concrete specs at construction time (self.ports holds the resolved list). Names must be unique.

  • elements (list of LumpedElement, optional) – Passive lumped circuit elements. None (default) uses the declarative elements the mesh carries (declared before meshing via add_element()); passing elements= overrides them completely. Elements act on the fields as pure loads: they are never excited, never recorded, and do not appear in the S-matrix. Their labels share the port-label namespace.

  • f_max (float, optional) – Upper band edge of the analysis [Hz]. None (default) uses the design frequency the mesh was generated for (mesh.f_max, recorded by from_geometry()); a mesh without one (Mesh.from_grid) requires an explicit value. An explicit value above the mesh’s design frequency warns — the grid undersamples the upper band. Single frequency source: sizes the default frequency axis (see f_axis), sets the mode-calculation frequency of the modal port builder, and parameterises the default excitation waveform.

  • f_min (float, default 0.0) – Lower band edge [Hz]. The default frequency axis never starts below f_max / n_freq (power waves are undefined at DC), so f_min = 0 yields a first point at f_max / n_freq. On the band pipeline set a real lower edge: the pulsed band drive needs spectral roll-off room below the first axis point, so an axis reaching toward DC forces an O(1/f_axis[0])-long pulse — the auto-sizing raises with the recommended value instead of hanging in the kernel build (see _band_setup).

  • n_freq (int, default 201) – Number of points on the default frequency axis.

  • waveform (magnelio.signals.Waveform, optional) – Source waveform applied to every excited (port, mode). Default (None): derived per excited mode — the lower band edge is max(f_cutoff, f_min) with f_cutoff the excited mode’s cut-off frequency (zero for TEM and lumped ports). A zero lower edge yields a DC-inclusive WaveformGaussian; a positive one a band-limited WaveformGaussianModulated over [max(f_cutoff, f_min), f_max], which keeps the pulse energy of TE/TM excitations above cut-off. Raises at run time when f_max does not exceed the excited mode’s cut-off. An explicit waveform whose f_max exceeds the analysis band warns.

  • monitors (iterable, default ()) – Field monitors forwarded to every FITTimeDomainSolver run.

  • verbose (bool, default True) – Print solver progress. None (the default) follows magnelio.set_verbosity().

  • port_model ({"modal", "band", "auto"}, default "modal") –

    Which port pipeline terminates and measures the run.

    "modal" (production default) — one modal port operator per spec: exact DTBC on every certified uniform chain (homogeneous TEM, numerical TE/TM — port floors −124…−166 dB), modal Mur-1st on the rest. On a transversally inhomogeneous line (microstrip, layered substrates) the QTEM fundamental fails the uniform-chain certificate and falls back to Mur — measured −26…−39 dB |S11| on a realistic shielded microstrip at |S21| errors below 0.01 dB. Fast (seconds), full time-domain power-wave access, no lower-band-edge restriction; the accepted trade for routine QTEM work. A verbose notice names the Mur-fallback channels with the measurement behind each one, states the floor they trade away, and prices the band alternative for this model — including the axis start it would require, so the switch it suggests is one that runs.

    "band" — the broadband band-subspace DTBC pipeline: the mode family is tracked over the analysis band, one operator terminates the whole band reflection-free (measured microstrip floors −171…−211 dB), and one pulsed record per excitation is decomposed per frequency with the true mode at every axis point. Requires every port to be a PortSpecMultiConductor, a strictly positive band (f_min!), and pays a kernel-build phase plus a longer record — for reflection-critical work, not the routine default. result.a()/b() are unavailable on band results.

    "auto" — build the modal operators and inspect their termination certificates: certified-everywhere runs use the modal pipeline, any Mur-fallback multi-conductor channel switches the run to the band pipeline. Mixing an uncertified multi-conductor port with non-multi-conductor specs raises.

  • band_options (dict, optional) – Expert overrides for the band pipeline. Recognised keys: f_band ((f_lo, f_hi) [Hz] subspace band; default derived from the frequency axis with 25 % roll-off guard), n_grid (mode-family tracking points, default 25), n_syn (synthesis-window steps; default auto-sized from the roll-off widths and doubled on a compactness failure), n_kernel_init (initial ghost-kernel length; default the next power of two above the planned run), svd_tol (subspace-rank threshold, default 1e-8), skirt (spectral amplitude at the band edges, 1e-7), dc_anchor (extend the fundamental channel’s excitation direction down to DC so the pulse length follows the measurement span rather than the axis start, default True).

  • project (str or Path, optional) – When set, run() writes the model and each excitation’s results into this project directory and returns a read-only Project reader instead of an in-memory ScatteringTDResult. Pointing at an existing project adds the new excitations as runs (fill-in) without rewriting the model. Both port pipelines stream; a band run additionally stores each port’s chain and recording profiles, so its S-matrix is re-derived on read like any other. result.a()/b() stay unavailable on a band run.

  • geometry (GeometryModel, optional) – Source geometry to persist (exact BREP + tessellated STL) when project is set. Optional — the mesh alone suffices for post-processing; geometry adds ParaView overlays and re-meshing.

  • backend ({"auto", "numpy", "cupy"}, default "auto") – Array backend of every FIT-TD run. "auto" uses the GPU (CuPy) when CuPy and a CUDA device are available and falls back to the NumPy CPU backend with a one-time notice; "cupy" requires the GPU; "numpy" forces the CPU. Not persisted in the project recipe — a resumed run re-resolves "auto" on the machine it runs on.

  • precision ({"single", "double", None}, default None) – Scalar precision of the FIT-TD time loop (fields + update coefficients). None resolves to the MAGNELIO_PRECISION environment variable else "single" (float32), the production default — it matches commercial FIT/FDTD tools, halves memory and lifts GPU throughput on consumer FP64-crippled cards, and its ~1e-7 field floor sits three-to-four orders of magnitude below the discretisation error. "double" (float64) is the opt-in for high-Q (Q ≳ 1e4-1e5) or high-dynamic-range studies. Orthogonal to backend. The DFT/S-parameter accumulators, the modal-port solve and the geometry pipeline stay double regardless. Persisted in the project recipe so a resumed run keeps its precision.

  • wall_model ({"perturbative", "sibc"}, default "perturbative") –

    Conductor-loss model of the TD runs.

    "perturbative" (default) — walls are lossless PEC in the field solve; conductor loss is evaluated after the fact by wall_loss_Q / MonitorWallLoss. Nothing changes against earlier versions.

    "sibc" — broadband Leontovich surface impedance in the update itself: every conductor wall (lossy-metal solids with their own σ/µ/roughness; plain-PEC solids and PEC boundary walls with the wall_sigma override) damps the field through a passive rational Z_s(ω) fit over the analysis band [f_axis[0], f_max] — S-parameters become lossy and self-consistent (loaded Q, attenuation in |S21|). Requires lossy-metal materials and/or wall_sigma; raises loudly otherwise. Port planes stay lossless (modes of the lossless cross-section — a lossy line’s α shows up in S21 over length, not in the port model); faces hosting ports carry no wall. AnalysisEigenmode keeps the perturbative route (non-goal).

  • wall_sigma (float, optional) – Conductivity [S/m] for SIBC walls that are not lossy metals (plain-PEC solids and PEC boundary-condition walls) — the same override rule as wall_loss_Q / MonitorWallLoss. Lossy-metal solids always use their own material values.

  • wall_mu (float, default 1.0) – Relative permeability accompanying wall_sigma.

  • wall_roughness (SurfaceRoughness, optional) – Roughness model for the same walls wall_sigma applies to; enters the SIBC as the causally completed K(f)·R_s fit.

  • params (dict | None)

  • method (str)

  • solver (str | None)

  • sources (Sequence | None)

  • port_source (str)

run(f_axis=None, excited=None, accuracy='normal', energy_stop_db=70.0, total_time_steps=None, taper_signals=False, checkpoint_interval=None, port_signal_stop_db='auto', max_time_steps='auto', excitations=None)#

Run one FIT-TD simulation per excited (port, mode) and merge.

excitations= is not accepted here: a scattering analysis excites channels, one independent run each (excited); simultaneous excitations belong to the general time-domain analysis.

Parameters:
  • f_axis (np.ndarray, optional) – Target frequencies [Hz], shape (Nf,). Strictly positive. Default: the constructor-derived axis (see the f_axis property).

  • excited (iterable, optional) – [(port_name, mode_idx), ...] or a list of bare port_name strings (mode 0 implied). Default: the first port at mode 0.

  • accuracy ({"draft", "normal", "high"}, default "normal") – Courant safety factor.

  • energy_stop_db (float, default 70.0) – Stop each TD run when stored EM energy has decayed by this many dB below peak. Calibrated against the well-absorbed TEM-line case: at 70 dB the truncation residual on V/I falls below ~7e-4 of peak, which keeps the rectangular-DFT sidelobes on |S21| below ~0.02 dB; the port floors themselves sit at the DTBC level (−130 dB class on certified lines). More aggressive cuts (40 dB) leave a residual of ~1 % that produces |S21| > 1 artefacts until either the run is extended or the rectangular window is replaced (see taper_signals). Set to None to disable the early stop; the run is then bounded by total_time_steps (an explicit value, or the auto-sized estimate as a fallback cap when both are left open). Not applicable on the band pipeline (see Notes).

  • total_time_steps (int, optional) – Exact leapfrog step count. Default (None): the run is unbounded and marches until a stop criterion fires (energy_stop_db or port_signal_stop_db, whichever first), backstopped only by the generous max_time_steps runtime cap — the auto-sized step estimate sets the energy-check cadence, never the length, so a slowly decaying structure keeps resolving instead of being cut off at a heuristic timeout. Pass an explicit value to force a fixed-length run (it also disables the runtime cap and the stall watchdog: an explicit length is a user decision). The check cadence is unchanged from the historical bounded default, so a well-absorbed run stops at the very same step (its S-parameters are unchanged).

  • taper_signals (bool, default False) – Apply a symmetric Tukey window (alpha = 0.05) to every recorded V/I time-series before the S-parameter DFT. Suppresses the rectangular-window sidelobes caused by a non-vanishing residual at the truncation edge — set to True if the default rectangular DFT shows |S|>1 ripple on a passive structure and you do not want to push energy_stop_db higher. On the project-store path the flag is recorded per run and the reader applies the same window when it derives the S-parameters.

  • port_signal_stop_db (float, None or "auto", default "auto") – Stop criterion: end each TD run when the modal-port |V| envelope has decayed by this many dB below its run peak. The robust termination for shielded lossless structures whose stored energy plateaus on TM-cut-off cavity content that no port can reach (energy_stop_db then never fires); the S-parameters depend only on the port signals this criterion watches. Whichever criterion fires first ends the run. "auto" (default) resolves to 60 dB when at least one modal port is present and to disabled on lumped-only runs (the criterion needs a modal |V| envelope to watch); None disables it explicitly. The criterion only arms once the auto-sized step estimate is reached, so it cannot fire in the quiet gap before the response reaches the far ports — runs that stop on the energy criterion earlier are untouched by it. Band-edge (cut-off) ring-down can hold the envelope at a plateau just above the threshold — it decays algebraically, so the threshold is then unreachable; a stall watchdog detects this (envelope slope provably too flat to reach the threshold before max_time_steps), accepts the plateau as the effective floor, and stops with a RuntimeWarning stating the achieved level. Not applicable on the band pipeline.

  • max_time_steps (int, None or "auto", default "auto") – Runtime cap for unbounded runs: if no stop criterion has fired by this step, the run ends with a RuntimeWarning (results truncated, resumable on the project-store path). "auto" sizes the cap at 40× the auto step estimate — roughly 10³ diagonal transits, far beyond any converging run; in ring-down terms it accommodates a loaded Q of about 900 · (structure size / wavelength) before a 60-dB decay is cut short. None removes the cap (march forever — watch it live or resume; also disables the stall watchdog, which projects against this cap). Ignored when total_time_steps is set.

  • checkpoint_interval (int, optional) – Only on the project-store path (project=): minimum number of leapfrog steps between periodic resume checkpoints (runs/<name>/checkpoint.h5), overwritten via temp+rename so a live reader never sees a partial file. Default: about an eighth of the auto-sized run length (≈ eight checkpoints). A final checkpoint is always written on normal completion (enables run-longer) and on a Ctrl-C graceful abort (enables resume). Ignored on the in-RAM path.

Returns:

Wrapper carrying the merged S-matrix (.s_params, single-column for one excited pair, K-column for K), the per-excitation V/I time series (.signals), and the sampled reference waveform (.reference_signal). Use result.S("p_out", "p_in") for direct S-parameter access. With project= the store’s Project reader instead — the same object open_project() returns, which implements the same S-parameter accessors.

Return type:

ScatteringTDResult or Project

Notes

When the run resolves to the band pipeline (port_model, constructor docstring) the record is a fixed-length pulsed broadband run: energy_stop_db and taper_signals do not apply (the band decomposition needs the complete synthesis window plus ring-down and DFTs the rectangular record; a truncation-quality warning fires if the record has not rung down), total_time_steps remains the manual override for the record length, and the built band ports are reused across excitations (state reset, kernels kept).

band_options: dict | None = None#
property f_axis: ndarray#

Default frequency axis [Hz] derived from f_max/f_min/n_freq.

linspace(max(f_min, f_max/n_freq), f_max, n_freq) — the lower bound keeps the axis strictly positive because power waves (and hence S-parameters) are undefined at DC.

f_min: float = 0.0#
n_freq: int = 201#
waveform: Waveform | None = None#
class magnelio.AnalysisTD(mesh, verbose=None, project=None, geometry=None, params=None, backend='auto', precision=None, method='auto', solver=None, f_max=None, ports=None, elements=None, sources=None, monitors=<factory>, port_model='modal', port_source='frozen', wall_model='perturbative', wall_sigma=None, wall_mu=1.0, wall_roughness=None)#

General time-domain analysis: simultaneous excitations, one march.

Parameters:
  • mesh (Mesh) – The mesh; carries the boundary closure and the ports, elements and sources declared on the model.

  • f_max (float, optional) – Upper band edge of the analysis [Hz]. None (default) uses the design frequency the mesh was generated for (mesh.f_max); a mesh without one (Mesh.from_grid) requires an explicit value. Parameterises the port-mode calculation and the default waveforms (a Gaussian over [0, f_max] for sources and TEM/lumped ports, a modulated Gaussian above a mode’s cut-off); a waveform reaching above it warns.

  • ports (list of port spec, optional) – None (default) uses the declarative ports the mesh carries; passing ports= overrides them completely. May be empty when the run is driven by sources alone.

  • elements (list of LumpedElement, optional) – Passive lumped circuit elements; None uses the mesh’s.

  • sources (list of Source, optional) – Model sources an excitation may name; None uses the mesh’s (model.add_source). Only the sources an excitation names inject anything.

  • monitors (iterable, default ()) – Field monitors recorded during the march.

  • port_model ({"modal"}, default "modal") – The port pipeline. The band-subspace pipeline decomposes S-parameters and belongs to the scattering analysis.

  • port_source ({"frozen", "dispersive"}, default "frozen") – What the port imprints. "frozen" drives one quasi-static mode profile with one propagation delay. "dispersive" drives a low-rank family solved along the band, so the launched field is the mode the grid actually carries at each frequency — worth 15-20 dB of reflection on an inhomogeneous line such as a microstrip, and nothing at all on a hollow guide, whose profile does not move with frequency. It costs one waveform and one plane write per rank term per step, about 1 % of the march.

  • wall_model (str) – Conductor-loss model of the run, as on AnalysisScatteringTD.

  • wall_sigma (float | None) – Conductor-loss model of the run, as on AnalysisScatteringTD.

  • wall_mu (float) – Conductor-loss model of the run, as on AnalysisScatteringTD.

  • wall_roughness (object) – Conductor-loss model of the run, as on AnalysisScatteringTD.

  • verbose (bool | None) – The arguments every analysis takes; project= streams the run into a project store and returns its reader.

  • project (object | None) – The arguments every analysis takes; project= streams the run into a project store and returns its reader.

  • geometry (object | None) – The arguments every analysis takes; project= streams the run into a project store and returns its reader.

  • params (dict | None) – The arguments every analysis takes; project= streams the run into a project store and returns its reader.

  • backend (str) – The arguments every analysis takes; project= streams the run into a project store and returns its reader.

  • precision (str | None) – The arguments every analysis takes; project= streams the run into a project store and returns its reader.

  • method (str) – The arguments every analysis takes; project= streams the run into a project store and returns its reader.

  • solver (str | None) – The arguments every analysis takes; project= streams the run into a project store and returns its reader.

Examples

>>> result = AnalysisTD(mesh=mesh).run(
...     excitations=[
...         Excitation("port1", waveform=signals.WaveformGaussianModulated(2e9, 8e9)),
...         Excitation("pw", waveform=signals.WaveformSine(f=5e9), amplitude=100.0),
...     ],
...     t_end=20e-9,
... )
>>> result.signal("port1").values
classmethod from_project(project, *, verbose=True)#

Reconstruct the analysis that produced a project.

Reads the reconstruction recipe (setup['recipe']) and rebuilds the analysis on the stored mesh. The persisted mesh is already PEC-consolidated and the recipe’s port specs are already resolved, so the constructor’s PEC consolidation is idempotent (it ORs the same bbox walls into an unchanged mask) and its declarative-port resolution a no-op — the reconstructed analysis is functionally identical to the one that ran, which is what makes a rebuilt-then- resumed run bit-exact on deterministically-built (e.g. TEM) ports.

Field monitors are rebuilt from the recipe; their data lives in the monitor write-through streams.

Parameters:

verbose (bool)

run(excitations=None, *, name=None, t_end=None, accuracy='normal', energy_stop_db=70.0, total_time_steps=None, port_signal_stop_db='auto', max_time_steps='auto', checkpoint_interval=None)#

March once with every excitation applied simultaneously.

Parameters:
  • excitations (iterable of Excitation, str or (str, int)) – What drives the run: Excitation objects, or the shorthands "port1" / ("port1", 1) for a port channel at unit amplitude with the default waveform. Every entry names a port declared on the model or a source, and each (name, mode) may appear once.

  • name (str, optional) – Run name in the project store (project=); default run_<n>. A name already taken in the project — by any run, including a scattering channel run — is an error.

  • t_end (float, optional) – Physical duration of the run [s]; exclusive with total_time_steps. Required when a waveform has no end (WaveformSine, WaveformStep without fall_time): a continuous-wave drive never decays, so the energy and port-signal criteria are disabled for such a run.

  • accuracy ({"draft", "normal", "high"}, default "normal") – Courant safety factor.

  • energy_stop_db (float, default 70.0) – Stop when the stored energy has decayed by this many dB below its peak (None disables).

  • total_time_steps (int, optional) – Exact leapfrog step count; default unbounded until a stop criterion fires, backstopped by max_time_steps.

  • port_signal_stop_db (float, None or "auto", default "auto") – Stop when every modal port’s |V| envelope has decayed by this many dB below its run peak. "auto" resolves to 60 dB when a modal port is present and to disabled otherwise.

  • max_time_steps (int, None or "auto", default "auto") – Runtime cap for unbounded runs (40× the auto step estimate).

  • checkpoint_interval (int, optional) – Minimum steps between resume checkpoints on the project path.

Returns:

The in-RAM result; with project= the store’s Project reader instead — the same object open_project() returns, whose result(name) rebuilds the TDResult.

Return type:

TDResult or Project

solve_ports()#

Solve every port’s 2D mode problem without a TD run.

Builds each port operator exactly as run() would (same mesh, same material matrices, mode calculation at f_max) and returns its mode solution as a PortReport — line impedance (numerical and, where available, analytical reference), cut-off frequencies, mode types, and per-mode modes[m].plot() transverse-profile plots. Use this to inspect and validate the port modes before spending time on the 3D time-domain simulation.

Lumped ports appear with an empty mode tuple and z_line_num = Z0.

Returns:

Keyed by port name, in ports order.

Return type:

dict[str, PortReport]

property boundary_conditions#

The mesh’s boundary closure (declared on the model).

Read-only view: the closure is fixed when the mesh is built, because its consequences (CPML grid extension, PMC grid-line placement, PEC wall mask) are baked into the grid and the mask by then. Re-declaring it here could only contradict them.

property cpml_thickness_cells: int#

CPML depth [cells] of the mesh’s closure.

elements: Sequence | None = None#
f_max: float | None = None#
monitors: tuple#
port_model: str = 'modal'#
port_source: str = 'frozen'#
ports: Sequence[PortSpecCoax | PortSpecRectWG | PortSpecNumerical | PortSpecMultiConductor | PortSpecLumped | PortWaveguide | PortAnalytical] | None = None#
sources: Sequence | None = None#
wall_model: str = 'perturbative'#
wall_mu: float = 1.0#
wall_roughness: object = None#
wall_sigma: float | None = None#
class magnelio.BoundaryConditions(xmin='PEC', xmax='PEC', ymin='PEC', ymax='PEC', zmin='PEC', zmax='PEC', cpml_thickness_cells=8, symmetry=<factory>)#

Boundary-condition specification for all six domain faces.

Parameters:
  • xmin (str or tuple) – BC type for each face. One of "PEC", "PMC", "CPML", "Periodic", or a symmetry declaration: "SymmetryPEC" / "SymmetryPMC" clip the computational domain at the plane (position 0.0; pass a ("SymmetryPEC", position) tuple for a plane away from the origin — the full geometry may be modelled and the discarded half is simply never meshed), while "ForceSymmetryPEC" / "ForceSymmetryPMC" declare the domain as built (the geometry itself already ends at the symmetry plane). Default "PEC" everywhere. Symmetry declarations are normalised on construction: the field keeps the physical wall type ("PEC"/"PMC") and the symmetry semantics move into symmetry.

  • xmax (str or tuple) – BC type for each face. One of "PEC", "PMC", "CPML", "Periodic", or a symmetry declaration: "SymmetryPEC" / "SymmetryPMC" clip the computational domain at the plane (position 0.0; pass a ("SymmetryPEC", position) tuple for a plane away from the origin — the full geometry may be modelled and the discarded half is simply never meshed), while "ForceSymmetryPEC" / "ForceSymmetryPMC" declare the domain as built (the geometry itself already ends at the symmetry plane). Default "PEC" everywhere. Symmetry declarations are normalised on construction: the field keeps the physical wall type ("PEC"/"PMC") and the symmetry semantics move into symmetry.

  • ymin (str or tuple) – BC type for each face. One of "PEC", "PMC", "CPML", "Periodic", or a symmetry declaration: "SymmetryPEC" / "SymmetryPMC" clip the computational domain at the plane (position 0.0; pass a ("SymmetryPEC", position) tuple for a plane away from the origin — the full geometry may be modelled and the discarded half is simply never meshed), while "ForceSymmetryPEC" / "ForceSymmetryPMC" declare the domain as built (the geometry itself already ends at the symmetry plane). Default "PEC" everywhere. Symmetry declarations are normalised on construction: the field keeps the physical wall type ("PEC"/"PMC") and the symmetry semantics move into symmetry.

  • ymax (str or tuple) – BC type for each face. One of "PEC", "PMC", "CPML", "Periodic", or a symmetry declaration: "SymmetryPEC" / "SymmetryPMC" clip the computational domain at the plane (position 0.0; pass a ("SymmetryPEC", position) tuple for a plane away from the origin — the full geometry may be modelled and the discarded half is simply never meshed), while "ForceSymmetryPEC" / "ForceSymmetryPMC" declare the domain as built (the geometry itself already ends at the symmetry plane). Default "PEC" everywhere. Symmetry declarations are normalised on construction: the field keeps the physical wall type ("PEC"/"PMC") and the symmetry semantics move into symmetry.

  • zmin (str or tuple) – BC type for each face. One of "PEC", "PMC", "CPML", "Periodic", or a symmetry declaration: "SymmetryPEC" / "SymmetryPMC" clip the computational domain at the plane (position 0.0; pass a ("SymmetryPEC", position) tuple for a plane away from the origin — the full geometry may be modelled and the discarded half is simply never meshed), while "ForceSymmetryPEC" / "ForceSymmetryPMC" declare the domain as built (the geometry itself already ends at the symmetry plane). Default "PEC" everywhere. Symmetry declarations are normalised on construction: the field keeps the physical wall type ("PEC"/"PMC") and the symmetry semantics move into symmetry.

  • zmax (str or tuple) – BC type for each face. One of "PEC", "PMC", "CPML", "Periodic", or a symmetry declaration: "SymmetryPEC" / "SymmetryPMC" clip the computational domain at the plane (position 0.0; pass a ("SymmetryPEC", position) tuple for a plane away from the origin — the full geometry may be modelled and the discarded half is simply never meshed), while "ForceSymmetryPEC" / "ForceSymmetryPMC" declare the domain as built (the geometry itself already ends at the symmetry plane). Default "PEC" everywhere. Symmetry declarations are normalised on construction: the field keeps the physical wall type ("PEC"/"PMC") and the symmetry semantics move into symmetry.

  • symmetry (dict, optional) – Canonical symmetry map {face: position_or_None}. Filled automatically from symmetry-declared face values; may also be passed directly. At most one face per axis can be a symmetry plane — two parallel mirror planes would describe an infinite image chain, not a finite full model.

  • cpml_thickness_cells (int, default 8) – Layer depth for every face declared "CPML", measured in bulk (h_max-sized) cells. One number for both consequences of that depth: the mesher extends the grid by this many cells on each CPML face, and the runtime layer grades its profile over the same span (previously the two were separate settings on MeshControl and the analysis and could disagree).

to_dict()#
Return type:

dict[str, str]

to_objects(grid)#

Materialise BC strings into runtime BC objects.

Parameters:

grid (GridLines) – The simulation grid. Required for PMC, CPML, and Periodic instances which need it for their internal index maps and polynomial profiles.

Returns:

Mapping face -> BC instance, ready to be passed as boundary_conditions= to FITTimeDomainSolver.

Return type:

dict[str, BoundaryProtocol]

cpml_thickness_cells: int = 8#
symmetry: dict#
xmax: str = 'PEC'#
xmin: str = 'PEC'#
ymax: str = 'PEC'#
ymin: str = 'PEC'#
zmax: str = 'PEC'#
zmin: str = 'PEC'#
class magnelio.Excitation(source, mode=0, waveform=None, amplitude=1.0, delay=0.0, phase=0.0)#

One port channel or model source, bound to a waveform and a weight.

Parameters:
  • source (str) – Name of the port or source to drive — a port declared with add_port() or a source declared with add_source().

  • mode (int, default 0) – Mode index on a port; ignored by sources.

  • waveform (Waveform, optional) – The time function. None (default) lets the run derive it: a Gaussian pulse over the analysis band, band-limited above a port mode’s cut-off frequency.

  • amplitude (float, default 1.0) – Peak amplitude in the natural unit of the source — sqrt(W) (incident power wave) for ports, V/m for a plane wave; each source publishes it as amplitude_unit.

  • delay (float, default 0.0) – Time offset [s] of the waveform; must not be negative.

  • phase (float, default 0.0) – Phase [degrees]. On a carrier waveform (one with f_center) it is applied as a delay of phase / (360 · f_center); on a baseband waveform it is rejected, because a baseband pulse has no phase. Two modes of one port at 90° make a circularly polarised feed.

Notes

Bare names and (name, mode) pairs are accepted wherever a list of excitations is: "port1" means Excitation("port1") and ("port1", 1) means Excitation("port1", mode=1).

Examples

>>> from magnelio import Excitation, signals
>>> exc = Excitation("port1", waveform=signals.WaveformGaussianModulated(8e9, 12e9))
>>> exc.source, exc.mode, exc.waveform.f_center
('port1', 0, 10000000000.0)
classmethod coerce(spec)#

Turn a shorthand into an Excitation.

"port1" → Excitation("port1"); ("port1", 1) → Excitation("port1", mode=1); an Excitation is returned as is.

Return type:

Excitation

effective_delay()#

The delay [s] including the phase, delay + phase / (360 · f_center).

Raises when a phase is set without a waveform to take it from.

Return type:

float

amplitude: float = 1.0#
delay: float = 0.0#
mode: int = 0#
phase: float = 0.0#
source: str#
waveform: Waveform | None = None#
class magnelio.GeometryModel(*, background=None, boundary_conditions=None, allow_overlaps=False)#

Container for a CSG geometry model (ordered list of shapes).

Shapes are stored in insertion order. Each spatial point should be covered by at most one shape; use CSG operations (Difference, Union, Intersection) to partition overlapping volumes.

Parameters:
  • background (Material or str or None) – Material for cells not covered by any shape — an instance or a built-in name ("air", "vacuum", "pec"). Defaults to air (eps=mu=1, sigma=0). The background fills the volume outside every shape; the boundary closure of the domain is declared separately via boundary_conditions and wins over it on the bbox faces.

  • boundary_conditions (BoundaryConditions or dict or None) – Closure of the six domain faces — "PEC", "PMC", "CPML", "Periodic", or a symmetry declaration ("SymmetryPEC"/"SymmetryPMC", optionally as a ("SymmetryPEC", position) tuple, or "ForceSymmetryPEC"/"ForceSymmetryPMC") per face. Declared here because it is a property of the modelled domain, not of the analysis run on it: a symmetry face is a mirror plane, a CPML face is an opening. from_geometry() derives all mesh-time consequences from it (CPML grid extension, PMC grid-line pull-in, PEC wall mask, symmetry domain clip), and the analyses read it back off the mesh. None (default) closes every face with PEC.

  • allow_overlaps (bool) – If False (default), from_geometry() raises GeometryOverlapError when shapes overlap. Set to True to restore legacy last-wins semantics.

Examples

A cavity carved out of a metal block:

from magnelio import GeometryModel, Material
from magnelio.geo import Brick, Difference

pec  = Material.pec()
air  = Material.air()

outer = Brick(origin=(0,0,0), size=(10e-3,10e-3,10e-3), material=pec)
cavity = Brick(origin=(2e-3,2e-3,2e-3), size=(6e-3,6e-3,6e-3), material=air)

model = GeometryModel()
model.add(Difference(outer, cavity))
model.add(cavity)

A magnetic symmetry plane at x = 0 — the full geometry may be modelled; the declared plane clips the mesh to x >= 0 and the mirror half is never meshed:

model = GeometryModel(
    background=pec,
    boundary_conditions={"xmin": "SymmetryPMC"},
)

For a plane away from the origin pass the position explicitly: {"xmin": ("SymmetryPMC", 1.5e-3)}. If the geometry itself already ends at the symmetry plane, declare it without clipping: {"xmin": "ForceSymmetryPMC"}.

add(shape)#

Add a CSG shape (or list of shapes) to the model.

A Group is flattened into its member shapes on insertion (recursively for nested Groups), so the mesher, material filling and overlap layers only ever see leaf shapes. Lists/tuples are added element-wise, and may themselves contain Groups.

Every shape entering the model must carry a material: a material-less shape is a construction body (a Boolean operand or an extrusion profile), not a physical object, and is rejected here rather than at mesh time.

Returns:

self, to allow chaining.

Return type:

GeometryModel

Raises:

ValueError – If shape carries no material.

add_element(element)#

Declare a passive lumped circuit element on the model.

Accepts a declarative magnelio.circuit.LumpedElement — a straight interior edge path carrying a trapezoidal RLC companion model as a pure passive load (no excitation, no S-matrix column). Like ports, elements travel with the mesh to the analysis; ports and elements share one name namespace because the solver keys per-operator checkpoint state by name.

Parameters:

element (magnelio.circuit.LumpedElement) – Declarative element; its name must be unique among the ports and elements of this model.

Returns:

self, for chaining.

Return type:

GeometryModel

add_port(port)#

Declare a port on the model, before meshing.

Accepts the declarative port objects (PortWaveguide, PortAnalytical) — the geometric declaration only; the mode physics is resolved by the analysis against the finished mesh, exactly as before.

Declaring ports here lets from_geometry() see which domain faces carry one: the equidistant-cell buffer the modal operators require is then generated only on those faces instead of on all six (the port-blind fallback). The mesh carries the declarations to the analysis, so AnalysisScatteringTD(mesh=mesh, ...) needs no ports= of its own.

Parameters:

port (PortWaveguide, PortAnalytical or PortLumped) – Declarative port; its name must be unique on this model.

Returns:

self, for chaining.

Return type:

GeometryModel

add_source(source)#

Declare a field source on the model, before meshing.

Accepts a magnelio.sources.SourceFieldIncident (such as SourcePlaneWave). Like ports and elements, sources travel with the mesh to the analysis, and an Excitation drives one by name — so ports, elements and sources share one name namespace.

Parameters:

source (magnelio.sources.Source) – Declarative source; its name must be unique among the ports, elements and sources of this model.

Returns:

self, for chaining.

Return type:

GeometryModel

bounding_box()#

Return the axis-aligned bounding box of all shapes combined.

Returns:

(min_corner, max_corner) in meters. Requires OCC.

Return type:

tuple

plot(mesh=None, **kwargs)#

Deprecated alias of show().

plot draws into matplotlib everywhere else in magnelio; the interactive 3D view is show.

plot_cross_section(normal, position, **kwargs)#

Plot a 2D cross-section at an axis-aligned plane.

Thin wrapper around plot_cross_section(). All keyword arguments are forwarded.

Parameters:
  • normal (str) – 'x', 'y', or 'z'.

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

  • **kwargs – Forwarded (mesh, scale_mm, flip, ax, title, deflection).

Returns:

  • fig (matplotlib.figure.Figure)

  • ax (matplotlib.axes.Axes)

show(mesh=None, **kwargs)#

Interactive 3D view of the model.

Thin wrapper around show_geometry(). In a notebook the view is a widget with an axis-aligned cutting plane driven from its toolbar; in a script it opens a window. It is the viewer every show() in magnelio opens — a field view (magnelio.fields.FieldState.show(), a monitor’s, an eigenmode result’s) is this view with the field laid on its cut and a second toolbar row.

Parameters:
  • mesh (Mesh, optional) – Show this mesh’s grid with the geometry: the grid cells — coloured by assigned material — on the cutting plane.

  • **kwargs – Forwarded (cut, flip, show_ports, show_wires, show_grid, mode, size, render_edges, edge_color, quality, scale_mm, camera).

Returns:

The plotter for mode="none"; otherwise the view is displayed and None is returned.

Return type:

pyvista.Plotter or None

validate()#

Check that no two shapes overlap volumetrically.

Overlaps between shapes of the same material are allowed and not reported: the overlap region is unambiguous (it gets that material either way). “Same material” is decided by value equality (Material.__eq__()), so two distinct Material.pec() instances count as the same. Overlaps between shapes of different materials are ambiguous and still raise.

Raises:

GeometryOverlapError – If any two shapes of different materials have a nonzero volumetric intersection.

Return type:

None

class magnelio.Material(name, epsilon=(1.0, 1.0, 1.0), mu=(1.0, 1.0, 1.0), sigma=(0.0, 0.0, 0.0), sigma_m=(0.0, 0.0, 0.0), is_pec=False, dispersion=None, dispersion_mu=None, roughness=None, color=None, alpha=1.0, visible=True)#

Electromagnetic material with diagonal anisotropic properties.

All values are in SI units. Permittivity and permeability are relative (dimensionless, relative to ε₀ and μ₀ respectively).

Wherever the public API expects a material (shape material=, GeometryModel(background=), …), the parameter-free built-ins may be named by string instead: "air", "vacuum" or "pec" (case-insensitive) resolve to the canonical instances of air(), vacuum() and pec().

Parameters:
  • name (str) – Human-readable identifier.

  • epsilon (tuple of float) – Relative permittivity (εrx, εry, εrz). Default (1, 1, 1) = vacuum.

  • mu (tuple of float) – Relative permeability (μrx, μry, μrz). Default (1, 1, 1) = vacuum.

  • sigma (tuple of float) – Electric conductivity (σx, σy, σz) in S/m. Default (0, 0, 0).

  • sigma_m (tuple of float) – Magnetic loss (σ*x, σ*y, σ*z) in Ω/m. Default (0, 0, 0).

  • is_pec (bool) – If True, the field solver treats this material as a Perfect Electric Conductor (edge masking; σ never enters M_sigma). A finite σ > 0 alongside is_pec=True marks a lossy metal: identical field solution, but surface-loss models consume σ (and μ) for the skin-effect surface resistance.

  • dispersion (DispersionModel, optional) – Pole-residue model for frequency-dependent permittivity. epsilon must then equal the model’s eps_inf on all three axes (it drives the mass matrix and the CFL limit); use dispersive() to get this right automatically. Incompatible with is_pec.

  • dispersion_mu (DispersionModel, optional) – Pole-residue model for frequency-dependent permeability — the H-side mirror of dispersion, realised by the same trapezoidal ADE on the H-faces. DispersionModel is reused verbatim: it is a relative-units pole-residue form, so its eps_inf field carries μ_inf here, and its passivity condition (Im ≥ 0 on the band) is the μ’’ ≥ 0 one. mu must then equal that eps_inf on all three axes; use dispersive_mu(). Incompatible with is_pec.

  • roughness (SurfaceRoughness, optional) – Surface-roughness model raising the skin-effect surface resistance by K(f) in loss models. Like σ it is consumed only by those models, never by the field solve — so it is meaningful on a lossy metal only (use lossy_metal()).

  • color (tuple of float, optional) – RGB colour tuple in [0, 1] for visualisation. None lets the plotting code assign a colour automatically.

  • alpha (float) – Opacity in [0, 1] for 3-D rendering (default opaque).

  • visible (bool) – Whether to render this material in 3-D visualisations.

classmethod air()#

Free-space / air material (ε=μ=1, σ=0).

Return type:

Material

classmethod dispersive(name, model, mu=1.0, sigma=0.0)#

Material with a pole-residue dispersive permittivity.

epsilon is set to the model’s eps_inf (the high-frequency limit that drives the mass matrix and the CFL condition); the pole set is realised in the solver by the trapezoidal ADE. An additional static conductivity may be given — it runs through the standard semi-implicit σ channel alongside the pole currents.

Parameters:
  • name (str) – Human-readable identifier.

  • model (DispersionModel) – Pole-residue permittivity model (passivity-checked at its construction).

  • mu (float, optional) – Relative permeability, default 1.

  • sigma (float, optional) – Static electric conductivity in S/m, default 0.

Return type:

Material

classmethod dispersive_mu(name, model, epsilon=1.0, sigma=0.0, sigma_m=0.0)#

Material with a pole-residue dispersive permeability.

The H-side mirror of dispersive(). mu is set to the model’s high-frequency limit (which drives the mass matrix and the CFL condition); the pole set is realised in the solver by the same trapezoidal ADE, on the H-faces. An additional magnetic loss σ* may be given — it runs through the standard semi-implicit σ* channel alongside the pole currents.

DispersionModel is reused verbatim for μ(ω): it is a relative-units pole-residue form, so its eps_inf field carries μ_inf and its constructors (debye, lorentz, drude, …) describe the magnetic analogues.

Both permittivity and permeability can be dispersive at once — pass dispersion= and dispersion_mu= to the constructor directly (epsilon and mu must then equal the respective high-frequency limits).

Parameters:
  • name (str) – Human-readable identifier.

  • model (DispersionModel) – Pole-residue permeability model (passivity-checked at its construction — for μ that check reads μ’’ >= 0).

  • epsilon (float, optional) – Relative permittivity, default 1.

  • sigma (float, optional) – Static electric conductivity in S/m, default 0.

  • sigma_m (float, optional) – Magnetic loss σ* in Ω/m, default 0.

Return type:

Material

classmethod from_isotropic(name, epsilon=1.0, mu=1.0, sigma=0.0, sigma_m=0.0)#

Create an isotropic material from scalar values.

Parameters:
  • name (str)

  • epsilon (float)

  • mu (float)

  • sigma (float)

  • sigma_m (float)

Return type:

Material

classmethod lossy_metal(name, sigma, mu=1.0, roughness=None)#

Good conductor: PEC for the field solve, finite σ for loss models.

The field solver treats the material exactly like PEC (edge masking — results are identical to pec()). The finite conductivity and permeability are consumed only by surface-loss models via the skin-effect surface resistance R_s(ω) = sqrt(ω·μ₀·μ_r / (2σ)).

Parameters:
  • name (str) – Human-readable identifier.

  • sigma (float) – Electric conductivity in S/m. Must be finite and > 0.

  • mu (float, optional) – Relative permeability (enters R_s), default 1.

  • roughness (SurfaceRoughness, optional) – Surface-roughness model. Multiplies R_s by its frequency-dependent factor K(f) >= 1 wherever a loss model evaluates this metal’s walls; None (default) is a perfectly smooth conductor.

Return type:

Material

classmethod pec()#

Perfect Electric Conductor.

Return type:

Material

classmethod vacuum()#

Alias for air().

Return type:

Material

alpha: float = 1.0#
color: tuple[float, float, float] | None = None#
dispersion: DispersionModel | None = None#
dispersion_mu: DispersionModel | None = None#
epsilon: tuple[float, float, float] = (1.0, 1.0, 1.0)#
property is_isotropic: bool#

True if ε, μ, σ, σ* are all isotropic (x=y=z).

property is_lossless: bool#

True if σ = 0, σ* = 0, not PEC, and not dispersive.

property is_lossy_metal: bool#

True for a PEC-classified material carrying a finite σ > 0.

Legacy stores may carry sigma = inf on plain PEC (written by versions that forced it); the finiteness check excludes those.

is_pec: bool = False#
mu: tuple[float, float, float] = (1.0, 1.0, 1.0)#
name: str#
roughness: SurfaceRoughness | None = None#
sigma: tuple[float, float, float] = (0.0, 0.0, 0.0)#
sigma_m: tuple[float, float, float] = (0.0, 0.0, 0.0)#
visible: bool = True#
class magnelio.Mesh(grid, material_id, material_library, pec_mask_edges, edge_material=None, face_material=None, f_max=None, boundary_conditions=None, ports=(), elements=(), sources=(), planes=None, _wall_backup=<factory>, _pair_coupling=None)#

Structured non-uniform hexahedral mesh with material assignments.

See spec.md for the data-structure specification.

Parameters:
  • grid (GridLines)

  • material_id (np.ndarray)

  • material_library (dict[int, 'Material'])

  • pec_mask_edges (np.ndarray)

  • edge_material (EdgeMaterialData | None)

  • face_material (FaceMaterialData | None)

  • f_max (float | None)

  • boundary_conditions (object)

  • ports (tuple)

  • elements (tuple)

  • sources (tuple)

  • planes (GridPlanes | None)

  • _wall_backup (dict)

  • _pair_coupling (object)

classmethod from_geometry(geometry, control, f_max, *, verbose=None)#

Generate a mesh from a geometry model.

Parameters:
  • geometry – A GeometryModel (CSG tree root).

  • control (MeshControl) – MeshControl parameters.

  • f_max (float) – Maximum simulation frequency [Hz]. Determines target cell size and is recorded on the mesh as its design frequency (mesh.f_max); the scattering analysis defaults its band to it.

  • verbose (bool | None) – Print build progress. None (the default) follows the process-wide setting of magnelio.set_verbosity().

Return type:

Mesh

The boundary closure is read off geometry.boundary_conditions; every mesh-time consequence follows from that one declaration:

  • CPML faces extend the grid by boundary_conditions.cpml_thickness_cells uniform cells, so the absorber has room to grade its profile.

  • PMC faces pull their outermost grid line inside the geometry bbox. The natural magnetic wall of the FIT operators sits half a boundary cell outside that line (see magnelio.boundaries.pmc); moving the line to one third of the boundary cell lands the wall exactly ON the declared face and makes PMC cut-offs converge at O(dx**2). Without it the wall sits half a boundary cell outside the bbox — an O(dx) bias, not an inconsistency.

  • PEC faces mask their tangential edges, giving the closed conducting chamber the mode solvers and the FIT update see. A PEC background fills the volume outside every shape but does not by itself close a face: a face declared PMC or CPML stays open through it (this supersedes the historical blanket bbox-wall forcing, which turned every symmetry plane and absorber into an electric wall).

Returns:

A fully populated Mesh.

Parameters:
  • control (MeshControl)

  • f_max (float)

  • verbose (bool | None)

Return type:

Mesh

Note

This method requires pythonocc-core for geometry queries. Import of OCC backend is deferred to this call.

classmethod from_grid(grid, regions=None, background=None, boundary_conditions=None)#

Create a mesh from an explicit grid without requiring OCC.

This is the OCC-free alternative to from_geometry(). Material regions are specified as axis-aligned bounding boxes (AABB).

Parameters:
  • grid (GridLines) – Pre-built GridLines.

  • regions (list[tuple['Material', tuple[float, float, float, float, float, float]]] | None) – List of (material, (xmin, ymin, zmin, xmax, ymax, zmax)) tuples. Regions are applied in order; later entries overwrite earlier ones where they overlap.

  • background (Material | None) – Material to fill all cells not covered by any region. Defaults to Material.air().

  • boundary_conditions – Closure of the six bbox faces (BoundaryConditions or dict); this is the OCC-free counterpart of declaring it on the GeometryModel. Faces closed with PEC get their tangential edges masked here, so the mode solvers and the FIT update see the wall. None closes every face with PEC. The grid is taken as given — unlike from_geometry(), this path cannot extend it for CPML or pull a PMC line in.

Returns:

A fully populated Mesh.

Return type:

Mesh

Example:

from magnelio.mesh.grid import GridLines
from magnelio.mesh.mesher import Mesh
from magnelio.materials.material import Material
import numpy as np

grid = GridLines(
    x=np.linspace(0, 10e-3, 11),
    y=np.linspace(0, 10e-3, 11),
    z=np.linspace(0, 10e-3, 11),
)
fr4 = Material(name="FR4", epsilon=(4.4, 4.4, 4.4))
mesh = Mesh.from_grid(grid, regions=[(fr4, (0, 0, 0, 10e-3, 10e-3, 1.6e-3))])
plot_section(normal, position, **kwargs)#

Plot an axis-aligned section of the mesh: cells and grid lines by origin.

Thin wrapper around magnelio.plots.plot_mesh_section(); see there for the keyword arguments — geometry= overlays the model’s section outline, fill= picks the cell shading (PEC coverage by default, classification, or the permittivity the normal edges see), edges=True adds the PEC-masked and partially free edges.

Parameters:
  • normal (str)

  • position (float)

with_boundary_conditions(boundary_conditions)#

Re-declare the boundary closure of an already-built mesh.

Returns a new Mesh carrying boundary_conditions, with the wall mask made to match: PEC faces get their tangential edges masked, and faces that are no longer PEC get the edges they had before any wall was forced on them — so this replaces the closure rather than adding to it, and a face can go from PEC back to PMC.

The grid is taken as given: a closure attached here cannot grow a CPML extension or pull a PMC grid line in, so declare it on the GeometryModel when those matter.

Notes

Taking a wall back off needs the pre-wall edge values, which only exist for walls this process forced (_wall_backup). A mesh reloaded from the project store has none, so on that path only re-declaring the same closure is meaningful — which is what the store does.

Return type:

Mesh

with_elements(elements)#

Attach passive lumped elements to an already-built mesh.

The late-declaration path for meshes not built through from_geometry(), mirroring with_ports(): the returned mesh carries elements exactly as if they had been declared on the GeometryModel via add_element, and AnalysisScatteringTD resolves them from the mesh.

Parameters:

elements (sequence of magnelio.circuit.LumpedElement) – Declarative elements; labels must be unique among the ports and elements of this mesh.

Returns:

New mesh sharing all data with self plus the elements.

Return type:

Mesh

with_pec_boundaries(faces)#

Consolidate PEC bbox boundary conditions into pec_mask_edges.

Returns a new Mesh whose pec_mask_edges additionally marks every primal edge tangential to one of the listed bbox faces as PEC. This is what makes a PEC closure and a geometric PEC block on that face indistinguishable to every downstream mesh consumer — the modal port factory, extract_conductor_groups_from_mesh(), the FIT solver’s PEC-zeroing step, and any HDF5 export.

Both mesh factories apply this to the PEC faces of their declared closure, so a mesh normally arrives already consolidated; the method remains for the component-level path that builds a Mesh directly, and as the primitive both factories call.

PMC, Periodic, and CPML faces are intentionally not consolidated:

  • PMC (H_tan = 0) translates to a Neumann condition for the modal Laplace / curl-curl problems, which is the natural (default) behaviour when no Dirichlet constraint is imposed — no mask change required. Masking it instead would impose E_tan = 0: the opposite symmetry.

  • Periodic ties opposite faces together; this is handled inside the FIT operator builders, not via the PEC mask.

  • CPML is an absorbing aux-variable layer; mode-solver semantics on a CPML lateral wall are an open architectural question deferred to a separate cleanup.

Parameters:

faces (iterable of str) – Bbox face names — any subset of {"xmin", "xmax", "ymin", "ymax", "zmin", "zmax"}. Duplicates and unknown names both raise.

Returns:

New mesh sharing all data with self except for an updated pec_mask_edges. The original mesh is not mutated.

Return type:

Mesh

with_ports(ports)#

Attach declarative ports to an already-built mesh.

The late-declaration path for meshes not built through from_geometry() (e.g. from_grid()): the returned mesh carries ports exactly as if they had been declared on the GeometryModel, and AnalysisScatteringTD resolves them from the mesh. The grid is taken as given — attaching ports here cannot regenerate the per-face buffer cells, so the port validator remains the backstop for faces that were never buffered.

Parameters:

ports (sequence of PortWaveguide / PortAnalytical) – Declarative ports with unique labels.

Returns:

New mesh sharing all data with self plus the ports.

Return type:

Mesh

with_sources(sources)#

Attach field sources to an already-built mesh.

The late-declaration path for meshes not built through from_geometry(), mirroring with_ports(): the returned mesh carries sources exactly as if they had been declared on the GeometryModel via add_source, and an Excitation drives them by name.

Parameters:

sources (sequence of magnelio.sources.Source) – Declarative sources; labels must be unique among the ports, elements and sources of this mesh.

Returns:

New mesh sharing all data with self plus the sources.

Return type:

Mesh

property Nx: int#
property Ny: int#
property Nz: int#
boundary_conditions: object = None#
edge_material: EdgeMaterialData | None = None#
elements: tuple = ()#
f_max: float | None = None#
face_material: FaceMaterialData | None = None#
grid: GridLines#
material_id: np.ndarray#
material_library: dict[int, 'Material']#
pec_mask_edges: np.ndarray#
planes: GridPlanes | None = None#
property pml_cells: dict[str, int]#

Number of absorber grid cells per domain face.

Maps face names ("xmin" … "zmax") to the number of grid cells the mesher appended outside the declared domain for that face’s CPML layer. Faces without an absorbing layer are absent. A mesh built without from_geometry() reports an empty mapping.

Returns:

Absorber cell count per face; a fresh copy on every access.

Return type:

dict of str to int

ports: tuple = ()#
sources: tuple = ()#
class magnelio.MeshControl(min_nodes_per_wavelength=20, min_cells_per_feature=4, growth_factor=1.3, max_cell_size=None, min_cell_size=None, forced_planes=<factory>, subdivide=<factory>, conformal=True, dey_mittra_eta=0.4, min_feature_gap=None, max_edge_refinement=4.0, wavelength_rule='local', singularity_refinement=1.0)#

Parameters controlling grid-line generation.

The mesher uses two cell-size scales per axis:

  • h_max (bulk) is wavelength-based, h_max = λ / min_nodes_per_wavelength. Cells in regions far from material interfaces are capped at h_max. With the default wavelength_rule="local" the wavelength is that of the densest material in the slab an axis interval spans, so air around a small dielectric is meshed at the air wavelength; "global" uses the densest material of the whole model everywhere.

  • h_fine (interface) is feature-based and per axis, h_fine = min_gap / min_cells_per_feature where min_gap is the smallest distance between adjacent material planes on that axis (axes without internal material boundaries stay at the wavelength size). Cells adjacent to material interfaces use h_fine.

Between interfaces and the bulk, cells grow geometrically by growth_factor from h_fine toward h_max (graded spacing). Importantly, h_fine only applies near interfaces — it does not shrink the entire mesh down to feature size.

Parameters:
  • min_nodes_per_wavelength (int, default 20) – Cells per shortest wavelength in the densest material; sets h_max.

  • min_cells_per_feature (int, default 4) – Cells across the smallest geometry gap; sets h_fine. Set to 0 to disable feature-based refinement (bulk-only meshing). Because cell counts are integers, the size actually generated may exceed h_fine by a few percent rather than add a cell; use min_cell_size for a hard floor.

  • growth_factor (float, default 1.3) – Geometric ratio between adjacent cells in graded regions. Must be > 1.0. Smaller values yield smoother grading at the cost of more cells.

  • max_cell_size (float or None, default None) – Hard upper bound on every cell, applied after h_max and h_fine are determined. None disables the cap.

  • min_cell_size (float or None, default None) – Hard lower bound on every cell. None disables the floor.

  • forced_planes (dict[str, list[float]], default empty) – Per-axis positions {"x": [...], "y": [...], "z": [...]} that the grid must include verbatim (e.g. probe points).

  • subdivide (dict[str, int], default empty) – Per-axis factor {"x": 2, ...} splitting every cell of the finished grid into that many equal cells — a nested h-refinement of the grid the rules produced, for convergence ladders (refine_port_modes()). Every plane the rules placed stays where it is; only the cells between them shrink. A CPML face on a subdivided axis keeps its declared cell count, so its absorber depth shrinks by the factor — use it on axes without absorbers.

  • conformal (bool, default True) – Enable conformal/Dey-Mittra material treatment for cells partially filled with PEC.

  • dey_mittra_eta (float, default 0.4) – Stability cutoff for Dey-Mittra cells (fraction of full cell area below which cells are treated as PEC-only).

  • min_feature_gap (float or None, default None) – Critical-plane clustering tolerance [m]. Adjacent critical planes closer than this are snapped to a single position before h_fine is computed. Without this, sub-feature gaps from float coordinates (e.g. unaligned random geometry) collapse h_fine to absurdly small values and explode the mesh. CAD geometries on a grid are unaffected. None (default) resolves to 1e-5 x the model bounding-box diagonal — the CSG float wiggle this tolerance absorbs is relative, so the default scales with the model, from meter-scale structures down to micron-scale optics.

  • max_edge_refinement (float, default 4.0) – Geometry edges — the onset of a chamfer or fillet, a loft section, the equator or iris circle of a revolved profile — get a grid plane of their own wherever the edge lies flat in an axis-normal plane, so the feature occupies at least one cell layer and the cell’s material average can see it. A feature that varies along the grid edges inside one cell has no effect at all until it reaches the cell’s midplane (a chamfer below half a cell height is invisible). This ratio caps the refinement: an edge plane whose cell would be smaller than h_max / max_edge_refinement (or than min_cell_size) is dropped with a warning naming it. The time step follows the smallest cell, so the ratio also bounds the runtime cost of resolving small edges. 0 disables edge planes altogether (material faces and silhouettes only).

  • wavelength_rule ({"local", "global"}, default "local") – Which wavelength sets the bulk cell size. "local": each axis interval between grid planes is a slab of the domain, and the densest material whose bounding box reaches into that slab sets its bulk size — the air box around a small ceramic or a thin substrate is meshed at the air wavelength, the dielectric at its own. "global": the densest material anywhere in the model sets one bulk size for the whole domain. Feature refinement, grading and the edge floor are the same under both rules; the rules differ only far from material interfaces.

  • singularity_refinement (float, default 1.0) – Refinement factor at conductor edges. Where a metal body forms a wedge of less than 180° — the edges of a strip, a patch, an iris — the field and the surface current are singular (r^(−1/3) at a 90° edge, r^(−1/2) at a knife edge) and the error of impedances and S-parameters converges only slowly with the cell size there. The grid planes holding such an edge start their grading on both sides at h_fine / singularity_refinement instead of h_fine and grow by growth_factor from there. Concave metal edges (the corners of a cavity), tangential edges (a fillet’s onset) and dielectric edges are regular and not refined. 1 disables the refinement. The finer edge cells bound the time step: expect the run time to scale roughly with the factor.

conformal: bool = True#
dey_mittra_eta: float = 0.4#
forced_planes: dict[str, list[float]]#
growth_factor: float = 1.3#
max_cell_size: float | None = None#
max_edge_refinement: float = 4.0#
min_cell_size: float | None = None#
min_cells_per_feature: int = 4#
min_feature_gap: float | None = None#
min_nodes_per_wavelength: int = 20#
singularity_refinement: float = 1.0#
subdivide: dict[str, int]#
wavelength_rule: str = 'local'#
magnelio.get_verbosity()#

Return the process-wide default for solver progress output.

Return type:

bool

magnelio.open_project(path)#

Open a project directory for read-only post-processing.

Parameters:

path (str or Path) – A project directory created by ProjectStore.create() (or, in later work packages, by an Analysis.run(project=...)).

Return type:

Project

magnelio.resume(project, excited=None, *, energy_stop_db=None, total_time_steps=None, port_signal_stop_db=None, max_time_steps='auto', checkpoint_interval=None, verbose=True)#

Continue a time-domain run in a project store from its checkpoint.

Opens project (a path or an already-open Project), rebuilds the run’s operators from the stored reconstruction recipe, loads the latest checkpoint.h5, and marches on — appending to the same results.h5 streams. The resume injects no seam: the checkpoint carries the full leapfrog state (E/H, CPML ψ, exact DTBC convolution history, Mur previous-values), so a resumed run is bit-identical to an uninterrupted run of the same total length on a deterministically-built line.

Parameters:
  • project (str or Path or Project) – The project directory (or an open reader) to continue.

  • excited (str or (str, int), optional) – Which run to resume: a scattering run by its excited (port, mode) pair, an AnalysisTD run by its name. May be omitted when the project holds exactly one run.

  • energy_stop_db (float, optional) – New stop threshold, dB below the original peak energy. Default (None): inherit the run’s launch criterion — so a Ctrl-C- aborted run finishes to its original target with a bare resume(project). To run a completed run longer, pass a deeper (larger) value than it originally used.

  • total_time_steps (int, optional) – New global step cap (not a delta — the target leapfrog count). Default (None): inherit the run’s launch value. Must exceed the checkpoint step.

  • port_signal_stop_db (float or "auto", optional) – New port-signal stop threshold, dB below the run’s peak modal |V| envelope (see AnalysisScatteringTD.run()). Default (None): inherit the run’s launch criterion together with the other two knobs; passing any knob explicitly disables the others, exactly as at launch.

  • max_time_steps (int, None or "auto", default "auto") – Runtime cap for the resumed segment (see AnalysisScatteringTD.run()). "auto" grants a fresh cap budget past the checkpoint step; an explicit int is an absolute step bound; None removes the cap and the stall watchdog. Not inherited — each segment gets its own.

  • checkpoint_interval (int, optional) – Minimum steps between resume checkpoints for the continued run (as in AnalysisScatteringTD.run()).

  • verbose (bool, default True) – Print solver progress.

Returns:

A reader over the (now-extended) project.

Return type:

Project

Raises:
  • ValueError – If the project carries no reconstruction recipe (written by an older version without one), the run has no checkpoint, or the effective criterion would not advance past the checkpoint step.

  • NotImplementedError – If the project’s analysis kind has no time-marching state to resume (e.g. an eigenmode result).

magnelio.set_verbosity(value)#

Set the process-wide default for solver progress output.

Every analysis, mesh build and port solve reports its progress unless told otherwise. This sets the default they all read; an individual object still overrides it with its own verbose= argument.

Parameters:

value (bool) – True to report progress (the default), False to run silently.

Return type:

None

Examples

>>> import magnelio as mio
>>> mio.set_verbosity(False)          # a batch sweep, no output
>>> mio.set_verbosity(True)           # back to the default