# -*- coding: utf-8 -*-
"""
matan - модуль предоставляющий математическеи функции
для работы с матричными выражениями
можно сказать аналог numpy
"""
from copy import deepcopy


def minor_det(matrix, r, c):
    """
    Минор матрицы, стоящей на пересечении строки 'r' и столбца 'c'
    """
    # число строк ИСХОДНОЙ матрицы
    rows = len(matrix)
    # число столбцов ИСХОДНОЙ матрицы
    cols = len(matrix[0])

    # Создаём МИНОРНУЮ матрицу (rows-1)*(cols-1), заполняя элементами
    # из старой матрицы, но не учитывая строку 'r' и столбец 'c'
    minor_matrix = []
    # Счётчик строк новой матрицы, чтобы можно было её заполнять
    cnt_row = -1
    # Обходим СТРОКИ ИСХОДНОЙ матрицы
    for i in range(rows):
        # если строка ИСХОДНОЙ матрицы не равна вычеркнутой строке 'r'
        if i != r:
            # Можно заполнять строку новой матрицы
            minor_matrix.append([])
            # Увеличиваем счётчик строк новой матрицы
            cnt_row += 1
            # Обходим СТОЛБЦЫ ИСХОДНОЙ матрицы
            for j in range(cols):
                # если столбец ИСХОДНОЙ матрицы не равен вычеркнутому 'c'
                if j != c:
                    # Можно добавить этот элемент в строку минорной
                    minor_matrix[cnt_row].append(matrix[i][j])

    # После получения минорной матрицы находим её определитель
    # это и есть Минор - результат функции
    return determinant(minor_matrix)


def determinant(matrix):
    """Определитель матрицы"""
    # число строк матрицы
    rows = len(matrix)
    # число столбцов матрицы
    cols = len(matrix[0])

    # Если число строк матрицы = 1, то эта матрица
    # состоит из одного элемента
    # Определитель такой матрицы матрицы будет сам этот элемент
    if (rows == 1):
        return matrix[0][0]

    # Если матрица состоит из более чем одного элемента, то
    # Найдём определитель матрицы, разложив её по первой строке
    # Переменная для хранения результата работы функции
    res = 0
    for j in range(cols):
        # Если элемент строки равен нулю, то просто пропускаем его
        # Чтобы не затрачивать ресурсы на вычисление минорный определитель
        if matrix[0][j] != 0:
            # Вычисляем Алгебраическое дополнение
            # для каждого элемента строки
            A = ((-1)**(0+j)) * minor_det(matrix, r=0, c=j)
            # Прибавляем его к результату функции
            res += A*matrix[0][j]

    # возвращаем резултат работы функции
    return res


def transpose(matrix):
    """Возвращает транспонированную матрицу"""
    # Транспонировать - это значит поменять местами элементы
    # матрицы относительно главной диагонали
    # Будем считать что матрица квадратная

    # Матрица которая будет транспонированной
    matrix = deepcopy(matrix)

    # Число строк матрицы
    size = len(matrix)

    for i in range(size-1):
        # нам нужно обходить элементы только что ВЫШЕ
        # главной диагонали j=i
        for j in range(i+1, size):
            matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]

    # Возвращаем транспонированную матрицу
    return matrix


def inverse_matrix(matrix, det=None):
    """Нахождение обратной матрицы"""
    # Для ускорения вычислений, в функцию можно передать
    # Значение определителя

    # Если определитель матрыцы не был передан
    if not det:
        # Находим определитель матрицы
        det = determinant(matrix)

    # Если определитель матрицы равен нулю
    if det == 0:
        # вызываем исключение, так как не получится найти
        # Обратную матрицу
        raise Exception("Определитель матрицы = 0")

    # Определдитель не нулевой, тогда можно искать обратную матрицу

    # Считаем, что матрица квадратная
    # число строк матрицы
    size = len(matrix)

    # Соберём матрицу Алгебраических дополнений
    A_matrix = []
    # Обходим СТРОКИ ИСХОДНОЙ матрицы
    for i in range(size):
        # Добавляем новую строку к матрице Алг. дополнений
        A_matrix.append([])
        # Обходим столбцы ИСХОДНОЙ матрицы
        for j in range(size):
            # Вычисляем Алгебраическое дополнение
            # для каждого элемента
            A = ((-1)**(i+j)) * minor_det(matrix, r=i, c=j)
            # Добавляем его в матрицу Алг. дополнений
            A_matrix[i].append(A)

    # Транспонируем матрицу Алг. дополнений
    trans_A = transpose(A_matrix)

    # Теперь можно вычислить обратную матрицу
    # По формуле это транспонированная матрица
    # Алгебраических дополений, разделённая на определитель
    # Исходной матрицы
    # То есть все члены транспонированной матрицы Алг. дополнений
    # нужно умножить на число 1/det

    res_matrix = []
    for i in range(size):
        # Добавляем новую строку к результирующей - ОБРАТНОЙ матрице
        res_matrix.append([])
        for j in range(size):
            # Вычисляем элемент обратной матрицы
            el = (1/det) * trans_A[i][j]
            # Добавляем этот элемент к результирующей матрице
            res_matrix[i].append(el)

    # Возвращаем результат работы функции - обратную матрицу
    return res_matrix