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

# # Useful Introduction to Python, GitHub, and LaTex
# Questions that you may be asking:
# - What is Python?
# - Why should I use it?
# - Is it venemous?
# - Why am I talking to myself?
#
# In this chapter, you are going to learn the answers to some of these questions and develop some basic skills.  These skills will be useful in later chapters and will form a springboard so that you can dive into the ocean that is Computational Physics.  The first thing that you may need is motivation, which lies in the history of scientific computing.
#
# ## A History
#
# A **computer** is a machine that can carryout sequences of arithmetic or logical operations.  It requires instructions in a specific order (i.e., syntax), and space to hold intermediate/final results (i.e., memory).  Prior to the 20th Century, a computer was a mechanical device that allowed users to perform arithmetic calculations quickly.  An abacus is one such device:
#
# | ![abacus](https://upload.wikimedia.org/wikipedia/commons/a/af/Abacus_6.png) |
# |:--:|
# |The Chinese suanpan. The number represented on this abacus is 6,302,715,408. (wikipedia:abacus)|
#
# The principle of the modern computer was proposed by **Alan Turing** (1936) in his paper On Computable Numbers. Turing proposed a "Universal Computing machine" that is capable of computing anything that is computable by executing instructions (program) stored on tape, allowing the machine to be programmable. The fundamental concept of Turing's design is the stored program, where all the instructions for computing are stored in memory. **Von Neumann** acknowledged that the central concept of the modern computer was due to this paper.
#
# **Colossus** was the world's first electronic digital programmable computer, where it used a large number of valves (vacuum tubes). It had paper-tape input and was capable of being configured to perform a variety of boolean logical operations on its data.  The **ENIAC** (Electronic Numerical Integrator and Computer) was the first electronic programmable computer built in the United States. Like the Colossus, a "program" on the ENIAC was defined by the states of its patch cables and switches. Once a program was written, it had to be mechanically set into the machine with the manual resetting of plugs and switches.
#
# | ![ENIAC](https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Eniac.jpg/1280px-Eniac.jpg)
# |:--:|
# |ENIAC was the first electronic device in the U.S. and performed ballistics trajectory calculations for the United States Army. (wikipedia:computer)|
#
# Along with military applications, computers became the catalyst for many scientific and engineering breakthroughs.  In the 1950s, IBM developed the programming language **FORTRAN** (Formula Translation) as a general-purpose, compiled imperative programming language that is especially suited to numeric computation and scientific computing.  Many scientific programs were developed in FORTRAN and are still in use today in computationally intensive areas such as numerical weather prediction, finite element analysis, computational fluid dynamics, geophysics, **computational physics**, crystallography and computational chemistry.
#
# Before the development of disk files, text editors and terminals, programs were most often entered on a keypunch keyboard onto 80-column punched cards, one line to a card. The resulting deck of cards would be fed into a card reader to be compiled. Punched card codes included no lower-case letters or many special characters, and special versions of the IBM 026 keypunch were offered that would correctly print the re-purposed special characters used in FORTRAN.  Reflecting punched card input practice, FORTRAN programs were originally written in a fixed-column format, with the first 72 columns read into twelve 36-bit words.
#
# | ![Punchcard](https://upload.wikimedia.org/wikipedia/commons/5/58/FortranCardPROJ039.agr.jpg)
# |:--:|
# |FORTRAN code on a punched card, showing the specialized uses of columns 1–5, 6 and 73–80. (wikipedia:fortran)|
#

# ## Why Python?
#
# Fast-forwarding from punchcards to the modern era, other computer languages (C, C++, Java) were developed to improve upon FORTRAN in terms of their usage (i.e., easier to code) and compilation speed.  FORTRAN has also changed over many versions that migrated it from punchcards to completely digital files.  The programming language C rivals FORTRAN for speed and has become the backbone of modern computing.  Back to Python.  What is it?
#
# Python is a scripting language, which means it takes commands in a more human-like language and translates those commands into machine code (zeros and ones).  The Python interpreter does this one line at a time, but its translation into machine code is less efficient than C or FORTRAN.  For applications where speed is key, Python is used as an interface while portions of the code are passed into the speedier languages.  Alternatively, one can develop their Python code using C extensions for Python (or Cython).
#
# Scientists are quickly adopting Python within a range of fields because it is open-source (i.e., free) and much easier for students to learn.  Especially if you have any prior experience in programming, where Python strips away many of the quirks from legacy languages.  Due to the simplified structure of Python programs, you can easily begin programming and get to your results.
#

# ##  Python Basics
#
# ### Create a Jupyter Notebook
#
# **To create a Jupyter notebook in Microsoft VS Code**: open the command palette (Windows: Ctrl + Shift + P, iOS: Command + Shift + P) and select the command "Jupyter: Create New Blank Notebook".
#
# Python programs include 3 elements: 1) import statements/libraries, 2) custom functions, and 3) regular statements.  Although this document has flowing text, most Python programs are written to be as short as possible.  For these examples, we are using an interactive Python mode, where code blocks are designated with
# > In [ ]:
#
# for input. These codeblocks can be run and re-run with tweaks, but you need to watch out for dependencies (i.e., assumptions that some other code exists in memory).  Let's start by importing a commonly used modules **numpy** and **matplotlib**

# In[1]:

import numpy as np
import matplotlib.pyplot as plt

# These import statements will allow us to use functions with the respective modules.  The numpy module was developed to make managing data structures easier, while the matplotlib library introduces functions from the Matlab Plotting Library  so that we can visualize our results.  To produce a graph, let's first generate some data:

# In[2]:

x = np.arange(1,5,0.5)
y = 3*x + 2

# The first line uses the 'arange' function from numpy, where we use 'np' as a shortcut label.  The 'arange' function takes in up to 3 values as input: starting value, stopping value, and step size to generate a numpy array with an interated sequence:
#
# >x = np.array([1. , 1.5, 2. , 2.5, 3. , 3.5, 4. , 4.5])
#
# Note that the generated values are floats (i.e., real numbers) and the final value of the array is 4.5 and **not** 5.0.  Python generators typically exclude the end point.  If we wanted to use integers (i.e., whole numbers) instead, then we could have used the 'range' function from pure Python without refferring to the numpy module and excluded the step size as input (e.g., range(0,5)).
#
# The second line generates another numpy array using the values stored in 'x'.  More precisely, it makes a temporary copy of 'x', where it performs a multiplication by 3 and addition of 2 to each element of the copied array.  Finally, the temporary copied array is stored as a variable called 'y'.
#
# >y = np.array([ 5. ,  6.5,  8. ,  9.5, 11. , 12.5, 14. , 15.5])

# Now, we are going to make a plot of the data that we generated.  To create a canvas in matplotlib, we start with the figure function and store it in a container called 'fig'.  The suptitle function uses a string (i.e., array of characters) as input and sets the keyword argument fontsize equal to 16 pt font.  We also introduce a container 'ax' that holds the axes for the canvas through the add_subplot function that takes a three digit number (row column index) as input.

# In[3]:

fig = plt.figure()
fig.suptitle('My first graph', fontsize=16)
ax = fig.add_subplot(111)
ax.plot(x,y,'k.',ms=18)

# To plot our generated data, we simply used the plot function on the axes (the Axes class inherits the plot function from the matplotlib module).  The plot function used the x and y arrays to generate the points, but we also had to tell it that we wanted black points ('k.') with a markersize (ms) equal to 18 pts.
#
# However, we may want to make a lot of plots and do not want to copy/paste a bunch of times.  To reuse our commands, we define a custom function 'create_plot' that takes the parameters we may want to vary as input.

# In[4]:

def create_plot(x,y,color,marker,ms):
    fig = plt.figure()
    fig.suptitle('My first graph', fontsize=16)
    ax = fig.add_subplot(111)
    ax.plot(x,y,marker=marker,color=color,ms=18,lw=0)

# Notice that Python uses a tab to delineate what should be included in the function.  Now let's change the color to blue ('b'), the marker to squares ('s') and the markersize to 12 points.

# In[5]:

create_plot(x,y,'b','s',12)

# We can even change the x,y values to something more complicated.

# In[6]:

x = np.arange(0,2*np.pi,0.5)
y = 3*np.sin(x+1)-2
create_plot(x,y,'r','^',1)

# There are many options within matplotlib, where you will find some more useful than others over time.  If in doubt, you can always check the **matplotlib gallery** (https://matplotlib.org/stable/gallery/index.html) or stackexchange (https://stackexchange.com/).  It is likely that someone has already run into your problem/customization and other people have provided a 'possible' solution (not all solutions are good!).  The last bit of customization for this section is to:
#
# 1. add axis labels
#     - ax.set_xlabel(*string,fontsize=22) or ax.set_ylabel(*string,fontsize=22)
# 2. modify tick marks
#     - ax.tick_params(axis='both', direction='out',length = 12.0, width = 8.0)
# 3. modify tick labels
#     - using ```$``` opens the latex interpreter for custom symbols (e.g., ```$\pi$``` = $\pi$)
#     - ax.set_xticklabels(['0','$\pi$/2','$\pi$','$3\pi/2$','2$\pi$'])
# 4. set sensible axis limits
#     - ax.set_xlim(0,2*np.pi)
# 5. add a legend
#     - add the keyword 'label' to the plot command and set equal to a string
#     - ax.legend(loc='upper right',fonstize=20)

# In[7]:

fig = plt.figure()
fig.suptitle('My first graph', fontsize=16)
ax = fig.add_subplot(111)
ax.plot(x,y,marker='*',color='orange',ms=18,lw=0,label='SHO')

ax.set_xlabel("Angle (rad.)",fontsize=22)
ax.set_ylabel("Amplitude (ft/s)",fontsize=22)
ax.tick_params(axis='both', direction='out',length = 12.0, width = 8.0)
ax.set_xlim(0,2*np.pi)
ax.set_xticks([0,np.pi/2,np.pi,3.*np.pi/2.,2*np.pi])
ax.set_xticklabels(['0','$\pi$/2','$\pi$','$3\pi/2$','2$\pi$'])
ax.legend(loc='upper center',fontsize=20)

# Figures can be saved to file using the `savefig` function, which takes the filename as an argument.  Additional keyword arguments (kwargs) can be applied that alter the display of the figure (e.g., dpi = 300 sets the dots per inch to 300).

# In[ ]:

fig.savefig("SHO.png",bbox_inches='tight',dpi=300)

# ### Modules to solve problems
#
# There are many modules within modules, where object-oriented notation is used to access them.  For example, you can generate a random number between 0--1 using the rand() function within the random module within the numpy module (i.e., np.random.rand()).

# In[ ]:

np.random.rand()

# Another extremely useful library for physicists is the linear algebra package in numpy. This package provides very fast routines for calculating anything having to do with matrices: eigenvalues, eigenvectors, solutions of systems of linear equations, and so on.
#
# >Example 1: In electronics, Kirchhoff's laws are used to solve for the currents through components in circuit networks. Applying these laws gives us systems of linear equations, which can then be expressed as matrix equations, such as:
#
# ![Kirchoff](Kirchoff_matrix.png)

# In[ ]:

A = np.matrix([[-13,2,4],[2,-11,6],[4,6,-15]]) #matrix is an array of an array
B = np.array([5,-10,5])
np.linalg.solve(A,B)

# ### Reading and writing data from a file

