import numpy as np import matplotlib.pyplot as plt from iterative_solvers import * def mat_vec_prod_p2(x_flat: np.ndarray, n: int, h_sq: float) -> np.ndarray: """ Computes the matrix-vector product y = Ax for the 2D PDE problem. """ x_2d = x_flat.reshape((n, n)) x_padded = np.zeros((n + 2, n + 2)) x_padded[1:-1, 1:-1] = x_2d y_2d = np.zeros((n, n)) # Apply the finite difference stencil: (4+h^2)u_ij - u_{i-1,j} - u_{i+1,j} - u_{i,j-1} - u_{i,j+1} for i in range(n): for j in range(n): y_2d[i, j] = (4.0 + h_sq) * x_padded[i + 1, j + 1] \ - x_padded[i, j + 1] \ - x_padded[i + 2, j + 1] \ - x_padded[i + 1, j] \ - x_padded[i + 1, j + 2] return y_2d.flatten() def get_diag_inv_p2(i: int, n: int, h_sq: float) -> float: """Returns 1/A_ii for Problem 2. Diagonal is constant: 4 + h^2.""" return 1.0 / (4.0 + h_sq) def get_off_diag_sum_p2(x_flat: np.ndarray, i_flat: int, n: int, **kwargs) -> float: """Computes the off-diagonal sum for a given row `i_flat` in Problem 2.""" row = i_flat // n col = i_flat % n sum_val = 0.0 if row > 0: sum_val += -x_flat[i_flat - n] if row < n - 1: sum_val += -x_flat[i_flat + n] if col > 0: sum_val += -x_flat[i_flat - 1] if col < n - 1: sum_val += -x_flat[i_flat + 1] return sum_val def solve_and_report_p2(n: int): """Solves the 2D PDE linear system for a given n and reports results.""" print(f"\n--- Solving for n = {n} (System size: {n*n} x {n*n}) ---") h = 1.0 / (n + 1) h_sq = h**2 N = n * n b = np.full(N, h_sq) omega = 2.0 / (1.0 + np.sin(np.pi * h)) iteration_counts = [] all_residuals = {} all_solutions = {} methods_to_run = { 'Jacobi': ('jacobi', {}), 'Gauss-Seidel': ('gauss_seidel', {}), 'SOR': ('sor', {'omega': omega}), 'Steepest Descent': (steepest_descent, {}), 'Conjugate Gradient': (conjugate_gradient, {}) } print("\nStarting iterative solvers...") for name, (method_func, params) in methods_to_run.items(): print(f"Running {name}...") solver_kwargs = {'n': n, 'h_sq': h_sq} if isinstance(method_func, str): solver_args = {'b': b, 'method': method_func, 'get_diag_inv': get_diag_inv_p2, 'get_off_diag_sum': get_off_diag_sum_p2, 'mat_vec_prod': mat_vec_prod_p2, **solver_kwargs, **params} solution, iters, res_hist = stationary_method(**solver_args) else: solver_args = {'b': b, 'mat_vec_prod': mat_vec_prod_p2, **solver_kwargs} solution, iters, res_hist = method_func(**solver_args) iteration_counts.append((name, iters)) all_residuals[name] = res_hist all_solutions[name] = solution if iters < MAX_ITER else None # Report iteration counts print("\n--- Iteration Counts ---") for name, iters in iteration_counts: status = "converged" if iters < MAX_ITER else "did NOT converge" print(f"{name:<20}: {iters} iterations ({status})") # Plot residue history plt.figure(figsize=(12, 8)) for name, res_hist in all_residuals.items(): res_log = np.log10(np.array(res_hist) + 1e-20) plt.plot(res_log, label=name, linestyle='-') plt.title(f'Convergence History for PDE Problem, n = {n}') plt.xlabel('Iteration Step (m)') plt.ylabel('log10(||r^(m)||_2)') plt.legend() plt.grid(True) current_ylim = plt.ylim() plt.ylim(bottom=max(current_ylim[0], -13), top=current_ylim[1]) plt.savefig(f'problem2_convergence_n{n}.png') print(f"\nConvergence plot saved to 'problem2_convergence_n{n}.png'") if all_solutions: print("Generating 3D surface plots of the solutions...") # Create the X, Y grid for the surface plot, including boundaries plot_grid = np.linspace(0, 1, n + 2) X, Y = np.meshgrid(plot_grid, plot_grid) # Create a figure for the subplots fig = plt.figure(figsize=(18, 10)) fig.suptitle(f'3D Surface Plots of Numerical Solutions for n = {n}', fontsize=16) # Loop through the stored solutions and create a subplot for each for i, (name, solution_flat) in enumerate(all_solutions.items()): # Reshape the 1D solution vector into a 2D grid solution_2d = solution_flat.reshape((n, n)) # Pad the solution with zero boundaries for plotting u_padded = np.zeros((n + 2, n + 2)) u_padded[1:-1, 1:-1] = solution_2d # Add a new 3D subplot ax = fig.add_subplot(2, 3, i + 1, projection='3d') # Plot the surface ax.plot_surface(X, Y, u_padded, cmap='viridis', edgecolor='none') # Set titles and labels for each subplot ax.set_title(name) ax.set_xlabel('x') ax.set_ylabel('y') ax.set_zlabel('u(x,y)') ax.view_init(elev=30, azim=-60) # Set a nice viewing angle plt.tight_layout(rect=[0, 0.03, 1, 0.95]) # Adjust layout to make room for suptitle plt.savefig(f'problem2_3d_solutions_n{n}.png') print(f"3D surface comparison plot saved to 'problem2_3d_solutions_n{n}.png'") if __name__ == "__main__": # Solve Problem 2 for n=16 and n=32 solve_and_report_p2(n=16) solve_and_report_p2(n=32)