Vector API Reference#

The Vector Type#

class pyevspace.Vector#
class pyevspace.Vector(x: SupportsFloat, y: SupportsFloat, z: SupportsFloat)
class pyevspace.Vector(container: Iterable)

The Vector type represents a vector of a 3-dimensional vector space over the real numbers. Vectors are mathematical objects that describe a length and direction. The three components of a 3D vector describe how many units the vector points in each direction of the x, y, and z axis of a reference frame. The distance from the origin to the (x, y, z) coordinate is the length of the vector. Vectors can represent positions in space, as well as other physical quantities such as velocities, accelerations, and forces. For example, a force vector’s length represents the quantity of the force (in units of force, like Newtons or pounds), and the direction of the vector represents the direction the force is applied.

The vector is the basic unit of PyEVSpace. The Vector type defines most arithmetic operators, as well as many methods useful for working with vectors, such as dot and cross product, or projecting one vector onto another. Vectors can also be rotated, within a reference frame or to or from another reference frame. The Matrix is used for such cases. While matrices can directly be used to apply transformations to vectors, the library also provides several functions for handling these rotation for you.

A vector can be initialized by specifying its components directly, or inside an iterable container that contains exactly those three elements. By not supplying any arguments the vector components will be initialized to zero. If initializing by provided components, all components must be specified.

Direct elements or elements of container must be a float, or compatible with SupportsFloat, meaning they can be converted to a float by defining a __float__() method.

Changed in version 0.16.0: Vector is now subclassable, and is no longer an immutable type, meaning class members can be directly modified or added.

Parameters:
  • x – value to initialize the x-component to

  • y – value to initialize the y-component to

  • z – value to initialize the z-component to

  • container – an iterable object of length three with elements compatible with SupportsFloat (this must be the only parameter if present)

Raises:

Other Constructors#

classmethod Vector.__new__(cls: type) Self#

Creates a new, uninitialized cls object if cls is a subclass of Vector. All components of the vector default to zero.

Parameters:

cls – must be a subtype of Vector

Returns:

an uninitialized Vector object

Raises:

TypeError – if cls is not a subtype of Vector

Changed in version 0.16.0: type can be a subtype of Vector, and an object of that type will be created and returned.

Vector.__init__()#
Vector.__init__(x: SupportsFloat, y: SupportsFloat, z: SupportsFloat)
Vector.__init__(container: Iterable)

Initialize a Vector depending on the arguments. This method and arguments behave identically to the Vector constructor.

Parameters:
  • x – value to initialize the x-component to

  • y – value to initialize the y-component to

  • z – value to initialize the z-component to

  • container – an iterable object of length three with elements compatible with SupportsFloat (this must be the only parameter if present)

Raises:

Subclassing Vector#

As of version 0.16.0 the Vector class supports subclassing. As the Vector class is implemented as a C struct, it has a specific memory layout, and as a consequence of that it is a @distjoint-base. This means Vector cannot be combined with any other types whose layout is defined as a C struct when inheriting, which most Python built-in types such as list or tuple are. For example combining Vector and list as base classes will raise a TypeError:

>>> class Foo(Vector, list):
...     pass
...
TypeError: multiple bases have instance lay-out conflict

This limitation does not apply when combining a @distjoin-base with a pure python class, and of course is not an issue if Vector is the only base class for a derived type.

While Vector supports inheritance, there is no way for the constructor to know how to initialize an inherited type. Because of this, any function (except Vector.__new__()) that returns a Vector type will always return a Vector instance, even on inherited types. To change this, you will need to override the function you want to return your type, and wrap the returned value in the constructor of your type. For example:

>>> class MyType(Vector):
...     def __init__(self, container, foo, bar):
...         super().__init__(self, container)
...         self.foo = foo
...         self.bar = bar
...     def __add__(self, other: MyType) -> MyType:
...         super().__add__(other)
...         return MyType(result, self.foo, self.bar)

In this example the result of the Vector.__add__() call is used to create a MyType instance. Any method you want to have this behavior must be implemented yourself, including module level functions that normally return a Vector type.

Instance Methods#

General Protocols#

Vector.__len__() int#

Returns the length of the Vector. This always returns 3.

Returns:

the method always returns 3

Vector.__getitem__(index: int) float#

Retrieves the indexed value of the underlying array.

Parameters:

index – the index of the value to retrieve

Returns:

the indexed value of self

Raises:
  • TypeError – if index is not or cannot be converted to an int

  • ValueError – if index is not in the interval [0, 2] (after adjusting for negative indexing)

