Tuesday, December 25, 2012

Matrix operations

So my first piece of code will be with respect to matrix operations. NumPy already has many in-built functions, but I still want to do this because I want to write my own code in the beginning rather than just use in-built components. So here goes matrix.py.


#! /usr/bin/env python
import sys
class Matrix:
""" For defining arrays of any given size.
Contains overloaded operators for print, add, subtract and multiply.
Does not contain = assignment operator! Use a(b) instead of a=b.
Last updated - Dec 25, 2012 """
def zeros(self, rows, columns=1):
""" Creates a zero matrix
Dimensions have to be passed as <Matrix>.zeros(rows, columns).
This function also creates the actual array.
So every method of the class/object should call this first before using the array."""
self.rows=rows
self.columns=columns
self.data=[]
for count1 in range(self.rows):
row_vector=[]
for count2 in range(self.columns):
row_vector.append(0)
self.data.append(row_vector)
def __init__(self, rows=1, columns=1):
"""Creates an object with a zero array of size rowsXcolumns.
If only one dimension is specified specified, a column vector is assumed.
If no dimensions are specified, an null matrix is created."""
self.rows=rows
self.columns=columns
self.zeros(self.rows, self.columns)
def __call__(self, matrix1):
""" Replaces the assignment operator =. Use as c(b) instead of c=b.
The = will only result in a reference to the object and not an actual copy.
This method ensures it. """
self.rows=matrix1.rows
self.columns=matrix1.columns
self.zeros(self.rows, self.columns)
for count1 in range(self.rows):
for count2 in range(self.columns):
self.data[count1][count2]=matrix1.data[count1][count2]
def identity(self, rows):
""" Generates an identity matrix of rowsXrows. """
self.rows=rows
self.columns=rows
self.zeros(self.rows, self.columns)
for count in range(self.rows):
self.data[count][count]=1.0
def display(self):
""" Explicit method for displaying an array. """
for count1 in range(self.rows):
for count2 in range(self.columns):
print self.data[count1][count2],
print "\t",
print "\n",
def __repr__(self):
""" Displays an array when print <Matrix> is used. """
for count1 in range(self.rows):
for count2 in range(self.columns):
print self.data[count1][count2],
print "\t",
print "\n",
return " "
def __add__(self, matrix1):
""" Left addition of two matrices. """
add_result=Matrix(self.rows, self.columns)
try:
if matrix1.rows: pass
except AttributeError:
print "*"*40
print "Can't add a float or integer to a matrix directly."
print "*"*40
sys.exit("Logic Error")
else:
if not (self.rows==matrix1.rows and self.columns==matrix1.columns):
print "Matrices not compatible for summation."
sys.exit("Logic Error")
else:
for count1 in range(self.rows):
for count2 in range(self.columns):
add_result.data[count1][count2]=self.data[count1][count2]+\
matrix1.data[count1][count2]
return add_result
def __radd__(self, matrix1):
""" Right addition of two matrices. """
add_result=Matrix(self.rows, self.columns)
try:
if matrix1.rows: pass
except AttributeError:
print "*"*40
print "Can't add a float or integer to a matrix directly."
print "*"*40
sys.exit("Logic Error")
else:
if not (self.rows==matrix1.rows and self.columns==matrix1.columns):
print "Matrices not compatible for summation."
else:
for count1 in range(self.rows):
for count2 in range(self.columns):
add_result.data[count1][count2]=self.data[count1][count2]+\
matrix1.data[count1][count2]
return add_result
def __sub__(self, matrix1):
""" Left subtraction of two matrices. """
sub_result=Matrix(self.rows, self.columns)
try:
if matrix1.rows: pass
except AttributeError:
print "*"*40
print "Can't subtract a float or integer to a matrix directly."
print "*"*40
sys.exit("Logic Error")
else:
if not (self.rows==matrix1.rows and self.columns==matrix1.columns):
print "Matrices not compatible for subtraction."
sys.exit("Logic Error")
else:
for count1 in range(self.rows):
for count2 in range(self.columns):
sub_result.data[count1][count2]=self.data[count1][count2]-\
matrix1.data[count1][count2]
return sub_result
def __rsub__(self, matrix1):
""" Right subtraction of two mnatrices. """
sub_result=Matrix(self.rows, self.columns)
try:
if matrix1.rows: pass
except AttributeError:
print "*"*40
print "Can't subtract a float or integer to a matrix directly."
print "*"*40
sys.exit("Logic Error")
else:
if not (self.rows==matrix1.rows and self.columns==matrix1.columns):
print "Matrices not compatible for subtraction."
sys.exit("Logic Error")
else:
for count1 in range(self.rows):
for count2 in range(self.columns):
sub_result.data[count1][count2]=matrix1.data[count1][count2]-\
self.data[count1][count2]
return sub_result
def __mul__(self, matrix1):
""" Left multiplication of two matrices.
The multiplication is NOT done elementwise but in the conventional mathematical sense. """
try:
if matrix1.rows: pass
except AttributeError:
mul_result=Matrix(self.rows, self.columns)
for count1 in range(self.rows):
for count2 in range(self.columns):
mul_result.data[count1][count2]=self.data[count1][count2]*matrix1
else:
mul_result=Matrix()
if not (self.columns==matrix1.rows):
print "Matrices not compatible for multiplication."
else:
mul_result.zeros(self.rows, matrix1.columns)
for count1 in range(self.rows):
for count2 in range(matrix1.columns):
for count3 in range(self.columns):
mul_result.data[count1][count2]+=self.data[count1][count3]*\
matrix1.data[count3][count2]
return mul_result
def __rmul__(self, matrix1):
""" Right multiplication of two matrices.
The multiplication is NOT done elementwise but in the conventional mathematical sense. """
try:
if matrix1.rows: pass
except AttributeError:
mul_result=Matrix(self.rows, self.columns)
for count1 in range(self.rows):
for count2 in range(self.columns):
mul_result.data[count1][count2]=self.data[count1][count2]*matrix1
else:
mul_result=Matrix()
if not (self.rows==matrix1.columns):
print "Matrices not compatible for multiplication."
else:
mul_result.zeros(matrix1.rows, self.columns)
for count1 in range(matrix1.rows):
for count2 in range(self.columns):
for count3 in range(self.rows):
mul_result.data[count1][count2]+=self.data[count1][count3]*\
matrix1.data[count3][count2]
return mul_result
def transpose(self):
""" Returns a matrix that is transpose of the calling object array.
A new object is returned. Original object is untouched. """
trans_result=Matrix(self.columns, self.rows)
for count1 in range(self.rows):
for count2 in range(self.columns):
trans_result.data[count2][count1]=self.data[count1][count2]
return trans_result
view raw matrix.py hosted with ❤ by GitHub






