Euler Problem 73
================

Consider the fraction, n/d, where n and d are positive integers. If n < d and HCF(n,d)=1, it is called a reduced proper fraction.

If we list the set of reduced proper fractions for d ≤ 8 in ascending order of size, we get:

1/8, 1/7, 1/6, 1/5, 1/4, 2/7, 1/3, 3/8, 2/5, 3/7, 1/2, 4/7, 3/5, 5/8, 2/3, 5/7, 3/4, 4/5, 5/6, 6/7, 7/8

It can be seen that there are 3 fractions between 1/3 and 1/2.

How many fractions lie between 1/3 and 1/2 in the sorted set of reduced proper fractions for d ≤ 12,000?

```python
from sympy import sieve
import numpy as np

N = 12000
mu = np.ones(N+1, dtype=int16)
for p in sieve.primerange(1, N+1):
    for k in range(p, N+1, p):
        mu[k] *= -1
    pp = p*p
    for k in range(pp, N+1, pp):
        mu[k] = 0
g = lambda n: int((n-2.0)**2/12+0.5)
print sum(mu[d] * g(N/d) for d in xrange(1, 10000))
```

    7295372

**Discussion:**

Let $g(n)$ denote the number of pairs of positive integers $(x,y)$ such that $y \le n$ and $\frac13 < \frac{x}{y} < \frac{1}{2}$.
Let $f(n)$ denote the number of pairs with the above properties and the additional condition that $x$ and $y$ are relatively prime. Then
$$g(n) = \sum_{k=1}^n f(n/k).$$
By the Mobius inversion formula,
$$f(n) = \sum_{k=1}^n \mu(k) g(n/k).$$
It can be shown that if $n$ is an integer, $g(n)$ is the integer nearest to $\frac1{12} (n-2)^2$.

Putting this information together gives an efficient algorithm for calculating $f(n)$.

```python

```

Euler 073 - Counting primes in a range.ipynb


```
import scipy.stats as ss
import numpy as np
c = ss.norm(0,1).cdf(-2)
print("the exact price is", c)
```

    the exact price is 0.022750131948179195

```
def OMC(N,alpha):
  s=0
  for i in range(N):
    if np.random.normal(0,1)<-2:
      s+=1
  return s/N
```

```
OMC(1000,2)
```

    0.028

```
def IS(N,alpha):
  s=0
  for i in range(N):
    A= np.random.normal(-alpha,1)
    if A<-2:
      s+= np.exp(alpha**2/2 + alpha*A)
  return s/N
```

```
IS(1000,2)
```

    0.022493176379587303

```
var_IS=0
var_omc=0
for i in range(10000):
  var_omc+= (c - OMC(1000,2))**2
  var_IS += (c - IS(1000,2))**2
  mse_omc = var_omc/10000
  mse_IS = var_IS/10000
print(mse_omc)
print(mse_IS)
```

    2.2159788036515605e-05
    1.235122055437548e-06

```
def MSE_OMC(alpha_hat,N):
  for i in range (N):
    x=ss.norm(0,1).cdf(-2)
  MSE_OMC = (1/N)*(x-x**2)
  return MSE_OMC
```

```
MSE_OMC(3,10000)
```

    2.2232563444519632e-06

\begin{equation}
\begin{aligned}
MSE(\hat{v}) &= var(\hat{v}) \\
&= \frac{1}{n} var(I(Y_{i} < - 2) e^{\frac{1}{2} \alpha^{2} + \alpha Y_{i}}) \\
&= \frac{1}{n} \{ \mathbb{E} [I(Y_{i} < - 2) e^{ \alpha^{2} + 2 \alpha Y_{i}}] - \Phi^{2}(-2) \}. \\
\end{aligned}
\end{equation}

\begin{equation}
\begin{aligned}
\mathbb{E} [I(Y_{i} < - 2) e^{ \alpha^{2} + 2 \alpha Y_{i}}] &= \int_{- \infty}^{-2} e^{\alpha^{2}+ 2 \alpha y}  \frac{1}{\sqrt{2 \pi}} e^{- \frac{(y + \alpha)^{2}}{2}} \, d y \\
&= \int_{- \infty}^{-2} \frac{1}{\sqrt{2 \pi}} e^{- \frac{y^{2} - \alpha y - \alpha^{2}}{2}} \, d y \\
&= \int_{- \infty}^{-2} \frac{1}{\sqrt{2 \pi}} e^{- \frac{(y - \alpha)^{2}}{2}}  e^{\alpha^{2}} \, d y  \\
&= e^{\alpha^{2}} \Phi(-2-\alpha),
\end{aligned}
\end{equation}

$$MSE(\hat{v}) = \frac{1}{n} \{e^{\alpha^{2}} \Phi(-2-\alpha) - \Phi^{2}(-2)\}.$$

```
def MSE_IS(alpha_hat,N):
  for i in range (N):
    x=ss.norm(0,1).cdf(-2)
    y=ss.norm(0,1).cdf(-2-alpha_hat)
    mse_IS = (1/N)*np.exp(alpha_hat**2)*y-(x**2)
  return mse_IS
```

```
MSE_IS(2,10000)
```

    -0.0005173955845382296

```
diff = MSE_OMC(3,10000)-MSE_IS(3,10000)
```

```
diff
```

    0.0005195594838295247

as we can see, as n is big enough, the mse of omc and IS is converge to 0, and under same alpha, the mse of IS is less than mse of omc.

```

```


# Model project - Externalities and Pigou taxes

Our model project consists of a microeconomic model describing the inefficiencies of pollution from production from a social economic point of view. We introduce a demand and a supply function, but the production of the suppliers is associated with a negative externality cost.

We apply model analysis methods to find the market and social equlibria output and prices, where we include graphs to illustrate these equlibria. As a method to solve this social inefficiency from the market powers, we introduce a Pigou tax and calculate the optimal size of this.

**1: Setup**

```python
#Importing the relevant packages

import sympy as sm
from sympy import *
import numpy as np

import scipy as sp
from scipy import optimize

import matplotlib.pyplot as plt

import ipywidgets as widgets
from ipywidgets import interact, fixed

sm.init_printing(use_unicode=True) #This code enables pretty printing for the mathematical symbols we will use
```

```python
#Defining the relevant variables and parameters from our model with the sympy symbols function
xd = sm.symbols('x_d') #The amount of goods demanded by consumers
xs = sm.symbols('x_s') #The amount of goods supplied by suppliers
A = sm.symbols('A') #The price consumers are willing to pay if they can only get an infinitely small amount of goods (assumed declining willingess to pay)
B = sm.symbols('B') #The price the suppliers are willing to sell for if they can only sell an infinitely small amount of goods
p = sm.symbols('p') #The price of goods
alpha = sm.symbols('alpha') #A measure of the consumers' sensitivity to changes in prices
beta = sm.symbols('beta') #A measure of the suppliers' sensitivity to changes in prices
delta = sm.symbols('\delta') #An abritrarily chosen multiplier that creates the negative externality through production
x = sm.symbols('x') #The quantity of the goods traded
xc = sm.symbols('xc') #Used for plotting x
deltax = sm.symbols('deltax') #Used for plotting delta
```

```python
#Checking whether the variables and parameters are correctly defined
xd, xs, A, B, p, alpha, beta, delta, x
```

**2: The Model**

To set up our model, we firstly introduce the following demand and supply functions from the consumers and suppliers of the economy respectively including the negative externality function. It is a simple economy-setup with only one representative consumer, one representative supplier and a single type of good. The agents seek to trade based on the following equations. The producers and the consumers does not care about the negative externality and therefore this doesn't impact their trading behaviour. The equations are as follows:

Demand: $x_{d}=\frac{A-p}{a}$

Supply: $x_{s}=\frac{B+p}{\beta}$

Negative externality: $C_E(x)=(\delta x)^2$

Firstly, we define the demand, supply and negative externality cost functions as follows:

```python
demand = (A-p)/alpha
supply = (B+p)/beta
externality = (delta*x)**2
demand, supply, externality #Prints the three definitions
```

Firstly, from the demand and supply functions we can calculate the market price for the good by setting the two equal to each other and solving for $p$. This yields:

```python
#Setting demand and supply equal to each other and solving for p
Marketprice = sm.solve(sm.Eq(demand,supply),p) #We use the sympy .Eq function to set the two equal to each other
#and use the sympy solve function to solve for p in this equation. Marketprice)
Marketprice
```

From this result we see that the price - intuitively - is positively dependent on the consumers' initial valuation of the goods and negatively of the producers' initial valuation. This result can be inserted in either the demand or the supply function to obtain the amount of traded goods in equilibrium where supply equals demand.

```python
#Finding the equilibrium output by inserting the marketprice into the demand function
Marketoutput = demand.subs(p, Marketprice[0])
#We use the .subs method to insert the marketprice result from before instead of p in the demand function.
#As the marketprice expression is defined as a list-type, we include the index [0] to refer to the expression herein.
sm.simplify(Marketoutput) #This function simplifies the marketoutput expression.
```

```python
#We can check whether we get the same result by inserting the market price equilibrium into the supply function
CheckMarketoutput = supply.subs(p, Marketprice[0]) #Same calculation procedure as in code cell above.
sm.simplify(CheckMarketoutput)
```

Luckily, we find that the two results are identical, which shows that the found price should be correct.
From the marketoutput expression we once again see, that more goods are traded if consumers are willing to pay high prices for initial goods (through A) and suppliers are willing to supply them cheaply (through B). We also see that it depends negatively on the price sensitiviy of both the agents.

Unfortunately, the production from the suppliers also create a negative externality due to pollution or some other externality. This is assumed to have a convex negative impact on society. This convex function can be seen from the graphical depiction below, where we as an example have set $\delta=1$.

```python
#We impose an externality cost of production due to emission
delta = 1
xc = np.linspace(0,2)
ExternalityCost = (delta*xc)**2
plt.plot(xc,ExternalityCost)
plt.xlabel("Quantity")
plt.ylabel("Costs from externality")
plt.title("The convex cost function of the externality")
plt.show
```

In order to find the social optimal quantity produces and the associated price, we start by calculating the marginal cost of the externality below. From this the convex nature of the externality is once again evident. We get:

```python
#Finding the marginal externality cost by differentiating w.r.t. x
MarginalExternalityCost = sm.diff(externality, x) #Using the sympy function "diff" to differentiate externality wrt.x
MarginalExternalityCost #Printing the result
```

We now also need to find the inverse supply function, which shall be added to the marginal externality cost to give us the social marginal cost of production.

```python
#Private marginal cost (the inverse supply function)
PrivateMarginalCost = sm.solve(sm.Eq(supply,x),p) #We set the supply expression equal to x and solve for p.
PrivateMarginalCost
```

```python
#Social marginal cost is the sum of the private marginal cost and the marginal externality cost
SocialMarginalCost = PrivateMarginalCost[0] + MarginalExternalityCost
SocialMarginalCost
```

Seen above is the social marginal cost function, which takes the negative effects of the externality into account and adds it to the supply function. As $\delta>0$, the social marginal cost will be larger than the private cost from the suppliers. The social marginal cost curve will thus have a steeper slope than the supply curve.
To now finally find the socially optimal amount of traded goods and the associated price, we start by finding the inverse demand function:

```python
#Inverse demand curve
InverseDemand = sm.solve(sm.Eq(demand,x),p)
InverseDemand
```

And we now set this inverse demand function equal to the social marginal cost and solve for $x$ to find the optimal amount of traded goods:

```python
#Finding the social optimal output by setting the demand function equal to the social marginal cost
SocialOptimal = sm.solve(sm.Eq(InverseDemand[0], SocialMarginalCost), x)
SocialOptimal
```

Now to finally find the optimal price, we insert this expression into the demand function:

```python
SocialOptimalPrice = sm.solve(sm.Eq(demand,SocialOptimal[0]),p)
SocialOptimalPrice
```

Which is the optimal price when considering the externality.

**3: Graphing the economy**

To give a graphical overview of the economy, we plot a graph below, where it is possible to change the value of the parameter $\delta$ to give an insight in how the social cost and thereby the optimum depend greatly on this parameter.

```python
def PlotGraph(A, alpha, beta, B, delta):
    #This function is able to plot the graphs of the demand, supply and SMC-functions with different parameter values.

    x = np.linspace(0,200) #Here we choose over which span the x quantity runs in the graph.
    d = A-alpha*x #Defining the demand function
    s = beta*x-B #Defining the supply function
    smc = x*(beta+2*delta**2)-B #Defining the social marginal cost function

    plt.xlabel("Quantity") #Labelling x-axis
    plt.ylabel("Price") #Labelling y-axis
    plt.grid() #Putting a grid in the background of the graph
    plt.title("Supply, demand and social marginal cost") #Adding title to graph
    plt.plot(x, d, label="D") #Plotting and labelling demand function
    plt.plot(x, s, label="S") #Plotting and labelling supply function
    plt.plot(x, smc, label="SMC") #Plotting and labelling SMC function
    plt.legend(loc="upper right") #Choosing to put label names in upper right corner.

```