# Python allows for many forms of input.  There are commands to request user input manually via the terminal window, where this can be a direct request or by command line arguments.  For computational physics, it is likely that you want to know how the output of a program changes with slightly different inputs or to visualize a data set.  At this point manual input is impractical and cumbersome.  In this section, you will learn how to read and write to data files so that you can save your work for later or prepare it for others to use.  Files are created using the open() function that takes a filename and a mode as input.  Here's a table detailing the different modes available to the open() function.
#
# | Character   | Meaning |
# | ----------- | :-----------: |
# | 'r'      | reading from a file; returns error if not found |
# | 'w'   | writing to a file; creates a new file/overwrites existing file) |
# | 'x'   | open for exclusive creation; failing if the file already exists |
# | 'a'   | open for writing; appending to the end of the file if it exists |
# | 'b'   | binary mode |
# | 't'   | text mode (default) |
# | '+'   | open for updating (reading and writing) |
#
# Let's generate some data using the equation for a simple pendulum:
#
# $\ddot{\theta} = \sqrt{\frac{g}{l}} \theta$,
#
# which has the solution
#
# $\theta(t) = \theta_{max} \sin \left(\sqrt{\frac{g}{l}} t \right)$,
#
# where $g$ represents the acceleration due to gravity, $l$ is the length, and $t$ is the time.  Now let's define some variables.
#

# In[ ]:

g = 9.81 #m/s^2 Earth gravity near the surface
l = 1 #meter long string
t = np.arange(0,10.5,0.01) #10 seconds with 0.5 sec increments
theta_max = 45 #maximum amplitude in degrees
fname = "Simple_Pendulum.txt"

theta = theta_max*np.sin(np.sqrt(g/l)*t)
out = open(fname,'w') #create a text file and open it for writing
out.write("#time (s), theta (deg)\n") #write a header

for i in range(0,len(t)):
    out.write("%1.2f, %1.3f\n" % (t[i],theta[i]))
out.close()

# The data is written to "Simple_Pendulum.txt", which exists in the same directory as this Jupyter notebook.  In practice, you may want to include an absolute path in the filename.  Using our code from before, we will read the data from the file.  Notice that we formatted the header with a \# symbol and the lines are comma delimited.  We are going to take advantage of this using the 'genfromtxt' function from numpy.

# In[ ]:

x,y = np.genfromtxt("Simple_Pendulum.txt",delimiter=',',comments='#',unpack=True)

fig = plt.figure()
fig.suptitle('Simple Pendulum', fontsize=16)
ax = fig.add_subplot(111)
ax.plot(x,y,'r-',lw=3)

ax.set_xlabel("Time (s)",fontsize=22)
ax.set_ylabel("Amplitude (deg)",fontsize=22)
ax.tick_params(axis='both', direction='out',length = 12.0, width = 8.0)
ax.set_xlim(0,10)
ax.set_xticks(np.arange(0,12,2))

# ## GitHub
#
# GitHub is a platform used by many disciplines to make code easier to develop, track, store, and share.  The platform provides a series of [guides](https://guides.github.com/) that introduce new users through common practices on GitHub.  In this section, we will focus on the [Hello World](https://guides.github.com/activities/hello-world/) guide.  Throughout the course, you will develop scripts to perform different tasks and a good way to organize your work is through a GitHub repository.  Repositories are free to create (as long as they are < 50 MB) and can serve as a useful place to backup your code.  To start this guide, you will need to have a GitHub.com account already created.
#
# ### Create a repository (**repo**)
# A repository (or repo) can contain any kind of code (python, C++, Fortran, etc), spreadsheets, data files, Jupyter notebooks, and most types of files that you can think of. To create a repository:
# - In the upper right corner, click + and then select **New Repository**.
# - Name your repo `hello-world` for this exercise.
# - Adding a short description is helpful for you (and others) to get a quick idea what the repo contains.
# - **Public** repos can be seen by anyone on the internet.  Select this option once your repo is ready for the world to see.  **Private** repos allow the creator more control about who is able to view the repo, where users are allowed access one-by-one.
# - Select **Initialize this repository with a README**.  Since your repos will likely be private, there is no need to add a license.  A license tells others what they may or *may not* do with your public work.  Academic researchers use a Creative Commons (CC) or MIT License that allow for pretty broad usage, where scientists within industry are more selective.
# - Click **Create repository**.
#
# ### Create a Branch
# Branching is the way to work on experimental parts of your repo.  By default your repo has a new branch named *main*, which is what each branch eventually converges to. To create a new branch:
#
# - Go to your repo `hello-world`
# - Click the drop down that says **branch:main**.
# - Type a branch name, `readme-edits`, into the new branch text box.
# - Select the blue **Create branch** box.
#
# Now you have two branches, `main` and `readme-edits`.  These two branches are identical right now, but the changes you make to the `readme-edits` branch will not directly affect the `main` branch.
#
# ### Make and commit changes
#
# After creating your *readme-edits* branch, you should now be on the code-view for that branch.  Let's make some edits and see what happens.  Saved changes on GitHub are called commits.  Each commit also contains a description explaining why a particular change was made.  This is particurly useful for developing code over large periods of time or within groups.  Let's make some changes to the **README.md** file and commit those to the repo:
# - Click the **README.md** file.
# - Click the pencil icon in the upper right corner to edit.
# - In the editor, write a bit about yourself.  What are your research interests?
# - Write a commite message that describes your changes (e.g., "I'm super awesome because I can make changes to my very own GitHub repo!")
# - Click the **Commit changes** button.
#
# The changes are now saved to the `readme-edits` branch, so now this branch is different than the `main` branch*.
#
# ### Open a Pull Request
#
# A pull request is a way for you (and others) to suggest changes to the *main* branch.  Since you made changes to the `readme-edits` branch, you can now issue a `pull request`.  A pull request will show the differences between both branches.  The changes, addition, and subtraction are shown in green and red.  In this process, you can also use the @mention system with other GitHub users to have discussions about the pull request and receive feedback.  Now we'll open a pull request so that you can see how to review changes (although you may not do this as often for your own repos).
#
# - Click the **Pull Request** tab, then click the green **New pull request** button.
# - In the **Example Comparisons** box, select the `readme-edits` branch to compare with `main`.
# - Look over the changes between branches and make sure they are what you want. Then click the green **Create Pull Request** button.
# - Give your pull request a title and write a brief description of your changes.  Logging the changes in your edits will make it easier to diagnose problems later.
# - Click **Create pull request**!
#
# ### Merge your Pull Request
#
# In the previous 2 sections, you made a new branch of `hello-world`, edited the branch, and submitted a pull request.  The final step is to bring the changes to the `main` branch.  To merge your `readme-edits` branch into `main`:
#
# - Click the green **Marge pull request** button to merge the changes into `main`.
# - Click **Confirm merge**.
# - Delete the `readme-edits` branch, since its changes have been incorporated with the **Delete branch** button in the purple box.
#
# ### GitHub Desktop
#
# The above guide can be used to create a repository through the web interface of GitHub.  When working with your own repos, it is a little easier to use **GitHub Desktop**, which is a desktop application that simplifies pushing changes to the `main` branch.  This requires a software install, which can be found [here](https://desktop.github.com/).  After installing the software, your can:
#
# - Clone your repository to your local path (File --> Clone repository).
# - Open your repo from your local path and edit your files.
# - After saving your files in the local path, the changes will appear in GitHub Desktop (simliar to a pull request).
# - Add a description of the changes and click commit to the *main* branch (blue button)
# - Click the **Push origin** button (update all your changes back to the web version of GitHub)
#

