This commit is contained in:
2026-01-07 15:08:09 +03:00
parent 61cc472669
commit 029815e4d7
7 changed files with 1664 additions and 0 deletions

View File

@@ -0,0 +1,345 @@
#!/usr/bin/env python3
"""
Градиентный спуск для одномерной функции с тремя методами выбора шага:
1. Константный шаг
2. Золотое сечение (одномерная оптимизация)
3. Правило Армихо
Функция: f(x) = sqrt(x^2 + 9) / 4 + (5 - x) / 5
Область: [-3, 8]
"""
import sys
from pathlib import Path
# Add parent directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))
import matplotlib.pyplot as plt
import numpy as np
from common.functions import TaskFunction1D
from common.gradient_descent import (
GradientDescentResult1D,
gradient_descent_1d,
heavy_ball_1d,
)
# ============================================================================
# НАСТРОЙКИ
# ============================================================================
# Стартовая точка
X0 = -1.0
# Параметры сходимости
EPS_X = 0.05
EPS_F = 0.001
MAX_ITERS = 100
# Шаг для константного метода (небольшой, чтобы было 3-4+ итерации)
CONSTANT_STEP = 0.5
# Параметры для правила Армихо
ARMIJO_PARAMS = {
"d_init": 2.0,
"epsilon": 0.1,
"theta": 0.5,
}
# Границы для золотого сечения
GOLDEN_SECTION_BOUNDS = (0.0, 30.0)
# Параметры для метода тяжёлого шарика
HEAVY_BALL_ALPHA = 0.5
HEAVY_BALL_BETA = 0.8
# Папка для сохранения графиков
OUTPUT_DIR = Path(__file__).parent / "plots"
# ============================================================================
# ВИЗУАЛИЗАЦИЯ
# ============================================================================
def plot_iteration(
func: TaskFunction1D,
result: GradientDescentResult1D,
iter_idx: int,
output_path: Path,
):
"""Построить график для одной итерации."""
info = result.iterations[iter_idx]
# Диапазон для графика
a, b = func.domain
x_plot = np.linspace(a, b, 500)
y_plot = [func(x) for x in x_plot]
plt.figure(figsize=(10, 6))
# Функция
plt.plot(x_plot, y_plot, "b-", linewidth=2, label="f(x)")
# Траектория до текущей точки
trajectory_x = [result.iterations[i].x for i in range(iter_idx + 1)]
trajectory_y = [result.iterations[i].f_x for i in range(iter_idx + 1)]
if len(trajectory_x) > 1:
plt.plot(
trajectory_x,
trajectory_y,
"g--",
linewidth=1.5,
alpha=0.7,
label="Траектория",
)
# Предыдущие точки
for i, (x, y) in enumerate(zip(trajectory_x[:-1], trajectory_y[:-1])):
plt.plot(x, y, "go", markersize=6, alpha=0.5)
# Текущая точка
plt.plot(
info.x,
info.f_x,
"ro",
markersize=12,
label=f"x = {info.x:.4f}, f(x) = {info.f_x:.4f}",
)
# Направление градиента (касательная)
grad_scale = 0.5
x_grad = np.array([info.x - grad_scale, info.x + grad_scale])
y_grad = info.f_x + info.grad * (x_grad - info.x)
plt.plot(
x_grad, y_grad, "m-", linewidth=2, alpha=0.6, label=f"f'(x) = {info.grad:.4f}"
)
plt.xlabel("x", fontsize=12)
plt.ylabel("f(x)", fontsize=12)
plt.title(
f"{result.method} — Итерация {info.iteration}\nШаг: {info.step_size:.6f}",
fontsize=14,
fontweight="bold",
)
plt.legend(fontsize=10, loc="upper right")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(output_path, dpi=150)
plt.close()
def plot_final_result(
func: TaskFunction1D,
result: GradientDescentResult1D,
output_path: Path,
):
"""Построить итоговый график с полной траекторией."""
a, b = func.domain
x_plot = np.linspace(a, b, 500)
y_plot = [func(x) for x in x_plot]
plt.figure(figsize=(10, 6))
# Функция
plt.plot(x_plot, y_plot, "b-", linewidth=2, label="f(x)")
# Траектория
trajectory_x = [it.x for it in result.iterations]
trajectory_y = [it.f_x for it in result.iterations]
plt.plot(
trajectory_x, trajectory_y, "g-", linewidth=2, alpha=0.7, label="Траектория"
)
# Все точки
for i, (x, y) in enumerate(zip(trajectory_x[:-1], trajectory_y[:-1])):
plt.plot(x, y, "go", markersize=8, alpha=0.6)
# Финальная точка
plt.plot(
result.x_star,
result.f_star,
"r*",
markersize=20,
label=f"x* = {result.x_star:.6f}\nf(x*) = {result.f_star:.6f}",
)
plt.xlabel("x", fontsize=12)
plt.ylabel("f(x)", fontsize=12)
plt.title(
f"{result.method} — Результат\nИтераций: {len(result.iterations) - 1}",
fontsize=14,
fontweight="bold",
)
plt.legend(fontsize=10, loc="upper right")
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig(output_path, dpi=150)
plt.close()
def run_and_visualize(
func: TaskFunction1D,
method: str,
method_name_short: str,
**kwargs,
):
"""Запустить метод и создать визуализации."""
result = gradient_descent_1d(
func=func,
x0=X0,
step_method=method,
eps_x=EPS_X,
eps_f=EPS_F,
max_iters=MAX_ITERS,
**kwargs,
)
# Создаём папку для этого метода
method_dir = OUTPUT_DIR / method_name_short
method_dir.mkdir(parents=True, exist_ok=True)
# Печатаем информацию
print(f"\n{'=' * 80}")
print(f"{result.method}")
print("=" * 80)
for info in result.iterations[:-1]: # Без финальной точки
print(
f"Итерация {info.iteration:3d}: "
f"x = {info.x:10.6f}, f(x) = {info.f_x:10.6f}, "
f"f'(x) = {info.grad:10.6f}, шаг = {info.step_size:.6f}"
)
# Строим график для каждой итерации
plot_iteration(
func,
result,
info.iteration - 1,
method_dir / f"iteration_{info.iteration:02d}.png",
)
# Итоговый результат
print("-" * 80)
print(f"x* = {result.x_star:.6f}")
print(f"f(x*) = {result.f_star:.6f}")
print(f"Итераций: {len(result.iterations) - 1}")
# Финальный график
plot_final_result(func, result, method_dir / "final_result.png")
print(f"Графики сохранены в: {method_dir}")
return result
def run_and_visualize_heavy_ball(
func: TaskFunction1D,
method_name_short: str,
alpha: float,
beta: float,
):
"""Запустить метод тяжёлого шарика и создать визуализации."""
result = heavy_ball_1d(
func=func,
x0=X0,
alpha=alpha,
beta=beta,
eps_x=EPS_X,
eps_f=EPS_F,
max_iters=MAX_ITERS,
)
# Создаём папку для этого метода
method_dir = OUTPUT_DIR / method_name_short
method_dir.mkdir(parents=True, exist_ok=True)
# Печатаем информацию
print(f"\n{'=' * 80}")
print(f"{result.method}")
print("=" * 80)
for info in result.iterations[:-1]:
print(
f"Итерация {info.iteration:3d}: "
f"x = {info.x:10.6f}, f(x) = {info.f_x:10.6f}, "
f"f'(x) = {info.grad:10.6f}, шаг = {info.step_size:.6f}"
)
plot_iteration(
func,
result,
info.iteration - 1,
method_dir / f"iteration_{info.iteration:02d}.png",
)
# Итоговый результат
print("-" * 80)
print(f"x* = {result.x_star:.6f}")
print(f"f(x*) = {result.f_star:.6f}")
print(f"Итераций: {len(result.iterations) - 1}")
# Финальный график
plot_final_result(func, result, method_dir / "final_result.png")
print(f"Графики сохранены в: {method_dir}")
return result
def main():
"""Главная функция."""
func = TaskFunction1D()
print("=" * 80)
print("ГРАДИЕНТНЫЙ СПУСК ДЛЯ ОДНОМЕРНОЙ ФУНКЦИИ")
print("=" * 80)
print(f"Функция: {func.name}")
print(f"Область: {func.domain}")
print(f"Стартовая точка: x₀ = {X0}")
print(f"Параметры: eps_x = {EPS_X}, eps_f = {EPS_F}, max_iters = {MAX_ITERS}")
# Создаём папку для графиков
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# 1. Константный шаг
run_and_visualize(
func,
method="constant",
method_name_short="constant",
step_size=CONSTANT_STEP,
)
# 2. Золотое сечение
run_and_visualize(
func,
method="golden_section",
method_name_short="golden_section",
golden_section_bounds=GOLDEN_SECTION_BOUNDS,
)
# 3. Правило Армихо
run_and_visualize(
func,
method="armijo",
method_name_short="armijo",
armijo_params=ARMIJO_PARAMS,
)
# 4. Метод тяжёлого шарика
run_and_visualize_heavy_ball(
func,
method_name_short="heavy_ball",
alpha=HEAVY_BALL_ALPHA,
beta=HEAVY_BALL_BETA,
)
print("\n" + "=" * 80)
print("ГОТОВО! Все графики сохранены.")
print("=" * 80)
if __name__ == "__main__":
main()