```python
widgets.interact(PlotGraph,A=widgets.fixed(800), alpha=widgets.fixed(4),
                 delta=widgets.FloatSlider(description="$\delta$", min=0.0, max=2 , step=0.05, value=1),
                 B=widgets.fixed(0), beta=widgets.fixed(2))
#These lines of code use the graphing function "PlotGraph" and adds a Floatslider, so the user can adjust
#the value of the delta parameter.
```

    interactive(children=(FloatSlider(value=1.0, description='$\\delta$', max=2.0, step=0.05), Output()), _dom_cla…

    <function __main__.PlotGraph(A, alpha, beta, B, delta)>

From this graph we clearly see that when $\delta$ increases the socially optimal price also increases and thereby the quantity traded will be reduced. When $\delta$ on the other hand reaches zero, the SMC and supply curve will be identical - so in the lack of externalities the social optimum will also be the market optimum.

In this example, when externalities are present this is however not the case as neither the consumers nor the producers cares about the externality. To take this into account and reach the social optimum, we will now look at the effects of introducing a Pigou tax.

**4: Pigou taxes**

A Pigou tax is a tax that aims at correcting ineffecient market outcomes as with the current example. The tax will aim at increasing the price level of the specific good with the externality and thus, hopefully, affect the trading behaviour so the externality is reduced optimally. The optimal size of the Pigouvian tax is the difference between what the consumers are willing to pay and what the suppliers are willing to sell their goods for at the socially optimal traded quantity.

We have already found the price level for the consumers in the social optimum, and now only need to find the price at which the suppliers are willing to sell at the social optimum. These two prices are:

```python
#Inserting the social optimal output into the supply function
SocialOptimalSupply = sm.solve(sm.Eq(SocialOptimal[0], supply), p)
SocialOptimalSupply,SocialOptimalPrice
```

And now we simply subtract the two from each other to get:

```python
#The optimal pigou tax is the difference between the demand and supply
PigouTax = SocialOptimalPrice[0] - SocialOptimalSupply[0]
sm.simplify(PigouTax)
```

Which is then the optimal size of the Pigouvian tax, that can bring the two agents of the economy to trade the desired level of goods from a social point of view. We will quickly have a graphical look at how the size of this tax is affected by the size of $\delta$ as this is not necessarily clear from the expression above:

```python
#First we choose the parameter values for the graphing example below
A = 800
B = 0
alpha = 4
beta = 2

deltax = np.linspace(0,8) #Choosing the span in which delta should be plotted on the graph

Pigoutax = (2*deltax**2*(A+B))/(2*deltax**2+alpha+beta) #Defining the function for the Pigouvian tax

plt.plot(deltax,Pigoutax) #Plotting the graph and adding labels and titles.
plt.xlabel("Delta")
plt.ylabel("Pigou")
plt.title("Pigoutax")
plt.show
```

As before we have arbitrarily set the parameter values - specifically $A=800, B=0, \alpha=4$ and $\beta=2$. We see that as $\delta$ increases so does the optimal size of the Pigouvian tax. The optimal size is concave wrt. the size of $\delta$ and converges towards the size of A, which is the highest possible amount a consumer will be willing to pay for an infinitely small amount of goods.

**5: Solving the model numerically**

We will now solve the model numerically, where we assign arbitrary value to the parameters of the model and calculate the equilibrium outcomes with and without externalities. We recall that the equlibria are given as:

Private market equilibrium price: $p^{opt}=\frac{A \beta-B \alpha}{\alpha+\beta}$

Private market equilibrium output: $x^{opt}=\frac{A+B}{\alpha+\beta}$

Social market equilibrium price: $p^{soc}=\frac{2A\delta^2+A\beta-B\alpha}{2\delta^2+\alpha+\beta}$

Social market equilibrium price: $x^{soc}=\frac{A+B}{2\delta^2+\alpha+\beta}$

We will continue to use the parameter values that we used in the graphs above, that is: $A=800$, $B=0$, $\alpha=4$, $\beta=2$, $\delta=1$

```python
#We find the market equilibria with and without the externality included given the chosen parameter values
MarketEquilibriumPrice_num = optimize.fsolve(lambda p: (A*beta-B*alpha)/(alpha+beta)-p,0)
MarketEquilibriumOutput_num = optimize.fsolve(lambda x: (A+B)/(alpha+beta)-x,0)
SocialEquilibriumPrice_num = optimize.fsolve(lambda p: (2*A*delta**2+A*beta-B*alpha)/(2*delta**2+alpha+beta)-p,0)
SocialEquilibriumOutput_num = optimize.fsolve(lambda x: (A+B)/(2*delta**2+alpha+beta)-x,0)

print(f'The equilibrium price in the economy without externality costs is: {MarketEquilibriumPrice_num}')
print(f'The equilibrium output in the economy without externality costs is: {MarketEquilibriumOutput_num}')
print(f'The equilibrium price in the economy with externality costs is: {SocialEquilibriumPrice_num}')
print(f'The equilibrium output in the economy with externality costs is: {SocialEquilibriumOutput_num}')
```

    The equilibrium price in the economy without externality costs is: [266.66666667]
    The equilibrium output in the economy without externality costs is: [133.33333333]
    The equilibrium price in the economy with externality costs is: [400.]
    The equilibrium output in the economy with externality costs is: [100.]

Thus, we have now numerically solved the equilibria with and without the negative externality cost. We know from the graph, that when we included this externality, the prices would increase and the output decrease. We now see that including the externality cost raises the equilibrium price from 267 to 400, while the output falls from 133 to 100.

As a method to correct this market inefficiency, we introduced a Pigouvian tax to reach this social market equilibrium. Given the chosen parameter values, we can find this optimal size:

```python
PigouTax_num = optimize.fsolve(lambda t: (2*delta**2*(A+B))/(2*delta**2+alpha+beta)-t,0)
print(f'The optimal size of the Pigouvian tax in the economy is: {PigouTax_num}')
```

    The optimal size of the Pigouvian tax in the economy is: [200.]

This optimal size of the tax means that there is a difference of 200 between the price that the buyer pays and the price that the seller receives for one unit of the good. Thus, when the buyer pays 400 for one unit of the good, the seller only receives 200. The remaining 200 goes to the government.

**6: Conclusion**

This model project has showed us how the presence of externalities in a market can cause differences between the market optimum and the social optimum. The larger the externality cost, the larger the difference between these two optimums will be. This is a very relevant insight as many parts of the real world economy are faced with similar issues, where many agents' behavior and incentives contradicts with what the society desires as a whole.

In the second part of the project, we introduced an instrument to fix potential market inefficiencies and lead the agents in the market towards the social optimum of goods traded. Of course this is a simple microeconomic setup, and in the real world it is impossible to identify the optimal size of the Pigouvian tax, as the exact desires and incentives from the agents of the economy are unknown. However, this tax is an effective way to reduce market inefficiencies, though it is impossible to get rid of all of them.

In the third and final part of the project, we solved the model numerically where we signed arbitrary values to the parameters of the model. Unsurprisingly, we found the output equilibrium to decrease and the price equilibrium to increase, when we included the negative externality in the model - which was similar to what we found from the graphical inspection. It's important to note that we have chosen the parameter values arbitrarily and changes to any of the variables will affect the equilibria.


```python
## Definitions and Framing

* **Supervised Machine Learning:**

    Using known data to generate some useful predictions of on unseen data

    Label $y$: The target variable that we are trying to predict, for example 'spam' or 'not spam'.

    Features: Something about the data that is used to represent the data, that is later fed into a model. Complicated machine learning models can have multiple features $x_1, x_2, \ldots, x_n$. So this is a vector $\mathbf{x}$.

    Model: Maps unseen data to predictions of label $y$. It is defined by internal parameters that are learned using a training set of labeled data

    Labeled data can be represented as $(\mathbf{x}, y)$. In unlabeled data, $y$ is not known and is predicted by the model.

* **Models**

    A model defines the relationship between the features and the label. There are two key phases in the life of a model: <br>

    Training: The phase where you the model is trained or learned. You show the model a number of examples of labeled data, and allow the model to learn the relationship between the features and the label. In other words, it is learning the values of the parameters in the model. These parameters in the model are often called hyperparameters.

    Inference: The phase where the model is used to generate labels $y'$ given features $\mathbf{x}$.
* **Regression vs. Classification**

    A regression predicts continuous values while a classification predicts discrete values.

## Descending into ML

Topics covered: Linear Regression, Training and Loss

* The $L_2$ loss is defined as

$$
\begin{align}
L_2 \textrm{ Loss } = \sum\limits_i (y_i - prediction_i(\mathbf{x}))^2
\end{align}
$$

where we sum over all data points in the training set $i$.

* The convention in machine learning is to represent the linear model as $y' = w_1 x_1 + b$ instead of the more traditional $y = mx + b$. We could easily generalize this regression from one feature to multiple features and the prediction would be given by $y' = w_1 x_1 + w_2 x_2 + w_3 x_3 + b$, where all the $w_i$'s are weights and all the $x_i$'s are features. The process of fitting the model is called training and the process of using the trained model to make a prediction is called inference.

* A commonly used loss function is the mean squared error (MSE), but this is neither the only loss function nor the best or most practical loss function for all cases.

## Reducing Loss

* One of the popular ways to find the minimum in the loss function is to use Gradient Descent. We calculate a gradient at each point and move in the direction of decreasing gradient. The step size that we use as we advance in the direction of decreasing gradient is called the learning rate. This has to be chosen carefully: in multi-dimensional problems, too large a learning rate can cause the code to become unstable.

* We can also get trapped in local minima if the space is *not-convex*. There is a whole sprawling field of non-convex optimization.

* There are two important flavors of gradient descent:

    **Stochastic Gradient Descent:**

    In theory, while using gradient descent, we need to calculate the gradient of the loss function considering all examples. However in practice, this is found to be excessive and computationally expensive. We therefore select only one example at random and calculate the gradient of the loss function considering only that one example. This is called stochastic gradient descent. Although this might require more steps to reach the optimum, overall there is usually less computation when dealing with very large data sets. Gradient calculations can be very expensive.

    Mathematically, we want to calculate

    $$
    \begin{align}
    w_{n+1} = w_n -\eta Q(w)
    \end{align}
    $$

    where $\eta$ is the learning rate and $Q(w) = \frac{1}{N}\sum_i Q_i(w)$ is the $L_2$ loss for the $i$-th example. What we do instead, in stochastic gradient descent is to first randomnly pick a particular example $k$ and calculate the loss function $Q_k(w)$ and then update $w$ using:

    $$
    \begin{align}
    w_{n+1} = w_n -\eta Q_k(w)
    \end{align}
    $$

    and perform this iteratively until the minimum criterion is reached.

    When there are multiple parameters to optimize, the gradient is of course a vector, and so is $\mathbf{w} = (w_1, w_2, \ldots)$ and we proceed in the direction of steepest gradient in steps $\eta$ large.

    **Mini-batch Gradient Descent**

    This is very similar to stochastic gradient descent except that instead of taking only one data point, we take batches of 10 or 100 of them.  Especially with datasets that contain duplicates, enormous datasets do not contain any further information than very large datasets. Mini-batch gradient descent exploits this fact and works very well in practice. Typical values of batch-size range between 10 and 1000. Stochastic gradient descent is an extreme example of mini-batch gradient descent with batch size 1.

* The algorithm to train the model in this case is iterative: we start with some intial guesses for the parameters, compute the loss function for those values, update the values of the parameters through some scheme (with the goal of moving in the direction of lower loss), calculate the loss for the updated values of the parameters, and proceed iteratively until we achieve convergence. Convergence is usually assumed when the loss becomes below some threshold value, or if the loss function starts changing extremely slowly.

## First steps with TensorFlow

* TensnsorFlow is a computational framework that allows you to build machine learning models. We can use both lower level APIs by defining models based on a series of mathematical operations or we could use predefined higher level APIs such as `tf.estimator`. These architectures can be linear regressors or neural networks.

* TensorFlow consists of the following two components:

    0. A graph protocol buffer
    0. A runtime that executes the distributed graph

* The **graph protocol buffer** or protobuf takes data structures written in a text file and then generates classes in Python (or other language) that allows you to load, save and interact with the data in a user friendly way. In this sense the protobuf and the runtime are akin to Python code and a Python interpreter.

* Because TensorFlow is built using APIs of various levels of abstraction, we have a choice of level. In general, I should chose the layer that offers the highest level of abstraction. Of course, the highest layers are also less flexible, so for some special modeling cases if I need more flexibility, I can just drop one run lower in the level of API.

### `tf.estimator` API

* The `tf.estimator` API is one of the highest level APIs that has a lot of prepackaged tools of use. Below we show some code to exemplify its use. In the example below, we are going to estimate the median housing pricebased on just one input feature. The data is from the 1990 California housing census data. Available [here](https://developers.google.com/machine-learning/crash-course/california-housing-data-description)

    First we perform some imports

    ```python
    import math
    from matplotlib import cm
    from matplotlib import gridspec
    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    from sklearn import metrics
    import tensorflow as tf
    ```
    First, let us load the data

    ```python
    california_housing_dataframe = pd.read_csv('https://download.mlcc.google.com/mledu-datasets/california_housing_train.csv', sep=',')
    ```

    We first want to randomize the data so that there are no unknown artifacts while performing the stochastic gradient descent. We also want to scale the value of `median_house_value` to be in the units of thousands so that we can use a reasonable and familiar learning rate.

    ```python
    california_housing_dataframe = california_housing_dataframe.reindex(
        np.random.permutation(california_housing_dataframe.index))
    california_housing_dataframe['median_house_value'] /= 1000
    ```
```


## Exercise 1 (5 points): Independent Component Analysis [Python]

While PCA (and SVD for that matter) tries to seek new axes which are designed to explain the variance in the data and hence remove correlations between feature dimensions (the covariance matrix of PCA transformed data is equal to the identity matrix), one can go even one step further and also remove statistical dependence between the data which is stronger than any correlation
removal technique. As a result, we retrieve the independent components which produced the original data. The algorithm designed for this purpose is called independent component analysis (ICA) and in this exercise, we are going to take a look at it.

Note: this exercise is based on the introduction to ICA in [1]. In case you have any questions about the theoretical background of the calculations we are going to perform here, feel free to read the corresponding section in this (very well written) paper.

In ICA, we assume a linear mixture 𝐴 between the sources in 𝑆 producing the output 𝑋. A common example is the so-called cocktail party problem [1] where we have two independent audio sources (e.g. music and voice) and two different recordings of the sources (e.g. two microphones). A linear combination of the two produced signals reaches each microphone so that our recordings 𝑋 are mixed together $as^1$

\begin{equation}X = SA \end{equation}

where both, 𝑋 and 𝑆, are matrices with the columns representing variables (here audio sources) and the rows observations (audio samples over time). In our case, we are using two sources which were manually combined over 10k samples. Hence, $X, S \in \mathbb{R}^{10000×2}$ and $A \in \mathbb{R}^{2×2}$. You only get the two “recorded” audio signals in 𝑋 and your task is to find the original source signals in $S$.

Mathematically, we need the inverse of the matrix 𝐴 to recover 𝑆. A common approach is to
simplify this by applying SVD first on 𝐴 so that the problem reduces to find three individual
matrices [1]
    \begin{equation}S = XA^{−1} = X(U\Sigma VT)^−1 = XV\Sigma^{-1}U T.\end{equation}

In the following, plot the result after each calculation. A summary of all plots which you should have in the end is shown in Figure 1. If you want to play the audio files in your Jupyter notebook, you can use the Audio function from the IPython.display package. For the test script, please pay also attention to Table 1.

1. The two audio signals are stored in the files $mixedA.wav$ and $mixedB.wav$. Load both files into your Python program and store them in one matrix (this is 𝑋). Note: the WAV files use the 32-bit floating-point format so that the loaded signal values should already be in the range [−1; 1].

2. Calculate the centralized data $X_c$ by subtracting the (column) mean $\mu_i$

\begin{equation}X_c = X − (\mu_1 \hspace{0.2cm} \mu_2) \end{equation}

3. We approach Equation 1 from the left side. It turns out that 𝑉 contains the eigenvectors of $X_c^T X_c$ in the columns and $\Sigma$ stores the square root of the eigenvalues of $X_c^TX_c$ on the diagonal (even though SVD was applied to 𝐴 and not $X_c$). Hence, we need to apply PCA to $X_c$ first and use the result to transform the data
\begin{equation}X_w = X_c V \Sigma^{-1} \end{equation}

This process is called whitening or sphering [1] of the data and there is also an option in sklearn.decomposition.PCA which you can use to calculate $X_w$.

4. The next step is to calculate $U^T$. To do so, we exploit the fact that $U^T$ is essentially a rotation matrix. This leads to a new (and not really scalable) approach to find $U^T$: we simply try a few rotation angles 𝜃 and choose the one which fits best.
To find out if a rotation matrix leads to a good or bad result, we exploit the fact of assumed independence of the signals and use a measure which tells us how independent our data currently are. This can be achieved by minimizing the so-called multi-information [1] of the rotated data2
\begin{equation}\theta^* = argmin_{\theta} \sum_{i=1}^2H ((X_wR(\theta))_i)\end{equation}
where H calculates the entropy based on the 𝑖-th column of the data transformed via the rotation matrix $R(\theta)$.

    a) Calculating the entropy of a vector 𝒙 ∈ ℝ10000 is not really applicable since it is not unlikely that most of the values occur only once leading lead to a very high entropy value which may not even change for different 𝜃 values. One way to overcome this issue is by grouping similar values together and then calculate the entropy from these groups. More concretely, this can be achieved by calculating a normalized histogram so that we have a discrete probability 𝑃(𝑖) for each of the 𝐵 bins. Then, we can simply apply the formula of the entropy
