```python
from IPython.core.display import display_html
from urllib.request import urlopen

cssurl = 'http://j.mp/1DnuN9M'
display_html(urlopen(cssurl).read(), raw=True)
```

<link href='http://fonts.googleapis.com/css?family=EB+Garamond' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Lato:100,100italic' rel='stylesheet' type='text/css'>

<style>
	@font-face{
   		font-family: "Computer Modern";
        	src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');}

        div.cell{
	        max-width:    800px;
	        margin-left:  auto;
	        margin-right: auto;}

	h1 {
        	font-family: 'Lato', sans-serif;}

	h2 {
        	font-family: 'Lato', serif;}

	h3{
		font-family:   'Lato', serif;
        	margin-top:    12px;
		margin-bottom: 3px;}

        h4{
		font-family: 'Lato', serif;}

	h5 {
        	font-family: 'Lato', serif;}

	div.text_cell_render{
        	font-family:  'EB Garamond', 'Computer Modern', 'Helvetica Neue', Arial, Helvetica, Geneva, sans-serif;
	        line-height:  140%;
	        font-size:    14pt;
		max-width:    650px;
		margin-left:  auto;
        	margin-right: auto;}

	.CodeMirror{
        	font-family: 'Consolas';}

	.text_cell_render h1 {
	        font-weight:   100;
	        font-size:     48pt;
		line-height:   100%;
	        color:         #CD2305;
	        margin-bottom: 0.5em;
	        margin-top:    0.5em;
	        display:       block;}

	.text_cell_render h2 {
        	font-weight:   100;
        	font-size:     36pt;}

	.text_cell_render h3 {
        	font-weight:   100;
        	font-size:     21pt;}

	.text_cell_render h4 {
        	font-weight:   100;
        	font-size:     18pt;}

	.text_cell_render h5 {
	        font-weight:   100;
	        font-size:     16pt;
	        color:         #CD2305;
	        font-style:    italic;
	        margin-bottom: 0.5em;
	        margin-top:    0.5em;
	        display:       block;}

	.text_cell_render h6 {
	        font-weight:   100;
	        font-size:     14pt;
	        color:         #CD2305;
	        font-style:    italic;
	        margin-bottom: 0.5em;
	        margin-top:    0.5em;
	        display:       block;}

	.warning{
	    	background-color: #FFFFE0;
	    	border-color: #FFCC99;
	    	border-left: 5px solid #FFCC99;
	    	padding: 0.5em;}

	.error{
	    	background-color: #fcf2f2;
	    	border-color: #dFb5b4;
	    	border-left: 5px solid #dfb5b4;
	    	padding: 0.5em;}

</style>

# Modelado matemático y simulación de sistema carro-pendulo

## Problema

Dado el sistema de la siguiente figura:

describir matemáticamente su comportamiento ante perturbaciones externas.

## Modelado matemático

Utilizaremos el enfoque de Euler-Lagrange, el cual nos dice que el primer paso para conseguir el modelo matemático es calcular el Lagrangiano $L$ del sistema, definido por:

$$
L = K - U
$$

en donde $K$ es la energía cinética del sistema y $U$ es la energia potencial del sistema.

El estado del sistema estará descrito por una distancia $x$ del centro del carro a un marco de referencia y el angulo $\theta$ del pendulo con respecto a la horizontal.

$$
q = \begin{pmatrix} x \\ \theta \end{pmatrix} = \begin{pmatrix} q_1 \\ q_2 \end{pmatrix} \implies
\dot{q} = \begin{pmatrix} \dot{x} \\ \dot{\theta} \end{pmatrix} = \begin{pmatrix} \dot{q}_1 \\ \dot{q}_2 \end{pmatrix} \implies
\ddot{q} = \begin{pmatrix} \ddot{x} \\ \ddot{\theta} \end{pmatrix} = \begin{pmatrix} \ddot{q}_1 \\ \ddot{q}_2 \end{pmatrix}
$$

Para calcular la energía cinética del sistema, obtenemos $K_1$ y $K_2$ asociadas al carro y al pendulo, en donde $K_i = \frac{1}{2} m_i v_i^2$, por lo que tenemos:

$$
K_1 = \frac{1}{2} m_1 v_1^2 = \frac{1}{2} m_1 \dot{x}^2 = \frac{1}{2} m_1 \dot{q}_1^2
$$

$$
K_2 = \frac{1}{2} m_2 v_2^2 = \frac{1}{2} m_2 \left[ \left( \dot{x} + \dot{x}_2 \right)^2 + \dot{y}_2^2 \right]
$$

con $x_2 = l \cos{\theta}$ y $y_2 = l \sin{\theta}$, por lo que sus derivadas son $\dot{x}_2 = -\dot{\theta} l \sin{\theta}$ y $\dot{y}_2 = \dot{\theta} l \cos{\theta}$, por lo que $K_2$ queda:

$$
\begin{align}
K_2 &= \frac{1}{2} m_2 \left[ \left( \dot{x} -\dot{\theta} l \sin{\theta} \right)^2 + \left( \dot{\theta} l \cos{\theta} \right)^2 \right] \\
&= \frac{1}{2} m_2 \left[ \left( \dot{x} -\dot{\theta} l \sin{\theta} \right)^2 + \left( \dot{\theta} l \cos{\theta} \right)^2 \right] \\
&= \frac{1}{2} m_2 \left[ \left( \dot{q}_1 -\dot{q}_2 l \sin{q_2} \right)^2 + \left( \dot{q}_2 l \cos{q_2} \right)^2 \right]
\end{align}
$$

Confirmemos estos calculos:

```python
from IPython.display import display

from sympy import var, simplify, collect, expand, solve, sqrt, sin, cos, Matrix, Integer, diff, Function, Rational
from sympy.physics.mechanics import mlatex, mechanics_printing
mechanics_printing()
```

```python
var("l t m1 m2 g")
```

```python
q1 = Function("q_1")(t)
q2 = Function("q_2")(t)

x = Function("x")(t)
```

```python
x1 = q1
y1 = Integer(0)
v1 = sqrt(x1.diff(t)**2 + y1.diff(t)**2)
v1, v1**2
```

```python
x2 = q1 + l*cos(q2)
y2 = l*sin(q2)
v2 = sqrt(x2.diff(t)**2 + y2.diff(t)**2)
v2, v2**2
```

```python
k1 = Rational(1, 2)*m1*v1**2
k2 = Rational(1, 2)*m2*v2**2
k1, k2
```

```python
K = (k1 + k2).expand().trigsimp().factor(q1.diff(t), q2.diff(t))
K
```

Entonces la energía cinética será:

$$
K = \frac{1}{2} \left[ (m_1 + m_2) \dot{q}_1^2 + m_2 l^2 \dot{q}_2^2 - 2 m_2 l \sin{q_2} \dot{q}_1 \dot{q}_2 \right]
$$

Lo cual puede ser escrito como una forma matricial cuadratica:

$$
K = \frac{1}{2}
\begin{pmatrix}
\dot{q}_1 & \dot{q}_2
\end{pmatrix}
\begin{pmatrix}
m_1 + m_2 & -m_2 l \sin{q_2} \\
-m_2 l \sin{q_2} & m_2 l^2
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix}
$$

```python
M  = Matrix([[m1 + m2, -m2*l*sin(q2)],
             [-m2*l*sin(q2), m2*l**2]])
qp = Matrix([[q1.diff(t)],
             [q2.diff(t)]])

(Rational(1, 2)*qp.T*M*qp)[0].expand().trigsimp().factor(q1.diff(t), q2.diff(t))
```

En este punto, introducimos una variable intermedia, tan solo para reducir un poco la notación:

$$
\lambda = l \sin{q_2}
$$

y su derivada con respecto al tiempo, la representamos como:

$$
\dot{\lambda} = \lambda' \dot{q}_2 \implies \lambda' = l \cos{q_2}
$$

por lo que la energía cinetica queda como:

