import random as rnd
import time
from multiprocessing import Pool

# Функция для генерации квадратной матрицы заданного размера со случайными значениями от 0 до 100
def generateMatrixWithSize(size):
    return [[rnd.randint(0, 100) for i in range(size)] for j in range(size)]

# Функция для вывода матрицы на экран 
def printMatrix(matrix):
    for row in matrix:
        print(*row, sep="\t")


# Функция для вычисления определителя матрицы (стандартный алгоритм, рекурсивный)
def determinantOGWay(matrix):
    size = len(matrix)
    if size == 1:
        return matrix[0][0]
    elif size == 2:
        return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]
    else:
        det = 0
        for c in range(size):
            submatrix = [row[:c] + row[c+1:] for row in matrix[1:]]
            det += ((-1)**c) * matrix[0][c] * determinantOGWay(submatrix)
        return det

# Функция для вычисления минора матрицы (для параллельного алгоритма)
def calculateMinor(matrix, row, col):
    return [r[:col] + r[col+1:] for r in (matrix[:row]+matrix[row+1:])]

# Функция для вычисления части определителя в отдельном процессе (worker для Pool)
def determinantPart(args):
    matrix, col_range_start, col_range_end = args
    size = len(matrix)
    det_part = 0
    for c in range(col_range_start, col_range_end):
        submatrix = calculateMinor(matrix, 0, c)
        det_part += ((-1)**c) * matrix[0][c] * determinantOGWay(submatrix)
    return det_part


# Функция для параллельного вычисления определителя матрицы с использованием процессов
def determinantWithProcesses(matrix, process_count):
    size = len(matrix)

    chunk_size = size // process_count
    remainder = size % process_count

    args = []
    start_c = 0
    for i in range(process_count):
        end_c = start_c + chunk_size + (1 if i < remainder else 0)
        args.append((matrix, start_c, end_c))
        start_c = end_c

    with Pool(processes=process_count) as pool:
        results = pool.map(determinantPart, args)
    
    return sum(results)


if __name__ == "__main__":

    matrix_sizes = [9,10,11] # С большими размерами рекурсивный метод очень долгий, лучше использовать другие алгоритмы (например в numpy)
    num_processes = [1, 2, 4]

    for size in matrix_sizes:
        matrix = generateMatrixWithSize(size)

        # Замер времени для стандартного вычисления определителя
        start_time = time.time()
        det_og = determinantOGWay(matrix)
        end_time = time.time()
        print(f"Обычный режим.  Размер матрицы: {size}x{size}. Время: {end_time - start_time} сек. Определитель: {det_og}")

        # Замер времени для параллельного вычисления определителя с разным количеством процессов
        for processes in num_processes:
            start_time = time.time()
            det_parallel = determinantWithProcesses(matrix, processes)
            end_time = time.time()
            print(f"Параллельный режим. Размер матрицы: {size}x{size}. Количество процессов: {processes}. Время: {end_time - start_time} сек. Определитель: {det_parallel}")

        print("\n\n")