Vector.__setitem__(index: int, value: SupportsFloat) float#

Sets the indexed value of the underlying array.

Parameters:
  • index – the index of the value to set

  • value – the value to set the array component to

Returns:

value

Raises:
  • TypeError – if index is not or cannot be converted to an int

  • TypeError – if value is not or cannot be converted to a float

  • ValueError – if index is not in the interval [0, 2]

Vector.__repr__() str#

Returns a string representation of self, representative of a constructor call with the component values of the vector. The format of the components follows the same as Vector.__str__().

Returns:

a string representation of self

Changed in version 0.16.0: The method now prepends the output with the module name for better scope resolution support when using the output with exec().

Vector.__str__() str#

Returns a string representation of self. The format of the output string is similar to a list, the components enclosed by square brackets. The format of the components are either decimal or scientific notation, whichever requires fewer bytes to store their string representations.

Returns:

a string representation of self

Vector.__reduce__()#

Allows support for pickling and deep copying. This function returns a 2-tuple containing a reference to the type constructor and a tuple of arguments to initialize an equivalent instance. For types inheriting from Vector, this function may need to be overloaded and modified to return your own type.

Returns:

a tuple used for reconstructing self’s state

Return type:

tuple[type, tuple[float, float, float]]

Iterability#

While the Vector class does not define an __iter__() method directly, the class does support iterability via the sequence protocol. Attempting to access Vector.__iter__() will raise an AttributeError, however a Vector can be used anywhere an iterable is expected.

>>> v = Vector(1, 2, 3)
>>> itr = iter(v)
>>> print(list(itr))
[1.0, 2.0, 3.0]
>>> for i in v:
...     print(i)
...
1.0
2.0
3.0

Arithmetic Operators#

Vector.__add__(other: Vector) Vector#

Standard vector addition.

Parameters:

other – the vector to be added

Returns:

the vector sum of self and other

Raises:

TypeError – if other is not a Vector type

Vector.__sub__(other: Vector) Vector#

Standard vector subtraction.

Parameters:

other – the vector to be subtracted

Returns:

the vector subtraction of self and other

Raises:

TypeError – if other is not a Vector type

Vector.__mul__(scalar: SupportsFloat) Vector#

Standard scalar multiplication.

Parameters:

scalar – the scalar to multiply each element of self by

Returns:

the scalar product

Raises:

TypeError – if scalar is not or cannot be converted to a float

Vector.__matmul__(matrix: Matrix) Vector#

Multiplication with the vector on the left side.

Parameters:

matrix – the matrix to multiply self by

Returns:

the transformation of self by matrix

Raises:

TypeError – if matrix is not a Matrix type

Vector.__truediv__(scalar: SupportsFloat) Vector#

Standard scalar division.

Parameters:

scalar – the scalar to divide each element of self by

Returns:

the scalar quotient

Raises:

TypeError – if scalar is not or cannot be converted to a float

Vector.__neg__() Vector#

Negates each component of a vector. This results in a vector of the same length as self, but points in the opposite direction.

Returns:

the negative of a vector

Vector.__iadd__(other: Vector) Vector#

Inplace standard vector addition.

Parameters:

other – the vector to add to self

Raises:

TypeError – if other is not an Vector type

Vector.__isub__(other: Vector) Vector#

Inplace standard vector subtraction.

Parameters:

other – the vector to subtract from self

Raises:

TypeError – if other is not an Vector type

Vector.__imul__(scalar: SupportsFloat) Vector#

Inplace standard scalar multiplication.

Parameters:

scalar – the scalar to multiply each element of self by

Raises:

TypeError – if scalar is not or cannot be converted to a float

Vector.__itruediv__(scalar: SupportsFloat) Vector#

Inplace standard scalar division.

Parameters:

scalar – the scalar to divide each element of self by

Raises:

TypeError – if scalar is not or cannot be converted to a float

Vector.__rmul__(scalar: SupportsFloat) Vector:#

Provides support for right-hand side scalar multiplication of Vector types, e.g. 1.5 * vector.

Parameters:

rhs – scalar to multiply self by

Returns:

self multiplied by scalar

Raises:

TypeError – if scalar is not or cannot be converted to a float

Logical Operators#

Vector.__eq__(other: Vector) bool#

Compares each element of two vectors for equality.

See also

Checkout Vector.compare_to_tol() and Vector.compare_to_ulp() for finer control over evaluating equality.

Parameters:

other – the vector to compare to self

Returns:

True if each component of self and other are equivalent, False otherwise

Raises:

