```python
from IPython.core.display import HTML, Image
css_file = 'style.css'
HTML(open(css_file, 'r').read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Philosopher:400,700,400italic,700italic' rel='stylesheet' type='text/css'>

<style>

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

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Philosopher', sans-serif;
    font-weight: 400;
    font-size: 2.2em;
    line-height: 100%;
    color: rgb(0, 80, 120);
    margin-bottom: 0.1em;
    margin-top: 0.1em;
    display: block;
}
.text_cell_render h2 {
    font-family: 'Philosopher', serif;
    font-weight: 400;
    font-size: 1.9em;
    line-height: 100%;
    color: rgb(245,179,64);
    margin-bottom: 0.1em;
    margin-top: 0.1em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Philosopher', serif;
    margin-top:12px;
    margin-bottom: 3px;
    font-style: italic;
    color: rgb(94,127,192);
}

.text_cell_render h4 {
    font-family: 'Philosopher', serif;
}

.text_cell_render h5 {
    font-family: 'Alegreya Sans', sans-serif;
    font-weight: 300;
    font-size: 16pt;
    color: grey;
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.1em;
    display: block;
}

.text_cell_render h6 {
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 10pt;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
        font-family: "PT Mono";
        font-size: 100%;
}

</style>

```python
from sympy import init_printing, Matrix, symbols
from IPython.display import Image
from warnings import filterwarnings
```

```python
init_printing(use_latex = 'mathjax')
filterwarnings('ignore')
```

# Projection matrices and least squares

## Least squares

* Consider from the previous lecture the three data point in the plane
$$ ({t}_{i},{y}_{i}) =(1,1), (2,2),(3,2) $$
* From this we need to construct a straight line
* This could be helpful say in, statistics (remember, though in statistics we might have to get rid of statistical outliers)
* Nonetheless (view image above) we note that we have a straight line in slope-intercept form
$$ {y}={C}+{Dt} $$
* On the line at *t* values of 1, 2, and 3 we will have
$$ {y}_{1}={C}+{D}=1 \\ {y}_{2}={C}+{2D}=2 \\ {y}_{3}={C}+{3D}=2 $$
* The actual *y* values at these *t* values are 1, 2, and 2, though
* We are thus including an error of
$$ \delta{y} \\ { \left( { e }_{ 1 } \right)  }^{ 2 }={ \left[ \left( C+D \right) -1 \right]  }^{ 2 }\\ { \left( { e }_{ 2 } \right)  }^{ 2 }={ \left[ \left( C+2D \right) -2 \right]  }^{ 2 }\\ { \left( { e }_{ 3 } \right)  }^{ 2 }={ \left[ \left( C+3D \right) -2 \right]  }^{ 2 } $$
* Since some are positive and some are negative (actual values below or above the line), we simply determine the square (which will always be positive)
* Adding the (three in our example here) squares we have the sum total of the error (which is actuall just the sqautre of the distance between the line and actual *y* values)
* The line will be the best fit when this error sum is at a minimum (hence *least squares*)
* We can do this with calculus or with linear algebra
* For calculus we take the partial derivatives of both unknowns and set to zero
* For linear algebra we project orthogonally onto the columnspace (hence minimizing the error)
    * Note that the solution **b** does not exist in the columnspace (it is not a linear combination of the columns)

### Calculus method

* We'll create a function *f*(C,D) and successively take the partial derivatives of both variables and set it to zero
* We fill then have two equation with two unknowns to solve (which is easy enough to do manually or by simple linear algebra and row reduction)

```python
C, D = symbols('C D')
```

```python
e1_squared = ((C + D) - 1) ** 2
e2_squared = ((C + 2 * D) - 2) ** 2
e3_squared = ((C + 3 * D) - 2) ** 2
f = e1_squared + e2_squared + e3_squared
f
```

$$\left(C + D - 1\right)^{2} + \left(C + 2 D - 2\right)^{2} + \left(C + 3 D - 2\right)^{2}$$

```python
f.expand() # Expanding the expression
```

$$3 C^{2} + 12 C D - 10 C + 14 D^{2} - 22 D + 9$$

* Doing the partial derivatives will be
$$ f\left( C,D \right) =3{ C }^{ 2 }+12CD-10C+14{ D }^{ 2 }-22D+9\\ \frac { \partial f }{ \partial C } =6C+12D-10=0\\ \frac { \partial f }{ \partial D } =12C+28D-22=0 $$

```python
f.diff(C) # Taking the partial derivative with respect to C
```

$$6 C + 12 D - 10$$

```python
f.diff(D) # Taking the partial derivative with respect to D
```

$$12 C + 28 D - 22$$

* Setting both equal to zero (and creating a simple augmented matrix) we get
$$ 6C+12D-10=0\\ 12C+28D-22=0\\ \therefore \quad 6C+12D=10\\ \therefore \quad 12C+28D=22 $$

```python
A_augm = Matrix([[6, 12, 10], [12, 28, 22]])
A_augm
```

$$\left[\begin{matrix}6 & 12 & 10\\12 & 28 & 22\end{matrix}\right]$$

```python
A_augm.rref() # Doing a Gauss-Jordan elimination to reduced row echelon form
```

$$\begin{pmatrix}\left[\begin{matrix}1 & 0 & \frac{2}{3}\\0 & 1 & \frac{1}{2}\end{matrix}\right], & \begin{bmatrix}0, & 1\end{bmatrix}\end{pmatrix}$$

* We now have a solution
$$ {y}=\frac{2}{3} + \frac{1}{2}{t}$$

### Linear algebra

* We note that we can construct the following
$$ {C}+{1D}={1} \\ {C}+{2D}={2} \\ {C}+{3D}={2} \\ {C}\begin{bmatrix} 1 \\ 1\\ 1 \end{bmatrix}+{D}\begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix}=\begin{bmatrix} 1 \\ 2 \\ 2 \end{bmatrix} \\ A\underline { x } =\underline { b } \\ \begin{bmatrix} 1 & 1 \\ 1 & 2 \\ 1 & 3 \end{bmatrix}\begin{bmatrix} C \\ D \end{bmatrix}=\begin{bmatrix} 1 \\ 2 \\ 2 \end{bmatrix} $$
* **b** is not in the columnspace of A and we have to do orthogonal projection
$$ { A }^{ T }A\hat { x } ={ A }^{ T }\underline { b } \\ \hat { x } ={ \left( { A }^{ T }A \right)  }^{ -1 }{ A }^{ T }\underline { b }  $$

```python
A = Matrix([[1, 1], [1, 2], [1, 3]])
b = Matrix([1, 2, 2])
A, b # Showing the two matrices
```

$$\begin{pmatrix}\left[\begin{matrix}1 & 1\\1 & 2\\1 & 3\end{matrix}\right], & \left[\begin{matrix}1\\2\\2\end{matrix}\right]\end{pmatrix}$$

```python
x_hat = (A.transpose() * A).inv() * A.transpose() * b
x_hat
```

$$\left[\begin{matrix}\frac{2}{3}\\\frac{1}{2}\end{matrix}\right]$$

* Again, we get the same values for C and D

* Remember the following
$$ \underline{b} = \underline{p}+\underline{e} $$
* **p** and **e** are perpendicular
* Indeed **p** is in the columnspace of A and **e** is perpendicular to the columspace (or any vector in the columnspace)

## Example problem

### Example problem 1

* Find the quadratic (second order polynomial) equation through the origin, with the following data points: (1,1), (2,5) and (-1,-2)

#### Solution

* Let's just think about a quadratic equation in *y* and *t*
$$ {y}={c}_{1} +{C}{t}+{D}{t}^{2} $$
* Through the origin (0,0) means *y* = 0 and *t* = 0, thus we have
$$ {0}={c}_{1} +{C}{0}+{D}{0}^{2} \\ {c}_{1}=0 \\ {y}={C}{t}+{D}{t}^{2} $$

* This gives us three equation for our three data points
$$ C\left( 1 \right) +D{ \left( 1 \right)  }^{ 2 }=1\\ C\left( 2 \right) +D{ \left( 2 \right)  }^{ 2 }=5\\ C\left( -1 \right) +D{ \left( -1 \right)  }^{ 2 }=-2\\ C\begin{bmatrix} 1 \\ 2 \\ -1 \end{bmatrix}+D\begin{bmatrix} 1 \\ 4 \\ 1 \end{bmatrix}=\begin{bmatrix} 1 \\ 5 \\ -2 \end{bmatrix}\\ A=\begin{bmatrix} 1 & 1 \\ 2 & 4 \\ -1 & 1 \end{bmatrix}\\ \underline { x } =\begin{bmatrix} C \\ D \end{bmatrix}\\ \underline { b } =\begin{bmatrix} 1 \\ 5 \\ -2 \end{bmatrix} $$

* Clearly **b** is not in the columnspace of A and we have to project orthogonally onto the columnspace using
$$ \hat { x } ={ \left( { A }^{ T }A \right)  }^{ -1 }{ A }^{ T }\underline { b } $$

```python
A = Matrix([[1, 1], [2, 4], [-1, 1]])
b = Matrix([1, 5, -2])
x_hat = (A.transpose() * A).inv() * A.transpose() * b
x_hat
```

$$\left[\begin{matrix}\frac{41}{22}\\\frac{5}{22}\end{matrix}\right]$$

* Here's a simple plot of the equation

```python
import matplotlib.pyplot as plt # The graph plotting module
import numpy as np # The numerical mathematics module
%matplotlib inline
```

```python
x = np.linspace(-2, 3, 100) # Creating 100 x-values
y = (41 / 22) * x + (5 / 22) * x ** 2 # From the equation above
plt.figure(figsize = (8, 6)) # Creating a plot of the indicated size
plt.plot(x, y, 'b-') # Plot the equation above , in essence 100 little plots using small segmnets of blue lines
plt.plot(1, 1, 'ro') # Plot the point in a red dot
plt.plot(2, 5, 'ro')
plt.plot(-1, -2, 'ro')
plt.plot(0, 0, 'gs') # Plot the origin as a green square
plt.show(); # Create the plot
```

```python