# ## LaTex (Preparing your work)
#
# In the past, scientists had to learn two skills: scientific inquiry and typesetting.  However, mathematicians developed a typesetting software, LaTex, that was more programattic, which made it easier to typeset equations within a document.  In this course, you will need to communicate your results to others (especially your instructor), where you will use LaTex.  To make it easier, we will use the online platform **Overleaf**.  Similar to the guide for GitHub, it is assumed that you have successfully created an Overleaf account.  Note that Overleaf provides its own guides that can be found [here](https://www.overleaf.com/learn/how-to/Creating_a_document_in_Overleaf).
#
# ### Creating a project
#
# Creating a project in Overleaf can be accomplished in **two** ways: 1) start a project from scratch or 2) start a project from a template.  To start a project from scratch:
#
# - Click the green **New Project** button
# - Select **Blank Project**
# - Name your project
# - Click **Create** and then the editor will open
#
# To start a project from a template:
#
# - Click the green **New Project** button
# - Select **Academic Journal** from the Templates
# - Find the **RevTex** tag at the bottom (collection of green tags) and Click it
# - Select the **RevTex 4.2** template from the American Physical Society
#
# When preparing your class assignments, you can build your project from scratch so that you can learn more about the LaTex environment.  To submit each of your projects, you will need to build from the **RevTex** template because it will import the default style for a Journal like *Physical Review*.
#
# ### Your 1st Document
#
# Open the blank project that you created in the previous section.  In this project, we will create a simple working example (look [here](https://www.overleaf.com/learn/latex/Creating_a_document_in_LaTeX)).  A LaTex document contains some *front* matter, a *body*, and some *back* matter.  The front matter tells the LaTex compiler what kind of document you are trying to create, how should the document be formatted *globally*.  The body will have the text, figures, and tables in a manner similar to most word processors.  The back matter will tell the LaTex compiler how to format references or setup an Appendix.
#
# Here's a simple working example:
#
# >\documentclass{article}
#
# >\begin{document}
#
# >First document. This is a simple example, with no extra parameters or packages included.
#
# >\end{document}
#
# This example will create an **article** document, adds the text, and compiles it as a *pdf* file in the right window.
#
# ### The Front Matter
#
# The *front* matter is everything before the *>\begin{document}* line.  Here, we will replace the *\documentclass{article}* with the following:
#
# >\documentclass[12pt, letterpaper]{article}
#
# >\usepackage[margin=1.0in]{geometry}
#
# >\usepackage[utf8]{inputenc}
# >
# >\title{First document}
#
# >\author{Your Name \thanks{funded by the Overleaf team}}
#
# >\date{\today}
#
# Here's an explanation of what we just added:
#
# >\documentclass[12pt, letterpaper]{article}
#
#
# This defines the type of document with some additional parameters inside brackets that are comma-separated can be passed to the command. The extra parameters set the font size (12pt) and the paper size (letterpaper). Note that Overleaf uses a European LaTeX distribution, which produces documents in A4 size by default, so letterpaper is important.  Another important parameter that can be passed to the \documentclass command is twocolumn if you want your text in a two-column format and twoside for two-side paper sheet printing.
#
#
# >\usepackage[margin=1.0in]{geometry}
#
#
# This defines the page margins.  Everything you submit should have 1 inch margins.  More detail about paper size, orientation, and margins can be found [here](https://www.overleaf.com/learn/latex/Page_size_and_margins)
#
# >\usepackage[utf8]{inputenc}
#
#
# This is the encoding for the document to allow special characters beyond ASCII to be used in the text. It can be omitted or changed to another encoding but utf-8 is recommended.
#
# The next three lines are self-descriptive.  But you will need the *\maketitle* **after** the \begin{document} for those items to appear.
#
# ### The Body
#
# The body is similar to what you would find in a normal word processor.  The first element to add to the body is the *abstract*.  An abstract informs the reader what will follow in the rest of the document.  For your class projects, this will be a <250 word summary of your work.  In your class assignments, you can write a summary of what you learned so that when *future* you comes back to it, it will hopefully make sense.  To create an abstract:
#
# >\begin{abstract}
#
# >This is a simple paragraph at the beginning of the document. A brief introduction to the main subject.
#
# >\end{abstract}
#
# you must create an abstract environment.  Environments (e.g., abstract, figure, table, equation) always have a \begin and an \end statement to tell the LaTex compiler that it needs to do something different here and for how long.
#
# There are other elements that in the body that **don't** need an environment because they are self explanatory to the compiler when to stop.  For example, you can organize the body using sections, subsections, subsubsections, etc.  Although an environment is not required, you do need a `\` to tell the compiler that it isn't really text either.
#
# >\section{Introduction}
#
# >\section{Methods}
#
# >\subsection{Newton's 1st Law}
#
# >\subsubsection{Einstein's Theory of General Relativity}
#
# >\section{Results}
#
# In the above examples:
#
# - The first section is the Introduction and it will be enumerated starting from 1.  The LaTex compiler will know when the Introduction ends when it encounters the next \section command.
# - The second section called Methods (enumerated with 2) has a subsection called Newton's 1st Law.  Subsections are then enumerated with a ".#", where the above subsection is 2.1. Subsubsections will gain an additional ".#" so that it will numbered 2.1.1.
# - The third section (enumerated with 3) tells the compiler to go back to the previous level in the tree.
#
# Between section commands, this is where the main text will appear. In contrast to a word processor (like Word), LaTex allows for inline commands.  The most common inline commands are:
#
# - Enter math mode with \$ signs.  Suppose you need the greek letter $\alpha$, then you can easily add it to your text by placing the \alpha between \$ \$.  This is less cumbersome than having to define a macro in Word.  Anything that you could do in an equation, can be done in math mode (e.g., \frac{1}{2}x^2 in between \$ signs appears as $\frac{1}{2}x^2$)
# - Cite a reference.  This will be explained more later.
# - Add a comment to the writer using \%.  Everything on a line that comes after \% will not appear in the pdf document, but can serve as a note for later.
# - You can add text formatting for **bold**, *italics*, or $\texttt{texttype}$ using: \textbf, \textit, or \texttt.
#
# Figures and tables are created using an environment (recall that this means begin and end statements).  For many of the extra features for figures, you will need to add `\usepackage{graphicx}` to the front matter. Figures and tables have similar structures as you can see in these basic examples:
#
# >\begin{figure}[!h]
#
# >\centering
#
# >\includegraphics[width=\linewidth]{filename.png}
#
# >\caption{This is the figure caption, which describes basic aspects of the figure to the reader. \label{fig:Fig1}}
#
# >\end{figure}
#
# and
#
# >\begin{table}[!h]
#
# >\centering
#
# >\begin{tabular}{c|c|c}
#
# >\hline
#
# >cell11 & cell12 & cell13 \ \
#
# >cell21 & cell22 & cell23 \ \
#
# >cell31 & cell32 & cell33
#
# >\end{tabular}
#
# >caption{This is a table caption, which describes the basic apsects of the table or gives the table a title.  \label{tab:Tab1}}
#
# >\end{table}
#
# The figure and table environment have a [] after the begin statement, where positioning arguments are placed (e.g., !=override default, h=here, t=top of page, b=bottom of page).  This is followed by \centering, which tells the LaTeX compiler to place the figure/table in the center of the page (<------center------->).  The figure environment relies on the `\includegraphics` command from the graphicx package, which this has a [] for arguments that tell the LaTeX compiler how to scale the figure.  In the above example, the figure is scaled so that the width of the figure spans an entire line.  The {} after \includegraphics holds the filename of the image (e.g., `filename.png`), where LaTex can handle many filetypes (e.g., png, jpg, and pdf are the most common).  The table environment is different in that it holds *tabular* environment within *table* environment.  The tabular environment has arguments {} that tell the LateX compiler:
#
# - the number of columns (implicitly),
# - the alignment within columns (explicitly), and
# - the borders between columns.
#
# The columns can be left (l), center (c), or (r) aligned, where the total number of these characters indicates the number of columns (3l's = 3 columns left aligned).  The \hline command draws a horizontal line that spans the width of the table.  The data within the table is separated using the `&` symbol and a row is terminated with `\\`.  The last row **doesn't** need to be terminated with `\\` and the `\end{tabular}` must follow on the next line.
#
# Both figures and tables use the *caption* environment to hold the description and a *label* environment so that the figure/table can be dynamically referenced in the text (using `\ref{fig:Fig1}` or `\ref{tab:Tab1}`).  The beauty of LaTex is that the referencing system keeps track of the figure and table numbering so that if the order of tables are switched, then the numbering is updated with the next compilation.  Finally, both figures and tables **require** an \end statement.
#
# The LaTex compiler will abort or crash if a given environment does not have matching {} or begin/end statements.  This is usually indicated in the compilation log (upper right button **View Logs**).
#
# ### The Back Matter
#
# The back matter contains supplementary information to the body (e.g., acknowledgments, references, appendices).  The acknowledgments (**note the spelling**) is an environment so it needs a \begin{acknowledgments} and an \end{ackowledgments}, where this section is where you would thank particular individuals/institutions that aided in the completion of the project (e.g., converstations, resources, proofing).
#
# An appendix is started with the `\appendix` command, which behaves much like the body but includes supplementary material (e.g., a derivation of an equation, how a new method was verified) and it's labeled with letters (A,B,C,...).  For you, this is where you can put the code that you generate using the \verbatim environment.  Addtional guides on how to include code in Latex can be found [here](https://www.overleaf.com/learn/latex/Code_listing).
#
# In addition to the ease of generating equations, LaTex is preferred because it makes referencing easier too with BibTex.  At the end of your document, references are included by telling LaTex the referencing style (e.g., apsrev4-2.bst for *Physical Review*) and a database of references (e.g., references.bib) through supplemental files.  You must include the following for the references:
#
# >\bibliographystyle{style_filename.bst}
#
# >\bibliography{reference_filename.bib}
#
# The reference database (*.bib file) will contain entries like the following:
#
# ```
# @ARTICLE{Berman1983,
#    author       = "Berman, Jr., G. P. and Izrailev, Jr., F. M.",
#    title        = "Stability of nonlinear modes",
#    journal      = "Physica D",
#    volume       = "88",
#    pages        = "445",
#    year         = "1983",
# }
# ```
# where the `Berman1983` is a label used for the inline citation command within the body (e.g., \cite{Berman1983}).  The quotation marks for each field tell BibTex not to change the formatting (i.e., captialization).  There are different types of environments that correspond to different references (e.g., ARTICLE, BOOK, INPROCEEDINGS, etc.).  Remember that environments require an opening { and closing }.
#
# The inline citations have more variations within Astronomy because that community uses the author (year) referencing style, while Physical review uses a [number] style.  In this course, you will use the latter.

# ## Problems
# - Complete the following problems in a Jupyter notebook, where you will save your results as an external file (*.png).
# - Create a LaTex document with:
#     - an abstract summary
#     - sections for each problem that state the problem, summarize what you did, and display the results
#     - include a reference for each solution (this can be textbooks)
#
# 1. Graph both of the following functions on a single figure, with a usefully sized scale.
#
#     a. $x^4e^{-2x}$
#
#     b. $[x^2e^{-x}\sin(x^2)]^2$
# 2. The file Ba137.txt contains two columns. The first is counts from a Geiger counter, and the second is time in seconds.
#
#     a. Make a useful graph of this data.
#
#     b. If this data follows an exponential curve, then plotting the natural log of the data (or plotting the raw data on a logrithmic scale) will result in a straight line. Determine whether this is the case, and explain your conclusion with an appropriate graph.
# 3. The data in the file Ba137.txt is actual data from a radioactive decay experiment; the first column is the number of decays $N$, the second is the time $t$ in seconds. We'd like to know the half-life $t_{1/2}$ of $^{137}$Ba. It should follow the decay equation
# $N = N_oe^{-\lambda t}$
# where $\lambda = \log(2)/\log(t_{1/2})$. Using the techniques you've learned in this notebook, load the data from file Ba137.txt into appropriately-named variables. Experiment with different values of $N_o$ and $\lambda$ to create a plot of the resulting equation on top of the data. What is your best estimate for $t_{1/2}$?
# 4. The normal modes and angular frequencies of those modes for a linear system of four coupled oscillators of mass m, separated by springs of equal strength k, are given by the eigenvectors and eigenvalues of M, shown below.  Find the eigenfrequencies (Try [linalg.eig](https://numpy.org/doc/stable/reference/generated/numpy.linalg.eig.html#numpy.linalg.eig) from numpy).
#
#     ![Coupled_Oscillator](Coupled_Oscillator_matrix.png)
# 5. Create a single plot that shows separate graphs of position, velocity, and acceleration for an object in free-fall. Your plot should have a single horizontal time axis and separate stacked graphs showing position, velocity, and acceleration each on their own vertical axis. The online matplotlib gallery will probably be helpful! Print the graph, with your name in the title.


#!/usr/bin/env python

# This script should return the x y z and orientation coordinates of the end effector of the left limb.
# PLEASE ADD YOUR CODE WHERE INDICATED
# Avoid modifying the rest of the code if not necessary
#
# Authors: Stefano Pietrosanti - s.pietrosanti@pgr.reading.ac.uk
#          Guy Butcher

import rospy
import baxter_interface
import numpy
from geometry_msgs.msg import (
    PoseStamped,
    Pose,
    Point,
    Quaternion,
)

print("MIM tutorial: forward kinematics.")
# Initialising ROS node
rospy.init_node("SSE_forward_kinematics")

######################  INSERT YOUR CODE HERE
# Create a "Limb" instance called "left_arm" linked to Baxter's left limb

left_arm = baxter_interface.Limb('left')

# Create a "pose" variable which holds the output of endpoint_pose()

pose = left_arm.endpoint_pose()

######################

# Return pose
print("Endpoint coordinates:")
print("X: " + str(pose['position'].x))
print("Y: " + str(pose['position'].y))
print("Z: " + str(pose['position'].z))


# ---
# 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="POrA585UFLms"
#
#
# ![GitHub](https://img.shields.io/github/license/probml/pyprobml)
#
# Colab authors: Kevin P. Murphy (murphyk@gmail.com) and Mahmoud Soliman (mjs@aucegypt.edu)
#
#

# + id="I3CEU8u0FQR0"
# Attribution
# This notebook is based on the following:
# https://github.com/mjsML/VizDoom-Keras-RL
# https://colab.research.google.com/github/keras-team/keras-io/blob/master/examples/rl/ipynb/actor_critic_cartpole.ipynb

# + id="qEYlbLuzFh_b" colab={"base_uri": "https://localhost:8080/"} outputId="b0661fbe-0e44-4a7a-ae7b-d5c081893257"
# Imports
from tensorflow.python.client import device_lib
from psutil import virtual_memory
import cv2
from google.colab.patches import cv2_imshow
# %tensorflow_version 2.x
import tensorflow as tf
import os

from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.model_selection import cross_val_score

from sklearn.datasets.samples_generator import make_blobs
from  IPython import display
from matplotlib import pyplot as plt

import numpy as np

import pathlib
import shutil
import tempfile

from tqdm import tqdm

# + id="X_8KoI6UFkRo" colab={"base_uri": "https://localhost:8080/"} outputId="0055b9e3-d953-4304-830f-ad9fdd54c2e0"
#title Hardware check

def find_accelerator():

  mem = virtual_memory()
  devices=device_lib.list_local_devices()
  RAM="Physical RAM: {:.2f} GB".format(mem.total/(1024*1024*1024))
  try:
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()
    device=["TPU at "+str(tpu.cluster_spec().as_dict()['worker'])]
  except ValueError:
    device =[d.physical_device_desc for d in devices if d.device_type=="GPU"]
  if not device:
    return None, RAM
  return device ,  RAM