\begin{equation}H = \sum_{i=1}^BP(i).log_2(P(i))\end{equation}
Write a function gentropy(values) which calculates the entropy based on this histogram approach. The number of bins 𝐵 is crucial here since it determines how many values are grouped together and are hence considered equal for the entropy calculation. You can set it to 𝐵 = 500.

    b) Test 180 different angular values from the range [0; 𝜋] and calculate the multiinformation with the help of your gentropy function and extract the angular value $\theta^*$ with the lowest corresponding multi-information.

5. We now have all ingredients together to reconstruct the original signals.

    a) Apply the final transformation matrix
\begin{equation}X_r = X_wU^T = X_wR(\theta^*)\end{equation}

    b) We can’t reconstruct the original amplitude values with ICA. Hence, the reconstruction is only correct up to a scaling factor. To avoid overdrive, scale the reconstructed signals $X_r$ to the range [−1; 1] via min-max scaling.

## Exercise 2 (5 points): Autoencoder [Python]

An autoencoder is an unsupervised learning technique based on neural networks which can be used to learn new features. The general idea is to construct a network hierarchy with an equal number of neurons in the input as well as in the output layer and use the input also as the teaching signal. Further, a bottleneck is introduced in the middle of the network hierarchy so that a simple forwarding of the signal is not possible. Instead, the network is forced to learn representative features to recover the data even when only a subset of the information is available.

