magnelio.sources#

Sources — model objects that inject fields, declared before meshing.

A source is declared on the GeometryModel with add_source and driven at run time by an Excitation that names it (the waveform and amplitude live there). Ports are sources and loads and stay in magnelio.ports.

class magnelio.sources.SourceCurrentPath(name, path=None, samples_per_cell=4)#

An impressed current along a path through the model.

Declared on the model with add_source() and driven by an Excitation naming it: the excitation’s amplitude is the peak current in amperes (amplitude_unit) and its waveform the time function. The current is prescribed, not solved for — the path is a source, not a wire. A closed path is a current loop (a magnetic dipole); an open one accumulates the charge its ends demand and radiates as an electric dipole.

Parameters:
  • name (str) – Source name — the handle an Excitation uses.

  • path (Curve or sequence of points) – The filament. Either a Curve (a polyline, arc, spline, helix, or any chain of them) or a sequence of at least two (x, y, z) points [m], which is taken as the vertices of a polyline. The current flows from the first point towards the last. The path must lie inside the meshed domain; under a symmetry declaration that is the meshed half, and the images on the other side are supplied by the symmetry wall.

  • samples_per_cell (int, default 4) – Curve samples per smallest cell while rasterising. Higher values only refine which edges a strongly curved path picks up; the dipole moment does not depend on it.

Notes

A path may cross itself or double back; every edge is impressed once with the net signed current it carries, so a segment traversed in both directions cancels, as it physically does. Edges the solver holds at zero — inside a perfect conductor, or tangential to a PEC wall — cannot take a current, and the source reports how many of its edges were swallowed that way rather than silently radiating less than asked.

Examples

A short vertical filament at the origin — a Hertzian dipole of 1 mA peak:

>>> src = SourceCurrentPath(name="dip", path=[(0, 0, -1e-3), (0, 0, 1e-3)])
>>> model.add_source(src)
>>> exc = magnelio.Excitation("dip", waveform=wf, amplitude=1e-3)

A one-turn loop of radius 5 mm in the xy-plane — a magnetic dipole:

>>> from magnelio.geo import Curve
>>> loop = Curve.arc((5e-3, 0, 0), (-5e-3, 0, 0), (0, -5e-3, 0)).joined(
...     Curve.arc((0, -5e-3, 0), (5e-3, 0, 0), (0, 5e-3, 0))
... )
>>> src = SourceCurrentPath(name="loop", path=loop)
attach(solver)#

Rasterise the path and fold −sign·β into one coefficient per edge.

Return type:

None

inject_E(fields, t_E)#

Add −sign·β·I on every path edge after the E update.

The current is taken at t_E − dt/2, the level of the C̃ᵀ ĥ term it stands beside.

Parameters:

t_E (float)

Return type:

None

inject_H(fields, t_H)#

Nothing — an electric current enters the E update only.

Parameters:

t_H (float)

Return type:

None

property curve#

The path as a Curve.

class magnelio.sources.SourceFieldIncident(name, corners=None, field=None)#

An incident field on a total-field/scattered-field box.

Declared on the model with add_source() and driven by an Excitation naming it. The incident field is any function of position and time — a Gaussian beam, a focused wave, a tabulated field — evaluated on the six box faces every step; the plane wave has its own fast path in SourcePlaneWave.

Parameters:
  • name (str) – Source name — the handle an Excitation uses.

  • field (callable) – field(x, y, z, t, drive) -> ((Ex, Ey, Ez), (Hx, Hy, Hz)), the incident field at the positions x, y, z (arrays of one shape, the samples of one box face) and time t [s]: E in V/m, H in A/m, each component broadcastable to the position shape. drive(τ) is the excitation’s time function amplitude · waveform(τ − delay), vectorised over τ, so a retarded field is drive(t − k̂·r / c₀). The field must itself solve the free-space Maxwell equations (a superposition of plane waves); an inconsistent E/H pair leaks into the scattered-field region.

  • corners (tuple of tuple, optional) – Two opposite corners ((x0, y0, z0), (x1, y1, z1)) of the total-field region [m] — the same form as from_corners(). Corner order does not matter, and a component may be None (or ±math.inf) to fall back to the default extent on that side (two bulk cells inside the domain boundary — the scattered-field shell the TF/SF split needs). Snapped to the nearest grid nodes. None (default) uses the default extent on all six sides.

Notes

The amplitude of the incident field is the excitation’s amplitude in V/m (amplitude_unit); the waveform is the excitation’s waveform. Neither is part of the source.

Examples

A plane wave along +z spelled out as a general field (the dedicated class is the faster way to say this):

>>> ETA0 = magnelio.constants.ETA0
>>> def pw(x, y, z, t, drive):
...     f = drive(t - z / C0)
...     return (f, 0.0, 0.0), (0.0, f / ETA0, 0.0)
>>> src = SourceFieldIncident(name="inc", field=pw)
classmethod from_ranges(*, x1=None, x2=None, dx=None, y1=None, y2=None, dy=None, z1=None, z2=None, dz=None, **kwargs)#

Build the same source from one coordinate range per axis.

