Source code for torx.geometry.polygon_3d_m

"""Implementation of a 3D toroidally closed polygon class."""
import numpy as np
import h5netcdf
import xarray as xr

from .polygon_2d_m import Polygon2D
from torx.autogrid_decorators_m import autogrid_method
from torx.autodoc_decorators_m import autodoc_class

[docs] @autodoc_class class Polygon3D: """3D polygon toroidally closed."""
[docs] @classmethod def initialize_from_file(self, file_name, variable_name, **kwargs): """Initialize the 3D polygon from a NetCDF file and variable name.""" self.file_name = file_name self.variable_name = variable_name ds = h5netcdf.File(file_name) polygon_vertices = ds.variables[variable_name][:].data phi_array = ds.variables["phi_array"][:].data ds.close() return self(phi_array, polygon_vertices, **kwargs)
[docs] def __init__(self, phi_array, polygon_vertices, interpolation_method="linear", num_field_periods=None): """ Initialize the 3D polygon with an array of toroidal angles. A phi values 2D polygons are defined in RZ. It is also possible to initialize a "3D" polygon with only a single phi value, essentially creating a 2D (axisymmetric) polygon. Since the 3D polygon is defined discretely in phi, an interpolation method in phi is required. Current options are "linear". If num_field_periods is passed, this is used to verify that the phi array is evenly spaced in a range [0, 2pi/num_field_periods). """ self.phi_array = np.atleast_1d(phi_array) self.polygon_vertices = polygon_vertices self.n_planes = polygon_vertices.shape[0] self.n_vertices = polygon_vertices.shape[-1] assert self.polygon_vertices.shape == (self.n_planes, 2, self.n_vertices) assert self.phi_array.size == self.n_planes if self.n_planes > 1: self.dphi = self.phi_array[1] - self.phi_array[0] if num_field_periods is not None: self.phi_max = 2.0 * np.pi / num_field_periods assert np.isclose(self.phi_array[-1] + self.dphi, self.phi_max) else: self.phi_max = self.phi_array[-1] + self.dphi assert np.allclose(self.phi_array, np.linspace(0.0, self.phi_max, num=self.n_planes, endpoint=False)) else: self.dphi = 0.0 self.phi_max = 0.0 if interpolation_method == "linear": self._interpolate_phi = self._interpolate_phi_linear else: raise Exception((f"Interpolation method {interpolation_method} is " "not supported!"))
def _interpolate_phi_linear(self, phi): """ Return a polygon at the given phi. Linearly interpolates the polygon array in phi. """ polygon_vertices = np.zeros((2, self.n_vertices)) if self.n_planes == 1: # No interpolation necessary, the 3D polygon is axisymmetric polygon_vertices[:] = self.polygon_vertices[0, :, :] else: phi_mod = phi % self.phi_max plane_left = int(phi_mod // self.dphi) plane_right = (plane_left + 1) % self.n_planes interp_frac = (phi_mod - self.phi_array[plane_left]) / self.dphi step = self.polygon_vertices[plane_right, :, :] \ - self.polygon_vertices[plane_left, :, :] polygon_vertices[:] = self.polygon_vertices[plane_left, :, :] \ + step * interp_frac interpolated_polygon = Polygon2D(x_points=polygon_vertices[0, :], y_points=polygon_vertices[1, :]) return interpolated_polygon
[docs] @autogrid_method def points_inside(self, r_norm, z_norm, phi, **kwargs): """ Test whether the points are inside the polygon array. Todo ---- This currently only works with xarray-type coordinates, because the autogrid decorator has not been fully ported to 3D! """ RR, ZZ, _ = xr.broadcast(r_norm, z_norm, phi) inside = xr.zeros_like(RR, dtype=bool) for k in range(phi.size): poly_interp = self._interpolate_phi(phi.values[k]) inside[{"phi":k}] = poly_interp.points_inside(RR[{"phi":k}].values, ZZ[{"phi":k}].values) return inside
[docs] def point_inside(self, r_norm, z_norm, phi, **kwargs): """Test whether the single point is inside the polygon array.""" poly_interp = self._interpolate_phi(phi) inside = poly_interp.point_inside(r_norm, z_norm) return inside