diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml new file mode 100644 index 0000000..bb29a05 --- /dev/null +++ b/.github/workflows/pipeline.yaml @@ -0,0 +1,28 @@ +name: Test Matrix + +on: + push: + branches: [ "dev-refactor", "dev", "master" ] + pull_request: + branches: [ "dev-refactor", "dev", "master" ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Simple Python setup + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run tests + run: | + python -m pytest tests/ -v -s \ No newline at end of file diff --git a/.gitignore b/.gitignore index e52acf8..2108fd4 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ Thumbs.db *.ppt *.docx +*.zip diff --git a/app.py b/app.py index c8ce4d4..013246c 100644 --- a/app.py +++ b/app.py @@ -3,414 +3,164 @@ app = Flask(__name__) -@app.route('/') -def index(): - """ - Página inicial/menu. - Irá renderizar o template 'index.html'. - """ - return render_template('index.html') +# Reader Function -#Soma&Subtracao -@app.route('/sum', methods=['GET', 'POST']) -def sum_sub(): +def handle_matrix_operation(operation_func, *args, **spargs): """ - Soma/Subtração de matrizes. - No POST, processa o cálculo. No GET, mostra o formulário. + Generic handler for matrix operations. + Catches errors and returns proper JSON response. """ - if request.method == 'POST': - try: - if not request.data: - return jsonify({'error': 'No data received'}), 400 - - data = request.get_json(force=True) - - if not data: - return jsonify({'error': 'Invalid JSON'}), 400 - - if 'rows' not in data or 'cols' not in data: - return jsonify({'error': 'Dimensões não fornecidas'}), 400 - - if 'matrix_a' not in data or 'matrix_b' not in data: - return jsonify({'error': 'Matrizes não fornecidas'}), 400 - - matrix_a = Matrix(data['rows'], data['cols'], data['matrix_a']) - matrix_b = Matrix(data['rows'], data['cols'], data['matrix_b']) - - result = matrix_a.add(matrix_b) if data['operation'] == 'sum' else matrix_a.subtract(matrix_b) - op_name = 'Soma' if data['operation'] == 'sum' else 'Subtração' - - return jsonify({ - 'matrix_a': matrix_a.to_list(), - 'matrix_b': matrix_b.to_list(), - 'result': result['data'], - 'operation': op_name, - 'dimensions': f"{data['rows']}x{data['cols']}" - }) + try: + result = operation_func(*args, **spargs) + + # If result is a Matrix object, convert to list + if isinstance(result, Matrix): + return jsonify({'result': result.to_list()}) + # if result is already a dict, return it as is + elif isinstance(result, dict): + return jsonify(result) + # else return normal result + else: + return jsonify({'result': result}) - except Exception as e: - return jsonify({'error': str(e)}), 400 - - return render_template('sum_sub.html') + except ValueError as e: + return jsonify({'error': str(e)}), 400 + except Exception as e: + return jsonify({'error': f'Erro: {str(e)}'}), 500 -@app.route('/scalar', methods=['GET', 'POST']) -def scalar(): - """ - Multiplicação de matriz por escalar. - """ - if request.method == "POST": - try: - if not request.data: - return jsonify({'error': 'No data received'}), 400 - data = request.get_json(force=True) - print(data) - if not data: - return jsonify({'error': 'Invalid JSON'}), 400 - - if 'rows' not in data or 'cols' not in data: - return jsonify({'error': 'Dimensões não fornecidas'}), 400 - - if 'matrix_a' not in data: - return jsonify({'error': 'Matriz não fornecida'}), 400 - - if 'scalar' not in data: - return jsonify({'error': 'Escalar não fornecido'}), 400 - - matrix_a = Matrix(data['rows'], data['cols'], data['matrix_a']) - result = matrix_a.scalar_multiply(data['scalar']) +# App Routes - return jsonify({ - 'matrix_a': matrix_a.to_list(), - 'result': result['data'], - 'dimensions': f"{data['rows']}x{data['cols']}" - }) - - except Exception as e: - return jsonify({'error': str(e)}), 400 - return render_template('scalar.html') +@app.route('/') +def index(): + """Página inicial/menu.""" + return render_template('index.html') + +@app.route('/sum-sub', methods=['GET', 'POST']) +def sum_sub(): + """Soma/Subtração de matrizes.""" + if request.method == 'GET': + return render_template('sum_sub.html') + + data = request.get_json() + matrix_a = Matrix(data['rows'], + data['cols'], + data['matrix_a']) + + matrix_b = Matrix(data['rows'], + data['cols'], + data['matrix_b']) + + if data['operation'] == 'add': + return handle_matrix_operation(matrix_a.add, matrix_b) + elif data['operation'] == 'subtract': + return handle_matrix_operation(matrix_a.subtract, matrix_b) + else: + return jsonify({'error': 'Operação inválida'}), 400 -#Multiplicacao @app.route('/multiply', methods=['GET', 'POST']) def multiply(): - if request.method == 'POST': - try: - - data = request.get_json(force=True) - - if not data: - return jsonify({'error': 'Invalid JSON'}), 400 - - if 'matrix_a' not in data or 'matrix_b' not in data: - return jsonify({'error': 'Matrizes não fornecidas'}), 400 - - matrix_a_data = data['matrix_a'] - matrix_b_data = data['matrix_b'] - - rows_a = len(matrix_a_data) - cols_a = len(matrix_a_data[0]) if matrix_a_data else 0 - rows_b = len(matrix_b_data) - cols_b = len(matrix_b_data[0]) if matrix_b_data else 0 - if cols_a != rows_b: - return jsonify({ - 'error': f'Matrizes incompatíveis: A.cols({cols_a}) ≠ B.rows({rows_b})' - }), 400 - - matrix_a = Matrix(rows_a, cols_a, matrix_a_data) - matrix_b = Matrix(rows_b, cols_b, matrix_b_data) - - result = matrix_a.multiply(matrix_b) - - return jsonify({ - 'matrix_a': matrix_a.to_list(), - 'matrix_b': matrix_b.to_list(), - 'result': result.to_list(), - }) - - except Exception as e: - return jsonify({'error': str(e)}), 400 + """Multiplicação de matrizes.""" + if request.method == 'GET': + return render_template('multiply.html') + + data = request.get_json() + matrix_a = Matrix(data['rows_a'], + data['cols_a'], + data['matrix_a']) + + matrix_b = Matrix(data['rows_b'], + data['cols_b'], + data['matrix_b']) - return render_template('multiply.html') + return handle_matrix_operation(matrix_a.multiply, matrix_b) -@app.route('/determinant', methods=['GET', 'POST']) +@app.route('/transpose', methods=['GET', 'POST']) +def transpose(): + """Transpor uma Matriz""" + if request.method == 'GET': + return render_template('transpose.html') + + data = request.get_json() + matrix = Matrix(data['rows'], + data['cols'], + data['matrix']) + + return handle_matrix_operation(matrix.transpose) +@app.route('/scalar', methods=['GET', 'POST']) +def scalar(): + """Multiplicação de matriz por escalar.""" + if request.method == 'GET': + return render_template('scalar.html') + + data = request.get_json() + matrix = Matrix(data['rows'], + data['cols'], + data['matrix']) + + return handle_matrix_operation(matrix.scalar_multiply, data['scalar']) + +@app.route('/determinant', methods=['GET', 'POST']) def determinant(): - if request.method == 'POST': - try: - data = request.get_json(force=True) - - if not data: - return jsonify({'error': 'Invalid JSON'}), 400 - - if 'size' not in data or 'matrix' not in data: - return jsonify({'error': 'Tamanho ou matriz não fornecidos'}), 400 - - size = data['size'] - matrix_data = data['matrix'] - method = data.get('method', 'auto') - - # Validar se é matriz quadrada - if len(matrix_data) != size or any(len(row) != size for row in matrix_data): - return jsonify({'error': 'A matriz deve ser quadrada'}), 400 - - # Criar objeto Matrix - matrix = Matrix(size, size, matrix_data) - - if not matrix.is_square(): - return jsonify({'error': 'Determinante só existe para matrizes quadradas'}), 400 - - # Calcular determinante - try: - determinant_value = matrix.determinant() - - # Arredondar para evitar -0.0000000000001 - if abs(determinant_value) < 1e-10: - determinant_value = 0.0 - - result = { - 'matrix': matrix.to_list(), - 'determinant': determinant_value, - 'size': size, - 'method': method, - 'is_singular': determinant_value == 0 - } - - # Adicionar detalhes do método se for pequena - if size <= 3: - if size == 2: - a, b = matrix_data[0][0], matrix_data[0][1] - c, d = matrix_data[1][0], matrix_data[1][1] - result['method_details'] = f"2×2: det = ad - bc = ({a}×{d}) - ({b}×{c}) = {a*d} - {b*c} = {determinant_value}" - elif size == 3: - a, b, c = matrix_data[0][0], matrix_data[0][1], matrix_data[0][2] - d, e, f = matrix_data[1][0], matrix_data[1][1], matrix_data[1][2] - g, h, i = matrix_data[2][0], matrix_data[2][1], matrix_data[2][2] - result['method_details'] = f"3×3 (Sarrus):\n" - result['method_details'] += f" (a*e*i + b*f*g + c*d*h) - (c*e*g + b*d*i + a*f*h)\n" - result['method_details'] += f" = ({a}×{e}×{i} + {b}×{f}×{g} + {c}×{d}×{h}) - ({c}×{e}×{g} + {b}×{d}×{i} + {a}×{f}×{h})\n" - result['method_details'] += f" = ({a*e*i} + {b*f*g} + {c*d*h}) - ({c*e*g} + {b*d*i} + {a*f*h})\n" - result['method_details'] += f" = {determinant_value}" - - return jsonify(result) - - except Exception as e: - return jsonify({'error': f'Erro ao calcular determinante: {str(e)}'}), 400 - - except Exception as e: - return jsonify({'error': str(e)}), 400 + """Cálculo de determinante.""" + if request.method == 'GET': + return render_template('determinant.html') + + data = request.get_json() + matrix = Matrix(data['size'], + data['size'], + data['matrix']) - return render_template('determinant.html') + return handle_matrix_operation(matrix.determinant) @app.route('/inverse', methods=['GET', 'POST']) def inverse(): - if request.method == 'POST': - try: - data = request.get_json(force=True) - - if not data: - return jsonify({'error': 'Invalid JSON'}), 400 - - if 'size' not in data or 'matrix' not in data: - return jsonify({'error': 'Tamanho ou matriz não fornecidos'}), 400 - - size = data['size'] - matrix_data = data['matrix'] - method = data.get('method', 'adjugate') # 'adjugate' ou 'gauss-jordan' - - # Validar se é matriz quadrada - if len(matrix_data) != size or any(len(row) != size for row in matrix_data): - return jsonify({'error': 'A matriz deve ser quadrada'}), 400 - - # Criar objeto Matrix - matrix = Matrix(size, size, matrix_data) - - # Verificar se é quadrada - if not matrix.is_square(): - return jsonify({'error': 'Matriz inversa só existe para matrizes quadradas'}), 400 - - # Calcular determinante primeiro - determinant_value = matrix.determinant() - - # Verificar se é singular - if abs(determinant_value) < 1e-10: - result = { - 'matrix': matrix.to_list(), - 'determinant': 0.0, - 'is_singular': True, - 'error': 'Matriz singular (determinante = 0). Não tem inversa.', - 'size': size - } - return jsonify(result) - - # Calcular inversa com método escolhido - if method == 'gauss-jordan': - inverse_matrix = matrix.gauss_jordan_inverse() - else: - inverse_matrix = matrix.inverse() - - # Verificar o resultado - verification = matrix.verify_inverse(inverse_matrix) - - # Preparar resposta - result = { - 'matrix': matrix.to_list(), - 'inverse': inverse_matrix.to_list(), - 'determinant': determinant_value, - 'is_singular': False, - 'size': size, - 'method': method, - 'verification': verification, - 'verification_ok': verification['is_correct'] - } - - # Adicionar detalhes para matrizes 2x2 - if size == 2: - a, b = matrix_data[0][0], matrix_data[0][1] - c, d = matrix_data[1][0], matrix_data[1][1] - det = determinant_value - result['formula_details'] = f"Inversa 2×2: (1/det) × [[d, -b], [-c, a]] = (1/{det}) × [[{d}, -{b}], [-{c}, {a}]]" - - return jsonify(result) - - except ValueError as e: - return jsonify({'error': str(e)}), 400 - except Exception as e: - return jsonify({'error': f'Erro ao calcular inversa: {str(e)}'}), 500 + """Cálculo de matriz inversa.""" + if request.method == 'GET': + return render_template('inverse.html') + + data = request.get_json() + matrix = Matrix(data['size'], + data['size'], + data['matrix']) - return render_template('inverse.html') + return handle_matrix_operation(matrix.inverse) @app.route('/encrypt', methods=['GET', 'POST']) def encrypt(): - """ - Criptografia de mensagens usando multiplicação matricial. - """ - if request.method == 'POST': - try: - data = request.get_json(force=True) - - if not data: - return jsonify({'error': 'Invalid JSON'}), 400 - - message = data.get('message', '') - matrix_data = data.get('encoding_matrix', []) - operation = data.get('operation', 'encrypt') - - if not message: - return jsonify({'error': 'Mensagem não pode estar vazia'}), 400 - - if not matrix_data: - return jsonify({'error': 'Matriz codificadora não fornecida'}), 400 - - size = len(matrix_data) - - # Validar matriz quadrada - if any(len(row) != size for row in matrix_data): - return jsonify({'error': 'A matriz deve ser quadrada'}), 400 - - # Criar matriz codificadora - encoding_matrix = Matrix(size, size, matrix_data) - - # Verificar se a matriz é invertível - det = encoding_matrix.determinant() - if abs(det) < 1e-10: - return jsonify({ - 'error': 'Matriz singular (det=0). Escolha uma matriz invertível para criptografia.' - }), 400 - - if operation == 'encrypt': - # Criptografar - result = encoding_matrix.encrypt_message(message, encoding_matrix) - - return jsonify({ - 'operation': 'encrypt', - 'original_message': message, - 'encoding_matrix': encoding_matrix.to_list(), - 'message_matrix': result['message_matrix'].to_list(), - 'encrypted_matrix': result['encrypted_matrix'].to_list(), - 'numeric_sequence': result['numeric_sequence'], - 'determinant': det - }) - - elif operation == 'decrypt': - # Para descriptografar, precisamos da matriz criptografada - encrypted_data = data.get('encrypted_matrix', []) - - if not encrypted_data: - return jsonify({'error': 'Matriz criptografada não fornecida'}), 400 - - # Criar matriz criptografada - encrypted_rows = len(encrypted_data) - encrypted_cols = len(encrypted_data[0]) if encrypted_data else 0 - encrypted_matrix = Matrix(encrypted_rows, encrypted_cols, encrypted_data) - - # Calcular matriz decodificadora (inversa) - decoding_matrix = encoding_matrix.inverse() - - # Descriptografar - result = encoding_matrix.decrypt_message(encrypted_matrix, decoding_matrix) - - return jsonify({ - 'operation': 'decrypt', - 'encrypted_matrix': encrypted_matrix.to_list(), - 'decoding_matrix': decoding_matrix.to_list(), - 'message_matrix': result['message_matrix'].to_list(), - 'decrypted_message': result['decrypted_message'], - 'numeric_sequence': result['numeric_sequence'] - }) - - else: - return jsonify({'error': 'Operação inválida. Use "encrypt" ou "decrypt"'}), 400 - - except ValueError as e: - return jsonify({'error': str(e)}), 400 - except Exception as e: - return jsonify({'error': f'Erro: {str(e)}'}), 500 + """Criptografia de mensagens usando multiplicação matricial.""" + if request.method == 'GET': + return render_template('encrypt.html') + + data = request.get_json() + encoding_matrix = Matrix(data['size'], + data['size'], + data['encoding_matrix']) - return render_template('encrypt.html') + result = encoding_matrix.encrypt_message(data['message']) + + return jsonify({ + 'encrypted_matrix': result['encrypted_matrix'].to_list(), + 'numeric_sequence': result['numeric_sequence'] + }) + +@app.route('/decrypt', methods=['GET', 'POST']) +def decrypt(): + """Descriptografia de mensagens usando multiplicação matricial.""" + if request.method == 'GET': + return render_template('decrypt.html') + + data = request.get_json() + encoding_matrix = Matrix(data['size'], + data['size'], + data['encoding_matrix']) + + encrypted_matrix = Matrix(data['size'], + data['encrypted_cols'], + data['encrypted_matrix']) + + return handle_matrix_operation(encoding_matrix.decrypt_message, encrypted_matrix) -@app.route('/check_matrix', methods=['POST']) -def check_matrix(): - """Endpoint para verificar se uma matriz é válida para criptografia.""" - try: - data = request.get_json(force=True) - - matrix_data = data['matrix'] - size = data['size'] - - # Validar tamanho - if len(matrix_data) != size or any(len(row) != size for row in matrix_data): - return jsonify({'valid': False, 'error': 'Matriz não é quadrada'}) - - # Criar objeto Matrix - matrix = Matrix(size, size, matrix_data) - - # Verificar se é quadrada - if not matrix.is_square(): - return jsonify({'valid': False, 'error': 'Matriz não é quadrada'}) - - # Calcular determinante - try: - det = matrix.determinant() - if abs(det) < 1e-10: - return jsonify({ - 'valid': False, - 'error': 'Matriz singular (determinante = 0)', - 'determinant': det - }) - - return jsonify({ - 'valid': True, - 'determinant': det, - 'message': 'Matriz válida para criptografia' - }) - - except Exception as e: - return jsonify({'valid': False, 'error': f'Erro ao calcular determinante: {str(e)}'}) - - except Exception as e: - return jsonify({'valid': False, 'error': str(e)}) - -# --- Execução do Servidor --- if __name__ == '__main__': - # Em ambiente de desenvolvimento, usar debug=True - app.run(debug=True) \ No newline at end of file + app.run(debug=True, host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/core/matrix.py b/core/matrix.py index 33ffdcb..4223d1c 100644 --- a/core/matrix.py +++ b/core/matrix.py @@ -1,384 +1,280 @@ class Matrix: - """A custom Matrix class implementing basic linear algebra operations.""" + + # Characters to numbers mapping for encryption/decryption + CHAR_TO_NUM = { + 'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8, 'I': 9, 'J': 10, + 'K': 11, 'L': 12, 'M': 13, 'N': 14, 'O': 15, 'P': 16, 'Q': 17, 'R': 18, 'S': 19, + 'T': 20, 'U': 21, 'V': 22, 'W': 23, 'X': 24, 'Y': 25, 'Z': 26, + '.': 27, ',': 28, ' ': 29, '_': 29, '-': 30 + } + + # Reverse mapping from numbers to characters using CHAR_TO_NUM + NUM_TO_CHAR = {v: k for k, v in CHAR_TO_NUM.items()} + # Ensure ' ' space maps to 29 as well + NUM_TO_CHAR[29] = ' ' def __init__(self, rows, cols, data=None): + # Intialize matrix with given rows and columns self.rows = rows self.cols = cols - if data is None: - self.data = [[0 for _ in range(cols)] for _ in range(rows)] + self.data = [] + for i in range(rows): + row = [] + for j in range(cols): + row.append(0) + self.data.append(row) else: - if len(data) != rows or any(len(row) != cols for row in data): - raise ValueError(f"Dimensões incorretas: esperado {rows}×{cols}") - self.data = [row[:] for row in data] + if not isinstance(data, list): + raise ValueError("Data deve ser uma lista") + if len(data) != rows: + raise ValueError(f"Número de linhas incorreto: esperado {rows}, recebido {len(data)}") + + for i, row in enumerate(data): + if not isinstance(row, list): + raise ValueError(f"Linha {i} não é uma lista") + if len(row) != cols: + raise ValueError(f"Linha {i}: esperado {cols} colunas, recebido {len(row)} colunas") + + self.data = [] + for row in data: + self.data.append(row[:]) def __str__(self): - return "\n".join("[" + " ".join(f"{x:8.2f}" for x in row) + "]" for row in self.data) + # String representation of the matrix + result = [] + for row in self.data: + row_str = "[" + for x in row: + row_str += f"{x:8.2f} " + row_str += "]" + result.append(row_str) + return "\n".join(result) def __repr__(self): - """Return representation of the matrix.""" - return f"Matrix({self.rows}x{self.cols})" + # Official representation in matrix form + return f"Matrix({self.rows}×{self.cols})" + + def to_list(self): + # Convert matrix data to a list of lists + result = [] + for row in self.data: + result.append(row[:]) + return result def get_element(self, row, col): - """Get element at position [row][col] (1-indexed).""" - return self.data[row - 1][col - 1] + # Get element at specified row and column + pass def set_element(self, row, col, value): - """Set element at position [row][col] (1-indexed).""" - self.data[row - 1][col - 1] = value + # Set element at specified row and column + pass + + def is_square(self): + # Check if the matrix is square (rows == cols) + return self.rows == self.cols def dimensions(self): - """Return dimensions as tuple (rows, cols).""" + # Return the dimensions of the matrix as (rows, cols) return (self.rows, self.cols) - def is_same_dimension(self, other): - """Check if two matrices have the same dimensions.""" + def add(self, other): + # Add two matrices (A + B) if not isinstance(other, Matrix): - raise TypeError("Can only compare with another Matrix") - return self.rows == other.rows and self.cols == other.cols - - def to_list(self): - return [row[:] for row in self.data] - - def is_square(self): - # Validar que a matriz é quadrada (mesmo número de linhas e colunas) - return self.cols == self.rows - - def _get_minor(self, row, col): - # ter a matriz menor, remover linha e coluna para calcular determinante com metodo laplace - minor_data = [] + raise TypeError("Só é possível adicionar outra Matrix") + if self.rows != other.rows or self.cols != other.cols: + raise ValueError(f"Dimensões incompatíveis: {self.dimensions()} vs {other.dimensions()}") + + result_data = [] for i in range(self.rows): - if i == row: - continue - new_row = [] + row = [] for j in range(self.cols): - if j == col: - continue - new_row.append(self.data[i][j]) - minor_data.append(new_row) - return Matrix(self.rows - 1, self.cols - 1, minor_data) - - def add(self, other): - """Add two matrices: C[i][j] = A[i][j] + B[i][j]""" - if not self.is_same_dimension(other): - raise ValueError(f"Dimensões incompatíveis: {self.dimensions()} vs {other.dimensions()}") - result = [[self.data[i][j] + other.data[i][j] for j in range(self.cols)] for i in range(self.rows)] - return {'data': result} + row.append(self.data[i][j] + other.data[i][j]) + result_data.append(row) + return Matrix(self.rows, self.cols, result_data) def subtract(self, other): - """Subtract two matrices: C[i][j] = A[i][j] - B[i][j]""" - if not self.is_same_dimension(other): + # Subtract two matrices (A - B) + if not isinstance(other, Matrix): + raise TypeError("Só é possível subtrair outra Matrix") + if self.rows != other.rows or self.cols != other.cols: raise ValueError(f"Dimensões incompatíveis: {self.dimensions()} vs {other.dimensions()}") - result = [[self.data[i][j] - other.data[i][j] for j in range(self.cols)] for i in range(self.rows)] - return {'data': result} + + result_data = [] + for i in range(self.rows): + row = [] + for j in range(self.cols): + row.append(self.data[i][j] - other.data[i][j]) + result_data.append(row) + return Matrix(self.rows, self.cols, result_data) def scalar_multiply(self, scalar): - result = [[self.data[i][j] * scalar for j in range(self.cols)] for i in range(self.rows)] - return {'data': result} + # Multiply matrix by a scalar value (scalar number * A) + result_data = [] + for i in range(self.rows): + row = [] + for j in range(self.cols): + row.append(self.data[i][j] * scalar) + result_data.append(row) + return Matrix(self.rows, self.cols, result_data) def multiply(self, other): + # Multiply two matrices (A * B) if not isinstance(other, Matrix): - raise TypeError("Operando deve ser uma Matrix") - + raise ValueError("Só é possivel multiplicar por outra Matrix") if self.cols != other.rows: - raise ValueError(f"Não é possível multiplicar: A.cols({self.cols}) ≠ B.rows({other.rows})") - - # Inicializar matriz resultado - result = Matrix(self.rows, other.cols) + raise ValueError(f"Dimensões incompatíveis para multiplicar | Tem de ser igual {self.cols} = {other.rows}") - # Multiplicação matricial + result_data = [] for i in range(self.rows): + row = [] for j in range(other.cols): - soma = 0 - for k in range(self.cols): # ou other.rows - soma += self.data[i][k] * other.data[k][j] - result.data[i][j] = soma - - return result + sum_value = 0 + for k in range(self.cols): + sum_value += self.data[i][k] * other.data[k][j] + row.append(sum_value) + result_data.append(row) + return Matrix(self.rows, other.cols, result_data) + + def transpose(self): + # Transpose the matrix (A^T) + result_data = [] + for i in range(self.cols): + row = [] + for j in range(self.rows): + row.append(self.data[j][i]) + result_data.append(row) + return Matrix(self.cols, self.rows, result_data) def determinant(self): - # calcular determinante com o uso de laplace + # Calculate the determinant of the matrix (det(A)) if not self.is_square(): - raise ValueError("Determinante so tem pa matrizes quadradas") - - # matrizes 1x1 + raise ValueError("Determinante só funciona para matrizes quadradas") + if self.rows == 1: return self.data[0][0] - # matrizes 2x2 if self.rows == 2: - return self.data[0][0] * self.data[1][1] - self.data[0][1] * self.data[1][0] + # (a*d - b*c) + return self.data[0][0] * self.data[1][1] - self.data[0][1] * self.data [1][0] - # matrizes 3x3 (regra sarras) if self.rows == 3: - a, b, c = self.data[0][0], self.data[0][1], self.data[0][2] - d, e, f = self.data[1][0], self.data[1][1], self.data[1][2] - g, h, i = self.data[2][0], self.data[2][1], self.data[2][2] - - # aquela cena de diagonal principal vezes diagonal secundaria + # Sarrus Rule criss cross. + a, b, c = self.data[0] + d, e, f = self.data[1] + g, h, i = self.data[2] + return (a*e*i + b*f*g + c*d*h) - (c*e*g + b*d*i + a*f*h) - # matrizes 4x4 ou maiores (cpfatores) + # Laplace method for any of the above & +(3x3) matrices det = 0 for j in range(self.cols): minor = self._get_minor(0, j) - cofactor = ((-1) ** j) * self.data[0][j] * minor.determinant() - det += cofactor + sign = (-1) ** j + det += sign * self.data[0][j] * minor.determinant() return det - - def get_cofactor(self, row, col): - """Calculate the cofactor at position (row, col).""" - minor = self._get_minor(row, col) - sign = 1 if (row + col) % 2 == 0 else -1 - return sign * minor.determinant() - - def adjugate(self): - """Calculate the adjugate (adjoint) matrix.""" - if not self.is_square(): - raise ValueError("Adjunta só existe para matrizes quadradas") - - n = self.rows - adj_data = [[0 for _ in range(n)] for _ in range(n)] - for i in range(n): - for j in range(n): - # adjunta é a transposta da matriz de cofatores - adj_data[j][i] = self.get_cofactor(i, j) - - return Matrix(n, n, adj_data) - - def transpose(self): - """Return the transpose of the matrix.""" - transposed_data = [[self.data[j][i] for j in range(self.rows)] for i in range(self.cols)] - return Matrix(self.cols, self.rows, transposed_data) - - def identity(self, n=None): - """Create an identity matrix of size n×n.""" - if n is None: - n = self.rows - identity_data = [[1 if i == j else 0 for j in range(n)] for i in range(n)] - return Matrix(n, n, identity_data) - + def inverse(self): - """Calculate matrix inverse using adjugate method.""" - # Verificar se é quadrada + # Calculate the inverse of the matrix (A^-1) if not self.is_square(): raise ValueError("Matriz inversa só existe para matrizes quadradas") - # Calcular determinante - det = self.determinant() - - # Verificar se é singular - if abs(det) < 1e-10: - raise ValueError("Matriz singular (determinante = 0). Não tem inversa.") - - n = self.rows - - # matriz 1x1 - if n == 1: - inverse_data = [[1 / self.data[0][0]]] - return Matrix(1, 1, inverse_data) - - # matriz 2x2 - if n == 2: - a, b = self.data[0][0], self.data[0][1] - c, d = self.data[1][0], self.data[1][1] - - inverse_data = [ - [d / det, -b / det], - [-c / det, a / det] - ] - return Matrix(2, 2, inverse_data) - - # Matrizes maiores: método da adjunta - # A⁻¹ = (1/det(A)) * adj(A) - adj = self.adjugate() - scalar = 1 / det - - # Multiplicar adjunta pelo escalar 1/det - inverse_data = [[adj.data[i][j] * scalar for j in range(n)] for i in range(n)] - - # Arredondar valores próximos de zero - for i in range(n): - for j in range(n): - if abs(inverse_data[i][j]) < 1e-10: - inverse_data[i][j] = 0.0 - - return Matrix(n, n, inverse_data) - - def gauss_jordan_inverse(self): - """Calculate inverse using Gauss-Jordan elimination (método alternativo).""" - if not self.is_square(): - raise ValueError("Matriz deve ser quadrada para ter inversa") - - n = self.rows det = self.determinant() - if abs(det) < 1e-10: - raise ValueError("Matriz singular (determinante = 0)") - - # Criar matriz aumentada [A|I] - augmented = [[0 for _ in range(2*n)] for _ in range(n)] - - for i in range(n): - for j in range(n): - augmented[i][j] = self.data[i][j] - augmented[i][n + i] = 1 + raise ValueError("Matriz singular (det=0), não possui inversa") - # Aplicar eliminação de Gauss-Jordan - for i in range(n): - # Pivot - pivot = augmented[i][i] - - # Normalizar linha - for j in range(2*n): - augmented[i][j] /= pivot - - # Eliminar outras linhas - for k in range(n): - if k != i: - factor = augmented[k][i] - for j in range(2*n): - augmented[k][j] -= factor * augmented[i][j] - - # Extrair a inversa da parte direita - inverse_data = [[augmented[i][n + j] for j in range(n)] for i in range(n)] - - # Arredondar valores próximos de zero - for i in range(n): - for j in range(n): - if abs(inverse_data[i][j]) < 1e-10: - inverse_data[i][j] = 0.0 - - return Matrix(n, n, inverse_data) - - def verify_inverse(self, inverse_matrix): - """Verify that A × A⁻¹ = I.""" - n = self.rows - - # Multiplicar A × A⁻¹ - product = self.multiply(inverse_matrix) - - # Criar matriz identidade - identity = self.identity(n) - - # Verificar se o produto é aproximadamente a identidade - is_correct = True - max_error = 0 - - for i in range(n): - for j in range(n): - error = abs(product.data[i][j] - identity.data[i][j]) - max_error = max(max_error, error) - if error > 1e-8: - is_correct = False - - return { - 'is_correct': is_correct, - 'product_matrix': product.to_list(), - 'identity_matrix': identity.to_list(), - 'max_error': max_error - } + adj = self._adjugate() + return adj.scalar_multiply(1 / det) + + def _get_minor(self, row, col): + # Get minor matrix after removing specified row and column (for determinant calculation) + minor_data = [] + for i in range(self.rows): + if i != row: + row_data = [] + for j in range(self.cols): + if j != col: + row_data.append(self.data[i][j]) + minor_data.append(row_data) + return Matrix(self.rows - 1, self.cols - 1, minor_data) + + def _get_cofactor(self, row, col): + # Get cofactor of element at specified row and column (for inverse calculation) + minor = self._get_minor(row, col) + sign = (-1) ** (row + col) + return sign * minor.determinant() + + def _adjugate(self): + # Calculate the adjugate of the matrix (for inverse calculation) + cofactor_data = [] + for i in range(self.rows): + row = [] + for j in range(self.cols): + row.append(self._get_cofactor(i, j)) + cofactor_data.append(row) + cofactor_matrix = Matrix(self.rows, self.cols, cofactor_data) + return cofactor_matrix.transpose() - def encrypt_message(self, message, encoding_matrix): - """ - Encrypt a message using matrix multiplication. - - Args: - message: String to encrypt (letters, spaces, punctuation) - encoding_matrix: Matrix object used for encoding (must be square) - - Returns: - dict with 'encrypted_matrix', 'message_matrix', 'numeric_sequence' - """ - if not isinstance(encoding_matrix, Matrix): - raise ValueError("Encoding matrix must be a Matrix object") - - if not encoding_matrix.is_square(): - raise ValueError("Encoding matrix must be square") - - # Mapping de letras para números - char_map = { - 'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8, 'I': 9, - 'J': 10, 'K': 11, 'L': 12, 'M': 13, 'N': 14, 'O': 15, 'P': 16, 'Q': 17, - 'R': 18, 'S': 19, 'T': 20, 'U': 21, 'V': 22, 'W': 23, 'X': 24, 'Y': 25, - 'Z': 26, ' ': 27, '.': 28, 'Ú': 29, 'Ã': 30, 'Ç': 31, 'Õ': 32, 'É': 33 - } - - # Converter mensagem para sequência numérica + # --------------Encryption/Decryption specific methods-------------- + @staticmethod + def char_to_num(char): + # Convert character to corresponding number based on CHAR_TO_NUM mapping "encryption" + return Matrix.CHAR_TO_NUM.get(char.upper(), 29) + + @staticmethod + def num_to_char(num): + # Convert number to corresponding character based on CHAR_TO_NUM mapping "decryption" + return Matrix.NUM_TO_CHAR.get(num, ' ') + + def encrypt_message(self, message): + # Encrypt message using matrix multiplication (message_matrix * encoding_matrix = encrypted_matrix) message = message.upper() - numeric_sequence = [char_map.get(char, 27) for char in message] + numbers = [] + for a in message: + numbers.append(Matrix.char_to_num(a)) - # Determinar dimensões da matriz - rows = encoding_matrix.rows - cols = len(numeric_sequence) // rows + while len(numbers) % self.rows != 0: + numbers.append(29) - # Preencher com espaços (27) se necessário - if len(numeric_sequence) % rows != 0: - cols += 1 - padding_needed = (rows * cols) - len(numeric_sequence) - numeric_sequence.extend([27] * padding_needed) - - # Criar a mensagem - message_data = [] - for i in range(rows): + num_cols = len(numbers) // self.rows + message_matrix_data = [] + for i in range(self.rows): row = [] - for j in range(cols): - index = j * rows + i - row.append(numeric_sequence[index]) - message_data.append(row) + for j in range(num_cols): + row.append(numbers[i * num_cols + j]) + message_matrix_data.append(row) + message_matrix = Matrix(self.rows, num_cols, message_matrix_data) - message_matrix = Matrix(rows, cols, message_data) + encrypted_matrix = self.multiply(message_matrix) - # Encriptar: A × M = C - encrypted_matrix = encoding_matrix.multiply(message_matrix) + numeric_sequence = [] + for rows in range(encrypted_matrix.rows): + for cols in range(encrypted_matrix.cols): + numeric_sequence.append(int(encrypted_matrix.data[rows][cols])) return { + 'message_matrix': message_matrix.to_list(), 'encrypted_matrix': encrypted_matrix, - 'message_matrix': message_matrix, - 'numeric_sequence': numeric_sequence, - 'original_message': message + 'numeric_sequence': numeric_sequence } - def decrypt_message(self, encrypted_matrix, decoding_matrix): - """ - Decrypt an encrypted matrix using the inverse matrix. - - Args: - encrypted_matrix: Matrix object with encrypted data - decoding_matrix: Inverse of the encoding matrix - - Returns: - dict with 'decrypted_message', 'numeric_sequence', 'message_matrix' - """ - if not isinstance(encrypted_matrix, Matrix): - raise ValueError("Encrypted matrix must be a Matrix object") - - if not isinstance(decoding_matrix, Matrix): - raise ValueError("Decoding matrix must be a Matrix object") - - # Mapping reverso - reverse_map = { - 1: 'A', 2: 'B', 3: 'C', 4: 'D', 5: 'E', 6: 'F', 7: 'G', 8: 'H', 9: 'I', - 10: 'J', 11: 'K', 12: 'L', 13: 'M', 14: 'N', 15: 'O', 16: 'P', 17: 'Q', - 18: 'R', 19: 'S', 20: 'T', 21: 'U', 22: 'V', 23: 'W', 24: 'X', 25: 'Y', - 26: 'Z', 27: ' ', 28: '.', 29: 'Ú', 30: 'Ã', 31: 'Ç', 32: 'Õ', 33: 'É' - } - - # Decriptar: B × C = M + def decrypt_message(self, encrypted_matrix): + # Decrypt message using matrix multiplication (encrypted_matrix * decoding_matrix = original_message_matrix) + decoding_matrix = self.inverse() message_matrix = decoding_matrix.multiply(encrypted_matrix) - # Extrair numeros da matriz - numeric_sequence = [] - for j in range(message_matrix.cols): - for i in range(message_matrix.rows): - numeric_sequence.append(round(message_matrix.get_element(i + 1, j + 1))) + numbers = [] + for rows in range(message_matrix.rows): + for cols in range(message_matrix.cols): + numbers.append(round(message_matrix.data[rows][cols])) - # Converter para mensagem - decrypted_message = ''.join([reverse_map.get(num, ' ') for num in numeric_sequence]) + decrypted_message = '' + for num in numbers: + decrypted_message += Matrix.num_to_char(num) return { - 'decrypted_message': decrypted_message.rstrip(), - 'numeric_sequence': numeric_sequence, - 'message_matrix': message_matrix + 'message_matrix': message_matrix.to_list(), + 'decrypted_message': decrypted_message, + 'numeric_sequence': numbers } \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index b0cd3db..3b9c1a5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# Matrix Project +# Matrix ## How to use - Clone the project using ```git clone https://github.com/tskxz/matrix``` diff --git a/docs/UMLS/Sequence_Diagram.png b/docs/UMLS/Sequence_Diagram.png new file mode 100644 index 0000000..2a85b9f Binary files /dev/null and b/docs/UMLS/Sequence_Diagram.png differ diff --git a/docs/UMLS/addition_subtraction_diagram.png b/docs/UMLS/addition_subtraction_diagram.png new file mode 100644 index 0000000..5c215aa Binary files /dev/null and b/docs/UMLS/addition_subtraction_diagram.png differ diff --git a/docs/UMLS/casos_uso.png b/docs/UMLS/casos_uso.png new file mode 100644 index 0000000..5ca4669 Binary files /dev/null and b/docs/UMLS/casos_uso.png differ diff --git a/docs/UMLS/decryption_diagram.png b/docs/UMLS/decryption_diagram.png new file mode 100644 index 0000000..e7e38f2 Binary files /dev/null and b/docs/UMLS/decryption_diagram.png differ diff --git a/docs/UMLS/determinant_diagram.png b/docs/UMLS/determinant_diagram.png new file mode 100644 index 0000000..e032a36 Binary files /dev/null and b/docs/UMLS/determinant_diagram.png differ diff --git a/docs/UMLS/encryption_diagram.png b/docs/UMLS/encryption_diagram.png new file mode 100644 index 0000000..008e715 Binary files /dev/null and b/docs/UMLS/encryption_diagram.png differ diff --git a/docs/UMLS/inverse_diagram.png b/docs/UMLS/inverse_diagram.png new file mode 100644 index 0000000..13c7afe Binary files /dev/null and b/docs/UMLS/inverse_diagram.png differ diff --git a/docs/UMLS/matrix_class.png b/docs/UMLS/matrix_class.png new file mode 100644 index 0000000..0ef3097 Binary files /dev/null and b/docs/UMLS/matrix_class.png differ diff --git a/docs/UMLS/multiply_diagram.png b/docs/UMLS/multiply_diagram.png new file mode 100644 index 0000000..8d60209 Binary files /dev/null and b/docs/UMLS/multiply_diagram.png differ diff --git a/docs/UMLS/unit_tests_diagram.png b/docs/UMLS/unit_tests_diagram.png new file mode 100644 index 0000000..a5ae9f6 Binary files /dev/null and b/docs/UMLS/unit_tests_diagram.png differ diff --git a/docs/UMLS/use_case_diagram.png b/docs/UMLS/use_case_diagram.png new file mode 100644 index 0000000..014b1f7 Binary files /dev/null and b/docs/UMLS/use_case_diagram.png differ diff --git a/docs/criptografia.md b/docs/algebra/criptografia.md similarity index 100% rename from docs/criptografia.md rename to docs/algebra/criptografia.md diff --git a/docs/algebra/matrix.pdf b/docs/algebra/matrix.pdf new file mode 100644 index 0000000..8889521 Binary files /dev/null and b/docs/algebra/matrix.pdf differ diff --git a/docs/algebra/matrix.png b/docs/algebra/matrix.png new file mode 100644 index 0000000..2d2ea01 Binary files /dev/null and b/docs/algebra/matrix.png differ diff --git a/docs/calculus.md b/docs/algebra/operations.md similarity index 100% rename from docs/calculus.md rename to docs/algebra/operations.md diff --git a/docs/analise_sistema.MD b/docs/analise_sistema.MD new file mode 100644 index 0000000..51cdc29 --- /dev/null +++ b/docs/analise_sistema.MD @@ -0,0 +1,86 @@ +Análise de sistema +Authors: Tanjil Khan, Dmytro Bohutsky, Luís Martins, Ricardo Magalhães +Date: 01/19/2026 + +## Requisitos funcionais +O sistema deve ter as seguintes funcionalidades: +##### RF01 – Introdução de Matrizes +O sistema deve permitir ao utilizador introduzir matrizes de dimensão variável, definindo explicitamente o número de linhas e colunas, bem como os respetivos valores reais. + +##### RF02 – Soma de Matrizes +O sistema deve permitir a soma de duas matrizes + +##### RF03 – Subtração de Matrizes +O sistema deve permitir a subtração de duas matrizes + +##### RF04 – Multiplicação de Matriz por Escalar +O sistema deve permitir a multiplicação de uma matriz + +##### RF05 – Multiplicação de Matrizes +O sistema deve permitir a multiplicação de duas matrizes + +##### RF06 – Cálculo do Determinante +O sistema deve calcular o determinante de uma matriz quadrada: + - Matrizes de ordem superior através da expansão por cofatores + - Caso a matriz não seja quadrada, o sistema deve informar o utilizador. + +##### RF07 – Cálculo da Matriz Inversa +O sistema deve calcular a matriz inversa de uma matriz quadrada, desde que o determinante seja diferente de zero. +Caso contrário, o sistema deve apresentar uma mensagem de erro adequada. + +##### RF08 – Conversão de Texto para Matriz Numérica +O sistema deve converter uma mensagem de texto numa matriz numérica com base numa tabela de codificação pré-definida (A=1, B=2, …, Z=26, sinais especiais). + +##### RF09 – Criptografia de Mensagens +O sistema deve permitir a criptografia de mensagens através da multiplicação da matriz da mensagem por uma matriz de codificação. + +##### RF10 – Descriptografia de Mensagens +O sistema deve permitir a recuperação da mensagem original através da multiplicação da matriz criptografada pela matriz inversa de decodificação. + +##### RF11 – Validação de Dados +O sistema deve validar: +- Dimensões das matrizes +- Valores numéricos introduzidos +- Condições matemáticas necessárias para cada operação + +## Requisitos não funcionais + +##### RNF01 – Desempenho +O sistema deve executar operações matriciais de pequena e média dimensão num tempo de resposta inferior a 1 segundo, garantindo fluidez na interação com o utilizador. + +##### RNF02 – Usabilidade +O sistema deve apresentar uma interface simples e intuitiva, permitindo que utilizadores com conhecimentos básicos de matemática consigam realizar operações sem dificuldade. + +##### RNF03 – Fiabilidade +O sistema deve garantir resultados matematicamente corretos, respeitando rigorosamente as definições formais das operações matriciais. + +##### RNF04 – Segurança dos Dados +O sistema não deve armazenar permanentemente mensagens introduzidas para criptografia, garantindo a confidencialidade dos dados do utilizador durante a execução. + +##### RNF05 – Robustez +O sistema deve lidar corretamente com erros, como: + - Matrizes incompatíveis + - Determinantes nulos + - Introdução de dados inválidos + - Apresentar mensagens claras ao utilizador. + +##### RNF06 – Escalabilidade +O sistema deve ser projetado de forma a permitir, no futuro, a adição de novas operações matemáticas ou métodos de criptografia sem necessidade de reestruturação profunda. + +##### RNF07 – Conformidade +O sistema deve respeitar boas práticas de desenvolvimento de software e normas académicas, garantindo clareza, organização e manutenção do código. + +### Casos de uso +![casos_uso](./UMLS/casos_uso.png) + +### Diagrama de classes +Neste diagrama, representamos a estrutura e relações da classe Matriz: +![diagrama_classes](./UMLS/matrix_class.png) + +### Diagrama de sequências +O seguinte diagrama mostra a sequência dos processos e a estrutura do nossso projeto: +![diagrama_sequencia](./UMLS/Sequence_Diagram.png) + +### Fluxograma - unit test +O seguinte fluxograma descreve como seria o fluxo enquanto se executa os testes: +![fluxograma_unittest](./UMLS/unit_tests_diagram.png) \ No newline at end of file diff --git a/docs/analise_sistema.html b/docs/analise_sistema.html new file mode 100644 index 0000000..0debc5c --- /dev/null +++ b/docs/analise_sistema.html @@ -0,0 +1,435 @@ + + + +analise_sistema.MD + + + + + + + + + + + + +

Análise de sistema
+Authors: Tanjil Khan, Dmytro Bohutsky, Luís Martins, Ricardo Magalhães
+Date: 01/19/2026

+

Requisitos funcionais

+

O sistema deve ter as seguintes funcionalidades:

+
RF01 – Introdução de Matrizes
+

O sistema deve permitir ao utilizador introduzir matrizes de dimensão variável, definindo explicitamente o número de linhas e colunas, bem como os respetivos valores reais.

+
RF02 – Soma de Matrizes
+

O sistema deve permitir a soma de duas matrizes

+
RF03 – Subtração de Matrizes
+

O sistema deve permitir a subtração de duas matrizes

+
RF04 – Multiplicação de Matriz por Escalar
+

O sistema deve permitir a multiplicação de uma matriz

+
RF05 – Multiplicação de Matrizes
+

O sistema deve permitir a multiplicação de duas matrizes

+
RF06 – Cálculo do Determinante
+

O sistema deve calcular o determinante de uma matriz quadrada: +- Matrizes de ordem superior através da expansão por cofatores +- Caso a matriz não seja quadrada, o sistema deve informar o utilizador.

+
RF07 – Cálculo da Matriz Inversa
+

O sistema deve calcular a matriz inversa de uma matriz quadrada, desde que o determinante seja diferente de zero. +Caso contrário, o sistema deve apresentar uma mensagem de erro adequada.

+
RF08 – Conversão de Texto para Matriz Numérica
+

O sistema deve converter uma mensagem de texto numa matriz numérica com base numa tabela de codificação pré-definida (A=1, B=2, …, Z=26, sinais especiais).

+
RF09 – Criptografia de Mensagens
+

O sistema deve permitir a criptografia de mensagens através da multiplicação da matriz da mensagem por uma matriz de codificação.

+
RF10 – Descriptografia de Mensagens
+

O sistema deve permitir a recuperação da mensagem original através da multiplicação da matriz criptografada pela matriz inversa de decodificação.

+
RF11 – Validação de Dados
+

O sistema deve validar:

+ +

Requisitos não funcionais

+
RNF01 – Desempenho
+

O sistema deve executar operações matriciais de pequena e média dimensão num tempo de resposta inferior a 1 segundo, garantindo fluidez na interação com o utilizador.

+
RNF02 – Usabilidade
+

O sistema deve apresentar uma interface simples e intuitiva, permitindo que utilizadores com conhecimentos básicos de matemática consigam realizar operações sem dificuldade.

+
RNF03 – Fiabilidade
+

O sistema deve garantir resultados matematicamente corretos, respeitando rigorosamente as definições formais das operações matriciais.

+
RNF04 – Segurança dos Dados
+

O sistema não deve armazenar permanentemente mensagens introduzidas para criptografia, garantindo a confidencialidade dos dados do utilizador durante a execução.

+
RNF05 – Robustez
+

O sistema deve lidar corretamente com erros, como: +- Matrizes incompatíveis +- Determinantes nulos +- Introdução de dados inválidos +- Apresentar mensagens claras ao utilizador.

+
RNF06 – Escalabilidade
+

O sistema deve ser projetado de forma a permitir, no futuro, a adição de novas operações matemáticas ou métodos de criptografia sem necessidade de reestruturação profunda.

+
RNF07 – Conformidade
+

O sistema deve respeitar boas práticas de desenvolvimento de software e normas académicas, garantindo clareza, organização e manutenção do código.

+

Casos de uso

+

casos_uso

+

Diagrama de classes

+

Neste diagrama, representamos a estrutura e relações da classe Matriz: +diagrama_classes

+

Diagrama de sequências

+

O seguinte diagrama mostra a sequência dos processos e a estrutura do nossso projeto: +diagrama_sequencia

+

Fluxograma - unit test

+

O seguinte fluxograma descreve como seria o fluxo enquanto se executa os testes: +fluxograma_unittest

+ + + diff --git a/docs/analise_sistema.pdf b/docs/analise_sistema.pdf new file mode 100644 index 0000000..dfd56c7 Binary files /dev/null and b/docs/analise_sistema.pdf differ diff --git a/docs/requisitos.md b/docs/requisitos.md new file mode 100644 index 0000000..3bda97e --- /dev/null +++ b/docs/requisitos.md @@ -0,0 +1,67 @@ +## Requisitos funcionais +O sistema deve ter as seguintes funcionalidades: +##### RF01 – Introdução de Matrizes +O sistema deve permitir ao utilizador introduzir matrizes de dimensão variável, definindo explicitamente o número de linhas e colunas, bem como os respetivos valores reais. + +##### RF02 – Soma de Matrizes +O sistema deve permitir a soma de duas matrizes + +##### RF03 – Subtração de Matrizes +O sistema deve permitir a subtração de duas matrizes + +##### RF04 – Multiplicação de Matriz por Escalar +O sistema deve permitir a multiplicação de uma matriz + +##### RF05 – Multiplicação de Matrizes +O sistema deve permitir a multiplicação de duas matrizes + +##### RF06 – Cálculo do Determinante +O sistema deve calcular o determinante de uma matriz quadrada: + - Matrizes de ordem superior através da expansão por cofatores + - Caso a matriz não seja quadrada, o sistema deve informar o utilizador. + +##### RF07 – Cálculo da Matriz Inversa +O sistema deve calcular a matriz inversa de uma matriz quadrada, desde que o determinante seja diferente de zero. +Caso contrário, o sistema deve apresentar uma mensagem de erro adequada. + +##### RF08 – Conversão de Texto para Matriz Numérica +O sistema deve converter uma mensagem de texto numa matriz numérica com base numa tabela de codificação pré-definida (A=1, B=2, …, Z=26, sinais especiais). + +##### RF09 – Criptografia de Mensagens +O sistema deve permitir a criptografia de mensagens através da multiplicação da matriz da mensagem por uma matriz de codificação. + +##### RF10 – Descriptografia de Mensagens +O sistema deve permitir a recuperação da mensagem original através da multiplicação da matriz criptografada pela matriz inversa de decodificação. + +##### RF11 – Validação de Dados +O sistema deve validar: +- Dimensões das matrizes +- Valores numéricos introduzidos +- Condições matemáticas necessárias para cada operação + +## Requisitos não funcionais + +##### RNF01 – Desempenho +O sistema deve executar operações matriciais de pequena e média dimensão num tempo de resposta inferior a 1 segundo, garantindo fluidez na interação com o utilizador. + +##### RNF02 – Usabilidade +O sistema deve apresentar uma interface simples e intuitiva, permitindo que utilizadores com conhecimentos básicos de matemática consigam realizar operações sem dificuldade. + +##### RNF03 – Fiabilidade +O sistema deve garantir resultados matematicamente corretos, respeitando rigorosamente as definições formais das operações matriciais. + +##### RNF04 – Segurança dos Dados +O sistema não deve armazenar permanentemente mensagens introduzidas para criptografia, garantindo a confidencialidade dos dados do utilizador durante a execução. + +##### RNF05 – Robustez +O sistema deve lidar corretamente com erros, como: + - Matrizes incompatíveis + - Determinantes nulos + - Introdução de dados inválidos + - Apresentar mensagens claras ao utilizador. + +##### RNF06 – Escalabilidade +O sistema deve ser projetado de forma a permitir, no futuro, a adição de novas operações matemáticas ou métodos de criptografia sem necessidade de reestruturação profunda. + +##### RNF07 – Conformidade +O sistema deve respeitar boas práticas de desenvolvimento de software e normas académicas, garantindo clareza, organização e manutenção do código. \ No newline at end of file diff --git a/docs/source/Sequence_Diagram.md b/docs/source/Sequence_Diagram.md new file mode 100644 index 0000000..3a1a9c7 --- /dev/null +++ b/docs/source/Sequence_Diagram.md @@ -0,0 +1,68 @@ +sequenceDiagram + participant Client as Client (Browser) + participant JS as JavaScript
+ participant Flask as Flask Server
(app.py) + participant Matrix as Matrix Class
(core/matrix.py) + participant Template as HTML Templates
(templates/*.html) + autonumber + + %% Initial Page Load + rect rgb(240, 248, 255) + Note over Client,Template: Initial Page Load (GET Request) + Client->>Flask: GET /[endpoint] + Flask->>Template: render_template('*.html') + Template-->>Flask: HTML Response + Flask-->>Client: HTML + CSS + JS + Client->>JS: Load JavaScript files + JS->>JS: Initialize DOM elements + JS->>JS: Attach event listeners + JS->>JS: generateInputs() + end + + %% Form Submission + rect rgb(255, 250, 240) + Note over Client,Matrix: Form Submission (POST Request) + Client->>JS: Fill form & click Submit + JS->>JS: e.preventDefault() + JS->>JS: collectMatrixData() + JS->>JS: Build payload object + + JS->>Flask: fetch('/[endpoint]', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)}) + + Flask->>Flask: request.get_json() + Flask->>Flask: Validate input data + + alt Valid Data + Flask->>Matrix: Create Matrix objects
Matrix(rows, cols, data) + Matrix->>Matrix: Perform operation
(add/multiply/inverse/etc.) + Matrix-->>Flask: Return result dict + Flask-->>JS: jsonify({result, ...}) + JS->>JS: displayResult(data) + JS->>Client: Update DOM with result + else Invalid Data + Flask-->>JS: jsonify({error: '...'}), 400 + JS->>JS: showError(msg) + JS->>Client: Display error message + end + end + + %% Example: Encryption Flow + rect rgb(240, 255, 240) + Note over Client,Matrix: Example: Encryption Flow + Client->>JS: Enter message + matrix + JS->>Flask: POST /encrypt
{operation, message, matrix} + Flask->>Matrix: encoding_matrix.determinant() + + alt det ≠ 0 (Invertible) + Flask->>Matrix: encrypt_message(message, matrix) + Matrix->>Matrix: Convert text → numbers + Matrix->>Matrix: Build message matrix + Matrix->>Matrix: Multiply matrices + Matrix-->>Flask: {encrypted_matrix, numeric_sequence} + Flask-->>JS: JSON response + JS->>Client: Show encrypted result + else det = 0 (Singular) + Flask-->>JS: {error: 'Matriz singular'} + JS->>Client: Show error + end + end \ No newline at end of file diff --git a/docs/source/addition_subtraction_diagram.md b/docs/source/addition_subtraction_diagram.md new file mode 100644 index 0000000..29c3a97 --- /dev/null +++ b/docs/source/addition_subtraction_diagram.md @@ -0,0 +1,52 @@ +sequenceDiagram + autonumber + participant Client + participant JS as JavaScript
+ participant Flask as Flask
(/add or /subtract route) + participant MatrixA as Matrix A + participant MatrixB as Matrix B + + Client->>JS: Fill two matrices & select operation + JS->>JS: collectMatrixData() + + alt Addition Selected + JS->>Flask: POST /add
{matrixA: {...}, matrixB: {...}} + else Subtraction Selected + JS->>Flask: POST /subtract
{matrixA: {...}, matrixB: {...}} + end + + Flask->>Flask: request.get_json() + Flask->>MatrixA: Matrix(rows_A, cols_A, data_A) + Flask->>MatrixB: Matrix(rows_B, cols_B, data_B) + + MatrixA->>MatrixB: is_same_dimension(matrixB) + + alt Different Dimensions + Flask-->>JS: Error 400 "Incompatible dimensions" + JS->>Client: showError("Matrices must have same dimensions") + else Same Dimensions + alt Addition Operation + MatrixA->>MatrixB: add(matrixB) + + loop For each row i + loop For each col j + MatrixB->>MatrixB: result[i][j] = A[i][j] + B[i][j] + end + end + else Subtraction Operation + MatrixA->>MatrixB: subtract(matrixB) + + loop For each row i + loop For each col j + MatrixB->>MatrixB: result[i][j] = A[i][j] - B[i][j] + end + end + end + + MatrixB-->>MatrixA: Result matrix + MatrixA->>MatrixA: to_dict() + MatrixA-->>Flask: {rows, cols, data} + Flask-->>JS: JSON {result: {...}} + JS->>JS: displayResult(data) + JS->>Client: Display result matrix + end \ No newline at end of file diff --git a/docs/source/casos_uso.drawio b/docs/source/casos_uso.drawio new file mode 100644 index 0000000..3f90871 --- /dev/null +++ b/docs/source/casos_uso.drawio @@ -0,0 +1,433 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/decryption_diagram.md b/docs/source/decryption_diagram.md new file mode 100644 index 0000000..1674f73 --- /dev/null +++ b/docs/source/decryption_diagram.md @@ -0,0 +1,44 @@ +sequenceDiagram + autonumber + participant Client + participant JS as JavaScript
+ participant Flask as Flask
(/decrypt route) + participant Matrix as Encoding Matrix + participant Inverse as Inverse Matrix + participant Crypto as Encryption Module + participant MsgMatrix as Encrypted Matrix + + Client->>JS: Enter encrypted numbers & encoding matrix + JS->>JS: collectMatrixData() + JS->>Flask: POST /decrypt
{encrypted: [...], encoding_matrix: {...}} + + Flask->>Flask: request.get_json() + Flask->>Matrix: Matrix(rows, cols, encoding_data) + Matrix->>Matrix: determinant() + + alt det == 0 + Flask-->>JS: Error 400 "Singular matrix" + JS->>Client: showError("Cannot decrypt") + else det ≠ 0 + Matrix->>Inverse: inverse() + Inverse-->>Flask: Inverse matrix + + Flask->>Crypto: decrypt_message(encrypted, encoding_matrix) + Crypto->>Crypto: Split encrypted into chunks + + loop For each encrypted chunk + Crypto->>MsgMatrix: Create encrypted matrix + MsgMatrix->>Inverse: multiply(inverse_matrix) + Inverse-->>MsgMatrix: Decrypted numbers + Crypto->>Crypto: Append to numeric_sequence + end + + Crypto->>Crypto: numbers_to_text(numeric_sequence) + + Note over Crypto: Convert: 0→A, 1→B, ..., 25→Z
26→Space + + Crypto-->>Flask: Original message (string) + Flask-->>JS: JSON {decrypted_message: "text"} + JS->>JS: displayResult(data) + JS->>Client: Display original message + end \ No newline at end of file diff --git a/docs/source/determinant_diagram.md b/docs/source/determinant_diagram.md new file mode 100644 index 0000000..5b2ceeb --- /dev/null +++ b/docs/source/determinant_diagram.md @@ -0,0 +1,45 @@ +sequenceDiagram + autonumber + participant Client + participant JS as JavaScript
+ participant Flask as Flask
(/determinant route) + participant Matrix as Matrix Object + + Client->>JS: Fill matrix & click "Calculate Determinant" + JS->>JS: collectMatrixData() + JS->>JS: Check if square matrix + + alt Not Square + JS->>Client: showError("Matrix must be square") + else Square Matrix + JS->>Flask: POST /determinant
{matrix: {...}} + Flask->>Flask: request.get_json() + Flask->>Matrix: Matrix(rows, cols, data) + Matrix->>Matrix: is_square() + + alt Not Square (server check) + Flask-->>JS: Error 400 + JS->>Client: showError() + else Square + Matrix->>Matrix: determinant() + + alt 1×1 Matrix + Matrix->>Matrix: return data[0][0] + else 2×2 Matrix + Matrix->>Matrix: return (a*d - b*c) + else 3×3 Matrix + Matrix->>Matrix: Sarrus rule calculation + else n×n Matrix (n>3) + loop For each column + Matrix->>Matrix: _get_minor(0, col) + Matrix->>Matrix: determinant() [recursive] + Matrix->>Matrix: cofactor * minor_det + end + end + + Matrix-->>Flask: determinant value (float) + Flask-->>JS: JSON {determinant: value} + JS->>JS: displayResult(data) + JS->>Client: Display "Determinant: {value}" + end + end \ No newline at end of file diff --git a/docs/source/encryption_diagram.md b/docs/source/encryption_diagram.md new file mode 100644 index 0000000..b076b8f --- /dev/null +++ b/docs/source/encryption_diagram.md @@ -0,0 +1,41 @@ +sequenceDiagram + autonumber + participant Client + participant JS as JavaScript
+ participant Flask as Flask
(/encrypt route) + participant Matrix as Encoding Matrix + participant Crypto as Encryption Module + participant MsgMatrix as Message Matrix + + Client->>JS: Enter message & encoding matrix + JS->>JS: collectMatrixData() + JS->>Flask: POST /encrypt
{message: "text", encoding_matrix: {...}} + + Flask->>Flask: request.get_json() + Flask->>Matrix: Matrix(rows, cols, encoding_data) + Matrix->>Matrix: determinant() + + alt det == 0 + Flask-->>JS: Error 400 "Singular matrix" + JS->>Client: showError("Matrix is not invertible") + else det ≠ 0 + Flask->>Crypto: encrypt_message(message, encoding_matrix) + Crypto->>Crypto: text_to_numbers(message) + + Note over Crypto: Convert: A→0, B→1, ..., Z→25
Space→26 + + Crypto->>Crypto: Calculate chunk_size
(rows * cols of encoding matrix) + Crypto->>Crypto: Pad message to fit chunks + + loop For each chunk + Crypto->>MsgMatrix: Create message matrix
(based on encoding matrix dimensions) + MsgMatrix->>Matrix: multiply(message_matrix) + Matrix-->>MsgMatrix: Encrypted chunk + Crypto->>Crypto: Append to numeric_sequence + end + + Crypto-->>Flask: {encrypted_matrix, numeric_sequence} + Flask-->>JS: JSON {encrypted_message: [...]} + JS->>JS: displayResult(data) + JS->>Client: Display encrypted numbers + end \ No newline at end of file diff --git a/docs/source/inverse_diagram.md b/docs/source/inverse_diagram.md new file mode 100644 index 0000000..9d118dd --- /dev/null +++ b/docs/source/inverse_diagram.md @@ -0,0 +1,55 @@ +sequenceDiagram + autonumber + participant Client + participant JS as JavaScript
+ participant Flask as Flask
(/inverse route) + participant Matrix as Matrix Object + participant Det as determinant() + participant Minor as _get_minor() + + Client->>JS: Fill matrix & click "Calculate Inverse" + JS->>JS: collectMatrixData() + JS->>Flask: POST /inverse
{matrix: {...}} + + Flask->>Flask: request.get_json() + Flask->>Matrix: Matrix(rows, cols, data) + Matrix->>Matrix: is_square() + + alt Not Square + Flask-->>JS: Error 400 "Must be square" + JS->>Client: showError() + else Square + Matrix->>Det: determinant() + Det-->>Matrix: det_value + + alt det == 0 + Flask-->>JS: Error 400 "Singular matrix" + JS->>Client: showError("Matrix is not invertible") + else det ≠ 0 + Matrix->>Matrix: inverse() + + alt 2×2 Matrix + Matrix->>Matrix: Swap a↔d, negate b,c + Matrix->>Matrix: scalar_multiply(1/det) + else n×n Matrix (n≥3) + loop For each row i + loop For each col j + Matrix->>Minor: _get_minor(i, j) + Minor-->>Matrix: submatrix + Matrix->>Det: determinant() on minor + Det-->>Matrix: minor_det + Matrix->>Matrix: cofactor = (-1)^(i+j) * minor_det + Matrix->>Matrix: adjugate[j][i] = cofactor + end + end + Matrix->>Matrix: scalar_multiply(1/det) + end + + Matrix-->>Flask: Inverse matrix + Flask->>Flask: inverse.to_dict() + Flask-->>JS: JSON {result: {...}} + JS->>JS: displayResult(data) + JS->>JS: matrixToHTML(inverse) + JS->>Client: Display inverse matrix + end + end \ No newline at end of file diff --git a/docs/source/matrix_class.drawio b/docs/source/matrix_class.drawio new file mode 100644 index 0000000..136ee8c --- /dev/null +++ b/docs/source/matrix_class.drawio @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/multiply_diagram.md b/docs/source/multiply_diagram.md new file mode 100644 index 0000000..fa6ed53 --- /dev/null +++ b/docs/source/multiply_diagram.md @@ -0,0 +1,38 @@ +sequenceDiagram + autonumber + participant Client + participant JS as JavaScript
+ participant Flask as Flask
(/multiply route) + participant MatrixA as Matrix A Object + participant MatrixB as Matrix B Object + participant Result as Result Matrix + + Client->>JS: Fill matrices & click "Multiply" + JS->>JS: collectMatrixData() + JS->>JS: Validate dimensions
(cols_A == rows_B) + + alt Incompatible Dimensions + JS->>Client: showError("Incompatible dimensions") + else Compatible + JS->>Flask: POST /multiply
{matrixA: {...}, matrixB: {...}} + Flask->>Flask: request.get_json() + Flask->>MatrixA: Matrix(rows, cols, data_A) + Flask->>MatrixB: Matrix(rows, cols, data_B) + + MatrixA->>MatrixB: multiply(matrixB) + + loop For each row i in A + loop For each col j in B + MatrixB->>MatrixB: sum(A[i][k] * B[k][j]) + end + end + + MatrixB-->>MatrixA: New Matrix (result) + MatrixA->>Result: Create result matrix + Result->>Result: to_dict() + Result-->>Flask: {rows, cols, data} + Flask-->>JS: JSON {result: {...}} + JS->>JS: displayResult(data) + JS->>JS: matrixToHTML(result) + JS->>Client: Display result matrix + end \ No newline at end of file diff --git a/docs/source/unit_tests_diagram.md b/docs/source/unit_tests_diagram.md new file mode 100644 index 0000000..8d80bd0 --- /dev/null +++ b/docs/source/unit_tests_diagram.md @@ -0,0 +1,87 @@ +graph TD + Start([Run Unit Tests]) --> TestCreation[Test Matrix Creation] + + TestCreation --> TC1[Create with data] + TestCreation --> TC2[Create zeros] + TestCreation --> TC3[Invalid dimensions] + + TC1 --> TestElements[Test Matrix Elements] + TC2 --> TestElements + TC3 --> TestElements + + TestElements --> TE1[Get/Set elements] + TestElements --> TE2[Check dimensions] + TestElements --> TE3[Is square?] + + TE1 --> TestBasic[Test Basic Operations] + TE2 --> TestBasic + TE3 --> TestBasic + + TestBasic --> TBA[Addition 2×2, 3×3] + TestBasic --> TBS[Subtraction 2×2, 3×3] + TestBasic --> TBSc[Scalar ×2, ×0, ×-1] + TestBasic --> TBErr[Dimension errors] + + TBA --> TestMultiply[Test Multiplication] + TBS --> TestMultiply + TBSc --> TestMultiply + TBErr --> TestMultiply + + TestMultiply --> TM1[2×2 × 2×2] + TestMultiply --> TM2[2×3 × 3×2] + TestMultiply --> TM3[Identity property] + TestMultiply --> TM4[Incompatible error] + + TM1 --> TestDet[Test Determinant] + TM2 --> TestDet + TM3 --> TestDet + TM4 --> TestDet + + TestDet --> TD1[1×1, 2×2, 3×3] + TestDet --> TD2[Zero det singular] + TestDet --> TD3[Non-square error] + + TD1 --> TestTranspose[Test Transpose] + TD2 --> TestTranspose + TD3 --> TestTranspose + + TestTranspose --> TT1[2×2 square] + TestTranspose --> TT2[3×2 rectangular] + + TT1 --> TestInverse[Test Inverse] + TT2 --> TestInverse + + TestInverse --> TI1[2×2 invertible] + TestInverse --> TI2[3×3 invertible] + TestInverse --> TI3[Verify A × A⁻¹ = I] + TestInverse --> TI4[Singular error] + TestInverse --> TI5[Non-square error] + + TI1 --> TestEncrypt[Test Encryption] + TI2 --> TestEncrypt + TI3 --> TestEncrypt + TI4 --> TestEncrypt + TI5 --> TestEncrypt + + TestEncrypt --> TEn1[Setup encoding matrix] + TestEncrypt --> TEn2[Encrypt message] + TestEncrypt --> TEn3[Decrypt message] + TestEncrypt --> TEn4[Verify inverse matrices] + TestEncrypt --> TEn5[Round-trip test] + + TEn1 --> End([All Tests Complete]) + TEn2 --> End + TEn3 --> End + TEn4 --> End + TEn5 --> End + + style Start fill:#e1f5ff + style TestCreation fill:#fff4e1 + style TestElements fill:#e8f5e9 + style TestBasic fill:#f3e5f5 + style TestMultiply fill:#ffe0b2 + style TestDet fill:#fff9c4 + style TestTranspose fill:#e1bee7 + style TestInverse fill:#ffccbc + style TestEncrypt fill:#c5cae9 + style End fill:#c8e6c9 \ No newline at end of file diff --git a/docs/source/use_case_diagram.md b/docs/source/use_case_diagram.md new file mode 100644 index 0000000..d7e71f3 --- /dev/null +++ b/docs/source/use_case_diagram.md @@ -0,0 +1,77 @@ +graph TB + Client([Client]) + + Client --> Menu[Access Main Menu] + + Menu --> SumSub[Sum/Subtraction Operations] + Menu --> Scalar[Scalar Multiplication] + Menu --> Multiply[Matrix Multiplication] + Menu --> Det[Determinant Calculation] + Menu --> Inverse[Matrix Inverse] + Menu --> Encrypt[Hill Cipher Encryption] + + SumSub --> SumSubInput[Input Two Matrices
Same Dimensions] + SumSubInput --> SumOp[Perform Addition] + SumSubInput --> SubOp[Perform Subtraction] + SumOp --> DisplayResult[Display Result Matrix] + SubOp --> DisplayResult + + Scalar --> ScalarInput[Input Matrix + Scalar Value] + ScalarInput --> ScalarCalc[Multiply Each Element] + ScalarCalc --> DisplayResult + + Multiply --> MultiplyInput[Input Two Matrices
A: m×n, B: n×p] + MultiplyInput --> CompatCheck{Check Compatibility
cols_A = rows_B?} + CompatCheck -->|Yes| MultiplyCalc[Perform Multiplication] + CompatCheck -->|No| ShowError[Show Error Message] + MultiplyCalc --> DisplayResult + + Det --> DetInput[Input Square Matrix
n×n, max 6×6] + DetInput --> SelectMethod[Select Method:
Auto/Direct/Laplace] + SelectMethod --> DetCalc[Calculate Determinant] + DetCalc --> DisplayDetValue[Display Determinant Value
Integer/Decimal] + DetCalc --> ShowSingular{det = 0?} + ShowSingular -->|Yes| ShowSingularMsg[Show Singular Matrix Message] + ShowSingular -->|No| ShowNonSingular[Show Non-Singular Message] + + Inverse --> InvInput[Input Square Matrix] + InvInput --> InvMethod[Select Method:
Adjugate/Gauss-Jordan] + InvMethod --> CheckDet{Check if
Determinant ≠ 0} + CheckDet -->|Yes| CalcInverse[Calculate Inverse Matrix] + CheckDet -->|No| ShowSingularError[Show Singular Matrix Error] + CalcInverse --> DisplayInverse[Display Original + Inverse] + DisplayInverse --> VerifyInv[Show Verification:
A × A⁻¹ = I] + + Encrypt --> EncryptChoice{Choose Operation} + EncryptChoice --> EncryptMsg[Encrypt Message] + EncryptChoice --> DecryptMsg[Decrypt Message] + + EncryptMsg --> InputMsg[Input Text Message] + InputMsg --> GenKey[Generate/Input
Key Matrix n×n] + GenKey --> ValidateKey{Validate Key
det ≠ 0?} + ValidateKey -->|No| ShowKeyError[Show Invalid Key Error] + ValidateKey -->|Yes| ConvertText[Convert Text to Numbers
A=1, B=2, ..., Space=29] + ConvertText --> CreateMsgMatrix[Create Message Matrix] + CreateMsgMatrix --> MatrixMult[Multiply:
Key × Message] + MatrixMult --> DisplayEncrypted[Display Encrypted Matrix
+ Number Sequence] + + DecryptMsg --> InputEncrypted[Input Encrypted Numbers] + InputEncrypted --> InputKeyDecrypt[Input Key Matrix] + InputKeyDecrypt --> CalcKeyInverse[Calculate Key Inverse] + CalcKeyInverse --> DecryptMult[Multiply:
Key⁻¹ × Encrypted] + DecryptMult --> ConvertToText[Convert Numbers to Text] + ConvertToText --> DisplayDecrypted[Display Original Message] + + style Client fill:#e1f5ff + style Menu fill:#fff4e1 + style SumSub fill:#e8f5e9 + style Scalar fill:#e8f5e9 + style Multiply fill:#e8f5e9 + style Det fill:#fff3e0 + style Inverse fill:#fff3e0 + style Encrypt fill:#f3e5f5 + style DisplayResult fill:#c8e6c9 + style ShowError fill:#ffcdd2 + style ShowSingularError fill:#ffcdd2 + style DisplayEncrypted fill:#d1c4e9 + style DisplayDecrypted fill:#d1c4e9 \ No newline at end of file diff --git a/static/css/components.css b/static/css/components.css new file mode 100644 index 0000000..0804245 --- /dev/null +++ b/static/css/components.css @@ -0,0 +1,328 @@ +/* Estilo do Menu & SideBar */ + +.menu-toggle { + height: 40px; + width: 40px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + cursor: pointer; + gap: 5px; + background-color: #1a1d26; + border: 1px solid #2a2e38; + transition: all 0.3s; +} + +.menu-toggle:hover { + background: #e8eaed; +} + +.menu-toggle span { + width: 20px; + height: 2px; + background-color: #6b7280; + transition: 0.3s; +} + +.overlay { + position: fixed; + inset: 0; + z-index: 40; + opacity: 0; + background: #0f1219dc; + backdrop-filter: blur(4px); + transition: opacity 0.3s; + pointer-events: none; +} + +.overlay.active { + opacity: 1; + pointer-events: auto; +} + +.overlay a { + text-decoration: none !important; + color: white !important; + padding: 12px 20px; +} + +.overlay a:hover { + text-decoration: none !important; + background-color: yellow; +} + +.sidebar { + display: flex; + flex-direction: column; + position: fixed; + top: 0; + right: 0; + width: 300px; + height: 100%; + background-color: #0f1219; + backdrop-filter: blur(12px); + border-left: 1px solid #2a2e38; + padding: 2rem 1rem; + box-shadow: 2px 0 5px rgba(0,0,0,0.1); + transform: translateX(100%); + transition: transform 0.3s ease-in-out; + z-index: 1000; +} + +.sidebar.active { + transform: translateX(0); +} + +.sidebar-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.5rem; + border-bottom: 1px solid #2a2e38; +} + +.sidebar-header-left { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.sidebar-header span { + font-weight: 500; + color: #6b7280; +} + +.close-sidebar { + width: 75px; + height: 25px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + background: transparent; + color: #6b7280; + transition: all 0.3s; + border: none; + border-radius: 8px; +} + +.close-sidebar:hover { + background: #2a2e38; + color: #e8eaed; +} + +.sidebar-menu { + flex: 1; + overflow-y: auto; + padding: 1rem; +} + +.sidebar-menu p { + font-weight: 500; + color: #6b7280; + padding: 1rem; +} + +.sidebar-menu a { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + color: #6b7280; + border-radius: 8px; + text-decoration: none; + transition: all 0.2s; +} + +.sidebar-menu a:hover { + background: #2a2e38; + color: #e8eaed; +} + +.sidebar-menu a.active { + color: #e8eaed; +} + +.divider { + height: 1px; + background: #2a2e38; + margin: 0.5rem 1rem; +} + +.header-content a { + text-decoration: none; + color: inherit; +} + +.header-content a:hover { + text-decoration: none; +} + +/* Estilo dos Formulários */ + +.matrix-input-grid { + display: grid; + gap: 2px; + margin: 0.5rem 0; + padding: 0.5rem; + background: #f8f9fa; + border: 1px solid #bdc3c7; + border-radius: 4px; + width: fit-content; +} + +.matrix-input-grid input { + width: 50px; + height: 50px; + text-align: center; + font-family: 'Courier New', monospace; + font-size: 0.95rem; + border: 1px solid #ddd; + border-radius: 2px; + margin: 0; + background: white; +} + +.matrix-input-grid input:focus { + border-color: #3498db; + outline: none; + background: #f0f8ff; +} + +.matrix-display { + margin: 0.5rem 0; + padding: 0.5rem; + background: #f8f9fa; + border: 1px solid #bdc3c7; + border-radius: 4px; + font-family: 'Courier New', monospace; + position: relative; + width: fit-content; +} + +.matrix-display::before, +.matrix-display::after { + content: ''; + position: absolute; + top: 8px; + bottom: 8px; + width: 6px; +} + +.matrix-display::before { + left: 8px; + border-left: 2px solid #2c3e50; + border-top: 2px solid #2c3e50; + border-bottom: 2px solid #2c3e50; +} + +.matrix-display::after { + right: 8px; + border-right: 2px solid #2c3e50; + border-top: 2px solid #2c3e50; + border-bottom: 2px solid #2c3e50; +} + +.matrix-display table { + border-collapse: collapse; + margin: 0 auto; +} + +.matrix-display td { + padding: 0.4rem 0.8rem; + text-align: center; + font-size: 1rem; + min-width: 40px; +} + +.matrix-section { + margin: 1.5rem 0; +} + +.matrix-section h3 { + font-size: 1rem; + margin-bottom: 0.5rem; + color: #e8eaf0; + font-weight: bold; +} + +.button-group { + display: flex; + gap: 1rem; + margin-top: 1rem; +} + +.button-group button { + flex: 1; +} + +.error-message { + background: #fff5f5; + color: #e74c3c; + padding: 0.75rem; + border-radius: 4px; + border-left: 3px solid #e74c3c; + margin: 1rem 0; + display: none; + font-size: 0.9rem; +} + +.error-message.show { + display: block; +} + +.loading { + text-align: center; + padding: 1.5rem; + display: none; +} + +.loading.show { + display: block; +} + +.spinner { + border: 3px solid #ecf0f1; + border-top: 3px solid #3498db; + border-radius: 50%; + width: 35px; + height: 35px; + animation: spin 1s linear infinite; + margin: 0 auto; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.dimension-inputs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + margin-bottom: 1rem; +} + +.dimension-inputs label { + display: block; + margin-bottom: 0.4rem; + font-size: 0.9rem; +} + +.dimension-inputs input { + width: 100%; + margin: 0; +} + +.result-info { + margin-top: 1rem; + padding: 0.75rem; + background: #ecf0f1; + border-radius: 4px; + font-size: 0.9rem; +} + +.result-info strong { + color: #2c3e50; +} \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css deleted file mode 100644 index 38f607c..0000000 --- a/static/css/style.css +++ /dev/null @@ -1,943 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -:root { - --primary-color: #1a237e; - --primary-light: #283593; - --primary-dark: #0d1650; - --secondary-color: #00796b; - --secondary-light: #4db8a8; - --accent-color: #d32f2f; - --success-color: #388e3c; - --warning-color: #f57c00; - --light-bg: #f5f5f5; - --white: #ffffff; - --dark-text: #212121; - --border-color: #e0e0e0; - --shadow: 0 2px 8px rgba(0, 0, 0, 0.12); - --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.15); -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; - background-color: var(--light-bg); - color: var(--dark-text); - line-height: 1.6; - min-height: 100vh; -} - -header { - background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%); - color: var(--white); - padding: 3rem 2rem; - text-align: center; - box-shadow: var(--shadow-lg); - border-bottom: 3px solid var(--secondary-color); -} - -header h1 { - font-size: 2.5rem; - font-weight: 600; - letter-spacing: 0.5px; - margin-bottom: 0.5rem; -} - -header p { - font-size: 1rem; - opacity: 0.95; - font-weight: 300; -} - -main { - max-width: 1100px; - margin: 2.5rem auto; - padding: 0 1.5rem; - min-height: calc(100vh - 300px); -} - -/* Menu/Index Styles */ -h2 { - color: var(--primary-color); - margin: 2rem 0 1.5rem; - font-size: 1.8rem; - font-weight: 600; - letter-spacing: 0.3px; -} - -ul { - list-style: none; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 1.5rem; - margin: 2rem 0; -} - -ul li { - background: var(--white); - padding: 1.5rem; - border-radius: 8px; - box-shadow: var(--shadow); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - border-left: 4px solid var(--secondary-color); - position: relative; - overflow: hidden; -} - -ul li::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, transparent, rgba(0, 121, 107, 0.1), transparent); - transition: left 0.5s ease; -} - -ul li:hover::before { - left: 100%; -} - -ul li:hover { - transform: translateY(-4px); - box-shadow: var(--shadow-lg); -} - -a { - color: var(--secondary-color); - text-decoration: none; - font-weight: 500; - transition: color 0.3s ease; -} - -a:hover { - color: var(--secondary-light); -} - -ul li a { - display: block; - color: var(--primary-color); - font-size: 1.1rem; - font-weight: 600; -} - -ul li a:hover { - color: var(--secondary-color); -} - -/* Back Link Styles */ -.back-link { - display: inline-flex; - align-items: center; - gap: 0.5rem; - margin: 1.5rem 0; - padding: 0.75rem 1.25rem; - background-color: var(--secondary-color); - color: var(--white); - border-radius: 6px; - transition: all 0.3s ease; - font-weight: 500; - box-shadow: var(--shadow); -} - -.back-link:hover { - background-color: var(--secondary-light); - transform: translateX(-3px); - box-shadow: var(--shadow-lg); - color: var(--white); -} - -/* Form Styles */ -form { - background: var(--white); - padding: 2rem; - border-radius: 8px; - box-shadow: var(--shadow); - margin: 2rem 0; -} - -form h3 { - color: var(--primary-color); - margin-bottom: 1.5rem; - font-size: 1.3rem; - font-weight: 600; -} - -label { - display: block; - margin-top: 1.25rem; - margin-bottom: 0.5rem; - font-weight: 500; - color: var(--dark-text); - font-size: 0.95rem; -} - -label:first-of-type { - margin-top: 0; -} - -input[type="text"], -input[type="number"], -textarea, -select { - width: 100%; - padding: 0.75rem; - border: 2px solid var(--border-color); - border-radius: 6px; - font-size: 1rem; - font-family: inherit; - transition: all 0.3s ease; - background-color: var(--white); -} - -input[type="text"]:focus, -input[type="number"]:focus, -textarea:focus, -select:focus { - outline: none; - border-color: var(--secondary-color); - box-shadow: 0 0 0 3px rgba(0, 121, 107, 0.1); -} - -textarea { - resize: vertical; - min-height: 100px; -} - -/* Button Styles */ -button, -input[type="submit"] { - background-color: var(--secondary-color); - color: var(--white); - padding: 0.75rem 2rem; - border: none; - border-radius: 6px; - font-size: 0.95rem; - font-weight: 600; - cursor: pointer; - transition: all 0.3s ease; - margin-top: 1.5rem; - margin-right: 0.75rem; - box-shadow: var(--shadow); -} - -button:hover, -input[type="submit"]:hover { - background-color: var(--secondary-light); - transform: translateY(-2px); - box-shadow: var(--shadow-lg); -} - -button:active, -input[type="submit"]:active { - transform: translateY(0); -} - -button:disabled, -input[type="submit"]:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.btn-secondary { - background-color: var(--primary-light); -} - -.btn-secondary:hover { - background-color: var(--primary-color); -} - -.btn-success { - background-color: var(--success-color); -} - -.btn-success:hover { - background-color: #2e7d32; -} - -.btn-warning { - background-color: var(--warning-color); -} - -.btn-warning:hover { - background-color: #e65100; -} - -.btn-danger { - background-color: var(--accent-color); -} - -.btn-danger:hover { - background-color: #b71c1c; -} - -/* Matrix Input Container */ -.matrix-container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 1.5rem; - margin: 2rem 0; -} - -.matrix-input { - background: var(--light-bg); - padding: 1.5rem; - border-radius: 8px; - border: 2px solid var(--border-color); - transition: border-color 0.3s ease; -} - -.matrix-input:focus-within { - border-color: var(--secondary-color); -} - -.matrix-input h3 { - color: var(--primary-color); - margin-bottom: 1rem; - font-size: 1.1rem; - font-weight: 600; -} - -.matrix-row { - display: flex; - gap: 0.5rem; - margin-bottom: 0.75rem; -} - -.matrix-row input { - flex: 1; - width: auto; - margin-top: 0; - padding: 0.5rem; - text-align: center; -} - -/* Result Styles */ -.result-container .matrix-container { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 2rem; - margin-top: 2rem; -} - -.result-container.show { - display: block; - animation: slideIn 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -@keyframes slideIn { - from { - opacity: 0; - transform: translateY(-15px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.result-container h3 { - color: var(--primary-color); - margin-bottom: 1.5rem; - font-size: 1.3rem; - font-weight: 600; -} - -.matrix-result { - background: var(--white); - padding: 1.5rem; - border-radius: 8px; - margin: 1rem 0; - box-shadow: var(--shadow); -} - -.matrix-result h4 { - color: var(--primary-color); - margin-bottom: 1rem; - font-size: 1.1rem; - font-weight: 600; - text-align: center; -} - -.matrix-result table { - width: auto; - margin: 0 auto; - border-collapse: separate; - border-spacing: 0; - border: 2px solid var(--primary-color); - border-radius: 8px; - overflow: hidden; -} - -.matrix-result tr:first-child td { - border-top: none; -} - -.matrix-result tr:last-child td { - border-bottom: none; -} - -.matrix-result td:first-child { - border-left: none; -} - -.matrix-result td:last-child { - border-right: none; -} - -.matrix-result tbody tr:hover td { - background-color: rgba(0, 121, 107, 0.08); - transition: background-color 0.2s ease; -} - -/* Alert/Message Styles */ -.alert { - padding: 1rem 1.25rem; - margin: 1rem 0; - border-radius: 6px; - border-left: 4px solid; - animation: alertSlide 0.3s ease; -} - -@keyframes alertSlide { - from { - opacity: 0; - transform: translateX(-20px); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -.alert p { - margin: 0; - font-weight: 500; -} - -.alert-info { - background-color: #e3f2fd; - color: #01579b; - border-left-color: #1976d2; -} - -.alert-success { - background-color: #e8f5e9; - color: #1b5e20; - border-left-color: var(--success-color); -} - -.alert-warning { - background-color: #fff3e0; - color: #e65100; - border-left-color: var(--warning-color); -} - -.alert-error, -.alert-danger { - background-color: #ffebee; - color: #b71c1c; - border-left-color: var(--accent-color); -} - -/* Loader Styles */ -.loader { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; - animation: fadeIn 0.2s ease; -} - -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -.loader p { - background: var(--white); - padding: 2rem; - border-radius: 8px; - font-weight: 600; - color: var(--primary-color); - box-shadow: var(--shadow-lg); -} - -/* Menu Button Grid */ -.button-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 1.5rem; - margin: 2rem 0; -} - -.menu-button { - display: block; - background: var(--white); - padding: 1.5rem; - border-radius: 8px; - box-shadow: var(--shadow); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - border-left: 4px solid var(--secondary-color); - position: relative; - overflow: hidden; - color: var(--primary-color); - font-size: 1.1rem; - font-weight: 600; - text-decoration: none; - cursor: pointer; -} - -.menu-button::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, transparent, rgba(0, 121, 107, 0.1), transparent); - transition: left 0.5s ease; -} - -.menu-button:hover::before { - left: 100%; -} - -.menu-button:hover { - transform: translateY(-4px); - box-shadow: var(--shadow-lg); - color: var(--secondary-color); -} - -/* Responsive Design */ -@media (max-width: 768px) { - header { - padding: 2rem 1rem; - } - - header h1 { - font-size: 1.8rem; - } - - main { - margin: 1.5rem auto; - padding: 0 1rem; - } - - ul { - grid-template-columns: 1fr; - } - - .matrix-container { - grid-template-columns: 1fr; - } - - form { - padding: 1.5rem; - } - - button, - input[type="submit"] { - width: 100%; - margin-right: 0; - margin-bottom: 0.5rem; - } - - h2 { - font-size: 1.5rem; - } - - .matrix-result td { - padding: 0.75rem 1rem; - min-width: 60px; - font-size: 1rem; - } - - .result-container .matrix-container { - grid-template-columns: 1fr; - } -} - -@media (max-width: 480px) { - header { - padding: 1.5rem 1rem; - } - - header h1 { - font-size: 1.4rem; - } - - h2 { - font-size: 1.3rem; - } - - .matrix-row { - flex-direction: column; - gap: 0.75rem; - } - - .matrix-row input { - width: 100%; - } - - ul li { - padding: 1rem; - } - - .result-container .matrix-container { - grid-template-columns: 1fr; - } -} - -/* Utility Classes */ -.text-center { - text-align: center; -} - -.text-right { - text-align: right; -} - -.mt-1 { margin-top: 0.5rem; } -.mt-2 { margin-top: 1rem; } -.mt-3 { margin-top: 1.5rem; } - -.mb-1 { margin-bottom: 0.5rem; } -.mb-2 { margin-bottom: 1rem; } -.mb-3 { margin-bottom: 1.5rem; } - -.hidden { - display: none !important; -} - -.inline-block { - display: inline-block; -} - -/* Footer */ -footer { - background-color: var(--primary-color); - color: var(--white); - text-align: center; - padding: 2rem; - margin-top: 3rem; - font-size: 0.9rem; - opacity: 0.9; -} - -/* Formatação de números decimais */ -.matrix-result td { - text-align: center; - padding: 0.75rem 1rem; - border: 1px solid var(--border-color); - font-family: 'Courier New', monospace; - font-size: 0.95rem; - min-width: 70px; - position: relative; -} - -/* Números formatados para 2 casas decimais */ -.matrix-result td.number { - font-variant-numeric: tabular-nums; -} - -/* Estilo especial para números inteiros */ -.matrix-result td.integer { - color: var(--primary-color); - font-weight: 500; -} - -/* Estilo para números decimais */ -.matrix-result td.decimal { - color: var(--secondary-color); -} - -/* Container para resultado com formatação */ -.result-details { - background: var(--white); - padding: 1.5rem; - border-radius: 8px; - margin: 1.5rem 0; - box-shadow: var(--shadow); -} - -.determinant-value { - font-size: 1.8rem; - font-family: 'Courier New', monospace; - font-weight: bold; - text-align: center; - padding: 1rem; - background: linear-gradient(135deg, #f5f5f5 0%, #ffffff 100%); - border-radius: 6px; - margin: 1rem 0; - border: 2px solid var(--border-color); -} - -/* Badge para indicar se é float ou inteiro */ -.value-type { - display: inline-block; - padding: 0.2rem 0.6rem; - border-radius: 4px; - font-size: 0.75rem; - font-weight: 600; - margin-left: 0.5rem; - vertical-align: middle; -} - -.value-type.float { - background-color: #e3f2fd; - color: #1976d2; -} - -.value-type.integer { - background-color: #e8f5e9; - color: #2e7d32; -} -/* Estilos específicos para criptografia */ -.operation-selector { - background: var(--white); - padding: 1.5rem; - border-radius: 8px; - margin-bottom: 1.5rem; - box-shadow: var(--shadow); -} - -.radio-group { - display: flex; - gap: 2rem; - margin-top: 1rem; -} - -.radio-group label { - display: flex; - align-items: center; - gap: 0.5rem; - cursor: pointer; - font-weight: 500; -} - -.radio-group input[type="radio"] { - width: auto; - margin: 0; -} - -.message-section, -.matrix-section { - background: var(--white); - padding: 1.5rem; - border-radius: 8px; - margin-bottom: 1.5rem; - box-shadow: var(--shadow); -} - -.message-section h3, -.matrix-section h3 { - color: var(--primary-color); - margin-bottom: 1rem; -} - -.message-box { - background: #f8f9fa; - padding: 1rem; - border-radius: 6px; - border: 1px solid var(--border-color); - font-family: 'Courier New', monospace; - margin: 0.5rem 0; - white-space: pre-wrap; - word-break: break-word; -} - -.message-box.success { - background-color: #d4edda; - border-color: #c3e6cb; - color: #155724; - font-size: 1.1rem; - font-weight: 500; - padding: 1rem; -} - -.numeric-sequence { - background: #e9ecef; - padding: 1rem; - border-radius: 6px; - font-family: 'Courier New', monospace; - overflow-x: auto; - margin: 0.5rem 0; - font-size: 0.95rem; -} - -.encrypted-sequence { - background: #fff3cd; - padding: 1rem; - border-radius: 6px; - border: 1px solid #ffc107; - margin: 0.5rem 0; - display: flex; - align-items: center; - justify-content: space-between; -} - -.encrypted-sequence code { - font-family: 'Courier New', monospace; - font-size: 0.9rem; - flex-grow: 1; - overflow-x: auto; -} - -.result-section { - margin-top: 1.5rem; -} - -.result-section h4 { - color: var(--primary-color); - margin: 1.5rem 0 0.75rem; - font-size: 1.1rem; - font-weight: 600; -} - -.matrix-table { - width: auto; - margin: 0 auto; - border-collapse: collapse; - border: 2px solid var(--primary-color); - border-radius: 6px; - overflow: hidden; -} - -.matrix-table td { - padding: 0.5rem 1rem; - border: 1px solid var(--border-color); - text-align: center; - min-width: 60px; - font-family: 'Courier New', monospace; -} - -.matrix-table tr:nth-child(even) { - background-color: #f8f9fa; -} - -.matrix-table tr:hover { - background-color: #e9ecef; -} - -/* Estilo para matriz pequena */ -.matrix-table.small { - font-size: 0.9rem; -} - -.matrix-table.small td { - padding: 0.25rem 0.5rem; - min-width: 40px; -} - -/* Estilos para resultados de criptografia */ -.result-section { - margin-top: 1.5rem; -} - -.result-section h4 { - color: var(--primary-color); - margin: 1.5rem 0 0.75rem; - font-size: 1.1rem; - font-weight: 600; - border-bottom: 2px solid var(--border-color); - padding-bottom: 0.5rem; -} - -.encrypted-sequence { - background: #fff3cd; - padding: 1rem; - border-radius: 6px; - border: 1px solid #ffc107; - margin: 0.5rem 0; -} - -.encrypted-numbers { - font-family: 'Courier New', monospace; - font-size: 0.9rem; - overflow-x: auto; - white-space: nowrap; - padding: 0.5rem; - background: rgba(255, 255, 255, 0.7); - border-radius: 4px; -} - -.matrix-error { - color: #dc3545; - padding: 1rem; - background: #f8d7da; - border-radius: 6px; - font-weight: 500; -} - -.message-box { - background: #f8f9fa; - padding: 1rem; - border-radius: 6px; - border: 1px solid var(--border-color); - font-family: 'Courier New', monospace; - margin: 0.5rem 0; - white-space: pre-wrap; - word-break: break-word; - line-height: 1.5; -} - -.numeric-sequence { - background: #e9ecef; - padding: 1rem; - border-radius: 6px; - font-family: 'Courier New', monospace; - overflow-x: auto; - margin: 0.5rem 0; - font-size: 0.95rem; - line-height: 1.6; -} - -.matrix-table { - margin: 0 auto; - border-collapse: collapse; - border: 2px solid var(--primary-color); - border-radius: 6px; - overflow: hidden; - background: white; -} - -.matrix-table td { - padding: 0.5rem 0.75rem; - border: 1px solid var(--border-color); - text-align: center; - min-width: 60px; - font-family: 'Courier New', monospace; - font-size: 0.9rem; -} - -.matrix-table tr:nth-child(even) { - background-color: #f8f9fa; -} - -.matrix-table tr:hover { - background-color: #e9ecef; -} \ No newline at end of file diff --git a/static/css/styles.css b/static/css/styles.css new file mode 100644 index 0000000..190cc0e --- /dev/null +++ b/static/css/styles.css @@ -0,0 +1,256 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: Arial, sans-serif; + line-height: 1.6; + background-color: #0f1219;; +} + +.grid-background { + position: fixed; + inset: 0; + top: 0; + left: 0; + opacity: 0.03; + pointer-events: none; + background-image: + linear-gradient(#e8eaed 1px, transparent 1px), + linear-gradient(90deg, #e8eaed 1px, transparent 1px); + background-size: 40px 40px; + width: 100%; + height: 100%; +} + +header { + background-color: #0f1219; + border-bottom: 1px solid #2a2d3e; + position: relative; + z-index: 10; +} + +.header-content { + margin: 0 auto; + max-width: 1024px; + display: flex; + justify-content: space-between; + color: #e8eaf0; + text-transform: uppercase; + background-color: #0f1219; + backdrop-filter: blur(8px); + padding: 1.5rem 1rem; +} + +header h1 { + font-size: 25px; + color: #e8eaf0; + font-weight: 500; + letter-spacing: -0.5px; +} + +main { + max-width: 1024px; + margin: 0 auto; + padding: 4rem 1rem; +} + +h2 { + font-size: 35; + font-weight: 500; + margin-bottom: 2rem; + color: #e8eaf0; + text-align: center; +} + +form { + position: relative; + background: #1a1d26; + padding: 1.5rem; + border: 1px, solid #2a2e38; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + margin-bottom: 1.5rem; +} + +label { + display: block; + font-weight: bold; + margin-bottom: 0.4rem; + color: #e8eaf0; + font-size: 0.9rem; +} + +input[type="number"], +textarea, +select { + width: 100%; + padding: 0.6rem; + border: 2px solid #2a2e38; + border-radius: 8px; + font-size: 0.95rem; + margin-bottom: 1rem; + font-family: Arial, sans-serif; + transition: all 0.3s; +} + +input[type="number"]:focus, +textarea:focus, +select:focus { + border-color: #e8eaf0; +} + +button { + padding: 0.65rem 1.25rem; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.95rem; + font-weight: bold; + transition: all 0.2s; +} + +button[type="submit"], +.btn-primary { + background-color: #0f1219; + color: #e8eaf0; + border: 1px solid #2a2d3e; + transition: all 0.3s; +} + +button[type="submit"]:hover, +.btn-primary:hover { + border-color: #3498db; + color: #3498db; +} + +button[type="button"], +.btn-secondary { + background-color: #0f1219; + color: #e8eaf0; + border: 1px solid #2a2d3e; +} + +button[type="button"]:hover, +.btn-secondary:hover { + border-color: #3498db; + color: #3498db; + transition: all 0.3s; +} + +#result { + position: relative; + background: #1a1d26; + padding: 1.5rem; + border: 1px solid #2a2e38; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + display: none; +} + +#result h3 { + color: #e8eaf0; +} + +#result p { + background: none !important; + color: #e8eaf0; +} + +#result.show { + display: block; +} + +/*Cartões na Página Inicial*/ + +ul { + display: grid; + list-style: none; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap:1rem; + position: relative; +} + +ul li { + margin: 1rem 0; + background: #1a1d26; + height: 175px; + width: 225px; + border: 1px solid #2a2e38; + transition: all 0.4s; + border-radius: 5px; +} + +ul li a { + display: block; + padding: 1rem; + color: #e8eaf0; + text-decoration: none; +} + +ul li:hover { + background-color: #22262f; + border-color: #e8eaf0; +} + +.card-tittle { + color: #e8eaf0; + padding-left: 1rem; + margin-top: 13px; + padding-bottom: 15px; + font-size: 15px; + font-weight: 600; +} + +.card-description { + color: #e8eaf0; + padding-left: 1rem; + margin-top: -15px; + font-size: 13px; + font-weight: 400; +} + +.card-icon { + margin-left: 1rem; + margin-top: 1rem; + justify-content: center; + align-items: center; +} + +.button-go { + display: flex; + flex-direction: row; +} + +.icon-go { + width: 17px; + height: 17px; +} + + +/*Responsividade*/ + +@media (max-width: 768px) { + header h1 { + font-size: 1.4rem; + } + + nav { + gap: 0.5rem; + } + + nav a { + padding: 0.4rem 0.8rem; + font-size: 0.9rem; + } + + main { + margin: 1rem auto; + } + + form { + padding: 1.5rem; + } +} \ No newline at end of file diff --git a/static/js/decrypt.js b/static/js/decrypt.js new file mode 100644 index 0000000..5e0b164 --- /dev/null +++ b/static/js/decrypt.js @@ -0,0 +1,159 @@ +const form = document.getElementById('decrypt-form'); +const generateBtn = document.getElementById('generate-btn'); + +generateBtn.addEventListener('click', function() { + const size = parseInt(document.getElementById('size').value); + const encryptedCols = parseInt(document.getElementById('encrypted-cols').value); + + clearMatrixInputs(); + hideError(); + hideResult(); + + generateMatrixInput(size, size, 'matrix-inputs', 'Matriz de Codificação', 'encoding-matrix'); + generateMatrixInput(size, encryptedCols, 'matrix-inputs', 'Matriz Encriptada', 'encrypted-matrix'); +}); + +form.addEventListener('submit', async function(e) { + e.preventDefault(); + hideError(); + hideResult(); + + const size = parseInt(document.getElementById('size').value); + const encryptedCols = parseInt(document.getElementById('encrypted-cols').value); + + const payload = { + size: size, + encoding_matrix: readMatrixValues('encoding-matrix'), + encrypted_cols: encryptedCols, + encrypted_matrix: readMatrixValues('encrypted-matrix') + }; + + try { + const result = await apiCall('/decrypt', payload); + const resultDiv = document.getElementById('result'); + resultDiv.innerHTML = `

Mensagem Desencriptada

${result.decrypted_message}

`; + showResult(); + + const exportBtn = document.createElement('button'); + exportBtn.textContent = 'Exportar como JSON'; + exportBtn.className = 'btn-secondary'; + exportBtn.style.marginTop = '1rem'; + + exportBtn.onclick = () => exportDecryptAsJSON( + payload.encoding_matrix, + payload.encrypted_matrix, + result.decrypted_message + ); + + resultDiv.appendChild(exportBtn); + + showResult(); + + + const exportXMLBtn = document.createElement('button'); + exportXMLBtn.textContent = 'Exportar como XML'; + exportXMLBtn.className = 'btn-secondary'; + exportXMLBtn.style.marginTop = '0.5rem'; + + exportXMLBtn.onclick = () => exportDecryptAsXML( + payload.encoding_matrix, + payload.encrypted_matrix, + result.decrypted_message + ); + + resultDiv.appendChild(exportXMLBtn); + + const exportHTMLBtn = document.createElement('button'); + exportHTMLBtn.textContent = 'Exportar como HTML'; + exportHTMLBtn.className = 'btn-secondary'; + exportHTMLBtn.style.marginTop = '0.5rem'; + + exportHTMLBtn.onclick = () => exportDecryptAsHTML( + payload.encoding_matrix, + payload.encrypted_matrix, + result.decrypted_message +); + +document.getElementById('result').appendChild(exportHTMLBtn); + + } catch (error) { + showError(error.message); + } +}); + +generateBtn.click(); + +function exportDecryptAsJSON(encodingMatrix, encryptedMatrix, decryptedMessage) { + const json = +`{ + "operation": "decrypt", + "encodingMatrix": ${prettyJson(encodingMatrix, 4)}, + "encryptedMatrix": ${prettyJson(encryptedMatrix, 4)}, + "decryptedMessage": "${decryptedMessage}" +}`; + + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'desencriptacao.json'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportDecryptAsXML(encodingMatrix, encryptedMatrix, decryptedMessage) { + const xml = +` + + ${prettyXML(encodingMatrix, 'encodingMatrix')} + ${prettyXML(encryptedMatrix, 'encryptedMatrix')} + ${decryptedMessage} +`; + + const blob = new Blob([xml], { type: 'application/xml' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'desencriptacao.xml'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportDecryptAsHTML(encodingMatrix, encryptedMatrix, decryptedMessage) { + const html = +` + + + + Desencriptação + + +

Operação: Decrypt

+ +

Matriz de Codificação

+ ${prettyHTML(encodingMatrix, 'encodingMatrix')} + +

Matriz Encriptada

+ ${prettyHTML(encryptedMatrix, 'encryptedMatrix')} + +

Mensagem Desencriptada

+

+ ${decryptedMessage} +

+ +`; + + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'desencriptacao.html'; + a.click(); + + URL.revokeObjectURL(url); +} \ No newline at end of file diff --git a/static/js/determinant.js b/static/js/determinant.js new file mode 100644 index 0000000..cb25ad4 --- /dev/null +++ b/static/js/determinant.js @@ -0,0 +1,142 @@ +const form = document.getElementById('matrix-form'); +const generateBtn = document.getElementById('generate-btn'); + +generateBtn.addEventListener('click', function() { + const size = parseInt(document.getElementById('size').value); + + clearMatrixInputs(); + hideError(); + hideResult(); + + generateMatrixInput(size, size, 'matrix-inputs', 'Matriz', 'matrix-a'); +}); + +form.addEventListener('submit', async function(e) { + e.preventDefault(); + hideError(); + hideResult(); + + const size = parseInt(document.getElementById('size').value); + + const payload = { + size: size, + matrix: readMatrixValues('matrix-a') + }; + + try { + const result = await apiCall('/determinant', payload); + const resultDiv = document.getElementById('result'); + resultDiv.innerHTML = `

Determinante

${result.result}

`; + showResult(); + + const exportBtn = document.createElement('button'); + exportBtn.textContent = 'Exportar como JSON'; + exportBtn.className = 'btn-secondary'; + exportBtn.style.marginTop = '1rem'; + + exportBtn.onclick = () => exportDeterminantAsJSON( + payload.matrix, + result.result + ); + + resultDiv.appendChild(exportBtn); + + showResult(); + + const exportXMLBtn = document.createElement('button'); + exportXMLBtn.textContent = 'Exportar como XML'; + exportXMLBtn.className = 'btn-secondary'; + exportXMLBtn.style.marginTop = '0.5rem'; + + exportXMLBtn.onclick = () => exportDeterminantAsXML( + payload.matrix, + result.result + ); + + resultDiv.appendChild(exportXMLBtn); + + const exportHTMLBtn = document.createElement('button'); + exportHTMLBtn.textContent = 'Exportar como HTML'; + exportHTMLBtn.className = 'btn-secondary'; + exportHTMLBtn.style.marginTop = '0.5rem'; + + exportHTMLBtn.onclick = () => exportDeterminantAsHTML( + payload.matrix, + result.result +); + + document.getElementById('result').appendChild(exportHTMLBtn); + + } catch (error) { + showError(error.message); + } +}); + +generateBtn.click(); + +function exportDeterminantAsJSON(matrix, determinant) { + const json = +`{ + "operation": "determinant", + "matrix": ${prettyJson(matrix, 4)}, + "result": ${determinant} +}`; + + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'determinante.json'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportDeterminantAsXML(matrix, determinant) { + const xml = +` + + ${prettyXML(matrix, 'matrix')} + ${determinant} +`; + + const blob = new Blob([xml], { type: 'application/xml' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'determinante.xml'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportDeterminantAsHTML(matrix, determinant) { + const html = +` + + + + Determinante + + +

Operação: Determinante

+ + ${prettyHTML(matrix, 'Matriz')} + +

Resultado

+

${determinant}

+ +`; + + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'determinante.html'; + a.click(); + + URL.revokeObjectURL(url); +} diff --git a/static/js/encrypt.js b/static/js/encrypt.js new file mode 100644 index 0000000..09c11cc --- /dev/null +++ b/static/js/encrypt.js @@ -0,0 +1,153 @@ +const form = document.getElementById('encrypt-form'); +const generateBtn = document.getElementById('generate-btn'); + +generateBtn.addEventListener('click', function() { + const size = parseInt(document.getElementById('size').value); + + clearMatrixInputs(); + hideError(); + hideResult(); + + generateMatrixInput(size, size, 'matrix-inputs', 'Matriz de Codificação', 'encoding-matrix'); +}); + +form.addEventListener('submit', async function(e) { + e.preventDefault(); + hideError(); + hideResult(); + + const payload = { + size: parseInt(document.getElementById('size').value), + encoding_matrix: readMatrixValues('encoding-matrix'), + message: document.getElementById('message').value + }; + + const payload_determinant = { + size: parseInt(document.getElementById('size').value), + matrix: readMatrixValues('encoding-matrix') + }; + + try { + const detResult = await apiCall('/determinant', payload_determinant); + if (detResult.result === 0) { + showError('A matriz de codificação deve ser invertível (det ≠ 0).'); + return; + } + const result = await apiCall('/encrypt', payload); + displayMatrix(result.encrypted_matrix, 'Mensagem Encriptada'); + + const exportBtn = document.createElement('button'); + exportBtn.textContent = 'Exportar como JSON'; + exportBtn.className = 'btn-secondary'; + exportBtn.style.marginTop = '1rem'; + + exportBtn.onclick = () => exportEncryptAsJSON( + payload.message, + payload.encoding_matrix, + result.encrypted_matrix + ); + document.getElementById('result').appendChild(exportBtn); + + const exportXMLBtn = document.createElement('button'); + exportXMLBtn.textContent = 'Exportar como XML'; + exportXMLBtn.className = 'btn-secondary'; + exportXMLBtn.style.marginTop = '0.5rem'; + + exportXMLBtn.onclick = () => exportEncryptAsXML( + payload.message, + payload.encoding_matrix, + result.encrypted_matrix + ); + + document.getElementById('result').appendChild(exportXMLBtn); + + const exportHTMLBtn = document.createElement('button'); + exportHTMLBtn.textContent = 'Exportar como HTML'; + exportHTMLBtn.className = 'btn-secondary'; + exportHTMLBtn.style.marginTop = '0.5rem'; + + exportHTMLBtn.onclick = () => exportEncryptAsHTML( + payload.message, + payload.encoding_matrix, + result.encrypted_matrix +); + +document.getElementById('result').appendChild(exportHTMLBtn); + + + } catch (error) { + showError(error.message); + } +}); + +generateBtn.click(); + +function exportEncryptAsJSON(message, encodingMatrix, encryptedMatrix) { + const json = +`{ + "operation": "encrypt", + "message": "${message}", + "encodingMatrix": ${prettyJson(encodingMatrix, 4)}, + "encryptedMatrix": ${prettyJson(encryptedMatrix, 4)} +}`; + + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'encriptacao.json'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportEncryptAsXML(message, encodingMatrix, encryptedMatrix) { + const xml = +` + + ${message} + ${prettyXML(encodingMatrix, 'encodingMatrix')} + ${prettyXML(encryptedMatrix, 'encryptedMatrix')} +`; + + const blob = new Blob([xml], { type: 'application/xml' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'encriptacao.xml'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportEncryptAsHTML(message, encodingMatrix, encryptedMatrix) { + const html = +` + + + + Encriptação + + +

Operação: Encriptação

+ +

Mensagem Original

+

${message}

+ + ${prettyHTML(encodingMatrix, 'Matriz de Codificação')} + ${prettyHTML(encryptedMatrix, 'Matriz Encriptada')} + +`; + + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'encriptacao.html'; + a.click(); + + URL.revokeObjectURL(url); +} diff --git a/static/js/inverse.js b/static/js/inverse.js new file mode 100644 index 0000000..c4a775d --- /dev/null +++ b/static/js/inverse.js @@ -0,0 +1,139 @@ +const form = document.getElementById('matrix-form'); +const generateBtn = document.getElementById('generate-btn'); + +generateBtn.addEventListener('click', function() { + const size = parseInt(document.getElementById('size').value); + + clearMatrixInputs(); + hideError(); + hideResult(); + + generateMatrixInput(size, size, 'matrix-inputs', 'Matriz', 'matrix-a'); +}); + +form.addEventListener('submit', async function(e) { + e.preventDefault(); + hideError(); + hideResult(); + + const size = parseInt(document.getElementById('size').value); + + const payload = { + size: size, + matrix: readMatrixValues('matrix-a') + }; + + try { + const result = await apiCall('/inverse', payload); + displayMatrix(result.result, 'Matriz Inversa'); + + const exportBtn = document.createElement('button'); + exportBtn.textContent = 'Exportar como JSON'; + exportBtn.className = 'btn-secondary'; + exportBtn.style.marginTop = '1rem'; + + exportBtn.onclick = () => exportInverseAsJSON( + payload.matrix, + decimalMatrix(result.result, 2) + ); + + document.getElementById('result').appendChild(exportBtn); + + const exportXMLBtn = document.createElement('button'); + exportXMLBtn.textContent = 'Exportar como XML'; + exportXMLBtn.className = 'btn-secondary'; + exportXMLBtn.style.marginTop = '0.5rem'; + + exportXMLBtn.onclick = () => exportInverseAsXML( + payload.matrix, + decimalMatrix(result.result, 2) + ); + + document.getElementById('result').appendChild(exportXMLBtn); + + const exportHTMLBtn = document.createElement('button'); + exportHTMLBtn.textContent = 'Exportar como HTML'; + exportHTMLBtn.className = 'btn-secondary'; + exportHTMLBtn.style.marginTop = '0.5rem'; + + exportHTMLBtn.onclick = () => exportInverseAsHTML( + payload.matrix, + decimalMatrix(result.result, 2) +); + +document.getElementById('result').appendChild(exportHTMLBtn); + + } catch (error) { + showError(error.message); + } +}); + +generateBtn.click(); + +const json = prettyJson(matrixA, 4); + + +function exportInverseAsJSON(originalMatrix, inverseMatrix) { + const json = +`{ + "operation": "inverse", + "originalMatrix": ${prettyJson(originalMatrix, 4)}, + "inverseMatrix": ${prettyJson(inverseMatrix, 4)} +}`; + + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'matriz_inversa.json'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportInverseAsXML(originalMatrix, inverseMatrix) { + const xml = +` + +${prettyXML(originalMatrix, 'originalMatrix')} +${prettyXML(inverseMatrix, 'inverseMatrix')} +`; + + const blob = new Blob([xml], { type: 'application/xml' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'matriz_inversa.xml'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportInverseAsHTML(originalMatrix, inverseMatrix) { + const html = +` + + + + Matriz Inversa + + +

Operação: Matriz Inversa

+ + ${prettyHTML(originalMatrix, 'Matriz Original')} + ${prettyHTML(inverseMatrix, 'Matriz Inversa')} + +`; + + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'matriz_inversa.html'; + a.click(); + + URL.revokeObjectURL(url); +} diff --git a/static/js/main.js b/static/js/main.js deleted file mode 100644 index 4fbbbf2..0000000 --- a/static/js/main.js +++ /dev/null @@ -1,231 +0,0 @@ -/** - * Attach event listeners to form elements - */ -function attachEventListeners() { - const forms = document.querySelectorAll('form'); - forms.forEach(form => { - form.addEventListener('submit', handleFormSubmit); - }); -} - -/** - * Handle form submission - */ -function handleFormSubmit(e) { - e.preventDefault(); - const form = e.target; - const formData = new FormData(form); - const endpoint = form.action || window.location.pathname; - - // Show loading state - showLoading(true); - - fetch(endpoint, { - method: 'POST', - body: formData - }) - .then(response => response.json()) - .then(data => { - showLoading(false); - if (data.error) { - showAlert('error', data.error); - } else { - displayResult(data); - showAlert('success', 'Cálculo realizado com sucesso!'); - } - }) - .catch(error => { - showLoading(false); - console.error('Error:', error); - showAlert('error', 'Erro ao processar o cálculo. Verifique os dados e tente novamente.'); - }); -} - -/** - * Display result in the page - */ -function displayResult(data) { - let resultHTML = '

Resultado:

'; - - if (data.result) { - resultHTML += `

${data.result}

`; - } - - if (data.matrix) { - resultHTML += matrixToHTML(data.matrix); - } - - if (data.details) { - resultHTML += `

${data.details}

`; - } - - let resultContainer = document.querySelector('.result-container'); - - if (!resultContainer) { - resultContainer = document.createElement('div'); - resultContainer.className = 'result-container'; - document.querySelector('main').appendChild(resultContainer); - } - - resultContainer.innerHTML = resultHTML; - resultContainer.classList.add('show'); - resultContainer.scrollIntoView({ behavior: 'smooth' }); -} - -/** - * Convert matrix to HTML table - */ -function matrixToHTML(matrix) { - if (!Array.isArray(matrix) || matrix.length === 0) { - return ''; - } - - let html = '
'; - - matrix.forEach(row => { - html += ''; - if (Array.isArray(row)) { - row.forEach(cell => { - html += ``; - }); - } else { - html += ``; - } - html += ''; - }); - - html += '
${parseFloat(cell).toFixed(2)}${parseFloat(row).toFixed(2)}
'; - return html; -} - -/** - * Show/hide loading indicator - */ -function showLoading(show) { - let loader = document.querySelector('.loader'); - - if (show) { - if (!loader) { - loader = document.createElement('div'); - loader.className = 'loader'; - loader.innerHTML = '

Calculando...

'; - document.body.appendChild(loader); - } - loader.style.display = 'flex'; - } else { - if (loader) { - loader.style.display = 'none'; - } - } -} - -/** - * Show alert message - */ -function showAlert(type, message) { - const alert = document.createElement('div'); - alert.className = `alert alert-${type}`; - alert.innerHTML = `

${message}

`; - document.querySelector('main').prepend(alert); - setTimeout(() => alert.remove(), 5000); -} - -/** - * Parse matrix input from form - * Expected format: rows separated by semicolons, elements by commas - * Example: "1,2,3;4,5,6" - */ -function parseMatrixInput(input) { - if (!input) return null; - - try { - const rows = input.split(';').map(row => { - return row.trim().split(',').map(num => { - const parsed = parseFloat(num.trim()); - if (isNaN(parsed)) { - throw new Error(`Valor inválido: "${num.trim()}"`); - } - return parsed; - }); - }); - return rows; - } catch (error) { - showAlert('error', `Erro ao processar matriz: ${error.message}`); - return null; - } -} - -/** - * Validate matrix dimensions - */ -function validateMatrixDimensions(matrix) { - if (!matrix || !Array.isArray(matrix) || matrix.length === 0) { - return false; - } - - const firstRowLength = matrix[0].length; - return matrix.every(row => row.length === firstRowLength); -} - -/** - * Get matrix dimensions - */ -function getMatrixDimensions(matrix) { - if (!matrix || matrix.length === 0) return null; - return { - rows: matrix.length, - columns: matrix[0].length - }; -} - -/** - * Format number for display - */ -function formatNumber(num) { - if (Number.isInteger(num)) { - return num.toString(); - } - return parseFloat(num).toFixed(4); -} - -/** - * Clear form and results - */ -function clearResults() { - const resultContainer = document.querySelector('.result-container'); - if (resultContainer) { - resultContainer.classList.remove('show'); - setTimeout(() => { - resultContainer.innerHTML = ''; - }, 300); - } -} - -/** - * Export matrix as CSV - */ -function exportMatrixAsCSV(matrix, filename = 'matrix.csv') { - if (!matrix || !Array.isArray(matrix)) return; - - let csvContent = 'data:text/csv;charset=utf-8,'; - matrix.forEach(row => { - csvContent += row.join(',') + '\n'; - }); - - const link = document.createElement('a'); - link.setAttribute('href', encodeURI(csvContent)); - link.setAttribute('download', filename); - link.click(); -} - -/** - * Show matrix info (dimensions, type, etc) - */ -function showMatrixInfo(matrix) { - const dims = getMatrixDimensions(matrix); - if (!dims) return ''; - - return `Dimensões: ${dims.rows}×${dims.columns}`; -} - -console.log('✓ main.js loaded'); \ No newline at end of file diff --git a/static/js/matrix-builder.js b/static/js/matrix-builder.js new file mode 100644 index 0000000..4e90f97 --- /dev/null +++ b/static/js/matrix-builder.js @@ -0,0 +1,71 @@ +function generateMatrixInput(rows, cols, containerId, label, gridId) { + const container = document.getElementById(containerId); + + const section = document.createElement('div'); + section.className = 'matrix-section'; + section.innerHTML = `

${label}

`; + + const grid = document.createElement('div'); + grid.className = 'matrix-input-grid'; + grid.style.gridTemplateColumns = `repeat(${cols}, 60px)`; + grid.id = gridId; + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + const input = document.createElement('input'); + input.type = 'number'; + input.step = 'any'; + input.value = '0'; + input.dataset.row = i; + input.dataset.col = j; + grid.appendChild(input); + } + } + + section.appendChild(grid); + container.appendChild(section); +} + +function readMatrixValues(gridId) { + const inputs = document.getElementById(gridId).querySelectorAll('input'); + const rows = Math.max(...Array.from(inputs).map(inp => parseInt(inp.dataset.row))) + 1; + const cols = Math.max(...Array.from(inputs).map(inp => parseInt(inp.dataset.col))) + 1; + + const matrix = Array(rows).fill().map(() => Array(cols).fill(0)); + + inputs.forEach(input => { + const row = parseInt(input.dataset.row); + const col = parseInt(input.dataset.col); + matrix[row][col] = parseFloat(input.value) || 0; + }); + + return matrix; +} + +function displayMatrix(matrix, title) { + const resultDiv = document.getElementById('result'); + resultDiv.innerHTML = `

${title}

`; + + const display = document.createElement('div'); + display.className = 'matrix-display'; + + const table = document.createElement('table'); + matrix.forEach(row => { + const tr = document.createElement('tr'); + row.forEach(value => { + const td = document.createElement('td'); + td.textContent = Number.isInteger(value) ? value : value.toFixed(2); + tr.appendChild(td); + }); + table.appendChild(tr); + }); + + display.appendChild(table); + resultDiv.appendChild(display); + showResult(); +} + +function clearMatrixInputs() { + const container = document.getElementById('matrix-inputs'); + if (container) container.innerHTML = ''; +} \ No newline at end of file diff --git a/static/js/matrix_determinant.js b/static/js/matrix_determinant.js deleted file mode 100644 index 9ec802b..0000000 --- a/static/js/matrix_determinant.js +++ /dev/null @@ -1,159 +0,0 @@ -const elements = { - size: document.getElementById("size"), - matrixInput: document.getElementById("matrixInput"), - form: document.getElementById("matrixForm"), - result: document.getElementById("result"), - generateBtn: document.getElementById("generateBtn"), - calculateBtn: document.getElementById("calculateBtn"), - method: document.getElementById("method") -}; - -function generateInputs() { - const n = parseInt(elements.size.value); - - elements.matrixInput.innerHTML = ` -
-

Matriz ${n}×${n}

- ${Array(n).fill().map((_, i) => ` -
- ${Array(n).fill().map((_, j) => ` - - `).join('')} -
- `).join('')} -

Preencha todos os elementos da matriz quadrada

-
- `; - - elements.calculateBtn.disabled = false; -} - -function collectMatrixData() { - const n = parseInt(elements.size.value); - const matrix = []; - - for (let i = 0; i < n; i++) { - matrix[i] = []; - for (let j = 0; j < n; j++) { - const input = document.querySelector( - `input[data-row="${i}"][data-col="${j}"]` - ); - matrix[i][j] = parseFloat(input.value) || 0; - } - } - - return matrix; -} - -function matrixToHTML(matrix, title) { - return ` -
-

${title}

- - - ${matrix.map(row => ` - - ${row.map(val => { - // Formatar número - const num = parseFloat(val); - const isInteger = Math.abs(num - Math.round(num)) < 0.0001; - const formatted = isInteger ? - num.toFixed(0) : - num.toFixed(2); - - // Classe CSS baseada no tipo - const cellClass = isInteger ? 'integer' : 'decimal number'; - - return ``; - }).join('')} - - `).join('')} - -
${formatted}
-
- `; -} - -function displayResult(data) { - // Formatar determinante - const detValue = parseFloat(data.determinant); - const isInteger = Math.abs(detValue - Math.round(detValue)) < 0.0001; - const formattedDet = isInteger ? detValue.toFixed(0) : detValue.toFixed(4); - const valueType = isInteger ? 'integer' : 'float'; - - elements.result.innerHTML = ` -
-
-

Matriz Informada

- ${matrixToHTML(data.matrix)} -
- -
-
- det(A) = ${formattedDet} - - ${isInteger ? 'INTEIRO' : 'DECIMAL'} - -
- -
- ${data.determinant === 0 ? - '
⚠️ Matriz SINGULAR (determinante = 0)
' : - '
✓ Matriz NÃO-SINGULAR (determinante ≠ 0)
'} -
-
-
- `; -} -function showError(msg) { - elements.result.innerHTML = ` -
-

Erro: ${msg}

-
- `; -} - -// Event listeners -elements.generateBtn.addEventListener('click', generateInputs); -elements.size.addEventListener('change', generateInputs); - -elements.form.addEventListener('submit', async (e) => { - e.preventDefault(); - - const matrix = collectMatrixData(); - const size = parseInt(elements.size.value); - const method = elements.method.value; - - const payload = { - size: size, - matrix: matrix, - method: method - }; - - console.log("Enviando:", payload); - - try { - const response = await fetch("/determinant", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload) - }); - - const data = await response.json(); - console.log("Resposta:", data); - - if (response.ok) { - displayResult(data); - } else { - showError(data.error || "Erro desconhecido"); - } - } catch (error) { - console.error("Erro:", error); - showError("Erro de comunicação com o servidor"); - } -}); - -// Inicializar -generateInputs(); \ No newline at end of file diff --git a/static/js/matrix_multiply.js b/static/js/matrix_multiply.js deleted file mode 100644 index 968bc84..0000000 --- a/static/js/matrix_multiply.js +++ /dev/null @@ -1,178 +0,0 @@ -const elements = { - rows_a: document.getElementById("rows_a"), - cols_a: document.getElementById("cols_a"), - rows_b: document.getElementById("rows_b"), - cols_b: document.getElementById("cols_b"), - matrices: document.getElementById("matrices"), - form: document.getElementById("matrixForm"), - result: document.getElementById("result"), - compatibilityMsg: document.getElementById("compatibilityMessage"), - generateBtn: document.getElementById("generateBtn") -}; - -function checkCompatibility() { - const cols_a = parseInt(elements.cols_a.value); - const rows_b = parseInt(elements.rows_b.value); - - if (cols_a === rows_b) { - elements.compatibilityMsg.innerHTML = " ✓ Compatível"; - elements.compatibilityMsg.style.color = "green"; - elements.generateBtn.disabled = false; - return true; - } else { - elements.compatibilityMsg.innerHTML = ` ✗ Incompatível (colunas A=${cols_a} ≠ linhas B=${rows_b})`; - elements.compatibilityMsg.style.color = "red"; - elements.generateBtn.disabled = true; - return false; - } -} - -function generateInputs() { - const r_a = parseInt(elements.rows_a.value); - const c_a = parseInt(elements.cols_a.value); - const r_b = parseInt(elements.rows_b.value); - const c_b = parseInt(elements.cols_b.value); - - elements.matrices.innerHTML = ` -
-
-

Matriz A (${r_a}×${c_a})

- ${Array(r_a).fill().map((_, i) => ` -
- ${Array(c_a).fill().map((_, j) => ` - - `).join('')} -
- `).join('')} -
- -
-

Matriz B (${r_b}×${c_b})

- ${Array(r_b).fill().map((_, i) => ` -
- ${Array(c_b).fill().map((_, j) => ` - - `).join('')} -
- `).join('')} -
-
- `; -} - -function collectMatrixData() { - const r_a = parseInt(elements.rows_a.value); - const c_a = parseInt(elements.cols_a.value); - const r_b = parseInt(elements.rows_b.value); - const c_b = parseInt(elements.cols_b.value); - - const matrices = { matrix_a: [], matrix_b: [] }; - // Matriz A - for (let i = 0; i < r_a; i++) { - matrices.matrix_a[i] = []; - for (let j = 0; j < c_a; j++) { - const a = document.querySelector( - `input[data-matrix="A"][data-row="${i}"][data-col="${j}"]` - ); - matrices.matrix_a[i][j] = parseFloat(a.value) || 0; - } - } - // Matriz B - for (let i = 0; i < r_b; i++) { - matrices.matrix_b[i] = []; - for (let j = 0; j < c_b; j++) { - const b = document.querySelector( - `input[data-matrix="B"][data-row="${i}"][data-col="${j}"]` - ); - matrices.matrix_b[i][j] = parseFloat(b.value) || 0; - } - } - - return matrices; -} - -function matrixToHTML(matrix, title) { - return ` -
-

${title}

- - - ${matrix - .map( - (row) => ` - - ${row - .map( - (val) => - `` - ) - .join("")} - - ` - ) - .join("")} - -
${parseFloat(val).toFixed( - 2 - )}
-
- `; -} - -function displayResult(data) { - elements.result.innerHTML = ` -
-

Dimensões: ${data.dimensions}

-
- ${matrixToHTML(data.matrix_a, "Matriz A")} - ${matrixToHTML(data.matrix_b, "Matriz B")} - ${matrixToHTML(data.result, "Resultado")} -
-
- `; - elements.result.scrollIntoView({ behavior: "smooth", block: "nearest" }); -} - -function showError(msg) { - elements.result.innerHTML = `

${msg}

`; -} - -[elements.rows_a, elements.cols_a, elements.rows_b, elements.cols_b].forEach(input => { - input.addEventListener('change', checkCompatibility); -}); - -elements.generateBtn.addEventListener('click', function() { - if (checkCompatibility()) { - generateInputs(); - } -}); - -elements.form.addEventListener("submit", async (e) => { - e.preventDefault(); - e.stopPropagation(); - - const payload = { - rows_a: parseInt(elements.rows_a.value), - cols_a: parseInt(elements.cols_a.value), - rows_b: parseInt(elements.rows_b.value), - cols_b: parseInt(elements.cols_b.value), - ...collectMatrixData(), - }; - - try { - const response = await fetch("/multiply", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - const data = await response.json(); - - response.ok ? displayResult(data) : showError(data.error); - } catch (error) { - console.error("Error:", error); - showError("Erro de comunicação"); - } -}); - -checkCompatibility(); // Verifica compatibilidade inicial \ No newline at end of file diff --git a/static/js/matrix_scalar.js b/static/js/matrix_scalar.js deleted file mode 100644 index 7ffc6bb..0000000 --- a/static/js/matrix_scalar.js +++ /dev/null @@ -1,141 +0,0 @@ -const elements = { - rows: document.getElementById("rows"), - cols: document.getElementById("cols"), - matrices: document.getElementById("matrices"), - form: document.getElementById("matrixForm"), - result: document.getElementById("result"), - scalar: document.getElementById("scalar") -}; - -function generateInputs() { - const r = parseInt(elements.rows.value); - const c = parseInt(elements.cols.value); - - elements.matrices.innerHTML = ` -
- ${["A"] - .map( - (name) => ` -
-

Matriz ${name}

- ${Array(r) - .fill() - .map( - (_, i) => ` -
- ${Array(c) - .fill() - .map( - (_, j) => - `` - ) - .join("")} -
- ` - ) - .join("")} -
- ` - ) - .join("")} -
- `; -} - -function collectMatrixData() { - const r = parseInt(elements.rows.value); - const c = parseInt(elements.cols.value); - const matrices = { matrix_a: [] }; - - for (let i = 0; i < r; i++) { - matrices.matrix_a[i] = []; - for (let j = 0; j < c; j++) { - const a = document.querySelector( - `input[data-matrix="A"][data-row="${i}"][data-col="${j}"]` - ); - - matrices.matrix_a[i][j] = parseFloat(a.value); - } - } - return matrices; -} - -function matrixToHTML(matrix, title) { - return ` -
-

${title}

- - - ${matrix - .map( - (row) => ` - - ${row - .map( - (val) => - `` - ) - .join("")} - - ` - ) - .join("")} - -
${parseFloat(val).toFixed( - 2 - )}
-
- `; -} - -function displayResult(data) { - elements.result.innerHTML = ` -
-

Dimensões: ${data.dimensions}

-
- ${matrixToHTML(data.matrix_a, "Matriz A")} - ${matrixToHTML(data.result, "Resultado")} -
-
- `; - elements.result.scrollIntoView({ behavior: "smooth", block: "nearest" }); -} - -function showError(msg) { - elements.result.innerHTML = `

${msg}

`; -} - -elements.rows.addEventListener("change", generateInputs); -elements.cols.addEventListener("change", generateInputs); - -elements.form.addEventListener("submit", async (e) => { - e.preventDefault(); - e.stopPropagation(); - - const payload = { - rows: parseInt(elements.rows.value), - cols: parseInt(elements.cols.value), - scalar: parseInt(elements.scalar.value), - ...collectMatrixData(), - }; - - console.log("Sending:", payload); - - try { - const response = await fetch("/scalar", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - const data = await response.json(); - console.log("Response:", data); - - response.ok ? displayResult(data) : showError(data.error); - } catch (error) { - console.error("Error:", error); - showError("Erro de comunicação"); - } -}); - -generateInputs(); diff --git a/static/js/matrix_sum_sub.js b/static/js/matrix_sum_sub.js deleted file mode 100644 index 99b6317..0000000 --- a/static/js/matrix_sum_sub.js +++ /dev/null @@ -1,144 +0,0 @@ -const elements = { - rows: document.getElementById("rows"), - cols: document.getElementById("cols"), - matrices: document.getElementById("matrices"), - form: document.getElementById("matrixForm"), - result: document.getElementById("result"), - operation: document.getElementById("operation"), -}; - -function generateInputs() { - const r = parseInt(elements.rows.value); - const c = parseInt(elements.cols.value); - - elements.matrices.innerHTML = ` -
- ${["A", "B"] - .map( - (name) => ` -
-

Matriz ${name}

- ${Array(r) - .fill() - .map( - (_, i) => ` -
- ${Array(c) - .fill() - .map( - (_, j) => - `` - ) - .join("")} -
- ` - ) - .join("")} -
- ` - ) - .join("")} -
- `; -} - -function collectMatrixData() { - const r = parseInt(elements.rows.value); - const c = parseInt(elements.cols.value); - const matrices = { matrix_a: [], matrix_b: [] }; - - for (let i = 0; i < r; i++) { - matrices.matrix_a[i] = []; - matrices.matrix_b[i] = []; - for (let j = 0; j < c; j++) { - const a = document.querySelector( - `input[data-matrix="A"][data-row="${i}"][data-col="${j}"]` - ); - const b = document.querySelector( - `input[data-matrix="B"][data-row="${i}"][data-col="${j}"]` - ); - matrices.matrix_a[i][j] = parseFloat(a.value); - matrices.matrix_b[i][j] = parseFloat(b.value); - } - } - return matrices; -} - -function matrixToHTML(matrix, title) { - return ` -
-

${title}

- - - ${matrix - .map( - (row) => ` - - ${row - .map( - (val) => - `` - ) - .join("")} - - ` - ) - .join("")} - -
${parseFloat(val).toFixed( - 2 - )}
-
- `; -} - -function displayResult(data) { - elements.result.innerHTML = ` -
-

${data.operation}

-

Dimensões: ${data.dimensions}

-
- ${matrixToHTML(data.matrix_a, "Matriz A")} - ${matrixToHTML(data.matrix_b, "Matriz B")} - ${matrixToHTML(data.result, "Resultado")} -
-
- `; - elements.result.scrollIntoView({ behavior: "smooth", block: "nearest" }); -} - -function showError(msg) { - elements.result.innerHTML = `

${msg}

`; -} - -elements.rows.addEventListener("change", generateInputs); -elements.cols.addEventListener("change", generateInputs); - -elements.form.addEventListener("submit", async (e) => { - e.preventDefault(); - e.stopPropagation(); - - const payload = { - rows: parseInt(elements.rows.value), - cols: parseInt(elements.cols.value), - operation: elements.operation.value, - ...collectMatrixData(), - }; - - try { - const response = await fetch("/sum", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), - }); - - const data = await response.json(); - - response.ok ? displayResult(data) : showError(data.error); - } catch (error) { - console.error("Error:", error); - showError("Erro de comunicação"); - } -}); - -generateInputs(); diff --git a/static/js/menu-sidebar.js b/static/js/menu-sidebar.js new file mode 100644 index 0000000..de0c830 --- /dev/null +++ b/static/js/menu-sidebar.js @@ -0,0 +1,4 @@ +function toggleMenu() { + document.getElementById('overlay').classList.toggle('active'); + document.getElementById('sidebar').classList.toggle('active'); +} \ No newline at end of file diff --git a/static/js/multiply.js b/static/js/multiply.js new file mode 100644 index 0000000..874eaa0 --- /dev/null +++ b/static/js/multiply.js @@ -0,0 +1,155 @@ +const form = document.getElementById('matrix-form'); +const generateBtn = document.getElementById('generate-btn'); + +generateBtn.addEventListener('click', function() { + const rowsA = parseInt(document.getElementById('rows-a').value); + const colsA = parseInt(document.getElementById('cols-a').value); + const rowsB = parseInt(document.getElementById('rows-b').value); + const colsB = parseInt(document.getElementById('cols-b').value); + + clearMatrixInputs(); + hideError(); + hideResult(); + hideCalculateButton(); + + if(colsA !== rowsB) { + showError('Error: As Colunas de A tem de ter a mesma quantidade das Linhas de B ( col_a = row_b )'); + return; + } + generateMatrixInput(rowsA, colsA, 'matrix-inputs', 'Matriz A', 'matrix-a'); + generateMatrixInput(rowsB, colsB, 'matrix-inputs', 'Matriz B', 'matrix-b'); + showCalculateButton(); +}); + +form.addEventListener('submit', async function(e) { + e.preventDefault(); + hideError(); + hideResult(); + showCalculateButton(); + + const payload = { + rows_a: parseInt(document.getElementById('rows-a').value), + cols_a: parseInt(document.getElementById('cols-a').value), + rows_b: parseInt(document.getElementById('rows-b').value), + cols_b: parseInt(document.getElementById('cols-b').value), + matrix_a: readMatrixValues('matrix-a'), + matrix_b: readMatrixValues('matrix-b') + }; + + try { + const result = await apiCall('/multiply', payload); + displayMatrix(result.result, 'Resultado (A × B)'); + + const exportBtn = document.createElement('button'); + exportBtn.textContent = 'Exportar como JSON'; + exportBtn.className = 'btn-secondary'; + exportBtn.style.marginTop = '1rem'; + + exportBtn.onclick = () => exportMultiplyAsJSON( + payload.matrix_a, + payload.matrix_b, + result.result + ); + + document.getElementById('result').appendChild(exportBtn); + + const exportXMLBtn = document.createElement('button'); + exportXMLBtn.textContent = 'Exportar como XML'; + exportXMLBtn.className = 'btn-secondary'; + exportXMLBtn.style.marginTop = '0.5rem'; + + exportXMLBtn.onclick = () => exportMultiplyAsXML( + payload.matrix_a, + payload.matrix_b, + result.result +); + +document.getElementById('result').appendChild(exportXMLBtn); + + const exportHTMLBtn = document.createElement('button'); + exportHTMLBtn.textContent = 'Exportar como HTML'; + exportHTMLBtn.className = 'btn-secondary'; + exportHTMLBtn.style.marginTop = '0.5rem'; + + exportHTMLBtn.onclick = () => exportMultiplyAsHTML( + payload.matrix_a, + payload.matrix_b, + result.result +); + +document.getElementById('result').appendChild(exportHTMLBtn); + + } catch (error) { + showError(error.message); + } +}); + +generateBtn.click(); + +function exportMultiplyAsJSON(matrixA, matrixB, resultMatrix) { + const json = +`{ + "operation": "multiply", + "matrixA": ${prettyJson(matrixA, 4)}, + "matrixB": ${prettyJson(matrixB, 4)}, + "result": ${prettyJson(resultMatrix, 4)} +}`; + + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'multiplicacao_matrizes.json'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportMultiplyAsXML(matrixA, matrixB, resultMatrix) { + const xml = +` + +${prettyXML(matrixA, 'matrixA')} +${prettyXML(matrixB, 'matrixB')} +${prettyXML(resultMatrix, 'result')} +`; + + const blob = new Blob([xml], { type: 'application/xml' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'multiplicacao_matrizes.xml'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportMultiplyAsHTML(matrixA, matrixB, resultMatrix) { + const html = +` + + + + Multiplicação de Matrizes + + +

Operação: Multiplicação de Matrizes (A × B)

+ + ${prettyHTML(matrixA, 'Matriz A')} + ${prettyHTML(matrixB, 'Matriz B')} + ${prettyHTML(resultMatrix, 'Resultado')} + +`; + + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'multiplicacao_matrizes.html'; + a.click(); + + URL.revokeObjectURL(url); +} diff --git a/static/js/scalar.js b/static/js/scalar.js new file mode 100644 index 0000000..26bde4d --- /dev/null +++ b/static/js/scalar.js @@ -0,0 +1,144 @@ +const form = document.getElementById('matrix-form'); +const generateBtn = document.getElementById('generate-btn'); + +generateBtn.addEventListener('click', function() { + const rows = parseInt(document.getElementById('rows').value); + const cols = parseInt(document.getElementById('cols').value); + + clearMatrixInputs(); + hideError(); + hideResult(); + + generateMatrixInput(rows, cols, 'matrix-inputs', 'Matriz', 'matrix-a'); +}); + +form.addEventListener('submit', async function(e) { + e.preventDefault(); + hideError(); + hideResult(); + + const payload = { + rows: parseInt(document.getElementById('rows').value), + cols: parseInt(document.getElementById('cols').value), + matrix: readMatrixValues('matrix-a'), + scalar: parseFloat(document.getElementById('scalar').value) + }; + + try { + const result = await apiCall('/scalar', payload); + displayMatrix(result.result, `Resultado (${payload.scalar} × Matriz)`); + + const exportBtn = document.createElement('button'); + exportBtn.textContent = 'Exportar como JSON'; + exportBtn.className = 'btn-secondary'; + exportBtn.style.marginTop = '1rem'; + + exportBtn.onclick = () => exportScalarAsJSON( + payload.scalar, + payload.matrix, + result.result + ); + + document.getElementById('result').appendChild(exportBtn); + + const exportXMLBtn = document.createElement('button'); + exportXMLBtn.textContent = 'Exportar como XML'; + exportXMLBtn.className = 'btn-secondary'; + exportXMLBtn.style.marginTop = '0.5rem'; + + + exportXMLBtn.onclick = () => exportScalarAsXML( + payload.scalar, + payload.matrix, + result.result +); + +document.getElementById('result').appendChild(exportXMLBtn); + + const exportHTMLBtn = document.createElement('button'); + exportHTMLBtn.textContent = 'Exportar como HTML'; + exportHTMLBtn.className = 'btn-secondary'; + exportHTMLBtn.style.marginTop = '0.5rem'; + + exportHTMLBtn.onclick = () => exportScalarAsHTML( + payload.scalar, + payload.matrix, + result.result +); + +document.getElementById('result').appendChild(exportHTMLBtn); + + } catch (error) { + showError(error.message); + } +}); + +generateBtn.click(); + +function exportScalarAsJSON(scalar, matrix, resultMatrix) { + const json = +`{ + "operation": "scalar", + "scalar": ${scalar}, + "matrix": ${prettyJson(matrix, 4)}, + "result": ${prettyJson(resultMatrix, 4)} +}`; + + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'scalar_multiplication.json'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportScalarAsXML(scalar, matrix, resultMatrix) { + const xml = +` + + ${scalar} +${prettyXML(matrix, 'matrix')} +${prettyXML(resultMatrix, 'result')} +`; + + const blob = new Blob([xml], { type: 'application/xml' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'scalar_multiplication.xml'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportScalarAsHTML(scalar, matrix, resultMatrix) { + const html = +` + + + + Multiplicação por Escalar + + +

Operação: Multiplicação por Escalar

+

Escalar: ${scalar}

+ + ${prettyHTML(matrix, 'Matriz Original')} + ${prettyHTML(resultMatrix, 'Resultado')} + +`; + + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'scalar_multiplication.html'; + a.click(); + + URL.revokeObjectURL(url); +} diff --git a/static/js/sum-sub.js b/static/js/sum-sub.js new file mode 100644 index 0000000..9722fa8 --- /dev/null +++ b/static/js/sum-sub.js @@ -0,0 +1,149 @@ +const form = document.getElementById('matrix-form'); +const generateBtn = document.getElementById('generate-btn'); + +generateBtn.addEventListener('click', function() { + const rows = parseInt(document.getElementById('rows').value); + const cols = parseInt(document.getElementById('cols').value); + + clearMatrixInputs(); + hideError(); + hideResult(); + + generateMatrixInput(rows, cols, 'matrix-inputs', 'Matriz A', 'matrix-a'); + generateMatrixInput(rows, cols, 'matrix-inputs', 'Matriz B', 'matrix-b'); +}); + +form.addEventListener('submit', async function(e) { + e.preventDefault(); + hideError(); + hideResult(); + + const payload = { + rows: parseInt(document.getElementById('rows').value), + cols: parseInt(document.getElementById('cols').value), + matrix_a: readMatrixValues('matrix-a'), + matrix_b: readMatrixValues('matrix-b'), + operation: document.getElementById('operation').value + }; + + try { + const result = await apiCall('/sum-sub', payload); + const title = payload.operation === 'add' ? 'Resultado (A + B)' : 'Resultado (A - B)'; + displayMatrix(result.result, title); + + const exportBtn = document.createElement('button'); + exportBtn.textContent = 'Exportar como JSON'; + exportBtn.className = 'btn-secondary'; + exportBtn.style.marginTop = '1rem'; + + exportBtn.onclick = () => exportAsJSON( + payload.matrix_a, + payload.matrix_b, + result.result, + payload.operation + ); + + document.getElementById('result').appendChild(exportBtn); + + const exportXMLBtn = document.createElement('button'); + exportXMLBtn.textContent = 'Exportar como XML'; + exportXMLBtn.className = 'btn-secondary'; + exportXMLBtn.style.marginTop = '0.5rem'; + + exportXMLBtn.onclick = () => exportAsXML( + payload.matrix_a, + payload.matrix_b, + result.result, + payload.operation +); + +document.getElementById('result').appendChild(exportXMLBtn); + + const exportHTMLBtn = document.createElement('button'); + exportHTMLBtn.textContent = 'Exportar como HTML'; + exportHTMLBtn.className = 'btn-secondary'; + exportHTMLBtn.style.marginTop = '0.5rem'; + + exportHTMLBtn.onclick = () => exportAsHTML( + payload.matrix_a, + payload.matrix_b, + result.result, + payload.operation +); + +document.getElementById('result').appendChild(exportHTMLBtn); + + } catch (error) { + showError(error.message); + } +}); + +generateBtn.click(); + +function exportAsJSON(matrixA, matrixB, matrixResult, operation) { + const json = +`{ + "operation": "${operation}", + "matrixA": ${prettyJson(matrixA, 4)}, + "matrixB": ${prettyJson(matrixB, 4)}, + "result": ${prettyJson(matrixResult, 4)} +}`; + + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'matrizes.json'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportAsXML(matrixA, matrixB, matrixResult, operation) { + const xml = + ` + + ${prettyXML(matrixA, 'matrixA')} + ${prettyXML(matrixB, 'matrixB')} + ${prettyXML(matrixResult, 'result')} + `; + + const blob = new Blob([xml], { type: 'application/xml' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'matrizes.xml'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportAsHTML(matrixA, matrixB, matrixResult, operation) { + const html = +` + + + + Resultado da Operação + + +

Operação: ${operation}

+ + ${prettyHTML(matrixA, 'Matriz A')} + ${prettyHTML(matrixB, 'Matriz B')} + ${prettyHTML(matrixResult, 'Resultado')} + +`; + + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'matrizes.html'; + a.click(); + + URL.revokeObjectURL(url); +} \ No newline at end of file diff --git a/static/js/transpose.js b/static/js/transpose.js new file mode 100644 index 0000000..1f6aab7 --- /dev/null +++ b/static/js/transpose.js @@ -0,0 +1,138 @@ +const form = document.getElementById('matrix-form'); +const generateBtn = document.getElementById('generate-btn'); + +generateBtn.addEventListener('click', function() { + const rows = parseInt(document.getElementById('rows').value); + const cols = parseInt(document.getElementById('cols').value); + + clearMatrixInputs(); + hideError(); + hideResult(); + + generateMatrixInput(rows, cols, 'matrix-inputs', 'Matriz', 'matrix-a'); +}); + +form.addEventListener('submit', async function(e) { + e.preventDefault(); + hideError(); + hideResult(); + + const payload = { + rows: parseInt(document.getElementById('rows').value), + cols: parseInt(document.getElementById('cols').value), + matrix: readMatrixValues('matrix-a') + }; + + try { + const result = await apiCall('/transpose', payload); + displayMatrix(result.result, 'Matriz Transposta'); + + const exportBtn = document.createElement('button'); + exportBtn.textContent = 'Exportar como JSON'; + exportBtn.className = 'btn-secondary'; + exportBtn.style.marginTop = '1rem'; + + exportBtn.onclick = () => exportTransposeAsJSON( + payload.matrix, + result.result + ); + + document.getElementById('result').appendChild(exportBtn); + + const exportXMLBtn = document.createElement('button'); + exportXMLBtn.textContent = 'Exportar como XML'; + exportXMLBtn.className = 'btn-secondary'; + exportXMLBtn.style.marginTop = '0.5rem'; + + exportXMLBtn.onclick = () => exportTransposeAsXML( + payload.matrix, + result.result +); + +document.getElementById('result').appendChild(exportXMLBtn); + + const exportHTMLBtn = document.createElement('button'); + exportHTMLBtn.textContent = 'Exportar como HTML'; + exportHTMLBtn.className = 'btn-secondary'; + exportHTMLBtn.style.marginTop = '0.5rem'; + + exportHTMLBtn.onclick = () => exportTransposeAsHTML( + payload.matrix, + result.result +); + +document.getElementById('result').appendChild(exportHTMLBtn); + + + } catch (error) { + showError(error.message); + } +}); + +generateBtn.click(); + +function exportTransposeAsJSON(matrix, transposedMatrix) { + const json = +`{ + "operation": "transpose", + "matrix": ${prettyJson(matrix, 4)}, + "result": ${prettyJson(transposedMatrix, 4)} +}`; + + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'matriz_transposta.json'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportTransposeAsXML(matrix, transposedMatrix) { + const xml = +` + +${prettyXML(matrix, 'matrix')} +${prettyXML(transposedMatrix, 'result')} +`; + + const blob = new Blob([xml], { type: 'application/xml' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'matriz_transposta.xml'; + a.click(); + + URL.revokeObjectURL(url); +} + +function exportTransposeAsHTML(matrix, transposedMatrix) { + const html = +` + + + + Matriz Transposta + + +

Operação: Transposta

+ + ${prettyHTML(matrix, 'Matriz Original')} + ${prettyHTML(transposedMatrix, 'Matriz Transposta')} + + +`; + + const blob = new Blob([html], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'matriz_transposta.html'; + a.click(); + + URL.revokeObjectURL(url); +} \ No newline at end of file diff --git a/static/js/utils.js b/static/js/utils.js new file mode 100644 index 0000000..b075911 --- /dev/null +++ b/static/js/utils.js @@ -0,0 +1,87 @@ +async function apiCall(endpoint, data) { + const response = await fetch(endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.error || 'API ERROR!'); + } + + return result; +} + +function showError(message) { + const errorDiv = document.getElementById('error'); + if (errorDiv) { + errorDiv.textContent = message; + errorDiv.classList.add('show'); + } +} + +function hideCalculateButton(){ + const btn = document.getElementById('calculate-btn'); + if (btn) btn.style.display = 'none'; +} + +function showCalculateButton() { + const btn = document.getElementById('calculate-btn'); + if (btn) btn.style.display = 'block'; +} + +function hideError() { + document.getElementById('error')?.classList.remove('show'); +} + +function showResult() { + document.getElementById('result')?.classList.add('show'); +} + +function hideResult() { + document.getElementById('result')?.classList.remove('show'); +} + +function prettyJson(matrix, indent = 2) { + const space = ' '.repeat(indent); + return '[\n' + + matrix + .map(row => `${space}[${row.join(', ')}]`) + .join(',\n') + + '\n]'; +} + +function decimalMatrix(matrix, decimals = 2) { + return matrix.map(row => + row.map(num => Number(num.toFixed(decimals))) + ); +} + +function prettyXML(matrix, tagName) { + let xml =`<${tagName}>\n`; + + matrix.forEach(row => { + xml += ` ${row.join(', ')}\n`; + }); + + xml += ` \n`; + return xml; +} + +function prettyHTML(matrix, title) { + let html = `

${title}

`; + html += ''; + + matrix.forEach(row => { + html += ''; + row.forEach(value => { + html += ``; + }); + html += ''; + }); + + html += '
${value}
'; + return html; +} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html deleted file mode 100644 index 8b910ee..0000000 --- a/templates/base.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - {% block title %}Projeto Matrix{% endblock %} - - - -
-

{% block header_text %}Projeto Matrix{% endblock %}

-
-
- {% block content %}{% endblock %} -
- - - \ No newline at end of file diff --git a/templates/decrypt.html b/templates/decrypt.html new file mode 100644 index 0000000..d7d1b34 --- /dev/null +++ b/templates/decrypt.html @@ -0,0 +1,40 @@ + + + + + + Desencriptação - Calculadora de Matrizes + + + + + {% include 'header.html' %} + +
+ +
+

Desencriptação de Mensagens

+ +
+ + + + + + + + +
+ + +
+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/templates/determinant.html b/templates/determinant.html index 7c44820..e3ea8f6 100644 --- a/templates/determinant.html +++ b/templates/determinant.html @@ -1,315 +1,37 @@ -{% extends "base.html" %} - -{% block title %}Determinante{% endblock %} -{% block header_text %}Cálculo de Determinante{% endblock %} - -{% block content %} - ← Voltar + + + + + + Determinante - Calculadora de Matrizes + + + + + {% include 'header.html' %} + +
+ +
+

Cálculo de Determinante

-
-

Regras importantes:

- -
- -
-
-
- - -
- -
- -
- -
- - -
- - + + + + + + +
+ +
- - - - -{% endblock %} \ No newline at end of file +
+
+ + + + + + \ No newline at end of file diff --git a/templates/encrypt.html b/templates/encrypt.html index 5110e69..a784a54 100644 --- a/templates/encrypt.html +++ b/templates/encrypt.html @@ -1,585 +1,40 @@ -{% extends "base.html" %} - -{% block title %}Criptografia com Matrizes{% endblock %} -{% block header_text %}Criptografia de Mensagens usando Matrizes{% endblock %} - -{% block content %} - ← Voltar ao Menu - -
-

Como funciona:

-
    -
  1. Escolha uma matriz quadrada invertível (chave secreta)
  2. -
  3. Digite sua mensagem
  4. -
  5. Para criptografar: Mensagem → Números → Matriz × Chave = Mensagem Cifrada
  6. -
  7. Para descriptografar: Mensagem Cifrada × Chave⁻¹ = Mensagem Original
  8. -
-

Tabela de conversão: A=1, B=2, ..., Z=26, Espaço=27, .=28, Ú=29, Ã=30, Ç=31, Õ=32, É=33

-
+ + + + + + Encriptação - Calculadora de Matrizes + + + + + {% include 'header.html' %} + +
+ +
+

Encriptação de Mensagens

-
- -
-

Operação

-
- - -
-
- - -
-

Mensagem

- - -

-
- - -
-

Matriz Codificadora (Chave)

-
-
- - -
- - -
- -
- -
- -
-

Importante: A matriz deve ser quadrada e ter determinante ≠ 0

-

-
-
- - - - - + + + + + + + + + +
+ +
- - - - -{% endblock %} \ No newline at end of file +
+
+ + + + + + \ No newline at end of file diff --git a/templates/header.html b/templates/header.html new file mode 100644 index 0000000..4411895 --- /dev/null +++ b/templates/header.html @@ -0,0 +1,21 @@ + + + + +
+ + {% include 'sidebar.html' %} + +
+ +

Calculadora de Matrizes

+
+ + +
+ +
\ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 7258640..0f83b25 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,16 +1,133 @@ -{% extends "base.html" %} - -{% block title %}Menu Principal{% endblock %} -{% block header_text %}Projeto Matrix - Menu{% endblock %} - -{% block content %} -

Escolha uma Operação:

-
- Soma/Subtração de Matrizes - Multiplicação por Escalar - Multiplicação de Matrizes - Determinante - Matriz Inversa - Criptografia (Hill Cipher) -
-{% endblock %} \ No newline at end of file + + + + + + Calculadora de Matrizes + + + + + {% include 'header.html' %} + +
+ +
+

Seleciona uma Operação

+ +
+ + \ No newline at end of file diff --git a/templates/inverse.html b/templates/inverse.html index 8461658..a950b9e 100644 --- a/templates/inverse.html +++ b/templates/inverse.html @@ -1,316 +1,37 @@ -{% extends "base.html" %} - -{% block title %}Matriz Inversa{% endblock %} -{% block header_text %}Cálculo de Matriz Inversa{% endblock %} - -{% block content %} - ← Voltar + + + + + + Inversa - Calculadora de Matrizes + + + + + {% include 'header.html' %} + +
+ +
+

Matriz Inversa

-
-

Regras importantes:

- -
- -
-
-
- - -
- -
- -
- -
- - -
- - + + + + + + +
+ +
- - - - -{% endblock %} \ No newline at end of file +
+
+ + + + + + \ No newline at end of file diff --git a/templates/multiply.html b/templates/multiply.html index fb7f740..885bf33 100644 --- a/templates/multiply.html +++ b/templates/multiply.html @@ -1,41 +1,56 @@ -{% extends "base.html" %} + + + + + + Multiplicação - Calculadora de Matrizes + + + + + {% include 'header.html' %} + +
-{% block title %}Multiplicação{% endblock %} -{% block header_text %}Multiplicação de Matrizes{% endblock %} - -{% block content %} - ← Voltar - -
-
-
-

Matriz A

- - - - -
-
-

Matriz B

- - - - -
-
- - -

- -

- Nota: Para multiplicação, o número de colunas de A deve ser igual ao número de linhas de B. -

- -
- +
+

Multiplicação de Matrizes

+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + + +
+ + - -
- -{% endblock %} +
+
+
+ + + + + + \ No newline at end of file diff --git a/templates/scalar.html b/templates/scalar.html index be8fa6b..c459313 100644 --- a/templates/scalar.html +++ b/templates/scalar.html @@ -1,29 +1,48 @@ -{% extends "base.html" %} + + + + + + Escalar - Calculadora de Matrizes + + + + + {% include 'header.html' %} + +
-{% block title %}Multiplicação por Escalar{% endblock %} -{% block header_text %}Multiplicação por Escalar de Matrizes{% endblock %} - -{% block content %} - ← Voltar -
-
-
- - -
-
- - -
+
+

Multiplicação por Escalar

+ + +
+
+ + +
+
+ +
- -
-

- -
- +
+ + + +
+ + + + + -

← Voltar ao Menu

+
- -{% endblock %} \ No newline at end of file +
+
+ + + + + + \ No newline at end of file diff --git a/templates/sidebar.html b/templates/sidebar.html new file mode 100644 index 0000000..6062034 --- /dev/null +++ b/templates/sidebar.html @@ -0,0 +1,96 @@ + + + + + \ No newline at end of file diff --git a/templates/sum_sub.html b/templates/sum_sub.html index d9a53db..6e6288e 100644 --- a/templates/sum_sub.html +++ b/templates/sum_sub.html @@ -1,34 +1,51 @@ -{% extends "base.html" %} + + + + + + Soma & Subtração - Calculadora de Matrizes + + + + + {% include 'header.html' %} + +
-{% block title %}Soma/Subtração{% endblock %} -{% block header_text %}Soma e Subtração de Matrizes{% endblock %} - -{% block content %} - ← Voltar - -
-
-
- - -
-
- - -
+
+

Soma & Subtração de Matrizes

+ + +
+
+ +
- - - - -
- +
+ + +
+
+ + + +
+ + + + + - -
- -{% endblock %} \ No newline at end of file +
+
+
+ + + + + + \ No newline at end of file diff --git a/templates/transpose.html b/templates/transpose.html new file mode 100644 index 0000000..28c61f2 --- /dev/null +++ b/templates/transpose.html @@ -0,0 +1,45 @@ + + + + + + Transposta - Calculadora de Matrizes + + + + + {% include 'header.html' %} + +
+ +
+

Matriz Transposta

+ +
+
+
+ + +
+
+ + +
+
+ + + +
+ + +
+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/tests/test_matrix.py b/tests/test_matrix.py index 43e27cb..312f08b 100644 --- a/tests/test_matrix.py +++ b/tests/test_matrix.py @@ -2,523 +2,219 @@ from core.matrix import Matrix -class TestMatrixCreation(unittest.TestCase): - """Test matrix initialization and basic operations.""" +class TestMatrixBasics(unittest.TestCase): - def test_create_matrix_with_data(self): - """Test creating a matrix with initial data.""" - data = [[1, 2], [3, 4]] - m = Matrix(2, 2, data) + def test_init_empty_matrix(self): + m = Matrix(2, 3) self.assertEqual(m.rows, 2) - self.assertEqual(m.cols, 2) - self.assertEqual(m.data, data) - print(f"\n[CREATE] Matrix created: {m.to_list()}") + self.assertEqual(m.cols, 3) + self.assertEqual(m.to_list(), [[0, 0, 0], [0, 0, 0]]) - def test_create_matrix_zeros(self): - """Test creating a matrix initialized with zeros.""" - m = Matrix(3, 3) - expected = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] - self.assertEqual(m.data, expected) - print(f"\n[CREATE ZEROS] 3x3 zero matrix: {m.to_list()}") + def test_init_with_data(self): + data = [[1, 2], [3, 4]] + m = Matrix(2, 2, data) + self.assertEqual(m.to_list(), [[1, 2], [3, 4]]) - def test_invalid_dimensions(self): - """Test that wrong data dimensions raise error.""" + def test_init_wrong_dimensions(self): with self.assertRaises(ValueError): Matrix(2, 2, [[1, 2, 3], [4, 5, 6]]) - print(f"\n[CREATE ERROR] Invalid dimensions correctly raised ValueError") - - -class TestMatrixElements(unittest.TestCase): - """Test getting and setting matrix elements.""" - - def setUp(self): - """Create a test matrix before each test.""" - self.matrix = Matrix(2, 2, [[1, 2], [3, 4]]) - - def test_get_element(self): - """Test getting elements from matrix (1-indexed).""" - self.assertEqual(self.matrix.get_element(1, 1), 1) - self.assertEqual(self.matrix.get_element(1, 2), 2) - self.assertEqual(self.matrix.get_element(2, 1), 3) - self.assertEqual(self.matrix.get_element(2, 2), 4) - print(f"\n[GET ELEMENT] Matrix: {self.matrix.to_list()}") - print(f" (1,1)={self.matrix.get_element(1,1)}, (1,2)={self.matrix.get_element(1,2)}") - print(f" (2,1)={self.matrix.get_element(2,1)}, (2,2)={self.matrix.get_element(2,2)}") - def test_set_element(self): - """Test setting elements in matrix.""" - print(f"\n[SET ELEMENT] Before: {self.matrix.to_list()}") - self.matrix.set_element(1, 1, 10) - self.assertEqual(self.matrix.get_element(1, 1), 10) - print(f" After setting (1,1)=10: {self.matrix.to_list()}") + def test_is_square(self): + m1 = Matrix(2, 2, [[1, 2], [3, 4]]) + m2 = Matrix(2, 3, [[1, 2, 3], [4, 5, 6]]) + self.assertTrue(m1.is_square()) + self.assertFalse(m2.is_square()) def test_dimensions(self): - """Test getting matrix dimensions.""" - dims = self.matrix.dimensions() - self.assertEqual(dims, (2, 2)) - print(f"\n[DIMENSIONS] Matrix dimensions: {dims}") - - def test_to_list(self): - """Test converting matrix to list.""" - result = self.matrix.to_list() - expected = [[1, 2], [3, 4]] - self.assertEqual(result, expected) - print(f"\n[TO LIST] Matrix as list: {result}") - - def test_is_square(self): - """Test checking if matrix is square.""" - self.assertTrue(self.matrix.is_square()) - non_square = Matrix(2, 3) - self.assertFalse(non_square.is_square()) - print(f"\n[IS SQUARE] 2x2 is square: True, 2x3 is square: False") + m = Matrix(3, 4) + self.assertEqual(m.dimensions(), (3, 4)) class TestMatrixAddition(unittest.TestCase): - """Test matrix addition.""" - def test_add_2x2_matrices(self): - """Test adding two 2×2 matrices.""" - A = Matrix(2, 2, [[1, 2], - [3, 4]]) - - B = Matrix(2, 2, [[5, 6], - [7, 8]]) - + def test_add_2x2(self): + A = Matrix(2, 2, [[1, 2], [3, 4]]) + B = Matrix(2, 2, [[5, 6], [7, 8]]) result = A.add(B) - expected = [[6, 8], [10, 12]] - self.assertEqual(result['data'], expected) - print(f"\n[ADD] A + B:") - print(f" A = {A.to_list()}") - print(f" B = {B.to_list()}") - print(f" Result = {result['data']}") - - def test_add_with_negatives(self): - """Test addition with negative numbers.""" - A = Matrix(2, 2, [[1, -2], - [-3, 4]]) - - B = Matrix(2, 2, [[-1, 2], - [3, -4]]) + self.assertEqual(result.to_list(), [[6, 8], [10, 12]]) + + def test_add_3x3(self): + A = Matrix(3, 3, [[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + B = Matrix(3, 3, [[9, 8, 7], [6, 5, 4], [3, 2, 1]]) result = A.add(B) - - expected = [[0, 0], - [0, 0]] - self.assertEqual(result['data'], expected) - print(f"\n[ADD NEGATIVES] A + B:") - print(f" A = {A.to_list()}") - print(f" B = {B.to_list()}") - print(f" Result = {result['data']}") - + self.assertEqual(result.to_list(), [[10, 10, 10], [10, 10, 10], [10, 10, 10]]) + def test_add_incompatible_dimensions(self): - """Test that adding incompatible matrices raises error.""" - A = Matrix(2, 2, [[1, 2], - [3, 4]]) - - B = Matrix(3, 3, [[1, 2, 3], - [4, 5, 6], - [7, 8, 9]]) - + A = Matrix(2, 2, [[1, 2], [3, 4]]) + B = Matrix(2, 3, [[1, 2, 3], [4, 5, 6]]) with self.assertRaises(ValueError): A.add(B) - print(f"\n[ADD ERROR] A(2x2) + B(3x3): ValueError raised correctly") + + def test_add_wrong_type(self): + A = Matrix(2, 2, [[1, 2], [3, 4]]) + with self.assertRaises(TypeError): + A.add([[1, 2], [3, 4]]) class TestMatrixSubtraction(unittest.TestCase): - """Test matrix subtraction.""" - def test_subtract_2x2_matrices(self): - """Test subtracting two 2×2 matrices.""" - A = Matrix(2, 2, [[5, 6], - [7, 8]]) - - B = Matrix(2, 2, [[1, 2], - [3, 4]]) + def test_subtract_2x2(self): + A = Matrix(2, 2, [[5, 6], [7, 8]]) + B = Matrix(2, 2, [[1, 2], [3, 4]]) result = A.subtract(B) - expected = [[4, 4], [4, 4]] - self.assertEqual(result['data'], expected) - print(f"\n[SUBTRACT] A - B:") - print(f" A = {A.to_list()}") - print(f" B = {B.to_list()}") - print(f" Result = {result['data']}") - - def test_subtract_same_matrix(self): - """Test subtracting a matrix from itself gives zeros.""" - A = Matrix(2, 2, [[1, 2], [3, 4]]) - result = A.subtract(A) - expected = [[0, 0], [0, 0]] - self.assertEqual(result['data'], expected) - print(f"\n[SUBTRACT SELF] A - A:") - print(f" A = {A.to_list()}") - print(f" Result = {result['data']} (zeros)") + self.assertEqual(result.to_list(), [[4, 4], [4, 4]]) def test_subtract_incompatible_dimensions(self): - """Test that subtracting incompatible matrices raises error.""" - A = Matrix(2, 2, [[1, 2], - [3, 4]]) - - B = Matrix(3, 3, [[1, 2, 3], - [4, 5, 6], - [7, 8, 9]]) - + A = Matrix(2, 2, [[1, 2], [3, 4]]) + B = Matrix(3, 2, [[1, 2], [3, 4], [5, 6]]) with self.assertRaises(ValueError): A.subtract(B) - print(f"\n[SUBTRACT ERROR] A(2x2) - B(3x3): ValueError raised correctly") -class TestScalarMultiplication(unittest.TestCase): - """Test scalar multiplication.""" +class TestMatrixScalarMultiply(unittest.TestCase): - def test_scalar_multiply_by_2(self): - """Test multiplying matrix by scalar 2.""" - A = Matrix(2, 2, [[1, 2], - [3, 4]]) - result = A.scalar_multiply(2) - expected = [[2, 4], - [6, 8]] - self.assertEqual(result['data'], expected) - print(f"\n[SCALAR x2] A * 2:") - print(f" A = {A.to_list()}") - print(f" Result = {result['data']}") - - def test_scalar_multiply_by_0(self): - """Test multiplying matrix by 0.""" - A = Matrix(2, 2, [[1, 2], - [3, 4]]) - result = A.scalar_multiply(0) - expected = [[0, 0], - [0, 0]] - self.assertEqual(result['data'], expected) - print(f"\n[SCALAR x0] A * 0:") - print(f" A = {A.to_list()}") - print(f" Result = {result['data']} (zeros)") + def test_scalar_multiply_2x2(self): + A = Matrix(2, 2, [[1, 2], [3, 4]]) + result = A.scalar_multiply(3) + self.assertEqual(result.to_list(), [[3, 6], [9, 12]]) - def test_scalar_multiply_by_negative(self): - """Test multiplying matrix by negative scalar.""" - A = Matrix(2, 2, [[1, 2], - [3, 4]]) - result = A.scalar_multiply(-1) - expected = [[-1, -2], - [-3, -4]] - self.assertEqual(result['data'], expected) - print(f"\n[SCALAR x-1] A * -1:") - print(f" A = {A.to_list()}") - print(f" Result = {result['data']}") + def test_scalar_multiply_zero(self): + A = Matrix(2, 2, [[1, 2], [3, 4]]) + result = A.scalar_multiply(0) + self.assertEqual(result.to_list(), [[0, 0], [0, 0]]) class TestMatrixMultiplication(unittest.TestCase): - """Test matrix multiplication.""" - def test_multiply_2x2_matrices(self): - """Test multiplying two 2×2 matrices.""" - A = Matrix(2, 2, [[1, 2], - [3, 4]]) - B = Matrix(2, 2, [[5, 6], - [7, 8]]) - C = A.multiply(B) - expected = [[19, 22], [43, 50]] - self.assertEqual(C.to_list(), expected) - print(f"\n[MULTIPLY 2x2] A * B:") - print(f" A = {A.to_list()}") - print(f" B = {B.to_list()}") - print(f" C = {C.to_list()}") + def test_multiply_2x2(self): + A = Matrix(2, 2, [[1, 2], [3, 4]]) + B = Matrix(2, 2, [[5, 6], [7, 8]]) + result = A.multiply(B) + self.assertEqual(result.to_list(), [[19, 22], [43, 50]]) - def test_multiply_2x3_by_3x2(self): - """Test multiplying 2×3 by 3×2 matrices.""" - A = Matrix(2, 3, [[1, 2, 3], - [4, 5, 6]]) - B = Matrix(3, 2, [[7, 8], - [9, 10], - [11, 12]]) - C = A.multiply(B) - self.assertEqual(C.dimensions(), (2, 2)) - print(f"\n[MULTIPLY 2x3*3x2] A * B:") - print(f" A(2x3) = {A.to_list()}") - print(f" B(3x2) = {B.to_list()}") - print(f" C(2x2) = {C.to_list()}") + def test_multiply_2x3_3x2(self): + A = Matrix(2, 3, [[1, 2, 3], [4, 5, 6]]) + B = Matrix(3, 2, [[7, 8], [9, 10], [11, 12]]) + result = A.multiply(B) + self.assertEqual(result.to_list(), [[58, 64], [139, 154]]) def test_multiply_incompatible_dimensions(self): - """Test that incompatible matrices raise error.""" - A = Matrix(2, 2, [[1, 2], - [3, 4]]) - B = Matrix(3, 3, [[1, 2, 3], - [4, 5, 6], - [7, 8, 9]]) + A = Matrix(2, 3, [[1, 2, 3], [4, 5, 6]]) + B = Matrix(2, 2, [[1, 2], [3, 4]]) with self.assertRaises(ValueError): A.multiply(B) - print(f"\n[MULTIPLY ERROR] A(2x2) * B(3x3): ValueError raised correctly") -class TestDeterminant(unittest.TestCase): - """Test determinant calculation.""" +class TestMatrixTranspose(unittest.TestCase): + + def test_transpose_2x3(self): + A = Matrix(2, 3, [[1, 2, 3], [4, 5, 6]]) + result = A.transpose() + self.assertEqual(result.to_list(), [[1, 4], [2, 5], [3, 6]]) + + def test_transpose_square(self): + A = Matrix(2, 2, [[1, 2], [3, 4]]) + result = A.transpose() + self.assertEqual(result.to_list(), [[1, 3], [2, 4]]) + + +class TestMatrixDeterminant(unittest.TestCase): def test_determinant_1x1(self): - """Test determinant of 1×1 matrix.""" A = Matrix(1, 1, [[5]]) - det = A.determinant() - self.assertEqual(det, 5) - print(f"\n[DET 1x1] det(A) = {det}") - print(f" A = {A.to_list()}") + self.assertEqual(A.determinant(), 5) def test_determinant_2x2(self): - """Test determinant of 2×2 matrix.""" - A = Matrix(2, 2, [[1, 2], - [3, 4]]) - det = A.determinant() - self.assertEqual(det, -2) - print(f"\n[DET 2x2] det(A) = {det}") - print(f" A = {A.to_list()}") - print(f" Formula: (1*4) - (2*3) = 4 - 6 = -2") + A = Matrix(2, 2, [[1, 2], [3, 4]]) + self.assertEqual(A.determinant(), -2) def test_determinant_3x3(self): - """Test determinant of 3×3 matrix.""" - A = Matrix(3, 3, [[1, 2, 3], - [0, 1, 4], - [5, 6, 0]]) - det = A.determinant() - self.assertEqual(det, 1) - print(f"\n[DET 3x3] det(A) = {det}") - print(f" A = {A.to_list()}") - - def test_determinant_identity_2x2(self): - """Test determinant of 2×2 identity matrix.""" - A = Matrix(2, 2, [[1, 0], - [0, 1]]) - det = A.determinant() - self.assertEqual(det, 1) - print(f"\n[DET IDENTITY] det(I) = {det}") - print(f" I = {A.to_list()}") - - def test_determinant_zero_determinant(self): - """Test determinant of singular matrix.""" - A = Matrix(2, 2, [[1, 2], - [2, 4]]) - det = A.determinant() - self.assertEqual(det, 0) - print(f"\n[DET SINGULAR] det(A) = {det}") - print(f" A = {A.to_list()} (singular matrix)") - - def test_determinant_non_square_matrix(self): - """Test that non-square matrix raises error.""" - A = Matrix(2, 3, [[1, 2, 3], - [4, 5, 6]]) + A = Matrix(3, 3, [[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + self.assertEqual(A.determinant(), 0) + + def test_determinant_3x3_nonzero(self): + A = Matrix(3, 3, [[6, 1, 1], [4, -2, 5], [2, 8, 7]]) + self.assertEqual(A.determinant(), -306) + + def test_determinant_4x4(self): + A = Matrix(4, 4, [ + [1, 0, 2, -1], + [3, 0, 0, 5], + [2, 1, 4, -3], + [1, 0, 5, 0] + ]) + self.assertEqual(A.determinant(), 30) + + def test_determinant_non_square(self): + A = Matrix(2, 3, [[1, 2, 3], [4, 5, 6]]) with self.assertRaises(ValueError): A.determinant() - print(f"\n[DET ERROR] det of non-square A(2x3): ValueError raised correctly") - - -class TestMatrixTranspose(unittest.TestCase): - """Test matrix transpose.""" - - def test_transpose_2x2(self): - """Test transpose of 2×2 matrix.""" - A = Matrix(2, 2, [[1, 2], - [3, 4]]) - A_T = A.transpose() - expected = [[1, 3], - [2, 4]] - self.assertEqual(A_T.to_list(), expected) - print(f"\n[TRANSPOSE 2x2] A^T:") - print(f" A = {A.to_list()}") - print(f" A^T = {A_T.to_list()}") - - def test_transpose_2x3(self): - """Test transpose of 2×3 matrix.""" - A = Matrix(2, 3, [[1, 2, 3], - [4, 5, 6]]) - A_T = A.transpose() - self.assertEqual(A_T.dimensions(), (3, 2)) - expected = [[1, 4], - [2, 5], - [3, 6]] - self.assertEqual(A_T.to_list(), expected) - print(f"\n[TRANSPOSE 2x3] A^T:") - print(f" A(2x3) = {A.to_list()}") - print(f" A^T(3x2) = {A_T.to_list()}") class TestMatrixInverse(unittest.TestCase): - """Test matrix inverse calculation.""" def test_inverse_2x2(self): - """Test inverse of 2×2 matrix.""" - A = Matrix(2, 2, [[4, 7], - [2, 6]]) - A_inv = A.inverse() - I = A.multiply(A_inv) - identity = [[1, 0], - [0, 1]] + A = Matrix(2, 2, [[4, 7], [2, 6]]) + inv = A.inverse() + expected = [[0.6, -0.7], [-0.2, 0.4]] + result = inv.to_list() for i in range(2): for j in range(2): - self.assertAlmostEqual(I.to_list()[i][j], identity[i][j], places=5) - print(f"\n[INVERSE 2x2] A^-1:") - print(f" A = {A.to_list()}") - print(f" A^-1 = {A_inv.to_list()}") - print(f" A * A^-1 = {I.to_list()} (identity)") + self.assertAlmostEqual(result[i][j], expected[i][j], places=5) + + def test_inverse_3x3(self): + A = Matrix(3, 3, [[1, 2, 3], [0, 1, 4], [5, 6, 0]]) + inv = A.inverse() + identity = A.multiply(inv) + result = identity.to_list() + for i in range(3): + for j in range(3): + expected = 1 if i == j else 0 + self.assertAlmostEqual(result[i][j], expected, places=5) def test_inverse_singular_matrix(self): - """Test that singular matrix raises error.""" - A = Matrix(2, 2, [[1, 2], - [2, 4]]) + A = Matrix(2, 2, [[1, 2], [2, 4]]) with self.assertRaises(ValueError): A.inverse() - print(f"\n[INVERSE ERROR] Singular matrix A:") - print(f" A = {A.to_list()}") - print(f" det(A) = 0, ValueError raised correctly") def test_inverse_non_square(self): - """Test that non-square matrix raises error.""" - A = Matrix(2, 3, [[1, 2, 3], - [4, 5, 6]]) + A = Matrix(2, 3, [[1, 2, 3], [4, 5, 6]]) with self.assertRaises(ValueError): A.inverse() - print(f"\n[INVERSE ERROR] Non-square A(2x3): ValueError raised correctly") - - -class TestMatrixIdentity(unittest.TestCase): - """Test identity matrix creation.""" - - def test_identity_3x3(self): - """Test creating 3×3 identity matrix.""" - A = Matrix(3, 3) - I = A.identity(3) - expected = [[1, 0, 0], - [0, 1, 0], - [0, 0, 1]] - self.assertEqual(I.to_list(), expected) - print(f"\n[IDENTITY 3x3] I = {I.to_list()}") - - -class TestMatrixDimensions(unittest.TestCase): - """Test dimension comparison.""" - - def test_same_dimensions(self): - """Test comparing matrices with same dimensions.""" - A = Matrix(2, 3) - B = Matrix(2, 3) - self.assertTrue(A.is_same_dimension(B)) - print(f"\n[DIMENSIONS] A(2x3) == B(2x3): True") - - def test_different_rows(self): - """Test comparing matrices with different rows.""" - A = Matrix(2, 3) - B = Matrix(3, 3) - self.assertFalse(A.is_same_dimension(B)) - print(f"\n[DIMENSIONS] A(2x3) == B(3x3): False") - - def test_different_columns(self): - """Test comparing matrices with different columns.""" - A = Matrix(2, 3) - B = Matrix(2, 4) - self.assertFalse(A.is_same_dimension(B)) - print(f"\n[DIMENSIONS] A(2x3) == B(2x4): False") class TestMatrixEncryption(unittest.TestCase): - """Test matrix encryption and decryption.""" - - def setUp(self): - """Set up encoding and decoding matrices.""" - self.encoding_matrix = Matrix(2, 2, [[5, 7], [2, 3]]) - self.decoding_matrix = Matrix(2, 2, [[3, -7], [-2, 5]]) - def test_encrypt_decrypt_example_message(self): - """Test encrypting and decrypting the example message.""" - message = "OS NÚMEROS GOVERNAM O MUNDO." + def test_encrypt_decrypt_example(self): + encoding_matrix = Matrix(2, 2, [[5, 7], [2, 3]]) + message = "OS NUMEROS GOVERNAM O MUNDO." - result = self.encoding_matrix.encrypt_message(message, self.encoding_matrix) - encrypted = result['encrypted_matrix'] + encrypted = encoding_matrix.encrypt_message(message) - decrypted_result = self.encoding_matrix.decrypt_message(encrypted, self.decoding_matrix) - decrypted_message = decrypted_result['decrypted_message'] + self.assertIn('message_matrix', encrypted) + self.assertIn('encrypted_matrix', encrypted) + self.assertIn('numeric_sequence', encrypted) - self.assertEqual(decrypted_message, message) - print(f"\n[ENCRYPT/DECRYPT FULL] Message encryption cycle:") - print(f" Original: '{message}'") - print(f" Numeric sequence: {result['numeric_sequence']}") - print(f" Message matrix: {result['message_matrix'].to_list()}") - print(f" Encrypted matrix: {encrypted.to_list()}") - print(f" Decrypted: '{decrypted_message}'") - print(f" Match: {message == decrypted_message}") - - def test_encrypt_message_structure(self): - """Test that encryption returns expected structure.""" - message = "HELLO" - result = self.encoding_matrix.encrypt_message(message, self.encoding_matrix) - - self.assertIn('encrypted_matrix', result) - self.assertIn('message_matrix', result) - self.assertIn('numeric_sequence', result) - self.assertIn('original_message', result) - print(f"\n[ENCRYPT STRUCTURE] Message: '{message}'") - print(f" Keys returned: {list(result.keys())}") - - def test_encrypt_short_message(self): - """Test encrypting a short message.""" - message = "HI" - result = self.encoding_matrix.encrypt_message(message, self.encoding_matrix) + decrypted = encoding_matrix.decrypt_message(encrypted['encrypted_matrix']) - self.assertIsInstance(result['encrypted_matrix'], Matrix) - print(f"\n[ENCRYPT SHORT] Message: '{message}'") - print(f" Numeric: {result['numeric_sequence']}") - print(f" Message matrix: {result['message_matrix'].to_list()}") - print(f" Encrypted: {result['encrypted_matrix'].to_list()}") - - def test_encrypt_with_padding(self): - """Test that odd-length messages get padded correctly.""" - message = "HELLO" - result = self.encoding_matrix.encrypt_message(message, self.encoding_matrix) - - self.assertEqual(len(result['numeric_sequence']), 6) - print(f"\n[ENCRYPT PADDING] Message: '{message}' (5 chars)") - print(f" Padded to: {len(result['numeric_sequence'])} numbers") - print(f" Numeric sequence: {result['numeric_sequence']}") - - def test_decrypt_returns_original(self): - """Test that decrypt returns original message.""" - message = "TEST MESSAGE" - - encrypted = self.encoding_matrix.encrypt_message(message, self.encoding_matrix) - decrypted = self.encoding_matrix.decrypt_message( - encrypted['encrypted_matrix'], - self.decoding_matrix - ) - - self.assertEqual(decrypted['decrypted_message'], message) - print(f"\n[DECRYPT] Message: '{message}'") - print(f" Encrypted matrix: {encrypted['encrypted_matrix'].to_list()}") - print(f" Decrypted: '{decrypted['decrypted_message']}'") - print(f" Match: {message == decrypted['decrypted_message']}") - - def test_encryption_with_special_chars(self): - """Test encryption with special characters.""" - message = "HELLO WORLD." - - result = self.encoding_matrix.encrypt_message(message, self.encoding_matrix) - decrypted = self.encoding_matrix.decrypt_message( - result['encrypted_matrix'], - self.decoding_matrix - ) - - self.assertEqual(decrypted['decrypted_message'], message) - print(f"\n[ENCRYPT SPECIAL] Message: '{message}'") - print(f" Contains: space and period") - print(f" Numeric: {result['numeric_sequence']}") - print(f" Decrypted: '{decrypted['decrypted_message']}'") - print(f" Match: {message == decrypted['decrypted_message']}") - - def test_verify_encoding_decoding_matrices(self): - """Test that encoding and decoding matrices are inverses.""" - identity = self.encoding_matrix.multiply(self.decoding_matrix) + original_upper = message.upper() + decrypted_text = decrypted['decrypted_message'] + decrypted_text = decrypted_text.rstrip(' ') + original_upper = original_upper.rstrip(' ') - self.assertAlmostEqual(identity.get_element(1, 1), 1, places=5) - self.assertAlmostEqual(identity.get_element(1, 2), 0, places=5) - self.assertAlmostEqual(identity.get_element(2, 1), 0, places=5) - self.assertAlmostEqual(identity.get_element(2, 2), 1, places=5) - print(f"\n[VERIFY INVERSE] A * A^-1:") - print(f" Encoding: {self.encoding_matrix.to_list()}") - print(f" Decoding: {self.decoding_matrix.to_list()}") - print(f" Product: {identity.to_list()}") - print(f" Is identity: True") + self.assertEqual(decrypted_text, original_upper) + + def test_char_to_num(self): + self.assertEqual(Matrix.char_to_num('A'), 1) + self.assertEqual(Matrix.char_to_num('Z'), 26) + self.assertEqual(Matrix.char_to_num('.'), 27) + self.assertEqual(Matrix.char_to_num(' '), 29) + + def test_num_to_char(self): + self.assertEqual(Matrix.num_to_char(1), 'A') + self.assertEqual(Matrix.num_to_char(26), 'Z') + self.assertEqual(Matrix.num_to_char(27), '.') + self.assertEqual(Matrix.num_to_char(29), ' ') if __name__ == '__main__': diff --git a/tests/test_routes.py b/tests/test_routes.py new file mode 100644 index 0000000..4dd4183 --- /dev/null +++ b/tests/test_routes.py @@ -0,0 +1,91 @@ +import pytest +from app import app + +@pytest.fixture +def client(): + """Create test client for Flask app""" + app.config['TESTING'] = True + with app.test_client() as client: + yield client + + +def test_sum_sub_get_route(client): + """Test GET request returns HTML page""" + response = client.get('/sum-sub') + assert response.status_code == 200 + assert b'' in response.data + + +def test_add_matrices(client): + """Test addition of two 2x2 matrices""" + payload = { + 'rows': 2, + 'cols': 2, + 'matrix_a': [[1, 2], [3, 4]], + 'matrix_b': [[5, 6], [7, 8]], + 'operation': 'add' + } + + + response = client.post('/sum-sub', json=payload, content_type='application/json') + + data = response.get_json() + + assert response.status_code == 200 + assert 'result' in data + assert data['result'] == [[6, 8], [10, 12]] + + +def test_subtract_matrices(client): + """Test subtraction of two 2x2 matrices""" + payload = { + 'rows': 2, + 'cols': 2, + 'matrix_a': [[5, 6], [7, 8]], + 'matrix_b': [[1, 2], [3, 4]], + 'operation': 'subtract' + } + + response = client.post('/sum-sub', json=payload, content_type='application/json') + + data = response.get_json() + + assert response.status_code == 200 + assert 'result' in data + assert data['result'] == [[4, 4], [4, 4]] + + +def test_invalid_operation(client): + """Test invalid operation returns error""" + payload = { + 'rows': 2, + 'cols': 2, + 'matrix_a': [[1, 2], [3, 4]], + 'matrix_b': [[5, 6], [7, 8]], + 'operation': 'invalid' + } + + response = client.post('/sum-sub', json=payload, content_type='application/json') + + data = response.get_json() + + assert response.status_code == 400 + assert 'error' in data + + +def test_add_3x3_matrices(client): + """Test addition of two 3x3 matrices""" + payload = { + 'rows': 3, + 'cols': 3, + 'matrix_a': [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + 'matrix_b': [[9, 8, 7], [6, 5, 4], [3, 2, 1]], + 'operation': 'add' + } + + response = client.post('/sum-sub', json=payload, content_type='application/json') + + data = response.get_json() + + assert response.status_code == 200 + assert data['result'] == [[10, 10, 10], [10, 10, 10], [10, 10, 10]] \ No newline at end of file