```python
%pylab inline
```

    Populating the interactive namespace from numpy and matplotlib

    c:\users\isomorphism\appdata\local\programs\python\python36\lib\site-packages\IPython\core\magics\pylab.py:160: UserWarning: pylab import has clobbered these variables: ['f']
    `%matplotlib` prevents importing * from pylab and numpy
      "\n`%matplotlib` prevents importing * from pylab and numpy"

# Probability Theory

## Week 1

**Topics:** Random sampling, permutations, combinations.

**Problems:** 3.1, 3.3, 3.4, 3.5, 3.6, additional problem set 1.

### Random sampling

Taking a random sample can be done in different ways:

||With replacement|Without replacement|
|---|---|---|
|**With order**|$n^k$|$n!, \quad k \leq n$|
|**Without order**|$\begin{pmatrix}n+k-1\\k\end{pmatrix}$|$\begin{pmatrix}n\\k\end{pmatrix}$|

This leads to four different cases which will be addressed seperately.

### Permutations

The general formula for permutations is:

$$ _nP_k = \dfrac{n!}{(n-k)!}$$

#### With replacement

#### Without replacement

### Combinations

The general formula for combinations is (which can also be written as the binomial coefficient):

$$ _nC_k=\dfrac{n!}{k!(n-k)!} = \begin{pmatrix}n\\k\end{pmatrix}$$

#### With replacement

#### Without replacement

## Week 2

**Topics**: Probability definitions, probability rules, grid chart.

**Problems:** 3.7, 3.8, 3.10, additional problem set 2.

### Probability definitions

#### Probability experiment

There are different examples of probability experiments:

1. One throw with a dice
2. Two throws with a dice
3. One throw with a coin
4. Pulling two cards from a deck of cards

#### Sample space

The sample space $\Omega$ are all the possible outcomes for a probability experiment. For the above given examples this gives:

1. $\Omega=\{1,2,3,4,5,6\}$
2. $\Omega=\{(a,b):a,b \in \{1,2,3,4,5,6\}\}$
3. $\Omega=\{H,T\}$
4. $\Omega=\{(a,b):a,b \in \{1,\ldots,52\}\}$

#### Event

An event is a subset of $\Omega$.

1. Throwing an even number, $A=\{2,4,6\}$
2. Throwing the same dices, $A=\{(1,1),(2,2),\ldots,(6,6)\}$
3. Throwing heads, $A=\{H\}$
4. Picking a heart A ($HA$) and club 2 ($C2$), $A=\{(HA,C2),(C2,HA)\}$

#### Elementary event

An elementary event $\omega$ (also called atomic event) is exactly one element from $\Omega$.

For example (1) all the elementary events are: $\{1\}$, $\{2\}$, $\{3\}$, $\{4\}$, $\{5\}$, $\{6\}$.

#### Probability (axiomatic definition)

For the probability function $P$ the following three axioms hold:

1. $P(S)=1$
2. $0\leq P(A) \leq 1$
3. $P(A \cap B) = P(A)+P(B)-P(A \cap B)$, where $P(A \cap B) = 0$ if and only if $A \cap B = \emptyset$.

#### Probability space

A probability space consists of the following:

1. A sample space $\Omega$ which are all the possible outcomes for the experiment.
2. A set of events, where each event is a set containing zero or more outcomes.
3. The assigment of probabilities to the events; that is, a function $P$ from events to probabilities.

With this we can determine the probability for an event $A$:

$$P(A) = \dfrac{n_a}{n_\Omega}$$

Where $n_a$ are the favorable outcomes and $n_\Omega$ are all the possible outcomes.

### Probability rules

Now we establish a few rules for probabilites. Later on when we cover conditional probilities we will add a few more rules to the list.

#### Sum rule

$P(A \cup B) = P(A)+P(B)-P(A \cap B)$ where $P(A \cap B) = 0$ if and only if $A \cap B = \emptyset$.

#### Complement rule

We can define this as $P(A) + P(\bar{A}) = P(S) = 1$. We use this to infer that $P(A) = 1-P(\bar{A})$. This is particularly useful because most of the times it is a lot easier to figure out what $P(\bar{A})$ is, and then use the complement rule to determine the probability of $P(A)$.

### Grid chart

A grid chart is useful to visualize conditions.

For example, suppose we want to throw $2$ dices, and we multiply the outcome. What is the probability that the result is greater than $20$?

Using the grid chart it is easy to see that:

$$P(x>20) = \frac{6}{6^2} = \frac{1}{6}$$

## Week 3

**Topics:** Conditional probability, independence, product rule, sampling with replacement, sampling without replacement, probability tree.

**Problems:** 3.2, 3.12, 3.13, 3.14, 3.15, 3.16, additional problem set 3.

### Conditional probability

A conditional probability gives more information about the possible outcome space, which is defined as:

$$P(A|B) = \dfrac{P(A \cap B)}{P(B)} ,\quad P(B)>0$$

### Indepence

If $P(A|B)=P(A)$ then the events $A$ and $B$ are independent.

### Product rule

The general product rule is:

$$ P(A \cap B) = P(A|B) \cdot P(B) = P(B|A) \cdot P(A) $$

In the case that $A$ and $B$ are independent, we use:

$$ P(A \cap B) = P(A) \cdot P(B)$$

### Probability rules

With the addition of above rules, we now have the following rules for probabilities:

1. $P(\Omega)=1$, sample space has a probability of 1.
2. $P(\emptyset)=0$, an empty set has a probability of 0.
3. $P(A)=1-P(\bar{A})$, complement rule.
4. $P(A\cup B)=P(A)+P(B)-P(A\cap B)$, sum rule.
5. $P(A\cup B)=P(A)+P(B)$, if and only if $A\cap B=\emptyset$.
6. $P(A\cap B)=P(A|B)\cdot P(B)$, product rule.
7. $P(A\cap B)=P(A)\cdot P(B)$, if and only if $A$ and $B$ are independent.

### Sampling with replacement

### Sampling without replacement

### Probability tree

## Week 4

**Topics:** Bayes' rule.

**Problems:** 3.9, 3.11, 3.17 till 3.31.

### Bayes' rule

#### Inference

When we have a cause and effect model where $A_i\xrightarrow[P(B|A_i)]{\text{cause-effect}}B$ is known, we can use Bayes' rule to infer $B\xleftarrow[P(A_i|B)]{\text{inference}}A_i$.

**General case**

The general use case for Bayes rule is:

$$ P(A|B) = \dfrac{P(A) \cdot P(B|A)}{P(A)\cdot P(B|A) + P(\bar{A})\cdot P(B|\bar{A})} $$

We can use this to infer $P(A|B)$ if only $P(B|A)$ is known.

**Definition**

Bayes' rule is defined as:

$$ P(A_i|B) = \dfrac{P(A_i)\cdot P(B|A_i)}{\sum\left[P(A_j)\cdot P(B|A_j)\right]}$$

such that the following holds:

1. $A_i \cap A_j = \emptyset $, the intersection must be disjunct.
2. $\cup A_i = \Omega$, the union of $A$ is $\Omega$.

## Week 5

**Topics:** Discrete stochastic variable, discrete probability function, cumulative distribution function, expected value, variance for a discrete stochastic variable.

**Problems:** 4.1, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8

### Stochastic variables

**Definition**

Given a sample space $\Omega$ and a function $\underline{k}$ such that $\underline{k}:\Omega\rightarrow\mathbb{R}$. Where the range is $\underline{R}_k \subset \mathbb{R}$. In this context $\underline{k}$ is the stochastic variable (or probability variable). With the following two rules:

1. $f(k) \geq 0$ for $\forall k$
2. $\sum f(k) = 1, \quad \left(\sum\limits_{k\in\underline{k}}(\underline{k}=k)=1\right)$

**Example**

Given a probability experiment: throwing with a dice.

We define $\underline{k}$ as the squared outcome of the throw. The mapping of the function $\underline{k}$ is defined as:

$$\Omega=\underbrace{\{1,2,3,4,5,6\}}_{\text{domain}} \xrightarrow{\underline{k}} \underbrace{\{1,4,9,16,25,36\}}_{\text{range}} = \underline{R}_k \subset \mathbb{R}$$

### Discrete probability distribution

The probability distribution is defined as $f(k)=P(\underline{k}=k)$ such that $k\in\underline{R}_k$. For the above given example this yields:

$$ f(1)=P(\underline{k}=1)=\dfrac{1}{6} \\ f(4)=P(\underline{k}=4)=\dfrac{1}{6} \\ \vdots \\ f(36)=P(\underline{k}=36)=\dfrac{1}{6}$$

**Code**

```python
def f(k):
    r = [1,4,9,16,25,36]
    if k in r: return 1/len(r)
    return 0
```

Plotting this function gives a pin bar graph with a vertical line at the value when there is a probability defined. All the other values are not defined because this is a discrete function.

```python
points = np.arange(0,37,0.01)
plot(points, [f(x) for x in points], c='b', lw=2);
```

### Cumulative distribution function

The cumulative distribution function $F(k)=P(\underline{k}\leq k)$ for the example above yields:

$$ F(1)=P(\underline{k}\leq1)=\dfrac{1}{6} \\ F(2)=P(\underline{k}\leq2)=\dfrac{1}{6} \\ F(4)=P(\underline{k}\leq4)=\dfrac{2}{6} \\ \vdots \\ F(36)=P(\underline{k}\leq36)=\dfrac{6}{6}=1$$

**Code**

```python
def F(k):
    x = 0
    r = [1,4,9,16,25,36]
    for i in r:
        if i <= k: x = i
    try: return (r.index(x)+1)/len(r)
    except: return 0
```

Plotting this function gives a staircase graph.

```python
points = np.arange(-10,50,0.01)
plot(points, [F(x) for x in points], c='b', lw=2);
```

### Expected value of a discrete stochastic variable

The expected value $E(\underline{k})$ is the weighted arithmetic mean from descriptive statistics but altered for a stochastic variable.

**Definition**

The formula for the expected value is:

$$ E(\underline{k})=\sum\limits_{k\in\underline{k}} k\cdot P(\underline{k}=k) $$

Notice that we do not divide by the sum of values since that sum would equal 1, hence it's omitted.

**Example**

The expected value for the example with a dice is:

$$E(\underline{k})=1 \cdot \dfrac{1}{6} + 4 \cdot \dfrac{1}{6} + 9 \cdot \dfrac{1}{6} + 16  \cdot \dfrac{1}{6} + 25 \cdot \dfrac{1}{6} + 36 \cdot \dfrac{1}{6} = \dfrac{91}{6} = 15 \dfrac{1}{6}$$

**Code**

```python
def E(r):
    return sum([x * 1/len(r) for x in r])
```

We can use this to easily determine the expected value for any range of $\underline{k}$ when there is a uniform distribution.

```python
expected = E([1,4,9,16,25,36])
expected
```

    15.166666666666668

Plotting $f(k)$, $F(k)$, and $E(\underline{k})$ yields:

```python
points = np.arange(0,50,0.01)
plot(points, [f(x) for x in points], c='lightblue', lw=2);
plot(points, [F(x) for x in points], c='b', lw=2);
scatter(expected, F(expected), c='r', lw=4);
```