There are three common use cases of an autoencoder: data denoising, variation learning and dimensionality reduction. In this exercise, we are going to apply the latter to a small example in order to explore the basics of autoencoders. We have already covered some dimensionality reduction techniques and it turns out that there is a relationship between autoencoders and PCA. We take this as a starting point and then extend to the general concept of non-linear PCA (with similar results as kernel PCA), i.e. instead of lines as main axes of our new coordinate system (eigenvectors) we now may end up with more arbitrary shapes which can reflect the data distribution better.

This exercise comes with some predefined code in the Autoencoder.ipynb Jupyter notebook. Use this file as the starting point for your implementation. As you can see, we are using a two-dimensional dataset here so that our projections are going to be one-dimensional. For the test script, please pay also attention to Table 2.

1. We can construct a very simple autoencoder for our dataset by using two input neurons, one neuron in the coding layer (bottleneck) and two output neurons as well as linear transfer functions. Then, we basically compute PCA and the output of the coding layer corresponds to the projections (also called codings) and the output of the network to the recovered data points based on these projections.

    a) This type of autoencoder is already implemented. Familiarize yourself with the code and execute it.

    b) As a comparison, compute PCA also in the normal way and extract the first eigenvector $\mu_1$.

    c) Visualize the data, the output of the autoencoder, the line of the eigenvector $\mu_1$ and some contour lines which show how the data are projected onto the new axis. That is, all points on one contour line map to the same projection value. A possible result is shown in Figure 3 (a)

2. We now want to change the autoencoder so that it performs non-linear PCA instead.

    a) Implement the hierarchy as shown in Figure 4 with the following notes in mind:
        • The en- and decoder now include an additional hidden layer with 20 neurons each.
        • The hidden layers use tanh(𝑥) as transfer functions and the code as well as the output layer use linear transfer
        functions.
        • It is recommended to constrain the autoencoder network in some way or another to get good codings. Here, we want to regularize all weights by adding an L1 penalty term to the error function
\begin{equation}E = E_0 + \lambda \sum_w |w| \end{equation}
        with the base loss $E_0$ (MSE) and the regularization term 𝜆 = 0.002. Note: you need to find out yourself how this can be implemented in TensorFlow.

    b) Visualize the result in the same way as before (without the eigenvector $\mu_1$). A possible solution is shown in Figure 3 (b)

3. Do you think that it would be a good idea to use a non-linear transfer function (like tanh(𝑥)) in the output layer (for our dataset)? If not, why?

4. Figure 2 shows a histogram of the projected values (the codings) of both autoencoders. As you can see, the histogram of the non-linear autoencoder is more balanced whereas in the linear case we have relatively clear peaks. Explain why we observe these differences.


In this notebook, we'll explore chemical reaction balance using python. We'll use atomic balance equations along with Python's built-in sympy package.

Notes:
1. v<molecule name> represents the co-efficient of that molecule.

```python
import numpy as np
import sympy as sm
import scipy as sp
```

Methane Combustion Reaction:

To balance this equation, we will use atomic balance. For each atom, a balance equation can be written.

```python
# creating variables from molecule name
sm.var(['vCH4','vO2','vCO2','vH2O'])

# writing atomic balance equation in sympy format
atom_balances = [
    sm.Eq(vCH4 + vCO2, 0), # Carbon (C) balance
    sm.Eq(4*vCH4 + 2*vH2O, 0), # Hydrogen (H) balance
    sm.Eq(2*vO2 + 2*vCO2 + vH2O, 0) # Oxygen (O) balance
]

# checking the atomic balance equations
for eqn in atom_balances:
    print(eqn)
```

    Eq(vCH4 + vCO2, 0)
    Eq(4*vCH4 + 2*vH2O, 0)
    Eq(2*vCO2 + vH2O + 2*vO2, 0)

Now, we need a basis equation to balance the equations. As, this is a combustion, we can take methane and basis as -1.

```python
# basis equation vCH4 and -1
basis_eqn = [sm.Eq(vCH4, -1)]

# checking the basis equations
for eqn in basis_eqn:
    print(eqn)
```

    Eq(vCH4, -1)

Now, let's solve the atom balance equations and basis equations using sympy module.

```python
# solving the atomic balance equations
balance_one = sm.solve(atom_balances + basis_eqn)

print(balance_one)
```

    {vCH4: -1, vCO2: 1, vH2O: 2, vO2: -2}

So, the balanced co-efficients are saved in a dictionary. For left hand side of the equation, co-efficient is negative and for right-hand side, co-efficient is positive.

Let's solve a Complex chemical reaction.
Here is the equation:

as before, we'll use atomic balance to solve this.

