```python
import pandas as pd
from pathlib import Path
import matplotlib.pyplot as plt
from collections import OrderedDict, defaultdict
from sklearn.preprocessing import LabelEncoder
import sys
import os
module_path = Path("../src/")
os.chdir(module_path)
sys.path.append(module_path)
import warnings
warnings.filterwarnings("ignore")
```

```python
from data_access.load_file import load_table, dict_to_df
from data_preparation import compute_work_item_times
from data_processing.functions import time_for_phase, set_end_date
from data_processing import features
```

First we load and preprocess the data

```python
filename = Path("../data/gdsc2_public.csv")
data = load_table(filename)
total_times = compute_work_item_times(data)
times = time_for_phase(data, end_date="2018-03-31", process=False)
times.dropna(inplace=True)
```

```python
open_wis = total_times[pd.isnull(total_times["duration_in_days"])]["work_item"].values
times_open = times[times.work_item.isin(open_wis)]
times_closed = times[~times.work_item.isin(open_wis)]
times_closed.dropna(inplace=True)
times_closed.loc[:, "receive_date"] = times_closed["from_timestamp"].apply(lambda x: x.date())
times_closed.loc[:, "drop_date"] = times_closed["to_timestamp"].apply(lambda x: x.date())
```

```python
times.loc[:, "receive_date"] = times["from_timestamp"].apply(lambda x: x.date())
times.loc[:, "drop_date"] = times["to_timestamp"].apply(lambda x: x.date())
daterange = pd.date_range(start=str(times["receive_date"].min()), end=str(times["drop_date"].max()), freq='D')
```

For easier handling we assume every working step to be closed until the last day in the dataset.

We now want to compute a measure for the experience of an employee. We define it as follow:

\begin{equation}
x^{er}_{exp}(t) = \frac{t_{spent}\sum{w^{er}_{closed}(t)}}{\sum{w_{closed}(t)}}
\end{equation}

We also want to define a measure for the workload of an employee:

\begin{equation}
x^{er}_{load}(t) = \frac{\sum{w^{er}_{open}(t)}}{\sum{w_{open}(t)}}
\end{equation}

with $t$ as time, $t_{spent}$ as time already spend in the company, $w$ being a working step and $er$ being a resource.

```python
def x_experience(times, resource, t, col="current_resource"):
    assert col in times.columns, col + " not in columns of dataframe"

    closed_tickets = times[times["receive_date"] < t]
    er = closed_tickets[closed_tickets[col] == resource]

    try:
        date_diff = (t - er["receive_date"].min())
        working_time = round(date_diff.total_seconds() / (24*3600), 2)
    except TypeError:
        working_time = 1
    if pd.isna(working_time):
        working_time = 1

    try:
        x_exp = (working_time*len(er))/len(closed_tickets)
    except ZeroDivisionError:
        x_exp = 0

    return x_exp
```

```python
def x_workload(times, resource, t, col="current_resource"):
    assert col in times.columns, col + " not in columns of dataframe"

    open_tickets = times[(times["receive_date"] <= t) &
                         (times["drop_date"] >= t)]
    er = open_tickets[open_tickets[col] == resource]

    try:
        x_load = (len(er))/len(open_tickets)
    except ZeroDivisionError:
        x_load = 0

    return x_load
```

We are going to test the functions with the most frequent resource.

```python
resource = "ER_00061"
plot_df = pd.DataFrame(index=daterange, columns=["x_exp", "x_load"])
for date in daterange:
    plot_df.loc[date, "x_exp"] = x_experience(times, resource, date.date())
    plot_df.loc[date, "x_load"] = x_workload(times, resource, date.date())
```

The function runs without errors. We want to plot the results, the graph should show an increasing trend for the experience and some kind of fluktuation for the workload.

```python
fig, ax = plt.subplots(1, 2, figsize=(15,5), sharex=True, constrained_layout=True)
plot_df["x_exp"].plot(color='g', ax=ax[0], label="x_exp")
plot_df["x_load"].plot(color='b', ax=ax[1], label="x_load")
ax[0].set_title("Experience of most frequent resource")
ax[1].set_title("Workload of most frequent resource")
ax[0].legend()
ax[1].legend()
plt.show()
```

Now we are able to compute the two measures for every resource for a given time. To add this as a feature to our model we also need a function to aggregate the measures regarding to the multiple resources working on one work item. We define the following as the employment rate for every work item:

\begin{equation}
x^{wi}_{emp} = \frac{\sum_{er}{x^{er}_{exp}(t)x^{er}_{load}(t)}}{\sum_{er}{w^{er}}}
\end{equation}

with $t$ as the time the employee recieves the task.

```python
def employment_rate(times, work_item, resource_col="current_resource"):
    assert resource_col in times.columns, resource_col + " not in columns of dateframe!"

    wi = times[times["work_item"] == work_item]
    resources = list(wi[resource_col].values)
    res_counter = defaultdict(int)
    numerator = 0
    x_exp_sum = 0
    x_load_sum = 0
    denumerator = len(resources)

    for resource in resources:
        if resources.count(resource) > 1:
            res_counter[resource] += 1
            t = wi[wi[resource_col] == resource]["receive_date"].iloc[res_counter[resource]-1]
        elif resources.count(resource) == 1:
            t = wi[wi[resource_col] == resource]["receive_date"].values[0]
        x_exp = x_experience(times, resource, t, resource_col)
        x_load = x_workload(times, resource, t, resource_col)
        x_exp_sum += x_exp
        x_load_sum += x_load
        numerator += (x_exp * x_load)

    x_emp = numerator/denumerator
    x_ex = x_exp_sum/denumerator
    x_l = x_load_sum/denumerator
    return x_emp, x_ex, x_l
```

Now we want to compute the measures for every work item and compare them

```python
x_emp, x_exp, x_load = employment_rate(times, "WI_000001")
```

```python
# res_df = pd.DataFrame(index=times.work_item.unique(), columns=["x_emp", "x_exp", "x_load"])
# counter = 0
# for wi in times.work_item.unique():
#     x_emp, x_exp, x_load = employment_rate(times, wi)
#     res_df.loc[wi, "x_emp"] = x_emp
#     res_df.loc[wi, "x_exp"] = x_exp
#     res_df.loc[wi, "x_load"] = x_load
#     counter += 1
#     if counter == 500:
#         print("500 work items done!")
#         counter = 0
```

Computation takes a while. We will write the data to the SQL-Server so we don't have to compute them every time.

```python
# import sqlalchemy
# engine = sqlalchemy.create_engine("XXX")
# con = engine.connect()
# # tosql = res_df.reset_index().rename(columns={"index":"work_item"})
# # tosql.to_sql(name="resource_employment_rate", con=con)
# res_df = pd.read_sql_table(table_name="resource_employment_rate", con=con)
# con.close()
```

```python
fig, ax = plt.subplots(3,1, figsize=(12,10), sharex=True, constrained_layout=True)
res_df["x_emp"].plot(ax=ax[0], label="x_emp")
ax[0].set_title("Employment rate")
ax[0].legend()
res_df["x_exp"].plot(ax=ax[1], label="x_exp")
ax[1].set_title("Experience rate")
ax[1].legend()
res_df["x_load"].plot(ax=ax[2], label="x_load")
ax[2].set_title("Workload rate")
ax[2].legend()
plt.show()
```

We want to add another feature regarding to the difficulty of a work item. Resources have days where they are closing a lot of items at once. But there are always some items that are skipping these 'closing days' so we assume that these items have a higher difficulty.

We define a closing day as
\begin{equation}
t^{er}_{close} =
\begin{cases}
      1 & \frac{\sum{w^{er}_{closed}(t)}}{\sum{w^{er}_{open}(t)}} \geq 0.25 \\
      0 & else
\end{cases}
\end{equation}

So if 30% of the open tickets the resource holds are closed it's a closing day. We define than the difficulty of a work item

\begin{equation}
x^{wi}_{diff} = \sum_{t}{\sum_{er}{t^{er}_{close}}}
\end{equation}

The difficulty is the sum of all closing days a work item skipped for every resource that worked on it.

Now let's implement this.

```python
def calc_t_close(times_closed, resource):
    res_df = times_closed[times_closed["current_resource"] == resource]
#     We calculate the number of items dropped at a drop date
    t_close = pd.DataFrame(res_df["drop_date"].value_counts()).reset_index().rename(columns={"index":"drop_date",
                                                                                "drop_date":"w_closed"})
#     We calculate the work items that are open over a drop date
    res_df["w_open"] = res_df["drop_date"].apply(lambda t: res_df[(res_df["drop_date"].apply(lambda x: x>=t)) &
                                                           (res_df["receive_date"].apply(lambda x: x<=t))].shape[0])
#     Now we merge them together and calculate the percentage
    t_close = pd.merge(t_close, res_df[["drop_date", "w_open"]].drop_duplicates())
    t_close.loc[:, "percentage_closed"] = t_close["w_closed"] / t_close["w_open"].apply(lambda x: 1 if x==0 else x)
    t_close.loc[:, "t_close"] = t_close["percentage_closed"].apply(lambda x: 1 if x >= 0.25 else 0)
    close_days = t_close[t_close["t_close"] == 1]["drop_date"].values
    return close_days
```

```python
times.loc[:, "x_diff"] = 0
for resource in times["current_resource"].unique():
    close_days = calc_t_close(times_closed, resource)
    res_df = times[times["current_resource"] == resource]
    for x, y in res_df.iterrows():
        difficulty = len([e for e in close_days if ((y["receive_date"]<e)&~(y["drop_date"]<=e))])
        times.loc[x, "x_diff"] = difficulty
```