### Variance of a discrete stochastic variable

The variance $\text{Var}(\underline{k})$ is the variance from descriptive statistics but is altered for a discrete stochastic variable.

**Definition**

The formula for the variance of a discrete stochastic variable is defined as:

$$ \text{Var}(\underline{k}) = \sum\limits_{k\in\underline{k}}\left(k-E(\underline{k})\right)^2\cdot P(\underline{k}=k_i) $$

Notice that we do not divide by the sum of probabilities since that sum would equal 1, hence it's omitted.

**Example**

An example for throwing a dice gives:

$$\text{Var}(\underline{k})= \left(1-\dfrac{91}{6}\right)^2 + \left(4-\dfrac{91}{6}\right)^2 + \ldots + \left(36-\dfrac{91}{6}\right)^2 = 149.14$$

**Standard deviation**

With the example given above, it is easy to determine the standard deviation, which is:

$$\sigma_{\underline{k}} = \sqrt{149.14} = 12.21$$

**Alternative method**

An alternative (preferred) method to calculate the variance of a discrete stochastic variable is:

$$\text{Var}(\underline{k})= E(\underline{k}^2)-\left[E(\underline{k})\right]^2$$

Proof:

We start with the definition:

$$\begin{align} &\sum\limits_{k\in\underline{k}}\left(k-E(\underline{k})\right)^2 \cdot P(\underline{k}=k) \\ = &\sum\limits_{k\in\underline{k}}\left(k^2 - 2k\cdot E(\underline{k})+\left[E(\underline{k})\right]^2 \right) \cdot P(\underline{k}=k) \\ = & \underbrace{\sum\limits_{k\in\underline{k}}k^2\cdot P(\underline{k}=k)}_{\text{def.}\ E(\underline{k}^2)}-2\sum\limits_{k\in\underline{k}}k\cdot E(\underline{k})\cdot P(\underline{k}=k)+\sum\limits_{k\in\underline{k}}\left[E(\underline{k})\right]^2\cdot P(\underline{k}=k) \\ = &E(\underline{k}^2) - 2\cdot E(\underline{k}) \cdot \underbrace{\sum\limits_{k\in\underline{k}} k \cdot P(\underline{k}=k)}_{\text{def.}\ E(\underline{k})} + \left[E(\underline{k})\right]^2 \cdot \underbrace{\sum\limits_{k\in\underline{k}} P(\underline{k}=k)}_{1} \\ = &E(\underline{k}^2)-2\cdot E(\underline{k})\cdot E(\underline{k})+\left[E(\underline{k})\right]^2 \\ = & E(\underline{k}^2) - \left[E(\underline{k})\right]^2 \end{align}$$

Which proves our formula.

**Code**

```python
def var(r):
    expected = E(r)
    return sum([(x-expected)**2*f(x) for x in r])
```

```python
var([1,4,9,16,25,36])
```

    149.13888888888886

```python
def var2(r):
    return E([x**2 for x in r]-E(r)**2)
```

```python
var2([1,4,9,16,25,36])
```

    149.13888888888886

```python
def stdev(r):
    return math.sqrt(var2(r))
```

```python
stdev([1,4,9,16,25,36])
```

    12.212243401148244

## Week 6

**Topics:** Rules for expected value and variance, summing stochastic variables, multi-dimensional stochastic variables.

**Problems:** 4.9 till 4.13, 4.20, 4.21, additional problem set 4.

### Applying linear transformations

When we apply a linear transformation of the form $\underline{m}=\alpha \underline{k} + A$ to our values, there are a few rules that apply to the expected value and variance that will help us. Instead of recalculating the expected value and/or variance we can use the observations below.

#### Multiplying with a scalar $\alpha$

When we multiply with a scalar $\alpha$, the expected value will also change by that factor:

$$E(\underline{m}) = E(\alpha\underline{k})=\alpha E(\underline{k})$$

If we apply a scalar $\alpha$ the variance will change with a factor $\alpha^2$:

$$\text{Var}(\underline{m})=\text{Var}(\alpha\underline{k})=\alpha^2\text{Var}(\underline{k})$$

Even if a negative value for $\alpha$ will be used, the standard deviation will remain positive, thus:

$$\sigma_{\underline{m}}=\sigma_{\alpha\underline{k}}=\sqrt{\alpha^2 \text{Var}(\underline{k})}=\ |\ \alpha \ |\ \sigma_\underline{k}$$

#### Adding a constant $A$

When we add a constant to the outcomes the expected value will also change by that constant value:

$$E(\underline{y}) = E(\underline{k} + A) = E(\underline{x})+A$$

If we shift all the values over a constant value $A$, the variance will not be affected because the spread between the variables will remain the same.

### Rules for expected value and variance

#### Sum rule for expected values

The expected value of the sum is the sum of the expected values:

$$ E(\underline{k}_{sum}) = E(\underline{k}_1) + E(\underline{k}_2) $$

#### Sum rule for variance

The variance of the sum is the sum of the variances:

$$ \text{Var}(\underline{k}_{sum}) = \text{Var}(\underline{k}_1) + \text{Var}(\underline{k}_2) $$

### Multidimensional stochastic variables

A multidimensional stochastic variable is defined as:

$$\begin{align}  f(k)&=P(\underline{k}=k),\ \text{any}\ m) \\&=\sum\limits_j P(\underline{k}=k, \underline{m}=m_j)\sum\limits_jp_{kj} \end{align}$$

## Week 7

### Populatiecorrelatiecoëfficiënt $\rho$

Om de populatiecorrelatiecoefficient van twee stochasten te berekenen volgen we de onderstaande stappen:

1. Bereken $E(\underline{x})$ en $E(\underline{y})$.
2. Bereken $E(\underline{x}^2)$ en $E(\underline{y}^2)$.
3. Bereken $\text{Var}(\underline{x})$ and $\text{Var}(\underline{y})$.
4. Bereken $E(\underline{x}\cdot\underline{y})$.
5. Bereken $\text{Cov}(\underline{x}, \underline{y})$.
6. Bereken $\rho(\underline{x}, \underline{y})$.

Waarbij $\text{Cov}(\underline{x},\underline{y})$ is gedefinieerd als:

$$ \text{Cov}(\underline{x},\underline{y}) = \sum\limits_{i=1}^r\sum\limits_{j=1}^k(x_i-\mu_\underline{x})(y_j-\mu_\underline{y})\cdot P(\underline{x}=x_i \land \underline{y} = y_j)$$

En net als bij Beschrijvende Statistiek, is $\rho$ gedefinieerd als:

$$ \rho = \frac{ \text{Cov}(\underline{x}, \underline{y}) }{\sigma_\underline{x} \cdot \sigma_\underline{y}} $$

### Simultane kansverdeling

### Onderling onafhankelijk

Twee stochasten zijn onderling onafhankelijk als:

$$ P(\underline{x}=x_i \land \underline{y}=y_i)=P(\underline{x}=x_i)\cdot P(\underline{y}=y_i)$$

voor alle getallen paren $(x_i,y_i)$.

```python

```


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

# Toggle cell visibility

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

# Hide the code completely

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

Toggle cell visibility here.

```python
%matplotlib notebook
import numpy as np
import control as control
import matplotlib.pyplot as plt
import ipywidgets as widgets
import scipy.signal as signal
continuous_update=False
```

## Risposta dei sistemi del primo ordine

La funzione di trasferimento del sistema del primo ordine selezionato è definita come

\begin{equation}
    \frac{K_p}{\tau_p s+1},
\end{equation}

dove $K_p$ e $\tau_p$ sono parametri del sistema.

La risposta del sistema dipende dal segnale di ingresso. In questo esempio vengono utilizzati la funzione gradino (trasformata di Laplace $\frac{1}{s}$), la funzione impulso unitario (trasformata di Laplace $1$), la funzione rampa unitaria (trasformata di Laplace $\frac{1}{s^2}$) e un segnale sinusoidale (trasformata di Laplace $\frac{1}{s^2+1}$) come segnali di ingresso.

Il grafico sottostante mostra il segnale di ingresso e il segnale di uscita corrispondente per i valori scelti dei parametri $K_p$ e $\tau_p$.

### Come usare questo notebook?

Testa le diverse funzioni di ingresso (gradino, impulso, rampa e sinusoide). Sposta gli sliders per modificare i valori di $K_p$ e $\tau_p$.

