diff --git "a/\320\2403213/vlasov_356550/lab1/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2401 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" "b/\320\2403213/vlasov_356550/lab1/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2401 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" new file mode 100644 index 0000000..63b4011 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab1/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2401 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" differ diff --git "a/\320\2403213/vlasov_356550/lab1/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2401 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" "b/\320\2403213/vlasov_356550/lab1/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2401 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" new file mode 100644 index 0000000..dce1847 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab1/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2401 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" differ diff --git "a/\320\2403213/vlasov_356550/lab1/src/gauss_method.py" "b/\320\2403213/vlasov_356550/lab1/src/gauss_method.py" new file mode 100644 index 0000000..f8034cf --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab1/src/gauss_method.py" @@ -0,0 +1,73 @@ +import copy + + +class GaussMethod: + def __init__(self, matrix_a, vector_b): + self.a_original = copy.deepcopy(matrix_a) # Исходная матрица A + self.b_original = copy.deepcopy(vector_b) # Исходный вектор B + self.a = copy.deepcopy(matrix_a) # Рабочая матрица для вычислений + self.b = copy.deepcopy(vector_b) # Рабочий вектор правой части + self.n = len(matrix_a) # Размерность матрицы + self.det = 1.0 # Инициализация определителя + self.swap_counter = 0 + + + def forward_elimination(self): + for i in range(self.n): + diagonal_element = self.a[i][i] + # Проверяем, не равен ли диагональный элемент нулю (с учетом погрешности) + if abs(diagonal_element) < 1e-10: + for k in range(i+1, self.n): + if abs(self.a[k][i]) > 1e-10: + self.a[i], self.a[k] = self.a[k], self.a[i] + self.b[i], self.b[k] = self.b[k], self.b[i] + self.swap_counter += 1 + self.det *= -1 + diagonal_element = self.a[i][i] + + break + else: + raise ValueError("матрица вырожденная") + + # Умножаем определитель на текущий диагональный элемент + self.det *= diagonal_element + + # Устраняем элементы под диагональю в текущем столбце + for j in range(i + 1, self.n): + m = self.a[j][i] / diagonal_element # Коэффициент для обнуления элемента + + # Вручную вычитаем строку i, умноженную на m + for k in range(self.n): + self.a[j][k] -= m * self.a[i][k] + + self.b[j] -= m * self.b[i] # Преобразуем правую часть + + + def back_substitution(self): + x = [0.0] * self.n # Вектор решения + + # Идем снизу вверх по треугольной матрице + for i in range(self.n - 1, -1, -1): + + # Вычисляем сумму уже найденных неизвестных вручную + s = 0.0 + for k in range(i + 1, self.n): + s += self.a[i][k] * x[k] + + # Находим текущую неизвестную + x[i] = (self.b[i] - s) / self.a[i][i] + + return x + + + def solve(self): + self.forward_elimination() # Приводим к треугольному виду + return self.back_substitution() # Находим решение + + + def get_triangular_matrix(self): + return self.a, self.b + + + def get_determinant(self): + return self.det diff --git "a/\320\2403213/vlasov_356550/lab1/src/input_handler.py" "b/\320\2403213/vlasov_356550/lab1/src/input_handler.py" new file mode 100644 index 0000000..7ba5a10 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab1/src/input_handler.py" @@ -0,0 +1,85 @@ +import numpy as np + + +class InputHandler: + @staticmethod + def generate_random_matrix(n): + matrix_a = np.random.uniform(-10, 10, (n, n)).tolist() + vector_b = np.random.uniform(-10, 10, n).tolist() + return matrix_a, vector_b + + + @staticmethod + def _input_from_keyboard(): + try: + n = int(input("Введите размерность матрицы n (n <= 20): ")) + if n <= 0 or n > 20: + raise ValueError("Размерность должна быть от 1 до 20!") + + print("Введите матрицу A (по строкам, числа через пробел):") + + matrix_a = [] + for _ in range(n): + row = list(map(float, input().replace(',', '.').split())) + + if len(row) != n: + raise ValueError("Некорректное количество элементов в строке!") + + matrix_a.append(row) + + print("Введите вектор B (по одному числу в строке):") + vector_b = [float(input().replace(',', '.')) for _ in range(n)] + + return matrix_a, vector_b, False + except ValueError as e: + raise ValueError(f"Ошибка ввода: {e}!") + + + @staticmethod + def _input_from_file(): + print("Содержимое файла должно быть в следующем формате: первая строка - n (размерность), далее n строк матрицы A (числа через пробел), затем n строк вектора B (по одному числу).") + print("Пример:\n2\n2.0 3.0\n1.0 1.0\n5.0\n2.0") + + try: + filename = input("Введите имя файла: ") + with open(filename, 'r') as f: + n = int(f.readline().strip()) + if n <= 0 or n > 20: + raise ValueError("Размерность должна быть от 1 до 20!") + + matrix_a = [list(map(float, f.readline().replace(',', '.').split())) for _ in range(n)] + vector_b = [float(f.readline().replace(',', '.')) for _ in range(n)] + + if any(len(row) != n for row in matrix_a): + raise ValueError("Некорректное количество элементов в строке матрицы!") + + return matrix_a, vector_b, False + except FileNotFoundError: + raise ValueError("Файл не найден!") + except ValueError as e: + raise ValueError(f"Ошибка чтения файла: {e}!") + + + @staticmethod + def _input_random(): + try: + n = int(input("Введите размерность случайной матрицы n (n <= 20): ")) + if n <= 0 or n > 20: + raise ValueError("Размерность должна быть от 1 до 20!") + + matrix_a, vector_b = InputHandler.generate_random_matrix(n) + + return matrix_a, vector_b, True + except ValueError as e: + raise ValueError(f"Ошибка генерации: {e}!") + + def get_input(self): + choice = input("Выберите способ ввода (1 - клавиатура, 2 - файл, 3 - случайная матрица): ") + if choice == '1': + return self._input_from_keyboard() + elif choice == '2': + return self._input_from_file() + elif choice == '3': + return self._input_random() + else: + raise ValueError("Некорректный выбор способа ввода!") diff --git "a/\320\2403213/vlasov_356550/lab1/src/main.py" "b/\320\2403213/vlasov_356550/lab1/src/main.py" new file mode 100644 index 0000000..f09a53e --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab1/src/main.py" @@ -0,0 +1,53 @@ +from gauss_method import GaussMethod +from input_handler import InputHandler +from output_handler import print_matrix, print_vector +from residuals_calculator import compute_residuals +from numpy_comparator import compare_with_numpy + + +class Application: + def __init__(self): + self.input_handler = InputHandler() + + + def run(self): + try: + # Ввод данных + matrix_a, vector_b, is_random = self.input_handler.get_input() + + if is_random: + print("\n[ СГЕНЕРИРОВАННАЯ СЛУЧАЙНАЯ МАТРИЦА ]") + print_matrix(matrix_a, "Матрица A") + print_vector(vector_b, "Вектор B") + else: + print("\n[ ВВЕДЁННЫЕ ДАННЫЕ ]") + print_matrix(matrix_a, "Матрица A") + print_vector(vector_b, "Вектор B") + + # Решение системы + gauss = GaussMethod(matrix_a, vector_b) + x = gauss.solve() + det = gauss.get_determinant() + triangular_a, triangular_b = gauss.get_triangular_matrix() + residuals = compute_residuals(matrix_a, x, vector_b) + + # Вывод результатов + print("\n[ РЕЗУЛЬТАТЫ ВЫЧИСЛЕНИЙ ]") + print_matrix(triangular_a, "Треугольная матрица A") + print_vector(triangular_b, "Преобразованный вектор B") + print_vector(x, "Вектор неизвестных x") + print(f"Определитель: {det:.6f}") + print_vector(residuals, "Вектор невязок r") + + # Сравнение с numpy + compare_with_numpy(matrix_a, vector_b) + + except ValueError as e: + print(f"Ошибка: {e}!") + except Exception as e: + print(f"Произошла непредвиденная ошибка: {e}!") + + +if __name__ == "__main__": + app = Application() + app.run() diff --git "a/\320\2403213/vlasov_356550/lab1/src/numpy_comparator.py" "b/\320\2403213/vlasov_356550/lab1/src/numpy_comparator.py" new file mode 100644 index 0000000..bcc92a3 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab1/src/numpy_comparator.py" @@ -0,0 +1,12 @@ +import numpy as np +from output_handler import print_vector + + +def compare_with_numpy(matrix_a, vector_b): + matrix_a_np = np.array(matrix_a) + vector_b_np = np.array(vector_b) + x_np = np.linalg.solve(matrix_a_np, vector_b_np) + det_np = np.linalg.det(matrix_a_np) + print("\n[ СРАВНИЕ С БИБЛИОТЕКОЙ NUMPY ]") + print_vector(x_np, "Решение с помощью numpy") + print(f"Определитель с помощью numpy: {det_np:.6f}") diff --git "a/\320\2403213/vlasov_356550/lab1/src/output_handler.py" "b/\320\2403213/vlasov_356550/lab1/src/output_handler.py" new file mode 100644 index 0000000..1d88e51 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab1/src/output_handler.py" @@ -0,0 +1,10 @@ +def print_matrix(matrix, name): + print(f"{name}:") + for row in matrix: + print(" ".join(f"{x:10.6f}" for x in row)) + + +def print_vector(vector, name): + print(f"{name}:") + for x in vector: + print(f"{x:10.6f}") diff --git "a/\320\2403213/vlasov_356550/lab1/src/residuals_calculator.py" "b/\320\2403213/vlasov_356550/lab1/src/residuals_calculator.py" new file mode 100644 index 0000000..2c453ed --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab1/src/residuals_calculator.py" @@ -0,0 +1,12 @@ +def compute_residuals(a_original, x, b_original): + n = len(a_original) + r = [0.0] * n + + # Вычисляем a_original * x + for i in range(n): + for j in range(n): + r[i] += a_original[i][j] * x[j] + # Вычитаем b_original + r[i] -= b_original[i] + + return r diff --git "a/\320\2403213/vlasov_356550/lab2/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2402 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" "b/\320\2403213/vlasov_356550/lab2/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2402 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" new file mode 100644 index 0000000..350cc9d Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab2/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2402 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" differ diff --git "a/\320\2403213/vlasov_356550/lab2/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2402 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" "b/\320\2403213/vlasov_356550/lab2/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2402 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" new file mode 100644 index 0000000..b0d0fe0 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab2/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2402 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" differ diff --git "a/\320\2403213/vlasov_356550/lab2/src/equations/__init__.py" "b/\320\2403213/vlasov_356550/lab2/src/equations/__init__.py" new file mode 100644 index 0000000..e69de29 diff --git "a/\320\2403213/vlasov_356550/lab2/src/equations/nonlinear_equations.py" "b/\320\2403213/vlasov_356550/lab2/src/equations/nonlinear_equations.py" new file mode 100644 index 0000000..220a5bc --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/equations/nonlinear_equations.py" @@ -0,0 +1,47 @@ +# equations/nonlinear_equations.py + +from typing import Callable +import numpy as np + + +class NonlinearEquations: + @staticmethod + def get_equations() -> dict[int, Callable[[float], float]]: + return { + 1: lambda x: np.sin(x) - x + 1, + 2: lambda x: x ** 3 - 2 * np.cos(x) - 1, + 3: lambda x: np.exp(x) - 3 * x - 2, + 4: lambda x: x ** 2 - 4 * np.sin(x), + 5: lambda x: np.tan(np.clip(x, -np.pi / 2 + 0.1, np.pi / 2 - 0.1)) - x - 1 # Ограничение + } + + + @staticmethod + def get_derivative(eq_id: int) -> Callable[[float], float]: + derivatives = { + 1: lambda x: np.cos(x) - 1, + 2: lambda x: 3 * x ** 2 + 2 * np.sin(x), + 3: lambda x: np.exp(x) - 3, + 4: lambda x: 2 * x - 4 * np.cos(x), + 5: lambda x: 1 / (np.cos(np.clip(x, -np.pi / 2 + 0.1, np.pi / 2 - 0.1)) ** 2) - 1 + } + + if eq_id in derivatives: + return derivatives[eq_id] + + raise ValueError("Производная не определена для данного уравнения") + + + @staticmethod + def get_phi(eq_id: int, a: float, b: float) -> Callable[[float], float]: + f = NonlinearEquations.get_equations()[eq_id] + f_prime = NonlinearEquations.get_derivative(eq_id) + x_vals = np.linspace(a, b, 100) + max_f_prime = max(abs(f_prime(x)) for x in x_vals if -1e10 < f_prime(x) < 1e10) + + if max_f_prime < 1e-6: + raise ValueError("Производная равна нулю на интервале") + + lambda_val = 0.9 / max_f_prime + + return lambda x: x - lambda_val * f(x) diff --git "a/\320\2403213/vlasov_356550/lab2/src/equations/nonlinear_systems.py" "b/\320\2403213/vlasov_356550/lab2/src/equations/nonlinear_systems.py" new file mode 100644 index 0000000..147857b --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/equations/nonlinear_systems.py" @@ -0,0 +1,27 @@ +# equations/nonlinear_systems.py + +from typing import Callable, Tuple +import numpy as np + + +class NonlinearSystems: + @staticmethod + def get_systems() -> dict[int, Tuple[Callable[[float, float], float], Callable[[float, float], float]]]: + return { + 1: (lambda x, y: np.sin(x) + y - 1, + lambda x, y: x - np.cos(y)), + 2: (lambda x, y: np.exp(x) - y - 1, + lambda x, y: 2 * x + np.sin(y) - 2) + } + + + @staticmethod + def get_phi_system(sys_id: int) -> Tuple[Callable[[float, float], float], Callable[[float, float], float]]: + if sys_id == 1: + return (lambda x, y: np.cos(y), # x = cos(y) + lambda x, y: 1 - np.sin(x)) # y = 1 - sin(x) + elif sys_id == 2: + return (lambda x, y: np.log(1 + y) if 1 + y > 0 else x, # x = ln(1 + y) + lambda x, y: (2 - np.sin(y)) / 2) # y = (2 - sin(y)) / 2 + + raise ValueError("Неверный номер системы") diff --git "a/\320\2403213/vlasov_356550/lab2/src/methods/__init__.py" "b/\320\2403213/vlasov_356550/lab2/src/methods/__init__.py" new file mode 100644 index 0000000..e69de29 diff --git "a/\320\2403213/vlasov_356550/lab2/src/methods/bisection.py" "b/\320\2403213/vlasov_356550/lab2/src/methods/bisection.py" new file mode 100644 index 0000000..f4fd6f3 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/methods/bisection.py" @@ -0,0 +1,40 @@ +# methods/bisection.py + +from typing import Callable + + +class BisectionMethod: + @staticmethod + def check_roots(f: Callable[[float], float], a: float, b: float) -> str | None: + fa, fb = f(a), f(b) + + if fa * fb >= 0: + return "Функция не меняет знак на концах интервала" + + return None + + + @staticmethod + def solve(f: Callable[[float], float], a: float, b: float, eps: float) -> tuple[float, int]: + if a >= b: + raise ValueError("Левая граница должна быть меньше правой") + + error = BisectionMethod.check_roots(f, a, b) + + if error: + raise ValueError(error) + + iters = 0 + while abs(b - a) > eps: # Итерируем до нужной точности + x = (a + b) / 2 # Вычисляем середину + + if f(x) == 0: + return x, iters + 1 + elif f(a) * f(x) < 0: # Корень в левой половине, обновляем b = x + b = x + else: # Иначе корень в правой половине, обновляем a = x + a = x + + iters += 1 + + return (a + b) / 2, iters diff --git "a/\320\2403213/vlasov_356550/lab2/src/methods/newton.py" "b/\320\2403213/vlasov_356550/lab2/src/methods/newton.py" new file mode 100644 index 0000000..34d7bc8 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/methods/newton.py" @@ -0,0 +1,39 @@ +# methods/newton.py + +from typing import Callable + + +class NewtonMethod: + @staticmethod + def choose_initial_approximation(f: Callable[[float], float], a: float, b: float) -> float: + fa, fb = f(a), f(b) + + if fa * fb < 0: # Если корень на интервале + return a if abs(fa) < abs(fb) else b + + return (a + b) / 2 # Иначе середина + + + @staticmethod + def solve(f: Callable[[float], float], f_prime: Callable[[float], float], a: float, b: float, eps: float, max_iter: int = 1000) -> tuple[float, int]: + x0 = NewtonMethod.choose_initial_approximation(f, a, b) + x_prev = x0 + fp = f_prime(x_prev) + + if abs(fp) < 1e-6 or not (-1e10 < fp < 1e10): + raise ValueError("Производная близка к нулю или не определена в начальной точке") + + for i in range(max_iter): + fx = f(x_prev) + fp = f_prime(x_prev) + + if abs(fp) < 1e-6 or not (-1e10 < fp < 1e10): + raise ValueError("Производная стала близка к нулю в процессе") + + x_curr = x_prev - fx / fp + + if abs(x_curr - x_prev) <= eps and abs(fx) <= eps: # Условие остановки + return x_curr, i + 1 + + x_prev = x_curr + raise ValueError("Метод Ньютона не сошелся за максимальное число итераций") diff --git "a/\320\2403213/vlasov_356550/lab2/src/methods/simple_iteration.py" "b/\320\2403213/vlasov_356550/lab2/src/methods/simple_iteration.py" new file mode 100644 index 0000000..07489d7 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/methods/simple_iteration.py" @@ -0,0 +1,47 @@ +# methods/simple_iteration.py + +from typing import Callable +import numpy as np + + +class SimpleIterationMethod: + @staticmethod + def choose_initial_approximation(f: Callable[[float], float], a: float, b: float) -> float: + fa, fb = abs(f(a)), abs(f(b)) + + return a if fa < fb else b + + + @staticmethod + def check_convergence(phi: Callable[[float], float], a: float, b: float) -> bool: + x_vals = np.linspace(a, b, 100) # Массив из 100 точек на интервале + max_derivative = 0 + + for x in x_vals: + h = 1e-6 * (1 + abs(x)) # Малый шаг, адаптированный к масштабу x + derivative = abs((phi(x + h) - phi(x)) / h) + max_derivative = max(max_derivative, derivative) + + if max_derivative >= 1: + print(f"Максимальная производная |φ'(x)| = {max_derivative:.4f} ≥ 1") + return False + + return True + + + @staticmethod + def solve(phi: Callable[[float], float], f: Callable[[float], float], a: float, b: float, eps: float, max_iter: int = 1000) -> tuple[float, int]: + x0 = SimpleIterationMethod.choose_initial_approximation(f, a, b) + x_prev = x0 + + for i in range(max_iter): + x_curr = phi(x_prev) + + if abs(x_curr - x_prev) <= eps: + return x_curr, i + 1 + + if abs(x_curr - x_prev) > 1e10: + raise ValueError("Метод простой итерации расходится") + + x_prev = x_curr + raise ValueError("Метод простой итерации не сошелся за максимальное число итераций") diff --git "a/\320\2403213/vlasov_356550/lab2/src/methods/system_iteration.py" "b/\320\2403213/vlasov_356550/lab2/src/methods/system_iteration.py" new file mode 100644 index 0000000..db42ea5 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/methods/system_iteration.py" @@ -0,0 +1,42 @@ +# methods/system_iteration.py + +from typing import Callable, Tuple + + +class SystemIterationMethod: + @staticmethod + def check_convergence(phi1: Callable[[float, float], float], phi2: Callable[[float, float], float], x0: float, y0: float) -> bool: + h = 1e-6 # Малый шаг, адаптированный к масштабу x + dphi1_dx = (phi1(x0 + h, y0) - phi1(x0, y0)) / h + dphi1_dy = (phi1(x0, y0 + h) - phi1(x0, y0)) / h + dphi2_dx = (phi2(x0 + h, y0) - phi2(x0, y0)) / h + dphi2_dy = (phi2(x0, y0 + h) - phi2(x0, y0)) / h + norm = max(abs(dphi1_dx) + abs(dphi1_dy), abs(dphi2_dx) + abs(dphi2_dy)) + print(f"Норма Якобиана = {norm:.4f}") + + return norm < 1.1 + + + @staticmethod + def solve(phi1: Callable[[float, float], float], phi2: Callable[[float, float], float], x0: float, y0: float, eps: float, f1: Callable[[float, float], float], f2: Callable[[float, float], float], max_iter: int = 1000) -> Tuple[Tuple[float, float], int, list]: + if not SystemIterationMethod.check_convergence(phi1, phi2, x0, y0): + raise ValueError("Условие сходимости не выполнено") + + x_prev, y_prev = x0, y0 + errors = [] + + for i in range(max_iter): + x_curr = phi1(x_prev, y_prev) + y_curr = phi2(x_prev, y_prev) + error = [abs(x_curr - x_prev), abs(y_curr - y_prev)] + errors.append(error) + + if max(error) <= eps and abs(f1(x_curr, y_curr)) <= eps and abs(f2(x_curr, y_curr)) <= eps: + return (x_curr, y_curr), i + 1, errors + + if max(error) > 1e10: + raise ValueError("Метод простой итерации для системы расходится") + + x_prev, y_prev = x_curr, y_curr + + raise ValueError("Метод простой итерации для системы не сошелся") diff --git "a/\320\2403213/vlasov_356550/lab2/src/utils/__init__.py" "b/\320\2403213/vlasov_356550/lab2/src/utils/__init__.py" new file mode 100644 index 0000000..e69de29 diff --git "a/\320\2403213/vlasov_356550/lab2/src/utils/file_input.py" "b/\320\2403213/vlasov_356550/lab2/src/utils/file_input.py" new file mode 100644 index 0000000..3fdfc24 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/utils/file_input.py" @@ -0,0 +1,45 @@ +# utils/file_input.py + +class FileInput: + @staticmethod + def read_input(filename: str, is_system: bool = False) -> tuple[int, float, float, float, None, None] | tuple[int, None, None, float, float, float]: + try: + with open(filename, 'r') as file: + line = file.readline().strip().split() + + if not line: + raise ValueError("Файл пуст") + + eq_id_or_sys_id = int(line[0]) + + if not is_system: # Для уравнений + if eq_id_or_sys_id not in {1, 2, 3, 4, 5}: + raise ValueError("Неверный номер уравнения") + + if len(line) != 4: # Ожидаем eq_id a b eps + raise ValueError("Для уравнения требуется формат: eq_id a b eps") + + a, b, eps = map(float, line[1:4]) + + if a >= b: + raise ValueError("Левая граница должна быть меньше правой") + + if eps <= 0: + raise ValueError("Погрешность должна быть положительной") + + return eq_id_or_sys_id, a, b, eps, None, None + else: # Для систем + if eq_id_or_sys_id not in {1, 2}: + raise ValueError("Неверный номер системы") + + if len(line) != 4: + raise ValueError("Для системы требуется формат: sys_id eps x0 y0") + + eps, x0, y0 = map(float, line[1:4]) + + if eps <= 0: + raise ValueError("Погрешность должна быть положительной") + + return eq_id_or_sys_id, None, None, eps, x0, y0 + except Exception as e: + raise ValueError(f"Ошибка чтения файла: {str(e)}") diff --git "a/\320\2403213/vlasov_356550/lab2/src/utils/file_output.py" "b/\320\2403213/vlasov_356550/lab2/src/utils/file_output.py" new file mode 100644 index 0000000..224217c --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/utils/file_output.py" @@ -0,0 +1,31 @@ +# utils/file_output.py + +from utils.input_validator import InputValidator + + +class FileOutput: + @staticmethod + def write_output(method_name: str, root, iters: int, f_val, validator: InputValidator, errors=None, filename: str = "output.txt"): + output_choice = validator.get_float("1 - Вывод на экран, 2 - Вывод в файл: ") + + result_str = f"Результат метода {method_name}:\n" + if isinstance(root, tuple): + result_str += f"x1 = {root[0]:.6f}, x2 = {root[1]:.6f}\n" + result_str += f"f(x1,x2) = ({f_val[0]:.6f}, {f_val[1]:.6f})\n" + if errors: + error_str = f"[{errors[-1][0]:.6f}, {errors[-1][1]:.6f}]" + result_str += f"Вектор погрешностей на последней итерации: {error_str}\n" + else: + result_str += f"x = {root:.6f}\n" + result_str += f"f(x) = {f_val:.6f}\n" + + result_str += f"Количество итераций: {iters}\n" + + if output_choice == 1: + print(result_str) + else: + try: + with open(filename, "w") as file: + file.write(result_str) + except Exception as e: + raise ValueError(f"Ошибка записи в файл: {str(e)}") \ No newline at end of file diff --git "a/\320\2403213/vlasov_356550/lab2/src/utils/graph_plotter.py" "b/\320\2403213/vlasov_356550/lab2/src/utils/graph_plotter.py" new file mode 100644 index 0000000..fd39fc7 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/utils/graph_plotter.py" @@ -0,0 +1,61 @@ +# utils/graph_plotter.py + +import matplotlib.pyplot as plt +import numpy as np +from typing import Callable, Tuple +from matplotlib.lines import Line2D + + +class GraphPlotter: + @staticmethod + def plot_function(f: Callable[[float], float], a: float, b: float, root: float = None): + x = np.linspace(a, b, 400) + y = [f(xi) for xi in x] + + plt.figure(figsize=(10, 6)) + plt.plot(x, y, label='f(x)') + plt.axhline(0, color='black', linewidth=0.5) + plt.axvline(0, color='black', linewidth=0.5) + + if root is not None: + plt.plot(root, f(root), 'ro', label=f'Корень x = {root:.4f}') + + plt.grid(True) + plt.legend() + plt.title('График функции') + plt.xlabel('x') + plt.ylabel('f(x)') + plt.show() + + + @staticmethod + def plot_system(f1: Callable[[float, float], float], f2: Callable[[float, float], float], x_range: Tuple[float, float], y_range: Tuple[float, float], root: Tuple[float, float] = None): + x = np.linspace(x_range[0], x_range[1], 400) + y = np.linspace(y_range[0], y_range[1], 400) + X, Y = np.meshgrid(x, y) + + Z1 = f1(X, Y) + Z2 = f2(X, Y) + + plt.figure(figsize=(10, 6)) + contour1 = plt.contour(X, Y, Z1, levels=[0], colors='blue', linewidths=2) + contour2 = plt.contour(X, Y, Z2, levels=[0], colors='green', linewidths=2) + + # Прокси-объекты для легенды + proxy1 = Line2D([0], [0], color='blue', linewidth=2) + proxy2 = Line2D([0], [0], color='green', linewidth=2) + + handles = [proxy1, proxy2] + labels = ['f1(x, y) = 0', 'f2(x, y) = 0'] + + if root is not None: + plt.plot(root[0], root[1], 'ro', label=f'Решение ({root[0]:.4f}, {root[1]:.4f})') + handles.append(plt.gca().get_lines()[-1]) + labels.append('Решение') + + plt.grid(True) + plt.legend(handles, labels) + plt.title('График системы уравнений') + plt.xlabel('x') + plt.ylabel('y') + plt.show() diff --git "a/\320\2403213/vlasov_356550/lab2/src/utils/input_validator.py" "b/\320\2403213/vlasov_356550/lab2/src/utils/input_validator.py" new file mode 100644 index 0000000..fafe4ba --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/utils/input_validator.py" @@ -0,0 +1,23 @@ +# utils/input_validator.py + +class InputValidator: + @staticmethod + def get_float(prompt: str) -> float: + while True: + try: + value = input(prompt).replace(',', '.') # Поддержка запятой + return float(value) + except ValueError: + print("Ошибка: Введите корректное число (можно использовать точку или запятую)") + + + @staticmethod + def get_positive_float(prompt: str) -> float | None: + while True: + value = InputValidator.get_float(prompt) + + if value <= 0: + print("Ошибка: Значение должно быть положительным") + continue + + return value diff --git "a/\320\2403213/vlasov_356550/lab2/src/utils/output_formatter.py" "b/\320\2403213/vlasov_356550/lab2/src/utils/output_formatter.py" new file mode 100644 index 0000000..055a29d --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab2/src/utils/output_formatter.py" @@ -0,0 +1,14 @@ +# utils/output_formatter.py + +class OutputFormatter: + @staticmethod + def print_result(method: str, root: float | tuple[float, float], iterations: int, fx: float | tuple[float, float]): + print(f"\nРезультат метода {method}:") + if isinstance(root, tuple): + print(f"x = {root[0]:.6f}, y = {root[1]:.6f}") + print(f"f(x,y) = ({fx[0]:.6f}, {fx[1]:.6f})") + else: + print(f"x = {root:.6f}") + print(f"f(x) = {fx:.6f}") + + print(f"Количество итераций: {iterations}") diff --git "a/\320\2403213/vlasov_356550/lab3/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2403 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" "b/\320\2403213/vlasov_356550/lab3/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2403 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" new file mode 100644 index 0000000..95ed3d4 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab3/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2403 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" differ diff --git "a/\320\2403213/vlasov_356550/lab3/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2403 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" "b/\320\2403213/vlasov_356550/lab3/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2403 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" new file mode 100644 index 0000000..d730429 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab3/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2403 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" differ diff --git "a/\320\2403213/vlasov_356550/lab3/src/core/__init__.py" "b/\320\2403213/vlasov_356550/lab3/src/core/__init__.py" new file mode 100644 index 0000000..f1c3c96 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/core/__init__.py" @@ -0,0 +1,2 @@ +from .integrator import Integrator +from .runge_rule import runge_rule diff --git "a/\320\2403213/vlasov_356550/lab3/src/core/integrator.py" "b/\320\2403213/vlasov_356550/lab3/src/core/integrator.py" new file mode 100644 index 0000000..adc7a43 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/core/integrator.py" @@ -0,0 +1,48 @@ +from typing import Dict, Tuple + +import numpy as np + +from core.runge_rule import runge_rule +from functions import IFunction +from integration import IIntegrationMethod, LeftRectangles, RightRectangles, MidRectangles, Trapezoid, Simpson + + +class Integrator: + def __init__(self): + self.methods: Dict[str, IIntegrationMethod] = { + "left_rect": LeftRectangles(), + "right_rect": RightRectangles(), + "mid_rect": MidRectangles(), + "trapezoid": Trapezoid(), + "simpson": Simpson() + } + self.method_orders = { + "left_rect": 1, + "right_rect": 1, + "mid_rect": 2, + "trapezoid": 2, + "simpson": 4 + } + + + def integrate_with_precision(self, func: IFunction, a: float, b: float, eps: float, method: str, n_start: int = 4) -> Tuple[float, int]: + n = n_start + method_obj = self.methods[method] + order = self.method_orders[method] + max_iterations = 20 # Ограничение числа итераций + max_n = 2**20 # Максимальное число разбиений + + for _ in range(max_iterations): + i_n = method_obj.integrate(func, a, b, n) + i_2n = method_obj.integrate(func, a, b, 2 * n) + error = runge_rule(i_n, i_2n, order) + + if error < eps or not np.isfinite(error): + return i_2n, 2 * n + + n *= 2 + + if n > max_n: + raise ValueError(f"Достигнуто максимальное число разбиений {max_n} для метода {method}") + + raise ValueError(f"Не удалось достичь точности {eps} за {max_iterations} итераций") diff --git "a/\320\2403213/vlasov_356550/lab3/src/core/runge_rule.py" "b/\320\2403213/vlasov_356550/lab3/src/core/runge_rule.py" new file mode 100644 index 0000000..4f39471 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/core/runge_rule.py" @@ -0,0 +1,11 @@ +import numpy as np + + +def runge_rule(i_n: float, i_2n: float, method_order: int) -> float: + """Оценивает погрешность по правилу Рунге.""" + diff = abs(i_2n - i_n) + + if not np.isfinite(diff): + return float('inf') + + return diff / (2 ** method_order - 1) # Формула Рунге diff --git "a/\320\2403213/vlasov_356550/lab3/src/functions/__init__.py" "b/\320\2403213/vlasov_356550/lab3/src/functions/__init__.py" new file mode 100644 index 0000000..df010eb --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/functions/__init__.py" @@ -0,0 +1,3 @@ +from .function_interface import IFunction +from .polynomial_function import PolynomialFunction +from .improper_function import ImproperFunction diff --git "a/\320\2403213/vlasov_356550/lab3/src/functions/function_interface.py" "b/\320\2403213/vlasov_356550/lab3/src/functions/function_interface.py" new file mode 100644 index 0000000..75ed8af --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/functions/function_interface.py" @@ -0,0 +1,20 @@ +from abc import ABC, abstractmethod + + +class IFunction(ABC): + @abstractmethod + def evaluate(self, x: float) -> float: + """Вычисляет значение функции в точке x.""" + pass + + + @abstractmethod + def has_discontinuity(self, a: float, b: float) -> bool: + """Проверяет наличие разрывов на отрезке [a, b].""" + pass + + + @abstractmethod + def get_expression(self) -> str: + """Возвращает строковое представление функции.""" + pass diff --git "a/\320\2403213/vlasov_356550/lab3/src/functions/improper_function.py" "b/\320\2403213/vlasov_356550/lab3/src/functions/improper_function.py" new file mode 100644 index 0000000..33b7ed2 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/functions/improper_function.py" @@ -0,0 +1,58 @@ +import numpy as np + +from functions import IFunction + + +class ImproperFunction(IFunction): + def __init__(self, expr: str): + self.expr = expr + self.epsilon = 1e-6 + + + # Вычисляет значение функции с учетом окрестности разрыва. + def evaluate(self, x: float) -> float: + try: + if self.expr == "1/sqrt(x)": + if x < self.epsilon: + x = self.epsilon + + return 1 / np.sqrt(x) + elif self.expr == "1/(x-1)**2": + if abs(x - 1) < self.epsilon: + raise ValueError("Разрыв в x=1") + + return 1 / (x - 1)**2 + + # Общая обработка для других выражений + if abs(x) < self.epsilon: + x = self.epsilon + + return eval(self.expr, {"x": x, "sqrt": np.sqrt}) + except (ZeroDivisionError, ValueError, OverflowError): + raise ValueError(f"Функция имеет разрыв в точке x = {x}") + + + # Проверяет наличие разрывов на отрезке [a, b]. + def has_discontinuity(self, a: float, b: float) -> bool: + # Проверка разрыва в x=0 для 1/sqrt(x) + if self.expr == "1/sqrt(x)" and a <= 0: + return True + + # Проверка разрыва в x=1 для 1/(x-1)^2 + if self.expr == "1/(x-1)**2" and a <= 1 <= b: + return True + + # Проверка значений на отрезке + x = np.linspace(max(a, self.epsilon), b, 1000) + + for xi in x: + try: + self.evaluate(xi) + except ValueError: + return True + + return False + + + def get_expression(self) -> str: + return self.expr.replace("sqrt(x)", "√x").replace("**2", "²") diff --git "a/\320\2403213/vlasov_356550/lab3/src/functions/polynomial_function.py" "b/\320\2403213/vlasov_356550/lab3/src/functions/polynomial_function.py" new file mode 100644 index 0000000..63ddc50 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/functions/polynomial_function.py" @@ -0,0 +1,47 @@ +from .function_interface import IFunction + + +class PolynomialFunction(IFunction): + def __init__(self, coeffs: list): + self.coeffs = coeffs # Коэффициенты полинома от старшей степени + + + # Вычисляет значение полинома в точке x методом Горнера. + def evaluate(self, x: float) -> float: + result = 0 + + for coeff in self.coeffs: + result = result * x + coeff + + return result + + + # Проверяет наличие разрывов (полиномы непрерывны). + def has_discontinuity(self, a: float, b: float) -> bool: + return False + + + def get_expression(self) -> str: + if not self.coeffs: + return "0" + + terms = [] + degree = len(self.coeffs) - 1 + + for i, coeff in enumerate(self.coeffs): + if coeff == 0: + continue + + power = degree - i + + if power == 0: + term = f"{coeff:+d}" if coeff > 0 else f"{coeff:d}" + elif power == 1: + term = f"{coeff:+d}x" if coeff != 1 else f"+x" if coeff > 0 else "-x" + else: + term = f"{coeff:+d}x^{power}" if coeff != 1 else f"+x^{power}" if coeff > 0 else f"-x^{power}" + terms.append(term) + + result = "".join(terms) + + return result[1:] if result.startswith("+") else result diff --git "a/\320\2403213/vlasov_356550/lab3/src/integration/__init__.py" "b/\320\2403213/vlasov_356550/lab3/src/integration/__init__.py" new file mode 100644 index 0000000..2c408c1 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/integration/__init__.py" @@ -0,0 +1,4 @@ +from .integration_interface import IIntegrationMethod +from .rectangle_methods import LeftRectangles, RightRectangles, MidRectangles +from .trapezoid_method import Trapezoid +from .simpson_method import Simpson diff --git "a/\320\2403213/vlasov_356550/lab3/src/integration/integration_interface.py" "b/\320\2403213/vlasov_356550/lab3/src/integration/integration_interface.py" new file mode 100644 index 0000000..ee4e375 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/integration/integration_interface.py" @@ -0,0 +1,10 @@ +from abc import ABC, abstractmethod + +from functions import IFunction + + +class IIntegrationMethod(ABC): + @abstractmethod + def integrate(self, func: IFunction, a: float, b: float, n: int) -> float: + """Вычисляет интеграл функции func на отрезке [a, b] с n разбиениями.""" + pass diff --git "a/\320\2403213/vlasov_356550/lab3/src/integration/rectangle_methods.py" "b/\320\2403213/vlasov_356550/lab3/src/integration/rectangle_methods.py" new file mode 100644 index 0000000..e17c4e9 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/integration/rectangle_methods.py" @@ -0,0 +1,65 @@ +from functions import IFunction +from integration import IIntegrationMethod + + +class LeftRectangles(IIntegrationMethod): + """ + Метод левых прямоугольников. + Формула: int_a^b f(x) dx ≈ h * sum_{i=0}^{n-1} f(x_i), где x_i = a + i * h, h = (b - a) / n. + Суммирует значения функции в левых концах подынтервалов. + """ + def integrate(self, func: IFunction, a: float, b: float, n: int) -> float: + h = (b - a) / n + result = 0 + + for i in range(n): + x_i = a + i * h + + try: + result += func.evaluate(x_i) + except ValueError: + continue # Пропускаем точки с разрывами + + return h * result + + +class RightRectangles(IIntegrationMethod): + """ + Метод правых прямоугольников. + Формула: int_a^b f(x) dx ≈ h * sum_{i=0}^{n-1} f(x_{i+1}), где x_{i+1} = a + (i+1) * h, h = (b - a) / n. + Суммирует значения функции в правых концах подынтервалов. + """ + def integrate(self, func: IFunction, a: float, b: float, n: int) -> float: + h = (b - a) / n + result = 0 + + for i in range(n): + x_i = a + (i + 1) * h + + try: + result += func.evaluate(x_i) + except ValueError: + continue + + return h * result + + +class MidRectangles(IIntegrationMethod): + """ + Метод средних прямоугольников. + Формула: int_a^b f(x) dx ≈ h * sum_{i=0}^{n-1} f(x_i + h/2), где x_i = a + i * h, h = (b - a) / n. + Использует значения функции в серединах подынтервалов для повышения точности. + """ + def integrate(self, func: IFunction, a: float, b: float, n: int) -> float: + h = (b - a) / n + result = 0 + + for i in range(n): + x_i = a + (i + 0.5) * h + + try: + result += func.evaluate(x_i) + except ValueError: + continue + + return h * result diff --git "a/\320\2403213/vlasov_356550/lab3/src/integration/simpson_method.py" "b/\320\2403213/vlasov_356550/lab3/src/integration/simpson_method.py" new file mode 100644 index 0000000..a7dcd12 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/integration/simpson_method.py" @@ -0,0 +1,22 @@ +from functions import IFunction +from integration import IIntegrationMethod + + +class Simpson(IIntegrationMethod): + """ + Метод Симпсона. + Формула: int_a^b f(x) dx ≈ (h/3) * [f(a) + f(b) + 4 * sum_{i=1,3,...}^{n-1} f(x_i) + 2 * sum_{i=2,4,...}^{n-2} f(x_i)], где x_i = a + i * h, h = (b - a) / n, n - четное. + Использует параболическую аппроксимацию на парах подынтервалов. + """ + def integrate(self, func: IFunction, a: float, b: float, n: int) -> float: + if n % 2 != 0: + raise ValueError("Число разбиений должно быть четным для метода Симпсона") + + h = (b - a) / n + result = func.evaluate(a) + func.evaluate(b) + + for i in range(1, n): + x_i = a + i * h + result += 4 * func.evaluate(x_i) if i % 2 == 1 else 2 * func.evaluate(x_i) + + return (h / 3) * result diff --git "a/\320\2403213/vlasov_356550/lab3/src/integration/trapezoid_method.py" "b/\320\2403213/vlasov_356550/lab3/src/integration/trapezoid_method.py" new file mode 100644 index 0000000..19828fe --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/integration/trapezoid_method.py" @@ -0,0 +1,19 @@ +from functions import IFunction +from integration import IIntegrationMethod + + +class Trapezoid(IIntegrationMethod): + """ + Метод трапеций. + Формула: int_a^b f(x) dx ≈ h * [(f(a) + f(b))/2 + sum_{i=1}^{n-1} f(x_i)], где x_i = a + i * h, h = (b - a) / n. + Аппроксимирует интеграл, соединяя точки функции линейно. + """ + def integrate(self, func: IFunction, a: float, b: float, n: int) -> float: + h = (b - a) / n + result = 0.5 * (func.evaluate(a) + func.evaluate(b)) + + for i in range(1, n): + x_i = a + i * h + result += func.evaluate(x_i) + + return h * result diff --git "a/\320\2403213/vlasov_356550/lab3/src/io/__init__.py" "b/\320\2403213/vlasov_356550/lab3/src/io/__init__.py" new file mode 100644 index 0000000..52fc40b --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/io/__init__.py" @@ -0,0 +1 @@ +from .input_parser import parse_float_input diff --git "a/\320\2403213/vlasov_356550/lab3/src/io/input_parser.py" "b/\320\2403213/vlasov_356550/lab3/src/io/input_parser.py" new file mode 100644 index 0000000..6c7cbfe --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/io/input_parser.py" @@ -0,0 +1,9 @@ +def parse_float_input(prompt: str) -> float: + """ Парсит ввод числа с заменой запятой на точку и обработкой ошибок. """ + while True: + value = input(prompt).replace(",", ".") + + try: + return float(value) + except ValueError: + print("Ошибка: введите корректное число (используйте точку как разделитель).") diff --git "a/\320\2403213/vlasov_356550/lab3/src/main.py" "b/\320\2403213/vlasov_356550/lab3/src/main.py" new file mode 100644 index 0000000..fbb3f02 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/main.py" @@ -0,0 +1,56 @@ +from functions.polynomial_function import PolynomialFunction +from functions.improper_function import ImproperFunction +from core import Integrator +from io import parse_float_input +from visualization.plotter import plot_function + + +def main(): + functions = [ + PolynomialFunction([-3, -5, 4, -2]), # Полином: -3x^3 - 5x^2 + 4x - 2 + PolynomialFunction([1, 0, 0, 0]), # Полином: x^3 + PolynomialFunction([0, 1, 0, 0]), # Полином: x^2 + ImproperFunction("1/sqrt(x)"), # Несобственный интеграл: 1/√x + ImproperFunction("1/(x-1)**2") # Несобственный интеграл: 1/(x-1)² + ] + + print("Выберите функцию:") + for i, func in enumerate(functions, 1): + print(f"{i}. {func.get_expression()}") + + try: + func_idx = int(input("Номер функции: ")) - 1 + func = functions[func_idx] + except (ValueError, IndexError): + print("Ошибка: выберите корректный номер функции.") + return + + a = parse_float_input("Введите нижний предел интегрирования (a): ") + b = parse_float_input("Введите верхний предел интегрирования (b): ") + eps = parse_float_input("Введите точность (eps): ") + + # Проверка корректности пределов. + if a >= b: + print("Ошибка: нижний предел должен быть меньше верхнего.") + return + + # Проверка наличия разрывов функции на отрезке. + if func.has_discontinuity(a, b): + print("Интеграл не существует: функция имеет разрывы на отрезке.") + return + + integrator = Integrator() + results = {} + + for method in integrator.methods: + try: + value, n = integrator.integrate_with_precision(func, a, b, eps, method) + results[method] = (value, n) + print(f"{method}: Интеграл = {value:.6f}, Число разбиений = {n}") + except ValueError as e: + print(f"{method}: Ошибка: {e}") + + plot_function(func, a, b) + +if __name__ == "__main__": + main() diff --git "a/\320\2403213/vlasov_356550/lab3/src/visualization/__init__.py" "b/\320\2403213/vlasov_356550/lab3/src/visualization/__init__.py" new file mode 100644 index 0000000..6a4efc1 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/visualization/__init__.py" @@ -0,0 +1 @@ +from .plotter import plot_function diff --git "a/\320\2403213/vlasov_356550/lab3/src/visualization/plotter.py" "b/\320\2403213/vlasov_356550/lab3/src/visualization/plotter.py" new file mode 100644 index 0000000..1daf32a --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab3/src/visualization/plotter.py" @@ -0,0 +1,25 @@ +import numpy as np +from matplotlib import pyplot as plt + +from functions import IFunction + + +def plot_function(func: IFunction, a: float, b: float) -> None: + """Строит график функции на отрезке [a, b].""" + x = np.linspace(a, b, 1000) + y = [] + + for xi in x: + try: + y.append(func.evaluate(xi)) + except ValueError: + y.append(np.nan) # Обработка разрывов + + plt.figure(figsize=(8, 6)) + plt.plot(x, y, label=func.get_expression()) + plt.xlabel("x") + plt.ylabel("f(x)") + plt.title("График функции") + plt.grid(True) + plt.legend() + plt.show() diff --git "a/\320\2403213/vlasov_356550/lab4/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2404 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" "b/\320\2403213/vlasov_356550/lab4/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2404 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" new file mode 100644 index 0000000..0573953 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab4/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2404 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" differ diff --git "a/\320\2403213/vlasov_356550/lab4/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2404 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" "b/\320\2403213/vlasov_356550/lab4/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2404 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" new file mode 100644 index 0000000..49a52d5 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab4/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2404 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" differ diff --git "a/\320\2403213/vlasov_356550/lab4/src/approximations/__init__.py" "b/\320\2403213/vlasov_356550/lab4/src/approximations/__init__.py" new file mode 100644 index 0000000..e69de29 diff --git "a/\320\2403213/vlasov_356550/lab4/src/approximations/calculations.py" "b/\320\2403213/vlasov_356550/lab4/src/approximations/calculations.py" new file mode 100644 index 0000000..7835602 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab4/src/approximations/calculations.py" @@ -0,0 +1,197 @@ +import numpy as np +from approximations.models import ApproximationModels +from approximations.metrics import MetricsCalculator + + +class ApproximationCalculator: + def __init__(self): + self.models = ApproximationModels().get_models() + self.metrics = MetricsCalculator() + + + @staticmethod + def _least_squares_linear(x, y): + """Метод наименьших квадратов для линейной аппроксимации y = a*x + b""" + n = len(x) + sum_x = np.sum(x) + sum_y = np.sum(y) + sum_xy = np.sum(x * y) + sum_x2 = np.sum(x ** 2) + + denom = n * sum_x2 - sum_x ** 2 + if denom == 0: + return [0, 0] + + a = (n * sum_xy - sum_x * sum_y) / denom + b = (sum_y * sum_x2 - sum_x * sum_xy) / denom + return [a, b] + + + @staticmethod + def _least_squares_quadratic(x, y): + """Метод наименьших квадратов для квадратичной аппроксимации y = a*x^2 + b*x + c""" + n = len(x) + sum_x = np.sum(x) + sum_x2 = np.sum(x ** 2) + sum_x3 = np.sum(x ** 3) + sum_x4 = np.sum(x ** 4) + sum_y = np.sum(y) + sum_xy = np.sum(x * y) + sum_x2y = np.sum(x ** 2 * y) + + A = np.array([ + [sum_x4, sum_x3, sum_x2], + [sum_x3, sum_x2, sum_x], + [sum_x2, sum_x, n] + ]) + B = np.array([sum_x2y, sum_xy, sum_y]) + + try: + coeffs = np.linalg.solve(A, B) + return coeffs.tolist() # [a, b, c] + except np.linalg.LinAlgError: + return [0, 0, 0] + + + @staticmethod + def _least_squares_cubic(x, y): + """Метод наименьших квадратов для кубической аппроксимации y = a*x^3 + b*x^2 + c*x + d""" + n = len(x) + sum_x = np.sum(x) + sum_x2 = np.sum(x ** 2) + sum_x3 = np.sum(x ** 3) + sum_x4 = np.sum(x ** 4) + sum_x5 = np.sum(x ** 5) + sum_x6 = np.sum(x ** 6) + sum_y = np.sum(y) + sum_xy = np.sum(x * y) + sum_x2y = np.sum(x ** 2 * y) + sum_x3y = np.sum(x ** 3 * y) + + A = np.array([ + [sum_x6, sum_x5, sum_x4, sum_x3], + [sum_x5, sum_x4, sum_x3, sum_x2], + [sum_x4, sum_x3, sum_x2, sum_x], + [sum_x3, sum_x2, sum_x, n] + ]) + B = np.array([sum_x3y, sum_x2y, sum_xy, sum_y]) + + try: + coeffs = np.linalg.solve(A, B) + return coeffs.tolist() # [a, b, c, d] + except np.linalg.LinAlgError: + return [0, 0, 0, 0] + + + @staticmethod + def _least_squares_exponential(x, y): + """Линеаризованная экспоненциальная аппроксимация y = a*exp(b*x)""" + # Линеаризация: ln(y) = ln(a) + b*x + y_safe = np.where(y > 0, y, 1e-10) # Избегаем log(0) + ln_y = np.log(y_safe) + + n = len(x) + sum_x = np.sum(x) + sum_lny = np.sum(ln_y) + sum_x_lny = np.sum(x * ln_y) + sum_x2 = np.sum(x ** 2) + + denom = n * sum_x2 - sum_x ** 2 + if denom == 0: + return [0, 0] + + b = (n * sum_x_lny - sum_x * sum_lny) / denom + ln_a = (sum_lny * sum_x2 - sum_x * sum_x_lny) / denom + a = np.exp(ln_a) + return [a, b] + + + @staticmethod + def _least_squares_logarithmic(x, y): + """Линеаризованная логарифмическая аппроксимация y = a*ln(x) + b""" + # Линеаризация: y = a*ln(x) + b + x_safe = np.where(x > 0, x, 1e-10) # Избегаем log(0) + ln_x = np.log(x_safe) + + n = len(ln_x) + sum_lnx = np.sum(ln_x) + sum_y = np.sum(y) + sum_lnx_y = np.sum(ln_x * y) + sum_lnx2 = np.sum(ln_x ** 2) + + denom = n * sum_lnx2 - sum_lnx ** 2 + if denom == 0: + return [0, 0] + + a = (n * sum_lnx_y - sum_lnx * sum_y) / denom + b = (sum_y * sum_lnx2 - sum_lnx * sum_lnx_y) / denom + return [a, b] + + + @staticmethod + def _least_squares_power(x, y): + """Линеаризованная степенная аппроксимация y = a*x^b""" + # Линеаризация: ln(y) = ln(a) + b*ln(x) + x_safe = np.where(x > 0, x, 1e-10) + y_safe = np.where(y > 0, y, 1e-10) + ln_x = np.log(x_safe) + ln_y = np.log(y_safe) + + # Применяем линейный метод наименьших квадратов для ln(x) и ln(y) + n = len(ln_x) + sum_lnx = np.sum(ln_x) + sum_lny = np.sum(ln_y) + sum_lnx_lny = np.sum(ln_x * ln_y) + sum_lnx2 = np.sum(ln_x ** 2) + + denom = n * sum_lnx2 - sum_lnx ** 2 + if denom == 0: + return [0, 0] + + b = (n * sum_lnx_lny - sum_lnx * sum_lny) / denom + ln_a = (sum_lny * sum_lnx2 - sum_lnx * sum_lnx_lny) / denom + a = np.exp(ln_a) + return [a, b] + + + def calculate_all(self, x, y): + results = {} + + for func_type, func in self.models.items(): + try: + if func_type == 'linear': + popt = self._least_squares_linear(x, y) + elif func_type == 'quadratic': + popt = self._least_squares_quadratic(x, y) + elif func_type == 'cubic': + popt = self._least_squares_cubic(x, y) + elif func_type == 'exponential': + popt = self._least_squares_exponential(x, y) + elif func_type == 'logarithmic': + popt = self._least_squares_logarithmic(x, y) + elif func_type == 'power': + popt = self._least_squares_power(x, y) + else: + raise ValueError(f"Unknown function type: {func_type}") + + y_pred = func(x, *popt) + + results[func_type] = { + 'coeffs': popt, + 'rmse': self.metrics.calculate_rmse(y, y_pred), + 'r2': self.metrics.calculate_r2(y, y_pred), + 'y_pred': y_pred + } + + if func_type == 'linear': + results[func_type]['pearson'] = self.metrics.calculate_pearson(x, y) + + except Exception: + results[func_type] = { + 'coeffs': [0] * (4 if func_type == 'cubic' else 3 if func_type == 'quadratic' else 2), + 'rmse': float('inf'), + 'r2': 0, + 'y_pred': np.zeros_like(y) + } + + return results diff --git "a/\320\2403213/vlasov_356550/lab4/src/approximations/metrics.py" "b/\320\2403213/vlasov_356550/lab4/src/approximations/metrics.py" new file mode 100644 index 0000000..91c7956 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab4/src/approximations/metrics.py" @@ -0,0 +1,38 @@ +import numpy as np + + +class MetricsCalculator: + @staticmethod + def calculate_rmse(y_true, y_pred): + return np.sqrt(np.mean((y_true - y_pred)**2)) + + @staticmethod + def calculate_pearson(x, y): + n = len(x) + mean_x = np.mean(x) + mean_y = np.mean(y) + cov = np.sum((x - mean_x) * (y - mean_y)) / n + std_x = np.sqrt(np.sum((x - mean_x)**2) / n) + std_y = np.sqrt(np.sum((y - mean_y)**2) / n) + + if std_x == 0 or std_y == 0: + return 0 + + return cov / (std_x * std_y) + + @staticmethod + def calculate_r2(y_true, y_pred): + ss_tot = np.sum((y_true - np.mean(y_true))**2) + ss_res = np.sum((y_true - y_pred)**2) + return 1 - ss_res / ss_tot if ss_tot != 0 else 0 + + @staticmethod + def interpret_r2(r2): + if r2 > 0.9: + return "(Excellent fit)" + elif r2 > 0.7: + return "(Good fit)" + elif r2 > 0.5: + return "(Moderate fit)" + else: + return "(Poor fit)" diff --git "a/\320\2403213/vlasov_356550/lab4/src/approximations/models.py" "b/\320\2403213/vlasov_356550/lab4/src/approximations/models.py" new file mode 100644 index 0000000..e178b46 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab4/src/approximations/models.py" @@ -0,0 +1,50 @@ +import numpy as np + + +class ApproximationModels: + # Линейная функция: y = a*x + b + @staticmethod + def linear(x, a, b): + return a * x + b + + + # Квадратичная функция: y = a*x² + b*x + c + @staticmethod + def quadratic(x, a, b, c): + return a * x**2 + b * x + c + + + # Кубическая функция: y = a*x³ + b*x² + c*x + d + @staticmethod + def cubic(x, a, b, c, d): + return a * x**3 + b * x**2 + c * x + d + + + # Экспоненциальная функция: y = a*exp(b*x) + @staticmethod + def exponential(x, a, b): + return a * np.exp(b * x) + + + # Логарифмическая функция: y = a*ln(x) + b + @staticmethod + def logarithmic(x, a, b): + x_safe = np.where(x > 0, x, 1e-10) # Избежание log(0) + return a * np.log(x_safe) + b + + + # Степенная функция: y = a*x^b + @staticmethod + def power(x, a, b): + x_safe = np.where(x > 0, x, 1e-10) # Избежание x <= 0 + return a * x_safe ** b + + def get_models(self): + return { + 'linear': self.linear, + 'quadratic': self.quadratic, + 'cubic': self.cubic, + 'exponential': self.exponential, + 'logarithmic': self.logarithmic, + 'power': self.power + } diff --git "a/\320\2403213/vlasov_356550/lab4/src/cli/__init__.py" "b/\320\2403213/vlasov_356550/lab4/src/cli/__init__.py" new file mode 100644 index 0000000..e69de29 diff --git "a/\320\2403213/vlasov_356550/lab4/src/cli/app.py" "b/\320\2403213/vlasov_356550/lab4/src/cli/app.py" new file mode 100644 index 0000000..4392804 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab4/src/cli/app.py" @@ -0,0 +1,145 @@ +import matplotlib.pyplot as plt +import numpy as np + +from approximations.calculations import ApproximationCalculator +from data.data_loader import DataLoader +from data.data_validation import DataValidator +from plotting.plotter import Plotter + + +class ConsoleApproximationApp: + def __init__(self): + # Инициализация компонентов приложения + self.data_loader = DataLoader() + self.data_validator = DataValidator() + self.calculator = ApproximationCalculator() + self.plotter = Plotter() + self.x = None + self.y = None + self.last_results = None + + + def run(self): + while True: + print("\n1. Загрузить данные из файла") + print("2. Ввести данные вручную") + print("3. Выполнить аппроксимацию") + print("4. Сохранить результаты в файл") + print("5. Выход") + choice = input("Выберите действие (1-5): ") + + if choice == "1": + file_path = input("Введите путь к файлу с данными (например, sample_data.txt): ") + try: + data = self.data_loader.load_from_file(file_path) + self.x, self.y = self.data_validator.validate(data) + print("Данные успешно загружены") + except Exception as e: + print(f"Ошибка: {str(e)}") + + elif choice == "2": + try: + data = self.data_loader.load_from_console() + self.x, self.y = self.data_validator.validate(data) + print("Данные успешно введены") + except Exception as e: + print(f"Ошибка: {str(e)}") + + elif choice == "3": + if self.x is None or self.y is None: + print("Ошибка: Данные не загружены") + continue + try: + self.last_results = self.calculator.calculate_all(self.x, self.y) + self.display_results(self.last_results, self.x, self.y) + self.plot_results() + except Exception as e: + print(f"Ошибка: {str(e)}") + + elif choice == "4": + if self.last_results is None: + print("Ошибка: Нет результатов для сохранения. Сначала выполните аппроксимацию.") + continue + try: + file_path = input("Введите имя файла для сохранения результатов (например, results.txt): ") + self.save_results(self.last_results, self.x, self.y, file_path) + print(f"Результаты успешно сохранены в {file_path}") + except Exception as e: + print(f"Ошибка при сохранении результатов: {str(e)}") + + elif choice == "5": + break + else: + print("Неверный выбор") + + + def display_results(self, results, x, y): + best_approx = min(results.items(), key=lambda x: x[1]['rmse']) + + for func_type, data in results.items(): + func_name = { + 'linear': 'Линейная', + 'quadratic': 'Квадратичная', + 'cubic': 'Кубическая', + 'exponential': 'Экспоненциальная', + 'logarithmic': 'Логарифмическая', + 'power': 'Степенная' + }[func_type] + + print(f"\n{func_name} аппроксимация:") + print(f"Коэффициенты: {data['coeffs']}") + print(f"Среднеквадратичное отклонение: {data['rmse']:.3f}") + print(f"R²: {data['r2']:.3f} {self.calculator.metrics.interpret_r2(data['r2'])}") + if func_type == 'linear': + print(f"Корреляция Пирсона: {data['pearson']:.3f}") + + epsilon = np.abs(y - data['y_pred']) + print("\nМассивы значений:") + print(f"x_i: {x}") + print(f"y_i: {y}") + print(f"φ(x_i): {data['y_pred']}") + print(f"ε_i: {epsilon}") + + print(f"\nЛучшая аппроксимация: {best_approx[0].upper()} " + f"(Среднеквадратичное отклонение: {best_approx[1]['rmse']:.3f})") + + + def save_results(self, results, x, y, file_path): + with open(file_path, 'w', encoding='utf-8') as f: + f.write("Результаты аппроксимации\n") + f.write("=" * 30 + "\n\n") + best_approx = min(results.items(), key=lambda x: x[1]['rmse']) + + for func_type, data in results.items(): + func_name = { + 'linear': 'Линейная', + 'quadratic': 'Квадратичная', + 'cubic': 'Кубическая', + 'exponential': 'Экспоненциальная', + 'logarithmic': 'Логарифмическая', + 'power': 'Степенная' + }[func_type] + + f.write(f"{func_name} аппроксимация:\n") + f.write(f"Коэффициенты: {data['coeffs']}\n") + f.write(f"Среднеквадратичное отклонение: {data['rmse']:.3f}\n") + f.write(f"R²: {data['r2']:.3f} {self.calculator.metrics.interpret_r2(data['r2'])}\n") + if func_type == 'linear': + f.write(f"Корреляция Пирсона: {data['pearson']:.3f}\n") + + epsilon = np.abs(y - data['y_pred']) + f.write("\nМассивы значений:\n") + f.write(f"x_i: {x.tolist()}\n") + f.write(f"y_i: {y.tolist()}\n") + f.write(f"φ(x_i): {data['y_pred'].tolist()}\n") + f.write(f"ε_i: {epsilon.tolist()}\n") + f.write("\n") + + f.write(f"Лучшая аппроксимация: {best_approx[0].upper()} " + f"(Среднеквадратичное отклонение: {best_approx[1]['rmse']:.3f})\n") + + + def plot_results(self): + fig, ax = plt.subplots(figsize=(8, 6)) + self.plotter.plot_approximations(ax, self.x, self.y, self.calculator.calculate_all(self.x, self.y)) + plt.show() diff --git "a/\320\2403213/vlasov_356550/lab4/src/data/__init__.py" "b/\320\2403213/vlasov_356550/lab4/src/data/__init__.py" new file mode 100644 index 0000000..e69de29 diff --git "a/\320\2403213/vlasov_356550/lab4/src/data/data_loader.py" "b/\320\2403213/vlasov_356550/lab4/src/data/data_loader.py" new file mode 100644 index 0000000..b044257 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab4/src/data/data_loader.py" @@ -0,0 +1,37 @@ +import numpy as np + + +class DataLoader: + @staticmethod + def load_from_file(file_path): + try: + data = np.loadtxt(file_path, delimiter=',', dtype=str) + data = np.array([[float(val.replace(',', '.')) for val in row] for row in data]) + return data + except Exception as e: + raise ValueError(f"Ошибка чтения файла: {str(e)}") + + + @staticmethod + def load_from_console(): + try: + n = int(input("Введите количество точек (8-12): ")) + if n < 8 or n > 12: + raise ValueError("Количество точек должно быть от 8 до 12") + + print("Введите пары x,y (через точку с запятой, например: 1.5; 2.3) построчно:") + data = [] + for i in range(n): + line = input(f"Точка {i + 1}: ") + x, y = line.split(';') + x = float(x.replace(',', '.')) + y = float(y.replace(',', '.')) + data.append([x, y]) + + return np.array(data) + except ValueError as e: + if "should be from 8 to 12" in str(e): + raise e + raise ValueError("Неверный формат данных. Используйте числа с запятой как разделителем") + except Exception as e: + raise ValueError(f"Ошибка ввода данных: {str(e)}") diff --git "a/\320\2403213/vlasov_356550/lab4/src/data/data_validation.py" "b/\320\2403213/vlasov_356550/lab4/src/data/data_validation.py" new file mode 100644 index 0000000..e8f30a3 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab4/src/data/data_validation.py" @@ -0,0 +1,10 @@ +class DataValidator: + @staticmethod + def validate(data): + # Проверка, что данные содержат 2 столбца (x, y) + if data.shape[1] != 2: + raise ValueError("Данные должны содержать 2 столбца (x, y)") + # Проверка, что количество точек от 8 до 12 + if len(data) < 8 or len(data) > 12: + raise ValueError("Данные должны содержать от 8 до 12 точек") + return data[:, 0], data[:, 1] diff --git "a/\320\2403213/vlasov_356550/lab4/src/main.py" "b/\320\2403213/vlasov_356550/lab4/src/main.py" new file mode 100644 index 0000000..9134a78 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab4/src/main.py" @@ -0,0 +1,6 @@ +from cli.app import ConsoleApproximationApp + + +if __name__ == "__main__": + app = ConsoleApproximationApp() + app.run() diff --git "a/\320\2403213/vlasov_356550/lab4/src/plotting/__init__.py" "b/\320\2403213/vlasov_356550/lab4/src/plotting/__init__.py" new file mode 100644 index 0000000..e69de29 diff --git "a/\320\2403213/vlasov_356550/lab4/src/plotting/plotter.py" "b/\320\2403213/vlasov_356550/lab4/src/plotting/plotter.py" new file mode 100644 index 0000000..c2e2f5f --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab4/src/plotting/plotter.py" @@ -0,0 +1,25 @@ +import numpy as np +from approximations.models import ApproximationModels + +class Plotter: + @staticmethod + def plot_approximations(ax, x, y, results): + ax.clear() + models = ApproximationModels() + + ax.scatter(x, y, color='black', label='Original Data') + + colors = ['r', 'g', 'b', 'c', 'm', 'y'] + x_fine = np.linspace(min(x) - 0.1 * (max(x) - min(x)), + max(x) + 0.1 * (max(x) - min(x)), 100) + + for i, (func_type, data) in enumerate(results.items()): + func = models.get_models()[func_type] + y_fine = func(x_fine, *data['coeffs']) + ax.plot(x_fine, y_fine, colors[i % len(colors)], + label=f'{func_type.capitalize()} (RMSE: {data["rmse"]:.3f})') + + ax.set_xlabel('x') + ax.set_ylabel('y') + ax.legend() + ax.grid(True) diff --git "a/\320\2403213/vlasov_356550/lab5/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2405 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" "b/\320\2403213/vlasov_356550/lab5/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2405 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" new file mode 100644 index 0000000..7407c12 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab5/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2405 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" differ diff --git "a/\320\2403213/vlasov_356550/lab5/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2405 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" "b/\320\2403213/vlasov_356550/lab5/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2405 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" new file mode 100644 index 0000000..3055b29 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab5/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2405 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" differ diff --git "a/\320\2403213/vlasov_356550/lab5/src/data_input.py" "b/\320\2403213/vlasov_356550/lab5/src/data_input.py" new file mode 100644 index 0000000..56152ec --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab5/src/data_input.py" @@ -0,0 +1,94 @@ +import numpy as np +from utils import parse_decimal + + +class DataInput: + @staticmethod + def keyboard_input(): + try: + n = int(input("Введите количество точек: ")) + if n < 2: + raise ValueError("Требуется как минимум 2 точки.") + + x_values = [] + y_values = [] + + for i in range(n): + x = parse_decimal(input(f"Введите x{i}: ")) + y = parse_decimal(input(f"Введите y{i}: ")) + if x is None or y is None: + raise ValueError("Некорректный формат числа. Используйте запятую для десятичной части.") + + x_values.append(x) + y_values.append(y) + + return x_values, y_values + except ValueError as e: + raise ValueError(f"Ошибка ввода: {str(e)}") + + + @staticmethod + def file_input(): + try: + filename = input("Введите имя файла (например, test-1.txt): ") + x_values = [] + y_values = [] + + with open(filename, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if not line: + continue + + values = line.split() + if len(values) != 2: + raise ValueError(f"Некорректный формат строки в файле: {line}") + + x = parse_decimal(values[0]) + y = parse_decimal(values[1]) + if x is None or y is None: + raise ValueError(f"Некорректный формат числа в строке: {line}") + + x_values.append(x) + y_values.append(y) + if len(x_values) < 2: + raise ValueError("Файл должен содержать как минимум 2 точки.") + + return x_values, y_values + except FileNotFoundError: + raise ValueError(f"Файл {filename} не найден.") + except ValueError as e: + raise ValueError(f"Ошибка чтения файла: {str(e)}") + except Exception as e: + raise ValueError(f"Неизвестная ошибка при чтении файла: {str(e)}") + + + @staticmethod + def function_based_input(): + print("Доступные функции:") + print("1. sin(x)") + print("2. exp(x)") + choice = input("Выберите функцию (1-2): ") + + try: + n = int(input("Введите количество точек: ")) + if n < 2: + raise ValueError("Требуется как минимум 2 точки.") + a = parse_decimal(input("Введите начало интервала: ")) + b = parse_decimal(input("Введите конец интервала: ")) + + if a is None or b is None or a >= b: + raise ValueError("Некорректный интервал.") + + x_values = np.linspace(a, b, n).tolist() + + if choice == '1': + y_values = [np.sin(x) for x in x_values] + elif choice == '2': + y_values = [np.exp(x) for x in x_values] + else: + raise ValueError("Неверный выбор функции.") + + return x_values, y_values + except ValueError as e: + raise ValueError(f"Ошибка ввода: {str(e)}") diff --git "a/\320\2403213/vlasov_356550/lab5/src/finite_differences.py" "b/\320\2403213/vlasov_356550/lab5/src/finite_differences.py" new file mode 100644 index 0000000..2c01015 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab5/src/finite_differences.py" @@ -0,0 +1,35 @@ +from decimal import Decimal + + +class FiniteDifferences: + def __init__(self, x_values, y_values): + self.x_values = [Decimal(str(x)) for x in x_values] + self.y_values = [Decimal(str(y)) for y in y_values] + self.n = len(x_values) + self.table = self._compute_table() + + + def _compute_table(self): + table = [[Decimal('0') for _ in range(self.n)] for _ in range(self.n)] + + for i in range(self.n): + table[i][0] = self.y_values[i] + + for j in range(1, self.n): + for i in range(self.n - j): + table[i][j] = table[i + 1][j - 1] - table[i][j - 1] + + return table + + + def display_table(self): + print("\nТаблица конечных разностей:") + print("x\t\ty\t\t" + "\t".join([f"Δ^{i}y" for i in range(1, self.n)])) + + for i in range(self.n): + row = [f"{self.x_values[i]:.4f}", f"{self.y_values[i]:.4f}"] + + for j in range(self.n - i - 1): + row.append(f"{self.table[i][j + 1]:.4f}") + + print("\t".join(row)) diff --git "a/\320\2403213/vlasov_356550/lab5/src/interpolation_methods.py" "b/\320\2403213/vlasov_356550/lab5/src/interpolation_methods.py" new file mode 100644 index 0000000..ae6df75 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab5/src/interpolation_methods.py" @@ -0,0 +1,206 @@ +from decimal import Decimal +from abc import ABC, abstractmethod + + +class Interpolation(ABC): + def __init__(self, x_values, y_values): + self.x_values = [Decimal(str(x)) for x in x_values] + self.y_values = [Decimal(str(y)) for y in y_values] + self.n = len(x_values) + + + @abstractmethod + def interpolate(self, x): + pass + + +class LagrangeInterpolation(Interpolation): + def interpolate(self, x): + x = Decimal(str(x)) + result = Decimal('0') + + for i in range(self.n): + term = self.y_values[i] + + for j in range(self.n): + if i != j: + term *= (x - self.x_values[j]) / (self.x_values[i] - self.x_values[j]) + + result += term + + return result + + +class NewtonDividedDiffInterpolation(Interpolation): + def __init__(self, x_values, y_values): + super().__init__(x_values, y_values) + self.divided_diff = self._compute_divided_differences() + + + def _compute_divided_differences(self): + n = self.n + table = [[Decimal('0') for _ in range(n)] for _ in range(n)] + + for i in range(n): + table[i][0] = self.y_values[i] + + for j in range(1, n): + for i in range(n - j): + table[i][j] = (table[i + 1][j - 1] - table[i][j - 1]) / (self.x_values[i + j] - self.x_values[i]) + + return table[0] + + + def interpolate(self, x): + x = Decimal(str(x)) + result = self.divided_diff[0] + term = Decimal('1') + + for i in range(1, self.n): + term *= (x - self.x_values[i - 1]) + result += self.divided_diff[i] * term + + return result + + +class GaussInterpolation(Interpolation): + def interpolate(self, x): + x = Decimal(str(x)) + mid = self.n // 2 + + if x < self.x_values[mid]: + return self._gauss_forward(x) + else: + return self._gauss_backward(x) + + + def _gauss_forward(self, x): + h = self.x_values[1] - self.x_values[0] + t = (x - self.x_values[self.n // 2]) / h + result = self.y_values[self.n // 2] + term = t + max_order = min(self.n // 2, self.n - self.n // 2 - 1) + + for i in range(1, max_order + 1): + delta = self._finite_difference(i, self.n // 2 - i) + result += (term * delta) / Decimal(str(self._factorial(i))) + term *= (t - i) * (t + i - 1) / Decimal(str(i)) + + return result + + + def _gauss_backward(self, x): + h = self.x_values[1] - self.x_values[0] + t = (x - self.x_values[self.n // 2]) / h + result = self.y_values[self.n // 2] + term = t + max_order = min(self.n // 2, self.n - self.n // 2 - 1) + + for i in range(1, max_order + 1): + delta = self._finite_difference(i, self.n // 2) + result += (term * delta) / Decimal(str(self._factorial(i))) + term *= (t + i) * (t - i + 1) / Decimal(str(i)) + + return result + + + def _finite_difference(self, order, start): + if order == 0: + if 0 <= start < self.n: + return self.y_values[start] + + raise IndexError("Индекс вне диапазона в конечной разности") + + if start + 1 >= self.n or start < 0: + raise IndexError("Индекс вне диапазона в конечной разности") + + return self._finite_difference(order - 1, start + 1) - self._finite_difference(order - 1, start) + + + def _factorial(self, n): + if n <= 1: + return 1 + + return n * self._factorial(n - 1) + + +class StirlingInterpolation(Interpolation): + def interpolate(self, x): + x = Decimal(str(x)) + h = self.x_values[1] - self.x_values[0] + mid = self.n // 2 + t = (x - self.x_values[mid]) / h + result = self.y_values[mid] + term = t + max_order = min(self.n // 2, self.n - mid - 1) + + for i in range(1, max_order + 1): + delta1 = self._finite_difference(i, mid - i) + delta2 = self._finite_difference(i, mid - i + 1) + avg_delta = (delta1 + delta2) / Decimal('2') + result += (term * avg_delta) / Decimal(str(self._factorial(i))) + term *= (t - i) * (t + i - 1) / Decimal(str(i)) + + return result + + + def _finite_difference(self, order, start): + if order == 0: + if 0 <= start < self.n: + return self.y_values[start] + + raise IndexError("Индекс вне диапазона в конечной разности") + + if start + 1 >= self.n or start < 0: + raise IndexError("Индекс вне диапазона в конечной разности") + + return self._finite_difference(order - 1, start + 1) - self._finite_difference(order - 1, start) + + + def _factorial(self, n): + if n <= 1: + return 1 + + return n * self._factorial(n - 1) + + +class BesselInterpolation(Interpolation): + def interpolate(self, x): + x = Decimal(str(x)) + h = self.x_values[1] - self.x_values[0] + mid = self.n // 2 + t = (x - self.x_values[mid]) / h + + if mid + 1 >= self.n: + mid = self.n - 2 + + result = (self.y_values[mid] + self.y_values[mid + 1]) / Decimal('2') + term = t - Decimal('0.5') + max_order = min(self.n // 2, self.n - mid - 2) + + for i in range(1, max_order + 1): + delta = self._finite_difference(i, mid - i + 1) + result += (term * delta) / Decimal(str(self._factorial(i))) + term *= (t + i - 1) * (t - i) / Decimal(str(i)) + + return result + + + def _finite_difference(self, order, start): + if order == 0: + if 0 <= start < self.n: + return self.y_values[start] + + raise IndexError("Индекс вне диапазона в конечной разности") + + if start + 1 >= self.n or start < 0: + raise IndexError("Индекс вне диапазона в конечной разности") + + return self._finite_difference(order - 1, start + 1) - self._finite_difference(order - 1, start) + + + def _factorial(self, n): + if n <= 1: + return 1 + + return n * self._factorial(n - 1) diff --git "a/\320\2403213/vlasov_356550/lab5/src/main.py" "b/\320\2403213/vlasov_356550/lab5/src/main.py" new file mode 100644 index 0000000..fd407b9 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab5/src/main.py" @@ -0,0 +1,93 @@ +import sys +from data_input import DataInput +from interpolation_methods import LagrangeInterpolation, NewtonDividedDiffInterpolation, GaussInterpolation, StirlingInterpolation, BesselInterpolation +from finite_differences import FiniteDifferences +from plotting import Plotter +from utils import parse_decimal, validate_input + + +def main(): + print("Лабораторная работа №5, Вариант 2") + data_input = DataInput() + plotter = Plotter() + + while True: + print("\nВыберите способ ввода данных:") + print("1. Ввод с клавиатуры") + print("2. Ввод из файла") + print("3. Ввод на основе функции") + print("4. Выход") + choice = input("Введите выбор (1-4): ") + + if choice == '4': + print("Выход из программы...") + sys.exit(0) + + try: + if choice == '1': + x_values, y_values = data_input.keyboard_input() + elif choice == '2': + x_values, y_values = data_input.file_input() + elif choice == '3': + x_values, y_values = data_input.function_based_input() + else: + print("Неверный выбор. Попробуйте снова.") + continue + + if not validate_input(x_values, y_values): + print("Некорректные входные данные. Попробуйте снова.") + continue + + fd = FiniteDifferences(x_values, y_values) + fd.display_table() + + x_input = input("Введите значение x для интерполяции (используйте запятую для десятичной части): ") + x_val = parse_decimal(x_input) + if x_val is None: + print("Некорректное значение x. Используйте числа с запятой для десятичной части.") + continue + + all_methods = [ + ("Лагранж", LagrangeInterpolation(x_values, y_values)), + ("Ньютон", NewtonDividedDiffInterpolation(x_values, y_values)), + ("Гаусс", GaussInterpolation(x_values, y_values)), + ("Стирлинг", StirlingInterpolation(x_values, y_values)), + ("Бессель", BesselInterpolation(x_values, y_values)) + ] + + print("\nРезультаты интерполяции:") + for name, method in all_methods: + try: + result = method.interpolate(x_val) + print(f"{name}: y({x_val}) = {result:.6f}") + except Exception as e: + print(f"Ошибка в {name}: {str(e)}") + + print("\nВыберите методы интерполяции для отображения на графике (введите номера через пробел, например, 1 2 3):") + for i, (name, _) in enumerate(all_methods, 1): + print(f"{i}. {name}") + + selected_indices = input("Введите номера методов: ").strip().split() + + selected_methods = [] + try: + for idx in selected_indices: + idx = int(idx) - 1 + if 0 <= idx < len(all_methods): + selected_methods.append(all_methods[idx][1]) + else: + print(f"Неверный номер метода: {idx + 1}. Пропускаем.") + if not selected_methods: + print("Не выбрано ни одного метода. График не будет построен.") + continue + except ValueError: + print("Некорректный ввод номеров методов. Используйте числа, разделенные пробелами.") + continue + + plotter.plot_interpolation(x_values, y_values, selected_methods, x_val) + + except Exception as e: + print(f"Произошла ошибка: {str(e)}") + +if __name__ == "__main__": + main() diff --git "a/\320\2403213/vlasov_356550/lab5/src/plotting.py" "b/\320\2403213/vlasov_356550/lab5/src/plotting.py" new file mode 100644 index 0000000..edc9aee --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab5/src/plotting.py" @@ -0,0 +1,37 @@ +import matplotlib.pyplot as plt +import numpy as np +from decimal import Decimal + + +class Plotter: + @staticmethod + def plot_interpolation(x_values, y_values, methods, x_val): + x_min = float(min(x_values)) - 0.1 + x_max = float(max(x_values)) + 0.1 + x_fine = np.linspace(x_min, x_max, 100) + + plt.figure(figsize=(10, 6)) + plt.scatter(x_values, y_values, color='red', label='Точки данных') + + colors = ['blue', 'green', 'purple', 'orange', 'cyan'] + for i, method in enumerate(methods): + try: + y_fine = [] + for x in x_fine: + try: + y = float(method.interpolate(Decimal(str(x)))) + y_fine.append(y) + except Exception as e: + print(f"Ошибка интерполяции для {method.__class__.__name__} при x={x}: {str(e)}") + y_fine.append(np.nan) + plt.plot(x_fine, y_fine, color=colors[i % len(colors)], label=method.__class__.__name__) + except Exception as e: + print(f"Ошибка построения графика для {method.__class__.__name__}: {str(e)}") + + plt.axvline(x=float(x_val), color='black', linestyle='--', label=f'x={x_val:.3f}') + plt.xlabel('x') + plt.ylabel('y') + plt.title('Интерполяционные многочлены') + plt.legend() + plt.grid(True) + plt.show() diff --git "a/\320\2403213/vlasov_356550/lab5/src/utils.py" "b/\320\2403213/vlasov_356550/lab5/src/utils.py" new file mode 100644 index 0000000..bf5fad0 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab5/src/utils.py" @@ -0,0 +1,19 @@ +from decimal import Decimal, InvalidOperation + + +def parse_decimal(value): + try: + value = value.replace(',', '.') + return Decimal(value) + except (InvalidOperation, ValueError): + return None + + +def validate_input(x_values, y_values): + if len(x_values) != len(y_values) or len(x_values) < 2: + return False + + if len(set(x_values)) != len(x_values): + return False + + return True diff --git "a/\320\2403213/vlasov_356550/lab6/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2406 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" "b/\320\2403213/vlasov_356550/lab6/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2406 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" new file mode 100644 index 0000000..8a87ac9 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab6/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2406 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).docx" differ diff --git "a/\320\2403213/vlasov_356550/lab6/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2406 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" "b/\320\2403213/vlasov_356550/lab6/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2406 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" new file mode 100644 index 0000000..9862fd9 Binary files /dev/null and "b/\320\2403213/vlasov_356550/lab6/reports/\320\222\321\213\321\207\320\234\320\260\321\202 \320\233\320\2406 (\320\222\320\273\320\260\321\201\320\276\320\262 \320\224.\320\235., P3213).pdf" differ diff --git "a/\320\2403213/vlasov_356550/lab6/src/main.py" "b/\320\2403213/vlasov_356550/lab6/src/main.py" new file mode 100644 index 0000000..06cb69a --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab6/src/main.py" @@ -0,0 +1,64 @@ +from ode import odes, ODE +from solvers import ImprovedEuler, RungeKutta4, Milne +from utils import validate_float, validate_interval, validate_positive, print_table +from plotter import plot_solutions +from typing import List, Tuple + + +def main(): + print("Доступные ОДУ:") + for i, ode in enumerate(odes, 1): + print(f"{i}. {ode.name}") + + try: + choice = int(input("Выберите ОДУ (1-3): ")) - 1 + if choice not in range(len(odes)): + raise ValueError("Некорректный выбор ОДУ") + ode = odes[choice] + + x0 = validate_float(input("Введите x0: "), "x0") + y0 = validate_float(input("Введите y0: "), "y0") + xn = validate_float(input("Введите xn: "), "xn") + validate_interval(x0, xn) + h = validate_float(input("Введите шаг h: "), "h") + validate_positive(h, "h") + epsilon = validate_float(input("Введите точность epsilon: "), "epsilon") + validate_positive(epsilon, "epsilon") + + solvers = [ + ("Усовершенствованный Эйлер", ImprovedEuler()), + ("Рунге-Кутта 4", RungeKutta4()), + ("Милн", Milne()) + ] + solutions = [] + + for name, solver in solvers: + x, y, max_error = solver.solve(ode, x0, y0, xn, h, epsilon) + y_exact = [ode.exact(xi) for xi in x] + f_vals = [ode.f(xi, yi) for xi, yi in zip(x, y)] + print_table(x, y, y_exact, f_vals, ode) + print(f"Максимальная погрешность ({name}): {max_error:.6f}") + solutions.append((name, x, y)) + + print("\nКакие методы отобразить на графике? (можно выбрать несколько):") + for i, (name, _) in enumerate(solvers, 1): + print(f"{i}. {name}") + print("Введите номера методов через пробел (например, '1 2 3' для всех):") + method_choices = input().strip().split() + method_choices = [int(choice) for choice in method_choices if choice.isdigit()] + methods_to_plot = [solvers[i - 1][0] for i in method_choices if 1 <= i <= len(solvers)] + if not methods_to_plot: + print("Не выбрано ни одного метода для отображения. График будет показан только с точным решением.") + + plot_solutions(ode, solutions, x0, xn, methods_to_plot) + print("\nГрафики отображены.") + + + except ValueError as e: + print(f"Ошибка: {e}") + except Exception as e: + print(f"Непредвиденная ошибка: {e}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git "a/\320\2403213/vlasov_356550/lab6/src/ode.py" "b/\320\2403213/vlasov_356550/lab6/src/ode.py" new file mode 100644 index 0000000..9b9587c --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab6/src/ode.py" @@ -0,0 +1,26 @@ +from typing import Callable +import numpy as np + +class ODE: + def __init__(self, name: str, f: Callable[[float, float], float], exact: Callable[[float], float]): + self.name = name + self.f = f + self.exact = exact + +odes = [ + ODE( + name="y' = y + (1+x)y^2", + f=lambda x, y: y + (1 + x) * y**2, + exact=lambda x: -1 / (x + np.exp(-x)) + ), + ODE( + name="y' = -2xy", + f=lambda x, y: -2 * x * y, + exact=lambda x: np.exp(-x**2) + ), + ODE( + name="y' = x + y", + f=lambda x, y: x + y, + exact=lambda x: -x - 1 + 2 * np.exp(x) + ) +] diff --git "a/\320\2403213/vlasov_356550/lab6/src/plotter.py" "b/\320\2403213/vlasov_356550/lab6/src/plotter.py" new file mode 100644 index 0000000..c6e2edb --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab6/src/plotter.py" @@ -0,0 +1,20 @@ +import matplotlib.pyplot as plt +import numpy as np +from typing import List, Tuple +from ode import ODE + +def plot_solutions(ode: ODE, solutions: List[Tuple[str, List[float], List[float]]], x0: float, xn: float, methods_to_plot: List[str]): + plt.figure(figsize=(10, 6)) + x_fine = np.linspace(x0, xn, 500) + y_exact = [ode.exact(x) for x in x_fine] + plt.plot(x_fine, y_exact, 'k-', label='Точное решение') + colors = ['b-', 'r-', 'g-'] + for (method, x, y), color in zip(solutions, colors): + if method in methods_to_plot: + plt.plot(x, y, color, label=method) + plt.title(f"Решения для {ode.name}") + plt.xlabel('x') + plt.ylabel('y') + plt.legend() + plt.grid(True) + plt.show() \ No newline at end of file diff --git "a/\320\2403213/vlasov_356550/lab6/src/solvers.py" "b/\320\2403213/vlasov_356550/lab6/src/solvers.py" new file mode 100644 index 0000000..26d5067 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab6/src/solvers.py" @@ -0,0 +1,96 @@ +from abc import ABC, abstractmethod +from typing import List, Tuple +from ode import ODE +import numpy as np + + +class ODESolver(ABC): + @abstractmethod + def solve(self, ode: ODE, x0: float, y0: float, xn: float, h: float, epsilon: float) -> Tuple[ + List[float], List[float], float]: + pass + + +class ImprovedEuler(ODESolver): + def solve(self, ode: ODE, x0: float, y0: float, xn: float, h: float, epsilon: float) -> Tuple[ + List[float], List[float], float]: + x = [x0] + y = [y0] + max_error = 0.0 + while x[-1] < xn - 1e-10: + xi, yi = x[-1], y[-1] + y_pred = yi + h * ode.f(xi, yi) + y_next = yi + (h / 2) * (ode.f(xi, yi) + ode.f(xi + h, y_pred)) + y_half = yi + (h / 2) * ode.f(xi, yi) + y_half = y_half + (h / 2) * ( + ode.f(xi + h / 2, y_half) + ode.f(xi + h, y_half + (h / 2) * ode.f(xi + h / 2, y_half))) + error = abs(y_next - y_half) / (2 ** 2 - 1) + if error > epsilon: + h /= 2 + continue + x.append(xi + h) + y.append(y_next) + max_error = max(max_error, abs(ode.exact(xi + h) - y_next)) + return x, y, max_error + + +class RungeKutta4(ODESolver): + def solve(self, ode: ODE, x0: float, y0: float, xn: float, h: float, epsilon: float) -> Tuple[ + List[float], List[float], float]: + x = [x0] + y = [y0] + max_error = 0.0 + while x[-1] < xn - 1e-10: + xi, yi = x[-1], y[-1] + k1 = h * ode.f(xi, yi) + k2 = h * ode.f(xi + h / 2, yi + k1 / 2) + k3 = h * ode.f(xi + h / 2, yi + k2 / 2) + k4 = h * ode.f(xi + h, yi + k3) + y_next = yi + (k1 + 2 * k2 + 2 * k3 + k4) / 6 + y_half = yi + for _ in range(2): + k1_h = (h / 2) * ode.f(xi, y_half) + k2_h = (h / 2) * ode.f(xi + h / 4, y_half + k1_h / 2) + k3_h = (h / 2) * ode.f(xi + h / 4, y_half + k2_h / 2) + k4_h = (h / 2) * ode.f(xi + h / 2, y_half + k3_h) + y_half = y_half + (k1_h + 2 * k2_h + 2 * k3_h + k4_h) / 6 + error = abs(y_next - y_half) / (2 ** 4 - 1) + if error > epsilon: + h /= 2 + continue + x.append(xi + h) + y.append(y_next) + max_error = max(max_error, abs(ode.exact(xi + h) - y_next)) + return x, y, max_error + + +class Milne(ODESolver): + def solve(self, ode: ODE, x0: float, y0: float, xn: float, h: float, epsilon: float) -> Tuple[ + List[float], List[float], float]: + x = [x0] + y = [y0] + rk4 = RungeKutta4() + x_rk4, y_rk4, _ = rk4.solve(ode, x0, y0, x0 + 3 * h, h, epsilon) + x.extend(x_rk4[1:]) + y.extend(y_rk4[1:]) + f_vals = [ode.f(x[i], y[i]) for i in range(len(x))] + max_error = max(abs(ode.exact(x[i]) - y[i]) for i in range(len(x))) + + while x[-1] < xn - 1e-10: + xi = x[-1] + h + y_pred = y[-4] + (4 * h / 3) * (2 * f_vals[-3] - f_vals[-2] + 2 * f_vals[-1]) + f_pred = ode.f(xi, y_pred) + y_corr = y[-2] + (h / 3) * (f_vals[-2] + 4 * f_vals[-1] + f_pred) + error = abs(ode.exact(xi) - y_corr) + if error > epsilon: + h /= 2 + x_rk4, y_rk4, _ = rk4.solve(ode, x[0], y[0], x[-1] + 3 * h, h, epsilon) + x, y = x_rk4, y_rk4 + f_vals = [ode.f(x[i], y[i]) for i in range(len(x))] + max_error = max(abs(ode.exact(x[i]) - y[i]) for i in range(len(x))) + continue + x.append(xi) + y.append(y_corr) + f_vals.append(f_pred) + max_error = max(max_error, error) + return x, y, max_error diff --git "a/\320\2403213/vlasov_356550/lab6/src/tests.py" "b/\320\2403213/vlasov_356550/lab6/src/tests.py" new file mode 100644 index 0000000..edd6a44 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab6/src/tests.py" @@ -0,0 +1,125 @@ +from ode import odes +from solvers import ImprovedEuler, RungeKutta4, Milne +from utils import validate_float, validate_interval, validate_positive, print_table +from plotter import plot_solutions +from typing import List, Tuple + + +def run_test(ode_index: int, x0: str, y0: str, xn: str, h: str, epsilon: str, test_name: str): + print(f"\n=== Тест: {test_name} ===") + try: + ode = odes[ode_index] + x0_val = validate_float(x0, "x0") + y0_val = validate_float(y0, "y0") + xn_val = validate_float(xn, "xn") + validate_interval(x0_val, xn_val) + h_val = validate_float(h, "h") + validate_positive(h_val, "h") + epsilon_val = validate_float(epsilon, "epsilon") + validate_positive(epsilon_val, "epsilon") + + solvers = [ + ("Усовершенствованный Эйлер", ImprovedEuler()), + ("Рунге-Кутта 4", RungeKutta4()), + ("Милн", Milne()) + ] + solutions = [] + + for name, solver in solvers: + x, y, max_error = solver.solve(ode, x0_val, y0_val, xn_val, h_val, epsilon_val) + y_exact = [ode.exact(xi) for xi in x] + f_vals = [ode.f(xi, yi) for xi, yi in zip(x, y)] + print_table(x, y, y_exact, f_vals, ode) + print(f"Максимальная погрешность ({name}): {max_error:.6f}") + solutions.append((name, x, y)) + + plot_solutions(ode, solutions, x0_val, xn_val) + print("Тест успешно завершен.") + + except ValueError as e: + print(f"Ошибка в тесте: {e}") + except Exception as e: + print(f"Непредвиденная ошибка: {e}") + + +def run_all_tests(): + # Тест 1: Корректные данные, первая ОДУ, большой шаг + run_test( + ode_index=0, + x0="1", + y0="-1", + xn="1,5", + h="0,1", + epsilon="0,001", + test_name="Корректные данные для y' = y + (1+x)y^2, h=0.1" + ) + + # Тест 2: Корректные данные, вторая ОДУ, малый шаг + run_test( + ode_index=1, + x0="0", + y0="1", + xn="1", + h="0,01", + epsilon="0,0001", + test_name="Корректные данные для y' = -2xy, h=0.01" + ) + + # Тест 3: Корректные данные, третья ОДУ, средний шаг + run_test( + ode_index=2, + x0="0", + y0="1", + xn="2", + h="0,05", + epsilon="0,0005", + test_name="Корректные данные для y' = x + y, h=0.05" + ) + + # Тест 4: Некорректный шаг (h=0) + run_test( + ode_index=0, + x0="1", + y0="-1", + xn="1,5", + h="0", + epsilon="0,001", + test_name="Некорректный шаг h=0" + ) + + # Тест 5: Некорректный интервал (x0 >= xn) + run_test( + ode_index=0, + x0="2", + y0="-1", + xn="1", + h="0,1", + epsilon="0,001", + test_name="Некорректный интервал x0 >= xn" + ) + + # Тест 6: Некорректное значение epsilon (не число) + run_test( + ode_index=0, + x0="1", + y0="-1", + xn="1,5", + h="0,1", + epsilon="abc", + test_name="Некорректное значение epsilon" + ) + + # Тест 7: Числа с запятой + run_test( + ode_index=0, + x0="1,0", + y0="-1,0", + xn="1,5", + h="0,1", + epsilon="0,001", + test_name="Числа с запятой" + ) + + +if __name__ == "__main__": + run_all_tests() \ No newline at end of file diff --git "a/\320\2403213/vlasov_356550/lab6/src/utils.py" "b/\320\2403213/vlasov_356550/lab6/src/utils.py" new file mode 100644 index 0000000..7446ba8 --- /dev/null +++ "b/\320\2403213/vlasov_356550/lab6/src/utils.py" @@ -0,0 +1,25 @@ +from typing import List +from ode import ODE + +def validate_float(value: str, name: str) -> float: + try: + value = value.replace(',', '.') + return float(value) + except ValueError: + raise ValueError(f"Некорректное значение {name}: должно быть числом (используйте запятую или точку как разделитель)") + +def validate_interval(x0: float, xn: float): + if x0 >= xn: + raise ValueError("x0 должно быть меньше xn") + +def validate_positive(value: float, name: str): + if value <= 0: + raise ValueError(f"{name} должно быть положительным") + +def print_table(x: List[float], y: List[float], y_exact: List[float], f_vals: List[float], ode: ODE): + print(f"\nРезультаты для {ode.name}:") + print("| i | x_i | y_i | f(x_i, y_i) | y_exact | |y_exact - y_i| |") + print("|---|-------|-------|-------------|---------|----------------|") + for i in range(len(x)): + error = abs(y_exact[i] - y[i]) + print(f"| {i} | {x[i]:.4f} | {y[i]:.6f} | {f_vals[i]:.6f} | {y_exact[i]:.6f} | {error:.6f} |")