```

Mathematics/Linear Algebra/0.0 MIT-18.06 - Jupyter/Lecture_16_Projection_matrices_and_least_squares.ipynb


# Basic Concepts
The bottom-up analysis of dynamic states of a network is based on network topology and kinetic theory describing the links in the network. In this chapter, we provide a primer for the basic concepts of dynamic analysis of network states. We also discuss basics of the kinetic theory that is needed to formulate and understand detailed dynamic models of biochemical reaction networks.

## Properties of Dynamic States
The three key dynamic properties outlined in the introduction - time constants, aggregate variables and transitions - are detailed in this section.

### Time scales
A fundamental quantity in dynamic analysis is the _time constant_. A time constant is a measure of time span over which significant changes occur in a state variable. It is thus a scaling factor for time and determines where in the time scale spectrum one needs to focus attention when dealing with a particular process or event of interest.

A general definition of a time constant is given by

$$\begin{equation} \tau = \frac{\Delta x}{|dx/dt|_{avg}} \tag{2.1} \end{equation}$$

where $\Delta{x}$ is a characteristic change in the state variable $x$ of interest and $|dx/dt|_{avg}$
is an estimate of the rate of change of the variable $x$. Notice the ratio between $\Delta{x}$ and the average derivative has units of time, and the time constant characterizes the time span over which these changes in $x$ occur, see Figure 2.1.

**Figure 2.1:** Illustration of the concept of a time constant, $\tau$, and its estimation as $\tau = \Delta x\ / |dx/dt|_{avg}.$

In a network, there are many time constants. In fact, there is a spectrum of time constants, $\tau_1,\ \tau_2, \dots \tau_r$ where $r$ is the rank of the Jacobian matrix defining the dynamic dimensionality of the dynamic response of the network. This spectrum of time constants typically spans many orders of magnitude. The consequences of a well-separated set of time constants is a key concern in the analysis of network dynamics.

### Forming aggregate variables through "pooling"
One important consequence of time scale hierarchy is the fact that we will have fast and slow events. If fast events are filtered out or ignored, one removes a dynamic degree of freedom from the dynamic description, thus reducing the dynamic dimension of a system. Removal of a dynamic dimension leads to "coarse-graining" of the dynamic description. Reduction in dynamic dimension results in the combination, or pooling, of variables into aggregate variables.

A simple example can be obtained from upper glycolysis. The first three reactions of this pathway are:

$$\begin{equation} \text{glucose}\ \underset{\stackrel{\frown}{ATP \ ADP}}{\stackrel{HK}{\longrightarrow}} \text{G6P} \underset{\text{fast}, \tau_f}{\stackrel{PGI}{\leftrightharpoons}} \text{F6P} \underset{\stackrel{\frown}{ATP \ ADP}}{\stackrel{PFK}{\longrightarrow}} \text{FDP} \tag{2.2} \end{equation}$$

This schema includes the second step in glycolysis where glucose-6-phosphate (G6P) is converted to fructose-6-phosphate (F6P) by the phosphogluco-isomerase (PGI). Isomerases are highly active enzymes and have rate constants that tend to be fast. In this case, PGI has a much faster response time than the response time of the flanking kinases in this pathway, hexokinase (HK) and phosphofructokinase (PFK). If one considers a time period that is much greater than $\tau_f$ (the time constant associated with PGI), this system is simplified to:

$$\begin{equation} \underset{\stackrel{\frown}{ATP \ ADP}}{\stackrel{HK}{\longrightarrow}} \ \underset{t \gg \tau_f}{\text{HP}} \ \underset{\stackrel{\frown}{ATP \ ADP}}{\stackrel{PFK}{\longrightarrow}} \tag{2.3} \end{equation}$$

where HP = (G6P+F6P) is the hexosephosphate pool. At a slow time scale (i.e, long compared to $\tau_f$), the isomerase reaction has effectively equilibrated, leading to the removal of its dynamics from the network. As a result, F6P and G6P become dynamically coupled and can be considered to be a single variable. HP is an example of an aggregate variable that results from pooling G6P and F6P into a single variable. Such aggregation of variables is a consequence of time-scale hierarchy in networks. Determining how to aggregate variables into meaningful quantities becomes an important consideration in the dynamic analysis of network states. Further examples of pooling variables are given in Section 2.3.

### Transitions
The dynamic analysis of a network comes down to examining its transient behavior as it moves from one state to another.

**Figure 2.2:** Illustration of a transition from one state to another. (a) A simple transition. (b) A more complex set of transitions.

One type of transition, or _transient response,_ is illustrated in Figure 2.2a, where a system is in a homeostatic state, labeled as state $\text{#1}$, and is perturbed at time zero. Over some time period, as a result of the perturbation, it transitions into another homeostatic state (state $\text{#2}$). We are interested in characteristics such as the time duration of this response, as well as looking at the dynamic states that the network exhibits during this transition. Complex types of transitions are shown in Figure 2.2b.

It should be noted that when complex kinetic models are studied, there are two ways to perturb a system and induce a transient response. One is to instantaneously change the initial condition of one of the state variables (typically a concentration), and the second is to change the state of an environmental variable that represents an input to the system. The latter perturbation is the one that is biologically meaningful, whereas the former may be of some mathematical interest.

### Visualizing dynamic states
There are several ways to graphically represent dynamic states:

* First, we can represent them on a map (Figure 2.3a). If we have a reaction or a compound map for a network of interest, we can simply draw it out on a computer screen and leave open spaces above the arrows and the concentrations into which we can write numerical values for these quantities. These quantities can then be displayed dynamically as the simulation proceeds, or by a graph showing the changes in the variable over time. This representation requires writing complex software to make such an interface.

* A second, and probably more common, way of viewing dynamic states is to simply graph the state variables, $x$, as a function of time (Figure 2.3b). Such graphs show how the variables move up and down, and on which time scales. Often, one uses a logarithmic scale for the y-axis, and that often delineates the different time constants on which a variable moves.

* A third way to represent dynamic solutions is to plot two state variables against one another in a two-dimensional plot (Figure 2.3c). This representation is known as a _phase portrait_. Plotting two variables against one another traces out a curve in this plane along which time is a parameter. At the beginning of the trajectory, time is zero, and at the end, time has gone to infinity. These phase portraits will be discussed in more detail in Chapter 3.

**Figure 2.3:** Graphical representation of dynamic states.

## Primer on Rate Laws
The reaction rates, $v_i$, are described mathematically using kinetic theory. In this section, we will discuss some of the fundamental concepts of kinetic theory that lead to their formation.

### Elementary reactions
The fundamental events in chemical reaction networks are elementary reactions. There are two types of elemental reactions:

$$\begin{align} &\text{linear}  &x \stackrel{v}{\rightarrow} \tag{2.4a} \\ &\text{bi-linear}  & x_1 + x_2 \stackrel{v}{\rightarrow} \tag{2.4b} \end{align}$$

A special case of a bi-linear reaction is when $x_1$ is the same as $x_2$ in which case the reaction is second order.

Elementary reactions represent the irreducible events of chemical transformations, analogous to a base pair being the irreducible unit of DNA sequence. Note that rates, $v$, and concentrations, $x$, are non-negative variables, that is;

$$\begin{equation} x \geq 0, \ v \geq 0 \tag{2.5} \end{equation}$$

### Mass action kinetics
The fundamental assumption underlying the mathematical description of reaction rates is that they are proportional to the collision frequency of molecules taking part in a reaction. Most commonly, reactions are bi-linear, where two different molecules collide to produce a chemical transformation. The probability of a collision is proportional to the concentration of a chemical species in a 3-dimensional unconstrained domain. This proportionality leads to the elementary reaction rates:

$$\begin{align} \text{linear} \ \ &v = kx \ &\text{where the units on}& \ k \ \text{are time}^{-1} \ \text{and} \tag{2.6a} \\ \text{bi-linear} \ \ &v = kx_1x_2 \ &\text{where the units on}& \ k \ \text{are time}^{-1}\text{conc}^{-1} \tag{2.6b} \end{align}$$

### Enzymes increase the probability of the 'right' collision
Not all collisions of molecules have the same probability of producing a chemical reaction. Collisions at certain angles are more likely to produce a reaction than others. As illustrated in Figure 2.4, molecules bound to the surface of an enzyme can be oriented to produce collisions at certain angles, thus accelerating the reaction rate. The numerical values of the rate constants are thus genetically determined as the structure of a protein is encoded in the sequence of the DNA. Sequence variation in the underlying gene in a population leads to differences amongst the individuals that make up the population. Principles of enzyme catalysis are further discussed in Section 5.1.

**Figure 2.4:** A schematic showing how the binding sites of two molecules on an enzyme bring them together to collide at an optimal angle to produce a reaction. Panel A: Two molecules can collide at random and various angles in free solution. Only a fraction of the collisions lead to a chemical reaction. Panel B: Two molecules bound to the surface of a reaction can only collide at a highly restricted angle, substantially enhancing the probability of a chemical reaction between the two compounds. Redrawn based on (Lowenstein, 2000).

### Generalized mass action kinetics
The reaction rates may not be proportional to the concentration in certain circumstances, and we may have what are called _power-law kinetics_. The mathematical form of the elementary rate laws are

$$\begin{align} v &= kx^a  \tag{2.7a} \\ v &= kx_1^ax_2^b  \tag{2.7b} \end{align}$$

where $a$ and $b$ can be greater or smaller than unity. In cases where a restricted geometry reduces the probability of collision relative to a geometrically-unrestricted case, the numerical values of $a$ and $b$ are less than unity, and vice versa.

### Combining elementary reactions
In the analysis of chemical kinetics, the elementary reactions are often combined into reaction mechanisms. Following are two such examples:

#### Reversible reactions:
If a chemical conversion is thermodynamically reversible, then the two opposite reactions can be combined as

$$\begin{equation} x_1 \underset{v_{-}}{\stackrel{v_+}{\rightleftharpoons}} x_2 \end{equation}$$

The net rate of the reaction can then be described by the difference between the forward and reverse reactions;

$$\begin{align} v_{net} &= v^+ - v^- = k^+x_1 - k^-x_2, \tag{2.8a} \\ &K_{eq} = x_2 / x_1 = k^+/k^- \tag{2.8b} \end{align}$$

where $K_{eq}$ is the equilibrium constant for the reaction. Note that $v_{net}$ can be positive or negative. Both $k^+$ and $k^-$ have units of reciprocal time. They are thus inverses of time constants. Similarly, a net reversible bi-linear reaction can be written as

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

The net rate of the reaction can then be described by

$$\begin{align} v_{net} &= v^+ - v^- = k^+x_1x_2 - k^-x_3, \\ &K_{eq} = x_3 / x_1x_2 = k^+/k^- \end{align}$$

where $K_{eq}$ is the equilibrium constant for the reaction. The units on the rate constant $(k^+)$ for a bi-linear reaction are concentration per time. Note that we can also write this equation as

$$\begin{equation} v_{net} = k^+x_1x_2 - k^-x_3 = k^+(x_1x_2 - x_3/K_{eq}) \end{equation}$$

that can be a convenient form as often the $K_{eq}$ is a known number with a thermodynamic basis, and thus only a numerical value for $k^+$ needs to be estimated.

#### Converting enzymatic reaction mechanisms into rate laws:
Often, more complex combinations of elementary reactions are analyzed. The classical irreversible Michaelis-Menten mechanism is comprised of three elementary reactions.

$$\begin{equation} S + E \underset{v_{-1} = k_{-1}x}{\stackrel{v_1 = k_1se}{\rightleftharpoons}} X \stackrel{v_2 = k_2x}{\longrightarrow} E + P \end{equation}$$

where a substrate, $S$, binds to an enzyme to form a complex, $X$, that can break down to generate the product, $P$. The concentrations of the corresponding chemical species is denoted with the same lower case letter; i.e., $e=[E]$, etc. This reaction mechanism has two conservation quantities associated with it: one on the enzyme $e_{tot} = e + x$ and one on the substrate $s_{tot} = s+x+p$.

A quasi-steady-state assumption (QSSA), $dx/dt=0$, is then applied to generate the classical rate law

$$\begin{equation} \frac{ds}{dt} = \frac{-v_ms}{K_m + s} \tag{2.9} \end{equation}$$

that describes the kinetics of this reaction mechanism. This expression is the best-known rate equation in enzyme kinetics. It has two parameters: the maximal reaction rate $v_m$, and the Michaelis-Menten constant $K_m = (k_{-1} + k_2)/k_1$. The use and applicability of kinetic assumptions to deriving rate laws for enzymatic reaction mechanisms is discussed in detail in Chapter 5.

It should be noted that the elimination of the elementary rates through the use of the simplifying kinetic assumptions _fundamentally changes_ the mathematical nature of the dynamic description from that of bi-linear equations to that of hyperbolic equations (i.e., Eq. 2.9) and, more generally, to ratios of polynomial functions.

### Pseudo-first order rate constants (PERCs)
The effects of temperature, pH, enzyme concentrations, and other factors that influence the kinetics can often be accounted for in a condition specific numerical value of a constant that looks like a regular elementary rate constant, as in Eq (2.4). The advantage of having such constants is that it simplifies the network dynamic analysis. The disadvantage is that dynamic descriptions based on PERCs are condition specific. This issue is discussed in Parts 3 and 4 of the book.

### The mass action ratio ($\Gamma$)
The equilibrium relationship among reactants and products of a chemical reaction are familiar to the reader. For example, the equilibrium relationship for the PGI reaction (Eq. (2.8)) is

$$\begin{equation} K_{eq} = \frac{[\text{F6P}]_{eq}}{[\text{G6P}]_{eq}} \tag{2.10} \end{equation}$$

This relationship is observed in a closed system after the reaction is allowed to proceed to equilibrium over a long time, $t \rightarrow \infty$, (which in practice has a meaning relative to the time constant of the reaction, $t \gg \tau_f$).

However, in a cell, as shown in Eq. (2.2), the PGI reactions operate in an "open" environment, i.e., G6P is being produced and F6P is being consumed. The reaction reaches a steady state in a cell that will have concentration values that are different from the equilibrium value. The _mass action ratio_ for open systems, defined to be analogous to the equilibrium constant, is

$$\begin{equation} \Gamma = \frac{[\text{F6P}]_{ss}}{[\text{G6P}]_{ss}} \tag{2.11} \end{equation}$$

The mass action ratio is denoted by $\Gamma$ in the literature.

### 'Distance' from equilibrium
The numerical value of the ratio $\Gamma / K_{eq}$ relative to unity can be used as a measure of how far a reaction is from equilibrium in a cell. Fast reversible reactions tend to be close to equilibrium in an open system. For instance, the net reaction rate for a reversible bi-linear reaction (Eq. (2.2)) can be written as:

$$\begin{equation} v_{net} = k^+x_1x_2 - k^-x_3 = k^+x_1x_2(1 - \Gamma/K_{eq}) \end{equation}$$

If the reaction is "fast" then $(k^+x_1x_2)$ is a "large" number and thus $(1 - \Gamma/K_{eq})$ tends to be a "small" number, since the net reaction rate is balanced relative to other reactions in the network.

### Recap
These basic considerations of reaction rates and enzyme kinetic rate laws are described in much more detail in other standard sources, e.g., (Segal, 1975). In this text, we are not so concerned about the details of the mathematical form of the rate laws, but rather with the order-of-magnitude of the rate constants and how they influence the properties of the dynamic response.

## More on Aggregate Variables
Pools, or aggregate variables, form as a result of well-separated time constants. Such pools can form in a hierarchical fashion. Aggregate variables can be physiologically significant, such as the total inventory of high-energy phosphate bonds, or the total inventory of particular types of redox equivalents. These important concepts are perhaps best illustrated through a simple example that should be considered a primer on a rather important and intricate subject matter. Formation of aggregate variables in complex models is seen throughout Parts III and IV of this text.

**Figure 2.5:** The chemical transformations involved in the distribution of high-energy phosphate bonds among adenosines.

### Distribution of high-energy phosphate among the adenylate phosphates
In Figure 2.5 we show the skeleton structure of the transfer of high-energy phosphate bonds among the adenylates. In this figure we denote the use of ATP by $v_1$ and the synthesis of ATP from ADP by $v_2$, $v_5$ and $v_{-5}$ denote the reaction rates of adenylate kinase that distributes the high energy phosphate bonds among ATP, ADP, and AMP, through the reaction

$$\begin{equation} 2 \text{ADP} \leftrightharpoons \text{ATP} + \text{AMP} \tag{2.12} \end{equation}$$

Finally, the synthesis of AMP and its degradation is denoted by $v_3$ and $v_4$, respectively. The dynamic mass balance equations that describe this schema are:

$$\begin{align} \frac{d \text{ATP}}{dt} &= -v_1 + v_2 + v_{5, net} \tag{2.13a} \\ \frac{d \text{ADP}}{dt} &= v_1 - v_2 - 2 v_{5, net} \tag{2.13b} \\ \frac{d \text{AMP}}{dt} &= v_3 - v_4 + v_{5, net} \tag{2.13c} \end{align} $$

The responsiveness of these reactions falls into three categories: $v_{5, net} (=v_5 - v_{-5})$ is a _fast_ reversible reaction, $v_1$ and $v_2$ have _intermediate_ time scales, and the kinetics of $v_3$ and $v_4$ are _slow_ and have large time constants associated with them. Based on this time scale decomposition, we can combine the three concentrations so that they lead to the elimination of the reactions of a particular response time category on the right hand side of (Eq. 2.13). These combinations are as follows:

* First, we can eliminate all but the slow reactions by forming the sum of the adenosine phosphates.

    $$\begin{equation} \frac{d}{dt}(\text{ATP} + \text{ADP} + \text{AMP}) = v_3 - v_4\ \text{(slow)} \tag{2.14} \end{equation}$$

    The only reaction rates that appear on the right hand side of the equation are $v_3$ and $v_4$, that are the slowest reactions in the system. Thus, the summation of ATP, ADP, and AMP is a pool or aggregate variable that is expected to exhibit the slowest dynamics in the system.

* The second pooled variable of interest is the summation of 2ATP and ADP that represents the total number of high energy phosphate bonds found in the system at any given point in time:

    $$\begin{equation} \frac{d}{dt}(2 \text{ATP} + \text{ADP}) = -v_1 + v_2\ \text{(intermediate)} \tag{2.15} \end{equation}$$

    This aggregate variable is only moved by the reaction rates of intermediate response times, those of $v_1$ and $v_2$.

* The third aggregate variable we can form is the sum of the energy carrying nucleotides which are

    $$\begin{equation} \frac{d}{dt}(\text{ATP} + \text{ADP}) = -v_{5, net}\ \text{(fast)} \tag{2.16} \end{equation}$$

    This summation will be the fastest aggregate variable in the system.

Notice that by combining the concentrations in certain ways, we define aggregate variables that may move on distinct time scales in the simple model system, and, in addition, we can interpret these variables in terms of their metabolic physiological significance. However, in general, time scale decomposition is more complex as the concentrations that influence the rate laws may move on many time scales and the arguments in the rate law functions must be pooled as well.

### Using ratios of aggregate variables to describe metabolic physiology
We can define an aggregate variable that represents the _capacity_ to carry high-energy phosphate bonds. That simply is the summation of $\text{ATP} + \text{ADP} + \text{AMP}.$ This number multiplied by 2 would be the total number of high energy phosphate bonds that can be stored in this system. The second variable that we can define here would be the _occupancy_ of that capacity, $\textit{2ATP + ADP}$, which is simply an enumeration of how much of that capacity is occupied by high-energy phosphate bonds. Notice that the occupancy variable has a conjugate pair, which would be the vacancy variable. The ratio of these two aggregate variables forms a charge

$$\begin{equation} \text{charge} = \frac{\text{occupancy}}{\text{capacity}} \tag{2.17} \end{equation}$$

called the _energy charge,_ given by

$$\begin{equation} \text{E.C} = \frac{2 \text{ATP} \ + \ \text{ADP}}{2(\text{ATP} \ + \ \text{ADP} \ + \ \text{AMP})} \tag{2.18} \end{equation}$$

which is a variable that varies between 0 and 1. This quantity is the _energy charge_ defined by Daniel Atkinson (Atkinson, 1968). In cells, the typical numerical range for this variable when measured is 0.80-0.90.

In a similar way, one can define other redox charges. For instance, the _catabolic redox charge_ on the NADH carrier can be defined as

$$\begin{equation} \text{C.R.C} = \frac{\text{NADH}}{\text{NADH} \ + \ \text{NAD}} \tag{2.19} \end{equation}$$

which simply is the fraction of the NAD pool that is in the reduced form of NADH. It typically has a low numerical value in cells, i.e., about 0.001-0.0025, and therefore this pool is typically discharged by passing the redox potential to the electron transfer system (ETS). The _anabolic redox charge_

$$\begin{equation} \text{A.R.C} = \frac{\text{NADPH}}{\text{NADPH} \ + \ \text{NADP}} \tag{2.20} \end{equation}$$

in contrast, tends to be in the range of 0.5 or higher, and thus this pool is charged and ready to drive biosynthetic reactions. Therefore, pooling variables together based on a time scale hierarchy and chemical characteristics can lead to aggregate variables that are physiologically meaningful.

In Chapter 8  we further explore these fundamental concepts of time scale hierarchy. They are then used in Parts III and IV in interpreting the dynamic states of realistic biological networks.

## Time Scale Decomposition
### Reduction in dimensionality
As illustrated by the examples given in the previous section, most biochemical reaction networks are characterized by many time constants. Typically, these time constants are of very different orders of magnitude. The hierarchy of time constants can be represented by the time axis, Figure 2.6. Fast transients are characterized by the processes at the extreme left and slow transients at the extreme right. The process time scale, i.e., the time scale of interest, can be represented by a _window of observation_ on this time axis. Typically, we have three principal ranges of time constants of interest if we want to focus on a limited set of events taking place in a network. We can thus decompose the system response in time. To characterize network dynamics completely we would have to study all the time constants.

**Figure 2.6:** Schematic illustration of network transients that overlap with the time span of observation. n, n + 1, ... represent the decadic order of time constants.

### Three principal time constants
One can readily conceptualize this by looking at a three-dimensional linear system where the first time constant represents the fast motion, the second represents the time scale of interest, and the third is a slow motion, see Figure 2.7. The general solution to a three-dimensional linear system is

$$\begin{align} \textbf{x}(t) &=\textbf{v}_1  \langle \textbf{u}_1, \ \textbf{x}_0 \rangle \ \text{exp}(\lambda_1 t) && \text{fast} \\ &+\textbf{v}_2 \langle \textbf{u}_2, \ \textbf{x}_0 \rangle \ \text{exp}(\lambda_2 t) && \text{intermediate} \\ &+\textbf{v}_3 \langle \textbf{u}_3, \ \textbf{x}_0 \rangle \ \text{exp}(\lambda_3 t) && \text{slow} \tag{2.21} \end{align}$$

where $\textbf{v}_i$ are the _eigenvectors,_ $\textbf{u}_i$ are the _eigenrows,_ and $\boldsymbol{\lambda}_i$ are the _eigenvalues_ of the Jacobian matrix. The eigenvalues are negative reciprocals of time constants.

The terms that have time constants faster than the observed window can be eliminated from the dynamic description as these terms are small. However, the mechanisms which have transients slower than the observed time exhibit high "inertia" and hardly move from their initial state and can be considered constants.

**Figure 2.7:** A schematic of a decay comprised of three dynamic modes with well-separated time constants.

#### Example: 3D motion simplifying to a 2D motion
Figure 2.8 illustrates a three-dimensional space where there is rapid motion into a slow two-dimensional subspace. The motion in the slow subspace is spanned by two "slow" eigenvectors, whereas the fast motion is in the direction of the "fast" eigenvector.

**Figure 2.8:** Fast motion into a two-dimensional subspace.

### Multiple time scales
In reality there are many more than three time scales in a realistic network. In metabolic systems there are typically many time scales and a hierarchical formation of pools, Figure 2.9. The formation of such hierarchies will be discussed in Parts III and IV of the text.

**Figure 2.9:** Multiple time scales in a metabolic network and the process of pool formation. This figure represents human folate metabolism. (a) A map of the folate network. (b) An illustration progressive pool formation. Beyond the first time scale pools form between CHF and CH2F; and 5MTHF, 10FTHF, SAM; and MET and SAH (these are abbreviations for the long, full names of these metabolites). DHF and THF form a pool beyond the second time scale. Beyond the third time scale CH2F/CHF join the 5MTHF/10FTHF/SAM pool. Beyond the fourth time scale HCY joins the MET/SAH pool. Ultimately, on time scales on the order of a minute and slower, interactions between the pools of folate carriers and methionine metabolites interact. Courtesy of Neema Jamshidi (Jamshidi, 2008a).

## Network Structure versus Dynamics
The stoichiometric matrix represents the topological structure of the network, and this structure has significant implications with respect to what dynamic states a network can take. Its null spaces give us information about pathways and pools. It also determines the structural features of the gradient matrix. Network topology can have a dominant effect on network dynamics.

### The null spaces of the stoichiometric matrix
Any matrix has a right and a left null space. The right null space, normally called just the null space, is defined by all vectors that give zero when post-multiplying that matrix:

$$\begin{equation} \textbf{Sv}=0 \tag{2.22} \end{equation}$$

The null space thus contains all the steady state flux solutions for the network. The null space can be spanned by a set of basis vectors that are pathway vectors (SB1).

The left null space is defined by all vectors that give zero when pre-multiplying that matrix:

$$\begin{equation} \textbf{lS}=0 \tag{2.23} \end{equation}$$

These vectors $\textbf{l}$ correspond to pools that are always conserved at all time scales. We will call them _time invariants_. Throughout the book we will look at these properties of the stoichiometric matrices that describe the networks being studied.

**Figure 2.10:** A schematic showing how the structure of $\textbf{S}$ and $\textbf{G}$ form matrices that have non-zero elements in the same location if one of these matrices is transposed. The columns of $\textbf{S}$ and the rows of $\textbf{G}$ have similar but not identical vectors in an n-dimensional space. Note that this similarity only holds once the two opposing elementary reactions have been combined into a net reaction.

### The structure of the gradient matrix
We will now examine some of the properties of $\textbf{G}$. If a compound $x_i$ participates in reaction $v_j$, then the entry $s_{i,j}$ is non-zero. Thus, a net reaction

$$\begin{equation} x_i + x_{i + 1} \stackrel{v_j}{\leftrightharpoons} x_{i + 2} \tag{2.24} \end{equation}$$

with a net reaction rate

$$\begin{equation} v_j = v_j^+ - v_j^- \tag{2.25} \end{equation}$$

generates three non-zero entries in $\textbf{S}$: $s_{i,j}$, $s_{i + 1,j}$, and $s_{i + 2,j}$. Since compounds $x_i$, $x_{i + 1}$, and $x_{i + 2}$ influence reaction $v_j$, they will also generate non-zero elements in $\textbf{G}$, see Figure 2.10. Thus, non-zero elements generated by the reactions are:

$$\begin{equation} g_{j, i} = \frac{\partial v_j}{\partial x_i}, \ g_{j, i + 1} = \frac{\partial v_j}{\partial x_{i + 1}}, \ \text{and} \  g_{j, i + 2} = \frac{\partial v_j}{\partial x_{i + 2}} \tag{2.26} \end{equation}$$

In general, every reaction in a network is a reversible reaction. Hence we have the the following relationships between the elements of $\textbf{S}$ and $\textbf{G}$:

$$\begin{align} \text{if} \ &s_{i, j} = 0 \ \text{then} \ g_{j, i} = 0 \\ \text{if} \ &s_{i, j} \ne 0 \ \text{then} \ g_{j, i} \ne 0 \\ \text{if} \ &s_{i, j} > 0 \ \text{then} \ g_{j, i} < 0 \\ \text{if} \ &s_{i, j} < 0 \ \text{then} \ g_{j, i} >0 \end{align}$$

Note that for the rare cases where a reaction is effectively irreversible, an element in $\textbf{G}$ can become very small, but in principle finite.

It can thus be seen that

$$\begin{equation} -\textbf{G}^T \ \tilde \ \ \textbf{S} \tag{2.27} \end{equation}$$

in the sense that both will have non-zero elements in the same location. These elements will have opposite signs.

### Stoichiometric autocatalysis
The fundamental structure of most catabolic pathways in a cell is such that a compound is imported into a cell and then some property stored on cofactors is transferred to the compound and the molecule is thus "charged" with this property. This charged form is then degraded into a waste product that is secreted from the cell. During that degradation process, the property that the molecule was charged with is re-extracted from the compound, often in larger quantities than was used in the initial charging of the compound. This pathway structure is the cellular equivalent of "it takes money to make money," and its basic network structure is in Figure 2.11.

**Figure 2.11:** The prototypic pathway structure for degradation of a carbon substrate.

This figure illustrates the import of a substrate, $S$, to a cell. It is charged with high-energy phosphate bonds to form an intermediate, $X$. $X$ is then subsequently degraded to a waste product, $W$, that is secreted. In the degradation process, ATP is recouped in a larger quantity than was used in the charging process. This means that there is a net production of ATP in the two steps, and that difference can be used to drive various load functions on metabolism.

The consequence of this schema is basically _stoichiometric autocatalysis_ that can lead to multiple steady states. The rate of formation of $\text{ATP}$ from this schema as balanced by the load parameters is illustrated in Figure 2.12. This figure shows that the $\text{ATP}$ generation is 0 if all the adenosine phosphates are in the form of $\text{ATP}$ because there is no $\text{ADP}$ to drive the conversion of X to W. The $\text{ATP}$ generation is also 0 if there is no $\text{ATP}$ available, because $S$ cannot be charged to form $X$. The curve in between $\text{ATP} = 0$ and $\text{ATP} = \text{ATP}_{max}$ will be positive. The $\text{ATP}$ load, or use rate, will be a curve that grows with $\text{ATP}$ concentration and is sketched here as a hyperbolic function. As shown, there are three intersections in this curve, with the upper stable steady-state being the physiological state of this system. This system can thus have multiple steady-states and this property is a consequence of the topological structure of this reaction network.

### Network structure
The three topics discussed in this section show that the stoichiometric matrix has a dominant effect on integrated network functions and sets constraints on the dynamic states that a network can achieve. The numerical values of the elements of the gradient matrix determine which of these states are chosen.

## Physico-Chemical Effects
Molecules have other physico-chemical properties besides the collision rates that are used in kinetic theory. They also have osmotic properties and are electrically charged. Both of these features influence dynamic descriptions of biochemical reaction networks.

### The constant volume assumption
Most systems that we identify in systems biology correspond to some biological entity. Such entities may be an organelle like the nucleus or the mitochondria, or it may be the whole cell, as illustrated in Figure 2.13.

A compound, $x_i$, internal to the system, has a mass balance on the total amount per cell. We denote this quantity with an $M_i$. $M_i$ is a product of the volume per cell, $V$, and the concentration of the compound, $x_i$, which is amount per volume

$$\begin{equation} M_i = V \ x_i \tag{2.28} \end{equation}$$

The time derivative of the amount per cell is given by:

$$\begin{equation} \frac{M_i}{dt} = \frac{d}{dt}(V \ x_i) = V \frac{d x_i}{dt} + x_i \frac{dV}{dt} \tag{2.29} \end{equation}$$

**Figure 2.13:** An illustration of a 'system' with a defined boundary, inputs and outputs, and an internal network of reactions. The $V$ volume of the system may change over time. $\Pi$ denotes osmotic pressure, see (Eq. 2.32).

The time change of the amount $M_i$ per cell is thus dependent on two dynamic variables. One is $dx_i/dt$ which is the time change in the concentration of $x_i$, and the second is $dV/dt$ which is the change in volume with time. The volume is typically taken to be time invariant and therefore the term $dV/dt$ is equal to 0 and therefore results in a system that is of _constant volume_. In this case

$$\begin{equation} \frac{d x_i}{dt} = \frac{1}{V}\frac{d M_i}{dt} \tag{2.30} \end{equation}$$

This constant volume assumption (recall Table 1.2) needs to be carefully scrutinized when one builds kinetic models since volumes of cellular compartments tend to fluctuate and such fluctuations can be very important. Very few kinetic models in the current literature account for volume variation because it is mathematically challenging and numerically difficult to deal with. A few kinetic models have appeared, however, that do take volume fluctuations into account (Joshi, 1989m and Klipp, 2005).

### Osmotic balance
Molecules come with osmotic pressure, electrical charge, and other properties, all of which impact the dynamic states of networks. For instance, in cells that do not have rigid walls, the osmotic pressure has to be balanced inside $(\Pi_{in})$ and outside $(\Pi_{out})$ of the cell (Figure 2.13), i.e.,

$$\begin{equation} \Pi_{in} = \Pi_{out} \tag{2.31} \end{equation}$$

At first approximation, osmotic pressure is proportional to the total solute concentration,

$$\begin{equation} \Pi = R T \sum_i x_i \tag{2.32} \end{equation}$$

although some compounds are more osmotically-active than others and have osmotic coefficients that are not unity. The consequences are that if a reaction takes one molecule and splits it into two, the reaction comes with an increase in osmotic pressure that will impact the total solute concentration allowable inside the cell, as it needs to be balanced relative to that outside the cell. Osmotic balance equations are algebraic equations that are often complicated and therefore are often conveniently ignored in the formulation of a kinetic model.

### Electroneutrality
Another constraint on dynamic network models is the accounting for electrical charge. Molecules tend to be charged positively or negatively. Elementary charges cannot be separated, and therefore the total number of positive and negative charges within a compartment must balance. Any import and export in and out of a compartment of a charged species has to be counterbalanced by the equivalent number of molecules of the opposite charge crossing the membrane. Typically, bilipid membranes are impermeable to cations, but permeable to anions. For instance, the deliberate displacement of sodium and potassium by the ATP-driven sodium potassium pump is typically balanced by chloride ions migrating in and out of a cell or a compartment leading to a state of electroneutrality both inside and outside the cell. The equations that describe electroneutrality are basically a summation of the charge, $z_i$, of a molecule multiplied by its concentration,

$$\begin{equation} \sum_i z_ix_i = 0 \tag{2.33} \end{equation}$$

and such terms are summed up over all the species in a compartment. That sum has to add up to 0 to maintain electroneutrality. Since that summation includes concentrations of species, it represents an algebraic equation that is a constraint on the allowable concentration states of a network.

## Summary

* Time constants are key quantities in dynamic analysis. Large biochemical reaction networks typically have a broad spectrum of time constants.

* Well-separated time constants lead to pooling of variables to form aggregates. Aggregate variables represent a coarse-grained (i.e., lower dimensional) view of network dynamics and can lead to physiologically meaningful variables.

* Elementary reactions and mass action kinetics are the irreducible events in dynamic descriptions of networks. Elementary reactions are often combined into reaction mechanisms from which rate laws are derived using simplifying assumptions.

* Network structure has an overarching effect on network dynamics. Certain physico-chemical effects can as well. Thus topological analysis is useful, and so is a careful examination of the assumptions (recall Table 1.2) that underlie the dynamic mass balances (Eq. (1.1)) for the system being modeled and simulated.

$\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.}}$


# Defining Custom Display Logic for Your Own Objects

## Overview

In Python, objects can declare their textual representation using the `__repr__` method.  IPython expands on this idea and allows objects to declare other, richer representations including:

* HTML
* JSON
* PNG
* JPEG
* SVG
* LaTeX

This Notebook shows how you can add custom display logic to your own classes, so that they can be displayed using these rich representations. There are two ways of accomplishing this:

1. Implementing special display methods such as `_repr_html_`.
2. Registering a display function for a particular type.

In this Notebook we show how both approaches work.

Before we get started, we will import the various display functions for displaying the different formats we will create.

```python
from IPython.display import display
from IPython.display import (
    display_html, display_jpeg, display_png,
    display_javascript, display_svg, display_latex
)
```

## Implementing special display methods

The main idea of the first approach is that you have to implement special display methods, one for each representation you want to use. Here is a list of the names of the special methods and the values they must return:

* `_repr_html_`: return raw HTML as a string
* `_repr_json_`: return raw JSON as a string
* `_repr_jpeg_`: return raw JPEG data
* `_repr_png_`: return raw PNG data
* `_repr_svg_`: return raw SVG data as a string
* `_repr_latex_`: return LaTeX commands in a string surrounded by "$".

### Model Citizen: pandas

A prominent example of a package that has IPython-aware rich representations of its objects is [pandas](http://pandas.pydata.org/).

A pandas DataFrame has a rich HTML table representation,
using `_repr_html_`.

```python
import io
import pandas
```

```python
%%writefile data.csv
Date,Open,High,Low,Close,Volume,Adj Close
2012-06-01,569.16,590.00,548.50,584.00,14077000,581.50
2012-05-01,584.90,596.76,522.18,577.73,18827900,575.26
2012-04-02,601.83,644.00,555.00,583.98,28759100,581.48
2012-03-01,548.17,621.45,516.22,599.55,26486000,596.99
2012-02-01,458.41,547.61,453.98,542.44,22001000,540.12
2012-01-03,409.40,458.24,409.00,456.48,12949100,454.53

