# Source code for points.vectors

```"""Contains the Vector class."""

import math
from math import sqrt, acos, pi

[docs]class Vector:
"""A Vector is a sequence of numbers. They can represent a point in space,
or the attributes of an object.

Vectors can be added and subtracted with ``+`` and ``-``, but ``*`` is
reserved for scalar multiplication - you can use it to multiply the vector
by a number but not by another vector (there are special methods for this).

:param values: The numbers that make up the Vector. If a single sequence is\
given, that sequence will be unpacked to make the vector."""

def __init__(self, *values):
if len(values) == 1:
try:
self._values = list(values[0])
return
except: pass
self._values = list(values)

def __repr__(self):
return "<Vector {}>".format(self._values)

def __str__(self):
if len(self._values) >= 10:
values = self._values[:2] + [] + self._values[-2:]
return "<Vector [{}, {}, (...{} items omitted...), {}, {}]>".format(
self._values[0], self._values[1], len(self._values) - 4,
self._values[-2], self._values[-1]
)
return repr(self)

def __contains__(self, item):
return item in self._values

def __iter__(self):
return iter(self._values)

def __getitem__(self, index):
return self._values[index]

def __setitem__(self, index, value):
self._values[index] = value

def __len__(self):
return len(self._values)

if not isinstance(other, Vector):
raise TypeError("Cannot add {} - not a Vector".format(other))
if len(self) != len(other):
raise ValueError("Cannot add {} - unequal length".format(other))
return Vector(v1 + v2 for v1, v2 in zip(self._values, other._values))

def __sub__(self, other):
if not isinstance(other, Vector):
raise TypeError("Cannot subtract {} - not a Vector".format(other))
if len(self) != len(other):
raise ValueError("Cannot subtract {} - unequal length".format(other))
return Vector(v1 - v2 for v1, v2 in zip(self._values, other._values))

def __mul__(self, other):
if isinstance(other, Vector):
raise TypeError("'*' is reserved for scalar multiplication")
else:
return Vector([v * other for v in self._values])

def __rmul__(self, other):
return self * other

[docs]    def length(self):
"""Returns the length of the vector. This is the number of values it
contains, not its :py:meth:`magnitude`.

:rtype: ``int``"""

return len(self)

[docs]    def values(self):
"""Returns the values in the vector.

:rtype: ``tuple``"""

return tuple(self._values)

[docs]    def magnitude(self):
"""Returns the magnitude of the vector - the length of the line it
represents in space.

:rtype: ``float``"""

return sqrt(sum([x**2 for x in self._values]))

[docs]    def append(self, value):
"""Adds a value to the end of the vector.

:param value: the value to add."""

self._values.append(value)

[docs]    def insert(self, index, value):
"""Insertes a value into the vector.

:param int index: The location to insert to.
:param value: the value to add."""

self._values.insert(index, value)

[docs]    def remove(self, value):
"""Removes a value from the vector.

:param value: the value to remove."""

self._values.remove(value)

[docs]    def pop(self, index=-1):
"""Removes a value from the vector and returns it.

:param index: the index to remove, default being ``-1``.
:returns: the removed value."""

return self._values.pop(index)

[docs]    def components(self):
"""Returns the individual components that sum to make up the vector.

:returns: ``tuple`` of ``Vector``"""

components = []
for index, value in enumerate(self._values):
component_values = [0] * len(self._values)
component_values[index] = value
components.append(Vector(*component_values))
return tuple(components)

[docs]    def linearly_dependent_on(self, *vectors):
"""Checks if this Vector is linearly dependent on a set of other
vectors - that is, whether it is possible to construct this vector
from a linear combination of the other vectors.

:param \*vectors: The vectors to check against.
:rtype: ``bool``"""

return self in VectorSpan(*vectors)

[docs]    def linearly_independent_of(self, *vectors):
"""Checks if this Vector is linearly independent of a set of other
vectors - that is, whether it is impossible to construct this Vector from
a linear combination of the other Vectors.

:param \*vectors: The vectors to check against.
:rtype: ``bool``"""

return not self.linearly_dependent_on(*vectors)

[docs]    def span(self):
"""Returns the vector's span - the set of all vectors that can be
constructed by scaling this vector.

:rtype: ``VectorSpan``"""

return VectorSpan(self)

[docs]    def span_with(self, *vectors):
"""Returns the span of this vector and others - the set of all vectors
that can be constructed by scaling and adding the vectors.

:rtype: ``VectorSpan``"""

return VectorSpan(self, *vectors)

[docs]    def dot(self, other):
"""Returns the dot product between this vector and another.

:param Vector other: The other Vector.
:raises TypeError: If a non-Vector is given.
:raises ValueError: If the Vectors are of different lengths.
:rtype: ``float``"""

if not isinstance(other, Vector):
raise TypeError("{} is not a Vector".format(other))
if self.length() != other.length():
raise ValueError("{} and {} not equal length".format(self, other))
return sum([u_i * v_i for u_i, v_i in zip(self._values, other._values)])

[docs]    def cross(self, other):
"""Returns the cross product between this vector and another. Only
three-dimensional Vectors can do this (vectors of length 3).

:param Vector other: The other Vector.
:raises TypeError: if a non-Vector is given.
:raises ValueError: if the Vectors are not three-dimensional.
:rtype: ``Vector``"""

if not isinstance(other, Vector):
raise TypeError("{} is not a Vector".format(other))
values, other = self._values, other._values
if len(values) != 3 or len(other) != 3:
raise ValueError("{} or {} is not 3D".format(self, other))
return Vector(
values[1] * other[2] - values[2] * other[1],
values[2] * other[0] - values[0] * other[2],
values[0] * other[1] - values[1] * other[0]
)

[docs]    def distance_to(self, other):
"""Returns the distance between this and another vector, when
originating at the origin.

:param Vector other: the other Vector.
:rtype: ``float``"""

vector = self - other
return vector.magnitude()

[docs]    def angle_with(self, other, degrees=False):
"""Returns the angle between this vector and another, in radians.

:param Vector other: The other Vector.
:param bool degrees: If ``True``, the angle will be returned in degrees.
:raises TypeError: If a non-Vector is given.
:rtype: ``float``"""

if not isinstance(other, Vector):
raise TypeError("{} is not a Vector".format(other))
if self.length() != other.length():
raise ValueError("{} and {} not equal length".format(self, other))
if self.magnitude() == 0 or other.magnitude() == 0:
ang = pi / 4
else:
ang = acos(self.dot(other) / (self.magnitude() * other.magnitude()))
return math.degrees(ang) if degrees else ang

[docs]class VectorSpan:
"""A VectorSpan represents all the vectors that can be obtained by
performing linear combinations of some starter set of vectors.

A Vector is ``in`` this span if it can be constructed from a linear
combination of the defining Vectors. This is calculated using Gaussian
elimination.

:param \*vectors: The vectors which define the span. Any vectors that are\
linearly dependent on the others will be discarded.
:raises ValueError: if vectors of different dimensions are provided."""

def __init__(self, *vectors):
self._vectors = {vectors[0]}
self._dimension = len(vectors[0])
for v in vectors[1:]:
if len(v) != self._dimension: raise ValueError(
"{} has Vectors of different dimensions".format(vectors)
)
if v.linearly_independent_of(*self._vectors):

def __repr__(self):
return "<VectorSpan{} - {} dimensions>".format(
" of " + repr(
list(list(self._vectors)[0].values())
) if len(self._vectors) == 1 else "", self._dimension
)

def __contains__(self, vector):
if len(vector) != self._dimension: return False
if set(vector.values()) == {0}: return True
if len(self._vectors) == 1:
one_vector = list(self._vectors)[0]
if set(one_vector.values()) == {0}: return False
if any(val1 == 0 and val2 != 0 for val1, val2
in zip(vector.values(), one_vector.values())): return False
if len(set([v1 / v2 for v1, v2 in zip(one_vector, vector)])) == 1:
return True
else:
from .matrices import Matrix
augmented = Matrix(*self._vectors, vector, columns=True)
augmented.gauss()
for row in augmented.rows():
if set(row[:-1]) == {0} and row[-1] != 0: return False
return True

[docs]    def dimension(self):
"""The vector space that the span inhabits - any vectors of a different
vector will never be ``in`` this span.

:rtype: ``int``"""

return self._dimension

[docs]    def rank(self):
"""The dimensions of the space the VectorSpan spans - regardless of the
overall Vector Space it inhabits.

For example a Vector Span in three dimensional space might have a rank
of 2 if it only spans a plane within that space.

:rtype: ``int``"""

return len(self._vectors)
```