The range spelling of corners=, as in from_ranges(): each axis takes up to two of its three keywords — the two bounds (x1, x2) or a bound and an extent (x1, dx / x2, dx). Here an axis may also be open: give nothing for the whole domain extent, or a single bound to reach the domain boundary on the other side. All remaining keyword arguments are forwarded to the constructor.

Examples

>>> src = SourcePlaneWave.from_ranges(name="pw", x1=1e-3, x2=9e-3, z1=1e-3, z2=19e-3)
attach(solver)#

Cache solver coefficients, snap the box and fold the face table.

Called once from FITTimeDomainSolver.setup(). Requires a bound waveform (set_excitation()).

Return type:

None

inject_E(fields, t_E)#

Apply TF/SF E-field corrections after the E update.

Uses H_inc at t = t_E - dt/2 (half a step behind E).

Parameters:

t_E (float)

Return type:

None

inject_H(fields, t_H)#

Apply TF/SF H-field corrections after the H update.

Uses E_inc at t = t_H - dt/2 (the E time level just computed).

Parameters:

t_H (float)

Return type:

None

class magnelio.sources.SourceFieldInitial(name, field, h_lead=0.0)#

The field at t = 0 of a time-domain run.

Declared on the model with add_source() (or passed as sources= to the analysis) and named by an Excitation whose amplitude scales the field (unit "1"); it has no waveform, delay or phase. Build one with from_project(), from_recording(), from_function() or from_arrays().

Parameters:
  • name (str) – Source name — the handle an Excitation uses.

  • field (FieldState) – E [V/m] and H [A/m] at t = 0 on the Yee positions. A field on a grid other than the run’s is resampled onto the run’s grid by trilinear interpolation per component (which does not preserve the discrete divergence exactly — prefer the run’s own grid).

  • h_lead (float, default 0.0) – Lead [s] of the magnetic samples over the electric ones. A field given at one instant — an eigenmode, a formula — has none; the state of a leapfrog march holds H half a time step after E, which from_recording() states from the recording’s dt. The run starts from H half its step ahead, and the field is moved there by a Faraday step of the difference — none at all when the lead is the run’s own half step, so a recorded march state is taken as it is.

Examples

>>> ring = sources.SourceFieldInitial.from_project("cavity_modes", name="mode0", mode=0)
>>> mesh = mio.Mesh.from_geometry(model, f_max=f_max).with_sources([ring])
>>> result = mio.AnalysisTD(mesh=mesh).run(excitations=["mode0"], t_end=50e-9)
>>> again = sources.SourceFieldInitial.from_recording(
...     result.monitors["volume"].recording, name="resume"
... )
classmethod from_arrays(grid, *, name, Ex=None, Ey=None, Ez=None, Hx=None, Hy=None, Hz=None)#

E and H from component arrays on the Yee positions of grid.

A component left None is zero. Shapes follow the table in magnelio.fields.

Parameters:
Return type:

SourceFieldInitial

classmethod from_function(grid, *, name, E=None, H=None)#

E and H as vector functions of position, sampled on grid.

See magnelio.fields.FieldState.from_function().

Parameters:
Return type:

SourceFieldInitial

classmethod from_project(project, *, name, mode=0, phase_deg=0.0)#

The eigenmode mode of a project’s stored eigenmode result.

The mode oscillates as E(t) = E_m cos(ωt), H(t) = −H_m sin(ωt) with the stored patterns E_m, H_m; the start is taken at the phase phase_deg of that cycle, so the default 0 starts at the instant of maximum electric field with H = 0.

Parameters:
  • project (str, Path or Project) – A project directory written by AnalysisEigenmode(project=…), or the opened Project.

  • name (str) – Source name.

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

  • phase_deg (float) – Phase ωt [degrees] of the cycle at which the run starts.

Return type:

SourceFieldInitial

classmethod from_recording(recording, *, name, t=None, frame=None)#

One frame of a FieldRecording as the start of a run.

A time monitor’s recording holds the march’s own state: E at the frame’s instant and H half a time step later, the leapfrog pair the solver marched with. The source takes both as they are (h_lead = dt/2), so a run on the same grid with the same time step continues the recorded one from that frame — a ring-down cut short resumes from its last frame, a state reached under one excitation is handed to another model. The recording of a monitor covering the whole domain is on the run’s grid; any other frame is resampled onto it, as any initial field is. A recording assembled without dt is taken as a field at one instant.

Parameters:
  • recording (FieldRecording) – The frames — a monitor’s recording, live or read back from a project.

  • name (str) – Source name.

  • t (float, optional) – Instant [s]; the nearest frame is taken.

  • frame (int, optional) – Frame index (exclusive with t). Default: the last frame.

Return type:

SourceFieldInitial

attach(solver)#

Write the scaled field into the solver’s state.

e(0) is the field on the primal edges (PEC edges zeroed). The march holds H half a step ahead of E when it enters its first E update, so the start is h(+dt/2). The field’s own H samples lead its E by h_lead, and the difference is a discrete Faraday step of that length: h(dt/2) = h(h_lead) − (½ − h_lead/dt)·β_H·(C e(0)). For an eigenmode of the discrete operator started at its E maximum (h_lead = 0, h(0) = 0) this is the exact leapfrog state of that mode, so the run is a pure oscillation of it; for a frame recorded by a march with this time step (h_lead = dt/2) it is the recorded state itself, so the run continues the recorded one.

