#!/usr/bin/env python
# coding: utf-8

#
# #  Tutorial 1: "What" models
# __Content creators:__ Matt Laporte, Byron Galbraith, Konrad Kording
#
# __Content reviewers:__ Dalin Guo, Aishwarya Balwani, Madineh Sarvestani, Maryam Vaziri-Pashkam, Michael Waskom
#
# We would like to acknowledge [Steinmetz _et al._ (2019)](https://www.nature.com/articles/s41586-019-1787-x) for sharing their data, a subset of which is used here.
#

# ___
# # Tutorial Objectives
# This is tutorial 1 of a 3-part series on different flavors of models used to understand neural data. In this tutorial we will explore 'What' models, used to describe the data. To understand what our data looks like, we will visualize it in different ways. Then we will compare it to simple mathematical models. Specifically, we will:
#
# - Load a dataset with spiking activity from hundreds of neurons and understand how it is organized
# - Make plots to visualize characteristics of the spiking activity across the population
# - Compute the distribution of "inter-spike intervals" (ISIs) for a single neuron
# - Consider several formal models of this distribution's shape and fit them to the data "by hand"

# In[ ]:

#@title Video 1: "What" Models
from IPython.display import YouTubeVideo
video = YouTubeVideo(id='KgqR_jbjMQg', width=854, height=480, fs=1)
print("Video available at https://youtube.com/watch?v=" + video.id)
video

# # Setup
#
#

# Python requires you to explictly "import" libraries before their functions are available to use. We will always specify our imports at the beginning of each notebook or script.

# In[ ]:

import numpy as np
import matplotlib.pyplot as plt

# Tutorial notebooks typically begin with several set-up steps that are hidden from view by default.
#
# **Important:** Even though the code is hidden, you still need to run it so that the rest of the notebook can work properly. Step through each cell, either by pressing the play button in the upper-left-hand corner or with a keyboard shortcut (`Cmd-Return` on a Mac, `Ctrl-Enter` otherwise). A number will appear inside the brackets (e.g. `[3]`) to tell you that the cell was executed and what order that happened in.
#
# If you are curious to see what is going on inside each cell, you can double click to expand. Once expanded, double-click the white space to the right of the editor to collapse again.

# In[ ]:

#@title Figure Settings
import ipywidgets as widgets #interactive display

get_ipython().run_line_magic('matplotlib', 'inline')
get_ipython().run_line_magic('config', "InlineBackend.figure_format = 'retina'")
plt.style.use("https://raw.githubusercontent.com/NeuromatchAcademy/course-content/master/nma.mplstyle")

# In[ ]:

#@title Helper functions

#@markdown Most of the tutorials make use of helper functions
#@markdown to simplify the code that you need to write. They are defined here.

# Please don't edit these, or worry about understanding them now!

def restrict_spike_times(spike_times, interval):
  """Given a spike_time dataset, restrict to spikes within given interval.

  Args:
    spike_times (sequence of np.ndarray): List or array of arrays,
      each inner array has spike times for a single neuron.
    interval (tuple): Min, max time values; keep min <= t < max.

  Returns:
    np.ndarray: like `spike_times`, but only within `interval`
  """
  interval_spike_times = []
  for spikes in spike_times:
    interval_mask = (spikes >= interval[0]) & (spikes < interval[1])
    interval_spike_times.append(spikes[interval_mask])
  return np.array(interval_spike_times, object)

# In[ ]:

#@title Data retrieval
#@markdown This cell downloads the example dataset that we will use in this tutorial.
import io
import requests
r = requests.get('https://osf.io/sy5xt/download')
if r.status_code != 200:
  print('Failed to download data')
else:
  spike_times = np.load(io.BytesIO(r.content), allow_pickle=True)['spike_times']

# ---
#
# # Section 1: Exploring the Steinmetz dataset
#
# In this tutorial we will explore the structure of a neuroscience dataset.
#
# We consider a subset of data from a study of [Steinmetz _et al._ (2019)](https://www.nature.com/articles/s41586-019-1787-x). In this study, Neuropixels probes were implanted in the brains of mice. Electrical potentials were measured by hundreds of electrodes along the length of each probe. Each electrode's measurements captured local variations in the electric field due to nearby spiking neurons. A spike sorting algorithm was used to infer spike times and cluster spikes according to common origin: a single cluster of sorted spikes is causally attributed to a single neuron.
#
# In particular, a single recording session of spike times and neuron assignments was loaded and assigned to `spike_times` in the preceding setup.
#
# Typically a dataset comes with some information about its structure. However, this information may be incomplete. You might also apply some transformations or "pre-processing" to create a working representation of the data of interest, which might go partly undocumented depending on the circumstances. In any case it is important to be able to use the available tools to investigate unfamiliar aspects of a data structure.
#
# Let's see what our data looks like...

# ## Section 1.1: Warming up with `spike_times`

# What is the Python type of our variable?

# In[ ]:

type(spike_times)

# You should see `numpy.ndarray`, which means that it's a normal NumPy array.
#
# If you see an error message, it probably means that you did not execute the set-up cells at the top of the notebook. So go ahead and make sure to do that.
#
# Once everything is running properly, we can ask the next question about the dataset: what's its shape?

# In[ ]:

spike_times.shape

# There are 734 entries in one dimension, and no other dimensions. What is the Python type of the first entry, and what is *its* shape?

# In[ ]:

idx = 0
print(
  type(spike_times[idx]),
  spike_times[idx].shape,
  sep="\n",
)

# It's also a NumPy array with a 1D shape! Why didn't this show up as a second dimension in the shape of `spike_times`? That is, why not `spike_times.shape == (734, 826)`?
#
# To investigate, let's check another entry.

# In[ ]:

idx = 321
print(
  type(spike_times[idx]),
  spike_times[idx].shape,
  sep="\n",
)

# It's also a 1D NumPy array, but it has a different shape. Checking the NumPy types of the values in these arrays, and their first few elements, we see they are composed of floating point numbers (not another level of `np.ndarray`):

# In[ ]:

i_neurons = [0, 321]
i_print = slice(0, 5)

for i in i_neurons:
  print(
    "Neuron {}:".format(i),
    spike_times[i].dtype,
    spike_times[i][i_print],
    "\n",
    sep="\n"
  )

# Note that this time we've checked the NumPy `dtype` rather than the Python variable type. These two arrays contain floating point numbers ("floats") with 32 bits of precision.
#
# The basic picture is coming together:
# - `spike_times` is 1D, its entries are NumPy arrays, and its length is the number of neurons (734): by indexing it, we select a subset of neurons.
# - An array in `spike_times` is also 1D and corresponds to a single neuron; its entries are floating point numbers, and its length is the number of spikes attributed to that neuron. By indexing it, we select a subset of spike times for that neuron.
#
# Visually, you can think of the data structure as looking something like this:
#
# ```
# | . . . . . |
# | . . . . . . . . |
# | . . . |
# | . . . . . . . |
# ```
#
# Before moving on, we'll calculate and store the number of neurons in the dataset and the number of spikes per neuron:

# In[ ]:

n_neurons = len(spike_times)
total_spikes_per_neuron = [len(spike_times_i) for spike_times_i in spike_times]

print(f"Number of neurons: {n_neurons}")
print(f"Number of spikes for first five neurons: {total_spikes_per_neuron[:5]}")

# In[ ]:

#@title Video 2: Exploring the dataset
from IPython.display import YouTubeVideo
video = YouTubeVideo(id='oHwYWUI_o1U', width=854, height=480, fs=1)
print("Video available at https://youtube.com/watch?v=" + video.id)
video

# ## Section 1.2: Getting warmer: counting and plotting total spike counts
#
# As we've seen, the number of spikes over the entire recording is variable between neurons. More generally, some neurons tend to spike more than others in a given period. Lets explore what the distribution of spiking looks like across all the neurons in the dataset.

# Are most neurons "loud" or "quiet", compared to the average? To see, we'll define bins of constant width in terms of total spikes and count the neurons that fall in each bin. This is known as a "histogram".
#
# You can plot a histogram with the matplotlib function `plt.hist`. If you just need to compute it, you can use the numpy function `np.histogram` instead.

# In[ ]:

plt.hist(total_spikes_per_neuron, bins=50, histtype="stepfilled")
plt.xlabel("Total spikes per neuron")
plt.ylabel("Number of neurons");

# Let's see what percentage of neurons have a below-average spike count:

# In[ ]:

mean_spike_count = np.mean(total_spikes_per_neuron)
frac_below_mean = (total_spikes_per_neuron < mean_spike_count).mean()
print(f"{frac_below_mean:2.1%} of neurons are below the mean")

# We can also see this by adding the average spike count to the histogram plot:

# In[ ]:

plt.hist(total_spikes_per_neuron, bins=50, histtype="stepfilled")
plt.xlabel("Total spikes per neuron")
plt.ylabel("Number of neurons")
plt.axvline(mean_spike_count, color="orange", label="Mean neuron")
plt.legend();

# This shows that the majority of neurons are relatively "quiet" compared to the mean, while a small number of neurons are exceptionally "loud": they must have spiked more often to reach a large count.
#
# ### Exercise 1: Comparing mean and median neurons
#
# If the mean neuron is more active than 68% of the population, what does that imply about the relationship between the mean neuron and the median neuron?
#
# *Exercise objective:* Reproduce the plot above, but add the median neuron.
#

# In[ ]:

# To complete the exercise, fill in the missing parts (...) and uncomment the code

median_spike_count = ...  # Hint: Try the function np.median

# plt.hist(..., bins=50, histtype="stepfilled")
# plt.axvline(..., color="limegreen", label="Median neuron")
# plt.axvline(mean_spike_count, color="orange", label="Mean neuron")
# plt.xlabel("Total spikes per neuron")
# plt.ylabel("Number of neurons")
# plt.legend()

# [*Click for solution*](https://github.com/NeuromatchAcademy/course-content/tree/master//tutorials/W1D1_ModelTypes/solutions/W1D1_Tutorial1_Solution_b3411d5d.py)
#
# *Example output:*
#
#
#
#

#
# *Bonus:* The median is the 50th percentile. What about other percentiles? Can you show the interquartile range on the histogram?

# ---
#
# # Section 2: Visualizing neuronal spiking activity

# ## Section 2.1: Getting a subset of the data
#
# Now we'll visualize trains of spikes. Because the recordings are long, we will first define a short time interval and restrict the visualization to only the spikes in this interval. We defined a utility function, `restrict_spike_times`, to do this for you. If you call `help()` on the function, it will tell you a little bit about itself:

# In[ ]:

help(restrict_spike_times)

# In[ ]:

t_interval = (5, 15)  # units are seconds after start of recording
interval_spike_times = restrict_spike_times(spike_times, t_interval)

# Is this a representative interval? What fraction of the total spikes fall in this interval?

# In[ ]:

original_counts = sum([len(spikes) for spikes in spike_times])
interval_counts = sum([len(spikes) for spikes in interval_spike_times])
frac_interval_spikes = interval_counts / original_counts
print(f"{frac_interval_spikes:.2%} of the total spikes are in the interval")

# How does this compare to the ratio between the interval duration and the experiment duration? (What fraction of the total time is in this interval?)
#
# We can approximate the experiment duration by taking the minimum and maximum spike time in the whole dataset. To do that, we "concatenate" all of the neurons into one array and then use `np.ptp` ("peak-to-peak") to get the difference between the maximum and minimum value:

# In[ ]:

spike_times_flat = np.concatenate(spike_times)
experiment_duration = np.ptp(spike_times_flat)
interval_duration = t_interval[1] - t_interval[0]

frac_interval_time = interval_duration / experiment_duration
print(f"{frac_interval_time:.2%} of the total time is in the interval")

# These two values—the fraction of total spikes and the fraction of total time—are similar. This suggests the average spike rate of the neuronal population is not very different in this interval compared to the entire recording.
#
# ## Section 2.2: Plotting spike trains and rasters
#
# Now that we have a representative subset, we're ready to plot the spikes, using the matplotlib `plt.eventplot` function. Let's look at a single neuron first:

# In[ ]:

neuron_idx = 1
plt.eventplot(interval_spike_times[neuron_idx], color=".2")
plt.xlabel("Time (s)")
plt.yticks([]);

# We can also plot multiple neurons. Here are three:

# In[ ]:

neuron_idx = [1, 11, 51]
plt.eventplot(interval_spike_times[neuron_idx], color=".2")
plt.xlabel("Time (s)")
plt.yticks([]);

# This makes a "raster" plot, where the spikes from each neuron appear in a different row.
#
# Plotting a large number of neurons can give you a sense for the characteristics in the population. Let's show every 5th neuron that was recorded:

# In[ ]:

neuron_idx = np.arange(0, len(spike_times), 5)
plt.eventplot(interval_spike_times[neuron_idx], color=".2")
plt.xlabel("Time (s)")
plt.yticks([]);

