# Notebook from waltherg/georgio
Path: _notebooks/2013-12-16-Most_Similar_PLoS_Biology.ipynb

# "Document clustering applied to scientific articles in Python"
> "In this article we look at document clustering implemented in Python with scikit-learn."
- toc: true
- branch: master
- badges: true
- comments: true
- categories: [python, scikit-learn, nlp]_____no_output_____# The Ten Most Similar PLoS Biology Articles_____no_output_____... at least by some measure.

I recently downloaded 1754 PLoS Biology articles as XML files through the PLoS API
and have looked at the distribution of the [time to publication](http://georg.io/2013/10/PLoS_Time_to_Publication/)
of PLoS Biology and other PLoS journals._____no_output_____Here I will play a little with [scikit-learn](http://scikit-learn.org/stable/) to see if I can discover those
PLoS Biology articles (in my data set) that are most similar to one another._____no_output_____## Import Packages_____no_output_____I started writing a [Python package (PLoSPy)](https://github.com/waltherg/PLoSPy) for more convient parsing
of the XML files I have download from PLoS._____no_output_____

import plospy
import os
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel
import itertools_____no_output_____

## Discover Data Files on Hard Disk_____no_output_____

all_names = [name for name in os.listdir('../plos/plos_biology/plos_biology_data') if '.dat' in name]_____no_output_____all_names[0:10]_____no_output_____print len(all_names)_____no_output_____

## Vectorize all Articles_____no_output_____To reduce memory use, I wrote the following method that returns an iterator over all article bodies.
In passing this iterator to the vectorizer, we avoid loading all articles into memory at once - despite
the use of an iterator here, I have not been able to repeat this experiment with all 65,000-odd PLoS ONE
articles without running out of memory._____no_output_____

ids = []
titles = []

def get_corpus(all_names):
    for name_i, name in enumerate(all_names):
        docs = plospy.PlosXml('../plos/plos_biology/plos_biology_data/'+name)
        for article in docs.docs:
            ids.append(article['id'])
            titles.append(article['title'])
            yield article['body']_____no_output_____corpus = get_corpus(all_names)
tfidf = TfidfVectorizer().fit_transform(corpus)_____no_output_____

Just as a sanity check, the number of DOIs in our data set should now equal 1754 as this is the number
of articles [I downloaded in the first place](http://georg.io/2013/10/PLoS_Time_to_Publication)._____no_output_____

len(ids)_____no_output_____

The vectorizer generated a matrix with 139,748 columns (these are the tokens, i.e. probably unique words used in
all 1754 PLoS Biology articles) and 1754 rows (corresponding to individual articles)._____no_output_____

tfidf.shape_____no_output_____

Let us now compute all pairwise cosine distances betweeen all 1754 vectors (articles) in matrix `tfidf`.
I copied and pasted most of this from a StackOverflow answer that I cannot find now - I will
add a link to the answer when I come across it again.

To get the ten most similar articles, we track the top five pairwise matches._____no_output_____

top_five = [[-1,-1,-1] for i in range(5)]
threshold = -1.

for index in range(len(ids)):
    cosine_similarities = linear_kernel(tfidf[index:index+1], tfidf).flatten()
    related_docs_indices = cosine_similarities.argsort()[:-5:-1]

    first = related_docs_indices[0]
    second = related_docs_indices[1]

    if first != index:
        print 'Error'
        break

    if cosine_similarities[second] > threshold:
        if first not in [top[0] for top in top_five] and first not in [top[1] for top in top_five]:
            scores = [top[2] for top in top_five]
            replace = scores.index(min(scores))
            # print 'replace',replace
            top_five[replace] = [first, second, cosine_similarities[second]]
            # print 'old threshold',threshold
            threshold = min(scores)
            # print 'new threshold',threshold_____no_output_____

## The Most Similar Articles_____no_output_____Let us now take a look at the results!_____no_output_____

for tf in top_five:
    print ''
    print('Cosine Similarity: %.2f' % tf[2])
    print('Title 1: %s' %titles[tf[0]])
    print('http://www.plosbiology.org/article/info%3Adoi%2F'+str(ids[tf[0]]))
    print ''
    print('Title 2: %s' %titles[tf[1]])
    print('http://www.plosbiology.org/article/info%3Adoi%2F'+str(ids[tf[1]]))
    print ''_____no_output_____


# Notebook from jason-neal/companion_simulations
Path: Notebooks/Try todcorr on simulated obs.ipynb

# Try todcorr algorithm
from https://github.com/zkbt/zachopy/blob/master/oned.py_____no_output_____

<code>
import matplotlib.pyplot as plt_____no_output_____import scipy
import numpy as np

def subtractContinuum(s, n=3, plot=False):
    '''Take a 1D array, use spline to subtract off continuum.
            subtractContinuum(s, n=3)
            required:
            s = the array
            optional:
            n = 3, the number of spline points to use
    '''

    x = np.arange(len(s))
    points = (np.arange(n)+1)*len(s)/(n+1)
    spline = scipy.interpolate.LSQUnivariateSpline(x,s,points)
    if plot:
        plt.ion()
        plt.figure()
        plt.plot(x, s)
        plt.plot(x, spline(x), linewidth=5, alpha=0.5)
    return s - spline(x)

def ccf(f, g, scale=1.0):
    '''Calculate the normalized cross-correlation function of two identically-size arrays.
        [required]:
        f = an N-element array (for example, spectrum of target star)
        g = an N-element array (for example, spectrum of template star)
        scale = a scalar indicating what the indices of f and g (one unit of "lag") correspond to
    '''

    # how long are our arrays
    N = len(f)

    # define the x-axis, if not supplied
    assert(len(f) == len(g))
    x = np.arange(-N+1, N, 1.0)*scale

    # calculation the normalized cross-correlation function
    sigma_f = np.sqrt(np.sum(f**2)/N)
    sigma_g = np.sqrt(np.sum(g**2)/N)
    C_fg = np.correlate(f, g, 'full')/N/sigma_f/sigma_g

    # WILL THIS WORK?
    return scipy.interpolate.interp1d(x,C_fg, fill_value=0.0, bounds_error=False)

def todcor(f, g1, g2, scale=1.0, luminosity_ratio=None):
    '''Calculate the 2D correlation of a 1D array with two template arrays.

    '''

    assert(len(f) == len(g1))
    assert(len(f) == len(g2))

    C_1 = ccf(f, g1, scale=scale)
    C_2 = ccf(f, g2, scale=scale)
    C_12 = ccf(g1, g2, scale=scale)

    N = len(f)
    sigma_g1 = np.sqrt(np.sum(g1**2)/N)
    sigma_g2 = np.sqrt(np.sum(g2**2)/N)

    def bestalphaprime(s1, s2):
        return sigma_g1/sigma_g2*(C_1(s1)*C_12(s2 - s1) - C_2(s2))/(C_2(s2)*C_12(s2-s1) - C_1(s1))

    def R(s1, s2):
        #a =
        if luminosity_ratio is None:
            a = np.maximum(np.minimum(bestalphaprime(s1,s2), sigma_g2/sigma_g1), 0.0)
            flexiblecorrelation = (C_1(s1) + a*C_2(s2))/np.sqrt(1.0 + 2*a*C_12(s2 - s1) + a**2)
            ok = np.isfinite(a)
            peak = np.argmax(flexiblecorrelation[ok].flatten())
            a = a[ok].flatten()[peak]
        else:
            a = luminosity_ratio*sigma_g2/sigma_g1
        # print("alpha spans", np.min(a), np.max(a))
        return (C_1(s1) + a*C_2(s2))/np.sqrt(1.0 + 2*a*C_12(s2 - s1) + a**2), a*sigma_g1/sigma_g2

    return R

_____no_output_____from astropy.io import fits
spec1 = fits.getdata("../tests/testdata/lte05200-4.50-0.0.PHOENIX-ACES-AGSS-COND-2011-HiRes.fits")
spec2 = fits.getdata("../tests/testdata/lte03400-4.50-0.0.PHOENIX-ACES-AGSS-COND-2011-HiRes.fits")
wav = fits.getdata("../tests/testdata/WAVE_PHOENIX-ACES-AGSS-COND-2011.fits")
wav = wav/10

from PyAstronomy import pyasl

spec1_shifted, wlprime = pyasl.dopplerShift(wav, spec1, -5)
spec2_shifted, wlprime = pyasl.dopplerShift(wav, spec2, 15)
spec_3 = spec1_shifted + spec2_shifted

spec_1 = spec1[(wav > 2100) * (wav< 2200)]
spec_2 = spec2[(wav > 2100) * (wav< 2200)]
spec_3 = spec_3[(wav > 2100) * (wav< 2200)]
wav_3 = wav[(wav > 2100) * (wav< 2200)]

# need to log resample so that evenly spaced in rv
def log_resample(x, drv=0.01):
    logx = np.log(x)
    logx = np.arange(logx[0], logx[-1], drv)
    return np.exp(logx)

new_full_wav = log_resample(wav, 0.001)
new_wav = log_resample(wav_3)
new_s1 = np.interp(new_full_wav, wav, spec1)
new_s2 = np.interp(new_full_wav, wav, spec2)
new_s3 = np.interp(new_wav, wav_3, spec_3)

_____no_output_____plt.plot(wav, spec1, label="1")
plt.plot(new_full_wav, new_s1, label="1")
plt.show()
plt.plot(wav_3, spec2, label="2")
plt.show()
plt.plot(wav_3, spec_3, label="3")
plt.legend()
plt.show()_____no_output_____s1 = subtractContinuum(spec1, n=5)
s2 = subtractContinuum(spec2,n=5)
s3 = subtractContinuum(spec_3,n=5)

plt.plot(s1)
plt.plot(s2)
plt.plot(s3)
plt.show()_____no_output_____R = todcor(s3, s1, s2)_____no_output_____rv = np.arange(-50, 50, 0.5)
gamma = np.arange(-50, 50, 1)
RV, GAM = np.meshgrid(rv, gamma, indexing="ij")
c_n1_n2 = np.empty((len(rv), len(gamma)))
a_n1_n2 = np.empty((len(rv), len(gamma)))

for jj, y in enumerate(gamma):
    for ii, x in enumerate(rv):

        res = R(x, y)
        c_n1_n2[ii, jj] = res[0]
        a_n1_n2[ii, jj] = res[1]
_____no_output_____plt.contourf(RV, GAM, c_n1_n2)

plt.title("correlation value")
plt.show()

a_n1_n2
plt.contourf(RV, GAM, a_n1_n2)
plt.title("flux ratio")
plt.show()_____no_output_____
</code>


# Notebook from ManchesterBioinference/GPcounts
Path: demo_notebooks/bulk_time_series.ipynb

# GPcounts applied to bulk RNA-Seq time series data_____no_output_____Nuha BinTayyash, 2020

GPcounts is a Gaussian process regression package for counts data with negative binomial and zero-inflated negative binomial likelihoods as described in the paper "Non-parametric modelling of temporal and spatial counts data from RNA-seq experiments".

This notebook shows how to run GPcounts with a negative binomial likelihood and we compare results with the more standard Gaussian likelihood to find differentially expressed genes using infer trajectory, one sample and two samples cases on the [fission yeast](https://bioconductor.org/packages/release/data/experiment/html/fission.html) gene expression dataset._____no_output_____

import numpy as np
import pandas as pd
import gpflow
from IPython.display import display_____no_output_____import tensorflow as tf _____no_output_____tf.__version_______no_output_____gpflow.__version_______no_output_____filename = '../data/fission_normalized_counts.csv'
Y = pd.read_csv(filename,index_col=[0])
X = pd.read_csv('../data/fission_col_data.csv',index_col=[0])
X = X[['minute']]_____no_output_____from GPcounts.GPcounts_Module import Fit_GPcounts_____no_output_____

Extract time series data for one gene_____no_output_____

genes_name = ['SPAC11D3.01c']
gp_counts = Fit_GPcounts(X.iloc[0:18,:],Y.iloc[:,0:18].loc[genes_name]) _____no_output_____

## 1. Using GP regression to learn hyper-parameters and infer trajectories _____no_output_____

likelihood = 'Negative_binomial' # choose the likelihood
results = gp_counts.Infer_trajectory(likelihood)
display(results)100%|██████████| 1/1 [00:03<00:00,  3.33s/it]

### Save GPflow models_____no_output_____By default, GPcounts create GPcounts_models folder to save GPflow models as checkpoints using tf.train.Checkpoint throughout the training procedure. The package saves single GPflow model, per each GP fit for each single gene and for each likelihood._____no_output_____To change the default folder name, use gp_counts object to set Folder name parameter._____no_output_____

gp_counts.folder_name = 'test_folder/'
likelihood = 'Negative_binomial' # choose the likelihood
results = gp_counts.Infer_trajectory(likelihood)
display(results)100%|██████████| 1/1 [00:00<00:00,  1.22it/s]

###  Print GP hyper-parameters_____no_output_____To print GP hyper-parameters, use GPcounts object to call load_predic_models() method that loads GPflow models for list of genes and make prediction with the selected likelihood. Predict argument is true by default, to load models only set predict to False.  _____no_output_____\** Note that gp_counts object is shared between different tests and likelihoods so you have to specify the test and likelihood you want to load its results._____no_output_____

genes_name = results.index.values # list of ganes name
test_name = 'Infer_trajectory' # name of the test
params = gp_counts.load_predict_models(genes_name,test_name,likelihood)100%|██████████| 1/1 [00:00<00:00,  2.08it/s]

###### load_predict_models() method returns params and params is a dictionary of three items_____no_output_____I. params['models'][0] is list of GPflow models of the first gene
_____no_output_____

gpflow.utilities.print_summary(params['models'][0], fmt='notebook')_____no_output_____

II. params['means'][0] is list of means predicted using GPflow models
_____no_output_____III. params['vars'][0] is list of variances/percentiles predicted using GPflow models
_____no_output_____### Plot GP posterior predictive distribution _____no_output_____To plot the fit of GP model on genes use plot function from helper.py file. In plot function we plot the GP posterior predictive distribution for each model and show $\pm1$ standard deviation in dark shade and $\pm2$ standard deviation in light shade for Gaussian likelihood and its equivalent percentiles for non-Gaussian likelihoods._____no_output_____

from helper import plot
plot(params,X.iloc[0:18,:].values,Y.iloc[:,0:18].loc[genes_name],results)SPAC11D3.01c

###  Initialize GP hyper-parameters_____no_output_____To replace the initial values for GPflow hyper-parameters, use GPcounts object to call init_hyper_parameters(length_scale,variance,alpha,km) method._____no_output_____

length_scale = 1.
variance = 1.
alpha = 1.
km = 1.
gp_counts.initialize_hyper_parameters(length_scale,variance,alpha,km)_____no_output_____results = gp_counts.Infer_trajectory(likelihood)
display(results)100%|██████████| 1/1 [00:00<00:00,  1.73it/s]

To restore the default hyper-parameters initialization for GPcounts call gp_counts.initialize_hyper_parameters() without sending any arguments_____no_output_____

gp_counts.initialize_hyper_parameters()_____no_output_____

To use GPcounts with Gaussian likelihood assuming log counts transformation $log(y+1)$ change the likelihood parameter_____no_output_____

likelihood = 'Gaussian' # change the likelihood to Gaussian will apply log count tramsformation log(y+1)
results = gp_counts.Infer_trajectory(likelihood)
display(results)100%|██████████| 1/1 [00:00<00:00,  2.27it/s]

Load GPflow models for Gaussian likelihood and print hyper-parameters_____no_output_____

params = gp_counts.load_predict_models(genes_name,test_name,likelihood)100%|██████████| 1/1 [00:00<00:00, 43.92it/s]
gpflow.utilities.print_summary(params['models'][0], fmt='notebook')_____no_output_____

Plot GP fit for Gaussian likelihood_____no_output_____

plot(params,X.iloc[0:18,:].values,Y.iloc[:,0:18].loc[genes_name],results)SPAC11D3.01c

To use GPcounts with Gaussian likelihood assuming any other transformation (not log count) change the likelihood parameter to Gaussian, set transform parameter to False and pass y transformed to GPcounts_____no_output_____\* Y is transformed using Anscombe transformation.ipynb notebook from [SpatialDE](https://github.com/Teichlab/SpatialDE) package_____no_output_____

filename = '../data/Anscombe_transformation_fission_normalized_counts.csv'
Y_transformed = pd.read_csv(filename,index_col=[0]) # Y is transformed using Anscombe transformation
gp_counts = Fit_GPcounts(X.iloc[0:18,:],Y_transformed.iloc[:,0:18].loc[genes_name]) _____no_output_____likelihood = 'Gaussian'
results = gp_counts.Infer_trajectory(likelihood,transform = False)
display(results)100%|██████████| 1/1 [00:00<00:00,  3.60it/s]
params = gp_counts.load_predict_models(genes_name,test_name,likelihood)
plot(params,X.iloc[0:18,:].values,Y_transformed.iloc[:,0:18].loc[genes_name],results)100%|██████████| 1/1 [00:00<00:00, 32.73it/s]

## 2. One-sample test

In a one-sample test we compute the log-likelihood ratio (LLR) between a dynamic and constant model_____no_output_____

gp_counts = Fit_GPcounts(X.iloc[0:18,:],Y.iloc[:,0:18].loc[genes_name]) _____no_output_____likelihood = 'Negative_binomial'
results = gp_counts.One_sample_test(likelihood)
display(results)100%|██████████| 1/1 [00:01<00:00,  1.44s/it]

Change test type to one sample test then load GPflow models and plot the GP fit _____no_output_____

test_name = 'One_sample_test'
params = gp_counts.load_predict_models(genes_name,test_name,likelihood)
plot(params,X.iloc[0:18,:].values,Y.iloc[:,0:18].loc[genes_name],results)100%|██████████| 1/1 [00:01<00:00,  1.01s/it]likelihood = 'Gaussian'
results = gp_counts.One_sample_test(likelihood)
display(results)100%|██████████| 1/1 [00:00<00:00,  1.72it/s]
params = gp_counts.load_predict_models(genes_name,test_name,likelihood)
plot(params,X.iloc[0:18,:].values,Y.iloc[:,0:18].loc[genes_name],results)100%|██████████| 1/1 [00:00<00:00, 18.63it/s]

## 3. Two-sample test

In a two-sample test we test the LLR between a model where the two time-series are replicates (same mean trajectory) and a model where the tractories are different (independent trajectories).

First we create a new GPcounts object to containing time series from two different conditions_____no_output_____

gp_counts = Fit_GPcounts(X,Y.loc[genes_name])_____no_output_____

Below we carry out a two sample test with a negative binomial likelihood

The shared-trajectory model has a lower log-likelihood than the sum of the independent model log-likelihood, providing evidence that the trajectories are different. _____no_output_____

likelihood = 'Negative_binomial'
results = gp_counts.Two_samples_test(likelihood)
display(results)100%|██████████| 1/1 [00:03<00:00,  3.74s/it]
test_name = 'Two_samples_test'
params = gp_counts.load_predict_models(genes_name,test_name,likelihood)
plot(params,X.values,Y.loc[genes_name],results)100%|██████████| 1/1 [00:01<00:00,  1.74s/it]

### Safe mode option_____no_output_____Set safe_mode to True to check:
1. if the mean of the GP posterior predictive distribution is in the mean of the data.
2. if the log-likelihood ratio LLR < 0 for small lengthscale or if the LLR takes on extreme values that would indicate a very large difference between the time-varying GP and constant model._____no_output_____

gp_counts = Fit_GPcounts(X,Y.loc[genes_name],safe_mode = True)
likelihood = 'Negative_binomial'
results = gp_counts.Two_samples_test(likelihood)
display(results)100%|██████████| 1/1 [00:12<00:00, 12.56s/it]

Now we carry out a two sample test with a Gaussian likelihood - in this case the shared model has a higher log-likelihood than the sum of the independent model log-likelihoods_____no_output_____

test_name = 'Two_samples_test'
params = gp_counts.load_predict_models(genes_name,test_name,likelihood)
plot(params,X.values,Y.loc[genes_name],results)100%|██████████| 1/1 [00:01<00:00,  1.50s/it]likelihood = 'Gaussian'
results = gp_counts.Two_samples_test(likelihood)
display(results)100%|██████████| 1/1 [00:00<00:00,  1.05it/s]
params = gp_counts.load_predict_models(genes_name,test_name,likelihood)
plot(params,X.values,Y.loc[genes_name],results)100%|██████████| 1/1 [00:00<00:00, 14.32it/s]


# Notebook from ka-rocha/LSSTC-DSFP-Sessions
Path: Sessions/Session10/Day2/MultiDimensionalMCMC.ipynb

!pip install emcee==3.0rc2
!pip install corner_____no_output_____import numpy as np
import pandas as pd
from scipy.optimize import minimize, newton
import emcee
import corner

import matplotlib.pyplot as plt

np.random.seed(42)_____no_output_____

#   Multi-Dimensional Integration with MCMC

-----

By Megan Bedell (Flatiron Institute)

10 September 2019_____no_output_____### Problem 1: Fitting a Sinusoid to Data

In this example, we will download a time series of radial velocities for the star HD209458. This star hosts a Hot Jupiter exoplanet. In fact, [this planet](https://en.wikipedia.org/wiki/HD_209458_b) was the first to be seen in transit and was discovered 20 years ago yesterday!

Because the eccentricity is low for this planet, we can fit its orbit in the radial velocities with a relatively simple model: a sinusoid._____no_output_____Below is a snippet of code that will download the time-series data from [NASA Exoplanet Archive](https://exoplanetarchive.ipac.caltech.edu/):_____no_output_____

datafile = 'https://exoplanetarchive.ipac.caltech.edu/data/ExoData/0108/0108859/data/UID_0108859_RVC_001.tbl'
data = pd.read_fwf(datafile, header=0, names=['t', 'rv', 'rv_err'], skiprows=22)
data['t'] -= data['t'][0]_____no_output_____

#### Problem 1a
Plot the data. Let's take a look at what we're working with!_____no_output_____

fig, ax = plt.subplots()
ax.errorbar( # complete
ax.set_xlabel('Time (days)')
ax.set_ylabel(r'RV (m s$^{-1}$)');_____no_output_____

#### Problem 1b

Write the sinusoid function that we want to fit and get ready to run MCMC with helper functions._____no_output_____First let's write a "get_model_predictions" function - this will resemble yesterday's same-named function, but instead of returning a line it should return a sinusoid. I suggest using the following free parameters, although there are a few alternative options that you may use instead:

```
theta = [p, # period of the sinusoid
         a, # semi-amplitude of the sinusoid
         t0, # reference x at which sine phase = 0
         rv0] # constant offset in y
```

The RV prediction is then:
$$RV(t) = a \sin\Big(\frac{2\pi}{p} (t - t_0)\Big) + rv_0$$
_____no_output_____

def get_model_predictions(theta, t):
  '''
  Calculate RV predictions for parameters theta and timestamps t.
  '''
  period, amplitude, t0, rv0 = theta
  model_preds = # complete
  return model_preds_____no_output_____

Write a lnprior function with flat priors on all parameters - again, this will be similar to yesterday's function, but with different values.

*Hint: some of the bounds on these parameters will be physically motivated (i.e. orbital period cannot be negative). For others, you'll need to guess something reasonable but generous - i.e., a Hot Jupiter planet probably does not have an orbital period above a year or so.*_____no_output_____

def lnprior(theta):
    period, amplitude, t0, rv0 = theta
    if 0 < period <= 1e4 and # complete
        lnp = np.log(1e-4) + # complete
    else:
        return -np.inf
    return lnp_____no_output_____

The following functions can be reused as-is from the previous day's Metropolis-Hastings exercise, so just copy-and-paste or import them:

lnlikelihood, lnposterior, hastings_ratio, propose_jump, mh_mcmc

_____no_output_____

def lnlikelihood(theta, y, x, y_unc):
    model_preds = get_model_predictions(theta, x)

    lnl = -np.sum((y-model_preds)**2/(2*y_unc**2))

    return lnl

def lnposterior(theta, y, x, y_unc):
    lnp = lnprior(theta)
    if not np.isfinite(lnp):
        return -np.inf
    lnl = lnlikelihood(theta, y, x, y_unc)
    lnpost = lnl + lnp

    return lnpost

def hastings_ratio(theta_1, theta_0, y, x, y_unc):
    lnpost1 = lnposterior(theta_1, y, x, y_unc)
    lnpost0 = lnposterior(theta_0, y, x, y_unc)

    h_ratio = np.exp(lnpost1 - lnpost0)

    return h_ratio

def propose_jump(theta, cov):
    if np.shape(theta) == np.shape(cov):
        cov = np.diag(np.array(cov)**2)

    proposed_position = np.random.multivariate_normal(theta, cov)

    return proposed_position

def mh_mcmc(theta_0, cov, nsteps, y, x, y_unc):

    positions = np.zeros((nsteps+1, len(theta_0)))
    lnpost_at_pos = -np.inf*np.ones(nsteps+1)
    acceptance_ratio = np.zeros_like(lnpost_at_pos)
    accepted = 0

    positions[0] = theta_0
    lnpost_at_pos[0] = lnposterior(theta_0, y, x, y_unc)

    for step_num in np.arange(1, nsteps+1):
        proposal = propose_jump(positions[step_num-1], cov)
        H = hastings_ratio(proposal, positions[step_num-1], y, x, y_unc)
        R = np.random.uniform()

        if H > R:
            accepted += 1
            positions[step_num] = proposal
            lnpost_at_pos[step_num] = lnposterior(proposal, y, x, y_unc)
            acceptance_ratio[step_num] = float(accepted)/step_num
        else:
            positions[step_num] = positions[step_num-1]
            lnpost_at_pos[step_num] = lnpost_at_pos[step_num-1]
            acceptance_ratio[step_num] = float(accepted)/step_num

    return (positions, lnpost_at_pos, acceptance_ratio)
 _____no_output_____

#### Problem 1c

Run the MCMC._____no_output_____Let's start with initialization values.

To save some time, I will assert that if we made a Lomb-Scargle periodogram of the RVs, there would be a peak near period = 3.53 days, so start with that guess and let's figure out what the best values might be for the other parameters.

(If you finish early and are up for a bonus problem, you can double-check my assertion using [astropy timeseries](https://docs.astropy.org/en/stable/timeseries/lombscargle.html)!)_____no_output_____

theta_0 = [3.53, # complete_____no_output_____

Now run the MCMC for 5000 steps. I'll give you (the diagonal of a) covariance matrix for the multi-dimensional Gaussian proposal function to start with. As you saw yesterday afternoon, this `cov` parameter sets the step sizes that the M-H algorithm will use when it proposes new values._____no_output_____

cov = [0.01, 1, 0.05, 0.01]
pos, lnpost, acc = mh_mcmc( # complete_____no_output_____

Do a pairs plot for the first two parameters. Does the behavior of this chain seem efficient?_____no_output_____

fig, ax = plt.subplots()
ax.plot( # complete
ax.plot(theta_0[0], theta_0[1], '*', ms=30,
        mfc='Crimson', mec='0.8', mew=2,
        alpha=0.7)
ax.set_xlabel('Period', fontsize=14)
ax.set_ylabel(r'K (m s$^{-1}$)', fontsize=14)
fig.tight_layout()_____no_output_____

#### Problem 1d

There were a couple of issues with the previous MCMC run. Let's start with this one: we started the chains running at a place that was not very close to the best-fit solution.

Find a better set of initialization values by optimizing *before* we run the MCMC._____no_output_____We'll use scipy.optimize.minimize to get best-fit parameters. Remember that the lnlikelihood function needs to be *maximized* not *minimized*, so we'll need a new function that works the same way, but negative._____no_output_____

def nll(*par):
  #complete

res = minimize(nll, theta_0,
               args=(data['rv'], data['t'], data['rv_err']),
               method='Powell')
print('Optimizer finished with message "{0}" and \n\
      best-fit parameters {1}'.format(res['message'], res['x']))_____no_output_____

Plot the data points and your best-fit model. Does the fit look reasonable? (You may need to zoom into a small time range to tell.)_____no_output_____

# complete
plt.xlabel('Time (days)')
plt.ylabel(r'RV (m s$^{-1}$)');_____no_output_____

Another way to see if we're on the right track is to plot the data phased to the orbital period that we found. Do that and optionally overplot the phased model as well._____no_output_____

period, amplitude, t0, rv0 = res['x']

fig, ax = plt.subplots()
phased_t = (data['t'] - t0) % period
# complete_____no_output_____

Now re-run the MCMC using these parameters as the initial values and make another pairs plot. Again, I'm going to give you some step size parameters to start with. Because we're now initializing the chain close to the likelihood maximum, we don't want it to move too far away, so I've lowered the values of `cov`._____no_output_____

theta_bestfit = res['x']
cov = [0.001, 0.1, 0.01, 0.1]

pos, lnpost, acc = mh_mcmc( # complete_____no_output_____fig, ax = plt.subplots()
ax.plot( # complete
ax.plot(theta_bestfit[0], theta_bestfit[1], '*', ms=30,
        mfc='Crimson', mec='0.8', mew=2,
        alpha=0.7)
ax.set_xlabel('Period', fontsize=14)
ax.set_ylabel(r'K (m s$^{-1}$)', fontsize=14)
fig.tight_layout()_____no_output_____

#### Problem 1e

Now let's tackle another issues: chain efficiency. Calculate the auto-correlation length of your chain._____no_output_____First, let's just plot the sequence of orbital period values in the chain in a trace plot. From eyeballing this sequence, about how many steps do you think are needed to reach a sample that is independent from the previous one(s)?_____no_output_____

plt.plot( # complete_____no_output_____

Writing an autocorrelation function for this purpose actually gets a bit tricky, so we'll use the built-in functionality of emcee.

For the documentation on these functions, check [the emcee user guide](https://emcee.readthedocs.io/en/latest/user/autocorr/).

For a more in-depth look at how this is calculated and why it's tricky, check out [this tutorial](https://emcee.readthedocs.io/en/latest/tutorials/autocorr/)._____no_output_____

acf = emcee.autocorr.function_1d(pos[:,0])
plt.plot(acf)
plt.xlabel('Lag')
plt.ylabel('Normalized ACF');_____no_output_____act = emcee.autocorr.integrated_time(pos[:,0], quiet=True)
print('The integrated autocorrelation time is estimated as: {0}'.format(act))_____no_output_____

#### Problem 1f

Change the step size of the MCMC. What does this do to the auto-correlation length? Does this seem better or worse, and why?_____no_output_____

cov = [0.0001, 0.1, 0.01, 0.1]
pos, lnpost, acc = mh_mcmc( # complete_____no_output_____plt.plot( # complete_____no_output_____acf = # complete_____no_output_____act = # complete_____no_output_____

#### Problem 1g

Using the step sizes and starting conditions that you deem best, run your MCMC for *at least* 500x the auto-correlation length to get a large number of independent samples. Plot the posterior distribution of radial velocity semi-amplitude K. This parameter is arguably the most important output of an RV fit, because it is a measurement of the mass of the planet._____no_output_____

pos, lnpost, acc = mh_mcmc(# complete_____no_output_____plt.hist( # complete
plt.xlabel(r'K (m s$^{-1}$)');_____no_output_____

From these results, what can we say about the true value of K? What is the probability that K > 84 m/s? 85 m/s? 90 m/s? Are these numbers a reliable estimator of the true probability, in your opinion?_____no_output_____

print('The probability that K > 84 m/s is: {0:.2f}'.format( # complete
print('The probability that K > 85 m/s is: {0:.2f}'.format( # complete
print('The probability that K > 90 m/s is: {0:.2f}'.format( # complete_____no_output_____

#### Challenge Problem 1h

Try some different values of `cov[0]` (the step size for the orbital period). Make a plot of the acceptance fraction as a function of step size. Does this make sense?_____no_output_____#### Challenge Problem 1i

For different values of `cov[0]`, plot the correlation length. Does this make sense?_____no_output_____### Problem 2: Fitting a Keplerian to Data

In the previous example, the orbit we were fitting had negligible eccentricity, so we were able to fit it with a sinusoid. In this example, we'll look at the high-eccentricity planet HD 80606b and fit a full Keplerian model to its RV data. This requires introducing some new free parameters to the model, which as we will see are not always straightforward to sample!_____no_output_____

datafile = 'https://exoplanetarchive.ipac.caltech.edu/data/ExoData/0045/0045982/data/UID_0045982_RVC_006.tbl'
data = pd.read_fwf(datafile, header=0, names=['t', 'rv', 'rv_err'], skiprows=21)
data['t'] -= data['t'][0]_____no_output_____

#### Problem 2a

Again, let's start by plotting the data. Make plots of the time series and the time series phased to a period of 111.4 days._____no_output_____

fig, ax = plt.subplots()
ax.errorbar( # complete
ax.set_xlabel('Time (days)')
ax.set_ylabel(r'RV (m s$^{-1}$)');_____no_output_____# phased plot goes here_____no_output_____

This planet's orbit should look pretty different from a sine wave!_____no_output_____#### Problem 2b

Remake the `get_model_predictions` and `lnprior` functions to fit a Keplerian.

Since this is a bit in the weeds of astronomy for the purposes of this workshop, I've gone ahead and written a solver for Kepler's equation and a `get_model_predictions` function that will deliver RVs for you. Read over the docstring and use the information given there to write a `lnprior` function for `theta`._____no_output_____

def calc_ea(ma, ecc):
    # Kepler solver - calculates eccentric anomaly
    tolerance = 1e-3
    ea = np.copy(ma)
    while True:
        diff = ea - ecc * np.sin(ea) - ma
        ea -= diff / (1. - ecc * np.cos(ea))
        if abs(diff).all() <= tolerance:
            break
    return ea

def get_model_predictions(theta, t):
    '''
    Calculate Keplerian orbital RVs

    Input
    -----
    theta : list
      A list of values for the following parameters:
       Orbital period,
       RV semi-amplitude,
       eccentricity (between 0-1),
       omega (argument of periastron; an angle in radians
              denoting the orbital phase where the planet
              passes closest to the host star)
       Tp (time of periastron; reference timestamp for the above)
       RV0 (constant RV offset)

    t : list or array
      Timestamps at which to calculate the RV

    Returns
    -------
    rvs : list or array
      Predicted RVs at the input times.
    '''
    P, K, ecc, omega, tp, rv0 = theta

    ma = 2. * np.pi / P * (t - tp)  # mean anomaly
    ea = calc_ea(ma, ecc)  # eccentric anomaly

    f = 2.0 * np.arctan2(np.sqrt(1+ecc)*np.sin(ea/2.0),
                         np.sqrt(1-ecc)*np.cos(ea/2.0)) # true anomaly
    rvs = - K * (np.cos(omega + f) + ecc*np.cos(omega))
    return rvs + rv0_____no_output_____def lnprior(theta):
    # complete_____no_output_____

#### Problem 2c

Play around with the starting parameters until you're convinced that you have a reasonable fit._____no_output_____

theta_0 = # complete

plt.errorbar(data['t'], data['rv'], data['rv_err'],
             fmt='o', ms=4)
xs = np.linspace(900, 1050, 1000)
plt.plot(xs, get_model_predictions(theta_0, xs), c='DarkOrange')
plt.xlim([900,1050])
plt.xlabel('Time (days)')
plt.ylabel(r'RV (m s$^{-1}$)');_____no_output_____

#### Problem 2d

Run the MCMC for 1000 steps and plot a trace of the eccentricity parameter. How efficiently is it running?

Optional challenge: if you wrote a Gibbs sampler yesterday, use that instead of Metropolis-Hastings here!_____no_output_____

cov = [0.1, 100, 0.01, 0.1, 0.1, 100]

pos, lnpost, acc = mh_mcmc( # complete_____no_output_____#complete
plt.ylabel('Eccentricity')
plt.xlabel('Step');_____no_output_____

#### Problem 2e

Make a corner plot of the results. Which parameters seem most correlated? Which are most and least well-constrained by the data?_____no_output_____

corner.corner( # complete_____no_output_____

#### Problem 2f

Ford et al. (2006) suggest mitigating this issue by reparameterizing the orbital parameters $e$ and $\omega$ as $e cos\omega$ and $e sin\omega$. Modify the `get_model_predictions` and `lnprior` functions accordingly and rerun the MCMC. Does performance improve?

*Note: the efficiency of a basic MCMC in this situation is never going to be excellent. We'll talk more about challenging cases like this and how to deal with them in later lectures!*_____no_output_____

def get_model_predictions(theta, t):
    # complete

def lnprior(theta):
    # complete_____no_output_____theta_0 = # complete
cov = # complete

pos, lnpost, acc = mh_mcmc( # complete_____no_output_____# complete
plt.ylabel('ecosw')
plt.xlabel('Step');_____no_output_____corner.corner( # complete_____no_output_____


# Notebook from pasqal-io/Pulser
Path: tutorials/applications/Spin chain of 3 atoms in XY mode.ipynb

# Simulation in XY mode_____no_output_____We introduce here a new mode of interaction between the atoms driven by the dipole-dipole interaction. This mode is called the XY mode. The interaction hamiltonian of a system of $N$ qubits is now given by

$$
H_{XY} = \frac{1}{2}\sum_{i<j} \frac{C_3}{R_{ij}^3}(\sigma^+_i \sigma^-_j + \sigma^-_i \sigma^+_j)
$$

where $\sigma^{±} = (\sigma^x ± i\sigma^y)/2$, $R_{ij}$ is the distance between atom $i$ and atom $j$, and $C_3$ is a coefficient dependent on the device and taken to 3070 MHz $\cdot \mu m^{-3}$ for the `MockDevice`.

The final hamiltonian of the system is

$$
H= \sum_{i=1}^N \frac{\hbar\Omega}{2} \sigma_i^x - \sum_{i=1}^N \frac{\hbar \delta}{2}  \sigma_i^z+H_{XY}.
\label{eq:XY_Hamiltonian}
$$

The Rydberg states involved are different from the ones of the Ising interaction, they are $|0\rangle = |62D_{3/2}, m_j=3/2 \rangle$ and $|1\rangle = |63P_{1/2}, m_j=1/2 \rangle$.

More details on the XY mode can be found in the following reference: [Barredo et al. 2014](https://arxiv.org/pdf/1408.1055.pdf)_____no_output_____

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

import pulser
from pulser import Pulse, Sequence, Register
from pulser.simulation import Simulation
from pulser.devices import MockDevice
from pulser.waveforms import BlackmanWaveform_____no_output_____

### Rabi oscillations of 1 atom_____no_output_____We start by showing Rabi oscillation in the XY mode in the case of a unique atom as shown in the figure 1 (c) of the [reference](https://arxiv.org/pdf/1408.1055.pdf). In a similar way as the Ising mode, the atom will oscillate between the two energy levels. The XY mode is only available in the `mw_global`channel. We initialize the register and instantiate the channel. _____no_output_____

coords = np.array([[0, 0]])
qubits = dict(enumerate(coords))

reg = Register(qubits)
seq = Sequence(reg, MockDevice)
seq.declare_channel('MW', 'mw_global')_____no_output_____

We then add a simple constant rabi pulse of amplitude $2\pi \times 4.6$ MHz and run the simulation. The measurement is necessarily done in the `XY` basis._____no_output_____

simple_pulse = Pulse.ConstantPulse(4000, 2*np.pi*4.6, 0, 0)
seq.add(simple_pulse, 'MW')
seq.measure(basis='XY')

sim = Simulation(seq, sampling_rate=0.2)

results = sim.run(progress_bar=True, nsteps=5000)10.0%. Run time:   0.02s. Est. time left: 00:00:00:00
20.0%. Run time:   0.03s. Est. time left: 00:00:00:00
30.0%. Run time:   0.04s. Est. time left: 00:00:00:00
40.0%. Run time:   0.04s. Est. time left: 00:00:00:00
50.0%. Run time:   0.05s. Est. time left: 00:00:00:00
60.0%. Run time:   0.06s. Est. time left: 00:00:00:00
70.0%. Run time:   0.06s. Est. time left: 00:00:00:00
80.0%. Run time:   0.07s. Est. time left: 00:00:00:00
90.0%. Run time:   0.07s. Est. time left: 00:00:00:00
Total run time:   0.08s

We plot the expectation of the excitation of the atom over the time._____no_output_____

def magnetization(j, total_sites):
    prod = [qutip.qeye(2) for _ in range(total_sites)]
    prod[j] = (qutip.sigmaz() + qutip.qeye(2)) / 2
    return qutip.tensor(prod)

magn = magnetization(0, 1)
plt.figure(figsize=[16, 6])
results.plot(magn)
plt.xlabel('Pulse duration (ns)', fontsize='x-large')
plt.ylabel('Excitation of the atom', fontsize='x-large')
plt.show()_____no_output_____

### Spin chain of 3 atoms_____no_output_____We now simulate the free evolution of a spin chain of 3 atoms, starting with 1 excitation in the initial state $|100\rangle$ as shown in the figure 3 (c) of the [reference](https://arxiv.org/pdf/1408.1055.pdf). _____no_output_____

coords = np.array([[-8., 0], [0, 0], [8., 0]])
qubits = dict(enumerate(coords))

reg = Register(qubits)
seq = Sequence(reg, MockDevice)
seq.declare_channel('ch0', 'mw_global')
reg.draw()

# State preparation using SLM mask
masked_qubits = [1, 2]
seq.config_slm_mask(masked_qubits)
masked_pulse = Pulse.ConstantDetuning(BlackmanWaveform(200, np.pi), 0, 0)
seq.add(masked_pulse, 'ch0')

# Simulation pulse
simple_pulse = Pulse.ConstantPulse(7000, 0, 0, 0)
seq.add(simple_pulse, 'ch0')
seq.measure(basis='XY')

sim = Simulation(seq, sampling_rate=1)
results = sim.run(nsteps=5000)_____no_output_____magn_list = [magnetization(j, 3) for j in range(3)]

expectations = results.expect(magn_list)

plt.figure(figsize=[16, 18])
plt.subplot(311)
plt.plot(expectations[0])
plt.ylabel('Excitation of atom 0', fontsize='x-large')
plt.xlabel('Time (ns)', fontsize='x-large')
plt.subplot(312)
plt.plot(expectations[1])
plt.ylabel('Excitation of atom 1', fontsize='x-large')
plt.xlabel('Time (ns)', fontsize='x-large')
plt.ylim([0, 1])
plt.subplot(313)
plt.plot(expectations[2])
plt.ylabel('Excitation of atom 2', fontsize='x-large')
plt.xlabel('Time (ns)', fontsize='x-large')
plt.show()_____no_output_____

## External field and angular dependency_____no_output_____An external magnetic field can be added to the experiment, and will modify the hamiltonian. The XY Hamiltonian is then

$$
H_{XY} = \frac{1}{2}\sum_{i<j} \frac{C_3\:[1 - 3\:\text{cos}^2(\theta_{ij})]}{R_{ij}^3}(\sigma^+_i \sigma^-_j + \sigma^-_i \sigma^+_j)
$$
where $\theta_{ij}$ is the angle between the vector of the two atoms and the external field as shown on the figure below.

We add an external field along the Y axis, and we put the qubit 2 at the angle such that $\text{cos}^2(\theta_{12}) = 1/3$, and the interaction between the qubits 1 and 2 cancels out. This is done by the method `set_magnetic_field`from the `Sequence`.

This is the principle that enables to create [topological phases](https://science.sciencemag.org/content/365/6455/775) on long chain of atoms._____no_output_____

coords = np.array([[-1., 0], [0, 0], [np.sqrt(2/3), np.sqrt(1/3)]]) * 8.
qubits = dict(enumerate(coords))

reg = Register(qubits)
seq = Sequence(reg, MockDevice)
seq.declare_channel('ch0', 'mw_global')
seq.set_magnetic_field(0., 1., 0)
reg.draw()_____no_output_____

We then simulate again the free evolution from the initial state $|100\rangle$. _____no_output_____

# State preparation using SLM mask
masked_qubits = [1, 2]
seq.config_slm_mask(masked_qubits)
masked_pulse = Pulse.ConstantDetuning(BlackmanWaveform(200, np.pi), 0, 0)
seq.add(masked_pulse, 'ch0')

# Simulation pulse
simple_pulse = Pulse.ConstantPulse(7000, 0, 0, 0)
seq.add(simple_pulse, 'ch0')
seq.measure(basis='XY')

sim = Simulation(seq, sampling_rate=1)
results = sim.run(progress_bar=True, nsteps=5000)10.0%. Run time:   0.04s. Est. time left: 00:00:00:00
20.0%. Run time:   0.08s. Est. time left: 00:00:00:00
30.0%. Run time:   0.12s. Est. time left: 00:00:00:00
40.0%. Run time:   0.16s. Est. time left: 00:00:00:00
50.0%. Run time:   0.20s. Est. time left: 00:00:00:00
60.0%. Run time:   0.24s. Est. time left: 00:00:00:00
70.0%. Run time:   0.28s. Est. time left: 00:00:00:00
80.0%. Run time:   0.31s. Est. time left: 00:00:00:00
90.0%. Run time:   0.35s. Est. time left: 00:00:00:00
Total run time:   0.39s
magn_list = [magnetization(j, 3) for j in range(3)]

expectations = results.expect(magn_list)

plt.figure(figsize=[16, 18])
plt.subplot(311)
plt.plot(expectations[0])
plt.ylabel('Excitation of atom 0', fontsize='x-large')
plt.xlabel('Time ($\mu$s)', fontsize='x-large')
plt.ylim([0, 1])
plt.subplot(312)
plt.plot(expectations[1])
plt.ylabel('Excitation of atom 1', fontsize='x-large')
plt.xlabel('Time ($\mu$s)', fontsize='x-large')
plt.ylim([0, 1])
plt.subplot(313)
plt.plot(expectations[2])
plt.ylabel('Excitation of atom 2', fontsize='x-large')
plt.xlabel('Time ($\mu$s)', fontsize='x-large')
plt.ylim([0, 1])_____no_output_____

We can see there that there is almost no excitation in the qubit 2. It still remains some because the interaction between the qubits 0 and 2 is not completely negligible._____no_output_____


# Notebook from mm5110/PIC16A
Path: homework/HW3.ipynb

# HW3

### PageRank

What is the most important website on the internet? Who is the "key player" on a sports team? Which countries are the most central players in the world economy? There is no one correct answer to any of these questions, but there is a most profitable one. [PageRank](https://en.wikipedia.org/wiki/PageRank) is an algorithm for ranking individual elements of complex systems, invited by Sergey Brin and Larry Page. It was the first and most famous algorithm used by the Google Search engine, and it is fair to say that the internet as we know it today would not exist without PageRank.

In this assignment, we will implement PageRank. There are many good ways to implement this algorithm, but in this assignment we will use our newfound skills with object-oriented programming and iterators.

### How it works

For the purposes of this example, let's assume that we are talking about webpages. PageRank works by allowing a "random surfer" to move around webpages by following links. Each time the surfer lands on a page, it then looks for all the links on that page. It then picks one at random and follows it, thereby arriving at the next page, where the process repeats. Eventually, the surfer will visit all the pages one or more times. Pages that the surfer visits more frequently have higher *PageRank scores*. Because the surfer moves between linked pages, PageRank expresses an intuitive idea: **important pages are linked to other important pages.** [This diagram](https://en.wikipedia.org/wiki/PageRank#/media/File:PageRanks-Example.jpg) from Wikipedia gives a nice illustration. Note that more important webpages (higher PageRank) tend to be connected to other important webpages.

A schematic for PageRank.

### Data

You can complete this assignment using data from one of two sources.

#### Option 1: Hamilton

This data set comes from the hit Broadway musical "Hamilton."

The Hamilton data set

The good folks at [The Hamilton Project](http://hamilton.newtfire.org/) analyzed the script for us, obtaining data on **who talks about whom** in each of the show's songs. When character A mentions character B, we'll think of this as a *link* from A to B, suggesting that B might be important.

If you use this data set, listening to the soundtrack while working is strongly recommended.

#### Option 2: Global Airline Network

The global airline network

Back in the Before Times, lots of people flew on airplanes. This data set includes a "link" from Airport A to Airport B whenever there is a flight from B to A. This data set was collected by the [OpenFlights Project](https://openflights.org/data.html).

## (A). Define Functions

In this part, all you have to do is hit `shift + enter` on the code block supplied below. This block defines two functions. The first one retrieves the data from the internet and saves it to your local computer, while the second reads in the data, producing a list of tuples. It's not important for you to be familiar with the code in these functions right now -- we'll discuss them soon. _____no_output_____

import urllib
import csv

def retrieve_data(url):
    """
    Retrieve a file from the specified url and save it in a local file
    called data.csv. The intended values of url are:

    1. https://philchodrow.github.io/PIC16A/homework/HW3-hamilton-data.csv
    2. https://philchodrow.github.io/PIC16A/homework/HW3-flights-data.csv
    """

    # grab the data and parse it
    filedata = urllib.request.urlopen(url)
    to_write = filedata.read()

    # write to file
    with open("data.csv", "wb") as f:
        f.write(to_write)

def read_data(path):
    """
    read downloaded data from a .csv file, and return a list of tuples.
    each tuple represents a link between states.
    """
    with open(path, "r") as f:
        reader = csv.reader(f)
        return [(row[0], row[1]) for row in list(reader)]_____no_output_____

## (B). Grab the Data

The data live at the following URLs:

- **Hamilton**:  `https://philchodrow.github.io/PIC16A/homework/HW3-hamilton-data.csv`
- **Airline**:   `https://philchodrow.github.io/PIC16A/homework/HW3-flights-data.csv`

In each data set, each row corresponds to a "link" between objects. In Hamilton, the pairs have format `mentioner, mentioned` while in the airline network the rows have format `destination, origin.`

Pick one of these data sets, and set the variable `url` appropriately by uncommenting one of the two lines below. Then, call `retrieve_data()` and `read_data()`. The `path` argument for `read_data()` should be set to `"data.csv"`.  Create a variable called `data` to hold the return value of `read_data()`.

#### Your solution_____no_output_____

# uncomment the second line if you'd prefer to
# work with the flights data.
url = "https://philchodrow.github.io/PIC16A/homework/HW3-hamilton-data.csv"
# url = "https://philchodrow.github.io/PIC16A/homework/HW3-flights-data.csv"

# Call your functions below
_____no_output_____

## (C). Examine the structure of the data

This would also be a good time to inspect the data to make sure you understand how it is structured. Write a function `describe(n)` that describes the meaning of the `n`th row of the data set you chose. In the Hamilton data set, your function should do the following:

```python
describe(5)

# output
"Element 5 of the Hamilton data set is ('burr', 'betsy'). This means that Burr mentions Betsy in a song."
```

In context of the airline flights data, your function should instead do this:

```python
describe(5)

# output
"Element 5 of the flights data set is ('SIN', 'BKK'). This means that there is a flight from BKK to SIN."
```

Please attend to capitalization and formatting. While the standard string concatenation operator `+` is completely fine for this task, the fancy `str.format()` function may make your code somewhat simpler. [This page](https://realpython.com/python-formatted-output/) has some useful examples in case you'd like to try this.

#### Your Solution_____no_output_____## (D). Data to Dictionary

Write a function called `data_to_dictionary` that converts the data into a dictionary such that:

1. There is a single key for each character (in Hamilton) or airport (in flights).
2. The value corresponding to each key is a list of the characters/airports to which that key links. The list should contain repeats if there are multiple links.

Here's an example of the desired behavior on a fake data set.

```python
data = [("a", "b"),
        ("a", "b"),
        ("a", "c"),
        ("b", "c"),
        ("b", "a")]

data_to_dictionary(data)

# output
{"a" : ["b", "b", "c"], "b" : ["a", "c"]}

```

#### Your Solution_____no_output_____## (E). Define a PR_DiGraph class

A **directed graph**, or DiGraph, is just a set of arrows ("edges") between objects ("nodes"). It is a natural way to represent data that represents one-way relationships, such as links from one webpage to another or mentions of one character by another. We already saw a directed graph above when we introduced the idea of PageRank. Here's a paired-down example.

Example of a directed graph.

Implement a `PR_DiGraph` class with a custom `__init__()` method and a `linked_by()` method. The `__init__()` method should accept two arguments: `data` and `iteration_limit`. The `__init__()` method should then construct an instance variable `self.link_dict` which is simply the output of `data_to_dictionary` applied to the argument `data`. `__init__()` should also construct an instance variable `self.iteration_limit`, which simply takes the value of `iteration_limit` supplied to `__init__()`. Don't worry about that one for now.

Then, define a method `self.linked_by(x)` which, when called, returns the value `self.link_dict[x]`.

Finally, add an `__iter__` method, which returns an object of class `PR_Iterator`. We will define this class in the next part.

Example session (using Hamilton):

```python
D = PR_DiGraph(data, iteration_limit = 10000)
D.linked_by('peggy')

# output
['peggy', 'schuylerSis']
```

#### Your Solution_____no_output_____## (F). Implement PR_Iterator

Define a `PR_Iterator` class with a custom `__next__()` method.

The `__init__` method of this class should create instance variables to store the `PR_DiGraph` object from which it was constructed; a counter `i`, starting at 0, to log the number of steps taken, and a `current_state` variable whose value is one of the keys of the `link_dict` of the `Pr_DiGraph`. You can choose its initial value arbitrarily; in my solution code I chose `self.current_state = "hamilton"`.

We are going to use iteration to implement the PageRank algorithm. This  means we are going to imagine a surfer who is following the links in our data set. **Implement the following two methods:**

1. `follow_link()`.
    1. Pick a random new character mentioned by the current character, or new airport served by the current airport. Let's call this `next_state`.
    2. If `next_state != current_state`, set `current_state` to `next_state`.
    3. Otherwise (if `next_state == current_state`), teleport (see below).
    4. You might run into `KeyError`s, in which case you should again teleport (use a `try-except` block).
2. `teleport()`.
    1. Set the current state to a new state (key of the link dict) completely at random.

*Hint*: use `random.choice` from the `random` module to choose elements of lists.

Finally, **implement** `__next__()`. `__next__()` should do `follow_link()` with 85% probability, and do `teleport()` with 15% probability. You should also define a custom `StopIteration` condition to ensure that only as many steps are taken as the `iteration_limit` supplied to the `PR_DiGraph` initializer.

1. To do something with 85% probability, use the following:

```
if random.random() < 0.85:
    # do the thing
else:
    # do the other thing
```

#### Example Usage

After you define your class, run the following code and show that it works. Note: your precise sequence may be different from mine.

```python
D = PR_DiGraph(data, iteration_limit = 5)
for char in D:
    print(char)

following link : current state = burr
following link : current state = washington
following link : current state = burr
following link : current state = hamilton
teleporting    : current state = washington
```

I have added printed messages here for you to more clearly see what should be happening, but it is not necessary for you to do this. It is sufficient for your output to look like:

```python
D = PR_DiGraph(data, iteration_limit = 5)
for char in D:
    print(char)

burr
washington
burr
hamilton
washington
```

#### Your Solution_____no_output_____

import random

_____no_output_____# run the below
D = PR_DiGraph(data, iteration_limit = 5)
for char in D:
    print(char)_____no_output_____

## (G). Compute PageRank

Finally, we are ready to compute the PageRank in our data set. Initialize a `PR_DiGraph` with a large iteration limit (say, 1,000,000). Use a `for`-loop to allow your surfer to randomly move through the data set. The number of times that the surfer visits state `x` is the PageRank score of `x`.

Create a `dict` which logs how many times a given state appears when iterating through the `PR_Digraph`. So, this dictionary holds the PageRank score of each state.

#### Your Solution_____no_output_____## (H).  Display Your Result

Use your favorite approach to show the results in sorted format, descending by PageRank score. The entries at the top should be the entries with highest PageRank. What are the most important elements in the data set?

You may show either the complete list or just the top 10.

Check your code by comparing your top 10 to mine. Because we are using a randomized algorithm, your results will not agree exactly with mine, but they should be relatively close. If your top 10 list is very different, then you might want to revisit your previous solutions.

For Hamilton, my top 10 were:

```
[('hamilton', 166062),
 ('burr', 99180),
 ('washington', 92246),
 ('jefferson', 72450),
 ('eliza', 51485),
 ('angelica', 48042),
 ('madison', 37421),
 ('lafayette', 34297),
 ('lee', 33678),
 ('jAdams', 31121)]
```

For the flights data, my top 10 were:

```
[('LHR', 18043), # London Heathrow
 ('ATL', 16370), # Atlanta
 ('JFK', 14795), # New York JFK
 ('FRA', 14156), # Frankfurt
 ('CDG', 14073), # Charles de Gaulle (Paris)
 ('LAX', 13199), # Los Angeles
 ('ORD', 12915), # Chicago O'Hare
 ('PEK', 12525), # Beijing
 ('AMS', 12410), # Amsterdam Schiphol
 ('PVG', 11517)] # Shanghai
```
#### Your solution_____no_output_____## (I). Submit!

Check that your code is appropriately documented (comments and docstrings), and turn it in. _____no_output_____


# Notebook from mauicv/alibi
Path: doc/source/examples/cf_mnist.ipynb

# Counterfactual instances on MNIST_____no_output_____Given a test instance $X$, this method can generate counterfactual instances $X^\prime$ given a desired counterfactual class $t$ which can either be a class specified upfront or any other class that is different from the predicted class of $X$.

The loss function for finding counterfactuals is the following:

$$L(X^\prime\vert X) = (f_t(X^\prime) - p_t)^2 + \lambda L_1(X^\prime, X).$$

The first loss term, guides the search towards instances $X^\prime$ for which the predicted class probability $f_t(X^\prime)$ is close to a pre-specified target class probability $p_t$ (typically $p_t=1$). The second loss term ensures that the counterfactuals are close in the feature space to the original test instance.

In this notebook we illustrate the usage of the basic counterfactual algorithm on the MNIST dataset._____no_output_____

Note

To enable support for Counterfactual, you may need to run

```bash
pip install alibi[tensorflow]
```

_____no_output_____

import tensorflow as tf
tf.get_logger().setLevel(40) # suppress deprecation messages
tf.compat.v1.disable_v2_behavior() # disable TF2 behaviour as alibi code still relies on TF1 constructs
from tensorflow.keras.layers import Conv2D, Dense, Dropout, Flatten, MaxPooling2D, Input
from tensorflow.keras.models import Model, load_model
from tensorflow.keras.utils import to_categorical
import matplotlib
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import os
from time import time
from alibi.explainers import Counterfactual
print('TF version: ', tf.__version__)
print('Eager execution enabled: ', tf.executing_eagerly()) # FalseTF version:  2.2.0
Eager execution enabled:  False

## Load and prepare MNIST data_____no_output_____

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
print('x_train shape:', x_train.shape, 'y_train shape:', y_train.shape)
plt.gray()
plt.imshow(x_test[1]);x_train shape: (60000, 28, 28) y_train shape: (60000,)

Prepare data: scale, reshape and categorize_____no_output_____

x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255
x_train = np.reshape(x_train, x_train.shape + (1,))
x_test = np.reshape(x_test, x_test.shape + (1,))
print('x_train shape:', x_train.shape, 'x_test shape:', x_test.shape)
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
print('y_train shape:', y_train.shape, 'y_test shape:', y_test.shape)x_train shape: (60000, 28, 28, 1) x_test shape: (10000, 28, 28, 1)
y_train shape: (60000, 10) y_test shape: (10000, 10)
xmin, xmax = -.5, .5
x_train = ((x_train - x_train.min()) / (x_train.max() - x_train.min())) * (xmax - xmin) + xmin
x_test = ((x_test - x_test.min()) / (x_test.max() - x_test.min())) * (xmax - xmin) + xmin_____no_output_____

## Define and train CNN model_____no_output_____

def cnn_model():
    x_in = Input(shape=(28, 28, 1))
    x = Conv2D(filters=64, kernel_size=2, padding='same', activation='relu')(x_in)
    x = MaxPooling2D(pool_size=2)(x)
    x = Dropout(0.3)(x)

    x = Conv2D(filters=32, kernel_size=2, padding='same', activation='relu')(x)
    x = MaxPooling2D(pool_size=2)(x)
    x = Dropout(0.3)(x)

    x = Flatten()(x)
    x = Dense(256, activation='relu')(x)
    x = Dropout(0.5)(x)
    x_out = Dense(10, activation='softmax')(x)

    cnn = Model(inputs=x_in, outputs=x_out)
    cnn.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

    return cnn_____no_output_____cnn = cnn_model()
cnn.summary()
cnn.fit(x_train, y_train, batch_size=64, epochs=3, verbose=0)
cnn.save('mnist_cnn.h5')Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_1 (InputLayer)         [(None, 28, 28, 1)]       0
_________________________________________________________________
conv2d (Conv2D)              (None, 28, 28, 64)        320
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 14, 14, 64)        0
_________________________________________________________________
dropout (Dropout)            (None, 14, 14, 64)        0
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 14, 14, 32)        8224
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 7, 7, 32)          0
_________________________________________________________________
dropout_1 (Dropout)          (None, 7, 7, 32)          0
_________________________________________________________________
flatten (Flatten)            (None, 1568)              0
_________________________________________________________________
dense (Dense)                (None, 256)               401664
_________________________________________________________________
dropout_2 (Dropout)          (None, 256)               0
_________________________________________________________________
dense_1 (Dense)              (None, 10)                2570
=================================================================
Total params: 412,778
Trainable params: 412,778
Non-trainable params: 0
_________________________________________________________________

Evaluate the model on test set_____no_output_____

cnn = load_model('mnist_cnn.h5')
score = cnn.evaluate(x_test, y_test, verbose=0)
print('Test accuracy: ', score[1])Test accuracy:  0.9871

## Generate counterfactuals_____no_output_____Original instance:_____no_output_____

X = x_test[0].reshape((1,) + x_test[0].shape)
plt.imshow(X.reshape(28, 28));_____no_output_____

Counterfactual parameters:_____no_output_____

shape = (1,) + x_train.shape[1:]
target_proba = 1.0
tol = 0.01 # want counterfactuals with p(class)>0.99
target_class = 'other' # any class other than 7 will do
max_iter = 1000
lam_init = 1e-1
max_lam_steps = 10
learning_rate_init = 0.1
feature_range = (x_train.min(),x_train.max())_____no_output_____

Run counterfactual:_____no_output_____

# initialize explainer
cf = Counterfactual(cnn, shape=shape, target_proba=target_proba, tol=tol,
                    target_class=target_class, max_iter=max_iter, lam_init=lam_init,
                    max_lam_steps=max_lam_steps, learning_rate_init=learning_rate_init,
                    feature_range=feature_range)

start_time = time()
explanation = cf.explain(X)
print('Explanation took {:.3f} sec'.format(time() - start_time))Explanation took 8.407 sec

Results:_____no_output_____

pred_class = explanation.cf['class']
proba = explanation.cf['proba'][0][pred_class]

print(f'Counterfactual prediction: {pred_class} with probability {proba}')
plt.imshow(explanation.cf['X'].reshape(28, 28));Counterfactual prediction: 9 with probability 0.9924006462097168

The counterfactual starting from a 7 moves towards the closest class as determined by the model and the data - in this case a 9. The evolution of the counterfactual during the iterations over $\lambda$ can be seen below (note that all of the following examples satisfy the counterfactual condition):_____no_output_____

n_cfs = np.array([len(explanation.all[iter_cf]) for iter_cf in range(max_lam_steps)])
examples = {}
for ix, n in enumerate(n_cfs):
    if n>0:
        examples[ix] = {'ix': ix, 'lambda': explanation.all[ix][0]['lambda'],
                       'X': explanation.all[ix][0]['X']}
columns = len(examples) + 1
rows = 1

fig = plt.figure(figsize=(16,6))

for i, key in enumerate(examples.keys()):
    ax = plt.subplot(rows, columns, i+1)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)
    plt.imshow(examples[key]['X'].reshape(28,28))
    plt.title(f'Iteration: {key}')_____no_output_____

Typically, the first few iterations find counterfactuals that are out of distribution, while the later iterations make the counterfactual more sparse and interpretable._____no_output_____Let's now try to steer the counterfactual to a specific class:_____no_output_____

target_class = 1

cf = Counterfactual(cnn, shape=shape, target_proba=target_proba, tol=tol,
                    target_class=target_class, max_iter=max_iter, lam_init=lam_init,
                    max_lam_steps=max_lam_steps, learning_rate_init=learning_rate_init,
                    feature_range=feature_range)

explanation = start_time = time()
explanation = cf.explain(X)
print('Explanation took {:.3f} sec'.format(time() - start_time))Explanation took 6.249 sec

Results:_____no_output_____

pred_class = explanation.cf['class']
proba = explanation.cf['proba'][0][pred_class]

print(f'Counterfactual prediction: {pred_class} with probability {proba}')
plt.imshow(explanation.cf['X'].reshape(28, 28));Counterfactual prediction: 1 with probability 0.9999160766601562

As you can see, by specifying a class, the search process can't go towards the closest class to the test instance (in this case a 9 as we saw previously), so the resulting counterfactual might be less interpretable. We can gain more insight by looking at the difference between the counterfactual and the original instance:_____no_output_____

plt.imshow((explanation.cf['X'] - X).reshape(28, 28));_____no_output_____

This shows that the counterfactual is stripping out the top part of the 7 to make to result in a prediction of 1 - not very surprising as the dataset has a lot of examples of diagonally slanted 1’s._____no_output_____Clean up:_____no_output_____

os.remove('mnist_cnn.h5')_____no_output_____


# Notebook from dongxulee/lifeCycle
Path: 20200907/.ipynb_checkpoints/renting-2housingOptions-checkpoint.ipynb

%pylab inline
from scipy.interpolate import interpn
from helpFunctions import surfacePlot
import numpy as np
from multiprocessing import Pool
from functools import partial
import warnings
import math
warnings.filterwarnings("ignore")
np.printoptions(precision=2)Populating the interactive namespace from numpy and matplotlib

### The value of renting
Assuming we obtain the value: $\tilde{V}_{t+1}(x_{t+1})$ where:
$x_{t+1} = [w_{t+1}, n_{t+1}, M_{t+1}, g_{t+1} = 0, e_{t+1}, s_{t+1}, (H)]$  from interpolation. We know $H$ and $M_t$ from the action taken and we could calculate mortgage payment $m$ and $rh$ (now treated as constant) is observed from the market. _____no_output_____* To start with we have state variable: $x_t = [w_t, n_t, e_t, s_t]$
* Housing choice is limited: $H_{\text{choice}} = \{1000, 2000\}$
* Mortgage choice is also limitted to discrete values $M_{t} = [0.2H, 0.5H, 0.8H]$
* Action: continue to rent: $a = (c, b, k, h)$ switch to owning a house: $a = (c, b, k, M, H)$
* Buying house activities can only happend during the age of 10 and 25._____no_output_____

# time line
T_min = 0
T_max = 70
T_R = 45
# discounting factor
beta = 1/(1+0.02)
# utility function parameter
gamma = 2
# relative importance of housing consumption and non durable consumption
alpha = 0.8
# parameter used to calculate the housing consumption
kappa = 0.3
# depreciation parameter
delta = 0.025
# housing parameter
chi = 0.3
# uB associated parameter
B = 2
# constant cost
c_h = 0.5
# All the money amount are denoted in thousand dollars
earningShock = [0.8,1.2]
# Define transition matrix of economical states
# GOOD -> GOOD 0.8, BAD -> BAD 0.6
Ps = np.array([[0.6, 0.4],[0.2, 0.8]])
# current risk free interest rate
r_b = np.array([0.03 ,0.05])
# stock return depends on current and future econ states
# r_k = np.array([[-0.2, 0.15],[-0.15, 0.2]])
r_k = np.array([[-0.15, 0.20],[-0.15, 0.20]])
# expected return on stock market
# r_bar = 0.0667
r_bar = 0.02
# probability of survival
Pa = np.load("prob.npy")
# deterministic income
detEarning = np.load("detEarning.npy")
# probability of employment transition Pe[s, s_next, e, e_next]
Pe = np.array([[[[0.3, 0.7], [0.1, 0.9]], [[0.25, 0.75], [0.05, 0.95]]],
               [[[0.25, 0.75], [0.05, 0.95]], [[0.2, 0.8], [0.01, 0.99]]]])
# tax rate before and after retirement
tau_L = 0.2
tau_R = 0.1

# some variables associate with 401k amount
Nt = [np.sum(Pa[t:]) for t in range(T_max-T_min)]
Dt = [np.ceil(((1+r_bar)**N - 1)/(r_bar*(1+r_bar)**N)) for N in Nt]
# income fraction goes into 401k
yi = 0.005
# mortgate rate
rh = 0.036
# this discount is used to calculate mortgage payment m
D = [((1+rh)**N - 1)/(rh*(1+rh)**N) for N in range(T_max-T_min)]
# housing price constant
pt = 2*250/1000
# renting price constant
pr = 2*30/1000_____no_output_____#Define the utility function
def u(c):
    # shift utility function to the left, so it only takes positive value
    return (np.float_power(c, 1-gamma) - 1)/(1 - gamma)

#Define the bequeath function, which is a function of wealth
def uB(tb):
    return B*u(tb)

#Calculate TB_rent
def calTB_rent(x):
    # change input x as numpy array
    # w, n, e, s = x
    TB = x[:,0] + x[:,1]
    return TB

#Calculate TB_own
def calTB_own(x):
    # change input x as numpy array
    # transiton from (w, n, e, s) -> (w, n, M, 0, e, s, H)
    TB = x[:,0] + x[:,1] + x[:,6]*pt - x[:,2]
    return TB

def u_rent(a):
    '''
    Input:
        action a: c, b, k, h = a
    Output:
        reward value: the length of return should be equal to the length of a
    '''
    c = a[:,0]
    h = a[:,3]
    C = np.float_power(c, alpha) * np.float_power(h, 1-alpha)
    return u(C)

def u_own(a):
    '''
    Input:
        action a: c, b, k, M, H = a
    Output:
        reward value: the length of return should be equal to the length of a
    '''
    c = a[:,0]
    H = a[:,4]
    C = np.float_power(c, alpha) * np.float_power((1+kappa)*H, 1-alpha)
    return u(C)

#Define the earning function, which applies for both employment and unemployment, good econ state and bad econ state
def y(t, x):
    w, n, e, s = x
    if t <= T_R:
        welfare = 5
        return detEarning[t] * earningShock[int(s)] * e + (1-e) * welfare
    else:
        return detEarning[t]

#Earning after tax and fixed by transaction in and out from 401k account
def yAT(t,x):
    yt = y(t, x)
    w, n, e, s = x
    if t <= T_R and e == 1:
        # 5% of the income will be put into the 401k
        return (1-tau_L)*(yt * (1-yi))
    if t <= T_R and e == 0:
        return yt
    else:
        # t > T_R, n/discounting amount will be withdraw from the 401k
        return (1-tau_R)*yt + n/Dt[t]

#Define the evolution of the amount in 401k account
def gn(t, n, x, s_next):
    w, n, e, s = x
    if t <= T_R and e == 1:
        # if the person is employed, then 5 percent of his income goes into 401k
        n_cur = n + y(t, x) * yi
    elif t <= T_R and e == 0:
        # if the perons is unemployed, then n does not change
        n_cur = n
    else:
        # t > T_R, n/discounting amount will be withdraw from the 401k
        n_cur = n - n/Dt[t]
    return (1+r_k[int(s), s_next])*n_cur _____no_output_____def transition_to_rent(x,a,t):
    '''
        imput a is np array constains all possible actions
        from x = [w, n, e, s] to x = [w, n, e, s]
    '''
    w, n, e, s = x
    # variables used to collect possible states and probabilities
    x_next = []
    prob_next = []
    for aa in a:
        c, b, k, h = aa
        for s_next in [0,1]:
                w_next =  b*(1+r_b[int(s)]) + k*(1+r_k[int(s), s_next])
                n_next = gn(t, n, x, s_next)
                if t >= T_R:
                    e_next = 0
                    x_next.append([w_next, n_next, e_next, s_next])
                    prob_next.append(Ps[int(s),s_next])
                else:
                    for e_next in [0,1]:
                        x_next.append([w_next, n_next, e_next, s_next])
                        prob_next.append(Ps[int(s),s_next] * Pe[int(s),s_next,int(e),e_next])
    return np.array(x_next), np.array(prob_next)

def transition_to_own(x,a,t):
    '''
        imput a is np array constains all possible actions
        from x = [w, n, e, s] to x = [w, n, M, g=0, e, s, H]
    '''
    w, n, e, s = x
    # variables used to collect possible states and probabilities
    x_next = []
    prob_next = []
    for aa in a:
        c, b, k, M, H = aa
        M_next = M*(1+rh)
        for s_next in [0,1]:
                w_next =  b*(1+r_b[int(s)]) + k*(1+r_k[int(s), s_next])
                n_next = gn(t, n, x, s_next)
                if t >= T_R:
                    e_next = 0
                    x_next.append([w_next, n_next, M_next, 0, e_next, s_next, H])
                    prob_next.append(Ps[int(s),s_next])
                else:
                    for e_next in [0,1]:
                        x_next.append([w_next, n_next, M_next, 0, e_next, s_next, H])
                        prob_next.append(Ps[int(s),s_next] * Pe[int(s),s_next,int(e),e_next])
    return np.array(x_next), np.array(prob_next)_____no_output_____# used to calculate dot product
def dotProduct(p_next, uBTB, t):
    if t >= T_R:
        return (p_next*uBTB).reshape((len(p_next)//2,2)).sum(axis = 1)
    else:
        return (p_next*uBTB).reshape((len(p_next)//4,4)).sum(axis = 1)

# Value function is a function of state and time, according to the restriction transfer from renting to ownning can only happen
# between the age: 10 - 25
def V(x, t, NN):
    w, n, e, s = x
    yat = yAT(t,x)

    # first define the objective function solver and then the objective function
    def obj_solver_rent(obj_rent):
    # a = [c, b, k, h]
    # Constrain: yat + w = c + b + k + pr*h
    # h_portion takes [0:0.05:0.95]
    # c_portion takes remaining [0:0.05:0.95]
    # b_portion takes reamining [0:0.05:0.95]
    # k is the remainder
        actions = []
        for hp in np.linspace(0,0.99,20):
            budget1 = yat + w
            h = budget1 * hp/pr
            budget2 = budget1 * (1-hp)
            for cp in np.linspace(0,1,11):
                c = budget2*cp
                budget3 = budget2 * (1-cp)
                for bp in np.linspace(0,1,11):
                    b = budget3* bp
                    k = budget3 * (1-bp)
                    # q = 1 not renting in this case
                    actions.append([c,b,k,h])

        actions = np.array(actions)
        values = obj_rent(actions)
        fun = np.max(values)
        ma = actions[np.argmax(values)]
        return fun, ma

    def obj_solver_own(obj_own):
    # a = [c, b, k, M, H]
    # possible value of H = {750, 1000, 1500, 2000} possible value of [0.2H, 0.4H, 0.6H, 0.8H]]*pt
    # (M, t, rh) --> m
    # Constrain: yat + w = c + b + k + (H*pt - M) + ch
        actions = []
        for H in H_options:
            for mp in M_options:
                M = mp*H*pt
                m = M/D[T_max - t - 1]
                # 5 is the welfare income which is also the minimum income
                if (H*pt - M) + c_h <= yat + w and m < pr*H + 5:
                    budget1 = yat + w - (H*pt - M) - c_h
                    # c_portion takes remaining [0:0.05:0.95]
                    # b_portion takes reamining [0:0.05:0.95]
                    # k is the remainder
                    for cp in np.linspace(0,1,11):
                        c = budget1*cp
                        budget2 = budget1 * (1-cp)
                        for bp in np.linspace(0,1,11):
                            b = budget2* bp
                            k = budget2 * (1-bp)
                            actions.append([c,b,k,M,H])
        if len(actions) == 0:
            return -np.inf, [0,0,0,0,0]
        else:
            actions = np.array(actions)
            values = obj_own(actions)
            fun = np.max(values)
            ma = actions[np.argmax(values)]
            return fun, ma

    if t == T_max-1:
        # The objective function of renting
        def obj_rent(actions):
            # a = [c, b, k, h]
            x_next, p_next  = transition_to_rent(x, actions, t)
            uBTB = uB(calTB_rent(x_next))
            return u_rent(actions) + beta * dotProduct(uBTB, p_next, t)

        fun, action = obj_solver_rent(obj_rent)
        return np.array([fun, action])

    elif t < 10 or t > 25:
        # The objective function of renting
        def obj_rent(actions):
            # a = [c, b, k, h]
            x_next, p_next  = transition_to_rent(x, actions, t)
            V_tilda = NN.predict(x_next) # V_rent_{t+1} used to approximate, shape of x is [w,n,e,s]
            uBTB = uB(calTB_rent(x_next))
            return u_rent(actions) + beta * (Pa[t] * dotProduct(V_tilda, p_next, t) + (1 - Pa[t]) * dotProduct(uBTB, p_next, t))

        fun, action = obj_solver_rent(obj_rent)
        return np.array([fun, action])

    else:
        # The objective function of renting
        def obj_rent(actions):
            # a = [c, b, k, h]
            x_next, p_next  = transition_to_rent(x, actions, t)
            V_tilda = NN.predict(x_next) # V_rent_{t+1} used to approximate, shape of x is [w,n,e,s]
            uBTB = uB(calTB_rent(x_next))
            return u_rent(actions) + beta * (Pa[t] * dotProduct(V_tilda, p_next, t) + (1 - Pa[t]) * dotProduct(uBTB, p_next, t))
        # The objective function of owning
        def obj_own(actions):
            # a = [c, b, k, M, H]
            x_next, p_next  = transition_to_own(x, actions, t)
            V_tilda = NN.predict(x_next) # V_own_{t+1} used to approximate, shape of x is [w,n,0,e,s,H,M]
            uBTB = uB(calTB_own(x_next))
            return u_own(actions) + beta * (Pa[t] * dotProduct(V_tilda, p_next, t) + (1 - Pa[t]) * dotProduct(uBTB, p_next, t))

        fun1, action1 = obj_solver_rent(obj_rent)
        fun2, action2 = obj_solver_own(obj_own)
        if fun1 > fun2:
            return np.array([fun1, action1])
        else:
            return np.array([fun2, action2])_____no_output_____# wealth discretization
ws = np.array([10,25,50,75,100,125,150,175,200,250,500,750,1000,1500,3000])
w_grid_size = len(ws)
# 401k amount discretization
ns = np.array([1, 5, 10, 15, 25, 40, 65, 100, 150, 300, 400,1000])
n_grid_size = len(ns)

xgrid = np.array([[w, n, e, s]
                            for w in ws
                            for n in ns
                            for e in [0,1]
                            for s in [0,1]
                            ]).reshape((w_grid_size, n_grid_size,2,2,4))

Vgrid = np.zeros((w_grid_size, n_grid_size,2,2, T_max-T_min))
cgrid = np.zeros((w_grid_size, n_grid_size,2,2, T_max-T_min))
bgrid = np.zeros((w_grid_size, n_grid_size,2,2, T_max-T_min))
kgrid = np.zeros((w_grid_size, n_grid_size,2,2, T_max-T_min))
hgrid = np.zeros((w_grid_size, n_grid_size,2,2, T_max-T_min))
# Policy function of buying a house
Mgrid = np.zeros((w_grid_size, n_grid_size,2,2, T_max-T_min))
Hgrid = np.zeros((w_grid_size, n_grid_size,2,2, T_max-T_min))_____no_output_____# H
V1000 = np.load("Vgrid1000.npy")
V1500 = np.load("Vgrid1500.npy")
V2000 = np.load("Vgrid2000.npy")
V750 = np.load("Vgrid750.npy")
H_options = [750, 1000, 1500, 2000]
M_options = [0.2, 0.5, 0.8]
Vown = [V750, V1000, V1500, V2000]
Hs = [750, 1000, 1500, 2000]_____no_output_____class iApproxy(object):
    def __init__(self, pointsRent, Vrent, Vown, t):
        self.Vrent = Vrent
        self.Vown = Vown
        self.Prent = pointsRent
        self.t = t
    def predict(self, xx):
        if xx.shape[1] == 4:
            # x = [w, n, e, s]
            pvalues = np.zeros(xx.shape[0])
            index00 = (xx[:,2] == 0) & (xx[:,3] == 0)
            index01 = (xx[:,2] == 0) & (xx[:,3] == 1)
            index10 = (xx[:,2] == 1) & (xx[:,3] == 0)
            index11 = (xx[:,2] == 1) & (xx[:,3] == 1)
            pvalues[index00]=interpn(self.Prent, self.Vrent[:,:,0,0], xx[index00][:,:2], bounds_error = False, fill_value = None)
            pvalues[index01]=interpn(self.Prent, self.Vrent[:,:,0,1], xx[index01][:,:2], bounds_error = False, fill_value = None)
            pvalues[index10]=interpn(self.Prent, self.Vrent[:,:,1,0], xx[index10][:,:2], bounds_error = False, fill_value = None)
            pvalues[index11]=interpn(self.Prent, self.Vrent[:,:,1,1], xx[index11][:,:2], bounds_error = False, fill_value = None)
            return pvalues
        else:
            # x = w, n, M, g=0, e, s, H
            pvalues = np.zeros(xx.shape[0])
            for i in range(len(Hs)):
                H = Hs[i]
                # Mortgage amount, * 0.25 is the housing price per unit
                Ms = np.array([0.01*H,0.05*H,0.1*H,0.2*H,0.3*H,0.4*H,0.5*H,0.6*H,0.7*H,0.8*H]) * pt
                points = (ws,ns,Ms)
                index00 = (xx[:,4] == 0) & (xx[:,5] == 0) & (xx[:,6] == H)
                index01 = (xx[:,4] == 0) & (xx[:,5] == 1) & (xx[:,6] == H)
                index10 = (xx[:,4] == 1) & (xx[:,5] == 0) & (xx[:,6] == H)
                index11 = (xx[:,4] == 1) & (xx[:,5] == 1) & (xx[:,6] == H)
                pvalues[index00]=interpn(points, self.Vown[i][:,:,:,0,0,0,self.t], xx[index00][:,:3], method = "nearest",bounds_error = False, fill_value = None)
                pvalues[index01]=interpn(points, self.Vown[i][:,:,:,0,0,1,self.t], xx[index01][:,:3], method = "nearest",bounds_error = False, fill_value = None)
                pvalues[index10]=interpn(points, self.Vown[i][:,:,:,0,1,0,self.t], xx[index10][:,:3], method = "nearest",bounds_error = False, fill_value = None)
                pvalues[index11]=interpn(points, self.Vown[i][:,:,:,0,1,1,self.t], xx[index11][:,:3], method = "nearest",bounds_error = False, fill_value = None)
            return pvalues_____no_output_____%%time
# value iteration part
xs = xgrid.reshape((w_grid_size*n_grid_size*2*2,4))
pool = Pool()
pointsRent = (ws, ns)
for t in range(T_max-1,T_min, -1):
    print(t)
    if t == T_max - 1:
        f = partial(V, t = t, NN = None)
        results = np.array(pool.map(f, xs))
    else:
        approx = iApproxy(pointsRent,Vgrid[:,:,:,:,t+1], Vown, t+1)
        f = partial(V, t = t, NN = approx)
        results = np.array(pool.map(f, xs))

    # here results need some clean up due to different length of the actions taken.
    # a = [c,b,k,h] or a = [c,b,k,M,H]
    Vgrid[:,:,:,:,t] = results[:,0].reshape((w_grid_size,n_grid_size,2,2))
    cgrid[:,:,:,:,t] = np.array([r[0] for r in results[:,1]]).reshape((w_grid_size,n_grid_size,2,2))
    bgrid[:,:,:,:,t] = np.array([r[1] for r in results[:,1]]).reshape((w_grid_size,n_grid_size,2,2))
    kgrid[:,:,:,:,t] = np.array([r[2] for r in results[:,1]]).reshape((w_grid_size,n_grid_size,2,2))
    # if a = [c, b, k, h]
    hgrid[:,:,:,:,t] = np.array([r[3] if len(r) == 4 else r[4] for r in results[:,1]]).reshape((w_grid_size,n_grid_size,2,2))
    # if a = [c, b, k, M, H]
    Mgrid[:,:,:,:,t] = np.array([r[3] if len(r) == 5 else 0 for r in results[:,1]]).reshape((w_grid_size,n_grid_size,2,2))
    Hgrid[:,:,:,:,t] = np.array([r[4] if len(r) == 5 else 0 for r in results[:,1]]).reshape((w_grid_size,n_grid_size,2,2))
pool.close()

# np.save("Vgrid_renting",Vgrid)
# np.save("cgrid_renting",cgrid)
# np.save("bgrid_renting",bgrid)
# np.save("kgrid_renting",kgrid)
# np.save("hgrid_renting",hgrid)
# np.save("Mgrid_renting",Mgrid)
# np.save("Hgrid_renting",Hgrid) 69
68
for tt in range(10,25):
    print(Hgrid[:,1,1,1,tt])_____no_output_____for tt in range(10,25):
    print(Hgrid[:,1,0,1,tt])_____no_output_____for tt in range(10,25):
    print(Hgrid[:,1,1,0,tt])_____no_output_____for tt in range(10,25):
    print(Hgrid[:,1,0,0,tt])_____no_output_____


# Notebook from IST256-classroom/2019-Spring-Learn-Python-mafudge
Path: content/lessons/12/Watch-Me-Code/WMC3-Reading-Data.ipynb

# Watch Me Code 3: Reading Data From an API into Pandas

This example will read from the star wars api http://swapi.co/api/people/ into a Pandas DataFrame_____no_output_____

<code>
import pandas as pd
import requests_____no_output_____# function to get star wars characters
def GetStarWarsCharacters():
    response = requests.get('http://swapi.co/api/people/')
    data_dict = response.json()
    characters = data_dict['results']
    return characters_____no_output_____# let's try out our function. notice it's a list of dictionary.
GetStarWarsCharacters()_____no_output_____# example of reading the star-wars api
characters = GetStarWarsCharacters() # This function gets the people as json
character_df = pd.DataFrame(characters)
character_df_____no_output_____# Let's get character names eye_xolorand skin colors
character_df[['name','skin_color', 'eye_color']]_____no_output_____# let's do the same for the male characters only
character_df[['name','skin_color', 'eye_color']][character_df['gender'] == 'male']_____no_output_____
</code>


# Notebook from UWashington-Astro300/Astro300-A20
Path: HW_Units.ipynb

# Units in Python_____no_output_____---

### [Astropy Units](http://docs.astropy.org/en/stable/units/index.html#module-astropy.units.si)

### [Astropy Constants](https://docs.astropy.org/en/stable/constants/#module-astropy.constants)

### [Numpy Math Functions](https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html)

---_____no_output_____* Use the `Astropy` units and constants packages to solve the following problems.
* Do not hardcode any constants!
* **Unless asked**, your units should be in the simplest SI units possible.
* Two digits after the decimal (.2f)
* For each problem:
  * Write a **FUNCTION** to solve the problem
  * Run the function with input with units
  * Format the output as a sentence - For example: `The range would be 123.34 km.`
  * Use f-strings to format your output - `f"The range would be {my_var:.2f} km."`
  * All numbers should be rounded to 2 digits after the decimal_____no_output_____

import numpy as np

from astropy import units as u
from astropy import constants as const
from astropy.units import imperial
imperial.enable()_____no_output_____

---

### The range of a projectile launched with a velocity (v) at and angle ($\theta$) is

$$ \large
R\ =\  {v^2 \over g}\ sin(2\theta)
$$_____no_output_____**Problem 1** - Find R for v = 123 mph and $\theta$ = 1000 arc minutes_____no_output_____

# Write a function
_____no_output_____# Run the Function
_____no_output_____# Format the output
_____no_output_____

----
**Problem 2** - How fast do you have to throw an abject at 33.3 degrees so that is goes exactly 100 yards? Express your answer in mph_____no_output_____

# Write a function
_____no_output_____# Run the Function
_____no_output_____# Format the output
_____no_output_____

---

### Kepler's third law can be expressed as:

$$ \large
T^2 = \left( {{4\pi^2} \over {GM}} \right)\ r^3
$$

Where __T__ is the orbial period of an object at distance (__r__) from the **center** of an object of mass (__M__)._____no_output_____And the velocity of an object in orbit is

$$ \large
v=\sqrt{GM\over r}
$$_____no_output_____**Problem 3** - Calculate the orbital period of International Space Station (ISS). ISS orbits 254 miles above the **surface** of the Earth. Expess your answer in minutes (make sure your answer makes sense!)._____no_output_____

# Write a function
_____no_output_____# Run the Function
_____no_output_____# Format the output
_____no_output_____

----
**Problem 4** - Calculate the velocity of ISS. Expess your answer in mph._____no_output_____

# Write a function
_____no_output_____# Run the Function
_____no_output_____# Format the output
_____no_output_____

----
**Problem 5** - The Procliamer's song [500 miles](https://youtu.be/MJuyn0WAYNI?t=27s) has a duration of 3 minutes and 33 seconds. Calculate at what altitude, above the Earth's surface, you would have to orbit to go 1000 miles in this time. Express your answer in miles above the Earth's surface._____no_output_____

# Write a function
_____no_output_____# Run the Function
_____no_output_____# Format the output
_____no_output_____

---

### The Power being received by a solar panel in space can be expressed as:

$$ \large
I\  =\ {{L_{\odot}} \over {4 \pi d^2}}\ \varepsilon
$$

Where __I__ is the power __per unit area__ at a distance (__d__) from the Sun, and $\varepsilon$ is the efficiency of the solar panel.

The solar panels that power spacecraft have an efficiency of about 40%._____no_output_____**Problem 6** -  The [New Horizons](http://pluto.jhuapl.edu/) spacecraft requires 220 Watts of power.

Calculate the area of a solar panel that would be needed to power New Horizons at a distance of 1 AU from the Sun._____no_output_____

# Write a function
_____no_output_____# Run the Function
_____no_output_____# Format the output
_____no_output_____

----
**Problem 7** - Express your answer in units of the area of a piece of US letter sized paper (8.5 in x 11 in)._____no_output_____

# Define the new unit
_____no_output_____# Format the output
_____no_output_____

----
**Problem 8** - Same question as above but now a d = 30 AU.

Express you answer in US letter sized paper_____no_output_____

# Run the Function
_____no_output_____# Format the output
_____no_output_____

----
**Problem 9** - [The UW Red Square](https://www.youtube.com/watch?v=diXpKym1svI) is (sort of) rectangular with a size of about 290 ft x 190 ft.

The main part of the Oort cloud is thought to be at a distance of about 10,000 AU.

Calculate the size of the solar panel New Horizons would need to operate in the Oort cloud.

Express your answer in units of the area UW's Red Square._____no_output_____

# Define the new unit
_____no_output_____# Run the Function
_____no_output_____# Format the output
_____no_output_____

----
**Problem 10** -  Calculate the maximum distance from the Sun where a solar panel the size of UW's Red Square can power the New Horizons spacecraft. Express your answer in AU._____no_output_____

# Write a function
_____no_output_____# Run the Function
_____no_output_____# Format the output
_____no_output_____

----

### Stellar Luminosity

The Luminosity of a star can be expressed as:

$$ \large
L\  =\ 4 \pi R^2 \sigma T^{4}
$$

Where **R** is the star's radius and **T** is the star's temperature, and $\sigma$ is the Stefan–Boltzmann constant._____no_output_____**Problem 11** - Calculate the temperature of a star that is the same size as the Sun and a luminosity of 2.99 $\times$ 10$^{27}$ BTUs/day._____no_output_____

# Write a function
_____no_output_____# Run the Function
_____no_output_____# Format the output
_____no_output_____

---

### Earth?

The self gravitational potential energy of the Earth is:

$$ \large
PE \ = \ \frac{3}{5} \cdot \frac{GM_{\oplus}^2}{R_{\oplus}}
$$

This is the amount of energy you need to give the Earth to move all of its components pieces infinitely far away (i.e. to destroy it!)._____no_output_____**Problem 12** - Calculate how massive an object you whould have to hit the Earth with at 45,000 mph to destroy the Earth. Express your answer in Earth masses._____no_output_____

# Write a function
_____no_output_____# Run the Function
_____no_output_____# Format the output
_____no_output_____

---

### Due Mon Oct 26 - 1 pm

- `Save your file to HTML: File -> Download as -> HTML`
- `Upload the .html file to Canvas`_____no_output_____