The contribution is added to the solver’s state, which the solver allocated as zeros: two initial fields excited in the same run superpose, as the linear start they are.

Return type:

None

clear_excitation()#

Drop the bound waveform — the source injects nothing.

Return type:

None

inject_E(fields, t_E)#

Nothing to inject — the field was written at t = 0.

Parameters:

t_E (float)

Return type:

None

inject_H(fields, t_H)#

Nothing to inject — the field was written at t = 0.

Parameters:

t_H (float)

Return type:

None

set_excitation(waveform, *, amplitude=1.0, delay=0.0)#

Bind the amplitude the field is scaled by; there is no waveform.

Parameters:
  • waveform (Waveform | None)

  • amplitude (float)

  • delay (float)

Return type:

None

property amplitude: float#

The bound scale factor (1 before set_excitation()).

class magnelio.sources.SourceFieldSurface(name, corners=None, field=None, recording=None, position=None, rotation=None)#

Replay a recorded Huygens surface as an equivalent source.

Parameters:
  • recording (SurfaceRecording) – The surface a MonitorFieldSurface recorded, from MonitorFieldSurface.recording() or from_file().

  • name (str) – Source name; an Excitation names it to set the scale factor and the delay.

  • position (tuple of float, optional) – Where the centre of the recorded box goes in this model [m]. Defaults to the position it had in the recording.

  • rotation (tuple, optional) – (axis, degrees) turn applied to the recording, e.g. ("z", 90). Multiples of 90° only.

  • corners (tuple[tuple, tuple] | None)

  • field (Callable | None)

Notes

The excitation that drives this source carries no waveform — the time function is the recording. Its amplitude scales the replayed field (dimensionless, 1 = as recorded) and its delay shifts it in time.

The replayed field is only as complete as the recording: a recording whose box was left open at a PEC/PMC wall is valid only in a model that continues that wall, and one that was stopped before the fields had decayed ends where its samples end (the replay holds the last sample, it does not extrapolate).

Memory: the recording is resampled onto this model’s patches once, at attach time — about as much again as the recording itself.

Examples

>>> src = sources.SourceFieldSurface(
...     recording=rec, name="antenna", position=(0.0, 0.0, 0.2)
... )
classmethod from_file(path, **kwargs)#

Build the source from a recording file.

path is what SurfaceRecording.save() wrote in the recording run; the two models share nothing else.

Examples

>>> src = sources.SourceFieldSurface.from_file(
...     "antenna.h5", name="antenna", position=(0, 0, 0.2)
... )
attach(solver)#

Snap the box, build the face corrections and resample the recording.

Return type:

None

inject_E(fields, t_E)#

Apply the E-side corrections after the E update.

Parameters:

t_E (float)

Return type:

None

inject_H(fields, t_H)#

Apply the H-side corrections after the H update.

Parameters:

t_H (float)

Return type:

None

set_excitation(waveform=None, *, amplitude=1.0, delay=0.0)#

Bind the scale factor and delay this source replays with.

Takes no waveform: the time function is the recording itself.

Parameters:
  • amplitude (float)

  • delay (float)

Return type:

None

class magnelio.sources.SourcePlaneWave(name, corners=None, field=None, direction=(0.0, 0.0, 1.0), polarization=(1.0, 0.0, 0.0))#

Plane-wave illumination on a total-field/scattered-field box.

Declared on the model with add_source() and driven by an Excitation naming it; the excitation’s amplitude is the peak incident field in V/m and its waveform the time function. Propagation must be along a grid axis.

Parameters:
  • name (str) – Source name — the handle an Excitation uses.

  • direction (tuple of float) – Propagation direction (kx, ky, kz); normalised, must be axis-aligned.

  • polarization (tuple of float) – E-field polarization vector; its component along direction is projected out and the rest normalised.

  • corners (tuple of tuple, optional) – Two opposite corners of the total-field region [m]; see SourceFieldIncident. None (default) leaves a two-cell scattered-field shell inside every domain face.

  • field (Callable | None)

Examples

>>> from magnelio import sources
>>> pw = sources.SourcePlaneWave(name="pw", direction=(0, 0, 1), polarization=(1, 0, 0))
attach(solver)#

Cache solver coefficients and snap TF/SF box to grid nodes.

Called once from FITTimeDomainSolver.setup(). Requires a bound waveform (set_excitation()).

Return type:

None

incident_E(r, t)#

Incident E-field vector [V/m] at position r and time t.

E_inc(r, t) = E0 · ê · f(t − k̂·r / c₀)

Parameters:
Return type:

ndarray

incident_H(r, t)#

Incident H-field vector [A/m] at position r and time t.

H_inc = (1/η₀) · (k̂ × ê) · f(t − k̂·r / c₀) = (E0/η₀) · ĥ · f(…)

Parameters:
Return type:

ndarray