Python/linear_algebra/src/lib.py

429 lines
13 KiB
Python
Raw Normal View History

2018-10-19 12:48:28 +00:00
"""
Created on Mon Feb 26 14:29:11 2018
@author: Christian Bender
@license: MIT-license
This module contains some useful classes and functions for dealing
with linear algebra in python.
Overview:
- class Vector
- function zeroVector(dimension)
- function unitBasisVector(dimension,pos)
- function axpy(scalar,vector1,vector2)
- function randomVector(N,a,b)
- class Matrix
- function squareZeroMatrix(N)
- function randomMatrix(W,H,a,b)
"""
from __future__ import annotations
2018-10-19 12:48:28 +00:00
import math
import random
from typing import Collection, overload
2018-10-19 12:48:28 +00:00
class Vector:
2018-10-19 12:48:28 +00:00
"""
This class represents a vector of arbitrary size.
You need to give the vector components.
Overview about the methods:
constructor(components : list) : init the vector
set(components : list) : changes the vector components.
__str__() : toString method
component(i : int): gets the i-th component (start by 0)
__len__() : gets the size of the vector (number of components)
euclidLength() : returns the euclidean length of the vector.
operator + : vector addition
operator - : vector subtraction
operator * : scalar multiplication and dot product
copy() : copies this vector and returns it.
changeComponent(pos,value) : changes the specified component.
TODO: compare-operator
2018-10-19 12:48:28 +00:00
"""
2019-10-05 05:14:13 +00:00
def __init__(self, components: Collection[float] | None = None) -> None:
2018-10-19 12:48:28 +00:00
"""
input: components or nothing
simple constructor for init the vector
2018-10-19 12:48:28 +00:00
"""
if components is None:
components = []
self.__components = list(components)
2019-10-05 05:14:13 +00:00
def set(self, components: Collection[float]) -> None:
2018-10-19 12:48:28 +00:00
"""
input: new components
changes the components of the vector.
replace the components with newer one.
2018-10-19 12:48:28 +00:00
"""
if len(components) > 0:
self.__components = list(components)
2018-10-19 12:48:28 +00:00
else:
raise Exception("please give any vector")
2019-10-05 05:14:13 +00:00
def __str__(self) -> str:
2018-10-19 12:48:28 +00:00
"""
returns a string representation of the vector
2018-10-19 12:48:28 +00:00
"""
return "(" + ",".join(map(str, self.__components)) + ")"
2019-10-05 05:14:13 +00:00
def component(self, i: int) -> float:
2018-10-19 12:48:28 +00:00
"""
input: index (start at 0)
output: the i-th component of the vector.
2018-10-19 12:48:28 +00:00
"""
2019-10-05 05:14:13 +00:00
if type(i) is int and -len(self.__components) <= i < len(self.__components):
2018-10-19 12:48:28 +00:00
return self.__components[i]
else:
raise Exception("index out of range")
2019-10-05 05:14:13 +00:00
def __len__(self) -> int:
2018-10-19 12:48:28 +00:00
"""
returns the size of the vector
2018-10-19 12:48:28 +00:00
"""
return len(self.__components)
2019-10-05 05:14:13 +00:00
def euclidLength(self) -> float:
2018-10-19 12:48:28 +00:00
"""
returns the euclidean length of the vector
2018-10-19 12:48:28 +00:00
"""
summe: float = 0
2018-10-19 12:48:28 +00:00
for c in self.__components:
2019-10-05 05:14:13 +00:00
summe += c ** 2
2018-10-19 12:48:28 +00:00
return math.sqrt(summe)
2019-10-05 05:14:13 +00:00
def __add__(self, other: Vector) -> Vector:
2018-10-19 12:48:28 +00:00
"""
input: other vector
assumes: other vector has the same size
returns a new vector that represents the sum.
2018-10-19 12:48:28 +00:00
"""
size = len(self)
if size == len(other):
result = [self.__components[i] + other.component(i) for i in range(size)]
return Vector(result)
2018-10-19 12:48:28 +00:00
else:
raise Exception("must have the same size")
2019-10-05 05:14:13 +00:00
def __sub__(self, other: Vector) -> Vector:
2018-10-19 12:48:28 +00:00
"""
input: other vector
assumes: other vector has the same size
returns a new vector that represents the difference.
2018-10-19 12:48:28 +00:00
"""
size = len(self)
if size == len(other):
result = [self.__components[i] - other.component(i) for i in range(size)]
return Vector(result)
2019-10-05 05:14:13 +00:00
else: # error case
2018-10-19 12:48:28 +00:00
raise Exception("must have the same size")
2019-10-05 05:14:13 +00:00
@overload
def __mul__(self, other: float) -> Vector:
...
@overload
def __mul__(self, other: Vector) -> float:
...
def __mul__(self, other: float | Vector) -> float | Vector:
2018-10-19 12:48:28 +00:00
"""
mul implements the scalar multiplication
and the dot-product
2018-10-19 12:48:28 +00:00
"""
2019-10-05 05:14:13 +00:00
if isinstance(other, float) or isinstance(other, int):
ans = [c * other for c in self.__components]
return Vector(ans)
2019-10-05 05:14:13 +00:00
elif isinstance(other, Vector) and (len(self) == len(other)):
size = len(self)
summe: float = 0
2018-10-19 12:48:28 +00:00
for i in range(size):
summe += self.__components[i] * other.component(i)
return summe
2019-10-05 05:14:13 +00:00
else: # error case
raise Exception("invalid operand!")
2019-10-05 05:14:13 +00:00
def magnitude(self) -> float:
"""
Magnitude of a Vector
>>> Vector([2, 3, 4]).magnitude()
5.385164807134504
"""
return sum([i ** 2 for i in self.__components]) ** (1 / 2)
def angle(self, other: Vector, deg: bool = False) -> float:
"""
find angle between two Vector (self, Vector)
>>> Vector([3, 4, -1]).angle(Vector([2, -1, 1]))
1.4906464636572374
>>> Vector([3, 4, -1]).angle(Vector([2, -1, 1]), deg = True)
85.40775111366095
>>> Vector([3, 4, -1]).angle(Vector([2, -1]))
Traceback (most recent call last):
...
Exception: invalid operand!
"""
num = self * other
den = self.magnitude() * other.magnitude()
if deg:
return math.degrees(math.acos(num / den))
else:
return math.acos(num / den)
def copy(self) -> Vector:
2018-10-19 12:48:28 +00:00
"""
copies this vector and returns it.
2018-10-19 12:48:28 +00:00
"""
return Vector(self.__components)
2019-10-05 05:14:13 +00:00
def changeComponent(self, pos: int, value: float) -> None:
2018-10-19 12:48:28 +00:00
"""
input: an index (pos) and a value
changes the specified component (pos) with the
'value'
2018-10-19 12:48:28 +00:00
"""
2019-10-05 05:14:13 +00:00
# precondition
assert -len(self.__components) <= pos < len(self.__components)
2018-10-19 12:48:28 +00:00
self.__components[pos] = value
2019-10-05 05:14:13 +00:00
def zeroVector(dimension: int) -> Vector:
2018-10-19 12:48:28 +00:00
"""
returns a zero-vector of size 'dimension'
2019-10-05 05:14:13 +00:00
"""
# precondition
assert isinstance(dimension, int)
return Vector([0] * dimension)
2018-10-19 12:48:28 +00:00
def unitBasisVector(dimension: int, pos: int) -> Vector:
2018-10-19 12:48:28 +00:00
"""
returns a unit basis vector with a One
at index 'pos' (indexing at 0)
2018-10-19 12:48:28 +00:00
"""
2019-10-05 05:14:13 +00:00
# precondition
assert isinstance(dimension, int) and (isinstance(pos, int))
ans = [0] * dimension
ans[pos] = 1
2018-10-19 12:48:28 +00:00
return Vector(ans)
2019-10-05 05:14:13 +00:00
def axpy(scalar: float, x: Vector, y: Vector) -> Vector:
2018-10-19 12:48:28 +00:00
"""
input: a 'scalar' and two vectors 'x' and 'y'
output: a vector
computes the axpy operation
2018-10-19 12:48:28 +00:00
"""
# precondition
2019-10-05 05:14:13 +00:00
assert (
isinstance(x, Vector)
and (isinstance(y, Vector))
and (isinstance(scalar, int) or isinstance(scalar, float))
)
return x * scalar + y
2018-10-19 12:48:28 +00:00
def randomVector(N: int, a: int, b: int) -> Vector:
2018-10-19 12:48:28 +00:00
"""
input: size (N) of the vector.
random range (a,b)
output: returns a random vector of size N, with
random integer components between 'a' and 'b'.
2018-10-19 12:48:28 +00:00
"""
random.seed(None)
ans = [random.randint(a, b) for _ in range(N)]
return Vector(ans)
2018-10-19 12:48:28 +00:00
class Matrix:
2018-10-19 12:48:28 +00:00
"""
class: Matrix
This class represents a arbitrary matrix.
2018-10-19 12:48:28 +00:00
Overview about the methods:
__str__() : returns a string representation
2018-10-19 12:48:28 +00:00
operator * : implements the matrix vector multiplication
implements the matrix-scalar multiplication.
changeComponent(x,y,value) : changes the specified component.
component(x,y) : returns the specified component.
width() : returns the width of the matrix
height() : returns the height of the matrix
operator + : implements the matrix-addition.
operator - _ implements the matrix-subtraction
"""
2019-10-05 05:14:13 +00:00
def __init__(self, matrix: list[list[float]], w: int, h: int) -> None:
2018-10-19 12:48:28 +00:00
"""
simple constructor for initializing
the matrix with components.
2018-10-19 12:48:28 +00:00
"""
self.__matrix = matrix
self.__width = w
self.__height = h
2019-10-05 05:14:13 +00:00
def __str__(self) -> str:
2018-10-19 12:48:28 +00:00
"""
returns a string representation of this
matrix.
2018-10-19 12:48:28 +00:00
"""
ans = ""
for i in range(self.__height):
ans += "|"
for j in range(self.__width):
2019-10-05 05:14:13 +00:00
if j < self.__width - 1:
2018-10-19 12:48:28 +00:00
ans += str(self.__matrix[i][j]) + ","
else:
ans += str(self.__matrix[i][j]) + "|\n"
return ans
2019-10-05 05:14:13 +00:00
def changeComponent(self, x: int, y: int, value: float) -> None:
2018-10-19 12:48:28 +00:00
"""
changes the x-y component of this matrix
2018-10-19 12:48:28 +00:00
"""
if 0 <= x < self.__height and 0 <= y < self.__width:
2018-10-19 12:48:28 +00:00
self.__matrix[x][y] = value
else:
2019-10-05 05:14:13 +00:00
raise Exception("changeComponent: indices out of bounds")
def component(self, x: int, y: int) -> float:
2018-10-19 12:48:28 +00:00
"""
returns the specified (x,y) component
2018-10-19 12:48:28 +00:00
"""
if 0 <= x < self.__height and 0 <= y < self.__width:
2018-10-19 12:48:28 +00:00
return self.__matrix[x][y]
else:
2019-10-05 05:14:13 +00:00
raise Exception("changeComponent: indices out of bounds")
def width(self) -> int:
2018-10-19 12:48:28 +00:00
"""
getter for the width
2018-10-19 12:48:28 +00:00
"""
return self.__width
2019-10-05 05:14:13 +00:00
def height(self) -> int:
2018-10-19 12:48:28 +00:00
"""
getter for the height
2018-10-19 12:48:28 +00:00
"""
return self.__height
2019-10-05 05:14:13 +00:00
def determinate(self) -> float:
"""
returns the determinate of an nxn matrix using Laplace expansion
"""
if self.__height == self.__width and self.__width >= 2:
total = 0
if self.__width > 2:
for x in range(0, self.__width):
for y in range(0, self.__height):
total += (
self.__matrix[x][y]
* (-1) ** (x + y)
* Matrix(
self.__matrix[0:x] + self.__matrix[x + 1 :],
self.__width - 1,
self.__height - 1,
).determinate()
)
else:
return (
self.__matrix[0][0] * self.__matrix[1][1]
- self.__matrix[0][1] * self.__matrix[1][0]
)
return total
else:
raise Exception("matrix is not square")
@overload
def __mul__(self, other: float) -> Matrix:
...
@overload
def __mul__(self, other: Vector) -> Vector:
...
def __mul__(self, other: float | Vector) -> Vector | Matrix:
2018-10-19 12:48:28 +00:00
"""
implements the matrix-vector multiplication.
implements the matrix-scalar multiplication
2018-10-19 12:48:28 +00:00
"""
2019-10-05 05:14:13 +00:00
if isinstance(other, Vector): # vector-matrix
if len(other) == self.__width:
2018-10-19 12:48:28 +00:00
ans = zeroVector(self.__height)
for i in range(self.__height):
summe: float = 0
2018-10-19 12:48:28 +00:00
for j in range(self.__width):
summe += other.component(j) * self.__matrix[i][j]
2019-10-05 05:14:13 +00:00
ans.changeComponent(i, summe)
2018-10-19 12:48:28 +00:00
summe = 0
return ans
else:
2019-10-05 05:14:13 +00:00
raise Exception(
"vector must have the same size as the "
+ "number of columns of the matrix!"
)
elif isinstance(other, int) or isinstance(other, float): # matrix-scalar
matrix = [
[self.__matrix[i][j] * other for j in range(self.__width)]
for i in range(self.__height)
]
return Matrix(matrix, self.__width, self.__height)
def __add__(self, other: Matrix) -> Matrix:
2018-10-19 12:48:28 +00:00
"""
implements the matrix-addition.
2018-10-19 12:48:28 +00:00
"""
2019-10-05 05:14:13 +00:00
if self.__width == other.width() and self.__height == other.height():
2018-10-19 12:48:28 +00:00
matrix = []
for i in range(self.__height):
row = []
for j in range(self.__width):
2019-10-05 05:14:13 +00:00
row.append(self.__matrix[i][j] + other.component(i, j))
2018-10-19 12:48:28 +00:00
matrix.append(row)
2019-10-05 05:14:13 +00:00
return Matrix(matrix, self.__width, self.__height)
2018-10-19 12:48:28 +00:00
else:
raise Exception("matrix must have the same dimension!")
2019-10-05 05:14:13 +00:00
def __sub__(self, other: Matrix) -> Matrix:
2018-10-19 12:48:28 +00:00
"""
implements the matrix-subtraction.
2018-10-19 12:48:28 +00:00
"""
2019-10-05 05:14:13 +00:00
if self.__width == other.width() and self.__height == other.height():
2018-10-19 12:48:28 +00:00
matrix = []
for i in range(self.__height):
row = []
for j in range(self.__width):
2019-10-05 05:14:13 +00:00
row.append(self.__matrix[i][j] - other.component(i, j))
2018-10-19 12:48:28 +00:00
matrix.append(row)
2019-10-05 05:14:13 +00:00
return Matrix(matrix, self.__width, self.__height)
2018-10-19 12:48:28 +00:00
else:
raise Exception("matrix must have the same dimension!")
2019-10-05 05:14:13 +00:00
2018-10-19 12:48:28 +00:00
def squareZeroMatrix(N: int) -> Matrix:
2018-10-19 12:48:28 +00:00
"""
returns a square zero-matrix of dimension NxN
2018-10-19 12:48:28 +00:00
"""
ans: list[list[float]] = [[0] * N for _ in range(N)]
2019-10-05 05:14:13 +00:00
return Matrix(ans, N, N)
def randomMatrix(W: int, H: int, a: int, b: int) -> Matrix:
2018-10-19 12:48:28 +00:00
"""
returns a random matrix WxH with integer components
between 'a' and 'b'
2018-10-19 12:48:28 +00:00
"""
random.seed(None)
matrix: list[list[float]] = [
[random.randint(a, b) for _ in range(W)] for _ in range(H)
]
2019-10-05 05:14:13 +00:00
return Matrix(matrix, W, H)