[75.12] Análisis Numérico

Trabajo Práctico 1

1er Cuatrimestre 2021

---

Búsqueda de raíces

---

AUTOR

Sánchez, Juan Pablo (jpsanchez@fi.uba.ar) - 105.865

de Luca Andrea, Felipe (fdeluca@fi.uba.ar) - 105.646

Litteri, Iván (ilitteri@fi.uba.ar - 106.223

CÁTEDRA

Sassano

FECHA DE ENTREGA

26 de mayo del 2021

LENGUAJE ELEGIDO

Python

CALIFICACIÓN

---

*Es importante correr todos los bloques de código si se quiere visualizar algún resultado ya que algunos dependen de bloques previos.*

```python
import numpy as np
from sympy import *
from matplotlib import pyplot as plt
from scipy import optimize
import matplotlib.ticker as mticker
np.seterr('raise')
```

    {'divide': 'warn', 'invalid': 'warn', 'over': 'warn', 'under': 'ignore'}

```python
# Función auxiliar para imprimir iteraciones
def imprimir_iteraciones(iteraciones, cant_semillas = 1, n_mostrar = 0):
  print("#\t\t\t Valor calculado\t\t Variación respecto al anterior")
  print(*[f"Semilla   {x}\t\t"+f"{iteraciones[x] : 1.20f}"[:18] for x in range (0, cant_semillas)], sep = '\n')
  print(*[f"Iteracion {x}\t\t"+f"{iteraciones[x] : 1.20f}"[:18]+"\t\t"+f"{abs(iteraciones[x-1] - iteraciones[x]) : 1.20f}"[:18] for x in range(cant_semillas, len(iteraciones))], sep = '\n')

# Función auxiliar para obtener la tolerancia al considerar un número 0. Tipo debe ser np.float32 o np.float64
def tol_cero(tipo):
  return np.float32(2)**np.float32(-120) if tipo == np.float32 else np.float64(2)**np.float64(-1000)
```

# 1. Métodos para seres queridos

En el marco de la época de la pandemia poder ayudar a los seres queridos es lo más importante que podemos hacer.

## (a) Buscar la forma de implementar un método visto en clase para ayudar o apoyar a un ser querido. De no ser posible dar un ejemplo de un uso de los métodos vistos en clase para el área en que se desarrollen profesionalmente.

Ayudamos a un familiar a la hora de ajustar el volumen del celular para poder ver una película. Decidimos emplear el método de bisección para hallar el volumen adecuado.

El método consiste en obtener, mediante un intervalo ($\tau$), una raíz definida como cero en el método. Para ello, necesitamos conocer si existe un cambio de signo en el mismo, de lo contrario no podemos garantizar la existencia de la raíz buscada. En caso de no encontrarla, se divide el intervalo en dos y se vuelve a evaluar el cambio de signo antedicho.  El proceso debe repetirse hasta mejorar la aproximación, de acuerdo con el error que estemos dispuestos a aceptar.

Como raíz o cero tomamos

  $$\text{raíz o cero = volúmen adecuado}$$

en donde nuestra función es

$$f(x) = \text{volúmen del celular}$$

Para la comprobación por el método de Bolzano

\begin{equation}
  f(\text{volúmen bajo}) < 0 \text{ y } f(\text{volúmen alto}) > 0
  \quad (\rho)
\end{equation}

Fig.1 En naranja y amarillo se ve el máximo y mínimo del diálogo. La segunda vez que preguntamos vemos el volumen en verde, en magenta el valor requerido por el usuario.

Con lo cual, por lo dicho en ($\rho$) debemos obtener un valor que no necesariamente se encuentre en el medio, ya que dependerá de la subjetividad del usuario en cuestión, pero que por el Teorema del Valor Intermedio sabemos que debe existir dentro del intervalo que poseemos.

Dicho esto, procederemos a preguntar:

- *¿El volumen está muy alto o bajo?* ($\lambda$)
- *Muy alto*

> Bajamos el volumen y volvemos a preguntar ($\lambda$)

- *Muy bajo*

>
 Vemos entonces la definición del intervalo que buscábamos ($\tau$) para aplicar el método que necesitamos. Como quedan dos intervalos (el primero, y el definido en el diálogo, lógicamente comprendido en el anterior) chequeamos cada uno por separado. Por ende, subimos y/o bajamos el volumen del teléfono y volvemos a preguntar.

> Lo haremos tantas veces como haga falta y de esa forma hallaremos el volumen adecuado.

Respecto al error, el mismo queda limitado por la cantidad de divisiones que posea la escala de volúmenes en el teléfono, con lo cual no podremos controlar ese aspecto.

## (b) Comentar la experiencia.

Se nos ocurrió esta idea ya que hace unos meses uno de los integrantes del grupo estaba ayudando a su abuela a usar su teléfono, siendo que ella no lo sabe utilizar muy bien.

En ese entonces, ella había estado recibiendo llamados pero tenía el teléfono silenciado y no se acordaba como subirle el volumen, por lo que me pidio ayuda ya que había pasado por su casa a dejarle unas cosas.

La experiencia fue muy similar a lo expresado anteriormente: puse un video de youtube para poder ir testeando el sonido, lo puse a la mitad y le pregunté si estaba muy alto o muy bajo. Así una o dos veces más y se lo deje, no tuvo más inconviententes.

# 2. Hallar $\pi$ por dos caminos

## (a) Algoritmo de Newton-Raphson

```python
# Realiza las cuentas con y devuelve el mismo tipo de dato que tiene la semilla
def nr(funcion, derivada, semilla, max_iter = 10000, corte = 0):
  tolerancia_cero = tol_cero(type(semilla))
  lista = [semilla]
  for i in range(1, max_iter):
    divisor = derivada(lista[-1])
    if (abs(divisor) < tolerancia_cero):
        raise ZeroDivisionError

    lista.append(lista[-1] - funcion(lista[-1])/divisor)
    if (abs(lista[i - 1] - lista[i]) <= corte):
      break

  return np.array(lista)
```

## (b) Algoritmo de Leibniz

```python
def leibniz(iteraciones, tipo):
    pi = tipo(0.0)
    signo = tipo(1.0)
    for iteracion in range(1, iteraciones, 2):
        pi += signo / tipo(iteracion)
        signo *= tipo(-1.0)

    return pi * tipo(4)
```

## (c) Ejecutar los algoritmos anteriores con iteraciones $n=10, n=100, n=1000, n=10000, n=100000$ utilizando una representación de punto flotante de $32$ bits.

```python
ITERACIONES = [10, 100, 1000, 10000, 100000]
```

### Con Newton-Raphson

```python
  iteraciones_32 = nr(lambda x : np.sin(x), lambda x : np.cos(x), np.float64(3))

  print("Con punto flotante de 32 bits:")
  imprimir_iteraciones(iteraciones_32)
```

    Con punto flotante de 32 bits:
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 3.000000000000000
    Iteracion 1		 3.142546543074277		 0.142546543074277
    Iteracion 2		 3.141592653300476		 0.000953889773800
    Iteracion 3		 3.141592653589793		 0.000000000289316
    Iteracion 4		 3.141592653589793		 0.000000000000000

> No hicimos las siguientes iteraciones ya que el valor permanecía invariante desde la $4^{ta}$ iteración

### Con Leibniz

```python
print(*[f'{x} iteraciones: {leibniz(x, np.float32)}' for x in ITERACIONES], sep = '\n')
```

    10 iteraciones: 3.3396823406219482
    100 iteraciones: 3.121594190597534
    1000 iteraciones: 3.1395931243896484
    10000 iteraciones: 3.14139723777771
    100000 iteraciones: 3.141575813293457

## (d) Ejecutar los algoritmos anteriores con iteraciones $n=10, n=100, n=1000, n=10000, n=100000$ utilizando una representación de punto flotante de $64$ bits.

### Con Newton-Raphson

```python
  iteraciones_64 = nr(lambda x : np.sin(x), lambda x : np.cos(x), np.float64(3))
  print("Con punto flotante de 64 bits:")
  imprimir_iteraciones(iteraciones_64)
```

    Con punto flotante de 64 bits:
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 3.000000000000000
    Iteracion 1		 3.142546543074277		 0.142546543074277
    Iteracion 2		 3.141592653300476		 0.000953889773800
    Iteracion 3		 3.141592653589793		 0.000000000289316
    Iteracion 4		 3.141592653589793		 0.000000000000000

> No hicimos las siguientes iteraciones ya que el valor permanecía invariante desde la $4^{ta}$ iteración

### Con Leibniz

```python
print(*[f'{x} iteraciones: {leibniz(x, np.float64)}' for x in ITERACIONES], sep = '\n')
```

    10 iteraciones: 3.3396825396825403
    100 iteraciones: 3.121594652591011
    1000 iteraciones: 3.139592655589785
    10000 iteraciones: 3.141392653591791
    100000 iteraciones: 3.1415726535897814

## (e) Ejecutar los programas solicitados en a y b con una calculadora (aclarar marca y modelo) y comparar las respuestas obtenidas con $n = 10, n = 100, n = 1000, n = 10000$ y $n = 100000$ (en caso de no alcanzar la memoria de la calculadora utilizar el máximo $n$ posible).

Para realizar este item utilizamos una calculadora CASIO fx-991ES PLUS. Cuenta con las siguientes especificaciones:

### Con Newton-Raphson

Se obtuvieron los siguientes resultados:

*   $p_0 = 3$
*   $p_1 = 3.142546543$
*   $p_2 = 3.141592653$
*   $p_3 = \pi = 3.141592654$

A partir de las siguientes iteraciones la calculadora continuaba mostrando $\pi$, siendo el error tan pequeño que esta ya no podía diferenciarlo de acuerdo a sus especificaciones.

### Con Leibniz

Se obtuvieron los siguientes resultados:
*   $p_0 = 1$
*   $p_1 = \frac{2}{3}$
*   $p_{10} = 0.8080789524$
*   $p_{100} = 0.7878733593$
*   $p_{1000} = 0.7856479136$
*   $p_{10000} = 0.7854231609$
*   $p_{100000} = 0.7854006633$

Aclaración: La serie de Leibniz converge a $\frac{1}{4}\pi$ y en este caso se calculó sin multiplicar el valor por 4. Por comodidad, el valor real de este número es $\frac{1}{4}\pi = 0.7853981634 \pm 0.0000000001$.

Se puede notar que al llegar a $100000$ iteraciones (tras 2 horas calculando), la calculadora acumuló más error del que venía con $10000$.

## (f) Representar las dos respuestas finales obtenidas (para $n = 100000$ y el método de Newton Raphson) en c, d y e de manera de expresarlo como $\pi = \overline{\pi} + ∆\pi$.

### Con punto flotante de 32 bits

Las siguientes iteraciones de Newton Raphson ya no variaban, por lo que el error del método ya era más pequeño que el del tipo de dato utilizado en el cálculo. Por lo tanto, el error del resultado será el del tipo de dato.

Un punto flotante de 32 bits tiene reservados 23 dígitos para la mantisa, más el dígito implícito, por lo que en base 10 tendrá $log_{10}(2^{24}) \rfloor = 7$ dígitos significativos. Por lo tanto:

 $$\pi = 3.141592 \pm 0.000001$$

### Con punto flotante de 64 bits

Nuevamente el error será el de la representación del tipo de dato. Un punto flotante de 64 bits tiene reservados 52 dígitos para la mantisa, más el dígito implícito, por lo que en base 10 tendrá $\log_{10}(2^{53}) \rfloor = 15$ dígitos significativos. Por lo tanto:

$$\pi = 3.14159265358979 \pm 0.00000000000001$$

### Con calculadora CASIO fx-991ES PLUS

En este caso, por las mismas razones expresadas anteriormente, el error va a ser el de la calculadora. En este caso, esta especificaba un error de $\pm 1$ en el décimo dígito, es decir, 10 cifras significativas:

$$\pi = 3.141592654 \pm 0.000000001$$

## (g) ¿Podemos afirmar que para la computadora el número $π$ es una constante?

Si bien la calculadora guarda el número como una constante, se debería considerar una variable a la hora de calcular el error y su propagación, ya que al ser $\pi$ un número irracional este tiene infinitos dígitos que son imposibles de almacenar en una computadora, y mucho menos hacer cálculos con todos ellos. El error a considerar dependerá de las especificaciones del tipo de dato que se este utilizando para almacenarlo.

# 3. Búsqueda de raíces

$$
f_{1}(x) = x^2 - 2\\
f_{2}(x) = x^5 - 6.6 \cdot x^4 + 5.12 \cdot x^3 +  21.312 \cdot x^2 - 38.016 \cdot x + 17.28\\
f_{3}(x) = (x-1.5) \cdot e^{-4 \cdot (x-1.5)^{2}}
$$

```python
f1 = lambda x : x*x - 2
f1_der = lambda x : 2*x
f1_der_2 = lambda x : 2

f2 = lambda x : x ** 5 - 6.6 * x ** 4 + 5.12 * x ** 3 + 21.312 * x ** 2 - 38.016 * x + 17.28
f2_der = lambda x : 5 * x ** 4 - 26.4 * x ** 3 + 15.36 * x ** 2 + 42.624 * x - 38.016
f2_der_2 = lambda x : 20 * x ** 3 - 79.2 * x ** 2 + 30.72 * x + 42.624

f3 = lambda x : (x - 1.5) * np.exp(-4 * (x - 1.5) ** 2)
f3_der = lambda x : np.exp(-4 * (x - 1.5) ** 2) * ((-8 * x + 12) * (x - 1.5) + 1)
f3_der_2 = lambda x : np.exp(-4 * (x - 1.5) ** 2) * (-24 * x + (x - 1.5) * (8 * x - 12) ** 2 + 36)
```

```python
INTERVALO = [0, 2]
```

```python
# CONSTANTES
# Cotas de error
ERRORES = [np.float64(10**-5), np.float64(10**-13)]
# Funciones a evaluar
FUNCIONES = [f1, f2, f3]
FUNCIONES_DER = [f1_der, f2_der, f3_der]
FUNCIONES_DER_2 = [f1_der_2, f2_der_2, f3_der_2]
# Mensajes
STR_ERRORES = ['10^(-5)', '10^(-13)']
STR_FUNCIONES = ['x**2 - 2', 'x**5 - 6.6 * x**4 + 5.12 * x**3 + 21.312 * x ** 2 - 38.016 * x + 17.28', '(x - 1.5) * np.exp(-4 * (x - 1.5)**2)']
# Resultados
resultados_raices = {'biseccion': {}, 'nr': {}, 'nr_mod': {}, 'secante': {}}
```

```python
# Función auxiliar para imprimir las distintas raíces de los métodos
def imprimir_raices_de_funciones(algoritmo, raices: dict, semillas, funciones = (1, 2, 3)) -> None:
  cant_semillas = 1
  k = 0
  nombre_metodo = algoritmo.__name__
  for i in funciones:
      for j, e in enumerate(ERRORES):
          print(f'Funcion {STR_FUNCIONES[i-1]} con error de {STR_ERRORES[j]}')
          try:
              if nombre_metodo == 'nr_mod':
                  raices['nr_mod'][f'{i}'] = algoritmo(FUNCIONES[i-1], FUNCIONES_DER[i-1], FUNCIONES_DER_2[i-1], semillas[k], corte = e)
              elif nombre_metodo == 'nr':
                  raices['nr'][f'{i}'] = algoritmo(FUNCIONES[i-1], FUNCIONES_DER[i-1], semillas[k], corte = e)
              else:
                  raices[nombre_metodo][f'{i}'] = algoritmo(FUNCIONES[i-1], *semillas[k], e)
                  cant_semillas = 2
          except Exception:
              print(f"ERROR: Divisor se hizo 0 al calcular la siguiente iteración\n")
              continue
          imprimir_iteraciones(raices[nombre_metodo][f'{i}'], cant_semillas)
          print("\n")
      k += 1
```

## (a) Graficar las funciones $f_{1}(x), f_{2}(x), f_{3}(x)$ en el intervalo $[0, 2]$

```python
x = np.linspace(0,2,num=1000)

plt.plot(x, list(map(f1, x)), label=r'$x^2 - 2$')
plt.plot(x, list(map(f2, x)), label=r'$x^5 - 6.6x^4 + 5.12x^2 - 38.016x + 17.28$')
plt.plot(x, list(map(f3, x)), label=r'$(x-1.5) \cdot e^{(-4(x-1.5)^{2})}$')

plt.axhline(0, color="black")
plt.axvline(0, color="black")

plt.xlim(INTERVALO)
plt.ylim(-2, 2)

plt.legend()
plt.show()
```

## (b) Hallar las raices de las funciones $f_{1}(x), f_{2}(x), f_{3}(x)$ en el intervalo $[0, 2]$ con los métodos de  Bisección, Newton-Raphson, Newton-Raphson modificado y Secante.

### Algoritmo de Bisección

```python
def biseccion(funcion, q0, q1, corte = 0, max_iter = 10000):
  contador = 1
  lista = [q0, q1]

  while abs(lista[contador] - lista[contador-1]) > corte:
    q2 = (q0 + q1) / 2
    if funcion(q0) * funcion(q2) <= 0:
      q1 = q2
      lista.append(q1)
    else:
      q0 = q2
      lista.append(q0)
    contador = contador + 1
    if (contador >= max_iter):
      break

  return np.array(lista)
```

### Algoritmo de Newton Raphson Modificado

```python
def nr_mod(funcion, derivada, derivada_2, semilla, corte = 0, max_iter = 10000):
  tolerancia_cero = tol_cero(type(semilla))
  lista = [semilla]
  for i in range(1, max_iter):
    divisor = derivada(lista[-1]) ** 2 - funcion(lista[-1]) * derivada_2(lista[-1])
    if (abs(divisor) < tolerancia_cero):
        raise ZeroDivisionError

    lista.append(lista[-1] - funcion(lista[-1]) * derivada(lista[-1]) / divisor)
    if (abs(lista[i - 1] - lista[i]) <= corte):
      break

  return np.array(lista)
```

### Algoritmo de Secante

```python
def secante(f, a, b, corte = 0, max_iter = 1000):
    tolerancia_cero = tol_cero(type(a))
    p = [a, b]
    p_n_div = lambda f, n, p: f(p[n-1]) - f(p[n-2])
    p_n = lambda f, n, p: p[n-1] - ((f(p[n-1]) * (p[n-1] - p[n-2])) / (p_n_div(f, n, p)))
    for n in range(2, max_iter-1):
        if (abs(p_n_div(f, n, p)) < tolerancia_cero):
            raise ZeroDivisionError
        p.append(p_n(f, n, p))
        if abs(p[n-1] - p[n]) <= corte:
            break

    return np.array(p)
```

### Por Bisección

```python
imprimir_raices_de_funciones(biseccion, resultados_raices, [(np.float64(0), np.float64(2))] * 3)

```

    Funcion x**2 - 2 con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.000000000000000
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.000000000000000		 1.000000000000000
    Iteracion 3		 1.500000000000000		 0.500000000000000
    Iteracion 4		 1.250000000000000		 0.250000000000000
    Iteracion 5		 1.375000000000000		 0.125000000000000
    Iteracion 6		 1.437500000000000		 0.062500000000000
    Iteracion 7		 1.406250000000000		 0.031250000000000
    Iteracion 8		 1.421875000000000		 0.015625000000000
    Iteracion 9		 1.414062500000000		 0.007812500000000
    Iteracion 10		 1.417968750000000		 0.003906250000000
    Iteracion 11		 1.416015625000000		 0.001953125000000
    Iteracion 12		 1.415039062500000		 0.000976562500000
    Iteracion 13		 1.414550781250000		 0.000488281250000
    Iteracion 14		 1.414306640625000		 0.000244140625000
    Iteracion 15		 1.414184570312500		 0.000122070312500
    Iteracion 16		 1.414245605468750		 0.000061035156250
    Iteracion 17		 1.414215087890625		 0.000030517578125
    Iteracion 18		 1.414199829101562		 0.000015258789062
    Iteracion 19		 1.414207458496093		 0.000007629394531

    Funcion x**2 - 2 con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.000000000000000
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.000000000000000		 1.000000000000000
    Iteracion 3		 1.500000000000000		 0.500000000000000
    Iteracion 4		 1.250000000000000		 0.250000000000000
    Iteracion 5		 1.375000000000000		 0.125000000000000
    Iteracion 6		 1.437500000000000		 0.062500000000000
    Iteracion 7		 1.406250000000000		 0.031250000000000
    Iteracion 8		 1.421875000000000		 0.015625000000000
    Iteracion 9		 1.414062500000000		 0.007812500000000
    Iteracion 10		 1.417968750000000		 0.003906250000000
    Iteracion 11		 1.416015625000000		 0.001953125000000
    Iteracion 12		 1.415039062500000		 0.000976562500000
    Iteracion 13		 1.414550781250000		 0.000488281250000
    Iteracion 14		 1.414306640625000		 0.000244140625000
    Iteracion 15		 1.414184570312500		 0.000122070312500
    Iteracion 16		 1.414245605468750		 0.000061035156250
    Iteracion 17		 1.414215087890625		 0.000030517578125
    Iteracion 18		 1.414199829101562		 0.000015258789062
    Iteracion 19		 1.414207458496093		 0.000007629394531
    Iteracion 20		 1.414211273193359		 0.000003814697265
    Iteracion 21		 1.414213180541992		 0.000001907348632
    Iteracion 22		 1.414214134216308		 0.000000953674316
    Iteracion 23		 1.414213657379150		 0.000000476837158
    Iteracion 24		 1.414213418960571		 0.000000238418579
    Iteracion 25		 1.414213538169860		 0.000000119209289
    Iteracion 26		 1.414213597774505		 0.000000059604644
    Iteracion 27		 1.414213567972183		 0.000000029802322
    Iteracion 28		 1.414213553071022		 0.000000014901161
    Iteracion 29		 1.414213560521602		 0.000000007450580
    Iteracion 30		 1.414213564246892		 0.000000003725290
    Iteracion 31		 1.414213562384247		 0.000000001862645
    Iteracion 32		 1.414213561452925		 0.000000000931322
    Iteracion 33		 1.414213561918586		 0.000000000465661
    Iteracion 34		 1.414213562151417		 0.000000000232830
    Iteracion 35		 1.414213562267832		 0.000000000116415
    Iteracion 36		 1.414213562326040		 0.000000000058207
    Iteracion 37		 1.414213562355143		 0.000000000029103
    Iteracion 38		 1.414213562369695		 0.000000000014551
    Iteracion 39		 1.414213562376971		 0.000000000007275
    Iteracion 40		 1.414213562373333		 0.000000000003637
    Iteracion 41		 1.414213562371514		 0.000000000001818
    Iteracion 42		 1.414213562372424		 0.000000000000909
    Iteracion 43		 1.414213562372879		 0.000000000000454
    Iteracion 44		 1.414213562373106		 0.000000000000227
    Iteracion 45		 1.414213562372992		 0.000000000000113
    Iteracion 46		 1.414213562373049		 0.000000000000056

    Funcion x**5 - 6.6 * x**4 + 5.12 * x**3 + 21.312 * x ** 2 - 38.016 * x + 17.28 con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.000000000000000
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.000000000000000		 1.000000000000000
    Iteracion 3		 1.500000000000000		 0.500000000000000
    Iteracion 4		 1.250000000000000		 0.250000000000000
    Iteracion 5		 1.125000000000000		 0.125000000000000
    Iteracion 6		 1.187500000000000		 0.062500000000000
    Iteracion 7		 1.218750000000000		 0.031250000000000
    Iteracion 8		 1.203125000000000		 0.015625000000000
    Iteracion 9		 1.195312500000000		 0.007812500000000
    Iteracion 10		 1.199218750000000		 0.003906250000000
    Iteracion 11		 1.201171875000000		 0.001953125000000
    Iteracion 12		 1.200195312500000		 0.000976562500000
    Iteracion 13		 1.199707031250000		 0.000488281250000
    Iteracion 14		 1.199951171875000		 0.000244140625000
    Iteracion 15		 1.200073242187500		 0.000122070312500
    Iteracion 16		 1.200012207031250		 0.000061035156250
    Iteracion 17		 1.199981689453125		 0.000030517578125
    Iteracion 18		 1.199996948242187		 0.000015258789062
    Iteracion 19		 1.200004577636718		 0.000007629394531

    Funcion x**5 - 6.6 * x**4 + 5.12 * x**3 + 21.312 * x ** 2 - 38.016 * x + 17.28 con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.000000000000000
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.000000000000000		 1.000000000000000
    Iteracion 3		 1.500000000000000		 0.500000000000000
    Iteracion 4		 1.250000000000000		 0.250000000000000
    Iteracion 5		 1.125000000000000		 0.125000000000000
    Iteracion 6		 1.187500000000000		 0.062500000000000
    Iteracion 7		 1.218750000000000		 0.031250000000000
    Iteracion 8		 1.203125000000000		 0.015625000000000
    Iteracion 9		 1.195312500000000		 0.007812500000000
    Iteracion 10		 1.199218750000000		 0.003906250000000
    Iteracion 11		 1.201171875000000		 0.001953125000000
    Iteracion 12		 1.200195312500000		 0.000976562500000
    Iteracion 13		 1.199707031250000		 0.000488281250000
    Iteracion 14		 1.199951171875000		 0.000244140625000
    Iteracion 15		 1.200073242187500		 0.000122070312500
    Iteracion 16		 1.200012207031250		 0.000061035156250
    Iteracion 17		 1.199981689453125		 0.000030517578125
    Iteracion 18		 1.199996948242187		 0.000015258789062
    Iteracion 19		 1.200004577636718		 0.000007629394531
    Iteracion 20		 1.200000762939453		 0.000003814697265
    Iteracion 21		 1.200002670288085		 0.000001907348632
    Iteracion 22		 1.200003623962402		 0.000000953674316
    Iteracion 23		 1.200004100799560		 0.000000476837158
    Iteracion 24		 1.200003862380981		 0.000000238418579
    Iteracion 25		 1.200003981590270		 0.000000119209289
    Iteracion 26		 1.200004041194915		 0.000000059604644
    Iteracion 27		 1.200004070997238		 0.000000029802322
    Iteracion 28		 1.200004085898399		 0.000000014901161
    Iteracion 29		 1.200004093348979		 0.000000007450580
    Iteracion 30		 1.200004097074270		 0.000000003725290
    Iteracion 31		 1.200004098936915		 0.000000001862645
    Iteracion 32		 1.200004099868237		 0.000000000931322
    Iteracion 33		 1.200004099402576		 0.000000000465661
    Iteracion 34		 1.200004099635407		 0.000000000232830
    Iteracion 35		 1.200004099751822		 0.000000000116415
    Iteracion 36		 1.200004099810030		 0.000000000058207
    Iteracion 37		 1.200004099839134		 0.000000000029103
    Iteracion 38		 1.200004099853686		 0.000000000014551
    Iteracion 39		 1.200004099846410		 0.000000000007275
    Iteracion 40		 1.200004099850048		 0.000000000003637
    Iteracion 41		 1.200004099848229		 0.000000000001818
    Iteracion 42		 1.200004099849138		 0.000000000000909
    Iteracion 43		 1.200004099849593		 0.000000000000454
    Iteracion 44		 1.200004099849820		 0.000000000000227
    Iteracion 45		 1.200004099849934		 0.000000000000113
    Iteracion 46		 1.200004099849991		 0.000000000000056

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.000000000000000
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.000000000000000		 1.000000000000000
    Iteracion 3		 1.500000000000000		 0.500000000000000
    Iteracion 4		 1.250000000000000		 0.250000000000000
    Iteracion 5		 1.375000000000000		 0.125000000000000
    Iteracion 6		 1.437500000000000		 0.062500000000000
    Iteracion 7		 1.468750000000000		 0.031250000000000
    Iteracion 8		 1.484375000000000		 0.015625000000000
    Iteracion 9		 1.492187500000000		 0.007812500000000
    Iteracion 10		 1.496093750000000		 0.003906250000000
    Iteracion 11		 1.498046875000000		 0.001953125000000
    Iteracion 12		 1.499023437500000		 0.000976562500000
    Iteracion 13		 1.499511718750000		 0.000488281250000
    Iteracion 14		 1.499755859375000		 0.000244140625000
    Iteracion 15		 1.499877929687500		 0.000122070312500
    Iteracion 16		 1.499938964843750		 0.000061035156250
    Iteracion 17		 1.499969482421875		 0.000030517578125
    Iteracion 18		 1.499984741210937		 0.000015258789062
    Iteracion 19		 1.499992370605468		 0.000007629394531

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.000000000000000
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.000000000000000		 1.000000000000000
    Iteracion 3		 1.500000000000000		 0.500000000000000
    Iteracion 4		 1.250000000000000		 0.250000000000000
    Iteracion 5		 1.375000000000000		 0.125000000000000
    Iteracion 6		 1.437500000000000		 0.062500000000000
    Iteracion 7		 1.468750000000000		 0.031250000000000
    Iteracion 8		 1.484375000000000		 0.015625000000000
    Iteracion 9		 1.492187500000000		 0.007812500000000
    Iteracion 10		 1.496093750000000		 0.003906250000000
    Iteracion 11		 1.498046875000000		 0.001953125000000
    Iteracion 12		 1.499023437500000		 0.000976562500000
    Iteracion 13		 1.499511718750000		 0.000488281250000
    Iteracion 14		 1.499755859375000		 0.000244140625000
    Iteracion 15		 1.499877929687500		 0.000122070312500
    Iteracion 16		 1.499938964843750		 0.000061035156250
    Iteracion 17		 1.499969482421875		 0.000030517578125
    Iteracion 18		 1.499984741210937		 0.000015258789062
    Iteracion 19		 1.499992370605468		 0.000007629394531
    Iteracion 20		 1.499996185302734		 0.000003814697265
    Iteracion 21		 1.499998092651367		 0.000001907348632
    Iteracion 22		 1.499999046325683		 0.000000953674316
    Iteracion 23		 1.499999523162841		 0.000000476837158
    Iteracion 24		 1.499999761581420		 0.000000238418579
    Iteracion 25		 1.499999880790710		 0.000000119209289
    Iteracion 26		 1.499999940395355		 0.000000059604644
    Iteracion 27		 1.499999970197677		 0.000000029802322
    Iteracion 28		 1.499999985098838		 0.000000014901161
    Iteracion 29		 1.499999992549419		 0.000000007450580
    Iteracion 30		 1.499999996274709		 0.000000003725290
    Iteracion 31		 1.499999998137354		 0.000000001862645
    Iteracion 32		 1.499999999068677		 0.000000000931322
    Iteracion 33		 1.499999999534338		 0.000000000465661
    Iteracion 34		 1.499999999767169		 0.000000000232830
    Iteracion 35		 1.499999999883584		 0.000000000116415
    Iteracion 36		 1.499999999941792		 0.000000000058207
    Iteracion 37		 1.499999999970896		 0.000000000029103
    Iteracion 38		 1.499999999985448		 0.000000000014551
    Iteracion 39		 1.499999999992724		 0.000000000007275
    Iteracion 40		 1.499999999996362		 0.000000000003637
    Iteracion 41		 1.499999999998181		 0.000000000001818
    Iteracion 42		 1.499999999999090		 0.000000000000909
    Iteracion 43		 1.499999999999545		 0.000000000000454
    Iteracion 44		 1.499999999999772		 0.000000000000227
    Iteracion 45		 1.499999999999886		 0.000000000000113
    Iteracion 46		 1.499999999999943		 0.000000000000056

### Por Newton-Rhapson

```python
imprimir_raices_de_funciones(nr, resultados_raices, [np.float64(1)]*3)
```

    Funcion x**2 - 2 con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.000000000000000
    Iteracion 1		 1.500000000000000		 0.500000000000000
    Iteracion 2		 1.416666666666666		 0.083333333333333
    Iteracion 3		 1.414215686274509		 0.002450980392156
    Iteracion 4		 1.414213562374689		 0.000002123899820

    Funcion x**2 - 2 con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.000000000000000
    Iteracion 1		 1.500000000000000		 0.500000000000000
    Iteracion 2		 1.416666666666666		 0.083333333333333
    Iteracion 3		 1.414215686274509		 0.002450980392156
    Iteracion 4		 1.414213562374689		 0.000002123899820
    Iteracion 5		 1.414213562373095		 0.000000000001594
    Iteracion 6		 1.414213562373094		 0.000000000000000

    Funcion x**5 - 6.6 * x**4 + 5.12 * x**3 + 21.312 * x ** 2 - 38.016 * x + 17.28 con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.000000000000000
    Iteracion 1		 1.067039106145254		 0.067039106145254
    Iteracion 2		 1.111500862584999		 0.044461756439744
    Iteracion 3		 1.141056567216819		 0.029555704631820
    Iteracion 4		 1.160727268147251		 0.019670700930432
    Iteracion 5		 1.173827768369525		 0.013100500222274
    Iteracion 6		 1.182555936035558		 0.008728167666032
    Iteracion 7		 1.188372391418122		 0.005816455382564
    Iteracion 8		 1.192249031514232		 0.003876640096109
    Iteracion 9		 1.194833025735817		 0.002583994221585
    Iteracion 10		 1.196555499438189		 0.001722473702371
    Iteracion 11		 1.197703732101451		 0.001148232663262
    Iteracion 12		 1.198469183878466		 0.000765451777014
    Iteracion 13		 1.198979468909419		 0.000510285030953
    Iteracion 14		 1.199319651889759		 0.000340182980339
    Iteracion 15		 1.199546437748632		 0.000226785858872
    Iteracion 16		 1.199697626550762		 0.000151188802129
    Iteracion 17		 1.199798419005017		 0.000100792454254
    Iteracion 18		 1.199865617415838		 0.000067198410821
    Iteracion 19		 1.199910427112309		 0.000044809696470
    Iteracion 20		 1.199940311395558		 0.000029884283248
    Iteracion 21		 1.199960238848694		 0.000019927453136
    Iteracion 22		 1.199973667897056		 0.000013429048362
    Iteracion 23		 1.199982656971679		 0.000008989074622

    Funcion x**5 - 6.6 * x**4 + 5.12 * x**3 + 21.312 * x ** 2 - 38.016 * x + 17.28 con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.000000000000000
    Iteracion 1		 1.067039106145254		 0.067039106145254
    Iteracion 2		 1.111500862584999		 0.044461756439744
    Iteracion 3		 1.141056567216819		 0.029555704631820
    Iteracion 4		 1.160727268147251		 0.019670700930432
    Iteracion 5		 1.173827768369525		 0.013100500222274
    Iteracion 6		 1.182555936035558		 0.008728167666032
    Iteracion 7		 1.188372391418122		 0.005816455382564
    Iteracion 8		 1.192249031514232		 0.003876640096109
    Iteracion 9		 1.194833025735817		 0.002583994221585
    Iteracion 10		 1.196555499438189		 0.001722473702371
    Iteracion 11		 1.197703732101451		 0.001148232663262
    Iteracion 12		 1.198469183878466		 0.000765451777014
    Iteracion 13		 1.198979468909419		 0.000510285030953
    Iteracion 14		 1.199319651889759		 0.000340182980339
    Iteracion 15		 1.199546437748632		 0.000226785858872
    Iteracion 16		 1.199697626550762		 0.000151188802129
    Iteracion 17		 1.199798419005017		 0.000100792454254
    Iteracion 18		 1.199865617415838		 0.000067198410821
    Iteracion 19		 1.199910427112309		 0.000044809696470
    Iteracion 20		 1.199940311395558		 0.000029884283248
    Iteracion 21		 1.199960238848694		 0.000019927453136
    Iteracion 22		 1.199973667897056		 0.000013429048362
    Iteracion 23		 1.199982656971679		 0.000008989074622
    Iteracion 24		 1.199988485096395		 0.000005828124716
    Iteracion 25		 1.199992892039387		 0.000004406942991
    Iteracion 26		 1.199998674815197		 0.000005782775809
    Iteracion 27		 1.200109601049251		 0.000110926234054
    Iteracion 28		 1.200073069843097		 0.000036531206154
    Iteracion 29		 1.200048737564417		 0.000024332278679
    Iteracion 30		 1.200032583841324		 0.000016153723092
    Iteracion 31		 1.200021759988341		 0.000010823852983
    Iteracion 32		 1.200014766941075		 0.000006993047265
    Iteracion 33		 1.200010747490507		 0.000004019450567
    Iteracion 34		 1.200007374996902		 0.000003372493604
    Iteracion 35		 1.200007374996902		 0.000000000000000

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-5)
    ERROR: Divisor se hizo 0 al calcular la siguiente iteración

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-13)
    ERROR: Divisor se hizo 0 al calcular la siguiente iteración