```

```python
df = pandas.read_csv("data.csv")
pandas.set_option('display.notebook_repr_html', False)
df
```

rich HTML can be activated via `pandas.set_option`.

```python
pandas.set_option('display.notebook_repr_html', True)
df
```

```python
lines = df._repr_html_().splitlines()
print("\n".join(lines[:20]))
```

### Exercise

Write a simple `Circle` Python class.  Don't even worry about properties such as radius, position, colors, etc. To help you out use the following representations (remember to wrap them in Python strings):

For HTML:

    ○

For SVG:

    <svg width="100px" height="100px">
        <circle cx="50" cy="50" r="20" stroke="black" stroke-width="1" fill="white"/>
    </svg>

For LaTeX (wrap with `$` and use a raw Python string):

    \bigcirc

After you write the class, create an instance and then use `display_html`, `display_svg` and `display_latex` to display those representations.

Tips : you can slightly tweek the representation to know from which `_repr_*_` method it came from.
For example in my solution the svg representation is blue, and the HTML one show "`HTML`" between brackets.

### Solution

Here is my simple `MyCircle` class:

```python
# %load ../../exercises/IPython Kernel/soln/mycircle.py
class MyCircle(object):

    def __init__(self, center=(0.0,0.0), radius=1.0, color='blue'):
        self.center = center
        self.radius = radius
        self.color = color

    def _repr_html_(self):
        return "○ (<b>html</b>)"

    def _repr_svg_(self):
        return """<svg width="100px" height="100px">
           <circle cx="50" cy="50" r="20" stroke="black" stroke-width="1" fill="blue"/>
        </svg>"""

    def _repr_latex_(self):
        return r"$\bigcirc \LaTeX$"

    def _repr_javascript_(self):
        return "alert('I am a circle!');"