a,r=find_accelerator()
print("Please make sure that the statement below says Accelerator found")
print("Accelerator found:",a,r)

# + id="w3V-stpMFlJN" colab={"base_uri": "https://localhost:8080/"} outputId="5f55dbeb-d950-4b08-b707-018a03c05f3a"
#title Install the extra required packages if any
# Installation of libs as per
# https://stackoverflow.com/questions/50667565/how-to-install-vizdoom-using-google-colab

# %%bash
# Install deps from
# https://github.com/mwydmuch/ViZDoom/blob/master/doc/Building.md#-linux

apt-get install build-essential zlib1g-dev libsdl2-dev libjpeg-dev \
nasm tar libbz2-dev libgtk2.0-dev cmake git libfluidsynth-dev libgme-dev \
libopenal-dev timidity libwildmidi-dev unzip
apt-get install libboost-all-dev
apt-get install liblua5.1-dev

# + [markdown] id="M5vmaF61ZvV5"
# #Partially observed Markov decision processes (POMDPs)
# We will start by exploring POMDPs , the states of the environment, $z_{t}$ , are hidden from the agent. The agent gets to see partial observations derived from the hidden state, which we denote by
# $s_{t} \in \mathcal{S}$ these are sampled from the observation model, $p(s_{t}|z_{t})$.
#
# In this example we will work with ViZDoom and Deep Recurrent Q Network.
#
# Note that this is a quick overview example, the details will be discussed later.

# + [markdown] id="JBTCb79f72TU"
#  ## Deep Recurrent Q Network

# + id="Q0npOg2hPfrF" colab={"base_uri": "https://localhost:8080/"} outputId="70d73c24-44f4-466b-ed6b-a338474c9b2c"
#title Install ViZDoom... takes few mins

# !pip install vizdoom

# + id="5HSk5mAdPbIv" colab={"base_uri": "https://localhost:8080/", "height": 1000} outputId="8480df6f-678c-46fd-c051-309b828bc49a"
#title Clone ViZDoom-Keras-RL repo and imports
# Clone VizDoom-Keras-RL
# !git clone https://github.com/mjsML/VizDoom-Keras-RL.git
# %cd /content/VizDoom-Keras-RL
from __future__ import print_function

import skimage as skimage
from skimage import transform, color, exposure
from skimage.viewer import ImageViewer
import random
from random import choice
import numpy as np
from collections import deque
import time

import json
from keras.models import model_from_json
from keras.models import Sequential, load_model, Model
from keras.layers.wrappers import TimeDistributed
from keras.layers.core import Dense, Dropout, Activation, Flatten, RepeatVector, Masking
from keras.layers import Convolution2D, Dense, Flatten, MaxPooling2D, Input, AveragePooling2D, Lambda, Activation, Embedding
#tf.keras.layers.Concatenate(axis=1)([x, y])
from keras.layers.recurrent import LSTM, GRU
#from keras.optimizers import SGD, Adam, rmsprop
from keras.optimizers import SGD, Adam
from keras import backend as K

from vizdoom import DoomGame, ScreenResolution
from vizdoom import *
import itertools as it
from time import sleep
import tensorflow as tf

from networks import Networks

# + id="7Pqux1c-Trj4"
#title Setup ViZDoom with defend the center scenario

#TF2 TF1 compatibility
config = tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.compat.v1.Session(config=config)
tf.compat.v1.keras.backend.set_session(sess)

from drqn import ReplayMemory,DoubleDQNAgent,preprocessImg

game = DoomGame()
game.load_config("/content/VizDoom-Keras-RL/defend_the_center.cfg")
game.set_sound_enabled(True)
game.set_screen_resolution(ScreenResolution.RES_640X480)
game.set_window_visible(False)
game.init()

game.new_episode()
game_state = game.get_state()

misc = game_state.game_variables  # [KILLCOUNT, AMMO, HEALTH]
prev_misc = misc

action_size = game.get_available_buttons_size()

img_rows, img_cols = 64, 64
img_channels = 3  # Color channel
trace_length = 4  # Temporal Dimension

state_size = (trace_length, img_rows, img_cols, img_channels)
agent = DoubleDQNAgent(state_size, action_size, trace_length)

agent.model = Networks.drqn(state_size, action_size, agent.learning_rate)
agent.target_model = Networks.drqn(
    state_size, action_size, agent.learning_rate)

s_t = game_state.screen_buffer  # 480 x 640
s_t = preprocessImg(s_t, size=(img_rows, img_cols))

is_terminated = game.is_episode_finished()

# + id="KEOjhw2tR1zl"
#title Start training DRQN Agent
epsilon = agent.initial_epsilon
GAME = 0
t = 0
max_life = 0  # Maximum episode life (Proxy for agent performance)
life = 0
episode_buf = []  # Save entire episode

# Buffer to compute rolling statistics
life_buffer, ammo_buffer, kills_buffer = [], [], []

while not game.is_episode_finished():

    loss = 0
    Q_max = 0
    r_t = 0
    a_t = np.zeros([action_size])

    # Epsilon Greedy
    if len(episode_buf) > agent.trace_length:
        # 1x8x64x64x3
        state_series = np.array(
            [trace[-1] for trace in episode_buf[-agent.trace_length:]])
        state_series = np.expand_dims(state_series, axis=0)
        action_idx = agent.get_action(state_series)
    else:
        action_idx = random.randrange(agent.action_size)
    a_t[action_idx] = 1

    a_t = a_t.astype(int)
    game.set_action(a_t.tolist())
    skiprate = agent.frame_per_action
    game.advance_action(skiprate)

    game_state = game.get_state()  # Observe again after we take the action
    is_terminated = game.is_episode_finished()

    # each frame we get reward of 0.1, so 4 frames will be 0.4
    r_t = game.get_last_reward()

    if (is_terminated):
        if (life > max_life):
            max_life = life
        GAME += 1
        life_buffer.append(life)
        ammo_buffer.append(misc[1])
        kills_buffer.append(misc[0])
        print("Episode Finish ", misc)
        game.new_episode()
        game_state = game.get_state()
        misc = game_state.game_variables
        s_t1 = game_state.screen_buffer

    s_t1 = game_state.screen_buffer
    misc = game_state.game_variables
    s_t1 = preprocessImg(s_t1, size=(img_rows, img_cols))

    r_t = agent.shape_reward(r_t, misc, prev_misc, t)

    if (is_terminated):
        life = 0
    else:
        life += 1

    # update the cache
    prev_misc = misc

    # Update epsilon
    if agent.epsilon > agent.final_epsilon and t > agent.observe:
        agent.epsilon -= (agent.initial_epsilon -
                          agent.final_epsilon) / agent.explore

    # Do the training
    if t > agent.observe:
        Q_max, loss = agent.train_replay()

    # save the sample <s, a, r, s'> to episode buffer
    episode_buf.append([s_t, action_idx, r_t, s_t1])

    if (is_terminated):
        agent.memory.add(episode_buf)
        episode_buf = []  # Reset Episode Buf

    s_t = s_t1
    t += 1

    # save progress every 10000 iterations
    if t % 10000 == 0:
        print("Now we save model")
        agent.model.save_weights("./models/drqn.h5", overwrite=True)

    # print info
    state = ""
    if t <= agent.observe:
        state = "observe"
    elif t > agent.observe and t <= agent.observe + agent.explore:
        state = "explore"
    else:
        state = "train"

    if (is_terminated):
        print("TIME", t, "/ GAME", GAME, "/ STATE", state,
              "/ EPSILON", agent.epsilon, "/ ACTION", action_idx, "/ REWARD", r_t,
              "/ Q_MAX %e" % np.max(Q_max), "/ LIFE", max_life, "/ LOSS", loss)

        # Save Agent's Performance Statistics
        if GAME % agent.stats_window_size == 0 and t > agent.observe:
            print("Update Rolling Statistics")
            agent.mavg_score.append(np.mean(np.array(life_buffer)))
            agent.var_score.append(np.var(np.array(life_buffer)))
            agent.mavg_ammo_left.append(np.mean(np.array(ammo_buffer)))
            agent.mavg_kill_counts.append(np.mean(np.array(kills_buffer)))

            # Reset rolling stats buffer
            life_buffer, ammo_buffer, kills_buffer = [], [], []

            # Write Rolling Statistics to file
            with open("statistics/drqn_stats.txt", "w") as stats_file:
                stats_file.write('Game: ' + str(GAME) + '\n')
                stats_file.write('Max Score: ' + str(max_life) + '\n')
                stats_file.write('mavg_score: ' +
                                  str(agent.mavg_score) + '\n')
                stats_file.write(
                    'var_score: ' + str(agent.var_score) + '\n')
                stats_file.write('mavg_ammo_left: ' +
                                  str(agent.mavg_ammo_left) + '\n')
                stats_file.write('mavg_kill_counts: ' +
                                  str(agent.mavg_kill_counts) + '\n')

# + [markdown] id="EFHwTMtjtrpI"
# # Fully observed Markov decision processes (MDPs)
# Now we explore fully observed Markov decision process.
#
# In a fully observable problem the observed state is equal to the hidden state (i.e., $s_{t}=z_{t}$).
#
# In this case, the POMDP reduces to a simpler model known as a Markov decision process or MDP
#

# + [markdown] id="PnFH2ojv7X6i"
#
# ## Actor Critic Method
# As an agent takes actions and moves through an environment, it learns to map the observed state of the environment to two possible outputs:
#
# **Recommended action:**
#
# A probabiltiy value for each action in the action space. The part of the agent responsible for this output is called the actor.
#
#
# **Estimated rewards in the future:**
#
# Sum of all rewards it expects to receive in the future. The part of the agent responsible for this output is the critic.
#
# Agent and Critic learn to perform their tasks, such that the recommended actions from the actor maximize the rewards.
#
#
# **CartPole-V0**
#
# A pole is attached to a cart placed on a frictionless track. The agent has to apply force to move the cart. It is rewarded for every time step the pole remains upright. The agent, therefore, must learn to keep the pole from falling over.

# + id="6UfvC4ni0Zan"
#@title Imports
import gym
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

# Configuration parameters for the whole setup
seed = 42
gamma = 0.99  # Discount factor for past rewards
max_steps_per_episode = 10000
env = gym.make("CartPole-v0")  # Create the environment
env.seed(seed)
eps = np.finfo(np.float32).eps.item()  # Smallest number such that 1.0 + eps != 1.0

# + id="ay_fSFKW0oxp" colab={"base_uri": "https://localhost:8080/", "height": 223} outputId="ff37bd81-65b4-4a63-91c5-c17ef635107a"
#@title Define Model
num_inputs = 4
num_actions = 2
num_hidden = 128

inputs = layers.Input(shape=(num_inputs,))
common = layers.Dense(num_hidden, activation="relu")(inputs)
action = layers.Dense(num_actions, activation="softmax")(common)
critic = layers.Dense(1)(common)

model = keras.Model(inputs=inputs, outputs=[action, critic])

# + id="uHyJ3w6n6Fdn" cellView="form"
#@title Train model
optimizer = keras.optimizers.Adam(learning_rate=0.01)
huber_loss = keras.losses.Huber()
action_probs_history = []
critic_value_history = []
rewards_history = []
running_reward = 0
episode_count = 0