$$
K = \frac{1}{2}
\begin{pmatrix}
\dot{q}_1 & \dot{q}_2
\end{pmatrix}
\begin{pmatrix}
m_1 + m_2 & -m_2 \lambda \\
-m_2 \lambda & m_2 l^2
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix}
$$

en donde el termino matricial es la matriz de masa $M(q)$ y $K(q, \dot{q}) = \dot{q}^T M(q) \dot{q}$.

Por otro lado, para calcular la energía potencial del sistema, tenemos que:

$$
U_1 = m_1 g h_1 = 0
$$

$$
U_2 = m_2 g h_2 = m_2 g l \sin{q_1} = m_2 g \lambda
$$

por lo que la energía potencial del sistema será:

$$
U = m_2 g \lambda
$$

```python
u1 = 0
u2 = m2*g*l*sin(q2)
U = u1 + u2
U
```

Cuando aplicamos la primer condición de optimalidad al Lagrangiano $L(t, q(t), \dot{q}(t)) = K(q(t), \dot{q}(t)) - U(q)$, tenemos la ecuación de Euler-Lagrage, la cual nos dice que:

$$
\frac{d}{dt} L_{\dot{q}} - L_q = 0
$$

por lo que debemos encontrar la derivada del Lagrangiano, con respecto a $q$, $\dot{q}$ y derivar esta ultima con respecto al tiempo. Empecemos con la derivada con respecto a $q$:

$$
L_q = K_q - U_q
$$

en donde:

$$
\begin{align}
K_q &= \frac{\partial}{\partial q} \left\{ \frac{1}{2} \left[ (m_1 + m_2) \dot{q}_1^2 + m_2 l^2 \dot{q}_2^2 - 2 m_2 \lambda \dot{q}_1 \dot{q}_2 \right] \right\} \\
&= \frac{1}{2}
\begin{pmatrix}
\frac{\partial}{\partial q_1} \left\{ \left[ (m_1 + m_2) \dot{q}_1^2 + m_2 l^2 \dot{q}_2^2 - 2 m_2 \lambda \dot{q}_1 \dot{q}_2 \right] \right\} \\
\frac{\partial}{\partial q_2} \left\{ \left[ (m_1 + m_2) \dot{q}_1^2 + m_2 l^2 \dot{q}_2^2 - 2 m_2 \lambda \dot{q}_1 \dot{q}_2 \right] \right\}
\end{pmatrix} \\
&= \frac{1}{2}
\begin{pmatrix}
0 \\
- 2 m_2 \lambda' \dot{q}_1 \dot{q}_2
\end{pmatrix} = - m_2 \lambda'
\begin{pmatrix}
0 & 0 \\
\dot{q}_2 & 0
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix}
\end{align}
$$

```python
Kq = Matrix([[K.diff(q1)], [K.diff(q2)]])
Kq
```

y la derivada de la energía potencial con respecto a $q$:

$$
U_q = \frac{\partial}{\partial q} \left\{ m_2 g \lambda \right\} =
\begin{pmatrix}
\frac{\partial}{\partial q_1} \left\{ m_2 g \lambda \right\} \\
\frac{\partial}{\partial q_2} \left\{ m_2 g \lambda \right\}
\end{pmatrix} =
\begin{pmatrix}
0 \\
m_2 g \lambda'
\end{pmatrix}
$$

```python
Uq = Matrix([[U.diff(q1)], [U.diff(q2)]])
Uq
```

```python
Lq = Kq - Uq
Lq
```

Ahora obtenemos la derivada con respecto a $\dot{q}$:

$$
L_{\dot{q}} = K_{\dot{q}} - U_{\dot{q}}
$$

en donde:

$$
K_{\dot{q}} = \frac{1}{2} \frac{\partial}{\partial \dot{q}} \left\{ \dot{q}^T M(q) \dot{q} \right\} = M(q) \dot{q}
$$

$$
U_{\dot{q}} = 0
$$

```python
Kqp = Matrix([[K.diff(q1.diff(t)).simplify()], [K.diff(q2.diff(t)).simplify()]])
Kqp
```

```python
M*qp
```

```python
Uqp = Matrix([[U.diff(q1.diff(t)).simplify()], [U.diff(q2.diff(t)).simplify()]])
Uqp
```

```python
Lqp = Kqp - Uqp
Lqp
```

Derivando con respeto al tiempo estas ultimas expresiones, obtenemos:

$$
\frac{d}{dt} K_{\dot{q}} = \dot{M}(q, \dot{q}) \dot{q} + M(q) \ddot{q}
$$

en donde

$$
\begin{align}
\dot{M}(q, \dot{q}) &= \frac{d}{dt} M(q) = \frac{d}{dt}
\begin{pmatrix}
m_1 + m_2 & -m_2 \lambda \\
-m_2 \lambda & m_2 l^2
\end{pmatrix} \\
&=
\begin{pmatrix}
0 & -m_2 \lambda' \dot{q}_2 \\
-m_2 \lambda' \dot{q}_2 & 0
\end{pmatrix} = -m_2 \lambda'
\begin{pmatrix}
0 & \dot{q}_2 \\
\dot{q}_2 & 0
\end{pmatrix}
\end{align}
$$

```python
M.diff(t)
```

```python
qpp = qp.diff(t)
qpp
```

```python
M*qpp + M.diff(t)*qp
```

```python
Kqp.diff(t) - Uqp.diff(t)
```

y ya tenemos todos los elementos que integran nuestra ecuación de Euler-Lagrange:

$$
\begin{align}
\frac{d}{dt} L_{\dot{q}} - L_q &= 0 \\
M(q) \ddot{q} + \dot{M}(q, \dot{q}) \dot{q} - K_q + U_q &= 0
\end{align}
$$

```python
Lqp.diff(t) - Lq
```

Tan solo cabe recalcar que el segundo y tercer termino se pueden reducir a uno solo:

$$
\begin{align}
\dot{M}(q, \dot{q}) \dot{q} - K_q &= -m_2 \lambda'
\begin{pmatrix}
0 & \dot{q}_2 \\
\dot{q}_2 & 0
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix} + m_2 \lambda'
\begin{pmatrix}
0 & 0 \\
\dot{q}_2 & 0
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix} \\
&= -m_2 \lambda'
\begin{pmatrix}
0 & \dot{q}_2 \\
0 & 0
\end{pmatrix}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix} = C(q, \dot{q})\dot{q}
\end{align}
$$

Por lo que finalmente tenemos:

$$
M(q) \ddot{q} + C(q, \dot{q}) \dot{q} + U_q = 0
$$

con:

$$
M(q) =
\begin{pmatrix}
m_1 + m_2 & -m_2 \lambda \\
-m_2 \lambda & m_2 l^2
\end{pmatrix} \quad
C(q, \dot{q}) = -m_2 \lambda'
\begin{pmatrix}
0 & \dot{q}_2 \\
0 & 0
\end{pmatrix} \quad
U_q =
\begin{pmatrix}
0 \\
m_2 g \lambda'
\end{pmatrix}
$$

```python
C = Matrix([[0, -m2*l*cos(q2)*q2.diff(t)], [0, 0]])
M*qpp + C*qp + Uq
```

## Comentario final del modelado

Sabemos que la energía total del sistema no cambia, es decir:

$$
\frac{dE}{dt} = 0
$$

y la energía total del sistema es $E = K + U$, lo cual atraves de  simplificación algebraica implica que:

$$
\dot{q}^T \left[ \dot{M}(q, \dot{q}) - 2 C(q, \dot{q}) \right] \dot{q} = 0
$$

por lo que una manera relativamente sencilla de comprobar que nuestros calculos son correctos es comprobar que esta matriz sea antisimetrica:

```python
M.diff(t) - 2*C
```

## Simulación

Para la simulación de este sistema podemos utilizar Simulink de MATLAB para introducir esta ecuación como un conjunto de bloques como los operadores matriciales e integradores. La formula que queremos usar es:

$$
\ddot{q} = M^{-1}(q)\left[ -C(q, \dot{q}) \dot{q} - U_q \right]
$$

El diagrama de bloques de esto se vería así:

Y una vez simulado esto, podemos guardar los datos de la simulación e importarlos aquí:

```python
# Se importa libreria de entrada y salida y se importan datos de simulacion en MATLAB
import scipy.io
datos = scipy.io.loadmat('./MATLAB/carropendulo.mat')
```

```python
# Se importan datos de simulación a variables de Python para poder manipularlas
datos_q1 = datos.get("q1")
datos_q2 = datos.get("q2")
```

```python
ts, simq1 = zip(*datos_q1)
ts, simq2 = zip(*datos_q2)
len(simq1), len(simq2)
```

Una vez importados los datos podemos ingenuamente graficarlos y tendriamos los puntos por los que pasa las dos masas:

```python
from matplotlib.pyplot import plot, style, figure
style.use("ggplot")
```

```python
from numpy import cos, sin, zeros, pi, arange, sqrt, linspace
```

```python
tau = 2*pi
```

```python
f = figure(figsize=(6, 6))
plot(simq1 + cos(simq2), sin(simq2), ".")
plot(simq1, zeros(len(simq1)), ".")
ax = f.gca()
ax.set_xlim(-0.5, 1.5)
ax.set_ylim(-1.5, 0.5);
f.savefig('./imagenes/tray.png')
```

Sin embargo es muy dificil encontrar un sentido físico a esto, ya que no vemos la trayectoria que sigue, por lo que podemos hacer algo aun mejor:

```python
from matplotlib import animation
from matplotlib.patches import Rectangle, Circle
```

```python
2.34*4
```

```python
fig = figure(figsize=(6, 6))

ax = fig.add_subplot(111, autoscale_on=False,
                     xlim=(-0.5, 1.5), ylim=(-1.5, 0.5))

linea, = ax.plot([], [], 'o-', lw=1.5, color='gray')
carro = Rectangle((10,10), 0.5, 0.25, lw=1.5, fc='white')

def init():
    linea.set_data([], [])
    carro.set_xy((-0.25, -0.125))
    ax.add_patch(carro)
    return linea, carro

def animate(i):
    thisy = [0, sin(simq2[i])]
    thisx = [simq1[i], simq1[i] + cos(simq2[i])]

    linea.set_data(thisx, thisy)
    carro.set_xy((simq1[i] - 0.25, -0.125))
    return linea, carro

ani = animation.FuncAnimation(fig, animate, arange(1, len(simq1)), interval=25,
                              blit=True, init_func=init)

ani.save('./imagenes/pendulum.gif', writer='imagemagick');
```

¡Esto si parece un pendulo en un carro!

Pero aun no estamos satisfechos, podemos hacerlo incluso sin MATLAB, lo único que tenemos que hacer es incrementar la dimensión del sistema para reducir el orden de la ecuación diferencial.

Nuestro estado aumentado del sistema será:

$$
\begin{pmatrix}
q_1 \\
q_2 \\
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix}
$$

Por lo que ahora tenemos que encontrar $f_1$, $f_2$, $f_3$ y $f_4$ tales que:

$$
\begin{align}
\frac{d}{dt}q_1 &= f_1(q_1, q_2, \dot{q}_1, \dot{q}_2) \\
\frac{d}{dt}q_2 &= f_2(q_1, q_2, \dot{q}_1, \dot{q}_2) \\
\frac{d}{dt}\dot{q}_1 &= f_3(q_1, q_2, \dot{q}_1, \dot{q}_2) \\
\frac{d}{dt}\dot{q}_2 &= f_4(q_1, q_2, \dot{q}_1, \dot{q}_2) \\
\end{align}
$$

Las primeras dos ecuaciones son faciles:

$$
\begin{align}
\frac{d}{dt}q_1 &= \dot{q}_1 \\
\frac{d}{dt}q_2 &= \dot{q}_2 \\
\end{align}
$$

y las otras dos, son las que ya teniamos:

$$
\frac{d}{dt}
\begin{pmatrix}
\dot{q}_1 \\
\dot{q}_2
\end{pmatrix} =
\ddot{q} = M^{-1}(q)\left[ -C(q, \dot{q}) \dot{q} - U_q \right]
$$

```python
def f(estado, tiempo):
    from numpy import zeros
    from numpy import matrix

    m1 = 1
    m2 = 1
    g = 9.81
    l = 1

    q1, q2, q1p, q2p = estado

    q = matrix([[q1], [q2]])
    qp = matrix([[q1p], [q2p]])

    lam = l*sin(q2)
    lamp = l*cos(q2)

    M = matrix([[m1 + m2, -m2*lam], [-m2*lam, m2*l**2]])
    C = -m2*lamp*matrix([[0, q2p], [0, 0]])
    U = matrix([[0], [m2*g*lamp]])

    qpp = M.I*(-C*qp - U)

    dydx = zeros(4)

    dydx[0] = q1p
    dydx[1] = q2p
    dydx[2] = qpp[0]
    dydx[3] = qpp[1]

    return dydx
```

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

```python
ts = linspace(0, 2.08, 100)
estado_inicial = [0, 0, 0, 0]
```

```python
estados = odeint(f, estado_inicial, ts)
q1, q2 = estados[:, 0], estados[:, 1]
```

```python
fig = figure(figsize=(8, 6))

ax = fig.add_subplot(111, autoscale_on=False,
                     xlim=(-0.8333, 1.8333), ylim=(-1.25, 0.75))

ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)

ax.axes.spines["right"].set_color("none")
ax.axes.spines["left"].set_color("none")
ax.axes.spines["top"].set_color("none")
ax.axes.spines["bottom"].set_color("none")

ax.set_axis_bgcolor('#F2F1EC')

linea, = ax.plot([], [], 'o-', lw=1.5, color='#393F40')
carro = Rectangle((10,10), 0.8, 0.35, lw=1.5, fc='#E5895C')
guia = Rectangle((10, 10), 2.6666, 0.1, lw=1.5, fc='#A4B187')
pendulo = Circle((10, 10), 0.125, lw=1.5, fc='#F3D966')

def init():
    linea.set_data([], [])
    guia.set_xy((-0.8333, -0.05))
    carro.set_xy((-0.4, -0.175))
    pendulo.center = (1, 0)
    ax.add_patch(guia)
    ax.add_patch(carro)
    ax.add_patch(pendulo)
    return linea, carro, pendulo

def animate(i):
    xs = [q1[i], q1[i] + cos(q2[i])]
    ys = [0, sin(q2[i])]

    linea.set_data(xs, ys)
    carro.set_xy((xs[0] - 0.4, ys[0] - 0.175))
    pendulo.center = (xs[1], ys[1])
    return linea, carro, pendulo

ani = animation.FuncAnimation(fig, animate, arange(1, len(q1)), interval=25,
                              blit=True, init_func=init)

ani.save('./imagenes/pendulumpython.gif', writer='imagemagick');
```

Espero te hayas divertido con esta larga explicación y al final sepas un truco mas.

Si deseas compartir este Notebook de IPython utiliza la siguiente dirección:

http://bit.ly/1M2tenc

o bien el siguiente código QR:

```python
# Codigo para generar codigo :)
from qrcode import make
img = make("http://bit.ly/1M2tenc")
img.save("./codigos/carropendulo.jpg")
```


# [ATM 623: Climate Modeling](../index.ipynb)