# *Question*: How does the information in this plot relate to the histogram of total spike counts that you saw above?

# In[ ]:

#@title Video 3: Visualizing activity
from IPython.display import YouTubeVideo
video = YouTubeVideo(id='QGA5FCW7kkA', width=854, height=480, fs=1)
print("Video available at https://youtube.com/watch?v=" + video.id)
video

# ---
#
# # Section 3: Inter-spike intervals and their distributions

# Given the ordered arrays of spike times for each neuron in `spike_times`, which we've just visualized, what can we ask next?
#
# Scientific questions are informed by existing models. So, what knowledge do we already have that can inform questions about this data?
#
# We know that there are physical constraints on neuron spiking. Spiking costs energy, which the neuron's cellular machinery can only obtain at a finite rate. Therefore neurons should have a refractory period: they can only fire as quickly as their metabolic processes can support, and there is a minimum delay between consecutive spikes of the same neuron.
#
# More generally, we can ask "how long does a neuron wait to spike again?" or "what is the longest a neuron will wait?" Can we transform spike times into something else, to address questions like these more directly?
#
# We can consider the inter-spike times (or interspike intervals: ISIs). These are simply the time differences between consecutive spikes of the same neuron.
#
# ### Exercise 2: Plot the distribution of ISIs for a single neuron
#
# *Exercise objective:* make a histogram, like we did for spike counts, to show the distribution of ISIs for one of the neurons in the dataset.
#
# Do this in three steps:
#
# 1. Extract the spike times for one of the neurons
# 2. Compute the ISIs (the amount of time between spikes, or equivalently, the difference between adjacent spike times)
# 3. Plot a histogram with the array of individual ISIs

# In[ ]:

def compute_single_neuron_isis(spike_times, neuron_idx):
  """Compute a vector of ISIs for a single neuron given spike times.

  Args:
    spike_times (list of 1D arrays): Spike time dataset, with the first
      dimension corresponding to different neurons.
    neuron_idx (int): Index of the unit to compute ISIs for.

  Returns:
    isis (1D array): Duration of time between each spike from one neuron.
  """
  #############################################################################
  # Students: Fill in missing code (...) and comment or remove the next line
  raise NotImplementedError("Exercise: compute single neuron ISIs")
  #############################################################################

  # Extract the spike times for the specified neuron
  single_neuron_spikes = ...

  # Compute the ISIs for this set of spikes
  # Hint: the function np.diff computes discrete differences along an array
  isis = ...

  return isis

# Uncomment the following lines when you are ready to test your function
# single_neuron_isis = compute_single_neuron_isis(spike_times, neuron_idx=283)
# plt.hist(single_neuron_isis, bins=50, histtype="stepfilled")
# plt.axvline(single_neuron_isis.mean(), color="orange", label="Mean ISI")
# plt.xlabel("ISI duration (s)")
# plt.ylabel("Number of spikes")
# plt.legend()

# [*Click for solution*](https://github.com/NeuromatchAcademy/course-content/tree/master//tutorials/W1D1_ModelTypes/solutions/W1D1_Tutorial1_Solution_4792dbfa.py)
#
# *Example output:*
#
#
#
#

# ---
#
# In general, the shorter ISIs are predominant, with counts decreasing rapidly (and smoothly, more or less) with increasing ISI. However, counts also rapidly decrease to zero with _decreasing_ ISI, below the maximum of the distribution (8-11 ms). The absence of these very low ISIs agrees with the refractory period hypothesis: the neuron cannot fire quickly enough to populate this region of the ISI distribution.
#
# Check the distributions of some other neurons. To resolve various features of the distributions, you might need to play with the value of `n_bins`. Using too few bins might smooth over interesting details, but if you use too many bins, the random variability will start to dominate.
#
# You might also want to restrict the range to see the shape of the distribution when focusing on relatively short or long ISIs. *Hint:* `plt.hist` takes a `range` argument

# ---
#
# # Section 4: What is the functional form of an ISI distribution?

# In[ ]:

#@title Video 4: ISI distribution
from IPython.display import YouTubeVideo
video = YouTubeVideo(id='DHhM80MOTe8', width=854, height=480, fs=1)
print("Video available at https://youtube.com/watch?v=" + video.id)
video

# The ISI histograms seem to follow continuous, monotonically decreasing functions above their maxima. The function is clearly non-linear. Could it belong to a single family of functions?
#
# To motivate the idea of using a mathematical function to explain physiological phenomena, let's define a few different function forms that we might expect the relationship to follow: exponential, inverse, and linear.

# In[ ]:

def exponential(xs, scale, rate, x0):
  """A simple parametrized exponential function, applied element-wise.

  Args:
    xs (np.ndarray or float): Input(s) to the function.
    scale (float): Linear scaling factor.
    rate (float): Exponential growth (positive) or decay (negative) rate.
    x0 (float): Horizontal offset.

  """
  ys = scale * np.exp(rate * (xs - x0))
  return ys

def inverse(xs, scale, x0):
  """A simple parametrized inverse function (`1/x`), applied element-wise.

  Args:
    xs (np.ndarray or float): Input(s) to the function.
    scale (float): Linear scaling factor.
    x0 (float): Horizontal offset.

  """
  ys = scale / (xs - x0)
  return ys

def linear(xs, slope, y0):
  """A simple linear function, applied element-wise.

  Args:
    xs (np.ndarray or float): Input(s) to the function.
    slope (float): Slope of the line.
    y0 (float): y-intercept of the line.

  """
  ys = slope * xs + y0
  return ys

# ### Interactive Demo: ISI functions explorer
#
# Here is an interactive demo where you can vary the parameters of these functions and see how well the resulting outputs correspond to the data. Adjust the parameters by moving the sliders and see how close you can get the lines to follow the falling curve of the histogram. This will give you a taste of what you're trying to do when you *fit a model* to data.
#
# "Interactive demo" cells have hidden code that defines an interface where you can play with the parameters of some function using sliders. You don't need to worry about how the code works – but you do need to **run the cell** to enable the sliders.
#

# In[ ]:

#@title

#@markdown Be sure to run this cell to enable the demo
# Don't worry about understanding this code! It's to setup an interactive plot.
single_neuron_idx = 283
single_neuron_spikes = spike_times[single_neuron_idx]
single_neuron_isis = np.diff(single_neuron_spikes)

counts, edges = np.histogram(
  single_neuron_isis,
  bins=50,
  range=(0, single_neuron_isis.max())
)

functions = dict(
  exponential=exponential,
  inverse=inverse,
  linear=linear,
)

colors = dict(
  exponential="C1",
  inverse="C2",
  linear="C4",
)

@widgets.interact(
  exp_scale=widgets.FloatSlider(1000, min=0, max=20000, step=250),
  exp_rate=widgets.FloatSlider(-10, min=-200, max=50, step=1),
  exp_x0=widgets.FloatSlider(0.1, min=-0.5, max=0.5, step=0.005),
  inv_scale=widgets.FloatSlider(1000, min=0, max=3e2, step=10),
  inv_x0=widgets.FloatSlider(0, min=-0.2, max=0.2, step=0.01),
  lin_slope=widgets.FloatSlider(-1e5, min=-6e5, max=1e5, step=10000),
  lin_y0=widgets.FloatSlider(10000, min=0, max=4e4, step=1000),
)
def fit_plot(
  exp_scale=1000, exp_rate=-10, exp_x0=0.1,
  inv_scale=1000, inv_x0=0,
  lin_slope=-1e5, lin_y0=2000,
):
  """Helper function for plotting function fits with interactive sliders."""
  func_params = dict(
    exponential=(exp_scale, exp_rate, exp_x0),
    inverse=(inv_scale, inv_x0),
    linear=(lin_slope, lin_y0),
  )
  f, ax = plt.subplots()
  ax.fill_between(edges[:-1], counts, step="post", alpha=.5)
  xs = np.linspace(1e-10, edges.max())
  for name, function in functions.items():
    ys = function(xs, *func_params[name])
    ax.plot(xs, ys, lw=3, color=colors[name], label=name);
  ax.set(
      xlim=(edges.min(), edges.max()),
      ylim=(0, counts.max() * 1.1),
      xlabel="ISI (s)",
      ylabel="Number of spikes",
  )
  ax.legend()

# In[ ]:

#@title Video 5: Fitting models by hand
from IPython.display import YouTubeVideo
video = YouTubeVideo(id='uW2HDk_4-wk', width=854, height=480, fs=1)
print("Video available at https://youtube.com/watch?v=" + video.id)
video

# # Summary
#
# In this tutorial, we loaded some neural data and poked at it to understand how the dataset is organized. Then we made some basic plots to visualize (1) the average level of activity across the population and (2) the distribution of ISIs for an individual neuron. In the very last bit, we started to think about using mathematical formalisms to understand or explain some physiological phenomenon. All of this only allowed us to understand "What" the data looks like.
#
# This is the first step towards developing models that can tell us something about the brain. That's what we'll focus on in the next two tutorials.


# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.11.3
#   kernelspec:
#     display_name: Python 3
#     name: python3
# ---

# + [markdown] id="view-in-github" colab_type="text"
#

# + [markdown] id="24_OboyL7tqe"
# # Introduction to colab
#
# Kevin Murphy, June 2021.
#
# Colab is Google's version of Jupyter notebooks, but has the following advantages:
# - it runs in the cloud, not locally, so you can use it from a cheap laptop, such as a Chromebook.
# - The notebook is saved in your Google drive, so you can share your notebook with someone else and work on it collaboratively.
# - it has nearly all of the packages you need for doing ML pre-installed
# - it gives you free access to GPUs
# - it has a [file editor](https://colab.research.google.com/github/probml/pyprobml/blob/master/book1/supplements/colab_intro.ipynb#scrollTo=DdXlYCe1AlJa&line=7&uniqifier=1), so you can separate your code from the output of your code, as with other IDEs, such as [Jupyter lab](https://jupyterlab.readthedocs.io/en/stable/).
# - it has various other useful features, such as collapsible sections (cf. code folding), and ways to specify parameters to your functions via [various GUI widgets](https://colab.research.google.com/notebooks/forms.ipynb) for use by non-programmers. (You can automatically execute  parameterized notebooks with different parameters using [papermill](https://papermill.readthedocs.io/en/latest/).)
#
# More details can be found in the [official introduction](https://colab.research.google.com/notebooks/intro.ipynb). Below we describe a few more tips and tricks, focusing on methods that I have found useful when developing the book. (More advanced tricks can be found in [this blog post](https://amitness.com/2020/06/google-colaboratory-tips/) and [this blog post](https://medium.com/@robertbracco1/configuring-google-colab-like-a-pro-d61c253f7573#4cf4).)
#

# + colab={"base_uri": "https://localhost:8080/"} id="ZjFsGQJ41k32" outputId="ba36c760-3d87-4b96-ea62-0fe44d308d95"
IS_COLAB = ('google.colab' in str(get_ipython()))
print(IS_COLAB)

# + id="B4KQOCig_xf1"
# Standard Python libraries
from __future__ import absolute_import, division, print_function, unicode_literals

import os
import time
import glob

from typing import Any, Callable, Dict, Iterator, Mapping, Optional, Sequence, Tuple

import numpy as np
import matplotlib.pyplot as plt

# + [markdown] id="8lAbDqny-vDq"
# # How to import and use standard libraries

# + [markdown] id="XHO2_uKXMbD4"
# Colab comes with most of the packages we need pre-installed.
# You can see them all using this command.
#
#
#
#

# + colab={"base_uri": "https://localhost:8080/"} id="0C54AJx40vJq" outputId="accb047b-6ac3-4e3c-97c5-6f3e1ccf7823"
# !pip list -v

# + [markdown] id="U9PghW_NT1HY"
# To install a new package called 'foo', use the following (see [this page](https://colab.research.google.com/notebooks/snippets/importing_libraries.ipynb) for details):
#
# ```
# # # !pip install foo
# ```
#
#

# + [markdown] id="fOBdg02-_Jws"
# ## Numpy

# + id="AzP2LAtN_L1m" colab={"base_uri": "https://localhost:8080/"} outputId="cbdc24bf-ea76-4192-c17d-a6d97c14f322"
import numpy as np
np.set_printoptions(precision=3)

A = np.random.randn(2,3)
print(A)

# + [markdown] id="76jPgsuk_1IP"
# ## Pandas

# + id="GimloDqo_4No" colab={"base_uri": "https://localhost:8080/", "height": 197} outputId="1ddca722-d652-4b44-c4e0-982c722dabeb"
import pandas as pd
pd.set_option('precision', 2) # 2 decimal places
pd.set_option('display.max_rows', 20)
pd.set_option('display.max_columns', 30)
pd.set_option('display.width', 100) # wide windows

url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data'
column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight',
                'Acceleration', 'Year', 'Origin', 'Name']