while True:  # Run until solved
    state = env.reset()
    episode_reward = 0
    with tf.GradientTape() as tape:
        for timestep in range(1, max_steps_per_episode):
            # env.render(); Adding this line would show the attempts
            # of the agent in a pop up window.

            state = tf.convert_to_tensor(state)
            state = tf.expand_dims(state, 0)

            # Predict action probabilities and estimated future rewards
            # from environment state
            action_probs, critic_value = model(state)
            critic_value_history.append(critic_value[0, 0])

            # Sample action from action probability distribution
            action = np.random.choice(num_actions, p=np.squeeze(action_probs))
            action_probs_history.append(tf.math.log(action_probs[0, action]))

            # Apply the sampled action in our environment
            state, reward, done, _ = env.step(action)
            rewards_history.append(reward)
            episode_reward += reward

            if done:
                break

        # Update running reward to check condition for solving
        running_reward = 0.05 * episode_reward + (1 - 0.05) * running_reward

        # Calculate expected value from rewards
        # - At each timestep what was the total reward received after that timestep
        # - Rewards in the past are discounted by multiplying them with gamma
        # - These are the labels for our critic
        returns = []
        discounted_sum = 0
        for r in rewards_history[::-1]:
            discounted_sum = r + gamma * discounted_sum
            returns.insert(0, discounted_sum)

        # Normalize
        returns = np.array(returns)
        returns = (returns - np.mean(returns)) / (np.std(returns) + eps)
        returns = returns.tolist()

        # Calculating loss values to update our network
        history = zip(action_probs_history, critic_value_history, returns)
        actor_losses = []
        critic_losses = []
        for log_prob, value, ret in history:
            # At this point in history, the critic estimated that we would get a
            # total reward = `value` in the future. We took an action with log probability
            # of `log_prob` and ended up recieving a total reward = `ret`.
            # The actor must be updated so that it predicts an action that leads to
            # high rewards (compared to critic's estimate) with high probability.
            diff = ret - value
            actor_losses.append(-log_prob * diff)  # actor loss

            # The critic must be updated so that it predicts a better estimate of
            # the future rewards.
            critic_losses.append(
                huber_loss(tf.expand_dims(value, 0), tf.expand_dims(ret, 0))
            )

        # Backpropagation
        loss_value = sum(actor_losses) + sum(critic_losses)
        grads = tape.gradient(loss_value, model.trainable_variables)
        optimizer.apply_gradients(zip(grads, model.trainable_variables))

        # Clear the loss and reward history
        action_probs_history.clear()
        critic_value_history.clear()
        rewards_history.clear()

    # Log details
    episode_count += 1
    if episode_count % 10 == 0:
        template = "running reward: {:.2f} at episode {}"
        print(template.format(running_reward, episode_count))

    if running_reward > 195:  # Condition to consider the task solved
        print("Solved at episode {}!".format(episode_count))
        break

# + [markdown] id="H0hXYOtJ6NbJ"
# ### Visualizations
#
#
# In early stages of training:
#
#
# ![Imgur](https://i.imgur.com/5gCs5kH.gif)
#
#
# In later stages of training:
#
#
# ![Imgur](https://i.imgur.com/5ziiZUD.gif)


# Вступление
# В этом руководстве вы немного узнали об обучении с подкреплением и использовали пакет stable-baselines,
# чтобы научить агента побеждать случайного противника. В этом упражнении вы проверите свое понимание и повозитесь с
# кодом, чтобы углубить свою интуицию.
from learntools.core import binder
binder.bind(globals())
from learntools.game_ai.ex4 import *

# 1) Установите архитектуру
# В этом руководстве вы узнали об одном способе создания нейронной сети, которая может выбирать ходы в Connect Four.
# Нейронная сеть имела выходной слой с семью узлами: по одному на каждый столбец игрового поля.
#
# Допустим, вы хотите создать нейронную сеть, которая может играть в шахматы. Сколько узлов вы должны поместить в
# выходной слой?
#
# Вариант A: 2 узла (количество игроков)
# Вариант B: 16 узлов (количество игровых фишек, с которыми начинает каждый игрок)
# Вариант C: 4672 узла (количество возможных ходов)
# Вариант D: 64 узла (количество квадратов на игровом поле)
# Используйте свой ответ, чтобы установить значение переменной best_option ниже. Ваш ответ должен быть одним из «A», «B», «C» или «D».
#
# Заполнить бланк
best_option = 'C'

# Правильно: если мы используем аналогичную сеть, как в учебнике, сеть должна выводить вероятность для каждого
# возможного хода.

# 2) Определите награду
# В этом руководстве вы узнали, как дать своему агенту награду, которая побудит его выигрывать игры Connect Four.
# Рассмотрим теперь обучение агента для победы в игре «Сапер». Цель игры - очистить доску, не взорвав бомбы.
#
# Чтобы играть в эту игру в поиске Google, нажмите кнопку [Играть] по этой ссылке.
#
# Изображение
# С каждым ходом выполняется одно из следующего:
#
# Агент выбрал неверный ход (другими словами, он попытался раскрыть квадрат, который был обнаружен как часть
# предыдущего хода). Предположим, на этом игра заканчивается, и агент проигрывает.
# Агент очищает квадрат, в котором не было спрятанной мины. Агент побеждает в игре, потому что открываются все
# клетки без мин.
# Агент очищает квадрат, в котором не было спрятанной мины, но который еще не выиграл или не проиграл игру.
# Агент подрывает мину и проигрывает игру.
# Как вы могли бы указать вознаграждение для каждого из этих четырех случаев, чтобы, максимизируя совокупное
# вознаграждение, агент попытался выиграть игру?
#
# После  того, как вы определились со своим ответом, запустите ячейку кода ниже, чтобы получить кредит за ответ на
# этот вопрос.
#
# Решение: вот возможное решение - после каждого хода мы даем агенту награду, которая говорит ему, насколько хорошо
# он справился:
#
# Если агент выигрывает игру этим ходом, он получает награду +1.
# В противном случае, если агент выбирает недопустимый ход, он получает награду -10.
# В противном случае, если он взорвет мину, он получит награду -1.
# В противном случае, если агент очищает квадрат без скрытой мины, он получает награду +1/100.
# Чтобы  проверить правильность своего ответа, обратите внимание, что награда за выбор неверного хода и за подрыв
# мины  должна быть отрицательной. Награда за победу в игре должна быть положительной. И награда за очистку квадрата
# без скрытой мины должна быть либо нулевой, либо слегка положительной.
#
# 3) (Необязательно) Измените код
# В  этой следующей части упражнения вы измените код из учебника, чтобы поэкспериментировать с созданием собственных
# агентов!  Существует множество гиперпараметров, связанных с указанием агента обучения с подкреплением,
# и у вас будет возможность изменить их, чтобы увидеть, как это повлияет на производительность.
#
# Во-первых,  нам нужно убедиться, что ваш блокнот Kaggle настроен для запуска кода. Начните с просмотра меню
# «Настройки» справа от записной книжки. Ваше меню будет выглядеть следующим образом:
#
# Если  ваш параметр «Интернет» отображается как ссылка «Требуется подтверждение по телефону», щелкните по этой
# ссылке.  Это перенесет вас в новое окно; затем следуйте инструкциям, чтобы подтвердить свою учетную запись. После
# выполнения этого шага в настройках «Интернет» будет отображаться значение «Выкл.», Как в примере справа.
#
# Когда  в настройках «Интернет» отображается значение «Выкл.», Нажмите, чтобы включить его. Вы увидите всплывающее
# окно,  в котором вам нужно будет «Принять», чтобы завершить процесс и переключить настройку на «Вкл.». Как только
# Интернет будет включен, вы готовы к работе!
#
# Начните с запуска ячейки кода ниже.
#
#import os
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

!pip install 'tensorflow==1.15.0'

import tensorflow as tf
from kaggle_environments import make, evaluate
from gym import spaces

!apt-get update
!apt-get install -y cmake libopenmpi-dev python3-dev zlib1g-dev
!pip install "stable-baselines[mpi]==2.9.0"

from stable_baselines.bench import Monitor
from stable_baselines.common.vec_env import DummyVecEnv
from stable_baselines import PPO1, A2C, ACER, ACKTR, TRPO
from stable_baselines.a2c.utils import conv, linear, conv_to_fc
from stable_baselines.common.policies import CnnPolicy

class ConnectFourGym:
    def __init__(self, agent2="random"):
        ks_env = make("connectx", debug=True)
        self.env = ks_env.train([None, agent2])
        self.rows = ks_env.configuration.rows
        self.columns = ks_env.configuration.columns
        # Learn about spaces here: http://gym.openai.com/docs/#spaces
        self.action_space = spaces.Discrete(self.columns)
        self.observation_space = spaces.Box(low=0, high=2,
                                            shape=(self.rows,self.columns,1), dtype=np.int)
        # Tuple corresponding to the min and max possible rewards
        self.reward_range = (-10, 1)
        # StableBaselines throws error if these are not defined
        self.spec = None
        self.metadata = None
    def reset(self):
        self.obs = self.env.reset()
        return np.array(self.obs['board']).reshape(self.rows,self.columns,1)
    def change_reward(self, old_reward, done):
        if old_reward == 1: # The agent won the game
            return 1
        elif done: # The opponent won the game
            return -1
        else: # Reward 1/42
            return 1/(self.rows*self.columns)
    def step(self, action):
        # Check if agent's move is valid
        is_valid = (self.obs['board'][int(action)] == 0)
        if is_valid: # Play the move
            self.obs, old_reward, done, _ = self.env.step(int(action))
            reward = self.change_reward(old_reward, done)
        else: # End the game and penalize agent
            reward, done, _ = -10, True, {}
        return np.array(self.obs['board']).reshape(self.rows,self.columns,1), reward, done, _

# Create ConnectFour environment
env = ConnectFourGym(agent2="random")

# Create directory for logging training information
log_dir = "log/"
os.makedirs(log_dir, exist_ok=True)

# Logging progress
monitor_env = Monitor(env, log_dir, allow_early_resets=True)

# Create a vectorized environment
vec_env = DummyVecEnv([lambda: monitor_env])

# Neural network for predicting action values
def modified_cnn(scaled_images, **kwargs):
    activ = tf.nn.relu
    layer_1 = activ(conv(scaled_images, 'c1', n_filters=32, filter_size=3, stride=1,
                         init_scale=np.sqrt(2), **kwargs))
    layer_2 = activ(conv(layer_1, 'c2', n_filters=64, filter_size=3, stride=1,
                         init_scale=np.sqrt(2), **kwargs))
    layer_2 = conv_to_fc(layer_2)
    return activ(linear(layer_2, 'fc1', n_hidden=512, init_scale=np.sqrt(2)))

class CustomCnnPolicy(CnnPolicy):
    def __init__(self, *args, **kwargs):
        super(CustomCnnPolicy, self).__init__(*args, **kwargs, cnn_extractor=modified_cnn)
#

# Initialize agent
model = PPO1(CustomCnnPolicy, vec_env, verbose=0)

# Train agent
model.learn(total_timesteps=100000)

# Plot cumulative reward
with open(os.path.join(log_dir, "monitor.csv"), 'rt') as fh:
    firstline = fh.readline()
    assert firstline[0] == '#'
    df = pd.read_csv(fh, index_col=None)['r']
df.rolling(window=1000).mean().plot()
plt.show()