[Brian E. J. Rose](http://www.atmos.albany.edu/facstaff/brose/index.html), University at Albany

# Lecture 7: Elementary greenhouse models

### About these notes:

This document uses the interactive [`Jupyter notebook`](https://jupyter.org) format. The notes can be accessed in several different ways:

- The interactive notebooks are hosted on `github` at https://github.com/brian-rose/ClimateModeling_courseware
- The latest versions can be viewed as static web pages [rendered on nbviewer](http://nbviewer.ipython.org/github/brian-rose/ClimateModeling_courseware/blob/master/index.ipynb)
- A complete snapshot of the notes as of May 2017 (end of spring semester) are [available on Brian's website](http://www.atmos.albany.edu/facstaff/brose/classes/ATM623_Spring2017/Notes/index.html).

[Also here is a legacy version from 2015](http://www.atmos.albany.edu/facstaff/brose/classes/ATM623_Spring2015/Notes/index.html).

Many of these notes make use of the `climlab` package, available at https://github.com/brian-rose/climlab

```python
#  Ensure compatibility with Python 2 and 3
from __future__ import print_function, division
```

## Contents

1. [A single layer atmosphere](#section1)
2. [Introducing the two-layer grey gas model](#section2)
3. [Tuning the grey gas model to observations](#section3)
4. [Level of emission](#section4)
5. [Radiative forcing in the 2-layer grey gas model](#section5)
6. [Radiative equilibrium in the 2-layer grey gas model](#section6)
7. [Summary](#section7)

____________

## 1. A single layer atmosphere
____________

We will make our first attempt at quantifying the greenhouse effect in the simplest possible greenhouse model: a single layer of atmosphere that is able to absorb and emit longwave radiation.

### Assumptions

- Atmosphere is a single layer of air at temperature $T_a$
- Atmosphere is **completely transparent to shortwave** solar radiation.
- The **surface** absorbs shortwave radiation $(1-\alpha) Q$
- Atmosphere is **completely opaque to infrared** radiation
- Both surface and atmosphere emit radiation as **blackbodies** ($\sigma T_s^4, \sigma T_a^4$)
- Atmosphere radiates **equally up and down** ($\sigma T_a^4$)
- There are no other heat transfer mechanisms

We can now use the concept of energy balance to ask what the temperature need to be in order to balance the energy budgets at the surface and the atmosphere, i.e. the **radiative equilibrium temperatures**.

### Energy balance at the surface

\begin{align}
\text{energy in} &= \text{energy out} \\
(1-\alpha) Q + \sigma T_a^4 &= \sigma T_s^4 \\
\end{align}

The presence of the atmosphere above means there is an additional source term: downwelling infrared radiation from the atmosphere.

We call this the **back radiation**.

### Energy balance for the atmosphere

\begin{align}
\text{energy in} &= \text{energy out} \\
\sigma T_s^4 &= A\uparrow + A\downarrow = 2 \sigma T_a^4 \\
\end{align}

which means that
$$ T_s = 2^\frac{1}{4} T_a \approx 1.2 T_a $$

So we have just determined that, in order to have a purely **radiative equilibrium**, we must have $T_s > T_a$.

*The surface must be warmer than the atmosphere.*

### Solve for the radiative equilibrium surface temperature

Now plug this into the surface equation to find

$$ \frac{1}{2} \sigma T_s^4 = (1-\alpha) Q  $$

and use the definition of the emission temperature $T_e$ to write

$$ (1-\alpha) Q = \sigma T_e^4 $$

*In fact, in this model, $T_e$ is identical to the atmospheric temperature $T_a$, since all the OLR originates from this layer.*

Solve for the surface temperature:
$$ T_s = 2^\frac{1}{4} T_e $$

Putting in observed numbers, $T_e = 255$ K gives a surface temperature of
$$T_s = 303 ~\text{K}$$

This model is one small step closer to reality: surface is warmer than atmosphere, emissions to space generated in the atmosphere, atmosphere heated from below and helping to keep surface warm.

BUT our model now overpredicts the surface temperature by about 15ºC (or K).

Ideas about why?

Basically we just need to read our **list of assumptions** above and realize that none of them are very good approximations:

- Atmosphere absorbs some solar radiation.
- Atmosphere is NOT a perfect absorber of longwave radiation
- Absorption and emission varies strongly with wavelength *(atmosphere does not behave like a blackbody)*.
- Emissions are not determined by a single temperature $T_a$ but by the detailed *vertical profile* of air temperture.
- Energy is redistributed in the vertical by a variety of dynamical transport mechanisms (e.g. convection and boundary layer turbulence).

____________

## 2. Introducing the two-layer grey gas model
____________

Let's generalize the above model just a little bit to build a slighly more realistic model of longwave radiative transfer.

We will address two shortcomings of our single-layer model:
1. No vertical structure
2. 100% longwave opacity

Relaxing these two assumptions gives us what turns out to be a very useful prototype model for **understanding how the greenhouse effect works**.

### Assumptions

- The atmosphere is **transparent to shortwave radiation** (still)
- Divide the atmosphere up into **two layers of equal mass** (the dividing line is thus at 500 hPa pressure level)
- Each layer **absorbs only a fraction $\epsilon$ ** of whatever longwave radiation is incident upon it.
- We will call the fraction $\epsilon$ the **absorptivity** of the layer.
- Assume $\epsilon$ is the same in each layer

This is called the **grey gas** model, where grey here means the emission and absorption have no spectral dependence.

We can think of this model informally as a "leaky greenhouse".

Note that the assumption that $\epsilon$ is the same in each layer is appropriate if the absorption is actually carried out by a gas that is **well-mixed** in the atmosphere.

Out of our two most important absorbers:

- CO$_2$ is well mixed
- H$_2$O is not (mostly confined to lower troposphere due to strong temperature dependence of the saturation vapor pressure).

But we will ignore this aspect of reality for now.

In order to build our model, we need to introduce one additional piece of physics known as **Kirchoff's Law**:

$$ \text{absorptivity} = \text{emissivity} $$

So if a layer of atmosphere at temperature $T$ absorbs a fraction $\epsilon$ of incident longwave radiation, it must emit

$$ \epsilon ~\sigma ~T^4 $$

both up and down.

### A sketch of the radiative fluxes in the 2-layer atmosphere

- Surface temperature is $T_s$
- Atm. temperatures are $T_0, T_1$ where $T_0$ is closest to the surface.
- absorptivity of atm layers is $\epsilon$
- Surface emission is $\sigma T_s^4$
- Atm emission is $\epsilon \sigma T_0^4, \epsilon \sigma T_1^4$ (up and down)
- Absorptivity = emissivity for atmospheric layers
- a fraction $(1-\epsilon)$ of the longwave beam is **transmitted** through each layer

### A fun aside: symbolic math with the `sympy` package

This two-layer grey gas model is simple enough that we can work out all the details algebraically. There are three temperatures to keep track of $(T_s, T_0, T_1)$, so we will have 3x3 matrix equations.

We all know how to work these things out with pencil and paper. But it can be tedious and error-prone.

Symbolic math software lets us use the computer to automate a lot of tedious algebra.

The [sympy](http://www.sympy.org/en/index.html) package is a powerful open-source symbolic math library that is well-integrated into the scientific Python ecosystem.

```python
import sympy
#  Allow sympy to produce nice looking equations as output
sympy.init_printing()
#  Define some symbols for mathematical quantities
#  Assume all quantities are positive (which will help simplify some expressions)
epsilon, T_e, T_s, T_0, T_1, sigma = \
    sympy.symbols('epsilon, T_e, T_s, T_0, T_1, sigma', positive=True)
#  So far we have just defined some symbols, e.g.
T_s
```

```python
#  We have hard-coded the assumption that the temperature is positive
sympy.ask(T_s>0)
```

    True

### Longwave emissions

Let's denote the emissions from each layer as
\begin{align}
E_s &= \sigma T_s^4 \\
E_0 &= \epsilon \sigma T_0^4 \\
E_1 &= \epsilon \sigma T_1^4
\end{align}

recognizing that $E_0$ and $E_1$ contribute to **both** the upwelling and downwelling beams.

```python
#  Define these operations as sympy symbols
#  And display as a column vector:
E_s = sigma*T_s**4
E_0 = epsilon*sigma*T_0**4
E_1 = epsilon*sigma*T_1**4
E = sympy.Matrix([E_s, E_0, E_1])
E
```

### Shortwave radiation
Since we have assumed the atmosphere is transparent to shortwave, the incident beam $Q$ passes unchanged from the top to the surface, where a fraction $\alpha$ is reflected upward out to space.

```python
#  Define some new symbols for shortwave radiation
Q, alpha = sympy.symbols('Q, alpha', positive=True)
#  Create a dictionary to hold our numerical values
tuned = {}
tuned[Q] = 341.3  #  global mean insolation in W/m2
tuned[alpha] = 101.9/Q.subs(tuned)  #  observed planetary albedo
tuned[sigma] = 5.67E-8  #  Stefan-Boltzmann constant in W/m2/K4
tuned
#  Numerical value for emission temperature
#T_e.subs(tuned)
```

### Upwelling beam

Let $U$ be the upwelling flux of longwave radiation.

The upward flux from the surface to layer 0 is
$$ U_0 = E_s $$
(just the emission from the suface).

```python
U_0 = E_s
U_0
```

Following this beam upward, we can write the upward flux from layer 0 to layer 1 as the sum of the transmitted component that originated below layer 0 and the new emissions from layer 0:

$$ U_1 = (1-\epsilon) U_0 + E_0 $$

```python
U_1 = (1-epsilon)*U_0 + E_0
U_1
```

Continuing to follow the same beam, the upwelling flux above layer 1 is
$$ U_2 = (1-\epsilon) U_1 + E_1 $$

```python
U_2 = (1-epsilon) * U_1 + E_1
```

Since there is no more atmosphere above layer 1, this upwelling flux is our Outgoing Longwave Radiation for this model:

$$ OLR = U_2 $$

```python
U_2
```

The three terms in the above expression represent the **contributions to the total OLR that originate from each of the three levels**.

Let's code this up explicitly for future reference:

```python
#  Define the contributions to OLR originating from each level
OLR_s = (1-epsilon)**2 *sigma*T_s**4
OLR_0 = epsilon*(1-epsilon)*sigma*T_0**4
OLR_1 = epsilon*sigma*T_1**4

OLR = OLR_s + OLR_0 + OLR_1

print( 'The expression for OLR is')
OLR
```

### Downwelling beam

Let $D$ be the downwelling longwave beam. Since there is no longwave radiation coming in from space, we begin with

```python
fromspace = 0
D_2 = fromspace
```

Between layer 1 and layer 0 the beam contains emissions from layer 1:

$$ D_1 = (1-\epsilon)D_2 + E_1 = E_1 $$

```python
D_1 = (1-epsilon)*D_2 + E_1
D_1
```

Finally between layer 0 and the surface the beam contains a transmitted component and the emissions from layer 0:

$$ D_0 = (1-\epsilon) D_1 + E_0 = \epsilon(1-\epsilon) \sigma T_1^4 + \epsilon \sigma T_0^4$$

```python
D_0 = (1-epsilon)*D_1 + E_0
D_0
```

This $D_0$ is what we call the **back radiation**, i.e. the longwave radiation from the atmosphere to the surface.

____________

## 3. Tuning the grey gas model to observations
____________

In building our new model we have introduced exactly one parameter, the absorptivity $\epsilon$. We need to choose a value for $\epsilon$.

We will tune our model so that it **reproduces the observed global mean OLR** given **observed global mean temperatures**.

To get appropriate temperatures for $T_s, T_0, T_1$, let's revisit the [global, annual mean lapse rate plot from NCEP Reanalysis data](Lecture06 -- Radiation.ipynb) from the previous lecture.

### Temperatures

First, we set
$$T_s = 288 \text{ K}  $$

From the lapse rate plot, an average temperature for the layer between 1000 and 500 hPa is

$$ T_0 = 275 \text{ K}$$

Defining an average temperature for the layer between 500 and 0 hPa is more ambiguous because of the lapse rate reversal at the tropopause. We will choose

$$ T_1 = 230 \text{ K}$$

From the graph, this is approximately the observed global mean temperature at 275 hPa or about 10 km.

```python
#  add to our dictionary of values:
tuned[T_s] = 288.
tuned[T_0] = 275.
tuned[T_1] = 230.
tuned
```

### OLR

From the [observed global energy budget](Lecture01 -- Planetary energy budget.ipynb) we set

$$ OLR = 238.5 \text{ W m}^{-2} $$

### Solving for $\epsilon$

We wrote down the expression for OLR as a function of temperatures and absorptivity in our model above.

We just need to equate this to the observed value and solve a **quadratic equation** for $\epsilon$.

This is where the real power of the symbolic math toolkit comes in.

Subsitute in the numerical values we are interested in:

```python
# the .subs() method for a sympy symbol means
#  substitute values in the expression using the supplied dictionary
#  Here we use observed values of Ts, T0, T1
OLR2 = OLR.subs(tuned)
OLR2
```

We have a quadratic equation for $\epsilon$.

Now use the `sympy.solve` function to solve the quadratic:

```python
#  The sympy.solve method takes an expression equal to zero
#  So in this case we subtract the tuned value of OLR from our expression
eps_solution = sympy.solve(OLR2 - 238.5, epsilon)
eps_solution
```

There are two roots, but the second one is unphysical since we must have $0 < \epsilon < 1$.

Just for fun, here is a simple of example of *filtering a list* using powerful Python *list comprehension* syntax:

```python
# Give me only the roots that are between zero and 1!
list_result = [eps for eps in eps_solution if 0<eps<1]
print( list_result)
# The result is a list with a single element.
#  We need to slice the list to get just the number:
eps_tuned = list_result[0]
print( eps_tuned)
```

    [0.586041150248834]
    0.586041150248834

We conclude that our tuned value is

$$ \epsilon = 0.586 $$

This is the absorptivity that guarantees that our model reproduces the observed OLR given the observed tempertures.

```python
tuned[epsilon] = eps_tuned
tuned
```

____________

## 4. Level of emission
____________

Even in this very simple greenhouse model, there is **no single level** at which the OLR is generated.

The three terms in our formula for OLR tell us the contributions from each level.

```python
OLRterms = sympy.Matrix([OLR_s, OLR_0, OLR_1])
OLRterms
```

Now evaluate these expressions for our tuned temperature and absorptivity:

```python
OLRtuned = OLRterms.subs(tuned)
OLRtuned
```

So we are getting about 67 W m$^{-2}$ from the surface, 79 W m$^{-2}$ from layer 0, and 93 W m$^{-2}$ from the top layer.

In terms of fractional contributions to the total OLR, we have (limiting the output to two decimal places):

```python
sympy.N(OLRtuned / 239., 2)
```

Notice that the largest single contribution is coming from the top layer. This is in spite of the fact that the emissions from this layer are weak, because it is so cold.

Comparing to observations, the actual contribution to OLR from the surface is about 22 W m$^{-2}$ (or about 9% of the total), not 67 W m$^{-2}$. So we certainly don't have all the details worked out yet!

As we will see later, to really understand what sets that observed 22 W m$^{-2}$, we will need to start thinking about the spectral dependence of the longwave absorptivity.

____________

## 5. Radiative forcing in the 2-layer grey gas model
____________

Adding some extra greenhouse absorbers will mean that a greater fraction of incident longwave radiation is absorbed in each layer.

Thus **$\epsilon$ must increase** as we add greenhouse gases.

Suppose we have $\epsilon$ initially, and the absorptivity increases to $\epsilon_2 = \epsilon + \delta_\epsilon$.

Suppose further that this increase happens **abruptly** so that there is no time for the temperatures to respond to this change. **We hold the temperatures fixed** in the column and ask how the radiative fluxes change.

**Do you expect the OLR to increase or decrease?**

Let's use our two-layer leaky greenhouse model to investigate the answer.

The components of the OLR before the perturbation are

```python
OLRterms
```

After the perturbation we have

```python
delta_epsilon = sympy.symbols('delta_epsilon')
OLRterms_pert = OLRterms.subs(epsilon, epsilon+delta_epsilon)
OLRterms_pert
```

Let's take the difference

```python
deltaOLR = OLRterms_pert - OLRterms
deltaOLR
```

To make things simpler, we will neglect the terms in $\delta_\epsilon^2$. This is perfectly reasonably because we are dealing with **small perturbations** where $\delta_\epsilon << \epsilon$.

Telling `sympy` to set the quadratic terms to zero gives us

```python
deltaOLR_linear = sympy.expand(deltaOLR).subs(delta_epsilon**2, 0)
deltaOLR_linear
```

Recall that the three terms are the contributions to the OLR from the three different levels. In this case, the **changes** in those contributions after adding more absorbers.

Now let's divide through by $\delta_\epsilon$ to get the normalized change in OLR per unit change in absorptivity:

```python
deltaOLR_per_deltaepsilon = \
    sympy.simplify(deltaOLR_linear / delta_epsilon)
deltaOLR_per_deltaepsilon
```

Now look at the **sign** of each term. Recall that $0 < \epsilon < 1$. **Which terms in the OLR go up and which go down?**

**THIS IS VERY IMPORTANT, SO STOP AND THINK ABOUT IT.**

The contribution from the **surface** must **decrease**, while the contribution from the **top layer** must **increase**.

**When we add absorbers, the average level of emission goes up!**

### "Radiative forcing" is the change in radiative flux at TOA after adding absorbers

In this model, only the longwave flux can change, so we define the radiative forcing as

$$ R = - \delta OLR $$

(with the minus sign so that $R$ is positive when the climate system is gaining extra energy).

We just worked out that whenever we add some extra absorbers, the emissions to space (on average) will originate from higher levels in the atmosphere.

What does this mean for OLR? Will it increase or decrease?

To get the answer, we just have to sum up the three contributions we wrote above:

```python
R = -sum(deltaOLR_per_deltaepsilon)
R
```

Is this a positive or negative number? The key point is this:

**It depends on the temperatures, i.e. on the lapse rate.**

### Greenhouse effect for an isothermal atmosphere

Stop and think about this question:

If the **surface and atmosphere are all at the same temperature**, does the OLR go up or down when $\epsilon$ increases (i.e. we add more absorbers)?

Understanding this question is key to understanding how the greenhouse effect works.

#### Let's solve the isothermal case

We will just set $T_s = T_0 = T_1$ in the above expression for the radiative forcing.

```python
R.subs([(T_0, T_s), (T_1, T_s)])
```

which then simplifies to

```python
sympy.simplify(R.subs([(T_0, T_s), (T_1, T_s)]))
```

#### The answer is zero

For an isothermal atmosphere, there is **no change** in OLR when we add extra greenhouse absorbers. Hence, no radiative forcing and no greenhouse effect.

Why?

The level of emission still must go up. But since the temperature at the upper level is the **same** as everywhere else, the emissions are exactly the same.

### The radiative forcing (change in OLR) depends on the lapse rate!

For a more realistic example of radiative forcing due to an increase in greenhouse absorbers, we can substitute in our tuned values for temperature and $\epsilon$.

We'll express the answer in W m$^{-2}$ for a 1% increase in $\epsilon$.

The three components of the OLR change are

```python
deltaOLR_per_deltaepsilon.subs(tuned) * 0.01
```

And the net radiative forcing is

```python
R.subs(tuned) * 0.01
```

So in our example, **the OLR decreases by 2.2 W m$^{-2}$**, or equivalently, the radiative forcing is +2.2 W m$^{-2}$.

What we have just calculated is this:

*Given the observed lapse rates, a small increase in absorbers will cause a small decrease in OLR.*

The greenhouse effect thus gets stronger, and energy will begin to accumulate in the system -- which will eventually cause temperatures to increase as the system adjusts to a new equilibrium.

____________

## 6. Radiative equilibrium in the 2-layer grey gas model
____________

In the previous section we:

- made no assumptions about the processes that actually set the temperatures.
- used the model to calculate radiative fluxes, **given observed temperatures**.
- stressed the importance of knowing the lapse rates in order to know how an increase in emission level would affect the OLR, and thus determine the radiative forcing.

A key question in climate dynamics is therefore this:

**What sets the lapse rate?**

It turns out that lots of different physical processes contribute to setting the lapse rate.

Understanding how these processes acts together and how they change as the climate changes is one of the key reasons for which we need more complex climate models.

For now, we will use our prototype greenhouse model to do the most basic lapse rate calculation: the **radiative equilibrium temperature**.

We assume that

- the only exchange of energy between layers is longwave radiation
- equilibrium is achieved when the **net radiative flux convergence** in each layer is zero.

### Compute the radiative flux convergence

First, the **net upwelling flux** is just the difference between flux up and flux down:

```python
#  Upwelling and downwelling beams as matrices
U = sympy.Matrix([U_0, U_1, U_2])
D = sympy.Matrix([D_0, D_1, D_2])
# Net flux, positive up
F = U-D
F
```

####  Net absorption is the flux convergence in each layer

(difference between what's coming in the bottom and what's going out the top of each layer)

```python
# define a vector of absorbed radiation -- same size as emissions
A = E.copy()

#  absorbed radiation at surface
A[0] = F[0]
# Compute the convergence
for n in range(2):
    A[n+1] = -(F[n+1]-F[n])

A
```

#### Radiative equilibrium means net absorption is ZERO in the atmosphere

The only other heat source is the **shortwave heating** at the **surface**.

In matrix form, here is the system of equations to be solved:

```python
radeq = sympy.Equality(A, sympy.Matrix([(1-alpha)*Q, 0, 0]))
radeq
```

Just as we did for the 1-layer model, it is helpful to rewrite this system using the definition of the **emission temperture** $T_e$

$$ (1-\alpha) Q = \sigma T_e^4 $$

```python
radeq2 = radeq.subs([((1-alpha)*Q, sigma*T_e**4)])
radeq2
```

In this form we can see that we actually have a **linear system** of equations for a set of variables $T_s^4, T_0^4, T_1^4$.

We can solve this matrix problem to get these as functions of $T_e^4$.

```python
# Solve for radiative equilibrium
fourthpower = sympy.solve(radeq2, [T_s**4, T_1**4, T_0**4])
fourthpower
```

This produces a dictionary of solutions for the fourth power of the temperatures!

A little manipulation gets us the solutions for temperatures that we want:

```python
# need the symbolic fourth root operation
from sympy.simplify.simplify import nthroot

fourthpower_list = [fourthpower[key] for key in [T_s**4, T_0**4, T_1**4]]
solution = sympy.Matrix([nthroot(item,4) for item in fourthpower_list])
#  Display result as matrix equation!
T = sympy.Matrix([T_s, T_0, T_1])
sympy.Equality(T, solution)
```

In more familiar notation, the radiative equilibrium solution is thus

\begin{align}
T_s &= T_e \left( \frac{2+\epsilon}{2-\epsilon} \right)^{1/4} \\
T_0 &= T_e \left( \frac{1+\epsilon}{2-\epsilon} \right)^{1/4} \\
T_1 &= T_e \left( \frac{ 1}{2 - \epsilon} \right)^{1/4}
\end{align}

Plugging in the tuned value $\epsilon = 0.586$ gives

```python
Tsolution = solution.subs(tuned)
#  Display result as matrix equation!
sympy.Equality(T, Tsolution)
```

Now we just need to know the Earth's emission temperature $T_e$!

(Which we already know is about 255 K)

```python
# Here's how to calculate T_e from the observed values
sympy.solve(((1-alpha)*Q - sigma*T_e**4).subs(tuned), T_e)
```

```python
# Need to unpack the list
Te_value = sympy.solve(((1-alpha)*Q - sigma*T_e**4).subs(tuned), T_e)[0]
Te_value
```

#### Now we finally get our solution for radiative equilibrium

```python
#  Output 4 significant digits
Trad = sympy.N(Tsolution.subs([(T_e, Te_value)]), 4)
sympy.Equality(T, Trad)
```

Compare these to the values we derived from the **observed lapse rates**:

```python
sympy.Equality(T, T.subs(tuned))
```

The **radiative equilibrium** solution is substantially **warmer at the surface** and **colder in the lower troposphere** than reality.

This is a very general feature of radiative equilibrium, and we will see it again very soon in this course.

____________

## 7. Summary
____________

## Key physical lessons

- Putting a **layer of longwave absorbers** above the surface keeps the **surface substantially warmer**, because of the **backradiation** from the atmosphere (greenhouse effect).
- The **grey gas** model assumes that each layer absorbs and emits a fraction $\epsilon$ of its blackbody value, independent of wavelength.

- With **incomplete absorption** ($\epsilon < 1$), there are contributions to the OLR from every level and the surface (there is no single **level of emission**)
- Adding more absorbers means that **contributions to the OLR** from **upper levels** go **up**, while contributions from the surface go **down**.
- This upward shift in the weighting of different levels is what we mean when we say the **level of emission goes up**.

- The **radiative forcing** caused by an increase in absorbers **depends on the lapse rate**.
- For an **isothermal atmosphere** the radiative forcing is zero and there is **no greenhouse effect**
- The radiative forcing is positive for our atmosphere **because tropospheric temperatures tends to decrease with height**.
- Pure **radiative equilibrium** produces a **warm surface** and **cold lower troposphere**.
- This is unrealistic, and suggests that crucial heat transfer mechanisms are missing from our model.

### And on the Python side...

Did we need `sympy` to work all this out? No, of course not. We could have solved the 3x3 matrix problems by hand. But computer algebra can be very useful and save you a lot of time and error, so it's good to invest some effort into learning how to use it.

Hopefully these notes provide a useful starting point.

### A follow-up assignment

You are now ready to tackle [Assignment 5](../Assignments/Assignment05 -- Radiative forcing in a grey radiation atmosphere.ipynb), where you are asked to extend this grey-gas analysis to many layers.

For more than a few layers, the analytical approach we used here is no longer very useful. You will code up a numerical solution to calculate OLR given temperatures and absorptivity, and look at how the lapse rate determines radiative forcing for a given increase in absorptivity.

[Back to ATM 623 notebook home](../index.ipynb)

____________
## Version information
____________

```python
%load_ext version_information
%version_information sympy
```

    Loading extensions from ~/.ipython/extensions is deprecated. We recommend managing extensions like any other Python packages, in site-packages.

Software	Version

Python	3.7.3 64bit [Clang 4.0.1 (tags/RELEASE_401/final)]

IPython	7.6.0

OS	Darwin 17.7.0 x86_64 i386 64bit

sympy	1.4

Wed Jul 03 14:50:34 2019 EDT

____________

## Credits

The author of this notebook is [Brian E. J. Rose](http://www.atmos.albany.edu/facstaff/brose/index.html), University at Albany.

It was developed in support of [ATM 623: Climate Modeling](http://www.atmos.albany.edu/facstaff/brose/classes/ATM623_Spring2015/), a graduate-level course in the [Department of Atmospheric and Envionmental Sciences](http://www.albany.edu/atmos/index.php)

Development of these notes and the [climlab software](https://github.com/brian-rose/climlab) is partially supported by the National Science Foundation under award AGS-1455071 to Brian Rose. Any opinions, findings, conclusions or recommendations expressed here are mine and do not necessarily reflect the views of the National Science Foundation.
____________

```python

```

Lectures/Lecture07 -- Elementary greenhouse models.ipynb


# Introduction

What are we going to do ?

    Move Data into R -> Tranfrom it -> Visualise it -> Model it.

## Before all that; what is **Jupyter**  and how to use it ?

Jupyter documents are called "notebooks" and can be seen as many things at once. For example, notebooks allow:

* coding in a standard web browser
* direct sharing with results
* using text with styles (such as italics and titles) to be explicitly marked using a wikitext language
* easy creation and display of beautiful equations
* easy creation and display of interactive visualizations

\begin{equation}
 e^{i\pi} +1=0
\end{equation}

```R
print("nithin")
```

    [1] "nithin"

###  The   .     Green  mode
This is the edit mode

To enter into green select all cell and press **Enter** & to exit to blue mode press **Esc**

### The   .    Blue mode
This is the command mode

We can do all sorts of edits on cell level in this mode .

Interseting functionalities can be found in help **HH**

 **Note**
    - Code mode can be selected by pressing 'y' this is the default one
    - Markdown mode can selected by pressing 'm'

    For more on jupyter notebooks please refer [1]

```R
print(NIthin)
```

Exercise 1

- Write your name and address in markdown mode cell
- In the next cell evaluate sum digits of your Age code cell

"nithin"

```R
2 + 4
```

6

# R Programming

R is a language and environment for statistical computing and graphics. It is an integrated suite of software facilities for data manipulation, calculation and graphical display. It includes

 - an effective data handling and storage facility,
 - a suite of operators for calculations on arrays, in particular matrices,
 - a large, coherent, integrated collection of intermediate tools for data analysis,
 - graphical facilities for data analysis and display either on-screen or on hardcopy, and
 - a well-developed, simple and effective programming language which includes conditionals, loops, user-defined recursive functions and input and output facilities.

## The Essentials

    - Statements and Variables
    - Data-Types and a few in-built functions
    - Conditions and Loops
    - Functions
    - DataFrame

```R
print("hello World!")
```

    [1] "hello World!"

```R
install.packages("tidyverse")
```

    Updating HTML index of packages in '.Library'
    Making 'packages.html' ... done

```R
install.packages("nycflights13")
```

### What can we do with R

```R
library(tidyverse)
```

    ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
    ✔ ggplot2 3.2.1     ✔ purrr   0.3.3
    ✔ tibble  2.1.3     ✔ dplyr   0.8.4
    ✔ tidyr   1.0.2     ✔ stringr 1.4.0
    ✔ readr   1.3.1     ✔ forcats 0.4.0
    ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
    ✖ dplyr::filter()  masks stats::filter()
    ✖ purrr::flatten() masks jsonlite::flatten()
    ✖ dplyr::lag()     masks stats::lag()

```R

rmax <- 30
out.df <- matrix(NA, ncol = 2, nrow = 0)
a <- 0.01
r <- seq(0, rmax, by = 0.01)
n <- 100

for (z in 1:length(r)) {

    xl <- vector()
    xl[1] <- 10
    for (i in 2:n) {

        xl[i] <- xl[i - 1] * r[z] * exp(-a * xl[i - 1])

    }
    uval <- unique(xl[40:n])
    ### Here is where we can save the output for ggplot
    out.df <- rbind(out.df, cbind(rep(r[z], length(uval)), uval))
}
out.df <- as.data.frame(out.df)
colnames(out.df) <- c("r", "N")
ggplot(out.df, aes(x = r, y = N)) + geom_point(size = 0.5)

```

```R

# ButteRflies design with Lorenz attractor

library(deSolve)
library(scatterplot3d)

# Parameters for the solver
param <- c(alpha = 10,
          beta = 8/3,
          c = 26.48)

# Initial state
yini <- c(x = 0.01, y = 0.0, z = 0.0)

# Lorenz function
lorenz <- function(Time, State, Param) {
  with(as.list(c(State, Param)), {
    xdot <- alpha * (y - x)
    ydot <- x * (c - z) - y
    zdot <- x*y - beta*z
    return(list(c(xdot, ydot, zdot)))
  })
}

# Run function
runIt <- function(times) {
  out <- as.data.frame(ode(func = lorenz, y = yini, parms = param, times = times))

  scatterplot3d(x=out[,2],
                y=out[,3],
                z=out[,4],
                color="red",
                type="l",
                box=FALSE,
                highlight.3d=F,
                grid=F,
                axis=F,
                xlab=NULL,
                ylab=NULL,
                zlab=NULL,
                main=NULL)

}

# Run All function combining functions
runAll <- function() {
  runIt(seq(0, 100, by=0.01))
}

# Command to produce graphical output
runAll()
```

# Reference

[1] https://jupyter.brynmawr.edu/services/public/dblank/Jupyter%20Notebook%20Users%20Manual.ipynb

```R

```


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

# Toggle cell visibility

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

# Hide the code completely

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

Toggle cell visibility here.

```python
# Examples:
# Factored form: 1/(x**2*(x**2 + 1))
# Expanded form: 1/(x**4+x**2)

import sympy as sym
from IPython.display import Latex, display, Markdown, Javascript, clear_output
from ipywidgets import widgets, Layout # Interactivity module
```

## Partial fraction decomposition - Input by function

When Laplace transform is used for system analysis, the Laplace transform of the output signal is obtained as a product of the transfer function and the Laplace transform of the input signal. The result of this multiplication can usually be quite difficult to comprehend. In order to execute the inverse Laplace transform we first perform the partial fraction decomposition. This example demonstrates this procedure.

---

### How to use this notebook?
Toggle between the option *Input by function* or *Input by polynomial coefficients*.

1. *Input by function*:
 * Example: To insert the function $\frac{1}{x^2(x^2 + 1)}$ (factored form) type 1/(x\*\*2\*(x\*\*2 + 1)); to insert the same function in the expanded form ($\frac{1}{x^4+x^2}$) type 1/(x\*\*4+x\*\*2).

2. *Input by polynomial coefficients*:
 * Use the sliders to select the order of the numerator and denominator of a rational function of interest.
 * Insert the coefficients for both numerator and denominator in the dedicated textboxes and click *Confirm*.

```python
## System selector buttons
style = {'description_width': 'initial'}
typeSelect = widgets.ToggleButtons(
    options=[('Input by function', 0), ('Input by polynomial coefficients', 1),],
    description='Select: ',style={'button_width':'230px'})

btnReset=widgets.Button(description="Reset")

# function
textbox=widgets.Text(description=('Insert the function:'),style=style)
btnConfirmFunc=widgets.Button(description="Confirm") # ex btnConfirm

# poly
btnConfirmPoly=widgets.Button(description="Confirm") # ex btn

display(typeSelect)

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

def on_button_clickedFunc(ev):
    eq = sym.sympify(textbox.value)

    if eq==sym.factor(eq):
        display(Markdown('Input function $%s$ is written in a factored form. ' %sym.latex(eq) + 'Its expanded form is $%s$.' %sym.latex(sym.expand(eq))))

    else:
        display(Markdown('Input function $%s$ is written in an expanded form. ' %sym.latex(eq) + 'Its factored form is $%s$.' %sym.latex(sym.factor(eq))))

    display(Markdown('The result of the partial fraction decomposition is: $%s$' %sym.latex(sym.apart(eq)) + '.'))
    display(btnReset)

def transfer_function(num,denom):
    num = np.array(num, dtype=np.float64)
    denom = np.array(denom, dtype=np.float64)
    len_dif = len(denom) - len(num)
    if len_dif<0:
        temp = np.zeros(abs(len_dif))
        denom = np.concatenate((temp, denom))
        transferf = np.vstack((num, denom))
    elif len_dif>0:
        temp = np.zeros(len_dif)
        num = np.concatenate((temp, num))
        transferf = np.vstack((num, denom))
    return transferf

def f(orderNum, orderDenom):
    global text1, text2
    text1=[None]*(int(orderNum)+1)
    text2=[None]*(int(orderDenom)+1)
    display(Markdown('2. Insert the coefficients of the numerator.'))
    for i in range(orderNum+1):
        text1[i]=widgets.Text(description=(r'a%i'%(orderNum-i)))
        display(text1[i])
    display(Markdown('3. Insert the coefficients of the denominator.'))
    for j in range(orderDenom+1):
        text2[j]=widgets.Text(description=(r'b%i'%(orderDenom-j)))
        display(text2[j])
    global orderNum1, orderDenom1
    orderNum1=orderNum
    orderDenom1=orderDenom

def on_button_clickedPoly(btn):
    clear_output()
    global num,denom
    enacbaNum=""
    enacbaDenom=""
    num=[None]*(int(orderNum1)+1)
    denom=[None]*(int(orderDenom1)+1)
    for i in range(int(orderNum1)+1):
        if text1[i].value=='' or text1[i].value=='Please insert a coefficient':
            text1[i].value='Please insert a coefficient'
        else:
            try:
                num[i]=int(text1[i].value)
            except ValueError:
                if text1[i].value!='' or text1[i].value!='Please insert a coefficient':
                    num[i]=sym.var(text1[i].value)

    for i in range (len(num)-1,-1,-1):
        if i==0:
            enacbaNum=enacbaNum+str(num[len(num)-i-1])
        elif i==1:
            enacbaNum=enacbaNum+"+"+str(num[len(num)-i-1])+"*x+"
        elif i==int(len(num)-1):
            enacbaNum=enacbaNum+str(num[0])+"*x**"+str(len(num)-1)
        else:
            enacbaNum=enacbaNum+"+"+str(num[len(num)-i-1])+"*x**"+str(i)

    for j in range(int(orderDenom1)+1):
        if text2[j].value=='' or text2[j].value=='Please insert a coefficient':
            text2[j].value='Please insert a coefficient'
        else:
            try:
                denom[j]=int(text2[j].value)
            except ValueError:
                if text2[j].value!='' or text2[j].value!='Please insert a coefficient':
                    denom[j]=sym.var(text2[j].value)

    for i in range (len(denom)-1,-1,-1):
        if i==0:
            enacbaDenom=enacbaDenom+"+"+str(denom[len(denom)-i-1])
        elif i==1:
            enacbaDenom=enacbaDenom+"+"+str(denom[len(denom)-i-1])+"*x"
        elif i==int(len(denom)-1):
            enacbaDenom=enacbaDenom+str(denom[0])+"*x**"+str(len(denom)-1)
        else:
            enacbaDenom=enacbaDenom+"+"+str(denom[len(denom)-i-1])+"*x**"+str(i)

    funcSym=sym.sympify('('+enacbaNum+')/('+enacbaDenom+')')

    DenomSym=sym.sympify(enacbaDenom)
    NumSym=sym.sympify(enacbaNum)
    DenomSymFact=sym.factor(DenomSym);
    funcFactSym=NumSym/DenomSymFact;

    if DenomSym==sym.expand(enacbaDenom):
        if DenomSym==DenomSymFact:
            display(Markdown('The function of interest is: $%s$. The numerator cannot be factored.' %sym.latex(funcSym)))
        else:
            display(Markdown('The function of interest is: $%s$. The numerator cannot be factored. The same function with the factored denominator can be written as: $%s$.' %(sym.latex(funcSym), sym.latex(funcFactSym))))

    if sym.apart(funcSym)==funcSym:
        display(Markdown('Partial fraction decomposition cannot be done.'))
    else:
        display(Markdown('The result of the partial fraction decomposition is: $%s$' %sym.latex(sym.apart(funcSym)) + '.'))

    btnReset.on_click(on_button_clickedReset)
    display(btnReset)

def partial_frac(index):

    if index==0:
        x = sym.Symbol('x')
        display(widgets.HBox((textbox, btnConfirmFunc)))
        btnConfirmFunc.on_click(on_button_clickedFunc)
        btnReset.on_click(on_button_clickedReset)

    elif index==1:
        display(Markdown('1. Define the order of the numerator (orderNum) and denominator (orderDenom).'))
        widgets.interact(f, orderNum=widgets.IntSlider(min=0,max=10,step=1,value=0),
                 orderDenom=widgets.IntSlider(min=0,max=10,step=1,value=0));
        btnConfirmPoly.on_click(on_button_clickedPoly)
        display(btnConfirmPoly)

input_data=widgets.interactive_output(partial_frac,{'index':typeSelect})
display(input_data)
```

    ToggleButtons(description='Select: ', options=(('Input by function', 0), ('Input by polynomial coefficients', …

    Output()