```python
from IPython.core.display import HTML
css_file = './custom.css'
HTML(open(css_file, "r").read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Philosopher:400,700,400italic,700italic' rel='stylesheet' type='text/css'>

###### Content provided under a Creative Commons Attribution license, CC-BY 4.0; code under MIT License. (c)2014 [David I. Ketcheson](http://davidketcheson.info)

##### version 0.1 - May 2014

# Hyperbolic Conservation Laws

\begin{equation*}
\newcommand{Dx}{\Delta x}
\newcommand{Dt}{\Delta t}
\newcommand{imh}{{i-1/2}}
\newcommand{iph}{{i+1/2}}
\end{equation*}
Many models of wave phenomena are governed by *hyperbolic conservation laws*.  In this short course, we will learn about hyperbolic conservation laws and their numerical solution.

## Conservation of mass

Imagine a fluid flowing in a narrow tube.  We'll use $q$ to indicate the density of the fluid and $u$ to indicate its velocity.  Both of these are functions of space and time: $q = q(x,t)$; $u=u(x,t)$.  The total mass in the section of tube $[x_1,x_2]$ is

\begin{equation}
\int_{x_1}^{x_2} q(x,t) dx.
\end{equation}

This total mass can change in time due to fluid flowing in or out of this section of the tube.  We call the rate of flow the *flux*, and represent it with the function $f(q)$.  Thus the net rate of flow of mass into (or out of) the interval $[x_1,x_2]$ at time $t$ is

$$f(q(x_1,t)) - f(q(x_2,t)).$$

We just said that this rate of flow must equal the time rate of change of total mass; i.e.

$$\frac{d}{dt} \int_{x_1}^{x_2} q(x,t) dx = f(q(x_1,t)) - f(q(x_2,t)).$$

Now since $\int_{x_1}^{x_2} \frac{\partial}{\partial x} f(q) dx = f(q(x_2,t)) - f(q(x_1,t))$, we can rewrite this as

$$\frac{d}{dt} \int_{x_1}^{x_2} q(x,t) dx = -\int_{x_1}^{x_2} \frac{\partial}{\partial x} f(q) dx.$$

Under certain smoothness assumptions on $q$, we can move the time derivative inside the integral.  We'll also put everything on the left side, to obtain

$$\int_{x_1}^{x_2} \left(\frac{\partial}{\partial t}q(x,t) + \frac{\partial}{\partial x} f(q)\right) dx = 0.$$

Since this integral is zero for *any* choice of $x_1,x_2$, it must be that the integrand (the expression in parentheses) is actually zero *everywhere*!  Therefore we can write the **differential conservation law**

$$q_t + f_x = 0.$$

Here and throughout the course, we use subscripts to denote partial derivatives.
This equation expresses the fact that the total mass is conserved -- since locally the mass can change only due to a net inflow or outflow.

## Advection

In order to solve the conservation law above, we need an expression for the flux, $f$.  The rate of flow is just mass times velocity: $f=u q$.  Thus we obtain the **continuity equation**

$$q_t + (uq)_x = 0.$$

In general, we need another equation to determine the velocity $u(x,t)$.  In [Lesson 4](Lesson_04_Fluid_dynamics.ipynb) we'll look at the full equations of fluid dynamics, but for now let's consider the simplest case, in which all of the fluid flows at a single, constant velocity $u(x,t)=a$.  Then the continuity equation becomes the **advection equation**

$$q_t + a q_x = 0.$$

This equation has a very simple solution.  If we are given the density $q(x,0)=q_0(x)$ at time zero, then the solution is just

$$q(x,t) = q_0(x-at).$$

Let's plot the solution of the advection equation on the interval $[0,1]$ for the initial condition
$$q_0(x) = e^{-2(x-1/2)^2}.$$

First, let's import all the modules we'll need.

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from clawpack.visclaw.JSAnimation import IPython_display
```

Next, we'll set up a grid and the initial condition:

```python
x = np.linspace(0,1,1000)  # Spatial grid
t = np.linspace(0,1)       # Temporal grid
a = 1.0                    # Advection speed

def q_0(x):                # Initial condition
    return np.exp(-200.*(x-0.2)**2)
```

Finally, let's make an animation of the solution.  It will take a few moments to run this code.  For now, you don't need to worry about understanding all of the plotting code below.  Just play with the animation until you have a feel for how the solution behaves.

```python
fig = plt.figure(figsize=(8,4))      # Create an empty figure
ax  = plt.axes()
line, = ax.plot([], [],linewidth=2)  # Create an empty line plot
plt.axis((0,1,-0.1,1.1))             # Set the bounds of the plot

def plot_q(t):
    line.set_data(x,q_0(x-a*t))  # Replace the line plot with the solution at time t

animation.FuncAnimation(fig, plot_q, frames=t)  # Animate the solution
```

    <input id="_anim_slider d475256bf6f6494" type="range" style="width:350px" name="points" min="0" max="1" step="1" value="0" onchange="anim d475256bf6f6494.set_frame(parseInt(this.value));"></input>

    <button onclick="anim d475256bf6f6494.slower()">–</button>
    <button onclick="anim d475256bf6f6494.first_frame()"></button>
    <button onclick="anim d475256bf6f6494.previous_frame()"></button>
    <button onclick="anim d475256bf6f6494.reverse_animation()"></button>
    <button onclick="anim d475256bf6f6494.pause_animation()"></button>
    <button onclick="anim d475256bf6f6494.play_animation()"></button>
    <button onclick="anim d475256bf6f6494.next_frame()"></button>
    <button onclick="anim d475256bf6f6494.last_frame()"></button>
    <button onclick="anim d475256bf6f6494.faster()">+</button>
  <form action="#n" name="_anim_loop_select d475256bf6f6494" class="anim_control">

    <input id="_frame_no d475256bf6f6494" type="textbox" size="1" onchange="anim d475256bf6f6494.set_frame(parseInt(this.value));" onpaste="this.onchange();" oninput="this.onchange();"></input>

    <input type="radio" name="state" value="once" > Once </input>
    <input type="radio" name="state" value="loop" checked> Loop </input>
    <input type="radio" name="state" value="reflect" > Reflect </input>
  </form>

As you can see, the initial pulse just moves to the right at speed $a$ as time advances.  This isn't very interesting, but it captures the most important feature of hyperbolic equations: waves travel at finite speed.

## Characteristics

Notice that the solution value is constant along the line $x-at=x_0$, in the $x-t$ plane, for each value of $x_0$.  These lines are called **characteristics**; they are the trajectories along which solution information is transmitted.  The value $a$ is referred to as the **characteristic velocity**.  The code below plots some of these characteristics.

When we learn about more complicated conservation laws, we'll see that information still travels along characteristics, but those characteristics aren't necessarily straight lines.

```python
fig = plt.figure(figsize=(8,4))
ax  = plt.axes()

for x_0 in np.linspace(0,1,10):
    ax.plot(x,(x-x_0)/a,'-k')
plt.ylim(0,1)
```

## A finite volume method for advection

We can easily solve the advection equation exactly.  But the advection equation is a prototype for more complicated conservation laws that we will only be able to solve approximately by using numerical methods.  In order to better understand these methods, we will discuss them first in the context of the advection equation.

For simplicity, we'll suppose that we wish to solve the advection equation on the interval $[0,1]$.  We introduce a set of equally spaced *grid cells* of width $\Dx$, and write $x_i$ to mean the center of cell $i$.  Thus the first cell is the interval $[0,\Dx]$ and $x_1=\Dx/2$.  We will also write $x_\imh$ or $x_\iph$ to denote the left or right boundary of cell $i$, respectively.

We write $Q_i$ to denote the *average* value of the solution over cell $i$:

$$Q_i = \frac{1}{\Dx} \int_{x_\imh}^{x_\iph} q \ dx.$$

The simplest finite volume method is obtained by supposing that the solution is actually *equal* to $Q_i$ over all of cell $i$.

Suppose $a>0$.  Then the flux into cell $i$ from the left is $a Q_{i-1}$ and the flux out of cell $i$ to the right is $a Q_i$.  Then our integral conservation law reads

$$Q_i'(t) = -\frac{a}{\Dx}\left(Q_i - Q_{i-1}\right).$$