```python
difficulty_df = pd.DataFrame(times.groupby("work_item")["x_diff"].sum()).reset_index()
plot_df = pd.merge(total_times.dropna(), difficulty_df).drop_duplicates()
total_times_c = compute_work_item_times(set_end_date(data, "2018-03-31"))
plot_df_open = pd.merge(total_times_c[total_times_c.work_item.isin(open_wis)], difficulty_df).drop_duplicates("work_item")
```

```python
plt.figure(figsize=(15,8))
plt.scatter(x=plot_df.x_diff, y=plot_df.duration_in_days, s=4, color='g', label="closed tickets")
plt.scatter(x=plot_df_open.x_diff, y=plot_df_open.duration_in_days, s=4, color='r', label="open tickets")
plt.title("Difficulty corresponding to duration")
plt.xlabel("difficulty")
plt.ylabel("duration in days")
plt.legend()
plt.show()
```

Now the difficulty is only resource related and has a wide range of values. We should think about a better scale for that.

```python

```


```python
from LOTlib3.Grammar import Grammar
from math import log
from LOTlib3.Hypotheses.LOTHypothesis import LOTHypothesis
from LOTlib3.DataAndObjects import FunctionData
from LOTlib3.TopN import TopN
from LOTlib3.Samplers.MetropolisHastings import MetropolisHastingsSampler
from LOTlib3.Eval import primitive
```

```python
import numpy as np
import pandas as pd

import os
import json
import time
import copy

from IPython.display import clear_output
from IPython.display import HTML

import matplotlib.pyplot as plt
from matplotlib import animation
from matplotlib import colors

from PIL import Image, ImageDraw
from IPython.display import Image as Im
from itertools import permutations, combinations
from sympy.utilities.iterables import multiset_permutations

# from skimage.segmentation import flood, flood_fill

%matplotlib inline
```

```python
# define solver
class ARCSolver:
    def __init__(self, task_filename):
        # load task and extract input and output pairs
        self.task_filename = task_filename
        self.task = self.load_task(task_filename)
        self.train_inputs, self.train_outputs, self.test_inputs, self.test_outputs = \
            self.extract_io_pairs()
        self.test_pred = np.zeros((5, 5))
        self.test_pred_height, self.test_pred_width = self.test_pred.shape

        self.solved = False  # have we solved the task yet?
        self.selected_colour = 0
        self.clipboard = None
        self.description = ''

        # variables for plotting
        self.cmap = colors.ListedColormap(
            ['#000000', '#0074D9','#FF4136','#2ECC40','#FFDC00',
             '#AAAAAA', '#F012BE', '#FF851B', '#7FDBFF', '#870C25'])
        self.colour_to_num = {'black': 0, 'blue': 1, 'red': 2, 'green': 3, 'yellow': 4,
                             'grey': 5, 'magenta': 6, 'orange': 7, 'light_blue': 8,
                             'maroon': 9}
        self.num_to_colour = {0: 'black', 1: 'blue', 2: 'red', 3: 'green', 4: 'yellow',
                             5: 'grey', 6: 'magneta', 7: 'orange', 8: 'light_blue',
                             9: 'maroon'}

    def load_task(self, task_filename):
        with open(task_filename, 'r') as f:
            task = json.load(f)
        return task

    def plot_task(self):
        """
        Plots the first train and test pairs of a specified task,
        using same color scheme as the ARC app
        """
        norm = colors.Normalize(vmin=0, vmax=9)
        n_train = len(self.task['train'])
        fig, axs = plt.subplots(n_train+1, 2, figsize=(10, 10))
        for i in range(n_train):
            axs[i, 0].imshow(self.task['train'][i]['input'], cmap=self.cmap, norm=norm)
            axs[i, 0].axis('off')
            axs[i, 0].set_title('Train Input')
            axs[i, 1].imshow(self.task['train'][i]['output'], cmap=self.cmap, norm=norm)
            axs[i, 1].axis('off')
            axs[i, 1].set_title('Train Output')
        axs[n_train, 0].imshow(self.task['test'][0]['input'], cmap=self.cmap, norm=norm)
        axs[n_train, 0].axis('off')
        axs[n_train, 0].set_title('Test Input')
        axs[n_train, 1].imshow(self.task['test'][0]['output'], cmap=self.cmap, norm=norm)
        axs[n_train, 1].axis('off')
        axs[n_train, 1].set_title('Test Output')
        plt.tight_layout()
        plt.show()

    def plot_grid(self, grid):
        """
        Plots a single grid
        """
        #plt.clf()

        #plt.draw()
        #display(plt)

    def plot_grids(self, grids):
        """
        Plots a list of grids
        """
        n_grids = len(grids)
        norm = colors.Normalize(vmin=0, vmax=9)
        fig, axs = plt.subplots(1, n_grids, figsize=(6, 6), squeeze=False)
        for i in range(n_grids):
            axs[0, i].imshow(grids[i], cmap=self.cmap, norm=norm)
            axs[0, i].axis('off')
        plt.tight_layout()
        plt.show()

    def extract_io_pairs(self):
        train = self.task['train']
        test = self.task['test']
        n_train = len(train)
        n_test = len(test)

        train_inputs = np.array([train[i]['input'] for i in range(n_train)])
        train_outputs = np.array([train[i]['output'] for i in range(n_train)])
        test_inputs = np.array([test[i]['input'] for i in range(n_test)])
        test_outputs = np.array([test[i]['output'] for i in range(n_test)])

        return train_inputs, train_outputs, test_inputs, test_outputs

    def copy_from_input(self):
        # copy over the first test input
        self.test_pred = self.test_inputs[0].copy()
        self.test_pred_height, self.test_pred_width = self.test_inputs[0].shape
        self.description = 'copy from input'

    def reset(self):
        # resets grid to all zeros with size of the grid based on current settings
        self.test_pred = np.zeros((self.test_pred_height, self.test_pred_width))
        self.description = 'reset'

    def resize(self):
        # resizes the grid
        prev_test_pred = self.test_pred.copy()
        prev_test_pred_width = self.test_pred_width
        prev_test_pred_height = self.test_pred_height

        # sample new grid size
        new_test_pred_width = np.random.choice(np.arange(1, 5))
        new_test_pred_height = np.random.choice(np.arange(1, 5))
        new_test_pred = np.zeros((new_test_pred_height, new_test_pred_width))

        # copy over values
        for i in range(min(prev_test_pred_height, new_test_pred_height)):
            for j in range(min(prev_test_pred_width, new_test_pred_width)):
                new_test_pred[i, j] = prev_test_pred[i, j]

        self.test_pred = new_test_pred
        self.test_pred_width = new_test_pred_width
        self.test_pred_height = new_test_pred_height
        self.description = f'resize: ({new_test_pred_height}, {new_test_pred_width})'

    def change_colour(self):
        self.selected_colour = np.random.choice(np.arange(10))
        self.description = f'change colour: {self.num_to_colour[self.selected_colour]}'

    def edit(self):
        # select a random location
        x = np.random.choice(np.arange(self.test_pred_width))
        y = np.random.choice(np.arange(self.test_pred_height))
        self.test_pred[y, x] = self.selected_colour
        self.description = f'edit: ({y}, {x})'

    def edit_rectangle(self):
        # selects a randomly selected region and changes the colour of all of the cells
        x_start = np.random.choice(np.arange(self.test_pred_width))
        x_end = np.random.choice(np.arange(x_start+1, self.test_pred_width+1))
        y_start = np.random.choice(np.arange(self.test_pred_height))
        y_end = np.random.choice(np.arange(y_start+1, self.test_pred_height+1))

        # select a new colour
        self.selected_colour = np.random.choice(np.arange(10))
        self.test_pred[y_start:y_end, x_start:x_end] = self.selected_colour
        self.description = f'edit rectangle from ({y_start}:{y_end}, {x_start}:{x_end}) to {self.selected_colour}'

    def copy(self):
        # copies a randomly selected region
        x_start = np.random.choice(np.arange(self.test_pred_width))
        x_end = np.random.choice(np.arange(x_start+1, self.test_pred_width+1))
        y_start = np.random.choice(np.arange(self.test_pred_height))
        y_end = np.random.choice(np.arange(y_start+1, self.test_pred_height+1))

        self.clipboard = self.test_pred[y_start:y_end, x_start:x_end].copy()
        self.description = f'copy from ({y_start}:{y_end}, {x_start}:{x_end})'
        #print(f'clipboard: {self.clipboard}')

    def paste(self):
        # pastes clipboard value into randomly selected location
        clipboard_height, clipboard_width = self.clipboard.shape
        x_start = np.random.choice(np.arange(self.test_pred_width))
        x_width = min(clipboard_width, self.test_pred_width - x_start)
        y_start = np.random.choice(np.arange(self.test_pred_height))
        y_height = min(clipboard_height, self.test_pred_height - y_start)

        self.test_pred[y_start:y_start+y_height, x_start:x_start+x_width] = self.clipboard[:y_height, :x_width]
        self.description = f'pasting from ({y_start}:{y_start+y_height}, {x_start}:{x_start+x_width})'

    def flood_fill(self):
        # flood fill at a random location
        x = np.random.choice(self.test_pred_width)
        y = np.random.choice(self.test_pred_height)
        self.test_pred = flood_fill(self.test_pred, (y, x),
                                    self.selected_colour)
        self.description = f'flood fill from: ({y}, {x})'

    def solve(self):
        fig = plt.figure(figsize=(6, 6))
        plt.ion()
        plt.show()
        norm = colors.Normalize(vmin=0, vmax=9)

        while not self.solved:
            clear_output()
            # randomly select available function
            if np.random.choice([0, 1]) == 0:
                self.change_colour()
            else:
                self.edit()

            plt.imshow(self.test_pred, cmap=self.cmap, norm=norm)
            plt.axis('off')
            plt.tight_layout()
            plt.pause(1)

            # check accuracy

```

