Files
math6601_projects/homework10/Homework10_Q1.py
2025-11-23 16:16:53 -05:00

250 lines
7.8 KiB
Python

import numpy as np
import matplotlib.pyplot as plt
import sys
# Configuration for plotting
plt.style.use('seaborn-v0_8')
plt.rcParams['figure.figsize'] = (18, 6)
def newton_method(f, df, x0, tolerance=1e-12, max_iter=20):
"""
Implements Newton's Method with overflow protection.
Returns: list of iteration history [(iter, x, error)]
"""
history = []
x = x0
for i in range(max_iter):
try:
# Check for divergence first
if abs(x) > 1e100:
break
fx = f(x)
dfx = df(x)
if abs(dfx) < 1e-15: # Avoid division by zero
break
history.append(x)
x_new = x - fx / dfx
# Check convergence
if abs(x_new - x) < tolerance:
x = x_new
history.append(x)
break
x = x_new
except OverflowError:
# Catch errors if numbers become too large
break
return history
def chord_method(f, df_x0, x0, tolerance=1e-12, max_iter=50):
"""
Implements Chord Method using fixed slope m = f'(x0) with overflow protection.
Returns: list of iteration history
"""
history = []
x = x0
m = df_x0 # Fixed slope
for i in range(max_iter):
try:
if abs(x) > 1e100:
break
fx = f(x)
history.append(x)
if abs(m) < 1e-15:
break
x_new = x - fx / m
if abs(x_new - x) < tolerance:
x = x_new
history.append(x)
break
x = x_new
except OverflowError:
break
return history
def secant_method(f, x0, x_minus_1, tolerance=1e-12, max_iter=20):
"""
Implements Secant Method with overflow protection.
Returns: list of iteration history
"""
history = []
x_curr = x0
x_prev = x_minus_1
for i in range(max_iter):
try:
if abs(x_curr) > 1e100:
break
history.append(x_curr)
fx_curr = f(x_curr)
fx_prev = f(x_prev)
if abs(fx_curr - fx_prev) < 1e-15:
break
# Secant update
x_new = x_curr - fx_curr * (x_curr - x_prev) / (fx_curr - fx_prev)
if abs(x_new - x_curr) < tolerance:
x_curr = x_new
history.append(x_curr)
break
x_prev = x_curr
x_curr = x_new
except OverflowError:
break
return history
def solve_and_save():
# Definition of problems
problems = [
{
'id': 'a',
'title': r'$f(x) = \cos(x) - x, x_0 = 0.5$',
'func_name': 'cos(x) - x',
'f': lambda x: np.cos(x) - x,
'df': lambda x: -np.sin(x) - 1,
'x0': 0.5,
'true_root': 0.73908513321516064165531208767387340401341175890075746496568063577328465488354759 # From wolframalpha
},
{
'id': 'b',
'title': r'$f(x) = \sin(x), x_0 = 3$',
'func_name': 'sin(x)',
'f': lambda x: np.sin(x),
'df': lambda x: np.cos(x),
'x0': 3.0,
'true_root': np.pi
},
{
'id': 'c',
'title': r'$f(x) = x^2 + 1, x_0 = 0.5$',
'func_name': 'x^2 + 1',
'f': lambda x: x**2 + 1,
'df': lambda x: 2*x,
'x0': 0.5,
'true_root': None # No real root
}
]
# Open file for writing text results
output_filename = 'numerical_results.txt'
image_filename = 'convergence_results.png'
with open(output_filename, 'w', encoding='utf-8') as txt_file:
fig, axes = plt.subplots(1, 3)
for idx, prob in enumerate(problems):
header_str = f"\n{'='*60}\nProblem ({prob['id']}): {prob['func_name']}, x0={prob['x0']}\n{'='*60}\n"
print(header_str) # Print to console to show progress
txt_file.write(header_str)
x0 = prob['x0']
f = prob['f']
df = prob['df']
# 1. Run Newton
hist_newton = newton_method(f, df, x0)
# 2. Run Chord (m = f'(x0))
hist_chord = chord_method(f, df(x0), x0)
# 3. Run Secant (x_-1 = 0.99 * x0)
x_minus_1 = 0.99 * x0
hist_secant = secant_method(f, x0, x_minus_1)
methods_data = {
"Newton": hist_newton,
"Chord": hist_chord,
"Secant": hist_secant
}
ax = axes[idx]
for m_name, history in methods_data.items():
iters = list(range(len(history)))
errors = []
# Calculate errors for plotting
# Note: Handling overflow for plotting large errors in problem c
for x in history:
try:
if prob['true_root'] is not None:
val = abs(x - prob['true_root'])
else:
val = abs(f(x)) # Residual for problem c
errors.append(val)
except OverflowError:
errors.append(float('inf'))
# Write to TXT file
txt_file.write(f"\nMethod: {m_name}\n")
txt_file.write(f"{'Iter':<5} | {'Value (x_k)':<25} | {'Error/Resid':<25}\n")
txt_file.write("-" * 60 + "\n")
for i, val in enumerate(history):
# Only write full history if short, or head/tail if long
if len(history) < 20 or i < 5 or i > len(history) - 3:
if prob['true_root'] is not None:
err_val = abs(val - prob['true_root'])
else:
try:
err_val = abs(f(val))
except OverflowError:
err_val = float('inf')
txt_file.write(f"{i:<5} | {val:<25.10g} | {err_val:<25.6e}\n")
elif i == 5:
txt_file.write("...\n")
# Add to plot
if prob['true_root'] is not None:
ylabel_text = "Error |x_k - x*|"
log_scale = True
ax.plot(iters, errors, marker='o', label=m_name)
else:
ylabel_text = "Residual |f(x_k)|"
log_scale = False
# For problem C, data explodes, so we limit plotting range to avoid ruining the graph
valid_errors = [e for e in errors if e < 1e10]
valid_iters = iters[:len(valid_errors)]
ax.plot(valid_iters, valid_errors, marker='x', label=m_name)
ax.set_title(f"Problem ({prob['id']})\n{prob['title']}")
ax.set_xlabel("Iteration")
ax.set_ylabel(ylabel_text)
if log_scale:
ax.set_yscale('log')
ax.legend()
if prob['id'] == 'c':
ax.set_ylim(0, 20)
ax.set_xticks(range(0, len(iters), 1))
ax.grid(True)
print(f"\nText results saved to: {output_filename}")
plt.tight_layout()
plt.savefig(image_filename)
print(f"Chart saved to: {image_filename}")
if __name__ == "__main__":
solve_and_save()