Applying a forward difference in time we obtain the *upwind method*

$$Q^{n+1}_i = Q^n_i -\frac{a}{\Dx}\left(Q_i - Q_{i-1}\right).$$

We call this the upwind method because the solution behaves as if it were being blown by a wind to the right, and the method uses the value $Q_{i-1}$ from the upwind direction.

Here is a bit of Python code to solve the advection equation using the upwind method.

```python
a = 1.0     # advection speed

m = 50      # number of cells
dx = 1./m   # Size of 1 grid cell
x = np.arange(-dx/2, 1.+dx/2, dx)  # Cell centers, including ghost cells

t = 0.      # Initial time
T = 0.5     # Final time
dt = 0.8 * dx / a  # Time step

Q = np.exp(-200*(x-0.2)**2)    # Initial data
Qnew = np.empty(Q.shape)

while t < T:

    # Extrapolation at boundaries:
    Qnew[0] = Q[1]
    Qnew[-1] = Q[-2]

    for i in range(1,len(x)):
        Qnew[i] = Q[i] - a*dt/dx * (Q[i]-Q[i-1])

    Q = Qnew.copy()
    t = t + dt

plt.plot(x,Q,linewidth = 2)
plt.title('t = '+str(t));
```

Notice how we set up a grid that contains an extra cell at each end, outside of the problem domain $[0,1]$.  These are called **ghost cells** and are often useful in handling the solution at the grid boundaries.

  The technique we have used to set the ghost cell values above, by copying the last value inside the grid to the ghost cells, is known as **zero-order extrapolation**.  It is useful for allowing waves to pass out of the domain (so-called *non-reflecting* boundaries).  Note that we don't actually need the ghost cell at the right end for the upwind method, but for other methods we will.

The upwind method is simple, but it is not very accurate.  Notice how the computed solution becomes wider and shorter over time.  This behavior is referred to as *dissipation*.

### Exercise

Now do the following with the code above:

1. Set $m=1000$ or more and notice that it takes some time to compute the solution.  Rewrite the inner loop (over $i$) as a single line with no loop, using numpy slicing.  For large values of $m$, the code with slicing is much faster.
1. Notice that the last step of the simulation goes past time $T$.  Modify the code so that the last step is adjusted to exactly reach $T$.
2. Change the code so that animation of the solution versus time is plotted.  You will want to accumulate frames of the solution in a list and then use the same kind of code we used above to animate the exact solution.
3. Add some code to plot the exact solution.

*Extra credit*: change the left boundary condition so that there is a sinusoidal wave coming in from the left:
$$u(0,t) = \sin(20 \pi t).$$
What do you notice about the sinusoid as it moves into the domain?

```python

```

After making it through the exercise above, you should feel pretty comfortable with the basics of scientific programming in Python.

## The CFL condition