```python
# displaying a single grid
def DisplayGrid(grid):
    grid = np.asarray(grid)
    nrows = len(grid[:,0])
    ncols = len(grid[0,:])
    height, width = nrows*50, ncols*50
    image = Image.new(size=(width,height),mode='RGB',color=(255,255,255))
    draw = ImageDraw.Draw(image)
    r = 0
    for row in grid:
        c = 0
        for col in row:
            draw.rectangle(xy=[c*50,r*50,(c+1)*50,(r+1)*50], fill=colorMap[np.abs(grid[r][c])])
            c += 1
        r += 1
    for i in range(ncols):
        draw.line([(i+1)*50,0,(i+1)*50,height],fill="grey")
    for i in range(nrows):
        draw.line([0,(i+1)*50,width,(i+1)*50],fill="grey")
    display(image)
```

```python
colorMap = {0:"black",1:"blue",2:"red", 3:"green",4:"yellow",5:"grey",6:"magenta",7:"orange",8:"cyan",9:"brown"}
```

```python
training_path = "/Users/aysjajohnson/Desktop/ARC-master/data/training/"
solver = ARCSolver(task_filename=os.path.join(training_path, '0ca9ddb6.json'))
solver.plot_grids(solver.train_inputs)
solver.plot_grids(solver.train_outputs)
```

```python
b = 0.05
```

How do you get LOTlib to start with a hypothesis and expand from there? This is awkward right now -- you have to add the input as a rule...

It also doesn't really understand how to compile lists of lists, just numbers.. I think we'd need to change the backend a bit

```python
# intializing basic hypothesis, will edit later
class ARCHypothesis(LOTHypothesis):
    def __init__(self, **kwargs):
        LOTHypothesis.__init__(self, grammar=grammar, display="lambda grid: %s", **kwargs)

    def __call__(self, *args):
        try:
            # try to do it from the superclass
            return LOTHypothesis.__call__(self, *args)
        except ZeroDivisionError:
            # and if we get an error, return nan
            return float("nan")

    def compute_single_likelihood(self, datum):
        # for each correct pixel, add log(1), for each incorrect, add log(exp(-b))
        ll = 0
        input_grid = self(*datum.input)
        output_grid = datum.output
        # assuming input and output are the same size for now
        height, width = len(input_grid[:,0]), len(input_grid[0,:])
        for i in range(height):
            for j in range(width):
                if input_grid[i][j] == output_grid[i][j]:
                    ll += log(1)
                elif input_grid[i][j] == 0 and output_grid[i][j] != 0:
                    ll += log(0.1)
                else:
                    ll += log(0.05)
        # print(self.value, ll)
        return ll

    def display_hypothesis(self):
        return self.value
```

```python
inputs = solver.train_inputs
outputs = solver.train_outputs
print([inputs[0]])
```

    [array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 2, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 1, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0]])]

```python
data = [FunctionData(input=[inputs[0]],output=outputs[0])]
print(data)
```

    [<[[0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 0 2 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 1 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]] -> [[0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 4 0 4 0 0 0 0 0]
     [0 0 2 0 0 0 0 0 0]
     [0 4 0 4 0 0 0 0 0]
     [0 0 0 0 0 0 7 0 0]
     [0 0 0 0 0 7 1 7 0]
     [0 0 0 0 0 0 7 0 0]
     [0 0 0 0 0 0 0 0 0]]>]

```python
@primitive
def red_squares_(grid):
    grid = copy.deepcopy(grid)
    height, width = len(grid[:,0]), len(grid[0,:])
    lst = []
    for i in range(height):
        for j in range(width):
            if grid[i][j] == 2:
                lst.append((i,j))
    return lst

@primitive
def blue_squares_(grid):
    grid = copy.deepcopy(grid)
    height, width = len(grid[:,0]), len(grid[0,:])
    lst = []
    for i in range(height):
        for j in range(width):
            if grid[i][j] == 1:
                lst.append((i,j))
    return lst

@primitive
def diag_(lst, grid):
    grid = copy.deepcopy(grid)
    height, width = len(grid[:,0]), len(grid[0,:])
    diag_lst = []
    for tup in lst:
        # check whether diagonals exist on board constraints
        N, E, S, W = tup[0]-1, tup[1] + 1, tup[0] + 1, tup[1] -1
        if  N > 0 and S < height-1 and W > 0 and E < width - 1:
            diag_lst.extend([(N,W),(N,E),(S,E),(S,W)])
    return diag_lst

@primitive
def cross_(lst, grid):
    grid = copy.deepcopy(grid)
    height, width = len(grid[:,0]), len(grid[0,:])
    cross_lst = []
    for tup in lst:
        # check whether diagonals exist on board constraints
        N, E, S, W = tup[0]-1, tup[1] + 1, tup[0] + 1, tup[1] -1
        if  N > 0 and S < height-1 and W > 0 and E < width - 1:
            cross_lst.extend([(N,tup[1]),(tup[0],E),(S,tup[1]),(tup[0],W)])
    return cross_lst

@primitive
def color_(lst, clr):
    return {k:clr for k in lst}

@primitive
def paint_(dict_, grid):
    grid = copy.deepcopy(grid)
    height, width = len(grid[:,0]), len(grid[0,:])
    for coord in dict_.keys():
        grid[coord[0]][coord[1]] = dict_[coord]
    return grid
```

```python
# initializing grammar
grammar = Grammar(start='GRID')

# adding production rules
grammar.add_rule('LIST', 'red_squares_', ['GRID'], 1.0)
grammar.add_rule('LIST', 'blue_squares_', ['GRID'], 1.0)
grammar.add_rule('LIST', 'diag_', ['LIST', 'GRID'], 1.0)
grammar.add_rule('LIST', 'cross_', ['LIST', 'GRID'], 1.0)
grammar.add_rule('DICT', 'color_', ['LIST','COLOR'], 1.0)
grammar.add_rule('GRID', 'paint_', ['DICT', 'GRID'], 1.0)

# adding numbers 0 through 9 as terminals
# for n in range(10):
#     grammar.add_rule('COLOR', str(n), None, 1.0)

# right now just consider 4 and 7 to see if this makes the right solution come up
grammar.add_rule('COLOR', str(4), None, 1.0)
grammar.add_rule('COLOR', str(7), None, 1.0)

grammar.add_rule('GRID', 'grid', None, 3.0)
```

    GRID -> grid	w/ p=3.0

```python
rs = red_squares_(inputs[0])
```

```python
diag_(rs, inputs[0])
```

    [(2, 1), (2, 3), (4, 3), (4, 1)]

```python
grammar.display_rules()
```

    LIST -> red_squares_['GRID']	w/ p=1.0
    LIST -> blue_squares_['GRID']	w/ p=1.0
    LIST -> diag_['LIST', 'GRID']	w/ p=1.0
    LIST -> cross_['LIST', 'GRID']	w/ p=1.0
    DICT -> color_['LIST', 'COLOR']	w/ p=1.0
    GRID -> paint_['DICT', 'GRID']	w/ p=1.0
    GRID -> grid	w/ p=3.0
    COLOR -> 0	w/ p=1.0
    COLOR -> 1	w/ p=1.0
    COLOR -> 2	w/ p=1.0
    COLOR -> 3	w/ p=1.0
    COLOR -> 4	w/ p=1.0
    COLOR -> 5	w/ p=1.0
    COLOR -> 6	w/ p=1.0
    COLOR -> 7	w/ p=1.0
    COLOR -> 8	w/ p=1.0
    COLOR -> 9	w/ p=1.0

```python
h = ARCHypothesis()
print(h)
```

    lambda grid: paint_(color_(blue_squares_(grid), 7), paint_(color_(red_squares_(grid), 4), grid))

```python
h(inputs[0])
```

    array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 4, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 7, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0]])

```python
DisplayGrid(h(inputs[0]))
```

```python
DisplayGrid(paint_(color_(diag_(red_squares_(inputs[0]),inputs[0]),4),inputs[0]))
```

```python
# problem: MHS considers the class to have no arguments. Current workaround, globally declare num_vars before running

tn = TopN(N=10) # store the top N

h0 = ARCHypothesis()
print(h0)

for h in MetropolisHastingsSampler(h0, data, steps=1000):
    tn.add(h)

for h in tn.get_all(sorted=True):
    print(h.posterior_score, h.likelihood,  h.prior, h)
```

```python
print(h)
```

    lambda grid: grid

```python
print(inputs[0])
```

    [[0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 0 2 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 1 0 0]
     [0 0 0 0 0 0 0 0 0]
     [0 0 0 0 0 0 0 0 0]]

```python

```


