Skip to content

space

Space sampling

ClosedFluxSurface

Closed poloidal magnetic flux surface.

Source code in tokamak_neutron_source/flux.py
class ClosedFluxSurface:
    """Closed poloidal magnetic flux surface."""

    def __init__(self, x: np.ndarray, z: np.ndarray):
        if not is_closed(x, z):
            raise FluxSurfaceError("This is not a closed flux surface.")
        self.x = np.asarray(x, dtype=float)
        self.z = np.asarray(z, dtype=float)

    @cached_property
    def center_of_mass(self) -> tuple[float, float]:
        """
        Centre of mass of the ClosedFluxSurface.

        Returns
        -------
        com:
            ClosedFluxSurface center of mass
        """
        return get_centroid_2d(self.x, self.z)

    @cached_property
    def area(self) -> float:
        """
        Enclosed area of the ClosedFluxSurface.

        Returns
        -------
        area:
            ClosedFluxSurface enclosed poloidal area
        """
        return get_area_2d(self.x, self.z)

    @cached_property
    def volume(self) -> float:
        """
        Volume of the ClosedFluxSurface.

        Returns
        -------
        volume:
            ClosedFluxSurface enclosed volume.
        """
        return 2 * np.pi * self.area * self.center_of_mass[0]

area cached property

Enclosed area of the ClosedFluxSurface.

Returns:

Name Type Description
area float

ClosedFluxSurface enclosed poloidal area

center_of_mass cached property

Centre of mass of the ClosedFluxSurface.

Returns:

Name Type Description
com tuple[float, float]

ClosedFluxSurface center of mass

volume cached property

Volume of the ClosedFluxSurface.

Returns:

Name Type Description
volume float

ClosedFluxSurface enclosed volume.

FluxPoint

Single poloidal magnetic flux point.

Source code in tokamak_neutron_source/flux.py
@dataclass
class FluxPoint:
    """Single poloidal magnetic flux point."""

    x: float
    z: float
    psi: float

sample_space_2d(lcfs, o_point, cell_side_length)

Sample the 2-D poloidal plane within the LCFS.

Parameters:

Name Type Description Default
lcfs ClosedFluxSurface

Last closed flux surface

required
o_point FluxPoint

O-point location

required
cell_side_length float

Side length of square cells [m]

required

Returns:

Name Type Description
x ndarray[tuple[Any, ...], dtype[~_ScalarT]]

Radial coordinates of sampled points [m]

z ndarray[tuple[Any, ...], dtype[~_ScalarT]]

Vertical coordinates of sampled points [m]

d_volume ndarray[tuple[Any, ...], dtype[~_ScalarT]]

Volumes of cells centred at points [m^3]

Notes

Creates points at the centres of square cells of fixed size (cell_side_length by cell_side_length). Only cells whose centres fall inside the LCFS polygon are kept. Cells are positioned such that the centre of one cell lies on the O-point.

Source code in tokamak_neutron_source/space.py
def sample_space_2d(
    lcfs: ClosedFluxSurface,
    o_point: FluxPoint,
    cell_side_length: float,
) -> tuple[npt.NDArray, npt.NDArray, npt.NDArray]:
    """
    Sample the 2-D poloidal plane within the LCFS.

    Parameters
    ----------
    lcfs:
        Last closed flux surface
    o_point:
        O-point location
    cell_side_length:
        Side length of square cells [m]

    Returns
    -------
    x:
        Radial coordinates of sampled points [m]
    z:
        Vertical coordinates of sampled points [m]
    d_volume:
        Volumes of cells centred at points [m^3]

    Notes
    -----
    Creates points at the centres of square cells of fixed size
    (cell_side_length by cell_side_length). Only cells whose centres fall
    inside the LCFS polygon are kept.
    Cells are positioned such that the  centre of one cell lies on
    the O-point.
    """
    # Get bounding box around LCFS (+ offset)
    polygon_path = Path(np.c_[lcfs.x, lcfs.z])
    off = 2.0 * cell_side_length  # Just to be sure
    x_min, x_max = np.min(lcfs.x) - off, np.max(lcfs.x) + off
    z_min, z_max = np.min(lcfs.z) - off, np.max(lcfs.z) + off

    # Shift grid so O-point lies on a cell center
    dx = (o_point.x - x_min) % cell_side_length - 0.5 * cell_side_length
    dz = (o_point.z - z_min) % cell_side_length - 0.5 * cell_side_length

    # Construct grid
    x_centers = np.arange(x_min + dx, x_max, cell_side_length) + 0.5 * cell_side_length
    z_centers = np.arange(z_min + dz, z_max, cell_side_length) + 0.5 * cell_side_length
    x, z = np.meshgrid(x_centers, z_centers, indexing="ij")
    points = np.c_[x.ravel(), z.ravel()]

    # Mask by LCFS polygon
    inside = polygon_path.contains_points(points)
    points = points[inside]

    # Volumes: toroidal rotation of each square cell
    d_volume = 2 * np.pi * points[:, 0] * cell_side_length**2

    return points[:, 0], points[:, 1], d_volume