magnelio.io#
An open Project is both the store reader and a scattering result: it
satisfies ScatteringResult, so S, db,
phase, plot_s and the Touchstone / scikit-rf exports work on it
exactly as on the in-RAM result. Its runs mapping hands out one
Run per run — state, step count, clock, energy trace, result and
monitors — and checkpoint_state reads a resume checkpoint back as a
CheckpointState mapping; see Projects and runs in the technical
description for the vocabulary.
Project store and CAD file import.
open_project lives in the core magnelio namespace; this
component holds the reader/writer classes and geometry file I/O.
The one-shot save_project/load_project (io/hdf5.py) was removed;
the store supersedes it.
import_step / import_brep read geometry drawn in a CAD
system into the geometry API; import_pcb reads a printed circuit
board from the fabrication data its layout tool writes.
- class magnelio.io.CheckpointState(data, *, run=None, path=None)#
A run’s resume checkpoint, as the solver’s
state_dictreads back.A read-only mapping with the nested shape
FITTimeDomainSolverwrote —n_completed, the peak energy and port signal, the flateandhfield vectors, and a group per boundary, port and monitor — sostate["e"]and"dispersion" in statework as on the plain dict, while printing the object shows its size and step rather than its field vectors.- Parameters:
data (dict)
run (str | None)
- get(k[, d]) D[k] if k in D, else d. d defaults to None.#
- items() a set-like object providing a view on D's items#
- keys() a set-like object providing a view on D's keys#
- values() an object providing a view on D's values#
- class magnelio.io.LoadedGeometry(shapes, background)#
Read-only geometry recovered from a project’s BREP + material list.
Mirrors the iteration surface of
GeometryModel(shapes,background, iteration,len) so it drops into the same consumers.- Parameters:
shapes (list)
- plot(**kwargs)#
Interactive 3D view — same wrapper as
GeometryModel.plot.
- plot_cross_section(normal, position, **kwargs)#
2D cross-section — same wrapper as
GeometryModel.plot_cross_section.- Parameters:
normal (str)
position (float)
- class magnelio.io.Project(path)#
Read-only view over a project directory.
Lazily loads and caches the mesh, geometry and setup metadata on first access. Implements the same scattering-result contract as the in-RAM
ScatteringTDResult(seemagnelio.analysis.result_interface), so a separate post-processing script and the return value ofrun()are one and the same reader.- Parameters:
path (str | Path)
- S(out_port, in_port, *, mode_out=0, mode_in=0, f_axis=None)#
S-parameter column, derived on read (optionally on a custom
f_axis).
- a(port, mode=0, *, excited=None, f_ref=None, destagger=True)#
Incident power-wave time series
a(t)(seeScatteringTDResult.a).
- b(port, mode=0, *, excited=None, f_ref=None, destagger=True)#
Outgoing power-wave time series
b(t)(seeScatteringTDResult.b).
- checkpoint_state(excited=None)#
Load a run’s resume checkpoint (
state_dict), orNone.Returns the solver state persisted at the last periodic / final / graceful-abort checkpoint as a
CheckpointState— a read-only mapping with the nested-dict shape of the solver’sstate_dict— orNoneif the run wrote no checkpoint (streaming without resume, or aborted before the first interval).resume()feeds this straight intoFITTimeDomainSolver.load_state_dict; then_completedentry is the step the checkpoint corresponds to.- Parameters:
excited (str | tuple[str, int] | None)
- Return type:
CheckpointState | None
- db(out_port, in_port, *, mode_out=0, mode_in=0, floor_db=-200.0, f_axis=None)#
S-parameter magnitude in dB, derived on read (see
S()).floor_dbclamps the result from below so an exact zero stays plottable instead of becoming-inf.
- deembed(distances)#
Shift port reference planes; return the de-embedded S-matrix.
Removes the feed-line propagation between a port plane and its new reference plane:
result.deembed({"port1": d})moves port1’s reference plane the distanced[m] from the port plane into the domain, and every S-parameter touching that port is multiplied by the inverse line propagation factor overd— reflections twice, transmissions once per shifted end. A negative distance moves the plane outward (adds line length); ports not named keep their plane.The shift uses the discrete dispersion of the port’s uniform feed chain — the same grid propagation the solver applied — so de-embedding a uniform feed line removes its phase down to the accuracy floor of the run itself, including the grid-dispersion part that an analytic
exp(-jβd)would leave behind on coarse meshes. It assumes the cross-section stays that of the port over the shifted length. A quasi-TEM channel (microstrip, CPW — an inhomogeneous cross-section on modal Mur) carries no certified line parameters; the run keeps the port’s dispersion record instead, and the shift uses the true discrete modes of the feed solved at every frequency of the axis, so the line’s physical dispersion is removed as well (the first call on such a result spends a few seconds on that solve). Only a channel with neither — a feed section that is not a uniform chain behind the port — falls back to the mode’s continuumγ(f), for a quasi-TEM mode the frequency-flat quasi-static one.Below its cut-off a channel’s factor grows exponentially with distance, so de-embedded values there keep the diagnostic character the raw ones have. Lumped ports carry no feed-line dispersion; naming one raises.
- Parameters:
distances (dict[str, float]) – Per-port shift distance [m], positive into the domain.
- Returns:
A new result referenced at the shifted planes — the original is untouched. It answers
S/db/phase/plot_sand the Touchstone / scikit-rf exports.- Return type:
- energy_trace(excited=None)#
Stored
(step, time, energy)trace of a run [structured array].The same as
project.runs[name].energy_trace; this form takes a scattering run’s excited pair and may omit the selector on a one-run project.- Parameters:
excited (str | tuple[str, int] | None)
- export_paraview(excited=None, *, glyph_percentile=98.0, bake_state=True, pvpython=None)#
Write the ready-to-open ParaView session for one run.
The only place the ParaView artefacts are written: a run’s close, a resume and the eigenmode solver write none of them. Writes the tessellated
geometry.vtmin the project directory if it is missing, one.vtrper frame of every field monitor underparaview/(collected by a.pvdper monitor),paraview_open.py(open withparaview --script=…), and — whenpvpythonis available and bake_state — the double-clickableparaview.pvsm, all in the run directory. Call it again after a resume; the set is regenerated.- Parameters:
excited (str or tuple, optional) – Selects the run: a port name, or a
(name, mode)pair. May be omitted when the project holds one run.glyph_percentile (float, default 98.0) – Percentile of the field-vector magnitude used as the glyph clip cap (edge singularities would otherwise dictate the arrow scaling).
bake_state (bool, default True) – Bake
paraview.pvsmviapvpythonwhen available.pvpython (str or Path or sequence of str, optional) – Which
pvpythonbakes the state, when the machine carries more than one ParaView (default:MAGNELIO_PVPYTHON, else the first onPATH). A path, or the command that launches one — a sandboxed ParaView is reached through its runner, e.g."flatpak run --command=pvpython org.paraview.ParaView". A state file names its proxies the way the release that wrote it spells them, so bake it with the ParaView that will open it;paraview_open.pybuilds the session live and needs no such care.
- Returns:
Written artefact paths (
script,stateorNone,monitors); empty when there is nothing to visualise.- Return type:
dict
- export_paraview_eigenmodes(*, glyph_percentile=98.0, bake_state=True, pvpython=None)#
Write the ParaView session for the stored eigenmodes.
The eigenmode counterpart of
export_paraview(), and like it the only place these files are written. Eigenmodes have no excitation and no time axis, so they belong to the project rather than to a run: the artefacts land in the project directory itself (paraview_open.py,paraview.pvsm,paraview/eigenmodes.pvdand one.vtrper mode).Stepping the ParaView time axis steps through the modes — degenerate pairs share an eigenfrequency exactly, so the frequency cannot serve as that axis; it travels as field data instead. Fields are peak-normalised per mode, since an eigenvector carries no absolute amplitude, and the divisors are written alongside so the scaling stays reversible.
- Parameters:
glyph_percentile (float, default 98.0) – Percentile of
|E|used as the glyph magnitude clip cap (a field peak on a conductor edge would otherwise dictate the arrow scaling).bake_state (bool, default True) – Bake
paraview.pvsmviapvpythonwhen available.pvpython (str or Path or sequence of str, optional) – Which
pvpythonbakes the state, when the machine carries more than one ParaView (default:MAGNELIO_PVPYTHON, else the first onPATH). A path, or the command that launches one — a sandboxed ParaView is reached through its runner, e.g."flatpak run --command=pvpython org.paraview.ParaView". A state file names its proxies the way the release that wrote it spells them, so bake it with the ParaView that will open it;paraview_open.pybuilds the session live and needs no such care.
- Returns:
Written artefact paths (
script,stateorNone,monitors); empty when the project stores no eigenmodes.- Return type:
dict
Examples
>>> project = mio.AnalysisEigenmode(mesh=mesh, n_modes=4, ... project="cavity").run() >>> project.export_paraview_eigenmodes()["script"]
- follow(interval=2.0, *, plot=False, timeout=None, stream=None)#
Watch this project and show its state in place until it is finished.
The zero-code form of
watch(): at every change the project’s summary and run table are shown again, replacing the previous one rather than scrolling below it. In a notebook the cell’s output is cleared and the table redrawn; on a terminal the table is redrawn over its own lines; in a log file each table is appended. Blocks until the project is finished (done,abortedorstale) ortimeoutseconds have passed, and returns the project. Interrupting the kernel (Ctrl-C) ends it early.plot=Trueadds the energy plot (plot_energy()) below the table, redrawn with it. A figure drawn inside a loop of your own would not show until the cell ends — the notebook’s inline backend flushes figures per cell — so the figure is rendered here at every change and replaced with the table. To draw your own picture, pass a callableplot(project, ax)that draws into the fresh axes it is given:def draw(proj, ax): proj.plot_energy(ax=ax) ax.set_ylim(-80, 0) proj.follow(interval=5, plot=draw)
On a terminal with a window-capable matplotlib backend the picture is one window redrawn in place; without one (a log, a headless run) only the table is shown.
For a panel that keeps moving while you work in other cells, see
monitor().- Parameters:
interval (float, default 2.0) – Seconds between two looks at the store.
plot (bool or callable, default False) –
Truefor the energy plot; a callableplot(project, ax)for a picture of your own.timeout (float, optional) – Give up after this many seconds.
stream (file-like, optional) – Where the text form goes; defaults to
sys.stdout. A notebook is recognised on its own.
- monitor(interval=2.0, *, x='time')#
A live panel for a notebook: the run table above the energy plot.
Returns an
ipywidgetsbox that a background thread refreshes everyintervalseconds — the project summary with its run table as HTML, and every run’s stored energy in dB below the peak as a picture — until the project is finished. Leave it as the last expression of a cell; the cell returns at once and the panel keeps moving.panel.stop()ends the refresh early. Needs thejupyterextra (pip install 'magnelio[jupyter]').- Parameters:
interval (float, default 2.0) – Seconds between refreshes.
x ({"time", "step"}, default "time") – Horizontal axis of the energy plot.
- monitors_for(excited=None)#
Lazy monitor readers for one run, keyed by monitor name.
Resolves each monitor kind by name into its matching lazy reader —
_LoadedFieldMonitor(MonitorFieldTime snapshots) and_LoadedFluxMonitor(MonitorFluxTime scalar series) from the run’sresults.h5, plus_LoadedFreqMonitor(MonitorFieldFrequency DFT) fromfields_freq.h5and_LoadedMonitorWallLoss(per-tag dissipated fractions) fromwall_loss.h5. The user only knows the name, not the kind or the file.excitedselects the run; it may be omitted when the project holds one run.- Parameters:
excited (str | tuple[str, int] | None)
- Return type:
dict
- phase(out_port, in_port, *, mode_out=0, mode_in=0, deg=True, unwrap=True, f_axis=None)#
Phase of one S-parameter over the frequency axis.
- Parameters:
out_port (str) – Observed / excited port names (modes via
mode_out/mode_in), as inS().in_port (str) – Observed / excited port names (modes via
mode_out/mode_in), as inS().deg (bool, default True) – Return degrees;
Falsereturns radians.unwrap (bool, default True) – Unwrap 2π discontinuities along the frequency axis.
f_axis (array-like, optional) – Custom frequency axis, on hosts whose
S()can recompute the spectrum (run results); a plainSParameterResultholds one fixed axis and rejects it.mode_out (int)
mode_in (int)
- Returns:
Phase per frequency point.
- Return type:
np.ndarray
- plot_balance(*excitations, deficit=False, ax=None)#
Plot the power balance of every excitation.
For each excited channel j the sum of the squared magnitudes over the observed channels,
Σ_i |S_ij|²— the share of the incident power that comes back out of the ports. On a lossless, fully exported network it is one; what is missing is what left the ports: ohmic and dielectric loss, and radiation.The reading depends on two things the plot cannot check. Only channels present in the result are summed, so a network whose higher modes or whose ports were not all exported reads short and the deficit looks like loss (
is_completesays whether every observed channel was also excited). And a channel that carries no propagating mode at a frequency isNaNthere; an evanescent channel transports no active power, so it counts as zero rather than poisoning the sum.- Parameters:
*excitations (str or tuple) – Excited channels to plot, each
portor(port, mode). Without arguments every excitation is plotted.deficit (bool, default False) – Plot
10·log10(1 − Σ)— the power that left the ports, in dB — instead of the sum itself. The useful form when the balance is close to one, where the interesting number is how far from it.ax (matplotlib.axes.Axes, optional)
- Returns:
fig (matplotlib.figure.Figure)
ax (matplotlib.axes.Axes)
- plot_energy(*, x='time', floor_db=None, ax=None)#
Plot every run’s stored energy in dB below its peak, one curve per run.
The figure the progress line reports, for the whole project: the legend names the runs, and the energy criterion is a dashed line when every run shares one.
xis"time"(nanoseconds) or"step"; the axis runs from ten dB below the criterion (floor_dbpins it) to +5 dB;axdraws into existing axes. Returns(fig, ax).- Parameters:
x (str)
floor_db (float | None)
- plot_polar(*pairs, mark=None, r_max=None, ax=None)#
Plot channels in the complex plane, magnitude over phase.
The polar counterpart of
plot_smith()for channels the Smith chart does not describe — transmission above all, whose magnitude is not bounded by one in a network with gain and which has no impedance reading.- Parameters:
*pairs (tuple) – As
plot_smith(); without arguments every recorded channel of every excitation.mark (array_like, optional) – Frequencies [Hz] to mark on every trace.
r_max (float, optional) – Radial limit; default the peak magnitude, at least one.
ax (matplotlib.axes.Axes, optional) – Must be a polar axes; one is created otherwise.
- Returns:
fig (matplotlib.figure.Figure)
ax (matplotlib.axes.Axes)
- plot_s(*pairs, db=True, floor_db=-200.0, ax=None)#
Plot S-parameter magnitudes over frequency.
- Parameters:
*pairs (tuple) – Channels to plot, each
(out_port, in_port)or(out_port, in_port, mode_out, mode_in). Without arguments every recorded channel of every excitation is plotted.db (bool, default True) – Magnitude in dB (with floor_db) instead of linear.
floor_db (float, default -200.0) – Clip floor for the dB display.
ax (matplotlib.axes.Axes, optional) – Axes to draw into; a new figure is created otherwise.
- Returns:
fig (matplotlib.figure.Figure)
ax (matplotlib.axes.Axes)
- plot_smith(*pairs, mark=None, labels=True, ax=None)#
Plot reflection channels on a Smith chart.
The complex reflection coefficient traced over frequency on the unit disc, with the circles of constant normalised resistance and reactance behind it. The chart is only readable against one normalisation: a channel whose reference impedance varies with frequency (a dispersive waveguide mode, DD-244) has no single set of circles, and its trace here is the power-wave coefficient against a moving reference. Call
renormalize()first —renormalize(50)— when that matters; a warning names the case.- Parameters:
*pairs (tuple) – Channels to draw, each
(out_port, in_port)or(out_port, in_port, mode_out, mode_in). Without arguments the reflection channel of every excitation.mark (array_like, optional) – Frequencies [Hz] to mark and label on every trace.
labels (bool, default True) – Draw the resistance labels of the chart’s grid.
ax (matplotlib.axes.Axes, optional) – Axes to draw into; a square figure is created otherwise.
- Returns:
fig (matplotlib.figure.Figure)
ax (matplotlib.axes.Axes)
- reference_impedance(port, mode=0)#
Reference impedance [Ω] of one channel along the frequency axis.
The real impedance the channel’s power waves — and so its row and column of the S-matrix — are defined against: the line impedance of a TEM or quasi-TEM port mode as the grid carries it, the wave impedance of a hollow-pipe mode (which varies with frequency), the Thévenin impedance of a lumped port. Full-model values on ports cut by a symmetry plane. See
renormalize()for moving to a common reference.- Parameters:
port (str)
mode (int)
- Return type:
- refresh()#
Re-read the metadata and drop cached run / S-parameter data.
A project that is not finished re-reads its metadata on its own whenever the file changes; call this to force a re-read on a finished project (a run resumed elsewhere), or to drop the cached run and S-parameter data. The immutable model (mesh, geometry) is kept. Returns
selffor chaining (project.refresh().s_params).- Return type:
- renormalize(z_ref)#
Re-reference the S-matrix to new port impedances.
The raw S-matrix is measured against each port mode’s own impedance on the grid (
reference_impedance()) — a uniform line is matched there whatever its impedance came out at. This returns the same network againstz_refinstead, typicallyrenormalize(50): what a network analyser with 50 Ω reference planes would read, and what a circuit simulator expects before it cascades this block with others. It acts on the square matrix over the excited channels: a channel that was observed but never excited stays matched to its own impedance and is left out, as the exports leave it out.Whether the re-referenced mismatch is real is a modelling question: a 49 Ω grid line feeding a 50 Ω system does reflect, while a line meant to be the 50 Ω one shows a discretisation artefact — converge its impedance on the port plane first (
refine_port_modes).- Parameters:
z_ref (float or dict) – New real reference impedance [Ω] for every channel, or a mapping
{port_name: Z}/{(port, mode): Z}, each value a scalar or an array on the frequency axis.- Returns:
A new result on the same channels; the original is untouched. It answers
S/db/phase/plot_sand the Touchstone / scikit-rf exports.- Return type:
- result(name=None)#
One run as a
TDResult.Rebuilds the in-RAM result object of the run from the store: the recorded port signals, the sampled excitations, the energy trace and lazy readers for the run’s monitors.
nameis the run name (run_1, or thename=given torun()); a scattering channel run may be named by its excited pair. May be omitted when the project holds one run.- Parameters:
name (str | tuple[str, int] | None)
- to_skrf(name='magnelio', *, channels=None, z_ref=None)#
Return the S-matrix as a
skrf.Network.Requires scikit-rf (extra
magnelio[interop]). Same sub-matrix, channel selection and warning asto_touchstone(); the network’sz0carries each channel’s reference impedance per frequency.- Parameters:
name (str, optional) – Network name.
channels (sequence of str or (str, int), optional) – Explicit channel selection, as in
to_touchstone().z_ref (float or dict, optional) – Renormalise first, as in
renormalize().
- Return type:
skrf.Network
- to_touchstone(path, *, channels=None, z_ref=None)#
Write the S-matrix as a Touchstone
.sNpfile.Exports the square sub-matrix over the excited channels — one Touchstone port per channel, so a multi-mode port occupies one port per mode. Channels that were observed but never excited are dropped from rows and columns alike; they carry a reflection-free boundary throughout the run, so the export is the network seen with them matched, the same quantity a network analyser measures with its unused ports terminated.
The option line’s
Rstates the reference impedance the data refer to. Touchstone 1.x holds one constant value for all ports, so passz_ref(typicallyz_ref=50) to renormalise first when the ports’ own references differ or vary with frequency; without it such a file is written with a nominalR 50, a warning, and each port’s actual reference in the header.Warns when a port that is exported carries propagating modes that the export leaves out: the file then looks like a complete N-port while the mode conversion at that port is missing from it.
The
.sNpextension must agree with the number of exported channels — Touchstone records the port count nowhere else — so a mismatch raises instead of writing an unreadable file. A path without an extension gets the matching one.- Parameters:
path (str or pathlib.Path) – Output file.
<name>.s{N}p, or<name>to have the extension filled in.channels (sequence of str or (str, int), optional) – Select the exported sub-network explicitly, e.g.
["port1", "port3"]to cut a two-port out of a fully excited three-port. A bare port name means mode 0. Every entry must have been excited.z_ref (float or dict, optional) – Renormalise to this reference before writing, as in
renormalize().
- Return type:
None
- watch(interval=2.0, *, on_change=None, timeout=None)#
Follow this project while another process writes it.
Polls the store every
intervalseconds and reports each change — a run starting or ending, a new energy sample, a status change — until the project is finished (done,abortedorstale) ortimeoutseconds have passed. The first report comes at once, so the current state is always delivered, and so is the final one.Without
on_changethis is a generator yielding the project itself at every change:for proj in mio.open_project("magic_tee").watch(): print(proj) # the run table, as it moves
Print (or plot) what you want to see: inside a loop a bare expression such as
proj.runs["port1_mode0"].energy_dbdisplays nothing, in a notebook as anywhere else — only the last expression of a cell is shown. For the ready-made display that replaces itself at every change, seefollow(); for a panel that keeps moving while other cells run,monitor().With
on_changethe loop runs here: the callable is called with the project at every change, and the project is returned when the loop ends:mio.open_project("magic_tee").watch(on_change=lambda p: p.plot_energy())
- Parameters:
interval (float, default 2.0) – Seconds between two looks at the store.
on_change (callable, optional) –
on_change(project)at every change, instead of yielding.timeout (float, optional) – Give up after this many seconds; the generator (or the call) then ends with the project in whatever state it is — check
statusto tell a finished project from one still marching.
- Returns:
The generator form without
on_change; the project itself with it.- Return type:
generator, or Project
- property channels: tuple#
Observed
(port_name, mode_idx)pairs, in S-matrix order.
- property eigenmodes#
The stored
EigenmodeResult, orNoneif absent (lazy).
- property elapsed: float | None#
Wall time of the marching [s], summed over the runs and their resumes.
- property excitations: tuple#
Excited
(port_name, mode_idx)pairs — the S-matrix columns stored.
- property finished#
Wall-clock end (UTC datetime) of the last march;
Nonewhile one marches.
- property geometry#
The reconstructed geometry (
LoadedGeometry) orNone.Requires OCC. Returns
Noneif the project carries no geometry (geometry.brepabsent).
- property grid#
The mesh grid (shortcut for
project.mesh.grid).
- property meta: dict#
The parsed
project.jsoncontents.Re-read from disk whenever the file changed, for as long as the project is not finished — so a project opened while a solver writes it shows the current state without
refresh(). The file is small and replaced atomically by the writer, so the check is onestatper access and a parse only on a change. Once the stored status is terminal the parsed copy is kept.
- property monitors: dict#
Monitor readers of the project’s sole run.
Convenience for the common single-excitation case (
project.monitors["Ez_plane"].plot(t=…)) — resolves time, flux, frequency and wall-loss monitors by name (seemonitors_for()). With more than one run the monitor name alone is ambiguous — usemonitors_for()with the excited pair.
- property params: dict#
Free-form user parameters stored with the project.
Whatever dict the analysis was given as
params=— design variables, sweep coordinates, notes. Empty when none were stored.
- property reference_signal#
Excitation waveform of the longest run (see
ScatteringTDResult).
- property runs: _RunIndex#
The project’s runs by name, each a
Run.A read-only mapping:
project.runs["port1_mode0"]is the run object,list(project.runs)the names, and the mapping itself prints as a table of every run’s state. Run states:pending(planned by the analysis, not started — no run directory on disk yet) →running→done/aborted; arunningrun whose writer process no longer exists reads asstale.
- property s_params#
The full S-matrix, derived on read from the stored signals.
- property settings: RunSettings#
Settings the stored run was produced with (result contract).
Filled from the stored recipe and run data; entries the store does not (yet) record are
None.
- property setup: dict#
Analysis setup metadata stored at creation time.
- property signals: dict#
{excited_key: {(port, mode): (V, I)}}across all runs.
- property started#
Wall-clock start (UTC datetime) of the first march, or
None.
- property status: str#
created→running→done/aborted/stale.donemeans every planned run is done: the analysis pre-registers its runs aspending, so the status holds atrunningin the gaps between sequential runs.abortedmeans a run ended on a graceful stop or an error and the analysis went with it — resume it, or run the analysis again.staleis not stored: it isrunningon disk with nobody writing — the solver process recorded on the project is gone from this host. A project written on another host cannot be told stale and readsrunninguntil its writer finishes.- Type:
Project status
- class magnelio.io.ProjectStore(path)#
Write-once model store for a project directory.
Persists the static model — geometry (BREP + VTM), mesh, and setup metadata. Streamed time-domain results and per-run resume checkpoints are added by later work packages;
ProjectStoreholds the directory handle they will attach to.Create a store with
create(); read one back withopen_project().- Parameters:
path (str | Path)
- classmethod create(path, mesh, *, geometry=None, setup=None, paraview=None, exist_ok=True)#
Create a project directory and write the static model.
- Parameters:
path (str or Path) – Project directory (created if absent).
mesh (Mesh) – The simulation mesh (grid, materials, conformal sub-cell data) — written to
mesh.h5.geometry (GeometryModel or list, optional) – Source geometry. When given, an exact
geometry.brepis written, plus ageometry.jsoncarrying per-shape names and materials in compound order. The tessellatedgeometry.vtmfor ParaView is written byProject.export_paraview().setup (dict, optional) – JSON-serialisable analysis metadata (dt, frequency plan, port/BC/excitation descriptions). Stored under
setupinproject.json; consumed by later work packages.paraview (bool, optional) – Deprecated and without effect: ParaView files are written on request by
Project.export_paraview().exist_ok (bool, default True) – Reuse an existing directory (raise if False and it exists).
- Return type:
- mark_analysis_finished(elapsed)#
Close the analysis stamp opened by
mark_analysis_started().- Parameters:
elapsed (float)
- Return type:
None
- mark_analysis_started()#
Stamp the start of an analysis call (
run()orresume()).The per-run stamps cover the marches; this one covers the whole call, setup included, which is what the
finished inline reports and what a reader wants to see at the top of a project.- Return type:
None
- open_run(run_name, *, excitations, excited, dt, f_axis, channels, port_modes, port_normal_dx, port_line_params, excitation_fns, recorder, port_band=None, port_model='modal', port_reference_scale=None, energy_stop_db=None, port_signal_stop_db=None, total_time_steps=None, taper_signals=False, monitors=None, grid=None)#
Open a live streaming sink for one time-domain run.
Declares the run’s resizable
results.h5streams (HDF5-SWMR), registers the run inproject.jsonasrunning(replacing apendingpre-registration, seeregister_planned_runs()), and returns a solver-attachable_RunSink. The solver appends the V/I and energy tails duringrun(); call_RunSink.close()when the run finishes to flip its state todone.run_nameis sanitised for the filesystem.excitationsare the run’s Excitation dicts (DD-224),excitedthe S-matrix column of a scattering channel run (Noneon a general time-domain run),excitation_fnsthe[(key, drive), ...]sampled alongside the signals.energy_stop_db/total_time_stepsare recorded in the run index soresume()can default to the run’s original stop criterion — e.g. finish an aborted run to the target it was launched with, without the caller repeating it.monitors+griddeclare the field-monitor write-through streams; the sink drains eachMonitorFieldTimeto disk as the run proceeds.- Parameters:
run_name (str)
excitations (list)
excited (tuple[str, int] | None)
dt (float)
port_modes (dict)
port_normal_dx (dict)
port_line_params (dict)
port_band (dict | None)
port_model (str)
port_reference_scale (dict | None)
energy_stop_db (float | None)
port_signal_stop_db (float | None)
total_time_steps (int | None)
taper_signals (bool)
- Return type:
_RunSink
- register_planned_runs(planned)#
Pre-register planned runs as
pendingin the run index.plannedis an iterable of(run_name, entry)pairs, one per run the caller is about to stream,entrythe index payload ({"excited": [port, mode]}for a scattering channel). Registering them up front closes the status gap between sequential runs: without it, finishing run k while run k+1 is not yet in the index made_finalize_run()reportstatus = "done"for a project that was still mid-analysis. Apendingentry counts as not-done, so the project status stays"running"until the last planned run finishes.Existing entries are left untouched (fill-in: a second analysis adding excitations must not clobber
done/abortedruns).pendingentries carry no run directory on disk; they are replaced wholesale whenopen_run()starts the run.- Return type:
None
- reopen_run(run_name, *, recorder, excitation_fns, dt, n_keep, step_offset, monitors=None, grid=None, monitor_keep=None, flux_keep=None)#
Reopen a run’s streams to append a resumed tail.
Truncates
results.h5back ton_keep(the checkpoint’s committed step count), marks the runrunningagain, and returns a sink whose reference sampling is offset to global stepstep_offsetso the appended tail is phase-aligned with the pre-resume stream. The caller attaches it to the resuming solver exactly like a fresh sink.monitor_keeptruncates each field monitor’s stream to its checkpointed snapshot count so the resumed run appends onward consistently.- Parameters:
run_name (str)
dt (float)
n_keep (int)
step_offset (int)
monitor_keep (dict | None)
flux_keep (dict | None)
- Return type:
_RunSink
- write_eigenmodes(result)#
Persist an
EigenmodeResulttoeigenmodes.h5.Eigenmode analysis has no time-marching state, so it produces a one-shot result rather than a streamable/resumable run. The ParaView session is written on request by
Project.export_paraview_eigenmodes()(DD-262).- Return type:
None
- class magnelio.io.Run(project, name)#
One run of a project: its index entry, its files, its result.
Handed out by
project.runs[name]. A live view — every attribute reads the project’s current metadata, so a run that is marching in another process shows its growing step count and energy without any call on your side.pendingruns have no directory yet and read as empty.- Parameters:
project (Project)
name (str)
- checkpoint_state()#
The run’s resume checkpoint (see
Project.checkpoint_state()).
- plot_energy(*, x='time', floor_db=None, ax=None)#
Plot the run’s stored energy in dB below its peak.
The figure the progress line reports, over the whole run, with the run’s energy criterion as a dashed line.
xis"time"(nanoseconds) or"step"; the axis runs from ten dB below the criterion (floor_dbpins it) to +5 dB;axdraws into existing axes. Returns(fig, ax).- Parameters:
x (str)
floor_db (float | None)
- result()#
The run as a
TDResult(seeProject.result()).
- property dt: float | None#
Solver time step [s].
- property elapsed: float | None#
Wall time of the marching [s], resumes summed.
On a run that is still marching, the time since its current march started is added to what earlier marches booked.
- property energy_db: float | None#
The latest stored energy in dB below the run’s peak — the progress figure.
- property energy_stop_db: float | None#
The energy criterion [dB below peak] the run stops at, if any.
- property energy_trace: ndarray#
Stored energy at the solver’s check cadence — a structured array.
Fields
step,time[s] andenergy[J]; read afresh on every access while the run marches, so it grows as the solver flushes. Empty on a pending run.
- property excitations: tuple#
Every
(source, mode)that drove the run.
- property excited: tuple | None#
The excited
(port, mode)of a scattering run;Noneon a general run.
- property finished#
UTC datetime the latest march ended;
Nonewhile marching.
- property has_checkpoint: bool#
Whether a resume checkpoint exists on disk.
- property host: str | None#
Host name of the solver that wrote (or writes) the run.
- property monitors: dict#
The run’s monitor readers by name (see
Project.monitors_for()).
- property n_energy_samples: int#
How many energy samples the run has flushed — the cheapest sign of life.
- property n_steps: int#
Leapfrog steps committed to disk so far.
The index books the count when a march ends; while a run marches, the step of its latest flushed energy sample stands in, so the count moves with the solver.
- property path: Path#
The run’s directory (
runs/<name>), whether it exists yet or not.
- property pid: int | None#
Process id of the solver that wrote (or writes) the run.
- property port_model: str | None#
"modal"or"band"— the port pipeline that wrote the run.
- property port_signal_stop_db: float | None#
The port-signal criterion [dB below peak] the run stops at, if any.
- property resumed#
UTC datetime the latest resumed march started, or
None.
- property started#
UTC datetime the first march started, or
None.
- property state: str#
pending,running,done,aborted— orstale.staleisrunningon disk with the recorded solver process gone from this host: the kernel died, or the machine rebooted. Resume the run, or run the analysis again.
- property stop_reason: str | None#
energy,port_signal,steps, …- Type:
Why the marching ended
- property total_time_steps: int | None#
The fixed step count the run was given, or
Nonefor an open-ended run.
- magnelio.io.import_brep(path, *, unit, material=None, heal=False, name=None)#
Import the solids of a BREP file.
BREP is the geometry kernel’s native dump: exact, but it records nothing but the geometry — no length unit, no names, no colours. The unit therefore has to be stated, and it has to be right: a file drawn in millimetres and read as meters is a thousand times too big, and nothing in the file says so. Prefer
import_step()whenever the CAD system can write STEP.- Parameters:
path (str or Path) – The
.brepfile to read.unit (str or float) – Length unit the file is written in:
"m","cm","mm","um","nm","in","mil"— or a number giving the length of one unit in meters.material (Material, optional) – Material for every solid in the file. Omitted, the solids come back as construction bodies (see
import_step()).heal (bool) – Repair each solid on import. Off by default: a BREP file comes from the same kernel and is normally already clean.
name (str, optional) – Name for the returned Group and the base name of its solids. Defaults to the file name.
- Returns:
The imported solids as
ImportedSolidmembers, named<name>for a single solid and<name>_1,<name>_2, … for several.- Return type:
- Raises:
FileNotFoundError – If path does not exist.
ValueError – If the file cannot be read, or contains no solid.
Examples
from magnelio import Material from magnelio.io import import_brep horn = import_brep("horn.brep", unit="mm", material=Material.pec())
- magnelio.io.import_pcb(path, materials=None, *, copper=None, name=None)#
Import a printed circuit board from its fabrication data.
The set is the one a layout tool writes for a board house: Gerber files for the copper layers and the board outline, Excellon drill files, and the
.gbrjobjob file that records the stackup. The job file is required — the Gerber files are flat drawings and say nothing about layer thicknesses or the dielectric, so without it there is no third dimension to build.Each layer of the stackup arrives as one solid: a copper layer with its real thickness, a dielectric filling the board outline. Plated holes arrive as solid copper barrels joining the layers they run between; unplated holes and slots are cut out of everything they pass through. Solder mask and silkscreen are ignored.
- Parameters:
path (str or Path) – The
.gbrjobjob file, or the folder holding the fabrication set (which must contain exactly one job file).materials (Material or dict, optional) – A single
Materialis given to every solid. A dict maps solid names to materials; keys may use shell wildcards ("via_*"), a literal name beats a wildcard, and every key must match at least one solid, so a typo is reported instead of silently doing nothing. The names are the layer names of the stackup ("F.Cu","dielectric_1") and"via_1","via_2", … for the barrels.copper (Material, optional) – Material for every copper layer and every plated barrel. Defaults to a perfect electric conductor. A copper layer is thin against any usable cell size, which the mesher resolves below the cell only for a perfect conductor — give a finite conductivity here only with a mesh fine enough to hold the metal thickness.
name (str, optional) – Name for the returned Group. Defaults to the project name in the job file, or the job file’s name.
- Returns:
The layers and barrels, each an
ImportedSolidcarrying its name and material. Add it to a model like any other shape.- Return type:
- Raises:
FileNotFoundError – If the job file, or a file it names, is missing.
ValueError – If the fabrication data is malformed, states no layer thicknesses, or a materials key matches no solid.
- Warns:
UserWarning – If the stackup states a loss tangent (which is reported, never modelled), or omits a dielectric constant.
Notes
Copper is placed where the stackup puts it, so the top of the topmost dielectric is at
z = 0, the top copper occupies0to its thickness, and the stack grows downwards.A plated hole is modelled as a solid cylinder rather than as a plated wall around a void. The wall is a closed conductor, so the space it encloses carries no field either way.
A copper layer is thin against any usable cell size. The mesher resolves it below the cell — one grid plane, thickness carried in the sub-cell material fractions — for a perfect conductor and only when
MeshControlcarries amin_cell_sizelarger than the metal thickness.Examples
Import a board and give the substrate a material of its own:
from magnelio import GeometryModel, Material from magnelio.io import import_pcb board = import_pcb( "fabrication/", {"dielectric_1": Material(name="RO4350B", epsilon=(3.66,) * 3)}, ) model = GeometryModel().add(board)
Inspect the names before assigning anything:
board = import_pcb("fabrication/") print([solid.name for solid in board.members()])
- magnelio.io.import_step(path, materials=None, *, heal=True, unify=False, name=None)#
Import the solids of a STEP file.
The file’s length unit is read from the file itself, so a model drawn in millimetres arrives at its true size in meters and no conversion is needed on the caller’s side. Assemblies are flattened: every solid comes back where it sits in the assembly, as a member of one
Group.STEP carries no material physics — only names — so materials are assigned here, against the name each solid carries in the file. Names survive a re-export of the same CAD model, so the same call keeps working after the drawing changes.
- Parameters:
path (str or Path) – The
.step/.stpfile to read.materials (Material or dict, optional) – A single
Materialis given to every solid. A dict maps solid names to materials; keys may use shell wildcards ("shield_*"), a literal name beats a wildcard, and"*"acts as a catch-all. Every key must match at least one solid, so a typo is reported instead of silently doing nothing. Solids left unmatched (and every solid if materials is omitted) come back as construction bodies — usable as Boolean operands, rejected byadd()until they are given a material.heal (bool) – Repair each solid on import (tolerances, orientation, small topological defects). Cheap and harmless on a clean file; turn it off only to inspect a file exactly as written.
unify (bool) – Additionally merge adjacent faces that lie on the same surface. CAD kernels often split one planar face into many; merging them simplifies the solid, at the price of editing its topology. Off by default.
name (str, optional) – Name for the returned Group. Defaults to the file name.
- Returns:
The imported solids, each an
ImportedSolidcarrying its name, colour and assigned material. Add it to a model like any other shape — a Group is flattened into its members on insertion.- Return type:
- Raises:
FileNotFoundError – If path does not exist.
ValueError – If the file cannot be read, contains no solid, or a materials key matches no solid.
Examples
Assign materials by the names the parts carry in the CAD model:
from magnelio import GeometryModel, Material from magnelio.io import import_step parts = import_step( "connector.step", {"pin": Material.pec(), "shell": Material.pec(), "insulator": Material(name="PTFE", epsilon=(2.1,) * 3)}, ) model = GeometryModel().add(parts)
Import first, inspect the names, assign afterwards:
parts = import_step("connector.step") print([s.name for s in parts.members()])
- magnelio.io.read_brep(path)#
Read a BREP compound file back into an ordered list of shapes.
- Parameters:
path (str or Path) – A
.brepfile written bywrite_brep().- Returns:
The compound’s direct children, in write order.
- Return type:
list of TopoDS_Shape
- magnelio.io.write_brep(shapes, path)#
Write an ordered list of geometry shapes to a BREP compound file.
- Parameters:
shapes (list) – Geometry shape objects (each exposing
_occ_shape()) or rawTopoDS_Shapeinstances. The write order is preserved and recovered verbatim byread_brep().path (str or Path) – Output
.brepfile.
- Return type:
None