```python
!pip install qiskit
!pip install qiskit-machine-learning
```

    Collecting qiskit
      Downloading qiskit-0.33.0.tar.gz (13 kB)
    Collecting qiskit-terra==0.19.0
      Downloading qiskit_terra-0.19.0-cp37-cp37m-manylinux2010_x86_64.whl (6.4 MB)
         |████████████████████████████████| 6.4 MB 4.0 MB/s
    Collecting qiskit-aer==0.9.1
      Downloading qiskit_aer-0.9.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (17.9 MB)
         |████████████████████████████████| 17.9 MB 559 kB/s
    Collecting qiskit-ibmq-provider==0.18.1
      Downloading qiskit_ibmq_provider-0.18.1-py3-none-any.whl (237 kB)
         |████████████████████████████████| 237 kB 71.4 MB/s
    Collecting qiskit-ignis==0.7.0
      Downloading qiskit_ignis-0.7.0-py3-none-any.whl (200 kB)
         |████████████████████████████████| 200 kB 59.7 MB/s
    Requirement already satisfied: scipy>=1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.9.1->qiskit) (1.4.1)
    Requirement already satisfied: numpy>=1.16.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-aer==0.9.1->qiskit) (1.19.5)
    Requirement already satisfied: python-dateutil>=2.8.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.1->qiskit) (2.8.2)
    Requirement already satisfied: urllib3>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.1->qiskit) (1.24.3)
    Collecting requests-ntlm>=1.1.0
      Downloading requests_ntlm-1.1.0-py2.py3-none-any.whl (5.7 kB)
    Collecting websocket-client>=1.0.1
      Downloading websocket_client-1.2.3-py3-none-any.whl (53 kB)
         |████████████████████████████████| 53 kB 1.8 MB/s
    Requirement already satisfied: requests>=2.19 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.18.1->qiskit) (2.23.0)
    Collecting retworkx>=0.8.0
      Downloading retworkx-0.10.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.4 MB)
         |████████████████████████████████| 1.4 MB 54.3 MB/s
    Requirement already satisfied: setuptools>=40.1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ignis==0.7.0->qiskit) (57.4.0)
    Collecting tweedledum<2.0,>=1.1
      Downloading tweedledum-1.1.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (943 kB)
         |████████████████████████████████| 943 kB 54.8 MB/s
    Collecting ply>=3.10
      Downloading ply-3.11-py2.py3-none-any.whl (49 kB)
         |████████████████████████████████| 49 kB 5.6 MB/s
    Collecting scipy>=1.0
      Downloading scipy-1.7.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (38.1 MB)
         |████████████████████████████████| 38.1 MB 1.1 MB/s
    Collecting symengine>=0.8
      Downloading symengine-0.8.1-cp37-cp37m-manylinux2010_x86_64.whl (38.2 MB)
         |████████████████████████████████| 38.2 MB 1.2 MB/s
    Requirement already satisfied: dill>=0.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.19.0->qiskit) (0.3.4)
    Requirement already satisfied: psutil>=5 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.19.0->qiskit) (5.4.8)
    Requirement already satisfied: sympy>=1.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.19.0->qiskit) (1.7.1)
    Collecting stevedore>=3.0.0
      Downloading stevedore-3.5.0-py3-none-any.whl (49 kB)
         |████████████████████████████████| 49 kB 5.6 MB/s
    Collecting python-constraint>=1.4
      Downloading python-constraint-1.4.0.tar.bz2 (18 kB)
    Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.8.0->qiskit-ibmq-provider==0.18.1->qiskit) (1.15.0)
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.1->qiskit) (2.10)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.1->qiskit) (3.0.4)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.18.1->qiskit) (2021.10.8)
    Collecting cryptography>=1.3
      Downloading cryptography-36.0.0-cp36-abi3-manylinux_2_24_x86_64.whl (3.6 MB)
         |████████████████████████████████| 3.6 MB 60.4 MB/s
    Collecting ntlm-auth>=1.0.2
      Downloading ntlm_auth-1.5.0-py2.py3-none-any.whl (29 kB)
    Requirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.7/dist-packages (from cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.18.1->qiskit) (1.15.0)
    Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.12->cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.18.1->qiskit) (2.21)
    Collecting pbr!=2.1.0,>=2.0.0
      Downloading pbr-5.8.0-py2.py3-none-any.whl (112 kB)
         |████████████████████████████████| 112 kB 71.0 MB/s
    Requirement already satisfied: importlib-metadata>=1.7.0 in /usr/local/lib/python3.7/dist-packages (from stevedore>=3.0.0->qiskit-terra==0.19.0->qiskit) (4.8.2)
    Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=1.7.0->stevedore>=3.0.0->qiskit-terra==0.19.0->qiskit) (3.6.0)
    Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=1.7.0->stevedore>=3.0.0->qiskit-terra==0.19.0->qiskit) (3.10.0.2)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy>=1.3->qiskit-terra==0.19.0->qiskit) (1.2.1)
    Building wheels for collected packages: qiskit, python-constraint
      Building wheel for qiskit (setup.py) ... done
      Created wheel for qiskit: filename=qiskit-0.33.0-py3-none-any.whl size=11763 sha256=7b20655b4b246c3c40dab4d04123c929fbd370f9961fa4425422e89928476ec9
      Stored in directory: /root/.cache/pip/wheels/d4/60/ef/c49ae113df02818c4ade5fb6e2e89bb928625412eda0bb25f5
      Building wheel for python-constraint (setup.py) ... done
      Created wheel for python-constraint: filename=python_constraint-1.4.0-py2.py3-none-any.whl size=24081 sha256=bc599173af47dd861b7e2c523ed208f0c110c69756d0834848052bc428ce1fbc
      Stored in directory: /root/.cache/pip/wheels/07/27/db/1222c80eb1e431f3d2199c12569cb1cac60f562a451fe30479
    Successfully built qiskit python-constraint
    Installing collected packages: pbr, tweedledum, symengine, stevedore, scipy, retworkx, python-constraint, ply, ntlm-auth, cryptography, websocket-client, requests-ntlm, qiskit-terra, qiskit-ignis, qiskit-ibmq-provider, qiskit-aer, qiskit
      Attempting uninstall: scipy
        Found existing installation: scipy 1.4.1
        Uninstalling scipy-1.4.1:
          Successfully uninstalled scipy-1.4.1
    ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
    albumentations 0.1.12 requires imgaug<0.2.7,>=0.2.5, but you have imgaug 0.2.9 which is incompatible.
    Successfully installed cryptography-36.0.0 ntlm-auth-1.5.0 pbr-5.8.0 ply-3.11 python-constraint-1.4.0 qiskit-0.33.0 qiskit-aer-0.9.1 qiskit-ibmq-provider-0.18.1 qiskit-ignis-0.7.0 qiskit-terra-0.19.0 requests-ntlm-1.1.0 retworkx-0.10.2 scipy-1.7.3 stevedore-3.5.0 symengine-0.8.1 tweedledum-1.1.1 websocket-client-1.2.3
    Collecting qiskit-machine-learning
      Downloading qiskit_machine_learning-0.2.1-py3-none-any.whl (96 kB)
         |████████████████████████████████| 96 kB 3.1 MB/s
    Requirement already satisfied: setuptools>=40.1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-machine-learning) (57.4.0)
    Requirement already satisfied: scipy>=1.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-machine-learning) (1.7.3)
    Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.7/dist-packages (from qiskit-machine-learning) (1.19.5)
    Requirement already satisfied: fastdtw in /usr/local/lib/python3.7/dist-packages (from qiskit-machine-learning) (0.3.4)
    Requirement already satisfied: scikit-learn>=0.20.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-machine-learning) (1.0.1)
    Requirement already satisfied: psutil>=5 in /usr/local/lib/python3.7/dist-packages (from qiskit-machine-learning) (5.4.8)
    Requirement already satisfied: qiskit-terra>=0.18.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-machine-learning) (0.19.0)
    Requirement already satisfied: stevedore>=3.0.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra>=0.18.0->qiskit-machine-learning) (3.5.0)
    Requirement already satisfied: retworkx>=0.10.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra>=0.18.0->qiskit-machine-learning) (0.10.2)
    Requirement already satisfied: sympy>=1.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra>=0.18.0->qiskit-machine-learning) (1.7.1)
    Requirement already satisfied: python-dateutil>=2.8.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra>=0.18.0->qiskit-machine-learning) (2.8.2)
    Requirement already satisfied: symengine>=0.8 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra>=0.18.0->qiskit-machine-learning) (0.8.1)
    Requirement already satisfied: tweedledum<2.0,>=1.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra>=0.18.0->qiskit-machine-learning) (1.1.1)
    Requirement already satisfied: dill>=0.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra>=0.18.0->qiskit-machine-learning) (0.3.4)
    Requirement already satisfied: ply>=3.10 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra>=0.18.0->qiskit-machine-learning) (3.11)
    Requirement already satisfied: python-constraint>=1.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra>=0.18.0->qiskit-machine-learning) (1.4.0)
    Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.8.0->qiskit-terra>=0.18.0->qiskit-machine-learning) (1.15.0)
    Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.20.0->qiskit-machine-learning) (1.1.0)
    Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.20.0->qiskit-machine-learning) (3.0.0)
    Requirement already satisfied: importlib-metadata>=1.7.0 in /usr/local/lib/python3.7/dist-packages (from stevedore>=3.0.0->qiskit-terra>=0.18.0->qiskit-machine-learning) (4.8.2)
    Requirement already satisfied: pbr!=2.1.0,>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from stevedore>=3.0.0->qiskit-terra>=0.18.0->qiskit-machine-learning) (5.8.0)
    Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=1.7.0->stevedore>=3.0.0->qiskit-terra>=0.18.0->qiskit-machine-learning) (3.6.0)
    Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata>=1.7.0->stevedore>=3.0.0->qiskit-terra>=0.18.0->qiskit-machine-learning) (3.10.0.2)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy>=1.3->qiskit-terra>=0.18.0->qiskit-machine-learning) (1.2.1)
    Installing collected packages: qiskit-machine-learning
    Successfully installed qiskit-machine-learning-0.2.1

```python
import numpy as np

# Importing standard Qiskit libraries
import qiskit
from qiskit import QuantumCircuit, transpile, Aer, IBMQ
from qiskit.tools.jupyter import *
from qiskit.visualization import *
#from ibm_quantum_widgets import *
from qiskit.providers.aer import QasmSimulator

# Loading your IBM Quantum account(s)
#provider = IBMQ.load_account()
```

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

from qiskit import Aer, QuantumCircuit
from qiskit.opflow import Z, I, StateFn
from qiskit.utils import QuantumInstance
from qiskit.circuit import Parameter
from qiskit.circuit.library import RealAmplitudes, ZZFeatureMap
from qiskit.algorithms.optimizers import COBYLA, L_BFGS_B

