Computational Seismology

Discontinuous Galerkin Method for Earthquake Rupture Dynamics - non-linear friction laws

This notebook is based on the paper [A new discontinuous Galerkin spectral element method for elastic waves with physicaly motivaled numerical fluxes](https://www.geophysik.uni-muenchen.de/~gabriel/kduru_waves2017.pdf).
Published in the [13th International Conference on Mathematical and Numerical Aspects of Wave Propagation](https://cceevents.umn.edu/waves-2017).

##### Authors:
* Kenneth Duru
* Ashim Rijal ([@ashimrijal](https://github.com/ashimrijal))
* Sneha Singh

---

## Basic Equations ##

To begin, consider the domain $\Omega = \Omega_{-}\cup  \Omega_{+}$, with $  \Omega_{-}:= [0, x_0]$,  $\Omega_{+}:= [x_0, L]$, $0<x_0<  L$.

The sub-domains are governed by the source-free elastic wave equation in a heterogeneous 1D medium is

\begin{align}
\rho^{\pm}(x)\partial_t v^{\pm}(x,t) -\partial_x \sigma^{\pm}(x,t) & = 0\\
\frac{1}{\mu^{\pm}(x)}\partial_t \sigma^{\pm}(x,t) -\partial_x v^{\pm}(x,t) & = 0
\end{align}

Here, the field variables and material parameters in the sub-domains  $  \Omega_{\pm}$ with the superscripts $\pm$:  $v^{\pm}$,  $\sigma^{\pm}$, $\rho^{\pm}$,  $\mu^{\pm}$, $Z_s^{\pm}$.

The interface at $x = x_0$ is governed by the general non-linear frictional condition.

\begin{align}
\text{force balance}:  \quad &\sigma^{-} = \sigma^{+} = \sigma, \nonumber \\
\text{fricition law}: \quad &\sigma =  \sigma_{n}\frac{f\left(\left|[\![ v ]\!]\right|,\psi\right)}{\left|[\![ v ]\!]\right|}[\![ v ]\!]
\end{align}

Here, $\sigma_{n} > 0$ is the effective normal stress,  $f\left(\left|[\![ v ]\!]\right|,\psi\right)$ is a non-linear friction coefficient modeling the frictional response of the interface. In general $f\left(\left|[\![ v ]\!]\right|,\psi\right)\ge 0$ is a monotically increasing positive function, with $f\left(0, \psi\right) = 0 $. Note that with $f\left(\left|[\![ v ]\!]\right|,\psi\right)$ we can describe both rate-and-state, and slip-weakening friction laws.

At the external boundaries $ x = 0, x = L$ we pose the general well-posed linear boundary conditions

\begin{equation}
\begin{split}
B_0(v, \sigma, Z_{s}, r_0): =\frac{Z_{s}}{2}\left({1-r_0}\right){v} -\frac{1+r_0}{2} {\sigma} = 0,  \quad \text{at} \quad x = 0, \\
 B_L(v, \sigma, Z_{s}, r_n): =\frac{Z_{s}}{2} \left({1-r_n}\right){v} + \frac{1+r_n}{2}{\sigma} = 0, \quad \text{at} \quad  x = L.
 \end{split}
\end{equation}

with the reflection coefficients $r_0$, $r_n$ being real numbers and  $|r_0|, |r_n| \le 1$.

Note that at $x = 0$,  while  $r_0 = -1$ yields a clamped wall, $r_0 = 0$  yields  an absorbing boundary, and  with $r_0 = 1$  we have a free-surface boundary condition. Similarly, at $x = L$, $r_n = -1$ yields a clamped wall, $r_n = 0$ yields an absorbing boundary, and  $r_n = 1$  gives a free-surface boundary condition.

We define the mechanical energy in each subdomain by

\begin{equation}
E^{\pm}(t) = \frac{1}{2}\int_{\Omega_{\pm}}{\left({\rho^{\pm}(x)} |v^{\pm}(x, t)|^2 + \frac{1}{\mu^{\pm}(x)}|\sigma^{\pm}(x, t)|^2\right) dx}.
\end{equation}

The elastic wave equation with the frictional interface condition, satisfies the energy balance

\begin{equation}
\frac{d \left(E^-(t)+E^+(t)\right)}{dt}  = -\sigma [\![ v]\!]  -v^-(0, t)\sigma^-(0, t) + v^+(L, t)\sigma^+(L, t),\le 0.
\end{equation}

With the above friction law the first term in the right hand side of the energy rate, $\sigma [\![ v]\!] = f\left(\left|[\![ v ]\!]\right|,\psi\right){\left|[\![ v ]\!]\right|} \ge 0$, is the work done by friction on the fault which is dissipated as heat.

### The discontinuous Galerkin spectral element method (DGSEM)###

We will design a provably stable DGSEM obeying the energy balance at the discrete level.

1) Discretize the spatial domain $x$ into $K$ elements and denote the ${k}^{th}$ element $e^k = [x_{k}, x_{k+1}]$ and the element width $\Delta{x}_k = x_{k+1}-x_{k}$. Consider two adjacent elements  $e^k = [x_{k}, x_{k+1}]$ and  $e^{k+1} = [x_{k+1}, x_{k+2}]$ with an interface at $x_{k+1}$. At the internal non-frictional interfaces we pose the physical conditions for a locked interface

\begin{align}
\text{force balance}:  \quad &\sigma^{-} = \sigma^{+} = \sigma, \nonumber \\
\text{no slip}: \quad & [\![ v]\!]  = 0
\end{align}

where $v^{-}, \sigma^{-}$ and $v^{+}, \sigma^{+}$ are the fields in $e^k = [x_{k}, x_{k+1}]$ and  $e^{k+1} = [x_{k+1}, x_{k+2}]$, respectively.

2) Within the element derive the weak form of the equation by multiplying both sides by an arbitrary test function and integrating over the element.

3) Next map the $e^k = [x_{k}, x_{k+1}]$ to a reference element $\xi = [-1, 1]$

4) Inside the transformed  element  $\xi \in [-1, 1]$, approximate the solution  and material parameters by a polynomial interpolant,  and write
\begin{equation}
v^k(\xi, t) = \sum_{j = 1}^{N+1}v_j^k(t) \mathcal{L}_j(\xi), \quad \sigma^k(\xi, t)  = \sum_{j = 1}^{N+1}\sigma_j^k(t) \mathcal{L}_j(\xi),
\end{equation}

\begin{equation}
\rho^k(\xi) = \sum_{j = 1}^{N+1}\rho_j^k \mathcal{L}_j(\xi), \quad \mu^k(\xi) = \sum_{j = 1}^{N+1}\mu_j^k \mathcal{L}_j(\xi),
\end{equation}

where $ \mathcal{L}_j$ is the $j$th interpolating polynomial of degree $N$. If we consider  nodal basis then the interpolating polynomials satisfy $ \mathcal{L}_j(\xi_i) = \delta_{ij}$.

The interpolating nodes $\xi_i$, $i = 1, 2, \dots, N+1$ are the nodes of a Gauss quadrature with

\begin{equation}
 \sum_{i = 1}^{N+1} f(\xi_i)w_i \approx \int_{-1}^{1}f(\xi) d\xi,
\end{equation}

where $w_i$ are quadrature weights.

5) At the element boundaries $\xi = \pm 1$, we generate transformed hat-variables $\widehat{v}^{k}(\pm 1, t)$, $\widehat{\sigma}^{k}(\pm 1, t)$ by solving a Riemann problem and constraining the solutions against interface and boundary conditions. These hat-variables encode the solutions of the IBVP at the element boundaries. For non-linear friction laws we must solve a non-linear algebraic problem to compute hat-variables at the element boundaries. Once the hat-variables are computed we communicate them to the element by penalizing hat variables against the incoming characteristics only. This gives the numerical fluctuations $F^k(-1, t)$ and $G^k(1, t)$ at the element boundaries.

6) Finally, the flux fluctuations are appended to the semi-discrete PDE with special penalty weights and we have

\begin{equation}
\begin{split}
\frac{d \boldsymbol{v}^k( t)}{ d t} &= \frac{2}{\Delta{x}_k} W^{-1}({\boldsymbol{\rho}}^{k})\left(Q \boldsymbol{\sigma}^k( t) - \boldsymbol{e}_{1}F^k(-1, t)- \boldsymbol{e}_{N+1}G^k(1, t)\right),
\end{split}
\end{equation}

\begin{equation}
\begin{split}
\frac{d \boldsymbol{\sigma}^k( t)}{ d t} &= \frac{2}{\Delta{x}_k} W^{-1}(1/{\boldsymbol{\mu}^{k}})\left(Q \boldsymbol{v}^k( t)  + \boldsymbol{e}_{1}\frac{1}{Z_{s}^{k}(-1)}F^k(-1, t)- \boldsymbol{e}_{N+1}\frac{1}{Z_{s}^{k}(1)}G^k(1, t)\right),
\end{split}
\end{equation}

where
\begin{align}
\boldsymbol{e}_{1} = [ \mathcal{L}_1(-1), \mathcal{L}_2(-1), \dots,  \mathcal{L}_{N+1}(-1) ]^T, \quad  \boldsymbol{e}_{N+1} = [ \mathcal{L}_1(1), \mathcal{L}_2(1), \dots,  \mathcal{L}_{N+1}(1) ]^T,
\end{align}
and
\begin{align}
G^k(1, t):= \frac{Z_{s}^{k}(1)}{2} \left(v^{k}(1, t)-\widehat{v}^{k}(1, t) \right) + \frac{1}{2}\left(\sigma^{k}(1, t)- \widehat{\sigma}^{k}(1, t)\right),
\end{align}
\begin{align}
F^{k}(-1, t):= \frac{Z_{s}^{k}(-1)}{2} \left(v^{k}(-1, t)-\widehat{v}^{k}(-1, t) \right) - \frac{1}{2}\left(\sigma^{k}(-1, t)- \widehat{\sigma}^{k}(-1, t)\right).
\end{align}

And the weighted elemental mass matrix $W(a)$ and the stiffness matrix $Q $ are defined by