```

Now create an instance and use the display methods:

```python
c = MyCircle()
```

```python
display_html(c)
```

```python
display_svg(c)
```

```python
display_latex(c)
```

```python
display_javascript(c)
```

## Adding IPython display support to existing objects

When you are directly writing your own classes, you can adapt them for display in IPython by following the above example.  But in practice, we often need to work with existing code we can't modify.  We now illustrate how to add these kinds of extended display capabilities to existing objects. To continue with our example above, we will add a PNG representation to our `Circle` class using Matplotlib.

### Model citizen: sympy

[SymPy](http://sympy.org) is another model citizen that defines rich representations of its object.
Unlike pandas above, sympy registers display formatters via IPython's display formatter API, rather than declaring `_repr_mime_` methods.

```python
from sympy import Rational, pi, exp, I, symbols
x, y, z = symbols("x y z")
```

```python
r = Rational(3,2)*pi + exp(I*x) / (x**2 + y)
r
```

SymPy provides an `init_printing` function that sets up advanced $\LaTeX$
representations of its objects.

```python
from sympy.interactive.printing import init_printing
init_printing()
r
```

To add a display method to an existing class, we must use IPython's display formatter API.  Here we show all of the available formatters:

```python
ip = get_ipython()
for mime, formatter in ip.display_formatter.formatters.items():
    print('%24s : %s' % (mime, formatter.__class__.__name__))

