# System of non-linear equations - Coupled chemical equilibria
Author: Björn Dahlgren, Applied Physcial Chemistry, KTH Royal Insitiute of Technology

In this example we will study the equilibria between aqueous cupric ions and ammonia.
We will use [ChemPy](https://github.com/bjodah/chempy) which is a Python package collecting functions and classes useful for chemistry related problems. We will also make use of SymPy for manipulating and inspecting the formulae encountered.

```python
from collections import defaultdict
from chempy import atomic_number
from chempy.chemistry import Species, Equilibrium
from chempy.equilibria import EqSystem, NumSysLin, NumSysLog, NumSysSquare
from IPython.display import Latex, display
import matplotlib.pyplot as plt
%matplotlib inline
def show(s):  # convenience function
    display(Latex('$'+s+'$'))
import sympy; import chempy; print('SymPy: %s, ChemPy: %s' % (sympy.__version__, chempy.__version__))
```

Let's define our species with names and composition, ChemPy can parse chemical formulae:

```python
NH3_complexes = ['CuNH3+2', 'Cu(NH3)2+2', 'Cu(NH3)3+2', 'Cu(NH3)4+2', 'Cu(NH3)5+2']
OH_complexes = ['Cu2(OH)2+2', 'Cu(OH)3-', 'Cu(OH)4-2']
substances = [
    Species.from_formula(n) for n in ['H+', 'OH-', 'NH4+', 'NH3', 'H2O', 'Cu+2'] +
        NH3_complexes + OH_complexes + ['Cu(OH)2(s)']
] #, CuOHp, CuOH2,
substance_names = [s.name for s in substances]
```

Let's see how the species are pretty-printed:

```python
show(', '.join([s.latex_name for s in substances]))
```

Let's define some initial concentrations. We will consider different amount of added ammonia in 10 mM solutions of $Cu^{2+}$:

```python
init_conc = defaultdict(float, {'H+': 1e-7, 'OH-': 1e-7, 'NH4+': 0,
                                'NH3': 1.0, 'Cu+2': 1e-2, 'H2O': 55.5})
```

Now, let us define the equilibria, data are from course material at Applied Physcial Chemistry, KTH Royal Insitiute of Technology.

```python
H2O_c = init_conc['H2O']
w_autop = Equilibrium({'H2O': 1}, {'H+': 1, 'OH-': 1}, 10**-14/H2O_c)
NH4p_pr = Equilibrium({'NH4+': 1}, {'H+': 1, 'NH3': 1}, 10**-9.26)
CuOH2_s = Equilibrium({'Cu(OH)2(s)': 1}, {'Cu+2': 1, 'OH-': 2}, 10**-18.8)
CuOH_B3 = Equilibrium({'Cu(OH)2(s)': 1, 'OH-': 1}, {'Cu(OH)3-': 1}, 10**-3.6)
CuOH_B4 = Equilibrium({'Cu(OH)2(s)': 1, 'OH-': 2}, {'Cu(OH)4-2': 1}, 10**-2.7)
Cu2OH2 = Equilibrium({'Cu+2': 2, 'H2O': 2}, {'Cu2(OH)2+2': 1, 'H+': 2}, 10**-10.6 / H2O_c**2)
CuNH3_B1 = Equilibrium({'CuNH3+2': 1}, {'Cu+2': 1, 'NH3': 1}, 10**-4.3)
CuNH3_B2 = Equilibrium({'Cu(NH3)2+2': 1}, {'Cu+2': 1, 'NH3': 2}, 10**-7.9)
CuNH3_B3 = Equilibrium({'Cu(NH3)3+2': 1}, {'Cu+2': 1, 'NH3': 3}, 10**-10.8)
CuNH3_B4 = Equilibrium({'Cu(NH3)4+2': 1}, {'Cu+2': 1, 'NH3': 4}, 10**-13.0)
CuNH3_B5 = Equilibrium({'Cu(NH3)5+2': 1}, {'Cu+2': 1, 'NH3': 5}, 10**-12.4)
equilibria = w_autop, NH4p_pr, CuNH3_B1, CuNH3_B2, CuNH3_B3, CuNH3_B4, CuNH3_B5, Cu2OH2, CuOH2_s, CuOH_B3, CuOH_B4
```

Let's see if we can print ``equilibria`` in a human-readable form:

```python
show(', '.join([s.latex_name for s in substances]))
show('~')
from math import log10
for eq in equilibria:
    ltx = eq.latex(dict(zip(substance_names, substances)))
    show(ltx + '~'*(80-len(ltx)) + 'lgK = {0:12.5g}'.format(log10(eq.param)))  # latex table would be better...
```

To keep our numerical treatment as simple as possible we will try to avoid representing
$Cu(OH)_2(s)$ explicitly (which is present in the three last equilibria). This is becuase the
system of equations change when precipitation sets in.

However, we do want to keep the two last equilibria, therefore we rewrite
those using the dissolution equilibria them only using dissolved species:

```python
new_eqs = CuOH2_s - CuOH_B3, CuOH2_s - CuOH_B4
[str(_) for _ in new_eqs]
```

Now it's time to exclude the precipitate species and replace the last three equilibria with our two new ones:

```python
#skip_subs, skip_eq = (4, 4) # (0, 0), (1, 1), (3, 3), (4, 4), (11, 9)
skip_subs, skip_eq = (1, 3)
simpl_subs = substances[:-skip_subs]
simpl_eq = equilibria[:-skip_eq] + new_eqs
simpl_c0 = {k.name: init_conc[k.name] for k in simpl_subs}
```

From the law of mass action we can from the equilbria and from the preservation of mass and charge formulate a non-linear system of equations:

```python
import sympy as sp
import numpy as np
sp.init_printing()
eqsys = EqSystem(simpl_eq, simpl_subs)
x, i, Ks = sp.symarray('x', eqsys.ns), sp.symarray('i', eqsys.ns), sp.symarray('K', eqsys.nr)
params = np.concatenate((i, Ks))
numsys_lin = NumSysLin(eqsys, backend=sp)
numsys_lin.f(x, params)
```

It turns out that the success of the numerical root finding process for above system of equations is terribly sensitive on the choice of the initial guess. We therefore reformulate the equations in terms of the logarithm of the concentrations:

```python
numsys_log = NumSysLog(eqsys, backend=sp)
f = numsys_log.f(x, params)
f
```

We can take a peek on the jacobian of this vector:

```python
sp.Matrix(1, len(f), lambda _, q: f[q]).jacobian(x)
```

The preservation equations of mass and charge actually contain a redundant equation, so currently our system is over-determined:

```python
len(f), eqsys.ns
```

We could cast the preservation equations into [reduced row echelon form](https://en.wikipedia.org/wiki/Row_echelon_form) (which would remove one equation), but for now we'll leave this be and rely on the Levenberg-Marquardt algorithm to solve our problem in a least-squares sense. (Levenberg-Marquardt uses [QR-decomposition](https://en.wikipedia.org/wiki/QR_decomposition) internally for which it is acceptable to have overdetermined systems).

Let's solve the equations for our inital concentrations:

```python
C, sol, sane = eqsys.root(simpl_c0, NumSys=NumSysLog)
assert sol['success'] and sane
C
```

Great, let's now vary the initial concentration of $NH_3$ and plot the equilibrium concentrations of our species:

```python
import numpy as np
plt.figure(figsize=(20,8))
NH3_varied = np.logspace(-4, 0, 200)
Cout_logC, extra, success = eqsys.roots(
    simpl_c0, NH3_varied, 'NH3', NumSys=NumSysLog, plot_kwargs={'latex_names': True})
all(success), sum([nfo['nfev'] for nfo in extra['info']]), sum([nfo['njev'] for nfo in extra['info']])
_ = plt.gca().set_ylim([1e-10, 60])
```

But the above diagram is only true if we are below the solubility limit of our neglected $\rm Cu(OH)_2(s)$.

Let's plot the solubility product in the same diagram:

```python
sol_prod = Cout_logC[:, eqsys.as_substance_index('Cu+2')]*Cout_logC[:, eqsys.as_substance_index('OH-')]**2
plt.figure(figsize=(20,6))
plt.loglog(NH3_varied, sol_prod, label='[$%s$][$%s$]$^2$' % (eqsys.substances['Cu+2'].latex_name,
                                                             eqsys.substances['OH-'].latex_name))
plt.loglog(NH3_varied, Cout_logC[:, eqsys.as_substance_index('H+')], ls=':',
           label='[$%s$]' % eqsys.substances['H+'].latex_name)
plt.loglog([NH3_varied[0], NH3_varied[-1]], [10**-18.8, 10**-18.8], 'k--', label='$K_{sp}(Cu(OH)_2(s))$')
plt.xlabel('[$NH_3$]')
_ = plt.legend()
```

We see that for a ammonia concentration exceeding ~500-600 mM we would not precipitate $Cu(OH)_2(s)$ even though our pH is quite high (almost 12).

We have solved the above system of equations for the *logarithm* of the concentrations. How big are our absolute and relative errors compared to the linear system? Let's plot them:

```python
fig, axes = plt.subplots(1, 2, figsize=(20, 8))
eqsys.plot_errors(Cout_logC, simpl_c0, NH3_varied, 'NH3', axes=axes)
```

Not bad. So the problem is essentially solved. But let's say that it is very important to know the exact position of the intersection for the solutbility limit. We can locate it using the secant method:

```python
from scipy.optimize import newton
convergence = []
def sol_lim(c_NH3):
    c0 = simpl_c0.copy()
    c0['NH3'] = c_NH3
    C, sol, sane = eqsys.root(c0, NumSys=NumSysLog)
    assert sol['success'] and sane
    prod = C[eqsys.as_substance_index('Cu+2')]*C[eqsys.as_substance_index('OH-')]**2
    discrepancy = prod/10**-18.8 - 1
    convergence.append(discrepancy)
    return discrepancy
Climit_NH3 = newton(sol_lim, 0.5)
convergence = np.array(convergence).reshape((len(convergence), 1))
print(convergence)
Climit_NH3
```

For fun, let's see what the equation system looks like if we canonicalize it by transforming the equations for equibliria and the equations for the preservation relations to their respective reduced row echelon form:

```python
numsys_log_rref = NumSysLog(eqsys, rref_equil=True, rref_preserv=True, backend=sp)
rf = numsys_log_rref.f(x, params)
rf
```

So the Jacobian should be considerably more diagonally dominant now:

```python
sp.Matrix(1, len(rf), lambda _, q: rf[q]).jacobian(x)
```

And let's see if this system converges as well:

```python
plt.figure(figsize=(20,8))
out = eqsys.roots(simpl_c0, NH3_varied, 'NH3', rref_equil=True,
                  rref_preserv=True, NumSys=NumSysLog, plot_kwargs={'latex_names': True})
_ = plt.gca().set_ylim([1e-10, 60])
```

Sinvce version 0.2.0 of chempy there is support for automatic reformulation of the system of equations when precipitation occurs:

```python
full_eqsys = EqSystem(equilibria[:-2] + new_eqs, substances)
full_numsys_log_rref = NumSysLog(full_eqsys, rref_equil=False, rref_preserv=False, precipitates=[False], backend=sp)
full_x, full_i, full_Ks = sp.symarray('x', full_eqsys.ns), sp.symarray('i', full_eqsys.ns), sp.symarray('K', full_eqsys.nr)
full_rf = full_numsys_log_rref.f(full_x, np.concatenate((full_i, full_Ks)))
full_rf
```

```python
def solve_and_plot_full(NumSys, plot_kwargs={'latex_names': True}, **kwargs):
    plt.figure(figsize=(18, 7))
    result = full_eqsys.roots(init_conc, NH3_varied, 'NH3', NumSys=NumSys, plot_kwargs=plot_kwargs or {}, **kwargs)

    try:
        cur_val = None
        for idx, condition in enumerate([nfo['intermediate_info'][0]['conditions'] for nfo in result[1]['info']]):
            any_precip = condition != (False,)*len(condition)
            if cur_val is None:
                if any_precip:
                    onset = idx
            elif cur_val == any_precip:
                pass
            else:
                if any_precip:
                    onset = idx
                else:
                    plt.axvspan(NH3_varied[onset], NH3_varied[idx], facecolor='gray', alpha=0.1)
                    onset = None
            cur_val = any_precip
        if onset is not None:
            plt.axvspan(NH3_varied[onset], NH3_varied[-1], facecolor='gray', alpha=0.1)
    except KeyError:
        pass
    return result
```

```python
xout_log, sols_log, sane_log = solve_and_plot_full(NumSysLog, solver='scipy', tol=1e-10, conditional_maxiter=30,
                                                   rref_equil=True, rref_preserv=True, method='lm')
plt.gca().set_ylim([1e-10, 60])
plt.gca().set_xscale('log')
plt.gca().set_yscale('log')
```

We see that the numerical solution is not perfect (it could probably be improved by scaling the components individually). But the principle is clear: we can solve the solve non-linear system of equations using this method.

Let's see if we can gain some more insight here:

```python
def sum_species(x, species, substance_names, weights=None):
    accum = np.zeros(x.shape[0])
    if weights is None:
        weights = [1]*len(substance_names)
    for idx in map(substance_names.index, species):
        accum += x[:, idx]*weights[idx]
    return accum

def plot_groups(varied, x):
    substance_names = list(full_eqsys.substances.keys())
    weights = [s.composition.get(atomic_number('Cu'), ) for s in full_eqsys.substances.values()]
    amines = sum_species(x, NH3_complexes, substance_names, weights)
    hydroxides = sum_species(x, OH_complexes, substance_names, weights)
    free = sum_species(x, ['Cu+2'], substance_names, weights)
    precip = sum_species(x, ['Cu(OH)2(s)'], substance_names, weights)

    tot = amines + hydroxides + free + precip

    plt.figure(figsize=(13.7, 7))
    plt.plot(NH3_varied, tot, label='tot')
    plt.plot(NH3_varied, amines, label='amines')
    plt.plot(NH3_varied, hydroxides, label='hydroxides')
    plt.plot(NH3_varied, free, label='free')
    plt.plot(NH3_varied, precip, label='precip')
    plt.legend(loc='best')
    plt.gca().set_xscale('log')

plot_groups(NH3_varied, xout_log)
```

Without any precipitates (we force the system to not precipitate any solids, applicable for short timescales for example):

```python
xout_static, sols_static, sane_static = solve_and_plot_full(
    NumSysLog, solver='scipy', tol=1e-12, # , NumSysSquare
    neqsys_type='static_conditions',
    rref_equil=True, rref_preserv=True,
    precipitates=(False,), method='lm', plot_kwargs=None
)
plt.gca().set_xscale('log')
plt.gca().set_yscale('log')
plt.gca().set_ylim([1e-10, 60])

plot_groups(NH3_varied, xout_static)
```


# Custom Models in pycalphad: Viscosity

## Viscosity Model Background

We are going to take a CALPHAD-based property model from the literature and use it to predict the viscosity of Al-Cu-Zr liquids.

For a binary alloy liquid under small undercooling, Gąsior suggested an entropy model of the form
$$\eta = (\sum_i x_i \eta_i ) (1 - 2\frac{S_{ex}}{R})$$

where $\eta_i$ is the viscosity of the element $i$, $x_i$ is the mole fraction, $S_{ex}$ is the excess entropy, and $R$ is the gas constant.

For more details on this model, see

1. M.E. Trybula, T. Gancarz, W. Gąsior, *Density, surface tension and viscosity of liquid binary Al-Zn and ternary Al-Li-Zn alloys*, Fluid Phase Equilibria 421 (2016) 39-48, [doi:10.1016/j.fluid.2016.03.013](http://dx.doi.org/10.1016/j.fluid.2016.03.013).

2. Władysław Gąsior, *Viscosity modeling of binary alloys: Comparative studies*, Calphad 44 (2014) 119-128, [doi:10.1016/j.calphad.2013.10.007](http://dx.doi.org/10.1016/j.calphad.2013.10.007).

3. Chenyang Zhou, Cuiping Guo, Changrong Li, Zhenmin Du, *Thermodynamic assessment of the phase equilibria and prediction of glass-forming ability of the Al–Cu–Zr system*, Journal of Non-Crystalline Solids 461 (2017) 47-60, [doi:10.1016/j.jnoncrysol.2016.09.031](https://doi.org/10.1016/j.jnoncrysol.2016.09.031).

```python
from pycalphad import Database
```

## TDB Parameters
We can calculate the excess entropy of the liquid using the Al-Cu-Zr thermodynamic database from Zhou et al.

We add three new parameters to describe the viscosity (in Pa-s) of the pure elements Al, Cu, and Zr:
```
   $ Viscosity test parameters
   PARAMETER ETA(LIQUID,AL;0) 2.98150E+02  +0.000281*EXP(12300/(8.3145*T));   6.00000E+03
  N REF:0 !
   PARAMETER ETA(LIQUID,CU;0) 2.98150E+02  +0.000657*EXP(21500/(8.3145*T));   6.00000E+03
  N REF:0 !
  PARAMETER ETA(LIQUID,ZR;0) 2.98150E+02  +4.74E-3 - 4.97E-6*(T-2128) ;   6.00000E+03
    N REF:0 !
```

Great! However, if we try to load the database now, we will get an error. This is because `ETA` parameters are not supported by default in pycalphad, so we need to tell pycalphad's TDB parser that "ETA" should be on the list of supported parameter types.

```python
dbf = Database('alcuzr-viscosity.tdb')
```

### Adding the `ETA` parameter to the TDB parser

```python
import pycalphad.io.tdb_keywords
pycalphad.io.tdb_keywords.TDB_PARAM_TYPES.append('ETA')
```

Now the database will load:

```python
dbf = Database('alcuzr-viscosity.tdb')
```

## Writing the Custom Viscosity Model

Now that we have our `ETA` parameters in the database, we need to write a `Model` class to tell pycalphad how to compute viscosity. All custom models are subclasses of the pycalphad `Model` class.

When the `ViscosityModel` is constructed, the `build_phase` method is run and we need to construct the viscosity model after doing all the other initialization using a new method `build_viscosity`. The implementation of `build_viscosity` needs to do four things:
1. Query the Database for all the `ETA` parameters
2. Compute their weighted sum
3. Compute the excess entropy of the liquid
4. Plug all the values into the Gąsior equation and return the result

Since the `build_phase` method sets the attribute `viscosity` to the `ViscosityModel`, we can access the property using `viscosity` as the output in pycalphad caluclations.

```python
from tinydb import where
import sympy
from pycalphad import Model, variables as v

class ViscosityModel(Model):
    def build_phase(self, dbe):
        super(ViscosityModel, self).build_phase(dbe)
        self.viscosity = self.build_viscosity(dbe)

    def build_viscosity(self, dbe):
        if self.phase_name != 'LIQUID':
            raise ValueError('Viscosity is only defined for LIQUID phase')
        phase = dbe.phases[self.phase_name]
        param_search = dbe.search
        # STEP 1
        eta_param_query = (
            (where('phase_name') == phase.name) & \
            (where('parameter_type') == 'ETA') & \
            (where('constituent_array').test(self._array_validity))
        )
        # STEP 2
        eta = self.redlich_kister_sum(phase, param_search, eta_param_query)
        # STEP 3
        excess_energy = self.GM - self.models['ref'] - self.models['idmix']
        #liquid_mod = Model(dbe, self.components, self.phase_name)
        ## we only want the excess contributions to the entropy
        #del liquid_mod.models['ref']
        #del liquid_mod.models['idmix']
        excess_entropy = -excess_energy.diff(v.T)
        ks = 2
        # STEP 4
        result = eta * (1 - ks * excess_entropy / v.R)
        self.eta = eta
        return result
```

## Performing Calculations

Now we can create an instance of `ViscosityModel` for the liquid phase using the `Database` object we created earlier. We can verify this model has a `viscosity` attribute containing a symbolic expression for the viscosity.

```python
mod = ViscosityModel(dbf, ['CU', 'ZR'], 'LIQUID')
print(mod.viscosity)
```

    (1 + 0.240543628600637*(LIQUID0CU*LIQUID0ZR*(LIQUID0CU - LIQUID0ZR)**3*Piecewise((105.895 - 13.6488*log(T), (T >= 298.15) & (T < 6000.0)), (0, True)) + LIQUID0CU*LIQUID0ZR*(LIQUID0CU - LIQUID0ZR)**2*Piecewise((36.8512*log(T) - 270.5305, (T >= 298.15) & (T < 6000.0)), (0, True)) + LIQUID0CU*LIQUID0ZR*(LIQUID0CU - LIQUID0ZR)*Piecewise((75.3798 - 9.6125*log(T), (T >= 298.15) & (T < 6000.0)), (0, True)) + LIQUID0CU*LIQUID0ZR*Piecewise((392.8485 - 51.3121*log(T), (T >= 298.15) & (T < 6000.0)), (0, True)))/(1.0*LIQUID0CU + 1.0*LIQUID0ZR))*(LIQUID0CU*Piecewise((0.000657*exp(2585.84400745685/T), (T >= 298.15) & (T < 6000.0)), (0, True)) + LIQUID0ZR*Piecewise((0.01531616 - 4.97e-6*T, (T >= 298.15) & (T < 6000.0)), (0, True)))

Finally we calculate and plot the viscosity.

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

mod = ViscosityModel(dbf, ['CU', 'ZR'], 'LIQUID')

temp = 2100
# NOTICE: we need to tell pycalphad about our model for this phase
models = {'LIQUID': mod}
res = calculate(dbf, ['CU', 'ZR'], 'LIQUID', P=101325, T=temp, model=models, output='viscosity')

fig = plt.figure(figsize=(6,6))
ax = fig.gca()
ax.scatter(res.X.sel(component='ZR'), 1000 * res.viscosity.values)
ax.set_xlabel('X(ZR)')
ax.set_ylabel('Viscosity (mPa-s)')
ax.set_xlim((0,1))
ax.set_title('Viscosity at {}K'.format(temp));
```

We repeat the calculation for Al-Cu.

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

temp = 1300
models = {'LIQUID': ViscosityModel}  # we can also use Model class
res = calculate(dbf, ['CU', 'AL'], 'LIQUID', P=101325, T=temp, model=models, output='viscosity')

fig = plt.figure(figsize=(6,6))
ax = fig.gca()
ax.scatter(res.X.sel(component='CU'), 1000 * res.viscosity.values)
ax.set_xlabel('X(CU)')
ax.set_ylabel('Viscosity (mPa-s)')
ax.set_xlim((0,1))
ax.set_title('Viscosity at {}K'.format(temp));
```

```python

```


# Clase 4a: Ecuaciones diferenciales ordinarias (EDOs)

_¿Te acuerdas de todos esos esquemas numéricos para integrar ecuaciones diferenciales ordinarias? Es bueno saber que existen y qué peculiaridades tiene cada uno, pero en este curso no queremos implementar esos esquemas: queremos resolver las ecuaciones. Los problemas de evolución están por todas partes en ingeniería y son de los más divertidos de programar._

Nos basaremos en algunos de los ejemplos de https://mat.caminos.upm.es/wiki/Categor%C3%ADa:Ecuaciones_Diferenciales

```python
import numpy as np
```

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

## Decaimiento exponencial y oscilador armónico

### Fundamentos

Para integrar EDOs vamos a usar la función `odeint` del paquete `integrate`, que permite integrar sistemas del tipo:

$$ \frac{d\mathbf{y}}{dt}=\mathbf{f}\left(\mathbf{y},t\right)$$

con condiciones iniciales $\mathbf{y}(\mathbf{0}) = \mathbf{y_0}$.

**¡Importante!**: La función del sistema recibe como primer argumento $\mathbf{y}$ (un array) y como segundo argumento el instante $t$ (un escalar). Esta convención va exactamente al revés que en MATLAB y si se hace al revés obtendremos errores o, lo que es peor, resultados incorrectos.

```python
from scipy.integrate import odeint
```

Vamos a integrar primero una EDO elemental, cuya solución ya conocemos:

$$y' + y = 0$$

$$f(y, t) = \frac{dy}{dt} = -y$$

```python
def f(y, t):
    return -y
```

Condiciones iniciales:

```python
y0 = 1
```

Vector de tiempos donde realizamos la integración:

```python
t = np.linspace(0, 3)
```

Integramos y representamos la solución:

```python
sol = odeint(f, y0, t)
plt.plot(t, sol)
```

### EDOs de orden superior

Tendremos que acordarnos ahora de cómo reducir las ecuaciones de orden. De nuevo, vamos a probar con un ejemplo académico:

$$y + y'' = 0$$

$$\mathbf{y} \leftarrow \pmatrix{y \\ y'}$$

$$\mathbf{f}(\mathbf{y}) = \frac{d\mathbf{y}}{dt} =  \pmatrix{y \\ y'}' = \pmatrix{y' \\ y''} = \pmatrix{y' \\ -y}$$

```python
def f(y):
    return np.array([y[1], -y[0]])

t = np.linspace(0, 10)
y0 = np.array([1.0, 0.0])

sol = odeint(f, y0, t)

plt.plot(t, sol[:, 0], label='$y$')
plt.plot(t, sol[:, 1], '--k', label='$\dot{y}$')
plt.legend()
```

## Ecuación de vigas: Modelo de Euler-Bernoulli

*Problema tomado de https://mat.caminos.upm.es/wiki/Ecuaci%C3%B3n_de_vigas:_Modelo_de_Euler-Bernoulli_%2813A%29, por María Aguilera, Paula Martínez, Miguel Sánchez, Laura García, Isabel Roselló y Sarah Boufounas*

¿Qué puede haber más caminero que resolver la ecuación de la viga? En esta sección vamos a estudiar el comportamiento de una viga de 10 metros de longitud y sección rectangular al someterla a la acción de diferentes esfuerzos y variar las dimensiones de su sección transversal, y luego calcularemos la respuesta de esta misma viga al cambiar sus condiciones de apoyo.

En este caso, supondremos que el desplazamiento de la viga $y(x)$ y el momento flector $M(x)$ cumplen la curva elástica:

\begin{equation}
y''=\frac{M(x)}{E I(x)}
\end{equation}

### Viga biapoyada sometida a la acción de momentos flectores

Datos:

* $E=5 \cdot 10^4$
* $a=0.6$, $b=0.3$
* $M(x)=\frac{L}{2}−|x−\frac{L}{2}|$

Nos acordamos de la expresión para el momento de inercia:

\begin{equation}
I=\frac{1}{12} b a^3
\end{equation}

(Luego veremos un caso en el que las dimensiones no son constantes a lo largo de la longitud)

En este caso las condiciones de contorno son:

\begin{align}
y(0) & = 0 \\
y(L) & = 0
\end{align}

Como **ya no tenemos un problema de evolución sino un problema de contorno** hay que escoger un método apropiado, así que nosotros vamos a usar el método del disparo (_shooting method_). ¿Por qué?

* Porque en Matewiki está resuelto por diferencias finitas
* Porque así podemos aprovechar lo que ya sabemos de problemas de evolución
* Porque en el fondo a nadie le gusta ensamblar matrices :)

Como no tenemos todas las condiciones iniciales, el método del disparo consiste en:

1. Probar una solución inicial.
2. Resolver el problema y comprobar si el otro extremo coincide con la condición de contorno dada.
3. Iterar hasta converger.

¡Vamos allá!

En primer lugar, probamos a resolver el problema con estas condiciones iniciales:

\begin{align}
y(0) & = 0 \\
y'(0) & = 5 \cdot 10^{-3}
\end{align}

Para comprobar que la solución no coincide con lo que buscamos.

```python
def f(y, x):
    return np.array([y[1], M(x) / (E * I(x))])

# Datos
L = 10
E = 5e4
a, b = 0.6, 0.3

# Dominio
x = np.linspace(0, L, num=50)

# Inercia
def I(x):
    return b * a**3 / 12

# Momento flector
def M(x):
    return L/2 - np.abs(x - L/2)

# Probamos solución
yp0 = -5e-2  # Prueba
y0 = np.array([0, yp0])

sol = odeint(f, y0, x)

plt.plot(t, sol[:, 0], label='$y$')
sol[-1, :]
```

Ahora iteramos sobre el desplazamiento en $L$ para converger a una solución buena. Para ello, vamos a crear una función:

* **Entrada**: $\tilde{y}'(0)$
* **Salida**: $y(L)$

```python
def viga_biap(yp0):
    y0 = np.array([0, yp0])
    sol = odeint(f, y0, x)
    return sol[-1, 0]

viga_biap(5e-2)
```

    0.96296297642903061

Y ahora la resolvemos utilizando el [método de la bisección](http://pybonacci.org/2012/04/18/ecuaciones-no-lineales-metodo-de-biseccion-y-metodo-de-newton-en-python/) o alguna función de `scipy.optimize`.

```python
from scipy.optimize import newton
newton(viga_biap, 5e-2)
```

    -0.046296287367577141

¡Y ya lo tenemos!

```python
yp0 = newton(viga_biap, 5e-2)
y0 = np.array([0, yp0])

sol = odeint(f, y0, x)

plt.plot(t, sol[:, 0], label='$y$')
```

## Para ampliar

* En nuestra edición anterior del curso de AeroPython puedes ver una aplicación muy interesante de lo que hemos visto hasta ahora al [**salto de Felix Baumgartner**](http://nbviewer.ipython.org/github/AeroPython/Curso_AeroPython/blob/v1.0/Notebooks/Clase6b_Finale.ipynb):

$$\displaystyle m \frac{d^2 y}{d t^2} = -m g + D$$

* Nuestro compañero Álex escribió un fenomenal artículo en Pybonacci sobre [el modelo presa depredador de Lotka Volterra](http://pybonacci.org/2015/01/05/ecuaciones-de-lotka-volterra-modelo-presa-depredador/):

\begin{align*}
\frac{dx}{dt} &= \alpha x - \beta x y \\
\frac{dy}{dt} &= -\gamma y + \delta y x
\end{align*}

* Puedes [modelizar problemas en derivadas parciales utilizando el método de los elementos finitos en Python con FEniCS](http://pybonacci.org/2015/01/20/fenics-resolucion-de-ecuaciones-diferenciales-en-python/), como la ecuación de Poisson:

\begin{equation*}
-\nabla^2 u = f \quad u \in \Omega.
\end{equation*}

---

Clase en vídeo, parte del [Curso de Python para científicos e ingenieros](http://cacheme.org/curso-online-python-cientifico-ingenieros/) grabado en la Escuela Politécnica Superior de la Universidad de Alicante.

```python
from IPython.display import YouTubeVideo

YouTubeVideo("1R_JnTajrRY", width=560, height=315, list="PLGBbVX_WvN7bMwYe7wWV5TZt1a58jTggB")
```

---

Si te ha gustado esta clase:

Tweet

---

####
¡Síguenos en Twitter!

###### Follow @AeroPython

#####
Curso AeroPython por Juan Luis Cano Rodriguez y Alejandro Sáez Mollejo se distribuye bajo una Licencia Creative Commons Atribución 4.0 Internacional.

#####

---
_Las siguientes celdas contienen configuración del Notebook_

_Para visualizar y utlizar los enlaces a Twitter el notebook debe ejecutarse como [seguro](http://ipython.org/ipython-doc/dev/notebook/security.html)_

    File > Trusted Notebook

```python
%%html
<a href="https://twitter.com/AeroPython" class="twitter-follow-button" data-show-count="false">Follow @AeroPython</a>

```

Follow @AeroPython

```python
# Esta celda da el estilo al notebook
from IPython.core.display import HTML
css_file = '../static/styles/style.css'
HTML(open(css_file, "r").read())
```

<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro|Josefin+Sans:400,700,400italic|Ubuntu+Condensed' rel='stylesheet' type='text/css'>

El estilo se ha aplicado =)


In graduate school I came up with an original proof for [Kraft's inequality](http://en.wikipedia.org/wiki/Kraft%27s_inequality). The proofs I could find in textbooks tend to be more complicated and less intutive to me, and so I'd like to share my proof here.

## Prefix Codes

First it is important to understand the concept of [prefix codes](http://en.wikipedia.org/wiki/Prefix_code). Prefix codes have nice properties and are widely used in applications such as JPEG encoding for images and MP3 for music. A prefix code is such that no codeword is a prefix of any other codewords. For instance, suppose we'd like to encode the letters $\{a, b, c, d\}$ in binaries, i.e. using $\{0, 1\}$. Then one possible coding scheme is as follows:

\begin{equation}
\left\{
\begin{aligned}
    a & \rightarrow 0 \\
    b & \rightarrow 01 \\
    c & \rightarrow 011 \\
    d & \rightarrow 0111 \\
\end{aligned}
\right.
\end{equation}

This is uniquely decodable but it is not a prefix code because the codeword for $a$ is a prefix for the codeword for $b$. This means that we can not instantaneously decode $a$ without waiting for the next bit of data (to determine whether it is actually $a$ or just the first half of $b$.)

Alternatively, we can encode using a prefix code as folows:

\begin{equation}
\left\{
\begin{aligned}
    a & \rightarrow 0 \\
    b & \rightarrow 10 \\
    c & \rightarrow 110 \\
    d & \rightarrow 111 \\
\end{aligned}
\right.
\end{equation}

As you can see no codeword is a prefix of another codeword. Naturally we'd like to know how to construct prefix codes efficiently, and what are the fundamental constraints on the lengths of the codewords.

## Kraft's Inequality

Kraft's inequality states that given a list of positive integers $(n_1, n_2, \dots n_r)$, there exists a prefix code with a set of codewords $(\sigma_1, \sigma_2, \dots \sigma_r)$ where the length of each codeword $|\sigma_i| = n_i, \forall i$, if and only if

\begin{equation}
    \sum_{i=1}^{r} s^{-n_i} \leq 1
\end{equation}

where $s$ is the size of the alphabet $S$.

In our previous example, the alphabet is simply the binary digits $S = \{0, 1\}$, and therefore the size of the alphabet $s = 2$. We can easily check that indeed the inequality holds:

\begin{equation}
    2^{-1} + 2^{-2} + 2^{-3} + 2^{-3} \leq 1
\end{equation}

## Simple Proof

First of all without loss of generality we can order $n_i$ so that

\begin{equation}
    n_1 \leq n_2 \leq \dots \leq n_r
\end{equation}

Now we try to construct a prefix code in increasing order, i.e., $i = 1, 2, \dots r$. There exists a prefix code if and only if at each step $j$, there is at least one codeword to choose that does not contain any of the previous $j - 1$ codewords as a prefix. In other words, the codeword $\sigma_j$ must not contain any of the previous codewords $(\sigma_1, \sigma_2, \dots \sigma_{j-1})$ as a prefix. For $\sigma_j$, there are $s^{n_j}$ combination of codewords we could choose from if there was no prefix constraints. But due to the codeword at a prior step $k < j$, $s^{n_j - n_k}$ codewords are now forbidden because they contain $\sigma_k$ as a prefix. Furthermore, the sets of forbidden codewords due to different prior steps are exclusive. Since if any two prior steps forbid the same codeword at step $j$, that would imply the smaller of the codewords in the two prior steps is a prefix of the other one, which contradicts the prefix assumption.

Therefore we know the total number of forbidden codewords at step $j$ is

\begin{equation}
    \sum_{i=1}^{j-1} s^{n_j - n_i}
\end{equation}

There exists a prefix code if and only if we have a codeword to choose at every step, namely,

\begin{equation}
    s^{n_j} > \sum_{i=1}^{j-1} s^{n_j - n_i}, \forall j = 2, 3, \dots r
\end{equation}

Since every term above is an integer, this is equivalent to

\begin{equation}
    s^{n_j} \geq \sum_{i=1}^{j-1} s^{n_j - n_i} + 1 = \sum_{i=1}^{j} s^{n_j - n_i}, \forall j = 1, 2, \dots r
\end{equation}

Now divide by $s^{n_j}$ on both sides we get

\begin{equation}
    1 \geq \sum_{i=1}^{j} s^{- n_i}, \forall j = 1, 2, \dots r
\end{equation}

Now since every term above is non-negative, this is equivalent to Kraft's inequality

\begin{equation}
    \sum_{i=1}^{r} s^{- n_i} \leq 1
\end{equation}

Notice that every argument in the proof goes both ways. Thus this proves that Kraft's inequality is both a necessary and sufficient condition for the existance of a prefix code.

## McMillian Inequality

Interestingly, Kraft's inequality can be shown to hold for all uniquely decodable codes, not just prefix codes. This result is also known as the Kraft-McMillian inequality. To see that, consider a sequence of $k$ codewords from any uniquely decodable code. For instance, for a uniquely decodable code

\begin{equation}
\left\{
\begin{aligned}
    a & \rightarrow 0 \\
    b & \rightarrow 01 \\
    c & \rightarrow 011 \\
\end{aligned}
\right.
\end{equation}

all possible sequences of $k$ codewords for $k = 2$ would be

\begin{equation}
\left\{
\begin{aligned}
    aa & \rightarrow 00 \\
    ab & \rightarrow 001 \\
    ac & \rightarrow 0011 \\
    ba & \rightarrow 010 \\
    bb & \rightarrow 0101 \\
    bc & \rightarrow 01011 \\
    ca & \rightarrow 0110 \\
    cb & \rightarrow 01101 \\
    cc & \rightarrow 011011 \\
\end{aligned}
\right.
\end{equation}

Now consider the following expression

\begin{equation}
    \left(\sum_{i=1}^{r} s^{- n_i}\right)^k
\end{equation}

This is equivalent to

\begin{equation}
    \sum_{j=k \cdot n_1}^{k \cdot n_r} C_j s^{-j}
\end{equation}

where $C_j$ is the number of combinations of $k$ codewords that have a total length of $j$. We can see that $j$ must be at least $k \cdot n_1$ and at most $k \cdot n_r$. Since there is only $s^j$ number of unique sequences with a length of $j$, in order for this code to be uniquely decodable, we must have

\begin{equation}
    C_j \leq s^j
\end{equation}

Therefore we now have

\begin{aligned}
    \left(\sum_{i=1}^{r} s^{- n_i}\right)^k & = & \sum_{j=k \cdot n_1}^{k \cdot n_r} C_j s^{-j} \\
                                            & \leq & \sum_{j=k \cdot n_1}^{k \cdot n_r} s^j s^{-j} \\
                                            & = & k (n_r - n_1) + 1
\end{aligned}

which means

\begin{aligned}
    \sum_{i=1}^{r} s^{- n_i} \leq \left(k (n_r - n_1) + 1\right)^{1/k}
\end{aligned}

Since this is true for any $k$, we can let $k$ go to infinity and see that we must satifiy Kraft's inequality.

## Shannon Entropy

Kraft's inequality can be used to show that any uniquely decodable code must have an expected length larger than or equal to the [Shannon Entropy](http://en.wiktionary.org/wiki/Shannon_entropy). If the codeword $\sigma_i$ occurs with probability $p_i$, then the Shannon Entropy is given by

\begin{equation}
    \sum_{i=1}^{r} p_i \log_s \frac{1}{p_i}
\end{equation}

To see that the expected code length has to be bound by the Shannon entropy, consider the difference between the Shannon entropy and the expected length of the code:

\begin{aligned}
    \sum_{i=1}^{r} p_i \log_s \frac{1}{p_i} - \sum_{i=1}^{r} p_i n_i = & \sum_{i=1}^{r} p_i \log_s \frac{s^{-n_i}}{p_i} \\
    \leq & \ \log_s \left( \sum_{i=1}^{r} p_i \frac{s^{-n_i}}{p_i} \right) \\
    = & \ \log_s \left( \sum_{i=1}^{r} s^{-n_i} \right) \\
    \leq & \ \log_s 1 = 0 \\
\end{aligned}

where we simply use the fact that $\log_s$ is a concave function and apply Kraft's inequality.


# Thermodynamic Model to predict gene expression.

(c) 2020 Tom Röschinger. This work is licensed under a [Creative Commons Attribution License CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). All code contained herein is licensed under an [MIT license](https://opensource.org/licenses/MIT).

```python
import wgregseq
%load_ext autoreload
%autoreload 2

import numpy as np
import pandas as pd

import matplotlib.pyplot as plt

# Set default plotting style
wgregseq.plotting_style();
```

    The autoreload extension is already loaded. To reload it, use:
      %reload_ext autoreload

In this notebook we write down a thermodynamic model to predict gene expression for promoter sequences. The goal is to use these predictions, and try to identify locations of binding sites, without knowing the underlying energy matrix for the binding site.

### Input

The input into the model is the binding energy of a transcription factor. This binding energy is given by an energy matrix that might be created arbitrarily, and by the sequence, which might contain single mutations or scrambles. Since the binding energy is the only thing that is going to vary between the sequences,

## Simple repression motif

From [Chure et al., 2019](https://www.pnas.org/content/116/37/18275.short) the fold change in expression due to the simple repression motif is given by

\begin{equation}
    \text{fold-change} = \left( 1 + e^{-\beta\Delta \epsilon_{RA} +\log\left( R_A/N_{NS} \right) } \right)^{-1},
\end{equation}

where $R_A$ is the repressor copy number, $N_{NS}$ the number of non-specific binding sites and $\Delta \epsilon_{RA}$ the binding energy of the repressor to the specific site compared to non-specific background. Since we are interested in the change in binding energy due to mutation, let's look at that.

Labeling fold change by $f$, we can write (for the wild type)

$$
-\log (1/f^{\text{(wt)}} -1) + \log (R_A/N_{NS}) = \beta\Delta\epsilon_{RA}^{\text{(wt)}}.
$$

Now, let's write the difference in binding energies. A nice thing is that the repressor copy number and number of non-specific binding sites cancels,

$$
-\log (1/f^{\text{(wt)}} -1) + \log (1/f^{\text{(mut)}} -1) = \beta(\Delta\epsilon_{RA}^{\text{(wt)}} - \Delta\epsilon_{RA}^{\text{(mut)}}).
$$

```python

```


# ***Introduction to Radar Using Python and MATLAB***
## Andy Harrison - Copyright (C) 2019 Artech House
<br/>

# Reflection and Transmission
***

Referring to Section 2.6, practical radar problems involve waves propagating in bounded regions and interacting with media of differing constitutive parameters.  These media may include the targets of interest to the radar system as well as other regions such as rain, buildings, trees, and birds.  Therefore, it is beneficial to study the reflection and refraction of electromagnetic waves occurring when a wave traveling in a given medium impinges on another medium with a different set of constitutive parameters.

The reflection and transmission coefficients for perpendicular polarization are given by (Equations 2.102 and 2.103)

\begin{align}
\Gamma_{{\scriptstyle TE}} &= \frac{\eta_2 \cos \theta_i - \eta_1 \cos \theta_t}{\eta_2 \cos \theta_i + \eta_1 \cos \theta_t},\\ \nonumber \\
\mathrm{T}_{{\scriptstyle TE}} &= \frac{2\, \eta_2 \cos \theta_i}{\eta_2 \cos \theta_i + \eta_1 \cos \theta_t}\nonumber
\end{align}

The reflection and transmission coefficients for parallel polarization are given by (Equations 2.114 and 2.115)

\begin{align}
\Gamma_{{\scriptstyle TM}} &= \frac{\eta_2 \cos \theta_t - \eta_1 \cos \theta_i}{\eta_2 \cos \theta_t + \eta_1 \cos \theta_i} \label{eq:parallel_reflection_coefficient},\\ \nonumber \\
\mathrm{T}_{{\scriptstyle TM}} &= \frac{2 \, \eta_2 \cos \theta_i}{\eta_2 \cos \theta_t + \eta_1 \cos \theta_i}.
\end{align}
***

Begin by getting library path

```python
import lib_path
```

Set the operating frequency (Hz), th relative permittivity, the relative permeability, and the conductivity (S/m) for the different regions using the `array` routine from `scipy`

```python
from numpy import array

frequency = 300e6

relative_permittivity = array([1.3, 2.8])

relative_permeability = array([1.0, 1.0])

conductivity = array([0.01, 0.01])
```

Set up the keyword args

```python
kwargs = {'frequency':              frequency,

          'relative_permittivity':  relative_permittivity,

          'relative_permeability':  relative_permeability,

          'conductivity':           conductivity}
```

Calculate the critical angle and Brewster angle using the `plane_waves` routine from `wave_propagation`

```python
from Libs.wave_propagation import plane_waves

critical_angle = plane_waves.critical_angle(**kwargs)

brewster_angle = plane_waves.brewster_angle(**kwargs)

# Display the results

print('Critical Angle {:.1f}'.format(critical_angle))

print('Brewster Angle {:.1f}'.format(brewster_angle))
```

    Critical Angle 81.1+51.0j
    Brewster Angle 54.8+3.0j

Set the incident angles using the `linspace` routine from `scipy`

```python
from numpy import linspace

from scipy.constants import pi

incident_angle = linspace(0., 0.5 * pi, 1000)
```

Set up the keyword args

```python
kwargs = {'frequency': frequency,

          'incident_angle': incident_angle,

          'relative_permittivity': relative_permittivity,

          'relative_permeability': relative_permeability,

          'conductivity': conductivity}
```

Calculate the reflection and transmission coefficients using the `plane_waves` routines from `wave_propagation`

```python
reflection_coefficient_te, transmission_coefficient_te, reflection_coefficient_tm, transmission_coefficient_tm = plane_waves.reflection_transmission(**kwargs)
```

Display the reflection and transmission coefficients using the `matplotlib` routines

```python
from matplotlib import pyplot as plt

from numpy import degrees

# Set the figure size

plt.rcParams["figure.figsize"] = (15, 10)

# Set up the axes

fig, axes1 = plt.subplots()

axes2 = axes1.twinx()

# Display the reflection coefficients

axes1.plot(degrees(incident_angle), abs(reflection_coefficient_te), 'b', label='|$\Gamma_{TE}$|')

axes1.plot(degrees(incident_angle), abs(reflection_coefficient_tm), 'b--', label='|$\Gamma_{TM}$|')

# Display the transmission coefficients

axes2.plot(degrees(incident_angle), abs(transmission_coefficient_te), 'r', label='|$T_{TE}$|')

axes2.plot(degrees(incident_angle), abs(transmission_coefficient_tm), 'r--', label='|$T_{TM}$|')

# Set the plot title and labels

axes1.set_title('Plane Wave Reflection and Transmission', size=14)

axes1.set_xlabel('Incident Angle (degrees)', size=12)

axes1.set_ylabel('|Reflection Coefficient|', size=12)

axes2.set_ylabel('|Transmission Coefficient|', size=12)

# Set the tick label size

axes1.tick_params(labelsize=12)

axes2.tick_params(labelsize=12)

# Set the legend

axes1.legend(loc='upper right', prop={'size': 10})

axes2.legend(loc='upper left', prop={'size': 10})

# Turn on the grid

axes1.grid(linestyle=':', linewidth=0.5)
```