\begin{align}
W_{ij}(a) = \sum_{m = 1}^{N+1} w_m \mathcal{L}_i(\xi_m)  {\mathcal{L}_j(\xi_m)} a(\xi_m), \quad Q_{ij} = \sum_{m = 1}^{N+1} w_m \mathcal{L}_i(\xi_m)  {\mathcal{L}_j^{\prime}(\xi_m)}.
\end{align}

7) Time extrapolation can be performed using any stable time stepping scheme like Runge-Kutta or ADER scheme.This notebook implements both Runge-Kutta and ADER schemes for solving the elastic wave equation in a hoterogeneous medium with a nonlinear frictional interface, modeled by a linear friction law (LN), slip-weakening friction law (SW) or the rate-and-state friction law (RS).

**** Exercises****

1. In this notebook we use linear, slip-weakening and rate-and-state friction laws. First choose the linear friction law. Initially the friction coefficient, alpha, is set to 1e1000000. What happens if you change that value to 0.1?
2. Now change the frinction law to be slip-weakening. Run the simulation keeping the Tau_0 = 81.24+0.1*0.36, initial load, as it is. What happens if we decrease the initial load below 81.24?
3. Change the friction law to be rate-and-state and run the simulation. Observe time evolution of slip-rate, stress on fault and slip.
4. Change the time-integrator from RK to ADER. Observe if there are changes in the solution or the CFL number. Vary the polynomial order N.

```python
# Parameters initialization and plotting the simulation
import Lagrange
import numpy as np
import timeintegrate
import quadraturerules
import specdiff
import utils
import matplotlib.pyplot as plt

#plt.switch_backend("TkAgg")          # plots in external window
plt.switch_backend("nbagg")           # plots within this notebook
```

```python
# Initialization and computation
# Tic
iplot  = 10

# At the moment, keep the length of the two domain the same
# Ahysical domain x = [ax, bx] (km)
# For the first domain
ax_1 = 0.0
bx_1 = 30.0

# Physical domain x = [ax, bx] (km)
# For the second domain
ax_2 = bx_1
bx_2 = 60.0

# Choose quadrature rules and the corresponding nodes
# We use Gauss-Legendre-Lobatto (Lobatto) or  Gauss-Legendre (Legendre) quadrature rule.

#node = 'Lobatto'
node = 'Legendre'

if node not in ('Lobatto', 'Legendre'):
    print('quadrature rule not implemented. choose node = Legendre or node = Lobatto')
    exit(-1)

# polynomial degree N: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
N = 5                        # Lagrange polynomial degree
NP = N+1                     # quadrature nodes per element

if N < 1 or N > 12:
    print('polynomial degree not implemented. choose N>= 1 and N <= 12')
    exit(-1)

# degrees of freedom to resolve the wavefield
deg_of_freedom1 = 301        # for first domain
deg_of_freedom2 = 301        # for second domain

# estimate the number of elements needed for a given polynomial degree and degrees of freedom
num_element1 = round(deg_of_freedom1/NP)       # for first domain
num_element2 = round(deg_of_freedom2/NP)       # for second domain

# Initialize the mesh
y_1 = np.zeros(NP*num_element1)
y_2 = np.zeros(NP*num_element2)

# Generate num_element dG elements in the interval [ax, bx]
x0_1 = np.linspace(ax_1,bx_1,num_element1+1)
dx_1 = np.diff(x0_1)                           # element sizes

x0_2 = np.linspace(ax_2,bx_2,num_element2+1)
dx_2 = np.diff(x0_2)                           # element sizes

# Generate Gauss quadrature nodes (psi): [-1, 1] and weights (w)
if node == 'Legendre':
    GL_return = quadraturerules.GL(N)
    psi = GL_return['xi']
    w = GL_return['weights'];

if node == 'Lobatto':
    gll_return = quadraturerules.gll(N)
    psi = gll_return['xi']
    w = gll_return['weights']

# Use the Gauss quadrature nodes (psi) generate the mesh (y)
for i in range (1,num_element1+1):
    for j in range (1,(N+2)):
        y_1[j+(N+1)*(i-1)-1] = dx_1[i-1]/2.0 * (psi[j-1] + 1.0) +x0_1[i-1]

for i in range (1,num_element2+1):
    for j in range (1,(N+2)):
        y_2[j+(N+1)*(i-1)-1] = dx_2[i-1]/2.0 * (psi[j-1] + 1.0) +x0_2[i-1]

deg_of_freedom1 = len(y_1)                     #same for both the domains
deg_of_freedom2 = len(y_2)                     #same for both the domains

# generate the spectral difference operator (D) in the reference element: [-1, 1]
D = specdiff.derivative_GL(N, psi, w)

# Boundary condition reflection coefficients
r0 = 0.0                          # r=0:absorbing, r=1:free-surface, r=-1: clamped,
rn = 0.0                          # r=0:absorbing, r=1:free-surface, r=-1: clamped,

# Initialize the wave-fields
L_1 = 0.5*(bx_1-ax_1)
L_2 = 0.5*(bx_2-ax_2)

delta_1 = 0.01*(bx_1-ax_1)
delta_2 = 0.01*(bx_2-ax_2)

x0_1 = 0.5*(bx_1+ax_1)
x0_2 = 0.5*(bx_2+ax_2)

# Material parameters
c1s = 3.464 + 0.0*np.sin(2.0*np.pi*y_1)             # shear wave speed (km/s)
rho1 = 2.67 + 0.0*y_1                               # density (g/cm^3)
mu1 = rho1 * c1s**2                                 # shear modulus (GPa)
Z1s = rho1*c1s                                      # shear impedance

c2s = 3.464 + 0.0*y_2                               # shear wave speed (km/s)
rho2 = 2.67 + 0.0*y_2                               # density (g/cm^3)
mu2 = rho2 * c2s**2                                 # shear modulus (GPa)
Z2s = rho2*c2s                                      # shear impedance

# Choose friction law: fric_law
# We use linear (LN: T = alpha*v, alpha >=0)
# Slip-weakening (SW)
# Rate-and-state friction law (RS)

fric_law = 'RS'

if fric_law not in ('LN', 'SW', 'RS'):
    print('friction law not implemented. choose fric_law = SW or fric_law = LN')
    exit(-1)

if fric_law  in ('LN'):
    alpha = 1e1000000             # initial friction coefficient
    S = 0.0                       # initial slip (in m)
    Tau_0 = 81.24+1*0.36          # initial load (81.24 in MPa), slight increase will unlock the fault
    W = 0.0
    u_1 = np.exp(-(y_1-x0_1)**2/delta_1)                                                  # Gaussian
    u_2 = 1/np.sqrt(2.0*np.pi*delta_2**2)*np.exp(-(y_2-x0_2)**2/(2.0*delta_2**2))         # Gaussian

if fric_law  in ('SW'):
    alpha = 1e1000000              # initial friction coefficient
    S = 0.0                        # initial slip (in m)
    Tau_0 = 81.24+0.1*0.36         # initial load (81.24 in MPa), slight increase will unlock the fault
    W = 0.0
    u_1 = 0.0*np.exp(-(y_1-x0_1)**2/delta_1)                                                # Gaussian
    u_2 = 0.0/np.sqrt(2.0*np.pi*delta_2**2)*np.exp(-(y_2-x0_2)**2/(2.0*delta_2**2))         # Gaussian

if fric_law  in ('RS'):
    alpha = 1e1000000                      # initial friction coefficient
    S = 0.0                                # initial slip (in m)
    Tau_0 = 81.24+0.1*0.36                 # initial load (81.24 in MPa), slight increase will unlock the fault
    W = 0.4367
    u_1 = 0.0*np.exp(-(y_1-x0_1)**2/delta_1)                                                # Gaussian
    u_2 = 0.0/np.sqrt(2.0*np.pi*delta_2**2)*np.exp(-(y_2-x0_2)**2/(2.0*delta_2**2))         # Gaussian

u_1 = np.transpose(u_1)
v_1 = np.transpose(u_1)

u_2 = np.transpose(u_2)
v_2 = np.transpose(u_2)

# Time stepping parameters
cfl = 0.5                            # CFL number
dt = (cfl/(max(max(c1s),max(c2s))*(2*N+1)))*min(min(dx_1), min(dx_2))         # time-step (s)
t = 0.0                              # initial time
Tend = 3.0                           # final time (s)
n = 0                                # counter

Vd = [0]
Sd = [Tau_0]
T = [t]
Slip = [0]

# Initialize animated plot
f1, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, sharex='col', sharey='row',figsize=(10,7))
line1 = ax1.plot(y_1, u_1,'r')
line2 = ax2.plot(y_2, u_2,'r')
line3 = ax3.plot(y_1, v_1,'r')
line4 = ax4.plot(y_2, v_2,'r')

ax1.set_ylabel('velocity [m/s]')
ax3.set_ylabel('stress [Mpa]')
ax3.set_xlabel('X [m]')
ax4.set_xlabel('X [m]')

ax3.set_xlim([ax_1, bx_1])
ax4.set_xlim([ax_2,bx_2])
f1.subplots_adjust(wspace=0)
plt.ion()
plt.show()

time_integrator = 'ADER'

for t in utils.drange (dt,Tend+dt,dt):
    n = n+1

    if time_integrator  in ('ADER'):
        ADER_Wave_1D_GL_return = timeintegrate.ADER_Wave_1D_GL(u_1,v_1,u_2,v_2,S,W,D,NP,num_element1,\
                                                            num_element2,dx_1,dx_2,w,psi,t,r0,rn,dt,\
                                                             rho1,mu1,rho2,mu2,alpha,Tau_0,fric_law)
        u_1 = ADER_Wave_1D_GL_return['Hu_1']
        v_1 = ADER_Wave_1D_GL_return['Hv_1']
        u_2 = ADER_Wave_1D_GL_return['Hu_2']
        v_2 = ADER_Wave_1D_GL_return['Hv_2']
        S   = ADER_Wave_1D_GL_return['H_d']
        W   = ADER_Wave_1D_GL_return['H_psi']

    if time_integrator  in ('RK'):
        RK4_Wave_1D_GL_return = timeintegrate.ADER_Wave_1D_GL(u_1,v_1,u_2,v_2,S,W,D,NP,num_element1,\
                                                               num_element2,dx_1,dx_2,w,psi,t,r0,rn,dt,\
                                                               rho1,mu1,rho2,mu2,alpha,Tau_0,fric_law)
        u_1 = RK4_Wave_1D_GL_return['Hu_1']
        v_1 = RK4_Wave_1D_GL_return['Hv_1']
        u_2 = RK4_Wave_1D_GL_return['Hu_2']
        v_2 = RK4_Wave_1D_GL_return['Hv_2']
        S   = RK4_Wave_1D_GL_return['H_d']
        W   = RK4_Wave_1D_GL_return['H_psi']

    Vd.append(np.abs(u_1[-1]-u_2[0]))
    Sd.append(v_2[0]+1.0*Tau_0)
    T.append(t)
    Slip.append(S)

    # Update plot
    if n % iplot == 0:
        for l in line1:
            l.remove()
            del l
        for l in line2:
            l.remove()
            del l
        for l in line3:
            l.remove()
            del l
        for l in line4:
            l.remove()
            del l

        # Display lines
        line1 = ax1.plot(y_1, u_1, 'r')
        line2 = ax2.plot(y_2, u_2, 'r')
        line3 = ax3.plot(y_1, Tau_0+v_1, 'r')
        line4 = ax4.plot(y_2, Tau_0+v_2, 'r')
        plt.gcf().canvas.draw()

plt.ioff()
plt.show()
```