```

Let's grab the PNG formatter:

```python
png_f = ip.display_formatter.formatters['image/png']
```

We will use the `for_type` method to register our display function.

```python
png_f.for_type?
```

As the docstring describes, we need to define a function the takes the object as a parameter and returns the raw PNG data.

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

```python
class AnotherCircle(object):
    def __init__(self, radius=1, center=(0,0), color='r'):
        self.radius = radius
        self.center = center
        self.color = color

    def __repr__(self):
        return "<%s Circle with r=%s at %s>" % (
                    self.color,
                    self.radius,
                    self.center,
                )

c = AnotherCircle()
c
```

```python
from IPython.core.pylabtools import print_figure

def png_circle(circle):
    """Render AnotherCircle to png data using matplotlib"""
    fig, ax = plt.subplots()
    patch = plt.Circle(circle.center,
                       radius=circle.radius,
                       fc=circle.color,
                       )
    ax.add_patch(patch)
    plt.axis('scaled')
    data = print_figure(fig, 'png')
    # We MUST close the figure, otherwise IPython's display machinery
    # will pick it up and send it as output, resulting in a double display
    plt.close(fig)
    return data
```

```python
c = AnotherCircle()
print(repr(png_circle(c)[:10]))
```

Now we register the display function for the type:

```python
png_f.for_type(AnotherCircle, png_circle)
```

Now all `Circle` instances have PNG representations!

```python
c2 = AnotherCircle(radius=2, center=(1,0), color='g')
c2
```

```python
display_png(c2)
```

## return the object

```python
# for demonstration purpose, I do the same with a circle that has no _repr_javascript method
class MyNoJSCircle(MyCircle):

    def _repr_javascript_(self):
        return

