diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6f9a0099f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "markdown.validate.enabled": false +} +{ + // Использовать ruff как форматер для Python + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports.ruff": true, + "source.fixAll.ruff": true + } + } +} \ No newline at end of file diff --git a/labyrinth.gif b/labyrinth.gif new file mode 100644 index 000000000..b62c103fd Binary files /dev/null and b/labyrinth.gif differ diff --git a/modulated_signal.gif b/modulated_signal.gif new file mode 100644 index 000000000..ef79f72fc Binary files /dev/null and b/modulated_signal.gif differ diff --git a/solutions/sem01/lesson12/task3.py b/solutions/sem01/lesson12/task3.py index 64c112ccc..58b0986e1 100644 --- a/solutions/sem01/lesson12/task3.py +++ b/solutions/sem01/lesson12/task3.py @@ -1,6 +1,3 @@ -import sys - - class FileOut: def __init__( self, diff --git a/solutions/sem02/lesson03/task1.py b/solutions/sem02/lesson03/task1.py index 2c3fc0b58..bc4040680 100644 --- a/solutions/sem02/lesson03/task1.py +++ b/solutions/sem02/lesson03/task1.py @@ -8,13 +8,27 @@ 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: + # result = abscissa.copy() + # result **= 2 + # result += abscissa *5 + # result += 1 + # return result + return (abscissa**2) * 3 + abscissa * 2 + 1 def get_mutual_l2_distances_vectorized( lhs: np.ndarray, rhs: np.ndarray, -) -> np.ndarray: ... +) -> np.ndarray: + if len(lhs[0]) != len(rhs[0]): + raise ShapeMismatchError + diff_coord = lhs[:, np.newaxis, :] - rhs[np.newaxis, :, :] + summ_coord = np.sum(diff_coord**2, 2) + return summ_coord**0.5 diff --git a/solutions/sem02/lesson03/task2.py b/solutions/sem02/lesson03/task2.py index fc823c1d6..a5d932cde 100644 --- a/solutions/sem02/lesson03/task2.py +++ b/solutions/sem02/lesson03/task2.py @@ -5,15 +5,31 @@ class ShapeMismatchError(Exception): pass -def convert_from_sphere( - distances: np.ndarray, - azimuth: np.ndarray, - inclination: np.ndarray, -) -> tuple[np.ndarray, np.ndarray, np.ndarray]: ... - - 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 + distance = np.sqrt(abscissa**2 + ordinates**2 + applicates**2) + azimuth = np.arctan2(ordinates, abscissa) + + angel_mest = np.zeros(distance.shape) + mask = distance > 0 + angel_mest[mask] = np.arccos(applicates[mask] / distance[mask]) + + return (distance, azimuth, angel_mest) + + +def convert_from_sphere( + distances: np.ndarray, + azimuth: np.ndarray, + inclination: 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) diff --git a/solutions/sem02/lesson03/task3.py b/solutions/sem02/lesson03/task3.py index 477acd0ce..97ac895c8 100644 --- a/solutions/sem02/lesson03/task3.py +++ b/solutions/sem02/lesson03/task3.py @@ -3,4 +3,20 @@ def get_extremum_indices( ordinates: np.ndarray, -) -> tuple[np.ndarray, np.ndarray]: ... +) -> tuple[np.ndarray, np.ndarray]: + if len(ordinates) < 3: + raise ValueError + + prev = ordinates[:-2] + curr = ordinates[1:-1] + next = ordinates[2:] + + min_mask = (curr < prev) & (curr < next) + max_mask = (curr > prev) & (curr > next) + + indices = np.arange(1, len(ordinates) - 1) + + min_index = indices[min_mask] + max_index = indices[max_mask] + + return (min_index, max_index) diff --git a/solutions/sem02/lesson04/task1.py b/solutions/sem02/lesson04/task1.py index 1b5526c1f..14792ff96 100644 --- a/solutions/sem02/lesson04/task1.py +++ b/solutions/sem02/lesson04/task1.py @@ -2,16 +2,73 @@ def pad_image(image: np.ndarray, pad_size: int) -> np.ndarray: - # ваш код - return image + if pad_size < 1: + raise ValueError + if image.ndim == 2: + strok, stolb = image.shape + new_strok = strok + 2 * pad_size + new_stolb = stolb + 2 * pad_size + new = np.zeros((new_strok, new_stolb), dtype=image.dtype) + new[pad_size : pad_size + strok, pad_size : pad_size + stolb] = image + return new + strok, stolb, applicate = image.shape + new_strok = strok + 2 * pad_size + new_stolb = stolb + 2 * pad_size + new = np.zeros((new_strok, new_stolb, applicate), dtype=image.dtype) + new[pad_size : pad_size + strok, pad_size : pad_size + stolb, :] = image + return new def blur_image( image: np.ndarray, kernel_size: int, ) -> np.ndarray: - # ваш код - return image + if kernel_size == 1: + return image + if kernel_size % 2 == 0 or kernel_size < 1: + raise ValueError + + pad = kernel_size // 2 + anticrop = pad_image(image, pad) + + integral = np.cumsum(np.cumsum(anticrop, axis=0), axis=1) + + if image.ndim == 2: + strok, stolb = image.shape + strok_pad, stolb_pad = anticrop.shape + + integral_padded = np.zeros((strok_pad + 1, stolb_pad + 1)) + integral_padded[1:, 1:] = integral + + i = np.arange(strok)[:, None] + j = np.arange(stolb)[None, :] + + total = ( + integral_padded[i + kernel_size, j + kernel_size] + - integral_padded[i, j + kernel_size] + - integral_padded[i + kernel_size, j] + + integral_padded[i, j] + ) + + return (total / (kernel_size * kernel_size)).astype(image.dtype) + + strok, stolb, applicate = image.shape + strok_pad, stolb_pad, _ = anticrop.shape + + integral_padded = np.zeros((strok_pad + 1, stolb_pad + 1, applicate)) + integral_padded[1:, 1:, :] = integral + + i = np.arange(strok)[:, None] + j = np.arange(stolb)[None, :] + + total = ( + integral_padded[i + kernel_size, j + kernel_size, :] + - integral_padded[i, j + kernel_size, :] + - integral_padded[i + kernel_size, j, :] + + integral_padded[i, j, :] + ) + + return (total / (kernel_size * kernel_size)).astype(image.dtype) if __name__ == "__main__": @@ -25,3 +82,4 @@ def blur_image( image_blured = blur_image(image, kernel_size=21) compare_images(image, image_blured) +# Саму идею суммы подсмотрел в интернете,ведь мое первое решение с фором внутри фора вычислялось>' diff --git a/solutions/sem02/lesson04/task2.py b/solutions/sem02/lesson04/task2.py index be9a2288f..2fb0f6e9d 100644 --- a/solutions/sem02/lesson04/task2.py +++ b/solutions/sem02/lesson04/task2.py @@ -2,9 +2,34 @@ def get_dominant_color_info( - image: np.ndarray[np.uint8], + image: np.ndarray, threshold: int = 5, ) -> tuple[np.uint8, float]: - # ваш код + if threshold < 1: + raise ValueError("threshold must be positive") - return 0, 0 + histogram = np.zeros(256, dtype=int) + + flat_image = image.flatten() + for pixel in flat_image: + histogram[pixel] += 1 + + max_sum = 0 + dominant_color = 0 + + colors_with_pixels = np.where(histogram > 0)[0] + + for color in colors_with_pixels: + left_bound = max(0, int(color) - threshold + 1) + right_bound = min(255, int(color) + threshold - 1) + current_sum = np.sum(histogram[left_bound : right_bound + 1]) + + if current_sum > max_sum: + max_sum = current_sum + dominant_color = color + + percentage = (max_sum / image.size) * 100 + return np.uint8(dominant_color), float(percentage) + + +# Я знаю, что это долго но так и не придумал как можно еще больше векторизовать функцию diff --git a/solutions/sem02/lesson05/task1.py b/solutions/sem02/lesson05/task1.py index e9c7c3c56..cb48a94b1 100644 --- a/solutions/sem02/lesson05/task1.py +++ b/solutions/sem02/lesson05/task1.py @@ -9,4 +9,14 @@ def can_satisfy_demand( costs: np.ndarray, resource_amounts: np.ndarray, demand_expected: np.ndarray, -) -> bool: ... +) -> bool: + if costs.ndim != 2 or resource_amounts.ndim != 1 or demand_expected.ndim != 1: + raise ShapeMismatchError() + + M, N = costs.shape + if resource_amounts.shape[0] != M or demand_expected.shape[0] != N: + raise ShapeMismatchError() + + resources_needed = costs @ demand_expected + + return bool(np.all(resources_needed <= resource_amounts)) diff --git a/solutions/sem02/lesson05/task2.py b/solutions/sem02/lesson05/task2.py index be1fb9d2b..b2a2804cb 100644 --- a/solutions/sem02/lesson05/task2.py +++ b/solutions/sem02/lesson05/task2.py @@ -8,4 +8,18 @@ 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.ndim != 2 or matrix.shape[0] != matrix.shape[1]: + raise ShapeMismatchError() + if matrix.shape[1] != vector.shape[0]: + raise ShapeMismatchError() + N = matrix.shape[0] + if np.linalg.matrix_rank(matrix) < N: + return None, None + dots_av = matrix @ vector + dots_vv = np.linalg.norm(matrix, axis=1) ** 2 + scalars = dots_av / dots_vv + projections = scalars[:, np.newaxis] * matrix + orthogonal_components = vector - projections + + return projections, orthogonal_components diff --git a/solutions/sem02/lesson05/task3.py b/solutions/sem02/lesson05/task3.py index 0c66906cb..bc330afa9 100644 --- a/solutions/sem02/lesson05/task3.py +++ b/solutions/sem02/lesson05/task3.py @@ -9,4 +9,17 @@ def adaptive_filter( Vs: np.ndarray, Vj: np.ndarray, diag_A: np.ndarray, -) -> np.ndarray: ... +) -> np.ndarray: + M = Vs.shape[0] + + if Vj.shape[0] != M: + raise ShapeMismatchError() + if diag_A.shape[0] != Vj.shape[1]: + raise ShapeMismatchError() + K = Vj.shape[1] + Vj_H = Vj.conj().T + A = np.diag(diag_A) + I_K = np.eye(K, dtype=complex) + inner = np.linalg.solve(I_K + Vj_H @ Vj @ A, Vj_H @ Vs) + y = Vs - Vj @ inner + return y diff --git a/solutions/sem02/lesson07/task1.py b/solutions/sem02/lesson07/task1.py index 3a505d89b..c0218ab8c 100644 --- a/solutions/sem02/lesson07/task1.py +++ b/solutions/sem02/lesson07/task1.py @@ -3,26 +3,73 @@ import matplotlib.pyplot as plt import numpy as np +plt.style.use("ggplot") + class ShapeMismatchError(Exception): pass +def _plot_dist(ax, data, dtype, vert): + if dtype == "hist": + ax.hist( + data, + bins=50, + color="cornflowerblue", + density=True, + alpha=0.7, + orientation="horizontal" if vert else "vertical", + ) + elif dtype == "box": + ax.boxplot( + data, + vert=vert, + patch_artist=True, + boxprops=dict(facecolor="lightsteelblue"), + medianprops=dict(color="k"), + ) + elif dtype == "violin": + p = ax.violinplot(data, vert=vert, showmedians=True) + for b in p["bodies"]: + b.set_facecolor("cornflowerblue") + b.set_edgecolor("blue") + for k in p: + if k != "bodies": + p[k].set_edgecolor("cornflowerblue") + + def visualize_diagrams( abscissa: np.ndarray, ordinates: np.ndarray, diagram_type: Any, ) -> None: - # ваш код - pass + if abscissa.shape != ordinates.shape: + raise ShapeMismatchError + if diagram_type not in ("hist", "violin", "box"): + raise ValueError + + fig = plt.figure(figsize=(8, 8)) + gs = plt.GridSpec(4, 4, wspace=0.2, hspace=0.2) + + ax1 = fig.add_subplot(gs[:-1, 1:]) + ax2 = fig.add_subplot(gs[:-1, 0], sharey=ax1) + ax3 = fig.add_subplot(gs[-1, 1:], sharex=ax1) + + ax1.scatter(abscissa, ordinates, color="cornflowerblue", alpha=0.7) + ax1.set_title("Диаграмма рассеяния", fontsize=14, color="dimgray") + + _plot_dist(ax3, abscissa, diagram_type, vert=False) + _plot_dist(ax2, ordinates, diagram_type, vert=True) + + ax3.invert_yaxis() + ax2.invert_xaxis() + + plt.savefig("task1.png") if __name__ == "__main__": + np.random.seed(42) mean = [2, 3] cov = [[1, 1], [1, 2]] - space = 0.2 - abscissa, ordinates = np.random.multivariate_normal(mean, cov, size=1000).T - visualize_diagrams(abscissa, ordinates, "hist") - plt.show() diff --git a/solutions/sem02/lesson07/task2.py b/solutions/sem02/lesson07/task2.py index decd607ef..b2217ebca 100644 --- a/solutions/sem02/lesson07/task2.py +++ b/solutions/sem02/lesson07/task2.py @@ -1 +1,79 @@ -# ваш код (используйте функции или классы для решения данной задачи) +import json +import os + +import matplotlib.pyplot as plt +import numpy as np + +plt.style.use("ggplot") + +STAGES = ["I", "II", "III", "IV"] +DATA_PATH = os.path.join(os.path.dirname(__file__), "data", "medic_data.json") + + +def load_data(path): + with open(path, "r") as f: + return json.load(f) + + +def count_stages(vals): + return [vals.count(s) for s in STAGES] + + +def plot_and_save(before, after, path): + x = np.arange(len(STAGES)) + w = 0.35 + + fig, ax = plt.subplots(figsize=(12, 7)) + + ax.bar( + x - w / 2, + before, + w, + label="до", + color="cornflowerblue", + ) + ax.bar( + x + w / 2, + after, + w, + label="после", + color="sandybrown", + ) + + ax.set_title( + "Стадии митральной недостаточности", + fontsize=17, + fontweight="bold", + color="dimgray", + ) + ax.set_ylabel( + "количество пациентов", + fontsize=14, + fontweight="bold", + color="dimgray", + ) + ax.set_xticks(x, labels=STAGES) + ax.tick_params( + axis="x", + labelsize=14, + labelcolor="dimgray", + ) + ax.legend(fontsize=13) + + plt.savefig(path) + + +def main(): + data = load_data(DATA_PATH) + before = count_stages(data["before"]) + after = count_stages(data["after"]) + plot_and_save(before, after, "task2.png") + + +if __name__ == "__main__": + main() +# Я нашел в интернете модуль os, надеюсь его можно было использовать +# Иначе на другом устройстве не работало бы, если бы я оставил путь +# для своей файловой системы +# Судя по графику имплан эффективен, ведь после его установки количество +# пациентов с высокой стадией уменьшилось (а с малой стадией увеличилось) diff --git a/solutions/sem02/lesson08/task1.py b/solutions/sem02/lesson08/task1.py index 89f88572f..5e85cbd83 100644 --- a/solutions/sem02/lesson08/task1.py +++ b/solutions/sem02/lesson08/task1.py @@ -1,34 +1,64 @@ -from functools import partial - import matplotlib.pyplot as plt import numpy as np - from IPython.display import HTML from matplotlib.animation import FuncAnimation def create_modulation_animation( - modulation, - fc, - num_frames, - plot_duration, - time_step=0.001, - animation_step=0.01, - save_path="" + modulation, fc, num_frames, plot_duration, time_step=0.001, animation_step=0.01, save_path="" ) -> FuncAnimation: - # ваш код - return FuncAnimation() + def signal(t, modulation, fc): + base_signal = np.sin(2 * np.pi * fc * t) + if modulation is None: + return base_signal + return modulation(t) * base_signal + + figure, axis = plt.subplots(figsize=(12, 6)) + num_points = int(plot_duration / time_step) + 1 + time_segment = np.linspace(0, plot_duration, num_points) + num_segment = signal(time_segment, modulation, fc) + line, *_ = axis.plot(time_segment, num_segment, c="coral") + result_duration = (num_frames - 1) * animation_step + plot_duration + result_num_points = int(result_duration / time_step) + 1 + result_time = np.linspace(0, result_duration, result_num_points) + total_signal = signal(result_time, modulation, fc) + axis.set_xlim(0, plot_duration) + ymax = 1.2 * np.max(np.abs(total_signal)) + axis.set_ylim(-ymax, ymax) + + def update(frame_id): + start = frame_id * animation_step + end = start + plot_duration + start_index = int(start / time_step) + end_index = start_index + num_points + time_minisegment = result_time[start_index:end_index] + signal_minisegment = total_signal[start_index:end_index] + line.set_data(time_minisegment, signal_minisegment) + axis.set_xlim(start, end) + return (line,) + + animation = FuncAnimation( + figure, + update, + frames=num_frames, + interval=50, + blit=False, + ) + if save_path: + animation.save(save_path, writer="pillow", fps=24) + return animation if __name__ == "__main__": + def modulation_function(t): - return np.cos(t * 6) + return np.cos(t * 6) - num_frames = 100 - plot_duration = np.pi / 2 - time_step = 0.001 - animation_step = np.pi / 200 - fc = 50 + num_frames = 100 + plot_duration = np.pi / 2 + time_step = 0.001 + animation_step = np.pi / 200 + fc = 50 save_path_with_modulation = "modulated_signal.gif" animation = create_modulation_animation( @@ -38,6 +68,6 @@ def modulation_function(t): plot_duration=plot_duration, time_step=time_step, animation_step=animation_step, - save_path=save_path_with_modulation + save_path=save_path_with_modulation, ) - HTML(animation.to_jshtml()) \ No newline at end of file + HTML(animation.to_jshtml()) diff --git a/solutions/sem02/lesson08/task2.py b/solutions/sem02/lesson08/task2.py index b677c0702..07dcaa30c 100644 --- a/solutions/sem02/lesson08/task2.py +++ b/solutions/sem02/lesson08/task2.py @@ -1,53 +1,141 @@ -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 animate_wave_algorithm( - maze: np.ndarray, - start: tuple[int, int], - end: tuple[int, int], - save_path: str = "" -) -> FuncAnimation: - # ваш код - return FuncAnimation() + 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 -if __name__ == "__main__": - # Пример 1 - maze = np.array([ - [0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 0], - [1, 1, 0, 1, 0, 1, 0], - [0, 0, 1, 1, 0, 1, 0], - [0, 0, 0, 0, 0, 1, 0], - [1, 1, 1, 1, 1, 1, 0], - [0, 0, 0, 0, 0, 0, 0], - ]) + distances, history = solution_wave(maze, start, end) + path_found = distances[end] != -1 - start = (2, 0) - end = (5, 0) - save_path = "labyrinth.gif" # Укажите путь для сохранения анимации + if path_found: + path = path_recovery(distances, start, end) + else: + path = [] + print("Путь не найден") - 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) + path_marker = distances.max() + 2 + 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) - # можете поменять, если захотите запустить из других точек + 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] + + 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([]) + + animation = FuncAnimation( + figure, + update, + frames=len(frames), + interval=200, + blit=True, + ) + + if save_path: + animation.save(save_path, writer="pillow", fps=5) + + return animation + + +if __name__ == "__main__": + maze = np.array( + [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 0], + [1, 1, 0, 1, 0, 1, 0], + [0, 0, 1, 1, 0, 1, 0], + [0, 0, 0, 0, 0, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0], + ] + ) 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()) - - \ No newline at end of file + save_path = "labyrinth.gif" + animation = animate_wave_algorithm(maze, start, end, save_path) + HTML(animation.to_jshtml())