158 lines
6.3 KiB
Python
158 lines
6.3 KiB
Python
import numpy
|
|
|
|
from matrix import Matrix
|
|
|
|
|
|
class Vector(Matrix):
|
|
def __init__(self, data=None, shape=None):
|
|
"""
|
|
|
|
:type data: numpy.ndarray | list | int
|
|
:type shape: (int, int)
|
|
"""
|
|
if shape is None and not isinstance(data, int):
|
|
shape = (len(data), 1)
|
|
|
|
if isinstance(data, numpy.ndarray):
|
|
self.__init__(data.tolist())
|
|
elif isinstance(data, list):
|
|
if len(data) == 1 and isinstance(data[0], list):
|
|
shape = (1, len(data[0]))
|
|
super().__init__(data, shape)
|
|
elif isinstance(data, int):
|
|
self.__init__([0] * data)
|
|
else:
|
|
raise ValueError("data must be a ``list``, a ``numpy.ndarray`` or an integer for dimension")
|
|
|
|
def __eq__(self, other):
|
|
"""
|
|
Return ``self==value``
|
|
|
|
:param other: The object to compare to; must be either a ``Vector``, a ``list`` or a ``numpy.ndarray``
|
|
:return: True if data in the same-shaped vectors are equal to the given data in other for each component otherwise False
|
|
"""
|
|
if isinstance(other, Vector):
|
|
if self.__shape__ != other.__shape__:
|
|
return False
|
|
data_to_compare = numpy.array(other.__data__).flatten().tolist()
|
|
elif isinstance(other, list):
|
|
data_to_compare = numpy.array(other).flatten().tolist()
|
|
elif isinstance(other, numpy.ndarray):
|
|
data_to_compare = other.flatten().tolist()
|
|
else:
|
|
raise ValueError("Vector type is not comparable to type of given ``other``")
|
|
return numpy.array_equal(data_to_compare, numpy.array(self.__data__).flatten().tolist())
|
|
|
|
def transpose(self):
|
|
"""
|
|
:return: the transpose of the vector
|
|
"""
|
|
transposed_data, shape = super().__transpose_internal__()
|
|
return Vector(transposed_data, shape)
|
|
|
|
def T(self):
|
|
return self.transpose()
|
|
|
|
def __neg__(self):
|
|
return Vector(super().__neg_internal__(), self.__shape__)
|
|
|
|
def __add__(self, other):
|
|
if isinstance(other, Vector):
|
|
if self.__shape__ != other.__shape__:
|
|
raise ValueError("The shape of the operands must be the same")
|
|
return Vector(super().__add_matrix_internal__(other), self.__shape__)
|
|
elif isinstance(other, int) or isinstance(other, float):
|
|
return Vector(super().__add_scalar_internal__(other), self.__shape__)
|
|
else:
|
|
raise ValueError("Only a number or another ``Vector`` can be added to a ``Vector``")
|
|
|
|
def __mul_vector_same_shape_internal__(self, other):
|
|
rows = self.__shape__[0]
|
|
cols = self.__shape__[1]
|
|
if rows >= cols:
|
|
new_data = [(self.__data__[i][0] * other.__data__[i][0]) for i in range(rows)]
|
|
else:
|
|
new_data = [(self.__data__[0][j] * other.__data__[0][j]) for j in range(cols)]
|
|
return new_data
|
|
|
|
def __mul_tensor_internal__(self, other):
|
|
rows = self.__shape__[0]
|
|
cols = other.__shape__[1]
|
|
return [[self.__data__[i][0] * other.__data__[0][j] for j in range(cols)] for i in range(rows)], (rows, cols)
|
|
|
|
def __mul__(self, other):
|
|
if isinstance(other, Vector):
|
|
if self.__shape__ == other.__shape__:
|
|
return Vector(self.__mul_vector_same_shape_internal__(other))
|
|
elif self.__shape__ == tuple(reversed(other.__shape__)):
|
|
if self.__shape__[0] == 1: # Case (_ ... _) * (_\n...\n_) = scalar
|
|
return super().__mul_matrix_internal__(other)[0]
|
|
else: # Case (_\n...\n_) * (_ ... _) = Matrix
|
|
new_data, shape = self.__mul_tensor_internal__(other)
|
|
return Matrix(new_data, shape)
|
|
else:
|
|
raise ValueError("The shapes of the operands must be the compatible")
|
|
elif isinstance(other, int) or isinstance(other, float):
|
|
return Vector(super().__mul_scalar_internal__(other))
|
|
else:
|
|
raise ValueError("A ``Vector`` can only be multiplied with an ``Vector`` (dot product or tensor), "
|
|
"a compatible ``Matrix`` or a scalar")
|
|
|
|
def __mul_matrix_vector_internal__(self, other):
|
|
rows = other.__shape__[0]
|
|
new_data = [0] * rows
|
|
for i in range(rows):
|
|
new_data[i] = sum([other.__data__[i][j] * self.__data__[j][0] for j in range(self.__shape__[0])])
|
|
return new_data
|
|
|
|
def __rmul__(self, other):
|
|
if isinstance(other, Matrix):
|
|
return Vector(self.__mul_matrix_vector_internal__(other))
|
|
return self * other
|
|
|
|
def __truediv_vector_internal__(self, other):
|
|
rows = self.__shape__[0]
|
|
cols = self.__shape__[1]
|
|
return [[(self.__data__[i][j] / other.__data__[i][j]) for j in range(cols)] for i in range(rows)]
|
|
|
|
def __truediv__(self, other):
|
|
if isinstance(other, Vector):
|
|
if self.__shape__ != other.__shape__:
|
|
raise ValueError("The ``Vector``s to be divided must have the same shape")
|
|
return Vector(self.__truediv_vector_internal__(other))
|
|
elif isinstance(other, int) or isinstance(other, float):
|
|
return Vector(super().__truediv_scalar_internal__(other))
|
|
else:
|
|
raise ValueError("A ``Vector`` can only be divided ba a number or another same-shaped ``Vector``")
|
|
|
|
def norm(self, **kwargs):
|
|
"""
|
|
Computes the 2-norm of the vector which is the Frobenius-Norm of a nx1 matrix.
|
|
|
|
:param kwargs: ignored
|
|
:return: the 2-norm of the vector
|
|
"""
|
|
return super().norm()
|
|
|
|
def normalize(self):
|
|
"""
|
|
A normalized vector has the length (norm) 1.
|
|
To achieve that the vector is divided by the norm of itself.
|
|
|
|
:return: the normalized vector
|
|
"""
|
|
return self / self.norm()
|
|
|
|
def __getitem__(self, key):
|
|
if isinstance(key, tuple):
|
|
return numpy.array(self.__data__).flatten()[[key]].tolist()
|
|
return numpy.array(self.__data__).flatten()[key].tolist()
|
|
|
|
def __setitem__(self, key, value):
|
|
manipulated_data = numpy.array(self.__data__).flatten()
|
|
if isinstance(key, tuple):
|
|
manipulated_data[[key]] = value
|
|
else:
|
|
manipulated_data[key] = value
|
|
self.__data__ = Vector(manipulated_data.tolist(), self.__shape__).__data__
|