<gh_stars>0
import math
from math import sqrt
import numbers

def zeroes(height, width):
        """
        Creates a matrix of zeroes.
        """
        g = [[0.0 for _ in range(width)] for __ in range(height)]
        return Matrix(g)

def identity(n):
        """
        Creates a n x n identity matrix.
        """
        I = zeroes(n, n)
        for i in range(n):
            I.g[i][i] = 1.0
        return I

class Matrix(object):

    # Constructor
    def __init__(self, grid):
        self.g = grid
        self.h = len(grid)
        self.w = len(grid[0])

    #
    # Primary matrix math methods
    #############################
 
    def determinant(self):
        """
        Calculates the determinant of a 1x1 or 2x2 matrix.
        """
        if not self.is_square():
            raise(ValueError, "Cannot calculate determinant of non-square matrix.")
        if self.h > 2:
            raise(NotImplementedError, "Calculating determinant not implemented for matrices largerer than 2x2.")
        
        # creating variable for result_det
        result_det = 0.0
        
        # check the dimension of Matrix
        # 1x1 or 2x2 ?
        if (self.h ==1):
            result_det = self.g[0]
        else:
            result_det = (self.g[0][0]*self.g[1][1]) - (self.g[0][1]*self.g[1][0])

        # return result
        return result_det

    def trace(self):
        """
        Calculates the trace of a matrix (sum of diagonal entries).
        """
        if not self.is_square():
            raise(ValueError, "Cannot calculate the trace of a non-square matrix.")

        # creating variable for result_tra
        result_tra = 0.0
        
        # count the sum of diagonal entries
        for i in range(self.w):
            result_tra += self.g[i][i]

        # return result
        return result_tra

    def inverse(self):
        """
        Calculates the inverse of a 1x1 or 2x2 Matrix.
        """
        if not self.is_square():
            raise(ValueError, "Non-square Matrix does not have an inverse.")
        if self.h > 2:
            raise(NotImplementedError, "inversion not implemented for matrices larger than 2x2.")

        # creating variable for result_inv, matrix dimension of self
        result_inv = zeroes(self.h, self.w)
        
        # check the dimension of Matrix
        # 1x1 or 2x2 ?
        if (self.w == 1):
            result_inv = (1/self.g[0][0])
        else:
            result_inv[0][0] = (self.g[1][1]) * (1/self.determinant())
            result_inv[0][1] = (-1) * (self.g[0][1]) * (1/self.determinant())
            result_inv[1][0] = (-1) * (self.g[1][0]) * (1/self.determinant())
            result_inv[1][1] = (self.g[0][0]) * (1/self.determinant())

        # return result
        return result_inv

    def T(self):
        """
        Returns a transposed copy of this Matrix.
        """
        # creating variable for result_T, matrix dimension of self
        result_T = zeroes(self.w, self.h)
        
        # transpose process        
        for i in range(self.h):
            for j in range(self.w):
                result_T[j][i] = self.g[i][j]

        # return result
        return result_T

    def is_square(self):
        return self.h == self.w

    #
    # Begin Operator Overloading
    ############################
    def __getitem__(self,idx):
        """
        Defines the behavior of using square brackets [] on instances
        of this class.

        Example:

        > my_matrix = Matrix([ [1, 2], [3, 4] ])
        > my_matrix[0]
          [1, 2]

        > my_matrix[0][0]
          1
        """
        return self.g[idx]

    def __repr__(self):
        """
        Defines the behavior of calling print on an instance of this class.
        """
        s = ""
        for row in self.g:
            s += " ".join(["{} ".format(x) for x in row])
            s += "\n"
        return s

    def __add__(self,other):
        """
        Defines the behavior of the + operator
        """
        if self.h != other.h or self.w != other.w:
            raise(ValueError, "Matrices can only be added if the dimensions are the same") 
            
        # creating variable for result_add, matrix dimension of self
        result_add = zeroes(self.h, self.w)

        # adding each element from self and other
        for i in range(self.h):
            for j in range(self.w):
                result_add[i][j] = self.g[i][j] + other.g[i][j]

        # return result
        return result_add
      
    def __neg__(self):
        """
        Defines the behavior of - operator (NOT subtraction)

        Example:

        > my_matrix = Matrix([ [1, 2], [3, 4] ])
        > negative  = -my_matrix
        > print(negative)
          -1.0  -2.0
          -3.0  -4.0
        """
        # creating variable for result_neg, matrix dimension of self
        result_neg = zeroes(self.h, self.w)

        # process each element
        for i in range(self.h):
            for j in range(self.w):
                result_neg[i][j] = (self.g[i][j]) * (-1)

        # return result
        return result_neg

    def __sub__(self, other):
        """
        Defines the behavior of - operator (as subtraction)
        """
        # creating variable for result_sub, matrix dimension of self
        result_sub = zeroes(self.h, self.w)

        # subtraction each element from self and other
        for i in range(self.h):
            for j in range(self.w):
                result_sub[i][j] = self.g[i][j] - other.g[i][j]

        # return result
        return result_sub

    def __mul__(self, other):
        """
        Defines the behavior of * operator (matrix multiplication)
        """
        # creating variable for result_mul, matrix dimension of self
        result_mul = zeroes(self.h, other.w)
        
        # multiplication each element from self and other
        for i in range(self.h):
            for j in range(other.w):
                for k in range(other.h):
                    result_mul[i][j] += self.g[i][k] * other.g[k][j]

        # return result
        return result_mul

    def __rmul__(self, other):
        """
        Called when the thing on the left of the * is not a matrix.

        Example:

        > identity = Matrix([ [1,0], [0,1] ])
        > doubled  = 2 * identity
        > print(doubled)
          2.0  0.0
          0.0  2.0
        """
        if isinstance(other, numbers.Number):
            result_rmul = self
            
            # process each element
            for i in range(self.h):
                for j in range(self.w):
                    result_rmul[i][j] *= other

            # return result
            return result_rmul