Source code for irfpy.util.vector3d

''' Three-dimension double-precision vector implementation

This is an implementation of Vector3d class.  API is refered to Java3D_.

.. _Java3D: https://java3d.dev.java.net/

.. note::

    The module is left for compatibility (some other modules depend
    on this implementation).
    For developing new module, there are no reasons of using this module.
    Use ``numpy.array`` for normal purpose for vector manipulations
    
.. codeauthor:: Yoshifumi Futaana
'''

from math import *
import logging
_logger = logging.getLogger(__name__)

import warnings
warnings.warn('''The vector3d module is left for compatibility
(some other modules depend on this implementation).
For developing new module, there are no reasons of using this module.
Use ``numpy.array`` for normal purpose for vector manipulations''',
DeprecationWarning)

[docs]class Vector3d: ''' Double precision of 3-d vector, constituting of *x*, *y*, and *z*. :param x: *x* component of the vector. :type x: float :param y: *y* component of the vector. :type y: float :param z: *z* component of the vector. :type z: float Simple to instance: >>> v = Vector3d(5, 1, 2) >>> print(v) Vector3d( 5, 1, 2 ) Only float is acceptable as arguments. The :meth:`clone` method deep-copies the vector. Substite is reference copy. >>> v1 = v >>> id(v1) == id(v) True >>> v2 = v.clone() >>> id(v2) == id(v) False >>> v2.equals(v) True Based on Java3D_, no accessor methods are available. Thus, cccessing the element is using *.x*, *.y*, and *.z* >>> print(v.x) 5 Apart from Java3D_, you can also get by index 0, 1, and 2. This enable to use this class as array-like. >>> print(v[2]) 2 But not that this index-based access is slower than the direct access by a factor of 2. You can change the value by accessing .x, .y and .z >>> v.y = -1 >>> print(v) Vector3d( 5, -1, 2 ) This means that the Vector3d class is not immutable. Thus, this instance cannot be used as a key of a dictionary. Comparison is not defined. :meth:`__eq__`, :meth:`__ne__`, :meth:`__cmp__`, or any other comparator is not implemented. :meth:`equals` and :meth:`epsilonEquals` support checking equality by each component. The API is based on Java3D_ API, thus methods are not intuitive. For example, operaters like :math:`v_1+v_2` are not supported. This isa Java3D_ policy: Method should not instance, but user should instance by himself. ''' def __init__(self, x=0, y=0, z=0): ''' Constructor specifying all the argument. Deafult value is (0,0,0) ''' self.x = x self.y = y self.z = z def __str__(self): return 'Vector3d( %g, %g, %g )' % (self.x, self.y, self.z) def __repr__(self): return 'Vector3d( %g, %g, %g) ' % (self.x, self.y, self.z) def __getitem__(self, key): ''' Overload the [] access. >>> v = Vector3d(1, 3.5, -3) >>> v.x 1 >>> v[0] 1 >>> v[1] 3.5 >>> v[2] -3 ''' if key == 0: return self.x elif key == 1: return self.y elif key == 2: return self.z else: raise IndexError('Vector3d::Out of index.')
[docs] def length(self): r''' Returns the length of this vector. The length of the vector :math:`\mathbf{v}=(v_x, v_y, v_z)` is :math:`\sqrt{v_x^2+v_y^2+v_z^2}` ''' return sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2)
[docs] def lengthSquared(self): ''' Returns the squared length of this vector ''' return self.x ** 2 + self.y ** 2 + self.z ** 2
[docs] def angle(self, v1): ''' Returns the angle in radians between self vector and given vector >>> v1 = Vector3d(1, 0, 0) >>> v2 = Vector3d(0, 1, 0) >>> print('%.1f' % (v1.angle(v2) * 180. / pi)) 90.0 Note that there happens a rarely numerical effect that exceeds the correct range of calculation. For example, the following calculation will make "ValueError" during cosine operation, unless specially treated. >>> x = 0.939856076502615267465046144935 >>> y = 0.105396307433879829473788447558 >>> z = 0.324903329992804690284913249343 >>> v = Vector3d(x, y, z) >>> v1 = Vector3d(x, y, z) >>> print(v.angle(v1)) 0.0 ''' inp = self.dot(v1) ls = self.length() l1 = v1.length() inp = inp / ls / l1 try: ang = acos(inp) # Theoretically, this should be [-1, 1] return ang except ValueError as e: # In rare case, numerically it violate the range _logger.warning( 'Numerical range error happend for angle calculateion\n' + ('\t%.30f %.30f %.30f\n' % (self.x, self.y, self.z)) + ('\t%.30f %.30f %.30f\n' % (v1.x, v1.y, v1.z)) + ('Inner products = %.30f\nReturning 0 or 180 degs\n' % inp) + 'If the inner products are not very close to 1 or -1,' + 'I should think more carefully....') if inp > 1: return 0. else: return pi
[docs] def dot(self, v1): ''' Returns the inner product of this vector and the given vector ''' return self.x * v1.x + self.y * v1.y + self.z * v1.z
[docs] def cross(self, v1, v2): ''' Sets this vector to the outer product of the given vectors ''' self.x = v1.y * v2.z - v1.z * v2.y self.y = v1.z * v2.x - v1.x * v2.z self.z = v1.x * v2.y - v1.y * v2.x
[docs] def normalize(self, v1=None): ''' Normalize the vector.''' if v1 is None: v1 = self l = v1.length() try: self.x = v1.x / l self.y = v1.y / l self.z = v1.z / l except ZeroDivisionError as e: raise ZeroDivisionError('Failed to normalize zero-length vector.')
[docs] def absolute(self, v1=None): ''' Set the each component of this vector to its aboslute values ''' if v1 is None: v1 = self self.x = abs(v1.x) self.y = abs(v1.y) self.z = abs(v1.z)
[docs] def add(self, v1, v2=None): ''' Sets the value of this vector to the sum of v1 and v2 (or self) ''' if v2 is None: v2 = self self.x = v1.x + v2.x self.y = v1.y + v2.y self.z = v1.z + v2.z
[docs] def sub(self, v1, v2=None): ''' Substitute vectors. Set the self vector to be self=self-v1 if v2 is omitted. Otherwise self=v1-v2 is set. ''' if v2 is None: self.x = self.x - v1.x self.y = self.y - v1.y self.z = self.z - v1.z else: self.x = v1.x - v2.x self.y = v1.y - v2.y self.z = v1.z - v2.z
[docs] def clamp(self, min, max): ''' Clamps the vector to the range [low,high]. ''' self.clampMax(max) self.clampMin(min)
[docs] def clampMax(self, max, v1=None): if v1 is None: v1 = self if v1.x > max: v1.x = max if v1.y > max: v1.y = max if v1.z > max: v1.z = max
[docs] def clampMin(self, min, v1=None): if v1 is None: v1 = self if v1.x < min: v1.x = min if v1.y < min: v1.y = min if v1.z < min: v1.z = min
[docs] def clone(self): return Vector3d(self.x, self.y, self.z)
[docs] def epsilonEquals(self, v1, epsilon): ''' Check whether the given vector is very close to self. Returns True if L-infinite distance between this vector and v1 is less than epsilon. >>> v1 = Vector3d(1.50, 2.80, 3.63) >>> v2 = Vector3d(1.51, 2.82, 3.64) >>> v1.epsilonEquals(v2, 0.1) True >>> v1.epsilonEquals(v2, 0.01) False ''' v3 = [abs(self.x - v1.x), abs(self.y - v1.y), abs(self.z - v1.z)] l = max(v3) _logger.debug('Diff=(%f,%f,%f) => Linf=%f' % (v3[0], v3[1], v3[2], l)) return l < epsilon
[docs] def equals(self, v1): return (self.x == v1.x and self.y == v1.y and self.z == v1.z)
[docs] def getTuple(self): ''' Returns the three-element python tuple. ''' return (self.x, self.y, self.z)
[docs] def interpolate(self, alpha, v1, v2=None): ''' Returns the interplated vector. Returns (1-alpha)*v1 + alpha*v2 or (1-alpha)*self+alpha*v1 Take care, this is different order of argument from Java3D API. ''' if v2 is None: self.x = (1 - alpha) * self.x + alpha * v1.x self.y = (1 - alpha) * self.y + alpha * v1.y self.z = (1 - alpha) * self.z + alpha * v1.z else: self.x = (1 - alpha) * v1.x + alpha * v2.x self.y = (1 - alpha) * v1.y + alpha * v2.y self.z = (1 - alpha) * v1.z + alpha * v2.z
[docs] def negate(self, v1=None): ''' >>> v = Vector3d(1, 2, 3) >>> v.negate() >>> print(v.x) -1 ''' if v1 is None: v1 = self self.x = -v1.x self.y = -v1.y self.z = -v1.z
[docs] def scale(self, s, v1=None): if v1 is None: v1 = self self.x = v1.x * s self.y = v1.y * s self.z = v1.z * s
[docs] def scaleAdd(self, s, v1, v2=None): ''' self = self*s+v1 (v2==None) or self = v1*s+v2 (v2!=None) ''' if v2 is None: self.x = self.x * s + v1.x self.y = self.y * s + v1.y self.z = self.z * s + v1.z else: self.x = v1.x * s + v2.x self.y = v1.y * s + v2.y self.z = v1.z * s + v2.z
[docs] def setTuple(self, tuple): ''' Set a tuple to self. >>> v = Vector3d() >>> id0 = id(v) >>> t = [1, 3, 5] >>> v.setTuple(t) >>> id0 == id(v) True >>> v.x 1 >>> v.y 3 >>> v.z 5 ''' self.x, self.y, self.z = tuple
import unittest import doctest
[docs]def doctests(): return unittest.TestSuite(( doctest.DocTestSuite(), ))
if __name__ == '__main__': unittest.main(defaultTest='doctests')