Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 101 additions & 11 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,106 @@
# Matrix

## How to use
- Clone the project using ```git clone https://github.com/tskxz/matrix```
- install packages using ```pip install -r requirements.txt``` or using these following commands
```
python3 -m venv venv
source ./venv/bin/activate
pip install -r ./requirements.txt
Matrix is a web-based matrix calculator built with Flask. It provides a user-friendly interface to perform a wide range of matrix operations, from basic arithmetic to advanced applications like message encryption and decryption.

## Features

- **Basic Arithmetic:** Addition, subtraction, and multiplication of matrices.
- **Scalar Operations:** Multiply a matrix by a scalar value.
- **Advanced Calculations:**
- Matrix Transpose
- Determinant (for square matrices of any size via Laplace expansion)
- Matrix Inverse (using the adjugate method)
- **Cryptography:**
- Encrypt text messages using an invertible encoding matrix (a form of Hill Cipher).
- Decrypt messages using the inverse of the encoding matrix.
- **Dynamic UI:** The interface dynamically generates input fields based on user-specified matrix dimensions.
- **Data Export:** Export the results of operations (including the input matrices) to JSON, XML, or HTML formats.
- **Error Handling:** Validates matrix dimensions and mathematical conditions (e.g., non-singular matrices for inversion) and provides clear error messages.

## Technology Stack

- **Backend:** Python, Flask
- **Frontend:** HTML, CSS, JavaScript (Vanilla)
- **Math Engine:** Custom `Matrix` class with core logic in Python.
- **Testing:** Pytest
- **CI/CD:** GitHub Actions

## Installation and Usage

To get a local copy up and running, follow these steps.

1. **Clone the repository:**
```sh
git clone https://github.com/tskxz/matrix.git
cd matrix
```

2. **Create and activate a virtual environment:**
- On macOS/Linux:
```sh
python3 -m venv venv
source venv/bin/activate
```
- On Windows:
```sh
python -m venv venv
.\venv\Scripts\activate
```

3. **Install dependencies:**
```sh
pip install -r requirements.txt
```

4. **Run the application:**
```sh
python app.py
```

5. Open your browser and navigate to `http://127.0.0.1:5000`.

## Running Tests

The project includes a suite of unit tests to ensure the correctness of the matrix operations. To run the tests:

```sh
python -m pytest tests/ -v -s
```
- then proceed to run ```python app.py```

## Run unit tests
## Project Structure

The repository is organized as follows:

```
├── app.py # Main Flask application with all routes
├── core/
│ └── matrix.py # Core Matrix class with all mathematical logic
├── static/
│ ├── css/ # CSS stylesheets
│ └── js/ # Frontend JavaScript for each operation
├── templates/ # HTML templates for the web interface
├── tests/
│ ├── test_matrix.py # Unit tests for the Matrix class
│ └── test_routes.py # Unit tests for the Flask routes
├── docs/ # System analysis, diagrams, and documentation
├── requirements.txt # Python dependencies
└── .github/ # CI workflows and PR templates
```
python -m pytest tests/test_matrix.py -v -s
```

## Cryptography Implementation

The application uses matrix multiplication for encryption and decryption, a method similar to the Hill Cipher.

1. **Character Mapping:** Each character in the message is converted to a number based on a predefined map (`A=1, B=2, ..., ' '=29`).
2. **Message Matrix:** The resulting sequence of numbers is organized into a matrix (`M`). The number of rows of this matrix matches the dimensions of the encoding matrix.
3. **Encryption:** The message matrix `M` is multiplied by a user-provided, invertible *encoding matrix* `A` to produce the *encrypted matrix* `N`.
- `N = A * M`
4. **Decryption:** To recover the original message, the encrypted matrix `N` is multiplied by the inverse of the encoding matrix, `A⁻¹`.
- `M = A⁻¹ * N`
5. **Conversion to Text:** The numbers in the resulting matrix `M` are converted back to characters to reveal the original message.

An encoding matrix must be invertible (i.e., its determinant must be non-zero) for decryption to be possible. The application validates this condition before performing any encryption.

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
39 changes: 39 additions & 0 deletions static/js/matrix-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,45 @@ function generateMatrixInput(rows, cols, containerId, label, gridId) {
const section = document.createElement('div');
section.className = 'matrix-section';
section.innerHTML = `<h3>${label}</h3>`;

const header = document.createElement('div');
const importBtn = document.createElement('button');
const fileInput = document.createElement('input');
importBtn.textContent = 'Importar CSV';
importBtn.type = 'button';
fileInput.type = 'file';
fileInput.accept = '.csv,text/csv';
fileInput.style.display = 'none';
fileInput.dataset.matrixId = gridId;

importBtn.addEventListener('click', function() {
fileInput.click();
});

fileInput.addEventListener('change', function(event) {
const file = event.target.files[0];
if (!file) return;

const reader = new FileReader();

reader.onload = function(e) {
const csvText = e.target.result;
importMatrixFromCSV(csvText, gridId, rows, cols);
};

reader.onerror = function() {
showError('Erro ao ler o ficheiro CSV.');
};

reader.readAsText(file);

event.target.value = '';
});

header.appendChild(importBtn);
header.appendChild(fileInput);

section.appendChild(header);

const grid = document.createElement('div');
grid.className = 'matrix-input-grid';
Expand Down
72 changes: 72 additions & 0 deletions static/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,76 @@ function prettyHTML(matrix, title) {

html += '</table>';
return html;
}

function importMatrixFromCSV(csvText, matrixId, expectedRows, expectedCols) {
try {
// Divide o CSV em linhas
const lines = csvText.trim().split('\n').filter(line => line.trim() !== '');

if (lines.length === 0) {
throw new Error('Arquivo CSV vazio.');
}

// Parse da matriz
const matrix = lines.map(line =>
line.split(',').map(num => {
const val = num.trim();
return val === '' ? 0 : parseFloat(val);
})
);

// Verifica se todas as linhas têm o mesmo número de colunas
const cols = matrix[0].length;
if (!matrix.every(row => row.length === cols)) {
throw new Error('Todas as linhas devem ter o mesmo número de elementos.');
}

// Verifica se as dimensões correspondem às esperadas
if (expectedRows && expectedCols) {
if (matrix.length !== expectedRows || cols !== expectedCols) {
throw new Error(`Dimensões incorretas. Esperado: ${expectedRows}x${expectedCols}, Obtido: ${matrix.length}x${cols}`);
}
}

// Preenche os inputs da matriz
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
const input = document.querySelector(`#${matrixId} [data-row="${i}"][data-col="${j}"]`);
if (input) {
input.value = matrix[i][j];
input.dispatchEvent(new Event('input'));
}
}
}

showSuccess(`Matriz ${matrixId === 'matrix-a' ? 'A' : 'B'} importada com sucesso!`);

} catch (error) {
console.error('Erro ao importar CSV:', error);
showError('Erro ao importar CSV: ' + error.message);
}
}

function showSuccess(message) {
hideError();
const successDiv = document.createElement('div');
successDiv.className = 'success-message';
successDiv.textContent = message;
successDiv.style.margin = '0.5rem 0';
successDiv.style.padding = '0.5rem';
successDiv.style.backgroundColor = '#d4edda';
successDiv.style.color = '#155724';
successDiv.style.border = '1px solid #c3e6cb';
successDiv.style.borderRadius = '0.25rem';
successDiv.style.fontSize = '0.9rem';

setTimeout(() => {
if (successDiv.parentNode) {
successDiv.parentNode.removeChild(successDiv);
}
}, 3000);

const form = document.getElementById('matrix-form');
form.parentNode.insertBefore(successDiv, form.nextSibling);
}