# Если ваш агент хорошо обучен, график (который показывает средние совокупные вознаграждения) со временем должен
# увеличиваться.
#
# Убедившись, что код работает, попробуйте внести поправки, чтобы посмотреть, сможете ли вы повысить
# производительность. Вы можете:
#
# измените PPO1 на A2C (или ACER, или ACKTR, или TRPO) при определении модели в этой строке кода: model = PPO1 (
# CustomCnnPolicy,  vec_env, verbose = 0). Это позволит вам увидеть, как на производительность может повлиять
# изменение алгоритма  с Proximal Policy Optimization [PPO] на один из:
# Преимущество актер-критик (A2C),
# или актер-критик с опытом воспроизведения (ACER),
# Актер-критик, использующий зону доверия с учетом фактора Кронекера (ACKTR), или
# Оптимизация политики доверенного региона (TRPO).
# измените метод c hange_reward () в классе ConnectFourGym, чтобы изменить вознаграждения, которые агент получает в
# различных условиях.  Вам также может потребоваться изменить self.reward_range в методе __init__ (этот кортеж всегда
# должен соответствовать минимальному и максимальному вознаграждению, которое может получить агент).
# измените agent2 на  другого агента при создании среды ConnectFour с env = ConnectFourGym (agent2 = "random").
# Например, вы можете  использовать агент «negamax» или другой настраиваемый агент. Учтите, что чем умнее вы сделаете
# оппонента, тем сложнее будет обучать вашего агента!


# Copyright (c) 2020, Huawei Technologies.All rights reserved.
#
# Licensed under the BSD 3-Clause License  (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://opensource.org/licenses/BSD-3-Clause
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import torch
import numpy as np
from common_utils import TestCase, run_tests
import unittest
from util_test import create_common_tensor, test_2args_broadcast, create_dtype_tensor, UT_FAST_MODE
from common_device_type import dtypes, instantiate_device_type_tests

class TestDiv(TestCase):
    def get_outputs(self, cpu_args, npu_args, dtype):
        # cpu not support fp16 div
        cpu_args = [i.float() if dtype==torch.half else i for i in cpu_args]
        cpu_output = torch.div(cpu_args[0], cpu_args[1]).to(dtype).numpy()
        npu_output = torch.div(npu_args[0], npu_args[1]).to("cpu").numpy()
        return cpu_output, npu_output

    def get_outputs_chk(self, cpu_args, npu_args, dtype):
        # cpu not support fp16 div
        cpu_out = torch.randn(6).to(dtype)
        npu_out = torch.randn(6).to("npu").to(dtype)
        cpu_args = [i.float() if dtype==torch.half else i for i in cpu_args]
        torch.div(cpu_args[0], cpu_args[1], out = cpu_out)
        torch.div(npu_args[0], npu_args[1], out = npu_out)
        cpu_output = cpu_out.to(dtype).numpy()
        npu_output = npu_out.to("cpu").numpy()
        return cpu_output, npu_output

    def test_div_broadcast(self, device):
        for item in test_2args_broadcast(torch.div):
            self.assertRtolEqual(item[0], item[1])

    # div not support bool
    @dtypes(torch.float, torch.half, torch.int)
    def test_div_dtype(self, device, dtype):
        cpu_input1, npu_input1 = create_dtype_tensor((2,3,4,5), dtype)
        # divisor can not be zero
        cpu_input2, npu_input2 = create_dtype_tensor((2,3,4,5), dtype, no_zero=True)
        cpu_output, npu_output = self.get_outputs([cpu_input1, cpu_input2], [npu_input1, npu_input2], dtype)

        # div 在int结果为负数时采用截断而不是向下取整的方式取整，所以选用numpy比较
        if dtype == torch.int:
            cpu_output = np.floor_divide(cpu_input1.numpy(), cpu_input2.numpy())

        self.assertRtolEqual(cpu_output, npu_output)

    @unittest.skipIf(UT_FAST_MODE, "Run UT in fast mode")
    def test_div_shape_format_fp16(self, device):
        format_list = [0, 3, 29]
        shape_list = [1, (64, 10), (32, 3, 3), (256, 2048, 7, 7)]
        shape_format = [
            [np.float16, i, j] for i in format_list for j in shape_list
        ]
        for item in shape_format:
            cpu_input1, npu_input1 = create_common_tensor(item, 1, 100)
            cpu_input2, npu_input2 = create_common_tensor(item, 1, 100)
            cpu_input1 = cpu_input1.to(torch.float32)
            cpu_input2 = cpu_input2.to(torch.float32)
            cpu_output, npu_output = self.get_outputs([cpu_input1, cpu_input2], [npu_input1, npu_input2], torch.half)
            self.assertRtolEqual(cpu_output, npu_output)

    @unittest.skipIf(UT_FAST_MODE, "Run UT in fast mode")
    def test_div_shape_format_fp32(self, device):
        format_list = [0, 3, 29]
        shape_list = [1, (64, 10), (32, 3, 3), (256, 2048, 7, 7), (2, 0, 2)]
        shape_format = [
            [np.float32, i, j] for i in format_list for j in shape_list
        ]
        for item in shape_format:
            cpu_input1, npu_input1 = create_common_tensor(item, 1, 100)
            cpu_input2, npu_input2 = create_common_tensor(item, 1, 100)
            cpu_output, npu_output = self.get_outputs([cpu_input1, cpu_input2], [npu_input1, npu_input2], torch.float)
            self.assertRtolEqual(cpu_output, npu_output)

    def test_div_mix_dtype_1(self, device):
        npu_input1, npu_input2 = create_common_tensor([np.int32, 0, (2, 3)], 1, 100)
        npu_input3, npu_input4 = create_common_tensor([np.float32, 0, (2, 3)], 1, 100)
        cpu_output, npu_output = self.get_outputs([npu_input1, npu_input3], [npu_input2, npu_input4], torch.float)
        self.assertRtolEqual(cpu_output, npu_output)

    def test_div_mix_dtype_2(self, device):
        npu_input1, npu_input2 = create_common_tensor([np.float32, 0, (2, 3)], 1, 100)
        npu_input3 = torch.tensor(3).int()
        cpu_output, npu_output = self.get_outputs([npu_input1, npu_input3], [npu_input2, npu_input3], torch.float)
        self.assertRtolEqual(cpu_output, npu_output)

    def test_div_scalar_dtype(self, device):
        cpu_input1, npu_input1 = create_common_tensor([np.int32, 0, (2, 3)], 1, 100)
        cpu_output = cpu_input1 / 0.5
        npu_output = npu_input1 / 0.5
        self.assertRtolEqual(cpu_output, npu_output.cpu())

    @unittest.skipIf(UT_FAST_MODE, "Run UT in fast mode")
    def test_div_shape_format_fp32(self, device):
        format_list = [0, 3, 29]
        shape_list = [1, (64, 10), (32, 3, 3), (256, 2048, 7, 7)]
        shape_format = [
            [np.float32, i, j] for i in format_list for j in shape_list
        ]
        for item in shape_format:
            cpu_input1, npu_input1 = create_common_tensor(item, 1, 100)
            cpu_input2, npu_input2 = create_common_tensor(item, 1, 100)
            cpu_output, npu_output = self.get_outputs_chk([cpu_input1, cpu_input2], [npu_input1, npu_input2], torch.float)
            self.assertRtolEqual(cpu_output, npu_output)

instantiate_device_type_tests(TestDiv, globals(), except_for="cpu")
if __name__ == "__main__":
    run_tests()


#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Wed Oct 16 22:26:40 2019