from qiskit_machine_learning.neural_networks import TwoLayerQNN, CircuitQNN
from qiskit_machine_learning.algorithms.classifiers import NeuralNetworkClassifier, VQC
from qiskit_machine_learning.algorithms.regressors import NeuralNetworkRegressor, VQR

from typing import Union

from qiskit_machine_learning.exceptions import QiskitMachineLearningError

from IPython.display import clear_output
quantum_instance = QuantumInstance(Aer.get_backend('aer_simulator'), shots=1024)
```

```python
'''
num_samples = 20
eps = 0.2
lb, ub = -np.pi, np.pi
X_ = np.linspace(lb, ub, num=50).reshape(50, 1)
f = lambda x: np.sin(x)

X = (ub - lb)*np.random.rand(num_samples, 1) + lb
y = f(X[:,0]) + eps*(2*np.random.rand(num_samples)-1)

print(X)
print(y)

plt.plot(X_, f(X_), 'r--')
plt.plot(X, y, 'bo')
plt.show()
'''
```

    "\nnum_samples = 20\neps = 0.2\nlb, ub = -np.pi, np.pi\nX_ = np.linspace(lb, ub, num=50).reshape(50, 1)\nf = lambda x: np.sin(x)\n\nX = (ub - lb)*np.random.rand(num_samples, 1) + lb\ny = f(X[:,0]) + eps*(2*np.random.rand(num_samples)-1)\n\nprint(X)\nprint(y)\n\nplt.plot(X_, f(X_), 'r--')\nplt.plot(X, y, 'bo')\nplt.show()\n"

```python
# construct simple feature map
param_x = Parameter('x')
feature_map = QuantumCircuit(1, name='fm')
feature_map.ry(param_x, 0)
print(feature_map)

# construct simple ansatz
param_y = Parameter('y')
ansatz = QuantumCircuit(1, name='vf')
ansatz.ry(param_y, 0)
print(ansatz)

# construct QNN
regression_opflow_qnn = TwoLayerQNN(1, feature_map, ansatz, quantum_instance=quantum_instance)
```

       ┌───────┐
    q: ┤ Ry(x) ├
       └───────┘
       ┌───────┐
    q: ┤ Ry(y) ├
       └───────┘

```python
# callback function that draws a live plot when the .fit() method is called
objective_func_vals = []
def callback_graph(weights, obj_func_eval):
    clear_output(wait=True)
    objective_func_vals.append(obj_func_eval)
    plt.title("Objective function value against iteration")
    plt.xlabel("Iteration")
    plt.ylabel("Objective function value")
    plt.plot(range(len(objective_func_vals)), objective_func_vals)
    plt.show()
```

```python
# construct the regressor from the neural network
regressor = NeuralNetworkRegressor(neural_network=regression_opflow_qnn,
                                   loss='l2',
                                   optimizer=L_BFGS_B(),
                                   callback=callback_graph)
```

```python
#from IPython.core.debugger import set_trace
```

```python
#import sys
'''
sys.path.append("../Qiskit_Dynamic_Modelling")
sys.path.append("./modelQ")
sys.path.append("./dynamicsQ")
'''
sys.path.append("./sample_data")
from dataloader import *
train_data = np.load('/content/sample_data/train_dataset.npz')
train_inputs = train_data["input"]
train_labels = train_data["labels"]
X_inputs = train_inputs[:50000]
y_labels = train_labels[:50000]
#train_dataset = DynamicsDataset(train_inputs, train_labels)
train_dataset = DynamicsDataset(X_inputs, y_labels)

train_dataloader = torch.utils.data.DataLoader(train_dataset,
                                                 batch_size=1,
                                                 shuffle=True,
                                                 collate_fn=DynamicsDataset.collate_fn,
                                                 pin_memory=True,
                                                 num_workers=1)
print(len(X_inputs))
print(len(y_labels))

```

    50000
    50000

```python
from qiskit import transpile, assemble
class QuanvCircuit:
    """
    This class defines filter circuit of Quanvolution layer
    """
    def __init__(self, kernel_size, backend, shots, threshold):
        # --- Circuit definition start ---
        self.n_qubits = kernel_size ** 2
        self._circuit = qiskit.QuantumCircuit(self.n_qubits)
        self.theta = [qiskit.circuit.Parameter('theta{}'.format(i)) for i in range(self.n_qubits)]

        for i in range(self.n_qubits):
            self._circuit.rx(self.theta[i], i)

        self._circuit.barrier()
        self._circuit.measure_all()
        # ---- Circuit definition end ----
        self.backend   = backend
        self.shots     = shots
        self.threshold = threshold

    def run(self, data):
        # encoding data to parameters
        thetas = [data]

        param_dict = dict()
        for theta in thetas:
            for i in range(self.n_qubits):
                param_dict[self.theta[i]] = theta[i]
        param_binds = [param_dict]

        List = [thetas]
        t_qc = transpile(self._circuit,
                         self.backend)
        qobj = assemble(t_qc,
                        shots=self.shots,
                        parameter_binds = param_binds)
        job = self.backend.run(qobj)
        result = job.result().get_counts()

        # decoding the result
        counts = 0
        for key, val in result.items():
            cnt = sum([int(char) for char in key])
            counts += cnt * val

        probabilities = counts / (self.shots * self.n_qubits)

        return probabilities
```

```python
backend = qiskit.Aer.get_backend('qasm_simulator')
filter_size = 2
circ = QuanvCircuit(filter_size, backend, 100, 127)
#data = torch.tensor([[0, 200], [100, 255]])
#data = torch.tensor([-2.8351264, 1.3314428,  -0.18492305,  0.00792312]).numpy()#1,2,3,4]).numpy()
data = [-2.8351264,  1.3314428,  -0.18492305,  0.00792312]

#
print(data)
#print(data.size())
print(circ.run(data))

circ._circuit.draw()#output='mpl')
```

    [-2.8351264, 1.3314428, -0.18492305, 0.00792312]
    0.3375

<pre style="word-wrap: normal;white-space: pre;background: #fff0;line-height: 1.1;font-family: "Courier New",Courier,monospace">        ┌────────────┐ ░  ░ ┌─┐
   q_0: ┤ Rx(theta0) ├─░──░─┤M├─────────
        ├────────────┤ ░  ░ └╥┘┌─┐
   q_1: ┤ Rx(theta1) ├─░──░──╫─┤M├──────
        ├────────────┤ ░  ░  ║ └╥┘┌─┐
   q_2: ┤ Rx(theta2) ├─░──░──╫──╫─┤M├───
        ├────────────┤ ░  ░  ║  ║ └╥┘┌─┐
   q_3: ┤ Rx(theta3) ├─░──░──╫──╫──╫─┤M├
        └────────────┘ ░  ░  ║  ║  ║ └╥┘
meas: 4/═════════════════════╩══╩══╩══╩═
                             0  1  2  3 </pre>

```python
import qiskit
backend = qiskit.Aer.get_backend('qasm_simulator')
filter_size = 2
circ = QuanvCircuit(filter_size, backend, 100, 127)
```

```python
#for (X_train, y_train) in train_dataloader:
print('Loading Training data')
xVal = []
yVal = []
#count = 0
for (X_train, y_train) in train_dataloader:
    #print('Training Starts')
    #print(X_train.detach().numpy()[0])
    #print(y_train.detach().numpy()[0])
    x = circ.run(X_train.detach().numpy()[0])
    y = circ.run(y_train.detach().numpy()[0])
    #regressor.fit(x, y)

    xVal.append([x])
    yVal.append(y)
    '''
    count += 1

    if (count == 50000):
        print('Loaded data :',count)
        break
    '''
print('x data',len(xVal))
print('y data',len(yVal))

```

    Loading Training data

    ./sample_data/dataloader.py:35: UserWarning: Creating a tensor from a list of numpy.ndarrays is extremely slow. Please consider converting the list to a single numpy.ndarray with numpy.array() before converting to a tensor. (Triggered internally at  ../torch/csrc/utils/tensor_new.cpp:201.)
      batch_x = torch.as_tensor(batch_x)

    x data 50000
    y data 50000

```python
#print(torch.tensor(xVal).detach().numpy())
#print(torch.tensor(yVal).detach().numpy())
import time
print('Training Started')
startTime = time.time()
regressor.fit(torch.tensor(xVal).detach().numpy(), torch.tensor(yVal).detach().numpy())
print('Total Training Time :', (time.time()-startTime))
```

```python
# create empty array for callback to store evaluations of the objective function
'''
objective_func_vals = []
plt.rcParams["figure.figsize"] = (12, 6)

# fit to data
#set_trace()

#import pdb; pdb.set_trace()
#regressor.fit(X, y)
regressor.fit([1], 1)
'''
# return to default figsize
#plt.rcParams["figure.figsize"] = (6, 4)

# score the result
regressor.score(torch.tensor(xVal).detach().numpy(), torch.tensor(yVal).detach().numpy())

```

    -1.0083991836847592

```python
print(objective_func_vals)
```

    [17497.53775066666, 4211.549464196395, 3357.536011807917, 1503.9061848238787, 1483.5268802255184, 1484.4908214588766, 1484.2207121472027, 1484.0932874567195, 1491.3789444947095, 1482.8597319869539, 1484.92946745298, 1481.9600680875417, 1486.2004153809164, 1491.457017724896]


# Collection of Simple Random Sample from Population of Websites

## Simple Random Sample

Below is a program that creates a simple random sample of websites according to Alexa Analytics' Top One Million sites CSV. The function `create_sample` creates a random integer between 0 and 999,999. That number acts as the index for the site we will get from the population CSV (e.g. The site at index 0 is google.com).

Once we have selected a site for our sample, we need to resolve its IP address (note: we will be using only IPv4 addresses as Alexa only offers those in their dataset and all websites should handle IPv4 and IPv6). If we are unable to resolve the address than we will randomly select another site to replace it.

When sampling is completed, the sample dataset (domains and IPv4 addresses) is exported to a CSV file.

```python
from random import randint
import pandas as pd
import socket