```python
atoms = ['K','Fe','S','C','N','Cr','O','H']

# creating variables from molecule name
sm.var(['vK4FeSCN6','vK2Cr2O7','vH2SO4','vFe2SO43','vCr2SO43','vCO2','vH2O','vK2SO4','vKNO3'])

# writing atomic balance equation in sympy format
atom_balances = [
    sm.Eq(4*vK4FeSCN6+2*vK2Cr2O7+0*vH2SO4+0*vFe2SO43+0*vCr2SO43+0*vCO2+0*vH2O+2*vK2SO4+vKNO3,0), # Potassium (K) balance
    sm.Eq(vK4FeSCN6+0*vK2Cr2O7+0*vH2SO4+2*vFe2SO43+0*vCr2SO43+0*vCO2+0*vH2O+0*vK2SO4+0*vKNO3,0), # Iron (Fe) balance
    sm.Eq(6*vK4FeSCN6+0*vK2Cr2O7+vH2SO4+3*vFe2SO43+3*vCr2SO43+0*vCO2+0*vH2O+vK2SO4+0*vKNO3,0), # Sulfur (S) balance
    sm.Eq(6*vK4FeSCN6+0*vK2Cr2O7+0*vH2SO4+0*vFe2SO43+0*vCr2SO43+vCO2+0*vH2O+0*vK2SO4+0*vKNO3,0), # Carbon (C) balance
    sm.Eq(6*vK4FeSCN6+0*vK2Cr2O7+0*vH2SO4+0*vFe2SO43+0*vCr2SO43+0*vCO2+0*vH2O+0*vK2SO4+vKNO3,0), # Nitrogen (N) balance
    sm.Eq(0*vK4FeSCN6+2*vK2Cr2O7+0*vH2SO4+0*vFe2SO43+2*vCr2SO43+0*vCO2+0*vH2O+0*vK2SO4+0*vKNO3,0), # Chromium (Cr) balance
    sm.Eq(0*vK4FeSCN6+7*vK2Cr2O7+4*vH2SO4+4*3*vFe2SO43+4*3*vCr2SO43+2*vCO2+vH2O+4*vK2SO4+3*vKNO3,0), # Oxygen (O) balance
    sm.Eq(0*vK4FeSCN6+0*vK2Cr2O7+2*vH2SO4+0*vFe2SO43+0*vCr2SO43+0*vCO2+vH2O+0*vK2SO4+0*vKNO3,0), # Hydrogen (H) balance
]

# checking the atomic balance equations
print(atoms)

for eqn in atom_balances:
    print(eqn)
```

    ['K', 'Fe', 'S', 'C', 'N', 'Cr', 'O', 'H']
    Eq(2*vK2Cr2O7 + 2*vK2SO4 + 4*vK4FeSCN6 + vKNO3, 0)
    Eq(2*vFe2SO43 + vK4FeSCN6, 0)
    Eq(3*vCr2SO43 + 3*vFe2SO43 + vH2SO4 + vK2SO4 + 6*vK4FeSCN6, 0)
    Eq(vCO2 + 6*vK4FeSCN6, 0)
    Eq(6*vK4FeSCN6 + vKNO3, 0)
    Eq(2*vCr2SO43 + 2*vK2Cr2O7, 0)
    Eq(2*vCO2 + 12*vCr2SO43 + 12*vFe2SO43 + vH2O + 4*vH2SO4 + 7*vK2Cr2O7 + 4*vK2SO4 + 3*vKNO3, 0)
    Eq(vH2O + 2*vH2SO4, 0)

```python
# basis equation vK2Cr2O7 and -1
basis_eqn = [sm.Eq(vK2Cr2O7, -1)]

# checking the basis equations
for eqn in basis_eqn:
    print(eqn)
```

    Eq(vK2Cr2O7, -1)

```python
# solving the atomic balance equations
balance_two = sm.solve(atom_balances + basis_eqn)

# co-efficients of balance equation molecules
print(balance_two)
```

    {vCO2: -6/43, vCr2SO43: 1, vFe2SO43: -1/86, vH2O: 355/43, vH2SO4: -355/86, vK2Cr2O7: -1, vK2SO4: 44/43, vK4FeSCN6: 1/43, vKNO3: -6/43}

```python

```

Reaction Balance and Stoichiometry/reaction_balance.ipynb


# Weyl Tensor calculations using Symbolic module

```python
import sympy
from sympy import cos, sin, sinh
from einsteinpy.symbolic import MetricTensor, WeylTensor

sympy.init_printing()
```

### Defining the Anti-de Sitter spacetime Metric

```python
syms = sympy.symbols("t chi theta phi")
t, ch, th, ph = syms
m = sympy.diag(-1, cos(t) ** 2, cos(t) ** 2 * sinh(ch) ** 2, cos(t) ** 2 * sinh(ch) ** 2 * sin(th) ** 2).tolist()
metric = MetricTensor(m, syms)
```

### Calculating the Weyl Tensor (with all indices covariant)

```python
weyl = WeylTensor.from_metric(metric)
weyl.tensor()
```

```python
weyl.config
```

    'llll'

docs/source/examples/Weyl Tensor symbolic calculation.ipynb


```python
# Pretty plotting config
%run plot_conf.py
```

```python
# Set style (need to be in a new cell)
plt_style()
```

```python
import torch
import torch.nn as nn
import matplotlib.pyplot as plt

# utility function
def show_scatterplot(X, norm=True, title=''):
    X = X.numpy()
    plt.figure()
    plt.axis('equal')
    plt.scatter(X[:, 0], X[:, 1], c=colors, s=p_size)
    if norm:
        plt.xlim(-6, 6)
        plt.ylim(-6, 6)
    plt.grid(True)
    plt.title(title)

# generate some points in 2-D space
n_points = 1000
p_size = 30
X = torch.randn(n_points, 2)
colors = X[:, 0].numpy()

show_scatterplot(X, norm=True, title='X')
```

# Visualizing Linear Transformations

* Generate a random matrix $W$

$
\begin{equation}
    W = U
  \left[ {\begin{array}{cc}
   s_1 & 0 \\
   0 & s_2 \\
  \end{array} } \right]
  V^\top
\end{equation}
$
* Compute $y = Wx$
* Larger singular values stretch the points
* Smaller singular values push them together
* $U, V$ rotate/reflect

```python
show_scatterplot(X, norm=True, title='X')

for i in range(10):
    # create a random matrix
    W = torch.randn(2, 2)
    # transform points
    Y = torch.mm(X, W)
    # compute singular values
    U,S,V = torch.svd(W)
    # plot
    show_scatterplot(Y, norm=True, title='y = Wx, singular values : [{:.3f}, {:.3f}]'.format(S[0], S[1]))
```

# Linear transformation with PyTorch

```python
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
```

```python
model = nn.Sequential(
        nn.Linear(2, 2, bias=False)
)
model.to(device)
Y = model(X).data
show_scatterplot(Y)
```

# Non-linear Transform: Map Points to a Square

* Linear transforms can rotate, reflect, stretch and compress, but cannot curve
* We need non-linearities for this
* Can (approximately) map points to a square by first stretching out by a factor $s$, then squashing with a tanh function

$
   f(x)= \tanh \left(
  \left[ {\begin{array}{cc}
   s & 0 \\
   0 & s \\
  \end{array} } \right]
  x
  \right)
$

```python
z = torch.linspace(-10, 10, 101)
s = torch.tanh(z)
plt.plot(z.numpy(), s.numpy())
plt.title('tanh() non linearity')
```

```python
show_scatterplot(X, title='X')
plt.axis('square')

model = nn.Sequential(
        nn.Linear(2, 2, bias=False),
        nn.Tanh()
    )

model.to(device)

for s in range(1, 10):
    W = s * torch.eye(2)
    model[0].weight.data.copy_(W)
    Y = model(X).data
    show_scatterplot(Y, False, title='f(x), s={}'.format(s))
    plt.axis('square')
    plt.axis([-1.2, 1.2, -1.2, 1.2])
```

# Visualize Functions Represented by Random Neural Networks

