250 lines
7.8 KiB
Python
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()
|