df = pd.read_csv(url, names=column_names, sep='\s+', na_values="?")

df.head()

# + [markdown] id="hUCC261x_7zZ"
# ## Sklearn

# + id="RCSwx_lE_7Jn" colab={"base_uri": "https://localhost:8080/", "height": 285} outputId="4036c988-bb39-4ad4-d9af-f45bc6330231"
import sklearn

from sklearn.datasets import load_iris
iris = load_iris()
# Extract numpy arrays
X = iris.data
y = iris.target

import matplotlib.pyplot as plt
plt.scatter(X[:,0], X[:,1])

# + [markdown] id="PJXF4csdBhsN"
# ## JAX

# + id="8JiSxcJJ79Bv" colab={"base_uri": "https://localhost:8080/"} outputId="97f3ebb7-93b8-4255-bccb-781d01ccb4cc"
# JAX (https://github.com/google/jax)

import jax
import jax.numpy as jnp
A = jnp.zeros((3,3))

# Check if JAX is using GPU
print("jax backend {}".format(jax.lib.xla_bridge.get_backend().platform))

# + [markdown] id="l99YLyorBdYE"
# ## Tensorflow

# + colab={"base_uri": "https://localhost:8080/"} id="StpReaSICLUm" outputId="5dfac104-8e5a-49cd-8400-eadb56650acd"

import tensorflow as tf
from tensorflow import keras
assert tf.__version__ >= "2.0"

print("tf version {}".format(tf.__version__))
print([d for d in tf.config.list_physical_devices()])

if not tf.config.list_physical_devices('GPU'):
    print("No GPU was detected. DNNs can be very slow without a GPU.")
    if IS_COLAB:
        print("Go to Runtime > Change runtime and select a GPU hardware accelerator.")

# + [markdown] id="grUUK1GrBfIY"
# ## PyTorch

# + id="Oi4Zmzla73A_" colab={"base_uri": "https://localhost:8080/"} outputId="558ed73a-5dfe-491a-be1c-64c949283e48"

import torch
import torchvision
print("torch version {}".format(torch.__version__))
if torch.cuda.is_available():
    print(torch.cuda.get_device_name(0))
else:
    print("Torch cannot find GPU")

# + [markdown] id="M4ayVFuc0FD9"
# # Plotting
#
# Colab has excellent support for plotting. We give some examples below.

# + [markdown] id="ENCT0EqifCDO"
# ## Static plots
#
# Colab lets you make static plots using matplotlib, as shown below.
# Note that plots are displayed inline by default, so
# ```
# # # %matplotlib inline
# ```
# is not needed.
#

# + colab={"base_uri": "https://localhost:8080/", "height": 295} id="j_k4tv4D1VaC" outputId="e1d38639-8a61-43dd-9c85-9091dc2d1f0d"
import matplotlib.pyplot as plt
plt.figure()
plt.plot(range(10))
plt.title('my plot')
plt.xlabel('x axis')
plt.savefig('myplot.png')

# + [markdown] id="eUCPm29t6VZf"
# ## Seaborn
#
# Seaborn is a library that makes matplotlib results look prettier. We can also update font size for plots, to make them more suitable for inclusion in papers.

# + id="1w9nRoF96cxy"
import matplotlib.pyplot as plt
import seaborn
import seaborn as sns
seaborn.set()
seaborn.set_style("whitegrid")

# Font sizes
SIZE_SMALL = 14
SIZE_MEDIUM = 18
SIZE_LARGE = 24

# https://stackoverflow.com/a/39566040
plt.rc('font', size=SIZE_SMALL)          # controls default text sizes
plt.rc('axes', titlesize=SIZE_SMALL)     # fontsize of the axes title
plt.rc('axes', labelsize=SIZE_SMALL)     # fontsize of the x and y labels
plt.rc('xtick', labelsize=SIZE_SMALL)    # fontsize of the tick labels
plt.rc('ytick', labelsize=SIZE_SMALL)    # fontsize of the tick labels
plt.rc('legend', fontsize=SIZE_SMALL)    # legend fontsize
plt.rc('figure', titlesize=SIZE_LARGE)   # fontsize of the figure title

# + id="LjXEXYe17I2t" colab={"base_uri": "https://localhost:8080/", "height": 307} outputId="a7e2ae2d-67d9-4270-f981-40700c6a9f06"
plt.figure()
plt.plot(range(10))
plt.title('my plot')
plt.xlabel('x axis')
plt.savefig('myplot.png')

# + [markdown] id="iFwo8LA4fIh9"
# ## Interactive plots
#
# Colab also lets you create interactive plots using various javascript libraries - see [here](https://colab.research.google.com/notebooks/charts.ipynb#scrollTo=QSMmdrrVLZ-N) for details.
#
# Below we illustrate how to use the [bokeh library](https://docs.bokeh.org/en/latest/index.html) to create an interactive plot of a  pandas time series, where if you mouse over the plot, it shows the corresponding (x,y) coordinates. (Another option is [plotly](https://plotly.com/graphing-libraries/).)

# + id="SiKRxZyo3Fa1"
import pandas as pd
import numpy as np
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
from bokeh.models import ColumnDataSource, HoverTool

# Call once to configure Bokeh to display plots inline in the notebook.
output_notebook()

# + id="7_4CJ3mQhLNa" colab={"base_uri": "https://localhost:8080/", "height": 616} outputId="88eb0172-352f-46bd-8303-1baf898e0a79"

np.random.seed(0)
dates = pd.date_range(start='2018-04-24', end='2018-08-27')
N = len(dates)
vals = np.random.standard_t(1, size=N)
dd = pd.DataFrame({'vals': vals, 'dates': dates}, index=dates)
dd['days'] = dd.dates.dt.strftime("%Y-%m-%d")

source = ColumnDataSource(dd)
hover = HoverTool(tooltips=[("Date", "@days"),
                            ("vals", "@vals")],
)
p = figure( x_axis_type="datetime")
p.line(x='dates', y='vals', source=source)
p.add_tools(hover)
show(p)

# + [markdown] id="nHGH3o_R28G9"
# We can also make plots that can you pan and zoom into.

# + id="q1jy_cQl3AYc" colab={"base_uri": "https://localhost:8080/", "height": 616} outputId="ae8fc2f4-a7e5-48e9-f8f0-f9fd32331ded"
N = 4000
np.random.seed(0)
x = np.random.random(size=N) * 100
y = np.random.random(size=N) * 100
radii = np.random.random(size=N) * 1.5
colors = ["#%02x%02x%02x" % (r, g, 150) for r, g in zip(np.floor(50+2*x).astype(int), np.floor(30+2*y).astype(int))]

p = figure()
p.circle(x, y, radius=radii, fill_color=colors, fill_alpha=0.6, line_color=None)
show(p)

# + [markdown] id="FwIG5xw8ad8I"
# ## Viewing an image file
#
# You can either use PIL or OpenCV to display (and manipulate) images.
# According to [this notebook](https://www.kaggle.com/vfdev5/pil-vs-opencv), OpenCV is faster, but for a small number of images, it doesn't really matter.

# + id="AqitJZE1bAi8" colab={"base_uri": "https://localhost:8080/", "height": 514} outputId="6e8ebecb-c6ab-45c6-bb2b-782585a3d648"
from PIL import Image
import requests
from io import BytesIO
#url = "https://github.com/probml/probml-notebooks/blob/master/images/cat_dog.jpg?raw=true"
url = "https://raw.githubusercontent.com/probml/probml-notebooks/main/images/cat_dog.jpg"

response = requests.get(url)
img = Image.open(BytesIO(response.content))
print(type(img))
display(img)

# + id="t0EbaZr9bSZL"
# #!wget https://github.com/probml/probml-notebooks/blob/master/images/cat_dog.jpg?raw=true -q -O cat_dog.jpg
# !wget https://raw.githubusercontent.com/probml/probml-notebooks/main/images/cat_dog.jpg -q -O cat_dog.jpg

# + id="Hbb1T75kuaPG" outputId="3c50ec45-c0a7-4268-be21-b6a90fa90467" colab={"base_uri": "https://localhost:8080/"}
# !ls -l

# + id="64X5Xv_Kaf_Q" colab={"base_uri": "https://localhost:8080/", "height": 496} outputId="e6345129-8241-49fd-daa4-f9adf9c749f0"
from google.colab.patches import cv2_imshow
import cv2

def show_image(img_path,size=None,ratio=None):
  img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
  cv2_imshow(img)

show_image('cat_dog.jpg')

# + [markdown] id="y4_LLDBWGkE4"
# ## Visualizing arrays
#
# If you use imshow, be careful of aliasing which can occur for certain figure sizes.

# + id="grpgCI4IG2pb" colab={"base_uri": "https://localhost:8080/", "height": 182} outputId="5455f0f5-7109-49c3-fa64-14bbbbd9b662"
np.random.seed(0)
fig, axs = plt.subplots(1,3,figsize=(8,8))
for t in range(3):
  X = np.random.binomial(1, 0.5, (128, 128))
  axs[t].imshow(X, cmap="Accent")

# + [markdown] id="kKo7IkOzHMRo"
# You can solve this by specifying `interpolation=nearest`:

# + id="4aoE3HkuHQt-" colab={"base_uri": "https://localhost:8080/", "height": 182} outputId="93829355-ebb8-474a-a6ea-e61e18a8d437"
np.random.seed(0)
fig, axs = plt.subplots(1,3,figsize=(8,8))
for t in range(3):
  X = np.random.binomial(1, 0.5, (128, 128))
  axs[t].imshow(X, cmap="Accent", interpolation='nearest')

# + [markdown] id="K7MgGBEvHTlB"
# Alternatively, you can call `matshow`, which is an alias for imshow with `interpolation=nearest`:
#

# + id="M3nsqSA3HZxC" colab={"base_uri": "https://localhost:8080/", "height": 182} outputId="f7600549-b0f3-4fef-8c02-a276a8ea8274"
np.random.seed(0)
fig, axs = plt.subplots(1,3,figsize=(8,8))
for t in range(3):
  X = np.random.binomial(1, 0.5, (128, 128))
  axs[t].matshow(X, cmap="Accent")

# + [markdown] id="qLh3fxl63IHW"
# ## Graphviz
#
# You can use graphviz to layout nodes of a graph and draw the structure.

# + colab={"base_uri": "https://localhost:8080/"} id="33v9WeNjfb2k" outputId="2567027c-8c11-4517-94b8-3ae984b1d328"
# !apt-get -y install python-pydot
# !apt-get -y install python-pydot-ng
# !apt-get -y install graphviz

# + colab={"base_uri": "https://localhost:8080/", "height": 498} id="ZXTgGVkaffjf" outputId="2037bb22-88c1-4013-cf8e-03c8ebe349d5"
from graphviz import Digraph
dot = Digraph(comment='Bayes net')
print(dot)
dot.node('C', 'Cloudy')
dot.node('R', 'Rain')
dot.node('S', 'Sprinkler')
dot.node('W', 'Wet grass')
dot.edge('C', 'R')
dot.edge('C', 'S')
dot.edge('R', 'W')
dot.edge('S', 'W')
print(dot.source)
dot.render('test-output/graph.jpg', view=True)
dot

# + [markdown] id="DaSY50JzpWnC"
# ## Progress bar
#

# + id="RNoYKY34Iqu6" colab={"base_uri": "https://localhost:8080/"} outputId="07194dcd-35de-47b0-b7b4-8158f83631c0"
from tqdm import tqdm
for i in tqdm(range(20)):
  x = np.random.randn(1000,1000)

# + [markdown] id="lyWwltzKlAHS"
# # Filing system issues
#
#
# Details here:
# - https://colab.research.google.com/notebooks/io.ipynb
# - https://neptune.ai/blog/google-colab-dealing-with-files
#
# Many other sources.

# + [markdown] id="7aCgO-moU2WA"
# ## Accessing local files
#
# Clicking on the file folder icon on the left hand side of colab lets you browse local files. Right clicking on a filename lets you download it to your local machine. Double clicking on a file will open it in the file viewer/ editor, which appears on the right hand side.
#
# The result should look something like this:
#
#
#
#
# You can also use standard unix commands to manipulate files, as we show below.

# + colab={"base_uri": "https://localhost:8080/"} id="dRh4BOIxHpEX" outputId="718b8efd-1988-4633-db29-6a414a350415"
# !pwd

# + colab={"base_uri": "https://localhost:8080/"} id="T7i8bvaghwy7" outputId="bb0012d2-bc82-4d5d-dfe3-acf2ba3e77a7"
# !ls

# + colab={"base_uri": "https://localhost:8080/"} id="HDNijfMPjPsE" outputId="d2bb4d90-86fc-44b2-9900-6050507e082b"
# !echo 'foo bar' > foo.txt
# !cat foo.txt

# + [markdown] id="5bnSVjwTr_bg"
# However, !cd does not work. You need to use the magic %cd.