cNoJS = MyNoJSCircle()
```

Of course you can now still return the object, and this will use compute all the representations, store them in the notebook and show you the appropriate one.

```python
cNoJS
```

Or just use `display(object)` if you are in a middle of a loop

```python
for i in range(3):
    display(cNoJS)
```

Advantage of using `display()` versus `display_*()` is that all representation will be stored in the notebook document and notebook file, they are then availlable for other frontends or post-processing tool like `nbconvert`.

Let's compare `display()` vs `display_html()` for our circle in the Notebook Web-app and we'll see later the difference in nbconvert.

```python
print("I should see a nice html circle in web-app, but")
print("nothing if the format I'm viewing the notebook in")
print("does not support html")
display_html(cNoJS)
```

```python
print("Whatever the format I will see a representation")
print("of my circle")
display(cNoJS)
```

```python
print("Same if I return the object")
cNoJS
```

```python
print("But not if I print it")
print(cNoJS)
```

## Cleanup

```python
!rm -f data.csv
```

```python

```

001-Jupyter/001-Tutorials/003-IPython-in-Depth/examples/IPython Kernel/Old Custom Display Logic.ipynb


```R
options(warn=-1)
# load library
library(reshape2) # for melt and cast functions
library(ggplot2) # for plotting functions
library(tm) # text mining library
#install.packages("SnowballC")
library(SnowballC)
```

## Question 1

### 1. Derive expectation and maximation steps of hard-EM algorithm for document clustering

- N is the total number of documents, K is the number of clusters.
- {d1....dn} are the documents, with corresponding latent variables {z1...zn) where zn:=(zn1,....,znk) is the cluster assignment vector for the nth documents, znk = 1 if the document belongs to the cluster k and zero otherwise.
- Parameters: Phi k is the cluster proportion,and mu k is the word proportion. Where the sum of phi of all clusters equal to 1 and the sum of word proportion of all words in cluster k equal to 1.
\begin{equation}
\sum_{k=1}^{K} \varphi_{k}=1
\end{equation}

and

\begin{equation}
\sum_{w \in \mathcal{A}} \mu_{k, w}=1
\end{equation}

Then the probability of observed documents is given by:
\begin{equation}
\begin{aligned}
p\left(d_{1}, \ldots, d_{N}\right)=\prod_{n=1}^{N} p\left(d_{n}\right) &=\prod_{n=1}^{N} \sum_{k=1}^{K} p\left(z_{n, k}=1, d_{n}\right) \\
&=\prod_{n=1}^{N} \sum_{k=1}^{K}\left(\varphi_{k} \prod_{w \in \mathcal{A}} \mu_{k, w}^{c\left(w, d_{n}\right)}\right)
\end{aligned}
\end{equation}

Apply log to above, then the log-likelihood is:

\begin{equation}
\begin{aligned}
\ln p\left(d_{1}, \ldots, d_{N}\right)=\sum_{n=1}^{N} \ln p\left(d_{n}\right) &=\sum_{n=1}^{N} \ln \sum_{k=1}^{K} p\left(z_{n, k}=1, d_{n}\right) \\
&=\sum_{n=1}^{N} \ln \sum_{k=1}^{K}\left(\varphi_{k} \prod_{w \in \mathcal{A}} \mu_{k, w}^{c\left(w, d_{n}\right)}\right)
\end{aligned}
\end{equation}

To maximise the Likelihood of incomplete Data, we use EM algorithm.

First, as the parameters are unknown, we initialize the starting values of parameters θ. These values will be called θold, and the unknown parameters we want to estimate will be θnew.

\begin{equation}
\theta^{\text {old }}=\left(\boldsymbol{\varphi}^{\text {old }}, \boldsymbol{\mu}_{1}^{\text {old }}, \ldots, \boldsymbol{\mu}_{K}^{\text {old }}\right)
\end{equation}

Define Q function:

\begin{equation}
\begin{aligned}
Q\left(\boldsymbol{\theta}, \boldsymbol{\theta}^{\text {old }}\right) &:=\sum_{n=1}^{N} \sum_{k=1}^{K} p\left(z_{n, k}=1 \mid d_{n}, \boldsymbol{\theta}^{\text {old }}\right) \ln p\left(z_{n, k}=1, d_{n} \mid \boldsymbol{\theta}\right) \\
&=\sum_{n=1}^{N} \sum_{k=1}^{K} p\left(z_{n, k}=1 \mid d_{n}, \boldsymbol{\theta}^{\text {old }}\right)\left(\ln \varphi_{k}+\sum_{w \in \mathcal{A}} c\left(w, d_{n}\right) \ln \mu_{k, w}\right) \\
&=\sum_{n=1}^{N} \sum_{k=1}^{K} \gamma\left(z_{n, k}\right)\left(\ln \varphi_{k}+\sum_{w \in \mathcal{A}} c\left(w, d_{n}\right) \ln \mu_{k, w}\right)
\end{aligned}
\end{equation}

where

\begin{equation}
\gamma\left(z_{n, k}\right) = p\left(z_{n, k}=1 \mid d_{n}, \boldsymbol{\theta}^{\text {old }}\right)
\end{equation}

are the responsability factors

E step:
1. calculate γ(znk) based on estimated parameters
\begin{equation}
\gamma\left(z_{n k}\right):=p\left(z_{n k}=1 \mid \boldsymbol{d}_{n}, \boldsymbol{\theta}^{\text {old }}\right)
\end{equation}

2. for each document, find the cluster with the maximum probability.

\begin{equation}
Z^{*}=\operatorname{argmax}_{z} \gamma\left(z_{n, k}\right)=\operatorname{argmax}_{z} p\left(z_{n, k}=1 \mid d_{n}, \theta^{\text {old }}\right)
\end{equation}

M step:

For hard EM, there is no expectation on the latent variables, so :

\begin{equation}
\mathcal{Q}\left(\theta, \theta^{\text {old }}\right)=\sum_{n=1}^{N} \ln p\left(z_{n, k=Z^{*}}=1, d_{n} \mid \theta\right)
\end{equation}

Find:
\begin{equation}
\operatorname{argmax}_{\theta} \sum_{n=1}^{N}\left(\ln \varphi_{k=Z^{*}}+\sum_{w \in \mathcal{A}} c\left(w, d_{n}\right) \ln \mu_{k=Z^{*}, w}\right)
\end{equation}

1. Sub the z* calculated into the partial derivatives below and recalculate the estimations of the parametors, update the parameters.

\begin{equation}
\varphi_{k}=\frac{N_{k}}{N} \text { where } N_{k}:=\sum_{n=1}^{N} \gamma\left(z_{n, k}\right)
\end{equation}

and

\begin{equation}
\mu_{k, w}=\frac{\sum_{n=1}^{\prime} \gamma\left(z_{n, k}\right) c\left(w, d_{n}\right)}{\sum_{w^{\prime} \in \mathcal{A}} \sum_{n=1}^{N} \gamma\left(z_{n, k}\right) c\left(w^{\prime}, d_{n}\right)}
\end{equation}

Use \begin{equation}
\boldsymbol{\theta}^{\text {old }} \leftarrow \boldsymbol{\theta}^{\text {new }}
\end{equation} and repeat until converge

### 2. Implement the hard-EM and soft-EM

```R
# Initialize parameters (theta_old function)
theta <- function(size, K, seed = 123456){
    set.seed(seed)     # set seed
    phi.hat  <- matrix(1/K,nrow = K, ncol=1) # assume all clusters have the same size (we will update this later on)
    mu.hat <- matrix(runif(K*size),nrow = K, ncol = size)    # initiate Mu
    mu.hat <- prop.table(mu.hat, margin = 1)   # normalization to ensure that sum of each row is 1

    return (list("phi.hat" = phi.hat, "mu.hat" = mu.hat))
}

