#!/usr/bin/env python
# Get a matrix into row-reduced echelon form.

import string

def gcf(a, b):
	(a, b) = (abs(int(a)), abs(int(b)))
	if b > a: (a, b) = (b, a)
	while b > 0: (a, b) = (b, a%b)
	return a

class Frac:
	top = 0
	bottom = 1

	def __init__(self, top, bottom = 1):
		self.top = top
		self.bottom = bottom
		self.normalise()

	def __str__(self):
		if self.bottom == 1:
			return str(self.top)
		else:
			return str(self.top) + "/" + str(self.bottom)

	def normalise(self):
		if self.bottom < 0:
			self.top = -self.top
			self.bottom = -self.bottom
		f = gcf(self.top, self.bottom)
		self.top /= f
		self.bottom /= f

	def float(self):
		return (1.0 * self.top) / self.bottom

	def __mul__(self, b):
		return Frac(self.top * b.top, self.bottom * b.bottom)
	def __div__(self, b):
		return Frac(self.top * b.bottom, self.bottom * b.top)
	def __add__(self, b):
		bottom = self.bottom * b.bottom
		return Frac((self.top * b.bottom) + (b.top * self.bottom),
		            bottom)
	def __sub__(self, b):
		bottom = self.bottom * b.bottom
		return Frac((self.top * b.bottom) - (b.top * self.bottom),
		            bottom)
	def __neg__(self):
		return Frac(-self.top, self.bottom)

xtm = [[[Frac(2),Frac(3),Frac(-1)],[Frac(5)]],
       [[Frac(1),Frac(-2),Frac(4)],[Frac(2)]]]
ytm = [[[Frac(1),Frac(-2),Frac(1),Frac(1),Frac(-2)],[Frac(2)]],
      [[Frac(3),Frac(2),Frac(-2),Frac(2),Frac(-1)],[Frac(3)]],
      [[Frac(2),Frac(4),Frac(-3),Frac(1),Frac(1)],[Frac(1)]]]
lam = 1
tm = [[[Frac(1),Frac(-2),Frac(2)],[Frac(3)]],
      [[Frac(3),Frac(lam),Frac(-1)],[Frac(4)]],
      [[Frac(2),Frac(4),Frac(-3)],[Frac(2)]]]

def mulrow(a, f): return [x * f for x in a]
def divrow(a, f): return [x / f for x in a]
def addrows(a, b): return [x + y for (x,y) in zip(a,b)]
def rowzero(l): return reduce(lambda x,y: x and y,[i.float() == 0 for i in l])

def explist(l): return string.join(map(lambda x: "%6s "%(str(x)), l))
def printmat(mat):
	for row in mat: print explist(row[0]), "|", explist(row[1])

def rref(mat):
	print "Before rref:" 
	printmat(mat)
	for i in range(len(mat)):
		m = mat[i]

		# Produce a leading one.
		f = m[0][i]
		if f.float() != 1 and f.float() != 0:
			print "Row", i, "divided by", f
			m[0] = divrow(m[0], f)
			m[1] = divrow(m[1], f)
			printmat(mat)

		# Go through the list making zeroes in the column.
		if rowzero(m[0]): continue
		for j in range(len(mat)):
			if j == i: continue
			mp = mat[j]
			f = -mp[0][i]
			if f.float() != 0:
				print "Row", i, "times", f, "added to row", j
				mp[0] = addrows(mp[0], mulrow(m[0], f))
				mp[1] = addrows(mp[1], mulrow(m[1], f))
				printmat(mat)

if __name__ == "__main__":
	print "Should be 18: ", gcf(54, 198)
	f = Frac(5,8) + Frac(3,8)
	print "Should be 1/1: ", f
	f += Frac(3,8)
	print "Should be 11/8: ", f
	rref(tm)