# + id="voUDbPTUsDI3" colab={"base_uri": "https://localhost:8080/"} outputId="b8e3f19f-244a-407f-9fe7-94b64f60ef4b"
# !pwd
# !mkdir dummy
# %cd dummy
# !ls
# %cd ..

# + [markdown] id="AhyYxQ8mrzev"
# To make a new (local) file in colab's editor, first create the file with the operating system, and then view it using colab.

# + id="LtbGecLQrysw" colab={"base_uri": "https://localhost:8080/", "height": 17} outputId="17613167-0d6d-4f6e-b66f-129eb2552b6a"
from google.colab import files
file = 'bar.py'
# !touch $file
files.view(file)

# + [markdown] id="PMet3XdcVF9O"
# If you make changes to a file containing code, the new version of the file will not be noticed unless you use the magic below.

# + id="0ufY8AO1VEUh"
# %load_ext autoreload
# %autoreload 2

# + [markdown] id="6a6nkLsKWQpu"
# ## Syncing with Google drive
#
# Files that you generate in, or upload to, colab are ephemeral, since colab is a temporary environment with an idle timeout of 90 minutes and an absolute timeout of 12 hours (24 hours for Colab pro). To save any files permanently, you need to mount your google drive folder as we show below. (Executing this command will open a new window in your browser - you need cut and paste the password that is shown into the prompt box.)
#
#

# + colab={"base_uri": "https://localhost:8080/"} id="cYZpcMiQkl15" outputId="f4cf4b76-100a-428f-8c63-14aad3207235"
from google.colab import drive
drive.mount('/content/gdrive')
# !pwd

# + id="wycWRaVxPh5P" colab={"base_uri": "https://localhost:8080/"} outputId="5571799c-360b-446f-9f70-375f7d14527b"
with open('/content/gdrive/MyDrive/foo.txt', 'w') as f:
  f.write('Hello Google Drive!')
# !cat /content/gdrive/MyDrive/foo.txt

# + [markdown] id="CeGz5P_RUyzG"
# To ensure that local changes are detected by colab, use this piece of magic.

# + id="K6RR9dfoUyMG"
# %load_ext autoreload
# %autoreload 2

# + [markdown] id="_x4_RdSfh272"
# ## Uploading data to colab from your local machine

# + id="IgQe1zgYh4hF"
from google.colab import files
# GUI lets you select the file
# the return value is a dict, mapping filename to bytes
uploaded = files.upload()

# + [markdown] id="2mUVyvAblY3v"
# ## Downloading data from colab to your local machine

# + id="fwdVtkGqlblY"
from google.colab import files
files.download('checkpoints/gan-mlp-mnist-epoch=02.ckpt')

# + [markdown] id="bIqJtkFnlF8I"
# ## Loading data from the web into colab
#
# You can use [wget](https://www.pair.com/support/kb/paircloud-downloading-files-with-wget/)
#
#

# + id="3oypH8Vclu86"
# !rm timemachine.*

# + colab={"base_uri": "https://localhost:8080/"} id="H0dvvsUclgdu" outputId="924597b5-451d-4eb3-d6dd-6e45763828dc"

# #!wget  https://github.com/probml/pyprobml/blob/master/data/timemachine.txt
# #!wget  https://github.com/probml/pyprobml/blob/master/data/timemachine.txt
# !wget https://raw.githubusercontent.com/probml/probml-data/main/data/timemachine.txt

# + colab={"base_uri": "https://localhost:8080/"} id="kj3fMYuwlyNR" outputId="f4a55c24-854b-4655-a48f-6284f10460a4"
# !head timemachine.txt

# + id="ZawrQx644OyW" outputId="dd6e15a0-71d0-4471-8fe0-06bf3248d144" colab={"base_uri": "https://localhost:8080/"}

datadir = '.'
import re
fname = os.path.join(datadir, 'timemachine.txt')
with open(fname, 'r') as f:
    lines = f.readlines()
    sentences = [re.sub('[^A-Za-z]+', ' ', st).lower().split()
                   for st in lines]
for  i in range(5):
  words = sentences[i]
  print(words)

# + [markdown] id="memojWlxuAyH"
# ## Loading code from the web into colab
#
# We can also download python code and run it locally.

# + id="siXx1f8et98t" colab={"base_uri": "https://localhost:8080/"} outputId="d0a115c5-3a81-4237-bf45-d1c8bd0d9d06"
# !wget -q https://raw.githubusercontent.com/probml/pyprobml/master/scripts/pyprobml_utils.py

import pyprobml_utils as pml
pml.test()

# + [markdown] id="57S7dQXbPSh6"
# ## Viewing all your notebooks
#
# You can see the list of colab notebooks that you have saved as shown below.

# + id="cTJGGK29PYM4" colab={"base_uri": "https://localhost:8080/"} outputId="dbb73578-2dd8-4f3c-f270-4dfbdedb60ff"
import re, pathlib, shutil
from pathlib import PosixPath

# Get a list of all your Notebooks
notebooks = [x for x in pathlib.Path("/content/gdrive/MyDrive/Colab Notebooks").iterdir() if
             re.search(r"\.ipynb", x.name, flags = re.I)]
print(notebooks[:2])

#n = PosixPath('/content/gdrive/MyDrive/Colab Notebooks/covid-open-data-paper.ipynb')

# + [markdown] id="buZsxpmUS37n"
# # Working with github
#
# You can open any jupyter notebook stored in github in a colab by replacing
# https://github.com/probml/.../intro.ipynb with https://colab.research.google.com/github/probml/.../intro.ipynb (see [this blog post](https://amitness.com/2020/06/google-colaboratory-tips/#6-open-notebooks-from-github).
#
# It is possible to download code (or data) from githib into a local directory on this virtual machine.  It is also possible to upload local files back to github, although that is more complex. See details below.

# + [markdown] id="rVvGT6GUBg2Q"
# ## Cloning a repo from github
#
# You can clone a public github repo into your local colab VM, as we show below,
# using the repo for this book as an example.
# (To clone a private repo, you need to specify your password,
# as explained [here](https://medium.com/@robertbracco1/configuring-google-colab-like-a-pro-d61c253f7573#6b70). Alternatively you can use the ssh method we describe below.)

# + id="uVZWqzdW7_ZG" colab={"base_uri": "https://localhost:8080/"} outputId="cbae2714-3da4-405e-93d6-32fe1d4bbf4e"

# !rm -rf pyprobml # Remove any old local directory to ensure fresh install
# !git clone https://github.com/probml/pyprobml

# + id="sL0CLHTm7HSH" colab={"base_uri": "https://localhost:8080/"} outputId="d23ce0d0-3403-4bfd-e666-34f715da765a"
# !pwd

# + colab={"base_uri": "https://localhost:8080/"} id="XdC34HzKT8L8" outputId="49d5d474-7af0-4a4d-adb3-3a11b049f3e3"
# !ls

# + [markdown] id="MNWWINngc5rn"
# We can run any script as shown below.
# (Note we first have to define the environment variable for where the figures will be stored.)

# + colab={"base_uri": "https://localhost:8080/", "height": 873} id="aYXkQP-DdApw" outputId="50b98443-467b-4ab9-ee6c-773cc80530ab"
import os
os.environ['PYPROBML']='pyprobml'

# %run pyprobml/scripts/activation_fun_plot.py

# + id="qItn37RW7R3N" colab={"base_uri": "https://localhost:8080/"} outputId="903c2f05-dafb-4db6-d5b9-9874a3b0cc1d"
# !ls pyprobml/figures

# + [markdown] id="YTT5eJ_qUDFe"
# We can also import code, as we show below.

# + colab={"base_uri": "https://localhost:8080/"} id="0jUdrHWLd95C" outputId="a6ba93bc-118c-42e7-c894-34eeef80c95b"
# !ls

# + colab={"base_uri": "https://localhost:8080/"} id="-NkBCWPdePFj" outputId="b33684a4-cd62-46fb-ab15-ef650f12d5c2"

import pyprobml.scripts.pyprobml_utils as pml
pml.test()

# + [markdown] id="yaISmcnNmnS7"
# ## Pushing local files back to github
#
# You can easily save your entire colab notebook to github by choosing 'Save a copy in github' under the File menu in the top left. But if you want to save individual files (eg code that you edited in the colab file editor, or a bunch of images or data files you created), the process is more complex.
#
# There are two main methods. You can either specify your username and password every time, as explained [here](https://medium.com/@robertbracco1/configuring-google-colab-like-a-pro-d61c253f7573#6b70). Or you can authenticate via ssh. The latter is more secure, but more complex, as we explain below.
#
# You first need to do some setup to create SSH keys on your current colab VM (virtual machine), manually add the keys to your github account, and then copy the keys to your mounted google drive so you can reuse the same keys in the future. This only has to be done once.
#
# After setup, you can use the `git_ssh` function we define below to securely execute git commands. This works by copying your SSH keys from your google drive to the current colab VM, executing the git command, and then deleting the keys from the VM for safety.
#

# + [markdown] id="0gOzFmcKoUuO"
# ### Setup
#
# Follow these steps. (These instructions are text, not code, since they require user interaction.)
#
# ```
# # # !ssh-keygen -t rsa -b 4096
# # # !ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
# # # !cat /root/.ssh/id_rsa.pub
# ```
# The cat command will display your public key in the colab window.
# Cut and paste this and manually add to your github account following [these instructions](https://github.com/settings/keys).
#
# Test it worked
# ```
# # # !ssh -T git@github.com
# ```
#
# Finally, save the generated keys to your Google drive
#
# ```
# from google.colab import drive
# drive.mount('/content/drive')
# # # !mkdir /content/drive/MyDrive/ssh/
# # # !cp  -r  ~/.ssh/* /content/drive/MyDrive/ssh/
# # # !ls /content/drive/MyDrive/ssh/
# ```
#

# + [markdown] id="oSiVyBG1xm44"
# ### Test previous setup
#
# Let us check that we can see our SSH keys in our mounted google drive.

# + colab={"base_uri": "https://localhost:8080/"} id="cCUxHiHAxcY2" outputId="9555ac68-2dc8-4b9a-9049-b8174ddfbf4c"
from google.colab import drive
drive.mount('/content/drive')

# !ls /content/drive/MyDrive/ssh/

# + [markdown] id="C-lPchgDpD7t"
# ### Executing git commands from colab via SSH
#
# The following function lets you securely doing a git command via SSH.
# It copies the keys from your google drive to the local VM, excecutes the command, then removes the keys.
#
#

# + colab={"base_uri": "https://localhost:8080/"} id="ONJ0Ump4wEho" outputId="ef47e2d3-18fd-4917-c9c5-77315c4fd0b6"
# !rm -rf pyprobml_utils.py # remove any old copies of this file
# !wget https://raw.githubusercontent.com/probml/pyprobml/master/scripts/pyprobml_utils.py

# + id="FJ1_fCk_K9Mc" colab={"base_uri": "https://localhost:8080/"} outputId="0bd007b2-09c0-4cb1-e295-6f734cf70252"
from google.colab import drive
drive.mount('/content/drive') # must do this before running git_ssh
import pyprobml_utils as pml # import script into namespace

# + [markdown] id="umvOwzMfvpmU"
# Below we clone the pyprobml repo to this colab VM using out github credentials, so we can later check stuff back in. **This is just an example - you should edit the `reponame`, `username` and `email` variables.***

# + colab={"base_uri": "https://localhost:8080/"} id="IbX9-PDpwlO0" outputId="957ae2fd-cab7-4267-8f4d-9709d6554910"

# !rm -rf pyprobml # remove any old copies of this directory
# #!git clone https://github.com/probml/pyprobml.git # clones using wrong credentials
pml.git_ssh("git clone https://github.com/probml/pyprobml.git",
            mail="murphyk@gmail.com", username="probml") # update to use your credentials

# + id="ir4nBvPfLXwF" colab={"base_uri": "https://localhost:8080/"} outputId="00fcd067-3f7f-4ee1-9c54-979b6d9722ce"

reponame = 'pyprobml'
username = 'probml'
email = 'murphyk@gmail.com' # update to use your credentials

# !rm -rf $reponame # remove any old copies of this directory
cmd = f"git clone https://github.com/{username}/{reponame}.git"
pml.git_ssh(cmd, email=email, username=username)

# + [markdown] id="4-1ZQyFUMq9A"
# Let's check that we can see this repo in our local drive.

# + id="gB5Lx38aMupR" colab={"base_uri": "https://localhost:8080/"} outputId="9c26d870-64bd-4589-b4ec-2607815d8ee5"
# !pwd
# !ls

# + id="od3MymSWNHY6" colab={"base_uri": "https://localhost:8080/"} outputId="02b156ec-c917-4955-bd77-f04177e94abd"
# !ls /content/$reponame/

# + [markdown] id="ft_pJJ4ZTLdl"
# Now we create a dummy file inside our local copy of this repo, and push it back to the  github (public) version of the repo.
#

