In this lecture, we will be looking at solving nonlinear equations. In particular, we will be looking at a very simple idea - fixed point iterations. While fixed point iterations have far reaching consequences for understanding various aspects of nonlinear dynamics as well, it lends an insight into the nature of convergence of roots and the dependency of the convergence behaviour depending on the functional choice.
set(0, "defaultlinelinewidth", 5);
set (0, "defaulttextfontname", "TimesNewRoman")
set (0, "defaulttextfontsize", 20)
set (0, "DefaultAxesFontName", "TimesNewRoman")
set(0, 'DefaultAxesFontSize', 20)
Let us consider the nonlinear equation $$\exp(x) - 3x^2 = 0, $$ whose roots we are interested it. In order to apply fixed point iterations, we will first cast it in the form of $x = f(x)$. Towards this, we can have multiple possibilities to do so. Let's consider the first possibility as shown below.
We rearrange the equation to write $$x = \ln 3 + 2 \ln x.$$ In case we had the root, the left hand side and right hand side of the equations would match exactly. However, if we guess a value different from the root, then the left hand side and right hand side would not match. In that case we can substitute the guess value in the right hand side function; in this case $\ln 3 + 2\ln x$. This value can then be assigned to $x$. This becomes the updated value of $x$.
We can program the aforementioned discussion in the following form: $$x^{(k+1)} = f(x^{(k)}) $$ where $k$ starts from 0 to $n$, where $n$ is the specified number of iterations. When $k =0$, we are at the first guess point, $x^{(0)}$. There is no universal form for the choice of neither the initial guess nor the number of iterations.
Let's first assess how the plot looks like and where to expect the roots to exist.
x = linspace(-2,5,200);
y = exp(x)-3*x.^2;
#plot(x, y,"linewidth",5, x,0.*x,"linewidth",5);
plot(x, y, x,0.*x);
ylim([-2, 2]);
h = legend ("exp(x) - 3x^2");
legend boxoff
legend (h, "location", "northwest");
set (h, "fontsize", 20,"fontname", "TimesNewRoman");
set(gca,'FontSize',20,"fontname", "TimesNewRoman");
xlabel("x", "fontsize",20,"fontname", "TimesNewRoman");
ylabel("y = f(x)","fontsize",20,"fontname", "TimesNewRoman");
title("Intersection points of f(x) with X-axis are the roots", "fontsize",20,"fontname", "TimesNewRoman");
x0 = 2.0;
Niter = 20;
x = zeros(1,Niter); # Initialization
x(1) = x0;
printf("Iteration: %d \t x: %f\n",0,x(1)); # This is for printing the 0th iterate - the intial value
# This function returns the RHS of the equation
function y = fx(x)
y = log(3) + 2*log(x);
end
# This is the loop where the values are updated
# We will print out the iteration number and updated value
for i=2:Niter+1
x(i) = fx(x(i-1));
printf("Iteration: %d \t x: %3.16f\n",i-1,x(i));
end
# Print the array which contains all the elements
disp(x);
plot(0:Niter, x, '-ok');
It can be clearly seen from the plot above that we start from an initial value of 2 at the zeroth iteration. The values are successively updated and eventually after 15 iterations we converge to the root of the equation. The iterated values of $x$ are also shown above.
Let us convert that into a function to plot all the family of curves.
fx(x)
takes as an input the value $x$ and returns $\ln 3 + 2\ln x$perform_iterations
takes as an input the initial guess x_0
and the number of iterations Niter
. Based on these values the function will perform the iterations and return the array x
which contains all the iterated values.x_0
to the function and plot the different arrays x
. function y = fx(x)
y = log(3) + 2*log(x);
end
function x = perform_iterations(x0, Niter)
x = zeros(1,Niter); # Initialization
x(1) = x0;
for i=2:Niter+1
x(i) = fx(x(i-1));
end
end
Niter = 20;
x0_a = linspace(1,5, 20);
for i=1:length(x0_a)
x = perform_iterations(x0_a(i), Niter);
plot(0:Niter, x, "linewidth",5);
hold on;
drawnow();
end
set(gca,'FontSize',20,"fontname", "TimesNewRoman");
xlabel("x", "fontsize",20,"fontname", "TimesNewRoman");
ylabel("y = f(x)","fontsize",20,"fontname", "TimesNewRoman");
Let us consider the same nonlinear equation $$\exp(x) - 3x^2 = 0$$ but now we will rearrange as $$x = \frac{1}{\sqrt{3}}\exp\frac{x}{2}$$. Let us reuse the same code above
x0 = 2.0;
Niter = 20;
x = zeros(1,Niter); # Initialization
x(1) = x0;
function y = fx(x)
y = 1/3^(0.5)*exp(x/2);
end
for i=2:Niter+1
x(i) = fx(x(i-1));
end
disp(x);
We find that this representation, too, convergence to the root. In the same way, the third root can also be found by starting with an appropriate initial guess. Try it out yourself!
While the previous nonlinear equation and its rearrangement to a fixed point iteration showed promise, it may not always be the case. Consider the nonlinear equation $$\exp(-x) - 3x = 0$$. Before attempting to find out the roots of this equation, let us first look at the curve represented by the equation itself.
x = linspace(-2, 4);
y = exp(-x) - 3*x;
plot(x,y, x, 0.*x);
ylim([-2, 2]);
It is clear from the curve above that there does exist a root somewhere between 0 and 1. Somewhere close to 0.25. So our initial guess for the fixed point iterations can be 0.25. Great!
Let us set up the equation in the form $x = f(x)$. We can rearrange $$\exp(-x) - 3x = 0\qquad \textrm{as}\qquad x = \frac{1}{3}\exp(-x)$$. Let's see what that gives us ...
x0 = 2.0; # Guess value
Niter = 20; # Number of iterations
x = zeros(1,Niter); # Initialization
x(1) = x0; # Setting the initial element of the array x
function y = fx(x)
y = 1/3*exp(-x);
end
for i=1:Niter
x(i+1) = fx(x(i));
end
printf("%g %g",x,fx(x(Niter)));
lia = ismembertol(x, 0.*x + fx(x(Niter)));
Well, the solution does converge to the value of 0.2576... with the choice of casting the iterations. The speed with which convergence is acheived can be probed with the help of the function np.isclose(x, fx(x[last]))
which tells us which elements of x
are close to the fx(x[last])
. After the first 10 odd terms, we see that we do not have much change in the iterated value of x.
The answer is no. This will be seen from the next case study. In this particular case, we will recast the iterations in the following form: $$\exp(-x) - 3x = 0\qquad \textrm{as}\qquad x = \exp(-x) - 2x$$. Let's investigate both the representations together:
x0 = 2.0;
Niter = 20;
x = zeros(1,Niter); # Initialization
xx = zeros(1,Niter);
x(1) = x0;
xx(1) = x0;
function y= fx(x) # THis function diverges
y = exp(-x)-2*x;
end
function y= gx(x)
y = 1/3*exp(-x); # THis function converges
end
for i =1:Niter
x(i+1) = fx(x(i));
xx(i+1) = gx(xx(i));
end
plot(0:Niter, x, '-b',0:Niter, xx,'-r')
ylim([-2, 2])
As seen from the curve of x
vs Niter
, the red curve iterates while the blue curve does not converge at all. It should be clear that the choice of the representation leads to the following two observations:
Let us investiage this phenomenon further by explicitly plotting how the iterations proceed to actually modify the guess value to the next value. For this, let us consider first the case that showed convergence - $$x = \ln 3 + 2\ln x$$.
We will plot the $f(x)$, which is the RHS of the above iteration scheme. Apart from this we will also plot $x$. The iterations can be logically split into the two steps: $$y_1 = \ln 3 + 2\ln x$$ followed by $$x_1 = y_1$$ hence the two curves give us the way the plots converge.
Niter = 10;
x = zeros(1,Niter);
xg = 1.1;
function y= fx(x)
y= log(3) + 2*log(x);
end
xp = linspace(0.2, 5, 100);
yp = fx(xp);
plot(xp, yp, "-;\ln 3 + 2\ln x;"); #plot(x, y, sprintf("-;J_{%d};",i), "linewidth",5);
hold on;
plot(xp, xp, "-;y = x;");
h = legend();
legend (h, "location", "southwest");
set (h, "fontsize", 16,"fontname", "TimesNewRoman");
legend boxoff
xlabel("x");
ylabel("x, f(x)");
#ax = gca(); set_aspect(0.6);
for i=1:Niter
xn = fx(xg);
plot(xg, fx(xg), 'ok', markersize=2);
x2 = xn; y2 = x2;
plot(x2, y2, 'ok', markersize=2 );
plot([xg, x2], [fx(xg), y2],'Color', 'blue' )
plot([x2, x2], [y2, fx(x2)], 'Color','red' )
xg = xn;
drawnow();
end
In the above curve, we have started with an initial guess near 1. This eventually iterates towards the root in the vicinity of 3.7. The intersection of the two curves are the roots since at those points of intersection $y = x$ and $y = f(x)$ $\implies$ $x = f(x)$.
Interestingly, despite starting the guess near the root near 1, we have coverged to the other root. We did, however, see that the second representation of the nonlinear equation, i.e. $$ \frac{1}{\sqrt{3}}\exp\left(\frac{x}{2}\right) $$ does give us the other root. Let's investigate the sequence of convergence for this particular case. It is easy for us to simply reuse the above code block and change the function to the desired one.
Niter = 10;
x = zeros(1,Niter);
xg = 3.5;
function y= fx(x)
y= 1/3^(0.5)*exp(x/2);
end
xp = linspace(0.2, 5, 100);
yp = fx(xp);
plot(xp, yp, "-;\ln 3 + 2\ln x;"); #plot(x, y, sprintf("-;J_{%d};",i), "linewidth",5);
hold on;
plot(xp, xp, "-;y = x;");
h = legend();
legend (h, "location", "northwest");
set (h, "fontsize", 16,"fontname", "TimesNewRoman");
legend boxoff
xlabel("x");
ylabel("x, f(x)");
#ax = gca(); set_aspect(0.6);
for i=1:Niter
xn = fx(xg);
plot(xg, fx(xg), 'ok', markersize=2);
x2 = xn; y2 = x2;
plot(x2, y2, 'ok', markersize=2 );
plot([xg, x2], [fx(xg), y2],'Color', 'blue' )
plot([x2, x2], [y2, fx(x2)], 'Color','red' )
xg = xn;
drawnow();
end
This particular sequence does depict convergence towards the other root. This happens despite choosing an initial guess quite close to the other root. If we compare the two curves, it is clear that in one case the curvature is concave down while in the other it is concave up. Regardless of that, the important thing is the slope of the curve at the root. In the slope of $f(x)$ at the root $X$ is such that $|df/dx|_X|<1$, then the iterations converge towards that root. This explains why deciding the shape of $f(x)$ has such a critical influence on the direction of convergence.
With this in mind, let's investigate the case where the iterations were seen to diverge despite choosing an initial guess quite close to the root. To recall, the iterations were setup in the following manner: $$x = \exp(-x)-2x$$.
Niter = 10;
x = zeros(1,Niter);
xg =0.25;
function y= fx(x)
y = exp(-x)-2*x;
end
xp = linspace(-5, 5, 100);
yp = fx(xp);
plot(xp, yp, "-;RHS function;");
hold on;
plot(xp, xp, "-; y = x;");
xlim([0, 0.5]);
ylim([-0.5,2]);
for i=1:Niter
xn = fx(xg);
plot(xg, fx(xg), 'ok', markersize=2);
x2 = xn; y2 = x2;
plot(x2, y2, 'ok', markersize=2 );
plot([xg, x2], [fx(xg), y2],'Color', 'blue' )
plot([x2, x2], [y2, fx(x2)], 'Color','red' )
xg = xn;
drawnow();
end
There are a few observables from the plot:
We can use one of several functions available in the submodule scipy.optimize
for solving nonlinear equations. Let us demonstrate this below:
Let us solve the nonlinear equation $\exp(x) - 3x^2 =0$ using the
fsolve
function of pythonbrentq
function of python; this is a bracketing methodnewton
function of python; this is the classic Newton-Raphson method. We must supply a Jacobian for this. This is essentially the derivative (analytical) of the function. In case we do not specify the derivative, the secant method is employed in place of the Newton-Raphson methodIn all the cases the functions are passed to the solver by means of a function handle. This is illustrated below:
function y= fx(x)
y= exp(x)-3*x.^2;
end
function y= fpx(x)
y= exp(x)-6*x;
end
[sol, res] = fsolve (@fx, -2);
disp(sol)
sol2 = fzero(@fx, -2);
disp(sol2)