(sec:KAM)=
# An Informal Introduction to  Ideas Related to KAM (Kolmogorov-Arnold-Moser) Theory

KAM theory has developed into a recognized  branch of dynamical systems theory that is concerned with the study of the persistence of quasiperiodic trajectories in Hamiltonian system subjected to perturbation (generally, a Hamiltonian perturbation). The origins of this mathematical theory lie in attempts to understand the convergence of perturbation series solutions representing quasiperiodic solutions of the three-body problem in celestial mechanics {cite}`moser2001stable,barrow1997poincare,dumas2014kam`. Significant progress was made in this area in the second half of the twentieth century, and its name represents an acronym formed by the names of the three founders: Andrei Kolmogorov, Vladimir Arnold and Jürgen Moser. The main question that KAM addresses is the following:

> What happens to the quasiperiodic trajectories of an integrable Hamiltonian system when the flow is modified with a perturbation that respects the symplectic structure of Hamilton's equations? Will the perturbation destroy these quasiperiodic solutions, or is it possible that some of them will survive and  only become slightly deformed?

The first answer to this question was provided by Kolmogorov in his famous plenary lecture {cite}`kolmogorov1954` at the International Congress of Mathematicians that took place in Amsterdam in 1954. Since the pioneering works of Poincaré on the three body problem, the intuition built into the scientific community was that typical systems in classical mechanics exhibit chaotic behaviour in some regions of their phase space. However, Kolmogorov's remarkable announcement produced a complete shock that turned this view upside down. In his talk he claimed that for near-integrable Hamiltonian systems, that is, integrable systems subjected to a perturbation, almost all quasiperiodic motion persists. As we will  explain in more detail shortly, quasiperiodic trajectories trace out tori in phase space. Indeed, they are often viewed synonomously with invariant tori. Moreover, it turns out that the quasiperiodic trajectories that lie on _**non-resonant tori**_ are more robust to survive the perturbation than those associated to  _**resonant tori**_. In fact, resonant tori are the first tori to disappear when the system is perturbed. Interestingly, among the non-resonant tori, KAM theory establishes a criterion to measure the "degree of non-resonance" that is related to their robustness with respect to the perturbation.

Despite the deep consequences and implications that the results presented by Kolmogorov would bring to the field of classical mechanics, he had only provided partial arguments for them and not complete mathematical proofs. Hence, the foundations of this new theory needed a rigorous scaffolding that arrived several years later, in the period 1962-1963, with the independent works of Arnold {cite}`arnold1962,arnold1963a,arnold1963b` and Moser {cite}`moser1962`.

The goal of this section is to provide the reader with a basic and brief overview of the mathematical ideas and concepts behind KAM theory, and how its development has contributed greatly to the recognition of this theory as an important achievement of the modern theory of dynamical systems. For the sake of clarity, we will  avoid many abstract and technical details of the theory.  Instead, we will focus on explaining the mathematics behind the terms we have highlighted in boldface in the text above, so that this can help us better understand the mathematical content of the questions introduced above in italics, and the answers to them provided by KAM theory. Most of the material discussed in this section is based on, and has been adapted from, the detailed and nice descriptions of KAM theory presented in the books {cite}`diacu1996,dumas2014kam,wiggins2003introduction`

For completeness, we begin our explanation of KAM theory by considering a Hamiltonian system with $N$ degrees-of-freedom (DoF) defined by a scalar function $H(\mathbf{q},\mathbf{p})$ known as the Hamiltonian, (see {eq}`eq:hamiltoneq` ). This function depends on $\mathbf{q} \in \mathbb{R}^N$, the configuration space coordinates that represent the DoF of the system, and $\mathbf{p} \in \mathbb{R}^N$, which corresponds to their canonically conjugate momenta. In this setup, the phase space where dynamics occurs is $2N$-dimensional, and if we label a point in phase space as $\mathbf{z} = (\mathbf{q},\mathbf{p}) \in \mathbb{R}^{2N}$, the evolution of the system's trajectories is governed by Hamilton's equations of motion:

```{math}
---
label:
---
\begin{equation}
\dot{\mathbf{z}} = \mathcal{J} \, \nabla_{\mathbf{z}} H \quad \Leftrightarrow \quad
\begin{cases}
\dot{q}_i = \dfrac{\partial H}{\partial p_i}, \\[.4cm]
\dot{p}_i = -\dfrac{\partial H}{\partial q_i},
\end{cases}
\quad i \in \lbrace 1, \ldots, N\rbrace
\end{equation}
```

where $\mathcal{J}$ is the symplectic  matrix (see {eq}`eq:symp_cond` )  and $\nabla_{\mathbf{z}}$ is the gradient operator acting on the Hamiltonian.

The value of the Hamiltonian corresponds to the total energy of the system, it is conserved along trajectories and therefore it is a constant of motion or a first integral of the system. Dynamics for a fixed energy is constrained to a $2N-1$-dimensional constant energy hypersurface embedded in the $2N$-dimensional phase space.

As stated in the introduction, KAM theory is concerned with perturbed integrable systems. In simple terms, a system is said to be integrable if one can find explicit formulas for its solutions, or at least, expressions for them that involve indicated integrals and elementary functions. As opposed to non-integrable systems, integrable systems admit closed form expressions for their trajectories, given initial conditions. This type of integrability is referred to as ''integration by quadratures'' and was first developed by Liouville {cite}`liouville1855note`.

Observe that a solution of a dynamical system is a trajectory, that is, a one-dimensional curve wandering around in the $2N$ high dimensional phase space. Therefore, in order to determine this trajectory one would need to be able to obtain $2N-1$ functionally independent constants of the motion (CoM), $F_{i}(\mathbf{q},\mathbf{p}) = C_i$ where $i \in \lbrace 1,\ldots,2N-1\rbrace$, so that the result of their intersection is the sought trajectory. This means that each CoM decreases the dimensionality of the accessible phase space for the system by one. Notice also that this approach of finding constants of the motion in order to determine the solutions of the system comes from the natural approach of integrating the $2N$ ordinary differential equations that define Hamilton's equations, an operation which would yield, if it can be carried out, $2N$ integration constants that are later obtained in the classical way from initial conditions.

However, due to the special symplectic structure of Hamiltonian systems, in order to establish the integrability of a system we only need to find $N$ CoM. This criterion is known as the Liouville-Mineur-Arnold Integrability Theorem {cite}`liouville1855note,mineur1935systemes,mineur1937systemes,arnol2013mathematical`, and plays a key role in the development of KAM theory. This result states the following:

 Liouville-Mineur-Arnold Integrability Theorem (LMAIT):
An $N$ DoF Hamiltonian system is integrable if and only if there exist $N$ constants of the motion $F_1,\ldots,F_N$ which are smooth analytic functions of the phase space variables, such that:

* they are functionally independent, which means that the vectors $\nabla F_1 , \ldots, \nabla F_N$ are linearly independent for almost all phase space points,
* they are in involution with each other, that is, $\lbrace F_i,F_j \rbrace = 0$ for each $i \neq j$.

Notice that the involution condition is equivalent to $\nabla F_i$ and $\nabla F_j$ being orthogonal in the symplectic sense

$$\lbrace F_i,F_j \rbrace = \left(\nabla F_i\right)^{T} \mathcal{J} \, \nabla F_j = 0.$$

This means geometrically that in the symplectic structure of Hamiltonian systems, the level hypersurfaces of the CoM in involution are orthogonal. The LMAIT criterion also ensures that if the intersection resulting from these CoM is a connected and compact manifold, it has the shape of an $N$-torus, denoted by $\mathbb{T}^{N}$. Such an object is the result of taking the cartesian product of $N$ copies of a circle, that is:

```{math}
\begin{equation*}
\mathbb{T}^{N} = \underbrace{S^1 \times \ldots \times S^1}_{\scriptsize{\mbox{$N$ times}}} = \left(\mathbb{R} / 2\pi \mathbb{Z}\right)^{N}
\end{equation*}
```

The LMAIT result goes further by ensuring that for the phase space regions where one can find these tori, there exists a canonical transformation to action-angle coordinates $(\mathbf{I},\boldsymbol{\theta})$ such that the Hamiltonian in these new variables only depends on the actions:

\begin{equation*}
K(\mathbf{I}) = K(I_1,\ldots,I_N)
\end{equation*}

and Hamilton's equations are given by:

```{math}
---
label:
---
\begin{equation}
\begin{cases}
\dot{\theta}_i = \dfrac{\partial K}{\partial I_i} = \omega_i(\mathbf{I}) \\[.3cm]
\dot{I}_i = -\dfrac{\partial K}{\partial \theta_i} = 0
\end{cases}
\; , \quad i \in \lbrace 1, \ldots,N \rbrace
\end{equation}
```

The action variables are $N$ independent constants of motion and they only depend on initial conditions:

```{math}
---
label: eq:actions
---
\begin{equation}
I_i(t) = I_i^{0} = \text{constant} \quad \Leftrightarrow \quad \mathbf{I}(t) = \mathbf{I}_0 = \text{constant}.
\label{eq:actions}
\end{equation}
```

The actions determine the energy of the system,
$$
K_0 = K(\mathbf{I}_0) = K\left(I_1^0,\ldots,I_N^0\right),
$$
and the differential equations for the angle variables yield:

$${\theta_i(t) = \omega_i(\mathbf{I}_0) \, t + \theta_i^0 \mod 2\pi \;\; , \quad i \in \lbrace 1, \ldots,N \rbrace}$$

where $\omega_i(\mathbf{I}_0)$ are the angular frequencies. We can write the angular solution in vector form:

```{math}
---
label: eq:angles
---
\begin{equation}
\boldsymbol{\theta}(t) = \boldsymbol{\omega}\left(\mathbf{I}_0\right) t + \boldsymbol{\theta}_0 \mod 2\pi
\label{eq:angles}
\end{equation}
```

where $\boldsymbol{\omega}\left(\mathbf{I}_0\right)$ is the frequency vector that characterizes the type of motion displayed by the trajectories of the integrable system. Consequently, we have completely solved the motion in the action-angle coordinate space, and since the change of variable from $(\mathbf{q},\mathbf{p})$ to $(\mathbf{I},\boldsymbol{\theta})$ is canonical (symplectic), it is invertible and we can express the solutions to the original Hamiltonian $H$ in the form $\mathbf{q} = \mathbf{q}(\mathbf{I},\boldsymbol{\theta})$ and $\mathbf{p} = \mathbf{p}(\mathbf{I},\boldsymbol{\theta})$.

Equations {eq}`eq:actions` and {eq}`eq:angles` tell us that the flow generated by an integrable Hamiltonian system is linear and takes place on invariant $N$-dimensional tori parametrized by $N$ angle variables. This behavior is known as quasiperiodic motion. Invariance means that a trajectory starting on one of these tori will never leave that torus. Given an energy level for the Hamiltonian system, this family of tori, which is parametrized by the action variables, foliates the energy manifold. Each member of the tori family can be labelled according to a particular value taken by the actions. In the case of $N = 2$ DoF, this foliation of the 3D energy hypersurface by 2D tori, similar to many concentric layers of an onion, can be easily visualized.  {numref}`fig:tori_foliat` shows a representation of this geometrical arrangement.

Next we introduce the notion of non-degeneracy of a Hamiltonian, which is a necessary requirement for many results described in the framework of KAM theory.

Kolmogorov non-degeneracy condition: Given an integrable Hamiltonian system in action-angle coordinates $K = K(\mathbf{I})$, we say that it is non-degenerate if the frequency map:

$${\mathbf{I} \;\; \mapsto \;\; \boldsymbol{\omega} = \boldsymbol{\omega}(\mathbf{I}) }$$

is a diffeomorphism. In particular, due to the inverse function theorem, a sufficient condition which implies that the Hamiltonian is non-degenerate is the following:

$${
\det \left(\dfrac{\partial \boldsymbol{\omega}}{\partial \mathbf{I}}\right) = \det \left( \text{Hess}_{K}\right) \neq 0
}$$

```{figure} figures/tori_foliation.png
---
name: fig:tori_foliat
---
Foliation of a 3D energy hypersurface by 2D tori for an integrable 2 DoF Hamiltonian system. Each torus in the family is labelled by the action variable. We have cut open the tori to better illustrate the layered structure.
```

At this point, it is important to define what we mean by a resonant and a non-resonant torus, because the informal statement of the main results of KAM theory that we gave at the beginning of this section relies heavily on this concept. Recall that KAM theory studies the conditions under which the tori of an integrable Hamiltonian persist  when the system is subjected to a small perturbation.

__Resonance:__ The frequency vector $\boldsymbol{\omega} \in \mathbb{R}^{N}$ is said to be resonant if there exists a vector of integers with at least one of its components not zero, that is, $\mathbf{k} \in \mathbb{Z}^{N} - \lbrace 0 \rbrace$, such that:
```{math}
---
label:
---
\begin{equation}
\mathbf{k} \cdot \boldsymbol{\omega} = \sum_{i = 1}^{N} k_i \, \omega_i = 0.
\end{equation}
```
Otherwise $\boldsymbol{\omega}$ is non-resonant. Tori characterized by a resonant (or non-resonant) frequency vector are known in the literature as resonant (or non-resonant) tori.

Consider the nearly-integrable Hamiltonian:

```{math}
---
label:
---
\begin{equation}
\mathcal{H}(\mathbf{I},\boldsymbol{\theta}) = h(\mathbf{I}) + \varepsilon  f(\mathbf{I},\boldsymbol{\theta})
\end{equation}
```