```python
show_scatterplot(X, title='x')
n_hidden = 5

for i in range(5):
    # create 1-layer neural networks with random weights
    model_1layer = nn.Sequential(
            nn.Linear(2, n_hidden, bias=True),
            nn.ReLU(),
            nn.Linear(n_hidden, 2, bias=True)
        )
    Y = model_1layer(X).data
    show_scatterplot(Y, False, title='f(x)')
```

```python
# deeper network with random weights
show_scatterplot(X, title='x')
n_hidden = 1000

for i in range(5):
    model_2layer = nn.Sequential(
        nn.Linear(2, n_hidden, bias=True),
        nn.ReLU(),
        nn.Linear(n_hidden, n_hidden, bias=True),
        nn.ReLU(),
        nn.Linear(n_hidden, n_hidden, bias=True),
        nn.ReLU(),
        nn.Linear(n_hidden, n_hidden, bias=True),
        nn.ReLU(),
        nn.Linear(n_hidden, 2, bias=True)
    )
    Y = model_2layer(X).data
    show_scatterplot(Y, False, title='f(x)')

```

```python

```


# Page Rank

CMPE 256

Cory Randolph

11/21/2021

# Prompt

Learning Objective:

Application of Page Rank algorithm. Calculate the Page Rank at node A.

# Page Rank

## Formula

General assumptions for this example of the Page Rank algorithm are that we are not using a damping factor and that we will only do one iteration to get the overall rank of the pages rather than multiple interactions which would narrow down to more precise ranking scores.

General Page Rank Fomula:

\begin{equation}
        PR(u) = \sum_{v \in B_u}
        \frac{PR(v)}{L(v)}
\end{equation}

Note: $PR()$ is the Page Rank and $L()$ is the number of outbound links

## Calculation

Page rank formula for our specific problem:

\begin{equation}
        PR(A) = \frac{PR(B)}{L(B)} +
        \frac{PR(C)}{L(C)} +
        \frac{PR(D)}{L(D)} +
        \frac{PR(E)}{L(E)}
\end{equation}

For the first iteration we assume that each node has an equal page rank of $\frac{1}{5} = 0.2$. Also, assume that a node that has no outbound nodes connecting to the nod in question has a Page Rank of 0.

\begin{equation}
        PR(A) = \frac{PR(B)}{L(B)} +
        \frac{PR(C)}{L(C)} +
        \frac{PR(D)}{L(D)} +
        \frac{PR(E)}{L(E)} \\[1em]
        = \frac{\frac{1}{5}}{1} +
        0 +
        \frac{\frac{1}{5}}{4} +
        \frac{\frac{1}{5}}{1} \\[1em]
        = \frac{9}{20} \\[1em]
        = 0.45
  \end{equation}


## Problem 9.3

```python
from sympy import symbols, oo, integrate, diff, exp, pi
```

```python
v, nu, m, h_bar, omega, h, alpha, c, d = symbols('v nu m h_bar omega h alpha c d', real=True, positive=True)
x = symbols('x', real=True)
```

Define the unperturbed first excited state wavefunction from Eqn 4.55

```python
psi_1_0 = (4*alpha**3/pi)**0.25*x*exp(-alpha*x**2/2)
psi_1_0
```

$\displaystyle \frac{1.4142135623731 \alpha^{0.75} x e^{- \frac{\alpha x^{2}}{2}}}{\pi^{0.25}}$

As a sanity check, make sure \\(\psi_{1}^{(0)}\\) is normalized

```python
integrate(psi_1_0**2, (x, -oo, oo))
```

$\displaystyle 1.0$

Calculate the first order perturbation correction.

```python
E_1_1 = integrate(psi_1_0*(c*x**3 + d*x**4)*psi_1_0, (x, -oo, oo))
E_1_1
```

$\displaystyle \frac{3.75 d}{\alpha^{2.0}}$

```python
E_1_1.subs({alpha: 4*pi**2*nu*m/h})
```

$\displaystyle \frac{0.234375 d h^{2.0}}{\pi^{4.0} m^{2.0} \nu^{2.0}}$

This matches the answer in the book!

```python

```


```python
%matplotlib inline
import matplotlib.pylab as plt
import numpy as np
```

```python
from sympy import *
```

```python
var("x y h s")
```

```python
l = symbols("\lambda")
l
```

```python
init_printing(True)
```

Ejemplo 1: restricciones presupuestarias

Supón que tienes una fábrica que produce cierto tipo de dispositivo que requiere acero como materia prima.

Tus costos son predominantemente mano de obra, que cuesta $\$20 $ por hora para tus trabajadores y el propio acero, que cuesta $ \$170$ por tonelada.

Supón que tus ingresos, $R$, se modelan vagamente por la siguiente ecuación:

$$R=200h^{2/3}s^{1/3} $$

- $h$ representa las horas de trabajo
- $s$ representa las toneladas de acero

Si tu presupuesto es de $\$20,000$, ¿cuál es el ingreso máximo posible?

```python
%matplotlib notebook
```

```python
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm

# Silly example data
X = np.arange(-50, 50, 0.25)
Y = np.arange(-50, 50, 0.25)
X, Y = np.meshgrid(X, Y)
Z = 200*X**2/3*Y**1/3

# Make the plot
fig = plt.figure()
ax = fig.gca(projection="3d")

surf = ax.plot_surface(X, Y, Z)
#ax.set_zlim(-1.01, 1.01)
#fig.colorbar(surf, shrink=0.5, aspect=5)

plt.show()
```

    <IPython.core.display.Javascript object>

```python
R=200*h**2/3*s**1/3
```

```python
const = 20*h + 170 *s  - 20000
```

```python
L = R + l*const
L
```

```python
diff(L,h),diff(L,s),diff(L,l)
```

```python
solve(_)
```

```python
2000/51,2000/3,80000000/1377
```

```python

```

```python
A=np.array([ [3,-2,8],[-2,2,1],[1,2,-3] ])
b=np.array([9,3,8])

np.linalg.solve(A,b)
```

    array([ 3.,  4.,  1.])

```python
A=np.array([ [1,2,-3],
            [-2,2,1],
            [3,-2,8]])
b=np.array([8,3,9])

np.linalg.solve(A,b)
```

    array([ 3.,  4.,  1.])

```python

```

```python
2*A[0] + A[1],-3*A[0] + A[2]
```

    (array([ 0,  6, -5]), array([ 0, -8, 17]))

```python

```


# Clustering Techniques Writeup
### September 26, 2016

### K-Means Clustering

K-Means clustering is a regression technique which involves 'fitting' a number $n$ of given values from a dataset around a pre-defined number of $k$ clusters.  The K-means clustering process seeks to minimize the Euclidean distance from each point $n$ from its associated $k$ cluster.  Note that although the $k$ clusters inhabit the same coordinate space as the $n$ data points, the $k$ cluster locations are not necessarily derived from the set of $n$ points.

The process of K-means involves first defining the number of $k$ clusters.  Defining the number of $k$ means is a non-trivial problem that is dependent on the specific experiment at hand.  Analysis with an elbow plot showing explained variance as a function of number of clusers allows the designer to designate the $k$ number by choosing the point where adding additional $k$ clusters has diminishing returns for explained variance.

The actual algorithm behind K-means clustering is relatively simple.  Given $n$ data points and $k$ clusters, we're looking for µk (cluster means that minimize the Euclidean distance between the $n$ data points and said cluster).  Effectively, the objective function we're trying to minimize can be written as (courtesy SciKit):

