diff --git a/conditions/sem02/lesson04/tasks.md b/conditions/sem02/lesson04/tasks.md index 39fce748..b976407a 100644 --- a/conditions/sem02/lesson04/tasks.md +++ b/conditions/sem02/lesson04/tasks.md @@ -29,7 +29,7 @@ Теперь мы готовы реализовывать фильтр размытия. Размытие изображения работает достаточно просто: - Сначала задается размер окна размытия - $l_w$, которое можно интерпретировать, как степень размытия. Чем больше окно размытия, тем сильнее результирующая картинка будет размыта. Обычно в качестве размеров окна размытия используются нечетные целые числа. -- Следующий шаг - это применение паддинга к входному изображению размеров $ N \times M $, причем ширина паддинга соответствует следующему выражению: $\lfloor\frac{l_w}{2}\rfloor$. +- Следующий шаг - это применение паддинга к входному изображению размеров $N \times M$, причем ширина паддинга соответствует следующему выражению: $\lfloor\frac{l_w}{2}\rfloor$. - Затем, по всему изображению с паддингом запускается обход скользящим окном размеров $l_w \times l_w$, причем центр окна всегда находится в пикселях, соответствующих пикселям исходного изображения. Т.е. центр окна размытия проходит пиксели из области $[l_w, l_w + N] \times [l_w, l_w + M]$ - В каждом положении окна размытия вычисляется среднее значений пикселей, попавших в окно. Результат записывается в новый массив, тех же размеров, что и исходное изображение. diff --git a/conditions/sem02/lesson05/tasks.md b/conditions/sem02/lesson05/tasks.md new file mode 100644 index 00000000..e985419a --- /dev/null +++ b/conditions/sem02/lesson05/tasks.md @@ -0,0 +1,80 @@ +## Задача 1. Оптимальное распределение ресурсов + +Некоторая компания производит $N$ типов товаров, используя $M$ типов ресурсов. Для учета производственных стоимостей в компании заведена таблица `costs`, которая представляет собой матрицу размеров $M \times N$. Элемент данной матрицы `costs[i][j]` показывает, сколько единиц i-ого ресурса требуется для производства одной единицы j-ого товара. Для учета доступных ресурсов в компании также определен одномерный массив `resource_amounts`, длины $M$. i-ый элемент массива `resource_amounts` соответствует количеству i-ого ресурса, доступного для использования в производстве. + +Каждый месяц аналитики компании, на основании данных анализа рынка, озвучивают ожидаемый спрос на производимые товары. Ожидаемый спрос характеризуется одномерным массивом `demand_expected`, состоящим из $N$ элементов. i-ому элементу массива `demand_expected` соответствует число товаров, которое необходимо произвести для удовлетворения ожидаемого спроса. + +Ваша задача - разработать функцию, которая позволяет определить, сможет ли компания удовлетворить ожидаемый спрос, учитывая текущее количество ресурсов и текущие производственные стоимости, или нет. + +Допишите код функции `can_satisfy_demand` в файле [task1](../../../solutions/sem02/lesson05/task1.py). + +**Входные данные**: +- `costs` - двумерный массив чисел с плавающей точкой - количества ресурсов, требуемых для производства товаров (размерность $M \times N$); +- `resource_amounts` - одномерный массив чисел с плавающей точкой - доступное количество ресурсов (длина $M$); +- `demand_expected` - одномерный массив целых чисел - необходимое число товаров для удовлетворения ожидаемого спроса (длина $N$); + +**Выходные данные**: +- Булево значение: `True`, если компания сможет удовлетворить ожидаемый спрос, иначе - `False`. + +*Сторонние эффекты*: +- Если размеры входных массивов не согласованы, необходимо возбудить исключение `ShapeMismatchError`. + +## Задача 2. Без базиса не выйдет + +Дана квадратная матрица $A$. В строках матрицы $A$ записаны векторы N-мерного пространства. Также дан вектор $\vec{a}$. Ваша задача - реализовать функцию, которая бы выполняла следующие вычисления: +- проверяла, являются ли вектора матрицы $A$ базисом в N-мерном пространстве; +- вычисляла бы ортогональные проекции $\vec{a}$ на базисные вектора, а также вычисляла бы ортогональные составляющие каждой проекции, если матрица $A$ задает базис. + +Допишите код функции `get_projections_components` в файле [task2](../../../solutions/sem02/lesson05/task2.py). + +**Входные данные**: +- `matrix` - двумерный массив `np.ndarray` - описание потенциального базиса; +- `vector` - одномерный массив `np.ndarray` - вектор, проекции которого необходимо вычислить; + +**Выходные данные**: +- Кортеж (`tuple`) двумерных массивов. Первый элемент кортежа - ортогональные проекции вектора на базис, второй элемент - ортогональные составляющие проекций вектора на базис. В случае, если входная матрица не задает базис, элементы кортежа - `None`. + +*Сторонние эффекты*: +- Если матрица $A$ не является квадратной, необходимо возбудить исключение `ShapeMismatchError`; +- Если количество столбцов матрицы $A$ отлично от количества элементов входного вектора, необходимо возбудить исключение `ShapeMismatchError`. + +*Замечания*: +- Гарантируется, что `vector` - не нулевой вектор. + +## Задача 3. Адаптивная фильтрация + +Фильтрация нужна для того, чтобы отделить полезный сигнал от шума или помех. Адаптивная фильтрация — это более умный способ фильтрации, который используется, когда шум или сигнал меняются со временем. Адаптивный фильтр сам подстраивается под изменения, чтобы всегда эффективно убирать шум или выделять нужный сигнал. + +В данной задаче рассматривается формула адаптивной фильтрации, которая основана на использовании корреляционной матрицы и обучающей выборки для настройки фильтра. Формула имеет вид: + +$$ y = R^{-1} V_s $$ + +где: +- $y$ — выходной сигнал после фильтрации (матрица размером $M \times N$) +- $R^{-1}$ — обратная корреляционная матрица (матрица размером $M \times M$) +- $V_s$ — обрабатываемая выборка сигнала (матрица размером $M \times N$) + +Обратная корреляционная матрица $R^{-1}$ вычисляется следующим образом: + +$$ R^{-1} = (I + V_j A V_j^H)^{-1} = I - V_j (I + V_j^H V_j A)^{-1} V_j^H $$ + +где: +- $I$ — единичная матрица +- $V_j$ — обучающая выборка (матрица комплексных чисел размером $M \times K$) +- $A$ — диагональная матрица комплексных чисел (размером $K \times K$), которая определяет мощность подавления +- $V_j^H$ — матрица, которая получается поэлементным сопряжением матрицы $V_j$ и транспонированием результата (эрмитово сопряженная матрица) + +Напишите функцию, которая вычисляет выходной сигнал $y$ по формуле адаптивной фильтрации, используя предоставленные матрицы $V_s$, $V_j$ и $A$. + +Допишите код функции `adaptive_filter` в файле [task3](../../../solutions/sem02/lesson05/task3.py). + +**Входные данные**: +- `Vs` — матрица обрабатываемой выборки сигнала (тип: `numpy.ndarray`, размерность: $M \times N$) +- `Vj` — матрица обучающей выборки (тип: `numpy.ndarray`, размерность: $M \times K$) +- `diag_A` — диагональ матрицы A (тип: `numpy.ndarray`, размерность: $K$) + +**Выходные данные**: +- `y` — выходной сигнал после фильтрации (тип: `numpy.ndarray`, размерность: $M \times N$) + +*Сторонние эффекты*: +- Если размеры входных массивов не согласованы, необходимо возбудить исключение `ShapeMismatchError`. diff --git a/deprecated_tests/sem02/tests/test_lesson03_tasks.py b/deprecated_tests/sem02/tests/task3/test_lesson03_tasks.py similarity index 100% rename from deprecated_tests/sem02/tests/test_lesson03_tasks.py rename to deprecated_tests/sem02/tests/task3/test_lesson03_tasks.py diff --git a/tests/test_lesson04_tasks.py b/deprecated_tests/sem02/tests/task4/test_lesson04_tasks.py similarity index 78% rename from tests/test_lesson04_tasks.py rename to deprecated_tests/sem02/tests/task4/test_lesson04_tasks.py index 05b170be..37d249b7 100644 --- a/tests/test_lesson04_tasks.py +++ b/deprecated_tests/sem02/tests/task4/test_lesson04_tasks.py @@ -6,7 +6,7 @@ from solutions.sem02.lesson04.task1 import blur_image, pad_image from solutions.sem02.lesson04.task2 import get_dominant_color_info -DATA_PATH = os.path.join("tests", "test_data", "lesson04") +DATA_PATH = os.path.join("test_data", "lesson04") class TestTask1: @@ -44,7 +44,10 @@ class TestTask1: pytest.param( np.array([[1, 2], [3, 4]], dtype=np.uint8), 1, - np.array([[0, 0, 0, 0], [0, 1, 2, 0], [0, 3, 4, 0], [0, 0, 0, 0]], dtype=np.uint8), + np.array( + [[0, 0, 0, 0], [0, 1, 2, 0], [0, 3, 4, 0], [0, 0, 0, 0]], + dtype=np.uint8, + ), id="2d_pad_1", ), pytest.param( @@ -112,18 +115,56 @@ class TestTask1: 2, np.array( [ - [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], - [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], - [[0, 0, 0], [0, 0, 0], [10, 20, 30], [40, 50, 60], [0, 0, 0], [0, 0, 0]], - [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], - [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], + [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], + [ + [0, 0, 0], + [0, 0, 0], + [10, 20, 30], + [40, 50, 60], + [0, 0, 0], + [0, 0, 0], + ], + [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], + [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], ], dtype=np.uint8, ), id="3d_rgb_pad_2", ), pytest.param( - np.zeros((2, 3), dtype=int), 1, np.zeros((4, 5), dtype=int), id="2d_zeros_pad_1" + np.zeros((2, 3), dtype=int), + 1, + np.zeros((4, 5), dtype=int), + id="2d_zeros_pad_1", ), pytest.param( np.arange(4095 * 4095).reshape(4095, 4095) % 256, @@ -182,7 +223,8 @@ def test_pad_size_validate(self): ), 3, np.array( - [[[39, 28, 82], [39, 28, 82]], [[39, 28, 82], [39, 28, 82]]], dtype=np.uint8 + [[[39, 28, 82], [39, 28, 82]], [[39, 28, 82], [39, 28, 82]]], + dtype=np.uint8, ), id="3d_blur_3", ), @@ -262,13 +304,25 @@ class TestTask2: id="chain_within_threshold", ), pytest.param( - np.array([[255, 0, 0]], dtype=np.uint8), 20, [0], 2 / 3, id="marginal_values_1" + np.array([[255, 0, 0]], dtype=np.uint8), + 20, + [0], + 2 / 3, + id="marginal_values_1", ), pytest.param( - np.array([[0, 255, 0]], dtype=np.uint8), 20, [0], 2 / 3, id="marginal_values_2" + np.array([[0, 255, 0]], dtype=np.uint8), + 20, + [0], + 2 / 3, + id="marginal_values_2", ), pytest.param( - np.array([[0, 0, 255]], dtype=np.uint8), 20, [0], 2 / 3, id="marginal_values_3" + np.array([[0, 0, 255]], dtype=np.uint8), + 20, + [0], + 2 / 3, + id="marginal_values_3", ), pytest.param( np.load(os.path.join(DATA_PATH, "test_task2_data1.npy")), @@ -279,8 +333,12 @@ class TestTask2: ), ], ) - def test_get_dominant_color_info(self, image, threshold, expected_color, expected_ratio): - color, ratio_percent = get_dominant_color_info(image.astype(np.uint8), threshold) + def test_get_dominant_color_info( + self, image, threshold, expected_color, expected_ratio + ): + color, ratio_percent = get_dominant_color_info( + image.astype(np.uint8), threshold + ) assert color in expected_color assert (abs(ratio_percent - expected_ratio * 100) < 1e-6) or ( diff --git a/tests/test_data/lesson04/test_task11_data_res.npy b/deprecated_tests/sem02/tests/test_data/lesson04/test_task11_data_res.npy similarity index 100% rename from tests/test_data/lesson04/test_task11_data_res.npy rename to deprecated_tests/sem02/tests/test_data/lesson04/test_task11_data_res.npy diff --git a/tests/test_data/lesson04/test_task12_data_res.npy b/deprecated_tests/sem02/tests/test_data/lesson04/test_task12_data_res.npy similarity index 100% rename from tests/test_data/lesson04/test_task12_data_res.npy rename to deprecated_tests/sem02/tests/test_data/lesson04/test_task12_data_res.npy diff --git a/tests/test_data/lesson04/test_task2_data1.npy b/deprecated_tests/sem02/tests/test_data/lesson04/test_task2_data1.npy similarity index 100% rename from tests/test_data/lesson04/test_task2_data1.npy rename to deprecated_tests/sem02/tests/test_data/lesson04/test_task2_data1.npy diff --git a/solutions/sem02/lesson05/task1.py b/solutions/sem02/lesson05/task1.py new file mode 100644 index 00000000..e9c7c3c5 --- /dev/null +++ b/solutions/sem02/lesson05/task1.py @@ -0,0 +1,12 @@ +import numpy as np + + +class ShapeMismatchError(Exception): + pass + + +def can_satisfy_demand( + costs: np.ndarray, + resource_amounts: np.ndarray, + demand_expected: np.ndarray, +) -> bool: ... diff --git a/solutions/sem02/lesson05/task2.py b/solutions/sem02/lesson05/task2.py new file mode 100644 index 00000000..be1fb9d2 --- /dev/null +++ b/solutions/sem02/lesson05/task2.py @@ -0,0 +1,11 @@ +import numpy as np + + +class ShapeMismatchError(Exception): + pass + + +def get_projections_components( + matrix: np.ndarray, + vector: np.ndarray, +) -> tuple[np.ndarray | None, np.ndarray | None]: ... diff --git a/solutions/sem02/lesson05/task3.py b/solutions/sem02/lesson05/task3.py new file mode 100644 index 00000000..0c66906c --- /dev/null +++ b/solutions/sem02/lesson05/task3.py @@ -0,0 +1,12 @@ +import numpy as np + + +class ShapeMismatchError(Exception): + pass + + +def adaptive_filter( + Vs: np.ndarray, + Vj: np.ndarray, + diag_A: np.ndarray, +) -> np.ndarray: ... diff --git a/tests/test_data/lesson05/Vj_data.npy b/tests/test_data/lesson05/Vj_data.npy new file mode 100644 index 00000000..8b9b5f7a Binary files /dev/null and b/tests/test_data/lesson05/Vj_data.npy differ diff --git a/tests/test_data/lesson05/Vs_data.npy b/tests/test_data/lesson05/Vs_data.npy new file mode 100644 index 00000000..da77bd6d Binary files /dev/null and b/tests/test_data/lesson05/Vs_data.npy differ diff --git a/tests/test_data/lesson05/diag_A_data.npy b/tests/test_data/lesson05/diag_A_data.npy new file mode 100644 index 00000000..ea53de5c Binary files /dev/null and b/tests/test_data/lesson05/diag_A_data.npy differ diff --git a/tests/test_data/lesson05/y_data.npy b/tests/test_data/lesson05/y_data.npy new file mode 100644 index 00000000..65637994 Binary files /dev/null and b/tests/test_data/lesson05/y_data.npy differ diff --git a/tests/test_lesson05_tasks.py b/tests/test_lesson05_tasks.py new file mode 100644 index 00000000..aeb37ebc --- /dev/null +++ b/tests/test_lesson05_tasks.py @@ -0,0 +1,226 @@ +import os + +import numpy as np +import pytest + +from solutions.sem02.lesson05.task1 import ShapeMismatchError as Task1ShapeMismatchError +from solutions.sem02.lesson05.task1 import can_satisfy_demand +from solutions.sem02.lesson05.task2 import ShapeMismatchError as Task2ShapeMismatchError +from solutions.sem02.lesson05.task2 import get_projections_components +from solutions.sem02.lesson05.task3 import ShapeMismatchError as Task3ShapeMismatchError +from solutions.sem02.lesson05.task3 import adaptive_filter + +DATA_PATH = os.path.join("tests", "test_data", "lesson05") + + +class TestTask1: + @pytest.mark.parametrize( + "costs, resource_amounts, demand_expected, expected", + [ + pytest.param( + np.eye(2), + np.array([3.0, 3.0]), + np.array([2, 2]), + True, + id="identity_enough", + ), + pytest.param( + np.eye(2), + np.array([2.0, 2.0]), + np.array([3, 3]), + False, + id="identity_not_enough", + ), + pytest.param( + np.array([[1.0, 2.0], [3.0, 4.0]]), + np.array([10.0, 20.0]), + np.array([2, 3]), + True, + id="general_enough", + ), + pytest.param( + np.array([[1.0, 2.0], [3.0, 4.0]]), + np.array([7.0, 17.0]), + np.array([2, 3]), + False, + id="general_not_enough", + ), + pytest.param( + np.array([[1.0]]), + np.array([5.0]), + np.array([5]), + True, + id="single_resource_product_exact", + ), + pytest.param( + np.array([[1.0, 0.0, 1.0], [0.0, 1.0, 1.0]]), + np.array([3.0, 3.0]), + np.array([1, 1, 1]), + True, + id="non_square_costs_enough", + ), + pytest.param( + np.array([[1.0, 0.0, 1.0], [0.0, 1.0, 1.0]]), + np.array([1.0, 1.0]), + np.array([1, 1, 1]), + False, + id="non_square_costs_not_enough", + ), + pytest.param( + np.eye(3), + np.array([0.0, 0.0, 0.0]), + np.array([0, 0, 0]), + True, + id="zero_demand", + ), + pytest.param( + np.eye(2), + np.full(shape=2, fill_value=3), + np.full(shape=2, fill_value=2), + True, + id="notebook_satisfy", + ), + pytest.param( + np.eye(2), + np.full(shape=2, fill_value=2), + np.full(shape=2, fill_value=3), + False, + id="notebook_not_satisfy", + ), + ], + ) + def test_can_satisfy_demand( + self, costs, resource_amounts, demand_expected, expected + ): + assert can_satisfy_demand(costs, resource_amounts, demand_expected) == expected + + def test_can_satisfy_demand_validate(self): + with pytest.raises(Task1ShapeMismatchError): + can_satisfy_demand( + np.array([[1.0, 2.0, 3.0]]), + np.array([1.0, 2.0]), + np.array([1]), + ) + + with pytest.raises(Task1ShapeMismatchError): + can_satisfy_demand( + np.array([[1.0, 2.0]]), + np.array([1.0]), + np.array([1, 2, 3]), + ) + + +class TestTask2: + @pytest.mark.parametrize( + "matrix, vector, proj_expected, orth_expected", + [ + pytest.param( + np.diag([2.0, 3.0]), + np.array([1.0, 2.0]), + np.array([[1.0, 0.0], [0.0, 2.0]]), + np.array([[0.0, 2.0], [1.0, 0.0]]), + id="diagonal_basis", + ), + pytest.param( + np.array([[1.0, 0.0], [1.0, 1.0]]), + np.array([0.0, 1.0]), + np.array([[0.0, 0.0], [0.5, 0.5]]), + np.array([[0.0, 1.0], [-0.5, 0.5]]), + id="non_orthogonal_basis", + ), + pytest.param( + np.eye(3), + np.array([1.0, 2.0, 3.0]), + np.array([[1.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 3.0]]), + np.array([[0.0, 2.0, 3.0], [1.0, 0.0, 3.0], [1.0, 2.0, 0.0]]), + id="identity_3d", + ), + pytest.param( + np.array([[1.0, 2.0], [2.0, 4.0]]), + np.array([0.0, 1.0]), + None, + None, + id="singular_not_basis", + ), + pytest.param( + np.array([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]), + np.array([1.0, 2.0, 3.0]), + None, + None, + id="rank_deficient_3d", + ), + pytest.param( + np.diag([2, 3]), + np.arange(start=1, stop=3), + np.array([[1, 0], [0, 2]]), + np.array([[0, 2], [1, 0]]), + id="notebook_diagonal", + ), + pytest.param( + np.array([[1, 0], [1, 1]]), + np.array([0, 1]), + np.array([[0, 0], [0.5, 0.5]]), + np.array([[0, 1], [-0.5, 0.5]]), + id="notebook_non_orthogonal", + ), + pytest.param( + np.array([[1, 2], [2, 4]]), + np.array([0, 1]), + None, + None, + id="notebook_singular", + ), + ], + ) + def test_get_projections_components( + self, matrix, vector, proj_expected, orth_expected + ): + projections, orthogonals = get_projections_components(matrix, vector) + + if proj_expected is None: + assert projections is None + assert orthogonals is None + else: + assert np.allclose(projections, proj_expected) + assert np.allclose(orthogonals, orth_expected) + + def test_get_projections_components_validate(self): + with pytest.raises(Task2ShapeMismatchError): + get_projections_components( + np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), + np.array([1.0, 2.0, 3.0]), + ) + + with pytest.raises(Task2ShapeMismatchError): + get_projections_components( + np.array([[1.0, 0.0], [0.0, 1.0]]), + np.array([1.0, 2.0, 3.0]), + ) + + +class TestTask3: + def test_adaptive_filter(self): + diag_A = np.load(os.path.join(DATA_PATH, "diag_A_data.npy")) + Vj = np.load(os.path.join(DATA_PATH, "Vj_data.npy")) + Vs = np.load(os.path.join(DATA_PATH, "Vs_data.npy")) + y_expected = np.load(os.path.join(DATA_PATH, "y_data.npy")) + + y = adaptive_filter(Vs, Vj, diag_A) + + assert y.shape == y_expected.shape + assert np.allclose(y, y_expected) + + def test_adaptive_filter_validate(self): + with pytest.raises(Task3ShapeMismatchError): + adaptive_filter( + np.array([[1.0], [2.0]]), + np.array([[1.0], [2.0], [3.0]]), + np.array([1.0]), + ) + + with pytest.raises(Task3ShapeMismatchError): + adaptive_filter( + np.array([[1.0], [2.0]]), + np.array([[1.0, 2.0], [3.0, 4.0]]), + np.array([1.0, 2.0, 3.0]), + )