where $h$ is the integrable Hamiltonian, $f$ is the perturbation and $\varepsilon \geq 0$ is the magnitude of the perturbation. KAM theory establishes that resonant tori are very fragile since they are the first ones to be destroyed when the perturbation is switched on, while most of the non-resonant tori (in the measure-theoretic sense) are robust and survive the perturbation, but become slightly deformed by it. The key ingredient which determines the ``degree of robustness" that allows us to quantify if a torus will survive or not is  the frequency vector associated to each of the tori in the integrable system. We give here the basic definitions of these concepts and, for  a more detailed analysis, the reader can refer to {cite}`wiggins2003introduction` and references therein.

__Add example of 2 DOF harmonic oscillators.__

In order to get a better idea and develop some geometrical intuition on the relationship between the definition of resonance and the corresponding dynamical behavior of trajectories on tori, we go back to the simple case provided by an integrable Hamiltonian system with $N = 2$ DoF. In this context, a resonant torus is determined by a frequency vector $\boldsymbol{\omega} = (\omega_1,\omega_2)$ that satisfies:

$${
k_1 \, \omega_1 + k_2 \, \omega_2 = 0 \;,\quad k_1 , k_2 \in \mathbb{Z}-\lbrace 0 \rbrace \quad \Leftrightarrow \quad \dfrac{\omega_2}{\omega_1} = -\dfrac{k_1}{k_2} \in \mathbb{Q}
}$$

Motion on resonant tori is periodic, and hence trajectories are closed. For example, if we consider motion on a torus with frequencies $\omega_1 = 2$ and $\omega_2 = 3$, a trajectory lying on this torus will wind around it making $3$ complete revolutions through the hole on the torus and $2$ full revolutions around the hole before it closes onto itself. This motion is illustrated in {numref}`fig:quasip_mot` A).

```{figure} figures/periodicFlow_w1_2_w2_3.png
---
name:
----
A) Periodic motion on a resonant torus with frequency ratio $\omega_2 / \omega_1 = 3/2$.
```

```{figure} figures/quasiperFlow_w1_1_w2_sqrt2.png
---
name: fig:quasip_mot
----
B) Quasiperiodic motion on a non-resonant torus with frequency ratio $\omega_2 / \omega_1 = \sqrt{2}$. Notice how the quasiperiodic trajectory densely and uniformly fills the surface of the non-resonant torus along its evolution. This type of motion is ergodic on the torus.
```

Interestingly, the resonant and non-resonant tori of the family that fill out the energy manifold of an integrable Hamiltonian system are intermingled in a similar way as the rational and irrational numbers are distributed among the reals. Moreover, it can be shown that almost all tori are non-resonant. This is also the case for rational and irrational numbers, since rationals are dense in the real numbers, but have zero measure. This means that if we select a real number at random, we will almost always pick an irrational number.

We can illustrate the results discussed by KAM theory by considering tori in the integrable system defined by the Chirikov standard map {cite}`chirikov1979,meiss2008` their persistence under perturbation. The Chirikov standard map is a two dimensional symplectic map that  displays many features of the Poincaré return maps for typical 2 DoF autonomous Hamiltonian systems. This discrete dynamical system is defined by a two-dimensional area-preserving map which depends on a parameter $k$ that determines the strength of the perturbation that is applied to the underlying integrable system. The solutions for the unperturbed system ($k = 0$) are one-dimensional tori, that is, circles, and are represented in {numref}`fig:std_map` A). Notice that these tori appear as lines because the left and right boundaries of the domain are identified, and so also, the top and bottom edges of the square. This is the manifestation of the toroidal geometry of the phase space of the standard map. If we increase the perturbation strength to $k = 0.3$ we observe in panel B) that most of the tori have survived the perturbation, but in a slightly distorted shape. Moreover, we notice also that some of the tori of the originally unperturbed system break (the resonant ones), and in the process they have generated smaller tori, and also hyperbolic points that possess stable and unstable manifolds. Increasing the perturbation further to $k = 0.8$, one can see in panel C) that non-resonant tori continue to break up into smaller tori, giving rise to structures known as islands of regularity, and that these subregions of the phase space are surrounded by a larger sea of random points that indicates chaotic motion.
Panels  D), E) and F) of {numref}`fig:std_map` show how with increasing $k$ all tori eventually break up.
This is in part one of the main messages that can be drawn from KAM theory; the coexistence of chaotic and regular motion in the phase space of Hamiltonian systems {cite}`markus1974generic`

```{figure} figures/std_map_k_0.png
---
name:
----
Phase space of the Chirikov's Standard Map as revealed by means of a Poincaré map for different values of the perturbation parameter: A) $k = 0$
```

```{figure} figures/std_map_k_03.png
---
name:
----
Phase space of the Chirikov's Standard Map as revealed by means of a Poincaré map for different values of the perturbation parameter: B) $k = 0.3$
```

```{figure} figures/std_map_k_08.png
---
name:
----
Phase space of the Chirikov's Standard Map as revealed by means of a Poincaré map for different values of the perturbation parameter: C) $k = 0.8$
```

```{figure} figures/std_map_k_12.png
---
name:
----
Phase space of the Chirikov's Standard Map as revealed by means of a Poincaré map for different values of the perturbation parameter: D) $k = 1.2$
```

```{figure} figures/std_map_k_24.png
---
name:
----
Phase space of the Chirikov's Standard Map as revealed by means of a Poincaré map for different values of the perturbation parameter: E) $k = 2.4$
```

```{figure} figures/std_map_k_150.png
---
name: fig:std_map
----
Phase space of the Chirikov's Standard Map as revealed by means of a Poincaré map for different values of the perturbation parameter: F) $k = 15$
```