# List of top one million sites according to Alexa Analytics/Website Ranking
# https://s3.amazonaws.com/alexa-static/top-1m.csv.zip
top_sites = pd.read_csv('top-1m.csv', header=None)[1]

# n is sample size
n=1000

# Dictionary used for stored sample data
sample = {
    'Website Domain' : [],
    'IPv4 Address' : []
}

def create_sample(n):
    i = 0
    while i < n:
        i += 1
        # Get random number between 0 and 999,999
        random_index = randint(0, len(top_sites) - 1)

        # If the site has not already been selected, add it to our data set
        if not top_sites[random_index] in sample['Website Domain']:
            try:
                # print("\033[0mGetting IPv4 Address for %s..." % top_sites[random_index])
                ipv4 = socket.gethostbyname(top_sites[random_index])
            # If we can't resolve the IP from the host name, replace it with a different host name
            except:
                # print("\033[1mFailed. Selecting new site for sample.")
                i -= 1
                continue
            sample['Website Domain'].append(top_sites[random_index])
            sample['IPv4 Address'].append(ipv4)

create_sample(n)

# Save sample to a CSV file
dataset = pd.DataFrame.from_dict(sample)
dataset.to_csv('website_sample.csv')

dataset
```

# Use Dataset Instead of Creating New Sample
Essentially importing `website_sample.csv` for our dataset so we don't have to create a new sample.

```python
n = 1000
dataset = pd.DataFrame.from_csv('website_sample.csv')
dataset
```

    /home/ari/.anaconda3/lib/python3.6/site-packages/ipykernel_launcher.py:2: FutureWarning: from_csv is deprecated. Please use read_csv(...) instead. Note that some of the default arguments are different, so please refer to the documentation for from_csv when changing your function calls

      Website Domain
      IPv4 Address

      0
      ipos.vn
      94.237.76.49

      1
      sectorul4news.ro
      89.42.219.210

      2
      newprint.ca
      37.218.253.61

      3
      destinationhotels.com
      23.100.83.213

      4
      flutetoday.com
      97.74.55.1

      5
      serialcrush.com
      62.149.142.158

      6
      mybmtc.com
      202.71.129.225

      7
      hmi.edu
      192.249.121.112

      8
      flashkit.com
      70.42.23.121

      9
      fucktubes.xxx
      104.28.23.71

      10
      thetodaypost.com
      104.31.66.246

      11
      avpgalaxy.net
      162.211.84.48

      12
      scwtenor.com
      74.208.236.209

      13
      ecom.ly
      104.18.47.2

      14
      redwolfwildernessadventures.com
      67.59.136.110

      15
      wesounds.com
      107.6.153.170

      16
      bloganten.ru
      92.53.114.3

      17
      karinto.in
      175.134.120.229

      18
      myscopeoutreach.org
      182.160.163.245

      19
      landsurveyor.blogfa.com
      149.56.201.253

      20
      finministry.com
      104.18.41.100

      21
      odeontravel.rs
      195.252.107.131

      22
      greeningtheblue.org
      104.27.147.128

      23
      jerusalemperspective.com
      104.199.115.212

      24
      bazi-oksana.ru
      5.101.152.32

      25
      leupold.com
      52.88.153.55

      26
      cloud9.gg
      23.227.38.32

      27
      goldsgym.com
      162.209.117.196

      28
      tagbox.in
      52.172.54.225

      29
      sexmamki.org
      151.80.209.25

      ...
      ...
      ...

      970
      jb.com.br
      152.199.54.25

      971
      vietadsonline.com
      171.244.34.197

      972
      itftkd.ir
      185.128.81.85

      973
      rouxroamer.com
      104.31.95.99

      974
      filejo.co.kr
      43.255.255.83

      975
      peraichi.com
      54.230.89.244

      976
      hw3d.net
      192.99.14.211

      977
      nekoshop.ru
      37.140.192.198

      978
      maxbestwork.com
      66.199.189.51

      979
      hindihelpguru.com
      199.250.213.223

      980
      exertion-fitness.com
      23.227.38.32

      981
      aktualnacenabytu.sk
      212.57.38.25

      982
      geoequipos.cl
      192.140.57.10

      983
      removenotifications.com
      192.64.119.93

      984
      matbit.net
      5.61.47.250

      985
      homebasedonlinevehiclemarketing.com
      146.66.96.176

      986
      xn--v8j5erc590uusnxox.com
      183.90.253.8

      987
      nflsport.icu
      198.54.121.189

      988
      izithakazelo.blog
      192.0.78.191

      989
      irinabiz.ru
      138.201.199.38

      990
      intercity.pl
      46.174.180.162

      991
      bongacams3.com
      31.192.123.62

      992
      twinstrangers.net
      52.214.239.109

      993
      textgeneratorfont.com
      162.241.133.121

      994
      silversaints.com
      212.188.174.246

      995
      evassmat.com
      104.28.24.228

      996
      mpets.mobi
      136.243.25.36

      997
      londongateway.com
      65.52.130.1

      998
      derangler.shop
      85.236.56.247

      999
      tavirekini.lv
      94.100.11.185

1000 rows × 2 columns

# Determining Proportion of Websites Running AWS

## Proportion of IPv4 addresses owned by AWS

The program takes a list of all IPv4 addresses owned by AWS and compares them to the list of addresses in our sample. AWS does not give a list of IPv4 address but instead gives a subnet of their addresses, this means they've purchased addresses in bulk so they're grouped together. In order to properly compare an IPv4 address to a subnet, python offers a library called `ipaddress` that breaks up subnets and ip addresses into a data format that can easily be compared.

If an address appears in Amazon's IPv4 range (their owned addresses) than the domain associated with the IP address is appended to a list. The list of websites is then exported as a CSV file.

```python
import json, requests, ipaddress

# List of IP Ranges (IPv4 and IPv6) owned by Amazon and used for AWS
# https://ip-ranges.amazonaws.com/ip-ranges.json
aws_ip_ranges = json.loads(requests.get('https://ip-ranges.amazonaws.com/ip-ranges.json').text)

# Determine if given IP address (ip_input) shows uo in AWS IPv4 Range
def check_aws(ip_input):
    # Compare given IP to all AWS IP addresses within AWS IPv4 Subnetwork
    for i in range(len(aws_ip_ranges['prefixes'])):
        # Parse IPv4 address for comparison
        site_ip = ipaddress.ip_address(ip_input)

        # Parse AWS IPv4 Subnet
        aws_subnet = ipaddress.ip_network(aws_ip_ranges['prefixes'][i]['ip_prefix'])

        # If IP is within the AWS IPv4 Range, the website is run on AWS
        if site_ip in aws_subnet:
            return True
    # If the website is not within the range, the
    # website operates independnetly of AWS
    return False

# List of websites using AWS
websites_using_aws = []

def get_aws_domains():

    # Check every IP within our sample against AWS IPv4 Range
    for i in range(len(dataset)):
        if check_aws(dataset['IPv4 Address'][i]):
            websites_using_aws.append(dataset['Website Domain'][i])

get_aws_domains()

# Save dataset of AWS websites to a CSV file
aws_df = pd.DataFrame({'AWS Websites':websites_using_aws})
aws_df.to_csv('websites_using_aws.csv')

aws_df
```

      AWS Websites

      0
      leupold.com

      1
      wanasatime.com

      2
      simplesdental.com

      3
      monetixwallet.com

      4
      10tv.in

      5
      rosedalecenter.com

      6
      margstacobistro.com

      7
      shanghainavi.com

      8
      keaweather.net

      9
      lion.co.nz

      10
      moomii.jp

      11
      figleafapp.com

      12
      maharajamultiplex.in

      13
      conchovalleyhomepage.com

      14
      honkmedia.net

      15
      willowtreeapps.com

      16
      playvod.ma

      17
      tigosports.gt

      18
      araelium.com

      19
      boostnote.io

      20
      echofoodshelf.org

      21
      obonsai.com.br

      22
      atcost.in

      23
      profsnhcadmission.in

      24
      manalonline.com

      25
      nj211.org

      26
      conta.no

      27
      cldmail.co.uk

      28
      obo.se

      29
      soft32.es

      ...
      ...

      31
      juegosmesa.cl

      32
      localone.app

      33
      grjapan.jp

      34
      peachysnaps.com

      35
      knottyladyyarns.com

      36
      amormaturo.com

      37
      hltmag.co.uk

      38
      oldtownportraitgallery.com

      39
      matterport.com

      40
      parkopedia.co.uk

      41
      rehabs.com

      42
      diablomedia.com

      43
      battersea.org.uk

      44
      qmo.org.au

      45
      midlandsb.com

      46
      kuflink.co.uk

      47
      lunns.com

      48
      send2sell.com

      49
      wvi.org

      50
      letsignite.in

      51
      kolkt.com

      52
      lucasmaier.com.br

      53
      ellevatenetwork.com

      54
      completefrance.com

      55
      bellevie-group.com

      56
      via.id

      57
      tweentribune.com

      58
      filesun.net

      59
      peraichi.com

      60
      twinstrangers.net

61 rows × 1 columns

# 1-Proportion Z-Test for Proportion of AWS to non-AWS Websites

## Testing in Python

This is a procedure for completing a one-proportion z-test given the sample dataset and proportion of websites using AWS. First we declare `p` as the claimed market share percentage (The Forbes article claimed AWS has a 31% market share). We calculate `q` and proceed with our success failure condition.

The `assert` lines basically test s/f - if np or qp is < 10 than the program stops and an exception (error) is raised. Because the index values when sampling were random, than we can assume our sample is random.

Next we calculate our z-score value with a one-proportion z-test:
$$
\begin{align}
z = \frac{\hat{p} - p}{\sigma} && \sigma = \sqrt{\frac{pq}{n}}
\end{align}
$$

The code for calculating z-score is `z = ((len(websites_using_aws)/n) - p)/sd`. For standard deviation, it's `sd = math.sqrt((p*q)/2)`.
Once we calculate our z-score, finally we can get our p-value. The library SciPy offers statistics which will allow us to calculate p-value similar to how a calculator would. Once complete, we can do hypothesis testing using a significance level of 5%.

```python
import math
import scipy.stats as st