# + colab={"base_uri": "https://localhost:8080/"} id="3hfluZVYTSNd" outputId="400a5892-fd9d-44ca-e67c-dfdb572a5c00"

# Make the dummy file in the scripts folder of repo
# %cd /content/$reponame
# !echo 'this is a test' > scripts/foo.txt

# Add file to the external repo
cmd = "git add scripts; git commit -m 'push from colab'; git push"
pml.git_ssh(cmd, email=email, username=username)

# + [markdown] id="l8MHjRl5Ptoi"
# We can check that it worked by visiting [this page](https://github.com/probml/pyprobml/blob/master/scripts/foo.txt) on github (note the time stamp on the top right):
#
#
#

# + [markdown] id="DAqqAhzzTS7F"
#
# Finally we clean up our mess.

# + colab={"base_uri": "https://localhost:8080/"} id="r16zYiNTu_Mz" outputId="d10b7525-d203-4622-b0e5-6b7f1f894529"

# %cd /content/$reponame
cmd = "git rm scripts/foo*.txt; git commit -m 'colab cleanup'; git push"
pml.git_ssh(cmd, email=email, username=username, verbose=True)
# %cd /content

# + [markdown] id="q-kRtmdm5d7X"
# # Software engineering tools
#
#  [Joel Grus has argued](https://docs.google.com/presentation/d/1n2RlMdmv1p25Xy5thJUhkKGvjtV-dkAIsUXP-AL4ffI/edit) that notebooks are bad for developing complex software, because they encourage creating monolithic notebooks instead of factoring out code into separate, well-tested files.
#
# [Jeremy Howard has responded to Joel's critiques here](https://www.youtube.com/watch?v=9Q6sLbz37gk&feature=youtu.be). In particular, the FastAI organization has created [nbdev](https://github.com/fastai/nbdev) which has various tools that make notebooks more useful.
#
#

# + [markdown] id="KoYhOfdLJOB5"
# ## Argparse
#
# Often code is designed to be run from the command line, and can be configured by passing in arguments and flags. To make this work in colab, you have to use `parse_known_args`, as in  the example below.
#

# + id="B-zBHHR0Ja-C" colab={"base_uri": "https://localhost:8080/"} outputId="a20c460e-76ee-46eb-c840-bddcf4059921"
def main(args):
  print('my awesome function')
  print(args.arg1)
  print(args.arg2)

import argparse
parser = argparse.ArgumentParser(description='My Demo')
parser.add_argument("-arg1", default=1, type=int,  help="An integer to print")
parser.add_argument("-arg2", "--argument2", default="foo", help="A string to print")
parser.add_argument("-f", "--flag", action="store_true", help="Just a flag")

#args = parser.parse_args() # error in colab
args, unused = parser.parse_known_args()

print(args)
print(unused)

# + id="lJhFvBfpJvzX" colab={"base_uri": "https://localhost:8080/"} outputId="263244ba-1e87-4cca-9d45-f703e02bad8e"
args.arg1 = 42
args.arg2 = 'bar'
main(args)

# + id="WY8-ToefJxjU" colab={"base_uri": "https://localhost:8080/"} outputId="b5c789d2-f813-409f-89b5-23c266e2cc89"
args.arg1 = 49
args.arg2 = 'foo'
main(args)

# + [markdown] id="I9YU0L_lKQIc"
# ## YAML files
#
# We show how to create a config file locally, and then pass it to your code.

# + id="cdbgSjt7Kaiv" colab={"base_uri": "https://localhost:8080/"} outputId="9782c31d-bf49-4613-c320-23eefc1032cd"
# %%writefile myconfig.yaml
model_params:
  name: 'VanillaVAE'
  in_channels: 3
  latent_dim: 128

exp_params:
  dataset: celeba
  data_path: "../../shared/Data/"

# + id="9GMLwuvxK8d6" colab={"base_uri": "https://localhost:8080/"} outputId="023cc596-1f65-4416-e208-f8d4ed3558bc"
# !cat myconfig.yaml

# + id="BQQvL8hZLn75" colab={"base_uri": "https://localhost:8080/", "height": 17} outputId="dd022703-d610-4782-d104-b59d1aa9254e"
from google.colab import files
file = 'myconfig.yaml'
# #!touch $file
files.view(file)

# + id="MFnxc7WGLven" colab={"base_uri": "https://localhost:8080/"} outputId="952c0a8e-5c3d-4c1d-c2fb-1cea85cf58f7"
# !cat myconfig.yaml

# + id="KMw1vYZ1KSLU" colab={"base_uri": "https://localhost:8080/"} outputId="3e5ec991-e4f4-458d-9076-7281a1a98cf4"
import yaml

filename = 'myconfig.yaml'

with open(filename, 'r') as file:
  config = yaml.safe_load(file)

print(type(config))
print(config)
print(config['model_params']['in_channels'])

# + [markdown] id="PuSsmj_fZ106"
# ## Avoiding problems with global state
#
# One of the main drawbacks of colab is that all variables are globally visible, so you may accidently write a function that depends on the current state of the notebook, but which is not passed in as an argument. Such a function may fail if used in a different context.
#
# One solution to this is to put most of your code in files, and then have the notebook simply import the code and run it, like you would from the command line. Then you can always run the notebook from scratch, to ensure consistency.
#
# Another solution is to use the [localscope](https://localscope.readthedocs.io/en/latest/README.html) package can catch some of these errors.
#
#
#

# + colab={"base_uri": "https://localhost:8080/"} id="Q0FmEeIgc0YI" outputId="0d71c4a8-a4fb-45bd-e137-c391e408c83f"
# !pip install localscope

# + id="9zfUiUB8d-jh"
from localscope import localscope

# + colab={"base_uri": "https://localhost:8080/"} id="wI5wXzUPdlOS" outputId="500dfc1f-1cbc-4fa6-a30e-02f32258fa31"
a = 'hello world'
def myfun():
   print(a) # silently accesses global variable

myfun()

# + colab={"base_uri": "https://localhost:8080/", "height": 337} id="T3V1iV32czq8" outputId="b346cb7a-f252-41bc-992b-dd72e5aec825"
a = 'hello world'
@localscope
def myfun():
  print(a)

myfun()

# + colab={"base_uri": "https://localhost:8080/", "height": 337} id="5t48_AMbAN8V" outputId="71fdaacf-4650-4a52-9fb5-da5873c33e73"
def myfun2():
  return 42

@localscope
def myfun3():
  return myfun2()

# + colab={"base_uri": "https://localhost:8080/"} id="DAqLZ8PdAquy" outputId="021e8282-a283-414c-ef1d-46521da89e49"
@localscope.mfc # allow for global methods, functions, classes
def myfun4():
  return myfun2()

myfun4()

# + [markdown] id="iZaeVouoAhXP"
# ## Factoring out functionality into files stored on github
#
# The recommended workflow is to  develop your code in the colab in the usual way, and when it is working, to factor out the core code into separate files. You can  edit these files locally in the colab editor, and then push the code to github when ready (see details above). To run functions defined in a local file, just import them. For example, suppose we have created the file /content/pyprobml/scripts/fit_flax.py; we  can use this idiom to run its test suite:
# ```
# import pyprobml.scripts.fit_flax as ff
# ff.test()
# ```
# If you make local edits, you want to be sure
#  that you always import the latest version of the file (not a cached version). So you need to use this piece of colab magic first:
# ```
# # # %load_ext autoreload
# # # %autoreload 2
# ```
#

# + [markdown] id="DdXlYCe1AlJa"
# ## File editors
#
# Colab has a simple file editor, illustrated below for an example file.
# This lets you separate your code from the output of your code, as with other IDEs, such as [Jupyter lab](https://jupyterlab.readthedocs.io/en/stable/).
#
#
#
#
#

# + [markdown] id="LgLmSpaHBxLu"
# You can click on a class name when holding Ctrl and the source code will open in the file viewer. (h/t [Amit Choudhary's blog](https://amitness.com/2020/06/google-colaboratory-tips/).
#
#
#
#

# + [markdown] id="4qi2xKMbAnlj"
#
# ## VScode
# The default colab file editor is very primitive.
# See [this article](https://amitness.com/vscode-on-colab/) for how to run VScode
# from inside your Colab browser. Unfortunately this is a bit slow. It is also possible to run VScode locally on your laptop, and have it connect to colab via SSH, but this is more complex (see [this blog post](https://amitness.com/vscode-on-colab/) or [this medium post](https://medium.com/@robertbracco1/configuring-google-colab-like-a-pro-d61c253f7573#4cf4) for details).
#
#

# + [markdown] id="7KrRcbQ71ZyZ"
# # Hardware accelerators
#
# By default, Colab runs on a CPU, but you can select GPU or TPU for extra speed, as we show below. To get access to more powerful machines (with faster processors, more memory, and longer idle timeouts), you can subscript to [Colab Pro](https://colab.research.google.com/signup). At the time of writing (Jan 2021), the cost is $10/month (USD). This is a good deal if you use GPUs a lot.
#
#
#
#
#

# + [markdown] id="Qpb9N-c-3RSf"
# ## CPUs
#
# To see what devices you have, use this command.
#

# + colab={"base_uri": "https://localhost:8080/"} id="SZYIR1Kk1ktp" outputId="04ab4999-74d4-47d6-f9cb-004ca13b590b"
from tensorflow.python.client import device_lib
device_lib.list_local_devices()

# + colab={"base_uri": "https://localhost:8080/"} id="hs_l1zUY3UuQ" outputId="99a7db7f-6325-452e-ca69-ab7439070d67"
# !cat /proc/version

# + colab={"base_uri": "https://localhost:8080/"} id="WHG13Iwa3Z5k" outputId="ebc74028-163e-48e2-a0c6-56667e4e0c0d"
from psutil import cpu_count

print('num cores', cpu_count())

# !cat /proc/cpuinfo

# + [markdown] id="PivaBI5za45p"
# ## Memory
#

# + colab={"base_uri": "https://localhost:8080/"} id="xg3C0Yrq3job" outputId="0b480191-8489-4b6c-fc26-c6f3b51aecc2"
from psutil import virtual_memory
ram_gb = virtual_memory().total / 1e9
print('RAM (GB)', ram_gb)

# + colab={"base_uri": "https://localhost:8080/"} id="W_I7m_hUbBpu" outputId="b688a7d0-1c53-4864-c79f-fde42cda4db6"

# !cat /proc/meminfo

# + [markdown] id="v0G2d13kIEz5"
# ## GPUs
#
# If you select the 'Runtime' menu at top left, and then select 'Change runtime type' and then select 'GPU', you can get free access to a GPU.
#
#
#
#

# + [markdown] id="MGVZB0esI0QG"
#
# To see what kind of GPU you are using, see below.
#

# + id="FikkXWQqBU9O" colab={"base_uri": "https://localhost:8080/"} outputId="ba7b6c8e-5089-4342-f9f4-def0e5ad86a8"
gpu_info = !nvidia-smi
gpu_info = '\n'.join(gpu_info)
print(gpu_info)

# + colab={"base_uri": "https://localhost:8080/"} id="GU6nII1F5S2S" outputId="d220a1a6-cc83-4718-c77e-2df38f13bf9c"
# !grep Model: /proc/driver/nvidia/gpus/*/information | awk '{$1="";print$0}'

# + id="hzi1OpMaZlAc"


from PIL import Image, ImageTk
import threading
import thread
import time
import Tkinter
import time
import cv2
import Skynet
import numpy as np
import math
import sys
import os

def clamp_aspect(ratio, width, height):
	width = float(width)
	height = float(height)
	if width > height * ratio:
		width = height * ratio
	elif height > width / ratio:
		height = width / ratio
	width = int(math.ceil(width))
	height = int(math.ceil(height))
	return width, height

def resize_to_height(img, height):
	ratio = float(height) / float(img.shape[0])
	width = int(math.ceil(float(img.shape[1]) * ratio))
	return cv2.resize(img, (width, height))

def centered_clamp_width(img, width):
	offset = img.shape[1] - width
	if not offset > 0:
		return img
	offset_a = offset//2
	offset_b = offset_a
	if offset % 2:
		offset_b += 1
	return img[:, offset_a:(img.shape[1]-offset_b)]

def threaded_rescale(frame_in, frame_out, tr_control, tr_lock, instance_id):
	tr_lock.acquire()
	while not tr_control["stop"]:
		if len(frame_in) and frame_in[tr_control['ci']] is not None and not tr_control['ready']:
			frame = frame_in[tr_control['ci']]
			tr_control['ready'] = True
			print "ID"+str(instance_id)+" got ci "+str(tr_control['ci'])
			size = clamp_aspect(16.0/9.0, tr_control["width"], tr_control["height"])
			tr_lock.release()
			frame = cv2.resize(frame, size)
			frame = Image.fromarray(frame)
			frame = ImageTk.PhotoImage(frame)
			tr_lock.acquire()
			frame_out[0]=frame
			tr_control['done']=False
		else:
			tr_lock.release()
			tr_lock.acquire()