\label{fig:std_map}
\caption{Phase space of the Chirikov's Standard Map as revealed by means of a Poincaré map for different values of the perturbation parameter: A) $k = 0$; B) $k = 0.3$; C) $k = 0.8$; D) $k = 1.2$; E) $k = 2.4$. F) $k = 15$.}
# References
```{bibliography} bibliography/chapter2.bib
```


# Big Assignment 3

## Background

The data for this exercise were used in Ebonya Washington's paper: "Female Socialization: How Daughters Affect Their Legislator Fathers' Voting on Women's Issues."  published in the American Economic Review in 2008. The paper asks whether having daughters influences the voting behavior of members of the US Congress. The hypothesis is that having (more) daughters makes legislators more likely to vote liberally on issues concerning women.

For this exercise, we will focus on votes that took place in the 108th Congress, which held session in 2003/04. As a measure of a liberal voting record, we use scores assigned by the American Association of University Women (AAUW), a liberal group that concerns itself with issues of interest to women. For the 108th Congress, the AAUW selected 9 pieces of legislation in the areas of education, equality and reproductive rights. The AAUW then assigned a score to each member of Congress. The scores range from 0 to 100 and measure the percentage of times the legislator voted in favor of the position held by the AAUW.

 The dataset `legislators.dta` contains the following characteristics for 320 members of the 108th Congress:

 * $ngirls$ number of daughters
 * $totchi$ number of children
 * $age$ Age
 * $female$ indicator for being female
 * $repub$ indicator for being a Republican
 *  $moredef$ proportion of people in the legislator's district who are in favor of "more spending on
defense"
 *  $aauw$ AAUW score

### (a) Estimate and report results for the following regression models:

Load in the data set `legislators.dta`. Remember, you will first need to call the `haven` package to do so.

Generate a variable `age2` $=\text{age}^2$

Generate an interaction variable `repubage` $= \text{repub}*\text{age}$

Generate an interaction variable `repubage2` $=\text{repub}*\text{age2}$

Estimate the following three regression models:

\begin{align}
aauw&=\beta_0 +\beta_1female+\beta_2repub+\beta_3age+u  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (1) \\
aauw&=\beta_0 +\beta_1female+\beta_2repub+\beta_3age+\beta_4 age2+u ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~(2)  \\
aauw&=\beta_0 + \beta_1female+\beta_2repub+\beta_3age+\beta_4 age2+\beta_5 repubage+\beta_6 repubage2+u\ \ \ (3)
\end{align}

```R
# Add Code for part (a) here.
```

### (b) Suggest which model is the best fit to the data

Add your written answer for part (b) here.

### (c) Interpret the effect of `age` on `aauw` in each model

Add your written answer for part (c) here.

### (d) Test whether there is an effect of age on aauw scores using the second model. Be sure to describe carefully the null and alternative hypothesis.

Step 1: Estimate the restricted model.

Step 2: Calculate the sum of squared residuals from the restricted and unrestricted models.

Step 3: Apply the F-stat formula.

Alternative (easier):

Step 2 (alternative): Find the $R^2$ in the summary of the restricted and unrestricted models.

Step 3 (alternative): Apply the $R^2$ version of the F-stat formula.

```R
#Add code for part d here.
```

### (e) Using the third model, predict the aauw score for a female republican who is 65 years old, and suggest a $95\%$ CI for that predicted value.

Hint: See part 2-A of section notes 7.

Add your written answer for part (e) here.

### (f) Suppose you learn that a 65 year-old female republican is about to be elected for the first time. Generate a $95\%$ CI for her voting score.

Hint: See part 2-B of section notes 7.

Add your written answer for part (f) here.

### (g) Interpret the coeffcient $\beta_2$ from model (3).

Add your written answer for part (g) here.

### (h) Suppose you think republicans and non-republicans may have different gender patterns in voting. That is, republican men may vote differently than republican women, who may vote differently thandemocratic women who may vote differently than democratic men. Write down an estimation equaiton to test whether republican women, democratic women, and democratic men vote differently than republican men.

Add your written answer to part (h) here.

### (i) Implement your test. Interpret each coeffcient.

```R
#Add your code for part i here
```

### (j) Adapt your regression to test whether democratic women vote differently than republican women. Please report your results (write out the estimating equations).

```R
#Add your code for part j here
```

Add your written answer for part (j) here.

## Downloading your Notebook

Download a PDF copy of your notebook by using __File > Download as > PDF via Chrome (.pdf)__. It does not matter what browser you are using, you can use this command to download a copy of your notebook while on Chrome/Firefox/Opera/Edge/Safari/heck probably even Internet Explorer or Netscape Navigator.

If you have a problem downloading your notebook or combining PDFs, or if you are using RStudio on your own computer and are unsure what sort of output I want, __please email me__.

Fall-19/Problem Set 3/Assignment 3 R.ipynb


# For loop

- To iterate over the List.

- for **int** in range():

- range(end no)
- range(start no.,end no.)
- range(start no.,end no.,step)

```python
d=[1,2,3,4,5]
```

```python
d.append(107)
d
```

    [1, 2, 3, 4, 5, 107]

```python
L = []
for i in range(10):
    L.append(i)
L
```

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

```python
a = []
for k in range(2,10):
    a.append(k)
a
```

    [2, 3, 4, 5, 6, 7, 8, 9]

```python
b = []
for i in range(1,21):
    b.append(i)
b
```

    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

```python
sum(b)
```

    210

```python
a+b;

```

```python
sum(a+b)
```

    254

```python
D = []
for i in range(1,5):
    for j in range(1,5):
        D.append(i+2*j)
        print(D)
```

    [3]
    [3, 5]
    [3, 5, 7]
    [3, 5, 7, 9]
    [3, 5, 7, 9, 4]
    [3, 5, 7, 9, 4, 6]
    [3, 5, 7, 9, 4, 6, 8]
    [3, 5, 7, 9, 4, 6, 8, 10]
    [3, 5, 7, 9, 4, 6, 8, 10, 5]
    [3, 5, 7, 9, 4, 6, 8, 10, 5, 7]
    [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9]
    [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11]
    [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11, 6]
    [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11, 6, 8]
    [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11, 6, 8, 10]
    [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11, 6, 8, 10, 12]

```python
D = []
for i in range(1,5):
    print(D)
    for j in range(1,5):
        D.append(i+2*j)
```

    []
    [3, 5, 7, 9]
    [3, 5, 7, 9, 4, 6, 8, 10]
    [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11]

```python
D = []
for i in range(1,5):
    for j in range(1,5):
        D.append(i+2*j)
D
```

    [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11, 6, 8, 10, 12]

```python
p = [] # empty list l
for i in range(1,5):
    for j in range(1,5):
        p.append(i+2*j)
        print('í=',i,'j=',j,'p=',p)
```

    í= 1 j= 1 p= [3]
    í= 1 j= 2 p= [3, 5]
    í= 1 j= 3 p= [3, 5, 7]
    í= 1 j= 4 p= [3, 5, 7, 9]
    í= 2 j= 1 p= [3, 5, 7, 9, 4]
    í= 2 j= 2 p= [3, 5, 7, 9, 4, 6]
    í= 2 j= 3 p= [3, 5, 7, 9, 4, 6, 8]
    í= 2 j= 4 p= [3, 5, 7, 9, 4, 6, 8, 10]
    í= 3 j= 1 p= [3, 5, 7, 9, 4, 6, 8, 10, 5]
    í= 3 j= 2 p= [3, 5, 7, 9, 4, 6, 8, 10, 5, 7]
    í= 3 j= 3 p= [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9]
    í= 3 j= 4 p= [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11]
    í= 4 j= 1 p= [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11, 6]
    í= 4 j= 2 p= [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11, 6, 8]
    í= 4 j= 3 p= [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11, 6, 8, 10]
    í= 4 j= 4 p= [3, 5, 7, 9, 4, 6, 8, 10, 5, 7, 9, 11, 6, 8, 10, 12]

```python
A = [10*k for k in range(10)]
print(A)
```

    [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]

```python
AA = [[10*x+y for x in range(4)] for y in range(3)]
print(AA)
```

    [[0, 10, 20, 30], [1, 11, 21, 31], [2, 12, 22, 32]]

```python
summ = 0
for i in range(1,11):
    summ = summ+i
    print(summ)
```

    1
    3
    6
    10
    15
    21
    28
    36
    45
    55

```python
product = 1
for i in range(1,11):
    product = product*i
    print(product)
```

    1
    2
    6
    24
    120
    720
    5040
    40320
    362880
    3628800

### Projectile
- Range
\begin{equation}
R=\frac{u^2\sin2\theta}{2g}
\end{equation}
- Max.Height
\begin{equation}
H=\frac{u^2\sin^2\theta}{2g}
\end{equation}

- using list

```python
import numpy as np
u=1000
g=9.8
angle=[]
Range=[]
PI=np.pi
for i in range(0,90):
    angle.append(i)
    Range.append(u**2*np.sin(2*PI*i/180)/(2*g))
```

- import matplotlib.pyplot as plt
- %matplotlib inline
- plt.plot(x,y)

```python
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(angle,Range)
plt.xlabel('Angle')
plt.ylabel('Range')
plt.title('Projectile')
```

- using dictionary

```python
angle = np.arange(1,90)
M = {"Range": [u**2*np.sin(2*PI*x/180)/(2*g) for x in angle],\
     "Max.height": [u**2*np.sin(PI*x/180)**2/(2*g) for x in angle]}
```

```python
import pandas as pd
DF = pd.DataFrame(M)
DF
```

      Range
      Max.height

      0
      1780.586566
      15.540127

      1
      3559.003762
      62.141575

      2
      5333.084861
      139.747567

      3
      7100.668416
      248.263553

      4
      8859.600901
      387.557321

      ...
      ...
      ...

      84
      8859.600901
      50632.850842

      85
      7100.668416
      50772.144611

      86
      5333.084861
      50880.660596

      87
      3559.003762
      50958.266588

      88
      1780.586566
      51004.868036

89 rows × 2 columns

```python
DF.plot()
plt.xlabel('Angle(degree)')
plt.ylabel('Distance(m)')
plt.title('Projectile')
```

```python
import numpy as np
u=1000
g=9.8
angle=[]
range=[]
PI=np.pi
for i in range(0,90):
    angle.append(i)
    Range.append(u**2*np.sin(2*PI*i/180)/(2*g))

```

```python
import matplotlib.pyplot as plt
%matplotlib. inline
plt.plot(angle,range)
plt.xlabel("Angle")
plt.ylabel("Range")
plt.title("projecticle")
```

    UsageError: Line magic function `%matplotlib.` not found.

```python

```


# Preeliminares y Redes Bayesianas

> Con el repaso de probabilidad que vimos la clase pasada estamos listos para comenzar nuestro camino de aprendizaje de los modelos gráficos probabilísticos. Esta clase revisaremos un par de conceptos matemáticos preeliminares, y finalmente veremos los fundamentos de uno de los dos modelos probabilísticos que estudiaremos: las **Redes Bayesianas**.

> **Objetivos:**
> - Entender el concepto de factor y las operaciones que podemos desarrollar con estos elementos.
> - Aprender qué es una red Bayesiana y cómo se factorizan distribuciones sobre ellas.
> - Entender el flujo de probabilidad que codifica una red Bayesiana y las independencias que implica.
> - Estudiar el modelo Naïve Bayes, y sus implicaciones de independencia.

> **Referencias:**
>
> - Probabilistic Graphical Models: Principles and Techniques, By Daphne Koller and Nir Friedman. Cap. 3.
> - Probabilistic Graphical Models Specialization, offered through Coursera. Prof. Daphne Koller.
> - Mastering Probabilistic Graphical Models Using Python, By Ankur Ankan and Abinash Panda. Cap. 1.

 Imagen recuperada de: https://upload.wikimedia.org/wikipedia/commons/1/18/Bayes%27_Theorem_MMB_01.jpg.

___

# 0. Recorderis

En la clase pasada vimos una extensa motivación de qué son y porqué usar modelos gráficos probabilísticos. Segregando cada palabra de este nombre:

- Modelo: construimos un modelo a partir de la experiencia de un experto y de los datos para modelar la situación que estamos enfrentando. El modelo es un ente por sí solo, y contiene todo nuestro conocimiento acerca del fenómeno sobre el que estamos razonando: las variables que involucra y cómo interactúan entre ellas. Entonces separamos el modelo de los algoritmos que usamos para razonar sobre él.

- Probabilísitico: hablamos que casi todas las aplicaciones reales tenían algún tipo de incertidumbre involucrada (observación parcial, ruido de medición, incluso dudamos de que el mundo sea determinista). Por tanto, la probabildiad es un enfoque que nos permite incorporar esta incertidumbre dentro del modelo y nos da herramientas para trabajar con la incertidumbre.

- Gráfico: finalmente, discutimos que codificar una distribución conjunta es una tarea compleja. En el caso más sencillo en el que tenemos $k$ VA binarias, necesitaríamos $2^k$ parámetros para especificar completamente la distribución conjunta. Por tanto, explotamos la teoría de grafos para codificar y manipular este tipo de distribuciones de manera efectiva.

Concluimos entonces que el uso de las representaciones gráficas está justificado por:

- Son una estructura de datos intuitiva y compacta para capturar las distribuciones de alta dimensionalidad.

- Nos permiten razonar de forma eficiente usando algoritmos que explotan la estructura gráfica.

- En ciertas ocasiones, tenemos una parametrización "*sparse*", lo que nos permite representar distribuciones grandes con un número pequeño de parámetros.

- Podemos adquirir conocimiento de expertos.

- Nos permite aprender los parámetros de manera robusta a partir de datos.

# 1. Preeliminares

## 1.1. Ejemplo

Consideramos el caso de un estudiante dentro de un curso. Queremos razonar acerca de
   - la $I$(nteligencia) del estudiante,
   - la $D$(ificultad) del curso,
   - la $C$(alificación) del estudiante dentro del curso,
   - el $P$(untaje) en las pruebas estatales, y
   - una carta de $R$(ecomendación) laboral que emite el profesor del curso acerca del estudiante.

Consideramos una discretización de las anteriores variables como sigue:
- $\mathrm{Val}(I)=\{i^0, i^1\}$, donde $i^0$ representa baja inteligencia e $i^1$ representa alta inteligencia;
- $\mathrm{Val}(D)=\{d^0, d^1\}$, donde $d^0$ representa fácil y $d^1$ representa difícil;
- $\mathrm{Val}(C)=\{c^0, c^1, c^2\}$, donde $c^0$ representa calificación baja ($< 6$), $c^1$ representa calificación media ($\geq 6$, $< 9$) y $c^2$ representa calificación alta ($\geq 9$);
- $\mathrm{Val}(P)=\{p^0, p^1\}$, donde $p^0$ representa mal puntaje y $p^1$ representa alto puntaje;
- $\mathrm{Val}(R)=\{r^0, r^1\}$, donde $r^0$ representa una carta de recomendación débil y $r^1$ representa una carta de recomendación fuerte.

**Pregunta.** De no usar modelos gráficos probabilísticos, ¿cuántos parámetros necesitamos para especificar por completo la distribución sobre las cinco variables mencionadas?

*Respuesta:* Necesitaríamos $2 \times 2 \times 3 \times 2 \times 2 -1 = 48 - 1 = 47$ parámetros.

Por otra parte, pensando en la **causalidad** de la situación, podríamos plantear la siguiente Red Bayesiana para modelar el ejemplo anterior:

```python
from IPython.display import Image
```

```python
Image(filename='figures/Student.png')
```

Al final de esta clase, entenderemos cómo codificar la distribución de las variables sobre esta red, y apartir de ello, porqué este modelo necesita muchos menos parámetros que el modelo genérico (distribución conjunta).

## 1.2. Distribuciones de probabilidad y sus operaciones

Consideremos el ejemplo del estudiante, esta vez reducido a las variables $I$, $D$ y $C$.

Una distribución conjunta, $P(I,D,C)$, sobre estas tres variables es:

|  $I$  |  $D$  |  $C$  |  $P$  |
| ----- | ----- | ----- | ----- |
| $i^0$ | $d^0$ | $c^0$ | 0.126 |
| $i^0$ | $d^0$ | $c^1$ | 0.168 |
| $i^0$ | $d^0$ | $c^2$ | 0.126 |
| $i^0$ | $d^1$ | $c^0$ | 0.126 |
| $i^0$ | $d^1$ | $c^1$ | 0.045 |
| $i^0$ | $d^1$ | $c^2$ | 0.009 |
| $i^1$ | $d^0$ | $c^0$ | 0.0056|
| $i^1$ | $d^0$ | $c^1$ | 0.0224|
| $i^1$ | $d^0$ | $c^2$ | 0.252 |
| $i^1$ | $d^1$ | $c^0$ | 0.024 |
| $i^1$ | $d^1$ | $c^1$ | 0.036 |
| $i^1$ | $d^1$ | $c^2$ | 0.06  |

**Pregunta.** ¿Cuántos parámetros en total?

*Respuesta:* $2 \times 2 \times 3 = 12$

```python
# Importamos pgmpy.factors.discrete.JointProbabilityDistribution
from pgmpy.factors.discrete import JointProbabilityDistribution as joint
```

```python
# help JointProbabilityDistribution
joint?
```

```python
# Crear la distribución conjunta
P_IDC = joint(variables=["I", "D", "C"],
              cardinality=[2, 2, 3],
              values=[0.126, 0.168, 0.126, 0.126, 0.045, 0.009, 0.0056, 0.0224, 0.252, 0.024, 0.036, 0.06])
```

```python
print(P_IDC)
```

    +------+------+------+------------+
    | I    | D    | C    |   P(I,D,C) |
    +======+======+======+============+
    | I(0) | D(0) | C(0) |     0.1260 |
    +------+------+------+------------+
    | I(0) | D(0) | C(1) |     0.1680 |
    +------+------+------+------------+
    | I(0) | D(0) | C(2) |     0.1260 |
    +------+------+------+------------+
    | I(0) | D(1) | C(0) |     0.1260 |
    +------+------+------+------------+
    | I(0) | D(1) | C(1) |     0.0450 |
    +------+------+------+------------+
    | I(0) | D(1) | C(2) |     0.0090 |
    +------+------+------+------------+
    | I(1) | D(0) | C(0) |     0.0056 |
    +------+------+------+------------+
    | I(1) | D(0) | C(1) |     0.0224 |
    +------+------+------+------------+
    | I(1) | D(0) | C(2) |     0.2520 |
    +------+------+------+------------+
    | I(1) | D(1) | C(0) |     0.0240 |
    +------+------+------+------------+
    | I(1) | D(1) | C(1) |     0.0360 |
    +------+------+------+------------+
    | I(1) | D(1) | C(2) |     0.0600 |
    +------+------+------+------------+

```python
# Verificar que la distribución es válida
P_IDC.values.sum()
```

    1.0

Entonces, tenemos que para cualquier distribución sobre un conjunto de variables $\bar{X}$:

$$\sum_{\bar{x}\in\mathrm{Val}(\bar{X})} P(\bar{X}=\bar{x}) = 1.$$

> **Independencia de parámetros:** son parámetros independientes aquellos cuyo valor no está completamente determinado por el valor de otros parámetros.

En este caso, la distribución se determina con 12 parámetros. Sin embargo, la suma de todos ellos debe ser uno (1). Por tanto, *sólo hay 11 parámetros independientes*.

### ¿Qué operaciones podemos llevar a cabo sobre una distribucción?

**1. Reducción**

Supongamos que observamos que la calificación final del estudiante es alta, esto es, $C=c^2$. La operación de *reducción* consiste en eliminar todas las filas que no son consistentes con la observación:

```python
# help método reduce
P_IDC.reduce?
```

```python
# Operación de reducción C=c2
P_IDc2 = P_IDC.reduce(values=[('C', 2)], inplace=False)
```

```python
P_IDc2
```

    <Joint Distribution representing P(I:2, D:2) at 0x7fc5c2f1ef50>

```python
print(P_IDc2)
```

    +------+------+----------+
    | I    | D    |   P(I,D) |
    +======+======+==========+
    | I(0) | D(0) |   0.1260 |
    +------+------+----------+
    | I(0) | D(1) |   0.0090 |
    +------+------+----------+
    | I(1) | D(0) |   0.2520 |
    +------+------+----------+
    | I(1) | D(1) |   0.0600 |
    +------+------+----------+

```python
P_IDc2.values.sum()
```

    0.447

Matemáticamente, esta operación equivale a considerar la distribución evaluada $P(I, D, C=c^2) = P(I, D, c^2)$.

**Pregunta**. ¿Es este resultado una distribución de probabilidad sobre las variables $I,D$?

*Respuesta:* No.

**2. Condición**

A partir de la operación de reducción, si queremos obtener una distribución legítima sobre las variables que no reducimos, debemos dividir sobre la suma:

```python
# help método conditional_distribution
P_IDC.conditional_distribution?
```

```python
# Operación de condición sobre C=c2
P_ID_given_c2 = P_IDC.conditional_distribution(values=[('C', 2)], inplace=False)
```

```python
P_ID_given_c2
```

    <Joint Distribution representing P(I:2, D:2) at 0x7fc5c2ef6450>

```python
print(P_ID_given_c2)
```

    +------+------+----------+
    | I    | D    |   P(I,D) |
    +======+======+==========+
    | I(0) | D(0) |   0.2819 |
    +------+------+----------+
    | I(0) | D(1) |   0.0201 |
    +------+------+----------+
    | I(1) | D(0) |   0.5638 |
    +------+------+----------+
    | I(1) | D(1) |   0.1342 |
    +------+------+----------+

```python
P_IDc2.values / P_IDc2.values.sum()
```

    array([[0.28187919, 0.02013423],
           [0.56375839, 0.13422819]])

```python
P_ID_given_c2.values.sum()
```

    1.0

Matemáticamente, esta operación equivale a considerar la distribución condicionada $P(I, D| C=c^2) = P(I, D| c^2)$.

**Pregunta**. ¿Es este resultado una distribución de probabilidad sobre las variables $I,D$?

*Respuesta:* Si.

**3. Marginalización**

Cuando tenemos una distribución de probabilidad sobre un conjunto de variables, y producimos una sobre un subconjunto de las variables originales. Por ejemplo, queremos la distribución marginal sobre $I, D$:

```python
# Imprimir distribución inicial
print(P_IDC)
```

    +------+------+------+------------+
    | I    | D    | C    |   P(I,D,C) |
    +======+======+======+============+
    | I(0) | D(0) | C(0) |     0.1260 |
    +------+------+------+------------+
    | I(0) | D(0) | C(1) |     0.1680 |
    +------+------+------+------------+
    | I(0) | D(0) | C(2) |     0.1260 |
    +------+------+------+------------+
    | I(0) | D(1) | C(0) |     0.1260 |
    +------+------+------+------------+
    | I(0) | D(1) | C(1) |     0.0450 |
    +------+------+------+------------+
    | I(0) | D(1) | C(2) |     0.0090 |
    +------+------+------+------------+
    | I(1) | D(0) | C(0) |     0.0056 |
    +------+------+------+------------+
    | I(1) | D(0) | C(1) |     0.0224 |
    +------+------+------+------------+
    | I(1) | D(0) | C(2) |     0.2520 |
    +------+------+------+------------+
    | I(1) | D(1) | C(0) |     0.0240 |
    +------+------+------+------------+
    | I(1) | D(1) | C(1) |     0.0360 |
    +------+------+------+------------+
    | I(1) | D(1) | C(2) |     0.0600 |
    +------+------+------+------------+

```python
# help método marginalize
P_IDC.marginalize?
```

```python
# Marginalizar C
P_ID = P_IDC.marginalize(variables=["C"], inplace=False)
```

```python
print(P_ID)
```

    +------+------+----------+
    | I    | D    |   P(I,D) |
    +======+======+==========+
    | I(0) | D(0) |   0.4200 |
    +------+------+----------+
    | I(0) | D(1) |   0.1800 |
    +------+------+----------+
    | I(1) | D(0) |   0.2800 |
    +------+------+----------+
    | I(1) | D(1) |   0.1200 |
    +------+------+----------+

Y si queremos la marginal sobre $I$ nada más:

```python
# Marginalizar C y D
P_I = P_IDC.marginalize(variables=["C", "D"], inplace=False)
```

```python
print(P_I)
```

    +------+--------+
    | I    |   P(I) |
    +======+========+
    | I(0) | 0.6000 |
    +------+--------+
    | I(1) | 0.4000 |
    +------+--------+

Matemáticamente, las anteriores operaciones equivalen a:

$$P(I, D) = \sum_{c\in\mathrm{Val}(C)} P(I, D, C=c), \text{ y }$$

$$P(I) = \sum_{(c,d)\in\mathrm{Val}(C,D)} P(I, D=d, C=c).$$

En abuso de la notación, para no hacer engorrosa la escritura, las anteriores sumas se expresan comúnmente como:

$$P(I, D) = \sum_{C} P(I, D, C), \text{ y }$$

$$P(I) = \sum_{C,D} P(I, D, C).$$

## 1.3. Factores

La manera en que formalizamos esto matemáticamente es através de la noción de factores:

> *Definición.* Un **factor** $\phi(X_1, \dots, X_k)$ es una función que toma un conjunto de VA $X_1, \dots, X_k$ y devuelve un valor real no negativo:
>
> $$\phi: \mathrm{Val}(X_1, \dots, X_k) \to \mathbb{R}_{\geq 0}.$$
>
> El conjunto de variables $\{X_1, \dots, X_k\}$ se le llama el **alcance** del factor $\phi$, y se denota como $\mathrm{alcance}[\phi]=\{X_1, \dots, X_k\}.$

Los factores son el bloque constructor fundamental para definir distribuciones en espacios con alta dimensión. Además, aplican tanto en redes Bayesianas como en redes de Markov.

**Ejemplos:**

- Una distribución conjunta: $\phi_1(I, D, C) = P(I, D, C)$; $\mathrm{alcance}[\phi_1]=\{I, D, C\}$.

- Una medida sin normalizar: $\phi_2(I, D) = P(I, D, c^2)$; $\mathrm{alcance}[\phi_2]=\{I, D\}$.

- Una distribución condicional: $\phi_3(I, D, C) = P(I, D | C)$; $\mathrm{alcance}[\phi_3]=\{I, D, C\}$. Para cada valor de C tenemos una distribución sobre $I,D$.

**Nota:** los factores no son necesariamente probabilidades.

```python
# Hallar distribución condicional
P_ID_given_c0 = P_IDC.conditional_distribution(values=[("C", 0)], inplace=False)
P_ID_given_c1 = P_IDC.conditional_distribution(values=[("C", 1)], inplace=False)
```

```python
print(P_ID_given_c0)
```

    +------+------+----------+
    | I    | D    |   P(I,D) |
    +======+======+==========+
    | I(0) | D(0) |   0.4474 |
    +------+------+----------+
    | I(0) | D(1) |   0.4474 |
    +------+------+----------+
    | I(1) | D(0) |   0.0199 |
    +------+------+----------+
    | I(1) | D(1) |   0.0852 |
    +------+------+----------+

```python
print(P_ID_given_c1)
```

    +------+------+----------+
    | I    | D    |   P(I,D) |
    +======+======+==========+
    | I(0) | D(0) |   0.6190 |
    +------+------+----------+
    | I(0) | D(1) |   0.1658 |
    +------+------+----------+
    | I(1) | D(0) |   0.0825 |
    +------+------+----------+
    | I(1) | D(1) |   0.1326 |
    +------+------+----------+

```python
print(P_ID_given_c2)
```

    +------+------+----------+
    | I    | D    |   P(I,D) |
    +======+======+==========+
    | I(0) | D(0) |   0.2819 |
    +------+------+----------+
    | I(0) | D(1) |   0.0201 |
    +------+------+----------+
    | I(1) | D(0) |   0.5638 |
    +------+------+----------+
    | I(1) | D(1) |   0.1342 |
    +------+------+----------+

### Operaciones entre factores

**1. Producto de factores**

$$\phi_1(A, B)$$

|   A   |   B   | $\phi_1$ |
| ----- | ----- | -------- |
| $a^0$ | $b^0$ | 5        |
| $a^0$ | $b^1$ | 8        |
| $a^1$ | $b^0$ | 1        |
| $a^1$ | $b^1$ | 0        |
| $a^2$ | $b^0$ | 3        |
| $a^2$ | $b^1$ | 9        |

$$\phi_2(B, C)$$

|   B   |   C   | $\phi_1$ |
| ----- | ----- | -------- |
| $b^0$ | $c^0$ | 5        |
| $b^0$ | $c^1$ | 7        |
| $b^1$ | $c^0$ | 1        |
| $b^1$ | $c^1$ | 2        |

$$\phi_3(A, B, C) = \phi_1(A, B) \times \phi_2(B, C)$$

|   A   |   B   |   C   | $\phi_3$ |
| ----- | ----- | ----- | ---------------- |
| $a^0$ | $b^0$ | $c^0$ | $5\times 5 = 25$ |
| $a^0$ | $b^0$ | $c^1$ | $5\times 7 = 35$ |
| $a^0$ | $b^1$ | $c^0$ | $8\times 1 = 8$  |
| $a^0$ | $b^1$ | $c^1$ | $8\times 2 = 16$ |
| $a^1$ | $b^0$ | $c^0$ | $1\times 5 = 5$  |
| $a^1$ | $b^0$ | $c^1$ | $1\times 7 = 7$  |
| $a^1$ | $b^1$ | $c^0$ | $0\times 1 = 0$  |
| $a^1$ | $b^1$ | $c^1$ | $0\times 2 = 0$  |
| $a^2$ | $b^0$ | $c^0$ | $3\times 5 = 15$ |
| $a^2$ | $b^0$ | $c^1$ | $3\times 7 = 21$ |
| $a^2$ | $b^1$ | $c^0$ | $9\times 1 = 9$  |
| $a^2$ | $b^1$ | $c^1$ | $9\times 2 = 18$ |

```python
# Importamos pgmpy.factors.discrete.DiscreteFactor
from pgmpy.factors.discrete import DiscreteFactor
```

```python
# help DiscreteFactor
DiscreteFactor?
```

```python
# Factores phi1, phi2
phi1 = DiscreteFactor(variables=["A", "B"],
                      cardinality=[3, 2],
                      values=[5, 8, 1, 0, 3, 9])
phi2 = DiscreteFactor(variables=["B", "C"],
                      cardinality=[2, 2],
                      values=[5, 7, 1,2])
```

```python
# Producto de factores
phi3 = phi1 * phi2
# phi3 = phi1.product(phi2)
```

```python
print(phi3)
```

    +------+------+------+--------------+
    | A    | B    | C    |   phi(A,B,C) |
    +======+======+======+==============+
    | A(0) | B(0) | C(0) |      25.0000 |
    +------+------+------+--------------+
    | A(0) | B(0) | C(1) |      35.0000 |
    +------+------+------+--------------+
    | A(0) | B(1) | C(0) |       8.0000 |
    +------+------+------+--------------+
    | A(0) | B(1) | C(1) |      16.0000 |
    +------+------+------+--------------+
    | A(1) | B(0) | C(0) |       5.0000 |
    +------+------+------+--------------+
    | A(1) | B(0) | C(1) |       7.0000 |
    +------+------+------+--------------+
    | A(1) | B(1) | C(0) |       0.0000 |
    +------+------+------+--------------+
    | A(1) | B(1) | C(1) |       0.0000 |
    +------+------+------+--------------+
    | A(2) | B(0) | C(0) |      15.0000 |
    +------+------+------+--------------+
    | A(2) | B(0) | C(1) |      21.0000 |
    +------+------+------+--------------+
    | A(2) | B(1) | C(0) |       9.0000 |
    +------+------+------+--------------+
    | A(2) | B(1) | C(1) |      18.0000 |
    +------+------+------+--------------+

**2. Marginalización**

**3. Reducción**

**Nota:** Los factores no están obligados a sumar uno. Por eso no hay operación de condición en factores.

### Actividad

1. Sean $\phi_1$ y $\phi_2$ los siguientes factores

$$\phi_1(X, Y)$$

|   X   |   Y   | $\phi_1$ |
| ----- | ----- | -------- |
| $x^0$ | $y^0$ | 0.8      |
| $x^0$ | $y^1$ | 0.5      |
| $x^1$ | $y^0$ | 0.5      |
| $x^1$ | $y^1$ | 0.6      |

$$\phi_2(Y, Z)$$

|   Y   |   Z   | $\phi_2$ |
| ----- | ----- | -------- |
| $y^0$ | $z^0$ | 0.2      |
| $y^0$ | $z^1$ | 0.2      |
| $y^1$ | $z^0$ | 0.9      |
| $y^1$ | $z^1$ | 1.0      |

Si $\psi(X,Y,Z) = \phi_1(X, Y) \phi_2(Y,Z)$
- $\psi(x^0, y^0, z^0)=0.8 \times 0.2$
- $\psi(x^0, y^1, z^1)=0.5 \times 1.0$
- $\psi(x^1, y^1, z^1)=0.6 \times 1.0$

2. Sea $\phi_1$ el siguiente factor

$$\phi_1(X, Y, Z)$$

|   X   |   Y   |   Z   | $\phi_1$ |
| ----- | ----- | ----- | -------- |
| $x^0$ | $y^0$ | $z^0$ | 10       |
| $x^0$ | $y^0$ | $z^1$ | 5        |
| $x^0$ | $y^1$ | $z^0$ | 15       |
| $x^0$ | $y^1$ | $z^1$ | 16       |
| $x^1$ | $y^0$ | $z^0$ | 14       |
| $x^1$ | $y^0$ | $z^1$ | 30       |
| $x^1$ | $y^1$ | $z^0$ | 2        |
| $x^1$ | $y^1$ | $z^1$ | 8        |

Si $\psi(X,Z) = \phi_1(X, y^1, Z)$

- $\psi(x^0, z^0)=15$
- $\psi(x^0, z^1)=16$
- $\psi(x^1, z^1)=8$

3. Sea $\phi_1$ el siguiente factor

$$\phi_1(X, Y, Z)$$

|   X   |   Y   |   Z   | $\phi_1$ |
| ----- | ----- | ----- | -------- |
| $x^0$ | $y^0$ | $z^0$ | 10       |
| $x^0$ | $y^0$ | $z^1$ | 5        |
| $x^0$ | $y^1$ | $z^0$ | 15       |
| $x^0$ | $y^1$ | $z^1$ | 16       |
| $x^1$ | $y^0$ | $z^0$ | 14       |
| $x^1$ | $y^0$ | $z^1$ | 30       |
| $x^1$ | $y^1$ | $z^0$ | 2        |
| $x^1$ | $y^1$ | $z^1$ | 8        |

Si $\psi(Y,Z) = \sum_{X}\phi_1(X, Y, Z)$

- $\psi(y^0, z^0)=10 + 14$
- $\psi(y^0, z^1)=5 + 30$
- $\psi(y^1, z^1)=16 + 8$

___
# 2. Fundamentos de Redes Bayesianas

## 2.1. Semántica y factorización.

Consideremos, de nuevo, el ejemplo del estudiante  Ver en el tablero y discutir modelamiento a partir de las variables

Ahora, con las distribuciones condiconales:

```python
Image(filename='figures/Student1.png')
```

- Como vimos antes, este modelo se produjo pensando en las dependencias de las VA.

- Se modela cada nodo con una distribución condicional.

Cuando todas las distribuciones condicionales están especificadas, tenemos una red Bayesiana completamente parametrizada, capaz de producir una distribución conjunta sobre todas las VA.

¿Cómo? Regla de la cadena + independencia.  Ver en el tablero   Descubrir deducción

\begin{align}
P(I, D, C, P, R) & = P(I, D, C, P) P(R | I, D, C, P) \\
                 & = P(I, D, C) P(P | I, D, C) P(R | C) \\
                 & = P(I, D) P(C | I, D) P(P | I) P(R | C) \\
                 & = P(I) P(D | I) P(C | I, D) P(P | I) P(R | C) \\
                 & = P(I) P(D) P(C | I, D) P(P | I) P(R | C) \\
\end{align}

Por ejemplo,

$$P(i^1, d^0, c^0, p^1, r^1)=P(i^1) P(d^0) P(c^0 | i^1, d^0) P(p^1 | i^1) P(r^1 | c^0) = 0.3 \times 0.6 \times 0.02 \times 0.8 \times 0.01 $$

Formalmente:

> *Definición.* Una **estructura de red Bayesiana** es un grafo acíclico dirigido, $\mathcal{G}$, cuyos nodos representan un conjunto de VA $\{X_1, \dots, X_n\}$. Sean $\mathrm{Pa}_{\mathcal{G}}X_i$ los nodos padres de $X_i$ en $\mathcal{G}$, y $\mathrm{NoDescendientes}_{\mathcal{G}}X_i$ los nodos que no son descendientes de $X_i$ en $\mathcal{G}$. Entonces, $\mathcal{G}$ codifica el siguiente conjunto de **independencias locales**
>
> $$\mathcal{I}_{l}(\mathcal{G}) = \{(X_i \perp \mathrm{NoDescendientes}_{\mathcal{G}}X_i | \mathrm{Pa}_{\mathcal{G}}X_i)\}.$$

De esta manera,

> *Definición.* Dado un grafo $\mathcal{G}$ sobre $\{X_1, \dots, X_n\}$, decimos que **$P$ factoriza sobre $\mathcal{G}$** si
>
> $$P(X_1, \dots, X_n) = \prod_{i=1}^{n} P(X_i | \mathrm{Pa}_{\mathcal{G}}X_i).$$

Finalmente,

> *Definición.* Una **red Bayesiana** es un par $\mathcal{B}=(\mathcal{G}, P)$ donde $P$ factoriza sobre $\mathcal{G}$ y $P$ está especificada como un conjunto de distribuciones condicionales de la forma $P(X_i | \mathrm{Pa}_{\mathcal{G}}X_i)$ asociada a cada nodo de la red.

**Actividad.**

1. Obtener las independencias locales que codifica la red Bayesiana del estudiante.

2. Comparar el número de parámetros independientes que necesita la red Bayesiana del estudiante contra el número de parámetros que necesitaría la distribución conjunta sin ninguna suposición de independencia.

**¿Cómo declarar una red Bayesiana en pgmpy?**

```python
# Importamos pgmpy.models.BayesianModel
from pgmpy.models import BayesianModel
# Importamos pgmpy.factors.discrete.TabularCPD
from pgmpy.factors.discrete import TabularCPD
```

```python
TabularCPD?
```

```python
# Definimos el esqueleto de la red mediante los arcos
student_model = BayesianModel([('D', 'C'),
                               ('I', 'C'),
                               ('I', 'E'),
                               ('C', 'R')])
```

```python
# Definimos distribución condicional de D
cpd_D = TabularCPD(variable='D',
                   variable_card=2,
                   values=[[0.6],
                           [0.4]])
# Definimos distribución condicional de I
cpd_I = TabularCPD(variable='I',
                   variable_card=2,
                   values=[[0.7],
                           [0.3]])
```

```python
print(cpd_D)
```

    +------+-----+
    | D(0) | 0.6 |
    +------+-----+
    | D(1) | 0.4 |
    +------+-----+

La representación de las distribuciones condicionales en `pgmpy` es un poquito distinto a como está en el dibujo de arriba. En `pgmpy` las columnas representan evidencia, y las filas los distintos estados de la variable en la distribución condicional:

|         | $i^0 d^0$ | $i^0 d^1$ | $i^1 d^0$ | $i^1 d^1$ |
| ------- | --------- | --------- | --------- | --------- |
| $c^0$   | 0.3       | 0.7       | 0.02      | 0.2       |
| $c^1$   | 0.4       | 0.25      | 0.08      | 0.3       |
| $c^2$   | 0.3       | 0.05      | 0.9       | 0.5       |

```python
# Definimos distribución condicional de C
cpd_C = TabularCPD(variable='C',
                   variable_card=3,
                   evidence=['I', 'D'],
                   evidence_card=[2, 2],
                   values=[[0.30, 0.70, 0.02, 0.20],
                           [0.40, 0.25, 0.08, 0.30],
                           [0.30, 0.05, 0.90, 0.50]])
# Definimos distribución condicional de P
cpd_E = TabularCPD(variable='E',
                   variable_card=2,
                   evidence=['I'],
                   evidence_card=[2],
                   values=[[0.95, 0.2],
                           [0.05, 0.8]])
# Definimos distribución condicional de R
cpd_R = TabularCPD(variable='R',
                   variable_card=2,
                   evidence=['C'],
                   evidence_card=[3],
                   values=[[0.99, 0.4, 0.1],
                           [0.01, 0.6, 0.9]])
```

```python
# Asociamos las distribuciones condicionales a la red
student_model.add_cpds(cpd_D, cpd_I, cpd_C, cpd_E, cpd_R)
```

    WARNING:root:Replacing existing CPD for D
    WARNING:root:Replacing existing CPD for I
    WARNING:root:Replacing existing CPD for C

```python
# Check the model for various errors. This method checks for the following errors:
# * Checks if the sum of the probabilities for each state is equal to 1 (tol=0.01).
# * Checks if the CPDs associated with nodes are consistent with their parents.
student_model.check_model()
```

    True

Una vez tenemos el modelo, podemos hacer varias cosas con él. Entre ellas, podemos verificar las independencias locales que codifica el modelo:

```python
# Verificar las independencias locales que implica el modelo
student_model.local_independencies('D')
```

    (D ⟂ E, I)

```python
student_model.local_independencies('C')
```

    (C ⟂ E | I, D)

```python
student_model.local_independencies(['D', 'I', 'C', 'E', 'R'])
```

    (D ⟂ E, I)
    (I ⟂ D)
    (C ⟂ E | I, D)
    (E ⟂ C, R, D | I)
    (R ⟂ E, I, D | C)

### 2.1.1. Patrones de razonamiento

Teniendo una situación modelada con una red Bayesiana, nos podemos plantear tres tipos básicos de razonamientos que poríamos querer resolver:

**1. Razonamiento causal**

Se refiere a cuando tenemos evidencia de causas y queremos razonar acerca de efectos

```python
Image(filename='figures/Student2.png')
```

Por ejemplo, la probabilidad de obtener una buena carta de recomendación es:

$$P(r^1) = \sum_{D,I,C,P} P(D,I,C,P,r^1) \approx 0.5.$$

Sin embargo, podemos evaluar cómo esta probabilidad cambia si la condicionamos sobre la inteligencia. Por ejemplo, si el estudiante no es muy inteligente

$$P(r^1 | i^0) = \frac{P(r^1, i^0)}{P(i^0)} = \frac{\sum_{D,C,P} P(D, i^0, C, P, r^1)}{\sum_{D,C,P,R} P(D, i^0, C, P, R)} \approx 0.39.$$

*¿Se esperaba esto o no?*

Por otra parte, si también condicionamos sobre la dificultad

$$P(r^1 | i^0, d^0) = \frac{P(r^1, i^0, d^0)}{P(i^0, d^0)} = \frac{\sum_{C,P} P(d^0, i^0, C, P, r^1)}{\sum_{C,P,R} P(d^0, i^0, C, P, R)} \approx 0.51.$$

*¿Se esperaba esto o no?*

**2. Razonamiento evidencial**

Se refiere a cuando tenemos evidencia de los efectos y que remos razonar acerca de las causas

```python
Image(filename='figures/Student3.png')
```

Por ejemplo, la probabilidad de que el curso sea difícil es:

$$P(d^1) = 0.4.$$

Condicionando sobre la calificación:

$$P(d^1 | c^0) = \frac{P(d^1, c^0)}{P(c^0)} = \frac{\sum_{I,P,R} P(d^1, I, c^0, P, R)}{\sum_{D,I,P,R} P(D, I, c^0, P, R)} \approx 0.63.$$

Similarmente, la probabilidad de que el estudiante sea inteligente es:

$$P(i^1) = 0.3.$$

Condicionando sobre la calificación:

$$P(i^1 | c^0) = \frac{P(i^1, c^0)}{P(c^0)} = \frac{\sum_{D,P,R} P(D, i^1, c^0, P, R)}{\sum_{D,I,P,R} P(D, I, c^0, P, R)} \approx 0.08.$$

**3. Razonamiento intercausal**

Se refiere a cuando hay flujo de información entre dos causas de un mismo efecto (por ejemplo, Dificultad e Inteligencia son causas de la Calificación).

¿Cómo es posible esto si dijimos que $(D\perp I)$? Veremos que en este caso cuando tenemos información de la causa, se produce este efecto.

De nuevo, la probabilidad de que el estudiante sea inteligente es:

$$P(i^1) = 0.3.$$

Condicionando sobre la calificación:

$$P(i^1 | c^0) = \frac{P(i^1, c^0)}{P(c^0)} \approx 0.08.$$

Aún más, si condicionamos sobre la dificultad:

$$P(i^1 | c^0, d^1) = \frac{P(i^1, c^0, d^1)}{P(c^0, d^1)} \approx 0.11.$$

Este tipo de razonamiento es un poco complejo de comprender, pues no tenemos relación directa (un arco en la red) entre $I$, y $D$.

### 2.1.2. Flujo de influencia probabilística

Ya hemos visto entonces que en una red Bayesiana, el conocimiento de ciertos nodos influencian a otros. Una propiedad atractiva de la codificaición de distribuciones en los grafos es poder definir a partir de la estructura del grafo cuándo el conocimiento acerca de una variable puede o no influenciar la probabilidad de a otra.

**¿Cuando puede $X$ influenciar a $Y$?** (Sin evidencia)

```python
Image(filename='figures/Causal.png')
```

¿Si o no? Si

```python
Image(filename='figures/Evidential.png')
```

¿Si o no? Si

```python
Image(filename='figures/Causal2.png')
```

¿Si o no? Si

```python
Image(filename='figures/Evidential2.png')
```

¿Si o no? Si

```python
Image(filename='figures/CommonCause.png')
```

¿Si o no? Si

```python
Image(filename='figures/CommonEvidence.png')
```

Esta estructura se conoce también como **estructura V**.

¿Si o no? No

A partir del análisis anterior:

> *Definición.* Un **camino** es una sucesión de nodos que están conectados por arcos simples en el grafo.

Por ejemplo, $D-C-R$, $D-C-I-P$ en el ejemplo del estudiante son caminos.

> *Definición.* Decimos que un camino $X_1 - \dots - X_k$ (sin evidencia) está **activo** si no contiene estructuras V.

Por tanto, sin evidencia, el camino $D-C-R$ está activo, pero $D-C-I-P$ no.

Ahora,

**¿Cuando puede $X$ influenciar a $Y$ dada evidencia de $\bar{Z}$?**

```python
Image(filename='figures/Causal.png')
```

¿Si o no? Si

```python
Image(filename='figures/Evidential.png')
```

¿Si o no? Si

```python
Image(filename='figures/Causal2.png')
```

1. $W \in \bar{Z}$

   ¿Si o no? No

2. $W \notin \bar{Z}$

   ¿Si o no? Si

```python
Image(filename='figures/Evidential2.png')
```

1. $W \in \bar{Z}$

   ¿Si o no? No

2. $W \notin \bar{Z}$

   ¿Si o no? Si

```python
Image(filename='figures/CommonCause.png')
```

1. $W \in \bar{Z}$

   ¿Si o no? No

2. $W \notin \bar{Z}$

   ¿Si o no? Si

```python
Image(filename='figures/CommonEvidence.png')
```

1. $W \in \bar{Z}$ (o alguno de los descendientes de $W$ está en $\bar{Z}$)

   ¿Si o no? Si

2. $W \notin \bar{Z}$ (y ninguno de los descendientes de $W$ está en $\bar{Z}$)

   ¿Si o no? No

```python
Image(filename='figures/Student.png')
```

> *Definición.* Decimos que un camino $X_1 - \dots - X_k$ está **activo dado $\bar{Z}$** si:
> - para toda estructura V $X_{i-1} \rightarrow X_{i} \leftarrow X_{i+1}$ tenemos que $X_i$ o alguno de sus descendientes es observado (está en $\bar{Z}$), y
> - ningún otro nodo (que no esté en estructuras V) es observado (está en $\bar{Z}$).

Por ejemplo, $D-C-I-P$ está activo cuando:

- Cuando conocemos $C$ o $R$, y
- Cuando no conocemos $I$

___

## 2.2. Independencia en redes Bayesianas

Comenzamos recordando los conceptos de independencia e independencia condicional.

Vimos que, dadas dos VA $X,Y$, la distribución $P$ satisface $(X \perp Y)$ si se cumple alguna de las siguientes:

- $P(X,Y) = P(X) P(Y)$,
- $P(X | Y) = P(X)$,
- $P(Y | X) = P(Y)$.

Similarmente, dadas las VA $X,Y,Z$, decimos que la distribución $P$ satiface $(X \perp Y | Z)$ si se cumple alguna de las siguientes:

- $P(X,Y|Z) = P(X|Z) P(Y|Z)$,
- $P(X | Y, Z) = P(X | Z)$,
- $P(Y | X, Z) = P(Y | Z)$.

De donde vemos que los conceptos de independencia están fuertemente relacionados con la factorización de distribuciones.

De manera que, si sabemos que $P$ factoriza sobre el grafo $\mathcal{G}$, **¿podemos leer dichas independencias a partir de la estructura de $\mathcal{G}$?**

Antes de continuar, se hace necesaria la siguiente definición acerca del conjunto de independencias asociado con una distribución:

> *Definición.* Sea $P$ una distribución. Definimos $\mathcal{I}(P)$ como el conjunto de independencias (tanto condicionales como no condicionales) de la forma $(X \perp Y)$ o $(X \perp Y | Z)$ que se satisfacen en $P$.

De manera que para decir "$P$ satisface las independencias locales en $\mathcal{G}$", podemos simplemente escribir $\mathcal{I}_l(\mathcal{G}) \subseteq \mathcal{I}(P)$.

Ahora, veamos una definición para el conjunto de independencias que pueden ser codificadas en un grafo.

> *Definición.* Sea $\mathcal{G}$ un grafo asociado con un conjunto de independencias $\mathcal{I}(\mathcal{G})$. Decimos que $\mathcal{G}$ es un I-map para un conjunto de independencias $\mathcal{I}$ si
>
> $$\mathcal{I}(\mathcal{G}) \subseteq \mathcal{I}.$$

De lo anterior, hacemos cierto abuso del lenguaje cuando decimos "$\mathcal{G}$ es in I-map para $P$" significando esto que $\mathcal{I}(\mathcal{G}) \subseteq \mathcal{I}(P)$.

Notamos que para que un grafo $\mathcal{G}$ sea un I-map para una distribución $P$ se requiere que cualquier independencia en $\mathcal{G}$ está presente en $P$; sin embargo $P$ puede tener independencias adicionales que no estén en $\mathcal{G}$.

### 2.2.1. I-map $\Rightarrow$ factorización

Con las anteriores definiciones estamos listos para el siguiente resultado.

> *Teorema.* Sea $\mathcal{G}$ una estructura de red Bayesiana y $P$ una distribución, ambas sobre $\{X_1, \dots, X_n\}$. Si $\mathcal{G}$ es un I-map para $P$, entonces $P$ factoriza sobre $\mathcal{G}$.

> *Prueba*. En clase ...

Lo que el teorema anterior dice es que las suposiciones de independencia implicadas por el grafo $\mathcal{G}$ nos permiten factorizar una distribución $P$, para la cual $\mathcal{G}$ es un I-map, en pequeñas distribuciones condicionales.

### 2.2.2. Factorización $\Rightarrow$ I-map

Resulta que no solo las independencias locales en un grafo nos permiten factorizar distribuciones sobre él, sino que estas dos nociones son completamente equivalentes.

> *Teorema.* Sea $\mathcal{G}$ una estructura de red Bayesiana y $P$ una distribución, ambas sobre $\{X_1, \dots, X_n\}$. Si $P$ factoriza sobre $\mathcal{G}$, entonces $\mathcal{G}$ es un I-map para $P$.

# Conclusión

En esta clase vimos:

1. El concepto de **factor**, que son el bloque constructor para definir distribuciones de alta dimensionalidad tanto en redes Bayesianas como en redes de Markov.

2. Los conceptos básicos de redes Bayesianas: cómo se construyen, cuando una distribución se factoriza sobre ellas y cómo hacerlo. Adicionalmente, vimos cómo declarar este tipo de objetos usando la librería de `pgmpy`.

3. Finalmente, vimos que las nociones de que un grafo codifica las independencias de una distribución (I-map) y de que una distribución se pueda factorizar sobre un grafo son **equivalentes**.

# Anuncios parroquiales

### 1. Quiz.

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


```python
from estado import *
from sympy import *
init_printing(use_unicode=True)
import numpy as np
```

```python

```

```python

estado_inicial_de_busca = estado('water','gas',200,120.46850585938)
estado_finalB = busca_estado('specific_enthalpy',2802.88935988,'T',estado_inicial_de_busca, precision=0.9)
```

    <class 'estado.estado'>

```python
print(estado_finalB)

```

    Estado = overheated steam, tabela: gas, Pressão = 2.0 Kpa, Temperatura = 166.48413085938 Celsius

```python
#vapor 15 bar e 320 C
#passa turbina
#vai pro volume de 0.6m3
#valvula aberta até reservatório tenha vapor a 15 bar e 400C
print('Ex1')
#itema
#massa que entrou, W realizado pela bomba, e entropia gerada
p_i = 1500
p_fA = 1500
t_i = 320
t_fA = 400
estado_inicial = estado('water','gas',p_i, t_i)
estado_finalA = estado('water','gas',p_fA, t_fA)

vol = 0.6 #m3
densidade_final = estado_finalA.density
massa_finalA = densidade_final*vol
print('resps A')
print('massa no reservatório: ', "{:.4f}".format(massa_finalA), 'Kg')

trab = massa_finalA*(estado_inicial.specific_enthalpy - estado_finalA.specific_inner_energy)

print('trabalho realizado pela turbina: ', "{:.4f}".format(trab), 'Kj')

Sger = (-1)*massa_finalA*(estado_inicial.specific_entropy - estado_finalA.specific_entropy)

print('Sger: ', "{:.4f}".format(Sger), 'kj')

#A massa que entra no reservatório, a temperatura final no tanque e a entropia gerada durante o processo
#de enchimento (pressão final no tanque é 15 bar), quando não há trabalho realizado pela turbina;
u_final = estado_inicial.specific_enthalpy
estado_inicial_de_busca = estado('water','gas',p_i,t_i)
estado_finalB = busca_estado('specific_inner_energy',u_final,'T',estado_inicial_de_busca)

print()
print('resps B')
massa_finalB = estado_finalB.density*vol
t_fB = estado_finalB.temperature
SgerB = (-1)*massa_finalB*(estado_inicial.specific_entropy - estado_finalB.specific_entropy)

print('massa no reservatorio: ', "{:.4f}".format(massa_finalB), 'kg')
print('T_final: ', "{:.4f}".format(t_fB), 'Celsius')
print('SgerB: ', "{:.4f}".format(SgerB), 'kj')

#O máximo trabalho que pode ser realizado pela turbina (pressão final no tanque é 15 bar). Determine para
#esse cenário a massa que entra no reservatório, a temperatura final no tanque e a entropia gerada durante
#o processo de enchimento
print()
print('resps C')
p_fC = 1500
SgerC = 0
estado_finalC = estado_inicial
massa_finalC = estado_finalC.density*vol
t_fC = estado_finalC.temperature
trab = massa_finalC*(estado_inicial.specific_enthalpy - estado_finalC.specific_inner_energy)

print('trab: ',"{:.4f}".format(trab),'kj')
print('massa no reservatorio: ', "{:.4f}".format(massa_finalC), 'kg')
print('T_final: ', "{:.4f}".format(t_fC), 'Celsius')
print('SgerB: ', "{:.4f}".format(SgerC), 'kj')

#Analise e justifique os resultados obtidos em a, b e c, em termos dos valores da massa que entra no tanque,
#temperatura final no tanque, trabalho realizado e entropia gerada.

#tomar item A como base
```

    Ex1
    resps A
    massa no reservatório:  2.9555 Kg
    trabalho realizado pela turbina:  386.0864 Kj
    Sger:  0.8129 kj
    <class 'estado.estado'>

    resps B
    massa no reservatorio:  2.6317 kg
    T_final:  477.5065 Celsius
    SgerB:  1.3458 kj

    resps C
    trab:  900.0000 kj
    massa no reservatorio:  3.3990 kg
    T_final:  320.0000 Celsius
    SgerB:  0.0000 kj

```python
#Um reservatório rígido e adiabático contém ar, que pode ser tratado como gás perfeito.
#Uma membrana mantém o ar separado em duas massas iguais, à mesma temperatura T1 e às pressões P1 e P2,
#sendo P1 > P2. A membrana se rompe permitindo a mistura das massas. Um aluno de Termo diz que espera que
#a pressão final seja maior que P1. Um outro aluno diz que a pressão final, Pf, necessariamente terá que ser menor
#que raiz quadrada do produto das duas pressões iniciais. Verifique as respostas dos dois alunos.

#Resp discord 7:12 pm, lui mandou
Cp0 = symbols('Cp')
R = symbols('R')
Tf, T1, T2, T = symbols('Tf T1 T2 T')
Pf, P1, P2 = symbols('Pf P1 P2')

DeltaS1 = Cp0*log(T/T) - R*log(Pf/P1)
DeltaS2 = Cp0*log(T/T) - R*log(Pf/P2)

f = DeltaS1 + DeltaS2
solve(f,Pf, domain=S.Reals)
```

```python
print('Ex 3')
n_usp = str(1)
X = int(n_usp[-1])
print('Fim do numero usp: ', X)
Vgasi = 0.15 #m3
Vaguai = 0.15 #me
tgasi = 50 #Celsius
taguai = 70 #celsius
tituloi = (10 + X)/100
t_frio = 25 #celsius
paguaf = 120 + 10*X #kpa
# processo de compressão do gás é adiabático e reversível
#que a hipótese de gás perfeito é
#válida, que os calores específicos podem ser considerados constantes
patm = 100 #kpa

#(a) A massa de água; (b) A massa de gás; (c) O título (se for saturado) ou a temperatura (se não for
#saturado) do estado final da água; (d) A temperatura final do gás; (d) O trabalho de compressão do gás; (e) O
#trabalho líquido realizado pela água; (f) A quantidade de calor transferida à água; (g) A quantidade de calor
#extraída do ambiente; (h) O trabalho realizado pela bomba de calor.

estado_inicial_agua = estado('water','saturado',T=taguai)
vvapi = 1/estado_inicial_agua.density_steam
vagi = 1/estado_inicial_agua.density_water

vtot = vvapi*tituloi + vagi*(1-tituloi)
denstot = 1/vtot
massa_de_agua = denstot*Vaguai
print('A. massa de agua: ',"{:.4f}".format(massa_de_agua),'Kg')

R = 0.2968
P = estado_inicial_agua.pressure*100
pgasi = P
V = Vgasi
T = tgasi + 273.15
massa_de_gas = (P*V)/(R*T)

print('B. massa de gas: ', "{:.4f}".format(massa_de_gas),'Kg')

densidade_final_agua = massa_de_agua/(Vgasi*2)
estado_finalAgua_se_sat = estado('water','saturado',p=paguaf)

sat = False
if estado_finalAgua_se_sat.density_steam < densidade_final_agua and densidade_final_agua < estado_finalAgua_se_sat.density_water:
    print('Fica saturado msm')
    estado_finalAgua = estado_finalAgua_se_sat
    vliq = 1/estado_finalAgua_se_sat.density_water
    vvapor = 1/estado_finalAgua_se_sat.density_steam
    meuv = 1/densidade_final_agua
    titulof =  (meuv - vliq)/(vvapor - vliq)
    print('C. Como é saturado, aqui está o titulo: ', "{:.4f}".format(titulof))
    sat = True
else:
    estado_inicial_de_busca = estado('water','gas',paguaf,300)
    estado_finalAgua = busca_estado('density',densidade_final_agua,'T', estado_inicial_de_busca, proporcionalidade=-1)
    print('C. Como não é saturado, aqui está a temp: ', estado_finalAgua.temperature, 'Celsius')
Cp0 = 1.041
Cv0 = 0.744
k =  1.4
DeltaS = 0 #sem entropia, adiabatico irreversilvel
T2 = symbols('T2')
f = Cp0*log((T2+273.15)/(tgasi+273.15)) -R*log(patm/pgasi)
T2gas = solveset(f).args[0]
T_final_gas = T2gas
print('D1. Temperatura final do gas', "{:.4f}".format(T2gas), 'Celsius')

Trab_gas = -1*massa_de_gas*Cv0*(T2gas-tgasi)

print('D2. Trabalho: ', "{:.4f}".format(Trab_gas), 'Kj')
VF = massa_de_gas*R*(T2gas+ 273.15)/patm

Trab_conj = patm*VF
Trab_agua = Trab_conj - Trab_gas
print('E. Trabalho liq realizado pela agua: ', "{:.4f}".format(Trab_agua), 'Kj')

Uaguai =  estado_inicial_agua.specific_inner_energy_v*tituloi + estado_inicial_agua.specific_inner_energy_water*(1-tituloi)

if sat:
    Uaguaf = estado_finalAgua.specific_inner_energy_steam*titulof + estado_finalAgua.specific_inner_energy_water*(1-titulof)
else:
    Uaguaf = estado_finalAgua.specific_inner_energy

deltaU = Uaguaf - Uaguai
Qh = deltaU*massa_de_agua + Trab_agua
print('F. Calor que vai para agua: ', "{:.4f}".format(Qh) , 'KJ')

Saguai =  estado_inicial_agua.specific_entropy_steam*tituloi + estado_inicial_agua.specific_entropy_water*(1-tituloi)

if sat:
    Saguaf = estado_finalAgua.specific_entropy_steam*titulof + estado_finalAgua.specific_entropy_water*(1-titulof)
else:
    Saguaf = estado_finalAgua.specific_entropy

deltaS = Saguaf - Saguai
Ql = massa_de_agua*deltaS*(t_frio+273.15)
print('G. Calor extraído do ambiente (Ql): ', "{:.4f}".format(Ql), 'KJ')
Trab_bomba = Qh - Ql
print('H. Trabalho executado pela bomba: ', "{:.4f}".format(Trab_bomba), 'KJ')
print()
print()
```

```python
print('Ex4')
#AR
T1 = 20 + 273.15#Kelvin
P1 = 100 #kpa
mponto = 0.025 #kg/seg
D1 = 0.01 #m
Wponto = -3.5 #Kw
T2 = 50 + 273.15#Kelvin
P2 = 650 #kpa
#ar sai sem e cinética
#Contudo, a energia cinética do ar que entra no compressor não pode ser desprezada
Vt = 1.5 #m3
Pi = 100 #kpa
Tt = 25 + 273.15#Kelvin estável, troca calor
Cv = 0.717
Cp = 1.005
R = Cp - Cv
K = Cp/Cv
Tamb = 25 + 273.15#Kelvin
A_entrada = np.pi *(D1**2)/4
dens1 = P1/(R*T1)
Vol_ponto = mponto / dens1
Vel_entrada = Vol_ponto/A_entrada
#a) a taxa de transferência de calor para o compressor;

Qponto =  - Wponto + mponto*(Cp*(T1-T2)) + (mponto*Vel_entrada**2/2)/1000
Qponto *= -1
print('A. Q ponto compressor: ', "{:.4f}".format(Qponto), 'KW/Kg')

#b) a pressão do ar no tanque após 200 segundos de operação;

mitanque = (Pi*Vt)/(R*Tt)
mentra = 200*mponto
mfinalt = mitanque + mentra
Pfinalt = (mfinalt*R*Tt)/Vt

print('B. Pressão apos 200 seg: ', "{:.4f}".format(Pfinalt), 'Kpa')

#c) a transferência de calor total do tanque para o ambiente durante os primeiros 200 s de funcionamento;
Qb = Qponto*200
Qt = symbols('Qt')
Eientr = Cp * T1 + (Vel_entrada**2/2)/1000

Wb = Wponto*200
DeltaEe = mentra * Cv * Tt
DeltaEd = Qb + Qt - Wb + mentra*(Eientr)
Eq = DeltaEd - DeltaEe
Qt = solve(Eq,Qt)[0]

print('C. A transferencia de calor no tanque é: ', "{:.4f}".format(Qt), 'KJ')

#d) a entropia gerada na válvula e tanque durante os primeiros 200 s de funcionamento;

Sger = symbols('Sger')

Santesb = 0 #interpolado pela tabela
Sdpsb = Santesb + Cp*log(T2/T1) -R* log(P2/P1)
Stcheio = Santesb + Cp*log(Tt/T1) -R* log(Pfinalt/P1)
Stvazio = Santesb + Cp*log(Tt/T1) -R* log(Pi/P1)

DeltaSe = Stcheio*mfinalt - Stvazio*mitanque
Se = Sdpsb
DeltaSd = Qt/Tt + mentra*Se + Sger

Eq = DeltaSd - DeltaSe
Sger = solve(Eq, Sger)[0]
print('D. Entropia gerada dps do compressor em 200s: ', "{:.4f}".format(Sger), 'Kj')

SgerB = Qponto*200/Tamb + mentra*Santesb - mentra*Sdpsb
SgerB *= -1

# Sgerpontob = Qponto/Tamb + mponto*Santesb - mponto*Sdpsb
# Sgerpontob *= -1
# SgerB = Sgerpontob*200

SLiq = Sger + SgerB

print('E. Entropia liq gerada: ', "{:.4f}".format(SLiq), 'KJ')
```

```python

```


```python
#we need to connect to a GUI event loop. This tells IPython where (and how) to display plots.
%matplotlib inline
from matplotlib import pyplot as plt
import numpy as np
import math
```

```python
#given gamma1 and theta
#taking inputs
N = int(input("Enter N( the total number of HN nodes)"))

E1 = float(input("Enter E1(initial energy)"))

#taking constants
alpha = float(input("Enter alpha"))#singh has taken this as 0.5
#beta = input("Enter beta")
gamma1 = float(input("Enter gamma"))#intial gamma will be given
theta = float(input("Enter theta"))#should validate a equation

print(N,E1,alpha,gamma1,theta)
#setting parameters are given based 4.2 section setting parameters
```

    Enter N( the total number of HN nodes)2000
    Enter E1(initial energy)0.5
    Enter alpha2
    Enter gamma0.4
    Enter theta0.025
    2000 0.5 2.0 0.4 0.025

```python
#assume i need 6 level heterogenity
#so now beta = gamma7
#finding gamma values
n =5
gammavalues = [gamma1]
#getting gamma values
for i in range(1,n+1):
    #gammai = gammai-1 - 2*theta
    gammavalues.append(round(gammavalues[i-1] - 2*theta,3))

print(gammavalues)
```

    [0.4, 0.35, 0.3, 0.25, 0.2, 0.15]

```python
#number of nodes in each level
beta = gammavalues[len(gammavalues)-1]
gammavalues[:] = gammavalues[:len(gammavalues)-1]

print(gammavalues)

#https://docs.sympy.org/latest/modules/solvers/solvers.html#systems-of-polynomial-equations

```

    [0.4, 0.35, 0.3, 0.25, 0.2]

```python
from sympy.solvers import solve
from sympy import Symbol
beta = Symbol('x',positive=True)

f =1
for i in range(n-1,-1,-1):
    f = f*(beta-gammavalues[i])
    f= f+1

f = f-2
print(f)
beta = solve(f, beta)
print(beta)

# beta1 = Symbol('x',positive=True)
# f1 = (1+(beta1-gammavalues[5]))
# f1 = ((beta1-gammavalues[4])*f1)+1
# f1 = ((beta1-gammavalues[3])*f1)+1
# f1 = ((beta1-gammavalues[2])*f1)+1
# f1 = ((beta1-gammavalues[1])*f1)+1
# f1 = ((beta1-gammavalues[0])*f1)
# f1 = f1-1
# print(f1)
# print(solve(f1, beta1))
```

    (x - 0.4)*((x - 0.35)*((x - 0.3)*((x - 0.25)*(x + 0.8) + 1) + 1) + 1) - 1
    [0.870518537398139]

```python
int(N*(beta[0]-gamma1))
# levelnodes = []
# for i in range(1,n+1):
#     nodes = N
#     for j in range(0,i):
#         nodes = nodes*(beta-gammavalues[j])
#     levelnodes.append(nodes)

#     print(levelnodes)
```

    941

```python

```


# Limit order book (LOB) simulator emulation

We shall build some stochastic emulators for the LOB simulation we built in the other notebook. The key part will be to emulate the mid price and spread time series well enough to reconstruct the LOB simulation.

```python
# Standard imports
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

# Get the class structures
import sys
sys.path.append("/Code/lobsem/src/")
from lobsem import lobsem
from agents import agentens
```

## Emulator formalism

In this section we'll introduce a Markovian approximation formalism for discussing ways in which we shall seek to emulate the full LOB simulation.

Let us denote the complete set of volumes for both bid $B_p\equiv\sum_{i=0}^{N_{\rm pr}}B_{pi}$ and ask $A_p\equiv\sum_{i=0}^{N_{\rm pr}}A_{pi}$ for all liquidity providing agents and at all $p$ prices, i.e., ${\cal V} = \{ \dots, B_{pi}, \dots, A_{pi}, \dots\}$. Using this notation, the overall LOB master equation for the distribution over volumes $P({\cal V}, t)$ is

$$
\begin{align}
\frac{{\rm d}}{{\rm d}t}P({\cal V}, t) &= \sum_{p=\theta}^\infty \sum_{i=1}^{N_{\rm pr}}\bigg[ \sum^\infty_{S=1}\lambda^B_{p}({\cal V}_{B_p:B_p-S}, t) L_{i} (S\vert B_{p}-S) P({\cal V}_{B_p:B_p-S}, t) - \lambda^B_{p}({\cal V}, t) P({\cal V}, t)\bigg] \\
& + \sum_{p=\theta}^\infty \sum_{i=1}^{N_{\rm pr}}\bigg[ \sum^\infty_{S=1}\lambda^A_{p}({\cal V}_{A_p:A_p-S}, t) L_{i} (S\vert A_{p}-S) P({\cal V}_{A_p:A_p-S}, t) - \lambda^A_{p}({\cal V}, t) P({\cal V}, t) \bigg] \\
& + \sum_{p=\theta}^\infty \sum_{i=1}^{N_{\rm ta}}\bigg[ \sum^\infty_{S=1}\mu^B_{pi}({\cal V}_{A_p:A_{p}+S}, t) M_{i} (S\vert {\cal V}_{A_p:A_p+S}, t) P({\cal V}_{A_p:A_p+S}, t) - \mu^B_{pi}({\cal V}, t) P({\cal V}, t)\bigg] \\
& + \sum_{p=\theta}^\infty \sum_{i=1}^{N_{\rm ta}}\bigg[ \sum^\infty_{S=1}\mu^A_{pi}({\cal V}_{B_p:B_{p}+S}, t) M_{i} (S\vert {\cal V}_{B_p:B_{p}+S}, t) P({\cal V}_{B_p:B_{p}+S}, t) - \mu^A_{pi}({\cal V}, t) P({\cal V}, t) \bigg] \\
& + \sum_{p=\theta}^\infty \sum_{i=1}^{N_{\rm pr}}\bigg[ (B_{p}+1)\nu^B P({\cal V}_{B_p:B_{p}+1}, t) - B_{p}\nu^B P({\cal V}, t)\bigg] \\
& + \sum_{p=\theta}^\infty \sum_{i=1}^{N_{\rm pr}}\bigg[ (A_{p}+1)\nu^A P({\cal V}_{A_p:A_{p}+1}, t) - A_{p}\nu^A P({\cal V}, t)\bigg] \,,
\end{align}
$$

where in the above relation we have used the notation ${\cal V}_{X:Y}$ to denote the set ${\cal V}$ where the element $X$ has been replaced with $Y$. The limit order rates for each liquidity-providing agent are given by

$$
\begin{align}
\lambda^B_{p}({\cal V}, t) &= \sum_{\delta b_-\in \{0, 1\}}(1+\alpha \delta b_-)P(\delta b_-\vert {\cal V}, t) e^{-\frac{d}{2}[a({\cal V})+b({\cal V})-2p]}\lambda^B \mathbb{1}_{a({\cal V})+b({\cal V})\geq 2p} \\
\lambda^A_{p}({\cal V}, t) &= \sum_{\delta a_+\in \{0, 1\}}(1+\alpha \delta a_+)P(\delta a_+\vert {\cal V}, t) e^{-\frac{d}{2}[2p-a({\cal V})-b({\cal V})]}\lambda^A \mathbb{1}_{2p\geq a({\cal V})+b({\cal V})} \,,
\end{align}
$$

where the distribution $P(\delta b_-\vert {\cal V}, t)$ denotes the conditional probability that the best bid price decreased ($\delta b_-=1$) or not ($\delta b_-=0$) in the last transaction of the market given the current state $({\cal V},t)$. Similarly, the distribution $P(\delta a_+\vert {\cal V}, t)$ denotes the conditional probability that the best ask price _increased_ ($\delta a_+=1$) or not ($\delta a_+=0$) in the last transaction of the market given the current state $({\cal V},t)$.

The market order rates for each liquidity-taking agent are given by

$$
\begin{align}
\mu^B_{pi}({\cal V}, t) &= \sum_{\epsilon_i\in\{-1, 1\}}(1+\epsilon_i)P_i(\epsilon_i\vert {\cal V}, t)\mu^B \mathbb{1}_{a({\cal V})=p} \\
\mu^A_{pi}({\cal V}, t) &= \sum_{\epsilon_i\in\{-1, 1\}}(1-\epsilon_i)P_i(\epsilon_i\vert {\cal V}, t)\mu^A \mathbb{1}_{b({\cal V})=p} \,.
\end{align}
$$

```python

```

## Coupled queue emulation: Hawkes kernel-based transition probabilities

Following the model structure ideas of [Blanc, Donier & Bouchaud (2015)](https://doi.org/10.1080/14697688.2016.1193215) design and build a Hawkes process emulator of the market dynamics above and use it to infer the process. **So let's try to build a model which compresses** $({\cal V},t)\rightarrow (\dots, B_p, \dots, A_p, \dots, t)$ and then infer all of the agent-averaged transition probabilities with Hawkes-style kernels to reconstruct the simulator.

Referring back to the simulator graph

we are essentially trying to compress the simulation such that the blue-shaded components are no longer explicit.

```python

```

## Price emulation: doubly-stochastic Gaussian processes

So let's now try to build a model which compresses $({\cal V},t)\rightarrow (b, a, t)$. To do this, we will need to make a few approximations to the expressions in the previous section in order to gain some tractability for designing the emulator. Using the processes described in the $P({\cal V},t)$ master equation, let us now write down an approximate master equation for $P(b, a, t)$ which assumes all price movements take value $\theta$ (we can investigate the validity of this assumption with respect to the full LOB simulation later)

$$
\begin{align}
&\frac{{\rm d}}{{\rm d}t}P(b, a, t) \simeq \\
& \sum_{p=b}^\infty \sum_{i=1}^{N_{\rm pr}} \bigg[ \tilde{\lambda}^B_{p}(b-\theta, a, t)J (\theta \vert b-\theta, a, t)P(b-\theta, a, t) - \tilde{\lambda}^B_{p}(b, a, t)P(b, a, t) \bigg] \\
& + \sum_{p=\theta}^a \sum_{i=1}^{N_{\rm pr}} \bigg[ \tilde{\lambda}^A_{p}(b, a+\theta, t)J (\theta \vert b, a+\theta, t)P(b, a+\theta, t) -  \tilde{\lambda}^A_{p}(b, a, t)P(b, a, t) \bigg] \\
& + \sum_{i=1}^{N_{\rm ta}} \bigg[ \tilde{\mu}^B_{(a-\theta )i}(b, a-\theta, t) \tilde{M}_{i} (A_{a-\theta}\vert b, a-\theta, t) J (\theta \vert b, a-\theta, t) P(b, a-\theta, t) - \tilde{\mu}^B_{ai}(b, a, t) \tilde{M}_{i} (A_{a}\vert b, a, t)P(b, a, t) \bigg] \\
& + \sum_{i=1}^{N_{\rm ta}} \bigg[ \tilde{\mu}^A_{(b+\theta )i}(b+\theta, a, t) \tilde{M}_{i} (B_{b+\theta}\vert b+\theta, a, t) J (\theta \vert b+\theta, a, t) P(b+\theta, a, t) - \tilde{\mu}^A_{bi}(b, a, t) \tilde{M}_{i} (B_b\vert b, a, t)P(b, a, t) \bigg] \\
& + \sum_{i=1}^{N_{\rm pr}} \nu^A  \bigg[ J (\theta \vert b, a-\theta, t) P(b, a-\theta, t) - P(b, a, t) \bigg]  + \sum_{i=1}^{N_{\rm pr}} \nu^B \bigg[ J (\theta \vert b+\theta, a, t)P(b+\theta, a, t) - P(b, a, t) \bigg] \,,
\end{align}
$$

where $J (\theta \vert b, a, t)$ is the conditional distribution over price jump size $\theta$ triggered by any individual market event (one could potentially distinguish between types of triggering events, however we shall attempt to approximate this as a single overall distribution here).

From the equation above we can quickly ascertain that the means and square-expecations of the marginal distributions over $b$ and $a$ evolve according to the equations

$$
\begin{align}
\frac{{\rm d}}{{\rm d}t}{\rm E}_t(b) &\simeq \sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{p=b}^\infty \sum_{i=1}^{N_{\rm pr}} {\rm E} (\theta \vert b, a, t) \tilde{\lambda}^B_{p}(b, a, t)P(b, a, t) - \sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{i=1}^{N_{\rm ta}} {\rm E} (\theta \vert b, a, t)\tilde{\mu}^A_{bi}(b, a, t) \tilde{M}_{i} (B_{b}\vert b, a, t)P(b, a, t) - \nu^B{\rm E}_t(\theta)  \\
\frac{{\rm d}}{{\rm d}t}{\rm E}_t(a) &\simeq \sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{i=1}^{N_{\rm ta}} {\rm E} (\theta \vert b, a, t)\tilde{\mu}^B_{ai}(b, a, t) \tilde{M}_{i} (A_{a}\vert b, a, t)P(b, a, t) + \nu^A{\rm E}_t(\theta) -\sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{p=a}^\theta \sum_{i=1}^{N_{\rm pr}} {\rm E} (\theta \vert b, a, t) \tilde{\lambda}^A_{p}(b, a, t)P(b, a, t) \\
\frac{{\rm d}}{{\rm d}t}{\rm E}_t(b^2) &\simeq \sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{p=b}^\infty \sum_{i=1}^{N_{\rm pr}} \big[ 2b{\rm E} (\theta \vert b, a, t) + {\rm E} (\theta^2 \vert b, a, t) \big] \tilde{\lambda}^B_{p}(b, a, t)P(b, a, t) \\
&- \sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{i=1}^{N_{\rm ta}} \big[ 2b{\rm E}(\theta \vert b, a, t) - {\rm E} (\theta^2 \vert b, a, t) \big] \tilde{\mu}^A_{bi}(b, a, t) \tilde{M}_{i} (B_{b}\vert b, a, t)P(b, a, t) - \big[ 2{\rm E}_t(b\theta) - {\rm E}_t(\theta^2) \big] \nu^B  \\
\frac{{\rm d}}{{\rm d}t}{\rm E}_t(a^2) &\simeq \sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{i=1}^{N_{\rm ta}} \big[ 2a{\rm E} (\theta \vert b, a, t) + {\rm E} (\theta^2 \vert b, a, t) \big] \tilde{\mu}^B_{ai}(b, a, t) \tilde{M}_{i} (A_{a}\vert b, a, t)P(b, a, t) \\
&+   \big[ 2{\rm E}_t(a\theta) - {\rm E}_t(\theta^2)  \big] \nu^A -\sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{p=a}^\theta \sum_{i=1}^{N_{\rm pr}} \big[ 2a{\rm E} (\theta \vert b, a, t) - {\rm E} (\theta^2 \vert b, a, t)\big] \tilde{\lambda}^A_{p}(b, a, t)P(b, a, t) \\
\frac{{\rm d}}{{\rm d}t}{\rm E}_t(ba) &\simeq \sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{p=b}^\infty \sum_{i=1}^{N_{\rm pr}} a{\rm E} (\theta \vert b, a, t) \tilde{\lambda}^B_{p}(b, a, t)P(b, a, t) - \sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{i=1}^{N_{\rm ta}} a{\rm E} (\theta \vert b, a, t) \tilde{\mu}^A_{bi}(b, a, t) \tilde{M}_{i} (B_{b}\vert b, a, t)P(b, a, t) - \nu^B {\rm E}_t(a\theta) \\
&+ \sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{i=1}^{N_{\rm ta}} b{\rm E} (\theta \vert b, a, t)\tilde{\mu}^B_{ai}(b, a, t) \tilde{M}_{i} (A_{a}\vert b, a, t)P(b, a, t) + \nu^A {\rm E}_t(b\theta) -\sum_{b=\theta}^\infty \sum_{a=\theta}^\infty \sum_{p=a}^\theta \sum_{i=1}^{N_{\rm pr}} b{\rm E} (\theta \vert b, a, t) \tilde{\lambda}^A_{p}(b, a, t)P(b, a, t) \,.
\end{align}
$$

**We can replace $P(b, a, t)$ for a Gaussian approximation and then emulate the price-dependent rates x jump sizes to solve this problem!**

**Note that we can also calculate the mid price easily from $P(b,a,t)$ using:** $2m(t)=a(t)+b(t)$

```python
nstockdim = 1
nreals = 1000
S0 = np.tensordot(
    np.random.normal(10.0, 0.1, size=nstockdim)**2.0,
    np.ones(nreals),
    axes=0,
)
vol0 = np.tensordot(
    np.random.normal(0.1, 0.1, size=nstockdim)**2.0,
    np.ones(nreals),
    axes=0,
)
x0 = np.append(S0, vol0, axis=0)
deltat = 0.01
T = 10.0
```

The Heston model for stochastic volatility is

$$
\begin{align}
{\rm d}S_t &= \mu S_t {\rm d}t + \sqrt{\nu_t}S_t{\rm d}W^S_t \\
{\rm d}\nu_t &= \theta (\omega -\nu_t) {\rm d}t + \xi \sqrt{\nu_t}{\rm d}W^\nu_t \\
{\rm d}W^\nu_t{\rm d}W^S_t &= \rho {\rm d}t \,.
\end{align}
$$

```python
# Heston model terms
mu = 0.01
theta = 0.1
omega = 0.5
xi = 0.01
rho = 0.1

def drift(x, t):
    drft = np.zeros_like(x)
    S, vol = x[:nstockdim], x[nstockdim:]
    drft[:nstockdim, :] = mu * S
    drft[nstockdim:, :] = theta * (omega - vol)
    return drft

def diffusion(x, t):
    diff = np.zeros_like(x)
    S, vol = x[:nstockdim], x[nstockdim:]
    diff[:nstockdim, :] = (vol ** 0.5) * S
    diff[nstockdim:, :] = xi * (vol ** 0.5)
    return diff
```

```python
mls = mlsolver(drift, diffusion)
mls.noise_covmat[0, 1] = rho
mls.noise_covmat[1, 0] = rho
mls.realisations = nreals
mls.ens._snapshot_rate = 5
mls.setup(x0, deltat)
mls.run(T)
```

```python
plt.plot(
    mls.ens.dataDict['CovMatrix']['t'],
    np.asarray(mls.ens.dataDict['CovMatrix']['x'])[:,0,0]
)
plt.plot(
    mls.ens.dataDict['CovMatrix']['t'],
    np.asarray(mls.ens.dataDict['CovMatrix']['x'])[:,1,1]
)
axes = plt.gca()
plt.show()
```

```python

```


# Spectral Estimation of Random Signals

*This jupyter notebook is part of a [collection of notebooks](../index.ipynb) on various topics of Digital Signal Processing. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Introduction

In the preceding sections various statistical measures have been introduced to characterize random processes and signals. For instance, the probability density function (PDF) $p_x(\theta)$, the mean value $\mu_x$, the auto-correlation function (ACF) $\varphi_{xx}[\kappa]$ and its Fourier transformation, the power spectral density (PSD) $\Phi_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$. For many random processes whose internal structure is known, these measures can be derived in closed-form. However, for practical random signals measures of interest have to be estimated from a limited number of samples. These estimated quantities can e.g. be used to fit a parametric model of the random process or as parameters in algorithms.

### Problem Statement

The estimation of the spectral properties of a random signal is of special interest for spectral analysis. The discrete Fourier transform (DFT) of a random signal is also random. It is not very well suited to gain insights into the average spectral structure of a random signal. We aim at estimating the PSD $\hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ of a wide-sense stationary (WSS) and ergodic process from a limited number of samples. This is known as [*spectral (density) estimation*](https://en.wikipedia.org/wiki/Spectral_density_estimation). Many techniques have been developed for this purpose. They can be classified into

1. non-parametric and
2. parametric

techniques. Non-parametric techniques estimate the PSD of the random signal without assuming any particular structure for the generating random process. In contrary, parametric techniques assume that the generating random process can be modeled by a few parameters. Their aim is to estimate these parameters in order to characterize the spectral properties of the random signal.

### Evaluation

Various measures have been introduced in order to evaluate the performance of a particular estimation technique. The estimate $\hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ can be regarded as a random signal itself. The performance of an estimator is therefore evaluated in a statistical sense. For the PSD, the following metrics are of interest

#### Bias

The [bias of an estimator](https://en.wikipedia.org/wiki/Estimator#Bias)

\begin{equation}
b_{\hat{\Phi}_{xx}}
= E\{ \hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) - \Phi_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})\}
= E\{ \hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) \} - \Phi_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})
\end{equation}

