Deadline: 19th of Esfand - 23:59 o'clock
The first part of your assignment focuses on the creation of a shell script designed to efficiently perform backup operations and generate comprehensive logs. By completing this task, you'll gain hands-on experience in scripting for backup solutions, enhancing your skills in automation and system administration.
Develop a bash shell script capable of:
- Executing backup operations for specified directories. The script will backup everything within the
Source Path
, put them all in a zip file, and put the zip file within theDestination Path
. - Generating detailed logs covering various aspects of the backup process, such as file specifics, timestamps, and system data.
- Script File Location: Find the
backup_script.sh
in the providedbash
folder. This is where you'll write your script. - Make Executable: Ensure
backup_script.sh
is executable. Usechmod +x backup_script.sh
in thebash
folder, if necessary.
-
Shebang Line: Begin your script with
#!/bin/bash
to specify the use of Bash. -
Input Validation: Your script must accept three parameters:
- Source Path: The directory to back up (required).
- Destination Path: Where the backup will be stored (required).
- Compression Level: Denote with
-c
or--compression
and a value. (Optional, The default is 1)
Display an error message and exit if the required inputs are not provided.
-
Backup Operation: Utilize
zip
to back up the target directory. Format the backup file asYYYY-MM-DD_directoryname.zip
and save it to the chosen destination path, whereYYYY-MM-DD
is the date of the backup anddirectoryname
is the name of the directory being backed up. -
Error Handling: Terminate the script with a non-zero status upon encountering errors.
- Produce a
backup_log.txt
in the destination path. - Ensure the log is clear and structured, following this example:
Backup Log Report
- Date:
2024-02-29
- Time:
14:00:00 UTC
Machine Information:
- Operating System:
Linux server 5.4.0-42-generic #46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
Backup Details:
- Source Path:
/home/user/data
- Destination Path:
/mnt/backup/2024-02-29_data_backup.zip
- Compression Level:
9
- Files Backed Up:
1,250
- Directories Backed Up:
75
Backup Summary:
- Start Time:
14:00:00 UTC
- End Time:
14:05:30 UTC
- Total Duration:
5 minutes 30 seconds
Validate your script using the need_backup
folder supplied for this assignment.
./backup_script.sh /home/user/documents /home/user/backup -c 9
Backup of /home/user/documents completed successfully.
Saved to /home/user/backup/2024-02-29_documents.zip
This part is designed to immerse you in the world of linear algebra through programming. You will create a fully functional algebra namespace in C++ that encompasses a variety of matrix operations. From initializing matrices to performing complex operations like multiplication and finding inverses, this assignment will hone your programming skills and teach you some of the features of C++20
.
Create a namespace with the implementation of all the below functions. be very carefull with all the pitfalls we gave you for each function, you have to check every condition and throw an error anywhere needed.
First, make sure the following types are defined within your algebra
namespace:
// Matrix data structure
template<typename T>
using MATRIX = std::vector<std::vector<T>>;
// Matrix initialization types
enum class MatrixType { Zeros, Ones, Identity, Random };
// Function template for matrix initialization
template<typename T>
MATRIX<T> create_matrix(std::size_t rows, std::size_t columns, std::optional<MatrixType> type = MatrixType::Zeros,
std::optional<T> lowerBound = std::nullopt, std::optional<T> upperBound = std::nullopt);
This function initializes a matrix of dimensions rows x columns
with values depending on the specified MatrixType
. The optional parameters lowerBound
and upperBound
are used for random initialization.
- Zero: Initializes all elements to 0.
- Ones: Initializes all elements to 1.
- Identity: Creates an identity matrix (1s on the diagonal, 0s elsewhere). Only works for square matrices.
- Random: Fills the matrix with random values within the range
[lowerBound, upperBound]
.
- Dimension Mismatch: For
MatrixType::Identity
, ensurerows == columns
. An identity matrix must be square. - Random Range: When
MatrixType::Random
is chosen, bothlowerBound
andupperBound
must be specified. EnsurelowerBound < upperBound
. You are also only allowed to userandom
library for generating your random numbers.
// Create a 5x5 zero matrix of integers
algebra::MATRIX<int> zerosMatrix = algebra::create_matrix<int>(5, 5);
// Create a 2x2 ones matrix of integers
algebra::MATRIX<int> onesMatrix = algebra::create_matrix<int>(2, 2, algebra::MatrixType::Ones);
// Create a 3x3 identity matrix of doubles
algebra::MATRIX<int> identityMatrix = algebra::create_matrix<int>(3, 3, algebra::MatrixType::Identity);
// Create a 4x4 matrix of random floats within [0.0, 1.0]
algebra::MATRIX<double> randomMatrix = algebra::create_matrix<double>(4, 4, algebra::MatrixType::Random, 0.0, 10.0);
template<typename T>
void display(const MATRIX<T>& matrix);
This function is designed to output the contents of a matrix in a formatted manner. It iterates through the matrix and prints each element, ensuring that the display is both aesthetically pleasing and informative. The function strictly uses the <format>
library for formatting output, catering to requirements such as fixed width and precision for each element, thus maintaining uniformity and readability.
- Formatting Consistency: Ensure that all numbers are displayed with a maximum width of 7 characters. This requires handling both large numbers and those with many decimal places carefully. Improper handling may lead to misaligned rows or columns, which we don not want.
- Library Restrictions: Students are restricted to using only the
<format>
library for formatting.
Given a matrix mat
:
algebra::MATRIX<int> mat = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
algebra::display(mat);
The expected output should be neatly formatted as follows:
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 7 | 8 | 9 |
template<typename T>
MATRIX<T> sum_sub(const MATRIX<T>& matrixA, const MATRIX<T>& matrixB, std::optional<std::string> operation = "sum");
This function performs either the summation or subtraction of two matrices, depending on the specified operation. By default, the function adds the corresponding elements of the input matrices (matrixA
and matrixB
). If the operation
parameter is explicitly set to "sub"
, it subtracts matrixB
from matrixA
.
- Dimension Mismatch: Ensure that both matrices have the same dimensions. Throw an error otherwise.
- Operation Parameter: The operation parameter accepts only two values:
"sum"
and"sub"
. Any other value will default to summation.
Summation of two matrices:
algebra::MATRIX<int> matrixA = {{1, 2, 3}, {4, 5, 6}};
algebra::MATRIX<int> matrixB = {{6, 5, 4}, {3, 2, 1}};
algebra::MATRIX<int> resultSum = algebra::sum_sub(matrixA, matrixB); // Default operation is "sum"
/* Expected Matrix
| 7 | 7 | 7 |
| 7 | 7 | 7 |
*/
Subtraction of two matrices:
algebra::MATRIX<int> matrixA = {{10, 20, 30}, {40, 50, 60}};
algebra::MATRIX<int> matrixB = {{1, 2, 3}, {4, 5, 6}};
algebra::MATRIX<int> resultSub = algebra::sum_sub(matrixA, matrixB, "sub");
/* Expected Matrix
| 9 | 18 | 27 |
| 36 | 45 | 54 |
*/
For scalar multiplication:
template<typename T>
MATRIX<T> multiply(const MATRIX<T>& matrix, const T scalar);
For matrix multiplication:
template<typename T>
MATRIX<T> multiply(const MATRIX<T>& matrixA, const MATRIX<T>& matrixB);
-
Scalar Multiplication: Multiplies each element of a given matrix by a scalar value. This operation enhances or reduces the magnitude of matrix elements uniformly, depending on the scalar value.
-
Matrix Multiplication: Computes the product of two matrices by performing the dot product of rows and columns. The operation is defined for two matrices
A
andB
where the number of columns inA
matches the number of rows inB
. The resulting matrix will have dimensions that match the number of rows inA
and the number of columns inB
.
- Dimension Mismatch: For matrix multiplication, it's crucial that the number of columns in
matrixA
matches the number of rows inmatrixB
. Failure to meet this condition must throw an error.
Scalar Multiplication:
algebra::MATRIX<int> mat = {{1, 2}, {3, 4}};
int scalar = 3;
algebra::MATRIX<int> scaledMatrix = algebra::multiply(mat, scalar);
/* Expected Matrix
| 3 | 6 |
| 9 | 12 |
*/
Matrix Multiplication:
algebra::MATRIX<int> matrixA = {{1, 2, 3}, {4, 5, 6}};
algebra::MATRIX<int> matrixB = {{7, 8}, {9, 10}, {11, 12}};
algebra::MATRIX<int> productMatrix = algebra::multiply(matrixA, matrixB);
/* Expected Matrix
| 58 | 64 |
| 139 | 154 |
*/
template<typename T>
MATRIX<T> hadamard_product(const MATRIX<T>& matrixA, const MATRIX<T>& matrixB);
This function computes the Hadamard Product (also known as the element-wise product) of two matrices of the same dimensions. The Hadamard Product is obtained by multiplying corresponding elements in the two matrices. The result is a new matrix where each element C[i][j]
is the product of elements A[i][j]
and B[i][j]
from the input matrices.
- Dimension Mismatch: The matrices
matrixA
andmatrixB
must be of the same size. Attempting to compute the Hadamard Product of matrices with differing dimensions must result in an error.
Given two matrices matA
and matB
:
algebra::MATRIX<int> matA = {{1, 2, 3}, {4, 5, 6}};
algebra::MATRIX<int> matB = {{7, 8, 9}, {10, 11, 12}};
algebra::MATRIX<int> result = algebra::hadamard_product(matA, matB);
/* Expected Matrix
| 7 | 16 | 27 |
| 40 | 55 | 72 |
*/
template<typename T>
MATRIX<T> transpose(const MATRIX<T>& matrix);
This function returns the transpose of a given matrix. The transpose of a matrix is obtained by flipping the matrix over its diagonal, thus turning its rows into columns and vice versa. This operation does not alter the original matrix but produces a new matrix where the element at position [i][j]
in the original matrix is moved to position [j][i]
in the transposed matrix.
- Empty Matrix: If the input matrix is empty, the function should gracefully handle this case and return an empty matrix as the transpose.
- Non-Square Matrices: The function should correctly handle non-square matrices (where the number of rows does not equal the number of columns). The dimensions of the transposed matrix will be different from the original matrix in such cases.
- Memory Usage: Transposing a large matrix may require significant memory for the new matrix, especially if the original matrix is large. Consider the memory implications of your implementation.
Given a matrix mat
:
algebra::MATRIX<int> mat = {{1, 2, 3}, {4, 5, 6}};
algebra::MATRIX<int> result = transpose(mat);
/* Expected Matrix
| 1 | 4 |
| 2 | 5 |
| 3 | 6 |
*/
template<typename T>
T trace(const MATRIX<T>& matrix);
This function calculates the trace of a given square matrix. The trace of a matrix is defined as the sum of the elements on the main diagonal (from the top left to the bottom right). It is a concept from linear algebra that applies only to square matrices and provides a quick, useful measure of a matrix's properties, including its eigenvalues' sum.
- Square Matrix Requirement: The trace can only be calculated for square matrices. If the input matrix is not square your program must throw an error.
Given a square matrix mat
:
algebra::MATRIX<int> mat = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int tr = algebra::trace(mat);
/* Expected Result
tr = 1 + 5 + 9 = 15
*/
template<typename T>
double determinant(const MATRIX<T>& matrix);
This function calculates the determinant of a given square matrix. The determinant is a scalar value that provides important information about the matrix, such as whether it is invertible (a non-zero determinant) or singular (a determinant of zero).
- Square Matrix Requirement: This function can only be applied to square matrices (matrices with equal number of rows and columns). Attempting to calculate the determinant of a non-square matrix must throw an error.
- Performance: Calculating the determinant of large matrices can be computationally expensive, especially using recursive methods. Consider the implications of this for very large matrices.
- Numerical Stability: For matrices with elements of floating-point types, the calculation of the determinant can suffer from numerical instability, leading to inaccurate results due to the limitations of floating-point arithmetic.
Given a square matrix mat
:
algebra::MATRIX<double> mat = {{4, 3}, {6, 3}};
double det = algebra::determinant(mat);
/* Expected Result
det = (4*3) - (3*6) = 12 - 18 = -6.0
*/
template<typename T>
MATRIX<double> inverse(const MATRIX<T>& matrix);
This function calculates the inverse of a given square matrix, if it exists. The inverse of a matrix A
is another matrix A^-1
such that the product of A
and A^-1
yields the identity matrix. The calculation of the inverse is typically based on the method of Gaussian elimination or finding the adjugate and determinant of the matrix. It is important to note that not all matrices have an inverse; a matrix must be square and have a non-zero determinant to be invertible.
- Square Matrix Requirement: Only square matrices (matrices with the same number of rows and columns) can have inverses. The function must check for this condition and handle non-square matrices with throwing error.
- Determinant Check: Before attempting to calculate the inverse, it's crucial to check that the determinant of the matrix is not zero. A zero determinant means the matrix is singular and does not have an inverse. if this happens, error is what you need to throw.
- Numerical Stability: For matrices with floating-point elements, the process of calculating the inverse may be subject to numerical instability, leading to inaccuracies. It's important to implement the algorithm carefully to minimize errors.
Given a square matrix mat
:
algebra::MATRIX<double> mat = {{4, 7}, {2, 6}};
algebra::MATRIX<double> invMat = algebra::inverse(mat);
/* Expected Matrix
| 3/5 | -7/10 |
| -1/5 | 2/5 |
*/
If you want to debug your code, set the if
statement to true
. This will allow you to place your debugging code in the designated section. Once you're done with the debugging process, remember to set the if
statement back to false
to test your program using the provided unit-test.cpp
.
Furthermore, whatever code you write should be implemented in the algebra.cpp
and algebra.h
files. Please refrain from making any changes to other files in the project.
#include <iostream>
#include <gtest/gtest.h>
#include "algebra.h"
int main(int argc, char **argv)
{
if (true) // Set to false to run unit-tests
{
// Debug section: Place your debugging code here
}
else
{
::testing::InitGoogleTest(&argc, argv);
std::cout << "RUNNING TESTS ..." << std::endl;
int ret{RUN_ALL_TESTS()};
if (!ret)
std::cout << "<<<SUCCESS>>>" << std::endl;
else
std::cout << "FAILED" << std::endl;
}
return 0;
}
Best Regards, Hamidi