Mientras que con las funciones $f_{1}(x)$ y $f_{2}(x)$ no hubo ningún problema al utilizar el algoritmo de Newton Raphson, la función $ f_{3}(x) = (x-1.5) \cdot e^{-4 \cdot (x-1.5)^{2}} $ da error. Esto se debe a que una derivada de las que el algoritmo evaluó al calcular la siguiente iteración dio nula, lo que fue resultado de que la sucesión diverja. En este caso, esto sucede ya que la semilla no esta lo suficientemente próxima a la raíz (se utiliza raiz = 1 como fue indicado).

El método de Newton Raphson se basa en utilizar la iteración por punto fijo de la función $ g(x) = x - \frac{f(x)}{f'(x)} $, ya que si $ g(r) = r $ entonces $\frac{f(r)}{f'(r)} = 0$ por lo que tenemos una raíz de $ f $ ($f'(r) \neq 0$).

La raíz de esta función se encuentra en $ r = 1,5 $, y una de las hipótesis utilizadas en la demostración de la convergencia de la iteración de punto fijo
es que para $ \forall x \in [a, b]$, $|g'(x)| < 1$ (unicidad del punto fijo),
donde $[a, b]$ es el intervalo dentro del cual estamos iterando.

En este caso, $g'(x) = 1 - \frac{f'(x)^2 - f(x)f''(x)}{f'(x)^2} = \frac{f(x)f''(x)}{f'(x)^2}$ pero  $g'(1) = -2$, por lo que incluir $ x = 1 $ en este intervalo implica que el método puede no converger.