class object_tail:
	def __init__(self, frame, point):
		self.locs = [point]
		self.face_id = None
		self.face_data = {}
		self.face_callback = None
		self.rek_req_active = False
		self.last_time = 0

	def __eq__(self, other):
		if other is None:
			return False
		return other.locs == self.locs

	def dist(self, point):
		return math.sqrt((point[1]-self.locs[-1][1])**2 + (point[0]-self.locs[-1][0])**2)

	def add(self, point):
		self.locs.append(point)

	def plot_tail(self, img, col):
		x, y, w, h = self.locs[-1]
		cv2.rectangle(img, (x, y), (x + w, y + h), col, 2)
		if len(self.face_data):
			text_name = self.face_data["First Name"]+" "+self.face_data["Last Name"]
			cv2.putText(img, text_name, (x,y+h+24), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2)

	def rekognize(self, img, facedb, callback=None):
		self.face_callback = callback
		x, y, w, h = self.locs[-1]
		if w < 80 or h < 80:
			return False
		self.rek_req_active = True
		x1 = x - (w/2)
		x2 = x + ((3*w)/2)
		y1 = y - (h/2)
		y2 = y + ((3*h)/2)
		x1=max(x1, 0)
		y1=max(y1, 0)
		x2=min(x2, img.shape[1])
		y2=min(y2, img.shape[0])
		opencv_image = img[y1:y2, x1:x2]
		t = threading.Thread(target=t_rekognize, args=(opencv_image, facedb, self.c_rekognize))
		t.daemon = True
		t.start()

	def c_rekognize(self, result):
		self.rek_req_active = False
		if len(result)!=3:
			print "Error: Bad data"
			return False
		if result[1] is None:
			return False
		self.face_id = result[1]["FaceId"]
		self.face_data = result[1]["PersonData"]
		self.last_time = time.time()
		if self.face_callback is not None:
			self.face_callback(self, result)

def t_rekognize(img, facedb, callback):
	enc_jpeg = cv2.imencode('.jpg', img)[1].tostring()
	result = facedb.identify_face(enc_jpeg, 0.85)
	callback(result)

class simpleapp_tk:
	def __init__(self):
		self.root=Tkinter.Tk()
		self.root.title("Skynet Watches")
		self.root.state("zoomed")
		self.root.focus_set()
		self.root.wm_iconbitmap(bitmap = "mia.ico")

		self.root.configure(bg="#555")
		self.cframe = Tkinter.Frame(self.root, bg="#555", width=200)
		self.cframe.pack(fill=Tkinter.Y, padx=10, side=Tkinter.LEFT)
		self.cframe.pack_propagate(0)

		self.vframe = Tkinter.Label(self.root, bd=0, bg='#222')
		self.vframe.pack(fill="both", expand=True)

		self.facedb = Skynet.FaceDatabase('creds', table='skynetdb', collection='Skynet', bucket='skynetdb')
		self.face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
		self.cfaces = {}

		self.tr_control = {
				'stop':False,
				'ready':False,
				'done':False,
				'width':100,
				'height':100,
				'ci':0,
				'buf_size':8,
				'show_frame':self.show_frame
			}
		#self.tr_lock = threading.Lock()
		self.tr_iframe = {}
		self.tr_oframe = [None]
		self.tracked_faces = []

		#Camera
		self.width, self.height = 1280, 720
		self.cap = cv2.VideoCapture(1)
		time.sleep(3)
		self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.width)
		self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.height)
		self.get_frame()
		#self.show_frame()

		#thread.start_new_thread(threaded_rescale, (self.tr_iframe, self.tr_oframe, self.tr_control, self.tr_lock, 1))
		#thread.start_new_thread(threaded_rescale, (self.tr_iframe, self.tr_oframe, self.tr_control, self.tr_lock, 2))
		#thread.start_new_thread(threaded_rescale, (self.tr_iframe, self.tr_oframe, self.tr_control, self.tr_lock, 3))
		#thread.start_new_thread(threaded_rescale, (self.tr_iframe, self.tr_oframe, self.tr_control, self.tr_lock, 4))
		#threading.Thread.__init__(self)

	def get_frame(self):
		_, frame = self.cap.read()
		frame = cv2.flip(frame, 1)

		gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
		faces = list(self.face_cascade.detectMultiScale(gray, 1.3, 5))
		faces = [list(x) for x in faces]
		if len(self.tracked_faces) == 0 or len(faces) == 0:
			self.tracked_faces = []
			for item in faces:
				inter = object_tail(frame, item)
				inter.rekognize(frame[:, :], self.facedb, self.pf_inter)
				self.tracked_faces.append(inter)
		else:
			try:
				self.tracked_faces = [x for x in self.tracked_faces if (min([x.dist(y) for y in faces]) < 45)]
				for tracker in self.tracked_faces:
						tracker.counter = 0
						try:
							index = [tracker.dist(x) for x in faces].index(min([tracker.dist(x) for x in faces]))
							tracker.add(faces[index])
							faces.remove(faces[index])
						except ValueError, e:
							print e
							print faces
				for point in faces:
					inter = object_tail(frame, point)
					inter.rekognize(frame[:, :], self.facedb, self.pf_inter)
					self.tracked_faces.append(inter)
			except ValueError, e:
				print e
				print faces
		current_faceids = []
		for face in self.tracked_faces:
			if face.face_id is not None:
				current_faceids.append(face.face_id)
			elif not face.rek_req_active and face.last_time < time.time():
				face.rekognize(frame[:, :], self.facedb, self.pf_inter)
		for faceid in self.cfaces:
			if not faceid in current_faceids and not self.cfaces[faceid]['hidden'] and not self.cfaces[faceid]['pop_timeout']:
				self.cfaces[faceid]["pop_timeout"] = self.root.after(5000, self.pop_face, (faceid))
		for faceid in current_faceids:
			if not faceid in self.cfaces or self.cfaces[faceid]['hidden']:
				self.push_face(faceid)

		col = 0
		for item in self.tracked_faces:
			item.plot_tail(frame, (255-col, col, 255))
			col += 255/3

		frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
		size = clamp_aspect(16.0/9.0, self.vframe.winfo_width(), self.vframe.winfo_height())
		frame = cv2.resize(frame, size)
		frame = Image.fromarray(frame)
		frame = ImageTk.PhotoImage(frame)
		self.vframe.configure(image=frame)
		self.vframe.image=frame
		'''
		self.tr_lock.acquire()
		self.tr_control['width'] = self.vframe.winfo_width()
		self.tr_control['height'] = self.vframe.winfo_height()
		next_index = self.tr_control['ci'] + 1
		if next_index >= self.tr_control['buf_size']:
			next_index = 0
		self.tr_iframe[next_index] = frame
		self.tr_control['ci'] = next_index
		self.tr_control['ready'] = False
		self.tr_lock.release()
		'''
		self.root.after(10, self.get_frame)

	def show_frame(self):
		self.tr_lock.acquire()
		if self.tr_oframe[0] is not None and not self.tr_control['done']:
			myframe = self.tr_oframe[0]
			self.tr_control['done']=True
			self.tr_lock.release()
			self.tr_lock.acquire()
		if not self.tr_control['stop']:
			self.root.after(10, self.show_frame)
		self.tr_lock.release()

	def pf_inter(self, tail, result):
		if len(tail.face_data) > 0:
			self.push_face(tail.face_id, "Confidence: "+"{0:.2f}".format(result[2])+"%")

	def push_face(self, faceid, addtl_label=False):
		if faceid in self.cfaces:
			if self.cfaces[faceid]['pop_timeout']:
				self.root.after_cancel(self.cfaces[faceid]["pop_timeout"])
				self.cfaces[faceid]["pop_timeout"] = False
			if self.cfaces[faceid]['ready'] and addtl_label != False:
				self.cfaces[faceid]["ui_extra"].config(text=addtl_label)
				self.cfaces[faceid]['addtl_label']=addtl_label
			if self.cfaces[faceid]['hidden']:
				self.cfaces[faceid]["ui_spacer"].pack(anchor=Tkinter.N)
				self.cfaces[faceid]["ui_pic"].pack(anchor=Tkinter.N, fill=Tkinter.X)
				self.cfaces[faceid]["ui_text"].pack(anchor=Tkinter.N, fill=Tkinter.X)
				if addtl_label != False or self.cfaces[faceid]['addtl_label'] != False:
					self.cfaces[faceid]["ui_extra"].pack(anchor=Tkinter.N, fill=Tkinter.X)
				self.cfaces[faceid]['hidden'] = False
			return
		self.cfaces[faceid] = {'ready':False,'hidden':False, 'pop_timeout':False, 'addtl_label':False}
		self.cfaces[faceid]["db_image"], self.cfaces[faceid]["db_data"] = self.facedb.get_by_faceid(faceid)
		self.cfaces[faceid]["ui_spacer"] = Tkinter.Frame(self.cframe, bg="#555", width=200, height=16)
		self.cfaces[faceid]["ui_spacer"].pack(anchor=Tkinter.N)
		self.cfaces[faceid]["ui_spacer"].pack_propagate(0)

		self.cfaces[faceid]["ui_pic"] = Tkinter.Label(self.cframe, bg="#444", bd=0)
		self.cfaces[faceid]["ui_pic"].pack(anchor=Tkinter.N, fill=Tkinter.X)
		image_data = np.fromstring(self.cfaces[faceid]["db_image"], dtype='uint8')
		opencv_image = cv2.imdecode(image_data, cv2.IMREAD_UNCHANGED)
		opencv_image = cv2.cvtColor(opencv_image, cv2.COLOR_BGR2RGB)
		opencv_image = resize_to_height(opencv_image, 150)
		opencv_image = centered_clamp_width(opencv_image, 200)
		frame = Image.fromarray(opencv_image)
		image = ImageTk.PhotoImage(frame)
		self.cfaces[faceid]["ui_pic"].configure(image=image)
		self.cfaces[faceid]["ui_pic"].image = image

		self.cfaces[faceid]["ui_text"] = Tkinter.Label(self.cframe, font=("Trebuchet MS", 16), bg="#444", fg="#FFF")
		new_text = self.cfaces[faceid]["db_data"]["First Name"]+" "+self.cfaces[faceid]["db_data"]["Last Name"]
		print new_text
		self.cfaces[faceid]["ui_text"].config(text=new_text)
		self.cfaces[faceid]["ui_text"].pack(anchor=Tkinter.N, fill=Tkinter.X)

		self.cfaces[faceid]["ui_extra"] = Tkinter.Label(self.cframe, font=("Trebuchet MS", 11), bg="#444", fg="#FEFEFE")
		extra_text = False
		if addtl_label != False:
			extra_text = addtl_label
			self.cfaces[faceid]['addtl_label']=addtl_label
		elif self.cfaces[faceid]['addtl_label'] != False:
			extra_text = self.cfaces[faceid]['addtl_label']
		if extra_text:
			self.cfaces[faceid]["ui_extra"].config(text=extra_text)
			self.cfaces[faceid]["ui_extra"].pack(anchor=Tkinter.N, fill=Tkinter.X)

		self.cfaces[faceid]['ready']=True

	def pop_face(self, faceid):
		if not faceid in self.cfaces:
			return
		try:
			self.cfaces[faceid]["ui_spacer"].pack_forget()
			self.cfaces[faceid]["ui_pic"].pack_forget()
			self.cfaces[faceid]["ui_text"].pack_forget()
			self.cfaces[faceid]["ui_extra"].pack_forget()
			self.cfaces[faceid]['hidden'] = True
		except KeyError, e:
			print e
			print self.cfaces[faceid]
			raise

	def end_it_all(self):
		while len([face for face in self.tracked_faces if face.rek_req_active]):
			time.sleep(10)
		print "Bye!"
		self.cap.release()
		self.root.destroy()
		os._exit(0)

	def run(self):
		self.root.protocol("WM_DELETE_WINDOW", self.end_it_all)
		self.root.mainloop()

if __name__ == "__main__":
	app = simpleapp_tk()
	app.run()


from .. import logging as logg
from ..preprocessing.neighbors import (
    compute_connectivities_umap,
    get_neighs,
    neighbors,
    verify_neighbors,
)
from ..preprocessing.utils import sum_var
from .utils import scale
from .velocity_model import velocity_model

from Bio import pairwise2
from Bio.SubsMat import MatrixInfo as matlist
from scipy.sparse import coo_matrix
import numpy as np
from tqdm import tqdm

