import numpy as np import matplotlib.pyplot as plt # --- 1. Define Function, Gradient, and Hessian --- # Objective function: Rosenbrock function def objective_function(x): x1, x2 = x[0], x[1] return 100 * (x2 - x1**2)**2 + (1 - x1)**2 # Gradient vector calculation def gradient(x): x1, x2 = x[0], x[1] df_dx1 = -400 * x1 * (x2 - x1**2) - 2 * (1 - x1) df_dx2 = 200 * (x2 - x1**2) return np.array([df_dx1, df_dx2]) # Hessian matrix calculation def hessian(x): x1, x2 = x[0], x[1] d2f_dx1dx1 = -400 * (x2 - 3 * x1**2) + 2 d2f_dx1dx2 = -400 * x1 d2f_dx2dx1 = -400 * x1 d2f_dx2dx2 = 200 return np.array([[d2f_dx1dx1, d2f_dx1dx2], [d2f_dx2dx1, d2f_dx2dx2]]) # --- 2. Backtracking Line Search --- def backtracking_line_search(x, p, grad, alpha_bar=1.0, rho=0.5, c1=1e-3): """ Finds a step size alpha that satisfies the 1st Wolfe condition (Armijo condition). Condition: f(x + alpha*p) <= f(x) + c1 * alpha * grad^T * p """ alpha = alpha_bar f_val = objective_function(x) grad_dot_p = np.dot(grad, p) # Loop until condition is met while objective_function(x + alpha * p) > f_val + c1 * alpha * grad_dot_p: alpha *= rho # Safety break to prevent infinite loops if alpha becomes too small if alpha < 1e-16: break return alpha # --- 3. Optimization Algorithms --- def run_optimization(method, x0, tol_ratio=1e-12, max_iter=20000): """ Runs the specified optimization method. Returns: list of alphas used, total iterations """ x = x0.copy() grad_0_norm = np.linalg.norm(gradient(x0)) alphas = [] print(f"[{method}] Starting from {x0}...") for i in range(max_iter): grad = gradient(x) grad_norm = np.linalg.norm(grad) # Convergence Check: ||grad f(xk)|| < 10^-12 * ||grad f(x0)|| if grad_norm < tol_ratio * grad_0_norm: print(f" -> Converged at iteration {i}. Final Loss: {objective_function(x):.6e}") return alphas, i # Determine Search Direction if method == "Steepest_Descent": direction = -grad elif method == "Newton": hess = hessian(x) try: # Solve H * d = -g for d direction = np.linalg.solve(hess, -grad) except np.linalg.LinAlgError: # Fallback if Hessian is singular direction = -grad # Perform Backtracking Line Search alpha = backtracking_line_search(x, direction, grad, alpha_bar=1.0, rho=0.5, c1=1e-3) # Update Position x = x + alpha * direction alphas.append(alpha) print(f" -> Did not converge within {max_iter} iterations.") print(f" -> Final Gradient Norm: {grad_norm:.6e}") return alphas, max_iter # --- 4. Main Execution and Plotting --- # Configuration initial_points = [np.array([1.2, 1.2]), np.array([-1.2, 1.0])] methods = ["Steepest_Descent", "Newton"] # Dictionary to hold simulation results results = {} # Run simulations for x0 in initial_points: for method in methods: # Construct a unique key for storage key = f"{method}_x0_{x0}" alphas, num_iters = run_optimization(method, x0) results[key] = {'alphas': alphas, 'iters': num_iters, 'x0': x0} # Plot configuration fig, axes = plt.subplots(2, 2, figsize=(14, 10)) plt.subplots_adjust(hspace=0.4, wspace=0.3) # Mapping results to subplots plot_mapping = { ("Steepest_Descent", str(initial_points[0])): (0, 0), ("Newton", str(initial_points[0])): (0, 1), ("Steepest_Descent", str(initial_points[1])): (1, 0), ("Newton", str(initial_points[1])): (1, 1), } for key, data in results.items(): method_name = key.split("_x0_")[0] x0_str = str(data['x0']) ax_idx = plot_mapping[(method_name, x0_str)] ax = axes[ax_idx] y_data = data['alphas'] x_data = range(len(y_data)) # --- PLOTTING LOGIC MODIFICATION --- # For Steepest Descent, show only first {limit} steps if method_name == "Steepest_Descent": limit = 30 if len(y_data) > limit: y_data = y_data[:limit] x_data = range(limit) title_suffix = f" (First {limit} Steps Shown)" else: title_suffix = "" else: title_suffix = "" ax.plot(x_data, y_data, marker='o', linestyle='-', markersize=4, linewidth=1.5) # Titles and Labels ax.set_title(f"{method_name}\nStart: {data['x0']}\nTotal Iters: {data['iters']}{title_suffix}") ax.set_xlabel("Iteration") ax.set_ylabel("Step Size (Alpha)") ax.grid(True, which='both', linestyle='--', alpha=0.7) # Add annotation for total iterations ax.text(0.95, 0.95, f"Total Iters: {data['iters']}", transform=ax.transAxes, verticalalignment='top', horizontalalignment='right', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8)) # Save and Show #plt.suptitle("Step Size vs Iteration (Rosenbrock Function)", fontsize=16) plt.savefig("optimization_results.png") #plt.show()