Otra forma de verlo es notar que la expresión de Newton-Rhapson se puede deducir a partir del polinomio de Taylor de $f(x)$ alrededor de un $x_{n}$. En tal caso, $f(x) = f(x_{n}) + f'(x_{n})(x - x_{n}) + \frac{f''(\xi)}{2}(x - x_{n})^2$, con $\xi$ entre $x$ y $x_{n}$. Entonces, si evaluamos la función en la raíz $r$ de $f(x)$ queda $0 = f(r) = f(x_{n}) + f'(x_{n})(r - x_{n}) + \frac{f''(\xi)}{2}(r - x_{n})^2$. El método de Newton-Raphson asume que $x_n$ esta lo suficientemente cerca de $r$ tal que $(r-x_n)^2 << (r-x_n)$ lo que nos permite despreciar el último término del polinomio y asi obtener la expresión $r \approx x_n - \frac{f(x_n)}{f'(x_n)} \Rightarrow x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}$. En este caso, la semilla no es lo suficientemente cercana como para que esto se cumpla.

Para poder hacere que converja, podemos acercar la semilla un poco (a 1,3):

```python
imprimir_raices_de_funciones(nr, resultados_raices, [np.float64(1.3)], funciones = (3,))
```

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.300000000000000
    Iteracion 1		 1.594117647058823		 0.294117647058823
    Iteracion 2		 1.492821654209128		 0.101295992849694
    Iteracion 3		 1.500002960343984		 0.007181306134856
    Iteracion 4		 1.499999999999999		 0.000002960343985

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.300000000000000
    Iteracion 1		 1.594117647058823		 0.294117647058823
    Iteracion 2		 1.492821654209128		 0.101295992849694
    Iteracion 3		 1.500002960343984		 0.007181306134856
    Iteracion 4		 1.499999999999999		 0.000002960343985
    Iteracion 5		 1.500000000000000		 0.000000000000000

### Por Newton-Rhapson Modificado

```python
imprimir_raices_de_funciones(nr_mod, resultados_raices, [np.float64(1)] * 3)
```

    Funcion x**2 - 2 con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.000000000000000
    Iteracion 1		 1.333333333333333		 0.333333333333333
    Iteracion 2		 1.411764705882352		 0.078431372549019
    Iteracion 3		 1.414211438474870		 0.002446732592517
    Iteracion 4		 1.414213562371500		 0.000002123896630

    Funcion x**2 - 2 con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.000000000000000
    Iteracion 1		 1.333333333333333		 0.333333333333333
    Iteracion 2		 1.411764705882352		 0.078431372549019
    Iteracion 3		 1.414211438474870		 0.002446732592517
    Iteracion 4		 1.414213562371500		 0.000002123896630
    Iteracion 5		 1.414213562373094		 0.000000000001594
    Iteracion 6		 1.414213562373095		 0.000000000000000

    Funcion x**5 - 6.6 * x**4 + 5.12 * x**3 + 21.312 * x ** 2 - 38.016 * x + 17.28 con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.000000000000000
    Iteracion 1		 1.198429561200949		 0.198429561200949
    Iteracion 2		 1.199999958931438		 0.001570397730488
    Iteracion 3		 1.199999939960625		 0.000000018970812

    Funcion x**5 - 6.6 * x**4 + 5.12 * x**3 + 21.312 * x ** 2 - 38.016 * x + 17.28 con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.000000000000000
    Iteracion 1		 1.198429561200949		 0.198429561200949
    Iteracion 2		 1.199999958931438		 0.001570397730488
    Iteracion 3		 1.199999939960625		 0.000000018970812
    Iteracion 4		 1.199999912385445		 0.000000027575179
    Iteracion 5		 1.199999871258021		 0.000000041127424
    Iteracion 6		 1.199999807715154		 0.000000063542867
    Iteracion 7		 1.199999711987405		 0.000000095727749
    Iteracion 8		 1.199999711987405		 0.000000000000000

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-5)
    ERROR: Divisor se hizo 0 al calcular la siguiente iteración

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-13)
    ERROR: Divisor se hizo 0 al calcular la siguiente iteración