A few thoughts particularly with respect to my ex-darling C++.

1. There isn't the possibility of overloading the "=" assignment operator. So is I want "a" and "b" as arrays and I want to do:
a=b
This will only cause a reference "a" to point to the the object "b". A copy will not be made. So if I change "b" later, "a" changes too. Which is not what I want. So I need to define the __call__ method. This was I could do:
a(b)
But of course you would need to define "a" as a matrix before this is run.

2. Indentation is a requirement in Python. It improves the readability of code. True. But doing away with braces {}, or the begin/end statement blocks is probably going too far. Just a beginner's thought anyway.

3. Function/Operator overloading: In C++, you can overload a function any number of times with a different number of variables, different type of variables etc. So an overloaded function could accept a float or a string and two completely different functions can be written. In Python, everything is an object. So you can't specify float or Matrix before an argument in a function/method. You have to figure out what that is inside the functions. This I feel is unnecessary ambiguity.
I was trying to code the "*" oveload operator for matrices, and would like to have the possibility of multiplying two matrices or a matrix and a float/integer. But the only was to do that was inside the overloaded operator. A little googling told me that type checking was not a good idea. Probably because they want to have  the possibility fo redefining the types or maybe defining additional super-types in later releases of Python. So the only thing I could use was exception handling. The code works but this isn't really an exception. This is a normal varation to the manner in which the operator is used on matrices. So seems odd.

4. Two more functions may be written depending on need - inverse and upper triangularization or lower triangularization. This will be decided later. The idea would be solve equations of the order Ax=b or E*d/dt(x)=Ax+bu. Using inverse to solve equations is usually avoided as matrix even close to singular will cause the simulation to blow up. So more on this later.

No comments:

Post a Comment