Source code for sigmaepsilon.math.linalg.vector

"""Vector class built on top of the linalg reference frame and abstract tensor layer."""

from copy import deepcopy as dcopy

from numpy import ndarray
import numbers

from .utils import show_vector
from .frame import ReferenceFrame as Frame
from .abstract import AbstractTensor
from .meta import FrameLike


__all__ = ["Vector"]


[docs] class Vector(AbstractTensor): """Extend `NumPy`'s ``ndarray`` class to handle arrays with associated reference frames. The class also provides a mechanism to transform vectors between different frames. Use it like if it was a ``numpy.ndarray`` instance. All parameters are identical to those of ``numpy.ndarray``, except that this class allows to specify an embedding frame. Parameters ---------- args: tuple, Optional Positional arguments forwarded to `numpy.ndarray`. frame: FrameLike, Optional The reference frame the vector is represented by its coordinates. kwargs: dict, Optional Keyword arguments forwarded to `numpy.ndarray`. Examples -------- Import the necessary classes: >>> import numpy as np >>> from sigmaepsilon.math.linalg import Vector, ReferenceFrame Create a default frame in 3d space, and create 2 others, each being rotated with 30 degrees around the third axis. >>> A = ReferenceFrame(dim=3) >>> B = A.orient_new('Body', [0, 0, 30*np.pi/180], 'XYZ') >>> C = B.orient_new('Body', [0, 0, 30*np.pi/180], 'XYZ') To create a vector in a frame: >>> vA = Vector([1.0, 1.0, 0.0], frame=A) To create a vector with a relative transformation to another one: >>> vB = vA.orient_new('Body', [0, 0, -30*np.pi/180], 'XYZ') Use the `array` property to get the componets of a `Vector`: >>> vB.array Array([1.3660254, 0.3660254, 0. ]) If you want to obtain the components of a vector in a specific target frame C, do this: >>> vB.show(C) array([ 1., -1., 0.]) The reason why the result is represented now as 'array' insted of 'Array' as in the previous case is that the Vector class is an array container. When you type `vB.array`, what is returned is a wrapped object, an instance of `Array`, which is also a class of this library. When you say `vB.show(C)`, a NumPy array is returned. Since the `Array` class is a direct subclass of NumPy's `ndarray` class, it doesn't really matter and the only difference is the capital first letter. To create a vector in a target frame C, knowing the components in a source frame A: >>> vC = Vector(vA.show(C), frame=C) See Also -------- :class:`~sigmaepsilon.math.linalg.tensor.Tensor` :class:`~sigmaepsilon.math.linalg.frame.ReferenceFrame` """ _frame_cls_ = Frame _HANDLED_TYPES_ = (numbers.Number,) def __init__( self, *args, frame: FrameLike | None = None, **kwargs, ): super().__init__(*args, frame=frame, **kwargs) @classmethod def _verify_input(cls, arr: ndarray, *_, **kwargs) -> bool: """Verify if an array input is acceptable for the current class. If not a general Tensor class is returned upon calling the creator. """ return True @property def rank(self) -> int: """Return the tensor rank (or order).""" return 1
[docs] def dual(self) -> "Vector": """Return the vector described in the dual (or reciprocal) frame.""" # NOTE Strictly this should be self.frame.Gram().T @ self.array, # but since the Gram matrix is symmetric, it's cheaper like this a = self.frame.Gram() @ self.array return self.__class__(a, frame=self.frame.dual())
[docs] def show(self, target: Frame = None, *, dcm: ndarray = None) -> ndarray: """Return the components in a target frame. If the target is `None`, the components are returned in the ambient frame. The transformation can also be specified with a proper DCM matrix. Parameters ---------- target: numpy.ndarray, Optional Target frame. dcm: numpy.ndarray, Optional The DCM matrix of the transformation. Returns ------- numpy.ndarray The components of the vector in a specified frame, or the ambient frame, depending on the arguments. """ if not isinstance(dcm, ndarray): if target is None: target = self._frame_cls_(dim=self._array.shape[-1]) dcm = self.frame.dcm(target=target) return show_vector(dcm, self.array) # dcm @ arr
[docs] def orient(self, *args, dcm: ndarray = None, **kwargs) -> "Vector": """Orient the vector inplace. If the transformation is not specified by 'dcm', all arguments are forwarded to `orient_new`. Parameters ---------- dcm: numpy.ndarray, Optional The DCM matrix of the transformation. Returns ------- Vector The same vector the function is called upon. See Also -------- :func:`orient_new` """ if not isinstance(dcm, ndarray): fcls = self.__class__._frame_cls_ dcm = fcls.eye(dim=len(self)).orient_new(*args, **kwargs).dcm() # self.array = dcm.T @ self._array self.array = show_vector(dcm.T, self.array) # self.array = np.linalg.inv(dcm) @ self._array # FIXME check this else: self.array = show_vector(dcm.T, self.array) # self.array = dcm.T @ self._array # FIXME check if inversion is necessary here # inversion might be necessary here because it is uncertain if the # dcm matrix was fabricated properly. # self.array = np.linalg.inv(dcm) @ self._array return self
[docs] def orient_new(self, *args, **kwargs) -> "Vector": """Return a transformed version of the instance. Returns ------- Vector A new vector. See Also -------- :func:`orient` """ fcls = self.__class__._frame_cls_ dcm = fcls.eye(dim=len(self)).orient_new(*args, **kwargs).dcm() array = dcm.T @ self._array # FIXME check if inversion is necessary or not # array = np.linalg.inv(dcm) @ self._array return Vector(array, frame=self.frame)
[docs] def copy(self, deep: bool = False, name: str = None) -> "Vector": """Return a shallow or deep copy of this object, depending of the argument `deepcopy`. Default is False. """ if deep: return self.__class__(dcopy(self.array), name=name) else: return self.__class__(self.array, name=name)
[docs] def deepcopy(self, name: str = None) -> "Vector": """Return a deep copy of the frame.""" return self.copy(deep=True, name=name)