# Creating initial values from datatset/claim
claimed_marketshare = 0.31

p = claimed_marketshare
q = 1-claimed_marketshare

# Success/Failure Condition, exception raised if np or nq is less than 10
assert n*p >= 10, True
assert n*q >= 10, True

# Calculate Z-Score & P-Value
sd = math.sqrt((p*q)/n)
z = ((len(websites_using_aws)/n) - p)/sd

p_value = st.norm.cdf(z)

print('P: %f\tQ: %f\nNP: %f\tNQ: %f\n\nP-Hat: %f\n\nZ-Score: %f\nP-Value: %f\n'
      % (p, q, n*p, n*q, (len(websites_using_aws)/n), z, p_value))

# Hypothesis Testing
significants_level = 0.05

if p_value <= (significants_level): print('\033[1mReject H0')
else: print('\033[1mFail to reject H0')
```

    P: 0.310000	Q: 0.690000
    NP: 310.000000	NQ: 690.000000

    P-Hat: 0.061000

    Z-Score: -17.025268
    P-Value: 0.000000

    Reject H0

## Interpretation

We've rejected the null-hypothesis as our P-Value is approximately zero. The claim made within the Forbes article is invalid according to this observational study as we can support the alternate hypothesis that Amazon does not have a 31% market share in cloud computing/hosting.

# Confidence Interval for 1-Proportion Sample

## Confidence Interval Based on Z-Test

This is a standard method to produce a confidence interval given the z-score, sample size, claimed population proportions and sample proportions produced by the one-proportion z-test.

We use the following equations to calculate the standard deviation and standard error for our confidence interval:
$$
\begin{align}
\sigma_p = \sqrt{\frac{pq}{n}} && SE_p = \sqrt{\frac{\hat{p}\hat{q}}{n}}
\end{align}
$$

Both $\hat{p}$ and $\hat{q}$ are simply $p$ and $q$ for the sample proportion. Using the z-score we calculated during our hypothesis test as our critical value, we can multipy it by our standard error to produce our margin of error, like so:
$$
ME = Z_c\sqrt{\frac{\hat{p}\hat{q}}{n}}
$$

```python
# Sample p (statistic) and q values
aws_p = len(websites_using_aws)/n
aws_q = 1 - aws_p

# Standard error for the sample proportion
se = math.sqrt((aws_p*aws_q)/n)

# Margin of error
me = z*se

print('Interval: (%f, %f)' % (aws_p + me, aws_p - me))
```

    Interval: (-0.067852, 0.189852)

## Interpretation

I am 95% confident that the true proportion of websites within the top one-million sites population is between -6.78% and 19%. By 95% confident I mean if the above procedures are reproduced with a sample size of 1,000, the proportion of websites within the sample that use AWS as their cloud provider will be between -6.78% and 19%. Because zero lies within our interval, the results of this study can be considered insignificant.


# A quick, practical intro to the Jupyter Notebook

Fernando Perez

## Introduction

The IPython Notebook is an **interactive computing environment** that enables users to author notebook documents that include:
- Live code
- Interactive widgets
- Plots
- Narrative text
- Equations
- Images
- Video

These documents provide a **complete and self-contained record of a computation** that can be converted to various formats and shared with others using email, [Dropbox](http://dropbox.com), version control systems (like git/[GitHub](http://github.com)) or [nbviewer.ipython.org](http://nbviewer.ipython.org).

### Components

The IPython Notebook combines three components:

* **The notebook web application**: An interactive web application for writing and running code interactively and authoring notebook documents.
* **Kernels**: Separate processes started by the notebook web application that runs users' code in a given language and returns output back to the notebook web application. The kernel also handles things like computations for interactive widgets, tab completion and introspection.
* **Notebook documents**: Self-contained documents that contain a representation of all content visible in the notebook web application, including inputs and outputs of the computations, narrative
text, equations, images, and rich media representations of objects. Each notebook document has its own kernel.

## Notebook web application

The notebook web application enables users to:

* **Edit code in the browser**, with automatic syntax highlighting, indentation, and tab completion/introspection.
* **Run code from the browser**, with the results of computations attached to the code which generated them.
* See the results of computations with **rich media representations**, such as HTML, LaTeX, PNG, SVG, PDF, etc.
* Create and use **interactive JavaScript wigets**, which bind interactive user interface controls and visualizations to reactive kernel side computations.
* Author **narrative text** using the [Markdown](https://daringfireball.net/projects/markdown/) markup language.
* Build **hierarchical documents** that are organized into sections with different levels of headings.
* Include mathematical equations using **LaTeX syntax in Markdown**, which are rendered in-browser by [MathJax](http://www.mathjax.org/).

## Kernels

Through IPython's kernel and messaging architecture, the Notebook allows code to be run in a range of different programming languages.  For each notebook document that a user opens, the web application starts a kernel that runs the code for that notebook. Each kernel is capable of running code in a single programming language and there are kernels available in over [100 programming languages](https://github.com/jupyter/jupyter/wiki/Jupyter-kernels).

[IPython](https://github.com/ipython/ipython) is the default kernel, it runs Python code.

Each of these kernels communicate with the notebook web application and web browser using a JSON over ZeroMQ/WebSockets message protocol that is described [here](https://jupyter-client.readthedocs.io/en/latest/messaging.html#messaging). Most users don't need to know about these details, but it helps to understand that "kernels run code."

## Notebook documents

Notebook documents contain the **inputs and outputs** of an interactive session as well as **narrative text** that accompanies the code but is not meant for execution. **Rich output** generated by running code, including HTML, images, video, and plots, is embeddeed in the notebook, which makes it a complete and self-contained record of a computation.

When you run the notebook web application on your computer, notebook documents are just **files on your local filesystem with a `.ipynb` extension**. This allows you to use familiar workflows for organizing your notebooks into folders and sharing them with others using email, Dropbox and version control systems.

Notebooks consist of a **linear sequence of cells**. There are three basic cell types:

* **Code cells:** Input and output of live code that is run in the kernel
* **Markdown cells:** Narrative text with embedded LaTeX equations
* **Raw cells:** Unformatted text that is included, without modification, when notebooks are converted to different formats using nbconvert

Internally, notebook documents are **[JSON](http://en.wikipedia.org/wiki/JSO) data** with **binary values [base64]**(http://en.wikipedia.org/wiki/Base64) encoded. This allows them to be **read and manipulated programmatically** by any programming language. Because JSON is a text format, notebook documents are version control friendly.

**Notebooks can be exported** to different static formats including HTML, reStructeredText, LaTeX, PDF, and slide shows using Jupyter's `nbconvert` utility.

Furthermore, any notebook document available from a **public URL on or GitHub can be shared** via http://nbviewer.jupyter.org. This service loads the notebook document from the URL and renders it as a static web page. The resulting web page may thus be shared with others **without their needing to install Jupyter**.

### Body

The body of a notebook is composed of cells. Each cell contains either markdown, code input, code output, or raw text. Cells can be included in any order and edited at-will, allowing for a large ammount of flexibility for constructing a narrative.

- **Markdown cells** - These are used to build a nicely formatted narrative around the code in the document. The majority of this lesson is composed of markdown cells.

- **Code cells** - These are used to define the computational code in the document. They come in two forms: the *input cell* where the user types the code to be executed, and the *output cell* which is the representation of the executed code. Depending on the code, this representation may be a simple scalar value, or something more complex like a plot or an interactive widget.

- **Raw cells** - These are used when text needs to be included in raw form, without execution or transformation.

This is what the three types of cells look like:

#### Modality

The notebook user interface is *modal*. This means that the keyboard behaves differently depending upon the current mode of the notebook. A notebook has two modes: **edit** and **command**.

**Edit mode** is indicated by a blue cell border and a prompt showing in the editor area. When a cell is in edit mode, you can type into the cell, like a normal text editor.

**Command mode** is indicated by a grey cell background. When in command mode, the structure of the notebook can be modified as a whole, but the text in individual cells cannot be changed. Most importantly, the keyboard is mapped to a set of shortcuts for efficiently performing notebook and cell actions. For example, pressing **`c`** when in command mode, will copy the current cell; no modifier is needed.

Enter edit mode by pressing `Enter` or using the mouse to click on a cell's editor area.

Enter command mode by pressing `Esc` or using the mouse to click *outside* a cell's editor area.

Do not attempt to type into a cell when in command mode; unexpected things will happen!

```python
%pylab inline
plot(rand(100))
```

#### Mouse navigation

The first concept to understand in mouse-based navigation is that **cells can be selected by clicking on them.** The currently selected cell is indicated with a blue outline or gray background depending on whether the notebook is in edit or command mode. Clicking inside a cell's editor area will enter edit mode. Clicking on the prompt or the output area of a cell will enter command mode.

The second concept to understand in mouse-based navigation is that **cell actions usually apply to the currently selected cell**. For example, to run the code in a cell, select it and then click the <button class='btn btn-default btn-xs'></button> button in the toolbar or the **`Run -> Run Selected Cells`** menu item. Similarly, to copy a cell, select it and then click the <button class='btn btn-default btn-xs'></button> button in the toolbar or the **`Edit -> Copy`** menu item. With this simple pattern, it should be possible to perform nearly every action with the mouse.

Markdown cells have one other state which can be modified with the mouse. These cells can either be rendered or unrendered. When they are rendered, a nice formatted representation of the cell's contents will be presented. When they are unrendered, the raw text source of the cell will be presented. To render the selected cell with the mouse, click the <button class='btn btn-default btn-xs'></button> button in the toolbar or the **`Run -> Run Selected Cells`** menu item. To unrender the selected cell, double click on the cell.

#### Keyboard Navigation

The modal user interface of the IPython Notebook has been optimized for efficient keyboard usage. This is made possible by having two different sets of keyboard shortcuts: one set that is active in edit mode and another in command mode.

The most important keyboard shortcuts are **`Enter`**, which enters edit mode, and **`Esc`**, which enters command mode.

In edit mode, most of the keyboard is dedicated to typing into the cell's editor. Thus, in edit mode there are relatively few shortcuts. In command mode, the entire keyboard is available for shortcuts, so there are many more possibilities.

The following shortcuts have been found to be the most useful in day-to-day tasks:

- Basic navigation: **`enter`**, **`shift-enter`**, **`up/k`**, **`down/j`**
- Saving the notebook: **`s`**
- Cell types: **`y`**, **`m`**, **`r`**
- Cell creation: **`a`**, **`b`**
- Cell editing: **`x`**, **`c`**, **`v`**, **`d`**, **`z`**, **`ctrl+shift+-`**
- Kernel operations: **`i`**, **`.`**

You can fully customize JupyterLab's keybindings by accessing the **`Settings -> Advanced Settings Editor`** menu item.

# Running Code

First and foremost, the Jupyter Notebook is an interactive environment for writing and running code. Jupyter is capable of running code in a wide range of languages. However, this notebook, and the default kernel in Jupyter, runs Python code.

## Code cells allow you to enter and run Python code

Run a code cell using `Shift-Enter` or pressing the <button class='btn btn-default btn-xs'></button> button in the toolbar above:

```python
a = 10
```

```python
print(a + 1)
```

Note the difference between the above printing statement and the operation below:

```python
a + 1
```

When a value is *returned* by a computation, it is displayed with a number, that tells you this is the output value of a given cell.  You can later refere to any of these values (should you need one that you forgot to assign to a named variable). The last three are available respectively as auto-generated variables called `_`, `__` and `___` (one, two and three underscores). In addition to these three convenience ones for recent results, you can use `_N`, where N is the number in `[N]`, to access any numbered output.

There are two other keyboard shortcuts for running code:

* `Alt-Enter` runs the current cell and inserts a new one below.
* `Ctrl-Enter` run the current cell and enters command mode.

## Managing the IPython Kernel

Code is run in a separate process called the IPython Kernel.  The Kernel can be interrupted or restarted.  Try running the following cell and then hit the <button class='btn btn-default btn-xs'></button> button in the toolbar above.

```python
import time
time.sleep(10)
```

If the Kernel dies you will be prompted to restart it. Here we call the low-level system libc.time routine with the wrong argument via
ctypes to segfault the Python interpreter:

```python
import sys
from ctypes import CDLL
# This will crash a Linux or Mac system
# equivalent calls can be made on Windows
dll = 'dylib' if sys.platform == 'darwin' else 'so.6'
libc = CDLL("libc.%s" % dll)
libc.time(-1)  # BOOM!!
```

## Cell menu

The "Run" menu has a number of items for running code in different ways, including

* Run Selected Cells
* Run All Cells
* Run Selected Cell or Current Line in Console
* Run All Above Selected Cell
* Run  Selected Cell and All Below
* Restart Kernel and Run All Cells

## Restarting the kernels

The kernel maintains the state of a notebook's computations. You can reset this state by restarting the kernel. This is done by clicking on the <button class='btn btn-default btn-xs'></button> in the toolbar above.

## sys.stdout and sys.stderr

The stdout and stderr streams are displayed as text in the output area.

```python
print("hi, stdout")
```

```python
from __future__ import print_function
print('hi, stderr', file=sys.stderr)
```

## Output is asynchronous

All output is displayed as it is generated in the Kernel: instead of blocking on the execution of the entire cell, output is made available to the Notebook immediately as it is generated by the kernel (even though the whole cell is submitted for execution as a single unit).

If you execute the next cell, you will see the output one piece at a time, not all at the end:

```python
import time, sys
for i in range(8):
    print(i)
    time.sleep(0.5)