```python
# sinus, step, ramp, x^2, sqrt(x)
functionSelect = widgets.ToggleButtons(
    options=[('gradino unitario', 0), ('impulso unitario', 1), ('rampa unitaria', 2), ('sinusoide', 3)],
    description='Seleziona: ')

fig = plt.figure(num='Risposta del sistema del primo ordine')
fig.set_size_inches((9.8, 3))
fig.set_tight_layout(True)
f1 = fig.add_subplot(1, 1, 1)

f1.grid(which='both', axis='both', color='lightgray')

f1.set_xlabel('$t$ [s]')
f1.set_ylabel('input, output')

f1.axhline(0,Color='black',linewidth=0.5)
f1.axvline(0,Color='black',linewidth=0.5)

inputf, = f1.plot([],[])
responsef, = f1.plot([],[])
arrowf, = f1.plot([],[])

num_samples=2041

def create_draw_functions(Kp,taup,index):
    t=np.linspace(-0.1,5,num_samples)

    num=[Kp]
    den=[taup,1]
    Wsys=control.tf(num,den)

    global inputf,responsef, arrowf

    if index==0:
        yin=np.zeros(2041)
        yin[40:num_samples]=1
        tnew=np.linspace(0,5,2001)
        tout,yout=control.step_response(Wsys, T=tnew)
    elif index==1:
        yin=signal.unit_impulse(2001, 0)
        tnew=np.linspace(0,5,2001)
        t=tnew
        tout,yout=control.impulse_response(Wsys,tnew,X0=0)
    elif index==2:
        yin=np.zeros(num_samples)
        yin[40:num_samples]=np.linspace(0,5,2001)
        tnew=np.linspace(0,5,2001)
        tout,yout,xx=control.forced_response(Wsys,tnew,yin[40:])
    elif index==3:
        yin=np.sin(np.linspace(0,30,2001))
        tnew=np.linspace(0,30,2001)
        t=tnew
        tout,yout,xx=control.forced_response(Wsys,tnew,yin)

    f1.lines.remove(inputf)
    f1.lines.remove(responsef)
    f1.lines.remove(arrowf)

    inputf, = f1.plot(t,yin,color='C0',label='input')
    responsef, = f1.plot(tout,yout,color='C1',label='output')

    if index == 1:
        arrowf, = f1.plot([-0.1,0,0.1],[0.95,1,0.95],color='C0')
    else:
        arrowf, = f1.plot([],[])

    f1.legend()

    f1.relim()
    f1.relim()
    f1.autoscale_view()
    f1.autoscale_view()

Kp_slider = widgets.FloatSlider(value=1, min=0, max=2, step=0.1, description='$K_p$',
                          continuous_update=True, layout=widgets.Layout(width='auto', flex='5 5 auto'),readout_format='.1f')

taup_slider = widgets.FloatSlider(value=1, min=0, max=2, step=0.1, description='$\\tau_p$',
                          continuous_update=True, layout=widgets.Layout(width='auto', flex='5 5 auto'),readout_format='.1f')

input_data = widgets.interactive_output(create_draw_functions, {'Kp':Kp_slider,
                                                                'taup':taup_slider,
                                                                'index':functionSelect})

def update_sliders(index):
    global x0_slider

    Kpval = [1,1,1,1]
    Kp_slider.value = Kpval[index]
    taupval = [1,1,1,1]
    taup_slider.value = taupval[index]

input_data2 = widgets.interactive_output(update_sliders, {'index':functionSelect})

display(functionSelect)

display(Kp_slider,taup_slider,input_data)

# display(Markdown("The system can be represented as $f(x)=5$ for small excursions of x about x0."))
```

    <IPython.core.display.Javascript object>

    ToggleButtons(description='Seleziona: ', options=(('gradino unitario', 0), ('impulso unitario', 1), ('rampa un…

    FloatSlider(value=1.0, description='$K_p$', layout=Layout(flex='5 5 auto', width='auto'), max=2.0, readout_for…

    FloatSlider(value=1.0, description='$\\tau_p$', layout=Layout(flex='5 5 auto', width='auto'), max=2.0, readout…

    Output()


# Computação Científica com Julia

 * Computação científica
 * Julia
## Como são as coisas no IPT hoje?

 * Planilha eletrônica
 * Excel
 * Deus nos acuda!

# Será que isso é o suficiente???

 * Como detectar erros em planilhas?
 * Como reutilizar cálculos já feitos?
 * Como fazer algo mais complexo?
 * Como fazer **gráficos decentes**?

## O problema do Excel é mais um problema de cultura do que qualquer outra coisa!

# O que eu quero dizer com cultura:

 * Modelagem matemática de problemas se resume ao que se fazia 100 anos atrás.
 * Pouco reaproveitamento
 * Pouco compartilhamento de trabalho já realizado
 * Endeusamento do que já funciona
 * Pouca discussão e ossificação do conhecimento

## Exemplo do que já ocorreu no passado

 * Túnel de vento (tuninho) circa 1990 -  Mestrado do Nilson
 * Algor - escoamento potencial
 * STAN5 (ou 7 de acordo com o Nilson)
 * Modelo em escala reduzida
 * Medição com fio quente

# Minha opinião

 * Compreender o que ocorre numa bancada ou em um trabalho externo
 * Dar ao computador o seu lugar de direito: um escravo muito burro mas muito forte!
 * Muito do que nós fazemaos é software. **Engenharia de Software**
 * Conhecer o que se faz aqui dentro e fora
 * Compartilhar este conhecimento

# Ideal

 * Todas as bancadas modeladas
   * Modelos hidráulicos **e**
   * Modelos CFD
   * Conhecimento profundo do que ocorre de fato nas bancadas.
 * Um modelo das plantas em campo **antes** de medir.
 * Cada área ter um conjunto de ferramentas bem conhecidas
 * Ser "piloto" de software não vai nos levar longe

# Computação Científica

 * Métodos básicos
   * Sistemas lineares
   * Aproximação
   * Equações diferenciais
   * Sistemas não lineares
 * Mais importante do que o básico, é saber como usar isso.

 * Métodos numérico rápidos e eficientes
   * Muito trabalhoso
   * Muito difícil
   * Coisa de profissa.

# Porque Julia

1. Ambiente interativo
   * Muito melhor que FORTRAN ou C/C++/Pascal/Java
   * Parecido com R/Python/Matlab/Mathematica/Scilab
2. Desempenho
   * Consegue desempenho de C/FORTRAN
   * Mas não precisa vetorizar
   * Praticamente tudo é implementado em Julia!
3. Biblioteca extensa
   * Visualização, Álgebra Linear, equações diferenciais, etc
   * Ainda não tem tudo o que Matlab tem
   * Muito fácil chamar Python (que hoje tem um ecossistema impressionante)
4. A linguagem é muito boa!

# Engenharia de Software

 * Controle de versões e repositórios de software (git e github)
 * Documentação no código (*literate programming*)
 * Testes unitários
 * Pensar no ambiente de trabalho:
   * Editor
   * Interface Notebook - Jupyter
   * REPL (terminal)
   * *Muito do que nós fazemos é programação exploratória*

# Estrutura do curso

Objetivo final:
$$
\nabla\cdot\pmb{u} = 0\\
\frac{\partial \pmb{u}}{\partial t} + \pmb{u}\cdot\nabla\pmb{u} =
-\nabla p + \frac{1}{Re}\nabla^2\pmb{u}
$$

Precisamos saber resolver este tipo de equação:
$$
\nabla^2 \phi = f\\
\frac{\partial \phi}{\partial t} = \alpha\nabla^2
\phi\\
\pmb{u}\cdot\phi = \alpha\nabla^2\phi
$$

Vamos focar no problema 1D para depois tentar avançar em outras direções

# Em paralelo: use algum software para resolver um problema concreto:

 * [OpenFoam](https://www.openfoam.com/)
 * [SU2](https://su2code.github.io/)
 * [CalculiX](http://www.calculix.de/)
 * [Code Saturne](https://www.code-saturne.org/)
 * [Code Aster](https://www.code-aster.org/)
 * [Fenics](https://fenicsproject.org/)
 * [FreeFem++](https://freefem.org/)
 * [Dedalus Project](http://dedalus-project.org/)
 * [Nektar](https://www.nektar.info/)
 * [MEEP](https://meep.readthedocs.io/en/latest/)

# Roteiro para $\nabla^2 u = f$

 * $u^\delta(x) = \sum \hat{u}_k \phi_k(x) \approx u(x)$ - Aproximação e interpolação
 * $\nabla^2 u^\delta - f = \varepsilon(x)$ - Erro da equação diferencial
 * Resíduos ponderados $\int_\Omega w_i(x) \nabla^2 u^\delta \:dx = \int_\Omega f w_i(x)\:dx$
 * Formulação fraca: $ -\int_\Omega \nabla w_i(x) \cdot \nabla u^\delta \:dx + \int_{\partial\Omega} w_i\frac{\partial u}{\partial n} = \int_\Omega f w_i(x)\:dx$
 * Galerkin: $w_i(x) = \phi_i(x)$
 * Sistema linear: $\left[A\right]\cdot\left\{\hat{u}\right\} = \left\{f\right\}$,
 $$A_{i,k} = -\int_\Omega\nabla\phi_i\cdot\nabla\phi_k\:dx + \int_{\partial\Omega}\phi_i\frac{\partial u}{\partial n}\\
 f_i = \int_\Omega f\phi_i\:dx
 $$

# Problema 1D

 1. Interpolação e aproximação: polinomial, senos e cossenos
 2. Derivadas: simbólica, numérica, diferenciação automática
 3. Quadratura, Simbólica, Numérica
 4. Sistemas lineares: Métodos diretos, métodos iterativos, *Explorar a estrutura da matriz*
 5. Equações diferenciais ordinárias: problemas de valor inicial
 6. Sistemas de equações não lineares: Newton-Raphson e outros

# Introduzindo Julia

 * http://julialang.org
 * Lista de discussão https://discourse.julialang.org/
 * Canal do youtube https://www.youtube.com/user/JuliaLanguage
 * Documentação: https://docs.julialang.org

```julia
# Caluladora
1+1

```

```julia
function soma(a,b)
    return a+b
end
soma2(a,b) = a+2b

x = 1
y = 10
soma2(x,y)
```

# Porque Julia consegue ser rápido como C?

```julia
code_native(soma2, (Int,Int))
```

```julia
code_native(soma2, (Float64,Float64))
```

```julia
using ForwardDiff

function f1(x)

    res = one(x)
    for i = 1:5
        res = x + res*x
    end
    return res
end
df1 = x -> ForwardDiff.derivative(f1, x)
f1b(x) = x + x*(one(x) + x*(one(x) + x * (one(x)+ 2x)))
df1b(x) = one(x) + 2x + 3x^2 + 4x^3 + 10x^4

```

```julia
df1(0.5) - df1b(0.5)
```

```julia
using BenchmarkTools

@btime f1(0.5)
@btime f1b(0.6)
@btime df1(0.7)
@btime df1b(0.7)
```

# Sistema de equações diferenciais ordinárias:

Equações de Lorenz
$$
\begin{align}
\frac{dx}{dt} &= σ(y-x) \\
\frac{dy}{dt} &= x(ρ-z) - y \\
\frac{dz}{dt} &= xy - βz \\
\end{align}
$$

```julia
using DifferentialEquations
using Plots
gr()
```

```julia
function lorenz(du,u,p,t)
 du[1] = 10.0*(u[2]-u[1])
 du[2] = u[1]*(28.0-u[3]) - u[2]
 du[3] = u[1]*u[2] - (8/3)*u[3]
end
```

```julia
u0 = [1.0;0.0;0.0]
tspan = (0.0,100.0)
prob = ODEProblem(lorenz,u0,tspan)
sol = solve(prob);
```

```julia
gr()
plot(sol,vars=(1,2,3))
```

```julia
using Psychro
using Unitful
```

```julia
println(volume(MoistAir, 20.0u"°C", DewPoint, 60.0u"°F", 1.0u"atm", u"cm^3/lb"))
println(wetbulb(MoistAir, 20.0u"°C", DewPoint, 60.0u"°F", 1.0u"atm"))
println(relhum(MoistAir, 20.0u"°C", DewPoint, 60.0u"°F", 1.0u"atm"))

```

```julia
using ACME

circ = @circuit begin
    j_in = voltagesource(), [-] ⟷ gnd
    r1 = resistor(1e3), [1] ⟷ j_in[+]
    c1 = capacitor(47e-9), [1] ⟷ r1[2], [2] ⟷ gnd
    d1 = diode(is=1e-15), [+] ⟷ r1[2], [-] ⟷ gnd
    d2 = diode(is=1.8e-15), [+] ⟷ gnd, [-] ⟷ r1[2]
    j_out = voltageprobe(), [+] ⟷ r1[2], [-] ⟷ gnd
end
model = DiscreteModel(circ, 1/4410)
t = 1/44100 * (0:44099)'
y = run!(model, sin.(2π*100 .* t));
plot(t[1,:],y[1,:])
```

```julia
using PyCall
```

```julia
math = pyimport("math")
println(math.sin(1))

np = pyimport("numpy")
nprand = pyimport("numpy.random")
nprand.randn(3,4)

```

```julia
using LinearAlgebra, SpecialFunctions, Plots, ApproxFun
x = Fun(identity,0..10)
f = sin(x^2)
g = cos(x)
```

```julia
h = f + g^2
r = roots(h)
rp = roots(h')

using Plots
plot(h)
scatter!(r,h.(r))
scatter!(rp,h.(rp))
```


```python
%matplotlib inline
```

```python
import sympy
import math

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

# High-School Maths Exercise
## Getting to Know Jupyter Notebook. Python Libraries and Best Practices. Basic Workflow

### Problem 1. Markdown
Jupyter Notebook is a very light, beautiful and convenient way to organize your research and display your results. Let's play with it for a while.

First, you can double-click each cell and edit its content. If you want to run a cell (that is, execute the code inside it), use Cell > Run Cells in the top menu or press Ctrl + Enter.

Second, each cell has a type. There are two main types: Markdown (which is for any kind of free text, explanations, formulas, results... you get the idea), and code (which is, well... for code :D).

Let me give you a...
#### Quick Introduction to Markdown
##### Text and Paragraphs
There are several things that you can do. As you already saw, you can write paragraph text just by typing it. In order to create a new paragraph, just leave a blank line. See how this works below:
```
This is some text.
This text is on a new line, but it will continue the same paragraph (so you can make your paragraphs more easily readable by just continuing on a new line, or just go on and on like this one line is ever continuing).

This text is displayed in a new paragraph.

And this is yet another paragraph.
```
**Result:**

This is some text.
This text is on a new line, but it will continue the same paragraph (so you can make your paragraphs more easily readable by just continuing on a new line, or just go on and on like this one line is ever continuing).

This text is displayed in a new paragraph.

And this is yet another paragraph.

##### Headings
There are six levels of headings. Level one is the highest (largest and most important), and level 6 is the smallest. You can create headings of several types by prefixing the header line with one to six "#" symbols (this is called a pound sign if you are ancient, or a sharp sign if you're a musician... or a hashtag if you're too young :D). Have a look:
```
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
```

**Result:**

# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6

It is recommended that you have **only one** H1 heading - this should be the header of your notebook (or scientific paper). Below that, you can add your name or just jump to the explanations directly.

##### Emphasis
You can create emphasized (stonger) text by using a **bold** or _italic_ font. You can do this in several ways (using asterisks (\*) or underscores (\_)). In order to "escape" a symbol, prefix it with a backslash (\). You can also strike thorugh your text in order to signify a correction.
```
**bold** __bold__
*italic* _italic_

This is \*\*not \*\* bold.

I ~~didn't make~~ a mistake.
```

**Result:**

**bold** __bold__
*italic* _italic_

This is \*\*not\*\* bold.

I ~~didn't make~~ a mistake.

##### Lists
You can add two types of lists: ordered and unordered. Lists can also be nested inside one another. To do this, press Tab once (it will be converted to 4 spaces).

To create an ordered list, just type the numbers. Don't worry if your numbers are wrong - Jupyter Notebook will create them properly for you. Well, it's better to have them properly numbered anyway...
```
1. This is
2. A list
10. With many
9. Items
    1. Some of which
    2. Can
        3. Be nested
42. You can also
    * Mix
    * list
    * types
```

**Result:**
1. This is
2. A list
10. With many
9. Items
    1. Some of which
    2. Can
        3. Be nested
42. You can also
    * Mix
    * list
    * types

To create an unordered list, type an asterisk, plus or minus at the beginning:
```
* This is
* An
    + Unordered
    - list
```

**Result:**
* This is
* An
    + Unordered
        - list

##### Links
There are many ways to create links but we mostly use one of them: we present links with some explanatory text. See how it works:
```
This is [a link](http://google.com) to Google.
```

**Result:**

This is [a link](http://google.com) to Google.

##### Images
They are very similar to links. Just prefix the image with an exclamation mark. The alt(ernative) text will be displayed if the image is not available. Have a look (hover over the image to see the title text):
```

```

**Result:**

If you want to resize images or do some more advanced stuff, just use HTML.

Did I mention these cells support HTML, CSS and JavaScript? Now I did.

##### Tables
These are a pain because they need to be formatted (somewhat) properly. Here's a good [table generator](http://www.tablesgenerator.com/markdown_tables). Just select File > Paste table data... and provide a tab-separated list of values. It will generate a good-looking ASCII-art table for you.
```
| Cell1 | Cell2 | Cell3 |
|-------|-------|-------|
| 1.1   | 1.2   | 1.3   |
| 2.1   | 2.2   | 2.3   |
| 3.1   | 3.2   | 3.3   |
```

**Result:**

| Cell1 | Cell2 | Cell3 |
|-------|-------|-------|
| 1.1   | 1.2   | 1.3   |
| 2.1   | 2.2   | 2.3   |
| 3.1   | 3.2   | 3.3   |

##### Code
Just use triple backtick symbols. If you provide a language, it will be syntax-highlighted. You can also use inline code with single backticks.

```python
def square(x):
    return x ** 2
```
This is `inline` code. No syntax highlighting here.

**Result:**
```python
def square(x):
    return x ** 2
```
This is `inline` code. No syntax highlighting here.

**Now it's your turn to have some Markdown fun.** In the next cell, try out some of the commands. You can just throw in some things, or do something more structured (like a small notebook).

1. # Heading
2. ## Heading
3. ### Heading
4. #### Heading
5. ##### Heading
6. ###### Heading

New Paragraph:
I would like to be **bold** but I am just *italic*.
So I am ~~**bold**~~ *italic* and that's it.

```python
def doMath(hard = true):
    if hard:
        studyHardForHours()
    else:
        goAndPlayOutside()
```

[GitHub](https://github.com/StanDimitroff/Math-Concepts)

### Problem 2. Formulas and LaTeX
Writing math formulas has always been hard. But scientists don't like difficulties and prefer standards. So, thanks to Donald Knuth (a very popular computer scientist, who also invented a lot of algorithms), we have a nice typesetting system, called LaTeX (pronounced _lah_-tek). We'll be using it mostly for math formulas, but it has a lot of other things to offer.

There are two main ways to write formulas. You could enclose them in single `$` signs like this: `$ ax + b $`, which will create an **inline formula**: $ ax + b $. You can also enclose them in double `$` signs `$$ ax + b $$` to produce $$ ax + b $$.

Most commands start with a backslash and accept parameters either in square brackets `[]` or in curly braces `{}`. For example, to make a fraction, you typically would write `$$ \frac{a}{b} $$`: $$ \frac{a}{b} $$.

[Here's a resource](http://www.stat.pitt.edu/stoffer/freetex/latex%20basics.pdf) where you can look up the basics of the math syntax. You can also search StackOverflow - there are all sorts of solutions there.

You're on your own now. Research and recreate all formulas shown in the next cell. Try to make your cell look exactly the same as mine. It's an image, so don't try to cheat by copy/pasting :D.

Note that you **do not** need to understand the formulas, what's written there or what it means. We'll have fun with these later in the course.

Equation of a line: $ y = ax + b $

Roots of the quadratic equation $ ax^2 + bx + c = 0 $: $ x_{1,2} = \frac{-b\pm \sqrt{b^2 - 4ac}}{2a} $

Taylor series expansion: $ f(x)|_{x=a} = f(a) + f'(a)(x-a) + \frac{f^n(a)}{2!}(x-a)^2 + \dots + \frac{f^n(a)}{n!}(x-a)^n + \dots $

Bionomial theorem: $ (x+y)^2 = \binom{n}{0}x^ny^0 + \binom{n}{1}x^1y^{n-1} + \dots + \binom{n}{n}x^0y^n = \sum\limits^n_{k=0} \binom{n}{k}x^{n-k}y^k$

An integral (this one is a lot of fun to solve :D): $ \int^{+\infty}_{-\infty} e^{-x^2}dx = \sqrt{\pi} $

A short matrix: $ \begin{pmatrix} 2 && 1 && 3 \\ 2 && 6 && 8 \\ 6 && 8 && 18 \end{pmatrix} $

A long matrix: $ A = \begin{pmatrix} a_{11} && a_{12} && \dots && a_{1n} \\ a_{21} && a_{22} && \dots && a_{2n} \\ \vdots && \vdots && \ddots && \vdots \\ a_{m1} && a_{m2} && \dots && a_{mn} \end{pmatrix}$

### Problem 3. Solving with Python
Let's first do some symbolic computation. We need to import `sympy` first.

**Should your imports be in a single cell at the top or should they appear as they are used?** There's not a single valid best practice. Most people seem to prefer imports at the top of the file though. **Note: If you write new code in a cell, you have to re-execute it!**

Let's use `sympy` to give us a quick symbolic solution to our equation. First import `sympy` (you can use the second cell in this notebook):
```python
import sympy
```

Next, create symbols for all variables and parameters. You may prefer to do this in one pass or separately:
```python
x = sympy.symbols('x')
a, b, c = sympy.symbols('a b c')
```

Now solve:
```python
sympy.solve(a * x**2 + b * x + c)
```

Hmmmm... we didn't expect that :(. We got an expression for $a$ because the library tried to solve for the first symbol it saw. This is an equation and we have to solve for $x$. We can provide it as a second paramter:
```python
sympy.solve(a * x**2 + b * x + c, x)
```

Finally, if we use `sympy.init_printing()`, we'll get a LaTeX-formatted result instead of a typed one. This is very useful because it produces better-looking formulas.

```python
x, a, b, c = sympy.symbols('x a b c')
sympy.init_printing()
sympy.solve(a * x**2 + b * x + c, x)
```

How about a function that takes $a, b, c$ (assume they are real numbers, you don't need to do additional checks on them) and returns the **real** roots of the quadratic equation?

Remember that in order to calculate the roots, we first need to see whether the expression under the square root sign is non-negative.

If $b^2 - 4ac > 0$, the equation has two real roots: $x_1, x_2$

If $b^2 - 4ac = 0$, the equation has one real root: $x_1 = x_2$

If $b^2 - 4ac < 0$, the equation has zero real roots

Write a function which returns the roots. In the first case, return a list of 2 numbers: `[2, 3]`. In the second case, return a list of only one number: `[2]`. In the third case, return an empty list: `[]`.

```python
def solve_quadratic_equation(a, b, c):
    """
    Returns the real solutions of the quadratic equation ax^2 + bx + c = 0
    """
    d = b**2-4*a*c # discriminant

    if d < 0:
        return []

    elif d == 0:
        x = (-b + math.sqrt(d)) / (2 * a)
        return [x]

    elif d > 0:
        x1 = (-b + math.sqrt(d)) / (2 * a)
        x2 = (-b - math.sqrt(d)) / (2 * a)

        return [x1, x2]
```

```python
# Testing: Execute this cell. The outputs should match the expected outputs. Feel free to write more tests
print(solve_quadratic_equation(1, -1, -2)) # [-1.0, 2.0]
print(solve_quadratic_equation(1, -8, 16)) # [4.0]
print(solve_quadratic_equation(1, 1, 1)) # []
```

    [2.0, -1.0]
    [4.0]
    []

**Bonus:** Last time we saw how to solve a linear equation. Remember that linear equations are just like quadratic equations with $a = 0$. In this case, however, division by 0 will throw an error. Extend your function above to support solving linear equations (in the same way we did it last time).

### Problem 4. Equation of a Line
Let's go back to our linear equations and systems. There are many ways to define what "linear" means, but they all boil down to the same thing.

The equation $ax + b = 0$ is called *linear* because the function $f(x) = ax+b$ is a linear function. We know that there are several ways to know what one particular function means. One of them is to just write the expression for it, as we did above. Another way is to **plot** it. This is one of the most exciting parts of maths and science - when we have to fiddle around with beautiful plots (although not so beautiful in this case).

The function produces a straight line and we can see it.

How do we plot functions in general? We know that functions take many (possibly infinitely many) inputs. We can't draw all of them. We could, however, evaluate the function at some points and connect them with tiny straight lines. If the points are too many, we won't notice - the plot will look smooth.

Now, let's take a function, e.g. $y = 2x + 3$ and plot it. For this, we're going to use `numpy` arrays. This is a special type of array which has two characteristics:
* All elements in it must be of the same type
* All operations are **broadcast**: if `x = [1, 2, 3, 10]` and we write `2 * x`, we'll get `[2, 4, 6, 20]`. That is, all operations are performed at all indices. This is very powerful, easy to use and saves us A LOT of looping.

There's one more thing: it's blazingly fast because all computations are done in C, instead of Python.

First let's import `numpy`. Since the name is a bit long, a common convention is to give it an **alias**:
```python
import numpy as np
```

Import that at the top cell and don't forget to re-run it.

Next, let's create a range of values, e.g. $[-3, 5]$. There are two ways to do this. `np.arange(start, stop, step)` will give us evenly spaced numbers with a given step, while `np.linspace(start, stop, num)` will give us `num` samples. You see, one uses a fixed step, the other uses a number of points to return. When plotting functions, we usually use the latter. Let's generate, say, 1000 points (we know a straight line only needs two but we're generalizing the concept of plotting here :)).
```python
x = np.linspace(-3, 5, 1000)
```
Now, let's generate our function variable
```python
y = 2 * x + 3
```

We can print the values if we like but we're more interested in plotting them. To do this, first let's import a plotting library. `matplotlib` is the most commnly used one and we usually give it an alias as well.
```python
import matplotlib.pyplot as plt
```

Now, let's plot the values. To do this, we just call the `plot()` function. Notice that the top-most part of this notebook contains a "magic string": `%matplotlib inline`. This hints Jupyter to display all plots inside the notebook. However, it's a good practice to call `show()` after our plot is ready.
```python
plt.plot(x, y)
plt.show()
```

```python
x = np.linspace(-3, 5, 1000)
y = 2 * x + 3
plt.plot(x, y)
plt.show()
```

It doesn't look too bad but we can do much better. See how the axes don't look like they should? Let's move them to zeto. This can be done using the "spines" of the plot (i.e. the borders).

All `matplotlib` figures can have many plots (subfigures) inside them. That's why when performing an operation, we have to specify a target figure. There is a default one and we can get it by using `plt.gca()`. We usually call it `ax` for "axis".
Let's save it in a variable (in order to prevent multiple calculations and to make code prettier). Let's now move the bottom and left spines to the origin $(0, 0)$ and hide the top and right one.
```python
ax = plt.gca()
ax.spines["bottom"].set_position("zero")
ax.spines["left"].set_position("zero")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
```

**Note:** All plot manipulations HAVE TO be done before calling `show()`. It's up to you whether they should be before or after the function you're plotting.

This should look better now. We can, of course, do much better (e.g. remove the double 0 at the origin and replace it with a single one), but this is left as an exercise for the reader :).

```python
x = np.linspace(-3, 5, 1000)
y = 2 * x + 3
ax = plt.gca()
ax.spines["bottom"].set_position("zero")
ax.spines["left"].set_position("zero")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
plt.plot(x, y)
plt.show()
```

### * Problem 5. Linearizing Functions
Why is the line equation so useful? The main reason is because it's so easy to work with. Scientists actually try their best to linearize functions, that is, to make linear functions from non-linear ones. There are several ways of doing this. One of them involves derivatives and we'll talk about it later in the course.

A commonly used method for linearizing functions is through algebraic transformations. Try to linearize
$$ y = ae^{bx} $$

Hint: The inverse operation of $e^{x}$ is $\ln(x)$. Start by taking $\ln$ of both sides and see what you can do. Your goal is to transform the function into another, linear function. You can look up more hints on the Internet :).

Write your result here.

### * Problem 6. Generalizing the Plotting Function
Let's now use the power of Python to generalize the code we created to plot. In Python, you can pass functions as parameters to other functions. We'll utilize this to pass the math function that we're going to plot.

Note: We can also pass *lambda expressions* (anonymous functions) like this:
```python
lambda x: x + 2```
This is a shorter way to write
```python
def some_anonymous_function(x):
    return x + 2
```

We'll also need a range of x values. We may also provide other optional parameters which will help set up our plot. These may include titles, legends, colors, fonts, etc. Let's stick to the basics now.

Write a Python function which takes another function, x range and number of points, and plots the function graph by evaluating it at every point.

**BIG hint:** If you want to use not only `numpy` functions for `f` but any one function, a very useful (and easy) thing to do, is to vectorize the function `f` (e.g. to allow it to be used with `numpy` broadcasting):
```python
f_vectorized = np.vectorize(f)
y = f_vectorized(x)
```

```python
def plot_math_function(f, min_x, max_x, num_points):
    x = np.linspace(min_x, max_x, num_points)
    f_vectorized = np.vectorize(f)
    y = f_vectorized(x)

    ax = plt.gca()
    ax.spines["bottom"].set_position("zero")
    ax.spines["left"].set_position("zero")
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)

    plt.plot(x, y)
    plt.show()
```

```python
plot_math_function(lambda x: 2 * x + 3, -3, 5, 1000)
plot_math_function(lambda x: -x + 8, -1, 10, 1000)
plot_math_function(lambda x: x**2 - x - 2, -3, 4, 1000)
plot_math_function(lambda x: np.sin(x), -np.pi, np.pi, 1000)
plot_math_function(lambda x: np.sin(x) / x, -4 * np.pi, 4 * np.pi, 1000)
```

### * Problem 7. Solving Equations Graphically
Now that we have a general plotting function, we can use it for more interesting things. Sometimes we don't need to know what the exact solution is, just to see where it lies. We can do this by plotting the two functions around the "=" sign and seeing where they intersect. Take, for example, the equation $2x + 3 = 0$. The two functions are $f(x) = 2x + 3$ and $g(x) = 0$. Since they should be equal, the point of their intersection is the solution of the given equation. We don't need to bother marking the point of intersection right now, just showing the functions.

To do this, we'll need to improve our plotting function yet once. This time we'll need to take multiple functions and plot them all on the same graph. Note that we still need to provide the $[x_{min}; x_{max}]$ range and it's going to be the same for all functions.

```python
vectorized_fs = [np.vectorize(f) for f in functions]
ys = [vectorized_f(x) for vectorized_f in vectorized_fs]
```

```python
def plot_math_functions(functions, min_x, max_x, num_points):
    x = np.linspace(min_x, max_x, num_points)
    vectorized_fs = [np.vectorize(f) for f in functions]
    ys = [vectorized_f(x) for vectorized_f in vectorized_fs]

    for y in ys:
        plt.plot(x, y)
        plt.show()
```

```python
plot_math_functions([lambda x: 2 * x + 3, lambda x: 0], -3, 5, 1000)
plot_math_functions([lambda x: 3 * x**2 - 2 * x + 5, lambda x: 3 * x + 7], -2, 3, 1000)
```

This is also a way to plot the solutions of systems of equation, like the one we solved last time. Let's actually try it.

```python
plot_math_functions([lambda x: (-4 * x + 7) / 3, lambda x: (-3 * x + 8) / 5, lambda x: (-x - 1) / -2], -1, 4, 1000)
```

### Problem 8. Trigonometric Functions
We already saw the graph of the function $y = \sin(x)$. But, how do we define the trigonometric functions once again? Let's quickly review that.

The two basic trigonometric functions are defined as the ratio of two sides:
$$ \sin(x) = \frac{\text{opposite}}{\text{hypotenuse}} $$
$$ \cos(x) = \frac{\text{adjacent}}{\text{hypotenuse}} $$

And also:
$$ \tan(x) = \frac{\text{opposite}}{\text{adjacent}} = \frac{\sin(x)}{\cos(x)} $$
$$ \cot(x) = \frac{\text{adjacent}}{\text{opposite}} = \frac{\cos(x)}{\sin(x)} $$

This is fine, but using this, "right-triangle" definition, we're able to calculate the trigonometric functions of angles up to $90^\circ$. But we can do better. Let's now imagine a circle centered at the origin of the coordinate system, with radius $r = 1$. This is called a "unit circle".

We can now see exactly the same picture. The $x$-coordinate of the point in the circle corresponds to $\cos(\alpha)$ and the $y$-coordinate - to $\sin(\alpha)$. What did we get? We're now able to define the trigonometric functions for all degrees up to $360^\circ$. After that, the same values repeat: these functions are **periodic**:
$$ \sin(k.360^\circ + \alpha) = \sin(\alpha), k = 0, 1, 2, \dots $$
$$ \cos(k.360^\circ + \alpha) = \cos(\alpha), k = 0, 1, 2, \dots $$

We can, of course, use this picture to derive other identities, such as:
$$ \sin(90^\circ + \alpha) = \cos(\alpha) $$

A very important property of the sine and cosine is that they accept values in the range $(-\infty; \infty)$ and produce values in the range $[-1; 1]$. The two other functions take values in the range $(-\infty; \infty)$ **except when their denominators are zero** and produce values in the same range.

#### Radians
A degree is a geometric object, $1/360$th of a full circle. This is quite inconvenient when we work with angles. There is another, natural and intrinsic measure of angles. It's called the **radian** and can be written as $\text{rad}$ or without any designation, so $\sin(2)$ means "sine of two radians".

It's defined as *the central angle of an arc with length equal to the circle's radius* and $1\text{rad} \approx 57.296^\circ$.

We know that the circle circumference is $C = 2\pi r$, therefore we can fit exactly $2\pi$ arcs with length $r$ in $C$. The angle corresponding to this is $360^\circ$ or $2\pi\ \text{rad}$. Also, $\pi rad = 180^\circ$.

(Some people prefer using $\tau = 2\pi$ to avoid confusion with always multiplying by 2 or 0.5 but we'll use the standard notation here.)

**NOTE:** All trigonometric functions in `math` and `numpy` accept radians as arguments. In order to convert between radians and degrees, you can use the relations $\text{[deg]} = 180/\pi.\text{[rad]}, \text{[rad]} =  \pi/180.\text{[deg]}$. This can be done using `np.deg2rad()` and `np.rad2deg()` respectively.

#### Inverse trigonometric functions
All trigonometric functions have their inverses. If you plug in, say $\pi/4$ in the $\sin(x)$ function, you get $\sqrt{2}/2$. The inverse functions (also called, arc-functions) take arguments in the interval $[-1; 1]$ and return the angle that they correspond to. Take arcsine for example:
$$ \arcsin(y) = x: sin(y) = x $$
$$ \arcsin\left(\frac{\sqrt{2}}{2}\right) = \frac{\pi}{4} $$

Please note that this is NOT entirely correct. From the relations we found:
$$\sin(x) = sin(2k\pi + x), k = 0, 1, 2, \dots $$

it follows that $\arcsin(x)$ has infinitely many values, separated by $2k\pi$ radians each:
$$ \arcsin\left(\frac{\sqrt{2}}{2}\right) = \frac{\pi}{4} + 2k\pi, k = 0, 1, 2, \dots $$

In most cases, however, we're interested in the first value (when $k = 0$). It's called the **principal value**.

Note 1: There are inverse functions for all four basic trigonometric functions: $\arcsin$, $\arccos$, $\arctan$, $\text{arccot}$. These are sometimes written as $\sin^{-1}(x)$, $cos^{-1}(x)$, etc. These definitions are completely equivalent.

Just notice the difference between $\sin^{-1}(x) := \arcsin(x)$ and $\sin(x^{-1}) = \sin(1/x)$.

#### Exercise
Use the plotting function you wrote above to plot the inverse trigonometric functions.

```python
plot_math_function(lambda x: np.arcsin(x), -3, 5, 1000)
plot_math_function(lambda x: np.arccos(x), -3, 5, 1000)
plot_math_function(lambda x: np.arctan(x), -3, 5, 1000)
```

```python
def plot_circle(x_c, y_c, r):
    """
    Plots the circle with center C(x_c; y_c) and radius r.
    This corresponds to plotting the equation x^2 + y^2 = r^2
    """
    circle = plt.Circle((x_c, y_c), r)
    ax=plt.gca()
    ax.add_patch(circle)
    plt.axis('scaled')
    plt.show()
```

```python
plot_circle(0, 0, 2)
```

### ** Problem 9. Perlin Noise
This algorithm has many applications in computer graphics and can serve to demonstrate several things... and help us learn about math, algorithms and Python :).
#### Noise
Noise is just random values. We can generate noise by just calling a random generator. Note that these are actually called *pseudorandom generators*. We'll talk about this later in this course.
We can generate noise in however many dimensions we want. For example, if we want to generate a single dimension, we just pick N random values and call it a day. If we want to generate a 2D noise space, we can take an approach which is similar to what we already did with `np.meshgrid()`.

$$ \text{noise}(x, y) = N, N \in [n_{min}, n_{max}] $$

This function takes two coordinates and returns a single number N between $n_{min}$ and $n_{max}$. (This is what we call a "scalar field").

Random variables are always connected to **distributions**. We'll talk about these a great deal but now let's just say that these define what our noise will look like. In the most basic case, we can have "uniform noise" - that is, each point in our little noise space $[n_{min}, n_{max}]$ will have an equal chance (probability) of being selected.

#### Perlin noise
There are many more distributions but right now we'll want to have a look at a particular one. **Perlin noise** is a kind of noise which looks smooth. It looks cool, especially if it's colored. The output may be tweaked to look like clouds, fire, etc. 3D Perlin noise is most widely used to generate random terrain.

#### Algorithm
... Now you're on your own :). Research how the algorithm is implemented (note that this will require that you understand some other basic concepts like vectors and gradients).

#### Your task
1. Research about the problem. See what articles, papers, Python notebooks, demos, etc. other people have created
2. Create a new notebook and document your findings. Include any assumptions, models, formulas, etc. that you're using
3. Implement the algorithm. Try not to copy others' work, rather try to do it on your own using the model you've created
4. Test and improve the algorithm
5. (Optional) Create a cool demo :), e.g. using Perlin noise to simulate clouds. You can even do an animation (hint: you'll need gradients not only in space but also in time)
6. Communicate the results (e.g. in the Softuni forum)

Hint: [This](http://flafla2.github.io/2014/08/09/perlinnoise.html) is a very good resource. It can show you both how to organize your notebook (which is important) and how to implement the algorithm.


# Recording of class
Background: people join from Americas/East Asia

Solution: recording of class
- Will be put on secret youtube links - not searchable
- Delete mid June

Consent: via poll

# Session 5:
## Growing Causal Trees
### - *Causal Forests and Generalized Random Forests*

*Andreas Bjerre-Nielsen*

## Agenda

1. [Causality](#Causality)
1. [Potential outcomes](#Potential-outcomes)
2. [Experiments](#Experiments)
3. [Matching](#Matching)
    - [Covariate based matching](#Covariate-matching)
    - [Propensity score matching](#Propensity-score-matching)

4. [Heterogeneous treatment effects with causal trees](#Causal-trees)
    - [Causal Forest](#Causal-Forest)
    - [Generalized Random Forest](#Generalized-Random-Forest)
    - [Application of causal forest](#Application-of-causal-forest)

# Buckle up...

```python
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
import pandas as pd
import seaborn as sns
%matplotlib inline
```

# Causality

## Correlation does not imply causation

Spurious or causal?

Figure below is adapted from chapter 5 in Judea Pearl's book titled "Book of Why"

```python
import networkx as nx

plt.rcParams.update({'axes.titlesize': 21})

f_lung_cancer, ax = plt.subplots(1,2,figsize=(17,6.7))
ax[0].set_title('Richard Doll and Austin Bradford Hill')
ax[1].set_title('Ronald A. Fisher')

s,l,g= 'Smoking', 'Lung\ncancer', 'Unobserved factors'
for i in range(2):
    G = nx.DiGraph()
    G.graph['dpi'] = 120
    G.add_nodes_from([s,l,g])

    G1 = G.copy()
    G1.add_edges_from([(s,l)])
    nx.draw_networkx_edges(G1,arrowsize=30,ax=ax[i],edge_color='blue',
                           pos = {g: [1,1], s: [0.2,0], l: [1.8,0]})

    if i>0:
        G2 = G.copy()
        G2.add_edges_from([(g,l),(g,s)])
        nx.draw_networkx_edges(G2,arrowsize=30,ax=ax[i],edge_color='red',
                               pos = {g: [1,1], s: [0.2,0], l: [1.8,0]})

    nx.draw_networkx_nodes(G,node_color='white',node_size=1000,alpha=0,ax=ax[i],
                           pos = {g: [1,1], s: [0,0], l: [2,0]})
    nx.draw_networkx_labels(G,ax=ax[i],font_size=16,
                            pos = {g: [1,1.1], s: [-.05,0], l: [2,0]})
    ax[i].axis('off')
    ax[i].set_xlim([-.3,2.3])
    ax[i].set_ylim([-.2,1.4])

```

## What is causality?

Relationship between two or more variables such that whereby a change in one or more variable(s) ***affect(s)*** the distribution of one or more other variable(s).

We can draw these relationships (from The Book of Why, Judea Pearl), e.g. smoking example.
- Ronald Fisher argued that unobserved confounders could cause smoking and lung cancer

```python
f_lung_cancer
```

## Establishing causality

Currently there are two broad approaches for establishing causal relationships:

- Experiment and quasi-experiments
    - Corresponds to what is taught in *Mostly Harmless Econometrics*
- Structural equation models
    - Used for structural econometric choice models etc.
    - Also used estimating causal graphs, e.g. as by Judea Pearl

# Potential outcomes

## The aim

We are interested in the effect of some treatment, e.g.
- getting admitted to a certain educaton on wages, life-expectancy
- access to paternity leave on wages (husband and wife)

## The Rubin Causal Model

Denote the treatment variable as $T_i$ where $T_i=1$ corresponds to unit $i$ being treated, while $T_i=0$ is not treated. Define the potential outcomes:

$Y_i=\begin{cases}
Y_i(1), & T_i=1;\\
Y_i(0), & T_i=0.
\end{cases}$

The observed outcome $Y_i$ can be written in terms of potential outcomes as
$$ Y_i = Y_{i}(0) + [Y_{i}(1)-Y_{i}(0)]\cdot T_i$$

$Y_{i}(1)-Y_{i}(0)$ is the *causal* effect of $T_i$ on $Y_i$.

But we never observe the same individual $i$ in both states. This is the **fundamental problem of causal inference**.

## Selection Bias

We need some way of estimating the state we do not observe (the ***counterfactual***)

Usually, our sample contains individuals from both states - treated and untreated.

So why not do a naive comparison of averages by treatment status? i.e. $E[Y_i|T_i = 1] - E[Y_i|T_i = 0]$

## Selection Bias II
We can rewrite into:
\begin{align}
\nonumber E[Y_i|T_i = 1] - E[Y_i|T_i = 0] = &E[Y_i(1)|T_i = 1] - E[Y_i(0)|T_i = 1] + \\
 \nonumber  &E[Y_i(0)|T_i = 1] - E[Y_i(0)|T_i = 0]
\end{align}

The decomposition:

 - $E[Y_i(1)|T_i = 1] - E[Y_i(0)|T_i = 1] = E[Y_i(1) - Y_i(0)|T_i = 1]$: the average *causal* effect of $T_i$ on $Y$.

- $E[Y_i(0)|T_i = 1] - E[Y_i(0)|T_i = 0]$: difference in average $Y_i(0)$ between the two groups. Likely to be different from 0 when individuals are allowed to self-select into treatment. Often referred to as ***selection bias***.

# Experiments

## Random assignment solves the problem

Random assignment implies $T_i$ is independent of potential outcomes

- Selection bias term is zero: $E[Y_{i}(0)|T_i = 1] = E[Y_{i}(0)|T_i = 0]$

- Intuition: non-treated individuals can be used as counterfactuals for treated (*what would have happened to individual $i$ had he not received the treatment*?)

- Overcome the fundamental problem of causal inference

## Randomization

Holland and Rubin (1986)

> no causation without manipulation

As mentioned, we need to worry when individuals are allowed to self-select

- A lot of thought has to go into the *randomization phase*.

- Randomization into treatment groups has to be manipulated by someone.

## Randomized Controlled Trials

*Randomized controlled trials (RCT)*: randomization done by researcher

- Survey experiments
- Field experiments

Note: difficult to say one is strictly better than the other. Randomization can be impractical and/or unethical.

## An alternative to experiments

*Quasi-experiments*: randomization happens by "accident"

- Matching (*today*)
- Differences in Differences
- Regression Discontinuity Design
- Instrument variables

# Matching

## The what and why of matching

**What** - we construct counterfactual potential treated and control units.
- We *match* observations across treatment and control based on similarity.

**Why** -  matching control for used covariates
- excludes (observable) confounders
- may improve precision of treatment estimate of experiments (less variance)

Note: An alternative to matching is to using regression - basically same idea.

Problem:
- matching does not unconfound generally!!
- unobserved factors may still confound

## The how of matching

We use a set of covariates $X$ for matching.

Two core ideas:
- We match on covariates
    - We require sufficient similarity by some metric over covarities
- We match on propensity
    - We require sufficient similar probability of treatment (prediction)

# Covariate based matching

## Exact matching

 We match a treatment $i$ obs. with control obs. $j$ if
 - $X_i=X_j$, i.e. they are exactly identical,
 - $||X_i-X_j||_2=0$, i.e. zero Euclidian distance

## Treatment effects

We can compute the Average Treatment Effect (ATE)

- For treatment obs. $i$ the counterfactual outcomes $Y_i(0)$ are the average of control $j$ where $X_j=X_i$.
- For control obs. $i$ the counterfactual outcomes $Y_i(1)$ are the average of treatment $j$ where $X_j=X_i$.

We can also compute treatment effects only for treament observations, known as Average Treatment Effect on the Treated (**ATT** or **ATET**).

## Balance of match

What happens if some observations are not matched?
- We get biased estimates!
- We not to check whether the match is balanced
    - Problem, exact matching usually leads to very few matches.

## Example of exact matching

Aim: understand whether traning program affects wages.

We have covariates and outcomes treatment and controls. (synthetic data from Scott Cunninghams's "Causal Inference - The Mixtape" book)

```python
scuse = 'https://storage.googleapis.com/causal-inference-mixtape.appspot.com/{0}.dta'
df = pd.read_stata(scuse.format('training_example')).replace('',np.nan)
arr = df.values[:20].astype('float')
X_cntrl, y_cntrl = arr[:20,4:5], arr[:20,5]
X_treat, y_treat = arr[:10,1:2], arr[:10,2]
```

```python
f,ax = plt.subplots()
sns.distplot(X_treat, bins=10, label='Treatment', ax=ax)
sns.distplot(X_cntrl, bins=10, label='Control', ax=ax)
ax.legend()
ax.set_xlim(15,55)
sns.despine(f)
f.savefig('fig/balance_labor.png',dpi=300)
```

## Example of exact matching (2)

We have only one dimension of covariate so we can easily check the balance.
- Problem no counterfactuals for control!!

## Example of exact matching (3)

We can match exactly using `RadiusNeighborsRegressor` with zero radius.
- OBS: in econometrics this radius is often known as a caliper

```python
from sklearn.neighbors import RadiusNeighborsRegressor as RNR

impute_t_exact = RNR(radius=0).fit(X_cntrl, y_cntrl).predict(X_treat)
impute_c_exact = RNR(radius=0).fit(X_treat, y_treat).predict(X_cntrl)
impute_c_exact
```

    One or more samples have no neighbors within specified radius; predicting NaN.

    array([10000., 11750., 10250.,    nan,    nan, 12250.,    nan, 13250.,
           11000., 12500., 13250.,    nan, 10500.,  9500.,    nan,    nan,
            9750., 12500.,    nan,    nan])

## Exact matching (4)

We can compute unbiased estimate of ATT:

```python
print(y_treat.mean(), y_cntrl.mean() )
diff = y_treat - impute_t_exact
print(f'ATT: {round(diff.mean(),1)} ± {round(diff.std()*1.96,1)}')
```

    11075.0 11101.25
    ATT: 1695.0 ± 646.7

## Other covariate based matching

We can extend exact matching in several ways

- Coarsened Exact Matching:
    - where continuous variables are split into blocks
    - very popular for experiments
- Radius / Caliper matching
- Nearest neighbor matching

We can also have different metrics:
- Euclidian
- Mahalanobis distance ($=(X-\bar{X})^TCOVAR(X)(X-\bar{X}) $)

Note that approximate matching on covariates may introduce other biases, see [Abadie and Imbenes (2011)](https://doi.org/10.1198/jbes.2009.07333).

# Propensity score matching

## Predicting treatment status

Alternative way of match on likelihood of treatment.

Procedure:
1. estimate a model that predicts treatment
2. match with observations of similar treatment likelihood
    - (use match function, e.g. nearest neighbor, caliper)
3. compute counterfactual outcomes for treatment and control
4. (possibly adjust for differences in observed covariates)
5. compute ATE

## Uncoundedness property

[Rosenbaum and Rubin (1983)](https://doi.org/10.1093/biomet/70.1.41) show that propensity score matching will unconfounded:
- can serve as an unbiased estimator of the average treatment effect
- endows non-experimental data with experimantal qualities

Critical requirement - conditional independence assumption (**CIA**):
- same as no unobserved confounders
- often CIA is violated
  - e.g. causal effect of taking education with registry data - many unobserved factors

## Summary - matching

Useful tool, but requires that we know all relevant factors
- can be useful to minimize variance of experimental estimates
- problem in observational studies - often there are unobserved confounders and selection

If we think there is selection effects or endogeneity:
- Use quasi-experimental methods which can handle this, e.g. diff-in-diff or regression discontinuity

# Causal trees

## Average Joe
Suppose, we have credible measures of average treatment effect, $\tau$.

Can we get personalized estimates?

- Measure whether certain groups are affected differently by our new school policy
    - e.g. boys vs. girls, natives vs. immigrants
- Some react positively to one kind of information, others to another

## Beyond average Joe

Conditional Average Treatment Effects (CATE)

- Treatment effect for given characteristics $x$
    - $\tau(x) = \mathbb{E}[Y_i(1)-Y_i(0)|X=x]$

Methods exist, e.g. use regression analysis.

But.. **True model is unknown**..!

- May need to test model on data.
- Can lead to conclusions based on data mining (dangerous!!)

## Being dishonest with you

An adaptive, data driven approach

- use all data for training decision tree
    - partitions X into categories based outcome similarity
    - enough treatment and control in each leaf
- then estimating treatment effects in partitions
    - measure treatment effects in each partition group (=leaf in tree model)

**Quiz**: is this different from propensity scores?

- Propensity scores has treatment assignment $T_i$ as target.

- The adaptive approach uses outcome $y_i$ as target.

## Getting honest with you

Could we use out-of-sample intuition?

[Athey and Imbens (2016)](https://doi.org/10.1073/pnas.1510489113) suggest to let data speak **honestly**:
- half of sample ($\mathcal{S}^{tr}$) for training decision tree
    - partitions X into categories based outcome similarity
    - enough treatment and control in each leaf
- other half ($\mathcal{S}^{est}$) for estimating treatment effects
    - measure treatment effects in each partition group (=leaf in tree model)

This is similar to splitting into train and test
- prevents data-leakage
- allows honest evaluation of model performance!

## Core assumption

Potential outcomes and treatment assignment are unconfounded given covariates

\begin{equation*}
T_i \,\perp\!\!\perp\, (Y_i(1), \,Y_i(0))\,\, | \,\, X
\end{equation*}

- where $\perp\!\!\perp$ is a symbol  for conditional independence (strong assumption!!)
- recall from earlier
    - always holds for experiments
    - or propensity scores (note: assumption cannot be tested)

## Modified splitting procedure

The usual way of training decision trees is Classification And Regression Trees (CART).

- Splits leaves repeatedly based on criteria (e.g. entropy, MSE)
- We can put in restriction, e.g. depth of trees (hyperparameters)

Causal trees
- new criteria:
    - expected MSE (in hypothetical test set): $\mathbb{E}[\underset{=MSE}{\underbrace{(Y_i-\bar{Y}_i)^2}} - Y_i^2]$
    - idea: new term $Y_i^2$ penalizes small leaves
- note: same ranking as MSE, matters for properties

## Modified splitting procedure

The usual way of training decision trees is Classification And Regression Trees (CART).

- Splits leaves repeatedly based on criteria (e.g. entropy, MSE)
- We can put in restriction, e.g. depth of trees (hyperparameters)

Causal trees
- criteria: $\mathbb{E}[(Y_i-\bar{Y_i})^2 - Y_i^2]$
- note: same ranking as MSE, matters for properties

## Inference

Partioning of the covariate data works like coarsened matching!

- Estimate average treatment effects locally for each group/leaf
- Corresponds to local matching!

## Inference - validation

[Athey and Imbens (2016)](https://doi.org/10.1073/pnas.1510489113) performs a simulation study under various scenarios.

Main take-away: **honest** outperforms **adapative** (convential CART).

## Summary - causal trees

Leverage machine learning idea:
- Heterogeneity is estimated separate from treatment effects.
- New scoring function makes smaller leafs.
- Outperforms adapative procedure

Main advantage
- Structure of heterogeneity from data.
- Can be part of pre-analysis plan - only one solution (given split of data!).

# Recap on Random Forest

## The  forest full of trees

What is the difference between a Decision Tree and a Random Forest?

- Decision tree iteratively splits data into subsets (partitions) and calculates mean outcome in leaves (end of splits)
- Minimize on some criteria, often entropy or similar loss function
- Collection/ensemble of decision trees
  - Subset of data by bootstrap (sampling with replacement)
  - Subset of features

## A special tree

So what distinguishes a Causal Tree from a Decision Tree?

- Causal tree estimates partition of data where treatment effects can be computed locally
- In order to have valid estimates we need **honesty** of trees by estimating partitions and treatment effects on different subsets of data
  - Analogy to train / test split

## A tradeoff in structure of heterogeneity

Two approaches?

- Data driven heterogeneity
  - Based on causal trees etc.
- A priori sensible heterogeneity
  - e.g. gender, socioeconomic, ethnicity
  - we use regression model and have interaction with desired variable

When to choose which?
- Choose data driven heterogeneity for policy where you want to maximize impact given data (no theory)
- If we want to test whether certain subgroups are adversely affected

## Limitations of Decision Trees

Random forests are nice but no asymptotic normality of prediction.

- Crucial for inference! (corresponds to MLR6 in Econometrics 1)

- Also holds for causal trees

# Random forest for inference and treatment effecs

## Causal  Trees

The goal of causal trees is to establish unbiased, consistent estimates of heterogeneous treatment effects
- also known as conditional average treatment effects (**CATE**)
- the effect size is denoted $\hat{\tau}(x)$;
- standard tools for inference, e.g. using statistical tests locally

## Causal Forest

What is the output from the decisions trees? Each tree produces a partitioning of the feature space $X$. Example of three trees:

(from [Athey, Wager, Tibshirani, 2019](https://doi.org/10.1214/18-aos1709))

## Double Sample Trees

For Causal Trees

- first half ($\mathcal{J}$, $|\mathcal{J}|=\lceil s / 2\rceil$)
  - training Decision Tree
    - minimize adjusted MSE
    - require at least $k$ observations for both treatment and control in all leaves of $\mathcal{I}$-sample
- other half  ($\mathcal{I}$, $|\mathcal{I}|=\lfloor s / 2\rfloor$)
  - estimating treatment effects, $\hat{\tau}(x)$

## Double Sample Trees (2)

For Regression Trees

- first half ($\mathcal{J}$, $|\mathcal{J}|=\lceil s / 2\rceil$)
  - training Decision Tree
    - minimize MSE / Gini etc.
    - require at least $k$ observations in all leaves of $\mathcal{I}$-sample
- other half  ($\mathcal{I}$, $|\mathcal{I}|=\lfloor s / 2\rfloor$)
  - estimating outcome, $\hat{\mu}(x)$

**Quiz:** How is this different from normal Decision Trees for regression problems?

- Unlike normal decision trees outcomes are estimated honestly.

## Main results: econometric properties (1)

[Wager and Athey (2017)](https://doi.org/10.1080/01621459.2017.1319839) show
 - We can estimate the variance of CATE
 - $\hat{V}_{IJ}(x)=\frac{n-1}{n}\left(\frac{n}{n-s}\right)^{2} \sum_{i=1}^{n} \operatorname{Cov}_{*}\left[\hat{\tau}_{b}^{*}(x), N_{i b}^{*}\right]^{2}$

## Main results: econometric properties (2)

From Theorem 4.1 in [Wager and Athey (2017)](https://doi.org/10.1080/01621459.2017.1319839)

- The conditional average treatment estimates are unbiased and consistent
  - unbiased: no systematic error of measurement
  - consistency: with more data our estimate approaches true value

- Moreover, we can do inference:
  - The variance estimator $\hat{V}_{IJ}(x)$ is consistent.
  - Treatment effect estimates are asymptotic normal and unbiased
    - $(\hat{\tau}(x)-\tau(x)) / \sqrt{\operatorname{Var}[\hat{\tau}(x)]} \Rightarrow \mathcal{N}(0,1)$

Caveat: only works for evaluating treatment effects in one point $x$! Do not perform multiple tests.

## Useful forests

Two more procedures

1. Double Sampled Trees
  - using Regression trees for predicting outcome (=$\hat{\mu}(x)$)
1. Propensity Trees
  -  using propensity trees for propensity score matching

What is the shared procedure?
- Each tree is estimated using repeated subsampling (**no replacement**)
  - Constrast to bootstrap aggregation in random forest (sample **with replacment**)
- Random subsample of features

## More results

[Wager and Athey (2017)](https://doi.org/10.1080/01621459.2017.1319839) show that the same properties of Double Sample Trees using causal trees also hold analogously for regression trees.
- Random forests  have the property of being asymptotic normal and can thus be used for inference
- Similar intuition as idea of nested CV where we could do inference

## Simulation experiment

[Wager,  and Athey (2017)](https://doi.org/10.1214/18-aos1709) compare causal forest to nearest neighbor methods

- random forest is kind of local nearest neighbor estimate
- based on work by Lin and Jeon (2006).

## Simulation (1)

- simulation setup: no treatment effect, only confounding factors
- method: propensity trees
- comparison of estimated treatment effects
    - lower MSE and better coverage
    - coverage falls for increasing number of variables $d$

(from [Athey, Wager, Tibshirani, 2019](https://doi.org/10.1214/18-aos1709))

## Simulation (2)

- setup: heterogeneous treatment effect, **no** confounding factors

- comparison of estimated treatment effects
    - lower MSE and better coverage
    - coverage falls for increasing number of variables $d$

(from [Athey, Wager, Tibshirani, 2019](https://doi.org/10.1214/18-aos1709))

## Alternatives to compute heterogeneous treatment effects

There are some existing and new frameworks for estimating heterogeneous treatment effects. For instance BART is already quite established and often outperforms GRF.

- [Chipman, George and McCulloch (2010)](https://doi.org/10.1214/09-aoas285) develops Bayesian Additive Regression Trees (BART)

- [Künzel et al. (2019)](https://doi.org/10.1073/pnas.1804597116) investigates more general class of prediction tools for partitioning data using

  - Lower EMSE in many cases relative to CF/GRF and BART

- [Nie and Wager (2017)](https://arxiv.org/pdf/1712.04912.pdf) investigates another class of methods called R-learners that leverages a smart representation of CATE.

## Round-up causal forest

Summary of [Wager and Athey (2017)](https://doi.org/10.1080/01621459.2017.1319839)
- builds on Causal Trees method
- strong econometric properties
  - unbiased and consistent
  - asymptotic normality given $x$
      - causal and regression forest allows inference!
- problem:
  - must choose focus
    - unconfounding (propensity) or
    - estimate CATE
  - coverage was not good, especially for higher $d$!

# Generalized Random Forest

## A higher aim

Causal forests are pretty cool. Can we use our honest procedure more generally?

- Estimate any quantity $\theta(x)$ identified via local moment conditions, e.g.
  - simultaneously unconfound and find heterogeneity?
  - find heterogeneous treatment effects from IV estimation?

## Estimating equations
The general estimating equation
  - $\mathbb{E}\left[\psi_{\theta(x), \nu(x)}\left(O_{i}\right) | X_{i}=x\right]=0, \quad \forall x.$

Where $\psi$  estimating function, maps parameters and data into moment equations
  - Parameters
    - $\theta$ parameter we want estimate
    - $\nu$ is nuisance we want to "partial out" (optional)
  - Data
    - $O_i$ main objects we are interested in modelling, e.g. $Y_i, T_i$
    - $X_i$ covariates

## Estimating equations

What is a moment condition?

- Similiar to solution to first order condition
- More general - can incorporate extra restrictions (e.g. unconfounding)

## Estimating equations

Suppose we want to estimate conditional average treatment effects

Functional form: $\psi_{\beta(x),c(x)}=Y_i-\beta(x)W_i-c(x) \left(1 \quad W_{i}\right)$ where
  - $\beta$ is treatment effect
  - $c$ is nuisance parameter

## Using a kernel

Kernel methods can be used to unconfound and compute heterogeneous effects simulateneously

- Problem how to decide weights?

(from [Athey, Wager, Tibshirani, 2019](https://doi.org/10.1214/18-aos1709))

## The Generalized Random Forest

[Athey, Wager, Tibshirani (2019)](https://doi.org/10.1214/18-aos1709) show that kernel weights can be estimated using forest methods

- can be adapted for different purposes
  - quantile regression
  - heterogeneous treatment effects
  - instrumental variables

## The Generalized Random Forest (2)

[Athey, Wager, Tibshirani (2019)](https://doi.org/10.1214/18-aos1709) use a procedure as follows:

1. Use estimating equation, $\psi$ to estimate tree splits iteratively on subsample.
2. View forests as a weights of similar neighbors
    - Amount of partitions where observations
  \begin{equation}\alpha_i(x)=\frac{1}{B}\sum_{b=1}\frac{\mathbb{1}(X_i\in L_b(x))}{|L_b(x)|}\end{equation}
3. Re-estimate $\psi$ using weights on entire sample.

Difference from Causal Forest - trees are used for constructing weights!

## Computing weights $\alpha$

Given a subsample $\mathcal{I}$ of data.

1. Split subsample $\mathcal{I}$ into $\mathcal{J}_1,\mathcal{J}_2$
1. Estimate trees on $\mathcal{J}_1$
  1. Compute estimating equations on subsample at each parent node (before split)
  1. Evaluate different splits - use approximate solutions with gradients
1. Estimate weights using forests on $\mathcal{J}_2$.
  \begin{equation}\alpha_i(x)=\frac{1}{B}\sum_{b=1}\frac{\mathbb{1}(X_i\in L_b(x))}{|L_b(x)|}\end{equation}
    - where $L_b(x)$ training examples falling in the same leaf as x

## Overall procedure

1. Repeatedly estimate trees where splits are based on estimating equation, $\psi$ to obtain weights $\alpha$.
1. Re-estimate $\psi$ using weights on entire sample where forests splits are weights.

## Estimating equations

Given $x$ compute the ***local*** estimating equations using weights $\alpha_i(x)$ on entire sample:

\begin{equation}
(\hat{\theta}(x), \hat{\nu}(x)) \in \operatorname{argmin}_{\theta, \nu}\left\{\left\|\sum_{i=1}^{n} \alpha_{i}(x) \psi_{\theta, \nu}\left(O_{i}\right)\right\|_{2}\right\}
\end{equation}

Example of applications in Athey et al. (2019)
- Conditional Average Treatment Effects
- Instrumental Variables
- Quantile Regressions

## Estimation equations for CATE

What does the local estimating equation look like under CATE?

- the estimating equations $\psi$ with possibly multi-dimensional treatments

\begin{align}\psi_{\beta(x), c(x)}\left(Y_{i}, W_{i}\right)=\left(Y_{i}-\beta(x) \cdot W_{i}-c(x)\right)\left(1 \quad W_{i}\right)\end{align}

- Note: $\left(1 \quad W_{i}\right)$ which implies there is a vector of equations

## Estimation equations for CATE (2)

What is the solution?
- Run local regression of $y_i$ on $W_i$ with weights $\alpha$

\begin{align}\hat{\theta}(x)=\xi^{\top}\left(\sum_{i=1}^{n} \alpha_{i}(x)\left(W_{i}-\bar{W}_{\alpha}\right)^{\otimes 2}\right)^{-1} \sum_{i=1}^{n} \alpha_{i}(x)\left(W_{i}-\bar{W}_{\alpha}\right)\left(Y_{i}-\bar{Y}_{\alpha}\right)\end{align}

## Main results

Athey et al (2019) show that Generalized Random Forests have the following propeties:

- Estimates, $\hat{\theta}(x)$, are consistent (Theorem 3)
- Asymptotic normality of estimates (Theorems 5,6)

# Application of causal forest

## Note about software

Use `econml` implementation

- Not implementation of generalized random forest
- Uses double machine learning to unconfound data
- Caution:
  - If you want to use in research - use GRF package by Athey et al.

## Make synthetic data

```python
import numpy as np

n, p = 1000, 10
X = np.random.RandomState(0).randn(n,p)
P = 0.4 + 0.2 * (X[:, 0] > 0)
T = (np.random.rand(n) > P).astype('float')
Y = np.max([X[:, 0] * T, np.zeros(n)],0) + \
    X[:, 1] + np.max([X[:, 2] * T, np.zeros(n)],0)
```

## Apply  causal forest from `econml`

Note - we use double maching learning here to unconfound treatment assignment and control for confounders of outcome

```python
t0,t1 = 0,1
from econml.dml import CausalForestDML
from sklearn.ensemble import GradientBoostingRegressor
est = CausalForestDML(model_y=GradientBoostingRegressor(),
                      model_t=GradientBoostingRegressor())
est.fit(Y, T, X=X, W=X)
```

    <econml.dml.causal_forest.CausalForestDML at 0x7fef1c314a30>

## Getting predicted treatment effects for x range

```python
n_plot = 1000
X_range = np.zeros([n_plot,p])
X_range[:,0] = np.linspace(-2,2,n_plot)
Y_dgp = np.max([X_range[:, 0] * np.ones(n_plot), np.zeros(n_plot)],0) + X_range[:, 1] + np.max([X_range[:, 2] * np.ones(n_plot), np.zeros(n_plot)],0)

tau_hat = est.effect(X_range, T0=t0, T1=t1)
tau_lb, tau_ub = est.effect_interval(X_range, T0=t0, T1=t1, alpha=0.05)
```

## Plotting treatment effects

```python
from matplotlib import pyplot as plt
%matplotlib inline
f,ax = plt.subplots(figsize=(8,6))
ax.plot(X_range[:,0], Y_dgp, color='red', label='Data Generating Process')
# plt.plot(X_range[:,0], tau_hat)
ax.fill_between(X_range[:,0], tau_lb, tau_ub, label="Causal Forest 95% CI")
ax.legend()
```

# Comparison
### Causal Forests and Generalized Random Forests

## Causal Trees and Forests
Strong econometric properties
- unbiased and consistent (trees and forests)
- asymptotic normality given $x$ (forests)

- weaknesses:
  - either unconfounding or heterogeneity
  - we "use" data to buy honesty at the price of statistical power

## Generalized Random Forest
Difference from Causal Forest - trees are used for constructing weights!
- strengths:
  - unconfounding (propensity) *AND* heterogeneity
  - additional uses
      - quantile regression
      - instrumental variables
      - clustered standard errors
      - and more
- weakness:
  - we "waste" data on honesty