def get_iterative_indices(
        indices,
        index,
        n_recurse_neighbors=0,
        max_neighs=None,
):
    def iterate_indices(indices, index, n_recurse_neighbors):
        if n_recurse_neighbors > 1:
            index = iterate_indices(indices, index, n_recurse_neighbors - 1)
        ix = np.append(index, indices[index])  # direct and indirect neighbors
        if np.isnan(ix).any():
            ix = ix[~np.isnan(ix)]
        return ix.astype(int)

    indices = np.unique(iterate_indices(indices, index, n_recurse_neighbors))
    if max_neighs is not None and len(indices) > max_neighs:
        indices = np.random.choice(indices, max_neighs, replace=False)
    return indices

def get_indices(dist, n_neighbors=None, mode_neighbors='distances'):
    D = dist.copy()
    D.data += 1e-6

    n_counts = sum_var(D > 0)
    n_neighbors = (
        n_counts.min() if n_neighbors is None else min(n_counts.min(), n_neighbors)
    )
    rows = np.where(n_counts > n_neighbors)[0]
    cumsum_neighs = np.insert(n_counts.cumsum(), 0, 0)
    dat = D.data

    for row in rows:
        n0, n1 = cumsum_neighs[row], cumsum_neighs[row + 1]
        rm_idx = n0 + dat[n0:n1].argsort()[n_neighbors:]
        dat[rm_idx] = 0
    D.eliminate_zeros()

    D.data -= 1e-6
    if mode_neighbors == 'distances':
        indices = D.indices.reshape((-1, n_neighbors))
    elif mode_neighbors == 'connectivities':
        knn_indices = D.indices.reshape((-1, n_neighbors))
        knn_distances = D.data.reshape((-1, n_neighbors))
        _, conn = compute_connectivities_umap(
            knn_indices, knn_distances, D.shape[0], n_neighbors
        )
        indices = get_indices_from_csr(conn)
    return indices, D

def predict_sequence_prob(seq_of_interest, vocabulary, model,
                          verbose=False):
    if 'esm' in model.name_:
        from .fb_semantics import predict_sequence_prob_fb
        return predict_sequence_prob_fb(
            seq_of_interest, model.alphabet_, model.model_,
            model.repr_layers_, verbose=verbose,
        )
    elif model.name_ == 'tape':
        from .tape_semantics import predict_sequence_prob_tape
        return predict_sequence_prob_tape(
            seq_of_interest, model
        )
    else:
        raise ValueError('Invalid model name {}'.format(model.name_))

def likelihood_compare(seq1, seq2, vocabulary, model,
                       pos1=None, pos2=None, seq_cache={}, verbose=False):
    likelihoods = []

    for seq_pred, positions in zip([ seq1, seq2 ], [ pos1, pos2 ]):
        if positions is None:
            positions = range(len(seq_pred))

        if seq_pred in seq_cache:
            seq_probs = seq_cache[seq_pred][list(positions)]

        else:
            y_pred = predict_sequence_prob(
                seq_pred, vocabulary, model, verbose=verbose
            )
            seq_probs = np.array([
                y_pred[i + 1, (
                    vocabulary[seq_pred[i]]
                    if seq_pred[i] in vocabulary else
                    model.unk_idx_
                )]
                for i in positions
            ])

        likelihoods.append(np.mean(seq_probs))

    return likelihoods[1] - likelihoods[0]

def align_seqs(seq1, seq2):
    # Align, prefer matches to gaps.
    return pairwise2.align.globalms(
        seq1, seq2, 5, -4, -4, -.1, one_alignment_only=True
    )[0]

def likelihood_muts(
        seq1, seq2, vocabulary, model,
        seq_cache={}, verbose=False, natural_aas=None,
):
    a_seq1, a_seq2, _, _, _ = align_seqs(seq1, seq2)

    # Map alignment to original indices.
    del1, sub1, del2, sub2 = [], [], [], []
    for a_seq, other_seq, deletions, substitutions in zip(
            [ a_seq1, a_seq2, ], [ a_seq2, a_seq1, ],
            [ del1, del2 ], [ sub1, sub2, ]
    ):
        orig_idx = 0
        for a_idx, ch in enumerate(a_seq):
            if ch == '-':
                continue
            if other_seq[a_idx] == '-':
                deletions.append(orig_idx)
            if natural_aas is not None and \
               (ch.upper() not in natural_aas or \
                other_seq[a_idx].upper() not in natural_aas):
                continue
            if other_seq[a_idx] != ch:
                substitutions.append(orig_idx)
            orig_idx += 1

    return likelihood_compare(
        seq1, seq2, vocabulary, model,
        pos1=sub1, pos2=sub2, seq_cache=seq_cache, verbose=verbose,
    )

def likelihood_blosum62(
        seq1, seq2, vocabulary, model,
        seq_cache={}, verbose=False, natural_aas=None,
):
    from Bio.SubsMat import MatrixInfo as matlist
    matrix = matlist.blosum62

    a_seq1, a_seq2, _, _, _ = align_seqs(seq1, seq2)

    scores = []
    for ch1, ch2 in zip(a_seq1, a_seq2):
        if ch1 == ch2:
            continue
        if (ch1, ch2) in matrix:
            scores.append(matrix[(ch1, ch2)])
        elif (ch2, ch1) in matrix:
            scores.append(matrix[(ch2, ch1)])

    return np.mean(scores)

def vals_to_csr(vals, rows, cols, shape, split_negative=False):
    graph = coo_matrix((vals, (rows, cols)), shape=shape)

    if split_negative:
        graph_neg = graph.copy()

        graph.data = np.clip(graph.data, 0, 1)
        graph_neg.data = np.clip(graph_neg.data, -1, 0)

        graph.eliminate_zeros()
        graph_neg.eliminate_zeros()

        return graph.tocsr(), graph_neg.tocsr()

    else:
        return graph.tocsr()

class VelocityGraph:
    def __init__(
            self,
            adata,
            seqs,
            score='lm',
            vkey='velocity',
            n_recurse_neighbors=None,
            random_neighbors_at_max=None,
            mode_neighbors='distances',
            include_set='natural_aas',
            verbose=False,
    ):
        self.adata = adata

        self.seqs = seqs
        self.seq_probs = {}

        self.score = score

        self.n_recurse_neighbors = n_recurse_neighbors
        if self.n_recurse_neighbors is None:
            if mode_neighbors == 'connectivities':
                self.n_recurse_neighbors = 1
            else:
                self.n_recurse_neighbors = 2

        if include_set == 'natural_aas':
            self.include_set = set([
                'A', 'R', 'N', 'D', 'C', 'Q', 'E', 'G', 'H', 'I',
                'L', 'K', 'M', 'F', 'P', 'S', 'T', 'W', 'Y', 'V',
            ])
        else:
            self.include_set = None

        if np.min((get_neighs(adata, 'distances') > 0).sum(1).A1) == 0:
            raise ValueError(
                'Your neighbor graph seems to be corrupted. '
                'Consider recomputing via scanpy.pp.neighbors.'
            )
        self.indices = get_indices(
            dist=get_neighs(adata, 'distances'),
            mode_neighbors=mode_neighbors,
        )[0]

        self.max_neighs = random_neighbors_at_max

        gkey, gkey_ = f'{vkey}_graph', f'{vkey}_graph_neg'
        self.graph = adata.uns[gkey] if gkey in adata.uns.keys() else []
        self.graph_neg = adata.uns[gkey_] if gkey_ in adata.uns.keys() else []

        self.self_prob = None

        self.verbose = verbose

    def compute_likelihoods(self, vocabulary, model):
        if self.verbose:
            iterator = tqdm(self.seqs)
        else:
            iterator = self.seqs

        if self.score == 'blosum62':
            return

        for seq in iterator:
            y_pred = predict_sequence_prob(
                seq, vocabulary, model, verbose=self.verbose
            )

            if self.score == 'lm':
                self.seq_probs[seq] = np.array([
                    y_pred[i + 1, (
                        vocabulary[seq[i]]
                        if seq[i] in vocabulary else
                        model.unk_idx_
                    )] for i in range(len(seq))
                ])
            else:
                raise ValueError('Invalid score {}'.format(self.score))

    def compute_gradients(self, vocabulary, model):
        n_obs = self.adata.X.shape[0]
        vals, rows, cols, uncertainties = [], [], [], []

        if self.verbose:
            iterator = tqdm(range(n_obs))
        else:
            iterator = range(n_obs)

        for i in iterator:
            neighs_idx = get_iterative_indices(
                self.indices, i, self.n_recurse_neighbors, self.max_neighs
            )

            if self.score == 'lm':
                score_fn = likelihood_muts
            elif self.score == 'blosum62':
                score_fn = likelihood_blosum62
            else:
                raise ValueError('Invalid score {}'.format(self.score))

            val = np.array([
                score_fn(
                    self.seqs[i], self.seqs[j],
                    vocabulary, model,
                    seq_cache=self.seq_probs, verbose=self.verbose,
                    natural_aas=self.include_set,
                ) for j in neighs_idx
            ])

            vals.extend(val)
            rows.extend(np.ones(len(neighs_idx)) * i)
            cols.extend(neighs_idx)

        vals = np.hstack(vals)
        vals[np.isnan(vals)] = 0

        self.graph, self.graph_neg = vals_to_csr(
            vals, rows, cols, shape=(n_obs, n_obs), split_negative=True
        )

        confidence = self.graph.max(1).A.flatten()
        self.self_prob = np.clip(np.percentile(confidence, 98) - confidence, 0, 1)

def velocity_graph(
        adata,
        model_name='esm1b',
        mkey='model',
        score='lm',
        seqs=None,
        vkey='velocity',
        n_recurse_neighbors=0,
        random_neighbors_at_max=None,
        mode_neighbors='distances',
        include_set=None,
        copy=False,
        verbose=True,
):
    """Computes velocity scores at each edge in the graph.

    At each edge connecting two sequences :math:`(x^{(a)}, x^{(b)})`,
    computes a score

    .. math::
        v_{ab} = \\frac{1}{|\\mathcal{M}|} \\sum_{i \in \\mathcal{M}}
        \\left[ \\log p\\left( x_i^{(b)} | x^{(a)} \\right) -
        \\log p\\left( x_i^{(a)} | x^{(b)} \\right) \\right]

    where :math:`\\mathcal{M} = \\left\\{ i : x_i^{(a)} \\neq x_i^{(b)} \\right\\}`
    is the set of positions at which the amino acid residues disagree.

    Arguments
    ---------
    adata: :class:`~anndata.Anndata`
        Annoated data matrix.
    model_name: `str` (default: `'esm1b'`)
        Language model used to compute likelihoods.
    mkey: `str` (default: `'model'`)
        Name at which language model is stored.
    score: `str` (default: `'lm'`)
        Type of velocity score.
    seqs: `list` (default: `'None'`)
        List of sequences; defaults to those in `adata.obs['seq']`.
    vkey: `str` (default: `'velocity'`)
        Name of velocity estimates to be used.
    n_recurse_neighbors: `int` (default: `0`)
        Number of recursions for neighbors search.
    random_neighbors_at_max: `int` or `None` (default: `None`)
        If number of iterative neighbors for an individual node is higher than this
        threshold, a random selection of such are chosen as reference neighbors.
    mode_neighbors: `str` (default: `'distances'`)
        Determines the type of KNN graph used. Options are 'distances' or
        'connectivities'. The latter yields a symmetric graph.
    include_set: `set` (default: `None`)
        Set of characters to explicitly include.
    verbose: `bool` (default: `True`)
        Print logging output.
    copy: `bool` (default: `False`)
        Return a copy instead of writing to adata.

    Returns
    -------
    Returns or updates `adata` with the attributes
    model: `.uns`
        language model
    velocity_graph: `.uns`
        sparse matrix with transition probabilities
    """

    adata = adata.copy() if copy else adata
    verify_neighbors(adata)

    if seqs is None:
        seqs = adata.obs['seq']
    if adata.X.shape[0] != len(seqs):
        raise ValueError('Number of sequences should correspond to '
                         'number of observations.')

    valid_scores = { 'lm', 'blosum62' }
    if score not in valid_scores:
        raise ValueError('Score must be one of {}'
                         .format(', '.join(valid_scores)))

    if mkey not in adata.uns or model_name != adata.uns[mkey].name_:
        velocity_model(
            adata,
            model_name=model_name,
            mkey=mkey,
        )
    model = adata.uns[mkey]
    vocabulary = model.vocabulary_

    vgraph = VelocityGraph(
        adata,
        seqs,
        score=score,
        vkey=vkey,
        n_recurse_neighbors=n_recurse_neighbors,
        random_neighbors_at_max=random_neighbors_at_max,
        mode_neighbors=mode_neighbors,
        include_set=include_set,
        verbose=verbose,
    )

    if verbose:
        logg.msg('Computing likelihoods...')
    vgraph.compute_likelihoods(vocabulary, model)
    if verbose:
        print('')

    if verbose:
        logg.msg('Computing velocity graph...')
    vgraph.compute_gradients(vocabulary, model)
    if verbose:
        print('')

    adata.uns[f'{vkey}_graph'] = vgraph.graph
    adata.uns[f'{vkey}_graph_neg'] = vgraph.graph_neg
    adata.obs[f'{vkey}_self_transition'] = vgraph.self_prob

    adata.layers[vkey] = np.zeros(adata.X.shape)

    return adata if copy else None


