#!/usr/bin/env python
# coding: utf-8
# # Multivariate Root Finding
#
# ## Table of Contents
#
# 1. [Fixed-point iteration](#first-bullet)
# 2. [Newton's method](#second-bullet)
# ## 1. Fixed-point iteration
#
# We want to rewrite $F(x)=0$ as $x = G(x)$ to obtain:
#
# $$
# x_{k+1} = G(x_k)
# $$
#
# We want $G$ to be a contraction, and satisfies if $||G(x) - G(y)|| \leq L||x-y||$, then $||x_k - \alpha||\leq L^k ||x_0 - \alpha||$. So $G$ will converge to a fixed point $\alpha$.
#
# For multivariate case, we have to use **Jacobian matrix** $J_G \in \mathbb{R}^{n \times n}$, which is
#
# $$
# (J_G)_{ij} = {\partial G_i \over \partial x_j}, \quad i,j = 1, ..., n
# $$
#
# If $||J_G(\alpha) < 1||$, then there is some neighborhood of $\alpha$ for which the fixed-point iteration converges to $\alpha$.
# ---
# The following program implements fixed-point iteration to solve a system of nonlinear equations:
#
# $$
# \begin{align}
# x_1^2 + x_2^2 - 1 & = 0\\
# 5x_1^2 + 21 x_2^2 - 9 &= 0
# \end{align}
# $$
#
# It can be rearrange to $x_1 = \sqrt{1-x_2^2} , x_2 = \sqrt{(9-5x_1^2)/21}$. So we define
#
# $$
# G_1(x_1, x_2) = \sqrt{1-x_2^2} ,\quad G_2(x_1, x_2) = \sqrt{(9-5x_1^2)/21}
# $$
# In[1]:
from math import sqrt
# Define f(x_1, x_2)
def f(x1,x2):
return (x1*x1+x2*x2-1,5*x1*x1+21*x2*x2-9)
# Store results
ks = []
x1s = []
x2s = []
f1s = []
f2s = []
# Print out solution
def print_sol(k,x1,x2):
(f1,f2)=f(x1,x2)
ks.append(k)
x1s.append(x1)
x2s.append(x2)
f1s.append(f1)
f2s.append(f2)
print("%4d %18.12g %18.12g %19.12g %19.12g" \
%(k,x1,x2,f1,f2))
# Define starting position
(x1,x2)=(0.,0.)
print_sol(0,x1,x2)
# Perform fixed-point iteration
for k in range(1,10):
(x1,x2)=(sqrt(1-x2*x2),sqrt((9.-5.*x1*x1)/21.))
print_sol(k,x1,x2)
# In[2]:
import matplotlib.pyplot as plt
import numpy as np
plt.figure(figsize=(15, 8), dpi=80)
plt.plot(ks,f1s, label = r"$f\ (x_1)$")
plt.plot(ks,f2s, label = r"$f\ (x_2)$")
plt.ylabel(r"$f\ (x)$")
plt.xlabel('x')
plt.title('Fixed-point iteration solving a system of nonlinear equations')
plt.legend()
plt.show()
# ---
# ## 2. Newton's method
# The generalization of Newton's method is
#
# $$
# x_{k+1} = x_k - J_F(x_k)^{-1} F(x_k), \quad k = 0,1,2, ...
# $$
#
# Rewrite it to the standard form for a linear system:
#
# $$
# J_F(x_j) \Delta x_k = - F(x_k), \quad k = 0,1,2,...
# $$
#
# where $\Delta x_k = x_{k+1}- x_k$.
#
# ---
# If $x_0$ is sufficiently close to $\alpha$, then Newton's method converges quadratically in $n$ dimension. This relies on **Taylor's Theorem**.
#
# The **multivariate Taylor theorem** is:
#
# Let $\phi(s) = F(x+ s \delta)$, then 1D Taylor Theorem yields
#
# $$
# \phi(1) = \phi(0) + \sum^k_{l=1} {\phi^{(l)} (0) \over l!} + \phi^{(k+1)}(\eta), \quad \eta \in (0,1)
# $$
#
# We have
#
# $$
# \begin{align}
# \phi(0) &= F(x)\\
# \phi(1) &= F(x+\delta)\\
# \phi'(s) &= {\partial F(x + s\delta) \over \partial x_1} \delta_1 + {\partial F(x + s\delta) \over \partial x_2} \delta_2 + ... + {\partial F(x + s\delta) \over \partial x_n} \delta_n\\
# \phi''(s) &= {\partial^2 F(x + s\delta) \over \partial x_1^2} \delta_1^2 + ... + {\partial^2 F(x + s\delta) \over \partial x_1x_n} \delta_1 \delta_n + ... + {\partial^2 F(x + s\delta) \over \partial x_1x_n} \delta_1\delta_n + ... + {\partial^2 F(x + s\delta) \over \partial x_n^2}\delta_n^2\\
# \end{align}
# $$
#
# where $x$ is a vector, $s$ is a scalar (telling how far along the vector $\delta$), and $\delta$ is a vector (considered as a direction).
#
#
# Hence, we have
#
# $$
# F(x + \delta) = F(x) + \sum^k_{l=1} {U_l(\delta)\over l!} + E_k
# $$
#
# where $U_l(x)$ is a direction derivative operator, evaluated at $x$.
#
# $$
# U_l(x) = \left[\left({\partial \over \partial x_1} \delta_1 + ... + {\partial \over \partial x_n} \delta_n\right)^l F\right]_{(x)}, \quad l = 1,2,...,k,
# $$
#
# and the error, evaluated at $(x+ \eta \delta)$.
#
# $$
# E_l = (U_{k+1})_{(x+ \eta \delta)}, \quad \eta \in (0,1)
# $$
#
# Let $A$ be an upper bound on the absolute value of all derivative of order $k+1$, we can derive the upper bound of $|E_k|$:
#
# $$
# \begin{align}
# |E_k| &\leq {1\over (k+1)!} |(A, ..., A)^T(||\delta||^{k+1}_\infty, ..., ||\delta||^{k+1}_\infty)|\\
# &= {1\over (k+1)!} A ||\delta||^{k+1}_\infty |(1,...,1)^T (1,...,1)|\\
# &= {n^{k+1} \over (k+1)!} A ||\delta||_\infty^{k+1}
# \end{align}
# $$
#
# ---
# To analyse Newton's method, we only need to use the first order Taylor expansion:
#
# $$
# F(x+\delta) = F(x) + \nabla F(x)^T \delta + E_1
# $$
#
# For each $F_i$, we have
#
# $$
# F_i(x+\delta) = F_i(x) + \nabla F_i(x)^T \delta + E_{i,1}
# $$
#
# So for $F$ we have
#
# $$
# F(x+\delta) = F(x) + J_F(x) \delta + E_F
# $$
#
# where $||E_F||_\infty \leq \max_{1 \leq i \leq n}|E_{i,1}| \leq {1\over 2} n^2 \left(\max_{1 \leq i, j, l \leq n} |{\partial^2 F_i \over \partial x_j \partial x_l}|\right) ||\delta||^2_{\infty}$.
#
#
# Let's Taylor expand $F(\alpha)$ at $F(x_k)$,
#
# $$
# 0 = F(\alpha) = F(x_k) + J_F(x_k)[\alpha - x_k] + E_F
# $$
#
# So that
#
# $$
# x_k - \alpha = [J_F(x_k)]^{-1} F(x_k) + [J_F(x_k)]^{-1} E_F
# $$
#
# Also the Newton iteration can be rewritten as
#
# $$
# J_F(x_k)[x_{k+1} - \alpha] = J_F(x_k) [x_k - \alpha] - F(x_k)
# $$
#
# Combine these two equations we have
#
# $$
# x_{k+1} - \alpha = [J_F(x_k)]^{-1} E_F
# $$
#
# so that $||x_{k+1} - \alpha||_{\infty} \leq const. ||x_k - \alpha||^2_\infty$, i.e. **quadratic convergence**.
# ---
# The following program implements a Newton's method for the two-point Gaussian quadrature rule.
#
# $$
# \begin{align}
# F_1(x_1, x_2, w_1, w_2) &= w_1 + w_2 -2 = 0\\
# F_2(x_1, x_2, w_1, w_2) &= w_1x_1 + w_2x_2 = 0\\
# F_3(x_1, x_2, w_1, w_2) &= w_1x_1^2 + w_2 x_2^2 - 2/3 = 0\\
# F_4(x_1, x_2, w_1, w_2) &= w_1x_1^3 + w_2x_2^3 = 0
# \end{align}
# $$
#
# The Jacobian of the system is required
#
# $$
# J_F(x_1, x_2, w_1, w_2) = \begin{bmatrix}0 & 0& 1 & 1 \\ w_1 & w_2 & x_1 & x_2 \\ 2w_1x_1 & 2w_2x_2 & x_1^2 & x_2^2\\ 3w_1x_1^2 & 3w_2x_2^2 & x_1^3 & x_2^3 \end{bmatrix}
# $$
# In[3]:
import numpy as np
from scipy.optimize import fsolve
# Function f(x_1,x_2,w_1,w_2)
def f(x):
print(x)
(x1,x2,w1,w2)=(x[0],x[1],x[2],x[3])
x1s=x1*x1;x2s=x2*x2
f=np.array([w1+w2-2,
w1*x1+w2*x2,
w1*x1s+w2*x2s-2./3,
w1*x1s*x1+w2*x2s*x2])
return f
# Analytical Jacobian
def jacobian(x):
(x1,x2,w1,w2)=(x[0],x[1],x[2],x[3])
x1s=x1*x1;x2s=x2*x2
return np.array([[0,0,1,1],
[w1,w2,x1,x2],
[2*w1*x1,2*w2*x2,x1s,x2s],
[3*w1*x1s,3*w2*x2s,x1s*x1,x2s*x2]])
# Initial condition
x0=np.array([-1.,1.,1.,1.])
# Newton's method
if True:
xk=x0
fk=f(xk)
err=np.linalg.norm(fk)
while err>1e-16:
xk-=np.linalg.solve(jacobian(xk),fk)
fk=f(xk)
err=np.linalg.norm(fk)
# Print solution
print("\nSolution is",xk)