# Chemical Reactions
The simulation procedure described in the previous chapter is now applied to a series of simple examples that represent chemical reactions. We first remind the reader of some key properties of chemical reactions that will show up in dynamic simulations and determine characteristics of network dynamic responses. We then go through a set of examples of chemical reactions that occur in a _closed system_. A closed system is isolated from its environment. No molecules enter or leave the system. Reactions being carried out in the laboratory in a sealed container represent an example of closed systems. In this chapter we assign numerical values to all the parameters for illustration purposes. We start by importing **MASSpy**:

```python
from mass import (
    MassModel, MassMetabolite, MassReaction, Simulation)
from mass.visualization import plot_time_profile, plot_phase_portrait
```

Other useful packages are also imported at this time.

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

## Basic Properties of Reactions
Links between molecular components in a biochemical reaction network are given by chemical reactions or associations between chemical components. These links are therefore characterized and constrained by basic chemical rules.

### Bi-linear reactions are prevalent in biology
Although there are linear reactions found in biological reaction networks, the prototypical transformations in living systems at the molecular level are bi-linear. This association involves two compounds coming together to either be chemically transformed through the breakage and formation of a new covalent bond, as is typical of metabolic reactions or macromolecular synthesis,

$$\begin{equation*} \text{X} +\text{Y} \rightleftharpoons \text{X-Y}\ \text{covalent bonds} \end{equation*}$$

or two molecules associated together to form a complex that may be held together by hydrogen bonds and/or other physical association forces to form a complex that has a different functionality than individual components,

$$\begin{equation*}\text{X} +\text{Y} \rightleftharpoons \text{X:Y}\ \text{association of molecules} \end{equation*}$$

Such association, for instance, could designate the binding of a transcription factor to DNA to form an activated site to which an activated polymerase binds. Such bi-linear association between two molecules might also involve the binding of an allosteric regulator to an allosteric enzyme that induces a conformational change in the enzyme.

### Properties of biochemical reactions
Chemical transformations have three key properties that will influence the dynamic features of reaction networks and how we interpret dynamic states:

#### Stoichiometry
The stoichiometry of chemical reactions is fixed and is described by integral numbers counting the molecules that react and that form as a consequence of the chemical reaction. Thus, stoichiometry basically represents "digital information." Chemical transformations are constrained by elemental and charge balancing, as well as other features. Stoichiometry is invariant between organisms for the same reactions and does not change with pressure, temperature, or other conditions. Stoichiometry gives the primary topological properties of a biochemical reaction network.

#### Thermodynamics
All reactions inside a cell are governed by thermodynamics that determine the equilibrium state of a reaction. The relative rates of the forward and reverse reactions are therefore fixed by basic thermodynamic properties. Unlike stoichiometry, thermodynamic properties do change with physico-chemical conditions such as pressure and temperature. Thus the thermodynamics of transformation between small molecules in cells are fixed but condition-dependent. The thermodynamic properties of associations between macromolecules can be changed by altering the amino acid sequence of a protein or by phosphorylation of amino acids in the interface region, or by conformational change induced by the binding of a small molecule ligand.

#### Absolute Rates
In contrast to stoichiometry and thermodynamics, the absolute rates of chemical reactions inside cells are highly manipulable. Highly evolved enzymes are very specific in catalyzing particular chemical transformations. Cells can thus extensively manipulate the absolute rates of reactions through changes in their DNA sequence.

All biochemical transformations are subject to the basic rules of chemistry and thermodynamics.

## The Reversible Linear Reaction
We start with the reversible linear reaction:

$$\begin{equation} x_1 \underset{v_{-1}}{\stackrel{v_1}{\rightleftharpoons}} x_2 \tag{4.1} \end{equation}$$

Here we have that

$$\begin{equation*} \textbf{S} = \begin{pmatrix} {-1} & {1} \\ {1}  & {-1} \\ \end{pmatrix}, \textbf{v}(\textbf{x}) = \begin{pmatrix} {v_1(x_1)} \\ {v_{-1}(x_2)} \end{pmatrix} = \begin{pmatrix} {k_1x_1} \\ {k_{-1}x_2} \end{pmatrix} \end{equation*}$$

and thus the differential equations that we will need to simulate are:

$$\begin{equation} \frac{dx_1}{dt} = -k_1x_1 + k_{-1}x_2, \frac{dx_2}{dt} = k_1x_1 - k_{-1}x_2 = -\frac{dx_1}{dt} \tag{4.2} \end{equation}$$

with the reaction rate given as the difference between two elementary reaction rates

$$\begin{equation} v_{1, net} = v_1 - v_{-1} = k_1x_1 - k_{-1}x_2 = k_1(x_1 - x_2/K_1) \tag{4.3} \end{equation}$$

where $K_1 = k_1/k_{-1} = x_{2, eq}/x_{1, eq}$ or the ratio of the product to reactant concentrations at equilibrium, the conventional definition of an equilibrium constant in chemistry. Note that in Eq. (4.3), $k_1$ represents the kinetics, or the rate of change, while $(x_1 - x_2/K_1)$ represents the thermodynamics measuring how far from equilibrium the system is, i.e.,$(x_{1, eq} - x_{2, eq}/K_1) = 0$.

Below, a sample solution is shown for $k_1 = 1$ and $k_{-1} = 2$. These simulation results can be examined further, and they reveal three important observations; 1) the existence of a conservation quantity, 2) a thermodynamic driving force, and 3) the pooling of variables based on chemistry and thermodynamics.

```python
# Create MassModel
model = MassModel('Linear_Reversible')
# Generate the MassMetabolites
x1 = MassMetabolite("x1")
x2 = MassMetabolite("x2")
# Generate the MassReactions
v1 = MassReaction("v1")
# Add metabolites to the reaction, add reaction to the model
v1.add_metabolites({x1: -1, x2: 1})
model.add_reactions([v1])
# Set parameters and initial conditions
v1.kf = 1
v1.kr = 2
model.update_initial_conditions({x1: 1, x2: 0})
# Utilize type 2 rate law for kf and kr parameters defined
model.get_rate_expressions(rate_type=2, update_reactions=True)
model
```

        Name	Linear_Reversible

        Memory address	0x07fd1902d7f10

        Stoichiometric Matrix
        2x1

        Matrix Rank
        1

        Number of metabolites
        2

        Initial conditions defined
        2/2

        Number of reactions
        1

        Number of genes
        0

        Number of enzyme modules
        0

        Number of groups
        0

        Objective expression
        0

        Compartments

```python
t0 = 0
tf = 2
sim = Simulation(model, verbose=True)
conc_sol, flux_sol = sim.simulate(model, time=(t0, tf), verbose=True)

# Define pools
pools = ["x1 - x2 / Keq_v1", "x1 + x2"]

for i, equation_str in enumerate(pools):
    pool_id = "p" + str(i + 1)
    conc_sol.make_aggregate_solution(
        pool_id, equation=equation_str,
        parameters={v1.Keq_str: v1.kf/v1.kr}, update=True)
```

    WARNING: No compartments found in model. Therefore creating compartment 'compartment' for entire model.

    Successfully loaded MassModel 'Linear_Reversible' into RoadRunner.
    Getting time points
    Setting output selections
    Setting simulation values for 'Linear_Reversible'
    Simulating 'Linear_Reversible'
    Simulation for 'Linear_Reversible' successful
    Adding 'Linear_Reversible' simulation solutions to output
    Updating stored solutions

```python
fig_4_1 = plt.figure(figsize=(9, 6))
gs = fig_4_1.add_gridspec(nrows=2, ncols=2, width_ratios=[1, 1.5],
                          height_ratios=[1, 1])

ax1 = fig_4_1.add_subplot(gs[0, 0])
ax2 = fig_4_1.add_subplot(gs[0, 1])
ax3 = fig_4_1.add_subplot(gs[1, 1])

plot_phase_portrait(
    conc_sol, x=x1, y=x2, ax=ax1,
    xlabel=x1.id, ylabel=x2.id,
    xlim=(-0.05, 1.05), ylim=(-0.05, 1.05),
    title=("(a) Phase portrait", {"size":"large"}),
    annotate_time_points="endpoints",
    annotate_time_points_labels=True);

plot_time_profile(
    conc_sol, ax=ax2, observable=model.metabolites, legend="right outside",
    xlabel="Time", ylabel="Concentrations",
    title=("(b) Time Profiles of Species", {"size": "large"}));

plot_time_profile(
    conc_sol, ax=ax3, observable=["p1", "p2"],
    legend="right outside",
    xlabel="Time", ylabel="Concentrations",
    title=("(c) Time Profiles of Pools", {"size": "large"}));
fig_4_1.tight_layout()
```

**Figure 4.1:** Dynamic simulation of the reaction $x_1 \underset{v_{-1}}{\stackrel{v_1}{\rightleftharpoons}} x_2$ for $k_1 =1$ and $k_{-1} = 2$, and $x_1(0)=1$, $x_2(0)=0$. (a) The phase portrait. (b) The time profiles. (c) The time profile of the pooled variables $p_1 = x_1 - x_2/K_1$ and $p_1 = x_1 + x_2$.

### Mass conservation:
The time profiles in Figure 4.1b show $x_1$ fall and $x_2$ rise to their equilibrium values. The phase portrait (Figure 4.1a) is a straight line of slope -1. This implies that

$$\begin{equation} p_1 = x_1 + x_2 = \big \langle (1, 1), (x_1, x_2)^T \big \rangle \tag{4.4} \end{equation}$$

is a constant. This summation represents a conservation quantity that stems from the fact that as $x_1$ reacts, $x_2$ appears in an equal and opposite amount. The stoichiometric matrix is singular with a rank of 1, showing that this is a one-dimensional dynamic system. It has a left null space that is spanned by the vector $(1, 1), i.e., (1, 1) \centerdot \textbf{S} = 0$, thus $p_2$ is in the left null space of $\textbf{S}$.

We also note that since $x_1 + x_2$ is a constant, we can describe the concentration of $x_1$ as a fraction of the total mass, i.e.,

$$\begin{equation*} f_1 = \frac{x_1}{x_1 + x_2} = \frac{x_1}{p_2} \end{equation*}$$

Pool sizes and the fraction of molecules in a particular state will be used later in the text to define physiologically useful quantities.

### Disequilibrium and the thermodynamic driving force:
A pooled variable:

$$\begin{equation} p_1 = x_1 - x_2/K_1 \tag{4.5} \end{equation}$$

can be formed (see Figure 4.1c). Combination of the differential equations for $x_1$ and $x_2$ leads to

$$\begin{equation} \frac{dp_1}{dt} = -(k_1 + k_{-1}) p_1 \tag{4.6} \end{equation}$$

and thus the time constant for this reaction is

$$\begin{equation} \tau_{1} = \frac{1}{k_1 + k_{-1}} \tag{4.7} \end{equation}$$

Note that when $t \rightarrow \infty, p_1 \rightarrow 0$ and then

$$\begin{equation} \frac{x_2}{x_1} \rightarrow \frac{k_1}{k_{-1}} = K_1 = \frac{x_{2,eq}}{x_{1, eq}} \tag{4.8} \end{equation}$$

the reaction has reached equilibrium. The pool $p_1$ thus represents a disequilibrium quantity  and represents the thermodynamic driver for the reaction, see Eq. (4.3). With an initial condition of $x_{1, 0} = 1$ and $K_1 = 1/2$, the eventual concentrations $(t \rightarrow \infty)$ will be $x_{1, eq} = 2/3$ and $x_{2, eq} = 1/3$.

### Representing dynamics with pools for reversible linear reactions
These considerations show that we can think about the dynamics of reaction (4.1) in terms of two pooled variables rather than the concentrations themselves. Thus, a useful pool transforming matrix for this reaction would be

$$\begin{equation} \textbf{P} = \begin{pmatrix} {1} & {-1/K_1} \\ {1}  & {1} \\ \end{pmatrix} \end{equation} \tag{4.9}$$

leading to disequilibrium $(p_1)$ and conservation $(p_2)$ quantities associated with the reaction in Eq. (4.1). The former quantity moves on the time scale given by $\tau_1$ while the latter is time invariant. For practical purposes, the dynamics of the reaction have relaxed within a time duration of three to five times  $\tau_1$ (see Figure 4.1b).

The differential equations for the pools can be obtained as

$$\begin{equation} \textbf{P} \frac{d\textbf{x}}{dt} = \frac{d}{dt}\begin{pmatrix} {p_1} \\ {p_2} \\ \end{pmatrix} = -(k_1 + k_{-1}) \begin{pmatrix} {x_1 - x_2/K_1} \\ {0} \\ \end{pmatrix} = -(k_1 + k_{-1})\begin{pmatrix} {p_1} \\ {0} \\ \end{pmatrix} \end{equation}$$

Therefore, the conservation quantity is a constant (time derivative is zero) and the disequilibrium pool is driven by a thermodynamic driving force that is itself multiplied by $-(k_1 + k_{-1})$, that is the inverse of the time constant for the reaction. Thus, the three key features of chemical reactions, the stoichiometry, thermodynamics, and kinetics, are separately accounted for.

## The Reversible Bi-Linear Reaction
The reaction mechanism for the reversible bi-linear reaction is:

$$\begin{equation} x_1 + x_2 \underset{v_{-1}}{\stackrel{v_1}{\rightleftharpoons}} x_3 \tag{4.10} \end{equation}$$

where the elementary reaction rates are

$$\begin{equation} v_1 = k_1x_1x_2, \ v_{-1} = k_{-1}x_3 \tag{4.11} \end{equation}$$

The forward rate, $v_1$, is a non-linear function, or more specifically, a bi-linear function. The variable

$$\begin{equation} p_1 = x_1x_2 - x_3/K_1 \tag{4.12} \end{equation}$$

represents a disequilibrium quantity. The dynamic states of this system can be computed from

$$\begin{equation} \frac{dx_1}{dt} = -v_1 + v_{-1} = -k_1x_1x_2 + k_{-1}x_3 = -k_1(x_1x_2 - x_3/K_1) = \frac{dx_2}{dt} = -\frac{dx_3}{dt} \tag{4.13} \end{equation}$$

This example will be used to illustrate the essential features of a bi-linear reaction; 1) That there are two conservation quantities associated with it, 2) How to compute the equilibrium state, 3) The use of linearization and deviation variables from the equilibrium state, 4) The derivation of a single linear disequilibrium quantity, and 5) Formation of pools.

### Conservation quantities for reversible bi-linear reactions
The stoichiometric matrix is

$$\begin{equation} S = \begin{pmatrix} {-1} & {1} \\ {-1} & {1} \\ {1}  & {-1} \\ \end{pmatrix} \end{equation}$$

The stoichiometric matrix has a rank of 1, and thus the dynamic dimension of this system is 1. Two vectors that span the left null space of **S** are (1,0,1) and (0,1,1) and the corresponding conservation quantities are:

$$\begin{equation} p_2 = x_1 + x_3, \ p_3 = x_2 + x_3 \tag{4.14} \end{equation}$$

This selection of conservation quantities is not unique, as one can find other sets of two vectors that span the left null space.

### The equilibrium state for reversible bi-linear reactions
We can examine the equilibrium state for the specific parameter values to be used for numerical simulation below, Figure 4.2. At equilibrium, $p_1 \rightarrow 0$ and we have that $(K_1 = 1)$

$$\begin{equation} x_{1, eq}x_{2, eq} = x_{3, eq} \tag{4.15} \end{equation}$$

and that

$$\begin{equation} x_1(0) = 3 =  x_{1, eq} + x_{3, eq}, \ \ \  x_2(0) = 2 =  x_{2, eq} + x_{3, eq} \tag{4.16} \end{equation}$$

These three equations can be combined to give a second order algebraic equation

$$\begin{equation} x_{3, eq}^2 - 6x_{3, eq} + 6 = 0  \tag{4.17} \end{equation}$$

that has a positive root that yields

$$\begin{equation} x_{1, eq} = 1.73,\ \  x_{2, eq} = 0.73, \ \ x_{3, eq} = 1.27 \tag{4.18} \end{equation}$$

### Linearization and deviation variables for reversible bi-linear reactions
Equation (13) can be linearized around the equilibrium point $\textbf{x}_{eq}$==(1.73,0.73,1.27) to give

$$\begin{equation} \frac{dx_1}{dt} = x_1x_2 - x_3  \tag{4.19} \end{equation}$$
$$\begin{equation} \rightarrow \frac{dx_1}{dt} = 0.73(x_1 -1.73) + 1.73(x_2 - 0.73) - (x_3 - 1.27) \tag{4.20} \end{equation}$$

where a numerical value of $k_1$ used is unity.

### The disequilibrium and conservation quantities for reversible bi-linear reactions
Equation (20) can be written in terms of deviation variables from the equilibrium state, i.e.,

$$\begin{equation} x_i' x_i - x_{i, eq} \tag{4.21} \end{equation}$$

as

$$\begin{equation} \frac{dx_1'}{dt} = 0.73x_1' + 1.73x_2' - x_3' = p_1' \tag{4.22} \end{equation}$$

which simply is the linearized version of the disequilibrium quantity in Eq.(4.12), and we have that

$$\begin{equation} \frac{dx_2'}{dt} =\frac{dx_1'}{dt} \ \ and \ \ \frac{dx_3'}{dt} = -\frac{dx_1'}{dt} \tag{4.23} \end{equation}$$

### Representing dynamics with pools for reversible bi-linear reactions
We can therefore form a pool transformation matrix as:

$$\begin{equation} \textbf{P} = \begin{pmatrix} {0.73} & {1.73} & {-1} \\ {1} & {0} & {1}  \\ {0} & {1} & {1} \\ \end{pmatrix} \end{equation} \tag{4.24}$$

where the first pool represents the disequilibrium quantity and the second and third are conservation quantities. Now we transform the deviation variables with this matrix, i.e., $\textbf{p'} = \textbf{Px'}$ and can look at the time derivatives of the pools

$$\begin{align} \frac{d\textbf{p'}}{dt} &= \textbf{P}\frac{d\textbf{x'}}{dt} \\ &= \begin{pmatrix} {-3.46} & {3.46} \\ {0} & {0} \\ {0} & {0} \\ \end{pmatrix}(0.73x_1' + 1.73x_2' - x_3') \\ &= 3.46\begin{pmatrix} {1} \\ {0} \\ {0} \\ \end{pmatrix} p_1' \end{align} \tag{4.25}$$

This result is similar to that obtained above for the linear reversible reaction. There are two conservation pools and a disequilibrium pool that is moved by itself multiplied by a characteristic rate constant. We note that the conservation quantities, for both the linear and bi-linear reaction, do not change if the reactions are irreversible (i.e., if $K_{eq} \rightarrow \infty$)

### Numerical simulation of reversible bi-linear reactions
The dynamic response of this reaction can readily be computed and the results graphed; see Figure 4.2.

```python
# Create MassModel
model = MassModel('BiLinear_Reversible')
# Generate the MassMetabolites
x1 = MassMetabolite("x1")
x2 = MassMetabolite("x2")
x3 = MassMetabolite("x3")
# Generate the MassReactions
v1 = MassReaction("v1")
# Add metabolites to the reaction, add reaction to the model
v1.add_metabolites({x1: -1, x2: -1, x3: 1})
model.add_reactions([v1])
# Set parameters and initial conditions
v1.kf = 1
v1.kr = 1
model.update_initial_conditions({x1: 3, x2: 2, x3: 0})
# Utilize type 2 rate law for kf and kr parameters defined
model.get_rate_expressions(rate_type=2, update_reactions=True)
model
```

        Name	BiLinear_Reversible

        Memory address	0x07fd19052fd10

        Stoichiometric Matrix
        3x1

        Matrix Rank
        1

        Number of metabolites
        3

        Initial conditions defined
        3/3

        Number of reactions
        1

        Number of genes
        0

        Number of enzyme modules
        0

        Number of groups
        0

        Objective expression
        0

        Compartments

```python
t0 = 0
tf = 5
sim = Simulation(model, verbose=True)
conc_sol, flux_sol = sim.simulate(model, time=(t0, tf), verbose=True)

# Define pools
pools = ['x1*x2 - x3 / Keq_v1', 'x1 + x3', 'x2 + x3']

for i, equation_str in enumerate(pools):
    pool_id = "p" + str(i + 1)
    conc_sol.make_aggregate_solution(
        pool_id, equation=equation_str,
        parameters={v1.Keq_str: v1.kf/v1.kr}, update=True)
```

    WARNING: No compartments found in model. Therefore creating compartment 'compartment' for entire model.

    Successfully loaded MassModel 'BiLinear_Reversible' into RoadRunner.
    Getting time points
    Setting output selections
    Setting simulation values for 'BiLinear_Reversible'
    Simulating 'BiLinear_Reversible'
    Simulation for 'BiLinear_Reversible' successful
    Adding 'BiLinear_Reversible' simulation solutions to output
    Updating stored solutions

```python
fig_4_2 = plt.figure(figsize=(12, 4))
gs = fig_4_2.add_gridspec(nrows=1, ncols=2)

ax1 = fig_4_2.add_subplot(gs[0, 0])
ax2 = fig_4_2.add_subplot(gs[0, 1])

plot_time_profile(
    conc_sol, ax=ax1, observable=model.metabolites,
    legend="lower outside",
    xlabel="Time", ylabel="Concentrations",
    title=("(a) Time Profiles of Species", {"size": "large"}));

plot_time_profile(
    conc_sol, ax=ax2, observable=["p1", "p2", "p3"],
    legend="lower outside",
    xlabel="Time", ylabel="Concentrations",
    title=("(b) Time Profiles of Pools", {"size": "large"}));
fig_4_2.tight_layout()
```

**Figure 4.2:** The concentration time profiles for the reaction $x_1 + x_2 {\rightleftharpoons} x_3$ for $k_1$ = $k_{-1} = 1$ and $x_1(0)=3$, $x_2(0)=2$, and $x_3(0)=0$. (a) The concentrations as a function of time. (b) The pools as a function of time.

## Connected Reversible Linear Reactions
Now we consider more than one reaction working simultaneously. We will consider two reversible first order reactions that are connected by an irreversible reaction;

$$\begin{equation} x_1 \underset{v_{-1}}{\stackrel{v_1}{\rightleftharpoons}} x_2 \stackrel{v_2} \rightarrow x_3 \underset{v_{-3}}{\stackrel{v_3}{\rightleftharpoons}} x_4 \tag{4.26} \end{equation}$$

The stoichiometric matrix and the reaction vector, are

$$\begin{equation} \textbf{S} = \begin{pmatrix} {-1} & {1} & {0} & {0} & {0}  \\ {1} & {-1} & {-1} & {0} & {0}  \\ {0} & {0} & {1} & {-1} & {1}  \\  {0} & {0} & {0} & {1} & {-1}  \\ \end{pmatrix}, \  \textbf{v}(\textbf{x})  = \begin{pmatrix}  {k_1x_1} \\  {k_{-1}x_2} \\ {k_2x_2} \\ {k_3x_3} \\ {k_{-3}x_4} \\\end{pmatrix}\end{equation} \tag{4.27}$$

and thus the dynamic mass balances are;

$$\begin{align} \frac{dx_1}{dt} &= -k_1x_1 + k_{-1}x_2 \\ \frac{dx_2}{dt} &=  k_1x_1 - k_{-1}x_2 - k_2x_2 \\ \frac{dx_3}{dt} &=  k_2x_2 - k_3x_3 + k_{-3}x_4 \\ \frac{dx_4}{dt} &=  k_3x_3 - k_{-3}x_4 \\ \end {align}  \tag{4.28}$$

The net reaction rates are:

$$\begin{equation} v_{1, net} = k_1x_1 - k_{-1}x_2 = k_1(x_1 - x_2/K_1)  \tag{4.29} \end{equation}$$

and

$$\begin{equation} v_{3, net} = k_3x_3 - k_{-3}x_4 = k_3(x_3 - x_4/K_3) \tag{4.30} \end{equation}$$

where $K_1 = k_1/k_{-1}$ and  $K_3 = k_3/k_{-3}$ are the equilibrium constants. This example can be used to illustrate three concepts: 1) dynamic decoupling, 2) stoichiometric decoupling, and 3) formation of multi-reaction pools.

### Dynamic decoupling through seperated time scales:
This linear system can be described by

$$\begin{equation} \frac{d\textbf{x}}{dt} = \textbf{Jx} \tag{4.31} \end{equation}$$

where the Jacobian matrix for this system is obtained directly from the equations in (4.28):

$$\begin{equation} \textbf{J} = \begin{pmatrix} {-k_1} & {k_{-1}} & {0} & {0} \\ {k_1} & {-k_{-1} - k_2} & {0} & {0} \\ {0} & {k_2} & {-k_3} & {-k_{-3}} \\ {0} & {0} & {k_3} & {k_{-3}} \\ \end{pmatrix} \end{equation} \tag{4.32}$$

