Process reference: line_of_sight
Generated from the docstrings of vaft.process.line_of_sight:
3 public functions. Each entry is what
vaft.process.describe("line_of_sight.<name>") prints.
All process categories
Overview
A line-integrated diagnostic measures B_i = int epsilon dl along sightline
i. On a grid of cells that is a matrix product B = G epsilon with
G_ij the length of sightline i inside cell j. This module builds
G from exact segment-cell intersections, so it is a geometric statement
about the chords and the grid, not a resampled image of a mask, and it
converges as the grid is refined.
The operator knows nothing about which diagnostic the chords belong to or
what the field is: soft X-ray emissivity, radiated power or density all
integrate the same way. Machine geometry is loaded elsewhere, for VEST by
vaft.machine_mapping.soft_x_rays.sxr_sightlines.
| Symbol | Meaning | Unit |
|---|---|---|
| G_ij | length of chord i inside cell j | m |
| epsilon_j | field value on cell j | any, per metre |
| B_i | line integral along chord i | any |
A grid node is a cell centre. Cell edges sit halfway between nodes and
half a spacing beyond the outermost ones, so the cells tile the rectangle the
grid spans plus half a cell all round, and a field sampled on the nodes of an
equilibrium map is integrated as piecewise constant on those cells. Cells are
flattened in C order over (R, Z), the order field.reshape(-1) gives
for a field indexed (R, Z).
A chord is a straight segment in one poloidal plane. Its end points are
(R, Z) at one toroidal angle; a sightline that crosses toroidal angle is
not representable here.
Functions
build_line_integral_operator | The path-length matrix of chords through the cells of an (R, Z) grid.
Convention |
clip_segment_to_polygon | The parameter intervals of a segment that lie inside a closed polygon. Convention |
project_emissivity | Line integrals of one or many (R, Z) fields through a path-length operator.
|
No function matches.
build_line_integral_operator
Machine-independentConvention-sensitive
source
build_line_integral_operator(r, z, sightlines, *, domain=None)
The path-length matrix of chords through the cells of an (R, Z) grid.
| Name | Type | Unit | Description |
|---|---|---|---|
r | array_like | m | Major-radius grid axis, the cell centres, strictly increasing. |
z | array_like | m | Height grid axis, the cell centres, strictly increasing. |
sightlines | Sightlines | - | The chords, one row of the operator each. |
domain | tuple of array_like, optional | m |
|
| Returns | |||
| — | scipy.sparse.csr_matrix | m |
|
Convention
Nodes are cell centres, with edges halfway between them and half a spacing
beyond the outermost node, and cells are flattened in C order over
(R, Z). A chord running exactly along an interior edge is assigned to
the cell above it, and along an outer edge to the outermost cell. Each entry is the exact length of the chord inside that cell,
so a row sums to the chord’s length inside the gridded rectangle and the
domain. Chords are straight in the (R, Z) plane; their toroidal angle
does not enter the operator.
Raises
ValueErrorA grid axis is not strictly increasing or has fewer than two values, or the domain polygon is degenerate.
Processing steps · Applicability · Limitations · Provenance
Processing steps
- Clip each chord to the domain polygon, if one is given.
- For each inside interval, collect the parameters at which it crosses a cell edge in either direction.
- Assign each piece between consecutive crossings to the cell containing its midpoint, with the piece’s length as the entry.
Applicability
Machine-independent.
Limitations
A pencil beam: no finite aperture, no viewing cone, no etendue weighting and no reflection. Deterministic but not cached; build it once per grid and chord set and reuse it.
Provenance
- Siddon, R. L., Med. Phys. 12, 252 (1985).
clip_segment_to_polygon
Machine-independentConvention-sensitive
source
clip_segment_to_polygon(p1, p2, polygon_r, polygon_z)
Show source vaft/process/line_of_sight.py, lines 101–179
def clip_segment_to_polygon(p1, p2, polygon_r, polygon_z) -> list[tuple[float, float]]:
"""The parameter intervals of a segment that lie inside a closed polygon.
Parameters
----------
p1 : sequence of float
``(R, Z)`` of the segment start [m].
p2 : sequence of float
``(R, Z)`` of the segment end [m].
polygon_r : array_like
Polygon vertex major radii, closed or open [m].
polygon_z : array_like
Polygon vertex heights [m].
Returns
-------
list of tuple of float
``(t_start, t_end)`` intervals of the segment parameter
``p = p1 + t (p2 - p1)``, ``0 <= t <= 1``, lying inside the polygon,
in increasing order [-].
Raises
------
ValueError
The polygon has fewer than three distinct vertices.
Convention
----------
Inside is decided by even-odd parity at each sub-interval's midpoint, so a
non-convex polygon, such as a limiter with a divertor recess, yields
several intervals. A segment grazing a vertex or running along an edge
splits there with zero-length pieces dropped.
Applicability
-------------
Machine-independent.
Limitations
-----------
The polygon is a single loop; holes are not supported.
Provenance
----------
.. [1] The even-odd rule for point-in-polygon, as
:class:`matplotlib.path.Path` applies it.
"""
from matplotlib.path import Path
pr = np.asarray(polygon_r, dtype=float).reshape(-1)
pz = np.asarray(polygon_z, dtype=float).reshape(-1)
if pr.size != pz.size:
raise ValueError("polygon_r and polygon_z must have the same length")
if pr.size and (pr[0] != pr[-1] or pz[0] != pz[-1]):
pr = np.append(pr, pr[0])
pz = np.append(pz, pz[0])
if np.unique(np.column_stack((pr, pz)), axis=0).shape[0] < 3:
raise ValueError("a polygon needs at least three distinct vertices")
a = np.asarray(p1, dtype=float)
d = np.asarray(p2, dtype=float) - a
e0 = np.column_stack((pr[:-1], pz[:-1]))
e = np.column_stack((pr[1:], pz[1:])) - e0
denom = d[0] * e[:, 1] - d[1] * e[:, 0]
rel = e0 - a
with np.errstate(divide="ignore", invalid="ignore"):
t = (rel[:, 0] * e[:, 1] - rel[:, 1] * e[:, 0]) / denom
u = (rel[:, 0] * d[1] - rel[:, 1] * d[0]) / denom
hit = (denom != 0) & (u >= 0.0) & (u <= 1.0) & (t > 0.0) & (t < 1.0)
cuts = np.unique(np.concatenate(([0.0, 1.0], t[hit])))
mids = 0.5 * (cuts[:-1] + cuts[1:])
inside = Path(np.column_stack((pr, pz))).contains_points(a + mids[:, None] * d)
intervals: list[tuple[float, float]] = []
for lo, hi, keep in zip(cuts[:-1], cuts[1:], inside):
if not keep or hi <= lo:
continue
if intervals and intervals[-1][1] == lo:
intervals[-1] = (intervals[-1][0], float(hi))
else:
intervals.append((float(lo), float(hi)))
return intervalsThe parameter intervals of a segment that lie inside a closed polygon.
| Name | Type | Unit | Description |
|---|---|---|---|
p1 | sequence of float | m |
|
p2 | sequence of float | m |
|
polygon_r | array_like | m | Polygon vertex major radii, closed or open. |
polygon_z | array_like | m | Polygon vertex heights. |
| Returns | |||
| — | list of tuple of float | - |
|
Convention
Inside is decided by even-odd parity at each sub-interval’s midpoint, so a non-convex polygon, such as a limiter with a divertor recess, yields several intervals. A segment grazing a vertex or running along an edge splits there with zero-length pieces dropped.
Raises
ValueErrorThe polygon has fewer than three distinct vertices.
Applicability · Limitations · Provenance
Applicability
Machine-independent.
Limitations
The polygon is a single loop; holes are not supported.
Provenance
- The even-odd rule for point-in-polygon, as
matplotlib.path.Pathapplies it.
project_emissivity
Machine-independent
source
project_emissivity(emissivity, operator)
Show source vaft/process/line_of_sight.py, lines 302–341
def project_emissivity(emissivity, operator) -> np.ndarray:
"""Line integrals of one or many ``(R, Z)`` fields through a path-length operator.
Parameters
----------
emissivity : array_like
Field indexed ``(..., R, Z)``; leading axes, such as time, are kept
[any, per metre].
operator : scipy.sparse matrix or ndarray
From :func:`build_line_integral_operator` for the same grid [m].
Returns
-------
numpy.ndarray
Line integrals shaped ``(..., n_chords)`` [any].
Raises
------
ValueError
The field's last two axes do not hold as many cells as the operator
has columns.
Applicability
-------------
Machine-independent.
Limitations
-----------
Exactly linear: whatever the field's unit, the result is that unit times
metres. No detector response, filter transmission or etendue is applied.
"""
field = np.asarray(emissivity, dtype=float)
if field.ndim < 2:
raise ValueError("emissivity must be indexed (..., R, Z)")
lead = field.shape[:-2]
cells = field.shape[-2] * field.shape[-1]
if cells != operator.shape[1]:
raise ValueError(f"the field has {cells} cells but the operator {operator.shape[1]} columns")
flat = field.reshape(-1, cells)
return np.asarray(operator @ flat.T).T.reshape(*lead, operator.shape[0])Line integrals of one or many (R, Z) fields through a path-length operator.
| Name | Type | Unit | Description |
|---|---|---|---|
emissivity | array_like | any, per metre | Field indexed |
operator | scipy.sparse matrix or ndarray | m | From |
| Returns | |||
| — | numpy.ndarray | any | Line integrals shaped |
Raises
ValueErrorThe field’s last two axes do not hold as many cells as the operator has columns.
Applicability · Limitations
Applicability
Machine-independent.
Limitations
Exactly linear: whatever the field’s unit, the result is that unit times metres. No detector response, filter transmission or etendue is applied.