En este caso, la función 2 convergió mucho más rápido que con Newton-Raphson ya que es una función cuya raíz tenía multiplicidad mayor a uno, por lo que con este método conservamos la convergencia cuadrática.

En cuanto a la función 3, nuevamente falló al calcular alguna de las iteraciones debido a que el divisor se hizo 0. Esto se debe a que la función no convergió a la raíz por las mismas razones que Newton-Raphson, y podemos solucionarlo acercando la semilla:

```python
imprimir_raices_de_funciones(nr_mod, resultados_raices, [np.float64(1.3)], funciones = (3,))
```

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.300000000000000
    Iteracion 1		 1.403030303030303		 0.103030303030303
    Iteracion 2		 1.486431596392994		 0.083401293362691
    Iteracion 3		 1.499960091346067		 0.013528494953072
    Iteracion 4		 1.499999999998983		 0.000039908652915
    Iteracion 5		 1.500000000000000		 0.000000000001016

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 1.300000000000000
    Iteracion 1		 1.403030303030303		 0.103030303030303
    Iteracion 2		 1.486431596392994		 0.083401293362691
    Iteracion 3		 1.499960091346067		 0.013528494953072
    Iteracion 4		 1.499999999998983		 0.000039908652915
    Iteracion 5		 1.500000000000000		 0.000000000001016
    Iteracion 6		 1.500000000000000		 0.000000000000000

### Por Secante

```python
imprimir_raices_de_funciones(secante, resultados_raices, [(np.float64(0), np.float64(2))]*3)
```

    Funcion x**2 - 2 con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.000000000000000
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.000000000000000		 1.000000000000000
    Iteracion 3		 1.333333333333333		 0.333333333333333
    Iteracion 4		 1.428571428571428		 0.095238095238095
    Iteracion 5		 1.413793103448275		 0.014778325123152
    Iteracion 6		 1.414211438474870		 0.000418335026594
    Iteracion 7		 1.414213562688869		 0.000002124213999

    Funcion x**2 - 2 con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.000000000000000
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.000000000000000		 1.000000000000000
    Iteracion 3		 1.333333333333333		 0.333333333333333
    Iteracion 4		 1.428571428571428		 0.095238095238095
    Iteracion 5		 1.413793103448275		 0.014778325123152
    Iteracion 6		 1.414211438474870		 0.000418335026594
    Iteracion 7		 1.414213562688869		 0.000002124213999
    Iteracion 8		 1.414213562373094		 0.000000000315774
    Iteracion 9		 1.414213562373094		 0.000000000000000

    Funcion x**5 - 6.6 * x**4 + 5.12 * x**3 + 21.312 * x ** 2 - 38.016 * x + 17.28 con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.000000000000000
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.475409836065574		 0.524590163934425
    Iteracion 3		 1.452611812121219		 0.022798023944355
    Iteracion 4		 1.375615183141658		 0.076996628979561
    Iteracion 5		 1.336718258411820		 0.038896924729838
    Iteracion 6		 1.302029414616354		 0.034688843795465
    Iteracion 7		 1.277401409498029		 0.024628005118324
    Iteracion 8		 1.258345986729271		 0.019055422768758
    Iteracion 9		 1.244086150835949		 0.014259835893321
    Iteracion 10		 1.233278489277362		 0.010807661558587
    Iteracion 11		 1.225128158694202		 0.008150330583159
    Iteracion 12		 1.218970580971631		 0.006157577722571
    Iteracion 13		 1.214322212037839		 0.004648368933791
    Iteracion 14		 1.210812345088710		 0.003509866949129
    Iteracion 15		 1.208162528877272		 0.002649816211438
    Iteracion 16		 1.206162000870271		 0.002000528007000
    Iteracion 17		 1.204651727883742		 0.001510272986529
    Iteracion 18		 1.203511582198426		 0.001140145685315
    Iteracion 19		 1.202650870698435		 0.000860711499991
    Iteracion 20		 1.202001114853348		 0.000649755845086
    Iteracion 21		 1.201510615051329		 0.000490499802019
    Iteracion 22		 1.201140340044438		 0.000370275006890
    Iteracion 23		 1.200860823175217		 0.000279516869221
    Iteracion 24		 1.200649819712207		 0.000211003463009
    Iteracion 25		 1.200490536930929		 0.000159282781278
    Iteracion 26		 1.200370296883105		 0.000120240047823
    Iteracion 27		 1.200279529022556		 0.000090767860549
    Iteracion 28		 1.200211014117034		 0.000068514905521
    Iteracion 29		 1.200159290976704		 0.000051723140329
    Iteracion 30		 1.200120251662231		 0.000039039314473
    Iteracion 31		 1.200090772794012		 0.000029478868218
    Iteracion 32		 1.200068548524957		 0.000022224269054
    Iteracion 33		 1.200051781243777		 0.000016767281179
    Iteracion 34		 1.200039179083400		 0.000012602160377
    Iteracion 35		 1.200029586394158		 0.000009592689242

    Funcion x**5 - 6.6 * x**4 + 5.12 * x**3 + 21.312 * x ** 2 - 38.016 * x + 17.28 con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.000000000000000
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.475409836065574		 0.524590163934425
    Iteracion 3		 1.452611812121219		 0.022798023944355
    Iteracion 4		 1.375615183141658		 0.076996628979561
    Iteracion 5		 1.336718258411820		 0.038896924729838
    Iteracion 6		 1.302029414616354		 0.034688843795465
    Iteracion 7		 1.277401409498029		 0.024628005118324
    Iteracion 8		 1.258345986729271		 0.019055422768758
    Iteracion 9		 1.244086150835949		 0.014259835893321
    Iteracion 10		 1.233278489277362		 0.010807661558587
    Iteracion 11		 1.225128158694202		 0.008150330583159
    Iteracion 12		 1.218970580971631		 0.006157577722571
    Iteracion 13		 1.214322212037839		 0.004648368933791
    Iteracion 14		 1.210812345088710		 0.003509866949129
    Iteracion 15		 1.208162528877272		 0.002649816211438
    Iteracion 16		 1.206162000870271		 0.002000528007000
    Iteracion 17		 1.204651727883742		 0.001510272986529
    Iteracion 18		 1.203511582198426		 0.001140145685315
    Iteracion 19		 1.202650870698435		 0.000860711499991
    Iteracion 20		 1.202001114853348		 0.000649755845086
    Iteracion 21		 1.201510615051329		 0.000490499802019
    Iteracion 22		 1.201140340044438		 0.000370275006890
    Iteracion 23		 1.200860823175217		 0.000279516869221
    Iteracion 24		 1.200649819712207		 0.000211003463009
    Iteracion 25		 1.200490536930929		 0.000159282781278
    Iteracion 26		 1.200370296883105		 0.000120240047823
    Iteracion 27		 1.200279529022556		 0.000090767860549
    Iteracion 28		 1.200211014117034		 0.000068514905521
    Iteracion 29		 1.200159290976704		 0.000051723140329
    Iteracion 30		 1.200120251662231		 0.000039039314473
    Iteracion 31		 1.200090772794012		 0.000029478868218
    Iteracion 32		 1.200068548524957		 0.000022224269054
    Iteracion 33		 1.200051781243777		 0.000016767281179
    Iteracion 34		 1.200039179083400		 0.000012602160377
    Iteracion 35		 1.200029586394158		 0.000009592689242
    Iteracion 36		 1.200022595112167		 0.000006991281990
    Iteracion 37		 1.200016515736524		 0.000006079375643
    Iteracion 38		 1.200013242226562		 0.000003273509961
    Iteracion 39		 1.200009968716600		 0.000003273509961
    Iteracion 40		 1.200007513584128		 0.000002455132471
    Iteracion 41		 1.200007513584128		 0.000000000000000

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-5)
    ERROR: Divisor se hizo 0 al calcular la siguiente iteración

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-13)
    ERROR: Divisor se hizo 0 al calcular la siguiente iteración

Se puede notar como a este método le toman mas iteraciones converger que para otro método como Newton-Raphson, ya que al estar basado en este pero utilizar la secante como aproximación de la derivada de una función es normal que tarde más.

Nuevamente, este método falla con la tercera función. Al estar este basado en Newton-Raphson, también puede fallar si las semillas se encuentran muy alejadas de la raíz.

Por lo tanto, podemos acercársela un poco para que converja:

```python
imprimir_raices_de_funciones(secante, resultados_raices, [(np.float64(0.7), np.float64(2))], funciones = (3,))
```

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-5)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.699999999999999
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.027104650345444		 0.972895349654555
    Iteracion 3		 1.525649236494845		 0.498544586149401
    Iteracion 4		 1.467387499691451		 0.058261736803394
    Iteracion 5		 1.499976699062182		 0.032589199370731
    Iteracion 6		 1.500000099411637		 0.000023400349455
    Iteracion 7		 1.499999999999999		 0.000000099411638

    Funcion (x - 1.5) * np.exp(-4 * (x - 1.5)**2) con error de 10^(-13)
    #			 Valor calculado		 Variación respecto al anterior
    Semilla   0		 0.699999999999999
    Semilla   1		 2.000000000000000
    Iteracion 2		 1.027104650345444		 0.972895349654555
    Iteracion 3		 1.525649236494845		 0.498544586149401
    Iteracion 4		 1.467387499691451		 0.058261736803394
    Iteracion 5		 1.499976699062182		 0.032589199370731
    Iteracion 6		 1.500000099411637		 0.000023400349455
    Iteracion 7		 1.499999999999999		 0.000000099411638
    Iteracion 8		 1.500000000000000		 0.000000000000000

## (c) Halle la raíz mediante la función de búsqueda de raíces de un lenguaje o paquete orientado a cálculo numérico (e.g. Python+SciPy: `scipy.optimize.brentq`).

```python
print(f"Raiz de f1 segun SciPy: {optimize.brentq(f1, 0, 2)}")
print(f"Raiz de f2 segun SciPy: {optimize.brentq(f2, 0, 2)}")
print(f"Raiz de f3 segun SciPy: {optimize.brentq(f3, 0, 2)}")
```

    Raiz de f1 segun SciPy: 1.4142135623731364
    Raiz de f2 segun SciPy: 1.2000081652661798
    Raiz de f3 segun SciPy: 1.5000000000000198

## (d) Compare los resultados obtenidos para los distintos métodos y cotas, grafique el orden de convergencia P y la constante asisntotica λ para todos los casos. Discuta ventajas y desventajas.
## ¿Son las que esperaba en base a la teoría?

```python
# Titulos
TITULOS = {
    'biseccion': 'Método Biseccion',
    'nr': 'Método Newton-Raphson',
    'nr_mod': 'Método Newton-Raphson Modificado',
    'secante': 'Método Secante'
}

# Funciones LaTeX
LATEX_FUNCIONES = [
    r'$x^2 - 2$',
    r'$x^5 - 6.6x^4 + 5.12x^2 - 38.016x + 17.28$',
    r'$(x-1.5) \cdot e^{(-4(x-1.5)^{2})}$'
]

def graficar(funcion, nombre, min = None, max = None):
  fig, ax = plt.subplots(4, 3, figsize=(18.5, 20))
  metodos = list(resultados_raices.keys())
  plt.subplots_adjust(top = 0.99, bottom=0.01, hspace=0.5, wspace=0.4)
  for j in range(3):
	  for i in range(4):
	    if (j == 1):
	  	  ax[i][j].set_title("\n\n" + TITULOS[metodos[i]] + "\n\n")

	    x, y = funcion(resultados_raices[metodos[i]][str(j+1)])
	    ax[i][j].plot(x, y, label = LATEX_FUNCIONES[j])
	    ax[i][j].set_xlabel('número de iteración')
	    ax[i][j].set_ylabel(nombre)
	    ax[i][j].legend()
	    ax[i][j].set_ylim(bottom = min, top = max)
	    ax[i][j].set_xticks(x[::len(x) // 10 + 1])
```

### Algoritmo para calcular el orden de convergencia por iteración

```python
def ordenes_convergencia(iteraciones):
  tolerancia_cero = tol_cero(type(iteraciones[0]))
  ordenes = []
  # La función no calcula con la ultima iteración si esta es igual a la anteúltima (ya había convergido)
  if (iteraciones[-1] == iteraciones[-2]):
  	iteraciones = iteraciones[:-1]

  x = range(2, len(iteraciones) - 1)
  for i in x:
    num = np.log(abs((iteraciones[i + 1] - iteraciones[i]) / (iteraciones[i] - iteraciones[i - 1])))
    den = np.log(abs((iteraciones[i] - iteraciones[i - 1]) / (iteraciones[i - 1] - iteraciones[i - 2])))

    # Si denominador es 0, significa que el error no varió entre 2 iteraciones, consideramos orden 0.
    if (abs(den) <= tolerancia_cero):
  	  ordenes.append(0)
  	  continue
    ordenes.append(num / den)
  return x , ordenes
```

###Algoritmo para calcular la constante asintótica por iteración

```python
def constante_asintotica(iteraciones):
  tolerancia_cero = tol_cero(type(iteraciones[0]))
  ctes = []
  # La función no calcula con la ultima iteración si esta es igual a la anteúltima (ya había convergido)
  if (iteraciones[-1] == iteraciones[-2]):
  	iteraciones = iteraciones[:-1]

  _, ordenes = ordenes_convergencia(iteraciones)
  x = range(2, len(iteraciones) - 1)
  for i in x:
  	num = abs(iteraciones[i] - iteraciones[i -1])
  	den = abs((iteraciones[i - 1] - iteraciones[i - 2])) ** ordenes[i - 2]

    # Si denominador es 0, significa que el error no varió entre 2 iteraciones, consideramos constante 0.
  	if (abs(den) <= tolerancia_cero):
  	  ctes.append(0)
  	  continue
  	ctes.append(num / den)

  return x, ctes
```

### Gráficos de orden de convergencia

```python
graficar(ordenes_convergencia, nombre = 'orden de convergencia')
```

#### Bisección

El orden de convergencia de la bisección dio exactamente igual a lo esperado. Siempre va a ser 1 ya que se trata de un método que iteración por iteración va reduciendo el error a la mitad linealmente, por lo que nunca va a variar.

#### Newton-Raphson

En el caso de la primera y última función, estuvo bastante cerca del valor esperado de 2 (o incluso superior) durante la mayor parte de las iteraciones.

En cuanto a la segunda función, se sostuvo más que nada alrededor de 1, que también era de esperar al ser una función con raíz doble con lo cual este método no mantiene la convergencia cuadrática.

#### Newton-Raphson modificado

Para la primera y última función, nuevamente el orden estuvo alrededor de 2 como se esperaba.

Aún así, en la segunda función esperabamos un orden de convergencia más cercano a 2 ya que se supone que este método mantiene convergencia cuadrática incluso cuando la raíz es múltiple. Es posible que esto se deba a la poca cantidad de iteraciones realizadas.

#### Secante

La primera función tuvo un orden promediando entre 1 y 2, como es esperado, ya que debe ser mayor a 1 pero menor a 2 al aproximar la derivada con una recta secante.

Para la segunda función, se mantuvo principalmente en 1 una vez más debido a la raíz múltiple de esta.

Para la tercera, el orden oscilo en valores alrededor de 2, lo cual es un poco superior a lo esperado pero sigue estando dentro de un rango esperado.

###Gráficos de constante asintótica

```python
graficar(constante_asintotica,  nombre = 'constante asintótica', min = 0, max = 1)
```

#### Bisección

En este caso, la constante asintótica es la esperada ya que como el orden de convergencia es 1, esto significa que $\varepsilon_n = 0,5 \cdot \varepsilon_{n-1}$, lo cual es exactamente lo que hace el método: va dividiendo a la mitad el intervalo de búsqueda, por lo que el error lo hace también.

#### Demás métodos

En cuanto a los otros métodos, la mayoría de las iteraciones lograron una constante asintótica entre 0 y 1, que es lo esperado, ya que implica que el error esta en efecto bajando con el orden de convergencia calculado.

Forzamos la escala del eje y entre 0 y 1 para que se pueda apreciar esto, ya que había ciertas iteraciones con picos muy pronunciados que hacían que la escala se agrande mucho y parezca que la constante valía 0 en la mayoría de las iteraciones. Esto provocó que los gráficos de Newton-Raphson y Newton-Raphson modificado queden vacíos para la tercera función, ya que estaban dando valores por fuera de este rango.

En aquellas iteraciones que se van de rango y en caso de las iteraciones con picos muy pronunciados, es bastante probable que se deban a la poca cantidad de iteraciones que se necesitaron para alcanzar el error buscado, lo que hace que las aproximaciones realizadas para el cálculo de la constante asintótica, que incluso arrastra también el error del orden de convergencia, no sean tan buenas. Calcular el valor real implicaría un limite con iteraciones tendiendo a infinito.


```python
# Erasmus+ ICCT project (2018-1-SI01-KA203-047081)

# Toggle cell visibility

from IPython.display import HTML
tag = HTML('''
Toggle cell visibility <a href="javascript:code_toggle()">here</a>.''')
display(tag)

# Hide the code completely

# from IPython.display import HTML
# tag = HTML('''<style>
# div.input {
#     display:none;
# }
# </style>''')
# display(tag)
```

Toggle cell visibility here.

```python
import sympy as sp # Symbolic Python
import numpy as np # Arrays, matrices and corresponding mathematical operations
from IPython.display import Latex, display, Markdown, clear_output # For displaying Markdown and LaTeX code
from ipywidgets import widgets # Interactivity module
from IPython.display import Javascript

# Function for the conversion of array/matrix to LaTeX/Markdown format.
def vmatrix(a):
    if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
    lines = str(a).replace('[', '').replace(']', '').splitlines()
    rv = [r'\begin{vmatrix}']
    rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
    rv +=  [r'\end{vmatrix}']
    return '\n'.join(rv)
```

## Routhov in Hurwitzov kriterij stabilnosti

V teoriji krmiljenja Routh-Hurwitzov kriterij stabilnosti je matematični test, ki se uporablja za detekcijo polov prenosne funkcije zaprtozančnega sistema, ki imajo pozitivne realne komponente. Število sprememb predznakov elementov v prvem stolpcu Routhovega razporeda podaja število polov, ki ležijo v desni polovici kompleksne ravnine. Zadosten in potreben pogoj stabilnosti lineranih časovno nespremenljivih sistemov je ta, da imajo vsi poli zaprtozančnega sistema negativne realne komponente. To pomeni, da ne sme priti do sprememb predznakov elementov v prvem stolpcu omenjenega razporeda. Podoben kriterij stabilnosti temelji na determinantah sistema, ki ja imenujemo Hurwitzov kriterij stabilnost.

Začetna točka za določanje stabilnosti sistema je karakteristični polinom, definiran kot:

\begin{equation}
    a_ns^n+a_{n-1}s^{n-1}+...+a_1s+a_0
\end{equation}

V primeru Routhovega kriterija zapišemo ti. Routhov razpored:

\begin{array}{l|ccccc}
     & 1 & 2 & 3 & 4 & 5 \\
     \hline
    s^n & a_n & a_{n-2} & a_{n-4} & a_{n-6} & \dots \\
    s^{n-1} & a_{n-1} & a_{n-3} & a_{n-5} & a_{n-7} &\dots \\
    s^{n-2} & b_1 & b_2 & b_3 & b_4 & \dots \\
    s^{n-3} & c_1 & c_2 & c_3 & c_4 & \dots \\
    s^{n-4} & d_1 & d_2 & d_3 & d_4 & \dots \\
    \vdots & \vdots & \vdots & \vdots & \vdots & \ddots\\
\end{array}

Koeficiente prvih dveh vrstic ($a_i$) dobimo iz karakterističnega polnima. Vse ostale koeficiente določimo z uporabo naslednjih formul:

\begin{array}{cccc}
    \,  \! \! \! \! b_1 \! = \! \frac{a_{n-1}a_{n-2}-a_n a_{n-3}}{a_{n-1}} & \! \!  \! \! \,  \! \! b_2 \!  = \!  \frac{a_{n-1}a_{n-4}-a_n a_{n-5}}{a_{n-1}} & \,  \! \! b_3 \! = \! \frac{a_{n-1}a_{n-6}-a_n a_{n-7}}{a_{n-1}} & \, \! \! \! \! \dots \\
     c_1=\frac{b_1a_{n-3}-a_{n-1} b_2}{b_1} & c_2=\frac{b_1a_{n-5}-a_{n-1}b_3}{b_1} & c_3=\frac{b_1a_{n-7}-a_{n-1}b_4}{b_1} & \, \! \! \! \! \dots \\
     d_1=\frac{c_1 b_2-b_1 c_2}{c_1} & d_2=\frac{c_1 b_3-b_1 c_3}{c_1} & d_3=\frac{c_1 b_4-b_1 c_4}{c_1} & \, \! \! \! \! \dots \\
    \vdots & \vdots & \vdots & \, \! \! \! \! \ddots \\
\end{array}

Če imajo vsi koeficienti v prvem stolpcu (koeficienti $n+1$) enak predznak (bodisi vsi pozitivnega ali vsi negativnega), je sistem stabilen. Število sprememb predznakov koeficientov v prvem stolpcu podaja število ničel karakterističnega polinoma, ki ležijo v levi polovici kompleksne ravnine.

V primeru Hurwitzovega kriterija najprej zapišemo determinanto $\Delta_n$ oblike $n\times n$ na podlagi karakterističnega polinoma.

\begin{equation}
    \Delta_n=
    \begin{array}{|cccccccc|}
        a_{n-1} & a_{n-3} & a_{n-5} & \dots & \left[ \begin{array}{cc} a_0 & \mbox{če je
        }n \mbox{ liho št.} \\ a_1 & \mbox{če je }n \mbox{ sodo št.} \end{array}
        \right] & 0 & \dots & 0  \\[3mm]
        a_{n} & a_{n-2} & a_{n-4} & \dots & \left[ \begin{array}{cc} a_1 & \mbox{če je }n \mbox{ liho št.} \\ a_0 & \mbox{če je }n \mbox{ sodo št.} \end{array} \right] & 0 & \dots & 0 \\
        0 & a_{n-1} & a_{n-3} & a_{n-5} & \dots &  \dots & \dots & 0 \\
        0 & a_{n} & a_{n-2} & a_{n-4} & \dots &  \dots & \dots & 0 \\
        0 & 0 & a_{n-1} & a_{n-3} & \dots &  \dots & \dots & 0 \\
        0 & 0 & a_{n} & a_{n-2} & \dots &  \dots & \dots & 0 \\
        \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots & \vdots \\
        0 & \dots & \dots & \dots & \dots & \dots & \dots & a_0 \\
    \end{array}
\end{equation}

Na podlagi determinante $\Delta_n$ tvorimo poddeterminante po glavni diagonali. Subdeterminanto $\Delta_1$ tako zapišemo kot

\begin{equation}
    \Delta_1=a_{n-1},
\end{equation}

subdterminanto $\Delta_2$ kot

\begin{equation}
    \Delta_2=
    \begin{array}{|cc|}
    a_{n-1} & a_{n-3} \\
    a_{n} & a_{n-2} \\
    \end{array},
\end{equation}

in subdeterminanto $\Delta_3$ kot

\begin{equation}
    \Delta_3=
    \begin{array}{|ccc|}
    a_{n-1} & a_{n-3} & a_{n-5} \\
    a_{n} & a_{n-2} & a_{n-4} \\
    0 & a_{n-1} & a_{n-3} \\
    \end{array}.
\end{equation}

Tako nadaljujemo vse dokler ne pridemo do subdeterminante $\Delta_{n-1}$. Sistem je stabilen, če so vse subdeterminante po glavni diagonali (od $\Delta_1$ do $\Delta_{n-1}$) ter determinanta $\Delta_n$ strogo večje od 0.

---

### Kako upravljati s tem interaktivnim primerom?

Najprej definiraj želen karakteristični polinom, z izbiro njegove stopnje ter vrednosti koeficientov, nato pa izberi želen kriterij stabilnosti (Routhov ali Hurwitzov).

