Far field and diffraction¶
Near-to-far-field projection from a closed or open monitor box, the fields of a recorded plane colocated on its grid nodes, and grating diffraction-order analysis. The far-field guide walks through a projection and states its measured accuracy.
Projection¶
far_field
¶
far_field(
data,
monitor_name: str,
*,
theta_rad: Union[
None, float, Sequence[float], ndarray
] = None,
phi_rad: Union[float, Sequence[float], ndarray] = 0.0,
axis: Optional[Axis] = None,
sign: float = 1.0,
freqs_hz: Optional[Sequence[float]] = None,
mesh: bool = True,
faces: Optional[
Sequence[Tuple[str, str, float]]
] = None,
colocate: bool = True,
simulation=None,
n_medium: Union[
None, str, float, Sequence[float], ndarray
] = None,
yee: str = "exact",
dl_um: Optional[float] = None,
ux: Union[None, float, Sequence[float], ndarray] = None,
uy: Union[None, float, Sequence[float], ndarray] = None,
plane_axis: Optional[Axis] = None,
direction: Optional[str] = None,
plane_um: Optional[float] = None,
x_um: Union[
None, float, Sequence[float], ndarray
] = None,
y_um: Union[
None, float, Sequence[float], ndarray
] = None,
exact: bool = False,
r_um: Optional[float] = None,
origin_um: Optional[Sequence[float]] = None,
) -> FarField
Project a recorded near-field surface onto the far field.
Single-plane (default) or box projection of the tangential E/H
recorded by a ProfileMonitor (data[monitor_name]), via the
surface-source currents J = n_hat x H, M = -n_hat x E and the
radiation integral (see the module docstring). Returns a FarField
whose dataset holds the far-field amplitudes
E_theta/E_phi and the intensity, radiated power and directivity
that follow, labelled by frequency and the grid's axes. The surface
radiates into a homogeneous, lossless medium of index n_medium
(vacuum by default).
Each recorded component is read at its own Yee point (yee="exact",
the default), on a uniform or a graded mesh, and a symmetry-reduced
run is mirrored into the full surface first. A single plane needs one
sample along its normal and may keep an interval_space stride. The
faces of a box must be recorded one cell thick and at every cell: a
face of size dl along its normal, centred at (k + 3/4) dl (on a
graded mesh, from a quarter of cell k to a quarter of cell k +
1), records the two normal samples the readout brings E and H
together from.
Parameters¶
data:
A RunResult (or any mapping) such that
data[monitor_name] is the recorded DFT field DataArray, dims
('f','component','z','y','x') (a single-plane slice has a singleton
normal axis). For a box pass per-face monitors via faces.
A plain mapping of arrays is read in the wire frame (the solver's
corner-anchored coordinates); a RunResult hands its planes over
through frame.wire_array, so a fitted run's user-frame
coordinates are handled, and its manifest gives the cell size (the
grid lines of a graded mesh) and the symmetry planes. The
medium sampling for simulation= reads the planes in that frame.
monitor_name:
Key of the near-field monitor in data (single-plane mode), or
the name of a FarFieldMonitor: its faces are
projected as recorded (faces is then not needed), into the
medium the simulation sampled at them unless n_medium is given,
mirrored through the symmetry planes it was compiled with, cut where
an open box leaves faces out, and with a plane's edge window. The
run keeps that recipe (a reloaded result reads it back from the
client.json beside the run); without it the faces are found by
their names, <name>.x_minus ... <name>.z_plus, with a
warning, the projection is into vacuum unless n_medium says
otherwise, and the faces are read as recorded (no open edge, no
window).
theta_rad, phi_rad:
Far-field directions (radians). With mesh=True (default) these are
1-D axes meshed into a (n_theta, n_phi) grid (theta_rad
slowest); with mesh=False they are paired elementwise.
theta_rad is the polar angle from +z, phi_rad the azimuth
from +x in the x-y plane. theta and phi are accepted as
the earlier spellings. One target grid is given: theta_rad
(directions), ux and uy (direction cosines), or
plane_um, x_um and y_um (points on a plane).
axis:
Plane normal "x"/"y"/"z" (single-plane mode). If None,
inferred from the monitor's singleton spatial dimension (pass it for
a plane recorded one cell thick).
sign:
Orientation of the outward normal: +1 along +axis (default,
i.e. radiating toward larger axis), -1 along -axis.
freqs_hz:
Frequencies to project (Hz). Defaults to every frequency on the monitor.
mesh:
Whether theta_rad/phi_rad are meshed (default) or paired.
faces:
Optional multi-face projection: a sequence of
(monitor_name, axis, sign) triples, one per face, summed with
each face's own outward normal. When given, monitor_name/axis/
sign are ignored. Each named monitor must be in data. Faces
whose in-plane extent is bounded by other faces (a closed box, or a
box with a face left out) are read as a box; parallel planes alone
are read as open planes. A list of a box with a face left out is read
without its open edge: along an axis that carries one face the faces
kept are not cut at all, so they run past that face and past the open
side alike. For a dipole at the centre of such a box 0.62 um in half
width, in index 1.45, that reads the pattern 9.4 and 6.2 % of the
peak (within 30 degrees, on exact dipole fields at 20 and 30 cells
per wavelength) away
from the same box declared with FarFieldMonitor.box(...,
exclude=...), which ends them at the declared planes. Declare an
open box that way.
colocate:
True (default): the planes are the engine's Yee-staggered
output, as a ProfileMonitor records them. False: synthetic
fields whose components already share their sample points; each
component is then read at the coordinate given, on one surface, and
no cell size is needed. With yee="legacy" it keeps its earlier
meaning (average each component to the in-plane node).
simulation:
The Simulation the data came from. Its
symmetry planes say how a symmetry-reduced run is mirrored into
the full surface (see yee); a RunResult records them itself,
and when both say, they must agree (ValueError: pass the run's
own simulation). A plain mapping of arrays from a symmetry-reduced
run needs it. Required by
n_medium="auto". When given, the medium at each face is sampled
half a cell beyond the recorded plane on the side it radiates into,
over the face's recorded extent (the sampling
diffraction_orders uses). A plane that
is not in one homogeneous medium there, sits in a lossy medium, or
sits in a medium of another index than the one projected into,
gives a UserWarning. The faces of a closed box that do not all
sit in one homogeneous medium raise ValueError when n_medium
is given or "auto"; with the default they give a
UserWarning and the projection stays the vacuum one.
n_medium:
Index of the homogeneous, lossless, nonmagnetic half-space the
surface radiates into (for a closed box, the medium around it). The
wavenumber becomes k = n_medium * 2 pi f / c0 and the wave
impedance eta = eta0 / n_medium, in the radiation integral, the
far-zone fields, the intensity (FarField.dataset["intensity"]), the
radiated power and the directivity. One of:
* ``None`` (default): index 1, vacuum, bit for bit the projection
``n_medium=1.0`` gives. With ``simulation=`` a face sitting in a
medium of another index (a plane inside a substrate) gives a
``UserWarning`` that names the index; the numbers do not change.
* a real number ``>= 1``, or one per projected frequency (the
``freqs_hz`` used): an index, not a permittivity (a silica
substrate of permittivity 2.1025 is ``n_medium=1.45``). Equal
values are reported as one float.
* ``"auto"``: the index sampled from ``simulation`` (required). In
a dispersive (pole) medium that is ``sqrt(Re eps(f))`` at each
projected frequency, and `FarField.n_medium` is then a
per-frequency array. A face that crosses structures, a medium
that is anisotropic, custom or PEC, or a lossy one (a loss tangent
``Im eps / Re eps`` above 1e-3 at a projected frequency, from
damped poles or ``conductivity_s_per_m``) raises ``ValueError``.
A half-space that is not homogeneous (a plane in the cladding over
a substrate, a box that straddles an interface) is outside the
model: its far field needs the layered Green's function. The
readout's errors scale with the cell in medium wavelengths,
``n_medium * k0 * dl``.
yee: How the recorded components are read.
* ``"exact"`` (default): each component at its own Yee point
(``E_c`` half a cell along ``c``, ``H_c`` half a cell along the
two other axes), with no averaging. A single plane (or parallel
planes alone) is read from its first normal sample, J on the H
plane half a cell above E. The faces of a box are read from their
two normal samples: the far fields of E brought onto the H plane
and of H brought onto the E plane are averaged, which cancels the
imbalance each leaves between the two currents and halves the part
of the error that depends on where the source sits. A
second-order error of the interpolation along the normal remains:
about ``(k dl)^2 / 8`` of the peak in front/back asymmetry; at 25
cells per wavelength in vacuum on a uniform mesh up to about
0.80 % with
the source near a box corner and up to about 1.06 % near a box
edge, within 2 % wherever the build does not warn. Box faces take
fourth-order weights at the
box edges. Refused, naming the face: a box face recorded with
``interval_space`` strides (when the cell size is known), a face
with more than two samples along its normal, and a box whose
faces do not reach one cell past it in their plane.
On a graded mesh each sample is placed from the run's grid lines
(a ``RunResult``'s manifest lists them), a half-offset component
at the midpoint of its cell; H is brought onto the E plane with
the two cells' widths as weights, and the box-edge rule runs on
the samples' true positions (see the module docstring). Every
number on a uniform mesh is unchanged. The asymmetry can be
larger than on a uniform mesh: with a region meshed twice as fine
around a dipole, ending just outside a box face, it reached 1.79 %
of the peak at 25 cells per wavelength in the background (0.92 %
at 35). The solver's time step follows the finest cell, so the
background runs at a lower Courant number and its dispersion
error grows, as in a dielectric; and the change of cell size just
outside the face is an inhomogeneity the projection, which
assumes one homogeneous medium outside the box, does not model.
Count the cells per wavelength in the background and use at
least 30, keep every box face at least 0.35 wavelengths from the
nearest grid line where the cell size changes, along its normal,
on either side, and keep every source at least 0.1 wavelengths
from one (a point source along each axis, a launch along its
normal): end a refinement inside the box with
that room, or carry it that far past every face, and keep
sources that far from where it ends. In that range the
front/back asymmetry of a vacuum dipole box stays within 2 % of
the peak at 30 background cells per wavelength for refinements 2
and 3 times as fine as the background (measured up to 0.90 % and
0.93 %, a dipole near a box edge; higher ratios are not covered),
falling at about second order for a source fixed in space.
Outside it no bound is given: a source on or just past the edge
of a refined region radiates an asymmetric field by itself, up
to 1.085 % (three times as fine) and 0.92 % (twice) at 30
background cells. A `FarFieldMonitor` warns
about both; see the known limitations.
On a symmetry-reduced run (its manifest, the simulation a
``RunResult`` carries, or ``simulation=`` records symmetry planes)
each face is mirrored at its own Yee points with the sign each
plane gives the component: a face across a plane must reach it,
and a box face normal to a plane gains its mirror image, the face
on the far side, read as the exact mirror of the recorded one.
Against the full run's box (its far-side faces the mirror of the
near ones), the pass that brings E onto the H plane agrees to the
solver's float32 precision. The pass that brings H onto the E
plane reads the image face at its outer sample, where the full
run reads its far-side face at the inner one, so it differs by
the readout's own second-order error, and so does their mean. On
FDTD dipole boxes at 16 cells per wavelength (three cases, two
symmetries, uniform and graded meshes) the mean differs by about
0.1 to 0.4 % of the peak intensity and falls at about second
order (0.04 to 0.06 % at 32); the H-onto-E pass by 0.26 to
0.77 %. On
exact dipole fields the two differ by up to 8.3e-3 and 4.1e-3 at
20 cells, 9.1e-4 and 4.5e-4 at 40. Neither the reduced nor the
full run is systematically closer to the exact pattern. A
mirror-symmetric field gives a pattern mirror-symmetric to
round-off.
Where this readout cannot place the samples it falls back to
``"legacy"`` with a warning: planes whose cell size cannot be
told (pass ``dl_um=``), planes of a graded mesh given as plain
arrays without the run's grid lines (pass the ``RunResult``), and
box faces recorded with one sample along their normal (a
``FutureWarning``: record them one cell thick). Such a fallback
mirrors a face across a symmetry plane the earlier way (each
component averaged onto the node, `unfold_symmetry`) but
cannot add the mirror image of a box face normal to a plane, so a
symmetry-reduced box that would fall back is refused, naming the
face and saying what to record. A plain array is
from a graded mesh when its samples are not evenly spaced, or are
spaced by other than a whole number of cells; one cut from an
evenly graded patch cannot be told from a uniform one.
* ``"legacy"``: the earlier readout, which averages each component
to the in-plane node (``colocate``) and leaves E and H half a cell
apart along the normal; it reads the first sample of a face
recorded one cell thick. On a closed box its front/back asymmetry
is first order in the cell size (0.28 of the peak at 25 cells per
wavelength for a vacuum dipole). It mirrors a symmetry-reduced
run only when ``simulation=`` declares the planes, as before, and
never adds the mirror image of a box face normal to a plane (such
a box stays open on the far side). Kept for one release; it gives
a ``FutureWarning``.
dl_um:
The uniform cell size the Yee offsets are measured in. By default it
is read from a RunResult's manifest, or from a face's two normal
samples. A value that disagrees with the manifest, with a face's
two normal samples, or with a face's in-plane sample spacing (which
must be a whole number of cells) raises ValueError, and so does
any value with a graded run (its manifest's grid lines place the
samples) or with plain arrays sampled unevenly. On a plain single plane with no run
attached nothing tells the cell size, so a dl_um that divides
the in-plane spacing is read as a stride: dl_um=dl / 2 reads a
plane recorded every cell as one recorded every second cell of
half the size.
ux, uy:
A k-space grid: direction cosines along the two in-plane axes of
plane_axis in xyz order (x and y for "z", x and z for
"y", y and z for "x"), meshed like theta_rad and
phi_rad (mesh), in the half space direction: the
direction with those cosines and +-sqrt(1 - ux^2 - uy^2) along
plane_axis. They are fractions of the wavenumber in the medium
projected into, so ux = m lambda / (n L) is the m-th grating
order of a period L. A point with ux^2 + uy^2 > 1 has no
direction (only an evanescent wave has that transverse wavevector,
and none reaches the far zone): its fields are nan and its
intensity 0, as diffraction_orders
reports evanescent orders. On a plane recorded over one period of a
periodic structure the power of order m is U (lambda / n)^2 /
(A cos theta) (W), with U the intensity at its point, A the
period's area (m^2) and theta the angle from the plane's normal.
To compare with diffraction_orders: its
(k1, k2) run along its in_plane_axes, which follow the
cyclic order (y and z for a plane normal to x, z and x for y, x and y
for z), so on a plane normal to y its k1 is uy and its
k2 is ux (times k); and its power carries the period's
area in um^2, 1e12 times the W of the formula. So compared, on an
FDTD grating the two agree to about 1e-4 (see the docs).
plane_axis:
The axis normal to a k-space grid's (ux, uy) plane or to a
Cartesian grid's plane. A single plane's own normal by default; a
surface of several faces needs it.
direction:
"+" or "-", the half space of a k-space grid along
plane_axis: by default a single plane's outward side when
plane_axis is its normal, else "+".
plane_um, x_um, y_um:
A Cartesian grid: points on the plane at plane_um along
plane_axis, at x_um and y_um along its two in-plane axes
in xyz order (meshed like theta_rad), in the frame of
data[name] (a fitted run's user frame). In the far-field
approximation each point reads the far-field amplitude of its
direction from origin_um, with r_um its distance; points
nearer than 2 D^2 / lambda (D twice the surface's farthest
reach from origin_um, its diagonal from its centre; lambda
in the medium), where the approximation's phase error passes pi
/ 8, give a warning: pass exact=True there.
exact:
False (default): the far-field approximation. True: the
field the surface's equivalent currents radiate at a finite
distance, with the full Green's function of the homogeneous medium
(near, intermediate and far terms), at the points of a Cartesian
grid, or on the sphere of radius r_um about origin_um for an
angle or k-space grid. The FarField holds r e^{-ikr}
times each spherical component of E and H (e_r, e_theta,
e_phi, h_r, h_theta, h_phi), which tend to the
far-field amplitudes as r grows, the difference falling as
1/r; FarField.cartesian gives the field itself, and its
dataset["intensity"] is the power per unit solid angle crossing
a sphere about origin_um there. It is computed one
of two ways, picked automatically:
* the angular spectrum of the plane's tangential E, for a meshed
Cartesian grid on a plane parallel to one recorded plane sampled
evenly along both in-plane axes: zero-padded to at least twice
the span of the plane and the points, the padding doubled until
doubling it again moves no value by more than 1e-4 of the
largest, with at most 2^22 padded points per frequency (about
0.45 GB at that size), and given up early when the moves, shrinking
at their last rate, cannot reach that within the cap;
* otherwise (a box, an angle or k-space grid, paired points, an
unevenly sampled plane, or a plane whose spectrum does not settle
within that padding, as a field still strong at its edges or read
far away), a direct sum over the recorded samples with the
far-field projection's quadrature, in blocks of about 2^18
(point, sample) pairs. Its cost grows as the number of points
times the number of samples (about a second of CPU per ten
million pairs). Near the surface's currents it loses accuracy,
because its kernel grows as ``1/R^3`` between samples. On a
dipole's exact field in a closed box, one sample spacing out it
is off by 3 to 6 % of the peak at 20 cells per wavelength, and
over the box's edges the band grows in spacings on finer meshes
(one spacing out up to 11 % at 40 cells and 21 % at 80; the
readout's own error is reached from two spacings at 20 cells,
three at 40 and four at 80, from two over a face's interior). A
point within two sample spacings or a tenth of a wavelength of
the currents, whichever is farther, gives a warning.
On exact fields, clear of that band, both read the field to the
readout's own error: a dipole in a closed box (uniform mesh, 20
and 40 cells per wavelength) to 7.0e-3 and 1.6e-3 of the peak at
1.2 to 20 um from the box centre, the far-field projection's error
at every distance; a beam that has decayed at its plane's edges,
the two ways within 1e-5 of each other and of the beam. On a plane
that cuts the field the two ways are different equivalent sources
(the tangential E alone against J and M) and differ by about the
cut's own error (6.9e-5 of the peak with 1.4e-3 of it at the
plane's edge, where the cut alone is 9.3e-5). A target point
within a box's span, or a Cartesian point not beyond a plane on
the side it radiates into, is refused; on an angle or k-space grid
of a plane the directions that end at or behind it (its grazing
rim) read ``nan``, with intensity 0 and a warning, and a grid with
no direction in front of it is refused. Needs the default
Yee-lattice readout.
r_um:
The distance (um) of an exact projection on an angle or k-space
grid, from origin_um.
origin_um:
The point (um, the frame of data[name]) distances are measured
from: the centre of an exact projection's sphere and of a Cartesian
grid's far-field approximation, by default the centre of the
surface's recorded extent (on a mirrored axis the symmetry plane:
the full surface's centre). Runs that record the same surface on
different grids, or with and without symmetry planes, can default
to origins a fraction of a cell apart, which moves the phase
e^{-ikr} of the exact amplitudes: pass origin_um to compare
runs. Given with far-field amplitudes on an angle or k-space grid,
it is also their phase reference (F e^{i k r_hat . o}); without
it they keep the phase they always had, referred to the run's
corner frame.
Returns¶
FarField
Its dataset labels the projection by frequency and
the grid's own axes ((f, theta, phi), (f, ux, uy), the
plane's two axes, or (f, point)), with the n_medium
projected into; its methods give the gain, cross section,
polarization bases and the field at a distance. See
FarField.
Examples¶
A plane recorded inside a silica substrate (index 1.45) below the
device, projected into the substrate within 20 degrees of -z::
theta = np.radians(np.linspace(160.0, 180.0, 41))
phi = np.radians(np.arange(0.0, 360.0, 10.0))
ff = far_field(data, "substrate_plane", theta_rad=theta, phi_rad=phi,
axis="z", sign=-1.0, n_medium=1.45)
u_sub = ff.dataset["intensity"].isel(f=0) # (theta, phi), W/sr
The same plane on a k-space grid, and a FarFieldMonitor box's
exact field on a plane 20 um above its centre::
u = np.linspace(-1.0, 1.0, 201)
ks = far_field(data, "substrate_plane", ux=u, uy=u, axis="z", sign=-1.0,
n_medium=1.45) # ks.dataset["intensity"]
xs = np.linspace(-10.0, 10.0, 81)
spot = far_field(data, "ff", plane_axis="z", plane_um=20.0, x_um=xs,
y_um=xs, exact=True)
ex = spot.cartesian()["Ex"].isel(f=0) # (x, y), V/m
FarField
dataclass
¶
FarField(
theta: ndarray,
phi: ndarray,
freqs_hz: ndarray,
e_theta: ndarray,
e_phi: ndarray,
n_medium: Union[float, ndarray] = 1.0,
grid: str = "angle",
shape: Optional[Tuple[int, int]] = None,
exact: bool = False,
r_um: Optional[ndarray] = None,
origin_um: Optional[Tuple[float, float, float]] = None,
e_r: Optional[ndarray] = None,
h_r: Optional[ndarray] = None,
h_theta: Optional[ndarray] = None,
h_phi: Optional[ndarray] = None,
ux: Optional[ndarray] = None,
uy: Optional[ndarray] = None,
direction: Optional[str] = None,
x_um: Optional[ndarray] = None,
y_um: Optional[ndarray] = None,
plane_um: Optional[float] = None,
plane_axis: Optional[str] = None,
incident_e_v_per_m: Optional[float] = None,
phase_origin_um: Optional[
Tuple[float, float, float]
] = None,
)
The projected field on a target grid, per frequency.
Labelled results. dataset is the projection as an
xarray.Dataset, labelled by frequency and by the grid's own
axes:
- an angle grid (
theta_rad,phi_rad): dims("f", "theta", "phi"), the polar angle from+zand the azimuth from+xin radians; - a k-space grid (
ux,uy): dims("f", "ux", "uy"), the direction cosines along the two in-plane axes ofplane_axisin xyz order (each coordinate'sattrs["axis"]names its axis: x and z on a plane normal to y), withtheta,phiandpropagatingas coordinates over(ux, uy)(nanangles, andFalse, where a point has no direction; a direction an exact projection of a plane leaves without field staysTrue, withnanamplitudes and intensity 0, sodataset["e_theta"].notnull()marks the points that carry a field, on every grid).diffraction_ordersruns its(k1, k2)in its own cyclic order instead (z and x on a plane normal to y; the same on a plane normal to z), and its order powers carry the period's area in um^2, so they are 1e12 times the watts ofU (lambda / n)^2 / (A cos theta)from this intensity; - a Cartesian grid (
plane_um,x_um,y_um): dims("f", a, b)withaandbthe plane's two in-plane axes in xyz order (("f", "x", "y")on a plane normal to z,("f", "x", "z")on one normal to y), in um, the plane's position a scalar coordinate named by its normal axis, andtheta,phiandr_um(each point's direction and distance fromorigin_um) as coordinates over the plane; - points paired elementwise (
mesh=False): dims("f", "point"), with the grid's coordinates alongpoint.
Along f (Hz) every array carries the vacuum wavelength wlen_um
and the index n_medium projected into as coordinates. The data
variables are the far-field amplitudes e_theta and e_phi (V,
see below), for an exact projection also e_r (V) and h_r,
h_theta, h_phi (A), the radiation intensity intensity
(W/sr), and on a meshed angle grid radiated_power (W, over f)
and directivity. An exact projection, or a Cartesian grid, carries
r_um: one distance (a scalar coordinate) on a sphere, one per point
on a plane. The labelled values are the arrays of the flat layout below
reshaped onto the grid's axes, bit for bit, as read-only views.
The methods gain, cross_section, polarization,
power_density and cartesian return labelled results
too, renormalize returns a copy read at a distance, and
photonhub.viz.plot_far_field_cut and
photonhub.viz.plot_far_field_map plot them.
The flat layout (deprecated). The attributes theta, phi,
e_theta, e_phi, e_r, h_r, h_theta, h_phi,
r_um, ux, uy, x_um, y_um, the properties
intensity and propagating, and the methods
radiated_power, directivity, reshape and
cartesian_fields hold or read the projection as earlier releases
returned it: arrays indexed [i_freq, i_point] over a flat list of
points (the grid's two axes meshed, the first slowest). They are kept
for one release; reading one gives a FutureWarning naming its
labelled replacement. Their values are unchanged.
Attributes¶
theta, phi:
The polar and azimuthal angles (radians) of each point, a flat
(n_point,) list (deprecated: dataset["theta"],
dataset["phi"]). On a k-space grid they are the directions of
its points (nan where a point has none, see ux); on a
Cartesian grid, the direction of each point seen from origin_um.
freqs_hz:
1-D array of frequencies (Hz), the f coordinate of
dataset.
e_theta, e_phi:
Complex far-field AMPLITUDES F = r e^{-i k r} E (the spherical
spreading factor e^{i k r}/r removed), shape (n_freq,
n_point) (deprecated: dataset["e_theta"], dataset["e_phi"]).
For an exact projection (exact) and on a Cartesian grid they are
the field at each point, scaled the same way with r its distance
from origin_um (r_um): E_theta = e_theta e^{i k r} / r.
n_medium:
Index of the medium the field was projected into: a float (1.0, the
default, is vacuum), or a (n_freq,) array when it varies with
frequency. It sets the wave impedance eta = eta0 / n_medium that
the intensity and every quantity derived from it use.
grid:
"angle" (theta_rad, phi_rad), "kspace" (ux,
uy) or "cartesian" (points on a plane, x_um, y_um).
shape:
(n1, n2) of a meshed grid (mesh=True), else None: the
sizes of the grid's two dims in dataset.
exact:
Whether the field is the exact one at a finite distance (the full
Green's function, far_field(..., exact=True)) rather than the
far-field approximation.
r_um, origin_um:
The distance of each point from origin_um (um, a flat
(n_point,) array; deprecated: dataset["r_um"]) and that
origin (um, the frame of data[name]): set for an exact
projection, on a Cartesian grid and after renormalize, else
None.
e_r, h_r, h_theta, h_phi:
For an exact projection, the radial E and the three H components at
each point, scaled like e_theta; else None (in the far zone
E_r = 0 and H = r_hat x E / eta). Deprecated:
dataset["e_r"] and so on.
ux, uy, direction:
On a k-space grid, the direction cosines of each point along the
two in-plane axes of plane_axis in xyz order (x and y for
plane_axis="z", x and z for "y", y and z for "x"), and
the half space, "+" or "-" along plane_axis. A point with
ux^2 + uy^2 > 1 has no direction (only an evanescent wave has
that transverse wavevector, and it never reaches the far zone): its
fields are nan and its intensity 0, as diffraction_orders
reports evanescent orders. ux and uy are flat lists
(deprecated: dataset["ux"], dataset["uy"]).
x_um, y_um, plane_um:
On a Cartesian grid, each point's coordinates along the two in-plane
axes of plane_axis in xyz order (flat lists; deprecated: the
dataset coordinates named by those axes), and the plane's
coordinate along it (um, the frame of data[name]).
plane_axis:
The axis normal to the (ux, uy) or (x, y) plane.
incident_e_v_per_m:
The amplitude (V/m) of the plane wave a run driven by one
TFSFBox launches, recorded by far_field
from the run's simulation; None otherwise. cross_section
divides by its intensity.
phase_origin_um:
The point (um, the frame of data[name]) the amplitudes' phase
refers to, recorded by far_field: origin_um when it was
given or implied (an exact projection, a Cartesian grid), else the
corner of the run's frame (Simulation.origin_um; 0 for a plain
mapping of arrays). renormalize measures its distance from
it. None for a FarField built by hand.
origin_um
class-attribute
instance-attribute
¶
phase_origin_um
class-attribute
instance-attribute
¶
dataset
property
¶
The projection as an xarray.Dataset labelled by
frequency and the grid's axes (see the class description): the
amplitudes e_theta, e_phi (and an exact projection's e_r,
h_r, h_theta, h_phi), intensity (W/sr), and on a
meshed angle grid radiated_power (W, integrated over the grid
with a sin(theta) solid-angle weight) and directivity (4
pi U / P). Select with .sel(f=..., method="nearest") or
.sel(phi=0.0); its arrays are read-only views of the
projection's own. f keeps the order the frequencies were
projected in, which for a FarFieldMonitor is monotonic;
frequencies given unsorted or repeated (freqs_hz=) need
.sortby("f") or .isel(f=...) instead of method="nearest".
radiated_power is meaningful when the grid tiles a contiguous
angular region (a full sphere or a hemisphere),
and a grid that covers only part of the light makes it, and the
directivity, relative to that part. Paired directions
(mesh=False) and k-space and Cartesian grids have neither.
propagating
property
¶
Which points have a direction, shape (n_point,): every point of
an angle or Cartesian grid, the points of a k-space grid with
ux^2 + uy^2 <= 1. A point with a direction can still carry no
field: in an exact projection of a plane, a direction that ends at
or behind it reads nan with intensity 0 while it stays
True here. Which points carry a field is
~np.isnan(e_theta) (labelled: dataset["e_theta"].notnull()).
Deprecated (the flat layout): dataset["propagating"] on a
k-space grid.
intensity
property
¶
Radiation intensity U = (|F_theta|^2 + |F_phi|^2)/(2 eta)
(W/sr), with eta = eta0 / n_medium the medium's wave impedance,
shape (n_freq, n_point): the power per unit solid angle in the far
zone. For an exact projection it is r^2 times the radial
component of the time-averaged Poynting vector at each point,
Re(E_theta H_phi* - E_phi H_theta*) r^2 / 2, the power per unit
solid angle crossing a sphere about origin_um there, which tends
to the far-zone value as r grows. 0 at a k-space point with no
direction, and at a point of an exact projection with no field (a
direction that ends at or behind a single plane). Deprecated (the
flat layout): dataset["intensity"].
gain
¶
Gain G = 4 pi U / P_in (dimensionless), labelled like
dataset's intensity: the intensity against the power the
source delivers, so G = e D with e the radiation efficiency
(the share of that power that leaves the surface) and D the
directivity. Without losses (and a surface that sees all the light)
G = D.
input_power_w is the power the source delivers (W): one number,
one per projected frequency, or an xarray.DataArray over
f read at the projected frequencies, such as the summed outward
flux of PowerMonitor planes that close a small
box centred on the source (a RunResult returns
each plane's in watts). Off centre, such a box's total is off by an
amount first order in the cell size times the offset (0.9 % for a
dipole 0.13 um off the centre of a box of half width 0.3 um at 30
cells per wavelength): centre it, or check it against a larger box.
The projection cannot tell it, so it is required:
ValueError without it. A point dipole's Hertzian power eta k^2
|p|^2 / (12 pi) (p its current moment, amplitude times its
cell's volume on a uniform mesh) is the power it delivers only
alone in a homogeneous lossless medium: nearby structures change it
(the local density of states), so measure it there.
cross_section
¶
Differential scattering cross section dsigma/dOmega = U /
I_inc in um^2/sr, labelled like dataset's intensity: the
scattered intensity U (W/sr) against the intensity I_inc
(W/m^2) of the incident plane wave. Its integral over the sphere
(dataset["radiated_power"] / I_inc on a full angle grid) is the
scattering cross section.
incident_intensity_w_per_m2 is I_inc: one number, one per
projected frequency, or an xarray.DataArray over f read
at the projected frequencies (a PowerMonitor
window inside a TFSFBox, divided by its area,
for instance). By default it is the declared plane wave's,
|E0|^2 / (2 eta) with E0 the amplitude of the run's one
TFSFBox (incident_e_v_per_m) and eta the medium's
impedance, so on the far-field approximation dsigma/dOmega =
(|e_theta|^2 + |e_phi|^2) / |E0|^2. The plane wave the grid
carries has cos(k dl / 2) of that intensity in a
PowerMonitor (0.8 % less at 25 cells per wavelength), so a cross
section divided by an incident flux measured without the scatterer
reads that much higher. A projection of anything else
(several sources, another kind, a plain mapping of arrays, a result
whose simulation is unknown) needs it given: ValueError. The
surface must hold scattered field only, as a TFSFBox's outside
does.
polarization
¶
polarization(
basis: str = "ludwig3",
*,
reference: Optional[str] = None,
boresight: Optional[str] = None,
) -> Dataset
The transverse field in a polarization basis, labelled like
dataset and scaled like e_theta (V; the intensity in
each is |amplitude|^2 / (2 eta)).
-
"ludwig3":coandcross, Ludwig's third definition (the polarization an antenna range measures). About the boresightbwith the co-polar referencex'there (y' = b x x')::i_co = x' - (r . x') (r + b) / (1 + r . b) i_cross = y' - (r . y') (r + b) / (1 + r . b)
For b = +z and x' = x this is co = cos(phi) E_theta -
sin(phi) E_phi and cross = sin(phi) E_theta + cos(phi)
E_phi. A Huygens source polarized along x' has no
cross-polar field anywhere, and a dipole along x' none in the
two principal planes. boresight is "+x" ... "-z": by
default "+z" on an angle grid, the grid's own axis and half
space on a k-space grid, and the side of the plane seen from
origin_um on a Cartesian one. reference is an axis normal
to it, by default the first of its in-plane axes in xyz order (x
for z, x for y, y for x). The definition has no value at r =
-b. For numerical stability, co and cross are nan
throughout the antipodal neighborhood 1 + r . b <= 1e-12.
A UserWarning identifies the boresight when requested
directions enter this neighborhood. Choose a boresight suited
to the hemisphere, or partition a full-sphere calculation.
* "circular": right and left, (E_theta - i E_phi) /
sqrt(2) and (E_theta + i E_phi) / sqrt(2). With the
e^{-i omega t} convention and the field leaving along r,
right is the field that turns clockwise seen from behind (the
IEEE right hand: thumb along r; optics texts such as Born and
Wolf call this left-handed), left the other way. A
dipole x + i y radiates right-hand circular light toward
+z and left-hand toward -z: an Ex dipole with an Ey
dipole whose GaussianPulse has phase=-pi/2 (its current
sin(omega t) against the Ex dipole's cos(omega t));
phase=+pi/2 turns both hands over.
On an exact projection the radial component e_r is left out (the
bases are transverse to r).
power_density
¶
The radial power density S = U / r^2 (W/m^2) at each point's
distance, labelled like dataset's intensity: the
time-averaged Poynting vector along r there, the intensity of
the light a detector at that point receives. It needs distances: an
exact projection (the exact radial flux at r), a Cartesian grid,
or far-field amplitudes given one with renormalize.
cartesian
¶
The field at each point in Cartesian components, Ex, Ey,
Ez (V/m) and Hx, Hy, Hz (A/m), labelled like
dataset, in the units of the recorded fields: the amplitudes
times e^{i k r} / r with k the medium's wavenumber and r
the point's distance. An exact projection gives all six components
as computed; in the far-field approximation E is transverse and H
= r_hat x E / eta. Needs distances, as power_density.
renormalize
¶
The same far-field amplitudes read at distance_um (um): a
copy whose points lie that far from the amplitudes' phase reference
(phase_origin_um: origin_um when far_field was
given one, else the corner of the run's frame; the magnitudes do not
depend on it), which the copy records as its origin_um, so that
power_density and cartesian give the field there.
The amplitudes themselves do not change: the far field falls as
1/r. At a finite distance R the copy's phase is off by about
k d^2 / (2 R), with d the distance from the phase reference
to the farthest part of the recorded surface (0.025 rad at 1 mm for
a surface 2.8 um from the run's corner at lambda = 1 um): give
far_field an origin_um at the emitter, so d is the
surface's own size, and keep R above 8 d^2 / lambda, where
that error is below pi / 8, or project with exact=True.
Far-field amplitudes on an angle or k-space grid only: an exact
projection does not fall as 1/r (project again with
r_um=), and each point of a Cartesian grid has its own
distance.
radiated_power
¶
Total radiated power P_rad = integral U dOmega per frequency
(shape (n_freq,)), integrated over the sampled directions with a
sin(theta) solid-angle weight via _solid_angle_weights.
Deprecated: dataset["radiated_power"] (a meshed angle grid).
This is only meaningful when the directions tile a contiguous angular
region (e.g. a full sphere or a hemisphere mesh); for a sparse cut it is
a crude quadrature. Used as the directivity normalizer. An angle grid
only (ValueError on a k-space or Cartesian grid); for an exact
projection it is the power crossing the sphere of radius r_um.
directivity
¶
Directivity D = 4 pi U / P_rad (dimensionless), shape
(n_freq, n_point), the radiation pattern normalized so its
solid-angle average is 1 (for an isotropic radiator D==1 everywhere).
An angle grid only, as radiated_power. Deprecated:
dataset["directivity"] (a meshed angle grid).
reshape
¶
Reshape the flat per-direction arrays back to a meshed
(n_freq, n_theta, n_phi) grid (the layout produced when theta
and phi were passed as 1-D axes and meshed by far_field).
Returns a dict with e_theta, e_phi, intensity,
directivity, theta (n_theta,) and phi (n_phi,).
The sizes default to shape. On a k-space or Cartesian grid
the axes are ux, uy or x_um, y_um instead, theta
and phi are (n1, n2) arrays, and there is no
directivity. An exact projection adds e_r, h_r,
h_theta, h_phi and r_um. Deprecated: dataset
holds the same arrays on the grid's axes.
cartesian_fields
¶
The field at each point in Cartesian components, Ex, Ey,
Ez (V/m) and Hx, Hy, Hz (A/m), each (n_freq,
n_point), in the units of the recorded fields: the amplitudes times
e^{i k r} / r with k the medium's wavenumber and r the
point's distance r_um. An exact projection gives all six
components; on a Cartesian grid in the far-field approximation E is
transverse and H = r_hat x E / eta. Needs a distance:
ValueError for far-field amplitudes on an angle or k-space grid
(project with exact=True and r_um=, or use
renormalize). Deprecated: cartesian returns them
labelled.
equivalent_currents
¶
equivalent_currents(
plane_fields: Mapping[str, DataArray],
*,
axis: Axis,
freq_hz: float,
sign: float = 1.0,
colocate: bool = True,
) -> Dict[str, ndarray]
Surface equivalent currents J = n_hat x H, M = -n_hat x E on a
single plane normal to axis, at one frequency.
sign is the orientation of the outward normal relative to +axis
(+1 for an outward normal along +axis, -1 along -axis).
colocate (default on) averages each Yee-staggered component to the
in-plane cell node before forming J/M, correct for the engine's
staggered DFT output; pass False for synthetic already-co-located
fields (see the module docstring's "Yee staggering").
Returns a dict with the three Cartesian components of J and M (keys
Jx,Jy,Jz,Mx,My,Mz, each a [i_t2, i_t1] complex array) plus the
plane geometry t1,t2 (axis names), c1,c2 (their coords, µm),
normal_um (the normal position, µm) and normal_axis (axis).
unfold_symmetry
¶
Mirror a plane recorded on a symmetry-reduced domain into the full plane.
A run with Simulation.symmetry solves a fraction of the domain and
records only that fraction. This returns the plane the full domain would
have recorded: each component is reflected through every symmetry plane
that cuts the monitor, with the sign that plane gives it
(_mirror_parity), and the mirrored coordinates run negative.
The symmetry planes sit on the domain's minimum faces, at coordinate 0, so
the recorded region has to start there; a monitor that stops short of a
symmetry plane cannot be unfolded and raises ValueError.
Components that are half a cell off the node along a mirrored axis are
averaged onto the node first (they have no sample on the plane itself), so
the result is node-centred: pass colocate=False if you hand it to
far_field yourself. far_field mirrors a symmetry-reduced
run's faces itself, each component at its own Yee point and with no
averaging (this function's parities, _mirror_parity); this one
serves the plotted, user-frame view of a result and the earlier readout.
A run with no symmetry is returned unchanged.
Near fields¶
colocated_fields
¶
colocated_fields(
data,
monitor_name: str,
*,
axis: Optional[str] = None,
freqs_hz: Union[
None, float, Sequence[float], ndarray
] = None,
simulation=None,
dl_um: Optional[float] = None,
) -> Dataset
A recorded field plane with E and H on the same points, at their true coordinates.
The solver records each component at its own Yee point (E_c half a
cell along c, H_c half a cell along the two other axes), while
data[name] labels every component with its cell's base node, so a
product of components read at those labels (a Poynting flux E x
H*, an intensity, an overlap) is wrong at first order in the cell.
This returns the plane with every recorded component on the plane's
grid nodes:
- In the plane, on the recorded base nodes (
data[name]'s own coordinates, which are the grid lines, in either order), less the first node along an axis where some component sits half a cell past the node and has no sample before it. Such a component is interpolated linearly onto each node from its two samples either side: weights 1/2 and 1/2 on a uniform mesh, from the two cells' widths on a graded one, from the distances to two samples a stride apart on a plane recorded withinterval_space. On an axis the plane spans whole, recorded at every cell, where the run wraps (periodic or Bloch boundaries, from the run's simulation orsimulation=; the cell count from the run's manifest or that simulation), the sample before the first node is the last one, a period back, times the Bloch phasee^{-i k L}, and every node is kept. A plane across the period recorded with a stride, or whose cell count is not known, cannot wrap and warns. - Along the normal, a plane recorded one cell thick (two samples on
neighbouring grid lines: from the quarter point of a cell
kto that of cellk + 1, which is sizedlcentred at(k + 3/4) dlon a uniform mesh; every face of aFarFieldMonitor, on any mesh) is read on one surface, the E plane of its upper sample: tangential E and normal H as recorded there, tangential H and normal E interpolated onto it from the two cells' H planes. That is where aPowerMonitorat that node reads H, and howfar_fieldbrings H onto E on a box face. A plane recorded with one sample cannot be moved along its normal: tangential E and normal H sit on its plane, tangential H and normal E half a cell past it (on a graded mesh, the middle of the cell), where the solver recorded them, with a warning when the plane holds both. Each variable'sattrsgive its position along the normal; a scalar coordinate named by the normal axis is present when all share one surface.
The samples are placed with far_field's readout: from the run's
grid lines (a RunResult's manifest lists them, so graded meshes
work), and a symmetry-reduced plane that starts on a symmetry plane is
first unfolded into the full plane, each component mirrored at its own
Yee point with the sign the plane gives it (a plane wholly on the kept
side comes back as recorded).
Accuracy. The interpolation is second order: a plane wave of
wavenumber k_a along an interpolated axis a is read
cos(k_a d / 2) of its value (d the cell), 1 - (k_a d)^2 /
8; on a plane recorded every s cells the node lies half a cell
from one sample and s - 1/2 cells from the other, and the error is
about 2 s - 1 times larger. On a dipole's exact field 0.3 to 0.6 um
from the plane, every component reads within 1.9 % of its peak (one
normal sample) and 2.2 % (two) at 20 cells per wavelength, 0.47 % and
0.59 % at 40. It grows quickly as the plane nears a source: for a
plane 0.09 um from a dipole, 24 to 34 % (one sample) and 32 to 62 %
(two) at 20 cells, 8.0 to 9.7 % and 9.5 to 13 % at 40, over the
dipole's orientation and the plane's axis and side.
Against far_field. On a uniform mesh, a plane recorded at every
cell with one sample along its normal is read on far_field's
own surfaces for a single plane (E on its plane, H half a cell past
it). There the colocated tangential fields, taken as the currents J =
n x H and M = -n x E and integrated with its quadrature, give its
radiation integrals times cos(k r_a d / 2) exactly, r_a the
direction's cosine along the axis a each current's field was
interpolated along, when the field has died out at the plane's edges:
the same total current and first moment, and its amplitude along the
plane's normal to round-off. On a plane that wraps across a period it
holds at the plane's diffraction orders (k r_a = k_B + 2 pi m / L
on each wrapped axis, k_B its Bloch wavevector), the directions a
periodic structure radiates into, but not between them, and not for
the first moment; on a Bloch plane the normal is not an order (the
total current then differs, by 2.8 % at 20 cells per wavelength on an
oblique plane wave). On a
plane recorded every s cells the total current and first moment are
kept but the factor is (s - 1/2)/s e^{i k r_a d/2} + e^{-i k r_a
(s - 1/2) d}/(2 s); on a graded mesh the relation holds to second
order (along the normal 2.7e-4, 6.6e-5 and 1.7e-5 of the amplitude,
every cell cut in two, then four). A plane recorded one cell thick is
read on its upper E plane, while far_field reads a single plane
from its first sample, so along the normal the two differ by the
normal interpolation's factor, (1 - cos(k dl / 2)) / 2, about (k
dl)^2 / 16 of the amplitude: 6.15e-3 at 20 cells per wavelength,
1.54e-3 at 40. On a FarFieldMonitor box face the fields are, bit for
bit, far_field's H-onto-E pass there brought onto the nodes
(far_field averages that pass with its E-onto-H pass).
far_field itself reads each component where it was recorded
and does not interpolate in the plane.
Power. A product of two colocated components carries the factor
twice. On an FDTD dipole in vacuum (uniform mesh, PML 0.5 um, a plane
0.6 um above it spanning the domain, its edges in the PML), the flux
1/2 Re (E x H*) . n summed over the nodes of a plane recorded one
cell thick reads 0.46 %, 0.21 % and 0.12 % below a PowerMonitor on
the same plane at 20, 30 and 40 cells per wavelength (the monitor pairs
each component with the one that shares its Yee point, and also
averages H onto the E plane). With one sample, H half a cell above E,
each point of E x H* is wrong at first order (4.2 %, 2.8 % and
2.1 % of the peak flux density); summed over a plane that error leaves
a term along the plane's edges, which vanishes where the field has
died out there or across a periodic cell and remains where the plane
is cut or runs into the PML, as here: 2.0 %, 1.3 % and 0.91 % low
(1.6 %, 0.96 % and 0.68 % with the PML 1.5 um from the dipole). Record
a plane one cell thick for any product of E and H. Those figures hold
for a plane whose edges reach the PML or where the field has died out.
A plane cut in the open loses about one cell's strip of flux at its
edges, first order, since no node precedes a half-offset component's
first sample: cut 1 um from the dipole axis, 1.8 %, 1.1 % and 0.78 %
low; cut 0.5 um from it, 5.6 %, 3.7 % and 2.8 %. Record such a plane a
few cells wider than the region it is integrated over. A plane that
spans a periodic cell, recorded at every cell, wraps and keeps every
node: one cell thick across a 1.6 um cell, 0.62 %, 0.28 % and 0.16 %
low, second order
(7.4 %, 4.7 % and 3.5 % when its first row and column were dropped).
Parameters¶
data:
A RunResult, or a mapping of recorded
field_dft arrays (in the wire frame, as far_field takes
them).
monitor_name:
A ProfileMonitor plane, a face of a
FarFieldMonitor ("<name>.z_plus" ...), or a
FarFieldMonitor plane by its own name. A face's dataset carries
n_medium, the index its recipe recorded, which far_field
projects into, when the recipe is known (the run keeps it, a
reloaded result reads it back from client.json, or
simulation= gives it). Without it, a surface read by its own
name (its face found by name, as far_field finds it) or a face
of a box (two or more faces recorded under its prefix) warns that
the index is not known; a lone face read by its own name is an
ordinary plane. An open box's cut and a plane's edge window are not
applied: they weight the projection, not the fields.
axis:
The plane's normal, "x", "y" or "z". By default the
axis with one sample, or a FarFieldMonitor face's own; pass it
for a plane recorded one cell thick.
freqs_hz:
Frequencies to read (Hz): the recorded frequency nearest each. By
default every recorded one.
simulation:
The Simulation the data came from: its symmetry
planes (a RunResult records them; both must agree), its
boundaries and grid (a periodic axis wraps; a plain mapping takes
the cell count from it) and its far-field recipes. Needed for a
plain mapping from a symmetry-reduced or periodic run.
dl_um:
The uniform cell size, for a plain mapping whose cell size nothing
else tells (a RunResult's manifest, or a plane's two normal
samples). Checked as far_field checks it.
Returns¶
xarray.Dataset
Dims ("f", a, b), with a, b the plane's two in-plane
axes in xyz order (("f", "x", "y") for a plane normal to z),
their coordinates the nodes in um in the frame of data[name];
wlen_um (vacuum) beside f, and n_medium for a
far-field face whose recipe is known. Data variables Ex ...
Hz, those the plane recorded (a FarFieldMonitor face records
the four tangential ones), in V/m and A/m for the sources as
declared (attrs["normalization"] says when the reader could not
restore them).
Raises¶
ValueError
For what far_field refuses on a plane, with its wording: more
than two samples along the normal, a dl_um that disagrees with
the run or the plane, samples off the run's grid lines, symmetry
planes that disagree with the run's; and where the readout cannot
place the samples (a plain array with no cell size, or from a
graded mesh without the run's grid lines), where far_field
falls back to its earlier readout with a warning. Also a plane
with one sample along an in-plane axis (unless the domain is one
cell there), a FarFieldMonitor box named instead of one of its
faces, and an empty freqs_hz.
Warns¶
UserWarning
A plane recorded with one sample along its normal that holds both
E and H (they are half a cell apart along it), or with two samples
that are not neighbours (read from the first); a plane across a
periodic or Bloch axis that cannot wrap (recorded with a stride, or
its cell count not known), once per plane; a far-field surface or
box face whose recipe is not known (no n_medium).
Examples¶
The power through a plane recorded one cell thick, from the colocated
fields, and a map of |E|^2::
ds = colocated_fields(data, "lid", axis="z").isel(f=0)
sz = 0.5 * np.real(ds["Ex"] * np.conj(ds["Hy"]) - ds["Ey"] * np.conj(ds["Hx"]))
power_w = float(sz.integrate(["x", "y"])) * 1e-12 # um^2 -> m^2
e2 = abs(ds["Ex"]) ** 2 + abs(ds["Ey"]) ** 2 + abs(ds["Ez"]) ** 2
Diffraction orders¶
diffraction_orders
¶
diffraction_orders(
simulation,
data,
monitor,
*,
n_medium: Optional[Union[float, ndarray]] = None,
) -> DiffractionOrders
Decompose a recorded periodic DFT plane into grating orders.
Parameters¶
simulation:
The run's Simulation (grid spacing, boundaries, symmetry and, when
n_medium is not given, the analytic cross-section all come from
it).
data:
The run's RunResult, or any name -> DataArray mapping, whose
coordinates are taken as the wire frame. The plane's position is read
through frame.wire_array, so a fitted run's user-frame
coordinates are handled.
monitor:
The full-plane ProfileMonitor (or its name) recording all four
tangential components of the plane normal to its zero-size axis.
n_medium:
Refractive index of the homogeneous region containing the plane: a
scalar, or one value per recorded frequency. Default: sampled from
the simulation cross-section at the plane (raises if the plane
crosses structures); in a dispersive (pole) medium it is
sqrt(Re eps(f)) at each recorded frequency, not eps_inf, and
DiffractionOrders.n_medium is then that per-frequency array.
Returns¶
DiffractionOrders
DiffractionOrders
dataclass
¶
DiffractionOrders(
axis: str,
in_plane_axes: Tuple[str, str],
freqs_hz: ndarray,
orders1: ndarray,
orders2: ndarray,
k1: ndarray,
k2: ndarray,
kn: ndarray,
propagating: ndarray,
amp_s_forward: ndarray,
amp_p_forward: ndarray,
amp_s_backward: ndarray,
amp_p_backward: ndarray,
power_forward: ndarray,
power_backward: ndarray,
n_medium: Union[float, ndarray],
area_um2: float,
_raw_plane_power: ndarray,
)
Result of diffraction_orders.
Order arrays are indexed (f, m1, m2) with orders1 / orders2
the integer order coordinates (ascending, 0-centred). Amplitudes are the
complex FULL E-field amplitudes of each order's s/p wave (engine phasor
units); they are NaN for evanescent/grazing orders, where a directional
plane-wave amplitude is not defined. Powers are 0 there.
theta_rad
property
¶
Polar diffraction angle from the +axis normal, per (f, m1, m2); NaN for non-propagating orders.
phi_rad
property
¶
Azimuth of the order's in-plane wavevector in the (u1, u2) frame, per (m1, m2) (frequency-independent).
total_power
¶
Sum of propagating order powers per frequency.
net_power
¶
Physical net power through the plane toward +axis per frequency:
total_power('+') - total_power('-') (counter-propagating
interference carries no net Poynting; evanescent-pair tunneling is
excluded with the evanescent mask).
staggered_plane_power
¶
The naive half-cell-staggered Poynting integral of the recorded
plane, computed directly in real space (E on-plane against H a half
cell off-plane). Per order it equals the directional net power times
cos(Re(k_n) d_axis / 2), an UNDER-count at coarse normal
sampling, so it serves as an independent bookkeeping cross-check
against the order decomposition, not as the physics reference
(net_power is that).