def fundamental_sector(self): from orix.vector.neo_euler import AxAngle from orix.vector.spherical_region import SphericalRegion symmetry = self.antipodal symmetry = symmetry[symmetry.angle > 0] axes, order = symmetry.get_highest_order_axis() if order > 6: return Vector3d.empty() axis = Vector3d.zvector().get_nearest(axes, inclusive=True) r = Rotation.from_neo_euler( AxAngle.from_axes_angles(axis, 2 * np.pi / order)) diads = symmetry.diads nearest_diad = axis.get_nearest(diads) if nearest_diad.size == 0: nearest_diad = axis.perpendicular n1 = axis.cross(nearest_diad).unit n2 = -(r * n1) next_diad = r * nearest_diad n = Vector3d.stack((n1, n2)).flatten() sr = SphericalRegion(n.unique()) inside = symmetry[symmetry.axis < sr] if inside.size == 0: return sr axes, order = inside.get_highest_order_axis() axis = axis.get_nearest(axes) r = Rotation.from_neo_euler( AxAngle.from_axes_angles(axis, 2 * np.pi / order)) nearest_diad = next_diad n1 = axis.cross(nearest_diad).unit n2 = -(r * n1) n = Vector3d(np.concatenate((n.data, n1.data, n2.data))) sr = SphericalRegion(n.unique()) return sr
def test_project_vector3d(self): """Works for Vector3d objects with single and multiple vectors""" vector_one = Vector3d((0.578, 0.578, 0.578)) output_a = LambertProjection.project(vector_one) expected_a = np.array((0.81417, 0.81417)) assert (output_a[..., 0, 0]) == pytest.approx(expected_a[0], abs=1e-3) assert output_a[..., 0, 1] == pytest.approx(expected_a[1], abs=1e-3) vector_two = Vector3d( np.array( [[0.578, 0.578, 0.578], [0, 0.707, 0.707], [0.707, 0, 0.707]] ) ) output_b = LambertProjection.project(vector_two) expected_x = np.array((0.81417, 0, 0.678)) expected_y = np.array((0.81417, 0.678, 0)) assert output_b[..., 0, 0] == pytest.approx(expected_x[0], abs=1e-3) assert output_b[..., 0, 1] == pytest.approx(expected_y[0], abs=1e-3) assert output_b[..., 1, 0] == pytest.approx(expected_x[1], abs=1e-3) assert output_b[..., 1, 1] == pytest.approx(expected_y[1], abs=1e-3) assert output_b[..., 2, 0] == pytest.approx(expected_x[2], abs=1e-3) assert output_b[..., 2, 1] == pytest.approx(expected_y[2], abs=1e-3)
def detector2sample( sample_tilt: float, detector_tilt: float, convention: Optional[str] = None, ) -> Rotation: """Rotation U_S to align detector frame D with sample frame S. Parameters ---------- sample_tilt Sample tilt in degrees. detector_tilt Detector tilt in degrees. convention Which sample reference frame to use, either the one used by EDAX TSL (default), "tsl", or the one used by Bruker, "bruker". Returns ------- Rotation """ # Rotation about sample (microscope) X axis tilt = -np.deg2rad((sample_tilt - 90) - detector_tilt) ax_angle = neo_euler.AxAngle.from_axes_angles(Vector3d.xvector(), tilt) r = Rotation.from_neo_euler(ax_angle) if convention != "bruker": # Followed by a 90 degree rotation about the sample Z axis, # if the TSL sample reference frame is used ax_angle_bruker2tsl = neo_euler.AxAngle.from_axes_angles( Vector3d.zvector(), np.pi / 2) r = Rotation.from_neo_euler(ax_angle_bruker2tsl) * r return r.to_matrix()[0]
def get_highest_order_axis(self): axis_orders = self.get_axis_orders() if len(axis_orders) == 0: return Vector3d.zvector(), np.infty highest_order = max(axis_orders.values()) axes = Vector3d.stack([ao for ao in axis_orders if axis_orders[ao] == highest_order]).flatten() return axes, highest_order
def test_orientation_persistence(symmetry, vector): v = symmetry.outer(vector).flatten() o = Orientation.random() oc = o.set_symmetry(symmetry) v1 = o * v v1 = Vector3d(v1.data.round(4)) v2 = oc * v v2 = Vector3d(v2.data.round(4)) assert v1._tuples == v2._tuples
def axis(self): """Vector3d : the axis of rotation.""" axis = Vector3d( np.stack((self.b.data, self.c.data, self.d.data), axis=-1)) axis[self.a.data < -1e-6] = -axis[self.a.data < -1e-6] axis[axis.norm.data == 0] = Vector3d.zvector() * np.sign( self.a[axis.norm.data == 0].data) axis.data = axis.data / axis.norm.data[..., np.newaxis] return axis
def get_equivalent_hkl(hkl, operations, unique=False, return_multiplicity=False): """Return symmetrically equivalent Miller indices. Parameters ---------- hkl : orix.vector.Vector3d, np.ndarray, list or tuple of int Miller indices. operations : orix.quaternion.symmetry.Symmetry Point group describing allowed symmetry operations. unique : bool, optional Whether to return only unique Miller indices. Default is False. return_multiplicity : bool, optional Whether to return the multiplicity of the input indices. Default is False. Returns ------- new_hkl : orix.vector.Vector3d The symmetrically equivalent Miller indices. multiplicity : np.ndarray Number of symmetrically equivalent indices. Only returned if `return_multiplicity` is True. """ new_hkl = operations.outer(Vector3d(hkl)) new_hkl = new_hkl.flatten().reshape(*new_hkl.shape[::-1]) multiplicity = None if unique: n_families = new_hkl.shape[0] multiplicity = np.zeros(n_families, dtype=int) temp_hkl = new_hkl[0].unique().data multiplicity[0] = temp_hkl.shape[0] if n_families > 1: for i, hkl in enumerate(new_hkl[1:]): temp_hkl2 = hkl.unique() multiplicity[i + 1] = temp_hkl2.size temp_hkl = np.append(temp_hkl, temp_hkl2.data, axis=0) new_hkl = Vector3d(temp_hkl[:multiplicity.sum()]) # Remove 1-dimensions new_hkl = new_hkl.squeeze() if unique and return_multiplicity: return new_hkl, multiplicity else: return new_hkl
def __init__( self, phase: Phase, uvw: Union[Vector3d, np.ndarray, list, tuple], uvw_detector: Union[Vector3d, np.ndarray, list, tuple], in_pattern: Union[np.ndarray, list, tuple], gnomonic_radius: Union[float, np.ndarray] = 10, ): """Positions of zone axes on the detector. Parameters ---------- phase A phase container with a crystal structure and a space and point group describing the allowed symmetry operations. uvw Miller indices. uvw_detector Zone axes coordinates on the detector. in_pattern Boolean array of shape (n, n_hkl) indicating whether an hkl is visible in a pattern. gnomonic_radius Only plane trace coordinates of bands with Hesse normal form distances below this radius is returned when called for. """ super().__init__(phase=phase, hkl=uvw) self._uvw_detector = Vector3d(uvw_detector) self._in_pattern = np.asarray(in_pattern) self.gnomonic_radius = gnomonic_radius
def iproject(xy: np.ndarray) -> Vector3d: """Convert from 2D Gnomonic coordinates (x_g, y_g) to 3D cartesian coordiantes (x, y, z). Parameters ---------- xy 2D coordinates on the form [[x_g0, y_g0], [x_g1, y_g1], ...]. Returns ------- cartesian_coordinates Cartesian coordinates (x, y, z) on the form [[x0, y0, z0], [x1, y1, z1], ...]. Examples -------- >>> import numpy as np >>> from kikuchipy.projections.gnomonic_projection import ( ... GnomonicProjection ... ) >>> xy_g = np.random.random_sample(20).reshape((10, 2)) >>> xyz = GnomonicProjection.iproject(xy_g) """ x, y = xy[..., 0], xy[..., 1] theta = np.arctan(np.sqrt(x**2 + y**2)) phi = np.arctan2(y, x) return Vector3d.from_polar(theta=theta, phi=phi)
def get_plot_data(self): """ Produces suitable Rotations for the construction of a wireframe for self """ from orix.vector import Vector3d # gets a grid of vector directions theta = np.linspace(0, 2 * np.pi - EPSILON, 361) rho = np.linspace(0, np.pi - EPSILON, 181) theta, rho = np.meshgrid(theta, rho) g = Vector3d.from_polar(rho, theta) # get the cell vector normal norms n = Rodrigues.from_rotation(self).norm.data[:, np.newaxis, np.newaxis] if n.size == 0: return Rotation.from_neo_euler(AxAngle.from_axes_angles(g, np.pi)) d = (-self.axis).dot_outer(g.unit).data x = n * d omega = 2 * np.arctan(np.where(x != 0, x**-1, np.pi)) # keeps the smallest allowed angle omega[omega < 0] = np.pi omega = np.min(omega, axis=0) r = Rotation.from_neo_euler(AxAngle.from_axes_angles(g.unit, omega)) return r
def iproject(xy: np.ndarray) -> Vector3d: """Convert (n, 2) array from Lambert to Cartesian coordinates.""" X = xy[..., 0] Y = xy[..., 1] # Arrays used in setting x and y true_term = Y * np.pi / (4 * X) false_term = X * np.pi / (4 * Y) abs_yx = abs(Y) <= abs(X) c_x = _eq_c(X) c_y = _eq_c(Y) cart = np.zeros(X.shape + (3, ), dtype=X.dtype) # Equations 8a and 8b from Callahan and De Graef (2013) cart[..., 0] = np.where(abs_yx, c_x * np.cos(true_term), c_y * np.sin(false_term)) cart[..., 1] = np.where(abs_yx, c_x * np.sin(true_term), c_y * np.cos(false_term)) cart[..., 2] = np.where( abs_yx, 1 - (2 * (X**2)) / np.pi, 1 - (2 * (Y**2)) / np.pi, ) return Vector3d(cart)
def spherical2xy(self, azimuth, polar): r"""Return stereographic coordinates (X, Y) from 3D unit vectors created from spherical coordinates, azimuth :math:`\phi` and polar :math:`\theta`, defined as in the ISO 31-11 standard [SphericalWolfram]_. Parameters ---------- azimuth : float or numpy.ndarray Spherical azimuth coordinate. polar : float or numpy.ndarray Spherical polar coordinate. Returns ------- x : numpy.ndarray Stereographic coordinate X of shape same shape as the input vector shape. Only the vectors with :math:`z` coordinate positive (`pole` = -1) or negative (`pole` = 1) are returned. y : numpy.ndarray Stereographic coordinate Y of shape same shape as the input vector shape. Only the vectors with :math:`z` coordinate positive (`pole` = -1) or negative (`pole` = 1) are returned. See Also -------- vector2xy """ v = Vector3d.from_polar(azimuth=azimuth, polar=polar) return self.vector2xy(v)
def spherical2xy_split(self, azimuth, polar): r"""Return two sets of stereographic coordinates (X, Y) from 3D unit vectors created from spherical coordinates, azimuth :math:`\phi` and polar :math:`\theta`, defined as in the ISO 31-11 standard [SphericalWolfram]_: one set for vectors in the upper hemisphere, and one for the lower. Parameters ---------- azimuth : float or numpy.ndarray Spherical azimuth coordinate. polar : float or numpy.ndarray Spherical polar coordinate. Returns ------- x_upper : numpy.ndarray Stereographic coordinate X of upper hemisphere vectors, of shape same shape as the input vector shape. y_upper : numpy.ndarray Stereographic coordinate Y of upper hemisphere vectors, of shape same shape as the input vector shape. x_lower : numpy.ndarray Stereographic coordinate X of lower hemisphere vectors, of shape same shape as the input vector shape. y_lower : numpy.ndarray Stereographic coordinate Y of lower hemisphere vectors, of shape same shape as the input vector shape. See Also -------- vector2xy """ v = Vector3d.from_polar(azimuth=azimuth, polar=polar) return self.vector2xy_split(v)
def __init__( self, phase: Phase, hkl: Union[Vector3d, np.ndarray], hkl_detector: Union[Vector3d, np.ndarray], in_pattern: np.ndarray, gnomonic_radius: Union[float, np.ndarray] = 10, ): """Center positions of Kikuchi bands on the detector for n simulated patterns. Parameters ---------- phase A phase container with a crystal structure and a space and point group describing the allowed symmetry operations. hkl All Miller indices present in any of the n patterns. hkl_detector Detector coordinates for all Miller indices per pattern, in the shape navigation_shape + (n_hkl, 3). in_pattern Boolean array of shape navigation_shape + (n_hkl,) indicating whether an hkl is visible in a pattern. gnomonic_radius Only plane trace coordinates of bands with Hesse normal form distances below this radius is returned when called for. """ super().__init__(phase=phase, hkl=hkl) self._hkl_detector = Vector3d(hkl_detector) self._in_pattern = np.atleast_2d(in_pattern) self.gnomonic_radius = gnomonic_radius
def _get_direction_cosines(detector: EBSDDetector) -> Vector3d: """Get the direction cosines between the detector and sample as done in EMsoft and :cite:`callahan2013dynamical`. Parameters ---------- detector : EBSDDetector EBSDDetector object with a certain detector geometry and one projection center. Returns ------- Vector3d Direction cosines for each detector pixel. """ pc = detector.pc_emsoft() xpc = pc[..., 0] ypc = pc[..., 1] L = pc[..., 2] # Detector coordinates in microns det_x = ( -((-xpc - (1.0 - detector.ncols) * 0.5) - np.arange(0, detector.ncols)) * detector.px_size ) det_y = ( (ypc - (1.0 - detector.nrows) * 0.5) - np.arange(0, detector.nrows) ) * detector.px_size # Auxilliary angle to rotate between reference frames theta_c = np.radians(detector.tilt) sigma = np.radians(detector.sample_tilt) alpha = (np.pi / 2) - sigma + theta_c ca = np.cos(alpha) sa = np.sin(alpha) # TODO: Enable detector azimuthal angle omega = np.radians(0) # angle between normal of sample and detector cw = np.cos(omega) sw = np.sin(omega) r_g_array = np.zeros((detector.nrows, detector.ncols, 3)) Ls = -sw * det_x + L * cw Lc = cw * det_x + L * sw i, j = np.meshgrid( np.arange(detector.nrows - 1, -1, -1), np.arange(detector.ncols), indexing="ij", ) r_g_array[..., 0] = det_y[i] * ca + sa * Ls[j] r_g_array[..., 1] = Lc[j] r_g_array[..., 2] = -sa * det_y[i] + ca * Ls[j] r_g = Vector3d(r_g_array) return r_g.unit
def get_axis_orders(self): s = self[self.angle > 0] if s.size == 0: return {} return { Vector3d(a): b + 1 for a, b in zip(*np.unique(s.axis.data, axis=0, return_counts=True)) }
def test_from_neo_euler_symmetry(self): v = AxAngle.from_axes_angles(axes=Vector3d.zvector(), angles=np.pi / 2) o1 = Orientation.from_neo_euler(v) assert np.allclose(o1.data, [0.7071, 0, 0, 0.7071]) assert o1.symmetry.name == "1" o2 = Orientation.from_neo_euler(v, symmetry=Oh) assert np.allclose(o2.data, [-1, 0, 0, 0]) assert o2.symmetry.name == "m-3m" o3 = o1.set_symmetry(Oh) assert np.allclose(o3.data, o2.data)
def test_get_circle(self): v = Vector3d([0, 0, 1]) oa = 0.5 * np.pi c = v.get_circle(opening_angle=oa, steps=101) assert c.size == 101 assert np.allclose(c.z.data, 0) assert np.allclose(v.angle_with(c).data, oa) assert np.allclose(c.mean().data, [0, 0, 0], atol=1e-2) assert np.allclose(v.cross(c[0, 0]).data, [1, 0, 0])
class TestSphericalCoordinates: @pytest.mark.parametrize( "vector, theta_desired, phi_desired, r_desired", [ (Vector3d((0.5, 0.5, 0.707107)), np.pi / 4, np.pi / 4, 1), (Vector3d( (-0.75, -0.433013, -0.5)), 2 * np.pi / 3, 7 * np.pi / 6, 1), ], ) def test_to_polar(self, vector, theta_desired, phi_desired, r_desired): theta, phi, r = vector.to_polar() assert np.allclose(theta.data, theta_desired) assert np.allclose(phi.data, phi_desired) assert np.allclose(r.data, r_desired) def test_polar_loop(self, vector): theta, phi, r = vector.to_polar() vector2 = Vector3d.from_polar(theta=theta.data, phi=phi.data, r=r.data) assert np.allclose(vector.data, vector2.data)
class TestSphericalCoordinates: @pytest.mark.parametrize( "v, polar_desired, azimuth_desired, radial_desired", [ (Vector3d((0.5, 0.5, 0.707107)), np.pi / 4, np.pi / 4, 1), (Vector3d( (-0.75, -0.433013, -0.5)), 2 * np.pi / 3, 7 * np.pi / 6, 1), ], ) def test_to_polar(self, v, polar_desired, azimuth_desired, radial_desired): azimuth, polar, radial = v.to_polar() assert np.allclose(polar.data, polar_desired) assert np.allclose(azimuth.data, azimuth_desired) assert np.allclose(radial.data, radial_desired) def test_polar_loop(self, vector): azimuth, polar, radial = vector.to_polar() vector2 = Vector3d.from_polar(azimuth=azimuth.data, polar=polar.data, radial=radial.data) assert np.allclose(vector.data, vector2.data)
def test_iproject(self): """Conversion from Lambert to Cartesian coordinates works""" vec = np.array((0.81417, 0.81417)) expected = Vector3d((0.5770240896680434, 0.5770240896680434, 0.5780020760218183)) # Vector3d(1,) output = LambertProjection.iproject(vec) # Vector3d(1,1) assert output[0].x.data[0] == pytest.approx(expected.x.data[0], rel=1e-3) assert output[0].y.data[0] == pytest.approx(expected.y.data[0], rel=1e-3) assert output[0].z.data[0] == pytest.approx(expected.z.data[0], rel=1e-3)
def test_shape_respect(self): """Check that LambertProjection.project() respects navigation axes""" sx = 60 a = np.arange(1, sx**2 + 1).reshape((sx, sx)) v = Vector3d(np.dstack([a, a, a])) assert v.shape == (sx, sx) assert v.data.shape == (sx, sx, 3) # Forward xy_lambert = LambertProjection.project(v) assert xy_lambert.shape == (sx, sx, 2) # and back xyz_frm_lambert = LambertProjection.iproject(xy_lambert) assert xyz_frm_lambert.shape == v.shape assert xyz_frm_lambert.data.shape == v.data.shape
def test_vector2xy_vector(self): """Works for Vector3d objects with single and multiple vectors""" vector_one = Vector3d((0.578, 0.578, 0.578)) output_a = LambertProjection.vector2xy(vector_one) expected_a = np.array((0.81417, 0.81417)) assert np.allclose(output_a[..., 0, 0], expected_a[0], atol=1e-3) assert np.allclose(output_a[..., 0, 1], expected_a[1], atol=1e-3) vector_two = Vector3d([[0.578, 0.578, 0.578], [0, 0.707, 0.707], [0.707, 0, 0.707]]) output_b = LambertProjection.vector2xy(vector_two) expected_x = np.array((0.81417, 0, 0.678)) expected_y = np.array((0.81417, 0.678, 0)) assert np.allclose(output_b[..., 0, 0], expected_x[0], atol=1e-3) assert np.allclose(output_b[..., 0, 1], expected_y[0], atol=1e-3) assert np.allclose(output_b[..., 1, 0], expected_x[1], atol=1e-3) assert np.allclose(output_b[..., 1, 1], expected_y[1], atol=1e-3) assert np.allclose(output_b[..., 2, 0], expected_x[2], atol=1e-3) assert np.allclose(output_b[..., 2, 1], expected_y[2], atol=1e-3)
def get_plot_data(self): from orix.vector import Vector3d theta = np.linspace(0, 2 * np.pi + 1e-9, 361) rho = np.linspace(0, np.pi, 181) theta, rho = np.meshgrid(theta, rho) g = Vector3d.from_polar(rho, theta) n = Rodrigues.from_rotation(self).norm.data[:, np.newaxis, np.newaxis] if n.size == 0: return Rotation.from_neo_euler(AxAngle.from_axes_angles(g, np.pi)) d = (-self.axis).dot_outer(g.unit).data x = n * d x = 2 * np.arctan(x**-1) x[x < 0] = np.pi x = np.min(x, axis=0) r = Rotation.from_neo_euler(AxAngle.from_axes_angles(g.unit, x)) return r
def __init__(self, phase, hkl): """A container for Miller indices, structure factors and related parameters for crystal planes (reciprocal lattice points, reflectors, g, etc.). Parameters ---------- phase : orix.crystal_map.phase_list.Phase A phase container with a crystal structure and a space and point group describing the allowed symmetry operations. hkl : orix.vector.Vector3d, np.ndarray, list, or tuple Miller indices. """ self.phase = phase self._raise_if_no_point_group() self._hkl = Vector3d(hkl) self._structure_factor = [None] * self.size self._theta = [None] * self.size
def detector2sample(sample_tilt: float, detector_tilt: float) -> Rotation: """Rotation U_S to align detector frame D with sample frame S. Parameters ---------- sample_tilt Sample tilt in degrees. detector_tilt Detector tilt in degrees. Returns ------- Rotation """ x_axis = Vector3d.xvector() tilt = -np.deg2rad((sample_tilt - 90) - detector_tilt) ax_angle = neo_euler.AxAngle.from_axes_angles(x_axis, tilt) return Rotation.from_neo_euler(ax_angle).to_matrix()[0]
def from_axes_angles(cls, axes, angles): """Create new AxAngle object explicitly from the given axes and angles. Parameters ---------- axes : Vector3d or array_like The axis of rotation. angles : array_like The angle of rotation, in radians. Returns ------- AxAngle """ axes = Vector3d(axes).unit angles = np.array(angles) axangle_data = angles[..., np.newaxis] * axes.data return cls(axangle_data)
def test_project(self): """Projecting cartesian coordinates to Gnomonic coordinates yields expected result. """ v = np.array([ [0.5901, 0.8439, 0.4139], [0.4994, 0.744, 0.6386], [0.8895, 0.7788, 0.5597], [0.6673, 0.8619, 0.6433], [0.7605, 0.0647, 0.9849], [0.5852, 0.3946, 0.5447], [0.4647, 0.7181, 0.6571], [0.8806, 0.5106, 0.6244], [0.0816, 0.4489, 0.0296], [0.7585, 0.1885, 0.2678], ]) xy = GnomonicProjection.vector2xy(v) # Desired project result? assert np.allclose( xy, np.array([ [1.42, 2.03], [0.78, 1.16], [1.58, 1.39], [1.03, 1.33], [0.77, 0.06], [1.07, 0.72], [0.70, 1.09], [1.41, 0.81], [2.75, 15.14], [2.83, 0.70], ]), atol=1e-1, ) # Same result passing Vector3d assert np.allclose(xy, GnomonicProjection.vector2xy(Vector3d(v)))
def test_spherical_projection(): """Compared against tests in orix.""" n = 10 v_arr = np.random.random_sample(n * 3).reshape((n, 3)) v = Vector3d(v_arr) # Vector3d polar = SphericalProjection.vector2xy(v_arr) assert np.allclose(polar[..., 0], v.polar.data) assert np.allclose(polar[..., 1], v.azimuth.data) assert np.allclose(polar[..., 2], v.radial.data) assert np.allclose(get_polar(v), v.polar.data) assert np.allclose(get_azimuth(v), v.azimuth.data) assert np.allclose(get_radial(v), v.radial.data) # NumPy array polar2 = SphericalProjection.vector2xy(v) assert np.allclose(polar2[..., 0], v.polar.data) assert np.allclose(polar2[..., 1], v.azimuth.data) assert np.allclose(polar2[..., 2], v.radial.data) assert np.allclose(get_polar(v_arr), v.polar.data) assert np.allclose(get_azimuth(v_arr), v.azimuth.data) assert np.allclose(get_radial(v_arr), v.radial.data)
def test_spherical_projection(): """Compared against tests in orix.""" n = 10 v_arr = np.random.random_sample(n * 3).reshape((n, 3)) v = Vector3d(v_arr) # Vector3d polar = SphericalProjection.project(v_arr) assert np.allclose(polar[..., 0], v.theta.data) assert np.allclose(polar[..., 1], v.phi.data) assert np.allclose(polar[..., 2], v.r.data) assert np.allclose(get_theta(v), v.theta.data) assert np.allclose(get_phi(v), v.phi.data) assert np.allclose(get_r(v), v.r.data) # NumPy array polar2 = SphericalProjection.project(v) assert np.allclose(polar2[..., 0], v.theta.data) assert np.allclose(polar2[..., 1], v.phi.data) assert np.allclose(polar2[..., 2], v.r.data) assert np.allclose(get_theta(v_arr), v.theta.data) assert np.allclose(get_phi(v_arr), v.phi.data) assert np.allclose(get_r(v_arr), v.r.data)