```python
%matplotlib inline
import control
from control.matlab import *
import numpy as np
import matplotlib.pyplot as plt

def pole_plot(poles, title='Pole Map'):
    plt.title(title)
    plt.scatter(np.real(poles), np.imag(poles), s=50, marker='x')

    plt.axhline(y=0, color='black');
    plt.axvline(x=0, color='black');
    plt.xlabel('Re');
    plt.ylabel('Im');
```

# Tune Those Gains!

State space control requires that you fill in up to three gain matrices (K, L  & I), each potentially containing a number of elements. Given the heurism that goes into selecting PID gains (of which there are only three) tuning a state space controller can seem a bit daunting. Fortunately, the state space control framework includes a formal way to calculate gains to arrive at what is called a Linear Quadratic Regulator as well as a Linear Quadratic Estimator.

The goal of this notebook will be to walk you through the steps necessary to formulate the gains for an LQR and LQE. Once we've arrived at some acceptable gains, you can cut and paste them directly into your arduino sketch and start controlling some cool, complex dynamic systems with hopefully less guesswork and gain tweaking than if you were use a PID controller.

We'll be working from the model for the cart pole system from the examples folder. This system has multiple outputs and is inherently unstable so it's a nice way of showing the power of state space control. Having said that, **this analysis can apply to any state space model**, so to adapt it your system, just modify the system matricies ($A,B,C,D$) and the and cost function weighting matrices ($ Q_{ctrl}, R_{ctrl}, Q_{est}, R_{est} $ ) and rerun the notebook.

## Cart Pole