quantifies the difference between the estimated $\hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ and the true $\Phi_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$. An estimator is
* biased if $b_{\hat{\Phi}_{xx}} \neq 0$ and
* bias-free if $b_{\hat{\Phi}_{xx}} = 0$.

#### Variance

The [variance of an estimator](https://en.wikipedia.org/wiki/Estimator#Variance)

\begin{equation}
\sigma^2_{\hat{\Phi}_{xx}} = E \left\{ \left(\hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) - E\{ \hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})\} \right)^2 \right\}
\end{equation}

quantifies its quadratic deviation from its mean value $E\{ \hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})\}$.

#### Consistency

A [consistent estimator](https://en.wikipedia.org/wiki/Estimator#Consistency) is an estimator for which the following conditions hold for a large number $N$ of samples:

1. the estimator is unbiased
    $$ \lim_{N \to \infty} b_{\hat{\Phi}_{xx}} = 0 $$

2. its variance converges towards zero
    $$ \lim_{N \to \infty} \sigma^2_{\hat{\Phi}_{xx}} = 0 $$

3. it converges in probability to the true $\Phi_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$
    $$ \lim_{N \to \infty} \Pr \left\{ | \hat{\Phi}_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) -  \Phi_{xx}(\mathrm{e}^{\,\mathrm{j}\,\Omega})| > \alpha \right\} = 0$$
    where $\alpha > 0$ denotes a (small) constant.

