# Notebook from matthewfeickert/MachineLearningStatistics
Path: notebooks/Markov.ipynb

# Machine Learning and Statistics for Physicists_____no_output_____Material for a [UC Irvine](https://uci.edu/) course offered by the [Department of Physics and Astronomy](https://www.physics.uci.edu/).

Content is maintained on [github](github.com/dkirkby/MachineLearningStatistics) and distributed under a [BSD3 license](https://opensource.org/licenses/BSD-3-Clause).

[Table of contents](Contents.ipynb)_____no_output_____

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()
import numpy as np
import pandas as pd_____no_output_____import scipy.stats_____no_output_____from mls import StochasticProcess_____no_output_____

## Stochastic Processes_____no_output_____A **stochastic process** is a black-box generator of random sequences
$$
x_0, x_1, x_2, \ldots
$$
where, in general, the value $x_n$ depends on the history of all previous samples, so the black box has long-term memory. The values appearing at the $n$-th position of many runs of the black box sample the random variable $X_n$. In general, samples can be multi-dimensional, $\vec{x}_i$, but this does not present any special challenges or require new concepts so we will stick with the cleaner 1D notation in the following.

Here is a simple example of a general stochastic process (implemented using `mls.StochasticProcess`):_____no_output_____

class StochasticExample(StochasticProcess):

    def initial(self, gen):
        return gen.uniform()

    def update(self, history, gen):
        return 0.5 * (np.mean(history) + gen.uniform())_____no_output_____

Two useful visualizations of a stochastic process are:
 - Plots of a few long runs output by the black box: how does it evolve in "time" $n$?
 - A pair plot of the first few random variables (from many short runs) for a more detailed look at short-range correlations._____no_output_____

StochasticExample().plot(nsamples_per_run=50, nruns=3)_____no_output_____StochasticExample().pairplot(nsamples_per_run=4, nruns=1000, x0cut=0.5)_____no_output_____

**EXERCISE:** For each panel of the pairplot above:
 - What distribution of random variables does each panel show?
 - What correlated random variables are marginalized out, if any?
 - What conditional probability do the two colors indicate?
 - Explain how the pairplot answers the question "Are $X_3$ and $X_0$ independent?", i.e. $P(X_3\mid X_0) = P(X_3)$?

Hint: the top-left panel shows $P(X_0\mid X_0 \le 0.5)$ and $P(X_0\mid X_0 > 0.5)$, with no marginalization since $X_0$ starts the random sequence._____no_output_____The histograms show the distributions $P(X_n)$ which are marginalized over $X_{n-1},\ldots, X_0$. The scatter plots show the joint distributions $P(X_i, X_j)$ which are marginalized over $X_{n-1},\ldots, X_0$ with $n = \max(i,j)$ and excluding $X_{\min(i,j)}$. Note that these are distributions of random variables $X_n$, not random values $x_n$.

More explicitly:
 - $P(X_0)$ has no correlated random variables marginalized out since it starts the sequence.
 - $P(X_1)$ is marginalized over $X_0$.
 - $P(X_2)$ is marginalized over $X_1, X_0$.
 - $P(X_3)$ is marginalized over $X_2, X_1, X_0$.
 - $P(X_0, X_1)$ has no correlated random variables marginalized out.
 - $P(X_1, X_2)$ is marginalized over $X_0$.
 - $P(X_2, X_3)$ is marginalized over $X_0, X_1$.
 - $P(X_1, X_3)$ is marginalized over $X_0, X_2$.

The two colors apply the conditions $X_0 \le 0.5$ and $X_0 > 0.5$, so, for example:
 - The $(x_2, x_3)$ scatter plot shows $P(X_2, X_3\mid X_0 \le 0.5)$ and $P(X_2, X_3\mid X_0 > 0.5)$.
 - The $x_3$ histogram shows $P(X_3\mid X_0 \le 0.5)$ and $P(X_3X_0 > 0.5)$.

The difference between the two $x_3$ histograms demonstrates that $P(X_3\mid X_0) \ne P(X_3)$, therefore $X_3$ and $X_0$ are *dependent*. In other words, $X_3$ "remembers" $X_0$.

Note that dependence nearly always implies correlation, but it is possible to construct [artificial counter-examples](https://en.wikipedia.org/wiki/Uncorrelated_random_variables#Example_of_dependence_without_correlation).

---_____no_output_____An observer watching many different sequences generated from the same black box would need to measure all these conditional probabilities to fully describe the process:
$$
P(X_0) \;,\;
P(X_1\mid X_0) \;,\;
P(X_2\mid X_1, X_0) \;,\; \ldots \;,\;
P(X_n\mid P_{n-1}, P_{n-2}\ldots, P_0) \; .
$$
The equivalent Bayesian network (up to $n=3$) is:
![Stochastic process graphical model](img/Markov/stochastic_graph.png)_____no_output_____## Markov Chain_____no_output_____A **Markov chain** is a special type of stochastic process where $X_n$ only depends directly on $X_{n-1}$ and not on any earlier samples. In this case, we say that $X_n$ and $X_{n-2}$ are [conditionally independent](https://en.wikipedia.org/wiki/Conditional_independence).

In other words, a Markov chain is a black box with very short-term memory. The samples $x_0, x_1, \ldots$ produced by a single run of a Markov chain are referred to as a **Markov-chain Monte Carlo (MCMC)**.

Our observer now only needs to measure:
$$
P(X_0) \;,\; P(X_1\mid X_0) \;,\; P(X_2\mid X_1) \;,\; \ldots \;,\; P(X_n\mid X_{n-1}) \; .
$$
The equivalent Bayesian network is:
![Markov chain graphical model](img/Markov/markov_graph.png)
Random variables without a direct connection (arrow) are conditionally independent.

An important subset of Markov chains are **stationary**, which makes our observer's job even easier since they have the property that
$$
P(X_1\mid X_0) = P(X_2\mid X_1) = \ldots = P(X_n\mid X_{n-1}) \; .
$$
In other words, a black box with very short-term memory and no sense of time.  Our observer now only has two conditional probability distributions to measure, the initial probability,
$$
P(X_0) \; ,
$$
and the update rule for $n\ge 1$,
$$
P(X_n\mid X_{n-1}) \; .
$$

Here is a simple example of a stationary Markov process:_____no_output_____

class StationaryMarkovExample(StochasticProcess):

    def initial(self, gen):
        return gen.uniform()

    def update(self, history, gen):
        return 0.5 * (history[-1] + gen.uniform())_____no_output_____StationaryMarkovExample().plot(nsamples_per_run=50, nruns=3)_____no_output_____StationaryMarkovExample().pairplot(nsamples_per_run=4, nruns=1000, x0cut=0.5)_____no_output_____

The pairplot above demonstrates that $X_n$ and $X_0$ are still dependent, $P(X_n\mid X_0) \ne P(X_n)$, even for a Markov process! Some probability calculus shows how this happens. Take $n=2$ for simplicity:
$$
\begin{aligned}
P(X_2\mid X_0) &= \int dX_1\, P(X_2, X_1\mid X_0) \\
&= \int dX_1\, P(X_2\mid X_1, X_0)\, P(X_1\mid X_0) \\
&= \int dX_1\, P(X_2\mid X_1)\, P(X_1\mid X_0) \; ,
\end{aligned}
$$
where the first two lines are completely general, but the last assumes a Markov process, $P(X_2\mid X_1, X_0) = P(X_2\mid X_1)$.  Compare with:
$$
\begin{aligned}
P(X_2) &= \int dX_1 dX_0\, P(X_2, X_1, X_0) \\
&= \int dX_1 dX_0\, P(X_2\mid X_1, X_0)\, P(X_1, X_0) \\
&= \int dX_1\, P(X_2\mid X_1)\, \int dX_0\, P(X_1, X_0) \\
&= \int dX_1\, P(X_2\mid X_1)\, P(X_1) \; .
\end{aligned}
$$
The comparison shows that $P(X_2\mid X_0) = P(X_2)$ would require that $P(X_1\mid X_0) = P(X_1)$.  In other words, the direct dependence of each $X_n$ on $X_{n-1}$ is sufficient to generate long-range dependencies all the way back to $X_0$._____no_output_____Another important property that some Markov chains have is **reversibility**,
$$
P(X_n\mid X_{n-1}) = P(X_{n-1}\mid X_n)
$$
which can also be defined in terms of forward conditional probabilities as
$$
P(X_n=x\mid X_{n-1}=y) = P(X_{n+1}=y\mid X_n=x) \; ,
$$
and roughly corresponds to time-reversal invariance.

*A reversible chain is always stationary, but not vice versa.*_____no_output_____The update rule for a stationary Markov chain,
$$
P(X_n\mid X_{n-1}) \; .
$$
can be conveniently visualized with a 2D plot. For example, taking $n=1$:_____no_output_____

StationaryMarkovExample().plot_conditionals(which=1)_____no_output_____

The blue line shows the mean $X_1$ at each $X_0$,
$$
\langle X_1\rangle = \int dX_1\, X_1\, P(X_1\mid X_0) \; .
$$
Note that this is essentially the $(x_1, x_0)$ scatter plot from above, but with samples now histogrammed to yield conditional probabilities._____no_output_____**DISCUSS:**
 - The speckling is due to using finite statistics to estimate the probability. How would this plot look different with infinite statistics?
 - This plot shows $P(X_1\mid X_0)$. Would $P(X_2\mid X_1)$ look any different? How about $P(X_2\mid X_0)$?
 - Are these conditional probabilities normalized along $X_0$? along $X_1$?
 - How would this plot change if we changed the definition of `initial()` in `StationaryMarkovExample`?
 - What condition on this plot does a reversible Markov chain satisfy? Is this example reversible?_____no_output_____With infinite statistics, the diagonal band would be a uniform color, since $P(X_1\mid X_0)$ is uniform (within the diagonal limits shown) for each $X_0$.

A plot of $P(X_2\mid X_1)$, or any $P(X_{n+1}\mid X_n)$ would like identical since this is the definition of a stationary Markov chain. However, $P(X_2\mid X_0)$ involves two updates, so its plot might look different.

A conditional probability $P(\alpha\mid \beta)$ is normalized along $\alpha$, with the value of $\beta$ considered fixed. In other words, a numpy array of values `T[n,m]` tabulating $P(X_m\mid X_n)$ on a grid satisfies:
```
np.sum(T, axis=1) = 1
```

This plot only shows the update rule and does not depend on how we chose an initial value for the Markov chain.

A reversible Markov chain has $P(X_1\mid X_0) = P(X_0\mid X_1)$, which makes this plot symmetric under the interchange of its axes. In other words, mirroring the plot along the diagonal should not change its appearance. Since this is not true in this example, `StationaryMarkovExample` is not reversible.

---_____no_output_____## Equilibrium Distributions_____no_output_____Repeated use of the (same) update rule associated with a stationary Markov chain reveals a useful feature. These plots show 1, 2, 4, and 8 iterations of the same rule:_____no_output_____

StationaryMarkovExample().plot_conditionals(which=(1, 2, 4, 8))_____no_output_____

**A stationary Markov chain eventually reaches an equilibrium $P(X_n\mid X_0) \rightarrow \tilde{P}(X_n)$ that does not depend on $X_0$.**

We saw earlier how, in general, $X_n$ and $X_0$ are dependent, but we now learn that stationarity tames this behavior and guarantees that $X_n$ and $X_0$ are independent for sufficiently large $n$.

We will not prove this result, but here is a less trivial example to help build the case:_____no_output_____

class StationaryMarkovExample2(StochasticProcess):

    def initial(self, gen):
        return gen.uniform()

    def update(self, history, gen):
        return np.abs(np.fmod(np.cos(2 * np.pi * history[-1]) + 0.1 * gen.normal(), 1))_____no_output_____StationaryMarkovExample2().plot_conditionals(which=(1, 2, 3, 8))_____no_output_____

For practical applications, there are two issues to deal with:
 - There is no way to know in advance how big $n$ needs to be to achieve equilibrium.
 - Given some stationary Markov chain, we can generate samples from *some* equilibrium distribution $\tilde{P}(X_n)$, but how do we build a chain to sample a specific distribution?

The second issue requires solving an [inverse problem](https://en.wikipedia.org/wiki/Inverse_problem), which is generally challenging. However, there is a general class of solutions that we will look at below._____no_output_____**EXERCISE:** Define your own Markov chain, following the `StationaryMarkovExample` above, and make similar plots. Is your chain stationary? For a challenge, try to build a reversible chain._____no_output_____The test for a stationary chain is whether it only uses `history[-1]` in its `update()` method.  The test for a reversible chain is whether its plot of $P(X_1\mid X_0)$ is symmetric about the diagonal.

Here is an example of a reversible (and therefore stationary) Markov chain:_____no_output_____

class ReversibleMarkovExample(StochasticProcess):

    def initial(self, gen):
        return gen.uniform()

    def update(self, history, gen):
        return np.fmod(history[-1] + 0.2 * gen.normal() + 1, 1)_____no_output_____ReversibleMarkovExample().plot_conditionals(which=(1, 2, 4, 8))_____no_output_____

Note that we can use the diagonal symmetry of the first plot to confirm that this is a reversible chain.

---_____no_output_____

# Your solution here..._____no_output_____

## Custom Markov Chains_____no_output_____The Metropolis-Hastings-Green (MHG) algorithm is a general approach to designing a custom Markov chain that has a specified target probability density, $\tilde{P}(X_n)$.  All practical algorithms are special cases of MHG:
 - Metropolis-Hastings-Green
   - Metropolis-Hastings
     - Metropolis
     - Gibbs
     - Hamiltonian

The simpler Metropolis-Hastings (MH) algorithm contains the essential ideas so we will focus on that.

The history of using Markov chains for practical inference is intimately connected with physics: many of the pioneers were physicists motivated by physics problems, including [Metropolis](https://en.wikipedia.org/wiki/Nicholas_Metropolis) and [Gibbs](https://en.wikipedia.org/wiki/Josiah_Willard_Gibbs), and the Hamiltonian method derives from classical mechanics.

See this [blog post](http://twiecki.github.io/blog/2014/01/02/visualizing-mcmc/) for helpful animations comparing some of these methods._____no_output_____### Metropolis-Hastings Updates_____no_output_____The MH algorithm relies on a **proposal distribution** $Q(X_{n+1}\mid X_n)$ that is easier to sample than $\tilde{P}(X_{n+1}\mid X_n)$.  (If you knew how to sample $P$ directly, you would not need MCMC!)

We often use a multivariate Gaussian for $Q$ since it is easy to sample from. Any proposal distribution is valid, but choosing a $Q$ "closer" to $P$ generally reaches the desired equilibrium faster.

The proposal distribution can either be used to update to the current state ("random walk") or to generate a new state:_____no_output_____

def sample_proposal(x0, gen, mu, C, mode):
    sample = gen.multivariate_normal(mu, C)
    return x0 + sample if mode == 'random_walk' else sample_____no_output_____def plot_proposals(n=50, seed=123):
    gen = np.random.RandomState(seed=seed)
    mu = np.zeros(2)
    C = np.diag((.3, .1))
    for xy0 in np.array([-3, -1]), np.array([3, 1]):
        xy = np.array([sample_proposal(xy0, gen, mu, C, 'random_walk') for i in range(n)])
        plt.scatter(*xy.T, label='random_walk')
        xy = np.array([sample_proposal(xy0, gen, mu, C, 'independent') for i in range(n)])
        plt.scatter(*xy.T, label='independent')
        plt.scatter(*xy0, marker='+', s=300, c='k')

plot_proposals()_____no_output_____

During each update we evaluate a proposed move to $x_{n+1}$ by calculating the **Hastings ratio**,
$$
r(x_{n+1}, x_n) = \frac{\tilde{P}(x_{n+1})}{\tilde{P}(x_n)}\, \frac{Q(x_n\mid x_{n+1})}{Q(x_{n+1}\mid x_n)} \; ,
$$
where $\tilde{P}$ is the desired equilibrium distribution. Since $\tilde{P}$ only appears in a ratio, it does not need to be normalized: we saw earlier how this freedom is very useful for performing Bayesian inference, where the normalization is generally not calculable.

In general, the Hastings ratio is $\ge 0$ but it can otherwise be arbitrarily large. We always accept a proposed move when $r(x_{n+1}, x_n) \ge 1$.  Otherwise, we accept it with a probability of $0\le r(x_{n+1}, x_n) < 1$.  When a proposed move is rejected, the update returns the original value (so repetitions are possible and quite normal).

In practice, we work with
$$
\log r(x_{n+1}, x_n) = \log \tilde{P}(x_{n+1}) - \log \tilde{P}(x_n)
+ \log Q(x_n\mid x_{n+1}) - \log Q(x_{n+1}\mid x_n)
$$
rather than $r(x_{n+1}, x_n)$ in order to minimize the effects of round-off errors.

We can now build a simple custom Markov chain for an arbitrary target equilibrium distribution:_____no_output_____

class MetropolisHastings(StochasticProcess):

    def __init__(self, x0, Qrms, logP, mode, seed=123):
        super().__init__(seed)
        self.x0 = np.asarray(x0)
        self.Q = scipy.stats.multivariate_normal(
            mean=np.zeros_like(x0), cov=np.diag(Qrms ** 2 * np.ones_like(x0)))
        self.logP = logP
        self.mode = mode

    def initial(self, gen):
        # Start from the origin.
        return self.x0

    def update(self, history, gen):
        x0 = history[-1]
        # Sample the proposal distribution Q to generate x1 and calculate
        # the log of the proposal ratio.
        sample = self.Q.rvs(random_state=gen)
        if self.mode == 'random_walk':
            x1 = x0 + sample
            log_Q_ratio = 0. # self.Q.logpdf(x0-x1) - self.Q.logpdf(x1-x0)
        else:
            x1 = sample
            log_Q_ratio = self.Q.logpdf(x0) - self.Q.logpdf(x1)
        # Calculate log of the Hastings ratio.
        log_ratio = self.logP(x1) - self.logP(x0) + log_Q_ratio
        # Accept x1 or keep x0?
        if log_ratio >= 0:
            return x1
        else:
            accept_prob = np.exp(log_ratio)
            return x1 if gen.uniform() < accept_prob else x0_____no_output_____

Define the un-normalized PDF for samples that are uniformly distributed within a (hyper)cube spanning $[-1,+1]$ along each axis:_____no_output_____

def logP(x):
    return 1. if np.max(np.abs(x)) < 1 else -np.inf_____no_output_____

Generate samples in 2D using a 2D Gaussian with zero mean and unit variance for the proposal distribution $Q$. Use the following utiliity function to make a scatter plot of the results using:
 - color to indicate the last position $n$ in the chain,
 - a superimposed plus ($+$) to indicate samples repeated twice,
 - a superimposed asterisk ($\ast$) to indicate samples repeated three or more times,
 - a larger circle to indicate the initial position, and
 - a dashed box showing the boundary of the target (square) distribution._____no_output_____

def plot_square(xy):
    _, ax = plt.subplots(figsize=(8, 8))
    xy_unique, xy_counts = np.unique(xy, axis=0, return_counts=True)
    repeated = xy_counts > np.arange(3).reshape(-1, 1)
    ax.scatter(*xy.T, s=100, c=np.arange(len(xy)), cmap='gist_rainbow', lw=0)
    ax.scatter(*xy[0], s=400, marker='o', edgecolors='k', facecolors='none', lw=2)
    ax.scatter(*xy_unique[repeated[1]].T, marker='+', s=30, c='w', lw=1, zorder=10)
    ax.scatter(*xy_unique[repeated[2]].T, marker='x', s=30, c='w', lw=1, zorder=10)
    ax.set_title('Chain has {} / {} unique samples with {} repeated'
                 .format(len(xy_unique), len(xy), np.count_nonzero(repeated[1])), fontsize='x-large')
    ax.set_xlim(-1.1, 1.1); ax.set_ylim(-1.1, 1.1)
    ax.add_artist(plt.Rectangle([-1,-1], 2, 2, fc='none', ec='k', ls='--', lw=1))_____no_output_____plot_square(MetropolisHastings([0,0], 1., logP, 'random_walk').run(500))_____no_output_____

Note that the unique points in the sample do cover the square uniformly and there is no obvious imprint of the Gaussian proposal distribution. However, there are also many repetitions and, more generally, successive points are highly correlated with each other.

The example above worked out reasonably well, but there are many pitfalls with the MH algorithm, some obvious and others more subtle:_____no_output_____

plot_square(MetropolisHastings([0,0], 0.3, logP, 'independent').run(500))_____no_output_____plot_square(MetropolisHastings([1, 1], 0.05, logP, 'random_walk').run(500))_____no_output_____

**EXERCISE:** Study the two examples above and describe how and why they are failing to sample the desired target distribution. Do the locations of repeated samples make sense?_____no_output_____The first example uses independent updates with a proposal distribution that is too narrow ($\sigma = 0.3$) to sample the edges of the box efficiently.  As a result, points far from the initial point (the origin) are repeated often and the chain would need to be much longer to populate the corners. Note that, although the density of samples is higher close to the origin, when weighted by the number of repetitions, the generated chain correctly samples the target (flat) distribution.

The second example use random-walk updates with a proposal distribution that is much narrower than the target ($\sigma = 0.05$) so tends to explore the target space slowly with long range correlations between samples. We have effectively simulated the [Brownian motion](https://en.wikipedia.org/wiki/Brownian_motion) of a particle in a 2D fluid. With a long enough chain, our particle will explore the full square, spending about the same amount of time in each part of it, but this is not true of shorter sections of the chain. Note how repeated samples occur only at the edges of square, where there is a high probability of a proposed sample falling outside the square and therefore being rejected.

Both of these examples are "correct" implementations of MH updates, but also have peculiar features that you might care about when using MCMC for pratical work. The lesson is that you should generally not assume that an MCMC chain has any desirable properties beyond its minimum guarantee of asymptotically sampling $\tilde{P}(X_n)$.

---_____no_output_____When the proposal distribution $Q$ is reversible, it cancels in the Hastings ratio,
$$
r(x_{n+1}, x_n) = \frac{\tilde{P}(x_{n+1})}{\tilde{P}(x_n)}\, \frac{Q(x_n\mid x_{n+1})}{Q(x_{n+1}\mid x_n)} \;\rightarrow\; \frac{\tilde{P}(x_{n+1})}{\tilde{P}(x_n)} \; .
$$
The resulting **Metropolis updates** are a special case of MH updates and eliminate the need to evaluate $Q$._____no_output_____### Gibbs Sampling_____no_output_____When sampling a multidimensional target distribution $\tilde{P}$, some additional constraints on $\tilde{P}$, allow us to exploit a different special case of MH updates and are the basis of **Gibbs sampling**.

For example, suppose we want to sample in 3D from $\tilde{P}(x, y, z)$, then Gibbs updates require that we can sample from each of the 1D conditional distributions:
$$
\tilde{P}(x\mid y, z) \quad ,\quad
\tilde{P}(y\mid x, z) \quad ,\quad
\tilde{P}(z\mid x, y) \; .
$$
This condition is trivially satisfied when the random variables are mutually independent,
$$
\tilde{P}(x,y,z) = P_X(x) P_Y(y) P_Z(z) \; ,
$$
but is more useful in cases where this is not true and simpler alternative methods are not available.

The rules for a single Gibbs update starting from sample $(x_{n-1}, y_{n-1}, z_{n-1})$ are:
 - Sample $x_n$ from $\tilde{P}(x\mid y_{n-1}, z_{n-1})$
 - Sample $y_n$ from $\tilde{P}(y\mid x_n, z_{n-1})$
 - Sample $z_n$ from $\tilde{P}(z\mid x_n, y_n)$
 - Accept the new sample $(x_n, y_n, z_n)$ with probability one (!)

We will not prove the correctness of this approach, but the key insight is that each sub-step samples from a conditional PDF that is proportional to the full joint PDF, for example:
$$
\tilde{P}(y\mid x, z) = \frac{\tilde{P}(x, y, z)}{\tilde{P}(x, z)} \propto \tilde{P}(x, y, z) \; .
$$
The 3D example above generalizes in the obvious way to higher dimensions.

[Gibbs sampling](https://en.wikipedia.org/wiki/Gibbs_sampling) places extra conditions on $\tilde{P}$, so is not a general-purpose algorithm, but is efficient and eliminates repeated samples when its conditions are met.  Gibbs sampling is often a good fit to target distributions that can be expressed using a graphical model with few dependencies._____no_output_____### Hamiltonian Sampling_____no_output_____Another special case of MH sampling requires that we can calculate all partial derivatives of our target $\log\tilde{P}$,
$$
\frac{\partial}{\partial z_i}\, \log\tilde{P}(\vec{x}) \; .
$$
The resulting **Hamiltonian sampling** method relies on a nifty physics analogy.

[Recall](https://en.wikipedia.org/wiki/Hamiltonian_mechanics) that the equations of motion for a classical system of particles with Hamiltonian $H$ are:
$$
\frac{dq_i}{dt} = + \frac{\partial H}{\partial p_i} \quad , \quad
\frac{dp_i}{dt} = - \frac{\partial H}{\partial q_i} \; ,
$$
where $q_i$ and $p_i$ are (generalized) position and momentum coordinates for particle $i$.

We identify $H$ with the total energy of the system and can often split it into separate kinetic and potential terms,
$$
H(q,p) = K(p) + U(q) \; ,
$$
where, generically,
$$
K(p) = \sum_i\, \frac{p_i^2}{2 m_i} \; ,
$$
with particle "masses" $m_i$, and $U(q)$ encapsulates all the specifics of a particular system. Using this split, we can simplify the equations of motion to:
$$
\frac{dq_i}{dt} = +\frac{p_i}{m_i} \quad ,\quad \frac{dp_i}{dt} = -\frac{\partial U}{\partial q_i} \; .
$$

We turn Hamiltonian dynamics into a stationary Markov chain with the following recipe:
 - Identify positions $q_i$ with the random variables we wish to sample.
 - Create new random variables $p_i$ for the corresponding momenta. We will treat these are nuisance parameters, but this does not look promising since we just doubled the dimension of our sampling space!
 - Assign fixed (arbitrary) masses $m_i$ to each "particle".
 - Use $U(q) = -\log\tilde{P}(q)$ for our "potential energy".

Plugging $U(q) = -\log\tilde{P}(q)$ into the $dp_i/dt$ equation of motion above reveals why we need to be able to calculate partial derivatives of $\log\tilde{P}$.

A single Hamiltonian Monte Carlo (HMC) update then consists of:
 - Pick a random starting point in $(q, p)$ space, to specify the initial conditions for our "particles".
 - Follow the evolution of our "particles" for some fixed time interval using Hamiltonian dynamics.
 - Use the final positions of our "particles" as a new sampled added to the chain.

How does this sample the target $\tilde{P}(q)$? The answer comes from statistical mechanics, which tells us that the probability that our system of particles is in a state with positions $q$ is given by the [canonical distribution](https://en.wikipedia.org/wiki/Canonical_ensemble):
$$
P(q) \propto \exp\left( -\frac{U(q)}{k_B T} \right) \; ,
$$
where $k_B$ is Boltzmann's constant and $T$ is the system's absolute temperature.

In practice, you can usually set $k_B T = 1$ and all masses $m_i = 1$ and this works surprisingly well!  The disadvantages of this approach are that:
 - It is relatively complex to implement, compared with the much simpler (and general purpose) MH sampling, so let someone else do this work for you.
 - It requires that you can evaluate all the necessary partial derivatives of your target $\tilde{P}$.

However, the main advantage is that HMC can often explore the target space much more efficiently than other methods, since it uses the additional information provided by the derivatives.  We will soon discuss computational graphs, which are a general-purpose framework for automating the necessary derivate calculations.

See this [blog post](http://arogozhnikov.github.io/2016/12/19/markov_chain_monte_carlo.html) for a more detailed explanation of HMC with some helpful interactive visualizations._____no_output_____## Practical Advice_____no_output_____It is tempting to assume that MCMC samples have desirable properties beyond their minimum guarantees, since this is often true, but avoid this temptation.

In particular, MCMC samples are only guaranteed to sample your target $\tilde{P}(X_n)$ for $n$ sufficiently large. Therefore:
 - There is no way to know how big $n$ needs to be for a particular $\tilde{P}$.
   - **Burn-in?** Should I throw away the first $B$ samples to ensure that my chain is independent of its initial starting point?
   - **No:** There is no practical way to know how big $B$ should be. Instead, ensure that your starting point is reasonably probable (according to $\tilde{P}$) and use all samples. If you do not know how to chose a reasonably probably starting point, you need to solve a separate optimization problem before you are ready to use MCMC (which is notoriously inefficient at discovering new regions of high probability).
 - There are no useful guarantees about $\tilde{P}(X_n, X_m)$ and, in general, you should assume that the consecutive samples in any stretch of the chain are highly correlated.
   - **Thinning?** Should I just keep every $T$-th sample so that my chain is uncorrelated?
   - **No:** There is no practical way to know in advance how big $T$ should be, and you can never get a better answer (for a fixed amount of computation) by throwing away valid information. Just accept that samples are correlated.

How long should your chain be?  You should ideally use empirical measurements to determine $k$ such that the [autocorrelation](https://en.wikipedia.org/wiki/Autocorrelation)
$$
\frac{\langle (X_{n+k} - \mu) (X_n - \mu)\rangle}{\sigma^2} \simeq 0 \; ,
$$
where $\mu$ and $\sigma$ are the long-term mean and standard deviation of $\tilde{P}(X_n)$, then generate a chain whose length is at least 10-100 times this [autocorrelation](https://en.wikipedia.org/wiki/Autocorrelation) length $k$.

Which update rule should you use?
 - Determine which special cases apply to your target $\tilde{P}$, so you know which algorithms are possible.
   - Can you sample from a complete set of conditional distributions?  If so, add Gibbs sampling to your list.
   - Can you compute all partial derivatives? If so, add HMC to your list.
 - There is no "best" algorithm, so you will need to benchmark your problem against the available methods.

Although it is instructive (and fun!) to implement simple update rules yourself, for serious work you should generally let someone else do the hard work for you by using an existing package.

Which package should you use?
 - For initial exploratory work, start with [emcee](http://dfm.io/emcee/), which implements [ensemble sampling](http://dx.doi.org/10.2140/camcos.2010.5.65) where many independent "walkers" simultaneously crawl around your target space, and has a nice [affine invariance](https://en.wikipedia.org/wiki/Affine_geometry) property, where the efficiency is not affected by any linear (aka "affine") transformation of your target space.
 - Look into [PyMC3](http://docs.pymc.io/notebooks/getting_started.html) or [Edward](http://edwardlib.org/) to explore HMC and other more advanced updating rules. These are generally more complex to use and have rigid rules for specifying your target $\tilde{P}$.
 - Consider an alternative approximate method for Bayesian inference, such as [variational inference](https://en.wikipedia.org/wiki/Variational_Bayesian_methods), with different tradeoffs.

This [blog post](http://twiecki.github.io/blog/2013/09/23/emcee-pymc/) compares emcee and PyMC3.

You will see examples of performing MCMC and variational inference with [Edward](http://edwardlib.org/) soon._____no_output_____


# Notebook from nicoborghi/MGCosmoPop
Path: MGCosmoPop/notebooks/tutorial.ipynb

# MGCosmoPop

This notebook shows how to use MGCosmoPop to sample from mass and redshift distributions, load GWTC-2 data, and compute posterior probabilities for astrophysical and cosmological parameters._____no_output_____

import os
# os.chdir('/Users/Michi/Dropbox/Local/Physics_projects/MGCosmoPop/MGCosmoPop/')
import sys
sys.path.append('/home/debian/software/MGCosmoPop/MGCosmoPop/')
import importlib
import numpy as np
import scipy.stats as ss
from scipy.integrate import cumtrapz
from scipy.interpolate import interp1d
import corner

import matplotlib.pyplot as plt
plt.rcParams["font.family"] = 'serif'
plt.rcParams["mathtext.fontset"] = "cm"

import Globals_____no_output_____

# Population

A population is characterised by three components:
* the mass distribution of the binaries, $p(m_1, m_2)$, implemented in population.astro.astroMassDistribution
* the distribution of the spins of the binaries, implemented in population.astro.astroSpinDistribution
* the evolution of the merger rate of the binaries with redshift, $\frac{d R}{dV_c dt} = R(z)$, implemented in population.astro.rateEvolution

A *population* is another separate class, that is instantiated by passing as arguments a mass distribution, spin distribution, and rate

Finally, allPopulation is an object that collects all populations and the cosmology and returns the (log) differential number of events per unit mass, distance and redshift (or det frame masses and luminosity distance)

_____no_output_____## 1. Instantiate a mass function

Supported:
* truncated power law
* broken power law
* (truncated power law with smoothed edges)_____no_output_____

from population.astro.astroMassDistribution import BrokenPowerLawMass_____no_output_____massFunction = BrokenPowerLawMass( )Broken power law mass function base values: {'alpha1': 1.6, 'alpha2': 5.6, 'beta': 1.4, 'deltam': 4.8, 'ml': 4, 'mh': 87, 'b': 0.43}

## 2. Instantiate spin distribution

For skipping the spin distribution, we use the class DummySpinDist.

A gaussian distribution for the components $\chi_p , \chi_{eff}$ is also supported._____no_output_____

from population.astro.astroSpinDistribution import DummySpinDist

spinDist = DummySpinDist()
_____no_output_____

## 3. Instantiate rate evolution objects

Supported:

- simple power law: $R(z)=R_0 (1+z)^{\lambda}$

- Madau-Dickinson Rate_____no_output_____

from population.astro.rateEvolution import AstroPhRateEvolution_____no_output_____MDrate = AstroPhRateEvolution() # Madau-Dickinson RateRate R0 is in yr^-1 Gpc^-3

## 4. Create an Astrophysical Population for each mass funtion and rate
_____no_output_____

from population.astro.astroPopulation import AstroPopulation_____no_output_____MDpop = AstroPopulation(MDrate, massFunction, spinDist)_____no_output_____

# Cosmology

Then, one needs to define a cosmology. This is done via the *Cosmo* object, that contains all the useful functions
_____no_output_____

from cosmology.cosmo import Cosmo
import astropy.units as u_____no_output_____myCosmo = Cosmo(dist_unit=u.Gpc)_____no_output_____

# All populations

Different populations can be put together to give the final number of compact binaries. The object taking care of this is *AllPopulations* .

It is instantiated by passing a cosmology (i.e. an instance of the *Cosmo* class).

Then, each population is added through the method *add_pop* .

This class allows to calculate $\frac{d N}{dm_1^{d} dm_2^{d} d(dL)} = \frac{d N}{dm_1 dm_2 dz}\frac{1}{(1+z) \frac{d(dL)}{dz}}  = \frac{1}{(1+z) \frac{d(dL)}{dz}} \frac{d V_c}{dz}\frac{1}{(1+z)} R(z) p(s_1, s_2)  p(m_1, m_2)  $
_____no_output_____

from population.allPopulations import AllPopulations_____no_output_____allPopsMD = AllPopulations(myCosmo)Setting cosmology.
5 parameters: ['H0', 'Om', 'w0', 'Xi0', 'n']
allPopsMD.add_pop(MDpop)Adding population of type AstroPopulation
11 parameters: ['R0', 'alphaRedshift', 'betaRedshift', 'zp', 'alpha1', 'alpha2', 'beta', 'deltam', 'ml', 'mh', 'b']
New parameter list: ['H0', 'Om', 'w0', 'Xi0', 'n', 'R0', 'alphaRedshift', 'betaRedshift', 'zp', 'alpha1', 'alpha2', 'beta', 'deltam', 'ml', 'mh', 'b']. Total 16 params

### Set parameters values

Finally, one can change the values of the population parameters. _____no_output_____

myParams = {'R0':20, 'alphaRedshift':3,  'betaRedshift':0.5, 'zp':3, 'ml':4. ,'mh':70, 'b':0.35, 'deltam':5. }_____no_output_____allPopsMD.set_values( myParams)#{'R0':50, 'alphaRedshift':0,  'betaRedshift':2, 'zp':2 })cosmo basevalues: {'H0': 67.74, 'Om': 0.3075, 'w0': -1, 'Xi0': 1, 'n': 1.91}
Setting values in populations...
Setting value of R0 to 20 in AstroPhRateEvolution
Setting value of alphaRedshift to 3 in AstroPhRateEvolution
Setting value of betaRedshift to 0.5 in AstroPhRateEvolution
Setting value of zp to 3 in AstroPhRateEvolution
Setting value of ml to 4.0 in BrokenPowerLawMass
Setting value of mh to 70 in BrokenPowerLawMass
Setting value of b to 0.35 in BrokenPowerLawMass
Setting value of deltam to 5.0 in BrokenPowerLawMass
Setting value of R0 to 20 in AstroPopulation
Setting value of alphaRedshift to 3 in AstroPopulation
Setting value of betaRedshift to 0.5 in AstroPopulation
Setting value of zp to 3 in AstroPopulation
Setting value of ml to 4.0 in AstroPopulation
Setting value of mh to 70 in AstroPopulation
Setting value of b to 0.35 in AstroPopulation
Setting value of deltam to 5.0 in AstroPopulation
Setting value of R0 to 20 in AllPopulations
Setting value of alphaRedshift to 3 in AllPopulations
Setting value of betaRedshift to 0.5 in AllPopulations
Setting value of zp to 3 in AllPopulations
Setting value of ml to 4.0 in AllPopulations
Setting value of mh to 70 in AllPopulations
Setting value of b to 0.35 in AllPopulations
Setting value of deltam to 5.0 in AllPopulations

### Visualise names and values of the population parameters_____no_output_____

# Parameters' names
allPopsMD.params_____no_output_____# Values of the population parameters
lambdaBase = allPopsMD.get_base_values(allPopsMD.params)
lambdaBase_____no_output_____

# Sampling

We can sample from the mass and redshift distributions with the `sample` method of the object allPopulations._____no_output_____

N=20000
zmax = 20_____no_output_____allMDSamples = allPopsMD.sample(N, zmax, lambdaBase)
m1sMD, m2sMD, zsMD = allMDSamples[:, :, 0], allMDSamples[:, :, 1], allMDSamples[:, :, 2] _____no_output_____

### Redshift distribution_____no_output_____

_ = plt.hist(zsMD, bins=50, density=True, label='samples')

R0, al, bb, zp, = allPopsMD.get_base_values(['R0', 'alphaRedshift', 'betaRedshift', 'zp', ])
H0, Om, w0 = allPopsMD.get_base_values(['H0', 'Om', 'w0',  ])

zz = np.linspace(0, zmax, 200)
zpdf = np.exp(allPopsMD._pops[0].rateEvol.log_dNdVdt(zz,  [R0, al, bb, zp,])+ allPopsMD.cosmo.log_dV_dz(zz, H0, Om, w0)-np.log1p(zz))
zpdf/=np.trapz(zpdf,zz)
plt.plot(zz, zpdf, label='pdf')

plt.xlabel('z', fontsize=15);
plt.ylabel('p(z)', fontsize=15);
plt.legend();/Users/Michi/Dropbox/Local/Physics_projects/MGCosmoPop/MGCosmoPop/cosmology/cosmo.py:116: RuntimeWarning: divide by zero encountered in log
  res =  np.log(4*np.pi)+3*np.log(self.clight)-3*np.log(H0)+2*np.log(self.uu(z, Om0, w0))-np.log(self.E(z, Om0, w0))

### Joint and marginal mass distribution_____no_output_____

alpha1, alpha2, beta, deltam, ml, mh, b = allPopsMD.get_base_values(['alpha1', 'alpha2', 'beta', 'ml', 'mh', 'b', 'deltam'])
alpha1, alpha2, beta, ml, mh, b, deltam_____no_output_____
ms = np.logspace( np.log10(ml-ml/20), np.log10(mh+mh/20), 2000 )
M1S, M2S = np.meshgrid(ms, ms, indexing='ij')

def pp_norm(x1, x2):
    return np.exp(allPopsMD._pops[0].massDist.logpdf([x1, x2], [alpha1, alpha2, beta, deltam, ml,  mh, b],  ))

PSNORM = np.where( M2S <= M1S, pp_norm(M1S, M2S) , 0)

pm1NORM = np.trapz(PSNORM, M2S, axis=1)
pm2NORM = np.trapz(PSNORM, M1S, axis=0)

pm1NORM /= np.trapz(pm1NORM, ms)
pm2NORM /= np.trapz(pm2NORM, ms)

fig = plt.figure(figsize=(10, 10))
grid = plt.GridSpec(4, 4)
joint_ax = fig.add_subplot(grid[1:,:-1])
m1_ax = fig.add_subplot(grid[0, :-1], sharex=joint_ax)
m2_ax = fig.add_subplot(grid[1:, -1], sharey=joint_ax)

x = joint_ax.contourf(M1S, M2S, PSNORM, cmap='viridis', levels=100)

m1_ax.plot(ms, pm1NORM, label='Marginal pdf')
_ = m1_ax.hist(m1sMD, bins=100, density=True, label='samples')
m1_ax.legend()

m2_ax.plot(pm2NORM, ms, label='Marginal pdf')
m2_ax.hist(m2sMD, bins=100, density=True, orientation='horizontal')
#m2_ax.legend()
#m2_ax.set_xscale('log')

joint_ax.set_xlabel(r'$m_1$ ($M_\odot$)', fontsize=20)
joint_ax.set_ylabel(r'$m_2$ ($M_\odot$)', fontsize=20)

m1_ax.set_ylabel(r'$p\left( m_1 \right)$', fontsize=20)
m2_ax.set_xlabel(r'$p\left( m_2 \right)$', fontsize=20)

plt.tight_layout()_____no_output_____

# Computing the population function_____no_output_____

# Now we can evaluate the population funtction.

m1 = np.array([30., ])
m2 = np.array([20., ])
z = np.array([1.5, ])

Lambda = [67.74, 0.3075, -1, 1.0, 0, 50.0, 3, 2, 2, 1.6, 5.6, 1.4, 5.0, 4, 70.0, 0.5]

logdN = allPopsMD.log_dN_dm1zdm2zddL( m1, m2, z, [], 1, Lambda, )

print(logdN)[-0.00984245]

# Loading data

Each data release (O1-O2, O3a) has a specific class that inherits from the abstract class ABSData.
A pre-defined function load_data is also available in sample.models .

Here we show how to load O1-O2 data_____no_output_____

from dataStructures.O1O2data import O1O2Data, O1O2InjectionsData
from dataStructures.mockData import GWMockInjectionsData_____no_output_____# We can provide a dictionary of events to use or not use. In this case we specify to exclude GW170817 which is a BNS

events = {'use': None,
          'not_use': ['GW170817', ] }

_____no_output_____fname_data = os.path.join(Globals.dataPath, 'O1O2')
O1O2 = O1O2Data(fname_data, events_use=events, nSamplesUse=None)
O1O2_1 = O1O2Data(fname_data, events_use=events, nSamplesUse=None)

allData = [O1O2, ] # One can add here other datasets in the listFAR th in LVC data: 1.0
In the O1O2 data we have the following BBH events, total 10 (excluding ['GW170817']):
['GW170729', 'GW151226', 'GW170809', 'GW170104', 'GW170608', 'GW170814', 'GW151012', 'GW170818', 'GW150914', 'GW170823']
Using only events with SNR>8.0 (round to 1 decimal digit)
Excluded the following events with SNR<8.0:
[]
10 events remaining.
Using only events with FAR<1.0
Excluded the following events with FAR>1.0:
[]
10 events remaining.
Excluding BBH events :
['GW170817']
Loading data...
Reading data from GW170729
Number of samples in LVC data: 5112
Reading data from GW151226
Number of samples in LVC data: 52252
Reading data from GW170809
Number of samples in LVC data: 14802
Reading data from GW170104
Number of samples in LVC data: 24972
Reading data from GW170608
Number of samples in LVC data: 6448
Reading data from GW170814
Number of samples in LVC data: 40000
Reading data from GW151012
Number of samples in LVC data: 16096
Reading data from GW170818
Number of samples in LVC data: 5862
Reading data from GW150914
Number of samples in LVC data: 8350
Reading data from GW170823
Number of samples in LVC data: 4278
We have 10 events.
Using distances in Gpc
Number of samples for each event: [5112, 52252, 14802, 24972, 6448, 40000, 16096, 5862, 8350, 4278]
Obs time (yrs): 0.4556164383561644
FAR th in LVC data: 1.0
In the O1O2 data we have the following BBH events, total 10 (excluding ['GW170817']):
['GW170729', 'GW151226', 'GW170809', 'GW170104', 'GW170608', 'GW170814', 'GW151012', 'GW170818', 'GW150914', 'GW170823']
Using only events with SNR>8.0 (round to 1 decimal digit)
Excluded the following events with SNR<8.0:
[]
10 events remaining.
Using only events with FAR<1.0
Excluded the following events with FAR>1.0:
[]
10 events remaining.
Excluding BBH events :
['GW170817']
Loading data...
Reading data from GW170729
Number of samples in LVC data: 5112
Reading data from GW151226
Number of samples in LVC data: 52252
Reading data from GW170809
Number of samples in LVC data: 14802
Reading data from GW170104
Number of samples in LVC data: 24972
Reading data from GW170608
Number of samples in LVC data: 6448
Reading data from GW170814
Number of samples in LVC data: 40000
Reading data from GW151012
Number of samples in LVC data: 16096
Reading data from GW170818
Number of samples in LVC data: 5862
Reading data from GW150914
Number of samples in LVC data: 8350
Reading data from GW170823
Number of samples in LVC data: 4278
We have 10 events.
Using distances in Gpc
Number of samples for each event: [5112, 52252, 14802, 24972, 6448, 40000, 16096, 5862, 8350, 4278]
Obs time (yrs): 0.4556164383561644

#### O2 injections_____no_output_____

# LVC injections
fname_inj  = os.path.join(fname_data, 'injections_O1O2an_spin.h5' )
InjDataO2_LVC = O1O2InjectionsData(fname_inj, )
Removing factor of 1/2 for each spin dimension from p_draw...
Re-weighting p_draw to go to detector frame quantities...
Number of total injections: 71000000.0
Number of injections that pass first threshold: 8472
Max redshift of injections: 1.1662752223838337
Obs time: 0.4556164383561644 yrs
# Injections generated with MGCosmoPop
fname_inj_1  = os.path.join(fname_data, 'injections_O1O2_40k','selected.h5' )

InjDataO2_mock = GWMockInjectionsData(fname_inj_1,  nInjUse=None,  Tobs=(48.3+118)/365.)Loading injections...
Dropping 0 points with exactly equal masses
Max redshift of injections assuming Planck 15 cosmology: 1.5390381026078779
Number of total injections: 63100000
Number of detected injections: 40031
Obs time: 0.4556164383561644
zs = myCosmo.z_from_dLGW_fast(InjDataO2_mock.dL, 67.74, 0.3075, -1, 1.0, 0)
m1s = InjDataO2_mock.m1z/(1+zs)
m2s = InjDataO2_mock.m2z/(1+zs)

zsLVC = myCosmo.z_from_dLGW_fast(InjDataO2_LVC.dL, 67.74, 0.3075, -1, 1.0, 0)
m1sLVC = InjDataO2_LVC.m1z/(1+zsLVC)
m2sLVC = InjDataO2_LVC.m2z/(1+zsLVC)

fig = corner.corner(np.array([m1sLVC, m2sLVC, zsLVC]).T, range=[(0,500), (0,500), (0,1.5)],  color='red', label='LVC', hist_kwargs=dict(density=True))
fig = corner.corner(np.array([m1s, m2s, zs]).T, range=[(0,500), (0,500), (0,1.5)],color='blue', label='MGCosmoPop', fig=fig, hist_kwargs=dict(density=True))

fig.legend(loc='upper right');WARNING:root:Too few points to create valid contours
WARNING:matplotlib.legend:No handles with labels found to put in legend.

#### Expected detections in O1-O2_____no_output_____

params_fixed_O2 = {        'w0': -1. ,
                        'Xi0':1.,
                        'Om': 0.311,
                    'R0':20,   'alphaRedshift':1.5,  'betaRedshift':2, 'zp':2,
                    'alpha1': 1.6, 'alpha2': 5.6, 'beta': 1.4, 'deltam': 4.8, 'ml': 4,
                       'mh': 80, 'b': 0.43
    }

allPopsMD.set_values(params_fixed_O2)
cosmo basevalues: {'H0': 67.74, 'Om': 0.311, 'w0': -1.0, 'Xi0': 1.0, 'n': 1.91}
Setting value of w0 to -1.0 in Cosmo
Setting value of Xi0 to 1.0 in Cosmo
Setting value of Om to 0.311 in Cosmo
Setting values in populations...
Setting value of R0 to 20 in AstroPhRateEvolution
Setting value of alphaRedshift to 1.5 in AstroPhRateEvolution
Setting value of betaRedshift to 2 in AstroPhRateEvolution
Setting value of zp to 2 in AstroPhRateEvolution
Setting value of alpha1 to 1.6 in BrokenPowerLawMass
Setting value of alpha2 to 5.6 in BrokenPowerLawMass
Setting value of beta to 1.4 in BrokenPowerLawMass
Setting value of deltam to 4.8 in BrokenPowerLawMass
Setting value of ml to 4 in BrokenPowerLawMass
Setting value of mh to 80 in BrokenPowerLawMass
Setting value of b to 0.43 in BrokenPowerLawMass
Setting value of R0 to 20 in AstroPopulation
Setting value of alphaRedshift to 1.5 in AstroPopulation
Setting value of betaRedshift to 2 in AstroPopulation
Setting value of zp to 2 in AstroPopulation
Setting value of alpha1 to 1.6 in AstroPopulation
Setting value of alpha2 to 5.6 in AstroPopulation
Setting value of beta to 1.4 in AstroPopulation
Setting value of deltam to 4.8 in AstroPopulation
Setting value of ml to 4 in AstroPopulation
Setting value of mh to 80 in AstroPopulation
Setting value of b to 0.43 in AstroPopulation
Setting value of w0 to -1.0 in AllPopulations
Setting value of Xi0 to 1.0 in AllPopulations
Setting value of Om to 0.311 in AllPopulations
Setting value of R0 to 20 in AllPopulations
Setting value of alphaRedshift to 1.5 in AllPopulations
Setting value of betaRedshift to 2 in AllPopulations
Setting value of zp to 2 in AllPopulations
Setting value of alpha1 to 1.6 in AllPopulations
Setting value of alpha2 to 5.6 in AllPopulations
Setting value of beta to 1.4 in AllPopulations
Setting value of deltam to 4.8 in AllPopulations
Setting value of ml to 4 in AllPopulations
Setting value of mh to 80 in AllPopulations
Setting value of b to 0.43 in AllPopulations
from posteriors.selectionBias import SelectionBiasInjections

selBias = SelectionBiasInjections( allPopsMD, [InjDataO2_mock, ], [ 'H0', ],  )

# We can compute the expected number of detections in the O1-O2 data-taking period, ant the associated MCC error

Lambda_test = [70., ]

Nexp, err, Neff = selBias.Ndet( Lambda_test, verbose=True, )

print('Around %s detections expected in the O1-O2 data-taking period with this population model' %int(Nexp[0]) )
Around 14 detections expected in the O1-O2 data-taking period with this population model

# Likelihood and posterior evaluation_____no_output_____

from posteriors.prior import Prior
from posteriors.likelihood import HyperLikelihood
from posteriors.posterior import Posterior
from posteriors.selectionBias import SelectionBiasInjections_____no_output_____# We specify the prior limits
priorLimits = { 'H0': (0, 500),
               'Xi0': (0.1, 10) ,
               'Om': (0.05, 1),
               'w0': (-2, -0.5),
               'n':(0.,10.),

               'R0': (1e-01, 1e03),
               'alphaRedshift': (-15, 15),
               'betaRedshift': (0., 15.),
               'zp':(0., 4.),

               'alpha1': (-4, 12),
               'alpha2': (-4, 12),
               'deltam':  (0, 10),
               'ml': (2, 10),
               'mh':( 30, 100),
               'b':  (0, 1) ,

               'muEff':(-1, 1.),
                'sigmaEff':(0.01, 1.),
                'muP':(0.01, 1.),
                'sigmaP':(0.01, 1.),

               }

# ... and the type of prior

priorNames = {'H0' : 'flat',
              'Xi0': 'flatLog',
              'Om': 'flat',
               'w0': 'flat',
              'n':'flat',

               'R0': 'flatLog',
              'alphaRedshift': 'flat',
               'betaRedshift': 'flat',
               'zp':'flat',

               'ml': 'flat',
               'mh':'flat',

               'alpha1':'flat',
               'alpha2':'flat',
               'deltam':'flat',
               'b':'flat',

               'muEff':'flat',
               'sigmaEff':'flat',
                'muP':'flat',
                'sigmaP':'flat',

               }

# Specify the parameters names on which to do the inference
params_inference = [ 'H0', ]

_____no_output_____allPopsMD.get_base_values(allPopsMD.params)_____no_output_____# Instantiate Prior object
myPrior = Prior(priorLimits, params_inference, priorNames, priorParams=None)

# Instantiate likelihood
myLik = HyperLikelihood(allPopsMD, allData, params_inference, verbose=True, safety_factor=30 )

_____no_output_____

# Instantiate selection bias

selBias = SelectionBiasInjections( allPopsMD, [InjDataO2_mock, ], params_inference,  )

# We can compute the expected number of detections in the O1-O2 data-taking period, ant the associated MCC error

Lambda_test = [70., ]

Nexp, err, Neff = selBias.Ndet( Lambda_test, verbose=True, )

print('Around %s detections expected in the O1-O2 data-taking period with this population model with my injections' %int(Nexp[0]) )
Around 14 detections expected in the O1-O2 data-taking period with this population model with my injections
# Instantiate posterior

myPost = Posterior(myLik, myPrior, selBias, verbose=True )
_____no_output_____

#### Example: posterior probability for $H_0$, assuming all the astrophysical parameters are known_____no_output_____

H0grid = np.linspace(20, 200, 30)
postGrid = [myPost.logPosterior([val, ]) for val in H0grid]_____no_output_____plt.plot(H0grid, np.exp(postGrid)/np.trapz(np.exp(postGrid), H0grid), label='O1-O2 data' );

plt.xlabel(r'$H_0$', fontsize=20);
plt.ylabel(r'p($H_0$)', fontsize=20);
plt.axvline(67.74, color='k', ls='--', lw=2, label='Planck value');
plt.legend();_____no_output_____


# Notebook from gladaikins/DietImprovement
Path: DietImprovement.ipynb

# Using Machine Learning in Hybrid Recommendation System for Diet Improvement Based on Health and Taste_____no_output_____## Yiren Qu_____no_output_____### Abstract
Recommendation systems are used everywhere today, such as for online shopping or Netflix videos. The use of these systems necessitates the ability to predict the taste of food effectively. In addition, other food concerns, such as food safety and nutritional value have become more important than before. With these thoughts, a novel diet improvement system, which is based on a recommendation system using hybrid matrix decomposition and K-nearest neighborhood algorithms, is proposed in this paper. The need to minimize the error of prediction in terms of taste and obtain a healthier prediction, required the evaluation of a combination of different algorithms and also required different components of algorithms to be changed to determine the most accurate way of improving the diet. The results of an additional psychological study on induced motivation were incorporated into the system to develop a method to generate a model for each user to enable them to make the transition from tasty food to tasty and healthy food. The system contributes a new hybrid optimized recommendation system based on novel system construction.
_____no_output_____#### Problem:
    Food, similar to online advertisement services, the prediction of the prices of products, and the weather forecast, is one of the most important major components in people’s lives and has become the focus of development of a number of computer applications by that would enable information analysis. Under modern pressure, people seem to lose concern about their own eating. In the near future, personal data of preferred food habits will connect to restaurants, home computers, and mobile devices including cars. People eating at restaurants will receive immediate information containing food recommendations based on their personal health condition and an optimized taste model. In a long term, the recommendation system would enable people to specify their own settings to make their diets healthier and easy to accept by using the induced motivation method, recommending tasty food to give user a buffer time to get used to it.

#### Goal:
    This system is to find a solution to recommend food that is both healthy and tasty for users with the hope that, in the long term, the diet may become healthier because of the average summation of taste and health by the personal model. Also, this system will be built on the base of a traditional recommendation system. It will also be developed in both time efficiency and accuracy of the prediction.
_____no_output_____#### Program Language Selection:
Python 3

##### External Library of Python:
- Numpy        - For Numeric Calculation and Quick Data Analysis
- Pandas(Python Data Analysis Library) - It is a new and great library for big data analysis._____no_output_____

import math
import numpy as np
import pandas as pd_____no_output_____

### Problem statement
This system aims to find a solution to recommend food that is both healthy and tasty for users with the hope that, in the long term, the diet may become healthier because of the average summation of taste and health by the personal model.
Specifically, assume there are $N_{u}$  users and $ N_{f}$ foods, given a set of training data, which is a set of triples containing columns: user id, food id, and ratings, define the $ \mathrm{{A}}_{u,f} $ "user-food matrix" "containing"  only known food ratings and $B_{u,f} $matrix as:

 $$ {\mathrm{A}}_{u,f}=\left[ \begin{array}{ccc}
{ID}_u & {ID}_f & R_{u,f} \\
\vdots  & \vdots  & \vdots  \end{array}
\right]\mathrm{\ \ \ \ size\ of\ A\ is\ }\left(3,N_a\right),\ in\ which\ N_a\le N_u\times N_f $$

$$ B_{u,f}=\left( \begin{array}{ccc}
R_{u1,f1} & \cdots  & R_{u1,fi} \\
\vdots  & \ddots  & \vdots  \\
R_{ui,f1} & \cdots  & R_{ui,f1} \end{array}
\right)\ \ \ \ size\ of\ B\ is\ \left(N_u,N_f\right) $$

The first task is to predict all the users’ ratings on given foods based on the training set, in this case the known food ratings set. In this problem, each rating is an integer between 1 and 10, representing from unlike to like, and the goal is to minimize the root mean square error (RMSE) of the results of the testing sets containing unknown ratings of food. The training set, error and RMSE, specifically, are defines as:
$$ S_{Test}=\left(\left[ \begin{array}{cc}
{ID}_u & {ID}_f \end{array}
\right],\cdots \right)\ ,\ in\ which\ {ID}_u,{ID}_f\in A_{u,f}\ and\ R_{u,f}\notin A_{u,f} $$
$$E_{u,f}=R_{u,f}-P_{u,f}$$,
$$ RMSE=\sqrt{\frac{1}{\left|S_{Test}\right|}\sum_{\left(u,f\right)\in S_{Test}}{{E_{u,f}}^2}}
$$
where $ {\left|S_{Test}\right|} $ is its cardinality, $ R_{u,f}$is the true rating, and $P_{u,f}$ is the predicted rating based on the recommendation system. As paper [1] on evaluating recommendation systems argues, RMSE is popular and highly accurate in expressing the performance of a recommendation system.

The second task is to generate a health score based on the comparison between users’ needs in terms of basic nutrition and food nutrition. In this problem, each health score is an integer between 1 and 10 and the goal is to sort the predicted food rating list after obtaining the average summation of the health score and predicted taste score. Specifically, define the health score, result score, and recommended food list as:
$$ L_{{u,P}_f}=\left[ \begin{array}{cc}
{ID}_f & P_{u,f} \\
\vdots  & \vdots  \end{array}
\right]\ \ \left(u,f\right)\in \ S_{Test}\ and\ A_{{u,P}_f}\ is\ a\ descending\ sorted\ list\ by\ P_{u,f}$$
$$H_{u,f}=\sum_{\left(u,f\right)\in }{\frac{h_{f,i}}{h_{u,i}}\times a_i}\ \ \left(u,f\in \ L_{{u,P}_f}\right)$$
$$L_{u,Result}=\ \left[P_{u,f}\times a_{u,t}+H_{u,f}\times a_{u,h},\cdots \right]\ \left(u,f\right)\in \ S_{Test}
$$
where$ L_{{u,P}_f}$ is a descending sorted list of tuples of predicted food scores, $h_{f,i} $is the nutritional value of food items, $h_{u,i}$ is the nutritional needs of users, $a_{u,i}$ is the weight of each nutritional component in the weighted summation, and $(a_{u,t},a_{u,h})$ are the weights according to the induced motivation model controlling combination of tasty and healthy components to make users easy to accept.

_____no_output_____### import files_____no_output_____

#load food csv
def foodcsv():
    foodcsv = pd.read_csv('E:/project/main/DRS/DRS/data/food.csv', index_col=0, )
    return foodcsv
# load score.csv
def scoredata():
    scorecsv = pd.read_csv('E:/project/main/DRS/DRS/data/score_mod.csv', index_col=0, )
    return scorecsv
def scoredata_original():
    scorecsv = pd.read_csv('E:/project/main/DRS/DRS/data/score.csv', index_col=0, )
    return scorecsv

_____no_output_____

### Show Data set Examples_____no_output_____

foodcsv()[["name","type","calories","fat","totalcarbs","sodium","protein","special"]].head(2)_____no_output_____scoredata().head(2)_____no_output_____scoredata_original().head(2)_____no_output_____

## System design_____no_output_____ The diet improvement system is divided into three parts: Cloud data storage and methods, nearline storage and methods, and local storage and methods as depicted in Figure 1.

In the system, all databases containing basic information are saved in the Cloud, and specifically, there is a"user-food matrix" and Food Dataset containing basic nutritional values, such as number of calories, amount of fat, total amount of carbon dioxides, sodium, and proteins. Nearline, according to [2], is a way of transiting data between Offline and Online. We set up the recommendation system based on a hybrid SVD / KNN method and a database of similar user and similar food generated by the KNN method. The local data consisting of the users’ own goal and the induced motivation model generated by the goal are saved offline and are recalled when generating the final recommended food list. The interaction interface displays all the results received from the recommendation system.

Figure 1 System Construction_____no_output_____### K-nearest neighbor (KNN) algorithm_____no_output_____- Euclidean Similarity Function

$$ sim\left(x,y\right)=\ \frac{1}{1+\sqrt{\sum{{(x-y)}^2}}}\ \ \ \ \ \ (1)$$_____no_output_____

def euclidean(s1, s2):
    """Take two pd.Series objects and return their euclidean 'similarity'."""
    diff = s1 - s2
    return 1 / (1 + np.sqrt(np.sum(diff ** 2)))_____no_output_____

- Cosine Similarity Function

$$ sim(x,y) = \frac{(x . y)}{\sqrt{(x . x) (y . y)}} $$_____no_output_____

def cosine(s1, s2):
    """Take two pd.Series objects and return their cosine similarity."""
    return np.sum(s1 * s2) / np.sqrt(np.sum(s1 ** 2) * np.sum(s2 ** 2))_____no_output_____

- Pearson correlation

$$ sim(x,y) = \frac{(x - \bar x).(y - \bar y)}{\sqrt{(x - \bar x).(x - \bar x) * (y - \bar y)(y - \bar y)}} $$_____no_output_____

def pearson(s1, s2):
    """Take two pd.Series objects and return a pearson correlation."""
    s1_c = s1 - s1.mean()
    s2_c = s2 - s2.mean()
    return np.sum(s1_c * s2_c) / np.sqrt(np.sum(s1_c ** 2) * np.sum(s2_c ** 2))_____no_output_____def knn(user_id,food_id):
    scorecsv_mod = scoredata()
    all_user_profiles = scorecsv_mod.pivot_table('rating', index='food_id', columns='user_id')
    user_condition = scorecsv_mod.user_id != user_id
    food_condition = scorecsv_mod.food_id == food_id
    ratings_by_others = scorecsv_mod.loc[user_condition & food_condition]
    # pivot table of other user's score of one food(I chose the most input food)
    if ratings_by_others.empty:
        return pd.DataFrame()

    ratings_by_others.set_index('user_id', inplace=True)
    their_ids = ratings_by_others.index
    their_profiles = all_user_profiles[their_ids]
    user_profile = all_user_profiles[user_id]
    sims = their_profiles.apply(lambda profile: pearson(profile, user_profile), axis=0) # apply cosine function
    sims = sims.to_frame().sort_values(by=0, axis=0, ascending=False)
    sims.rename(columns={0:food_id},inplace=True)
    sims=sims.head(13)
    return sims
def knnf(food_id,user_id):    #K-food
    scorecsv = scoredata()
    all_food_profiles = scorecsv.pivot_table('rating', index='user_id', columns='food_id')
    user_condition = scorecsv.user_id == user_id
    food_condition = scorecsv.food_id != food_id
    ratings_by_others = scorecsv.loc[user_condition & food_condition]
    # using having most input user 183 to generate
    if ratings_by_others.empty:
        return pd.DataFrame()
    ratings_by_others.set_index('food_id', inplace=True)
    their_ids = ratings_by_others.index

    their_profiles = all_food_profiles[their_ids]
    food_profile = all_food_profiles[food_id]
    sims = their_profiles.apply(lambda profile: pearson(profile, food_profile), axis=0)
    sims = sims.to_frame().sort_values(by=0, axis=0, ascending=False)
    sims=sims.head(13)
    return sims_____no_output_____knn(68,3).head()_____no_output_____knnf(1,183).head()_____no_output_____%timeit -n10 knn(1,1)
%timeit -n10 knnf(1,1)10 loops, best of 3: 105 ms per loop
10 loops, best of 3: 22.3 ms per loop

### Pure KNN Method to predict foods' scores_____no_output_____

class CollabEuclideanReco:
    """ Collaborative filtering using a custom sim(u,u'). """

    def learn(self):
        """ Prepare datastructures for estimation. """
        scorecsv = scoredata()
        self.all_user_profiles = scorecsv.pivot_table('rating', index='food_id', columns='user_id')
        self.all_food_profiles = scorecsv.pivot_table('rating', index='user_id', columns='food_id')

    def estimate(self, user_id, food_id):
        """ Ratings weighted by correlation similarity. """
        scorecsv = scoredata()

        user_condition = scorecsv.user_id != user_id
        food_condition = scorecsv.food_id == food_id
        ratings_by_others = scorecsv.loc[user_condition & food_condition]
        if ratings_by_others.empty:
            return 5.5

        ratings_by_others.set_index('user_id', inplace=True)
        their_ids = ratings_by_others.index
        their_ratings = ratings_by_others.rating
        their_profiles = self.all_user_profiles[their_ids]
        user_profile = self.all_user_profiles[user_id]
        sims = their_profiles.apply(lambda profile: pearson(profile, user_profile), axis=0)
        ratings_sims = pd.DataFrame({'sim': sims, 'rating': their_ratings})
        ratings_sims = ratings_sims[ratings_sims.sim > 0]

        if ratings_sims.empty:
            userest = their_ratings.mean()
        else:
            b = ratings_sims.sort_values(by="sim", axis=0, ascending=False)
            b = b.head(13)
            userest = np.average(b.rating, weights=b.sim)

        user_condition = scorecsv.user_id == user_id
        food_condition = scorecsv.food_id != food_id
        ratings_by_others = scorecsv.loc[user_condition & food_condition]
        if ratings_by_others.empty:
            return 6

        ratings_by_others.set_index('food_id', inplace=True)
        their_ids = ratings_by_others.index
        their_ratings = ratings_by_others.rating
        their_profiles = self.all_food_profiles[their_ids]
        food_profile = self.all_food_profiles[food_id]
        sims = their_profiles.apply(lambda profile: pearson(profile, food_profile), axis=0)
        ratings_sims = pd.DataFrame({'sim': sims, 'rating': their_ratings})
        ratings_sims = ratings_sims[ratings_sims.sim > 0]
        if ratings_sims.empty:
            return (userest + their_ratings.mean()) / 2
        else:
            c = ratings_sims.sort_values(by="sim", axis=0, ascending=False)
            c = c.head(13)
        return (userest + np.average(c.rating, weights=c.sim)) / 2_____no_output_____rec=CollabEuclideanReco()
rec.learn()
rec.estimate(64,19)_____no_output_____

##  Single value decomposition SVD_____no_output_____$$Matrix\ B=\left( \begin{array}{ccc}
R_{u1,f1} & \cdots  & R_{u1,f} \\
\vdots  & \ddots  & \vdots  \\
R_{u,f1} & \cdots  & R_{u,f} \end{array}
\right)\ \ \ \approx \ \ \left( \begin{array}{cc}
R_{u1,k1} &  \begin{array}{cc}
R_{u1,k2} & \cdots  \end{array}
 \\
 \begin{array}{c}
R_{u2,k1} \\
\vdots  \end{array}
 &  \begin{array}{cc}
 \begin{array}{c}
R_{u2,k2} \\
\vdots  \end{array}
 &  \begin{array}{c}
\cdots  \\
R_{u,k} \end{array}
 \end{array}
 \end{array}
\right)\ \ \times\ \ {\left( \begin{array}{cc}
R_{k1,f1} &  \begin{array}{cc}
R_{k1,f2} & \cdots  \end{array}
 \\
 \begin{array}{c}
R_{k2,f1} \\
\vdots  \end{array}
 &  \begin{array}{c}
 \begin{array}{cc}
R_{k2,f2} & \cdots  \end{array}
 \\
 \begin{array}{cc}
\vdots  & R_{k,f} \end{array}
 \end{array}
 \end{array}
\right)}^{Transpose}
$$_____no_output_____$$B = UV^{T}$$
$$
U = \left(
\begin{array}{ccc}
 u_{00} & u_{01} & u_{02}  \\
 u_{10} & u_{11} & u_{12}  \\
 u_{20} & u_{21} & u_{22}  \\
 u_{30} & u_{31} & u_{32}  \\
 u_{40} & u_{41} & u_{42} \\
 u_{50} & u_{51} & u_{52}  \\
 u_{60} & u_{61} & u_{62}  \\
 u_{70} & u_{71} & u_{72}  \\
\end{array}
\right) \, \text{and } V = \left(
\begin{array}{cccccccccc}
 v_{00}  & v_{01} & v_{02} & v_{03} & v_{04} &v_{05} & v_{06} & v_{07} & v_{08} & v_{09} \\
 v_{10}  & v_{11} & v_{12} & v_{13} & v_{14} &v_{15} & v_{16} & v_{17} & v_{18} & v_{19} \\
 v_{20}  & v_{01} & v_{02} & v_{23} & v_{24} &v_{25} & v_{26} & v_{07} & v_{28} & v_{29} \\
\end{array}
\right)
$$_____no_output_____#### Using KNN model to scale down the input array_____no_output_____

def knn_array(f,u):
    R = scoredata().pivot_table('rating', index='user_id', columns='food_id').fillna(0)
    a = list(knnf(f,u).index)
    b = list(knn(u,f).index)
    a.append(f)
    b.append(u)
    RC = R.iloc[R.index.isin(b)]
    RT = R.T
    RT = RT.iloc[RT.index.astype(float).isin(a)]
    RO = RC[RT.index]
    R = np.array(RO)
    return R,RO_____no_output_____

- Example  User -6 and food -19_____no_output_____

R,RO=knn_array(19,64)
RO.head()_____no_output_____import numpy
def matrix_factorization(u, f, steps=3500, alpha=0.0003, beta=0.02):
    R, RO = knn_array(f, u)
    N = len(R)
    M = len(R[0])
    K = 4
    P = numpy.random.rand(N, K)
    Q = numpy.random.rand(M, K)
    Q = Q.T
    for step in range(steps):
        for i in range(len(R)):
            for j in range(len(R[i])):
                if R[i][j] > 0:
                    eij = R[i][j] - numpy.dot(P[i, :], Q[:, j])
                    for k in range(K):
                        P[i][k] = P[i][k] + alpha * (2 * eij * Q[k][j] - beta * P[i][k])
                        Q[k][j] = Q[k][j] + alpha * (2 * eij * P[i][k] - beta * Q[k][j])
        eR = numpy.dot(P, Q)
        e = 0
        for i in range(len(R)):
            for j in range(len(R[i])):
                if R[i][j] > 0:
                    e = e + pow(R[i][j] - numpy.dot(P[i, :], Q[:, j]), 2)
                    for k in range(K):
                        e += (beta / 2) * (pow(P[i][k], 2) + pow(Q[k][j], 2))
        if e < 0.001:
            break
    Rpred = numpy.dot(P, Q)
    scorepred = pd.DataFrame(Rpred[0:, 0:], index=RO.index, columns=RO.columns)
    try:
        temp=scorepred.loc[u].loc[f]
        if temp>0:

            return temp
        else:
            return 0
    except:
        print(u, f, "error")
        return 5.78_____no_output_____matrix_factorization(64,19)_____no_output_____

- real score of user-64 food-19_____no_output_____

realsurvey_64 = pd.read_csv("data/survey/recfood/ID64.csv",index_col=0)
realsurvey_64.loc[19]["score"]_____no_output_____

### Induced Motivation Model (IMM) based on custom goal_____no_output_____The health score of food is different for different users and even on different days when the aim is to complete a dietary goal or keeping a healthy diet. In the process, a user may have a personal goal, such as losing two kilograms of weight in 30 days, gaining four kilograms of weight in 25 days to train muscles. According to [8], the ideal perfect model for a user to gradually change their eating diet and eating choice, specifically, should first recommend tastier but less healthy food to the user, after which it would be possible for them to transit to food which is both tasty and healthy. Thus, we use IMM as the model of weight in the weighted summation to control the combination of tasty and healthy components in order to make user easy to accept. The mathematics model we are using here is a logistic model, with slight changes to the start and end and with logarithmic growth in the middle to satisfy both time efficiency and allow adjustment time for the user to adapt. In the evaluation, we use information of a virtual person to test whether the system meet the needs. The particular person is a normal healthy18 year-old male student with normal activities with an initial height of 180 centimeters and an initial weight of 75 kilograms, and with the goal of losing two kilograms of weight in 30 days. The custom IMM graph is shown in Figure 2:_____no_output__________no_output_____## Health score calculation_____no_output_____$$ H_{u,f}=\sum_{\left(u,f\right)\in }{\frac{h_{f,i}}{h_{u,i}}?a_i}\ \ \left(u,f\in \ L_{{u,P}_f}\right) $$_____no_output_____

def healthscore(user, food):
    def dif(a, b):
        return math.fabs(float(math.fabs(a - b) / b))

    ts = 1  # control differrent type of food and their percentage of calories of a lunch
    if food.type == 1:
        ts = 0.6
    elif food.type == 2:
        ts = 0.3
    elif food.type == 3:
        ts = 0.3
    elif food.type == 4:
        ts = 0.2
    elif food.type == 5:
        ts = 0.1
    basic = user.getcalorie() / 3 * ts
    carbs = ts * basic * 0.45
    fatmin = basic * 0.2 / 9.0 * ts
    fatmax = basic * 0.35 / 9.0 * ts
    sodium = 2400 / 3.0 * ts
    if user.age >= 51:
        sodium = 1500 / 3 * ts
    protein = 0.9 * user.getweight()
    if 0.5 <= user.goal <= 1.5:
        protein = 1.5 * user.getweight()
    elif user.goal > 1.5:
        protein = 2.2 * user.getweight()
    if "protein" in user.special:
        protein = user.special["protein"]
    protein /= 3
    protein *= ts
    if food.getfat() in [fatmin, fatmax]:
        fat = 0
    else:
        fat = min(dif(food.getfat(), fatmin), dif(food.getfat(), fatmax))
    diff = dif(food.getcal(), basic) * 0.5 + dif(food.gettotalcarbs(), carbs) * 0.1 + dif(food.getsodium(),
                                                                                sodium) * 0.1 + dif(
            food.getprotein(), protein) * 0.1 + fat * 0.2
    result -= math.pow(7 * diff, 2) + 100
    if diff <= 0.05:
        result = 100
    if result <= 40: result = 40

    if veg(user, food): result = 0 # if vegtarian, test food if veg, ifnot return 0
    return result

def veg(user, food):
    if "veg" in user.special:
        if food.getspecial("veg"):
            return True
        else:
            return False
def health(p):# input p user_id list
        user = []
        food = fooddata()
        healthcsv = pd.DataFrame()
        for i in p:
            user.append(userselect(i))
        for i in user:
               for j in food:
                    if j.foodid not in healthcsv.columns:
                        healthcsv[j.foodid] = np.nan
                    score = healthscore(i, j)
                    healthcsv.loc[i.id, j.foodid] = score
        healthcsv = healthcsv.round(3)
        return healthcsv_____no_output______health=health([183]).multiply(.1)
_health_____no_output_____

## Evaluation
### Next two algorithms will cause some error so I need to build test & train files and evaluation function to test

- There are few useable open-source large-scale datasets on food ratings; therefore, a three-week track lunch survey was sent out on school days. A survey listing all the courses served in the cafeteria was distributed online as a Google form. The survey reviewed participants’ opinions based only on the taste of the foods they ate for lunch during the specified period expressed as a score ranging from 1 to 10.
- The resulting matrix generated from the track survey contains responses from 186 people and pertains to 64 courses of food. The respondents consist of 165 students aging from 15 to 18 and 21 adult teachers; thus, the matrix has a size of $(186 \times 64)$. Matrix A contained a total of 1235 entries. The system was evaluated by randomly dividing the original Matrix A into $Matrix A_{Training}$ and $Set  S_{Test}$. The size of $Matrix A_{Training} $ is $(555\times3)$ and the cardinality of the Set test is 212. All of the entries were useable and valuable ranging from 1 to 10 as integers.
_____no_output_____#### Create train & test files_____no_output_____

def gene_test_train():
    scorecsv = scoredata()
    scorecsv = scorecsv.ix[np.random.choice(scorecsv.index, size=800, replace=False)]
    user_ids_larger_1 = pd.value_counts(scorecsv.user_id, sort=False) > 1
    user_ids_larger_1 = user_ids_larger_1[user_ids_larger_1].index

    scorecsv = scorecsv.select(lambda l: scorecsv.loc[l, 'user_id'] in user_ids_larger_1)
    assert np.all(scorecsv.user_id.value_counts() > 1)

    def assign_to_set(df):
        sampled_ids = np.random.choice(df.index,
                                       size=np.int64(np.ceil(df.index.size * 0.2)),
                                       replace=False)
        df.ix[sampled_ids, 'for_testing'] = True
        return df

    scorecsv['for_testing'] = False
    grouped = scorecsv.groupby('user_id', group_keys=False).apply(assign_to_set)
    scorecsv_train = scorecsv[grouped.for_testing == False]
    scorecsv_test = scorecsv[grouped.for_testing == True]
    assert len(scorecsv_train.index & scorecsv_test.index) == 0
    scorecsv_train.to_csv('E:/project/main/DRS/DRS/data/test_score/scorecsv_train.csv')
    scorecsv_test.to_csv('E:/project/main/DRS/DRS/data/test_score/scorecsv_test.csv')
gene_test_train()_____no_output_____scorecsv_train = pd.read_csv('E:/project/main/DRS/DRS/data/test_score/scorecsv_train.csv',index_col=0)
scorecsv_test=pd.read_csv('E:/project/main/DRS/DRS/data/test_score/scorecsv_test.csv',index_col=0)_____no_output_____scorecsv_train.head()_____no_output_____scorecsv_test.head()_____no_output_____

### Evaluation:

- RMSE: $\sqrt{\frac{\sum(\hat y - y)^2}{n}}$
_____no_output_____

def compute_rmse(y_pred, y_true):
    """ Compute Root Mean Squared Error. """

    return np.sqrt(np.mean(np.power(y_pred - y_true, 2)))_____no_output_____def evaluate_data():
    train = pd.read_csv("E:/project/main/DRS/DRS/data/test_score/scorecsv_train.csv")
    test = pd.read_csv("E:/project/main/DRS/DRS/data/test_score/scorecsv_test.csv")
    return train, test

def evaluate(estimate_f):
    """ RMSE-based predictive performance evaluation with pandas. """
    scorecsv_train, scorecsv_test = evaluate_data()
    ids_to_estimate = zip(scorecsv_test.user_id, scorecsv_test.food_id)
    estimated = np.array([estimate_f(u, i) for (u, i) in ids_to_estimate])
    real = scorecsv_test.rating.values
    return compute_rmse(estimated, real)_____no_output_____

## Evaluation Result_____no_output_____### naive program  (using average score)_____no_output_____

def naive(a,b):
    return 5.798
evaluate(naive)_____no_output_____

### pure KNN method_____no_output_____

Euclidean
Cosine
Pearson

RMSE-Using training dataset
2.46493
2.49006
2.54757

RMSE-Using whole dataset
1.85630
2.28051
1.80219

_____no_output_____

Figure 3 Cross Validation from KNN Algorithm_____no_output_____### Regular LFM Algorithm_____no_output_____- When Step=3500,α=0.0003 and β=0.02,RMSE = 1.00169_____no_output_____- Best Time of Three is 5 min 30 s_____no_output_____### Modified LFM Algorithm ( Hybrid method with KNN)_____no_output_____#### Test RMSE in different cost function components_____no_output_____
Figure 3 Cross Validation for LFM algorithm: Steps are ranged from 3000 to 7000
_____no_output_____
Figure 4 Cross Validation for LFM algorithm: α is ranging from 0.0001 to 0.001_____no_output_____
Figure 5 Cross Validation for LFM algorithm: β is ranging from 0.01 to 0.1_____no_output_____- best time of three is 13.1 seconds_____no_output_____### Health_____no_output_____The virtual user we created to represent normal health conditions is an 18 year-old male student with normal activities and with an initial height of 180 centimeters  and an initial weight 75 kilograms. The goal that was specified for weight loss was 2kilograms in 30 days; thus, his BMR was about 1860.55 and the daily calories needed to satisfy the goal were about 2435 calories per day for about 1000 calories permitted for lunch. In the list of recommended foods, 86.67% of the food was found to satisfy the user’s needs._____no_output_____## Real test result_____no_output_____This study used a post-survey to receive feedback about the accuracy of the prediction. In the post-survey, the system generated six recommended foods and randomly select six foods from food list. Mixing the order of the total 12 foods, the survey is a blind test in order to make sure people answer this survey without personal influences. There are total 376 foods’ rating received. Among 376 foods, 188 foods are from recommended food list with 63.3% accuracy predicting user’s attitude on foods. The average rating for recommended foods is 7.87234, while the average ranting for random foods is 4.49468. It clearly shows more foods in the recommended food list are tasty for users.
_____no_output_____## REFFERENCES
- [1] Gunawardana, A., & Shani, G. (2015). Evaluating Recommender Systems. Recommender Systems Handbook, 265-308.
- [2] Neumann, S., Thum, A., & Böttcher, C. (2012). Nearline acquisition and processing of liquid chromatography-tandem mass spectrometry data. Metabolomics, 9(S1), 84-91.
- [3] Sarwar, B., Karypis, G., Konstan, J., & Reidl, J. (2001). Item-based collaborative filtering recommendation algorithms. Proceedings of the Tenth International Conference on World Wide Web - WWW '01.
- [4] Matrix Factorization: A Simple Tutorial and Implementation in Python. (n.d.). Retrieved Jan, 2016, from http://www.quuxlabs.com/blog/2010/09/matrix-factorization-a-simple-tutorial-and-implementation-in-python/
- [5] Schelter, S., Boden, C., & Markl, V. (2012). Scalable similarity-based neighborhood methods with MapReduce. Proceedings of the Sixth ACM Conference on Recommender Systems - RecSys '12.
- [6] Ryza, S., Laserson, U., Owen, S., & Wills, J. (2015). Advanced analytics with Spark. Sebastopol, CA: O'Reilly Media.
- [7] Takács, G., Pilászy, I., Németh, B., & Tikk, D. (2008). Matrix factorization and neighbor based algorithms for the netflix prize problem. Proceedings of the 2008 ACM Conference on Recommender Systems - RecSys '08.
- [8] Miller, Neal E. "Motivation and Psychological Stress." The Physiological Mechanisms of Motivation (1982): 409-32. Web.
- [9] Stephen, Farenga, J., and Ness Daniel. "Calories, Energy, and the Food You Eat." Questia. Web. Feb. 2016. <https://www.questia.com/article/1G1-143734185/calories-energy-and-the-food-you-eat>.
- [10] "Nonessential Nitrogen Supplements And Essential Amino Acid Requirements." Nutrition Reviews 21.6 (2009): 183-84. Web.
_____no_output_____