import numpy as np from sympy import symbols, sympify, lambdify import matplotlib.pyplot as plt import io import base64 # Configure Matplotlib for headless environments plt.switch_backend('Agg') def golden_section_search(func_str, a, b, tol=1e-4): """ Perform Golden Section Search optimization. Args: func_str (str): Function expression as a string. a (float): Left bound of the interval. b (float): Right bound of the interval. tol (float): Tolerance for the stopping criterion. Returns: dict: A dictionary containing the results, including the minimum point, function value at the minimum, and iteration details. """ golden_ratio = (3 - np.sqrt(5)) / 2 # Approximately 0.381966 iterations = [] k = 0 if a >= b: raise ValueError("Invalid bounds: Left bound 'a' must be less than right bound 'b'.") try: x = symbols('x') expr = sympify(func_str) func = lambdify(x, expr, modules=['numpy']) except Exception as e: raise ValueError(f"Invalid function string: {str(e)}") x1 = a + golden_ratio * (b - a) x2 = b - golden_ratio * (b - a) f_x1 = float(func(x1)) f_x2 = float(func(x2)) # Check initial points for domain errors if np.isnan(f_x1) or np.isinf(f_x1): raise ValueError(f"Function evaluation resulted in NaN or Inf at x = {x1}") if np.isnan(f_x2) or np.isinf(f_x2): raise ValueError(f"Function evaluation resulted in NaN or Inf at x = {x2}") max_iter = 100 while (b - a) > tol and k < max_iter: iterations.append({ 'k': k, 'a': float(a), 'b': float(b), 'x1': float(x1), 'x2': float(x2), 'f_x1': f_x1, 'f_x2': f_x2, 'interval': float(b - a) }) k += 1 if f_x1 < f_x2: b = x2 x2 = x1 f_x2 = f_x1 x1 = a + golden_ratio * (b - a) f_x1 = float(func(x1)) if np.isnan(f_x1) or np.isinf(f_x1): raise ValueError(f"Function evaluation resulted in NaN or Inf at x = {x1}") else: a = x1 x1 = x2 f_x1 = f_x2 x2 = b - golden_ratio * (b - a) f_x2 = float(func(x2)) if np.isnan(f_x2) or np.isinf(f_x2): raise ValueError(f"Function evaluation resulted in NaN or Inf at x = {x2}") x_min = (a + b) / 2 f_min = float(func(x_min)) if np.isnan(f_min) or np.isinf(f_min): raise ValueError(f"Function evaluation resulted in NaN or Inf at x = {x_min}") result = { 'x_min': float(x_min), 'f_min': f_min, 'iterations': iterations, 'num_iterations': len(iterations) } if k >= max_iter: result['warning'] = f"Maximum iteration limit ({max_iter}) reached before tolerance was met. Result may be less accurate." return result def create_plot(func_str, bounds, iterations, x_min, f_min): """ Generates a plot of the function, search interval, and minimum point. Args: func_str (str): The function expression. bounds (tuple): The initial (a, b) search bounds. iterations (list): A list of dictionaries with iteration data. x_min (float): The calculated x-coordinate of the minimum. f_min (float): The calculated function value at the minimum. Returns: str: A base64 encoded string of the plot image. """ try: x = symbols('x') expr = sympify(func_str) func = lambdify(x, expr, 'numpy') except Exception as e: raise ValueError(f"Invalid function for plotting: {str(e)}") a, b = bounds x_vals = np.linspace(a - 0.1 * (b - a), b + 0.1 * (b - a), 400) y_vals = func(x_vals) # Filter out nan/inf for plotting valid = ~(np.isnan(y_vals) | np.isinf(y_vals)) x_plot = x_vals[valid] y_plot = y_vals[valid] fig, ax = plt.subplots(figsize=(10, 6)) # Plot the function ax.plot(x_plot, y_plot, label=f'f(x) = ${func_str}$', color='royalblue', linewidth=2) # Highlight the final minimum point ax.plot(x_min, f_min, 'ro', markersize=8, label=f'Minimum ({x_min:.4f}, {f_min:.4f})') # Show initial search bounds ax.axvline(x=a, color='gray', linestyle='--', label=f'Initial Bounds [{a}, {b}]') ax.axvline(x=b, color='gray', linestyle='--') # Style the plot ax.set_title('Golden Section Search Visualization', fontsize=16) ax.set_xlabel('x', fontsize=12) ax.set_ylabel('f(x)', fontsize=12) ax.grid(True, which='both', linestyle='--', linewidth=0.5) ax.legend() plt.tight_layout() # Save plot to a memory buffer buf = io.BytesIO() plt.savefig(buf, format='png') buf.seek(0) # Encode image to base64 image_base64 = base64.b64encode(buf.read()).decode('utf-8') plt.close(fig) return image_base64