diff --git a/solutions/sem02/lesson03/task1.py b/solutions/sem02/lesson03/task1.py index 2c3fc0b58..a44df2051 100644 --- a/solutions/sem02/lesson03/task1.py +++ b/solutions/sem02/lesson03/task1.py @@ -8,13 +8,23 @@ class ShapeMismatchError(Exception): def sum_arrays_vectorized( lhs: np.ndarray, rhs: np.ndarray, -) -> np.ndarray: ... +) -> np.ndarray: + + if lhs.shape != rhs.shape: + raise ShapeMismatchError + return lhs + rhs -def compute_poly_vectorized(abscissa: np.ndarray) -> np.ndarray: ... +def compute_poly_vectorized(abscissa: np.ndarray) -> np.ndarray: + return 3 * (abscissa ** 2) + 2 * abscissa + 1 def get_mutual_l2_distances_vectorized( lhs: np.ndarray, rhs: np.ndarray, -) -> np.ndarray: ... +) -> np.ndarray: + + if lhs.shape[1] != rhs.shape[1]: + raise ShapeMismatchError + + return np.sqrt(((lhs[:, None, :] - rhs[None, :, :]) ** 2).sum(axis=2)) diff --git a/solutions/sem02/lesson03/task2.py b/solutions/sem02/lesson03/task2.py index fc823c1d6..5ac56687a 100644 --- a/solutions/sem02/lesson03/task2.py +++ b/solutions/sem02/lesson03/task2.py @@ -9,11 +9,33 @@ def convert_from_sphere( distances: np.ndarray, azimuth: np.ndarray, inclination: np.ndarray, -) -> tuple[np.ndarray, np.ndarray, np.ndarray]: ... +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + + if not (distances.shape == azimuth.shape == inclination.shape): + raise ShapeMismatchError + + abscissa = distances * np.sin(inclination) * np.cos(azimuth) + ordinates = distances * np.sin(inclination) * np.sin(azimuth) + applicates = distances * np.cos(inclination) + + return abscissa, ordinates, applicates def convert_to_sphere( abscissa: np.ndarray, ordinates: np.ndarray, applicates: np.ndarray, -) -> tuple[np.ndarray, np.ndarray, np.ndarray]: ... +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + + if not (abscissa.shape == ordinates.shape == applicates.shape): + raise ShapeMismatchError + + r = np.sqrt(abscissa**2 + ordinates**2 + applicates**2) + azimuth = np.arctan2(ordinates, abscissa) + + inclination = np.zeros_like(r) + + mask = r != 0 + inclination[mask] = np.arccos(applicates[mask] / r[mask]) + + return r, azimuth, inclination diff --git a/solutions/sem02/lesson03/task3.py b/solutions/sem02/lesson03/task3.py index 477acd0ce..06886ce09 100644 --- a/solutions/sem02/lesson03/task3.py +++ b/solutions/sem02/lesson03/task3.py @@ -3,4 +3,19 @@ def get_extremum_indices( ordinates: np.ndarray, -) -> tuple[np.ndarray, np.ndarray]: ... +) -> tuple[np.ndarray, np.ndarray]: + + if len(ordinates) < 3: + raise ValueError + + left_compare = ordinates[1:-1] > ordinates[:-2] + right_compare = ordinates[1:-1] > ordinates[2:] + + max = left_compare & right_compare + + min = (~left_compare) & (~right_compare) + + max_indices = np.where(max)[0] + 1 + min_indices = np.where(min)[0] + 1 + + return min_indices, max_indices diff --git a/solutions/sem02/lesson04/task1.py b/solutions/sem02/lesson04/task1.py index 1b5526c1f..994cdf1d2 100644 --- a/solutions/sem02/lesson04/task1.py +++ b/solutions/sem02/lesson04/task1.py @@ -2,16 +2,62 @@ def pad_image(image: np.ndarray, pad_size: int) -> np.ndarray: - # ваш код - return image + + if pad_size < 1: + raise ValueError + + if image.ndim == 2: + + h, w = image.shape + + padded = np.zeros((h + 2*pad_size, w + 2*pad_size), dtype=image.dtype) + padded[pad_size:pad_size+h, pad_size:pad_size+w] = image + + elif image.ndim == 3: + + h, w, c = image.shape + + padded = np.zeros((h + 2*pad_size, w + 2*pad_size, c), dtype=image.dtype) + padded[pad_size:pad_size+h, pad_size:pad_size+w, :] = image + + else: + raise ValueError + + return padded def blur_image( image: np.ndarray, kernel_size: int, ) -> np.ndarray: - # ваш код - return image + + if kernel_size < 1 or kernel_size % 2 == 0: + raise ValueError + + pad = kernel_size // 2 + k = kernel_size + + padded = pad_image(image.astype(np.float64), pad) + integral = np.cumsum(np.cumsum(padded, axis=0), axis=1) + + if image.ndim == 2: + h, w = image.shape + + sums = (integral[k:, k:] - integral[:-k, k:] - + integral[k:, :-k] + integral[:-k, :-k]) + + result = sums[:h, :w] / (k * k) + + else: + h, w, c = image.shape + result = np.zeros((h, w, c), dtype=np.float64) + + for ch in range(c): + sums = (integral[k:, k:, ch] - integral[:-k, k:, ch] - + integral[k:, :-k, ch] + integral[:-k, :-k, ch]) + result[:, :, ch] = sums[:h, :w] / (k * k) + + return np.clip(result, 0, 255).astype(np.uint8) if __name__ == "__main__": diff --git a/solutions/sem02/lesson04/task2.py b/solutions/sem02/lesson04/task2.py index be9a2288f..2b1f2277a 100644 --- a/solutions/sem02/lesson04/task2.py +++ b/solutions/sem02/lesson04/task2.py @@ -5,6 +5,33 @@ def get_dominant_color_info( image: np.ndarray[np.uint8], threshold: int = 5, ) -> tuple[np.uint8, float]: - # ваш код + + if threshold < 1: + raise ValueError + + pixels = image.flatten() + pixels.sort() - return 0, 0 + current_group_start = 0 + current_group_size = 1 + best_group_start = 0 + best_group_size = 1 + + for i in range(1, len(pixels)): + if pixels[i] - pixels[current_group_start] < threshold: + current_group_size += 1 + else: + if current_group_size > best_group_size: + best_group_size = current_group_size + best_group_start = current_group_start + current_group_start = i + current_group_size = 1 + + if current_group_size > best_group_size: + best_group_size = current_group_size + best_group_start = current_group_start + + total_pixels = len(pixels) + percentage = (best_group_size / total_pixels) * 100 + + return (pixels[best_group_start], percentage) \ No newline at end of file diff --git a/solutions/sem02/lesson05/task1.py b/solutions/sem02/lesson05/task1.py index e9c7c3c56..f96a11160 100644 --- a/solutions/sem02/lesson05/task1.py +++ b/solutions/sem02/lesson05/task1.py @@ -9,4 +9,10 @@ def can_satisfy_demand( costs: np.ndarray, resource_amounts: np.ndarray, demand_expected: np.ndarray, -) -> bool: ... +) -> bool: + M, N = costs.shape + + if len(resource_amounts) != M or len(demand_expected) != N: + raise ShapeMismatchError + + return bool(np.all(costs @ demand_expected <= resource_amounts)) \ No newline at end of file diff --git a/solutions/sem02/lesson05/task2.py b/solutions/sem02/lesson05/task2.py index be1fb9d2b..8dfd859de 100644 --- a/solutions/sem02/lesson05/task2.py +++ b/solutions/sem02/lesson05/task2.py @@ -8,4 +8,22 @@ class ShapeMismatchError(Exception): def get_projections_components( matrix: np.ndarray, vector: np.ndarray, -) -> tuple[np.ndarray | None, np.ndarray | None]: ... +) -> tuple[np.ndarray | None, np.ndarray | None]: + if matrix.shape[0] != matrix.shape[1]: + raise ShapeMismatchError + + n = matrix.shape[0] + if len(vector) != n: + raise ShapeMismatchError + + if abs(np.linalg.det(matrix)) < 1e-10: + return None, None + + coeffs = np.linalg.solve(matrix.T, vector) + + projections = np.array([coeffs[i] * matrix[i] for i in range(n)]) + + total_projection = np.sum(projections, axis=0) + components = vector - total_projection + + return projections, components diff --git a/solutions/sem02/lesson05/task3.py b/solutions/sem02/lesson05/task3.py index 0c66906cb..48062f307 100644 --- a/solutions/sem02/lesson05/task3.py +++ b/solutions/sem02/lesson05/task3.py @@ -9,4 +9,25 @@ def adaptive_filter( Vs: np.ndarray, Vj: np.ndarray, diag_A: np.ndarray, -) -> np.ndarray: ... +) -> np.ndarray: + M, N = Vs.shape + M2, K = Vj.shape + + if M != M2 or len(diag_A) != K: + raise ShapeMismatchError + + Vj_H = Vj.conj().T + + A = np.diag(diag_A.astype(complex)) + + I_K = np.eye(K, dtype=complex) + matrix_to_invert = I_K + Vj_H @ Vj @ A + + inv_matrix = np.linalg.inv(matrix_to_invert) + + I_M = np.eye(M, dtype=complex) + R_inv = I_M - Vj @ inv_matrix @ Vj_H + + y = R_inv @ Vs + + return y \ No newline at end of file diff --git a/solutions/sem02/lesson07/task1.py b/solutions/sem02/lesson07/task1.py index 3a505d89b..ae54a13ce 100644 --- a/solutions/sem02/lesson07/task1.py +++ b/solutions/sem02/lesson07/task1.py @@ -13,8 +13,87 @@ def visualize_diagrams( ordinates: np.ndarray, diagram_type: Any, ) -> None: - # ваш код - pass + + if len(abscissa) != len(ordinates): + raise ShapeMismatchError( + f"Длины массивов должны совпадать: abscissa={len(abscissa)}, ordinates={len(ordinates)}" + ) + + # проверка допустимости diagram_type + valid_types = ["hist", "violin", "box"] + if diagram_type not in valid_types: + raise ValueError( + f"diagram_type должен быть одним из: {valid_types}, получено '{diagram_type}'" + ) + + # Создаем фигуру с сеткой 2x2 + plt.figure(figsize=(10, 10)) + + # Основная диаграмма рассеяния (нижний левый) + scatter_ax = plt.subplot(2, 2, 3) + scatter_ax.scatter( + abscissa, ordinates, alpha=0.6, s=20, c="steelblue", edgecolors="white", linewidth=0.5 + ) + scatter_ax.set_xlabel("X") + scatter_ax.set_ylabel("Y") + scatter_ax.set_title("Диаграмма рассеяния") + scatter_ax.grid(True, alpha=0.3) + + # Распределение по оси X (верхний левый) + x_ax = plt.subplot(2, 2, 1) + + if diagram_type == "hist": + x_ax.hist(abscissa, bins=30, color="steelblue", alpha=0.7, edgecolor="white") + x_ax.set_title("Распределение по X (гистограмма)") + + elif diagram_type == "violin": + x_ax.violinplot(abscissa, positions=[0], showmeans=True, showmedians=True) + x_ax.set_title("Распределение по X (скрипичная диаграмма)") + + elif diagram_type == "box": + x_ax.boxplot(abscissa, vert=True, positions=[0]) + x_ax.set_title("Распределение по X (ящик с усами)") + + x_ax.set_xlabel("X") + x_ax.set_ylabel("Плотность") + x_ax.grid(True, alpha=0.3) + + # Распределение по оси Y (нижний правый) + y_ax = plt.subplot(2, 2, 4) + + if diagram_type == "hist": + y_ax.hist( + ordinates, + bins=30, + color="lightcoral", + alpha=0.7, + edgecolor="white", + orientation="horizontal", + ) + y_ax.set_title("Распределение по Y (гистограмма)") + + elif diagram_type == "violin": + y_ax.violinplot(ordinates, positions=[0], vert=False, showmeans=True, showmedians=True) + y_ax.set_title("Распределение по Y (скрипичная диаграмма)") + + elif diagram_type == "box": + y_ax.boxplot(ordinates, vert=False, positions=[0]) + y_ax.set_title("Распределение по Y (ящик с усами)") + + y_ax.set_xlabel("Плотность") + y_ax.set_ylabel("Y") + y_ax.grid(True, alpha=0.3) + + # Убираем пустой угол (верхний правый) + plt.subplot(2, 2, 2).axis("off") + + # Общий заголовок + plt.suptitle( + f"Визуализация данных\nТип распределения: {diagram_type}", fontsize=14, fontweight="bold" + ) + + # Автоматическая подгонка + plt.tight_layout() if __name__ == "__main__": diff --git a/solutions/sem02/lesson07/task2.py b/solutions/sem02/lesson07/task2.py index decd607ef..c293220ff 100644 --- a/solutions/sem02/lesson07/task2.py +++ b/solutions/sem02/lesson07/task2.py @@ -1 +1,105 @@ -# ваш код (используйте функции или классы для решения данной задачи) +import json + +import matplotlib.pyplot as plt +import numpy as np + + +def visualize_diagrams(filepath: str) -> None: + with open(filepath, "r", encoding="utf-8") as f: + data = json.load(f) + + roman_to_number = {"I": 1, "II": 2, "III": 3, "IV": 4} + + before_numbers = [roman_to_number[x] for x in data["before"]] + after_numbers = [roman_to_number[x] for x in data["after"]] + + before_counts = [before_numbers.count(i) for i in range(1, 5)] + after_counts = [after_numbers.count(i) for i in range(1, 5)] + + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5)) + + x = np.arange(4) + categories = ["I", "II", "III", "IV"] + + ax1.bar(x, before_counts, color="steelblue", edgecolor="white", linewidth=2) + ax1.set_title("До установки импланта", fontsize=14, fontweight="bold") + ax1.set_xlabel("Степень митральной недостаточности", fontsize=11) + ax1.set_ylabel("Количество пациентов", fontsize=11) + ax1.set_xticks(x) + ax1.set_xticklabels(categories, fontsize=10) + ax1.grid(axis="y", alpha=0.3, linestyle="--") + + for bar, count in zip(ax1.bar(x, before_counts, color="steelblue"), before_counts): + ax1.text( + bar.get_x() + bar.get_width() / 2.0, + count + 0.5, + str(count), + ha="center", + va="bottom", + fontweight="bold", + ) + + ax2.bar(x, after_counts, color="lightcoral", edgecolor="white", linewidth=2) + ax2.set_title("После установки импланта", fontsize=14, fontweight="bold") + ax2.set_xlabel("Степень митральной недостаточности", fontsize=11) + ax2.set_ylabel("Количество пациентов", fontsize=11) + ax2.set_xticks(x) + ax2.set_xticklabels(categories, fontsize=10) + ax2.grid(axis="y", alpha=0.3, linestyle="--") + + for bar, count in zip(ax2.bar(x, after_counts, color="lightcoral"), after_counts): + ax2.text( + bar.get_x() + bar.get_width() / 2.0, + count + 0.5, + str(count), + ha="center", + va="bottom", + fontweight="bold", + ) + + plt.suptitle( + "Распределение пациентов по степени митральной недостаточности\n" \ + "до и после установки кардио-импланта", + fontsize=16, + fontweight="bold", + ) + + plt.tight_layout() + plt.savefig("mitral_insufficiency_distribution.png", dpi=150, bbox_inches="tight") + plt.show() + + total_before = sum(before_counts) + total_after = sum(after_counts) + + print("\n" + "=" * 60) + print("АНАЛИЗ ЭФФЕКТИВНОСТИ ИМПЛАНТА") + print("=" * 60) + + for i, grade in enumerate(categories): + before_pct = (before_counts[i] / total_before) * 100 + after_pct = (after_counts[i] / total_after) * 100 + change = after_pct - before_pct + arrow = "↑" if change > 0 else "↓" if change < 0 else "→" + print( + f"{grade} степень: {before_pct:5.1f}% → {after_pct:5.1f}% ({arrow} {abs(change):.1f}%)" + ) + + severe_before = before_counts[2] + before_counts[3] + severe_after = after_counts[2] + after_counts[3] + + print("\n" + "=" * 60) + print("ВЫВОД:") + print("=" * 60) + + if severe_before > 0: + reduction = ((severe_before - severe_after) / severe_before) * 100 + if reduction > 30: + print(f"✅ Имплант эффективен! Снижение тяжелых форм на {reduction:.1f}%") + elif reduction > 10: + print(f"⚠️ Имплант умеренно эффективен (снижение на {reduction:.1f}%)") + else: + print(f"❌ Имплант малоэффективен (снижение на {reduction:.1f}%)") + + +if __name__ == "__main__": + visualize_diagrams("medic_data.json") diff --git a/solutions/sem02/lesson08/modulated_signal.gif b/solutions/sem02/lesson08/modulated_signal.gif new file mode 100644 index 000000000..17c74776f Binary files /dev/null and b/solutions/sem02/lesson08/modulated_signal.gif differ diff --git a/solutions/sem02/lesson08/task1.py b/solutions/sem02/lesson08/task1.py index 89f88572f..a68a6e09f 100644 --- a/solutions/sem02/lesson08/task1.py +++ b/solutions/sem02/lesson08/task1.py @@ -1,8 +1,6 @@ -from functools import partial import matplotlib.pyplot as plt import numpy as np - from IPython.display import HTML from matplotlib.animation import FuncAnimation @@ -16,8 +14,49 @@ def create_modulation_animation( animation_step=0.01, save_path="" ) -> FuncAnimation: - # ваш код - return FuncAnimation() + def signal(t): + carrier = np.sin(2 * np.pi * fc * t) + return carrier if modulation is None else modulation(t) * carrier + + fig, ax = plt.subplots(figsize=(12, 6)) + num_points = int(plot_duration / time_step) + 1 + time_segment = np.linspace(0, plot_duration, num_points) + + line, = ax.plot(time_segment, signal(time_segment), 'b-', lw=2) + + if modulation is not None: + envelope, = ax.plot(time_segment, modulation(time_segment), 'r--', alpha=0.7) + + total_duration = (num_frames - 1) * animation_step + plot_duration + total_points = int(total_duration / time_step) + 1 + total_time = np.linspace(0, total_duration, total_points) + total_signal = signal(total_time) + + ax.set_xlim(0, plot_duration) + ax.set_ylim(-1.2 * np.max(np.abs(total_signal)), 1.2 * np.max(np.abs(total_signal))) + ax.grid(True, alpha=0.3) + ax.set_xlabel('Time (s)') + ax.set_ylabel('Amplitude') + ax.set_title('Modulated Signal') + + def update(frame): + start = frame * animation_step + idx = int(start / time_step) + line.set_data(total_time[idx:idx+num_points], total_signal[idx:idx+num_points]) + ax.set_xlim(start, start + plot_duration) + if modulation is not None: + envelope.set_data( + total_time[idx:idx+num_points], + modulation(total_time[idx:idx+num_points]) + ) + return (line,) + + anim = FuncAnimation(fig, update, frames=num_frames, interval=50) + + if save_path: + anim.save(save_path, writer='pillow') + + return anim if __name__ == "__main__": diff --git a/solutions/sem02/lesson08/task2.py b/solutions/sem02/lesson08/task2.py index b677c0702..e6032ebcf 100644 --- a/solutions/sem02/lesson08/task2.py +++ b/solutions/sem02/lesson08/task2.py @@ -1,25 +1,136 @@ -from functools import partial - import matplotlib.pyplot as plt import numpy as np - from IPython.display import HTML from matplotlib.animation import FuncAnimation +def animate_wave_algorithm( + maze: np.ndarray, + start: tuple[int, int], + end: tuple[int, int], + save_path: str = "", +) -> FuncAnimation: + + def solution_wave(maze, start, end): + height, width = maze.shape + distances = np.full(maze.shape, fill_value=-1, dtype=int) + distances[start] = 0 + history = [distances.copy()] + cur_wave = [start] + moves = [(-1, 0), (1, 0), (0, -1), (0, 1)] + + while cur_wave: + next_wave = [] + for y, x in cur_wave: + cur_value = distances[y, x] + for move_y, move_x in moves: + new_y = y + move_y + new_x = x + move_x + if not (0 <= new_y < height and 0 <= new_x < width): + continue + if maze[new_y, new_x] != 1: + continue + if distances[new_y, new_x] != -1: + continue + distances[new_y, new_x] = cur_value + 1 + next_wave.append((new_y, new_x)) + history.append(distances.copy()) + if distances[end] != -1: + break + cur_wave = next_wave + return distances, history + def path_recovery(distances, start, end): + height, width = distances.shape + moves = [(-1, 0), (1, 0), (0, -1), (0, 1)] + path = [end] + cur_cell = end + while cur_cell != start: + cur_value = distances[cur_cell] + for move_y, move_x in moves: + new_y = cur_cell[0] + move_y + new_x = cur_cell[1] + move_x + if not (0 <= new_y < height and 0 <= new_x < width): + continue + if distances[new_y, new_x] == cur_value - 1: + cur_cell = (new_y, new_x) + path.append(cur_cell) + break + path.reverse() + return path + + def build_frames(history, path, path_marker): + frames = list(history) + if path: + state = history[-1].copy() + for cell in path: + state[cell] = path_marker + frames.append(state.copy()) + return frames + + # алгос + distances, history = solution_wave(maze, start, end) + path_found = distances[end] != -1 + + if path_found: + path = path_recovery(distances, start, end) + else: + path = [] + print("Путь не найден") + + path_marker = distances.max() + 2 if path_found else 999 + frames = build_frames(history, path, path_marker) + + # графика + figure, axis = plt.subplots(figsize=(8, 8)) + height, width = maze.shape + + axis.imshow(maze, cmap="gray", alpha=0.1) + + + axis.set_xticks(np.arange(width + 1) - 0.5, minor=True) + axis.set_yticks(np.arange(height + 1) - 0.5, minor=True) + axis.grid(which="minor", color="black", linestyle="-", linewidth=0.5) + axis.set_xticks([]) + axis.set_yticks([]) + + texts = [ + [axis.text(x, y, "", ha="center", va="center") for x in range(width)] + for y in range(height) + ] + + # обновление кадра + def update(frame_id): + curr_frame = frames[frame_id] + for y in range(height): + for x in range(width): + val = curr_frame[y, x] + if val == -1: + texts[y][x].set_text("") + elif val == path_marker: + texts[y][x].set_text("*") + texts[y][x].set_color("red") + else: + texts[y][x].set_text(str(val)) + texts[y][x].set_color("black") + return [t for row in texts for t in row] + + # анимация + animation = FuncAnimation( + figure, + update, + frames=len(frames), + interval=200, + blit=True, + ) + + if save_path: + animation.save(save_path, writer="pillow", fps=5) + + return animation -def animate_wave_algorithm( - maze: np.ndarray, - start: tuple[int, int], - end: tuple[int, int], - save_path: str = "" -) -> FuncAnimation: - # ваш код - return FuncAnimation() if __name__ == "__main__": - # Пример 1 maze = np.array([ [0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 0], @@ -29,25 +140,10 @@ def animate_wave_algorithm( [1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0, 0], ]) - - start = (2, 0) - end = (5, 0) - save_path = "labyrinth.gif" # Укажите путь для сохранения анимации - - animation = animate_wave_algorithm(maze, start, end, save_path) - HTML(animation.to_jshtml()) - - # Пример 2 - maze_path = "./data/maze.npy" - loaded_maze = np.load(maze_path) - - # можете поменять, если захотите запустить из других точек start = (2, 0) end = (5, 0) - loaded_save_path = "loaded_labyrinth.gif" - - loaded_animation = animate_wave_algorithm(loaded_maze, start, end, loaded_save_path) - HTML(loaded_animation.to_jshtml()) + save_path = "labyrinth.gif" - \ No newline at end of file + animation = animate_wave_algorithm(maze, start, end, save_path) + HTML(animation.to_jshtml()) \ No newline at end of file