TypeError – if other is not a Vector type

Vector.__ne__(other: Vector) bool#

Compares each element of two vectors for an inequality.

Parameters:

other – the vector to compare to self

Returns:

True if any component of self and other are not equivalent, False otherwise

Raises:

TypeError – if other is not a Vector type

Note

All other logic operators are not implemented and will raise a TypeError.

Vector Operators#

Vector.compare_to_tol(other: Vector, rel_tol: float = 1e-9, abs_tol: float = 1e-15) bool#

Compares self to other using tolerance based mechanics. This is the same way Vector.__eq__() and Vector.__ne__() compare vectors. This function allows customization of the realtive and absolute tolerance values. Using the default tolerance values, this function is equivalent to Vector.__eq__(). For comparing any two components of self and other, the components are considered equal if, for diff = abs(a - b), diff <= abs_tol + rel_tol * max((abs(a), abs(b))).

Parameters:
  • other – the vector to compare to self

  • rel_tol – the relative tolerance of the comparison

  • abs_tol – the absolute tolerance of the comparison

Returns:

True if all components are equal, False otherwise

Raises:

TypeError – if other is not a Vector type or rel_tol or abs_tol are not float types.

Vector.compare_to_ulp(other: Vector, max_ulps: int) bool#

Compares self to other using ULP (Unit in the Last Place) based mechanics. The number of ULPs between two floating-point numbers is the number of representable floating-point value between the two numbers. This can be a valid way of comparing two floating-point numbers in certan domains, however for the types of projects PyEVSpace was built to support, it is not sufficient, which is why Vector.__eq__() uses tolerance based comparisons.

The most obvious example of why ULP based comparison does not suffice for our uses is comparing two values that would be very common to encounter. The values cos(math.pi / 4) and sin(math.pi / 4) should be identical, however because of the binary representation of floating-point values, the number of ULPs between these values is quite large. In fact these values evaluate as False using the built-in Python equality operator:

>>> print(cos(pi / 4))
0.7071067811865476
>>> print(sin(pi / 4))
0.7071067811865475
>>> #            ^ notice the rounding differences
>>> print(cos(pi / 4) == sin(pi / 4))
False

Using a default number of ULPs that accommodates this equality would also allow other, less equivalent values to evaluate as equal which should not be accepted.

This method still allows users who would like an ULP based comparison to be able to use it. In some cases, a very high level of vector equality may be waranted. If tolerance based comparison is still ideal for you but you want finer controls over the tolerance values, see Vector.compare_to_tol().

Parameters:
  • other – the vector to compare to self

  • max_ulps – the maximum amount of ULPs two components can differ by and still evaluate as equal

Returns:

True if all components are equal, False otherwise

Raises:

TypeError – if other is not a Vector type or max_ulps is not an int type

Vector.magnitude() float#

Computes the geometric length of the vector.

Returns:

the magnitude of self

Vector.magnitude_squared() float#

Computes the square of the magnitude of the vector. Because Vector.magnitude() computes the square of a vector’s magnitude and returns it’s square root, calling Vector.magnitude() and squaring the result will result in rounding errors. Calling this method directly preserves accuracy in such cases.

Note

This is identical to vector_dot(self, self), but is more explicit and preserves intent when reading code.

Returns:

the square of the magnitude of self

Vector.norm() Vector#

Compute a new vector equal to the norm of self. This vector points in the same direction as self, but has a length of one, also called a unit vector.

Returns:

self as a unit vector

Vector.normalize() None#

Normalizes self by dividing each element by the magnitude of the vector. This results in a vector with length equal to one.

Returns:

None

Vector.mag()#

Removed in version 0.15.0: Use Vector.magnitude()

Vector.mag2()#

Removed in version 0.15.0: Use Vector.magnitude_squared()

Attributes#

Vector.E1#

Elementary vector that represents the x-axis of the standard basis. This value should not be modified.

Value:

Vector(1, 0, 0)

Type:

Vector

Vector.E2#

Elementary vector that represents the y-axis of the standard basis. This value should not be modified.

Value:

Vector(0, 1, 0)

Type:

Vector

Vector.E3#

Elementary vector that represents the z-axis of the standard basis. This value should not be modified.

Value:

Vector(0, 0, 1)

Type:

Vector

Vector.e1#

Removed in version 0.15.0: Use Vector.E1 instead.

Vector.e2#

Removed in version 0.15.0: Use Vector.E2 instead.

Vector.e3#

Removed in version 0.15.0: Use Vector.E3 instead.

Module Methods#