```python
# Plotting time evolution of slip-rate, stress on the fault and slip
plt.figure(figsize=(10,7))
plt.subplot(3,1,1)
plt.plot(T,Vd,'r-')
plt.title('time evolution of slip rate')
plt.ylabel('slip-rate [m/s]')
plt.xlabel('time [s]')
plt.ylim([0, 8])

plt.subplot(3,1,2)
plt.plot(T, Sd, 'r-')
plt.title('time evolution of stress on the fault')
plt.ylabel('stress on the fault [MPa]')
plt.xlabel('time [s]')
plt.ylim([40, 83])

plt.subplot(3,1,3)
plt.plot(T, Slip, 'b-')
plt.xlabel('time [s]')
plt.ylabel('slip [m]')
plt.title('time evolution of slip')

plt.tight_layout()
plt.show()
```

```python

```


 INF-495 - Modelado Computacional Aplicado

 Prof. Claudio Torres, Ph.D.

 Version: 1.02

## Textbook: Computational Mathematical Modeling, An Integrated Approach Across Scales by Daniela Calvetti and Erkki Somersalo.

# Chapter 1

```python
import numpy as np
import scipy.sparse.linalg as sp
import sympy as sym
from scipy.linalg import toeplitz

import ipywidgets as widgets
from ipywidgets import IntSlider

import matplotlib.pyplot as plt
%matplotlib inline
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter

plt.style.use('ggplot')

import matplotlib as mpl
mpl.rcParams['font.size'] = 14
mpl.rcParams['axes.labelsize'] = 20
mpl.rcParams['xtick.labelsize'] = 14
mpl.rcParams['ytick.labelsize'] = 14
sym.init_printing()

from scipy.integrate import solve_ivp
from ipywidgets import interact
```

## Hints:
I strongly suggest to review the following jupyter notebook:
- [Intro to ODE] https://github.com/tclaudioe/Scientific-Computing/blob/master/SC1v2/11_ODE.ipynb

## 1.2 Ordinary differential equations

A differential equation, in this case a scalar initial value problem (IVP), can be defined as follows:
$$\begin{align*}
    \dfrac{\mathrm{d}x}{\mathrm{d}t}(t) &= f(t,x(t)),\\
    x(0) &= x_0.
\end{align*}$$
For a known function $f(t,x)$ and $x_0$.
Particularly one could integrate both sides of the first equation as follows,
$$\begin{align*}
    \int_0^{h} \dfrac{\mathrm{d}x}{\mathrm{d}t}(t)\,\mathrm{d}t & = \int_0^{h} f(t,x(t))\,\mathrm{d}t,\\
    x(h)-x(0) &= \int_0^{h} f(t,x(t))\,\mathrm{d}t,
\end{align*}$$
where the Fundamental Theorem of Calculus was used.
Now, we solve for the unknow value $x(h)$:
$$\begin{align*}
    x(h) &= x_0 + \int_0^{h} f(t,x(t))\,\mathrm{d}t.
\end{align*}$$
Up to this point, we have not made any approximation.
From this point, one can derive several numerical method, depending how you approximate the integral.
For instance, if we consider $h=\Delta t$ and we use a Reimann sum from the left (only one interval from $0$ to $\Delta t$), we obtain the method called Forward Euler.
But if we do the same but with the Reimann sum from the right, we obtain backward Euler.
Another two options that are easy to see are the midpoint rule and the trapezoidal rule.
Notice that in some cases mentioned, you would need a non-linear equation (or a system in higher dimension) for each time step.

### Example 1

Considers the following IVP:
$$\begin{align*}
\dfrac{\mathrm{d}x}{\mathrm{d}t}(t) &= -\alpha\,x(t),\\
x(0) &= x_0,
\end{align*}$$
for which we know the solution:
$$\begin{align*}
x(t) &= x_0\,\exp(-\alpha\,t)
\end{align*}$$

