# Approximate methods for differential equations and integration¶

These examples are adapted from Sage wiki: https://wiki.sagemath.org/SageWiki

In [1]:
x,y = var('x,y')


## Approximate integration¶

The following two cells implement three integration methods: midpoint, trapezoidal, Simpson's rules. In all the three methods the interval $[-1,1]$ is partitioned into $n$ subintervals. The corresponding sums consist of n (midpoint) and n+1 terms (trapezoidal, Simpson's rule), respectively.

In [2]:
import scipy
import numpy
from scipy.special.orthogonal import p_roots, t_roots, u_roots
from scipy.integrate import quad, trapz, simps
from sage.ext.fast_eval import fast_float
from numpy import linspace

methods = {
'Midpoint': {'w': 1, 'xmin': -1, 'xmax': 1,
'func': lambda n: (linspace(-1r+1.0r/n,1r-1.0r/n,n),
numpy.array([1.0r]*n)*2.0r/n
),
'nterms': lambda n: n},
'Trapezoid': {'w': 1, 'xmin': -1, 'xmax': 1,
'func': lambda n: (linspace(-1r,1r,n+1),
numpy.array([1.0r]+[2.0r]*(n-1)+[1.0r])*1.0r/n
),
'nterms': lambda n: n+1},
'Simpson': {'w': 1, 'xmin': -1, 'xmax': 1,
'func': lambda n: (linspace(-1r,1r,n+1),
numpy.array([1.0r]+[4.0r,2.0r]*int((n-1.0r)/2.0r)+[4.0r,1.0r])*2.0r/(3.0r*n)
),
'nterms': lambda n: n}}
var("x")
def box(center, height, width2,**kwds):
return polygon([(center-width2,0),
(center+width2,0),(center+width2,height),(center-width2,height)],**kwds)

In [3]:
@interact
def weights(n=slider(2,30,2,default=10),
f=input_box(default=cos(x),type=SR),
show_method=["Trapezoid","Midpoint","Simpson"]):
ff = fast_float(f,'x')
method = methods[show_method]
xcoords,w = (method['func'])(int(n))
xmin = method['xmin']
xmax = method['xmax']
plot_min = max(xmin, -10)
plot_max = min(xmax, 10)

coords = zip(xcoords,w)
max_weight = max(w)
coords_scaled = zip(xcoords,w/max_weight)

f_graph = plot(f,plot_min,plot_max)
#     boxes = sum(box(x,ff(x),(xmax-xmin)/n/2,rgbcolor=(0.5,0.5,0.5),alpha=0.3) for x,w in coords)
stems = sum(line([(x,0),(x,ff(x))],rgbcolor=(1-y,1-y,1-y),
thickness=2,markersize=6,alpha=y) for x,y in coords_scaled)
points = sum([point([(x,0),
(x,ff(x))],rgbcolor='black',pointsize=30) for x,_ in coords])
graph = stems+points+f_graph#+boxes
show(graph,xmin=plot_min,xmax=plot_max,aspect_ratio="auto")
approximation = sum([w*ff(x) for x,w in zip(xcoords,w)])
x_val = linspace(-1, 1,n)
y_val = [*map(ff,x_val)]
trapezoid = integral-trapz(y_val, x_val)
simpson = integral-simps(y_val, x_val)
pretty_print(html(r"$$\sum_{i=1}^{i=%s} w_i f(x_i^*) \Delta x = %s\approx \int_{-1}^{1}%s \,dx = %s$$"%
( (method['nterms'])(int(n)), approximation,  latex(f), integral)))
error_data = [integral-approximation, trapezoid, simpson, integral_error]
print("Errors for integration methods:\n")
print("Our implementation:\n%s\t %s\n\nBuilt-in:\nquad\t\t %s\ntrapz\t\t %s\nsimps\t\t %s" % (
show_method,integral-approximation,integral_error,trapezoid,simpson))


