diff --git a/.gitignore b/.gitignore index c64fcdf..5596d38 100644 --- a/.gitignore +++ b/.gitignore @@ -419,4 +419,4 @@ FodyWeavers.xsd # Rider .idea -.git \ No newline at end of file +.git diff --git a/HW1/HW1.sln b/HW1/HW1.sln new file mode 100644 index 0000000..3ad542c --- /dev/null +++ b/HW1/HW1.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixMultiplication", "MatrixMultiplication\MatrixMultiplication.csproj", "{3C1ACBE6-BD12-4934-A58E-10141C3D6419}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "matrixmultiplication.Test", "matrixmultiplication.Test\matrixmultiplication.Test.csproj", "{93242727-EAF9-411B-AFFD-8FFFF083BE84}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3C1ACBE6-BD12-4934-A58E-10141C3D6419}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C1ACBE6-BD12-4934-A58E-10141C3D6419}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3C1ACBE6-BD12-4934-A58E-10141C3D6419}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3C1ACBE6-BD12-4934-A58E-10141C3D6419}.Release|Any CPU.Build.0 = Release|Any CPU + {93242727-EAF9-411B-AFFD-8FFFF083BE84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93242727-EAF9-411B-AFFD-8FFFF083BE84}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93242727-EAF9-411B-AFFD-8FFFF083BE84}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93242727-EAF9-411B-AFFD-8FFFF083BE84}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/HW1/MatrixMultiplication/MatrixBench.cs b/HW1/MatrixMultiplication/MatrixBench.cs new file mode 100644 index 0000000..c37b03b --- /dev/null +++ b/HW1/MatrixMultiplication/MatrixBench.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace MatrixMultiplication; + +using System; +using System.Diagnostics; +using System.Linq; + +/// +/// Compare the operating speed with the sequential version depending on the matrix sizes. +/// +public static class MatrixBench +{ + /// + /// Runs the benchmark for multiple matrix sizes and saves the results to a file. + /// + public static void RunBenchmark() + { + int[] sizes = [100, 200, 300, 400, 500]; + const string filePath = "resultBenchmark.txt"; + + File.WriteAllText(filePath, $"{"Size",4} {"Seq_Exp",10} {"Seq_Dev",10} {"Par_Exp",10} {"Par_Dev",10}\n"); + + foreach (var size in sizes) + { + var sequentialResult = BenchmarkMatrixMultiplication(size, false); + var parallelResult = BenchmarkMatrixMultiplication(size, true); + + var line = + $"{size,4} {sequentialResult.Expectation,10:F4} {sequentialResult.Deviation,10:F4} {parallelResult.Expectation,10:F4} {parallelResult.Deviation,10:F4}"; + + File.AppendAllText("resultBenchmark.txt", line + Environment.NewLine); + } + } + + /// + /// Runs a matrix multiplication benchmark and computes mathematical expectation and standard deviation. + /// + /// The number of rows and columns of the square matrix. + /// If true, uses parallel matrix multiplication. + /// Number of measurement repetitions for statistics (default 100). + /// + /// Tuple (expectation, deviation):. + /// - Expectation: mathematical expectation time in milliseconds. + /// - Deviation: standard deviation time in milliseconds. + /// + private static (double Expectation, double Deviation) BenchmarkMatrixMultiplication(int size, bool useParallel, int repeat = 10) + { + var elapsedTimes = new double[repeat]; + for (var i = 0; i < repeat; i++) + { + elapsedTimes[i] = PerformSingleRun(size, useParallel); + } + + var expectation = elapsedTimes.Average(); + var deviation = Math.Sqrt(elapsedTimes.Average(t => Math.Pow(t - expectation, 2))); + + return (expectation, deviation); + } + + /// + /// Performs a single run of matrix multiplication with randomly generated matrix. + /// + /// The number of rows and columns of the square matrix. + /// If true, uses parallel matrix multiplication. + /// Elapsed time in milliseconds for a single multiplication. + private static long PerformSingleRun(int size, bool useParallel) + { + var matrix1 = MatrixUtils.GenerateRandomMatrix(size, size); + var matrix2 = MatrixUtils.GenerateRandomMatrix(size, size); + + var stopwatch = Stopwatch.StartNew(); + + if (useParallel) + { + MatrixUtils.MultiplyMatrixParallel(matrix1, matrix2); + } + else + { + MatrixUtils.MultiplyMatrix(matrix1, matrix2); + } + + stopwatch.Stop(); + + return stopwatch.ElapsedMilliseconds; + } +} \ No newline at end of file diff --git a/HW1/MatrixMultiplication/MatrixFormatException.cs b/HW1/MatrixMultiplication/MatrixFormatException.cs new file mode 100644 index 0000000..f0ef9c5 --- /dev/null +++ b/HW1/MatrixMultiplication/MatrixFormatException.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace MatrixMultiplication; + +/// +/// The exception that is thrown when a matrix file has an invalid format. +/// +public class MatrixFormatException : Exception +{ + /// + /// Initializes a new instance of the class. + /// + public MatrixFormatException() + { + } + + /// + /// Initializes a new instance of the class with a message. + /// + /// The error message. + public MatrixFormatException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a message and inner exception. + /// + /// The error message. + /// The inner exception. + public MatrixFormatException(string message, Exception innerException) + : base(message, innerException) + { + } +} \ No newline at end of file diff --git a/HW1/MatrixMultiplication/MatrixMultiplication.csproj b/HW1/MatrixMultiplication/MatrixMultiplication.csproj new file mode 100644 index 0000000..917eeb4 --- /dev/null +++ b/HW1/MatrixMultiplication/MatrixMultiplication.csproj @@ -0,0 +1,27 @@ + + + + Exe + net9.0 + enable + enable + true + $(NoWarn);1591 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/HW1/MatrixMultiplication/MatrixUtils.cs b/HW1/MatrixMultiplication/MatrixUtils.cs new file mode 100644 index 0000000..de08e66 --- /dev/null +++ b/HW1/MatrixMultiplication/MatrixUtils.cs @@ -0,0 +1,242 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace MatrixMultiplication; + +/// +/// Helper static class for working with matrices. +/// Contains functions for generating, checking, and other matrix operations. +/// +public static class MatrixUtils +{ + /// + /// Generates a random integer matrix of the specified size with values in the given range. + /// + /// Number of rows in the matrix. + /// Number of columns in the matrix. + /// Minimum value for matrix elements (inclusive). + /// Maximum value for matrix elements (exclusive). + /// A 2D integer array filled with random values. + public static int[,] GenerateRandomMatrix(int rows, int columns, int minValue = -100, int maxValue = 100) + { + var matrix = new int[rows, columns]; + var randomNumbers = new Random(); + + for (var i = 0; i < rows; i++) + { + for (var j = 0; j < columns; j++) + { + matrix[i, j] = randomNumbers.Next(minValue, maxValue); + } + } + + return matrix; + } + + /// + /// Multiplies two matrices and returns the result. + /// + /// First matrix. + /// Second matrix. + /// Resulting matrix after multiplication. + public static int[,] MultiplyMatrix(int[,] matrix1, int[,] matrix2) + { + var lengthRowMatrix1 = matrix1.GetLength(0); + var lengthColumnMatrix1 = matrix1.GetLength(1); + var lengthRowMatrix2 = matrix2.GetLength(0); + var lengthColumnMatrix2 = matrix2.GetLength(1); + + // Checking the compatibility of matrices for multiplication + if (lengthColumnMatrix1 != lengthRowMatrix2) + { + throw new ArgumentException("The number of columns of the first matrix is not equal to the number of rows of the second one!"); + } + + var result = new int[lengthRowMatrix1, lengthColumnMatrix2]; + + for (var i = 0; i < lengthRowMatrix1; i++) + { + for (var j = 0; j < lengthColumnMatrix2; j++) + { + var sum = 0; + for (var k = 0; k < lengthColumnMatrix1; k++) + { + sum += matrix1[i, k] * matrix2[k, j]; + } + + result[i, j] = sum; + } + } + + return result; + } + + /// + /// Multiplies two matrices in parallel. + /// Each cell of the result matrix is computed in a separate thread. + /// + /// First matrix. + /// Second matrix. + /// Resulting matrix of multiplication. + /// Thrown when dimensions are not compatible for multiplication. + public static int[,] MultiplyMatrixParallel(int[,] matrix1, int[,] matrix2) + { + var lengthRowMatrix1 = matrix1.GetLength(0); + var lengthColumnMatrix1 = matrix1.GetLength(1); + var lengthRowMatrix2 = matrix2.GetLength(0); + var lengthColumnMatrix2 = matrix2.GetLength(1); + + // Checking the compatibility of matrices for multiplication + if (lengthColumnMatrix1 != lengthRowMatrix2) + { + throw new ArgumentException("The number of columns of the first matrix is not equal to the number of rows of the second one."); + } + + var result = new int[lengthRowMatrix1, lengthColumnMatrix2]; + var numThreads = Math.Min(Environment.ProcessorCount, lengthRowMatrix1); + var threads = new Thread[numThreads]; + + for (var t = 0; t < numThreads; t++) + { + var threadIndex = t; + threads[t] = new Thread(() => + { + for (var i = threadIndex; i < lengthRowMatrix1; i += numThreads) + { + for (var j = 0; j < lengthColumnMatrix2; j++) + { + var sum = 0; + for (var k = 0; k < lengthColumnMatrix1; k++) + { + sum += matrix1[i, k] * matrix2[k, j]; + } + + result[i, j] = sum; + } + } + }); + threads[t].Start(); + } + + for (var i = 0; i < numThreads; i++) + { + threads[i].Join(); + } + + return result; + } + + /// + /// Compares two matrices for equality. + /// + /// First matrix. + /// Second matrix. + /// + /// true — if the matrices are the same size and all their elements match. + /// false — if the dimensions are different or at least one element is different. + /// + public static bool AreMatrixEqual(int[,] matrix1, int[,] matrix2) + { + if (matrix1.GetLength(0) != matrix2.GetLength(0) || + matrix1.GetLength(1) != matrix2.GetLength(1)) + { + return false; + } + + var rows = matrix1.GetLength(0); + var columns = matrix1.GetLength(1); + + for (var i = 0; i < rows; i++) + { + for (var j = 0; j < columns; j++) + { + if (matrix1[i, j] != matrix2[i, j]) + { + return false; + } + } + } + + return true; + } + + /// + /// Reads a matrix of integers from a text file. + /// + /// The path to the file containing the matrix. + /// matrix from file. + public static int[,] ReadMatrixFromFile(string path) + { + // Checking the existence of the file + if (!File.Exists(path)) + { + throw new FileNotFoundException("File not found."); + } + + var lines = File.ReadAllLines(path); + + // Checking that the file is not empty + if (lines.Length == 0) + { + throw new MatrixFormatException("Matrix file is empty."); + } + + var rows = lines.Length; + var columns = lines[0].Split(' ', StringSplitOptions.RemoveEmptyEntries).Length; + + var matrix = new int[rows, columns]; + + for (var i = 0; i < rows; i++) + { + // Check that the string contains only numbers, spaces, and minus signs. + if (lines[i].Any(c => !char.IsDigit(c) && c != ' ' && c != '-')) + { + throw new MatrixFormatException("Invalid character in matrix file: only digits, spaces and minus signs are allowed."); + } + + var nums = lines[i].Split(' ', StringSplitOptions.RemoveEmptyEntries) + .Select(int.Parse) + .ToArray(); + + // Checking that all rows and columns in the matrix have the same length + if (nums.Length != columns) + { + throw new MatrixFormatException("Invalid matrix format: the matrix is not complete."); + } + + for (var j = 0; j < columns; j++) + { + matrix[i, j] = nums[j]; + } + } + + return matrix; + } + + /// + /// Writes a matrix to a text file. + /// + /// The path to the output file. + /// Matrix for writing to file to write. + public static void WriteMatrixToFile(string path, int[,] matrix) + { + var rows = matrix.GetLength(0); + var columns = matrix.GetLength(1); + + using var writer = new StreamWriter(path); + for (var i = 0; i < rows; i++) + { + for (var j = 0; j < columns; j++) + { + writer.Write(matrix[i, j]); + if (j < columns - 1) + { + writer.Write(" "); + } + } + + writer.WriteLine(); + } + } +} \ No newline at end of file diff --git a/HW1/MatrixMultiplication/Program.cs b/HW1/MatrixMultiplication/Program.cs new file mode 100644 index 0000000..41ffa23 --- /dev/null +++ b/HW1/MatrixMultiplication/Program.cs @@ -0,0 +1,64 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +using MatrixMultiplication; + +// 1. To multiply matrices from files: +// Write dotnet run -- +// Input files must contain matrices as strings of numbers separated by spaces. +// Each line of the file is a row of the matrix. +// The program checks the correctness of the matrix sizes and creates a new file with the product. +// Example: +// dotnet run -- "TestMatrix/TestFile1.txt" "TestMatrix/TestFile2.txt" "TestMatrix/result.txt" +// +// 2. To run the statistics benchmark: +// dotnet run -- benchmark +// +if (args.Length == 1 && args[0].Equals("benchmark", StringComparison.CurrentCultureIgnoreCase)) +{ + MatrixBench.RunBenchmark(); +} +else if (args.Length != 3 || string.IsNullOrEmpty(args[0]) || string.IsNullOrEmpty(args[1]) || string.IsNullOrEmpty(args[2])) +{ + Console.WriteLine("Invalid arguments"); + Console.WriteLine( + "Usage:\n" + + "1) Matrix multiplication:\n" + + " dotnet run -- \n" + + "2) Run benchmark:\n" + + " dotnet run -- benchmark"); +} +else +{ + var matrixPath1 = args[0]; + var matrixPath2 = args[1]; + var resultFile = args[2]; + + try + { + var matrix1 = MatrixUtils.ReadMatrixFromFile(matrixPath1); + var matrix2 = MatrixUtils.ReadMatrixFromFile(matrixPath2); + var matrixResult = MatrixUtils.MultiplyMatrixParallel(matrix1, matrix2); + + MatrixUtils.WriteMatrixToFile(resultFile, matrixResult); + + Console.WriteLine($"Matrix multiplication completed successfully. Result saved in '{resultFile}'."); + } + catch (MatrixFormatException ex) + { + Console.WriteLine($"Matrix error: {ex.Message}"); + } + catch (FileNotFoundException ex) + { + Console.WriteLine($"File error: {ex.Message}"); + } + catch (ArgumentException ex) + { + Console.WriteLine($"Multiplication error: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"Unexpected error: {ex.Message}"); + } +} diff --git a/HW1/MatrixMultiplication/resultBenchmark.txt b/HW1/MatrixMultiplication/resultBenchmark.txt new file mode 100644 index 0000000..d85199e --- /dev/null +++ b/HW1/MatrixMultiplication/resultBenchmark.txt @@ -0,0 +1,6 @@ +Size Seq_Exp Seq_Dev Par_Exp Par_Dev + 100 9,6000 1,2806 7,7000 2,5710 + 200 46,6000 6,2960 15,7000 1,2689 + 300 164,4000 20,0659 97,3000 47,3984 + 400 1010,1000 55,4084 327,5000 204,1613 + 500 2477,7000 791,3668 306,6000 30,5817 diff --git a/HW1/MatrixMultiplication/stylecop.json b/HW1/MatrixMultiplication/stylecop.json new file mode 100644 index 0000000..76c8e76 --- /dev/null +++ b/HW1/MatrixMultiplication/stylecop.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "khusainovilas", + "copyrightText": "Copyright (c) {companyName}. All rights reserved." + } + } +} \ No newline at end of file diff --git a/HW1/matrixmultiplication.Test/MatrixmultiplicationTest.cs b/HW1/matrixmultiplication.Test/MatrixmultiplicationTest.cs new file mode 100644 index 0000000..4dbf6cf --- /dev/null +++ b/HW1/matrixmultiplication.Test/MatrixmultiplicationTest.cs @@ -0,0 +1,93 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace Matrixmultiplication.Test; + +using MatrixMultiplication; + +/// +/// NUnit tests for matrix multiplication functions. +/// Tests both sequential and parallel implementations for correctness. +/// +public class MatrixmultiplicationTest +{ + private int[,] matrix1; + private int[,] matrix2; + private int[,] matrixExpected; + + /// + /// Initializes matrices before each test. + /// + [SetUp] + public void Setup() + { + this.matrix1 = new int[,] + { + { 1, 2 }, + { 3, 4 }, + }; + this.matrix2 = new int[,] + { + { 2, 0 }, + { 1, 2 }, + }; + this.matrixExpected = new int[,] + { + { 4, 4 }, + { 10, 8 }, + }; + } + + /// + /// Tests the sequential matrix multiplication function. + /// + [Test] + public void MatrixUtils_MatrixMultiply_Matrix1_Matrix2() + { + var result = MatrixUtils.MultiplyMatrix(this.matrix1, this.matrix2); + Assert.That(MatrixUtils.AreMatrixEqual(result, this.matrixExpected), Is.True); + } + + /// + /// Tests the parallel matrix multiplication function. + /// + [Test] + public void MatrixUtils_MultiplyMatrixParallel_Matrix1_Matrix2() + { + var result = MatrixUtils.MultiplyMatrixParallel(this.matrix1, this.matrix2); + Assert.That(MatrixUtils.AreMatrixEqual(result, this.matrixExpected), Is.True); + } + + /// + /// Tests multiplication with 1x1 matrices. + /// + [Test] + public void MultiplyMatrix_OneByOneMatrices_ReturnsCorrectValue() + { + int[,] a = { { 5 } }; + int[,] b = { { 3 } }; + int[,] expected = { { 15 } }; + + var resultSeq = MatrixUtils.MultiplyMatrix(a, b); + var resultPar = MatrixUtils.MultiplyMatrixParallel(a, b); + + Assert.Multiple(() => + { + Assert.That(MatrixUtils.AreMatrixEqual(resultSeq, expected), Is.True); + Assert.That(MatrixUtils.AreMatrixEqual(resultPar, expected), Is.True); + }); + } + + /// + /// Tests multiplication with empty matrices. + /// + [Test] + public void MultiplyMatrix_EmptyMatrices_ThrowsArgumentException() + { + int[,] empty = new int[0, 0]; + + Assert.Throws(() => MatrixUtils.MultiplyMatrix(empty, this.matrix2)); + Assert.Throws(() => MatrixUtils.MultiplyMatrixParallel(empty, this.matrix2)); + } +} diff --git a/HW1/matrixmultiplication.Test/matrixmultiplication.Test.csproj b/HW1/matrixmultiplication.Test/matrixmultiplication.Test.csproj new file mode 100644 index 0000000..8f34892 --- /dev/null +++ b/HW1/matrixmultiplication.Test/matrixmultiplication.Test.csproj @@ -0,0 +1,36 @@ + + + + net9.0 + enable + enable + + false + true + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + diff --git a/HW1/matrixmultiplication.Test/stylecop.json b/HW1/matrixmultiplication.Test/stylecop.json new file mode 100644 index 0000000..76c8e76 --- /dev/null +++ b/HW1/matrixmultiplication.Test/stylecop.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "khusainovilas", + "copyrightText": "Copyright (c) {companyName}. All rights reserved." + } + } +} \ No newline at end of file