import tensorflow as tf
import numpy as np

print np.log(-1)


# python src/chapter7/chapter7note.py
# python3 src/chapter7/chapter7note.py
'''
Class Chapter7_1

Class Chapter7_2

Class Chapter7_3

Class Chapter7_4
'''

from __future__ import division, absolute_import, print_function

import sys as _sys
import math as _math
import random as _random
import time as _time
from random import randint as _randint
from copy import copy as _copy, deepcopy as _deepcopy
from numpy import arange as _arange

if __name__ == '__main__':
    import quicksort
    import stooge
else:
    from . import quicksort
    from . import stooge

class Chapter7_1:
    def note(self):
        '''
        Summary
        ====
        Print chapter7.1 note

        Example
        ====
        >>> Chapter7_1().note()
        '''
        print('chapter7.1 note as follow')
        print('第7章 快速排序')
        print('快速排序是一种排序算法，对包含n个数的输入数组进行排序，最坏情况的运行时间为Θ(n^2)')
        print('虽然这个最坏情况运行时间比较差，但是快速排序通常是用于排序最佳的实用选择，这是因为其平均性能相当好')
        print('快速排序期望的运行时间为Θ(nlgn),且Θ(nlgn)记号中隐含的常数因子很小')
        print('快速排序能够进行就地排序，在虚存坏境中也能很好地工作')
        print('7.1 快速排序的描述')
        print('像合并排序一样，快速排序也是基于分治模式的')
        print(' 1.分解:数组A[p..r]被划分成两个(可能为空的)子数组A[p..q-1]和A[q+1..r]')
        print('  使得A[p..q-1]中的每个元素都小于等于A(q),而且，小于等于A[q+1..r]')
        print('  下标q也在这个划分过程中进行计算')
        print(' 2.解决:通过递归调用快速排序，对子数组A[p..q-1]和A[q+1..r]排序')
        print(' 3.合并:因为这两个子数组是就地排序的(不开辟新的数组),将他们合并不需要任何操作，整个数组A[p..r]已经排好序')
        print('子数组快速排序伪代码')
        print('QUICKSORT(A,p,r)')
        print(' 1. if q < r')
        print(' 2.   q <- PARTITION(A,p,r)')
        print(' 3.       QUICKSORT(A,p,q-1)')
        print(' 3.       QUICKSORT(A,q+1,r)')
        print('排序完整的数组A，调用QUICKSORT(A,0,len(A))即可')
        print('快速排序算法的关键是PARTITION过程，它对子数组A[q..r]进行就地重排')
        print('PARTITION(A,p,r)')
        print(' 1. x <- A[r]')
        print(' 2. i <- p-1')
        print(' 3. for j <- p to r-1')
        print(' 4.  if A[j] <= x')
        print(' 5.      i <- i+1')
        print(' 6.      exchange A[i] <-> A[j]')
        print(' 7. exchange A[i+1] <-> A[r]')
        print(' 8. return i + 1')
        A = [8, 9, 6, 7, 4, 5, 2, 3, 1]
        print('数组A', _deepcopy(A), '的快速排序过程为:',
            quicksort.quicksort(A))
        A = [13, 19, 9, 5, 12, 8, 7, 4, 11, 2, 6, 21]
        print('练习7.1-1 数组A', _deepcopy(A),
            '的一步partition过程得到middle索引为：',
            quicksort.partition(A, 0, len(A) - 1))
        A = [11, 11, 11, 11, 11]
        print('练习7.1-2 数组A', _deepcopy(A),
            '的一步partition过程得到middle索引为：',
            quicksort.partition(A, 0, len(A) - 1))
        print('练习7.1-3 就一个长度为n的for循环，且一定会执行，所以时间复杂度为Θ(n)，然后用确界的夹逼定义证明')
        print('练习7.1-4 不等号方向改变即可')
        # python src/chapter7/chapter7note.py
        # python3 src/chapter7/chapter7note.py

class Chapter7_2:
    def note(self):
        '''
        Summary
        ====
        Print chapter7.2 note

        Example
        ====
        >>> Chapter7_2().note()
        '''
        print('chapter7.2 note as follow')
        print('7.2 快速排序的性能')
        print('快速排序的运行时间与划分是否对称有关，而后者由与选择了哪个元素来进行划分有关')
        print('如果划分是对称的，那么快速排序算法从渐进上与合并算法一样快，否则就和插入排序一样慢')
        print('快速情况的最坏情况划分行为发生在划分过程产生的两个区域分别包含n-1个元素和1个0元素的时候')
        print('假设每次划分都出现了这种不对称划分，划分的时间代价为Θ(n),故算法的运行时间可以递归地写为')
        print('T(n)=T(n-1)+T(0)+Θ(n),递归式的解为T(n)=Θ(n^2)')
        print('快速排序的最坏情况并不比插入排序的最坏情况更好')
        print('另外，当一个已经排序好时，快速排序运行时间Θ(n^2)，插入排序运行时间Θ(n)')
        print('快速排序最佳情况是是其中一个字问题的大小为[n/2],另一个问题的大小为[n/2]-1')
        print('在这种情况下，快速排序的运行时间要快的多，T(n)<=T(n/2)+Θ(n)')
        print('根据主定理，以上递归式的解为O(nlgn)')
        print('平衡的划分')
        print('快速排序的平均运行时间与其最佳运行时间很接近')
        print('练习7.2-1 T(1)=T(0)+Θ(n),T(2)=T(1)+Θ(n),T(n)=T(n-1)+Θ(n)')
        print(' 从第一个式子加到第n个式子，T(n)=Θ(n^2)')
        print('练习7.2-2 数组A中的每个元素都相同时，也属于元素已经排序好的情况，',
            '所以调用PARTITION子程序每次都会得到最差的分配，所以最坏情况运行时间为T(n)=Θ(n^2)')
        print('练习7.2-3 根据书中的描写降序排序好的元素，会导致每次分配都得到最差的情况，又递归式和主定理得T(n)=Θ(n^2)')
        print('练习7.2-4 对已经排序好的支票对于快速排序来说属于最差情况输入，运行时间为O(n^2)')
        print(' 而对于插入排序来说却是最优输入，运行时间为O(n)')
        print('练习7.2-5 [滑稽]但是平均的运行时间仍然为O(nlgn)')
        print('练习7.2-6 略')
        # python src/chapter7/chapter7note.py
        # python3 src/chapter7/chapter7note.py

class Chapter7_3:
    '''
    chapter7.3 content : note, function, etc..

    See Also
    ========
    Chapter7_1 Chapter7_2 Chapter7_4
    '''
    def note(self):
        '''
        Summary
        ====
        Print chapter7.3 note

        Example
        ====
        >>> Chapter7_3().note()
        '''
        print('chapter7.3 note as follow')
        print('7.3 快速排序的随机化版本')
        print('在探讨快速排序的平均性态过程中，假定输入数据的所有排列都是等可能的')
        print('但在工程中，这个假设就不会总是成立')
        print('虽然第五章介绍过一些随机算法，但是如果采用一种不同的，称为随机取样的随机化技术的话，可以使分析更加简单')
        print('在这种方法中，不是时钟采用A[r]作为主元，而是从子数组A[p..r]中随机选择一个元素')
        print('然后将这个随机元素与A[r]交换作为主元')
        print('因为主元元素是随机选择的，在期望的平均情况下，对输入数组的划分比较对称')
        A = [8, 7, 6, 5, 4, 3, 2, 1]
        print('数组[8, 7, 6, 5, 4, 3, 2, 1]的随机化快速排序：',
            quicksort.randomized_quicksort(A))
        print('练习7.3-1:大部分时候输入的待排序序列我们是不知道的，而对于快速排序来讲，一个平均的输入才能反映其算法性能，最坏情况出现的概率比较小')
        print('练习7.3-2:最佳情况调用Θ(n)次，最坏情况调用Θ(n^2)次')
        # python src/chapter7/chapter7note.py
        # python3 src/chapter7/chapter7note.py

class Chapter7_4:
    '''
    chapter7.4 content : note, function, etc..

    See Also
    ========
    Chapter7_1 Chapter7_2 Chapter7_3
    '''
    def note(self):
        '''
        Summary
        ====
        Print chapter7.4 note

        Example
        ====
        ``Chapter7_4().note()``
        '''
        print('chapter7.4 note as follow')
        print('7.4 快速排序分析')
        print('7.4.1 最坏情况分析')
        print('如果快速排序中每一层递归上所做的都是最坏情况划分，则运行时间为Θ(n^2)')
        print('7.4.2 期望的运行时间')
        print('RANDOMZIED-QUICKSORT的平均情况运行时间为O(nlgn)')
        print('运行时间和比较')
        print('quicksort的运行时间是由花在过程PARTITION上的时间所决定的。')
        print('每当PARTITION过程被调用时，就要选出一个主元元素，后续对QUICKSORT和PARTITION的各次递归调用中，都不会包含该元素')
        print('于是，在快速排序算法的整个执行过程中，最多只可能调用PARTITION过程n次，调用一次PARTITION的时间为O(1)在加上一段时间')
        print('引理7.1 设当QUICKSORT在一个包含n个元素的数组上运行时，PARTITION在第四行所做的比较次数为X,那么QUICKSORT的运行时间为O(n+X)')
        print('练习7.4-1 递归式子T(n)=max(T(q)+T(n-q-1)+Θ(n))中，T(n)=Ω(n^2)')
        print('练习7.4-2 快速排序的最佳情况运行时间为Ω(nlgn)')
        print('练习7.4-3 略')
        print('练习7.4-4 RANDOMIZED-QUICKSORT算法期望的运行时间为Ω(nlgn)')
        print('练习7.4-5 对插入排序来说，当其输入已经是几乎排好序的，运行时间是很快的')
        print(' 当在一个长度小于k的子数组上调用快速排序时，让它不做任何排序就返回。',
            '当顶层的快速排序调用返回后，对整个数组运行插入排序来完成排序过程。',
            '这一排序算法的期望运行时间为O(nk+nlg(n/k))')
        print('练习7.4-6 PARTITION过程做这样的修改，从数组A中随机地选出三个元素，并围绕这三个数的中数(即这三个元素的中间值)进行划分',
            '求出以a的函数形式表示的、最坏情况中a:(1-a)划分的近似概率')
        A = [13, 19, 9, 5, 12, 8, 7, 4, 11, 2, 6, 21]
        print('思考题7-1：数组A', _deepcopy(A), '的HOARE-PARTITION算法过程为:',
            quicksort.hoare_partition(A, 0, len(A) - 1))
        print('数组A', _deepcopy(A), '的HOARE-QUICKSORT的过程为：', quicksort.hoare_quicksort(A))
        print('思考题7-2:对快速排序算法的另一种分析')
        print(' 着重关注每一次QUICKSORT递归调用的期望运行时间，而不是执行的比较次数')
        print(' a) 给定一个大小为n的数组，任何特定元素被选为主元的概率为1/n')
        print('思考题7-3 Stooge排序')
        A = [8, 7, 56, 43, 21]
        print('数组A', _deepcopy(A), '的Stooge排序结果为:', stooge.stoogesort(A), A)
        print('思考题7-4 快速排序的堆栈深度')
        print(' 7.1中的快速排序算法包含有两个对其自身的递归调用,但是第二个递归不是必须的')
        A = [8, 7, 56, 43, 21]
        print('数组A', _deepcopy(A), '的尾递归快速排序结果为:', quicksort.morequicksort(A))
        print('思考题7-5 \"三数取中\"划分 也就是主元素RANDOMIZED-QUICKSORT的RANDOMIZED-PARTITION过程')
        print(' 三数取中方法仅仅影响其运行时间Ω(nlgn)中的常数因子')
        print('思考题7-6 对区间的模糊排序:算法的目标是对这些区间进行模糊排序')
        print('模糊排序算法的期望运行时间为Θ(nlgn),但当所有区间都重叠时，期望的运行时间为Θ(n)')
        # python src/chapter7/chapter7note.py
        # python3 src/chapter7/chapter7note.py

chapter7_1 = Chapter7_1()
chapter7_2 = Chapter7_2()
chapter7_3 = Chapter7_3()
chapter7_4 = Chapter7_4()

def printchapter7note():
    '''
    print chapter7 note.
    '''
    print('Run main : single chapter seven!')
    chapter7_1.note()
    chapter7_2.note()
    chapter7_3.note()
    chapter7_4.note()

# python src/chapter7/chapter7note.py
# python3 src/chapter7/chapter7note.py
if __name__ == '__main__':
    printchapter7note()
else:
    pass