# Helper Function
# This function is needed to prevent numerical overflow/underflow when working with small numbers
logSum <- function(v) {
   m = max(v)
   return ( m + log(sum(exp(v-m))))
}
```

```R
# train objective function
train_obj <- function(theta_old, wf) {
  N <- dim(wf)[2] # number of documents
  K <- dim(theta_old$mu.hat)[1] # number of cluster

  nloglike = 0
  for (n in 1:N){
    lprob <- matrix(0,ncol = 1, nrow=K)
    for (k in 1:K){
      lprob[k,1] = sum(wf[,n] * log(theta_old$mu.hat[k,]))
    }
    nloglike <- nloglike - logSum(lprob + log(theta_old$phi))
  }

  return (nloglike)
}
```

```R
# EM function for document clustering(hard & soft)
EM.step <- function(wf, K = 4, max.epoch=10, soft = TRUE, seed){

    # Parameters Setting
    N <- ncol(wf) # number of documents
    W <- nrow(wf)   # number of words i.e. vocabulary size
    theta_old = theta(W, K, seed = seed) # initialize parameters
    gamma <- matrix(,nrow=N, ncol=K)  # empty posterior matrix

    # check initial values
    print(train_obj(theta_old,wf))
    # EM-step
    for(epoch in 1:max.epoch){

        # E step:
        for (n in 1:N){
            for (k in 1:K){
                ## calculate the posterior based on the estimated mu and rho in the "log space"
                gamma[n,k] <- log(theta_old$phi.hat[k]) +  sum(wf[,n] * log(theta_old$mu.hat[k,]))
            }
            # normalisation to sum to 1 in the log space
            logZ = logSum(gamma[n,])
            gamma[n,] = gamma[n,] - logZ
        }

        # converting back from the log space
        gamma <- exp(gamma)

        # for hard EM, we want the k with the highest probability to be 1
        if(soft == FALSE){
            # hard assignments:
            max.prob <- gamma==apply(gamma, 1, max) # for each point find the cluster with the maximum (estimated) probability
            gamma[max.prob] <- 1 # assign each point to the cluster with the highest probability
            gamma[!max.prob] <- 0 # remove points from clusters with lower probabilites
        }

        # M step:
        # we need this matrix (same shape as the ) here because when calculating mean,
        # it can result in zero which leads to log calculation result in NaN, to avoid this issue, add a small number to avoid 0
        eps = matrix(1e-10, nrow = W, ncol = K)
        for (k in 1:K){
            ## recalculate the estimations:
            theta_old$phi.hat[k] <- sum(gamma[,k])/N  # the cluster size
            theta_old$mu.hat[k,] <- ((wf%*%gamma[,k])+eps[,k])/sum((wf%*%gamma[,k])+eps[,k]) # new means (cluster cenroids)
            }
        # evaluate and compare likelihood
        print(train_obj(theta_old,wf))
    }

    # keep the final parameters and gamma (posterior matrix)
    return(list("theta"= theta_old,"posterior"=gamma))
}
```

### 3. run soft-EM and hard-EM on provided data with K = 4

```R
## read the file (each line of the text file is one document)
text <- readLines('./Task2A.txt')