The last condition ensures that a consistent estimator does not generate outliers. Consistency is a desired property of an estimator. It ensures that if the number of samples $N$ increases towards infinity, the resulting estimates converges towards the true PSD.

#### Example - Discrete Fourier transform of sample functions

The following example computes and plots the magnitude of the discrete Fourier transform (DFT) $|X_n[\mu]|$ of an ensemble of random signals $x_n[k]$. In the plot, each color denotes one sample function and $\Omega[\mu] = \frac{2 \pi}{N} \mu$ where $N$ denotes the length of the sample function. The magnitude spectra are plotted as continuous signals for ease of illustration.

```
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import scipy.signal as sig

N = 128  # number of samples
M = 5  # number of sample functions

# generate random signal
np.random.seed(1)
x = np.random.normal(size=(M, N))
h = sig.firwin2(N, [0, .4, .42, .65, .67, 1], [0, 0, 1, 1, 0, 0])
x = [np.convolve(xi, h, mode='same') for xi in x]

# DFT of signal
X = np.fft.rfft(x, axis=1)
Om = np.linspace(0, np.pi, X.shape[1])

# plot signal and its spectrum
plt.figure(figsize=(10,4))
plt.plot(Om, np.abs(X.T))
plt.title('Magnitude spectrum')
plt.xlabel(r'$\Omega[\mu]$')
plt.ylabel(r'$|X[\mu]|$')
plt.axis([0, np.pi, 0, 30]);

# The original example had 256 samples.
# I cut the number of samples in half, tripled it, etc.
# Saw various ouptuts.
```

**Exercise**

* Increase the number `N` of samples. What changes? What does not change with respect to the evaluation criteria introduced above?
* Is the DFT of a single sample function a consistent estimator for the spectral properties of a random process?

Solution: Increasing the number of samples does only lead to an increase in the number of discrete frequencies $\mu$. The amplitude of the fluctuations (variance) of the spectra within $1.3 < \Omega < 2$ is not decreased when increasing the number of samples. The DFT of a single sample function is hence not a consistent estimator since at least the second condition is violated.

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Digital Signal Processing - Lecture notes featuring computational examples, 2016-2018*.