diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..75a2e367 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,31 @@ +name: Run Tests + +on: + [ push, pull_request ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Install mypy + run: | + python -m pip install mypy + - name: Run mypy + run: | + mypy . + - name: Run tests + run: | + python ./scripts/run_tests.py diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..624cd195 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,2 @@ +[mypy] +exclude = venv/ diff --git a/project/matrix.py b/project/matrix.py new file mode 100644 index 00000000..9c7ac409 --- /dev/null +++ b/project/matrix.py @@ -0,0 +1,73 @@ +from typing import List + + +class Matrix: + """ + Класс для работы с матрицами, включая сложение, умножение и транспонирование. + """ + + def __init__(self, data: List[List[float]]): + """ + Инициализирует матрицу. + + :param data: Двумерный список чисел, представляющий матрицу. + """ + self.data: List[List[float]] = data + self.rows: int = len(data) + self.cols: int = len(data[0]) if data else 0 + + def __str__(self) -> str: + """ + Возвращает строковое представление матрицы. + + :return: Строка, представляющая матрицу. + """ + return "\n".join(["\t".join(map(str, row)) for row in self.data]) + + def add(self, other: "Matrix") -> "Matrix": + """ + Складывает две матрицы. + + :param other: Вторая матрица для сложения. + :return: Новая матрица, представляющая сумму. + :raises ValueError: Если матрицы имеют разные размеры. + """ + if self.rows != other.rows or self.cols != other.cols: + raise ValueError("Matrices must have the same dimensions for addition.") + + result = [ + [self.data[i][j] + other.data[i][j] for j in range(self.cols)] + for i in range(self.rows) + ] + return Matrix(result) + + def multiply(self, other: "Matrix") -> "Matrix": + """ + Умножает две матрицы. + + :param other: Вторая матрица для умножения. + :return: Новая матрица, представляющая произведение. + :raises ValueError: Если число столбцов первой матрицы не равно числу строк второй матрицы. + """ + if self.cols != other.rows: + raise ValueError( + "Number of columns in the first matrix must equal the number of rows in the second matrix." + ) + + result = [ + [ + sum(self.data[i][k] * other.data[k][j] for k in range(self.cols)) + for j in range(other.cols) + ] + for i in range(self.rows) + ] + return Matrix(result) + + def transpose(self) -> "Matrix": + """ + Транспонирует матрицу, меняя строки и столбцы местами. + + :return: Новая транспонированная матрица. + """ + result = [[self.data[j][i] for j in range(self.rows)] for i in range(self.cols)] + return Matrix(result) diff --git a/project/vectors.py b/project/vectors.py new file mode 100644 index 00000000..011ae591 --- /dev/null +++ b/project/vectors.py @@ -0,0 +1,64 @@ +import math +from typing import List + + +class Vector: + """ + Класс для работы с векторами в многомерном пространстве. + """ + + def __init__(self, components: List[float]): + """ + Инициализирует вектор с заданными компонентами. + + :param components: Список чисел, представляющих компоненты вектора. + """ + self.components: List[float] = components + + def __str__(self) -> str: + """ + Возвращает строковое представление вектора. + + :return: Строка, представляющая вектор. + """ + return f"Vector({self.components})" + + def length(self) -> float: + """ + Вычисляет длину (модуль) вектора. + + :return: Длина вектора. + """ + return math.sqrt(sum(x**2 for x in self.components)) + + def dot_product(self, other: "Vector") -> float: + """ + Вычисляет скалярное произведение с другим вектором. + + :param other: Второй вектор. + :return: Скалярное произведение векторов. + :raises ValueError: Если векторы имеют разные размеры. + """ + if len(self.components) != len(other.components): + raise ValueError("Vectors must have the same dimension for dot product") + return sum(x * y for x, y in zip(self.components, other.components)) + + def angle_between(self, other: "Vector") -> float: + """ + Вычисляет угол между двумя векторами в радианах. + + :param other: Второй вектор. + :return: Угол в радианах. + :raises ValueError: Если хотя бы один из векторов имеет нулевую длину. + """ + dot_prod = self.dot_product(other) + length_self = self.length() + length_other = other.length() + + if length_self == 0 or length_other == 0: + raise ValueError("Cannot compute angle with zero-length vector") + + cos_theta = dot_prod / (length_self * length_other) + # Ограничиваем значение cos_theta из-за возможных ошибок округления + cos_theta = max(-1.0, min(1.0, cos_theta)) + return math.acos(cos_theta) diff --git a/tests/test_basic.py b/tests/test_basic.py deleted file mode 100644 index 4811167b..00000000 --- a/tests/test_basic.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest -import project # on import will print something from __init__ file - - -def setup_module(module): - print("basic setup module") - - -def teardown_module(module): - print("basic teardown module") - - -def test_1(): - assert 1 + 1 == 2 - - -def test_2(): - assert "1" + "1" == "11" diff --git a/tests/test_matrix.py b/tests/test_matrix.py new file mode 100644 index 00000000..dcf5bfda --- /dev/null +++ b/tests/test_matrix.py @@ -0,0 +1,41 @@ +import pytest +from project.matrix import Matrix + + +def test_matrix_addition(): + A = Matrix([[1, 2], [3, 4]]) + B = Matrix([[5, 6], [7, 8]]) + expected = Matrix([[6, 8], [10, 12]]) + assert A.add(B).data == expected.data + + +def test_matrix_addition_dimension_mismatch(): + A = Matrix([[1, 2, 3], [4, 5, 6]]) + B = Matrix([[7, 8], [9, 10]]) + with pytest.raises( + ValueError, match="Matrices must have the same dimensions for addition." + ): + A.add(B) + + +def test_matrix_multiplication(): + A = Matrix([[1, 2], [3, 4]]) + B = Matrix([[2, 0], [1, 2]]) + expected = Matrix([[4, 4], [10, 8]]) + assert A.multiply(B).data == expected.data + + +def test_matrix_multiplication_dimension_mismatch(): + A = Matrix([[1, 2], [3, 4]]) + B = Matrix([[5, 6, 7]]) + with pytest.raises( + ValueError, + match="Number of columns in the first matrix must equal the number of rows in the second matrix.", + ): + A.multiply(B) + + +def test_matrix_transpose(): + A = Matrix([[1, 2, 3], [4, 5, 6]]) + expected = Matrix([[1, 4], [2, 5], [3, 6]]) + assert A.transpose().data == expected.data diff --git a/tests/test_vectors.py b/tests/test_vectors.py new file mode 100644 index 00000000..12b3e2e2 --- /dev/null +++ b/tests/test_vectors.py @@ -0,0 +1,38 @@ +import pytest +import math +from project.vectors import Vector + + +def test_vector_length(): + v = Vector([3, 4]) + assert v.length() == 5 + + +def test_dot_product(): + v1 = Vector([1, 2, 3]) + v2 = Vector([4, -5, 6]) + assert v1.dot_product(v2) == 12 + + +def test_angle_between(): + v1 = Vector([1, 0]) + v2 = Vector([0, 1]) + assert math.isclose(v1.angle_between(v2), math.pi / 2, rel_tol=1e-6) + + +def test_dot_product_dimension_mismatch(): + v1 = Vector([1, 2, 3]) + v2 = Vector([4, 5]) + with pytest.raises( + ValueError, match="Vectors must have the same dimension for dot product" + ): + v1.dot_product(v2) + + +def test_angle_with_zero_length_vector(): + v1 = Vector([0, 0, 0]) + v2 = Vector([1, 2, 3]) + with pytest.raises( + ValueError, match="Cannot compute angle with zero-length vector" + ): + v1.angle_between(v2)