You can find the details on the system modelling for the inverted pendulum [here](http://ctms.engin.umich.edu/CTMS/index.php?example=InvertedPendulum&section=SystemModeling) but by way of a quick introduction, the physical system consists of a pendulum mounted on top of a moving cart shown below. The cart is fitted with two sensors that measure the angle of the stick and the displacement of the cart. The cart also has an actuator to apply a horizontal force on the cart to drive it forwards and backwards. The aim of the controller is to manipulate the force on the cart in order to balance the stick upright.

The state for this system is defined as:

\begin{equation}
\mathbf{x} = [ cart \;displacement, \; cart \;velocity, \; stick \;angle, \; stick \;angular\; velocity ]^T
\end{equation}

and the state space model that describes this system is as follows:

```python
A = [[0.0,   1.0,  0.0,   0.0 ],
     [0.0,  -0.18, 2.68,  0.0 ],
     [0.00,  0.00, 0.00,  1.00],
     [0.00, -0.45, 31.21, 0.00]]

B = [[0.00],
     [1.82],
     [0.00],
     [4.55]]

C = [[1, 0, 0, 0],[0,0,1,0]]

D = [[0],[0]]
```

## Take a look at the Open Loop System Poles

To get an idea of how this system behaves before we add any feedback control we can look at the poles of the open loop (uncontrolled) system. Poles are the roots of a characteristic equation derived from the system model. They're often complex numbers (having a real and an imaginary component) and are useful in understanding how the output of a system will respond to changes in its input.

There's quite a bit of interesting information that can be gleaned from looking at system poles but for now we'll focus on stability; which is determined by the pole with the largest real component (the x-axis in the plot below). If a system has any poles with positive real components then that system will be inherently unstable (i.e some or all of the state will shoot off to infinity if the system is left to its own devices).

The inverted pendulum has a pole at Re(+5.56) which makes sense when you consider that if the stick is stood up on its end and let go it'd fall over (obviously it'd stop short of infinity when it hits the side of the cart, but this isn't taken into account by the model). Using a feedback controller we'll move this pole over to the left of the imaginary axis in the plot below and in so doing, stabilise the system.

```python
plant = ss(A, B, C, D)
open_loop_poles = pole(plant)

print '\nThe poles of the open loop system are:\n'
print open_loop_poles

pole_plot(open_loop_poles, 'Open Loop Poles')
```

# Design a Control Law

With a model defined, we can get started on calculating the gain matrix K (the control law) which determines the control input necessary to regulate the system state to $ \boldsymbol{0} $ (all zeros, you might want to control it to other set points but to do so just requires offsetting the control law which can be calculated on the arduino).

## Check for Controllability

For it to be possible to control a system defined by a given state space model, that model needs to be controllable. Being controllable simply means that the available set of control inputs are capable of driving the entire state to a desired set point. If there's a part of the system that is totally decoupled from the actuators that are manipulated by the controller then a system won't be controllable.

A system is controllable if the rank of the controllability matrix is the same as the number of states in the system model.

```python
controllability = ctrb(A, B)

print 'The controllability matrix is:\n'
print controllability

if np.linalg.matrix_rank(controllability) == np.array(B).shape[0]:
    print '\nThe system is controllable!'
else:
    print '\nThe system is not controllable, double-check your modelling and that you entered the system matrices correctly'
```

    The controllability matrix is:

    [[   0.            1.82         -0.3276       12.252968  ]
     [   1.82         -0.3276       12.252968     -4.40045424]
     [   0.            4.55         -0.819       142.15292   ]
     [   4.55         -0.819       142.15292     -31.0748256 ]]

    The system is controllable!

## Fill out the Quadratic Cost Function

Assuming the system is controllable, we can get started on calculating the control gains. The approach we take here is to calculate a Linear Quadratic Regulator. An LQR is basically a control law (K matrix) that minimises the quadratic cost function:

\begin{equation}
J = \int_0^\infty (\boldsymbol{x}' Q \boldsymbol{x} + \boldsymbol{u}' R \boldsymbol{u})\; dt
\end{equation}

The best way to think about this cost function is to realise that whenever we switch on the state space controller, it'll expend some amount of control effort to bring the state to $ \boldsymbol{0} $ (all zeros). Ideally it'll do that as quickly and with as little overshoot as possible. We can represent that with the expression $ \int_0^\infty \boldsymbol{x}' \;\boldsymbol{x} \; dt $. Similarly it's probably a good idea to keep the control effort to a minimum such that the controller is energy efficient and doesn't damage the system with overly aggressive control inputs. This total control effort can be represented with $ \int_0^\infty \boldsymbol{u}' \;\boldsymbol{u} \; dt $.

Inevitably, there'll be some parts of the state and some control inputs that we care about minimising more than others. To reflect that in the cost function we specify two matrices; $ Q $ and $ R $

$ Q_{ctrl} \in \mathbf{R}^{X \;\times\; X} $ is the state weight matrix; the elements on its diagonal represent how important it is to tighly control the corresponding state element (as in Q[0,0] corresponds to x[0]).

$ R_{ctrl} \in \mathbf{R}^{U \;\times\; U} $ is the input weight matrix; the elements on its diagonal represent how important it is to minimise the use of the corresponding control input (as in R[0,0] corresponds to u[0]).

```python
Q_ctrl = [[5000, 0, 0,   0],
          [0,    0, 0,   0],
          [0,    0, 100, 0],
          [0,    0, 0,   0]]

R_ctrl = [1]
```

## Calculate the Gain Matrix for a Linear Quadratic Regulator

With a cost function defined, the cell below will calculate the gain matrix K for an LQR. Bear in mind it usually takes a bit of tweaking of the cost function to arrive at a good K matrix. Also note that it's the relative value of each weight that's important, not their absolute values. You can multiply both $ Q_{ctrl} $ and $ R_{ctrl} $ by 1e20 and you'll still wind up with the same gains.

To guide your tweaking it's helpful to see the effect that different weightings have on the closed loop system poles. Remember that the further the dominant pole (largest real component) is to the left of the Im axis, the more stable your system will be. That said, don't get carried away and set ridiculously high gains; your actual system might not behave in exactly the same way as the model and an overly aggressive controller might just end up destabilising the system under realistic conditions.

```python
K, _, _ = lqr(A, B, Q_ctrl, R_ctrl)

print 'The control gain is:\n'
print 'K = ', K

plant_w_full_state_feedback = ss(A,
                                 B,
                                 np.identity(plant.states),
                                 np.zeros([plant.states, plant.inputs]))

controller = ss(np.zeros([plant.states, plant.states]),
                np.zeros([plant.states, plant.states]),
                np.zeros([plant.inputs, plant.states]),
                K)

closed_loop_system = feedback(plant_w_full_state_feedback, controller)
closed_loop_poles = pole(closed_loop_system)

print '\nThe poles of the closed loop system are:\n'
print closed_loop_poles

pole_plot(closed_loop_poles, 'Closed Loop Poles')
```

# Design an Estimator

If you're lucky enough to be able to directly observe the system's entire state (in which case the $ C $ matrix will be an identity matrix) then you're done!

This obviously isn't the case for the cart pole since given our sensors we aren't able to directly observe the cart velocity or the stick angular velocity (we can differentiate the sensor readings ofcourse, but doing so is a bad idea if the sensors are even a little bit noisy). Because of this, we'll need to introduce an estimator into the feedback controller to reconstruct those parts of the state based on our sensor readings $ \mathbf{y} $.

There's a nice duality between the estimator and the controller so the basic approach we take to calculate the estimator gain ($ L $ matrix) are very similar those for the control law above.

## Check for Observability

Observability tells us whether the sensors we've attached to our system (as defined by the C matrix) are sufficient to derive an estimate of the state to feed into our control law. If for example, a part of the state was completely decoupled from all of the sensor measurements we take, then the system won't be observable and it'll be impossible to estimate and ultimately, to control.

Similar to controllability, a system is observable if the rank of the observability matrix is the same as the number of states in the model.

```python
observability = obsv(A, C)

print 'The observability matrix is:\n'
print observability

if np.linalg.matrix_rank(observability) == plant.states:
    print '\nThe system is observable!'
else:
    print '\nThe system is not observable, double-check your modelling and that you entered the matrices correctly'
```

    The observability matrix is:

    [[  1.       0.       0.       0.    ]
     [  0.       0.       1.       0.    ]
     [  0.       1.       0.       0.    ]
     [  0.       0.       0.       1.    ]
     [  0.      -0.18     2.68     0.    ]
     [  0.      -0.45    31.21     0.    ]
     [  0.       0.0324  -0.4824   2.68  ]
     [  0.       0.081   -1.206   31.21  ]]

    The system is observable!

## Fill out the Noise Covariance Matrices

To calculate the estimator gain L, we can use the same algorithm as that used to calculate the control law. Again we define two matrices, $ Q $ and $ R $ however in this case their interpretations are slightly different.

$ Q_{est} \in \mathbf{R}^{X \;\times\; X} $ is referred to as the process noise covariance, it represents the accuracy of the state space model in being able to predict the next state based on the last state and the control input. It's assumed that the actual system is subject to some unknown noise which throws out the estimate of the state and in cases where that noise is very high, it's best to rely more heavily on the sensor readings.

$ R_{est}  \in \mathbf{R}^{Y \;\times\; Y} $ is referred to as the sensor noise covariance, it represents the accuracy of the sensor readings in being able to observe the state. Here again, it's assumed that the actual sensors are subject to some unknown noise which throws out their measurements. In cases where this noise is very high, it's best to be less reliant on sensor readings.

```python
Q_est = [[100, 0,    0,    0   ],
         [0,    1000, 0,    0   ],
         [0,    0,    100, 0   ],
         [0,    0,    0,    10000]]

R_est = [[1,0],[0,1]]
```

## Calculate the Gain Matrix for a Linear Quadratic Estimator (aka Kalman Filter)

Ideally, the estimator's covariance matrices can be calculated empirically using data collected from the system's actual sensors and its model. Doing so is a bit outside of the scope of this notebook, so instead  we can just tweak the noise values to come up with an estimator that converges on the actual state with a reasonable settling time based on the closed loop poles.

```python
L, _, _ = lqr(np.array(A).T, np.array(C).T, Q_est, R_est)
L = L.T

print 'The estimator gain is:\n'
print 'L = ', L

controller_w_estimator = ss(A - np.matmul(B , K) - np.matmul(L , C),
                            L,
                            K,
                            np.zeros([plant.inputs, plant.outputs]))

closed_loop_system_w_estimator = feedback(plant, controller_w_estimator)
closed_loop_estimator_poles = pole(closed_loop_system_w_estimator)

print '\nThe poles of the closed loop system are:\n'
print closed_loop_estimator_poles

pole_plot(closed_loop_estimator_poles, 'Closed Loop Poles with Estimation')
```

# And You're Done!

Congratulations! You've tuned an LQR and an LQE to suit your system model and you can now cut and paste the gains into your arduino sketch.

## Coming soon, Integral Gain selection!


```
import petsc4py
import sys

petsc4py.init(sys.argv)

from petsc4py import PETSc
import mshr
from dolfin import *
import sympy as sy
import numpy as np
import ExactSol
import MatrixOperations as MO
import CheckPetsc4py as CP

n = 4
# domain = mshr.Rectangle(Point(-1., -1.), Point(1., 1.)) - mshr.Rectangle(Point(0., -1.), Point(1., 0.) )
# mesh = mshr.generate_mesh(domain, n)
mesh = RectangleMesh(-1,-1,1,1,n,n, 'crossed')
cell_f = CellFunction('size_t', mesh, 0)
for cell in cells(mesh):
    v = cell.get_vertex_coordinates()
    y = v[np.arange(0,6,2)]
    x = v[np.arange(1,6,2)]
    xone = np.ones(3)
    xone[x > 0] = 0
    yone = np.ones(3)
    yone[y < 0] = 0
    if np.sum(xone)+ np.sum(yone)>5.5:
        cell_f[cell] = 1
mesh = SubMesh(mesh, cell_f, 0)

```

```
plot(mesh)
```

    <dolfin.cpp.io.VTKPlotter; proxy of <Swig Object of type 'std::shared_ptr< dolfin::VTKPlotter > *' at 0x113e21e70> >

```

```


ST0256 - Análisis Numérico

Capítulo 4: Interpolación Numérica

2021/01

MEDELLÍN - COLOMBIA

 Text provided under a Creative Commons Attribution license, CC-BY. All code is made available under the FSF-approved MIT license.(c) Carlos Alberto Alvarez Henao

***

***Docente:*** Carlos Alberto Álvarez Henao, I.C. D.Sc.

***e-mail:*** calvar52@eafit.edu.co

***skype:*** carlos.alberto.alvarez.henao

***Herramienta:*** [Jupyter notebook](http://jupyter.org/)

***Kernel:*** Python 3.8

***

Tabla de Contenidos

1  Introducción

1.1  Propósitos de la interpolación

1.2  Diferencias entre Interpolación, Aproximación y Ajuste de curvas

1.3  Interpolación vs Aproximacion

1.4  Interpolación vs Ajuste de curvas

1.5  Elección de la función de interpolación

2  Interpolación Polinomial

2.1  Introducción

2.2  Interpolación Lineal

2.2.1  Ejemplo interpolación lineal

2.2.2  Visualización computacional

2.2.3  Mejoras al esquema de interpolación lineal

2.3  Interpolación cuadrática

2.3.1  Ejemplo interpolación cuadrática

2.3.2  Implementación computacional

2.4  Polinomio de diferencias divididas de Newton

2.4.1  Implementación computacional

2.5  Análisis de Error para la interpolación polinomial tipo Newton

2.6  Polinomios de Interpolación de Lagrange

2.6.1  Introducción

2.6.2  Polinomio de Interpolación de Lagrange de primer grado

2.6.3  Polinomio de Interpolación de Lagrange de segundo grado

2.6.4  Ejemplo polinomio de interpolación de Lagrange

2.6.5  Implementación computacional

2.6.6  Funciones de base

2.6.7  Dificultades de los polinomios de Lagrange

3  Interpolación mediante trazadores

3.1  Introducción

3.2  Trazadores lineales

3.3  Trazadores cuadráticos

3.4  Trazadores cúbicos

3.4.1  Alternativa I:

3.4.2  Alternativa II:

3.4.3  Alternativa III:

3.4.4  Alternativa IV:

3.5  Ejemplo aplicación

3.5.1  Trazadores cuadráticos

3.5.2  Trazadores cúbicos

## Introducción

La información (datos) resultante de la medición de un evento ya sea natural o social viene dada en forma discreta o tabular, es decir, se expresa como un conjunto de pares ordenados $(x_i,y_i)$. Por ejemplo, los datos obtenidos de los censos poblacionales realizados en Colombia desde 1985 según el [DANE](https://www.dane.gov.co/) son:

|Año|Población*|
|:----:|:----:|
|1985|30802|
|1990|34130|
|1995|37472|
|2000|40296|
|2005|42889|
|2010|45510|
|2015|48203|

(\* en miles de habitantes)

***Nota:*** Ejemplo tomado de las notas de clase del curso Simulación Computacional de la Universidad EAFIT y es autoría  del profesor Nicolás Guarín.

```python
import numpy as np
import matplotlib.pyplot as plt
import scipy.interpolate
from scipy.optimize import curve_fit

```

```python
start = 1985.
stop = 2015.
step = int((stop - start) / 5) + 1

x = np.linspace(start, stop, step)
y = [30802, 34130, 37472, 40296, 42889, 45510, 48203]

plt.plot(x,y, 'o')
plt.grid(True)
```

Si quisieramos responder a la pregunta: ¿Cuál era la población de Colombia en el 2012?

```python
fig, ax = plt.subplots()

ax.plot(x, y, 'o')
ax.vlines(x=2012, ymin=30000.0, ymax=48203.0, color='r')
ax.hlines(y=46000.0, xmin=1985.0, xmax=2012.0, color='y')
ax.hlines(y=47000.0, xmin=1985.0, xmax=2012.0, color='b')
ax.hlines(y=48000.0, xmin=1985.0, xmax=2012.0, color='g')
plt.grid(True)
plt.show()
```

¿cuál de los valores mostrados arriba es el más correcto? Podemos plantear varias ideas para determinar dicho valor:

- Considerando que la función es constante entre los valores

```python
fig, ax = plt.subplots()

ax.plot(x, y, 'o')

ax.hlines(y=y[0], xmin=x[0], xmax=x[0] + 2, color='b')
ax.vlines(x=x[0] + 2, ymin=y[0], ymax=y[1], color='b')

for i in range(1, len(x)-1):
    ax.hlines(y=y[i], xmin=x[i]-3, xmax= x[i] + 2, color='b')
    ax.vlines(x=x[i]+2, ymin=y[i], ymax=y[i+1], color='b')

ax.hlines(y=y[-1], xmin=x[-1]-3, xmax=x[-1]+2, color='b')

ax.vlines(x = 2012, ymin = 30000.0, ymax = y[-1], color = 'r')
ax.hlines(y = y[-1], xmin = 1985.0, xmax = 2012.0, color = 'r')

plt.grid(True)
plt.show()
```

- Asumiendo que la función es lineal entre valores

```python
fig, ax = plt.subplots()

ax.plot(x, y, 'o--')
ax.vlines(x = 2012, ymin = 30000.0, ymax = 46700, color = 'r')
ax.hlines(y = 46700, xmin = 1985.0, xmax = 2012.0, color = 'r')

plt.grid(True)
plt.show()
```

- Determinando un polinomio que pase por cada uno de los puntos.

```python
fig, ax = plt.subplots()

ax.plot(x, y, 'o')

t = np.linspace(0, 1, len(x)) # parameter t to parametrize x and y
pxLagrange = scipy.interpolate.lagrange(t, x) # X(T)
pyLagrange = scipy.interpolate.lagrange(t, y) # Y(T)
n = 100
ts = np.linspace(t[0],t[-1],n)
xLagrange = pxLagrange(ts) # lagrange x coordinates
yLagrange = pyLagrange(ts) # lagrange y coordinates
ax.plot(xLagrange, yLagrange,'b-')

ax.vlines(x=2012, ymin=30000.0, ymax=46700, color='r')
ax.hlines(y=46700, xmin=1985.0, xmax=2012.0, color='r')

ax.grid(True)
plt.show()
```

- ajustando la curva que mejor se aproxime a cada uno de los datos. En este ejemplo haremos un ajuste lineal, pero no es la única forma de hacerlo.

```python
# define the true objective function
def objective(x, a, b):
    return a * x + b

# curve fit
popt, _ = curve_fit(objective, x, y)

# summarize the parameter values
a, b = popt
print('y = %.5f * x + %.5f' % (a, b))

fig, ax = plt.subplots()

# plot input vs output
ax.scatter(x, y)

# define a sequence of inputs between the smallest and largest known inputs
x_line = np.arange(min(x), max(x), 1)

# calculate the output for the range
y_line = objective(x_line, a, b)

# create a line plot for the mapping function
ax.plot(x_line, y_line, '--', color='b')

yfit = a * 2012 + b

ax.vlines(x=2012, ymin=30000.0, ymax=yfit, color='r')
ax.hlines(y=yfit, xmin=1985.0, xmax=2012.0, color='r')

plt.grid(True)
plt.show()
```

En este curso nos centraremos en los esquemas de interpolación, por lo que los esquemas de ajuste no se tratarán.

[Volver a la Tabla de Contenido](#TOC)

### Propósitos de la interpolación

Los problemas de interpolación surgen de muchas fuentes diferentes y pueden tener muchos propósitos diferentes. Algunos de estos incluyen:

- Trazar una curva suave a través de puntos de datos discretos

- Evaluación rápida y sencilla de una función matemática

- Reemplazar una función difícil por una fácil

- Diferenciar o integrar datos tabulares

[Volver a la Tabla de Contenido](#TOC)

### Diferencias entre Interpolación, Aproximación y Ajuste de curvas

Las técnicas para resolver el problema de determinar un valor intermedio entre dos valores conocidos se pueden enmarcar en:

- [Interpolación](https://en.wikipedia.org/wiki/Interpolation)

- [Aproximación](https://en.wikipedia.org/wiki/Approximation_theory)

- [Ajuste de curvas](https://en.wikipedia.org/wiki/Curve_fitting)

A continuación vamos a describir brevemente las diferencias entre ellas.

[Volver a la Tabla de Contenido](#TOC)

### Interpolación vs Aproximacion

En interpolación, se ajustan todos los puntos de datos de forma exacta mientras que la aproximación, como su nombre indica, solo se aproxima.

Cuando se trata de la idoneidad, la interpolación es apropiada para suavizar esos datos ruidosos y no es apropiada cuando los puntos de datos están sujetos a errores experimentales u otras fuentes de error significativo. Tener un gran conjunto de puntos de datos también puede sobrecargar la interpolación. Por otro lado, la aproximación es principalmente apropiada para el diseño de rutinas de biblioteca para calcular funciones especiales. Esto se debe a la naturaleza de estas funciones: se considera que los valores exactos no son esenciales y, hasta cierto punto, ineficaces cuando los valores aproximados funcionan.

[Volver a la Tabla de Contenido](#TOC)

### Interpolación vs Ajuste de curvas

En el ajuste de curvas, no ajustamos todos nuestros puntos de datos. Por eso tenemos el concepto de residuos. En la interpolación, se obliga a la función a ajustarse a todos los puntos de datos. Ver la referencia del [Cuarteto de ascombe](https://es.wikipedia.org/wiki/Cuarteto_de_Anscombe) como un contra ejemplo de los inconvenientes en los ajustes de curvas.

Ahora que sabemos de qué categoría estamos hablando, reduzcamos a las familias de funciones utilizadas para la interpolación.

[Volver a la Tabla de Contenido](#TOC)

### Elección de la función de interpolación

Es importante darse cuenta de que existe cierta arbitrariedad en la mayoría de los problemas de interpolación. Hay arbitrariamente muchas funciones que interpolan un conjunto dado de datos. Simplemente requiriendo que alguna función matemática se ajuste a los puntos de datos deje exactamente abiertos tales
preguntas como:

- ¿Qué forma debe tener la función? Puede haber consideraciones matemáticas o físicas relevantes que sugieran una forma particular de interpolante.

- ¿Cómo debería comportarse la función entre puntos de datos?

- ¿Debería la función heredar propiedades de los datos, como monotonicidad, convexidad o periodicidad?

- Si se grafican la función y los datos, ¿los resultados deberían ser agradables a la vista?

- ¿Estamos interesados principalmente en los valores de los parámetros que definen la función de interpolación, o simplemente en evaluar la función en varios puntos para graficar u otros propósitos?

La elección de la función de interpolación depende de las respuestas a estas preguntas, así como de los datos que se deben ajustar y generalmente se basa en:

- Qué tan fácil es trabajar con la función (determinar sus parámetros a partir de los datos, evaluar la función en un punto dado, diferenciar o integrar la función, etc.)

- Qué tan bien las propiedades de la función coinciden con las propiedades de los datos a ser t (suavidad, monotonicidad, convexidad, periodicidad, etc.)

Algunas familias de funciones que se utilizan comúnmente para la interpolación incluyen:

- [Polinomios](https://en.wikipedia.org/wiki/Polynomial_interpolation)

- [Interpolación a trazos](https://en.wikipedia.org/wiki/Spline_interpolation)

- Funciones trigonométricas

- Exponenciales

- Funciones racionales

En este capítulo nos centraremos en la interpolación por polinomios e interpolación a trazos.

[Volver a la Tabla de Contenido](#TOC)

## Interpolación Polinomial

### Introducción

La interpolación polinomial es el tipo de interpolación más simple y común. Una de sus características es que siempre hay un polinomio único de grado como máximo $n-1$ que pasa por $n$ puntos de datos.
Hay muchas formas de calcular o representar un polinomio, pero se reducen a la misma función matemática. Algunos de los métodos son la base monomial, la base de [Lagrange](https://en.wikipedia.org/wiki/Lagrange_polynomial) y la base de [Newton](https://en.wikipedia.org/wiki/Newton_polynomial). Como puede observar, reciben el nombre de su base.

***Inconvenientes:***

- ***Polinomio de alto grado:*** una elección adecuada de funciones de base y puntos de interpolación puede mitigar algunas de las dificultades asociadas con polinomio de alto grado.

- ***Sobreajuste ([overfitting](https://en.wikipedia.org/wiki/Overfitting)):*** ajuste de un solo polinomio a una gran cantidad de puntos de datos, lo que probablemente produciría un comportamiento de oscilación insatisfactorio en el interpolante.

La fórmula general de un polinomio de $n$-ésimo orden es:

\begin{equation*}
f_n(x) = a_0 + a_1x + a_2x^2 +…+  a_nx^n
\label{eq:Ec4_1} \tag{4.1}
\end{equation*}

El polinomio de interpolación dado por la ecuación $\eqref{eq:Ec4_1}$ consiste en determinar el único polinomio de n-ésimo orden que se ajusta a los $n+1$ puntos dados. Este polinomio proporciona una fórmula para calcular los valores intermedios.

[Volver a la Tabla de Contenido](#TOC)

### Interpolación Lineal

El método más simple de interpolación es conectar dos puntos mediante una línea recta.

 Fuente: Chapra, S., Canale, R. Métodos Numéricos para ingenieros, 5a Ed. Mc. Graw Hill. 2007

De la figura se tiene:

\begin{equation*}
\frac{f_1(x)-f(x_0)}{x-x_0}=\frac{f(x_1)-f(x_0)}{x_1-x_0}
\label{eq:Ec4_2} \tag{4.2}
\end{equation*}

reordenando,

\begin{equation*}
f_1(x)=f(x_0)+\frac{f(x_1)-f(x_0)}{x_1-x_0}(x-x_0)
\label{eq:Ec4_3} \tag{4.3}
\end{equation*}

[Volver a la Tabla de Contenido](#TOC)

#### Ejemplo interpolación lineal

- Estime el valor de $Ln(2)$ empleando la interpolación lineal, entre $x_0=0$ y $x_1=6$

Evaluando el valor del logaritmo en cada uno de los dos puntos, $Ln(1)=0$ y $Ln(6)=1.791759$

$$f_1(2)=0+\frac{1.791759-0}{6-1}(2-1)=0.3583519$$

El valor exacto es $Ln(2)=0.693147$, que representa un error relativo porcentual de

$$Er(\%)=\frac{|0.693147-0.3583519|}{0.693147}=48.3\%$$

Si se disminuye el valor del intervalo a evaluar, por ejemplo en $x_1=4$, se llega a

$$f_1(2)=0+\frac{1.386294-0}{4-1}(2-1)=0.462098$$

obteniendo un error relativo porcentual del $33.3\%$.

[Volver a la Tabla de Contenido](#TOC)

#### Visualización computacional

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

n = 20

fig, ax = plt.subplots()

x = np.linspace(1, 6, n)
y = np.log(x)

x1 = [x[0], x[-1]]
y1 = [np.log(x[0]), np.log(x[-1])]

x2 = [x[0], 4]
y2 = [np.log(x[0]), np.log(4)]

ax.plot(x,y, '-', x1, y1, 'o-', x2,y2, 'o-')
ax.vlines(x = 2, ymin = 0.0, ymax = np.log(2), color = 'r', linestyles='dashed')

plt.grid(True)

```

El error en la interpolación lineal resulta de aproximar una curva con una línea recta.

[Volver a la Tabla de Contenido](#TOC)

#### Mejoras al esquema de interpolación lineal

- Disminuir el tamaño del intervalo.

- Introducir alguna curvatura en la línea que conecta los puntos.

[Volver a la Tabla de Contenido](#TOC)

### Interpolación cuadrática

Si tres (3) puntos de los datos están disponibles, esto puede realizarse con un polinomio de segundo grado (parábola).

 Fuente: Chapra, S., Canale, R. Métodos Numéricos para ingenieros, 5a Ed. Mc. Graw Hill. 2007

La forma general de una ecuación cuadrática puede expresarse de la siguiente forma:

\begin{equation*}
f_2(x) = b_0 + b_1(x–x_0) + b_2(x–x_0)(x–x_1)
\label{eq:Ec4_4} \tag{4.4}
\end{equation*}

Debemos determinar los valores de los coeficientes $b_i$.

- Para $b_0$, en la ecuación $\eqref{eq:Ec4_4}$, con $x = x_0$:

\begin{equation*}
b_0 = f(x_0)
\label{eq:Ec4_5} \tag{4.5}
\end{equation*}

- Para $b_1$, sustituyendo la ecuación $\eqref{eq:Ec4_5}$ en la la ecuación $\eqref{eq:Ec4_4}$, y evaluando en $x = x_1$:

\begin{equation*}
b_1 = \frac{f(x_1)-f(x_0)}{(x_1 - x_0)}
\label{eq:Ec4_6} \tag{4.6}
\end{equation*}

- Para $b_2$, las ecuaciones $\eqref{eq:Ec4_5}$ y $\eqref{eq:Ec4_6}$ pueden sustituirse en la ecuación $\eqref{eq:Ec4_4}$, evaluada en $x_2$

\begin{equation*}
b_2=\frac{\frac{f(x_2)-f(x_1)}{(x_2-x_1)}-\frac{f(x_1)-f(x_0)}{(x_1-x_0)}}{(x_2 - x_0)}
\label{eq:Ec4_7} \tag{4.7}
\end{equation*}

[Volver a la Tabla de Contenido](#TOC)

#### Ejemplo interpolación cuadrática

Continuando con el ejemplo anterior, se van a considerar los siguientes puntos:

$$x_0=1 \hspace{1cm} f(x_0)=0.000000$$
$$x_1=4 \hspace{1cm} f(x_1)=1.386294$$
$$x_2=6 \hspace{1cm} f(x_2)=1.791759$$

De las ecuaciones anteriores,

$$b_0=0$$

$$b_1=\frac{1.386294-0}{4-1}=0.4620981$$

$$b_2=\frac{\frac{1.791759-1.386294}{6-4}-0.4620981}{6-1}=-0.0518731$$

Sustituyendo estos valores en la ecuación cuadrática inicial, se llega a:

$$f_2(x)=0+0.4620981(x-1)-0.0518731(x-1)(x-4)$$

y evaluando en $x=2$, se llega a

$$f_2(2)=0.565844$$

que representa un error relativo porcentual del $18.4\%$

[Volver a la Tabla de Contenido](#TOC)

#### Implementación computacional

```python
def difdiv2o(x, y, xm):
    b0 = y[0]
    b1 = (y[1] - b0) / (x[1] - x[0])
    b2 = ((y[2] - y[1]) / (x[2] - x[1]) - b1) / (x[2] - x[0])

    return b0 + b1 * (xm - x[0]) + b2 * (xm - x[0]) * (xm - x[1])
```

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

n = 20

fig, ax = plt.subplots()

x = np.linspace(1, 6, n)
y = np.log(x)

x1 = [x[0], 4, x[-1]]
y1 = [np.log(x[0]), np.log(x1[1]), np.log(x[-1])]
ym = difdiv2o(x1,y1,2)

x1.insert(1, 2)
y1.insert(1,ym)

ax.plot(x, y, '-', x1, y1, 'o-')
ax.vlines(x = 2, ymin = 0.0, ymax = np.log(2), color = 'r', linestyles='dashed')

plt.grid(True)
```

### Polinomio de diferencias divididas de Newton

Lo anterior puede ser generalizado para ajustar un polinomio de $n$-ésimo orden a $n+1$ datos:

 Fuente: medium.com

\begin{equation*}
f_n(x) = b_0+b_1(x–x_0)+\ldots+b_n(x–x_0)(x–x_1)\ldots(x – x_{n-1})
\label{eq:Ec4_8} \tag{4.8}
\end{equation*}

De igual manera que para las interpolaciones lineal y cuadrática, se llega a:

\begin{equation*}
f_n(x)=f(x_0)+(x–x_0)f[x_1,x_0]+(x–x_0)(x–x_1)f[x_2,x-,x_0]+\ldots+(x–x_0)(x–x_1)\ldots(x–x_{n-1})f[x_n, x_{n-1},\ldots,x_2,x_1,x_0]
\label{eq:Ec4_9} \tag{4.9}
\end{equation*}

Conocido como *Polinomio de interpolación por [diferencias divididas de Newton](https://en.wikipedia.org/wiki/Divided_differences)*. Las evaluaciones de las funciones puestas entre paréntesis son diferencias divididas finitas.

- ***Primera diferencia dividida:***

\begin{equation*}
f[x_i, x_j]=\frac{f(x_i)-f(x_j)}{(x_i-x_j)}
\label{eq:Ec4_10} \tag{4.10}
\end{equation*}

- ***Segunda diferencia dividida:*** representa la diferencia de las dos primeras diferencias divididas

\begin{equation*}
f[x_i, x_j,x_k]=\frac{f[x_i,x_j]-f[x_j,x_k]}{(x_i-x_k)}
\label{eq:Ec4_11} \tag{4.11}
\end{equation*}

$$\vdots$$

- ***$n$-ésima diferencia dividida:*** representa la diferencia de las dos primeras diferencias divididas
\begin{equation*}
f[x_n, x_{n-1},\ldots, x_1,x_0]=\frac{f[x_n,x_{n-1},\ldots,x_1]-f[x_{n-1},x_{n-2},\ldots,x_0]}{(x_n-x_0)}
\label{eq:Ec4_12} \tag{4.12}
\end{equation*}

Este proceso recursivo lo podemos visualizar de la siguiente manera:

 Fuente: Wikimedia.org

[Volver a la Tabla de Contenido](#TOC)

#### Implementación computacional

En la página 513 del libro de Chapra y Canale, Figura 18.7, se tiene un algoritmo en Fortran para la implementación del código de Diferencias Divididas tipo Newton. Se invita al estudiante a que lo estudie y codifique en el lenguaje de preferencia.

```python
# Escriba aquí su código
```

[Volver a la Tabla de Contenido](#TOC)

### Análisis de Error para la interpolación polinomial tipo Newton

La ecuación $\eqref{eq:Ec4_9}$ es similar a la *serie de expansión de Taylor*. Se agregan términos en forma secuencial para capturar el comportamiento de alto orden de la función a analizar. Estos términos son diferencias divididas finitas y, así, representan aproximaciones de derivadas de orden mayor.

El error de truncamiento se expresa entonces como:

\begin{equation*}
R_n=\frac{f^{(n+1)}(\xi)}{(n+1)!} \left ( x_{i+1}-x_i\right )^{n+1}
\label{eq:Ec4_13} \tag{4.13}
\end{equation*}

Para una interpolación de n-ésimo orden, una relación análoga para el error es

\begin{equation*}
R_n=\frac{f^{(n+1)}(\xi)}{(n+1)!}(x-x_0)(x-x_1) \ldots (x-x_n)
\label{eq:Ec4_14} \tag{4.14}
\end{equation*}

Observe que en la ecuación [(4.14)](#Ec4_14), la función debe conocerse. Para resolver esta situación, una formulación alternativa es el uso de la diferencia dividida para aproximar la derivada $(n+1)$–ésima y que no requiere el conocimiento previo de la función.

\begin{equation*}
R_n=f_n[x_n,x_{n-1},x_{n-2},\ldots,x_2,x_1,x_0](x-x_0)(x-x_1) \ldots (x-x_n)
\label{eq:Ec4_15} \tag{4.15}
\end{equation*}

Debido a que la ecuación [(4.15)](#Ec4_15) contiene el término $f_n(x)$ no puede resolverse para estimar el error, pero, si se dispone de un dato adicional, la ecuación [(4.15)](#Ec4_15) puede usarse:

\begin{equation*}
R_n=f_n[x_{n+1}, x_n,x_{n-1},x_{n-2},\ldots,x_2,x_1,x_0](x-x_0)(x-x_1) \ldots (x-x_n)
\label{eq:Ec4_16} \tag{4.16}
\end{equation*}

[Volver a la Tabla de Contenido](#TOC)

### Polinomios de Interpolación de Lagrange

#### Introducción

El [polinomio de interpolación de Lagrange](https://en.wikipedia.org/wiki/Lagrange_polynomial) evita el cálculo de las diferencias divididas en el esquema de Newton. De una forma general, se representa como la [combinación lineal](https://en.wikipedia.org/wiki/Linear_combination):

\begin{equation*}
f_n(x)=\sum \limits_{i=0}^n L_i(x)f(x_i)
\label{eq:Ec4_17} \tag{4.17}
\end{equation*}

donde $L_i$ son las bases polinómicas de *[Lagrange](https://es.wikipedia.org/wiki/Joseph-Louis_Lagrange)* dadas por:

\begin{equation*}
L_i(x)=\prod_{\substack{j=0\\ j \ne i}}^n \frac{x-x_j}{x_i-x_j}
\label{eq:Ec4_18} \tag{4.18}
\end{equation*}

de $\eqref{eq:Ec4_18}$ se observa que todas las funciones $L_i$ son polinomios de grado $n$ que tienen la propiedad

\begin{equation*}
L_i(x_j)=\delta_{ij}, \quad \delta_{ij} = \left \{
\begin{aligned}
1, \quad i=j,\\
0, \quad i \ne j
\end{aligned}
\right.
\label{eq:Ec4_19} \tag{4.19}
\end{equation*}

donde $\delta_{is}$ es el [delta de Kronecker](https://en.wikipedia.org/wiki/Kronecker_delta).

[Volver a la Tabla de Contenido](#TOC)

#### Polinomio de Interpolación de Lagrange de primer grado

Tomando $n=1$ (lineal), se tiene

\begin{equation*}
f_1(x)=\frac{(x-x_1)}{(x_0-x_1)}f(x_0)+\frac{(x-x_0)}{(x_1-x_0)}f(x_1)
\label{eq:Ec4_19a} \tag{4.19a}
\end{equation*}

[Volver a la Tabla de Contenido](#TOC)

#### Polinomio de Interpolación de Lagrange de segundo grado

Tomando $n=2$ (cuadrático), se tiene

\begin{equation*}
f_2(x)=\frac{(x-x_1)(x-x_2)}{(x_0-x_1)(x_0-x_2)}f(x_0) + \frac{(x-x_0)(x-x_2)}{(x_1-x_0)(x_1-x_2)}f(x_1) + \frac{(x-x_0)(x-x_1)}{(x_2-x_0)(x_2-x_1)}f(x_2)
\label{eq:Ec4_20} \tag{4.20}
\end{equation*}

[Volver a la Tabla de Contenido](#TOC)

#### Ejemplo polinomio de interpolación de Lagrange

retomando el ejemplo que se ha venido trabajando anteriormente,

$$x_0=1 \hspace{1cm} f(x_0)=0.000000$$
$$x_1=4 \hspace{1cm} f(x_1)=1.386294$$
$$x_2=6 \hspace{1cm} f(x_2)=1.791759$$

- ***Polinomio de primer grado:***

$$f_1(2)=\frac{2-4}{1-4}(0)+\frac{2-1}{4-1}(1.386294) = 0.462098$$

- ***Polinomio de segundo grado:***

$$f_2(2)=\frac{(2-4)(2-6)}{(1-4)(1-6)}(0)+\frac{(2-1)(2-6)}{(4-1)(4-6)}(1.386294)+\frac{(2-1)(2-4)}{(6-1)(6-4)}(1.791759) = 0.565844$$

- ***Nota:*** Realice una comparación con los resultados obtenidos con los correspondientes esquemas lineal y cuadrático en el esquema de [diferencias divididas de Newton](#DDN).

#### Implementación computacional

```python
def lagrange(x ,i , xm ):
    n = len(xm) - 1
    y = 1.0
    for j in range(n + 1):
        if i != j:
            y *= (x - xm[j]) / (xm[i] - xm[j])
    return y
```

```python
def interpolation(x, xm, ym):
    n = len(xm) - 1
    lagrpoly = np.array([lagrange(x, i, xm) for i in range(n + 1)])
    y = np.dot(ym, lagrpoly)
    return y
```

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

xm = np.array([1,4,6])
ym = np.log(xm)
#xm = np.array([1, 2, 3, 4, 5, 6])
#ym = np.array([-3, 0, -1, 2, 1, 4])
#ym = np.sin(xm)

xplot = np.linspace(-1., 6.0, 100)
yplot = interpolation(xplot, xm, ym)
plt.plot(xm, ym, '--', xplot, yplot, '-')
plt.grid('True')
```

[Volver a la Tabla de Contenido](#TOC)

#### Funciones de base

Para entender un poco más cómo es que trabaja la interpolación entre cada uno de los puntos, recordemos que dichos polinomios interpolantes de Lagrange deben cumplir con la propiedad del delta de Kronecker. Para visualizar esto, se implementará la descripción propuesta en el dcumento [Interpolación de Lagrange 1D](https://github.com/AppliedMechanics-EAFIT/modelacion_computacional/blob/master/notebooks/02a_interpolacion.ipynb) realizado por los profesores *Juan David Gómez Cataño* y *Nicolás Guarín Zapata* para el curso de Modelación Computacional en el programa de Ingeniería Civil de la Universidad EAFIT. Todo el crédito para ellos.

```python
# llamado a las biblitecas numéricas, de visualización y simbólicas
import numpy as np
import matplotlib.pyplot as plt
from scipy import interpolate
import sympy as sym
sym.init_printing()
```

```python
def lagrange_poly(x, order, i, xi=None):
    if xi == None:
        xi = sym.symbols('x:%d'%(order+1))
    index = list(range(order+1))
    index.pop(i)
    return sym.prod([(x - xi[j])/(xi[i] - xi[j]) for j in index])
```

```python
fun = lambda x: x**3 + 4.0*x**2 - 10.0
```

```python
npts = 200
x_pts = np.linspace(-1, 1, npts)
```

```python
pts = np.array([-1, 1, 0])
fd = fun(pts)
```

```python
plt.figure()
y_pts = fun(x_pts)
plt.plot(x_pts , y_pts)
plt.plot(pts, fd, 'ko')
```

```python
x = sym.symbols('x')
pol = []
pol.append(sym.simplify(lagrange_poly(x, 2, 0, [-1,1,0])))
pol.append(sym.simplify(lagrange_poly(x, 2, 1, [-1,1,0])))
pol.append(sym.simplify(lagrange_poly(x, 2, 2, [-1,1,0])))
pol
```

```python
plt.figure()
print(npts)
for k in range(3):

    for i in range(npts):
        yy[i] = pol[k].subs([(x, x_pts[i])])
        print(yy[i])
    plt.plot(x_pts, yy)
```

[Volver a la Tabla de Contenido](#TOC)

#### Dificultades de los polinomios de Lagrange

Los polinomios de interpolación de Lagrange presentan dificultades cuando se tienen polinomios de orden muy alto, agravado cuando se tienen puntos equidistantes o se presentan saltos (discontinuidades) en la solución.

A esta situación se le conoce como *[fenómeno de Runge](https://en.wikipedia.org/wiki/Runge%27s_phenomenon)*. Veamos el siguiente ejemplo:

- Dada la ecuación

$$f(x)=\frac{1}{1+25x^2}$$

si se interpola esta función utilizando nodos equidistantes $x_i \in [-1, 1]$ tal que

$$x_i=-1+(i-1)\frac{2}{n} \quad i \in \{1, 2, 3, \ldots, n, n+1\}$$

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

```python
n = 200
x = [-1 + 2 * (i - 1) / n for i in range(1, n + 2)]
y = [1 / (1+ 25 * i **2) for i in x]

plt.plot(x,y)
plt.grid(True)
```

Ahora vamos a emplear, como ejemplo, una serie de polinomios interpolantes para evaluar su comportamiento, desde orden 1 (2 puntos) hasta de orden 9 (10 puntos).

```python
# Polinomios interpolantes de orden 1-9 (2-10 puntos)

data = [2, 3, 4, 6, 10]

#fig = plt.figure(figsize=(16, 6), dpi= 50, facecolor='w', edgecolor='k')
#ax = fig.add_subplot(111)

plt.plot(x, y, '-', label = 'Exacta')
plt.title('Fenómeno de Runge')
xplot = np.linspace(-1., 1.0, 100)

for i in data:
    xRi = np.linspace(-1, 1, i)
    yRi = [1 / (1+ 25 * j **2) for j in xRi]

    yploti = interpolation(xplot, xRi, yRi)
    string = "P_orden " + str(i-1)
    plt.plot(xplot, yploti, '-', label = string)

#    idx = np.argwhere(np.diff(np.sign(f - g))).flatten()
#    plt.plot(x[idx], f[idx], 'ro')

    plt.legend()

plt.plot(x, y, '-', label = 'Exacta')
plt.title('Fenómeno de Runge')
plt.grid('True')

```

Se observa que a medida que se aumenta el orden del polinomio, intentando obtener un mejor ajuste, se presentan oscilaciones en los puntos extremos.

***Como actividad complementaria, se invita al estudiante reproducir el fenómeno de Runge que se representa en la gráfica de la Figura 18.14 (p. 526) que se encuentra en el libro de Chapra y Canale, 5a Ed.***

[Volver a la Tabla de Contenido](#TOC)

## Interpolación mediante trazadores

### Introducción

En las secciones anteriores, se usaron polinomios de $n$-ésimo grado para interpolar entre $n+1$ puntos que se tenían como datos, por ejemplo, para $10$ puntos se puede obtener un polinomio exacto de noveno grado. Esta curva podría agrupar todas las curvas al menos hasta, e incluso, la novena derivada. No obstante, hay casos, como el que se acaba de observar, donde estas funciones llevan a resultados erróneos a causa de los errores de redondeo y los puntos lejanos (*fenómeno de Runge*).

Como alternativa para intentar mitigar esta situación se pueden implementar polinomios de menor grado en subconjuntos de los datos. Tales polinomios conectores se denominan trazadores o [splines](https://en.wikipedia.org/wiki/Spline_(mathematics)).

Supongamos que empleamos curvas de tercer grado para unir dos conjuntos de datos, cada una de esas funciones se pueden construir de tal forma que las conexiones entre ecuaciones cúbicas adyacentes resulten visualmente suaves. Podría parecer que la aproximación de tercer grado de los trazadores sería inferior a la expresión de noveno grado, entonces, por qué un trazador resulta preferible?

El concepto de trazador se originó en la técnica de dibujo que usa una cinta delgada y flexible (llamada spline, en inglés), para dibujar curvas suaves a través de un conjunto de puntos. Se coloca un papel sobre una mesa y alfileres en el papel en la ubicación de los datos. Una curva cúbica suave resulta al entrelazar la cinta entre los alfileres. De aquí que se haya adoptado el nombre de Trazador Cúbico para los polinomios de este tipo.

 Fuente: Chapra, S., Canale, R. Métodos Numéricos para ingenieros, 5a Ed. Mc. Graw Hill. 2007

[Volver a la Tabla de Contenido](#TOC)

### Trazadores lineales

La unión más simple entre dos puntos es una línea recta. Los trazadores de primer grado para un grupo de datos ordenados pueden definirse como un conjunto de funciones lineales:

\begin{equation*}
\begin{split}
f(x) & = f(x_0) + m_0(x - x_0), \quad x_0 \le x \le x_1 \\
f(x) & = f(x_1) + m_1(x - x_1), \quad x_1 \le x \le x_2 \\
f(x) & = f(x_2) + m_2(x - x_2), \quad x_2 \le x \le x_3 \\
&\vdots \\
f(x) & = f(x_{n-1}) + m_{n-1}(x - x_{n-1}), \quad x_{n-1} \le x \le x_n
\end{split}
\label{eq:Ec4_21} \tag{4.21}
\end{equation*}

donde $m_i=\frac{f(x_{i+1})-f(x_i)}{(x_{i+1}-x_i)}$ es la pendiente de la línea recta que une los puntos. La principal desventaja de los trazadores de primer grado es que no son suaves. En los puntos donde se encuentran dos trazadores, la pendiente cambia de forma abrupta. La primer derivada de la función es discontinua en esos puntos.

[Volver a la Tabla de Contenido](#TOC)

### Trazadores cuadráticos

Para asegurar que las derivadas $m$-ésimas sean continuas en los nodos, se debe emplear un trazador de un grado de, al menos, $m+1$. El objetivo de los trazadores cuadráticos es obtener un polinomio de segundo grado para cada intervalo entre los datos. De manera general, el polinomio en cada intervalo se representa como:

\begin{equation*}
\begin{split}
f(x_i) = a_ix^2+b_ix+c_i
\end{split}
\label{eq:Ec4_22} \tag{4.22}
\end{equation*}

Para $n+1$ datos ($i=0, 1, 2,\ldots, n$) existen $n$ intervalos y, en consecuencia, $3n$ constantes desconocidas ($a$, $b$ y $c$) por evaluar. Por lo tanto, se requieren $3n$ ecuaciones o condiciones para evaluar las incógnitas. Éstas son:

1. Los valores de la función de polinomios adyacentes deben ser iguales en los nodos interiores. Esta condición se representa como:

\begin{equation*}
\begin{split}
a_{i−1}x_{i−1}^2+b_{i−1}x_{i−1}+c_{i−1}&=f(x_{i−1}) \\
a_i x_{i−1}^2+b_i x_{i−1}+c_i&=f(x_{i−1})
\end{split}
\label{eq:Ec4_23} \tag{4.23}
\end{equation*}

   para $i=2$ a $n$. Como sólo se emplean nodos interiores, las ecuaciones anteriores proporcionan, cada una, $n–1$ condiciones; en total, $2n–2$ condiciones.

2. La primera y la última función deben pasar a través de los puntos extremos. Esto agrega dos ecuaciones más:

\begin{equation*}
\begin{split}
a_{1}x_{0}^2+b_{1}x_{0}+c_{1}&=f(x_{0}) \\
a_n x_{n}^2+b_n x_{n}+c_n&=f(x_{n})
\end{split}
\label{eq:Ec4_24} \tag{4.24}
\end{equation*}

   En total se tienen $2n−2+2=2n$ condiciones

3. La primera derivada de la ecuación: $f_i(x)=a_ix^2+b_i x+c$ es: $f'(x_𝑖)=2a_ix+b$. De manera general, esta condición se representa como:

\begin{equation*}
\begin{split}
2a_{i−1} x_{i−1}+b_{i−1}=2a_i x_{i−1}+b_i
\end{split}
\label{eq:Ec4_25} \tag{4.25}
\end{equation*}

   para $i = 2an$. Esto proporciona otras $n–1$ condiciones, llegando a un total de $2n+n−1=3n−1$. Como se tienen $3n$ incógnitas, falta una condición más.

4. Suponga que en el primer punto la segunda derivada es cero. Esto es: $f''_𝑖(x)=2a_i$, que se puede expresar matemáticamente como:

\begin{equation*}
\begin{split}
a_1=0
\end{split}
\label{eq:Ec4_26} \tag{4.26}
\end{equation*}

 Fuente: Chapra, S., Canale, R. Métodos Numéricos para ingenieros, 5a Ed. Mc. Graw Hill. 2007

[Volver a la Tabla de Contenido](#TOC)

### Trazadores cúbicos

El objetivo es obtener polinomios de tercer grado para cada intervalo entre los nodos:

\begin{equation*}
\begin{split}
f_i(x)=a_i x^3+b_i x^2+c_i x+d_i
\end{split}
\label{eq:Ec4_27} \tag{4.27}
\end{equation*}

Así para los $n+1$ datos ($i=0,1,2,\ldots,n$) existen $n$ intervalos y, por lo tanto, se requerirán de $4n$ condiciones para evaluar las incógnitas. Estas son:

1. Los valores de la función deben ser iguales en los nodos interiores: $2n−2$ condiciones.

2. La primera y la última función deben pasar a través de los puntos extremos: $2$ condiciones.

3. Las primeras derivadas en los nodos interiores deben ser iguales: $n−1$ condiciones.

4. Las segundas derivadas en los nodos interiores deben ser iguales: $n−1$.

5. Las segundas derivadas en los nodos extremos son cero: $2$ condiciones.

Suponga que se tienen $n+1$ puntos, $P_k(x_k,y_k)$, donde los $y_k=f(x_k)$, $k=0m,1,2,\ldots,n$, en los cuales se requiere interpolar una función $f$. Las abcisas, $x_k$, no se requiere que sean equidistantes, pero sí estén ordenadas, es decir: $x_0<x_1<x_2<\ldots<x_n$.

Se definirá una función $s(x)$ cúbica a trazos en $[x_0, x_n]$, construida a partir de una serie de polinómios c´bicos $q_0(x), q_1(x), q_2(x), \ldots, q_{n-1}(x)$ tales que $s(x)=q_k(x)$ en $[x_k,x_{k+1}]$, para $k=0, 1, 2, \ldots, n-1$.

para que $s(x)$ interpole en los puntos $P_0, P_1, P_2, \ldots, P_n$, los polinomios $q_k(x)$ deben cumplir:

\begin{equation*}
\begin{split}
q_k(x_k)&=y_k \\
q_k(x_{k+1})&=y_{k+1} \quad k=0,1,2 \ldots, n-1
\end{split}
\label{eq:Ec4_28} \tag{4.28}
\end{equation*}

De la ecuación [(4.28)](#Ec4_28) se obtienen $2n$ condiciones. También, los polinomios $q_k(x)$ del interpolador cúbico $s(x)$ deben tener la misma pendiente y concavidad, es decir

\begin{equation*}
\begin{split}
q'_{k-1}(x_k)&=q'_k(x_k) \\
q''_{k-1}(x_{k})&=q''_{k}(x_k) \quad k=1,2 \ldots, n-1
\end{split}
\label{eq:Ec4_29} \tag{4.29}
\end{equation*}

de la ecuación [(4.29)](#Ec4_29) se obtienen otras $2(n-1)$ condiciones a ser satisfechas. Las ecuaciones [(4.28)](#Ec4_28) y [(4.29)](#Ec4_29) son condiciones de continuidad mediante la primera y segunda derivada.

Si $s(x)$ es cúbica a trozos en el intervalo $[x_0, x_n]$, su derivada segunda $s''(x)$ es lineal en el mismo intervalo e interpola en los puntos $(x_k, s''(x_k))$ y $(x_{k+1}, s''(x_{k+1}))$ en $[x_k, x_{k+1}]$. Por tanto, $q_k(x)$ es un polinomio de grado uno que interpola en los puntos $(x_k, s''(x_k))$ y $(x_{k+1}, s''(x_{k+1}))$

\begin{equation*}
\begin{split}
q''_k(x)=s''(x_k) \frac{x-x_{k+1}}{x_k-x_{k+1}}+s''(x_{k+1}) \frac{x-x_{k}}{x_{k+1}-x_{k}}, \quad k=0,1,2,\ldots,n-1
\end{split}
\end{equation*}

sean,

\begin{equation*}
\begin{split}
h_k&=x_{k+1}-x_k, \quad k=0,1,2,\ldots,n-1 \\
\sigma_k&=s''(x_k), \quad k=0,1,2,\ldots,n
\end{split}
\end{equation*}

reemplazando,

\begin{equation*}
\begin{split}
q''_k(x)=\frac{\sigma_k}{h_k}(x_{k+1}-x)+\frac{\sigma_{k+1}}{h_k}(x-x_k), \quad k=0,1,2,\ldots,n
\end{split}
\label{eq:Ec4_30} \tag{4.30}
\end{equation*}

donde $h_k$ y $\sigma_k$ son constantes, con $\sigma_k$ aún por determinar. Para ello, integrando dos veces se tiene

\begin{equation*}
\begin{split}
q_k(x)=\frac{\sigma_k}{h_k}\frac{(x_{k+1}-x)^3}{6}+\frac{\sigma_{k+1}}{h_k}\frac{(x-x_k)}{6}+C_k +D_kx
\end{split}
\label{eq:Ec4_31} \tag{4.31}
\end{equation*}

El término lineal $C_k+D_kx$, se puede reescribir como:

\begin{equation*}
\begin{split}
C_k+D_kx=A_k(x-x_k)+B_k(x_{k+1}-x)
\end{split}
\end{equation*}

donde $A_k$ y $B_k$ son constantes arbitrarias. La ecuación [(4.31)](#Ec4_31) queda

\begin{equation*}
\begin{split}
q_k(x)=\frac{\sigma_k}{h_k}\frac{(x_{k+1}-x)^3}{6}+\frac{\sigma_{k+1}}{h_k}\frac{(x-x_k)}{6}+A_k(x-x_k)+B_k(x_{k+1}-x)
\end{split}
\label{eq:Ec4_32} \tag{4.32}
\end{equation*}

Ahora aplicando las condiciones dadas en las ecuaciones [(4.28)](#Ec4_28) a esta ecuación,

\begin{equation*}
\begin{split}
y_k&=\frac{\sigma_k}{h_k}\frac{h_k^3}{6}+\frac{\sigma_{k+1}}{h_k}\times 0+A_k \times 0 + B_kh_k=\frac{\sigma_k}{6}h_k^2+B_kh_k \\
y_{k+1}&=\frac{\sigma_{k+1}}{h_k}h_k^3+A_kh_k=\frac{\sigma_{k+1}}{6}h_k^2+A_kh_k
\end{split}
\label{eq:Ec4_33} \tag{4.33}
\end{equation*}

De estas dos ecuaciones con dos incógnitas, se despejan $A_k$ y $B_k$, y se sustituyen en la ecuación [(4.32)](#Ec4_32), resultando:

\begin{equation*}
\begin{split}
q_k(x)&=\frac{\sigma_k}{6} \left[ \frac{(x_{k+1}-x)^3}{h_k}-h_k(x_{k+1}-x) \right] \\
&+\frac{\sigma_{k+1}}{6} \left[ \frac{(x-x_k)^3}{h_k}-h_k(x-x_k) \right] \\
&+y_k \left[ \frac{(x_{k+1}-x)}{h_k} \right] +y_{k+1} \left[ \frac{(x-x_k)}{h_k} \right], \quad k=0,1,2,\ldots,n-1
\end{split}
\label{eq:Ec4_34} \tag{4.34}
\end{equation*}

Que corresponde a la ecuación para el spline $q_k(x)$. Falta conocer los valores $\sigma_k$, con $k=0,1,2,\ldots, n$, que proporcionan otras $n+1$ incógnitas. Para esto, empleamos las condiciones dadas en la ecuación [(4.29)](#Ec4_29) y derivando la ecuación [(4.34)](#Ec4_34), se tiene:

\begin{equation*}
\begin{split}
q'_k(x)&=\frac{\sigma_k}{6} \left[ \frac{-3(x_{k+1}-x)^2}{h_k}+h_k \right]+\frac{\sigma_{k+1}}{6} \left[ \frac{3(x_k-x)^2}{h_k}-h_k \right]+\frac{y_{k+1}-y_k}{h_k}
\end{split}
\label{eq:Ec4_35} \tag{4.35}
\end{equation*}

entonces,

\begin{equation*}
\begin{split}
q'_k(x)&=\frac{\sigma_k}{6}(-2h_k)+\frac{\sigma_{k+1}}{6}(-h_k)+\frac{y_{k+1}-y_k}{h_k}
\end{split}
\label{eq:Ec4_36} \tag{4.36}
\end{equation*}

y

\begin{equation*}
\begin{split}
q'_k(x_{k+1})&=\frac{\sigma_k}{6}(h_k)+\frac{\sigma_{k+1}}{6}(2h_k)+\frac{y_{k+1}-y_k}{h_k}
\end{split}
\label{eq:Ec4_37} \tag{4.37}
\end{equation*}

Reemplazando $k$ por $k-1$ en la ecuación [(4.37)](#Ec4_37) para obtener $q'_{k-1}(x_k)$ e igualando en la ecuación [(4.36)](#Ec4_36) se llega a:

\begin{equation*}
\begin{split}
h_{k-1}\sigma_{k-1}+2(h_{k-1}+h_k)\sigma_k+h_k\sigma_{k+1}=6 \left( \frac{y_{k+1}-y_k}{h_k} - \frac{y_k-y_{k-1}}{h_{k-1}} \right), \quad k=1,2,3,\ldots, n-1
\end{split}
\label{eq:Ec4_38} \tag{4.38}
\end{equation*}

obsérvese que el término entre paréntesis se puede representar como diferenciales $(\Delta y_k)$ o incluso como diferencias divididas de Newton, $f[x_k,x_{k+1}]$ vistas al comienzo del capítulo.

Hay que tener en cuenta que el índice $k$ varía de $1$ a $n-1$, produciéndose $n-1$ ecuaciones lineales con $n+1$ incógnitas. Esto genera un sistema indeterminado, con infinitas soluciones. Existen varias formas de determinar $\sigma_0$ y $\sigma_n$ de la primera y $n-1$-ésima ecuación, llegándose a un sistema tridiagonal de orden $n-1$ con las variables $\sigma_k$, $k=1, 2, 3, \ldots, n-1$.

[Volver a la Tabla de Contenido](#TOC)

#### Alternativa I:

Especificando el valor de la segunda derivada, $s''(x)$ en los puntos extremos: $\sigma_0=s''(x_0)$ y $\sigma_n=s''(x_n)$. Si se supone $\sigma_0=0$ y $\sigma_n=0$ se denomina [spline cúbico natural](https://towardsdatascience.com/numerical-interpolation-natural-cubic-spline-52c1157b98ac)

[Volver a la Tabla de Contenido](#TOC)

#### Alternativa II:

Suponer que $s''(x)$ es constante en los extremos, es decir, $\sigma_0=\sigma_1$ y $\sigma_n=\sigma_{n-1}$

[Volver a la Tabla de Contenido](#TOC)

#### Alternativa III:

Suponer que $s''(x)$ es lineal cerca de los extremos, es decir,

$$\sigma_0=\frac{1}{h_1}((h_0+h_1)\sigma_1 - h_0\sigma_2)$$

y

$$\sigma_n=\frac{1}{h_{n-2}}((h_{n-2}-h_{n-1})\sigma_{n-2}+ (h_{n-2}+h_{n-1})\sigma_{n-1})$$

[Volver a la Tabla de Contenido](#TOC)

#### Alternativa IV:

Especificar el valor de $s'(x)$ en los puntos extremos:

$$\sigma_0=\frac{3}{h_0}[\Delta y_0-s'(x_0)]-\frac{1}{2}\sigma_1$$

y

$$\sigma_n=\frac{3}{h_{n-1}}[s'(x_n)-\Delta y_{n-1}]-\frac{1}{2}\sigma_{n-1}$$

[Volver a la Tabla de Contenido](#TOC)

### Ejemplo aplicación

Dado el siguiente conjunto de datos:

|x|f(x)|
|:--:|:--:|
|3.0|2.5|
|4.5|1.0|
|7.0|2.5|
|9.0|0.5|

Evalúe el valor en $x=5.0$ empleando trazadores cuadráticos y cúbicos

[Volver a la Tabla de Contenido](#TOC)

#### Trazadores cuadráticos

De la tabla se tienen cuatro puntos y $n=3$ intervalos, por lo que se deben determinar $3n=3\times3=9$ incógnitas.

1. De las ecuaciones [(4.23)](#Ec4_23) se determinan $2\times3-2=4$ condiciones así,

\begin{equation*}
\begin{split}
4.5^{2}a_{1}+4.5b_{1}+c_{1}&=1.0 \\
4.5^{2}a_{2}+4.5b_{2}+c_{2}&=1.0 \\
7.0^{2}a_{2}+7.0b_{2}+c_{2}&=2.5 \\
7.0^{2}a_{3}+7.0b_{3}+c_{3}&=2.5
\end{split}
\end{equation*}

2. La primera y última función pasan por los puntos extremos, agregando 2 ecuaciones más, ecuación [(4.24)](#Ec4_24):

\begin{equation*}
\begin{split}
3.0^2a_{1}+3.0b_{1}+c_{1}&=2.5 \\
9.0^2a_{3}+9.0b_{3}+c_{3}&=0.5
\end{split}
\end{equation*}

3. La continuidad de las derivadas crean adicionalmente $3-1=2$ condiciones, ecuación [(4.25)](#Ec4_25):

\begin{equation*}
\begin{split}
9.0a_{1}+b_{1}=9.0a_{2}+b_{2} \\
14.0a_{2}+b_{2}=14.0a_{3}+b_{3}
\end{split}
\end{equation*}

4. De la ecuación [(4.26)](#Ec4_26) se obtiene la ecuación faltante, es decir,

\begin{equation*}
\begin{split}
a_{1}=0
\end{split}
\end{equation*}

Esta última ecuación especifica de forma exacta el valor de una de las $9$ condiciones requridas, por lo que el problema se reduce a encontrar las restantes $8$ ecuaciones. Estas condiciones se expresan de forma matricial de la siguiente manera:

\begin{align*}
\left[\begin{array}{cccc}
  4.5 & 1 & 0     & 0   & 0 & 0 & 0 & 0 \\
  0   & 0 & 20.25 & 4.5 & 1 & 0 & 0 & 0 \\
  0   & 0 & 49    & 7   & 1 & 0 & 0 & 0 \\
  0   & 0 & 0     & 0   & 0 & 49 & 7 & 1 \\
  3   & 1 & 0     & 0   & 0 & 0  & 0 & 0 \\
  0   & 0 & 0     & 0   & 0 & 81 & 9 & 1 \\
  1   & 0 & -9    & -1  & 0 & 0 & 0 & 0 \\
  0   & 0 & 14    & 1   & 0 & -14 & -1 & 0 \\
\end{array}\right]
\begin{Bmatrix}
  b_{1}  \\
  c_{1}  \\
  a_{2}  \\
  b_{2}  \\
  c_{2}  \\
  a_{3}  \\
  b_{3}  \\
  c_{3}  \\
\end{Bmatrix}
= \begin{Bmatrix}
  1.0  \\
  1.0  \\
  2.5  \\
  2.5  \\
  2.5  \\
  0.5  \\
  0.0  \\
  0.0
\end{Bmatrix}
\end{align*}

Empleando una de las técnicas de resolución de sistemas de ecuaciones lineales vistas en el capítulo anterior, se llega a las respuetas:

\begin{array}{crl}
a_1&=0.0, &b_1&=-1, &c_1&=5.5 \\
a_2&=0.64, &b_2&=-6.76, &c_2&=18.46 \\
a_3&=-1.6, &b_3&=24.6, &c_3&=-91.3
\end{array}

Sustituyendo estos valores en las ecuaciones cuadráticas originales,

\begin{array}{crl}
f_1(x)=-x+5.5, &3.0\le x \le 4.5 \\
f_2(x)=0.64x^2-6.76x+18.46, &4.5 \le x \le 7.0 \\
f_3(x)=-1.6x^2+24.6x-91.3, &7.0\le x \le 9.0 \\
\end{array}

Por último, se emplea la ecuación $f_2$, para predecir el valor en $x=5$, que es $f_2(5)=0.66$

[Volver a la Tabla de Contenido](#TOC)

#### Trazadores cúbicos

[Volver a la Tabla de Contenido](#TOC)

```python
from IPython.core.display import HTML
def css_styling():
    styles = open('./nb_style.css', 'r').read()
    return HTML(styles)
css_styling()
```

<link href='http://fonts.googleapis.com/css?family=Fenix' rel='stylesheet' type='text/css'>
<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=Source+Code+Pro:300,400' rel='stylesheet' type='text/css'>

```python

```