```python
# Warning: In general, we will use \dot{y}=f(t,y), y(0)=y_0,
# so don't get mixed up with the notation from the textbook.

def f_example_1_interact(alpha_input=1,T_max=5,p=2):
    # Example 1, numerically.
    def f_example_1(t, y, alpha):
        return -alpha*y

    # Initial condition
    y0=1
    # time where we want your solution
    t = np.linspace(0, T_max, 100)

    sol = solve_ivp(f_example_1, [0,T_max], (y0,), t_eval=t, args=(alpha_input,))

    plt.figure()
    plt.plot(t, sol.y[0], 'b', label='y(t)')
    for i in np.arange(1,np.ceil(T_max/(np.log(p)/alpha_input))):
        plt.axvline(x=i*np.log(p)/alpha_input)
        print('t: ',i*np.log(p)/alpha_input,', exp(t): ',np.exp(-alpha_input*i*np.log(p)/alpha_input))
    plt.legend(loc='best')
    plt.xlabel('t')
    plt.ylabel(r'$\exp(-\alpha\,t)$')
    plt.title('What are the red lines?')
    plt.grid(True)
    plt.show()
interact(f_example_1_interact,alpha_input=(0.1,10,0.1),T_max=(0.1,100,0.1),p=(2,10,1))
```

    interactive(children=(FloatSlider(value=1.0, description='alpha_input', max=10.0, min=0.1), FloatSlider(value=…

    <function __main__.f_example_1_interact(alpha_input=1, T_max=5, p=2)>

### Example 2

Considers the following IVP:
$$\begin{align*}
\dfrac{\mathrm{d}x}{\mathrm{d}t}(t) &= \sqrt{x(t)}\\
x(0) &= 0,
\end{align*}$$

Recall that $2^{-1074}$ is the smallest number greater than 0 that ``double precision'' can store. See:
- https://github.com/tclaudioe/Scientific-Computing/blob/master/SC1v2/02_floating_point_arithmetic.ipynb

```python
print(2**-1074,', ',2**-1075)
```

    5e-324 ,  0.0

```python
# Example 2, numerically.
def f_example_2(t,y):
    return np.sqrt(y)
# Initial condition
y0 = 0.0
# time where we want your solution
t = np.linspace(0, 1, 100)
sol_a = solve_ivp(f_example_2, [0,1], (y0,), t_eval=t)
sol_b = solve_ivp(f_example_2, [0,1], (y0+np.power(2.0,-1074),), t_eval=t)

plt.figure()
plt.plot(t, sol_a.y[0], 'b', label='y_a(t)')
plt.plot(t, sol_b.y[0], 'r', label='y_b(t)')
plt.legend(loc='best')
plt.xlabel('t')
plt.grid(True)
plt.show()
```

### Example 3

Considers the following IVP:
$$\begin{align*}
\dfrac{\mathrm{d}x}{\mathrm{d}t}(t) &= x(t)^2\\
x(0) &= 1,
\end{align*}$$
for which we know the solution:
$$\begin{align*}
x(t) &= \dfrac{1}{1-t}
\end{align*}$$

```python
def f_example_3_interact(T_max=0.5):
    # Example 3, numerically.
    def f_example_3(t,y):
        return y*y

    # Initial condition
    y0=1
    # time where we want your solution
    t = np.linspace(0, T_max, 100)
    sol = solve_ivp(f_example_3, [0,T_max], (y0,), t_eval=t)

    plt.figure()
    plt.plot(t, sol.y[0], 'b', label='y(t)')
    #plt.plot(t, 1/(1-t), 'r--', label='y_e(t)')
    plt.legend(loc='best')
    plt.xlabel('t')
    plt.grid(True)
    plt.show()
interact(f_example_3_interact,T_max=(0.1,1,0.001))
```

    interactive(children=(FloatSlider(value=0.5, description='T_max', max=1.0, min=0.1, step=0.001), Output()), _d…

    <function __main__.f_example_3_interact(T_max=0.5)>

### Example 4

See classnotes!

```python
def f_example_4(t,y,alpha):
    return alpha*(1/2-y/(1+y))

# Initial condition
y0=3
# time where we want your solution
t = np.linspace(0, 10, 100)

plt.figure(figsize=(10,8))
for j in np.arange(1,6):
    sol= odeint(f_example_4, y0, t, args=(4/j,), tfirst=True)
    plt.plot(t, sol, label=r'$\xi(t)$'+r', $\tau= $'+str(j))
    plt.axvline(x=j,color='k')
    plt.text(j,0.5,r'$\tau= $'+str(j))
plt.legend(loc='best')
plt.ylabel('Concentration')
plt.xlabel('t')
plt.ylim((0,3))
plt.grid(True)
plt.show()
```

```python
def f_example_4b(t,y,alpha,phi_in):
    return phi_in-alpha*(y/(1+y))

# Initial condition
y0=3
# time where we want your solution
t = np.linspace(0, 1, 100)

plt.figure(figsize=(10,8))
sol= odeint(f_example_4b, y0, t, args=(4,5), tfirst=True)
plt.plot(t, sol, label=r'$\xi(t)$')
plt.legend(loc='best')
plt.ylabel('Concentration')
plt.xlabel('t')
plt.title(r'What happens then if $\phi_{in}\geq\alpha$?')
plt.grid(True)
plt.show()
```

### Example 7, for completeness.

Plotting:
$$\begin{align*}
f(t) &= \dfrac{t^2}{\pi^2+t^2}\,\sin(5\,t),\quad 0 \leq t \leq 4\,\pi
\end{align*}$$

```python
t = np.linspace(0,4*np.pi,100)

f = lambda t: (t**2/(np.pi**2+t**2))*np.sin(t)

plt.figure()
plt.plot(t,f(t),'b-',label='f(t)')
plt.legend(loc='best')
plt.xlabel('t')
plt.grid(True)
plt.show()
```

### Example 8, for completeness.

Plotting:
$$\begin{align*}
f(t,\tau) &= \exp(-t/\tau)\,\sin(2\,\pi\,t),\quad 0 \leq t \leq 2.
\end{align*}$$

```python
t = np.linspace(0,2,100)

f = lambda t,tau: np.exp(-t/tau)*np.sin(2*np.pi*t)

plt.figure()
plt.plot(t,f(t,1),'b-',label=r'$f(t,\tau=1)$')
plt.plot(t,f(t,0.5),'r--',label=r'$f(t,\tau=0.5)$')
plt.legend(loc='best')
plt.xlabel('t')
plt.grid(True)
plt.show()
```

### Example 9, for completeness.

Plotting:
$$\begin{align*}
f(t,u) &= u^2\,\dfrac{t}{1+t},\quad 0 \leq t, u \in \mathbb{R}.
\end{align*}$$

```python
elev_widget = IntSlider(min=0, max=180, step=10, value=40)
azim_widget = IntSlider(min=0, max=360, step=10, value=230)

def example_9_interact(elev=40,azim=230):
    f = lambda t,u: (u**2)*(t/(1+t))

    nt, nu = (100, 50)
    t = np.linspace(0, 5, nt)
    u = np.linspace(-1, 1, nu)
    tt, uu = np.meshgrid(t, u)
    zz = f(tt,uu)

    fig = plt.figure(figsize=(5,5))
    ax  = fig.gca(projection='3d')

    surf = ax.plot_surface(tt,uu,zz, cmap=cm.coolwarm, linewidth=0, antialiased=False)
    ax.view_init(elev,azim)
    ax.set_xlabel('t')
    ax.set_ylabel('u')
    ax.set_zlabel('f')
    plt.grid(True)
    plt.show()
interact(example_9_interact,elev=elev_widget,azim=azim_widget)
```

    interactive(children=(IntSlider(value=40, description='elev', max=180, step=10), IntSlider(value=230, descript…

    <function __main__.example_9_interact(elev=40, azim=230)>

### Example 10, for completeness.

$$\begin{equation*}
f(t)=
\begin{cases}
t\,\sin(t), & 0\leq t < 2, \\
\dfrac{\sin(\pi\,t)}{t}, & 2 < t.
\end{cases}
\end{equation*}$$

```python
t = np.linspace(0,4,100)

def f(t):
    if t<=2:
        return t*np.sin(t)
    else:
        return np.sin(np.pi*t)/t

fv=np.vectorize(f, otypes=[np.float64])

plt.figure()
plt.plot(t,fv(t),'b-',label='$f(t)$')
plt.plot(t[t<=2],fv(t[t<=2]),'r--',label=r'$f_r(t)$')
plt.plot(t[t>2],fv(t[t>2]),'r--')
plt.legend(loc='best')
plt.xlabel('t')
plt.grid(True)
plt.show()
```

### Example 11, for completeness.

Write a function that return $f(t)=\exp(-\alpha\,t^2)$ and $f'(t)=-2\,\alpha\,t\,\exp(-\alpha\,t^2)$.

```python
def f_and_fprime(t,alpha):
    f = lambda t: np.exp(-alpha*t**2)
    fp = lambda t: -2*alpha*t*f(t)
    return np.array([f(t),fp(t)])

print(f_and_fprime(t=1,alpha=1))
```

    [ 0.36787944 -0.73575888]

## Homework

- Plot the output of Example 11.
- Example 12, 13 and 14.
- Solve numerically the following problem and plot the solution:
Let $y_1(t)$ be the number of rabbits at time $t$ and $y_2(t)$ be the number of foxes at time $t$.
Consider they are in an isolated island and the rabiits have unlimited grass to eat and the foxes eat rabbits.
So, if there are not foxes, the population will grow without limits, this meains, $\dot{y_1}(t)=y_1(t)$, this means the numbers of rabbits grow exponentially.
Now, if there are not rabbits, we have $\dot{y_2}(t)=-y_2(t)$, since the population of foxes will decay without food. but, how do the two population could interact?

$$\begin{align*}
\dot{y}_1(t)&=(1-y_2(t)/\mu_2)\,y_1(t)\\
\dot{y}_2(t)&=-(1-y_1(t)/\mu_1)\,y_2(t)\\
y_1(0)&=400\\
y_2(0)&=100
\end{align*}$$

donde $\mu_1=300$ y $\mu_2=200$ son constantes de normalización.

# Acknowledgements
* _Material originally created by professor Claudio Torres_ (`ctorres@inf.utfsm.cl`). v.1.00. DI UTFSM. April 2020.
* _Update September 2021 - v1.02 - C.Torres_ : Using solve_ivp.


Massimo Nocentini

April 20, 2018: cleanup, `sin` and `cos` $g$ poly

November 16 and 18, 2016: `expt` and `log` $g$ poly

November 9, 2016: splitting from "big" notebook

Abstract

Theory of matrix functions, with applications to Catalan array $\mathcal{C}$.

```python
from sympy import *
from sympy.abc import n, i, N, x, lamda, phi, z, j, r, k, a, alpha

from commons import *
from matrix_functions import *
from sequences import *
import functions_catalog

init_printing()
```

# Catalan array $\mathcal{C}$

```python
m=8
```

```python
C = define(let=Symbol(r'\mathcal{{C}}_{{ {} }}'.format(m)),
           be=Matrix(m, m, lambda n,k: (k+1)*binomial(2*n-k, n-k)/(n+1) if n > 0 else int(not k)))
C
```

```python
eigendata = spectrum(C)
eigendata
```

```python
data, eigenvals, multiplicities = eigendata.rhs
```

```python
Phi_poly = Phi_poly_ctor(deg=m-1)
Phi_poly
```

```python
Phi_polynomials = component_polynomials(eigendata, early_eigenvals_subs=True)
Phi_polynomials
```

```python
cmatrices = component_matrices(C, Phi_polynomials)
list(cmatrices.values())
```

## `power` function

```python
f_power, g_power, G_power = functions_catalog.power(eigendata, Phi_polynomials)
```

```python
C_power = G_power(C)
C_power
```

```python
define(C_power.lhs, C_power.rhs.applyfunc(factor)) # factored
```

```python
assert C_power.rhs == (C.rhs**r).applyfunc(simplify)
```

```python
inspect(C_power.rhs)
```

    nature(is_ordinary=True, is_exponential=False)

```python
production_matrix(C_power.rhs).applyfunc(factor)
```

## `inverse` function

```python
f_inverse, g_inverse, G_inverse = functions_catalog.inverse(eigendata, Phi_polynomials)
```

```python
C_inverse = G_inverse(C)
C_inverse, G_inverse(C_inverse)
```

```python
inspect(C_inverse.rhs)
```

    nature(is_ordinary=True, is_exponential=False)

```python
production_matrix(C_inverse.rhs)
```

```python
assert C_inverse.rhs*C.rhs == Matrix(m, m, identity_matrix())
assert C_inverse.rhs == C_power.rhs.subs({r:-1})
```

## `sqrt` function

```python
f_sqrt, g_sqrt, G_sqrt = functions_catalog.square_root(eigendata, Phi_polynomials)
```

```python
C_sqrt = G_sqrt(C)
C_sqrt
```

```python
inspect(C_sqrt.rhs)
```

    nature(is_ordinary=True, is_exponential=False)

```python
production_matrix(C_sqrt.rhs)
```

```python
assert C.rhs**(S(1)/2) == C_sqrt.rhs
assert C_sqrt.rhs == C_power.rhs.subs({r:S(1)/2})
```

## `expt` function

```python
f_exp, g_exp, G_exp = functions_catalog.exp(eigendata, Phi_polynomials)
```

```python
C_exp = G_exp(C)
C_exp
```

```python
define(C_exp.lhs, C_exp.rhs.applyfunc(factor))
```

```python
C_exp1 = define(let=Subs(C_exp.lhs, alpha, 1), be=C_exp.rhs.subs({alpha:1}))
C_exp1
```

```python
inspect(C_exp.rhs)
```

    nature(is_ordinary=False, is_exponential=False)

```python
inspect(C_exp1.rhs)
```

    nature(is_ordinary=False, is_exponential=False)

```python
eigendata_Cexpt = spectrum(C_exp1)
eigendata_Cexpt
```

```python
Phi_polynomials_Cexpt = component_polynomials(eigendata_Cexpt, early_eigenvals_subs=True)
Phi_polynomials_Cexpt
```

## `log` function

```python
f_log, g_log, G_log = functions_catalog.log(eigendata, Phi_polynomials)
```

```python
C_log = G_log(C)
C_log
```

```python
inspect(C_log.rhs[1:,:-1])
```

    nature(is_ordinary=False, is_exponential=True)

```python
production_matrix(C_log.rhs[1:,:-1])
```

```python
g_log_Cexpt = Hermite_interpolation_polynomial(f_log, eigendata_Cexpt, Phi_polynomials_Cexpt)
g_log_Cexpt
```

```python
g_log_Cexpt = g_log_Cexpt.subs(eigendata_Cexpt.rhs[1])
g_log_Cexpt
```

```python
with lift_to_matrix_function(g_log_Cexpt) as G_log_Cexpt:
    CC = G_log_Cexpt(C_exp1)
CC
```

## `sin` function

```python
f_sin, g_sin, G_sin = functions_catalog.sin(eigendata, Phi_polynomials)
```

```python
C_sin = G_sin(C)
C_sin
```

```python
inspect(C_sin.rhs) # takes long to evaluate
```

## `cos` function

```python
f_cos, g_cos, G_cos = functions_catalog.cos(eigendata, Phi_polynomials)
```

```python
C_cos = G_cos(C)
C_cos
```

```python
inspect(C_sin.rhs) # takes long to evaluate
```

```python
assert (C_sin.rhs**2 + C_cos.rhs**2).applyfunc(trigsimp) == Matrix(m,m, identity_matrix())
```

---

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.


 ILI286 - Computación Científica II

 EDO - Aplicaciones Avanzadas

  [S]cientific [C]omputing [T]eam

 Version: 1.15

## Algunos algoritmos utilizados en este notebook han sido obtenidos del libro "Lloyd N. Trefethen, Spectral Methods in MATLAB, SIAM, Philadelphia, 2000" y traducidos a python.

# Tabla de Contenidos
* [Introducción](#intro)
* [Configuraciones](#config)
* [Problemas de Valor Inicial](#IVP)
    * [Algunos Solver Típicos](#solver)
        * [Regiones de Estabilidad](#stab)
        * [Convergencia](#conver)
    * [Problemas de segundo orden y más dimensiones](#high)
* [Problemas de Valor de Frontera](#BVP)
    * [Diferencias Finitas](#DD)
    * [Método de Disparo](#MD)
* [Acknowledgements](#acknowledgements)

# Introducción
En este notebook estudiaremos métodos numéricos para la resolución de ecuaciones diferenciales ordinarias (EDO). Se verán problemas de valor inicial (IVP) y de valor de frontera (BVP), sus principales métodos de resolución y se analizarán estabilidad y convergencia para los primeros mencionados.

# Configuraciones

### Librerías y parámetros utilizados en el notebook

```python
%matplotlib inline
import matplotlib.pyplot as plt
from matplotlib import pyplot #
import numpy as np
from scipy.integrate import odeint
from pylab import * #
from numpy import linalg as LA
from matplotlib.legend_handler import HandlerLine2D
from scipy.linalg import toeplitz
from scipy.optimize import root
from ipywidgets import interact
import sympy as sym
import matplotlib as mpl
mpl.rcParams['font.size'] = 14
mpl.rcParams['axes.labelsize'] = 20
mpl.rcParams['xtick.labelsize'] = 14
mpl.rcParams['ytick.labelsize'] = 14
sym.init_printing()

# Global parameter that controls the figure's size
L = 15

# Source: https://github.com/tclaudioe/Scientific-Computing/blob/master/SC5/02%20Differentiation%20matrices.ipynb
def plot_matrices_with_values(ax,M):
    N=M.shape[0]
    cmap = plt.get_cmap('GnBu')
    ax.matshow(M, cmap=cmap)
    for i in np.arange(0, N):
        for j in np.arange(0, N):
            ax.text(i, j, '{:.2f}'.format(M[i,j]), va='center', ha='center', color='r')
```

# Problemas de Valor Inicial

Un problema de valor inicial (IVP) corresponde a una ecuación diferencial ordinaria de la forma:

$$ \dot{y}(t) = f(t,y(t)) $$
sujeta a una condición inicial:

$$ y(t_0) = y_0 $$
siendo un sistema que evoluciona en el tiempo.

# Algunos Solver Típicos

Los solver típicos para resolver IVP's son los denominados métodos de Runge-Kutta, los cuales son un conjunto de métodos iterativos para la resolución numérica de ecuaciones diferenciales. Dentro de este conjunto, los más utilizados y los cuales veremos en este curso son:

* Runge-Kutta de primer orden (RK1) o Método de Euler:

\begin{align*}
k_1 &= h \cdot f(t_i,y_i) \\
y_{i+1} &= y_i + k_1
\end{align*}

* Runge-Kutta de segundo orden (RK2)

\begin{align*}
k_{1} &= h \cdot f(t_i,y_i) \\
k_{2} &= h \cdot f(t_i + h, y_i + k_1) \\
y_{i+1} &= y_i + \dfrac{1}{2}(k_1 + k_2) \\
\end{align*}

* Runge-Kutta de cuarto orden (RK4)

\begin{align*}
k_{1} &= h \cdot f(t_i,y_i) \\
k_{2} &= h \cdot f(t_i + \dfrac{h}{2}, y_i + \dfrac{1}{2}k_1) \\
k_{3} &= h \cdot f(t_i + \dfrac{h}{2}, y_i + \dfrac{1}{2}k_2) \\
k_{4} &= h \cdot f(t_i + h, y + k_3) \\
y_{i+1} &= y_i + \dfrac{1}{6}(k_1 + 2k_2 + 2k_3 + k_4)
\end{align*}

Para todos los métodos vistos, $h$ corresponde a $\Delta t$, es decir, la distancia entre un tiempo $t_i$ y el siguiente $t_{i+1}$, con $i =0,...,n-1$, al discretizar el intervalo de tiempo en $n$ subintervalos.

```python
# Forward Euler Method
def euler_ode(y,t,f,h):
    return y+h*f(t,y)

# Runge-Kutta of Second order
def RK2_ode(y,t,f,h):
    k1=y+h/2.0*f(t,y) #or euler_ode(y,t,f,h)
    return y+h*f(t+h/2.0,k1)
    # k1=h*f(t,y)
    # return y+h*f(t+h/2.0,y+k1/2.0)

# Runge-Kutta
def RK4_ode(y,t,f,h):
    k1=f(t,y)
    k2=f(t+h/2.0,y+(h/2.0)*k1)
    k3=f(t+h/2.0,y+(h/2.0)*k2)
    k4=f(t+h,y+h*k3)
    return y+(h/6.0)*(k1+2.0*k2+2.0*k3+k4)
```

### Ejemplo en 1-D

Resolviendo $\dot{y}(t) = y(t)(1-y(t))$ para distintas condiciones iniciales $y_0$, tenemos:

```python
# Logistic Equation
def my_f_1D(t,y):
    return np.array(y*(1-y))
```

```python
def plot_log_eq(h=0.15,solver='euler'):
    t_times = np.arange(0, 4, h)
    fig = plt.figure(figsize=(L/2,L/2))
    fig.clf()
    ax = fig.gca()
    ax.axis("equal")
    ax.grid(True)
    ax.set_title("Numerical Approximations and Direction Field")
    ax.axis([0, 4, -2, 2])

    for y0 in np.linspace(-1,5,40):
        y_output = np.zeros(t_times.size)
        y_output[0] = y0
        for i in range(1,t_times.size):
            if solver=='euler':
                y_output[i]=euler_ode(y_output[i-1],t_times[i-1],my_f_1D,h)
            elif solver=='RK2':
                y_output[i]=RK2_ode(y_output[i-1],t_times[i-1],my_f_1D,h)
            else:
                y_output[i]=RK4_ode(y_output[i-1],t_times[i-1],my_f_1D,h)
        plt.plot(t_times,y_output,'k-',alpha=0.5)
        plt.plot(t_times,y_output,'.')

    X,Y = np.meshgrid(np.arange(0,4,.2), np.arange(-2,2,.2))
    theta = np.arctan(my_f_1D(0,Y))
    U = np.cos(theta)
    V = np.sin(theta)
    plt.quiver(X,Y,U,V,alpha=0.5)
interact(plot_log_eq,h=(0.01,1,0.01),solver=['euler','RK2','RK4'])
```

    interactive(children=(FloatSlider(value=0.15, description='h', max=1.0, min=0.01, step=0.01), Dropdown(descrip…

    <function __main__.plot_log_eq(h=0.15, solver='euler')>

## Regiones de Estabilidad

Para analizar la estabilidad lineal de un método consideremos la ecuación diferencial $\dot{y} = \lambda y$, con $y(0)=1$, cuya solución es $y(t) = e^{\lambda t}$.

Para Euler tendríamos:

\begin{align*}
    y_{i+1} &= y_i + \lambda h y_i \\
    y_{i+1} &= (1 + \lambda h ) y_i \\
    y_{i+1} &= (1 + \lambda h )^{i+1} y_0
\end{align*}

Considerando la parte real de $\lambda$ negativa y $h$ positivo, para recrear la solución se necesita que $\left|1+\lambda h \right| <1$. Este dominio, se denomina la región de estabilidad del método.

Del mismo modo, para los demás métodos tenemos:

* RK2:
$$ \left|1+\lambda h + \dfrac{(\lambda h)^2}{2!} \right| <1 $$

* RK4:
$$ \left| 1+\lambda h + \dfrac{(\lambda h)^2}{2!} + \dfrac{(\lambda h)^3}{3!} + \dfrac{(\lambda h)^4}{4!} \right| <1 $$

```python
def zplot2(z, ax=plt.gca(), lw=1.5, line_color='k'):
    ax.plot(np.real(z), np.imag(z), line_color, lw=lw)
def runge_kutta_stability_regions():
    z = np.exp(1j * np.pi * np.arange(201)/100.)
    r = z-1
    d = 1-1./z;
    # Order 1
    W1, W2, W3, W4 = [0], [0], [0], [0]
    for zi in z[1:]:
        W1.append( W1[-1]-(1.+W1[-1]-zi) )
    for zi in z[1:]:
        W2.append( W2[-1]-(1+W2[-1]+.5*W2[-1]**2-zi**2)/(1+W2[-1]) )
    for zi in z[1:]:
        num = (1+W4[-1]+.5*W4[-1]**2+W4[-1]**3/6+W4[-1]**4/24-zi**4)
        den = (1+W4[-1]+W4[-1]**2/2+W4[-1]**3/6.)
        W4.append( W4[-1] - num/den )
    return W1, W2, W4
W1,W2,W4=runge_kutta_stability_regions()
```

```python
fig = plt.figure(figsize=(L/2,L/2))
ax=fig.gca()
zplot2(W1,ax,line_color='r')
zplot2(W2,ax,line_color='g')
zplot2(W4,ax,line_color='b')
ax.axis("equal")
ax.axis([-5, 2, -3.5, 3.5])
ax.grid("on")
ax.set_title("Stability Regions of some Runge-Kutta ODE solvers")
```

### Entonces, ¿cómo utilizamos la región de estabilidad?

Lo importante de este análisis es notar que no podemos definir $h$ a priori. Dados un método y $\lambda$ conocidos, debemos estudiar la región de estabilidad para determinar que valor de $h$ utilizar.

```python
def plot_log_eq_stability_region(y0=1.2, h=0.5, T=10):
    # How did I get this? Why do I need it?
    def my_f_1D_prime(y):
        return np.array(1-2*y)

    # Choosing which solver to use
    solvers = ('euler','RK2','RK4')
    solver = solvers[2]

    t_times = np.arange(0, T+h, h)
    y_output = np.zeros(t_times.size)
    y_output[0] = y0

    for i in range(1,t_times.size):
        if solver=='euler':
            y_output[i]=euler_ode(y_output[i-1],t_times[i-1],my_f_1D,h)
            c1,c2,c3='b','k','k'
        elif solver=='RK2':
            y_output[i]=RK2_ode(y_output[i-1],t_times[i-1],my_f_1D,h)
            c1,c2,c3='k','b','k'
        else:
            y_output[i]=RK4_ode(y_output[i-1],t_times[i-1],my_f_1D,h)
            c1,c2,c3='k','k','b'

    fig = plt.figure(figsize=(L,L))
    ax1 = fig.add_subplot(221)
    ax2 = fig.add_subplot(222)
    ax3 = fig.add_subplot(223)

    ax1.axis("equal")
    ax1.grid(True)
    ax1.set_title("Numerical Approximation")
    ax1.axis([0, T, -0.5, 2])
    ax1.plot(t_times,y_output,'-')
    ax1.plot(t_times,y_output,'.k')

    # The next code is to plot in logscale unstable numerical simulations
    y_all=h*my_f_1D_prime(y_output)
    y_pos=y_all>0
    y_neg=np.logical_not(y_pos)
    ax2.semilogy(t_times[y_pos],y_all[y_pos],'.r',ms=20)
    ax2.semilogy(t_times[y_neg],-y_all[y_neg],'.b',ms=20)
    ax2.grid("on")
    ax2.set_title("Numerical Approximation in log scale")

    zplot2(W1,ax3,line_color=c1)
    zplot2(W2,ax3,line_color=c2)
    zplot2(W4,ax3,line_color=c3)
    k_lambdah=h*my_f_1D_prime(y_output)
    ax3.plot(np.real(k_lambdah),np.imag(k_lambdah),'.r',ms=20)
    ax3.axis("equal")
    ax3.axis([-5, 2, -3.5, 3.5])
    ax3.grid("on")
    ax3.set_title("Stability Region and $k=\lambda\,h$")

#y0=1.2 # 1.2, 2.0, 4.0
#h=0.5  # 0.5, 0.1
#T=10
interact(plot_log_eq_stability_region,y0=(-2,5,0.1),h=(0.01,1,0.1), T=(1,20,1))
```

    interactive(children=(FloatSlider(value=1.2, description='y0', max=5.0, min=-2.0), FloatSlider(value=0.5, desc…

    <function __main__.plot_log_eq_stability_region(y0=1.2, h=0.5, T=10)>

## Convergencia

¡Esto es muy importante! Estudiémoslo...

Para hablar de convergencia, tenemos que tener en cuenta el orden de cada método. En palabras simples y resumidas podemos decir lo siguiente:

* Euler es un método de primer orden $O(h)$, por lo que si $h$ se reduce a la mitad, el error también lo hace.
* RK2 es un método de segundo orden $O(h^2)$, por lo que si $h$ se reduce a la mitad, el error se reduce a la cuarta parte.
* RK4 es un método de cuarto orden $O(h^4)$, por lo que si $h$ se reduce a la mitad, el error se reduce 16 veces.

Para ver esto, consideremos el siguiente problema:

\begin{align*}
\dot{y}(t)&=-3\,y(t)+6\,t+5\\
y(0)&=3
\end{align*}

cuya solución es $y(t)=2e^{-3t}+2t+1$.

```python
y0=3.0
time_test=0.5
def my_f(t,y):
    return np.array(-3*y+6*t+5)
y_sol = lambda t: 2.*np.exp(-3.*t)+2.*t+1
```

```python
fig = plt.figure(figsize=(L/2,L/2))
fig.clf()
ax = fig.gca()
ax.axis("equal")
ax.grid(True)
ax.set_title("Convergence Analysis")

h_list = np.logspace(-5,-1,5)
for h in h_list:
    t_times = np.arange(0, time_test+h, h)
    y_output = np.zeros(t_times.size)
    y_output[0] = y0
    for i in range(1,t_times.size):
        y_output[i]=euler_ode(y_output[i-1],t_times[i-1],my_f,h)
    plt.loglog(h,abs(y_output[-1]-y_sol(t_times[-1])),'b.',ms=20,label='Euler',alpha=.5)
    y_output = np.zeros(t_times.size)
    y_output[0] = y0
    for i in range(1,t_times.size):
        y_output[i]=RK2_ode(y_output[i-1],t_times[i-1],my_f,h)
    plt.loglog(h,abs(y_output[-1]-y_sol(t_times[-1])),'rs',ms=20,label='RK2',alpha=.5)
    y_output = np.zeros(t_times.size)
    y_output[0] = y0
    for i in range(1,t_times.size):
        y_output[i]=RK4_ode(y_output[i-1],t_times[i-1],my_f,h)
    plt.loglog(h,abs(y_output[-1]-y_sol(t_times[-1])),'gd',ms=20,label='RK4',alpha=.5)
    if h==h_list[0]:
        plt.legend(numpoints=1, loc="lower right")
plt.loglog(h_list,h_list,'k-')
plt.loglog(h_list,np.power(h_list,2.),'k-')
plt.loglog(h_list,np.power(h_list,4.),'k-')
```

# Problemas de segundo orden y más dimensiones

¿Por qué debemos estudiar problemas de dimensiones más altas?

¡Porque son geniales!

### Van der pool

El ocsilador Van der Pol es un oscilador no conservativo con amortiguamiento no lineal. Su evolución en el tiempo sigue una ecuación diferencial de segundo orden:

$$\ddot{y}-\mu\,(1-y^2)\,\dot{y} + y = 0$$ con $y(0)=2\ $   y   $\dot{y}(0)=0$

$\mu$ es un parámetro escalar que indica la no linealidad y la fuerza del amortiguamiento.

$y_1(t)=y(t)$

$y_2(t)=\dot{y}(t)$

$\dot{y}_1=\dot{y} = y_2$

$\dot{y}_2=\ddot{y} = \mu (1-y^2) \dot{y} - y = \mu (1-y_1^2) y_2 - y_1$

```python
mu=10
def my_vdp(t,y,mu=mu):
    y1 = y[0]
    y2 = y[1]
    return np.array([y2, mu*(1-y1**2)*y2-y1])

def my_vdp_eig_jacobian(t,y,mu=mu):
    J=[[0,1],[-(2*mu*y[0]*y[1]+1), mu*(1-y[0]**2)]]
    lambs,vs= LA.eig(J)
    return lambs
```

```python
def plotting_vdp(y01=2,y02=0,h=0.01,T=200,solver='euler'):
    y0=np.array([y01, y02])
    t_times = np.arange(0, T, h)
    y_output = np.zeros([t_times.size,2])
    y_output[0,:] = y0
    for i in range(1,t_times.size):
        #y_output[i,:]=euler_ode(y_output[i-1,:],t_times[i-1],my_vdp,h)
        if solver=='euler':
            y_output[i]=euler_ode(y_output[i-1],t_times[i-1],my_vdp,h)
        elif solver=='RK2':
            y_output[i]=RK2_ode(y_output[i-1],t_times[i-1],my_vdp,h)
        else:
            y_output[i]=RK4_ode(y_output[i-1],t_times[i-1],my_vdp,h)

    fig = plt.figure(figsize=(L,L/2))
    ax1 = fig.add_subplot(121)
    ax2 = fig.add_subplot(122)

    ax1.grid(True)
    ax1.set_title("Numerical Approximation")
    ax1.plot(t_times,y_output[:,0],'-')
    ax1.set(xlabel='t', ylabel='$y_1$')

    ax2.grid(True)
    ax2.set_title("Phase Portrait")
    ax2.plot(y_output[:,0],y_output[:,1],'-')
    ax2.set(xlabel='$y=y_1$', ylabel='$\dot{y}=y_2$')

interact(plotting_vdp,y01=(-3,3,0.1),y02=(-3,3,0.1),h=(0.001,1,0.01),T=(10,200,10),solver=['euler','RK2','RK4'])
```

    interactive(children=(FloatSlider(value=2.0, description='y01', max=3.0, min=-3.0), FloatSlider(value=0.0, des…

    <function __main__.plotting_vdp(y01=2, y02=0, h=0.01, T=200, solver='euler')>

### ¿Qué hay acerca de la región de estabilidad en sistemas dinámicos de EDO's?

```python
def plotting_vpd_as_system(y01=2,y02=0,h=0.02,T=40,solver='euler'):
    t_times = np.arange(0, T+h, h)
    y_output = np.zeros([2,t_times.size])
    y_output[:,0] = y0

    for i in range(1,t_times.size):
        if solver=='euler':
            y_output[:,i]=euler_ode(y_output[:,i-1],t_times[i-1],my_vdp,h)
            c1='b'
            c2='k'
            c3='k'
        elif solver=='RK2':
            y_output[:,i]=RK2_ode(y_output[:,i-1],t_times[i-1],my_vdp,h)
            c1='k'
            c2='b'
            c3='k'
        else:
            y_output[:,i]=RK4_ode(y_output[:,i-1],t_times[i-1],my_vdp,h)
            c1='k'
            c2='k'
            c3='b'

    fig = plt.figure(figsize=(L,L))
    ax1 = fig.add_subplot(221)
    ax2 = fig.add_subplot(222)
    ax3 = fig.add_subplot(223)

    ax1.axis('equal')
    ax1.grid(True)
    ax1.set_title("Numerical Approximation")
    ax1.axis([0, T, -0.5, 2])
    ax1.plot(t_times,y_output[0,:],'-')
    ax1.plot(t_times,y_output[1,:],'r-')

    zplot2(W1,ax2,line_color=c1)
    zplot2(W2,ax2,line_color=c2)
    zplot2(W4,ax2,line_color=c3)
    for i in range(1,t_times.size):
        k_lambdah=h*my_vdp_eig_jacobian(t_times[i],y_output[:,i])
        ax2.plot(np.real(k_lambdah[0]),np.imag(k_lambdah[0]),'.r',ms=10,alpha=.4)
        ax2.plot(np.real(k_lambdah[1]),np.imag(k_lambdah[1]),'sm',ms=10,alpha=.4)
    ax2.axis('equal')
    ax2.axis([-5, 2, -3.5, 3.5])
    ax2.grid('on')

    ax3.grid(True)
    ax3.set_title("Phase Portrait")
    ax3.plot(y_output[0,:],y_output[1,:],'-')
    ax3.set(xlabel='$y=y_1$', ylabel='$\dot{y}=y_2$')

interact(plotting_vpd_as_system,y01=(-3,3,0.1),y02=(-3,-3,0.1),h=(0.01,1,0.01),T=(10,200,10),solver=['euler','RK2','RK4'])
```

    interactive(children=(FloatSlider(value=2.0, description='y01', max=3.0, min=-3.0), FloatSlider(value=-3.0, de…

    <function __main__.plotting_vpd_as_system(y01=2, y02=0, h=0.02, T=40, solver='euler')>

### Modelo Lotka-Volterra (ecuaciones predador-presa), es decir, lobos comiendo conejos y conejos escondiendo y comiendo pasto infinito.

Se definen $y_1(t)$ como el número de conejos en el tiempo $t$ y $y_2(t)$ como el número de lobos en el tiempo $t$.
Todo esto está pasando en una isla, aunque estamos considerando la presencia de pasto infinito. Entonces, si no hay lobos, obtenemos $\dot{y1}(t)=y_1(t)$, en otras palabras, el número de conejos crece exponencialmente.
Ahora, si no hubiesen conejos, obtenemos $\dot{y_2}(t)=-y_2(t)$, debido a que la población de lobos decae exponencialmente. Pero, ¿qué pasa si tenemos ambas especies?

\begin{align*}
\dot{y_1}(t)&=(1-y_2(t)/\mu_2)\,y_1(t)\\
\dot{y_2}(t)&=-(1-y_1(t)/\mu_1)\,y_2(t)\\
y_1(0)&=400\\
y_2(0)&=100
\end{align*}

donde $\mu_1$ y $\mu_2$ son constantes de normalización.

```python
mu1=300
mu2=200
def f_predprey(t,y,mu1=mu1,mu2=mu2):
    y1 = y[0]
    y2 = y[1]
    return np.array([(1-y2/mu2)*y1, -(1-y1/mu1)*y2])

def f_predprey_jacobian(t,y,mu1=mu1,mu2=mu2):
    J=[[1-y[1]/mu2,-y[0]/mu2],[y[1]/mu1, -(1-y[0]/mu1)]]
    lambs,vs= LA.eig(J)
    return lambs
```

```python
y0=[400, 100]
h=0.03
T=30

# Choosing which solver to use
solvers = ('euler','RK2','RK4')
solver = solvers[0]

def plotting_predprey(y01=400,y02=100,h=0.03,T=30,solver='euler'):
    t_times = np.arange(0, T+h, h)
    y_output = np.zeros([2,t_times.size])
    y_output[:,0] = y0

    for i in range(1,t_times.size):
        if solver=='euler':
            y_output[:,i]=euler_ode(y_output[:,i-1],t_times[i-1],f_predprey,h)
            c1='b'
            c2='k'
            c3='k'
        elif solver=='RK2':
            y_output[:,i]=RK2_ode(y_output[:,i-1],t_times[i-1],f_predprey,h)
            c1='k'
            c2='b'
            c3='k'
        else:
            y_output[:,i]=RK4_ode(y_output[:,i-1],t_times[i-1],f_predprey,h)
            c1='k'
            c2='k'
            c3='b'

    fig = plt.figure(figsize=(L,L))
    ax1 = fig.add_subplot(221)
    ax2 = fig.add_subplot(222)
    ax3 = fig.add_subplot(223)

    ax1.grid(True)
    ax1.set_title('Numerical Approximation')
    ax1.plot(t_times,y_output[0,:],'-')
    ax1.plot(t_times,y_output[1,:],'r-')
    ax1.axis([0, T, 0, 600])

    zplot2(W1,ax2,line_color=c1)
    zplot2(W2,ax2,line_color=c2)
    zplot2(W4,ax2,line_color=c3)
    for i in range(1,t_times.size):
        k_lambdah=h*my_vdp_eig_jacobian(t_times[i],y_output[:,i])
        ax2.plot(np.real(k_lambdah[0]),np.imag(k_lambdah[0]),'.r',ms=10,alpha=.4)
        ax2.plot(np.real(k_lambdah[1]),np.imag(k_lambdah[1]),'sm',ms=10,alpha=.4)
    ax2.axis('equal')
    ax2.axis([-5, 2, -3.5, 3.5])
    ax2.grid('on')

    ax3.grid(True)
    ax3.set_title('Phase Portrait')
    ax3.plot(y_output[0,:],y_output[1,:],'-')
    ax3.set(xlabel='$y_1$', ylabel='$y_2$')
    ax3.axis([0, 800, 0, 600])

interact(plotting_predprey,y01=(0,1000,10),y02=(0,1000,10),h=(0.01,1,0.01),T=(10,200,10),solver=['euler','RK2','RK4'])
```

    interactive(children=(IntSlider(value=400, description='y01', max=1000, step=10), IntSlider(value=100, descrip…

    <function __main__.plotting_predprey(y01=400, y02=100, h=0.03, T=30, solver='euler')>

```python
def f_trig(t,y):
    y1 = y[0]
    y2 = y[1]
    return np.array([y2, -y1])

def f_trig_jacobian(t,y):
    #J=[[0,1],[-1, 0]]
    #lambs,vs= LA.eig(J)
    lambs=np.array([0.+1.j, 0.-1.j])
    return lambs

def plotting_f_trig(y01=1,y02=0,h=0.2,T=100,solver='euler'):

    t_times = np.arange(0, T+h, h)
    y_output = np.zeros([2,t_times.size])
    y_output[:,0] = y0

    for i in range(1,t_times.size):
        if solver=='euler':
            y_output[:,i]=euler_ode(y_output[:,i-1],t_times[i-1],f_trig,h)
            c1='b'
            c2='k'
            c3='k'
        elif solver=='RK2':
            y_output[:,i]=RK2_ode(y_output[:,i-1],t_times[i-1],f_trig,h)
            c1='k'
            c2='b'
            c3='k'
        else:
            y_output[:,i]=RK4_ode(y_output[:,i-1],t_times[i-1],f_trig,h)
            c1='k'
            c2='k'
            c3='b'
    fig = plt.figure(figsize=(L,L))
    ax1 = fig.add_subplot(221)
    ax2 = fig.add_subplot(222)
    ax3 = fig.add_subplot(223)

    ax1.grid(True)
    ax1.set_title('Numerical Approximation')
    ax1.plot(t_times,y_output[0,:],'-')
    ax1.plot(t_times,y_output[1,:],'r-')

    zplot2(W1,ax2,line_color=c1)
    zplot2(W2,ax2,line_color=c2)
    zplot2(W4,ax2,line_color=c3)
    for i in range(1,t_times.size):
        k_lambdah=h*f_trig_jacobian(t_times[i],y_output[:,i])
        ax2.plot(np.real(k_lambdah[0]),np.imag(k_lambdah[0]),'.r',ms=10,alpha=.4)
        ax2.plot(np.real(k_lambdah[1]),np.imag(k_lambdah[1]),'sm',ms=10,alpha=.4)
    ax2.axis('equal')
    ax2.axis([-5, 2, -3.5, 3.5])
    ax2.grid('on')

    ax3.grid(True)
    ax3.set_title('Phase Portrait')
    ax3.plot(y_output[0,:],y_output[1,:],'-')
    ax3.set(xlabel='$y_1$', ylabel='$y_2$')

interact(plotting_f_trig,y01=(-2,2,0.1),y02=(-2,2,0.1),h=(0.01,1,0.01),T=(10,200,10),solver=['euler','RK2','RK4'])
```

    interactive(children=(FloatSlider(value=1.0, description='y01', max=2.0, min=-2.0), FloatSlider(value=0.0, des…

    <function __main__.plotting_f_trig(y01=1, y02=0, h=0.2, T=100, solver='euler')>

```python
def f_exp(t,y):
    y1 = y[0]
    return np.array([-y1])
def f_exp_jac(t,y):
    return -1

def plotting_f_exp(y0=1,h=0.1,T=10,solver='euler'):

    t_times = np.arange(0, T+h, h)
    y_output = np.zeros([2,t_times.size])
    y_output[:,0] = y0

    for i in range(1,t_times.size):
        if solver=='euler':
            y_output[:,i]=euler_ode(y_output[:,i-1],t_times[i-1],f_exp,h)
            c1='b'
            c2='k'
            c3='k'
        elif solver=='RK2':
            y_output[:,i]=RK2_ode(y_output[:,i-1],t_times[i-1],f_exp,h)
            c1='k'
            c2='b'
            c3='k'
        else:
            y_output[:,i]=RK4_ode(y_output[:,i-1],t_times[i-1],f_exp,h)
            c1='k'
            c2='k'
            c3='b'

    fig = plt.figure(figsize=(L,L/2))
    ax1 = fig.add_subplot(121)
    ax2 = fig.add_subplot(122)

    ax1.grid(True)
    ax1.set_title('Numerical Approximation')
    ax1.plot(t_times,y_output[0,:],'.k')
    ax1.plot(t_times,y_output[0,:],'-',alpha=0.5)
    ax1.axis([0, T, -2, 2])

    zplot2(W1,ax2,line_color=c1)
    zplot2(W2,ax2,line_color=c2)
    zplot2(W4,ax2,line_color=c3)
    for i in range(1,t_times.size):
        k_lambdah=h*f_exp_jac(t_times[i],y_output[:,i])
        ax2.plot(np.real(k_lambdah),np.imag(k_lambdah),'.r',ms=10,alpha=.4)
    ax2.axis('equal')
    ax2.axis([-5, 2, -3.5, 3.5])
    ax2.grid('on')

interact(plotting_f_exp,y0=(-2,2,0.1),h=(0.01,10,0.01),T=(1,20,1),solver=['euler','RK2','RK4'])
```

    interactive(children=(FloatSlider(value=1.0, description='y0', max=2.0, min=-2.0), FloatSlider(value=0.1, desc…

    <function __main__.plotting_f_exp(y0=1, h=0.1, T=10, solver='euler')>

# Problemas de Valor de Frontera

Un problema de valor de frontera (BVP) corresponde a una ecuación diferencial ordinaria de la forma:
$$ y''(x) = f(x,y(x),y'(x)) $$
sujeta a condiciones de borde:
$$ y(a) = y_a $$
$$ y(b) = y_b $$
Intentemos resolver $y''(x)=4y(x)$, con $y(0)=y_0$ y $y(1)=y_n$.

## Diferencias Finitas

Este método consiste en aproximar las derivadas de $y(x)$ mediante diferencias finitas, para luego resolver un sistema de ecuaciones lineales o no lineal.

Dentro de las más conocidas están:

* Forward Difference:
$$ y'(x) = \dfrac{y(x+h) - y(x)}{h} + O(h)$$

* Backward Difference:
$$ y'(x) = \dfrac{y(x) - y(x-h)}{h} + O(h)$$

* Central Difference:
$$ y'(x) = \dfrac{y(x+h) - y(x-h)}{2h} + O(h^2)$$

Y para aproximar la segunda derivada utilizamos:
\begin{align*}
y''(x) &= \dfrac{\text{Forward Difference } - \text{ Backward Difference}}{h} + O(h^2) \\
y''(x) &= \dfrac{\dfrac{y(x+h) - y(x)}{h} - \dfrac{y(x) - y(x-h)}{h}}{h} + O(h^2) \\
y''(x) &= \dfrac{y(x+h) - 2y(x) + y(x-h)}{h^2} + O(h^2) \\
\end{align*}

```python
def solve_finite_difference_eq(I=4,y0=1,yn=3):
    # Spatial discretization, 'I' represents the number of intervals to be used.
    h=(1.-0)/I
    ## Boundarty conditions
    #y0=1.
    #y1=3.

    # Building Finite Difference Discretization
    deltas=-(2.+4.*(h**2.))
    A=toeplitz(np.append(np.array([deltas, 1.]), np.zeros(I-3)))
    # Building RHS
    b=np.append(-y0, np.zeros(I-3))
    b=np.append(b,-yn)
    # Solving the linear system of equations
    w=np.linalg.solve(A, b)
    # Adding back the boundary conditions into the solution
    w=np.append([y0], w)
    w=np.append(w,[yn])
    t_FD = np.linspace(0,1,I+1)
    w_FD = w
    return t_FD, w_FD, A

def plot_solution_finite_difference_eq(I=4,y0=1,yn=3):

    # Solving by Finite Difference
    t_FD, w_FD, A = solve_finite_difference_eq(I,y0,yn)

    # Plotting
    fig = plt.figure(figsize=(L,L/2))
    ax1 = fig.add_subplot(121)
    ax2 = fig.add_subplot(122)

    # Plotting the discrete solution
    ax1.grid(True)
    ax1.set_title("FD")
    ax1.plot(t_FD,w_FD,'.',ms=20)
    #ax1.axis([0, 1, 0, 3])
    ax1.grid("on")

    # Plotting the pattern and coefficients of the tri-diagonal matrix
    plot_matrices_with_values(ax2,A)

interact(plot_solution_finite_difference_eq,I=(3,25,1),y0=(-5,5,0.1),yn=(-5,5,0.1))
```

    interactive(children=(IntSlider(value=4, description='I', max=25, min=3), FloatSlider(value=1.0, description='…

    <function __main__.plot_solution_finite_difference_eq(I=4, y0=1, yn=3)>

## Método de Disparo

Este método consiste en tratar el BVP como si fuera un IVP mediante las siguientes consideraciones:

* Teniendo el BVP $ y''(x) = f(x,y(x),y'(x)) $,con $ y(a) = y_a $ y $ y(b) = y_b $.
* Consideremos $x \rightarrow t$ y $ \ddot{y}(t) = f(t,y(t),\dot{y}(t)) $,con $ y(t_0) = y_0 $ y $ \dot{y}(t_0) = \alpha $.
* Y definamos la función $F(\alpha) = y(t_n,\alpha) - y_b$.

Entonces solo basta con encontrar una raíz de $F(\alpha)$ para que la solución de este IVP corresponda a la solución del BVP que en realidad estamos resolviendo.

```python
def my_f(t,y):
    return np.array([y[1],4*y[0]])
```

```python
alpha=-1.
N=100

def shooting_method_101(alpha=-1, N=50, I=4, y0=1, yn=3):
    T=1.0
    h=T/(N-1.)
    t_times = np.linspace(0,T,num=N)
    y_output = np.zeros([t_times.size,2])
    y_output[0,:] = [y0,alpha]
    for i in range(1,t_times.size):
        y_output[i,:]=euler_ode(y_output[i-1,:],t_times[i-1],my_f,h)

    fig = plt.figure(figsize=(L/2,L/2))
    ax = fig.gca()
    plt.grid(True)
    plt.title("Numerical Approximation")
    plt.plot(t_times,y_output[:,0],'rd',ms=12,alpha=0.5,label='y: actual solution')
    plt.plot(t_times,y_output[:,1],'m.',ms=12,alpha=0.5,label='$\dot{y}$: derivative of solution')

    # Solving by Finite Difference
    t_FD, w_FD, _ = solve_finite_difference_eq(I,y0,yn)
    plt.plot(t_FD,w_FD,'.',ms=20,alpha=1)

    # Plotting distance to be reduced, so the missing initial condition is found.
    plt.plot([t_FD[-1],t_times[-1]],[w_FD[-1],y_output[-1,0]],'g-',label='Difference to be reduced')

    plt.plot(0,1,'.k',ms=12,label='Left Boundary Condition')
    plt.plot(1,3,'.k',ms=12,label='Right Boundary Condition')

    plt.legend()

interact(shooting_method_101,alpha=(-2,2,0.1),N=(10,200,10),I=(3,20), y0=(-5,5,0.1),yn=(-5,5,0.1))
```

    interactive(children=(FloatSlider(value=-1.0, description='alpha', max=2.0, min=-2.0), IntSlider(value=50, des…

    <function __main__.shooting_method_101(alpha=-1, N=50, I=4, y0=1, yn=3)>

### Ahora hagámoslo automáticamente

```python
N_grid = 100
def func(alpha):
    yn=3
    t_times,y_output = my_solver(N_grid,alpha)
    return y_output[-1,0]-yn

def my_solver(N,alpha):
    y0=1.
    T=1.0
    h=T/(N-1.)
    t_times = np.linspace(0,T,num=N)
    y_output = np.zeros([t_times.size,2])
    y_output[0,:] = [y0,alpha]
    for i in range(1,t_times.size):
        y_output[i,:]=RK4_ode(y_output[i-1,:],t_times[i-1],my_f,h)
    return t_times,y_output
```

```python
alpha = root(func, 0.)

t_times,y_output = my_solver(N_grid,alpha.x)

fig = plt.figure(figsize=(L/2,L/2))
#ax = fig.gca()
plt.grid(True)
plt.title("Numerical Approximation")
plt.plot(t_times,y_output[:,0],'rd',ms=20,alpha=0.5)
t_FD, w_FD, _ = solve_finite_difference_eq()
plt.plot(t_FD,w_FD,'.',ms=20,alpha=1)
```

# Acknowledgements
* _Material creado por profesor Claudio Torres_ (`ctorres@inf.utfsm.cl`) _y ayudantes: Alvaro Salinas y Martín
Villanueva. DI UTFSM. Abril 2016._

***
### DISCLAIMER ###

El presente notebook ha sido creado para el curso **ILI286 - Computación Científica 2**, del [Departamento de Informática](http://www.inf.utfsm.cl/), [Universidad Técnica Federico Santa María](http://www.utfsm.cl/).

El material ha sido creado por Claudio Torres <ctorres@inf.utfsm.cl> y Sebastian Flores <sebastian.flores@usm.cl>, y es distribuido sin restricciones. En caso de encontrar un error, por favor no dude en contactarnos.

[Update 2015] Se ha actualizado los notebooks a Python 3 e includio el "magic" "%matplotlib inline" antes de cargar matplotlib para que los gráficos se generen en el notebook.

[Update 2016] (Álvaro) Modificaciones mayores al formato original. Agregado contexto: Introducción, Tabla de Contenidos, Explicaciones de cada método.

[Update 2019] (C. Torres) Small changes. Fixing issue with title of sections and identation. Adding 'interact' to the logistic equation! Adding interact to everything, work in progress. All done, Enjoy!
***

```python

```