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
GeometryModelor onMesh.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 thann_modesmodes emits aRuntimeWarningeither 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
projectis set, the model and the eigenmode result are written into the project directory and a read-onlyProjectreader is returned instead (its.eigenmodesyields theEigenmodeResult). Eigenmode analysis has no time-marching state, so it is a one-shot result — the streaming/resume machinery does not apply.- Return type:
- property boundary_conditions: dict[str, str]#
The mesh’s boundary closure as
{face: type_str}.
- geometry: object | None = None#
- 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 viaadd_port()). Passingports=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.portsholds 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 viaadd_element()); passingelements=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), sof_min = 0yields a first point atf_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 anO(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 ismax(f_cutoff, f_min)withf_cutoffthe excited mode’s cut-off frequency (zero for TEM and lumped ports). A zero lower edge yields a DC-inclusivegaussian; a positive one a band-limitedmodulated_gaussianover[max(f_cutoff, f_min), f_max], which keeps the pulse energy of TE/TM excitations above cut-off. Raises at run time whenf_maxdoes not exceed the excited mode’s cut-off. Per-specexcitationfields are ignored.monitors (iterable, default ()) – Field monitors forwarded to every
FITTimeDomainSolverrun.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 aPortSpecMultiConductor, 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-onlyProjectreader instead of an in-memoryScatteringTDResult. 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
projectis 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).
Noneresolves to theMAGNELIO_PRECISIONenvironment 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 tobackend. 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 bywall_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 thewall_sigmaoverride) damps the field through a passive rationalZ_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/orwall_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.AnalysisEigenmodekeeps 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_sigmaapplies to; enters the SIBC as the causally completedK(f)·R_sfit.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 thef_axisproperty).excited (iterable, optional) –
[(port_name, mode_idx), ...]or a list of bareport_namestrings (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| > 1artefacts until either the run is extended or the rectangular window is replaced (seetaper_signals). Set toNoneto disable the early stop; the run is then bounded bytotal_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_dborport_signal_stop_db, whichever first), backstopped only by the generousmax_time_stepsruntime 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 toTrueif the default rectangular DFT shows|S|>1ripple on a passive structure and you do not want to pushenergy_stop_dbhigher. 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_dbthen 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);Nonedisables 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 beforemax_time_steps), accepts the plateau as the effective floor, and stops with aRuntimeWarningstating 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 about900 · (structure size / wavelength)before a 60-dB decay is cut short.Noneremoves the cap (march forever — watch it live or resume; also disables the stall watchdog, which projects against this cap). Ignored whentotal_time_stepsis 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). Useresult.S("p_out", "p_in")for direct S-parameter access.- Return type:
Notes
When the run resolves to the band pipeline (
port_model, constructor docstring) the record is a fixed-length pulsed broadband run:energy_stop_dbandtaper_signalsdo 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_stepsremains 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 atf_max) and returns its mode solution as aPortReport— line impedance (numerical and, where available, analytical reference), cut-off frequencies, mode types, and per-modemodes[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
portsorder.- 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#
- 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 intosymmetry.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 intosymmetry.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 intosymmetry.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 intosymmetry.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 intosymmetry.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 intosymmetry.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 onMeshControland 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 asboundary_conditions=toFITTimeDomainSolver.- 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()raisesGeometryOverlapErrorwhen 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 tox >= 0and 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
Groupis 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:
- 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:
- 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, soAnalysisScatteringTD(mesh=mesh, ...)needs noports=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:
- 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 distinctMaterial.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=Truemarks 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.
epsilonmust then equal the model’seps_infon all three axes (it drives the mass matrix and the CFL limit); usedispersive()to get this right automatically. Incompatible withis_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.DispersionModelis reused verbatim: it is a relative-units pole-residue form, so itseps_inffield carries μ_inf here, and its passivity condition (Im ≥ 0 on the band) is the μ’’ ≥ 0 one.mumust then equal thateps_infon all three axes; usedispersive_mu(). Incompatible withis_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.
Nonelets 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 dispersive(name, model, mu=1.0, sigma=0.0)#
Material with a pole-residue dispersive permittivity.
epsilonis set to the model’seps_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:
- 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().muis 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.DispersionModelis reused verbatim for μ(ω): it is a relative-units pole-residue form, so itseps_inffield carries μ_inf and its constructors (debye,lorentz,drude, …) describe the magnetic analogues.Both permittivity and permeability can be dispersive at once — pass
dispersion=anddispersion_mu=to the constructor directly (epsilonandmumust 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:
- 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:
- 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 resistanceR_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:
- 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 = infon 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.mdfor 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) –
MeshControlparameters.f_max (float) – Maximum simulation frequency [Hz]. Determines target cell size.
- Return type:
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_cellsuniform 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)
- Return type:
Note
This method requires
pythonocc-corefor 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:
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 (
BoundaryConditionsor dict); this is the OCC-free counterpart of declaring it on theGeometryModel. Faces closed with PEC get their tangential edges masked here, so the mode solvers and the FIT update see the wall.Nonecloses every face with PEC. The grid is taken as given — unlikefrom_geometry(), this path cannot extend it for CPML or pull a PMC line in.
- Returns:
A fully populated
Mesh.- Return type:
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
Meshcarrying 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
GeometryModelwhen 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:
- with_elements(elements)#
Attach passive lumped elements to an already-built mesh.
The late-declaration path for meshes not built through
from_geometry(), mirroringwith_ports(): the returned mesh carries elements exactly as if they had been declared on theGeometryModelviaadd_element, andAnalysisScatteringTDresolves 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
selfplus the elements.- Return type:
- with_pec_boundaries(faces)#
Consolidate PEC bbox boundary conditions into
pec_mask_edges.Returns a new
Meshwhosepec_mask_edgesadditionally 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
Meshdirectly, 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 imposeE_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
selfexcept for an updatedpec_mask_edges. The original mesh is not mutated.- Return type:
- 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 theGeometryModel, andAnalysisScatteringTDresolves 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
selfplus the ports.- Return type:
- 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#
- 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 withoutfrom_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 ath_max.h_fine(interface) is feature-based and per axis,h_fine = min_gap / min_cells_per_featurewheremin_gapis 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 useh_fine.
Between interfaces and the bulk, cells grow geometrically by
growth_factorfromh_finetowardh_max(graded spacing). Importantly,h_fineonly 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 exceedh_fineby a few percent rather than add a cell; usemin_cell_sizefor 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_maxandh_fineare determined.Nonedisables the cap.min_cell_size (float or None, default None) – Hard lower bound on every cell.
Nonedisables 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_fineis computed. Without this, sub-feature gaps from float coordinates (e.g. unaligned random geometry) collapseh_fineto absurdly small values and explode the mesh. CAD geometries on a grid are unaffected.None(default) resolves to1e-5 xthe 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.
- 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-openProject), rebuilds the run’s operators from the stored reconstruction recipe, loads the latestcheckpoint.h5, and marches on — appending to the sameresults.h5streams. 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 bareresume(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 (seeAnalysisScatteringTD.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;Noneremoves 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:
- 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).