```

## Large outputs

To better handle large outputs, the output area can be collapsed. Run the following cell and then click on the vertical blue bar to the left of the output:

```python
for i in range(50):
    print(i)
```

---
# Markdown Cells

Text can be added to IPython Notebooks using Markdown cells.  Markdown is a popular markup language that is a superset of HTML. Its specification can be found here:

<http://daringfireball.net/projects/markdown/>

You can view the source of a cell by double clicking on it, or while the cell is selected in command mode, press `Enter` to edit it. One A cell has been editted, use `Shift-Enter` to re-render it.

## Markdown basics

You can make text *italic* or **bold**.

You can build nested itemized or enumerated lists:

* One
    - Sublist
        - This
  - Sublist
        - That
        - The other thing
* Two
  - Sublist
* Three
  - Sublist

Now another list:

1. Here we go
    1. Sublist
    2. Sublist
2. There we go
3. Now this

You can add horizontal rules:

---

Here is a blockquote:

> Beautiful is better than ugly.
> Explicit is better than implicit.
> Simple is better than complex.
> Complex is better than complicated.
> Flat is better than nested.
> Sparse is better than dense.
> Readability counts.
> Special cases aren't special enough to break the rules.
> Although practicality beats purity.
> Errors should never pass silently.
> Unless explicitly silenced.
> In the face of ambiguity, refuse the temptation to guess.
> There should be one-- and preferably only one --obvious way to do it.
> Although that way may not be obvious at first unless you're Dutch.
> Now is better than never.
> Although never is often better than *right* now.
> If the implementation is hard to explain, it's a bad idea.
> If the implementation is easy to explain, it may be a good idea.
> Namespaces are one honking great idea -- let's do more of those!

And shorthand for links:

[IPython's website](http://ipython.org)

You can add headings using Markdown's syntax:

# Heading 1

# Heading 2

## Heading 2.1

## Heading 2.2

## Embedded code

You can embed code meant for illustration instead of execution in Python:

    def f(x):
        """a docstring"""
        return x**2

or other languages:

    if (i=0; i<n; i++) {
      printf("hello %d\n", i);
      x += 4;
    }

## LaTeX equations

Courtesy of MathJax, you can include mathematical expressions both inline:
$e^{i\pi} + 1 = 0$  and displayed:

$$e^x=\sum_{i=0}^\infty \frac{1}{i!}x^i$$

Use single dolars delimiter for inline math, so `$thisisinline\int math$`  will give $this is inline\int math$, for example to refer to variable within text.

Double dollars `$$\int_0^{2\pi} f(r, \phi) \partial \phi $$` is used for standalone formulas:

$$\int_0^{2\pi} f(r, \phi) \partial \phi $$

## Github flavored markdown (GFM)

The Notebook webapp support Github flavored markdown meaning that you can use triple backticks for code blocks

```python
print "Hello World"
```

```javascript
console.log("Hello World")
```

Gives
```python
print "Hello World"
```

```javascript
console.log("Hello World")
```

And a table like this :

| This | is   |
|------|------|
|   a  | table|

A nice HTML Table

| This | is   |
|------|------|
|   a  | table|

## General HTML

Because Markdown is a superset of HTML you can even add things like HTML tables:

Header 1
Header 2

row 1, cell 1
row 1, cell 2

row 2, cell 1
row 2, cell 2

## Local files

If you have local files in your Notebook directory, you can refer to these files in Markdown cells directly:

    [subdirectory/]<filename>

For example, in the images folder, we have the Python logo:

and a video with the HTML5 video tag:

    <video controls src="images/animation.m4v" />

<video controls src="images/animation.m4v" />

These do not embed the data into the notebook file, and require that the files exist when you are viewing the notebook.

### Security of local files

Note that this means that the IPython notebook server also acts as a generic file server
for files inside the same tree as your notebooks.  Access is not granted outside the
notebook folder so you have strict control over what files are visible, but for this
reason it is highly recommended that you do not run the notebook server with a notebook
directory at a high level in your filesystem (e.g. your home directory).

When you run the notebook in a password-protected manner, local file access is restricted
to authenticated users unless read-only views are active.

---

# Typesetting Equations

The Markdown parser included in IPython is MathJax-aware.  This means that you can freely mix in mathematical expressions using the [MathJax subset of Tex and LaTeX](http://docs.mathjax.org/en/latest/tex.html#tex-support).

You can use single-dollar signs to include inline math, e.g. `$e^{i \pi} = -1$` will render as $e^{i \pi} = -1$, and double-dollars for displayed math:

```
$$
e^x=\sum_{i=0}^\infty \frac{1}{i!}x^i
$$
```

renders as:

$$
e^x=\sum_{i=0}^\infty \frac{1}{i!}x^i
$$

You can also use more complex LaTeX constructs for displaying math, such as:

```
\begin{align}
\dot{x} & = \sigma(y-x) \\
\dot{y} & = \rho x - y - xz \\
\dot{z} & = -\beta z + xy
\end{align}
```

to produce the Lorenz equations:

\begin{align}
\dot{x} & = \sigma(y-x) \\
\dot{y} & = \rho x - y - xz \\
\dot{z} & = -\beta z + xy
\end{align}

Please refer to the MathJax documentation for a comprehensive description of which parts of LaTeX are supported, but note that Jupyter's support for LaTeX is **limited to mathematics**. You can **not** use LaTeX typesetting constrcuts for text or document structure, for text formatting you should restrict yourself to Markdown syntax.