pyevspace.vector_angle(lhs: Vector, rhs: Vector) float#

Computes the shortest angle between vectors lhs and rhs in radians. The order of operands does not matter, and vector_angle(lhs, rhs) == vector_angle(rhs, lhs) is always True.

Parameters:
  • lhs – left-hand side of the operation

  • rhs – right-hand side of the operation

Returns:

the shortest angle between lhs and rhs in radians

Raises:

TypeError – if lhs or rhs is not a Vector type

pyevspace.vector_cross(lhs: Vector, rhs: Vector) Vector#

Computes the cross product of lhs and rhs. PyEVSpace uses right-handed a coordinate system. If a left-handed cross product is needed, negate the result or swap the order of arguents.

>>> v1 = Vector(1, 2, 3)
>>> v2 = Vector(4, 5, 6)
>>> print(vector_cross(v1, v2)) # opposite direction for left-handed system
[-3, 6, -3]
>>> # either negate the answer
>>> print(-vector_cross(v1, v2))
[3, -6, 3]
>>> # or swap the order of arguments
>>> print(vector_cross(v2, v1))
[3, -6, 2]
Parameters:
  • lhs – left-hand side of the operation

  • rhs – right-hand side of the operation

Returns:

the right-handed cross product of lhs and rhs

Raises:

TypeError – if lhs or rhs is not a Vector type

pyevspace.vector_dot(lhs: Vector, rhs: Vector) float#

Computes the dot product between two vectors. The order of arguments is not significant as vector_dot(v1, v2) == vector_dot(v2, v1).

Parameters:
  • lhs – first vector argument

  • rhs – second vector argument

Returns:

the dot product of lhs and rhs

Raises:

TypeError – if lhs or rhs is not a Vector type

pyevspace.vector_exclude(vector: Vector, exclude: Vector) Vector#

Computes a vector from vector with all direction of exclude removed from it. This results in a linearly independent vector from exclude such that vector_dot(result, exclude) is 0.0. In other words, the resulting vector and exclude are othogonal to one another.

Another way of viewing this is, if exclude is viewed as a normal vector to a plane, the resulting vector is the projection of vector onto that plane. This resulting vector points in all directions of vector, except any portions in the direction of exclude, hence it lies in this plane.

Parameters:
  • vector – base vector

  • exclude – vector whose direction will be excluded from vector

Returns:

vector with any direction of exclude removed

Raises:

TypeError – if vector or exclude is not a Vector type

pyevspace.vector_proj(vector: Vector, onto: Vector) Vector#

Projects one vector onto another. This results in a vector pointing in the same direction as onto, but whose length is vector.magnitude * cos(vector_angle(vector, onto)).

Parameters:
  • vector – vector to project

  • onto – vector being projected onto

Returns:

vector projected onto onto

Raises:

TypeError – if vector or onto is not a Vector type

pyevspace.dot(lhs, rhs)#

Removed in version 0.15.0: Use vector_dot()

pyevspace.cross(lhs, rhs)#

Removed in version 0.15.0: Use vector_cross()

pyevspace.norm(vector)#

Removed in version 0.15.0: Use Vector.norm()

pyevspace.vang(lhs, rhs)#

Removed in version 0.15.0: Use vector_angle()

pyevspace.vxcl(vector, exclude)#

Removed in version 0.15.0: Use vector_exclude()

pyevspace.proj(vector, onto)#

Removed in version 0.15.0: Use vector_proj()

Buffer Protocol#

Vector.__buffer__(flags: int) memoryview#

Returns a memoryview object with self as the reference object.

Parameters:

flags – a combined enumerated value from inspect.BufferFlags to control the exported memoryview object

Returns:

the exported memoryview object

The Vector class supports the buffer protocol and can be used by other objects which also support the buffer interface. For example it can be used to instantiate a memoryview object

>>> vector = Vector(1, 2, 3)
>>> view = memoryview(vector)
>>> view[2] = 3.14
>>> print(vector)
[1, 2, 3.14]

This can also be used for interfacing with buffer supporting libraries like NumPy.

>>> import numpy as np
>>> from pyevspace import Vector
>>>
>>> vector = Vector(1, 2, 3)
>>> print(vector)
[1, 2, 3]
>>> # give arr access to the underlying data buffer of vector
>>> arr = np.ndarray((3,), buffer=vector)
>>> print(arr)
[1, 2, 3]
>>> # changes to arr also modify vector as they share the same memory
>>> arr[0] = 10
>>> print(arr)
[10, 2, 3]
>>> print(vector)
[10, 2, 3]