In the formula above, $w_i$ are the weights on values of $f$, which show up in the integration rules. For example, we have seen that the n-th sum in the midpoint rule is $$M_n = \Delta x\left(f(\bar x_1) + f(\bar x_2)+\ldots+ f(\bar x_n) \right).$$ We interpret this expression as the sum $$\sum_{i=1}^n w_i f(\bar x_i)\,\Delta x,$$ where all the $w_i$ are equal to 1. On the other hand, for the trapezoidal rule, the n-th sum is $$M_n = \frac{\Delta x}{2} \left(f(x_0) + 2f(\bar x_1)+\ldots+ 2f(x_{n-1}) + f(x_{n}) \right),$$ so in this sum $$\sum_{i=0}^n w_i f(x_i)\,\Delta x,$$ $w_i$ are equal to $$\frac 12, \ 1, \ 1, \ 1, \ldots, 1,\ 1, \ \frac12.$$ (Just multiply the 1/2 in front with the coefficients at $f(x_i)$.)

## Simple case of Euler's method¶

The following cell implements Euler's method and compares the approximate solution obtained with it (blue) to the exact solution (red).

In [4]:
@interact
def euler_method(
y_exact_in = input_box('exp(x)', type = str, label = 'Exact solution = '),
y_prime_in = input_box('exp(x)', type = str, label = "y' = "),
start = input_box(0.0, label = 'x starting value: '),
startval = input_box(1.0, label = 'y starting value: '),
stop = input_box(3.0, label = 'x stopping value: '),
nsteps = slider([2^m for m in range(0,10)], default = 10, label = 'Number of steps: '),
):
y_exact = lambda x: eval(y_exact_in)
#     dy/dx = F(x,y)
y_prime = lambda x,y: eval(y_prime_in)
stepsize = float((stop-start)/nsteps)
sol = [startval]+[0.0]*nsteps
xvals = [start]+[0.0]*nsteps
for step in range(nsteps):
sol[step+1] = sol[step] + stepsize*y_prime(xvals[step],sol[step])
xvals[step+1] = xvals[step] + stepsize
sol_max = max(sol + [find_local_maximum(y_exact,start,stop)[0]])
sol_min = min(sol + [find_local_minimum(y_exact,start,stop)[0]])
show(plot(y_exact(x),start,stop,rgbcolor=(1,0,0))+line([[xvals[index],sol[index]] for index in range(len(sol))]),xmin=start,xmax = stop, ymax = sol_max, ymin = sol_min)


## Direction field compared to Euler's method¶

In the next two cells, we plot the vector field for the vector with coordinates $(f,g)$, where both $f$ and $g$ depend on $(x,y)$. The vector field puts at every point $(x,y)$ of the plain vector $$(f(x,y), g(x,y).$$ In the setting from the lecture, where we had $$\frac{dy}{dx} = F(x,y),$$ this means that $$F(x,y) = \frac{g(x,y)}{f(x,y)}.$$ That is, the slope of a vector is given by the ratio of second coordinate to the first one.

The blue curve connects the points $(x_k, y_k)$ for $k\geq 0$, obtained by the iterations of the Euler's method. By making the step size ($\Delta x$ in the lecture) sufficiently small, we get a good approximation to the solution of the equation, corresponding to the vector field.

In [5]:
from sage.ext.fast_eval import fast_float

In [6]:
@interact
def _(f = input_box(default=y), g=input_box(default=-x*y+x^3-x),
xmin=input_box(default=-1), xmax=input_box(default=1),
ymin=input_box(default=-1), ymax=input_box(default=1),
start_x=input_box(default=0.5), start_y=input_box(default=0.5),
step_size= slider([2^m for m in range(-10,1)], default =0.125, label = r'$\Delta x$ '),
steps=slider([2^m for m in range(0,11)], default =128, label = 'Number of steps: ') ):
ff = fast_float(f, 'x', 'y')
gg = fast_float(g, 'x', 'y')
steps = int(steps)

points = [ (start_x, start_y) ]
for i in range(steps):
xx, yy = points[-1]
try:
points.append( (xx + step_size * ff(xx,yy), yy + step_size * gg(xx,yy)) )
except (ValueError, ArithmeticError, TypeError):
break

starting_point = point(points[0], pointsize=50)
solution = line(points)
vector_field = plot_vector_field( (f,g), (x,xmin,xmax), (y,ymin,ymax) )

result = vector_field + starting_point + solution

pretty_print(
html(
r"$\displaystyle\frac{dy}{dx} = \frac gf = \frac{%s}{%s}$  <br> " % (latex(g),latex(y))
)
)
result.show(xmin=xmin,xmax=xmax,ymin=ymin,ymax=ymax)

In [ ]:


In [ ]: