https://lcvmwww.epfl.ch/~lcvm/dna_teaching_05_06/exercises/ex5.pdf

$$E(\theta, \phi, \lambda) = \frac12 \theta^2 + \frac12(\phi - \theta)^2 + \lambda(\cos\theta + \cos\phi)$$

```python
from math import sin, cos
def energy(theta, phi, lam):
    return 0.5 * theta ** 2 + 0.5 * (phi - theta)**2 + lam * (cos(theta) + cos(phi))
```

The equilibrium condition is given by
$$0 = \frac{\partial E}{\partial \theta} = \theta + \theta - \phi - \lambda\sin\theta = 2\theta-\phi- \lambda\sin\theta,\quad
  0 = \frac{\partial E}{\partial \phi} = \phi - \theta - \lambda\sin\phi$$

```python
def F(theta, phi, lam):
    return 2*theta - phi - lam * sin(theta), phi - theta - lam * sin(phi)
```

The Jacobian of F is given by
$$J = \begin{pmatrix}2-\lambda\cos(\theta) & -1\\-1 & 1-\lambda\cos\phi\end{pmatrix}$$

```python
import numpy as np
def J(theta, phi, lam):
    return np.array([[2-lam*cos(theta), -1], [-1, 1-lam*cos(phi)]])
J(0,0,0)
```

    array([[ 2., -1.],
           [-1.,  1.]])

In the case of the straight rod (\theta = \phi = 0) J(0,0,\lambda) is singular when $$\lambda^2 - 3\lambda + 1 = 0$$
thus when $\lambda = \frac12(3\pm\sqrt5)$

```python
J(0,0,0.5*(3-5**.5))
```

    array([[ 1.61803399, -1.        ],
           [-1.        ,  0.61803399]])

```python
J(0,0,0.5*(3+5**.5))
```

    array([[-0.61803399, -1.        ],
           [-1.        , -1.61803399]])

The null spaces are spanned by $(1, \frac12(\sqrt5+1))$ and $(\frac12(\sqrt5+1), -1)$ respectively

```python
J(0,0,0.5*(3-5**.5)) @ np.array([1,0.5*(5**0.5+1)]), J(0,0,0.5*(3+5**.5)) @ np.array([0.5*(5**0.5+1), -1])
```

    (array([0.00000000e+00, 2.22044605e-16]),
     array([-2.22044605e-16,  0.00000000e+00]))

## Stability
The hessian is identical to J in this case. Its eigenvalues are given by
$$\mu_1\mu_2 = \det J(0,0,\lambda) = \lambda^2 - 3\lambda+1\quad\text{and}$$
$$\mu_1 + \mu_2 = \operatorname{tr}J(0,0,\lambda) = 3-2\lambda$$
Thus for $\lambda < \frac12(3-\sqrt5)$ and $\lambda > \frac12(3+\sqrt5)$ the eigenvalues have the same sign, and in between they have opposite signs. For $\lambda < \frac12(3-\sqrt5)$ their sum is positive thus the two eigenvalues are positive and for $\lambda > \frac12(3+\sqrt5)$ their sum is negative and thus they are both negative.

```python
np.linalg.eig(J(0,0,0.3))
```

    (array([2.31803399, 0.08196601]),
     array([[ 0.85065081,  0.52573111],
            [-0.52573111,  0.85065081]]))

```python
np.linalg.eig(J(0,0,1.5))
```

    (array([ 1.11803399, -1.11803399]),
     array([[ 0.85065081,  0.52573111],
            [-0.52573111,  0.85065081]]))

```python
np.linalg.eig(J(0,0,2.7))
```

    (array([-0.08196601, -2.31803399]),
     array([[ 0.85065081,  0.52573111],
            [-0.52573111,  0.85065081]]))

```python
np.linalg.eig(J(0,0,0.5*(3-5**.5)))
```

    (array([2.23606798e+00, 1.11022302e-16]),
     array([[ 0.85065081,  0.52573111],
            [-0.52573111,  0.85065081]]))

# Bifurcation Shape

```python
from sympy import *
eps, phi, the, lam, E = symbols('ε φ θ λ E')
```

```python
phi_ = symbols(['φ_%d' % i for i in range(4)])
the_ = symbols(['θ_%d' % i for i in range(4)])
lam_ = symbols(['λ_%d' % i for i in range(4)])
phi_eps = sum(eps**i * phi_[i] for i in range(4))
the_eps = sum(eps**i * the_[i] for i in range(4))
lam_eps = sum(eps**i * lam_[i] for i in range(4))
the_eps
```

$\displaystyle ε^{3} θ_{3} + ε^{2} θ_{2} + ε θ_{1} + θ_{0}$

```python
E = (the**2 + (phi-the)**2)/2 + lam*(cos(phi)+cos(the))
```

```python
F1, F2 = diff(E, the), diff(E, phi)
```

```python
F1_eps = F1.subs([(the, the_eps), (phi, phi_eps), (lam, lam_eps)])
```

```python
F1_eps.subs(eps,0)
```

$\displaystyle 2 θ_{0} - λ_{0} \sin{\left(θ_{0} \right)} - φ_{0}$

```python
diff(F1_eps, eps).subs(eps, 0)
```

$\displaystyle - θ_{1} λ_{0} \cos{\left(θ_{0} \right)} + 2 θ_{1} - λ_{1} \sin{\left(θ_{0} \right)} - φ_{1}$

```python
conditions = [
    diff(diff(E, x).subs([(the, the_eps), (phi, phi_eps), (lam, lam_eps)]), eps, i).subs(eps, 0) * factorial(i)
    for i in range(4)
    for x in (phi, the)
]; conditions
```

    [-θ_0 - λ_0*sin(φ_0) + φ_0,
     2*θ_0 - λ_0*sin(θ_0) - φ_0,
     -θ_1 - λ_0*φ_1*cos(φ_0) - λ_1*sin(φ_0) + φ_1,
     -θ_1*λ_0*cos(θ_0) + 2*θ_1 - λ_1*sin(θ_0) - φ_1,
     -4*θ_2 + 2*λ_0*φ_1**2*sin(φ_0) - 4*λ_0*φ_2*cos(φ_0) - 4*λ_1*φ_1*cos(φ_0) - 4*λ_2*sin(φ_0) + 4*φ_2,
     2*θ_1**2*λ_0*sin(θ_0) - 4*θ_1*λ_1*cos(θ_0) - 4*θ_2*λ_0*cos(θ_0) + 8*θ_2 - 4*λ_2*sin(θ_0) - 4*φ_2,
     -36*θ_3 + 6*λ_0*φ_1**3*cos(φ_0) + 36*λ_0*φ_1*φ_2*sin(φ_0) - 36*λ_0*φ_3*cos(φ_0) + 18*λ_1*φ_1**2*sin(φ_0) - 36*λ_1*φ_2*cos(φ_0) - 36*λ_2*φ_1*cos(φ_0) - 36*λ_3*sin(φ_0) + 36*φ_3,
     6*θ_1**3*λ_0*cos(θ_0) + 18*θ_1**2*λ_1*sin(θ_0) + 36*θ_1*θ_2*λ_0*sin(θ_0) - 36*θ_1*λ_2*cos(θ_0) - 36*θ_2*λ_1*cos(θ_0) - 36*θ_3*λ_0*cos(θ_0) + 72*θ_3 - 36*λ_3*sin(θ_0) - 36*φ_3]

```python
cond_1 = [c.subs([(the_[0], 0), (phi_[0],0), (lam_[0], (3-sqrt(5))/2)]) for c in conditions]; cond_1
```

    [0,
     0,
     -θ_1 - φ_1*(3/2 - sqrt(5)/2) + φ_1,
     -θ_1*(3/2 - sqrt(5)/2) + 2*θ_1 - φ_1,
     -4*θ_2 - 4*λ_1*φ_1 - 4*φ_2*(3/2 - sqrt(5)/2) + 4*φ_2,
     -4*θ_1*λ_1 - 4*θ_2*(3/2 - sqrt(5)/2) + 8*θ_2 - 4*φ_2,
     -36*θ_3 - 36*λ_1*φ_2 - 36*λ_2*φ_1 + 6*φ_1**3*(3/2 - sqrt(5)/2) - 36*φ_3*(3/2 - sqrt(5)/2) + 36*φ_3,
     6*θ_1**3*(3/2 - sqrt(5)/2) - 36*θ_1*λ_2 - 36*θ_2*λ_1 - 36*θ_3*(3/2 - sqrt(5)/2) + 72*θ_3 - 36*φ_3]

```python
from sympy.solvers.solveset import linsolve
linsolve(cond_1[:4], (the_[1], phi_[1]))

```

$\displaystyle \left\{\left( \frac{φ_{1} \left(-1 + \sqrt{5}\right)}{2}, \  φ_{1}\right)\right\}$

```python
cond_2 = [c.subs([(phi_[1],1), (the_[1], (sqrt(5)-1)/2)]).simplify() for c in cond_1]
#cond_2[4] /= 2
#cond_2[5] /= (1+sqrt(5))
#cond_2[5] = cond_2[5].simplify()
cond_2
```

    [0,
     0,
     0,
     0,
     -4*θ_2 - 4*λ_1 - 2*φ_2 + 2*sqrt(5)*φ_2,
     2*θ_2 + 2*sqrt(5)*θ_2 - 2*sqrt(5)*λ_1 + 2*λ_1 - 4*φ_2,
     -36*θ_3 - 36*λ_1*φ_2 - 36*λ_2 - 18*φ_3 + 18*sqrt(5)*φ_3 - 3*sqrt(5) + 9,
     -36*θ_2*λ_1 + 18*θ_3 + 18*sqrt(5)*θ_3 - 18*sqrt(5)*λ_2 + 18*λ_2 - 36*φ_3 - 33 + 15*sqrt(5)]

```python
linsolve(cond_2[:6], (the_[2], phi_[2], lam_[1]))
```

$\displaystyle \left\{\left( \frac{φ_{2} \left(-1 + \sqrt{5}\right)}{2}, \  φ_{2}, \  0\right)\right\}$

```python
cond_3 = [c.subs([(phi_[2],1), (the_[2], (sqrt(5)-1)/2), (lam_[1], 0)]).simplify() for c in cond_2]
cond_3
```

    [0,
     0,
     0,
     0,
     0,
     0,
     -36*θ_3 - 36*λ_2 - 18*φ_3 + 18*sqrt(5)*φ_3 - 3*sqrt(5) + 9,
     18*θ_3 + 18*sqrt(5)*θ_3 - 18*sqrt(5)*λ_2 + 18*λ_2 - 36*φ_3 - 33 + 15*sqrt(5)]

```python
linsolve(cond_3, (the_[3], phi_[3], lam_[2]))
```

$\displaystyle \left\{\left( - \frac{φ_{3} \left(1 - \sqrt{5}\right)}{2} - \frac{1}{4} + \frac{7 \sqrt{5}}{60}, \  φ_{3}, \  \frac{1}{2} - \frac{\sqrt{5}}{5}\right)\right\}$

```python

```

```python

```

```python

```

```python

```

```python

```


## Ejemplo 2-2
Se requiere determinar las impedancias del circuito equivalente de un transformador de 20 kVA, 8000/240V, 60Hz. La prueba de circuito abierto se realizó en la prueba de circuito secundario del transformador (para reducir el voltaje máximo que se desea medir), y la prueba de cortocircuito se realizó en el lado primario (para reducir la máxima corriente que se tenía que medir). Se tomaron los siguientes datos:

| Prueba de circuito abierto  | Prueba de cortocircuito  |
|------|------|
| (en el secundario)          | (en el secundario)       |
| $V_{Cab} = 240 \,\mathrm{V}$ | $V_{CC} = 489 \, \mathrm{V}$ |
| $I_{Cab} = 7,133 \, \mathrm{A}$ | $I_{CC} = 2,5 \, \mathrm{A}$ |
| $P_{CAb} = 400 \, \mathrm{W}$ | $P_{CC} = 240 \, \mathrm{W}$ |

Encuentre las impedancias del circuito equivalente referido al lado primario y dibuje el circuito.

### Solución
La razón de vueltas de este transformador es $a = 8000/240$. El factor de potencia durante la prueba de ciruito abierto es:

$$ \mathrm{fp} = \cos \theta = \frac{P_{Cab}}{V_{Cab} I_{Cab}} $$

```python
import numpy as np
import cmath
```

```python
# Datos:
Vcab = 240   ; Vcc = 489 # V
Icab = 7.133 ; Icc = 2.5 # A
Pcab = 400   ; Pcc = 240 # W
a = 8000/240
```

```python
fp_ca = Pcab/(Vcab*Icab)
```

```python
# La admitancia de exitación está dada por
Ye = cmath.rect( Icab/Vcab , -np.arccos(fp_ca) )
```

```python
print('Ye = (%.4f ∠ %.2f° S)'%(abs(Ye), np.rad2deg(np.angle(Ye)) ))
print('Ye = {:.5f} S'.format(Ye))
```

    Ye = (0.0297 ∠ -76.49° S)
    Ye = 0.00694-0.02890j S

$$ Y_e = \frac{1}{R_{N}} - j \frac{1}{X_M} $$

```python
RNS = abs(1/Ye.real)
XMS = abs(1/Ye.imag)
```

```python
print('RNS = %.1f Ohm'%RN)
print('XMS = %.2f Ohm'%XM)
```

    RNS = 144.0 Ohm
    XMS = 34.60 Ohm

```python
# El factor de potencia durante la prueba de cortocircuito es
fp_cc = Pcc/(Vcc*Icc)
```

```python
print('fp_cc = %.3f'%fp_cc)
```

    fp_cc = 0.196

```python
# La impedancia en serie está dada por
Zse = cmath.rect(Vcc/Icc,np.arccos(fp_cc))
```

```python
print('Zse = (%.1f ∠ %.2f°) Ohm'%(abs(Zse), np.rad2deg( np.angle(Zse) ) ))
print('Zse = {:.2f} Ohm'.format(Zse))
```

    Zse = (195.6 ∠ 78.68°) Ohm
    Zse = 38.40+191.79j Ohm

```python
Req = Zse.real
Xeq = Zse.imag
```

```python
print('Req = %.2f Ohm'%Req)
print('Xeq = %.2f Ohm'%Xeq)
```

    Req = 38.40 Ohm
    Xeq = 191.79 Ohm

El circuito equivalente simplificado resultante que se refiere al lado de alto voltaje (primario) se puede determinar convirtiendo los valores de la rama de excitación al lado de alto voltaje.

```python
RNP = a**2 * RNS
XMP = a**2 * XMS
```

```python
print('RNP = %.1f kOhm'%(RNP/1e3))
print('XMP = %.1f kOhm'%(XMP/1e3))
```

    RNP = 160.0 kOhm
    XMP = 38.4 kOhm

## Ejemplo 2-5
Se va a probar un transformador de 15kVA y 2300/230V para determinar los componentes de la rama de excitación, sus impedancias en serie y su regulación de voltaje. Se obtuvieron los siguientes datos de las pruebas realizadas al transformador:

$$\begin{array}{c|c}
\hline
\mbox{Prueba de circuito abierto} & \mbox{Prueba de cortocircuito} \\
\mbox{(lado de bajo voltaje)} & \mbox{(lado de alto voltaje)} \\
\hline
V_{CAb} = 230 \, V & V_{CC} = 47 \, V \\
I_{CAb} = 2,1 \, A & I_{CC} = 6,0 \, A \\
P_{CAb} = 50 \, W & P_{CC} = 160 \, W \\
\hline
\end{array}$$

1. Encuentre el circuito equivalente de este transformador referido al lado de alto voltaje.
2. Encuentre el circuito equivalente de este transformador referido al lado de bajo voltaje.
3. Calcule la regulación de voltaje a plena carga con un factor de potencia de 0,8 en retraso, un factor de potencia de 1,0 y un factor de potencia de 0,8 en adelanto usando la ecuación la ecuación exacta para $V_p$.
4. Haga una gráfica de la regualción de voltaje conforme se incrementa la carga desde vacío hasta plena carga con factores de potencia de 0,8 en retraso y 1,0 y 0,8 en adelanto.
5. ¿Cuál es la eficiencia del transformador en plena carga con un factor de potencia de 0,8 en retraso?

### Solución
1. La relación de espiras de este transformador es a=2300/230=10. Los valores de la rama de excitación equivalente del transformador con referencia al lado secundario (de bajo voltaje) se pueden calcular a partir de los datos de la prueba de cortocircuito. A partir de los datos de la prueba de circuito abierto, el ángulo de impedancia de circuito abierto es

$$ \theta_{CAb} = \arccos \frac{P_{CAb}}{V_{CAb} I_{CAb}} $$

```python
import numpy as np
import cmath
```

```python
# Datos:
Vcab = 230 ; Vcc =  47  # V
Icab = 2.1 ; Icc =   6  # A
Pcab = 50  ; Pcc = 160  # W
a = 2300/230
```

```python
theta_cab = np.arccos( Pcab/(Vcab*Icab) )
# La admitancia de excitación es
YE = cmath.rect( Icab/Vcab , theta_cab )
# Los elementos de la rama de exitación
# referida al secundario son
RNS = 1/YE.real
XMS = 1/YE.imag
# Con los datos de la prueba de cortocircuito,
# el ángulo de impedancia de cortocirciuto es
theta_cc = np.arccos( Pcc/(Vcc*Icc) )
# La impedancia en serie equivalente
ZES = cmath.rect(Vcc/Icc , theta_cc)
ReqP = ZES.real
XeqP = ZES.imag
RNP = a**2 * RNS
XMP = a**2 * XMS
```

```python
print('theta_cab = %.2f°'%np.rad2deg(theta_cab))
print('YE = (%.5f ∠ %.2f°) S'%(abs(YE),np.rad2deg(theta_cab)))
print('YE = {:.6f} S'.format(YE))
print('RNS = %.1f Ohm'%RNS)
print('XMS = %.1f Ohm'%XMS)
print('theta_cc = %.2f°'%np.rad2deg(theta_cc))
print('ZES = (%.3f ∠ %.2f°) Ohm'%(abs(ZES),np.rad2deg(theta_cc)))
print('ZES = {:.2f} Ohm'.format(ZES))
print('ReqP = %.2f Ohm'%ReqP)
print('XeqP = %.2f Ohm'%XeqP)
print('RNP = %.2f kOhm'%(RNP/1e3) )
print('XMP = %.2f kOhm'%(XMP/1e3) )
```

    theta_cab = 84.06°
    YE = (0.00913 ∠ 84.06°) S
    YE = 0.000945+0.009081j S
    RNS = 1058.0 Ohm
    XMS = 110.1 Ohm
    theta_cc = 55.43°
    ZES = (7.833 ∠ 55.43°) Ohm
    ZES = 4.44+6.45j Ohm
    ReqP = 4.44 Ohm
    XeqP = 6.45 Ohm
    RNP = 105.80 kOhm
    XMP = 11.01 kOhm

---------
2) Para encontrar el circuito equivalente referido al lado de bajo voltaje, simplemente se requiere dividir la impedancia entre $a^2$. Como $a = N_p/N_S=10$, los valores resultantes son

```python
RNS = RNP/a**2
XMS = XMP/a**2
ReqS = ReqP/a**2
XeqS = XeqP/a**2
ZeqS = complex(ReqS,XeqS)
```

```python
print('RNS = %.1f Ohm'%RNS)
print('XMS = %.1f Ohm'%XMS)
print('ReqS = %.4f Ohm'%ReqS)
print('XeqS = %.4f Ohm'%XeqS)
```

    RNS = 1058.0 Ohm
    XMS = 110.1 Ohm
    ReqS = 0.0444 Ohm
    XeqS = 0.0645 Ohm

---------
3) La corriente a plena carga en el lado secundario del transformador es

```python
SN = 15e3 # VA
VSN = 230 # V
```

```python
ISN = SN/VSN
phi = np.arccos(0.8)
IS = cmath.rect(ISN , phi)
```

```python
print('IS = (%.2f ∠ %.2f°) A'%(abs(IS), np.rad2deg(phi)))
```

    IS = (65.22 ∠ 36.87°) A

```python
Vs = 230 # V
Vpa = Vs + ReqS * IS + XeqS * IS
```

```python
print('Vpa = (%.2f ∠ %.2f°) V'%(abs(Vpa), np.rad2deg(np.angle(Vpa))))
print('Vpa = {:.2f} V'.format(Vpa))
```

    Vpa = (235.72 ∠ 1.04°) V
    Vpa = 235.68+4.26j V

```python
RV = (abs(Vpa) - Vs)/Vs * 100
```

```python
RV
```

    2.488189620533749

## Problema N°1
Un transformador monofásico de 380/110V cuya $S_N = 5 \, \mathrm{kVA}$. Ensayado con el devanado de baja tensión (o secundario) en cortocircuito; se debe aplicar 33V al lado primario para que circule corriente nominal, siendo la potencia consumida por la máquina $P_{CC} = 100,2 \, \mathrm{W}$.

1. Cuál será la regulación, cuando la carga aplicada tiene un factor de potencia del 0,8 en atraso, y consume la corriente nominal.
2. Y el rendimiento, cuando la carga aplicada tiene un factor de potencia del 0,5 en atraso, entregando su corriente nominal, y las pérdidas en el núcleo son de 80W.

### Solución

__ítem a)__

__Datos:__
$\left\{
\begin{array}{l}
S_N = 5000 \, \mathrm{VA} \\
U_{1N} = 380 \, \mathrm{V} \\
U_{2N} = 110 \, \mathrm{V} \\
U_{CC} = 33 \, \mathrm{V} \\
P_{CC} = 100,2 \, \mathrm{W} \\
\mathrm{fp} = 0,8
\end{array}
\right.$

__Cálculo de corriente nominal 1__
$$ I_{1N} = \frac{S_N}{U_{1N}} = 13,16 \, \mathrm{A} $$

__Cálculo de corriente nominal 2__
$$ I_{2N} = \frac{S_N}{U_{2N}} = 45.45 \, \mathrm{A} $$

__Cálculo de $\varphi_e$__
$$ P_{CC} = U_{CC} I_{1N} \cos \varphi_e $$

$$ \varphi_e = \frac{P_{CC}}{U_{CC} I_{1N}} = 78,7^\circ $$

__Cálculo de $\varphi_2$__
$$ \cos \varphi_2 = \mathrm{fp} = 0,8 $$

$$ \varphi_2 = \arccos 0,8 = 36,87^\circ $$

__Fasorial:__

Del diagrama fasorial se deduce:
$$ U_1^2 = [U'_2 + U'_e \cos (\varphi_{e} - \varphi_2)]^2 + [U'_e \sin (\varphi_e - \varphi_2)]^2 $$

Cálculo auxiliar

$\left\{
\begin{array}{l}
U'_e \cos (\varphi_e - \varphi_2) = 33 \cos (78,7 - 36,9) = 24,7 \, \mathrm{V} \\
U'_e \sin (\varphi_e - \varphi_2) = 33 \sin (78,7 - 36,9) = 21,9 \, \mathrm{V}
\end{array}
\right.$

Entonces

$$ 380^2 = (U'_2 + 24,7)^2 + (21,9)^2 $$

$$ (U'_2)^2 + 49,4 U'_2 - 143310 = 0 $$

$$ U'_2 = 354,7 \, \mathrm{V} $$

__El porcentaje de regulación resulta:__

$$ \varepsilon \% = \frac{U_1 - U'_2}{U_1} \times 100 $$

$$ \varepsilon \% = \frac{380 - 354,7}{380} \times 100 = 6,7\% $$

__ítem b)__

__Datos:__
$\left\{
\begin{array}{l}
\mathrm{fp}_b = 0,5 \\
P_0 = 80 \, \mathrm{W}
\end{array}
\right.$

$$I_2 = I'_2 \cdot a = 45,45 \, \mathrm{V}$$

$$ U_2 = \frac{U'_2}{a} = 102,65 \, \mathrm{V} $$

$$ \varphi_b = \arccos (\mathrm{fp}_b) = 60^\circ$$

$$ \eta = \frac{U_2 I_2 \cos \varphi_b}{U_2 I_2 \cos \varphi_b + P_{cc} + P_0} \times 100 = 92,8 \% $$

### Resolución en python:

```python
import numpy as np
```

```python
# Datos:
SN  =  5e3  # VA
U1N = 380   # V
U2N = 110   # V
Ucc =  33   # V
Pcc = 100.2 # W
fp  = 0.8
```

```python
# Corriente nominal primaria
I1N = SN/U1N
# Corriente nominal secundaria
I2N = SN/U2N
# (relación de transformación)
a = U1N/U2N
```

```python
phi_cc = np.arccos( Pcc/(Ucc*I1N) )
phi_2 = np.arccos(fp)
```

```python
print('phi_cc = %.2f°'%np.rad2deg(phi_cc))
print('phi_2 = %.2f°'%np.rad2deg(phi_2))
```

    phi_cc = 76.66°
    phi_2 = 36.87°

```python
import sympy as sp
```

```python
U2eq = sp.symbols('U2')
Eq = sp.Eq(U1N**2 , (U2eq + Ucc*sp.cos(phi_cc - phi_2))**2 + (Ucc*sp.sin(phi_cc - phi_2)**2) )
```

```python
Sol = sp.solve(Eq,U2eq)
```

```python
U2eq = Sol[1]
```

```python
print('U2eq = %.2f V'%U2eq)
```

    U2eq = 354.62 V

```python
reg = (U1N - U2)/U1N * 100
```

```python
print('reg = %.2f %%'%reg)
```

    reg = 6.68 %

```python
##########
# Item b
##########
I2eq = I1N
I2 = I2eq*a
U2 = U2eq/a
fp_b = 0.5
phi_b = np.arccos(fp_b)
Po = 80 # W

n = (U2*I2*np.cos(phi_b))/(U2*I2*np.cos(phi_b) + Po + Pcc) * 100
```

```python
print('n = %.1f %%'%n)
```

    n = 92.8 %

```python
%reset -s -f
```

## Problema N°2
Se desea conocer el valor más alto del rendimiento que puede alcanzar un transformador monofásico, con los siguientes valores nominales: $S_N = 14 \, \mathrm{kVA}$, $800/400 \, \mathrm{V}$, y conociendo que: $U_{CC} = 3 \, \%$, $P_{fe} = 95 \, \mathrm{W}$ y $P_{CC} = 180 \, \mathrm{W}$.

### Solución

__Datos:__
$\left\{
\begin{array}{l}
S_N = 14 \, \mathrm{kVA} \\
U_{N1} = 800 \, \mathrm{V} \\
U_{N2} = 400 \, \mathrm{V} \\
U_{cc} = 3 \% \\
P_{fe} = 95 \, \mathrm{W} \\
P_{cc} = 180 \, \mathrm{W}
\end{array}
\right.$

__Relación de transformación__
$$ a = \frac{U_{N1}}{U_{N2}} = 2 $$

__Corriente nominal primaria__
$$ I_{N1} = \frac{S_N}{U_{N1}} = 17,5 \, A $$

__Corriente nominal secundaria__
$$  I_{N2} = \frac{S_N}{U_{N2}} = 35 \, A $$

__Vacío:__

En el ensayo de vacío __se aplica la tensión nominal en el lado de baja tensión__ del transformador mientras que __el lado de alta tensión queda en circuito abierto.__

$$U_0 = U_{N2} = 400 \, \mathrm{V}$$

$P_0 = P_{fe}$
$$ I_0 = \frac{P_0}{U_0} = 0,2375 \, A $$

$$ P_0 = I_0 U_0 \cos \varphi_0 $$

Entonces
$$ \varphi_0 = \arccos \left( \frac{P_0}{I_0 U_0} \right) $$

__Cortocircuito:__

$$ I_{cc} = I_{N2} $$

$$ Z_{eq1} = \frac{U_{cc}}{I_{cc}} $$

$$ \varphi_{cc} = \arccos \left( \frac{P_{cc}}{I_{cc} U_{cc}} \right) = 64,62^\circ $$

$$ R_{eq1} = Z_{eq1} \cos \varphi_{cc} \quad \Rightarrow \quad R_{eq2} = \frac{R_{eq1}}{a^2} $$

$$ X_{eq1} = Z_{eq1} \sin \varphi_{cc} \quad \Rightarrow \quad X_{eq2} = \frac{X_{eq1}}{a^2} $$

__Rendimiento máximo:__

$$ P_0 = I_2^2 R_{eq2} \quad \Rightarrow \quad I_\mathrm{2 \eta max} = \sqrt{\frac{P_0}{R_{eq2}}} $$

$U_{20} = U_0$

$$ U_\mathrm{2 \eta max} = U_{20} - I_\mathrm{2 \eta max} (R_{eq2} \cdot 1 + 0) $$

$$ \eta_\mathrm{max} = \frac{U_\mathrm{2 \eta max} I_\mathrm{2 \eta max} \cdot 1}{U_\mathrm{2 \eta max} I_\mathrm{2 \eta max} \cdot 1 + P_{cu} + P_0} \times 100 = 98,7 \% $$

### Resolución en python:

```python
import numpy as np
```

```python
# Datos:
SN  = 14000     # VA
UN1 =   800     # V
UN2 =   400     # V
Po  =    95     # W
Pcc =   180     # W
Ucc = 3/100*UN1
```

```python
a = UN1/UN2  #     (relación de transformación)
IN1 = SN/UN1 # A   (corriente nominal primaria)
IN2 = SN/UN2 # A   (corriente nominal secundaria)
```

```python
# Vacío
Uo = UN2
Io = Po/Uo

phi_o = np.arccos(Po/(Io*Uo))

Ip = Io*np.cos(phi_o)
Im = Io*np.sin(phi_o)

# Corto circuito
Icc = IN2
phi_cc = np.arccos(Pcc/(Icc*Ucc))

Zeq1 = Ucc/Icc
Req1 = Zeq1*np.cos(phi_cc)
Xeq1 = Zeq1*np.sin(phi_cc)
Req2 = Req1/a**2
Xeq2 = Xeq1/a**2

# Rendimiento
U20 = Uo
I2max = np.sqrt(Po/Req2)
U2max = U20 - I2max*(Req2*1 + 0)

n = (U2max*I2max*1)/(U2max*I2max + Pcc + Po) * 100
```

```python
print('-------------------')
print('Rendimiento máximo:')
print('n = %.1f %%'%n)
print('-------------------')
```

    -------------------
    Rendimiento máximo:
    n = 98.7 %
    -------------------

```python
%reset -s -f
```


$\Large{\text{Ensemble methods}}$

Let us first generate a synthetic data set.

We shall generate $n$ ($n$ being even) samples where the feature vector of each sample is 2-dimensional of the form $x^i = (x^i_1,x^i_2), i \in \{1,2,\ldots,n\}$. We assume that $\frac{n}{2}$ samples are from a spiral shaped data set called $S_1$ and other $\frac{n}{2}$ samples are from a different spiral called $S_2$. For each sample $x^i$ we have the following labeling scheme:

$
\begin{align}
y^i = \begin{cases}
        +1 \text{ if } x^i \in S_1 \\
        -1 \text{ if }  x^i \in S_2.
      \end{cases}
\end{align}
$

Here the spirals $S_1$ and $S_2$ are associated with the parametric forms:
$x_1 = r(\varphi) \cos \varphi$ and $x_2 = r(\varphi) \sin \varphi$ where$\varphi$ the angle and $r(\varphi)$ is a (monotonically increasing) radius function depending on the angle $\varphi$. The coordinates are $x_1$ and $x_2$.

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

num_samples = 600
angle = np.linspace(0,2*pi,int(num_samples/2))
mean = [0.0, 0.0]
cov = [ [6.0, 6.0], [6.0, 6.0] ]

X = np.zeros( (num_samples, 2) )

r_1 = 2*angle + pi
data_1 = np.array([np.cos(angle)*r_1, np.sin(angle)*r_1]).T
#print(data_1.shape)
X_1 = data_1 + np.random.multivariate_normal(mean, cov, int(num_samples/2)) #np.random.randn(int(num_samples/2),2)
X[:int(num_samples/2),:] = X_1

r_2 = -2*angle - pi
data_2 = np.array([np.cos(angle)*r_2, np.sin(angle)*r_2]).T
X_2 = data_2 + np.random.multivariate_normal(mean, cov, int(num_samples/2)) #np.random.randn(int(num_samples/2),2)
X[int(num_samples/2):,:] = X_2

y = np.ones(num_samples)
y[int(num_samples/2):] = -1*y[int(num_samples/2)]
#print(y)

print(X.shape)
print(y.shape)

figure, axes = plt.subplots(1)

plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='o', color='blue')

plt.title( 'Data from two spirals' )
plt.show()

```

Consider an input space $\mathcal{X} \subseteq {\mathbb{R}}^d$ and the output space $\mathcal{Y} = \{+1,-1\}$. Assume a fixed (but unknown) distribution $P(X,Y)$ over $\mathcal{X} \times \mathcal{Y}$.

Let us assume that there are 15 base classifiers $C_1, C_2, \ldots, C_{15},$ where each classifier has an error rate of $\epsilon = 0.25$ on some sample from a fixed data distribution $P(X,Y)$.

To predict the label for a test sample $\hat{x}$, we adopt the following inference procedure (called $\textbf{Ensemble Classifier}(\textbf{EC})$):

1.   Predict $C_i(\hat{x})$ using each classifier $C_i, \ i \in \{1,2,\ldots,15\}$.
2.   Predict the final label $\hat{y} = \arg\max_{y \in \mathcal{Y}} \sum_{i=1}^{15} \delta(y==C_i(\hat{x}))$ where $\delta(p)$ is the indicator function given by:

  $
  \delta(p) =
  \begin{cases}
  1 \text{ if } p \text{ is true. } \\
  0 \text{ if } p \text{ is false. }
  \end{cases}
  $

$\textbf{Question:}$ What would be error rate of the classifier obtained from the above inference algorithm $\textbf{EC}$?

$\textbf{One possible answer:}$

Suppose the classifiers are assumed to be independent, then the $\textbf{EC}$ classifier would make an error only when more than half of the classifiers (i.e. more than 7 classifiers) make error in the prediction. Hence  we may write the error rate of $\textbf{EC}$ as:

$
\begin{align}
\text{error}_{\textbf{EC}} = \sum_{i=8}^{15} \begin{pmatrix} 15 \\  i \end{pmatrix} {\epsilon}^{i} (1-\epsilon)^{15-i}
\end{align}
$

which is approximately $0.017$. Note that this error rate is considerably smaller than the individual error rates of the classifiers.

```python
#compute the error rates of EC for different error rates of C_i
import math

def comb(n, k):
    return math.factorial(n) // math.factorial(k) // math.factorial(n - k)

epsilons = np.linspace(0,1,11)
errors = np.zeros(epsilons.shape)

num_classifiers = 15
eps_idx=0
for epsilon in epsilons:
  error_EC = 0
  for j in np.arange(np.ceil(num_classifiers/2),num_classifiers+1):
    err = comb(num_classifiers,j)*math.pow(epsilon,j)*math.pow(1-epsilon,num_classifiers-j)
    error_EC += err
  errors[eps_idx] = error_EC
  eps_idx+=1

figure, ax = plt.subplots(1)

plt.plot(epsilons, errors, marker='o')
ax.set_xlabel('$\epsilon$')
ax.set_ylabel('$Error_{EC}$')
ax.set_xlim(0, 1)
ax.set_ylim(errors.min()-0.1,errors.max()+0.1)
ax.set_xticks(epsilons)
ax.set_yticks(np.linspace(0,1,11))
ax.set_title('Error rate of Ensemble Classifier vs $\epsilon$')
plt.show()

```

$\textbf{Important to note:}$

1.   The base classifiers $C_1, C_2, \ldots, C_{15}$ are assumed to be independent.
2.   The error rate $\epsilon$ of each base classifier must be less than $0.5$ for the ensemble classifier to behave better. $\textbf{What is meant by a base classifier having an error rate less than } 0.5$?

$\large{\text{Ways of building an ensemble classifier}}$

$\textbf{Create multiple data partitions from training data}$

1.   Resample the original training data $D$ (using sampling with replacement) and create different data paritions $D_1, D_2, \ldots, D_M$.
2.   Train different classifiers $C_i$ on respective data partition $D_i$, $i \in \{1,2,\ldots,M\}$.
3.   For a test data point $\hat{x}$ predict the label $\hat{y}=\text{MajorityVote}(C_1(\hat{x}),C_2(\hat{x}), \ldots, C_M(\hat{x}))$.

```python
from urllib.request import urlopen
from PIL import Image

img = Image.open(urlopen('https://github.com/balamurugan-palaniappan-CEP/AIML_CEP_2021/raw/main/images/ensemble_classifier.png'))
img
```

```python
np.random.seed(1000)
#Create an index array
indexarr = np.arange(num_samples) #index array
np.random.shuffle(indexarr) #shuffle the indices
#print('shuffled indices of samples:')
#print(indexarr)
```

```python
#Use the samples corresponding to first 80% of indexarr for training
num_train = int(0.8*num_samples)
#Use the remaining 20% samples for testing
num_test = num_samples-num_train
print('num_train: ',num_train, 'num_test: ', num_test)
```

    num_train:  480 num_test:  120

```python
#Use the first 80% of indexarr to create the train data features and train labels
train_X = X[indexarr[0:num_train]]
train_y = y[indexarr[0:num_train]]
print('shape of train data features:')
print(train_X.shape)
print('shape of train data labels')
print(train_y.shape)
```

    shape of train data features:
    (480, 2)
    shape of train data labels
    (480,)

```python
#Use remaining 20% of indexarr to create the test data and test labels
test_X = X[indexarr[num_train:num_samples]]
test_y = y[indexarr[num_train:num_samples]]
print('shape of test data features:')
print(test_X.shape)
print('shape of test data labels')
print(test_y.shape)
```

    shape of test data features:
    (120, 2)
    shape of test data labels
    (120,)

```python
#Let us now use three different base classifiers and check the decision boundary
from sklearn.svm import LinearSVC #import linear SVM
from sklearn.neighbors import KNeighborsClassifier
from sklearn import tree #decision tree from scikit learn
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
# creating an object of LogisticRegression class

clf_list = []

#clf_linearsvc = LinearSVC(C=1.0)
clf_neigh = KNeighborsClassifier(n_neighbors=1, metric='euclidean') #weights='uniform' (default) or 'distance'
clf_svc = SVC(kernel='rbf', gamma=1)
clf_tree = tree.DecisionTreeClassifier(criterion='entropy')
#clf_logit = LogisticRegression(C=1.0) # C is set to be large number in order to remove the inbuilt regularization

#clf_list.append(clf_linearsvc)
clf_list.append(clf_neigh)
clf_list.append(clf_svc)
clf_list.append(clf_tree)
#clf_list.append(clf_logit)

clf_names = ['Nearest Neighbors', 'Kernel SVM', 'Decision Tree']
num_classifiers = 3

# create a mesh to plot in
h=0.05 #mesh step size
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, h),
                    np.arange(x2_min, x2_max, h))

for i in range(num_classifiers):
  print('classifier:',clf_list[i])
  indexarr = np.arange(train_X.shape[0]) #index array for train data
  np.random.shuffle(indexarr) #shuffle the indices
  #we shall choose 60% of the data
  partition_prop = 0.6
  num_samples_partition = int(partition_prop*train_X.shape[0])

  X_partition = train_X[indexarr[0:num_samples_partition]]
  y_partition = train_y[indexarr[0:num_samples_partition]]

  base_clf = clf_list[i]
  base_clf_model = base_clf.fit(X_partition,y_partition.ravel())

  #test accuracy
  from sklearn.metrics import accuracy_score
  test_y_predicted = base_clf_model.predict(test_X)
  test_acc = accuracy_score(test_y, test_y_predicted)
  print('test accuracy from classifier:',clf_names[i],' is:', test_acc)

  if i == 0:
    Z_all_clf = base_clf_model.predict(np.c_[xx1.ravel(), xx2.ravel()])
    # Put the result into a color plot
    Z = Z_all_clf.reshape(xx1.shape)

    test_pred_all_clf = test_y_predicted

    fig, (ax1, ax2) = plt.subplots(1,2, figsize=(16,6))
    ax1.contourf(xx1, xx2, Z, cmap=plt.cm.coolwarm, alpha=0.8)

    # Plot also the training points
    ax1.scatter(train_X[:, 0], train_X[:, 1], c=train_y, cmap=plt.cm.coolwarm)
    # plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
    # plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='s', color='green')
    xlabel = 'x1' + str('\n')+'Classifier='+str(clf_names[i])
    ax1.set_xlabel(xlabel)
    ax1.set_ylabel('x2')
    ax1.set_xlim(xx1.min(), xx1.max())
    ax1.set_ylim(xx2.min(), xx2.max())
    ax1.set_xticks(())
    ax1.set_yticks(())
    ax1.set_title('decision boundary with training points')

    #plot the test points along with decision boundaries
    ax2.contourf(xx1, xx2, Z, cmap=plt.cm.coolwarm, alpha=0.8)

    # Plot also the test points
    ax2.scatter(test_X[:, 0], test_X[:, 1], c=test_y, cmap=plt.cm.coolwarm)
    # plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
    # plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='s', color='green')
    xlabel = 'x1' + str('\n')+'Classifier='+str(clf_names[i])
    ax2.set_xlabel(xlabel)
    ax2.set_ylabel('x2')
    ax2.set_xlim(xx1.min(), xx1.max())
    ax2.set_ylim(xx2.min(), xx2.max())
    ax2.set_xticks(())
    ax2.set_yticks(())
    ax2.set_title('decision boundary with test points')

    plt.show()

  elif i ==1:
    Z_base_clf = base_clf_model.predict(np.c_[xx1.ravel(), xx2.ravel()])
    Z_all_clf = np.column_stack( (Z_all_clf,Z_base_clf) )
    # Put the result into a color plot
    Z = Z_base_clf.reshape(xx1.shape)

    test_pred_all_clf = np.column_stack( (test_pred_all_clf,test_y_predicted) )

    fig, (ax1, ax2) = plt.subplots(1,2, figsize=(16,6))
    ax1.contourf(xx1, xx2, Z, cmap=plt.cm.coolwarm, alpha=0.8)

    # Plot also the training points
    ax1.scatter(train_X[:, 0], train_X[:, 1], c=train_y, cmap=plt.cm.coolwarm)
    # plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
    # plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='s', color='green')
    xlabel = 'x1' + str('\n')+'Classifier='+str(clf_names[i])
    ax1.set_xlabel(xlabel)
    ax1.set_ylabel('x2')
    ax1.set_xlim(xx1.min(), xx1.max())
    ax1.set_ylim(xx2.min(), xx2.max())
    ax1.set_xticks(())
    ax1.set_yticks(())
    ax1.set_title('decision boundary with training points')

    #plot the test points along with decision boundaries
    ax2.contourf(xx1, xx2, Z, cmap=plt.cm.coolwarm, alpha=0.8)

    # Plot also the test points
    ax2.scatter(test_X[:, 0], test_X[:, 1], c=test_y, cmap=plt.cm.coolwarm)
    # plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
    # plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='s', color='green')
    xlabel = 'x1' + str('\n')+'Classifier='+str(clf_names[i])
    ax2.set_xlabel(xlabel)
    ax2.set_ylabel('x2')
    ax2.set_xlim(xx1.min(), xx1.max())
    ax2.set_ylim(xx2.min(), xx2.max())
    ax2.set_xticks(())
    ax2.set_yticks(())
    ax2.set_title('decision boundary with test points')

    plt.show()

  elif i==2:
    Z_base_clf = base_clf_model.predict(np.c_[xx1.ravel(), xx2.ravel()])
    Z_all_clf = np.column_stack( (Z_all_clf,Z_base_clf) )
    test_pred_all_clf = np.column_stack( (test_pred_all_clf,test_y_predicted) )

    Z = Z_base_clf.reshape(xx1.shape)
    fig, (ax1, ax2) = plt.subplots(1,2, figsize=(16,6))
    ax1.contourf(xx1, xx2, Z, cmap=plt.cm.coolwarm, alpha=0.8)

    # Put the result into a color plot

    # Plot also the training points
    ax1.scatter(train_X[:, 0], train_X[:, 1], c=train_y, cmap=plt.cm.coolwarm)
    # plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
    # plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='s', color='green')
    xlabel = 'x1' + str('\n')+'Classifier='+str(clf_names[i])
    ax1.set_xlabel(xlabel)
    ax1.set_ylabel('x2')
    ax1.set_xlim(xx1.min(), xx1.max())
    ax1.set_ylim(xx2.min(), xx2.max())
    ax1.set_xticks(())
    ax1.set_yticks(())
    ax1.set_title('decision boundary with training points')

    #plot the test points along with decision boundaries
    ax2.contourf(xx1, xx2, Z, cmap=plt.cm.coolwarm, alpha=0.8)

    # Plot also the test points
    ax2.scatter(test_X[:, 0], test_X[:, 1], c=test_y, cmap=plt.cm.coolwarm)
    # plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
    # plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='s', color='green')
    xlabel = 'x1' + str('\n')+'Classifier='+str(clf_names[i])
    ax2.set_xlabel(xlabel)
    ax2.set_ylabel('x2')
    ax2.set_xlim(xx1.min(), xx1.max())
    ax2.set_ylim(xx2.min(), xx2.max())
    ax2.set_xticks(())
    ax2.set_yticks(())
    ax2.set_title('decision boundary with test points')

    plt.show()

```

```python
Z_all_clf = np.array(Z_all_clf)
print(Z_all_clf.shape)

test_pred_all_clf = np.array(test_pred_all_clf)
print(test_pred_all_clf.shape)
```

    (639271, 3)
    (120, 3)

```python

from scipy import stats

Z_all_clf = Z_all_clf.astype(int)

test_pred_all_clf = test_pred_all_clf.astype(int)

Z, counts = stats.mode(Z_all_clf, axis=1)

test_pred, counts = stats.mode(test_pred_all_clf, axis=1)
test_acc = accuracy_score(test_y, test_pred)
print('test accuracy from ensemble classifier is:', test_acc)

# for i in range(Z_all_clf.shape[0]):
#   #print(Z_all_clf[i])
#   if (i+1)%100000 == 0:
#     print('*')
#   unique, counts = np.unique(Z_all_clf[i].astype(int), return_counts=True)
#   Z[i] = unique[np.argmax(counts)]

```

    test accuracy from ensemble classifier is: 0.85

```python
# Put the result into a color plot
Z = Z.reshape(xx1.shape)
fig, (ax1, ax2) = plt.subplots(1,2, figsize=(16,6))
ax1.contourf(xx1, xx2, Z, cmap=plt.cm.coolwarm, alpha=0.8)

# Put the result into a color plot

# Plot also the training points
ax1.scatter(train_X[:, 0], train_X[:, 1], c=train_y, cmap=plt.cm.coolwarm)
# plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
# plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='s', color='green')
xlabel = 'x1' + str('\n')+'Ensemble Classifier'
ax1.set_xlabel(xlabel)
ax1.set_ylabel('x2')
ax1.set_xlim(xx1.min(), xx1.max())
ax1.set_ylim(xx2.min(), xx2.max())
ax1.set_xticks(())
ax1.set_yticks(())
ax1.set_title('decision boundary with training points')

#plot the test points along with decision boundaries
ax2.contourf(xx1, xx2, Z, cmap=plt.cm.coolwarm, alpha=0.8)

# Plot also the test points
ax2.scatter(test_X[:, 0], test_X[:, 1], c=test_y, cmap=plt.cm.coolwarm)
# plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
# plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='s', color='green')
xlabel = 'x1' + str('\n')+'Ensemble Classifier'
ax2.set_xlabel(xlabel)
ax2.set_ylabel('x2')
ax2.set_xlim(xx1.min(), xx1.max())
ax2.set_ylim(xx2.min(), xx2.max())
ax2.set_xticks(())
ax2.set_yticks(())
ax2.set_title('decision boundary with test points')

plt.show()
```

$\large{\text{Ways of building an ensemble classifier}}$

$\textbf{Create multiple attribute partitions from training data}$

1.   Resample the attributes from original training data $D$ (using sampling with replacement) and create different feature paritions $F_1, F_2, \ldots, F_M$. Note that the number of samples in these partitions might be same as that in $D$ or might be different.
2.   Train different classifiers $C_i$ on respective feature partition $F_i$, $i \in \{1,2,\ldots,M\}$.
3.   For a test data point $\hat{x}$ first create feature partitions based on $F_1, F_2, \ldots, F_M$ and predict the label $\hat{y}=\text{MajorityVote}(C_1(F_1(\hat{x})),C_2(F_2(\hat{x})), \ldots, C_M(F_M(\hat{x})))$.

```python
from urllib.request import urlopen
from PIL import Image

img = Image.open(urlopen('https://github.com/balamurugan-palaniappan-CEP/AIML_CEP_2021/raw/main/images/ensemble_classifier_RF.png'))
img
```

```python
from sklearn.ensemble import RandomForestClassifier

clf_rf = RandomForestClassifier(n_estimators = 100,  random_state=0)
clf_model = clf_rf.fit(train_X, train_y)

test_y_predicted = clf_model.predict(test_X)
test_acc = accuracy_score(test_y, test_y_predicted)
print('test accuracy from RF classifier: is:', test_acc)

```

    test accuracy from RF classifier: is: 0.85

```python
Z = clf_model.predict(np.c_[xx1.ravel(), xx2.ravel()])
Z = Z.reshape(xx1.shape)
fig, (ax1, ax2) = plt.subplots(1,2, figsize=(16,6))
ax1.contourf(xx1, xx2, Z, cmap=plt.cm.coolwarm, alpha=0.8)

# Put the result into a color plot

# Plot also the training points
ax1.scatter(train_X[:, 0], train_X[:, 1], c=train_y, cmap=plt.cm.coolwarm)
# plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
# plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='s', color='green')
xlabel = 'x1' + str('\n')+'RF Classifier'
ax1.set_xlabel(xlabel)
ax1.set_ylabel('x2')
ax1.set_xlim(xx1.min(), xx1.max())
ax1.set_ylim(xx2.min(), xx2.max())
ax1.set_xticks(())
ax1.set_yticks(())
ax1.set_title('decision boundary with training points')

#plot the test points along with decision boundaries
ax2.contourf(xx1, xx2, Z, cmap=plt.cm.coolwarm, alpha=0.8)

# Plot also the test points
ax2.scatter(test_X[:, 0], test_X[:, 1], c=test_y, cmap=plt.cm.coolwarm)
# plt.scatter(X[:int(num_samples/2),0],X[:int(num_samples/2),1], marker='o', color='red')
# plt.scatter(X[int(num_samples/2):,0],X[int(num_samples/2):,1], marker='s', color='green')
xlabel = 'x1' + str('\n')+'RF Classifier'
ax2.set_xlabel(xlabel)
ax2.set_ylabel('x2')
ax2.set_xlim(xx1.min(), xx1.max())
ax2.set_ylim(xx2.min(), xx2.max())
ax2.set_xticks(())
ax2.set_yticks(())
ax2.set_title('decision boundary with test points')

plt.show()
```

$\large{\text{Exercise}}$

1.   For the two spirals dataset considered above, try the ensemble of the following classifiers

*   Nearest Neighbor with $3$ nearest neighbors and Manhattan metric
*   Nearest Neighbor with $5$ nearest neighbors and Chebyshev metric
*   Nearest Neighbor with $7$ nearest neighbors weighted by the Euclidean distance
*   Nearest Neighbor with $11$ nearest neighbors weighted by the Chebyshev metric
*   Kernel SVM with polynomial kernel with a suitable $p$
*   Kernel SVM with sigmoid kernel with a suitable $\gamma$
*   Decision tree with gini metric

Analyze the training set and test set performance obtained by each classifier and by the ensemble classifier.

2.   Write suitable code to obtain the type of features used in each tree in the random forest. Write suitable code to get the individual predictions from the trees in the random forest.

```python

```


# Continuous Signals

*This Jupyter notebook is part of a [collection of notebooks](../index.ipynb) in the bachelors module Signals and Systems, Communications Engineering, Universität Rostock. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Standard Signals

Certain [signals](https://en.wikipedia.org/wiki/Signal_%28electrical_engineering%29) play an important role in the theory and practical application of [signal processing](https://en.wikipedia.org/wiki/Signal_processing). They emerge from the theory of signals and systems, are used to characterize the properties of linear time-invariant (LTI) systems or frequently occur in practical applications. These standard signals are introduced and illustrated in the following. The treatise is limited to one-dimensional deterministic time- and amplitude-continuous signals.

### Complex Exponential Signal

The complex exponential signal is defined by the [complex exponential function](https://en.wikipedia.org/wiki/Exponential_function#Complex_plane)

\begin{equation}
x(t) = e^{s t}
\end{equation}

where $s = \sigma + j \omega$ denotes the complex frequency with $\sigma, \omega \in \mathbb{R}$. The signal is often used as a generalized representation of harmonic signals. Using [Euler's formula](https://en.wikipedia.org/wiki/Euler's_formula) above definition can be reformulated as

\begin{equation}
x(t) = e^{(\sigma + j \omega) t} = e^{\sigma t} \cos(\omega t) + j e^{\sigma t} \sin(\omega t)
\end{equation}

The real/imaginary part of the exponential signal is given by a weighted cosine/sine with angular frequency $\omega = 2 \pi f$. For $t>0$, the time-dependent weight $e^{\sigma t}$ is

* exponentially decaying over time for $\sigma < 0$,
* constantly one for $\sigma = 0$,
* exponentially growing over time for $\sigma > 0$,

and vice-versa for $t<0$. The complex exponential signal is used to model harmonic signals with exponentially decreasing or increasing amplitude.

**Example**

The following example illustrates the complex exponential signal and its parameters. The Python module [SymPy](http://docs.sympy.org/latest/index.html) is used for this purpose. It provides functionality for symbolic functions and mathematics. The required symbolic variables need to be defined explicitly before usage. In the example $t$, $\omega$ and $\sigma$ are defined as symbolic variables, followed by the definition of the exponential signal itself.

```python
%matplotlib inline
import sympy as sym
sym.init_printing()

t, sigma, omega = sym.symbols('t sigma omega', real=True)
s = sigma + 1j*omega
x = sym.exp(s*t)
x
```

Now specific values for the complex frequency $s = \sigma + j \omega$ are considered for illustration. A new signal is defined by substituting both $\sigma$ and $\omega$ with specific values. The real and imaginary part of the signal is plotted for illustration.

```python
y = x.subs({omega: 10, sigma : -.1})

sym.plot(sym.re(y), (t, 0, 2*sym.pi), ylabel=r'Re{$e^{st}$}')
sym.plot(sym.im(y), (t, 0, 2*sym.pi), ylabel=r'Im{$e^{st}$}');
```

**Exercise**

* Try out other values for `omega` and `sigma` to create signals with increasing/constant/decreasing amplitude and different frequencies.

### Dirac Impulse

The Dirac impulse is one of the most important signals in the theory of signals and systems. It is used for the characterization of LTI systems and the modeling of impulse-like signals. The Dirac impulse is defined by way of the [Dirac delta function](https://en.wikipedia.org/wiki/Dirac_delta_function) which is not a function in the conventional sense. It is a generalized function or *distribution*. The Dirac impulse is denoted as $\delta(t)$. The Dirac delta function is defined by its effect on other functions. A rigorous treatment is beyond the scope of this course material. Please refer to the literature for a detailed discussion of the mathematical foundations of the Dirac delta distribution. Fortunately for its application in signal processing it is suitable to consider only certain properties. The most relevant ones are

1. **Sifting property**
    \begin{equation}
    \int_{-\infty}^{\infty} \delta(t) \cdot x(t) = x(0)
    \end{equation}
    where $x(t)$ needs to be differentiable at $t=0$. The sifting property implies $\int_{-\infty}^{\infty} \delta(t) = 1$.

2. **Multiplication**
    \begin{equation}
    x(t) \cdot \delta(t) = x(0) \cdot \delta(t)
    \end{equation}
    where $x(t)$ needs to be differentiable at $t=0$.

3. **Linearity**
    \begin{equation}
    a \cdot \delta(t) + b \cdot \delta(t) = (a+b) \cdot \delta(t)
    \end{equation}

4. **Scaling**
    \begin{equation}
    \delta(a t) = \frac{1}{|a|} \delta(t)
    \end{equation}
    where $a \in \mathbb{R} \setminus 0$. This implies that the Dirac impulse is a function with even symmetry.

5. **Derivation**
    \begin{equation}
    \int_{-\infty}^{\infty} \frac{d \delta(t)}{dt} \cdot x(t) \; dt = - \frac{d x(t)}{dt} \bigg\vert_{t = 0}
    \end{equation}

It is important to note that the product $\delta(t) \cdot \delta(t)$ of two Dirac impulses is not defined.

**Example**

This example illustrates some of the basic properties of the Dirac impulse. Let's first define a Dirac impulse by way of the Dirac delta function

```python
delta = sym.DiracDelta(t)
delta
```

Now let's check the sifting property by defining an arbitrary signal (function) $f(t)$ and integrating over its product with the Delta impulse

```python
f = sym.Function('f')(t)
sym.integrate(delta*f, (t, -sym.oo, sym.oo))
```

**Exercise**

* Derive the sifting property for a shifted Dirac impulse $\delta(t-\tau)$ and check your results by modifing above example.

### Heaviside Signal

The Heaviside signal is defined by the [Heaviside step function](https://en.wikipedia.org/wiki/Heaviside_step_function)

\begin{equation}
\epsilon(t) = \begin{cases} 0 & t<0 \\  \frac{1}{2} & t=0 \\ 1 & t > 0 \end{cases}
\end{equation}

Note that alternative definitions exist that differ with respect to the value of $\epsilon(t)$ at $t=0$. The Heaviside signal is used to represent a signal that switches on at a specified time and stays switched on indefinitely. The Heaviside signal can be related to the Dirac impulse by

\begin{equation}
\int_{-\infty}^{t} \delta(\tau) \; d\tau = \epsilon(t)
\end{equation}

**Example**

In the following, a Heaviside signal $\epsilon(t)$ is defined and plotted. Note that `Sympy` denotes the Heaviside function by $\theta(t)$.

```python
step = sym.Heaviside(t)
step
```

```python
sym.plot(step, (t, -2, 2), ylim=[-0.2, 1.2], ylabel=r'$\epsilon(t)$');
```

Let's construct a harmonic signal $\cos(\omega t)$ with $\omega=2$ which is switched on at $t=0$. Considering the definition of the Heaviside function, the desired signal is given as

\begin{equation}
x(t) = \cos(\omega t) \cdot \epsilon(t)
\end{equation}

```python
x = sym.cos(omega*t) * sym.Heaviside(t)
sym.plot(x.subs(omega,2), (t, -2, 10), ylim=[-1.2, 1.2], ylabel=r'$x(t)$');
```

### Rectangular Signal

The rectangular signal is defined by the [rectangular function](https://en.wikipedia.org/wiki/Rectangular_function)

\begin{equation}
\text{rect}(t) = \begin{cases} 1 & |t| < \frac{1}{2} \\ \frac{1}{2} & |t| = \frac{1}{2} \\ 1 & |t| > \frac{1}{2} \end{cases}
\end{equation}

Note that alternative definitions exist that differ with respect to the value of $\text{rect}(t)$ at $t = \pm \frac{1}{2}$. The rectangular signal is used to represent a signal which is switched on for a limited period of time or has finite duration. The rectangular signal can be related to the Heaviside signal by

\begin{equation}
\text{rect}(t) = \epsilon \left(t + \frac{1}{2} \right) - \epsilon \left(t - \frac{1}{2} \right)
\end{equation}

**Example**

The Heaviside function is used to define a rectangular function in `Sympy`. This function is then used as rectangular signal.

```python
class rect(sym.Function):

    @classmethod
    def eval(cls, arg):
        return sym.Heaviside(arg + sym.S.Half) - sym.Heaviside(arg - sym.S.Half)
```

```python
sym.plot(rect(t), (t, -1, 1), ylim=[-0.2, 1.2], ylabel=r'rect$(t)$');
```

**Exercise**

* Use $\text{rect}(t)$ to construct a harmonic signal $\cos(\omega t)$ with $\omega=2$ which is switched on at $t=-\frac{1}{2}$ and switched off at $t=+\frac{1}{2}$.

### Sign Signal

The sign signal is defined by the [sign/signum function](https://en.wikipedia.org/wiki/Sign_function) which evaluates the sign of its argument

\begin{equation}
\text{sgn}(t) = \begin{cases} 1 & t>0 \\ 0 & t=0 \\ -1 & t < 0  \end{cases}
\end{equation}

The sign signal is useful to represent the absolute value of a real-valued signal $x(t) \in \mathbb{R}$ by a multiplication

\begin{equation}
|x(t)| = x(t) \cdot \text{sgn}(x(t))
\end{equation}

It is related to the Heaviside function by

\begin{equation}
\text{sgn}(t) = 2 \cdot \epsilon(t) - 1
\end{equation}

**Example**

The following example illustrates the sign signal $\text{sgn}(t)$. Note that the sign function is represented as $\text{sign}(t)$ in `Sympy`.

```python
sgn = sym.sign(t)
sgn
```

```python
sym.plot(sgn, (t, -2, 2), ylim=[-1.2, 1.2], ylabel=r'sgn$(t)$');
```

**Exercise**

* Check the values of $\text{sgn}(t)$ for $t \to 0^-$, $t = 0$ and $t \to 0^+$ as implemented in `SymPy`. Do they conform to above definition?

**Copyright**

The notebook is provided as [Open Educational Resource](https://de.wikipedia.org/wiki/Open_Educational_Resources). Feel free to use the notebook for your own educational purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Signals and Systems* by Sascha Spors.


# Programación lineal

> La programación lineal es el campo de la optimización matemática dedicado a maximizar o minimizar (optimizar) funciones lineales, denominada función objetivo, de tal forma que las variables de dicha función estén sujetas a una serie de restricciones expresadas mediante un sistema de ecuaciones o inecuaciones también lineales.

**Referencias:**
- https://es.wikipedia.org/wiki/Programaci%C3%B3n_lineal
- https://docs.scipy.org/doc/scipy-0.18.1/reference/optimize.html

## 1. Apuntes históricos

- 1826:	Joseph Fourier anticipa la programación lineal. Carl Friedrich Gauss resuelve ecuaciones lineales por eliminación "gaussiana".
- 1902:	Gyula Farkas concibe un método para resolver sistemas de inecuaciones.
- Es hasta la Segunda Guerra Mundial que se plantea la programación lineal como un modelo matemático para planificar gastos y retornos, de modo que se reduzcan costos de guerra y aumentar pérdidas del enemigo. Secreto hasta 1947 (posguerra).
- 1947:	George Dantzig publica el algoritmo simplex y John von Neumann desarrolló la teoría de la dualidad. Se sabe que Leonid Kantoróvich también formuló la teoría en forma independiente.
- Fue usado por muchas industrias en la planificación diaria.

**Hasta acá, tiempos exponenciales de solución. Lo siguiente, tiempo polinomial.**

- 1979: Leonid Khachiyan, diseñó el llamado Algoritmo del elipsoide, a través del cual demostró que el problema de la programación lineal es resoluble de manera eficiente, es decir, en tiempo polinomial.
- 1984: Narendra Karmarkar introduce el método del punto interior para resolver problemas de programación lineal.

**Mencionar complejidad computacional.**

## 2. Motivación

Cuando se quiere optimizar una función de varias variables con restricciones, se puede aplicar siempre el método de Multiplicadores de Lagrange. Sin embargo, este método es computacionalmente muy complejo conforme crece el número de variables.

Por tanto, cuando la función a optimizar y las restricciones son de caracter lineal, los métodos de solución que se pueden desarrollar son computacionalmente eficientes, por lo que es útil realizar la distinción.

## 3. Problemas de programación lineal

De acuerdo a lo descrito anteriormente, un problema de programación lineal puede escribirse en la siguiente forma:

\begin{equation}
\begin{array}{ll}
\min_{x_1,\dots,x_n} & c_1x_1+\dots+c_nx_n \\
\text{s. a. }        & a^{eq}_{j,1}x_1+\dots+a^{eq}_{j,n}x_n=b^{eq}_j \text{ para } 1\leq j\leq m_1 \\
                     & a_{k,1}x_1+\dots+a_{k,n}x_n\leq b_k \text{ para } 1\leq k\leq m_2,
\end{array}
\end{equation}
donde:
- $x_i$ para $i=1,\dots,n$ son las incógnitas o variables de decisión,
- $c_i$ para $i=1,\dots,n$ son los coeficientes de la función a optimizar,
- $a^{eq}_{j,i}$ para $j=1,\dots,m_1$ e $i=1,\dots,n$, son los coeficientes de la restricción de igualdad,
- $a_{k,i}$ para $k=1,\dots,m_2$ e $i=1,\dots,n$, son los coeficientes de la restricción de desigualdad,
- $b^{eq}_j$ para $j=1,\dots,m_1$ son valores conocidos que deben ser respetados estrictamente, y
- $b_k$ para $k=1,\dots,m_2$ son valores conocidos que no deben ser superados.

Equivalentemente, el problema puede escribirse como

\begin{equation}
\begin{array}{ll}
\min_{\boldsymbol{x}} & \boldsymbol{c}^T\boldsymbol{x} \\
\text{s. a. }        & \boldsymbol{A}_{eq}\boldsymbol{x}=\boldsymbol{b}_{eq} \\
                     & \boldsymbol{A}\boldsymbol{x}\leq\boldsymbol{b},
\end{array}
\end{equation}
donde:
- $\boldsymbol{x}=\left[x_1\quad\dots\quad x_n\right]^T$,
- $\boldsymbol{c}=\left[c_1\quad\dots\quad c_n\right]^T$,
- $\boldsymbol{A}_{eq}=\left[\begin{array}{ccc}a^{eq}_{1,1} & \dots & a^{eq}_{1,n}\\ \vdots & \ddots & \vdots\\ a^{eq}_{m_1,1} & \dots & a^{eq}_{m_1,n}\end{array}\right]$,
- $\boldsymbol{A}=\left[\begin{array}{ccc}a_{1,1} & \dots & a_{1,n}\\ \vdots & \ddots & \vdots\\ a_{m_2,1} & \dots & a_{m_2,n}\end{array}\right]$,
- $\boldsymbol{b}_{eq}=\left[b^{eq}_1\quad\dots\quad b^{eq}_{m_1}\right]^T$, y
- $\boldsymbol{b}=\left[b_1\quad\dots\quad b_{m_2}\right]^T$.

**Nota:** el problema $\max_{\boldsymbol{x}}\boldsymbol{g}(\boldsymbol{x})$ es equivalente a $\min_{\boldsymbol{x}}-\boldsymbol{g}(\boldsymbol{x})$.

## 4. Ejemplo básico
Una compañía produce dos productos ($X_1$ y $X_2$) usando dos máquinas ($A$ y $B$). Cada unidad de $X_1$ que se produce requiere 50 minutos en la máquina $A$ y 30 minutos en la máquina $B$. Cada unidad de $X_2$ que se produce requiere 24 minutos en la máquina $A$ y 33 minutos en la máquina $B$.

Al comienzo de la semana hay 30 unidades de $X_1$ y 90 unidades de $X_2$ en inventario. El tiempo de uso disponible de la máquina $A$ es de 40 horas y el de la máquina $B$ es de 35 horas.

La demanda para $X_1$ en la semana actual es de 75 unidades y de $X_2$ es de 95 unidades. La política de la compañía es maximizar la suma combinada de unidades de $X_1$ e $X_2$ en inventario al finalizar la semana.

Formular el problema de decidir cuánto hacer de cada producto en la semana como un problema de programación lineal.

### Solución

Sean:
- $x_1$ la cantidad de unidades de $X_1$ a ser producidas en la semana, y
- $x_2$ la cantidad de unidades de $X_2$ a ser producidas en la semana.

Notar que lo que se quiere es maximizar $x_1+x_2$.

Restricciones:
1. El tiempo de uso disponible de la máquina $A$ es de 40 horas: $50x_1+24x_2\leq 40(60)\Rightarrow 50x_1+24x_2\leq 2400$.
2. El tiempo de uso disponible de la máquina $B$ es de 35 horas: $30x_1+33x_2\leq 35(60)\Rightarrow 30x_1+33x_2\leq 2100$.
3. La demanda para $X_1$ en la semana actual es de 75 unidades: $x_1+30\geq 75\Rightarrow x_1\geq 45\Rightarrow -x_1\leq -45$.
4. La demanda para $X_2$ en la semana actual es de 95 unidades: $x_2+90\geq 95\Rightarrow x_2\geq 5\Rightarrow -x_2\leq -5$.

Finalmente, el problema puede ser expresado en la forma explicada como:
\begin{equation}
\begin{array}{ll}
\min_{x_1,x_2} & -x_1-x_2 \\
\text{s. a. }  & 50x_1+24x_2\leq 2400 \\
               & 30x_1+33x_2\leq 2100 \\
               & -x_1\leq -45 \\
               & -x_2\leq -5,
\end{array}
\end{equation}

o, eqivalentemente
\begin{equation}
\begin{array}{ll}
\min_{\boldsymbol{x}} & \boldsymbol{c}^T\boldsymbol{x} \\
\text{s. a. }        & \boldsymbol{A}_{eq}\boldsymbol{x}=\boldsymbol{b}_{eq} \\
                     & \boldsymbol{A}\boldsymbol{x}\leq\boldsymbol{b},
\end{array}
\end{equation}
con
- $\boldsymbol{c}=\left[-1 \quad -1\right]^T$,
- $\boldsymbol{A}=\left[\begin{array}{cc}50 & 24 \\ 30 & 33\\ -1 & 0\\ 0 & -1\end{array}\right]$, y
- $\boldsymbol{b}=\left[2400\quad 2100\quad -45\quad -5\right]^T$.

Preferiremos, en adelante, la notación vectorial/matricial.

Este problema está sencillo pues solo es en dos variables. La solución gráfica es válida.

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

```python
def res1(x1):
    return (2400-50*x1)/24
def res2(x1):
    return (2100-30*x1)/33
```

```python
x1 = np.linspace(40, 50)
r1 = res1(x1)
r2 = res2(x1)
```

```python
plt.figure(figsize = (8,6))
plt.plot(x1, res1(x1), 'b--', label = 'res1')
plt.plot(x1, res2(x1), 'r--', label = 'res2')
plt.plot([45, 45], [0, 25], 'k', label = 'res3')
plt.plot([40, 50], [5, 5], 'm', label = 'res4')
plt.fill_between(np.array([45.0, 45.6]), res1(np.array([45.0, 45.6])), 5*np.ones(2))
plt.text(44.8,4.9,'$(45,5)$',fontsize=10)
plt.text(44.75,6.25,'$(45,6.25)$',fontsize=10)
plt.text(45.6,5.1,'$(45.6,5)$',fontsize=10)
plt.legend(loc = 'best')
plt.xlabel('$x_1$')
plt.ylabel('$x_2$')
plt.axis([44, 46, 4, 7])
plt.show()
```

## 5. ¿Cómo se resuelve en python?

### 5.1 Librería `SciPy`

`SciPy` es un software de código abierto basado en `Python` para matemáticas, ciencia e ingeniería.

En particular, los siguientes son algunos de los paquetes básicos:
- `NumPy`
- **Librería `SciPy`**
- `SymPy`
- `matplotlib`
- `pandas`

La **Librería `SciPy`** es uno de los paquetes principales y provee varias rutinas numéricas eficientes. Entre ellas, para integración numérica y optimización.

En esta clase, y en lo que resta del módulo, estaremos utilizando el módulo `optimize` de la librería `SciPy`.

```python
import scipy.optimize as opt
```

El módulo `optimize` que acabamos de importar contiene varias funciones para optimización y búsqueda de raices ($f(x)=0$). Entre ellas se encuentra la función `linprog`

```python
help(opt.linprog)
```

    Help on function linprog in module scipy.optimize._linprog:

    linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None, method='simplex', callback=None, options=None)
        Minimize a linear objective function subject to linear
        equality and inequality constraints.

        Linear Programming is intended to solve the following problem form:

        Minimize:     c^T * x

        Subject to:   A_ub * x <= b_ub
                      A_eq * x == b_eq

        Parameters
        ----------
        c : array_like
            Coefficients of the linear objective function to be minimized.
        A_ub : array_like, optional
            2-D array which, when matrix-multiplied by x, gives the values of the
            upper-bound inequality constraints at x.
        b_ub : array_like, optional
            1-D array of values representing the upper-bound of each inequality
            constraint (row) in A_ub.
        A_eq : array_like, optional
            2-D array which, when matrix-multiplied by x, gives the values of the
            equality constraints at x.
        b_eq : array_like, optional
            1-D array of values representing the RHS of each equality constraint
            (row) in A_eq.
        bounds : sequence, optional
            ``(min, max)`` pairs for each element in ``x``, defining
            the bounds on that parameter. Use None for one of ``min`` or
            ``max`` when there is no bound in that direction. By default
            bounds are ``(0, None)`` (non-negative)
            If a sequence containing a single tuple is provided, then ``min`` and
            ``max`` will be applied to all variables in the problem.
        method : str, optional
            Type of solver.  At this time only 'simplex' is supported
            :ref:`(see here) <optimize.linprog-simplex>`.
        callback : callable, optional
            If a callback function is provide, it will be called within each
            iteration of the simplex algorithm. The callback must have the signature
            `callback(xk, **kwargs)` where xk is the current solution vector
            and kwargs is a dictionary containing the following::

                "tableau" : The current Simplex algorithm tableau
                "nit" : The current iteration.
                "pivot" : The pivot (row, column) used for the next iteration.
                "phase" : Whether the algorithm is in Phase 1 or Phase 2.
                "basis" : The indices of the columns of the basic variables.

        options : dict, optional
            A dictionary of solver options. All methods accept the following
            generic options:

                maxiter : int
                    Maximum number of iterations to perform.
                disp : bool
                    Set to True to print convergence messages.

            For method-specific options, see `show_options('linprog')`.

        Returns
        -------
        A `scipy.optimize.OptimizeResult` consisting of the following fields:

            x : ndarray
                The independent variable vector which optimizes the linear
                programming problem.
            fun : float
                Value of the objective function.
            slack : ndarray
                The values of the slack variables.  Each slack variable corresponds
                to an inequality constraint.  If the slack is zero, then the
                corresponding constraint is active.
            success : bool
                Returns True if the algorithm succeeded in finding an optimal
                solution.
            status : int
                An integer representing the exit status of the optimization::

                     0 : Optimization terminated successfully
                     1 : Iteration limit reached
                     2 : Problem appears to be infeasible
                     3 : Problem appears to be unbounded

            nit : int
                The number of iterations performed.
            message : str
                A string descriptor of the exit status of the optimization.

        See Also
        --------
        show_options : Additional options accepted by the solvers

        Notes
        -----
        This section describes the available solvers that can be selected by the
        'method' parameter. The default method is :ref:`Simplex <optimize.linprog-simplex>`.

        Method *Simplex* uses the Simplex algorithm (as it relates to Linear
        Programming, NOT the Nelder-Mead Simplex) [1]_, [2]_. This algorithm
        should be reasonably reliable and fast.

        .. versionadded:: 0.15.0

        References
        ----------
        .. [1] Dantzig, George B., Linear programming and extensions. Rand
               Corporation Research Study Princeton Univ. Press, Princeton, NJ, 1963
        .. [2] Hillier, S.H. and Lieberman, G.J. (1995), "Introduction to
               Mathematical Programming", McGraw-Hill, Chapter 4.
        .. [3] Bland, Robert G. New finite pivoting rules for the simplex method.
               Mathematics of Operations Research (2), 1977: pp. 103-107.

        Examples
        --------
        Consider the following problem:

        Minimize: f = -1*x[0] + 4*x[1]

        Subject to: -3*x[0] + 1*x[1] <= 6
                     1*x[0] + 2*x[1] <= 4
                                x[1] >= -3

        where:  -inf <= x[0] <= inf

        This problem deviates from the standard linear programming problem.
        In standard form, linear programming problems assume the variables x are
        non-negative.  Since the variables don't have standard bounds where
        0 <= x <= inf, the bounds of the variables must be explicitly set.

        There are two upper-bound constraints, which can be expressed as

        dot(A_ub, x) <= b_ub

        The input for this problem is as follows:

        >>> c = [-1, 4]
        >>> A = [[-3, 1], [1, 2]]
        >>> b = [6, 4]
        >>> x0_bounds = (None, None)
        >>> x1_bounds = (-3, None)
        >>> from scipy.optimize import linprog
        >>> res = linprog(c, A_ub=A, b_ub=b, bounds=(x0_bounds, x1_bounds),
        ...               options={"disp": True})
        Optimization terminated successfully.
             Current function value: -22.000000
             Iterations: 1
        >>> print(res)
             fun: -22.0
         message: 'Optimization terminated successfully.'
             nit: 1
           slack: array([ 39.,   0.])
          status: 0
         success: True
               x: array([ 10.,  -3.])

        Note the actual objective value is 11.428571.  In this case we minimized
        the negative of the objective function.

### 5.2 Solución del ejemplo básico con linprog
Ya hicimos la solución gráfica. Contrastemos con la solución que nos da `linprog`...

```python
import numpy as np
```

```python
# Resolver...
c = np.array([-1, -1])
A = np.array([[50, 24], [30, 33], [-1, 0], [0, -1]])
b = np.array([2400, 2100, -45, -5])
```

```python
resultado = opt.linprog(c, A_ub=A, b_ub=b)
```

```python
resultado
```

         fun: -51.25
     message: 'Optimization terminated successfully.'
         nit: 4
       slack: array([   0.  ,  543.75,    0.  ,    1.25])
      status: 0
     success: True
           x: array([ 45.  ,   6.25])

```python

```

Las cantidades de $X_1$ y $X_2$ que se deben producir para maximizar el inventario al final de la semana, con las restricciones de tiempo de uso de las máquinas y de demanda es:

$$x_1=45$$

$$x_2=6.25$$

## 6. Optimización de inversión en bonos

### 6.1 Ejemplo 1

**Referencia:**

```python
from IPython.display import YouTubeVideo
YouTubeVideo('gukxBus8lOs')
```

El objetivo de este problema es determinar la mejor estrategia de inversión, dados diferentes tipos de bono, la máxima cantidad que puede ser invertida en cada bono, el porcentaje de retorno y los años de madurez. También hay una cantidad fija de dinero disponible ($\$750,000$). Por lo menos la mitad de este dinero debe ser invertido en bonos con 10 años o más para la madurez. Se puede invertir un máximo del $25\%$ de esta cantidad en cada bono. Finalmente, hay otra restricción que no permite usar más de $35\%$ en bonos de alto riesgo.

Existen seis (6) opciones de inversión con las letras correspondientes $A_i$

1. $A_1$:(Tasa de retorno=$8.65\%$; Años para la madurez=11, Riesgo=Bajo)
1. $A_2$:(Tasa de retorno=$9.50\%$; Años para la madurez=10, Riesgo=Alto)
1. $A_3$:(Tasa de retorno=$10.00\%$; Años para la madurez=6, Riesgo=Alto)
1. $A_4$:(Tasa de retorno=$8.75\%$; Años para la madurez=10, Riesgo=Bajo)
1. $A_5$:(Tasa de retorno=$9.25\%$; Años para la madurez=7, Riesgo=Alto)
1. $A_6$:(Tasa de retorno=$9.00\%$; Años para la madurez=13, Riesgo=Bajo)

Lo que se quiere entonces es maximizar el retorno que deja la inversión.

Este problema puede ser resuelto con programación lineal. Formalmente, puede ser descrito como:

$$\max_{A_1,A_2,...,A_6}\sum^{6}_{i=1} A_iR_i,$$

donde $A_i$ representa la cantidad invertida en la opción, y $R_i$ representa la tasa de retorno respectiva.

Plantear restricciones...

```python
# Resolver...
c = -np.array([8.65, 9.5, 10, 8.75, 9.25, 9])/100
A = np.array([[-1, -1, 0, -1, 0, -1], [0, 1, 1, 0, 1, 0], [1, 1, 1, 1, 1, 1]])
b = np.array([-.5, .35, 1])*750000
bounds = (0, 0.25*750000)
```

```python
res_bonos = opt.linprog(c, A_ub=A, b_ub=b, bounds=(bounds, bounds, bounds, bounds, bounds, bounds))
```

```python
res_bonos.x
```

    array([ 112500.,   75000.,  187500.,  187500.,       0.,  187500.])

### 6.2 Ejemplo 2

**Referencia:**
- https://la.mathworks.com/help/optim/ug/maximize-long-term-investments-using-linear-programming.html

Suponga que se tiene una cantidad inicial de dinero $C_0$ para invertir en un periodo de tiempo $T$ años en $N$ bonos sin cupones. Cada bono paga una tasa de interés compuesta anualmente, y paga el principal más el interés compuesto al final del periodo de madurez. El objetivo es maximizar la cantidad total de dinero después de $T$ años.

Se puede incluir la restricción de que ninguna inversión individual puede superar una fracción del capital total.

Suponga que:
- El capital inicial es $C_0=\$1000$.
- El periodo de tiempo es $T=5$ años.
- El número de bonos es $N=4$.

Para modelar dinero que no se invierte, tenemos una opción $B_0$ disponible cada año que tiene periodo de madurez un año y una tasa de interés del $0\%$.

- El Bono 1, denotado por $B_1$, se puede comprar en el año 1, tiene periodo de madurez de 4 años, y una tasa de interés del $2\%$.
- El Bono 2, denotado por $B_2$, se puede comprar en el año 5, tiene periodo de madurez de 1 año, y una tasa de interés del $4\%$.
- El Bono 3, denotado por $B_3$, se puede comprar en el año 2, tiene periodo de madurez de 4 años, y una tasa de interés del $6\%$.
- El Bono 4, denotado por $B_4$, se puede comprar en el año 2, tiene periodo de madurez de 3 años, y una tasa de interés del $6\%$.

Separando la opción $B_0$ (no invertir) en 5 bonos con periodo de madurez de 1 año y tasa de interés del $0\%$, este problema se puede modelar equivalentemente como si se tuviera un total de 9 bonos disponibles: $B_k$ para $k=1,\dots,9$.

Ver en el tablero una representación gráfica y plantear

```python
# Resolver...
rho = np.array([0, 0, 0, 0, 0, 2, 4, 6, 6])
mad = np.array([1, 1, 1, 1, 1, 4, 1, 4, 3])

r = (1+rho/100)**mad

# Matrices para linprog
c = -np.array([0, 0, 0, 0, r[4], 0, r[6], r[7], 0])
Aeq = np.array([[1, 0, 0, 0, 0, 1, 0, 0, 0],
                [-r[0], 1, 0, 0, 0, 0, 0, 1, 1],
                [0, -r[1], 1, 0, 0, 0, 0, 0, 0],
                [0, 0, -r[2], 1, 0, 0, 0, 0, 0],
                [0, 0, 0, -r[3], 1, -r[5], 1, 0, -r[8]]])
beq = np.array([1000, 0, 0, 0, 0])
```

```python
res_bonos2 = opt.linprog(c, A_eq=Aeq, b_eq=beq)
```

```python
res_bonos2
```

         fun: -1262.4769600000004
     message: 'Optimization terminated successfully.'
         nit: 7
       slack: array([], dtype=float64)
      status: 0
     success: True
           x: array([ 1000.,     0.,     0.,     0.,     0.,     0.,     0.,  1000.,
               0.])

```python
res_bonos2.x
```

    array([ 1000.,     0.,     0.,     0.,     0.,     0.,     0.,  1000.,
               0.])

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


```
%matplotlib inline
from __future__ import  division
from IPython.html.widgets import interact,fixed
from scipy import stats
import sympy
from sympy import S, init_printing, Sum, summation,expand
from sympy import stats as st
from sympy.abc import k
import re
```

## Maximum likelihood estimation

$$\hat{p} = \frac{1}{n} \sum_{i=1}^n X_i$$

$$ \mathbb{E}(\hat{p}) = p $$

$$ \mathbb{V}(\hat{p}) = \frac{p(1-p)}{n} $$

```
sympy.plot( k*(1-k),(k,0,1),ylabel='$n^2 \mathbb{V}$',xlabel='p',fontsize=28)
```

```
b=stats.bernoulli(p=.8)
samples=b.rvs(100)
print var(samples)
print mean(samples)
```

    0.1476
    0.82

```
def slide_figure(n=100,m=1000,p=0.8):
    fig,ax=subplots()
    ax.axis(ymax=25)
    b=stats.bernoulli(p=p)
    v=iter(b.rvs,2)
    samples=b.rvs(n*1000)
    tmp=(samples.reshape(n,-1).mean(axis=0))
    ax.hist(tmp,normed=1)
    ax1=ax.twinx()
    ax1.axis(ymax=25)
    ax1.plot(linspace(0,1,200),stats.norm(mean(tmp),std(tmp)).pdf(linspace(0.0,1,200)),lw=3,color='r')
```

```
interact(slide_figure, n=(100,500),p=(0.01,1,.05),m=fixed(500));
```

## Maximum A-Posteriori (MAP) Estimation

```
from sympy.abc import p,n,k

sympy.plot(st.density(st.Beta('p',3,3))(p),(p,0,1) ,xlabel='p')
```

### Maximize the MAP function to get form of estimator

```
obj=sympy.expand_log(sympy.log(p**k*(1-p)**(n-k) * st.density(st.Beta('p',6,6))(p)))
```

```
sol=sympy.solve(sympy.simplify(sympy.diff(obj,p)),p)[0]
print sol
```

    (k + 5)/(n + 10)

$ \hat{p}_{MAP} = \frac{(5+\sum_{i=1}^n X_i )}{(n + 10)}  $

with corresponding expectation

$ \mathbb{E}  = \frac{(5+n p )}{(n + 10)} $

which is a biased estimator. The variance of this estimator is the following:

$$ \mathbb{V}(\hat{p}_{MAP}) = \frac{n (1-p) p}{(n+10)^2} $$

compare this to the variance of the maximum likelihood estimator, which is reproduced here:

$$ \mathbb{V}(\hat{p}_{ML}) = \frac{p(1-p)}{n} $$

```
n=5
def show_bias(n=30):
    sympy.plot(p,(5+n*p)/(n+10),(p,0,1),aspect_ratio=1,title='more samples reduce bias')

interact(show_bias,n=(10,500,10));
```

Compute the variance of the MAP estimator

```
sum(sympy.var('x1:10'))
expr=((5+(sum(sympy.var('x1:10'))))/(n+10))
```

```
def apply_exp(expr):
    tmp=re.sub('x[\d]+\*\*2','p',str(expand(expr)))
    tmp=re.sub('x[\d]+\*x[\d]+','p**2',tmp)
    tmp=re.sub('x[\d]+','p',tmp)
    return sympy.sympify(tmp)
```

```
ex2 = apply_exp(expr**2)
print ex2
```

    8*p**2/25 + 11*p/25 + 1/9

```
tmp=sympy.simplify(ex2 - (apply_exp(expr))**2 )
sympy.plot(tmp,p*(1-p)/10,(p,0,1))
```

### General case

```
def generate_expr(num_samples=10,alpha=6):
    n = sympy.symbols('n')
    obj=sympy.expand_log(sympy.log(p**k*(1-p)**(n-k) * st.density(st.Beta('p',alpha,alpha))(p)))
    sol=sympy.solve(sympy.simplify(sympy.diff(obj,p)),p)[0]
    expr=sol.replace(k,(sum(sympy.var('x1:%d'%(num_samples)))))
    expr=expr.subs(n,num_samples)
    ex2 = apply_exp(expr**2)
    ex =  apply_exp(expr)
    return (ex,sympy.simplify(ex2-ex**2))
```

```
num_samples=10
X_bias,X_v = generate_expr(num_samples,alpha=2)
p1=sympy.plot(X_v,(p,0,1),show=False,line_color='b',ylim=(0,.03),xlabel='p')
X_bias,X_v = generate_expr(num_samples,alpha=6)
p2=sympy.plot(X_v,(p,0,1),show=False,line_color='r',xlabel='p')
p3=sympy.plot(p*(1-p)/num_samples,(p,0,1),show=False,line_color='g',xlabel='p')
p1.append(p2[0])
p1.append(p3[0])
p1.show()
```

```
p1=sympy.plot(n*(1-p)*p/(n+10)**2,(p,0,1),show=False,line_color='b',ylim=(0,.05),xlabel='p',ylabel='variance')
p2=sympy.plot((1-p)*p/n,(p,0,1),show=False,line_color='r',ylim=(0,.05),xlabel='p')
p1.append(p2[0])
p1.show()
```

```
def show_variance(n=5):
    p1=sympy.plot(n*(1-p)*p/(n+10)**2,(p,0,1),show=False,line_color='b',ylim=(0,.05),xlabel='p',ylabel='variance')
    p2=sympy.plot((1-p)*p/n,(p,0,1),show=False,line_color='r',ylim=(0,.05),xlabel='p')
    p1.append(p2[0])
    p1.show()
interact(show_variance,n=(10,120,2));

```

The obvious question is what is the value of  a biased estimator? The key fact is that the MAP estimator is biased, yes, but it is biased   according to the prior probability of $\theta$. Suppose that the true parameter $p=1/2$ which is exactly at the peak of the prior probability function. In this case, what is the bias?

$ \mathbb{E}  = \frac{(5+n p )}{(n + 10)} -p \rightarrow 0$

and the variance of the MAP estimator at this point is the following:

$$ \frac{n p (1-p)}{(n+10)^2} \rightarrow $$

```
pv=.55
nsamp=100/2
fig,ax=subplots()
rv = stats.bernoulli(pv)
map_est=(rv.rvs((nsamp,1000)).sum(axis=0)+5)/(nsamp+10);
ml_est=(rv.rvs((nsamp,1000)).sum(axis=0))/(nsamp);
_,bins,_=ax.hist(map_est,bins=20,alpha=.3,normed=True,label='MAP');
ax.hist(ml_est,bins=20,alpha=.3,normed=True,label='ML');
ax.vlines(map_est.mean(),0,12,lw=3,linestyle=':',color='b')
ax.vlines(pv,0,12,lw=3,color='r',linestyle=':')
ax.vlines(ml_est.mean(),0,12,lw=3,color='g',linestyle=':')
ax.legend()
```

```

```