$$
\begin{align}
\sum\limits_{i=0}^n \min_{µ_j \subseteq C} ||(x_j - µ_i)||^2
\end{align}
$$

For the analysis, we must first choose $k$ starting mean µ's.  This is often achieved by randomly sampling $k$ points from the dataset  arbitrarily.  Below, we have an image of the data set at the start, with arbitrarily placed means. *(Image sequence courtesy David Runyan, from Project Rhea)*

After choosing the initial set of starting means, we can apply a three step iterative process to eventually 'converge' on finalized means:

1)  **Fit n points to µ means:**   Calculate the Euclidean distances and assign each $n$ to its closest µ.  In this case, all points are closest to the blue node, so all $n$ are assigned to the blue µ.

2)  **Define new µ's by finding average:**   Find the center of each cluster and assign those as new µ's.  Only the blue µ moves, as the other nodes have no values assigned to them.

3)  **Repeat until convergence:**   Repeat until there is no change in the µ positions.  I've shown the final converged image below.

**PROS:**
1.  Oft-implemented, existing documentation.
2.  Has SciKit implementation in Python.

**CONS:**
1.  Defining number of means is a non-trivial process.
2.  Does not necessarily converge to global solution, as randomized starting positions will alter final result for different results.
3.  Does not set the means to positions to some value from the dataset (if we're interested in brain clusters, we want our clusters to be centered around some set of pre-defined nodes, not some weighted average determined by k-means.)
4.  Assumes similarly sized 'clusters', which is a very large assumption to be making for brain areas.

### K-Medoid Clustering

K-Medoids clustering is similar to K-means in that both start with a known, pre-defined number of 'k' clusters.  However, K-medoids sets the 'k' clusters to points that occur naturally in the dataset of 'n' points.  Thus, while K-means finds new 'means' and fits each 'n' point to these new means by reducing some summed error, K-medoids instead seeks to maximize some measure of similarity (eg: the similarity matrix) between each points and its medoid (which itself is one of the pre-existing points).

The process for K-Medoids is similar to K-Means; again, the problem of defining the number of 'k' means is a difficult process.  From there, an additional difficulty lies in how to define the 'similarity' between two points.  A way to do so would be to make a similarity matrix that is has each corresponding value as the inverse of the Euclidean distance between the points.  Aside from this, a similar process would be applied iteratively to converge to medoids that maximize the similarity between their respective nodes.

The specific name of the algorithmic approach is Partitioning Around Medoids, or PAM.  The steps are, specifically:
1)  Arbitrarily select 'k' of the 'n' nodes as the medoids.
2)  Calculate the total 'similarity' (eg, by finding the inverse of the total of the distances between all 'n' and their closest 'k', or by using some other measure).
3)  Swap one 'n' with one 'k', and recalculate the 'similarity' measure.  If the 'similarity' increased, keep the new configuration and continue.  Otherwise, return to the previous configuration.

**PROS:**
1.  Simple, like K-means.

**CONS:**
1.  Has similiar faults as the K-means methodology (defining 'k', not necessarily global, assumes equal sized regions)
2.  Recalculating similarity at each step relative to all other points is very computationally intensive

### Spectral Clustering
Up until now, we've focused on clustering techniques centered around compactness.  Spectral clustering is a different dataset fitting technique that focuses on connectivity instead.

The actual algorithm behind K-means clustering is relatively simple. Given 'n' data points and 'k' clusters, we're looking for µk (cluster means that minimize the Euclidean distance between the 'n' data points and said cluster).

**Pros:**
1. abc
2. abc

**Cons:**
1. abc
2. abc

### Louvain Method of Community Detection
The Louvain method of community detection is a clustering technique that relies on monitoring the relative connectiveness of communities.

From Blondel, Guillaume, Lambiotte and Lefebvre's seminal paper that defined the Louvain method, they defined first a modularity value.  From the paper, "the modularity of a partition is a scalar value between -1 and 1 that measures the density of links inside communities as compared to links between communities."  In the case of weighted nodes (where for instance they used the number of calls between between two phone users), the modularity is defined as:

$$
\begin{align}
Q = \frac{1}{2m} \sum\limits_{i, \ j} \big[ A_{i, \ j} - \frac{k_i k_j}{2m} \big] \ \delta \big( c_i , c_j \big)
\end{align}
$$

In this case, the $A_{i, \ j}$ refers to the weight of the edge between $i$ and $j$.  From there, they defined $k_i$ as the $\sum_{j} A_{i, \ j}$ as the "weights of the edges attached to vertex $i$".  $c_i$ is the 'community' to which node $i$ is assigned, while the $\delta$-function $\delta \big( u, v \big)$ is 1 if  $u$ is $v$, and 0 otherwise.  $m$ is defined to be $\frac{1}{2}\sum_{i, \ j} A_{i, \ j}$  In lay speech, then, what this calculates is 'how connected' some node $i$ is relative some node $j$ (since we're subtracting from the edge weight of $ij$ the edge weights of all connections from $i$ and $j$).

From there, the paper progresses to explain how this traditional definition of modularity takes a significant amount of storage power to process.  Data analytics on the original data using continuous modularity calls take more processing power than is truly necessary.  What Blondel, Guillaume, Lambiotte, and Lefebvre did was define an algorithmically (and computationally) more efficient method to measure the *change* in modularity.  It does so by calculating a $\Delta Q$ value that is the *change* in modularity by moving (inserting) some node $i$ from an 'isolated neighborhood' into $C$:

$$
\begin{align}
\Delta Q  = \bigg[ \frac{\sum_{in} + \ k_{i, \ in}}{2m} - \big( \frac{\sum_{tot} + \ k_i}{2m} \big)^2 \bigg] - \bigg[ \frac{\sum_{in}}{2m} - \big( \frac{\sum_{tot}}{2m} \big)^2 - \big( \frac{k_i}{2m} \big)^2 \bigg]
\end{align}
$$

A similar equation is provided for the removal of a node $i$ from the neighborhood $C$.

Application-wise, the process for Louvain's method is similar to the approach for all the other regression techniques (K-means, K-medoids).  The approach first assigns and puts each node $N$ into a unique community (so it's in its own community).  They then calculate the $\Delta Q$ by adding the node $i$ into community $j$; if after all similar additions into other neighborhoods, the $\Delta Q$ is maximized by adding i to j, that process is fulfilled.  Otherwise, the system is reverted to before the addition.

By applying this until a local minimum occurs (when no changes to $i$ increase or decrease $\Delta Q$, the Louvain method can create a very solid mapping.

**Pros:**
1.  Computationally efficient, their calculations in the paper show a significant reduction in computation time (running on 6.3M nodes, their algorithm only took 197 seconds.  The only comparable algorithm that even converged was the Wakita and Tsumuri's CNN implementation method, which took almost three times as long).
2.  Accuracy seems to be high, according to their own discussion and results.

**Cons:**
1.  Requires weighted edges (we haven't generated these yet).  If our weights are defined by the number of connections within the epsilon ball, what's the point of using weighted edges over K-means or K-medoids?  Isn't that just an extra layer of complexity?
2.  Requires a C-based workaround that allows us to run the methods in Python (so I won't be able to use these lovely Markdown/Python notebooks).  Not too big a problem, but it's nice when packets like SciKit already has implementations of the other methods.  Link:  http://perso.crans.org/aynaud/communities/

Link to Louvain's Method paper: http://arxiv.org/abs/0803.0476

```latex
%%latex
\begin{align}
\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\
\nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} & = 0
\end{align}
```

\begin{align}
\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\
\nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} & = 0
\end{align}

```python

```