## the terms before '\t' are the lables (the newsgroup names) and all the remaining text after '\t' are the actual documents
docs <- strsplit(text, '\t')
rm(text) # just free some memory!

# store the labels for evaluation
labels <- unlist(lapply(docs, function(x) x[1]))

# store the unlabeled texts
docs <- data.frame(unlist(lapply(docs, function(x) x[2])))
```

```R
# preprocessing
docs$doc_id <- rownames(docs)
colnames(docs) <- c("text","doc_id")

# create a corpus
docs <- DataframeSource(docs)
corp <- Corpus(docs)

# Preprocessing:
corp <- tm_map(corp, removeWords, stopwords("english")) # remove stop words
#(the most common word in a language that can be find in any document)
corp <- tm_map(corp, removePunctuation) # remove punctuation
corp <- tm_map(corp, stemDocument) # perform stemming (reducing inflected and derived words to their root form)
corp <- tm_map(corp, removeNumbers) # remove all numbers
corp <- tm_map(corp, stripWhitespace) # remove redundant spaces

# Create a matrix which its rows are the documents and colomns are the words.
# Each number in Document Term Matrix shows the frequency of a word (colomn header) in a particular document (row title)
dtm <- DocumentTermMatrix(corp)
# reduce the sparcity of out dtm
dtm <- removeSparseTerms(dtm, 0.90)

# store word frequency into a matrix
wf <- t(as.matrix(dtm))
```

```R
# run soft and hard EM
EM_soft <- EM.step(wf, K=4, max.epoch=15,soft = TRUE, seed = 123456)
EM_hard <- EM.step(wf, K=4, max.epoch=15,soft = FALSE, seed = 123456)
```

    [1] 459718.3
    [1] 425849.8
    [1] 422287.2
    [1] 420268.8
    [1] 419696.7
    [1] 419511.3
    [1] 419432.7
    [1] 419403
    [1] 419385.8
    [1] 419303.3
    [1] 419291.5
    [1] 419283
    [1] 419274.8
    [1] 419248.1
    [1] 419226.4
    [1] 419218.6
    [1] 459718.3
    [1] 425717.5
    [1] 422187.1
    [1] 420212.3
    [1] 419695.3
    [1] 419536.1
    [1] 419450
    [1] 419417.2
    [1] 419402
    [1] 419394.1
    [1] 419387.3
    [1] 419383.3
    [1] 419382.4
    [1] 419382.5
    [1] 419382.5
    [1] 419382.5

### 4. Perform PCA on clustering

```R
##--- Cluster Visualization -------------------------------------------------
cluster.viz <- function(counts, color.vector, title=' '){
    # PCA
    p.comp <- prcomp(counts, scale. = TRUE, center = TRUE)
    # visualize
    plot(p.comp$x, col=color.vector, pch=1,  main=title)
}
```

```R
# visualization settings
options(repr.plot.width=18, repr.plot.height=10)
par(mfrow=c(1,2))

# Get the color.vector(labels)
label.soft  <- apply(EM_soft$posterior, 1, which.max)
label.hard  <- apply(EM_hard$posterior, 1, which.max)

# normalize the count matrix for better visualization
counts <- scale(wf)
counts[is.nan(counts)] <- 0

# visualize the clusters estimated by soft and hard EM
cluster.viz(t(counts), label.soft, 'Estimated Clusters (Soft EM) - normalized')
cluster.viz(t(counts), label.hard, 'Estimated Clusters (Hard EM) - normalized')
cluster.viz(t(wf), label.soft, 'Estimated Clusters (Soft EM)')
cluster.viz(t(wf), label.hard, 'Estimated Clusters (Hard EM)')
# visualize the real clusters
cluster.viz(t(counts), factor(labels), 'Real Clusters - normalized')
cluster.viz(t(wf), factor(labels), 'Real Clusters')
```

Machine Learning study notes/Latent Variable Models and Neural Networks/src/31436285_assessment_2_q1.ipynb