from dataclasses import dataclass
from math import cos, hypot, sin
from typing import Self
__all__ = ["Vector3D", "dist3"]
[docs]
@dataclass
class Vector3D:
"""
Three-dimensional vector with common operations.
Attributes:
x: :math:`x` component of the vector.
y: :math:`y` component of the vector.
z: :math:`z` component of the vector.
"""
x: float
y: float
z: float
[docs]
@classmethod
def from_two_points(
cls, p1: tuple[float, float, float], p2: tuple[float, float, float]
) -> Self:
"""
Constructor for the vector that joins two points.
Parameters:
p1: Initial point.
p2: End point.
Returns:
The vector that goes from `p1` to `p2`.
"""
return cls(p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2])
[docs]
@classmethod
def cylindrical(cls, r: float, theta: float, z: float) -> Self:
"""
Constructs a vector expressed in cylindrical coordinates.
Parameters:
r: Magnitude of the projection of the vector onto the :math:`xy` plane.
theta: Angle of the projection onto the :math:`xy` plane with respect to
the positive :math:`x`-axis.
z: :math:`z` component of the vector.
Returns:
The corresponding vector.
"""
return cls(r * cos(theta), r * sin(theta), z)
[docs]
@classmethod
def spherical(cls, rho: float, phi: float, theta: float) -> Self:
"""
Constructs a vector expressed in spherical coordinates.
Parameters:
rho: Magnitude of the vector.
phi: Angle formed with the positive :math:`z` axis.
theta: Angle of the projection onto the :math:`xy` plane with respect to
the positive :math:`x`-axis.
Returns:
The corresponding vector.
"""
return cls(
rho * sin(phi) * cos(theta), rho * sin(phi) * sin(theta), rho * cos(theta)
)
def __call__(self) -> tuple[float, float, float]:
"""Converts the coordinates of the vector to a point."""
return (self.x, self.y, self.z)
def __add__(self, other: Self) -> Self:
return self.__class__(self.x + other.x, self.y + other.y, self.z + other.z)
def __iadd__(self, other: Self) -> Self:
self.x += other.x
self.y += other.y
self.z += other.z
return self
def __sub__(self, other: Self) -> Self:
return self.__class__(self.x - other.x, self.y - other.y, self.z - other.z)
def __isub__(self, other: Self) -> Self:
self.x -= other.x
self.y -= other.y
self.z -= other.z
return self
def __mul__(self, alpha: float) -> Self:
return self.__class__(self.x * alpha, self.y * alpha, self.z * alpha)
def __rmul__(self, alpha: float) -> Self:
return self * alpha
def __imul__(self, alpha: float) -> Self:
self.x *= alpha
self.y *= alpha
self.z *= alpha
return self
def __truediv__(self, alpha: float) -> Self:
return self.__class__(self.x / alpha, self.y / alpha, self.z / alpha)
def __itruediv__(self, alpha: float) -> Self:
self.x /= alpha
self.y /= alpha
self.z /= alpha
return self
def __matmul__(self, other: Self) -> float:
"""Standard inner product."""
return self.x * other.x + self.y * other.y + self.z * other.z
def __xor__(self, other: Self) -> Self:
"""Cross product."""
return self.__class__(
self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z,
self.x * other.y - self.y * other.x,
)
def __ixor__(self, other: Self) -> Self:
x, y, z = self.x, self.y, self.z
self.x = y * other.z - z * other.y
self.y = z * other.x - x * other.z
self.z = x * other.y - y * other.x
return self
def __neg__(self) -> Self:
return self.__class__(-self.x, -self.y, -self.z)
def __abs__(self) -> float:
"""Magnitude of the vector."""
return hypot(self.x, self.y, self.z)
def __or__(self: Self, other: Self) -> bool:
"""Check whether two vectors are parallel."""
if self() == (0, 0) or other() == (0, 0):
return False
if self.x == 0 and other.x != 0:
return False
if self.y == 0 and other.y != 0:
return False
if self.z == 0 and other.z != 0:
return False
qs = [
o / s
for s, o in ((self.x, other.x), (self.y, other.y), (self.z, other.z))
if s != 0
]
return all(qi == qs[0] for qi in qs)
@property
def unitary(self) -> Self:
"""
Vector normalized to have unit norm.
"""
return self / abs(self)
[docs]
def dist3(p1: tuple[float, float, float], p2: tuple[float, float, float]) -> float:
r"""
Computes the Euclidean distance between two points in :math:`\mathbf R^3`.
Parameters:
p1: First point.
p2: Second point.
"""
return hypot(p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2])