Note that for linear systems, $\textbf{x} = \textbf{x'}$. Observe that the second column in $\textbf{J}$ is a combination of the second and third column in $\textbf{S}$;

$$\begin{equation} \begin{pmatrix} {j_{12}} \\ {j_{22}} \\ {j_{32}} \\ {j_{42}} \end{pmatrix} = \begin{pmatrix} {1} \\ {-1} \\ {0} \\ {0} \end{pmatrix} + \begin{pmatrix} {0} \\ {-1} \\ {1} \\ {0} \end{pmatrix} k_2 = k_{-1}\textbf{s}_2 + k_2\textbf{s}_3 \end{equation} \tag{4.33}$$

```python
# Create MassModel
model = MassModel('Connected_Linear_Reversible')
# Generate the MassMetabolites
x1 = MassMetabolite("x1")
x2 = MassMetabolite("x2")
x3 = MassMetabolite("x3")
x4 = MassMetabolite("x4")
# Generate the MassReactions
v1 = MassReaction("v1")
v2 = MassReaction("v2", reversible=False)
v3 = MassReaction("v3")
# Add metabolites to the reaction, add reaction to the model
v1.add_metabolites({x1: -1, x2: 1})
v2.add_metabolites({x2: -1, x3: 1})
v3.add_metabolites({x3: -1, x4: 1})
model.add_reactions([v1, v2, v3])
# Set parameters and initial conditions
v1.kf = 1
v1.Keq = 1
v2.kf = 1
v3.kf = 1
v3.Keq = 1
model.update_initial_conditions({x1: 1, x2: 0, x3: 0, x4: 0})
model
```

        Name	Connected_Linear_Reversible

        Memory address	0x07fd1902a1ad0

        Stoichiometric Matrix
        4x3

        Matrix Rank
        3

        Number of metabolites
        4

        Initial conditions defined
        4/4

        Number of reactions
        3

        Number of genes
        0

        Number of enzyme modules
        0

        Number of groups
        0

        Objective expression
        0

        Compartments

```python
t0 = 0
tf = 10

sim = Simulation(model, verbose=True)
conc_sol, flux_sol = sim.simulate(model, time=(t0, tf), verbose=True)
```

    WARNING: No compartments found in model. Therefore creating compartment 'compartment' for entire model.

    Successfully loaded MassModel 'Connected_Linear_Reversible' into RoadRunner.
    Getting time points
    Setting output selections
    Setting simulation values for 'Connected_Linear_Reversible'
    Simulating 'Connected_Linear_Reversible'
    Simulation for 'Connected_Linear_Reversible' successful
    Adding 'Connected_Linear_Reversible' simulation solutions to output
    Updating stored solutions

```python
fig_4_3 = plt.figure(figsize=(6, 4))
gs = fig_4_3.add_gridspec(nrows=1, ncols=1)

ax1 = fig_4_3.add_subplot(gs[0, 0])

plot_time_profile(
    conc_sol, ax=ax1, legend="right outside",
    xlabel="Time", ylabel="Concentrations",
    title=("Time Profiles of Species", {"size": "large"}));
fig_4_3.tight_layout()
```

**Figure 4.3:** The dynamic response of the reactions in Eq. (4.17) for $K_1=K_3=1$ and $k_1=k_2=k_3=1$. The graphs show the concentrations varying with time for $x_1(0)=1, x_2(0)=x_3(0)=x_4(0)=0$.

The kinetic effects of $x_2$ are thus felt through both reactions 2 and 3 (i.e., the second and third column in $\textbf{S}$ that are the corresponding reaction vectors), $\textbf{s}_2$ and $\textbf{s}_3$. These two reaction vectors are weighted by the rate constants (reciprocal of the time constants). Therefore, we expect that if $k_2$ is numerically much smaller than $k_{-1}$ then the dynamic coupling is 'weak.' We consider two sets of parameter values.

* First, we simulate this system with all the rate constants being equal, Figure 4.3. All the concentrations are moving on the same time scale. For a series of reactions, the overall dynamics are expected to unfold on a time scale that is the sum of the individual time constants. Here, this sum is three, and the dynamics have relaxed after a time period of three to five times this value.

* Next, we make the second reaction ten times slower compared to the other two by decreasing $k_2$. We see that the two faster reactions come to a quasi-equilibrium state relatively quickly (relative to reaction 3), and form two conservation pools that exchange mass slowly. The sum of the rate constants for the three reactions in series is now seven, and the dynamics unfold on this time scale.

#### Stoichiometric decoupling
Reaction 3 does not influence reaction 1 at all. They are separated by the irreversible reaction 2. Thus, changes in the kinetics of reaction 3 will not influence the progress of reaction 1. This can be illustrated through simulation by changing the rate constants for reaction 3 and observing what happens to reaction 1.

#### Formation of multi-reaction pools:
We can form the following pooled variables based on the properties of the individual reversible reactions

$$\begin{align} &p_1 = x_1 - x_2/K_1\ && disequilibrium\ quantity\ for\ reaction\ 1 \\ &p_2 = x_1 + x_2    \ && conservation\ quantity\ for\ reaction\ 1 \\ &p_3 = x_3 - x_4/K_3\ && disequilibrium\ quantity\ for\ reaction\ 3 \\ &p_4 = x_3 + x_4    \ && conservation\ quantity\ for\ reaction\ 3\end{align} \tag{4.34}$$

A representation of the dynamics of this reaction system can be obtained by plotting these pools as a function of time; Figure 4.4a. To prepare this plot, we use the pooling matrix

$$\begin{equation} \textbf{P} = \begin{pmatrix} {1} & {-1/K_1} & {0} & {0} \\ {1} & {1} & {0} & {0} \\ {0} & {0} & {1} & {-1/K_3}  \\ {0} & {0} & {1} & {1} \\ \end{pmatrix} \end{equation} \tag{4.35}$$

to post-process the output. However, we note that the conservation quantities associated with the individual reactions are no longer time-invariant.

The rank of $\textbf{S}$ is 3 and its one-dimensional left null space is spanned by (1,1,1,1); thus the conservation quantity is $x_1 + x_2 + x_3 + x_4$. Therefore an alternative pooling matrix may be formulated as

$$\begin{equation} \textbf{P} = \begin{pmatrix} {1} & {-1/K_1}  & {0} & {0} \\ {0} & {1} & {0} & {0} \\ {0} & {0} & {1} & {-1/K_3} \\ {1} & {1} & {1} & {1} \\ \end{pmatrix}\end{equation} \tag{4.36}$$

where we use $x_2$ as the coupling variable and the overall conservation pool instead of the conservation pools associated with the individual reactions. The two conservation pools are combined into one overall mass conservation pool.

We can now derive the dynamic mass balances on the pools as

$$\begin{align} \frac{d\textbf{p}}{dt} = \textbf{PSv} &= \begin{pmatrix} {-(k_1 + k_{-1})(x_1 - x_2/K_1) +\frac{k_2}{K_1}x_2} \\ {k_1(x_1 - x_2/K_1) - k_2x_2} \\ {-(k_3 + k_{-3})(x_3 - x_4/K_3) +k_2x_2} \\ {0}\end{pmatrix}  \\ &= \begin{pmatrix} {-(k_1 + k_{-1})p_1 +\frac{k_2}{K_1}x_2} \\ {k_1p_1 - k_2p_2} \\ {-(k_3 + k_{-3})p_3 +k_2p_2} \\{0}\end{pmatrix} \\ &= \begin{pmatrix} {-(k_1 + k_{-1})} \\ {k_1}  \\ {0} \\ {0} \end{pmatrix}p_1 + \begin{pmatrix} {\frac{k_2}{K_1}} \\ {-k_2} \\ {k_2} \\ {0} \end{pmatrix}p_2 + \begin{pmatrix} {0} \\ {0} \\ {-(k_3 + k_{-3})} \\ {0} \end{pmatrix}p_3 \end{align} \tag{4.37}$$

This equation shows that $p_1$ and $p_3$ create fast motion compared to $p_2$ given the relative numerical values of the rate constants; $p_2$ creates a slow drift in this system for the numerical values used in Figure 4.4b.

```python
# Define pools
reg_pools = ['x1 - x2 / Keq_v1', 'x1 + x2',
             'x3 - x4 / Keq_v3', 'x3 + x4']

alt_pools = ['x1 - x2 / Keq_v1', 'x2',
             'x3 - x4 / Keq_v3', 'x1 + x2 + x3 + x4']

for prefix, pools in zip(["p", "alt_p"], [reg_pools, alt_pools]):
    for i, equation_str in enumerate(pools):
        pool_id = prefix + str(i + 1)
        conc_sol.make_aggregate_solution(
            pool_id, equation=equation_str,
            parameters={v1.Keq_str: v1.Keq, v3.Keq_str: v3.Keq},
            update=True)
```

```python
fig_4_4 = plt.figure(figsize=(12, 8))
gs = fig_4_4.add_gridspec(nrows=2, ncols=2)

ax1 = fig_4_4.add_subplot(gs[0, 0])
ax2 = fig_4_4.add_subplot(gs[1, 0])
ax3 = fig_4_4.add_subplot(gs[1, 1])

plot_time_profile(
    conc_sol, ax=ax1, observable=model.metabolites,
    legend="right outside", xlabel="Time", ylabel="Concentrations",
    title=("(a) Time Profiles of Species", {"size": "large"}));

plot_time_profile(
    conc_sol, observable=["p1", "p2", "p3"], ax=ax2,
    legend="lower outside", xlabel="Time", ylabel="Concentrations",
    title=("(b) Time Profiles of Pools", {"size": "large"}));

plot_time_profile(
    conc_sol, observable=["alt_p1", "alt_p2", "alt_p3"], ax=ax3,
    legend="lower outside", xlabel="Time", ylabel="Concentrations",
    title=("(c) Time Profiles of Alternate Pools",  {"size": "large"}));
fig_4_4.tight_layout()
```

**Figure 4.4:** (a) The time profiles (b) The conservation pools $p_2 = x_1 + x_2$ and $p_4 = x_3 + x_4$ and the disequilibrium pools $p_1 = x_1 - x_2/K_1$ and $p_3 = x_3 - x_4/K_3$ for the individual reactions. The disequilibrium pools move quickly towards a quasi-equilibrium state, while the conservation pools move more slowly. These pools are defined in Eq (4.35). (c) The dynamic response with alternative pools; $p_2 = x_2$, and $p_4 = x_1 + x_2 + x_3 + x_4$. These pools are defined in Eq (4.36).

## Connected Reversible Bi-linear Reactions
An important case of connected bi-linear reactions is represented by the reaction mechanism

$$\begin{equation} x_1 + x_2 \underset{v_{-1}}{\stackrel{v_1}{\rightleftharpoons}} x_3 \underset{v_{-2}}{\stackrel{v_2}{\rightleftharpoons}} x_4 + x_5 \tag{4.38} \end{equation}$$

This reaction network is similar to reaction mechanisms for enzymes, and thus leads us into the treatment of enzyme kinetics (Chapter 5). The elementary reaction rates are:

$$\begin{equation} v_1 = k_1x_1x_2, \ \  v_{-1} = k_{-1}x_3 \ \ v_2 = k_2x_3, \text{and} \ \ v_{-2} = k_{-2}x_4x_5 \tag{4.39} \end{equation}$$

and the equilibrium constants are $K_1 = k_1/k_{-1}$ and $K_2 = k_2/k_{-2}$. There are two disequilibrium quantities.

$$\begin{equation} p_1 = x_1x_2 - x_3 / K_1 \tag{4.40} \end{equation}$$
$$\begin{equation} p_2 = x_3 - x_4x_5 / K_2 \tag{4.41} \end{equation}$$

We now explore the same features of this coupled system of bi-linear reactions as we did for the single reversible bi-linear reaction.

### Conservation quantities for connected reversible bi-linear reactions
The (5x4) stoichiometric matrix

$$\begin{equation} \textbf{S} = \begin{pmatrix} {-1} & {1} & {0} & {0} \\ {-1} & {1} & {0} & {0} \\ {1} & {-1} & {-1} & {1} \\ {0} & {0} & {1} & {-1} \\ {0} & {0} & {1} & {-1} \\ \end{pmatrix} \end{equation} \tag{4.42}$$

has a rank of 2 and thus there are three conservation variables and two independent dynamic variables.

The conservation quantities are not unique and which one we will use depends on the reaction chemistry that is being studied. An example is

$$\begin{equation} AB + C \underset{v_{-1}}{\stackrel{v_1}{\rightleftharpoons}} ABC \underset{v_{-2}}{\stackrel{v_2}{\rightleftharpoons}} A + BC \tag{4.43} \end{equation}$$

in which case the three independent conservation quantities would be:

$$\begin{equation} \text{Conservation of A}:\ p_3 =  x_1 + x_3 + x_4 \\ \text{Conservation of B}:\ p_4 =  x_1 + x_3 + x_5 \\ \text{Conservation of C}:\ p_5 =  x_2 + x_3 + x_5 \end{equation}$$  $$\tag{4.44}$$

These are convex quantities as all the coefficients are non-negative (the concentrations are $x_i>0$). The individual bi-linear reactions have two each, but once coupled, the number of conservation quantities drops by one.

### The equilibrium state for connected reversible bi-linear reactions
The computation of the equilibrium state involves setting the net fluxes to zero and combining those equations with the conservation quantities to get a set of independent equations. For convenience of illustration, we pick $K_1 = K_2 = 1$ and the equilibrium equations become

$$\begin{equation} x_{1, eq}x_{2, eq} = x_{3, eq} = x_{4, eq}x_{5, eq} \tag{4.45} \end{equation}$$

and if we pick $p_3 = p_4 = p_5 = 3$ then the solution for the equilibrium state is simple: $x_{1, eq}x_{2, eq} = x_{3, eq} = x_{4, eq}x_{5, eq} = 1$. These equations can also be solved for arbitrary parameter values.

### Linearization and deviation variables for connected reversible bi-linear reactions
By linearizing the differential equations around the steady state we obtain

$$\begin{align} \frac{dx_1'}{dt} &= \frac{dx_2'}{dt} = -(k_1x_{1,eq})x_2' -(k_1x_{2,eq})x_1' + k_{-1}x_3' \\ \frac{dx_3'}{dt} &= (k_1x_{2,eq})x_1' + (k_1x_{1,eq})x_2' - (k_{-1} + k_2)x_3' + (k_{-2}x_{5,eq})x_4'+ (k_{-2}x_{4,eq})x_5'\\ \frac{dx_4'}{dt} &= \frac{dx_5'}{dt} = k_{2}x_3' - (k_{-2}x_{5,eq})x_4' -(k_{-2}x_{4,eq})x_5' \end{align} \tag{4.46}$$

where $x_i' = x_i - x_{i, eq}$ represent the concentration deviation around equilibrium, $i$ =1,2,3,4 and 5.

### The disequilibrium and conservation quantities for connected reversible bi-linear reactions
Similar to the reversible bi-linear reaction, we obtain two pools that represent the disequilibrium driving forces of the two reactions,

$$\begin{align} p_1 &= x_1x_2 - x_3 / K_1 \approx (x_{2, eq})x_1' + (x_{1, eq})x_2' - (1/K_1)x_3' = p_1'\\ p_2 &= x_3 - x_4x_5 / K_2 \approx x_3' - (x_{5, eq}/K_2)x_4' - (x_{4, eq}/K_2)x_5' = p_2' \end{align} \tag{4.47}$$

and the three pools that represent conservative quantities do not change:

$$\begin{align} p_3 &=  x_1 + x_3 + x_4 \\ p_4 &=  x_1 + x_3 + x_5 \\ p_5 &=  x_2 + x_3 + x_5 \end{align} \tag{4.48}$$

We thus can define the pooling matrix as:

$$\begin{align} \textbf{P} &= \begin{pmatrix} {x_{2, eq}} & {x_{1, eq}} & {1/K_1} & {0} & {0} \\ {0} & {0} & {1} & {-x_{5, eq}/K_2} & {-x_{4, eq}/K_2} \\ {1} & {0} & {1} & {1} & {0} \\ {1} & {0} & {1} & {0} & {1} \\ {0} & {1} & {1} & {0} & {1} \\ \end{pmatrix} \\ &= \begin{pmatrix}  {1} & {1} & {-1} & {0} & {0} \\ {0} & {0} & {1} & {-1} & {-1} \\ {1} & {0} & {1} & {1} & {0} \\ {1} & {0} & {1} & {0} & {1} \\ {0} & {1} & {1} & {0} & {1} \\ \end{pmatrix} \end{align} \tag{4.49}$$

for the particular equilibrium constants and concentrations values given above.

The differential equations for the pools are then formed by (and at this stage we remove the conservation pools as they are always constant):

$$\begin{equation} \frac{d\textbf{p'}}{dt} = \textbf{PSv(x)} \ \text{where} \ \textbf{v(x)} \approx \begin{pmatrix} {k_1p_1'} \\ {k_2p_2'} \\ \end{pmatrix} = \begin{pmatrix} {k_1} & {0} \\ {0} & {k_2} \\ \end{pmatrix} \begin{pmatrix} {p_1'} \\ {p_2'} \\ \end{pmatrix} \end{equation} \tag{4.50}$$

which gives

$$\begin{equation} \frac{d\textbf{p'}}{dt} = \begin{pmatrix} {-(x_{2, eq} + x_{1, eq} + 1/K_1)} \\ {1} \\ \end{pmatrix}k_1p_1' + \begin{pmatrix} {1/K_1} \\ {-(1 + (x_{5, eq} + x_{4, eq})/K_2} \\ \end{pmatrix}k_2p_2' \end{equation} \tag{4.51}$$

### Numerical simulation of connected reversible bi-linear reactions
These equations can be simulated once parameter values and initial conditions are specified. In order to illustrate the dynamic behavior in terms of the pools, we consider the particular situation where $K_1 = K_2 = x_{1, eq} = x_{2, eq} = x_{3, eq} = x_{4, eq} = x_{5, eq} = 1$, see Figure 4.5.

```python
# Create MassModel
model = MassModel('Connected_BiLinear_Reversible')
# Generate the MassMetabolites
x1 = MassMetabolite("x1")
x2 = MassMetabolite("x2")
x3 = MassMetabolite("x3")
x4 = MassMetabolite("x4")
x5 = MassMetabolite("x5")
# Generate the MassReactions
v1 = MassReaction("v1")
v2 = MassReaction("v2")
# Add metabolites to the reaction, add reaction to the model
v1.add_metabolites({x1: -1, x2: -1, x3: 1})
v2.add_metabolites({x3: -1, x4: 1, x5: 1})
model.add_reactions([v1, v2])
# Set parameters and initial conditions
v1.kf = 1
v1.kr = 1
v2.kf = 1
v2.kr = 1
model.update_initial_conditions({x1: 3, x2: 3, x3: 0, x4: 0, x5: 0})

# Utilize type 2 rate law for kf and kr parameters defined
model.get_rate_expressions(rate_type=2, update_reactions=True)
model
```

        Name	Connected_BiLinear_Reversible

        Memory address	0x07fd190e33b50

        Stoichiometric Matrix
        5x2

        Matrix Rank
        2

        Number of metabolites
        5

        Initial conditions defined
        5/5

        Number of reactions
        2

        Number of genes
        0

        Number of enzyme modules
        0

        Number of groups
        0

        Objective expression
        0

        Compartments

```python
t0 = 0
tf = 10
sim = Simulation(model, verbose=True)
conc_sol, flux_sol = sim.simulate(model, time=(t0, tf), verbose=True)

# Define pools
pools = ['x1*x2 - x3 / Keq_v1', 'x3 - x4*x5 / Keq_v2',
         'x1 + x3 + x4', 'x1 + x3 + x5', 'x2 + x3 + x4']

for i, equation_str in enumerate(pools):
    pool_id = "p" + str(i + 1)
    conc_sol.make_aggregate_solution(
        pool_id, equation=equation_str,
        parameters={v1.Keq_str: v1.kf/v1.kr,
                    v2.Keq_str: v2.kf/v2.kr}, update=True)
```

    WARNING: No compartments found in model. Therefore creating compartment 'compartment' for entire model.

    Successfully loaded MassModel 'Connected_BiLinear_Reversible' into RoadRunner.
    Getting time points
    Setting output selections
    Setting simulation values for 'Connected_BiLinear_Reversible'
    Simulating 'Connected_BiLinear_Reversible'
    Simulation for 'Connected_BiLinear_Reversible' successful
    Adding 'Connected_BiLinear_Reversible' simulation solutions to output
    Updating stored solutions

```python
fig_4_5 = plt.figure(figsize=(12, 4))
gs = fig_4_5.add_gridspec(nrows=1, ncols=2)

ax1 = fig_4_5.add_subplot(gs[0, 0])
ax2 = fig_4_5.add_subplot(gs[0, 1])

plot_time_profile(
    conc_sol, observable=model.metabolites, ax=ax1, legend="left outside",
    xlabel="Time", ylabel="Concentrations",
    title=("(a) Time Profiles of Species",  {"size": "large"}));

plot_time_profile(
    conc_sol, ax=ax2, observable=["p1", "p2", "p3", "p4", "p5"],
    legend="right outside",
    xlabel="Time", ylabel="Concentrations",
    title=("(a) Time Profiles of Pools",  {"size": "large"}));
fig_4_5.tight_layout()
```

**Figure 4.5:** The concentration time profiles for the reaction system $x_1 + x_2 \rightleftharpoons x_3 \rightleftharpoons x_4 + x_5$ for $k_1 = k_{-1} = k_2 = k_{-2} = 1$
and $x_1(0)=3, x_2(0)=3, x_3(0)=0, x_4(0)=0, x_5(0)=0$ (a) The concentrations as a function of time. (b) The pools as a function of time.

In this situation, the dynamic equation for the linearized pools becomes

$$\begin{equation} \frac{d\textbf{p'}}{dt} = \begin{pmatrix} {-3} \\ {1} \\ \end{pmatrix}k_1p_1' + \begin{pmatrix} {1} \\ {-3} \\ \end{pmatrix}k_2p_2' \end{equation} \tag{4.52}$$

We can solve this equation and present the results with a dynamic phase portrait, Figure 4.6. The dynamic behavior of the non-equilibrium pools is shown for a range of parameters. We make three observations here.

1. Figure 4.6 shows that the dynamics for the pools can be decomposed to consider a fast equilibration of the two disequilibrium pools followed by the slow decay of the slower disequilibrium pool (Change values for $k_1$ and $k_2$ to visualize).

2. When reaction 1 is 10 times faster than reaction 2, then initial motion is along the vector $(-3,1)^T$, and when reaction 1 is 10 times slower than reaction 2, then initial motion is along the vector $(1,-3)^T$,

3. The linearized pools move in a similar fashion to the bi-linear disequilibrium pools, see Figure 4.6. The bi-linear and linear simulation do not change that much even though $x_1$, $x_2$, $x_4$, $x_5$ are 25% from their equilibrium value, and $x_3$ is 50% away from equilibrium.

```python
# Set new initial conditions
model.update_initial_conditions({x1: 0.75, x2: 0.75, x3: 1.5,
                                 x4: 0.75, x5: 0.75})
sim.update_model_simulation_values(model)
conc_sol, flux_sol = sim.simulate(model, time=(t0, tf))

for i, equation_str in enumerate(pools):
    pool_id = "p" + str(i + 1)
    conc_sol.make_aggregate_solution(
        pool_id, equation=equation_str,
        parameters={v1.Keq_str: v1.kf/v1.kr,
                    v2.Keq_str: v2.kf/v2.kr}, update=True)
```

```python
# Visualize solution
fig_4_6 = plt.figure(figsize=(5, 5))
gs = fig_4_6.add_gridspec(nrows=1, ncols=1)

ax1 = fig_4_6.add_subplot(gs[0, 0])

plot_phase_portrait(
    conc_sol, x="p1", y="p2", ax=ax1, legend="best",
    xlabel="p1", ylabel="p2", xlim=(-1.5, .5), ylim=(-.5, 1.5),
    title=("Phase portrait of p1 vs p2", {"size": "large"}),
    annotate_time_points="endpoints",
    annotate_time_points_labels=True);
fig_4_6.tight_layout()
```

**Figure 4.6:** The dynamic response of $x_1 + x_2 \rightleftharpoons x_3 \rightleftharpoons x_4 + x_5$ for $K_1 = K_{-1} = 1$. The graphs show the concentrations varying with time for $x_3(0)=1.5, \ x_1(0) = x_2(0) = x_4(0) = x_5(0) =0.75$ The disequilibrium pools $p_1 = x_1x_2 - x_3 / K_1$ (x-axis) and $p_2 = x_3 - x_4x_5 / K_2$ (y-axis) shown in a phase portrait.

## Summary

* Chemical properties associated with chemical reactions are; stoichiometry thermodynamics, and kinetics. The first two are physico-chemical properties, while the third can be biologically altered through enzyme action.

* Each net reaction can be described by pooled variables that represent a dis-equilibrium quantity and a mass conservation quantity that is associated with the reaction.

* If a reaction is fast compared to its network environment, its disequilibrium variable can be relaxed and then described by the conservation quantity associated with the reaction.

* Linearizing bi-linear rate laws does not create much error for small changes around the reference state.

* Removing a time scale from a model corresponds to reducing the dynamic dimension of the transient response by one.

* As the number of reactions grow, the number of conservation quantities may change.

* Irreversibility of reactions does not change the number of conservation quantities for a system.

$\tiny{\text{© B. Ø. Palsson 2011;}\ \text{This publication is in copyright.}\\ \text{Subject to statutory exception and to the provisions of relevant collective licensing agreements,}\\ \text{no reproduction of any part may take place without the written permission of Cambridge University Press.}}$


> Heavily adopted code and format from greilliam

# Simulated Classification

1. State assumptions
2. Formally define classification/regression problem
3. provide algorithm for solving problem (including choosing hyperparameters as appropriate)
4. sample data from a simulation setting inspired by your data (from both null and alternative as defined before)
5. compute accuracy
6. plot accuracy vs. sample size in simulation
7. apply method directly on real data
8. explain the degree to which you believe the result and why

## Step 1: State assumptions

Histogram Equalization
\begin{align}
F(k) = floor((L-1) \sum_{n=0}^k p_n ) \
n = 0, 1..L-1 \
L=256 \
\end{align}

\begin{align}
\newline
F(x,y) | ~~ Histogram~Data\
F_{x|0} & = Norm(\mu0)\
F_{x|1} & = Norm(\mu1)\
F_{x|2} & = Norm(\mu2)\
\mu0 \neq \mu1 \neq \mu2 \
\end{align}

Additionally we assume the elements correspond to features 1-4.

## Step 2: Formally define classification/regression problem

\begin{align}
\newline
X = {\mu0, \mu1, \mu2}\
Y = { 0, 1, 2} \
\end{align}

Classification is applied to reduce the estimated error.
Objective is to minimize Error:
$E[l] = \sum \Theta(\hat{Y}_i \neq Y_i)$

## Step 3: Provide algorithm for solving problem (including choosing hyperparameters as appropriate)

"Machine Learning" is usually classification or prediction
  - predictive is subject specific

Clarity brains have $(X, Y)$ ~iid $F_{xy}$ which is some distribution
  - X is subject
  - Y is $\{0, 1\}$
  - Function g(x) spits out a class label (thus g is a classifier function)
  - G = {g: map from reals to {0, 1}}
    - Classifier takes a single x
      - If $x>k$ but $<0$ is one classifier
      - Best clasisifier is statistical decision theory
        - Need to define a loss function that tells us how wrong we are
        - We need to choose classifier that minimizes loss
        - G* = $\underset{g \in G}{argmin} l(g(x), y)$
        - Squared error is a good option $(g(x)-y)^2$
          - Problem is that $(0-1)^2 = (1-0)^2$ so you don't know which side of the "wrong" you are
        - Absolute error is $|g(x)-y|$
        - Zero one error
          - If $g(x)=y$ then $l=0$
          - If $g(y)!=y$ then $l=1$
    - If L is the set of loss functions
      - L = {l: yxy -> Real+}
        - Here we are finding which scores are the best
    - Definitions: Voxels, Priors, Baye's rule F_(x,y) = F_(x|y)F_y=F_(y|x)F_x ->
      - $F_(x|y)=N(M_y. 1)$
      - $F_y = Bern(pi)$
      - Next need to fit the joint distribution
        - After fitting the Bayes optimal is called the Bayes plugin
        - MLE - minimizing squared error
        - Sample n train sample (xi, yi) ~iid F_xx generate training data i∈[n_train]
        - estimate classifier theta
          - sample iidF_xy i∈[n_test]

    - Best classifier is called the Bayes Optimal
      - g* = argmax F_((x=(x)|(y)=y))
        - Can use a posterari if priors are not equal
          - F_(x=x, y=y) = F_(x|y)F_y
        - compute argmax for y∈y
          - Let y = 0 is .99 y-1 is .01

    - Next need to relect get change level accuracy which will almost definitely ahppen if you use a regular loss function
      - Use histogram instead of image data
        - Classifier list:
          - LDA
            - Variances are the same
            - Made by Fischer
            - Finds optimal linear classifier (optimal line) under the assumptions that we have made
              - Advantages: Very interpretable, Very fast, Linear
          - Random Forest
            - Decision tree thresholds are created
            - Choose a loss function and then try to do a greedy search
            - Find the optimal thresholds to maximize purity
            - Change thresholds to maximize purity so that most of one group is in one part and the others are in the others
            - Random Forest uses decision trees on subsets of your data, since each tree is noisy and can overfit, so averageing over many different classifiers it will be much more effective
            - This is an ensemble method
              - Every single classifier is on a different point on the bias variance tradeoff so when you average everything it will be more consistent
          - SVM
          - Logistic
          - Neural Network
            - Uses linear algebra, runs on GPU
            - Takes in more information and is very useful for computer vision techniques
            - Natively do the classificiation
          - KNN
            - K nearest neighbor
            - specify apriori k and find the distance between the points and K
            - Assuming K is big enough, it will always converge irrespectively
            - Doesn't care about the distributions, but it is universally consistent
          - QDA
            - Quadratic descriminatory analysis
            - Optimal discriminatory boundary is curved
            - Covariance matrices

Classification Techniques :

K Nearest Neighbors(3) - Default Parameters \
Support Vector Machines - Linear , C= 0.5 - Default\
Random Forest - Max depth = 5 , N Estimators = 10, Max Features =1 - Default\
Linear Discriminant Analysis \
Quadratic Discriminant Analysis \
We ran into errors with QDA, for which reason it was ignored in the assignment.

```python
import os
PATH="/Users/david/Desktop/CourseWork/TheArtOfDataScience/claritycontrol/code/scripts/" # use your own path
os.chdir(PATH)

import clarity as cl  # I wrote this module for easier operations on data
import clarity.resources as rs
import csv,gc  # garbage memory collection :)

import numpy as np
import matplotlib.pyplot as plt
import jgraph as ig
%matplotlib inline

# settings for histogram
BINS=32 # histogram bins
RANGE=(10.0,300.0)
```

### Histogram data preparation

> Skip this step if the data are already baked, just eat them!

This and next step will load raw image datasets (which are pretty large) and extract histograms from these dataset. The datasets are not included in the repository (Because they are too large), but you can skip this and next step, use the histogram data that already generated in the repository.

1. Set suitable data value range to get histogram from the majority of the datasets.

```python
for token in rs.TOKENS:
    c = cl.Clarity(token)
    fname = rs.HIST_DATA_PATH+token+".csv"
    hist, bin_edges = c.loadImg().getHistogram(bins=BINS,range=RANGE,density=False)
    np.savetxt(fname,hist,delimiter=',')
    print fname,"saved."
    del c
    gc.collect()
```

    Image Loaded: ../data/raw/Cocaine174.img
    ../data/hist/Cocaine174.csv saved.
    Image Loaded: ../data/raw/Cocaine175.img
    ../data/hist/Cocaine175.csv saved.
    Image Loaded: ../data/raw/Cocaine178.img
    ../data/hist/Cocaine178.csv saved.
    Image Loaded: ../data/raw/Control181.img
    ../data/hist/Control181.csv saved.
    Image Loaded: ../data/raw/Control182.img
    ../data/hist/Control182.csv saved.
    Image Loaded: ../data/raw/Control189.img
    ../data/hist/Control189.csv saved.
    Image Loaded: ../data/raw/Control239.img
    ../data/hist/Control239.csv saved.
    Image Loaded: ../data/raw/Control258.img
    ../data/hist/Control258.csv saved.
    Image Loaded: ../data/raw/Fear187.img
    ../data/hist/Fear187.csv saved.
    Image Loaded: ../data/raw/Fear197.img
    ../data/hist/Fear197.csv saved.
    Image Loaded: ../data/raw/Fear199.img
    ../data/hist/Fear199.csv saved.
    Image Loaded: ../data/raw/Fear200.img
    ../data/hist/Fear200.csv saved.

### Scale data

```python
import numpy as np
import clarity.resources as rs
features = np.empty(shape=(1,BINS))
for token in rs.TOKENS:
    fname = rs.HIST_DATA_PATH+token+".csv"
    data = np.loadtxt(fname,delimiter=',')
    features = np.vstack([features,data])
features = features[1:,]
minc = np.min(features)
maxc = np.max(features)
features = (features-minc)/(maxc-minc)
print features
np.savetxt(rs.HIST_DATA_PATH+"features.csv",features,delimiter=',')
```

    [[  3.02680054e-08   5.44824096e-08   2.05822436e-07   5.87199304e-07
        1.83726793e-06   6.61658597e-06   2.59366538e-05   1.53237831e-04
        5.70115436e-03   4.51718743e-01   1.00000000e+00   1.35286291e-01
        8.49485827e-02   7.86618544e-02   7.35613535e-02   6.39701853e-02
        5.84338221e-02   4.16227176e-02   3.39392057e-02   2.77592599e-02
        2.26080025e-02   1.85846279e-02   1.54385957e-02   1.29580479e-02
        1.10633858e-02   9.58203931e-03   8.43465187e-03   7.51167143e-03
        6.77831398e-03   6.13509768e-03   5.61766007e-03   5.66908239e-03]
     [  5.75092102e-08   1.63447229e-07   3.29921258e-07   7.65780536e-07
        2.70898648e-06   6.04452067e-06   2.48560860e-05   1.17918095e-04
        3.74111033e-03   2.92376673e-01   5.84049388e-01   1.55948727e-01
        1.33041235e-01   1.10344462e-01   8.33999686e-02   5.94000858e-02
        4.60053584e-02   2.86529742e-02   2.09942094e-02   1.57893807e-02
        1.21508849e-02   9.69784168e-03   7.96022813e-03   6.71293811e-03
        5.74635963e-03   4.98288249e-03   4.34869513e-03   3.79060457e-03
        3.33360309e-03   2.91697611e-03   2.57125192e-03   2.48431616e-03]
     [  1.51340027e-08   3.66242865e-07   2.99653253e-07   4.78234485e-07
        1.74949071e-06   5.71157261e-06   2.15841146e-05   1.47459669e-04
        4.75858749e-03   3.65928895e-01   5.59457624e-01   9.14320651e-02
        7.04332368e-02   6.77734601e-02   6.61138926e-02   6.02482195e-02
        5.87921771e-02   4.49651953e-02   3.86687359e-02   3.28632205e-02
        2.74471362e-02   2.28967502e-02   1.90871041e-02   1.59889346e-02
        1.34611687e-02   1.14588645e-02   9.82884766e-03   8.54004507e-03
        7.50793030e-03   6.61872592e-03   5.89751502e-03   5.80544883e-03]
     [  3.63216064e-08   6.96164123e-08   2.30036841e-07   5.81145703e-07
        3.06826770e-05   5.18793612e-06   1.32725204e-05   7.93142812e-05
        2.81480040e-03   2.27715179e-01   3.88426713e-01   4.86363048e-02
        3.30274910e-02   3.20018384e-02   3.28574847e-02   3.29833875e-02
        3.59319392e-02   3.01235029e-02   2.75277914e-02   2.42873199e-02
        2.08061179e-02   1.76493012e-02   1.49466922e-02   1.27984447e-02
        1.11091631e-02   9.75884987e-03   8.67802177e-03   7.79015826e-03
        7.11130139e-03   6.48077343e-03   5.96284851e-03   6.07441638e-03]
     [  9.08040161e-09   3.08733655e-07   9.08040161e-08   2.72412048e-07
        1.01700498e-06   3.18722096e-06   1.19074333e-05   7.10420354e-05
        2.30429719e-03   1.88612606e-01   3.07727452e-01   6.41034917e-02
        6.12421027e-02   6.40454376e-02   6.18823649e-02   5.43280337e-02
        4.93737908e-02   3.35422378e-02   2.49486728e-02   1.82312308e-02
        1.34255372e-02   1.02467338e-02   8.07892109e-03   6.57214043e-03
        5.49152724e-03   4.67500239e-03   4.07299598e-03   3.58319912e-03
        3.20458875e-03   2.88046684e-03   2.60133832e-03   2.60068453e-03]
     [  6.35628113e-08   1.48313226e-07   2.84519250e-07   7.99075341e-07
        3.58675864e-06   8.91090078e-06   2.40842519e-05   1.47432427e-04
        4.54237405e-03   3.57057751e-01   5.48353403e-01   1.44579656e-01
        1.03936638e-01   7.37345530e-02   4.85449711e-02   3.20946643e-02
        2.47973903e-02   1.60047890e-02   1.22824932e-02   9.62169040e-03
        7.62616016e-03   6.16654008e-03   5.07482156e-03   4.24220018e-03
        3.58879265e-03   3.05773746e-03   2.62668777e-03   2.26323562e-03
        1.96884900e-03   1.70269032e-03   1.49130765e-03   1.42861050e-03]
     [  6.96164123e-08   1.18045221e-07   3.72296466e-07   7.89994940e-07
        1.88872353e-06   6.16861949e-06   2.43203423e-05   1.57578263e-04
        5.39432759e-03   4.22281610e-01   6.68446346e-01   1.20708070e-01
        8.04814670e-02   6.31055404e-02   5.22423896e-02   4.33928239e-02
        3.99923679e-02   2.89120350e-02   2.35104855e-02   1.89281002e-02
        1.51471511e-02   1.22824206e-02   1.01160638e-02   8.46310077e-03
        7.12237039e-03   6.07994029e-03   5.20926296e-03   4.50099768e-03
        3.92865392e-03   3.42491864e-03   3.02073785e-03   2.94322755e-03]
     [  2.11876038e-08   8.17236145e-08   2.14902838e-07   5.81145703e-07
        3.54741023e-06   8.78982876e-06   2.38723758e-05   1.79274369e-04
        5.95812368e-03   4.64601472e-01   6.34477707e-01   8.05992186e-02
        6.43304381e-02   6.39353075e-02   6.14997047e-02   5.54860483e-02
        5.27806062e-02   3.82177487e-02   3.09508758e-02   2.50019384e-02
        2.01594753e-02   1.64956997e-02   1.36656594e-02   1.14503289e-02
        9.70391646e-03   8.28194071e-03   7.10695793e-03   6.15469016e-03
        5.40964926e-03   4.74080806e-03   4.20901132e-03   4.10615458e-03]
     [  1.24098822e-07   2.17929639e-07   3.60189264e-07   6.78003320e-07
        1.89175033e-06   5.66919740e-06   2.02674564e-05   1.37934327e-04
        4.31221613e-03   3.23488629e-01   4.72462055e-01   9.56688382e-02
        7.78883222e-02   7.01059247e-02   5.98414599e-02   4.71343913e-02
        4.03152942e-02   2.79142745e-02   2.27308120e-02   1.87224594e-02
        1.53413506e-02   1.26528919e-02   1.04781902e-02   8.81702456e-03
        7.56055728e-03   6.58286136e-03   5.78614087e-03   5.12447016e-03
        4.59966533e-03   4.10518298e-03   3.68900698e-03   3.64269088e-03]
     [  5.75092102e-08   9.68576172e-08   1.78581232e-07   5.44824096e-07
        1.59815068e-06   5.24241853e-06   1.89144765e-05   1.22225232e-04
        4.28175139e-03   3.32084395e-01   5.21520490e-01   8.18890443e-02
        6.44157788e-02   5.91305219e-02   5.30068656e-02   4.60999217e-02
        4.42667762e-02   3.33409010e-02   2.82204747e-02   2.37420660e-02
        1.98499335e-02   1.66562594e-02   1.39673678e-02   1.17270996e-02
        9.87607785e-03   8.36676982e-03   7.17462508e-03   6.21617658e-03
        5.47604213e-03   4.82562809e-03   4.30427986e-03   4.25544546e-03]
     [  1.48313226e-07   1.75554431e-07   4.08618072e-07   9.05013360e-07
        2.22772519e-06   7.56700134e-06   2.32034529e-05   1.40591858e-04
        4.59383571e-03   3.52431895e-01   5.96727000e-01   1.51405639e-01
        1.13602783e-01   8.30500614e-02   5.40600529e-02   3.30028922e-02
        2.26985584e-02   1.32255959e-02   9.49274264e-03   7.10924921e-03
        5.44678507e-03   4.31920502e-03   3.49370873e-03   2.88800357e-03
        2.42458830e-03   2.04189175e-03   1.74202965e-03   1.48730017e-03
        1.27906537e-03   1.10062943e-03   9.60428024e-04   9.17032785e-04]
     [  0.00000000e+00   1.33179224e-07   1.33179224e-07   2.87546051e-07
        1.38930145e-06   2.94507692e-06   9.83407494e-06   6.18950442e-05
        2.06246794e-03   1.63011485e-01   2.58921671e-01   4.43054648e-02
        3.46486605e-02   3.27022038e-02   3.08151722e-02   2.81701844e-02
        2.80819501e-02   2.16081807e-02   1.83370235e-02   1.52709412e-02
        1.25290171e-02   1.02423268e-02   8.38137413e-03   6.85885411e-03
        5.64271290e-03   4.68338360e-03   3.92759151e-03   3.32991342e-03
        2.88653255e-03   2.52064382e-03   2.24798660e-03   2.22261293e-03]]

### Setup Step

```python
from sklearn import cross_validation
from sklearn.cross_validation import LeaveOneOut
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis

%matplotlib inline

np.random.seed(12345678)  # for reproducibility, set random seed

# Cocaine = ["Cocaine174","Cocaine175","Cocaine178"]
# Control = ["Control181","Control182","Control189","Control239","Control258"]
# Fear = ["Fear187","Fear197","Fear199","Fear200"]

features = np.loadtxt(rs.HIST_DATA_PATH+"features.csv",delimiter=',')
temp_mu = np.mean(features,axis=1)
temp_std = np.std(features,axis=1)

mu = [np.mean(temp_mu[0:3]),np.mean(temp_mu[3:8]),np.mean(temp_mu[8:12])]
std = [np.mean(temp_std[0:3]),np.mean(temp_std[3:8]),np.mean(temp_std[8:12])]
print mu
print std
std=[1,1,1]

# define number of subjects per class
S = np.array((9, 21, 30, 39, 45, 63, 81, 96, 108, 210, 333))

names = ["Nearest Neighbors", "Linear SVM", "Random Forest",
         "Linear Discriminant Analysis", "Quadratic Discriminant Analysis"]

classifiers = [
    KNeighborsClassifier(3),
    SVC(kernel="linear", C=0.5),
    RandomForestClassifier(max_depth=5, n_estimators=10, max_features=1),
    LinearDiscriminantAnalysis()]
#     QuadraticDiscriminantAnalysis()]
```

    [0.056280388503550105, 0.042498942485078253, 0.038805754482162662]
    [0.13726273492760607, 0.1031691019233711, 0.093076772034756644]

## Steps 4 & 5: Sample data from setting similar to data and record classification accuracy

```python
accuracy = np.zeros((len(S), len(classifiers), 2), dtype=np.dtype('float64'))
for idx1, s in enumerate(S):
    s0=s/3
    s1=s/3
    s2=s/3

    x0 = np.random.normal(mu[0],std[0],(s0,BINS))
    x1 = np.random.normal(mu[1],std[1],(s1,BINS))
    x2 = np.random.normal(mu[2],std[2],(s2,BINS))
    X = x0
    X = np.vstack([X,x1])
    X = np.vstack([X,x2])
    y = np.append(np.append(np.zeros(s0), np.ones(s1)),np.ones(s2)*2)
    for idx2, cla in enumerate(classifiers):
        X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, test_size=0.4, random_state=0)
        clf = cla.fit(X_train, y_train)
        loo = LeaveOneOut(len(X))
        scores = cross_validation.cross_val_score(clf, X, y, cv=loo)
        accuracy[idx1, idx2,] = [scores.mean(), scores.std()]
        print("Accuracy of %s: %0.2f (+/- %0.2f)" % (names[idx2], scores.mean(), scores.std() * 2))

print accuracy
```

    Accuracy of Nearest Neighbors: 0.33 (+/- 0.94)
    Accuracy of Linear SVM: 0.44 (+/- 0.99)
    Accuracy of Random Forest: 0.11 (+/- 0.63)
    Accuracy of Linear Discriminant Analysis: 0.22 (+/- 0.83)
    Accuracy of Nearest Neighbors: 0.24 (+/- 0.85)
    Accuracy of Linear SVM: 0.24 (+/- 0.85)
    Accuracy of Random Forest: 0.14 (+/- 0.70)

    /usr/local/lib/python2.7/site-packages/sklearn/discriminant_analysis.py:387: UserWarning: Variables are collinear.
      warnings.warn("Variables are collinear.")
    /usr/local/lib/python2.7/site-packages/sklearn/discriminant_analysis.py:453: UserWarning: The priors do not sum to 1. Renormalizing
      UserWarning)

    Accuracy of Linear Discriminant Analysis: 0.19 (+/- 0.79)
    Accuracy of Nearest Neighbors: 0.30 (+/- 0.92)
    Accuracy of Linear SVM: 0.27 (+/- 0.88)
    Accuracy of Random Forest: 0.40 (+/- 0.98)
    Accuracy of Linear Discriminant Analysis: 0.37 (+/- 0.96)
    Accuracy of Nearest Neighbors: 0.28 (+/- 0.90)
    Accuracy of Linear SVM: 0.28 (+/- 0.90)
    Accuracy of Random Forest: 0.26 (+/- 0.87)
    Accuracy of Linear Discriminant Analysis: 0.33 (+/- 0.94)
    Accuracy of Nearest Neighbors: 0.27 (+/- 0.88)
    Accuracy of Linear SVM: 0.27 (+/- 0.88)
    Accuracy of Random Forest: 0.40 (+/- 0.98)
    Accuracy of Linear Discriminant Analysis: 0.36 (+/- 0.96)
    Accuracy of Nearest Neighbors: 0.44 (+/- 0.99)
    Accuracy of Linear SVM: 0.37 (+/- 0.96)
    Accuracy of Random Forest: 0.32 (+/- 0.93)
    Accuracy of Linear Discriminant Analysis: 0.30 (+/- 0.92)
    Accuracy of Nearest Neighbors: 0.32 (+/- 0.93)
    Accuracy of Linear SVM: 0.26 (+/- 0.88)
    Accuracy of Random Forest: 0.22 (+/- 0.83)
    Accuracy of Linear Discriminant Analysis: 0.40 (+/- 0.98)
    Accuracy of Nearest Neighbors: 0.32 (+/- 0.94)
    Accuracy of Linear SVM: 0.25 (+/- 0.87)
    Accuracy of Random Forest: 0.32 (+/- 0.94)
    Accuracy of Linear Discriminant Analysis: 0.24 (+/- 0.85)
    Accuracy of Nearest Neighbors: 0.29 (+/- 0.90)
    Accuracy of Linear SVM: 0.47 (+/- 1.00)
    Accuracy of Random Forest: 0.28 (+/- 0.90)
    Accuracy of Linear Discriminant Analysis: 0.38 (+/- 0.97)
    Accuracy of Nearest Neighbors: 0.27 (+/- 0.88)
    Accuracy of Linear SVM: 0.38 (+/- 0.97)
    Accuracy of Random Forest: 0.35 (+/- 0.96)
    Accuracy of Linear Discriminant Analysis: 0.37 (+/- 0.96)
    Accuracy of Nearest Neighbors: 0.39 (+/- 0.97)
    Accuracy of Linear SVM: 0.34 (+/- 0.94)
    Accuracy of Random Forest: 0.35 (+/- 0.95)
    Accuracy of Linear Discriminant Analysis: 0.32 (+/- 0.93)
    [[[ 0.33333333  0.47140452]
      [ 0.44444444  0.49690399]
      [ 0.11111111  0.31426968]
      [ 0.22222222  0.41573971]]

     [[ 0.23809524  0.42591771]
      [ 0.23809524  0.42591771]
      [ 0.14285714  0.34992711]
      [ 0.19047619  0.39267673]]

     [[ 0.3         0.45825757]
      [ 0.26666667  0.44221664]
      [ 0.4         0.48989795]
      [ 0.36666667  0.48189441]]

     [[ 0.28205128  0.44999817]
      [ 0.28205128  0.44999817]
      [ 0.25641026  0.43665093]
      [ 0.33333333  0.47140452]]

     [[ 0.26666667  0.44221664]
      [ 0.26666667  0.44221664]
      [ 0.4         0.48989795]
      [ 0.35555556  0.47868132]]

     [[ 0.44444444  0.49690399]
      [ 0.36507937  0.48145241]
      [ 0.31746032  0.4654882 ]
      [ 0.3015873   0.45894706]]

     [[ 0.32098765  0.46685606]
      [ 0.25925926  0.43822813]
      [ 0.22222222  0.41573971]
      [ 0.39506173  0.48886395]]

     [[ 0.32291667  0.46759116]
      [ 0.25        0.4330127 ]
      [ 0.32291667  0.46759116]
      [ 0.23958333  0.42682919]]

     [[ 0.28703704  0.45237902]
      [ 0.47222222  0.4992278 ]
      [ 0.27777778  0.44790321]
      [ 0.37962963  0.48529473]]

     [[ 0.26666667  0.44221664]
      [ 0.38095238  0.48562091]
      [ 0.35238095  0.47771186]
      [ 0.36666667  0.48189441]]

     [[ 0.38738739  0.48715336]
      [ 0.33633634  0.47245551]
      [ 0.34834835  0.47644703]
      [ 0.31831832  0.46582375]]]

## Step 6: Plot Accuracy versus N

```python
plt.errorbar(S, accuracy[:,0,0], yerr = accuracy[:,0,1], hold=True, label=names[0])
plt.errorbar(S, accuracy[:,1,0], yerr = accuracy[:,1,1], color='green', hold=True, label=names[1])
plt.errorbar(S, accuracy[:,2,0], yerr = accuracy[:,2,1], color='red', hold=True, label=names[2])
plt.errorbar(S, accuracy[:,3,0], yerr = accuracy[:,3,1], color='black', hold=True, label=names[3])
# plt.errorbar(S, accuracy[:,4,0], yerr = accuracy[:,4,1], color='brown', hold=True, label=names[4])
plt.xscale('log')
plt.xlabel('number of samples')
plt.ylabel('accuracy')
plt.title('Accuracy of classification under simulated data')
plt.axhline(1, color='red', linestyle='--')
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))
plt.show()
```

## Step 7: Apply technique to data

```python
y=np.array([0,0,0,1,1,1,1,1,2,2,2,2])
features = np.loadtxt(rs.HIST_DATA_PATH+"features.csv",delimiter=',')
```

```python
accuracy=np.zeros((len(classifiers),2))
for idx, cla in enumerate(classifiers):
    X_train, X_test, y_train, y_test = cross_validation.train_test_split(features, y, test_size=0.4, random_state=0)
    clf = cla.fit(X_train, y_train)
    loo = LeaveOneOut(len(features))
    scores = cross_validation.cross_val_score(clf, features, y, cv=loo)
    accuracy[idx,] = [scores.mean(), scores.std()]
    print("Accuracy of %s: %0.2f (+/- %0.2f)" % (names[idx], scores.mean(), scores.std() * 2))
```

    Accuracy of Nearest Neighbors: 0.08 (+/- 0.55)
    Accuracy of Linear SVM: 0.25 (+/- 0.87)
    Accuracy of Random Forest: 0.25 (+/- 0.87)
    Accuracy of Linear Discriminant Analysis: 0.25 (+/- 0.87)

## Step 8: Reflect on result

Our results are highly unsatisfactory with very low accuracy rates and very high error bar values. This is roughly what we expected however due to the nature of our data being rather unsuited for this kind of analysis. We will do some statistical reconfigurations in order to analyze the data to a more satisfactory state.


# [ATM 623: Climate Modeling](../index.ipynb)
[Brian E. J. Rose](http://www.atmos.albany.edu/facstaff/brose/index.html), University at Albany
# Lecture 7: Grey radiation modeling with climlab

### About these notes:

This document uses the interactive [`IPython notebook`](http://ipython.org/notebook.html) format (now also called [`Jupyter`](https://jupyter.org)). 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 2015 (end of spring semester) are [available on Brian's website](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

## Contents

1. [Introducing `climlab`](#section1)
2. [Using `climlab` to implement the two-layer leaky greenhouse model](#section2)
3. [The observed annual, global mean temperature profile](#section3)
4. [A 30-layer model using the observed temperatures](#section4)
5. [Radiative forcing in the 30-layer model](#section5)
6. [Radiative equilibrium in the 30-layer model](#section6)
7. [Radiative-Convective Equilibrium in the 30-layer model](#section7)
8. [Putting stratospheric ozone in the grey-gas model](#section8)

____________

## 1. Introducing `climlab`
____________

``climlab`` is a flexible engine for process-oriented climate modeling.
It is based on a very general concept of a model as a collection of individual,
interacting processes. ``climlab`` defines a base class called ``Process``, which
can contain an arbitrarily complex tree of sub-processes (each also some
sub-class of ``Process``). Every climate process (radiative, dynamical,
physical, turbulent, convective, chemical, etc.) can be simulated as a stand-alone
process model given appropriate input, or as a sub-process of a more complex model.
New classes of model can easily be defined and run interactively by putting together an
appropriate collection of sub-processes.

``climlab`` is a work-in-progress, and the code base will evolve substantially over the course of this semester.
The latest code can always be found on ``github``:

https://github.com/brian-rose/climlab

You are strongly encouraged to clone the ``climlab`` repository and use ``git`` to keep your local copy up-to-date.

Running this notebook requires that ``climlab`` is already installed on your system.

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

____________

## 2. Using `climlab` to implement the two-layer leaky greenhouse model
____________

One of the things that ``climlab`` is set up to do is the grey-radiation modeling we have already been discussing.

Since we already derived a [complete analytical solution to the two-layer leaky greenhouse model](Lecture06 -- Elementary greenhouse models.ipynb), we will use this to validate the `climlab` code.

### Validation

We want to verify that the model reproduces the observed OLR given observed temperatures, and the absorptivity that we tuned in the analytical model. The target numbers are:

\begin{align}
T_s &= 288 \text{ K} \\
T_0 &= 275 \text{ K} \\
T_1 &= 230 \text{ K} \\
\end{align}

$$ \epsilon = 0.58377  $$

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

### Initialize a model in `climlab`
The first thing we do is create a new model.

The following example code is sparsely commented but will hopefully orient you on the basics of defining and working with a `climlab Process` object.

```python
#  Test in a 2-layer atmosphere
col = climlab.GreyRadiationModel(num_lev=2)
print col
```

    climlab Process of type <class 'climlab.model.column.GreyRadiationModel'>.
    State variables and domain shapes:
      Tatm: (2,)
      Ts: (1,)
    The subprocess tree:
    top: <class 'climlab.model.column.GreyRadiationModel'>
       LW: <class 'climlab.radiation.radiation.Radiation'>
       SW: <class 'climlab.radiation.radiation.RadiationSW'>
       surface: <class 'climlab.surface.surface_radiation.SurfaceRadiation'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>

```python
col.subprocess
```

    {'LW': <climlab.radiation.radiation.Radiation at 0x110fdbfd0>,
     'SW': <climlab.radiation.radiation.RadiationSW at 0x1066b2210>,
     'insolation': <climlab.radiation.insolation.FixedInsolation at 0x1066b2250>,
     'surface': <climlab.surface.surface_radiation.SurfaceRadiation at 0x110fdbf10>}

```python
col.state
```

    {'Tatm': Field([ 278.,  200.]), 'Ts': Field([ 288.])}

```python
col.Ts
```

    Field([ 288.])

```python
col.Ts[:] = 288.
col.Tatm[:] = np.array([275., 230.])
col.state
```

    {'Tatm': Field([ 275.,  230.]), 'Ts': Field([ 288.])}

```python
LW = col.subprocess['LW']
print LW
```

    climlab Process of type <class 'climlab.radiation.radiation.Radiation'>.
    State variables and domain shapes:
      Tatm: (2,)
      Ts: (1,)
    The subprocess tree:
    top: <class 'climlab.radiation.radiation.Radiation'>

```python
LW.absorptivity
```

    Field([ 0.47737425,  0.47737425])

```python
LW.absorptivity = 0.58377
LW.absorptivity
```

    Field([ 0.58377,  0.58377])

```python
#  This does all the calculations that would be performed at each time step,
#  but doesn't actually update the temperatures
col.compute_diagnostics()
col.diagnostics
```

    {'ASR': Field([ 239.2513]),
     'LW_absorbed_atm': array([-96.82138041,  20.03935568]),
     'LW_absorbed_sfc': Field([-162.23386935]),
     'LW_down_sfc': array([ 227.87116061]),
     'LW_emission': Field([ 189.31461699,   92.63278385]),
     'LW_up_sfc': Field([ 390.10502995]),
     'OLR': array([ 239.01589408]),
     'SW_absorbed_atm': array([-0., -0.]),
     'SW_absorbed_sfc': Field([ 239.2513]),
     'SW_absorbed_total': 239.25130000000001,
     'SW_down_TOA': Field([ 341.3]),
     'SW_down_sfc': array([ 341.3]),
     'SW_emission': Field([ 0.,  0.]),
     'SW_up_TOA': array([ 102.0487]),
     'SW_up_sfc': Field([ 0.]),
     'insolation': Field([ 341.3]),
     'planetary_albedo': Field([ 0.299])}

```python
#  Check OLR against our analytical solution
col.diagnostics['OLR']
```

    array([ 239.01589408])

```python
col.state
```

    {'Tatm': Field([ 275.,  230.]), 'Ts': Field([ 288.])}

```python
# perform a single time step
col.step_forward()
```

```python
col.state
```

    {'Tatm': Field([ 273.36692033,  230.33800245]), 'Ts': Field([ 289.59144429])}

```python
# integrate out to radiative equilibrium
col.integrate_years(2.)
```

    Integrating for 730 steps, 730.4844 days, or 2.0 years.
    Total elapsed time is 2.00962539378 years.

```python
# Check for equilibrium
col.diagnostics['ASR'] - col.diagnostics['OLR']
```

    Field([ -5.20579960e-07])

```python
#  Compare these temperatures against our analytical solutions for radiative equilibrium
col.state
```

    {'Tatm': Field([ 262.08988341,  233.62925798]), 'Ts': Field([ 296.20384538])}

So it looks like `climlab` agrees with our analytical results. That's good.

____________

## 3. The observed annual, global mean temperature profile
____________

We want to model the OLR in a column whose temperatures match observations. As we've done before, we'll calculate the global, annual mean air temperature from the NCEP Reanalysis data.

```python
# This will try to read the data over the internet.
#  If you have a local copy of the data, just use the local path to the .nc file instead of the URL
ncep_url = "http://www.esrl.noaa.gov/psd/thredds/dodsC/Datasets/ncep.reanalysis.derived/"
ncep_air = nc.Dataset( ncep_url + "pressure/air.mon.1981-2010.ltm.nc" )
level = ncep_air.variables['level'][:]
lat = ncep_air.variables['lat'][:]
# A log-pressure height coordinate
zstar = -np.log(level/1000)
```

```python
Tzon = np.mean(ncep_air.variables['air'][:],axis=(0,3))
Tglobal = np.average( Tzon , weights=np.cos(np.deg2rad(lat)), axis=1) + climlab.constants.tempCtoK
#  Note the useful conversion factor. climlab.constants has lots of commonly used constant pre-defined
```

```python
#  Here we are plotting with respect to log(pressure) but labeling the axis in pressure units
fig = plt.figure( figsize=(8,6) )
ax = fig.add_subplot(111)
ax.plot( Tglobal , zstar )
yticks = np.array([1000., 750., 500., 250., 100., 50., 20., 10.])
ax.set_yticks(-np.log(yticks/1000.))
ax.set_yticklabels(yticks)
ax.set_xlabel('Temperature (K)', fontsize=16)
ax.set_ylabel('Pressure (hPa)', fontsize=16 )
ax.set_title('Global, annual mean sounding from NCEP Reanalysis', fontsize = 24)
ax.grid()
```

____________

## 4. A 30-layer model using the observed temperatures
____________

```python
#  initialize a grey radiation model with 30 levels
col = climlab.GreyRadiationModel()
print col
```

    climlab Process of type <class 'climlab.model.column.GreyRadiationModel'>.
    State variables and domain shapes:
      Tatm: (30,)
      Ts: (1,)
    The subprocess tree:
    top: <class 'climlab.model.column.GreyRadiationModel'>
       LW: <class 'climlab.radiation.radiation.Radiation'>
       SW: <class 'climlab.radiation.radiation.RadiationSW'>
       surface: <class 'climlab.surface.surface_radiation.SurfaceRadiation'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>

```python
# interpolate to 30 evenly spaced pressure levels
lev = col.lev
Tinterp = np.flipud(np.interp(np.flipud(lev), np.flipud(level), np.flipud(Tglobal)))
Tinterp
#  Need to 'flipud' because the interpolation routine needs the pressure data to be in increasing order
```

    array([ 287.4463874 ,  285.6810201 ,  283.98269653,  282.48550415,
            280.98831177,  279.29533895,  277.60236613,  275.90939331,
            274.21642049,  272.25855509,  270.03579712,  267.81303914,
            265.29490662,  262.48139954,  259.66789246,  256.48087056,
            252.92033386,  249.35979716,  245.279658  ,  240.67991638,
            236.08017476,  231.30422974,  226.3520813 ,  221.78252157,
            217.19397481,  212.58644104,  208.29142761,  206.96233453,
            211.66334534,  224.34736633])

```python
# Initialize model with observed temperatures
col.Ts[:] = Tglobal[0]
col.Tatm[:] = Tinterp
```

```python
#  A handy re-usable routine for making a plot of the temperature profiles
#  We will plot temperatures with respect to log(pressure) to get a height-like coordinate
def plot_sounding(collist):
    color_cycle=['r', 'g', 'b', 'y']
    # col is either a column model object or a list of column model objects
    if isinstance(collist, climlab.Process):
        # make a list with a single item
        collist = [collist]
    fig = plt.figure()
    ax = fig.add_subplot(111)
    for i, col in enumerate(collist):
        zstar = -np.log(col.lev/climlab.constants.ps)
        ax.plot(col.Tatm, zstar, color=color_cycle[i])
        ax.plot(col.Ts, 0, 'o', markersize=12, color=color_cycle[i])
    #ax.invert_yaxis()
    yticks = np.array([1000., 750., 500., 250., 100., 50., 20., 10.])
    ax.set_yticks(-np.log(yticks/1000.))
    ax.set_yticklabels(yticks)
    ax.set_xlabel('Temperature (K)')
    ax.set_ylabel('Pressure (hPa)')
    ax.grid()
    return ax
```

```python
# This should look just like the observations
plot_sounding(col)
```

### Tune absorptivity to get observed OLR

```python
col.compute_diagnostics()
col.diagnostics['OLR']
```

    array([ 263.15000167])

```python
# Need to tune absorptivity to get OLR = 239
epsarray = np.linspace(0.01, 0.1, 100)
OLRarray = np.zeros_like(epsarray)
```

```python
for i in range(epsarray.size):
    col.subprocess['LW'].absorptivity = epsarray[i]
    col.compute_diagnostics()
    OLRarray[i] = col.diagnostics['OLR']

plt.plot(epsarray, OLRarray)
plt.grid()
```

The necessary value seems to lie near 0.055 or so.

We can be more precise with a numerical root-finder.

```python
def OLRanom(eps):
    col.subprocess['LW'].absorptivity = eps
    col.compute_diagnostics()
    return col.diagnostics['OLR'] - 239.
```

```python
# Use numerical root-finding to get the equilibria
from scipy.optimize import brentq
# brentq is a root-finding function
#  Need to give it a function and two end-points
#  It will look for a zero of the function between those end-points
eps = brentq(OLRanom, 0.01, 0.1)
print eps
```

    0.0534031813092

```python
col.subprocess['LW'].absorptivity = eps
col.subprocess['LW'].absorptivity
```

    Field([ 0.05340318,  0.05340318,  0.05340318,  0.05340318,  0.05340318,
            0.05340318,  0.05340318,  0.05340318,  0.05340318,  0.05340318,
            0.05340318,  0.05340318,  0.05340318,  0.05340318,  0.05340318,
            0.05340318,  0.05340318,  0.05340318,  0.05340318,  0.05340318,
            0.05340318,  0.05340318,  0.05340318,  0.05340318,  0.05340318,
            0.05340318,  0.05340318,  0.05340318,  0.05340318,  0.05340318])

```python
col.compute_diagnostics()
col.diagnostics['OLR']
```

    array([ 239.])

____________

## 5. Radiative forcing in the 30-layer model
____________

Let's compute radiative forcing for a **2% increase in absorptivity**.

```python
#  clone our model using a built-in climlab function
col2 = climlab.process_like(col)
print col2
```

    climlab Process of type <class 'climlab.model.column.GreyRadiationModel'>.
    State variables and domain shapes:
      Tatm: (30,)
      Ts: (1,)
    The subprocess tree:
    top: <class 'climlab.model.column.GreyRadiationModel'>
       LW: <class 'climlab.radiation.radiation.Radiation'>
       SW: <class 'climlab.radiation.radiation.RadiationSW'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>
       surface: <class 'climlab.surface.surface_radiation.SurfaceRadiation'>

```python
col2.subprocess['LW'].absorptivity *= 1.02
col2.subprocess['LW'].absorptivity
```

    Field([ 0.05447124,  0.05447124,  0.05447124,  0.05447124,  0.05447124,
            0.05447124,  0.05447124,  0.05447124,  0.05447124,  0.05447124,
            0.05447124,  0.05447124,  0.05447124,  0.05447124,  0.05447124,
            0.05447124,  0.05447124,  0.05447124,  0.05447124,  0.05447124,
            0.05447124,  0.05447124,  0.05447124,  0.05447124,  0.05447124,
            0.05447124,  0.05447124,  0.05447124,  0.05447124,  0.05447124])

```python
#  Radiative forcing by definition is the change in TOA radiative flux, HOLDING THE TEMPERATURES FIXED.
col2.Ts - col.Ts
```

    Field([ 0.])

```python
col2.Tatm - col.Tatm
```

    Field([ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
            0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
            0.,  0.,  0.,  0.])

```python
col2.compute_diagnostics()
col2.diagnostics['OLR']
```

    array([ 237.15483194])

The OLR decreased after we added the extra absorbers, as we expect. Now we can calculate the Radiative Forcing:

```python
RF = -(col2.diagnostics['OLR'] - col.diagnostics['OLR'])
print 'The radiative forcing is %f W/m2.' %RF
```

    The radiative forcing is 1.845168 W/m2.

____________

## 6. Radiative equilibrium in the 30-layer model
____________

```python
re = climlab.process_like(col)
```

```python
#  To get to equilibrium, we just time-step the model forward long enough
re.integrate_years(2.)
```

    Integrating for 730 steps, 730.4844 days, or 2.0 years.
    Total elapsed time is 2.91039753895 years.

```python
#  Check for energy balance
re.diagnostics['ASR'] - re.diagnostics['OLR']
```

    Field([ -8.67412012e-07])

```python
plot_sounding([col, re])
```

Some properties of the **radiative equilibrium** temperature profile:

- The surface is warmer than observed.
- The lower troposphere is colder than observed.
- Very cold air is sitting immediately above the warm surface.
- There is no tropopause, no stratosphere.

____________

## 7. Radiative-Convective Equilibrium in the 30-layer model
____________

We recognize that the large drop in temperature just above the surface is unphysical. Parcels of air in direct contact with the ground will be warmed by mechansisms other than radiative transfer.

These warm air parcels will then become buoyant, and will convect upward, mixing their heat content with the environment.

We **parameterize** the statistical effects of this mixing through a **convective adjustment**.

At each timestep, our model checks for any locations at which the **lapse rate** exceeds some threshold. Unstable layers are removed through an energy-conserving mixing formula.

This process is assumed to be fast relative to radiative heating. In the model, it is instantaneous.

```python
rce = climlab.RadiativeConvectiveModel(adj_lapse_rate=6.)
print rce
```

    climlab Process of type <class 'climlab.model.column.RadiativeConvectiveModel'>.
    State variables and domain shapes:
      Tatm: (30,)
      Ts: (1,)
    The subprocess tree:
    top: <class 'climlab.model.column.RadiativeConvectiveModel'>
       convective adjustment: <class 'climlab.convection.convadj.ConvectiveAdjustment'>
       LW: <class 'climlab.radiation.radiation.Radiation'>
       SW: <class 'climlab.radiation.radiation.RadiationSW'>
       surface: <class 'climlab.surface.surface_radiation.SurfaceRadiation'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>

This model is exactly like our previous models, except for one additional subprocess called ``convective adjustment``.

We passed a parameter ``adj_lapse_rate`` (in K / km) that sets the neutrally stable lapse rate -- in this case, 6 K / km.

This number is chosed to very loosely represent the net effect of **moist convection**. We'll look at this in more detail later.

```python
# Set our tuned absorptivity value
rce.subprocess['LW'].absorptivity = eps
```

```python
#  Run out to equilibrium
rce.integrate_years(2.)
```

    Integrating for 730 steps, 730.4844 days, or 2.0 years.
    Total elapsed time is 1.99867375676 years.

```python
#  Check for energy balance
rce.diagnostics['ASR'] - rce.diagnostics['OLR']
```

    Field([  6.23502038e-06])

```python
#  Make a plot to compare observations, Radiative Equilibrium, and Radiative-Convective Equilibrium
plot_sounding([col, re, rce])
```

Introducing convective adjustment into the model cools the surface quite a bit (compared to Radiative Equilibrium, in green here) -- and warms the lower troposphere. It gives us a MUCH better fit to observations.

But of course we still have no stratosphere.

____________

## 8. Putting stratospheric ozone in the grey-gas model
____________

Our model has no equivalent of the stratosphere, where temperature increases with height. That's because our model has been completely transparent to shortwave radiation up until now.

We can load the observed ozone climatology from the input files for the CESM model:

```python
datapath = "http://ramadda.atmos.albany.edu:8080/repository/opendap/latest/Top/Users/Brian+Rose/CESM+runs/"
endstr = "/entry.das"

ozone = nc.Dataset( datapath + 'som_input/ozone_1.9x2.5_L26_2000clim_c091112.nc' + endstr )
```

```python
print ozone.variables['O3']
```

    <type 'netCDF4.Variable'>
    float32 O3(time, lev, lat, lon)
        units: mol/mol
        long_name: O3 concentration
        cell_method: time: mean
    unlimited dimensions: time
    current shape = (12, 26, 96, 144)
    filling off

```python
lat_O3 = ozone.variables['lat'][:]
lon_O3 = ozone.variables['lon'][:]
lev_O3 = ozone.variables['lev'][:]
```

The pressure levels in this dataset are:

```python
print lev_O3
```

    [   3.544638     7.3888135   13.967214    23.944625    37.23029     53.114605
       70.05915     85.439115   100.514695   118.250335   139.115395   163.66207
      192.539935   226.513265   266.481155   313.501265   368.81798    433.895225
      510.455255   600.5242     696.79629    787.70206    867.16076    929.648875
      970.55483    992.5561   ]

### Take the global average of the ozone climatology, and plot it as a function of pressure (or height)

```python
O3_zon = np.mean( ozone.variables['O3'][:],axis=(0,3) )
print O3_zon.shape
```

    (26, 96)

```python
O3_global = np.average( O3_zon, axis=1, weights=np.cos(np.deg2rad(lat_O3)))
print O3_global
```

    [  7.82792904e-06   8.64150570e-06   7.58940041e-06   5.24567122e-06
       3.17761578e-06   1.82320025e-06   9.80756909e-07   6.22870516e-07
       4.47620522e-07   3.34481172e-07   2.62570325e-07   2.07898125e-07
       1.57074552e-07   1.12425546e-07   8.06005076e-08   6.27826466e-08
       5.42990612e-08   4.99506108e-08   4.60075675e-08   4.22977777e-08
       3.80559086e-08   3.38768551e-08   3.12171622e-08   2.97807148e-08
       2.87980981e-08   2.75429919e-08]

```python
ax = plt.figure(figsize=(10,8)).add_subplot(111)
ax.plot( O3_global * 1.E6, -np.log(lev_O3/climlab.constants.ps) )
ax.set_xlabel('Ozone (ppm)', fontsize=16)
ax.set_ylabel('Pressure (hPa)', fontsize=16 )
ax.set_yticks( -np.log(yticks/1000.) )
ax.set_yticklabels( yticks )
ax.grid()
ax.set_title('Global, annual mean ozone concentration', fontsize = 24);
```

This shows that most of the ozone is indeed in the stratosphere, and peaks near the top of the stratosphere.

Now create a new column model object **on the same pressure levels as the ozone data**.  We are also going set an adjusted lapse rate of 6 K / km.

```python
oz_col = climlab.RadiativeConvectiveModel(lev = lev_O3, adj_lapse_rate=6)
print oz_col
```

    climlab Process of type <class 'climlab.model.column.RadiativeConvectiveModel'>.
    State variables and domain shapes:
      Tatm: (26,)
      Ts: (1,)
    The subprocess tree:
    top: <class 'climlab.model.column.RadiativeConvectiveModel'>
       convective adjustment: <class 'climlab.convection.convadj.ConvectiveAdjustment'>
       LW: <class 'climlab.radiation.radiation.Radiation'>
       SW: <class 'climlab.radiation.radiation.RadiationSW'>
       surface: <class 'climlab.surface.surface_radiation.SurfaceRadiation'>
       insolation: <class 'climlab.radiation.insolation.FixedInsolation'>

Now we will do something new: let the column absorb some shortwave radiation. We will assume that the shortwave absorptivity is proportional to the ozone concentration we plotted above.

First we have to deal with a little inconsistency:

```python
print lev_O3
print oz_col.lev
```

    [   3.544638     7.3888135   13.967214    23.944625    37.23029     53.114605
       70.05915     85.439115   100.514695   118.250335   139.115395   163.66207
      192.539935   226.513265   266.481155   313.501265   368.81798    433.895225
      510.455255   600.5242     696.79629    787.70206    867.16076    929.648875
      970.55483    992.5561   ]
    [ 992.5561     970.55483    929.648875   867.16076    787.70206    696.79629
      600.5242     510.455255   433.895225   368.81798    313.501265
      266.481155   226.513265   192.539935   163.66207    139.115395
      118.250335   100.514695    85.439115    70.05915     53.114605    37.23029
       23.944625    13.967214     7.3888135    3.544638 ]

The two arrays are in reverse order!

So we need to flip the ozone data before using it:

```python
O3_flipped = np.flipud(O3_global)
```

Now we need to weight the absorptivity by the pressure (mass) of each layer.

```python
# This number is an arbitrary parameter that scales how absorptive we are making the ozone
# in our grey gas model
ozonefactor = 75
dp = oz_col.Tatm.domain.lev.delta
epsSW = np.flipud(O3_global) * dp * ozonefactor
```

We want to use the field `epsSW` as the absorptivity for our SW radiation model.

Let's see what the absorptivity is current set to:

```python
print oz_col.subprocess['SW'].absorptivity
```

    [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.]

It defaults to zero.

Before changing this (putting in the ozone), let's take a look at the shortwave absorption in the column:

```python
oz_col.compute_diagnostics()
```

```python
oz_col.diagnostics['SW_absorbed_atm']
```

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

Let's now put in the ozone:

```python
oz_col.subprocess['SW'].absorptivity = epsSW
print oz_col.subprocess['SW'].absorptivity
```

    [  3.81013258e-05   6.79353164e-05   1.15468099e-04   1.66169128e-04
       2.16427967e-04   2.67120883e-04   2.95567938e-04   2.87482268e-04
       2.65307565e-04   2.45147963e-04   2.40936627e-04   2.62922886e-04
       3.11733077e-04   3.70212125e-04   4.16507314e-04   4.47141525e-04
       4.84170276e-04   5.50761578e-04   7.11369789e-04   1.18884324e-03
       2.24450947e-03   3.47591208e-03   4.57614181e-03   4.71182560e-03
       3.37750312e-03   3.20948559e-03]

Let's check how this changes the SW absorption:

```python
oz_col.compute_diagnostics()
oz_col.diagnostics['SW_absorbed_atm']
```

    array([ 0.01641658,  0.02927233,  0.04975715,  0.07161233,  0.09328389,
            0.11515129,  0.12743552,  0.12396838,  0.11442202,  0.1057411 ,
            0.10393805,  0.11343945,  0.13452308,  0.1597929 ,  0.17981756,
            0.19309136,  0.20913838,  0.23797741,  0.30750441,  0.51429087,
            0.97239294,  1.50916588,  1.99236556,  2.05685577,  1.47671292,
            1.40571625])

It is now non-zero, and largest near the top of the column (bottom of array) where the ozone concentration is highest.

Now it's time to run the model out to radiative-convective equilibrium

```python
oz_col.integrate_years(1.)
```

    Integrating for 365 steps, 365.2422 days, or 1.0 years.
    Total elapsed time is 1.01576433391 years.

```python
print oz_col.diagnostics['ASR'] - oz_col.diagnostics['OLR']
```

    [-0.00553705]

And let's now see what we got!

```python
#  Make a plot to compare observations, Radiative Equilibrium, Radiative-Convective Equilibrium, and RCE with ozone!
plot_sounding([col, re, rce, oz_col])
```

And we finally have something that looks looks like the tropopause, with temperature increasing above at approximately the correct rate.

There are still plenty of discrepancies between this model solution and the observations, including:

- Tropopause temperature is too warm, by about 15 degrees.
- Surface temperature is too cold

There are a number of parameters we might adjust if we wanted to improve the fit, including:

- Longwave absorptivity
- Surface albedo

Feel free to experiment! (That's what models are for, after all).

### The take home message

The dominant effect of stratospheric ozone is to vastly increase the radiative equilibrium temperature in the ozone layer. The temperature needs to be higher so that the longwave emission can balance the shortwave absorption.

Without ozone to absorb incoming solar radiation, the **temperature does not increase with height**.

This simple grey-gas model illustrates this principle very clearly.

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

____________
## 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), offered in Spring 2015.
____________

____________
## Version information
____________

```python
%install_ext http://raw.github.com/jrjohansson/version_information/master/version_information.py
%load_ext version_information
%version_information numpy, climlab
```

    Installed version_information.py. To use it, type:
      %load_ext version_information

Software	Version

Python	2.7.9 64bit [GCC 4.2.1 (Apple Inc. build 5577)]

IPython	3.1.0

OS	Darwin 14.3.0 x86_64 i386 64bit

numpy	1.9.2

climlab	0.2.11

Thu May 14 16:04:11 2015 EDT

Lectures/Lecture07 -- Grey radiation modeling with climlab.ipynb