Core (magnelio)#

The model container and run vocabulary, the problem classes, and the project-store entry points — the ten 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), the problem classes (Analysis*) and the project-store entry points (open_project, resume). 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.circuit, magnelio.signals, magnelio.solver, magnelio.analysis, magnelio.post, magnelio.plots, magnelio.io, magnelio.constants — and underscore modules are internal.

class magnelio.AnalysisEigenmode(mesh, n_modes=5, solver=None, sigma=None, verbose=True, project=None, geometry=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). Only PEC and PMC are meaningful for an eigenmode problem.

  • 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) – Print solver progress information.

  • project (object | None)

  • geometry (object | None)

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 (its .eigenmodes yields the EigenmodeResult). 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}.

geometry: object | None = None#
mesh: Mesh#
n_modes: int = 5#
project: object | None = None#
sigma: float | None = None#
solver: str | None = None#
verbose: bool = True#
class magnelio.AnalysisScatteringTD(mesh, f_max, ports=None, elements=None, f_min=0.0, n_freq=201, excitation=None, monitors=<factory>, verbose=True, port_model='modal', band_options=None, project=None, geometry=None, backend='auto', precision=None, wall_model='perturbative', wall_sigma=None, wall_mu=1.0, wall_roughness=None, params=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) – Upper band edge of the analysis [Hz]. 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.

  • excitation (ExcitationSpec, 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 gaussian; a positive one a band-limited modulated_gaussian 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. Per-spec excitation fields are ignored.

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

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

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

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

  • 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. Modal pipeline only in this version (band + project is a follow-up).

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

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 not part of the recipe and are not restored (their data lives in the memory-efficient monitor write-through streams instead); they do not affect the leapfrog update, so V/I and S-parameters are unchanged.

Parameters:

verbose (bool)

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

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

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.

Return type:

ScatteringTDResult

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

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]

backend: str = 'auto'#
band_options: dict | None = None#
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#
excitation: ExcitationSpec | 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_max: float#
f_min: float = 0.0#
geometry: object | None = None#
mesh: Mesh#
monitors: tuple#
n_freq: int = 201#
params: dict | None = None#
port_model: str = 'modal'#
ports: Sequence[PortSpecCoax | PortSpecRectWG | PortSpecNumerical | PortSpecMultiConductor | PortSpecLumped | PortWaveguide | PortAnalytical] | None = None#
precision: str | None = None#
project: object | None = None#
verbose: bool = True#
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.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 None) – Material for cells not covered by any shape. 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

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

Interactive 3D view in Jupyter via pythonocc.

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

Parameters:

**kwargs – Forwarded (size, render_edges, edge_color, quality).

Return type:

JupyterRenderer

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)

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

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, pec_surface=None, boundary_conditions=None, ports=(), elements=(), _wall_backup=<factory>)#

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)

  • pec_surface (PECSurfaceData | None)

  • boundary_conditions (object)

  • ports (tuple)

  • elements (tuple)

  • _wall_backup (dict)

classmethod from_geometry(geometry, control, f_max)#

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.

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:
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))])
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

property Nx: int#
property Ny: int#
property Nz: int#
boundary_conditions: object = None#
edge_material: EdgeMaterialData | None = None#
elements: tuple = ()#
face_material: FaceMaterialData | None = None#
grid: GridLines#
material_id: np.ndarray#
material_library: dict[int, 'Material']#
pec_mask_edges: np.ndarray#
pec_surface: PECSurfaceData | 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 = ()#
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>, conformal=True, dey_mittra_eta=0.4, min_feature_gap=None)#

Parameters controlling grid-line generation.

The mesher uses two cell-size scales per axis:

  • h_max (bulk) is wavelength-based, h_max = λ_min / min_nodes_per_wavelength. Cells in regions far from material interfaces are capped at h_max.

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

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

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#
min_cell_size: float | None = None#
min_cells_per_feature: int = 4#
min_feature_gap: float | None = None#
min_nodes_per_wavelength: int = 20#
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, by its excited (port, mode) pair. 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).