Take a look at the line of code that sets the time step:
```python
    dt = 0.8 * dx / a
```
You might be wondering where that formula came from.  Rearranging that equation, we have
$$a \frac{\Delta t}{\Delta x} = 0.8.$$
The quantity $\nu = a \frac{\Delta t}{\Delta x}$ is the distance the exact solution moves during each time step, in units of grid cells.  It is referred to as the *CFL number* or just the *Courant number* after the authors Courant, Friedrichs and Lewy who [established its importance](http://www.stat.uchicago.edu/~lekheng/courses/302/classics/courant-friedrichs-lewy.pdf).  Try the following values of $\nu$ in the code above, and compare the results with those you obtained already using $\nu=0.8$.
1. $\nu = 1.0$
2. $\nu = 1.5$
3. $\nu = 0.1$

Finally, try setting $a$ to a negative value.  What happens?

The results you have observed can be explained as follows.  Over a time step of size $\Dt$, the solution moves by an amount $a \Dt$.  So $q(x_i,t_n)$ should be given exactly by $q(x_i-a\Dt,t_{n-1})$.  This is referred to as the *domain of dependence* of the solution.

The upwind method uses the values $Q_i^{n-1}$ and $Q_{i-1}^{n-1}$ to compute $Q_i^n$.  The points $(x_{i-1},t_n)$ and $(x_i,t_n)$ (as well as the locations of solution values they depend, and the ones those depend on, and so forth) are the *numerical domain of dependence*.

For this to work, the true domain of dependence $x_i-a\Dt$ must lie within the numerical domain of dependence, as $\Dt,\Dx \to 0$.  This is known as the [CFL condition](http://en.wikipedia.org/wiki/Courant%E2%80%93Friedrichs%E2%80%93Lewy_condition).

For the upwind method, that means that we must have
$$x_i - \Dx \le x_i - a \Dt \le x_i$$
or in other words
$$0 \le a \frac{\Dt}{\Dx} \le 1.$$

If the CFL condition is violated, the information that is used by the numerical method doesn't include the true information that influences the exact solution, so the numerical solution cannot be convergent.

## The Lax-Friedrichs method

The upwind method gets its name from the fact that it uses the value $U_{i-1}$ and not $U_{i+1}$.  Assuming $a>0$, the correct solution value $U_i^{n+1}$ should come from a point to the left of $x_i$ at time $t_n$ (i.e., the wind blows to the right, so $x_{i-1}$ is *upwind* of $x_i$).

This bias is fine for the advection equation, where we know everything moves in the same direction.  But for more complicated conservation laws, things may move in either direction.  It will be useful to have a method that uses information from both directions.  The simplest such method is known as the **Lax-Friedrichs** method.  For the conservation law $q_t + f(q)_x$, this method is

$$Q_i^{n+1} = \frac{1}{2}(Q_{i-1}^n + Q_{i+1}^n) - \frac{\Dt}{2\Dx}\left(f(Q_{i+1}^n) - f(Q_{i-1}^n)\right).$$

Notice that the flux difference term clearly approximates $f(q)_x$.  Meanwhile, the value of $q$ itself is approximated by taking the average of two neighboring values.  This average makes this method dissipative too (but it ensures that the solution is stable).

### Exercise

1. What does the CFL condition imply for the time step when using the Lax-Friedrichs method?

2. In the cell below, implement the Lax-Friedrichs method for advection.

```python

```

*Extra credit*: Compute the norm of the difference between the approximate and exact solution.  How does it change if you decrease $\Dx$?

## Accuracy

The methods we have used so far (i.e., the *upwind method* and the *Lax-Friedrichs method*) are both dissipative.  Furthermore, both of these methods are only *first order accurate*, meaning that if we reduce the values of $\Dt$ and $\Dx$ by a factor of two, the overall error decreases only by a factor of two.  In [Lesson 3](Lesson_03_High-resolution_methods.ipynb), we will learn about more accurate methods.  But first, in [Lesson 2](Lesson_02_Traffic.ipynb) we'll look at a model for traffic flow.


**Notas para contenedor de docker:**

Comando de docker para ejecución de la nota de forma local:

nota: cambiar `dir_montar` por la ruta de directorio que se desea mapear a `/datos` dentro del contenedor de docker.

```
dir_montar=<ruta completa de mi máquina a mi directorio>#aquí colocar la ruta al directorio a montar, por ejemplo:
#dir_montar=/Users/erick/midirectorio.
```

Ejecutar:

```
$docker run --rm -v $dir_montar:/datos --name jupyterlab_prope_r_kernel_tidyverse -p 8888:8888 -d palmoreck/jupyterlab_prope_r_kernel_tidyverse:3.0.16

```

Ir a `localhost:8888` y escribir el password para jupyterlab: `qwerty`

Detener el contenedor de docker:

```
docker stop jupyterlab_prope_r_kernel_tidyverse
```

Documentación de la imagen de docker `palmoreck/jupyterlab_prope_r_kernel_tidyverse:3.0.16` en [liga](https://github.com/palmoreck/dockerfiles/tree/master/jupyterlab/prope_r_kernel_tidyverse).

---

Para ejecución de la nota usar:

[docker](https://www.docker.com/) (instalación de forma **local** con [Get docker](https://docs.docker.com/install/)) y ejecutar comandos que están al inicio de la nota de forma **local**.

O bien dar click en alguno de los botones siguientes:

[](https://mybinder.org/v2/gh/palmoreck/dockerfiles-for-binder/jupyterlab_prope_r_kernel_tidyerse?urlpath=lab/tree/Propedeutico/Python/clases/2_calculo_DeI/1_aproximacion_a_derivadas_e_integrales.ipynb) esta opción crea una máquina individual en un servidor de Google, clona el repositorio y permite la ejecución de los notebooks de jupyter.

[](https://repl.it/languages/python3) esta opción no clona el repositorio, no ejecuta los notebooks de jupyter pero permite ejecución de instrucciones de Python de forma colaborativa con [repl.it](https://repl.it/). Al dar click se crearán nuevos ***repl*** debajo de sus users de ***repl.it***.

## Se sugiere apoyar esta nota con la lectura de los capítulos 5 y 6 del libro de texto de J. Kiusalaas "Numerical Methods in Engineering with Python 3".

# Función

Una función, $f$, es una regla de correspondencia entre un conjunto nombrado dominio, $D_f$ y otro conjunto nombrado codominio, $C_f$.

Notación: $f: A \rightarrow B$ es una función de un conjunto $\text{dom}f \subseteq A$ en un conjunto $B$.

---

**Observación**

$\text{dom}f$ (el dominio de $f$) podría ser un subconjunto propio de $A$, esto es, algunos elementos de $A$ y otros no, son mapeados a elementos de $B$.

---

**Ejemplos**

* La regla de correspondencia que asocia a cada estudiante su clave única.

* La regla de correspondencia que asocia a cada persona una casilla para votar en elecciones.

* $f: \mathbb{R} \rightarrow \mathbb{R}$ con $f(x) = x^2$.

```python
import matplotlib.pyplot as plt
import numpy as np
```

```python
x = np.linspace(-1,1,100) #100 puntos equidistantes entre -1,1
```

```python
y = lambda x: x**2
```

```python
y_eval = y(x)
```

```python
plt.plot(x,y_eval)
plt.title('y=x^2')
plt.show()
```

# Derivada de una función

Consideremos en lo que sigue $f: \mathbb{R} \rightarrow \mathbb{R}$.

$f$ es diferenciable en $x_0 \in (a,b)$ si $\displaystyle \lim_{x \rightarrow x_0} \frac{f(x)-f(x_0)}{x-x_0}$ existe y escribimos:

$$f^{(1)}(x_0) = \displaystyle \lim_{x \rightarrow x_0} \frac{f(x)-f(x_0)}{x-x_0}.$$

$f$ es diferenciable en $[a,b]$ si es diferenciable en cada punto de $[a,b]$. Análogamente definiendo la variable $h=x-x_0$ se tiene:

$f^{(1)}(x_0) = \displaystyle \lim_{h \rightarrow 0} \frac{f(x_0+h)-f(x_0)}{h}$ que típicamente se escribe como:

$$f^{(1)}(x) = \displaystyle \lim_{h \rightarrow 0} \frac{f(x+h)-f(x)}{h}.$$

---

**Comentario**

Si $f$ es diferenciable en $x_0$ entonces $f(x) \approx f(x_0) + f^{(1)}(x_0)(x-x_0)$. Gráficamente:

---

**Notación:** $\mathcal{C}^n([a,b])=\{\text{funciones } f:\mathbb{R} \rightarrow \mathbb{R} \text{ con } n \text{ derivadas continuas en el intervalo [a,b]}\}$.

---

**Observación**

En la definición anterior se calculan límites los cuales pueden calcularse con el paquete *SymPy*.

```python
import sympy
```

**Límite de $\frac{\sin(x)}{x}$ para $x \rightarrow 0$:**

```python
x = sympy.Symbol("x")
```

```python
quotient = sympy.sin(x)/x
```

```python
sympy.limit(quotient,x,0)
```

$\displaystyle 1$

---

**Límite de $\frac{\cos(x+h) - \cos(x)}{h}$ para $h \rightarrow 0$:**

```python
x, h = sympy.symbols("x, h")
```

```python
quotient = (sympy.cos(x+h) - sympy.cos(x))/h
```

```python
sympy.limit(quotient, h, 0)
```

$\displaystyle - \sin{\left(x \right)}$

Lo anterior corresponde a la **derivada de $\cos(x)$**:

```python
x = sympy.Symbol("x")
```

```python
sympy.cos(x).diff(x)
```

$\displaystyle - \sin{\left(x \right)}$

**Si queremos evaluar la derivada podemos usar:**

```python
sympy.cos(x).diff(x).subs(x,sympy.pi/2)
```

$\displaystyle -1$

**Otra forma:**

```python
sympy.Derivative(sympy.cos(x), x)
```

$\displaystyle \frac{d}{d x} \cos{\left(x \right)}$

```python
sympy.Derivative(sympy.cos(x), x).doit_numerically(sympy.pi/2)
```

$\displaystyle -1.0$

# Errores absolutos y relativos de una aproximación

Si `aprox` es mi cantidad con la que aproximo a mi objetivo `obj` entonces el error absoluto de `aprox` y el error relativo de `aprox` es:

$$ErrAbs(\text{aprox}) = |\text{aprox} - \text{obj}|.$$

$$ErrRel(\text{aprox}) = \frac{ErrAbs(\text{aprox})}{|\text{obj}|}.$$

---

**Observación**

* Obsérvese que `obj` debe ser distinto de cero para que el error relativo esté bien definido.

* si $ErrRel(aprox) \approx 10^{-k}$ se dice que `aprox` aproxima a `obj` con alrededor de $k$ dígitos correctos. Por ejemplo si $k=3$ entonces la cantidad `aprox` aproxima a la cantidad `obj` con alrededor de $3$ dígitos de precisión.

---

# Aproximación a una función por el teorema de Taylor

Las fórmulas de aproximación a las derivadas por diferencias finitas y a integrales definidas en un intervalo por las reglas de cuadratura Newton-Cotes pueden obtenerse con los **polinomios de Taylor** presentes en el teorema del mismo autor, el cual, bajo ciertas hipótesis nos proporciona una expansión de una función alrededor de un punto. Otras opciones son con polinomios de Lagrange, ver [Lagrange_polynomial](https://en.wikipedia.org/wiki/Lagrange_polynomial). El teorema de Taylor es el siguiente:

Sea $f \in \mathcal{C}^n([a,b])$, $f^{(n+1)}$ existe en [a,b]. Si $x_0 \in [a,b]$ entonces $\forall x \in [a,b]$ se tiene: $f(x) = P_n(x) + R_n(x)$ donde:

$$P_n(x) = \displaystyle \sum_{k=0}^n \frac{f^{(k)}(x_0)(x-x_0)^k}{k!} \quad (f^{(0)} = f)$$ y $$R_n(x) = \frac{f^{(n+1)}(\xi_x)(x-x_0)^{(n+1)}}{(n+1)!}$$ con $\xi_x$ entre $x_0, x$ y $x_0$ se llama centro.

## Ejemplo

Aproximemos a la función $\frac{1}{x}$ en el intervalo $[1,2]$ con polinomios de Taylor de orden $n$ con $n \in \{0,1,2\}$ con centro en $x_0=1.5$. Los polinomios de Taylor son:

$$P_0(x) = f(x_0) = \frac{2}{3} \quad \text{(constante)}$$

$$P_1(x) = f(x_0) + f^{(1)}(x_0)(x-x_0) = \frac{2}{3} - \frac{1}{x_0^2}(x-x_0) =\frac{2}{3} - \frac{1}{1.5^2}(x-1.5) \quad \text{(lineal)}$$

$$P_2(x) = f(x_0) + f^{(1)}(x_0)(x-x_0) + \frac{f^{(2)}(x_0)(x-x_0)^2}{2} = \frac{2}{3} - \frac{1}{x_0^2}(x-x_0) + \frac{1}{x_0^3}(x-x_0)^2 = \frac{2}{3} -\frac{1}{1.5^2}(x-1.5) + \frac{1}{1.5^3}(x-1.5)^2 \quad \text{(cuadrático)}$$

---

**Ejercicio**

Graficar la función y los polinomios constante, lineal y cuadrático en una sola gráfica con `matplotlib` en el intervalo [1,2]. ¿Cuánto es la aproximación de los polinomios en x=1.9? Calcula el error relativo de tus aproximaciones.

---

**Comentario**

Otras aproximaciones a una función se pueden realizar con:

* Interpoladores polinomiales (representación por Vandermonde, Newton, Lagrange).

---

# Diferenciación numérica por diferencias finitas

Las fórmulas de diferencias finitas pueden obtenerse con el teorema de Taylor. Por ejemplo:

Sea $f \in \mathcal{C}^1([a,b])$ y $f^{(2)}$ existe y está acotada $\forall x \in [a,b]$ entonces, si $x+h \in [a,b]$ con $h>0$ por el teorema de Taylor se tiene:

$$f(x+h) = f(x) + f^{(1)}(x)h + f^{(2)}(\xi_{x+h})\frac{h^2}{2}$$ con $\xi_{x+h} \in [x,x+h]$

Y al despejar $f^{(1)}(x)$ se tiene la **aproximación por diferencias hacia delante a la primera derivada de $f$**:

$$f^{(1)}(x) = \frac{f(x+h)-f(x)}{h} - f^{(2)}(\xi_{x+h})\frac{h}{2}$$

---

**Observación**

La aproximación por diferencias finitas a la primer derivada de la función tiene un error de orden $\mathcal{O}(h)$ por lo que una elección de $h$ igual a $.1 = 10^{-1}$ generará aproximaciones con alrededor de un dígito correcto.

---

Así también pueden obtenerse la versión centrada y aproximaciones a la segunda derivada de $f$:

**Aproximación por diferencias hacia delante para la segunda derivada**

$$\frac{d^2f(x)}{dx} \approx \frac{f(x+2h)-2f(x+h)+f(x)}{h^2}$$

**Aproximación por diferencias centradas a la primer y segunda derivada**

$$ \frac{df(x)}{dx} \approx \frac{f(x+h)-f(x-h)}{2h}$$

$$ \frac{d^2f(x)}{dx} \approx \frac{f(x+h)-2f(x)+f(x-h)}{h^2}$$

**Interpretación geométrica de aproximación por diferencias centradas a la primer derivada de $f$:**

---

**Ejercicio**

Aproximar la primera y segunda derivadas de la función `arctan` con diferencias finitas centradas en el punto $x=0.5$

```python
def central_approx_diff(f,x,h=0.0001): #el parámetro h tiene un valor default
    df =(f(x+h) - f(x-h))/(2.0*h) #primera derivada
    ddf =(f(x+h) - 2.0*f(x) + f(x-h))/h**2 #segunda derivada
    return df,ddf
```

```python
import math
```

```python
#Ejemplo de llamada a función utilizando el parámetro de default de h=0.0001
x = 0.5 #punto donde se realizará la aproximación
df, ddf = central_approx_diff(math.atan, x)
print('Primera derivada:', df)
print('Segunda derivada:', ddf)
```

    Primera derivada: 0.7999999995730867
    Segunda derivada: -0.6399999918915711

```python
#Ejemplo de llamada a función utilizando h=1e-6
h = 1e-6
x = 0.5
df, ddf = central_approx_diff(math.atan, 0.5,h)
print('Primera derivada:', df)
print('Segunda derivada:', ddf)
```

    Primera derivada: 0.799999999995249
    Segunda derivada: -0.639877040242709

```python
#derivadas analíticas:
d = 1/(1+x**2)
dd = (-2*x)/(1+x**2)**2
print(d)
print(dd)
```

    0.8
    -0.64

```python
def relative_absolute_error(aprox, obj):
    if(np.abs(obj) > 0):
        return np.abs(aprox-obj)/np.abs(obj)
    else:
        return np.abs(aprox-obj)
```

```python
rel_err_df = relative_absolute_error(df, d)
```

```python
rel_err_df
```

    5.938860514476119e-12

```python
rel_err_ddf = relative_absolute_error(ddf, dd)
```

```python
rel_err_ddf
```

    0.00019212462076725195

---

**Comentario**

Otra forma de evaluar las aproximaciones realizadas es con módulos o paquetes de Python creados para este propósito en lugar de crear nuestras funciones como la de `relative_absolute_error`. En la siguiente celda instalamos el paquete [pytest](https://docs.pytest.org/en/latest/) y mostramos cómo evaluar la calidad de la aproximación con la función [approx](https://docs.pytest.org/en/latest/reference.html#pytest-approx) de este paquete.

---

```python
!pip3 install -q --user pytest
```

      WARNING: The scripts py.test and pytest are installed in '/home/propeuser/.local/bin' which is not on PATH.
      Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.

```python
from pytest import approx
```

```python
df == approx(d)
```

    True

```python
ddf == approx(dd)
```

    False

Y podemos usar un valor definido de tolerancia definido para hacer la prueba (por default se tiene una tolerancia de $10^{-6}$).

```python
ddf == approx(dd, rel=1e-3,abs=1e-3)
```

    True

---

**Ejercicios**

La diferenciación numérica por diferencias finitas **no es un proceso con una alta exactitud** pues los problemas del redondeo de la aritmética en la máquina se hacen presentes en el mismo. Como ejemplo de esta situación hágase el siguiente ejercicio 1.

1) **(Tarea) Realizar una gráfica de log(error relativo) vs log(h) (h en el eje horizontal) para aproximar la segunda derivada de $f(x)=e^{-x}$ en $x=1$ con $h \in \{10^{-16}, 10^{-14}, \dots , 10^{-1}\}$ y diferencias hacia delante. Valor a aproximar: $f^{(2)}(1) = e^{-1}$. Usar:**

$$\frac{d^2f(x)}{dx} \approx \frac{f(x+2h)-2f(x+h)+f(x)}{h^2}$$

2) **Crear un módulo con nombre `central_finite_derivative.py` en el que se tengan dos funciones de Python que aproximen la primera y segunda derivada de una función en un punto `x`. Ambas funciones reciben `fun`, `x` y `h` donde: `fun` es la función a calcularse su primera y segunda derivadas, `x` es el punto donde se realiza la aproximación y `h` es el parámetro de espaciado entre `x` y `x+h` igual a $h=10^{-6}$. La salida de cada función es un `float`.
Función de prueba: `math.atan` y `x=0.9`.**.

**Los nombres de las funciones y sus salidas son:**

| central_finite_derivative.py   | parámetros de entrada |salida|
|:---:|:---:|:---:|
| approx_first_derivative  | fun (function), x (float) ,h (float) | float|
| approx_second_derivative | fun (function), x (float), h (float)| float|

**3) (Tarea) Mismo ejercicio que 2) pero función de prueba: `math.asin` y `x=0.5`.**

---

## Diferenciación numérica en más dimensiones

La anterior aproximación por diferencias finitas también puede utilizarse para aproximar el gradiente de una función $f: \mathbb{R}^n \rightarrow \mathbb{R}$ considerando que:

$$\nabla f(x) =
\begin{array}{l}
\left[ \begin{array}{c}
\frac{\partial f(x)}{\partial x_1}\\
\vdots\\
\frac{\partial f(x)}{\partial x_n}
\end{array}
\right] = \left[
\begin{array}{c}
\displaystyle \lim_{h \rightarrow 0} \frac{f(x+he_1) - f(x)}{h}\\
\vdots\\
\displaystyle \lim_{h \rightarrow 0} \frac{f(x+he_n) - f(x)}{h}
\end{array}
\right]
\end{array} \in \mathbb{R}^n$$

con $e_i$ vectores canónicos (poseen 1 en la posición $i$ y cero en las restantes) para $i=1, \dots, n$.

---

**Observación**

El gradiente de una función como se definió arriba también es una función, de hecho: $\nabla f: \mathbb{R}^n \rightarrow \mathbb{R}^n$.

---

En este contexto el teorema de Taylor para el polinomio de grado 2 se puede escribir como: $$P_2(x) = f(x_0) + \nabla f(x_0)^T(x-x_0) + \frac{1}{2}(x-x_0)^T\nabla^2f(x_0)(x-x_0) $$

# Integración numérica

Las reglas o métodos por cuadratura nos ayudan a aproximar integrales con sumas de la forma:

$$\displaystyle \int_a^bf(x)dx \approx \displaystyle \sum_{i=0}^nw_if(x_i)$$

donde: $w_i$ es el peso para el nodo $x_i$. Los valores $f(x_i)$ se asumen conocidos.

Todas las reglas o métodos por cuadratura se obtienen con interpoladores polinomiales del integrando (por ejemplo usando la representación de Lagrange) o también con el teorema Taylor.

Se realizan aproximaciones numéricas por:
* Desconocimiento de la función en todo el intervalo $[a,b]$ y sólo se conoce en los nodos su valor.
* Inexistencia de antiderivada o primitiva del integrando. Por ejemplo:

$$\displaystyle \int_a^be^{-\frac{x^2}{2}}dx$$ con $a,b$ números reales.

Dependiendo de la ubicación de los nodos y pesos es el método de cuadratura que resulta:

* Newton-Cotes si los nodos y pesos son equidistantes como la regla del rectángulo, trapecio y Simpson (con el teorema de Taylor es posible obtener tales fórmulas).
* Cuadratura Gaussiana si se desea obtener reglas o fórmulas que tengan la mayor exactitud posible. Ejemplos de este tipo de cuadratura se tiene la regla por cuadratura Gauss-Legendre en [-1,1] o Gauss-Hermite para el caso de integrales en $[-\infty, \infty]$ con integrando $e^{-x^2}f(x)$.

---

**Observación**

Con *SymPy* también es posible calcular integrales definidas o indefinidas.

**Integral indefinida de $\sin(x)$:**

```python
x = sympy.Symbol('x')
```

```python
sympy.integrate(sympy.sin(x))
```

$\displaystyle - \cos{\left(x \right)}$

**Integral definida de: $\displaystyle \int_0^\infty e^{-x}dx$:**

```python
sympy.integrate(sympy.exp(-x), (x, 0, sympy.oo))
```

$\displaystyle 1$

**otra forma:**

```python
sympy.Integral(sympy.exp(-x), (x, 0, sympy.oo))
```

$\displaystyle \int\limits_{0}^{\infty} e^{- x}\, dx$

```python
sympy.Integral(sympy.exp(-x), (x, 0, sympy.oo)).doit()
```

$\displaystyle 1$

---

## Newton-Cotes

## Regla simple del rectángulo o del punto medio

Denotaremos a esta regla como $Rf$. En este caso se aproxima el integrando $f$ por un polinomio de grado **cero** con nodo en $x_1 = \frac{a+b}{2}$. Entonces:

$$\displaystyle \int_a^bf(x)dx \approx \int_a^bf(x_1)dx = (b-a)f(x_1)=(b-a)f\left( \frac{a+b}{2} \right ) = hf(x_1)$$

con $h=b-a, x_1=\frac{a+b}{2}$.

### Ejemplo de implementación de regla simple de rectángulo

Utilizar la regla simple del rectángulo para aproximar la integral $\displaystyle \int_0^1e^{-x^2}dx \approx 0.7468241328124271$.

```python
f=lambda x: math.exp(-x**2) #integrand function
```

```python
def Rf(f,a,b):
    node=(a+b)/2 #middle point
    h=b-a
    return h*f(node) #polynomial of zero degree
```

```python
Rf(f,0,1)
```

    0.7788007830714049

Para contrastar con nuestra implementación usamos la función de `quad` dentro del paquete `scipy`. Ver [liga1 a quad](https://docs.scipy.org/doc/scipy/reference/tutorial/integrate.html), [liga 2 a quad](https://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.quad.html#scipy.integrate.quad) y [liga 3 a quad](https://github.com/scipy/scipy/blob/v1.4.1/scipy/integrate/quadpack.py#L44-L432) para referencias sobre `quad`.

```python
from scipy.integrate import quad
```

```python
obj,err = quad(f, 0, 1)
```

```python
obj
```

    0.7468241328124271

```python
err
```

    8.291413475940725e-15

```python
relative_absolute_error(Rf(f,0,1), obj )
```

    0.04281684114646715

```python
Rf(f,0,1) == approx(obj)
```

    False

```python
Rf(f,0,1) == approx(obj, abs=1e-1, rel=1e-1)
```

    True

## Regla compuesta del rectángulo

En cada subintervalo construído como $[a_{i-1},a_i]$ con $i=1,\dots,n$ se aplica la regla simple $Rf$, esto es:

$$\displaystyle \int_{a_{i-1}}^{a_i}f(x)dx \approx R_i(f) \forall i=1,\dots,n.$$

De forma sencilla se puede ver que la regla compuesta del rectángulo $R_c(f)$ se escribe:

$$R_c(f) = \displaystyle \sum_{i=1}^n(a_i-a_{i-1})f\left( \frac{a_i+a_{i-1}}{2}\right) = \frac{h}{n}\sum_{i=1}^nf\left( \frac{a_i+a_{i-1}}{2}\right)$$

con $h=b-a$ y $n$ número de subintervalos.

---

**Nota**

Los nodos para el caso del rectángulo se obtienen con la fórmula: $x_i = a +(i+\frac{1}{2})\hat{h}, \forall i=0,\dots,n-1, \hat{h}=\frac{h}{n}$. Por ejemplo si $a=1, b=2$ y $\hat{h}=\frac{1}{4}$ (por tanto $n=4$ subintervalos) entonces:

Los subintervalos que tenemos son: $\left[1,\frac{5}{4}\right], \left[\frac{5}{4}, \frac{6}{4}\right], \left[\frac{6}{4}, \frac{7}{4}\right]$ y $\left[\frac{7}{4}, 2\right]$.

Los nodos están dados por:

$$x_0 = 1 + \left(0 + \frac{1}{2} \right)\frac{1}{4} = 1 + \frac{1}{8} = \frac{9}{8}$$

$$x_1 = 1 + \left(1 + \frac{1}{2}\right)\frac{1}{4} = 1 + \frac{3}{2}\cdot \frac{1}{4} = \frac{11}{8}$$

$$x_2 = 1 + \left(2 + \frac{1}{2}\right)\frac{1}{4} = 1 + \frac{5}{8}\cdot \frac{1}{4} = \frac{13}{8}$$

$$x_3 = 1 + \left(3 + \frac{1}{2}\right)\frac{1}{4} = 1 + \frac{7}{2}\cdot \frac{1}{4} = \frac{15}{8}$$

---

### Ejemplo de implementación de regla compuesta de rectángulo

Utilizar la regla compuesta del rectángulo para aproximar la integral $\int_0^1e^{-x^2}dx \approx 0.7468241328124271$.

```python
f=lambda x: np.exp(-x**2)
```

```python
def Rcf(f,a,b,n): #Rcf: composite rectangle method
    """
    Compute numerical approximation using rectangle or mid-point
    method in an interval.
    Nodes are generated via formula: x_i = a+(i+1/2)h_hat for
    i=0,1,...,n-1 and h_hat=(b-a)/n
    Args:

        f (float): function expression of integrand.

        a (float): left point of interval.

        b (float): right point of interval.

        n (int): number of subintervals.

    Returns:

        sum_res (float): numerical approximation to integral
            of f in the interval a,b
    """
    h_hat = (b-a)/n
    sum_res = 0
    for i in range(n):
        x = a+(i+1/2)*h_hat
        sum_res += f(x)
    return h_hat*sum_res
```

```python
a = 0
b = 1
```

```python
aprox_1=Rcf(f,a,b,1) #1 subinterval
aprox_1
```

    0.7788007830714049

```python
aprox_2=Rcf(f,a,b,2) #2 subintervals
aprox_2
```

    0.7545979437721995

```python
aprox_3=Rcf(f,a,b,10**3)#1000 subintervals
aprox_3
```

    0.746824163469049

Y se puede evaluar el error de aproximación con el error relativo:

```python
obj, err = quad(f, a, b)
(relative_absolute_error(aprox_1,obj), relative_absolute_error(aprox_2,obj), relative_absolute_error(aprox_3,obj))
```

    (0.04281684114646715, 0.010409158754012628, 4.1049318789768585e-08)

```python
obj
```

    0.7468241328124271

## Regla del trapecio

De forma sencilla se puede ver que la regla compuesta del trapecio $T_c(f)$ se escribe como:

$$T_c(f) = \displaystyle \frac{h}{2n}\left[f(x_0)+f(x_n)+2\displaystyle\sum_{i=1}^{n-1}f(x_i)\right]$$

con $h=b-a$ y $n$ número de subintervalos.

---

**Nota**

Los nodos para el caso del trapecio se obtienen con la fórmula: $x_i = a +i\hat{h}, \forall i=0,\dots,n, \hat{h}=\frac{h}{n}$.

---

**(Tarea) Ejercicio:**

**En un módulo con nombre `numerical_integration.py` aproximar el valor de la integral $\displaystyle \int_0^{\pi}sin(x)dx = 2$ con regla compuesta del trapecio con $n=10^4$ subintervalos. Para este caso utilizar la función:**

```
def Tcf(f,a,b,n): #Tcf: composite trapezoidal method for f
    """
    Compute numerical approximation using trapezoidal method in
    an interval.
    Nodes are generated via formula: x_i = a+ih_hat for i=0,1,...,n and h_hat=(b-a)/n
    Args:
        f (function): function expression of integrand
        a (float): left point of interval
        b (float): right point of interval
        n (float): number of subintervals
    Returns:
        sum_res (float): numerical approximation to integral of f in the interval a,b
    """
```

---

## Regla de Simpson

**Revisar sección 6.2 del libro de texto de J. Kiusalaas Numerical Methods in Engineering with Python 3 para la fórmula de Simpson.**

## Cuadratura Gaussiana

Una distinción con la cuadratura por Newton-Cotes es que en este caso los pesos y nodos se eligen para tener una regla o fórmula que integre de forma exacta a los polinomios de grado menor o igual que $2n+1$ con $n \in \{0,1,2,\dots\}$. Esto es:

$$\displaystyle \int_a^b w(x)Q_m(x)dx = \sum_{i=0}^nw_iQ_m(x_i)$$ con $Q_m$ polinomio de grado $m \leq 2n+1$ y $w(x)$ función de ponderación.

## Gauss-Legendre

Si se elige la base canónica de polinomios $\{1, x, x^2, \dots x^{2n+1}\}$, $w(x)=1$ (no ponderación), el intervalo $[-1,1]$ y la solución del siguiente sistema de ecuaciones podemos obtener la regla por cuadratura conocida con el nombre de Gauss-Legendre.

---

**Comentario**

Otra forma de obtener esta regla es vía los polinomios de Legendre, ver [Legendre polynomials](https://en.wikipedia.org/wiki/Legendre_polynomials)

---

Teniendo el objetivo de integrar de forma exacta los polinomios de la base canónica para el caso de $n=1$, resultan las siguientes ecuaciones:

$$2=\displaystyle \int_{-1}^{1}1dx = w_0 \cdot 1 + w_1\cdot1$$

$$0 = \displaystyle \int_{-1}^1xdx = w_0x_0 + w_1x_1$$

$$\frac{2}{3} = \displaystyle \int_{-1}^1x^2dx = w_0x_0^2 + w_1x_1^2$$

$$0 = \displaystyle \int_{-1}^1x^3dx = w_0x_0^3 + w_1x_1^3$$

el cual es un sistema de 4 ecuaciones no lineales con 4 incógnitas: $(w_0, x_0), (w_1, x_1)$ cuya solución es: $w_0 = w_1 = 1$ y $x_0 =-\sqrt{\frac{1}{3}} \approx -0.57735, x_1 = \sqrt{\frac{1}{3}} \approx 0.57735$.

Ya se han calculado los pesos y nodos para diferentes valores de los grados de los polinomios. A continuación se tiene la siguiente tabla para $n \in \{0,1,2,3,4\}$ y una integral definida en el intervalo $[-1,1]$:

|n  |grado|# (num de nodos + num de pesos)|pesos: $w_i, w_{i+1}$    |nodos: $x_i, x_{i+1}$              |
|---|:----:|:---:|:-------:|:-----------------:|
|0  |1|2|2|0|
|1  |3|4|1,1|-$\sqrt{\frac{1}{3}}$,$\sqrt{\frac{1}{3}}$|
|2  |5|6|$\frac{5}{9}$, $\frac{8}{9}$, $\frac{5}{9}$ |$-\sqrt{\frac{3}{5}}$, 0, $\sqrt{\frac{3}{5}}$  |
|3  |7|8|0.347855, 0.652145, 0.652145, 0.347855|-0.861136,-0.339981,0.339981,0.861136|
|4  |9|10|0.236927, 0.478629, 0.568889, 0.478629, 0.236927 | -0.90618, -0.538469, 0, 0.538469, 0.90618|

Y para una integral en $[a,b]$ se utiliza la fórmula de cambio de variable:

$$\displaystyle \int_{a}^{b}f(t)dt \approx \frac{(b-a)}{2} \displaystyle \sum_{i=0}^nw_if \left (\frac{1}{2}[(b-a)x_i+a+b] \right )$$

con los pesos definidos para el intervalo $[-1,1]$

### Ejemplo con la cuadratura de Gauss-Legendre utilizando dos nodos aproximar la integral $\int_0^1e^{-t^2}dt \approx 0.7468241328124271$

**Solución:** Utilizamos $f(t) = e^{-t^2}$ en:

$$\displaystyle \int_{a}^{b}f(t)dt \approx \frac{(b-a)}{2} \displaystyle \sum_{i=0}^nw_if \left (\frac{1}{2}[(b-a)x_i+a+b] \right )$$

por lo que se tiene:

$$
\begin{eqnarray}
\int_0^1e^{-t^2}dt &=& \frac{(1-0)}{2} \displaystyle \sum_{i=0}^2w_i \cdot \exp\left[-\left ({\frac{1}{2}[(1-0)x_i + 0 + 1]} \right) ^2 \right] \nonumber \\
&=& \frac{1}{2} \left ( 1\cdot \exp \left[ -\left ( \frac{1}{2} \left[-\sqrt{\frac{1}{3}}+1 \right] \right)^2 \right] + 1\cdot \exp \left[ -\left ( \frac{1}{2} \left[\sqrt{\frac{1}{3}}+1 \right] \right)^2 \right] \right) \nonumber \\
\end{eqnarray}
$$

y haciendo los cálculos en Python:

```python
cte0 = -(1/2*(-math.sqrt(1/3)+1))**2
cte1 = -(1/2*(math.sqrt(1/3)+1))**2
w0 = 1
w1 = 1
approx_GL = 1/2*(w0*math.exp(cte0) + w1*math.exp(cte1))
```

```python
approx_GL
```

    0.7465946882828597

```python
obj
```

    0.7468241328124271

```python
relative_absolute_error(approx_GL, obj)
```

    0.00030722698890749486

---

**(Tarea) Ejercicio: aproximar la integral de:**

$$\displaystyle \int_0^1e^{-\frac{t^2}{2}}dt \approx .855624391892149$$

**con cuadratura Gauss-Legendre**

**1) En el módulo `numerical_integration.py` crear la función:**

```
def GLf(f,a,b,n): #GLf: Gauss-Legendre quadrature for f
    """
    Compute numerical approximation using quadrature Gauss-Legendre.
    Weights and nodes are obtained with table for n=0,1,2,3,4
    Args:
        f (function): function expression of integrand
        a (float): left point of interval
        b (float): right point of interval
        n (float): number of subintervals
    Returns:
        sum_res (float): numerical approximation to integral of f in the interval a,b
    """
```

**2) Realizar una gráfica de la forma error relativo vs $n$ ($n$ en el eje horizontal).**

---

## Otras reglas de cuadratura Gaussiana

Las reglas de cuadratura Gaussiana como se escribió en la sección anterior buscan tener la mayor exactitud posible para integrar polinomios de grado menor o igual $2n+1$. Diferentes elecciones de polinomios resultan en distintas reglas. Entre las más populares se encuentran:

* Gauss-Chebyshev.
* Gauss-Laguerre.
* Gauss-Hermite.
* Cuadratura Gaussiana con singularidad logarítmica.

Y se puede probar que los nodos en la regla de cuadratura de cada una de las reglas anteriores son las raíces de los polinomios que las definen.

**Revisar sección 6.4 el libro de texto de J. Kiusalaas Numerical Methods in Engineering with Python 3 para la expresiones de las reglas anteriores y los valores de los pesos y nodos de cada regla anterior para diferentes números de nodos. Como ayuda está el documento: [Gauss-Hermite-2-nodos.pdf](https://drive.google.com/file/d/1w7fGm0oOAoYlVeL_S61O1IGAdpaMBkJM/view?usp=sharing) para su consulta.**

---

**Ejercicio**

**(Tarea) Aproximar las integrales: $$(2\pi\sigma^2)^{-\frac{1}{2}}\displaystyle \int_{-\infty}^\infty te^{\frac{-(t-\mu)^2}{2\sigma^2}}dt$$**

$$(2\pi\sigma^2)^{-\frac{1}{2}}\displaystyle \int_{-\infty}^\infty t^2e^{\frac{-(t-\mu)^2}{2\sigma^2}}dt$$

**donde: $\sigma=0.25, \mu=0.15$ cuyos valores respectivamente son: $0.15, 0.085$ con cuadratura de Gauss-Hermite y $n=5$. Para lo anterior, realizar cambio de variable $x=\frac{t-\mu}{\sqrt{2\sigma^2}}, dt=\sqrt{2\sigma^2}dx$. En el módulo de `numerical_integration.py` crear una función:**

```
def GHf(f,mu, sigma): #GHf: Gauss-Hermite quadrature for f
    """
    Compute numerical approximation using quadrature Gauss-Hermite.
    Weights and nodes are obtained with table in Kiusalaas for n=6
    Args:
        f (function): function expression of integrand
        mu (float): mean
        sigma (float): standard deviation
    Returns:
        sum_res (float): numerical approximation to integral of f in the interval a,b
    """

```

---

## Integración numérica en más dimensiones

**Revisar sección 6.5 del libro de texto de J. Kiusalaas Numerical Methods in Engineering with Python 3 integrales múltiples hasta el ejemplo 6.14.**

### The curse of dimensionality

Como puede observarse en el desarrollo de la sección 6.5, la aproximación por integración numérica a integrales múltiples por los métodos por Newton-Cotes o cuadratura Gaussiana implican sustituir la integral $\int$ sobre una región por una $\sum$ y evaluaciones del integrando en un conjunto de nodos multiplicados por pesos (suma ponderada). Esto para dimensiones igual a dos o tres es viable pero para dimensiones altas no es computacionalmente práctico.

La razón de lo anterior tiene que ver con la cantidad de nodos y finalmente evaluaciones del integrando que se tienen que realizar para tener una aproximación con una exactitud aceptable. Por ejemplo, la regla del rectángulo o del trapecio tienen un error de orden $\mathcal{O}(\hat{h}^2)$ independientemente de si se está aproximando integrales de una o más dimensiones.

Supóngase que se utilizan $n$ nodos para tener un valor de espaciado igual a $\hat{h}$ en una dimensión, entonces para $\mathcal{D}$ dimensiones se requerirían $N=n^\mathcal{D}$ evaluaciones del integrando, o bien, si se tiene un valor de $N$ igual a $10, 000$ y $\mathcal{D}=4$ dimensiones el error sería del orden $\mathcal{O}(N^{-2/\mathcal{D}})$ lo que implicaría un valor de $\hat{h}=.1$ para aproximadamente sólo **dos dígitos** correctos en la aproximación (para el enunciado anterior recuérdese que $\hat{h}$ es proporcional a $n^{-1}$ y $n$ = $N^{1/\mathcal{D}}$). Este esfuerzo enorme de evaluar $N$ veces el integrando para una exactitud pequeña se debe al problema de generar puntos para *llenar* un espacio $\mathcal{D}$-dimensional y se conoce con el nombre de la maldición de la dimensionalidad, [***the curse of dimensionality***](https://en.wikipedia.org/wiki/Curse_of_dimensionality).

Como alternativa a los métodos por cuadratura anteriores para las integrales de más dimensiones se tienen los métodos de integración por el método Monte Carlo que generan aproximaciones con una exactitud moderada (del orden de $\mathcal{O}(n^{-1/2})$) para un número de puntos moderado independiente de la dimensión. Tales métodos de integración son similares a los métodos por cuadratura en el sentido que se eligen puntos en los que se evaluará el integrando para sumar sus valores pero la diferencia con estos métodos, es que en el método de integración por Monte Carlo los puntos son seleccionados de una forma **aleatoria** (de hecho es pseudo-aleatoria pues se generan con un programa de computadora) en lugar de generarse con una fórmula.

Los métodos por integración por Monte Carlo requieren el concepto de **variables aleatorias**.

## Referencias

* [SymPy](https://www.sympy.org/en/index.html) y [Numerical Python by Robert Johansson, Apress](https://www.apress.com/gp/book/9781484242452)


# Funciones de utilidad y aversión al riesgo

Antes de comenzar la clase, por favor entrar [este enlace](http://cursos.iteso.mx/course/view.php?id=1480)

En el módulo anterior aprendimos
- qué es un portafolio, cómo medir su rendimiento esperado y su volatilidad;
- un portafolio de activos riesgosos tiene menos riesgo que la suma ponderada de los riesgos individuales,
- y que esto se logra mediante el concepto de diversificación;
- la diversificación elimina el riesgo idiosincrático, que es el que afecta a cada compañía en particular,
- sin embargo, el riesgo de mercado no se puede eliminar porque afecta a todos por igual.
- Finalmente, aprendimos conceptos importantes como frontera de mínima varianza, portafolios eficientes y el portafolio de mínima varianza, que son claves en el problema de selección óptima de portafolios.

Muy bien, sin embargo, para plantear el problema de selección óptima de portafolios necesitamos definir la función que vamos a optimizar: función de utilidad.

**Objetivos:**
- ¿Cómo tomamos decisiones según los economistas?
- ¿Cómo toman decisiones los inversionistas?
- ¿Qué son las funciones de utilidad?

*Referencia:*
- Notas del curso "Portfolio Selection and Risk Management", Rice University, disponible en Coursera.
___

## 1. Introducción

La teoría económica comienza con una suposición muy importante:
- **cada individuo actúa para obtener el mayor beneficio posible con los recursos disponibles**.
- En otras palabras, **maximizan su propia utilidad**

¿Qué es utilidad?
- Es un concepto relacionado con la felicidad, pero más amplio.
- Por ejemplo, yo obtengo utilidad de lavar mis dientes o comer sano. Ninguna de las dos me brindan felicidad, pero lo primero mantendrá mis dientes sanos y en el largo plazo, lo segundo probablemente contribuirá a una buena vejez.

Los economistas no se preocupan en realidad por lo que nos da utilidad, sino simplemente que cada uno de nosotros tiene sus propias preferencias.
- Por ejemplo, a mi me gusta el café, el fútbol, los perros, la academia, viajar, entre otros.
- Ustedes tienen sus propias preferencias también.

La vida es compleja y con demasiada incertidumbre. Debemos tomar decisiones a cada momento, y estas decisiones involucran ciertos "trade-off".
- Por ejemplo, normalmente tenemos una compensación entre utilidad hoy contra utilidad en el futuro.
- Debemos balancear nuestro consumo hoy contra nuestro consumo luego.
- Por ejemplo, ustedes gastan cerca de cuatro horas a la semana viniendo a clases de portafolios, porque esperan que esto contribuya a mejorar su nivel de vida en el futuro.

De manera que los economistas dicen que cada individuo se comporta como el siguiente optimizador:

\begin{align}
\max & \quad\text{Utilidad}\\
\text{s. a.} & \quad\text{Recursos disponibles}
\end{align}

¿Qué tiene que ver todo esto con el curso?
- En este módulo desarrollaremos herramientas para describir las preferencias de los inversionistas cuando se encuentran con decisiones de riesgo y rendimiento.
- Veremos como podemos medir la actitud frente al riesgo, ¿cuánto te gusta o disgusta el riesgo?
- Finalmente, veremos como podemos formular el problema de maximizar la utilidad de un inversionista para tomar la decisión de inversión óptima.
___

## 2. Funciones de utilidad.

¿Cómo tomamos decisiones?
Por ejemplo:
- Ustedes tienen que decidir si venir a clase o quedarse en su casa viendo Netflix, o ir al gimnasio.
- Tienen que decidir entre irse de fiesta cada fin, o ahorrar para salir de vacaciones.

En el caso de un portafolio, la decisión que se debe tomar es **¿cuáto riesgo estás dispuesto a tomar por qué cantidad de rendimiento?**

**¿Cómo evaluarías el "trade-off" entre tener cetes contra una estrategia muy riesgosa con un posible altísimo rendimiento?**

De manera que veremos como tomamos decisiones cuando tenemos distintas posibilidades. Específicamente, hablaremos acerca de las **preferencias**, como los economistas usan dichas preferencias para explicar las decisiones y los "trade-offs" en dichas decisiones.

Usamos las **preferencias** para describir las decisiones que tomamos. Las preferencias nos dicen cómo un individuo evalúa los "trade-offs" entre distintas elecciones.

Por definición, las preferencias son únicas para cada individuo. En el problema de selección de portafolios:
- las preferencias que dictan cuánto riesgo estás dispuesto a asumir por cuánto rendimiento, son específicas para cada uno de ustedes.
- Sus respuestas a esa pregunta pueden ser muy distintas, porque tenemos distintas preferencias.

Ahora, nosotros no podemos *cuantificar* dichas preferencias.
- Por esto usamos el concepto de utilidad, para medir qué tan satisfecho está un individuo con sus elecciones.
- Así que podemos pensar en la utilidad como un indicador numérico que describe las preferencias,
- o un índice que nos ayuda a clasificar diferentes decisiones.
- En términos simples, **la utilidad nos ayuda a transmitir a números la noción de cómo te sientes**;
- mientras más utilidad, mejor te sientes.

**Función de utilidad**: manera sistemática de asignar una medida o indicador numérico para clasificar diferentes escogencias.

El número que da una función de utilidad no tiene significado alguno. Simplemente es una manera de clasificar diferentes decisiones.

**Ejemplo.**

Podemos escribir la utilidad de un inversionista como función de la riqueza,

$$U(W).$$

- $U(W)$ nos da una medida de qué tan satisfechos estamos con el nivel de riqueza que tenemos.
- $U(W)$ no es la riqueza como tal, sino que la función de utilidad traduce la cantidad de riqueza en un índice numérico subjetivo.

¿Cómo luciría gráficamente una función de utilidad de riqueza $U(W)$?

 Ver en el tablero
- ¿Qué caracteristicas debe tener?
- ¿Cómo es su primera derivada?
- ¿Cómo es su segunda derivada?
- Tiempos buenos: riqueza alta (¿cómo es la primera derivada acá?)
- Tiempos malos: poca riqueza (¿cómo es la primera derivada acá?)

```python
from matplotlib import pyplot as plt
%matplotlib inline
import numpy as np
```

```python
W = np.linspace(0, 1.5, 100)
U1 = W
U2 = W**2
U3 = W**0.5
```

```python
plt.figure(figsize=(6, 4))
plt.plot(W, U1, label=r'$U_1(W)=W$')
plt.plot(W, U2, label=r'$U_2(W)=W^2$')
plt.plot(W, U3, label=r'$U_3(W)=\sqrt{W}$')
plt.grid()
plt.legend(loc='best')
plt.xlabel('Wealth ($W$)')
plt.ylabel('Utility ($U$)')
#plt.axis([0, 0.4, 0, 0.8])
#plt.axis([1, 1.2, 1, 1.2])
```

## 3. Aversión al riesgo

Una dimensión importante en la toma de decisiones en finanzas y economía es la **incertidumbre**. Probablemente no hay ninguna decisión en economía que no involucre riesgo.

- A la mayoría de las personas no les gusta mucho el riesgo.
- De hecho, estudios del comportamiento humano de cara al riesgo, sugieren fuertemente que los seres humanos somos aversos al riesgo.
- Por ejemplo, la mayoría de hogares poseen seguros para sus activos.
- Así, cuando planteamos el problema de selección óptima de portafolios, suponemos que el inversionista es averso al riesgo.

¿Qué significa esto en términos de preferencias? ¿Cómo lo medimos?

- Como seres humanos, todos tenemos diferentes genes y preferencias, y esto aplica también a la actitud frente al riesgo.
- Por tanto, la aversión al riesgo es clave en cómo describimos las preferencias de un inversinista.
- Individuos con un alto grado de aversión al riesgo valorarán la seguridad a un alto precio, mientras otros no tanto.
- De manera que alguien con alta aversión al riesgo, no querrá enfrentarse a una situación con resultado incierto y querrá pagar una gran prima de seguro para eliminar dicho riesgo.
- O equivalentemente, una persona con alta aversión al riesgo requerirá una compensación alta si se decide a asumir ese riesgo.

El **grado de aversión al riesgo** mide qué tanto un inversionista prefiere un resultado seguro a un resultado incierto.

Lo opuesto a aversión al riesgo es **tolerancia al riesgo**.

 Ver en el tablero gráficamente, cómo se explica la aversión al riesgo desde las funciones de utilidad.

**Conclusión:** la concavidad en la función de utilidad dicta qué tan averso al riesgo es el individuo.

```python
def straight(x1, y1, x2, y2, x):
    m = (y2 - y1) / (x2 - x1)
    return m * (x - x1) + y1
```

```python
plt.figure(figsize=(6, 4))
plt.plot(W, U3, label=r'$U_3(W)=\sqrt{W}$')
x1, x2 = 0.1, 1.0
y1, y2 = x1**0.5, x2**0.5
plt.axvline(x=x1, ls='--', color='grey')
plt.axvline(x=x2, ls='--', color='grey')
plt.axhline(y=y1, ls='--', color='grey')
plt.axhline(y=y2, ls='--', color='grey')
plt.plot(x1, y1, '*b', ms=5)
plt.plot(x2, y2, '*g', ms=5)
plt.plot(0.5 * x1 + 0.5 * x2, 0.5 * y1 + 0.5 * y2, 'or', ms=5)
plt.plot(0.5 * x1 + 0.5 * x2, (0.5 * x1 + 0.5 * x2)**0.5, 'om', ms=5)
plt.plot(W, straight(x1, y1, x2, y2, W))
plt.grid()
plt.legend(loc='best')
plt.xlabel('Wealth ($W$)')
plt.ylabel('Utility ($U$)')
```

```python
0.5 * 0.1 + 0.5 * 1.0
```

    0.55

```python
0.5 * y1 + 0.5 * y2
```

    0.658113883008419

### ¿Cómo medimos el grado de aversión al riesgo de un individuo?

¿Saben cuál es su coeficiente de aversión al riesgo? Podemos estimarlo.

Suponga que se puede participar en la siguiente lotería:
- usted puede ganar $\$1000$ con $50\%$ de probabilidad, o
- puede ganar $\$500$ con $50\%$ de probabilidad.

Es decir, de entrada usted tendrá $\$500$ seguros pero también tiene la posibilidad de ganar $\$1000$.

¿Cuánto estarías dispuesto a pagar por esta oportunidad?

Bien, podemos relacionar tu respuesta con tu coeficiente de aversión al riesgo.

| Coeficiente de aversión al riesgo | Cantidad que pagarías |
| --------------------------------- | --------------------- |
| 0                                 | 750                   |
| 0.5                               | 729                   |
| 1                                 | 707                   |
| 2                                 | 667                   |
| 3                                 | 632                   |
| 4                                 | 606                   |
| 5                                 | 586                   |
| 10                                | 540                   |
| 15                                | 525                   |
| 20                                | 519                   |
| 50                                | 507                   |

La mayoría de la gente está dispuesta a pagar entre $\$540$ (10) y $\$707$ (1). Es muy raro encontrar coeficientes de aversión al riesgo menores a 1. Esto está soportado por una gran cantidad de encuestas.

- En el mundo financiero, los consultores financieros utilizan cuestionarios para medir el coeficiente de aversión al riesgo.

**Ejemplo.** Describir en términos de aversión al riesgo las siguientes funciones de utilidad que dibujaré en el tablero.
___

# Anuncios

## 1. Quiz la siguiente clase (pendientes del chat).
## 2. Tarea 4 entrega 2 para hoy, viernes 13 de marzo.
## 3. Tarea 5 entrega 2 para martes 17 de marzo.

Created with Jupyter by Esteban Jiménez Rodríguez.