Source code for tensorbay.geometry.vector

#!/usr/bin/env python3
#
# Copyright 2021 Graviti. Licensed under MIT License.
#

"""The implementation of the TensorBay vector."""

from itertools import zip_longest
from math import hypot, sqrt
from sys import version_info
from typing import Dict, Iterable, Mapping, Optional, Sequence, Tuple, Type, TypeVar, Union

from tensorbay.utility import ReprType, UserSequence

if version_info >= (3, 8):
    # math.hypot method supports n-dimensional points in Python3.8
    # only the two dimensional case was supported before Python3.8
    _hypot_for_n_dimension = hypot
else:

    def _hypot_for_n_dimension(*coordinates: float) -> float:  # type: ignore[misc]
        return sqrt(sum(x * x for x in coordinates))


_V = TypeVar("_V", bound="Vector")
_V2 = TypeVar("_V2", bound="Vector2D")
_V3 = TypeVar("_V3", bound="Vector3D")

_T = Union["Vector2D", "Vector3D"]


[docs]class Vector(UserSequence[float]): """This class defines the basic concept of Vector. :class:`Vector` contains the coordinates of a 2D vector or a 3D vector. Arguments: x: The x coordinate of the vector. y: The y coordinate of the vector. z: The z coordinate of the vector. Examples: >>> Vector(1, 2) Vector2D(1, 2) >>> Vector(1, 2, 3) Vector3D(1, 2, 3) """ _data: Tuple[float, ...] _repr_type = ReprType.INSTANCE _DIMENSION: Optional[int] = None def __new__( # type: ignore[misc] # pylint: disable=unused-argument cls: Type[_V], x: float, y: float, z: Optional[float] = None, ) -> _T: """Create a new instance of :class:`Vector`. Arguments: x: The x coordinate of the vector. y: The y coordinate of the vector. z: The z coordinate of the vector. Returns: The created :class:`Vector2D` or :class:`Vector3D` object. """ ReturnType = Vector2D if z is None else Vector3D obj: _T = object.__new__(ReturnType) return obj def __bool__(self) -> bool: return any(self._data) def __neg__(self: _V) -> _V: result: _V = object.__new__(self.__class__) result._data = tuple(-coordinate for coordinate in self._data) return result def __add__(self: _V, other: Iterable[float]) -> _V: """Calculate the sum of the vector and other vector. Arguments: other: The added vector. Returns: The sum vector of adding two vectors. """ try: result: _V = object.__new__(self.__class__) result._data = tuple(i + j for i, j in zip_longest(self._data, other)) return result except TypeError: return NotImplemented def __radd__(self: _V, other: Sequence[float]) -> _V: """Calculate the sum of the vector and the other vector. Arguments: other: The added vector. Returns: The sum vector of adding two vectors. """ return self.__add__(other) def __sub__(self: _V, other: Iterable[float]) -> _V: try: result: _V = object.__new__(self.__class__) result._data = tuple(i - j for i, j in zip_longest(self._data, other)) return result except TypeError: return NotImplemented def __rsub__(self: _V, other: Iterable[float]) -> _V: try: result: _V = object.__new__(self.__class__) result._data = tuple(i - j for i, j in zip_longest(other, self._data)) return result except TypeError: return NotImplemented def __mul__(self: _V, other: float) -> _V: try: if isinstance(other, (int, float)): result: _V = object.__new__(self.__class__) result._data = tuple(i * other for i in self._data) return result except TypeError: pass return NotImplemented def __rmul__(self: _V, other: float) -> _V: return self.__mul__(other) def __truediv__(self: _V, other: float) -> _V: try: if isinstance(other, (int, float)): result: _V = object.__new__(self.__class__) result._data = tuple(i / other for i in self._data) return result except TypeError: pass return NotImplemented def __floordiv__(self: _V, other: float) -> _V: try: if isinstance(other, (int, float)): result: _V = object.__new__(self.__class__) result._data = tuple(i // other for i in self._data) return result except TypeError: pass return NotImplemented def __abs__(self) -> float: return _hypot_for_n_dimension(*self._data) def _repr_head(self) -> str: return f"{self.__class__.__name__}{self._data}"
[docs] @staticmethod def loads(contents: Mapping[str, float]) -> _T: """Loads a :class:`Vector` from a dict containing coordinates of the vector. Arguments: contents: A dict containing coordinates of the vector. Returns: The loaded :class:`Vector2D` or :class:`Vector3D` object. Examples: >>> contents = {"x": 1.0, "y": 2.0} >>> Vector.loads(contents) Vector2D(1.0, 2.0) >>> contents = {"x": 1.0, "y": 2.0, "z": 3.0} >>> Vector.loads(contents) Vector3D(1.0, 2.0, 3.0) """ if "z" in contents: return Vector3D.loads(contents) return Vector2D.loads(contents)
[docs]class Vector2D(Vector): """This class defines the concept of Vector2D. :class:`Vector2D` contains the coordinates of a 2D vector. Arguments: x: The x coordinate of the 2D vector. y: The y coordinate of the 2D vector. Examples: >>> Vector2D(1, 2) Vector2D(1, 2) """ _DIMENSION = 2 def __new__( # pylint: disable=unused-argument cls: Type[_V2], *args: float, **kwargs: float ) -> _V2: """Create a :class:`Vector2D` instance. Arguments: args: The coordinates of the 2D vector. kwargs: The coordinates of the 2D vector. Returns: The created :class:`Vector2D` instance. """ obj: _V2 = object.__new__(cls) return obj def __init__(self, x: float, y: float) -> None: self._data = (x, y) def __abs__(self) -> float: return hypot(*self._data)
[docs] @classmethod def loads(cls: Type[_V2], contents: Mapping[str, float]) -> _V2: """Load a :class:`Vector2D` object from a dict containing coordinates of a 2D vector. Arguments: contents: A dict containing coordinates of a 2D vector. Returns: The loaded :class:`Vector2D` object. Examples: >>> contents = {"x": 1.0, "y": 2.0} >>> Vector2D.loads(contents) Vector2D(1.0, 2.0) """ return cls(**contents)
@property def x(self) -> float: """Return the x coordinate of the vector. Returns: X coordinate in float type. Examples: >>> vector_2d = Vector2D(1, 2) >>> vector_2d.x 1 """ return self._data[0] @property def y(self) -> float: """Return the y coordinate of the vector. Returns: Y coordinate in float type. Examples: >>> vector_2d = Vector2D(1, 2) >>> vector_2d.y 2 """ return self._data[1]
[docs] def dumps(self) -> Dict[str, float]: """Dumps the vector into a dict. Returns: A dict containing the vector coordinate. Examples: >>> vector_2d = Vector2D(1, 2) >>> vector_2d.dumps() {'x': 1, 'y': 2} """ return {"x": self._data[0], "y": self._data[1]}
[docs]class Vector3D(Vector): """This class defines the concept of Vector3D. :class:`Vector3D` contains the coordinates of a 3D Vector. Arguments: x: The x coordinate of the 3D vector. y: The y coordinate of the 3D vector. z: The z coordinate of the 3D vector. Examples: >>> Vector3D(1, 2, 3) Vector3D(1, 2, 3) """ _DIMENSION = 3 def __new__( # pylint: disable=unused-argument cls: Type[_V3], *args: float, **kwargs: float ) -> _V3: """Create a :class:`Vector3D` instance. Arguments: args: The coordinates of the 3D vector. kwargs: The coordinates of the 3D vector. Returns: The created :class:`Vector3D` instance. """ obj: _V3 = object.__new__(cls) return obj def __init__(self, x: float, y: float, z: float) -> None: self._data = (x, y, z)
[docs] @classmethod def loads(cls: Type[_V3], contents: Mapping[str, float]) -> _V3: """Load a :class:`Vector3D` object from a dict containing coordinates of a 3D vector. Arguments: contents: A dict contains coordinates of a 3D vector. Returns: The loaded :class:`Vector3D` object. Examples: >>> contents = {"x": 1.0, "y": 2.0, "z": 3.0} >>> Vector3D.loads(contents) Vector3D(1.0, 2.0, 3.0) """ return cls(**contents)
@property def x(self) -> float: """Return the x coordinate of the vector. Returns: X coordinate in float type. Examples: >>> vector_3d = Vector3D(1, 2, 3) >>> vector_3d.x 1 """ return self._data[0] @property def y(self) -> float: """Return the y coordinate of the vector. Returns: Y coordinate in float type. Examples: >>> vector_3d = Vector3D(1, 2, 3) >>> vector_3d.y 2 """ return self._data[1] @property def z(self) -> float: """Return the z coordinate of the vector. Returns: Z coordinate in float type. Examples: >>> vector_3d = Vector3D(1, 2, 3) >>> vector_3d.z 3 """ return self._data[2]
[docs] def dumps(self) -> Dict[str, float]: """Dumps the vector into a dict. Returns: A dict containing the vector coordinates. Examples: >>> vector_3d = Vector3D(1, 2, 3) >>> vector_3d.dumps() {'x': 1, 'y': 2, 'z': 3} """ return {"x": self._data[0], "y": self._data[1], "z": self._data[2]}