@author: alexis
"""

import pytest
import S3_imgproc_tools as S3
import numpy as np
import cv2

###########################################################################################
##              Partie 1
###########################################################################################

## on effectuent une fonction test pour chaque fonction qui renverrat la couleurs inverser de l'image
## Dans une premier temps on doit charger l'image
## On effectue l'inversison d'une couleur sur un pixel de l'image. Sela nous servirat comme valeurs de test
def test_invert_colors_manual():
    image_couleur=np.array([ np.random.randint(255, size=(3, 3)) ])
    pixel_test=255-image_couleur[0,0]
    S3.invert_colors_manual(image_couleur)
    assert pixel_test.all()==image_couleur[0,0].all()

## on teste si les paramètre passer sont controler
def test_invert_colors_manual_parametre():
    test_parametre="on effectuent un test"
    with pytest.raises(ValueError):
        S3.invert_colors_manual(test_parametre)
## test si la fonction utilisant la bilbiothèque numpy fonctionne
def test_invert_colors_numpy():
    image_couleur=np.array([ np.random.randint(255, size=(3, 3)) ])
    pixel_test=255-image_couleur[0,0]
    S3.invert_colors_numpy(image_couleur)
    assert pixel_test.all()==image_couleur[0,0].all()

## on teste si les paramètre passer sont controler
def test_invert_colors_numpy_parametre():
    test_parametre="on effectuent un test"
    with pytest.raises(ValueError):
        S3.invert_colors_numpy(test_parametre)

## test si la fonction utilisant la bilbiothèque opencv fonctionne
def test_invert_colors_opencv():
    image_couleur=np.array([ np.random.randint(255, size=(3, 3)) ])
    pixel_test=255-image_couleur[0,0]
    S3.invert_colors_opencv(image_couleur)
    assert pixel_test.all()==image_couleur[0,0].all()
## on teste si les paramètre passer sont controler
def test_invert_colors_opencv_parametre():
    test_parametre="on effectuent un test"
    with pytest.raises(ValueError):
        S3.invert_colors_opencv(test_parametre)

## long et fastidieu nous répétont beaucoups de code avec la versions précédente.
## Il serai par conséquent beaucoups plus judicieux d'effectuer un bloque fonctionelle appelant
## toutes les fonction à la fois et nous renvoyant un entier correspondant au nombre de fonction qui on réussis
## pour ce faire nosu fessons appel à la fonction test_image du fichier S3_imgproc_tool.py

def test_pixel_inverser():
    image_couleur=np.array([ np.random.randint(255, size=(3, 3)) ])
    image_inverser=S3.invert_colors_opencv(image_couleur)
    resultat=S3.test_pixel(image_couleur,image_inverser)
    assert resultat==0

## Cette fonction nous permet de teste la bonne exécutions de tous nos bloque fonctionelles
def test_all_function():
    image_couleur=np.array([ np.random.randint(255, size=(3, 3)) ])
    compteur_test=S3.test_image2(image_couleur)
    assert compteur_test==3

## on teste si les paramètre passer sont controler
def test_all_function_parametre():
    test_parametre="on effectuent un test"
    with pytest.raises(ValueError):
        S3.test_image2(test_parametre)

## Nous souhaitons obtenir le temps d"éxécutions de nos fonctions d'inversions
## de couleurs sur des images de taille differentes

def test_time_image_parametre():
    test_parametre="on effectuent un test"
    with pytest.raises(ValueError):
        S3.time_image(test_parametre)

def test_delai_execution_comparer():
    time=S3.delai_execution_comparer()
    assert 1== isinstance(time, list)

###########################################################################################
##              Partie 2
###########################################################################################

def test_threshold_image_manual():
    imagee=cv2.imread("../S3_image_test/480.jpg")
    image_nb=S3.threshold_image_manual(imagee)
    assert 255 in image_nb
    assert 0 in image_nb

## on teste si les paramètre passer sont controler
def test_threshold_image_manual_parametre():
    test_parametre="on effectuent un test"
    with pytest.raises(ValueError):
        S3.threshold_image_manual(test_parametre)

## on teste si les paramètre passer sont controler
def test_threshold_image_numpy_parametre():
    test_parametre="on effectuent un test"
    with pytest.raises(ValueError):
        S3.threshold_image_numpy(test_parametre)

def test_threshold_image_numpy():
    imagee=cv2.imread("../S3_image_test/480.jpg")
    img=S3.threshold_image_numpy(imagee)
    assert 255 in img
    assert 0 in img

def threshold_colors_opencv():
    n=np.arange(0,255)
    imagee=cv2.imread("../S3_image_test/480.jpg")
    image_nb=S3.threshold_colors_opencv(imagee)
    assert image_nb.all() in n.all()
## on teste si les paramètre passer sont controler
def threshold_colors_opencv_parametre():
    test_parametre="on effectuent un test"
    with pytest.raises(ValueError):
        S3.threshold_colors_opencv(test_parametre)

def time_image_noir_blanc_parametre():
    test_parametre="on effectuent un test"
    with pytest.raises(ValueError):
        S3.time_image_noir_blanc(test_parametre)

def test_alexis():
    test_retour=S3.delai_execution_comparer_image_noir_blanc()
    assert 1== isinstance(test_retour, list)


#!/usr/bin/env python
# Copyright 2014-2018 The PySCF Developers. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest
import tempfile
from functools import reduce
import numpy
from pyscf import gto
from pyscf import lib

class KnownValues(unittest.TestCase):
    def test_parse_pople(self):
        self.assertEqual(gto.basis._parse_pople_basis('631g(d)', 'C'),
                         ('pople-basis/6-31G.dat', 'pople-basis/6-31G-polarization-d.dat'))
        self.assertEqual(gto.basis._parse_pople_basis('631g**', 'C'),
                         ('pople-basis/6-31Gss.dat',))
        self.assertEqual(gto.basis._parse_pople_basis('631++g**', 'C'),
                         ('pople-basis/6-31++Gss.dat',))
        self.assertRaises(KeyError, gto.basis._parse_pople_basis, '631g++', 'C')

    def test_basis_load(self):
        self.assertEqual(gto.basis.load(__file__, 'H'), [])
        self.assertRaises(KeyError, gto.basis.load, 'abas', 'H')
        #self.assertRaises(RuntimeError, gto.basis.load(__file__, 'C'), [])

        self.assertEqual(len(gto.basis.load('631++g**', 'C')), 8)
        self.assertEqual(len(gto.basis.load('ccpcvdz', 'C')), 7)

        basdat = gto.basis.load('minao', 'C') + gto.basis.load('sto3g', 'C')
        basdat1 = gto.basis.parse_nwchem.parse(
            gto.basis.parse_nwchem.convert_basis_to_nwchem('C', basdat), 'C')
        bas = []
        for b in sorted(basdat, reverse=True):
            b1 = b[:1]
            for x in b[1:]:
                b1.append(list(x))
            bas.append(b1)
        bas = [b for b in bas if b[0]==0] + [b for b in bas if b[0]==1]
        self.assertEqual(bas, basdat1)

    def test_basis_load_ecp(self):
        self.assertEqual(gto.basis.load_ecp(__file__, 'H'), [])

    def test_parse_basis(self):
        basis_str = '''
#BASIS SET: (6s,3p) -> [2s,1p]
C    S
     71.6168370              0.15432897
     13.0450960              0.53532814
#
      3.5305122              0.44463454
C    SP
      2.9412494             -0.09996723             0.15591627
      0.6834831              0.39951283             0.60768372
      0.2222899              0.70011547             0.39195739       '''
        self.assertRaises(KeyError, gto.basis.parse_nwchem.parse, basis_str, 'O')
        basis_dat = gto.basis.parse_nwchem.parse(basis_str)
        self.assertEqual(len(basis_dat), 3)

    def test_parse_ecp(self):
        ecp_str = '''
#
Na nelec 10
Na ul
1    175.5502590            -10.0000000
2     35.0516791            -47.4902024
#
2      7.9060270            -17.2283007

Na S
0    243.3605846              3.0000000
1     41.5764759             36.2847626
2     13.2649167             72.9304880
Na P
0   1257.2650682              5.0000000
1    189.6248810            117.4495683
2     54.5247759            423.3986704
'''
        ecpdat = gto.basis.parse_nwchem.parse_ecp(ecp_str, 'Na')
        self.assertEqual(ecpdat[0], 10)
        self.assertEqual(len(ecpdat[1]), 3)
        ecpdat1 = gto.basis.parse_nwchem.parse_ecp(ecp_str)
        self.assertEqual(ecpdat, ecpdat1)

        ecpdat1 = gto.basis.parse_nwchem.parse_ecp(
            gto.basis.parse_nwchem.convert_ecp_to_nwchem('Na', ecpdat), 'Na')
        self.assertEqual(ecpdat, ecpdat1)

    def test_optimize_contraction(self):
        bas = gto.parse(r'''
#BASIS SET: (6s,3p) -> [2s,1p]
        C    S
              2.9412494             -0.09996723
              0.6834831              0.39951283
              0.2222899              0.70011547
        C    S
              2.9412494             0.15591627
              0.6834831             0.60768372
              0.2222899             0.39195739
                                    ''', optimize=True)
        self.assertEqual(len(bas), 1)

        bas = [[1, 0,
                [2.9412494, -0.09996723],
                [0.6834831,  0.39951283],
                [0.2222899,  0.70011547]],
               [1, 1,
                [2.9412494, -0.09996723],
                [0.6834831,  0.39951283],
                [0.2222899,  0.70011547]],
               [1, 1,
                [2.9412494,  0.15591627],
                [0.6834831,  0.60768372],
                [0.2222899,  0.39195739]]]
        bas = gto.basis.parse_nwchem.optimize_contraction(bas)
        self.assertEqual(len(bas), 2)

    def test_remove_zero(self):
        bas = gto.parse(r'''
        C    S
        7.2610457926   0.0000000000   0.0000000000
        2.1056583087   0.0000000000   0.0000000000
        0.6439906571   1.0000000000   0.0000000000
        0.0797152017   0.0000000000   1.0000000000
        0.0294029590   0.0000000000   0.0000000000
                                    ''')
        self.assertEqual(len(bas[0]), 3)

        bas = [[0, 0,
                [7.2610457926,  0.0000000000,  0.0000000000],
                [2.1056583087,  0.0000000000,  0.0000000000],
                [0.6439906571,  1.0000000000,  0.0000000000],
                [0.0797152017,  0.0000000000,  1.0000000000],
                [0.0294029590,  0.0000000000,  0.0000000000]]]
        bas = gto.basis.parse_nwchem.remove_zero(bas)
        self.assertEqual(len(bas[0]), 4)

if __name__ == "__main__":
    print("test basis module")
    unittest.main()


import numpy as np

items = np.asarray(['CIA cover-up',\
        'reptilians',\
        'Soviets',\
        'awesome background music',\
        'assurances of person\'s intelligence',\
        '\"with so many stories, it must be true!\"',\
        'story told many years after the occurrence',\
        '\"the (x) effect\"',\
        'calling a crackpot a \"researcher\"',
        'calling a wild story a \"report\"',\
        'non-paranormal news story about sex',\
        'impossible physics explanation',\
        '\"but if [absurd hypothetical] WERE true...\"',
        'ouija board',\
        'seance',\
        'Martians',\
        'aliens from a specific star',\
        'speculation on our future robot overlords',\
        'legit university or journal mentioned',\
        'paranormal conference or convention',\
        'personal story told during ad break',\
        '\"ancient knowledge\" invoked',\
        'dream retold in improbable detail',\
        'mysteriously destroyed evidence',\
        '\"High Strangeness\"',\
        'expert whose qualifications consist of grad school',\
        'apocalypse prediction',\
        'uncomfortably detailed sex fantasies',\
        'wild extrapolation of future technology',\
        'Japanese sex bots',\
        '\"intriguing\" used as a substitute for \"batshit insane\"',\
        'poorly designed science experiment',\
        'casual racism :(',\
        'archangel Uriel',\
        '\"compelling evidence\"',\
        '\"as you know, [thing no normal person knows about]\"',\
        'evidence against paranormal explanation interpreted as proof',\
        'NASA',\
        'best story is in the Plus extension',\
        'cats',\
        'Iceland',\
        'crop circles',\
        'Ancient Aliens',\
        'evolutionary psychology',\
        'government mind control',\
        'Big Brother is watching',\
        'time travel'])


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

# # Telco Customer Churn
#
# ## Part 1
# ### Data Cleaning
# - [Cleaning](#cleaning)
#
# ## Part 2
# ### Exploratory Analysis
# - [Exploratory Analysis](#explo)
#
# For EDA, I do some very simple exploratory analysis to try and pull out interesting factors that may be affecting customer churn.
#
#
#
# ## Part 3
# - [Machine Learning](#ml)
#
# I then fit a decision tree classifier and a random forest classifier to classify the data with highest possible accuracy without overfitting (maintaing a good bias variance tradeoff !)
#
#
#    - Decision Tree Classifier
#    - Random Forest Classifier
#    - Logistic Regression
#
#
# ## Recommendation
#
# ### Demographic
# - One thing I noted is that individuals without partners/dependents are more likely to leave the company. In my opinion, individuals 'with' partners and dependents are more likely  to remain as most telecom companies offer special deals for families, couples etc.. As a recommendation, I would recommend the telecom company to maybe have a special promotion/marketing campaign targeted towards individuals without partners/dependents.
#
#
# ### Service-Specific
# - Based off the feature importances and the EDA, I can conclude that the company needs to relaunch/remodel their FiberOptic Service.
#
#
# - On the other hand, a lot of customers that are leaving the company seem to also not be registered for any support-like services (security, protection etc..) I would recommend the company to start offering these as bundles with services as customers who are signed up for these security-like services are more likely to remain with the company. (or make it mandatory to have these or just make them included)
#
# ### Payment-Specific
# - No recommendations

# In[ ]:

import pandas as pd
import os
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
import warnings
from sklearn import tree
from sklearn.model_selection import train_test_split
from sklearn.externals.six import StringIO
from IPython.display import Image
from sklearn.tree import export_graphviz
import pydotplus
from sklearn.model_selection import cross_val_score
from sklearn.metrics import confusion_matrix
from sklearn.metrics import f1_score
from sklearn import metrics
from sklearn.model_selection import RandomizedSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
import warnings

# In[ ]:

df = pd.read_csv("../../../input/blastchar_telco-customer-churn/WA_Fn-UseC_-Telco-Customer-Churn.csv")
df.head(5)

# The dataset is very clean, there are no null values. Here, I replaced the binary outcome variable from Yes and No into 1 and 0.

#

# ### Cleaning

# In[ ]:

print(df.isnull().sum())
df['Churn'].replace('Yes',1,inplace=True)
df['Churn'].replace('No',0,inplace=True)

# Farther down, I ran into an issue with the Total Charges Column as highlighted below. In the next few cells, I just adjust this column by removing these empty strings and reformatting the column to a numeric type

# In[ ]:

#There is an issue with the Total Charges colummns (the data is stored as a string)
print(' The data type for the Total Charges Column is:',type(df['TotalCharges'].loc[4]))
#While attempting to convert this to a numeric type, ran into another problem at some positions,empty strings
print(df['TotalCharges'][(df['TotalCharges'] == ' ')])

# In[ ]:

# Drop rows where there is no value for Total Charges
index = [488,753,936,1082,1340,3331,3826,4380,5218,6670,6754]
for i in index:
    df.drop(i,axis=0,inplace=True)

# In[ ]:

# Convert from str to float
df['TotalCharges'].apply(float)

#
#

# ### Exploratory Analysis
#
# - For my EDA, I split up the data into 3 categories. The first categroy is demographic which contains features like gender, partner etc..

#    #### Demographic
# Few things that stood out that seem to be quite different at the demographic level:
# - Number of Senior Citizens
# - Number of Individuals with Dependents
#
# In terms of churn, one interesting thing stands out, is that although the distribution of individuals with partners is equal, individuals without a partner are more likely to leave the company while individuals with a partner are more likely to remain as customers. This is a factor that is out of the company's control
#
#

# In[ ]:

# Inspecting frequency in the different demographic variables that are not related to the service
dem = ['gender','SeniorCitizen','Partner','Dependents']

for i in dem:
    sns.barplot(x = df.groupby(str(i))['Churn'].sum().reset_index()[str(i)]                , y = df.groupby(str(i))['Churn'].sum().reset_index()['Churn'],)
    print()
    print(df.groupby(str(i))['customerID'].count().reset_index())

#  #### Service-Specific
#
# - One thing that stood out may seem to uncover a service that may be causing high churn rates. Looking at the Internet Service, it seems that people with Fiber Optic service are more likely to leave the company. Although individuals with fiber optic services make up a large proportion of internet service customers, DSL customers have a much lower churn rate and make up almost the same number of individuals/customers.
#    - Proportion of individuals with DSL leaving the company 459/2416 * 100 = 19%
#    - Proportion of individuals with Fiber Optic leaving the company 1297/3096 * 100 = 42%
#
#
# - OnlineSecurity seems to be another factor causing high churn rates
#   - Proportion of individuals with Online Security leaving the company = 295/3497 *100 = 8.4%
#   - Proportion of individuals without Online Security leaving the company = 1461/2015*100= 73%
#
#
# - OnlineBackup,Device Protection and Tech Support also follow the same pattern as online security where individuals without these services are more likely to leave the company.
#
#
#
# ##### Streaming Services
#   - More than 40% of individuals with streaming TV and streaming movies service are also unsubscribing which may indicate that the streaming services could also be improved

# In[ ]:

cat = ['PhoneService','MultipleLines','InternetService','OnlineSecurity','OnlineBackup','DeviceProtection','TechSupport','StreamingTV','StreamingMovies']

for i in cat:
    sns.barplot(x = df.groupby(str(i))['Churn'].sum().reset_index()[str(i)]                , y = df.groupby(str(i))['Churn'].sum().reset_index()['Churn'],)
    print()
    print(df.groupby(str(i))['customerID'].count().reset_index())

# #### Payment-Specific
#    - One thing that stood out was that the majority of customers pay for Telco services on a month to month contract basis.
#
#

# In[ ]:

pay = ['Contract','PaperlessBilling','PaymentMethod']

for i in pay:
    sns.barplot(x = df.groupby(str(i))['Churn'].sum().reset_index()[str(i)]                , y = df.groupby(str(i))['Churn'].sum().reset_index()['Churn'])
    print()
    print(df.groupby(str(i))['customerID'].count().reset_index())

#

#

# ### Machine Learning
# - Start off converting variables to their appropriate format. Convert Binary Variables to 1 for Yes and 0 for no. For variables with more than 2 categories I used pd.get_dummies.

# In[ ]:

# Convert Binary Categories to 0's and 1's
df['Partner'].replace('Yes',1,inplace=True)
df['Partner'].replace('No',0,inplace=True)
df['Dependents'].replace('Yes',1,inplace=True)
df['Dependents'].replace('No',0,inplace=True)
df['gender'].replace('Male',1,inplace=True)
df['gender'].replace('Female',0,inplace=True)
df['PhoneService'].replace('Yes',1,inplace=True)
df['PhoneService'].replace('No',0,inplace=True)
df['PaperlessBilling'].replace('Yes',1,inplace=True)
df['PaperlessBilling'].replace('No',0,inplace=True)

# In[ ]:

## Prepare Categorical Variables with more than 2 categories
cat_X = df[['MultipleLines','InternetService','OnlineSecurity','OnlineBackup',            'DeviceProtection','TechSupport','StreamingTV','StreamingMovies',           'Contract','PaymentMethod']]
# Dummy Categorical Variables
for i in cat_X:
    cat_X = pd.concat([cat_X,pd.get_dummies(cat_X[str(i)],                                            drop_first=True,prefix=str(i))],axis=1)

cat_X = cat_X.drop(columns=['MultipleLines','InternetService','OnlineSecurity','OnlineBackup',            'DeviceProtection','TechSupport','StreamingTV','StreamingMovies',           'Contract','PaymentMethod'])

# In[ ]:

features = pd.concat([df[['tenure','Partner','Dependents','gender','PhoneService',                          'PaperlessBilling','MonthlyCharges','TotalCharges']],cat_X],axis=1)

# The first tree is just a proof of concept to get an idea of which features really minimize confusion during the learning process for the tree (gini impurity).
#
#    - Tenure and Fiber Optic Services seem to be really important features to classify whether a customer will leave or remain.

# ### Decision Tree Classifier

# In[ ]:

# Used stratified split as the classes are imbalanced
X=features
y= df['Churn']
X_train, X_test, y_train, y_test = train_test_split(features, df['Churn'],                                                     test_size=0.33, random_state=42,stratify=y)
my_DT = tree.DecisionTreeClassifier(max_depth=3)
my_DT.fit(X_train, y_train)
dot_data = StringIO()
export_graphviz(my_DT, out_file=dot_data,feature_names=features.columns,filled=True, rounded=True,special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
Image(graph.create_png())

# Cross-Validation to find optimal value for the max_depth

# In[ ]:

X = features
y = df['Churn']
depth_range = np.arange(1,50,1)
val_scores = []
for d in depth_range:
    my_DT = tree.DecisionTreeClassifier(max_depth=d)
    scores = cross_val_score(my_DT, X, y, cv=10, scoring='accuracy')
    val_scores.append(scores.mean())
print(val_scores)

# In[ ]:

#Plot results from cross-validation
fig,(ax1,ax2) = plt.subplots(ncols=2,figsize=(10,5))
ax1.plot(depth_range, val_scores)
ax1.set_xlabel('Max_Depth Values')
ax1.set_ylabel('Cross-Validated Accuracy Scores')

# A more zoomed in version of the first plot
ax2.plot(depth_range,val_scores)
ax2.set_xlim(1,15)
ax2.set_xlabel('Max_Depth Values')
ax2.set_ylabel('Cross-Validated Accuracy Scores')

# Decision Tree with Optimized Hyperparameters

# In[ ]:

my_DT = tree.DecisionTreeClassifier(max_depth=3)
my_DT.fit(X_train,y_train)
print(my_DT.score(X_train,y_train))
print(my_DT.score(X_test,y_test))

# In[ ]:

# What are the 10 most important features for classification ?
imp = pd.DataFrame(my_DT.feature_importances_).sort_values(by=0,ascending=False).head(10).index.values
imp_vals = pd.DataFrame(my_DT.feature_importances_).sort_values(by=0,ascending=False).head(10)

for i,j in zip(imp,imp_vals[0]):
    print(features.columns[i],j)

# #### Confusion Matrix Decision Tree Classifier
#   - The x axis shows the predicted values and the y axis show the true class values. On the top right we have the "false positives" which are the X values that are actually 0s but were classified (predicted) as 1s. On the bottom left we have the "false negatives" which are values that are actually 1s (churn) but were classified (predicted) as 0s.
#
#   - The F1 score shown above the confusion matrix represents the harmonic mean of precision and recall. It receives equal contribution from precision and recall, hence the higher the score (closer to 1) the better the model is at classifying.

# In[ ]:

y_pred = my_DT.predict(X_test)
def cm(pred):
    cm = confusion_matrix(y_test, pred)
    fig = plt.plot(figsize=(8,5))
    plt.xlabel('Predicted')
    plt.ylabel('Actual')
    print(f1_score(y_test,pred))
    return print()
cm(y_pred)

# #### ROC-AUC Decision Tree Classifier
#  - The red line (random predictor) is used as a baseline to see whether the model is useful.
#  - The blue line demonstrates the TPR and FPR at varying thresholds.
#  - The greater the Area under the Curve (AUC), the better the model is at classifying.

# In[ ]:

y_proba_DT = my_DT.predict_proba(X_test)

# In[ ]:

def roc_auc(prediction,model):
    fpr, tpr, thresholds = metrics.roc_curve(y_test,prediction)
    auc = metrics.auc(fpr, tpr)

    plt.title('Receiver Operating Characteristic '+str(model))
    plt.plot(fpr, tpr, color='blue', label = 'AUC = %0.2f' % auc)
    plt.legend(loc = 'lower right')
    plt.plot([0, 1], [0, 1],'--',color='red')
    plt.xlim([0, 1])
    plt.ylim([0, 1])
    plt.ylabel('True Positive Rate')
    plt.xlabel('False Positive Rate')
    return print()

# In[ ]:

roc_auc(y_proba_DT[:, 1],'Decision Tree Classifier')

# ### Random Forest Regressor

# In[ ]:

from sklearn.ensemble import RandomForestClassifier
RF = RandomForestClassifier()
RF.fit(X_train,y_train)
print(RF.score(X_train,y_train))
print(RF.score(X_test,y_test))

# In[ ]:

warnings.filterwarnings('ignore')
# Number of trees in random forest
n_estimators = np.arange(10,1000,10)
# Number of features to consider at every split
# Maximum number of levels in tree
max_depth = np.arange(1,25,2)
# Minimum number of samples required to split a node
min_samples_split = [2,4,8]
# Minimum number of samples required at each leaf node
min_samples_leaf = [1, 2, 4]
# Method of selecting samples for training each tree
bootstrap = [True, False]
# Create the random grid
grid = {'n_estimators': n_estimators,'max_depth': max_depth,'min_samples_split': min_samples_split,'min_samples_leaf': min_samples_leaf,'bootstrap': bootstrap}
rf_random = RandomizedSearchCV(estimator = RF, param_distributions = grid, n_iter = 100, cv = 3, verbose=2, random_state=42, n_jobs = -1)
# Fit the random search model
rf_random.fit(X_train, y_train)

# In[ ]:

print(rf_random.best_score_)
print(rf_random.best_params_)

# In[ ]:

RFR=RandomForestClassifier(bootstrap= True, max_depth= 11, min_samples_split= 2, n_estimators=30,min_samples_leaf= 4)
RFR.fit(X_train,y_train)
print(RFR.score(X_train,y_train))
print(RFR.score(X_test,y_test))
y_pred_rf = RFR.predict(X_test)

# The random forest scores slightly better and brings down the number of false negatives from 419 to 321 but .. results in twice the amount of false positives.

# #### Confusion Matrix Random Forest Classifier

# In[ ]:

cm(y_pred_rf)

# #### ROC-AUC Random Forest Classifier

# In[ ]:

y_proba_rf = RFR.predict_proba(X_test)

# In[ ]:

roc_auc(y_proba_rf[:,1],'Random Forest Classifier')

# ### Logistic Regression

# In[ ]:

lr = LogisticRegression()
print(lr.fit(X_train,y_train))
print(lr.score(X_train,y_train))
print(lr.score(X_test,y_test))

# In[ ]:

penalty = ['l1', 'l2']
C = np.logspace(0, 4, 10)
hyperparameters = dict(C=C, penalty=penalty)
clf = GridSearchCV(lr, hyperparameters, cv=5, verbose=0)
grid_model = clf.fit(X_train, y_train)

# In[ ]:

print('Best Penalty:', grid_model.best_estimator_.get_params()['penalty'])
print('Best C:', grid_model.best_estimator_.get_params()['C'])

# In[ ]:

print(grid_model.score(X_train,y_train))
print(grid_model.score(X_test,y_test))
y_pred_lr = grid_model.predict(X_test)

# #### ROC-AUC Curve Logistic Regression

# In[ ]:

cm(y_pred_lr)

# #### ROC-AUC Curve Logistic Regression

# In[ ]:

y_proba_lr = grid_model.predict_proba(X_test)

# In[ ]:

roc_auc(y_proba_lr[:,1],'Logistic Regression')