```python
polynomialOrder = input ("Vnesi stopnjo karakterističnega polinoma (pritisni Enter za potrditev):")
try:
    val = int(polynomialOrder)
except ValueError:
    display(Markdown('Stopnja polinoma mora biti pozitivno celo število. Prosim ponovno vnesi stopnjo.'))
display(Markdown('Vnesi koeficiente karakterističnega polinoma (uporabi $K$ za nedoločne koeficiente) in klikni na gumb "Potrdi".'))
text=[None]*(int(polynomialOrder)+1)
for i in range(int(polynomialOrder)+1):
    text[i]=widgets.Text(description=('$s^%i$'%(-(i-int(polynomialOrder)))))
    display(text[i])
btn1=widgets.Button(description="Potrdi")
btnReset=widgets.Button(description="Ponastavi")
display(widgets.HBox((btn1, btnReset)))

btn2=widgets.Button(description="Potrdi")
w=widgets.Select(
    options=['Routh', 'Hurwitz'],
    rows=3,
    description='Izberi:',
    disabled=False
)

coef=[None]*(int(polynomialOrder)+1)

def on_button_clickedReset(ev):
    display(Javascript("Jupyter.notebook.execute_cells_below()"))

def on_button_clicked1(btn1):
    clear_output()
    for i in range(int(polynomialOrder)+1):
        if text[i].value=='' or text[i].value=='Vnesi koeficient':
            text[i].value='Vnesi koeficient'
        else:
            try:
                coef[i]=float(text[i].value)
            except ValueError:
                if text[i].value!='' or text[i].value!='Vnesi koeficient':
                    coef[i]=sp.var(text[i].value)
    coef.reverse()
    enacba="$"
    for i in range (int(polynomialOrder),-1,-1):
        if i==int(polynomialOrder):
            enacba=enacba+str(coef[i])+"s^"+str(i)
        elif i==1:
            enacba=enacba+"+"+str(coef[i])+"s"
        elif i==0:
            enacba=enacba+"+"+str(coef[i])+"$"
        else:
            enacba=enacba+"+"+str(coef[i])+"s^"+str(i)
    coef.reverse()
    display(Markdown('Izbran karakterisitčni polinom je enak:'), Markdown(enacba))
    display(Markdown('Ali bi uporabil Routhov ali Hurwitzov kriterij stabilnosti?'))
    display(w)
    display(widgets.HBox((btn2, btnReset)))
    display(out)

def on_button_clicked2(btn2):

    if w.value=='Routh':

        s=np.zeros((len(coef), len(coef)//2+(len(coef)%2)),dtype=object)
        xx=np.zeros((len(coef), len(coef)//2+(len(coef)%2)),dtype=object)
        check_index=0

        if len(s[0]) == len(coef[::2]):
            s[0] = coef[::2]
        elif len(s[0])-1 == len(coef[::2]):
            s[0,:-1] = coef[::2]
        #soda mesta
        if len(s[1]) == len(coef[1::2]):
            s[1] = coef[1::2]
        elif len(s[1])-1 == len(coef[1::2]):
            s[1,:-1] = coef[1::2]

        for i in range(len(s[2:,:])):
            i+=2
            for j in range(len(s[0,0:-1])):
                s[i,j] = (s[i-1,0]*s[i-2,j+1]-s[i-2,0]*s[i-1,j+1]) / s[i-1,0]
                if s[i,0] == 0:
                    epsilon=sp.Symbol('\u03B5')
                    s[i,0] = epsilon
                    check_index=1

        if check_index==1:
            for i in range(len(s)):
                for j in range(len(s[0])):
                    xx[i,j] = sp.limit(s[i,j],epsilon,0)

            positive_check=xx[:,0]>0
            negative_check=xx[:,0]<0
            if all(positive_check)==True:
                with out:
                    clear_output()
                    display(Markdown('En izmed elementov v prvem stolpcu Routhovega razporeda je enak 0. Nadomestimo ga z $\epsilon$ in opazujemo kaj se dogaja s predzanki ko gre vrednost $\epsilon$ proti 0.'))
                    display(Markdown('Routhov razpored $%s$\n' % vmatrix(s)))
                    display(Markdown('Sistem je stabilen, ker so vsi predznaki koeficientov v prvem stolpcu Routhovega razporeda pozitivni.'))
                    display(Markdown('Routhov razpored $%s$\n' % vmatrix(xx)))

            elif all(negative_check)==True:
                with out:
                    clear_output()
                    display(Markdown('En izmed elementov v prvem stolpcu Routhovega razporeda je enak 0. Nadomestimo ga z $\epsilon$ in opazujemo kaj se dogaja s predzanki ko gre vrednost $\epsilon$ proti 0-'))
                    display(Markdown('Routhov razpored $%s$\n' % vmatrix(s)))
                    display(Markdown('Sistem je stabilen, ker so vsi predznaki koeficientov v prvem stolpcu Routhovega razporeda negativni.'))
                    display(Markdown('Routhov razpored $%s$\n' % vmatrix(xx)))
            else:
                with out:
                    clear_output()
                    display(Markdown('One of the elements in the first column of the Routh table is equal to 0. We replace it with $\epsilon$ and observe the values of the elements when value of $\epsilon$ goes to zero.'))
                    display(Markdown('Routhov razpored $%s$\n' % vmatrix(s)))
                    display(Markdown('Sistem je nestabilen, ker se spreminja predznak koeficientov v prvem stolpcu Routhovega razporeda.'))
                    display(Markdown('Routhov razpored $%s$\n' % vmatrix(xx)))

        elif check_index==0:

            if all(isinstance(x, (int,float)) for x in coef):
                positive_check=s[:,0]>0
                negative_check=s[:,0]<0
                if all(positive_check)==True:
                    with out:
                        clear_output()
                        display(Markdown('Sistem je stabilen, ker so vsi predznaki koeficientov v prvem stolpcu Routhovega razporeda pozitivni.'))
                        display(Markdown('Routhov razpored $%s$' % vmatrix(s)))
                elif all(negative_check)==True:
                    with out:
                        clear_output()
                        display(Markdown('Sistem je stabilen, ker so vsi predznaki koeficientov v prvem stolpcu Routhovega razporeda negativni.'))
                        display(Markdown('Routhov razpored $%s$' % vmatrix(s)))
                else:
                    with out:
                        clear_output()
                        display(Markdown('Sistem je nestabilen, ker se spreminja predznak koeficientov v prvem stolpcu Routhovega razporeda.'))
                        display(Markdown('Routhov razpored $%s$' % vmatrix(s)))

            else:
                testSign=[]
                for i in range(len(s)):
                    if isinstance(s[i,0],(int,float)):
                        testSign.append(s[i,0]>0)
                solution=[]
                if all(elem == True for elem in testSign):
                    for x in s[:,0]:
                        if not isinstance(x,(sp.numbers.Integer,sp.numbers.Float,int,float)):
                            solution.append(sp.solve(x>0,K)) # Define the solution for each value of the determinant
                    with out:
                        clear_output()
                        display(Markdown('Routhov razpored $%s$' % vmatrix(s)))
                        display(Markdown('Vsi določni koeficienti v prvem stolpcu so negativne, zato je sistem stabilen za::'))
                        print(solution)
                elif all(elem == False for elem in test):
                    for x in s[:,0]:
                        if not isinstance(x,(sp.numbers.Integer,sp.numbers.Float,int,float)):
                            solution.append(sp.solve(x<0,K)) # Define the solution for each value of the determinant
                    with out:
                        clear_output()
                        display(Markdown('Routhov razpored $%s$' % vmatrix(s)))
                        display(Markdown('Vsi določni koeficienti v prvem stolpcu so negativne, zato je sistem stabilen za:'))
                        print(solution)
                else:
                    with out:
                        display(Markdown('Routhov razpored $%s$' % vmatrix(s)))
                        display(Markdown('Sistem je nestabilen, ker se spreminja predznak koeficientov v prvem stolpcu.'))

    elif w.value=='Hurwitz':

        # Check if all the coefficients are numbers or not and preallocate basic determinant.

        if all(isinstance(x, (int,float)) for x in coef):
            determinant=np.zeros([len(coef)-1,len(coef)-1])
        else:
            determinant=np.zeros([len(coef)-1,len(coef)-1],dtype=object)

        # Define the first two rows of the basic determinant.
        for i in range(len(coef)-1):
            try:
                determinant[0,i]=coef[2*i+1]
            except:
                determinant[0,i]=0

        for i in range(len(coef)-1):
            try:
                determinant[1,i]=coef[2*i]
            except:
                determinant[1,i]=0
        # Define the remaining rows of the basic determinant by shifting the first two rows.
        for i in range(2,len(coef)-1):
            determinant[i,:]=np.roll(determinant[i-2,:],1)
            determinant[2:,0]=0

        # Define all the subdeterminants.
        subdet=[];
        for i in range(len(determinant)-1):
            subdet.append(determinant[0:i+1,0:i+1])

        # Append the basic determinant to the subdeterminants' array.
        subdet.append(determinant)

        # Check if all coefficients are numbers.
        if all(isinstance(x, (int,float)) for x in coef):
            det_value=[] # Preallocate array containing values of all determinants.
            for i in range(len(subdet)):
                det_value.append(np.linalg.det(subdet[i])); # Calculate determinant and append the values to det_value.

            if all(i > 0 for i in det_value)==True: # Check if all values in det_value are positive or not.
                with out:
                    clear_output()
                    display(Markdown('Sistem je stabilen, ker so vse determinante pozitivne.'))
                    for i in range(len(subdet)):
                        display(Markdown('$\Delta_{%i}=$'%(i+1) + '$%s$' %vmatrix(subdet[i]) + '$=%s$' %det_value[i]))
            else:
                with out:
                    clear_output()
                    display(Markdown('Sistem je nestabilen, ker niso vse determinante pozitivne.'))
                    for i in range(len(subdet)):
                        display(Markdown('$\Delta_{%i}=$'%(i+1) + '$%s$' %vmatrix(subdet[i]) + '$=%s$' %det_value[i]))
        else:
            subdetSym=[] # Preallocate subdetSym.
            det_value=[] # Preallocate det_value.
            solution=[] # Preallocate solution.
            for i in subdet:
                subdetSym.append(sp.Matrix(i)) # Transform matrix subdet to symbolic.
            for i in range(len(subdetSym)):
                det_value.append(subdetSym[i].det()) # Calculate the value of the determinant.
            testSign=[]
            for i in range(len(det_value)):
                if isinstance(s[i,0],(int,float,sp.numbers.Integer,sp.numbers.Float)):
                    testSign.append(s[i,0]>0)
            if all(elem == True for elem in testSign):
                solution=[]
                for x in det_value:
                    if not isinstance(x,(sp.numbers.Integer,sp.numbers.Float,int,float)):
                        solution.append(sp.solve(x>0,K)) # Define the solution for each value of the determinant
                for i in range(len(subdet)):
                    with out:
                        clear_output()
                        display(Markdown('$\Delta_{%i}=$'%(i+1) + '$%s$' %vmatrix(subdet[i]) + '$=%s$' %det_value[i]))
                display(Markdown('Sistem je stabilen za:'))
                print(solution)

            else:
                with out:
                    clear_output()
                    display(Markdown('Sistem je nestabilen, ker vse determinante niso pozitivne.'))
                for i in range(len(subdet)):
                    display(Markdown('$\Delta_{%i}=$'%(i+1) + '$%s$' %vmatrix(subdet[i]) + '$=%s$' %det_value[i]))

global out
out=widgets.Output()

btn3=widgets.Button(description="Ponastavivse")
w=widgets.Select(
    options=['Routh', 'Hurwitz'],
    rows=3,
    description='Izberi:',
    disabled=False
)

btn1.on_click(on_button_clicked1)
btn2.on_click(on_button_clicked2)
btnReset.on_click(on_button_clickedReset)
```

    <IPython.core.display.Javascript object>