# -*- coding: utf-8 -*-
"""
==================================================================
Custom NumPy Vector class (:mod:`sknano.core.math._vector`)
==================================================================
.. currentmodule:: sknano.core.math._vector
"""
from __future__ import absolute_import, division, print_function
__docformat__ = 'restructuredtext en'
import numbers
import numpy as np
from ._point import Point
from ._transforms import rotate, transformation_matrix
__all__ = ['Vector', 'angle', 'cross', 'dot', 'scalar_triple_product',
'vector_triple_product']
[docs]class Vector(np.ndarray):
"""Abstract object representation of a vector in :math:`R^n`
Parameters
----------
v : array_like, optional
components of vector
nd : {None, int}, optional
p0 : array_like, optional
Origin `Point` of vector in :math:`R^n` space.
p : array_like, optional
Terminating `Point` of vector.
:math:`x, y` coordinates of point in :math:`R^2` space.
:math:`x, y, z` coordinates of point in :math:`R^3` space.
dtype : data-type, optional
copy : bool, optional
Notes
-----
.. todo::
add new methods for coordinate transformations
Examples
--------
I began writing my own `Point` and `Vector` classes while trying to
teach myself about subclassing :class:`~numpy:numpy.ndarray`.
I still don't completely understand the machinery of it, but I ended
up with some code that's been useful for handling math operations involving
points and vectors. It could use some more testing and there are
some math operations don't behave as expected on account of my
lack of understanding on how the who subclassing thing works.
Here are some examples of their use.
"""
__array_priority__ = 15.0
__hash__ = np.ndarray.__hash__
_verbosity = 0
def __new__(cls, v=None, nd=None, p0=None, p=None, dtype=None, copy=True):
if Vector._verbosity > 0:
print('In Vector.__new__\n'
'cls: {}\n'.format(cls) +
'v: {}\n'.format(v) +
'type(v): {}\n'.format(type(v)))
if isinstance(v, Vector):
if dtype is None:
intype = v.dtype
else:
intype = np.dtype(dtype)
vec = v.view(cls)
if p0 is not None:
vec = Vector(np.asarray(vec), p0=p0)
if intype != v.dtype:
return vec.astype(intype)
if copy:
return vec.copy()
else:
return vec
dtype = np.dtype(dtype)
if isinstance(v, (tuple, list, np.ndarray)):
try:
for i, coord in enumerate(v[:]):
if coord is None:
v[i] = 0.0
except TypeError:
v = np.zeros(len(v), dtype=dtype)
else:
v = np.asarray(v, dtype=dtype)
nd = len(v)
if p0 is None:
p0 = Point(nd=nd, dtype=dtype)
else:
p0 = Point(p0, nd=nd, dtype=dtype)
p = p0 + v
else:
if nd is None or not isinstance(nd, numbers.Number):
nd = 3
if p is None:
p = Point(nd=nd, dtype=dtype)
else:
p = Point(p, nd=nd, dtype=dtype)
if p0 is None:
p0 = Point(nd=nd, dtype=dtype)
else:
p0 = Point(p0, nd=nd, dtype=dtype)
v = p - p0
arr = np.array(v, dtype=dtype, copy=copy).view(cls)
#vec = np.ndarray.__new__(cls, arr.shape, arr.dtype, buffer=arr)
vec = super(Vector, cls).__new__(cls, arr.shape, arr.dtype, buffer=arr)
vec.nd = nd
vec._p = p
vec._p0 = p0
if nd == 2:
vec.x, vec.y = vec
elif nd == 3:
vec.x, vec.y, vec.z = vec
return vec
def __array_finalize__(self, obj):
if obj is None:
return None
self.nd = len(obj)
if Vector._verbosity > 2:
try:
print('In Vector.__array_finalize__\n'
'self: {}\n'.format(self) +
'type(self): {}\n'.format(type(self)) +
'self.p: {}\n'.format(self.p) +
'type(self.p): {}\n'.format(type(self.p)) +
'self.p0: {}\n'.format(self.p0) +
'type(self.p0): {}\n'.format(type(self.p0)) +
'obj: {}\n'.format(obj) +
'type(obj): {}\n'.format(type(obj)) +
'obj.p: {}\n'.format(obj.p) +
'type(obj.p): {}\n'.format(type(obj.p)) +
'obj.p0: {}\n'.format(obj.p0) +
'type(obj.p0): {}\n'.format(type(obj.p0)) +
'obj.shape: {}\n'.format(obj.shape))
except AttributeError:
pass
if self.nd == 2:
self.x, self.y = obj
elif self.nd == 3:
self.x, self.y, self.z = obj
self._p0 = getattr(obj, 'p0', None)
self._p = getattr(obj, 'p', None)
if self._p0 is not None and self._p is None:
try:
self._p = self._p0 + self.__array__()
except TypeError:
try:
self._p = self._p0 + np.asarray(obj)
except TypeError:
pass
def __array_prepare__(self, obj, context=None):
if Vector._verbosity > 2:
print('In Vector.__array_prepare__\n'
'self: {}\n'.format(self) +
'type(self): {}\n'.format(type(self)) +
'obj: {}\n'.format(obj) +
'type(obj): {}\n'.format(type(obj)) +
'context: {}\n'.format(context))
if self.__array_priority__ >= Vector.__array_priority__:
ret = obj if isinstance(obj, type(self)) else obj.view(type(self))
else:
ret = obj.view(Vector)
if context is None:
return ret
return super(Vector, self).__array_prepare__(obj, context)
def __array_wrap__(self, obj, context=None):
if Vector._verbosity > 2:
try:
print('In Vector.__array_wrap__\n'
'self: {}\n'.format(self) +
'type(self): {}\n'.format(type(self)) +
'self.p: {}\n'.format(self.p) +
'type(self.p): {}\n'.format(type(self.p)) +
'self.p0: {}\n'.format(self.p0) +
'type(self.p0): {}\n'.format(type(self.p0)) +
'obj: {}\n'.format(obj) +
'type(obj): {}\n'.format(type(obj)) +
'obj.p: {}\n'.format(obj.p) +
'type(obj.p): {}\n'.format(type(obj.p)) +
'obj.p0: {}\n'.format(obj.p0) +
'type(obj.p0): {}\n'.format(type(obj.p0)) +
'context: {}\n'.format(context))
except TypeError:
pass
#if obj.shape == ():
# return obj[()]
#else:
res = super(Vector, self).__array_wrap__(obj, context)
res = Vector(res.__array__(), p0=self.p0)
#res.p = self.p
#res = Vector(p0=self.p0, p=self.p)
#res = Vector(res.__array__())
if Vector._verbosity > 0:
print('In Vector.__array_wrap__\n'
'self: {}\n'.format(self) +
'type(self): {}\n'.format(type(self)) +
'self.p: {}\n'.format(self.p) +
'type(self.p): {}\n'.format(type(self.p)) +
'self.p0: {}\n'.format(self.p0) +
'type(self.p0): {}\n'.format(type(self.p0)) +
'obj: {}\n'.format(obj) +
'type(obj): {}\n'.format(type(obj)) +
'obj.p: {}\n'.format(obj.p) +
'type(obj.p): {}\n'.format(type(obj.p)) +
'obj.p0: {}\n'.format(obj.p0) +
'type(obj.p0): {}\n'.format(type(obj.p0)) +
'res: {}\n'.format(res) +
'type(res): {}\n'.format(type(res)) +
'res.p: {}\n'.format(res.p) +
'type(res.p): {}\n'.format(type(res.p)) +
'res.p0: {}\n'.format(res.p0) +
'type(res.p0): {}\n'.format(type(res.p0)) +
'context: {}\n'.format(context))
#return super(Vector, self).__array_wrap__(obj, context)
return res
def __str__(self):
return repr(self)
def __repr__(self):
try:
if np.allclose(self.p0, np.zeros_like(self.p0)):
return "Vector({!r})".format(self.__array__().tolist())
else:
return "Vector({!r}, p0={!r}, p={!r})".format(
self.__array__().tolist(),
self.p0.tolist(), self.p.tolist())
except AttributeError:
return "Vector({!r})".format(self.__array__().tolist())
def __getattr__(self, name):
if Vector._verbosity > 4:
print('In Vector.__getattr__\n'
'self: {}\n'.format(self.__array__()) +
'name: {}\n'.format(name))
try:
nd = len(self)
if nd == 2 and name in ('x', 'y'):
if name == 'x':
return self[0]
else:
return self[1]
elif nd == 3 and name in ('x', 'y', 'z'):
if name == 'x':
return self[0]
elif name == 'y':
return self[1]
else:
return self[2]
except TypeError:
pass
return super(Vector, self).__getattribute__(name)
def __setattr__(self, name, value):
if Vector._verbosity > 3:
try:
print('In Vector.__setattr__\n'
'self: {}\n'.format(self) +
'type(self): {}\n'.format(type(self)) +
'name: {}\n'.format(name) +
'value: {}\n'.format(value))
except TypeError:
pass
#nd = len(self)
nd = getattr(self, 'nd', None)
if nd is not None and nd == 2 and name in ('x', 'y'):
if name == 'x':
self[0] = value
try:
self._p.x = self._p0.x + value
except AttributeError:
pass
else:
self[1] = value
try:
self._p.y = self._p0.y + value
except AttributeError:
pass
elif nd is not None and nd == 3 and name in ('x', 'y', 'z'):
if name == 'x':
self[0] = value
try:
self._p.x = self._p0.x + value
except AttributeError:
pass
elif name == 'y':
self[1] = value
try:
self._p.y = self._p0.y + value
except AttributeError:
pass
else:
self[2] = value
try:
self._p.z = self._p0.z + value
except AttributeError:
pass
else:
super(Vector, self).__setattr__(name, value)
#if name is not None and name.endswith('p'):
# try:
# self[:] = self._p.__array__() - self._p0.__array__()
# except (AttributeError, TypeError):
# pass
def __iadd__(self, other):
"""Add other to self in-place."""
super(Vector, self).__iadd__(other)
self._update_p()
return self
def __isub__(self, other):
"""Subtract other from self in-place."""
super(Vector, self).__isub__(other)
self._update_p()
return self
def __imul__(self, other):
super(Vector, self).__imul__(other)
self._update_p()
return self
def __idiv__(self, other):
super(Vector, self).__idiv__(other)
self._update_p()
return self
def __ifloordiv__(self, other):
super(Vector, self).__ifloordiv__(other)
self._update_p()
return self
def __itruediv__(self, other):
super(Vector, self).__itruediv__(other)
self._update_p()
return self
def __ipow__(self, other):
super(Vector, self).__ipow__(other)
self._update_p()
return self
def __eq__(self, other):
if self is other or (np.allclose(self, other) and
np.allclose(self.p0, other.p0) and
np.allclose(self.p, other.p)):
return True
else:
return False
def __lt__(self, other):
return self.norm < other.norm
def __le__(self, other):
return self < other or self == other
def __gt__(self, other):
return not (self < other or self == other)
def __ge__(self, other):
return not (self < other)
def __ne__(self, other):
return not self == other
def _update_p(self):
self._p[:] = self._p0[:] + self.__array__()
@property
def length(self):
"""Alias for :attr:`Vector.norm`."""
return self.norm
@property
def unit_vector(self):
"""Unit vector for `Vector`."""
return self / self.norm
@property
def magnitude(self):
"""Alias for :attr:`Vector.norm`."""
return self.norm
@property
def mag(self):
"""Alias for :attr:`Vector.norm`."""
return self.norm
@property
def norm(self):
"""Return the vector norm."""
return np.sqrt((self**2).sum())
@property
def p(self):
"""Terminating :class:`Point` of vector."""
return self._p
@p.setter
def p(self, value=np.ndarray):
"""Set new terminating :class:`Point` of vector."""
self._p[:] = value
self[:] = self._p - self._p0
@property
def p0(self):
"""Origin :class:`Point` of vector."""
return self._p0
@p0.setter
def p0(self, value=np.ndarray):
"""Set new origin :class:`Point` of vector."""
self._p0[:] = value
self[:] = self._p - self._p0
def _translate_p0(self, t, fix_components=False):
if fix_components:
self.translate(t)
else:
self.p0.translate(t)
def _translate_p(self, t, fix_components=False):
if fix_components:
self.translate(t)
else:
self.p.translate(t)
[docs] def dot(self, other, out=None):
"""Computes dot product of two vectors."""
return self.__array__().dot(other.__array__())
[docs] def normalize(self):
"""Normalize the `Vector`."""
self[:] = self / self.length
[docs] def projection(self, v):
"""Compute vector projection onto vector `v`."""
u = self
return dot(u, v) / dot(v, v) * v
[docs] def rezero_components(self, epsilon=1.0e-10):
"""Alias for :meth:`Vector.rezero`"""
self.rezero(epsilon=epsilon)
[docs] def rezero(self, epsilon=1.0e-10):
"""Re-zero `Vector` coordinates near zero.
Set `Vector` coordinates with absolute value less than `epsilon` to
zero.
Parameters
----------
epsilon : float, optional
Smallest allowed absolute value of any :math:`x,y,z` coordinate.
"""
self[np.where(np.abs(self.__array__()) <= epsilon)] = 0.0
[docs] def rotate(self, angle=None, rot_axis=None, anchor_point=None,
rot_point=None, from_vector=None, to_vector=None,
deg2rad=False, transform_matrix=None, verbose=False):
"""Rotate `Vector` coordinates.
Parameters
----------
angle : float
rot_axis : :class:`~sknano.core.math.Vector`, optional
anchor_point : :class:`~sknano.core.math.Point`, optional
rot_point : :class:`~sknano.core.math.Point`, optional
from_vector, to_vector : :class:`~sknano.core.math.Vector`, optional
deg2rad : bool, optional
transform_matrix : :class:`~numpy:numpy.ndarray`
"""
if transform_matrix is None:
transform_matrix = \
transformation_matrix(angle=angle, rot_axis=rot_axis,
anchor_point=anchor_point,
rot_point=rot_point,
from_vector=from_vector,
to_vector=to_vector, deg2rad=deg2rad,
verbose=verbose)
self.p0 = rotate(self.p0, transform_matrix=transform_matrix)
self.p = rotate(self.p, transform_matrix=transform_matrix)
[docs] def scale(self):
return NotImplemented
[docs] def translate(self, t, fix_anchor_point=False):
"""Translate `Vector` coordinates by :class:`Vector` `t`.
Parameters
----------
t : :class:`Vector`
fix_anchor_point : bool, optional
"""
self.p += t
if not fix_anchor_point:
self.p0 += t
def angle(u, v):
"""Compute the angle between two Cartesian vectors.
Parameters
----------
u, v : `Vector`
Returns
-------
:class:`~numpy:numpy.number`
"""
return np.arccos(dot(u, v) / (u.norm * v.norm))
def cross(u, v, p0=None):
"""Vector cross product of two vectors.
Parameters
----------
u, v : `Vector`
p0 : `Point`, optional
Returns
-------
np.number or `Vector`
"""
val = np.cross(np.asarray(u), np.asarray(v))
if p0 is None:
p0 = u.p0
if val.shape == ():
return val[()]
else:
return Vector(val, p0=p0)
def dot(u, v):
"""Dot product of two vectors.
Parameters
----------
u, v : `Vector`
Returns
-------
val
"""
return np.dot(np.asarray(u), np.asarray(v))
def scalar_triple_product(u, v, w):
"""Compute scalar triple product of three vectors.
Parameters
----------
u, v, w : `Vector`
Returns
-------
float
"""
return dot(u, cross(v, w))
def vector_triple_product(u, v, w):
"""Compute vector triple product of three vectors.
Parameters
----------
u, v, w : `Vector`
Returns
-------
`Vector`
"""
return cross(u, cross(v, w))